@reactionary/source 0.0.52 → 0.2.16

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 (293) hide show
  1. package/.env-template +19 -0
  2. package/.github/workflows/pull-request.yml +3 -1
  3. package/.github/workflows/release.yml +9 -0
  4. package/.vscode/extensions.json +0 -2
  5. package/LICENSE +21 -0
  6. package/README.md +175 -23
  7. package/core/package.json +6 -3
  8. package/core/src/cache/cache.interface.ts +1 -0
  9. package/core/src/cache/index.ts +4 -0
  10. package/core/src/cache/memory-cache.ts +30 -2
  11. package/core/src/cache/noop-cache.ts +15 -1
  12. package/core/src/cache/redis-cache.ts +20 -0
  13. package/core/src/client/client-builder.ts +71 -54
  14. package/core/src/client/client.ts +9 -47
  15. package/core/src/client/index.ts +2 -0
  16. package/core/src/decorators/index.ts +1 -0
  17. package/core/src/decorators/reactionary.decorator.ts +203 -34
  18. package/core/src/index.ts +6 -19
  19. package/core/src/initialization.ts +1 -18
  20. package/core/src/metrics/metrics.ts +67 -0
  21. package/core/src/providers/analytics.provider.ts +1 -6
  22. package/core/src/providers/base.provider.ts +5 -69
  23. package/core/src/providers/cart.provider.ts +15 -55
  24. package/core/src/providers/category.provider.ts +7 -11
  25. package/core/src/providers/checkout.provider.ts +17 -15
  26. package/core/src/providers/identity.provider.ts +6 -8
  27. package/core/src/providers/index.ts +2 -1
  28. package/core/src/providers/inventory.provider.ts +15 -5
  29. package/core/src/providers/order-search.provider.ts +29 -0
  30. package/core/src/providers/order.provider.ts +47 -15
  31. package/core/src/providers/price.provider.ts +30 -36
  32. package/core/src/providers/product-search.provider.ts +61 -0
  33. package/core/src/providers/product.provider.ts +71 -12
  34. package/core/src/providers/profile.provider.ts +74 -14
  35. package/core/src/providers/store.provider.ts +3 -5
  36. package/core/src/schemas/capabilities.schema.ts +10 -3
  37. package/core/src/schemas/errors/generic.error.ts +9 -0
  38. package/core/src/schemas/errors/index.ts +4 -0
  39. package/core/src/schemas/errors/invalid-input.error.ts +9 -0
  40. package/core/src/schemas/errors/invalid-output.error.ts +9 -0
  41. package/core/src/schemas/errors/not-found.error.ts +9 -0
  42. package/core/src/schemas/index.ts +7 -0
  43. package/core/src/schemas/models/analytics.model.ts +2 -1
  44. package/core/src/schemas/models/base.model.ts +6 -24
  45. package/core/src/schemas/models/cart.model.ts +5 -8
  46. package/core/src/schemas/models/category.model.ts +4 -9
  47. package/core/src/schemas/models/checkout.model.ts +6 -7
  48. package/core/src/schemas/models/cost.model.ts +4 -3
  49. package/core/src/schemas/models/currency.model.ts +2 -1
  50. package/core/src/schemas/models/identifiers.model.ts +106 -62
  51. package/core/src/schemas/models/identity.model.ts +10 -19
  52. package/core/src/schemas/models/index.ts +2 -1
  53. package/core/src/schemas/models/inventory.model.ts +8 -5
  54. package/core/src/schemas/models/order-search.model.ts +28 -0
  55. package/core/src/schemas/models/order.model.ts +20 -26
  56. package/core/src/schemas/models/payment.model.ts +14 -17
  57. package/core/src/schemas/models/price.model.ts +11 -11
  58. package/core/src/schemas/models/product-search.model.ts +42 -0
  59. package/core/src/schemas/models/product.model.ts +64 -22
  60. package/core/src/schemas/models/profile.model.ts +19 -22
  61. package/core/src/schemas/models/shipping-method.model.ts +24 -29
  62. package/core/src/schemas/models/store.model.ts +9 -5
  63. package/core/src/schemas/mutations/analytics.mutation.ts +8 -7
  64. package/core/src/schemas/mutations/base.mutation.ts +2 -1
  65. package/core/src/schemas/mutations/cart.mutation.ts +33 -33
  66. package/core/src/schemas/mutations/checkout.mutation.ts +23 -30
  67. package/core/src/schemas/mutations/identity.mutation.ts +4 -3
  68. package/core/src/schemas/mutations/profile.mutation.ts +38 -3
  69. package/core/src/schemas/queries/base.query.ts +2 -1
  70. package/core/src/schemas/queries/cart.query.ts +3 -3
  71. package/core/src/schemas/queries/category.query.ts +18 -18
  72. package/core/src/schemas/queries/checkout.query.ts +7 -9
  73. package/core/src/schemas/queries/identity.query.ts +2 -1
  74. package/core/src/schemas/queries/index.ts +2 -1
  75. package/core/src/schemas/queries/inventory.query.ts +5 -5
  76. package/core/src/schemas/queries/order-search.query.ts +10 -0
  77. package/core/src/schemas/queries/order.query.ts +3 -2
  78. package/core/src/schemas/queries/price.query.ts +10 -4
  79. package/core/src/schemas/queries/product-search.query.ts +16 -0
  80. package/core/src/schemas/queries/product.query.ts +13 -6
  81. package/core/src/schemas/queries/profile.query.ts +5 -2
  82. package/core/src/schemas/queries/store.query.ts +6 -5
  83. package/core/src/schemas/result.ts +107 -0
  84. package/core/src/schemas/session.schema.ts +4 -4
  85. package/core/src/test/reactionary.decorator.spec.ts +249 -0
  86. package/core/src/zod-utils.ts +19 -0
  87. package/core/tsconfig.json +1 -1
  88. package/core/tsconfig.spec.json +2 -26
  89. package/core/vitest.config.ts +14 -0
  90. package/documentation/1-purpose.md +114 -0
  91. package/documentation/2-getting-started.md +229 -0
  92. package/documentation/3-querying-and-changing-data.md +74 -0
  93. package/documentation/4-product-data.md +107 -0
  94. package/documentation/5-cart-and-checkout.md +211 -0
  95. package/documentation/6-product-search.md +143 -0
  96. package/documentation/7-marketing.md +3 -0
  97. package/eslint.config.mjs +1 -0
  98. package/examples/node/eslint.config.mjs +1 -4
  99. package/examples/node/package.json +10 -3
  100. package/examples/node/project.json +4 -1
  101. package/examples/node/src/basic/basic-node-provider-model-extension.spec.ts +22 -23
  102. package/examples/node/src/basic/basic-node-provider-query-extension.spec.ts +15 -11
  103. package/examples/node/src/basic/basic-node-setup.spec.ts +44 -28
  104. package/examples/node/src/basic/client-creation.spec.ts +53 -0
  105. package/examples/node/src/capabilities/cart.spec.ts +255 -0
  106. package/examples/node/src/capabilities/category.spec.ts +193 -0
  107. package/examples/node/src/capabilities/checkout.spec.ts +341 -0
  108. package/examples/node/src/capabilities/identity.spec.ts +93 -0
  109. package/examples/node/src/capabilities/inventory.spec.ts +66 -0
  110. package/examples/node/src/capabilities/order-search.spec.ts +265 -0
  111. package/examples/node/src/capabilities/order.spec.ts +91 -0
  112. package/examples/node/src/capabilities/price.spec.ts +51 -0
  113. package/examples/node/src/capabilities/product-search.spec.ts +293 -0
  114. package/examples/node/src/capabilities/product.spec.ts +122 -0
  115. package/examples/node/src/capabilities/profile.spec.ts +316 -0
  116. package/examples/node/src/capabilities/store.spec.ts +26 -0
  117. package/examples/node/src/utils.ts +147 -0
  118. package/examples/node/tsconfig.json +9 -12
  119. package/examples/node/tsconfig.lib.json +1 -2
  120. package/examples/node/tsconfig.spec.json +2 -14
  121. package/examples/node/vitest.config.ts +14 -0
  122. package/migrations.json +22 -5
  123. package/nx.json +8 -47
  124. package/package.json +24 -96
  125. package/providers/algolia/README.md +39 -2
  126. package/providers/algolia/package.json +2 -1
  127. package/providers/algolia/src/core/initialize.ts +7 -14
  128. package/providers/algolia/src/index.ts +2 -4
  129. package/providers/algolia/src/providers/index.ts +1 -0
  130. package/providers/algolia/src/providers/product-search.provider.ts +241 -0
  131. package/providers/algolia/src/schema/capabilities.schema.ts +2 -3
  132. package/providers/algolia/src/schema/index.ts +3 -0
  133. package/providers/algolia/src/schema/search.schema.ts +8 -8
  134. package/providers/algolia/tsconfig.json +1 -1
  135. package/providers/algolia/tsconfig.lib.json +1 -1
  136. package/providers/algolia/tsconfig.spec.json +2 -14
  137. package/providers/algolia/vitest.config.ts +14 -0
  138. package/providers/commercetools/README.md +30 -3
  139. package/providers/commercetools/package.json +2 -1
  140. package/providers/commercetools/src/core/client.ts +178 -99
  141. package/providers/commercetools/src/core/initialize.ts +130 -74
  142. package/providers/commercetools/src/core/token-cache.ts +45 -0
  143. package/providers/commercetools/src/index.ts +3 -2
  144. package/providers/commercetools/src/providers/cart.provider.ts +281 -341
  145. package/providers/commercetools/src/providers/category.provider.ts +223 -138
  146. package/providers/commercetools/src/providers/checkout.provider.ts +631 -449
  147. package/providers/commercetools/src/providers/identity.provider.ts +50 -29
  148. package/providers/commercetools/src/providers/index.ts +2 -2
  149. package/providers/commercetools/src/providers/inventory.provider.ts +76 -74
  150. package/providers/commercetools/src/providers/order-search.provider.ts +220 -0
  151. package/providers/commercetools/src/providers/order.provider.ts +96 -61
  152. package/providers/commercetools/src/providers/price.provider.ts +147 -117
  153. package/providers/commercetools/src/providers/product-search.provider.ts +528 -0
  154. package/providers/commercetools/src/providers/product.provider.ts +249 -74
  155. package/providers/commercetools/src/providers/profile.provider.ts +445 -28
  156. package/providers/commercetools/src/providers/store.provider.ts +54 -40
  157. package/providers/commercetools/src/schema/capabilities.schema.ts +3 -1
  158. package/providers/commercetools/src/schema/commercetools.schema.ts +17 -3
  159. package/providers/commercetools/src/schema/configuration.schema.ts +1 -0
  160. package/providers/commercetools/src/schema/session.schema.ts +7 -0
  161. package/providers/commercetools/src/test/caching.spec.ts +82 -0
  162. package/providers/commercetools/src/test/identity.spec.ts +109 -0
  163. package/providers/commercetools/src/test/test-utils.ts +21 -19
  164. package/providers/commercetools/tsconfig.json +1 -1
  165. package/providers/commercetools/tsconfig.lib.json +1 -1
  166. package/providers/commercetools/tsconfig.spec.json +2 -14
  167. package/providers/commercetools/vitest.config.ts +15 -0
  168. package/providers/fake/README.md +20 -4
  169. package/providers/fake/package.json +2 -1
  170. package/providers/fake/src/core/initialize.ts +47 -49
  171. package/providers/fake/src/providers/analytics.provider.ts +5 -7
  172. package/providers/fake/src/providers/cart.provider.ts +163 -92
  173. package/providers/fake/src/providers/category.provider.ts +78 -50
  174. package/providers/fake/src/providers/checkout.provider.ts +254 -0
  175. package/providers/fake/src/providers/identity.provider.ts +57 -65
  176. package/providers/fake/src/providers/index.ts +6 -2
  177. package/providers/fake/src/providers/inventory.provider.ts +40 -36
  178. package/providers/fake/src/providers/order-search.provider.ts +78 -0
  179. package/providers/fake/src/providers/order.provider.ts +106 -0
  180. package/providers/fake/src/providers/price.provider.ts +93 -41
  181. package/providers/fake/src/providers/product-search.provider.ts +206 -0
  182. package/providers/fake/src/providers/product.provider.ts +56 -41
  183. package/providers/fake/src/providers/profile.provider.ts +147 -0
  184. package/providers/fake/src/providers/store.provider.ts +30 -20
  185. package/providers/fake/src/schema/capabilities.schema.ts +5 -1
  186. package/providers/fake/src/test/cart.provider.spec.ts +59 -80
  187. package/providers/fake/src/test/category.provider.spec.ts +145 -87
  188. package/providers/fake/src/test/checkout.provider.spec.ts +222 -0
  189. package/providers/fake/src/test/order-search.provider.spec.ts +50 -0
  190. package/providers/fake/src/test/order.provider.spec.ts +44 -0
  191. package/providers/fake/src/test/price.provider.spec.ts +50 -45
  192. package/providers/fake/src/test/product.provider.spec.ts +15 -7
  193. package/providers/fake/src/test/profile.provider.spec.ts +167 -0
  194. package/providers/fake/tsconfig.json +1 -1
  195. package/providers/fake/tsconfig.lib.json +1 -1
  196. package/providers/fake/tsconfig.spec.json +2 -12
  197. package/providers/fake/vitest.config.ts +14 -0
  198. package/providers/medusa/README.md +30 -0
  199. package/providers/medusa/TESTING.md +98 -0
  200. package/providers/medusa/eslint.config.mjs +19 -0
  201. package/providers/medusa/package.json +22 -0
  202. package/providers/medusa/project.json +34 -0
  203. package/providers/medusa/src/core/client.ts +370 -0
  204. package/providers/medusa/src/core/initialize.ts +78 -0
  205. package/providers/medusa/src/index.ts +13 -0
  206. package/providers/medusa/src/providers/cart.provider.ts +575 -0
  207. package/providers/medusa/src/providers/category.provider.ts +247 -0
  208. package/providers/medusa/src/providers/checkout.provider.ts +636 -0
  209. package/providers/medusa/src/providers/identity.provider.ts +137 -0
  210. package/providers/medusa/src/providers/inventory.provider.ts +173 -0
  211. package/providers/medusa/src/providers/order-search.provider.ts +202 -0
  212. package/providers/medusa/src/providers/order.provider.ts +226 -0
  213. package/providers/medusa/src/providers/price.provider.ts +140 -0
  214. package/providers/medusa/src/providers/product-search.provider.ts +243 -0
  215. package/providers/medusa/src/providers/product.provider.ts +261 -0
  216. package/providers/medusa/src/providers/profile.provider.ts +392 -0
  217. package/providers/medusa/src/schema/capabilities.schema.ts +18 -0
  218. package/providers/medusa/src/schema/configuration.schema.ts +11 -0
  219. package/providers/medusa/src/schema/medusa.schema.ts +31 -0
  220. package/providers/medusa/src/test/cart.provider.spec.ts +240 -0
  221. package/providers/medusa/src/test/category.provider.spec.ts +231 -0
  222. package/providers/medusa/src/test/checkout.spec.ts +349 -0
  223. package/providers/medusa/src/test/identity.provider.spec.ts +122 -0
  224. package/providers/medusa/src/test/inventory.provider.spec.ts +88 -0
  225. package/providers/medusa/src/test/large-cart.provider.spec.ts +103 -0
  226. package/providers/medusa/src/test/price.provider.spec.ts +104 -0
  227. package/providers/medusa/src/test/product.provider.spec.ts +146 -0
  228. package/providers/medusa/src/test/search.provider.spec.ts +203 -0
  229. package/providers/medusa/src/test/test-utils.ts +13 -0
  230. package/providers/medusa/src/utils/medusa-helpers.ts +89 -0
  231. package/providers/medusa/tsconfig.json +21 -0
  232. package/providers/medusa/tsconfig.lib.json +9 -0
  233. package/providers/medusa/tsconfig.spec.json +4 -0
  234. package/providers/medusa/vitest.config.ts +15 -0
  235. package/providers/meilisearch/README.md +48 -0
  236. package/providers/meilisearch/eslint.config.mjs +22 -0
  237. package/providers/meilisearch/package.json +13 -0
  238. package/providers/meilisearch/project.json +34 -0
  239. package/providers/meilisearch/src/core/initialize.ts +21 -0
  240. package/providers/meilisearch/src/index.ts +6 -0
  241. package/providers/meilisearch/src/providers/index.ts +1 -0
  242. package/providers/meilisearch/src/providers/order-search.provider.ts +222 -0
  243. package/providers/meilisearch/src/providers/product-search.provider.ts +251 -0
  244. package/providers/meilisearch/src/schema/capabilities.schema.ts +10 -0
  245. package/providers/meilisearch/src/schema/configuration.schema.ts +11 -0
  246. package/providers/meilisearch/src/schema/index.ts +3 -0
  247. package/providers/meilisearch/src/schema/search.schema.ts +14 -0
  248. package/providers/meilisearch/tsconfig.json +24 -0
  249. package/providers/meilisearch/tsconfig.lib.json +10 -0
  250. package/providers/meilisearch/tsconfig.spec.json +4 -0
  251. package/providers/meilisearch/vitest.config.ts +14 -0
  252. package/providers/posthog/package.json +2 -1
  253. package/providers/posthog/tsconfig.json +1 -1
  254. package/tsconfig.base.json +5 -0
  255. package/vitest.config.ts +10 -0
  256. package/core/src/providers/search.provider.ts +0 -18
  257. package/core/src/schemas/models/search.model.ts +0 -36
  258. package/core/src/schemas/queries/search.query.ts +0 -9
  259. package/examples/next/.swcrc +0 -30
  260. package/examples/next/eslint.config.mjs +0 -21
  261. package/examples/next/index.d.ts +0 -6
  262. package/examples/next/next-env.d.ts +0 -5
  263. package/examples/next/next.config.js +0 -31
  264. package/examples/next/project.json +0 -9
  265. package/examples/next/public/.gitkeep +0 -0
  266. package/examples/next/public/favicon.ico +0 -0
  267. package/examples/next/src/app/global.css +0 -0
  268. package/examples/next/src/app/layout.tsx +0 -18
  269. package/examples/next/src/app/page.module.scss +0 -2
  270. package/examples/next/src/app/page.tsx +0 -47
  271. package/examples/next/src/instrumentation.ts +0 -9
  272. package/examples/next/tsconfig.json +0 -44
  273. package/examples/node/jest.config.ts +0 -10
  274. package/jest.config.ts +0 -6
  275. package/jest.preset.js +0 -3
  276. package/providers/algolia/jest.config.ts +0 -10
  277. package/providers/algolia/src/providers/product.provider.ts +0 -66
  278. package/providers/algolia/src/providers/search.provider.ts +0 -106
  279. package/providers/algolia/src/test/search.provider.spec.ts +0 -91
  280. package/providers/commercetools/jest.config.cjs +0 -10
  281. package/providers/commercetools/src/providers/search.provider.ts +0 -96
  282. package/providers/commercetools/src/test/cart.provider.spec.ts +0 -199
  283. package/providers/commercetools/src/test/category.provider.spec.ts +0 -168
  284. package/providers/commercetools/src/test/checkout.provider.spec.ts +0 -312
  285. package/providers/commercetools/src/test/identity.provider.spec.ts +0 -88
  286. package/providers/commercetools/src/test/inventory.provider.spec.ts +0 -41
  287. package/providers/commercetools/src/test/price.provider.spec.ts +0 -81
  288. package/providers/commercetools/src/test/product.provider.spec.ts +0 -80
  289. package/providers/commercetools/src/test/profile.provider.spec.ts +0 -49
  290. package/providers/commercetools/src/test/search.provider.spec.ts +0 -61
  291. package/providers/commercetools/src/test/store.provider.spec.ts +0 -37
  292. package/providers/fake/jest.config.cjs +0 -10
  293. package/providers/fake/src/providers/search.provider.ts +0 -132
