@reactionary/source 0.0.42 → 0.0.51

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 (137) hide show
  1. package/.vscode/settings.json +5 -0
  2. package/core/package.json +3 -1
  3. package/core/src/cache/cache.interface.ts +14 -18
  4. package/core/src/cache/memory-cache.ts +56 -0
  5. package/core/src/cache/noop-cache.ts +5 -23
  6. package/core/src/cache/redis-cache.ts +28 -38
  7. package/core/src/client/client-builder.ts +3 -3
  8. package/core/src/client/client.ts +11 -11
  9. package/core/src/decorators/reactionary.decorator.ts +80 -8
  10. package/core/src/index.ts +2 -1
  11. package/{examples/node/src/test-utils.ts → core/src/initialization.ts} +20 -10
  12. package/core/src/providers/analytics.provider.ts +1 -1
  13. package/core/src/providers/base.provider.ts +61 -25
  14. package/core/src/providers/cart.provider.ts +17 -18
  15. package/core/src/providers/category.provider.ts +9 -9
  16. package/core/src/providers/checkout.provider.ts +156 -0
  17. package/core/src/providers/identity.provider.ts +8 -7
  18. package/core/src/providers/index.ts +4 -1
  19. package/core/src/providers/inventory.provider.ts +4 -4
  20. package/core/src/providers/order.provider.ts +31 -0
  21. package/core/src/providers/price.provider.ts +6 -6
  22. package/core/src/providers/product.provider.ts +6 -6
  23. package/core/src/providers/profile.provider.ts +22 -0
  24. package/core/src/providers/search.provider.ts +4 -4
  25. package/core/src/providers/store.provider.ts +14 -0
  26. package/core/src/schemas/capabilities.schema.ts +4 -2
  27. package/core/src/schemas/models/analytics.model.ts +1 -1
  28. package/core/src/schemas/models/cart.model.ts +1 -20
  29. package/core/src/schemas/models/checkout.model.ts +68 -0
  30. package/core/src/schemas/models/cost.model.ts +21 -0
  31. package/core/src/schemas/models/identifiers.model.ts +84 -35
  32. package/core/src/schemas/models/identity.model.ts +19 -20
  33. package/core/src/schemas/models/index.ts +5 -0
  34. package/core/src/schemas/models/order.model.ts +47 -0
  35. package/core/src/schemas/models/payment.model.ts +3 -10
  36. package/core/src/schemas/models/product.model.ts +6 -3
  37. package/core/src/schemas/models/profile.model.ts +3 -2
  38. package/core/src/schemas/models/shipping-method.model.ts +34 -3
  39. package/core/src/schemas/models/store.model.ts +11 -0
  40. package/core/src/schemas/mutations/cart.mutation.ts +2 -2
  41. package/core/src/schemas/mutations/checkout.mutation.ts +50 -0
  42. package/core/src/schemas/mutations/identity.mutation.ts +6 -0
  43. package/core/src/schemas/mutations/index.ts +2 -1
  44. package/core/src/schemas/mutations/profile.mutation.ts +9 -0
  45. package/core/src/schemas/queries/cart.query.ts +1 -1
  46. package/core/src/schemas/queries/checkout.query.ts +22 -0
  47. package/core/src/schemas/queries/identity.query.ts +1 -1
  48. package/core/src/schemas/queries/index.ts +4 -1
  49. package/core/src/schemas/queries/inventory.query.ts +4 -12
  50. package/core/src/schemas/queries/order.query.ts +9 -0
  51. package/core/src/schemas/queries/price.query.ts +1 -1
  52. package/core/src/schemas/queries/product.query.ts +8 -1
  53. package/core/src/schemas/queries/profile.query.ts +7 -0
  54. package/core/src/schemas/queries/search.query.ts +1 -1
  55. package/core/src/schemas/queries/store.query.ts +11 -0
  56. package/core/src/schemas/session.schema.ts +31 -6
  57. package/eslint.config.mjs +7 -0
  58. package/examples/next/src/app/page.tsx +4 -12
  59. package/examples/node/package.json +1 -3
  60. package/examples/node/src/basic/basic-node-provider-model-extension.spec.ts +9 -8
  61. package/examples/node/src/basic/basic-node-provider-query-extension.spec.ts +4 -3
  62. package/examples/node/src/basic/basic-node-setup.spec.ts +4 -5
  63. package/nx.json +1 -0
  64. package/otel/src/metrics.ts +2 -1
  65. package/otel/src/provider-instrumentation.ts +2 -1
  66. package/otel/src/trace-decorator.ts +8 -8
  67. package/otel/src/tracer.ts +7 -6
  68. package/otel/src/trpc-middleware.ts +3 -2
  69. package/package.json +4 -1
  70. package/providers/algolia/src/core/initialize.ts +4 -3
  71. package/providers/algolia/src/providers/product.provider.ts +21 -13
  72. package/providers/algolia/src/providers/search.provider.ts +9 -9
  73. package/providers/algolia/src/schema/capabilities.schema.ts +1 -1
  74. package/providers/algolia/src/test/search.provider.spec.ts +10 -10
  75. package/providers/algolia/src/test/test-utils.ts +1 -1
  76. package/providers/commercetools/README.md +11 -0
  77. package/providers/commercetools/package.json +1 -0
  78. package/providers/commercetools/src/core/client.ts +174 -87
  79. package/providers/commercetools/src/core/initialize.ts +23 -16
  80. package/providers/commercetools/src/providers/cart.provider.ts +98 -117
  81. package/providers/commercetools/src/providers/category.provider.ts +34 -37
  82. package/providers/commercetools/src/providers/checkout.provider.ts +644 -0
  83. package/providers/commercetools/src/providers/identity.provider.ts +22 -82
  84. package/providers/commercetools/src/providers/index.ts +5 -0
  85. package/providers/commercetools/src/providers/inventory.provider.ts +67 -48
  86. package/providers/commercetools/src/providers/order.provider.ts +165 -0
  87. package/providers/commercetools/src/providers/price.provider.ts +76 -50
  88. package/providers/commercetools/src/providers/product.provider.ts +43 -23
  89. package/providers/commercetools/src/providers/profile.provider.ts +61 -0
  90. package/providers/commercetools/src/providers/search.provider.ts +12 -15
  91. package/providers/commercetools/src/providers/store.provider.ts +78 -0
  92. package/providers/commercetools/src/schema/capabilities.schema.ts +4 -2
  93. package/providers/commercetools/src/schema/commercetools.schema.ts +7 -5
  94. package/providers/commercetools/src/schema/configuration.schema.ts +2 -0
  95. package/providers/commercetools/src/test/cart.provider.spec.ts +38 -17
  96. package/providers/commercetools/src/test/category.provider.spec.ts +18 -17
  97. package/providers/commercetools/src/test/checkout.provider.spec.ts +312 -0
  98. package/providers/commercetools/src/test/identity.provider.spec.ts +88 -0
  99. package/providers/commercetools/src/test/inventory.provider.spec.ts +41 -0
  100. package/providers/commercetools/src/test/price.provider.spec.ts +10 -9
  101. package/providers/commercetools/src/test/product.provider.spec.ts +28 -10
  102. package/providers/commercetools/src/test/profile.provider.spec.ts +49 -0
  103. package/providers/commercetools/src/test/search.provider.spec.ts +8 -7
  104. package/providers/commercetools/src/test/store.provider.spec.ts +37 -0
  105. package/providers/commercetools/src/test/test-utils.ts +13 -38
  106. package/providers/fake/src/core/initialize.ts +96 -38
  107. package/providers/fake/src/providers/analytics.provider.ts +6 -5
  108. package/providers/fake/src/providers/cart.provider.ts +30 -27
  109. package/providers/fake/src/providers/category.provider.ts +16 -12
  110. package/providers/fake/src/providers/identity.provider.ts +22 -14
  111. package/providers/fake/src/providers/index.ts +1 -0
  112. package/providers/fake/src/providers/inventory.provider.ts +13 -13
  113. package/providers/fake/src/providers/price.provider.ts +13 -13
  114. package/providers/fake/src/providers/product.provider.ts +20 -14
  115. package/providers/fake/src/providers/search.provider.ts +7 -5
  116. package/providers/fake/src/providers/store.provider.ts +47 -0
  117. package/providers/fake/src/schema/capabilities.schema.ts +4 -1
  118. package/providers/fake/src/test/cart.provider.spec.ts +18 -18
  119. package/providers/fake/src/test/category.provider.spec.ts +55 -37
  120. package/providers/fake/src/test/price.provider.spec.ts +9 -14
  121. package/providers/fake/src/test/product.provider.spec.ts +27 -0
  122. package/providers/fake/src/test/test-utils.ts +2 -33
  123. package/providers/posthog/src/core/initialize.ts +3 -3
  124. package/providers/posthog/src/schema/capabilities.schema.ts +1 -1
  125. package/trpc/src/client.ts +42 -41
  126. package/trpc/src/index.ts +4 -3
  127. package/trpc/src/integration.spec.ts +11 -11
  128. package/trpc/src/server.ts +27 -25
  129. package/trpc/src/transparent-client.spec.ts +4 -5
  130. package/trpc/src/types.ts +24 -22
  131. package/core/src/cache/cache-evaluation.interface.ts +0 -19
  132. package/core/src/providers/cart-payment.provider.ts +0 -56
  133. package/core/src/schemas/mutations/cart-payment.mutation.ts +0 -21
  134. package/core/src/schemas/queries/cart-payment.query.ts +0 -12
  135. package/providers/commercetools/src/providers/cart-payment.provider.ts +0 -192
  136. package/providers/commercetools/src/test/cart-payment.provider.spec.ts +0 -149
  137. package/trpc/src/test-utils.ts +0 -31
