@reactionary/source 0.3.18 → 0.6.2

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (191) hide show
  1. package/README.md +28 -14
  2. package/core/src/client/client-builder.ts +54 -6
  3. package/core/src/factories/cart.factory.ts +40 -0
  4. package/core/src/factories/category.factory.ts +40 -0
  5. package/core/src/factories/checkout.factory.ts +59 -0
  6. package/core/src/factories/identity.factory.ts +26 -0
  7. package/core/src/factories/index.ts +15 -0
  8. package/core/src/factories/inventory.factory.ts +26 -0
  9. package/core/src/factories/order-search.factory.ts +33 -0
  10. package/core/src/factories/order.factory.ts +21 -0
  11. package/core/src/factories/price.factory.ts +29 -0
  12. package/core/src/factories/product-associations.factory.ts +30 -0
  13. package/core/src/factories/product-list.factory.ts +82 -0
  14. package/core/src/factories/product-reviews.factory.ts +58 -0
  15. package/core/src/factories/product-search.factory.ts +31 -0
  16. package/core/src/factories/product.factory.ts +21 -0
  17. package/core/src/factories/profile.factory.ts +23 -0
  18. package/core/src/factories/store.factory.ts +21 -0
  19. package/core/src/index.ts +3 -1
  20. package/core/src/providers/cart.provider.ts +12 -10
  21. package/core/src/providers/category.provider.ts +9 -7
  22. package/core/src/providers/checkout.provider.ts +14 -10
  23. package/core/src/providers/identity.provider.ts +7 -7
  24. package/core/src/providers/inventory.provider.ts +4 -4
  25. package/core/src/providers/order-search.provider.ts +4 -2
  26. package/core/src/providers/order.provider.ts +4 -4
  27. package/core/src/providers/price.provider.ts +5 -5
  28. package/core/src/providers/product-associations.provider.ts +6 -4
  29. package/core/src/providers/product-list.provider.ts +13 -8
  30. package/core/src/providers/product-reviews.provider.ts +8 -4
  31. package/core/src/providers/product-search.provider.ts +15 -28
  32. package/core/src/providers/product.provider.ts +6 -6
  33. package/core/src/providers/profile.provider.ts +8 -8
  34. package/core/src/providers/store.provider.ts +2 -2
  35. package/core/src/test/client-builder.spec.ts +81 -0
  36. package/examples/node/package.json +7 -7
  37. package/examples/node/src/basic/basic-node-provider-model-extension.spec.ts +10 -4
  38. package/examples/node/src/basic/basic-node-provider-query-extension.spec.ts +8 -2
  39. package/examples/node/src/basic/basic-node-setup.spec.ts +5 -1
  40. package/examples/node/src/basic/client-creation.spec.ts +3 -3
  41. package/examples/node/src/utils.ts +41 -41
  42. package/examples/node/tsconfig.lib.json +2 -1
  43. package/package.json +1 -1
  44. package/providers/algolia/src/core/initialize.ts +76 -21
  45. package/providers/algolia/src/core/initialize.types.ts +107 -0
  46. package/providers/algolia/src/factories/index.ts +1 -0
  47. package/providers/algolia/src/factories/product-search/product-search.factory.ts +182 -0
  48. package/providers/algolia/src/index.ts +2 -2
  49. package/providers/algolia/src/providers/analytics.provider.ts +4 -4
  50. package/providers/algolia/src/providers/product-search.provider.ts +67 -184
  51. package/providers/algolia/src/schema/capabilities.schema.ts +81 -7
  52. package/providers/algolia/src/test/analytics.spec.ts +9 -1
  53. package/providers/algolia/src/test/client-builder-product-search-extension.example.ts +85 -0
  54. package/providers/commercetools/src/core/capability-descriptors.ts +324 -0
  55. package/providers/commercetools/src/core/initialize.ts +35 -151
  56. package/providers/commercetools/src/core/initialize.types.ts +174 -0
  57. package/providers/commercetools/src/factories/cart/cart.factory.ts +142 -0
  58. package/providers/commercetools/src/factories/category/category.factory.ts +77 -0
  59. package/providers/commercetools/src/factories/checkout/checkout-initializer-overrides.example.ts +94 -0
  60. package/providers/commercetools/src/factories/checkout/checkout.factory.ts +338 -0
  61. package/providers/commercetools/src/factories/identity/identity.factory.ts +26 -0
  62. package/providers/commercetools/src/factories/inventory/inventory.factory.ts +49 -0
  63. package/providers/commercetools/src/factories/order/order.factory.ts +149 -0
  64. package/providers/commercetools/src/factories/order-search/order-search.factory.ts +108 -0
  65. package/providers/commercetools/src/factories/price/price.factory.ts +76 -0
  66. package/providers/commercetools/src/factories/product/product-factory-baseline.example.ts +14 -0
  67. package/providers/commercetools/src/factories/product/product-factory-schema-and-parse-extension.example.ts +35 -0
  68. package/providers/commercetools/src/factories/product/product-factory-schema-extension.example.ts +23 -0
  69. package/providers/commercetools/src/factories/product/product-initializer-factory-extension.example.ts +41 -0
  70. package/providers/commercetools/src/factories/product/product-provider-custom-method-only.example.ts +47 -0
  71. package/providers/commercetools/src/factories/product/product-provider-schema-signature-extension.example.ts +61 -0
  72. package/providers/commercetools/src/factories/product/product.factory.ts +220 -0
  73. package/providers/commercetools/src/factories/product/utils.example.ts +9 -0
  74. package/providers/commercetools/src/factories/product-associations/product-associations.factory.ts +103 -0
  75. package/providers/commercetools/src/factories/product-list/product-list.factory.ts +122 -0
  76. package/providers/commercetools/src/factories/product-reviews/product-reviews.factory.ts +81 -0
  77. package/providers/commercetools/src/factories/product-search/product-search.factory.ts +182 -0
  78. package/providers/commercetools/src/factories/profile/profile.factory.ts +94 -0
  79. package/providers/commercetools/src/factories/store/store.factory.ts +49 -0
  80. package/providers/commercetools/src/index.ts +15 -0
  81. package/providers/commercetools/src/providers/cart.provider.ts +67 -193
  82. package/providers/commercetools/src/providers/category.provider.ts +24 -64
  83. package/providers/commercetools/src/providers/checkout.provider.ts +50 -322
  84. package/providers/commercetools/src/providers/identity.provider.ts +35 -15
  85. package/providers/commercetools/src/providers/inventory.provider.ts +13 -31
  86. package/providers/commercetools/src/providers/order-search.provider.ts +16 -110
  87. package/providers/commercetools/src/providers/order.provider.ts +13 -144
  88. package/providers/commercetools/src/providers/price.provider.ts +37 -51
  89. package/providers/commercetools/src/providers/product-associations.provider.ts +39 -104
  90. package/providers/commercetools/src/providers/product-list.provider.ts +38 -23
  91. package/providers/commercetools/src/providers/product-reviews.provider.ts +34 -14
  92. package/providers/commercetools/src/providers/product-search.provider.ts +17 -170
  93. package/providers/commercetools/src/providers/product.provider.ts +20 -199
  94. package/providers/commercetools/src/providers/profile.provider.ts +27 -73
  95. package/providers/commercetools/src/providers/store.provider.ts +13 -31
  96. package/providers/commercetools/src/schema/capabilities.schema.ts +258 -20
  97. package/providers/commercetools/src/test/caching.spec.ts +18 -2
  98. package/providers/commercetools/src/test/client-builder-merge-extensions.example.ts +125 -0
  99. package/providers/fake/src/core/initialize.ts +213 -44
  100. package/providers/fake/src/core/initialize.types.ts +164 -0
  101. package/providers/fake/src/factories/cart/cart.factory.ts +34 -0
  102. package/providers/fake/src/factories/category/category.factory.ts +40 -0
  103. package/providers/fake/src/factories/checkout/checkout.factory.ts +53 -0
  104. package/providers/fake/src/factories/identity/identity.factory.ts +25 -0
  105. package/providers/fake/src/factories/index.ts +14 -0
  106. package/providers/fake/src/factories/inventory/inventory.factory.ts +25 -0
  107. package/providers/fake/src/factories/order/order.factory.ts +22 -0
  108. package/providers/fake/src/factories/order-search/order-search.factory.ts +27 -0
  109. package/providers/fake/src/factories/price/price.factory.ts +26 -0
  110. package/providers/fake/src/factories/product/product.factory.ts +22 -0
  111. package/providers/fake/src/factories/product-associations/product-associations.factory.ts +25 -0
  112. package/providers/fake/src/factories/product-reviews/product-reviews.factory.ts +53 -0
  113. package/providers/fake/src/factories/product-search/product-search.factory.ts +27 -0
  114. package/providers/fake/src/factories/profile/profile.factory.ts +22 -0
  115. package/providers/fake/src/factories/store/store.factory.ts +22 -0
  116. package/providers/fake/src/index.ts +2 -0
  117. package/providers/fake/src/providers/cart.provider.ts +23 -14
  118. package/providers/fake/src/providers/category.provider.ts +120 -105
  119. package/providers/fake/src/providers/checkout.provider.ts +39 -20
  120. package/providers/fake/src/providers/identity.provider.ts +40 -34
  121. package/providers/fake/src/providers/inventory.provider.ts +26 -24
  122. package/providers/fake/src/providers/order-search.provider.ts +38 -30
  123. package/providers/fake/src/providers/order.provider.ts +21 -37
  124. package/providers/fake/src/providers/price.provider.ts +42 -34
  125. package/providers/fake/src/providers/product-associations.provider.ts +23 -10
  126. package/providers/fake/src/providers/product-reviews.provider.ts +71 -69
  127. package/providers/fake/src/providers/product-search.provider.ts +43 -70
  128. package/providers/fake/src/providers/product.provider.ts +34 -32
  129. package/providers/fake/src/providers/profile.provider.ts +62 -55
  130. package/providers/fake/src/providers/store.provider.ts +38 -22
  131. package/providers/fake/src/schema/capabilities.schema.ts +175 -18
  132. package/providers/fake/src/test/cart.provider.spec.ts +20 -3
  133. package/providers/fake/src/test/category.provider.spec.ts +4 -1
  134. package/providers/fake/src/test/checkout.provider.spec.ts +12 -2
  135. package/providers/fake/src/test/client-builder-product-extension.example.ts +75 -0
  136. package/providers/fake/src/test/order-search.provider.spec.ts +4 -7
  137. package/providers/fake/src/test/order.provider.spec.ts +4 -6
  138. package/providers/fake/src/test/price.provider.spec.ts +3 -1
  139. package/providers/fake/src/test/product.provider.spec.ts +8 -2
  140. package/providers/fake/src/test/profile.provider.spec.ts +4 -2
  141. package/providers/google-analytics/src/core/initialize.ts +37 -12
  142. package/providers/google-analytics/src/core/initialize.types.ts +47 -0
  143. package/providers/google-analytics/src/index.ts +1 -0
  144. package/providers/google-analytics/src/schema/capabilities.schema.ts +31 -5
  145. package/providers/medusa/src/core/initialize.ts +324 -81
  146. package/providers/medusa/src/core/initialize.types.ts +184 -0
  147. package/providers/medusa/src/factories/cart/cart.factory.ts +34 -0
  148. package/providers/medusa/src/factories/category/category.factory.ts +37 -0
  149. package/providers/medusa/src/factories/checkout/checkout.factory.ts +50 -0
  150. package/providers/medusa/src/factories/identity/identity.factory.ts +22 -0
  151. package/providers/medusa/src/factories/index.ts +12 -0
  152. package/providers/medusa/src/factories/inventory/inventory.factory.ts +25 -0
  153. package/providers/medusa/src/factories/order/order.factory.ts +22 -0
  154. package/providers/medusa/src/factories/order-search/order-search.factory.ts +27 -0
  155. package/providers/medusa/src/factories/price/price.factory.ts +26 -0
  156. package/providers/medusa/src/factories/product/product.factory.ts +22 -0
  157. package/providers/medusa/src/factories/product-associations/product-associations.factory.ts +25 -0
  158. package/providers/medusa/src/factories/product-search/product-search.factory.ts +27 -0
  159. package/providers/medusa/src/factories/profile/profile.factory.ts +22 -0
  160. package/providers/medusa/src/index.ts +2 -0
  161. package/providers/medusa/src/providers/cart.provider.ts +33 -20
  162. package/providers/medusa/src/providers/category.provider.ts +30 -12
  163. package/providers/medusa/src/providers/checkout.provider.ts +42 -17
  164. package/providers/medusa/src/providers/identity.provider.ts +1 -1
  165. package/providers/medusa/src/providers/inventory.provider.ts +21 -7
  166. package/providers/medusa/src/providers/order-search.provider.ts +16 -5
  167. package/providers/medusa/src/providers/order.provider.ts +17 -5
  168. package/providers/medusa/src/providers/price.provider.ts +26 -7
  169. package/providers/medusa/src/providers/product-associations.provider.ts +19 -8
  170. package/providers/medusa/src/providers/product-search.provider.ts +19 -31
  171. package/providers/medusa/src/providers/product.provider.ts +47 -11
  172. package/providers/medusa/src/providers/profile.provider.ts +35 -11
  173. package/providers/medusa/src/schema/capabilities.schema.ts +229 -18
  174. package/providers/medusa/src/test/cart.provider.spec.ts +18 -2
  175. package/providers/medusa/src/test/category.provider.spec.ts +4 -1
  176. package/providers/medusa/src/test/checkout.spec.ts +9 -9
  177. package/providers/medusa/src/test/inventory.provider.spec.ts +3 -1
  178. package/providers/medusa/src/test/large-cart.provider.spec.ts +8 -2
  179. package/providers/medusa/src/test/price.provider.spec.ts +8 -1
  180. package/providers/medusa/src/test/product.provider.spec.ts +3 -1
  181. package/providers/medusa/src/test/search.provider.spec.ts +16 -3
  182. package/providers/meilisearch/src/core/initialize.ts +88 -21
  183. package/providers/meilisearch/src/core/initialize.types.ts +119 -0
  184. package/providers/meilisearch/src/factories/index.ts +2 -0
  185. package/providers/meilisearch/src/factories/order-search/order-search.factory.ts +27 -0
  186. package/providers/meilisearch/src/factories/product-search/product-search.factory.ts +27 -0
  187. package/providers/meilisearch/src/index.ts +2 -0
  188. package/providers/meilisearch/src/providers/index.ts +1 -0
  189. package/providers/meilisearch/src/providers/order-search.provider.ts +21 -6
  190. package/providers/meilisearch/src/providers/product-search.provider.ts +24 -8
  191. package/providers/meilisearch/src/schema/capabilities.schema.ts +95 -8