@@ -1,61 +1,478 @@
1
1
  import type {
2
2
  Profile,
3
+ ProfileMutationAddShippingAddress,
4
+ ProfileMutationMakeShippingAddressDefault,
5
+ ProfileMutationRemoveShippingAddress,
6
+ ProfileMutationSetBillingAddress,
3
7
  ProfileMutationUpdate,
4
8
  ProfileQuerySelf,
5
9
  RequestContext,
10
+ Result,
11
+ NotFoundError,
12
+ InvalidInputError,
13
+ Address,
14
+ ProfileMutationUpdateShippingAddress
15
+ } from '@reactionary/core';
16
+ import {
17
+ ProfileMutationUpdateSchema,
18
+ ProfileProvider,
19
+ ProfileSchema,
20
+ Reactionary,
21
+ success,
22
+ error,
23
+ ProfileMutationSetBillingAddressSchema,
24
+ ProfileMutationRemoveShippingAddressSchema,
25
+ ProfileMutationAddShippingAddressSchema
6
26
  } from '@reactionary/core';
7
- import { ProfileProvider } from '@reactionary/core';
8
27
  import type z from 'zod';
9
- import { CommercetoolsClient } from '../core/client.js';
10
28
  import type { CommercetoolsConfiguration } from '../schema/configuration.schema.js';
