From c46cf74bab23d63ef281bf30da44fc8e6e7ac8a4 Mon Sep 17 00:00:00 2001 From: Florian Wild <362217+floze@users.noreply.github.com> Date: Fri, 1 Dec 2023 10:16:46 +0100 Subject: [PATCH 01/22] fix(core): Export VendureEntityEvent abstract class from index (#2556) --- packages/core/src/event-bus/index.ts | 1 + 1 file changed, 1 insertion(+) diff --git a/packages/core/src/event-bus/index.ts b/packages/core/src/event-bus/index.ts index 52455a6128..f7239df7de 100644 --- a/packages/core/src/event-bus/index.ts +++ b/packages/core/src/event-bus/index.ts @@ -1,6 +1,7 @@ export * from './event-bus'; export * from './event-bus.module'; export * from './vendure-event'; +export * from './vendure-entity-event'; export * from './events/account-registration-event'; export * from './events/account-verified-event'; From 6dfb618a837219fca95958ee40f8e2585140e3d6 Mon Sep 17 00:00:00 2001 From: Michael Bromley Date: Wed, 29 Nov 2023 15:02:44 +0100 Subject: [PATCH 02/22] docs: Add ids to list view template example --- .../creating-list-views/index.md | 10 ++++++---- 1 file changed, 6 insertions(+), 4 deletions(-) diff --git a/docs/docs/guides/extending-the-admin-ui/creating-list-views/index.md b/docs/docs/guides/extending-the-admin-ui/creating-list-views/index.md index 32226b43a5..d4138134a1 100644 --- a/docs/docs/guides/extending-the-admin-ui/creating-list-views/index.md +++ b/docs/docs/guides/extending-the-admin-ui/creating-list-views/index.md @@ -185,12 +185,13 @@ This is the standard layout for any list view. The main functionality is provide /> - + {{ review.id }} - + {{ review.title }} @@ -216,10 +218,10 @@ This is the standard layout for any list view. The main functionality is provide - + - + {{ review.authorName }} From 0763432e3e1f2544639e987b4c41cfb9bea3147f Mon Sep 17 00:00:00 2001 From: Michael Bromley Date: Fri, 1 Dec 2023 09:27:24 +0100 Subject: [PATCH 03/22] docs: Add docs isGraphQlErrorResult function --- .../developer-guide/error-handling/index.mdx | 27 +++++++++++++++++++ .../core/src/common/error/error-result.ts | 24 ++++++++++++++++- 2 files changed, 50 insertions(+), 1 deletion(-) diff --git a/docs/docs/guides/developer-guide/error-handling/index.mdx b/docs/docs/guides/developer-guide/error-handling/index.mdx index 34e3434b25..2e23b73f00 100644 --- a/docs/docs/guides/developer-guide/error-handling/index.mdx +++ b/docs/docs/guides/developer-guide/error-handling/index.mdx @@ -231,6 +231,33 @@ Here's how a response would look in both the success and error result cases: +### Handling ErrorResults in plugin code + +If you are writing a plugin which deals with internal Vendure service methods that may return ErrorResults, +then you can use the `isGraphQlErrorResult()` function to check whether the result is an ErrorResult: + +```ts +import { Injectable} from '@nestjs/common'; +import { isGraphQlErrorResult, Order, OrderService, OrderState, RequestContext } from '@vendure/core'; + +@Injectable() +export class MyService { + + constructor(private orderService: OrderService) {} + + async myMethod(ctx: RequestContext, order: Order, newState: OrderState) { + const transitionResult = await this.orderService.transitionToState(ctx, order.id, newState); + if (isGraphQlErrorResult(transitionResult)) { + // The transition failed with an ErrorResult + throw transitionResult; + } else { + // TypeScript will correctly infer the type of `transitionResult` to be `Order` + return transitionResult; + } + } +} +``` + ### Handling ErrorResults in client code Because we know all possible ErrorResult that may occur for a given mutation, we can handle them in an exhaustive manner. In other diff --git a/packages/core/src/common/error/error-result.ts b/packages/core/src/common/error/error-result.ts index b15b1b05bb..5953c7b47a 100644 --- a/packages/core/src/common/error/error-result.ts +++ b/packages/core/src/common/error/error-result.ts @@ -37,6 +37,9 @@ export type JustErrorResults = Exclud * type UpdateOrderItemsResult = Order | OrderModificationError | OrderLimitError | NegativeQuantityError; * type T1 = ErrorResultUnion; * // T1 = VendureEntityOrder | OrderModificationError | OrderLimitError | NegativeQuantityError; + * ``` + * + * @docsCategory errors */ export type ErrorResultUnion = | JustErrorResults @@ -44,7 +47,26 @@ export type ErrorResultUnion( input: T, From 8e9ee070d0d3918f2029a0d37c08ed90c4785764 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Giosu=C3=A8=20Delgado?= <11545723+giosueDelgado@users.noreply.github.com> Date: Mon, 4 Dec 2023 10:22:40 +0100 Subject: [PATCH 04/22] fix(elasticsearch-plugin): Fix type to allow the promise on custom mapping definition (#2562) --- packages/elasticsearch-plugin/src/types.ts | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/packages/elasticsearch-plugin/src/types.ts b/packages/elasticsearch-plugin/src/types.ts index 85a8c387e4..25d5571ee7 100644 --- a/packages/elasticsearch-plugin/src/types.ts +++ b/packages/elasticsearch-plugin/src/types.ts @@ -271,7 +271,7 @@ type GraphQlPermittedReturnType = PrimitiveTypeVariations; type CustomMappingDefinition = { graphQlType: T; public?: boolean; - valueFn: (...args: Args) => R; + valueFn: (...args: Args) => Promise | R; }; type TypeVariationMap = { From c059bc00146347413936ad4dacca06b086d3c493 Mon Sep 17 00:00:00 2001 From: Lacey Pevey <7490308+pevey@users.noreply.github.com> Date: Mon, 4 Dec 2023 03:25:10 -0600 Subject: [PATCH 05/22] docs: Change plugin version info for bullmq and fix typos on writing a plugin page (#2554) --- docs/docs/guides/developer-guide/plugins/index.mdx | 5 ++--- .../job-queue-plugin/bull-mqjob-queue-plugin.md | 6 +++--- packages/job-queue-plugin/src/bullmq/plugin.ts | 6 +++--- 3 files changed, 8 insertions(+), 9 deletions(-) diff --git a/docs/docs/guides/developer-guide/plugins/index.mdx b/docs/docs/guides/developer-guide/plugins/index.mdx index 3d74a45738..d50a50ea65 100644 --- a/docs/docs/guides/developer-guide/plugins/index.mdx +++ b/docs/docs/guides/developer-guide/plugins/index.mdx @@ -138,7 +138,7 @@ export class MyPlugin implements NestModule { In Vendure **plugins** are used to extend the core functionality of the server. Plugins can be pre-made functionality that you can install via npm, or they can be custom plugins that you write yourself. -For any unit of functionality that you need to add to your project, you'll be writing creating a Vendure plugin. By convention, plugins are stored in the `plugins` directory of your project. However, this is not a requirement, and you are free to arrange your plugin files in any way you like. +For any unit of functionality that you need to add to your project, you'll be creating a Vendure plugin. By convention, plugins are stored in the `plugins` directory of your project. However, this is not a requirement, and you are free to arrange your plugin files in any way you like. ```txt ├──src @@ -281,7 +281,6 @@ In order to make use of this custom field in a type-safe way, we can tell TypeSc ``` ```ts title="src/plugins/wishlist-plugin/types.ts" -import { CustomCustomerFields } from '@vendure/core/dist/entity/custom-entity-fields'; import { WishlistItem } from './entities/wishlist-item.entity'; declare module '@vendure/core/dist/entity/custom-entity-fields' { @@ -312,7 +311,7 @@ Let's create a service to handle the wishlist functionality: ├── wishlist.service.ts ``` -```ts title="src/plugins/wishlist-plugin/wishlist.service.ts" +```ts title="src/plugins/wishlist-plugin/services/wishlist.service.ts" import { Injectable } from '@nestjs/common'; import { Customer, diff --git a/docs/docs/reference/core-plugins/job-queue-plugin/bull-mqjob-queue-plugin.md b/docs/docs/reference/core-plugins/job-queue-plugin/bull-mqjob-queue-plugin.md index 4929843471..cb1ab57c83 100644 --- a/docs/docs/reference/core-plugins/job-queue-plugin/bull-mqjob-queue-plugin.md +++ b/docs/docs/reference/core-plugins/job-queue-plugin/bull-mqjob-queue-plugin.md @@ -32,13 +32,13 @@ in processing jobs. ## Installation -`yarn add @vendure/job-queue-plugin bullmq@1` +`yarn add @vendure/job-queue-plugin bullmq` or -`npm install @vendure/job-queue-plugin bullmq@1` +`npm install @vendure/job-queue-plugin bullmq` -**Note:** The v1.x version of this plugin is designed to work with bullmq v1.x. +**Note:** The v1.x version of this plugin is designed to work with bullmq v1.x, etc. *Example* diff --git a/packages/job-queue-plugin/src/bullmq/plugin.ts b/packages/job-queue-plugin/src/bullmq/plugin.ts index c82d533af9..12f2839895 100644 --- a/packages/job-queue-plugin/src/bullmq/plugin.ts +++ b/packages/job-queue-plugin/src/bullmq/plugin.ts @@ -28,13 +28,13 @@ import { BullMQPluginOptions } from './types'; * * ## Installation * - * `yarn add \@vendure/job-queue-plugin bullmq@1` + * `yarn add \@vendure/job-queue-plugin bullmq` * * or * - * `npm install \@vendure/job-queue-plugin bullmq@1` + * `npm install \@vendure/job-queue-plugin bullmq` * - * **Note:** The v1.x version of this plugin is designed to work with bullmq v1.x. + * **Note:** The v1.x version of this plugin is designed to work with bullmq v1.x, etc. * * @example * ```ts From b5a265f93dcb65ddf30b2d3f62beb961f667d539 Mon Sep 17 00:00:00 2001 From: Alexis Vigoureux Date: Mon, 4 Dec 2023 10:27:32 +0100 Subject: [PATCH 06/22] fix(core): Send the correct amount to `refundOrder` (#2559) --- packages/core/src/config/order/default-order-process.ts | 7 +++++-- 1 file changed, 5 insertions(+), 2 deletions(-) diff --git a/packages/core/src/config/order/default-order-process.ts b/packages/core/src/config/order/default-order-process.ts index 2c78c91580..d1592c0d3f 100644 --- a/packages/core/src/config/order/default-order-process.ts +++ b/packages/core/src/config/order/default-order-process.ts @@ -413,10 +413,13 @@ export function configureDefaultOrderProcess(options: DefaultOrderProcessOptions order.active = false; order.orderPlacedAt = new Date(); await Promise.all( - order.lines.map(line => + order.lines.map(line => { + line.orderPlacedQuantity = line.quantity connection .getRepository(ctx, OrderLine) - .update(line.id, { orderPlacedQuantity: line.quantity }), + .update(line.id, { orderPlacedQuantity: line.quantity }) + return line + } ), ); eventBus.publish(new OrderPlacedEvent(fromState, toState, ctx, order)); From 1b58aa7cfa977b58f65b52ef7cbebbd4fbef224b Mon Sep 17 00:00:00 2001 From: Michael Bromley Date: Mon, 4 Dec 2023 14:09:52 +0100 Subject: [PATCH 07/22] fix(admin-ui): Fix display of asset detail focal point buttons --- .../asset-preview.component.html | 70 ++++++++++--------- .../asset-preview.component.scss | 2 +- 2 files changed, 37 insertions(+), 35 deletions(-) diff --git a/packages/admin-ui/src/lib/core/src/shared/components/asset-preview/asset-preview.component.html b/packages/admin-ui/src/lib/core/src/shared/components/asset-preview/asset-preview.component.html index 71ce78d359..e371f46735 100644 --- a/packages/admin-ui/src/lib/core/src/shared/components/asset-preview/asset-preview.component.html +++ b/packages/admin-ui/src/lib/core/src/shared/components/asset-preview/asset-preview.component.html @@ -107,42 +107,44 @@
currencyCode: CurrencyCode!
-
price: Int!
+
price: Money!
}
@@ -3026,6 +3034,8 @@ import MemberDescription from '@site/src/components/MemberDescription';
perCustomerUsageLimit: Int
+
usageLimit: Int
+
name: String!
description: String!
diff --git a/docs/docs/reference/graphql-api/admin/queries.md b/docs/docs/reference/graphql-api/admin/queries.md index 1e81e348f8..b1f27b9801 100644 --- a/docs/docs/reference/graphql-api/admin/queries.md +++ b/docs/docs/reference/graphql-api/admin/queries.md @@ -1,8 +1,6 @@ --- title: "Queries" -weight: 1 -date: 2023-07-21T15:33:44.314Z -showtoc: true +isDefaultIndex: false generated: true --- diff --git a/docs/docs/reference/graphql-api/shop/enums.md b/docs/docs/reference/graphql-api/shop/enums.md index 55bac18f47..e104ec8544 100644 --- a/docs/docs/reference/graphql-api/shop/enums.md +++ b/docs/docs/reference/graphql-api/shop/enums.md @@ -1,8 +1,6 @@ --- title: "Enums" -weight: 5 -date: 2023-07-21T15:33:42.677Z -showtoc: true +isDefaultIndex: false generated: true --- diff --git a/docs/docs/reference/graphql-api/shop/input-types.md b/docs/docs/reference/graphql-api/shop/input-types.md index 5be6f1ee6e..1cd14516c1 100644 --- a/docs/docs/reference/graphql-api/shop/input-types.md +++ b/docs/docs/reference/graphql-api/shop/input-types.md @@ -1,8 +1,6 @@ --- title: "Input Objects" -weight: 4 -date: 2023-07-21T15:33:42.677Z -showtoc: true +isDefaultIndex: false generated: true --- @@ -473,6 +471,87 @@ import MemberDescription from '@site/src/components/MemberDescription';
or: [ID!]
+
}
+ + + +## FacetValueFilterParameter + +
+
input FacetValueFilterParameter + {
+ + +
createdAt: DateOperators
+ +
updatedAt: DateOperators
+ +
languageCode: StringOperators
+ +
facetId: IDOperators
+ + + + + + +
}
+ +
+ +## FacetValueListOptions + +
+
input FacetValueListOptions + {
+
"""
+
Skips the first n results, for use in pagination
+
"""
+
skip: Int
+ +
"""
+
Takes n results, for use in pagination
+
"""
+
take: Int
+ +
"""
+
Specifies which properties to sort the results by
+
"""
+ + +
"""
+
Allows the results to be filtered
+
"""
+ + +
"""
+
Specifies whether multiple "filter" arguments should be combines with a logical AND or OR operation. Defaults to AND.
+
"""
+
filterOperator: LogicalOperator
+ + +
}
+ +
+ +## FacetValueSortParameter + +
+
input FacetValueSortParameter + {
+ + +
createdAt: SortOrder
+ +
updatedAt: SortOrder
+ +
facetId: SortOrder
+ +
name: SortOrder
+ +
code: SortOrder
+ +
}
diff --git a/docs/docs/reference/graphql-api/shop/mutations.md b/docs/docs/reference/graphql-api/shop/mutations.md index 78317d83e3..8278fb2c13 100644 --- a/docs/docs/reference/graphql-api/shop/mutations.md +++ b/docs/docs/reference/graphql-api/shop/mutations.md @@ -1,8 +1,6 @@ --- title: "Mutations" -weight: 2 -date: 2023-07-21T15:33:42.677Z -showtoc: true +isDefaultIndex: false generated: true --- diff --git a/docs/docs/reference/graphql-api/shop/object-types.md b/docs/docs/reference/graphql-api/shop/object-types.md index 73c7083d89..41bfb4ce99 100644 --- a/docs/docs/reference/graphql-api/shop/object-types.md +++ b/docs/docs/reference/graphql-api/shop/object-types.md @@ -1,8 +1,6 @@ --- title: "Types" -weight: 3 -date: 2023-07-21T15:33:42.677Z -showtoc: true +isDefaultIndex: false generated: true --- @@ -795,6 +793,11 @@ import MemberDescription from '@site/src/components/MemberDescription';
values: [FacetValue!]!
+
"""
+
Returns a paginated, sortable, filterable list of the Facet's values. Added in v2.1.0.
+
"""
+ +
translations: [FacetTranslation!]!
customFields: JSON
@@ -850,6 +853,8 @@ import MemberDescription from '@site/src/components/MemberDescription';
facet: Facet!
+
facetId: ID!
+
name: String!
code: String!
@@ -859,6 +864,19 @@ import MemberDescription from '@site/src/components/MemberDescription';
customFields: JSON
+
}
+ + +## FacetValueList + +
+
type FacetValueList + {
+
items: [FacetValue!]!
+ +
totalItems: Int!
+ +
}
@@ -1622,6 +1640,9 @@ import MemberDescription from '@site/src/components/MemberDescription';
"""
proratedUnitPriceWithTax: Money!
+
"""
+
The quantity of items purchased
+
"""
quantity: Int!
"""
@@ -2288,6 +2309,8 @@ import MemberDescription from '@site/src/components/MemberDescription';
perCustomerUsageLimit: Int
+
usageLimit: Int
+
name: String!
description: String!
diff --git a/docs/docs/reference/graphql-api/shop/queries.md b/docs/docs/reference/graphql-api/shop/queries.md index c5d3d803b6..fc6d494a36 100644 --- a/docs/docs/reference/graphql-api/shop/queries.md +++ b/docs/docs/reference/graphql-api/shop/queries.md @@ -1,8 +1,6 @@ --- title: "Queries" -weight: 1 -date: 2023-07-21T15:33:42.677Z -showtoc: true +isDefaultIndex: false generated: true --- diff --git a/docs/docs/reference/typescript-api/common/permission.md b/docs/docs/reference/typescript-api/common/permission.md index 1b1aaa9024..36e0ec9366 100644 --- a/docs/docs/reference/typescript-api/common/permission.md +++ b/docs/docs/reference/typescript-api/common/permission.md @@ -11,7 +11,7 @@ import MemberDescription from '@site/src/components/MemberDescription'; ## Permission - + Permissions for administrators and customers. Used to control access to GraphQL resolvers via the Allow decorator. diff --git a/docs/docs/reference/typescript-api/errors/error-result-union.md b/docs/docs/reference/typescript-api/errors/error-result-union.md new file mode 100644 index 0000000000..1169cdc1b2 --- /dev/null +++ b/docs/docs/reference/typescript-api/errors/error-result-union.md @@ -0,0 +1,34 @@ +--- +title: "ErrorResultUnion" +isDefaultIndex: false +generated: true +--- + +import MemberInfo from '@site/src/components/MemberInfo'; +import GenerationInfo from '@site/src/components/GenerationInfo'; +import MemberDescription from '@site/src/components/MemberDescription'; + + +## ErrorResultUnion + + + +Used to construct a TypeScript return type for a query or mutation which, in the GraphQL schema, +returns a union type composed of a success result (e.g. Order) plus one or more ErrorResult +types. + +Since the TypeScript entities do not correspond 1-to-1 with their GraphQL type counterparts, +we use this type to substitute them. + +*Example* + +```ts +type UpdateOrderItemsResult = Order | OrderModificationError | OrderLimitError | NegativeQuantityError; +type T1 = ErrorResultUnion; +// T1 = VendureEntityOrder | OrderModificationError | OrderLimitError | NegativeQuantityError; +``` + +```ts title="Signature" +type ErrorResultUnion = | JustErrorResults + | E +``` diff --git a/docs/docs/reference/typescript-api/errors/is-graph-ql-error-result.md b/docs/docs/reference/typescript-api/errors/is-graph-ql-error-result.md new file mode 100644 index 0000000000..2aa117ad05 --- /dev/null +++ b/docs/docs/reference/typescript-api/errors/is-graph-ql-error-result.md @@ -0,0 +1,44 @@ +--- +title: "IsGraphQlErrorResult" +isDefaultIndex: false +generated: true +--- + +import MemberInfo from '@site/src/components/MemberInfo'; +import GenerationInfo from '@site/src/components/GenerationInfo'; +import MemberDescription from '@site/src/components/MemberDescription'; + + +## isGraphQlErrorResult + + + +Returns true if the ErrorResultUnion is actually an ErrorResult type. This is useful when dealing with +certain internal service method that return an ErrorResultUnion. + +*Example* + +```ts +import { isGraphQlErrorResult } from '@vendure/core'; + +// ... + +const transitionResult = await this.orderService.transitionToState(ctx, order.id, newState); +if (isGraphQlErrorResult(transitionResult)) { + // The transition failed with an ErrorResult + throw transitionResult; +} else { + // TypeScript will correctly infer the type of `transitionResult` to be `Order` + return transitionResult; +} +``` + +```ts title="Signature" +function isGraphQlErrorResult(input: T): input is JustErrorResults +``` +Parameters + +### input + + + diff --git a/docs/docs/reference/typescript-api/events/vendure-entity-event.md b/docs/docs/reference/typescript-api/events/vendure-entity-event.md index 790f830596..a5d825a3c4 100644 --- a/docs/docs/reference/typescript-api/events/vendure-entity-event.md +++ b/docs/docs/reference/typescript-api/events/vendure-entity-event.md @@ -11,10 +11,9 @@ import MemberDescription from '@site/src/components/MemberDescription'; ## VendureEntityEvent - + The base class for all entity events used by the EventBus system. -* For event type `'updated'` the entity is the one before applying the patch (if not documented otherwise). * For event type `'deleted'` the input will most likely be an `id: ID` ```ts title="Signature" diff --git a/docs/docs/reference/typescript-api/orders/default-guest-checkout-strategy.md b/docs/docs/reference/typescript-api/orders/default-guest-checkout-strategy.md index 58a2fd8ddb..9e87458b56 100644 --- a/docs/docs/reference/typescript-api/orders/default-guest-checkout-strategy.md +++ b/docs/docs/reference/typescript-api/orders/default-guest-checkout-strategy.md @@ -57,7 +57,7 @@ class DefaultGuestCheckoutStrategy implements GuestCheckoutStrategy { ### setCustomerForOrder -RequestContext, order: Order, input: CreateCustomerInput) => Promise<ErrorResultUnion<SetCustomerForOrderResult, Customer>>`} /> +RequestContext, order: Order, input: CreateCustomerInput) => Promise<ErrorResultUnion<SetCustomerForOrderResult, Customer>>`} /> diff --git a/docs/docs/reference/typescript-api/orders/guest-checkout-strategy.md b/docs/docs/reference/typescript-api/orders/guest-checkout-strategy.md index b2f28b3579..523fd7f3db 100644 --- a/docs/docs/reference/typescript-api/orders/guest-checkout-strategy.md +++ b/docs/docs/reference/typescript-api/orders/guest-checkout-strategy.md @@ -51,7 +51,7 @@ interface GuestCheckoutStrategy extends InjectableStrategy { ### setCustomerForOrder -RequestContext, order: Order, input: CreateCustomerInput) => | ErrorResultUnion<SetCustomerForOrderResult, Customer> | Promise<ErrorResultUnion<SetCustomerForOrderResult, Customer>>`} /> +RequestContext, order: Order, input: CreateCustomerInput) => | ErrorResultUnion<SetCustomerForOrderResult, Customer> | Promise<ErrorResultUnion<SetCustomerForOrderResult, Customer>>`} /> This method is called when the `setCustomerForOrder` mutation is executed. It should return either a Customer object or an ErrorResult. diff --git a/docs/docs/reference/typescript-api/orders/order-process.md b/docs/docs/reference/typescript-api/orders/order-process.md index 6e5676e178..2371270df0 100644 --- a/docs/docs/reference/typescript-api/orders/order-process.md +++ b/docs/docs/reference/typescript-api/orders/order-process.md @@ -196,7 +196,7 @@ Parameters ## defaultOrderProcess - + This is the built-in OrderProcess that ships with Vendure. A customized version of this process can be created using the configureDefaultOrderProcess function, which allows you to pass in an object diff --git a/docs/docs/reference/typescript-api/request/request-context-service.md b/docs/docs/reference/typescript-api/request/request-context-service.md index e9f33a9df9..7263b1a7cf 100644 --- a/docs/docs/reference/typescript-api/request/request-context-service.md +++ b/docs/docs/reference/typescript-api/request/request-context-service.md @@ -11,7 +11,7 @@ import MemberDescription from '@site/src/components/MemberDescription'; ## RequestContextService - + Creates new RequestContext instances. diff --git a/docs/docs/reference/typescript-api/services/channel-service.md b/docs/docs/reference/typescript-api/services/channel-service.md index d7b05aa64a..e3f07cc147 100644 --- a/docs/docs/reference/typescript-api/services/channel-service.md +++ b/docs/docs/reference/typescript-api/services/channel-service.md @@ -11,7 +11,7 @@ import MemberDescription from '@site/src/components/MemberDescription'; ## ChannelService - + Contains methods relating to Channel entities. @@ -90,12 +90,12 @@ Returns the default Channel. ### create -RequestContext, input: CreateChannelInput) => Promise<ErrorResultUnion<CreateChannelResult, Channel>>`} /> +RequestContext, input: CreateChannelInput) => Promise<ErrorResultUnion<CreateChannelResult, Channel>>`} /> ### update -RequestContext, input: UpdateChannelInput) => Promise<ErrorResultUnion<UpdateChannelResult, Channel>>`} /> +RequestContext, input: UpdateChannelInput) => Promise<ErrorResultUnion<UpdateChannelResult, Channel>>`} /> ### delete diff --git a/docs/docs/reference/typescript-api/services/collection-service.md b/docs/docs/reference/typescript-api/services/collection-service.md index 28af8bb05b..9171ae4a1b 100644 --- a/docs/docs/reference/typescript-api/services/collection-service.md +++ b/docs/docs/reference/typescript-api/services/collection-service.md @@ -11,7 +11,7 @@ import MemberDescription from '@site/src/components/MemberDescription'; ## CollectionService - + Contains methods relating to Collection entities. diff --git a/docs/docs/reference/typescript-api/services/customer-service.md b/docs/docs/reference/typescript-api/services/customer-service.md index 2e91471aa6..44884e197f 100644 --- a/docs/docs/reference/typescript-api/services/customer-service.md +++ b/docs/docs/reference/typescript-api/services/customer-service.md @@ -31,8 +31,8 @@ class CustomerService { refreshVerificationToken(ctx: RequestContext, emailAddress: string) => Promise; verifyCustomerEmailAddress(ctx: RequestContext, verificationToken: string, password?: string) => Promise>; requestPasswordReset(ctx: RequestContext, emailAddress: string) => Promise; - resetPassword(ctx: RequestContext, passwordResetToken: string, password: string) => Promise< - User | PasswordResetTokenExpiredError | PasswordResetTokenInvalidError | PasswordValidationError + resetPassword(ctx: RequestContext, passwordResetToken: string, password: string) => Promise< + User | PasswordResetTokenExpiredError | PasswordResetTokenInvalidError | PasswordValidationError >; requestUpdateEmailAddress(ctx: RequestContext, userId: ID, newEmailAddress: string) => Promise; updateEmailAddress(ctx: RequestContext, token: string) => Promise; @@ -69,8 +69,8 @@ class CustomerService { RequestContext, userId: ID, filterOnChannel: = true) => Promise<Customer | undefined>`} /> -Returns the Customer entity associated with the given userId, if one exists. -Setting `filterOnChannel` to `true` will limit the results to Customers which are assigned +Returns the Customer entity associated with the given userId, if one exists. +Setting `filterOnChannel` to `true` will limit the results to Customers which are assigned to the current active Channel only. ### findAddressesByCustomerId @@ -84,15 +84,15 @@ Returns all Address Returns a list of all CustomerGroup entities. ### create -RequestContext, input: CreateCustomerInput, password?: string) => Promise<ErrorResultUnion<CreateCustomerResult, Customer>>`} /> - -Creates a new Customer, including creation of a new User with the special `customer` Role. - -If the `password` argument is specified, the Customer will be immediately verified. If not, -then an AccountRegistrationEvent is published, so that the customer can have their -email address verified and set their password in a later step using the `verifyCustomerEmailAddress()` -method. +RequestContext, input: CreateCustomerInput, password?: string) => Promise<ErrorResultUnion<CreateCustomerResult, Customer>>`} /> +Creates a new Customer, including creation of a new User with the special `customer` Role. + +If the `password` argument is specified, the Customer will be immediately verified. If not, +then an AccountRegistrationEvent is published, so that the customer can have their +email address verified and set their password in a later step using the `verifyCustomerEmailAddress()` +method. + This method is intended to be used in admin-created Customer flows. ### update @@ -101,59 +101,59 @@ This method is intended to be used in admin-created Customer flows. ### update -RequestContext, input: UpdateCustomerInput) => Promise<ErrorResultUnion<UpdateCustomerResult, Customer>>`} /> +RequestContext, input: UpdateCustomerInput) => Promise<ErrorResultUnion<UpdateCustomerResult, Customer>>`} /> ### update -RequestContext, input: UpdateCustomerInput | (UpdateCustomerShopInput & { id: ID })) => Promise<ErrorResultUnion<UpdateCustomerResult, Customer>>`} /> +RequestContext, input: UpdateCustomerInput | (UpdateCustomerShopInput & { id: ID })) => Promise<ErrorResultUnion<UpdateCustomerResult, Customer>>`} /> ### registerCustomerAccount RequestContext, input: RegisterCustomerInput) => Promise<RegisterCustomerAccountResult | EmailAddressConflictError | PasswordValidationError>`} /> -Registers a new Customer account with the NativeAuthenticationStrategy and starts -the email verification flow (unless AuthOptions `requireVerification` is set to `false`) -by publishing an AccountRegistrationEvent. - +Registers a new Customer account with the NativeAuthenticationStrategy and starts +the email verification flow (unless AuthOptions `requireVerification` is set to `false`) +by publishing an AccountRegistrationEvent. + This method is intended to be used in storefront Customer-creation flows. ### refreshVerificationToken RequestContext, emailAddress: string) => Promise<void>`} /> -Refreshes a stale email address verification token by generating a new one and +Refreshes a stale email address verification token by generating a new one and publishing a AccountRegistrationEvent. ### verifyCustomerEmailAddress -RequestContext, verificationToken: string, password?: string) => Promise<ErrorResultUnion<VerifyCustomerAccountResult, Customer>>`} /> +RequestContext, verificationToken: string, password?: string) => Promise<ErrorResultUnion<VerifyCustomerAccountResult, Customer>>`} /> -Given a valid verification token which has been published in an AccountRegistrationEvent, this +Given a valid verification token which has been published in an AccountRegistrationEvent, this method is used to set the Customer as `verified` as part of the account registration flow. ### requestPasswordReset RequestContext, emailAddress: string) => Promise<void>`} /> -Publishes a new PasswordResetEvent for the given email address. This event creates +Publishes a new PasswordResetEvent for the given email address. This event creates a token which can be used in the `resetPassword()` method. ### resetPassword -RequestContext, passwordResetToken: string, password: string) => Promise< User | PasswordResetTokenExpiredError | PasswordResetTokenInvalidError | PasswordValidationError >`} /> +RequestContext, passwordResetToken: string, password: string) => Promise< User | PasswordResetTokenExpiredError | PasswordResetTokenInvalidError | PasswordValidationError >`} /> -Given a valid password reset token created by a call to the `requestPasswordReset()` method, +Given a valid password reset token created by a call to the `requestPasswordReset()` method, this method will change the Customer's password to that given as the `password` argument. ### requestUpdateEmailAddress RequestContext, userId: ID, newEmailAddress: string) => Promise<boolean | EmailAddressConflictError>`} /> -Publishes a IdentifierChangeRequestEvent for the given User. This event contains a token -which is then used in the `updateEmailAddress()` method to change the email address of the User & +Publishes a IdentifierChangeRequestEvent for the given User. This event contains a token +which is then used in the `updateEmailAddress()` method to change the email address of the User & Customer. ### updateEmailAddress RequestContext, token: string) => Promise<boolean | IdentifierChangeTokenInvalidError | IdentifierChangeTokenExpiredError>`} /> -Given a valid email update token published in a IdentifierChangeRequestEvent, this method +Given a valid email update token published in a IdentifierChangeRequestEvent, this method will update the Customer & User email address. ### createOrUpdate @@ -184,8 +184,8 @@ Creates a new Addre RequestContext, order: Order) => `} /> -If the Customer associated with the given Order does not yet have any Addresses, -this method will create new Address(es) based on the Order's shipping & billing +If the Customer associated with the given Order does not yet have any Addresses, +this method will create new Address(es) based on the Order's shipping & billing addresses. ### addNoteToCustomer diff --git a/docs/docs/reference/typescript-api/services/facet-service.md b/docs/docs/reference/typescript-api/services/facet-service.md index 2ec7eff90a..274e80e484 100644 --- a/docs/docs/reference/typescript-api/services/facet-service.md +++ b/docs/docs/reference/typescript-api/services/facet-service.md @@ -91,7 +91,7 @@ Returns the Facet which contains the given FacetValue id. Assigns Facets to the specified Channel ### removeFacetsFromChannel -RequestContext, input: RemoveFacetsFromChannelInput) => Promise<Array<ErrorResultUnion<RemoveFacetFromChannelResult, Facet>>>`} /> +RequestContext, input: RemoveFacetsFromChannelInput) => Promise<Array<ErrorResultUnion<RemoveFacetFromChannelResult, Facet>>>`} /> Remove Facets from the specified Channel diff --git a/docs/docs/reference/typescript-api/services/order-service.md b/docs/docs/reference/typescript-api/services/order-service.md index 70f0024405..aa70fae38b 100644 --- a/docs/docs/reference/typescript-api/services/order-service.md +++ b/docs/docs/reference/typescript-api/services/order-service.md @@ -166,23 +166,23 @@ User's Customer account. Updates the custom fields of an Order. ### addItemToOrder -RequestContext, orderId: ID, productVariantId: ID, quantity: number, customFields?: { [key: string]: any }) => Promise<ErrorResultUnion<UpdateOrderItemsResult, Order>>`} /> +RequestContext, orderId: ID, productVariantId: ID, quantity: number, customFields?: { [key: string]: any }) => Promise<ErrorResultUnion<UpdateOrderItemsResult, Order>>`} /> Adds an item to the Order, either creating a new OrderLine or incrementing an existing one. ### adjustOrderLine -RequestContext, orderId: ID, orderLineId: ID, quantity: number, customFields?: { [key: string]: any }) => Promise<ErrorResultUnion<UpdateOrderItemsResult, Order>>`} /> +RequestContext, orderId: ID, orderLineId: ID, quantity: number, customFields?: { [key: string]: any }) => Promise<ErrorResultUnion<UpdateOrderItemsResult, Order>>`} /> Adjusts the quantity and/or custom field values of an existing OrderLine. ### removeItemFromOrder -RequestContext, orderId: ID, orderLineId: ID) => Promise<ErrorResultUnion<RemoveOrderItemsResult, Order>>`} /> +RequestContext, orderId: ID, orderLineId: ID) => Promise<ErrorResultUnion<RemoveOrderItemsResult, Order>>`} /> Removes the specified OrderLine from the Order. ### removeAllItemsFromOrder -RequestContext, orderId: ID) => Promise<ErrorResultUnion<RemoveOrderItemsResult, Order>>`} /> +RequestContext, orderId: ID) => Promise<ErrorResultUnion<RemoveOrderItemsResult, Order>>`} /> Removes all OrderLines from the Order. ### addSurchargeToOrder @@ -197,7 +197,7 @@ Adds a Surcharg Removes a Surcharge from the Order. ### applyCouponCode -RequestContext, orderId: ID, couponCode: string) => Promise<ErrorResultUnion<ApplyCouponCodeResult, Order>>`} /> +RequestContext, orderId: ID, couponCode: string) => Promise<ErrorResultUnion<ApplyCouponCodeResult, Order>>`} /> Applies a coupon code to the Order, which should be a valid coupon code as specified in the configuration of an active Promotion. @@ -242,7 +242,7 @@ The quote also includes a price for each method, as determined by the configured Returns an array of quotes stating which PaymentMethods may be used on this Order. ### setShippingMethod -RequestContext, orderId: ID, shippingMethodIds: ID[]) => Promise<ErrorResultUnion<SetOrderShippingMethodResult, Order>>`} /> +RequestContext, orderId: ID, shippingMethodIds: ID[]) => Promise<ErrorResultUnion<SetOrderShippingMethodResult, Order>>`} /> Sets the ShippingMethod to be used on this Order. ### transitionToState @@ -258,7 +258,7 @@ Transitions a Fulfillment to the given state and then transitions the Order stat whether all Fulfillments of the Order are shipped or delivered. ### modifyOrder -RequestContext, input: ModifyOrderInput) => Promise<ErrorResultUnion<ModifyOrderResult, Order>>`} /> +RequestContext, input: ModifyOrderInput) => Promise<ErrorResultUnion<ModifyOrderResult, Order>>`} /> Allows the Order to be modified, which allows several aspects of the Order to be changed: @@ -273,20 +273,20 @@ Order, except history entry and additional payment actions. __Using dryRun option, you must wrap function call in transaction manually.__ ### transitionPaymentToState -RequestContext, paymentId: ID, state: PaymentState) => Promise<ErrorResultUnion<TransitionPaymentToStateResult, Payment>>`} /> +RequestContext, paymentId: ID, state: PaymentState) => Promise<ErrorResultUnion<TransitionPaymentToStateResult, Payment>>`} /> Transitions the given Payment to a new state. If the order totalWithTax price is then covered by Payments, the Order state will be automatically transitioned to `PaymentSettled` or `PaymentAuthorized`. ### addPaymentToOrder -RequestContext, orderId: ID, input: PaymentInput) => Promise<ErrorResultUnion<AddPaymentToOrderResult, Order>>`} /> +RequestContext, orderId: ID, input: PaymentInput) => Promise<ErrorResultUnion<AddPaymentToOrderResult, Order>>`} /> Adds a new Payment to the Order. If the Order totalWithTax is covered by Payments, then the Order state will get automatically transitioned to the `PaymentSettled` or `PaymentAuthorized` state. ### addManualPaymentToOrder -RequestContext, input: ManualPaymentInput) => Promise<ErrorResultUnion<AddManualPaymentToOrderResult, Order>>`} /> +RequestContext, input: ManualPaymentInput) => Promise<ErrorResultUnion<AddManualPaymentToOrderResult, Order>>`} /> This method is used after modifying an existing completed order using the `modifyOrder()` method. If the modifications cause the order total to increase (such as when adding a new OrderLine), then there will be an outstanding charge to @@ -296,19 +296,19 @@ This method allows you to add a new Payment and assumes the actual processing ha dashboard of your payment provider. ### settlePayment -RequestContext, paymentId: ID) => Promise<ErrorResultUnion<SettlePaymentResult, Payment>>`} /> +RequestContext, paymentId: ID) => Promise<ErrorResultUnion<SettlePaymentResult, Payment>>`} /> Settles a payment by invoking the PaymentMethodHandler's `settlePayment()` method. Automatically transitions the Order state if all Payments are settled. ### cancelPayment -RequestContext, paymentId: ID) => Promise<ErrorResultUnion<CancelPaymentResult, Payment>>`} /> +RequestContext, paymentId: ID) => Promise<ErrorResultUnion<CancelPaymentResult, Payment>>`} /> Cancels a payment by invoking the PaymentMethodHandler's `cancelPayment()` method (if defined), and transitions the Payment to the `Cancelled` state. ### createFulfillment -RequestContext, input: FulfillOrderInput) => Promise<ErrorResultUnion<AddFulfillmentToOrderResult, Fulfillment>>`} /> +RequestContext, input: FulfillOrderInput) => Promise<ErrorResultUnion<AddFulfillmentToOrderResult, Fulfillment>>`} /> Creates a new Fulfillment associated with the given Order and OrderItems. ### getOrderFulfillments @@ -323,13 +323,13 @@ Returns an array of all Fulfillments associated with the Order. Returns an array of all Surcharges associated with the Order. ### cancelOrder -RequestContext, input: CancelOrderInput) => Promise<ErrorResultUnion<CancelOrderResult, Order>>`} /> +RequestContext, input: CancelOrderInput) => Promise<ErrorResultUnion<CancelOrderResult, Order>>`} /> Cancels an Order by transitioning it to the `Cancelled` state. If stock is being tracked for the ProductVariants in the Order, then new StockMovements will be created to correct the stock levels. ### refundOrder -RequestContext, input: RefundOrderInput) => Promise<ErrorResultUnion<RefundOrderResult, Refund>>`} /> +RequestContext, input: RefundOrderInput) => Promise<ErrorResultUnion<RefundOrderResult, Refund>>`} /> Creates a {@link Refund} against the order and in doing so invokes the `createRefund()` method of the PaymentMethodHandler. diff --git a/docs/docs/reference/typescript-api/services/product-service.md b/docs/docs/reference/typescript-api/services/product-service.md index 4eb644aebf..0b2191b854 100644 --- a/docs/docs/reference/typescript-api/services/product-service.md +++ b/docs/docs/reference/typescript-api/services/product-service.md @@ -11,13 +11,13 @@ import MemberDescription from '@site/src/components/MemberDescription'; ## ProductService - + Contains methods relating to Product entities. ```ts title="Signature" class ProductService { - constructor(connection: TransactionalConnection, channelService: ChannelService, roleService: RoleService, assetService: AssetService, productVariantService: ProductVariantService, facetValueService: FacetValueService, taxRateService: TaxRateService, collectionService: CollectionService, listQueryBuilder: ListQueryBuilder, translatableSaver: TranslatableSaver, eventBus: EventBus, slugValidator: SlugValidator, customFieldRelationService: CustomFieldRelationService, translator: TranslatorService, productOptionGroupService: ProductOptionGroupService) + constructor(connection: TransactionalConnection, channelService: ChannelService, assetService: AssetService, productVariantService: ProductVariantService, facetValueService: FacetValueService, listQueryBuilder: ListQueryBuilder, translatableSaver: TranslatableSaver, eventBus: EventBus, slugValidator: SlugValidator, customFieldRelationService: CustomFieldRelationService, translator: TranslatorService, productOptionGroupService: ProductOptionGroupService) findAll(ctx: RequestContext, options?: ListQueryOptions, relations?: RelationPaths) => Promise>>; findOne(ctx: RequestContext, productId: ID, relations?: RelationPaths) => Promise | undefined>; findByIds(ctx: RequestContext, productIds: ID[], relations?: RelationPaths) => Promise>>; @@ -38,7 +38,7 @@ class ProductService { ### constructor -TransactionalConnection, channelService: ChannelService, roleService: RoleService, assetService: AssetService, productVariantService: ProductVariantService, facetValueService: FacetValueService, taxRateService: TaxRateService, collectionService: CollectionService, listQueryBuilder: ListQueryBuilder, translatableSaver: TranslatableSaver, eventBus: EventBus, slugValidator: SlugValidator, customFieldRelationService: CustomFieldRelationService, translator: TranslatorService, productOptionGroupService: ProductOptionGroupService) => ProductService`} /> +TransactionalConnection, channelService: ChannelService, assetService: AssetService, productVariantService: ProductVariantService, facetValueService: FacetValueService, listQueryBuilder: ListQueryBuilder, translatableSaver: TranslatableSaver, eventBus: EventBus, slugValidator: SlugValidator, customFieldRelationService: CustomFieldRelationService, translator: TranslatorService, productOptionGroupService: ProductOptionGroupService) => ProductService`} /> ### findAll @@ -107,7 +107,7 @@ each of the Product's variants, and will assign the Product's Assets to the Chan ### removeOptionGroupFromProduct -RequestContext, productId: ID, optionGroupId: ID, force?: boolean) => Promise<ErrorResultUnion<RemoveOptionGroupFromProductResult, Translated<Product>>>`} /> +RequestContext, productId: ID, optionGroupId: ID, force?: boolean) => Promise<ErrorResultUnion<RemoveOptionGroupFromProductResult, Translated<Product>>>`} /> diff --git a/docs/docs/reference/typescript-api/services/promotion-service.md b/docs/docs/reference/typescript-api/services/promotion-service.md index 1e622413bf..4bc9b09a93 100644 --- a/docs/docs/reference/typescript-api/services/promotion-service.md +++ b/docs/docs/reference/typescript-api/services/promotion-service.md @@ -76,12 +76,12 @@ class PromotionService { ### createPromotion -RequestContext, input: CreatePromotionInput) => Promise<ErrorResultUnion<CreatePromotionResult, Promotion>>`} /> +RequestContext, input: CreatePromotionInput) => Promise<ErrorResultUnion<CreatePromotionResult, Promotion>>`} /> ### updatePromotion -RequestContext, input: UpdatePromotionInput) => Promise<ErrorResultUnion<UpdatePromotionResult, Promotion>>`} /> +RequestContext, input: UpdatePromotionInput) => Promise<ErrorResultUnion<UpdatePromotionResult, Promotion>>`} /> ### softDeletePromotion diff --git a/docs/docs/reference/typescript-api/services/user-service.md b/docs/docs/reference/typescript-api/services/user-service.md index b75a446f5f..f239636cef 100644 --- a/docs/docs/reference/typescript-api/services/user-service.md +++ b/docs/docs/reference/typescript-api/services/user-service.md @@ -88,7 +88,7 @@ Sets the RequestContext, verificationToken: string, password?: string) => Promise<ErrorResultUnion<VerifyCustomerAccountResult, User>>`} /> +RequestContext, verificationToken: string, password?: string) => Promise<ErrorResultUnion<VerifyCustomerAccountResult, User>>`} /> Verifies a verificationToken by looking for a User which has previously had it set using the `setVerificationToken()` method, and checks that the token is valid and has not expired. diff --git a/docs/sidebars.js b/docs/sidebars.js index 5a83793616..9cb905de02 100644 --- a/docs/sidebars.js +++ b/docs/sidebars.js @@ -267,6 +267,12 @@ const sidebars = { link: { type: 'doc', id: 'reference/core-plugins/payments-plugin/index' }, items: [{ type: 'autogenerated', dirName: 'reference/core-plugins/payments-plugin' }], }, + { + type: 'category', + label: 'StellatePlugin', + link: { type: 'doc', id: 'reference/core-plugins/stellate-plugin/index' }, + items: [{ type: 'autogenerated', dirName: 'reference/core-plugins/stellate-plugin' }], + }, ], }, { diff --git a/package.json b/package.json index 08099b82bd..cf10865561 100644 --- a/package.json +++ b/package.json @@ -12,8 +12,7 @@ "format": "prettier --write --html-whitespace-sensitivity ignore", "docs:generate-typescript-docs": "ts-node scripts/docs/generate-typescript-docs.ts", "docs:generate-graphql-docs": "ts-node scripts/docs/generate-graphql-docs.ts --api=shop && ts-node scripts/docs/generate-graphql-docs.ts --api=admin", - "docs:update-build-info": "ts-node scripts/docs/update-build-info.ts", - "docs:build": "yarn docs:generate-graphql-docs && yarn docs:generate-typescript-docs && yarn docs:update-build-info", + "docs:build": "yarn docs:generate-graphql-docs && yarn docs:generate-typescript-docs", "codegen": "tsc -p scripts/codegen/plugins && ts-node scripts/codegen/generate-graphql-types.ts", "version": "yarn check-imports && yarn check-angular-versions && yarn build && yarn check-core-type-defs && yarn generate-changelog && git add CHANGELOG* && git add */version.ts", "dev-server:start": "cd packages/dev-server && yarn start", diff --git a/packages/stellate-plugin/.gitignore b/packages/stellate-plugin/.gitignore new file mode 100644 index 0000000000..368c3fdfbe --- /dev/null +++ b/packages/stellate-plugin/.gitignore @@ -0,0 +1,3 @@ +yarn-error.log +lib +e2e/__data__/*.sqlite diff --git a/packages/stellate-plugin/README.md b/packages/stellate-plugin/README.md new file mode 100644 index 0000000000..bd4b67cdc7 --- /dev/null +++ b/packages/stellate-plugin/README.md @@ -0,0 +1,7 @@ +# Vendure Stellate Plugin + +Integrates your Vendure server with the [Stellate](TaxRateEvent) GraphQL API cache. + +`npm install @vendure/stellate-plugin` + +For documentation, see [docs.vendure.io/typescript-api/core-plugins/stellate-plugin/](https://docs.vendure.io/typescript-api/core-plugins/stellate-plugin/) diff --git a/packages/stellate-plugin/index.ts b/packages/stellate-plugin/index.ts new file mode 100644 index 0000000000..e270ae43f8 --- /dev/null +++ b/packages/stellate-plugin/index.ts @@ -0,0 +1,6 @@ +export * from './src/stellate-plugin'; +export * from './src/service/stellate.service'; +export * from './src/default-purge-rules'; +export * from './src/purge-rule'; +export * from './src/types'; +export * from './src/constants'; diff --git a/packages/stellate-plugin/package.json b/packages/stellate-plugin/package.json new file mode 100644 index 0000000000..738cfc23a1 --- /dev/null +++ b/packages/stellate-plugin/package.json @@ -0,0 +1,27 @@ +{ + "name": "@vendure/stellate-plugin", + "version": "2.1.4", + "license": "MIT", + "main": "lib/index.js", + "types": "lib/index.d.ts", + "files": [ + "lib/**/*" + ], + "scripts": { + "watch": "tsc -p ./tsconfig.build.json --watch", + "build": "rimraf lib && tsc -p ./tsconfig.build.json", + "lint": "eslint --fix ." + }, + "homepage": "https://www.vendure.io", + "funding": "https://github.com/sponsors/michaelbromley", + "publishConfig": { + "access": "public" + }, + "dependencies": { + "node-fetch": "^2.7.0" + }, + "devDependencies": { + "@vendure/common": "^2.1.4", + "@vendure/core": "^2.1.4" + } +} diff --git a/packages/stellate-plugin/src/api/api-extensions.ts b/packages/stellate-plugin/src/api/api-extensions.ts new file mode 100644 index 0000000000..239c6f3679 --- /dev/null +++ b/packages/stellate-plugin/src/api/api-extensions.ts @@ -0,0 +1,17 @@ +import gql from 'graphql-tag'; + +export const shopApiExtensions = gql` + """ + This type is here to allow us to easily purge the Stellate cache + of any search results where the collectionSlug is used. We cannot rely on + simply purging the SearchResult type, because in the case of an empty 'items' + array, Stellate cannot know that that particular query now needs to be purged. + """ + type SearchResponseCacheIdentifier { + collectionSlug: String + } + + extend type SearchResponse { + cacheIdentifier: SearchResponseCacheIdentifier + } +`; diff --git a/packages/stellate-plugin/src/api/search-response.resolver.ts b/packages/stellate-plugin/src/api/search-response.resolver.ts new file mode 100644 index 0000000000..4e522480ff --- /dev/null +++ b/packages/stellate-plugin/src/api/search-response.resolver.ts @@ -0,0 +1,11 @@ +import { Info, ResolveField, Resolver } from '@nestjs/graphql'; +import { GraphQLResolveInfo } from 'graphql/type'; + +@Resolver('SearchResponse') +export class SearchResponseFieldResolver { + @ResolveField() + cacheIdentifier(@Info() info: GraphQLResolveInfo) { + const collectionSlug = (info.variableValues.input as any)?.collectionSlug; + return { collectionSlug }; + } +} diff --git a/packages/stellate-plugin/src/constants.ts b/packages/stellate-plugin/src/constants.ts new file mode 100644 index 0000000000..80409e0e7b --- /dev/null +++ b/packages/stellate-plugin/src/constants.ts @@ -0,0 +1,2 @@ +export const STELLATE_PLUGIN_OPTIONS = 'STELLATE_PLUGIN_OPTIONS'; +export const loggerCtx = 'StellatePlugin'; diff --git a/packages/stellate-plugin/src/default-purge-rules.ts b/packages/stellate-plugin/src/default-purge-rules.ts new file mode 100644 index 0000000000..780ea40057 --- /dev/null +++ b/packages/stellate-plugin/src/default-purge-rules.ts @@ -0,0 +1,105 @@ +/* eslint-disable @typescript-eslint/no-floating-promises */ +import { + CollectionEvent, + CollectionModificationEvent, + Logger, + ProductChannelEvent, + ProductEvent, + ProductVariantChannelEvent, + ProductVariantEvent, + StockMovementEvent, + TaxRateEvent, +} from '@vendure/core'; + +import { loggerCtx } from './constants'; +import { PurgeRule } from './purge-rule'; + +export const purgeProductsOnProductEvent = new PurgeRule({ + eventType: ProductEvent, + handler: ({ events, stellateService }) => { + const products = events.map(e => e.product); + stellateService.purgeProducts(products); + stellateService.purgeSearchResults(products); + }, +}); + +export const purgeProductVariantsOnProductVariantEvent = new PurgeRule({ + eventType: ProductVariantEvent, + handler: ({ events, stellateService }) => { + const variants = events.map(e => e.variants).flat(); + stellateService.purgeProductVariants(variants); + stellateService.purgeSearchResults(variants); + }, +}); + +export const purgeProductsOnChannelEvent = new PurgeRule({ + eventType: ProductChannelEvent, + handler: ({ events, stellateService }) => { + const products = events.map(e => e.product); + stellateService.purgeProducts(products); + stellateService.purgeSearchResults(products); + }, +}); + +export const purgeProductVariantsOnChannelEvent = new PurgeRule({ + eventType: ProductVariantChannelEvent, + handler: ({ events, stellateService }) => { + const variants = events.map(e => e.productVariant); + stellateService.purgeProductVariants(variants); + stellateService.purgeSearchResults(variants); + }, +}); + +export const purgeProductVariantsOnStockMovementEvent = new PurgeRule({ + eventType: StockMovementEvent, + handler: ({ events, stellateService }) => { + const variants = events.map(e => e.stockMovements.map(m => m.productVariant)).flat(); + stellateService.purgeProductVariants(variants); + stellateService.purgeSearchResults(variants); + }, +}); + +export const purgeCollectionsOnCollectionModificationEvent = new PurgeRule({ + eventType: CollectionModificationEvent, + handler: ({ events, stellateService }) => { + const collectionsToPurge = events.filter(e => e.productVariantIds.length).map(e => e.collection); + Logger.debug( + `purgeCollectionsOnCollectionModificationEvent, collectionsToPurge: ${collectionsToPurge + .map(c => c.id) + .join(', ')}`, + loggerCtx, + ); + if (collectionsToPurge.length) { + stellateService.purgeCollections(collectionsToPurge); + stellateService.purgeSearchResponseCacheIdentifiers(collectionsToPurge); + } + }, +}); + +export const purgeCollectionsOnCollectionEvent = new PurgeRule({ + eventType: CollectionEvent, + handler: ({ events, stellateService }) => { + const collections = events.map(e => e.entity); + stellateService.purgeCollections(collections); + }, +}); + +export const purgeAllOnTaxRateEvent = new PurgeRule({ + eventType: TaxRateEvent, + handler: ({ stellateService }) => { + stellateService.purgeAllOfType('ProductVariant'); + stellateService.purgeAllOfType('Product'); + stellateService.purgeAllOfType('SearchResponse'); + }, +}); + +export const defaultPurgeRules = [ + purgeAllOnTaxRateEvent, + purgeCollectionsOnCollectionEvent, + purgeCollectionsOnCollectionModificationEvent, + purgeProductsOnChannelEvent, + purgeProductsOnProductEvent, + purgeProductVariantsOnChannelEvent, + purgeProductVariantsOnProductVariantEvent, + purgeProductVariantsOnStockMovementEvent, +]; diff --git a/packages/stellate-plugin/src/purge-rule.ts b/packages/stellate-plugin/src/purge-rule.ts new file mode 100644 index 0000000000..d73f9ca37b --- /dev/null +++ b/packages/stellate-plugin/src/purge-rule.ts @@ -0,0 +1,60 @@ +import { Type } from '@vendure/common/lib/shared-types'; +import { VendureEvent, Injector } from '@vendure/core'; + +import { StellateService } from './service/stellate.service'; + +/** + * @description + * Configures a {@link PurgeRule}. + * + * @docsCategory core plugins/StellatePlugin + * @docsPage PurgeRule + */ +export interface PurgeRuleConfig { + /** + * @description + * Specifies which VendureEvent will trigger this purge rule. + */ + eventType: Type; + /** + * @description + * How long to buffer events for in milliseconds before executing the handler. This allows + * us to efficiently batch calls to the Stellate Purge API. + * + * @default 5000 + */ + bufferTime?: number; + /** + * @description + * The function to invoke when the specified event is published. This function should use the + * {@link StellateService} instance to call the Stellate Purge API. + */ + handler: (handlerArgs: { + events: Event[]; + stellateService: StellateService; + injector: Injector; + }) => void | Promise; +} + +/** + * @description + * Defines a rule that listens for a particular VendureEvent and uses that to + * make calls to the [Stellate Purging API](https://docs.stellate.co/docs/purging-api) via + * the provided {@link StellateService} instance. + * + * @docsCategory core plugins/StellatePlugin + * @docsPage PurgeRule + * @docsWeight 0 + */ +export class PurgeRule { + get eventType(): Type { + return this.config.eventType; + } + get bufferTimeMs(): number | undefined { + return this.config.bufferTime; + } + handle(handlerArgs: { events: Event[]; stellateService: StellateService; injector: Injector }) { + return this.config.handler(handlerArgs); + } + constructor(private config: PurgeRuleConfig) {} +} diff --git a/packages/stellate-plugin/src/service/stellate.service.ts b/packages/stellate-plugin/src/service/stellate.service.ts new file mode 100644 index 0000000000..a3913fd265 --- /dev/null +++ b/packages/stellate-plugin/src/service/stellate.service.ts @@ -0,0 +1,150 @@ +import { Inject } from '@nestjs/common'; +import { Collection, ID, Logger, Product, ProductVariant } from '@vendure/core'; +import fetch from 'node-fetch'; + +import { loggerCtx, STELLATE_PLUGIN_OPTIONS } from '../constants'; +import { StellatePluginOptions } from '../types'; + +type CachedType = + | 'Product' + | 'ProductVariant' + | 'Collection' + | 'SearchResponse' + | 'SearchResult' + | 'SearchResponseCacheIdentifier' + | string; + +/** + * @description + * The StellateService is used to purge the Stellate cache when certain events occur. + * + * @docsCategory core plugins/StellatePlugin + */ +export class StellateService { + private readonly purgeApiUrl: string; + + constructor(@Inject(STELLATE_PLUGIN_OPTIONS) private options: StellatePluginOptions) { + this.purgeApiUrl = `https://admin.stellate.co/${options.serviceName}`; + } + + /** + * @description + * Purges the cache for the given Products. + */ + async purgeProducts(products: Product[]) { + Logger.verbose(`Purging cache: Product(${products.map(p => p.id).join(', ')})`, loggerCtx); + await this.purge( + 'Product', + products.map(p => p.id), + ); + } + + /** + * @description + * Purges the cache for the given ProductVariants. + */ + async purgeProductVariants(productVariants: ProductVariant[]) { + Logger.verbose( + `Purging cache: ProductVariant(${productVariants.map(p => p.id).join(', ')})`, + loggerCtx, + ); + await this.purge( + 'ProductVariant', + productVariants.map(p => p.id), + ); + } + + /** + * @description + * Purges the cache for SearchResults which contain the given Products or ProductVariants. + */ + async purgeSearchResults(items: Array) { + const productIds = items.map(item => (item instanceof Product ? item.id : item.productId)); + Logger.verbose(`Purging cache: SearchResult(${productIds.join(', ')})`, loggerCtx); + await this.purge('SearchResult', productIds, 'productId'); + } + + /** + * @description + * Purges the entire cache for the given type. + */ + async purgeAllOfType(type: CachedType) { + Logger.verbose(`Purging cache: All ${type}s`, loggerCtx); + await this.purge(type); + } + + /** + * @description + * Purges the cache for the given Collections. + */ + async purgeCollections(collections: Collection[]) { + Logger.verbose(`Purging cache: Collection(${collections.map(c => c.id).join(', ')})`, loggerCtx); + await this.purge( + 'Collection', + collections.map(p => p.id), + ); + } + + /** + * @description + * Purges the cache of SearchResults for the given Collections based on slug. + */ + async purgeSearchResponseCacheIdentifiers(collections: Collection[]) { + const slugs = collections.map(c => c.slug ?? c.translations?.[0]?.slug); + if (slugs.length) { + Logger.verbose(`Purging cache: SearchResponseCacheIdentifier(${slugs.join(', ')})`, loggerCtx); + await this.purge('SearchResponseCacheIdentifier', slugs); + } + } + + /** + * @description + * Purges the cache for the given type and keys. + */ + purge(type: CachedType, keys?: ID[], keyName = 'id') { + const payload = { + query: ` + mutation PurgeType($type: String!, $keyFields: [KeyFieldInput!]) { + _purgeType(type: $type, keyFields: $keyFields) + } + `, + variables: { + type, + keyFields: keys?.filter(id => !!id).map(id => ({ name: keyName, value: id.toString() })), + }, + }; + if (this.options.debugLogging === true) { + const keyFieldsLength = payload.variables.keyFields?.length ?? 0; + if (5 < keyFieldsLength) { + payload.variables.keyFields = payload.variables.keyFields?.slice(0, 5); + } + Logger.debug('Purge arguments:\n' + JSON.stringify(payload.variables, null, 2), loggerCtx); + if (5 < keyFieldsLength) { + Logger.debug(`(A further ${keyFieldsLength - 5} keyFields truncated)`, loggerCtx); + } + } + if (this.options.devMode === true) { + // no-op + } else { + return fetch(this.purgeApiUrl, { + method: 'POST', + headers: { + 'Content-Type': 'application/json', + 'stellate-token': this.options.apiToken, + }, + body: JSON.stringify(payload), + timeout: 5000, + }) + .then(res => res.json()) + .then(json => { + if (json.data?._purgeType !== true) { + const errors = json.errors?.map((e: any) => e.message) as string[]; + Logger.error(`Purge failed: ${errors.join(', ') ?? JSON.stringify(json)}`, loggerCtx); + } + }) + .catch((err: any) => { + Logger.error(`Purge error: ${err.message as string}`, loggerCtx); + }); + } + } +} diff --git a/packages/stellate-plugin/src/stellate-plugin.ts b/packages/stellate-plugin/src/stellate-plugin.ts new file mode 100644 index 0000000000..a39cfb8700 --- /dev/null +++ b/packages/stellate-plugin/src/stellate-plugin.ts @@ -0,0 +1,288 @@ +import { Inject, OnApplicationBootstrap } from '@nestjs/common'; +import { ModuleRef } from '@nestjs/core'; +import { EventBus, Injector, PluginCommonModule, VendurePlugin } from '@vendure/core'; +import { buffer, debounceTime } from 'rxjs/operators'; + +import { shopApiExtensions } from './api/api-extensions'; +import { SearchResponseFieldResolver } from './api/search-response.resolver'; +import { STELLATE_PLUGIN_OPTIONS } from './constants'; +import { StellateService } from './service/stellate.service'; +import { StellatePluginOptions } from './types'; + +const StellateOptionsProvider = { + provide: STELLATE_PLUGIN_OPTIONS, + useFactory: () => StellatePlugin.options, +}; + +/** + * @description + * A plugin to integrate the [Stellate](https://stellate.co/) GraphQL caching service with your Vendure server. + * The main purpose of this plugin is to ensure that cached data gets correctly purged in + * response to events inside Vendure. For example, changes to a Product's description should + * purge any associated record for that Product in Stellate's cache. + * + * ## Pre-requisites + * + * You will first need to [set up a free Stellate account](https://stellate.co/signup). + * + * You will also need to generate an **API token** for the Stellate Purging API. For instructions on how to generate the token, + * see the [Stellate Purging API docs](https://docs.stellate.co/docs/purging-api#authentication). + * + * ## Installation + * + * ``` + * npm install \@vendure/stellate-plugin + * ``` + * + * ## Configuration + * + * The plugin is configured via the `StellatePlugin.init()` method. This method accepts an options object + * which defines the Stellate service name and API token, as well as an array of {@link PurgeRule}s which + * define how the plugin will respond to Vendure events in order to trigger calls to the + * Stellate [Purging API](https://stellate.co/docs/graphql-edge-cache/purging-api). + * + * @example + * ```ts + * import { StellatePlugin, defaultPurgeRules } from '\@vendure/stellate-plugin'; + * import { VendureConfig } from '\@vendure/core'; + * + * export const config: VendureConfig = { + * // ... + * plugins: [ + * StellatePlugin.init({ + * // The Stellate service name, i.e. `.stellate.sh` + * serviceName: 'my-service', + * // The API token for the Stellate Purging API. See the "pre-requisites" section above. + * apiToken: process.env.STELLATE_PURGE_API_TOKEN, + * debugMode: !isProd || process.env.STELLATE_DEBUG_MODE ? true : false, + * debugLogging: process.env.STELLATE_DEBUG_MODE ? true : false, + * purgeRules: [ + * ...defaultPurgeRules, + * // custom purge rules can be added here + * ], + * }), + * ], + * }; + * ``` + * + * In your Stellate dashboard, you can use the following configuration example as a sensible default for a + * Vendure application: + * + * @example + * ```ts + * import { Config } from "stellate"; + * + * const config: Config = { + * config: { + * name: "my-vendure-server", + * originUrl: "https://my-vendure-server.com/shop-api", + * ignoreOriginCacheControl: true, + * passThroughOnly: false, + * scopes: { + * SESSION_BOUND: "header:authorization|cookie:session", + * }, + * headers: { + * "access-control-expose-headers": "vendure-auth-token", + * }, + * rootTypeNames: { + * query: "Query", + * mutation: "Mutation", + * }, + * keyFields: { + * types: { + * SearchResult: ["productId"], + * SearchResponseCacheIdentifier: ["collectionSlug"], + * }, + * }, + * rules: [ + * { + * types: [ + * "Product", + * "Collection", + * "ProductVariant", + * "SearchResponse", + * ], + * maxAge: 900, + * swr: 900, + * description: "Cache Products & Collections", + * }, + * { + * types: ["Channel"], + * maxAge: 9000, + * swr: 9000, + * description: "Cache active channel", + * }, + * { + * types: ["Order", "Customer", "User"], + * maxAge: 0, + * swr: 0, + * description: "Do not cache user data", + * }, + * ], + * }, + * }; + * export default config; + * ``` + * + * ## Storefront setup + * + * In your storefront, you should point your GraphQL client to the Stellate GraphQL API endpoint, which is + * `https://.stellate.sh`. + * + * Wherever you are using the `search` query (typically in product listing & search pages), you should also add the + * `cacheIdentifier` field to the query. This will ensure that the Stellate cache is correctly purged when + * a Product or Collection is updated. + * + * @example + * ```ts + * import { graphql } from '../generated/gql'; + * + * export const searchProductsDocument = graphql(` + * query SearchProducts($input: SearchInput!) { + * search(input: $input) { + * // highlight-start + * cacheIdentifier { + * collectionSlug + * } + * // highlight-end + * items { + * # ... + * } + * } + * } + * }`); + * ``` + * + * ## Custom PurgeRules + * + * The configuration above only accounts for caching of some of the built-in Vendure entity types. If you have + * custom entity types, you may well want to add them to the Stellate cache. In this case, you'll also need a way to + * purge those entities from the cache when they are updated. This is where the {@link PurgeRule} comes in. + * + * Let's imagine that you have built a simple CMS plugin for Vendure which exposes an `Article` entity in your Shop API, and + * you have added this to your Stellate configuration: + * + * @example + * ```ts + * import { Config } from "stellate"; + * + * const config: Config = { + * config: { + * // ... + * rules: [ + * // ... + * { + * types: ["Article"], + * maxAge: 900, + * swr: 900, + * description: "Cache Articles", + * }, + * ], + * }, + * // ... + * }; + * export default config; + * ``` + * + * You can then add a custom {@link PurgeRule} to the StellatePlugin configuration: + * + * @example + * ```ts + * import { StellatePlugin, defaultPurgeRules } from "\@vendure/stellate-plugin"; + * import { VendureConfig } from "\@vendure/core"; + * import { ArticleEvent } from "./plugins/cms/events/article-event"; + * + * export const config: VendureConfig = { + * // ... + * plugins: [ + * StellatePlugin.init({ + * // ... + * purgeRules: [ + * ...defaultPurgeRules, + * new PurgeRule({ + * eventType: ArticleEvent, + * handler: async ({ events, stellateService }) => { + * const articleIds = events.map((e) => e.article.id); + * stellateService.purge("Article", articleIds); + * }, + * }), + * ], + * }), + * ], + * }; + * ``` + * + * ## DevMode & Debug Logging + * + * In development, you can set `devMode: true`, which will prevent any calls being made to the Stellate Purging API. + * + * If you want to log the calls that _would_ be made to the Stellate Purge API when in devMode, you can set `debugLogging: true`. + * Note that debugLogging generates a lot of debug-level logging, so it is recommended to only enable this when needed. + * + * @example + * ```ts + * import { StellatePlugin, defaultPurgeRules } from '\@vendure/stellate-plugin'; + * import { VendureConfig } from '\@vendure/core'; + * + * export const config: VendureConfig = { + * // ... + * plugins: [ + * StellatePlugin.init({ + * // ... + * devMode: !process.env.PRODUCTION, + * debugLogging: process.env.STELLATE_DEBUG_MODE ? true : false, + * purgeRules: [ + * ...defaultPurgeRules, + * ], + * }), + * ], + * }; + * ``` + * + * + * @since 2.1.5 + * @docsCategory core plugins/StellatePlugin + */ +@VendurePlugin({ + imports: [PluginCommonModule], + providers: [StellateOptionsProvider, StellateService], + shopApiExtensions: { + schema: shopApiExtensions, + resolvers: [SearchResponseFieldResolver], + }, + compatibility: '^2.0.0', +}) +export class StellatePlugin implements OnApplicationBootstrap { + static options: StellatePluginOptions; + + static init(options: StellatePluginOptions) { + this.options = options; + return this; + } + + constructor( + @Inject(STELLATE_PLUGIN_OPTIONS) private options: StellatePluginOptions, + private eventBus: EventBus, + private stellateService: StellateService, + private moduleRef: ModuleRef, + ) {} + + onApplicationBootstrap() { + const injector = new Injector(this.moduleRef); + + for (const purgeRule of this.options.purgeRules ?? []) { + const source$ = this.eventBus.ofType(purgeRule.eventType); + source$ + .pipe( + buffer( + source$.pipe( + debounceTime(purgeRule.bufferTimeMs ?? this.options.defaultBufferTimeMs ?? 2000), + ), + ), + ) + .subscribe(events => + purgeRule.handle({ events, injector, stellateService: this.stellateService }), + ); + } + } +} diff --git a/packages/stellate-plugin/src/types.ts b/packages/stellate-plugin/src/types.ts new file mode 100644 index 0000000000..e2169ad358 --- /dev/null +++ b/packages/stellate-plugin/src/types.ts @@ -0,0 +1,52 @@ +import { PurgeRule } from './purge-rule'; + +/** + * @description + * Configuration options for the StellatePlugin. + * + * @docsCategory core plugins/StellatePlugin + */ +export interface StellatePluginOptions { + /** + * @description + * The Stellate service name, i.e. `.stellate.sh` + */ + serviceName: string; + /** + * @description + * The Stellate Purging API token. For instructions on how to generate the token, + * see the [Stellate docs](https://docs.stellate.co/docs/purging-api#authentication) + */ + apiToken: string; + /** + * @description + * An array of {@link PurgeRule} instances which are used to define how the plugin will + * respond to Vendure events in order to trigger calls to the Stellate Purging API. + */ + purgeRules: PurgeRule[]; + /** + * @description + * When events are published, the PurgeRules will buffer those events in order to efficiently + * batch requests to the Stellate Purging API. You may wish to change the default, e.g. if you are + * running in a serverless environment and cannot introduce pauses after the main request has completed. + * + * @default 2000 + */ + defaultBufferTimeMs?: number; + /** + * @description + * When set to `true`, calls will not be made to the Stellate Purge API. + * + * @default false + */ + devMode?: boolean; + /** + * @description + * If set to true, the plugin will log the calls that would be made + * to the Stellate Purge API. Note, this generates a + * lot of debug-level logging. + * + * @default false + */ + debugLogging?: boolean; +} diff --git a/packages/stellate-plugin/src/write.mjs b/packages/stellate-plugin/src/write.mjs new file mode 100644 index 0000000000..944ff0f69b --- /dev/null +++ b/packages/stellate-plugin/src/write.mjs @@ -0,0 +1,210 @@ +import fetch from 'node-fetch' +import { getIntrospectionQuery } from 'graphql' + +// TODO: you can get a token by means of using settings --> api-tokens +const token = ''; +const apiUrl = 'https://api.graphcdn.io/api' +const originUrl = 'https://trygql.formidable.dev/graphql/basic-pokedex' + +// The app { config { input } } can be used to write the graphcdn.YAML file with help of libs like "@atomist/yaml-updater" +async function listOrganizations() { + const response = await fetch(apiUrl, { + method: 'POST', + headers: { + 'Content-Type': 'application/json', + 'graphcdn-token': token, + }, + body: JSON.stringify({ + query: /* GraphQL */ ` + query { + user { + organizations { + id + name + slug + } + } + } + `, + }) + }) + + const result = await response.json() + + return result.data.user.organizations +} + +async function pushAppConfig(orgId, schema) { + const config = { + name: 'node-write-test', + originUrl, + schema: originUrl, + queryDepthLimit: 20, + ignoreOriginCacheControl: true, + enablePlayground: true, + injectHeaders: true, + headers: { + 'something-to-inject': '1', + }, + keyFields: { + types: { + Pokemon: ['id', 'name'] + } + }, + scopes: { + AUTHENTICATED: 'header:Authorization', + }, + rootTypeNames: { query: 'Query' }, + rules: [ + { description: 'Cache all queries', maxAge: 600, swr: 900, scope: 'AUTHENTICATED', types: ['Query'] }, + ], + bypassCacheHeaders: [{ name: 'x-preview-token' }], + } + + const result = await fetch(apiUrl, { + method: 'POST', + headers: { + 'Content-Type': 'application/json', + 'graphcdn-token': token, + }, + body: JSON.stringify({ + query: /* GraphQL */ ` + mutation ( + $input: Input! + $appName: String! + $schema: IntrospectionQuery + ) { + pushAppConfig( + input: $input + appName: $appName + schema: $schema + allowDeletion: true + ) + } + `, + variables: { + input: config, + schema: schema, + appName: config.name, + } + }) + }) +} + +async function createApp(orgId, schema) { + const config = { + name: 'node-write-test', + originUrl, + schema: originUrl, + queryDepthLimit: 20, + ignoreOriginCacheControl: true, + enablePlayground: true, + injectHeaders: true, + headers: { + 'something-to-inject': '1', + }, + keyFields: { + types: { + Pokemon: ['id', 'name'] + } + }, + scopes: { + AUTHENTICATED: 'header:Authorization', + }, + rootTypeNames: { query: 'Query' }, + rules: [ + { description: 'Cache all queries', maxAge: 600, swr: 900, scope: 'AUTHENTICATED', types: ['Query'] }, + ], + bypassCacheHeaders: [{ name: 'x-preview-token' }], + } + + const result = await fetch(apiUrl, { + method: 'POST', + headers: { + 'Content-Type': 'application/json', + 'graphcdn-token': token, + }, + body: JSON.stringify({ + query: /* GraphQL */ ` + mutation ( + $input: Input! + $schema: IntrospectionQuery + $organizationId: String! + ) { + createAppCli( + input: $input + schema: $schema + organizationId: $organizationId + ) { + id + config { + input + } + } + } + `, + variables: { + input: config, + schema: schema, + organizationId: orgId + } + }) + }) + + return await result.json() +} + +async function getServices(slug) { + const response = await fetch(apiUrl, { + method: 'POST', + headers: { + 'Content-Type': 'application/json', + 'graphcdn-token': token, + }, + body: JSON.stringify({ + query: /* GraphQL */` + query ($slug: String!) { + organization(slug: $slug) { + name + apps { + name + updatedAt + config { + input + } + } + } + } + `, + variables: { slug } + }) + }) + + const result = await response.json() + + return result.data.organization.apps +} + +async function main() { + const introspectionQuery = getIntrospectionQuery() + + const introspectionResponse = await fetch(originUrl, { + method: 'POST', + headers: { + 'Content-Type': 'application/json', + }, + body: JSON.stringify({ + query: introspectionQuery, + }) + }) + + const { data: schema } = await introspectionResponse.json() + + const organizations = await listOrganizations(); + + console.log(organizations) + const result = await createApp(organizations[0].id, schema) + console.log(result) +} + +main() diff --git a/packages/stellate-plugin/tsconfig.build.json b/packages/stellate-plugin/tsconfig.build.json new file mode 100644 index 0000000000..821b637ae7 --- /dev/null +++ b/packages/stellate-plugin/tsconfig.build.json @@ -0,0 +1,9 @@ +{ + "extends": "./tsconfig.json", + "compilerOptions": { + "outDir": "./lib" + }, + "files": [ + "./index.ts" + ] +} diff --git a/packages/stellate-plugin/tsconfig.json b/packages/stellate-plugin/tsconfig.json new file mode 100644 index 0000000000..f343c8c4b3 --- /dev/null +++ b/packages/stellate-plugin/tsconfig.json @@ -0,0 +1,10 @@ +{ + "extends": "../../tsconfig.json", + "compilerOptions": { + "declaration": true, + "removeComments": false, + "noLib": false, + "skipLibCheck": true, + "sourceMap": true + } +} diff --git a/scripts/changelogs/generate-changelog.ts b/scripts/changelogs/generate-changelog.ts index 5c5e0b901d..a257d31230 100644 --- a/scripts/changelogs/generate-changelog.ts +++ b/scripts/changelogs/generate-changelog.ts @@ -35,6 +35,7 @@ const VALID_SCOPES: string[] = [ 'testing', 'ui-devkit', 'harden-plugin', + 'stellate-plugin', ]; const mainTemplate = fs.readFileSync(path.join(__dirname, 'template.hbs'), 'utf-8'); diff --git a/scripts/docs/generate-typescript-docs.ts b/scripts/docs/generate-typescript-docs.ts index 071fb04313..2de072e5c4 100644 --- a/scripts/docs/generate-typescript-docs.ts +++ b/scripts/docs/generate-typescript-docs.ts @@ -49,6 +49,10 @@ const sections: DocsSectionConfig[] = [ sourceDirs: ['packages/harden-plugin/src/'], outputPath: '', }, + { + sourceDirs: ['packages/stellate-plugin/src/'], + outputPath: '', + }, { sourceDirs: ['packages/admin-ui/src/lib/', 'packages/ui-devkit/src/'], exclude: [/generated-types/], From cde0a46a8da6e2c1fb103cab24fb45bcb5493eca Mon Sep 17 00:00:00 2001 From: Michael Bromley Date: Tue, 5 Dec 2023 14:29:35 +0100 Subject: [PATCH 09/22] chore(sentry-plugin): Add sentry plugin --- packages/sentry-plugin/.gitignore | 3 + packages/sentry-plugin/README.md | 7 + packages/sentry-plugin/index.ts | 4 + packages/sentry-plugin/package.json | 28 ++++ .../src/api/admin-test.resolver.ts | 32 ++++ .../sentry-plugin/src/api/api-extensions.ts | 14 ++ .../src/api/error-test.service.ts | 11 ++ packages/sentry-plugin/src/constants.ts | 3 + .../sentry-plugin/src/sentry-apollo-plugin.ts | 58 +++++++ .../src/sentry-context.middleware.ts | 25 +++ packages/sentry-plugin/src/sentry-plugin.ts | 144 ++++++++++++++++++ packages/sentry-plugin/src/sentry.filter.ts | 27 ++++ packages/sentry-plugin/src/sentry.service.ts | 40 +++++ packages/sentry-plugin/src/types.ts | 26 ++++ packages/sentry-plugin/tsconfig.build.json | 9 ++ packages/sentry-plugin/tsconfig.json | 10 ++ scripts/changelogs/generate-changelog.ts | 1 + scripts/docs/generate-typescript-docs.ts | 4 + yarn.lock | 40 +++++ 19 files changed, 486 insertions(+) create mode 100644 packages/sentry-plugin/.gitignore create mode 100644 packages/sentry-plugin/README.md create mode 100644 packages/sentry-plugin/index.ts create mode 100644 packages/sentry-plugin/package.json create mode 100644 packages/sentry-plugin/src/api/admin-test.resolver.ts create mode 100644 packages/sentry-plugin/src/api/api-extensions.ts create mode 100644 packages/sentry-plugin/src/api/error-test.service.ts create mode 100644 packages/sentry-plugin/src/constants.ts create mode 100644 packages/sentry-plugin/src/sentry-apollo-plugin.ts create mode 100644 packages/sentry-plugin/src/sentry-context.middleware.ts create mode 100644 packages/sentry-plugin/src/sentry-plugin.ts create mode 100644 packages/sentry-plugin/src/sentry.filter.ts create mode 100644 packages/sentry-plugin/src/sentry.service.ts create mode 100644 packages/sentry-plugin/src/types.ts create mode 100644 packages/sentry-plugin/tsconfig.build.json create mode 100644 packages/sentry-plugin/tsconfig.json diff --git a/packages/sentry-plugin/.gitignore b/packages/sentry-plugin/.gitignore new file mode 100644 index 0000000000..368c3fdfbe --- /dev/null +++ b/packages/sentry-plugin/.gitignore @@ -0,0 +1,3 @@ +yarn-error.log +lib +e2e/__data__/*.sqlite diff --git a/packages/sentry-plugin/README.md b/packages/sentry-plugin/README.md new file mode 100644 index 0000000000..ed8d95ed83 --- /dev/null +++ b/packages/sentry-plugin/README.md @@ -0,0 +1,7 @@ +# Vendure Sentry Plugin + +Integrates your Vendure server with the [Sentry](https://sentry.io/) application monitoring service. + +`npm install @vendure/sentry-plugin` + +For documentation, see [docs.vendure.io/typescript-api/core-plugins/sentry-plugin/](https://docs.vendure.io/typescript-api/core-plugins/sentry-plugin/) diff --git a/packages/sentry-plugin/index.ts b/packages/sentry-plugin/index.ts new file mode 100644 index 0000000000..fca085aec0 --- /dev/null +++ b/packages/sentry-plugin/index.ts @@ -0,0 +1,4 @@ +export * from './src/sentry-plugin'; +export * from './src/sentry.service'; +export * from './src/types'; +export * from './src/constants'; diff --git a/packages/sentry-plugin/package.json b/packages/sentry-plugin/package.json new file mode 100644 index 0000000000..38d0b17e99 --- /dev/null +++ b/packages/sentry-plugin/package.json @@ -0,0 +1,28 @@ +{ + "name": "@vendure/sentry-plugin", + "version": "2.1.4", + "license": "MIT", + "main": "lib/index.js", + "types": "lib/index.d.ts", + "files": [ + "lib/**/*" + ], + "scripts": { + "watch": "tsc -p ./tsconfig.build.json --watch", + "build": "rimraf lib && tsc -p ./tsconfig.build.json", + "lint": "eslint --fix ." + }, + "homepage": "https://www.vendure.io", + "funding": "https://github.com/sponsors/michaelbromley", + "publishConfig": { + "access": "public" + }, + "peerDependencies": { + "@sentry/node": "^7.85.0" + }, + "devDependencies": { + "@vendure/common": "^2.1.4", + "@vendure/core": "^2.1.4", + "@sentry/node": "^7.85.0" + } +} diff --git a/packages/sentry-plugin/src/api/admin-test.resolver.ts b/packages/sentry-plugin/src/api/admin-test.resolver.ts new file mode 100644 index 0000000000..937f5c1c65 --- /dev/null +++ b/packages/sentry-plugin/src/api/admin-test.resolver.ts @@ -0,0 +1,32 @@ +import { Args, Mutation, Resolver } from '@nestjs/graphql'; +import { Allow, Permission, UserInputError } from '@vendure/core'; + +import { SentryService } from '../sentry.service'; +import { ErrorTestService } from './error-test.service'; + +declare const a: number; + +@Resolver() +export class SentryAdminTestResolver { + constructor(private sentryService: SentryService, private errorTestService: ErrorTestService) {} + + @Allow(Permission.SuperAdmin) + @Mutation() + async createTestError(@Args() args: { errorType: string }) { + switch (args.errorType) { + case 'UNCAUGHT_ERROR': + return a / 10; + case 'THROWN_ERROR': + throw new UserInputError('SentryPlugin Test Error'); + case 'CAPTURED_ERROR': + this.sentryService.captureException(new Error('SentryPlugin Direct error')); + return true; + case 'CAPTURED_MESSAGE': + this.sentryService.captureMessage('Captured message'); + return true; + case 'DATABASE_ERROR': + await this.errorTestService.createDatabaseError(); + return true; + } + } +} diff --git a/packages/sentry-plugin/src/api/api-extensions.ts b/packages/sentry-plugin/src/api/api-extensions.ts new file mode 100644 index 0000000000..6633714597 --- /dev/null +++ b/packages/sentry-plugin/src/api/api-extensions.ts @@ -0,0 +1,14 @@ +import gql from 'graphql-tag'; + +export const testApiExtensions = gql` + enum TestErrorType { + UNCAUGHT_ERROR + THROWN_ERROR + CAPTURED_ERROR + CAPTURED_MESSAGE + DATABASE_ERROR + } + extend type Mutation { + createTestError(errorType: TestErrorType!): Boolean + } +`; diff --git a/packages/sentry-plugin/src/api/error-test.service.ts b/packages/sentry-plugin/src/api/error-test.service.ts new file mode 100644 index 0000000000..4a1f2df346 --- /dev/null +++ b/packages/sentry-plugin/src/api/error-test.service.ts @@ -0,0 +1,11 @@ +import { Injectable } from '@nestjs/common'; +import { TransactionalConnection } from '@vendure/core'; + +@Injectable() +export class ErrorTestService { + constructor(private connection: TransactionalConnection) {} + + createDatabaseError() { + return this.connection.rawConnection.query('SELECT * FROM non_existent_table'); + } +} diff --git a/packages/sentry-plugin/src/constants.ts b/packages/sentry-plugin/src/constants.ts new file mode 100644 index 0000000000..baa4e64601 --- /dev/null +++ b/packages/sentry-plugin/src/constants.ts @@ -0,0 +1,3 @@ +export const SENTRY_PLUGIN_OPTIONS = 'SENTRY_PLUGIN_OPTIONS'; +export const SENTRY_TRANSACTION_KEY = 'SENTRY_PLUGIN_TRANSACTION'; +export const loggerCtx = 'SentryPlugin'; diff --git a/packages/sentry-plugin/src/sentry-apollo-plugin.ts b/packages/sentry-plugin/src/sentry-apollo-plugin.ts new file mode 100644 index 0000000000..59deaf9fa3 --- /dev/null +++ b/packages/sentry-plugin/src/sentry-apollo-plugin.ts @@ -0,0 +1,58 @@ +/* eslint-disable @typescript-eslint/require-await */ +import { + ApolloServerPlugin, + GraphQLRequestListener, + GraphQLRequestContext, + GraphQLRequestContextDidEncounterErrors, +} from '@apollo/server'; +import { Transaction, setContext } from '@sentry/node'; + +import { SENTRY_TRANSACTION_KEY } from './constants'; + +/** + * Based on https://github.com/ntegral/nestjs-sentry/issues/97#issuecomment-1252446807 + */ +export class SentryApolloPlugin implements ApolloServerPlugin { + constructor(private options: { enableTracing: boolean }) {} + + async requestDidStart({ + request, + contextValue, + }: GraphQLRequestContext): Promise> { + const { enableTracing } = this.options; + const transaction: Transaction | undefined = contextValue.req[SENTRY_TRANSACTION_KEY]; + if (request.operationName) { + if (enableTracing) { + // set the transaction Name if we have named queries + transaction?.setName(request.operationName); + } + setContext('Graphql Request', { + operation_name: request.operationName, + variables: request.variables, + }); + } + + return { + // hook for transaction finished + async willSendResponse(context) { + transaction?.finish(); + }, + async executionDidStart() { + return { + // hook for each new resolver + willResolveField({ info }) { + if (enableTracing) { + const span = transaction?.startChild({ + op: 'resolver', + description: `${info.parentType.name}.${info.fieldName}`, + }); + return () => { + span?.finish(); + }; + } + }, + }; + }, + }; + } +} diff --git a/packages/sentry-plugin/src/sentry-context.middleware.ts b/packages/sentry-plugin/src/sentry-context.middleware.ts new file mode 100644 index 0000000000..21cb0b75ab --- /dev/null +++ b/packages/sentry-plugin/src/sentry-context.middleware.ts @@ -0,0 +1,25 @@ +import { Inject, Injectable, NestMiddleware } from '@nestjs/common'; +import { Request, Response, NextFunction } from 'express'; + +import { SENTRY_PLUGIN_OPTIONS, SENTRY_TRANSACTION_KEY } from './constants'; +import { SentryService } from './sentry.service'; +import { SentryPluginOptions } from './types'; + +@Injectable() +export class SentryContextMiddleware implements NestMiddleware { + constructor( + @Inject(SENTRY_PLUGIN_OPTIONS) private options: SentryPluginOptions, + private sentryService: SentryService, + ) {} + + use(req: Request, res: Response, next: NextFunction) { + if (this.options.enableTracing) { + const transaction = this.sentryService.startTransaction({ + op: 'resolver', + name: `GraphQLTransaction`, + }); + req[SENTRY_TRANSACTION_KEY] = transaction; + } + next(); + } +} diff --git a/packages/sentry-plugin/src/sentry-plugin.ts b/packages/sentry-plugin/src/sentry-plugin.ts new file mode 100644 index 0000000000..24818eeec2 --- /dev/null +++ b/packages/sentry-plugin/src/sentry-plugin.ts @@ -0,0 +1,144 @@ +import { MiddlewareConsumer, NestModule } from '@nestjs/common'; +import { APP_FILTER } from '@nestjs/core'; +import { PluginCommonModule, VendurePlugin } from '@vendure/core'; + +import { SentryAdminTestResolver } from './api/admin-test.resolver'; +import { testApiExtensions } from './api/api-extensions'; +import { ErrorTestService } from './api/error-test.service'; +import { SENTRY_PLUGIN_OPTIONS } from './constants'; +import { SentryApolloPlugin } from './sentry-apollo-plugin'; +import { SentryContextMiddleware } from './sentry-context.middleware'; +import { SentryExceptionsFilter } from './sentry.filter'; +import { SentryService } from './sentry.service'; +import { SentryPluginOptions } from './types'; + +const SentryOptionsProvider = { + provide: SENTRY_PLUGIN_OPTIONS, + useFactory: () => SentryPlugin.options, +}; + +/** + * @description + * This plugin integrates the [Sentry](https://sentry.io) error tracking & performance monitoring + * service with your Vendure server. In addition to capturing errors, it also provides built-in + * support for [tracing](https://docs.sentry.io/product/sentry-basics/concepts/tracing/) as well as + * enriching your Sentry events with additional context about the request. + * + * ## Pre-requisites + * + * This plugin depends on access to Sentry, which can be self-hosted or used as a cloud service. + * + * If using the hosted SaaS option, you must have a Sentry account and a project set up ([sign up here](https://sentry.io/signup/)). When setting up your project, + * select the "Node.js" platform and no framework. + * + * Once set up, you will be given a [Data Source Name (DSN)](https://docs.sentry.io/product/sentry-basics/concepts/dsn-explainer/) + * which you will need to provide to the plugin. + * + * ## Installation + * + * Install this plugin as well as the `@sentry/node` package: + * + * ```sh + * npm install --save @vendure/sentry-plugin @sentry/node + * ``` + * + * ## Configuration + * + * Before using the plugin, you must configure it with the DSN provided by Sentry: + * + * ```ts + * import { VendureConfig } from '\@vendure/core'; + * import { SentryPlugin } from '\@vendure/sentry-plugin'; + * + * export const config: VendureConfig = { + * // ... + * plugins: [ + * // ... + * // highlight-start + * SentryPlugin.init({ + * dsn: process.env.SENTRY_DSN, + * // Optional configuration + * includeErrorTestMutation: true, + * enableTracing: true, + * // you can also pass in any of the options from @sentry/node + * }), + * // highlight-end + * ], + * }; + *``` + * + * ## Tracing + * + * This plugin includes built-in support for [tracing](https://docs.sentry.io/product/sentry-basics/concepts/tracing/), which allows you to see the performance of your + * GraphQL resolvers in the Sentry dashboard. To enable tracing, set the `enableTracing` option to `true` as shown above. + * + * ## Instrumenting your own code + * + * You may want to add your own custom spans to your code. To do so, you can use the `Sentry` object + * just as you would in any Node application. For example: + * + * ```ts + * import * as Sentry from "\@sentry/node"; + * + * export class MyService { + * async myMethod() { + * Sentry.setContext('My Custom Context,{ + * key: 'value', + * }); + * } + * } + * ``` + * + * ## Error test mutation + * + * To test whether your Sentry configuration is working correctly, you can set the `includeErrorTestMutation` option to `true`. This will add a mutation to the Admin API + * which will throw an error of the type specified in the `errorType` argument. For example: + * + * ```gql + * mutation CreateTestError { + * createTestError(errorType: DATABASE_ERROR) + * } + * ``` + * + * You should then be able to see the error in your Sentry dashboard (it may take a couple of minutes to appear). + * + * @docsCategory core plugins/SentryPlugin + */ +@VendurePlugin({ + imports: [PluginCommonModule], + providers: [ + SentryOptionsProvider, + SentryService, + ErrorTestService, + { + provide: APP_FILTER, + useClass: SentryExceptionsFilter, + }, + ], + configuration: config => { + config.apiOptions.apolloServerPlugins.push( + new SentryApolloPlugin({ + enableTracing: !!SentryPlugin.options.enableTracing, + }), + ); + return config; + }, + adminApiExtensions: { + schema: () => (SentryPlugin.options.includeErrorTestMutation ? testApiExtensions : undefined), + resolvers: () => (SentryPlugin.options.includeErrorTestMutation ? [SentryAdminTestResolver] : []), + }, + exports: [SentryService], + compatibility: '^2.0.0', +}) +export class SentryPlugin implements NestModule { + static options: SentryPluginOptions = {} as any; + + configure(consumer: MiddlewareConsumer): any { + consumer.apply(SentryContextMiddleware).forRoutes('*'); + } + + static init(options: SentryPluginOptions) { + this.options = options; + return this; + } +} diff --git a/packages/sentry-plugin/src/sentry.filter.ts b/packages/sentry-plugin/src/sentry.filter.ts new file mode 100644 index 0000000000..cd248842ec --- /dev/null +++ b/packages/sentry-plugin/src/sentry.filter.ts @@ -0,0 +1,27 @@ +import type { ArgumentsHost, ExceptionFilter } from '@nestjs/common'; +import { Catch, ExecutionContext } from '@nestjs/common'; +import { GqlContextType, GqlExecutionContext } from '@nestjs/graphql'; +import { setContext } from '@sentry/node'; + +import { SentryService } from './sentry.service'; + +@Catch() +export class SentryExceptionsFilter implements ExceptionFilter { + constructor(private readonly sentryService: SentryService) {} + + catch(exception: Error, host: ArgumentsHost): void { + if (host.getType() === 'graphql') { + const gqlContext = GqlExecutionContext.create(host as ExecutionContext); + const info = gqlContext.getInfo(); + setContext('GraphQL Error Context', { + fieldName: info.fieldName, + path: info.path, + }); + } + const variables = (exception as any).variables; + if (variables) { + setContext('GraphQL Error Variables', variables); + } + this.sentryService.captureException(exception); + } +} diff --git a/packages/sentry-plugin/src/sentry.service.ts b/packages/sentry-plugin/src/sentry.service.ts new file mode 100644 index 0000000000..307fc339d3 --- /dev/null +++ b/packages/sentry-plugin/src/sentry.service.ts @@ -0,0 +1,40 @@ +import { Inject, Injectable, OnApplicationBootstrap, OnApplicationShutdown } from '@nestjs/common'; +import * as Sentry from '@sentry/node'; +import { CaptureContext, TransactionContext } from '@sentry/types'; + +import { SENTRY_PLUGIN_OPTIONS } from './constants'; +import { SentryPluginOptions } from './types'; + +@Injectable() +export class SentryService implements OnApplicationBootstrap, OnApplicationShutdown { + constructor(@Inject(SENTRY_PLUGIN_OPTIONS) private options: SentryPluginOptions) {} + + onApplicationBootstrap(): any { + const integrations = this.options.integrations ?? [ + new Sentry.Integrations.Http({ tracing: true }), + ...Sentry.autoDiscoverNodePerformanceMonitoringIntegrations(), + ]; + Sentry.init({ + ...this.options, + tracesSampleRate: this.options.tracesSampleRate ?? 1.0, + integrations, + dsn: this.options.dsn, + }); + } + + onApplicationShutdown() { + return Sentry.close(); + } + + captureException(exception: Error) { + Sentry.captureException(exception); + } + + captureMessage(message: string, captureContext?: CaptureContext) { + Sentry.captureMessage(message, captureContext); + } + + startTransaction(context: TransactionContext) { + return Sentry.startTransaction(context); + } +} diff --git a/packages/sentry-plugin/src/types.ts b/packages/sentry-plugin/src/types.ts new file mode 100644 index 0000000000..b31eb99ba1 --- /dev/null +++ b/packages/sentry-plugin/src/types.ts @@ -0,0 +1,26 @@ +import { Transaction } from '@sentry/node'; +import { NodeOptions } from '@sentry/node/types/types'; + +import { SENTRY_TRANSACTION_KEY } from './constants'; + +/** + * @description + * Configuration options for the {@link SentryPlugin}. + * + * @docsCategory core plugins/SentryPlugin + */ +export interface SentryPluginOptions extends NodeOptions { + /** + * @description + * The [Data Source Name](https://docs.sentry.io/product/sentry-basics/concepts/dsn-explainer/) for your Sentry instance. + */ + dsn: string; + enableTracing?: boolean; + includeErrorTestMutation?: boolean; +} + +declare module 'express' { + interface Request { + [SENTRY_TRANSACTION_KEY]: Transaction | undefined; + } +} diff --git a/packages/sentry-plugin/tsconfig.build.json b/packages/sentry-plugin/tsconfig.build.json new file mode 100644 index 0000000000..821b637ae7 --- /dev/null +++ b/packages/sentry-plugin/tsconfig.build.json @@ -0,0 +1,9 @@ +{ + "extends": "./tsconfig.json", + "compilerOptions": { + "outDir": "./lib" + }, + "files": [ + "./index.ts" + ] +} diff --git a/packages/sentry-plugin/tsconfig.json b/packages/sentry-plugin/tsconfig.json new file mode 100644 index 0000000000..f343c8c4b3 --- /dev/null +++ b/packages/sentry-plugin/tsconfig.json @@ -0,0 +1,10 @@ +{ + "extends": "../../tsconfig.json", + "compilerOptions": { + "declaration": true, + "removeComments": false, + "noLib": false, + "skipLibCheck": true, + "sourceMap": true + } +} diff --git a/scripts/changelogs/generate-changelog.ts b/scripts/changelogs/generate-changelog.ts index a257d31230..b9dbbea933 100644 --- a/scripts/changelogs/generate-changelog.ts +++ b/scripts/changelogs/generate-changelog.ts @@ -36,6 +36,7 @@ const VALID_SCOPES: string[] = [ 'ui-devkit', 'harden-plugin', 'stellate-plugin', + 'sentry-plugin', ]; const mainTemplate = fs.readFileSync(path.join(__dirname, 'template.hbs'), 'utf-8'); diff --git a/scripts/docs/generate-typescript-docs.ts b/scripts/docs/generate-typescript-docs.ts index 2de072e5c4..5e3728522f 100644 --- a/scripts/docs/generate-typescript-docs.ts +++ b/scripts/docs/generate-typescript-docs.ts @@ -53,6 +53,10 @@ const sections: DocsSectionConfig[] = [ sourceDirs: ['packages/stellate-plugin/src/'], outputPath: '', }, + { + sourceDirs: ['packages/sentry-plugin/src/'], + outputPath: '', + }, { sourceDirs: ['packages/admin-ui/src/lib/', 'packages/ui-devkit/src/'], exclude: [/generated-types/], diff --git a/yarn.lock b/yarn.lock index 9e117b0907..2d285a9514 100644 --- a/yarn.lock +++ b/yarn.lock @@ -4335,6 +4335,46 @@ "@angular-devkit/schematics" "16.2.0" jsonc-parser "3.2.0" +"@sentry-internal/tracing@7.85.0": + version "7.85.0" + resolved "https://registry.yarnpkg.com/@sentry-internal/tracing/-/tracing-7.85.0.tgz#1b4781a61e1e43badeff826cf40abe33dd760f1d" + integrity sha512-p3YMUwkPCy2su9cm/3+7QYR4RiMI0+07DU1BZtht9NLTzY2O87/yvUbn1v2yHR3vJQTy/+7N0ud9/mPBFznRQQ== + dependencies: + "@sentry/core" "7.85.0" + "@sentry/types" "7.85.0" + "@sentry/utils" "7.85.0" + +"@sentry/core@7.85.0": + version "7.85.0" + resolved "https://registry.yarnpkg.com/@sentry/core/-/core-7.85.0.tgz#dd90d772a5f75ff674f931f59b22a3fc286d0983" + integrity sha512-DFDAc4tWmHN5IWhr7XbHCiyF1Xgb95jz8Uj/JTX9atlgodId1UIbER77qpEmH3eQGid/QBdqrlR98zCixgSbwg== + dependencies: + "@sentry/types" "7.85.0" + "@sentry/utils" "7.85.0" + +"@sentry/node@^7.85.0": + version "7.85.0" + resolved "https://registry.yarnpkg.com/@sentry/node/-/node-7.85.0.tgz#cf4e6022b5cd1f3fb007186c5e04427b108ebe1d" + integrity sha512-uiBtRW9G017NHoCXBlK3ttkTwHXLFyI8ndHpaObtyajKTv3ptGIThVEn7DuK7Pwor//RjwjSEEOa7WDK+FdMVQ== + dependencies: + "@sentry-internal/tracing" "7.85.0" + "@sentry/core" "7.85.0" + "@sentry/types" "7.85.0" + "@sentry/utils" "7.85.0" + https-proxy-agent "^5.0.0" + +"@sentry/types@7.85.0": + version "7.85.0" + resolved "https://registry.yarnpkg.com/@sentry/types/-/types-7.85.0.tgz#648488b90f958ca6a86922cc5d26004853410ba6" + integrity sha512-R5jR4XkK5tBU2jDiPdSVqzkmjYRr666bcGaFGUHB/xDQCjPsjk+pEmCCL+vpuWoaZmQJUE1hVU7rgnVX81w8zg== + +"@sentry/utils@7.85.0": + version "7.85.0" + resolved "https://registry.yarnpkg.com/@sentry/utils/-/utils-7.85.0.tgz#b84467fd07bc2ef09fdf382ddcdcdc3f5b0d78b0" + integrity sha512-JZ7seNOLvhjAQ8GeB3GYknPQJkuhF88xAYOaESZP3xPOWBMFUN+IO4RqjMqMLFDniOwsVQS7GB/MfP+hxufieg== + dependencies: + "@sentry/types" "7.85.0" + "@sigstore/bundle@^1.1.0": version "1.1.0" resolved "https://registry.yarnpkg.com/@sigstore/bundle/-/bundle-1.1.0.tgz#17f8d813b09348b16eeed66a8cf1c3d6bd3d04f1" From e3324b5e986fe4239a98612a8712791ad0b2b2ca Mon Sep 17 00:00:00 2001 From: Michael Bromley Date: Tue, 5 Dec 2023 14:37:55 +0100 Subject: [PATCH 10/22] docs(sentry-plugin): Add sentry plugin to docs sidebar --- docs/sidebars.js | 6 ++++++ packages/sentry-plugin/src/sentry-plugin.ts | 8 +++++--- 2 files changed, 11 insertions(+), 3 deletions(-) diff --git a/docs/sidebars.js b/docs/sidebars.js index 9cb905de02..4ee5440c58 100644 --- a/docs/sidebars.js +++ b/docs/sidebars.js @@ -267,6 +267,12 @@ const sidebars = { link: { type: 'doc', id: 'reference/core-plugins/payments-plugin/index' }, items: [{ type: 'autogenerated', dirName: 'reference/core-plugins/payments-plugin' }], }, + { + type: 'category', + label: 'SentryPlugin', + link: { type: 'doc', id: 'reference/core-plugins/sentry-plugin/index' }, + items: [{ type: 'autogenerated', dirName: 'reference/core-plugins/sentry-plugin' }], + }, { type: 'category', label: 'StellatePlugin', diff --git a/packages/sentry-plugin/src/sentry-plugin.ts b/packages/sentry-plugin/src/sentry-plugin.ts index 24818eeec2..8b92646074 100644 --- a/packages/sentry-plugin/src/sentry-plugin.ts +++ b/packages/sentry-plugin/src/sentry-plugin.ts @@ -39,7 +39,7 @@ const SentryOptionsProvider = { * Install this plugin as well as the `@sentry/node` package: * * ```sh - * npm install --save @vendure/sentry-plugin @sentry/node + * npm install --save \@vendure/sentry-plugin \@sentry/node * ``` * * ## Configuration @@ -60,7 +60,9 @@ const SentryOptionsProvider = { * // Optional configuration * includeErrorTestMutation: true, * enableTracing: true, - * // you can also pass in any of the options from @sentry/node + * // you can also pass in any of the options from \@sentry/node + * // for instance: + * tracesSampleRate: 1.0, * }), * // highlight-end * ], @@ -94,7 +96,7 @@ const SentryOptionsProvider = { * To test whether your Sentry configuration is working correctly, you can set the `includeErrorTestMutation` option to `true`. This will add a mutation to the Admin API * which will throw an error of the type specified in the `errorType` argument. For example: * - * ```gql + * ```graphql * mutation CreateTestError { * createTestError(errorType: DATABASE_ERROR) * } From 08797da003e293fbf2af1ba094eede5f8fa26fcb Mon Sep 17 00:00:00 2001 From: Michael Bromley Date: Tue, 5 Dec 2023 15:00:51 +0100 Subject: [PATCH 11/22] docs(sentry-plugin): Add missing sentry docs --- .../core-plugins/sentry-plugin/index.md | 131 ++++++++++++++++++ .../sentry-plugin/sentry-plugin-options.md | 48 +++++++ 2 files changed, 179 insertions(+) create mode 100644 docs/docs/reference/core-plugins/sentry-plugin/index.md create mode 100644 docs/docs/reference/core-plugins/sentry-plugin/sentry-plugin-options.md diff --git a/docs/docs/reference/core-plugins/sentry-plugin/index.md b/docs/docs/reference/core-plugins/sentry-plugin/index.md new file mode 100644 index 0000000000..f981d629ba --- /dev/null +++ b/docs/docs/reference/core-plugins/sentry-plugin/index.md @@ -0,0 +1,131 @@ +--- +title: "SentryPlugin" +isDefaultIndex: false +generated: true +--- + +import MemberInfo from '@site/src/components/MemberInfo'; +import GenerationInfo from '@site/src/components/GenerationInfo'; +import MemberDescription from '@site/src/components/MemberDescription'; + + +## SentryPlugin + + + +This plugin integrates the [Sentry](https://sentry.io) error tracking & performance monitoring +service with your Vendure server. In addition to capturing errors, it also provides built-in +support for [tracing](https://docs.sentry.io/product/sentry-basics/concepts/tracing/) as well as +enriching your Sentry events with additional context about the request. + +## Pre-requisites + +This plugin depends on access to Sentry, which can be self-hosted or used as a cloud service. + +If using the hosted SaaS option, you must have a Sentry account and a project set up ([sign up here](https://sentry.io/signup/)). When setting up your project, +select the "Node.js" platform and no framework. + +Once set up, you will be given a [Data Source Name (DSN)](https://docs.sentry.io/product/sentry-basics/concepts/dsn-explainer/) +which you will need to provide to the plugin. + +## Installation + +Install this plugin as well as the `@sentry/node` package: + +```sh +npm install --save @vendure/sentry-plugin @sentry/node +``` + +## Configuration + +Before using the plugin, you must configure it with the DSN provided by Sentry: + +```ts +import { VendureConfig } from '@vendure/core'; +import { SentryPlugin } from '@vendure/sentry-plugin'; + +export const config: VendureConfig = { + // ... + plugins: [ + // ... + // highlight-start + SentryPlugin.init({ + dsn: process.env.SENTRY_DSN, + // Optional configuration + includeErrorTestMutation: true, + enableTracing: true, + // you can also pass in any of the options from @sentry/node + // for instance: + tracesSampleRate: 1.0, + }), + // highlight-end + ], +}; +``` + +## Tracing + +This plugin includes built-in support for [tracing](https://docs.sentry.io/product/sentry-basics/concepts/tracing/), which allows you to see the performance of your +GraphQL resolvers in the Sentry dashboard. To enable tracing, set the `enableTracing` option to `true` as shown above. + +## Instrumenting your own code + +You may want to add your own custom spans to your code. To do so, you can use the `Sentry` object +just as you would in any Node application. For example: + +```ts +import * as Sentry from "@sentry/node"; + +export class MyService { + async myMethod() { + Sentry.setContext('My Custom Context,{ + key: 'value', + }); + } +} +``` + +## Error test mutation + +To test whether your Sentry configuration is working correctly, you can set the `includeErrorTestMutation` option to `true`. This will add a mutation to the Admin API +which will throw an error of the type specified in the `errorType` argument. For example: + +```graphql +mutation CreateTestError { + createTestError(errorType: DATABASE_ERROR) +} +``` + +You should then be able to see the error in your Sentry dashboard (it may take a couple of minutes to appear). + +```ts title="Signature" +class SentryPlugin implements NestModule { + static options: SentryPluginOptions = {} as any; + configure(consumer: MiddlewareConsumer) => any; + init(options: SentryPluginOptions) => ; +} +``` +* Implements: NestModule + + + +
+ +### options + +SentryPluginOptions`} /> + + +### configure + + any`} /> + + +### init + +SentryPluginOptions) => `} /> + + + + +
diff --git a/docs/docs/reference/core-plugins/sentry-plugin/sentry-plugin-options.md b/docs/docs/reference/core-plugins/sentry-plugin/sentry-plugin-options.md new file mode 100644 index 0000000000..6b46137edd --- /dev/null +++ b/docs/docs/reference/core-plugins/sentry-plugin/sentry-plugin-options.md @@ -0,0 +1,48 @@ +--- +title: "SentryPluginOptions" +isDefaultIndex: false +generated: true +--- + +import MemberInfo from '@site/src/components/MemberInfo'; +import GenerationInfo from '@site/src/components/GenerationInfo'; +import MemberDescription from '@site/src/components/MemberDescription'; + + +## SentryPluginOptions + + + +Configuration options for the SentryPlugin. + +```ts title="Signature" +interface SentryPluginOptions extends NodeOptions { + dsn: string; + enableTracing?: boolean; + includeErrorTestMutation?: boolean; +} +``` +* Extends: NodeOptions + + + +
+ +### dsn + + + +The [Data Source Name](https://docs.sentry.io/product/sentry-basics/concepts/dsn-explainer/) for your Sentry instance. +### enableTracing + + + + +### includeErrorTestMutation + + + + + + +
From 5e0d1f329a49ac6dee218661ed4d75ec86457c5f Mon Sep 17 00:00:00 2001 From: Michael Bromley Date: Tue, 5 Dec 2023 15:30:21 +0100 Subject: [PATCH 12/22] docs: Fix links to plugin docs in readmes --- packages/sentry-plugin/README.md | 2 +- packages/stellate-plugin/README.md | 2 +- 2 files changed, 2 insertions(+), 2 deletions(-) diff --git a/packages/sentry-plugin/README.md b/packages/sentry-plugin/README.md index ed8d95ed83..f2ec744784 100644 --- a/packages/sentry-plugin/README.md +++ b/packages/sentry-plugin/README.md @@ -4,4 +4,4 @@ Integrates your Vendure server with the [Sentry](https://sentry.io/) application `npm install @vendure/sentry-plugin` -For documentation, see [docs.vendure.io/typescript-api/core-plugins/sentry-plugin/](https://docs.vendure.io/typescript-api/core-plugins/sentry-plugin/) +For documentation, see [docs.vendure.io/reference/core-plugins/sentry-plugin/](https://docs.vendure.io/reference/core-plugins/sentry-plugin/) diff --git a/packages/stellate-plugin/README.md b/packages/stellate-plugin/README.md index bd4b67cdc7..f7ad80eac5 100644 --- a/packages/stellate-plugin/README.md +++ b/packages/stellate-plugin/README.md @@ -4,4 +4,4 @@ Integrates your Vendure server with the [Stellate](TaxRateEvent) GraphQL API cac `npm install @vendure/stellate-plugin` -For documentation, see [docs.vendure.io/typescript-api/core-plugins/stellate-plugin/](https://docs.vendure.io/typescript-api/core-plugins/stellate-plugin/) +For documentation, see [docs.vendure.io/reference/core-plugins/stellate-plugin/](https://docs.vendure.io/reference/core-plugins/stellate-plugin/) From 99e04d1d0b34379c7193cc48d222c4513bd9eff0 Mon Sep 17 00:00:00 2001 From: Jonas Osburg Date: Mon, 11 Dec 2023 11:49:18 +0100 Subject: [PATCH 13/22] fix(core): Fix loading multiple customField relations (#2566) fixes #2555 --- .../test-plugins/list-query-plugin.ts | 45 +++++++++++++++++-- .../core/e2e/list-query-builder.e2e-spec.ts | 42 +++++++++++++++++ .../list-query-builder/list-query-builder.ts | 13 ++++-- 3 files changed, 94 insertions(+), 6 deletions(-) diff --git a/packages/core/e2e/fixtures/test-plugins/list-query-plugin.ts b/packages/core/e2e/fixtures/test-plugins/list-query-plugin.ts index 36d4d29d93..b780f3dea6 100644 --- a/packages/core/e2e/fixtures/test-plugins/list-query-plugin.ts +++ b/packages/core/e2e/fixtures/test-plugins/list-query-plugin.ts @@ -39,9 +39,25 @@ export class CustomFieldRelationTestEntity extends VendureEntity { parent: Relation; } +@Entity() +export class CustomFieldOtherRelationTestEntity extends VendureEntity { + constructor(input: Partial) { + super(input); + } + + @Column() + data: string; + + @ManyToOne(() => TestEntity) + parent: Relation; +} + class TestEntityCustomFields { @OneToMany(() => CustomFieldRelationTestEntity, child => child.parent) relation: Relation; + + @OneToMany(() => CustomFieldOtherRelationTestEntity, child => child.parent) + otherRelation: Relation; } @Entity() @@ -143,7 +159,7 @@ export class TestEntityTranslation extends VendureEntity implements Translation< @ManyToOne(type => TestEntity, base => base.translations) base: TestEntity; - customFields: {}; + customFields: never; } @Entity() @@ -171,7 +187,12 @@ export class ListQueryResolver { return this.listQueryBuilder .build(TestEntity, args.options, { ctx, - relations: ['orderRelation', 'orderRelation.customer', 'customFields.relation'], + relations: [ + 'orderRelation', + 'orderRelation.customer', + 'customFields.relation', + 'customFields.otherRelation', + ], customPropertyMap: { customerLastName: 'orderRelation.customer.lastName', }, @@ -222,8 +243,14 @@ const apiExtensions = gql` data: String! } + type CustomFieldOtherRelationTestEntity implements Node { + id: ID! + data: String! + } + type TestEntityCustomFields { relation: [CustomFieldRelationTestEntity!]! + otherRelation: [CustomFieldOtherRelationTestEntity!]! } type TestEntity implements Node { @@ -272,7 +299,13 @@ const apiExtensions = gql` @VendurePlugin({ imports: [PluginCommonModule], - entities: [TestEntity, TestEntityPrice, TestEntityTranslation, CustomFieldRelationTestEntity], + entities: [ + TestEntity, + TestEntityPrice, + TestEntityTranslation, + CustomFieldRelationTestEntity, + CustomFieldOtherRelationTestEntity, + ], adminApiExtensions: { schema: apiExtensions, resolvers: [ListQueryResolver], @@ -409,6 +442,12 @@ export class ListQueryPlugin implements OnApplicationBootstrap { data: nestedContent.data, }), ); + await this.connection.getRepository(CustomFieldOtherRelationTestEntity).save( + new CustomFieldOtherRelationTestEntity({ + parent: testEntity, + data: nestedContent.data, + }), + ); } } } diff --git a/packages/core/e2e/list-query-builder.e2e-spec.ts b/packages/core/e2e/list-query-builder.e2e-spec.ts index 84b47bdf96..b4b228f7ef 100644 --- a/packages/core/e2e/list-query-builder.e2e-spec.ts +++ b/packages/core/e2e/list-query-builder.e2e-spec.ts @@ -1268,6 +1268,27 @@ describe('ListQueryBuilder', () => { }, ]); }); + + it('should resolve multiple relations in customFields successfully', async () => { + const { testEntities } = await shopClient.query(GET_LIST_WITH_MULTIPLE_CUSTOM_FIELD_RELATION, { + options: { + filter: { + label: { eq: 'A' }, + }, + }, + }); + + expect(testEntities.items).toEqual([ + { + id: 'T_1', + label: 'A', + customFields: { + relation: [{ id: 'T_1', data: 'A' }], + otherRelation: [{ id: 'T_1', data: 'A' }], + }, + }, + ]); + }); }); }); @@ -1351,3 +1372,24 @@ const GET_LIST_WITH_CUSTOM_FIELD_RELATION = gql` } } `; + +const GET_LIST_WITH_MULTIPLE_CUSTOM_FIELD_RELATION = gql` + query GetTestWithMultipleCustomFieldRelation($options: TestEntityListOptions) { + testEntities(options: $options) { + items { + id + label + customFields { + relation { + id + data + } + otherRelation { + id + data + } + } + } + } + } +`; diff --git a/packages/core/src/service/helpers/list-query-builder/list-query-builder.ts b/packages/core/src/service/helpers/list-query-builder/list-query-builder.ts index d060c1a68b..c44ba88e67 100644 --- a/packages/core/src/service/helpers/list-query-builder/list-query-builder.ts +++ b/packages/core/src/service/helpers/list-query-builder/list-query-builder.ts @@ -493,14 +493,21 @@ export class ListQueryBuilder implements OnApplicationBootstrap { loadEagerRelations: true, } as FindManyOptions) .then(results => - results.map(r => ({ relation: relationPaths[0] as keyof T, entity: r })), + results.map(r => ({ + relations: relationPaths[0].startsWith('customFields.') + ? relationPaths + : [relationPaths[0]], + entity: r, + })), ); }), ).then(all => all.flat()); for (const entry of entitiesIdsWithRelations) { const finalEntity = entityMap.get(entry.entity.id); - if (finalEntity) { - this.assignDeep(entry.relation, entry.entity, finalEntity); + for (const relation of entry.relations) { + if (finalEntity) { + this.assignDeep(relation, entry.entity, finalEntity); + } } } return Array.from(entityMap.values()); From e7bb52bd973df4da6f37588615f26f900431df3a Mon Sep 17 00:00:00 2001 From: Martijn Date: Mon, 11 Dec 2023 11:57:09 +0100 Subject: [PATCH 14/22] chore: Update pull_request_template.md (#2570) For some reason, emojis don't work from templates anymore, you have to use the unicode emojis. And, we definitely need emojis in the PR template --- .github/pull_request_template.md | 6 +++--- 1 file changed, 3 insertions(+), 3 deletions(-) diff --git a/.github/pull_request_template.md b/.github/pull_request_template.md index e72141f2b3..d9af20bc4e 100644 --- a/.github/pull_request_template.md +++ b/.github/pull_request_template.md @@ -12,11 +12,11 @@ You can add screenshots here if applicable. # Checklist -:pushpin: Always: +📌 Always: - [ ] I have set a clear title - [ ] My PR is small and contains a single feature - [ ] I have [checked my own PR](## "Fix typo's and remove unused or commented out code") -:zap: Most of the time: +👍 Most of the time: - [ ] I have added or updated test cases -- [ ] I have updated the README if needed \ No newline at end of file +- [ ] I have updated the README if needed From 40ac41608f4d95d30a3ba29048d23e6dcfae985d Mon Sep 17 00:00:00 2001 From: Chris <134155599+chrislaai@users.noreply.github.com> Date: Mon, 11 Dec 2023 04:58:50 -0600 Subject: [PATCH 15/22] chore: Update README.md (#2571) Now that we no longer have to do this, I get no errors on my first build. --- README.md | 3 --- 1 file changed, 3 deletions(-) diff --git a/README.md b/README.md index df96e100e4..bdff435dea 100644 --- a/README.md +++ b/README.md @@ -48,9 +48,6 @@ The root directory has a `package.json` which contains build-related dependencie * Generating TypeScript types from the GraphQL schema * Linting, formatting & testing tasks to run on git commit & push -> Note: -> When you do `yarn` for the first time, you will need to manually create the `package` folder under [/packages/admin-ui](/packages/admin-ui). - ### 2. Build all packages `yarn build` From ed80c6848e08922c368846dfdc9ef77aae984369 Mon Sep 17 00:00:00 2001 From: Martijn Date: Mon, 11 Dec 2023 12:06:01 +0100 Subject: [PATCH 16/22] fix(payments-plugin): Mollie - ignore completed state to prevent unneccesary error throwing (#2569) --- packages/payments-plugin/src/mollie/mollie.service.ts | 7 ++++++- 1 file changed, 6 insertions(+), 1 deletion(-) diff --git a/packages/payments-plugin/src/mollie/mollie.service.ts b/packages/payments-plugin/src/mollie/mollie.service.ts index 2c9aadb43f..dd7401b5ec 100644 --- a/packages/payments-plugin/src/mollie/mollie.service.ts +++ b/packages/payments-plugin/src/mollie/mollie.service.ts @@ -184,7 +184,7 @@ export class MollieService { orderInput.method = molliePaymentMethodCode as MollieClientMethod; } const mollieOrder = await mollieClient.orders.create(orderInput); - Logger.info(`Created Mollie order ${mollieOrder.id} for order ${order.code}`); + Logger.info(`Created Mollie order ${mollieOrder.id} for order ${order.code}`, loggerCtx); const url = mollieOrder.getCheckoutUrl(); if (!url) { throw Error('Unable to getCheckoutUrl() from Mollie order'); @@ -255,6 +255,11 @@ export class MollieService { if (order.state === 'PaymentAuthorized' && mollieOrder.status === OrderStatus.completed) { return this.settleExistingPayment(ctx, order, mollieOrder.id); } + if (autoCapture && mollieOrder.status === OrderStatus.completed) { + // When autocapture is enabled, we should not handle the completed status from Mollie, + // because the order will be transitioned to PaymentSettled during auto capture + return; + } // Any other combination of Mollie status and Vendure status indicates something is wrong. throw Error( `Unhandled incoming Mollie status '${mollieOrder.status}' for order ${order.code} with status '${order.state}'`, From 82ee9131e18edb83e88e8a216ab1e1a1df403396 Mon Sep 17 00:00:00 2001 From: Michael Bromley Date: Mon, 11 Dec 2023 11:55:55 +0100 Subject: [PATCH 17/22] docs: Fix `localeText` naming in custom fields docs Fixes #2553 --- .../developer-guide/custom-fields/index.md | 2 +- .../core-plugins/stellate-plugin/purge-rule.md | 18 +++++++++--------- .../default-form-config-hash.md | 18 +++++++++--------- .../custom-fields/custom-field-type.md | 2 +- packages/common/src/shared-types.ts | 2 +- 5 files changed, 21 insertions(+), 21 deletions(-) diff --git a/docs/docs/guides/developer-guide/custom-fields/index.md b/docs/docs/guides/developer-guide/custom-fields/index.md index 8d69678353..10589226a2 100644 --- a/docs/docs/guides/developer-guide/custom-fields/index.md +++ b/docs/docs/guides/developer-guide/custom-fields/index.md @@ -141,7 +141,7 @@ The following types are available for custom fields: | `string` | Short string data | url, label | | `localeString` | Localized short strings | localized url | | `text` | Long text data | extended product info, json config object | -| `localText` | Localized long text | localized extended product info | +| `localeText` | Localized long text | localized extended product info | | `int` | Integer | product weight, customer loyalty points, monetary values | | `float` | Floating point number | product review rating | | `boolean` | Boolean | isDownloadable flag on product | diff --git a/docs/docs/reference/core-plugins/stellate-plugin/purge-rule.md b/docs/docs/reference/core-plugins/stellate-plugin/purge-rule.md index ea3c4f2a8d..fd9168f88d 100644 --- a/docs/docs/reference/core-plugins/stellate-plugin/purge-rule.md +++ b/docs/docs/reference/core-plugins/stellate-plugin/purge-rule.md @@ -13,8 +13,8 @@ import MemberDescription from '@site/src/components/MemberDescription'; -Defines a rule that listens for a particular VendureEvent and uses that to -make calls to the [Stellate Purging API](https://docs.stellate.co/docs/purging-api) via +Defines a rule that listens for a particular VendureEvent and uses that to +make calls to the [Stellate Purging API](https://docs.stellate.co/docs/purging-api) via the provided StellateService instance. ```ts title="Signature" @@ -63,10 +63,10 @@ Configures a StellateService; injector: Injector; }) => void | Promise<void>`} /> +StellateService; injector: Injector; }) => void | Promise<void>`} /> -The function to invoke when the specified event is published. This function should use the +The function to invoke when the specified event is published. This function should use the StellateService instance to call the Stellate Purge API. diff --git a/docs/docs/reference/typescript-api/configurable-operation-def/default-form-config-hash.md b/docs/docs/reference/typescript-api/configurable-operation-def/default-form-config-hash.md index 501b33dbe0..abed9b463d 100644 --- a/docs/docs/reference/typescript-api/configurable-operation-def/default-form-config-hash.md +++ b/docs/docs/reference/typescript-api/configurable-operation-def/default-form-config-hash.md @@ -29,15 +29,15 @@ type DefaultFormConfigHash = { 'product-selector-form-input': Record; 'relation-form-input': Record; 'rich-text-form-input': Record; - 'select-form-input': { - options?: Array<{ value: string; label?: Array> }>; + 'select-form-input': { + options?: Array<{ value: string; label?: Array> }>; }; '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; } @@ -107,7 +107,7 @@ type DefaultFormConfigHash = { ### 'select-form-input' - + ### 'text-form-input' @@ -117,12 +117,12 @@ type DefaultFormConfigHash = { ### 'textarea-form-input' - + ### 'product-multi-form-input' - + ### 'combination-mode-form-input' diff --git a/docs/docs/reference/typescript-api/custom-fields/custom-field-type.md b/docs/docs/reference/typescript-api/custom-fields/custom-field-type.md index c3c0bf531a..4ab1958a77 100644 --- a/docs/docs/reference/typescript-api/custom-fields/custom-field-type.md +++ b/docs/docs/reference/typescript-api/custom-fields/custom-field-type.md @@ -21,7 +21,7 @@ Type | DB type | GraphQL type string | varchar | String localeString | varchar | String text | longtext(m), text(p,s) | String -localText | longtext(m), text(p,s) | String +localeText | longtext(m), text(p,s) | String int | int | Int float | double precision | Float boolean | tinyint (m), bool (p), boolean (s) | Boolean diff --git a/packages/common/src/shared-types.ts b/packages/common/src/shared-types.ts index 88a1f30729..7fc4ba866b 100644 --- a/packages/common/src/shared-types.ts +++ b/packages/common/src/shared-types.ts @@ -87,7 +87,7 @@ export type ID = string | number; * string | varchar | String * localeString | varchar | String * text | longtext(m), text(p,s) | String - * localText | longtext(m), text(p,s) | String + * localeText | longtext(m), text(p,s) | String * int | int | Int * float | double precision | Float * boolean | tinyint (m), bool (p), boolean (s) | Boolean From d09452e5c61aa1bb5cb460e3a0331b299a2bbc05 Mon Sep 17 00:00:00 2001 From: Michael Bromley Date: Mon, 11 Dec 2023 13:50:00 +0100 Subject: [PATCH 18/22] fix(core): Fix bug when instantiating entity from object with getter Relates to #2574 --- .../core/src/entity/base/base.entity.spec.ts | 69 +++++++++++++++++++ packages/core/src/entity/base/base.entity.ts | 10 ++- 2 files changed, 77 insertions(+), 2 deletions(-) create mode 100644 packages/core/src/entity/base/base.entity.spec.ts diff --git a/packages/core/src/entity/base/base.entity.spec.ts b/packages/core/src/entity/base/base.entity.spec.ts new file mode 100644 index 0000000000..b386f3b3bb --- /dev/null +++ b/packages/core/src/entity/base/base.entity.spec.ts @@ -0,0 +1,69 @@ +import { DeepPartial } from '@vendure/common/lib/shared-types'; +import { describe, expect, it } from 'vitest'; + +import { Calculated } from '../../common/index'; +import { CalculatedPropertySubscriber } from '../subscribers'; + +import { VendureEntity } from './base.entity'; + +class ChildEntity extends VendureEntity { + constructor(input?: DeepPartial) { + super(input); + } + + name: string; + + get nameLoud(): string { + return this.name.toUpperCase(); + } +} + +class ChildEntityWithCalculated extends VendureEntity { + constructor(input?: DeepPartial) { + super(input); + } + + name: string; + + @Calculated() + get nameLoudCalculated(): string { + return this.name.toUpperCase(); + } +} + +describe('VendureEntity', () => { + it('instantiating a child entity', () => { + const child = new ChildEntity({ + name: 'foo', + }); + + expect(child.name).toBe('foo'); + expect(child.nameLoud).toBe('FOO'); + }); + + it('instantiating from existing entity with getter', () => { + const child1 = new ChildEntity({ + name: 'foo', + }); + + const child2 = new ChildEntity(child1); + + expect(child2.name).toBe('foo'); + expect(child2.nameLoud).toBe('FOO'); + }); + + it('instantiating from existing entity with calculated getter', () => { + const calculatedPropertySubscriber = new CalculatedPropertySubscriber(); + const child1 = new ChildEntityWithCalculated({ + name: 'foo', + }); + + // This is what happens to entities after being loaded from the DB + calculatedPropertySubscriber.afterLoad(child1); + + const child2 = new ChildEntityWithCalculated(child1); + + expect(child2.name).toBe('foo'); + expect(child2.nameLoudCalculated).toBe('FOO'); + }); +}); diff --git a/packages/core/src/entity/base/base.entity.ts b/packages/core/src/entity/base/base.entity.ts index de6bb47f38..dbd8abfb48 100644 --- a/packages/core/src/entity/base/base.entity.ts +++ b/packages/core/src/entity/base/base.entity.ts @@ -13,8 +13,14 @@ import { PrimaryGeneratedId } from '../entity-id.decorator'; export abstract class VendureEntity { protected constructor(input?: DeepPartial) { if (input) { - for (const [key, value] of Object.entries(input)) { - (this as any)[key] = value; + for (const [key, descriptor] of Object.entries(Object.getOwnPropertyDescriptors(input))) { + if (descriptor.get && !descriptor.set) { + // A getter has been moved to the entity instance + // by the CalculatedPropertySubscriber + // and cannot be copied over to the new instance. + continue; + } + (this as any)[key] = descriptor.value; } } } From ee040326d1307e70fb41c48f5e7d0c1eceb3cf3d Mon Sep 17 00:00:00 2001 From: Michael Bromley Date: Mon, 11 Dec 2023 13:52:06 +0100 Subject: [PATCH 19/22] fix(core): OrderLineEvent includes ID of deleted OrderLine Fixes #2574 --- packages/core/src/service/services/order.service.ts | 6 ++++-- 1 file changed, 4 insertions(+), 2 deletions(-) diff --git a/packages/core/src/service/services/order.service.ts b/packages/core/src/service/services/order.service.ts index 715db9857b..0604c0feda 100644 --- a/packages/core/src/service/services/order.service.ts +++ b/packages/core/src/service/services/order.service.ts @@ -588,8 +588,9 @@ export class OrderService { let updatedOrderLines = [orderLine]; if (correctedQuantity === 0) { order.lines = order.lines.filter(l => !idsAreEqual(l.id, orderLine.id)); + const deletedOrderLine = new OrderLine(orderLine); await this.connection.getRepository(ctx, OrderLine).remove(orderLine); - this.eventBus.publish(new OrderLineEvent(ctx, order, orderLine, 'deleted')); + this.eventBus.publish(new OrderLineEvent(ctx, order, deletedOrderLine, 'deleted')); updatedOrderLines = []; } else { await this.orderModifier.updateOrderLineQuantity(ctx, orderLine, correctedQuantity, order); @@ -620,8 +621,9 @@ export class OrderService { const orderLine = this.getOrderLineOrThrow(order, orderLineId); order.lines = order.lines.filter(line => !idsAreEqual(line.id, orderLineId)); const updatedOrder = await this.applyPriceAdjustments(ctx, order); + const deletedOrderLine = new OrderLine(orderLine); await this.connection.getRepository(ctx, OrderLine).remove(orderLine); - this.eventBus.publish(new OrderLineEvent(ctx, order, orderLine, 'deleted')); + this.eventBus.publish(new OrderLineEvent(ctx, order, deletedOrderLine, 'deleted')); return updatedOrder; } From 52c084129f2b98bc00b44c267ce751ef4b569540 Mon Sep 17 00:00:00 2001 From: Michael Bromley Date: Mon, 11 Dec 2023 14:05:38 +0100 Subject: [PATCH 20/22] fix(core): Remove redundant constraint when creating allocations Fixes #2563. In an earlier version, before the concept of "allocations" was introduced, this method created "Sales" rather than "Allocations". As such, it had the constraint that the `order.active` must be `false` in order to create a Sale. Since we switched this to Allocations, this constraint no longer makes sense. For instance, a custom OrderProcess may introduce a new state before the order becomes inactive, yet which should allocated stock. --- packages/core/src/i18n/messages/en.json | 1 - packages/core/src/service/services/stock-movement.service.ts | 3 --- 2 files changed, 4 deletions(-) diff --git a/packages/core/src/i18n/messages/en.json b/packages/core/src/i18n/messages/en.json index dcab06c5b0..b8fb899e27 100644 --- a/packages/core/src/i18n/messages/en.json +++ b/packages/core/src/i18n/messages/en.json @@ -6,7 +6,6 @@ "cannot-delete-sole-superadmin": "The sole SuperAdmin cannot be deleted", "cannot-locate-customer-for-user": "Cannot locate a Customer for the user", "cannot-modify-role": "The role \"{ roleCode }\" cannot be modified", - "cannot-create-sales-for-active-order": "Cannot create a Sale for an Order which is still active", "cannot-move-collection-into-self": "Cannot move a Collection into itself", "cannot-transition-payment-from-to": "Cannot transition Payment from \"{ fromState }\" to \"{ toState }\"", "cannot-transition-refund-from-to": "Cannot transition Refund from \"{ fromState }\" to \"{ toState }\"", diff --git a/packages/core/src/service/services/stock-movement.service.ts b/packages/core/src/service/services/stock-movement.service.ts index e38fa9503d..f642286408 100644 --- a/packages/core/src/service/services/stock-movement.service.ts +++ b/packages/core/src/service/services/stock-movement.service.ts @@ -137,9 +137,6 @@ export class StockMovementService { * increased, indicating that this quantity of stock is allocated and cannot be sold. */ async createAllocationsForOrder(ctx: RequestContext, order: Order): Promise { - if (order.active !== false) { - throw new InternalServerError('error.cannot-create-allocations-for-active-order'); - } const lines = order.lines.map(orderLine => ({ orderLineId: orderLine.id, quantity: orderLine.quantity, From cc4826dfb7c1a2f4e6ed8daa13eb017090d8bd9a Mon Sep 17 00:00:00 2001 From: Martijn Date: Tue, 12 Dec 2023 08:52:35 +0100 Subject: [PATCH 21/22] fix(payments-plugin): Fix Mollie channel awareness (#2575) --- .../src/mollie/mollie.controller.ts | 20 +++++++++++++++---- .../src/mollie/mollie.service.ts | 9 ++++----- 2 files changed, 20 insertions(+), 9 deletions(-) diff --git a/packages/payments-plugin/src/mollie/mollie.controller.ts b/packages/payments-plugin/src/mollie/mollie.controller.ts index 42f57cc44a..be41ca78c5 100644 --- a/packages/payments-plugin/src/mollie/mollie.controller.ts +++ b/packages/payments-plugin/src/mollie/mollie.controller.ts @@ -1,17 +1,16 @@ import { Body, Controller, Param, Post } from '@nestjs/common'; -import { Ctx, Logger, RequestContext, Transaction } from '@vendure/core'; +import { Ctx, Logger, RequestContext, Transaction, ChannelService, LanguageCode } from '@vendure/core'; import { loggerCtx } from './constants'; import { MollieService } from './mollie.service'; @Controller('payments') export class MollieController { - constructor(private mollieService: MollieService) {} + constructor(private mollieService: MollieService, private channelService: ChannelService) {} @Post('mollie/:channelToken/:paymentMethodId') @Transaction() async webhook( - @Ctx() ctx: RequestContext, @Param('channelToken') channelToken: string, @Param('paymentMethodId') paymentMethodId: string, @Body() body: any, @@ -20,8 +19,10 @@ export class MollieController { return Logger.warn(' Ignoring incoming webhook, because it has no body.id.', loggerCtx); } try { + // We need to construct a RequestContext based on the channelToken, + // because this is an incoming webhook, not a graphql request with a valid Ctx + const ctx = await this.createContext(channelToken); await this.mollieService.handleMollieStatusUpdate(ctx, { - channelToken, paymentMethodId, orderId: body.id, }); @@ -34,4 +35,15 @@ export class MollieController { throw error; } } + + private async createContext(channelToken: string): Promise { + const channel = await this.channelService.getChannelFromToken(channelToken); + return new RequestContext({ + apiType: 'admin', + isAuthorized: true, + authorizedAsOwnerOnly: false, + channel, + languageCode: LanguageCode.en, + }); + } } diff --git a/packages/payments-plugin/src/mollie/mollie.service.ts b/packages/payments-plugin/src/mollie/mollie.service.ts index dd7401b5ec..31eeea9846 100644 --- a/packages/payments-plugin/src/mollie/mollie.service.ts +++ b/packages/payments-plugin/src/mollie/mollie.service.ts @@ -33,7 +33,6 @@ import { amountToCents, getLocale, toAmount, toMollieAddress, toMollieOrderLines import { MolliePluginOptions } from './mollie.plugin'; interface OrderStatusInput { - channelToken: string; paymentMethodId: string; orderId: string; } @@ -199,10 +198,10 @@ export class MollieService { */ async handleMollieStatusUpdate( ctx: RequestContext, - { channelToken, paymentMethodId, orderId }: OrderStatusInput, + { paymentMethodId, orderId }: OrderStatusInput, ): Promise { Logger.info( - `Received status update for channel ${channelToken} for Mollie order ${orderId}`, + `Received status update for channel ${ctx.channel.token} for Mollie order ${orderId}`, loggerCtx, ); const paymentMethod = await this.paymentMethodService.findOne(ctx, paymentMethodId); @@ -213,12 +212,12 @@ export class MollieService { const apiKey = paymentMethod.handler.args.find(a => a.name === 'apiKey')?.value; const autoCapture = paymentMethod.handler.args.find(a => a.name === 'autoCapture')?.value === 'true'; if (!apiKey) { - throw Error(`No apiKey found for payment ${paymentMethod.id} for channel ${channelToken}`); + throw Error(`No apiKey found for payment ${paymentMethod.id} for channel ${ctx.channel.token}`); } const client = createMollieClient({ apiKey }); const mollieOrder = await client.orders.get(orderId); Logger.info( - `Processing status '${mollieOrder.status}' for order ${mollieOrder.orderNumber} for channel ${channelToken} for Mollie order ${orderId}`, + `Processing status '${mollieOrder.status}' for order ${mollieOrder.orderNumber} for channel ${ctx.channel.token} for Mollie order ${orderId}`, loggerCtx, ); let order = await this.orderService.findOneByCode(ctx, mollieOrder.orderNumber, ['payments']); From e5ecd1fac86ed8e6f85c39a19b3aa7561dcff3b2 Mon Sep 17 00:00:00 2001 From: Michael Bromley Date: Thu, 14 Dec 2023 10:07:43 +0100 Subject: [PATCH 22/22] chore: Publish v2.1.5 --- CHANGELOG.md | 17 +++++++++++++++++ lerna.json | 2 +- packages/admin-ui-plugin/package.json | 6 +++--- packages/admin-ui/package-lock.json | 2 +- packages/admin-ui/package.json | 4 ++-- .../src/lib/core/src/common/version.ts | 2 +- packages/asset-server-plugin/package.json | 6 +++--- packages/cli/package.json | 4 ++-- packages/common/package.json | 2 +- packages/core/package.json | 4 ++-- packages/create/package.json | 6 +++--- packages/dev-server/package.json | 18 +++++++++--------- packages/elasticsearch-plugin/package.json | 6 +++--- packages/email-plugin/package.json | 6 +++--- packages/harden-plugin/package.json | 6 +++--- packages/job-queue-plugin/package.json | 6 +++--- packages/payments-plugin/package.json | 8 ++++---- packages/sentry-plugin/package.json | 8 ++++---- packages/stellate-plugin/package.json | 6 +++--- packages/testing/package.json | 6 +++--- packages/ui-devkit/package.json | 8 ++++---- 21 files changed, 75 insertions(+), 58 deletions(-) diff --git a/CHANGELOG.md b/CHANGELOG.md index 94035f362b..ca9c9a6f64 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -1,3 +1,20 @@ +## 2.1.5 (2023-12-14) + + +#### Fixes + +* **admin-ui** Fix display of asset detail focal point buttons ([1b58aa7](https://github.com/vendure-ecommerce/vendure/commit/1b58aa7)) +* **core** Export VendureEntityEvent abstract class from index (#2556) ([c46cf74](https://github.com/vendure-ecommerce/vendure/commit/c46cf74)), closes [#2556](https://github.com/vendure-ecommerce/vendure/issues/2556) +* **core** Fix bug when instantiating entity from object with getter ([d09452e](https://github.com/vendure-ecommerce/vendure/commit/d09452e)), closes [#2574](https://github.com/vendure-ecommerce/vendure/issues/2574) +* **core** Fix loading multiple customField relations (#2566) ([99e04d1](https://github.com/vendure-ecommerce/vendure/commit/99e04d1)), closes [#2566](https://github.com/vendure-ecommerce/vendure/issues/2566) [#2555](https://github.com/vendure-ecommerce/vendure/issues/2555) +* **core** OrderLineEvent includes ID of deleted OrderLine ([ee04032](https://github.com/vendure-ecommerce/vendure/commit/ee04032)), closes [#2574](https://github.com/vendure-ecommerce/vendure/issues/2574) +* **core** Remove redundant constraint when creating allocations ([52c0841](https://github.com/vendure-ecommerce/vendure/commit/52c0841)), closes [#2563](https://github.com/vendure-ecommerce/vendure/issues/2563) +* **core** Send the correct amount to `refundOrder` (#2559) ([b5a265f](https://github.com/vendure-ecommerce/vendure/commit/b5a265f)), closes [#2559](https://github.com/vendure-ecommerce/vendure/issues/2559) +* **elasticsearch-plugin** Fix type to allow the promise on custom mapping definition (#2562) ([8e9ee07](https://github.com/vendure-ecommerce/vendure/commit/8e9ee07)), closes [#2562](https://github.com/vendure-ecommerce/vendure/issues/2562) +* **payments-plugin** Fix Mollie channel awareness (#2575) ([cc4826d](https://github.com/vendure-ecommerce/vendure/commit/cc4826d)), closes [#2575](https://github.com/vendure-ecommerce/vendure/issues/2575) +* **payments-plugin** Mollie - ignore completed state to prevent unneccesary error throwing (#2569) ([ed80c68](https://github.com/vendure-ecommerce/vendure/commit/ed80c68)), closes [#2569](https://github.com/vendure-ecommerce/vendure/issues/2569) +* **stellate-plugin** Add stellate plugin ([2254576](https://github.com/vendure-ecommerce/vendure/commit/2254576)) + ## 2.1.4 (2023-11-24) diff --git a/lerna.json b/lerna.json index 41a9399532..79a432f809 100644 --- a/lerna.json +++ b/lerna.json @@ -1,6 +1,6 @@ { "packages": ["packages/*"], - "version": "2.1.4", + "version": "2.1.5", "npmClient": "yarn", "command": { "version": { diff --git a/packages/admin-ui-plugin/package.json b/packages/admin-ui-plugin/package.json index 5b9d054f11..56ee123946 100644 --- a/packages/admin-ui-plugin/package.json +++ b/packages/admin-ui-plugin/package.json @@ -1,6 +1,6 @@ { "name": "@vendure/admin-ui-plugin", - "version": "2.1.4", + "version": "2.1.5", "main": "lib/index.js", "types": "lib/index.d.ts", "files": [ @@ -21,8 +21,8 @@ "devDependencies": { "@types/express": "^4.17.8", "@types/fs-extra": "^9.0.1", - "@vendure/common": "^2.1.4", - "@vendure/core": "^2.1.4", + "@vendure/common": "^2.1.5", + "@vendure/core": "^2.1.5", "express": "^4.17.1", "rimraf": "^3.0.2", "typescript": "4.9.5" diff --git a/packages/admin-ui/package-lock.json b/packages/admin-ui/package-lock.json index 2bf866d7b9..986e9c5834 100644 --- a/packages/admin-ui/package-lock.json +++ b/packages/admin-ui/package-lock.json @@ -1,6 +1,6 @@ { "name": "@vendure/admin-ui", - "version": "2.1.4", + "version": "2.1.5", "lockfileVersion": 1, "requires": true, "dependencies": { diff --git a/packages/admin-ui/package.json b/packages/admin-ui/package.json index b269ac3729..4e7558c0c5 100644 --- a/packages/admin-ui/package.json +++ b/packages/admin-ui/package.json @@ -1,6 +1,6 @@ { "name": "@vendure/admin-ui", - "version": "2.1.4", + "version": "2.1.5", "license": "MIT", "scripts": { "ng": "ng", @@ -49,7 +49,7 @@ "@ng-select/ng-select": "^11.1.1", "@ngx-translate/core": "^15.0.0", "@ngx-translate/http-loader": "^8.0.0", - "@vendure/common": "^2.1.4", + "@vendure/common": "^2.1.5", "@webcomponents/custom-elements": "^1.6.0", "apollo-angular": "^5.0.0", "apollo-upload-client": "^17.0.0", diff --git a/packages/admin-ui/src/lib/core/src/common/version.ts b/packages/admin-ui/src/lib/core/src/common/version.ts index 07b9507588..b6022f583c 100644 --- a/packages/admin-ui/src/lib/core/src/common/version.ts +++ b/packages/admin-ui/src/lib/core/src/common/version.ts @@ -1,2 +1,2 @@ // Auto-generated by the set-version.js script. -export const ADMIN_UI_VERSION = '2.1.4'; +export const ADMIN_UI_VERSION = '2.1.5'; diff --git a/packages/asset-server-plugin/package.json b/packages/asset-server-plugin/package.json index 8289a15bcc..b83dc06f60 100644 --- a/packages/asset-server-plugin/package.json +++ b/packages/asset-server-plugin/package.json @@ -1,6 +1,6 @@ { "name": "@vendure/asset-server-plugin", - "version": "2.1.4", + "version": "2.1.5", "main": "lib/index.js", "types": "lib/index.d.ts", "files": [ @@ -27,8 +27,8 @@ "@types/fs-extra": "^11.0.1", "@types/node-fetch": "^2.5.8", "@types/sharp": "^0.30.4", - "@vendure/common": "^2.1.4", - "@vendure/core": "^2.1.4", + "@vendure/common": "^2.1.5", + "@vendure/core": "^2.1.5", "express": "^4.17.1", "node-fetch": "^2.6.7", "rimraf": "^3.0.2", diff --git a/packages/cli/package.json b/packages/cli/package.json index f69dcc0311..9b629824bb 100644 --- a/packages/cli/package.json +++ b/packages/cli/package.json @@ -1,6 +1,6 @@ { "name": "@vendure/cli", - "version": "2.1.4", + "version": "2.1.5", "description": "A modern, headless ecommerce framework", "repository": { "type": "git", @@ -34,7 +34,7 @@ ], "dependencies": { "@clack/prompts": "^0.7.0", - "@vendure/common": "^2.1.4", + "@vendure/common": "^2.1.5", "change-case": "^4.1.2", "commander": "^11.0.0", "fs-extra": "^11.1.1", diff --git a/packages/common/package.json b/packages/common/package.json index d3c0115e97..f1aa9c1b63 100644 --- a/packages/common/package.json +++ b/packages/common/package.json @@ -1,6 +1,6 @@ { "name": "@vendure/common", - "version": "2.1.4", + "version": "2.1.5", "main": "index.js", "license": "MIT", "scripts": { diff --git a/packages/core/package.json b/packages/core/package.json index c34ab3644a..055a4ca035 100644 --- a/packages/core/package.json +++ b/packages/core/package.json @@ -1,6 +1,6 @@ { "name": "@vendure/core", - "version": "2.1.4", + "version": "2.1.5", "description": "A modern, headless ecommerce framework", "repository": { "type": "git", @@ -50,7 +50,7 @@ "@nestjs/testing": "10.2.1", "@nestjs/typeorm": "10.0.0", "@types/fs-extra": "^9.0.1", - "@vendure/common": "^2.1.4", + "@vendure/common": "^2.1.5", "bcrypt": "^5.1.1", "body-parser": "^1.20.2", "chalk": "^4.1.2", diff --git a/packages/create/package.json b/packages/create/package.json index 255876c5c3..4890a277e9 100644 --- a/packages/create/package.json +++ b/packages/create/package.json @@ -1,6 +1,6 @@ { "name": "@vendure/create", - "version": "2.1.4", + "version": "2.1.5", "license": "MIT", "bin": { "create": "./index.js" @@ -28,14 +28,14 @@ "@types/fs-extra": "^9.0.1", "@types/handlebars": "^4.1.0", "@types/semver": "^6.2.2", - "@vendure/core": "^2.1.4", + "@vendure/core": "^2.1.5", "rimraf": "^3.0.2", "ts-node": "^10.9.1", "typescript": "4.9.5" }, "dependencies": { "@clack/prompts": "^0.7.0", - "@vendure/common": "^2.1.4", + "@vendure/common": "^2.1.5", "commander": "^11.0.0", "cross-spawn": "^7.0.3", "detect-port": "^1.5.1", diff --git a/packages/dev-server/package.json b/packages/dev-server/package.json index deeb4cdb76..55687d46f7 100644 --- a/packages/dev-server/package.json +++ b/packages/dev-server/package.json @@ -1,6 +1,6 @@ { "name": "dev-server", - "version": "2.1.4", + "version": "2.1.5", "main": "index.js", "license": "MIT", "private": true, @@ -15,18 +15,18 @@ }, "dependencies": { "@nestjs/axios": "^3.0.0", - "@vendure/admin-ui-plugin": "^2.1.4", - "@vendure/asset-server-plugin": "^2.1.4", - "@vendure/common": "^2.1.4", - "@vendure/core": "^2.1.4", - "@vendure/elasticsearch-plugin": "^2.1.4", - "@vendure/email-plugin": "^2.1.4", + "@vendure/admin-ui-plugin": "^2.1.5", + "@vendure/asset-server-plugin": "^2.1.5", + "@vendure/common": "^2.1.5", + "@vendure/core": "^2.1.5", + "@vendure/elasticsearch-plugin": "^2.1.5", + "@vendure/email-plugin": "^2.1.5", "typescript": "4.9.5" }, "devDependencies": { "@types/csv-stringify": "^3.1.0", - "@vendure/testing": "^2.1.4", - "@vendure/ui-devkit": "^2.1.4", + "@vendure/testing": "^2.1.5", + "@vendure/ui-devkit": "^2.1.5", "commander": "^7.1.0", "concurrently": "^8.2.1", "csv-stringify": "^5.3.3", diff --git a/packages/elasticsearch-plugin/package.json b/packages/elasticsearch-plugin/package.json index 8c019262ca..b73e4deefc 100644 --- a/packages/elasticsearch-plugin/package.json +++ b/packages/elasticsearch-plugin/package.json @@ -1,6 +1,6 @@ { "name": "@vendure/elasticsearch-plugin", - "version": "2.1.4", + "version": "2.1.5", "license": "MIT", "main": "lib/index.js", "types": "lib/index.d.ts", @@ -26,8 +26,8 @@ "fast-deep-equal": "^3.1.3" }, "devDependencies": { - "@vendure/common": "^2.1.4", - "@vendure/core": "^2.1.4", + "@vendure/common": "^2.1.5", + "@vendure/core": "^2.1.5", "rimraf": "^3.0.2", "typescript": "4.9.5" } diff --git a/packages/email-plugin/package.json b/packages/email-plugin/package.json index 537524cac4..2d0410083c 100644 --- a/packages/email-plugin/package.json +++ b/packages/email-plugin/package.json @@ -1,6 +1,6 @@ { "name": "@vendure/email-plugin", - "version": "2.1.4", + "version": "2.1.5", "license": "MIT", "main": "lib/index.js", "types": "lib/index.d.ts", @@ -35,8 +35,8 @@ "@types/fs-extra": "^9.0.1", "@types/handlebars": "^4.1.0", "@types/mjml": "^4.0.4", - "@vendure/common": "^2.1.4", - "@vendure/core": "^2.1.4", + "@vendure/common": "^2.1.5", + "@vendure/core": "^2.1.5", "rimraf": "^3.0.2", "typescript": "4.9.5" } diff --git a/packages/harden-plugin/package.json b/packages/harden-plugin/package.json index b68c63d9b0..2cdadcff77 100644 --- a/packages/harden-plugin/package.json +++ b/packages/harden-plugin/package.json @@ -1,6 +1,6 @@ { "name": "@vendure/harden-plugin", - "version": "2.1.4", + "version": "2.1.5", "license": "MIT", "main": "lib/index.js", "types": "lib/index.d.ts", @@ -21,7 +21,7 @@ "graphql-query-complexity": "^0.12.0" }, "devDependencies": { - "@vendure/common": "^2.1.4", - "@vendure/core": "^2.1.4" + "@vendure/common": "^2.1.5", + "@vendure/core": "^2.1.5" } } diff --git a/packages/job-queue-plugin/package.json b/packages/job-queue-plugin/package.json index 36d75cd2e8..96a09e21f4 100644 --- a/packages/job-queue-plugin/package.json +++ b/packages/job-queue-plugin/package.json @@ -1,6 +1,6 @@ { "name": "@vendure/job-queue-plugin", - "version": "2.1.4", + "version": "2.1.5", "license": "MIT", "main": "package/index.js", "types": "package/index.d.ts", @@ -23,8 +23,8 @@ }, "devDependencies": { "@google-cloud/pubsub": "^2.8.0", - "@vendure/common": "^2.1.4", - "@vendure/core": "^2.1.4", + "@vendure/common": "^2.1.5", + "@vendure/core": "^2.1.5", "bullmq": "^3.15.5", "ioredis": "^5.3.0", "rimraf": "^3.0.2", diff --git a/packages/payments-plugin/package.json b/packages/payments-plugin/package.json index 2a1dc0d135..9432dbc4ee 100644 --- a/packages/payments-plugin/package.json +++ b/packages/payments-plugin/package.json @@ -1,6 +1,6 @@ { "name": "@vendure/payments-plugin", - "version": "2.1.4", + "version": "2.1.5", "license": "MIT", "main": "package/index.js", "types": "package/index.d.ts", @@ -46,9 +46,9 @@ "@mollie/api-client": "^3.7.0", "@types/braintree": "^2.22.15", "@types/localtunnel": "2.0.1", - "@vendure/common": "^2.1.4", - "@vendure/core": "^2.1.4", - "@vendure/testing": "^2.1.4", + "@vendure/common": "^2.1.5", + "@vendure/core": "^2.1.5", + "@vendure/testing": "^2.1.5", "braintree": "^3.16.0", "localtunnel": "2.0.2", "nock": "^13.1.4", diff --git a/packages/sentry-plugin/package.json b/packages/sentry-plugin/package.json index 38d0b17e99..2b6186a3f4 100644 --- a/packages/sentry-plugin/package.json +++ b/packages/sentry-plugin/package.json @@ -1,6 +1,6 @@ { "name": "@vendure/sentry-plugin", - "version": "2.1.4", + "version": "2.1.5", "license": "MIT", "main": "lib/index.js", "types": "lib/index.d.ts", @@ -21,8 +21,8 @@ "@sentry/node": "^7.85.0" }, "devDependencies": { - "@vendure/common": "^2.1.4", - "@vendure/core": "^2.1.4", - "@sentry/node": "^7.85.0" + "@sentry/node": "^7.85.0", + "@vendure/common": "^2.1.5", + "@vendure/core": "^2.1.5" } } diff --git a/packages/stellate-plugin/package.json b/packages/stellate-plugin/package.json index 738cfc23a1..d1e7b36794 100644 --- a/packages/stellate-plugin/package.json +++ b/packages/stellate-plugin/package.json @@ -1,6 +1,6 @@ { "name": "@vendure/stellate-plugin", - "version": "2.1.4", + "version": "2.1.5", "license": "MIT", "main": "lib/index.js", "types": "lib/index.d.ts", @@ -21,7 +21,7 @@ "node-fetch": "^2.7.0" }, "devDependencies": { - "@vendure/common": "^2.1.4", - "@vendure/core": "^2.1.4" + "@vendure/common": "^2.1.5", + "@vendure/core": "^2.1.5" } } diff --git a/packages/testing/package.json b/packages/testing/package.json index 97b82aa9ef..e6b80f255e 100644 --- a/packages/testing/package.json +++ b/packages/testing/package.json @@ -1,6 +1,6 @@ { "name": "@vendure/testing", - "version": "2.1.4", + "version": "2.1.5", "description": "End-to-end testing tools for Vendure projects", "keywords": [ "vendure", @@ -38,7 +38,7 @@ "dependencies": { "@graphql-typed-document-node/core": "^3.2.0", "@types/node-fetch": "^2.6.4", - "@vendure/common": "^2.1.4", + "@vendure/common": "^2.1.5", "faker": "^4.1.0", "form-data": "^4.0.0", "graphql": "16.8.0", @@ -49,7 +49,7 @@ "devDependencies": { "@types/mysql": "^2.15.15", "@types/pg": "^7.14.5", - "@vendure/core": "^2.1.4", + "@vendure/core": "^2.1.5", "mysql": "^2.18.1", "pg": "^8.4.0", "rimraf": "^3.0.0", diff --git a/packages/ui-devkit/package.json b/packages/ui-devkit/package.json index 013cad6fd7..dddf975d85 100644 --- a/packages/ui-devkit/package.json +++ b/packages/ui-devkit/package.json @@ -1,6 +1,6 @@ { "name": "@vendure/ui-devkit", - "version": "2.1.4", + "version": "2.1.5", "description": "A library for authoring Vendure Admin UI extensions", "keywords": [ "vendure", @@ -40,8 +40,8 @@ "@angular/cli": "^16.2.0", "@angular/compiler": "^16.2.2", "@angular/compiler-cli": "^16.2.2", - "@vendure/admin-ui": "^2.1.4", - "@vendure/common": "^2.1.4", + "@vendure/admin-ui": "^2.1.5", + "@vendure/common": "^2.1.5", "chalk": "^4.1.0", "chokidar": "^3.5.3", "fs-extra": "^11.1.1", @@ -51,7 +51,7 @@ "devDependencies": { "@rollup/plugin-node-resolve": "^15.2.1", "@types/fs-extra": "^11.0.1", - "@vendure/core": "^2.1.4", + "@vendure/core": "^2.1.5", "react": "^18.2.0", "react-dom": "^18.2.0", "rimraf": "^3.0.2",