@@ -0,0 +1,76 @@
1
+ import type { Price as CTPrice, ProductVariant as CTProductVariant } from '@commercetools/platform-sdk';
2
+ import type {
3
+ PriceSchema} from '@reactionary/core';
4
+ import {
5
+ type AnyPriceSchema,
6
+ type Currency,
7
+ type MonetaryAmount,
8
+ type Price,
9
+ type PriceFactory,
10
+ type PriceIdentifier,
11
+ type RequestContext,
12
+ } from '@reactionary/core';
13
+ import type * as z from 'zod';
14
+
15
+ export class CommercetoolsPriceFactory<
16
+ TPriceSchema extends AnyPriceSchema = typeof PriceSchema,
17
+ > implements PriceFactory<TPriceSchema>
18
+ {
19
+ public readonly priceSchema: TPriceSchema;
20
+
21
+ constructor(priceSchema: TPriceSchema) {
22
+ this.priceSchema = priceSchema;
23
+ }
24
+
25
+ public parsePrice(
26
+ context: RequestContext,
27
+ data: CTProductVariant,
28
+ options = { includeDiscounts: false },
29
+ ): z.output<TPriceSchema> {
30
+ const price = data.price as CTPrice | undefined;
31
+
32
+ if (!price) {
33
+ return this.priceSchema.parse({
34
+ identifier: {
35
+ variant: {
36
+ sku: data.sku || '',
37
+ },
38
+ },
39
+ onSale: false,
40
+ tieredPrices: [],
41
+ unitPrice: {
42
+ value: -1,
43
+ currency: context.languageContext.currencyCode,
44
+ },
45
+ } satisfies Price);
46
+ }
47
+
48
+ let unitPrice = {
49
+ value: price.value.centAmount / 100,
50
+ currency: price.value.currencyCode as Currency,
51
+ } satisfies MonetaryAmount;
52
+
53
+ if (options.includeDiscounts) {
54
+ const discountedPrice = price.discounted?.value || price.value;
55
+ unitPrice = {
56
+ value: discountedPrice.centAmount / 100,
57
+ currency: price.value.currencyCode as Currency,
58
+ } satisfies MonetaryAmount;
59
+ }
60
+
61
+ const identifier = {
62
+ variant: {
63
+ sku: data.sku || '',
64
+ },
65
+ } satisfies PriceIdentifier;
66
+
67
+ const result = {
68
+ identifier,
69
+ onSale: options.includeDiscounts && !!price.discounted,
70
+ tieredPrices: [],
71
+ unitPrice,
72
+ } satisfies Price;
73
+
74
+ return this.priceSchema.parse(result);
75
+ }
76
+ }
@@ -0,0 +1,14 @@
1
+ import { ProductSchema } from "@reactionary/core";
2
+ import { CommercetoolsProductFactory } from "./product.factory.js";
3
+ import { assertNotAny, assertType } from "./utils.example.js";
4
+
5
+ const context = {} as any;
6
+
7
+ const baseFactory = new CommercetoolsProductFactory(ProductSchema);
8
+ const baseProduct = baseFactory.parseProduct(context, {} as any);
9
+
10
+ assertNotAny(baseProduct);
11
+ assertNotAny(baseProduct.name);
12
+ assertType<string>(baseProduct.name);
13
+ // @ts-expect-error base schema does not include extendedValue
14
+ assertType<string>(baseProduct.extendedValue);
@@ -0,0 +1,35 @@
1
+ import { ProductSchema } from "@reactionary/core";
2
+ import type { RequestContext } from "@reactionary/core";
3
+ import type { ProductProjection } from "@commercetools/platform-sdk";
4
+ import { CommercetoolsProductFactory } from "./product.factory.js";
5
+ import * as z from "zod";
6
+ import { assertNotAny, assertType } from "./utils.example.js";
7
+
8
+ const context = {} as any;
9
+
10
+ const ExtendedProductSchema = ProductSchema.safeExtend({
11
+ extendedValue: z.string(),
12
+ });
13
+
14
+ class ExtendedCommercetoolsProductFactory extends CommercetoolsProductFactory<typeof ExtendedProductSchema> {
15
+ constructor() {
16
+ super(ExtendedProductSchema);
17
+ }
18
+
19
+ public override parseProduct(context: RequestContext, data: ProductProjection) {
20
+ const base = super.parseProduct(context, data);
21
+
22
+ // PAIN: Needing the satisfies here - possibly unavoidable
23
+ return {
24
+ ...base,
25
+ extendedValue: "from-parse",
26
+ } satisfies z.output<typeof ExtendedProductSchema>;
27
+ }
28
+ }
29
+
30
+ const extendedFactory = new ExtendedCommercetoolsProductFactory();
31
+ const extendedProduct = extendedFactory.parseProduct(context, {} as any);
32
+
33
+ assertNotAny(extendedProduct);
34
+ assertNotAny(extendedProduct.extendedValue);
35
+ assertType<string>(extendedProduct.extendedValue);
@@ -0,0 +1,23 @@
1
+ import { ProductSchema } from "@reactionary/core";
2
+ import { CommercetoolsProductFactory } from "./product.factory.js";
3
+ import * as z from "zod";
4
+ import { assertNotAny, assertType } from "./utils.example.js";
5
+
6
+ const context = {} as any;
7
+
8
+ const ExtendedProductSchema = ProductSchema.safeExtend({
9
+ extendedValue: z.string(),
10
+ });
11
+
12
+ class ExtendedCommercetoolsProductFactory extends CommercetoolsProductFactory<typeof ExtendedProductSchema> {
13
+ constructor() {
14
+ super(ExtendedProductSchema);
15
+ }
16
+ }
17
+
18
+ const extendedFactory = new ExtendedCommercetoolsProductFactory();
19
+ const extendedProduct = extendedFactory.parseProduct(context, {} as any);
20
+
21
+ assertNotAny(extendedProduct);
22
+ assertNotAny(extendedProduct.extendedValue);
23
+ assertType<string>(extendedProduct.extendedValue);
@@ -0,0 +1,41 @@
1
+ import { ProductSchema } from "@reactionary/core";
2
+ import { CommercetoolsProductFactory } from "./product.factory.js";
3
+ import { withCommercetoolsCapabilities } from "../../core/initialize.js";
4
+ import * as z from "zod";
5
+ import { assertNotAny, assertType } from "./utils.example.js";
6
+
7
+ const cache = {} as any;
8
+ const context = {} as any;
9
+ const config = {} as any;
10
+
11
+ const ExtendedProductSchema = ProductSchema.safeExtend({
12
+ extendedValue: z.string(),
13
+ });
14
+
15
+ class ExtendedCommercetoolsProductFactory extends CommercetoolsProductFactory<typeof ExtendedProductSchema> {
16
+ constructor() {
17
+ super(ExtendedProductSchema);
18
+ }
19
+ }
20
+
21
+ const capabilityFactory = withCommercetoolsCapabilities(config, {
22
+ product: {
23
+ enabled: true,
24
+ factory: new ExtendedCommercetoolsProductFactory(),
25
+ },
26
+ });
27
+
28
+ const client = capabilityFactory(cache, context);
29
+
30
+ client.product
31
+ .getById({
32
+ identifier: { key: "p-1" },
33
+ })
34
+ .then((x) => {
35
+ assertNotAny(x);
36
+ if (x.success) {
37
+ assertNotAny(x.value);
38
+ assertNotAny(x.value.extendedValue);
39
+ assertType<string>(x.value.extendedValue);
40
+ }
41
+ });
@@ -0,0 +1,47 @@
1
+ import { ProductSchema } from "@reactionary/core";
2
+ import type { ProductFactoryOutput, Result } from "@reactionary/core";
3
+ import { CommercetoolsProductFactory } from "./product.factory.js";
4
+ import { CommercetoolsProductProvider } from "../../providers/product.provider.js";
5
+ import { withCommercetoolsCapabilities } from "../../core/initialize.js";
6
+ import { assertNotAny, assertType } from "./utils.example.js";
7
+
8
+ const cache = {} as any;
9
+ const context = {} as any;
10
+ const config = {} as any;
11
+ const api = {} as any;
12
+
13
+ class ExtendedCommercetoolsProductProvider extends CommercetoolsProductProvider<CommercetoolsProductFactory> {
14
+ public async getByCustomIdentifier(
15
+ identifier: string
16
+ ): Promise<Result<ProductFactoryOutput<CommercetoolsProductFactory>>> {
17
+ return this.getById({
18
+ identifier: { key: identifier },
19
+ });
20
+ }
21
+ }
22
+
23
+ const withExtendedProviderFactory = withCommercetoolsCapabilities(config, {
24
+ product: {
25
+ enabled: true,
26
+ factory: new CommercetoolsProductFactory(ProductSchema),
27
+ provider: ({ cache, context, config, commercetoolsApi }) =>
28
+ new ExtendedCommercetoolsProductProvider(
29
+ cache,
30
+ context,
31
+ config,
32
+ commercetoolsApi,
33
+ new CommercetoolsProductFactory(ProductSchema)
34
+ ),
35
+ },
36
+ });
37
+
38
+ const client = withExtendedProviderFactory(cache, context);
39
+
40
+ client.product.getByCustomIdentifier("p-2").then((x) => {
41
+ assertNotAny(x);
42
+ if (x.success) {
43
+ assertNotAny(x.value);
44
+ assertNotAny(x.value.name);
45
+ assertType<string>(x.value.name);
46
+ }
47
+ });
@@ -0,0 +1,61 @@
1
+ import { ProductQueryByIdSchema, ProductSchema } from "@reactionary/core";
2
+ import type {
3
+ ProductFactoryOutput,
4
+ ProductQueryById as BaseProductQueryById,
5
+ Result,
6
+ } from "@reactionary/core";
7
+ import { CommercetoolsProductFactory } from "./product.factory.js";
8
+ import { CommercetoolsProductProvider } from "../../providers/product.provider.js";
9
+ import * as z from "zod";
10
+ import { assertNotAny, assertType } from "./utils.example.js";
11
+
12
+ const cache = {} as any;
13
+ const context = {} as any;
14
+ const config = {} as any;
15
+ const api = {} as any;
16
+
17
+ const ExtendedProductSchema = ProductSchema.safeExtend({
18
+ extendedValue: z.string(),
19
+ });
20
+
21
+ class ExtendedCommercetoolsProductFactory extends CommercetoolsProductFactory<typeof ExtendedProductSchema> {
22
+ constructor() {
23
+ super(ExtendedProductSchema);
24
+ }
25
+ }
26
+
27
+ const ExtendedProductQueryByIdSchema = ProductQueryByIdSchema.extend({
28
+ auditTrail: z.boolean(),
29
+ });
30
+ type ExtendedProductQueryById = z.infer<typeof ExtendedProductQueryByIdSchema>;
31
+
32
+ class ExtendedCommercetoolsProductProvider extends CommercetoolsProductProvider<ExtendedCommercetoolsProductFactory> {
33
+ public override async getById(
34
+ payload: ExtendedProductQueryById
35
+ ) {
36
+ const basePayload: BaseProductQueryById = {
37
+ identifier: payload.identifier,
38
+ };
39
+ return super.getById(basePayload);
40
+ }
41
+ }
42
+
43
+ const extendedProvider = new ExtendedCommercetoolsProductProvider(
44
+ cache,
45
+ context,
46
+ config,
47
+ api,
48
+ new ExtendedCommercetoolsProductFactory()
49
+ );
50
+
51
+ extendedProvider.getById({
52
+ identifier: { key: "p-1" },
53
+ auditTrail: true
54
+ }).then((x) => {
55
+ assertNotAny(x);
56
+ if (x.success) {
57
+ assertNotAny(x.value);
58
+ assertNotAny(x.value.extendedValue);
59
+ assertType<string>(x.value.extendedValue);
60
+ }
61
+ });
@@ -0,0 +1,220 @@
1
+ import {
2
+ ImageSchema,
3
+ ProductAttributeIdentifierSchema,
4
+ ProductAttributeSchema,
5
+ ProductAttributeValueIdentifierSchema,
6
+ ProductAttributeValueSchema,
7
+ type AnyProductSchema,
8
+ type Product,
9
+ type ProductFactory,
10
+ type ProductIdentifier,
11
+ type ProductVariant,
12
+ type ProductVariantIdentifier,
13
+ type RequestContext,
14
+ } from '@reactionary/core';
15
+ import type * as z from 'zod';
16
+ import type {
17
+ ProductProjection,
18
+ ProductVariant as CTProductVariant,
19
+ Attribute as CTAttribute,
20
+ } from '@commercetools/platform-sdk';
21
+ import type {
22
+ Image,
23
+ ProductAttribute,
24
+ ProductAttributeIdentifier,
25
+ ProductAttributeValue,
26
+ ProductAttributeValueIdentifier,
27
+ ProductOptionIdentifier,
28
+ ProductVariantOption,
29
+
30
+ ProductSchema} from '@reactionary/core';
31
+
32
+ export class CommercetoolsProductFactory<
33
+ TProductSchema extends AnyProductSchema = typeof ProductSchema,
34
+ > implements ProductFactory<TProductSchema>
35
+ {
36
+ public readonly productSchema: TProductSchema;
37
+
38
+ // PAIN: not being able to assign a default for productSchema (productSchema: TProductSchema = ProductSchema)
39
+ // because it will invalidate TS' conservative instantiation-point generics
40
+ // PAIN: consider whether we can pass in request context here
41
+ constructor(productSchema: TProductSchema) {
42
+ this.productSchema = productSchema;
43
+ }
44
+
45
+ public parseProduct(
46
+ context: RequestContext,
47
+ data: ProductProjection,
48
+ ): z.output<TProductSchema> {
49
+ const identifier = { key: data.key || data.id } satisfies ProductIdentifier;
50
+ const name = data.name[context.languageContext.locale];
51
+ const slug = data.slug[context.languageContext.locale];
52
+
53
+ let description = '';
54
+ if (data.description) {
55
+ description = data.description[context.languageContext.locale];
56
+ }
57
+
58
+ const variantLevelAttributes =
59
+ data.masterVariant.attributes?.map((x) =>
60
+ this.parseAttribute(context, x),
61
+ ) || [];
62
+
63
+ const specialAttributes = [
64
+ 'reactionaryaccessories',
65
+ 'reactionaryspareparts',
66
+ 'reactionaryreplacements',
67
+ ];
68
+
69
+ const productLevelAttributes =
70
+ data.attributes
71
+ .filter((x) => !specialAttributes.includes(x.name))
72
+ .map((x) => this.parseAttribute(context, x)) || [];
73
+
74
+ const sharedAttributes = [
75
+ ...productLevelAttributes,
76
+ ...variantLevelAttributes,
77
+ ];
78
+ const mainVariant = this.parseVariant(context, data.masterVariant, data);
79
+
80
+ const otherVariants = [];
81
+ for (const variant of data.variants || []) {
82
+ if (variant.id !== data.masterVariant.id) {
83
+ otherVariants.push(this.parseVariant(context, variant, data));
84
+ }
85
+ }
86
+
87
+ const result = {
88
+ identifier,
89
+ name,
90
+ slug,
91
+ description,
92
+ sharedAttributes,
93
+ mainVariant,
94
+ brand: '',
95
+ longDescription: '',
96
+ manufacturer: '',
97
+ options: [],
98
+ parentCategories: [],
99
+ published: true,
100
+ variants: otherVariants,
101
+ } satisfies Product;
102
+
103
+ // PAIN: having to actually do the parse here to guarantee that the generics are satisfied.
104
+ // probably fair, given that this relationship can't be expressed in TS otherwise...
105
+ // unless we were willing to accept ownership of the responsibility and do an 'as T'
106
+ return this.productSchema.parse(result);
107
+ }
108
+
109
+ /**
110
+ * Return true, if the attribute is a defining attribute (ie an option)
111
+ * @param attr a variant attribute
112
+ * @returns true if the attribute is an option
113
+ */
114
+ protected isVariantAttributeAnOption(attr: CTAttribute): boolean {
115
+ // for now, the assumption is that any variant attribute is a defining attribute (ie an option)
116
+ // ideally this should be verified with the product type.
117
+ return true;
118
+ }
119
+
120
+ protected parseVariant(
121
+ context: RequestContext,
122
+ variant: CTProductVariant,
123
+ product: ProductProjection,
124
+ ): ProductVariant {
125
+ const identifier = {
126
+ sku: variant.sku!,
127
+ } satisfies ProductVariantIdentifier;
128
+
129
+ const images = [
130
+ ...(variant.images || []).map((img) =>
131
+ ImageSchema.parse({
132
+ sourceUrl: img.url,
133
+ altText: img.label || '',
134
+ width: img.dimensions?.w,
135
+ height: img.dimensions?.h,
136
+ } satisfies Image),
137
+ ),
138
+ ];
139
+
140
+ const options =
141
+ (variant.attributes ?? [])
142
+ .filter((attr) => this.isVariantAttributeAnOption(attr))
143
+ .map((attr) => {
144
+ const attrVal = this.parseAttributeValue(context, attr);
145
+ const optionIdentifier: ProductOptionIdentifier = {
146
+ key: attr.name,
147
+ };
148
+ const option: ProductVariantOption = {
149
+ identifier: optionIdentifier,
150
+ name: attr.name,
151
+ value: {
152
+ identifier: {
153
+ key: attrVal.value,
154
+ option: optionIdentifier,
155
+ },
156
+ label: attrVal.label,
157
+ },
158
+ };
159
+ return option;
160
+ }) || [];
161
+
162
+ const result = {
163
+ identifier,
164
+ images,
165
+ barcode: '',
166
+ ean: '',
167
+ gtin: '',
168
+ name: product.name[context.languageContext.locale],
169
+ options,
170
+ upc: '',
171
+ } satisfies ProductVariant;
172
+
173
+ return result;
174
+ }
175
+
176
+ protected parseAttribute(
177
+ context: RequestContext,
178
+ attr: CTAttribute,
179
+ ): ProductAttribute {
180
+ const result = ProductAttributeSchema.parse({
181
+ identifier: ProductAttributeIdentifierSchema.parse({
182
+ key: attr.name,
183
+ } satisfies Partial<ProductAttributeIdentifier>),
184
+ group: '',
185
+ name: attr.name,
186
+ values: [this.parseAttributeValue(context, attr)],
187
+ } satisfies Partial<ProductAttribute>);
188
+
189
+ return result;
190
+ }
191
+
192
+ protected parseAttributeValue(context: RequestContext, attr: CTAttribute): ProductAttributeValue {
193
+ let attrValue = '';
194
+ if (attr.value && Array.isArray(attr.value)) {
195
+ attrValue = attr.value[0];
196
+ }
197
+
198
+ if (attr.value && typeof attr.value === 'object') {
199
+ if (context.languageContext.locale in attr.value) {
200
+ attrValue = attr.value[context.languageContext.locale];
201
+ } else {
202
+ attrValue = '-';
203
+ }
204
+ }
205
+
206
+ if (typeof attr.value === 'string') {
207
+ attrValue = attr.value;
208
+ }
209
+
210
+ const attrVal = ProductAttributeValueSchema.parse({
211
+ identifier: ProductAttributeValueIdentifierSchema.parse({
212
+ key: attrValue,
213
+ } satisfies Partial<ProductAttributeValueIdentifier>),
214
+ value: String(attrValue),
215
+ label: String(attrValue),
216
+ } satisfies Partial<ProductAttributeValue>);
217
+
218
+ return attrVal;
219
+ }
220
+ }
@@ -0,0 +1,9 @@
1
+ export const assertType = <T>(_value: T) => {
2
+ void _value;
3
+ };
4
+
5
+ type IsAny<T> = 0 extends (1 & T) ? true : false;
6
+
7
+ export const assertNotAny = <T>(_value: IsAny<T> extends true ? never : T) => {
8
+ void _value;
9
+ };
@@ -0,0 +1,103 @@
1
+ import type { ProductProjection, ProductVariant as CTProductVariant } from '@commercetools/platform-sdk';
2
+ import type {
3
+ ProductAssociationSchema} from '@reactionary/core';
4
+ import {
5
+ ImageSchema,
6
+ ProductOptionIdentifierSchema,
7
+ ProductSearchResultItemVariantSchema,
8
+ ProductVariantIdentifierSchema,
9
+ ProductVariantOptionSchema,
10
+ type AnyProductAssociationSchema,
11
+ type ProductAssociation,
12
+ type ProductAssociationsFactory,
13
+ type ProductOptionIdentifier,
14
+ type ProductSearchResultItem,
15
+ type ProductSearchResultItemVariant,
16
+ type ProductVariantIdentifier,
17
+ type ProductVariantOption,
18
+ type RequestContext,
19
+ } from '@reactionary/core';
20
+ import type * as z from 'zod';
21
+
22
+ export class CommercetoolsProductAssociationsFactory<
23
+ TProductAssociationSchema extends AnyProductAssociationSchema = typeof ProductAssociationSchema,
24
+ > implements ProductAssociationsFactory<TProductAssociationSchema>
25
+ {
26
+ public readonly productAssociationSchema: TProductAssociationSchema;
27
+
28
+ constructor(productAssociationSchema: TProductAssociationSchema) {
29
+ this.productAssociationSchema = productAssociationSchema;
30
+ }
31
+
32
+ public parseAssociation(
33
+ context: RequestContext,
34
+ data: {
35
+ sourceProductKey: string;
36
+ relation: 'accessory' | 'sparepart' | 'replacement';
37
+ product: ProductProjection;
38
+ },
39
+ ): z.output<TProductAssociationSchema> {
40
+ const product = this.parseSearchResultItem(context, data.product);
41
+ const result = {
42
+ associationIdentifier: {
43
+ key: `${data.sourceProductKey}-${data.relation}-${product.identifier.key}`,
44
+ },
45
+ associationReturnType: 'productSearchResultItem',
46
+ product,
47
+ } satisfies ProductAssociation;
48
+
49
+ return this.productAssociationSchema.parse(result);
50
+ }
51
+
52
+ protected parseSearchResultItem(
53
+ context: RequestContext,
54
+ data: ProductProjection,
55
+ ): ProductSearchResultItem {
56
+ const variants = [data.masterVariant, ...data.variants].map((variant) =>
57
+ this.parseVariant(context, variant, data),
58
+ );
59
+
60
+ return {
61
+ identifier: { key: data.id },
62
+ name: data.name[context.languageContext.locale] || data.id,
63
+ slug: data.slug?.[context.languageContext.locale] || data.id,
64
+ variants,
65
+ } satisfies ProductSearchResultItem;
66
+ }
67
+
68
+ protected parseVariant(
69
+ context: RequestContext,
70
+ variant: CTProductVariant,
71
+ product: ProductProjection,
72
+ ): ProductSearchResultItemVariant {
73
+ const sourceImage = variant.images?.[0];
74
+ const image = ImageSchema.parse({
75
+ sourceUrl: sourceImage?.url || '',
76
+ height: sourceImage?.dimensions.h || undefined,
77
+ width: sourceImage?.dimensions.w || undefined,
78
+ altText: sourceImage?.label || product.name[context.languageContext.locale] || undefined,
79
+ });
80
+
81
+ const mappedOptions =
82
+ variant.attributes
83
+ ?.filter((attribute) => attribute.name === 'Color')
84
+ .map((option) =>
85
+ ProductVariantOptionSchema.parse({
86
+ identifier: ProductOptionIdentifierSchema.parse({
87
+ key: option.name,
88
+ } satisfies Partial<ProductOptionIdentifier>),
89
+ name: option.value || '',
90
+ } satisfies Partial<ProductVariantOption>),
91
+ ) || [];
92
+
93
+ const mappedOption = mappedOptions?.[0];
94
+
95
+ return ProductSearchResultItemVariantSchema.parse({
96
+ variant: ProductVariantIdentifierSchema.parse({
97
+ sku: variant.sku || '',
98
+ } satisfies ProductVariantIdentifier),
99
+ image,
100
+ options: mappedOption,
101
+ } satisfies Partial<ProductSearchResultItemVariant>);
102
+ }
103
+ }