Переглянути джерело

feat(core): Create improved error-handling infrastructure

This commit introduces a new graphql-code-generator plugin which generates error classes and supporting code to enable error result union types as described in #437
Michael Bromley 5 роки тому
батько
коміт
0c0a7b2078
33 змінених файлів з 1682 додано та 1022 видалено
  1. 7 7
      package.json
  2. 135 112
      packages/admin-ui/src/lib/core/src/common/generated-types.ts
  3. 21 0
      packages/admin-ui/src/lib/core/src/common/introspection-result.ts
  4. 3 0
      packages/admin-ui/src/lib/core/src/data/definitions/product-definitions.ts
  5. 2 0
      packages/admin-ui/src/lib/order/src/public_api.ts
  6. 15 13
      packages/asset-server-plugin/e2e/asset-server-plugin.e2e-spec.ts
  7. 47 12
      packages/asset-server-plugin/e2e/graphql/generated-e2e-asset-server-plugin-types.ts
  8. 9 2
      packages/common/src/generated-shop-types.ts
  9. 125 102
      packages/common/src/generated-types.ts
  10. 57 41
      packages/core/e2e/asset.e2e-spec.ts
  11. 191 167
      packages/core/e2e/graphql/generated-e2e-admin-types.ts
  12. 148 109
      packages/core/e2e/graphql/generated-e2e-shop-types.ts
  13. 6 3
      packages/core/e2e/graphql/shared-definitions.ts
  14. 15 0
      packages/core/src/api/config/configure-graphql-module.ts
  15. 29 0
      packages/core/src/api/config/generate-error-code-enum.ts
  16. 12 3
      packages/core/src/api/middleware/asset-interceptor-plugin.ts
  17. 7 3
      packages/core/src/api/resolvers/admin/asset.resolver.ts
  18. 10 1
      packages/core/src/api/schema/admin-api/asset.api.graphql
  19. 9 0
      packages/core/src/api/schema/common/common-types.graphql
  20. 48 0
      packages/core/src/common/error/generated-graphql-admin-errors.ts
  21. 17 0
      packages/core/src/common/error/generated-graphql-shop-errors.ts
  22. 1 0
      packages/core/src/common/index.ts
  23. 1 1
      packages/core/src/data-import/providers/asset-importer/asset-importer.ts
  24. 6 6
      packages/core/src/plugin/default-search-plugin/search-strategy/search-strategy-utils.ts
  25. 10 4
      packages/core/src/service/services/asset.service.ts
  26. 92 35
      packages/elasticsearch-plugin/e2e/graphql/generated-e2e-elasticsearch-plugin-types.ts
  27. 0 0
      schema-admin.json
  28. 0 0
      schema-shop.json
  29. 22 4
      scripts/codegen/generate-graphql-types.ts
  30. 2 0
      scripts/codegen/plugins/.gitignore
  31. 200 0
      scripts/codegen/plugins/graphql-errors-plugin.ts
  32. 12 0
      scripts/codegen/plugins/tsconfig.json
  33. 423 397
      yarn.lock

+ 7 - 7
package.json

@@ -13,7 +13,7 @@
     "docs:watch": "concurrently --restart-tries 5 -n docgen,hugo,webpack -c green,blue,cyan \"yarn generate-graphql-docs && yarn generate-typescript-docs -w\" \"cd docs && hugo server\" \"cd docs && yarn webpack -w\"",
     "docs:build": "yarn generate-graphql-docs && yarn generate-typescript-docs && cd docs && yarn webpack --prod && node build.js && hugo",
     "docs:deploy": "cd docs && yarn && cd .. && yarn docs:build",
-    "codegen": "ts-node scripts/codegen/generate-graphql-types.ts",
+    "codegen": "tsc -p scripts/codegen/plugins && ts-node scripts/codegen/generate-graphql-types.ts",
     "generate-typescript-docs": "ts-node scripts/docs/generate-typescript-docs.ts",
     "generate-graphql-docs": "ts-node scripts/docs/generate-graphql-docs.ts --api=shop && ts-node scripts/docs/generate-graphql-docs.ts --api=admin",
     "version": "yarn check-imports && yarn build && yarn generate-changelog && git add CHANGELOG.md && git add */version.ts",
@@ -29,12 +29,12 @@
   "devDependencies": {
     "@commitlint/cli": "^8.2.0",
     "@commitlint/config-conventional": "^8.2.0",
-    "@graphql-codegen/add": "1.13.1",
-    "@graphql-codegen/cli": "1.13.1",
-    "@graphql-codegen/fragment-matcher": "1.13.1",
-    "@graphql-codegen/typescript": "1.13.1",
-    "@graphql-codegen/typescript-compatibility": "1.13.1",
-    "@graphql-codegen/typescript-operations": "1.13.1",
+    "@graphql-codegen/add": "2.0.1",
+    "@graphql-codegen/cli": "1.17.8",
+    "@graphql-codegen/fragment-matcher": "1.17.8",
+    "@graphql-codegen/typescript": "1.17.9",
+    "@graphql-codegen/typescript-compatibility": "2.0.0",
+    "@graphql-codegen/typescript-operations": "1.17.8",
     "@types/graphql": "^14.0.5",
     "@types/jest": "^25.1.4",
     "@types/klaw-sync": "^6.0.0",

Різницю між файлами не показано, бо вона завелика
+ 135 - 112
packages/admin-ui/src/lib/core/src/common/generated-types.ts


+ 21 - 0
packages/admin-ui/src/lib/core/src/common/introspection-result.ts

@@ -278,6 +278,27 @@ const result: IntrospectionResultData = {
                     },
                 ],
             },
+            {
+                kind: 'UNION',
+                name: 'CreateAssetResult',
+                possibleTypes: [
+                    {
+                        name: 'Asset',
+                    },
+                    {
+                        name: 'MimeTypeError',
+                    },
+                ],
+            },
+            {
+                kind: 'INTERFACE',
+                name: 'ErrorResult',
+                possibleTypes: [
+                    {
+                        name: 'MimeTypeError',
+                    },
+                ],
+            },
         ],
     },
 };

+ 3 - 0
packages/admin-ui/src/lib/core/src/data/definitions/product-definitions.ts

