@reactionary/source 0.0.31 → 0.0.32
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/core/package.json +1 -1
- package/core/src/client/client-builder.ts +63 -0
- package/core/src/decorators/trpc.decorators.ts +144 -0
- package/core/src/index.ts +3 -0
- package/core/src/providers/analytics.provider.ts +2 -28
- package/core/src/providers/base.provider.ts +7 -135
- package/core/src/providers/cart.provider.ts +9 -22
- package/core/src/providers/identity.provider.ts +7 -21
- package/core/src/providers/inventory.provider.ts +4 -18
- package/core/src/providers/price.provider.ts +5 -22
- package/core/src/providers/product.provider.ts +5 -28
- package/core/src/providers/search.provider.ts +5 -22
- package/core/src/schemas/mutations/base.mutation.ts +0 -1
- package/core/src/schemas/mutations/cart.mutation.ts +0 -6
- package/core/src/schemas/mutations/identity.mutation.ts +0 -5
- package/core/src/schemas/queries/base.query.ts +0 -1
- package/core/src/schemas/queries/cart.query.ts +1 -3
- package/core/src/schemas/queries/identity.query.ts +1 -3
- package/core/src/schemas/queries/inventory.query.ts +0 -1
- package/core/src/schemas/queries/price.query.ts +0 -3
- package/core/src/schemas/queries/product.query.ts +2 -7
- package/core/src/schemas/queries/search.query.ts +0 -3
- package/examples/node/package.json +1 -5
- package/examples/node/src/basic/basic-node-provider-model-extension.spec.ts +97 -0
- package/examples/node/src/basic/basic-node-provider-query-extension.spec.ts +84 -0
- package/examples/node/src/basic/basic-node-setup.spec.ts +40 -0
- package/otel/src/index.ts +3 -0
- package/otel/src/trace-decorator.ts +246 -0
- package/package.json +2 -1
- package/providers/algolia/src/core/initialize.ts +3 -3
- package/providers/algolia/src/providers/product.provider.ts +44 -11
- package/providers/algolia/src/providers/search.provider.ts +47 -66
- package/providers/commercetools/src/core/client.ts +0 -1
- package/providers/commercetools/src/core/initialize.ts +8 -9
- package/providers/commercetools/src/providers/cart.provider.ts +58 -90
- package/providers/commercetools/src/providers/identity.provider.ts +34 -51
- package/providers/commercetools/src/providers/inventory.provider.ts +16 -29
- package/providers/commercetools/src/providers/price.provider.ts +30 -35
- package/providers/commercetools/src/providers/product.provider.ts +48 -38
- package/providers/commercetools/src/providers/search.provider.ts +32 -47
- package/providers/commercetools/src/schema/capabilities.schema.ts +1 -1
- package/providers/fake/package.json +1 -0
- package/providers/fake/src/core/initialize.ts +12 -11
- package/providers/fake/src/index.ts +4 -0
- package/providers/fake/src/providers/analytics.provider.ts +19 -0
- package/providers/fake/src/providers/cart.provider.ts +107 -0
- package/providers/fake/src/providers/identity.provider.ts +78 -68
- package/providers/fake/src/providers/inventory.provider.ts +54 -0
- package/providers/fake/src/providers/price.provider.ts +60 -0
- package/providers/fake/src/providers/product.provider.ts +53 -49
- package/providers/fake/src/providers/search.provider.ts +15 -33
- package/trpc/__mocks__/superjson.js +25 -0
- package/trpc/jest.config.ts +14 -0
- package/trpc/package.json +2 -1
- package/trpc/src/client.ts +176 -0
- package/trpc/src/index.ts +35 -62
- package/trpc/src/integration.spec.ts +216 -0
- package/trpc/src/server.ts +123 -0
- package/trpc/src/transparent-client.spec.ts +160 -0
- package/trpc/src/types.ts +142 -0
- package/trpc/tsconfig.json +3 -0
- package/trpc/tsconfig.lib.json +2 -1
- package/trpc/tsconfig.spec.json +15 -0
- package/tsconfig.base.json +0 -2
- package/examples/angular/e2e/example.spec.ts +0 -9
- package/examples/angular/eslint.config.mjs +0 -41
- package/examples/angular/playwright.config.ts +0 -38
- package/examples/angular/project.json +0 -86
- package/examples/angular/public/favicon.ico +0 -0
- package/examples/angular/src/app/app.component.html +0 -6
- package/examples/angular/src/app/app.component.scss +0 -22
- package/examples/angular/src/app/app.component.ts +0 -14
- package/examples/angular/src/app/app.config.ts +0 -16
- package/examples/angular/src/app/app.routes.ts +0 -25
- package/examples/angular/src/app/cart/cart.component.html +0 -4
- package/examples/angular/src/app/cart/cart.component.scss +0 -14
- package/examples/angular/src/app/cart/cart.component.ts +0 -73
- package/examples/angular/src/app/identity/identity.component.html +0 -6
- package/examples/angular/src/app/identity/identity.component.scss +0 -18
- package/examples/angular/src/app/identity/identity.component.ts +0 -49
- package/examples/angular/src/app/product/product.component.html +0 -14
- package/examples/angular/src/app/product/product.component.scss +0 -11
- package/examples/angular/src/app/product/product.component.ts +0 -42
- package/examples/angular/src/app/search/search.component.html +0 -35
- package/examples/angular/src/app/search/search.component.scss +0 -129
- package/examples/angular/src/app/search/search.component.ts +0 -50
- package/examples/angular/src/app/services/product.service.ts +0 -35
- package/examples/angular/src/app/services/search.service.ts +0 -48
- package/examples/angular/src/app/services/trpc.client.ts +0 -27
- package/examples/angular/src/index.html +0 -13
- package/examples/angular/src/main.ts +0 -7
- package/examples/angular/src/styles.scss +0 -17
- package/examples/angular/src/test-setup.ts +0 -6
- package/examples/angular/tsconfig.app.json +0 -10
- package/examples/angular/tsconfig.editor.json +0 -6
- package/examples/angular/tsconfig.json +0 -32
- package/examples/node/src/initialize-algolia.spec.ts +0 -29
- package/examples/node/src/initialize-commercetools.spec.ts +0 -31
- package/examples/node/src/initialize-extended-providers.spec.ts +0 -38
- package/examples/node/src/initialize-mixed-providers.spec.ts +0 -36
- package/examples/node/src/providers/custom-algolia-product.provider.ts +0 -18
- package/examples/node/src/schemas/custom-product.schema.ts +0 -8
- package/examples/trpc-node/.env.example +0 -52
- package/examples/trpc-node/eslint.config.mjs +0 -3
- package/examples/trpc-node/project.json +0 -61
- package/examples/trpc-node/src/assets/.gitkeep +0 -0
- package/examples/trpc-node/src/main.ts +0 -59
- package/examples/trpc-node/src/router-instance.ts +0 -54
- package/examples/trpc-node/tsconfig.app.json +0 -9
- package/examples/trpc-node/tsconfig.json +0 -13
package/core/package.json
CHANGED
|
@@ -0,0 +1,63 @@
|
|
|
1
|
+
import { Cache } from "../cache/cache.interface";
|
|
2
|
+
import { NoOpCache } from "../cache/noop-cache";
|
|
3
|
+
import { Client } from "./client";
|
|
4
|
+
import { AnalyticsProvider } from "../providers/analytics.provider";
|
|
5
|
+
|
|
6
|
+
type CapabilityFactory<T> = (cache: Cache) => T;
|
|
7
|
+
|
|
8
|
+
type MergeCapabilities<Acc, New> = Omit<Acc, keyof New> & New;
|
|
9
|
+
|
|
10
|
+
export class ClientBuilder<TClient = object> {
|
|
11
|
+
private factories: Array<CapabilityFactory<Partial<Client>>> = [];
|
|
12
|
+
private cache: Cache | undefined;
|
|
13
|
+
|
|
14
|
+
withCapability<TNew extends Partial<Client>>(
|
|
15
|
+
factory: CapabilityFactory<TNew>
|
|
16
|
+
): ClientBuilder<MergeCapabilities<TClient, TNew>> {
|
|
17
|
+
const newBuilder = new ClientBuilder<MergeCapabilities<TClient, TNew>>();
|
|
18
|
+
newBuilder.factories = [...this.factories, factory];
|
|
19
|
+
newBuilder.cache = this.cache;
|
|
20
|
+
return newBuilder;
|
|
21
|
+
}
|
|
22
|
+
|
|
23
|
+
withCache(cache: Cache): ClientBuilder<TClient> {
|
|
24
|
+
const newBuilder = new ClientBuilder<TClient>();
|
|
25
|
+
newBuilder.factories = [...this.factories];
|
|
26
|
+
newBuilder.cache = cache;
|
|
27
|
+
return newBuilder;
|
|
28
|
+
}
|
|
29
|
+
|
|
30
|
+
build(): TClient & { cache: Cache } {
|
|
31
|
+
let client = {} as TClient;
|
|
32
|
+
|
|
33
|
+
// Use provided cache or default to NoOpCache
|
|
34
|
+
const sharedCache = this.cache || new NoOpCache();
|
|
35
|
+
|
|
36
|
+
const mergedAnalytics: AnalyticsProvider[] = [];
|
|
37
|
+
|
|
38
|
+
for (const factory of this.factories) {
|
|
39
|
+
const provider = factory(sharedCache);
|
|
40
|
+
client = {
|
|
41
|
+
...client,
|
|
42
|
+
...provider
|
|
43
|
+
};
|
|
44
|
+
|
|
45
|
+
if (provider.analytics) {
|
|
46
|
+
mergedAnalytics.push(...provider.analytics);
|
|
47
|
+
}
|
|
48
|
+
}
|
|
49
|
+
|
|
50
|
+
// Add merged analytics if any were collected
|
|
51
|
+
if (mergedAnalytics.length > 0) {
|
|
52
|
+
(client as Record<string, unknown>)['analytics'] = mergedAnalytics;
|
|
53
|
+
}
|
|
54
|
+
|
|
55
|
+
// Add cache to complete the client
|
|
56
|
+
const completeClient = {
|
|
57
|
+
...client,
|
|
58
|
+
cache: sharedCache
|
|
59
|
+
} as TClient & { cache: Cache };
|
|
60
|
+
|
|
61
|
+
return completeClient;
|
|
62
|
+
}
|
|
63
|
+
}
|
|
@@ -0,0 +1,144 @@
|
|
|
1
|
+
import 'reflect-metadata';
|
|
2
|
+
|
|
3
|
+
/**
|
|
4
|
+
* Metadata keys for TRPC method decorators
|
|
5
|
+
*/
|
|
6
|
+
export const TRPC_QUERY_METADATA_KEY = Symbol('trpc:query');
|
|
7
|
+
export const TRPC_MUTATION_METADATA_KEY = Symbol('trpc:mutation');
|
|
8
|
+
|
|
9
|
+
|
|
10
|
+
|
|
11
|
+
/**
|
|
12
|
+
* Options for TRPC method decorators
|
|
13
|
+
*/
|
|
14
|
+
export interface TRPCMethodOptions {
|
|
15
|
+
/** Custom name for the TRPC procedure (defaults to method name) */
|
|
16
|
+
name?: string;
|
|
17
|
+
/** Description for documentation purposes */
|
|
18
|
+
description?: string;
|
|
19
|
+
}
|
|
20
|
+
|
|
21
|
+
/**
|
|
22
|
+
* Decorator to mark a provider method as a TRPC query procedure
|
|
23
|
+
* Query procedures are read-only operations (GET-like)
|
|
24
|
+
*
|
|
25
|
+
* @example
|
|
26
|
+
* ```typescript
|
|
27
|
+
* class ProductProvider extends BaseProvider {
|
|
28
|
+
* @trpcQuery()
|
|
29
|
+
* async getById(payload: ProductQueryById, session: Session): Promise<Product> {
|
|
30
|
+
* // implementation
|
|
31
|
+
* }
|
|
32
|
+
*
|
|
33
|
+
* @trpcQuery({ name: 'findBySlug', description: 'Find product by URL slug' })
|
|
34
|
+
* async getBySlug(payload: ProductQueryBySlug, session: Session): Promise<Product> {
|
|
35
|
+
* // implementation
|
|
36
|
+
* }
|
|
37
|
+
* }
|
|
38
|
+
* ```
|
|
39
|
+
*/
|
|
40
|
+
export function trpcQuery(options: TRPCMethodOptions = {}): MethodDecorator {
|
|
41
|
+
return function (target: any, propertyKey: string | symbol, descriptor: PropertyDescriptor) {
|
|
42
|
+
// Store metadata about this method being a TRPC query
|
|
43
|
+
const metadata = {
|
|
44
|
+
methodName: String(propertyKey),
|
|
45
|
+
isQuery: true,
|
|
46
|
+
isMutation: false,
|
|
47
|
+
options
|
|
48
|
+
};
|
|
49
|
+
|
|
50
|
+
// Store on the prototype so it's inherited
|
|
51
|
+
Reflect.defineMetadata(TRPC_QUERY_METADATA_KEY, metadata, target, propertyKey);
|
|
52
|
+
|
|
53
|
+
// Also store a list of all TRPC methods on the class
|
|
54
|
+
const existingMethods = Reflect.getMetadata(TRPC_QUERY_METADATA_KEY, target.constructor) || [];
|
|
55
|
+
existingMethods.push({ propertyKey, metadata });
|
|
56
|
+
Reflect.defineMetadata(TRPC_QUERY_METADATA_KEY, existingMethods, target.constructor);
|
|
57
|
+
};
|
|
58
|
+
}
|
|
59
|
+
|
|
60
|
+
/**
|
|
61
|
+
* Decorator to mark a provider method as a TRPC mutation procedure
|
|
62
|
+
* Mutation procedures are write operations that modify state (POST/PUT/DELETE-like)
|
|
63
|
+
*
|
|
64
|
+
* @example
|
|
65
|
+
* ```typescript
|
|
66
|
+
* class CartProvider extends BaseProvider {
|
|
67
|
+
* @trpcMutation()
|
|
68
|
+
* async add(payload: CartAddMutation, session: Session): Promise<Cart> {
|
|
69
|
+
* // implementation
|
|
70
|
+
* }
|
|
71
|
+
*
|
|
72
|
+
* @trpcMutation({ name: 'removeItem' })
|
|
73
|
+
* async remove(payload: CartRemoveMutation, session: Session): Promise<Cart> {
|
|
74
|
+
* // implementation
|
|
75
|
+
* }
|
|
76
|
+
* }
|
|
77
|
+
* ```
|
|
78
|
+
*/
|
|
79
|
+
export function trpcMutation(options: TRPCMethodOptions = {}): MethodDecorator {
|
|
80
|
+
return function (target: any, propertyKey: string | symbol, descriptor: PropertyDescriptor) {
|
|
81
|
+
// Store metadata about this method being a TRPC mutation
|
|
82
|
+
const metadata = {
|
|
83
|
+
methodName: String(propertyKey),
|
|
84
|
+
isQuery: false,
|
|
85
|
+
isMutation: true,
|
|
86
|
+
options
|
|
87
|
+
};
|
|
88
|
+
|
|
89
|
+
// Store on the prototype so it's inherited
|
|
90
|
+
Reflect.defineMetadata(TRPC_MUTATION_METADATA_KEY, metadata, target, propertyKey);
|
|
91
|
+
|
|
92
|
+
// Also store a list of all TRPC methods on the class
|
|
93
|
+
const existingMethods = Reflect.getMetadata(TRPC_MUTATION_METADATA_KEY, target.constructor) || [];
|
|
94
|
+
existingMethods.push({ propertyKey, metadata });
|
|
95
|
+
Reflect.defineMetadata(TRPC_MUTATION_METADATA_KEY, existingMethods, target.constructor);
|
|
96
|
+
};
|
|
97
|
+
}
|
|
98
|
+
|
|
99
|
+
/**
|
|
100
|
+
* Get all TRPC query methods from a class or instance
|
|
101
|
+
*/
|
|
102
|
+
export function getTRPCQueryMethods(target: any): Array<{ propertyKey: string | symbol; metadata: any }> {
|
|
103
|
+
const constructor = typeof target === 'function' ? target : target.constructor;
|
|
104
|
+
return Reflect.getMetadata(TRPC_QUERY_METADATA_KEY, constructor) || [];
|
|
105
|
+
}
|
|
106
|
+
|
|
107
|
+
/**
|
|
108
|
+
* Get all TRPC mutation methods from a class or instance
|
|
109
|
+
*/
|
|
110
|
+
export function getTRPCMutationMethods(target: any): Array<{ propertyKey: string | symbol; metadata: any }> {
|
|
111
|
+
const constructor = typeof target === 'function' ? target : target.constructor;
|
|
112
|
+
return Reflect.getMetadata(TRPC_MUTATION_METADATA_KEY, constructor) || [];
|
|
113
|
+
}
|
|
114
|
+
|
|
115
|
+
/**
|
|
116
|
+
* Get all TRPC methods (both queries and mutations) from a class or instance
|
|
117
|
+
*/
|
|
118
|
+
export function getAllTRPCMethods(target: any): Array<{ propertyKey: string | symbol; metadata: any }> {
|
|
119
|
+
return [
|
|
120
|
+
...getTRPCQueryMethods(target),
|
|
121
|
+
...getTRPCMutationMethods(target)
|
|
122
|
+
];
|
|
123
|
+
}
|
|
124
|
+
|
|
125
|
+
/**
|
|
126
|
+
* Check if a method is marked as a TRPC query
|
|
127
|
+
*/
|
|
128
|
+
export function isTRPCQuery(target: any, methodName: string | symbol): boolean {
|
|
129
|
+
return !!Reflect.getMetadata(TRPC_QUERY_METADATA_KEY, target, methodName);
|
|
130
|
+
}
|
|
131
|
+
|
|
132
|
+
/**
|
|
133
|
+
* Check if a method is marked as a TRPC mutation
|
|
134
|
+
*/
|
|
135
|
+
export function isTRPCMutation(target: any, methodName: string | symbol): boolean {
|
|
136
|
+
return !!Reflect.getMetadata(TRPC_MUTATION_METADATA_KEY, target, methodName);
|
|
137
|
+
}
|
|
138
|
+
|
|
139
|
+
/**
|
|
140
|
+
* Check if a method is marked for TRPC exposure (query or mutation)
|
|
141
|
+
*/
|
|
142
|
+
export function isTRPCMethod(target: any, methodName: string | symbol): boolean {
|
|
143
|
+
return isTRPCQuery(target, methodName) || isTRPCMutation(target, methodName);
|
|
144
|
+
}
|
package/core/src/index.ts
CHANGED
|
@@ -4,6 +4,9 @@ export * from './cache/redis-cache';
|
|
|
4
4
|
export * from './cache/noop-cache';
|
|
5
5
|
|
|
6
6
|
export * from './client/client';
|
|
7
|
+
export * from './client/client-builder';
|
|
8
|
+
|
|
9
|
+
export * from './decorators/trpc.decorators';
|
|
7
10
|
|
|
8
11
|
export * from './providers/analytics.provider';
|
|
9
12
|
export * from './providers/base.provider';
|
|
@@ -1,33 +1,7 @@
|
|
|
1
1
|
import { AnalyticsEvent } from '../schemas/models/analytics.model';
|
|
2
|
-
import { AnalyticsMutation } from '../schemas/mutations/analytics.mutation';
|
|
3
|
-
import { AnalyticsQuery } from '../schemas/queries/analytics.query';
|
|
4
2
|
import { BaseProvider } from './base.provider';
|
|
5
|
-
import { CacheEvaluation } from '../cache/cache-evaluation.interface';
|
|
6
|
-
import { Session } from '../schemas/session.schema';
|
|
7
|
-
import * as crypto from 'crypto';
|
|
8
3
|
|
|
9
4
|
export abstract class AnalyticsProvider<
|
|
10
|
-
T extends AnalyticsEvent = AnalyticsEvent
|
|
11
|
-
|
|
12
|
-
M extends AnalyticsMutation = AnalyticsMutation
|
|
13
|
-
> extends BaseProvider<T, Q, M> {
|
|
14
|
-
|
|
15
|
-
protected override getCacheEvaluation(query: Q, _session: Session): CacheEvaluation {
|
|
16
|
-
const providerName = this.constructor.name.toLowerCase();
|
|
17
|
-
|
|
18
|
-
// Hash analytics query parameters
|
|
19
|
-
const relevantFields = {
|
|
20
|
-
type: typeof query === 'object' && query !== null && 'type' in query ? (query as any).type : undefined,
|
|
21
|
-
dateRange: typeof query === 'object' && query !== null && 'dateRange' in query ? (query as any).dateRange : undefined,
|
|
22
|
-
filters: typeof query === 'object' && query !== null && 'filters' in query ? (query as any).filters : undefined
|
|
23
|
-
};
|
|
24
|
-
const analyticsHash = crypto.createHash('md5').update(JSON.stringify(relevantFields)).digest('hex').substring(0, 12);
|
|
25
|
-
const key = `${providerName}:analytics:${analyticsHash}`;
|
|
26
|
-
|
|
27
|
-
return {
|
|
28
|
-
key,
|
|
29
|
-
cacheDurationInSeconds: 0,
|
|
30
|
-
canCache: false
|
|
31
|
-
};
|
|
32
|
-
}
|
|
5
|
+
T extends AnalyticsEvent = AnalyticsEvent
|
|
6
|
+
> extends BaseProvider<T> {
|
|
33
7
|
}
|
|
@@ -1,32 +1,20 @@
|
|
|
1
1
|
import { z } from 'zod';
|
|
2
|
-
import { Session } from '../schemas/session.schema';
|
|
3
|
-
import { BaseQuery } from '../schemas/queries/base.query';
|
|
4
|
-
import { BaseMutation } from '../schemas/mutations/base.mutation';
|
|
5
2
|
import { BaseModel } from '../schemas/models/base.model';
|
|
6
|
-
import { createProviderInstrumentation } from '@reactionary/otel';
|
|
7
3
|
import { Cache } from '../cache/cache.interface';
|
|
8
|
-
import { CacheEvaluation } from '../cache/cache-evaluation.interface';
|
|
9
|
-
import * as crypto from 'crypto';
|
|
10
4
|
|
|
11
5
|
/**
|
|
12
6
|
* Base capability provider, responsible for mutations (changes) and queries (fetches)
|
|
13
7
|
* for a given business object domain.
|
|
14
8
|
*/
|
|
15
9
|
export abstract class BaseProvider<
|
|
16
|
-
T extends BaseModel = BaseModel
|
|
17
|
-
Q extends BaseQuery = BaseQuery,
|
|
18
|
-
M extends BaseMutation = BaseMutation
|
|
10
|
+
T extends BaseModel = BaseModel
|
|
19
11
|
> {
|
|
20
|
-
protected instrumentation: ReturnType<typeof createProviderInstrumentation>;
|
|
21
12
|
protected cache: Cache;
|
|
22
13
|
|
|
23
14
|
constructor(
|
|
24
|
-
public readonly schema: z.ZodType<T>,
|
|
25
|
-
public readonly querySchema: z.ZodType<Q, Q>,
|
|
26
|
-
public readonly mutationSchema: z.ZodType<M, M>,
|
|
15
|
+
public readonly schema: z.ZodType<T>,
|
|
27
16
|
cache: Cache
|
|
28
17
|
) {
|
|
29
|
-
this.instrumentation = createProviderInstrumentation(this.constructor.name);
|
|
30
18
|
this.cache = cache;
|
|
31
19
|
}
|
|
32
20
|
|
|
@@ -46,128 +34,12 @@ export abstract class BaseProvider<
|
|
|
46
34
|
}
|
|
47
35
|
|
|
48
36
|
/**
|
|
49
|
-
*
|
|
50
|
-
*
|
|
51
|
-
* result list will never contain nulls or undefined. The order
|
|
52
|
-
* of the results will match the order of the queries.
|
|
37
|
+
* Handler for parsing a response from a remote provider and converting it
|
|
38
|
+
* into the typed domain model.
|
|
53
39
|
*/
|
|
54
|
-
|
|
55
|
-
|
|
56
|
-
'query',
|
|
57
|
-
async (span) => {
|
|
58
|
-
span.setAttribute('provider.query.count', queries.length);
|
|
59
|
-
|
|
60
|
-
let cacheHits = 0;
|
|
61
|
-
let cacheMisses = 0;
|
|
62
|
-
const results: T[] = [];
|
|
40
|
+
protected parseSingle(_body: unknown): T {
|
|
41
|
+
const model = this.newModel();
|
|
63
42
|
|
|
64
|
-
|
|
65
|
-
for (const query of queries) {
|
|
66
|
-
let result: T | null = null;
|
|
67
|
-
|
|
68
|
-
// Get cache evaluation from provider
|
|
69
|
-
const cacheInfo = this.getCacheEvaluation(query, session);
|
|
70
|
-
|
|
71
|
-
// Try cache first if caching is enabled
|
|
72
|
-
if (cacheInfo.canCache && cacheInfo.key) {
|
|
73
|
-
try {
|
|
74
|
-
result = await this.cache.get(cacheInfo.key, this.schema);
|
|
75
|
-
if (result) {
|
|
76
|
-
cacheHits++;
|
|
77
|
-
span.setAttribute('provider.cache.hit', true);
|
|
78
|
-
}
|
|
79
|
-
} catch (error) {
|
|
80
|
-
// Cache error shouldn't break the query - log and continue
|
|
81
|
-
console.warn(`Cache get error for ${this.constructor.name}:`, error);
|
|
82
|
-
}
|
|
83
|
-
}
|
|
84
|
-
|
|
85
|
-
// If not in cache, fetch from source
|
|
86
|
-
if (!result) {
|
|
87
|
-
const singleResult = await this.fetch([query], session);
|
|
88
|
-
result = singleResult[0];
|
|
89
|
-
cacheMisses++;
|
|
90
|
-
|
|
91
|
-
// Store in cache if caching is enabled
|
|
92
|
-
if (result && cacheInfo.canCache && cacheInfo.key) {
|
|
93
|
-
try {
|
|
94
|
-
await this.cache.put(cacheInfo.key, result, cacheInfo.cacheDurationInSeconds);
|
|
95
|
-
} catch (error) {
|
|
96
|
-
// Cache put error shouldn't break the query - log and continue
|
|
97
|
-
console.warn(`Cache put error for ${this.constructor.name}:`, error);
|
|
98
|
-
}
|
|
99
|
-
}
|
|
100
|
-
}
|
|
101
|
-
|
|
102
|
-
if (result) {
|
|
103
|
-
this.assert(result);
|
|
104
|
-
results.push(result);
|
|
105
|
-
}
|
|
106
|
-
}
|
|
107
|
-
|
|
108
|
-
span.setAttribute('provider.result.count', results.length);
|
|
109
|
-
span.setAttribute('provider.cache.hits', cacheHits);
|
|
110
|
-
span.setAttribute('provider.cache.misses', cacheMisses);
|
|
111
|
-
span.setAttribute('provider.cache.hit_ratio', cacheHits / (cacheHits + cacheMisses));
|
|
112
|
-
|
|
113
|
-
return results;
|
|
114
|
-
},
|
|
115
|
-
{ queryCount: queries.length }
|
|
116
|
-
);
|
|
117
|
-
}
|
|
118
|
-
|
|
119
|
-
/**
|
|
120
|
-
* Executes the listed mutations in order and returns the final state
|
|
121
|
-
* resulting from that set of operations.
|
|
122
|
-
*/
|
|
123
|
-
public async mutate(mutations: M[], session: Session): Promise<T> {
|
|
124
|
-
return this.instrumentation.traceMutation(
|
|
125
|
-
'mutate',
|
|
126
|
-
async (span) => {
|
|
127
|
-
span.setAttribute('provider.mutation.count', mutations.length);
|
|
128
|
-
|
|
129
|
-
// Perform the mutation
|
|
130
|
-
const result = await this.process(mutations, session);
|
|
131
|
-
this.assert(result);
|
|
132
|
-
|
|
133
|
-
return result;
|
|
134
|
-
},
|
|
135
|
-
{ mutationCount: mutations.length }
|
|
136
|
-
);
|
|
137
|
-
}
|
|
138
|
-
|
|
139
|
-
/**
|
|
140
|
-
* The internal extension point for providers implementating query
|
|
141
|
-
* capabilities.
|
|
142
|
-
*/
|
|
143
|
-
protected abstract fetch(queries: Q[], session: Session): Promise<T[]>;
|
|
144
|
-
|
|
145
|
-
/**
|
|
146
|
-
* The internal extension point for providers implementing mutation
|
|
147
|
-
* capabilities.
|
|
148
|
-
*/
|
|
149
|
-
protected abstract process(
|
|
150
|
-
mutations: M[],
|
|
151
|
-
session: Session
|
|
152
|
-
): Promise<T>;
|
|
153
|
-
|
|
154
|
-
/**
|
|
155
|
-
* Provider-specific cache evaluation logic.
|
|
156
|
-
* Returns information about how this query should be cached.
|
|
157
|
-
* Override this method to enable caching with custom keys and TTL.
|
|
158
|
-
* Default implementation returns no caching.
|
|
159
|
-
*/
|
|
160
|
-
protected getCacheEvaluation(query: Q, session: Session): CacheEvaluation {
|
|
161
|
-
// Default implementation: generate a cache key but don't cache
|
|
162
|
-
const providerName = this.constructor.name.toLowerCase();
|
|
163
|
-
const userId = session.identity?.id || 'anonymous';
|
|
164
|
-
const queryHash = crypto.createHash('md5').update(JSON.stringify(query)).digest('hex').substring(0, 12);
|
|
165
|
-
const key = `${providerName}:${userId}:${queryHash}`;
|
|
166
|
-
|
|
167
|
-
return {
|
|
168
|
-
key,
|
|
169
|
-
cacheDurationInSeconds: 0,
|
|
170
|
-
canCache: false
|
|
171
|
-
};
|
|
43
|
+
return this.assert(model);
|
|
172
44
|
}
|
|
173
45
|
}
|
|
@@ -1,27 +1,14 @@
|
|
|
1
|
-
import { CartQuery } from "../schemas/queries/cart.query";
|
|
2
|
-
import { CartMutation } from "../schemas/mutations/cart.mutation";
|
|
3
1
|
import { BaseProvider } from "./base.provider";
|
|
4
2
|
import { Cart } from "../schemas/models/cart.model";
|
|
5
|
-
import {
|
|
6
|
-
import { Session } from
|
|
7
|
-
import
|
|
3
|
+
import { CartQueryById } from "../schemas/queries/cart.query";
|
|
4
|
+
import { Session } from "../schemas/session.schema";
|
|
5
|
+
import { CartMutationItemAdd, CartMutationItemQuantityChange, CartMutationItemRemove } from "../schemas/mutations/cart.mutation";
|
|
8
6
|
|
|
9
7
|
export abstract class CartProvider<
|
|
10
|
-
T extends Cart = Cart
|
|
11
|
-
|
|
12
|
-
|
|
13
|
-
|
|
14
|
-
|
|
15
|
-
|
|
16
|
-
const providerName = this.constructor.name.toLowerCase();
|
|
17
|
-
const userId = session.identity?.id || 'anonymous';
|
|
18
|
-
const queryHash = crypto.createHash('md5').update(JSON.stringify(query)).digest('hex').substring(0, 12);
|
|
19
|
-
const key = `${providerName}:cart:${userId}:${queryHash}`;
|
|
20
|
-
|
|
21
|
-
return {
|
|
22
|
-
key,
|
|
23
|
-
cacheDurationInSeconds: 0,
|
|
24
|
-
canCache: false
|
|
25
|
-
};
|
|
26
|
-
}
|
|
8
|
+
T extends Cart = Cart
|
|
9
|
+
> extends BaseProvider<T> {
|
|
10
|
+
public abstract getById(payload: CartQueryById, session: Session): Promise<T>;
|
|
11
|
+
public abstract add(payload: CartMutationItemAdd, session: Session): Promise<T>;
|
|
12
|
+
public abstract remove(payload: CartMutationItemRemove, session: Session): Promise<T>;
|
|
13
|
+
public abstract changeQuantity(payload: CartMutationItemQuantityChange, session: Session): Promise<T>;
|
|
27
14
|
}
|
|
@@ -1,27 +1,13 @@
|
|
|
1
1
|
import { Identity } from "../schemas/models/identity.model";
|
|
2
|
-
import {
|
|
3
|
-
import {
|
|
2
|
+
import { IdentityMutationLogin, IdentityMutationLogout } from "../schemas/mutations/identity.mutation";
|
|
3
|
+
import { IdentityQuerySelf } from "../schemas/queries/identity.query";
|
|
4
|
+
import { Session } from "../schemas/session.schema";
|
|
4
5
|
import { BaseProvider } from "./base.provider";
|
|
5
|
-
import { CacheEvaluation } from '../cache/cache-evaluation.interface';
|
|
6
|
-
import { Session } from '../schemas/session.schema';
|
|
7
6
|
|
|
8
7
|
export abstract class IdentityProvider<
|
|
9
8
|
T extends Identity = Identity,
|
|
10
|
-
|
|
11
|
-
|
|
12
|
-
|
|
13
|
-
|
|
14
|
-
protected override getCacheEvaluation(_query: Q, session: Session): CacheEvaluation {
|
|
15
|
-
const providerName = this.constructor.name.toLowerCase();
|
|
16
|
-
const userId = session.identity?.id;
|
|
17
|
-
const key = userId
|
|
18
|
-
? `${providerName}:identity:${userId}`
|
|
19
|
-
: `${providerName}:identity:anonymous`;
|
|
20
|
-
|
|
21
|
-
return {
|
|
22
|
-
key,
|
|
23
|
-
cacheDurationInSeconds: 0,
|
|
24
|
-
canCache: false
|
|
25
|
-
};
|
|
26
|
-
}
|
|
9
|
+
> extends BaseProvider<T> {
|
|
10
|
+
public abstract getSelf(payload: IdentityQuerySelf, session: Session): Promise<T>;
|
|
11
|
+
public abstract login(payload: IdentityMutationLogin, session: Session): Promise<T>;
|
|
12
|
+
public abstract logout(payload: IdentityMutationLogout, session: Session): Promise<T>;
|
|
27
13
|
}
|
|
@@ -1,24 +1,10 @@
|
|
|
1
1
|
import { Inventory } from '../schemas/models/inventory.model';
|
|
2
2
|
import { InventoryQuery } from '../schemas/queries/inventory.query';
|
|
3
|
-
import { InventoryMutation } from '../schemas/mutations/inventory.mutation';
|
|
4
|
-
import { BaseProvider } from './base.provider';
|
|
5
|
-
import { CacheEvaluation } from '../cache/cache-evaluation.interface';
|
|
6
3
|
import { Session } from '../schemas/session.schema';
|
|
4
|
+
import { BaseProvider } from './base.provider';
|
|
7
5
|
|
|
8
6
|
export abstract class InventoryProvider<
|
|
9
|
-
T extends Inventory = Inventory
|
|
10
|
-
|
|
11
|
-
|
|
12
|
-
> extends BaseProvider<T, Q, M> {
|
|
13
|
-
|
|
14
|
-
protected override getCacheEvaluation(query: Q, _session: Session): CacheEvaluation {
|
|
15
|
-
const providerName = this.constructor.name.toLowerCase();
|
|
16
|
-
const key = `${providerName}:inventory:${query.sku}`;
|
|
17
|
-
|
|
18
|
-
return {
|
|
19
|
-
key,
|
|
20
|
-
cacheDurationInSeconds: 0,
|
|
21
|
-
canCache: false
|
|
22
|
-
};
|
|
23
|
-
}
|
|
7
|
+
T extends Inventory = Inventory
|
|
8
|
+
> extends BaseProvider<T> {
|
|
9
|
+
public abstract getBySKU(payload: InventoryQuery, session: Session): Promise<T>;
|
|
24
10
|
}
|
|
@@ -1,27 +1,10 @@
|
|
|
1
1
|
import { Price } from '../schemas/models/price.model';
|
|
2
|
-
import {
|
|
3
|
-
import { PriceQuery } from '../schemas/queries/price.query';
|
|
4
|
-
import { BaseProvider } from './base.provider';
|
|
5
|
-
import { CacheEvaluation } from '../cache/cache-evaluation.interface';
|
|
2
|
+
import { PriceQueryBySku } from '../schemas/queries/price.query';
|
|
6
3
|
import { Session } from '../schemas/session.schema';
|
|
7
|
-
import
|
|
4
|
+
import { BaseProvider } from './base.provider';
|
|
8
5
|
|
|
9
6
|
export abstract class PriceProvider<
|
|
10
|
-
T extends Price = Price
|
|
11
|
-
|
|
12
|
-
|
|
13
|
-
> extends BaseProvider<T, Q, M> {
|
|
14
|
-
|
|
15
|
-
protected override getCacheEvaluation(query: Q, _session: Session): CacheEvaluation {
|
|
16
|
-
const providerName = this.constructor.name.toLowerCase();
|
|
17
|
-
// Hash complex SKU objects (price queries with currency, customer group, etc.)
|
|
18
|
-
const skuHash = crypto.createHash('md5').update(JSON.stringify(query.sku)).digest('hex').substring(0, 12);
|
|
19
|
-
const key = `${providerName}:price:${skuHash}`;
|
|
20
|
-
|
|
21
|
-
return {
|
|
22
|
-
key,
|
|
23
|
-
cacheDurationInSeconds: 0,
|
|
24
|
-
canCache: false
|
|
25
|
-
};
|
|
26
|
-
}
|
|
7
|
+
T extends Price = Price
|
|
8
|
+
> extends BaseProvider<T> {
|
|
9
|
+
public abstract getBySKU(payload: PriceQueryBySku, session: Session): Promise<T>;
|
|
27
10
|
}
|
|
@@ -1,34 +1,11 @@
|
|
|
1
1
|
import { Product } from '../schemas/models/product.model';
|
|
2
|
-
import { ProductMutation } from '../schemas/mutations/product.mutation';
|
|
3
|
-
import { ProductQuery, ProductQueryById, ProductQueryBySlug } from '../schemas/queries/product.query';
|
|
4
2
|
import { BaseProvider } from './base.provider';
|
|
5
3
|
import { Session } from '../schemas/session.schema';
|
|
6
|
-
import {
|
|
7
|
-
import * as crypto from 'crypto';
|
|
4
|
+
import { ProductQueryById, ProductQueryBySlug } from '../schemas/queries/product.query';
|
|
8
5
|
|
|
9
6
|
export abstract class ProductProvider<
|
|
10
|
-
T extends Product = Product
|
|
11
|
-
|
|
12
|
-
|
|
13
|
-
|
|
14
|
-
|
|
15
|
-
protected override getCacheEvaluation(query: Q, _session: Session): CacheEvaluation {
|
|
16
|
-
const providerName = this.constructor.name.toLowerCase();
|
|
17
|
-
|
|
18
|
-
let key: string;
|
|
19
|
-
if (query.query === 'slug') {
|
|
20
|
-
key = `${providerName}:product:slug:${(query as ProductQueryBySlug).slug}`;
|
|
21
|
-
} else if (query.query === 'id') {
|
|
22
|
-
key = `${providerName}:product:id:${(query as ProductQueryById).id}`;
|
|
23
|
-
} else {
|
|
24
|
-
const queryHash = crypto.createHash('md5').update(JSON.stringify(query)).digest('hex').substring(0, 12);
|
|
25
|
-
key = `${providerName}:product:${queryHash}`;
|
|
26
|
-
}
|
|
27
|
-
|
|
28
|
-
return {
|
|
29
|
-
key,
|
|
30
|
-
cacheDurationInSeconds: 300, // Products are moderately stable - 5 minutes
|
|
31
|
-
canCache: true
|
|
32
|
-
};
|
|
33
|
-
}
|
|
7
|
+
T extends Product = Product
|
|
8
|
+
> extends BaseProvider<T> {
|
|
9
|
+
public abstract getById(payload: ProductQueryById, session: Session): Promise<T>;
|
|
10
|
+
public abstract getBySlug(payload: ProductQueryBySlug, session: Session): Promise<T>;
|
|
34
11
|
}
|
|
@@ -1,29 +1,12 @@
|
|
|
1
1
|
import { SearchResult } from '../schemas/models/search.model';
|
|
2
|
-
import {
|
|
3
|
-
import { SearchMutation } from '../schemas/mutations/search.mutation';
|
|
4
|
-
import { BaseProvider } from './base.provider';
|
|
5
|
-
import { CacheEvaluation } from '../cache/cache-evaluation.interface';
|
|
2
|
+
import { SearchQueryByTerm } from '../schemas/queries/search.query';
|
|
6
3
|
import { Session } from '../schemas/session.schema';
|
|
7
|
-
import
|
|
4
|
+
import { BaseProvider } from './base.provider';
|
|
8
5
|
|
|
9
6
|
export abstract class SearchProvider<
|
|
10
|
-
T extends SearchResult = SearchResult
|
|
11
|
-
|
|
12
|
-
|
|
13
|
-
> extends BaseProvider<T, Q, M> {
|
|
14
|
-
|
|
15
|
-
protected override getCacheEvaluation(query: Q, _session: Session): CacheEvaluation {
|
|
16
|
-
const providerName = this.constructor.name.toLowerCase();
|
|
17
|
-
// Hash search parameters (term, filters, pagination, etc.)
|
|
18
|
-
const searchHash = crypto.createHash('md5').update(JSON.stringify(query.search)).digest('hex').substring(0, 12);
|
|
19
|
-
const key = `${providerName}:search:${searchHash}`;
|
|
20
|
-
|
|
21
|
-
return {
|
|
22
|
-
key,
|
|
23
|
-
cacheDurationInSeconds: 0,
|
|
24
|
-
canCache: false
|
|
25
|
-
};
|
|
26
|
-
}
|
|
7
|
+
T extends SearchResult = SearchResult
|
|
8
|
+
> extends BaseProvider<T> {
|
|
9
|
+
public abstract queryByTerm(payload: SearchQueryByTerm, session: Session): Promise<SearchResult>;
|
|
27
10
|
}
|
|
28
11
|
|
|
29
12
|
|