@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,18 +1,18 @@
1
1
  import type { AnalyticsProvider } from "../providers/analytics.provider.js";
2
2
  import type { ProductProvider } from "../providers/product.provider.js";
3
- import type { SearchProvider } from "../providers/search.provider.js";
3
+ import type { ProductSearchProvider } from "../providers/product-search.provider.js";
4
4
  import type { IdentityProvider } from '../providers/identity.provider.js';
5
5
  import type { CartProvider } from "../providers/cart.provider.js";
6
6
  import type { PriceProvider } from "../providers/price.provider.js";
7
7
  import type { InventoryProvider } from "../providers/inventory.provider.js";
8
8
  import type { Cache } from "../cache/cache.interface.js";
9
- import { RedisCache } from "../cache/redis-cache.js";
10
9
  import type { CategoryProvider } from "../providers/category.provider.js";
11
- import type { CheckoutProvider } from "../providers/index.js";
10
+ import type { CheckoutProvider, OrderProvider, ProfileProvider, StoreProvider } from "../providers/index.js";
11
+ import type { OrderSearchProvider } from "../providers/order-search.provider.js";
12
12
 
13
13
  export interface Client {
14
14
  product: ProductProvider,
15
- search: SearchProvider,
15
+ productSearch: ProductSearchProvider,
16
16
  identity: IdentityProvider,
17
17
  cache: Cache,
18
18
  cart: CartProvider,
@@ -20,48 +20,10 @@ export interface Client {
20
20
  analytics: Array<AnalyticsProvider>,
21
21
  price: PriceProvider,
22
22
  inventory: InventoryProvider,
23
- category: CategoryProvider
23
+ category: CategoryProvider,
24
+ profile: ProfileProvider,
25
+ store: StoreProvider,
26
+ order: OrderProvider,
27
+ orderSearch: OrderSearchProvider,
24
28
  }
25
29
 
26
- export interface BuildClientOptions {
27
- cache?: Cache;
28
- }
29
-
30
- export function buildClient<T extends Partial<Client>>(
31
- providerFactories: Array<(cache: Cache) => T>,
32
- options: BuildClientOptions = {}
33
- ): Required<T> {
34
- let client = { } as Required<T>;
35
-
36
- // Create shared cache instance
37
- const sharedCache = options.cache || new RedisCache();
38
-
39
- const mergedAnalytics = [];
40
-
41
- for (const factory of providerFactories) {
42
- const provider = factory(sharedCache);
43
- client = {
44
- ...client,
45
- ...provider
46
- }
47
-
48
- if (provider.analytics) {
49
- mergedAnalytics.push(...provider.analytics);
50
- }
51
- }
52
-
53
- client.analytics = mergedAnalytics;
54
-
55
- // Add cache to complete the client
56
- const completeClient = {
57
- ...client,
58
- cache: sharedCache
59
- } as Required<T>;
60
-
61
- return completeClient;
62
- }
63
-
64
- // Convenience function to create a shared cache instance
65
- export function createCache(): Cache {
66
- return new RedisCache();
67
- }
@@ -0,0 +1,2 @@
1
+ export * from './client-builder.js';
2
+ export * from './client.js';
@@ -0,0 +1 @@
1
+ export * from './reactionary.decorator.js';
@@ -1,24 +1,20 @@
1
+ import type { Tracer } from '@opentelemetry/api';
2
+ import { SpanStatusCode, trace } from '@opentelemetry/api';
3
+ import { z } from 'zod';
1
4
  import type { BaseProvider } from '../providers/index.js';
2
- import type {
3
- Tracer, } from '@opentelemetry/api';
4
- import {
5
- trace,
6
- SpanKind
7
- } from '@opentelemetry/api';
5
+ import { getReactionaryMeter } from '../metrics/metrics.js';
6
+ import { error, success, type Result } from '../schemas/result.js';
7
+ import type {
8
+ InvalidInputError,
9
+ InvalidOutputError,
10
+ GenericError,
11
+ } from '../schemas/index.js';
8
12
 
9
13
  const TRACER_NAME = '@reactionary';
10
14
  const TRACER_VERSION = '0.0.1';
11
15
 
12
- let globalTracer: Tracer | null = null;
13
-
14
16
  export function getTracer(): Tracer {
15
- if (!globalTracer) {
16
- // Simply get the tracer from the API
17
- // If the SDK is not initialized by the host application,
18
- // this will return a ProxyTracer that produces NonRecordingSpans
19
- globalTracer = trace.getTracer(TRACER_NAME, TRACER_VERSION);
20
- }
21
- return globalTracer;
17
+ return trace.getTracer(TRACER_NAME, TRACER_VERSION);
22
18
  }
23
19
 
24
20
  /**
@@ -51,7 +47,16 @@ export class ReactionaryDecoratorOptions {
51
47
  */
52
48
  public cacheTimeToLiveInSeconds = 60;
53
49
 
54
- };
50
+ /**
51
+ * The schema for the input (query or mutation) type, for validation purposes
52
+ */
53
+ public inputSchema: z.ZodType = z.unknown();
54
+
55
+ /**
56
+ * The schema for the primary output type, for validation purposes
57
+ */
58
+ public outputSchema: z.ZodType = z.unknown();
59
+ }
55
60
 