@@ -371,6 +371,9 @@ export const CREATE_ASSETS = gql`
     mutation CreateAssets($input: [CreateAssetInput!]!) {
         createAssets(input: $input) {
             ...Asset
+            ... on ErrorResult {
+                message
+            }
         }
     }
     ${ASSET_FRAGMENT}

+ 2 - 0
packages/admin-ui/src/lib/order/src/public_api.ts

@@ -1,7 +1,9 @@
 // This file was generated by the build-public-api.ts script
 export * from './components/cancel-order-dialog/cancel-order-dialog.component';
 export * from './components/fulfill-order-dialog/fulfill-order-dialog.component';
+export * from './components/fulfillment-card/fulfillment-card.component';
 export * from './components/fulfillment-detail/fulfillment-detail.component';
+export * from './components/fulfillment-state-label/fulfillment-state-label.component';
 export * from './components/line-fulfillment/line-fulfillment.component';
 export * from './components/line-refunds/line-refunds.component';
 export * from './components/order-custom-fields-card/order-custom-fields-card.component';

+ 15 - 13
packages/asset-server-plugin/e2e/asset-server-plugin.e2e-spec.ts

@@ -63,8 +63,8 @@ describe('AssetServerPlugin', () => {
         const { createAssets }: CreateAssets.Mutation = await adminClient.fileUploadMutation({
             mutation: CREATE_ASSETS,
             filePaths: filesToUpload,
-            mapVariables: (filePaths) => ({
-                input: filePaths.map((p) => ({ file: null })),
+            mapVariables: filePaths => ({
+                input: filePaths.map(p => ({ file: null })),
             }),
         });
 
@@ -188,7 +188,7 @@ describe('AssetServerPlugin', () => {
         let testImages: CreateAssets.CreateAssets[] = [];
 
         async function testMimeTypeOfAssetWithExt(ext: string, expectedMimeType: string) {
-            const testImage = testImages.find((i) => i.source.endsWith(ext))!;
+            const testImage = testImages.find(i => i.source.endsWith(ext))!;
             const result = await fetch(testImage.source);
             const contentType = result.headers.get('Content-Type');
 
@@ -198,12 +198,12 @@ describe('AssetServerPlugin', () => {
         beforeAll(async () => {
             const formats = ['gif', 'jpg', 'png', 'svg', 'tiff', 'webp'];
 
-            const filesToUpload = formats.map((ext) => path.join(__dirname, `fixtures/assets/test.${ext}`));
+            const filesToUpload = formats.map(ext => path.join(__dirname, `fixtures/assets/test.${ext}`));
             const { createAssets }: CreateAssets.Mutation = await adminClient.fileUploadMutation({
                 mutation: CREATE_ASSETS,
                 filePaths: filesToUpload,
-                mapVariables: (filePaths) => ({
-                    input: filePaths.map((p) => ({ file: null })),
+                mapVariables: filePaths => ({
+                    input: filePaths.map(p => ({ file: null })),
                 }),
             });
 
@@ -239,13 +239,15 @@ describe('AssetServerPlugin', () => {
 export const CREATE_ASSETS = gql`
     mutation CreateAssets($input: [CreateAssetInput!]!) {
         createAssets(input: $input) {
-            id
-            name
-            source
-            preview
-            focalPoint {
-                x
-                y
+            ... on Asset {
+                id
+                name
+                source
+                preview
+                focalPoint {
+                    x
+                    y
+                }
             }
         }
     }

+ 47 - 12
packages/asset-server-plugin/e2e/graphql/generated-e2e-asset-server-plugin-types.ts

@@ -1,6 +1,6 @@
 // tslint:disable
 export type Maybe<T> = T | null;
-
+export type Exact<T extends { [key: string]: unknown }> = { [K in keyof T]: T[K] };
 /** All built-in and custom scalars, mapped to their actual values */
 export type Scalars = {
     ID: string;
@@ -8,8 +8,11 @@ export type Scalars = {
     Boolean: boolean;
     Int: number;
     Float: number;
+    /** A date-time string at UTC, such as 2007-12-03T10:15:30Z, compliant with the `date-time` format outlined in section 5.6 of the RFC 3339 profile of the ISO 8601 standard for representation of dates and times using the Gregorian calendar. */
     DateTime: any;
+    /** The `JSON` scalar type represents JSON values as specified by [ECMA-404](http://www.ecma-international.org/publications/files/ECMA-ST/ECMA-404.pdf). */
     JSON: any;
+    /** The `Upload` scalar type represents a file upload. */
     Upload: any;
 };
 
@@ -443,6 +446,8 @@ export type CreateAssetInput = {
     file: Scalars['Upload'];
 };
 
+export type CreateAssetResult = Asset | MimeTypeError;
+
 export type CreateChannelInput = {
     code: Scalars['String'];
     token: Scalars['String'];
@@ -1119,6 +1124,16 @@ export enum DeletionResult {
     NOT_DELETED = 'NOT_DELETED',
 }
 
+export enum ErrorCode {
+    UnknownError = 'UnknownError',
+    MimeTypeError = 'MimeTypeError',
+}
+
+export type ErrorResult = {
+    code: ErrorCode;
+    message: Scalars['String'];
+};
+
 export type Facet = Node & {
     __typename?: 'Facet';
     isPrivate: Scalars['Boolean'];
@@ -1770,6 +1785,14 @@ export type LoginResult = {
     user: CurrentUser;
 };
 
+export type MimeTypeError = ErrorResult & {
+    __typename?: 'MimeTypeError';
+    code: ErrorCode;
+    message: Scalars['String'];
+    fileName: Scalars['String'];
+    mimeType: Scalars['String'];
+};
+
 export type MoveCollectionInput = {
     collectionId: Scalars['ID'];
     parentId: Scalars['ID'];
@@ -1787,7 +1810,7 @@ export type Mutation = {
     /** Assign a Role to an Administrator */
     assignRoleToAdministrator: Administrator;
     /** Create a new Asset */
-    createAssets: Array<Asset>;
+    createAssets: Array<CreateAssetResult>;
     /** Update an existing Asset */
     updateAsset: Asset;
     /** Delete an Asset */
@@ -3699,36 +3722,48 @@ export type Zone = Node & {
     members: Array<Country>;
 };
 
-export type CreateAssetsMutationVariables = {
+export type CreateAssetsMutationVariables = Exact<{
     input: Array<CreateAssetInput>;
-};
+}>;
 
 export type CreateAssetsMutation = { __typename?: 'Mutation' } & {
     createAssets: Array<
-        { __typename?: 'Asset' } & Pick<Asset, 'id' | 'name' | 'source' | 'preview'> & {
-                focalPoint?: Maybe<{ __typename?: 'Coordinate' } & Pick<Coordinate, 'x' | 'y'>>;
-            }
+        | ({ __typename?: 'Asset' } & Pick<Asset, 'id' | 'name' | 'source' | 'preview'> & {
+                  focalPoint?: Maybe<{ __typename?: 'Coordinate' } & Pick<Coordinate, 'x' | 'y'>>;
+              })
+        | { __typename?: 'MimeTypeError' }
     >;
 };
 
-export type DeleteAssetMutationVariables = {
+export type DeleteAssetMutationVariables = Exact<{
     id: Scalars['ID'];
     force: Scalars['Boolean'];
-};
+}>;
 
 export type DeleteAssetMutation = { __typename?: 'Mutation' } & {
     deleteAsset: { __typename?: 'DeletionResponse' } & Pick<DeletionResponse, 'result'>;
 };
 
+type DiscriminateUnion<T, U> = T extends U ? T : never;
+
 export namespace CreateAssets {
     export type Variables = CreateAssetsMutationVariables;
     export type Mutation = CreateAssetsMutation;
-    export type CreateAssets = NonNullable<CreateAssetsMutation['createAssets'][0]>;
-    export type FocalPoint = NonNullable<NonNullable<CreateAssetsMutation['createAssets'][0]>['focalPoint']>;
+    export type CreateAssets = NonNullable<NonNullable<CreateAssetsMutation['createAssets']>[number]>;
+    export type AssetInlineFragment = DiscriminateUnion<
+        NonNullable<NonNullable<CreateAssetsMutation['createAssets']>[number]>,
+        { __typename?: 'Asset' }
+    >;
+    export type FocalPoint = NonNullable<
+        DiscriminateUnion<
+            NonNullable<NonNullable<CreateAssetsMutation['createAssets']>[number]>,
+            { __typename?: 'Asset' }
+        >['focalPoint']
+    >;
 }
 
 export namespace DeleteAsset {
     export type Variables = DeleteAssetMutationVariables;
     export type Mutation = DeleteAssetMutation;
-    export type DeleteAsset = DeleteAssetMutation['deleteAsset'];
+    export type DeleteAsset = NonNullable<DeleteAssetMutation['deleteAsset']>;
 }

+ 9 - 2
packages/common/src/generated-shop-types.ts

@@ -1,6 +1,6 @@
 // tslint:disable
-export type Maybe<T> = T | null;
-
+export type Maybe<T> = T;
+export type Exact<T extends { [key: string]: unknown }> = { [K in keyof T]: T[K] };
 /** All built-in and custom scalars, mapped to their actual values */
 export type Scalars = {
     ID: string | number;
@@ -8,8 +8,11 @@ export type Scalars = {
     Boolean: boolean;
     Int: number;
     Float: number;
+    /** A date-time string at UTC, such as 2007-12-03T10:15:30Z, compliant with the `date-time` format outlined in section 5.6 of the RFC 3339 profile of the ISO 8601 standard for representation of dates and times using the Gregorian calendar. */
     DateTime: any;
+    /** The `JSON` scalar type represents JSON values as specified by [ECMA-404](http://www.ecma-international.org/publications/files/ECMA-ST/ECMA-404.pdf). */
     JSON: any;
+    /** The `Upload` scalar type represents a file upload. */
     Upload: any;
 };
 
@@ -804,6 +807,10 @@ export enum DeletionResult {
     NOT_DELETED = 'NOT_DELETED',
 }
 
+export enum ErrorCode {
+    UnknownError = 'UnknownError',
+}
+
 export type Facet = Node & {
     __typename?: 'Facet';
     id: Scalars['ID'];

+ 125 - 102
packages/common/src/generated-types.ts

@@ -1,6 +1,6 @@
 // tslint:disable
-export type Maybe<T> = T | null;
-
+export type Maybe<T> = T;
+export type Exact<T extends { [key: string]: unknown }> = { [K in keyof T]: T[K] };
 /** All built-in and custom scalars, mapped to their actual values */
 export type Scalars = {
   ID: string | number;
@@ -8,8 +8,11 @@ export type Scalars = {
   Boolean: boolean;
   Int: number;
   Float: number;
+  /** A date-time string at UTC, such as 2007-12-03T10:15:30Z, compliant with the `date-time` format outlined in section 5.6 of the RFC 3339 profile of the ISO 8601 standard for representation of dates and times using the Gregorian calendar. */
   DateTime: any;
+  /** The `JSON` scalar type represents JSON values as specified by [ECMA-404](http://www.ecma-international.org/publications/files/ECMA-ST/ECMA-404.pdf). */
   JSON: any;
+  /** The `Upload` scalar type represents a file upload. */
   Upload: any;
 };
 
@@ -26,7 +29,7 @@ export type AddNoteToOrderInput = {
 };
 
 export type Address = Node & {
-   __typename?: 'Address';
+  __typename?: 'Address';
   id: Scalars['ID'];
   createdAt: Scalars['DateTime'];
   updatedAt: Scalars['DateTime'];
@@ -45,7 +48,7 @@ export type Address = Node & {
 };
 
 export type Adjustment = {
-   __typename?: 'Adjustment';
+  __typename?: 'Adjustment';
   adjustmentSource: Scalars['String'];
   type: AdjustmentType;
   description: Scalars['String'];
@@ -63,7 +66,7 @@ export enum AdjustmentType {
 }
 
 export type Administrator = Node & {
-   __typename?: 'Administrator';
+  __typename?: 'Administrator';
   id: Scalars['ID'];
   createdAt: Scalars['DateTime'];
   updatedAt: Scalars['DateTime'];
@@ -82,7 +85,7 @@ export type AdministratorFilterParameter = {
 };
 
 export type AdministratorList = PaginatedList & {
-   __typename?: 'AdministratorList';
+  __typename?: 'AdministratorList';
   items: Array<Administrator>;
   totalItems: Scalars['Int'];
 };
@@ -104,7 +107,7 @@ export type AdministratorSortParameter = {
 };
 
 export type Asset = Node & {
-   __typename?: 'Asset';
+  __typename?: 'Asset';
   id: Scalars['ID'];
   createdAt: Scalars['DateTime'];
   updatedAt: Scalars['DateTime'];
@@ -133,7 +136,7 @@ export type AssetFilterParameter = {
 };
 
 export type AssetList = PaginatedList & {
-   __typename?: 'AssetList';
+  __typename?: 'AssetList';
   items: Array<Asset>;
   totalItems: Scalars['Int'];
 };
@@ -175,7 +178,7 @@ export type AuthenticationInput = {
 };
 
 export type AuthenticationMethod = Node & {
-   __typename?: 'AuthenticationMethod';
+  __typename?: 'AuthenticationMethod';
   id: Scalars['ID'];
   createdAt: Scalars['DateTime'];
   updatedAt: Scalars['DateTime'];
@@ -183,7 +186,7 @@ export type AuthenticationMethod = Node & {
 };
 
 export type BooleanCustomFieldConfig = CustomField & {
-   __typename?: 'BooleanCustomFieldConfig';
+  __typename?: 'BooleanCustomFieldConfig';
   name: Scalars['String'];
   type: Scalars['String'];
   list: Scalars['Boolean'];
@@ -198,7 +201,7 @@ export type BooleanOperators = {
 };
 
 export type Cancellation = Node & StockMovement & {
-   __typename?: 'Cancellation';
+  __typename?: 'Cancellation';
   id: Scalars['ID'];
   createdAt: Scalars['DateTime'];
   updatedAt: Scalars['DateTime'];
@@ -217,7 +220,7 @@ export type CancelOrderInput = {
 };
 
 export type Channel = Node & {
-   __typename?: 'Channel';
+  __typename?: 'Channel';
   id: Scalars['ID'];
   createdAt: Scalars['DateTime'];
   updatedAt: Scalars['DateTime'];
@@ -231,7 +234,7 @@ export type Channel = Node & {
 };
 
 export type Collection = Node & {
-   __typename?: 'Collection';
+  __typename?: 'Collection';
   isPrivate: Scalars['Boolean'];
   id: Scalars['ID'];
   createdAt: Scalars['DateTime'];
@@ -258,7 +261,7 @@ export type CollectionProductVariantsArgs = {
 };
 
 export type CollectionBreadcrumb = {
-   __typename?: 'CollectionBreadcrumb';
+  __typename?: 'CollectionBreadcrumb';
   id: Scalars['ID'];
   name: Scalars['String'];
   slug: Scalars['String'];
@@ -276,7 +279,7 @@ export type CollectionFilterParameter = {
 };
 
 export type CollectionList = PaginatedList & {
-   __typename?: 'CollectionList';
+  __typename?: 'CollectionList';
   items: Array<Collection>;
   totalItems: Scalars['Int'];
 };
@@ -299,7 +302,7 @@ export type CollectionSortParameter = {
 };
 
 export type CollectionTranslation = {
-   __typename?: 'CollectionTranslation';
+  __typename?: 'CollectionTranslation';
   id: Scalars['ID'];
   createdAt: Scalars['DateTime'];
   updatedAt: Scalars['DateTime'];
@@ -310,13 +313,13 @@ export type CollectionTranslation = {
 };
 
 export type ConfigArg = {
-   __typename?: 'ConfigArg';
+  __typename?: 'ConfigArg';
   name: Scalars['String'];
   value: Scalars['String'];
 };
 
 export type ConfigArgDefinition = {
-   __typename?: 'ConfigArgDefinition';
+  __typename?: 'ConfigArgDefinition';
   name: Scalars['String'];
   type: Scalars['String'];
   list: Scalars['Boolean'];
@@ -331,13 +334,13 @@ export type ConfigArgInput = {
 };
 
 export type ConfigurableOperation = {
-   __typename?: 'ConfigurableOperation';
+  __typename?: 'ConfigurableOperation';
   code: Scalars['String'];
   args: Array<ConfigArg>;
 };
 
 export type ConfigurableOperationDefinition = {
-   __typename?: 'ConfigurableOperationDefinition';
+  __typename?: 'ConfigurableOperationDefinition';
   code: Scalars['String'];
   args: Array<ConfigArgDefinition>;
   description: Scalars['String'];
@@ -349,7 +352,7 @@ export type ConfigurableOperationInput = {
 };
 
 export type Coordinate = {
-   __typename?: 'Coordinate';
+  __typename?: 'Coordinate';
   x: Scalars['Float'];
   y: Scalars['Float'];
 };
@@ -360,7 +363,7 @@ export type CoordinateInput = {
 };
 
 export type Country = Node & {
-   __typename?: 'Country';
+  __typename?: 'Country';
   id: Scalars['ID'];
   createdAt: Scalars['DateTime'];
   updatedAt: Scalars['DateTime'];
@@ -381,7 +384,7 @@ export type CountryFilterParameter = {
 };
 
 export type CountryList = PaginatedList & {
-   __typename?: 'CountryList';
+  __typename?: 'CountryList';
   items: Array<Country>;
   totalItems: Scalars['Int'];
 };
@@ -402,7 +405,7 @@ export type CountrySortParameter = {
 };
 
 export type CountryTranslation = {
-   __typename?: 'CountryTranslation';
+  __typename?: 'CountryTranslation';
   id: Scalars['ID'];
   createdAt: Scalars['DateTime'];
   updatedAt: Scalars['DateTime'];
@@ -443,6 +446,8 @@ export type CreateAssetInput = {
   file: Scalars['Upload'];
 };
 
+export type CreateAssetResult = Asset | MimeTypeError;
+
 export type CreateChannelInput = {
   code: Scalars['String'];
   token: Scalars['String'];
@@ -927,14 +932,14 @@ export enum CurrencyCode {
 }
 
 export type CurrentUser = {
-   __typename?: 'CurrentUser';
+  __typename?: 'CurrentUser';
   id: Scalars['ID'];
   identifier: Scalars['String'];
   channels: Array<CurrentUserChannel>;
 };
 
 export type CurrentUserChannel = {
-   __typename?: 'CurrentUserChannel';
+  __typename?: 'CurrentUserChannel';
   id: Scalars['ID'];
   token: Scalars['String'];
   code: Scalars['String'];
@@ -942,7 +947,7 @@ export type CurrentUserChannel = {
 };
 
 export type Customer = Node & {
-   __typename?: 'Customer';
+  __typename?: 'Customer';
   groups: Array<CustomerGroup>;
   history: HistoryEntryList;
   id: Scalars['ID'];
@@ -980,7 +985,7 @@ export type CustomerFilterParameter = {
 };
 
 export type CustomerGroup = Node & {
-   __typename?: 'CustomerGroup';
+  __typename?: 'CustomerGroup';
   id: Scalars['ID'];
   createdAt: Scalars['DateTime'];
   updatedAt: Scalars['DateTime'];
@@ -1000,7 +1005,7 @@ export type CustomerGroupFilterParameter = {
 };
 
 export type CustomerGroupList = PaginatedList & {
-   __typename?: 'CustomerGroupList';
+  __typename?: 'CustomerGroupList';
   items: Array<CustomerGroup>;
   totalItems: Scalars['Int'];
 };
@@ -1020,7 +1025,7 @@ export type CustomerGroupSortParameter = {
 };
 
 export type CustomerList = PaginatedList & {
-   __typename?: 'CustomerList';
+  __typename?: 'CustomerList';
   items: Array<Customer>;
   totalItems: Scalars['Int'];
 };
@@ -1056,7 +1061,7 @@ export type CustomField = {
 export type CustomFieldConfig = StringCustomFieldConfig | LocaleStringCustomFieldConfig | IntCustomFieldConfig | FloatCustomFieldConfig | BooleanCustomFieldConfig | DateTimeCustomFieldConfig;
 
 export type CustomFields = {
-   __typename?: 'CustomFields';
+  __typename?: 'CustomFields';
   Address: Array<CustomFieldConfig>;
   Collection: Array<CustomFieldConfig>;
   Customer: Array<CustomFieldConfig>;
@@ -1091,7 +1096,7 @@ export type DateRange = {
  * See https://developer.mozilla.org/en-US/docs/Web/HTML/Element/input/datetime-local#Additional_attributes
  */
 export type DateTimeCustomFieldConfig = CustomField & {
-   __typename?: 'DateTimeCustomFieldConfig';
+  __typename?: 'DateTimeCustomFieldConfig';
   name: Scalars['String'];
   type: Scalars['String'];
   list: Scalars['Boolean'];
@@ -1105,7 +1110,7 @@ export type DateTimeCustomFieldConfig = CustomField & {
 };
 
 export type DeletionResponse = {
-   __typename?: 'DeletionResponse';
+  __typename?: 'DeletionResponse';
   result: DeletionResult;
   message?: Maybe<Scalars['String']>;
 };
@@ -1117,8 +1122,18 @@ export enum DeletionResult {
   NOT_DELETED = 'NOT_DELETED'
 }
 
+export enum ErrorCode {
+  UnknownError = 'UnknownError',
+  MimeTypeError = 'MimeTypeError'
+}
+
+export type ErrorResult = {
+  code: ErrorCode;
+  message: Scalars['String'];
+};
+
 export type Facet = Node & {
-   __typename?: 'Facet';
+  __typename?: 'Facet';
   isPrivate: Scalars['Boolean'];
   id: Scalars['ID'];
   createdAt: Scalars['DateTime'];
@@ -1141,7 +1156,7 @@ export type FacetFilterParameter = {
 };
 
 export type FacetList = PaginatedList & {
-   __typename?: 'FacetList';
+  __typename?: 'FacetList';
   items: Array<Facet>;
   totalItems: Scalars['Int'];
 };
@@ -1162,7 +1177,7 @@ export type FacetSortParameter = {
 };
 
 export type FacetTranslation = {
-   __typename?: 'FacetTranslation';
+  __typename?: 'FacetTranslation';
   id: Scalars['ID'];
   createdAt: Scalars['DateTime'];
   updatedAt: Scalars['DateTime'];
@@ -1178,7 +1193,7 @@ export type FacetTranslationInput = {
 };
 
 export type FacetValue = Node & {
-   __typename?: 'FacetValue';
+  __typename?: 'FacetValue';
   id: Scalars['ID'];
   createdAt: Scalars['DateTime'];
   updatedAt: Scalars['DateTime'];
@@ -1195,13 +1210,13 @@ export type FacetValue = Node & {
  * by the search, and in what quantity.
  */
 export type FacetValueResult = {
-   __typename?: 'FacetValueResult';
+  __typename?: 'FacetValueResult';
   facetValue: FacetValue;
   count: Scalars['Int'];
 };
 
 export type FacetValueTranslation = {
-   __typename?: 'FacetValueTranslation';
+  __typename?: 'FacetValueTranslation';
   id: Scalars['ID'];
   createdAt: Scalars['DateTime'];
   updatedAt: Scalars['DateTime'];
@@ -1217,7 +1232,7 @@ export type FacetValueTranslationInput = {
 };
 
 export type FloatCustomFieldConfig = CustomField & {
-   __typename?: 'FloatCustomFieldConfig';
+  __typename?: 'FloatCustomFieldConfig';
   name: Scalars['String'];
   type: Scalars['String'];
   list: Scalars['Boolean'];
@@ -1231,7 +1246,7 @@ export type FloatCustomFieldConfig = CustomField & {
 };
 
 export type Fulfillment = Node & {
-   __typename?: 'Fulfillment';
+  __typename?: 'Fulfillment';
   nextStates: Array<Scalars['String']>;
   id: Scalars['ID'];
   createdAt: Scalars['DateTime'];
@@ -1249,7 +1264,7 @@ export type FulfillOrderInput = {
 };
 
 export type GlobalSettings = {
-   __typename?: 'GlobalSettings';
+  __typename?: 'GlobalSettings';
   id: Scalars['ID'];
   createdAt: Scalars['DateTime'];
   updatedAt: Scalars['DateTime'];
@@ -1260,7 +1275,7 @@ export type GlobalSettings = {
 };
 
 export type HistoryEntry = Node & {
-   __typename?: 'HistoryEntry';
+  __typename?: 'HistoryEntry';
   id: Scalars['ID'];
   createdAt: Scalars['DateTime'];
   updatedAt: Scalars['DateTime'];
@@ -1278,7 +1293,7 @@ export type HistoryEntryFilterParameter = {
 };
 
 export type HistoryEntryList = PaginatedList & {
-   __typename?: 'HistoryEntryList';
+  __typename?: 'HistoryEntryList';
   items: Array<HistoryEntry>;
   totalItems: Scalars['Int'];
 };
@@ -1323,14 +1338,14 @@ export enum HistoryEntryType {
 }
 
 export type ImportInfo = {
-   __typename?: 'ImportInfo';
+  __typename?: 'ImportInfo';
   errors?: Maybe<Array<Scalars['String']>>;
   processed: Scalars['Int'];
   imported: Scalars['Int'];
 };
 
 export type IntCustomFieldConfig = CustomField & {
-   __typename?: 'IntCustomFieldConfig';
+  __typename?: 'IntCustomFieldConfig';
   name: Scalars['String'];
   type: Scalars['String'];
   list: Scalars['Boolean'];
@@ -1344,7 +1359,7 @@ export type IntCustomFieldConfig = CustomField & {
 };
 
 export type Job = Node & {
-   __typename?: 'Job';
+  __typename?: 'Job';
   id: Scalars['ID'];
   createdAt: Scalars['DateTime'];
   startedAt?: Maybe<Scalars['DateTime']>;
@@ -1371,7 +1386,7 @@ export type JobFilterParameter = {
 };
 
 export type JobList = PaginatedList & {
-   __typename?: 'JobList';
+  __typename?: 'JobList';
   items: Array<Job>;
   totalItems: Scalars['Int'];
 };
@@ -1384,7 +1399,7 @@ export type JobListOptions = {
 };
 
 export type JobQueue = {
-   __typename?: 'JobQueue';
+  __typename?: 'JobQueue';
   name: Scalars['String'];
   running: Scalars['Boolean'];
 };
@@ -1741,7 +1756,7 @@ export enum LanguageCode {
 }
 
 export type LocaleStringCustomFieldConfig = CustomField & {
-   __typename?: 'LocaleStringCustomFieldConfig';
+  __typename?: 'LocaleStringCustomFieldConfig';
   name: Scalars['String'];
   type: Scalars['String'];
   list: Scalars['Boolean'];
@@ -1754,7 +1769,7 @@ export type LocaleStringCustomFieldConfig = CustomField & {
 };
 
 export type LocalizedString = {
-   __typename?: 'LocalizedString';
+  __typename?: 'LocalizedString';
   languageCode: LanguageCode;
   value: Scalars['String'];
 };
@@ -1765,10 +1780,18 @@ export enum LogicalOperator {
 }
 
 export type LoginResult = {
-   __typename?: 'LoginResult';
+  __typename?: 'LoginResult';
   user: CurrentUser;
 };
 
+export type MimeTypeError = ErrorResult & {
+  __typename?: 'MimeTypeError';
+  code: ErrorCode;
+  message: Scalars['String'];
+  fileName: Scalars['String'];
+  mimeType: Scalars['String'];
+};
+
 export type MoveCollectionInput = {
   collectionId: Scalars['ID'];
   parentId: Scalars['ID'];
@@ -1776,7 +1799,7 @@ export type MoveCollectionInput = {
 };
 
 export type Mutation = {
-   __typename?: 'Mutation';
+  __typename?: 'Mutation';
   /** Create a new Administrator */
   createAdministrator: Administrator;
   /** Update an existing Administrator */
@@ -1786,7 +1809,7 @@ export type Mutation = {
   /** Assign a Role to an Administrator */
   assignRoleToAdministrator: Administrator;
   /** Create a new Asset */
-  createAssets: Array<Asset>;
+  createAssets: Array<CreateAssetResult>;
   /** Update an existing Asset */
   updateAsset: Asset;
   /** Delete an Asset */
@@ -2431,7 +2454,7 @@ export type NumberRange = {
 };
 
 export type Order = Node & {
-   __typename?: 'Order';
+  __typename?: 'Order';
   nextStates: Array<Scalars['String']>;
   id: Scalars['ID'];
   createdAt: Scalars['DateTime'];
@@ -2471,7 +2494,7 @@ export type OrderHistoryArgs = {
 };
 
 export type OrderAddress = {
-   __typename?: 'OrderAddress';
+  __typename?: 'OrderAddress';
   fullName?: Maybe<Scalars['String']>;
   company?: Maybe<Scalars['String']>;
   streetLine1?: Maybe<Scalars['String']>;
@@ -2500,7 +2523,7 @@ export type OrderFilterParameter = {
 };
 
 export type OrderItem = Node & {
-   __typename?: 'OrderItem';
+  __typename?: 'OrderItem';
   id: Scalars['ID'];
   createdAt: Scalars['DateTime'];
   updatedAt: Scalars['DateTime'];
@@ -2515,7 +2538,7 @@ export type OrderItem = Node & {
 };
 
 export type OrderLine = Node & {
-   __typename?: 'OrderLine';
+  __typename?: 'OrderLine';
   id: Scalars['ID'];
   createdAt: Scalars['DateTime'];
   updatedAt: Scalars['DateTime'];
@@ -2537,7 +2560,7 @@ export type OrderLineInput = {
 };
 
 export type OrderList = PaginatedList & {
-   __typename?: 'OrderList';
+  __typename?: 'OrderList';
   items: Array<Order>;
   totalItems: Scalars['Int'];
 };
@@ -2550,7 +2573,7 @@ export type OrderListOptions = {
 };
 
 export type OrderProcessState = {
-   __typename?: 'OrderProcessState';
+  __typename?: 'OrderProcessState';
   name: Scalars['String'];
   to: Array<Scalars['String']>;
 };
@@ -2575,7 +2598,7 @@ export type PaginatedList = {
 };
 
 export type Payment = Node & {
-   __typename?: 'Payment';
+  __typename?: 'Payment';
   id: Scalars['ID'];
   createdAt: Scalars['DateTime'];
   updatedAt: Scalars['DateTime'];
@@ -2589,7 +2612,7 @@ export type Payment = Node & {
 };
 
 export type PaymentMethod = Node & {
-   __typename?: 'PaymentMethod';
+  __typename?: 'PaymentMethod';
   id: Scalars['ID'];
   createdAt: Scalars['DateTime'];
   updatedAt: Scalars['DateTime'];
@@ -2607,7 +2630,7 @@ export type PaymentMethodFilterParameter = {
 };
 
 export type PaymentMethodList = PaginatedList & {
-   __typename?: 'PaymentMethodList';
+  __typename?: 'PaymentMethodList';
   items: Array<PaymentMethod>;
   totalItems: Scalars['Int'];
 };
@@ -2671,13 +2694,13 @@ export enum Permission {
 
 /** The price range where the result has more than one price */
 export type PriceRange = {
-   __typename?: 'PriceRange';
+  __typename?: 'PriceRange';
   min: Scalars['Int'];
   max: Scalars['Int'];
 };
 
 export type Product = Node & {
-   __typename?: 'Product';
+  __typename?: 'Product';
   enabled: Scalars['Boolean'];
   channels: Array<Channel>;
   id: Scalars['ID'];
@@ -2708,7 +2731,7 @@ export type ProductFilterParameter = {
 };
 
 export type ProductList = PaginatedList & {
-   __typename?: 'ProductList';
+  __typename?: 'ProductList';
   items: Array<Product>;
   totalItems: Scalars['Int'];
 };
@@ -2721,7 +2744,7 @@ export type ProductListOptions = {
 };
 
 export type ProductOption = Node & {
-   __typename?: 'ProductOption';
+  __typename?: 'ProductOption';
   id: Scalars['ID'];
   createdAt: Scalars['DateTime'];
   updatedAt: Scalars['DateTime'];
@@ -2735,7 +2758,7 @@ export type ProductOption = Node & {
 };
 
 export type ProductOptionGroup = Node & {
-   __typename?: 'ProductOptionGroup';
+  __typename?: 'ProductOptionGroup';
   id: Scalars['ID'];
   createdAt: Scalars['DateTime'];
   updatedAt: Scalars['DateTime'];
@@ -2748,7 +2771,7 @@ export type ProductOptionGroup = Node & {
 };
 
 export type ProductOptionGroupTranslation = {
-   __typename?: 'ProductOptionGroupTranslation';
+  __typename?: 'ProductOptionGroupTranslation';
   id: Scalars['ID'];
   createdAt: Scalars['DateTime'];
   updatedAt: Scalars['DateTime'];
@@ -2764,7 +2787,7 @@ export type ProductOptionGroupTranslationInput = {
 };
 
 export type ProductOptionTranslation = {
-   __typename?: 'ProductOptionTranslation';
+  __typename?: 'ProductOptionTranslation';
   id: Scalars['ID'];
   createdAt: Scalars['DateTime'];
   updatedAt: Scalars['DateTime'];
@@ -2789,7 +2812,7 @@ export type ProductSortParameter = {
 };
 
 export type ProductTranslation = {
-   __typename?: 'ProductTranslation';
+  __typename?: 'ProductTranslation';
   id: Scalars['ID'];
   createdAt: Scalars['DateTime'];
   updatedAt: Scalars['DateTime'];
@@ -2809,7 +2832,7 @@ export type ProductTranslationInput = {
 };
 
 export type ProductVariant = Node & {
-   __typename?: 'ProductVariant';
+  __typename?: 'ProductVariant';
   enabled: Scalars['Boolean'];
   stockOnHand: Scalars['Int'];
   trackInventory: Scalars['Boolean'];
@@ -2857,7 +2880,7 @@ export type ProductVariantFilterParameter = {
 };
 
 export type ProductVariantList = PaginatedList & {
-   __typename?: 'ProductVariantList';
+  __typename?: 'ProductVariantList';
   items: Array<ProductVariant>;
   totalItems: Scalars['Int'];
 };
@@ -2882,7 +2905,7 @@ export type ProductVariantSortParameter = {
 };
 
 export type ProductVariantTranslation = {
-   __typename?: 'ProductVariantTranslation';
+  __typename?: 'ProductVariantTranslation';
   id: Scalars['ID'];
   createdAt: Scalars['DateTime'];
   updatedAt: Scalars['DateTime'];
@@ -2898,7 +2921,7 @@ export type ProductVariantTranslationInput = {
 };
 
 export type Promotion = Node & {
-   __typename?: 'Promotion';
+  __typename?: 'Promotion';
   id: Scalars['ID'];
   createdAt: Scalars['DateTime'];
   updatedAt: Scalars['DateTime'];
@@ -2924,7 +2947,7 @@ export type PromotionFilterParameter = {
 };
 
 export type PromotionList = PaginatedList & {
-   __typename?: 'PromotionList';
+  __typename?: 'PromotionList';
   items: Array<Promotion>;
   totalItems: Scalars['Int'];
 };
@@ -2948,7 +2971,7 @@ export type PromotionSortParameter = {
 };
 
 export type Query = {
-   __typename?: 'Query';
+  __typename?: 'Query';
   administrators: AdministratorList;
   administrator?: Maybe<Administrator>;
   /** Get a list of Assets */
@@ -3211,7 +3234,7 @@ export type QueryZoneArgs = {
 };
 
 export type Refund = Node & {
-   __typename?: 'Refund';
+  __typename?: 'Refund';
   id: Scalars['ID'];
   createdAt: Scalars['DateTime'];
   updatedAt: Scalars['DateTime'];
@@ -3242,7 +3265,7 @@ export type RemoveProductsFromChannelInput = {
 };
 
 export type Return = Node & StockMovement & {
-   __typename?: 'Return';
+  __typename?: 'Return';
   id: Scalars['ID'];
   createdAt: Scalars['DateTime'];
   updatedAt: Scalars['DateTime'];
@@ -3253,7 +3276,7 @@ export type Return = Node & StockMovement & {
 };
 
 export type Role = Node & {
-   __typename?: 'Role';
+  __typename?: 'Role';
   id: Scalars['ID'];
   createdAt: Scalars['DateTime'];
   updatedAt: Scalars['DateTime'];
@@ -3271,7 +3294,7 @@ export type RoleFilterParameter = {
 };
 
 export type RoleList = PaginatedList & {
-   __typename?: 'RoleList';
+  __typename?: 'RoleList';
   items: Array<Role>;
   totalItems: Scalars['Int'];
 };
@@ -3292,7 +3315,7 @@ export type RoleSortParameter = {
 };
 
 export type Sale = Node & StockMovement & {
-   __typename?: 'Sale';
+  __typename?: 'Sale';
   id: Scalars['ID'];
   createdAt: Scalars['DateTime'];
   updatedAt: Scalars['DateTime'];
@@ -3315,19 +3338,19 @@ export type SearchInput = {
 };
 
 export type SearchReindexResponse = {
-   __typename?: 'SearchReindexResponse';
+  __typename?: 'SearchReindexResponse';
   success: Scalars['Boolean'];
 };
 
 export type SearchResponse = {
-   __typename?: 'SearchResponse';
+  __typename?: 'SearchResponse';
   items: Array<SearchResult>;
   totalItems: Scalars['Int'];
   facetValues: Array<FacetValueResult>;
 };
 
 export type SearchResult = {
-   __typename?: 'SearchResult';
+  __typename?: 'SearchResult';
   enabled: Scalars['Boolean'];
   /** An array of ids of the Collections in which this result appears */
   channelIds: Array<Scalars['ID']>;
@@ -3356,7 +3379,7 @@ export type SearchResult = {
 };
 
 export type SearchResultAsset = {
-   __typename?: 'SearchResultAsset';
+  __typename?: 'SearchResultAsset';
   id: Scalars['ID'];
   preview: Scalars['String'];
   focalPoint?: Maybe<Coordinate>;
@@ -3371,7 +3394,7 @@ export type SearchResultSortParameter = {
 };
 
 export type ServerConfig = {
-   __typename?: 'ServerConfig';
+  __typename?: 'ServerConfig';
   orderProcess: Array<OrderProcessState>;
   permittedAssetTypes: Array<Scalars['String']>;
   customFieldConfig: CustomFields;
@@ -3383,7 +3406,7 @@ export type SettleRefundInput = {
 };
 
 export type ShippingMethod = Node & {
-   __typename?: 'ShippingMethod';
+  __typename?: 'ShippingMethod';
   id: Scalars['ID'];
   createdAt: Scalars['DateTime'];
   updatedAt: Scalars['DateTime'];
@@ -3402,7 +3425,7 @@ export type ShippingMethodFilterParameter = {
 };
 
 export type ShippingMethodList = PaginatedList & {
-   __typename?: 'ShippingMethodList';
+  __typename?: 'ShippingMethodList';
   items: Array<ShippingMethod>;
   totalItems: Scalars['Int'];
 };
@@ -3415,7 +3438,7 @@ export type ShippingMethodListOptions = {
 };
 
 export type ShippingMethodQuote = {
-   __typename?: 'ShippingMethodQuote';
+  __typename?: 'ShippingMethodQuote';
   id: Scalars['ID'];
   price: Scalars['Int'];
   priceWithTax: Scalars['Int'];
@@ -3433,7 +3456,7 @@ export type ShippingMethodSortParameter = {
 
 /** The price value where the result has a single price */
 export type SinglePrice = {
-   __typename?: 'SinglePrice';
+  __typename?: 'SinglePrice';
   value: Scalars['Int'];
 };
 
@@ -3443,7 +3466,7 @@ export enum SortOrder {
 }
 
 export type StockAdjustment = Node & StockMovement & {
-   __typename?: 'StockAdjustment';
+  __typename?: 'StockAdjustment';
   id: Scalars['ID'];
   createdAt: Scalars['DateTime'];
   updatedAt: Scalars['DateTime'];
@@ -3464,7 +3487,7 @@ export type StockMovement = {
 export type StockMovementItem = StockAdjustment | Sale | Cancellation | Return;
 
 export type StockMovementList = {
-   __typename?: 'StockMovementList';
+  __typename?: 'StockMovementList';
   items: Array<StockMovementItem>;
   totalItems: Scalars['Int'];
 };
@@ -3483,7 +3506,7 @@ export enum StockMovementType {
 }
 
 export type StringCustomFieldConfig = CustomField & {
-   __typename?: 'StringCustomFieldConfig';
+  __typename?: 'StringCustomFieldConfig';
   name: Scalars['String'];
   type: Scalars['String'];
   list: Scalars['Boolean'];
@@ -3497,7 +3520,7 @@ export type StringCustomFieldConfig = CustomField & {
 };
 
 export type StringFieldOption = {
-   __typename?: 'StringFieldOption';
+  __typename?: 'StringFieldOption';
   value: Scalars['String'];
   label?: Maybe<Array<LocalizedString>>;
 };
@@ -3508,7 +3531,7 @@ export type StringOperators = {
 };
 
 export type TaxCategory = Node & {
-   __typename?: 'TaxCategory';
+  __typename?: 'TaxCategory';
   id: Scalars['ID'];
   createdAt: Scalars['DateTime'];
   updatedAt: Scalars['DateTime'];
@@ -3516,7 +3539,7 @@ export type TaxCategory = Node & {
 };
 
 export type TaxRate = Node & {
-   __typename?: 'TaxRate';
+  __typename?: 'TaxRate';
   id: Scalars['ID'];
   createdAt: Scalars['DateTime'];
   updatedAt: Scalars['DateTime'];
@@ -3537,7 +3560,7 @@ export type TaxRateFilterParameter = {
 };
 
 export type TaxRateList = PaginatedList & {
-   __typename?: 'TaxRateList';
+  __typename?: 'TaxRateList';
   items: Array<TaxRate>;
   totalItems: Scalars['Int'];
 };
@@ -3575,7 +3598,7 @@ export type TestShippingMethodOrderLineInput = {
 };
 
 export type TestShippingMethodQuote = {
-   __typename?: 'TestShippingMethodQuote';
+  __typename?: 'TestShippingMethodQuote';
   price: Scalars['Int'];
   priceWithTax: Scalars['Int'];
   description: Scalars['String'];
@@ -3583,7 +3606,7 @@ export type TestShippingMethodQuote = {
 };
 
 export type TestShippingMethodResult = {
-   __typename?: 'TestShippingMethodResult';
+  __typename?: 'TestShippingMethodResult';
   eligible: Scalars['Boolean'];
   quote?: Maybe<TestShippingMethodQuote>;
 };
@@ -3806,7 +3829,7 @@ export type UpdateZoneInput = {
 
 
 export type User = Node & {
-   __typename?: 'User';
+  __typename?: 'User';
   id: Scalars['ID'];
   createdAt: Scalars['DateTime'];
   updatedAt: Scalars['DateTime'];
@@ -3819,7 +3842,7 @@ export type User = Node & {
 };
 
 export type Zone = Node & {
-   __typename?: 'Zone';
+  __typename?: 'Zone';
   id: Scalars['ID'];
   createdAt: Scalars['DateTime'];
   updatedAt: Scalars['DateTime'];

+ 57 - 41
packages/core/e2e/asset.e2e-spec.ts

@@ -26,7 +26,6 @@ import {
     GET_PRODUCT_WITH_VARIANTS,
     UPDATE_ASSET,
 } from './graphql/shared-definitions';
-import { assertThrowsWithMessage } from './utils/assert-throws-with-message';
 
 describe('Asset resolver', () => {
     const { server, adminClient } = createTestEnvironment(
@@ -137,6 +136,10 @@ describe('Asset resolver', () => {
     });
 
     describe('createAssets', () => {
+        function isAsset(input: CreateAssets.CreateAssets): input is CreateAssets.AssetInlineFragment {
+            return input.hasOwnProperty('name');
+        }
+
         it('permitted types by mime type', async () => {
             const filesToUpload = [
                 path.join(__dirname, 'fixtures/assets/pps1.jpg'),
@@ -150,30 +153,30 @@ describe('Asset resolver', () => {
                 }),
             });
 
-            expect(createAssets.map(a => omit(a, ['id'])).sort((a, b) => (a.name < b.name ? -1 : 1))).toEqual(
-                [
-                    {
-                        fileSize: 1680,
-                        focalPoint: null,
-                        mimeType: 'image/jpeg',
-                        name: 'pps1.jpg',
-                        preview: 'test-url/test-assets/pps1__preview.jpg',
-                        source: 'test-url/test-assets/pps1.jpg',
-                        type: 'IMAGE',
-                    },
-                    {
-                        fileSize: 1680,
-                        focalPoint: null,
-                        mimeType: 'image/jpeg',
-                        name: 'pps2.jpg',
-                        preview: 'test-url/test-assets/pps2__preview.jpg',
-                        source: 'test-url/test-assets/pps2.jpg',
-                        type: 'IMAGE',
-                    },
-                ],
-            );
+            expect(createAssets.length).toBe(2);
+            const results = createAssets.filter(isAsset);
+            expect(results.map(a => omit(a, ['id'])).sort((a, b) => (a.name < b.name ? -1 : 1))).toEqual([
+                {
+                    fileSize: 1680,
+                    focalPoint: null,
+                    mimeType: 'image/jpeg',
+                    name: 'pps1.jpg',
+                    preview: 'test-url/test-assets/pps1__preview.jpg',
+                    source: 'test-url/test-assets/pps1.jpg',
+                    type: 'IMAGE',
+                },
+                {
+                    fileSize: 1680,
+                    focalPoint: null,
+                    mimeType: 'image/jpeg',
+                    name: 'pps2.jpg',
+                    preview: 'test-url/test-assets/pps2__preview.jpg',
+                    source: 'test-url/test-assets/pps2.jpg',
+                    type: 'IMAGE',
+                },
+            ]);
 
-            createdAssetId = createAssets[0].id;
+            createdAssetId = results[0].id;
         });
 
         it('permitted type by file extension', async () => {
@@ -186,7 +189,9 @@ describe('Asset resolver', () => {
                 }),
             });
 
-            expect(createAssets.map(a => omit(a, ['id']))).toEqual([
+            expect(createAssets.length).toBe(1);
+            const results = createAssets.filter(isAsset);
+            expect(results.map(a => omit(a, ['id']))).toEqual([
                 {
                     fileSize: 1680,
                     focalPoint: null,
@@ -199,19 +204,23 @@ describe('Asset resolver', () => {
             ]);
         });
 
-        it(
-            'not permitted type',
-            assertThrowsWithMessage(async () => {
-                const filesToUpload = [path.join(__dirname, 'fixtures/assets/dummy.txt')];
-                const { createAssets }: CreateAssets.Mutation = await adminClient.fileUploadMutation({
-                    mutation: CREATE_ASSETS,
-                    filePaths: filesToUpload,
-                    mapVariables: filePaths => ({
-                        input: filePaths.map(p => ({ file: null })),
-                    }),
-                });
-            }, `The MIME type 'text/plain' is not permitted.`),
-        );
+        it('not permitted type', async () => {
+            const filesToUpload = [path.join(__dirname, 'fixtures/assets/dummy.txt')];
+            const { createAssets }: CreateAssets.Mutation = await adminClient.fileUploadMutation({
+                mutation: CREATE_ASSETS,
+                filePaths: filesToUpload,
+                mapVariables: filePaths => ({
+                    input: filePaths.map(p => ({ file: null })),
+                }),
+            });
+
+            expect(createAssets.length).toBe(1);
+            expect(createAssets[0]).toEqual({
+                message: 'error.mime-type-not-permitted',
+                mimeType: 'text/plain',
+                fileName: 'dummy.txt',
+            });
+        });
     });
 
     describe('updateAsset', () => {
@@ -375,9 +384,16 @@ export const CREATE_ASSETS = gql`
     mutation CreateAssets($input: [CreateAssetInput!]!) {
         createAssets(input: $input) {
             ...Asset
-            focalPoint {
-                x
-                y
+            ... on Asset {
+                focalPoint {
+                    x
+                    y
+                }
+            }
+            ... on MimeTypeError {
+                message
+                fileName
+                mimeType
             }
         }
     }

Різницю між файлами не показано, бо вона завелика
+ 191 - 167
packages/core/e2e/graphql/generated-e2e-admin-types.ts


+ 148 - 109
packages/core/e2e/graphql/generated-e2e-shop-types.ts

@@ -1,6 +1,6 @@
 // tslint:disable
 export type Maybe<T> = T | null;
-
+export type Exact<T extends { [key: string]: unknown }> = { [K in keyof T]: T[K] };
 /** All built-in and custom scalars, mapped to their actual values */
 export type Scalars = {
     ID: string;
@@ -8,8 +8,11 @@ export type Scalars = {
     Boolean: boolean;
     Int: number;
     Float: number;
+    /** A date-time string at UTC, such as 2007-12-03T10:15:30Z, compliant with the `date-time` format outlined in section 5.6 of the RFC 3339 profile of the ISO 8601 standard for representation of dates and times using the Gregorian calendar. */
     DateTime: any;
+    /** The `JSON` scalar type represents JSON values as specified by [ECMA-404](http://www.ecma-international.org/publications/files/ECMA-ST/ECMA-404.pdf). */
     JSON: any;
+    /** The `Upload` scalar type represents a file upload. */
     Upload: any;
 };
 
@@ -804,6 +807,10 @@ export enum DeletionResult {
     NOT_DELETED = 'NOT_DELETED',
 }
 
+export enum ErrorCode {
+    UnknownError = 'UnknownError',
+}
+
 export type Facet = Node & {
     __typename?: 'Facet';
     id: Scalars['ID'];
@@ -2424,10 +2431,10 @@ export type TestOrderFragmentFragment = { __typename?: 'Order' } & Pick<
         };
     };
 
-export type AddItemToOrderMutationVariables = {
+export type AddItemToOrderMutationVariables = Exact<{
     productVariantId: Scalars['ID'];
     quantity: Scalars['Int'];
-};
+}>;
 
 export type AddItemToOrderMutation = { __typename?: 'Mutation' } & {
     addItemToOrder?: Maybe<
@@ -2453,9 +2460,9 @@ export type AddItemToOrderMutation = { __typename?: 'Mutation' } & {
     >;
 };
 
-export type SearchProductsShopQueryVariables = {
+export type SearchProductsShopQueryVariables = Exact<{
     input: SearchInput;
-};
+}>;
 
 export type SearchProductsShopQuery = { __typename?: 'Query' } & {
     search: { __typename?: 'SearchResponse' } & Pick<SearchResponse, 'totalItems'> & {
@@ -2479,16 +2486,16 @@ export type SearchProductsShopQuery = { __typename?: 'Query' } & {
         };
 };
 
-export type RegisterMutationVariables = {
+export type RegisterMutationVariables = Exact<{
     input: RegisterCustomerInput;
-};
+}>;
 
 export type RegisterMutation = { __typename?: 'Mutation' } & Pick<Mutation, 'registerCustomerAccount'>;
 
-export type VerifyMutationVariables = {
+export type VerifyMutationVariables = Exact<{
     password?: Maybe<Scalars['String']>;
     token: Scalars['String'];
-};
+}>;
 
 export type VerifyMutation = { __typename?: 'Mutation' } & {
     verifyCustomerAccount: { __typename?: 'LoginResult' } & {
@@ -2496,28 +2503,28 @@ export type VerifyMutation = { __typename?: 'Mutation' } & {
     };
 };
 
-export type RefreshTokenMutationVariables = {
+export type RefreshTokenMutationVariables = Exact<{
     emailAddress: Scalars['String'];
-};
+}>;
 
 export type RefreshTokenMutation = { __typename?: 'Mutation' } & Pick<
     Mutation,
     'refreshCustomerVerification'
 >;
 
-export type RequestPasswordResetMutationVariables = {
+export type RequestPasswordResetMutationVariables = Exact<{
     identifier: Scalars['String'];
-};
+}>;
 
 export type RequestPasswordResetMutation = { __typename?: 'Mutation' } & Pick<
     Mutation,
     'requestPasswordReset'
 >;
 
-export type ResetPasswordMutationVariables = {
+export type ResetPasswordMutationVariables = Exact<{
     token: Scalars['String'];
     password: Scalars['String'];
-};
+}>;
 
 export type ResetPasswordMutation = { __typename?: 'Mutation' } & {
     resetPassword: { __typename?: 'LoginResult' } & {
@@ -2525,34 +2532,34 @@ export type ResetPasswordMutation = { __typename?: 'Mutation' } & {
     };
 };
 
-export type RequestUpdateEmailAddressMutationVariables = {
+export type RequestUpdateEmailAddressMutationVariables = Exact<{
     password: Scalars['String'];
     newEmailAddress: Scalars['String'];
-};
+}>;
 
 export type RequestUpdateEmailAddressMutation = { __typename?: 'Mutation' } & Pick<
     Mutation,
     'requestUpdateCustomerEmailAddress'
 >;
 
-export type UpdateEmailAddressMutationVariables = {
+export type UpdateEmailAddressMutationVariables = Exact<{
     token: Scalars['String'];
-};
+}>;
 
 export type UpdateEmailAddressMutation = { __typename?: 'Mutation' } & Pick<
     Mutation,
     'updateCustomerEmailAddress'
 >;
 
-export type GetActiveCustomerQueryVariables = {};
+export type GetActiveCustomerQueryVariables = Exact<{ [key: string]: never }>;
 
 export type GetActiveCustomerQuery = { __typename?: 'Query' } & {
     activeCustomer?: Maybe<{ __typename?: 'Customer' } & Pick<Customer, 'id' | 'emailAddress'>>;
 };
 
-export type CreateAddressShopMutationVariables = {
+export type CreateAddressShopMutationVariables = Exact<{
     input: CreateAddressInput;
-};
+}>;
 
 export type CreateAddressShopMutation = { __typename?: 'Mutation' } & {
     createCustomerAddress: { __typename?: 'Address' } & Pick<Address, 'id' | 'streetLine1'> & {
@@ -2560,9 +2567,9 @@ export type CreateAddressShopMutation = { __typename?: 'Mutation' } & {
         };
 };
 
-export type UpdateAddressShopMutationVariables = {
+export type UpdateAddressShopMutationVariables = Exact<{
     input: UpdateAddressInput;
-};
+}>;
 
 export type UpdateAddressShopMutation = { __typename?: 'Mutation' } & {
     updateCustomerAddress: { __typename?: 'Address' } & Pick<Address, 'streetLine1'> & {
@@ -2570,51 +2577,51 @@ export type UpdateAddressShopMutation = { __typename?: 'Mutation' } & {
         };
 };
 
-export type DeleteAddressShopMutationVariables = {
+export type DeleteAddressShopMutationVariables = Exact<{
     id: Scalars['ID'];
-};
+}>;
 
 export type DeleteAddressShopMutation = { __typename?: 'Mutation' } & Pick<Mutation, 'deleteCustomerAddress'>;
 
-export type UpdateCustomerMutationVariables = {
+export type UpdateCustomerMutationVariables = Exact<{
     input: UpdateCustomerInput;
-};
+}>;
 
 export type UpdateCustomerMutation = { __typename?: 'Mutation' } & {
     updateCustomer: { __typename?: 'Customer' } & Pick<Customer, 'id' | 'firstName' | 'lastName'>;
 };
 
-export type UpdatePasswordMutationVariables = {
+export type UpdatePasswordMutationVariables = Exact<{
     old: Scalars['String'];
     new: Scalars['String'];
-};
+}>;
 
 export type UpdatePasswordMutation = { __typename?: 'Mutation' } & Pick<Mutation, 'updateCustomerPassword'>;
 
-export type GetActiveOrderQueryVariables = {};
+export type GetActiveOrderQueryVariables = Exact<{ [key: string]: never }>;
 
 export type GetActiveOrderQuery = { __typename?: 'Query' } & {
     activeOrder?: Maybe<{ __typename?: 'Order' } & TestOrderFragmentFragment>;
 };
 
-export type AdjustItemQuantityMutationVariables = {
+export type AdjustItemQuantityMutationVariables = Exact<{
     orderLineId: Scalars['ID'];
     quantity: Scalars['Int'];
-};
+}>;
 
 export type AdjustItemQuantityMutation = { __typename?: 'Mutation' } & {
     adjustOrderLine?: Maybe<{ __typename?: 'Order' } & TestOrderFragmentFragment>;
 };
 
-export type RemoveItemFromOrderMutationVariables = {
+export type RemoveItemFromOrderMutationVariables = Exact<{
     orderLineId: Scalars['ID'];
-};
+}>;
 
 export type RemoveItemFromOrderMutation = { __typename?: 'Mutation' } & {
     removeOrderLine?: Maybe<{ __typename?: 'Order' } & TestOrderFragmentFragment>;
 };
 
-export type GetShippingMethodsQueryVariables = {};
+export type GetShippingMethodsQueryVariables = Exact<{ [key: string]: never }>;
 
 export type GetShippingMethodsQuery = { __typename?: 'Query' } & {
     eligibleShippingMethods: Array<
@@ -2622,9 +2629,9 @@ export type GetShippingMethodsQuery = { __typename?: 'Query' } & {
     >;
 };
 
-export type SetShippingMethodMutationVariables = {
+export type SetShippingMethodMutationVariables = Exact<{
     id: Scalars['ID'];
-};
+}>;
 
 export type SetShippingMethodMutation = { __typename?: 'Mutation' } & {
     setOrderShippingMethod?: Maybe<
@@ -2636,9 +2643,9 @@ export type SetShippingMethodMutation = { __typename?: 'Mutation' } & {
     >;
 };
 
-export type SetCustomerForOrderMutationVariables = {
+export type SetCustomerForOrderMutationVariables = Exact<{
     input: CreateCustomerInput;
-};
+}>;
 
 export type SetCustomerForOrderMutation = { __typename?: 'Mutation' } & {
     setCustomerForOrder?: Maybe<
@@ -2653,17 +2660,17 @@ export type SetCustomerForOrderMutation = { __typename?: 'Mutation' } & {
     >;
 };
 
-export type GetOrderByCodeQueryVariables = {
+export type GetOrderByCodeQueryVariables = Exact<{
     code: Scalars['String'];
-};
+}>;
 
 export type GetOrderByCodeQuery = { __typename?: 'Query' } & {
     orderByCode?: Maybe<{ __typename?: 'Order' } & TestOrderFragmentFragment>;
 };
 
-export type GetOrderPromotionsByCodeQueryVariables = {
+export type GetOrderPromotionsByCodeQueryVariables = Exact<{
     code: Scalars['String'];
-};
+}>;
 
 export type GetOrderPromotionsByCodeQuery = { __typename?: 'Query' } & {
     orderByCode?: Maybe<
@@ -2673,23 +2680,23 @@ export type GetOrderPromotionsByCodeQuery = { __typename?: 'Query' } & {
     >;
 };
 
-export type GetAvailableCountriesQueryVariables = {};
+export type GetAvailableCountriesQueryVariables = Exact<{ [key: string]: never }>;
 
 export type GetAvailableCountriesQuery = { __typename?: 'Query' } & {
     availableCountries: Array<{ __typename?: 'Country' } & Pick<Country, 'id' | 'code'>>;
 };
 
-export type TransitionToStateMutationVariables = {
+export type TransitionToStateMutationVariables = Exact<{
     state: Scalars['String'];
-};
+}>;
 
 export type TransitionToStateMutation = { __typename?: 'Mutation' } & {
     transitionOrderToState?: Maybe<{ __typename?: 'Order' } & Pick<Order, 'id' | 'state'>>;
 };
 
-export type SetShippingAddressMutationVariables = {
+export type SetShippingAddressMutationVariables = Exact<{
     input: CreateAddressInput;
-};
+}>;
 
 export type SetShippingAddressMutation = { __typename?: 'Mutation' } & {
     setOrderShippingAddress?: Maybe<
@@ -2712,9 +2719,9 @@ export type SetShippingAddressMutation = { __typename?: 'Mutation' } & {
     >;
 };
 
-export type SetBillingAddressMutationVariables = {
+export type SetBillingAddressMutationVariables = Exact<{
     input: CreateAddressInput;
-};
+}>;
 
 export type SetBillingAddressMutation = { __typename?: 'Mutation' } & {
     setOrderBillingAddress?: Maybe<
@@ -2737,9 +2744,9 @@ export type SetBillingAddressMutation = { __typename?: 'Mutation' } & {
     >;
 };
 
-export type AddPaymentToOrderMutationVariables = {
+export type AddPaymentToOrderMutationVariables = Exact<{
     input: PaymentInput;
-};
+}>;
 
 export type AddPaymentToOrderMutation = { __typename?: 'Mutation' } & {
     addPaymentToOrder?: Maybe<
@@ -2756,7 +2763,7 @@ export type AddPaymentToOrderMutation = { __typename?: 'Mutation' } & {
     >;
 };
 
-export type GetActiveOrderPaymentsQueryVariables = {};
+export type GetActiveOrderPaymentsQueryVariables = Exact<{ [key: string]: never }>;
 
 export type GetActiveOrderPaymentsQuery = { __typename?: 'Query' } & {
     activeOrder?: Maybe<
@@ -2779,11 +2786,11 @@ export type GetActiveOrderPaymentsQuery = { __typename?: 'Query' } & {
     >;
 };
 
-export type GetNextOrderStatesQueryVariables = {};
+export type GetNextOrderStatesQueryVariables = Exact<{ [key: string]: never }>;
 
 export type GetNextOrderStatesQuery = { __typename?: 'Query' } & Pick<Query, 'nextOrderStates'>;
 
-export type GetCustomerAddressesQueryVariables = {};
+export type GetCustomerAddressesQueryVariables = Exact<{ [key: string]: never }>;
 
 export type GetCustomerAddressesQuery = { __typename?: 'Query' } & {
     activeOrder?: Maybe<
@@ -2799,7 +2806,7 @@ export type GetCustomerAddressesQuery = { __typename?: 'Query' } & {
     >;
 };
 
-export type GetCustomerOrdersQueryVariables = {};
+export type GetCustomerOrdersQueryVariables = Exact<{ [key: string]: never }>;
 
 export type GetCustomerOrdersQuery = { __typename?: 'Query' } & {
     activeOrder?: Maybe<
@@ -2815,23 +2822,23 @@ export type GetCustomerOrdersQuery = { __typename?: 'Query' } & {
     >;
 };
 
-export type ApplyCouponCodeMutationVariables = {
+export type ApplyCouponCodeMutationVariables = Exact<{
     couponCode: Scalars['String'];
-};
+}>;
 
 export type ApplyCouponCodeMutation = { __typename?: 'Mutation' } & {
     applyCouponCode?: Maybe<{ __typename?: 'Order' } & TestOrderFragmentFragment>;
 };
 
-export type RemoveCouponCodeMutationVariables = {
+export type RemoveCouponCodeMutationVariables = Exact<{
     couponCode: Scalars['String'];
-};
+}>;
 
 export type RemoveCouponCodeMutation = { __typename?: 'Mutation' } & {
     removeCouponCode?: Maybe<{ __typename?: 'Order' } & TestOrderFragmentFragment>;
 };
 
-export type RemoveAllOrderLinesMutationVariables = {};
+export type RemoveAllOrderLinesMutationVariables = Exact<{ [key: string]: never }>;
 
 export type RemoveAllOrderLinesMutation = { __typename?: 'Mutation' } & {
     removeAllOrderLines?: Maybe<{ __typename?: 'Order' } & TestOrderFragmentFragment>;
@@ -2839,52 +2846,72 @@ export type RemoveAllOrderLinesMutation = { __typename?: 'Mutation' } & {
 
 type DiscriminateUnion<T, U> = T extends U ? T : never;
 
-type RequireField<T, TNames extends string> = T & { [P in TNames]: (T & { [name: string]: never })[P] };
-
 export namespace TestOrderFragment {
     export type Fragment = TestOrderFragmentFragment;
-    export type Adjustments = NonNullable<TestOrderFragmentFragment['adjustments'][0]>;
-    export type Lines = NonNullable<TestOrderFragmentFragment['lines'][0]>;
-    export type ProductVariant = NonNullable<TestOrderFragmentFragment['lines'][0]>['productVariant'];
+    export type Adjustments = NonNullable<NonNullable<TestOrderFragmentFragment['adjustments']>[number]>;
+    export type Lines = NonNullable<NonNullable<TestOrderFragmentFragment['lines']>[number]>;
+    export type ProductVariant = NonNullable<
+        NonNullable<NonNullable<TestOrderFragmentFragment['lines']>[number]>['productVariant']
+    >;
     export type _Adjustments = NonNullable<
-        NonNullable<TestOrderFragmentFragment['lines'][0]>['adjustments'][0]
+        NonNullable<
+            NonNullable<NonNullable<TestOrderFragmentFragment['lines']>[number]>['adjustments']
+        >[number]
     >;
     export type ShippingMethod = NonNullable<TestOrderFragmentFragment['shippingMethod']>;
     export type Customer = NonNullable<TestOrderFragmentFragment['customer']>;
     export type User = NonNullable<NonNullable<TestOrderFragmentFragment['customer']>['user']>;
-    export type History = TestOrderFragmentFragment['history'];
-    export type Items = NonNullable<TestOrderFragmentFragment['history']['items'][0]>;
+    export type History = NonNullable<TestOrderFragmentFragment['history']>;
+    export type Items = NonNullable<
+        NonNullable<NonNullable<TestOrderFragmentFragment['history']>['items']>[number]
+    >;
 }
 
 export namespace AddItemToOrder {
     export type Variables = AddItemToOrderMutationVariables;
     export type Mutation = AddItemToOrderMutation;
     export type AddItemToOrder = NonNullable<AddItemToOrderMutation['addItemToOrder']>;
-    export type Lines = NonNullable<NonNullable<AddItemToOrderMutation['addItemToOrder']>['lines'][0]>;
+    export type Lines = NonNullable<
+        NonNullable<NonNullable<AddItemToOrderMutation['addItemToOrder']>['lines']>[number]
+    >;
     export type ProductVariant = NonNullable<
-        NonNullable<AddItemToOrderMutation['addItemToOrder']>['lines'][0]
-    >['productVariant'];
+        NonNullable<
+            NonNullable<NonNullable<AddItemToOrderMutation['addItemToOrder']>['lines']>[number]
+        >['productVariant']
+    >;
     export type Adjustments = NonNullable<
-        NonNullable<NonNullable<AddItemToOrderMutation['addItemToOrder']>['lines'][0]>['adjustments'][0]
+        NonNullable<
+            NonNullable<
+                NonNullable<NonNullable<AddItemToOrderMutation['addItemToOrder']>['lines']>[number]
+            >['adjustments']
+        >[number]
     >;
     export type _Adjustments = NonNullable<
-        NonNullable<AddItemToOrderMutation['addItemToOrder']>['adjustments'][0]
+        NonNullable<NonNullable<AddItemToOrderMutation['addItemToOrder']>['adjustments']>[number]
     >;
 }
 
 export namespace SearchProductsShop {
     export type Variables = SearchProductsShopQueryVariables;
     export type Query = SearchProductsShopQuery;
-    export type Search = SearchProductsShopQuery['search'];
-    export type Items = NonNullable<SearchProductsShopQuery['search']['items'][0]>;
-    export type Price = NonNullable<SearchProductsShopQuery['search']['items'][0]>['price'];
+    export type Search = NonNullable<SearchProductsShopQuery['search']>;
+    export type Items = NonNullable<
+        NonNullable<NonNullable<SearchProductsShopQuery['search']>['items']>[number]
+    >;
+    export type Price = NonNullable<
+        NonNullable<NonNullable<NonNullable<SearchProductsShopQuery['search']>['items']>[number]>['price']
+    >;
     export type SinglePriceInlineFragment = DiscriminateUnion<
-        RequireField<NonNullable<SearchProductsShopQuery['search']['items'][0]>['price'], '__typename'>,
-        { __typename: 'SinglePrice' }
+        NonNullable<
+            NonNullable<NonNullable<NonNullable<SearchProductsShopQuery['search']>['items']>[number]>['price']
+        >,
+        { __typename?: 'SinglePrice' }
     >;
     export type PriceRangeInlineFragment = DiscriminateUnion<
-        RequireField<NonNullable<SearchProductsShopQuery['search']['items'][0]>['price'], '__typename'>,
-        { __typename: 'PriceRange' }
+        NonNullable<
+            NonNullable<NonNullable<NonNullable<SearchProductsShopQuery['search']>['items']>[number]>['price']
+        >,
+        { __typename?: 'PriceRange' }
     >;
 }
 
@@ -2896,8 +2923,8 @@ export namespace Register {
 export namespace Verify {
     export type Variables = VerifyMutationVariables;
     export type Mutation = VerifyMutation;
-    export type VerifyCustomerAccount = VerifyMutation['verifyCustomerAccount'];
-    export type User = VerifyMutation['verifyCustomerAccount']['user'];
+    export type VerifyCustomerAccount = NonNullable<VerifyMutation['verifyCustomerAccount']>;
+    export type User = NonNullable<NonNullable<VerifyMutation['verifyCustomerAccount']>['user']>;
 }
 
 export namespace RefreshToken {
@@ -2913,8 +2940,8 @@ export namespace RequestPasswordReset {
 export namespace ResetPassword {
     export type Variables = ResetPasswordMutationVariables;
     export type Mutation = ResetPasswordMutation;
-    export type ResetPassword = ResetPasswordMutation['resetPassword'];
-    export type User = ResetPasswordMutation['resetPassword']['user'];
+    export type ResetPassword = NonNullable<ResetPasswordMutation['resetPassword']>;
+    export type User = NonNullable<NonNullable<ResetPasswordMutation['resetPassword']>['user']>;
 }
 
 export namespace RequestUpdateEmailAddress {
@@ -2936,15 +2963,19 @@ export namespace GetActiveCustomer {
 export namespace CreateAddressShop {
     export type Variables = CreateAddressShopMutationVariables;
     export type Mutation = CreateAddressShopMutation;
-    export type CreateCustomerAddress = CreateAddressShopMutation['createCustomerAddress'];
-    export type Country = CreateAddressShopMutation['createCustomerAddress']['country'];
+    export type CreateCustomerAddress = NonNullable<CreateAddressShopMutation['createCustomerAddress']>;
+    export type Country = NonNullable<
+        NonNullable<CreateAddressShopMutation['createCustomerAddress']>['country']
+    >;
 }
 
 export namespace UpdateAddressShop {
     export type Variables = UpdateAddressShopMutationVariables;
     export type Mutation = UpdateAddressShopMutation;
-    export type UpdateCustomerAddress = UpdateAddressShopMutation['updateCustomerAddress'];
-    export type Country = UpdateAddressShopMutation['updateCustomerAddress']['country'];
+    export type UpdateCustomerAddress = NonNullable<UpdateAddressShopMutation['updateCustomerAddress']>;
+    export type Country = NonNullable<
+        NonNullable<UpdateAddressShopMutation['updateCustomerAddress']>['country']
+    >;
 }
 
 export namespace DeleteAddressShop {
@@ -2955,7 +2986,7 @@ export namespace DeleteAddressShop {
 export namespace UpdateCustomer {
     export type Variables = UpdateCustomerMutationVariables;
     export type Mutation = UpdateCustomerMutation;
-    export type UpdateCustomer = UpdateCustomerMutation['updateCustomer'];
+    export type UpdateCustomer = NonNullable<UpdateCustomerMutation['updateCustomer']>;
 }
 
 export namespace UpdatePassword {
@@ -2966,25 +2997,27 @@ export namespace UpdatePassword {
 export namespace GetActiveOrder {
     export type Variables = GetActiveOrderQueryVariables;
     export type Query = GetActiveOrderQuery;
-    export type ActiveOrder = TestOrderFragmentFragment;
+    export type ActiveOrder = NonNullable<GetActiveOrderQuery['activeOrder']>;
 }
 
 export namespace AdjustItemQuantity {
     export type Variables = AdjustItemQuantityMutationVariables;
     export type Mutation = AdjustItemQuantityMutation;
-    export type AdjustOrderLine = TestOrderFragmentFragment;
+    export type AdjustOrderLine = NonNullable<AdjustItemQuantityMutation['adjustOrderLine']>;
 }
 
 export namespace RemoveItemFromOrder {
     export type Variables = RemoveItemFromOrderMutationVariables;
     export type Mutation = RemoveItemFromOrderMutation;
-    export type RemoveOrderLine = TestOrderFragmentFragment;
+    export type RemoveOrderLine = NonNullable<RemoveItemFromOrderMutation['removeOrderLine']>;
 }
 
 export namespace GetShippingMethods {
     export type Variables = GetShippingMethodsQueryVariables;
     export type Query = GetShippingMethodsQuery;
-    export type EligibleShippingMethods = NonNullable<GetShippingMethodsQuery['eligibleShippingMethods'][0]>;
+    export type EligibleShippingMethods = NonNullable<
+        NonNullable<GetShippingMethodsQuery['eligibleShippingMethods']>[number]
+    >;
 }
 
 export namespace SetShippingMethod {
@@ -3008,22 +3041,24 @@ export namespace SetCustomerForOrder {
 export namespace GetOrderByCode {
     export type Variables = GetOrderByCodeQueryVariables;
     export type Query = GetOrderByCodeQuery;
-    export type OrderByCode = TestOrderFragmentFragment;
+    export type OrderByCode = NonNullable<GetOrderByCodeQuery['orderByCode']>;
 }
 
 export namespace GetOrderPromotionsByCode {
     export type Variables = GetOrderPromotionsByCodeQueryVariables;
     export type Query = GetOrderPromotionsByCodeQuery;
-    export type OrderByCode = TestOrderFragmentFragment;
+    export type OrderByCode = NonNullable<GetOrderPromotionsByCodeQuery['orderByCode']>;
     export type Promotions = NonNullable<
-        NonNullable<GetOrderPromotionsByCodeQuery['orderByCode']>['promotions'][0]
+        NonNullable<NonNullable<GetOrderPromotionsByCodeQuery['orderByCode']>['promotions']>[number]
     >;
 }
 
 export namespace GetAvailableCountries {
     export type Variables = GetAvailableCountriesQueryVariables;
     export type Query = GetAvailableCountriesQuery;
-    export type AvailableCountries = NonNullable<GetAvailableCountriesQuery['availableCountries'][0]>;
+    export type AvailableCountries = NonNullable<
+        NonNullable<GetAvailableCountriesQuery['availableCountries']>[number]
+    >;
 }
 
 export namespace TransitionToState {
@@ -3053,9 +3088,9 @@ export namespace SetBillingAddress {
 export namespace AddPaymentToOrder {
     export type Variables = AddPaymentToOrderMutationVariables;
     export type Mutation = AddPaymentToOrderMutation;
-    export type AddPaymentToOrder = TestOrderFragmentFragment;
+    export type AddPaymentToOrder = NonNullable<AddPaymentToOrderMutation['addPaymentToOrder']>;
     export type Payments = NonNullable<
-        NonNullable<NonNullable<AddPaymentToOrderMutation['addPaymentToOrder']>['payments']>[0]
+        NonNullable<NonNullable<AddPaymentToOrderMutation['addPaymentToOrder']>['payments']>[number]
     >;
 }
 
@@ -3064,7 +3099,7 @@ export namespace GetActiveOrderPayments {
     export type Query = GetActiveOrderPaymentsQuery;
     export type ActiveOrder = NonNullable<GetActiveOrderPaymentsQuery['activeOrder']>;
     export type Payments = NonNullable<
-        NonNullable<NonNullable<GetActiveOrderPaymentsQuery['activeOrder']>['payments']>[0]
+        NonNullable<NonNullable<GetActiveOrderPaymentsQuery['activeOrder']>['payments']>[number]
     >;
 }
 
@@ -3081,7 +3116,7 @@ export namespace GetCustomerAddresses {
     export type Addresses = NonNullable<
         NonNullable<
             NonNullable<NonNullable<GetCustomerAddressesQuery['activeOrder']>['customer']>['addresses']
-        >[0]
+        >[number]
     >;
 }
 
@@ -3091,27 +3126,31 @@ export namespace GetCustomerOrders {
     export type ActiveOrder = NonNullable<GetCustomerOrdersQuery['activeOrder']>;
     export type Customer = NonNullable<NonNullable<GetCustomerOrdersQuery['activeOrder']>['customer']>;
     export type Orders = NonNullable<
-        NonNullable<GetCustomerOrdersQuery['activeOrder']>['customer']
-    >['orders'];
+        NonNullable<NonNullable<GetCustomerOrdersQuery['activeOrder']>['customer']>['orders']
+    >;
     export type Items = NonNullable<
-        NonNullable<NonNullable<GetCustomerOrdersQuery['activeOrder']>['customer']>['orders']['items'][0]
+        NonNullable<
+            NonNullable<
+                NonNullable<NonNullable<GetCustomerOrdersQuery['activeOrder']>['customer']>['orders']
+            >['items']
+        >[number]
     >;
 }
 
 export namespace ApplyCouponCode {
     export type Variables = ApplyCouponCodeMutationVariables;
     export type Mutation = ApplyCouponCodeMutation;
-    export type ApplyCouponCode = TestOrderFragmentFragment;
+    export type ApplyCouponCode = NonNullable<ApplyCouponCodeMutation['applyCouponCode']>;
 }
 
 export namespace RemoveCouponCode {
     export type Variables = RemoveCouponCodeMutationVariables;
     export type Mutation = RemoveCouponCodeMutation;
-    export type RemoveCouponCode = TestOrderFragmentFragment;
+    export type RemoveCouponCode = NonNullable<RemoveCouponCodeMutation['removeCouponCode']>;
 }
 
 export namespace RemoveAllOrderLines {
     export type Variables = RemoveAllOrderLinesMutationVariables;
     export type Mutation = RemoveAllOrderLinesMutation;
-    export type RemoveAllOrderLines = TestOrderFragmentFragment;
+    export type RemoveAllOrderLines = NonNullable<RemoveAllOrderLinesMutation['removeAllOrderLines']>;
 }

+ 6 - 3
packages/core/e2e/graphql/shared-definitions.ts

@@ -346,13 +346,16 @@ export const REMOVE_PRODUCT_FROM_CHANNEL = gql`
     }
     ${PRODUCT_WITH_VARIANTS_FRAGMENT}
 `;
+
 export const UPDATE_ASSET = gql`
     mutation UpdateAsset($input: UpdateAssetInput!) {
         updateAsset(input: $input) {
             ...Asset
-            focalPoint {
-                x
-                y
+            ... on Asset {
+                focalPoint {
+                    x
+                    y
+                }
             }
         }
     }

+ 15 - 0
packages/core/src/api/config/configure-graphql-module.ts

@@ -8,6 +8,11 @@ import { GraphQLDateTime } from 'graphql-iso-date';
 import GraphQLJSON from 'graphql-type-json';
 import path from 'path';
 
+import {
+    adminErrorOperationTypeResolvers,
+    ErrorResult,
+} from '../../common/error/generated-graphql-admin-errors';
+import { shopErrorOperationTypeResolvers } from '../../common/error/generated-graphql-shop-errors';
 import { ConfigModule } from '../../config/config.module';
 import { ConfigService } from '../../config/config.service';
 import { I18nModule } from '../../i18n/i18n.module';
@@ -21,6 +26,7 @@ import { IdCodecPlugin } from '../middleware/id-codec-plugin';
 import { TranslateErrorsPlugin } from '../middleware/translate-errors-plugin';
 
 import { generateAuthenticationTypes } from './generate-auth-types';
+import { generateErrorCodeEnum } from './generate-error-code-enum';
 import { generateListOptions } from './generate-list-options';
 import {
     addGraphQLCustomFields,
@@ -133,6 +139,14 @@ async function createGraphQLOptions(
             StockMovement: stockMovementResolveType,
             CustomFieldConfig: customFieldsConfigResolveType,
             CustomField: customFieldsConfigResolveType,
+            ErrorResult: {
+                __resolveType(value: ErrorResult) {
+                    return value.__typename;
+                },
+            },
+            ...(options.apiType === 'admin'
+                ? adminErrorOperationTypeResolvers
+                : shopErrorOperationTypeResolvers),
         },
         uploads: {
             maxFileSize: configService.assetOptions.uploadMaxFileSize,
@@ -177,6 +191,7 @@ async function createGraphQLOptions(
         schema = addServerConfigCustomFields(schema, customFields);
         schema = addOrderLineCustomFieldsInput(schema, customFields.OrderLine || []);
         schema = generateAuthenticationTypes(schema, authStrategies);
+        schema = generateErrorCodeEnum(schema);
         if (apiType === 'shop') {
             schema = addRegisterCustomerCustomFieldsInput(schema, customFields.Customer || []);
         }

+ 29 - 0
packages/core/src/api/config/generate-error-code-enum.ts

@@ -0,0 +1,29 @@
+import { buildSchema, extendSchema, GraphQLSchema, parse } from 'graphql';
+
+export const ERROR_INTERFACE_NAME = 'ErrorResult';
+
+/**
+ * Generates the members of the `ErrorCode` enum dynamically, by getting the names of
+ * all the types which inherit from the `ErrorResult` interface.
+ */
+export function generateErrorCodeEnum(typeDefsOrSchema: string | GraphQLSchema): GraphQLSchema {
+    const schema = typeof typeDefsOrSchema === 'string' ? buildSchema(typeDefsOrSchema) : typeDefsOrSchema;
+    const errorNodes = Object.values(schema.getTypeMap())
+        .map(type => type.astNode)
+        .filter(node => {
+            return (
+                node &&
+                node?.kind === 'ObjectTypeDefinition' &&
+                node.interfaces?.map(i => i.name.value).includes(ERROR_INTERFACE_NAME)
+            );
+        });
+    if (!errorNodes.length) {
+        return schema;
+    }
+
+    const errorCodeEnum = `
+        extend enum ErrorCode {
+            ${errorNodes.map(n => n?.name.value).join('\n')}
+        }`;
+    return extendSchema(schema, parse(errorCodeEnum));
+}

+ 12 - 3
packages/core/src/api/middleware/asset-interceptor-plugin.ts

@@ -1,6 +1,6 @@
 import { Asset } from '@vendure/common/lib/generated-types';
 import { ApolloServerPlugin, GraphQLRequestListener, GraphQLServiceContext } from 'apollo-server-plugin-base';
-import { DocumentNode } from 'graphql';
+import { DocumentNode, GraphQLNamedType, isUnionType } from 'graphql';
 
 import { AssetStorageStrategy } from '../../config/asset-storage-strategy/asset-storage-strategy';
 import { ConfigService } from '../../config/config.service';
@@ -49,8 +49,12 @@ export class AssetInterceptorPlugin implements ApolloServerPlugin {
             return;
         }
         this.graphqlValueTransformer.transformValues(typeTree, data, (value, type) => {
-            const isAssetType = type && (type.name === 'Asset' || type.name === 'SearchResultAsset');
-            if (isAssetType) {
+            if (!type) {
+                return value;
+            }
+            const isAssetType = this.isAssetType(type);
+            const isUnionWithAssetType = isUnionType(type) && type.getTypes().find(t => this.isAssetType(t));
+            if (isAssetType || isUnionWithAssetType) {
                 if (value && !Array.isArray(value)) {
                     if (value.preview) {
                         value.preview = toAbsoluteUrl(request, value.preview);
@@ -77,4 +81,9 @@ export class AssetInterceptorPlugin implements ApolloServerPlugin {
             return value;
         });
     }
+
+    private isAssetType(type: GraphQLNamedType): boolean {
+        const assetTypeNames = ['Asset', 'SearchResultAsset'];
+        return assetTypeNames.includes(type.name);
+    }
 }

+ 7 - 3
packages/core/src/api/resolvers/admin/asset.resolver.ts

@@ -1,5 +1,6 @@
 import { Args, Mutation, Query, Resolver } from '@nestjs/graphql';
 import {
+    CreateAssetResult,
     MutationCreateAssetsArgs,
     MutationDeleteAssetArgs,
     MutationDeleteAssetsArgs,
@@ -36,15 +37,18 @@ export class AssetResolver {
     @Transaction()
     @Mutation()
     @Allow(Permission.CreateCatalog)
-    async createAssets(@Ctx() ctx: RequestContext, @Args() args: MutationCreateAssetsArgs): Promise<Asset[]> {
+    async createAssets(
+        @Ctx() ctx: RequestContext,
+        @Args() args: MutationCreateAssetsArgs,
+    ): Promise<CreateAssetResult[]> {
         // TODO: Currently we validate _all_ mime types up-front due to limitations
         // with the existing error handling mechanisms. With a solution as described
         // in https://github.com/vendure-ecommerce/vendure/issues/437 we could defer
         // this check to the individual processing of a single Asset.
-        await this.assetService.validateInputMimeTypes(args.input);
+        // await this.assetService.validateInputMimeTypes(args.input);
         // TODO: Is there some way to parellelize this while still preserving
         // the order of files in the upload? Non-deterministic IDs mess up the e2e test snapshots.
-        const assets: Asset[] = [];
+        const assets: CreateAssetResult[] = [];
         for (const input of args.input) {
             const asset = await this.assetService.create(ctx, input);
             assets.push(asset);

+ 10 - 1
packages/core/src/api/schema/admin-api/asset.api.graphql

@@ -7,7 +7,7 @@ type Query {
 
 type Mutation {
     "Create a new Asset"
-    createAssets(input: [CreateAssetInput!]!): [Asset!]!
+    createAssets(input: [CreateAssetInput!]!): [CreateAssetResult!]!
     "Update an existing Asset"
     updateAsset(input: UpdateAssetInput!): Asset!
     "Delete an Asset"
@@ -16,6 +16,15 @@ type Mutation {
     deleteAssets(ids: [ID!]!, force: Boolean): DeletionResponse!
 }
 
+type MimeTypeError implements ErrorResult {
+    code: ErrorCode!
+    message: String!
+    fileName: String!
+    mimeType: String!
+}
+
+union CreateAssetResult = Asset | MimeTypeError
+
 # generated by generateListOptions function
 input AssetListOptions
 

+ 9 - 0
packages/core/src/api/schema/common/common-types.graphql

@@ -81,6 +81,15 @@ enum SortOrder {
     DESC
 }
 
+enum ErrorCode {
+    UnknownError
+}
+
+interface ErrorResult {
+    code: ErrorCode!
+    message: String!
+}
+
 input StringOperators {
     eq: String
     contains: String

+ 48 - 0
packages/core/src/common/error/generated-graphql-admin-errors.ts

@@ -0,0 +1,48 @@
+// tslint:disable
+/** This file was generated by the graphql-errors-plugin, which is part of the "codegen" npm script. */
+
+import { ErrorCode } from '@vendure/common/lib/generated-types';
+
+export type Scalars = {
+    ID: string;
+    String: string;
+    Boolean: boolean;
+    Int: number;
+    Float: number;
+    DateTime: any;
+    JSON: any;
+    Upload: any;
+};
+
+export class ErrorResult {
+    readonly __typename: string;
+    readonly code: ErrorCode;
+    message: Scalars['String'];
+}
+
+export class MimeTypeError extends ErrorResult {
+    readonly __typename = 'MimeTypeError';
+    readonly code = ErrorCode.MimeTypeError;
+    constructor(
+        public message: Scalars['String'],
+        public fileName: Scalars['String'],
+        public mimeType: Scalars['String'],
+    ) {
+        super();
+    }
+}
+
+const errorTypeNames = new Set(['MimeTypeError']);
+export function isGraphQLError(
+    input: any,
+): input is import('@vendure/common/lib/generated-types').ErrorResult {
+    return input instanceof ErrorResult || errorTypeNames.has(input.__typename);
+}
+
+export const adminErrorOperationTypeResolvers = {
+    CreateAssetResult: {
+        __resolveType(value: any) {
+            return isGraphQLError(value) ? (value as any).__typename : 'Asset';
+        },
+    },
+};

+ 17 - 0
packages/core/src/common/error/generated-graphql-shop-errors.ts

@@ -0,0 +1,17 @@
+// tslint:disable
+/** This file was generated by the graphql-errors-plugin, which is part of the "codegen" npm script. */
+
+import { ErrorCode } from '@vendure/common/lib/generated-types';
+
+export type Scalars = {
+    ID: string;
+    String: string;
+    Boolean: boolean;
+    Int: number;
+    Float: number;
+    DateTime: any;
+    JSON: any;
+    Upload: any;
+};
+
+export const shopErrorOperationTypeResolvers = {};

+ 1 - 0
packages/core/src/common/index.ts

@@ -2,6 +2,7 @@ export * from './finite-state-machine/finite-state-machine';
 export * from './finite-state-machine/types';
 export * from './async-queue';
 export * from './error/errors';
+export * from './error/generated-graphql-admin-errors';
 export * from './injector';
 export * from './ttl-cache';
 export * from './utils';

+ 1 - 1
packages/core/src/data-import/providers/asset-importer/asset-importer.ts

@@ -33,7 +33,7 @@ export class AssetImporter {
                     if (fileStat.isFile()) {
                         try {
                             const stream = fs.createReadStream(filename);
-                            const asset = await this.assetService.createFromFileStream(stream);
+                            const asset = (await this.assetService.createFromFileStream(stream)) as Asset;
                             this.assetMap.set(assetPath, asset);
                             assets.push(asset);
                         } catch (err) {

+ 6 - 6
packages/core/src/plugin/default-search-plugin/search-strategy/search-strategy-utils.ts

@@ -22,15 +22,15 @@ export function mapToSearchResult(raw: any, currencyCode: CurrencyCode): SearchR
             ? ({ min: raw.minPriceWithTax, max: raw.maxPriceWithTax } as PriceRange)
             : ({ value: raw.si_priceWithTax } as SinglePrice);
 
-    const productAsset: SearchResultAsset | null = !raw.si_productAssetId
-        ? null
+    const productAsset: SearchResultAsset | undefined = !raw.si_productAssetId
+        ? undefined
         : {
               id: raw.si_productAssetId,
               preview: raw.si_productPreview,
               focalPoint: parseFocalPoint(raw.si_productPreviewFocalPoint),
           };
-    const productVariantAsset: SearchResultAsset | null = !raw.si_productVariantAssetId
-        ? null
+    const productVariantAsset: SearchResultAsset | undefined = !raw.si_productVariantAssetId
+        ? undefined
         : {
               id: raw.si_productVariantAssetId,
               preview: raw.si_productVariantPreview,
@@ -79,7 +79,7 @@ export function createFacetIdCountMap(facetValuesResult: Array<{ facetValues: st
     return result;
 }
 
-function parseFocalPoint(focalPoint: any): Coordinate | null {
+function parseFocalPoint(focalPoint: any): Coordinate | undefined {
     if (focalPoint && typeof focalPoint === 'string') {
         try {
             return JSON.parse(focalPoint);
@@ -87,5 +87,5 @@ function parseFocalPoint(focalPoint: any): Coordinate | null {
             // fall though
         }
     }
-    return null;
+    return;
 }

+ 10 - 4
packages/core/src/service/services/asset.service.ts

@@ -2,8 +2,10 @@ import { Injectable } from '@nestjs/common';
 import {
     AssetType,
     CreateAssetInput,
+    CreateAssetResult,
     DeletionResponse,
     DeletionResult,
+    ErrorResult,
     UpdateAssetInput,
 } from '@vendure/common/lib/generated-types';
 import { ID, PaginatedList, Type } from '@vendure/common/lib/shared-types';
@@ -15,6 +17,7 @@ import { Stream } from 'stream';
 
 import { RequestContext } from '../../api/common/request-context';
 import { InternalServerError, UserInputError } from '../../common/error/errors';
+import { isGraphQLError, MimeTypeError } from '../../common/error/generated-graphql-admin-errors';
 import { ListQueryOptions } from '../../common/types/common-types';
 import { getAssetType, idsAreEqual } from '../../common/utils';
 import { ConfigService } from '../../config/config.service';
@@ -161,10 +164,13 @@ export class AssetService {
     /**
      * Create an Asset based on a file uploaded via the GraphQL API.
      */
-    async create(ctx: RequestContext, input: CreateAssetInput): Promise<Asset> {
+    async create(ctx: RequestContext, input: CreateAssetInput): Promise<CreateAssetResult> {
         const { createReadStream, filename, mimetype } = await input.file;
         const stream = createReadStream();
         const asset = await this.createAssetInternal(ctx, stream, filename, mimetype);
+        if (isGraphQLError(asset)) {
+            return asset;
+        }
         this.eventBus.publish(new AssetEvent(ctx, asset, 'created'));
         return asset;
     }
@@ -228,7 +234,7 @@ export class AssetService {
     /**
      * Create an Asset from a file stream created during data import.
      */
-    async createFromFileStream(stream: ReadStream): Promise<Asset> {
+    async createFromFileStream(stream: ReadStream): Promise<CreateAssetResult> {
         const filePath = stream.path;
         if (typeof filePath === 'string') {
             const filename = path.basename(filePath);
@@ -244,10 +250,10 @@ export class AssetService {
         stream: Stream,
         filename: string,
         mimetype: string,
-    ): Promise<Asset> {
+    ): Promise<CreateAssetResult> {
         const { assetOptions } = this.configService;
         if (!this.validateMimeType(mimetype)) {
-            throw new UserInputError('error.mime-type-not-permitted', { mimetype });
+            return new MimeTypeError('error.mime-type-not-permitted', filename, mimetype);
         }
         const { assetPreviewStrategy, assetStorageStrategy } = assetOptions;
         const sourceFileName = await this.getSourceFileName(filename);

+ 92 - 35
packages/elasticsearch-plugin/e2e/graphql/generated-e2e-elasticsearch-plugin-types.ts

@@ -1,6 +1,6 @@
 // tslint:disable
 export type Maybe<T> = T | null;
-
+export type Exact<T extends { [key: string]: unknown }> = { [K in keyof T]: T[K] };
 /** All built-in and custom scalars, mapped to their actual values */
 export type Scalars = {
     ID: string;
@@ -8,8 +8,11 @@ export type Scalars = {
     Boolean: boolean;
     Int: number;
     Float: number;
+    /** A date-time string at UTC, such as 2007-12-03T10:15:30Z, compliant with the `date-time` format outlined in section 5.6 of the RFC 3339 profile of the ISO 8601 standard for representation of dates and times using the Gregorian calendar. */
     DateTime: any;
+    /** The `JSON` scalar type represents JSON values as specified by [ECMA-404](http://www.ecma-international.org/publications/files/ECMA-ST/ECMA-404.pdf). */
     JSON: any;
+    /** The `Upload` scalar type represents a file upload. */
     Upload: any;
 };
 
@@ -443,6 +446,8 @@ export type CreateAssetInput = {
     file: Scalars['Upload'];
 };
 
+export type CreateAssetResult = Asset | MimeTypeError;
+
 export type CreateChannelInput = {
     code: Scalars['String'];
     token: Scalars['String'];
@@ -1119,6 +1124,16 @@ export enum DeletionResult {
     NOT_DELETED = 'NOT_DELETED',
 }
 
+export enum ErrorCode {
+    UnknownError = 'UnknownError',
+    MimeTypeError = 'MimeTypeError',
+}
+
+export type ErrorResult = {
+    code: ErrorCode;
+    message: Scalars['String'];
+};
+
 export type Facet = Node & {
     __typename?: 'Facet';
     isPrivate: Scalars['Boolean'];
@@ -1770,6 +1785,14 @@ export type LoginResult = {
     user: CurrentUser;
 };
 
+export type MimeTypeError = ErrorResult & {
+    __typename?: 'MimeTypeError';
+    code: ErrorCode;
+    message: Scalars['String'];
+    fileName: Scalars['String'];
+    mimeType: Scalars['String'];
+};
+
 export type MoveCollectionInput = {
     collectionId: Scalars['ID'];
     parentId: Scalars['ID'];
@@ -1787,7 +1810,7 @@ export type Mutation = {
     /** Assign a Role to an Administrator */
     assignRoleToAdministrator: Administrator;
     /** Create a new Asset */
-    createAssets: Array<Asset>;
+    createAssets: Array<CreateAssetResult>;
     /** Update an existing Asset */
     updateAsset: Asset;
     /** Delete an Asset */
@@ -3699,9 +3722,9 @@ export type Zone = Node & {
     members: Array<Country>;
 };
 
-export type SearchProductsAdminQueryVariables = {
+export type SearchProductsAdminQueryVariables = Exact<{
     input: SearchInput;
-};
+}>;
 
 export type SearchProductsAdminQuery = { __typename?: 'Query' } & {
     search: { __typename?: 'SearchResponse' } & Pick<SearchResponse, 'totalItems'> & {
@@ -3742,9 +3765,9 @@ export type SearchProductsAdminQuery = { __typename?: 'Query' } & {
         };
 };
 
-export type SearchFacetValuesQueryVariables = {
+export type SearchFacetValuesQueryVariables = Exact<{
     input: SearchInput;
-};
+}>;
 
 export type SearchFacetValuesQuery = { __typename?: 'Query' } & {
     search: { __typename?: 'SearchResponse' } & Pick<SearchResponse, 'totalItems'> & {
@@ -3756,9 +3779,9 @@ export type SearchFacetValuesQuery = { __typename?: 'Query' } & {
         };
 };
 
-export type SearchGetPricesQueryVariables = {
+export type SearchGetPricesQueryVariables = Exact<{
     input: SearchInput;
-};
+}>;
 
 export type SearchGetPricesQuery = { __typename?: 'Query' } & {
     search: { __typename?: 'SearchResponse' } & {
@@ -3775,7 +3798,7 @@ export type SearchGetPricesQuery = { __typename?: 'Query' } & {
     };
 };
 
-export type ReindexMutationVariables = {};
+export type ReindexMutationVariables = Exact<{ [key: string]: never }>;
 
 export type ReindexMutation = { __typename?: 'Mutation' } & {
     reindex: { __typename?: 'Job' } & Pick<
@@ -3784,9 +3807,9 @@ export type ReindexMutation = { __typename?: 'Mutation' } & {
     >;
 };
 
-export type GetJobInfoQueryVariables = {
+export type GetJobInfoQueryVariables = Exact<{
     id: Scalars['ID'];
-};
+}>;
 
 export type GetJobInfoQuery = { __typename?: 'Query' } & {
     job?: Maybe<
@@ -3796,25 +3819,35 @@ export type GetJobInfoQuery = { __typename?: 'Query' } & {
 
 type DiscriminateUnion<T, U> = T extends U ? T : never;
 
-type RequireField<T, TNames extends string> = T & { [P in TNames]: (T & { [name: string]: never })[P] };
-
 export namespace SearchProductsAdmin {
     export type Variables = SearchProductsAdminQueryVariables;
     export type Query = SearchProductsAdminQuery;
-    export type Search = SearchProductsAdminQuery['search'];
-    export type Items = NonNullable<SearchProductsAdminQuery['search']['items'][0]>;
+    export type Search = NonNullable<SearchProductsAdminQuery['search']>;
+    export type Items = NonNullable<
+        NonNullable<NonNullable<SearchProductsAdminQuery['search']>['items']>[number]
+    >;
     export type ProductAsset = NonNullable<
-        NonNullable<SearchProductsAdminQuery['search']['items'][0]>['productAsset']
+        NonNullable<
+            NonNullable<NonNullable<SearchProductsAdminQuery['search']>['items']>[number]
+        >['productAsset']
     >;
     export type FocalPoint = NonNullable<
-        NonNullable<NonNullable<SearchProductsAdminQuery['search']['items'][0]>['productAsset']>['focalPoint']
+        NonNullable<
+            NonNullable<
+                NonNullable<NonNullable<SearchProductsAdminQuery['search']>['items']>[number]
+            >['productAsset']
+        >['focalPoint']
     >;
     export type ProductVariantAsset = NonNullable<
-        NonNullable<SearchProductsAdminQuery['search']['items'][0]>['productVariantAsset']
+        NonNullable<
+            NonNullable<NonNullable<SearchProductsAdminQuery['search']>['items']>[number]
+        >['productVariantAsset']
     >;
     export type _FocalPoint = NonNullable<
         NonNullable<
-            NonNullable<SearchProductsAdminQuery['search']['items'][0]>['productVariantAsset']
+            NonNullable<
+                NonNullable<NonNullable<SearchProductsAdminQuery['search']>['items']>[number]
+            >['productVariantAsset']
         >['focalPoint']
     >;
 }
@@ -3822,40 +3855,64 @@ export namespace SearchProductsAdmin {
 export namespace SearchFacetValues {
     export type Variables = SearchFacetValuesQueryVariables;
     export type Query = SearchFacetValuesQuery;
-    export type Search = SearchFacetValuesQuery['search'];
-    export type FacetValues = NonNullable<SearchFacetValuesQuery['search']['facetValues'][0]>;
-    export type FacetValue = NonNullable<SearchFacetValuesQuery['search']['facetValues'][0]>['facetValue'];
+    export type Search = NonNullable<SearchFacetValuesQuery['search']>;
+    export type FacetValues = NonNullable<
+        NonNullable<NonNullable<SearchFacetValuesQuery['search']>['facetValues']>[number]
+    >;
+    export type FacetValue = NonNullable<
+        NonNullable<
+            NonNullable<NonNullable<SearchFacetValuesQuery['search']>['facetValues']>[number]
+        >['facetValue']
+    >;
 }
 
 export namespace SearchGetPrices {
     export type Variables = SearchGetPricesQueryVariables;
     export type Query = SearchGetPricesQuery;
-    export type Search = SearchGetPricesQuery['search'];
-    export type Items = NonNullable<SearchGetPricesQuery['search']['items'][0]>;
-    export type Price = NonNullable<SearchGetPricesQuery['search']['items'][0]>['price'];
+    export type Search = NonNullable<SearchGetPricesQuery['search']>;
+    export type Items = NonNullable<
+        NonNullable<NonNullable<SearchGetPricesQuery['search']>['items']>[number]
+    >;
+    export type Price = NonNullable<
+        NonNullable<NonNullable<NonNullable<SearchGetPricesQuery['search']>['items']>[number]>['price']
+    >;
     export type PriceRangeInlineFragment = DiscriminateUnion<
-        RequireField<NonNullable<SearchGetPricesQuery['search']['items'][0]>['price'], '__typename'>,
-        { __typename: 'PriceRange' }
+        NonNullable<
+            NonNullable<NonNullable<NonNullable<SearchGetPricesQuery['search']>['items']>[number]>['price']
+        >,
+        { __typename?: 'PriceRange' }
     >;
     export type SinglePriceInlineFragment = DiscriminateUnion<
-        RequireField<NonNullable<SearchGetPricesQuery['search']['items'][0]>['price'], '__typename'>,
-        { __typename: 'SinglePrice' }
+        NonNullable<
+            NonNullable<NonNullable<NonNullable<SearchGetPricesQuery['search']>['items']>[number]>['price']
+        >,
+        { __typename?: 'SinglePrice' }
+    >;
+    export type PriceWithTax = NonNullable<
+        NonNullable<NonNullable<NonNullable<SearchGetPricesQuery['search']>['items']>[number]>['priceWithTax']
     >;
-    export type PriceWithTax = NonNullable<SearchGetPricesQuery['search']['items'][0]>['priceWithTax'];
     export type _PriceRangeInlineFragment = DiscriminateUnion<
-        RequireField<NonNullable<SearchGetPricesQuery['search']['items'][0]>['priceWithTax'], '__typename'>,
-        { __typename: 'PriceRange' }
+        NonNullable<
+            NonNullable<
+                NonNullable<NonNullable<SearchGetPricesQuery['search']>['items']>[number]
+            >['priceWithTax']
+        >,
+        { __typename?: 'PriceRange' }
     >;
     export type _SinglePriceInlineFragment = DiscriminateUnion<
-        RequireField<NonNullable<SearchGetPricesQuery['search']['items'][0]>['priceWithTax'], '__typename'>,
-        { __typename: 'SinglePrice' }
+        NonNullable<
+            NonNullable<
+                NonNullable<NonNullable<SearchGetPricesQuery['search']>['items']>[number]
+            >['priceWithTax']
+        >,
+        { __typename?: 'SinglePrice' }
     >;
 }
 
 export namespace Reindex {
     export type Variables = ReindexMutationVariables;
     export type Mutation = ReindexMutation;
-    export type Reindex = ReindexMutation['reindex'];
+    export type Reindex = NonNullable<ReindexMutation['reindex']>;
 }
 
 export namespace GetJobInfo {

Різницю між файлами не показано, бо вона завелика
+ 0 - 0
schema-admin.json


Різницю між файлами не показано, бо вона завелика
+ 0 - 0
schema-shop.json


+ 22 - 4
scripts/codegen/generate-graphql-types.ts

@@ -50,13 +50,29 @@ Promise.all([
             },
             strict: true,
         };
-        const commonPlugins = [{ add: '// tslint:disable' }, 'typescript'];
+        const disableTsLintPlugin = { add: { content: '// tslint:disable' } };
+        const graphQlErrorsPlugin = path.join(__dirname, './plugins/graphql-errors-plugin.js');
+        const commonPlugins = [disableTsLintPlugin, 'typescript'];
         const clientPlugins = [...commonPlugins, 'typescript-operations', 'typescript-compatibility'];
 
         return generate({
             overwrite: true,
             generates: {
-                [path.join(__dirname, '../../packages/core/e2e/graphql/generated-e2e-admin-types.ts')]: {
+                [path.join(
+                    __dirname,
+                    '../../packages/core/src/common/error/generated-graphql-admin-errors.ts',
+                )]: {
+                    schema: [ADMIN_SCHEMA_OUTPUT_FILE],
+                    plugins: [disableTsLintPlugin, graphQlErrorsPlugin],
+                },
+                [path.join(
+                    __dirname,
+                    '../../packages/core/src/common/error/generated-graphql-shop-errors.ts',
+                )]: {
+                    schema: [SHOP_SCHEMA_OUTPUT_FILE],
+                    plugins: [disableTsLintPlugin, graphQlErrorsPlugin],
+                },
+                /*[path.join(__dirname, '../../packages/core/e2e/graphql/generated-e2e-admin-types.ts')]: {
                     schema: [ADMIN_SCHEMA_OUTPUT_FILE],
                     documents: E2E_ADMIN_QUERY_FILES,
                     plugins: clientPlugins,
@@ -101,7 +117,7 @@ Promise.all([
                 )]: {
                     schema: [ADMIN_SCHEMA_OUTPUT_FILE, path.join(__dirname, 'client-schema.ts')],
                     documents: CLIENT_QUERY_FILES,
-                    plugins: [{ add: '// tslint:disable' }, 'fragment-matcher'],
+                    plugins: [disableTsLintPlugin, 'fragment-matcher'],
                     config,
                 },
                 [path.join(__dirname, '../../packages/common/src/generated-types.ts')]: {
@@ -112,6 +128,7 @@ Promise.all([
                         scalars: {
                             ID: 'string | number',
                         },
+                        maybeValue: 'T',
                     },
                 },
                 [path.join(__dirname, '../../packages/common/src/generated-shop-types.ts')]: {
@@ -122,8 +139,9 @@ Promise.all([
                         scalars: {
                             ID: 'string | number',
                         },
+                        maybeValue: 'T',
                     },
-                },
+                },*/
             },
         });
     })

+ 2 - 0
scripts/codegen/plugins/.gitignore

@@ -0,0 +1,2 @@
+*.js
+*.js.map

+ 200 - 0
scripts/codegen/plugins/graphql-errors-plugin.ts

@@ -0,0 +1,200 @@
+import { PluginFunction } from '@graphql-codegen/plugin-helpers';
+import { buildScalars } from '@graphql-codegen/visitor-plugin-common';
+import {
+    FieldDefinitionNode,
+    GraphQLFieldMap,
+    GraphQLNamedType,
+    GraphQLObjectType,
+    GraphQLSchema,
+    GraphQLType,
+    GraphQLUnionType,
+    InterfaceTypeDefinitionNode,
+    isNamedType,
+    isObjectType,
+    isTypeDefinitionNode,
+    isUnionType,
+    NonNullTypeNode,
+    ObjectTypeDefinitionNode,
+    parse,
+    printSchema,
+    UnionTypeDefinitionNode,
+    visit,
+    Visitor,
+} from 'graphql';
+
+// This plugin generates classes for all GraphQL types which implement the `ErrorResult` interface.
+// This means that when returning an error result from a GraphQL operation, you can use one of
+// the generated classes rather than constructing the object by hand.
+// It also generates type resolvers to be used by Apollo Server to discriminate between
+// members of returned union types.
+
+export const ERROR_INTERFACE_NAME = 'ErrorResult';
+const empty = () => '';
+
+const errorsVisitor: Visitor<any> = {
+    NonNullType(node: NonNullTypeNode): string {
+        return node.type.kind === 'NamedType' ? node.type.name.value : '';
+    },
+    FieldDefinition(node: FieldDefinitionNode): string {
+        return `  ${node.name.value}: Scalars['${node.type}']`;
+    },
+    ScalarTypeDefinition: empty,
+    InputObjectTypeDefinition: empty,
+    EnumTypeDefinition: empty,
+    UnionTypeDefinition: empty,
+    InterfaceTypeDefinition(node: InterfaceTypeDefinitionNode) {
+        if (node.name.value !== ERROR_INTERFACE_NAME) {
+            return '';
+        }
+        return [
+            `export class ${ERROR_INTERFACE_NAME} {`,
+            `  readonly __typename: string;`,
+            `  readonly code: ErrorCode;`,
+            ...node.fields.filter(f => !(f as any).includes('code:')).map(f => `${f};`),
+            `}`,
+        ].join('\n');
+    },
+
+    ObjectTypeDefinition(
+        node: ObjectTypeDefinitionNode,
+        key: number | string | undefined,
+        parent: any,
+    ): string {
+        if (!inheritsFromErrorResult(node)) {
+            return '';
+        }
+        const originalNode = parent[key] as ObjectTypeDefinitionNode;
+
+        return [
+            `export class ${node.name.value} extends ${ERROR_INTERFACE_NAME} {`,
+            `  readonly __typename = '${node.name.value}';`,
+            `  readonly code = ErrorCode.${node.name.value};`,
+            `  constructor(`,
+            ...node.fields.filter(f => !(f as any).includes('code:')).map(f => `    public ${f},`),
+            `  ) {`,
+            `    super();`,
+            `  }`,
+            `}`,
+        ].join('\n');
+    },
+};
+
+export const plugin: PluginFunction<any> = (schema, documents, config, info) => {
+    const printedSchema = printSchema(schema); // Returns a string representation of the schema
+    const astNode = parse(printedSchema); // Transforms the string into ASTNode
+    const result = visit(astNode, { leave: errorsVisitor });
+    const defs = result.definitions.filter(d => !!d);
+    return {
+        content: [
+            `/** This file was generated by the graphql-errors-plugin, which is part of the "codegen" npm script. */`,
+            `import { ErrorCode } from '@vendure/common/lib/generated-types';`,
+            generateScalars(schema, config),
+            ...defs,
+            defs.length ? generateIsErrorFunction(schema) : '',
+            generateTypeResolvers(schema),
+        ].join('\n\n'),
+    };
+};
+
+function generateScalars(schema: GraphQLSchema, config: any): string {
+    const scalarMap = buildScalars(schema, config.scalars);
+    const allScalars = Object.keys(scalarMap)
+        .map(scalarName => {
+            const scalarValue = scalarMap[scalarName].type;
+            const scalarType = schema.getType(scalarName);
+
+            return `  ${scalarName}: ${scalarValue};`;
+        })
+        .join('\n');
+    return `export type Scalars = {\n${allScalars}\n};`;
+}
+
+function generateErrorClassSource(node: ObjectTypeDefinitionNode) {
+    let source = `export class ${node.name.value} {`;
+    for (const field of node.fields) {
+        source += `  ${1}`;
+    }
+}
+
+function generateIsErrorFunction(schema: GraphQLSchema) {
+    const errorNodes = Object.values(schema.getTypeMap())
+        .map(type => type.astNode)
+        .filter(isObjectTypeDefinition)
+        .filter(node => inheritsFromErrorResult(node));
+    return `
+const errorTypeNames = new Set([${errorNodes.map(n => `'${n.name.value}'`).join(', ')}]);
+export function isGraphQLError(input: any): input is import('@vendure/common/lib/generated-types').${ERROR_INTERFACE_NAME} {
+  return input instanceof ${ERROR_INTERFACE_NAME} || errorTypeNames.has(input.__typename);
+}`;
+}
+
+function generateTypeResolvers(schema: GraphQLSchema) {
+    const mutations = getOperationsThatReturnErrorUnions(schema, schema.getMutationType().getFields());
+    const queries = getOperationsThatReturnErrorUnions(schema, schema.getQueryType().getFields());
+    const operations = [...mutations, ...queries];
+    const isAdminApi = !!schema.getType('UpdateGlobalSettingsInput');
+    const varName = isAdminApi ? `adminErrorOperationTypeResolvers` : `shopErrorOperationTypeResolvers`;
+    const result = [`export const ${varName} = {`];
+    for (const operation of operations) {
+        const returnType = unwrapType(operation.type) as GraphQLUnionType;
+        const nonErrorResult = returnType.getTypes().find(t => !inheritsFromErrorResult(t));
+        result.push(
+            `  ${returnType.name}: {`,
+            `    __resolveType(value: any) {`,
+            // tslint:disable-next-line:no-non-null-assertion
+            `      return isGraphQLError(value) ? (value as any).__typename : '${nonErrorResult!.name}';`,
+            `    },`,
+            `  },`,
+        );
+    }
+    result.push(`};`);
+    return result.join('\n');
+}
+
+function getOperationsThatReturnErrorUnions(schema: GraphQLSchema, fields: GraphQLFieldMap<any, any>) {
+    return Object.values(fields).filter(operation => {
+        const innerType = unwrapType(operation.type);
+        if (innerType.astNode?.kind === 'UnionTypeDefinition') {
+            return isUnionOfResultAndErrors(schema, innerType.astNode);
+        }
+        return false;
+    });
+}
+
+function isUnionOfResultAndErrors(schema: GraphQLSchema, node: UnionTypeDefinitionNode) {
+    const errorResultTypes = node.types.filter(namedType => {
+        const type = schema.getType(namedType.name.value);
+        if (isObjectType(type)) {
+            if (inheritsFromErrorResult(type)) {
+                return true;
+            }
+        }
+        return false;
+    });
+    return (errorResultTypes.length = node.types.length - 1);
+}
+
+function isObjectTypeDefinition(node: any): node is ObjectTypeDefinitionNode {
+    return node && isTypeDefinitionNode(node) && node.kind === 'ObjectTypeDefinition';
+}
+
+function inheritsFromErrorResult(node: ObjectTypeDefinitionNode | GraphQLObjectType): boolean {
+    const interfaceNames = isObjectType(node)
+        ? node.getInterfaces().map(i => i.name)
+        : node.interfaces.map(i => i.name.value);
+    return interfaceNames.includes(ERROR_INTERFACE_NAME);
+}
+
+/**
+ * Unwraps the inner type from a higher-order type, e.g. [Address!]! => Address
+ */
+function unwrapType(type: GraphQLType): GraphQLNamedType {
+    if (isNamedType(type)) {
+        return type;
+    }
+    let innerType = type;
+    while (!isNamedType(innerType)) {
+        innerType = innerType.ofType;
+    }
+    return innerType;
+}

+ 12 - 0
scripts/codegen/plugins/tsconfig.json

@@ -0,0 +1,12 @@
+{
+  "compilerOptions": {
+    "module": "commonjs",
+    "target": "ES2017",
+    "sourceMap": true,
+    "skipLibCheck": true,
+    "esModuleInterop": true
+  },
+  "files": [
+    "graphql-errors-plugin.ts"
+  ]
+}

Різницю між файлами не показано, бо вона завелика
+ 423 - 397
yarn.lock


Деякі файли не було показано, через те що забагато файлів було змінено