Przeglądaj źródła

refactor: Reorganize CustomFields TypeScript & GraphQL types

Michael Bromley 6 lat temu
rodzic
commit
e0583dddd2
55 zmienionych plików z 850 dodań i 528 usunięć
  1. 1 1
      admin-ui/src/app/catalog/components/collection-detail/collection-detail.component.ts
  2. 1 1
      admin-ui/src/app/catalog/components/facet-detail/facet-detail.component.ts
  3. 1 1
      admin-ui/src/app/catalog/components/product-detail/product-detail.component.ts
  4. 6 7
      admin-ui/src/app/common/base-detail.component.ts
  5. 175 134
      admin-ui/src/app/common/generated-types.ts
  6. 2 2
      admin-ui/src/app/common/utilities/create-updated-translatable.spec.ts
  7. 9 9
      admin-ui/src/app/common/utilities/create-updated-translatable.ts
  8. 1 1
      admin-ui/src/app/customer/components/customer-detail/customer-detail.component.ts
  9. 7 7
      admin-ui/src/app/data/add-custom-fields.spec.ts
  10. 6 2
      admin-ui/src/app/data/add-custom-fields.ts
  11. 46 1
      admin-ui/src/app/data/definitions/settings-definitions.ts
  12. 2 2
      admin-ui/src/app/data/providers/base-data.service.ts
  13. 1 2
      admin-ui/src/app/settings/components/global-settings/global-settings.component.ts
  14. 1 1
      admin-ui/src/app/shared/components/custom-field-control/custom-field-control.component.ts
  15. 23 1
      packages/common/src/generated-shop-types.ts
  16. 155 133
      packages/common/src/generated-types.ts
  17. 0 60
      packages/common/src/shared-types.ts
  18. 85 0
      packages/core/e2e/custom-fields.e2e-spec.ts
  19. 141 119
      packages/core/e2e/graphql/generated-e2e-admin-types.ts
  20. 23 1
      packages/core/e2e/graphql/generated-e2e-shop-types.ts
  21. 22 6
      packages/core/src/api/config/__snapshots__/graphql-custom-fields.spec.ts.snap
  22. 2 1
      packages/core/src/api/config/configure-graphql-module.ts
  23. 0 2
      packages/core/src/api/config/generate-list-options.spec.ts
  24. 2 1
      packages/core/src/api/config/graphql-custom-fields.spec.ts
  25. 29 4
      packages/core/src/api/config/graphql-custom-fields.ts
  26. 2 2
      packages/core/src/api/resolvers/admin/global-settings.resolver.ts
  27. 6 2
      packages/core/src/api/schema/type/global-settings.type.graphql
  28. 1 1
      packages/core/src/config/config.service.ts
  29. 59 0
      packages/core/src/config/custom-field/custom-field-types.ts
  30. 1 1
      packages/core/src/config/default-config.ts
  31. 1 1
      packages/core/src/config/vendure-config.ts
  32. 2 1
      packages/core/src/entity/address/address.entity.ts
  33. 0 1
      packages/core/src/entity/asset/asset.entity.ts
  34. 2 1
      packages/core/src/entity/collection/collection-translation.entity.ts
  35. 2 1
      packages/core/src/entity/collection/collection.entity.ts
  36. 2 1
      packages/core/src/entity/custom-entity-fields.ts
  37. 1 1
      packages/core/src/entity/customer/customer.entity.ts
  38. 2 1
      packages/core/src/entity/facet-value/facet-value.entity.ts
  39. 2 1
      packages/core/src/entity/facet/facet-translation.entity.ts
  40. 2 1
      packages/core/src/entity/facet/facet.entity.ts
  41. 2 1
      packages/core/src/entity/global-settings/global-settings.entity.ts
  42. 2 1
      packages/core/src/entity/order-line/order-line.entity.ts
  43. 1 1
      packages/core/src/entity/product-option-group/product-option-group-translation.entity.ts
  44. 1 1
      packages/core/src/entity/product-option-group/product-option-group.entity.ts
  45. 1 1
      packages/core/src/entity/product-option/product-option-translation.entity.ts
  46. 1 1
      packages/core/src/entity/product-option/product-option.entity.ts
  47. 1 1
      packages/core/src/entity/product-variant/product-variant-translation.entity.ts
  48. 2 1
      packages/core/src/entity/product-variant/product-variant.entity.ts
  49. 2 1
      packages/core/src/entity/product/product-translation.entity.ts
  50. 2 1
      packages/core/src/entity/product/product.entity.ts
  51. 2 1
      packages/core/src/entity/user/user.entity.ts
  52. 6 1
      packages/dev-server/dev-config.ts
  53. 0 0
      schema-admin.json
  54. 0 0
      schema-shop.json
  55. 1 1
      scripts/codegen/generate-graphql-types.ts

+ 1 - 1
admin-ui/src/app/catalog/components/collection-detail/collection-detail.component.ts

@@ -10,7 +10,6 @@ import { FormArray, FormBuilder, FormGroup, Validators } from '@angular/forms';
 import { ActivatedRoute, Router } from '@angular/router';
 import { combineLatest, Observable } from 'rxjs';
 import { mergeMap, shareReplay, take } from 'rxjs/operators';