@@ -0,0 +1,5 @@
1
+ {
2
+ "typescript.preferences.importModuleSpecifierEnding": "auto",
3
+ "typescript.preferences.importModuleSpecifier": "relative",
4
+ "typescript.preferences.preferTypeOnlyAutoImports": true
5
+ }
package/core/package.json CHANGED
@@ -5,6 +5,8 @@
5
5
  "types": "src/index.d.ts",
6
6
  "dependencies": {
7
7
  "zod": "4.1.9",
8
- "@upstash/redis": "^1.34.9"
8
+ "@upstash/redis": "^1.34.9",
9
+ "@reactionary/otel": "0.0.1",
10
+ "node-object-hash": "^3.1.1"
9
11
  }
10
12
  }
@@ -1,4 +1,10 @@
1
- import { z } from 'zod';
1
+ import type { z } from 'zod';
2
+ import type { BaseModel } from '../schemas/models';
3
+
4
+ export interface CacheEntryOptions {
5
+ ttlSeconds: number;
6
+ dependencyIds: Array<string>;
7
+ }
2
8
 
3
9
  /**
4
10
  * Generic cache interface that can be implemented by different cache backends
@@ -8,31 +14,21 @@ export interface Cache {
8
14
  /**
9
15
  * Retrieves a value from cache and validates it against the provided schema
10
16
  */
