@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,104 @@
1
+ import { describe, it, expect, beforeEach, vi, assert } from 'vitest';
2
+ import { MedusaPriceProvider } from '../providers/price.provider.js';
3
+ import type { MedusaConfiguration } from '../schema/configuration.schema.js';
4
+ import {
5
+ PriceSchema,
6
+ NoOpCache,
7
+ createInitialRequestContext,
8
+ type RequestContext,
9
+ } from '@reactionary/core';
10
+ import { getMedusaTestConfiguration } from './test-utils.js';
11
+ import { MedusaAPI } from '../core/client.js';
12
+
13
+ const testData = {
14
+ skuWithoutTiers: '8712581327934',
15
+ skuWithTiers: '8710895937641',
16
+ };
17
+
18
+ // FIXME: Currently broken in terms of actually looking up anything...
19
+ describe('Medusa Price Provider', () => {
20
+ let provider: MedusaPriceProvider;
21
+ let reqCtx: RequestContext;
22
+
23
+ beforeEach(() => {
24
+ reqCtx = createInitialRequestContext();
25
+ const config = getMedusaTestConfiguration();
26
+ const client = new MedusaAPI(config, reqCtx);
27
+
28
+ provider = new MedusaPriceProvider(config, new NoOpCache(), reqCtx, client);
29
+ });
30
+
31
+ it('should be able to get prices for a product without tiers', async () => {
32
+ const result = await provider.getCustomerPrice({
33
+ variant: { sku: testData.skuWithoutTiers },
34
+ });
35
+
36
+ if (!result.success) {
37
+ assert.fail();
38
+ }
39
+
40
+ expect(result.value.identifier.variant.sku).toBe(testData.skuWithoutTiers);
41
+ expect(result.value.unitPrice.value).toBeGreaterThan(0);
42
+ expect(result.value.unitPrice.currency).toBe(
43
+ reqCtx.languageContext.currencyCode
44
+ );
45
+ expect(result.value.tieredPrices.length).toBe(0);
46
+ });
47
+
48
+ it.skip('should be able to get prices for a product with tiers', async () => {
49
+ const result = await provider.getCustomerPrice({
50
+ variant: { sku: testData.skuWithTiers },
51
+ });
52
+
53
+ if (!result.success) {
54
+ assert.fail();
55
+ }
56
+
57
+ expect(result.value.identifier.variant.sku).toBe(testData.skuWithTiers);
58
+ expect(result.value.unitPrice.value).toBeGreaterThan(0);
59
+ expect(result.value.unitPrice.currency).toBe(reqCtx.languageContext.currencyCode);
60
+ expect(result.value.tieredPrices.length).toBeGreaterThan(0);
61
+
62
+ expect(result.value.tieredPrices[0].minimumQuantity).toBeGreaterThan(0);
63
+ expect(result.value.tieredPrices[0].price.value).toBeLessThanOrEqual(
64
+ result.value.unitPrice.value
65
+ );
66
+ expect(result.value.tieredPrices[0].price.currency).toBe(
67
+ reqCtx.languageContext.currencyCode
68
+ );
69
+ });
70
+
71
+ it('should return NotFound for an unknown SKU', async () => {
72
+ const result = await provider.getCustomerPrice({
73
+ variant: { sku: 'unknown-sku' },
74
+ });
75
+
76
+ if (result.success) {
77
+ assert.fail();
78
+ }
79
+ });
80
+
81
+ it('can look up multiple prices at once', async () => {
82
+ const skus = [
83
+ testData.skuWithTiers,
84
+ testData.skuWithoutTiers,
85
+ 'unknown-sku',
86
+ ];
87
+ const results = await Promise.all(
88
+ skus.map((sku) => provider.getCustomerPrice({ variant: { sku: sku } }))
89
+ );
90
+
91
+ const success = results.filter(x => x.success);
92
+
93
+ if (results.length !== success.length) {
94
+ assert.fail();
95
+ }
96
+
97
+ expect(success).toHaveLength(skus.length);
98
+ expect(success[0].value.identifier.variant.sku).toBe(testData.skuWithTiers);
99
+ expect(success[0].value.unitPrice.value).toBeGreaterThan(0);
100
+ expect(success[1].value.identifier.variant.sku).toBe(testData.skuWithoutTiers);
101
+ expect(success[1].value.unitPrice.value).toBeGreaterThan(0);
102
+ expect(success[2].value.identifier.variant.sku).toBe('unknown-sku');
103
+ });
104
+ });
@@ -0,0 +1,146 @@
1
+ import 'dotenv/config';
2
+ import type { RequestContext } from '@reactionary/core';
3
+ import {
4
+ NoOpCache,
5
+ ProductSchema,
6
+ createInitialRequestContext,
7
+ } from '@reactionary/core';
8
+ import { MedusaProductProvider } from '../providers/product.provider.js';
9
+ import { getMedusaTestConfiguration } from './test-utils.js';
10
+ import { describe, expect, it, beforeAll, beforeEach, assert } from 'vitest';
11
+ import { MedusaAPI } from '../index.js';
12
+
13
+ const testData = {
14
+ product: {
15
+ name: 'LV-CA31 SCART Cable',
16
+ slug: 'lv-ca31-scart-cable-101080',
17
+ image: 'https://images.icecat.biz/img/norm/high/101080-3513.jpg',
18
+ sku: '4960999194479',
19
+ },
20
+ productWithMultiVariants: {
21
+ slug: 'hp-gk859aa-mouse-office-bluetooth-laser-1600-dpi-1377612',
22
+ },
23
+ };
24
+
25
+ describe('Medusa Product Provider', () => {
26
+ let provider: MedusaProductProvider;
27
+ let reqCtx: RequestContext;
28
+
29
+ beforeEach(() => {
30
+ reqCtx = createInitialRequestContext();
31
+ const client = new MedusaAPI(getMedusaTestConfiguration(), reqCtx);
32
+ provider = new MedusaProductProvider(
33
+ getMedusaTestConfiguration(),
34
+ new NoOpCache(),
35
+ reqCtx,
36
+ client
37
+ );
38
+ });
39
+
40
+ it('should be able to get a product by id', async () => {
41
+ const slugResult = await provider.getBySlug({
42
+ slug: testData.product.slug,
43
+ });
44
+
45
+ if (!slugResult.success) {
46
+ assert.fail();
47
+ }
48
+
49
+ const result = await provider.getById({
50
+ identifier: slugResult.value.identifier,
51
+ });
52
+
53
+ if (!result.success) {
54
+ assert.fail();
55
+ }
56
+
57
+ expect(result.value.identifier.key).toBe(slugResult.value.identifier.key);
58
+ expect(result.value.name).toBe(testData.product.name);
59
+
60
+ expect(result.value.mainVariant).toBeDefined();
61
+ expect(result.value.mainVariant.identifier.sku).toBe(testData.product.sku);
62
+ expect(result.value.mainVariant.images[0].sourceUrl).toBe(
63
+ testData.product.image
64
+ );
65
+ expect(result.value.sharedAttributes.length).toBeGreaterThan(1);
66
+ expect(result.value.sharedAttributes[1].values.length).toBeGreaterThan(0);
67
+ expect(result.value.sharedAttributes[1].values[0].value).toBeTruthy();
68
+ });
69
+
70
+ it('should be able to get a product with multiple variants by slug', async () => {
71
+ const result = await provider.getBySlug({
72
+ slug: testData.productWithMultiVariants.slug,
73
+ });
74
+
75
+ if (!result.success) {
76
+ assert.fail();
77
+ }
78
+
79
+ expect(result.value.identifier.key).toBeTruthy();
80
+ expect(result.value.slug).toBe(testData.productWithMultiVariants.slug);
81
+ expect(result.value.mainVariant).toBeDefined();
82
+ expect(result.value.variants.length).toBeGreaterThan(0);
83
+ expect(result.value.variants[0].identifier.sku).toBeTruthy();
84
+ expect(result.value.variants[0].identifier.sku).not.toBe(
85
+ result.value.mainVariant.identifier.sku
86
+ );
87
+ expect(result.value.sharedAttributes.length).toBeGreaterThan(1);
88
+ expect(result.value.sharedAttributes[1].values.length).toBeGreaterThan(0);
89
+ expect(result.value.sharedAttributes[1].values[0].value).toBeTruthy();
90
+ });
91
+
92
+ it('should be able to get a product by sku', async () => {
93
+ const slugResult = await provider.getBySlug({
94
+ slug: testData.product.slug,
95
+ });
96
+
97
+ if (!slugResult.success) {
98
+ assert.fail();
99
+ }
100
+
101
+ const result = await provider.getBySKU({
102
+ variant: { sku: testData.product.sku },
103
+ });
104
+
105
+ if (!result.success) {
106
+ assert.fail();
107
+ }
108
+
109
+ expect(result.value.identifier.key).toBe(slugResult.value.identifier.key);
110
+ expect(result.value.name).toBe(testData.product.name);
111
+ expect(result.value.mainVariant).toBeDefined();
112
+ expect(result.value.mainVariant.identifier.sku).toBe(testData.product.sku);
113
+ expect(result.value.mainVariant.images[0].sourceUrl).toBe(testData.product.image);
114
+ expect(result.value.sharedAttributes.length).toBeGreaterThan(1);
115
+ expect(result.value.sharedAttributes[1].values.length).toBeGreaterThan(0);
116
+ expect(result.value.sharedAttributes[1].values[0].value).toBeTruthy();
117
+ });
118
+
119
+ it('should return null for unknown slug', async () => {
120
+ const result = await provider.getBySlug({ slug: 'unknown-slug' });
121
+
122
+ expect(result).toBeNull();
123
+ });
124
+
125
+ it('should contain both product level and variant level attributes', async () => {
126
+ const result = await provider.getBySlug({ slug: testData.product.slug });
127
+
128
+ if (!result.success) {
129
+ assert.fail();
130
+ }
131
+
132
+ expect(result.value.sharedAttributes.length).toBeGreaterThan(1);
133
+ expect(result.value.sharedAttributes[1].values.length).toBeGreaterThan(0);
134
+ expect(result.value.sharedAttributes[1].values[0].value).toBeTruthy();
135
+ });
136
+
137
+ it('should return a placeholder product for unknown id', async () => {
138
+ const result = await provider.getById({
139
+ identifier: { key: 'unknown-id' },
140
+ });
141
+
142
+ if (!result.success) {
143
+ assert.fail();
144
+ }
145
+ });
146
+ });
@@ -0,0 +1,203 @@
1
+ import 'dotenv/config';
2
+ import { createInitialRequestContext, NoOpCache, ProductSearchQueryByTermSchema, ProductSearchResultItemSchema, type ProductSearchQueryCreateNavigationFilter } from '@reactionary/core';
3
+ import { assert, describe, expect, it } from 'vitest';
4
+ import { MedusaSearchProvider } from '../providers/product-search.provider.js';
5
+ import { getMedusaTestConfiguration } from './test-utils.js';
6
+ import { MedusaAPI } from '../index.js';
7
+ import { MedusaCategoryProvider } from '../providers/category.provider.js';
8
+
9
+ const testData = {
10
+ searchTerm: 'printer',
11
+ }
12
+ describe('Medusa Search Provider', () => {
13
+ const reqCtx = createInitialRequestContext();
14
+ const client = new MedusaAPI(getMedusaTestConfiguration(), reqCtx);
15
+ const provider = new MedusaSearchProvider(
16
+ getMedusaTestConfiguration(),
17
+ new NoOpCache(),
18
+ reqCtx,
19
+ client
20
+ );
21
+
22
+ const categoryProvider = new MedusaCategoryProvider(
23
+ getMedusaTestConfiguration(),
24
+ new NoOpCache(),
25
+ reqCtx,
26
+ client
27
+ );
28
+
29
+ it('should be able to get a result by term', async () => {
30
+ const result = await provider.queryByTerm(ProductSearchQueryByTermSchema.parse({ search: {
31
+ term: testData.searchTerm,
32
+ paginationOptions: {
33
+ pageNumber: 1,
34
+ pageSize: 20,
35
+ },
36
+ facets: [],
37
+ filters: [],
38
+ }}));
39
+
40
+ if (!result.success) {
41
+ assert.fail();
42
+ }
43
+
44
+ expect(result.value.items.length).toBeGreaterThan(0);
45
+ expect(result.value.facets.length).toBe(0);
46
+ });
47
+
48
+ it('should be able to paginate', async () => {
49
+ const firstPage = await provider.queryByTerm(ProductSearchQueryByTermSchema.parse({ search: {
50
+ term: testData.searchTerm,
51
+ paginationOptions: {
52
+ pageNumber: 1,
53
+ pageSize: 2,
54
+ },
55
+ facets: [],
56
+ filters: []
57
+ }}));
58
+
59
+ const secondPage = await provider.queryByTerm(ProductSearchQueryByTermSchema.parse({ search: {
60
+ term: testData.searchTerm,
61
+ paginationOptions: {
62
+ pageNumber: 2,
63
+ pageSize: 2
64
+ },
65
+ facets: [],
66
+ filters: []
67
+ }}));
68
+
69
+ if (!firstPage.success || !secondPage.success) {
70
+ assert.fail();
71
+ }
72
+
73
+ expect(firstPage.value.pageNumber).toBe(1);
74
+ expect(secondPage.value.pageNumber).toBe(2);
75
+ expect(firstPage.value.items[0].identifier.key).not.toEqual(
76
+ secondPage.value.items[0].identifier.key
77
+ );
78
+ });
79
+
80
+ it ('should be able to apply a top level category filter', async () => {
81
+ // First, get a category to filter on
82
+ const categories = await categoryProvider.findTopCategories({
83
+ paginationOptions: {
84
+ pageNumber: 1,
85
+ pageSize: 2,
86
+ },
87
+ });
88
+
89
+ if (!categories.success) {
90
+ assert.fail();
91
+ }
92
+
93
+ // medusa does not support subtree searches, so we have to drill down to a leaf category
94
+ let candidate = categories.value.items[0];
95
+ while(candidate) {
96
+ const children = await categoryProvider.findChildCategories({
97
+ parentId: candidate.identifier,
98
+ paginationOptions: {
99
+ pageNumber: 1,
100
+ pageSize: 10,
101
+ },
102
+ });
103
+
104
+ if (!children.success) {
105
+ assert.fail();
106
+ }
107
+
108
+ if(children.value.items.length > 0) {
109
+ candidate = children.value.items[0];
110
+ } else {
111
+ break;
112
+ }
113
+ }
114
+
115
+ const unfilteredSearch = await provider.queryByTerm({
116
+ search: {
117
+ term: "",
118
+ paginationOptions: {
119
+ pageNumber: 1,
120
+ pageSize: 1,
121
+ },
122
+ facets: [],
123
+ filters: [],
124
+ },
125
+ });
126
+
127
+ if (!unfilteredSearch.success) {
128
+ assert.fail();
129
+ }
130
+
131
+ expect(unfilteredSearch.value.totalCount).toBeGreaterThan(0);
132
+
133
+ const breadCrumb = await categoryProvider.getBreadcrumbPathToCategory({
134
+ id: candidate.identifier,
135
+ });
136
+
137
+ if (!breadCrumb.success) {
138
+ assert.fail();
139
+ }
140
+
141
+ expect(breadCrumb.value.length).toBeGreaterThan(0);
142
+
143
+ const categoryFilter = await provider.createCategoryNavigationFilter({
144
+ categoryPath: breadCrumb.value,
145
+ } satisfies ProductSearchQueryCreateNavigationFilter);
146
+
147
+ if (!categoryFilter.success) {
148
+ assert.fail();
149
+ }
150
+
151
+ const filteredSearch = await provider.queryByTerm({
152
+ search: {
153
+ term: "",
154
+ categoryFilter: categoryFilter.value,
155
+ paginationOptions: {
156
+ pageNumber: 1,
157
+ pageSize: 1,
158
+ },
159
+ facets: [],
160
+ filters: [],
161
+ },
162
+ });
163
+
164
+ if (!filteredSearch.success) {
165
+ assert.fail();
166
+ }
167
+
168
+ expect(filteredSearch.value.totalCount).toBeLessThan(unfilteredSearch.value.totalCount);
169
+ expect(filteredSearch.value.totalCount).toBeGreaterThan(0);
170
+ });
171
+
172
+ it('should be able to change page size', async () => {
173
+ const smallPage = await provider.queryByTerm(ProductSearchQueryByTermSchema.parse({ search: {
174
+ term: testData.searchTerm,
175
+ paginationOptions: {
176
+ pageNumber: 1,
177
+ pageSize: 2,
178
+ },
179
+ facets: [],
180
+ filters: [],
181
+ }}));
182
+
183
+ const largePage = await provider.queryByTerm(ProductSearchQueryByTermSchema.parse({ search: {
184
+ term: testData.searchTerm,
185
+ paginationOptions: {
186
+ pageNumber: 1,
187
+ pageSize: 30,
188
+ },
189
+ facets: [],
190
+ filters: [],
191
+ }}));
192
+
193
+ if (!smallPage.success || !largePage.success) {
194
+ assert.fail();
195
+ }
196
+
197
+ expect(smallPage.value.items.length).toBe(2);
198
+ expect(smallPage.value.pageSize).toBe(2);
199
+ expect(largePage.value.items.length).toBe(30);
200
+ expect(largePage.value.pageSize).toBe(30);
201
+ });
202
+
203
+ });
@@ -0,0 +1,13 @@
1
+ import 'dotenv/config';
2
+
3
+ import { MedusaConfigurationSchema, type MedusaConfiguration } from '../schema/configuration.schema.js';
4
+
5
+ export function getMedusaTestConfiguration(): MedusaConfiguration {
6
+ return MedusaConfigurationSchema.parse({
7
+ publishable_key: process.env['MEDUSA_PUBLISHABLE_KEY'] || '',
8
+ adminApiKey: process.env['MEDUSA_ADMIN_KEY'] || '',
9
+ apiUrl: process.env['MEDUSA_API_URL'] || '',
10
+ defaultCurrency: process.env['MEDUSA_DEFAULT_CURRENCY'] || '',
11
+ });
12
+ }
13
+
@@ -0,0 +1,89 @@
1
+ import type { StoreCart, StoreOrder } from '@medusajs/types';
2
+ import type { CostBreakDown, Currency } from '@reactionary/core';
3
+ import createDebug from 'debug';
4
+
5
+ const debug = createDebug('reactionary:medusa:helpers');
6
+
7
+ /**
8
+ * Parses cost breakdown from Medusa StoreCart
9
+ */
10
+ export function parseMedusaCostBreakdown(remote: StoreCart | StoreOrder): CostBreakDown {
11
+ const grandTotal = remote.total || 0;
12
+ const shippingTotal = remote.shipping_total || 0;
13
+ const taxTotal = remote.tax_total || 0;
14
+ const discountTotal = remote.discount_total || 0;
15
+ const subtotal = remote.subtotal || 0;
16
+ const currency = (remote.currency_code || 'EUR').toUpperCase() as Currency;
17
+
18
+ return {
19
+ totalTax: {
20
+ value: taxTotal,
21
+ currency,
22
+ },
23
+ totalDiscount: {
24
+ value: discountTotal,
25
+ currency,
26
+ },
27
+ totalSurcharge: {
28
+ value: 0,
29
+ currency,
30
+ },
31
+ totalShipping: {
32
+ value: shippingTotal,
33
+ currency,
34
+ },
35
+ totalProductPrice: {
36
+ value: subtotal,
37
+ currency,
38
+ },
39
+ grandTotal: {
40
+ value: grandTotal,
41
+ currency,
42
+ },
43
+ };
44
+ }
45
+
46
+ /**
47
+ * Parses item price structure from Medusa line item
48
+ */
49
+ export function parseMedusaItemPrice(
50
+ remoteItem: { unit_price?: number; quantity: number; discount_total?: number },
51
+ currency: Currency
52
+ ) {
53
+ const unitPrice = remoteItem.unit_price || 0;
54
+ const totalPrice = unitPrice * remoteItem.quantity || 0;
55
+ const discountTotal = remoteItem.discount_total || 0;
56
+
57
+ return {
58
+ unitPrice: {
59
+ value: unitPrice,
60
+ currency,
61
+ },
62
+ unitDiscount: {
63
+ value: discountTotal / remoteItem.quantity,
64
+ currency,
65
+ },
66
+ totalPrice: {
67
+ value: totalPrice,
68
+ currency,
69
+ },
70
+ totalDiscount: {
71
+ value: discountTotal,
72
+ currency,
73
+ },
74
+ };
75
+ }
76
+
77
+ /**
78
+ * Handles provider errors with consistent formatting
79
+ */
80
+ export function handleProviderError(action: string, error: unknown): never {
81
+ if (debug.enabled) {
82
+ debug(`Failed to ${action}:`, error);
83
+ }
84
+ throw new Error(
85
+ `Failed to ${action}: ${
86
+ error instanceof Error ? error.message : 'Unknown error'
87
+ }`
88
+ );
89
+ }
@@ -0,0 +1,21 @@
1
+ {
2
+ "extends": "../../tsconfig.base.json",
3
+ "compilerOptions": {
4
+ "module": "NodeNext",
5
+ "moduleResolution": "nodenext",
6
+ "forceConsistentCasingInFileNames": true,
7
+ "strict": true,
8
+ "importHelpers": true,
9
+ "noImplicitOverride": true,
10
+ "noImplicitReturns": true,
11
+ "noFallthroughCasesInSwitch": true,
12
+ "noPropertyAccessFromIndexSignature": true
13
+ },
14
+ "files": [],
15
+ "include": [],
16
+ "references": [
17
+ {
18
+ "path": "./tsconfig.lib.json"
19
+ }
20
+ ]
21
+ }
@@ -0,0 +1,9 @@
1
+ {
2
+ "extends": "./tsconfig.json",
3
+ "compilerOptions": {
4
+ "outDir": "../../dist/out-tsc",
5
+ "declaration": true,
6
+ "types": ["node"]
7
+ },
8
+ "include": ["src/**/*.ts"]
9
+ }
@@ -0,0 +1,4 @@
1
+ {
2
+ "extends": "./tsconfig.lib.json",
3
+ "exclude": []
4
+ }
@@ -0,0 +1,15 @@
1
+ /// <reference types="vitest" />
2
+ import { defineConfig, defineProject } from 'vitest/config';
3
+ import tsconfigPaths from 'vite-tsconfig-paths';
4
+ import { resolve } from 'path';
5
+ import { nxViteTsPaths } from '@nx/vite/plugins/nx-tsconfig-paths.plugin';
6
+
7
+ export default defineProject({
8
+ plugins: [nxViteTsPaths()],
9
+ test: {
10
+ root: resolve(__dirname),
11
+ globals: true,
12
+ environment: 'node',
13
+ include: ['src/**/*.{test,spec}.{js,ds,ba,vv,ft,ts}', 'src/**/*.test.{ts,js}'],
14
+ },
15
+ });
@@ -0,0 +1,48 @@
1
+ # Meilisearch provider for Reactionary
2
+
3
+ ## Supports
4
+
5
+
6
+ | Feature | Support | Notes |
7
+ | ----------- | ----------- | --------- |
8
+ | product | Full | |
9
+ | productSearch | Full | |
10
+ | identity | N/A | |
11
+ | cart | N/A | |
12
+ | checkout | N/A | |
13
+ | order | N/A | Possibly later |
14
+ | inventory | N/A | |
15
+ | price | N/A | |
16
+ | category | N/A | Possibly later |
17
+ | store | N/A | Possibly later |
18
+
19
+
20
+ ## Notes
21
+ The expected Meilisearch schema must contain at least these fields
22
+
23
+ ```json
24
+ {
25
+ id: string;
26
+ slug:string;
27
+ name: string;
28
+ variants: [
29
+ {
30
+ sku: string;
31
+ image: string;
32
+ }
33
+ ]
34
+ }
35
+ ```
36
+
37
+ You can have more, for use with facets, and additional searchable fields, but these must be in the index, and constitutes what we are expecting to get back.
38
+
39
+ The `id` corresponds to your productIdentifier, and `sku` should match your SKU
40
+
41
+
42
+ ## Building
43
+
44
+ Run `nx build provider-meilisearch` to build the library.
45
+
46
+ ## Running unit tests
47
+
48
+ Run `nx test provider-meilisearch` to execute the unit tests via [Jest](https://jestjs.io).
@@ -0,0 +1,22 @@
1
+ import baseConfig from '../../eslint.config.mjs';
2
+
3
+ export default [
4
+ ...baseConfig,
5
+ {
6
+ files: ['**/*.json'],
7
+ rules: {
8
+ '@nx/dependency-checks': [
9
+ 'error',
10
+ {
11
+ ignoredFiles: [
12
+ '{projectRoot}/eslint.config.{js,cjs,mjs}',
13
+ '{projectRoot}/esbuild.config.{js,ts,mjs,mts}',
14
+ ],
15
+ },
16
+ ],
17
+ },
18
+ languageOptions: {
19
+ parser: await import('jsonc-eslint-parser'),
20
+ },
21
+ },
22
+ ];
@@ -0,0 +1,13 @@
1
+ {
2
+ "name": "@reactionary/provider-meilisearch",
3
+ "version": "0.0.1",
4
+ "main": "index.js",
5
+ "types": "src/index.d.ts",
6
+ "dependencies": {
7
+ "@reactionary/core": "0.0.1",
8
+ "meilisearch": "0.55.0",
9
+ "zod": "4.1.9"
10
+ },
11
+ "type": "module",
12
+ "sideEffects": false
13
+ }