@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
@@ -0,0 +1,137 @@
1
+ import {
2
+ type Identity,
3
+ type IdentityMutationLogin,
4
+ type IdentityMutationLogout,
5
+ type IdentityMutationRegister,
6
+ type IdentityQuerySelf,
7
+ type RequestContext,
8
+ type Cache,
9
+ IdentityProvider,
10
+ Reactionary,
11
+ IdentityQuerySelfSchema,
12
+ IdentitySchema,
13
+ IdentityMutationRegisterSchema,
14
+ IdentityMutationLoginSchema,
15
+ IdentityMutationLogoutSchema,
16
+ type AnonymousIdentity,
17
+ type RegisteredIdentity,
18
+ type Result,
19
+ success,
20
+ } from '@reactionary/core';
21
+ import type { MedusaConfiguration } from '../schema/configuration.schema.js';
22
+ import type z from 'zod';
23
+ import type { MedusaAPI } from '../core/client.js';
24
+ import createDebug from 'debug';
25
+
26
+ const debug = createDebug('reactionary:medusa:identity');
27
+
28
+ export class MedusaIdentityProvider extends IdentityProvider {
29
+ protected config: MedusaConfiguration;
30
+ protected medusaApi: MedusaAPI;
31
+
32
+ constructor(
33
+ config: MedusaConfiguration,
34
+ cache: Cache,
35
+ context: RequestContext,
36
+ medusaApi: MedusaAPI
37
+ ) {
38
+ super(cache, context);
39
+
40
+ this.config = config;
41
+ this.medusaApi = medusaApi;
42
+ }
43
+
44
+ protected createAnonymousIdentity(): AnonymousIdentity {
45
+ return {
46
+ type: 'Anonymous',
47
+ };
48
+ }
49
+
50
+ @Reactionary({
51
+ inputSchema: IdentityQuerySelfSchema,
52
+ outputSchema: IdentitySchema,
53
+ })
54
+ public override async getSelf(
55
+ _payload: IdentityQuerySelf
56
+ ): Promise<Result<Identity>> {
57
+ try {
58
+ const medusaClient = await this.medusaApi.getClient();
59
+ const token = await medusaClient.client.getToken();
60
+
61
+ if (!token) {
62
+ debug('No active session token found, returning anonymous identity');
63
+ return success(this.createAnonymousIdentity());
64
+ }
65
+
66
+ // Try to fetch customer details to verify authentication
67
+ const customerResponse = await medusaClient.store.customer.retrieve();
68
+
69
+ if (customerResponse.customer) {
70
+ debug('Customer authenticated:', customerResponse.customer.email);
71
+ return success({
72
+ id: {
73
+ userId: customerResponse.customer.id,
74
+ },
75
+ type: 'Registered',
76
+ } satisfies RegisteredIdentity);
77
+ }
78
+
79
+ return success(this.createAnonymousIdentity());
80
+ } catch (error) {
81
+ debug('getSelf failed, returning anonymous identity:', error);
82
+ return success(this.createAnonymousIdentity());
83
+ }
84
+ }
85
+
86
+ @Reactionary({
87
+ inputSchema: IdentityMutationLoginSchema,
88
+ outputSchema: IdentitySchema,
89
+ })
90
+ public override async login(payload: IdentityMutationLogin): Promise<Result<Identity>> {
91
+ debug('Attempting login for user:', payload.username);
92
+ const identity = await this.medusaApi.login(
93
+ payload.username,
94
+ payload.password,
95
+ this.context
96
+ ) satisfies Identity;
97
+
98
+ return success(identity);
99
+ }
100
+
101
+ @Reactionary({
102
+ inputSchema: IdentityMutationLogoutSchema,
103
+ outputSchema: IdentitySchema,
104
+ })
105
+ public override async logout(_payload: IdentityMutationLogout): Promise<Result<Identity>> {
106
+ debug('Logging out user');
107
+ const identity = await this.medusaApi.logout(this.context);
108
+
109
+ return success(identity);
110
+ }
111
+
112
+ @Reactionary({
113
+ inputSchema: IdentityMutationRegisterSchema,
114
+ outputSchema: IdentitySchema,
115
+ })
116
+ public override async register(
117
+ payload: IdentityMutationRegister
118
+ ): Promise<Result<Identity>> {
119
+ debug('Registering new user:', payload.username);
120
+
121
+ // Extract first and last name from username or use defaults
122
+ // In a real implementation, you might want to add firstName/lastName to the schema
123
+ const nameParts = payload.username.split('.');
124
+ const firstName = nameParts[0] || 'User';
125
+ const lastName = nameParts.slice(1).join(' ') || 'Account';
126
+
127
+ const identity = await this.medusaApi.register(
128
+ payload.username, // Using username as email
129
+ payload.password,
130
+ firstName,
131
+ lastName,
132
+ this.context
133
+ );
134
+
135
+ return success(identity);
136
+ }
137
+ }
@@ -0,0 +1,173 @@
1
+ import {
2
+ type Inventory,
3
+ type InventoryQueryBySKU,
4
+ type RequestContext,
5
+ type Cache,
6
+ InventoryProvider,
7
+ InventorySchema,
8
+ InventoryQueryBySKUSchema,
9
+ Reactionary,
10
+ type InventoryIdentifier,
11
+ type InventoryStatus,
12
+ type NotFoundError,
13
+ type Result,
14
+ success,
15
+ } from '@reactionary/core';
16
+ import type z from 'zod';
17
+ import type { MedusaConfiguration } from '../schema/configuration.schema.js';
18
+ import { MedusaAdminAPI, type MedusaAPI } from '../core/client.js';
19
+ import createDebug from 'debug';
20
+
21
+ const debug = createDebug('reactionary:medusa:inventory');
22
+
23
+ export class MedusaInventoryProvider extends InventoryProvider {
24
+ protected config: MedusaConfiguration;
25
+
26
+ constructor(
27
+ config: MedusaConfiguration,
28
+ cache: Cache,
29
+ context: RequestContext,
30
+ public medusaApi: MedusaAPI
31
+ ) {
32
+ super(cache, context);
33
+ this.config = config;
34
+ }
35
+
36
+ @Reactionary({
37
+ inputSchema: InventoryQueryBySKUSchema,
38
+ outputSchema: InventorySchema,
39
+ })
40
+ public override async getBySKU(payload: InventoryQueryBySKU): Promise<Result<Inventory, NotFoundError>> {
41
+ const sku = payload.variant.sku;
42
+ const fulfillmentCenterKey = payload.fulfilmentCenter.key;
43
+
44
+ if (debug.enabled) {
45
+ debug(`Fetching inventory for SKU: ${sku}, fulfillment center: ${fulfillmentCenterKey}`);
46
+ }
47
+
48
+ try {
49
+ const adminClient = await (new MedusaAdminAPI(this.config, this.context).getClient());
50
+
51
+ // Get inventory items for this variant
52
+ const inventoryResponse = await adminClient.admin.inventoryItem.list({
53
+ sku: [sku],
54
+ limit: 1,
55
+ offset: 0
56
+ })
57
+
58
+ if (!inventoryResponse.inventory_items || inventoryResponse.inventory_items.length === 0) {
59
+ if (debug.enabled) {
60
+ debug(`No inventory items found for SKU: ${sku}`);
61
+ }
62
+ return success(this.createEmptyInventoryResult(sku, fulfillmentCenterKey));
63
+ }
64
+
65
+ const inventoryItem = inventoryResponse.inventory_items[0];
66
+
67
+ // Get inventory levels for this item and location
68
+ // In Medusa, we need to find the stock location that matches our fulfillment center key
69
+ const locationsResponse = await adminClient.admin.stockLocation.list({
70
+ name: fulfillmentCenterKey,
71
+ limit: 1,
72
+ offset: 0
73
+ });
74
+
75
+ let quantity = 0;
76
+ let locationFound = false;
77
+
78
+ if (locationsResponse.stock_locations && locationsResponse.stock_locations.length > 0) {
79
+ const location = locationsResponse.stock_locations[0];
80
+ locationFound = true;
81
+
82
+ // Get inventory level for this item at this location
83
+ const levelsResponse = await adminClient.admin.inventoryItem.listLevels(inventoryItem.id, {
84
+ location_id: [location.id],
85
+ });
86
+
87
+ if (levelsResponse.inventory_levels && levelsResponse.inventory_levels.length > 0) {
88
+ const level = levelsResponse.inventory_levels[0];
89
+ quantity = level.stocked_quantity - level.reserved_quantity;
90
+ }
91
+ }
92
+
93
+ if (!locationFound) {
94
+ if (debug.enabled) {
95
+ debug(`No stock location found with name: ${fulfillmentCenterKey}`);
96
+ }
97
+ return success(this.createEmptyInventoryResult(sku, fulfillmentCenterKey));
98
+ }
99
+
100
+ return success(this.parseSingle({
101
+ sku: payload.variant.sku,
102
+ fulfillmentCenterKey,
103
+ quantity,
104
+ inventoryItemId: inventoryItem.id,
105
+ }));
106
+
107
+ } catch (error) {
108
+ if (debug.enabled) {
109
+ debug(`Error fetching inventory for SKU: ${sku}`, error);
110
+ }
111
+ return success(this.createEmptyInventoryResult(sku, fulfillmentCenterKey));
112
+ }
113
+ }
114
+
115
+ protected parseSingle(_body: unknown): Inventory {
116
+ const { sku, fulfillmentCenterKey, quantity } = _body as {
117
+ sku: string;
118
+ fulfillmentCenterKey: string;
119
+ quantity: number;
120
+ inventoryItemId: string;
121
+ };
122
+
123
+ const identifier = {
124
+ variant: {
125
+ sku,
126
+ },
127
+ fulfillmentCenter: {
128
+ key: fulfillmentCenterKey,
129
+ },
130
+ } satisfies InventoryIdentifier;
131
+
132
+ let status: InventoryStatus = 'outOfStock';
133
+ if (quantity > 0) {
134
+ status = 'inStock';
135
+ }
136
+
137
+ const result = {
138
+ identifier,
139
+ quantity,
140
+ status
141
+ } satisfies Inventory;
142
+
143
+ return result;
144
+ }
145
+
146
+ /**
147
+ * Utility function to create an empty inventory result.
148
+ * This is used when no inventory is found for a given SKU + fulfillment center combination.
149
+ * @param sku
150
+ * @param fulfillmentCenterKey
151
+ * @returns
152
+ */
153
+ protected createEmptyInventoryResult(sku: string, fulfillmentCenterKey: string): Inventory {
154
+ const identifier = {
155
+ variant: { sku },
156
+ fulfillmentCenter: { key: fulfillmentCenterKey },
157
+ } satisfies InventoryIdentifier;
158
+
159
+ const quantity = 0;
160
+ const status = 'outOfStock';
161
+ const result = {
162
+ identifier,
163
+ quantity,
164
+ status
165
+ } satisfies Inventory;
166
+
167
+ return result;
168
+ }
169
+
170
+ protected override getResourceName(): string {
171
+ return 'inventory';
172
+ }
173
+ }
@@ -0,0 +1,202 @@
1
+ import type {
2
+ RequestContext,
3
+ Cache,
4
+ OrderSearchQueryByTerm,
5
+ OrderSearchResult,
6
+ Result,
7
+ OrderStatus,
8
+ Address,
9
+ IdentityIdentifier,
10
+ MonetaryAmount,
11
+ Currency,
12
+ OrderSearchResultItem,
13
+ OrderSearchIdentifier,
14
+ AddressIdentifier,
15
+ OrderInventoryStatus,
16
+ } from '@reactionary/core';
17
+ import {
18
+ AddressIdentifierSchema,
19
+ OrderSearchProvider,
20
+ OrderSearchQueryByTermSchema,
21
+ OrderSearchResultSchema,
22
+ Reactionary,
23
+ success,
24
+ } from '@reactionary/core';
25
+ import type { MedusaConfiguration } from '../schema/configuration.schema.js';
26
+ import type { MedusaAPI } from '../core/client.js';
27
+ import createDebug from 'debug';
28
+ import type { OrderStatus as MedusaOrderStatus, StoreOrder, StoreOrderAddress, StoreOrderListResponse } from '@medusajs/types';
29
+
30
+ const debug = createDebug('reactionary:medusa:order-search');
31
+
32
+ export class MedusaOrderSearchProvider extends OrderSearchProvider {
33
+ protected config: MedusaConfiguration;
34
+
35
+ constructor(
36
+ config: MedusaConfiguration,
37
+ cache: Cache,
38
+ context: RequestContext,
39
+ public medusaApi: MedusaAPI
40
+ ) {
41
+ super(cache, context);
42
+
43
+ this.config = config;
44
+
45
+ }
46
+
47
+ @Reactionary({
48
+ inputSchema: OrderSearchQueryByTermSchema,
49
+ outputSchema: OrderSearchResultSchema,
50
+ })
51
+ public async queryByTerm(payload: OrderSearchQueryByTerm): Promise<Result<OrderSearchResult>> {
52
+ debug('queryByTerm', payload);
53
+
54
+ const medusa = await this.medusaApi.getClient();
55
+
56
+ if (payload.search.term) {
57
+ debug('Searching orders by term is not supported in Medusa');
58
+ }
59
+
60
+ if (payload.search.partNumber) {
61
+ debug('Searching orders by part number is not supported in Medusa');
62
+ }
63
+
64
+ if (payload.search.startDate) {
65
+ debug('Searching orders by start date is not supported in Medusa');
66
+ }
67
+ if (payload.search.endDate) {
68
+ debug('Searching orders by end date is not supported in Medusa');
69
+ }
70
+ /*
71
+ if (payload.search.user && payload.search.user.userId) {
72
+ debug('Searching orders by customer ID is not supported in Medusa');
73
+ } */
74
+ const statusFilter: MedusaOrderStatus[] = (payload.search.orderStatus ?? []).map((status) => {
75
+ let retStatus: MedusaOrderStatus = 'draft';
76
+ if (status === 'AwaitingPayment') {
77
+ retStatus = 'draft';
78
+ }
79
+ if (status === 'ReleasedToFulfillment') {
80
+ retStatus = 'pending';
81
+ }
82
+ if (status === 'Shipped') {
83
+ retStatus = 'completed';
84
+ }
85
+ if (status === 'Cancelled') {
86
+ retStatus = 'canceled';
87
+ }
88
+ return retStatus;
89
+ });
90
+
91
+
92
+ const response = await medusa.store.order.list({
93
+ status: statusFilter,
94
+ limit: payload.search.paginationOptions.pageSize,
95
+ offset:
96
+ (payload.search.paginationOptions.pageNumber - 1) *
97
+ payload.search.paginationOptions.pageSize,
98
+
99
+ });
100
+
101
+ const result = this.parsePaginatedResult(response, payload) as OrderSearchResult;
102
+ if (debug.enabled) {
103
+ debug(
104
+ `Search for term "${payload.search.term}" returned ${response.orders.length} orders (page ${payload.search.paginationOptions.pageNumber} of ${result.totalPages})`
105
+ );
106
+ }
107
+
108
+ return success(result);
109
+ }
110
+
111
+ protected composeAddressFromStoreAddress(storeAddress: StoreOrderAddress): Address {
112
+ return {
113
+ identifier: AddressIdentifierSchema.parse({
114
+ nickName: storeAddress.id,
115
+ } satisfies AddressIdentifier),
116
+ firstName: storeAddress.first_name || '',
117
+ lastName: storeAddress.last_name || '',
118
+ streetAddress: storeAddress.address_1 || '',
119
+ streetNumber: storeAddress.address_2 || '',
120
+ city: storeAddress.city || '',
121
+ postalCode: storeAddress.postal_code || '',
122
+ countryCode: storeAddress.country_code || '',
123
+ region: '',
124
+ };
125
+ }
126
+
127
+
128
+
129
+ protected parseSingle(body: StoreOrder) {
130
+ const identifier = { key: body.id };
131
+ const userId: IdentityIdentifier = {
132
+ userId: body.customer_id || '',
133
+ };
134
+ const customerName = `${body.billing_address?.first_name} ${body.billing_address?.last_name}`;
135
+ const shippingAddress = this.composeAddressFromStoreAddress(body.shipping_address!);
136
+ const orderDate = new Date(body.created_at).toISOString();
137
+
138
+ let orderStatus: OrderStatus = 'AwaitingPayment'
139
+ if (body.status === 'draft') {
140
+ orderStatus = 'AwaitingPayment';
141
+ }
142
+ if (body.status === 'pending') {
143
+ orderStatus = 'ReleasedToFulfillment';
144
+ }
145
+ if (body.status === 'completed') {
146
+ orderStatus = 'Shipped';
147
+ }
148
+ if (body.status === 'canceled') {
149
+ orderStatus = 'Cancelled';
150
+ }
151
+ let inventoryStatus: OrderInventoryStatus = 'NotAllocated'
152
+ // Medusa does not have direct mapping for inventory status on orders
153
+ // This is a placeholder logic and may need to be adjusted based on actual requirements
154
+ if(body.fulfillment_status === "fulfilled") {
155
+ inventoryStatus = 'Allocated';
156
+ }
157
+
158
+ const totalAmount: MonetaryAmount = {
159
+ currency: body.currency_code.toUpperCase() as Currency,
160
+ value: body.total ? body.total : 0
161
+ };
162
+
163
+ const order = {
164
+ identifier,
165
+ userId,
166
+ customerName,
167
+ shippingAddress,
168
+ orderDate,
169
+ orderStatus,
170
+ inventoryStatus,
171
+ totalAmount
172
+ } satisfies OrderSearchResultItem;
173
+
174
+ return order;
175
+ }
176
+
177
+ protected parsePaginatedResult(
178
+ body: StoreOrderListResponse,
179
+ query: OrderSearchQueryByTerm
180
+ ) {
181
+ const identifier = {
182
+ ...query.search,
183
+ } satisfies OrderSearchIdentifier;
184
+
185
+ const orders: OrderSearchResultItem[] = body.orders.map((o) => {
186
+ return this.parseSingle(o);
187
+ })
188
+
189
+ const result = {
190
+ identifier,
191
+ pageNumber: (Math.ceil(body.offset / body.limit) || 0) + 1,
192
+ pageSize: body.limit,
193
+ totalCount: body.count,
194
+ totalPages: Math.ceil(body.count / body.limit || 0) + 1,
195
+ items: orders,
196
+ } satisfies OrderSearchResult;
197
+
198
+ return result;
199
+ }
200
+
201
+
202
+ }