Browse Source

docs: Add docs for entity duplication

Relates to #627
Michael Bromley 1 year ago
parent
commit
c58ce5baaa
34 changed files with 599 additions and 68 deletions
  1. 97 0
      docs/docs/reference/admin-ui-api/action-bar/action-bar-dropdown-menu-item.md
  2. 42 0
      docs/docs/reference/admin-ui-api/action-bar/add-action-bar-dropdown-menu-item.md
  3. 9 9
      docs/docs/reference/admin-ui-api/list-detail-views/detail-component-with-resolver.md
  4. 7 7
      docs/docs/reference/admin-ui-api/list-detail-views/typed-base-list-component.md
  5. 1 1
      docs/docs/reference/typescript-api/assets/asset-options.md
  6. 1 1
      docs/docs/reference/typescript-api/auth/auth-options.md
  7. 1 1
      docs/docs/reference/typescript-api/auth/cookie-options.md
  8. 1 1
      docs/docs/reference/typescript-api/auth/superadmin-credentials.md
  9. 1 1
      docs/docs/reference/typescript-api/common/job-state.md
  10. 1 1
      docs/docs/reference/typescript-api/common/language-code.md
  11. 1 1
      docs/docs/reference/typescript-api/common/permission.md
  12. 1 1
      docs/docs/reference/typescript-api/configuration/api-options.md
  13. 1 1
      docs/docs/reference/typescript-api/configuration/default-config.md
  14. 199 0
      docs/docs/reference/typescript-api/configuration/entity-duplicator.md
  15. 8 1
      docs/docs/reference/typescript-api/configuration/entity-options.md
  16. 1 1
      docs/docs/reference/typescript-api/configuration/runtime-vendure-config.md
  17. 1 1
      docs/docs/reference/typescript-api/configuration/system-options.md
  18. 1 1
      docs/docs/reference/typescript-api/configuration/vendure-config.md
  19. 2 1
      docs/docs/reference/typescript-api/custom-fields/index.md
  20. 4 4
      docs/docs/reference/typescript-api/fulfillment/fulfillment-handler.md
  21. 1 1
      docs/docs/reference/typescript-api/import-export/import-export-options.md
  22. 1 1
      docs/docs/reference/typescript-api/job-queue/job-queue-options.md
  23. 1 1
      docs/docs/reference/typescript-api/orders/order-options.md
  24. 1 1
      docs/docs/reference/typescript-api/payment/payment-options.md
  25. 1 1
      docs/docs/reference/typescript-api/products-stock/catalog-options.md
  26. 1 1
      docs/docs/reference/typescript-api/promotions/promotion-options.md
  27. 47 0
      docs/docs/reference/typescript-api/service-helpers/entity-duplicator-service.md
  28. 1 1
      docs/docs/reference/typescript-api/shipping/shipping-options.md
  29. 1 1
      docs/docs/reference/typescript-api/tax/tax-options.md
  30. 7 0
      packages/core/src/api/schema/admin-api/duplicate-entity.api.graphql
  31. 0 23
      packages/core/src/config/entity/entity-duplication-strategy.ts
  32. 131 1
      packages/core/src/config/entity/entity-duplicator.ts
  33. 9 2
      packages/core/src/config/vendure-config.ts
  34. 17 0
      packages/core/src/service/helpers/entity-duplicator/entity-duplicator.service.ts

+ 97 - 0
docs/docs/reference/admin-ui-api/action-bar/action-bar-dropdown-menu-item.md

