Răsfoiți Sursa

Merge branch 'master' into minor

Michael Bromley 2 ani în urmă
părinte
comite
f83455cbf3
100 a modificat fișierele cu 1845 adăugiri și 242 ștergeri
  1. 3 3
      .github/pull_request_template.md
  2. 17 0
      CHANGELOG.md
  3. 0 3
      README.md
  4. 1 1
      docs/docs/guides/developer-guide/custom-fields/index.md
  5. 27 0
      docs/docs/guides/developer-guide/error-handling/index.mdx
  6. 2 3
      docs/docs/guides/developer-guide/plugins/index.mdx
  7. 6 4
      docs/docs/guides/extending-the-admin-ui/creating-list-views/index.md
  8. 1 1
      docs/docs/reference/admin-ui-api/action-bar/action-bar-context.md
  9. 12 1
      docs/docs/reference/admin-ui-api/action-bar/action-bar-item.md
  10. 1 1
      docs/docs/reference/admin-ui-api/action-bar/router-link-definition.md
  11. 4 2
      docs/docs/reference/admin-ui-api/nav-menu/add-nav-menu-item.md
  12. 3 1
      docs/docs/reference/admin-ui-api/nav-menu/add-nav-menu-section.md
  13. 12 1
      docs/docs/reference/admin-ui-api/nav-menu/nav-menu-section.md
  14. 1 1
      docs/docs/reference/admin-ui-api/react-extensions/register-react-data-table-component.md
  15. 3 3
      docs/docs/reference/core-plugins/job-queue-plugin/bull-mqjob-queue-plugin.md
  16. 131 0
      docs/docs/reference/core-plugins/sentry-plugin/index.md
  17. 48 0
      docs/docs/reference/core-plugins/sentry-plugin/sentry-plugin-options.md
  18. 281 0
      docs/docs/reference/core-plugins/stellate-plugin/index.md
  19. 95 0
      docs/docs/reference/core-plugins/stellate-plugin/purge-rule.md
  20. 69 0
      docs/docs/reference/core-plugins/stellate-plugin/stellate-plugin-options.md
  21. 75 0
      docs/docs/reference/core-plugins/stellate-plugin/stellate-service.md
  22. 1 3
      docs/docs/reference/graphql-api/admin/enums.md
  23. 13 3
      docs/docs/reference/graphql-api/admin/input-types.md
  24. 1 3
      docs/docs/reference/graphql-api/admin/mutations.md
  25. 14 4
      docs/docs/reference/graphql-api/admin/object-types.md
  26. 1 3
      docs/docs/reference/graphql-api/admin/queries.md
  27. 1 3
      docs/docs/reference/graphql-api/shop/enums.md
  28. 82 3
      docs/docs/reference/graphql-api/shop/input-types.md
  29. 1 3
      docs/docs/reference/graphql-api/shop/mutations.md
  30. 26 3
      docs/docs/reference/graphql-api/shop/object-types.md
  31. 1 3
      docs/docs/reference/graphql-api/shop/queries.md
  32. 1 1
      docs/docs/reference/typescript-api/common/permission.md
  33. 9 9
      docs/docs/reference/typescript-api/configurable-operation-def/default-form-config-hash.md
  34. 1 1
      docs/docs/reference/typescript-api/custom-fields/custom-field-type.md
  35. 34 0
      docs/docs/reference/typescript-api/errors/error-result-union.md
  36. 44 0
      docs/docs/reference/typescript-api/errors/is-graph-ql-error-result.md
  37. 1 2
      docs/docs/reference/typescript-api/events/vendure-entity-event.md
  38. 1 1
      docs/docs/reference/typescript-api/orders/default-guest-checkout-strategy.md
  39. 1 1
      docs/docs/reference/typescript-api/orders/guest-checkout-strategy.md
  40. 1 1
      docs/docs/reference/typescript-api/orders/order-process.md
  41. 1 1
      docs/docs/reference/typescript-api/request/request-context-service.md
  42. 3 3
      docs/docs/reference/typescript-api/services/channel-service.md
  43. 1 1
      docs/docs/reference/typescript-api/services/collection-service.md
  44. 29 29
      docs/docs/reference/typescript-api/services/customer-service.md
  45. 1 1
      docs/docs/reference/typescript-api/services/facet-service.md
  46. 15 15
      docs/docs/reference/typescript-api/services/order-service.md
  47. 4 4
      docs/docs/reference/typescript-api/services/product-service.md
  48. 2 2
      docs/docs/reference/typescript-api/services/promotion-service.md
  49. 1 1
      docs/docs/reference/typescript-api/services/user-service.md
  50. 12 0
      docs/sidebars.js
  51. 1 1
      lerna.json
  52. 1 2
      package.json
  53. 3 3
      packages/admin-ui-plugin/package.json
  54. 1 1
      packages/admin-ui/package-lock.json
  55. 2 2
      packages/admin-ui/package.json
  56. 1 1
      packages/admin-ui/src/lib/core/src/common/version.ts
  57. 36 34
      packages/admin-ui/src/lib/core/src/shared/components/asset-preview/asset-preview.component.html
  58. 1 1
      packages/admin-ui/src/lib/core/src/shared/components/asset-preview/asset-preview.component.scss
  59. 3 3
      packages/asset-server-plugin/package.json
  60. 2 2
      packages/cli/package.json
  61. 1 1
      packages/common/package.json
  62. 1 1
      packages/common/src/shared-types.ts
  63. 42 3
      packages/core/e2e/fixtures/test-plugins/list-query-plugin.ts
  64. 42 0
      packages/core/e2e/list-query-builder.e2e-spec.ts
  65. 2 2
      packages/core/package.json
  66. 23 1
      packages/core/src/common/error/error-result.ts
  67. 5 2
      packages/core/src/config/order/default-order-process.ts
  68. 69 0
      packages/core/src/entity/base/base.entity.spec.ts
  69. 8 2
      packages/core/src/entity/base/base.entity.ts
  70. 1 0
      packages/core/src/event-bus/index.ts
  71. 0 1
      packages/core/src/i18n/messages/en.json
  72. 10 3
      packages/core/src/service/helpers/list-query-builder/list-query-builder.ts
  73. 4 2
      packages/core/src/service/services/order.service.ts
  74. 0 3
      packages/core/src/service/services/stock-movement.service.ts
  75. 3 3
      packages/create/package.json
  76. 9 9
      packages/dev-server/package.json
  77. 3 3
      packages/elasticsearch-plugin/package.json
  78. 1 1
      packages/elasticsearch-plugin/src/types.ts
  79. 3 3
      packages/email-plugin/package.json
  80. 3 3
      packages/harden-plugin/package.json
  81. 3 3
      packages/job-queue-plugin/package.json
  82. 3 3
      packages/job-queue-plugin/src/bullmq/plugin.ts
  83. 4 4
      packages/payments-plugin/package.json
  84. 16 4
      packages/payments-plugin/src/mollie/mollie.controller.ts
  85. 10 6
      packages/payments-plugin/src/mollie/mollie.service.ts
  86. 3 0
      packages/sentry-plugin/.gitignore
  87. 7 0
      packages/sentry-plugin/README.md
  88. 4 0
      packages/sentry-plugin/index.ts
  89. 28 0
      packages/sentry-plugin/package.json
  90. 32 0
      packages/sentry-plugin/src/api/admin-test.resolver.ts
  91. 14 0
      packages/sentry-plugin/src/api/api-extensions.ts
  92. 11 0
      packages/sentry-plugin/src/api/error-test.service.ts
  93. 3 0
      packages/sentry-plugin/src/constants.ts
  94. 58 0
      packages/sentry-plugin/src/sentry-apollo-plugin.ts
  95. 25 0
      packages/sentry-plugin/src/sentry-context.middleware.ts
  96. 146 0
      packages/sentry-plugin/src/sentry-plugin.ts
  97. 27 0
      packages/sentry-plugin/src/sentry.filter.ts
  98. 40 0
      packages/sentry-plugin/src/sentry.service.ts
  99. 26 0
      packages/sentry-plugin/src/types.ts
  100. 9 0
      packages/sentry-plugin/tsconfig.build.json

+ 3 - 3
.github/pull_request_template.md

@@ -12,11 +12,11 @@ You can add screenshots here if applicable.
 
 
 # Checklist
 # Checklist
 
 