11
- get<T>(key: string, schema: z.ZodType<T>): Promise<T | null>;
17
+ get<T extends BaseModel>(key: string, schema: z.ZodType<T>): Promise<T | null>;
12
18
 
13
19
  /**
14
20
  * Stores a value in cache with optional expiration time
15
21
  */
16
- put(key: string, value: unknown, ttlSeconds?: number): Promise<void>;
17
-
18
- /**
19
- * Removes one or more keys from cache
20
- * Supports wildcard patterns (implementation dependent)
21
- */
22
- del(keys: string | string[]): Promise<void>;
23
-
24
- /**
25
- * Finds all keys matching a pattern (implementation dependent)
26
- */
27
- keys(pattern: string): Promise<string[]>;
22
+ put(key: string, value: unknown, options: CacheEntryOptions): Promise<void>;
28
23
 
29
24
  /**
30
- * Clears all cache entries or entries matching a pattern
25
+ * Removes entries from the cache based on a set of dependency
26
+ * ids
31
27
  */
32
- clear(pattern?: string): Promise<void>;
28
+ invalidate(dependencyIds: Array<string>): Promise<void>;
33
29
 
34
30
  /**
35
- * Gets basic cache statistics (implementation dependent)
31
+ * Removes all entries from cache, wiping it completely
36
32
  */
37
- getStats(): Promise<{ hits: number; misses: number; size: number }>;
33
+ clear(): Promise<void>;
38
34
  }
