@reactionary/source 0.0.42 → 0.0.48
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.
- package/.claude/settings.local.json +28 -0
- package/.vscode/settings.json +5 -0
- package/core/package.json +3 -1
- package/core/src/cache/cache.interface.ts +14 -18
- package/core/src/cache/memory-cache.ts +56 -0
- package/core/src/cache/noop-cache.ts +5 -23
- package/core/src/cache/redis-cache.ts +28 -38
- package/core/src/client/client-builder.ts +3 -3
- package/core/src/client/client.ts +10 -10
- package/core/src/decorators/reactionary.decorator.ts +80 -8
- package/core/src/index.ts +2 -1
- package/{examples/node/src/test-utils.ts → core/src/initialization.ts} +20 -10
- package/core/src/providers/analytics.provider.ts +1 -1
- package/core/src/providers/base.provider.ts +61 -25
- package/core/src/providers/cart-payment.provider.ts +7 -6
- package/core/src/providers/cart.provider.ts +17 -18
- package/core/src/providers/category.provider.ts +9 -9
- package/core/src/providers/identity.provider.ts +8 -7
- package/core/src/providers/index.ts +2 -0
- package/core/src/providers/inventory.provider.ts +4 -4
- package/core/src/providers/price.provider.ts +6 -6
- package/core/src/providers/product.provider.ts +5 -5
- package/core/src/providers/profile.provider.ts +22 -0
- package/core/src/providers/search.provider.ts +4 -4
- package/core/src/providers/store.provider.ts +14 -0
- package/core/src/schemas/capabilities.schema.ts +2 -1
- package/core/src/schemas/models/analytics.model.ts +1 -1
- package/core/src/schemas/models/identifiers.model.ts +66 -38
- package/core/src/schemas/models/identity.model.ts +16 -20
- package/core/src/schemas/models/index.ts +1 -0
- package/core/src/schemas/models/profile.model.ts +3 -2
- package/core/src/schemas/models/shipping-method.model.ts +2 -2
- package/core/src/schemas/models/store.model.ts +11 -0
- package/core/src/schemas/mutations/cart-payment.mutation.ts +1 -1
- package/core/src/schemas/mutations/cart.mutation.ts +2 -2
- package/core/src/schemas/mutations/identity.mutation.ts +6 -0
- package/core/src/schemas/mutations/index.ts +1 -0
- package/core/src/schemas/mutations/profile.mutation.ts +9 -0
- package/core/src/schemas/queries/cart.query.ts +1 -1
- package/core/src/schemas/queries/identity.query.ts +1 -1
- package/core/src/schemas/queries/index.ts +2 -0
- package/core/src/schemas/queries/inventory.query.ts +4 -12
- package/core/src/schemas/queries/price.query.ts +1 -1
- package/core/src/schemas/queries/profile.query.ts +7 -0
- package/core/src/schemas/queries/search.query.ts +1 -1
- package/core/src/schemas/queries/store.query.ts +11 -0
- package/core/src/schemas/session.schema.ts +31 -6
- package/eslint.config.mjs +7 -0
- package/examples/next/src/app/page.tsx +4 -12
- package/examples/node/package.json +1 -3
- package/examples/node/src/basic/basic-node-provider-model-extension.spec.ts +9 -8
- package/examples/node/src/basic/basic-node-provider-query-extension.spec.ts +4 -3
- package/examples/node/src/basic/basic-node-setup.spec.ts +4 -5
- package/nx.json +1 -0
- package/otel/src/metrics.ts +2 -1
- package/otel/src/provider-instrumentation.ts +2 -1
- package/otel/src/tracer.ts +7 -6
- package/otel/src/trpc-middleware.ts +3 -2
- package/package.json +2 -1
- package/providers/algolia/src/core/initialize.ts +4 -3
- package/providers/algolia/src/providers/product.provider.ts +15 -13
- package/providers/algolia/src/providers/search.provider.ts +9 -9
- package/providers/algolia/src/schema/capabilities.schema.ts +1 -1
- package/providers/algolia/src/test/search.provider.spec.ts +10 -10
- package/providers/algolia/src/test/test-utils.ts +1 -1
- package/providers/commercetools/README.md +11 -0
- package/providers/commercetools/src/core/client.ts +144 -88
- package/providers/commercetools/src/core/initialize.ts +14 -11
- package/providers/commercetools/src/providers/cart-payment.provider.ts +30 -29
- package/providers/commercetools/src/providers/cart.provider.ts +88 -116
- package/providers/commercetools/src/providers/category.provider.ts +34 -37
- package/providers/commercetools/src/providers/identity.provider.ts +22 -82
- package/providers/commercetools/src/providers/index.ts +2 -0
- package/providers/commercetools/src/providers/inventory.provider.ts +67 -48
- package/providers/commercetools/src/providers/price.provider.ts +76 -50
- package/providers/commercetools/src/providers/product.provider.ts +20 -23
- package/providers/commercetools/src/providers/profile.provider.ts +61 -0
- package/providers/commercetools/src/providers/search.provider.ts +12 -15
- package/providers/commercetools/src/providers/store.provider.ts +78 -0
- package/providers/commercetools/src/schema/capabilities.schema.ts +2 -1
- package/providers/commercetools/src/test/cart-payment.provider.spec.ts +13 -17
- package/providers/commercetools/src/test/cart.provider.spec.ts +18 -17
- package/providers/commercetools/src/test/category.provider.spec.ts +18 -17
- package/providers/commercetools/src/test/identity.provider.spec.ts +88 -0
- package/providers/commercetools/src/test/inventory.provider.spec.ts +41 -0
- package/providers/commercetools/src/test/price.provider.spec.ts +9 -8
- package/providers/commercetools/src/test/product.provider.spec.ts +9 -8
- package/providers/commercetools/src/test/profile.provider.spec.ts +49 -0
- package/providers/commercetools/src/test/search.provider.spec.ts +8 -7
- package/providers/commercetools/src/test/store.provider.spec.ts +37 -0
- package/providers/commercetools/src/test/test-utils.ts +0 -39
- package/providers/fake/src/core/initialize.ts +96 -38
- package/providers/fake/src/providers/analytics.provider.ts +6 -5
- package/providers/fake/src/providers/cart.provider.ts +30 -27
- package/providers/fake/src/providers/category.provider.ts +12 -12
- package/providers/fake/src/providers/identity.provider.ts +22 -14
- package/providers/fake/src/providers/index.ts +1 -0
- package/providers/fake/src/providers/inventory.provider.ts +13 -13
- package/providers/fake/src/providers/price.provider.ts +13 -13
- package/providers/fake/src/providers/product.provider.ts +11 -11
- package/providers/fake/src/providers/search.provider.ts +7 -5
- package/providers/fake/src/providers/store.provider.ts +47 -0
- package/providers/fake/src/schema/capabilities.schema.ts +4 -1
- package/providers/fake/src/test/cart.provider.spec.ts +18 -18
- package/providers/fake/src/test/category.provider.spec.ts +55 -37
- package/providers/fake/src/test/price.provider.spec.ts +9 -14
- package/providers/fake/src/test/product.provider.spec.ts +27 -0
- package/providers/fake/src/test/test-utils.ts +2 -33
- package/providers/posthog/src/core/initialize.ts +3 -3
- package/providers/posthog/src/schema/capabilities.schema.ts +1 -1
- package/trpc/src/client.ts +42 -41
- package/trpc/src/index.ts +4 -3
- package/trpc/src/integration.spec.ts +11 -11
- package/trpc/src/server.ts +26 -24
- package/trpc/src/test-utils.ts +1 -1
- package/trpc/src/types.ts +24 -22
- package/core/src/cache/cache-evaluation.interface.ts +0 -19
|
@@ -0,0 +1,28 @@
|
|
|
1
|
+
{
|
|
2
|
+
"permissions": {
|
|
3
|
+
"allow": [
|
|
4
|
+
"Bash(npx nx build:*)",
|
|
5
|
+
"Bash(npx tsc:*)",
|
|
6
|
+
"Bash(npx nx lint:*)",
|
|
7
|
+
"Bash(npx nx test:*)",
|
|
8
|
+
"Bash(npx nx run:*)",
|
|
9
|
+
"Bash(npx ts-node:*)",
|
|
10
|
+
"Bash(npx tsx:*)",
|
|
11
|
+
"Bash(grep:*)",
|
|
12
|
+
"Bash(pnpm list:*)",
|
|
13
|
+
"Bash(npx nx affected:build:*)",
|
|
14
|
+
"Bash(npx nx affected:lint:*)",
|
|
15
|
+
"Bash(cat:*)",
|
|
16
|
+
"Bash(npx nx show projects:*)",
|
|
17
|
+
"Bash(npx nx g:*)",
|
|
18
|
+
"Bash(pnpm add:*)",
|
|
19
|
+
"Bash(npx nx show project:*)",
|
|
20
|
+
"Bash(npx nx reset:*)",
|
|
21
|
+
"WebSearch",
|
|
22
|
+
"Bash(node:*)",
|
|
23
|
+
"Bash(npm view:*)"
|
|
24
|
+
],
|
|
25
|
+
"deny": [],
|
|
26
|
+
"ask": []
|
|
27
|
+
}
|
|
28
|
+
}
|
package/core/package.json
CHANGED
|
@@ -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,
|
|
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
|
-
*
|
|
25
|
+
* Removes entries from the cache based on a set of dependency
|
|
26
|
+
* ids
|
|
31
27
|
*/
|
|
32
|
-
|
|
28
|
+
invalidate(dependencyIds: Array<string>): Promise<void>;
|
|
33
29
|
|
|
34
30
|
/**
|
|
35
|
-
*
|
|
31
|
+
* Removes all entries from cache, wiping it completely
|
|
36
32
|
*/
|
|
37
|
-
|
|
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,
|
|
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
|
|
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
|
|
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(
|
|
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
|
|
33
|
-
|
|
34
|
-
}
|
|
36
|
+
const serialized = JSON.stringify(value);
|
|
37
|
+
const multi = this.redis.multi();
|
|
35
38
|
|
|
36
|
-
|
|
37
|
-
|
|
38
|
-
|
|
39
|
-
|
|
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
|
-
|
|
54
|
-
return await this.redis.keys(pattern);
|
|
45
|
+
await multi.exec();
|
|
55
46
|
}
|
|
56
47
|
|
|
57
|
-
public async
|
|
58
|
-
const
|
|
59
|
-
|
|
60
|
-
|
|
61
|
-
|
|
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
|
|
66
|
-
//
|
|
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 { CartPaymentProvider } from "../providers";
|
|
12
12
|
|
|
13
13
|
export interface Client {
|
|
14
14
|
product: ProductProvider,
|
|
@@ -1,16 +1,88 @@
|
|
|
1
|
-
|
|
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:
|
|
43
|
+
target: BaseProvider,
|
|
4
44
|
propertyKey: string | symbol,
|
|
5
45
|
descriptor: PropertyDescriptor
|
|
6
46
|
): PropertyDescriptor {
|
|
7
47
|
const original = descriptor.value;
|
|
8
|
-
|
|
9
|
-
|
|
10
|
-
|
|
11
|
-
|
|
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 {
|
|
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: '
|
|
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,22 +1,22 @@
|
|
|
1
|
-
import { z } from 'zod';
|
|
2
|
-
import {
|
|
3
|
-
|
|
4
|
-
import {
|
|
5
|
-
|
|
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,
|
|
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,
|
|
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
|
-
|
|
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 =
|
|
56
|
-
const currencyPart =
|
|
57
|
-
const storePart =
|
|
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
|
-
|
|
90
|
+
protected generateCacheKeySingle(
|
|
91
|
+
identifier: IdentifierType,
|
|
92
|
+
reqCtx: RequestContext
|
|
93
|
+
): string {
|
|
63
94
|
const type = this.getResourceName();
|
|
64
|
-
|
|
65
|
-
const
|
|
66
|
-
|
|
67
|
-
|
|
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
|
}
|
|
@@ -1,6 +1,7 @@
|
|
|
1
|
-
import { CartPaymentInstruction } from '../schemas/models/payment.model';
|
|
2
|
-
import { CartPaymentMutationAddPayment, CartPaymentMutationCancelPayment } from '../schemas/mutations/cart-payment.mutation';
|
|
3
|
-
import { CartPaymentQueryByCart } from '../schemas/queries/cart-payment.query';
|
|
1
|
+
import type { CartPaymentInstruction } from '../schemas/models/payment.model';
|
|
2
|
+
import type { CartPaymentMutationAddPayment, CartPaymentMutationCancelPayment } from '../schemas/mutations/cart-payment.mutation';
|
|
3
|
+
import type { CartPaymentQueryByCart } from '../schemas/queries/cart-payment.query';
|
|
4
|
+
import type { RequestContext} from '../schemas/session.schema';
|
|
4
5
|
import { Session } from '../schemas/session.schema';
|
|
5
6
|
import { BaseProvider } from './base.provider';
|
|
6
7
|
|
|
@@ -20,7 +21,7 @@ export abstract class CartPaymentProvider<
|
|
|
20
21
|
* @param cartIdentifier
|
|
21
22
|
* @param session
|
|
22
23
|
*/
|
|
23
|
-
public abstract getByCartIdentifier(payload: CartPaymentQueryByCart,
|
|
24
|
+
public abstract getByCartIdentifier(payload: CartPaymentQueryByCart, reqCtx: RequestContext): Promise<T[]>;
|
|
24
25
|
|
|
25
26
|
|
|
26
27
|
/**
|
|
@@ -33,7 +34,7 @@ export abstract class CartPaymentProvider<
|
|
|
33
34
|
* @param payload
|
|
34
35
|
* @param session
|
|
35
36
|
*/
|
|
36
|
-
public abstract initiatePaymentForCart(payload: CartPaymentMutationAddPayment,
|
|
37
|
+
public abstract initiatePaymentForCart(payload: CartPaymentMutationAddPayment, reqCtx: RequestContext): Promise<T>;
|
|
37
38
|
|
|
38
39
|
|
|
39
40
|
|
|
@@ -48,7 +49,7 @@ export abstract class CartPaymentProvider<
|
|
|
48
49
|
* @param session
|
|
49
50
|
* @returns
|
|
50
51
|
*/
|
|
51
|
-
public abstract cancelPaymentInstruction(payload: CartPaymentMutationCancelPayment,
|
|
52
|
+
public abstract cancelPaymentInstruction(payload: CartPaymentMutationCancelPayment, reqCtx: RequestContext): Promise<T>;
|
|
52
53
|
|
|
53
54
|
protected override getResourceName(): string {
|
|
54
55
|
return 'cart-payment-instruction';
|