-:pushpin: Always:
+📌 Always:
 - [ ] I have set a clear title
 - [ ] I have set a clear title
 - [ ] My PR is small and contains a single feature
 - [ ] My PR is small and contains a single feature
 - [ ] I have [checked my own PR](## "Fix typo's and remove unused or commented out code")
 - [ ] I have [checked my own PR](## "Fix typo's and remove unused or commented out code")
 
 
-:zap: Most of the time:
+👍 Most of the time:
 - [ ] I have added or updated test cases
 - [ ] I have added or updated test cases
-- [ ] I have updated the README if needed
+- [ ] I have updated the README if needed

+ 17 - 0
CHANGELOG.md

@@ -1,3 +1,20 @@
+## <small>2.1.5 (2023-12-14)</small>
+
+
+#### Fixes
+
+* **admin-ui** Fix display of asset detail focal point buttons ([1b58aa7](https://github.com/vendure-ecommerce/vendure/commit/1b58aa7))
+* **core** Export VendureEntityEvent abstract class from index (#2556) ([c46cf74](https://github.com/vendure-ecommerce/vendure/commit/c46cf74)), closes [#2556](https://github.com/vendure-ecommerce/vendure/issues/2556)
+* **core** Fix bug when instantiating entity from object with getter ([d09452e](https://github.com/vendure-ecommerce/vendure/commit/d09452e)), closes [#2574](https://github.com/vendure-ecommerce/vendure/issues/2574)
+* **core** Fix loading multiple customField relations (#2566) ([99e04d1](https://github.com/vendure-ecommerce/vendure/commit/99e04d1)), closes [#2566](https://github.com/vendure-ecommerce/vendure/issues/2566) [#2555](https://github.com/vendure-ecommerce/vendure/issues/2555)
+* **core** OrderLineEvent includes ID of deleted OrderLine ([ee04032](https://github.com/vendure-ecommerce/vendure/commit/ee04032)), closes [#2574](https://github.com/vendure-ecommerce/vendure/issues/2574)
+* **core** Remove redundant constraint when creating allocations ([52c0841](https://github.com/vendure-ecommerce/vendure/commit/52c0841)), closes [#2563](https://github.com/vendure-ecommerce/vendure/issues/2563)
+* **core** Send the correct amount to `refundOrder` (#2559) ([b5a265f](https://github.com/vendure-ecommerce/vendure/commit/b5a265f)), closes [#2559](https://github.com/vendure-ecommerce/vendure/issues/2559)
+* **elasticsearch-plugin** Fix type to allow the promise on custom mapping definition (#2562) ([8e9ee07](https://github.com/vendure-ecommerce/vendure/commit/8e9ee07)), closes [#2562](https://github.com/vendure-ecommerce/vendure/issues/2562)
+* **payments-plugin** Fix Mollie channel awareness (#2575) ([cc4826d](https://github.com/vendure-ecommerce/vendure/commit/cc4826d)), closes [#2575](https://github.com/vendure-ecommerce/vendure/issues/2575)
+* **payments-plugin** Mollie - ignore completed state to prevent unneccesary error throwing (#2569) ([ed80c68](https://github.com/vendure-ecommerce/vendure/commit/ed80c68)), closes [#2569](https://github.com/vendure-ecommerce/vendure/issues/2569)
+* **stellate-plugin** Add stellate plugin ([2254576](https://github.com/vendure-ecommerce/vendure/commit/2254576))
+
 ## <small>2.1.4 (2023-11-24)</small>
 ## <small>2.1.4 (2023-11-24)</small>
 
 
 
 

+ 0 - 3
README.md

@@ -48,9 +48,6 @@ The root directory has a `package.json` which contains build-related dependencie
 * Generating TypeScript types from the GraphQL schema
 * Generating TypeScript types from the GraphQL schema
 * Linting, formatting & testing tasks to run on git commit & push
 * Linting, formatting & testing tasks to run on git commit & push
 
 
-> Note:
-> When you do `yarn` for the first time, you will need to manually create the `package` folder under [/packages/admin-ui](/packages/admin-ui).
-
 ### 2. Build all packages
 ### 2. Build all packages
 
 
 `yarn build`
 `yarn build`

+ 1 - 1
docs/docs/guides/developer-guide/custom-fields/index.md

@@ -141,7 +141,7 @@ The following types are available for custom fields:
 | `string`       | Short string data            | url, label                                               |
 | `string`       | Short string data            | url, label                                               |
 | `localeString` | Localized short strings      | localized url                                            |
 | `localeString` | Localized short strings      | localized url                                            |
 | `text`         | Long text data               | extended product info, json config object                |
 | `text`         | Long text data               | extended product info, json config object                |
-| `localText`    | Localized long text          | localized extended product info                          |
+| `localeText`   | Localized long text          | localized extended product info                          |
 | `int`          | Integer                      | product weight, customer loyalty points, monetary values |
 | `int`          | Integer                      | product weight, customer loyalty points, monetary values |
 | `float`        | Floating point number        | product review rating                                    |
 | `float`        | Floating point number        | product review rating                                    |
 | `boolean`      | Boolean                      | isDownloadable flag on product                           |
 | `boolean`      | Boolean                      | isDownloadable flag on product                           |

+ 27 - 0
docs/docs/guides/developer-guide/error-handling/index.mdx

@@ -231,6 +231,33 @@ Here's how a response would look in both the success and error result cases:
 </TabItem>
 </TabItem>
 </Tabs>
 </Tabs>
 
 
+### Handling ErrorResults in plugin code
+
+If you are writing a plugin which deals with internal Vendure service methods that may return ErrorResults,
+then you can use the `isGraphQlErrorResult()` function to check whether the result is an ErrorResult:
+
+```ts
+import { Injectable} from '@nestjs/common';
+import { isGraphQlErrorResult, Order, OrderService, OrderState, RequestContext } from '@vendure/core';
+
+@Injectable()
+export class MyService {
+
+    constructor(private orderService: OrderService) {}
+
+    async myMethod(ctx: RequestContext, order: Order, newState: OrderState) {
+        const transitionResult = await this.orderService.transitionToState(ctx, order.id, newState);
+        if (isGraphQlErrorResult(transitionResult)) {
+            // The transition failed with an ErrorResult
+            throw transitionResult;
+        } else {
+            // TypeScript will correctly infer the type of `transitionResult` to be `Order`
+            return transitionResult;
+        }
+    }
+}
+```
+
 ### Handling ErrorResults in client code
 ### Handling ErrorResults in client code
 
 
 Because we know all possible ErrorResult that may occur for a given mutation, we can handle them in an exhaustive manner. In other
 Because we know all possible ErrorResult that may occur for a given mutation, we can handle them in an exhaustive manner. In other

+ 2 - 3
docs/docs/guides/developer-guide/plugins/index.mdx

@@ -138,7 +138,7 @@ export class MyPlugin implements NestModule {
 
 
 In Vendure **plugins** are used to extend the core functionality of the server. Plugins can be pre-made functionality that you can install via npm, or they can be custom plugins that you write yourself.
 In Vendure **plugins** are used to extend the core functionality of the server. Plugins can be pre-made functionality that you can install via npm, or they can be custom plugins that you write yourself.
 
 
-For any unit of functionality that you need to add to your project, you'll be writing creating a Vendure plugin. By convention, plugins are stored in the `plugins` directory of your project. However, this is not a requirement, and you are free to arrange your plugin files in any way you like.
+For any unit of functionality that you need to add to your project, you'll be creating a Vendure plugin. By convention, plugins are stored in the `plugins` directory of your project. However, this is not a requirement, and you are free to arrange your plugin files in any way you like.
 
 
 ```txt
 ```txt
 ├──src
 ├──src
@@ -281,7 +281,6 @@ In order to make use of this custom field in a type-safe way, we can tell TypeSc
 ```
 ```
 
 
 ```ts title="src/plugins/wishlist-plugin/types.ts"
 ```ts title="src/plugins/wishlist-plugin/types.ts"
-import { CustomCustomerFields } from '@vendure/core/dist/entity/custom-entity-fields';
 import { WishlistItem } from './entities/wishlist-item.entity';
 import { WishlistItem } from './entities/wishlist-item.entity';
 
 
 declare module '@vendure/core/dist/entity/custom-entity-fields' {
 declare module '@vendure/core/dist/entity/custom-entity-fields' {
@@ -312,7 +311,7 @@ Let's create a service to handle the wishlist functionality:
         ├── wishlist.service.ts
         ├── wishlist.service.ts
 ```
 ```
 
 
-```ts title="src/plugins/wishlist-plugin/wishlist.service.ts"
+```ts title="src/plugins/wishlist-plugin/services/wishlist.service.ts"
 import { Injectable } from '@nestjs/common';
 import { Injectable } from '@nestjs/common';
 import {
 import {
     Customer,
     Customer,

+ 6 - 4
docs/docs/guides/extending-the-admin-ui/creating-list-views/index.md

@@ -185,12 +185,13 @@ This is the standard layout for any list view. The main functionality is provide
     />
     />
     
     
     <!-- Here we define all the available columns -->
     <!-- Here we define all the available columns -->
-    <vdr-dt2-column [heading]="'common.id' | translate" [hiddenByDefault]="true">
+    <vdr-dt2-column id="id" [heading]="'common.id' | translate" [hiddenByDefault]="true">
         <ng-template let-review="item">
         <ng-template let-review="item">
             {{ review.id }}
             {{ review.id }}
         </ng-template>
         </ng-template>
     </vdr-dt2-column>
     </vdr-dt2-column>
     <vdr-dt2-column
     <vdr-dt2-column
+            id="created-at"
             [heading]="'common.created-at' | translate"
             [heading]="'common.created-at' | translate"
             [hiddenByDefault]="true"
             [hiddenByDefault]="true"
             [sort]="sorts.get('createdAt')"
             [sort]="sorts.get('createdAt')"
@@ -200,6 +201,7 @@ This is the standard layout for any list view. The main functionality is provide
         </ng-template>
         </ng-template>
     </vdr-dt2-column>
     </vdr-dt2-column>
     <vdr-dt2-column
     <vdr-dt2-column
+            id="updated-at"
             [heading]="'common.updated-at' | translate"
             [heading]="'common.updated-at' | translate"
             [hiddenByDefault]="true"
             [hiddenByDefault]="true"
             [sort]="sorts.get('updatedAt')"
             [sort]="sorts.get('updatedAt')"
@@ -208,7 +210,7 @@ This is the standard layout for any list view. The main functionality is provide
             {{ review.updatedAt | localeDate : 'short' }}
             {{ review.updatedAt | localeDate : 'short' }}
         </ng-template>
         </ng-template>
     </vdr-dt2-column>
     </vdr-dt2-column>
-    <vdr-dt2-column heading="Title" [optional]="false" [sort]="sorts.get('title')">
+    <vdr-dt2-column id="title" heading="Title" [optional]="false" [sort]="sorts.get('title')">
         <ng-template let-review="item">
         <ng-template let-review="item">
             <a class="button-ghost" [routerLink]="['./', review.id]"
             <a class="button-ghost" [routerLink]="['./', review.id]"
             ><span>{{ review.title }}</span>
             ><span>{{ review.title }}</span>
@@ -216,10 +218,10 @@ This is the standard layout for any list view. The main functionality is provide
             </a>
             </a>
         </ng-template>
         </ng-template>
     </vdr-dt2-column>
     </vdr-dt2-column>
-    <vdr-dt2-column heading="Rating" [sort]="sorts.get('rating')">
+    <vdr-dt2-column id="rating" heading="Rating" [sort]="sorts.get('rating')">
         <ng-template let-review="item"><my-star-rating-component [rating]="review.rating"    /></ng-template>
         <ng-template let-review="item"><my-star-rating-component [rating]="review.rating"    /></ng-template>
     </vdr-dt2-column>
     </vdr-dt2-column>
-    <vdr-dt2-column heading="Author" [sort]="sorts.get('authorName')">
+    <vdr-dt2-column id="author" heading="Author" [sort]="sorts.get('authorName')">
         <ng-template let-review="item">{{ review.authorName }}</ng-template>
         <ng-template let-review="item">{{ review.authorName }}</ng-template>
     </vdr-dt2-column>
     </vdr-dt2-column>
 </vdr-data-table-2>
 </vdr-data-table-2>

+ 1 - 1
docs/docs/reference/admin-ui-api/action-bar/action-bar-context.md

@@ -11,7 +11,7 @@ import MemberDescription from '@site/src/components/MemberDescription';
 
 
 ## ActionBarContext
 ## ActionBarContext
 
 
-<GenerationInfo sourceFile="packages/admin-ui/src/lib/core/src/providers/nav-builder/nav-builder-types.ts" sourceLine="78" packageName="@vendure/admin-ui" />
+<GenerationInfo sourceFile="packages/admin-ui/src/lib/core/src/providers/nav-builder/nav-builder-types.ts" sourceLine="89" packageName="@vendure/admin-ui" />
 
 
 Providers available to the onClick handler of an <a href='/reference/admin-ui-api/action-bar/action-bar-item#actionbaritem'>ActionBarItem</a> or <a href='/reference/admin-ui-api/nav-menu/nav-menu-item#navmenuitem'>NavMenuItem</a>.
 Providers available to the onClick handler of an <a href='/reference/admin-ui-api/action-bar/action-bar-item#actionbaritem'>ActionBarItem</a> or <a href='/reference/admin-ui-api/nav-menu/nav-menu-item#navmenuitem'>NavMenuItem</a>.
 
 

+ 12 - 1
docs/docs/reference/admin-ui-api/action-bar/action-bar-item.md

@@ -11,7 +11,7 @@ import MemberDescription from '@site/src/components/MemberDescription';
 
 
 ## ActionBarItem
 ## ActionBarItem
 
 
-<GenerationInfo sourceFile="packages/admin-ui/src/lib/core/src/providers/nav-builder/nav-builder-types.ts" sourceLine="96" packageName="@vendure/admin-ui" />
+<GenerationInfo sourceFile="packages/admin-ui/src/lib/core/src/providers/nav-builder/nav-builder-types.ts" sourceLine="107" packageName="@vendure/admin-ui" />
 
 
 A button in the ActionBar area at the top of one of the list or detail views.
 A button in the ActionBar area at the top of one of the list or detail views.
 
 
@@ -88,7 +88,18 @@ dynamically enable/disable or show/hide the button.
 
 
 <MemberInfo kind="property" type={`string | string[]`}   />
 <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>
 </div>

+ 1 - 1
docs/docs/reference/admin-ui-api/action-bar/router-link-definition.md

@@ -11,7 +11,7 @@ import MemberDescription from '@site/src/components/MemberDescription';
 
 
 ## RouterLinkDefinition
 ## RouterLinkDefinition
 
 
-<GenerationInfo sourceFile="packages/admin-ui/src/lib/core/src/providers/nav-builder/nav-builder-types.ts" sourceLine="128" packageName="@vendure/admin-ui" />
+<GenerationInfo sourceFile="packages/admin-ui/src/lib/core/src/providers/nav-builder/nav-builder-types.ts" sourceLine="154" packageName="@vendure/admin-ui" />
 
 
 A function which returns the router link for an <a href='/reference/admin-ui-api/action-bar/action-bar-item#actionbaritem'>ActionBarItem</a> or <a href='/reference/admin-ui-api/nav-menu/nav-menu-item#navmenuitem'>NavMenuItem</a>.
 A function which returns the router link for an <a href='/reference/admin-ui-api/action-bar/action-bar-item#actionbaritem'>ActionBarItem</a> or <a href='/reference/admin-ui-api/nav-menu/nav-menu-item#navmenuitem'>NavMenuItem</a>.
 
 

+ 4 - 2
docs/docs/reference/admin-ui-api/nav-menu/add-nav-menu-item.md

@@ -11,7 +11,7 @@ import MemberDescription from '@site/src/components/MemberDescription';
 
 
 ## addNavMenuItem
 ## addNavMenuItem
 
 
-<GenerationInfo sourceFile="packages/admin-ui/src/lib/core/src/extension/add-nav-menu-item.ts" sourceLine="64" packageName="@vendure/admin-ui" />
+<GenerationInfo sourceFile="packages/admin-ui/src/lib/core/src/extension/add-nav-menu-item.ts" sourceLine="68" packageName="@vendure/admin-ui" />
 
 
 Add a menu item to an existing section specified by `sectionId`. The id of the section
 Add a menu item to an existing section specified by `sectionId`. The id of the section
 can be found by inspecting the DOM and finding the `data-section-id` attribute.
 can be found by inspecting the DOM and finding the `data-section-id` attribute.
@@ -24,6 +24,8 @@ This should be used in the NgModule `providers` array of your ui extension modul
 *Example*
 *Example*
 
 
 ```ts title="providers.ts"
 ```ts title="providers.ts"
+import { addNavMenuItem } from '@vendure/admin-ui/core';
+
 export default [
 export default [
     addNavMenuItem({
     addNavMenuItem({
         id: 'reviews',
         id: 'reviews',
@@ -33,7 +35,7 @@ export default [
     },
     },
     'marketing'),
     'marketing'),
 ];
 ];
-``
+```
 
 
 ```ts title="Signature"
 ```ts title="Signature"
 function addNavMenuItem(config: NavMenuItem, sectionId: string, before?: string): Provider
 function addNavMenuItem(config: NavMenuItem, sectionId: string, before?: string): Provider

+ 3 - 1
docs/docs/reference/admin-ui-api/nav-menu/add-nav-menu-section.md

@@ -11,7 +11,7 @@ import MemberDescription from '@site/src/components/MemberDescription';
 
 
 ## addNavMenuSection
 ## addNavMenuSection
 
 
-<GenerationInfo sourceFile="packages/admin-ui/src/lib/core/src/extension/add-nav-menu-item.ts" sourceLine="28" packageName="@vendure/admin-ui" />
+<GenerationInfo sourceFile="packages/admin-ui/src/lib/core/src/extension/add-nav-menu-item.ts" sourceLine="30" packageName="@vendure/admin-ui" />
 
 
 Add a section to the main nav menu. Providing the `before` argument will
 Add a section to the main nav menu. Providing the `before` argument will
 move the section before any existing section with the specified id. If
 move the section before any existing section with the specified id. If
@@ -22,6 +22,8 @@ This should be used in the NgModule `providers` array of your ui extension modul
 *Example*
 *Example*
 
 
 ```ts title="providers.ts"
 ```ts title="providers.ts"
+import { addNavMenuSection } from '@vendure/admin-ui/core';
+
 export default [
 export default [
     addNavMenuSection({
     addNavMenuSection({
         id: 'reports',
         id: 'reports',

+ 12 - 1
docs/docs/reference/admin-ui-api/nav-menu/nav-menu-section.md

@@ -60,7 +60,18 @@ interface NavMenuSection {
 
 
 <MemberInfo kind="property" type={`string | ((userPermissions: string[]) =&#62; boolean)`}   />
 <MemberInfo kind="property" type={`string | ((userPermissions: string[]) =&#62; boolean)`}   />
 
 
-Control the display of this item based on the user permissions.
+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`
 ### collapsible
 ### collapsible
 
 
 <MemberInfo kind="property" type={`boolean`}   />
 <MemberInfo kind="property" type={`boolean`}   />

+ 1 - 1
docs/docs/reference/admin-ui-api/react-extensions/register-react-data-table-component.md

@@ -42,7 +42,7 @@ export default [
         tableId: 'product-list',
         tableId: 'product-list',
         columnId: 'slug',
         columnId: 'slug',
         props: {
         props: {
-        foo: 'bar',
+          foo: 'bar',
         },
         },
     }),
     }),
 ];
 ];

+ 3 - 3
docs/docs/reference/core-plugins/job-queue-plugin/bull-mqjob-queue-plugin.md

@@ -32,13 +32,13 @@ in processing jobs.
 
 
 ## Installation
 ## Installation
 
 
-`yarn add @vendure/job-queue-plugin bullmq@1`
+`yarn add @vendure/job-queue-plugin bullmq`
 
 
 or
 or
 
 
-`npm install @vendure/job-queue-plugin bullmq@1`
+`npm install @vendure/job-queue-plugin bullmq`
 
 
-**Note:** The v1.x version of this plugin is designed to work with bullmq v1.x.
+**Note:** The v1.x version of this plugin is designed to work with bullmq v1.x, etc.
 
 
 *Example*
 *Example*
 
 

+ 131 - 0
docs/docs/reference/core-plugins/sentry-plugin/index.md

@@ -0,0 +1,131 @@
+---
+title: "SentryPlugin"
+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';
+
+
+## SentryPlugin
+
+<GenerationInfo sourceFile="packages/sentry-plugin/src/sentry-plugin.ts" sourceLine="109" packageName="@vendure/sentry-plugin" />
+
+This plugin integrates the [Sentry](https://sentry.io) error tracking & performance monitoring
+service with your Vendure server. In addition to capturing errors, it also provides built-in
+support for [tracing](https://docs.sentry.io/product/sentry-basics/concepts/tracing/) as well as
+enriching your Sentry events with additional context about the request.
+
+## Pre-requisites
+
+This plugin depends on access to Sentry, which can be self-hosted or used as a cloud service.
+
+If using the hosted SaaS option, you must have a Sentry account and a project set up ([sign up here](https://sentry.io/signup/)). When setting up your project,
+select the "Node.js" platform and no framework.
+
+Once set up, you will be given a [Data Source Name (DSN)](https://docs.sentry.io/product/sentry-basics/concepts/dsn-explainer/)
+which you will need to provide to the plugin.
+
+## Installation
+
+Install this plugin as well as the `@sentry/node` package:
+
+```sh
+npm install --save @vendure/sentry-plugin @sentry/node
+```
+
+## Configuration
+
+Before using the plugin, you must configure it with the DSN provided by Sentry:
+
+```ts
+import { VendureConfig } from '@vendure/core';
+import { SentryPlugin } from '@vendure/sentry-plugin';
+
+export const config: VendureConfig = {
+    // ...
+    plugins: [
+        // ...
+        // highlight-start
+        SentryPlugin.init({
+            dsn: process.env.SENTRY_DSN,
+            // Optional configuration
+            includeErrorTestMutation: true,
+            enableTracing: true,
+            // you can also pass in any of the options from @sentry/node
+            // for instance:
+            tracesSampleRate: 1.0,
+        }),
+        // highlight-end
+    ],
+};
+```
+
+## Tracing
+
+This plugin includes built-in support for [tracing](https://docs.sentry.io/product/sentry-basics/concepts/tracing/), which allows you to see the performance of your
+GraphQL resolvers in the Sentry dashboard. To enable tracing, set the `enableTracing` option to `true` as shown above.
+
+## Instrumenting your own code
+
+You may want to add your own custom spans to your code. To do so, you can use the `Sentry` object
+just as you would in any Node application. For example:
+
+```ts
+import * as Sentry from "@sentry/node";
+
+export class MyService {
+    async myMethod() {
+         Sentry.setContext('My Custom Context,{
+             key: 'value',
+         });
+    }
+}
+```
+
+## Error test mutation
+
+To test whether your Sentry configuration is working correctly, you can set the `includeErrorTestMutation` option to `true`. This will add a mutation to the Admin API
+which will throw an error of the type specified in the `errorType` argument. For example:
+
+```graphql
+mutation CreateTestError {
+    createTestError(errorType: DATABASE_ERROR)
+}
+```
+
+You should then be able to see the error in your Sentry dashboard (it may take a couple of minutes to appear).
+
+```ts title="Signature"
+class SentryPlugin implements NestModule {
+    static options: SentryPluginOptions = {} as any;
+    configure(consumer: MiddlewareConsumer) => any;
+    init(options: SentryPluginOptions) => ;
+}
+```
+* Implements: <code>NestModule</code>
+
+
+
+<div className="members-wrapper">
+
+### options
+
+<MemberInfo kind="property" type={`<a href='/reference/core-plugins/sentry-plugin/sentry-plugin-options#sentrypluginoptions'>SentryPluginOptions</a>`}   />
+
+
+### configure
+
+<MemberInfo kind="method" type={`(consumer: MiddlewareConsumer) => any`}   />
+
+
+### init
+
+<MemberInfo kind="method" type={`(options: <a href='/reference/core-plugins/sentry-plugin/sentry-plugin-options#sentrypluginoptions'>SentryPluginOptions</a>) => `}   />
+
+
+
+
+</div>

+ 48 - 0
docs/docs/reference/core-plugins/sentry-plugin/sentry-plugin-options.md

@@ -0,0 +1,48 @@
+---
+title: "SentryPluginOptions"
+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';
+
+
+## SentryPluginOptions
+
+<GenerationInfo sourceFile="packages/sentry-plugin/src/types.ts" sourceLine="12" packageName="@vendure/sentry-plugin" />
+
+Configuration options for the <a href='/reference/core-plugins/sentry-plugin/#sentryplugin'>SentryPlugin</a>.
+
+```ts title="Signature"
+interface SentryPluginOptions extends NodeOptions {
+    dsn: string;
+    enableTracing?: boolean;
+    includeErrorTestMutation?: boolean;
+}
+```
+* Extends: <code>NodeOptions</code>
+
+
+
+<div className="members-wrapper">
+
+### dsn
+
+<MemberInfo kind="property" type={`string`}   />
+
+The [Data Source Name](https://docs.sentry.io/product/sentry-basics/concepts/dsn-explainer/) for your Sentry instance.
+### enableTracing
+
+<MemberInfo kind="property" type={`boolean`}   />
+
+
+### includeErrorTestMutation
+
+<MemberInfo kind="property" type={`boolean`}   />
+
+
+
+
+</div>

+ 281 - 0
docs/docs/reference/core-plugins/stellate-plugin/index.md

@@ -0,0 +1,281 @@
+---
+title: "StellatePlugin"
+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';
+
+
+## StellatePlugin
+
+<GenerationInfo sourceFile="packages/stellate-plugin/src/stellate-plugin.ts" sourceLine="246" packageName="@vendure/stellate-plugin" since="2.1.5" />
+
+A plugin to integrate the [Stellate](https://stellate.co/) GraphQL caching service with your Vendure server.
+The main purpose of this plugin is to ensure that cached data gets correctly purged in
+response to events inside Vendure. For example, changes to a Product's description should
+purge any associated record for that Product in Stellate's cache.
+
+## Pre-requisites
+
+You will first need to [set up a free Stellate account](https://stellate.co/signup).
+
+You will also need to generate an **API token** for the Stellate Purging API. For instructions on how to generate the token,
+see the [Stellate Purging API docs](https://docs.stellate.co/docs/purging-api#authentication).
+
+## Installation
+
+```
+npm install @vendure/stellate-plugin
+```
+
+## Configuration
+
+The plugin is configured via the `StellatePlugin.init()` method. This method accepts an options object
+which defines the Stellate service name and API token, as well as an array of <a href='/reference/core-plugins/stellate-plugin/purge-rule#purgerule'>PurgeRule</a>s which
+define how the plugin will respond to Vendure events in order to trigger calls to the
+Stellate [Purging API](https://stellate.co/docs/graphql-edge-cache/purging-api).
+
+*Example*
+
+```ts
+import { StellatePlugin, defaultPurgeRules } from '@vendure/stellate-plugin';
+import { VendureConfig } from '@vendure/core';
+
+export const config: VendureConfig = {
+   // ...
+   plugins: [
+       StellatePlugin.init({
+           // The Stellate service name, i.e. `<serviceName>.stellate.sh`
+           serviceName: 'my-service',
+           // The API token for the Stellate Purging API. See the "pre-requisites" section above.
+           apiToken: process.env.STELLATE_PURGE_API_TOKEN,
+           debugMode: !isProd || process.env.STELLATE_DEBUG_MODE ? true : false,
+           debugLogging: process.env.STELLATE_DEBUG_MODE ? true : false,
+           purgeRules: [
+               ...defaultPurgeRules,
+               // custom purge rules can be added here
+           ],
+       }),
+   ],
+};
+```
+
+In your Stellate dashboard, you can use the following configuration example as a sensible default for a
+Vendure application:
+
+*Example*
+
+```ts
+import { Config } from "stellate";
+
+const config: Config = {
+  config: {
+    name: "my-vendure-server",
+    originUrl: "https://my-vendure-server.com/shop-api",
+    ignoreOriginCacheControl: true,
+    passThroughOnly: false,
+    scopes: {
+      SESSION_BOUND: "header:authorization|cookie:session",
+    },
+    headers: {
+      "access-control-expose-headers": "vendure-auth-token",
+    },
+    rootTypeNames: {
+      query: "Query",
+      mutation: "Mutation",
+    },
+    keyFields: {
+      types: {
+        SearchResult: ["productId"],
+        SearchResponseCacheIdentifier: ["collectionSlug"],
+      },
+    },
+    rules: [
+      {
+        types: [
+          "Product",
+          "Collection",
+          "ProductVariant",
+          "SearchResponse",
+        ],
+        maxAge: 900,
+        swr: 900,
+        description: "Cache Products & Collections",
+      },
+      {
+        types: ["Channel"],
+        maxAge: 9000,
+        swr: 9000,
+        description: "Cache active channel",
+      },
+      {
+        types: ["Order", "Customer", "User"],
+        maxAge: 0,
+        swr: 0,
+        description: "Do not cache user data",
+      },
+    ],
+  },
+};
+export default config;
+```
+
+## Storefront setup
+
+In your storefront, you should point your GraphQL client to the Stellate GraphQL API endpoint, which is
+`https://<service-name>.stellate.sh`.
+
+Wherever you are using the `search` query (typically in product listing & search pages), you should also add the
+`cacheIdentifier` field to the query. This will ensure that the Stellate cache is correctly purged when
+a Product or Collection is updated.
+
+*Example*
+
+```ts
+import { graphql } from '../generated/gql';
+
+export const searchProductsDocument = graphql(`
+    query SearchProducts($input: SearchInput!) {
+        search(input: $input) {
+            // highlight-start
+            cacheIdentifier {
+                collectionSlug
+            }
+            // highlight-end
+            items {
+               # ...
+            }
+        }
+    }
+}`);
+```
+
+## Custom PurgeRules
+
+The configuration above only accounts for caching of some of the built-in Vendure entity types. If you have
+custom entity types, you may well want to add them to the Stellate cache. In this case, you'll also need a way to
+purge those entities from the cache when they are updated. This is where the <a href='/reference/core-plugins/stellate-plugin/purge-rule#purgerule'>PurgeRule</a> comes in.
+
+Let's imagine that you have built a simple CMS plugin for Vendure which exposes an `Article` entity in your Shop API, and
+you have added this to your Stellate configuration:
+
+*Example*
+
+```ts
+import { Config } from "stellate";
+
+const config: Config = {
+    config: {
+        // ...
+        rules: [
+            // ...
+            {
+                types: ["Article"],
+                maxAge: 900,
+                swr: 900,
+                description: "Cache Articles",
+            },
+        ],
+    },
+    // ...
+};
+export default config;
+```
+
+You can then add a custom <a href='/reference/core-plugins/stellate-plugin/purge-rule#purgerule'>PurgeRule</a> to the StellatePlugin configuration:
+
+*Example*
+
+```ts
+import { StellatePlugin, defaultPurgeRules } from "@vendure/stellate-plugin";
+import { VendureConfig } from "@vendure/core";
+import { ArticleEvent } from "./plugins/cms/events/article-event";
+
+export const config: VendureConfig = {
+    // ...
+    plugins: [
+        StellatePlugin.init({
+            // ...
+            purgeRules: [
+                ...defaultPurgeRules,
+                new PurgeRule({
+                    eventType: ArticleEvent,
+                    handler: async ({ events, stellateService }) => {
+                        const articleIds = events.map((e) => e.article.id);
+                        stellateService.purge("Article", articleIds);
+                    },
+                }),
+            ],
+        }),
+    ],
+};
+```
+
+## DevMode & Debug Logging
+
+In development, you can set `devMode: true`, which will prevent any calls being made to the Stellate Purging API.
+
+If you want to log the calls that _would_ be made to the Stellate Purge API when in devMode, you can set `debugLogging: true`.
+Note that debugLogging generates a lot of debug-level logging, so it is recommended to only enable this when needed.
+
+*Example*
+
+```ts
+import { StellatePlugin, defaultPurgeRules } from '@vendure/stellate-plugin';
+import { VendureConfig } from '@vendure/core';
+
+export const config: VendureConfig = {
+   // ...
+   plugins: [
+       StellatePlugin.init({
+           // ...
+           devMode: !process.env.PRODUCTION,
+           debugLogging: process.env.STELLATE_DEBUG_MODE ? true : false,
+           purgeRules: [
+               ...defaultPurgeRules,
+           ],
+       }),
+   ],
+};
+```
+
+```ts title="Signature"
+class StellatePlugin implements OnApplicationBootstrap {
+    static options: StellatePluginOptions;
+    init(options: StellatePluginOptions) => ;
+    constructor(options: StellatePluginOptions, eventBus: EventBus, stellateService: StellateService, moduleRef: ModuleRef)
+    onApplicationBootstrap() => ;
+}
+```
+* Implements: <code>OnApplicationBootstrap</code>
+
+
+
+<div className="members-wrapper">
+
+### options
+
+<MemberInfo kind="property" type={`<a href='/reference/core-plugins/stellate-plugin/stellate-plugin-options#stellatepluginoptions'>StellatePluginOptions</a>`}   />
+
+
+### init
+
+<MemberInfo kind="method" type={`(options: <a href='/reference/core-plugins/stellate-plugin/stellate-plugin-options#stellatepluginoptions'>StellatePluginOptions</a>) => `}   />
+
+
+### constructor
+
+<MemberInfo kind="method" type={`(options: <a href='/reference/core-plugins/stellate-plugin/stellate-plugin-options#stellatepluginoptions'>StellatePluginOptions</a>, eventBus: <a href='/reference/typescript-api/events/event-bus#eventbus'>EventBus</a>, stellateService: <a href='/reference/core-plugins/stellate-plugin/stellate-service#stellateservice'>StellateService</a>, moduleRef: ModuleRef) => StellatePlugin`}   />
+
+
+### onApplicationBootstrap
+
+<MemberInfo kind="method" type={`() => `}   />
+
+
+
+
+</div>

+ 95 - 0
docs/docs/reference/core-plugins/stellate-plugin/purge-rule.md

@@ -0,0 +1,95 @@
+---
+title: "PurgeRule"
+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';
+
+
+## PurgeRule
+
+<GenerationInfo sourceFile="packages/stellate-plugin/src/purge-rule.ts" sourceLine="49" packageName="@vendure/stellate-plugin" />
+
+Defines a rule that listens for a particular VendureEvent and uses that to
+make calls to the [Stellate Purging API](https://docs.stellate.co/docs/purging-api) via
+the provided <a href='/reference/core-plugins/stellate-plugin/stellate-service#stellateservice'>StellateService</a> instance.
+
+```ts title="Signature"
+class PurgeRule<Event extends VendureEvent = VendureEvent> {
+    eventType: Type<Event>
+    bufferTimeMs: number | undefined
+    handle(handlerArgs: { events: Event[]; stellateService: StellateService; injector: Injector }) => ;
+    constructor(config: PurgeRuleConfig<Event>)
+}
+```
+
+<div className="members-wrapper">
+
+### eventType
+
+<MemberInfo kind="property" type={`Type&#60;Event&#62;`}   />
+
+
+### bufferTimeMs
+
+<MemberInfo kind="property" type={`number | undefined`}   />
+
+
+### handle
+
+<MemberInfo kind="method" type={`(handlerArgs: { events: Event[]; stellateService: <a href='/reference/core-plugins/stellate-plugin/stellate-service#stellateservice'>StellateService</a>; injector: <a href='/reference/typescript-api/common/injector#injector'>Injector</a> }) => `}   />
+
+
+### constructor
+
+<MemberInfo kind="method" type={`(config: <a href='/reference/core-plugins/stellate-plugin/purge-rule#purgeruleconfig'>PurgeRuleConfig</a>&#60;Event&#62;) => PurgeRule`}   />
+
+
+
+
+</div>
+
+
+## PurgeRuleConfig
+
+<GenerationInfo sourceFile="packages/stellate-plugin/src/purge-rule.ts" sourceLine="13" packageName="@vendure/stellate-plugin" />
+
+Configures a <a href='/reference/core-plugins/stellate-plugin/purge-rule#purgerule'>PurgeRule</a>.
+
+```ts title="Signature"
+interface PurgeRuleConfig<Event extends VendureEvent> {
+    eventType: Type<Event>;
+    bufferTime?: number;
+    handler: (handlerArgs: {
+        events: Event[];
+        stellateService: StellateService;
+        injector: Injector;
+    }) => void | Promise<void>;
+}
+```
+
+<div className="members-wrapper">
+
+### eventType
+
+<MemberInfo kind="property" type={`Type&#60;Event&#62;`}   />
+
+Specifies which VendureEvent will trigger this purge rule.
+### bufferTime
+
+<MemberInfo kind="property" type={`number`} default="5000"   />
+
+How long to buffer events for in milliseconds before executing the handler. This allows
+us to efficiently batch calls to the Stellate Purge API.
+### handler
+
+<MemberInfo kind="property" type={`(handlerArgs: {
         events: Event[];
         stellateService: <a href='/reference/core-plugins/stellate-plugin/stellate-service#stellateservice'>StellateService</a>;
         injector: <a href='/reference/typescript-api/common/injector#injector'>Injector</a>;
     }) =&#62; void | Promise&#60;void&#62;`}   />
+
+The function to invoke when the specified event is published. This function should use the
+<a href='/reference/core-plugins/stellate-plugin/stellate-service#stellateservice'>StellateService</a> instance to call the Stellate Purge API.
+
+
+</div>

+ 69 - 0
docs/docs/reference/core-plugins/stellate-plugin/stellate-plugin-options.md

@@ -0,0 +1,69 @@
+---
+title: "StellatePluginOptions"
+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';
+
+
+## StellatePluginOptions
+
+<GenerationInfo sourceFile="packages/stellate-plugin/src/types.ts" sourceLine="9" packageName="@vendure/stellate-plugin" />
+
+Configuration options for the StellatePlugin.
+
+```ts title="Signature"
+interface StellatePluginOptions {
+    serviceName: string;
+    apiToken: string;
+    purgeRules: PurgeRule[];
+    defaultBufferTimeMs?: number;
+    devMode?: boolean;
+    debugLogging?: boolean;
+}
+```
+
+<div className="members-wrapper">
+
+### serviceName
+
+<MemberInfo kind="property" type={`string`}   />
+
+The Stellate service name, i.e. `<service-name>.stellate.sh`
+### apiToken
+
+<MemberInfo kind="property" type={`string`}   />
+
+The Stellate Purging API token. For instructions on how to generate the token,
+see the [Stellate docs](https://docs.stellate.co/docs/purging-api#authentication)
+### purgeRules
+
+<MemberInfo kind="property" type={`<a href='/reference/core-plugins/stellate-plugin/purge-rule#purgerule'>PurgeRule</a>[]`}   />
+
+An array of <a href='/reference/core-plugins/stellate-plugin/purge-rule#purgerule'>PurgeRule</a> instances which are used to define how the plugin will
+respond to Vendure events in order to trigger calls to the Stellate Purging API.
+### defaultBufferTimeMs
+
+<MemberInfo kind="property" type={`number`} default="2000"   />
+
+When events are published, the PurgeRules will buffer those events in order to efficiently
+batch requests to the Stellate Purging API. You may wish to change the default, e.g. if you are
+running in a serverless environment and cannot introduce pauses after the main request has completed.
+### devMode
+
+<MemberInfo kind="property" type={`boolean`} default="false"   />
+
+When set to `true`, calls will not be made to the Stellate Purge API.
+### debugLogging
+
+<MemberInfo kind="property" type={`boolean`} default="false"   />
+
+If set to true, the plugin will log the calls that would be made
+to the Stellate Purge API. Note, this generates a
+lot of debug-level logging.
+
+
+</div>

+ 75 - 0
docs/docs/reference/core-plugins/stellate-plugin/stellate-service.md

@@ -0,0 +1,75 @@
+---
+title: "StellateService"
+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';
+
+
+## StellateService
+
+<GenerationInfo sourceFile="packages/stellate-plugin/src/service/stellate.service.ts" sourceLine="23" packageName="@vendure/stellate-plugin" />
+
+The StellateService is used to purge the Stellate cache when certain events occur.
+
+```ts title="Signature"
+class StellateService {
+    constructor(options: StellatePluginOptions)
+    purgeProducts(products: Product[]) => ;
+    purgeProductVariants(productVariants: ProductVariant[]) => ;
+    purgeSearchResults(items: Array<ProductVariant | Product>) => ;
+    purgeAllOfType(type: CachedType) => ;
+    purgeCollections(collections: Collection[]) => ;
+    purgeSearchResponseCacheIdentifiers(collections: Collection[]) => ;
+    purge(type: CachedType, keys?: ID[], keyName:  = 'id') => ;
+}
+```
+
+<div className="members-wrapper">
+
+### constructor
+
+<MemberInfo kind="method" type={`(options: <a href='/reference/core-plugins/stellate-plugin/stellate-plugin-options#stellatepluginoptions'>StellatePluginOptions</a>) => StellateService`}   />
+
+
+### purgeProducts
+
+<MemberInfo kind="method" type={`(products: <a href='/reference/typescript-api/entities/product#product'>Product</a>[]) => `}   />
+
+Purges the cache for the given Products.
+### purgeProductVariants
+
+<MemberInfo kind="method" type={`(productVariants: <a href='/reference/typescript-api/entities/product-variant#productvariant'>ProductVariant</a>[]) => `}   />
+
+Purges the cache for the given ProductVariants.
+### purgeSearchResults
+
+<MemberInfo kind="method" type={`(items: Array&#60;<a href='/reference/typescript-api/entities/product-variant#productvariant'>ProductVariant</a> | <a href='/reference/typescript-api/entities/product#product'>Product</a>&#62;) => `}   />
+
+Purges the cache for SearchResults which contain the given Products or ProductVariants.
+### purgeAllOfType
+
+<MemberInfo kind="method" type={`(type: CachedType) => `}   />
+
+Purges the entire cache for the given type.
+### purgeCollections
+
+<MemberInfo kind="method" type={`(collections: <a href='/reference/typescript-api/entities/collection#collection'>Collection</a>[]) => `}   />
+
+Purges the cache for the given Collections.
+### purgeSearchResponseCacheIdentifiers
+
+<MemberInfo kind="method" type={`(collections: <a href='/reference/typescript-api/entities/collection#collection'>Collection</a>[]) => `}   />
+
+Purges the cache of SearchResults for the given Collections based on slug.
+### purge
+
+<MemberInfo kind="method" type={`(type: CachedType, keys?: <a href='/reference/typescript-api/common/id#id'>ID</a>[], keyName:  = 'id') => `}   />
+
+Purges the cache for the given type and keys.
+
+
+</div>

+ 1 - 3
docs/docs/reference/graphql-api/admin/enums.md

@@ -1,8 +1,6 @@
 ---
 ---
 title: "Enums"
 title: "Enums"
-weight: 5
-date: 2023-07-21T15:33:44.314Z
-showtoc: true
+isDefaultIndex: false
 generated: true
 generated: true
 ---
 ---
 <!-- This file was generated from the Vendure source. Do not modify. Instead, re-run the "docs:build" script -->
 <!-- This file was generated from the Vendure source. Do not modify. Instead, re-run the "docs:build" script -->

+ 13 - 3
docs/docs/reference/graphql-api/admin/input-types.md

@@ -1,8 +1,6 @@
 ---
 ---
 title: "Input Objects"
 title: "Input Objects"
-weight: 4
-date: 2023-07-21T15:33:44.314Z
-showtoc: true
+isDefaultIndex: false
 generated: true
 generated: true
 ---
 ---
 <!-- This file was generated from the Vendure source. Do not modify. Instead, re-run the "docs:build" script -->
 <!-- This file was generated from the Vendure source. Do not modify. Instead, re-run the "docs:build" script -->
@@ -1258,6 +1256,8 @@ import MemberDescription from '@site/src/components/MemberDescription';
 
 
 <div class="graphql-code-line ">perCustomerUsageLimit: <a href="/reference/graphql-api/admin/object-types#int">Int</a></div>
 <div class="graphql-code-line ">perCustomerUsageLimit: <a href="/reference/graphql-api/admin/object-types#int">Int</a></div>
 
 
+<div class="graphql-code-line ">usageLimit: <a href="/reference/graphql-api/admin/object-types#int">Int</a></div>
+
 <div class="graphql-code-line ">conditions: [<a href="/reference/graphql-api/admin/input-types#configurableoperationinput">ConfigurableOperationInput</a>!]!</div>
 <div class="graphql-code-line ">conditions: [<a href="/reference/graphql-api/admin/input-types#configurableoperationinput">ConfigurableOperationInput</a>!]!</div>
 
 
 <div class="graphql-code-line ">actions: [<a href="/reference/graphql-api/admin/input-types#configurableoperationinput">ConfigurableOperationInput</a>!]!</div>
 <div class="graphql-code-line ">actions: [<a href="/reference/graphql-api/admin/input-types#configurableoperationinput">ConfigurableOperationInput</a>!]!</div>
@@ -1822,6 +1822,8 @@ import MemberDescription from '@site/src/components/MemberDescription';
 
 
 <div class="graphql-code-line ">languageCode: <a href="/reference/graphql-api/admin/input-types#stringoperators">StringOperators</a></div>
 <div class="graphql-code-line ">languageCode: <a href="/reference/graphql-api/admin/input-types#stringoperators">StringOperators</a></div>
 
 
+<div class="graphql-code-line ">facetId: <a href="/reference/graphql-api/admin/input-types#idoperators">IDOperators</a></div>
+
 <div class="graphql-code-line ">name: <a href="/reference/graphql-api/admin/input-types#stringoperators">StringOperators</a></div>
 <div class="graphql-code-line ">name: <a href="/reference/graphql-api/admin/input-types#stringoperators">StringOperators</a></div>
 
 
 <div class="graphql-code-line ">code: <a href="/reference/graphql-api/admin/input-types#stringoperators">StringOperators</a></div>
 <div class="graphql-code-line ">code: <a href="/reference/graphql-api/admin/input-types#stringoperators">StringOperators</a></div>
@@ -1877,6 +1879,8 @@ import MemberDescription from '@site/src/components/MemberDescription';
 
 
 <div class="graphql-code-line ">updatedAt: <a href="/reference/graphql-api/admin/enums#sortorder">SortOrder</a></div>
 <div class="graphql-code-line ">updatedAt: <a href="/reference/graphql-api/admin/enums#sortorder">SortOrder</a></div>
 
 
+<div class="graphql-code-line ">facetId: <a href="/reference/graphql-api/admin/enums#sortorder">SortOrder</a></div>
+
 <div class="graphql-code-line ">name: <a href="/reference/graphql-api/admin/enums#sortorder">SortOrder</a></div>
 <div class="graphql-code-line ">name: <a href="/reference/graphql-api/admin/enums#sortorder">SortOrder</a></div>
 
 
 <div class="graphql-code-line ">code: <a href="/reference/graphql-api/admin/enums#sortorder">SortOrder</a></div>
 <div class="graphql-code-line ">code: <a href="/reference/graphql-api/admin/enums#sortorder">SortOrder</a></div>
@@ -2860,6 +2864,8 @@ import MemberDescription from '@site/src/components/MemberDescription';
 
 
 <div class="graphql-code-line ">perCustomerUsageLimit: <a href="/reference/graphql-api/admin/input-types#numberoperators">NumberOperators</a></div>
 <div class="graphql-code-line ">perCustomerUsageLimit: <a href="/reference/graphql-api/admin/input-types#numberoperators">NumberOperators</a></div>
 
 
+<div class="graphql-code-line ">usageLimit: <a href="/reference/graphql-api/admin/input-types#numberoperators">NumberOperators</a></div>
+
 <div class="graphql-code-line ">name: <a href="/reference/graphql-api/admin/input-types#stringoperators">StringOperators</a></div>
 <div class="graphql-code-line ">name: <a href="/reference/graphql-api/admin/input-types#stringoperators">StringOperators</a></div>
 
 
 <div class="graphql-code-line ">description: <a href="/reference/graphql-api/admin/input-types#stringoperators">StringOperators</a></div>
 <div class="graphql-code-line ">description: <a href="/reference/graphql-api/admin/input-types#stringoperators">StringOperators</a></div>
@@ -2925,6 +2931,8 @@ import MemberDescription from '@site/src/components/MemberDescription';
 
 
 <div class="graphql-code-line ">perCustomerUsageLimit: <a href="/reference/graphql-api/admin/enums#sortorder">SortOrder</a></div>
 <div class="graphql-code-line ">perCustomerUsageLimit: <a href="/reference/graphql-api/admin/enums#sortorder">SortOrder</a></div>
 
 
+<div class="graphql-code-line ">usageLimit: <a href="/reference/graphql-api/admin/enums#sortorder">SortOrder</a></div>
+
 <div class="graphql-code-line ">name: <a href="/reference/graphql-api/admin/enums#sortorder">SortOrder</a></div>
 <div class="graphql-code-line ">name: <a href="/reference/graphql-api/admin/enums#sortorder">SortOrder</a></div>
 
 
 <div class="graphql-code-line ">description: <a href="/reference/graphql-api/admin/enums#sortorder">SortOrder</a></div>
 <div class="graphql-code-line ">description: <a href="/reference/graphql-api/admin/enums#sortorder">SortOrder</a></div>
@@ -4469,6 +4477,8 @@ import MemberDescription from '@site/src/components/MemberDescription';
 
 
 <div class="graphql-code-line ">perCustomerUsageLimit: <a href="/reference/graphql-api/admin/object-types#int">Int</a></div>
 <div class="graphql-code-line ">perCustomerUsageLimit: <a href="/reference/graphql-api/admin/object-types#int">Int</a></div>
 
 
+<div class="graphql-code-line ">usageLimit: <a href="/reference/graphql-api/admin/object-types#int">Int</a></div>
+
 <div class="graphql-code-line ">conditions: [<a href="/reference/graphql-api/admin/input-types#configurableoperationinput">ConfigurableOperationInput</a>!]</div>
 <div class="graphql-code-line ">conditions: [<a href="/reference/graphql-api/admin/input-types#configurableoperationinput">ConfigurableOperationInput</a>!]</div>
 
 
 <div class="graphql-code-line ">actions: [<a href="/reference/graphql-api/admin/input-types#configurableoperationinput">ConfigurableOperationInput</a>!]</div>
 <div class="graphql-code-line ">actions: [<a href="/reference/graphql-api/admin/input-types#configurableoperationinput">ConfigurableOperationInput</a>!]</div>

+ 1 - 3
docs/docs/reference/graphql-api/admin/mutations.md

@@ -1,8 +1,6 @@
 ---
 ---
 title: "Mutations"
 title: "Mutations"
-weight: 2
-date: 2023-07-21T15:33:44.314Z
-showtoc: true
+isDefaultIndex: false
 generated: true
 generated: true
 ---
 ---
 <!-- This file was generated from the Vendure source. Do not modify. Instead, re-run the "docs:build" script -->
 <!-- This file was generated from the Vendure source. Do not modify. Instead, re-run the "docs:build" script -->

+ 14 - 4
docs/docs/reference/graphql-api/admin/object-types.md

@@ -1,8 +1,6 @@
 ---
 ---
 title: "Types"
 title: "Types"
-weight: 3
-date: 2023-07-21T15:33:44.314Z
-showtoc: true
+isDefaultIndex: false
 generated: true
 generated: true
 ---
 ---
 <!-- This file was generated from the Vendure source. Do not modify. Instead, re-run the "docs:build" script -->
 <!-- This file was generated from the Vendure source. Do not modify. Instead, re-run the "docs:build" script -->
@@ -1126,6 +1124,11 @@ import MemberDescription from '@site/src/components/MemberDescription';
 
 
 <div class="graphql-code-line ">values: [<a href="/reference/graphql-api/admin/object-types#facetvalue">FacetValue</a>!]!</div>
 <div class="graphql-code-line ">values: [<a href="/reference/graphql-api/admin/object-types#facetvalue">FacetValue</a>!]!</div>
 
 
+<div class="graphql-code-line comment">"""</div>
+<div class="graphql-code-line comment">Returns a paginated, sortable, filterable list of the Facet's values. Added in v2.1.0.</div>
+<div class="graphql-code-line comment">"""</div>
+<div class="graphql-code-line ">valueList(options: <a href="/reference/graphql-api/admin/input-types#facetvaluelistoptions">FacetValueListOptions</a>): <a href="/reference/graphql-api/admin/object-types#facetvaluelist">FacetValueList</a>!</div>
+
 <div class="graphql-code-line ">translations: [<a href="/reference/graphql-api/admin/object-types#facettranslation">FacetTranslation</a>!]!</div>
 <div class="graphql-code-line ">translations: [<a href="/reference/graphql-api/admin/object-types#facettranslation">FacetTranslation</a>!]!</div>
 
 
 <div class="graphql-code-line ">customFields: <a href="/reference/graphql-api/admin/object-types#json">JSON</a></div>
 <div class="graphql-code-line ">customFields: <a href="/reference/graphql-api/admin/object-types#json">JSON</a></div>
@@ -1200,6 +1203,8 @@ import MemberDescription from '@site/src/components/MemberDescription';
 
 
 <div class="graphql-code-line ">facet: <a href="/reference/graphql-api/admin/object-types#facet">Facet</a>!</div>
 <div class="graphql-code-line ">facet: <a href="/reference/graphql-api/admin/object-types#facet">Facet</a>!</div>
 
 
+<div class="graphql-code-line ">facetId: <a href="/reference/graphql-api/admin/object-types#id">ID</a>!</div>
+
 <div class="graphql-code-line ">name: <a href="/reference/graphql-api/admin/object-types#string">String</a>!</div>
 <div class="graphql-code-line ">name: <a href="/reference/graphql-api/admin/object-types#string">String</a>!</div>
 
 
 <div class="graphql-code-line ">code: <a href="/reference/graphql-api/admin/object-types#string">String</a>!</div>
 <div class="graphql-code-line ">code: <a href="/reference/graphql-api/admin/object-types#string">String</a>!</div>
@@ -2265,6 +2270,9 @@ import MemberDescription from '@site/src/components/MemberDescription';
 <div class="graphql-code-line comment">"""</div>
 <div class="graphql-code-line comment">"""</div>
 <div class="graphql-code-line ">proratedUnitPriceWithTax: <a href="/reference/graphql-api/admin/object-types#money">Money</a>!</div>
 <div class="graphql-code-line ">proratedUnitPriceWithTax: <a href="/reference/graphql-api/admin/object-types#money">Money</a>!</div>
 
 
+<div class="graphql-code-line comment">"""</div>
+<div class="graphql-code-line comment">The quantity of items purchased</div>
+<div class="graphql-code-line comment">"""</div>
 <div class="graphql-code-line ">quantity: <a href="/reference/graphql-api/admin/object-types#int">Int</a>!</div>
 <div class="graphql-code-line ">quantity: <a href="/reference/graphql-api/admin/object-types#int">Int</a>!</div>
 
 
 <div class="graphql-code-line comment">"""</div>
 <div class="graphql-code-line comment">"""</div>
@@ -2982,7 +2990,7 @@ import MemberDescription from '@site/src/components/MemberDescription';
  &#123;</div>
  &#123;</div>
 <div class="graphql-code-line ">currencyCode: <a href="/reference/graphql-api/admin/enums#currencycode">CurrencyCode</a>!</div>
 <div class="graphql-code-line ">currencyCode: <a href="/reference/graphql-api/admin/enums#currencycode">CurrencyCode</a>!</div>
 
 
-<div class="graphql-code-line ">price: <a href="/reference/graphql-api/admin/object-types#int">Int</a>!</div>
+<div class="graphql-code-line ">price: <a href="/reference/graphql-api/admin/object-types#money">Money</a>!</div>
 
 
 
 
 <div class="graphql-code-line top-level">&#125;</div>
 <div class="graphql-code-line top-level">&#125;</div>
@@ -3026,6 +3034,8 @@ import MemberDescription from '@site/src/components/MemberDescription';
 
 
 <div class="graphql-code-line ">perCustomerUsageLimit: <a href="/reference/graphql-api/admin/object-types#int">Int</a></div>
 <div class="graphql-code-line ">perCustomerUsageLimit: <a href="/reference/graphql-api/admin/object-types#int">Int</a></div>
 
 
+<div class="graphql-code-line ">usageLimit: <a href="/reference/graphql-api/admin/object-types#int">Int</a></div>
+
 <div class="graphql-code-line ">name: <a href="/reference/graphql-api/admin/object-types#string">String</a>!</div>
 <div class="graphql-code-line ">name: <a href="/reference/graphql-api/admin/object-types#string">String</a>!</div>
 
 
 <div class="graphql-code-line ">description: <a href="/reference/graphql-api/admin/object-types#string">String</a>!</div>
 <div class="graphql-code-line ">description: <a href="/reference/graphql-api/admin/object-types#string">String</a>!</div>

+ 1 - 3
docs/docs/reference/graphql-api/admin/queries.md

@@ -1,8 +1,6 @@
 ---
 ---
 title: "Queries"
 title: "Queries"
-weight: 1
-date: 2023-07-21T15:33:44.314Z
-showtoc: true
+isDefaultIndex: false
 generated: true
 generated: true
 ---
 ---
 <!-- This file was generated from the Vendure source. Do not modify. Instead, re-run the "docs:build" script -->
 <!-- This file was generated from the Vendure source. Do not modify. Instead, re-run the "docs:build" script -->

+ 1 - 3
docs/docs/reference/graphql-api/shop/enums.md

@@ -1,8 +1,6 @@
 ---
 ---
 title: "Enums"
 title: "Enums"
-weight: 5
-date: 2023-07-21T15:33:42.677Z
-showtoc: true
+isDefaultIndex: false
 generated: true
 generated: true
 ---
 ---
 <!-- This file was generated from the Vendure source. Do not modify. Instead, re-run the "docs:build" script -->
 <!-- This file was generated from the Vendure source. Do not modify. Instead, re-run the "docs:build" script -->

+ 82 - 3
docs/docs/reference/graphql-api/shop/input-types.md

@@ -1,8 +1,6 @@
 ---
 ---
 title: "Input Objects"
 title: "Input Objects"
-weight: 4
-date: 2023-07-21T15:33:42.677Z
-showtoc: true
+isDefaultIndex: false
 generated: true
 generated: true
 ---
 ---
 <!-- This file was generated from the Vendure source. Do not modify. Instead, re-run the "docs:build" script -->
 <!-- This file was generated from the Vendure source. Do not modify. Instead, re-run the "docs:build" script -->
@@ -473,6 +471,87 @@ import MemberDescription from '@site/src/components/MemberDescription';
 <div class="graphql-code-line ">or: [<a href="/reference/graphql-api/shop/object-types#id">ID</a>!]</div>
 <div class="graphql-code-line ">or: [<a href="/reference/graphql-api/shop/object-types#id">ID</a>!]</div>
 
 
 
 
+<div class="graphql-code-line top-level">&#125;</div>
+
+</div>
+
+## FacetValueFilterParameter
+
+<div class="graphql-code-block">
+<div class="graphql-code-line top-level">input <span class="graphql-code-identifier">FacetValueFilterParameter</span>
+ &#123;</div>
+<div class="graphql-code-line ">id: <a href="/reference/graphql-api/shop/input-types#idoperators">IDOperators</a></div>
+
+<div class="graphql-code-line ">createdAt: <a href="/reference/graphql-api/shop/input-types#dateoperators">DateOperators</a></div>
+
+<div class="graphql-code-line ">updatedAt: <a href="/reference/graphql-api/shop/input-types#dateoperators">DateOperators</a></div>
+
+<div class="graphql-code-line ">languageCode: <a href="/reference/graphql-api/shop/input-types#stringoperators">StringOperators</a></div>
+
+<div class="graphql-code-line ">facetId: <a href="/reference/graphql-api/shop/input-types#idoperators">IDOperators</a></div>
+
+<div class="graphql-code-line ">name: <a href="/reference/graphql-api/shop/input-types#stringoperators">StringOperators</a></div>
+
+<div class="graphql-code-line ">code: <a href="/reference/graphql-api/shop/input-types#stringoperators">StringOperators</a></div>
+
+
+<div class="graphql-code-line top-level">&#125;</div>
+
+</div>
+
+## FacetValueListOptions
+
+<div class="graphql-code-block">
+<div class="graphql-code-line top-level">input <span class="graphql-code-identifier">FacetValueListOptions</span>
+ &#123;</div>
+<div class="graphql-code-line comment">"""</div>
+<div class="graphql-code-line comment">Skips the first n results, for use in pagination</div>
+<div class="graphql-code-line comment">"""</div>
+<div class="graphql-code-line ">skip: <a href="/reference/graphql-api/shop/object-types#int">Int</a></div>
+
+<div class="graphql-code-line comment">"""</div>
+<div class="graphql-code-line comment">Takes n results, for use in pagination</div>
+<div class="graphql-code-line comment">"""</div>
+<div class="graphql-code-line ">take: <a href="/reference/graphql-api/shop/object-types#int">Int</a></div>
+
+<div class="graphql-code-line comment">"""</div>
+<div class="graphql-code-line comment">Specifies which properties to sort the results by</div>
+<div class="graphql-code-line comment">"""</div>
+<div class="graphql-code-line ">sort: <a href="/reference/graphql-api/shop/input-types#facetvaluesortparameter">FacetValueSortParameter</a></div>
+
+<div class="graphql-code-line comment">"""</div>
+<div class="graphql-code-line comment">Allows the results to be filtered</div>
+<div class="graphql-code-line comment">"""</div>
+<div class="graphql-code-line ">filter: <a href="/reference/graphql-api/shop/input-types#facetvaluefilterparameter">FacetValueFilterParameter</a></div>
+
+<div class="graphql-code-line comment">"""</div>
+<div class="graphql-code-line comment">Specifies whether multiple "filter" arguments should be combines with a logical AND or OR operation. Defaults to AND.</div>
+<div class="graphql-code-line comment">"""</div>
+<div class="graphql-code-line ">filterOperator: <a href="/reference/graphql-api/shop/enums#logicaloperator">LogicalOperator</a></div>
+
+
+<div class="graphql-code-line top-level">&#125;</div>
+
+</div>
+
+## FacetValueSortParameter
+
+<div class="graphql-code-block">
+<div class="graphql-code-line top-level">input <span class="graphql-code-identifier">FacetValueSortParameter</span>
+ &#123;</div>
+<div class="graphql-code-line ">id: <a href="/reference/graphql-api/shop/enums#sortorder">SortOrder</a></div>
+
+<div class="graphql-code-line ">createdAt: <a href="/reference/graphql-api/shop/enums#sortorder">SortOrder</a></div>
+
+<div class="graphql-code-line ">updatedAt: <a href="/reference/graphql-api/shop/enums#sortorder">SortOrder</a></div>
+
+<div class="graphql-code-line ">facetId: <a href="/reference/graphql-api/shop/enums#sortorder">SortOrder</a></div>
+
+<div class="graphql-code-line ">name: <a href="/reference/graphql-api/shop/enums#sortorder">SortOrder</a></div>
+
+<div class="graphql-code-line ">code: <a href="/reference/graphql-api/shop/enums#sortorder">SortOrder</a></div>
+
+
 <div class="graphql-code-line top-level">&#125;</div>
 <div class="graphql-code-line top-level">&#125;</div>
 
 
 </div>
 </div>

+ 1 - 3
docs/docs/reference/graphql-api/shop/mutations.md

@@ -1,8 +1,6 @@
 ---
 ---
 title: "Mutations"
 title: "Mutations"
-weight: 2
-date: 2023-07-21T15:33:42.677Z
-showtoc: true
+isDefaultIndex: false
 generated: true
 generated: true
 ---
 ---
 <!-- This file was generated from the Vendure source. Do not modify. Instead, re-run the "docs:build" script -->
 <!-- This file was generated from the Vendure source. Do not modify. Instead, re-run the "docs:build" script -->

+ 26 - 3
docs/docs/reference/graphql-api/shop/object-types.md

@@ -1,8 +1,6 @@
 ---
 ---
 title: "Types"
 title: "Types"
-weight: 3
-date: 2023-07-21T15:33:42.677Z
-showtoc: true
+isDefaultIndex: false
 generated: true
 generated: true
 ---
 ---
 <!-- This file was generated from the Vendure source. Do not modify. Instead, re-run the "docs:build" script -->
 <!-- This file was generated from the Vendure source. Do not modify. Instead, re-run the "docs:build" script -->
@@ -795,6 +793,11 @@ import MemberDescription from '@site/src/components/MemberDescription';
 
 
 <div class="graphql-code-line ">values: [<a href="/reference/graphql-api/shop/object-types#facetvalue">FacetValue</a>!]!</div>
 <div class="graphql-code-line ">values: [<a href="/reference/graphql-api/shop/object-types#facetvalue">FacetValue</a>!]!</div>
 
 
+<div class="graphql-code-line comment">"""</div>
+<div class="graphql-code-line comment">Returns a paginated, sortable, filterable list of the Facet's values. Added in v2.1.0.</div>
+<div class="graphql-code-line comment">"""</div>
+<div class="graphql-code-line ">valueList(options: <a href="/reference/graphql-api/shop/input-types#facetvaluelistoptions">FacetValueListOptions</a>): <a href="/reference/graphql-api/shop/object-types#facetvaluelist">FacetValueList</a>!</div>
+
 <div class="graphql-code-line ">translations: [<a href="/reference/graphql-api/shop/object-types#facettranslation">FacetTranslation</a>!]!</div>
 <div class="graphql-code-line ">translations: [<a href="/reference/graphql-api/shop/object-types#facettranslation">FacetTranslation</a>!]!</div>
 
 
 <div class="graphql-code-line ">customFields: <a href="/reference/graphql-api/shop/object-types#json">JSON</a></div>
 <div class="graphql-code-line ">customFields: <a href="/reference/graphql-api/shop/object-types#json">JSON</a></div>
@@ -850,6 +853,8 @@ import MemberDescription from '@site/src/components/MemberDescription';
 
 
 <div class="graphql-code-line ">facet: <a href="/reference/graphql-api/shop/object-types#facet">Facet</a>!</div>
 <div class="graphql-code-line ">facet: <a href="/reference/graphql-api/shop/object-types#facet">Facet</a>!</div>
 
 
+<div class="graphql-code-line ">facetId: <a href="/reference/graphql-api/shop/object-types#id">ID</a>!</div>
+
 <div class="graphql-code-line ">name: <a href="/reference/graphql-api/shop/object-types#string">String</a>!</div>
 <div class="graphql-code-line ">name: <a href="/reference/graphql-api/shop/object-types#string">String</a>!</div>
 
 
 <div class="graphql-code-line ">code: <a href="/reference/graphql-api/shop/object-types#string">String</a>!</div>
 <div class="graphql-code-line ">code: <a href="/reference/graphql-api/shop/object-types#string">String</a>!</div>
@@ -859,6 +864,19 @@ import MemberDescription from '@site/src/components/MemberDescription';
 <div class="graphql-code-line ">customFields: <a href="/reference/graphql-api/shop/object-types#json">JSON</a></div>
 <div class="graphql-code-line ">customFields: <a href="/reference/graphql-api/shop/object-types#json">JSON</a></div>
 
 
 
 
+<div class="graphql-code-line top-level">&#125;</div>
+</div>
+
+## FacetValueList
+
+<div class="graphql-code-block">
+<div class="graphql-code-line top-level">type <span class="graphql-code-identifier">FacetValueList</span>
+ &#123;</div>
+<div class="graphql-code-line ">items: [<a href="/reference/graphql-api/shop/object-types#facetvalue">FacetValue</a>!]!</div>
+
+<div class="graphql-code-line ">totalItems: <a href="/reference/graphql-api/shop/object-types#int">Int</a>!</div>
+
+
 <div class="graphql-code-line top-level">&#125;</div>
 <div class="graphql-code-line top-level">&#125;</div>
 </div>
 </div>
 
 
@@ -1622,6 +1640,9 @@ import MemberDescription from '@site/src/components/MemberDescription';
 <div class="graphql-code-line comment">"""</div>
 <div class="graphql-code-line comment">"""</div>
 <div class="graphql-code-line ">proratedUnitPriceWithTax: <a href="/reference/graphql-api/shop/object-types#money">Money</a>!</div>
 <div class="graphql-code-line ">proratedUnitPriceWithTax: <a href="/reference/graphql-api/shop/object-types#money">Money</a>!</div>
 
 
+<div class="graphql-code-line comment">"""</div>
+<div class="graphql-code-line comment">The quantity of items purchased</div>
+<div class="graphql-code-line comment">"""</div>
 <div class="graphql-code-line ">quantity: <a href="/reference/graphql-api/shop/object-types#int">Int</a>!</div>
 <div class="graphql-code-line ">quantity: <a href="/reference/graphql-api/shop/object-types#int">Int</a>!</div>
 
 
 <div class="graphql-code-line comment">"""</div>
 <div class="graphql-code-line comment">"""</div>
@@ -2288,6 +2309,8 @@ import MemberDescription from '@site/src/components/MemberDescription';
 
 
 <div class="graphql-code-line ">perCustomerUsageLimit: <a href="/reference/graphql-api/shop/object-types#int">Int</a></div>
 <div class="graphql-code-line ">perCustomerUsageLimit: <a href="/reference/graphql-api/shop/object-types#int">Int</a></div>
 
 
+<div class="graphql-code-line ">usageLimit: <a href="/reference/graphql-api/shop/object-types#int">Int</a></div>
+
 <div class="graphql-code-line ">name: <a href="/reference/graphql-api/shop/object-types#string">String</a>!</div>
 <div class="graphql-code-line ">name: <a href="/reference/graphql-api/shop/object-types#string">String</a>!</div>
 
 
 <div class="graphql-code-line ">description: <a href="/reference/graphql-api/shop/object-types#string">String</a>!</div>
 <div class="graphql-code-line ">description: <a href="/reference/graphql-api/shop/object-types#string">String</a>!</div>

+ 1 - 3
docs/docs/reference/graphql-api/shop/queries.md

@@ -1,8 +1,6 @@
 ---
 ---
 title: "Queries"
 title: "Queries"
-weight: 1
-date: 2023-07-21T15:33:42.677Z
-showtoc: true
+isDefaultIndex: false
 generated: true
 generated: true
 ---
 ---
 <!-- This file was generated from the Vendure source. Do not modify. Instead, re-run the "docs:build" script -->
 <!-- This file was generated from the Vendure source. Do not modify. Instead, re-run the "docs:build" script -->

+ 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="4210" packageName="@vendure/common" />
+<GenerationInfo sourceFile="packages/common/src/generated-types.ts" sourceLine="4211" 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.

+ 9 - 9
docs/docs/reference/typescript-api/configurable-operation-def/default-form-config-hash.md

@@ -29,15 +29,15 @@ type DefaultFormConfigHash = {
     'product-selector-form-input': Record<string, never>;
     'product-selector-form-input': Record<string, never>;
     'relation-form-input': Record<string, never>;
     'relation-form-input': Record<string, never>;
     'rich-text-form-input': Record<string, never>;
     'rich-text-form-input': Record<string, never>;
-    'select-form-input': {
-        options?: Array<{ value: string; label?: Array<Omit<LocalizedString, '__typename'>> }>;
+    'select-form-input': {
+        options?: Array<{ value: string; label?: Array<Omit<LocalizedString, '__typename'>> }>;
     };
     };
     'text-form-input': { prefix?: string; suffix?: string };
     'text-form-input': { prefix?: string; suffix?: string };
-    'textarea-form-input': {
-        spellcheck?: boolean;
+    'textarea-form-input': {
+        spellcheck?: boolean;
     };
     };
-    'product-multi-form-input': {
-        selectionMode?: 'product' | 'variant';
+    'product-multi-form-input': {
+        selectionMode?: 'product' | 'variant';
     };
     };
     'combination-mode-form-input': Record<string, never>;
     'combination-mode-form-input': Record<string, never>;
 }
 }
@@ -107,7 +107,7 @@ type DefaultFormConfigHash = {
 
 
 ### 'select-form-input'
 ### 'select-form-input'
 
 
-<MemberInfo kind="property" type={`{
         options?: Array&#60;{ value: string; label?: Array&#60;Omit&#60;LocalizedString, '__typename'&#62;&#62; }&#62;;
     }`}   />
+<MemberInfo kind="property" type={`{         options?: Array&#60;{ value: string; label?: Array&#60;Omit&#60;LocalizedString, '__typename'&#62;&#62; }&#62;;     }`}   />
 
 
 
 
 ### 'text-form-input'
 ### 'text-form-input'
@@ -117,12 +117,12 @@ type DefaultFormConfigHash = {
 
 
 ### 'textarea-form-input'
 ### 'textarea-form-input'
 
 
-<MemberInfo kind="property" type={`{
         spellcheck?: boolean;
     }`}   />
+<MemberInfo kind="property" type={`{         spellcheck?: boolean;     }`}   />
 
 
 
 
 ### 'product-multi-form-input'
 ### 'product-multi-form-input'
 
 
-<MemberInfo kind="property" type={`{
         selectionMode?: 'product' | 'variant';
     }`}   />
+<MemberInfo kind="property" type={`{         selectionMode?: 'product' | 'variant';     }`}   />
 
 
 
 
 ### 'combination-mode-form-input'
 ### 'combination-mode-form-input'

+ 1 - 1
docs/docs/reference/typescript-api/custom-fields/custom-field-type.md

@@ -21,7 +21,7 @@ Type         | DB type                               | GraphQL type
 string       | varchar                               | String
 string       | varchar                               | String
 localeString | varchar                               | String
 localeString | varchar                               | String
 text         | longtext(m), text(p,s)                | String
 text         | longtext(m), text(p,s)                | String
-localText    | longtext(m), text(p,s)                | String
+localeText    | longtext(m), text(p,s)                | String
 int          | int                                   | Int
 int          | int                                   | Int
 float        | double precision                      | Float
 float        | double precision                      | Float
 boolean      | tinyint (m), bool (p), boolean (s)    | Boolean
 boolean      | tinyint (m), bool (p), boolean (s)    | Boolean

+ 34 - 0
docs/docs/reference/typescript-api/errors/error-result-union.md

@@ -0,0 +1,34 @@
+---
+title: "ErrorResultUnion"
+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';
+
+
+## ErrorResultUnion
+
+<GenerationInfo sourceFile="packages/core/src/common/error/error-result.ts" sourceLine="44" packageName="@vendure/core" />
+
+Used to construct a TypeScript return type for a query or mutation which, in the GraphQL schema,
+returns a union type composed of a success result (e.g. Order) plus one or more ErrorResult
+types.
+
+Since the TypeScript entities do not correspond 1-to-1 with their GraphQL type counterparts,
+we use this type to substitute them.
+
+*Example*
+
+```ts
+type UpdateOrderItemsResult = Order | OrderModificationError | OrderLimitError | NegativeQuantityError;
+type T1 = ErrorResultUnion<UpdateOrderItemsResult, VendureEntityOrder>;
+// T1 = VendureEntityOrder | OrderModificationError | OrderLimitError | NegativeQuantityError;
+```
+
+```ts title="Signature"
+type ErrorResultUnion<T extends GraphQLErrorResult | U, E extends VendureEntity, U = any> = | JustErrorResults<T>
+    | E
+```

+ 44 - 0
docs/docs/reference/typescript-api/errors/is-graph-ql-error-result.md

@@ -0,0 +1,44 @@
+---
+title: "IsGraphQlErrorResult"
+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';
+
+
+## isGraphQlErrorResult
+
+<GenerationInfo sourceFile="packages/core/src/common/error/error-result.ts" sourceLine="71" packageName="@vendure/core" />
+
+Returns true if the <a href='/reference/typescript-api/errors/error-result-union#errorresultunion'>ErrorResultUnion</a> is actually an ErrorResult type. This is useful when dealing with
+certain internal service method that return an ErrorResultUnion.
+
+*Example*
+
+```ts
+import { isGraphQlErrorResult } from '@vendure/core';
+
+// ...
+
+const transitionResult = await this.orderService.transitionToState(ctx, order.id, newState);
+if (isGraphQlErrorResult(transitionResult)) {
+    // The transition failed with an ErrorResult
+    throw transitionResult;
+} else {
+    // TypeScript will correctly infer the type of `transitionResult` to be `Order`
+    return transitionResult;
+}
+```
+
+```ts title="Signature"
+function isGraphQlErrorResult<T extends GraphQLErrorResult | U, U = any>(input: T): input is JustErrorResults<T>
+```
+Parameters
+
+### input
+
+<MemberInfo kind="parameter" type={`T`} />
+

+ 1 - 2
docs/docs/reference/typescript-api/events/vendure-entity-event.md

@@ -11,10 +11,9 @@ import MemberDescription from '@site/src/components/MemberDescription';
 
 
 ## VendureEntityEvent
 ## VendureEntityEvent
 
 
-<GenerationInfo sourceFile="packages/core/src/event-bus/vendure-entity-event.ts" sourceLine="13" packageName="@vendure/core" />
+<GenerationInfo sourceFile="packages/core/src/event-bus/vendure-entity-event.ts" sourceLine="12" packageName="@vendure/core" />
 
 
 The base class for all entity events used by the EventBus system.
 The base class for all entity events used by the EventBus system.
-* For event type `'updated'` the entity is the one before applying the patch (if not documented otherwise).
 * For event type `'deleted'` the input will most likely be an `id: ID`
 * For event type `'deleted'` the input will most likely be an `id: ID`
 
 
 ```ts title="Signature"
 ```ts title="Signature"

+ 1 - 1
docs/docs/reference/typescript-api/orders/default-guest-checkout-strategy.md

@@ -57,7 +57,7 @@ class DefaultGuestCheckoutStrategy implements GuestCheckoutStrategy {
 
 
 ### setCustomerForOrder
 ### setCustomerForOrder
 
 
-<MemberInfo kind="method" type={`(ctx: <a href='/reference/typescript-api/request/request-context#requestcontext'>RequestContext</a>, order: <a href='/reference/typescript-api/entities/order#order'>Order</a>, input: CreateCustomerInput) => Promise&#60;ErrorResultUnion&#60;SetCustomerForOrderResult, <a href='/reference/typescript-api/entities/customer#customer'>Customer</a>&#62;&#62;`}   />
+<MemberInfo kind="method" type={`(ctx: <a href='/reference/typescript-api/request/request-context#requestcontext'>RequestContext</a>, order: <a href='/reference/typescript-api/entities/order#order'>Order</a>, input: CreateCustomerInput) => Promise&#60;<a href='/reference/typescript-api/errors/error-result-union#errorresultunion'>ErrorResultUnion</a>&#60;SetCustomerForOrderResult, <a href='/reference/typescript-api/entities/customer#customer'>Customer</a>&#62;&#62;`}   />
 
 
 
 
 
 

+ 1 - 1
docs/docs/reference/typescript-api/orders/guest-checkout-strategy.md

@@ -51,7 +51,7 @@ interface GuestCheckoutStrategy extends InjectableStrategy {
 
 
 ### setCustomerForOrder
 ### setCustomerForOrder
 
 
-<MemberInfo kind="method" type={`(ctx: <a href='/reference/typescript-api/request/request-context#requestcontext'>RequestContext</a>, order: <a href='/reference/typescript-api/entities/order#order'>Order</a>, input: CreateCustomerInput) => | ErrorResultUnion&#60;SetCustomerForOrderResult, <a href='/reference/typescript-api/entities/customer#customer'>Customer</a>&#62;
         | Promise&#60;ErrorResultUnion&#60;SetCustomerForOrderResult, <a href='/reference/typescript-api/entities/customer#customer'>Customer</a>&#62;&#62;`}   />
+<MemberInfo kind="method" type={`(ctx: <a href='/reference/typescript-api/request/request-context#requestcontext'>RequestContext</a>, order: <a href='/reference/typescript-api/entities/order#order'>Order</a>, input: CreateCustomerInput) => | <a href='/reference/typescript-api/errors/error-result-union#errorresultunion'>ErrorResultUnion</a>&#60;SetCustomerForOrderResult, <a href='/reference/typescript-api/entities/customer#customer'>Customer</a>&#62;
         | Promise&#60;<a href='/reference/typescript-api/errors/error-result-union#errorresultunion'>ErrorResultUnion</a>&#60;SetCustomerForOrderResult, <a href='/reference/typescript-api/entities/customer#customer'>Customer</a>&#62;&#62;`}   />
 
 
 This method is called when the `setCustomerForOrder` mutation is executed.
 This method is called when the `setCustomerForOrder` mutation is executed.
 It should return either a Customer object or an ErrorResult.
 It should return either a Customer object or an ErrorResult.

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

@@ -196,7 +196,7 @@ Parameters
 
 
 ## defaultOrderProcess
 ## defaultOrderProcess
 
 
-<GenerationInfo sourceFile="packages/core/src/config/order/default-order-process.ts" sourceLine="474" packageName="@vendure/core" since="2.0.0" />
+<GenerationInfo sourceFile="packages/core/src/config/order/default-order-process.ts" sourceLine="477" packageName="@vendure/core" since="2.0.0" />
 
 
 This is the built-in <a href='/reference/typescript-api/orders/order-process#orderprocess'>OrderProcess</a> that ships with Vendure. A customized version of this process
 This is the built-in <a href='/reference/typescript-api/orders/order-process#orderprocess'>OrderProcess</a> that ships with Vendure. A customized version of this process
 can be created using the <a href='/reference/typescript-api/orders/order-process#configuredefaultorderprocess'>configureDefaultOrderProcess</a> function, which allows you to pass in an object
 can be created using the <a href='/reference/typescript-api/orders/order-process#configuredefaultorderprocess'>configureDefaultOrderProcess</a> function, which allows you to pass in an object

+ 1 - 1
docs/docs/reference/typescript-api/request/request-context-service.md

@@ -11,7 +11,7 @@ import MemberDescription from '@site/src/components/MemberDescription';
 
 
 ## RequestContextService
 ## RequestContextService
 
 
-<GenerationInfo sourceFile="packages/core/src/service/helpers/request-context/request-context.service.ts" sourceLine="24" packageName="@vendure/core" />
+<GenerationInfo sourceFile="packages/core/src/service/helpers/request-context/request-context.service.ts" sourceLine="25" packageName="@vendure/core" />
 
 
 Creates new <a href='/reference/typescript-api/request/request-context#requestcontext'>RequestContext</a> instances.
 Creates new <a href='/reference/typescript-api/request/request-context#requestcontext'>RequestContext</a> instances.
 
 

+ 3 - 3
docs/docs/reference/typescript-api/services/channel-service.md

@@ -11,7 +11,7 @@ import MemberDescription from '@site/src/components/MemberDescription';
 
 
 ## ChannelService
 ## ChannelService
 
 
-<GenerationInfo sourceFile="packages/core/src/service/services/channel.service.ts" sourceLine="53" packageName="@vendure/core" />
+<GenerationInfo sourceFile="packages/core/src/service/services/channel.service.ts" sourceLine="54" packageName="@vendure/core" />
 
 
 Contains methods relating to <a href='/reference/typescript-api/entities/channel#channel'>Channel</a> entities.
 Contains methods relating to <a href='/reference/typescript-api/entities/channel#channel'>Channel</a> entities.
 
 
@@ -90,12 +90,12 @@ Returns the default Channel.
 
 
 ### create
 ### create
 
 
-<MemberInfo kind="method" type={`(ctx: <a href='/reference/typescript-api/request/request-context#requestcontext'>RequestContext</a>, input: CreateChannelInput) => Promise&#60;ErrorResultUnion&#60;CreateChannelResult, <a href='/reference/typescript-api/entities/channel#channel'>Channel</a>&#62;&#62;`}   />
+<MemberInfo kind="method" type={`(ctx: <a href='/reference/typescript-api/request/request-context#requestcontext'>RequestContext</a>, input: CreateChannelInput) => Promise&#60;<a href='/reference/typescript-api/errors/error-result-union#errorresultunion'>ErrorResultUnion</a>&#60;CreateChannelResult, <a href='/reference/typescript-api/entities/channel#channel'>Channel</a>&#62;&#62;`}   />
 
 
 
 
 ### update
 ### update
 
 
-<MemberInfo kind="method" type={`(ctx: <a href='/reference/typescript-api/request/request-context#requestcontext'>RequestContext</a>, input: UpdateChannelInput) => Promise&#60;ErrorResultUnion&#60;UpdateChannelResult, <a href='/reference/typescript-api/entities/channel#channel'>Channel</a>&#62;&#62;`}   />
+<MemberInfo kind="method" type={`(ctx: <a href='/reference/typescript-api/request/request-context#requestcontext'>RequestContext</a>, input: UpdateChannelInput) => Promise&#60;<a href='/reference/typescript-api/errors/error-result-union#errorresultunion'>ErrorResultUnion</a>&#60;UpdateChannelResult, <a href='/reference/typescript-api/entities/channel#channel'>Channel</a>&#62;&#62;`}   />
 
 
 
 
 ### delete
 ### delete

+ 1 - 1
docs/docs/reference/typescript-api/services/collection-service.md

@@ -11,7 +11,7 @@ import MemberDescription from '@site/src/components/MemberDescription';
 
 
 ## CollectionService
 ## CollectionService
 
 
-<GenerationInfo sourceFile="packages/core/src/service/services/collection.service.ts" sourceLine="65" packageName="@vendure/core" />
+<GenerationInfo sourceFile="packages/core/src/service/services/collection.service.ts" sourceLine="66" packageName="@vendure/core" />
 
 
 Contains methods relating to <a href='/reference/typescript-api/entities/collection#collection'>Collection</a> entities.
 Contains methods relating to <a href='/reference/typescript-api/entities/collection#collection'>Collection</a> entities.
 
 

+ 29 - 29
docs/docs/reference/typescript-api/services/customer-service.md

@@ -31,8 +31,8 @@ class CustomerService {
     refreshVerificationToken(ctx: RequestContext, emailAddress: string) => Promise<void>;
     refreshVerificationToken(ctx: RequestContext, emailAddress: string) => Promise<void>;
     verifyCustomerEmailAddress(ctx: RequestContext, verificationToken: string, password?: string) => Promise<ErrorResultUnion<VerifyCustomerAccountResult, Customer>>;
     verifyCustomerEmailAddress(ctx: RequestContext, verificationToken: string, password?: string) => Promise<ErrorResultUnion<VerifyCustomerAccountResult, Customer>>;
     requestPasswordReset(ctx: RequestContext, emailAddress: string) => Promise<void>;
     requestPasswordReset(ctx: RequestContext, emailAddress: string) => Promise<void>;
-    resetPassword(ctx: RequestContext, passwordResetToken: string, password: string) => Promise<
-        User | PasswordResetTokenExpiredError | PasswordResetTokenInvalidError | PasswordValidationError
+    resetPassword(ctx: RequestContext, passwordResetToken: string, password: string) => Promise<
+        User | PasswordResetTokenExpiredError | PasswordResetTokenInvalidError | PasswordValidationError
     >;
     >;
     requestUpdateEmailAddress(ctx: RequestContext, userId: ID, newEmailAddress: string) => Promise<boolean | EmailAddressConflictError>;
     requestUpdateEmailAddress(ctx: RequestContext, userId: ID, newEmailAddress: string) => Promise<boolean | EmailAddressConflictError>;
     updateEmailAddress(ctx: RequestContext, token: string) => Promise<boolean | IdentifierChangeTokenInvalidError | IdentifierChangeTokenExpiredError>;
     updateEmailAddress(ctx: RequestContext, token: string) => Promise<boolean | IdentifierChangeTokenInvalidError | IdentifierChangeTokenExpiredError>;
@@ -69,8 +69,8 @@ class CustomerService {
 
 
 <MemberInfo kind="method" type={`(ctx: <a href='/reference/typescript-api/request/request-context#requestcontext'>RequestContext</a>, userId: <a href='/reference/typescript-api/common/id#id'>ID</a>, filterOnChannel:  = true) => Promise&#60;<a href='/reference/typescript-api/entities/customer#customer'>Customer</a> | undefined&#62;`}   />
 <MemberInfo kind="method" type={`(ctx: <a href='/reference/typescript-api/request/request-context#requestcontext'>RequestContext</a>, userId: <a href='/reference/typescript-api/common/id#id'>ID</a>, filterOnChannel:  = true) => Promise&#60;<a href='/reference/typescript-api/entities/customer#customer'>Customer</a> | undefined&#62;`}   />
 
 
-Returns the Customer entity associated with the given userId, if one exists.
-Setting `filterOnChannel` to `true` will limit the results to Customers which are assigned
+Returns the Customer entity associated with the given userId, if one exists.
+Setting `filterOnChannel` to `true` will limit the results to Customers which are assigned
 to the current active Channel only.
 to the current active Channel only.
 ### findAddressesByCustomerId
 ### findAddressesByCustomerId
 
 
@@ -84,15 +84,15 @@ Returns all <a href='/reference/typescript-api/entities/address#address'>Address
 Returns a list of all <a href='/reference/typescript-api/entities/customer-group#customergroup'>CustomerGroup</a> entities.
 Returns a list of all <a href='/reference/typescript-api/entities/customer-group#customergroup'>CustomerGroup</a> entities.
 ### create
 ### create
 
 
-<MemberInfo kind="method" type={`(ctx: <a href='/reference/typescript-api/request/request-context#requestcontext'>RequestContext</a>, input: CreateCustomerInput, password?: string) => Promise&#60;ErrorResultUnion&#60;CreateCustomerResult, <a href='/reference/typescript-api/entities/customer#customer'>Customer</a>&#62;&#62;`}   />
-
-Creates a new Customer, including creation of a new User with the special `customer` Role.
-
-If the `password` argument is specified, the Customer will be immediately verified. If not,
-then an <a href='/reference/typescript-api/events/event-types#accountregistrationevent'>AccountRegistrationEvent</a> is published, so that the customer can have their
-email address verified and set their password in a later step using the `verifyCustomerEmailAddress()`
-method.
+<MemberInfo kind="method" type={`(ctx: <a href='/reference/typescript-api/request/request-context#requestcontext'>RequestContext</a>, input: CreateCustomerInput, password?: string) => Promise&#60;<a href='/reference/typescript-api/errors/error-result-union#errorresultunion'>ErrorResultUnion</a>&#60;CreateCustomerResult, <a href='/reference/typescript-api/entities/customer#customer'>Customer</a>&#62;&#62;`}   />
 
 
+Creates a new Customer, including creation of a new User with the special `customer` Role.
+
+If the `password` argument is specified, the Customer will be immediately verified. If not,
+then an <a href='/reference/typescript-api/events/event-types#accountregistrationevent'>AccountRegistrationEvent</a> is published, so that the customer can have their
+email address verified and set their password in a later step using the `verifyCustomerEmailAddress()`
+method.
+
 This method is intended to be used in admin-created Customer flows.
 This method is intended to be used in admin-created Customer flows.
 ### update
 ### update
 
 
@@ -101,59 +101,59 @@ This method is intended to be used in admin-created Customer flows.
 
 
 ### update
 ### update
 
 
-<MemberInfo kind="method" type={`(ctx: <a href='/reference/typescript-api/request/request-context#requestcontext'>RequestContext</a>, input: UpdateCustomerInput) => Promise&#60;ErrorResultUnion&#60;UpdateCustomerResult, <a href='/reference/typescript-api/entities/customer#customer'>Customer</a>&#62;&#62;`}   />
+<MemberInfo kind="method" type={`(ctx: <a href='/reference/typescript-api/request/request-context#requestcontext'>RequestContext</a>, input: UpdateCustomerInput) => Promise&#60;<a href='/reference/typescript-api/errors/error-result-union#errorresultunion'>ErrorResultUnion</a>&#60;UpdateCustomerResult, <a href='/reference/typescript-api/entities/customer#customer'>Customer</a>&#62;&#62;`}   />
 
 
 
 
 ### update
 ### update
 
 
-<MemberInfo kind="method" type={`(ctx: <a href='/reference/typescript-api/request/request-context#requestcontext'>RequestContext</a>, input: UpdateCustomerInput | (UpdateCustomerShopInput &#38; { id: <a href='/reference/typescript-api/common/id#id'>ID</a> })) => Promise&#60;ErrorResultUnion&#60;UpdateCustomerResult, <a href='/reference/typescript-api/entities/customer#customer'>Customer</a>&#62;&#62;`}   />
+<MemberInfo kind="method" type={`(ctx: <a href='/reference/typescript-api/request/request-context#requestcontext'>RequestContext</a>, input: UpdateCustomerInput | (UpdateCustomerShopInput &#38; { id: <a href='/reference/typescript-api/common/id#id'>ID</a> })) => Promise&#60;<a href='/reference/typescript-api/errors/error-result-union#errorresultunion'>ErrorResultUnion</a>&#60;UpdateCustomerResult, <a href='/reference/typescript-api/entities/customer#customer'>Customer</a>&#62;&#62;`}   />
 
 
 
 
 ### registerCustomerAccount
 ### registerCustomerAccount
 
 
 <MemberInfo kind="method" type={`(ctx: <a href='/reference/typescript-api/request/request-context#requestcontext'>RequestContext</a>, input: RegisterCustomerInput) => Promise&#60;RegisterCustomerAccountResult | EmailAddressConflictError | PasswordValidationError&#62;`}   />
 <MemberInfo kind="method" type={`(ctx: <a href='/reference/typescript-api/request/request-context#requestcontext'>RequestContext</a>, input: RegisterCustomerInput) => Promise&#60;RegisterCustomerAccountResult | EmailAddressConflictError | PasswordValidationError&#62;`}   />
 
 
-Registers a new Customer account with the <a href='/reference/typescript-api/auth/native-authentication-strategy#nativeauthenticationstrategy'>NativeAuthenticationStrategy</a> and starts
-the email verification flow (unless <a href='/reference/typescript-api/auth/auth-options#authoptions'>AuthOptions</a> `requireVerification` is set to `false`)
-by publishing an <a href='/reference/typescript-api/events/event-types#accountregistrationevent'>AccountRegistrationEvent</a>.
-
+Registers a new Customer account with the <a href='/reference/typescript-api/auth/native-authentication-strategy#nativeauthenticationstrategy'>NativeAuthenticationStrategy</a> and starts
+the email verification flow (unless <a href='/reference/typescript-api/auth/auth-options#authoptions'>AuthOptions</a> `requireVerification` is set to `false`)
+by publishing an <a href='/reference/typescript-api/events/event-types#accountregistrationevent'>AccountRegistrationEvent</a>.
+
 This method is intended to be used in storefront Customer-creation flows.
 This method is intended to be used in storefront Customer-creation flows.
 ### refreshVerificationToken
 ### refreshVerificationToken
 
 
 <MemberInfo kind="method" type={`(ctx: <a href='/reference/typescript-api/request/request-context#requestcontext'>RequestContext</a>, emailAddress: string) => Promise&#60;void&#62;`}   />
 <MemberInfo kind="method" type={`(ctx: <a href='/reference/typescript-api/request/request-context#requestcontext'>RequestContext</a>, emailAddress: string) => Promise&#60;void&#62;`}   />
 
 
-Refreshes a stale email address verification token by generating a new one and
+Refreshes a stale email address verification token by generating a new one and
 publishing a <a href='/reference/typescript-api/events/event-types#accountregistrationevent'>AccountRegistrationEvent</a>.
 publishing a <a href='/reference/typescript-api/events/event-types#accountregistrationevent'>AccountRegistrationEvent</a>.
 ### verifyCustomerEmailAddress
 ### verifyCustomerEmailAddress
 
 
-<MemberInfo kind="method" type={`(ctx: <a href='/reference/typescript-api/request/request-context#requestcontext'>RequestContext</a>, verificationToken: string, password?: string) => Promise&#60;ErrorResultUnion&#60;VerifyCustomerAccountResult, <a href='/reference/typescript-api/entities/customer#customer'>Customer</a>&#62;&#62;`}   />
+<MemberInfo kind="method" type={`(ctx: <a href='/reference/typescript-api/request/request-context#requestcontext'>RequestContext</a>, verificationToken: string, password?: string) => Promise&#60;<a href='/reference/typescript-api/errors/error-result-union#errorresultunion'>ErrorResultUnion</a>&#60;VerifyCustomerAccountResult, <a href='/reference/typescript-api/entities/customer#customer'>Customer</a>&#62;&#62;`}   />
 
 
-Given a valid verification token which has been published in an <a href='/reference/typescript-api/events/event-types#accountregistrationevent'>AccountRegistrationEvent</a>, this
+Given a valid verification token which has been published in an <a href='/reference/typescript-api/events/event-types#accountregistrationevent'>AccountRegistrationEvent</a>, this
 method is used to set the Customer as `verified` as part of the account registration flow.
 method is used to set the Customer as `verified` as part of the account registration flow.
 ### requestPasswordReset
 ### requestPasswordReset
 
 
 <MemberInfo kind="method" type={`(ctx: <a href='/reference/typescript-api/request/request-context#requestcontext'>RequestContext</a>, emailAddress: string) => Promise&#60;void&#62;`}   />
 <MemberInfo kind="method" type={`(ctx: <a href='/reference/typescript-api/request/request-context#requestcontext'>RequestContext</a>, emailAddress: string) => Promise&#60;void&#62;`}   />
 
 
-Publishes a new <a href='/reference/typescript-api/events/event-types#passwordresetevent'>PasswordResetEvent</a> for the given email address. This event creates
+Publishes a new <a href='/reference/typescript-api/events/event-types#passwordresetevent'>PasswordResetEvent</a> for the given email address. This event creates
 a token which can be used in the `resetPassword()` method.
 a token which can be used in the `resetPassword()` method.
 ### resetPassword
 ### resetPassword
 
 
-<MemberInfo kind="method" type={`(ctx: <a href='/reference/typescript-api/request/request-context#requestcontext'>RequestContext</a>, passwordResetToken: string, password: string) => Promise&#60;         <a href='/reference/typescript-api/entities/user#user'>User</a> | PasswordResetTokenExpiredError | PasswordResetTokenInvalidError | PasswordValidationError     &#62;`}   />
+<MemberInfo kind="method" type={`(ctx: <a href='/reference/typescript-api/request/request-context#requestcontext'>RequestContext</a>, passwordResetToken: string, password: string) => Promise&#60;
         <a href='/reference/typescript-api/entities/user#user'>User</a> | PasswordResetTokenExpiredError | PasswordResetTokenInvalidError | PasswordValidationError
     &#62;`}   />
 
 
-Given a valid password reset token created by a call to the `requestPasswordReset()` method,
+Given a valid password reset token created by a call to the `requestPasswordReset()` method,
 this method will change the Customer's password to that given as the `password` argument.
 this method will change the Customer's password to that given as the `password` argument.
 ### requestUpdateEmailAddress
 ### requestUpdateEmailAddress
 
 
 <MemberInfo kind="method" type={`(ctx: <a href='/reference/typescript-api/request/request-context#requestcontext'>RequestContext</a>, userId: <a href='/reference/typescript-api/common/id#id'>ID</a>, newEmailAddress: string) => Promise&#60;boolean | EmailAddressConflictError&#62;`}   />
 <MemberInfo kind="method" type={`(ctx: <a href='/reference/typescript-api/request/request-context#requestcontext'>RequestContext</a>, userId: <a href='/reference/typescript-api/common/id#id'>ID</a>, newEmailAddress: string) => Promise&#60;boolean | EmailAddressConflictError&#62;`}   />
 
 
-Publishes a <a href='/reference/typescript-api/events/event-types#identifierchangerequestevent'>IdentifierChangeRequestEvent</a> for the given User. This event contains a token
-which is then used in the `updateEmailAddress()` method to change the email address of the User &
+Publishes a <a href='/reference/typescript-api/events/event-types#identifierchangerequestevent'>IdentifierChangeRequestEvent</a> for the given User. This event contains a token
+which is then used in the `updateEmailAddress()` method to change the email address of the User &
 Customer.
 Customer.
 ### updateEmailAddress
 ### updateEmailAddress
 
 
 <MemberInfo kind="method" type={`(ctx: <a href='/reference/typescript-api/request/request-context#requestcontext'>RequestContext</a>, token: string) => Promise&#60;boolean | IdentifierChangeTokenInvalidError | IdentifierChangeTokenExpiredError&#62;`}   />
 <MemberInfo kind="method" type={`(ctx: <a href='/reference/typescript-api/request/request-context#requestcontext'>RequestContext</a>, token: string) => Promise&#60;boolean | IdentifierChangeTokenInvalidError | IdentifierChangeTokenExpiredError&#62;`}   />
 
 
-Given a valid email update token published in a <a href='/reference/typescript-api/events/event-types#identifierchangerequestevent'>IdentifierChangeRequestEvent</a>, this method
+Given a valid email update token published in a <a href='/reference/typescript-api/events/event-types#identifierchangerequestevent'>IdentifierChangeRequestEvent</a>, this method
 will update the Customer & User email address.
 will update the Customer & User email address.
 ### createOrUpdate
 ### createOrUpdate
 
 
@@ -184,8 +184,8 @@ Creates a new <a href='/reference/typescript-api/entities/address#address'>Addre
 
 
 <MemberInfo kind="method" type={`(ctx: <a href='/reference/typescript-api/request/request-context#requestcontext'>RequestContext</a>, order: <a href='/reference/typescript-api/entities/order#order'>Order</a>) => `}   />
 <MemberInfo kind="method" type={`(ctx: <a href='/reference/typescript-api/request/request-context#requestcontext'>RequestContext</a>, order: <a href='/reference/typescript-api/entities/order#order'>Order</a>) => `}   />
 
 
-If the Customer associated with the given Order does not yet have any Addresses,
-this method will create new Address(es) based on the Order's shipping & billing
+If the Customer associated with the given Order does not yet have any Addresses,
+this method will create new Address(es) based on the Order's shipping & billing
 addresses.
 addresses.
 ### addNoteToCustomer
 ### addNoteToCustomer
 
 

+ 1 - 1
docs/docs/reference/typescript-api/services/facet-service.md

@@ -91,7 +91,7 @@ Returns the Facet which contains the given FacetValue id.
 Assigns Facets to the specified Channel
 Assigns Facets to the specified Channel
 ### removeFacetsFromChannel
 ### removeFacetsFromChannel
 
 
-<MemberInfo kind="method" type={`(ctx: <a href='/reference/typescript-api/request/request-context#requestcontext'>RequestContext</a>, input: RemoveFacetsFromChannelInput) => Promise&#60;Array&#60;ErrorResultUnion&#60;RemoveFacetFromChannelResult, <a href='/reference/typescript-api/entities/facet#facet'>Facet</a>&#62;&#62;&#62;`}   />
+<MemberInfo kind="method" type={`(ctx: <a href='/reference/typescript-api/request/request-context#requestcontext'>RequestContext</a>, input: RemoveFacetsFromChannelInput) => Promise&#60;Array&#60;<a href='/reference/typescript-api/errors/error-result-union#errorresultunion'>ErrorResultUnion</a>&#60;RemoveFacetFromChannelResult, <a href='/reference/typescript-api/entities/facet#facet'>Facet</a>&#62;&#62;&#62;`}   />
 
 
 Remove Facets from the specified Channel
 Remove Facets from the specified Channel
 
 

+ 15 - 15
docs/docs/reference/typescript-api/services/order-service.md

@@ -166,23 +166,23 @@ User's Customer account.
 Updates the custom fields of an Order.
 Updates the custom fields of an Order.
 ### addItemToOrder
 ### addItemToOrder
 
 
-<MemberInfo kind="method" type={`(ctx: <a href='/reference/typescript-api/request/request-context#requestcontext'>RequestContext</a>, orderId: <a href='/reference/typescript-api/common/id#id'>ID</a>, productVariantId: <a href='/reference/typescript-api/common/id#id'>ID</a>, quantity: number, customFields?: { [key: string]: any }) => Promise&#60;ErrorResultUnion&#60;UpdateOrderItemsResult, <a href='/reference/typescript-api/entities/order#order'>Order</a>&#62;&#62;`}   />
+<MemberInfo kind="method" type={`(ctx: <a href='/reference/typescript-api/request/request-context#requestcontext'>RequestContext</a>, orderId: <a href='/reference/typescript-api/common/id#id'>ID</a>, productVariantId: <a href='/reference/typescript-api/common/id#id'>ID</a>, quantity: number, customFields?: { [key: string]: any }) => Promise&#60;<a href='/reference/typescript-api/errors/error-result-union#errorresultunion'>ErrorResultUnion</a>&#60;UpdateOrderItemsResult, <a href='/reference/typescript-api/entities/order#order'>Order</a>&#62;&#62;`}   />
 
 
 Adds an item to the Order, either creating a new OrderLine or
 Adds an item to the Order, either creating a new OrderLine or
 incrementing an existing one.
 incrementing an existing one.
 ### adjustOrderLine
 ### adjustOrderLine
 
 
-<MemberInfo kind="method" type={`(ctx: <a href='/reference/typescript-api/request/request-context#requestcontext'>RequestContext</a>, orderId: <a href='/reference/typescript-api/common/id#id'>ID</a>, orderLineId: <a href='/reference/typescript-api/common/id#id'>ID</a>, quantity: number, customFields?: { [key: string]: any }) => Promise&#60;ErrorResultUnion&#60;UpdateOrderItemsResult, <a href='/reference/typescript-api/entities/order#order'>Order</a>&#62;&#62;`}   />
+<MemberInfo kind="method" type={`(ctx: <a href='/reference/typescript-api/request/request-context#requestcontext'>RequestContext</a>, orderId: <a href='/reference/typescript-api/common/id#id'>ID</a>, orderLineId: <a href='/reference/typescript-api/common/id#id'>ID</a>, quantity: number, customFields?: { [key: string]: any }) => Promise&#60;<a href='/reference/typescript-api/errors/error-result-union#errorresultunion'>ErrorResultUnion</a>&#60;UpdateOrderItemsResult, <a href='/reference/typescript-api/entities/order#order'>Order</a>&#62;&#62;`}   />
 
 
 Adjusts the quantity and/or custom field values of an existing OrderLine.
 Adjusts the quantity and/or custom field values of an existing OrderLine.
 ### removeItemFromOrder
 ### removeItemFromOrder
 
 
-<MemberInfo kind="method" type={`(ctx: <a href='/reference/typescript-api/request/request-context#requestcontext'>RequestContext</a>, orderId: <a href='/reference/typescript-api/common/id#id'>ID</a>, orderLineId: <a href='/reference/typescript-api/common/id#id'>ID</a>) => Promise&#60;ErrorResultUnion&#60;RemoveOrderItemsResult, <a href='/reference/typescript-api/entities/order#order'>Order</a>&#62;&#62;`}   />
+<MemberInfo kind="method" type={`(ctx: <a href='/reference/typescript-api/request/request-context#requestcontext'>RequestContext</a>, orderId: <a href='/reference/typescript-api/common/id#id'>ID</a>, orderLineId: <a href='/reference/typescript-api/common/id#id'>ID</a>) => Promise&#60;<a href='/reference/typescript-api/errors/error-result-union#errorresultunion'>ErrorResultUnion</a>&#60;RemoveOrderItemsResult, <a href='/reference/typescript-api/entities/order#order'>Order</a>&#62;&#62;`}   />
 
 
 Removes the specified OrderLine from the Order.
 Removes the specified OrderLine from the Order.
 ### removeAllItemsFromOrder
 ### removeAllItemsFromOrder
 
 
-<MemberInfo kind="method" type={`(ctx: <a href='/reference/typescript-api/request/request-context#requestcontext'>RequestContext</a>, orderId: <a href='/reference/typescript-api/common/id#id'>ID</a>) => Promise&#60;ErrorResultUnion&#60;RemoveOrderItemsResult, <a href='/reference/typescript-api/entities/order#order'>Order</a>&#62;&#62;`}   />
+<MemberInfo kind="method" type={`(ctx: <a href='/reference/typescript-api/request/request-context#requestcontext'>RequestContext</a>, orderId: <a href='/reference/typescript-api/common/id#id'>ID</a>) => Promise&#60;<a href='/reference/typescript-api/errors/error-result-union#errorresultunion'>ErrorResultUnion</a>&#60;RemoveOrderItemsResult, <a href='/reference/typescript-api/entities/order#order'>Order</a>&#62;&#62;`}   />
 
 
 Removes all OrderLines from the Order.
 Removes all OrderLines from the Order.
 ### addSurchargeToOrder
 ### addSurchargeToOrder
@@ -197,7 +197,7 @@ Adds a <a href='/reference/typescript-api/entities/surcharge#surcharge'>Surcharg
 Removes a <a href='/reference/typescript-api/entities/surcharge#surcharge'>Surcharge</a> from the Order.
 Removes a <a href='/reference/typescript-api/entities/surcharge#surcharge'>Surcharge</a> from the Order.
 ### applyCouponCode
 ### applyCouponCode
 
 
-<MemberInfo kind="method" type={`(ctx: <a href='/reference/typescript-api/request/request-context#requestcontext'>RequestContext</a>, orderId: <a href='/reference/typescript-api/common/id#id'>ID</a>, couponCode: string) => Promise&#60;ErrorResultUnion&#60;ApplyCouponCodeResult, <a href='/reference/typescript-api/entities/order#order'>Order</a>&#62;&#62;`}   />
+<MemberInfo kind="method" type={`(ctx: <a href='/reference/typescript-api/request/request-context#requestcontext'>RequestContext</a>, orderId: <a href='/reference/typescript-api/common/id#id'>ID</a>, couponCode: string) => Promise&#60;<a href='/reference/typescript-api/errors/error-result-union#errorresultunion'>ErrorResultUnion</a>&#60;ApplyCouponCodeResult, <a href='/reference/typescript-api/entities/order#order'>Order</a>&#62;&#62;`}   />
 
 
 Applies a coupon code to the Order, which should be a valid coupon code as specified in the configuration
 Applies a coupon code to the Order, which should be a valid coupon code as specified in the configuration
 of an active <a href='/reference/typescript-api/entities/promotion#promotion'>Promotion</a>.
 of an active <a href='/reference/typescript-api/entities/promotion#promotion'>Promotion</a>.
@@ -242,7 +242,7 @@ The quote also includes a price for each method, as determined by the configured
 Returns an array of quotes stating which <a href='/reference/typescript-api/entities/payment-method#paymentmethod'>PaymentMethod</a>s may be used on this Order.
 Returns an array of quotes stating which <a href='/reference/typescript-api/entities/payment-method#paymentmethod'>PaymentMethod</a>s may be used on this Order.
 ### setShippingMethod
 ### setShippingMethod
 
 
-<MemberInfo kind="method" type={`(ctx: <a href='/reference/typescript-api/request/request-context#requestcontext'>RequestContext</a>, orderId: <a href='/reference/typescript-api/common/id#id'>ID</a>, shippingMethodIds: <a href='/reference/typescript-api/common/id#id'>ID</a>[]) => Promise&#60;ErrorResultUnion&#60;SetOrderShippingMethodResult, <a href='/reference/typescript-api/entities/order#order'>Order</a>&#62;&#62;`}   />
+<MemberInfo kind="method" type={`(ctx: <a href='/reference/typescript-api/request/request-context#requestcontext'>RequestContext</a>, orderId: <a href='/reference/typescript-api/common/id#id'>ID</a>, shippingMethodIds: <a href='/reference/typescript-api/common/id#id'>ID</a>[]) => Promise&#60;<a href='/reference/typescript-api/errors/error-result-union#errorresultunion'>ErrorResultUnion</a>&#60;SetOrderShippingMethodResult, <a href='/reference/typescript-api/entities/order#order'>Order</a>&#62;&#62;`}   />
 
 
 Sets the ShippingMethod to be used on this Order.
 Sets the ShippingMethod to be used on this Order.
 ### transitionToState
 ### transitionToState
@@ -258,7 +258,7 @@ Transitions a Fulfillment to the given state and then transitions the Order stat
 whether all Fulfillments of the Order are shipped or delivered.
 whether all Fulfillments of the Order are shipped or delivered.
 ### modifyOrder
 ### modifyOrder
 
 
-<MemberInfo kind="method" type={`(ctx: <a href='/reference/typescript-api/request/request-context#requestcontext'>RequestContext</a>, input: ModifyOrderInput) => Promise&#60;ErrorResultUnion&#60;ModifyOrderResult, <a href='/reference/typescript-api/entities/order#order'>Order</a>&#62;&#62;`}   />
+<MemberInfo kind="method" type={`(ctx: <a href='/reference/typescript-api/request/request-context#requestcontext'>RequestContext</a>, input: ModifyOrderInput) => Promise&#60;<a href='/reference/typescript-api/errors/error-result-union#errorresultunion'>ErrorResultUnion</a>&#60;ModifyOrderResult, <a href='/reference/typescript-api/entities/order#order'>Order</a>&#62;&#62;`}   />
 
 
 Allows the Order to be modified, which allows several aspects of the Order to be changed:
 Allows the Order to be modified, which allows several aspects of the Order to be changed:
 
 
@@ -273,20 +273,20 @@ Order, except history entry and additional payment actions.
 __Using dryRun option, you must wrap function call in transaction manually.__
 __Using dryRun option, you must wrap function call in transaction manually.__
 ### transitionPaymentToState
 ### transitionPaymentToState
 
 
-<MemberInfo kind="method" type={`(ctx: <a href='/reference/typescript-api/request/request-context#requestcontext'>RequestContext</a>, paymentId: <a href='/reference/typescript-api/common/id#id'>ID</a>, state: <a href='/reference/typescript-api/payment/payment-state#paymentstate'>PaymentState</a>) => Promise&#60;ErrorResultUnion&#60;TransitionPaymentToStateResult, <a href='/reference/typescript-api/entities/payment#payment'>Payment</a>&#62;&#62;`}   />
+<MemberInfo kind="method" type={`(ctx: <a href='/reference/typescript-api/request/request-context#requestcontext'>RequestContext</a>, paymentId: <a href='/reference/typescript-api/common/id#id'>ID</a>, state: <a href='/reference/typescript-api/payment/payment-state#paymentstate'>PaymentState</a>) => Promise&#60;<a href='/reference/typescript-api/errors/error-result-union#errorresultunion'>ErrorResultUnion</a>&#60;TransitionPaymentToStateResult, <a href='/reference/typescript-api/entities/payment#payment'>Payment</a>&#62;&#62;`}   />
 
 
 Transitions the given <a href='/reference/typescript-api/entities/payment#payment'>Payment</a> to a new state. If the order totalWithTax price is then
 Transitions the given <a href='/reference/typescript-api/entities/payment#payment'>Payment</a> to a new state. If the order totalWithTax price is then
 covered by Payments, the Order state will be automatically transitioned to `PaymentSettled`
 covered by Payments, the Order state will be automatically transitioned to `PaymentSettled`
 or `PaymentAuthorized`.
 or `PaymentAuthorized`.
 ### addPaymentToOrder
 ### addPaymentToOrder
 
 
-<MemberInfo kind="method" type={`(ctx: <a href='/reference/typescript-api/request/request-context#requestcontext'>RequestContext</a>, orderId: <a href='/reference/typescript-api/common/id#id'>ID</a>, input: PaymentInput) => Promise&#60;ErrorResultUnion&#60;AddPaymentToOrderResult, <a href='/reference/typescript-api/entities/order#order'>Order</a>&#62;&#62;`}   />
+<MemberInfo kind="method" type={`(ctx: <a href='/reference/typescript-api/request/request-context#requestcontext'>RequestContext</a>, orderId: <a href='/reference/typescript-api/common/id#id'>ID</a>, input: PaymentInput) => Promise&#60;<a href='/reference/typescript-api/errors/error-result-union#errorresultunion'>ErrorResultUnion</a>&#60;AddPaymentToOrderResult, <a href='/reference/typescript-api/entities/order#order'>Order</a>&#62;&#62;`}   />
 
 
 Adds a new Payment to the Order. If the Order totalWithTax is covered by Payments, then the Order
 Adds a new Payment to the Order. If the Order totalWithTax is covered by Payments, then the Order
 state will get automatically transitioned to the `PaymentSettled` or `PaymentAuthorized` state.
 state will get automatically transitioned to the `PaymentSettled` or `PaymentAuthorized` state.
 ### addManualPaymentToOrder
 ### addManualPaymentToOrder
 
 
-<MemberInfo kind="method" type={`(ctx: <a href='/reference/typescript-api/request/request-context#requestcontext'>RequestContext</a>, input: ManualPaymentInput) => Promise&#60;ErrorResultUnion&#60;AddManualPaymentToOrderResult, <a href='/reference/typescript-api/entities/order#order'>Order</a>&#62;&#62;`}   />
+<MemberInfo kind="method" type={`(ctx: <a href='/reference/typescript-api/request/request-context#requestcontext'>RequestContext</a>, input: ManualPaymentInput) => Promise&#60;<a href='/reference/typescript-api/errors/error-result-union#errorresultunion'>ErrorResultUnion</a>&#60;AddManualPaymentToOrderResult, <a href='/reference/typescript-api/entities/order#order'>Order</a>&#62;&#62;`}   />
 
 
 This method is used after modifying an existing completed order using the `modifyOrder()` method. If the modifications
 This method is used after modifying an existing completed order using the `modifyOrder()` method. If the modifications
 cause the order total to increase (such as when adding a new OrderLine), then there will be an outstanding charge to
 cause the order total to increase (such as when adding a new OrderLine), then there will be an outstanding charge to
@@ -296,19 +296,19 @@ This method allows you to add a new Payment and assumes the actual processing ha
 dashboard of your payment provider.
 dashboard of your payment provider.
 ### settlePayment
 ### settlePayment
 
 
-<MemberInfo kind="method" type={`(ctx: <a href='/reference/typescript-api/request/request-context#requestcontext'>RequestContext</a>, paymentId: <a href='/reference/typescript-api/common/id#id'>ID</a>) => Promise&#60;ErrorResultUnion&#60;<a href='/reference/typescript-api/payment/payment-method-types#settlepaymentresult'>SettlePaymentResult</a>, <a href='/reference/typescript-api/entities/payment#payment'>Payment</a>&#62;&#62;`}   />
+<MemberInfo kind="method" type={`(ctx: <a href='/reference/typescript-api/request/request-context#requestcontext'>RequestContext</a>, paymentId: <a href='/reference/typescript-api/common/id#id'>ID</a>) => Promise&#60;<a href='/reference/typescript-api/errors/error-result-union#errorresultunion'>ErrorResultUnion</a>&#60;<a href='/reference/typescript-api/payment/payment-method-types#settlepaymentresult'>SettlePaymentResult</a>, <a href='/reference/typescript-api/entities/payment#payment'>Payment</a>&#62;&#62;`}   />
 
 
 Settles a payment by invoking the <a href='/reference/typescript-api/payment/payment-method-handler#paymentmethodhandler'>PaymentMethodHandler</a>'s `settlePayment()` method. Automatically
 Settles a payment by invoking the <a href='/reference/typescript-api/payment/payment-method-handler#paymentmethodhandler'>PaymentMethodHandler</a>'s `settlePayment()` method. Automatically
 transitions the Order state if all Payments are settled.
 transitions the Order state if all Payments are settled.
 ### cancelPayment
 ### cancelPayment
 
 
-<MemberInfo kind="method" type={`(ctx: <a href='/reference/typescript-api/request/request-context#requestcontext'>RequestContext</a>, paymentId: <a href='/reference/typescript-api/common/id#id'>ID</a>) => Promise&#60;ErrorResultUnion&#60;<a href='/reference/typescript-api/payment/payment-method-types#cancelpaymentresult'>CancelPaymentResult</a>, <a href='/reference/typescript-api/entities/payment#payment'>Payment</a>&#62;&#62;`}   />
+<MemberInfo kind="method" type={`(ctx: <a href='/reference/typescript-api/request/request-context#requestcontext'>RequestContext</a>, paymentId: <a href='/reference/typescript-api/common/id#id'>ID</a>) => Promise&#60;<a href='/reference/typescript-api/errors/error-result-union#errorresultunion'>ErrorResultUnion</a>&#60;<a href='/reference/typescript-api/payment/payment-method-types#cancelpaymentresult'>CancelPaymentResult</a>, <a href='/reference/typescript-api/entities/payment#payment'>Payment</a>&#62;&#62;`}   />
 
 
 Cancels a payment by invoking the <a href='/reference/typescript-api/payment/payment-method-handler#paymentmethodhandler'>PaymentMethodHandler</a>'s `cancelPayment()` method (if defined), and transitions the Payment to
 Cancels a payment by invoking the <a href='/reference/typescript-api/payment/payment-method-handler#paymentmethodhandler'>PaymentMethodHandler</a>'s `cancelPayment()` method (if defined), and transitions the Payment to
 the `Cancelled` state.
 the `Cancelled` state.
 ### createFulfillment
 ### createFulfillment
 
 
-<MemberInfo kind="method" type={`(ctx: <a href='/reference/typescript-api/request/request-context#requestcontext'>RequestContext</a>, input: FulfillOrderInput) => Promise&#60;ErrorResultUnion&#60;AddFulfillmentToOrderResult, <a href='/reference/typescript-api/entities/fulfillment#fulfillment'>Fulfillment</a>&#62;&#62;`}   />
+<MemberInfo kind="method" type={`(ctx: <a href='/reference/typescript-api/request/request-context#requestcontext'>RequestContext</a>, input: FulfillOrderInput) => Promise&#60;<a href='/reference/typescript-api/errors/error-result-union#errorresultunion'>ErrorResultUnion</a>&#60;AddFulfillmentToOrderResult, <a href='/reference/typescript-api/entities/fulfillment#fulfillment'>Fulfillment</a>&#62;&#62;`}   />
 
 
 Creates a new Fulfillment associated with the given Order and OrderItems.
 Creates a new Fulfillment associated with the given Order and OrderItems.
 ### getOrderFulfillments
 ### getOrderFulfillments
@@ -323,13 +323,13 @@ Returns an array of all Fulfillments associated with the Order.
 Returns an array of all Surcharges associated with the Order.
 Returns an array of all Surcharges associated with the Order.
 ### cancelOrder
 ### cancelOrder
 
 
-<MemberInfo kind="method" type={`(ctx: <a href='/reference/typescript-api/request/request-context#requestcontext'>RequestContext</a>, input: CancelOrderInput) => Promise&#60;ErrorResultUnion&#60;CancelOrderResult, <a href='/reference/typescript-api/entities/order#order'>Order</a>&#62;&#62;`}   />
+<MemberInfo kind="method" type={`(ctx: <a href='/reference/typescript-api/request/request-context#requestcontext'>RequestContext</a>, input: CancelOrderInput) => Promise&#60;<a href='/reference/typescript-api/errors/error-result-union#errorresultunion'>ErrorResultUnion</a>&#60;CancelOrderResult, <a href='/reference/typescript-api/entities/order#order'>Order</a>&#62;&#62;`}   />
 
 
 Cancels an Order by transitioning it to the `Cancelled` state. If stock is being tracked for the ProductVariants
 Cancels an Order by transitioning it to the `Cancelled` state. If stock is being tracked for the ProductVariants
 in the Order, then new <a href='/reference/typescript-api/entities/stock-movement#stockmovement'>StockMovement</a>s will be created to correct the stock levels.
 in the Order, then new <a href='/reference/typescript-api/entities/stock-movement#stockmovement'>StockMovement</a>s will be created to correct the stock levels.
 ### refundOrder
 ### refundOrder
 
 
-<MemberInfo kind="method" type={`(ctx: <a href='/reference/typescript-api/request/request-context#requestcontext'>RequestContext</a>, input: RefundOrderInput) => Promise&#60;ErrorResultUnion&#60;RefundOrderResult, Refund&#62;&#62;`}   />
+<MemberInfo kind="method" type={`(ctx: <a href='/reference/typescript-api/request/request-context#requestcontext'>RequestContext</a>, input: RefundOrderInput) => Promise&#60;<a href='/reference/typescript-api/errors/error-result-union#errorresultunion'>ErrorResultUnion</a>&#60;RefundOrderResult, Refund&#62;&#62;`}   />
 
 
 Creates a {@link Refund} against the order and in doing so invokes the `createRefund()` method of the
 Creates a {@link Refund} against the order and in doing so invokes the `createRefund()` method of the
 <a href='/reference/typescript-api/payment/payment-method-handler#paymentmethodhandler'>PaymentMethodHandler</a>.
 <a href='/reference/typescript-api/payment/payment-method-handler#paymentmethodhandler'>PaymentMethodHandler</a>.

+ 4 - 4
docs/docs/reference/typescript-api/services/product-service.md

@@ -11,13 +11,13 @@ import MemberDescription from '@site/src/components/MemberDescription';
 
 
 ## ProductService
 ## ProductService
 
 
-<GenerationInfo sourceFile="packages/core/src/service/services/product.service.ts" sourceLine="57" packageName="@vendure/core" />
+<GenerationInfo sourceFile="packages/core/src/service/services/product.service.ts" sourceLine="53" packageName="@vendure/core" />
 
 
 Contains methods relating to <a href='/reference/typescript-api/entities/product#product'>Product</a> entities.
 Contains methods relating to <a href='/reference/typescript-api/entities/product#product'>Product</a> entities.
 
 
 ```ts title="Signature"
 ```ts title="Signature"
 class ProductService {
 class ProductService {
-    constructor(connection: TransactionalConnection, channelService: ChannelService, roleService: RoleService, assetService: AssetService, productVariantService: ProductVariantService, facetValueService: FacetValueService, taxRateService: TaxRateService, collectionService: CollectionService, listQueryBuilder: ListQueryBuilder, translatableSaver: TranslatableSaver, eventBus: EventBus, slugValidator: SlugValidator, customFieldRelationService: CustomFieldRelationService, translator: TranslatorService, productOptionGroupService: ProductOptionGroupService)
+    constructor(connection: TransactionalConnection, channelService: ChannelService, assetService: AssetService, productVariantService: ProductVariantService, facetValueService: FacetValueService, listQueryBuilder: ListQueryBuilder, translatableSaver: TranslatableSaver, eventBus: EventBus, slugValidator: SlugValidator, customFieldRelationService: CustomFieldRelationService, translator: TranslatorService, productOptionGroupService: ProductOptionGroupService)
     findAll(ctx: RequestContext, options?: ListQueryOptions<Product>, relations?: RelationPaths<Product>) => Promise<PaginatedList<Translated<Product>>>;
     findAll(ctx: RequestContext, options?: ListQueryOptions<Product>, relations?: RelationPaths<Product>) => Promise<PaginatedList<Translated<Product>>>;
     findOne(ctx: RequestContext, productId: ID, relations?: RelationPaths<Product>) => Promise<Translated<Product> | undefined>;
     findOne(ctx: RequestContext, productId: ID, relations?: RelationPaths<Product>) => Promise<Translated<Product> | undefined>;
     findByIds(ctx: RequestContext, productIds: ID[], relations?: RelationPaths<Product>) => Promise<Array<Translated<Product>>>;
     findByIds(ctx: RequestContext, productIds: ID[], relations?: RelationPaths<Product>) => Promise<Array<Translated<Product>>>;
@@ -38,7 +38,7 @@ class ProductService {
 
 
 ### constructor
 ### constructor
 
 
-<MemberInfo kind="method" type={`(connection: <a href='/reference/typescript-api/data-access/transactional-connection#transactionalconnection'>TransactionalConnection</a>, channelService: <a href='/reference/typescript-api/services/channel-service#channelservice'>ChannelService</a>, roleService: <a href='/reference/typescript-api/services/role-service#roleservice'>RoleService</a>, assetService: <a href='/reference/typescript-api/services/asset-service#assetservice'>AssetService</a>, productVariantService: <a href='/reference/typescript-api/services/product-variant-service#productvariantservice'>ProductVariantService</a>, facetValueService: <a href='/reference/typescript-api/services/facet-value-service#facetvalueservice'>FacetValueService</a>, taxRateService: <a href='/reference/typescript-api/services/tax-rate-service#taxrateservice'>TaxRateService</a>, collectionService: <a href='/reference/typescript-api/services/collection-service#collectionservice'>CollectionService</a>, listQueryBuilder: <a href='/reference/typescript-api/data-access/list-query-builder#listquerybuilder'>ListQueryBuilder</a>, translatableSaver: <a href='/reference/typescript-api/service-helpers/translatable-saver#translatablesaver'>TranslatableSaver</a>, eventBus: <a href='/reference/typescript-api/events/event-bus#eventbus'>EventBus</a>, slugValidator: <a href='/reference/typescript-api/service-helpers/slug-validator#slugvalidator'>SlugValidator</a>, customFieldRelationService: CustomFieldRelationService, translator: <a href='/reference/typescript-api/service-helpers/translator-service#translatorservice'>TranslatorService</a>, productOptionGroupService: <a href='/reference/typescript-api/services/product-option-group-service#productoptiongroupservice'>ProductOptionGroupService</a>) => ProductService`}   />
+<MemberInfo kind="method" type={`(connection: <a href='/reference/typescript-api/data-access/transactional-connection#transactionalconnection'>TransactionalConnection</a>, channelService: <a href='/reference/typescript-api/services/channel-service#channelservice'>ChannelService</a>, assetService: <a href='/reference/typescript-api/services/asset-service#assetservice'>AssetService</a>, productVariantService: <a href='/reference/typescript-api/services/product-variant-service#productvariantservice'>ProductVariantService</a>, facetValueService: <a href='/reference/typescript-api/services/facet-value-service#facetvalueservice'>FacetValueService</a>, listQueryBuilder: <a href='/reference/typescript-api/data-access/list-query-builder#listquerybuilder'>ListQueryBuilder</a>, translatableSaver: <a href='/reference/typescript-api/service-helpers/translatable-saver#translatablesaver'>TranslatableSaver</a>, eventBus: <a href='/reference/typescript-api/events/event-bus#eventbus'>EventBus</a>, slugValidator: <a href='/reference/typescript-api/service-helpers/slug-validator#slugvalidator'>SlugValidator</a>, customFieldRelationService: CustomFieldRelationService, translator: <a href='/reference/typescript-api/service-helpers/translator-service#translatorservice'>TranslatorService</a>, productOptionGroupService: <a href='/reference/typescript-api/services/product-option-group-service#productoptiongroupservice'>ProductOptionGroupService</a>) => ProductService`}   />
 
 
 
 
 ### findAll
 ### findAll
@@ -107,7 +107,7 @@ each of the Product's variants, and will assign the Product's Assets to the Chan
 
 
 ### removeOptionGroupFromProduct
 ### removeOptionGroupFromProduct
 
 
-<MemberInfo kind="method" type={`(ctx: <a href='/reference/typescript-api/request/request-context#requestcontext'>RequestContext</a>, productId: <a href='/reference/typescript-api/common/id#id'>ID</a>, optionGroupId: <a href='/reference/typescript-api/common/id#id'>ID</a>, force?: boolean) => Promise&#60;ErrorResultUnion&#60;RemoveOptionGroupFromProductResult, Translated&#60;<a href='/reference/typescript-api/entities/product#product'>Product</a>&#62;&#62;&#62;`}   />
+<MemberInfo kind="method" type={`(ctx: <a href='/reference/typescript-api/request/request-context#requestcontext'>RequestContext</a>, productId: <a href='/reference/typescript-api/common/id#id'>ID</a>, optionGroupId: <a href='/reference/typescript-api/common/id#id'>ID</a>, force?: boolean) => Promise&#60;<a href='/reference/typescript-api/errors/error-result-union#errorresultunion'>ErrorResultUnion</a>&#60;RemoveOptionGroupFromProductResult, Translated&#60;<a href='/reference/typescript-api/entities/product#product'>Product</a>&#62;&#62;&#62;`}   />
 
 
 
 
 
 

+ 2 - 2
docs/docs/reference/typescript-api/services/promotion-service.md

@@ -76,12 +76,12 @@ class PromotionService {
 
 
 ### createPromotion
 ### createPromotion
 
 
-<MemberInfo kind="method" type={`(ctx: <a href='/reference/typescript-api/request/request-context#requestcontext'>RequestContext</a>, input: CreatePromotionInput) => Promise&#60;ErrorResultUnion&#60;CreatePromotionResult, <a href='/reference/typescript-api/entities/promotion#promotion'>Promotion</a>&#62;&#62;`}   />
+<MemberInfo kind="method" type={`(ctx: <a href='/reference/typescript-api/request/request-context#requestcontext'>RequestContext</a>, input: CreatePromotionInput) => Promise&#60;<a href='/reference/typescript-api/errors/error-result-union#errorresultunion'>ErrorResultUnion</a>&#60;CreatePromotionResult, <a href='/reference/typescript-api/entities/promotion#promotion'>Promotion</a>&#62;&#62;`}   />
 
 
 
 
 ### updatePromotion
 ### updatePromotion
 
 
-<MemberInfo kind="method" type={`(ctx: <a href='/reference/typescript-api/request/request-context#requestcontext'>RequestContext</a>, input: UpdatePromotionInput) => Promise&#60;ErrorResultUnion&#60;UpdatePromotionResult, <a href='/reference/typescript-api/entities/promotion#promotion'>Promotion</a>&#62;&#62;`}   />
+<MemberInfo kind="method" type={`(ctx: <a href='/reference/typescript-api/request/request-context#requestcontext'>RequestContext</a>, input: UpdatePromotionInput) => Promise&#60;<a href='/reference/typescript-api/errors/error-result-union#errorresultunion'>ErrorResultUnion</a>&#60;UpdatePromotionResult, <a href='/reference/typescript-api/entities/promotion#promotion'>Promotion</a>&#62;&#62;`}   />
 
 
 
 
 ### softDeletePromotion
 ### softDeletePromotion

+ 1 - 1
docs/docs/reference/typescript-api/services/user-service.md

@@ -88,7 +88,7 @@ Sets the <a href='/reference/typescript-api/entities/authentication-method#nativ
 flow.
 flow.
 ### verifyUserByToken
 ### verifyUserByToken
 
 
-<MemberInfo kind="method" type={`(ctx: <a href='/reference/typescript-api/request/request-context#requestcontext'>RequestContext</a>, verificationToken: string, password?: string) => Promise&#60;ErrorResultUnion&#60;VerifyCustomerAccountResult, <a href='/reference/typescript-api/entities/user#user'>User</a>&#62;&#62;`}   />
+<MemberInfo kind="method" type={`(ctx: <a href='/reference/typescript-api/request/request-context#requestcontext'>RequestContext</a>, verificationToken: string, password?: string) => Promise&#60;<a href='/reference/typescript-api/errors/error-result-union#errorresultunion'>ErrorResultUnion</a>&#60;VerifyCustomerAccountResult, <a href='/reference/typescript-api/entities/user#user'>User</a>&#62;&#62;`}   />
 
 
 Verifies a verificationToken by looking for a User which has previously had it set using the
 Verifies a verificationToken by looking for a User which has previously had it set using the
 `setVerificationToken()` method, and checks that the token is valid and has not expired.
 `setVerificationToken()` method, and checks that the token is valid and has not expired.

+ 12 - 0
docs/sidebars.js

@@ -267,6 +267,18 @@ const sidebars = {
                     link: { type: 'doc', id: 'reference/core-plugins/payments-plugin/index' },
                     link: { type: 'doc', id: 'reference/core-plugins/payments-plugin/index' },
                     items: [{ type: 'autogenerated', dirName: 'reference/core-plugins/payments-plugin' }],
                     items: [{ type: 'autogenerated', dirName: 'reference/core-plugins/payments-plugin' }],
                 },
                 },
+                {
+                    type: 'category',
+                    label: 'SentryPlugin',
+                    link: { type: 'doc', id: 'reference/core-plugins/sentry-plugin/index' },
+                    items: [{ type: 'autogenerated', dirName: 'reference/core-plugins/sentry-plugin' }],
+                },
+                {
+                    type: 'category',
+                    label: 'StellatePlugin',
+                    link: { type: 'doc', id: 'reference/core-plugins/stellate-plugin/index' },
+                    items: [{ type: 'autogenerated', dirName: 'reference/core-plugins/stellate-plugin' }],
+                },
             ],
             ],
         },
         },
         {
         {

+ 1 - 1
lerna.json

@@ -1,6 +1,6 @@
 {
 {
     "packages": ["packages/*"],
     "packages": ["packages/*"],
-    "version": "2.1.4",
+    "version": "2.1.5",
     "npmClient": "yarn",
     "npmClient": "yarn",
     "command": {
     "command": {
         "version": {
         "version": {

+ 1 - 2
package.json

@@ -12,8 +12,7 @@
     "format": "prettier --write --html-whitespace-sensitivity ignore",
     "format": "prettier --write --html-whitespace-sensitivity ignore",
     "docs:generate-typescript-docs": "ts-node scripts/docs/generate-typescript-docs.ts",
     "docs:generate-typescript-docs": "ts-node scripts/docs/generate-typescript-docs.ts",
     "docs:generate-graphql-docs": "ts-node scripts/docs/generate-graphql-docs.ts --api=shop && ts-node scripts/docs/generate-graphql-docs.ts --api=admin",
     "docs:generate-graphql-docs": "ts-node scripts/docs/generate-graphql-docs.ts --api=shop && ts-node scripts/docs/generate-graphql-docs.ts --api=admin",
-    "docs:update-build-info": "ts-node scripts/docs/update-build-info.ts",
-    "docs:build": "yarn docs:generate-graphql-docs && yarn docs:generate-typescript-docs && yarn docs:update-build-info",
+    "docs:build": "yarn docs:generate-graphql-docs && yarn docs:generate-typescript-docs",
     "codegen": "tsc -p scripts/codegen/plugins && ts-node scripts/codegen/generate-graphql-types.ts",
     "codegen": "tsc -p scripts/codegen/plugins && ts-node scripts/codegen/generate-graphql-types.ts",
     "version": "yarn check-imports && yarn check-angular-versions && yarn build && yarn check-core-type-defs && yarn generate-changelog && git add CHANGELOG* && git add */version.ts",
     "version": "yarn check-imports && yarn check-angular-versions && yarn build && yarn check-core-type-defs && yarn generate-changelog && git add CHANGELOG* && git add */version.ts",
     "dev-server:start": "cd packages/dev-server && yarn start",
     "dev-server:start": "cd packages/dev-server && yarn start",

+ 3 - 3
packages/admin-ui-plugin/package.json

@@ -1,6 +1,6 @@
 {
 {
     "name": "@vendure/admin-ui-plugin",
     "name": "@vendure/admin-ui-plugin",
-    "version": "2.1.4",
+    "version": "2.1.5",
     "main": "lib/index.js",
     "main": "lib/index.js",
     "types": "lib/index.d.ts",
     "types": "lib/index.d.ts",
     "files": [
     "files": [
@@ -21,8 +21,8 @@
     "devDependencies": {
     "devDependencies": {
         "@types/express": "^4.17.8",
         "@types/express": "^4.17.8",
         "@types/fs-extra": "^9.0.1",
         "@types/fs-extra": "^9.0.1",
-        "@vendure/common": "^2.1.4",
-        "@vendure/core": "^2.1.4",
+        "@vendure/common": "^2.1.5",
+        "@vendure/core": "^2.1.5",
         "express": "^4.17.1",
         "express": "^4.17.1",
         "rimraf": "^3.0.2",
         "rimraf": "^3.0.2",
         "typescript": "4.9.5"
         "typescript": "4.9.5"

+ 1 - 1
packages/admin-ui/package-lock.json

@@ -1,6 +1,6 @@
 {
 {
     "name": "@vendure/admin-ui",
     "name": "@vendure/admin-ui",
-    "version": "2.1.4",
+    "version": "2.1.5",
     "lockfileVersion": 1,
     "lockfileVersion": 1,
     "requires": true,
     "requires": true,
     "dependencies": {
     "dependencies": {

+ 2 - 2
packages/admin-ui/package.json

@@ -1,6 +1,6 @@
 {
 {
     "name": "@vendure/admin-ui",
     "name": "@vendure/admin-ui",
-    "version": "2.1.4",
+    "version": "2.1.5",
     "license": "MIT",
     "license": "MIT",
     "scripts": {
     "scripts": {
         "ng": "ng",
         "ng": "ng",
@@ -49,7 +49,7 @@
         "@ng-select/ng-select": "^11.1.1",
         "@ng-select/ng-select": "^11.1.1",
         "@ngx-translate/core": "^15.0.0",
         "@ngx-translate/core": "^15.0.0",
         "@ngx-translate/http-loader": "^8.0.0",
         "@ngx-translate/http-loader": "^8.0.0",
-        "@vendure/common": "^2.1.4",
+        "@vendure/common": "^2.1.5",
         "@webcomponents/custom-elements": "^1.6.0",
         "@webcomponents/custom-elements": "^1.6.0",
         "apollo-angular": "^5.0.0",
         "apollo-angular": "^5.0.0",
         "apollo-upload-client": "^17.0.0",
         "apollo-upload-client": "^17.0.0",

+ 1 - 1
packages/admin-ui/src/lib/core/src/common/version.ts

@@ -1,2 +1,2 @@
 // Auto-generated by the set-version.js script.
 // Auto-generated by the set-version.js script.
-export const ADMIN_UI_VERSION = '2.1.4';
+export const ADMIN_UI_VERSION = '2.1.5';

+ 36 - 34
packages/admin-ui/src/lib/core/src/shared/components/asset-preview/asset-preview.component.html

@@ -107,42 +107,44 @@
         </vdr-card>
         </vdr-card>
     </vdr-page-detail-sidebar>
     </vdr-page-detail-sidebar>
     <div class="carousel-container">
     <div class="carousel-container">
-    <button *ngIf="showSlideButtons" (click)="previousImage()" [class.disabled]="disablePreviousButton"><clr-icon shape="caret left" class="color-weight-800"></clr-icon
-        ></button>
-    <div class="preview-image" #previewDiv [class.centered]="centered">
-        <div class="image-wrapper">
-            <vdr-focal-point-control
-                [width]="width"
-                [height]="height"
-                [fpx]="fpx"
-                [fpy]="fpy"
-                [editable]="settingFocalPoint"
-                (focalPointChange)="onFocalPointChange($event)"
-            >
-                <img
-                    class="asset-image"
-                    [src]="asset | assetPreview : size"
-                    [ngClass]="size"
-                    #imageElement
-                    (load)="onImageLoad()"
-                />
-            </vdr-focal-point-control>
-            <div class="focal-point-info" *ngIf="settingFocalPoint">
-                <button class="icon-button" (click)="setFocalPointCancel()">
-                    <clr-icon shape="times"></clr-icon>
-                </button>
-                <button
-                    class="btn btn-primary btn-sm"
-                    (click)="setFocalPointEnd()"
-                    [disabled]="!lastFocalPoint"
+        <button *ngIf="showSlideButtons" (click)="previousImage()" class="carousel-button" [class.disabled]="disablePreviousButton">
+            <clr-icon shape="caret left" class="color-weight-800"></clr-icon>
+        </button>
+        <div class="preview-image" #previewDiv [class.centered]="centered">
+            <div class="image-wrapper">
+                <vdr-focal-point-control
+                    [width]="width"
+                    [height]="height"
+                    [fpx]="fpx"
+                    [fpy]="fpy"
+                    [editable]="settingFocalPoint"
+                    (focalPointChange)="onFocalPointChange($event)"
                 >
                 >
-                    <clr-icon shape="crosshairs"></clr-icon>
-                    {{ 'asset.set-focal-point' | translate }}
-                </button>
+                    <img
+                        class="asset-image"
+                        [src]="asset | assetPreview : size"
+                        [ngClass]="size"
+                        #imageElement
+                        (load)="onImageLoad()"
+                    />
+                </vdr-focal-point-control>
+                <div class="focal-point-info" *ngIf="settingFocalPoint">
+                    <button class="icon-button" (click)="setFocalPointCancel()">
+                        <clr-icon shape="times"></clr-icon>
+                    </button>
+                    <button
+                        class="btn btn-primary btn-sm"
+                        (click)="setFocalPointEnd()"
+                        [disabled]="!lastFocalPoint"
+                    >
+                        <clr-icon shape="crosshairs"></clr-icon>
+                        {{ 'asset.set-focal-point' | translate }}
+                    </button>
+                </div>
             </div>
             </div>
         </div>
         </div>
-    </div>
-    <button  *ngIf="showSlideButtons" (click)="nextImage()" [class.disabled]="disableNextButton"><clr-icon shape="caret right" class="color-weight-800"></clr-icon
-        ></button>
+        <button *ngIf="showSlideButtons" (click)="nextImage()" class="carousel-button" [class.disabled]="disableNextButton">
+            <clr-icon shape="caret right" class="color-weight-800"></clr-icon>
+        </button>
     </div>
     </div>
 </vdr-page-detail-layout>
 </vdr-page-detail-layout>

+ 1 - 1
packages/admin-ui/src/lib/core/src/shared/components/asset-preview/asset-preview.component.scss

@@ -96,7 +96,7 @@
     justify-content: space-between;
     justify-content: space-between;
     align-items: center;
     align-items: center;
 
 
-    button{
+    button.carousel-button {
         cursor: pointer;
         cursor: pointer;
         width: 30px;
         width: 30px;
         height: 30px;
         height: 30px;

+ 3 - 3
packages/asset-server-plugin/package.json

@@ -1,6 +1,6 @@
 {
 {
     "name": "@vendure/asset-server-plugin",
     "name": "@vendure/asset-server-plugin",
-    "version": "2.1.4",
+    "version": "2.1.5",
     "main": "lib/index.js",
     "main": "lib/index.js",
     "types": "lib/index.d.ts",
     "types": "lib/index.d.ts",
     "files": [
     "files": [
@@ -27,8 +27,8 @@
         "@types/fs-extra": "^11.0.1",
         "@types/fs-extra": "^11.0.1",
         "@types/node-fetch": "^2.5.8",
         "@types/node-fetch": "^2.5.8",
         "@types/sharp": "^0.30.4",
         "@types/sharp": "^0.30.4",
-        "@vendure/common": "^2.1.4",
-        "@vendure/core": "^2.1.4",
+        "@vendure/common": "^2.1.5",
+        "@vendure/core": "^2.1.5",
         "express": "^4.17.1",
         "express": "^4.17.1",
         "node-fetch": "^2.6.7",
         "node-fetch": "^2.6.7",
         "rimraf": "^3.0.2",
         "rimraf": "^3.0.2",

+ 2 - 2
packages/cli/package.json

@@ -1,6 +1,6 @@
 {
 {
     "name": "@vendure/cli",
     "name": "@vendure/cli",
-    "version": "2.1.4",
+    "version": "2.1.5",
     "description": "A modern, headless ecommerce framework",
     "description": "A modern, headless ecommerce framework",
     "repository": {
     "repository": {
         "type": "git",
         "type": "git",
@@ -34,7 +34,7 @@
     ],
     ],
     "dependencies": {
     "dependencies": {
         "@clack/prompts": "^0.7.0",
         "@clack/prompts": "^0.7.0",
-        "@vendure/common": "^2.1.4",
+        "@vendure/common": "^2.1.5",
         "change-case": "^4.1.2",
         "change-case": "^4.1.2",
         "commander": "^11.0.0",
         "commander": "^11.0.0",
         "fs-extra": "^11.1.1",
         "fs-extra": "^11.1.1",

+ 1 - 1
packages/common/package.json

@@ -1,6 +1,6 @@
 {
 {
     "name": "@vendure/common",
     "name": "@vendure/common",
-    "version": "2.1.4",
+    "version": "2.1.5",
     "main": "index.js",
     "main": "index.js",
     "license": "MIT",
     "license": "MIT",
     "scripts": {
     "scripts": {

+ 1 - 1
packages/common/src/shared-types.ts

@@ -88,7 +88,7 @@ export type ID = string | number;
  * string       | varchar                               | String
  * string       | varchar                               | String
  * localeString | varchar                               | String
  * localeString | varchar                               | String
  * text         | longtext(m), text(p,s)                | String
  * text         | longtext(m), text(p,s)                | String
- * localText    | longtext(m), text(p,s)                | String
+ * localeText    | longtext(m), text(p,s)                | String
  * int          | int                                   | Int
  * int          | int                                   | Int
  * float        | double precision                      | Float
  * float        | double precision                      | Float
  * boolean      | tinyint (m), bool (p), boolean (s)    | Boolean
  * boolean      | tinyint (m), bool (p), boolean (s)    | Boolean

+ 42 - 3
packages/core/e2e/fixtures/test-plugins/list-query-plugin.ts

@@ -39,9 +39,25 @@ export class CustomFieldRelationTestEntity extends VendureEntity {
     parent: Relation<TestEntity>;
     parent: Relation<TestEntity>;
 }
 }
 
 
+@Entity()
+export class CustomFieldOtherRelationTestEntity extends VendureEntity {
+    constructor(input: Partial<CustomFieldOtherRelationTestEntity>) {
+        super(input);
+    }
+
+    @Column()
+    data: string;
+
+    @ManyToOne(() => TestEntity)
+    parent: Relation<TestEntity>;
+}
+
 class TestEntityCustomFields {
 class TestEntityCustomFields {
     @OneToMany(() => CustomFieldRelationTestEntity, child => child.parent)
     @OneToMany(() => CustomFieldRelationTestEntity, child => child.parent)
     relation: Relation<CustomFieldRelationTestEntity[]>;
     relation: Relation<CustomFieldRelationTestEntity[]>;
+
+    @OneToMany(() => CustomFieldOtherRelationTestEntity, child => child.parent)
+    otherRelation: Relation<CustomFieldOtherRelationTestEntity[]>;
 }
 }
 
 
 @Entity()
 @Entity()
@@ -143,7 +159,7 @@ export class TestEntityTranslation extends VendureEntity implements Translation<
     @ManyToOne(type => TestEntity, base => base.translations)
     @ManyToOne(type => TestEntity, base => base.translations)
     base: TestEntity;
     base: TestEntity;
 
 
-    customFields: {};
+    customFields: never;
 }
 }
 
 
 @Entity()
 @Entity()
@@ -171,7 +187,12 @@ export class ListQueryResolver {
         return this.listQueryBuilder
         return this.listQueryBuilder
             .build(TestEntity, args.options, {
             .build(TestEntity, args.options, {
                 ctx,
                 ctx,
-                relations: ['orderRelation', 'orderRelation.customer', 'customFields.relation'],
+                relations: [
+                    'orderRelation',
+                    'orderRelation.customer',
+                    'customFields.relation',
+                    'customFields.otherRelation',
+                ],
                 customPropertyMap: {
                 customPropertyMap: {
                     customerLastName: 'orderRelation.customer.lastName',
                     customerLastName: 'orderRelation.customer.lastName',
                 },
                 },
@@ -222,8 +243,14 @@ const apiExtensions = gql`
         data: String!
         data: String!
     }
     }
 
 
+    type CustomFieldOtherRelationTestEntity implements Node {
+        id: ID!
+        data: String!
+    }
+
     type TestEntityCustomFields {
     type TestEntityCustomFields {
         relation: [CustomFieldRelationTestEntity!]!
         relation: [CustomFieldRelationTestEntity!]!
+        otherRelation: [CustomFieldOtherRelationTestEntity!]!
     }
     }
 
 
     type TestEntity implements Node {
     type TestEntity implements Node {
@@ -272,7 +299,13 @@ const apiExtensions = gql`
 
 
 @VendurePlugin({
 @VendurePlugin({
     imports: [PluginCommonModule],
     imports: [PluginCommonModule],
-    entities: [TestEntity, TestEntityPrice, TestEntityTranslation, CustomFieldRelationTestEntity],
+    entities: [
+        TestEntity,
+        TestEntityPrice,
+        TestEntityTranslation,
+        CustomFieldRelationTestEntity,
+        CustomFieldOtherRelationTestEntity,
+    ],
     adminApiExtensions: {
     adminApiExtensions: {
         schema: apiExtensions,
         schema: apiExtensions,
         resolvers: [ListQueryResolver],
         resolvers: [ListQueryResolver],
@@ -409,6 +442,12 @@ export class ListQueryPlugin implements OnApplicationBootstrap {
                                 data: nestedContent.data,
                                 data: nestedContent.data,
                             }),
                             }),
                         );
                         );
+                        await this.connection.getRepository(CustomFieldOtherRelationTestEntity).save(
+                            new CustomFieldOtherRelationTestEntity({
+                                parent: testEntity,
+                                data: nestedContent.data,
+                            }),
+                        );
                     }
                     }
                 }
                 }
             }
             }

+ 42 - 0
packages/core/e2e/list-query-builder.e2e-spec.ts

@@ -1268,6 +1268,27 @@ describe('ListQueryBuilder', () => {
                 },
                 },
             ]);
             ]);
         });
         });
+
+        it('should resolve multiple relations in customFields successfully', async () => {
+            const { testEntities } = await shopClient.query(GET_LIST_WITH_MULTIPLE_CUSTOM_FIELD_RELATION, {
+                options: {
+                    filter: {
+                        label: { eq: 'A' },
+                    },
+                },
+            });
+
+            expect(testEntities.items).toEqual([
+                {
+                    id: 'T_1',
+                    label: 'A',
+                    customFields: {
+                        relation: [{ id: 'T_1', data: 'A' }],
+                        otherRelation: [{ id: 'T_1', data: 'A' }],
+                    },
+                },
+            ]);
+        });
     });
     });
 });
 });
 
 
@@ -1351,3 +1372,24 @@ const GET_LIST_WITH_CUSTOM_FIELD_RELATION = gql`
         }
         }
     }
     }
 `;
 `;
+
+const GET_LIST_WITH_MULTIPLE_CUSTOM_FIELD_RELATION = gql`
+    query GetTestWithMultipleCustomFieldRelation($options: TestEntityListOptions) {
+        testEntities(options: $options) {
+            items {
+                id
+                label
+                customFields {
+                    relation {
+                        id
+                        data
+                    }
+                    otherRelation {
+                        id
+                        data
+                    }
+                }
+            }
+        }
+    }
+`;

+ 2 - 2
packages/core/package.json

@@ -1,6 +1,6 @@
 {
 {
     "name": "@vendure/core",
     "name": "@vendure/core",
-    "version": "2.1.4",
+    "version": "2.1.5",
     "description": "A modern, headless ecommerce framework",
     "description": "A modern, headless ecommerce framework",
     "repository": {
     "repository": {
         "type": "git",
         "type": "git",
@@ -50,7 +50,7 @@
         "@nestjs/testing": "10.2.1",
         "@nestjs/testing": "10.2.1",
         "@nestjs/typeorm": "10.0.0",
         "@nestjs/typeorm": "10.0.0",
         "@types/fs-extra": "^9.0.1",
         "@types/fs-extra": "^9.0.1",
-        "@vendure/common": "^2.1.4",
+        "@vendure/common": "^2.1.5",
         "bcrypt": "^5.1.1",
         "bcrypt": "^5.1.1",
         "body-parser": "^1.20.2",
         "body-parser": "^1.20.2",
         "chalk": "^4.1.2",
         "chalk": "^4.1.2",

+ 23 - 1
packages/core/src/common/error/error-result.ts

@@ -37,6 +37,9 @@ export type JustErrorResults<T extends GraphQLErrorResult | U, U = any> = Exclud
  * type UpdateOrderItemsResult = Order | OrderModificationError | OrderLimitError | NegativeQuantityError;
  * type UpdateOrderItemsResult = Order | OrderModificationError | OrderLimitError | NegativeQuantityError;
  * type T1 = ErrorResultUnion<UpdateOrderItemsResult, VendureEntityOrder>;
  * type T1 = ErrorResultUnion<UpdateOrderItemsResult, VendureEntityOrder>;
  * // T1 = VendureEntityOrder | OrderModificationError | OrderLimitError | NegativeQuantityError;
  * // T1 = VendureEntityOrder | OrderModificationError | OrderLimitError | NegativeQuantityError;
+ * ```
+ *
+ * @docsCategory errors
  */
  */
 export type ErrorResultUnion<T extends GraphQLErrorResult | U, E extends VendureEntity, U = any> =
 export type ErrorResultUnion<T extends GraphQLErrorResult | U, E extends VendureEntity, U = any> =
     | JustErrorResults<T>
     | JustErrorResults<T>
@@ -44,7 +47,26 @@ export type ErrorResultUnion<T extends GraphQLErrorResult | U, E extends Vendure
 
 
 /**
 /**
  * @description
  * @description
- * Returns true if the ErrorResultUnion is actually an ErrorResult type.
+ * Returns true if the {@link ErrorResultUnion} is actually an ErrorResult type. This is useful when dealing with
+ * certain internal service method that return an ErrorResultUnion.
+ *
+ * @example
+ * ```ts
+ * import { isGraphQlErrorResult } from '\@vendure/core';
+ *
+ * // ...
+ *
+ * const transitionResult = await this.orderService.transitionToState(ctx, order.id, newState);
+ * if (isGraphQlErrorResult(transitionResult)) {
+ *     // The transition failed with an ErrorResult
+ *     throw transitionResult;
+ * } else {
+ *     // TypeScript will correctly infer the type of `transitionResult` to be `Order`
+ *     return transitionResult;
+ * }
+ * ```
+ *
+ * @docsCategory errors
  */
  */
 export function isGraphQlErrorResult<T extends GraphQLErrorResult | U, U = any>(
 export function isGraphQlErrorResult<T extends GraphQLErrorResult | U, U = any>(
     input: T,
     input: T,

+ 5 - 2
packages/core/src/config/order/default-order-process.ts

@@ -413,10 +413,13 @@ export function configureDefaultOrderProcess(options: DefaultOrderProcessOptions
                     order.active = false;
                     order.active = false;
                     order.orderPlacedAt = new Date();
                     order.orderPlacedAt = new Date();
                     await Promise.all(
                     await Promise.all(
-                        order.lines.map(line =>
+                        order.lines.map(line => {
+                            line.orderPlacedQuantity = line.quantity
                             connection
                             connection
                                 .getRepository(ctx, OrderLine)
                                 .getRepository(ctx, OrderLine)
-                                .update(line.id, { orderPlacedQuantity: line.quantity }),
+                                .update(line.id, { orderPlacedQuantity: line.quantity })
+                            return line
+                        }
                         ),
                         ),
                     );
                     );
                     eventBus.publish(new OrderPlacedEvent(fromState, toState, ctx, order));
                     eventBus.publish(new OrderPlacedEvent(fromState, toState, ctx, order));

+ 69 - 0
packages/core/src/entity/base/base.entity.spec.ts

@@ -0,0 +1,69 @@
+import { DeepPartial } from '@vendure/common/lib/shared-types';
+import { describe, expect, it } from 'vitest';
+
+import { Calculated } from '../../common/index';
+import { CalculatedPropertySubscriber } from '../subscribers';
+
+import { VendureEntity } from './base.entity';
+
+class ChildEntity extends VendureEntity {
+    constructor(input?: DeepPartial<ChildEntity>) {
+        super(input);
+    }
+
+    name: string;
+
+    get nameLoud(): string {
+        return this.name.toUpperCase();
+    }
+}
+
+class ChildEntityWithCalculated extends VendureEntity {
+    constructor(input?: DeepPartial<ChildEntity>) {
+        super(input);
+    }
+
+    name: string;
+
+    @Calculated()
+    get nameLoudCalculated(): string {
+        return this.name.toUpperCase();
+    }
+}
+
+describe('VendureEntity', () => {
+    it('instantiating a child entity', () => {
+        const child = new ChildEntity({
+            name: 'foo',
+        });
+
+        expect(child.name).toBe('foo');
+        expect(child.nameLoud).toBe('FOO');
+    });
+
+    it('instantiating from existing entity with getter', () => {
+        const child1 = new ChildEntity({
+            name: 'foo',
+        });
+
+        const child2 = new ChildEntity(child1);
+
+        expect(child2.name).toBe('foo');
+        expect(child2.nameLoud).toBe('FOO');
+    });
+
+    it('instantiating from existing entity with calculated getter', () => {
+        const calculatedPropertySubscriber = new CalculatedPropertySubscriber();
+        const child1 = new ChildEntityWithCalculated({
+            name: 'foo',
+        });
+
+        // This is what happens to entities after being loaded from the DB
+        calculatedPropertySubscriber.afterLoad(child1);
+
+        const child2 = new ChildEntityWithCalculated(child1);
+
+        expect(child2.name).toBe('foo');
+        expect(child2.nameLoudCalculated).toBe('FOO');
+    });
+});

+ 8 - 2
packages/core/src/entity/base/base.entity.ts

@@ -13,8 +13,14 @@ import { PrimaryGeneratedId } from '../entity-id.decorator';
 export abstract class VendureEntity {
 export abstract class VendureEntity {
     protected constructor(input?: DeepPartial<VendureEntity>) {
     protected constructor(input?: DeepPartial<VendureEntity>) {
         if (input) {
         if (input) {
-            for (const [key, value] of Object.entries(input)) {
-                (this as any)[key] = value;
+            for (const [key, descriptor] of Object.entries(Object.getOwnPropertyDescriptors(input))) {
+                if (descriptor.get && !descriptor.set) {
+                    // A getter has been moved to the entity instance
+                    // by the CalculatedPropertySubscriber
+                    // and cannot be copied over to the new instance.
+                    continue;
+                }
+                (this as any)[key] = descriptor.value;
             }
             }
         }
         }
     }
     }

+ 1 - 0
packages/core/src/event-bus/index.ts

@@ -1,6 +1,7 @@
 export * from './event-bus';
 export * from './event-bus';
 export * from './event-bus.module';
 export * from './event-bus.module';
 export * from './vendure-event';
 export * from './vendure-event';
+export * from './vendure-entity-event';
 
 
 export * from './events/account-registration-event';
 export * from './events/account-registration-event';
 export * from './events/account-verified-event';
 export * from './events/account-verified-event';

+ 0 - 1
packages/core/src/i18n/messages/en.json

@@ -6,7 +6,6 @@
     "cannot-delete-sole-superadmin": "The sole SuperAdmin cannot be deleted",
     "cannot-delete-sole-superadmin": "The sole SuperAdmin cannot be deleted",
     "cannot-locate-customer-for-user": "Cannot locate a Customer for the user",
     "cannot-locate-customer-for-user": "Cannot locate a Customer for the user",
     "cannot-modify-role": "The role \"{ roleCode }\" cannot be modified",
     "cannot-modify-role": "The role \"{ roleCode }\" cannot be modified",
-    "cannot-create-sales-for-active-order": "Cannot create a Sale for an Order which is still active",
     "cannot-move-collection-into-self": "Cannot move a Collection into itself",
     "cannot-move-collection-into-self": "Cannot move a Collection into itself",
     "cannot-transition-payment-from-to": "Cannot transition Payment from \"{ fromState }\" to \"{ toState }\"",
     "cannot-transition-payment-from-to": "Cannot transition Payment from \"{ fromState }\" to \"{ toState }\"",
     "cannot-transition-refund-from-to": "Cannot transition Refund from \"{ fromState }\" to \"{ toState }\"",
     "cannot-transition-refund-from-to": "Cannot transition Refund from \"{ fromState }\" to \"{ toState }\"",

+ 10 - 3
packages/core/src/service/helpers/list-query-builder/list-query-builder.ts

@@ -493,14 +493,21 @@ export class ListQueryBuilder implements OnApplicationBootstrap {
                         loadEagerRelations: true,
                         loadEagerRelations: true,
                     } as FindManyOptions<T>)
                     } as FindManyOptions<T>)
                     .then(results =>
                     .then(results =>
-                        results.map(r => ({ relation: relationPaths[0] as keyof T, entity: r })),
+                        results.map(r => ({
+                            relations: relationPaths[0].startsWith('customFields.')
+                                ? relationPaths
+                                : [relationPaths[0]],
+                            entity: r,
+                        })),
                     );
                     );
             }),
             }),
         ).then(all => all.flat());
         ).then(all => all.flat());
         for (const entry of entitiesIdsWithRelations) {
         for (const entry of entitiesIdsWithRelations) {
             const finalEntity = entityMap.get(entry.entity.id);
             const finalEntity = entityMap.get(entry.entity.id);
-            if (finalEntity) {
-                this.assignDeep(entry.relation, entry.entity, finalEntity);
+            for (const relation of entry.relations) {
+                if (finalEntity) {
+                    this.assignDeep(relation, entry.entity, finalEntity);
+                }
             }
             }
         }
         }
         return Array.from(entityMap.values());
         return Array.from(entityMap.values());

+ 4 - 2
packages/core/src/service/services/order.service.ts

@@ -588,8 +588,9 @@ export class OrderService {
         let updatedOrderLines = [orderLine];
         let updatedOrderLines = [orderLine];
         if (correctedQuantity === 0) {
         if (correctedQuantity === 0) {
             order.lines = order.lines.filter(l => !idsAreEqual(l.id, orderLine.id));
             order.lines = order.lines.filter(l => !idsAreEqual(l.id, orderLine.id));
+            const deletedOrderLine = new OrderLine(orderLine);
             await this.connection.getRepository(ctx, OrderLine).remove(orderLine);
             await this.connection.getRepository(ctx, OrderLine).remove(orderLine);
-            this.eventBus.publish(new OrderLineEvent(ctx, order, orderLine, 'deleted'));
+            this.eventBus.publish(new OrderLineEvent(ctx, order, deletedOrderLine, 'deleted'));
             updatedOrderLines = [];
             updatedOrderLines = [];
         } else {
         } else {
             await this.orderModifier.updateOrderLineQuantity(ctx, orderLine, correctedQuantity, order);
             await this.orderModifier.updateOrderLineQuantity(ctx, orderLine, correctedQuantity, order);
@@ -620,8 +621,9 @@ export class OrderService {
         const orderLine = this.getOrderLineOrThrow(order, orderLineId);
         const orderLine = this.getOrderLineOrThrow(order, orderLineId);
         order.lines = order.lines.filter(line => !idsAreEqual(line.id, orderLineId));
         order.lines = order.lines.filter(line => !idsAreEqual(line.id, orderLineId));
         const updatedOrder = await this.applyPriceAdjustments(ctx, order);
         const updatedOrder = await this.applyPriceAdjustments(ctx, order);
+        const deletedOrderLine = new OrderLine(orderLine);
         await this.connection.getRepository(ctx, OrderLine).remove(orderLine);
         await this.connection.getRepository(ctx, OrderLine).remove(orderLine);
-        this.eventBus.publish(new OrderLineEvent(ctx, order, orderLine, 'deleted'));
+        this.eventBus.publish(new OrderLineEvent(ctx, order, deletedOrderLine, 'deleted'));
         return updatedOrder;
         return updatedOrder;
     }
     }
 
 

+ 0 - 3
packages/core/src/service/services/stock-movement.service.ts

@@ -137,9 +137,6 @@ export class StockMovementService {
      * increased, indicating that this quantity of stock is allocated and cannot be sold.
      * increased, indicating that this quantity of stock is allocated and cannot be sold.
      */
      */
     async createAllocationsForOrder(ctx: RequestContext, order: Order): Promise<Allocation[]> {
     async createAllocationsForOrder(ctx: RequestContext, order: Order): Promise<Allocation[]> {
-        if (order.active !== false) {
-            throw new InternalServerError('error.cannot-create-allocations-for-active-order');
-        }
         const lines = order.lines.map(orderLine => ({
         const lines = order.lines.map(orderLine => ({
             orderLineId: orderLine.id,
             orderLineId: orderLine.id,
             quantity: orderLine.quantity,
             quantity: orderLine.quantity,

+ 3 - 3
packages/create/package.json

@@ -1,6 +1,6 @@
 {
 {
     "name": "@vendure/create",
     "name": "@vendure/create",
-    "version": "2.1.4",
+    "version": "2.1.5",
     "license": "MIT",
     "license": "MIT",
     "bin": {
     "bin": {
         "create": "./index.js"
         "create": "./index.js"
@@ -28,14 +28,14 @@
         "@types/fs-extra": "^9.0.1",
         "@types/fs-extra": "^9.0.1",
         "@types/handlebars": "^4.1.0",
         "@types/handlebars": "^4.1.0",
         "@types/semver": "^6.2.2",
         "@types/semver": "^6.2.2",
-        "@vendure/core": "^2.1.4",
+        "@vendure/core": "^2.1.5",
         "rimraf": "^3.0.2",
         "rimraf": "^3.0.2",
         "ts-node": "^10.9.1",
         "ts-node": "^10.9.1",
         "typescript": "4.9.5"
         "typescript": "4.9.5"
     },
     },
     "dependencies": {
     "dependencies": {
         "@clack/prompts": "^0.7.0",
         "@clack/prompts": "^0.7.0",
-        "@vendure/common": "^2.1.4",
+        "@vendure/common": "^2.1.5",
         "commander": "^11.0.0",
         "commander": "^11.0.0",
         "cross-spawn": "^7.0.3",
         "cross-spawn": "^7.0.3",
         "detect-port": "^1.5.1",
         "detect-port": "^1.5.1",

+ 9 - 9
packages/dev-server/package.json

@@ -1,6 +1,6 @@
 {
 {
     "name": "dev-server",
     "name": "dev-server",
-    "version": "2.1.4",
+    "version": "2.1.5",
     "main": "index.js",
     "main": "index.js",
     "license": "MIT",
     "license": "MIT",
     "private": true,
     "private": true,
@@ -15,18 +15,18 @@
     },
     },
     "dependencies": {
     "dependencies": {
         "@nestjs/axios": "^3.0.0",
         "@nestjs/axios": "^3.0.0",
-        "@vendure/admin-ui-plugin": "^2.1.4",
-        "@vendure/asset-server-plugin": "^2.1.4",
-        "@vendure/common": "^2.1.4",
-        "@vendure/core": "^2.1.4",
-        "@vendure/elasticsearch-plugin": "^2.1.4",
-        "@vendure/email-plugin": "^2.1.4",
+        "@vendure/admin-ui-plugin": "^2.1.5",
+        "@vendure/asset-server-plugin": "^2.1.5",
+        "@vendure/common": "^2.1.5",
+        "@vendure/core": "^2.1.5",
+        "@vendure/elasticsearch-plugin": "^2.1.5",
+        "@vendure/email-plugin": "^2.1.5",
         "typescript": "4.9.5"
         "typescript": "4.9.5"
     },
     },
     "devDependencies": {
     "devDependencies": {
         "@types/csv-stringify": "^3.1.0",
         "@types/csv-stringify": "^3.1.0",
-        "@vendure/testing": "^2.1.4",
-        "@vendure/ui-devkit": "^2.1.4",
+        "@vendure/testing": "^2.1.5",
+        "@vendure/ui-devkit": "^2.1.5",
         "commander": "^7.1.0",
         "commander": "^7.1.0",
         "concurrently": "^8.2.1",
         "concurrently": "^8.2.1",
         "csv-stringify": "^5.3.3",
         "csv-stringify": "^5.3.3",

+ 3 - 3
packages/elasticsearch-plugin/package.json

@@ -1,6 +1,6 @@
 {
 {
     "name": "@vendure/elasticsearch-plugin",
     "name": "@vendure/elasticsearch-plugin",
-    "version": "2.1.4",
+    "version": "2.1.5",
     "license": "MIT",
     "license": "MIT",
     "main": "lib/index.js",
     "main": "lib/index.js",
     "types": "lib/index.d.ts",
     "types": "lib/index.d.ts",
@@ -26,8 +26,8 @@
         "fast-deep-equal": "^3.1.3"
         "fast-deep-equal": "^3.1.3"
     },
     },
     "devDependencies": {
     "devDependencies": {
-        "@vendure/common": "^2.1.4",
-        "@vendure/core": "^2.1.4",
+        "@vendure/common": "^2.1.5",
+        "@vendure/core": "^2.1.5",
         "rimraf": "^3.0.2",
         "rimraf": "^3.0.2",
         "typescript": "4.9.5"
         "typescript": "4.9.5"
     }
     }

+ 1 - 1
packages/elasticsearch-plugin/src/types.ts

@@ -271,7 +271,7 @@ type GraphQlPermittedReturnType = PrimitiveTypeVariations<GraphQlPrimitive>;
 type CustomMappingDefinition<Args extends any[], T extends GraphQlPermittedReturnType, R> = {
 type CustomMappingDefinition<Args extends any[], T extends GraphQlPermittedReturnType, R> = {
     graphQlType: T;
     graphQlType: T;
     public?: boolean;
     public?: boolean;
-    valueFn: (...args: Args) => R;
+    valueFn: (...args: Args) => Promise<R> | R;
 };
 };
 
 
 type TypeVariationMap<GqlType extends GraphQlPrimitive, TsType> = {
 type TypeVariationMap<GqlType extends GraphQlPrimitive, TsType> = {

+ 3 - 3
packages/email-plugin/package.json

@@ -1,6 +1,6 @@
 {
 {
     "name": "@vendure/email-plugin",
     "name": "@vendure/email-plugin",
-    "version": "2.1.4",
+    "version": "2.1.5",
     "license": "MIT",
     "license": "MIT",
     "main": "lib/index.js",
     "main": "lib/index.js",
     "types": "lib/index.d.ts",
     "types": "lib/index.d.ts",
@@ -35,8 +35,8 @@
         "@types/fs-extra": "^9.0.1",
         "@types/fs-extra": "^9.0.1",
         "@types/handlebars": "^4.1.0",
         "@types/handlebars": "^4.1.0",
         "@types/mjml": "^4.0.4",
         "@types/mjml": "^4.0.4",
-        "@vendure/common": "^2.1.4",
-        "@vendure/core": "^2.1.4",
+        "@vendure/common": "^2.1.5",
+        "@vendure/core": "^2.1.5",
         "rimraf": "^3.0.2",
         "rimraf": "^3.0.2",
         "typescript": "4.9.5"
         "typescript": "4.9.5"
     }
     }

+ 3 - 3
packages/harden-plugin/package.json

@@ -1,6 +1,6 @@
 {
 {
     "name": "@vendure/harden-plugin",
     "name": "@vendure/harden-plugin",
-    "version": "2.1.4",
+    "version": "2.1.5",
     "license": "MIT",
     "license": "MIT",
     "main": "lib/index.js",
     "main": "lib/index.js",
     "types": "lib/index.d.ts",
     "types": "lib/index.d.ts",
@@ -21,7 +21,7 @@
         "graphql-query-complexity": "^0.12.0"
         "graphql-query-complexity": "^0.12.0"
     },
     },
     "devDependencies": {
     "devDependencies": {
-        "@vendure/common": "^2.1.4",
-        "@vendure/core": "^2.1.4"
+        "@vendure/common": "^2.1.5",
+        "@vendure/core": "^2.1.5"
     }
     }
 }
 }

+ 3 - 3
packages/job-queue-plugin/package.json

@@ -1,6 +1,6 @@
 {
 {
     "name": "@vendure/job-queue-plugin",
     "name": "@vendure/job-queue-plugin",
-    "version": "2.1.4",
+    "version": "2.1.5",
     "license": "MIT",
     "license": "MIT",
     "main": "package/index.js",
     "main": "package/index.js",
     "types": "package/index.d.ts",
     "types": "package/index.d.ts",
@@ -23,8 +23,8 @@
     },
     },
     "devDependencies": {
     "devDependencies": {
         "@google-cloud/pubsub": "^2.8.0",
         "@google-cloud/pubsub": "^2.8.0",
-        "@vendure/common": "^2.1.4",
-        "@vendure/core": "^2.1.4",
+        "@vendure/common": "^2.1.5",
+        "@vendure/core": "^2.1.5",
         "bullmq": "^3.15.5",
         "bullmq": "^3.15.5",
         "ioredis": "^5.3.0",
         "ioredis": "^5.3.0",
         "rimraf": "^3.0.2",
         "rimraf": "^3.0.2",

+ 3 - 3
packages/job-queue-plugin/src/bullmq/plugin.ts

@@ -28,13 +28,13 @@ import { BullMQPluginOptions } from './types';
  *
  *
  * ## Installation
  * ## Installation
  *
  *
- * `yarn add \@vendure/job-queue-plugin bullmq@1`
+ * `yarn add \@vendure/job-queue-plugin bullmq`
  *
  *
  * or
  * or
  *
  *
- * `npm install \@vendure/job-queue-plugin bullmq@1`
+ * `npm install \@vendure/job-queue-plugin bullmq`
  *
  *
- * **Note:** The v1.x version of this plugin is designed to work with bullmq v1.x.
+ * **Note:** The v1.x version of this plugin is designed to work with bullmq v1.x, etc.
  *
  *
  * @example
  * @example
  * ```ts
  * ```ts

+ 4 - 4
packages/payments-plugin/package.json

@@ -1,6 +1,6 @@
 {
 {
     "name": "@vendure/payments-plugin",
     "name": "@vendure/payments-plugin",
-    "version": "2.1.4",
+    "version": "2.1.5",
     "license": "MIT",
     "license": "MIT",
     "main": "package/index.js",
     "main": "package/index.js",
     "types": "package/index.d.ts",
     "types": "package/index.d.ts",
@@ -46,9 +46,9 @@
         "@mollie/api-client": "^3.7.0",
         "@mollie/api-client": "^3.7.0",
         "@types/braintree": "^2.22.15",
         "@types/braintree": "^2.22.15",
         "@types/localtunnel": "2.0.1",
         "@types/localtunnel": "2.0.1",
-        "@vendure/common": "^2.1.4",
-        "@vendure/core": "^2.1.4",
-        "@vendure/testing": "^2.1.4",
+        "@vendure/common": "^2.1.5",
+        "@vendure/core": "^2.1.5",
+        "@vendure/testing": "^2.1.5",
         "braintree": "^3.16.0",
         "braintree": "^3.16.0",
         "localtunnel": "2.0.2",
         "localtunnel": "2.0.2",
         "nock": "^13.1.4",
         "nock": "^13.1.4",

+ 16 - 4
packages/payments-plugin/src/mollie/mollie.controller.ts

@@ -1,17 +1,16 @@
 import { Body, Controller, Param, Post } from '@nestjs/common';
 import { Body, Controller, Param, Post } from '@nestjs/common';
-import { Ctx, Logger, RequestContext, Transaction } from '@vendure/core';
+import { Ctx, Logger, RequestContext, Transaction, ChannelService, LanguageCode } from '@vendure/core';
 
 
 import { loggerCtx } from './constants';
 import { loggerCtx } from './constants';
 import { MollieService } from './mollie.service';
 import { MollieService } from './mollie.service';
 
 
 @Controller('payments')
 @Controller('payments')
 export class MollieController {
 export class MollieController {
-    constructor(private mollieService: MollieService) {}
+    constructor(private mollieService: MollieService, private channelService: ChannelService) {}
 
 
     @Post('mollie/:channelToken/:paymentMethodId')
     @Post('mollie/:channelToken/:paymentMethodId')
     @Transaction()
     @Transaction()
     async webhook(
     async webhook(
-        @Ctx() ctx: RequestContext,
         @Param('channelToken') channelToken: string,
         @Param('channelToken') channelToken: string,
         @Param('paymentMethodId') paymentMethodId: string,
         @Param('paymentMethodId') paymentMethodId: string,
         @Body() body: any,
         @Body() body: any,
@@ -20,8 +19,10 @@ export class MollieController {
             return Logger.warn(' Ignoring incoming webhook, because it has no body.id.', loggerCtx);
             return Logger.warn(' Ignoring incoming webhook, because it has no body.id.', loggerCtx);
         }
         }
         try {
         try {
+            // We need to construct a RequestContext based on the channelToken,
+            // because this is an incoming webhook, not a graphql request with a valid Ctx
+            const ctx = await this.createContext(channelToken);
             await this.mollieService.handleMollieStatusUpdate(ctx, {
             await this.mollieService.handleMollieStatusUpdate(ctx, {
-                channelToken,
                 paymentMethodId,
                 paymentMethodId,
                 orderId: body.id,
                 orderId: body.id,
             });
             });
@@ -34,4 +35,15 @@ export class MollieController {
             throw error;
             throw error;
         }
         }
     }
     }
+
+    private async createContext(channelToken: string): Promise<RequestContext> {
+        const channel = await this.channelService.getChannelFromToken(channelToken);
+        return new RequestContext({
+            apiType: 'admin',
+            isAuthorized: true,
+            authorizedAsOwnerOnly: false,
+            channel,
+            languageCode: LanguageCode.en,
+        });
+    }
 }
 }

+ 10 - 6
packages/payments-plugin/src/mollie/mollie.service.ts

@@ -37,7 +37,6 @@ import { amountToCents, getLocale, toAmount, toMollieAddress, toMollieOrderLines
 import { MolliePluginOptions } from './mollie.plugin';
 import { MolliePluginOptions } from './mollie.plugin';
 
 
 interface OrderStatusInput {
 interface OrderStatusInput {
-    channelToken: string;
     paymentMethodId: string;
     paymentMethodId: string;
     orderId: string;
     orderId: string;
 }
 }
@@ -189,7 +188,7 @@ export class MollieService {
             orderInput.method = molliePaymentMethodCode as MollieClientMethod;
             orderInput.method = molliePaymentMethodCode as MollieClientMethod;
         }
         }
         const mollieOrder = await mollieClient.orders.create(orderInput);
         const mollieOrder = await mollieClient.orders.create(orderInput);
-        Logger.info(`Created Mollie order ${mollieOrder.id} for order ${order.code}`);
+        Logger.info(`Created Mollie order ${mollieOrder.id} for order ${order.code}`, loggerCtx);
         const url = mollieOrder.getCheckoutUrl();
         const url = mollieOrder.getCheckoutUrl();
         if (!url) {
         if (!url) {
             throw Error('Unable to getCheckoutUrl() from Mollie order');
             throw Error('Unable to getCheckoutUrl() from Mollie order');
@@ -204,10 +203,10 @@ export class MollieService {
      */
      */
     async handleMollieStatusUpdate(
     async handleMollieStatusUpdate(
         ctx: RequestContext,
         ctx: RequestContext,
-        { channelToken, paymentMethodId, orderId }: OrderStatusInput,
+        { paymentMethodId, orderId }: OrderStatusInput,
     ): Promise<void> {
     ): Promise<void> {
         Logger.info(
         Logger.info(
-            `Received status update for channel ${channelToken} for Mollie order ${orderId}`,
+            `Received status update for channel ${ctx.channel.token} for Mollie order ${orderId}`,
             loggerCtx,
             loggerCtx,
         );
         );
         const paymentMethod = await this.paymentMethodService.findOne(ctx, paymentMethodId);
         const paymentMethod = await this.paymentMethodService.findOne(ctx, paymentMethodId);
@@ -218,12 +217,12 @@ export class MollieService {
         const apiKey = paymentMethod.handler.args.find(a => a.name === 'apiKey')?.value;
         const apiKey = paymentMethod.handler.args.find(a => a.name === 'apiKey')?.value;
         const autoCapture = paymentMethod.handler.args.find(a => a.name === 'autoCapture')?.value === 'true';
         const autoCapture = paymentMethod.handler.args.find(a => a.name === 'autoCapture')?.value === 'true';
         if (!apiKey) {
         if (!apiKey) {
-            throw Error(`No apiKey found for payment ${paymentMethod.id} for channel ${channelToken}`);
+            throw Error(`No apiKey found for payment ${paymentMethod.id} for channel ${ctx.channel.token}`);
         }
         }
         const client = createMollieClient({ apiKey });
         const client = createMollieClient({ apiKey });
         const mollieOrder = await client.orders.get(orderId);
         const mollieOrder = await client.orders.get(orderId);
         Logger.info(
         Logger.info(
-            `Processing status '${mollieOrder.status}' for order ${mollieOrder.orderNumber} for channel ${channelToken} for Mollie order ${orderId}`,
+            `Processing status '${mollieOrder.status}' for order ${mollieOrder.orderNumber} for channel ${ctx.channel.token} for Mollie order ${orderId}`,
             loggerCtx,
             loggerCtx,
         );
         );
         let order = await this.orderService.findOneByCode(ctx, mollieOrder.orderNumber, ['payments']);
         let order = await this.orderService.findOneByCode(ctx, mollieOrder.orderNumber, ['payments']);
@@ -260,6 +259,11 @@ export class MollieService {
         if (order.state === 'PaymentAuthorized' && mollieOrder.status === OrderStatus.completed) {
         if (order.state === 'PaymentAuthorized' && mollieOrder.status === OrderStatus.completed) {
             return this.settleExistingPayment(ctx, order, mollieOrder.id);
             return this.settleExistingPayment(ctx, order, mollieOrder.id);
         }
         }
+        if (autoCapture && mollieOrder.status === OrderStatus.completed) {
+            // When autocapture is enabled, we should not handle the completed status from Mollie,
+            // because the order will be transitioned to PaymentSettled during auto capture
+            return;
+        }
         // Any other combination of Mollie status and Vendure status indicates something is wrong.
         // Any other combination of Mollie status and Vendure status indicates something is wrong.
         throw Error(
         throw Error(
             `Unhandled incoming Mollie status '${mollieOrder.status}' for order ${order.code} with status '${order.state}'`,
             `Unhandled incoming Mollie status '${mollieOrder.status}' for order ${order.code} with status '${order.state}'`,

+ 3 - 0
packages/sentry-plugin/.gitignore

@@ -0,0 +1,3 @@
+yarn-error.log
+lib
+e2e/__data__/*.sqlite

+ 7 - 0
packages/sentry-plugin/README.md

@@ -0,0 +1,7 @@
+# Vendure Sentry Plugin
+
+Integrates your Vendure server with the [Sentry](https://sentry.io/) application monitoring service.
+
+`npm install @vendure/sentry-plugin`
+
+For documentation, see [docs.vendure.io/reference/core-plugins/sentry-plugin/](https://docs.vendure.io/reference/core-plugins/sentry-plugin/)

+ 4 - 0
packages/sentry-plugin/index.ts

@@ -0,0 +1,4 @@
+export * from './src/sentry-plugin';
+export * from './src/sentry.service';
+export * from './src/types';
+export * from './src/constants';

+ 28 - 0
packages/sentry-plugin/package.json

@@ -0,0 +1,28 @@
+{
+    "name": "@vendure/sentry-plugin",
+    "version": "2.1.5",
+    "license": "MIT",
+    "main": "lib/index.js",
+    "types": "lib/index.d.ts",
+    "files": [
+        "lib/**/*"
+    ],
+    "scripts": {
+        "watch": "tsc -p ./tsconfig.build.json --watch",
+        "build": "rimraf lib && tsc -p ./tsconfig.build.json",
+        "lint": "eslint --fix ."
+    },
+    "homepage": "https://www.vendure.io",
+    "funding": "https://github.com/sponsors/michaelbromley",
+    "publishConfig": {
+        "access": "public"
+    },
+    "peerDependencies": {
+        "@sentry/node": "^7.85.0"
+    },
+    "devDependencies": {
+        "@sentry/node": "^7.85.0",
+        "@vendure/common": "^2.1.5",
+        "@vendure/core": "^2.1.5"
+    }
+}

+ 32 - 0
packages/sentry-plugin/src/api/admin-test.resolver.ts

@@ -0,0 +1,32 @@
+import { Args, Mutation, Resolver } from '@nestjs/graphql';
+import { Allow, Permission, UserInputError } from '@vendure/core';
+
+import { SentryService } from '../sentry.service';
+import { ErrorTestService } from './error-test.service';
+
+declare const a: number;
+
+@Resolver()
+export class SentryAdminTestResolver {
+    constructor(private sentryService: SentryService, private errorTestService: ErrorTestService) {}
+
+    @Allow(Permission.SuperAdmin)
+    @Mutation()
+    async createTestError(@Args() args: { errorType: string }) {
+        switch (args.errorType) {
+            case 'UNCAUGHT_ERROR':
+                return a / 10;
+            case 'THROWN_ERROR':
+                throw new UserInputError('SentryPlugin Test Error');
+            case 'CAPTURED_ERROR':
+                this.sentryService.captureException(new Error('SentryPlugin Direct error'));
+                return true;
+            case 'CAPTURED_MESSAGE':
+                this.sentryService.captureMessage('Captured message');
+                return true;
+            case 'DATABASE_ERROR':
+                await this.errorTestService.createDatabaseError();
+                return true;
+        }
+    }
+}

+ 14 - 0
packages/sentry-plugin/src/api/api-extensions.ts

@@ -0,0 +1,14 @@
+import gql from 'graphql-tag';
+
+export const testApiExtensions = gql`
+    enum TestErrorType {
+        UNCAUGHT_ERROR
+        THROWN_ERROR
+        CAPTURED_ERROR
+        CAPTURED_MESSAGE
+        DATABASE_ERROR
+    }
+    extend type Mutation {
+        createTestError(errorType: TestErrorType!): Boolean
+    }
+`;

+ 11 - 0
packages/sentry-plugin/src/api/error-test.service.ts

@@ -0,0 +1,11 @@
+import { Injectable } from '@nestjs/common';
+import { TransactionalConnection } from '@vendure/core';
+
+@Injectable()
+export class ErrorTestService {
+    constructor(private connection: TransactionalConnection) {}
+
+    createDatabaseError() {
+        return this.connection.rawConnection.query('SELECT * FROM non_existent_table');
+    }
+}

+ 3 - 0
packages/sentry-plugin/src/constants.ts

@@ -0,0 +1,3 @@
+export const SENTRY_PLUGIN_OPTIONS = 'SENTRY_PLUGIN_OPTIONS';
+export const SENTRY_TRANSACTION_KEY = 'SENTRY_PLUGIN_TRANSACTION';
+export const loggerCtx = 'SentryPlugin';

+ 58 - 0
packages/sentry-plugin/src/sentry-apollo-plugin.ts

@@ -0,0 +1,58 @@
+/* eslint-disable @typescript-eslint/require-await */
+import {
+    ApolloServerPlugin,
+    GraphQLRequestListener,
+    GraphQLRequestContext,
+    GraphQLRequestContextDidEncounterErrors,
+} from '@apollo/server';
+import { Transaction, setContext } from '@sentry/node';
+
+import { SENTRY_TRANSACTION_KEY } from './constants';
+
+/**
+ * Based on https://github.com/ntegral/nestjs-sentry/issues/97#issuecomment-1252446807
+ */
+export class SentryApolloPlugin implements ApolloServerPlugin {
+    constructor(private options: { enableTracing: boolean }) {}
+
+    async requestDidStart({
+        request,
+        contextValue,
+    }: GraphQLRequestContext<any>): Promise<GraphQLRequestListener<any>> {
+        const { enableTracing } = this.options;
+        const transaction: Transaction | undefined = contextValue.req[SENTRY_TRANSACTION_KEY];
+        if (request.operationName) {
+            if (enableTracing) {
+                // set the transaction Name if we have named queries
+                transaction?.setName(request.operationName);
+            }
+            setContext('Graphql Request', {
+                operation_name: request.operationName,
+                variables: request.variables,
+            });
+        }
+
+        return {
+            // hook for transaction finished
+            async willSendResponse(context) {
+                transaction?.finish();
+            },
+            async executionDidStart() {
+                return {
+                    // hook for each new resolver
+                    willResolveField({ info }) {
+                        if (enableTracing) {
+                            const span = transaction?.startChild({
+                                op: 'resolver',
+                                description: `${info.parentType.name}.${info.fieldName}`,
+                            });
+                            return () => {
+                                span?.finish();
+                            };
+                        }
+                    },
+                };
+            },
+        };
+    }
+}

+ 25 - 0
packages/sentry-plugin/src/sentry-context.middleware.ts

@@ -0,0 +1,25 @@
+import { Inject, Injectable, NestMiddleware } from '@nestjs/common';
+import { Request, Response, NextFunction } from 'express';
+
+import { SENTRY_PLUGIN_OPTIONS, SENTRY_TRANSACTION_KEY } from './constants';
+import { SentryService } from './sentry.service';
+import { SentryPluginOptions } from './types';
+
+@Injectable()
+export class SentryContextMiddleware implements NestMiddleware {
+    constructor(
+        @Inject(SENTRY_PLUGIN_OPTIONS) private options: SentryPluginOptions,
+        private sentryService: SentryService,
+    ) {}
+
+    use(req: Request, res: Response, next: NextFunction) {
+        if (this.options.enableTracing) {
+            const transaction = this.sentryService.startTransaction({
+                op: 'resolver',
+                name: `GraphQLTransaction`,
+            });
+            req[SENTRY_TRANSACTION_KEY] = transaction;
+        }
+        next();
+    }
+}

+ 146 - 0
packages/sentry-plugin/src/sentry-plugin.ts

@@ -0,0 +1,146 @@
+import { MiddlewareConsumer, NestModule } from '@nestjs/common';
+import { APP_FILTER } from '@nestjs/core';
+import { PluginCommonModule, VendurePlugin } from '@vendure/core';
+
+import { SentryAdminTestResolver } from './api/admin-test.resolver';
+import { testApiExtensions } from './api/api-extensions';
+import { ErrorTestService } from './api/error-test.service';
+import { SENTRY_PLUGIN_OPTIONS } from './constants';
+import { SentryApolloPlugin } from './sentry-apollo-plugin';
+import { SentryContextMiddleware } from './sentry-context.middleware';
+import { SentryExceptionsFilter } from './sentry.filter';
+import { SentryService } from './sentry.service';
+import { SentryPluginOptions } from './types';
+
+const SentryOptionsProvider = {
+    provide: SENTRY_PLUGIN_OPTIONS,
+    useFactory: () => SentryPlugin.options,
+};
+
+/**
+ * @description
+ * This plugin integrates the [Sentry](https://sentry.io) error tracking & performance monitoring
+ * service with your Vendure server. In addition to capturing errors, it also provides built-in
+ * support for [tracing](https://docs.sentry.io/product/sentry-basics/concepts/tracing/) as well as
+ * enriching your Sentry events with additional context about the request.
+ *
+ * ## Pre-requisites
+ *
+ * This plugin depends on access to Sentry, which can be self-hosted or used as a cloud service.
+ *
+ * If using the hosted SaaS option, you must have a Sentry account and a project set up ([sign up here](https://sentry.io/signup/)). When setting up your project,
+ * select the "Node.js" platform and no framework.
+ *
+ * Once set up, you will be given a [Data Source Name (DSN)](https://docs.sentry.io/product/sentry-basics/concepts/dsn-explainer/)
+ * which you will need to provide to the plugin.
+ *
+ * ## Installation
+ *
+ * Install this plugin as well as the `@sentry/node` package:
+ *
+ * ```sh
+ * npm install --save \@vendure/sentry-plugin \@sentry/node
+ * ```
+ *
+ * ## Configuration
+ *
+ * Before using the plugin, you must configure it with the DSN provided by Sentry:
+ *
+ * ```ts
+ * import { VendureConfig } from '\@vendure/core';
+ * import { SentryPlugin } from '\@vendure/sentry-plugin';
+ *
+ * export const config: VendureConfig = {
+ *     // ...
+ *     plugins: [
+ *         // ...
+ *         // highlight-start
+ *         SentryPlugin.init({
+ *             dsn: process.env.SENTRY_DSN,
+ *             // Optional configuration
+ *             includeErrorTestMutation: true,
+ *             enableTracing: true,
+ *             // you can also pass in any of the options from \@sentry/node
+ *             // for instance:
+ *             tracesSampleRate: 1.0,
+ *         }),
+ *         // highlight-end
+ *     ],
+ * };
+ *```
+ *
+ * ## Tracing
+ *
+ * This plugin includes built-in support for [tracing](https://docs.sentry.io/product/sentry-basics/concepts/tracing/), which allows you to see the performance of your
+ * GraphQL resolvers in the Sentry dashboard. To enable tracing, set the `enableTracing` option to `true` as shown above.
+ *
+ * ## Instrumenting your own code
+ *
+ * You may want to add your own custom spans to your code. To do so, you can use the `Sentry` object
+ * just as you would in any Node application. For example:
+ *
+ * ```ts
+ * import * as Sentry from "\@sentry/node";
+ *
+ * export class MyService {
+ *     async myMethod() {
+ *          Sentry.setContext('My Custom Context,{
+ *              key: 'value',
+ *          });
+ *     }
+ * }
+ * ```
+ *
+ * ## Error test mutation
+ *
+ * To test whether your Sentry configuration is working correctly, you can set the `includeErrorTestMutation` option to `true`. This will add a mutation to the Admin API
+ * which will throw an error of the type specified in the `errorType` argument. For example:
+ *
+ * ```graphql
+ * mutation CreateTestError {
+ *     createTestError(errorType: DATABASE_ERROR)
+ * }
+ * ```
+ *
+ * You should then be able to see the error in your Sentry dashboard (it may take a couple of minutes to appear).
+ *
+ * @docsCategory core plugins/SentryPlugin
+ */
+@VendurePlugin({
+    imports: [PluginCommonModule],
+    providers: [
+        SentryOptionsProvider,
+        SentryService,
+        ErrorTestService,
+        {
+            provide: APP_FILTER,
+            useClass: SentryExceptionsFilter,
+        },
+    ],
+    configuration: config => {
+        config.apiOptions.apolloServerPlugins.push(
+            new SentryApolloPlugin({
+                enableTracing: !!SentryPlugin.options.enableTracing,
+            }),
+        );
+        return config;
+    },
+    adminApiExtensions: {
+        schema: () => (SentryPlugin.options.includeErrorTestMutation ? testApiExtensions : undefined),
+        resolvers: () => (SentryPlugin.options.includeErrorTestMutation ? [SentryAdminTestResolver] : []),
+    },
+    exports: [SentryService],
+    compatibility: '^2.0.0',
+})
+export class SentryPlugin implements NestModule {
+    static options: SentryPluginOptions = {} as any;
+
+    configure(consumer: MiddlewareConsumer): any {
+        consumer.apply(SentryContextMiddleware).forRoutes('*');
+    }
+
+    static init(options: SentryPluginOptions) {
+        this.options = options;
+        return this;
+    }
+}

+ 27 - 0
packages/sentry-plugin/src/sentry.filter.ts

@@ -0,0 +1,27 @@
+import type { ArgumentsHost, ExceptionFilter } from '@nestjs/common';
+import { Catch, ExecutionContext } from '@nestjs/common';
+import { GqlContextType, GqlExecutionContext } from '@nestjs/graphql';
+import { setContext } from '@sentry/node';
+
+import { SentryService } from './sentry.service';
+
+@Catch()
+export class SentryExceptionsFilter implements ExceptionFilter {
+    constructor(private readonly sentryService: SentryService) {}
+
+    catch(exception: Error, host: ArgumentsHost): void {
+        if (host.getType<GqlContextType>() === 'graphql') {
+            const gqlContext = GqlExecutionContext.create(host as ExecutionContext);
+            const info = gqlContext.getInfo();
+            setContext('GraphQL Error Context', {
+                fieldName: info.fieldName,
+                path: info.path,
+            });
+        }
+        const variables = (exception as any).variables;
+        if (variables) {
+            setContext('GraphQL Error Variables', variables);
+        }
+        this.sentryService.captureException(exception);
+    }
+}

+ 40 - 0
packages/sentry-plugin/src/sentry.service.ts

@@ -0,0 +1,40 @@
+import { Inject, Injectable, OnApplicationBootstrap, OnApplicationShutdown } from '@nestjs/common';
+import * as Sentry from '@sentry/node';
+import { CaptureContext, TransactionContext } from '@sentry/types';
+
+import { SENTRY_PLUGIN_OPTIONS } from './constants';
+import { SentryPluginOptions } from './types';
+
+@Injectable()
+export class SentryService implements OnApplicationBootstrap, OnApplicationShutdown {
+    constructor(@Inject(SENTRY_PLUGIN_OPTIONS) private options: SentryPluginOptions) {}
+
+    onApplicationBootstrap(): any {
+        const integrations = this.options.integrations ?? [
+            new Sentry.Integrations.Http({ tracing: true }),
+            ...Sentry.autoDiscoverNodePerformanceMonitoringIntegrations(),
+        ];
+        Sentry.init({
+            ...this.options,
+            tracesSampleRate: this.options.tracesSampleRate ?? 1.0,
+            integrations,
+            dsn: this.options.dsn,
+        });
+    }
+
+    onApplicationShutdown() {
+        return Sentry.close();
+    }
+
+    captureException(exception: Error) {
+        Sentry.captureException(exception);
+    }
+
+    captureMessage(message: string, captureContext?: CaptureContext) {
+        Sentry.captureMessage(message, captureContext);
+    }
+
+    startTransaction(context: TransactionContext) {
+        return Sentry.startTransaction(context);
+    }
+}

+ 26 - 0
packages/sentry-plugin/src/types.ts

@@ -0,0 +1,26 @@
+import { Transaction } from '@sentry/node';
+import { NodeOptions } from '@sentry/node/types/types';
+
+import { SENTRY_TRANSACTION_KEY } from './constants';
+
+/**
+ * @description
+ * Configuration options for the {@link SentryPlugin}.
+ *
+ * @docsCategory core plugins/SentryPlugin
+ */
+export interface SentryPluginOptions extends NodeOptions {
+    /**
+     * @description
+     * The [Data Source Name](https://docs.sentry.io/product/sentry-basics/concepts/dsn-explainer/) for your Sentry instance.
+     */
+    dsn: string;
+    enableTracing?: boolean;
+    includeErrorTestMutation?: boolean;
+}
+
+declare module 'express' {
+    interface Request {
+        [SENTRY_TRANSACTION_KEY]: Transaction | undefined;
+    }
+}

+ 9 - 0
packages/sentry-plugin/tsconfig.build.json

@@ -0,0 +1,9 @@
+{
+  "extends": "./tsconfig.json",
+  "compilerOptions": {
+    "outDir": "./lib"
+  },
+  "files": [
+    "./index.ts"
+  ]
+}

Unele fișiere nu au fost afișate deoarece prea multe fișiere au fost modificate în acest diff