@@ -0,0 +1,56 @@
1
+ import type { BaseModel } from '../schemas/models';
2
+ import type { Cache, CacheEntryOptions } from './cache.interface';
3
+ import type z from 'zod';
4
+
5
+ /**
6
+ * Memory version of the cache. Primarily useful for local development.
7
+ * This is NOT suited for production use.
8
+ */
9
+ export class MemoryCache implements Cache {
10
+ protected entries = new Array<{ key: string; value: unknown, options: CacheEntryOptions }>();
11
+
12
+ public async get<T extends BaseModel>(key: string, schema: z.ZodType<T>): Promise<T | null> {
13
+ const c = this.entries.find((x) => x.key === key);
14
+
15
+ if (!c) {
16
+ return null;
17
+ }
18
+
19
+ const parsed = schema.parse(c.value);
20
+
21
+ parsed.meta.cache.hit = true;
22
+
23
+ return parsed;
24
+ }
25
+
26
+ public async put(
27
+ key: string,
28
+ value: unknown,
29
+ options: CacheEntryOptions
30
+ ): Promise<void> {
31
+ this.entries.push({
32
+ key,
33
+ value,
34
+ options
35
+ });
36
+
37
+ return;
38
+ }
39
+
40
+ public async invalidate(dependencyIds: Array<string>): Promise<void> {
41
+ let index = 0;
42
+ for (const entry of this.entries) {
43
+ for (const entryDependency of entry.options.dependencyIds) {
44
+ if (dependencyIds.indexOf(entryDependency) > -1) {
45
+ this.entries.splice(index, 1);
46
+ }
47
+ }
48
+
49
+ index++;
50
+ }
51
+ }
52
+
53
+ public async clear(): Promise<void> {
54
+ this.entries = [];
55
+ }
56
+ }
@@ -1,5 +1,5 @@
1
- import { Cache } from './cache.interface';
2
- import z from 'zod';
1
+ import type { Cache, CacheEntryOptions } from './cache.interface';
2
+ import type z from 'zod';
3
3
 
4
4
  /**
5
5
  * No-op cache implementation that never stores or returns data.
@@ -7,36 +7,18 @@ import z from 'zod';
7
7
  */