@@ -0,0 +1,97 @@
+---
+title: "ActionBarDropdownMenuItem"
+isDefaultIndex: false
+generated: true
+---
+<!-- This file was generated from the Vendure source. Do not modify. Instead, re-run the "docs:build" script -->
+import MemberInfo from '@site/src/components/MemberInfo';
+import GenerationInfo from '@site/src/components/GenerationInfo';
+import MemberDescription from '@site/src/components/MemberDescription';
+
+
+## ActionBarDropdownMenuItem
+
+<GenerationInfo sourceFile="packages/admin-ui/src/lib/core/src/providers/nav-builder/nav-builder-types.ts" sourceLine="227" packageName="@vendure/admin-ui" since="2.2.0" />
+
+A dropdown menu item in the ActionBar area at the top of one of the list or detail views.
+
+```ts title="Signature"
+interface ActionBarDropdownMenuItem {
+    id: string;
+    label: string;
+    locationId: ActionBarLocationId;
+    hasDivider?: boolean;
+    buttonState?: (context: ActionBarContext) => Observable<ActionBarButtonState | undefined>;
+    onClick?: (event: MouseEvent, context: ActionBarContext) => void;
+    routerLink?: RouterLinkDefinition;
+    icon?: string;
+    requiresPermission?: string | string[];
+}
+```
+
+<div className="members-wrapper">
+
+### id
+
+<MemberInfo kind="property" type={`string`}   />
+
+A unique identifier for the item.
+### label
+
+<MemberInfo kind="property" type={`string`}   />
+
+The label to display for the item. This can also be a translation token,
+e.g. `invoice-plugin.print-invoice`.
+### locationId
+
+<MemberInfo kind="property" type={`<a href='/reference/admin-ui-api/action-bar/action-bar-location-id#actionbarlocationid'>ActionBarLocationId</a>`}   />
+
+The location in the UI where this menu item should be displayed.
+### hasDivider
+
+<MemberInfo kind="property" type={`boolean`}   />
+
+Whether to render a divider above this item.
+### buttonState
+
+<MemberInfo kind="property" type={`(context: <a href='/reference/admin-ui-api/action-bar/action-bar-context#actionbarcontext'>ActionBarContext</a>) =&#62; Observable&#60;ActionBarButtonState | undefined&#62;`}   />
+
+A function which returns an observable of the button state, allowing you to
+dynamically enable/disable or show/hide the button.
+### onClick
+
+<MemberInfo kind="property" type={`(event: MouseEvent, context: <a href='/reference/admin-ui-api/action-bar/action-bar-context#actionbarcontext'>ActionBarContext</a>) =&#62; void`}   />
+
+
+### routerLink
+
+<MemberInfo kind="property" type={`<a href='/reference/admin-ui-api/action-bar/router-link-definition#routerlinkdefinition'>RouterLinkDefinition</a>`}   />
+
+
+### icon
+
+<MemberInfo kind="property" type={`string`}   />
+
+An optional icon to display with the item. The icon
+should be a valid shape name from the [Clarity Icons](https://core.clarity.design/foundation/icons/shapes/)
+set.
+### requiresPermission
+
+<MemberInfo kind="property" type={`string | string[]`}   />
+
+Control the display of this item based on the user permissions. Note: if you attempt to pass a
+<a href='/reference/typescript-api/auth/permission-definition#permissiondefinition'>PermissionDefinition</a> object, you will get a compilation error. Instead, pass the plain
+string version. For example, if the permission is defined as:
+
+```ts
+export const MyPermission = new PermissionDefinition('ProductReview');
+```
+then the generated permission strings will be:
+
+- `CreateProductReview`
+- `ReadProductReview`
+- `UpdateProductReview`
+- `DeleteProductReview`
+
+
+</div>

+ 42 - 0
docs/docs/reference/admin-ui-api/action-bar/add-action-bar-dropdown-menu-item.md

@@ -0,0 +1,42 @@
+---
+title: "AddActionBarDropdownMenuItem"
+isDefaultIndex: false
+generated: true
+---
+<!-- This file was generated from the Vendure source. Do not modify. Instead, re-run the "docs:build" script -->
+import MemberInfo from '@site/src/components/MemberInfo';
+import GenerationInfo from '@site/src/components/GenerationInfo';
+import MemberDescription from '@site/src/components/MemberDescription';
+
+
+## addActionBarDropdownMenuItem
+
+<GenerationInfo sourceFile="packages/admin-ui/src/lib/core/src/extension/add-action-bar-dropdown-menu-item.ts" sourceLine="27" packageName="@vendure/admin-ui" since="2.2.0" />
+
+Adds a dropdown menu item to the ActionBar at the top right of each list or detail view. The locationId can
+be determined by pressing `ctrl + u` when running the Admin UI in dev mode.
+
+*Example*
+
+```ts title="providers.ts"
+import { addActionBarDropdownMenuItem } from '@vendure/admin-ui/core';
+
+export default [
+    addActionBarDropdownMenuItem({
+        id: 'print-invoice',
+        label: 'Print Invoice',
+        locationId: 'order-detail',
+        routerLink: ['/extensions/invoicing'],
+    }),
+];
+```
+
+```ts title="Signature"
+function addActionBarDropdownMenuItem(config: ActionBarDropdownMenuItem): Provider
+```
+Parameters
+
+### config
+
+<MemberInfo kind="parameter" type={`<a href='/reference/admin-ui-api/action-bar/action-bar-dropdown-menu-item#actionbardropdownmenuitem'>ActionBarDropdownMenuItem</a>`} />
+

+ 9 - 9
docs/docs/reference/admin-ui-api/list-detail-views/detail-component-with-resolver.md

@@ -13,8 +13,8 @@ import MemberDescription from '@site/src/components/MemberDescription';
 
 
 <GenerationInfo sourceFile="packages/admin-ui/src/lib/core/src/common/base-detail.component.ts" sourceLine="256" packageName="@vendure/admin-ui" />
 <GenerationInfo sourceFile="packages/admin-ui/src/lib/core/src/common/base-detail.component.ts" sourceLine="256" packageName="@vendure/admin-ui" />
 
 
-A helper function for creating tabs that point to a <a href='/reference/admin-ui-api/list-detail-views/typed-base-detail-component#typedbasedetailcomponent'>TypedBaseDetailComponent</a>. This takes
-care of the route resolver parts so that the detail component automatically has access to the
+A helper function for creating tabs that point to a <a href='/reference/admin-ui-api/list-detail-views/typed-base-detail-component#typedbasedetailcomponent'>TypedBaseDetailComponent</a>. This takes
+care of the route resolver parts so that the detail component automatically has access to the
 correct resolved detail data.
 correct resolved detail data.
 
 
 *Example*
 *Example*
@@ -40,17 +40,17 @@ export class ProductSpecsUiExtensionModule {}
 ```
 ```
 
 
 ```ts title="Signature"
 ```ts title="Signature"
-function detailComponentWithResolver<T extends TypedDocumentNode<any, { id: string }>, Field extends keyof ResultOf<T>, R extends Field>(config: {
-    component: Type<TypedBaseDetailComponent<T, Field>>;
-    query: T;
-    entityKey: R;
-    getBreadcrumbs?: (entity: ResultOf<T>[R]) => BreadcrumbValue;
-    variables?: T extends TypedDocumentNode<any, infer V> ? Omit<V, 'id'> : never;
+function detailComponentWithResolver<T extends TypedDocumentNode<any, { id: string }>, Field extends keyof ResultOf<T>, R extends Field>(config: {
+    component: Type<TypedBaseDetailComponent<T, Field>>;
+    query: T;
+    entityKey: R;
+    getBreadcrumbs?: (entity: ResultOf<T>[R]) => BreadcrumbValue;
+    variables?: T extends TypedDocumentNode<any, infer V> ? Omit<V, 'id'> : never;
 }): void
 }): void
 ```
 ```
 Parameters
 Parameters
 
 
 ### config
 ### config
 
 
-<MemberInfo kind="parameter" type={`{     component: Type&#60;<a href='/reference/admin-ui-api/list-detail-views/typed-base-detail-component#typedbasedetailcomponent'>TypedBaseDetailComponent</a>&#60;T, Field&#62;&#62;;     query: T;     entityKey: R;     getBreadcrumbs?: (entity: ResultOf&#60;T&#62;[R]) =&#62; BreadcrumbValue;     variables?: T extends TypedDocumentNode&#60;any, infer V&#62; ? Omit&#60;V, 'id'&#62; : never; }`} />
+<MemberInfo kind="parameter" type={`{
     component: Type&#60;<a href='/reference/admin-ui-api/list-detail-views/typed-base-detail-component#typedbasedetailcomponent'>TypedBaseDetailComponent</a>&#60;T, Field&#62;&#62;;
     query: T;
     entityKey: R;
     getBreadcrumbs?: (entity: ResultOf&#60;T&#62;[R]) =&#62; BreadcrumbValue;
     variables?: T extends TypedDocumentNode&#60;any, infer V&#62; ? Omit&#60;V, 'id'&#62; : never;
 }`} />
 
 

+ 7 - 7
docs/docs/reference/admin-ui-api/list-detail-views/typed-base-list-component.md

@@ -13,7 +13,7 @@ import MemberDescription from '@site/src/components/MemberDescription';
 
 
 <GenerationInfo sourceFile="packages/admin-ui/src/lib/core/src/common/base-list.component.ts" sourceLine="199" packageName="@vendure/admin-ui" />
 <GenerationInfo sourceFile="packages/admin-ui/src/lib/core/src/common/base-list.component.ts" sourceLine="199" packageName="@vendure/admin-ui" />
 
 
-A version of the <a href='/reference/admin-ui-api/list-detail-views/base-list-component#baselistcomponent'>BaseListComponent</a> which is designed to be used with a
+A version of the <a href='/reference/admin-ui-api/list-detail-views/base-list-component#baselistcomponent'>BaseListComponent</a> which is designed to be used with a
 [TypedDocumentNode](https://the-guild.dev/graphql/codegen/plugins/typescript/typed-document-node).
 [TypedDocumentNode](https://the-guild.dev/graphql/codegen/plugins/typescript/typed-document-node).
 
 
 ```ts title="Signature"
 ```ts title="Signature"
@@ -25,11 +25,11 @@ class TypedBaseListComponent<T extends TypedDocumentNode<any, Vars>, Field exten
     protected serverConfigService = inject(ServerConfigService);
     protected serverConfigService = inject(ServerConfigService);
     protected permissionsService = inject(PermissionsService);
     protected permissionsService = inject(PermissionsService);
     constructor()
     constructor()
-    configure(config: {
-        document: T;
-        getItems: (data: ResultOf<T>) => { items: Array<ItemOf<ResultOf<T>, Field>>; totalItems: number };
-        setVariables?: (skip: number, take: number) => VariablesOf<T>;
-        refreshListOnChanges?: Array<Observable<any>>;
+    configure(config: {
+        document: T;
+        getItems: (data: ResultOf<T>) => { items: Array<ItemOf<ResultOf<T>, Field>>; totalItems: number };
+        setVariables?: (skip: number, take: number) => VariablesOf<T>;
+        refreshListOnChanges?: Array<Observable<any>>;
     }) => ;
     }) => ;
     ngOnInit() => ;
     ngOnInit() => ;
     createFilterCollection() => DataTableFilterCollection<NonNullable<NonNullable<Vars['options']>['filter']>>;
     createFilterCollection() => DataTableFilterCollection<NonNullable<NonNullable<Vars['options']>['filter']>>;
@@ -84,7 +84,7 @@ class TypedBaseListComponent<T extends TypedDocumentNode<any, Vars>, Field exten
 
 
 ### configure
 ### configure
 
 
-<MemberInfo kind="method" type={`(config: {         document: T;         getItems: (data: ResultOf&#60;T&#62;) =&#62; { items: Array&#60;ItemOf&#60;ResultOf&#60;T&#62;, Field&#62;&#62;; totalItems: number };         setVariables?: (skip: number, take: number) =&#62; VariablesOf&#60;T&#62;;         refreshListOnChanges?: Array&#60;Observable&#60;any&#62;&#62;;     }) => `}   />
+<MemberInfo kind="method" type={`(config: {
         document: T;
         getItems: (data: ResultOf&#60;T&#62;) =&#62; { items: Array&#60;ItemOf&#60;ResultOf&#60;T&#62;, Field&#62;&#62;; totalItems: number };
         setVariables?: (skip: number, take: number) =&#62; VariablesOf&#60;T&#62;;
         refreshListOnChanges?: Array&#60;Observable&#60;any&#62;&#62;;
     }) => `}   />
 
 
 
 
 ### ngOnInit
 ### ngOnInit

+ 1 - 1
docs/docs/reference/typescript-api/assets/asset-options.md

@@ -11,7 +11,7 @@ import MemberDescription from '@site/src/components/MemberDescription';
 
 
 ## AssetOptions
 ## AssetOptions
 
 
-<GenerationInfo sourceFile="packages/core/src/config/vendure-config.ts" sourceLine="626" packageName="@vendure/core" />
+<GenerationInfo sourceFile="packages/core/src/config/vendure-config.ts" sourceLine="627" packageName="@vendure/core" />
 
 
 The AssetOptions define how assets (images and other files) are named and stored, and how preview images are generated.
 The AssetOptions define how assets (images and other files) are named and stored, and how preview images are generated.
 
 

+ 1 - 1
docs/docs/reference/typescript-api/auth/auth-options.md

@@ -11,7 +11,7 @@ import MemberDescription from '@site/src/components/MemberDescription';
 
 
 ## AuthOptions
 ## AuthOptions
 
 
-<GenerationInfo sourceFile="packages/core/src/config/vendure-config.ts" sourceLine="328" packageName="@vendure/core" />
+<GenerationInfo sourceFile="packages/core/src/config/vendure-config.ts" sourceLine="329" packageName="@vendure/core" />
 
 
 The AuthOptions define how authentication and authorization is managed.
 The AuthOptions define how authentication and authorization is managed.
 
 

+ 1 - 1
docs/docs/reference/typescript-api/auth/cookie-options.md

@@ -11,7 +11,7 @@ import MemberDescription from '@site/src/components/MemberDescription';
 
 
 ## CookieOptions
 ## CookieOptions
 
 
-<GenerationInfo sourceFile="packages/core/src/config/vendure-config.ts" sourceLine="223" packageName="@vendure/core" />
+<GenerationInfo sourceFile="packages/core/src/config/vendure-config.ts" sourceLine="224" packageName="@vendure/core" />
 
 
 Options for the handling of the cookies used to track sessions (only applicable if
 Options for the handling of the cookies used to track sessions (only applicable if
 `authOptions.tokenMethod` is set to `'cookie'`). These options are passed directly
 `authOptions.tokenMethod` is set to `'cookie'`). These options are passed directly

+ 1 - 1
docs/docs/reference/typescript-api/auth/superadmin-credentials.md

@@ -11,7 +11,7 @@ import MemberDescription from '@site/src/components/MemberDescription';
 
 
 ## SuperadminCredentials
 ## SuperadminCredentials
 
 
-<GenerationInfo sourceFile="packages/core/src/config/vendure-config.ts" sourceLine="802" packageName="@vendure/core" />
+<GenerationInfo sourceFile="packages/core/src/config/vendure-config.ts" sourceLine="803" packageName="@vendure/core" />
 
 
 These credentials will be used to create the Superadmin user & administrator
 These credentials will be used to create the Superadmin user & administrator
 when Vendure first bootstraps.
 when Vendure first bootstraps.

+ 1 - 1
docs/docs/reference/typescript-api/common/job-state.md

@@ -11,7 +11,7 @@ import MemberDescription from '@site/src/components/MemberDescription';
 
 
 ## JobState
 ## JobState
 
 
-<GenerationInfo sourceFile="packages/common/src/generated-types.ts" sourceLine="2131" packageName="@vendure/common" />
+<GenerationInfo sourceFile="packages/common/src/generated-types.ts" sourceLine="2161" packageName="@vendure/common" />
 
 
 The state of a Job in the JobQueue
 The state of a Job in the JobQueue
 
 

+ 1 - 1
docs/docs/reference/typescript-api/common/language-code.md

@@ -11,7 +11,7 @@ import MemberDescription from '@site/src/components/MemberDescription';
 
 
 ## LanguageCode
 ## LanguageCode
 
 
-<GenerationInfo sourceFile="packages/common/src/generated-types.ts" sourceLine="2149" packageName="@vendure/common" />
+<GenerationInfo sourceFile="packages/common/src/generated-types.ts" sourceLine="2179" packageName="@vendure/common" />
 
 
 Languages in the form of a ISO 639-1 language code with optional
 Languages in the form of a ISO 639-1 language code with optional
 region or script modifier (e.g. de_AT). The selection available is based
 region or script modifier (e.g. de_AT). The selection available is based

+ 1 - 1
docs/docs/reference/typescript-api/common/permission.md

@@ -11,7 +11,7 @@ import MemberDescription from '@site/src/components/MemberDescription';
 
 
 ## Permission
 ## Permission
 
 
-<GenerationInfo sourceFile="packages/common/src/generated-types.ts" sourceLine="4277" packageName="@vendure/common" />
+<GenerationInfo sourceFile="packages/common/src/generated-types.ts" sourceLine="4313" packageName="@vendure/common" />
 
 
 Permissions for administrators and customers. Used to control access to
 Permissions for administrators and customers. Used to control access to
 GraphQL resolvers via the <a href='/reference/typescript-api/request/allow-decorator#allow'>Allow</a> decorator.
 GraphQL resolvers via the <a href='/reference/typescript-api/request/allow-decorator#allow'>Allow</a> decorator.

+ 1 - 1
docs/docs/reference/typescript-api/configuration/api-options.md

@@ -11,7 +11,7 @@ import MemberDescription from '@site/src/components/MemberDescription';
 
 
 ## ApiOptions
 ## ApiOptions
 
 
-<GenerationInfo sourceFile="packages/core/src/config/vendure-config.ts" sourceLine="66" packageName="@vendure/core" />
+<GenerationInfo sourceFile="packages/core/src/config/vendure-config.ts" sourceLine="67" packageName="@vendure/core" />
 
 
 The ApiOptions define how the Vendure GraphQL APIs are exposed, as well as allowing the API layer
 The ApiOptions define how the Vendure GraphQL APIs are exposed, as well as allowing the API layer
 to be extended with middleware.
 to be extended with middleware.

+ 1 - 1
docs/docs/reference/typescript-api/configuration/default-config.md

@@ -11,7 +11,7 @@ import MemberDescription from '@site/src/components/MemberDescription';
 
 
 ## defaultConfig
 ## defaultConfig
 
 
-<GenerationInfo sourceFile="packages/core/src/config/default-config.ts" sourceLine="59" packageName="@vendure/core" />
+<GenerationInfo sourceFile="packages/core/src/config/default-config.ts" sourceLine="60" packageName="@vendure/core" />
 
 
 The default configuration settings which are used if not explicitly overridden in the bootstrap() call.
 The default configuration settings which are used if not explicitly overridden in the bootstrap() call.
 
 

+ 199 - 0
docs/docs/reference/typescript-api/configuration/entity-duplicator.md

@@ -0,0 +1,199 @@
+---
+title: "EntityDuplicator"
+isDefaultIndex: false
+generated: true
+---
+<!-- This file was generated from the Vendure source. Do not modify. Instead, re-run the "docs:build" script -->
+import MemberInfo from '@site/src/components/MemberInfo';
+import GenerationInfo from '@site/src/components/GenerationInfo';
+import MemberDescription from '@site/src/components/MemberDescription';
+
+
+## EntityDuplicator
+
+<GenerationInfo sourceFile="packages/core/src/config/entity/entity-duplicator.ts" sourceLine="158" packageName="@vendure/core" since="2.2.0" />
+
+An EntityDuplicator is used to define the logic for duplicating entities when the `duplicateEntity` mutation is called.
+This allows you to add support for duplication of both core and custom entities.
+
+*Example*
+
+```ts title=src/config/custom-collection-duplicator.ts
+import { Collection, LanguageCode, Permission
+  EntityDuplicator, TransactionalConnection, CollectionService } from '@vendure/core';
+
+let collectionService: CollectionService;
+let connection: TransactionalConnection;
+
+// This is just an example - we already ship with a built-in duplicator for Collections.
+const customCollectionDuplicator = new EntityDuplicator({
+    code: 'custom-collection-duplicator',
+    description: [{ languageCode: LanguageCode.en, value: 'Custom collection duplicator' }],
+    args: {
+        throwError: {
+            type: 'boolean',
+            defaultValue: false,
+        },
+    },
+    forEntities: ['Collection'],
+    requiresPermission: [Permission.UpdateCollection],
+    init(injector) {
+        collectionService = injector.get(CollectionService);
+        connection = injector.get(TransactionalConnection);
+    },
+    duplicate: async input => {
+        const { ctx, id, args } = input;
+
+        const original = await connection.getEntityOrThrow(ctx, Collection, id, {
+            relations: {
+                assets: true,
+                featuredAsset: true,
+            },
+        });
+        const newCollection = await collectionService.create(ctx, {
+            isPrivate: original.isPrivate,
+            customFields: original.customFields,
+            assetIds: original.assets.map(a => a.id),
+            featuredAssetId: original.featuredAsset?.id,
+            parentId: original.parentId,
+            filters: original.filters.map(f => ({
+                code: f.code,
+                arguments: f.args,
+            })),
+            inheritFilters: original.inheritFilters,
+            translations: original.translations.map(t => ({
+                languageCode: t.languageCode,
+                name: `${t.name} (copy)`,
+                slug: `${t.slug}-copy`,
+                description: t.description,
+                customFields: t.customFields,
+            })),
+        });
+
+        if (args.throwError) {
+            // If an error is thrown at any point during the duplication process, the entire
+            // transaction will get automatically rolled back, and the mutation will return
+            // an ErrorResponse containing the error message.
+            throw new Error('Dummy error');
+        }
+
+        return newCollection;
+    },
+});
+```
+
+The duplicator then gets passed to your VendureConfig object:
+
+```ts title=src/vendure-config.ts
+import { VendureConfig, defaultEntityDuplicators } from '@vendure/core';
+import { customCollectionDuplicator } from './config/custom-collection-duplicator';
+
+export const config: VendureConfig = {
+   // ...
+   entityOptions: {
+     entityDuplicators: [
+         ...defaultEntityDuplicators,
+         customCollectionDuplicator,
+     ],
+   },
+};
+```
+
+```ts title="Signature"
+class EntityDuplicator<T extends ConfigArgs = ConfigArgs> extends ConfigurableOperationDef<T> {
+    constructor(config: EntityDuplicatorConfig<T>)
+    duplicate(input: {
+        ctx: RequestContext;
+        entityName: string;
+        id: ID;
+        args: ConfigArg[];
+    }) => Promise<VendureEntity>;
+}
+```
+* Extends: <code><a href='/reference/typescript-api/configurable-operation-def/#configurableoperationdef'>ConfigurableOperationDef</a>&#60;T&#62;</code>
+
+
+
+<div className="members-wrapper">
+
+### constructor
+
+<MemberInfo kind="method" type={`(config: <a href='/reference/typescript-api/configuration/entity-duplicator#entityduplicatorconfig'>EntityDuplicatorConfig</a>&#60;T&#62;) => EntityDuplicator`}   />
+
+
+### duplicate
+
+<MemberInfo kind="method" type={`(input: {         ctx: <a href='/reference/typescript-api/request/request-context#requestcontext'>RequestContext</a>;         entityName: string;         id: <a href='/reference/typescript-api/common/id#id'>ID</a>;         args: ConfigArg[];     }) => Promise&#60;<a href='/reference/typescript-api/entities/vendure-entity#vendureentity'>VendureEntity</a>&#62;`}   />
+
+
+
+
+</div>
+
+
+## DuplicateEntityFn
+
+<GenerationInfo sourceFile="packages/core/src/config/entity/entity-duplicator.ts" sourceLine="21" packageName="@vendure/core" since="2.2.0" />
+
+A function which performs the duplication of an entity.
+
+```ts title="Signature"
+type DuplicateEntityFn<T extends ConfigArgs> = (input: {
+    ctx: RequestContext;
+    entityName: string;
+    id: ID;
+    args: ConfigArgValues<T>;
+}) => Promise<VendureEntity>
+```
+
+
+## EntityDuplicatorConfig
+
+<GenerationInfo sourceFile="packages/core/src/config/entity/entity-duplicator.ts" sourceLine="36" packageName="@vendure/core" since="2.2.0" />
+
+Configuration for creating a new EntityDuplicator.
+
+```ts title="Signature"
+interface EntityDuplicatorConfig<T extends ConfigArgs> extends ConfigurableOperationDefOptions<T> {
+    requiresPermission: Array<Permission | string> | Permission | string;
+    forEntities: string[];
+    duplicate: DuplicateEntityFn<T>;
+}
+```
+* Extends: <code><a href='/reference/typescript-api/configurable-operation-def/configurable-operation-def-options#configurableoperationdefoptions'>ConfigurableOperationDefOptions</a>&#60;T&#62;</code>
+
+
+
+<div className="members-wrapper">
+
+### requiresPermission
+
+<MemberInfo kind="property" type={`Array&#60;<a href='/reference/typescript-api/common/permission#permission'>Permission</a> | string&#62; | <a href='/reference/typescript-api/common/permission#permission'>Permission</a> | string`}   />
+
+The permissions required in order to execute this duplicator. If an array is passed,
+then the administrator must have at least one of the permissions in the array.
+### forEntities
+
+<MemberInfo kind="property" type={`string[]`}   />
+
+The entities for which this duplicator is able to duplicate.
+### duplicate
+
+<MemberInfo kind="property" type={`<a href='/reference/typescript-api/configuration/entity-duplicator#duplicateentityfn'>DuplicateEntityFn</a>&#60;T&#62;`}   />
+
+The function which performs the duplication.
+
+*Example*
+
+```ts
+duplicate: async input => {
+  const { ctx, id, args } = input;
+
+  // perform the duplication logic here
+
+  return newEntity;
+}
+```
+
+
+</div>

+ 8 - 1
docs/docs/reference/typescript-api/configuration/entity-options.md

@@ -11,13 +11,14 @@ import MemberDescription from '@site/src/components/MemberDescription';
 
 
 ## EntityOptions
 ## EntityOptions
 
 
-<GenerationInfo sourceFile="packages/core/src/config/vendure-config.ts" sourceLine="944" packageName="@vendure/core" since="1.3.0" />
+<GenerationInfo sourceFile="packages/core/src/config/vendure-config.ts" sourceLine="945" packageName="@vendure/core" since="1.3.0" />
 
 
 Options relating to the internal handling of entities.
 Options relating to the internal handling of entities.
 
 
 ```ts title="Signature"
 ```ts title="Signature"
 interface EntityOptions {
 interface EntityOptions {
     entityIdStrategy?: EntityIdStrategy<any>;
     entityIdStrategy?: EntityIdStrategy<any>;
+    entityDuplicators?: Array<EntityDuplicator<any>>;
     moneyStrategy?: MoneyStrategy;
     moneyStrategy?: MoneyStrategy;
     channelCacheTtl?: number;
     channelCacheTtl?: number;
     zoneCacheTtl?: number;
     zoneCacheTtl?: number;
@@ -43,6 +44,12 @@ on an existing Vendure database will lead to problems with broken foreign-key
 references. To change primary key types like this, you'll need to start with
 references. To change primary key types like this, you'll need to start with
 a fresh database.
 a fresh database.
 :::
 :::
+### entityDuplicators
+
+<MemberInfo kind="property" type={`Array&#60;<a href='/reference/typescript-api/configuration/entity-duplicator#entityduplicator'>EntityDuplicator</a>&#60;any&#62;&#62;`} default="defaultEntityDuplicators"  since="2.2.0"  />
+
+An array of <a href='/reference/typescript-api/configuration/entity-duplicator#entityduplicator'>EntityDuplicator</a> instances which are used to duplicate entities
+when using the `duplicateEntity` mutation.
 ### moneyStrategy
 ### moneyStrategy
 
 
 <MemberInfo kind="property" type={`<a href='/reference/typescript-api/money/money-strategy#moneystrategy'>MoneyStrategy</a>`} default="<a href='/reference/typescript-api/money/default-money-strategy#defaultmoneystrategy'>DefaultMoneyStrategy</a>"  since="2.0.0"  />
 <MemberInfo kind="property" type={`<a href='/reference/typescript-api/money/money-strategy#moneystrategy'>MoneyStrategy</a>`} default="<a href='/reference/typescript-api/money/default-money-strategy#defaultmoneystrategy'>DefaultMoneyStrategy</a>"  since="2.0.0"  />

+ 1 - 1
docs/docs/reference/typescript-api/configuration/runtime-vendure-config.md

@@ -11,7 +11,7 @@ import MemberDescription from '@site/src/components/MemberDescription';
 
 
 ## RuntimeVendureConfig
 ## RuntimeVendureConfig
 
 
-<GenerationInfo sourceFile="packages/core/src/config/vendure-config.ts" sourceLine="1182" packageName="@vendure/core" />
+<GenerationInfo sourceFile="packages/core/src/config/vendure-config.ts" sourceLine="1192" packageName="@vendure/core" />
 
 
 This interface represents the VendureConfig object available at run-time, i.e. the user-supplied
 This interface represents the VendureConfig object available at run-time, i.e. the user-supplied
 config values have been merged with the <a href='/reference/typescript-api/configuration/default-config#defaultconfig'>defaultConfig</a> values.
 config values have been merged with the <a href='/reference/typescript-api/configuration/default-config#defaultconfig'>defaultConfig</a> values.

+ 1 - 1
docs/docs/reference/typescript-api/configuration/system-options.md

@@ -11,7 +11,7 @@ import MemberDescription from '@site/src/components/MemberDescription';
 
 
 ## SystemOptions
 ## SystemOptions
 
 
-<GenerationInfo sourceFile="packages/core/src/config/vendure-config.ts" sourceLine="1024" packageName="@vendure/core" since="1.6.0" />
+<GenerationInfo sourceFile="packages/core/src/config/vendure-config.ts" sourceLine="1034" packageName="@vendure/core" since="1.6.0" />
 
 
 Options relating to system functions.
 Options relating to system functions.
 
 

+ 1 - 1
docs/docs/reference/typescript-api/configuration/vendure-config.md

@@ -11,7 +11,7 @@ import MemberDescription from '@site/src/components/MemberDescription';
 
 
 ## VendureConfig
 ## VendureConfig
 
 
-<GenerationInfo sourceFile="packages/core/src/config/vendure-config.ts" sourceLine="1052" packageName="@vendure/core" />
+<GenerationInfo sourceFile="packages/core/src/config/vendure-config.ts" sourceLine="1062" packageName="@vendure/core" />
 
 
 All possible configuration options are defined by the
 All possible configuration options are defined by the
 [`VendureConfig`](https://github.com/vendure-ecommerce/vendure/blob/master/server/src/config/vendure-config.ts) interface.
 [`VendureConfig`](https://github.com/vendure-ecommerce/vendure/blob/master/server/src/config/vendure-config.ts) interface.

+ 2 - 1
docs/docs/reference/typescript-api/custom-fields/index.md

@@ -13,7 +13,8 @@ import MemberDescription from '@site/src/components/MemberDescription';
 
 
 <GenerationInfo sourceFile="packages/core/src/config/custom-field/custom-field-types.ts" sourceLine="159" packageName="@vendure/core" />
 <GenerationInfo sourceFile="packages/core/src/config/custom-field/custom-field-types.ts" sourceLine="159" packageName="@vendure/core" />
 
 
-Most entities can have additional fields added to them by defining an array of <a href='/reference/typescript-api/custom-fields/custom-field-config#customfieldconfig'>CustomFieldConfig</a>objects on against the corresponding key.
+Most entities can have additional fields added to them by defining an array of <a href='/reference/typescript-api/custom-fields/custom-field-config#customfieldconfig'>CustomFieldConfig</a>
+objects on against the corresponding key.
 
 
 *Example*
 *Example*
 
 

+ 4 - 4
docs/docs/reference/typescript-api/fulfillment/fulfillment-handler.md

@@ -11,7 +11,7 @@ import MemberDescription from '@site/src/components/MemberDescription';
 
 
 ## FulfillmentHandler
 ## FulfillmentHandler
 
 
-<GenerationInfo sourceFile="packages/core/src/config/fulfillment/fulfillment-handler.ts" sourceLine="150" packageName="@vendure/core" />
+<GenerationInfo sourceFile="packages/core/src/config/fulfillment/fulfillment-handler.ts" sourceLine="149" packageName="@vendure/core" />
 
 
 A FulfillmentHandler is used when creating a new <a href='/reference/typescript-api/entities/fulfillment#fulfillment'>Fulfillment</a>. When the `addFulfillmentToOrder` mutation
 A FulfillmentHandler is used when creating a new <a href='/reference/typescript-api/entities/fulfillment#fulfillment'>Fulfillment</a>. When the `addFulfillmentToOrder` mutation
 is executed, the specified handler will be used and it's `createFulfillment` method is called. This method
 is executed, the specified handler will be used and it's `createFulfillment` method is called. This method
@@ -110,7 +110,7 @@ class FulfillmentHandler<T extends ConfigArgs = ConfigArgs> extends Configurable
 
 
 ## FulfillmentHandlerConfig
 ## FulfillmentHandlerConfig
 
 
-<GenerationInfo sourceFile="packages/core/src/config/fulfillment/fulfillment-handler.ts" sourceLine="49" packageName="@vendure/core" />
+<GenerationInfo sourceFile="packages/core/src/config/fulfillment/fulfillment-handler.ts" sourceLine="48" packageName="@vendure/core" />
 
 
 The configuration object used to instantiate a <a href='/reference/typescript-api/fulfillment/fulfillment-handler#fulfillmenthandler'>FulfillmentHandler</a>.
 The configuration object used to instantiate a <a href='/reference/typescript-api/fulfillment/fulfillment-handler#fulfillmenthandler'>FulfillmentHandler</a>.
 
 
@@ -152,7 +152,7 @@ shipping API.
 
 
 ## CreateFulfillmentFn
 ## CreateFulfillmentFn
 
 
-<GenerationInfo sourceFile="packages/core/src/config/fulfillment/fulfillment-handler.ts" sourceLine="34" packageName="@vendure/core" />
+<GenerationInfo sourceFile="packages/core/src/config/fulfillment/fulfillment-handler.ts" sourceLine="33" packageName="@vendure/core" />
 
 
 The function called when creating a new Fulfillment
 The function called when creating a new Fulfillment
 
 
@@ -168,7 +168,7 @@ type CreateFulfillmentFn<T extends ConfigArgs> = (
 
 
 ## CreateFulfillmentResult
 ## CreateFulfillmentResult
 
 
-<GenerationInfo sourceFile="packages/core/src/config/fulfillment/fulfillment-handler.ts" sourceLine="24" packageName="@vendure/core" />
+<GenerationInfo sourceFile="packages/core/src/config/fulfillment/fulfillment-handler.ts" sourceLine="23" packageName="@vendure/core" />
 
 
 
 
 
 

+ 1 - 1
docs/docs/reference/typescript-api/import-export/import-export-options.md

@@ -11,7 +11,7 @@ import MemberDescription from '@site/src/components/MemberDescription';
 
 
 ## ImportExportOptions
 ## ImportExportOptions
 
 
-<GenerationInfo sourceFile="packages/core/src/config/vendure-config.ts" sourceLine="879" packageName="@vendure/core" />
+<GenerationInfo sourceFile="packages/core/src/config/vendure-config.ts" sourceLine="880" packageName="@vendure/core" />
 
 
 Options related to importing & exporting data.
 Options related to importing & exporting data.
 
 

+ 1 - 1
docs/docs/reference/typescript-api/job-queue/job-queue-options.md

@@ -11,7 +11,7 @@ import MemberDescription from '@site/src/components/MemberDescription';
 
 
 ## JobQueueOptions
 ## JobQueueOptions
 
 
-<GenerationInfo sourceFile="packages/core/src/config/vendure-config.ts" sourceLine="903" packageName="@vendure/core" />
+<GenerationInfo sourceFile="packages/core/src/config/vendure-config.ts" sourceLine="904" packageName="@vendure/core" />
 
 
 Options related to the built-in job queue.
 Options related to the built-in job queue.
 
 

+ 1 - 1
docs/docs/reference/typescript-api/orders/order-options.md

@@ -11,7 +11,7 @@ import MemberDescription from '@site/src/components/MemberDescription';
 
 
 ## OrderOptions
 ## OrderOptions
 
 
-<GenerationInfo sourceFile="packages/core/src/config/vendure-config.ts" sourceLine="481" packageName="@vendure/core" />
+<GenerationInfo sourceFile="packages/core/src/config/vendure-config.ts" sourceLine="482" packageName="@vendure/core" />
 
 
 
 
 
 

+ 1 - 1
docs/docs/reference/typescript-api/payment/payment-options.md

@@ -11,7 +11,7 @@ import MemberDescription from '@site/src/components/MemberDescription';
 
 
 ## PaymentOptions
 ## PaymentOptions
 
 
-<GenerationInfo sourceFile="packages/core/src/config/vendure-config.ts" sourceLine="824" packageName="@vendure/core" />
+<GenerationInfo sourceFile="packages/core/src/config/vendure-config.ts" sourceLine="825" packageName="@vendure/core" />
 
 
 Defines payment-related options in the <a href='/reference/typescript-api/configuration/vendure-config#vendureconfig'>VendureConfig</a>.
 Defines payment-related options in the <a href='/reference/typescript-api/configuration/vendure-config#vendureconfig'>VendureConfig</a>.
 
 

+ 1 - 1
docs/docs/reference/typescript-api/products-stock/catalog-options.md

@@ -11,7 +11,7 @@ import MemberDescription from '@site/src/components/MemberDescription';
 
 
 ## CatalogOptions
 ## CatalogOptions
 
 
-<GenerationInfo sourceFile="packages/core/src/config/vendure-config.ts" sourceLine="673" packageName="@vendure/core" />
+<GenerationInfo sourceFile="packages/core/src/config/vendure-config.ts" sourceLine="674" packageName="@vendure/core" />
 
 
 Options related to products and collections.
 Options related to products and collections.
 
 

+ 1 - 1
docs/docs/reference/typescript-api/promotions/promotion-options.md

@@ -11,7 +11,7 @@ import MemberDescription from '@site/src/components/MemberDescription';
 
 
 ## PromotionOptions
 ## PromotionOptions
 
 
-<GenerationInfo sourceFile="packages/core/src/config/vendure-config.ts" sourceLine="735" packageName="@vendure/core" />
+<GenerationInfo sourceFile="packages/core/src/config/vendure-config.ts" sourceLine="736" packageName="@vendure/core" />
 
 
 
 
 
 

+ 47 - 0
docs/docs/reference/typescript-api/service-helpers/entity-duplicator-service.md

@@ -0,0 +1,47 @@
+---
+title: "EntityDuplicatorService"
+isDefaultIndex: false
+generated: true
+---
+<!-- This file was generated from the Vendure source. Do not modify. Instead, re-run the "docs:build" script -->
+import MemberInfo from '@site/src/components/MemberInfo';
+import GenerationInfo from '@site/src/components/GenerationInfo';
+import MemberDescription from '@site/src/components/MemberDescription';
+
+
+## EntityDuplicatorService
+
+<GenerationInfo sourceFile="packages/core/src/service/helpers/entity-duplicator/entity-duplicator.service.ts" sourceLine="23" packageName="@vendure/core" since="2.2.0" />
+
+This service is used to duplicate entities using one of the configured
+<a href='/reference/typescript-api/configuration/entity-duplicator#entityduplicator'>EntityDuplicator</a> functions.
+
+```ts title="Signature"
+class EntityDuplicatorService {
+    constructor(configService: ConfigService, configArgService: ConfigArgService, connection: TransactionalConnection)
+    getEntityDuplicators(ctx: RequestContext) => EntityDuplicatorDefinition[];
+    duplicateEntity(ctx: RequestContext, input: DuplicateEntityInput) => Promise<DuplicateEntityResult>;
+}
+```
+
+<div className="members-wrapper">
+
+### constructor
+
+<MemberInfo kind="method" type={`(configService: ConfigService, configArgService: ConfigArgService, connection: <a href='/reference/typescript-api/data-access/transactional-connection#transactionalconnection'>TransactionalConnection</a>) => EntityDuplicatorService`}   />
+
+
+### getEntityDuplicators
+
+<MemberInfo kind="method" type={`(ctx: <a href='/reference/typescript-api/request/request-context#requestcontext'>RequestContext</a>) => EntityDuplicatorDefinition[]`}   />
+
+Returns all configured <a href='/reference/typescript-api/configuration/entity-duplicator#entityduplicator'>EntityDuplicator</a> definitions.
+### duplicateEntity
+
+<MemberInfo kind="method" type={`(ctx: <a href='/reference/typescript-api/request/request-context#requestcontext'>RequestContext</a>, input: DuplicateEntityInput) => Promise&#60;DuplicateEntityResult&#62;`}   />
+
+Duplicates an entity using the specified <a href='/reference/typescript-api/configuration/entity-duplicator#entityduplicator'>EntityDuplicator</a>. The duplication is performed
+within a transaction, so if an error occurs, the transaction will be rolled back.
+
+
+</div>

+ 1 - 1
docs/docs/reference/typescript-api/shipping/shipping-options.md

@@ -11,7 +11,7 @@ import MemberDescription from '@site/src/components/MemberDescription';
 
 
 ## ShippingOptions
 ## ShippingOptions
 
 
-<GenerationInfo sourceFile="packages/core/src/config/vendure-config.ts" sourceLine="751" packageName="@vendure/core" />
+<GenerationInfo sourceFile="packages/core/src/config/vendure-config.ts" sourceLine="752" packageName="@vendure/core" />
 
 
 
 
 
 

+ 1 - 1
docs/docs/reference/typescript-api/tax/tax-options.md

@@ -11,7 +11,7 @@ import MemberDescription from '@site/src/components/MemberDescription';
 
 
 ## TaxOptions
 ## TaxOptions
 
 
-<GenerationInfo sourceFile="packages/core/src/config/vendure-config.ts" sourceLine="856" packageName="@vendure/core" />
+<GenerationInfo sourceFile="packages/core/src/config/vendure-config.ts" sourceLine="857" packageName="@vendure/core" />
 
 
 
 
 
 

+ 7 - 0
packages/core/src/api/schema/admin-api/duplicate-entity.api.graphql

@@ -1,8 +1,15 @@
 extend type Query {
 extend type Query {
+    """
+    Returns all configured EntityDuplicators.
+    """
     entityDuplicators: [EntityDuplicatorDefinition!]!
     entityDuplicators: [EntityDuplicatorDefinition!]!
 }
 }
 
 
 extend type Mutation {
 extend type Mutation {
+    """
+    Duplicate an existing entity using a specific EntityDuplicator.
+    Since v2.2.0.
+    """
     duplicateEntity(input: DuplicateEntityInput!): DuplicateEntityResult!
     duplicateEntity(input: DuplicateEntityInput!): DuplicateEntityResult!
 }
 }
 
 

+ 0 - 23
packages/core/src/config/entity/entity-duplication-strategy.ts

@@ -1,23 +0,0 @@
-import { Permission } from '@vendure/common/lib/generated-types';
-import { ID } from '@vendure/common/lib/shared-types';
-
-import { RequestContext } from '../../api/index';
-import { ConfigArgs, ConfigArgValues } from '../../common/configurable-operation';
-import { InjectableStrategy } from '../../common/index';
-import { VendureEntity } from '../../entity/index';
-
-export type InputArgValues<Strategy extends EntityDuplicationStrategy> = ConfigArgValues<
-    ReturnType<Strategy['defineInputArgs']>
->;
-
-export interface EntityDuplicationStrategy extends InjectableStrategy {
-    readonly requiresPermission: Array<Permission | string> | Permission | string;
-    readonly canDuplicateEntities: string[];
-    defineInputArgs(): ConfigArgs;
-    duplicate(input: {
-        ctx: RequestContext;
-        entityName: string;
-        id: ID;
-        args: InputArgValues<EntityDuplicationStrategy>;
-    }): Promise<VendureEntity>;
-}

+ 131 - 1
packages/core/src/config/entity/entity-duplicator.ts

@@ -10,6 +10,14 @@ import {
 } from '../../common/configurable-operation';
 } from '../../common/configurable-operation';
 import { VendureEntity } from '../../entity/index';
 import { VendureEntity } from '../../entity/index';
 
 
+/**
+ * @description
+ * A function which performs the duplication of an entity.
+ *
+ * @docsPage EntityDuplicator
+ * @docsCategory configuration
+ * @since 2.2.0
+ */
 export type DuplicateEntityFn<T extends ConfigArgs> = (input: {
 export type DuplicateEntityFn<T extends ConfigArgs> = (input: {
     ctx: RequestContext;
     ctx: RequestContext;
     entityName: string;
     entityName: string;
@@ -19,28 +27,150 @@ export type DuplicateEntityFn<T extends ConfigArgs> = (input: {
 
 
 /**
 /**
  * @description
  * @description
+ * Configuration for creating a new EntityDuplicator.
  *
  *
- *
+ * @docsPage EntityDuplicator
+ * @docsCategory configuration
+ * @since 2.2.0
  */
  */
 export interface EntityDuplicatorConfig<T extends ConfigArgs> extends ConfigurableOperationDefOptions<T> {
 export interface EntityDuplicatorConfig<T extends ConfigArgs> extends ConfigurableOperationDefOptions<T> {
+    /**
+     * @description
+     * The permissions required in order to execute this duplicator. If an array is passed,
+     * then the administrator must have at least one of the permissions in the array.
+     */
     requiresPermission: Array<Permission | string> | Permission | string;
     requiresPermission: Array<Permission | string> | Permission | string;
+    /**
+     * @description
+     * The entities for which this duplicator is able to duplicate.
+     */
     forEntities: string[];
     forEntities: string[];
+    /**
+     * @description
+     * The function which performs the duplication.
+     *
+     * @example
+     * ```ts
+     * duplicate: async input => {
+     *   const { ctx, id, args } = input;
+     *
+     *   // perform the duplication logic here
+     *
+     *   return newEntity;
+     * }
+     * ```
+     */
     duplicate: DuplicateEntityFn<T>;
     duplicate: DuplicateEntityFn<T>;
 }
 }
 
 
+/**
+ * @description
+ * An EntityDuplicator is used to define the logic for duplicating entities when the `duplicateEntity` mutation is called.
+ * This allows you to add support for duplication of both core and custom entities.
+ *
+ * @example
+ * ```ts title=src/config/custom-collection-duplicator.ts
+ * import { Collection, LanguageCode, Permission
+ *   EntityDuplicator, TransactionalConnection, CollectionService } from '\@vendure/core';
+ *
+ * let collectionService: CollectionService;
+ * let connection: TransactionalConnection;
+ *
+ * // This is just an example - we already ship with a built-in duplicator for Collections.
+ * const customCollectionDuplicator = new EntityDuplicator({
+ *     code: 'custom-collection-duplicator',
+ *     description: [{ languageCode: LanguageCode.en, value: 'Custom collection duplicator' }],
+ *     args: {
+ *         throwError: {
+ *             type: 'boolean',
+ *             defaultValue: false,
+ *         },
+ *     },
+ *     forEntities: ['Collection'],
+ *     requiresPermission: [Permission.UpdateCollection],
+ *     init(injector) {
+ *         collectionService = injector.get(CollectionService);
+ *         connection = injector.get(TransactionalConnection);
+ *     },
+ *     duplicate: async input => {
+ *         const { ctx, id, args } = input;
+ *
+ *         const original = await connection.getEntityOrThrow(ctx, Collection, id, {
+ *             relations: {
+ *                 assets: true,
+ *                 featuredAsset: true,
+ *             },
+ *         });
+ *         const newCollection = await collectionService.create(ctx, {
+ *             isPrivate: original.isPrivate,
+ *             customFields: original.customFields,
+ *             assetIds: original.assets.map(a => a.id),
+ *             featuredAssetId: original.featuredAsset?.id,
+ *             parentId: original.parentId,
+ *             filters: original.filters.map(f => ({
+ *                 code: f.code,
+ *                 arguments: f.args,
+ *             })),
+ *             inheritFilters: original.inheritFilters,
+ *             translations: original.translations.map(t => ({
+ *                 languageCode: t.languageCode,
+ *                 name: `${t.name} (copy)`,
+ *                 slug: `${t.slug}-copy`,
+ *                 description: t.description,
+ *                 customFields: t.customFields,
+ *             })),
+ *         });
+ *
+ *         if (args.throwError) {
+ *             // If an error is thrown at any point during the duplication process, the entire
+ *             // transaction will get automatically rolled back, and the mutation will return
+ *             // an ErrorResponse containing the error message.
+ *             throw new Error('Dummy error');
+ *         }
+ *
+ *         return newCollection;
+ *     },
+ * });
+ * ```
+ *
+ * The duplicator then gets passed to your VendureConfig object:
+ *
+ * ```ts title=src/vendure-config.ts
+ * import { VendureConfig, defaultEntityDuplicators } from '\@vendure/core';
+ * import { customCollectionDuplicator } from './config/custom-collection-duplicator';
+ *
+ * export const config: VendureConfig = {
+ *    // ...
+ *    entityOptions: {
+ *      entityDuplicators: [
+ *          ...defaultEntityDuplicators,
+ *          customCollectionDuplicator,
+ *      ],
+ *    },
+ * };
+ * ```
+ *
+ * @docsPage EntityDuplicator
+ * @docsWeight 0
+ * @docsCategory configuration
+ * @since 2.2.0
+ */
 export class EntityDuplicator<T extends ConfigArgs = ConfigArgs> extends ConfigurableOperationDef<T> {
 export class EntityDuplicator<T extends ConfigArgs = ConfigArgs> extends ConfigurableOperationDef<T> {
     private _forEntities: string[];
     private _forEntities: string[];
     private _requiresPermission: Array<Permission | string> | Permission | string;
     private _requiresPermission: Array<Permission | string> | Permission | string;
     private duplicateFn: DuplicateEntityFn<T>;
     private duplicateFn: DuplicateEntityFn<T>;
 
 
+    /** @internal */
     canDuplicate(entityName: string): boolean {
     canDuplicate(entityName: string): boolean {
         return this._forEntities.includes(entityName);
         return this._forEntities.includes(entityName);
     }
     }
 
 
+    /** @internal */
     get forEntities() {
     get forEntities() {
         return this._forEntities;
         return this._forEntities;
     }
     }
 
 
+    /** @internal */
     get requiresPermission(): Permission[] {
     get requiresPermission(): Permission[] {
         return (Array.isArray(this._requiresPermission)
         return (Array.isArray(this._requiresPermission)
             ? this._requiresPermission
             ? this._requiresPermission

+ 9 - 2
packages/core/src/config/vendure-config.ts

@@ -24,7 +24,6 @@ import { ProductVariantPriceUpdateStrategy } from './catalog/product-variant-pri
 import { StockDisplayStrategy } from './catalog/stock-display-strategy';
 import { StockDisplayStrategy } from './catalog/stock-display-strategy';
 import { StockLocationStrategy } from './catalog/stock-location-strategy';
 import { StockLocationStrategy } from './catalog/stock-location-strategy';
 import { CustomFields } from './custom-field/custom-field-types';
 import { CustomFields } from './custom-field/custom-field-types';
-import { EntityDuplicationStrategy } from './entity/entity-duplication-strategy';
 import { EntityDuplicator } from './entity/entity-duplicator';
 import { EntityDuplicator } from './entity/entity-duplicator';
 import { EntityIdStrategy } from './entity/entity-id-strategy';
 import { EntityIdStrategy } from './entity/entity-id-strategy';
 import { MoneyStrategy } from './entity/money-strategy';
 import { MoneyStrategy } from './entity/money-strategy';
@@ -962,7 +961,15 @@ export interface EntityOptions {
      * @default AutoIncrementIdStrategy
      * @default AutoIncrementIdStrategy
      */
      */
     entityIdStrategy?: EntityIdStrategy<any>;
     entityIdStrategy?: EntityIdStrategy<any>;
-    entityDuplicators?: EntityDuplicator<any>[];
+    /**
+     * @description
+     * An array of {@link EntityDuplicator} instances which are used to duplicate entities
+     * when using the `duplicateEntity` mutation.
+     *
+     * @since 2.2.0
+     * @default defaultEntityDuplicators
+     */
+    entityDuplicators?: Array<EntityDuplicator<any>>;
     /**
     /**
      * @description
      * @description
      * Defines the strategy used to store and round monetary values.
      * Defines the strategy used to store and round monetary values.

+ 17 - 0
packages/core/src/service/helpers/entity-duplicator/entity-duplicator.service.ts

@@ -12,6 +12,14 @@ import { ConfigService, Logger } from '../../../config/index';
 import { TransactionalConnection } from '../../../connection/index';
 import { TransactionalConnection } from '../../../connection/index';
 import { ConfigArgService } from '../config-arg/config-arg.service';
 import { ConfigArgService } from '../config-arg/config-arg.service';
 
 
+/**
+ * @description
+ * This service is used to duplicate entities using one of the configured
+ * {@link EntityDuplicator} functions.
+ *
+ * @docsCategory service-helpers
+ * @since 2.2.0
+ */
 @Injectable()
 @Injectable()
 export class EntityDuplicatorService {
 export class EntityDuplicatorService {
     constructor(
     constructor(
@@ -20,6 +28,10 @@ export class EntityDuplicatorService {
         private connection: TransactionalConnection,
         private connection: TransactionalConnection,
     ) {}
     ) {}
 
 
+    /**
+     * @description
+     * Returns all configured {@link EntityDuplicator} definitions.
+     */
     getEntityDuplicators(ctx: RequestContext): EntityDuplicatorDefinition[] {
     getEntityDuplicators(ctx: RequestContext): EntityDuplicatorDefinition[] {
         return this.configArgService.getDefinitions('EntityDuplicator').map(x => ({
         return this.configArgService.getDefinitions('EntityDuplicator').map(x => ({
             ...x.toGraphQlType(ctx),
             ...x.toGraphQlType(ctx),
@@ -29,6 +41,11 @@ export class EntityDuplicatorService {
         }));
         }));
     }
     }
 
 
+    /**
+     * @description
+     * Duplicates an entity using the specified {@link EntityDuplicator}. The duplication is performed
+     * within a transaction, so if an error occurs, the transaction will be rolled back.
+     */
     async duplicateEntity(ctx: RequestContext, input: DuplicateEntityInput): Promise<DuplicateEntityResult> {
     async duplicateEntity(ctx: RequestContext, input: DuplicateEntityInput): Promise<DuplicateEntityResult> {
         const duplicator = this.configService.entityOptions.entityDuplicators.find(
         const duplicator = this.configService.entityOptions.entityDuplicators.find(
             s => s.forEntities.includes(input.entityName) && s.code === input.duplicatorInput.code,
             s => s.forEntities.includes(input.entityName) && s.code === input.duplicatorInput.code,