-import { CustomFieldConfig } from 'shared/shared-types';
 
 import { BaseDetailComponent } from '../../../common/base-detail.component';
 import {
@@ -18,6 +17,7 @@ import {
     ConfigurableOperation,
     ConfigurableOperationInput,
     CreateCollectionInput,
+    CustomFieldConfig,
     FacetWithValues,
     GetActiveChannel,
     LanguageCode,

+ 1 - 1
admin-ui/src/app/catalog/components/facet-detail/facet-detail.component.ts

@@ -4,13 +4,13 @@ import { ActivatedRoute, Router } from '@angular/router';
 import { combineLatest, EMPTY, forkJoin, Observable } from 'rxjs';
 import { map, mergeMap, switchMap, take } from 'rxjs/operators';
 import { normalizeString } from 'shared/normalize-string';
-import { CustomFieldConfig } from 'shared/shared-types';
 import { notNullOrUndefined } from 'shared/shared-utils';
 
 import { BaseDetailComponent } from '../../../common/base-detail.component';
 import {
     CreateFacetInput,
     CreateFacetValueInput,
+    CustomFieldConfig,
     DeletionResult,
     FacetWithValues,
     LanguageCode,

+ 1 - 1
admin-ui/src/app/catalog/components/product-detail/product-detail.component.ts

@@ -5,7 +5,6 @@ import { ActivatedRoute, Router } from '@angular/router';
 import { combineLatest, EMPTY, merge, Observable } from 'rxjs';
 import { distinctUntilChanged, map, mergeMap, switchMap, take, withLatestFrom } from 'rxjs/operators';
 import { normalizeString } from 'shared/normalize-string';
-import { CustomFieldConfig } from 'shared/shared-types';
 import { notNullOrUndefined } from 'shared/shared-utils';
 import { unique } from 'shared/unique';
 import { IGNORE_CAN_DEACTIVATE_GUARD } from 'src/app/shared/providers/routing/can-deactivate-detail-guard';
@@ -13,6 +12,7 @@ import { IGNORE_CAN_DEACTIVATE_GUARD } from 'src/app/shared/providers/routing/ca
 import { BaseDetailComponent } from '../../../common/base-detail.component';
 import {
     CreateProductInput,
+    CustomFieldConfig,
     FacetWithValues,
     LanguageCode,
     ProductWithVariants,

+ 6 - 7
admin-ui/src/app/common/base-detail.component.ts

@@ -1,12 +1,11 @@
 import { FormGroup } from '@angular/forms';
-import { ActivatedRoute, Data, Router } from '@angular/router';
-import { combineLatest, Observable, of, Subject } from 'rxjs';
-import { distinctUntilChanged, map, share, shareReplay, switchMap, takeUntil, tap } from 'rxjs/operators';
-import { CustomFieldConfig, CustomFields } from 'shared/shared-types';
+import { ActivatedRoute, Router } from '@angular/router';
+import { combineLatest, Observable, Subject } from 'rxjs';
+import { distinctUntilChanged, map, shareReplay, switchMap, takeUntil, tap } from 'rxjs/operators';
 
 import { ServerConfigService } from '../data/server-config';
 
-import { LanguageCode } from './generated-types';
+import { CustomFieldConfig, CustomFields, LanguageCode } from './generated-types';
 import { getDefaultLanguage } from './utilities/get-default-language';
 
 export abstract class BaseDetailComponent<Entity extends { id: string; updatedAt?: string }> {
@@ -62,8 +61,8 @@ export abstract class BaseDetailComponent<Entity extends { id: string; updatedAt
 
     protected abstract setFormValues(entity: Entity, languageCode: LanguageCode): void;
 
-    protected getCustomFieldConfig(key: keyof CustomFields): CustomFieldConfig[] {
-        return this.serverConfigService.serverConfig.customFields[key] || [];
+    protected getCustomFieldConfig(key: Exclude<keyof CustomFields, '__typename'>): CustomFieldConfig[] {
+        return this.serverConfigService.serverConfig.customFieldConfig[key] || [];
     }
 
     protected setQueryParam(key: string, value: any) {

+ 175 - 134
admin-ui/src/app/common/generated-types.ts

@@ -933,6 +933,28 @@ export type CustomerSortParameter = {
   emailAddress?: Maybe<SortOrder>,
 };
 
+export type CustomFieldConfig = {
+  __typename?: 'CustomFieldConfig',
+  name: Scalars['String'],
+  type: Scalars['String'],
+};
+
+export type CustomFields = {
+  __typename?: 'CustomFields',
+  Address: Array<CustomFieldConfig>,
+  Collection: Array<CustomFieldConfig>,
+  Customer: Array<CustomFieldConfig>,
+  Facet: Array<CustomFieldConfig>,
+  FacetValue: Array<CustomFieldConfig>,
+  GlobalSettings: Array<CustomFieldConfig>,
+  OrderLine: Array<CustomFieldConfig>,
+  Product: Array<CustomFieldConfig>,
+  ProductOption: Array<CustomFieldConfig>,
+  ProductOptionGroup: Array<CustomFieldConfig>,
+  ProductVariant: Array<CustomFieldConfig>,
+  User: Array<CustomFieldConfig>,
+};
+
 export type DateOperators = {
   eq?: Maybe<Scalars['DateTime']>,
   before?: Maybe<Scalars['DateTime']>,
@@ -1545,10 +1567,20 @@ export type MoveCollectionInput = {
 
 export type Mutation = {
   __typename?: 'Mutation',
+  /** Create a new Administrator */
+  createAdministrator: Administrator,
+  /** Update an existing Administrator */
+  updateAdministrator: Administrator,
+  /** Assign a Role to an Administrator */
+  assignRoleToAdministrator: Administrator,
   /** Create a new Asset */
   createAssets: Array<Asset>,
   login: LoginResult,
   logout: Scalars['Boolean'],
+  /** Create a new Channel */
+  createChannel: Channel,
+  /** Update an existing Channel */
+  updateChannel: Channel,
   /** Create a new Collection */
   createCollection: Collection,
   /** Update an existing Collection */
@@ -1557,16 +1589,12 @@ export type Mutation = {
   deleteCollection: DeletionResponse,
   /** Move a Collection to a different parent or index */
   moveCollection: Collection,
-  /** Create a new Channel */
-  createChannel: Channel,
-  /** Update an existing Channel */
-  updateChannel: Channel,
-  /** Create a new Administrator */
-  createAdministrator: Administrator,
-  /** Update an existing Administrator */
-  updateAdministrator: Administrator,
-  /** Assign a Role to an Administrator */
-  assignRoleToAdministrator: Administrator,
+  /** Create a new Country */
+  createCountry: Country,
+  /** Update an existing Country */
+  updateCountry: Country,
+  /** Delete a Country */
+  deleteCountry: DeletionResponse,
   /** Create a new CustomerGroup */
   createCustomerGroup: CustomerGroup,
   /** Update an existing CustomerGroup */
@@ -1575,14 +1603,8 @@ export type Mutation = {
   addCustomersToGroup: CustomerGroup,
   /** Remove Customers from a CustomerGroup */
   removeCustomersFromGroup: CustomerGroup,
-  /** Create a new Country */
-  createCountry: Country,
-  /** Update an existing Country */
-  updateCountry: Country,
-  /** Delete a Country */
-  deleteCountry: DeletionResponse,
-  importProducts?: Maybe<ImportInfo>,
   updateGlobalSettings: GlobalSettings,
+  importProducts?: Maybe<ImportInfo>,
   /** Create a new Customer. If a password is provided, a new User will also be created an linked to the Customer. */
   createCustomer: Customer,
   /** Update an existing Customer */
@@ -1613,7 +1635,6 @@ export type Mutation = {
   refundOrder: Refund,
   settleRefund: Refund,
   addNoteToOrder: Order,
-  reindex: JobInfo,
   /** Update an existing PaymentMethod */
   updatePaymentMethod: PaymentMethod,
   /** Create a new ProductOptionGroup */
@@ -1624,6 +1645,7 @@ export type Mutation = {
   createProductOption: ProductOption,
   /** Create a new ProductOption within a ProductOptionGroup */
   updateProductOption: ProductOption,
+  reindex: JobInfo,
   /** Create a new Product */
   createProduct: Product,
   /** Update an existing Product */
@@ -1640,6 +1662,9 @@ export type Mutation = {
   updateProductVariants: Array<Maybe<ProductVariant>>,
   /** Delete a ProductVariant */
   deleteProductVariant: DeletionResponse,
+  createPromotion: Promotion,
+  updatePromotion: Promotion,
+  deletePromotion: DeletionResponse,
   /** Create a new Role */
   createRole: Role,
   /** Update an existing Role */
@@ -1648,13 +1673,14 @@ export type Mutation = {
   createShippingMethod: ShippingMethod,
   /** Update an existing ShippingMethod */
   updateShippingMethod: ShippingMethod,
-  createPromotion: Promotion,
-  updatePromotion: Promotion,
-  deletePromotion: DeletionResponse,
   /** Create a new TaxCategory */
   createTaxCategory: TaxCategory,
   /** Update an existing TaxCategory */
   updateTaxCategory: TaxCategory,
+  /** Create a new TaxRate */
+  createTaxRate: TaxRate,
+  /** Update an existing TaxRate */
+  updateTaxRate: TaxRate,
   /** Create a new Zone */
   createZone: Zone,
   /** Update an existing Zone */
@@ -1665,10 +1691,6 @@ export type Mutation = {
   addMembersToZone: Zone,
   /** Remove members from a Zone */
   removeMembersFromZone: Zone,
-  /** Create a new TaxRate */
-  createTaxRate: TaxRate,
-  /** Update an existing TaxRate */
-  updateTaxRate: TaxRate,
   requestStarted: Scalars['Int'],
   requestCompleted: Scalars['Int'],
   setAsLoggedIn: UserStatus,
@@ -1677,6 +1699,22 @@ export type Mutation = {
 };
 
 
+export type MutationCreateAdministratorArgs = {
+  input: CreateAdministratorInput
+};
+
+
+export type MutationUpdateAdministratorArgs = {
+  input: UpdateAdministratorInput
+};
+
+
+export type MutationAssignRoleToAdministratorArgs = {
+  administratorId: Scalars['ID'],
+  roleId: Scalars['ID']
+};
+
+
 export type MutationCreateAssetsArgs = {
   input: Array<CreateAssetInput>
 };
@@ -1689,6 +1727,16 @@ export type MutationLoginArgs = {
 };
 
 
+export type MutationCreateChannelArgs = {
+  input: CreateChannelInput
+};
+
+
+export type MutationUpdateChannelArgs = {
+  input: UpdateChannelInput
+};
+
+
 export type MutationCreateCollectionArgs = {
   input: CreateCollectionInput
 };
@@ -1709,29 +1757,18 @@ export type MutationMoveCollectionArgs = {
 };
 
 
-export type MutationCreateChannelArgs = {
-  input: CreateChannelInput
-};
-
-
-export type MutationUpdateChannelArgs = {
-  input: UpdateChannelInput
-};
-
-
-export type MutationCreateAdministratorArgs = {
-  input: CreateAdministratorInput
+export type MutationCreateCountryArgs = {
+  input: CreateCountryInput
 };
 
 
-export type MutationUpdateAdministratorArgs = {
-  input: UpdateAdministratorInput
+export type MutationUpdateCountryArgs = {
+  input: UpdateCountryInput
 };
 
 
-export type MutationAssignRoleToAdministratorArgs = {
-  administratorId: Scalars['ID'],
-  roleId: Scalars['ID']
+export type MutationDeleteCountryArgs = {
+  id: Scalars['ID']
 };
 
 
@@ -1757,18 +1794,8 @@ export type MutationRemoveCustomersFromGroupArgs = {
 };
 
 
-export type MutationCreateCountryArgs = {
-  input: CreateCountryInput
-};
-
-
-export type MutationUpdateCountryArgs = {
-  input: UpdateCountryInput
-};
-
-
-export type MutationDeleteCountryArgs = {
-  id: Scalars['ID']
+export type MutationUpdateGlobalSettingsArgs = {
+  input: UpdateGlobalSettingsInput
 };
 
 
@@ -1777,11 +1804,6 @@ export type MutationImportProductsArgs = {
 };
 
 
-export type MutationUpdateGlobalSettingsArgs = {
-  input: UpdateGlobalSettingsInput
-};
-
-
 export type MutationCreateCustomerArgs = {
   input: CreateCustomerInput,
   password?: Maybe<Scalars['String']>
@@ -1943,6 +1965,21 @@ export type MutationDeleteProductVariantArgs = {
 };
 
 
+export type MutationCreatePromotionArgs = {
+  input: CreatePromotionInput
+};
+
+
+export type MutationUpdatePromotionArgs = {
+  input: UpdatePromotionInput
+};
+
+
+export type MutationDeletePromotionArgs = {
+  id: Scalars['ID']
+};
+
+
 export type MutationCreateRoleArgs = {
   input: CreateRoleInput
 };
@@ -1963,28 +2000,23 @@ export type MutationUpdateShippingMethodArgs = {
 };
 
 
-export type MutationCreatePromotionArgs = {
-  input: CreatePromotionInput
-};
-
-
-export type MutationUpdatePromotionArgs = {
-  input: UpdatePromotionInput
+export type MutationCreateTaxCategoryArgs = {
+  input: CreateTaxCategoryInput
 };
 
 
-export type MutationDeletePromotionArgs = {
-  id: Scalars['ID']
+export type MutationUpdateTaxCategoryArgs = {
+  input: UpdateTaxCategoryInput
 };
 
 
-export type MutationCreateTaxCategoryArgs = {
-  input: CreateTaxCategoryInput
+export type MutationCreateTaxRateArgs = {
+  input: CreateTaxRateInput
 };
 
 
-export type MutationUpdateTaxCategoryArgs = {
-  input: UpdateTaxCategoryInput
+export type MutationUpdateTaxRateArgs = {
+  input: UpdateTaxRateInput
 };
 
 
@@ -2015,16 +2047,6 @@ export type MutationRemoveMembersFromZoneArgs = {
 };
 
 
-export type MutationCreateTaxRateArgs = {
-  input: CreateTaxRateInput
-};
-
-
-export type MutationUpdateTaxRateArgs = {
-  input: UpdateTaxRateInput
-};
-
-
 export type MutationSetAsLoggedInArgs = {
   username: Scalars['String'],
   loginTime: Scalars['String']
@@ -2536,21 +2558,21 @@ export type PromotionSortParameter = {
 
 export type Query = {
   __typename?: 'Query',
+  administrators: AdministratorList,
+  administrator?: Maybe<Administrator>,
   assets: AssetList,
   asset?: Maybe<Asset>,
   me?: Maybe<CurrentUser>,
-  collections: CollectionList,
-  collection?: Maybe<Collection>,
-  collectionFilters: Array<ConfigurableOperation>,
   channels: Array<Channel>,
   channel?: Maybe<Channel>,
   activeChannel: Channel,
-  administrators: AdministratorList,
-  administrator?: Maybe<Administrator>,
-  customerGroups: Array<CustomerGroup>,
-  customerGroup?: Maybe<CustomerGroup>,
+  collections: CollectionList,
+  collection?: Maybe<Collection>,
+  collectionFilters: Array<ConfigurableOperation>,
   countries: CountryList,
   country?: Maybe<Country>,
+  customerGroups: Array<CustomerGroup>,
+  customerGroup?: Maybe<CustomerGroup>,
   globalSettings: GlobalSettings,
   customers: CustomerList,
   customer?: Maybe<Customer>,
@@ -2560,54 +2582,52 @@ export type Query = {
   jobs: Array<JobInfo>,
   order?: Maybe<Order>,
   orders: OrderList,
-  search: SearchResponse,
   paymentMethods: PaymentMethodList,
   paymentMethod?: Maybe<PaymentMethod>,
   productOptionGroups: Array<ProductOptionGroup>,
   productOptionGroup?: Maybe<ProductOptionGroup>,
+  search: SearchResponse,
   products: ProductList,
   /** Get a Product either by id or slug. If neither id nor slug is speicified, an error will result. */
   product?: Maybe<Product>,
+  promotion?: Maybe<Promotion>,
+  promotions: PromotionList,
+  adjustmentOperations: AdjustmentOperations,
   roles: RoleList,
   role?: Maybe<Role>,
   shippingMethods: ShippingMethodList,
   shippingMethod?: Maybe<ShippingMethod>,
   shippingEligibilityCheckers: Array<ConfigurableOperation>,
   shippingCalculators: Array<ConfigurableOperation>,
-  promotion?: Maybe<Promotion>,
-  promotions: PromotionList,
-  adjustmentOperations: AdjustmentOperations,
   taxCategories: Array<TaxCategory>,
   taxCategory?: Maybe<TaxCategory>,
-  zones: Array<Zone>,
-  zone?: Maybe<Zone>,
   taxRates: TaxRateList,
   taxRate?: Maybe<TaxRate>,
+  zones: Array<Zone>,
+  zone?: Maybe<Zone>,
   networkStatus: NetworkStatus,
   userStatus: UserStatus,
   uiState: UiState,
 };
 
 
-export type QueryAssetsArgs = {
-  options?: Maybe<AssetListOptions>
+export type QueryAdministratorsArgs = {
+  options?: Maybe<AdministratorListOptions>
 };
 
 
-export type QueryAssetArgs = {
+export type QueryAdministratorArgs = {
   id: Scalars['ID']
 };
 
 
-export type QueryCollectionsArgs = {
-  languageCode?: Maybe<LanguageCode>,
-  options?: Maybe<CollectionListOptions>
+export type QueryAssetsArgs = {
+  options?: Maybe<AssetListOptions>
 };
 
 
-export type QueryCollectionArgs = {
-  id: Scalars['ID'],
-  languageCode?: Maybe<LanguageCode>
+export type QueryAssetArgs = {
+  id: Scalars['ID']
 };
 
 
@@ -2616,18 +2636,15 @@ export type QueryChannelArgs = {
 };
 
 
-export type QueryAdministratorsArgs = {
-  options?: Maybe<AdministratorListOptions>
-};
-
-
-export type QueryAdministratorArgs = {
-  id: Scalars['ID']
+export type QueryCollectionsArgs = {
+  languageCode?: Maybe<LanguageCode>,
+  options?: Maybe<CollectionListOptions>
 };
 
 
-export type QueryCustomerGroupArgs = {
-  id: Scalars['ID']
+export type QueryCollectionArgs = {
+  id: Scalars['ID'],
+  languageCode?: Maybe<LanguageCode>
 };
 
 
@@ -2641,6 +2658,11 @@ export type QueryCountryArgs = {
 };
 
 
+export type QueryCustomerGroupArgs = {
+  id: Scalars['ID']
+};
+
+
 export type QueryCustomersArgs = {
   options?: Maybe<CustomerListOptions>
 };
@@ -2683,11 +2705,6 @@ export type QueryOrdersArgs = {
 };
 
 
-export type QuerySearchArgs = {
-  input: SearchInput
-};
-
-
 export type QueryPaymentMethodsArgs = {
   options?: Maybe<PaymentMethodListOptions>
 };
@@ -2710,6 +2727,11 @@ export type QueryProductOptionGroupArgs = {
 };
 
 
+export type QuerySearchArgs = {
+  input: SearchInput
+};
+
+
 export type QueryProductsArgs = {
   languageCode?: Maybe<LanguageCode>,
   options?: Maybe<ProductListOptions>
@@ -2723,6 +2745,16 @@ export type QueryProductArgs = {
 };
 
 
+export type QueryPromotionArgs = {
+  id: Scalars['ID']
+};
+
+
+export type QueryPromotionsArgs = {
+  options?: Maybe<PromotionListOptions>
+};
+
+
 export type QueryRolesArgs = {
   options?: Maybe<RoleListOptions>
 };
@@ -2743,17 +2775,17 @@ export type QueryShippingMethodArgs = {
 };
 
 
-export type QueryPromotionArgs = {
+export type QueryTaxCategoryArgs = {
   id: Scalars['ID']
 };
 
 
-export type QueryPromotionsArgs = {
-  options?: Maybe<PromotionListOptions>
+export type QueryTaxRatesArgs = {
+  options?: Maybe<TaxRateListOptions>
 };
 
 
-export type QueryTaxCategoryArgs = {
+export type QueryTaxRateArgs = {
   id: Scalars['ID']
 };
 
@@ -2762,16 +2794,6 @@ export type QueryZoneArgs = {
   id: Scalars['ID']
 };
 
-
-export type QueryTaxRatesArgs = {
-  options?: Maybe<TaxRateListOptions>
-};
-
-
-export type QueryTaxRateArgs = {
-  id: Scalars['ID']
-};
-
 export type Refund = Node & {
   __typename?: 'Refund',
   id: Scalars['ID'],
@@ -2916,7 +2938,7 @@ export type SearchResultSortParameter = {
 
 export type ServerConfig = {
   __typename?: 'ServerConfig',
-  customFields?: Maybe<Scalars['JSON']>,
+  customFieldConfig: CustomFields,
 };
 
 export type SettleRefundInput = {
@@ -4048,10 +4070,12 @@ export type UpdateGlobalSettingsMutationVariables = {
 
 export type UpdateGlobalSettingsMutation = ({ __typename?: 'Mutation' } & { updateGlobalSettings: ({ __typename?: 'GlobalSettings' } & GlobalSettingsFragment) });
 
+export type CustomFieldConfigFragment = ({ __typename?: 'CustomFieldConfig' } & Pick<CustomFieldConfig, 'name' | 'type'>);
+
 export type GetServerConfigQueryVariables = {};
 
 
-export type GetServerConfigQuery = ({ __typename?: 'Query' } & { globalSettings: ({ __typename?: 'GlobalSettings' } & { serverConfig: ({ __typename?: 'ServerConfig' } & Pick<ServerConfig, 'customFields'>) }) });
+export type GetServerConfigQuery = ({ __typename?: 'Query' } & { globalSettings: ({ __typename?: 'GlobalSettings' } & { serverConfig: ({ __typename?: 'ServerConfig' } & { customFieldConfig: ({ __typename?: 'CustomFields' } & { Address: Array<({ __typename?: 'CustomFieldConfig' } & CustomFieldConfigFragment)>, Collection: Array<({ __typename?: 'CustomFieldConfig' } & CustomFieldConfigFragment)>, Customer: Array<({ __typename?: 'CustomFieldConfig' } & CustomFieldConfigFragment)>, Facet: Array<({ __typename?: 'CustomFieldConfig' } & CustomFieldConfigFragment)>, FacetValue: Array<({ __typename?: 'CustomFieldConfig' } & CustomFieldConfigFragment)>, GlobalSettings: Array<({ __typename?: 'CustomFieldConfig' } & CustomFieldConfigFragment)>, OrderLine: Array<({ __typename?: 'CustomFieldConfig' } & CustomFieldConfigFragment)>, Product: Array<({ __typename?: 'CustomFieldConfig' } & CustomFieldConfigFragment)>, ProductOption: Array<({ __typename?: 'CustomFieldConfig' } & CustomFieldConfigFragment)>, ProductOptionGroup: Array<({ __typename?: 'CustomFieldConfig' } & CustomFieldConfigFragment)>, ProductVariant: Array<({ __typename?: 'CustomFieldConfig' } & CustomFieldConfigFragment)>, User: Array<({ __typename?: 'CustomFieldConfig' } & CustomFieldConfigFragment)> }) }) }) });
 
 export type JobInfoFragment = ({ __typename?: 'JobInfo' } & Pick<JobInfo, 'id' | 'name' | 'state' | 'progress' | 'duration' | 'result'>);
 
@@ -4921,11 +4945,28 @@ export namespace UpdateGlobalSettings {
   export type UpdateGlobalSettings = GlobalSettingsFragment;
 }
 
+export namespace CustomFieldConfig {
+  export type Fragment = CustomFieldConfigFragment;
+}
+
 export namespace GetServerConfig {
   export type Variables = GetServerConfigQueryVariables;
   export type Query = GetServerConfigQuery;
   export type GlobalSettings = GetServerConfigQuery['globalSettings'];
   export type ServerConfig = GetServerConfigQuery['globalSettings']['serverConfig'];
+  export type CustomFieldConfig = GetServerConfigQuery['globalSettings']['serverConfig']['customFieldConfig'];
+  export type Address = CustomFieldConfigFragment;
+  export type Collection = CustomFieldConfigFragment;
+  export type Customer = CustomFieldConfigFragment;
+  export type Facet = CustomFieldConfigFragment;
+  export type FacetValue = CustomFieldConfigFragment;
+  export type _GlobalSettings = CustomFieldConfigFragment;
+  export type OrderLine = CustomFieldConfigFragment;
+  export type Product = CustomFieldConfigFragment;
+  export type ProductOption = CustomFieldConfigFragment;
+  export type ProductOptionGroup = CustomFieldConfigFragment;
+  export type ProductVariant = CustomFieldConfigFragment;
+  export type User = CustomFieldConfigFragment;
 }
 
 export namespace JobInfo {

+ 2 - 2
admin-ui/src/app/common/utilities/create-updated-translatable.spec.ts

@@ -1,6 +1,6 @@
-import { CustomFieldConfig, DeepPartial } from 'shared/shared-types';
+import { DeepPartial } from 'shared/shared-types';
 
-import { LanguageCode, ProductWithVariants } from '../generated-types';
+import { CustomFieldConfig, LanguageCode, ProductWithVariants } from '../generated-types';
 
 import { createUpdatedTranslatable } from './create-updated-translatable';
 

+ 9 - 9
admin-ui/src/app/common/utilities/create-updated-translatable.ts

@@ -1,12 +1,7 @@
-import {
-    CustomFieldConfig,
-    CustomFieldsObject,
-    CustomFieldType,
-    MayHaveCustomFields,
-} from 'shared/shared-types';
+import { CustomFieldsObject, CustomFieldType } from 'shared/shared-types';
 import { assertNever } from 'shared/shared-utils';
 
-import { LanguageCode } from '../generated-types';
+import { CustomFieldConfig, LanguageCode } from '../generated-types';
 
 export interface TranslatableUpdateOptions<T extends { translations: any[] } & MayHaveCustomFields> {
     translatable: T;
@@ -16,6 +11,10 @@ export interface TranslatableUpdateOptions<T extends { translations: any[] } & M
     defaultTranslation?: Partial<T['translations'][number]>;
 }
 
+export type MayHaveCustomFields = {
+    customFields?: { [key: string]: any };
+};
+
 /**
  * When updating an entity which has translations, the value from the form will pertain to the current
  * languageCode. This function ensures that the "translations" array is correctly set based on the
@@ -38,7 +37,8 @@ export function createUpdatedTranslatable<T extends { translations: any[] } & Ma
             if (field.type === 'localeString') {
                 newTranslatedCustomFields[field.name] = value;
             } else {
-                newCustomFields[field.name] = value === '' ? getDefaultValue(field.type) : value;
+                newCustomFields[field.name] =
+                    value === '' ? getDefaultValue(field.type as CustomFieldType) : value;
             }
         }
         newTranslation.customFields = newTranslatedCustomFields;
@@ -80,7 +80,7 @@ function getDefaultValue(type: CustomFieldType): any {
  * those of `obj`.
  */
 function patchObject<T extends { [key: string]: any }>(obj: T, patch: { [key: string]: any }): T {
-    const clone = Object.assign({}, obj);
+    const clone: any = Object.assign({}, obj);
     Object.keys(clone).forEach(key => {
         if (patch.hasOwnProperty(key)) {
             clone[key] = patch[key];

+ 1 - 1
admin-ui/src/app/customer/components/customer-detail/customer-detail.component.ts

@@ -3,7 +3,6 @@ import { FormArray, FormBuilder, FormControl, FormGroup, Validators } from '@ang
 import { ActivatedRoute, Router } from '@angular/router';
 import { forkJoin, Observable, Subject } from 'rxjs';
 import { filter, map, merge, mergeMap, shareReplay, take } from 'rxjs/operators';
-import { CustomFieldConfig } from 'shared/shared-types';
 import { notNullOrUndefined } from 'shared/shared-utils';
 
 import { BaseDetailComponent } from '../../../common/base-detail.component';
@@ -11,6 +10,7 @@ import {
     CreateAddressInput,
     CreateCustomerInput,
     Customer,
+    CustomFieldConfig,
     GetAvailableCountries,
     GetCustomer,
     GetCustomerQuery,

+ 7 - 7
admin-ui/src/app/data/add-custom-fields.spec.ts

@@ -1,6 +1,6 @@
 import { DocumentNode, FieldNode, FragmentDefinitionNode } from 'graphql';
 
-import { CustomFields } from 'shared/shared-types';
+import { CustomFieldConfig, CustomFields } from '../common/generated-types';
 
 import { addCustomFields } from './add-custom-fields';
 
@@ -143,11 +143,11 @@ describe('addCustomFields()', () => {
     });
 
     it('Adds customFields to Product fragment', () => {
-        const customFieldsConfig: CustomFields = {
+        const customFieldsConfig: Partial<CustomFields> = {
             Product: [{ name: 'custom1', type: 'string' }, { name: 'custom2', type: 'boolean' }],
         };
 
-        const result = addCustomFields(documentNode, customFieldsConfig);
+        const result = addCustomFields(documentNode, customFieldsConfig as CustomFields);
         const productFragmentDef = result.definitions[1] as FragmentDefinitionNode;
         const customFieldsDef = productFragmentDef.selectionSet.selections[2] as FieldNode;
         expect(productFragmentDef.selectionSet.selections.length).toBe(3);
@@ -157,11 +157,11 @@ describe('addCustomFields()', () => {
     });
 
     it('Adds customFields to Product translations', () => {
-        const customFieldsConfig: CustomFields = {
+        const customFieldsConfig: Partial<CustomFields> = {
             Product: [{ name: 'customLocaleString', type: 'localeString' }],
         };
 
-        const result = addCustomFields(documentNode, customFieldsConfig);
+        const result = addCustomFields(documentNode, customFieldsConfig as CustomFields);
         const productFragmentDef = result.definitions[1] as FragmentDefinitionNode;
         const translationsField = productFragmentDef.selectionSet.selections[1] as FieldNode;
         const customTranslationFieldsDef = translationsField.selectionSet!.selections[2] as FieldNode;
@@ -172,11 +172,11 @@ describe('addCustomFields()', () => {
     });
 
     function addsCustomFieldsToType(type: keyof CustomFields, indexOfDefinition: number) {
-        const customFieldsConfig: CustomFields = {
+        const customFieldsConfig: Partial<CustomFields> = {
             [type]: [{ name: 'custom', type: 'boolean' }],
         };
 
-        const result = addCustomFields(documentNode, customFieldsConfig);
+        const result = addCustomFields(documentNode, customFieldsConfig as CustomFields);
         const fragmentDef = result.definitions[indexOfDefinition] as FragmentDefinitionNode;
         const customFieldsDef = fragmentDef.selectionSet.selections[0] as FieldNode;
         expect(fragmentDef.selectionSet.selections.length).toBe(1);

+ 6 - 2
admin-ui/src/app/data/add-custom-fields.ts

@@ -6,7 +6,8 @@ import {
     Kind,
     SelectionNode,
 } from 'graphql';
-import { CustomFields } from 'shared/shared-types';
+
+import { CustomFields } from '../common/generated-types';
 
 /**
  * Given a GraphQL AST (DocumentNode), this function looks for fragment definitions and adds and configured
@@ -16,7 +17,10 @@ export function addCustomFields(documentNode: DocumentNode, customFields: Custom
     const fragmentDefs = documentNode.definitions.filter(isFragmentDefinition);
 
     for (const fragmentDef of fragmentDefs) {
-        const entityType = fragmentDef.typeCondition.name.value as keyof CustomFields;
+        const entityType = fragmentDef.typeCondition.name.value as keyof Pick<
+            CustomFields,
+            Exclude<keyof CustomFields, '__typename'>
+        >;
         const customFieldsForType = customFields[entityType];
         if (customFieldsForType && customFieldsForType.length) {
             fragmentDef.selectionSet.selections.push({

+ 46 - 1
admin-ui/src/app/data/definitions/settings-definitions.ts

@@ -382,14 +382,59 @@ export const UPDATE_GLOBAL_SETTINGS = gql`
     ${GLOBAL_SETTINGS_FRAGMENT}
 `;
 
+export const CUSTOM_FIELD_CONFIG_FRAGMENT = gql`
+    fragment CustomFieldConfig on CustomFieldConfig {
+        name
+        type
+    }
+`;
+
 export const GET_SERVER_CONFIG = gql`
     query GetServerConfig {
         globalSettings {
             serverConfig {
-                customFields
+                customFieldConfig {
+                    Address {
+                        ...CustomFieldConfig
+                    }
+                    Collection {
+                        ...CustomFieldConfig
+                    }
+                    Customer {
+                        ...CustomFieldConfig
+                    }
+                    Facet {
+                        ...CustomFieldConfig
+                    }
+                    FacetValue {
+                        ...CustomFieldConfig
+                    }
+                    GlobalSettings {
+                        ...CustomFieldConfig
+                    }
+                    OrderLine {
+                        ...CustomFieldConfig
+                    }
+                    Product {
+                        ...CustomFieldConfig
+                    }
+                    ProductOption {
+                        ...CustomFieldConfig
+                    }
+                    ProductOptionGroup {
+                        ...CustomFieldConfig
+                    }
+                    ProductVariant {
+                        ...CustomFieldConfig
+                    }
+                    User {
+                        ...CustomFieldConfig
+                    }
+                }
             }
         }
     }
+    ${CUSTOM_FIELD_CONFIG_FRAGMENT}
 `;
 
 export const JOB_INFO_FRAGMENT = gql`

+ 2 - 2
admin-ui/src/app/data/providers/base-data.service.ts

@@ -7,8 +7,8 @@ import { ExecutionResult } from 'apollo-link';
 import { DocumentNode } from 'graphql/language/ast';
 import { Observable } from 'rxjs';
 import { map } from 'rxjs/operators';
-import { CustomFields } from 'shared/shared-types';
 
+import { CustomFields } from '../../common/generated-types';
 import { LocalStorageService } from '../../core/providers/local-storage/local-storage.service';
 import { addCustomFields } from '../add-custom-fields';
 import { QueryResult } from '../query-result';
@@ -33,7 +33,7 @@ export class BaseDataService {
     ) {}
 
     private get customFields(): CustomFields {
-        return this.serverConfigService.serverConfig.customFields || {};
+        return this.serverConfigService.serverConfig.customFieldConfig || {};
     }
 
     /**

+ 1 - 2
admin-ui/src/app/settings/components/global-settings/global-settings.component.ts

@@ -2,10 +2,9 @@ import { ChangeDetectionStrategy, ChangeDetectorRef, Component, OnInit } from '@
 import { FormBuilder, FormGroup } from '@angular/forms';
 import { ActivatedRoute, Router } from '@angular/router';
 import { switchMap } from 'rxjs/operators';
-import { CustomFieldConfig } from 'shared/shared-types';
 
 import { BaseDetailComponent } from '../../../common/base-detail.component';
-import { GlobalSettings, LanguageCode } from '../../../common/generated-types';
+import { CustomFieldConfig, GlobalSettings, LanguageCode } from '../../../common/generated-types';
 import { _ } from '../../../core/providers/i18n/mark-for-extraction';
 import { NotificationService } from '../../../core/providers/notification/notification.service';
 import { DataService } from '../../../data/providers/data.service';

+ 1 - 1
admin-ui/src/app/shared/components/custom-field-control/custom-field-control.component.ts

@@ -1,7 +1,7 @@
 import { Component, Input, OnInit } from '@angular/core';
 import { FormGroup } from '@angular/forms';
 
-import { CustomFieldConfig } from 'shared/shared-types';
+import { CustomFieldConfig } from '../../../common/generated-types';
 
 /**
  * This component renders the appropriate type of form input control based

+ 23 - 1
packages/common/src/generated-shop-types.ts

@@ -655,6 +655,28 @@ export type CustomerList = PaginatedList & {
     totalItems: Scalars['Int'];
 };
 
+export type CustomFieldConfig = {
+    __typename?: 'CustomFieldConfig';
+    name: Scalars['String'];
+    type: Scalars['String'];
+};
+
+export type CustomFields = {
+    __typename?: 'CustomFields';
+    Address: Array<CustomFieldConfig>;
+    Collection: Array<CustomFieldConfig>;
+    Customer: Array<CustomFieldConfig>;
+    Facet: Array<CustomFieldConfig>;
+    FacetValue: Array<CustomFieldConfig>;
+    GlobalSettings: Array<CustomFieldConfig>;
+    OrderLine: Array<CustomFieldConfig>;
+    Product: Array<CustomFieldConfig>;
+    ProductOption: Array<CustomFieldConfig>;
+    ProductOptionGroup: Array<CustomFieldConfig>;
+    ProductVariant: Array<CustomFieldConfig>;
+    User: Array<CustomFieldConfig>;
+};
+
 export type DateOperators = {
     eq?: Maybe<Scalars['DateTime']>;
     before?: Maybe<Scalars['DateTime']>;
@@ -1909,7 +1931,7 @@ export type SearchResultSortParameter = {
 
 export type ServerConfig = {
     __typename?: 'ServerConfig';
-    customFields?: Maybe<Scalars['JSON']>;
+    customFieldConfig: CustomFields;
 };
 
 export type ShippingMethod = Node & {

+ 155 - 133
packages/common/src/generated-types.ts

@@ -932,6 +932,28 @@ export type CustomerSortParameter = {
   emailAddress?: Maybe<SortOrder>,
 };
 
+export type CustomFieldConfig = {
+  __typename?: 'CustomFieldConfig',
+  name: Scalars['String'],
+  type: Scalars['String'],
+};
+
+export type CustomFields = {
+  __typename?: 'CustomFields',
+  Address: Array<CustomFieldConfig>,
+  Collection: Array<CustomFieldConfig>,
+  Customer: Array<CustomFieldConfig>,
+  Facet: Array<CustomFieldConfig>,
+  FacetValue: Array<CustomFieldConfig>,
+  GlobalSettings: Array<CustomFieldConfig>,
+  OrderLine: Array<CustomFieldConfig>,
+  Product: Array<CustomFieldConfig>,
+  ProductOption: Array<CustomFieldConfig>,
+  ProductOptionGroup: Array<CustomFieldConfig>,
+  ProductVariant: Array<CustomFieldConfig>,
+  User: Array<CustomFieldConfig>,
+};
+
 export type DateOperators = {
   eq?: Maybe<Scalars['DateTime']>,
   before?: Maybe<Scalars['DateTime']>,
@@ -1544,10 +1566,20 @@ export type MoveCollectionInput = {
 
 export type Mutation = {
   __typename?: 'Mutation',
+  /** Create a new Administrator */
+  createAdministrator: Administrator,
+  /** Update an existing Administrator */
+  updateAdministrator: Administrator,
+  /** Assign a Role to an Administrator */
+  assignRoleToAdministrator: Administrator,
   /** Create a new Asset */
   createAssets: Array<Asset>,
   login: LoginResult,
   logout: Scalars['Boolean'],
+  /** Create a new Channel */
+  createChannel: Channel,
+  /** Update an existing Channel */
+  updateChannel: Channel,
   /** Create a new Collection */
   createCollection: Collection,
   /** Update an existing Collection */
@@ -1556,16 +1588,12 @@ export type Mutation = {
   deleteCollection: DeletionResponse,
   /** Move a Collection to a different parent or index */
   moveCollection: Collection,
-  /** Create a new Channel */
-  createChannel: Channel,
-  /** Update an existing Channel */
-  updateChannel: Channel,
-  /** Create a new Administrator */
-  createAdministrator: Administrator,
-  /** Update an existing Administrator */
-  updateAdministrator: Administrator,
-  /** Assign a Role to an Administrator */
-  assignRoleToAdministrator: Administrator,
+  /** Create a new Country */
+  createCountry: Country,
+  /** Update an existing Country */
+  updateCountry: Country,
+  /** Delete a Country */
+  deleteCountry: DeletionResponse,
   /** Create a new CustomerGroup */
   createCustomerGroup: CustomerGroup,
   /** Update an existing CustomerGroup */
@@ -1574,14 +1602,8 @@ export type Mutation = {
   addCustomersToGroup: CustomerGroup,
   /** Remove Customers from a CustomerGroup */
   removeCustomersFromGroup: CustomerGroup,
-  /** Create a new Country */
-  createCountry: Country,
-  /** Update an existing Country */
-  updateCountry: Country,
-  /** Delete a Country */
-  deleteCountry: DeletionResponse,
-  importProducts?: Maybe<ImportInfo>,
   updateGlobalSettings: GlobalSettings,
+  importProducts?: Maybe<ImportInfo>,
   /** Create a new Customer. If a password is provided, a new User will also be created an linked to the Customer. */
   createCustomer: Customer,
   /** Update an existing Customer */
@@ -1612,7 +1634,6 @@ export type Mutation = {
   refundOrder: Refund,
   settleRefund: Refund,
   addNoteToOrder: Order,
-  reindex: JobInfo,
   /** Update an existing PaymentMethod */
   updatePaymentMethod: PaymentMethod,
   /** Create a new ProductOptionGroup */
@@ -1623,6 +1644,7 @@ export type Mutation = {
   createProductOption: ProductOption,
   /** Create a new ProductOption within a ProductOptionGroup */
   updateProductOption: ProductOption,
+  reindex: JobInfo,
   /** Create a new Product */
   createProduct: Product,
   /** Update an existing Product */
@@ -1639,6 +1661,9 @@ export type Mutation = {
   updateProductVariants: Array<Maybe<ProductVariant>>,
   /** Delete a ProductVariant */
   deleteProductVariant: DeletionResponse,
+  createPromotion: Promotion,
+  updatePromotion: Promotion,
+  deletePromotion: DeletionResponse,
   /** Create a new Role */
   createRole: Role,
   /** Update an existing Role */
@@ -1647,13 +1672,14 @@ export type Mutation = {
   createShippingMethod: ShippingMethod,
   /** Update an existing ShippingMethod */
   updateShippingMethod: ShippingMethod,
-  createPromotion: Promotion,
-  updatePromotion: Promotion,
-  deletePromotion: DeletionResponse,
   /** Create a new TaxCategory */
   createTaxCategory: TaxCategory,
   /** Update an existing TaxCategory */
   updateTaxCategory: TaxCategory,
+  /** Create a new TaxRate */
+  createTaxRate: TaxRate,
+  /** Update an existing TaxRate */
+  updateTaxRate: TaxRate,
   /** Create a new Zone */
   createZone: Zone,
   /** Update an existing Zone */
@@ -1664,10 +1690,22 @@ export type Mutation = {
   addMembersToZone: Zone,
   /** Remove members from a Zone */
   removeMembersFromZone: Zone,
-  /** Create a new TaxRate */
-  createTaxRate: TaxRate,
-  /** Update an existing TaxRate */
-  updateTaxRate: TaxRate,
+};
+
+
+export type MutationCreateAdministratorArgs = {
+  input: CreateAdministratorInput
+};
+
+
+export type MutationUpdateAdministratorArgs = {
+  input: UpdateAdministratorInput
+};
+
+
+export type MutationAssignRoleToAdministratorArgs = {
+  administratorId: Scalars['ID'],
+  roleId: Scalars['ID']
 };
 
 
@@ -1683,6 +1721,16 @@ export type MutationLoginArgs = {
 };
 
 
+export type MutationCreateChannelArgs = {
+  input: CreateChannelInput
+};
+
+
+export type MutationUpdateChannelArgs = {
+  input: UpdateChannelInput
+};
+
+
 export type MutationCreateCollectionArgs = {
   input: CreateCollectionInput
 };
@@ -1703,29 +1751,18 @@ export type MutationMoveCollectionArgs = {
 };
 
 
-export type MutationCreateChannelArgs = {
-  input: CreateChannelInput
-};
-
-
-export type MutationUpdateChannelArgs = {
-  input: UpdateChannelInput
-};
-
-
-export type MutationCreateAdministratorArgs = {
-  input: CreateAdministratorInput
+export type MutationCreateCountryArgs = {
+  input: CreateCountryInput
 };
 
 
-export type MutationUpdateAdministratorArgs = {
-  input: UpdateAdministratorInput
+export type MutationUpdateCountryArgs = {
+  input: UpdateCountryInput
 };
 
 
-export type MutationAssignRoleToAdministratorArgs = {
-  administratorId: Scalars['ID'],
-  roleId: Scalars['ID']
+export type MutationDeleteCountryArgs = {
+  id: Scalars['ID']
 };
 
 
@@ -1751,18 +1788,8 @@ export type MutationRemoveCustomersFromGroupArgs = {
 };
 
 
-export type MutationCreateCountryArgs = {
-  input: CreateCountryInput
-};
-
-
-export type MutationUpdateCountryArgs = {
-  input: UpdateCountryInput
-};
-
-
-export type MutationDeleteCountryArgs = {
-  id: Scalars['ID']
+export type MutationUpdateGlobalSettingsArgs = {
+  input: UpdateGlobalSettingsInput
 };
 
 
@@ -1771,11 +1798,6 @@ export type MutationImportProductsArgs = {
 };
 
 
-export type MutationUpdateGlobalSettingsArgs = {
-  input: UpdateGlobalSettingsInput
-};
-
-
 export type MutationCreateCustomerArgs = {
   input: CreateCustomerInput,
   password?: Maybe<Scalars['String']>
@@ -1937,6 +1959,21 @@ export type MutationDeleteProductVariantArgs = {
 };
 
 
+export type MutationCreatePromotionArgs = {
+  input: CreatePromotionInput
+};
+
+
+export type MutationUpdatePromotionArgs = {
+  input: UpdatePromotionInput
+};
+
+
+export type MutationDeletePromotionArgs = {
+  id: Scalars['ID']
+};
+
+
 export type MutationCreateRoleArgs = {
   input: CreateRoleInput
 };
@@ -1957,28 +1994,23 @@ export type MutationUpdateShippingMethodArgs = {
 };
 
 
-export type MutationCreatePromotionArgs = {
-  input: CreatePromotionInput
-};
-
-
-export type MutationUpdatePromotionArgs = {
-  input: UpdatePromotionInput
+export type MutationCreateTaxCategoryArgs = {
+  input: CreateTaxCategoryInput
 };
 
 
-export type MutationDeletePromotionArgs = {
-  id: Scalars['ID']
+export type MutationUpdateTaxCategoryArgs = {
+  input: UpdateTaxCategoryInput
 };
 
 
-export type MutationCreateTaxCategoryArgs = {
-  input: CreateTaxCategoryInput
+export type MutationCreateTaxRateArgs = {
+  input: CreateTaxRateInput
 };
 
 
-export type MutationUpdateTaxCategoryArgs = {
-  input: UpdateTaxCategoryInput
+export type MutationUpdateTaxRateArgs = {
+  input: UpdateTaxRateInput
 };
 
 
@@ -2008,16 +2040,6 @@ export type MutationRemoveMembersFromZoneArgs = {
   memberIds: Array<Scalars['ID']>
 };
 
-
-export type MutationCreateTaxRateArgs = {
-  input: CreateTaxRateInput
-};
-
-
-export type MutationUpdateTaxRateArgs = {
-  input: UpdateTaxRateInput
-};
-
 export type Node = {
   __typename?: 'Node',
   id: Scalars['ID'],
@@ -2514,21 +2536,21 @@ export type PromotionSortParameter = {
 
 export type Query = {
   __typename?: 'Query',
+  administrators: AdministratorList,
+  administrator?: Maybe<Administrator>,
   assets: AssetList,
   asset?: Maybe<Asset>,
   me?: Maybe<CurrentUser>,
-  collections: CollectionList,
-  collection?: Maybe<Collection>,
-  collectionFilters: Array<ConfigurableOperation>,
   channels: Array<Channel>,
   channel?: Maybe<Channel>,
   activeChannel: Channel,
-  administrators: AdministratorList,
-  administrator?: Maybe<Administrator>,
-  customerGroups: Array<CustomerGroup>,
-  customerGroup?: Maybe<CustomerGroup>,
+  collections: CollectionList,
+  collection?: Maybe<Collection>,
+  collectionFilters: Array<ConfigurableOperation>,
   countries: CountryList,
   country?: Maybe<Country>,
+  customerGroups: Array<CustomerGroup>,
+  customerGroup?: Maybe<CustomerGroup>,
   globalSettings: GlobalSettings,
   customers: CustomerList,
   customer?: Maybe<Customer>,
@@ -2538,51 +2560,49 @@ export type Query = {
   jobs: Array<JobInfo>,
   order?: Maybe<Order>,
   orders: OrderList,
-  search: SearchResponse,
   paymentMethods: PaymentMethodList,
   paymentMethod?: Maybe<PaymentMethod>,
   productOptionGroups: Array<ProductOptionGroup>,
   productOptionGroup?: Maybe<ProductOptionGroup>,
+  search: SearchResponse,
   products: ProductList,
   /** Get a Product either by id or slug. If neither id nor slug is speicified, an error will result. */
   product?: Maybe<Product>,
+  promotion?: Maybe<Promotion>,
+  promotions: PromotionList,
+  adjustmentOperations: AdjustmentOperations,
   roles: RoleList,
   role?: Maybe<Role>,
   shippingMethods: ShippingMethodList,
   shippingMethod?: Maybe<ShippingMethod>,
   shippingEligibilityCheckers: Array<ConfigurableOperation>,
   shippingCalculators: Array<ConfigurableOperation>,
-  promotion?: Maybe<Promotion>,
-  promotions: PromotionList,
-  adjustmentOperations: AdjustmentOperations,
   taxCategories: Array<TaxCategory>,
   taxCategory?: Maybe<TaxCategory>,
-  zones: Array<Zone>,
-  zone?: Maybe<Zone>,
   taxRates: TaxRateList,
   taxRate?: Maybe<TaxRate>,
+  zones: Array<Zone>,
+  zone?: Maybe<Zone>,
 };
 
 
-export type QueryAssetsArgs = {
-  options?: Maybe<AssetListOptions>
+export type QueryAdministratorsArgs = {
+  options?: Maybe<AdministratorListOptions>
 };
 
 
-export type QueryAssetArgs = {
+export type QueryAdministratorArgs = {
   id: Scalars['ID']
 };
 
 
-export type QueryCollectionsArgs = {
-  languageCode?: Maybe<LanguageCode>,
-  options?: Maybe<CollectionListOptions>
+export type QueryAssetsArgs = {
+  options?: Maybe<AssetListOptions>
 };
 
 
-export type QueryCollectionArgs = {
-  id: Scalars['ID'],
-  languageCode?: Maybe<LanguageCode>
+export type QueryAssetArgs = {
+  id: Scalars['ID']
 };
 
 
@@ -2591,18 +2611,15 @@ export type QueryChannelArgs = {
 };
 
 
-export type QueryAdministratorsArgs = {
-  options?: Maybe<AdministratorListOptions>
-};
-
-
-export type QueryAdministratorArgs = {
-  id: Scalars['ID']
+export type QueryCollectionsArgs = {
+  languageCode?: Maybe<LanguageCode>,
+  options?: Maybe<CollectionListOptions>
 };
 
 
-export type QueryCustomerGroupArgs = {
-  id: Scalars['ID']
+export type QueryCollectionArgs = {
+  id: Scalars['ID'],
+  languageCode?: Maybe<LanguageCode>
 };
 
 
@@ -2616,6 +2633,11 @@ export type QueryCountryArgs = {
 };
 
 
+export type QueryCustomerGroupArgs = {
+  id: Scalars['ID']
+};
+
+
 export type QueryCustomersArgs = {
   options?: Maybe<CustomerListOptions>
 };
@@ -2658,11 +2680,6 @@ export type QueryOrdersArgs = {
 };
 
 
-export type QuerySearchArgs = {
-  input: SearchInput
-};
-
-
 export type QueryPaymentMethodsArgs = {
   options?: Maybe<PaymentMethodListOptions>
 };
@@ -2685,6 +2702,11 @@ export type QueryProductOptionGroupArgs = {
 };
 
 
+export type QuerySearchArgs = {
+  input: SearchInput
+};
+
+
 export type QueryProductsArgs = {
   languageCode?: Maybe<LanguageCode>,
   options?: Maybe<ProductListOptions>
@@ -2698,6 +2720,16 @@ export type QueryProductArgs = {
 };
 
 
+export type QueryPromotionArgs = {
+  id: Scalars['ID']
+};
+
+
+export type QueryPromotionsArgs = {
+  options?: Maybe<PromotionListOptions>
+};
+
+
 export type QueryRolesArgs = {
   options?: Maybe<RoleListOptions>
 };
@@ -2718,17 +2750,17 @@ export type QueryShippingMethodArgs = {
 };
 
 
-export type QueryPromotionArgs = {
+export type QueryTaxCategoryArgs = {
   id: Scalars['ID']
 };
 
 
-export type QueryPromotionsArgs = {
-  options?: Maybe<PromotionListOptions>
+export type QueryTaxRatesArgs = {
+  options?: Maybe<TaxRateListOptions>
 };
 
 
-export type QueryTaxCategoryArgs = {
+export type QueryTaxRateArgs = {
   id: Scalars['ID']
 };
 
@@ -2737,16 +2769,6 @@ export type QueryZoneArgs = {
   id: Scalars['ID']
 };
 
-
-export type QueryTaxRatesArgs = {
-  options?: Maybe<TaxRateListOptions>
-};
-
-
-export type QueryTaxRateArgs = {
-  id: Scalars['ID']
-};
-
 export type Refund = Node & {
   __typename?: 'Refund',
   id: Scalars['ID'],
@@ -2891,7 +2913,7 @@ export type SearchResultSortParameter = {
 
 export type ServerConfig = {
   __typename?: 'ServerConfig',
-  customFields?: Maybe<Scalars['JSON']>,
+  customFieldConfig: CustomFields,
 };
 
 export type SettleRefundInput = {

+ 0 - 60
packages/common/src/shared-types.ts

@@ -46,66 +46,6 @@ export type ID = string | number;
  */
 export type CustomFieldType = 'string' | 'localeString' | 'int' | 'float' | 'boolean' | 'datetime';
 
-/**
- * @description
- * Configures a custom field on an entity in the {@link CustomFields} config object.
- *
- * @docsCategory custom-fields
- */
-export interface CustomFieldConfig {
-    name: string;
-    type: CustomFieldType;
-}
-
-/**
- * @description
- * Most entities can have additional fields added to them by defining an array of {@link CustomFieldConfig}
- * objects on against the corresponding key.
- *
- * @example
- * ```TypeScript
- * bootstrap({
- *     // ...
- *     customFields: {
- *         Product: [
- *             { name: 'infoUrl', type: 'string' },
- *             { name: 'downloadable', type: 'boolean' },
- *             { name: 'shortName', type: 'localeString' },
- *         ],
- *         User: [
- *             { name: 'socialLoginToken', type: 'string' },
- *         ],
- *     },
- * })
- * ```
- *
- * @docsCategory custom-fields
- */
-export interface CustomFields {
-    Address?: CustomFieldConfig[];
-    Collection?: CustomFieldConfig[];
-    Customer?: CustomFieldConfig[];
-    Facet?: CustomFieldConfig[];
-    FacetValue?: CustomFieldConfig[];
-    GlobalSettings?: CustomFieldConfig[];
-    OrderLine?: CustomFieldConfig[];
-    Product?: CustomFieldConfig[];
-    ProductOption?: CustomFieldConfig[];
-    ProductOptionGroup?: CustomFieldConfig[];
-    ProductVariant?: CustomFieldConfig[];
-    User?: CustomFieldConfig[];
-}
-
-/**
- * This interface should be implemented by any entity which can be extended
- * with custom fields.
- */
-export interface HasCustomFields {
-    customFields: CustomFieldsObject;
-}
-
-export type MayHaveCustomFields = Partial<HasCustomFields>;
-
 export type CustomFieldsObject = { [key: string]: any; };
 
 /**

+ 85 - 0
packages/core/e2e/custom-fields.e2e-spec.ts

@@ -0,0 +1,85 @@
+import gql from 'graphql-tag';
+import path from 'path';
+
+import { TEST_SETUP_TIMEOUT_MS } from './config/test-config';
+import { GetProductList } from './graphql/generated-e2e-admin-types';
+import { GET_PRODUCT_LIST } from './graphql/shared-definitions';
+import { TestAdminClient, TestShopClient } from './test-client';
+import { TestServer } from './test-server';
+
+// tslint:disable:no-non-null-assertion
+
+describe('Custom fields', () => {
+    const adminClient = new TestAdminClient();
+    const shopClient = new TestShopClient();
+    const server = new TestServer();
+
+    beforeAll(async () => {
+        await server.init({
+            productsCsvPath: path.join(__dirname, 'fixtures/e2e-products-minimal.csv'),
+            customerCount: 1,
+        }, {
+            customFields: {
+                Product: [
+                    { name: 'foo', type: 'string' },
+                ],
+            },
+        });
+        await adminClient.init();
+        await shopClient.init();
+    }, TEST_SETUP_TIMEOUT_MS);
+
+    afterAll(async () => {
+        await server.destroy();
+    });
+
+    it('globalSettings.serverConfig.customFieldConfig', async () => {
+        const { globalSettings } = await adminClient.query(gql`
+            query {
+                globalSettings {
+                    serverConfig {
+                        customFieldConfig {
+                            Product {
+                                name
+                                type
+                            }
+                        }
+                    }
+                }
+            }
+        `);
+
+        expect(globalSettings.serverConfig.customFieldConfig).toEqual({
+            Product: [
+                { name: 'foo', type: 'string' },
+            ],
+        });
+    });
+
+    it('works', async () => {
+        const { products } = await adminClient.query(gql`
+            query GetProductsCustomFields {
+                products {
+                    items {
+                        id
+                        name
+                        customFields {
+                            foo
+                        }
+                    }
+                }
+            }`, {
+            options: {},
+        });
+
+        expect(products.items).toEqual([
+            {
+                id: 'T_1',
+                name: 'Laptop',
+                customFields: {
+                    foo: null,
+                },
+            },
+        ]);
+    });
+});

+ 141 - 119
packages/core/e2e/graphql/generated-e2e-admin-types.ts

@@ -931,6 +931,28 @@ export type CustomerSortParameter = {
     emailAddress?: Maybe<SortOrder>;
 };
 
+export type CustomFieldConfig = {
+    __typename?: 'CustomFieldConfig';
+    name: Scalars['String'];
+    type: Scalars['String'];
+};
+
+export type CustomFields = {
+    __typename?: 'CustomFields';
+    Address: Array<CustomFieldConfig>;
+    Collection: Array<CustomFieldConfig>;
+    Customer: Array<CustomFieldConfig>;
+    Facet: Array<CustomFieldConfig>;
+    FacetValue: Array<CustomFieldConfig>;
+    GlobalSettings: Array<CustomFieldConfig>;
+    OrderLine: Array<CustomFieldConfig>;
+    Product: Array<CustomFieldConfig>;
+    ProductOption: Array<CustomFieldConfig>;
+    ProductOptionGroup: Array<CustomFieldConfig>;
+    ProductVariant: Array<CustomFieldConfig>;
+    User: Array<CustomFieldConfig>;
+};
+
 export type DateOperators = {
     eq?: Maybe<Scalars['DateTime']>;
     before?: Maybe<Scalars['DateTime']>;
@@ -1541,10 +1563,20 @@ export type MoveCollectionInput = {
 
 export type Mutation = {
     __typename?: 'Mutation';
+    /** Create a new Administrator */
+    createAdministrator: Administrator;
+    /** Update an existing Administrator */
+    updateAdministrator: Administrator;
+    /** Assign a Role to an Administrator */
+    assignRoleToAdministrator: Administrator;
     /** Create a new Asset */
     createAssets: Array<Asset>;
     login: LoginResult;
     logout: Scalars['Boolean'];
+    /** Create a new Channel */
+    createChannel: Channel;
+    /** Update an existing Channel */
+    updateChannel: Channel;
     /** Create a new Collection */
     createCollection: Collection;
     /** Update an existing Collection */
@@ -1553,16 +1585,12 @@ export type Mutation = {
     deleteCollection: DeletionResponse;
     /** Move a Collection to a different parent or index */
     moveCollection: Collection;
-    /** Create a new Channel */
-    createChannel: Channel;
-    /** Update an existing Channel */
-    updateChannel: Channel;
-    /** Create a new Administrator */
-    createAdministrator: Administrator;
-    /** Update an existing Administrator */
-    updateAdministrator: Administrator;
-    /** Assign a Role to an Administrator */
-    assignRoleToAdministrator: Administrator;
+    /** Create a new Country */
+    createCountry: Country;
+    /** Update an existing Country */
+    updateCountry: Country;
+    /** Delete a Country */
+    deleteCountry: DeletionResponse;
     /** Create a new CustomerGroup */
     createCustomerGroup: CustomerGroup;
     /** Update an existing CustomerGroup */
@@ -1571,14 +1599,8 @@ export type Mutation = {
     addCustomersToGroup: CustomerGroup;
     /** Remove Customers from a CustomerGroup */
     removeCustomersFromGroup: CustomerGroup;
-    /** Create a new Country */
-    createCountry: Country;
-    /** Update an existing Country */
-    updateCountry: Country;
-    /** Delete a Country */
-    deleteCountry: DeletionResponse;
-    importProducts?: Maybe<ImportInfo>;
     updateGlobalSettings: GlobalSettings;
+    importProducts?: Maybe<ImportInfo>;
     /** Create a new Customer. If a password is provided, a new User will also be created an linked to the Customer. */
     createCustomer: Customer;
     /** Update an existing Customer */
@@ -1609,7 +1631,6 @@ export type Mutation = {
     refundOrder: Refund;
     settleRefund: Refund;
     addNoteToOrder: Order;
-    reindex: JobInfo;
     /** Update an existing PaymentMethod */
     updatePaymentMethod: PaymentMethod;
     /** Create a new ProductOptionGroup */
@@ -1620,6 +1641,7 @@ export type Mutation = {
     createProductOption: ProductOption;
     /** Create a new ProductOption within a ProductOptionGroup */
     updateProductOption: ProductOption;
+    reindex: JobInfo;
     /** Create a new Product */
     createProduct: Product;
     /** Update an existing Product */
@@ -1636,6 +1658,9 @@ export type Mutation = {
     updateProductVariants: Array<Maybe<ProductVariant>>;
     /** Delete a ProductVariant */
     deleteProductVariant: DeletionResponse;
+    createPromotion: Promotion;
+    updatePromotion: Promotion;
+    deletePromotion: DeletionResponse;
     /** Create a new Role */
     createRole: Role;
     /** Update an existing Role */
@@ -1644,13 +1669,14 @@ export type Mutation = {
     createShippingMethod: ShippingMethod;
     /** Update an existing ShippingMethod */
     updateShippingMethod: ShippingMethod;
-    createPromotion: Promotion;
-    updatePromotion: Promotion;
-    deletePromotion: DeletionResponse;
     /** Create a new TaxCategory */
     createTaxCategory: TaxCategory;
     /** Update an existing TaxCategory */
     updateTaxCategory: TaxCategory;
+    /** Create a new TaxRate */
+    createTaxRate: TaxRate;
+    /** Update an existing TaxRate */
+    updateTaxRate: TaxRate;
     /** Create a new Zone */
     createZone: Zone;
     /** Update an existing Zone */
@@ -1661,10 +1687,19 @@ export type Mutation = {
     addMembersToZone: Zone;
     /** Remove members from a Zone */
     removeMembersFromZone: Zone;
-    /** Create a new TaxRate */
-    createTaxRate: TaxRate;
-    /** Update an existing TaxRate */
-    updateTaxRate: TaxRate;
+};
+
+export type MutationCreateAdministratorArgs = {
+    input: CreateAdministratorInput;
+};
+
+export type MutationUpdateAdministratorArgs = {
+    input: UpdateAdministratorInput;
+};
+
+export type MutationAssignRoleToAdministratorArgs = {
+    administratorId: Scalars['ID'];
+    roleId: Scalars['ID'];
 };
 
 export type MutationCreateAssetsArgs = {
@@ -1677,6 +1712,14 @@ export type MutationLoginArgs = {
     rememberMe?: Maybe<Scalars['Boolean']>;
 };
 
+export type MutationCreateChannelArgs = {
+    input: CreateChannelInput;
+};
+
+export type MutationUpdateChannelArgs = {
+    input: UpdateChannelInput;
+};
+
 export type MutationCreateCollectionArgs = {
     input: CreateCollectionInput;
 };
@@ -1693,25 +1736,16 @@ export type MutationMoveCollectionArgs = {
     input: MoveCollectionInput;
 };
 
-export type MutationCreateChannelArgs = {
-    input: CreateChannelInput;
-};
-
-export type MutationUpdateChannelArgs = {
-    input: UpdateChannelInput;
-};
-
-export type MutationCreateAdministratorArgs = {
-    input: CreateAdministratorInput;
+export type MutationCreateCountryArgs = {
+    input: CreateCountryInput;
 };
 
-export type MutationUpdateAdministratorArgs = {
-    input: UpdateAdministratorInput;
+export type MutationUpdateCountryArgs = {
+    input: UpdateCountryInput;
 };
 
-export type MutationAssignRoleToAdministratorArgs = {
-    administratorId: Scalars['ID'];
-    roleId: Scalars['ID'];
+export type MutationDeleteCountryArgs = {
+    id: Scalars['ID'];
 };
 
 export type MutationCreateCustomerGroupArgs = {
@@ -1732,26 +1766,14 @@ export type MutationRemoveCustomersFromGroupArgs = {
     customerIds: Array<Scalars['ID']>;
 };
 
-export type MutationCreateCountryArgs = {
-    input: CreateCountryInput;
-};
-
-export type MutationUpdateCountryArgs = {
-    input: UpdateCountryInput;
-};
-
-export type MutationDeleteCountryArgs = {
-    id: Scalars['ID'];
+export type MutationUpdateGlobalSettingsArgs = {
+    input: UpdateGlobalSettingsInput;
 };
 
 export type MutationImportProductsArgs = {
     csvFile: Scalars['Upload'];
 };
 
-export type MutationUpdateGlobalSettingsArgs = {
-    input: UpdateGlobalSettingsInput;
-};
-
 export type MutationCreateCustomerArgs = {
     input: CreateCustomerInput;
     password?: Maybe<Scalars['String']>;
@@ -1882,6 +1904,18 @@ export type MutationDeleteProductVariantArgs = {
     id: Scalars['ID'];
 };
 
+export type MutationCreatePromotionArgs = {
+    input: CreatePromotionInput;
+};
+
+export type MutationUpdatePromotionArgs = {
+    input: UpdatePromotionInput;
+};
+
+export type MutationDeletePromotionArgs = {
+    id: Scalars['ID'];
+};
+
 export type MutationCreateRoleArgs = {
     input: CreateRoleInput;
 };
@@ -1898,18 +1932,6 @@ export type MutationUpdateShippingMethodArgs = {
     input: UpdateShippingMethodInput;
 };
 
-export type MutationCreatePromotionArgs = {
-    input: CreatePromotionInput;
-};
-
-export type MutationUpdatePromotionArgs = {
-    input: UpdatePromotionInput;
-};
-
-export type MutationDeletePromotionArgs = {
-    id: Scalars['ID'];
-};
-
 export type MutationCreateTaxCategoryArgs = {
     input: CreateTaxCategoryInput;
 };
@@ -1918,6 +1940,14 @@ export type MutationUpdateTaxCategoryArgs = {
     input: UpdateTaxCategoryInput;
 };
 
+export type MutationCreateTaxRateArgs = {
+    input: CreateTaxRateInput;
+};
+
+export type MutationUpdateTaxRateArgs = {
+    input: UpdateTaxRateInput;
+};
+
 export type MutationCreateZoneArgs = {
     input: CreateZoneInput;
 };
@@ -1940,14 +1970,6 @@ export type MutationRemoveMembersFromZoneArgs = {
     memberIds: Array<Scalars['ID']>;
 };
 
-export type MutationCreateTaxRateArgs = {
-    input: CreateTaxRateInput;
-};
-
-export type MutationUpdateTaxRateArgs = {
-    input: UpdateTaxRateInput;
-};
-
 export type Node = {
     __typename?: 'Node';
     id: Scalars['ID'];
@@ -2442,21 +2464,21 @@ export type PromotionSortParameter = {
 
 export type Query = {
     __typename?: 'Query';
+    administrators: AdministratorList;
+    administrator?: Maybe<Administrator>;
     assets: AssetList;
     asset?: Maybe<Asset>;
     me?: Maybe<CurrentUser>;
-    collections: CollectionList;
-    collection?: Maybe<Collection>;
-    collectionFilters: Array<ConfigurableOperation>;
     channels: Array<Channel>;
     channel?: Maybe<Channel>;
     activeChannel: Channel;
-    administrators: AdministratorList;
-    administrator?: Maybe<Administrator>;
-    customerGroups: Array<CustomerGroup>;
-    customerGroup?: Maybe<CustomerGroup>;
+    collections: CollectionList;
+    collection?: Maybe<Collection>;
+    collectionFilters: Array<ConfigurableOperation>;
     countries: CountryList;
     country?: Maybe<Country>;
+    customerGroups: Array<CustomerGroup>;
+    customerGroup?: Maybe<CustomerGroup>;
     globalSettings: GlobalSettings;
     customers: CustomerList;
     customer?: Maybe<Customer>;
@@ -2466,63 +2488,59 @@ export type Query = {
     jobs: Array<JobInfo>;
     order?: Maybe<Order>;
     orders: OrderList;
-    search: SearchResponse;
     paymentMethods: PaymentMethodList;
     paymentMethod?: Maybe<PaymentMethod>;
     productOptionGroups: Array<ProductOptionGroup>;
     productOptionGroup?: Maybe<ProductOptionGroup>;
+    search: SearchResponse;
     products: ProductList;
     /** Get a Product either by id or slug. If neither id nor slug is speicified, an error will result. */
     product?: Maybe<Product>;
+    promotion?: Maybe<Promotion>;
+    promotions: PromotionList;
+    adjustmentOperations: AdjustmentOperations;
     roles: RoleList;
     role?: Maybe<Role>;
     shippingMethods: ShippingMethodList;
     shippingMethod?: Maybe<ShippingMethod>;
     shippingEligibilityCheckers: Array<ConfigurableOperation>;
     shippingCalculators: Array<ConfigurableOperation>;
-    promotion?: Maybe<Promotion>;
-    promotions: PromotionList;
-    adjustmentOperations: AdjustmentOperations;
     taxCategories: Array<TaxCategory>;
     taxCategory?: Maybe<TaxCategory>;
-    zones: Array<Zone>;
-    zone?: Maybe<Zone>;
     taxRates: TaxRateList;
     taxRate?: Maybe<TaxRate>;
+    zones: Array<Zone>;
+    zone?: Maybe<Zone>;
 };
 
-export type QueryAssetsArgs = {
-    options?: Maybe<AssetListOptions>;
+export type QueryAdministratorsArgs = {
+    options?: Maybe<AdministratorListOptions>;
 };
 
-export type QueryAssetArgs = {
+export type QueryAdministratorArgs = {
     id: Scalars['ID'];
 };
 
-export type QueryCollectionsArgs = {
-    languageCode?: Maybe<LanguageCode>;
-    options?: Maybe<CollectionListOptions>;
+export type QueryAssetsArgs = {
+    options?: Maybe<AssetListOptions>;
 };
 
-export type QueryCollectionArgs = {
+export type QueryAssetArgs = {
     id: Scalars['ID'];
-    languageCode?: Maybe<LanguageCode>;
 };
 
 export type QueryChannelArgs = {
     id: Scalars['ID'];
 };
 
-export type QueryAdministratorsArgs = {
-    options?: Maybe<AdministratorListOptions>;
-};
-
-export type QueryAdministratorArgs = {
-    id: Scalars['ID'];
+export type QueryCollectionsArgs = {
+    languageCode?: Maybe<LanguageCode>;
+    options?: Maybe<CollectionListOptions>;
 };
 
-export type QueryCustomerGroupArgs = {
+export type QueryCollectionArgs = {
     id: Scalars['ID'];
+    languageCode?: Maybe<LanguageCode>;
 };
 
 export type QueryCountriesArgs = {
@@ -2533,6 +2551,10 @@ export type QueryCountryArgs = {
     id: Scalars['ID'];
 };
 
+export type QueryCustomerGroupArgs = {
+    id: Scalars['ID'];
+};
+
 export type QueryCustomersArgs = {
     options?: Maybe<CustomerListOptions>;
 };
@@ -2567,10 +2589,6 @@ export type QueryOrdersArgs = {
     options?: Maybe<OrderListOptions>;
 };
 
-export type QuerySearchArgs = {
-    input: SearchInput;
-};
-
 export type QueryPaymentMethodsArgs = {
     options?: Maybe<PaymentMethodListOptions>;
 };
@@ -2589,6 +2607,10 @@ export type QueryProductOptionGroupArgs = {
     languageCode?: Maybe<LanguageCode>;
 };
 
+export type QuerySearchArgs = {
+    input: SearchInput;
+};
+
 export type QueryProductsArgs = {
     languageCode?: Maybe<LanguageCode>;
     options?: Maybe<ProductListOptions>;
@@ -2600,6 +2622,14 @@ export type QueryProductArgs = {
     languageCode?: Maybe<LanguageCode>;
 };
 
+export type QueryPromotionArgs = {
+    id: Scalars['ID'];
+};
+
+export type QueryPromotionsArgs = {
+    options?: Maybe<PromotionListOptions>;
+};
+
 export type QueryRolesArgs = {
     options?: Maybe<RoleListOptions>;
 };
@@ -2616,22 +2646,10 @@ export type QueryShippingMethodArgs = {
     id: Scalars['ID'];
 };
 
-export type QueryPromotionArgs = {
-    id: Scalars['ID'];
-};
-
-export type QueryPromotionsArgs = {
-    options?: Maybe<PromotionListOptions>;
-};
-
 export type QueryTaxCategoryArgs = {
     id: Scalars['ID'];
 };
 
-export type QueryZoneArgs = {
-    id: Scalars['ID'];
-};
-
 export type QueryTaxRatesArgs = {
     options?: Maybe<TaxRateListOptions>;
 };
@@ -2640,6 +2658,10 @@ export type QueryTaxRateArgs = {
     id: Scalars['ID'];
 };
 
+export type QueryZoneArgs = {
+    id: Scalars['ID'];
+};
+
 export type Refund = Node & {
     __typename?: 'Refund';
     id: Scalars['ID'];
@@ -2786,7 +2808,7 @@ export type SearchResultSortParameter = {
 
 export type ServerConfig = {
     __typename?: 'ServerConfig';
-    customFields?: Maybe<Scalars['JSON']>;
+    customFieldConfig: CustomFields;
 };
 
 export type SettleRefundInput = {

+ 23 - 1
packages/core/e2e/graphql/generated-e2e-shop-types.ts

@@ -655,6 +655,28 @@ export type CustomerList = PaginatedList & {
     totalItems: Scalars['Int'];
 };
 
+export type CustomFieldConfig = {
+    __typename?: 'CustomFieldConfig';
+    name: Scalars['String'];
+    type: Scalars['String'];
+};
+
+export type CustomFields = {
+    __typename?: 'CustomFields';
+    Address: Array<CustomFieldConfig>;
+    Collection: Array<CustomFieldConfig>;
+    Customer: Array<CustomFieldConfig>;
+    Facet: Array<CustomFieldConfig>;
+    FacetValue: Array<CustomFieldConfig>;
+    GlobalSettings: Array<CustomFieldConfig>;
+    OrderLine: Array<CustomFieldConfig>;
+    Product: Array<CustomFieldConfig>;
+    ProductOption: Array<CustomFieldConfig>;
+    ProductOptionGroup: Array<CustomFieldConfig>;
+    ProductVariant: Array<CustomFieldConfig>;
+    User: Array<CustomFieldConfig>;
+};
+
 export type DateOperators = {
     eq?: Maybe<Scalars['DateTime']>;
     before?: Maybe<Scalars['DateTime']>;
@@ -1909,7 +1931,7 @@ export type SearchResultSortParameter = {
 
 export type ServerConfig = {
     __typename?: 'ServerConfig';
-    customFields?: Maybe<Scalars['JSON']>;
+    customFieldConfig: CustomFields;
 };
 
 export type ShippingMethod = Node & {

+ 22 - 6
packages/core/src/api/config/__snapshots__/graphql-custom-fields.spec.ts.snap

@@ -1,7 +1,9 @@
 // Jest Snapshot v1, https://goo.gl/fbAQLP
 
 exports[`addGraphQLCustomFields() extends a type 1`] = `
-"scalar JSON
+"scalar DateTime
+
+scalar JSON
 
 type Product {
   id: ID
@@ -23,6 +25,8 @@ input DateOperators {
   eq: String
 }
 
+scalar DateTime
+
 scalar JSON
 
 input NumberOperators {
@@ -38,7 +42,7 @@ type ProductCustomFields {
   available: Boolean
   shortName: String
   rating: Float
-  published: String
+  published: DateTime
 }
 
 input ProductFilterParameter {
@@ -56,7 +60,9 @@ input StringOperators {
 `;
 
 exports[`addGraphQLCustomFields() extends a type with SortParameters 1`] = `
-"scalar JSON
+"scalar DateTime
+
+scalar JSON
 
 type Product {
   id: ID
@@ -91,6 +97,8 @@ input CreateProductInput {
   customFields: CreateProductCustomFieldsInput
 }
 
+scalar DateTime
+
 scalar JSON
 
 type Product {
@@ -115,6 +123,8 @@ input CreateProductInput {
   customFields: CreateProductCustomFieldsInput
 }
 
+scalar DateTime
+
 scalar JSON
 
 type Product {
@@ -148,7 +158,9 @@ input ProductTranslationInput {
 `;
 
 exports[`addGraphQLCustomFields() extends a type with a translation 1`] = `
-"scalar JSON
+"scalar DateTime
+
+scalar JSON
 
 type Product {
   id: ID
@@ -173,7 +185,9 @@ type ProductTranslationCustomFields {
 `;
 
 exports[`addGraphQLCustomFields() extends a type with an Update input 1`] = `
-"scalar JSON
+"scalar DateTime
+
+scalar JSON
 
 type Product {
   id: ID
@@ -197,7 +211,9 @@ input UpdateProductInput {
 `;
 
 exports[`addGraphQLCustomFields() uses JSON scalar if no custom fields defined 1`] = `
-"scalar JSON
+"scalar DateTime
+
+scalar JSON
 
 type Product {
   id: ID

+ 2 - 1
packages/core/src/api/config/configure-graphql-module.ts

@@ -17,7 +17,7 @@ import { IdEncoderExtension } from '../middleware/id-encoder-extension';
 import { TranslateErrorExtension } from '../middleware/translate-errors-extension';
 
 import { generateListOptions } from './generate-list-options';
-import { addGraphQLCustomFields, addOrderLineCustomFieldsInput } from './graphql-custom-fields';
+import { addGraphQLCustomFields, addOrderLineCustomFieldsInput, addServerConfigCustomFields } from './graphql-custom-fields';
 
 export interface GraphQLApiOptions {
     apiType: 'shop' | 'admin';
@@ -130,6 +130,7 @@ async function createGraphQLOptions(
         const typeDefs = await typesLoader.mergeTypesByPaths(options.typePaths);
         let schema = generateListOptions(typeDefs);
         schema = addGraphQLCustomFields(schema, customFields);
+        schema = addServerConfigCustomFields(schema, customFields);
         schema = addOrderLineCustomFieldsInput(schema, customFields.OrderLine || []);
         const pluginSchemaExtensions = getPluginAPIExtensions(configService.plugins, apiType).map(
             e => e.schema,

+ 0 - 2
packages/core/src/api/config/generate-list-options.spec.ts

@@ -1,7 +1,5 @@
 import { buildSchema, printType } from 'graphql';
 
-import { CustomFields } from '@vendure/common/lib/shared-types';
-
 import { generateListOptions } from './generate-list-options';
 // tslint:disable:no-non-null-assertion
 

+ 2 - 1
packages/core/src/api/config/graphql-custom-fields.spec.ts

@@ -1,6 +1,7 @@
-import { CustomFieldConfig, CustomFields } from '@vendure/common/lib/shared-types';
 import { printSchema } from 'graphql';
 
+import { CustomFieldConfig, CustomFields } from '../../config/custom-field/custom-field-types';
+
 import { addGraphQLCustomFields, addOrderLineCustomFieldsInput } from './graphql-custom-fields';
 
 describe('addGraphQLCustomFields()', () => {

+ 29 - 4
packages/core/src/api/config/graphql-custom-fields.ts

@@ -1,6 +1,8 @@
-import { CustomFieldConfig, CustomFields, CustomFieldType } from '@vendure/common/lib/shared-types';
+import { CustomFieldType } from '@vendure/common/lib/shared-types';
 import { assertNever } from '@vendure/common/lib/shared-utils';
-import { buildSchema, extendSchema, GraphQLArgument, GraphQLInputObjectType, GraphQLSchema, parse } from 'graphql';
+import { buildSchema, extendSchema, GraphQLInputObjectType, GraphQLSchema, parse } from 'graphql';
+
+import { CustomFieldConfig, CustomFields } from '../../config/custom-field/custom-field-types';
 
 /**
  * Given a CustomFields config object, generates an SDL string extending the built-in
@@ -21,6 +23,12 @@ export function addGraphQLCustomFields(
         `;
     }
 
+    if (!schema.getType('DateTime')) {
+        customFieldTypeDefs += `
+            scalar DateTime
+        `;
+    }
+
     for (const entityName of Object.keys(customFieldConfig)) {
         const customEntityFields = customFieldConfig[entityName as keyof CustomFields] || [];
 
@@ -139,6 +147,22 @@ export function addGraphQLCustomFields(
     return extendSchema(schema, parse(customFieldTypeDefs));
 }
 
+export function addServerConfigCustomFields(typeDefsOrSchema: string | GraphQLSchema,
+                                            customFieldConfig: CustomFields) {
+    const schema = typeof typeDefsOrSchema === 'string' ? buildSchema(typeDefsOrSchema) : typeDefsOrSchema;
+    const customFieldTypeDefs = `
+            type CustomFields {
+                ${Object.keys(customFieldConfig).reduce((output, name) => output + name + `: [CustomFieldConfig!]!\n`, '')}
+            }
+
+            extend type ServerConfig {
+                customFieldConfig: CustomFields!
+            }
+        `;
+
+    return extendSchema(schema, parse(customFieldTypeDefs));
+}
+
 /**
  * If CustomFields are defined on the OrderLine entity, then an extra `customFields` argument
  * must be added to the `addItemToOrder` and `adjustOrderLine` mutations.
@@ -178,7 +202,7 @@ export function addOrderLineCustomFieldsInput(typeDefsOrSchema: string | GraphQL
     return new GraphQLSchema(schemaConfig);
 }
 
-type GraphQLFieldType = 'String' | 'Int' | 'Float' | 'Boolean' | 'ID';
+type GraphQLFieldType = 'DateTime' | 'String' | 'Int' | 'Float' | 'Boolean' | 'ID';
 
 /**
  * Maps an array of CustomFieldConfig objects into a string of SDL fields.
@@ -208,9 +232,10 @@ function getFilterOperator(type: CustomFieldType): string {
 function getGraphQlType(type: CustomFieldType): GraphQLFieldType {
     switch (type) {
         case 'string':
-        case 'datetime':
         case 'localeString':
             return 'String';
+        case 'datetime':
+            return 'DateTime';
         case 'boolean':
             return 'Boolean';
         case 'int':

+ 2 - 2
packages/core/src/api/resolvers/admin/global-settings.resolver.ts

@@ -20,9 +20,9 @@ export class GlobalSettingsResolver {
      * Exposes a subset of the VendureConfig which may be of use to clients.
      */
     @ResolveProperty()
-    serverConfig(): Partial<VendureConfig> {
+    serverConfig() {
         return {
-            customFields: this.configService.customFields,
+            customFieldConfig: this.configService.customFields,
         };
     }
 

+ 6 - 2
packages/core/src/api/schema/type/global-settings.type.graphql

@@ -7,6 +7,10 @@ type GlobalSettings {
     serverConfig: ServerConfig!
 }
 
-type ServerConfig {
-    customFields: JSON
+# Programatically extended by the addGraphQLCustomFields function
+type ServerConfig
+
+type CustomFieldConfig {
+    name: String!
+    type: String!
 }

+ 1 - 1
packages/core/src/config/config.service.ts

@@ -1,13 +1,13 @@
 import { Injectable } from '@nestjs/common';
 import { CorsOptions } from '@nestjs/common/interfaces/external/cors-options.interface';
 import { LanguageCode } from '@vendure/common/lib/generated-types';
-import { CustomFields } from '@vendure/common/lib/shared-types';
 import { RequestHandler } from 'express';
 import { ConnectionOptions } from 'typeorm';
 
 import { ReadOnlyRequired } from '../common/types/common-types';
 
 import { getConfig } from './config-helpers';
+import { CustomFields } from './custom-field/custom-field-types';
 import { EntityIdStrategy } from './entity-id-strategy/entity-id-strategy';
 import { Logger, VendureLogger } from './logger/vendure-logger';
 import {

+ 59 - 0
packages/core/src/config/custom-field/custom-field-types.ts

@@ -0,0 +1,59 @@
+import { CustomFieldConfig as GraphQLCustomFieldConfig } from '@vendure/common/lib/generated-types';
+import { CustomFieldsObject, CustomFieldType } from '@vendure/common/src/shared-types';
+
+/**
+ * @description
+ * Configures a custom field on an entity in the {@link CustomFields} config object.
+ *
+ * @docsCategory custom-fields
+ */
+export interface CustomFieldConfig extends Omit<GraphQLCustomFieldConfig, '__typename'> {
+    type: CustomFieldType;
+}
+
+/**
+ * @description
+ * Most entities can have additional fields added to them by defining an array of {@link CustomFieldConfig}
+ * objects on against the corresponding key.
+ *
+ * @example
+ * ```TypeScript
+ * bootstrap({
+ *     // ...
+ *     customFields: {
+ *         Product: [
+ *             { name: 'infoUrl', type: 'string' },
+ *             { name: 'downloadable', type: 'boolean' },
+ *             { name: 'shortName', type: 'localeString' },
+ *         ],
+ *         User: [
+ *             { name: 'socialLoginToken', type: 'string' },
+ *         ],
+ *     },
+ * })
+ * ```
+ *
+ * @docsCategory custom-fields
+ */
+export interface CustomFields {
+    Address?: CustomFieldConfig[];
+    Collection?: CustomFieldConfig[];
+    Customer?: CustomFieldConfig[];
+    Facet?: CustomFieldConfig[];
+    FacetValue?: CustomFieldConfig[];
+    GlobalSettings?: CustomFieldConfig[];
+    OrderLine?: CustomFieldConfig[];
+    Product?: CustomFieldConfig[];
+    ProductOption?: CustomFieldConfig[];
+    ProductOptionGroup?: CustomFieldConfig[];
+    ProductVariant?: CustomFieldConfig[];
+    User?: CustomFieldConfig[];
+}
+
+/**
+ * This interface should be implemented by any entity which can be extended
+ * with custom fields.
+ */
+export interface HasCustomFields {
+    customFields: CustomFieldsObject;
+}

+ 1 - 1
packages/core/src/config/default-config.ts

@@ -1,12 +1,12 @@
 import { Transport } from '@nestjs/microservices';
 import { LanguageCode } from '@vendure/common/lib/generated-types';
-import { CustomFields } from '@vendure/common/lib/shared-types';
 
 import { ReadOnlyRequired } from '../common/types/common-types';
 
 import { DefaultAssetNamingStrategy } from './asset-naming-strategy/default-asset-naming-strategy';
 import { NoAssetPreviewStrategy } from './asset-preview-strategy/no-asset-preview-strategy';
 import { NoAssetStorageStrategy } from './asset-storage-strategy/no-asset-storage-strategy';
+import { CustomFields } from './custom-field/custom-field-types';
 import { AutoIncrementIdStrategy } from './entity-id-strategy/auto-increment-id-strategy';
 import { DefaultLogger } from './logger/default-logger';
 import { TypeOrmLogger } from './logger/typeorm-logger';

+ 1 - 1
packages/core/src/config/vendure-config.ts

@@ -1,7 +1,6 @@
 import { CorsOptions } from '@nestjs/common/interfaces/external/cors-options.interface';
 import { ClientOptions, Transport } from '@nestjs/microservices';
 import { LanguageCode } from '@vendure/common/lib/generated-types';
-import { CustomFields } from '@vendure/common/lib/shared-types';
 import { RequestHandler } from 'express';
 import { Observable } from 'rxjs';
 import { ConnectionOptions } from 'typeorm';
@@ -13,6 +12,7 @@ import { OrderState } from '../service/helpers/order-state-machine/order-state';
 import { AssetNamingStrategy } from './asset-naming-strategy/asset-naming-strategy';
 import { AssetPreviewStrategy } from './asset-preview-strategy/asset-preview-strategy';
 import { AssetStorageStrategy } from './asset-storage-strategy/asset-storage-strategy';
+import { CustomFields } from './custom-field/custom-field-types';
 import { EntityIdStrategy } from './entity-id-strategy/entity-id-strategy';
 import { VendureLogger } from './logger/vendure-logger';
 import { OrderMergeStrategy } from './order-merge-strategy/order-merge-strategy';

+ 2 - 1
packages/core/src/entity/address/address.entity.ts

@@ -1,6 +1,7 @@
-import { DeepPartial, HasCustomFields } from '@vendure/common/lib/shared-types';
+import { DeepPartial } from '@vendure/common/lib/shared-types';
 import { Column, Entity, ManyToOne } from 'typeorm';
 
+import { HasCustomFields } from '../../config/custom-field/custom-field-types';
 import { VendureEntity } from '../base/base.entity';
 import { Country } from '../country/country.entity';
 import { CustomAddressFields } from '../custom-entity-fields';

+ 0 - 1
packages/core/src/entity/asset/asset.entity.ts

@@ -1,6 +1,5 @@
 import { AssetType } from '@vendure/common/lib/generated-types';
 import { DeepPartial } from '@vendure/common/lib/shared-types';
-import { HasCustomFields } from '@vendure/common/lib/shared-types';
 import { Column, Entity, JoinColumn, OneToMany, OneToOne } from 'typeorm';
 
 import { Address } from '../address/address.entity';

+ 2 - 1
packages/core/src/entity/collection/collection-translation.entity.ts

@@ -1,8 +1,9 @@
 import { LanguageCode } from '@vendure/common/lib/generated-types';
-import { DeepPartial, HasCustomFields } from '@vendure/common/lib/shared-types';
+import { DeepPartial } from '@vendure/common/lib/shared-types';
 import { Column, Entity, ManyToOne } from 'typeorm';
 
 import { Translation } from '../../common/types/locale-types';
+import { HasCustomFields } from '../../config/custom-field/custom-field-types';
 import { VendureEntity } from '../base/base.entity';
 import { CustomCollectionFieldsTranslation } from '../custom-entity-fields';
 

+ 2 - 1
packages/core/src/entity/collection/collection.entity.ts

@@ -1,5 +1,5 @@
 import { ConfigurableOperation } from '@vendure/common/lib/generated-types';
-import { DeepPartial, HasCustomFields } from '@vendure/common/lib/shared-types';
+import { DeepPartial } from '@vendure/common/lib/shared-types';
 import {
     Column,
     Entity,
@@ -13,6 +13,7 @@ import {
 
 import { ChannelAware } from '../../common/types/common-types';
 import { LocaleString, Translatable, Translation } from '../../common/types/locale-types';
+import { HasCustomFields } from '../../config/custom-field/custom-field-types';
 import { Asset } from '../asset/asset.entity';
 import { VendureEntity } from '../base/base.entity';
 import { Channel } from '../channel/channel.entity';

+ 2 - 1
packages/core/src/entity/custom-entity-fields.ts

@@ -1,7 +1,8 @@
-import { CustomFieldConfig, CustomFields, CustomFieldType, Type } from '@vendure/common/lib/shared-types';
+import { CustomFieldType, Type } from '@vendure/common/lib/shared-types';
 import { assertNever } from '@vendure/common/lib/shared-utils';
 import { Column, ColumnType, Connection, ConnectionOptions, getConnection } from 'typeorm';
 
+import { CustomFieldConfig, CustomFields } from '../config/custom-field/custom-field-types';
 import { VendureConfig } from '../config/vendure-config';
 
 import { VendureEntity } from './base/base.entity';

+ 1 - 1
packages/core/src/entity/customer/customer.entity.ts

@@ -1,8 +1,8 @@
 import { DeepPartial } from '@vendure/common/lib/shared-types';
-import { HasCustomFields } from '@vendure/common/lib/shared-types';
 import { Column, Entity, JoinColumn, JoinTable, ManyToMany, OneToMany, OneToOne } from 'typeorm';
 
 import { SoftDeletable } from '../../common/types/common-types';
+import { HasCustomFields } from '../../config/custom-field/custom-field-types';
 import { Address } from '../address/address.entity';
 import { VendureEntity } from '../base/base.entity';
 import { CustomCustomerFields } from '../custom-entity-fields';

+ 2 - 1
packages/core/src/entity/facet-value/facet-value.entity.ts

@@ -1,7 +1,8 @@
-import { DeepPartial, HasCustomFields } from '@vendure/common/lib/shared-types';
+import { DeepPartial } from '@vendure/common/lib/shared-types';
 import { Column, Entity, ManyToOne, OneToMany } from 'typeorm';
 
 import { LocaleString, Translatable, Translation } from '../../common/types/locale-types';
+import { HasCustomFields } from '../../config/custom-field/custom-field-types';
 import { VendureEntity } from '../base/base.entity';
 import { CustomFacetValueFields } from '../custom-entity-fields';
 import { Facet } from '../facet/facet.entity';

+ 2 - 1
packages/core/src/entity/facet/facet-translation.entity.ts

@@ -1,8 +1,9 @@
 import { LanguageCode } from '@vendure/common/lib/generated-types';
-import { DeepPartial, HasCustomFields } from '@vendure/common/lib/shared-types';
+import { DeepPartial } from '@vendure/common/lib/shared-types';
 import { Column, Entity, ManyToOne } from 'typeorm';
 
 import { Translation } from '../../common/types/locale-types';
+import { HasCustomFields } from '../../config/custom-field/custom-field-types';
 import { VendureEntity } from '../base/base.entity';
 import { CustomFacetFieldsTranslation } from '../custom-entity-fields';
 

+ 2 - 1
packages/core/src/entity/facet/facet.entity.ts

@@ -1,7 +1,8 @@
-import { DeepPartial, HasCustomFields } from '@vendure/common/lib/shared-types';
+import { DeepPartial } from '@vendure/common/lib/shared-types';
 import { Column, Entity, OneToMany } from 'typeorm';
 
 import { LocaleString, Translatable, Translation } from '../../common/types/locale-types';
+import { HasCustomFields } from '../../config/custom-field/custom-field-types';
 import { VendureEntity } from '../base/base.entity';
 import { CustomFacetFields } from '../custom-entity-fields';
 import { FacetValue } from '../facet-value/facet-value.entity';

+ 2 - 1
packages/core/src/entity/global-settings/global-settings.entity.ts

@@ -1,8 +1,9 @@
 import { LanguageCode } from '@vendure/common/lib/generated-types';
-import { DeepPartial, HasCustomFields } from '@vendure/common/lib/shared-types';
+import { DeepPartial } from '@vendure/common/lib/shared-types';
 import { Column, Entity } from 'typeorm';
 
 import { VendureEntity } from '..';
+import { HasCustomFields } from '../../config/custom-field/custom-field-types';
 import { CustomGlobalSettingsFields } from '../custom-entity-fields';
 
 @Entity()

+ 2 - 1
packages/core/src/entity/order-line/order-line.entity.ts

@@ -1,8 +1,9 @@
 import { Adjustment, AdjustmentType } from '@vendure/common/lib/generated-types';
-import { DeepPartial, HasCustomFields } from '@vendure/common/lib/shared-types';
+import { DeepPartial } from '@vendure/common/lib/shared-types';
 import { Column, Entity, ManyToOne, OneToMany } from 'typeorm';
 
 import { Calculated } from '../../common/calculated-decorator';
+import { HasCustomFields } from '../../config/custom-field/custom-field-types';
 import { Asset } from '../asset/asset.entity';
 import { VendureEntity } from '../base/base.entity';
 import { CustomOrderLineFields, CustomProductFields } from '../custom-entity-fields';

+ 1 - 1
packages/core/src/entity/product-option-group/product-option-group-translation.entity.ts

@@ -1,9 +1,9 @@
 import { LanguageCode } from '@vendure/common/lib/generated-types';
 import { DeepPartial } from '@vendure/common/lib/shared-types';
-import { HasCustomFields } from '@vendure/common/lib/shared-types';
 import { Column, Entity, ManyToOne } from 'typeorm';
 
 import { Translation } from '../../common/types/locale-types';
+import { HasCustomFields } from '../../config/custom-field/custom-field-types';
 import { VendureEntity } from '../base/base.entity';
 import { CustomProductOptionGroupFieldsTranslation } from '../custom-entity-fields';
 

+ 1 - 1
packages/core/src/entity/product-option-group/product-option-group.entity.ts

@@ -1,8 +1,8 @@
 import { DeepPartial } from '@vendure/common/lib/shared-types';
-import { HasCustomFields } from '@vendure/common/lib/shared-types';
 import { Column, Entity, ManyToOne, OneToMany } from 'typeorm';
 
 import { LocaleString, Translatable, Translation } from '../../common/types/locale-types';
+import { HasCustomFields } from '../../config/custom-field/custom-field-types';
 import { VendureEntity } from '../base/base.entity';
 import { CustomProductOptionGroupFields } from '../custom-entity-fields';
 import { ProductOption } from '../product-option/product-option.entity';

+ 1 - 1
packages/core/src/entity/product-option/product-option-translation.entity.ts

@@ -1,9 +1,9 @@
 import { LanguageCode } from '@vendure/common/lib/generated-types';
 import { DeepPartial } from '@vendure/common/lib/shared-types';
-import { HasCustomFields } from '@vendure/common/lib/shared-types';
 import { Column, Entity, ManyToOne } from 'typeorm';
 
 import { Translation } from '../../common/types/locale-types';
+import { HasCustomFields } from '../../config/custom-field/custom-field-types';
 import { VendureEntity } from '../base/base.entity';
 import { CustomProductOptionFieldsTranslation } from '../custom-entity-fields';
 

+ 1 - 1
packages/core/src/entity/product-option/product-option.entity.ts

@@ -1,9 +1,9 @@
 import { DeepPartial, ID } from '@vendure/common/lib/shared-types';
-import { HasCustomFields } from '@vendure/common/lib/shared-types';
 import { Column, Entity, ManyToOne, OneToMany } from 'typeorm';
 
 import { LocaleString, Translatable, Translation } from '../../common/types/locale-types';
 import { idType } from '../../config/config-helpers';
+import { HasCustomFields } from '../../config/custom-field/custom-field-types';
 import { VendureEntity } from '../base/base.entity';
 import { CustomProductOptionFields } from '../custom-entity-fields';
 import { ProductOptionGroup } from '../product-option-group/product-option-group.entity';

+ 1 - 1
packages/core/src/entity/product-variant/product-variant-translation.entity.ts

@@ -1,9 +1,9 @@
 import { LanguageCode } from '@vendure/common/lib/generated-types';
 import { DeepPartial } from '@vendure/common/lib/shared-types';
-import { HasCustomFields } from '@vendure/common/lib/shared-types';
 import { Column, Entity, ManyToOne } from 'typeorm';
 
 import { Translation } from '../../common/types/locale-types';
+import { HasCustomFields } from '../../config/custom-field/custom-field-types';
 import { VendureEntity } from '../base/base.entity';
 import { CustomProductVariantFieldsTranslation } from '../custom-entity-fields';
 

+ 2 - 1
packages/core/src/entity/product-variant/product-variant.entity.ts

@@ -1,9 +1,10 @@
 import { CurrencyCode } from '@vendure/common/lib/generated-types';
-import { DeepPartial, HasCustomFields } from '@vendure/common/lib/shared-types';
+import { DeepPartial } from '@vendure/common/lib/shared-types';
 import { Column, Entity, JoinTable, ManyToMany, ManyToOne, OneToMany } from 'typeorm';
 
 import { SoftDeletable } from '../../common/types/common-types';
 import { LocaleString, Translatable, Translation } from '../../common/types/locale-types';
+import { HasCustomFields } from '../../config/custom-field/custom-field-types';
 import { Asset } from '../asset/asset.entity';
 import { VendureEntity } from '../base/base.entity';
 import { Collection } from '../collection/collection.entity';

+ 2 - 1
packages/core/src/entity/product/product-translation.entity.ts

@@ -1,8 +1,9 @@
 import { LanguageCode } from '@vendure/common/lib/generated-types';
-import { DeepPartial, HasCustomFields, ID } from '@vendure/common/lib/shared-types';
+import { DeepPartial, ID } from '@vendure/common/lib/shared-types';
 import { Column, Entity, Index, ManyToOne, RelationId } from 'typeorm';
 
 import { Translation } from '../../common/types/locale-types';
+import { HasCustomFields } from '../../config/custom-field/custom-field-types';
 import { VendureEntity } from '../base/base.entity';
 import { CustomProductFieldsTranslation } from '../custom-entity-fields';
 

+ 2 - 1
packages/core/src/entity/product/product.entity.ts

@@ -1,9 +1,10 @@
-import { DeepPartial, HasCustomFields } from '@vendure/common/lib/shared-types';
+import { DeepPartial } from '@vendure/common/lib/shared-types';
 import { doc } from 'prettier';
 import { Column, Entity, JoinColumn, JoinTable, ManyToMany, ManyToOne, OneToMany } from 'typeorm';
 
 import { ChannelAware, SoftDeletable } from '../../common/types/common-types';
 import { LocaleString, Translatable, Translation } from '../../common/types/locale-types';
+import { HasCustomFields } from '../../config/custom-field/custom-field-types';
 import { Asset } from '../asset/asset.entity';
 import { VendureEntity } from '../base/base.entity';
 import { Channel } from '../channel/channel.entity';

+ 2 - 1
packages/core/src/entity/user/user.entity.ts

@@ -1,6 +1,7 @@
-import { DeepPartial, HasCustomFields } from '@vendure/common/lib/shared-types';
+import { DeepPartial } from '@vendure/common/lib/shared-types';
 import { Column, Entity, JoinTable, ManyToMany } from 'typeorm';
 
+import { HasCustomFields } from '../../config/custom-field/custom-field-types';
 import { VendureEntity } from '../base/base.entity';
 import { CustomUserFields } from '../custom-entity-fields';
 import { Role } from '../role/role.entity';

+ 6 - 1
packages/dev-server/dev-config.ts

@@ -28,7 +28,12 @@ export const devConfig: VendureConfig = {
     paymentOptions: {
         paymentMethodHandlers: [examplePaymentHandler],
     },
-    customFields: {},
+    customFields: {
+        Product: [
+            { type: 'string', name: 'foo' },
+            { type: 'datetime', name: 'expires' },
+        ],
+    },
     logger: new DefaultLogger({ level: LogLevel.Verbose }),
     importExportOptions: {
         importAssetsDir: path.join(__dirname, 'import-assets'),

Plik diff jest za duży
+ 0 - 0
schema-admin.json


Plik diff jest za duży
+ 0 - 0
schema-shop.json


+ 1 - 1
scripts/codegen/generate-graphql-types.ts

@@ -9,7 +9,7 @@ import { ADMIN_API_PATH, API_PORT, SHOP_API_PATH } from '../../packages/common/s
 import { downloadIntrospectionSchema } from './download-introspection-schema';
 
 const CLIENT_QUERY_FILES = path.join(__dirname, '../../admin-ui/src/app/data/definitions/**/*.ts');
-const E2E_ADMIN_QUERY_FILES = path.join(__dirname, '../../packages/core/e2e/**/!(plugin.e2e-spec|shop-definitions).ts');
+const E2E_ADMIN_QUERY_FILES = path.join(__dirname, '../../packages/core/e2e/**/!(plugin.e2e-spec|shop-definitions|custom-fields.e2e-spec).ts');
 const E2E_SHOP_QUERY_FILES = [
     path.join(__dirname, '../../packages/core/e2e/graphql/shop-definitions.ts'),
 ];

Niektóre pliki nie zostały wyświetlone z powodu dużej ilości zmienionych plików