@reactionary/source 0.0.30 → 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/CLAUDE.md +11 -0
- package/core/package.json +1 -1
- package/core/src/cache/cache-evaluation.interface.ts +19 -0
- package/core/src/cache/cache.interface.ts +38 -0
- package/core/src/cache/noop-cache.ts +42 -0
- package/core/src/cache/redis-cache.ts +55 -22
- package/core/src/client/client-builder.ts +63 -0
- package/core/src/client/client.ts +27 -3
- package/core/src/decorators/trpc.decorators.ts +144 -0
- package/core/src/index.ts +6 -1
- package/core/src/providers/analytics.provider.ts +3 -6
- package/core/src/providers/base.provider.ts +13 -63
- package/core/src/providers/cart.provider.ts +10 -6
- package/core/src/providers/identity.provider.ts +8 -5
- package/core/src/providers/inventory.provider.ts +5 -6
- package/core/src/providers/price.provider.ts +6 -6
- package/core/src/providers/product.provider.ts +6 -6
- package/core/src/providers/search.provider.ts +6 -6
- 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/mutations/product.mutation.ts +0 -1
- 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 +11 -9
- 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 +28 -24
- package/providers/commercetools/src/providers/cart.provider.ts +58 -89
- package/providers/commercetools/src/providers/identity.provider.ts +34 -50
- package/providers/commercetools/src/providers/inventory.provider.ts +16 -38
- 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 +17 -14
- 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 -67
- 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/providers/posthog/src/core/initialize.ts +6 -4
- 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/core/src/cache/caching-strategy.ts +0 -25
- 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 -52
- package/examples/trpc-node/tsconfig.app.json +0 -9
- package/examples/trpc-node/tsconfig.json +0 -13
|
@@ -0,0 +1,246 @@
|
|
|
1
|
+
import { SpanKind, SpanStatusCode } from '@opentelemetry/api';
|
|
2
|
+
import { getTracer } from './tracer';
|
|
3
|
+
|
|
4
|
+
/**
|
|
5
|
+
* Options for the @traced decorator
|
|
6
|
+
*/
|
|
7
|
+
export interface TracedOptions {
|
|
8
|
+
/** Whether to capture function arguments as span attributes (default: true) */
|
|
9
|
+
captureArgs?: boolean;
|
|
10
|
+
/** Whether to capture the return value as a span attribute (default: true) */
|
|
11
|
+
captureResult?: boolean;
|
|
12
|
+
/** Custom span name to use instead of the function name */
|
|
13
|
+
spanName?: string;
|
|
14
|
+
/** OpenTelemetry SpanKind (default: INTERNAL) */
|
|
15
|
+
spanKind?: SpanKind;
|
|
16
|
+
}
|
|
17
|
+
|
|
18
|
+
/**
|
|
19
|
+
* Safely serializes a value for use as a span attribute
|
|
20
|
+
* Handles circular references and large objects
|
|
21
|
+
*/
|
|
22
|
+
function safeSerialize(value: unknown, maxDepth = 3, currentDepth = 0): string {
|
|
23
|
+
if (currentDepth >= maxDepth) {
|
|
24
|
+
return '[Max depth reached]';
|
|
25
|
+
}
|
|
26
|
+
|
|
27
|
+
if (value === null) return 'null';
|
|
28
|
+
if (value === undefined) return 'undefined';
|
|
29
|
+
|
|
30
|
+
const type = typeof value;
|
|
31
|
+
|
|
32
|
+
if (type === 'string' || type === 'number' || type === 'boolean') {
|
|
33
|
+
return String(value);
|
|
34
|
+
}
|
|
35
|
+
|
|
36
|
+
if (type === 'function') {
|
|
37
|
+
return `[Function: ${(value as { name?: string }).name || 'anonymous'}]`;
|
|
38
|
+
}
|
|
39
|
+
|
|
40
|
+
if (value instanceof Date) {
|
|
41
|
+
return value.toISOString();
|
|
42
|
+
}
|
|
43
|
+
|
|
44
|
+
if (value instanceof Error) {
|
|
45
|
+
return `[Error: ${value.message}]`;
|
|
46
|
+
}
|
|
47
|
+
|
|
48
|
+
if (Array.isArray(value)) {
|
|
49
|
+
if (value.length > 10) {
|
|
50
|
+
return `[Array(${value.length})]`;
|
|
51
|
+
}
|
|
52
|
+
try {
|
|
53
|
+
return JSON.stringify(value.map(item =>
|
|
54
|
+
safeSerialize(item, maxDepth, currentDepth + 1)
|
|
55
|
+
));
|
|
56
|
+
} catch {
|
|
57
|
+
return '[Array - circular reference]';
|
|
58
|
+
}
|
|
59
|
+
}
|
|
60
|
+
|
|
61
|
+
if (type === 'object') {
|
|
62
|
+
try {
|
|
63
|
+
const keys = Object.keys(value as object);
|
|
64
|
+
if (keys.length > 20) {
|
|
65
|
+
return `[Object with ${keys.length} keys]`;
|
|
66
|
+
}
|
|
67
|
+
const simplified: Record<string, unknown> = {};
|
|
68
|
+
for (const key of keys.slice(0, 10)) {
|
|
69
|
+
simplified[key] = safeSerialize(
|
|
70
|
+
(value as Record<string, unknown>)[key],
|
|
71
|
+
maxDepth,
|
|
72
|
+
currentDepth + 1
|
|
73
|
+
);
|
|
74
|
+
}
|
|
75
|
+
return JSON.stringify(simplified);
|
|
76
|
+
} catch {
|
|
77
|
+
return '[Object - circular reference]';
|
|
78
|
+
}
|
|
79
|
+
}
|
|
80
|
+
|
|
81
|
+
return String(value);
|
|
82
|
+
}
|
|
83
|
+
|
|
84
|
+
/**
|
|
85
|
+
* TypeScript decorator for tracing function execution
|
|
86
|
+
* Automatically creates OpenTelemetry spans for decorated methods
|
|
87
|
+
* Supports both Stage 2 (legacy) and Stage 3 decorator syntax
|
|
88
|
+
*
|
|
89
|
+
* @example
|
|
90
|
+
* ```typescript
|
|
91
|
+
* class MyService {
|
|
92
|
+
* @traced()
|
|
93
|
+
* async fetchData(id: string): Promise<Data> {
|
|
94
|
+
* // method implementation
|
|
95
|
+
* }
|
|
96
|
+
*
|
|
97
|
+
* @traced({ spanName: 'custom-operation', captureResult: false })
|
|
98
|
+
* processData(data: Data): void {
|
|
99
|
+
* // method implementation
|
|
100
|
+
* }
|
|
101
|
+
* }
|
|
102
|
+
* ```
|
|
103
|
+
*/
|
|
104
|
+
export function traced(options: TracedOptions = {}): any {
|
|
105
|
+
const {
|
|
106
|
+
captureArgs = true,
|
|
107
|
+
captureResult = true,
|
|
108
|
+
spanName,
|
|
109
|
+
spanKind = SpanKind.INTERNAL
|
|
110
|
+
} = options;
|
|
111
|
+
|
|
112
|
+
// Stage 2 (legacy) decorator
|
|
113
|
+
return function (
|
|
114
|
+
target: any,
|
|
115
|
+
propertyKey?: string | symbol,
|
|
116
|
+
descriptor?: PropertyDescriptor
|
|
117
|
+
): any {
|
|
118
|
+
// Handle Stage 3 decorator (when called with context)
|
|
119
|
+
if (typeof propertyKey === 'object' && propertyKey && 'kind' in propertyKey) {
|
|
120
|
+
const context = propertyKey as any;
|
|
121
|
+
const originalMethod = target;
|
|
122
|
+
const methodName = String(context.name);
|
|
123
|
+
|
|
124
|
+
return createTracedMethod(originalMethod, methodName, {
|
|
125
|
+
captureArgs,
|
|
126
|
+
captureResult,
|
|
127
|
+
spanName,
|
|
128
|
+
spanKind
|
|
129
|
+
});
|
|
130
|
+
}
|
|
131
|
+
|
|
132
|
+
// Handle Stage 2 decorator
|
|
133
|
+
if (descriptor && typeof descriptor.value === 'function') {
|
|
134
|
+
const originalMethod = descriptor.value;
|
|
135
|
+
const methodName = String(propertyKey);
|
|
136
|
+
|
|
137
|
+
descriptor.value = createTracedMethod(originalMethod, methodName, {
|
|
138
|
+
captureArgs,
|
|
139
|
+
captureResult,
|
|
140
|
+
spanName,
|
|
141
|
+
spanKind
|
|
142
|
+
});
|
|
143
|
+
|
|
144
|
+
return descriptor;
|
|
145
|
+
}
|
|
146
|
+
|
|
147
|
+
return target;
|
|
148
|
+
};
|
|
149
|
+
}
|
|
150
|
+
|
|
151
|
+
function createTracedMethod(
|
|
152
|
+
originalMethod: (...args: any[]) => any,
|
|
153
|
+
methodName: string,
|
|
154
|
+
options: {
|
|
155
|
+
captureArgs: boolean;
|
|
156
|
+
captureResult: boolean;
|
|
157
|
+
spanName?: string;
|
|
158
|
+
spanKind: SpanKind;
|
|
159
|
+
}
|
|
160
|
+
): any {
|
|
161
|
+
const { captureArgs, captureResult, spanName, spanKind } = options;
|
|
162
|
+
|
|
163
|
+
function tracedMethod(this: any, ...args: any[]): any {
|
|
164
|
+
const tracer = getTracer();
|
|
165
|
+
const className = this?.constructor?.name || 'Unknown';
|
|
166
|
+
const effectiveSpanName = spanName || `${className}.${methodName}`;
|
|
167
|
+
|
|
168
|
+
// Start the span
|
|
169
|
+
const span = tracer.startSpan(effectiveSpanName, {
|
|
170
|
+
kind: spanKind,
|
|
171
|
+
attributes: {
|
|
172
|
+
'function.name': methodName,
|
|
173
|
+
'function.class': className,
|
|
174
|
+
}
|
|
175
|
+
});
|
|
176
|
+
|
|
177
|
+
// Capture arguments if enabled
|
|
178
|
+
if (captureArgs && args.length > 0) {
|
|
179
|
+
args.forEach((arg, index) => {
|
|
180
|
+
try {
|
|
181
|
+
span.setAttribute(`function.args.${index}`, safeSerialize(arg));
|
|
182
|
+
} catch {
|
|
183
|
+
span.setAttribute(`function.args.${index}`, '[Serialization error]');
|
|
184
|
+
}
|
|
185
|
+
});
|
|
186
|
+
span.setAttribute('function.args.count', args.length);
|
|
187
|
+
}
|
|
188
|
+
|
|
189
|
+
// Helper function to finalize span with result
|
|
190
|
+
const finalizeSpan = (result: unknown, isError = false) => {
|
|
191
|
+
if (!isError && captureResult && result !== undefined) {
|
|
192
|
+
try {
|
|
193
|
+
span.setAttribute('function.result', safeSerialize(result));
|
|
194
|
+
} catch {
|
|
195
|
+
span.setAttribute('function.result', '[Serialization error]');
|
|
196
|
+
}
|
|
197
|
+
}
|
|
198
|
+
|
|
199
|
+
if (isError) {
|
|
200
|
+
span.setStatus({
|
|
201
|
+
code: SpanStatusCode.ERROR,
|
|
202
|
+
message: result instanceof Error ? result.message : String(result)
|
|
203
|
+
});
|
|
204
|
+
if (result instanceof Error) {
|
|
205
|
+
span.recordException(result);
|
|
206
|
+
}
|
|
207
|
+
} else {
|
|
208
|
+
span.setStatus({ code: SpanStatusCode.OK });
|
|
209
|
+
}
|
|
210
|
+
|
|
211
|
+
span.end();
|
|
212
|
+
};
|
|
213
|
+
|
|
214
|
+
try {
|
|
215
|
+
const result = originalMethod.apply(this, args);
|
|
216
|
+
|
|
217
|
+
// Handle async functions
|
|
218
|
+
if (result instanceof Promise) {
|
|
219
|
+
return result
|
|
220
|
+
.then((value) => {
|
|
221
|
+
finalizeSpan(value);
|
|
222
|
+
return value;
|
|
223
|
+
})
|
|
224
|
+
.catch((error) => {
|
|
225
|
+
finalizeSpan(error, true);
|
|
226
|
+
throw error;
|
|
227
|
+
});
|
|
228
|
+
}
|
|
229
|
+
|
|
230
|
+
// Handle sync functions
|
|
231
|
+
finalizeSpan(result);
|
|
232
|
+
return result;
|
|
233
|
+
} catch (error) {
|
|
234
|
+
finalizeSpan(error, true);
|
|
235
|
+
throw error;
|
|
236
|
+
}
|
|
237
|
+
}
|
|
238
|
+
|
|
239
|
+
// Preserve the original function's name and properties
|
|
240
|
+
Object.defineProperty(tracedMethod, 'name', {
|
|
241
|
+
value: methodName,
|
|
242
|
+
configurable: true
|
|
243
|
+
});
|
|
244
|
+
|
|
245
|
+
return tracedMethod;
|
|
246
|
+
}
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@reactionary/source",
|
|
3
|
-
"version": "0.0.
|
|
3
|
+
"version": "0.0.32",
|
|
4
4
|
"license": "MIT",
|
|
5
5
|
"private": false,
|
|
6
6
|
"dependencies": {
|
|
@@ -40,6 +40,7 @@
|
|
|
40
40
|
"posthog-node": "^4.18.0",
|
|
41
41
|
"react": "19.0.0",
|
|
42
42
|
"react-dom": "19.0.0",
|
|
43
|
+
"reflect-metadata": "^0.2.2",
|
|
43
44
|
"rxjs": "~7.8.0",
|
|
44
45
|
"search-insights": "^2.17.3",
|
|
45
46
|
"superjson": "^2.2.2",
|
|
@@ -1,4 +1,4 @@
|
|
|
1
|
-
import { Client,
|
|
1
|
+
import { Client, ProductSchema, Cache } from "@reactionary/core";
|
|
2
2
|
import { AlgoliaProductProvider } from "../providers/product.provider";
|
|
3
3
|
import { AlgoliaSearchProvider } from "../providers/search.provider";
|
|
4
4
|
import { AlgoliaCapabilities } from "../schema/capabilities.schema";
|
|
@@ -6,15 +6,17 @@ import { AlgoliaConfiguration } from "../schema/configuration.schema";
|
|
|
6
6
|
import { AlgoliaSearchResultSchema } from "../schema/search.schema";
|
|
7
7
|
|
|
8
8
|
export function withAlgoliaCapabilities(configuration: AlgoliaConfiguration, capabilities: AlgoliaCapabilities) {
|
|
9
|
-
|
|
9
|
+
return (cache: Cache) => {
|
|
10
|
+
const client: Partial<Client> = {};
|
|
10
11
|
|
|
11
|
-
|
|
12
|
-
|
|
13
|
-
|
|
12
|
+
if (capabilities.product) {
|
|
13
|
+
client.product = new AlgoliaProductProvider(configuration, ProductSchema, cache);
|
|
14
|
+
}
|
|
14
15
|
|
|
15
|
-
|
|
16
|
-
|
|
17
|
-
|
|
16
|
+
if (capabilities.search) {
|
|
17
|
+
client.search = new AlgoliaSearchProvider(configuration, AlgoliaSearchResultSchema, cache);
|
|
18
|
+
}
|
|
18
19
|
|
|
19
|
-
|
|
20
|
+
return client;
|
|
21
|
+
};
|
|
20
22
|
}
|
|
@@ -1,25 +1,58 @@
|
|
|
1
|
-
import {
|
|
1
|
+
import {
|
|
2
|
+
Product,
|
|
3
|
+
ProductProvider,
|
|
4
|
+
ProductQueryById,
|
|
5
|
+
ProductQueryBySlug,
|
|
6
|
+
Session,
|
|
7
|
+
Cache
|
|
8
|
+
} from '@reactionary/core';
|
|
2
9
|
import { z } from 'zod';
|
|
3
10
|
import { AlgoliaConfiguration } from '../schema/configuration.schema';
|
|
4
11
|
|
|
5
12
|
export class AlgoliaProductProvider<
|
|
6
|
-
T extends Product = Product
|
|
7
|
-
|
|
8
|
-
M extends ProductMutation = ProductMutation
|
|
9
|
-
> extends ProductProvider<T, Q, M> {
|
|
13
|
+
T extends Product = Product
|
|
14
|
+
> extends ProductProvider<T> {
|
|
10
15
|
protected config: AlgoliaConfiguration;
|
|
11
16
|
|
|
12
|
-
constructor(config: AlgoliaConfiguration, schema: z.ZodType<T>,
|
|
13
|
-
super(schema,
|
|
17
|
+
constructor(config: AlgoliaConfiguration, schema: z.ZodType<T>, cache: Cache) {
|
|
18
|
+
super(schema, cache);
|
|
14
19
|
|
|
15
20
|
this.config = config;
|
|
16
21
|
}
|
|
17
22
|
|
|
18
|
-
|
|
19
|
-
|
|
23
|
+
public override async getById(
|
|
24
|
+
payload: ProductQueryById,
|
|
25
|
+
_session: Session
|
|
26
|
+
): Promise<T> {
|
|
27
|
+
// TODO: Implement Algolia product fetch by ID
|
|
28
|
+
const result = this.newModel();
|
|
29
|
+
result.identifier = { key: payload.id };
|
|
30
|
+
result.name = `Algolia Product ${payload.id}`;
|
|
31
|
+
result.slug = payload.id;
|
|
32
|
+
result.description = 'Product from Algolia';
|
|
33
|
+
result.meta = {
|
|
34
|
+
cache: { hit: false, key: payload.id },
|
|
35
|
+
placeholder: true
|
|
36
|
+
};
|
|
37
|
+
|
|
38
|
+
return this.assert(result);
|
|
20
39
|
}
|
|
21
40
|
|
|
22
|
-
|
|
23
|
-
|
|
41
|
+
public override async getBySlug(
|
|
42
|
+
payload: ProductQueryBySlug,
|
|
43
|
+
_session: Session
|
|
44
|
+
): Promise<T> {
|
|
45
|
+
// TODO: Implement Algolia product fetch by slug
|
|
46
|
+
const result = this.newModel();
|
|
47
|
+
result.identifier = { key: payload.slug };
|
|
48
|
+
result.name = `Algolia Product ${payload.slug}`;
|
|
49
|
+
result.slug = payload.slug;
|
|
50
|
+
result.description = 'Product from Algolia';
|
|
51
|
+
result.meta = {
|
|
52
|
+
cache: { hit: false, key: payload.slug },
|
|
53
|
+
placeholder: true
|
|
54
|
+
};
|
|
55
|
+
|
|
56
|
+
return this.assert(result);
|
|
24
57
|
}
|
|
25
58
|
}
|
|
@@ -1,125 +1,106 @@
|
|
|
1
1
|
import {
|
|
2
|
-
SearchIdentifier,
|
|
3
|
-
SearchMutation,
|
|
4
2
|
SearchProvider,
|
|
5
|
-
|
|
3
|
+
SearchQueryByTerm,
|
|
6
4
|
SearchResult,
|
|
7
|
-
|
|
8
|
-
|
|
5
|
+
SearchResultFacet,
|
|
6
|
+
SearchResultProduct,
|
|
9
7
|
Session,
|
|
8
|
+
Cache,
|
|
10
9
|
} from '@reactionary/core';
|
|
11
10
|
import { algoliasearch } from 'algoliasearch';
|
|
12
11
|
import { z } from 'zod';
|
|
13
12
|
import { AlgoliaConfiguration } from '../schema/configuration.schema';
|
|
14
13
|
|
|
15
14
|
export class AlgoliaSearchProvider<
|
|
16
|
-
T extends SearchResult = SearchResult
|
|
17
|
-
|
|
18
|
-
M extends SearchMutation = SearchMutation
|
|
19
|
-
> extends SearchProvider<T, Q, M> {
|
|
15
|
+
T extends SearchResult = SearchResult
|
|
16
|
+
> extends SearchProvider<T> {
|
|
20
17
|
protected config: AlgoliaConfiguration;
|
|
21
18
|
|
|
22
|
-
constructor(config: AlgoliaConfiguration, schema: z.ZodType<T>,
|
|
23
|
-
super(schema,
|
|
19
|
+
constructor(config: AlgoliaConfiguration, schema: z.ZodType<T>, cache: Cache) {
|
|
20
|
+
super(schema, cache);
|
|
24
21
|
|
|
25
22
|
this.config = config;
|
|
26
23
|
}
|
|
27
24
|
|
|
28
|
-
|
|
29
|
-
|
|
30
|
-
|
|
31
|
-
|
|
32
|
-
const result = await this.get(query.search);
|
|
33
|
-
|
|
34
|
-
results.push(result);
|
|
35
|
-
}
|
|
36
|
-
|
|
37
|
-
return results;
|
|
38
|
-
}
|
|
39
|
-
|
|
40
|
-
protected override process(mutations: M[], session: Session): Promise<T> {
|
|
41
|
-
throw new Error('Method not implemented.');
|
|
42
|
-
}
|
|
43
|
-
|
|
44
|
-
protected async get(identifier: SearchIdentifier): Promise<T> {
|
|
25
|
+
public override async queryByTerm(
|
|
26
|
+
payload: SearchQueryByTerm,
|
|
27
|
+
_session: Session
|
|
28
|
+
): Promise<SearchResult> {
|
|
45
29
|
const client = algoliasearch(this.config.appId, this.config.apiKey);
|
|
46
30
|
const remote = await client.search<unknown>({
|
|
47
31
|
requests: [
|
|
48
32
|
{
|
|
49
33
|
indexName: this.config.indexName,
|
|
50
|
-
query:
|
|
51
|
-
page:
|
|
52
|
-
hitsPerPage:
|
|
34
|
+
query: payload.search.term,
|
|
35
|
+
page: payload.search.page,
|
|
36
|
+
hitsPerPage: payload.search.pageSize,
|
|
53
37
|
facets: ['*'],
|
|
54
38
|
analytics: true,
|
|
55
39
|
clickAnalytics: true,
|
|
56
|
-
facetFilters:
|
|
40
|
+
facetFilters: payload.search.facets.map(
|
|
57
41
|
(x) => `${encodeURIComponent(x.facet.key)}:${x.key}`
|
|
58
42
|
),
|
|
59
43
|
},
|
|
60
44
|
],
|
|
61
45
|
});
|
|
62
46
|
|
|
63
|
-
|
|
64
|
-
|
|
65
|
-
return parsed;
|
|
47
|
+
return this.parseSearchResult(remote, payload);
|
|
66
48
|
}
|
|
67
49
|
|
|
68
|
-
protected
|
|
50
|
+
protected parseSearchResult(remote: unknown, payload: SearchQueryByTerm): T {
|
|
69
51
|
const result = this.newModel();
|
|
52
|
+
const remoteData = remote as { results: Array<{ facets: Record<string, Record<string, number>>; hits: Array<{ objectID: string; slug?: string; name?: string; image?: string }>; index: string; queryID: string; nbPages: number }> };
|
|
53
|
+
const remoteProducts = remoteData.results[0];
|
|
70
54
|
|
|
71
|
-
|
|
72
|
-
|
|
55
|
+
// Parse facets
|
|
73
56
|
for (const id in remoteProducts.facets) {
|
|
74
57
|
const f = remoteProducts.facets[id];
|
|
75
58
|
|
|
76
|
-
const facet =
|
|
77
|
-
|
|
78
|
-
|
|
59
|
+
const facet = {
|
|
60
|
+
identifier: { key: id },
|
|
61
|
+
name: id,
|
|
62
|
+
values: []
|
|
63
|
+
} as SearchResultFacet;
|
|
79
64
|
|
|
80
65
|
for (const vid in f) {
|
|
81
66
|
const fv = f[vid];
|
|
82
|
-
|
|
83
|
-
|
|
84
|
-
|
|
85
|
-
|
|
86
|
-
|
|
87
|
-
|
|
88
|
-
|
|
89
|
-
|
|
90
|
-
|
|
91
|
-
|
|
92
|
-
x.facet.key == facetValue.identifier.facet.key &&
|
|
93
|
-
x.key == facetValue.identifier.key
|
|
94
|
-
)
|
|
95
|
-
) {
|
|
96
|
-
facetValue.active = true;
|
|
97
|
-
}
|
|
98
|
-
|
|
99
|
-
facet.values.push(facetValue);
|
|
67
|
+
const isActive = payload.search.facets.find(
|
|
68
|
+
(x) => x.facet.key === id && x.key === vid
|
|
69
|
+
);
|
|
70
|
+
|
|
71
|
+
facet.values.push({
|
|
72
|
+
identifier: { key: vid, facet: { key: id } },
|
|
73
|
+
count: fv,
|
|
74
|
+
name: vid,
|
|
75
|
+
active: !!isActive
|
|
76
|
+
});
|
|
100
77
|
}
|
|
101
78
|
|
|
102
79
|
result.facets.push(facet);
|
|
103
80
|
}
|
|
104
81
|
|
|
82
|
+
// Parse products
|
|
105
83
|
for (const p of remoteProducts.hits) {
|
|
106
84
|
result.products.push({
|
|
107
|
-
identifier: {
|
|
108
|
-
key: p.objectID,
|
|
109
|
-
},
|
|
85
|
+
identifier: { key: p.objectID },
|
|
110
86
|
slug: p.slug,
|
|
111
87
|
name: p.name,
|
|
112
|
-
image: p.image
|
|
113
|
-
});
|
|
88
|
+
image: p.image
|
|
89
|
+
} as SearchResultProduct);
|
|
114
90
|
}
|
|
115
91
|
|
|
92
|
+
// Set result metadata
|
|
116
93
|
result.identifier = {
|
|
117
|
-
...
|
|
94
|
+
...payload.search,
|
|
118
95
|
index: remoteProducts.index,
|
|
119
96
|
key: remoteProducts.queryID
|
|
120
97
|
};
|
|
121
98
|
result.pages = remoteProducts.nbPages;
|
|
99
|
+
result.meta = {
|
|
100
|
+
cache: { hit: false, key: payload.search.term },
|
|
101
|
+
placeholder: false
|
|
102
|
+
};
|
|
122
103
|
|
|
123
|
-
return result;
|
|
104
|
+
return this.assert(result);
|
|
124
105
|
}
|
|
125
106
|
}
|
|
@@ -1,7 +1,6 @@
|
|
|
1
1
|
import { ClientBuilder } from '@commercetools/ts-client';
|
|
2
2
|
import { createApiBuilderFromCtpClient } from '@commercetools/platform-sdk';
|
|
3
3
|
import { CommercetoolsConfiguration } from '../schema/configuration.schema';
|
|
4
|
-
import { Session } from '@reactionary/core';
|
|
5
4
|
|
|
6
5
|
const ANONYMOUS_SCOPES = ['view_published_products', 'manage_shopping_lists', 'view_shipping_methods', 'manage_customers', 'view_product_selections', 'view_categories', 'view_project_settings', 'manage_order_edits', 'view_sessions', 'view_standalone_prices', 'manage_orders', 'view_tax_categories', 'view_cart_discounts', 'view_discount_codes', 'create_anonymous_token', 'manage_sessions', 'view_products', 'view_types'];
|
|
7
6
|
const GUEST_SCOPES = [...ANONYMOUS_SCOPES];
|
|
@@ -1,4 +1,4 @@
|
|
|
1
|
-
import {
|
|
1
|
+
import { CartSchema, Client, IdentitySchema, InventorySchema, PriceSchema, ProductSchema, SearchResultSchema, Cache } from "@reactionary/core";
|
|
2
2
|
import { CommercetoolsCapabilities } from "../schema/capabilities.schema";
|
|
3
3
|
import { CommercetoolsSearchProvider } from "../providers/search.provider";
|
|
4
4
|
import { CommercetoolsProductProvider } from '../providers/product.provider';
|
|
@@ -7,34 +7,38 @@ import { CommercetoolsIdentityProvider } from "../providers/identity.provider";
|
|
|
7
7
|
import { CommercetoolsCartProvider } from "../providers/cart.provider";
|
|
8
8
|
import { CommercetoolsInventoryProvider } from "../providers/inventory.provider";
|
|
9
9
|
import { CommercetoolsPriceProvider } from "../providers/price.provider";
|
|
10
|
-
import { InventoryMutationSchema } from "core/src/schemas/mutations/inventory.mutation";
|
|
11
10
|
|
|
12
|
-
export function withCommercetoolsCapabilities(
|
|
13
|
-
|
|
11
|
+
export function withCommercetoolsCapabilities(
|
|
12
|
+
configuration: CommercetoolsConfiguration,
|
|
13
|
+
capabilities: CommercetoolsCapabilities
|
|
14
|
+
) {
|
|
15
|
+
return (cache: Cache) => {
|
|
16
|
+
const client: Partial<Client> = {};
|
|
14
17
|
|
|
15
|
-
|
|
16
|
-
|
|
17
|
-
|
|
18
|
+
if (capabilities.product) {
|
|
19
|
+
client.product = new CommercetoolsProductProvider(configuration, ProductSchema, cache);
|
|
20
|
+
}
|
|
18
21
|
|
|
19
|
-
|
|
20
|
-
|
|
21
|
-
|
|
22
|
+
if (capabilities.search) {
|
|
23
|
+
client.search = new CommercetoolsSearchProvider(configuration, SearchResultSchema, cache);
|
|
24
|
+
}
|
|
22
25
|
|
|
23
|
-
|
|
24
|
-
|
|
25
|
-
|
|
26
|
+
if (capabilities.identity) {
|
|
27
|
+
client.identity = new CommercetoolsIdentityProvider(configuration, IdentitySchema, cache);
|
|
28
|
+
}
|
|
26
29
|
|
|
27
|
-
|
|
28
|
-
|
|
29
|
-
|
|
30
|
+
if (capabilities.cart) {
|
|
31
|
+
client.cart = new CommercetoolsCartProvider(configuration, CartSchema, cache);
|
|
32
|
+
}
|
|
30
33
|
|
|
31
|
-
|
|
32
|
-
|
|
33
|
-
|
|
34
|
+
if (capabilities.inventory) {
|
|
35
|
+
client.inventory = new CommercetoolsInventoryProvider(configuration, InventorySchema, cache);
|
|
36
|
+
}
|
|
34
37
|
|
|
35
|
-
|
|
36
|
-
|
|
37
|
-
|
|
38
|
+
if (capabilities.price) {
|
|
39
|
+
client.price = new CommercetoolsPriceProvider(configuration, PriceSchema, cache);
|
|
40
|
+
}
|
|
38
41
|
|
|
39
|
-
|
|
40
|
-
}
|
|
42
|
+
return client;
|
|
43
|
+
};
|
|
44
|
+
}
|