瀏覽代碼

Merge branch 'minor' into fix/bullmq-single-redis-queue

Michael Bromley 1 年之前
父節點
當前提交
8bb5bd6095
共有 100 個文件被更改,包括 2973 次插入258 次删除
  1. 14 5
      .github/workflows/build_and_test.yml
  2. 0 3
      .graphqlconfig
  3. 21 0
      .vscode/launch.json
  4. 63 0
      CHANGELOG.md
  5. 34 18
      README.md
  6. 1 1
      SECURITY.md
  7. 117 0
      docker-compose.yml
  8. 12 0
      docs/docs/guides/core-concepts/orders/index.md
  9. 4 1
      docs/docs/guides/deployment/horizontal-scaling.md
  10. 4 0
      docs/docs/guides/deployment/production-configuration/index.md
  11. 二進制
      docs/docs/guides/developer-guide/cache/cache-service.webp
  12. 206 0
      docs/docs/guides/developer-guide/cache/index.mdx
  13. 106 0
      docs/docs/guides/developer-guide/custom-fields/index.md
  14. 1 1
      docs/docs/guides/developer-guide/events/index.mdx
  15. 1 1
      docs/docs/guides/developer-guide/plugins/index.mdx
  16. 264 0
      docs/docs/guides/developer-guide/security/index.md
  17. 41 17
      docs/docs/guides/getting-started/installation/index.md
  18. 0 4
      docs/docs/guides/storefront/active-order/index.mdx
  19. 14 14
      docs/docs/reference/admin-ui-api/alerts/alert-config.md
  20. 1 1
      docs/docs/reference/admin-ui-api/alerts/alert-context.md
  21. 1 1
      docs/docs/reference/admin-ui-api/components/asset-picker-dialog-component.md
  22. 14 8
      docs/docs/reference/admin-ui-api/components/data-table2component.md
  23. 1 1
      docs/docs/reference/admin-ui-api/components/product-variant-selector-component.md
  24. 99 15
      docs/docs/reference/admin-ui-api/custom-input-components/default-inputs.md
  25. 15 1
      docs/docs/reference/admin-ui-api/list-detail-views/base-list-component.md
  26. 13 1
      docs/docs/reference/admin-ui-api/list-detail-views/typed-base-list-component.md
  27. 20 13
      docs/docs/reference/admin-ui-api/services/data-service.md
  28. 1 1
      docs/docs/reference/core-plugins/admin-ui-plugin/admin-ui-plugin-options.md
  29. 1 1
      docs/docs/reference/core-plugins/admin-ui-plugin/index.md
  30. 13 1
      docs/docs/reference/core-plugins/asset-server-plugin/asset-server-options.md
  31. 1 1
      docs/docs/reference/core-plugins/asset-server-plugin/cache-config.md
  32. 1 1
      docs/docs/reference/core-plugins/asset-server-plugin/hashed-asset-naming-strategy.md
  33. 1 1
      docs/docs/reference/core-plugins/asset-server-plugin/image-transform-mode.md
  34. 1 1
      docs/docs/reference/core-plugins/asset-server-plugin/image-transform-preset.md
  35. 148 0
      docs/docs/reference/core-plugins/asset-server-plugin/image-transform-strategy.md
  36. 32 5
      docs/docs/reference/core-plugins/asset-server-plugin/index.md
  37. 1 1
      docs/docs/reference/core-plugins/asset-server-plugin/local-asset-storage-strategy.md
  38. 122 0
      docs/docs/reference/core-plugins/asset-server-plugin/preset-only-strategy.md
  39. 3 3
      docs/docs/reference/core-plugins/asset-server-plugin/s3asset-storage-strategy.md
  40. 2 2
      docs/docs/reference/core-plugins/asset-server-plugin/sharp-asset-preview-strategy.md
  41. 1 1
      docs/docs/reference/core-plugins/elasticsearch-plugin/index.md
  42. 1 1
      docs/docs/reference/core-plugins/email-plugin/email-event-handler-with-async-data.md
  43. 27 1
      docs/docs/reference/core-plugins/email-plugin/email-event-handler.md
  44. 28 6
      docs/docs/reference/core-plugins/email-plugin/email-plugin-types.md
  45. 2 2
      docs/docs/reference/core-plugins/email-plugin/email-send-event.md
  46. 2 2
      docs/docs/reference/core-plugins/payments-plugin/mollie-plugin.md
  47. 34 1
      docs/docs/reference/core-plugins/payments-plugin/stripe-plugin.md
  48. 1 0
      docs/docs/reference/graphql-api/admin/enums.md
  49. 5 9
      docs/docs/reference/graphql-api/admin/input-types.md
  50. 24 0
      docs/docs/reference/graphql-api/admin/mutations.md
  51. 243 4
      docs/docs/reference/graphql-api/admin/object-types.md
  52. 1 0
      docs/docs/reference/graphql-api/shop/enums.md
  53. 28 4
      docs/docs/reference/graphql-api/shop/mutations.md
  54. 214 3
      docs/docs/reference/graphql-api/shop/object-types.md
  55. 1 1
      docs/docs/reference/typescript-api/assets/asset-options.md
  56. 7 8
      docs/docs/reference/typescript-api/auth/auth-options.md
  57. 1 1
      docs/docs/reference/typescript-api/auth/cookie-options.md
  58. 77 0
      docs/docs/reference/typescript-api/auth/default-session-cache-strategy.md
  59. 12 0
      docs/docs/reference/typescript-api/auth/external-authentication-service.md
  60. 14 5
      docs/docs/reference/typescript-api/auth/session-cache-strategy.md
  61. 1 1
      docs/docs/reference/typescript-api/auth/superadmin-credentials.md
  62. 48 0
      docs/docs/reference/typescript-api/cache/cache-config.md
  63. 80 0
      docs/docs/reference/typescript-api/cache/cache-service.md
  64. 62 0
      docs/docs/reference/typescript-api/cache/cache-strategy.md
  65. 44 0
      docs/docs/reference/typescript-api/cache/default-cache-plugin.md
  66. 83 0
      docs/docs/reference/typescript-api/cache/index.md
  67. 45 0
      docs/docs/reference/typescript-api/cache/redis-cache-plugin.md
  68. 73 0
      docs/docs/reference/typescript-api/cache/redis-cache-strategy.md
  69. 58 0
      docs/docs/reference/typescript-api/cache/request-context-cache-service.md
  70. 90 0
      docs/docs/reference/typescript-api/cache/sql-cache-strategy.md
  71. 1 1
      docs/docs/reference/typescript-api/common/admin-ui/admin-ui-app-config.md
  72. 1 1
      docs/docs/reference/typescript-api/common/admin-ui/admin-ui-app-dev-mode-config.md
  73. 1 1
      docs/docs/reference/typescript-api/common/admin-ui/admin-ui-config.md
  74. 46 2
      docs/docs/reference/typescript-api/common/bootstrap.md
  75. 1 1
      docs/docs/reference/typescript-api/common/currency-code.md
  76. 1 1
      docs/docs/reference/typescript-api/common/entity-relation-paths.md
  77. 1 1
      docs/docs/reference/typescript-api/common/job-state.md
  78. 1 1
      docs/docs/reference/typescript-api/common/language-code.md
  79. 1 1
      docs/docs/reference/typescript-api/common/permission.md
  80. 1 1
      docs/docs/reference/typescript-api/configurable-operation-def/config-arg-type.md
  81. 2 1
      docs/docs/reference/typescript-api/configurable-operation-def/default-form-component-id.md
  82. 16 10
      docs/docs/reference/typescript-api/configurable-operation-def/default-form-config-hash.md
  83. 1 1
      docs/docs/reference/typescript-api/configuration/api-options.md
  84. 1 1
      docs/docs/reference/typescript-api/configuration/default-config.md
  85. 1 1
      docs/docs/reference/typescript-api/configuration/entity-options.md
  86. 1 1
      docs/docs/reference/typescript-api/configuration/runtime-vendure-config.md
  87. 8 1
      docs/docs/reference/typescript-api/configuration/system-options.md
  88. 2 2
      docs/docs/reference/typescript-api/configuration/vendure-config.md
  89. 2 1
      docs/docs/reference/typescript-api/custom-fields/custom-field-config.md
  90. 4 2
      docs/docs/reference/typescript-api/custom-fields/custom-field-type.md
  91. 8 1
      docs/docs/reference/typescript-api/custom-fields/index.md
  92. 25 0
      docs/docs/reference/typescript-api/custom-fields/struct-custom-field-config.md
  93. 45 0
      docs/docs/reference/typescript-api/custom-fields/struct-field-config.md
  94. 1 1
      docs/docs/reference/typescript-api/custom-fields/typed-custom-single-field-config.md
  95. 54 45
      docs/docs/reference/typescript-api/data-access/transactional-connection.md
  96. 1 1
      docs/docs/reference/typescript-api/default-search-plugin/index.md
  97. 12 2
      docs/docs/reference/typescript-api/entities/history-entry.md
  98. 7 0
      docs/docs/reference/typescript-api/entities/order-line.md
  99. 12 2
      docs/docs/reference/typescript-api/entities/payment.md
  100. 14 0
      docs/docs/reference/typescript-api/entities/product-variant.md

+ 14 - 5
.github/workflows/build_and_test.yml

@@ -60,23 +60,25 @@ jobs:
       mariadb:
         image: bitnami/mariadb:10.3
         env:
-          ALLOW_EMPTY_PASSWORD: yes
+          MARIADB_ROOT_USER: vendure
+          MARIADB_ROOT_PASSWORD: password
         ports:
           - 3306
         options: --health-cmd="mysqladmin ping" --health-interval=10s --health-timeout=5s --health-retries=3
       mysql:
         image: bitnami/mysql:8.0
         env:
-          ALLOW_EMPTY_PASSWORD: yes
           MYSQL_AUTHENTICATION_PLUGIN: mysql_native_password
+          MYSQL_ROOT_USER: vendure
+          MYSQL_ROOT_PASSWORD: password
         ports:
           - 3306
         options: --health-cmd="mysqladmin ping --silent" --health-interval=10s --health-timeout=20s --health-retries=10
       postgres:
-        image: postgres:12
+        image: postgres:16
         env:
-          POSTGRES_USER: admin
-          POSTGRES_PASSWORD: secret
+          POSTGRES_USER: vendure
+          POSTGRES_PASSWORD: password
         ports:
           - 5432
         options: --health-cmd=pg_isready --health-interval=10s --health-timeout=5s --health-retries=3
@@ -96,6 +98,12 @@ jobs:
         ports:
           - 9200
         options: --health-cmd="curl --silent --fail localhost:9200/_cluster/health" --health-interval=10s --health-timeout=5s --health-retries=3
+      redis:
+        image: bitnami/redis:7.4.1
+        env:
+          ALLOW_EMPTY_PASSWORD: yes
+        ports:
+          - 6379
     strategy:
       fail-fast: false
       matrix:
@@ -119,5 +127,6 @@ jobs:
           E2E_MARIADB_PORT: ${{ job.services.mariadb.ports['3306'] }}
           E2E_POSTGRES_PORT: ${{ job.services.postgres.ports['5432'] }}
           E2E_ELASTIC_PORT: ${{ job.services.elastic.ports['9200'] }}
+          E2E_REDIS_PORT: ${{ job.services.redis.ports['6379'] }}
           DB: ${{ matrix.db }}
         run: npm run e2e

+ 0 - 3
.graphqlconfig

@@ -1,3 +0,0 @@
-{
-    "schema": "./schema-admin.json"
-}

+ 21 - 0
.vscode/launch.json

@@ -0,0 +1,21 @@
+{
+    // Use IntelliSense to learn about possible attributes.
+    // Hover to view descriptions of existing attributes.
+    // For more information, visit: https://go.microsoft.com/fwlink/?linkid=830387
+    "version": "0.2.0",
+    "configurations": [
+        {
+            "type": "node",
+            "request": "launch",
+            "name": "Debug Dev Server",
+            "program": "${workspaceFolder}/packages/dev-server/index.ts",
+            "outFiles": ["${workspaceFolder}/**/*.js"],
+            "runtimeExecutable": "${workspaceRoot}/node_modules/.bin/ts-node",
+            "runtimeArgs": ["--transpile-only"],
+            "skipFiles": ["<node_internals>/**", "node_modules/**"],
+            "cwd": "${workspaceRoot}",
+            "resolveSourceMapLocations": ["${workspaceFolder}/**", "!**/node_modules/**"],
+            "console": "integratedTerminal"
+        }
+    ]
+}

+ 63 - 0
CHANGELOG.md