56
61
  /**
57
62
  * Decorator for provider functions to provide functionality such as caching, tracing and type-checked
@@ -67,6 +72,13 @@ export function Reactionary(options: Partial<ReactionaryDecoratorOptions>) {
67
72
  const original = descriptor.value;
68
73
  const scope = `${target.constructor.name}.${propertyKey.toString()}`;
69
74
  const configuration = { ...new ReactionaryDecoratorOptions(), ...options };
75
+ const meter = getReactionaryMeter();
76
+ const attributes = {
77
+ 'labels.scope': scope,
78
+ };
79
+ const startTime = performance.now();
80
+ let status = 'ok';
81
+ let cacheStatus = 'miss';
70
82
 
71
83
  if (!original) {
72
84
  throw new Error(
@@ -75,34 +87,191 @@ export function Reactionary(options: Partial<ReactionaryDecoratorOptions>) {
75
87
  }
76
88
 
77
89
  descriptor.value = async function (this: BaseProvider, ...args: any[]) {
78
- const tracer = getTracer();
90
+ return traceSpan(scope, async () => {
91
+ meter.requestInProgress.add(1, attributes);
92
+ try {
93
+ const input = validateInput(args[0], configuration.inputSchema);
79
94
 
80
- return tracer.startActiveSpan(
81
- propertyKey.toString(),
82
- { kind: SpanKind.SERVER },
83
- async (span) => {
84
- const cacheKey = this.generateCacheKeyForQuery(scope, args[0]);
95
+ if (!input.success) {
96
+ return input;
97
+ }
85
98
 
86
- const fromCache = await this.cache.get(cacheKey, this.schema);
87
- let result = fromCache;
99
+ const cacheKey = this.generateCacheKeyForQuery(scope, input.value);
100
+ let fromCache = null;
88
101
 
89
- if (!result) {
90
- result = await original.apply(this, args);
102
+ if (options.cache) {
103
+ fromCache = await this.cache.get(
104
+ cacheKey,
105
+ options.outputSchema as any
106
+ );
107
+ }
91
108
 
92
- const dependencyIds = this.generateDependencyIdsForModel(result);
109
+ let result;
110
+ if (!fromCache) {
111
+ result = await original.apply(this, [input.value]);
93
112
 
94
- this.cache.put(cacheKey, result, {
95
- ttlSeconds: configuration.cacheTimeToLiveInSeconds,
96
- dependencyIds: dependencyIds
97
- });
113
+ const r = result as Result<unknown>;
114
+
115
+ if (r.success) {
116
+ const dependencyIds = this.generateDependencyIdsForModel(r.value);
117
+
118
+ if (options.cache) {
119
+ this.cache.put(cacheKey, r.value, {
120
+ ttlSeconds: configuration.cacheTimeToLiveInSeconds,
121
+ dependencyIds: dependencyIds,
122
+ });
123
+ }
124
+ }
125
+ } else {
126
+ result = success(fromCache);
127
+ cacheStatus = 'hit';
98
128
  }
99
129
 
100
- span.end();
101
- return this.assert(result as any);
130
+ // TODO: Update the below after fixing the method signature.
131
+ // That is, making the decorator only applicable to methods with
132
+ // a signature returning a result.
133
+ const validatedResult = validateOutput(
134
+ result as any,
135
+ configuration.outputSchema
136
+ );
137
+
138
+ if (validatedResult.success) {
139
+ validatedResult.meta.cache.hit = !!fromCache;
140
+ validatedResult.meta.cache.key = cacheKey;
141
+ }
142
+
143
+ return validatedResult;
144
+ } catch (err) {
145
+ status = 'error';
146
+ trace
147
+ .getActiveSpan()
148
+ ?.setStatus({
149
+ code: SpanStatusCode.ERROR,
150
+ message: errorToString(err),
151
+ });
152
+
153
+ // TODO: Decide if we want to redact the error message, since the client should never rely
154
+ // on the internals. On the other hand, it is REALLY convenient to have it during development...
155
+ const result = error<GenericError>({
156
+ type: 'Generic',
157
+ message: errorToString(err),
158
+ });
159
+
160
+ return result;
161
+ } finally {
162
+ const duration = performance.now() - startTime;
163
+ const finalAttributes = {
164
+ ...attributes,
165
+ 'labels.status': status,
166
+ 'labels.cache_status': cacheStatus,
167
+ };
168
+ meter.requestInProgress.add(-1, finalAttributes);
169
+ meter.requests.add(1, finalAttributes);
170
+ meter.requestDuration.record(duration, finalAttributes);
102
171
  }
103
- );
172
+ });
104
173
  };
105
174
 
106
175
  return descriptor;
107
176
  };
108
177
  }
178
+
179
+ /**
180
+ * Utility function to handle input validation.
181
+ */
182
+ export function validateInput<T>(
183
+ input: T,
184
+ schema: z.ZodType | undefined
185
+ ): Result<T> {
186
+ if (!schema) {
187
+ return success(input);
188
+ }
189
+
190
+ let validated: Result<T> = success(input);
191
+ const parse = schema.safeParse(input);
192
+
193
+ if (!parse.success) {
194
+ validated = error<InvalidInputError>({
195
+ type: 'InvalidInput',
196
+ error: parse.error,
197
+ });
198
+ }
199
+
200
+ return validated;
201
+ }
202
+
203
+ /**
204
+ * Utility function to handle output validation.
205
+ */
206
+ export function validateOutput(
207
+ output: Result<unknown>,
208
+ schema: z.ZodType | undefined
209
+ ): Result<unknown> {
210
+ if (!schema) {
211
+ return output;
212
+ }
213
+
214
+ let validated = output;
215
+ if (output.success) {
216
+ const parse = schema.safeParse(output.value);
217
+
218
+ if (!parse.success) {
219
+ validated = error<InvalidOutputError>({
220
+ type: 'InvalidOutput',
221
+ error: parse.error,
222
+ });
223
+ }
224
+ }
225
+
226
+ return validated;
227
+ }
228
+
229
+ /**
230
+ * Utility function to wrap entry / exit into decorated functions in a
231
+ * traced span for OTEL handling.
232
+ */
233
+ export async function traceSpan<T>(
234
+ name: string,
235
+ fn: () => Promise<T> | T
236
+ ): Promise<T> {
237
+ const tracer: Tracer = getTracer();
238
+
239
+ return tracer.startActiveSpan(name, async (span) => {
240
+ try {
241
+ return await fn();
242
+ } catch (err: unknown) {
243
+ if (err instanceof Error) {
244
+ span.recordException(err);
245
+ span.setStatus({ code: 2, message: err.message });
246
+ } else {
247
+ span.recordException({ message: String(err) });
248
+ span.setStatus({ code: 2, message: String(err) });
249
+ }
250
+
251
+ // TODO: Instead of re-throwing here, we might actually want to return a distinct error type
252
+ // but I am unsure if that is really a task for the decorator (outside of input / output parsing)
253
+ // it could perhaps always handle an unknown exception as a generic error type, for consolidation
254
+ throw err;
255
+ } finally {
256
+ span.end();
257
+ }
258
+ });
259
+ }
260
+
261
+ /**
262
+ * Utility function for converting a caught error to a plain
263
+ * string for reporting back.
264
+ */
265
+ export function errorToString(err: unknown): string {
266
+ if (err instanceof Error) {
267
+ return err.stack ?? err.message;
268
+ }
269
+ if (typeof err === 'string') {
270
+ return err;
271
+ }
272
+ try {
273
+ return JSON.stringify(err);
274
+ } catch {
275
+ return String(err);
276
+ }
277
+ }
package/core/src/index.ts CHANGED
@@ -1,19 +1,6 @@
1
- export * from './cache/cache.interface.js';
2
- export * from './cache/redis-cache.js';
3
- export * from './cache/memory-cache.js';
4
- export * from './cache/noop-cache.js';
5
-
6
- export * from './client/client.js';
7
- export * from './client/client-builder.js';
8
-
9
- export * from './decorators/reactionary.decorator.js';
10
-
11
- export * from './providers/index.js'
12
-
13
- export * from './schemas/capabilities.schema.js';
14
- export * from './schemas/session.schema.js';
15
-
16
- export * from './schemas/models/index.js';
17
- export * from './schemas/mutations/index.js';
18
- export * from './schemas/queries/index.js';
19
- export * from './initialization.js';
1
+ export * from './cache/index.js';
2
+ export * from './client/index.js';
3
+ export * from './decorators/index.js';
4
+ export * from './providers/index.js';
5
+ export * from './schemas/index.js';
6
+ export * from './initialization.js';
@@ -2,25 +2,8 @@ import type { RequestContext } from "./schemas/session.schema.js";
2
2
 