8
8
  export class NoOpCache implements Cache {
9
9
  public async get<T>(_key: string, _schema: z.ZodType<T>): Promise<T | null> {
10
- // Always return null - never a cache hit
11
10
  return null;
12
11
  }
13
12
 
14
- public async put(_key: string, _value: unknown, _ttlSeconds?: number): Promise<void> {
15
- // No-op - silently ignore cache write requests
13
+ public async put(_key: string, _value: unknown, options: CacheEntryOptions): Promise<void> {
16
14
  return;
17
15
  }
18
16
 
19
- public async del(_keys: string | string[]): Promise<void> {
20
- // No-op - silently ignore delete requests
17
+ public async invalidate(dependencyIds: Array<string>): Promise<void> {
21
18
  return;
22
19
  }
23
20
 
24
- public async keys(_pattern: string): Promise<string[]> {
25
- // Always return empty array
26
- return [];
27
- }
28
-
29
- public async clear(_pattern?: string): Promise<void> {
30
- // No-op - silently ignore clear requests
21
+ public async clear(): Promise<void> {
31
22
  return;
32
23
  }
33
-
34
- public async getStats(): Promise<{ hits: number; misses: number; size: number }> {
35
- // Always return zeros
36
- return {
37
- hits: 0,
38
- misses: 0,
39
- size: 0
40
- };
41
- }
42
24
  }
@@ -1,6 +1,6 @@
1
1
  import { Redis } from '@upstash/redis';
2
- import { Cache } from './cache.interface';
3
- import z from 'zod';
2
+ import type { Cache, CacheEntryOptions } from './cache.interface';
3
+ import type z from 'zod';
4
4
 
5
5
  export class RedisCache implements Cache {
6
6
  protected redis: Redis;
@@ -13,62 +13,52 @@ export class RedisCache implements Cache {
13
13
  if (!key) {
14
14
  return null;
15
15
  }
16
-
16
+
17
17
  const unvalidated = await this.redis.get(key);
18
18
  const parsed = schema.safeParse(unvalidated);
19
19
 
20
20
  if (parsed.success) {
21
21
  return parsed.data;
22
22
  }
23
-
23
+
24
24
  return null;
25
25
  }
26
26
 
27
- public async put(key: string, value: unknown, ttlSeconds?: number): Promise<void> {
27
+ public async put(
28
+ key: string,
29
+ value: unknown,
30
+ options: CacheEntryOptions
31
+ ): Promise<void> {
28
32
  if (!key) {
29
33
  return;
30
34
  }
31
35
 
32
- const options = ttlSeconds ? { ex: ttlSeconds } : undefined;
33
- await this.redis.set(key, value, options);
34
- }
36
+ const serialized = JSON.stringify(value);
37
+ const multi = this.redis.multi();
35
38
 
36
- public async del(keys: string | string[]): Promise<void> {
37
- const keyArray = Array.isArray(keys) ? keys : [keys];
38
-
39
- for (const key of keyArray) {
40
- if (key.includes('*')) {
41
- // Handle wildcard patterns
42
- const matchingKeys = await this.redis.keys(key);
43
- if (matchingKeys.length > 0) {
44
- await this.redis.del(...matchingKeys);
45
- }
46
- } else {
47
- // Delete specific key
48
- await this.redis.del(key);
49
- }
39
+ multi.set(key, serialized, { ex: options.ttlSeconds });
40
+
41
+ for (const depId of options.dependencyIds) {
42
+ multi.sadd(`dep:${depId}`, key);
50
43
  }
51
- }
52
44
 
53
- public async keys(pattern: string): Promise<string[]> {
54
- return await this.redis.keys(pattern);
45
+ await multi.exec();
55
46
  }
56
47
 
57
- public async clear(pattern?: string): Promise<void> {
58
- const searchPattern = pattern || '*';
59
- const keys = await this.redis.keys(searchPattern);
60
- if (keys.length > 0) {
61
- await this.redis.del(...keys);
48
+ public async invalidate(dependencyIds: Array<string>): Promise<void> {
49
+ for (const id of dependencyIds) {
50
+ const depKey = `dep:${id}`;
51
+ const keys = await this.redis.smembers(depKey);
52
+
53
+ if (keys.length > 0) {
54
+ await this.redis.del(...keys);
55
+ }
56
+
57
+ await this.redis.del(depKey);
62
58
  }
63
59
  }
64
60
 
65
- public async getStats(): Promise<{ hits: number; misses: number; size: number }> {
66
- // Basic cache statistics (could be enhanced with actual Redis metrics)
67
- const keys = await this.redis.keys('*');
68
- return {
69
- hits: 0, // Would need to track this separately
70
- misses: 0, // Would need to track this separately
71
- size: keys.length
72
- };
61
+ public async clear(): Promise<void> {
62
+ // Not sure about supporting this on Redis.
73
63
  }
74
64
  }
@@ -1,7 +1,7 @@
1
- import { Cache } from "../cache/cache.interface";
1
+ import type { Cache } from "../cache/cache.interface";
2
2
  import { NoOpCache } from "../cache/noop-cache";
3
- import { Client } from "./client";
4
- import { AnalyticsProvider } from "../providers/analytics.provider";
3
+ import type { Client } from "./client";
4
+ import type { AnalyticsProvider } from "../providers/analytics.provider";
5
5
 
6
6
  type CapabilityFactory<T> = (cache: Cache) => T;
7
7
 
@@ -1,14 +1,14 @@
1
- import { AnalyticsProvider } from "../providers/analytics.provider";
2
- import { ProductProvider } from "../providers/product.provider";
3
- import { SearchProvider } from "../providers/search.provider";
4
- import { IdentityProvider } from '../providers/identity.provider';
5
- import { CartProvider } from "../providers/cart.provider";
6
- import { PriceProvider } from "../providers/price.provider";
7
- import { InventoryProvider } from "../providers/inventory.provider";
8
- import { Cache } from "../cache/cache.interface";
1
+ import type { AnalyticsProvider } from "../providers/analytics.provider";
2
+ import type { ProductProvider } from "../providers/product.provider";
3
+ import type { SearchProvider } from "../providers/search.provider";
4
+ import type { IdentityProvider } from '../providers/identity.provider';
5
+ import type { CartProvider } from "../providers/cart.provider";
6
+ import type { PriceProvider } from "../providers/price.provider";
7
+ import type { InventoryProvider } from "../providers/inventory.provider";
8
+ import type { Cache } from "../cache/cache.interface";
9
9
  import { RedisCache } from "../cache/redis-cache";
10
- import { CategoryProvider } from "../providers/category.provider";
11
- import { CartPaymentProvider } from "../providers";
10
+ import type { CategoryProvider } from "../providers/category.provider";
11
+ import type { CheckoutProvider } from "../providers";
12
12
 
13
13
  export interface Client {
14
14
  product: ProductProvider,
@@ -16,7 +16,7 @@ export interface Client {
16
16
  identity: IdentityProvider,
17
17
  cache: Cache,
18
18
  cart: CartProvider,
19
- cartPayment: CartPaymentProvider,
19
+ checkout: CheckoutProvider,
20
20
  analytics: Array<AnalyticsProvider>,
21
21
  price: PriceProvider,
22
22
  inventory: InventoryProvider,
@@ -1,16 +1,88 @@
1
- export function Reactionary(options: unknown): MethodDecorator {
1
+ import type { BaseProvider } from '../providers';
2
+ import { getTracer, SpanKind } from '@reactionary/otel';
3
+
4
+ /**
5
+ * The options associated with annotating a provider function and marking
6
+ * it as a reactionary entrypoint to be called
7
+ */
8
+ export class ReactionaryDecoratorOptions {
9
+ /**
10
+ * Whether or not the query is eligible for caching. Queries that depend
11
+ * heavily on personalization, for example, are likely to be a poor fit
12
+ * for caching.
13
+ */
14
+ public cache = false;
15
+
16
+ /**
17
+ * Whether or not the cache entry should be variable based on the locale
18
+ * of the context in which it is querried.
19
+ */
20
+ public localeDependentCaching = false;
21
+
22
+ /**
23
+ * Whether or not the cache entry should be variable based on the currency
24
+ * of the context in which it is querried.
25
+ */
26
+ public currencyDependentCaching = false;
27
+
28
+ /**
29
+ * The number of seconds which a cache entry should be considered valid for the
30
+ * given query.
31
+ */
32
+ public cacheTimeToLiveInSeconds = 60;
33
+
34
+ };
35
+
36
+ /**
37
+ * Decorator for provider functions to provide functionality such as caching, tracing and type-checked
38
+ * assertion through Zod. It should only be used with publically accessible queries or mutations on
39
+ * providers.
40
+ */
41
+ export function Reactionary(options: Partial<ReactionaryDecoratorOptions>) {
2
42
  return function (
3
- target: any,
43
+ target: BaseProvider,
4
44
  propertyKey: string | symbol,
5
45
  descriptor: PropertyDescriptor
6
46
  ): PropertyDescriptor {
7
47
  const original = descriptor.value;
8
-
9
- descriptor.value = function (...args: any[]) {
10
- console.log('calling through reactionary decoration!');
11
- return original.apply(this, args);
48
+ const scope = `${target.constructor.name}.${propertyKey.toString()}`;
49
+ const configuration = { ...new ReactionaryDecoratorOptions(), ...options };
50
+
51
+ if (!original) {
52
+ throw new Error(
53
+ '@Reactionary decorator may only be applied to methods on classes extending BaseProvider.'
54
+ );
55
+ }
56
+
57
+ descriptor.value = async function (this: BaseProvider, ...args: any[]) {
58
+ const tracer = getTracer();
59
+
60
+ return tracer.startActiveSpan(
61
+ propertyKey.toString(),
62
+ { kind: SpanKind.SERVER },
63
+ async (span) => {
64
+ const cacheKey = this.generateCacheKeyForQuery(scope, args[0]);
65
+
66
+ const fromCache = await this.cache.get(cacheKey, this.schema);
67
+ let result = fromCache;
68
+
69
+ if (!result) {
70
+ result = await original.apply(this, args);
71
+
72
+ const dependencyIds = this.generateDependencyIdsForModel(result);
73
+
74
+ this.cache.put(cacheKey, result, {
75
+ ttlSeconds: configuration.cacheTimeToLiveInSeconds,
76
+ dependencyIds: dependencyIds
77
+ });
78
+ }
79
+
80
+ span.end();
81
+ return this.assert(result as any);
82
+ }
83
+ );
12
84
  };
13
-
85
+
14
86
  return descriptor;
15
87
  };
16
- }
88
+ }
package/core/src/index.ts CHANGED
@@ -1,6 +1,6 @@
1
1
  export * from './cache/cache.interface';
2
- export * from './cache/cache-evaluation.interface';
3
2
  export * from './cache/redis-cache';
3
+ export * from './cache/memory-cache';
4
4
  export * from './cache/noop-cache';
5
5
 
6
6
  export * from './client/client';
@@ -16,3 +16,4 @@ export * from './schemas/session.schema';
16
16
  export * from './schemas/models/';
17
17
  export * from './schemas/mutations/';
18
18
  export * from './schemas/queries';
19
+ export * from './initialization';
@@ -1,10 +1,8 @@
1
- import { Session } from '@reactionary/core';
1
+ import type { RequestContext } from "./schemas/session.schema";
2
2
 
3
-
4
-
5
- export function createAnonymousTestSession(): Session {
3
+ export function createInitialRequestContext(): RequestContext {
6
4
  return {
7
- id: 'test-session-id',
5
+ id: '',
8
6
  identity: {
9
7
  type: 'Anonymous',
10
8
  meta: {
@@ -15,19 +13,31 @@ export function createAnonymousTestSession(): Session {
15
13
  token: undefined,
16
14
  issued: new Date(),
17
15
  expiry: new Date(new Date().getTime() + 3600 * 1000),
18
- logonId: "",
19
- createdAt: "",
20
- updatedAt: "",
16
+ logonId: '',
17
+ createdAt: '',
18
+ updatedAt: '',
21
19
  keyring: [],
22
- currentService: undefined
20
+ currentService: undefined,
23
21
  },
24
22
  languageContext: {
25
23
  locale: 'en-US',
26
24
  currencyCode: 'USD',
27
- countryCode: 'US',
28
25
  },
29
26
  storeIdentifier: {
30
27
  key: 'the-good-store',
31
28
  },
29
+ taxJurisdiction: {
30
+ countryCode: 'US',
31
+ stateCode: '',
32
+ countyCode: '',
33
+ cityCode: '',
34
+ },
35
+ session: {},
36
+
37
+ correlationId: '',
38
+ isBot: false,
39
+ clientIp: '',
40
+ userAgent: '',
41
+ referrer: '',
32
42
  };
33
43
  }
@@ -1,4 +1,4 @@
1
- import { AnalyticsEvent } from '../schemas/models/analytics.model';
1
+ import type { AnalyticsEvent } from '../schemas/models/analytics.model';
2
2
  import { BaseProvider } from './base.provider';
3
3
 
4
4
  export abstract class AnalyticsProvider<
@@ -1,22 +1,22 @@
1
- import { z } from 'zod';
2
- import { BaseModel, createPaginatedResponseSchema } from '../schemas/models/base.model';
3
- import { Cache } from '../cache/cache.interface';
4
- import { Session } from '../schemas/session.schema';
5
- import { IdentifierType } from '../schemas/models/identifiers.model';
1
+ import type { z } from 'zod';
2
+ import type {
3
+ BaseModel} from '../schemas/models/base.model';
4
+ import {
5
+ createPaginatedResponseSchema,
6
+ } from '../schemas/models/base.model';
7
+ import type { Cache } from '../cache/cache.interface';
8
+ import type { RequestContext, Session } from '../schemas/session.schema';
9
+ import type { IdentifierType } from '../schemas/models/identifiers.model';
10
+ import { hasher } from "node-object-hash";
6
11
 
7
12
  /**
8
13
  * Base capability provider, responsible for mutations (changes) and queries (fetches)
9
14
  * for a given business object domain.
10
15
  */
11
- export abstract class BaseProvider<
12
- T extends BaseModel = BaseModel
13
- > {
16
+ export abstract class BaseProvider<T extends BaseModel = BaseModel> {
14
17
  protected cache: Cache;
15
18
 
16
- constructor(
17
- public readonly schema: z.ZodType<T>,
18
- cache: Cache
19
- ) {
19
+ constructor(public readonly schema: z.ZodType<T>, cache: Cache) {
20
20
  this.cache = cache;
21
21
  }
22
22
 
@@ -39,37 +39,73 @@ export abstract class BaseProvider<
39
39
  * Handler for parsing a response from a remote provider and converting it
40
40
  * into the typed domain model.
41
41
  */
42
- protected parseSingle(_body: unknown, session: Session): T {
42
+ protected parseSingle(_body: unknown, reqCtx: RequestContext): T {
43
43
  const model = this.newModel();
44
44
 
45
45
  return this.assert(model);
46
46
  }
47
47
 
48
48
 
49
- protected parsePaginatedResult(_body: unknown, session: Session): z.infer<ReturnType<typeof createPaginatedResponseSchema<typeof this.schema>>> {
49
+ protected parsePaginatedResult(_body: unknown, reqCtx: RequestContext): z.infer<ReturnType<typeof createPaginatedResponseSchema<typeof this.schema>>> {
50
50
  return createPaginatedResponseSchema(this.schema).parse({});
51
51
  }
52
+
53
+ public generateDependencyIdsForModel(model: unknown): Array<string> {
54
+ // TODO: Messy because we can't guarantee that a model has an identifier (type-wise)
55
+ const identifier = (model as any)?.identifier;
52
56
 
53
- protected generateCacheKeyPaginatedResult(resultSetName: string, res: ReturnType<typeof this.parsePaginatedResult>, session: Session): string {
57
+ if (!identifier) {
58
+ return [];
59
+ }
60
+
61
+ const h = hasher({ sort: true, coerce: false });
62
+ const hash = h.hash(identifier);
63
+
64
+ return [hash];
65
+ }
66
+
67
+ protected generateCacheKeyForQuery(scope: string, query: object): string {
68
+ const h = hasher({ sort: true, coerce: false });
69
+
70
+ const queryHash = h.hash(query);
71
+
72
+ // TODO: This really should include the internationalization parts as well (locale, currency, etc), or at least provide the option
73
+ // for specifying in the decorator whether they do (eg categories don't really seem to depend on currency...)
74
+
75
+ return `${scope}:${queryHash}`;
76
+ }
77
+
78
+ protected generateCacheKeyPaginatedResult(
79
+ resultSetName: string,
80
+ res: ReturnType<typeof this.parsePaginatedResult>,
81
+ reqCtx: RequestContext
82
+ ): string {
54
83
  const type = this.getResourceName();
55
- const langPart = session.languageContext.locale;
56
- const currencyPart = session.languageContext.currencyCode || 'default';
57
- const storePart = session.storeIdentifier?.key || 'default';
84
+ const langPart = reqCtx.languageContext.locale;
85
+ const currencyPart = reqCtx.languageContext.currencyCode || 'default';
86
+ const storePart = reqCtx.storeIdentifier?.key || 'default';
58
87
  return `${type}-${resultSetName}-paginated|pageNumber:${res.pageNumber}|pageSize:${res.pageSize}|store:${storePart}|lang:${langPart}|currency:${currencyPart}`;
59
88
  }
60
89
 
61
-
62
- protected generateCacheKeySingle(identifier: IdentifierType, session: Session): string {
90
+ protected generateCacheKeySingle(
91
+ identifier: IdentifierType,
92
+ reqCtx: RequestContext
93
+ ): string {
63
94
  const type = this.getResourceName();
64
- const idPart = Object.entries(identifier).map(([k, v]) => `${k}:${(v as any).key}`).join('#');
65
- const langPart = session.languageContext.locale;
66
- const currencyPart = session.languageContext.currencyCode || 'default';
67
- const storePart = session.storeIdentifier?.key || 'default';
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
+
68
104
  return `${type}-${idPart}|store:${storePart}|lang:${langPart}|currency:${currencyPart}`;
69
105
  }
70
106
 
71
107
  /**
72
108
  * Returns the abstract resource name provided by the remote system.
73
109
  */
74
- protected abstract getResourceName(): string ;
110
+ protected abstract getResourceName(): string;
75
111
  }