@@ -1,3 +1,66 @@
+## <small>3.0.6 (2024-11-15)</small>
+
+
+#### Fixes
+
+* **admin-ui** Fix collection product filter dark theme (#3172) ([9f4eb9e](https://github.com/vendure-ecommerce/vendure/commit/9f4eb9e)), closes [#3172](https://github.com/vendure-ecommerce/vendure/issues/3172)
+* **admin-ui** Fix incorrect type when dealing with numeric value in list (#3094) ([76d66c6](https://github.com/vendure-ecommerce/vendure/commit/76d66c6)), closes [#3094](https://github.com/vendure-ecommerce/vendure/issues/3094) [#3093](https://github.com/vendure-ecommerce/vendure/issues/3093)
+* **admin-ui** Fix variant detail quick-jump component (#3189) ([478989e](https://github.com/vendure-ecommerce/vendure/commit/478989e)), closes [#3189](https://github.com/vendure-ecommerce/vendure/issues/3189)
+* **admin-ui** Make registerPageTab work on 'order-list' location (#3187) ([61d808b](https://github.com/vendure-ecommerce/vendure/commit/61d808b)), closes [#3187](https://github.com/vendure-ecommerce/vendure/issues/3187)
+* **admin-ui** Refund order dialog is showing the wrong field for prorated unit price (#3151) ([3777555](https://github.com/vendure-ecommerce/vendure/commit/3777555)), closes [#3151](https://github.com/vendure-ecommerce/vendure/issues/3151)
+* **admin-ui** Swedish translation adjustments (#3174) ([a21f129](https://github.com/vendure-ecommerce/vendure/commit/a21f129)), closes [#3174](https://github.com/vendure-ecommerce/vendure/issues/3174)
+* **common** Allow null on idsAreEqual function (#3171) ([7bba907](https://github.com/vendure-ecommerce/vendure/commit/7bba907)), closes [#3171](https://github.com/vendure-ecommerce/vendure/issues/3171)
+* **core** Added deprecation notices to the old refund input fields (#3119) ([7324bb3](https://github.com/vendure-ecommerce/vendure/commit/7324bb3)), closes [#3119](https://github.com/vendure-ecommerce/vendure/issues/3119)
+* **core** Disallow deletion of default channel (#3181) ([2ed3211](https://github.com/vendure-ecommerce/vendure/commit/2ed3211)), closes [#3181](https://github.com/vendure-ecommerce/vendure/issues/3181)
+* **core** Fix error on internal Administrator customFields (#3159) ([e03b7f0](https://github.com/vendure-ecommerce/vendure/commit/e03b7f0)), closes [#3159](https://github.com/vendure-ecommerce/vendure/issues/3159)
+* **core** Fix merging order with conflicting products using UseGuestStrategy (#3155) ([f0607aa](https://github.com/vendure-ecommerce/vendure/commit/f0607aa)), closes [#3155](https://github.com/vendure-ecommerce/vendure/issues/3155)
+* **core** Fix returning stale data in Role Update Event (#3154) ([71f85d2](https://github.com/vendure-ecommerce/vendure/commit/71f85d2)), closes [#3154](https://github.com/vendure-ecommerce/vendure/issues/3154)
+* **payments-plugin** Check for eligibility of Mollie method (#3200) ([a12dedc](https://github.com/vendure-ecommerce/vendure/commit/a12dedc)), closes [#3200](https://github.com/vendure-ecommerce/vendure/issues/3200)
+* **payments-plugin** prevent false positive logging (#3195) ([961297d](https://github.com/vendure-ecommerce/vendure/commit/961297d)), closes [#3195](https://github.com/vendure-ecommerce/vendure/issues/3195)
+* **testing** Make test client's `fileUploadMutation` work for more input variable shapes (#3188) ([a8938f4](https://github.com/vendure-ecommerce/vendure/commit/a8938f4)), closes [#3188](https://github.com/vendure-ecommerce/vendure/issues/3188)
+
+## <small>3.0.5 (2024-10-15)</small>
+
+
+#### Fixes
+
+* **asset-server-plugin** Fix local file read vulnerability when using the LocalAssetStorageStrategy ([e2ee0c4](https://github.com/vendure-ecommerce/vendure/commit/e2ee0c43159b3d13b51b78654481094fdd4850c5)). See the [security advisory](https://github.com/vendure-ecommerce/vendure/security/advisories/GHSA-r9mq-3c9r-fmjq)
+* **admin-ui** Fix theme & ui language switcher ([c93589b](https://github.com/vendure-ecommerce/vendure/commit/c93589b)), closes [#3111](https://github.com/vendure-ecommerce/vendure/issues/3111)
+* **core** Do not include deleted variants when indexing productInStock (#3110) ([73cb190](https://github.com/vendure-ecommerce/vendure/commit/73cb190)), closes [#3110](https://github.com/vendure-ecommerce/vendure/issues/3110) [#3109](https://github.com/vendure-ecommerce/vendure/issues/3109)
+* **core** Fix coupon code validation across multiple channels ([e57cc1b](https://github.com/vendure-ecommerce/vendure/commit/e57cc1b)), closes [#2052](https://github.com/vendure-ecommerce/vendure/issues/2052)
+* **core** Fix filtering on list queries of tree entities ([227da05](https://github.com/vendure-ecommerce/vendure/commit/227da05)), closes [#3107](https://github.com/vendure-ecommerce/vendure/issues/3107)
+* **core** Improve error message on populating without tax rates ([7e36131](https://github.com/vendure-ecommerce/vendure/commit/7e36131)), closes [#1926](https://github.com/vendure-ecommerce/vendure/issues/1926)
+
+#### Features
+
+* **create** Improved getting started experience (#3128) ([adb4384](https://github.com/vendure-ecommerce/vendure/commit/adb4384)), closes [#3128](https://github.com/vendure-ecommerce/vendure/issues/3128)
+
+## <small>3.0.4 (2024-10-04)</small>
+
+
+#### Fixes
+
+* **admin-ui-plugin** Implement rate limiting on static server ([9516c71](https://github.com/vendure-ecommerce/vendure/commit/9516c71))
+* **admin-ui** Add padding to default relation custom field  dropdown ([02e68e0](https://github.com/vendure-ecommerce/vendure/commit/02e68e0))
+* **admin-ui** Add support for custom fields on CustomerGroup list ([7128a33](https://github.com/vendure-ecommerce/vendure/commit/7128a33))
+* **admin-ui** Enable selective loading of custom fields ([9d7744b](https://github.com/vendure-ecommerce/vendure/commit/9d7744b)), closes [#3097](https://github.com/vendure-ecommerce/vendure/issues/3097)
+* **admin-ui** Fix bad locale detection regex ([f336d7f](https://github.com/vendure-ecommerce/vendure/commit/f336d7f))
+* **admin-ui** Lazy-load only selected custom fields in list views ([690dd0f](https://github.com/vendure-ecommerce/vendure/commit/690dd0f)), closes [#3097](https://github.com/vendure-ecommerce/vendure/issues/3097)
+* **admin-ui** Unsubscribe from alerts when logging out (#3071) ([f38340b](https://github.com/vendure-ecommerce/vendure/commit/f38340b)), closes [#3071](https://github.com/vendure-ecommerce/vendure/issues/3071) [#2188](https://github.com/vendure-ecommerce/vendure/issues/2188)
+* **asset-server-plugin** Do not return raw error message on error ([801980e](https://github.com/vendure-ecommerce/vendure/commit/801980e))
+* **core** Correctly parse numeric sessionDuration and verificationTokenDuration values (#3080) ([98e4118](https://github.com/vendure-ecommerce/vendure/commit/98e4118)), closes [#3080](https://github.com/vendure-ecommerce/vendure/issues/3080)
+* **core** Fix issues caused by f235249f ([5a4299a](https://github.com/vendure-ecommerce/vendure/commit/5a4299a))
+* **core** Fix RequestContext race condition causing null activeOrder ([f235249](https://github.com/vendure-ecommerce/vendure/commit/f235249)), closes [#2097](https://github.com/vendure-ecommerce/vendure/issues/2097)
+* **core** Handle empty state for product and variant id filter (#3064) ([9a03c84](https://github.com/vendure-ecommerce/vendure/commit/9a03c84)), closes [#3064](https://github.com/vendure-ecommerce/vendure/issues/3064)
+* **core** Prevent theoretical polynomial regex attack ([9f4a814](https://github.com/vendure-ecommerce/vendure/commit/9f4a814))
+* **core** Remove duplicate call in applyCouponCode resolver ([bffc58a](https://github.com/vendure-ecommerce/vendure/commit/bffc58a))
+* **core** Replace insecure randomness with secure randomBytes ([cb556d8](https://github.com/vendure-ecommerce/vendure/commit/cb556d8))
+* **payments-plugin** Use default channel in Stripe webhook calls to reach all orders (#3076) ([8434111](https://github.com/vendure-ecommerce/vendure/commit/8434111)), closes [#3076](https://github.com/vendure-ecommerce/vendure/issues/3076)
+
+#### Perf
+
+* **core** Fix performance when using FacetValue-based checks ([a735bdf](https://github.com/vendure-ecommerce/vendure/commit/a735bdf))
+* **admin-ui** List views only load the visible custom fields, closes [#3097](https://github.com/vendure-ecommerce/vendure/issues/3097)
 
 ## <small>3.0.3 (2024-09-11)</small>
 

+ 34 - 18
README.md

@@ -62,32 +62,44 @@ Packages must be built (i.e. TypeScript compiled, admin ui app built, certain as
 
 Note that this can take a few minutes.
 
-### 3. Set up the server
+### 3. Start the docker containers
 
-The server requires an SQL database to be available. The simplest option is to use SQLite, but if you have Docker available you can use the [dev-server docker-compose file](./packages/dev-server/docker-compose.yml) which will start up both MariaDB and Postgres as well as their GUI management tools.
+All the necessary infrastructure is defined in the root [docker-compose.yml](./docker-compose.yml) file. At a minimum,
+you will need to start a database, for example:
 
-Vendure uses [TypeORM](http://typeorm.io), and officially supports **MySQL**, **PostgreSQL** and **SQLite**, though other TypeORM-supported databases may work.
+```bash
+docker-compose up -d mariadb
+```
 
-1. Configure the [dev config](./packages/dev-server/dev-config.ts), making sure the connection settings in the `getDbConfig()` function are correct for the database type you will be using.
-2. Create the database using your DB admin tool of choice (e.g. phpMyAdmin if you are using the docker image suggested above). Name it according to the `getDbConfig()` settings. If you are using SQLite, you can skip this step.
-3. Populate mock data: 
-   ```bash
-    cd packages/dev-server
-    DB=<mysql|postgres|sqlite> npm run populate
-    ```
-   If you do not specify the `DB` variable, it will default to "mysql".
+MariaDB/MySQL is the default that will be used by the dev server if you don't explicitly set the `DB` environment variable.
 
-### 4. Run the dev server
+If for example you are doing development on the Elasticsearch plugin, you will also need to start the Elasticsearch container:
 
+```bash
+docker-compose up -d elasticsearch
 ```
+
+### 4. Populate test data
+
+Vendure uses [TypeORM](http://typeorm.io), and officially supports **MySQL**, **MariaDB**, **PostgreSQL** and **SQLite**.
+
+The first step is to populate the dev server with some test data:
+
+```bash
 cd packages/dev-server
-DB=<mysql|postgres|sqlite> npm run start
-```
-Or if you are in the root package 
+
+[DB=mysql|postres|sqlite] npm run populate
+ ```
+
+If you do not specify the `DB` variable, it will default to "mysql". If you specifically want to develop against Postgres,
+you need to run the `postgres_16` container and then run `DB=postgres npm run populate`. 
+
+### 5. Run the dev server
+
 ```
-DB=<mysql|postgres|sqlite> npm run dev-server:start
+cd packages/dev-server
+[DB=mysql|postgres|sqlite] npm run dev
 ```
-If you do not specify the `DB` argument, it will default to "mysql".
 
 ### Testing admin ui changes locally
 
@@ -125,11 +137,15 @@ npm run watch:core-common
 ```shell
 # Terminal 2
 cd packages/dev-server
-DB=sqlite npm run start
+DB=sqlite npm run dev
 ```
 
 3. The dev-server will now have your local changes from the changed package.
 
+### Interactive debugging
+
+To debug the dev server with VS Code use the include [launch.json](/.vscode/launch.json) configuration.
+
 ### Code generation
 
 [graphql-code-generator](https://github.com/dotansimha/graphql-code-generator) is used to automatically create TypeScript interfaces for all GraphQL server operations and admin ui queries. These generated interfaces are used in both the admin ui and the server.

+ 1 - 1
SECURITY.md

@@ -11,4 +11,4 @@
 
 ## Reporting a Vulnerability
 
-To report a security vulnarability, email [contact@vendure.io](mailto:contact@vendure.io).
+To report a security vulnerability, email [contact@vendure.io](mailto:contact@vendure.io).

+ 117 - 0
docker-compose.yml

@@ -0,0 +1,117 @@
+# This contains the services required to develop and test Vendure
+# locally. It includes multiple SQL databases (for testing specific
+# versions), Elasticsearch, Redis etc.
+version: '3.7'
+name: vendure-monorepo
+services:
+  mariadb:
+    image: 'bitnami/mariadb:latest'
+    container_name: mariadb
+    environment:
+      MARIADB_DATABASE: vendure-dev
+      MARIADB_ROOT_USER: vendure
+      MARIADB_ROOT_PASSWORD: password
+    volumes:
+      - 'mariadb_data:/bitnami'
+    ports:
+      - '3306:3306'
+  mysql_8:
+    image: bitnami/mysql:8.0
+    container_name: mysql-8
+    environment:
+      MYSQL_AUTHENTICATION_PLUGIN: mysql_native_password
+      MYSQL_DATABASE: vendure-dev
+      MYSQL_ROOT_USER: vendure
+      MYSQL_ROOT_PASSWORD: password
+    volumes:
+      - 'mysql_data:/bitnami'
+    ports:
+      - '3306:3306'
+  mysql_5:
+    image: bitnami/mysql:5.7
+    container_name: mysql-5.7
+    environment:
+      MYSQL_AUTHENTICATION_PLUGIN: mysql_native_password
+      MYSQL_DATABASE: vendure-dev
+      MYSQL_ROOT_USER: vendure
+      MYSQL_ROOT_PASSWORD: password
+    volumes:
+      - 'mysql_data:/bitnami'
+    ports:
+      - '3306:3306'
+  postgres_12:
+    image: postgres:12.3
+    container_name: postgres_12
+    environment:
+      POSTGRES_DB: vendure-dev
+      POSTGRES_USER: vendure
+      POSTGRES_PASSWORD: password
+      PGDATA: /var/lib/postgresql/data
+    volumes:
+      - postgres_12_data:/var/lib/postgresql/data
+    ports:
+      - "5432:5432"
+    command: postgres -c shared_preload_libraries=pg_stat_statements -c pg_stat_statements.track=all -c pg_stat_statements.max=100000 -c max_connections=200
+  postgres_16:
+    image: postgres:16
+    container_name: postgres_16
+    environment:
+      POSTGRES_DB: vendure-dev
+      POSTGRES_USER: vendure
+      POSTGRES_PASSWORD: password
+      PGDATA: /var/lib/postgresql/data
+    volumes:
+      - postgres_16_data:/var/lib/postgresql/data
+    ports:
+      - "5432:5432"
+    command: postgres -c shared_preload_libraries=pg_stat_statements -c pg_stat_statements.track=all -c pg_stat_statements.max=100000 -c max_connections=200
+  # This is the Keycloak service which is used
+  # to test the Keycloak auth strategy
+  keycloak:
+    image: quay.io/keycloak/keycloak
+    ports:
+      - "9000:8080"
+    environment:
+      KEYCLOAK_ADMIN: admin
+      KEYCLOAK_ADMIN_PASSWORD: admin
+    command:
+      - start-dev
+      - --import-realm
+    volumes:
+      - keycloak_data:/opt/keycloak/data
+  elasticsearch:
+    image: docker.elastic.co/elasticsearch/elasticsearch:7.10.2
+    container_name: elasticsearch
+    environment:
+      - discovery.type=single-node
+      - bootstrap.memory_lock=true
+      - "ES_JAVA_OPTS=-Xms512m -Xmx512m"
+    ulimits:
+      memlock:
+        soft: -1
+        hard: -1
+    volumes:
+      - esdata:/usr/share/elasticsearch/data
+    ports:
+      - 9200:9200
+  redis:
+    image: bitnami/redis:7.4.1
+    hostname: redis
+    container_name: redis
+    environment:
+      - ALLOW_EMPTY_PASSWORD=yes
+    ports:
+      - "6379:6379"
+volumes:
+  postgres_16_data:
+    driver: local
+  postgres_12_data:
+    driver: local
+  mariadb_data:
+    driver: local
+  mysql_data:
+    driver: local
+  keycloak_data:
+    driver: local
+  esdata:
+    driver: local

+ 12 - 0
docs/docs/guides/core-concepts/orders/index.md

@@ -352,3 +352,15 @@ If you have defined custom order states, the Admin UI will allow you to manually
 order from one state to another:
 
 ![./custom-order-ui.webp](./custom-order-ui.webp)
+
+## Order Interceptors
+
+Vendure v3.1 introduces the concept of [Order Interceptors](/reference/typescript-api/orders/order-interceptor/). 
+These are a way to intercept operations that add, modify or remove order lines. Examples use-cases include:
+
+* Preventing certain products from being added to the order based on some criteria, e.g. if the  product is already in another active order.
+* Enforcing a minimum or maximum quantity of a given product in the order
+* Using a CAPTCHA to prevent automated order creation
+
+Check the [Order Interceptor](/reference/typescript-api/orders/order-interceptor/) docs for more information as well as a complete
+example of how to implement an interceptor.

+ 4 - 1
docs/docs/guides/deployment/horizontal-scaling.md

@@ -22,7 +22,10 @@ In Vendure, both the server and the worker can be scaled horizontally. Scaling t
 In order to run Vendure in a multi-instance configuration, there are some important configuration changes you'll need to make. The key consideration in configuring Vendure for this scenario is to ensure that any persistent state is managed externally from the Node process, and is shared by all instances. Namely:
 
 * The JobQueue should be stored externally using the [DefaultJobQueuePlugin](/reference/typescript-api/job-queue/default-job-queue-plugin/) (which stores jobs in the database) or the [BullMQJobQueuePlugin](/reference/core-plugins/job-queue-plugin/bull-mqjob-queue-plugin) (which stores jobs in Redis), or some other custom JobQueueStrategy. **Note:** the BullMQJobQueuePlugin is much more efficient than the DefaultJobQueuePlugin, and is recommended for production applications.
-* A custom [SessionCacheStrategy](/reference/typescript-api/auth/session-cache-strategy/) must be used which stores the session cache externally (such as in the database or Redis), since the default strategy stores the cache in-memory and will cause inconsistencies in multi-instance setups. [Example Redis-based SessionCacheStrategy](/reference/typescript-api/auth/session-cache-strategy/)
+* An appropriate [CacheStrategy](/reference/typescript-api/cache/cache-strategy/) must be used which stores the cache externally. Both the [DefaultCachePlugin](/reference/typescript-api/cache/default-cache-plugin/) and the [RedisCachePlugin](/reference/typescript-api/cache/redis-cache-plugin/) are suitable 
+  for multi-instance setups. The DefaultCachePlugin uses the database to store the cache data, which is simple and effective, while the RedisCachePlugin uses a Redis server to store the cache data and can have better performance characteristics.
+* If you are on a version prior to v3.1, a custom [SessionCacheStrategy](/reference/typescript-api/auth/session-cache-strategy/) must be used which stores the session cache externally (such as in the database or Redis), since the default strategy stores the cache in-memory and will cause inconsistencies in multi-instance setups. [Example Redis-based SessionCacheStrategy](/reference/typescript-api/auth/session-cache-strategy/).
+  From v3.1 the session cache is handled by the underlying cache strategy, so you normally don't need to define a custom SessionCacheStrategy.
 * When using cookies to manage sessions, make sure all instances are using the _same_ cookie secret:
     ```ts title="src/vendure-config.ts"
     const config: VendureConfig = {

+ 4 - 0
docs/docs/guides/deployment/production-configuration/index.md

@@ -116,3 +116,7 @@ In **Postgres**, you can execute:
 show timezone;
 ```
 and you should expect to see `UTC` or `Etc/UTC`.
+
+## Security Considerations
+
+Please read over the [Security](/guides/developer-guide/security) section of the Developer Guide for more information on how to secure your Vendure application.

二進制
docs/docs/guides/developer-guide/cache/cache-service.webp


+ 206 - 0
docs/docs/guides/developer-guide/cache/index.mdx

@@ -0,0 +1,206 @@
+---
+title: "Cache"
+---
+
+Caching is a technique to improve performance of a system by saving the results of expensive
+operations and reusing them when the same operation is requested again.
+
+Vendure uses caching in a number of places to improve performance, and the same caching
+mechanism is available for use in custom plugins.
+
+## CacheService
+
+The [`CacheService`](/reference/typescript-api/cache/cache-service) is the general-purpose API for interacting with the cache.
+It provides methods for setting, getting and deleting cache entries.
+
+![CacheService](./cache-service.webp)
+
+Internally, the `CacheService` uses a [CacheStrategy](/reference/typescript-api/cache/cache-strategy) to store the data. The cache strategy is responsible for
+the actual storage and retrieval of the data. The `CacheService` provides a consistent API which can be used
+regardless of the underlying cache strategy.
+
+:::info
+From Vendure v3.1, new projects are created with the [DefaultCachePlugin](/reference/typescript-api/cache/default-cache-plugin) enabled by default. This plugin
+uses the database to store the cache data. This is a simple and effective cache strategy which is suitable
+for most use-cases.
+
+For more advanced use-cases, you can use the [RedisCachePlugin](/reference/typescript-api/cache/redis-cache-plugin) which uses a Redis
+server to store the cache data and can have better performance characteristics.
+:::
+
+### Multi-instance use
+
+It is common to run Vendure in a multi-instance setup, where multiple instances of the server and worker are
+running in parallel.
+
+The `CacheService` is designed to work in this environment. Both the [DefaultCachePlugin](/reference/typescript-api/cache/default-cache-plugin)
+and the [RedisCachePlugin](/reference/typescript-api/cache/redis-cache-plugin) use a single shared cache across all
+instances.
+
+This means that if one instance sets a cache entry, it will be available to all other instances. Likewise,
+if one instance deletes a cache entry, it will be deleted for all other instances.
+
+### Usage
+
+The `CacheService` can be injected into any service, resolver, strategy or configurable operation.
+
+```ts
+import { Injectable } from '@nestjs/common';
+import { CacheService } from '@vendure/core';
+
+@Injectable()
+export class MyService {
+    constructor(private cacheService: CacheService) {}
+
+    async myMethod() {
+        const cacheKey = 'MyService.myMethod';
+        const cachedValue = await this.cacheService.get(cacheKey);
+        if (cachedValue) {
+            return cachedValue;
+        }
+        const newValue = await this.expensiveOperation();
+        // Cache the result for 1 minute (60 * 1000 milliseconds)
+        await this.cacheService.set(cacheKey, newValue, { ttl: 60 * 1000 });
+        return newValue;
+    }
+
+    private async expensiveOperation() {
+        // Do something expensive
+    }
+}
+```
+
+:::info
+
+The data stored in the cache must be serializable. This means you cannot store instances of classes,
+functions, or other non-serializable data types.
+
+:::
+
+### Cache key naming
+
+When setting a cache entry, it is important to choose a unique key which will not conflict
+with other cache entries. The key should be namespaced to avoid conflicts. For example,
+you can use the name of the class & method as part of the key. If there is an identifier
+which is unique to the operation, that can be used as well.
+
+```ts
+getVariantIds(productId: ID): Promise<ID[]> {
+    const cacheKey = `ProductService.getVariantIds:${productId}`;
+    const cachedValue = await this.cacheService.get(cacheKey);
+    if (cachedValue) {
+        return cachedValue;
+    }
+    const newValue = await this.expensiveOperation(productId);
+    await this.cacheService.set(cacheKey, newValue, { ttl: 60 * 1000 });
+    return newValue;
+}
+```
+
+### Cache eviction
+
+The cache is not infinite, and entries will be evicted after a certain time. The time-to-live (TTL)
+of a cache entry can be set when calling `set()`. If no TTL is set, the cache entry will remain
+in the cache indefinitely.
+
+Cache entries can also be manually deleted using the `delete()` method:
+
+```ts
+await this.cacheService.delete(cacheKey);
+```
+
+### Cache tags
+
+When setting a cache entry, you can also specify a list of tags. This allows you to invalidate
+all cache entries which share a tag. For example, if you have a cache entry which is related to
+a Product, you can tag it with the Product's ID. When the Product is updated, you can invalidate
+all cache entries which are tagged with that Product ID.
+
+```ts
+const cacheKey = `ProductService.getVariantIds:${productId}`;
+
+await this.cacheService.set(cacheKey, newValue, {
+    tags: [`Product:${productId}`]
+});
+
+// later
+
+await this.cacheService.invalidateTags([`Product:${productId}`]);
+```
+
+### createCache Helper
+
+The `createCache` helper function can be used to create a [Cache](/reference/typescript-api/cache) instance
+which is a convenience wrapper around the `CacheService` APIs:
+
+```ts
+import { Injectable } from '@nestjs/common';
+import { CacheService, ID, EventBus, ProductEvent,RequestContext } from '@vendure/core';
+
+@Injectable()
+export class FacetValueChecker {
+    // Create a Cache instance with a 1-day TTL
+    private facetValueCache = this.cacheService.createCache({
+        getKey: (productId: ID) => `FacetValueChecker.${productId}`,
+        options: { ttl: 1000 * 60 * 60 * 24 },
+    });
+
+    constructor(private cacheService: CacheService, private eventBus: EventBus) {
+        this.eventBus.ofType(ProductEvent).subscribe(event => {
+            if (event.type !== 'created') {
+                // Invalidate the cache entry when a Product is updated or deleted
+                this.facetValueCache.delete(event.entity.id);
+            }
+        });
+    }
+
+    async getFacetValueIdsForProduct(ctx: RequestContext, productId: ID): Promise<ID[]> {
+        return this.facetValueCache.get(productId, () =>
+            // This function will only be called if the cache entry does not exist
+            // or has expired. It will set the result in the cache automatically.
+            this.calculateFacetValueIdsForProduct(ctx, productId));
+    }
+
+    async calculateFacetValueIdsForProduct(ctx: RequestContext, productId: ID): Promise<ID[]> {
+        // Do something expensive
+    }
+}
+```
+
+## RequestContextCache
+
+The [RequestContextCacheService](/reference/typescript-api/cache/request-context-cache-service) is a specialized
+cache service which is scoped to the current request. This is useful when you want to cache data
+for the duration of a single request, but not across multiple requests.
+
+This can be especially useful in resolvers, where you may want to cache the result of a specific resolved
+field which may be requested multiple times within the same request.
+
+For example, in Vendure core, when dealing with product lists, there's a particular very hot
+code path that is used to calculate the correct prices to return for each product. As part of this
+calculation, we need to know the active tax zone, which can be expensive to calculate newly
+for each product. We use the `RequestContextCacheService` to cache the active tax zone for the
+duration of the request.
+
+```ts
+const activeTaxZone = await this.requestContextCache.get(
+    ctx,
+    'activeTaxZone',
+    () => taxZoneStrategy
+        .determineTaxZone(ctx, zones, ctx.channel, order),
+);
+```
+
+Internally, the `RequestContextCacheService` makes used of the WeakMap data structure which means the cached
+data will be automatically garbage-collected when the request is finished. It is also able to store
+any kind of data, not just serializable data.
+
+## Session Cache
+
+There is an additional cache which is specifically used to cache session data, since this data is commonly
+accessed on almost all requests. Since v3.1, the default is to use the [DefaultSessionCacheStrategy](/reference/typescript-api/auth/default-session-cache-strategy)
+which internally just uses whatever the current `CacheStrategy` is to store the data.
+
+This means that in most cases you don't need to worry about the session cache, but if you have specific
+requirements, you can create a custom session cache strategy and set it via the `authOptions.sessionCacheStrategy`
+config property.

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

@@ -146,6 +146,7 @@ The following types are available for custom fields:
 | `float`        | Floating point number        | product review rating                                    |
 | `boolean`      | Boolean                      | isDownloadable flag on product                           |
 | `datetime`     | A datetime                   | date that variant is back in stock                       |
+| `struct`       | Structured json-like data    | Key-value attributes with additional data for products   |
 | `relation`     | A relation to another entity | Asset used as a customer avatar, related Products        |
 
 To see the underlying DB data type and GraphQL type used for each, see the [CustomFieldType doc](/reference/typescript-api/custom-fields/custom-field-type).
@@ -882,6 +883,111 @@ const config = {
 
 The step value. See [the MDN datetime-local docs](https://developer.mozilla.org/en-US/docs/Web/HTML/Element/input/datetime-local#step) to understand how this is used.
 
+### Properties for `struct` fields
+
+:::info
+The `struct` custom field type is available from Vendure v3.1.0.
+:::
+
+In addition to the common properties, the `struct` custom fields have some type-specific properties:
+
+- [`fields`](#fields)
+
+#### fields
+
+<CustomFieldProperty required={true} type="StructFieldConfig[]" typeLink="/reference/typescript-api/custom-fields/struct-field-config" />
+
+A `struct` is a data structure comprising a set of named fields, each with its own type. The `fields` property is an array of `StructFieldConfig` objects, each of which defines a field within the struct.
+
+```ts title="src/vendure-config.ts"
+const config = {
+    // ...
+    customFields: {
+        Product: [
+            {
+                name: 'dimensions',
+                type: 'struct',
+                // highlight-start
+                fields: [
+                    { name: 'length', type: 'int' },
+                    { name: 'width', type: 'int' },
+                    { name: 'height', type: 'int' },
+                ],
+                // highlight-end
+            },
+        ]
+    }
+};
+```
+
+When querying the `Product` entity, the `dimensions` field will be an object with the fields `length`, `width` and `height`:
+
+```graphql
+query {
+    product(id: 1) {
+        customFields {
+            dimensions {
+                length
+                width
+                height
+            }
+        }
+    }
+}
+```
+
+Struct fields support many of the same properties as other custom fields, such as `list`, `label`, `description`, `validate`, `ui` and 
+type-specific properties such as `options` and `pattern` for string types.
+
+:::note
+The following properties are **not** supported for `struct` fields: `public`, `readonly`, `internal`, `defaultValue`, `nullable`, `unique`, `requiresPermission`.
+:::
+
+```ts title="src/vendure-config.ts"
+import { LanguageCode } from '@vendure/core';
+
+const config = {
+    // ...
+    customFields: {
+        OrderLine: [
+            {
+                name: 'customizationOptions',
+                type: 'struct',
+                fields: [
+                    {
+                        name: 'color',
+                        type: 'string',
+                        // highlight-start
+                        options: [
+                            {value: 'red', label: [{languageCode: LanguageCode.en, value: 'Red'}]},
+                            {value: 'blue', label: [{languageCode: LanguageCode.en, value: 'Blue'}]},
+                        ],
+                        // highlight-end
+                    },
+                    {
+                        name: 'engraving',
+                        type: 'string',
+                        // highlight-start
+                        validate: (value: any) => {
+                            if (value.length > 20) {
+                                return 'Engraving text must be 20 characters or fewer';
+                            }
+                        },
+                    },
+                    {
+                        name: 'notifyEmailAddresses',
+                        type: 'string',
+                        // highlight-start
+                        list: true,
+                        // highlight-end
+                    }
+                ],
+            },
+        ]
+    }
+};
+```
+
 ### Properties for `relation` fields
 
 In addition to the common properties, the `relation` custom fields have some type-specific properties:

+ 1 - 1
docs/docs/guides/developer-guide/events/index.mdx

@@ -255,7 +255,7 @@ type BlogPostInputTypes = CreateBlogPostInput | UpdateBlogPostInput | ID | ID[];
  * This event is fired whenever a BlogPost is added, updated
  * or deleted.
  */
-export class BlogPostEvent extends VendureEntityEvent<BlogPost[], BlogPostInputTypes> {
+export class BlogPostEvent extends VendureEntityEvent<BlogPost, BlogPostInputTypes> {
     constructor(
         ctx: RequestContext,
         entity: BlogPost,

+ 1 - 1
docs/docs/guides/developer-guide/plugins/index.mdx

@@ -358,7 +358,7 @@ export class WishlistService {
      */
     async addItem(ctx: RequestContext, variantId: ID): Promise<WishlistItem[]> {
         const customer = await this.getCustomerWithWishlistItems(ctx);
-        const variant = this.productVariantService.findOne(ctx, variantId);
+        const variant = await this.productVariantService.findOne(ctx, variantId);
         if (!variant) {
             throw new UserInputError(`No ProductVariant with the id ${variantId} could be found`);
         }

+ 264 - 0
docs/docs/guides/developer-guide/security/index.md

@@ -0,0 +1,264 @@
+---
+title: "Security"
+---
+
+Security of your Vendure application includes considering how to prevent and protect against common security threats such as:
+
+- Data breaches
+- Unauthorized access
+- Attacks aimed at disrupting the service
+
+Vendure itself is designed with security in mind, but you must also consider the security of your own application code, the server environment, and the network architecture.
+
+## Basics
+
+Here are some basic measures you should use to secure your Vendure application. These are not exhaustive, but they are a good starting point.
+
+### Change the default credentials
+
+Do not deploy any public Vendure instance with the default superadmin credentials (`superadmin:superadmin`). Use your hosting platform's environment variables to set a **strong** password for the Superadmin account.
+
+```ts
+import { VendureConfig } from '@vendure/core';
+
+export const config: VendureConfig = {
+  authOptions: {
+    tokenMethod: ['bearer', 'cookie'],
+    superadminCredentials: {
+      identifier: process.env.SUPERADMIN_USERNAME,
+      password: process.env.SUPERADMIN_PASSWORD,
+    },
+  },
+  // ...
+};
+```
+
+### Use the HardenPlugin
+
+It is recommended that you install and configure the [HardenPlugin](/reference/core-plugins/harden-plugin/) for all production deployments. This plugin locks down your schema 
+(disabling introspection and field suggestions) and protects your Shop API against malicious queries that could otherwise overwhelm your server.
+
+Install the plugin:
+
+```sh
+npm install @vendure/harden-plugin
+
+# or
+
+yarn add @vendure/harden-plugin
+```
+
+Then add it to your VendureConfig:
+
+```ts
+import { VendureConfig } from '@vendure/core';
+import { HardenPlugin } from '@vendure/harden-plugin';
+
+const IS_DEV = process.env.APP_ENV === 'dev';
+
+export const config: VendureConfig = {
+  // ...
+  plugins: [
+    HardenPlugin.init({
+      maxQueryComplexity: 500,
+      apiMode: IS_DEV ? 'dev' : 'prod',
+    }),
+    // ...
+  ]
+};
+```
+
+:::info
+For a detailed explanation of how to best configure this plugin, see the [HardenPlugin docs](/reference/core-plugins/harden-plugin/).
+:::
+
+### Harden the AssetServerPlugin
+
+If you are using the [AssetServerPlugin](/reference/core-plugins/asset-server-plugin/), it is possible by default to use the dynamic
+image transform feature to overload the server with requests for new image sizes & formats. To prevent this, you can
+configure the plugin to only allow transformations for the preset sizes, and limited quality levels and formats.
+Since v3.1 we ship the [PresetOnlyStrategy](/reference/core-plugins/asset-server-plugin/preset-only-strategy/) for this purpose, and
+you can also create your own strategies.
+
+```ts
+import { VendureConfig } from '@vendure/core';
+import { AssetServerPlugin, PresetOnlyStrategy } from '@vendure/asset-server-plugin';
+
+export const config: VendureConfig = {
+  // ...
+  plugins: [
+    AssetServerPlugin.init({
+      // ...
+      // highlight-start  
+      imageTransformStrategy: new PresetOnlyStrategy({
+        defaultPreset: 'large',
+        permittedQuality: [0, 50, 75, 85, 95],
+        permittedFormats: ['jpg', 'webp', 'avif'],
+        allowFocalPoint: false,
+      }),
+      // highlight-end
+    }),
+  ]
+};
+```
+
+## OWASP Top Ten Security Assessment
+
+The Open Worldwide Application Security Project (OWASP) is a nonprofit foundation that works to improve the security of software.
+
+It publishes a top 10 list of common web application vulnerabilities: https://owasp.org/Top10
+
+This section assesses Vendure against this list, stating what is covered **out of the box** (built in to the framework or easily configurable) and what needs to be **additionally considered.**
+
+### 1. Broken Access Control
+
+Reference: https://owasp.org/Top10/A01_2021-Broken_Access_Control/
+
+Out of the box:
+
+- Vendure uses role-based access control
+- We deny by default for non-public API requests
+- Built-in CORS controls for session cookies
+- Directory listing is not possible via default configuration (e.g. exposing web root dir contents)
+- Stateful session identifiers should be invalidated on the server after logout. On logout we delete all session records from the DB & session cache.
+
+To consider:
+
+- Rate limit API and controller access to minimize the harm from automated attack tooling.
+
+### 2. Cryptographic Failures
+
+Reference: https://owasp.org/Top10/A02_2021-Cryptographic_Failures/
+
+Out of the box:
+
+- Vendure defaults to bcrypt with 12 salt rounds for storing passwords. This strategy is configurable if security requirements mandate alternative algorithms.
+- No deprecated hash functions (SHA1, MD5) are used in security-related contexts (only for things like creating cache keys).
+- Payment information is not stored in Vendure by default. Payment integrations rely on the payment provider to store all sensitive data.
+
+To consider:
+
+- The Vendure server will not use TLS be default. The usual configuration is to handle this at the gateway level on your production platform.
+- If a network caching layer is used (e.g. Stellate), ensure it is configured to not cache user-related data (customer details, active order etc)
+
+### 3. Injection
+
+Reference: https://owasp.org/Top10/A03_2021-Injection/
+
+Out of the box:
+
+- GraphQL has built-in validation of incoming data
+- All database operations are parameterized - no string concatenation using user-supplied data.
+- List queries apply default limits to prevent mass disclosure of records.
+
+To consider:
+
+- If using custom fields, you should consider defining a validation function to prevent bad data from getting into the database.
+
+### 4. Insecure Design
+
+Reference: https://owasp.org/Top10/A04_2021-Insecure_Design/
+
+Out of the box:
+
+- Use of established libraries for the critical underlying components: NestJS, TypeORM, Angular.
+- End-to-end tests of security-related flows such as authentication, verification, and RBAC permissions controls.
+- Harden plugin provides pre-configured protections against common attack vectors targeting GraphQL APIs.
+
+To consider:
+
+- Tiered exposure such as an API gateway which prevents exposure of the Admin API to the public internet.
+- Limit resource usage of Vendure server & worker instances via containerization.
+- Rate limiting & other network-level protections (such as Cloudflare) should be considered.
+
+### 5. Security Misconfiguration
+
+Reference: https://owasp.org/Top10/A05_2021-Security_Misconfiguration/
+
+Out of the box:
+
+- Single point of configuration for the entire application, reducing the chance of misconfiguration.
+- A default setup only requires a database, which means there are few components to configure and harden.
+- Stack traces are not leaked in API errors
+
+To consider:
+
+- Ensure the default superadmin credentials are not used in production
+- Use environment variables to turn off development features such as the GraphQL playground
+- Use the HardenPlugin in production to automatically turn of development features and restrict system information leaking via API.
+- Use fine-grained permissions and roles for your administrator accounts to reduce the attack surface if an account is compromised.
+
+### 6. Vulnerable and Outdated Components
+
+Reference: https://owasp.org/Top10/A06_2021-Vulnerable_and_Outdated_Components/
+
+Out of the box:
+
+- All dependencies are updated to current versions with each minor release
+- Modular design limits the number of dependencies for core packages.
+- Automated code & dependency scanning is used in the Vendure repo
+
+To consider:
+
+- Run your own audits on your code base.
+- Use version override mechanisms if needed to patch and critical Vendure dependencies that did not yet get updated.
+
+### 7. Identification and Authentication Failures
+
+Reference: https://owasp.org/Top10/A07_2021-Identification_and_Authentication_Failures/
+
+Out of the box:
+
+- Valid usernames are not leaked via mechanisms such as account reset
+- Does not permit "knowlege-based" account recovery
+- Uses strong password hashing (bcrypt with 12 salt rounds)
+- Session identifiers are not exposed in API urls (instead we use headers/cookies)
+- New session tokens always regenerated after successful login
+- Sessions deleted during logout
+- Cryptographically-strong, high-entropy session tokens are used (crypto.randomBytes API)
+
+To consider:
+
+- Implementing a multi-factor authentication flow
+- Do not use default superadmin credentials in production
+- Implementing a custom PasswordValidationStrategy to disallow weak/common passwords
+- Subscribe to AttemptedLoginEvent to implement detection of brute-force attacks
+
+### 8. Software and Data Integrity Failures
+
+Reference: https://owasp.org/Top10/A08_2021-Software_and_Data_Integrity_Failures/
+
+To consider:
+
+- Exercise caution when introducing new dependencies to your project.
+- Do not use untrusted Vendure plugins. Where possible review the code prior to use.
+- Exercise caution if using auto-updating mechanisms for dependencies.
+- If storing serialized data in custom fields, implement validation to prevent untrusted data getting into the database.
+- Evaluate your CI/CD pipeline against the OWASP recommendations for this point
+
+### 9. Security Logging and Monitoring Failures
+
+Reference: https://owasp.org/Top10/A09_2021-Security_Logging_and_Monitoring_Failures/
+
+Out of the box:
+
+- APIs for integrating logging & monitoring tools & services, e.g. configurable Logger interface & ErrorHandlerStrategy
+- Official Sentry integration for application performance monitoring
+
+To consider:
+
+- Integrate with dedicated logging tools for improved log management
+- Integrate with monitoring tools such as Sentry
+- Use the EventBus to monitor events such as repeated failed login attempts and high-value orders
+
+### 10. Server-Side Request Forgery (SSRF)
+
+Reference: [https://owasp.org/Top10/A10_2021-Server-Side_Request_Forgery_(SSRF)/](https://owasp.org/Top10/A10_2021-Server-Side_Request_Forgery_%28SSRF%29/)
+
+Out of the box:
+
+- By default Vendure does not rely on requests to remote servers for core functionality
+
+To consider:
+
+- Review the OWASP recommendations against your network architecture

+ 41 - 17
docs/docs/guides/getting-started/installation/index.md

@@ -8,22 +8,46 @@ import TabItem from '@theme/TabItem';
 
 ## Requirements
  
-* [Node.js](https://nodejs.org/en/) **v18** or above, with support for **even-numbered Node.js versions**.
-* The [supported TypeScript version](https://github.com/vendure-ecommerce/vendure/blob/master/packages/create/src/constants.ts#L7) is set upon installation. Upgrading to a newer version of TypeScript might result in compilation errors.
-* If you want to use MySQL, MariaDB, or Postgres as your data store, then you'll need an instance available locally. However, **if you are just testing out Vendure, we recommend using SQLite**, which has no external requirements.
-* If you use **Yarn**, from Vendure v2.2.0+, you'll need to use **Yarn 2** (Berry) or above.
+* [Node.js](https://nodejs.org/en/) **v18** or above, with support for **even-numbered Node.js versions**. (Odd-numbered versions should still work but are not officially supported.)
+
+### Optional
+* [Docker Desktop](https://www.docker.com/products/docker-desktop/): If you want to use the quick start with Postgres, you must have Docker Desktop installed. If you do not have Docker Desktop installed 
+  then SQLite will be used for your database.
+* If you want to use an existing MySQL, MariaDB, or Postgres server as your data store, then you'll need an instance available locally. However, **if you are just testing out Vendure, we recommend the quick start flow, which handles the database for you**.
 
 ## @vendure/create
 
 The recommended way to get started with Vendure is by using the [@vendure/create](https://github.com/vendure-ecommerce/vendure/tree/master/packages/create) tool. This is a command-line tool which will scaffold and configure your new Vendure project and install all dependencies.
 
-### 1. Run the command
+### Quick Start
 
-```
+First run the following command in your terminal, replacing `my-shop` with the name of your project:
+
+```bash
 npx @vendure/create my-shop
 ```
 
-### 2. Select a database
+Next choose the "Quick Start" option. This is the fastest way to get a Vendure server up and running, and will handle
+all the configuration for you. If you have Docker Desktop installed, it will create and configure a Postgres database for you. If not, it will use SQLite.
+
+```text
+┌  Let's create a Vendure App ✨
+│
+◆  How should we proceed?
+// highlight-next-line
+│  ● Quick Start (Get up an running in a single step)
+│  ○ Manual Configuration
+└
+```
+
+And that's it! After a minute or two you'll have a fully-functional Vendure server running locally.
+
+### Manual Configuration
+
+If you'd rather have more control over the configuration, you can choose the "Manual Configuration" option. 
+This will prompt you to select a database, and whether to populate the database with sample data.
+
+#### 1. Select a database
 
 Vendure supports a number of different databases. The `@vendure/create` tool will prompt you to select one. 
 
@@ -40,7 +64,7 @@ If you select MySQL, MariaDB or Postgres, you need to make sure you:
 3. know the username and password for a user with access to that database
 :::
 
-### 3. Populate with data
+#### 2. Populate with data
 
 The final prompt will ask whether to populate your new Vendure server with some sample product data.
 
@@ -50,7 +74,7 @@ building your own storefront.
 
 ![Vendure Create step 2](./create-2.webp)
 
-### 4. Complete setup
+#### 3. Complete setup
 
 Next, a project scaffold will be created and dependencies installed. This may take a few minutes.
 
@@ -59,15 +83,13 @@ Once complete, you'll see a message like this:
 ![Vendure Create step 3](./create-3.webp)
 
 
-### 5. Start the server
+### Start the server
 
 Follow the instructions to move into the new directory created for your project, and start the server:
 
 ```bash
 cd my-shop
 
-yarn dev
-# or
 npm run dev
 ```
 
@@ -97,11 +119,13 @@ Use `npx vendure add` to start adding plugins & custom functionality to your Ven
 
 ### Troubleshooting
 
-If you encounter any issues during installation, you can get a more detailed output by setting the log level to `verbose`:
-
-```sh
-npx @vendure/create my-shop --log-level verbose
-```
+- If you encounter any issues during installation, you can get a more detailed output by setting the log level to `verbose`:
+   ```sh
+   npx @vendure/create my-shop --log-level verbose
+   ```
+- The [supported TypeScript version](https://github.com/vendure-ecommerce/vendure/blob/master/packages/create/src/constants.ts#L7) is set upon installation. Upgrading to a newer version of TypeScript might result in compilation errors because
+  TypeScript sometimes introduces stricter checks in newer versions. 
+- If you want to use **Yarn**, from Vendure v2.2.0+, you'll need to use **Yarn 2** (Berry) or above.
 
 ## Set up a storefront
 

+ 0 - 4
docs/docs/guides/storefront/active-order/index.mdx

@@ -190,10 +190,6 @@ To remove a coupon code from the active order, we use the [`removeCouponCode` mu
 mutation RemoveCouponCode($couponCode: String!) {
   removeCouponCode(couponCode: $couponCode) {
     ...ActiveOrder
-    ... on ErrorResult {
-      errorCode
-      message
-    }
   }
 }
 ```

+ 14 - 14
docs/docs/reference/admin-ui-api/alerts/alert-config.md

@@ -11,7 +11,7 @@ import MemberDescription from '@site/src/components/MemberDescription';
 
 ## AlertConfig
 
-<GenerationInfo sourceFile="packages/admin-ui/src/lib/core/src/providers/alerts/alerts.service.ts" sourceLine="62" packageName="@vendure/admin-ui" since="2.2.0" />
+<GenerationInfo sourceFile="packages/admin-ui/src/lib/core/src/providers/alerts/alerts.service.ts" sourceLine="63" packageName="@vendure/admin-ui" since="2.2.0" />
 
 A configuration object for an Admin UI alert.
 
@@ -22,9 +22,9 @@ interface AlertConfig<T = any> {
     recheck?: (context: AlertContext) => Observable<any>;
     isAlert: (data: T, context: AlertContext) => boolean;
     action: (data: T, context: AlertContext) => void;
-    label: (
-        data: T,
-        context: AlertContext,
+    label: (
+        data: T,
+        context: AlertContext,
     ) => { text: string; translationVars?: { [key: string]: string | number } };
     requiredPermissions?: Permission[];
 }
@@ -41,18 +41,18 @@ A unique identifier for the alert.
 
 <MemberInfo kind="property" type={`(context: <a href='/reference/admin-ui-api/alerts/alert-context#alertcontext'>AlertContext</a>) =&#62; T | Promise&#60;T&#62; | Observable&#60;T&#62;`}   />
 
-A function which is gets the data used to determine whether the alert should be shown.
-Typically, this function will query the server or some other remote data source.
-
-This function will be called once when the Admin UI app bootstraps, and can be also
+A function which is gets the data used to determine whether the alert should be shown.
+Typically, this function will query the server or some other remote data source.
+
+This function will be called once when the Admin UI app bootstraps, and can be also
 set to run at regular intervals by setting the `recheckIntervalMs` property.
 ### recheck
 
 <MemberInfo kind="property" type={`(context: <a href='/reference/admin-ui-api/alerts/alert-context#alertcontext'>AlertContext</a>) =&#62; Observable&#60;any&#62;`} default={`undefined`}   />
 
-A function which returns an Observable which is used to determine when to re-run the `check`
-function. Whenever the observable emits, the `check` function will be called again.
-
+A function which returns an Observable which is used to determine when to re-run the `check`
+function. Whenever the observable emits, the `check` function will be called again.
+
 A basic time-interval-based recheck can be achieved by using the `interval` function from RxJS.
 
 *Example*
@@ -69,7 +69,7 @@ If this is not set, the `check` function will only be called once when the Admin
 
 <MemberInfo kind="property" type={`(data: T, context: <a href='/reference/admin-ui-api/alerts/alert-context#alertcontext'>AlertContext</a>) =&#62; boolean`}   />
 
-A function which determines whether the alert should be shown based on the data returned by the `check`
+A function which determines whether the alert should be shown based on the data returned by the `check`
 function.
 ### action
 
@@ -78,14 +78,14 @@ function.
 A function which is called when the alert is clicked in the Admin UI.
 ### label
 
-<MemberInfo kind="property" type={`(
         data: T,
         context: <a href='/reference/admin-ui-api/alerts/alert-context#alertcontext'>AlertContext</a>,
     ) =&#62; { text: string; translationVars?: { [key: string]: string | number } }`}   />
+<MemberInfo kind="property" type={`(         data: T,         context: <a href='/reference/admin-ui-api/alerts/alert-context#alertcontext'>AlertContext</a>,     ) =&#62; { text: string; translationVars?: { [key: string]: string | number } }`}   />
 
 A function which returns the text used in the UI to describe the alert.
 ### requiredPermissions
 
 <MemberInfo kind="property" type={`<a href='/reference/typescript-api/common/permission#permission'>Permission</a>[]`}   />
 
-A list of permissions which the current Administrator must have in order. If the current
+A list of permissions which the current Administrator must have in order. If the current
 Administrator does not have these permissions, none of the other alert functions will be called.
 
 

+ 1 - 1
docs/docs/reference/admin-ui-api/alerts/alert-context.md

@@ -11,7 +11,7 @@ import MemberDescription from '@site/src/components/MemberDescription';
 
 ## AlertContext
 
-<GenerationInfo sourceFile="packages/admin-ui/src/lib/core/src/providers/alerts/alerts.service.ts" sourceLine="28" packageName="@vendure/admin-ui" since="2.2.0" />
+<GenerationInfo sourceFile="packages/admin-ui/src/lib/core/src/providers/alerts/alerts.service.ts" sourceLine="29" packageName="@vendure/admin-ui" since="2.2.0" />
 
 The context object which is passed to the `check`, `isAlert`, `label` and `action` functions of an
 <a href='/reference/admin-ui-api/alerts/alert-config#alertconfig'>AlertConfig</a> object.

+ 1 - 1
docs/docs/reference/admin-ui-api/components/asset-picker-dialog-component.md

@@ -11,7 +11,7 @@ import MemberDescription from '@site/src/components/MemberDescription';
 
 ## AssetPickerDialogComponent
 
-<GenerationInfo sourceFile="packages/admin-ui/src/lib/core/src/shared/components/asset-picker-dialog/asset-picker-dialog.component.ts" sourceLine="52" packageName="@vendure/admin-ui" />
+<GenerationInfo sourceFile="packages/admin-ui/src/lib/core/src/shared/components/asset-picker-dialog/asset-picker-dialog.component.ts" sourceLine="51" packageName="@vendure/admin-ui" />
 
 A dialog which allows the creation and selection of assets.
 

+ 14 - 8
docs/docs/reference/admin-ui-api/components/data-table2component.md

@@ -84,6 +84,7 @@ class DataTable2Component<T> implements AfterContentInit, OnChanges, OnDestroy {
     @Input() activeIndex = -1;
     @Output() pageChange = new EventEmitter<number>();
     @Output() itemsPerPageChange = new EventEmitter<number>();
+    @Output() visibleColumnsChange = new EventEmitter<Array<DataTable2ColumnComponent<T>>>();
     @ContentChildren(DataTable2ColumnComponent) columns: QueryList<DataTable2ColumnComponent<T>>;
     @ContentChildren(DataTableCustomFieldColumnComponent)
     customFieldColumns: QueryList<DataTableCustomFieldColumnComponent<T>>;
@@ -95,6 +96,7 @@ class DataTable2Component<T> implements AfterContentInit, OnChanges, OnDestroy {
     route = inject(ActivatedRoute);
     filterPresetService = inject(FilterPresetService);
     dataTableCustomComponentService = inject(DataTableCustomComponentService);
+    dataTableConfigService = inject(DataTableConfigService);
     protected customComponents = new Map<string, { config: DataTableComponentConfig; injector: Injector }>();
     rowTemplate: TemplateRef<any>;
     currentStart: number;
@@ -103,7 +105,7 @@ class DataTable2Component<T> implements AfterContentInit, OnChanges, OnDestroy {
     showSearchFilterRow = false;
     protected uiLanguage$: Observable<LanguageCode>;
     protected destroy$ = new Subject<void>();
-    constructor(changeDetectorRef: ChangeDetectorRef, localStorageService: LocalStorageService, dataService: DataService)
+    constructor(changeDetectorRef: ChangeDetectorRef, dataService: DataService)
     selectionManager: void
     allColumns: void
     visibleSortedColumns: void
@@ -117,7 +119,6 @@ class DataTable2Component<T> implements AfterContentInit, OnChanges, OnDestroy {
     trackByFn(index: number, item: any) => ;
     onToggleAllClick() => ;
     onRowClick(item: T, event: MouseEvent) => ;
-    getDataTableConfig() => DataTableConfig;
 }
 ```
 * Implements: <code>AfterContentInit</code>, <code>OnChanges</code>, <code>OnDestroy</code>
@@ -176,6 +177,11 @@ class DataTable2Component<T> implements AfterContentInit, OnChanges, OnDestroy {
 <MemberInfo kind="property" type={``}   />
 
 
+### visibleColumnsChange
+
+<MemberInfo kind="property" type={``}   />
+
+
 ### columns
 
 <MemberInfo kind="property" type={`QueryList&#60;DataTable2ColumnComponent&#60;T&#62;&#62;`}   />
@@ -226,6 +232,11 @@ class DataTable2Component<T> implements AfterContentInit, OnChanges, OnDestroy {
 <MemberInfo kind="property" type={``}   />
 
 
+### dataTableConfigService
+
+<MemberInfo kind="property" type={``}   />
+
+
 ### customComponents
 
 <MemberInfo kind="property" type={``}   />
@@ -268,7 +279,7 @@ class DataTable2Component<T> implements AfterContentInit, OnChanges, OnDestroy {
 
 ### constructor
 
-<MemberInfo kind="method" type={`(changeDetectorRef: ChangeDetectorRef, localStorageService: LocalStorageService, dataService: <a href='/reference/admin-ui-api/services/data-service#dataservice'>DataService</a>) => DataTable2Component`}   />
+<MemberInfo kind="method" type={`(changeDetectorRef: ChangeDetectorRef, dataService: <a href='/reference/admin-ui-api/services/data-service#dataservice'>DataService</a>) => DataTable2Component`}   />
 
 
 ### selectionManager
@@ -336,11 +347,6 @@ class DataTable2Component<T> implements AfterContentInit, OnChanges, OnDestroy {
 <MemberInfo kind="method" type={`(item: T, event: MouseEvent) => `}   />
 
 
-### getDataTableConfig
-
-<MemberInfo kind="method" type={`() => DataTableConfig`}   />
-
-
 
 
 </div>

+ 1 - 1
docs/docs/reference/admin-ui-api/components/product-variant-selector-component.md

@@ -19,7 +19,7 @@ A component for selecting product variants via an autocomplete-style select inpu
 
 ```HTML
 <vdr-product-variant-selector
-  (productSelected)="selectResult($event)"></vdr-product-selector>
+  (productSelected)="selectResult($event)"></vdr-product-variant-selector>
 ```
 
 ```ts title="Signature"

+ 99 - 15
docs/docs/reference/admin-ui-api/custom-input-components/default-inputs.md

@@ -58,7 +58,7 @@ class BooleanFormInputComponent implements FormInputComponent {
 
 <GenerationInfo sourceFile="packages/admin-ui/src/lib/core/src/shared/dynamic-form-inputs/code-editor-form-input/html-editor-form-input.component.ts" sourceLine="23" packageName="@vendure/admin-ui" />
 
-A JSON editor input with syntax highlighting and error detection. Works well
+A JSON editor input with syntax highlighting and error detection. Works well
 with `text` type fields.
 
 ```ts title="Signature"
@@ -101,7 +101,7 @@ class HtmlEditorFormInputComponent extends BaseCodeEditorFormInputComponent impl
 
 <GenerationInfo sourceFile="packages/admin-ui/src/lib/core/src/shared/dynamic-form-inputs/code-editor-form-input/json-editor-form-input.component.ts" sourceLine="33" packageName="@vendure/admin-ui" />
 
-A JSON editor input with syntax highlighting and error detection. Works well
+A JSON editor input with syntax highlighting and error detection. Works well
 with `text` type fields.
 
 ```ts title="Signature"
@@ -276,7 +276,7 @@ class CurrencyFormInputComponent implements FormInputComponent {
 
 <GenerationInfo sourceFile="packages/admin-ui/src/lib/core/src/shared/dynamic-form-inputs/customer-group-form-input/customer-group-form-input.component.ts" sourceLine="20" packageName="@vendure/admin-ui" />
 
-Allows the selection of a Customer via an autocomplete select input.
+Allows the selection of a Customer via an autocomplete select input.
 Should be used with `ID` type fields which represent Customer IDs.
 
 ```ts title="Signature"
@@ -415,7 +415,7 @@ class DateFormInputComponent implements FormInputComponent {
 
 <GenerationInfo sourceFile="packages/admin-ui/src/lib/core/src/shared/dynamic-form-inputs/facet-value-form-input/facet-value-form-input.component.ts" sourceLine="16" packageName="@vendure/admin-ui" />
 
-Allows the selection of multiple FacetValues via an autocomplete select input.
+Allows the selection of multiple FacetValues via an autocomplete select input.
 Should be used with `ID` type **list** fields which represent FacetValue IDs.
 
 ```ts title="Signature"
@@ -425,13 +425,13 @@ class FacetValueFormInputComponent implements FormInputComponent {
     readonly: boolean;
     formControl: UntypedFormControl;
     config: InputComponentConfig;
-    valueTransformFn = (values: FacetValueFragment[]) => {
-        const isUsedInConfigArg = this.config.__typename === 'ConfigArgDefinition';
-        if (isUsedInConfigArg) {
-            return JSON.stringify(values.map(s => s.id));
-        } else {
-            return values;
-        }
+    valueTransformFn = (values: FacetValueFragment[]) => {
+        const isUsedInConfigArg = this.config.__typename === 'ConfigArgDefinition';
+        if (isUsedInConfigArg) {
+            return JSON.stringify(values.map(s => s.id));
+        } else {
+            return values;
+        }
     };
 }
 ```
@@ -600,7 +600,7 @@ class PasswordFormInputComponent implements FormInputComponent {
 
 <GenerationInfo sourceFile="packages/admin-ui/src/lib/core/src/shared/dynamic-form-inputs/product-selector-form-input/product-selector-form-input.component.ts" sourceLine="20" packageName="@vendure/admin-ui" />
 
-Allows the selection of multiple ProductVariants via an autocomplete select input.
+Allows the selection of multiple ProductVariants via an autocomplete select input.
 Should be used with `ID` type **list** fields which represent ProductVariant IDs.
 
 ```ts title="Signature"
@@ -682,8 +682,8 @@ class ProductSelectorFormInputComponent implements FormInputComponent, OnInit {
 
 <GenerationInfo sourceFile="packages/admin-ui/src/lib/core/src/shared/dynamic-form-inputs/relation-form-input/relation-form-input.component.ts" sourceLine="17" packageName="@vendure/admin-ui" />
 
-The default input component for `relation` type custom fields. Allows the selection
-of a ProductVariant, Product, Customer or Asset. For other entity types, a custom
+The default input component for `relation` type custom fields. Allows the selection
+of a ProductVariant, Product, Customer or Asset. For other entity types, a custom
 implementation will need to be defined. See <a href='/reference/admin-ui-api/custom-input-components/register-form-input-component#registerforminputcomponent'>registerFormInputComponent</a>.
 
 ```ts title="Signature"
@@ -774,7 +774,7 @@ class RichTextFormInputComponent implements FormInputComponent {
 
 <GenerationInfo sourceFile="packages/admin-ui/src/lib/core/src/shared/dynamic-form-inputs/select-form-input/select-form-input.component.ts" sourceLine="18" packageName="@vendure/admin-ui" />
 
-Uses a select input to allow the selection of a string value. Should be used with
+Uses a select input to allow the selection of a string value. Should be used with
 `string` type fields with options.
 
 ```ts title="Signature"
@@ -843,6 +843,90 @@ class SelectFormInputComponent implements FormInputComponent, OnInit {
 
 
 
+</div>
+
+
+## StructFormInputComponent
+
+<GenerationInfo sourceFile="packages/admin-ui/src/lib/core/src/shared/dynamic-form-inputs/struct-form-input/struct-form-input.component.ts" sourceLine="18" packageName="@vendure/admin-ui" />
+
+A checkbox input. The default input component for `boolean` fields.
+
+```ts title="Signature"
+class StructFormInputComponent implements FormInputComponent, OnInit, OnDestroy {
+    static readonly id: DefaultFormComponentId = 'struct-form-input';
+    readonly: boolean;
+    formControl: UntypedFormControl;
+    config: DefaultFormComponentConfig<'struct-form-input'>;
+    uiLanguage$: Observable<LanguageCode>;
+    protected structFormGroup = new FormGroup({});
+    protected fields: Array<{
+        def: StructCustomFieldFragment['fields'][number];
+        formControl: FormControl;
+    }>;
+    constructor(dataService: DataService)
+    ngOnInit() => ;
+    ngOnDestroy() => ;
+}
+```
+* Implements: <code><a href='/reference/admin-ui-api/custom-input-components/form-input-component#forminputcomponent'>FormInputComponent</a></code>, <code>OnInit</code>, <code>OnDestroy</code>
+
+
+
+<div className="members-wrapper">
+
+### id
+
+<MemberInfo kind="property" type={`<a href='/reference/typescript-api/configurable-operation-def/default-form-component-id#defaultformcomponentid'>DefaultFormComponentId</a>`}   />
+
+
+### readonly
+
+<MemberInfo kind="property" type={`boolean`}   />
+
+
+### formControl
+
+<MemberInfo kind="property" type={`UntypedFormControl`}   />
+
+
+### config
+
+<MemberInfo kind="property" type={`DefaultFormComponentConfig&#60;'struct-form-input'&#62;`}   />
+
+
+### uiLanguage$
+
+<MemberInfo kind="property" type={`Observable&#60;<a href='/reference/typescript-api/common/language-code#languagecode'>LanguageCode</a>&#62;`}   />
+
+
+### structFormGroup
+
+<MemberInfo kind="property" type={``}   />
+
+
+### fields
+
+<MemberInfo kind="property" type={`Array&#60;{
         def: StructCustomFieldFragment['fields'][number];
         formControl: FormControl;
     }&#62;`}   />
+
+
+### constructor
+
+<MemberInfo kind="method" type={`(dataService: <a href='/reference/admin-ui-api/services/data-service#dataservice'>DataService</a>) => StructFormInputComponent`}   />
+
+
+### ngOnInit
+
+<MemberInfo kind="method" type={`() => `}   />
+
+
+### ngOnDestroy
+
+<MemberInfo kind="method" type={`() => `}   />
+
+
+
+
 </div>
 
 

+ 15 - 1
docs/docs/reference/admin-ui-api/list-detail-views/base-list-component.md

@@ -11,7 +11,7 @@ import MemberDescription from '@site/src/components/MemberDescription';
 
 ## BaseListComponent
 
-<GenerationInfo sourceFile="packages/admin-ui/src/lib/core/src/common/base-list.component.ts" sourceLine="40" packageName="@vendure/admin-ui" />
+<GenerationInfo sourceFile="packages/admin-ui/src/lib/core/src/common/base-list.component.ts" sourceLine="43" packageName="@vendure/admin-ui" />
 
 This is a base class which implements the logic required to fetch and manipulate
 a list of data from a query which returns a PaginatedList type.
@@ -33,11 +33,15 @@ class BaseListComponent<ResultType, ItemType, VariableType extends Record<string
     currentPage$: Observable<number>;
     protected destroy$ = new Subject<void>();
     protected refresh$ = new BehaviorSubject<undefined>(undefined);
+    protected visibleCustomFieldColumnChange$ = new Subject<
+        Array<DataTableCustomFieldColumnComponent<any>>
+    >();
     constructor(router: Router, route: ActivatedRoute)
     setQueryFn(listQueryFn: ListQueryFn<ResultType>, mappingFn: MappingFn<ItemType, ResultType>, onPageChangeFn?: OnPageChangeFn<VariableType>, defaults?: { take: number; skip: number }) => ;
     refreshListOnChanges(streams: Array<Observable<any>>) => ;
     setPageNumber(page: number) => ;
     setItemsPerPage(perPage: number) => ;
+    setVisibleColumns(columns: Array<DataTable2ColumnComponent<any>>) => ;
     refresh() => ;
     setQueryParam(hash: { [key: string]: any }, options?: { replaceUrl?: boolean; queryParamsHandling?: QueryParamsHandling }) => ;
     setQueryParam(key: string, value: any, options?: { replaceUrl?: boolean; queryParamsHandling?: QueryParamsHandling }) => ;
@@ -95,6 +99,11 @@ class BaseListComponent<ResultType, ItemType, VariableType extends Record<string
 <MemberInfo kind="property" type={``}   />
 
 
+### visibleCustomFieldColumnChange$
+
+<MemberInfo kind="property" type={``}   />
+
+
 ### constructor
 
 <MemberInfo kind="method" type={`(router: Router, route: ActivatedRoute) => BaseListComponent`}   />
@@ -120,6 +129,11 @@ Sets the current page number in the url.
 <MemberInfo kind="method" type={`(perPage: number) => `}   />
 
 Sets the number of items per page in the url.
+### setVisibleColumns
+
+<MemberInfo kind="method" type={`(columns: Array&#60;DataTable2ColumnComponent&#60;any&#62;&#62;) => `}   />
+
+
 ### refresh
 
 <MemberInfo kind="method" type={`() => `}   />

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

@@ -11,7 +11,7 @@ import MemberDescription from '@site/src/components/MemberDescription';
 
 ## TypedBaseListComponent
 
-<GenerationInfo sourceFile="packages/admin-ui/src/lib/core/src/common/base-list.component.ts" sourceLine="199" packageName="@vendure/admin-ui" />
+<GenerationInfo sourceFile="packages/admin-ui/src/lib/core/src/common/base-list.component.ts" sourceLine="217" packageName="@vendure/admin-ui" />
 
 A version of the <a href='/reference/admin-ui-api/list-detail-views/base-list-component#baselistcomponent'>BaseListComponent</a> which is designed to be used with a
 [TypedDocumentNode](https://the-guild.dev/graphql/codegen/plugins/typescript/typed-document-node).
@@ -24,6 +24,8 @@ class TypedBaseListComponent<T extends TypedDocumentNode<any, Vars>, Field exten
     protected router = inject(Router);
     protected serverConfigService = inject(ServerConfigService);
     protected permissionsService = inject(PermissionsService);
+    protected dataTableConfigService = inject(DataTableConfigService);
+    protected dataTableListId: string | undefined;
     constructor()
     configure(config: {
         document: T;
@@ -77,6 +79,16 @@ class TypedBaseListComponent<T extends TypedDocumentNode<any, Vars>, Field exten
 <MemberInfo kind="property" type={``}   />
 
 
+### dataTableConfigService
+
+<MemberInfo kind="property" type={``}   />
+
+
+### dataTableListId
+
+<MemberInfo kind="property" type={`string | undefined`}   />
+
+
 ### constructor
 
 <MemberInfo kind="method" type={`() => TypedBaseListComponent`}   />

+ 20 - 13
docs/docs/reference/admin-ui-api/services/data-service.md

@@ -20,8 +20,8 @@ to be effectively cached.
 
 ```ts title="Signature"
 class DataService {
-    query(query: DocumentNode | TypedDocumentNode<T, V>, variables?: V, fetchPolicy: WatchQueryFetchPolicy = 'cache-and-network') => QueryResult<T, V>;
-    mutate(mutation: DocumentNode | TypedDocumentNode<T, V>, variables?: V, update?: MutationUpdaterFn<T>) => Observable<T>;
+    query(query: DocumentNode | TypedDocumentNode<T, V>, variables?: V, fetchPolicy: WatchQueryFetchPolicy = 'cache-and-network', options: ExtendedQueryOptions = {}) => QueryResult<T, V>;
+    mutate(mutation: DocumentNode | TypedDocumentNode<T, V>, variables?: V, update?: MutationUpdaterFn<T>, options: ExtendedQueryOptions = {}) => Observable<T>;
 }
 ```
 
@@ -29,7 +29,7 @@ class DataService {
 
 ### query
 
-<MemberInfo kind="method" type={`(query: DocumentNode | TypedDocumentNode&#60;T, V&#62;, variables?: V, fetchPolicy: WatchQueryFetchPolicy = 'cache-and-network') => <a href='/reference/admin-ui-api/services/data-service#queryresult'>QueryResult</a>&#60;T, V&#62;`}   />
+<MemberInfo kind="method" type={`(query: DocumentNode | TypedDocumentNode&#60;T, V&#62;, variables?: V, fetchPolicy: WatchQueryFetchPolicy = 'cache-and-network', options: ExtendedQueryOptions = {}) => <a href='/reference/admin-ui-api/services/data-service#queryresult'>QueryResult</a>&#60;T, V&#62;`}   />
 
 Perform a GraphQL query. Returns a <a href='/reference/admin-ui-api/services/data-service#queryresult'>QueryResult</a> which allows further control over
 they type of result returned, e.g. stream of values, single value etc.
@@ -50,7 +50,7 @@ const result$ = this.dataService.query(gql`
 ```
 ### mutate
 
-<MemberInfo kind="method" type={`(mutation: DocumentNode | TypedDocumentNode&#60;T, V&#62;, variables?: V, update?: MutationUpdaterFn&#60;T&#62;) => Observable&#60;T&#62;`}   />
+<MemberInfo kind="method" type={`(mutation: DocumentNode | TypedDocumentNode&#60;T, V&#62;, variables?: V, update?: MutationUpdaterFn&#60;T&#62;, options: ExtendedQueryOptions = {}) => Observable&#60;T&#62;`}   />
 
 Perform a GraphQL mutation.
 
@@ -74,21 +74,22 @@ const result$ = this.dataService.mutate(gql`
 
 ## QueryResult
 
-<GenerationInfo sourceFile="packages/admin-ui/src/lib/core/src/data/query-result.ts" sourceLine="19" packageName="@vendure/admin-ui" />
+<GenerationInfo sourceFile="packages/admin-ui/src/lib/core/src/data/query-result.ts" sourceLine="31" packageName="@vendure/admin-ui" />
 
 This class wraps the Apollo Angular QueryRef object and exposes some getters
 for convenience.
 
 ```ts title="Signature"
 class QueryResult<T, V extends Record<string, any> = Record<string, any>> {
-    constructor(queryRef: QueryRef<T, V>, apollo: Apollo)
-    completed$ = new Subject<void>();
+    constructor(queryRef: QueryRef<T, V>, apollo: Apollo, customFieldMap: Map<string, CustomFieldConfig[]>)
     refetchOnChannelChange() => QueryResult<T, V>;
+    refetchOnCustomFieldsChange(customFieldsToInclude$: Observable<string[]>) => QueryResult<T, V>;
     single$: Observable<T>
     stream$: Observable<T>
     ref: QueryRef<T, V>
     mapSingle(mapFn: (item: T) => R) => Observable<R>;
     mapStream(mapFn: (item: T) => R) => Observable<R>;
+    destroy() => ;
 }
 ```
 
@@ -96,12 +97,7 @@ class QueryResult<T, V extends Record<string, any> = Record<string, any>> {
 
 ### constructor
 
-<MemberInfo kind="method" type={`(queryRef: QueryRef&#60;T, V&#62;, apollo: Apollo) => QueryResult`}   />
-
-
-### completed$
-
-<MemberInfo kind="property" type={``}   />
+<MemberInfo kind="method" type={`(queryRef: QueryRef&#60;T, V&#62;, apollo: Apollo, customFieldMap: Map&#60;string, <a href='/reference/typescript-api/custom-fields/custom-field-config#customfieldconfig'>CustomFieldConfig</a>[]&#62;) => QueryResult`}   />
 
 
 ### refetchOnChannelChange
@@ -109,6 +105,12 @@ class QueryResult<T, V extends Record<string, any> = Record<string, any>> {
 <MemberInfo kind="method" type={`() => <a href='/reference/admin-ui-api/services/data-service#queryresult'>QueryResult</a>&#60;T, V&#62;`}   />
 
 Re-fetch this query whenever the active Channel changes.
+### refetchOnCustomFieldsChange
+
+<MemberInfo kind="method" type={`(customFieldsToInclude$: Observable&#60;string[]&#62;) => <a href='/reference/admin-ui-api/services/data-service#queryresult'>QueryResult</a>&#60;T, V&#62;`}  since="3.0.4"  />
+
+Re-fetch this query whenever the custom fields change, updating the query to include the
+specified custom fields.
 ### single$
 
 <MemberInfo kind="property" type={`Observable&#60;T&#62;`}   />
@@ -134,6 +136,11 @@ Returns a single-result Observable after applying the map function.
 <MemberInfo kind="method" type={`(mapFn: (item: T) =&#62; R) => Observable&#60;R&#62;`}   />
 
 Returns a multiple-result Observable after applying the map function.
+### destroy
+
+<MemberInfo kind="method" type={`() => `}   />
+
+Signals to the internal Observable subscriptions that they should complete.
 
 
 </div>

+ 1 - 1
docs/docs/reference/core-plugins/admin-ui-plugin/admin-ui-plugin-options.md

@@ -11,7 +11,7 @@ import MemberDescription from '@site/src/components/MemberDescription';
 
 ## AdminUiPluginOptions
 
-<GenerationInfo sourceFile="packages/admin-ui-plugin/src/plugin.ts" sourceLine="43" packageName="@vendure/admin-ui-plugin" />
+<GenerationInfo sourceFile="packages/admin-ui-plugin/src/plugin.ts" sourceLine="44" packageName="@vendure/admin-ui-plugin" />
 
 Configuration options for the <a href='/reference/core-plugins/admin-ui-plugin/#adminuiplugin'>AdminUiPlugin</a>.
 

+ 1 - 1
docs/docs/reference/core-plugins/admin-ui-plugin/index.md

@@ -11,7 +11,7 @@ import MemberDescription from '@site/src/components/MemberDescription';
 
 ## AdminUiPlugin
 
-<GenerationInfo sourceFile="packages/admin-ui-plugin/src/plugin.ts" sourceLine="129" packageName="@vendure/admin-ui-plugin" />
+<GenerationInfo sourceFile="packages/admin-ui-plugin/src/plugin.ts" sourceLine="130" packageName="@vendure/admin-ui-plugin" />
 
 This plugin starts a static server for the Admin UI app, and proxies it via the `/admin/` path of the main Vendure server.
 

+ 13 - 1
docs/docs/reference/core-plugins/asset-server-plugin/asset-server-options.md

@@ -11,7 +11,7 @@ import MemberDescription from '@site/src/components/MemberDescription';
 
 ## AssetServerOptions
 
-<GenerationInfo sourceFile="packages/asset-server-plugin/src/types.ts" sourceLine="72" packageName="@vendure/asset-server-plugin" />
+<GenerationInfo sourceFile="packages/asset-server-plugin/src/types.ts" sourceLine="74" packageName="@vendure/asset-server-plugin" />
 
 The configuration options for the AssetServerPlugin.
 
@@ -23,6 +23,7 @@ interface AssetServerOptions {
     previewMaxWidth?: number;
     previewMaxHeight?: number;
     presets?: ImageTransformPreset[];
+    imageTransformStrategy?: ImageTransformStrategy | ImageTransformStrategy[];
     namingStrategy?: AssetNamingStrategy;
     previewStrategy?: AssetPreviewStrategy;
     storageStrategyFactory?: (
@@ -70,6 +71,17 @@ The max height in pixels of a generated preview image.
 <MemberInfo kind="property" type={`<a href='/reference/core-plugins/asset-server-plugin/image-transform-preset#imagetransformpreset'>ImageTransformPreset</a>[]`}   />
 
 An array of additional <a href='/reference/core-plugins/asset-server-plugin/image-transform-preset#imagetransformpreset'>ImageTransformPreset</a> objects.
+### imageTransformStrategy
+
+<MemberInfo kind="property" type={`<a href='/reference/core-plugins/asset-server-plugin/image-transform-strategy#imagetransformstrategy'>ImageTransformStrategy</a> | <a href='/reference/core-plugins/asset-server-plugin/image-transform-strategy#imagetransformstrategy'>ImageTransformStrategy</a>[]`} default={`[]`}  since="3.1.0"  />
+
+The strategy or strategies to use to determine the parameters for transforming an image.
+This can be used to implement custom image transformation logic, for example to
+limit transform parameters to a known set of presets.
+
+If multiple strategies are provided, they will be executed in the order in which they are defined.
+If a strategy throws an error, the image transformation will be aborted and the error
+will be logged, with an HTTP 400 response sent to the client.
 ### namingStrategy
 
 <MemberInfo kind="property" type={`<a href='/reference/typescript-api/assets/asset-naming-strategy#assetnamingstrategy'>AssetNamingStrategy</a>`} default={`<a href='/reference/core-plugins/asset-server-plugin/hashed-asset-naming-strategy#hashedassetnamingstrategy'>HashedAssetNamingStrategy</a>`}   />

+ 1 - 1
docs/docs/reference/core-plugins/asset-server-plugin/cache-config.md

@@ -11,7 +11,7 @@ import MemberDescription from '@site/src/components/MemberDescription';
 
 ## CacheConfig
 
-<GenerationInfo sourceFile="packages/asset-server-plugin/src/types.ts" sourceLine="52" packageName="@vendure/asset-server-plugin" />
+<GenerationInfo sourceFile="packages/asset-server-plugin/src/types.ts" sourceLine="54" packageName="@vendure/asset-server-plugin" />
 
 A configuration option for the Cache-Control header in the AssetServerPlugin asset response.
 

+ 1 - 1
docs/docs/reference/core-plugins/asset-server-plugin/hashed-asset-naming-strategy.md

@@ -11,7 +11,7 @@ import MemberDescription from '@site/src/components/MemberDescription';
 
 ## HashedAssetNamingStrategy
 
-<GenerationInfo sourceFile="packages/asset-server-plugin/src/hashed-asset-naming-strategy.ts" sourceLine="20" packageName="@vendure/asset-server-plugin" />
+<GenerationInfo sourceFile="packages/asset-server-plugin/src/config/hashed-asset-naming-strategy.ts" sourceLine="20" packageName="@vendure/asset-server-plugin" />
 
 An extension of the <a href='/reference/typescript-api/assets/default-asset-naming-strategy#defaultassetnamingstrategy'>DefaultAssetNamingStrategy</a> which prefixes file names with
 the type (`'source'` or `'preview'`) as well as a 2-character sub-directory based on

+ 1 - 1
docs/docs/reference/core-plugins/asset-server-plugin/image-transform-mode.md

@@ -11,7 +11,7 @@ import MemberDescription from '@site/src/components/MemberDescription';
 
 ## ImageTransformMode
 
-<GenerationInfo sourceFile="packages/asset-server-plugin/src/types.ts" sourceLine="21" packageName="@vendure/asset-server-plugin" />
+<GenerationInfo sourceFile="packages/asset-server-plugin/src/types.ts" sourceLine="23" packageName="@vendure/asset-server-plugin" />
 
 Specifies the way in which an asset preview image will be resized to fit in the
 proscribed dimensions:

+ 1 - 1
docs/docs/reference/core-plugins/asset-server-plugin/image-transform-preset.md

@@ -11,7 +11,7 @@ import MemberDescription from '@site/src/components/MemberDescription';
 
 ## ImageTransformPreset
 
-<GenerationInfo sourceFile="packages/asset-server-plugin/src/types.ts" sourceLine="39" packageName="@vendure/asset-server-plugin" />
+<GenerationInfo sourceFile="packages/asset-server-plugin/src/types.ts" sourceLine="41" packageName="@vendure/asset-server-plugin" />
 
 A configuration option for an image size preset for the AssetServerPlugin.
 

+ 148 - 0
docs/docs/reference/core-plugins/asset-server-plugin/image-transform-strategy.md

@@ -0,0 +1,148 @@
+---
+title: "ImageTransformStrategy"
+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';
+
+
+## ImageTransformStrategy
+
+<GenerationInfo sourceFile="packages/asset-server-plugin/src/config/image-transform-strategy.ts" sourceLine="56" packageName="@vendure/asset-server-plugin" since="3.1.0" />
+
+An injectable strategy which is used to determine the parameters for transforming an image.
+This can be used to implement custom image transformation logic, for example to
+limit transform parameters to a known set of presets.
+
+This is set via the `imageTransformStrategy` option in the AssetServerOptions. Multiple
+strategies can be defined and will be executed in the order in which they are defined.
+
+If a strategy throws an error, the image transformation will be aborted and the error
+will be logged, with an HTTP 400 response sent to the client.
+
+```ts title="Signature"
+interface ImageTransformStrategy extends InjectableStrategy {
+    getImageTransformParameters(
+        args: GetImageTransformParametersArgs,
+    ): Promise<ImageTransformParameters> | ImageTransformParameters;
+}
+```
+* Extends: <code><a href='/reference/typescript-api/common/injectable-strategy#injectablestrategy'>InjectableStrategy</a></code>
+
+
+
+<div className="members-wrapper">
+
+### getImageTransformParameters
+
+<MemberInfo kind="method" type={`(args: <a href='/reference/core-plugins/asset-server-plugin/image-transform-strategy#getimagetransformparametersargs'>GetImageTransformParametersArgs</a>) => Promise&#60;<a href='/reference/core-plugins/asset-server-plugin/image-transform-strategy#imagetransformparameters'>ImageTransformParameters</a>&#62; | <a href='/reference/core-plugins/asset-server-plugin/image-transform-strategy#imagetransformparameters'>ImageTransformParameters</a>`}   />
+
+Given the input parameters, return the parameters which should be used to transform the image.
+
+
+</div>
+
+
+## ImageTransformParameters
+
+<GenerationInfo sourceFile="packages/asset-server-plugin/src/config/image-transform-strategy.ts" sourceLine="14" packageName="@vendure/asset-server-plugin" since="3.1.0" />
+
+Parameters which are used to transform the image.
+
+```ts title="Signature"
+interface ImageTransformParameters {
+    width: number | undefined;
+    height: number | undefined;
+    mode: ImageTransformMode | undefined;
+    quality: number | undefined;
+    format: ImageTransformFormat | undefined;
+    fpx: number | undefined;
+    fpy: number | undefined;
+    preset: string | undefined;
+}
+```
+
+<div className="members-wrapper">
+
+### width
+
+<MemberInfo kind="property" type={`number | undefined`}   />
+
+
+### height
+
+<MemberInfo kind="property" type={`number | undefined`}   />
+
+
+### mode
+
+<MemberInfo kind="property" type={`<a href='/reference/core-plugins/asset-server-plugin/image-transform-mode#imagetransformmode'>ImageTransformMode</a> | undefined`}   />
+
+
+### quality
+
+<MemberInfo kind="property" type={`number | undefined`}   />
+
+
+### format
+
+<MemberInfo kind="property" type={`ImageTransformFormat | undefined`}   />
+
+
+### fpx
+
+<MemberInfo kind="property" type={`number | undefined`}   />
+
+
+### fpy
+
+<MemberInfo kind="property" type={`number | undefined`}   />
+
+
+### preset
+
+<MemberInfo kind="property" type={`string | undefined`}   />
+
+
+
+
+</div>
+
+
+## GetImageTransformParametersArgs
+
+<GenerationInfo sourceFile="packages/asset-server-plugin/src/config/image-transform-strategy.ts" sourceLine="33" packageName="@vendure/asset-server-plugin" since="3.1.0" />
+
+The arguments passed to the `getImageTransformParameters` method of an ImageTransformStrategy.
+
+```ts title="Signature"
+interface GetImageTransformParametersArgs {
+    req: Request;
+    availablePresets: ImageTransformPreset[];
+    input: ImageTransformParameters;
+}
+```
+
+<div className="members-wrapper">
+
+### req
+
+<MemberInfo kind="property" type={`Request`}   />
+
+
+### availablePresets
+
+<MemberInfo kind="property" type={`<a href='/reference/core-plugins/asset-server-plugin/image-transform-preset#imagetransformpreset'>ImageTransformPreset</a>[]`}   />
+
+
+### input
+
+<MemberInfo kind="property" type={`<a href='/reference/core-plugins/asset-server-plugin/image-transform-strategy#imagetransformparameters'>ImageTransformParameters</a>`}   />
+
+
+
+
+</div>

+ 32 - 5
docs/docs/reference/core-plugins/asset-server-plugin/index.md

@@ -11,7 +11,7 @@ import MemberDescription from '@site/src/components/MemberDescription';
 
 ## AssetServerPlugin
 
-<GenerationInfo sourceFile="packages/asset-server-plugin/src/plugin.ts" sourceLine="153" packageName="@vendure/asset-server-plugin" />
+<GenerationInfo sourceFile="packages/asset-server-plugin/src/plugin.ts" sourceLine="176" packageName="@vendure/asset-server-plugin" />
 
 The `AssetServerPlugin` serves assets (images and other files) from the local file system, and can also be configured to use
 other storage strategies (e.g. <a href='/reference/core-plugins/asset-server-plugin/s3asset-storage-strategy#s3assetstoragestrategy'>S3AssetStorageStrategy</a>. It can also perform on-the-fly image transformations
@@ -133,14 +133,41 @@ large | 800px | 800px | resize
 By default, the AssetServerPlugin will cache every transformed image, so that the transformation only needs to be performed a single time for
 a given configuration. Caching can be disabled per-request by setting the `?cache=false` query parameter.
 
+### Limiting transformations
+
+By default, the AssetServerPlugin will allow any transformation to be performed on an image. However, it is possible to restrict the transformations
+which can be performed by using an <a href='/reference/core-plugins/asset-server-plugin/image-transform-strategy#imagetransformstrategy'>ImageTransformStrategy</a>. This can be used to limit the transformations to a known set of presets, for example.
+
+This is advisable in order to prevent abuse of the image transformation feature, as it can be computationally expensive.
+
+Since v3.1.0 we ship with a <a href='/reference/core-plugins/asset-server-plugin/preset-only-strategy#presetonlystrategy'>PresetOnlyStrategy</a> which allows only transformations using a known set of presets.
+
+*Example*
+
+```ts
+import { AssetServerPlugin, PresetOnlyStrategy } from '@vendure/core';
+
+// ...
+
+AssetServerPlugin.init({
+  //...
+  imageTransformStrategy: new PresetOnlyStrategy({
+    defaultPreset: 'thumbnail',
+    permittedQuality: [0, 50, 75, 85, 95],
+    permittedFormats: ['jpg', 'webp', 'avif'],
+    allowFocalPoint: false,
+  }),
+});
+```
+
 ```ts title="Signature"
-class AssetServerPlugin implements NestModule, OnApplicationBootstrap {
+class AssetServerPlugin implements NestModule, OnApplicationBootstrap, OnApplicationShutdown {
     init(options: AssetServerOptions) => Type<AssetServerPlugin>;
-    constructor(processContext: ProcessContext)
+    constructor(options: AssetServerOptions, processContext: ProcessContext, moduleRef: ModuleRef, assetServer: AssetServer)
     configure(consumer: MiddlewareConsumer) => ;
 }
 ```
-* Implements: <code>NestModule</code>, <code>OnApplicationBootstrap</code>
+* Implements: <code>NestModule</code>, <code>OnApplicationBootstrap</code>, <code>OnApplicationShutdown</code>
 
 
 
@@ -153,7 +180,7 @@ class AssetServerPlugin implements NestModule, OnApplicationBootstrap {
 Set the plugin options.
 ### constructor
 
-<MemberInfo kind="method" type={`(processContext: <a href='/reference/typescript-api/common/process-context#processcontext'>ProcessContext</a>) => AssetServerPlugin`}   />
+<MemberInfo kind="method" type={`(options: <a href='/reference/core-plugins/asset-server-plugin/asset-server-options#assetserveroptions'>AssetServerOptions</a>, processContext: <a href='/reference/typescript-api/common/process-context#processcontext'>ProcessContext</a>, moduleRef: ModuleRef, assetServer: AssetServer) => AssetServerPlugin`}   />
 
 
 ### configure

+ 1 - 1
docs/docs/reference/core-plugins/asset-server-plugin/local-asset-storage-strategy.md

@@ -11,7 +11,7 @@ import MemberDescription from '@site/src/components/MemberDescription';
 
 ## LocalAssetStorageStrategy
 
-<GenerationInfo sourceFile="packages/asset-server-plugin/src/local-asset-storage-strategy.ts" sourceLine="15" packageName="@vendure/asset-server-plugin" />
+<GenerationInfo sourceFile="packages/asset-server-plugin/src/config/local-asset-storage-strategy.ts" sourceLine="15" packageName="@vendure/asset-server-plugin" />
 
 A persistence strategy which saves files to the local file system.
 

+ 122 - 0
docs/docs/reference/core-plugins/asset-server-plugin/preset-only-strategy.md

@@ -0,0 +1,122 @@
+---
+title: "PresetOnlyStrategy"
+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';
+
+
+## PresetOnlyStrategy
+
+<GenerationInfo sourceFile="packages/asset-server-plugin/src/config/preset-only-strategy.ts" sourceLine="85" packageName="@vendure/asset-server-plugin" since="3.1.0" />
+
+An <a href='/reference/core-plugins/asset-server-plugin/image-transform-strategy#imagetransformstrategy'>ImageTransformStrategy</a> which only allows transformations to be made using
+presets which are defined in the available presets.
+
+With this strategy enabled, requests to the asset server must include a `preset` parameter (or use the default preset)
+
+This is valid: `http://localhost:3000/assets/some-asset.jpg?preset=medium`
+
+This is invalid: `http://localhost:3000/assets/some-asset.jpg?w=200&h=200`, and the dimensions will be ignored.
+
+The strategy can be configured to allow only certain quality values and formats, and to
+optionally allow the focal point to be specified in the URL.
+
+If a preset is not found in the available presets, an error will be thrown.
+
+*Example*
+
+```ts
+import { AssetServerPlugin, PresetOnlyStrategy } from '@vendure/core';
+
+// ...
+
+AssetServerPlugin.init({
+  //...
+  imageTransformStrategy: new PresetOnlyStrategy({
+    defaultPreset: 'thumbnail',
+    permittedQuality: [0, 50, 75, 85, 95],
+    permittedFormats: ['jpg', 'webp', 'avif'],
+    allowFocalPoint: true,
+  }),
+});
+```
+
+```ts title="Signature"
+class PresetOnlyStrategy implements ImageTransformStrategy {
+    constructor(options: PresetOnlyStrategyOptions)
+    getImageTransformParameters({
+        input,
+        availablePresets,
+    }: GetImageTransformParametersArgs) => Promise<ImageTransformParameters> | ImageTransformParameters;
+}
+```
+* Implements: <code><a href='/reference/core-plugins/asset-server-plugin/image-transform-strategy#imagetransformstrategy'>ImageTransformStrategy</a></code>
+
+
+
+<div className="members-wrapper">
+
+### constructor
+
+<MemberInfo kind="method" type={`(options: <a href='/reference/core-plugins/asset-server-plugin/preset-only-strategy#presetonlystrategyoptions'>PresetOnlyStrategyOptions</a>) => PresetOnlyStrategy`}   />
+
+
+### getImageTransformParameters
+
+<MemberInfo kind="method" type={`({
+        input,
+        availablePresets,
+    }: <a href='/reference/core-plugins/asset-server-plugin/image-transform-strategy#getimagetransformparametersargs'>GetImageTransformParametersArgs</a>) => Promise&#60;<a href='/reference/core-plugins/asset-server-plugin/image-transform-strategy#imagetransformparameters'>ImageTransformParameters</a>&#62; | <a href='/reference/core-plugins/asset-server-plugin/image-transform-strategy#imagetransformparameters'>ImageTransformParameters</a>`}   />
+
+
+
+
+</div>
+
+
+## PresetOnlyStrategyOptions
+
+<GenerationInfo sourceFile="packages/asset-server-plugin/src/config/preset-only-strategy.ts" sourceLine="16" packageName="@vendure/asset-server-plugin" />
+
+Configuration options for the <a href='/reference/core-plugins/asset-server-plugin/preset-only-strategy#presetonlystrategy'>PresetOnlyStrategy</a>.
+
+```ts title="Signature"
+interface PresetOnlyStrategyOptions {
+    defaultPreset: string;
+    permittedQuality?: number[];
+    permittedFormats?: ImageTransformFormat[];
+    allowFocalPoint?: boolean;
+}
+```
+
+<div className="members-wrapper">
+
+### defaultPreset
+
+<MemberInfo kind="property" type={`string`}   />
+
+The name of the default preset to use if no preset is specified in the URL.
+### permittedQuality
+
+<MemberInfo kind="property" type={`number[]`} default={`[0, 50, 75, 85, 95]`}   />
+
+The permitted quality of the transformed images. If set to 'any', then any quality is permitted.
+If set to an array of numbers (0-100), then only those quality values are permitted.
+### permittedFormats
+
+<MemberInfo kind="property" type={`ImageTransformFormat[]`} default={`['jpg', 'webp', 'avif']`}   />
+
+The permitted formats of the transformed images. If set to 'any', then any format is permitted.
+If set to an array of strings e.g. `['jpg', 'webp']`, then only those formats are permitted.
+### allowFocalPoint
+
+<MemberInfo kind="property" type={`boolean`} default={`false`}   />
+
+Whether to allow the focal point to be specified in the URL.
+
+
+</div>

+ 3 - 3
docs/docs/reference/core-plugins/asset-server-plugin/s3asset-storage-strategy.md

@@ -11,7 +11,7 @@ import MemberDescription from '@site/src/components/MemberDescription';
 
 ## S3AssetStorageStrategy
 
-<GenerationInfo sourceFile="packages/asset-server-plugin/src/s3-asset-storage-strategy.ts" sourceLine="155" packageName="@vendure/asset-server-plugin" />
+<GenerationInfo sourceFile="packages/asset-server-plugin/src/config/s3-asset-storage-strategy.ts" sourceLine="155" packageName="@vendure/asset-server-plugin" />
 
 An <a href='/reference/typescript-api/assets/asset-storage-strategy#assetstoragestrategy'>AssetStorageStrategy</a> which uses [Amazon S3](https://aws.amazon.com/s3/) object storage service.
 To us this strategy you must first have access to an AWS account.
@@ -100,7 +100,7 @@ class S3AssetStorageStrategy implements AssetStorageStrategy {
 
 ## S3Config
 
-<GenerationInfo sourceFile="packages/asset-server-plugin/src/s3-asset-storage-strategy.ts" sourceLine="19" packageName="@vendure/asset-server-plugin" />
+<GenerationInfo sourceFile="packages/asset-server-plugin/src/config/s3-asset-storage-strategy.ts" sourceLine="19" packageName="@vendure/asset-server-plugin" />
 
 Configuration for connecting to AWS S3.
 
@@ -149,7 +149,7 @@ Using type `any` in order to avoid the need to include `aws-sdk` dependency in g
 
 ## configureS3AssetStorage
 
-<GenerationInfo sourceFile="packages/asset-server-plugin/src/s3-asset-storage-strategy.ts" sourceLine="119" packageName="@vendure/asset-server-plugin" />
+<GenerationInfo sourceFile="packages/asset-server-plugin/src/config/s3-asset-storage-strategy.ts" sourceLine="119" packageName="@vendure/asset-server-plugin" />
 
 Returns a configured instance of the <a href='/reference/core-plugins/asset-server-plugin/s3asset-storage-strategy#s3assetstoragestrategy'>S3AssetStorageStrategy</a> which can then be passed to the <a href='/reference/core-plugins/asset-server-plugin/asset-server-options#assetserveroptions'>AssetServerOptions</a>
 `storageStrategyFactory` property.

+ 2 - 2
docs/docs/reference/core-plugins/asset-server-plugin/sharp-asset-preview-strategy.md

@@ -11,7 +11,7 @@ import MemberDescription from '@site/src/components/MemberDescription';
 
 ## SharpAssetPreviewStrategy
 
-<GenerationInfo sourceFile="packages/asset-server-plugin/src/sharp-asset-preview-strategy.ts" sourceLine="95" packageName="@vendure/asset-server-plugin" />
+<GenerationInfo sourceFile="packages/asset-server-plugin/src/config/sharp-asset-preview-strategy.ts" sourceLine="95" packageName="@vendure/asset-server-plugin" />
 
 This <a href='/reference/typescript-api/assets/asset-preview-strategy#assetpreviewstrategy'>AssetPreviewStrategy</a> uses the [Sharp library](https://sharp.pixelplumbing.com/) to generate
 preview images of uploaded binary files. For non-image binaries, a generic "file" icon with the mime type
@@ -62,7 +62,7 @@ class SharpAssetPreviewStrategy implements AssetPreviewStrategy {
 
 ## SharpAssetPreviewConfig
 
-<GenerationInfo sourceFile="packages/asset-server-plugin/src/sharp-asset-preview-strategy.ts" sourceLine="17" packageName="@vendure/asset-server-plugin" />
+<GenerationInfo sourceFile="packages/asset-server-plugin/src/config/sharp-asset-preview-strategy.ts" sourceLine="17" packageName="@vendure/asset-server-plugin" />
 
 This <a href='/reference/typescript-api/assets/asset-preview-strategy#assetpreviewstrategy'>AssetPreviewStrategy</a> uses the [Sharp library](https://sharp.pixelplumbing.com/) to generate
 preview images of uploaded binary files. For non-image binaries, a generic "file" icon with the mime type

+ 1 - 1
docs/docs/reference/core-plugins/elasticsearch-plugin/index.md

@@ -11,7 +11,7 @@ import MemberDescription from '@site/src/components/MemberDescription';
 
 ## ElasticsearchPlugin
 
-<GenerationInfo sourceFile="packages/elasticsearch-plugin/src/plugin.ts" sourceLine="223" packageName="@vendure/elasticsearch-plugin" />
+<GenerationInfo sourceFile="packages/elasticsearch-plugin/src/plugin.ts" sourceLine="224" packageName="@vendure/elasticsearch-plugin" />
 
 This plugin allows your product search to be powered by [Elasticsearch](https://github.com/elastic/elasticsearch) - a powerful Open Source search
 engine. This is a drop-in replacement for the DefaultSearchPlugin which exposes many powerful configuration options enabling your storefront

+ 1 - 1
docs/docs/reference/core-plugins/email-plugin/email-event-handler-with-async-data.md

@@ -11,7 +11,7 @@ import MemberDescription from '@site/src/components/MemberDescription';
 
 ## EmailEventHandlerWithAsyncData
 
-<GenerationInfo sourceFile="packages/email-plugin/src/handler/event-handler.ts" sourceLine="455" packageName="@vendure/email-plugin" />
+<GenerationInfo sourceFile="packages/email-plugin/src/handler/event-handler.ts" sourceLine="492" packageName="@vendure/email-plugin" />
 
 Identical to the <a href='/reference/core-plugins/email-plugin/email-event-handler#emaileventhandler'>EmailEventHandler</a> but with a `data` property added to the `event` based on the result
 of the `.loadData()` function.

+ 27 - 1
docs/docs/reference/core-plugins/email-plugin/email-event-handler.md

@@ -11,7 +11,7 @@ import MemberDescription from '@site/src/components/MemberDescription';
 
 ## EmailEventHandler
 
-<GenerationInfo sourceFile="packages/email-plugin/src/handler/event-handler.ts" sourceLine="135" packageName="@vendure/email-plugin" />
+<GenerationInfo sourceFile="packages/email-plugin/src/handler/event-handler.ts" sourceLine="136" packageName="@vendure/email-plugin" />
 
 The EmailEventHandler defines how the EmailPlugin will respond to a given event.
 
@@ -135,6 +135,7 @@ class EmailEventHandler<T extends string = string, Event extends EventWithContex
     setSubject(defaultSubject: string | SetSubjectFn<Event>) => EmailEventHandler<T, Event>;
     setFrom(from: string) => EmailEventHandler<T, Event>;
     setOptionalAddressFields(optionalAddressFieldsFn: SetOptionalAddressFieldsFn<Event>) => ;
+    setMetadata(optionalSetMetadataFn: SetMetadataFn<Event>) => ;
     setAttachments(setAttachmentsFn: SetAttachmentsFn<Event>) => ;
     addTemplate(config: EmailTemplateConfig) => EmailEventHandler<T, Event>;
     loadData(loadDataFn: LoadDataFn<Event, R>) => EmailEventHandlerWithAsyncData<R, T, Event, EventWithAsyncData<Event, R>>;
@@ -192,6 +193,31 @@ setTemplateVars() method.
 <MemberInfo kind="method" type={`(optionalAddressFieldsFn: <a href='/reference/core-plugins/email-plugin/email-plugin-types#setoptionaladdressfieldsfn'>SetOptionalAddressFieldsFn</a>&#60;Event&#62;) => `}  since="1.1.0"  />
 
 A function which allows <a href='/reference/core-plugins/email-plugin/email-plugin-types#optionaladdressfields'>OptionalAddressFields</a> to be specified such as "cc" and "bcc".
+### setMetadata
+
+<MemberInfo kind="method" type={`(optionalSetMetadataFn: <a href='/reference/core-plugins/email-plugin/email-plugin-types#setmetadatafn'>SetMetadataFn</a>&#60;Event&#62;) => `}  since="3.1.0"  />
+
+A function which allows <a href='/reference/core-plugins/email-plugin/email-plugin-types#emailmetadata'>EmailMetadata</a> to be specified for the email. This can be used
+to store arbitrary data about the email which can be used for tracking or other purposes.
+
+It will be exposed in the <a href='/reference/core-plugins/email-plugin/email-send-event#emailsendevent'>EmailSendEvent</a> as `event.metadata`. Here's an example of usage:
+
+- An <a href='/reference/typescript-api/events/event-types#orderstatetransitionevent'>OrderStateTransitionEvent</a> occurs, and the EmailEventListener starts processing it.
+- The EmailEventHandler attaches metadata to the email:
+   ```ts
+   new EmailEventListener(EventType.ORDER_CONFIRMATION)
+     .on(OrderStateTransitionEvent)
+     .setMetadata(event => ({
+       type: EventType.ORDER_CONFIRMATION,
+       orderId: event.order.id,
+     }));
+  ```
+- Then, the EmailPlugin tries to send the email and publishes <a href='/reference/core-plugins/email-plugin/email-send-event#emailsendevent'>EmailSendEvent</a>,
+  passing ctx, emailDetails, error or success, and this metadata.
+- In another part of the server, we have an eventBus that subscribes to EmailSendEvent. We can use
+  `metadata.type` and `metadata.orderId` to identify the related order. For example, we can indicate on the
+   order that the email was successfully sent, or in case of an error, send a notification confirming
+   the order in another available way.
 ### setAttachments
 
 <MemberInfo kind="method" type={`(setAttachmentsFn: <a href='/reference/core-plugins/email-plugin/email-plugin-types#setattachmentsfn'>SetAttachmentsFn</a>&#60;Event&#62;) => `}   />

+ 28 - 6
docs/docs/reference/core-plugins/email-plugin/email-plugin-types.md

@@ -130,7 +130,7 @@ type EmailAttachment = Omit<Attachment, 'raw'> & { path?: string }
 
 ## LoadTemplateInput
 
-<GenerationInfo sourceFile="packages/email-plugin/src/types.ts" sourceLine="401" packageName="@vendure/email-plugin" />
+<GenerationInfo sourceFile="packages/email-plugin/src/types.ts" sourceLine="402" packageName="@vendure/email-plugin" />
 
 The object passed to the <a href='/reference/core-plugins/email-plugin/template-loader#templateloader'>TemplateLoader</a> `loadTemplate()` method.
 
@@ -168,7 +168,7 @@ EmailEventHandler's `setTemplateVars()` method.
 
 ## SetTemplateVarsFn
 
-<GenerationInfo sourceFile="packages/email-plugin/src/types.ts" sourceLine="434" packageName="@vendure/email-plugin" />
+<GenerationInfo sourceFile="packages/email-plugin/src/types.ts" sourceLine="435" packageName="@vendure/email-plugin" />
 
 A function used to define template variables available to email templates.
 See <a href='/reference/core-plugins/email-plugin/email-event-handler#emaileventhandler'>EmailEventHandler</a>.setTemplateVars().
@@ -183,7 +183,7 @@ type SetTemplateVarsFn<Event> = (
 
 ## SetAttachmentsFn
 
-<GenerationInfo sourceFile="packages/email-plugin/src/types.ts" sourceLine="448" packageName="@vendure/email-plugin" />
+<GenerationInfo sourceFile="packages/email-plugin/src/types.ts" sourceLine="449" packageName="@vendure/email-plugin" />
 
 A function used to define attachments to be sent with the email.
 See https://nodemailer.com/message/attachments/ for more information about
@@ -196,7 +196,7 @@ type SetAttachmentsFn<Event> = (event: Event) => EmailAttachment[] | Promise<Ema
 
 ## SetSubjectFn
 
-<GenerationInfo sourceFile="packages/email-plugin/src/types.ts" sourceLine="456" packageName="@vendure/email-plugin" />
+<GenerationInfo sourceFile="packages/email-plugin/src/types.ts" sourceLine="457" packageName="@vendure/email-plugin" />
 
 A function used to define the subject to be sent with the email.
 
@@ -211,7 +211,7 @@ type SetSubjectFn<Event> = (
 
 ## OptionalAddressFields
 
-<GenerationInfo sourceFile="packages/email-plugin/src/types.ts" sourceLine="470" packageName="@vendure/email-plugin" since="1.1.0" />
+<GenerationInfo sourceFile="packages/email-plugin/src/types.ts" sourceLine="471" packageName="@vendure/email-plugin" since="1.1.0" />
 
 Optional address-related fields for sending the email.
 
@@ -247,7 +247,7 @@ An email address that will appear on the _Reply-To:_ field
 
 ## SetOptionalAddressFieldsFn
 
-<GenerationInfo sourceFile="packages/email-plugin/src/types.ts" sourceLine="496" packageName="@vendure/email-plugin" since="1.1.0" />
+<GenerationInfo sourceFile="packages/email-plugin/src/types.ts" sourceLine="497" packageName="@vendure/email-plugin" since="1.1.0" />
 
 A function used to set the <a href='/reference/core-plugins/email-plugin/email-plugin-types#optionaladdressfields'>OptionalAddressFields</a>.
 
@@ -256,3 +256,25 @@ type SetOptionalAddressFieldsFn<Event> = (
     event: Event,
 ) => OptionalAddressFields | Promise<OptionalAddressFields>
 ```
+
+
+## SetMetadataFn
+
+<GenerationInfo sourceFile="packages/email-plugin/src/types.ts" sourceLine="509" packageName="@vendure/email-plugin" since="3.1.0" />
+
+A function used to set the <a href='/reference/core-plugins/email-plugin/email-plugin-types#emailmetadata'>EmailMetadata</a>.
+
+```ts title="Signature"
+type SetMetadataFn<Event> = (event: Event) => EmailMetadata | Promise<EmailMetadata>
+```
+
+
+## EmailMetadata
+
+<GenerationInfo sourceFile="packages/email-plugin/src/types.ts" sourceLine="519" packageName="@vendure/email-plugin" since="3.1.0" />
+
+Metadata that can be attached to an email via the <a href='/reference/core-plugins/email-plugin/email-event-handler#emaileventhandler'>EmailEventHandler</a>`.setMetadata()` method.
+
+```ts title="Signature"
+type EmailMetadata = Record<string, any>
+```

+ 2 - 2
docs/docs/reference/core-plugins/email-plugin/email-send-event.md

@@ -19,7 +19,7 @@ which occurred.
 
 ```ts title="Signature"
 class EmailSendEvent extends VendureEvent {
-    constructor(ctx: RequestContext, details: EmailDetails, success: boolean, error?: Error)
+    constructor(ctx: RequestContext, details: EmailDetails, success: boolean, error?: Error, metadata?: EmailMetadata)
 }
 ```
 * Extends: <code><a href='/reference/typescript-api/events/vendure-event#vendureevent'>VendureEvent</a></code>
@@ -30,7 +30,7 @@ class EmailSendEvent extends VendureEvent {
 
 ### constructor
 
-<MemberInfo kind="method" type={`(ctx: <a href='/reference/typescript-api/request/request-context#requestcontext'>RequestContext</a>, details: <a href='/reference/core-plugins/email-plugin/email-plugin-types#emaildetails'>EmailDetails</a>, success: boolean, error?: Error) => EmailSendEvent`}   />
+<MemberInfo kind="method" type={`(ctx: <a href='/reference/typescript-api/request/request-context#requestcontext'>RequestContext</a>, details: <a href='/reference/core-plugins/email-plugin/email-plugin-types#emaildetails'>EmailDetails</a>, success: boolean, error?: Error, metadata?: <a href='/reference/core-plugins/email-plugin/email-plugin-types#emailmetadata'>EmailMetadata</a>) => EmailSendEvent`}   />
 
 
 

+ 2 - 2
docs/docs/reference/core-plugins/payments-plugin/mollie-plugin.md

@@ -11,7 +11,7 @@ import MemberDescription from '@site/src/components/MemberDescription';
 
 ## MolliePlugin
 
-<GenerationInfo sourceFile="packages/payments-plugin/src/mollie/mollie.plugin.ts" sourceLine="192" packageName="@vendure/payments-plugin" />
+<GenerationInfo sourceFile="packages/payments-plugin/src/mollie/mollie.plugin.ts" sourceLine="191" packageName="@vendure/payments-plugin" />
 
 Plugin to enable payments through the [Mollie platform](https://docs.mollie.com/).
 This plugin uses the Order API from Mollie, not the Payments API.
@@ -145,7 +145,7 @@ Initialize the mollie payment plugin
 
 ## MolliePluginOptions
 
-<GenerationInfo sourceFile="packages/payments-plugin/src/mollie/mollie.plugin.ts" sourceLine="29" packageName="@vendure/payments-plugin" />
+<GenerationInfo sourceFile="packages/payments-plugin/src/mollie/mollie.plugin.ts" sourceLine="28" packageName="@vendure/payments-plugin" />
 
 Configuration options for the Mollie payments plugin.
 

+ 34 - 1
docs/docs/reference/core-plugins/payments-plugin/stripe-plugin.md

@@ -182,7 +182,7 @@ Initialize the Stripe payment plugin
 
 ## StripePluginOptions
 
-<GenerationInfo sourceFile="packages/payments-plugin/src/stripe/types.ts" sourceLine="27" packageName="@vendure/payments-plugin" />
+<GenerationInfo sourceFile="packages/payments-plugin/src/stripe/types.ts" sourceLine="29" packageName="@vendure/payments-plugin" />
 
 Configuration options for the Stripe payments plugin.
 
@@ -199,6 +199,11 @@ interface StripePluginOptions {
         ctx: RequestContext,
         order: Order,
     ) => AdditionalPaymentIntentCreateParams | Promise<AdditionalPaymentIntentCreateParams>;
+    requestOptions?: (
+        injector: Injector,
+        ctx: RequestContext,
+        order: Order,
+    ) => AdditionalRequestOptions | Promise<AdditionalRequestOptions>;
     customerCreateParams?: (
         injector: Injector,
         ctx: RequestContext,
@@ -275,6 +280,34 @@ export const config: VendureConfig = {
   ],
 };
 ```
+### requestOptions
+
+<MemberInfo kind="property" type={`(
         injector: <a href='/reference/typescript-api/common/injector#injector'>Injector</a>,
         ctx: <a href='/reference/typescript-api/request/request-context#requestcontext'>RequestContext</a>,
         order: <a href='/reference/typescript-api/entities/order#order'>Order</a>,
     ) =&#62; AdditionalRequestOptions | Promise&#60;AdditionalRequestOptions&#62;`}  since="3.1.0"  />
+
+Provide additional options to the Stripe payment intent creation. By default,
+the plugin will already pass the `idempotencyKey` parameter.
+
+For example, if you want to provide a `stripeAccount` for the payment intent, you can do so like this:
+
+*Example*
+
+```ts
+import { VendureConfig } from '@vendure/core';
+import { StripePlugin } from '@vendure/payments-plugin/package/stripe';
+
+export const config: VendureConfig = {
+  // ...
+  plugins: [
+    StripePlugin.init({
+      requestOptions: (injector, ctx, order) => {
+        return {
+          stripeAccount: ctx.channel.seller?.customFields.connectedAccountId
+        },
+      }
+    }),
+  ],
+};
+```
 ### customerCreateParams
 
 <MemberInfo kind="property" type={`(
         injector: <a href='/reference/typescript-api/common/injector#injector'>Injector</a>,
         ctx: <a href='/reference/typescript-api/request/request-context#requestcontext'>RequestContext</a>,
         order: <a href='/reference/typescript-api/entities/order#order'>Order</a>,
     ) =&#62; AdditionalCustomerCreateParams | Promise&#60;AdditionalCustomerCreateParams&#62;`}  since="2.1.0"  />

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

@@ -423,6 +423,7 @@ import MemberDescription from '@site/src/components/MemberDescription';
 <div class="graphql-code-line ">ORDER_MODIFICATION_ERROR</div>
 <div class="graphql-code-line ">INELIGIBLE_SHIPPING_METHOD_ERROR</div>
 <div class="graphql-code-line ">NO_ACTIVE_ORDER_ERROR</div>
+<div class="graphql-code-line ">ORDER_INTERCEPTOR_ERROR</div>
 <div class="graphql-code-line top-level">&#125;</div>
 </div>
 

+ 5 - 9
docs/docs/reference/graphql-api/admin/input-types.md

@@ -2917,20 +2917,16 @@ import MemberDescription from '@site/src/components/MemberDescription';
 
 <div class="graphql-code-block">
 <div class="graphql-code-line top-level">input <span class="graphql-code-identifier">RefundOrderInput</span> &#123;</div>
-<div class="graphql-code-line ">lines: [<a href="/reference/graphql-api/admin/input-types#orderlineinput">OrderLineInput</a>!]!</div>
+<div class="graphql-code-line ">lines: [<a href="/reference/graphql-api/admin/input-types#orderlineinput">OrderLineInput</a>!]</div>
 
-<div class="graphql-code-line ">shipping: <a href="/reference/graphql-api/admin/object-types#money">Money</a>!</div>
+<div class="graphql-code-line ">shipping: <a href="/reference/graphql-api/admin/object-types#money">Money</a></div>
 
-<div class="graphql-code-line ">adjustment: <a href="/reference/graphql-api/admin/object-types#money">Money</a>!</div>
+<div class="graphql-code-line ">adjustment: <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">If an amount is specified, this value will be used to create a Refund rather than calculating the</div>
-
-<div class="graphql-code-line comment">amount automatically. This was added in v2.2 and will be the preferred way to specify the refund</div>
-
-<div class="graphql-code-line comment">amount in the future. The `lines`, <code>shipping</code> and <code>adjustment</code> fields will likely be removed in a future</div>
+<div class="graphql-code-line comment">The amount to be refunded to this particular payment. This was introduced in v2.2.0 as the preferred way to specify the refund amount.</div>
 
-<div class="graphql-code-line comment">version.</div>
+<div class="graphql-code-line comment">Can be as much as the total amount of the payment minus the sum of all previous refunds.</div>
 <div class="graphql-code-line comment">"""</div>
 <div class="graphql-code-line ">amount: <a href="/reference/graphql-api/admin/object-types#money">Money</a></div>
 

+ 24 - 0
docs/docs/reference/graphql-api/admin/mutations.md

@@ -1538,6 +1538,30 @@ import MemberDescription from '@site/src/components/MemberDescription';
 <div class="graphql-code-line ">transitionPaymentToState(id: <a href="/reference/graphql-api/admin/object-types#id">ID</a>!, state: <a href="/reference/graphql-api/admin/object-types#string">String</a>!): <a href="/reference/graphql-api/admin/object-types#transitionpaymenttostateresult">TransitionPaymentToStateResult</a>!</div>
 
 
+<div class="graphql-code-line top-level">&#125;</div>
+</div>
+
+## unsetDraftOrderBillingAddress
+<div class="graphql-code-block">
+<div class="graphql-code-line top-level comment">"""</div>
+<div class="graphql-code-line top-level comment">Unsets the billing address for a draft Order</div>
+<div class="graphql-code-line top-level comment">"""</div>
+<div class="graphql-code-line top-level">type <span class="graphql-code-identifier">Mutation</span> &#123;</div>
+<div class="graphql-code-line ">unsetDraftOrderBillingAddress(orderId: <a href="/reference/graphql-api/admin/object-types#id">ID</a>!): <a href="/reference/graphql-api/admin/object-types#order">Order</a>!</div>
+
+
+<div class="graphql-code-line top-level">&#125;</div>
+</div>
+
+## unsetDraftOrderShippingAddress
+<div class="graphql-code-block">
+<div class="graphql-code-line top-level comment">"""</div>
+<div class="graphql-code-line top-level comment">Unsets the shipping address for a draft Order</div>
+<div class="graphql-code-line top-level comment">"""</div>
+<div class="graphql-code-line top-level">type <span class="graphql-code-identifier">Mutation</span> &#123;</div>
+<div class="graphql-code-line ">unsetDraftOrderShippingAddress(orderId: <a href="/reference/graphql-api/admin/object-types#id">ID</a>!): <a href="/reference/graphql-api/admin/object-types#order">Order</a>!</div>
+
+
 <div class="graphql-code-line top-level">&#125;</div>
 </div>
 

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

@@ -134,6 +134,8 @@ import MemberDescription from '@site/src/components/MemberDescription';
 
 <div class="graphql-code-line ">orderLine: <a href="/reference/graphql-api/admin/object-types#orderline">OrderLine</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 top-level">&#125;</div>
 </div>
@@ -267,6 +269,26 @@ import MemberDescription from '@site/src/components/MemberDescription';
 <div class="graphql-code-line ">ui: <a href="/reference/graphql-api/admin/object-types#json">JSON</a></div>
 
 
+<div class="graphql-code-line top-level">&#125;</div>
+</div>
+
+## BooleanStructFieldConfig
+
+<div class="graphql-code-block">
+<div class="graphql-code-line top-level">type <span class="graphql-code-identifier">BooleanStructFieldConfig</span> &#123;</div>
+<div class="graphql-code-line ">name: <a href="/reference/graphql-api/admin/object-types#string">String</a>!</div>
+
+<div class="graphql-code-line ">type: <a href="/reference/graphql-api/admin/object-types#string">String</a>!</div>
+
+<div class="graphql-code-line ">list: <a href="/reference/graphql-api/admin/object-types#boolean">Boolean</a>!</div>
+
+<div class="graphql-code-line ">label: [<a href="/reference/graphql-api/admin/object-types#localizedstring">LocalizedString</a>!]</div>
+
+<div class="graphql-code-line ">description: [<a href="/reference/graphql-api/admin/object-types#localizedstring">LocalizedString</a>!]</div>
+
+<div class="graphql-code-line ">ui: <a href="/reference/graphql-api/admin/object-types#json">JSON</a></div>
+
+
 <div class="graphql-code-line top-level">&#125;</div>
 </div>
 
@@ -336,6 +358,8 @@ import MemberDescription from '@site/src/components/MemberDescription';
 
 <div class="graphql-code-line ">orderLine: <a href="/reference/graphql-api/admin/object-types#orderline">OrderLine</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 top-level">&#125;</div>
 </div>
@@ -793,7 +817,7 @@ import MemberDescription from '@site/src/components/MemberDescription';
 
 <div class="graphql-code-block">
 <div class="graphql-code-line top-level">union <span class="graphql-code-identifier">CustomFieldConfig</span> =</div>
-<div class="graphql-code-line "><a href="/reference/graphql-api/admin/object-types#stringcustomfieldconfig">StringCustomFieldConfig</a> | <a href="/reference/graphql-api/admin/object-types#localestringcustomfieldconfig">LocaleStringCustomFieldConfig</a> | <a href="/reference/graphql-api/admin/object-types#intcustomfieldconfig">IntCustomFieldConfig</a> | <a href="/reference/graphql-api/admin/object-types#floatcustomfieldconfig">FloatCustomFieldConfig</a> | <a href="/reference/graphql-api/admin/object-types#booleancustomfieldconfig">BooleanCustomFieldConfig</a> | <a href="/reference/graphql-api/admin/object-types#datetimecustomfieldconfig">DateTimeCustomFieldConfig</a> | <a href="/reference/graphql-api/admin/object-types#relationcustomfieldconfig">RelationCustomFieldConfig</a> | <a href="/reference/graphql-api/admin/object-types#textcustomfieldconfig">TextCustomFieldConfig</a> | <a href="/reference/graphql-api/admin/object-types#localetextcustomfieldconfig">LocaleTextCustomFieldConfig</a></div>
+<div class="graphql-code-line "><a href="/reference/graphql-api/admin/object-types#stringcustomfieldconfig">StringCustomFieldConfig</a> | <a href="/reference/graphql-api/admin/object-types#localestringcustomfieldconfig">LocaleStringCustomFieldConfig</a> | <a href="/reference/graphql-api/admin/object-types#intcustomfieldconfig">IntCustomFieldConfig</a> | <a href="/reference/graphql-api/admin/object-types#floatcustomfieldconfig">FloatCustomFieldConfig</a> | <a href="/reference/graphql-api/admin/object-types#booleancustomfieldconfig">BooleanCustomFieldConfig</a> | <a href="/reference/graphql-api/admin/object-types#datetimecustomfieldconfig">DateTimeCustomFieldConfig</a> | <a href="/reference/graphql-api/admin/object-types#relationcustomfieldconfig">RelationCustomFieldConfig</a> | <a href="/reference/graphql-api/admin/object-types#textcustomfieldconfig">TextCustomFieldConfig</a> | <a href="/reference/graphql-api/admin/object-types#localetextcustomfieldconfig">LocaleTextCustomFieldConfig</a> | <a href="/reference/graphql-api/admin/object-types#structcustomfieldconfig">StructCustomFieldConfig</a></div>
 </div>
 
 ## CustomFields
@@ -802,7 +826,7 @@ import MemberDescription from '@site/src/components/MemberDescription';
 <div class="graphql-code-line top-level comment">"""</div>
 <div class="graphql-code-line top-level comment">This type is deprecated in v2.2 in favor of the EntityCustomFields type,</div>
 
-<div class="graphql-code-line top-level comment">which allows custom fields to be defined on user-supplies entities.</div>
+<div class="graphql-code-line top-level comment">which allows custom fields to be defined on user-supplied entities.</div>
 <div class="graphql-code-line top-level comment">"""</div>
 <div class="graphql-code-line top-level">type <span class="graphql-code-identifier">CustomFields</span> &#123;</div>
 <div class="graphql-code-line ">Address: [<a href="/reference/graphql-api/admin/object-types#customfieldconfig">CustomFieldConfig</a>!]!</div>
@@ -827,10 +851,14 @@ import MemberDescription from '@site/src/components/MemberDescription';
 
 <div class="graphql-code-line ">GlobalSettings: [<a href="/reference/graphql-api/admin/object-types#customfieldconfig">CustomFieldConfig</a>!]!</div>
 
+<div class="graphql-code-line ">HistoryEntry: [<a href="/reference/graphql-api/admin/object-types#customfieldconfig">CustomFieldConfig</a>!]!</div>
+
 <div class="graphql-code-line ">Order: [<a href="/reference/graphql-api/admin/object-types#customfieldconfig">CustomFieldConfig</a>!]!</div>
 
 <div class="graphql-code-line ">OrderLine: [<a href="/reference/graphql-api/admin/object-types#customfieldconfig">CustomFieldConfig</a>!]!</div>
 
+<div class="graphql-code-line ">Payment: [<a href="/reference/graphql-api/admin/object-types#customfieldconfig">CustomFieldConfig</a>!]!</div>
+
 <div class="graphql-code-line ">PaymentMethod: [<a href="/reference/graphql-api/admin/object-types#customfieldconfig">CustomFieldConfig</a>!]!</div>
 
 <div class="graphql-code-line ">Product: [<a href="/reference/graphql-api/admin/object-types#customfieldconfig">CustomFieldConfig</a>!]!</div>
@@ -845,14 +873,24 @@ import MemberDescription from '@site/src/components/MemberDescription';
 
 <div class="graphql-code-line ">Promotion: [<a href="/reference/graphql-api/admin/object-types#customfieldconfig">CustomFieldConfig</a>!]!</div>
 
+<div class="graphql-code-line ">Refund: [<a href="/reference/graphql-api/admin/object-types#customfieldconfig">CustomFieldConfig</a>!]!</div>
+
 <div class="graphql-code-line ">Region: [<a href="/reference/graphql-api/admin/object-types#customfieldconfig">CustomFieldConfig</a>!]!</div>
 
 <div class="graphql-code-line ">Seller: [<a href="/reference/graphql-api/admin/object-types#customfieldconfig">CustomFieldConfig</a>!]!</div>
 
+<div class="graphql-code-line ">Session: [<a href="/reference/graphql-api/admin/object-types#customfieldconfig">CustomFieldConfig</a>!]!</div>
+
+<div class="graphql-code-line ">ShippingLine: [<a href="/reference/graphql-api/admin/object-types#customfieldconfig">CustomFieldConfig</a>!]!</div>
+
 <div class="graphql-code-line ">ShippingMethod: [<a href="/reference/graphql-api/admin/object-types#customfieldconfig">CustomFieldConfig</a>!]!</div>
 
+<div class="graphql-code-line ">StockLevel: [<a href="/reference/graphql-api/admin/object-types#customfieldconfig">CustomFieldConfig</a>!]!</div>
+
 <div class="graphql-code-line ">StockLocation: [<a href="/reference/graphql-api/admin/object-types#customfieldconfig">CustomFieldConfig</a>!]!</div>
 
+<div class="graphql-code-line ">StockMovement: [<a href="/reference/graphql-api/admin/object-types#customfieldconfig">CustomFieldConfig</a>!]!</div>
+
 <div class="graphql-code-line ">TaxCategory: [<a href="/reference/graphql-api/admin/object-types#customfieldconfig">CustomFieldConfig</a>!]!</div>
 
 <div class="graphql-code-line ">TaxRate: [<a href="/reference/graphql-api/admin/object-types#customfieldconfig">CustomFieldConfig</a>!]!</div>
@@ -990,6 +1028,37 @@ import MemberDescription from '@site/src/components/MemberDescription';
 <div class="graphql-code-line ">ui: <a href="/reference/graphql-api/admin/object-types#json">JSON</a></div>
 
 
+<div class="graphql-code-line top-level">&#125;</div>
+</div>
+
+## DateTimeStructFieldConfig
+
+<div class="graphql-code-block">
+<div class="graphql-code-line top-level comment">"""</div>
+<div class="graphql-code-line top-level comment">Expects the same validation formats as the <code>&lt;input type="datetime-local"&gt;</code> HTML element.</div>
+
+<div class="graphql-code-line top-level comment">See https://developer.mozilla.org/en-US/docs/Web/HTML/Element/input/datetime-local#Additional_attributes</div>
+<div class="graphql-code-line top-level comment">"""</div>
+<div class="graphql-code-line top-level">type <span class="graphql-code-identifier">DateTimeStructFieldConfig</span> &#123;</div>
+<div class="graphql-code-line ">name: <a href="/reference/graphql-api/admin/object-types#string">String</a>!</div>
+
+<div class="graphql-code-line ">type: <a href="/reference/graphql-api/admin/object-types#string">String</a>!</div>
+
+<div class="graphql-code-line ">list: <a href="/reference/graphql-api/admin/object-types#boolean">Boolean</a>!</div>
+
+<div class="graphql-code-line ">label: [<a href="/reference/graphql-api/admin/object-types#localizedstring">LocalizedString</a>!]</div>
+
+<div class="graphql-code-line ">description: [<a href="/reference/graphql-api/admin/object-types#localizedstring">LocalizedString</a>!]</div>
+
+<div class="graphql-code-line ">min: <a href="/reference/graphql-api/admin/object-types#string">String</a></div>
+
+<div class="graphql-code-line ">max: <a href="/reference/graphql-api/admin/object-types#string">String</a></div>
+
+<div class="graphql-code-line ">step: <a href="/reference/graphql-api/admin/object-types#int">Int</a></div>
+
+<div class="graphql-code-line ">ui: <a href="/reference/graphql-api/admin/object-types#json">JSON</a></div>
+
+
 <div class="graphql-code-line top-level">&#125;</div>
 </div>
 
@@ -1310,6 +1379,32 @@ import MemberDescription from '@site/src/components/MemberDescription';
 <div class="graphql-code-line ">ui: <a href="/reference/graphql-api/admin/object-types#json">JSON</a></div>
 
 
+<div class="graphql-code-line top-level">&#125;</div>
+</div>
+
+## FloatStructFieldConfig
+
+<div class="graphql-code-block">
+<div class="graphql-code-line top-level">type <span class="graphql-code-identifier">FloatStructFieldConfig</span> &#123;</div>
+<div class="graphql-code-line ">name: <a href="/reference/graphql-api/admin/object-types#string">String</a>!</div>
+
+<div class="graphql-code-line ">type: <a href="/reference/graphql-api/admin/object-types#string">String</a>!</div>
+
+<div class="graphql-code-line ">list: <a href="/reference/graphql-api/admin/object-types#boolean">Boolean</a>!</div>
+
+<div class="graphql-code-line ">label: [<a href="/reference/graphql-api/admin/object-types#localizedstring">LocalizedString</a>!]</div>
+
+<div class="graphql-code-line ">description: [<a href="/reference/graphql-api/admin/object-types#localizedstring">LocalizedString</a>!]</div>
+
+<div class="graphql-code-line ">min: <a href="/reference/graphql-api/admin/object-types#float">Float</a></div>
+
+<div class="graphql-code-line ">max: <a href="/reference/graphql-api/admin/object-types#float">Float</a></div>
+
+<div class="graphql-code-line ">step: <a href="/reference/graphql-api/admin/object-types#float">Float</a></div>
+
+<div class="graphql-code-line ">ui: <a href="/reference/graphql-api/admin/object-types#json">JSON</a></div>
+
+
 <div class="graphql-code-line top-level">&#125;</div>
 </div>
 
@@ -1439,6 +1534,8 @@ import MemberDescription from '@site/src/components/MemberDescription';
 
 <div class="graphql-code-line ">data: <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>
+
 
 <div class="graphql-code-line top-level">&#125;</div>
 </div>
@@ -1575,6 +1672,32 @@ import MemberDescription from '@site/src/components/MemberDescription';
 <div class="graphql-code-line ">ui: <a href="/reference/graphql-api/admin/object-types#json">JSON</a></div>
 
 
+<div class="graphql-code-line top-level">&#125;</div>
+</div>
+
+## IntStructFieldConfig
+
+<div class="graphql-code-block">
+<div class="graphql-code-line top-level">type <span class="graphql-code-identifier">IntStructFieldConfig</span> &#123;</div>
+<div class="graphql-code-line ">name: <a href="/reference/graphql-api/admin/object-types#string">String</a>!</div>
+
+<div class="graphql-code-line ">type: <a href="/reference/graphql-api/admin/object-types#string">String</a>!</div>
+
+<div class="graphql-code-line ">list: <a href="/reference/graphql-api/admin/object-types#boolean">Boolean</a>!</div>
+
+<div class="graphql-code-line ">label: [<a href="/reference/graphql-api/admin/object-types#localizedstring">LocalizedString</a>!]</div>
+
+<div class="graphql-code-line ">description: [<a href="/reference/graphql-api/admin/object-types#localizedstring">LocalizedString</a>!]</div>
+
+<div class="graphql-code-line ">min: <a href="/reference/graphql-api/admin/object-types#int">Int</a></div>
+
+<div class="graphql-code-line ">max: <a href="/reference/graphql-api/admin/object-types#int">Int</a></div>
+
+<div class="graphql-code-line ">step: <a href="/reference/graphql-api/admin/object-types#int">Int</a></div>
+
+<div class="graphql-code-line ">ui: <a href="/reference/graphql-api/admin/object-types#json">JSON</a></div>
+
+
 <div class="graphql-code-line top-level">&#125;</div>
 </div>
 
@@ -2140,6 +2263,23 @@ import MemberDescription from '@site/src/components/MemberDescription';
 <div class="graphql-code-line ">customFields: <a href="/reference/graphql-api/admin/object-types#json">JSON</a></div>
 
 
+<div class="graphql-code-line top-level">&#125;</div>
+</div>
+
+## OrderInterceptorError
+
+<div class="graphql-code-block">
+<div class="graphql-code-line top-level comment">"""</div>
+<div class="graphql-code-line top-level comment">Returned when an order operation is rejected by an OrderInterceptor method.</div>
+<div class="graphql-code-line top-level comment">"""</div>
+<div class="graphql-code-line top-level">type <span class="graphql-code-identifier">OrderInterceptorError</span> &#123;</div>
+<div class="graphql-code-line ">errorCode: <a href="/reference/graphql-api/admin/enums#errorcode">ErrorCode</a>!</div>
+
+<div class="graphql-code-line ">message: <a href="/reference/graphql-api/admin/object-types#string">String</a>!</div>
+
+<div class="graphql-code-line ">interceptorError: <a href="/reference/graphql-api/admin/object-types#string">String</a>!</div>
+
+
 <div class="graphql-code-line top-level">&#125;</div>
 </div>
 
@@ -2473,6 +2613,8 @@ import MemberDescription from '@site/src/components/MemberDescription';
 
 <div class="graphql-code-line ">metadata: <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>
+
 
 <div class="graphql-code-line top-level">&#125;</div>
 </div>
@@ -3106,6 +3248,8 @@ import MemberDescription from '@site/src/components/MemberDescription';
 
 <div class="graphql-code-line ">metadata: <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>
+
 
 <div class="graphql-code-line top-level">&#125;</div>
 </div>
@@ -3273,6 +3417,8 @@ import MemberDescription from '@site/src/components/MemberDescription';
 
 <div class="graphql-code-line ">quantity: <a href="/reference/graphql-api/admin/object-types#int">Int</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 top-level">&#125;</div>
 </div>
@@ -3295,7 +3441,7 @@ import MemberDescription from '@site/src/components/MemberDescription';
 
 <div class="graphql-code-block">
 <div class="graphql-code-line top-level">union <span class="graphql-code-identifier">RemoveOrderItemsResult</span> =</div>
-<div class="graphql-code-line "><a href="/reference/graphql-api/admin/object-types#order">Order</a> | <a href="/reference/graphql-api/admin/object-types#ordermodificationerror">OrderModificationError</a></div>
+<div class="graphql-code-line "><a href="/reference/graphql-api/admin/object-types#order">Order</a> | <a href="/reference/graphql-api/admin/object-types#ordermodificationerror">OrderModificationError</a> | <a href="/reference/graphql-api/admin/object-types#orderinterceptorerror">OrderInterceptorError</a></div>
 </div>
 
 ## Return
@@ -3314,6 +3460,8 @@ import MemberDescription from '@site/src/components/MemberDescription';
 
 <div class="graphql-code-line ">quantity: <a href="/reference/graphql-api/admin/object-types#int">Int</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 top-level">&#125;</div>
 </div>
@@ -3368,6 +3516,8 @@ import MemberDescription from '@site/src/components/MemberDescription';
 
 <div class="graphql-code-line ">quantity: <a href="/reference/graphql-api/admin/object-types#int">Int</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 top-level">&#125;</div>
 </div>
@@ -3593,6 +3743,8 @@ import MemberDescription from '@site/src/components/MemberDescription';
 
 <div class="graphql-code-line ">discounts: [<a href="/reference/graphql-api/admin/object-types#discount">Discount</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 top-level">&#125;</div>
 </div>
@@ -3717,6 +3869,8 @@ import MemberDescription from '@site/src/components/MemberDescription';
 
 <div class="graphql-code-line ">quantity: <a href="/reference/graphql-api/admin/object-types#int">Int</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 top-level">&#125;</div>
 </div>
@@ -3739,6 +3893,8 @@ import MemberDescription from '@site/src/components/MemberDescription';
 
 <div class="graphql-code-line ">stockLocation: <a href="/reference/graphql-api/admin/object-types#stocklocation">StockLocation</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 top-level">&#125;</div>
 </div>
@@ -3849,6 +4005,69 @@ import MemberDescription from '@site/src/components/MemberDescription';
 <div class="graphql-code-line top-level">&#125;</div>
 </div>
 
+## StringStructFieldConfig
+
+<div class="graphql-code-block">
+<div class="graphql-code-line top-level">type <span class="graphql-code-identifier">StringStructFieldConfig</span> &#123;</div>
+<div class="graphql-code-line ">name: <a href="/reference/graphql-api/admin/object-types#string">String</a>!</div>
+
+<div class="graphql-code-line ">type: <a href="/reference/graphql-api/admin/object-types#string">String</a>!</div>
+
+<div class="graphql-code-line ">list: <a href="/reference/graphql-api/admin/object-types#boolean">Boolean</a>!</div>
+
+<div class="graphql-code-line ">label: [<a href="/reference/graphql-api/admin/object-types#localizedstring">LocalizedString</a>!]</div>
+
+<div class="graphql-code-line ">description: [<a href="/reference/graphql-api/admin/object-types#localizedstring">LocalizedString</a>!]</div>
+
+<div class="graphql-code-line ">length: <a href="/reference/graphql-api/admin/object-types#int">Int</a></div>
+
+<div class="graphql-code-line ">pattern: <a href="/reference/graphql-api/admin/object-types#string">String</a></div>
+
+<div class="graphql-code-line ">options: [<a href="/reference/graphql-api/admin/object-types#stringfieldoption">StringFieldOption</a>!]</div>
+
+<div class="graphql-code-line ">ui: <a href="/reference/graphql-api/admin/object-types#json">JSON</a></div>
+
+
+<div class="graphql-code-line top-level">&#125;</div>
+</div>
+
+## StructCustomFieldConfig
+
+<div class="graphql-code-block">
+<div class="graphql-code-line top-level">type <span class="graphql-code-identifier">StructCustomFieldConfig</span> &#123;</div>
+<div class="graphql-code-line ">name: <a href="/reference/graphql-api/admin/object-types#string">String</a>!</div>
+
+<div class="graphql-code-line ">type: <a href="/reference/graphql-api/admin/object-types#string">String</a>!</div>
+
+<div class="graphql-code-line ">list: <a href="/reference/graphql-api/admin/object-types#boolean">Boolean</a>!</div>
+
+<div class="graphql-code-line ">fields: [<a href="/reference/graphql-api/admin/object-types#structfieldconfig">StructFieldConfig</a>!]!</div>
+
+<div class="graphql-code-line ">label: [<a href="/reference/graphql-api/admin/object-types#localizedstring">LocalizedString</a>!]</div>
+
+<div class="graphql-code-line ">description: [<a href="/reference/graphql-api/admin/object-types#localizedstring">LocalizedString</a>!]</div>
+
+<div class="graphql-code-line ">readonly: <a href="/reference/graphql-api/admin/object-types#boolean">Boolean</a></div>
+
+<div class="graphql-code-line ">internal: <a href="/reference/graphql-api/admin/object-types#boolean">Boolean</a></div>
+
+<div class="graphql-code-line ">nullable: <a href="/reference/graphql-api/admin/object-types#boolean">Boolean</a></div>
+
+<div class="graphql-code-line ">requiresPermission: [<a href="/reference/graphql-api/admin/enums#permission">Permission</a>!]</div>
+
+<div class="graphql-code-line ">ui: <a href="/reference/graphql-api/admin/object-types#json">JSON</a></div>
+
+
+<div class="graphql-code-line top-level">&#125;</div>
+</div>
+
+## StructFieldConfig
+
+<div class="graphql-code-block">
+<div class="graphql-code-line top-level">union <span class="graphql-code-identifier">StructFieldConfig</span> =</div>
+<div class="graphql-code-line "><a href="/reference/graphql-api/admin/object-types#stringstructfieldconfig">StringStructFieldConfig</a> | <a href="/reference/graphql-api/admin/object-types#intstructfieldconfig">IntStructFieldConfig</a> | <a href="/reference/graphql-api/admin/object-types#floatstructfieldconfig">FloatStructFieldConfig</a> | <a href="/reference/graphql-api/admin/object-types#booleanstructfieldconfig">BooleanStructFieldConfig</a> | <a href="/reference/graphql-api/admin/object-types#datetimestructfieldconfig">DateTimeStructFieldConfig</a> | <a href="/reference/graphql-api/admin/object-types#textstructfieldconfig">TextStructFieldConfig</a></div>
+</div>
+
 ## Success
 
 <div class="graphql-code-block">
@@ -4051,6 +4270,26 @@ import MemberDescription from '@site/src/components/MemberDescription';
 <div class="graphql-code-line ">ui: <a href="/reference/graphql-api/admin/object-types#json">JSON</a></div>
 
 
+<div class="graphql-code-line top-level">&#125;</div>
+</div>
+
+## TextStructFieldConfig
+
+<div class="graphql-code-block">
+<div class="graphql-code-line top-level">type <span class="graphql-code-identifier">TextStructFieldConfig</span> &#123;</div>
+<div class="graphql-code-line ">name: <a href="/reference/graphql-api/admin/object-types#string">String</a>!</div>
+
+<div class="graphql-code-line ">type: <a href="/reference/graphql-api/admin/object-types#string">String</a>!</div>
+
+<div class="graphql-code-line ">list: <a href="/reference/graphql-api/admin/object-types#boolean">Boolean</a>!</div>
+
+<div class="graphql-code-line ">label: [<a href="/reference/graphql-api/admin/object-types#localizedstring">LocalizedString</a>!]</div>
+
+<div class="graphql-code-line ">description: [<a href="/reference/graphql-api/admin/object-types#localizedstring">LocalizedString</a>!]</div>
+
+<div class="graphql-code-line ">ui: <a href="/reference/graphql-api/admin/object-types#json">JSON</a></div>
+
+
 <div class="graphql-code-line top-level">&#125;</div>
 </div>
 
@@ -4100,7 +4339,7 @@ import MemberDescription from '@site/src/components/MemberDescription';
 
 <div class="graphql-code-block">
 <div class="graphql-code-line top-level">union <span class="graphql-code-identifier">UpdateOrderItemsResult</span> =</div>
-<div class="graphql-code-line "><a href="/reference/graphql-api/admin/object-types#order">Order</a> | <a href="/reference/graphql-api/admin/object-types#ordermodificationerror">OrderModificationError</a> | <a href="/reference/graphql-api/admin/object-types#orderlimiterror">OrderLimitError</a> | <a href="/reference/graphql-api/admin/object-types#negativequantityerror">NegativeQuantityError</a> | <a href="/reference/graphql-api/admin/object-types#insufficientstockerror">InsufficientStockError</a></div>
+<div class="graphql-code-line "><a href="/reference/graphql-api/admin/object-types#order">Order</a> | <a href="/reference/graphql-api/admin/object-types#ordermodificationerror">OrderModificationError</a> | <a href="/reference/graphql-api/admin/object-types#orderlimiterror">OrderLimitError</a> | <a href="/reference/graphql-api/admin/object-types#negativequantityerror">NegativeQuantityError</a> | <a href="/reference/graphql-api/admin/object-types#insufficientstockerror">InsufficientStockError</a> | <a href="/reference/graphql-api/admin/object-types#orderinterceptorerror">OrderInterceptorError</a></div>
 </div>
 
 ## UpdatePromotionResult

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

@@ -393,6 +393,7 @@ import MemberDescription from '@site/src/components/MemberDescription';
 <div class="graphql-code-line ">ORDER_MODIFICATION_ERROR</div>
 <div class="graphql-code-line ">INELIGIBLE_SHIPPING_METHOD_ERROR</div>
 <div class="graphql-code-line ">NO_ACTIVE_ORDER_ERROR</div>
+<div class="graphql-code-line ">ORDER_INTERCEPTOR_ERROR</div>
 <div class="graphql-code-line ">ORDER_PAYMENT_STATE_ERROR</div>
 <div class="graphql-code-line ">INELIGIBLE_PAYMENT_METHOD_ERROR</div>
 <div class="graphql-code-line ">PAYMENT_FAILED_ERROR</div>

+ 28 - 4
docs/docs/reference/graphql-api/shop/mutations.md

@@ -13,7 +13,7 @@ import MemberDescription from '@site/src/components/MemberDescription';
 ## addItemToOrder
 <div class="graphql-code-block">
 <div class="graphql-code-line top-level comment">"""</div>
-<div class="graphql-code-line top-level comment">Adds an item to the order. If custom fields are defined on the OrderLine entity, a third argument 'customFields' will be available.</div>
+<div class="graphql-code-line top-level comment">Adds an item to the Order. If custom fields are defined on the OrderLine entity, a third argument 'customFields' will be available.</div>
 <div class="graphql-code-line top-level comment">"""</div>
 <div class="graphql-code-line top-level">type <span class="graphql-code-identifier">Mutation</span> &#123;</div>
 <div class="graphql-code-line ">addItemToOrder(productVariantId: <a href="/reference/graphql-api/shop/object-types#id">ID</a>!, quantity: <a href="/reference/graphql-api/shop/object-types#int">Int</a>!): <a href="/reference/graphql-api/shop/object-types#updateorderitemsresult">UpdateOrderItemsResult</a>!</div>
@@ -267,7 +267,7 @@ import MemberDescription from '@site/src/components/MemberDescription';
 ## setOrderBillingAddress
 <div class="graphql-code-block">
 <div class="graphql-code-line top-level comment">"""</div>
-<div class="graphql-code-line top-level comment">Sets the billing address for this order</div>
+<div class="graphql-code-line top-level comment">Sets the billing address for the active Order</div>
 <div class="graphql-code-line top-level comment">"""</div>
 <div class="graphql-code-line top-level">type <span class="graphql-code-identifier">Mutation</span> &#123;</div>
 <div class="graphql-code-line ">setOrderBillingAddress(input: <a href="/reference/graphql-api/shop/input-types#createaddressinput">CreateAddressInput</a>!): <a href="/reference/graphql-api/shop/object-types#activeorderresult">ActiveOrderResult</a>!</div>
@@ -279,7 +279,7 @@ import MemberDescription from '@site/src/components/MemberDescription';
 ## setOrderCustomFields
 <div class="graphql-code-block">
 <div class="graphql-code-line top-level comment">"""</div>
-<div class="graphql-code-line top-level comment">Allows any custom fields to be set for the active order</div>
+<div class="graphql-code-line top-level comment">Allows any custom fields to be set for the active Order</div>
 <div class="graphql-code-line top-level comment">"""</div>
 <div class="graphql-code-line top-level">type <span class="graphql-code-identifier">Mutation</span> &#123;</div>
 <div class="graphql-code-line ">setOrderCustomFields(input: <a href="/reference/graphql-api/shop/input-types#updateorderinput">UpdateOrderInput</a>!): <a href="/reference/graphql-api/shop/object-types#activeorderresult">ActiveOrderResult</a>!</div>
@@ -291,7 +291,7 @@ import MemberDescription from '@site/src/components/MemberDescription';
 ## setOrderShippingAddress
 <div class="graphql-code-block">
 <div class="graphql-code-line top-level comment">"""</div>
-<div class="graphql-code-line top-level comment">Sets the shipping address for this order</div>
+<div class="graphql-code-line top-level comment">Sets the shipping address for the active Order</div>
 <div class="graphql-code-line top-level comment">"""</div>
 <div class="graphql-code-line top-level">type <span class="graphql-code-identifier">Mutation</span> &#123;</div>
 <div class="graphql-code-line ">setOrderShippingAddress(input: <a href="/reference/graphql-api/shop/input-types#createaddressinput">CreateAddressInput</a>!): <a href="/reference/graphql-api/shop/object-types#activeorderresult">ActiveOrderResult</a>!</div>
@@ -327,6 +327,30 @@ import MemberDescription from '@site/src/components/MemberDescription';
 <div class="graphql-code-line ">transitionOrderToState(state: <a href="/reference/graphql-api/shop/object-types#string">String</a>!): <a href="/reference/graphql-api/shop/object-types#transitionordertostateresult">TransitionOrderToStateResult</a></div>
 
 
+<div class="graphql-code-line top-level">&#125;</div>
+</div>
+
+## unsetOrderBillingAddress
+<div class="graphql-code-block">
+<div class="graphql-code-line top-level comment">"""</div>
+<div class="graphql-code-line top-level comment">Unsets the billing address for the active Order. Available since version 3.1.0</div>
+<div class="graphql-code-line top-level comment">"""</div>
+<div class="graphql-code-line top-level">type <span class="graphql-code-identifier">Mutation</span> &#123;</div>
+<div class="graphql-code-line ">unsetOrderBillingAddress: <a href="/reference/graphql-api/shop/object-types#activeorderresult">ActiveOrderResult</a>!</div>
+
+
+<div class="graphql-code-line top-level">&#125;</div>
+</div>
+
+## unsetOrderShippingAddress
+<div class="graphql-code-block">
+<div class="graphql-code-line top-level comment">"""</div>
+<div class="graphql-code-line top-level comment">Unsets the shipping address for the active Order. Available since version 3.1.0</div>
+<div class="graphql-code-line top-level comment">"""</div>
+<div class="graphql-code-line top-level">type <span class="graphql-code-identifier">Mutation</span> &#123;</div>
+<div class="graphql-code-line ">unsetOrderShippingAddress: <a href="/reference/graphql-api/shop/object-types#activeorderresult">ActiveOrderResult</a>!</div>
+
+
 <div class="graphql-code-line top-level">&#125;</div>
 </div>
 

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

@@ -207,6 +207,26 @@ import MemberDescription from '@site/src/components/MemberDescription';
 <div class="graphql-code-line ">ui: <a href="/reference/graphql-api/shop/object-types#json">JSON</a></div>
 
 
+<div class="graphql-code-line top-level">&#125;</div>
+</div>
+
+## BooleanStructFieldConfig
+
+<div class="graphql-code-block">
+<div class="graphql-code-line top-level">type <span class="graphql-code-identifier">BooleanStructFieldConfig</span> &#123;</div>
+<div class="graphql-code-line ">name: <a href="/reference/graphql-api/shop/object-types#string">String</a>!</div>
+
+<div class="graphql-code-line ">type: <a href="/reference/graphql-api/shop/object-types#string">String</a>!</div>
+
+<div class="graphql-code-line ">list: <a href="/reference/graphql-api/shop/object-types#boolean">Boolean</a>!</div>
+
+<div class="graphql-code-line ">label: [<a href="/reference/graphql-api/shop/object-types#localizedstring">LocalizedString</a>!]</div>
+
+<div class="graphql-code-line ">description: [<a href="/reference/graphql-api/shop/object-types#localizedstring">LocalizedString</a>!]</div>
+
+<div class="graphql-code-line ">ui: <a href="/reference/graphql-api/shop/object-types#json">JSON</a></div>
+
+
 <div class="graphql-code-line top-level">&#125;</div>
 </div>
 
@@ -581,7 +601,7 @@ import MemberDescription from '@site/src/components/MemberDescription';
 
 <div class="graphql-code-block">
 <div class="graphql-code-line top-level">union <span class="graphql-code-identifier">CustomFieldConfig</span> =</div>
-<div class="graphql-code-line "><a href="/reference/graphql-api/shop/object-types#stringcustomfieldconfig">StringCustomFieldConfig</a> | <a href="/reference/graphql-api/shop/object-types#localestringcustomfieldconfig">LocaleStringCustomFieldConfig</a> | <a href="/reference/graphql-api/shop/object-types#intcustomfieldconfig">IntCustomFieldConfig</a> | <a href="/reference/graphql-api/shop/object-types#floatcustomfieldconfig">FloatCustomFieldConfig</a> | <a href="/reference/graphql-api/shop/object-types#booleancustomfieldconfig">BooleanCustomFieldConfig</a> | <a href="/reference/graphql-api/shop/object-types#datetimecustomfieldconfig">DateTimeCustomFieldConfig</a> | <a href="/reference/graphql-api/shop/object-types#relationcustomfieldconfig">RelationCustomFieldConfig</a> | <a href="/reference/graphql-api/shop/object-types#textcustomfieldconfig">TextCustomFieldConfig</a> | <a href="/reference/graphql-api/shop/object-types#localetextcustomfieldconfig">LocaleTextCustomFieldConfig</a></div>
+<div class="graphql-code-line "><a href="/reference/graphql-api/shop/object-types#stringcustomfieldconfig">StringCustomFieldConfig</a> | <a href="/reference/graphql-api/shop/object-types#localestringcustomfieldconfig">LocaleStringCustomFieldConfig</a> | <a href="/reference/graphql-api/shop/object-types#intcustomfieldconfig">IntCustomFieldConfig</a> | <a href="/reference/graphql-api/shop/object-types#floatcustomfieldconfig">FloatCustomFieldConfig</a> | <a href="/reference/graphql-api/shop/object-types#booleancustomfieldconfig">BooleanCustomFieldConfig</a> | <a href="/reference/graphql-api/shop/object-types#datetimecustomfieldconfig">DateTimeCustomFieldConfig</a> | <a href="/reference/graphql-api/shop/object-types#relationcustomfieldconfig">RelationCustomFieldConfig</a> | <a href="/reference/graphql-api/shop/object-types#textcustomfieldconfig">TextCustomFieldConfig</a> | <a href="/reference/graphql-api/shop/object-types#localetextcustomfieldconfig">LocaleTextCustomFieldConfig</a> | <a href="/reference/graphql-api/shop/object-types#structcustomfieldconfig">StructCustomFieldConfig</a></div>
 </div>
 
 ## Customer
@@ -693,6 +713,37 @@ import MemberDescription from '@site/src/components/MemberDescription';
 <div class="graphql-code-line ">ui: <a href="/reference/graphql-api/shop/object-types#json">JSON</a></div>
 
 
+<div class="graphql-code-line top-level">&#125;</div>
+</div>
+
+## DateTimeStructFieldConfig
+
+<div class="graphql-code-block">
+<div class="graphql-code-line top-level comment">"""</div>
+<div class="graphql-code-line top-level comment">Expects the same validation formats as the <code>&lt;input type="datetime-local"&gt;</code> HTML element.</div>
+
+<div class="graphql-code-line top-level comment">See https://developer.mozilla.org/en-US/docs/Web/HTML/Element/input/datetime-local#Additional_attributes</div>
+<div class="graphql-code-line top-level comment">"""</div>
+<div class="graphql-code-line top-level">type <span class="graphql-code-identifier">DateTimeStructFieldConfig</span> &#123;</div>
+<div class="graphql-code-line ">name: <a href="/reference/graphql-api/shop/object-types#string">String</a>!</div>
+
+<div class="graphql-code-line ">type: <a href="/reference/graphql-api/shop/object-types#string">String</a>!</div>
+
+<div class="graphql-code-line ">list: <a href="/reference/graphql-api/shop/object-types#boolean">Boolean</a>!</div>
+
+<div class="graphql-code-line ">label: [<a href="/reference/graphql-api/shop/object-types#localizedstring">LocalizedString</a>!]</div>
+
+<div class="graphql-code-line ">description: [<a href="/reference/graphql-api/shop/object-types#localizedstring">LocalizedString</a>!]</div>
+
+<div class="graphql-code-line ">min: <a href="/reference/graphql-api/shop/object-types#string">String</a></div>
+
+<div class="graphql-code-line ">max: <a href="/reference/graphql-api/shop/object-types#string">String</a></div>
+
+<div class="graphql-code-line ">step: <a href="/reference/graphql-api/shop/object-types#int">Int</a></div>
+
+<div class="graphql-code-line ">ui: <a href="/reference/graphql-api/shop/object-types#json">JSON</a></div>
+
+
 <div class="graphql-code-line top-level">&#125;</div>
 </div>
 
@@ -917,6 +968,32 @@ import MemberDescription from '@site/src/components/MemberDescription';
 <div class="graphql-code-line ">ui: <a href="/reference/graphql-api/shop/object-types#json">JSON</a></div>
 
 
+<div class="graphql-code-line top-level">&#125;</div>
+</div>
+
+## FloatStructFieldConfig
+
+<div class="graphql-code-block">
+<div class="graphql-code-line top-level">type <span class="graphql-code-identifier">FloatStructFieldConfig</span> &#123;</div>
+<div class="graphql-code-line ">name: <a href="/reference/graphql-api/shop/object-types#string">String</a>!</div>
+
+<div class="graphql-code-line ">type: <a href="/reference/graphql-api/shop/object-types#string">String</a>!</div>
+
+<div class="graphql-code-line ">list: <a href="/reference/graphql-api/shop/object-types#boolean">Boolean</a>!</div>
+
+<div class="graphql-code-line ">label: [<a href="/reference/graphql-api/shop/object-types#localizedstring">LocalizedString</a>!]</div>
+
+<div class="graphql-code-line ">description: [<a href="/reference/graphql-api/shop/object-types#localizedstring">LocalizedString</a>!]</div>
+
+<div class="graphql-code-line ">min: <a href="/reference/graphql-api/shop/object-types#float">Float</a></div>
+
+<div class="graphql-code-line ">max: <a href="/reference/graphql-api/shop/object-types#float">Float</a></div>
+
+<div class="graphql-code-line ">step: <a href="/reference/graphql-api/shop/object-types#float">Float</a></div>
+
+<div class="graphql-code-line ">ui: <a href="/reference/graphql-api/shop/object-types#json">JSON</a></div>
+
+
 <div class="graphql-code-line top-level">&#125;</div>
 </div>
 
@@ -995,6 +1072,8 @@ import MemberDescription from '@site/src/components/MemberDescription';
 
 <div class="graphql-code-line ">data: <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>
@@ -1145,6 +1224,32 @@ import MemberDescription from '@site/src/components/MemberDescription';
 <div class="graphql-code-line ">ui: <a href="/reference/graphql-api/shop/object-types#json">JSON</a></div>
 
 
+<div class="graphql-code-line top-level">&#125;</div>
+</div>
+
+## IntStructFieldConfig
+
+<div class="graphql-code-block">
+<div class="graphql-code-line top-level">type <span class="graphql-code-identifier">IntStructFieldConfig</span> &#123;</div>
+<div class="graphql-code-line ">name: <a href="/reference/graphql-api/shop/object-types#string">String</a>!</div>
+
+<div class="graphql-code-line ">type: <a href="/reference/graphql-api/shop/object-types#string">String</a>!</div>
+
+<div class="graphql-code-line ">list: <a href="/reference/graphql-api/shop/object-types#boolean">Boolean</a>!</div>
+
+<div class="graphql-code-line ">label: [<a href="/reference/graphql-api/shop/object-types#localizedstring">LocalizedString</a>!]</div>
+
+<div class="graphql-code-line ">description: [<a href="/reference/graphql-api/shop/object-types#localizedstring">LocalizedString</a>!]</div>
+
+<div class="graphql-code-line ">min: <a href="/reference/graphql-api/shop/object-types#int">Int</a></div>
+
+<div class="graphql-code-line ">max: <a href="/reference/graphql-api/shop/object-types#int">Int</a></div>
+
+<div class="graphql-code-line ">step: <a href="/reference/graphql-api/shop/object-types#int">Int</a></div>
+
+<div class="graphql-code-line ">ui: <a href="/reference/graphql-api/shop/object-types#json">JSON</a></div>
+
+
 <div class="graphql-code-line top-level">&#125;</div>
 </div>
 
@@ -1483,6 +1588,23 @@ 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 top-level">&#125;</div>
+</div>
+
+## OrderInterceptorError
+
+<div class="graphql-code-block">
+<div class="graphql-code-line top-level comment">"""</div>
+<div class="graphql-code-line top-level comment">Returned when an order operation is rejected by an OrderInterceptor method.</div>
+<div class="graphql-code-line top-level comment">"""</div>
+<div class="graphql-code-line top-level">type <span class="graphql-code-identifier">OrderInterceptorError</span> &#123;</div>
+<div class="graphql-code-line ">errorCode: <a href="/reference/graphql-api/shop/enums#errorcode">ErrorCode</a>!</div>
+
+<div class="graphql-code-line ">message: <a href="/reference/graphql-api/shop/object-types#string">String</a>!</div>
+
+<div class="graphql-code-line ">interceptorError: <a href="/reference/graphql-api/shop/object-types#string">String</a>!</div>
+
+
 <div class="graphql-code-line top-level">&#125;</div>
 </div>
 
@@ -1822,6 +1944,8 @@ import MemberDescription from '@site/src/components/MemberDescription';
 
 <div class="graphql-code-line ">metadata: <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>
@@ -2350,6 +2474,8 @@ import MemberDescription from '@site/src/components/MemberDescription';
 
 <div class="graphql-code-line ">metadata: <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>
@@ -2433,7 +2559,7 @@ import MemberDescription from '@site/src/components/MemberDescription';
 
 <div class="graphql-code-block">
 <div class="graphql-code-line top-level">union <span class="graphql-code-identifier">RemoveOrderItemsResult</span> =</div>
-<div class="graphql-code-line "><a href="/reference/graphql-api/shop/object-types#order">Order</a> | <a href="/reference/graphql-api/shop/object-types#ordermodificationerror">OrderModificationError</a></div>
+<div class="graphql-code-line "><a href="/reference/graphql-api/shop/object-types#order">Order</a> | <a href="/reference/graphql-api/shop/object-types#ordermodificationerror">OrderModificationError</a> | <a href="/reference/graphql-api/shop/object-types#orderinterceptorerror">OrderInterceptorError</a></div>
 </div>
 
 ## RequestPasswordResetResult
@@ -2637,6 +2763,8 @@ import MemberDescription from '@site/src/components/MemberDescription';
 
 <div class="graphql-code-line ">discounts: [<a href="/reference/graphql-api/shop/object-types#discount">Discount</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>
@@ -2800,6 +2928,69 @@ import MemberDescription from '@site/src/components/MemberDescription';
 <div class="graphql-code-line top-level">&#125;</div>
 </div>
 
+## StringStructFieldConfig
+
+<div class="graphql-code-block">
+<div class="graphql-code-line top-level">type <span class="graphql-code-identifier">StringStructFieldConfig</span> &#123;</div>
+<div class="graphql-code-line ">name: <a href="/reference/graphql-api/shop/object-types#string">String</a>!</div>
+
+<div class="graphql-code-line ">type: <a href="/reference/graphql-api/shop/object-types#string">String</a>!</div>
+
+<div class="graphql-code-line ">list: <a href="/reference/graphql-api/shop/object-types#boolean">Boolean</a>!</div>
+
+<div class="graphql-code-line ">label: [<a href="/reference/graphql-api/shop/object-types#localizedstring">LocalizedString</a>!]</div>
+
+<div class="graphql-code-line ">description: [<a href="/reference/graphql-api/shop/object-types#localizedstring">LocalizedString</a>!]</div>
+
+<div class="graphql-code-line ">length: <a href="/reference/graphql-api/shop/object-types#int">Int</a></div>
+
+<div class="graphql-code-line ">pattern: <a href="/reference/graphql-api/shop/object-types#string">String</a></div>
+
+<div class="graphql-code-line ">options: [<a href="/reference/graphql-api/shop/object-types#stringfieldoption">StringFieldOption</a>!]</div>
+
+<div class="graphql-code-line ">ui: <a href="/reference/graphql-api/shop/object-types#json">JSON</a></div>
+
+
+<div class="graphql-code-line top-level">&#125;</div>
+</div>
+
+## StructCustomFieldConfig
+
+<div class="graphql-code-block">
+<div class="graphql-code-line top-level">type <span class="graphql-code-identifier">StructCustomFieldConfig</span> &#123;</div>
+<div class="graphql-code-line ">name: <a href="/reference/graphql-api/shop/object-types#string">String</a>!</div>
+
+<div class="graphql-code-line ">type: <a href="/reference/graphql-api/shop/object-types#string">String</a>!</div>
+
+<div class="graphql-code-line ">list: <a href="/reference/graphql-api/shop/object-types#boolean">Boolean</a>!</div>
+
+<div class="graphql-code-line ">fields: [<a href="/reference/graphql-api/shop/object-types#structfieldconfig">StructFieldConfig</a>!]!</div>
+
+<div class="graphql-code-line ">label: [<a href="/reference/graphql-api/shop/object-types#localizedstring">LocalizedString</a>!]</div>
+
+<div class="graphql-code-line ">description: [<a href="/reference/graphql-api/shop/object-types#localizedstring">LocalizedString</a>!]</div>
+
+<div class="graphql-code-line ">readonly: <a href="/reference/graphql-api/shop/object-types#boolean">Boolean</a></div>
+
+<div class="graphql-code-line ">internal: <a href="/reference/graphql-api/shop/object-types#boolean">Boolean</a></div>
+
+<div class="graphql-code-line ">nullable: <a href="/reference/graphql-api/shop/object-types#boolean">Boolean</a></div>
+
+<div class="graphql-code-line ">requiresPermission: [<a href="/reference/graphql-api/shop/enums#permission">Permission</a>!]</div>
+
+<div class="graphql-code-line ">ui: <a href="/reference/graphql-api/shop/object-types#json">JSON</a></div>
+
+
+<div class="graphql-code-line top-level">&#125;</div>
+</div>
+
+## StructFieldConfig
+
+<div class="graphql-code-block">
+<div class="graphql-code-line top-level">union <span class="graphql-code-identifier">StructFieldConfig</span> =</div>
+<div class="graphql-code-line "><a href="/reference/graphql-api/shop/object-types#stringstructfieldconfig">StringStructFieldConfig</a> | <a href="/reference/graphql-api/shop/object-types#intstructfieldconfig">IntStructFieldConfig</a> | <a href="/reference/graphql-api/shop/object-types#floatstructfieldconfig">FloatStructFieldConfig</a> | <a href="/reference/graphql-api/shop/object-types#booleanstructfieldconfig">BooleanStructFieldConfig</a> | <a href="/reference/graphql-api/shop/object-types#datetimestructfieldconfig">DateTimeStructFieldConfig</a> | <a href="/reference/graphql-api/shop/object-types#textstructfieldconfig">TextStructFieldConfig</a></div>
+</div>
+
 ## Success
 
 <div class="graphql-code-block">
@@ -2964,6 +3155,26 @@ import MemberDescription from '@site/src/components/MemberDescription';
 <div class="graphql-code-line ">ui: <a href="/reference/graphql-api/shop/object-types#json">JSON</a></div>
 
 
+<div class="graphql-code-line top-level">&#125;</div>
+</div>
+
+## TextStructFieldConfig
+
+<div class="graphql-code-block">
+<div class="graphql-code-line top-level">type <span class="graphql-code-identifier">TextStructFieldConfig</span> &#123;</div>
+<div class="graphql-code-line ">name: <a href="/reference/graphql-api/shop/object-types#string">String</a>!</div>
+
+<div class="graphql-code-line ">type: <a href="/reference/graphql-api/shop/object-types#string">String</a>!</div>
+
+<div class="graphql-code-line ">list: <a href="/reference/graphql-api/shop/object-types#boolean">Boolean</a>!</div>
+
+<div class="graphql-code-line ">label: [<a href="/reference/graphql-api/shop/object-types#localizedstring">LocalizedString</a>!]</div>
+
+<div class="graphql-code-line ">description: [<a href="/reference/graphql-api/shop/object-types#localizedstring">LocalizedString</a>!]</div>
+
+<div class="graphql-code-line ">ui: <a href="/reference/graphql-api/shop/object-types#json">JSON</a></div>
+
+
 <div class="graphql-code-line top-level">&#125;</div>
 </div>
 
@@ -2992,7 +3203,7 @@ import MemberDescription from '@site/src/components/MemberDescription';
 
 <div class="graphql-code-block">
 <div class="graphql-code-line top-level">union <span class="graphql-code-identifier">UpdateOrderItemsResult</span> =</div>
-<div class="graphql-code-line "><a href="/reference/graphql-api/shop/object-types#order">Order</a> | <a href="/reference/graphql-api/shop/object-types#ordermodificationerror">OrderModificationError</a> | <a href="/reference/graphql-api/shop/object-types#orderlimiterror">OrderLimitError</a> | <a href="/reference/graphql-api/shop/object-types#negativequantityerror">NegativeQuantityError</a> | <a href="/reference/graphql-api/shop/object-types#insufficientstockerror">InsufficientStockError</a></div>
+<div class="graphql-code-line "><a href="/reference/graphql-api/shop/object-types#order">Order</a> | <a href="/reference/graphql-api/shop/object-types#ordermodificationerror">OrderModificationError</a> | <a href="/reference/graphql-api/shop/object-types#orderlimiterror">OrderLimitError</a> | <a href="/reference/graphql-api/shop/object-types#negativequantityerror">NegativeQuantityError</a> | <a href="/reference/graphql-api/shop/object-types#insufficientstockerror">InsufficientStockError</a> | <a href="/reference/graphql-api/shop/object-types#orderinterceptorerror">OrderInterceptorError</a></div>
 </div>
 
 ## Upload

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

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

+ 7 - 8
docs/docs/reference/typescript-api/auth/auth-options.md

@@ -11,7 +11,7 @@ import MemberDescription from '@site/src/components/MemberDescription';
 
 ## AuthOptions
 
-<GenerationInfo sourceFile="packages/core/src/config/vendure-config.ts" sourceLine="330" packageName="@vendure/core" />
+<GenerationInfo sourceFile="packages/core/src/config/vendure-config.ts" sourceLine="332" packageName="@vendure/core" />
 
 The AuthOptions define how authentication and authorization is managed.
 
@@ -23,7 +23,7 @@ interface AuthOptions {
     authTokenHeaderKey?: string;
     sessionDuration?: string | number;
     sessionCacheStrategy?: SessionCacheStrategy;
-    sessionCacheTTL?: number;
+    sessionCacheTTL?: string | number;
     requireVerification?: boolean;
     verificationTokenDuration?: string | number;
     superadminCredentials?: SuperadminCredentials;
@@ -82,16 +82,15 @@ If passed as a number should represent milliseconds and if passed as a string de
 [zeit/ms](https://github.com/zeit/ms.js).  Eg: `60`, `'2 days'`, `'10h'`, `'7d'`
 ### sessionCacheStrategy
 
-<MemberInfo kind="property" type={`<a href='/reference/typescript-api/auth/session-cache-strategy#sessioncachestrategy'>SessionCacheStrategy</a>`} default={`<a href='/reference/typescript-api/auth/in-memory-session-cache-strategy#inmemorysessioncachestrategy'>InMemorySessionCacheStrategy</a>`}   />
+<MemberInfo kind="property" type={`<a href='/reference/typescript-api/auth/session-cache-strategy#sessioncachestrategy'>SessionCacheStrategy</a>`} default={`<a href='/reference/typescript-api/auth/default-session-cache-strategy#defaultsessioncachestrategy'>DefaultSessionCacheStrategy</a>`}   />
 
-This strategy defines how sessions will be cached. By default, sessions are cached using a simple
-in-memory caching strategy which is suitable for development and low-traffic, single-instance
-deployments.
+This strategy defines how sessions will be cached. By default, since v3.1.0, sessions are cached using
+the underlying cache strategy defined in the <a href='/reference/typescript-api/configuration/system-options#systemoptions'>SystemOptions</a>`.cacheStrategy`.
 ### sessionCacheTTL
 
-<MemberInfo kind="property" type={`string | number`} default={`300`} />
+<MemberInfo kind="property" type={`string | number`} default={`300`}   />
 
-The "time to live" of a given item in the session cache. This determines the length of time that a cache entry 
+The "time to live" of a given item in the session cache. This determines the length of time that a cache entry
 is kept before being considered "stale" and being replaced with fresh data taken from the database.
 
 If passed as a number should represent seconds and if passed as a string describes a time span per

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

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

+ 77 - 0
docs/docs/reference/typescript-api/auth/default-session-cache-strategy.md

@@ -0,0 +1,77 @@
+---
+title: "DefaultSessionCacheStrategy"
+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';
+
+
+## DefaultSessionCacheStrategy
+
+<GenerationInfo sourceFile="packages/core/src/config/session-cache/default-session-cache-strategy.ts" sourceLine="17" packageName="@vendure/core" since="3.1.0" />
+
+The default <a href='/reference/typescript-api/auth/session-cache-strategy#sessioncachestrategy'>SessionCacheStrategy</a> delegates to the configured
+<a href='/reference/typescript-api/cache/cache-strategy#cachestrategy'>CacheStrategy</a> to store the session data. This should be suitable
+for most use-cases, assuming you select a suitable <a href='/reference/typescript-api/cache/cache-strategy#cachestrategy'>CacheStrategy</a>
+
+```ts title="Signature"
+class DefaultSessionCacheStrategy implements SessionCacheStrategy {
+    protected cacheService: CacheService;
+    constructor(options?: {
+            ttl?: number;
+            cachePrefix?: string;
+        })
+    init(injector: Injector) => ;
+    set(session: CachedSession) => Promise<void>;
+    get(sessionToken: string) => Promise<CachedSession | undefined>;
+    delete(sessionToken: string) => void | Promise<void>;
+    clear() => Promise<void>;
+}
+```
+* Implements: <code><a href='/reference/typescript-api/auth/session-cache-strategy#sessioncachestrategy'>SessionCacheStrategy</a></code>
+
+
+
+<div className="members-wrapper">
+
+### cacheService
+
+<MemberInfo kind="property" type={`<a href='/reference/typescript-api/cache/cache-service#cacheservice'>CacheService</a>`}   />
+
+
+### constructor
+
+<MemberInfo kind="method" type={`(options?: {
             ttl?: number;
             cachePrefix?: string;
         }) => DefaultSessionCacheStrategy`}   />
+
+
+### init
+
+<MemberInfo kind="method" type={`(injector: <a href='/reference/typescript-api/common/injector#injector'>Injector</a>) => `}   />
+
+
+### set
+
+<MemberInfo kind="method" type={`(session: <a href='/reference/typescript-api/auth/session-cache-strategy#cachedsession'>CachedSession</a>) => Promise&#60;void&#62;`}   />
+
+
+### get
+
+<MemberInfo kind="method" type={`(sessionToken: string) => Promise&#60;<a href='/reference/typescript-api/auth/session-cache-strategy#cachedsession'>CachedSession</a> | undefined&#62;`}   />
+
+
+### delete
+
+<MemberInfo kind="method" type={`(sessionToken: string) => void | Promise&#60;void&#62;`}   />
+
+
+### clear
+
+<MemberInfo kind="method" type={`() => Promise&#60;void&#62;`}   />
+
+
+
+
+</div>

+ 12 - 0
docs/docs/reference/typescript-api/auth/external-authentication-service.md

@@ -39,6 +39,10 @@ class ExternalAuthenticationService {
             roles: Role[];
         }) => ;
     findUser(ctx: RequestContext, strategy: string, externalIdentifier: string) => Promise<User | undefined>;
+    createUser(ctx: RequestContext, config: {
+            strategy: string;
+            externalIdentifier: string;
+        }) => Promise<User>;
 }
 ```
 
@@ -85,6 +89,14 @@ Administrator record in Vendure for that user.
 <MemberInfo kind="method" type={`(ctx: <a href='/reference/typescript-api/request/request-context#requestcontext'>RequestContext</a>, strategy: string, externalIdentifier: string) => Promise&#60;<a href='/reference/typescript-api/entities/user#user'>User</a> | undefined&#62;`}   />
 
 
+### createUser
+
+<MemberInfo kind="method" type={`(ctx: <a href='/reference/typescript-api/request/request-context#requestcontext'>RequestContext</a>, config: {
             strategy: string;
             externalIdentifier: string;
         }) => Promise&#60;<a href='/reference/typescript-api/entities/user#user'>User</a>&#62;`}   />
+
+Looks up a User based on their identifier from an external authentication
+provider. Creates the user if does not exist. Unlike `findCustomerUser` and `findAdministratorUser`,
+this method does not enforce that the User is associated with a Customer or
+Administrator account.
 
 
 </div>

+ 14 - 5
docs/docs/reference/typescript-api/auth/session-cache-strategy.md

@@ -11,17 +11,26 @@ import MemberDescription from '@site/src/components/MemberDescription';
 
 ## SessionCacheStrategy
 
-<GenerationInfo sourceFile="packages/core/src/config/session-cache/session-cache-strategy.ts" sourceLine="155" packageName="@vendure/core" />
+<GenerationInfo sourceFile="packages/core/src/config/session-cache/session-cache-strategy.ts" sourceLine="164" packageName="@vendure/core" />
 
 This strategy defines how sessions get cached. Since most requests will need the Session
 object for permissions data, it can become a bottleneck to go to the database and do a multi-join
 SQL query each time. Therefore, we cache the session data only perform the SQL query once and upon
 invalidation of the cache.
 
-The Vendure default is to use a the <a href='/reference/typescript-api/auth/in-memory-session-cache-strategy#inmemorysessioncachestrategy'>InMemorySessionCacheStrategy</a>, which is fast and suitable for
-single-instance deployments. However, for multi-instance deployments (horizontally scaled, serverless etc.),
-you will need to define a custom strategy that stores the session cache in a shared data store, such as in the
-DB or in Redis.
+The Vendure default from v3.1+ is to use a the <a href='/reference/typescript-api/auth/default-session-cache-strategy#defaultsessioncachestrategy'>DefaultSessionCacheStrategy</a>, which delegates
+to the configured <a href='/reference/typescript-api/cache/cache-strategy#cachestrategy'>CacheStrategy</a> to store the session data. This should be suitable
+for most use-cases.
+
+:::note
+
+If you are using v3.1 or later, you should not normally need to implement a custom `SessionCacheStrategy`,
+since this is now handled by the <a href='/reference/typescript-api/auth/default-session-cache-strategy#defaultsessioncachestrategy'>DefaultSessionCacheStrategy</a>.
+
+:::
+
+Prior to v3.1, the default was to use the <a href='/reference/typescript-api/auth/in-memory-session-cache-strategy#inmemorysessioncachestrategy'>InMemorySessionCacheStrategy</a>, which is fast but suitable for
+single-instance deployments.
 
 :::info
 

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

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

+ 48 - 0
docs/docs/reference/typescript-api/cache/cache-config.md

@@ -0,0 +1,48 @@
+---
+title: "CacheConfig"
+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';
+
+
+## CacheConfig
+
+<GenerationInfo sourceFile="packages/core/src/cache/cache.ts" sourceLine="14" packageName="@vendure/core" since="3.1.0" />
+
+Configuration for a new <a href='/reference/typescript-api/cache/#cache'>Cache</a> instance.
+
+```ts title="Signature"
+interface CacheConfig {
+    getKey: (id: string | number) => string;
+    options?: SetCacheKeyOptions;
+}
+```
+
+<div className="members-wrapper">
+
+### getKey
+
+<MemberInfo kind="property" type={`(id: string | number) =&#62; string`}   />
+
+A function which generates a cache key from the given id.
+This key will be used to store the value in the cache.
+
+By convention, the key should be namespaced to avoid conflicts.
+
+*Example*
+
+```ts
+getKey: id => `MyStrategy:getProductVariantIds:${id}`,
+```
+### options
+
+<MemberInfo kind="property" type={`SetCacheKeyOptions`}   />
+
+Options available when setting the value in the cache.
+
+
+</div>

+ 80 - 0
docs/docs/reference/typescript-api/cache/cache-service.md

@@ -0,0 +1,80 @@
+---
+title: "CacheService"
+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';
+
+
+## CacheService
+
+<GenerationInfo sourceFile="packages/core/src/cache/cache.service.ts" sourceLine="20" packageName="@vendure/core" since="3.1.0" />
+
+The CacheService is used to cache data in order to optimize performance.
+
+Internally it makes use of the configured <a href='/reference/typescript-api/cache/cache-strategy#cachestrategy'>CacheStrategy</a> to persist
+the cache into a key-value store.
+
+```ts title="Signature"
+class CacheService {
+    protected cacheStrategy: CacheStrategy;
+    constructor(configService: ConfigService)
+    createCache(config: CacheConfig) => Cache;
+    get(key: string) => Promise<T | undefined>;
+    set(key: string, value: T, options?: SetCacheKeyOptions) => Promise<void>;
+    delete(key: string) => Promise<void>;
+    invalidateTags(tags: string[]) => Promise<void>;
+}
+```
+
+<div className="members-wrapper">
+
+### cacheStrategy
+
+<MemberInfo kind="property" type={`<a href='/reference/typescript-api/cache/cache-strategy#cachestrategy'>CacheStrategy</a>`}   />
+
+
+### constructor
+
+<MemberInfo kind="method" type={`(configService: ConfigService) => CacheService`}   />
+
+
+### createCache
+
+<MemberInfo kind="method" type={`(config: <a href='/reference/typescript-api/cache/cache-config#cacheconfig'>CacheConfig</a>) => <a href='/reference/typescript-api/cache/#cache'>Cache</a>`}   />
+
+Creates a new <a href='/reference/typescript-api/cache/#cache'>Cache</a> instance with the given configuration.
+
+The `Cache` instance provides a convenience wrapper around the `CacheService`
+methods.
+### get
+
+<MemberInfo kind="method" type={`(key: string) => Promise&#60;T | undefined&#62;`}   />
+
+Gets an item from the cache, or returns undefined if the key is not found, or the
+item has expired.
+### set
+
+<MemberInfo kind="method" type={`(key: string, value: T, options?: SetCacheKeyOptions) => Promise&#60;void&#62;`}   />
+
+Sets a key-value pair in the cache. The value must be serializable, so cannot contain
+things like functions, circular data structures, class instances etc.
+
+Optionally a "time to live" (ttl) can be specified, which means that the key will
+be considered stale after that many milliseconds.
+### delete
+
+<MemberInfo kind="method" type={`(key: string) => Promise&#60;void&#62;`}   />
+
+Deletes an item from the cache.
+### invalidateTags
+
+<MemberInfo kind="method" type={`(tags: string[]) => Promise&#60;void&#62;`}   />
+
+Deletes all items from the cache which contain at least one matching tag.
+
+
+</div>

+ 62 - 0
docs/docs/reference/typescript-api/cache/cache-strategy.md

@@ -0,0 +1,62 @@
+---
+title: "CacheStrategy"
+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';
+
+
+## CacheStrategy
+
+<GenerationInfo sourceFile="packages/core/src/config/system/cache-strategy.ts" sourceLine="36" packageName="@vendure/core" since="3.1.0" />
+
+The CacheStrategy defines how the underlying shared cache mechanism is implemented.
+
+It is used by the <a href='/reference/typescript-api/cache/cache-service#cacheservice'>CacheService</a> to take care of storage and retrieval of items
+from the cache.
+
+```ts title="Signature"
+interface CacheStrategy extends InjectableStrategy {
+    get<T extends JsonCompatible<T>>(key: string): Promise<T | undefined>;
+    set<T extends JsonCompatible<T>>(key: string, value: T, options?: SetCacheKeyOptions): Promise<void>;
+    delete(key: string): Promise<void>;
+    invalidateTags(tags: string[]): Promise<void>;
+}
+```
+* Extends: <code><a href='/reference/typescript-api/common/injectable-strategy#injectablestrategy'>InjectableStrategy</a></code>
+
+
+
+<div className="members-wrapper">
+
+### get
+
+<MemberInfo kind="method" type={`(key: string) => Promise&#60;T | undefined&#62;`}   />
+
+Gets an item from the cache, or returns undefined if the key is not found, or the
+item has expired.
+### set
+
+<MemberInfo kind="method" type={`(key: string, value: T, options?: SetCacheKeyOptions) => Promise&#60;void&#62;`}   />
+
+Sets a key-value pair in the cache. The value must be serializable, so cannot contain
+things like functions, circular data structures, class instances etc.
+
+Optionally a "time to live" (ttl) can be specified, which means that the key will
+be considered stale after that many milliseconds.
+### delete
+
+<MemberInfo kind="method" type={`(key: string) => Promise&#60;void&#62;`}   />
+
+Deletes an item from the cache.
+### invalidateTags
+
+<MemberInfo kind="method" type={`(tags: string[]) => Promise&#60;void&#62;`}   />
+
+Deletes all items from the cache which contain at least one matching tag.
+
+
+</div>

+ 44 - 0
docs/docs/reference/typescript-api/cache/default-cache-plugin.md

@@ -0,0 +1,44 @@
+---
+title: "DefaultCachePlugin"
+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';
+
+
+## DefaultCachePlugin
+
+<GenerationInfo sourceFile="packages/core/src/plugin/default-cache-plugin/default-cache-plugin.ts" sourceLine="34" packageName="@vendure/core" since="3.1.0" />
+
+This plugin provides a simple SQL-based cache strategy <a href='/reference/typescript-api/cache/sql-cache-strategy#sqlcachestrategy'>SqlCacheStrategy</a> which stores cached
+items in the database.
+It is suitable for production use (including multi-instance setups). For increased performance
+you can also consider using the <a href='/reference/typescript-api/cache/redis-cache-plugin#rediscacheplugin'>RedisCachePlugin</a>.
+
+```ts title="Signature"
+class DefaultCachePlugin {
+    static options: DefaultCachePluginInitOptions = {
+        cacheSize: 10_000,
+    };
+    init(options: DefaultCachePluginInitOptions) => ;
+}
+```
+
+<div className="members-wrapper">
+
+### options
+
+<MemberInfo kind="property" type={`DefaultCachePluginInitOptions`}   />
+
+
+### init
+
+<MemberInfo kind="method" type={`(options: DefaultCachePluginInitOptions) => `}   />
+
+
+
+
+</div>

+ 83 - 0
docs/docs/reference/typescript-api/cache/index.md

@@ -0,0 +1,83 @@
+---
+title: "Cache"
+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';
+
+
+## Cache
+
+<GenerationInfo sourceFile="packages/core/src/cache/cache.ts" sourceLine="72" packageName="@vendure/core" since="3.1.0" />
+
+A convenience wrapper around the <a href='/reference/typescript-api/cache/cache-service#cacheservice'>CacheService</a> methods which provides a simple
+API for caching and retrieving data.
+
+The advantage of using the `Cache` class rather than directly calling the `CacheService`
+methods is that it allows you to define a consistent way of generating cache keys and
+to set default cache options, and takes care of setting the value in cache if it does not
+already exist.
+
+In most cases, using the `Cache` class will result in simpler and more readable code.
+
+This class is normally created via the <a href='/reference/typescript-api/cache/cache-service#cacheservice'>CacheService</a>.`createCache()` method.
+
+*Example*
+
+```ts
+const cache = cacheService.createCache({
+  getKey: id => `ProductVariantIds:${id}`,
+  options: {
+    ttl: 1000 * 60 * 60,
+    tags: ['products'],
+  },
+});
+
+// This will fetch the value from the cache if it exists, or
+// fetch it from the ProductService if not, and then cache
+// using the key 'ProductVariantIds.${id}'.
+const variantIds = await cache.get(id, async () => {
+  const variants await ProductService.getVariantsByProductId(ctx, id) ;
+  // The cached value must be serializable, so we just return the ids
+  return variants.map(v => v.id);
+});
+```
+
+```ts title="Signature"
+class Cache {
+    constructor(config: CacheConfig, cacheService: CacheService)
+    get(id: string | number, getValueFn: () => T | Promise<T>) => Promise<T>;
+    delete(id: string | number | Array<string | number>) => Promise<void>;
+    invalidateTags(tags: string[]) => Promise<void>;
+}
+```
+
+<div className="members-wrapper">
+
+### constructor
+
+<MemberInfo kind="method" type={`(config: <a href='/reference/typescript-api/cache/cache-config#cacheconfig'>CacheConfig</a>, cacheService: <a href='/reference/typescript-api/cache/cache-service#cacheservice'>CacheService</a>) => Cache`}   />
+
+
+### get
+
+<MemberInfo kind="method" type={`(id: string | number, getValueFn: () =&#62; T | Promise&#60;T&#62;) => Promise&#60;T&#62;`}   />
+
+Retrieves the value from the cache if it exists, otherwise calls the `getValueFn` function
+to get the value, sets it in the cache and returns it.
+### delete
+
+<MemberInfo kind="method" type={`(id: string | number | Array&#60;string | number&#62;) => Promise&#60;void&#62;`}   />
+
+Deletes one or more items from the cache.
+### invalidateTags
+
+<MemberInfo kind="method" type={`(tags: string[]) => Promise&#60;void&#62;`}   />
+
+Invalidates one or more tags in the cache.
+
+
+</div>

+ 45 - 0
docs/docs/reference/typescript-api/cache/redis-cache-plugin.md

@@ -0,0 +1,45 @@
+---
+title: "RedisCachePlugin"
+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';
+
+
+## RedisCachePlugin
+
+<GenerationInfo sourceFile="packages/core/src/plugin/redis-cache-plugin/redis-cache-plugin.ts" sourceLine="17" packageName="@vendure/core" since="3.1.0" />
+
+This plugin provides a Redis-based <a href='/reference/typescript-api/cache/redis-cache-strategy#rediscachestrategy'>RedisCacheStrategy</a> which stores cached items in a Redis instance.
+This is a high-performance cache strategy which is suitable for production use, and is a drop-in
+replacement for the <a href='/reference/typescript-api/cache/default-cache-plugin#defaultcacheplugin'>DefaultCachePlugin</a>.
+
+```ts title="Signature"
+class RedisCachePlugin {
+    static options: RedisCachePluginInitOptions = {
+        maxItemSizeInBytes: 128_000,
+        redisOptions: {},
+        namespace: 'vendure-cache',
+    };
+    init(options: RedisCachePluginInitOptions) => ;
+}
+```
+
+<div className="members-wrapper">
+
+### options
+
+<MemberInfo kind="property" type={`RedisCachePluginInitOptions`}   />
+
+
+### init
+
+<MemberInfo kind="method" type={`(options: RedisCachePluginInitOptions) => `}   />
+
+
+
+
+</div>

+ 73 - 0
docs/docs/reference/typescript-api/cache/redis-cache-strategy.md

@@ -0,0 +1,73 @@
+---
+title: "RedisCacheStrategy"
+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';
+
+
+## RedisCacheStrategy
+
+<GenerationInfo sourceFile="packages/core/src/plugin/redis-cache-plugin/redis-cache-strategy.ts" sourceLine="17" packageName="@vendure/core" since="3.1.0" />
+
+A <a href='/reference/typescript-api/cache/cache-strategy#cachestrategy'>CacheStrategy</a> which stores cached items in a Redis instance.
+This is a high-performance cache strategy which is suitable for production use.
+
+```ts title="Signature"
+class RedisCacheStrategy implements CacheStrategy {
+    constructor(options: RedisCachePluginInitOptions)
+    init() => ;
+    destroy() => ;
+    get(key: string) => Promise<T | undefined>;
+    set(key: string, value: T, options?: SetCacheKeyOptions) => Promise<void>;
+    delete(key: string) => Promise<void>;
+    invalidateTags(tags: string[]) => Promise<void>;
+}
+```
+* Implements: <code><a href='/reference/typescript-api/cache/cache-strategy#cachestrategy'>CacheStrategy</a></code>
+
+
+
+<div className="members-wrapper">
+
+### constructor
+
+<MemberInfo kind="method" type={`(options: RedisCachePluginInitOptions) => RedisCacheStrategy`}   />
+
+
+### init
+
+<MemberInfo kind="method" type={`() => `}   />
+
+
+### destroy
+
+<MemberInfo kind="method" type={`() => `}   />
+
+
+### get
+
+<MemberInfo kind="method" type={`(key: string) => Promise&#60;T | undefined&#62;`}   />
+
+
+### set
+
+<MemberInfo kind="method" type={`(key: string, value: T, options?: SetCacheKeyOptions) => Promise&#60;void&#62;`}   />
+
+
+### delete
+
+<MemberInfo kind="method" type={`(key: string) => Promise&#60;void&#62;`}   />
+
+
+### invalidateTags
+
+<MemberInfo kind="method" type={`(tags: string[]) => Promise&#60;void&#62;`}   />
+
+
+
+
+</div>

+ 58 - 0
docs/docs/reference/typescript-api/cache/request-context-cache-service.md

@@ -0,0 +1,58 @@
+---
+title: "RequestContextCacheService"
+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';
+
+
+## RequestContextCacheService
+
+<GenerationInfo sourceFile="packages/core/src/cache/request-context-cache.service.ts" sourceLine="15" packageName="@vendure/core" />
+
+This service is used to cache arbitrary data relative to an ongoing request.
+It does this by using a WeakMap bound to the current RequestContext, so the cached
+data is available for the duration of the request. Once the request completes, the
+cached data will be automatically garbage-collected.
+
+This is useful for caching data which is expensive to compute and which is needed
+multiple times during the handling of a single request.
+
+```ts title="Signature"
+class RequestContextCacheService {
+    set(ctx: RequestContext, key: any, val: T) => void;
+    get(ctx: RequestContext, key: any) => T | undefined;
+    get(ctx: RequestContext, key: any, getDefault?: () => T) => T;
+    get(ctx: RequestContext, key: any, getDefault?: () => T) => T | Promise<T> | undefined;
+}
+```
+
+<div className="members-wrapper">
+
+### set
+
+<MemberInfo kind="method" type={`(ctx: <a href='/reference/typescript-api/request/request-context#requestcontext'>RequestContext</a>, key: any, val: T) => void`}   />
+
+Set a value in the RequestContext cache.
+### get
+
+<MemberInfo kind="method" type={`(ctx: <a href='/reference/typescript-api/request/request-context#requestcontext'>RequestContext</a>, key: any) => T | undefined`}   />
+
+Get a value from the RequestContext cache. If the value is not found, the `getDefault`
+function will be called to get the value, which will then be cached and returned.
+### get
+
+<MemberInfo kind="method" type={`(ctx: <a href='/reference/typescript-api/request/request-context#requestcontext'>RequestContext</a>, key: any, getDefault?: () =&#62; T) => T`}   />
+
+
+### get
+
+<MemberInfo kind="method" type={`(ctx: <a href='/reference/typescript-api/request/request-context#requestcontext'>RequestContext</a>, key: any, getDefault?: () =&#62; T) => T | Promise&#60;T&#62; | undefined`}   />
+
+
+
+
+</div>

+ 90 - 0
docs/docs/reference/typescript-api/cache/sql-cache-strategy.md

@@ -0,0 +1,90 @@
+---
+title: "SqlCacheStrategy"
+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';
+
+
+## SqlCacheStrategy
+
+<GenerationInfo sourceFile="packages/core/src/plugin/default-cache-plugin/sql-cache-strategy.ts" sourceLine="18" packageName="@vendure/core" since="3.1.0" />
+
+
+
+```ts title="Signature"
+class SqlCacheStrategy implements CacheStrategy {
+    protected cacheSize = 10_000;
+    protected ttlProvider: CacheTtlProvider;
+    constructor(config?: { cacheSize?: number; cacheTtlProvider?: CacheTtlProvider })
+    protected connection: TransactionalConnection;
+    protected configService: ConfigService;
+    init(injector: Injector) => ;
+    get(key: string) => Promise<T | undefined>;
+    set(key: string, value: T, options?: SetCacheKeyOptions) => ;
+    delete(key: string) => ;
+    invalidateTags(tags: string[]) => ;
+}
+```
+* Implements: <code><a href='/reference/typescript-api/cache/cache-strategy#cachestrategy'>CacheStrategy</a></code>
+
+
+
+<div className="members-wrapper">
+
+### cacheSize
+
+<MemberInfo kind="property" type={``}   />
+
+
+### ttlProvider
+
+<MemberInfo kind="property" type={`CacheTtlProvider`}   />
+
+
+### constructor
+
+<MemberInfo kind="method" type={`(config?: { cacheSize?: number; cacheTtlProvider?: CacheTtlProvider }) => SqlCacheStrategy`}   />
+
+
+### connection
+
+<MemberInfo kind="property" type={`<a href='/reference/typescript-api/data-access/transactional-connection#transactionalconnection'>TransactionalConnection</a>`}   />
+
+
+### configService
+
+<MemberInfo kind="property" type={`ConfigService`}   />
+
+
+### init
+
+<MemberInfo kind="method" type={`(injector: <a href='/reference/typescript-api/common/injector#injector'>Injector</a>) => `}   />
+
+
+### get
+
+<MemberInfo kind="method" type={`(key: string) => Promise&#60;T | undefined&#62;`}   />
+
+
+### set
+
+<MemberInfo kind="method" type={`(key: string, value: T, options?: SetCacheKeyOptions) => `}   />
+
+
+### delete
+
+<MemberInfo kind="method" type={`(key: string) => `}   />
+
+
+### invalidateTags
+
+<MemberInfo kind="method" type={`(tags: string[]) => `}   />
+
+
+
+
+</div>

+ 1 - 1
docs/docs/reference/typescript-api/common/admin-ui/admin-ui-app-config.md

@@ -11,7 +11,7 @@ import MemberDescription from '@site/src/components/MemberDescription';
 
 ## AdminUiAppConfig
 
-<GenerationInfo sourceFile="packages/common/src/shared-types.ts" sourceLine="349" packageName="@vendure/common" />
+<GenerationInfo sourceFile="packages/common/src/shared-types.ts" sourceLine="355" packageName="@vendure/common" />
 
 Configures the path to a custom-build of the Admin UI app.
 

+ 1 - 1
docs/docs/reference/typescript-api/common/admin-ui/admin-ui-app-dev-mode-config.md

@@ -11,7 +11,7 @@ import MemberDescription from '@site/src/components/MemberDescription';
 
 ## AdminUiAppDevModeConfig
 
-<GenerationInfo sourceFile="packages/common/src/shared-types.ts" sourceLine="377" packageName="@vendure/common" />
+<GenerationInfo sourceFile="packages/common/src/shared-types.ts" sourceLine="383" packageName="@vendure/common" />
 
 Information about the Admin UI app dev server.
 

+ 1 - 1
docs/docs/reference/typescript-api/common/admin-ui/admin-ui-config.md

@@ -11,7 +11,7 @@ import MemberDescription from '@site/src/components/MemberDescription';
 
 ## AdminUiConfig
 
-<GenerationInfo sourceFile="packages/common/src/shared-types.ts" sourceLine="215" packageName="@vendure/common" />
+<GenerationInfo sourceFile="packages/common/src/shared-types.ts" sourceLine="221" packageName="@vendure/common" />
 
 This interface describes JSON config file (vendure-ui-config.json) used by the Admin UI.
 The values are loaded at run-time by the Admin UI app, and allow core configuration to be

+ 46 - 2
docs/docs/reference/typescript-api/common/bootstrap.md

@@ -11,7 +11,7 @@ import MemberDescription from '@site/src/components/MemberDescription';
 
 ## bootstrap
 
-<GenerationInfo sourceFile="packages/core/src/bootstrap.ts" sourceLine="106" packageName="@vendure/core" />
+<GenerationInfo sourceFile="packages/core/src/bootstrap.ts" sourceLine="159" packageName="@vendure/core" />
 
 Bootstraps the Vendure server. This is the entry point to the application.
 
@@ -49,6 +49,27 @@ bootstrap(config, {
 });
 ```
 
+### Ignoring compatibility errors for plugins
+
+Since v3.1.0, you can ignore compatibility errors for specific plugins by passing the `ignoreCompatibilityErrorsForPlugins` option.
+
+This should be used with caution, only if you are sure that the plugin will still work as expected with the current version of Vendure.
+
+*Example*
+
+```ts
+import { bootstrap } from '@vendure/core';
+import { config } from './vendure-config';
+import { MyPlugin } from './plugins/my-plugin';
+
+bootstrap(config, {
+  // Let's say that `MyPlugin` is not yet compatible with the current version of Vendure
+  // but we know that it will still work as expected, and we are not able to publish
+  // a new version of the plugin right now.
+  ignoreCompatibilityErrorsForPlugins: [MyPlugin],
+});
+```
+
 ```ts title="Signature"
 function bootstrap(userConfig: Partial<VendureConfig>, options?: BootstrapOptions): Promise<INestApplication>
 ```
@@ -73,7 +94,8 @@ Vendure server.
 
 ```ts title="Signature"
 interface BootstrapOptions {
-    nestApplicationOptions: NestApplicationOptions;
+    nestApplicationOptions?: NestApplicationOptions;
+    ignoreCompatibilityErrorsForPlugins?: Array<DynamicModule | Type<any>>;
 }
 ```
 
@@ -84,6 +106,28 @@ interface BootstrapOptions {
 <MemberInfo kind="property" type={`NestApplicationOptions`}   />
 
 These options get passed directly to the `NestFactory.create()` method.
+### ignoreCompatibilityErrorsForPlugins
+
+<MemberInfo kind="property" type={`Array&#60;DynamicModule | Type&#60;any&#62;&#62;`} default={`[]`}  since="3.1.0"  />
+
+By default, if a plugin specifies a compatibility range which does not include the current
+Vendure version, the bootstrap process will fail. This option allows you to ignore compatibility
+errors for specific plugins.
+
+This setting should be used with caution, only if you are sure that the plugin will still
+work as expected with the current version of Vendure.
+
+*Example*
+
+```ts
+import { bootstrap } from '@vendure/core';
+import { config } from './vendure-config';
+import { MyPlugin } from './plugins/my-plugin';
+
+bootstrap(config, {
+ ignoreCompatibilityErrorsForPlugins: [MyPlugin],
+});
+```
 
 
 </div>

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

@@ -11,7 +11,7 @@ import MemberDescription from '@site/src/components/MemberDescription';
 
 ## CurrencyCode
 
-<GenerationInfo sourceFile="packages/common/src/generated-types.ts" sourceLine="982" packageName="@vendure/common" />
+<GenerationInfo sourceFile="packages/common/src/generated-types.ts" sourceLine="994" packageName="@vendure/common" />
 
 ISO 4217 currency code
 

+ 1 - 1
docs/docs/reference/typescript-api/common/entity-relation-paths.md

@@ -11,7 +11,7 @@ import MemberDescription from '@site/src/components/MemberDescription';
 
 ## EntityRelationPaths
 
-<GenerationInfo sourceFile="packages/core/src/common/types/entity-relation-paths.ts" sourceLine="23" packageName="@vendure/core" />
+<GenerationInfo sourceFile="packages/core/src/common/types/entity-relation-paths.ts" sourceLine="25" packageName="@vendure/core" />
 
 This type allows type-safe access to entity relations using strings with dot notation.
 It works to 2 levels deep.

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

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

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

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

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

@@ -11,7 +11,7 @@ import MemberDescription from '@site/src/components/MemberDescription';
 
 ## Permission
 
-<GenerationInfo sourceFile="packages/common/src/generated-types.ts" sourceLine="4335" packageName="@vendure/common" />
+<GenerationInfo sourceFile="packages/common/src/generated-types.ts" sourceLine="4422" packageName="@vendure/common" />
 
 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.

+ 1 - 1
docs/docs/reference/typescript-api/configurable-operation-def/config-arg-type.md

@@ -11,7 +11,7 @@ import MemberDescription from '@site/src/components/MemberDescription';
 
 ## ConfigArgType
 
-<GenerationInfo sourceFile="packages/common/src/shared-types.ts" sourceLine="126" packageName="@vendure/common" />
+<GenerationInfo sourceFile="packages/common/src/shared-types.ts" sourceLine="130" packageName="@vendure/common" />
 
 Certain entities (those which implement <a href='/reference/typescript-api/configurable-operation-def/#configurableoperationdef'>ConfigurableOperationDef</a>) allow arbitrary
 configuration arguments to be specified which can then be set in the admin-ui and used in

+ 2 - 1
docs/docs/reference/typescript-api/configurable-operation-def/default-form-component-id.md

@@ -11,7 +11,7 @@ import MemberDescription from '@site/src/components/MemberDescription';
 
 ## DefaultFormComponentId
 
-<GenerationInfo sourceFile="packages/common/src/shared-types.ts" sourceLine="135" packageName="@vendure/common" />
+<GenerationInfo sourceFile="packages/common/src/shared-types.ts" sourceLine="139" packageName="@vendure/common" />
 
 The ids of the default form input components that ship with the
 Admin UI.
@@ -34,4 +34,5 @@ type DefaultFormComponentId = | 'boolean-form-input'
     | 'textarea-form-input'
     | 'product-multi-form-input'
     | 'combination-mode-form-input'
+    | 'struct-form-input'
 ```

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

@@ -11,7 +11,7 @@ import MemberDescription from '@site/src/components/MemberDescription';
 
 ## DefaultFormConfigHash
 
-<GenerationInfo sourceFile="packages/common/src/shared-types.ts" sourceLine="160" packageName="@vendure/common" />
+<GenerationInfo sourceFile="packages/common/src/shared-types.ts" sourceLine="165" packageName="@vendure/common" />
 
 Used to define the expected arguments for a given default form input component.
 
@@ -29,17 +29,18 @@ type DefaultFormConfigHash = {
     'product-selector-form-input': Record<string, never>;
     'relation-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 };
-    '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>;
+    'struct-form-input': Record<string, never>;
 }
 ```
 
@@ -107,7 +108,7 @@ type DefaultFormConfigHash = {
 
 ### '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'
@@ -117,12 +118,12 @@ type DefaultFormConfigHash = {
 
 ### 'textarea-form-input'
 
-<MemberInfo kind="property" type={`{         spellcheck?: boolean;     }`}   />
+<MemberInfo kind="property" type={`{
         spellcheck?: boolean;
     }`}   />
 
 
 ### 'product-multi-form-input'
 
-<MemberInfo kind="property" type={`{         selectionMode?: 'product' | 'variant';     }`}   />
+<MemberInfo kind="property" type={`{
         selectionMode?: 'product' | 'variant';
     }`}   />
 
 
 ### 'combination-mode-form-input'
@@ -130,6 +131,11 @@ type DefaultFormConfigHash = {
 <MemberInfo kind="property" type={`Record&#60;string, never&#62;`}   />
 
 
+### 'struct-form-input'
+
+<MemberInfo kind="property" type={`Record&#60;string, never&#62;`}   />
+
+
 
 
 </div>

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

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

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

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

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

@@ -11,7 +11,7 @@ import MemberDescription from '@site/src/components/MemberDescription';
 
 ## EntityOptions
 
-<GenerationInfo sourceFile="packages/core/src/config/vendure-config.ts" sourceLine="953" packageName="@vendure/core" since="1.3.0" />
+<GenerationInfo sourceFile="packages/core/src/config/vendure-config.ts" sourceLine="965" packageName="@vendure/core" since="1.3.0" />
 
 Options relating to the internal handling of entities.
 

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

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

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

@@ -11,7 +11,7 @@ import MemberDescription from '@site/src/components/MemberDescription';
 
 ## SystemOptions
 
-<GenerationInfo sourceFile="packages/core/src/config/vendure-config.ts" sourceLine="1042" packageName="@vendure/core" since="1.6.0" />
+<GenerationInfo sourceFile="packages/core/src/config/vendure-config.ts" sourceLine="1054" packageName="@vendure/core" since="1.6.0" />
 
 Options relating to system functions.
 
@@ -19,6 +19,7 @@ Options relating to system functions.
 interface SystemOptions {
     healthChecks?: HealthCheckStrategy[];
     errorHandlers?: ErrorHandlerStrategy[];
+    cacheStrategy?: CacheStrategy;
 }
 ```
 
@@ -36,6 +37,12 @@ that any critical systems which the Vendure server depends on are also healthy.
 
 Defines an array of <a href='/reference/typescript-api/errors/error-handler-strategy#errorhandlerstrategy'>ErrorHandlerStrategy</a> instances which are used to define logic to be executed
 when an error occurs, either on the server or the worker.
+### cacheStrategy
+
+<MemberInfo kind="property" type={`<a href='/reference/typescript-api/cache/cache-strategy#cachestrategy'>CacheStrategy</a>`} default={`InMemoryCacheStrategy`}  since="3.1.0"  />
+
+Defines the underlying method used to store cache key-value pairs which powers the
+<a href='/reference/typescript-api/cache/cache-service#cacheservice'>CacheService</a>.
 
 
 </div>

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

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

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

@@ -11,7 +11,7 @@ import MemberDescription from '@site/src/components/MemberDescription';
 
 ## CustomFieldConfig
 
-<GenerationInfo sourceFile="packages/core/src/config/custom-field/custom-field-types.ts" sourceLine="124" packageName="@vendure/core" />
+<GenerationInfo sourceFile="packages/core/src/config/custom-field/custom-field-types.ts" sourceLine="228" packageName="@vendure/core" />
 
 An object used to configure a custom field.
 
@@ -25,4 +25,5 @@ type CustomFieldConfig = | StringCustomFieldConfig
     | BooleanCustomFieldConfig
     | DateTimeCustomFieldConfig
     | RelationCustomFieldConfig
+    | StructCustomFieldConfig
 ```

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

@@ -11,7 +11,7 @@ import MemberDescription from '@site/src/components/MemberDescription';
 
 ## CustomFieldType
 
-<GenerationInfo sourceFile="packages/common/src/shared-types.ts" sourceLine="103" packageName="@vendure/common" />
+<GenerationInfo sourceFile="packages/common/src/shared-types.ts" sourceLine="104" packageName="@vendure/common" />
 
 A data type for a custom field. The CustomFieldType determines the data types used in the generated
 database columns and GraphQL fields as follows (key: m = MySQL, p = Postgres, s = SQLite):
@@ -21,11 +21,12 @@ Type         | DB type                               | GraphQL type
 string       | varchar                               | String
 localeString | varchar                               | String
 text         | longtext(m), text(p,s)                | String
-localeText    | longtext(m), text(p,s)                | String
+localeText   | longtext(m), text(p,s)                | String
 int          | int                                   | Int
 float        | double precision                      | Float
 boolean      | tinyint (m), bool (p), boolean (s)    | Boolean
 datetime     | datetime (m,s), timestamp (p)         | DateTime
+struct       | json (m), jsonb (p), text (s)         | JSON
 relation     | many-to-one / many-to-many relation   | As specified in config
 
 Additionally, the CustomFieldType also dictates which [configuration options](/reference/typescript-api/custom-fields/#custom-field-config-properties)
@@ -41,4 +42,5 @@ type CustomFieldType = | 'string'
     | 'relation'
     | 'text'
     | 'localeText'
+    | 'struct'
 ```

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

@@ -11,7 +11,7 @@ import MemberDescription from '@site/src/components/MemberDescription';
 
 ## CustomFields
 
-<GenerationInfo sourceFile="packages/core/src/config/custom-field/custom-field-types.ts" sourceLine="159" packageName="@vendure/core" />
+<GenerationInfo sourceFile="packages/core/src/config/custom-field/custom-field-types.ts" sourceLine="264" packageName="@vendure/core" />
 
 Most entities can have additional fields added to them by defining an array of <a href='/reference/typescript-api/custom-fields/custom-field-config#customfieldconfig'>CustomFieldConfig</a>
 objects on against the corresponding key.
@@ -47,8 +47,10 @@ type CustomFields = {
     FacetValue?: CustomFieldConfig[];
     Fulfillment?: CustomFieldConfig[];
     GlobalSettings?: CustomFieldConfig[];
+    HistoryEntry?: CustomFieldConfig[];
     Order?: CustomFieldConfig[];
     OrderLine?: CustomFieldConfig[];
+    Payment?: CustomFieldConfig[];
     PaymentMethod?: CustomFieldConfig[];
     Product?: CustomFieldConfig[];
     ProductOption?: CustomFieldConfig[];
@@ -56,10 +58,15 @@ type CustomFields = {
     ProductVariant?: CustomFieldConfig[];
     ProductVariantPrice?: CustomFieldConfig[];
     Promotion?: CustomFieldConfig[];
+    Refund?: CustomFieldConfig[];
     Region?: CustomFieldConfig[];
     Seller?: CustomFieldConfig[];
+    Session?: CustomFieldConfig[];
+    ShippingLine?: CustomFieldConfig[];
     ShippingMethod?: CustomFieldConfig[];
+    StockLevel?: CustomFieldConfig[];
     StockLocation?: CustomFieldConfig[];
+    StockMovement?: CustomFieldConfig[];
     TaxCategory?: CustomFieldConfig[];
     TaxRate?: CustomFieldConfig[];
     User?: CustomFieldConfig[];

+ 25 - 0
docs/docs/reference/typescript-api/custom-fields/struct-custom-field-config.md

@@ -0,0 +1,25 @@
+---
+title: "StructCustomFieldConfig"
+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';
+
+
+## StructCustomFieldConfig
+
+<GenerationInfo sourceFile="packages/core/src/config/custom-field/custom-field-types.ts" sourceLine="215" packageName="@vendure/core" since="3.1.0" />
+
+Configures a "struct" custom field.
+
+```ts title="Signature"
+type StructCustomFieldConfig = TypedCustomFieldConfig<
+    'struct',
+    Omit<GraphQLStructCustomFieldConfig, 'fields'>
+> & {
+    fields: StructFieldConfig[];
+}
+```

+ 45 - 0
docs/docs/reference/typescript-api/custom-fields/struct-field-config.md

@@ -0,0 +1,45 @@
+---
+title: "StructFieldConfig"
+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';
+
+
+## StructFieldConfig
+
+<GenerationInfo sourceFile="packages/core/src/config/custom-field/custom-field-types.ts" sourceLine="200" packageName="@vendure/core" since="3.1.0" />
+
+Configures an individual field of a "struct" custom field. The individual fields share
+the same API as the top-level custom fields, with the exception that they do not support the
+`readonly`, `internal`, `nullable`, `unique` and `requiresPermission` options.
+
+*Example*
+
+```ts
+const customFields: CustomFields = {
+  Product: [
+    {
+      name: 'specifications',
+      type: 'struct',
+      fields: [
+        { name: 'processor', type: 'string' },
+        { name: 'ram', type: 'string' },
+        { name: 'screenSize', type: 'float' },
+      ],
+    },
+  ],
+};
+```
+
+```ts title="Signature"
+type StructFieldConfig = | StringStructFieldConfig
+    | TextStructFieldConfig
+    | IntStructFieldConfig
+    | FloatStructFieldConfig
+    | BooleanStructFieldConfig
+    | DateTimeStructFieldConfig
+```

+ 1 - 1
docs/docs/reference/typescript-api/custom-fields/typed-custom-single-field-config.md

@@ -11,7 +11,7 @@ import MemberDescription from '@site/src/components/MemberDescription';
 
 ## TypedCustomSingleFieldConfig
 
-<GenerationInfo sourceFile="packages/core/src/config/custom-field/custom-field-types.ts" sourceLine="66" packageName="@vendure/core" />
+<GenerationInfo sourceFile="packages/core/src/config/custom-field/custom-field-types.ts" sourceLine="75" packageName="@vendure/core" />
 
 Configures a custom field on an entity in the <a href='/reference/typescript-api/custom-fields/#customfields'>CustomFields</a> config object.
 

+ 54 - 45
docs/docs/reference/typescript-api/data-access/transactional-connection.md

@@ -11,14 +11,14 @@ import MemberDescription from '@site/src/components/MemberDescription';
 
 ## TransactionalConnection
 
-<GenerationInfo sourceFile="packages/core/src/connection/transactional-connection.ts" sourceLine="40" packageName="@vendure/core" />
-
-The TransactionalConnection is a wrapper around the TypeORM `Connection` object which works in conjunction
-with the <a href='/reference/typescript-api/request/transaction-decorator#transaction'>Transaction</a> decorator to implement per-request transactions. All services which access the
-database should use this class rather than the raw TypeORM connection, to ensure that db changes can be
-easily wrapped in transactions when required.
-
-The service layer does not need to know about the scope of a transaction, as this is covered at the
+<GenerationInfo sourceFile="packages/core/src/connection/transactional-connection.ts" sourceLine="41" packageName="@vendure/core" />
+
+The TransactionalConnection is a wrapper around the TypeORM `Connection` object which works in conjunction
+with the <a href='/reference/typescript-api/request/transaction-decorator#transaction'>Transaction</a> decorator to implement per-request transactions. All services which access the
+database should use this class rather than the raw TypeORM connection, to ensure that db changes can be
+easily wrapped in transactions when required.
+
+The service layer does not need to know about the scope of a transaction, as this is covered at the
 API by the use of the `Transaction` decorator.
 
 ```ts title="Signature"
@@ -26,8 +26,12 @@ class TransactionalConnection {
     constructor(dataSource: DataSource, transactionWrapper: TransactionWrapper)
     rawConnection: DataSource
     getRepository(target: ObjectType<Entity> | EntitySchema<Entity> | string) => Repository<Entity>;
-    getRepository(ctx: RequestContext | undefined, target: ObjectType<Entity> | EntitySchema<Entity> | string) => Repository<Entity>;
-    getRepository(ctxOrTarget: RequestContext | ObjectType<Entity> | EntitySchema<Entity> | string | undefined, maybeTarget?: ObjectType<Entity> | EntitySchema<Entity> | string) => Repository<Entity>;
+    getRepository(ctx: RequestContext | undefined, target: ObjectType<Entity> | EntitySchema<Entity> | string, options?: {
+            replicationMode?: ReplicationMode;
+        }) => Repository<Entity>;
+    getRepository(ctxOrTarget: RequestContext | ObjectType<Entity> | EntitySchema<Entity> | string | undefined, maybeTarget?: ObjectType<Entity> | EntitySchema<Entity> | string, options?: {
+            replicationMode?: ReplicationMode;
+        }) => Repository<Entity>;
     withTransaction(work: (ctx: RequestContext) => Promise<T>) => Promise<T>;
     withTransaction(ctx: RequestContext, work: (ctx: RequestContext) => Promise<T>) => Promise<T>;
     withTransaction(ctxOrWork: RequestContext | ((ctx: RequestContext) => Promise<T>), maybeWork?: (ctx: RequestContext) => Promise<T>) => Promise<T>;
@@ -51,48 +55,53 @@ class TransactionalConnection {
 
 <MemberInfo kind="property" type={`DataSource`}   />
 
-The plain TypeORM Connection object. Should be used carefully as any operations
-performed with this connection will not be performed within any outer
+The plain TypeORM Connection object. Should be used carefully as any operations
+performed with this connection will not be performed within any outer
 transactions.
 ### getRepository
 
 <MemberInfo kind="method" type={`(target: ObjectType&#60;Entity&#62; | EntitySchema&#60;Entity&#62; | string) => Repository&#60;Entity&#62;`}   />
 
-Returns a TypeORM repository. Note that when no RequestContext is supplied, the repository will not
-be aware of any existing transaction. Therefore, calling this method without supplying a RequestContext
+Returns a TypeORM repository. Note that when no RequestContext is supplied, the repository will not
+be aware of any existing transaction. Therefore, calling this method without supplying a RequestContext
 is discouraged without a deliberate reason.
 ### getRepository
 
-<MemberInfo kind="method" type={`(ctx: <a href='/reference/typescript-api/request/request-context#requestcontext'>RequestContext</a> | undefined, target: ObjectType&#60;Entity&#62; | EntitySchema&#60;Entity&#62; | string) => Repository&#60;Entity&#62;`}   />
+<MemberInfo kind="method" type={`(ctx: <a href='/reference/typescript-api/request/request-context#requestcontext'>RequestContext</a> | undefined, target: ObjectType&#60;Entity&#62; | EntitySchema&#60;Entity&#62; | string, options?: {
             replicationMode?: ReplicationMode;
         }) => Repository&#60;Entity&#62;`}   />
 
-Returns a TypeORM repository which is bound to any existing transactions. It is recommended to _always_ pass
-the RequestContext argument when possible, otherwise the queries will be executed outside of any
-ongoing transactions which have been started by the <a href='/reference/typescript-api/request/transaction-decorator#transaction'>Transaction</a> decorator.
+Returns a TypeORM repository which is bound to any existing transactions. It is recommended to _always_ pass
+the RequestContext argument when possible, otherwise the queries will be executed outside of any
+ongoing transactions which have been started by the <a href='/reference/typescript-api/request/transaction-decorator#transaction'>Transaction</a> decorator.
+
+The `options` parameter allows specifying additional configurations, such as the `replicationMode`,
+which determines whether the repository should interact with the master or replica database.
 ### getRepository
 
-<MemberInfo kind="method" type={`(ctxOrTarget: <a href='/reference/typescript-api/request/request-context#requestcontext'>RequestContext</a> | ObjectType&#60;Entity&#62; | EntitySchema&#60;Entity&#62; | string | undefined, maybeTarget?: ObjectType&#60;Entity&#62; | EntitySchema&#60;Entity&#62; | string) => Repository&#60;Entity&#62;`}   />
-
+<MemberInfo kind="method" type={`(ctxOrTarget: <a href='/reference/typescript-api/request/request-context#requestcontext'>RequestContext</a> | ObjectType&#60;Entity&#62; | EntitySchema&#60;Entity&#62; | string | undefined, maybeTarget?: ObjectType&#60;Entity&#62; | EntitySchema&#60;Entity&#62; | string, options?: {
             replicationMode?: ReplicationMode;
         }) => Repository&#60;Entity&#62;`}   />
 
+Returns a TypeORM repository. Depending on the parameters passed, it will either be transaction-aware
+or not. If `RequestContext` is provided, the repository is bound to any ongoing transactions. The
+`options` parameter allows further customization, such as selecting the replication mode (e.g., 'master').
 ### withTransaction
 
 <MemberInfo kind="method" type={`(work: (ctx: <a href='/reference/typescript-api/request/request-context#requestcontext'>RequestContext</a>) =&#62; Promise&#60;T&#62;) => Promise&#60;T&#62;`}  since="1.3.0"  />
 
-Allows database operations to be wrapped in a transaction, ensuring that in the event of an error being
-thrown at any point, the entire transaction will be rolled back and no changes will be saved.
-
-In the context of API requests, you should instead use the <a href='/reference/typescript-api/request/transaction-decorator#transaction'>Transaction</a> decorator on your resolver or
-controller method.
-
-On the other hand, for code that does not run in the context of a GraphQL/REST request, this method
-should be used to protect against non-atomic changes to the data which could leave your data in an
-inconsistent state.
-
-Such situations include function processed by the JobQueue or stand-alone scripts which make use
-of Vendure internal services.
-
-If there is already a <a href='/reference/typescript-api/request/request-context#requestcontext'>RequestContext</a> object available, you should pass it in as the first
-argument in order to create transactional context as the copy. If not, omit the first argument and an empty
-RequestContext object will be created, which is then used to propagate the transaction to
+Allows database operations to be wrapped in a transaction, ensuring that in the event of an error being
+thrown at any point, the entire transaction will be rolled back and no changes will be saved.
+
+In the context of API requests, you should instead use the <a href='/reference/typescript-api/request/transaction-decorator#transaction'>Transaction</a> decorator on your resolver or
+controller method.
+
+On the other hand, for code that does not run in the context of a GraphQL/REST request, this method
+should be used to protect against non-atomic changes to the data which could leave your data in an
+inconsistent state.
+
+Such situations include function processed by the JobQueue or stand-alone scripts which make use
+of Vendure internal services.
+
+If there is already a <a href='/reference/typescript-api/request/request-context#requestcontext'>RequestContext</a> object available, you should pass it in as the first
+argument in order to create transactional context as the copy. If not, omit the first argument and an empty
+RequestContext object will be created, which is then used to propagate the transaction to
 all inner method calls.
 
 *Example*
@@ -128,40 +137,40 @@ private async transferCredit(outerCtx: RequestContext, fromId: ID, toId: ID, amo
 
 <MemberInfo kind="method" type={`(ctx: <a href='/reference/typescript-api/request/request-context#requestcontext'>RequestContext</a>, isolationLevel?: <a href='/reference/typescript-api/request/transaction-decorator#transactionisolationlevel'>TransactionIsolationLevel</a>) => `}   />
 
-Manually start a transaction if one is not already in progress. This method should be used in
+Manually start a transaction if one is not already in progress. This method should be used in
 conjunction with the `'manual'` mode of the <a href='/reference/typescript-api/request/transaction-decorator#transaction'>Transaction</a> decorator.
 ### commitOpenTransaction
 
 <MemberInfo kind="method" type={`(ctx: <a href='/reference/typescript-api/request/request-context#requestcontext'>RequestContext</a>) => `}   />
 
-Manually commits any open transaction. Should be very rarely needed, since the <a href='/reference/typescript-api/request/transaction-decorator#transaction'>Transaction</a> decorator
-and the internal TransactionInterceptor take care of this automatically. Use-cases include situations
-in which the worker thread needs to access changes made in the current transaction, or when using the
+Manually commits any open transaction. Should be very rarely needed, since the <a href='/reference/typescript-api/request/transaction-decorator#transaction'>Transaction</a> decorator
+and the internal TransactionInterceptor take care of this automatically. Use-cases include situations
+in which the worker thread needs to access changes made in the current transaction, or when using the
 Transaction decorator in manual mode.
 ### rollBackTransaction
 
 <MemberInfo kind="method" type={`(ctx: <a href='/reference/typescript-api/request/request-context#requestcontext'>RequestContext</a>) => `}   />
 
-Manually rolls back any open transaction. Should be very rarely needed, since the <a href='/reference/typescript-api/request/transaction-decorator#transaction'>Transaction</a> decorator
-and the internal TransactionInterceptor take care of this automatically. Use-cases include when using the
+Manually rolls back any open transaction. Should be very rarely needed, since the <a href='/reference/typescript-api/request/transaction-decorator#transaction'>Transaction</a> decorator
+and the internal TransactionInterceptor take care of this automatically. Use-cases include when using the
 Transaction decorator in manual mode.
 ### getEntityOrThrow
 
 <MemberInfo kind="method" type={`(ctx: <a href='/reference/typescript-api/request/request-context#requestcontext'>RequestContext</a>, entityType: Type&#60;T&#62;, id: <a href='/reference/typescript-api/common/id#id'>ID</a>, options: <a href='/reference/typescript-api/data-access/get-entity-or-throw-options#getentityorthrowoptions'>GetEntityOrThrowOptions</a>&#60;T&#62; = {}) => Promise&#60;T&#62;`}   />
 
-Finds an entity of the given type by ID, or throws an `EntityNotFoundError` if none
+Finds an entity of the given type by ID, or throws an `EntityNotFoundError` if none
 is found.
 ### findOneInChannel
 
 <MemberInfo kind="method" type={`(ctx: <a href='/reference/typescript-api/request/request-context#requestcontext'>RequestContext</a>, entity: Type&#60;T&#62;, id: <a href='/reference/typescript-api/common/id#id'>ID</a>, channelId: <a href='/reference/typescript-api/common/id#id'>ID</a>, options: FindOneOptions&#60;T&#62; = {}) => `}   />
 
-Like the TypeOrm `Repository.findOne()` method, but limits the results to
+Like the TypeOrm `Repository.findOne()` method, but limits the results to
 the given Channel.
 ### findByIdsInChannel
 
 <MemberInfo kind="method" type={`(ctx: <a href='/reference/typescript-api/request/request-context#requestcontext'>RequestContext</a>, entity: Type&#60;T&#62;, ids: <a href='/reference/typescript-api/common/id#id'>ID</a>[], channelId: <a href='/reference/typescript-api/common/id#id'>ID</a>, options: FindManyOptions&#60;T&#62;) => `}   />
 
-Like the TypeOrm `Repository.findByIds()` method, but limits the results to
+Like the TypeOrm `Repository.findByIds()` method, but limits the results to
 the given Channel.
 
 

+ 1 - 1
docs/docs/reference/typescript-api/default-search-plugin/index.md

@@ -11,7 +11,7 @@ import MemberDescription from '@site/src/components/MemberDescription';
 
 ## DefaultSearchPlugin
 
-<GenerationInfo sourceFile="packages/core/src/plugin/default-search-plugin/default-search-plugin.ts" sourceLine="69" packageName="@vendure/core" />
+<GenerationInfo sourceFile="packages/core/src/plugin/default-search-plugin/default-search-plugin.ts" sourceLine="70" packageName="@vendure/core" />
 
 The DefaultSearchPlugin provides a full-text Product search based on the full-text searching capabilities of the
 underlying database.

+ 12 - 2
docs/docs/reference/typescript-api/entities/history-entry.md

@@ -11,13 +11,13 @@ import MemberDescription from '@site/src/components/MemberDescription';
 
 ## HistoryEntry
 
-<GenerationInfo sourceFile="packages/core/src/entity/history-entry/history-entry.entity.ts" sourceLine="14" packageName="@vendure/core" />
+<GenerationInfo sourceFile="packages/core/src/entity/history-entry/history-entry.entity.ts" sourceLine="16" packageName="@vendure/core" />
 
 An abstract entity representing an entry in the history of an Order (<a href='/reference/typescript-api/entities/order-history-entry#orderhistoryentry'>OrderHistoryEntry</a>)
 or a Customer (<a href='/reference/typescript-api/entities/customer-history-entry#customerhistoryentry'>CustomerHistoryEntry</a>).
 
 ```ts title="Signature"
-class HistoryEntry extends VendureEntity {
+class HistoryEntry extends VendureEntity implements HasCustomFields {
     @Index()
     @ManyToOne(type => Administrator)
     administrator?: Administrator;
@@ -27,11 +27,16 @@ class HistoryEntry extends VendureEntity {
     isPublic: boolean;
     @Column('simple-json')
     data: any;
+    @Column(type => CustomHistoryEntryFields)
+    customFields: CustomHistoryEntryFields;
 }
 ```
 * Extends: <code><a href='/reference/typescript-api/entities/vendure-entity#vendureentity'>VendureEntity</a></code>
 
 
+* Implements: <code>HasCustomFields</code>
+
+
 
 <div className="members-wrapper">
 
@@ -55,6 +60,11 @@ class HistoryEntry extends VendureEntity {
 <MemberInfo kind="property" type={`any`}   />
 
 
+### customFields
+
+<MemberInfo kind="property" type={`CustomHistoryEntryFields`}   />
+
+
 
 
 </div>

+ 7 - 0
docs/docs/reference/typescript-api/entities/order-line.md

@@ -40,6 +40,8 @@ class OrderLine extends VendureEntity implements HasCustomFields {
     @Index()
     @ManyToOne(type => TaxCategory)
     taxCategory: TaxCategory;
+    @EntityId({ nullable: true })
+    taxCategoryId: ID;
     @Index()
     @ManyToOne(type => Asset, asset => asset.featuredInVariants, { onDelete: 'SET NULL' })
     featuredAsset: Asset;
@@ -144,6 +146,11 @@ The <a href='/reference/typescript-api/entities/product-variant#productvariant'>
 <MemberInfo kind="property" type={`<a href='/reference/typescript-api/entities/tax-category#taxcategory'>TaxCategory</a>`}   />
 
 
+### taxCategoryId
+
+<MemberInfo kind="property" type={`<a href='/reference/typescript-api/common/id#id'>ID</a>`}   />
+
+
 ### featuredAsset
 
 <MemberInfo kind="property" type={`<a href='/reference/typescript-api/entities/asset#asset'>Asset</a>`}   />

+ 12 - 2
docs/docs/reference/typescript-api/entities/payment.md

@@ -11,13 +11,13 @@ import MemberDescription from '@site/src/components/MemberDescription';
 
 ## Payment
 
-<GenerationInfo sourceFile="packages/core/src/entity/payment/payment.entity.ts" sourceLine="18" packageName="@vendure/core" />
+<GenerationInfo sourceFile="packages/core/src/entity/payment/payment.entity.ts" sourceLine="20" packageName="@vendure/core" />
 
 A Payment represents a single payment transaction and exists in a well-defined state
 defined by the <a href='/reference/typescript-api/payment/payment-state#paymentstate'>PaymentState</a> type.
 
 ```ts title="Signature"
-class Payment extends VendureEntity {
+class Payment extends VendureEntity implements HasCustomFields {
     constructor(input?: DeepPartial<Payment>)
     @Column() method: string;
     @Money() amount: number;
@@ -32,11 +32,16 @@ class Payment extends VendureEntity {
     order: Order;
     @OneToMany(type => Refund, refund => refund.payment)
     refunds: Refund[];
+    @Column(type => CustomPaymentFields)
+    customFields: CustomPaymentFields;
 }
 ```
 * Extends: <code><a href='/reference/typescript-api/entities/vendure-entity#vendureentity'>VendureEntity</a></code>
 
 
+* Implements: <code>HasCustomFields</code>
+
+
 
 <div className="members-wrapper">
 
@@ -85,6 +90,11 @@ class Payment extends VendureEntity {
 <MemberInfo kind="property" type={`<a href='/reference/typescript-api/entities/refund#refund'>Refund</a>[]`}   />
 
 
+### customFields
+
+<MemberInfo kind="property" type={`CustomPaymentFields`}   />
+
+
 
 
 </div>

+ 14 - 0
docs/docs/reference/typescript-api/entities/product-variant.md

@@ -37,6 +37,8 @@ class ProductVariant extends VendureEntity implements Translatable, HasCustomFie
     @Index()
     @ManyToOne(type => Asset, asset => asset.featuredInVariants, { onDelete: 'SET NULL' })
     featuredAsset: Asset;
+    @EntityId({ nullable: true })
+    featuredAssetId: ID;
     @OneToMany(type => ProductVariantAsset, productVariantAsset => productVariantAsset.productVariant, {
         onDelete: 'SET NULL',
     })
@@ -44,6 +46,8 @@ class ProductVariant extends VendureEntity implements Translatable, HasCustomFie
     @Index()
     @ManyToOne(type => TaxCategory, taxCategory => taxCategory.productVariants)
     taxCategory: TaxCategory;
+    @EntityId({ nullable: true })
+    taxCategoryId: ID;
     @OneToMany(type => ProductVariantPrice, price => price.variant, { eager: true })
     productVariantPrices: ProductVariantPrice[];
     @OneToMany(type => ProductVariantTranslation, translation => translation.base, { eager: true })
@@ -149,6 +153,11 @@ class ProductVariant extends VendureEntity implements Translatable, HasCustomFie
 <MemberInfo kind="property" type={`<a href='/reference/typescript-api/entities/asset#asset'>Asset</a>`}   />
 
 
+### featuredAssetId
+
+<MemberInfo kind="property" type={`<a href='/reference/typescript-api/common/id#id'>ID</a>`}   />
+
+
 ### assets
 
 <MemberInfo kind="property" type={`ProductVariantAsset[]`}   />
@@ -159,6 +168,11 @@ class ProductVariant extends VendureEntity implements Translatable, HasCustomFie
 <MemberInfo kind="property" type={`<a href='/reference/typescript-api/entities/tax-category#taxcategory'>TaxCategory</a>`}   />
 
 
+### taxCategoryId
+
+<MemberInfo kind="property" type={`<a href='/reference/typescript-api/common/id#id'>ID</a>`}   />
+
+
 ### productVariantPrices
 
 <MemberInfo kind="property" type={`<a href='/reference/typescript-api/entities/product-variant-price#productvariantprice'>ProductVariantPrice</a>[]`}   />

Some files were not shown because too many files changed in this diff