3
3
  export function createInitialRequestContext(): RequestContext {
4
4
  return {
5
- id: '',
6
- identity: {
7
- type: 'Anonymous',
8
- meta: {
9
- cache: { hit: false, key: '' },
10
- placeholder: false,
11
- },
12
- id: { userId: 'anonymous' },
13
- token: undefined,
14
- issued: new Date(),
15
- expiry: new Date(new Date().getTime() + 3600 * 1000),
16
- logonId: '',
17
- createdAt: '',
18
- updatedAt: '',
19
- keyring: [],
20
- currentService: undefined,
21
- },
22
5
  languageContext: {
23
- locale: 'en-US',
6
+ locale: 'en',
24
7
  currencyCode: 'USD',
25
8
  },
26
9
  storeIdentifier: {
@@ -0,0 +1,67 @@
1
+ import type { Counter, Gauge } from '@opentelemetry/api';
2
+ import { metrics, type Histogram, type UpDownCounter } from '@opentelemetry/api';
3
+
4
+ const METRICS_NAME = '@reactionary';
5
+ const TRACER_VERSION = '0.0.1';
6
+
7
+ export interface ReactionaryCacheMetrics {
8
+ items: Gauge;
9
+ hits: Counter;
10
+ misses: Counter;
11
+ }
12
+
13
+ let globalCacheMetrics: ReactionaryCacheMetrics | null = null;
14
+ export function getReactionaryCacheMeter(): ReactionaryCacheMetrics {
15
+ if (!globalCacheMetrics) {
16
+ const meter = metrics.getMeter(METRICS_NAME, TRACER_VERSION);
17
+ globalCacheMetrics = {
18
+ items: meter.createGauge('reactionary_cache_items', {
19
+ description: 'Tracks the number of items in the cache',
20
+ }),
21
+ hits: meter.createCounter('reactionary_cache_hits', {
22
+ description: 'Counts the number of cache hits',
23
+ }),
24
+ misses: meter.createCounter('reactionary_cache_misses', {
25
+ description: 'Counts the number of cache misses',
26
+ }),
27
+ };
28
+ }
29
+ return globalCacheMetrics;
30
+ }
31
+
32
+
33
+ export interface ReactionaryMetrics {
34
+ requests: Counter;
35
+ requestDuration: Histogram;
36
+ requestInProgress: UpDownCounter;
37
+ }
38
+
39
+ let globalMetrics: ReactionaryMetrics | null = null;
40
+
41
+ export function getReactionaryMeter(): ReactionaryMetrics {
42
+ if (!globalMetrics) {
43
+ const meter = metrics.getMeter(METRICS_NAME, TRACER_VERSION);
44
+ globalMetrics = {
45
+ requests: meter.createCounter('reactionary_provider_requests', {
46
+ description:
47
+ 'Counts the number of requests made to provider queries and mutations',
48
+ }),
49
+ requestDuration: meter.createHistogram(
50
+ 'reactionary_provider_request_duration',
51
+ {
52
+ description:
53
+ 'Records the duration of provider query and mutation requests',
54
+ unit: 'ms',
55
+ }
56
+ ),
57
+ requestInProgress: meter.createUpDownCounter(
58
+ 'reactionary_provider_requests_in_progress',
59
+ {
60
+ description:
61
+ 'Tracks the number of in-progress requests to provider queries and mutations',
62
+ }
63
+ ),
64
+ };
65
+ }
66
+ return globalMetrics;
67
+ }
@@ -1,11 +1,6 @@
1
- import type { AnalyticsEvent } from '../schemas/models/analytics.model.js';
2
1
  import { BaseProvider } from './base.provider.js';
3
2
 
4
- export abstract class AnalyticsProvider<
5
- T extends AnalyticsEvent = AnalyticsEvent
6
- > extends BaseProvider<T> {
7
-
8
-
3
+ export abstract class AnalyticsProvider extends BaseProvider {
9
4
  protected override getResourceName(): string {
10
5
  return 'analytics';
11
6
  }
@@ -1,53 +1,18 @@
1
- import type { z } from 'zod';
2
- import type {
3
- BaseModel} from '../schemas/models/base.model.js';
4
- import {
5
- createPaginatedResponseSchema,
6
- } from '../schemas/models/base.model.js';
7
1
  import type { Cache } from '../cache/cache.interface.js';
8
- import type { RequestContext, Session } from '../schemas/session.schema.js';
9
- import type { IdentifierType } from '../schemas/models/identifiers.model.js';
2
+ import { type RequestContext } from '../schemas/session.schema.js';
10
3
  import { hasher } from "node-object-hash";
11
4
 
12
5
  /**
13
6
  * Base capability provider, responsible for mutations (changes) and queries (fetches)
14
7
  * for a given business object domain.
15
8
  */
16
- export abstract class BaseProvider<T extends BaseModel = BaseModel> {
9
+ export abstract class BaseProvider {
17
10
  protected cache: Cache;
11
+ protected context: RequestContext;
18
12
 
19
- constructor(public readonly schema: z.ZodType<T>, cache: Cache) {
13
+ constructor(cache: Cache, context: RequestContext) {
20
14
  this.cache = cache;
21
- }
22
-
23
- /**
24
- * Validates that the final domain model constructed by the provider
25
- * fulfills the schema as defined. This will throw an exception.
26
- */
27
- protected assert(value: T) {
28
- return this.schema.parse(value);
29
- }
30
-
31
- /**
32
- * Creates a new model entity based on the schema defaults.
33
- */
34
- protected newModel(): T {
35
- return this.schema.parse({});
36
- }
37
-
38
- /**
39
- * Handler for parsing a response from a remote provider and converting it
40
- * into the typed domain model.
41
- */
42
- protected parseSingle(_body: unknown, reqCtx: RequestContext): T {
43
- const model = this.newModel();
44
-
45
- return this.assert(model);
46
- }
47
-
48
-
49
- protected parsePaginatedResult(_body: unknown, reqCtx: RequestContext): z.infer<ReturnType<typeof createPaginatedResponseSchema<typeof this.schema>>> {
50
- return createPaginatedResponseSchema(this.schema).parse({});
15
+ this.context = context;
51
16
  }
52
17
 
53
18
  public generateDependencyIdsForModel(model: unknown): Array<string> {
@@ -75,35 +40,6 @@ export abstract class BaseProvider<T extends BaseModel = BaseModel> {
75
40
  return `${scope}:${queryHash}`;
76
41
  }
77
42
 
78
- protected generateCacheKeyPaginatedResult(
79
- resultSetName: string,
80
- res: ReturnType<typeof this.parsePaginatedResult>,
81
- reqCtx: RequestContext
82
- ): string {
83
- const type = this.getResourceName();
84
- const langPart = reqCtx.languageContext.locale;
85
- const currencyPart = reqCtx.languageContext.currencyCode || 'default';
86
- const storePart = reqCtx.storeIdentifier?.key || 'default';
87
- return `${type}-${resultSetName}-paginated|pageNumber:${res.pageNumber}|pageSize:${res.pageSize}|store:${storePart}|lang:${langPart}|currency:${currencyPart}`;
88
- }
89
-
90
- protected generateCacheKeySingle(
91
- identifier: IdentifierType,
92
- reqCtx: RequestContext
93
- ): string {
94
- const type = this.getResourceName();
95
-
96
- const idPart = Object.entries(identifier)
97
- .map(([k, v]) => `${k}:${v}`)
98
- .join('#');
99
-
100
- const langPart = reqCtx.languageContext.locale;
101
- const currencyPart = reqCtx.languageContext.currencyCode || 'default';
102
- const storePart = reqCtx.storeIdentifier?.key || 'default';
103
-
104
- return `${type}-${idPart}|store:${storePart}|lang:${langPart}|currency:${currencyPart}`;
105
- }
106
-
107
43
  /**
108
44
  * Returns the abstract resource name provided by the remote system.
109
45
  */