11
29
  import type { Cache } from '@reactionary/core';
12
- import type { Customer } from '@commercetools/platform-sdk';
30
+ import type { Customer, MyCustomerUpdateAction, Address as CTAddress } from '@commercetools/platform-sdk';
31
+ import type { CommercetoolsAPI } from '../core/client.js';
13
32
 
14
- export class CommercetoolsProfileProvider<
15
- T extends Profile = Profile
16
- > extends ProfileProvider<T> {
33
+ export class CommercetoolsProfileProvider extends ProfileProvider {
17
34
  protected config: CommercetoolsConfiguration;
35
+ protected commercetools: CommercetoolsAPI;
18
36
 
19
37
  constructor(
20
38
  config: CommercetoolsConfiguration,
21
- schema: z.ZodType<T>,
22
- cache: Cache
39
+ cache: Cache,
40
+ context: RequestContext,
41
+ commercetools: CommercetoolsAPI
23
42
  ) {
24
- super(schema, cache);
43
+ super(cache, context);
25
44
 
26
45
  this.config = config;
46
+ this.commercetools = commercetools;
27
47
  }
28
48
 
29
- protected async getClient(reqCtx: RequestContext) {
30
- const client = await new CommercetoolsClient(this.config).getClient(reqCtx);
49
+ protected async getClient() {
50
+ const client = await this.commercetools.getClient();
31
51
  return client.withProjectKey({ projectKey: this.config.projectKey });
32
52
  }
33
53
 
34
- public override async getSelf(
35
- payload: ProfileQuerySelf,
36
- reqCtx: RequestContext
37
- ): Promise<T> {
38
- const client = await this.getClient(reqCtx);
54
+ public override async getById(payload: ProfileQuerySelf): Promise<Result<Profile, NotFoundError>> {
55
+ const client = await this.getClient();
56
+
57
+ const remote = await client.me().get().execute();
58
+
59
+ if (remote.body.id !== payload.identifier.userId) {
60
+ return error<NotFoundError>({
61
+ type: 'NotFound',
62
+ identifier: payload.identifier,
63
+ });
64
+ }
65
+
66
+ const model = this.parseSingle(remote.body);
67
+ return success(model);
68
+ }
69
+
70
+
71
+
72
+ @Reactionary({
73
+ inputSchema: ProfileMutationAddShippingAddressSchema,
74
+ outputSchema: ProfileSchema,
75
+ })
76
+ public override async addShippingAddress(payload: ProfileMutationAddShippingAddress): Promise<Result<Profile, NotFoundError>> {
77
+ const client = await this.getClient();
78
+
79
+ const remote = await client.me().get().execute();
80
+ let customer = remote.body;
81
+
82
+ if (customer.id !== payload.identifier.userId) {
83
+ return error<NotFoundError>({
84
+ type: 'NotFound',
85
+ identifier: payload.identifier,
86
+ });
87
+ }
88
+
89
+ const updateResponse = await client
90
+ .me()
91
+ .post({
92
+ body: {
93
+ version: customer.version,
94
+ actions: [
95
+ {
96
+ action: 'addAddress',
97
+ address: this.createCTAddressDraft(payload.address)
98
+ }
99
+ ] as MyCustomerUpdateAction[],
100
+ }
101
+ })
102
+ .execute();
103
+ customer = updateResponse.body;
104
+ const model = this.parseSingle(customer);
105
+ return success(model);
106
+ }
107
+
108
+ public override async updateShippingAddress(payload: ProfileMutationUpdateShippingAddress): Promise<Result<Profile, NotFoundError>> {
109
+ const client = await this.getClient();
110
+
111
+ const remote = await client.me().get().execute();
112
+ let customer = remote.body;
113
+
114
+ if (customer.id !== payload.identifier.userId) {
115
+ return error<NotFoundError>({
116
+ type: 'NotFound',
117
+ identifier: payload.identifier,
118
+ });
119
+ }
120
+
121
+ const targetAddress = customer.addresses.find(addr => addr.key === payload.address.identifier.nickName);
122
+
123
+ if (!targetAddress) {
124
+ return error<NotFoundError>({
125
+ type: 'NotFound',
126
+ identifier: payload.identifier,
127
+ });
128
+ }
129
+
130
+
131
+ const updateResponse = await client
132
+ .me()
133
+ .post({
134
+ body: {
135
+ version: customer.version,
136
+ actions: [
137
+ {
138
+ action: 'changeAddress',
139
+ addressId: targetAddress.id!,
140
+ address: this.createCTAddressDraft(payload.address)
141
+ }
142
+ ] as MyCustomerUpdateAction[],
143
+ }
144
+ })
145
+ .execute();
146
+ customer = updateResponse.body;
147
+ const model = this.parseSingle(customer);
148
+ return success(model);
149
+ }
150
+
151
+ @Reactionary({
152
+ inputSchema: ProfileMutationRemoveShippingAddressSchema,
153
+ outputSchema: ProfileSchema,
154
+ })
155
+ public override async removeShippingAddress(payload: ProfileMutationRemoveShippingAddress): Promise<Result<Profile, NotFoundError>> {
156
+ const client = await this.getClient();
157
+
158
+ const remote = await client.me().get().execute();
159
+ let customer = remote.body;
160
+
161
+ if (customer.id !== payload.identifier.userId) {
162
+ return error<NotFoundError>({
163
+ type: 'NotFound',
164
+ identifier: payload.identifier,
165
+ });
166
+ }
167
+
168
+ const updateActions: MyCustomerUpdateAction[] = [];
169
+ const addressToRemove = customer.addresses.find(addr => addr.key === payload.addressIdentifier.nickName);
170
+
171
+ if (addressToRemove) {
172
+ updateActions.push({
173
+ action: 'removeAddress',
174
+ addressId: addressToRemove.id!
175
+ });
176
+ } else {
177
+ return error<NotFoundError>({
178
+ type: 'NotFound',
179
+ identifier: payload.identifier,
180
+ });
181
+ }
182
+
183
+ /**
184
+ * if the address we remove is the default shipping address, we check to see if there are other non-billing addresses available, and make a random one
185
+ * the new shipping address
186
+ */
187
+ const needsNewDefaultShippingAddress = addressToRemove && customer.defaultShippingAddressId === addressToRemove.id;
188
+ if (needsNewDefaultShippingAddress) {
189
+ const newDefaultAddress = customer.addresses.find(addr => addr.id !== addressToRemove.id && addr.id !== customer.defaultBillingAddressId);
190
+ if (newDefaultAddress) {
191
+ updateActions.push({
192
+ action: 'setDefaultShippingAddress',
193
+ addressKey: newDefaultAddress.key
194
+ });
195
+ }
196
+ }
197
+
198
+
199
+ const updateResponse = await client
200
+ .me()
201
+ .post({
202
+ body: {
203
+ version: customer.version,
204
+ actions: updateActions,
205
+ },
206
+ })
207
+ .execute();
208
+ customer = updateResponse.body;
209
+
210
+ const model = this.parseSingle(customer);
211
+ return success(model);
212
+ }
213
+
214
+
215
+
216
+ public override async makeShippingAddressDefault(payload: ProfileMutationMakeShippingAddressDefault): Promise<Result<Profile, NotFoundError>> {
217
+ const client = await this.getClient();
39
218
 
40
219
  const remote = await client.me().get().execute();
41
- const model = this.parseSingle(remote.body, reqCtx);
220
+ let customer = remote.body;
221
+
222
+ if (customer.id !== payload.identifier.userId) {
223
+ return error<NotFoundError>({
224
+ type: 'NotFound',
225
+ identifier: payload.identifier,
226
+ });
227
+ }
228
+ const addressToMakeDefault = customer.addresses.find(addr => addr.key === payload.addressIdentifier.nickName);
229
+
230
+ if (!addressToMakeDefault) {
231
+ return error<NotFoundError>({
232
+ type: 'NotFound',
233
+ identifier: payload.identifier,
234
+ });
235
+ }
236
+
237
+ if (addressToMakeDefault.id === customer.defaultBillingAddressId) {
238
+ return error<InvalidInputError>({
239
+ type: 'InvalidInput',
240
+ error: {
241
+ field: 'addressIdentifier',
242
+ message: 'Cannot set shipping address as default billing address',
243
+ }
244
+ });
245
+ }
42
246
 
43
- return model;
247
+
248
+ const updateResponse = await client
249
+ .me()
250
+ .post({
251
+ body: {
252
+ version: customer.version,
253
+ actions: [
254
+ {
255
+ action: 'setDefaultShippingAddress',
256
+ addressKey: addressToMakeDefault.key
257
+ }
258
+ ] as MyCustomerUpdateAction[],
259
+ },
260
+ })
261
+ .execute();
262
+ customer = updateResponse.body;
263
+ const model = this.parseSingle(customer);
264
+ return success(model);
44
265
  }
45
266
 
46
- public override async update(
47
- payload: ProfileMutationUpdate,
48
- reqCtx: RequestContext
49
- ): Promise<T> {
50
- throw new Error('Method not implemented.');
267
+
268
+ @Reactionary({
269
+ inputSchema: ProfileMutationSetBillingAddressSchema,
270
+ outputSchema: ProfileSchema,
271
+ })
272
+ public override async setBillingAddress(payload: ProfileMutationSetBillingAddress): Promise<Result<Profile, NotFoundError>> {
273
+ const client = await this.getClient();
274
+
275
+ const remote = await client.me().get().execute();
276
+ let customer = remote.body;
277
+
278
+ if (customer.id !== payload.identifier.userId) {
279
+ return error<NotFoundError>({
280
+ type: 'NotFound',
281
+ identifier: payload.identifier,
282
+ });
283
+ }
284
+
285
+ const updateActions: MyCustomerUpdateAction[] = [];
286
+ const mainAddress = customer.defaultBillingAddressId ? customer.addresses.find(addr => addr.id === customer.defaultBillingAddressId): null;
287
+ if (!mainAddress) {
288
+ const newAddress = this.createCTAddressDraft(payload.address);
289
+ updateActions.push({
290
+ action: 'addAddress',
291
+ address: newAddress
292
+ });
293
+ updateActions.push({
294
+ action: 'setDefaultBillingAddress',
295
+ addressKey: newAddress.key
296
+ });
297
+ } else {
298
+ updateActions.push({
299
+ action: 'changeAddress',
300
+ addressId: mainAddress.id!,
301
+ address: this.createCTAddressDraft( payload.address)
302
+ });
303
+ }
304
+
305
+ if (updateActions.length > 0) {
306
+ const updateResponse = await client
307
+ .me()
308
+ .post({
309
+ body: {
310
+ version: customer.version,
311
+ actions: updateActions,
312
+ },
313
+ })
314
+ .execute();
315
+ customer = updateResponse.body;
316
+ }
317
+ const model = this.parseSingle(customer);
318
+ return success(model);
51
319
  }
52
320
 
53
- protected override parseSingle(body: Customer, reqCtx: RequestContext): T {
54
- const model = this.newModel();
321
+ @Reactionary({
322
+ inputSchema: ProfileMutationUpdateSchema,
323
+ outputSchema: ProfileSchema,
324
+ })
325
+ public override async update(payload: ProfileMutationUpdate): Promise<Result<Profile, NotFoundError>> {
326
+ const client = await this.getClient();
327
+
328
+ const remote = await client.me().get().execute();
329
+ let customer = remote.body;
330
+
331
+ if (customer.id !== payload.identifier.userId) {
332
+ return error<NotFoundError>({
333
+ type: 'NotFound',
334
+ identifier: payload.identifier,
335
+ });
336
+ }
337
+
338
+ const updateActions: MyCustomerUpdateAction[] = [];
339
+ if (payload.email !== undefined) {
340
+ updateActions.push({
341
+ action: 'changeEmail',
342
+ email: payload.email,
343
+ });
344
+ }
345
+
346
+
347
+ const mainAddress = customer.defaultBillingAddressId ? customer.addresses.find(addr => addr.id === customer.defaultBillingAddressId): null;
348
+ if (!mainAddress) {
349
+ updateActions.push({
350
+ action: 'addAddress',
351
+ address: {
352
+ key: `billing-address-${customer.id}`,
353
+ email: payload.email || customer.email,
354
+ phone: payload.phone,
355
+ country: this.context.taxJurisdiction.countryCode
356
+ }
357
+ });
358
+
359
+ updateActions.push({
360
+ action: 'setDefaultBillingAddress',
361
+ addressKey: `billing-address-${customer.id}`
362
+ });
363
+ } else {
364
+ updateActions.push({
365
+ action: 'changeAddress',
366
+ addressId: mainAddress.id!,
367
+ address: {
368
+ ...mainAddress,
369
+ email: payload.email || customer.email,
370
+ phone: payload.phone
371
+ }
372
+ });
373
+ }
374
+
375
+ if (updateActions.length > 0) {
376
+ const updateResponse = await client
377
+ .me()
378
+ .post({
379
+ body: {
380
+ version: customer.version,
381
+ actions: updateActions,
382
+ },
383
+ })
384
+ .execute();
385
+ customer = updateResponse.body;
386
+ }
387
+ const model = this.parseSingle(customer);
388
+ return success(model);
389
+
390
+ }
391
+
392
+ protected parseAddress(address: CTAddress): Address {
393
+ const result = {
394
+ identifier: {
395
+ nickName: address.key || '',
396
+ },
397
+ firstName: address.firstName || '',
398
+ lastName: address.lastName || '',
399
+ streetAddress: address.streetName || '',
400
+ streetNumber: address.streetNumber || '',
401
+ city: address.city || '',
402
+ region: address.region || '',
403
+ postalCode: address.postalCode || '',
404
+ countryCode: address.country,
405
+ } satisfies Address;
406
+
407
+ return result;
408
+ }
409
+
410
+ protected parseSingle(body: Customer): Profile {
411
+ const email = body.email;
412
+ const emailVerified = body.isEmailVerified;
413
+ let defaultCTBillingAddress = body.addresses.find(addr => addr.id === body.defaultBillingAddressId);
414
+ const phone = defaultCTBillingAddress?.phone ?? '';
415
+
416
+
417
+ // if we only have the phone number on the billing address, we dont really have a billing address, so we ignore it
418
+ if (this.isIncompleteAddress(defaultCTBillingAddress)) {
419
+ defaultCTBillingAddress = undefined;
420
+ }
421
+
422
+ const defaultCTShippingAddress = body.addresses.find(addr => addr.id === body.defaultShippingAddressId);
423
+
424
+ const alternateShippingAddresses = body.addresses.filter(x => x.id !== body.defaultBillingAddressId && x.id !== body.defaultShippingAddressId).map(addr => this.parseAddress(addr));
425
+ const billingAddress = defaultCTBillingAddress ? this.parseAddress(defaultCTBillingAddress) : undefined;
426
+ const shippingAddress = defaultCTShippingAddress ? this.parseAddress(defaultCTShippingAddress) : undefined;
427
+
428
+ const result = {
429
+ identifier: {
430
+ userId: body.id
431
+ },
432
+ email,
433
+ emailVerified,
434
+ alternateShippingAddresses,
435
+ billingAddress: billingAddress,
436
+ shippingAddress: shippingAddress,
437
+ createdAt: body.createdAt,
438
+ phone,
439
+ phoneVerified: false,
440
+ updatedAt: body.lastModifiedAt
441
+ } satisfies Profile;
442
+
443
+ return result;
444
+ }
445
+
446
+ protected createCTAddressDraft( address: Address): CTAddress {
447
+ return {
448
+ key: address.identifier.nickName,
449
+ firstName: address.firstName,
450
+ lastName: address.lastName,
451
+ streetName: address.streetAddress,
452
+ streetNumber: address.streetNumber,
453
+ postalCode: address.postalCode,
454
+ city: address.city,
455
+ region: address.region,
456
+ country: address.countryCode || this.context.taxJurisdiction.countryCode,
457
+ };
458
+ }
55
459
 
56
- model.email = body.email;
57
- model.emailVerified = body.isEmailVerified;
58
460
 
59
- return model;
461
+ /**
462
+ * Checks if an address only contains phone information and lacks essential address fields.
463
+ * An address is considered incomplete if it exists but has no firstName, lastName, streetName,
464
+ * streetNumber, or city.
465
+ * @param address - The address to check, or undefined
466
+ * @returns true if the address exists but lacks essential fields, false otherwise (including when address is undefined)
467
+ */
468
+ protected isIncompleteAddress(address: CTAddress | undefined): boolean {
469
+ if (!address) {
470
+ return false;
471
+ }
472
+ return !address.firstName &&
473
+ !address.lastName &&
474
+ !address.streetName &&
475
+ !address.streetNumber &&
476
+ !address.city;
60
477
  }
61
478
  }
@@ -2,77 +2,91 @@ import type {
2
2
  RequestContext,
3
3
  Cache,
4
4
  StoreQueryByProximity,
5
+ Store,
6
+ StoreIdentifier,
7
+ FulfillmentCenterIdentifier,
8
+ Result,
5
9
  } from '@reactionary/core';
6
- import { StoreProvider } from '@reactionary/core';
7
- import type z from 'zod';
10
+ import { Reactionary, StoreProvider, StoreQueryByProximitySchema, StoreSchema, success, error } from '@reactionary/core';
11
+ import z from 'zod';
8
12
  import type { CommercetoolsConfiguration } from '../schema/configuration.schema.js';
9
- import { CommercetoolsClient } from '../core/client.js';
10
13
  import type { Channel } from '@commercetools/platform-sdk';
11
- import type { Store } from '@reactionary/core';
14
+ import type { CommercetoolsAPI } from '../core/client.js';
12
15
 
13
- export class CommercetoolsStoreProvider<
14
- T extends Store = Store
15
- > extends StoreProvider<T> {
16
+ export class CommercetoolsStoreProvider extends StoreProvider {
16
17
  protected config: CommercetoolsConfiguration;
18
+ protected commercetools: CommercetoolsAPI;
17
19
 
18
20
  constructor(
19
21
  config: CommercetoolsConfiguration,
20
- schema: z.ZodType<T>,
21
- cache: Cache
22
+ cache: Cache,
23
+ context: RequestContext,
24
+ commercetools: CommercetoolsAPI
22
25
  ) {
23
- super(schema, cache);
26
+ super(cache, context);
24
27
 
25
28
  this.config = config;
29
+ this.commercetools = commercetools;
26
30
  }
27
31
 
28
- protected async getClient(reqCtx: RequestContext) {
29
- const client = await new CommercetoolsClient(this.config).getClient(reqCtx);
30
- return client
31
- .withProjectKey({ projectKey: this.config.projectKey });
32
+ protected async getClient() {
33
+ const client = await this.commercetools.getClient();
34
+ return client.withProjectKey({ projectKey: this.config.projectKey });
32
35
  }
33
36
 
37
+ @Reactionary({
38
+ inputSchema: StoreQueryByProximitySchema,
39
+ outputSchema: z.array(StoreSchema),
40
+ })
34
41
  public override async queryByProximity(
35
- payload: StoreQueryByProximity,
36
- reqCtx: RequestContext
37
- ): Promise<Array<T>> {
38
- const client = await this.getClient(reqCtx);
42
+ payload: StoreQueryByProximity
43
+ ): Promise<Result<Array<Store>>> {
44
+ const client = await this.getClient();
39
45
 
40
46
  const remote = await client
41
- .channels()
42
- .get({
43
- queryArgs: {
44
- limit: payload.limit,
45
- where: `geoLocation within circle(${ payload.longitude }, ${ payload.latitude }, ${payload.distance * 1000}) AND roles contains any ("InventorySupply")`
46
- }
47
- }).execute();
47
+ .channels()
48
+ .get({
49
+ queryArgs: {
50
+ limit: payload.limit,
51
+ where: `geoLocation within circle(${payload.longitude}, ${
52
+ payload.latitude
53
+ }, ${
54
+ payload.distance * 1000
55
+ }) AND roles contains any ("InventorySupply")`,
56
+ },
57
+ })
58
+ .execute();
48
59
 
49
60
  const results = [];
50
61
 
51
62
  for (const r of remote.body.results) {
52
- results.push(this.parseSingle(r, reqCtx));
63
+ results.push(this.parseSingle(r));
53
64
  }
54
65
 
55
- return results;
66
+ return success(results);
56
67
  }
57
68
 
58
- protected override parseSingle(
59
- body: Channel,
60
- reqCtx: RequestContext
61
- ): T {
62
- const model = this.newModel();
69
+ protected parseSingle(body: Channel): Store {
63
70
 
71
+ let name = '';
64
72
  if (body.name && body.name['la']) {
65
- model.name = body.name['la'];
73
+ name = body.name['la'];
66
74
  }
67
75
 
68
- model.identifier = {
69
- key: body.key
70
- };
76
+ const identifier = {
77
+ key: body.key,
78
+ } satisfies StoreIdentifier;
71
79
 
72
- model.fulfillmentCenter = {
73
- key: body.key
74
- };
80
+ const fulfillmentCenter = {
81
+ key: body.key,
82
+ } satisfies FulfillmentCenterIdentifier;
75
83
 
76
- return model;
84
+ const result = {
85
+ identifier,
86
+ fulfillmentCenter,
87
+ name,
88
+ } satisfies Store;
89
+
90
+ return result;
77
91
  }
78
92
  }
@@ -3,15 +3,17 @@ import type { z } from 'zod';
3
3
 
4
4
  export const CommercetoolsCapabilitiesSchema = CapabilitiesSchema.pick({
5
5
  product: true,
6
- search: true,
6
+ productSearch: true,
7
7
  identity: true,
8
8
  cart: true,
9
9
  checkout: true,
10
10
  order: true,
11
+ orderSearch: true,
11
12
  inventory: true,
12
13
  price: true,
13
14
  category: true,
14
15
  store: true,
16
+ profile: true
15
17
  }).partial();
16
18
 
17
19
  export type CommercetoolsCapabilities = z.infer<typeof CommercetoolsCapabilitiesSchema>;
@@ -1,5 +1,5 @@
1
- import { CartIdentifierSchema, CheckoutIdentifierSchema, OrderIdentifierSchema } from "@reactionary/core";
2
- import z from "zod";
1
+ import { BaseModelSchema, CartIdentifierSchema, CategoryIdentifierSchema, CheckoutIdentifierSchema, OrderIdentifierSchema } from "@reactionary/core";
2
+ import { keyof, z } from "zod";
3
3
 
4
4
  export const CommercetoolsCartIdentifierSchema = CartIdentifierSchema.extend({
5
5
  version: z.number().default(0)
@@ -14,7 +14,21 @@ export const CommercetoolsCheckoutIdentifierSchema = CheckoutIdentifierSchema.ex
14
14
  version: z.number().default(0)
15
15
  });
16
16
 
17
- export type CommercetoolsCheckoutIdentifier = z.infer<typeof CommercetoolsCheckoutIdentifierSchema>;
17
+ export const CommercetoolsResolveCategoryQueryByKeySchema = z.object({
18
+ key: z.string().describe('The key of the category to resolve.'),
19
+ });
20
+ export const CommercetoolsResolveCategoryQueryByIdSchema = z.object({
21
+ id: z.string().describe('The ID of the category to resolve.'),
22
+ });
23
+ export const CommercetoolsCategoryLookupSchema = BaseModelSchema.extend({
24
+ id: z.string(),
25
+ key: z.string().optional(),
26
+ name: z.record(z.string(), z.string()),
27
+ });
18
28
 
29
+ export type CommercetoolsCheckoutIdentifier = z.infer<typeof CommercetoolsCheckoutIdentifierSchema>;
19
30
  export type CommercetoolsCartIdentifier = z.infer<typeof CommercetoolsCartIdentifierSchema>;
20
31
  export type CommercetoolsOrderIdentifier = z.infer<typeof CommercetoolsOrderIdentifierSchema>;
32
+ export type CommercetoolsResolveCategoryQueryByKey = z.infer<typeof CommercetoolsResolveCategoryQueryByKeySchema>;
33
+ export type CommercetoolsResolveCategoryQueryById = z.infer<typeof CommercetoolsResolveCategoryQueryByIdSchema>;
34
+ export type CommercetoolsCategoryLookup = z.infer<typeof CommercetoolsCategoryLookupSchema>;
@@ -9,6 +9,7 @@ export const CommercetoolsConfigurationSchema = z.looseObject({
9
9
  clientSecret: z.string(),
10
10
  scopes: z.array(z.string()).default(() => []),
11
11
  paymentMethods: PaymentMethodSchema.array().optional().default(() => []),
12
+ facetFieldsForSearch: z.array(z.string()).default(() => []),
12
13
  });
13
14
 
14
15
  export type CommercetoolsConfiguration = z.infer<typeof CommercetoolsConfigurationSchema>;