@reactionary/source 0.0.40 → 0.0.41
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/src/decorators/reactionary.decorator.ts +16 -0
- package/core/src/index.ts +2 -0
- package/core/src/schemas/mutations/cart.mutation.ts +6 -6
- package/examples/next/src/app/page.tsx +20 -15
- package/examples/node/package.json +3 -1
- package/examples/node/src/basic/basic-node-provider-model-extension.spec.ts +13 -5
- package/examples/node/src/basic/basic-node-provider-query-extension.spec.ts +11 -1
- package/examples/node/src/basic/basic-node-setup.spec.ts +7 -3
- package/examples/node/src/test-utils.ts +26 -0
- package/otel/src/trace-decorator.ts +6 -5
- package/package.json +4 -3
- package/providers/algolia/src/test/search.provider.spec.ts +28 -19
- package/providers/algolia/src/test/test-utils.ts +26 -0
- package/providers/commercetools/package.json +1 -1
- package/providers/commercetools/src/core/client.ts +26 -2
- package/providers/commercetools/src/core/initialize.ts +32 -6
- package/providers/commercetools/src/providers/cart.provider.ts +6 -4
- package/providers/commercetools/src/providers/category.provider.ts +2 -2
- package/providers/commercetools/src/providers/price.provider.ts +2 -1
- package/providers/commercetools/src/providers/search.provider.ts +33 -12
- package/providers/commercetools/src/test/category.provider.spec.ts +1 -14
- package/providers/fake/src/core/initialize.ts +6 -8
- package/trpc/src/integration.spec.ts +24 -23
- package/trpc/src/test-utils.ts +26 -0
- package/trpc/src/transparent-client.spec.ts +22 -26
- package/providers/algolia/src/test/product.provider.spec.ts +0 -18
|
@@ -0,0 +1,16 @@
|
|
|
1
|
+
export function Reactionary(options: unknown): MethodDecorator {
|
|
2
|
+
return function (
|
|
3
|
+
target: any,
|
|
4
|
+
propertyKey: string | symbol,
|
|
5
|
+
descriptor: PropertyDescriptor
|
|
6
|
+
): PropertyDescriptor {
|
|
7
|
+
const original = descriptor.value;
|
|
8
|
+
|
|
9
|
+
descriptor.value = function (...args: any[]) {
|
|
10
|
+
console.log('calling through reactionary decoration!');
|
|
11
|
+
return original.apply(this, args);
|
|
12
|
+
};
|
|
13
|
+
|
|
14
|
+
return descriptor;
|
|
15
|
+
};
|
|
16
|
+
}
|
package/core/src/index.ts
CHANGED
|
@@ -6,6 +6,8 @@ export * from './cache/noop-cache';
|
|
|
6
6
|
export * from './client/client';
|
|
7
7
|
export * from './client/client-builder';
|
|
8
8
|
|
|
9
|
+
export * from './decorators/reactionary.decorator';
|
|
10
|
+
|
|
9
11
|
export * from './providers/analytics.provider';
|
|
10
12
|
export * from './providers/base.provider';
|
|
11
13
|
export * from './providers/cart.provider';
|
|
@@ -3,19 +3,19 @@ import { BaseMutationSchema } from './base.mutation';
|
|
|
3
3
|
import { CartIdentifierSchema, CartItemIdentifierSchema, ProductIdentifierSchema } from '../models/identifiers.model';
|
|
4
4
|
|
|
5
5
|
export const CartMutationItemAddSchema = BaseMutationSchema.extend({
|
|
6
|
-
cart: CartIdentifierSchema.
|
|
7
|
-
product: ProductIdentifierSchema.
|
|
6
|
+
cart: CartIdentifierSchema.nonoptional(),
|
|
7
|
+
product: ProductIdentifierSchema.nonoptional(),
|
|
8
8
|
quantity: z.number()
|
|
9
9
|
});
|
|
10
10
|
|
|
11
11
|
export const CartMutationItemRemoveSchema = BaseMutationSchema.extend({
|
|
12
|
-
cart: CartIdentifierSchema.
|
|
13
|
-
item: CartItemIdentifierSchema.
|
|
12
|
+
cart: CartIdentifierSchema.nonoptional(),
|
|
13
|
+
item: CartItemIdentifierSchema.nonoptional()
|
|
14
14
|
});
|
|
15
15
|
|
|
16
16
|
export const CartMutationItemQuantityChangeSchema = BaseMutationSchema.extend({
|
|
17
|
-
cart: CartIdentifierSchema.
|
|
18
|
-
item: CartItemIdentifierSchema.
|
|
17
|
+
cart: CartIdentifierSchema.nonoptional(),
|
|
18
|
+
item: CartItemIdentifierSchema.nonoptional(),
|
|
19
19
|
quantity: z.number()
|
|
20
20
|
});
|
|
21
21
|
|
|
@@ -1,6 +1,7 @@
|
|
|
1
1
|
import styles from './page.module.scss';
|
|
2
2
|
import { ClientBuilder, NoOpCache, SessionSchema } from '@reactionary/core';
|
|
3
3
|
import { withFakeCapabilities } from '@reactionary/provider-fake';
|
|
4
|
+
import { withCommercetoolsCapabilities } from '@reactionary/provider-commercetools';
|
|
4
5
|
|
|
5
6
|
export default async function Index() {
|
|
6
7
|
const client = new ClientBuilder()
|
|
@@ -17,11 +18,12 @@ export default async function Index() {
|
|
|
17
18
|
category: 1,
|
|
18
19
|
},
|
|
19
20
|
},
|
|
20
|
-
{ search: true, product:
|
|
21
|
+
{ search: true, product: true, identity: false }
|
|
21
22
|
)
|
|
22
23
|
)
|
|
23
24
|
.withCache(new NoOpCache())
|
|
24
25
|
.build();
|
|
26
|
+
|
|
25
27
|
|
|
26
28
|
const session = SessionSchema.parse({
|
|
27
29
|
id: '1234567890',
|
|
@@ -32,20 +34,23 @@ export default async function Index() {
|
|
|
32
34
|
},
|
|
33
35
|
});
|
|
34
36
|
|
|
35
|
-
const search = await client.search
|
|
36
|
-
|
|
37
|
-
|
|
38
|
-
|
|
39
|
-
|
|
40
|
-
|
|
37
|
+
const search = await client.search.queryByTerm(
|
|
38
|
+
{
|
|
39
|
+
search: {
|
|
40
|
+
facets: [],
|
|
41
|
+
page: 1,
|
|
42
|
+
pageSize: 12,
|
|
43
|
+
term: 'glass',
|
|
44
|
+
},
|
|
41
45
|
},
|
|
42
|
-
|
|
46
|
+
session
|
|
47
|
+
);
|
|
43
48
|
|
|
44
|
-
return
|
|
45
|
-
|
|
46
|
-
|
|
47
|
-
|
|
48
|
-
|
|
49
|
-
|
|
50
|
-
|
|
49
|
+
return (
|
|
50
|
+
<div className={styles.page}>
|
|
51
|
+
{search.products.map((product, index) => (
|
|
52
|
+
<div key={index}>{product.name}</div>
|
|
53
|
+
))}
|
|
54
|
+
</div>
|
|
55
|
+
);
|
|
51
56
|
}
|
|
@@ -3,7 +3,6 @@ import {
|
|
|
3
3
|
Cache,
|
|
4
4
|
NoOpCache,
|
|
5
5
|
ProductSchema,
|
|
6
|
-
SessionSchema,
|
|
7
6
|
ProductQueryById,
|
|
8
7
|
ProductQueryBySlug,
|
|
9
8
|
} from '@reactionary/core';
|
|
@@ -11,12 +10,11 @@ import {
|
|
|
11
10
|
FakeProductProvider,
|
|
12
11
|
withFakeCapabilities,
|
|
13
12
|
} from '@reactionary/provider-fake';
|
|
13
|
+
import { createAnonymousTestSession } from '../test-utils';
|
|
14
14
|
import z from 'zod';
|
|
15
15
|
|
|
16
16
|
describe('basic node provider extension (models)', () => {
|
|
17
|
-
const session =
|
|
18
|
-
id: '1234567890',
|
|
19
|
-
});
|
|
17
|
+
const session = createAnonymousTestSession();
|
|
20
18
|
|
|
21
19
|
const ExtendedProductModel = ProductSchema.extend({
|
|
22
20
|
gtin: z.string().default('gtin-default'),
|
|
@@ -39,7 +37,12 @@ describe('basic node provider extension (models)', () => {
|
|
|
39
37
|
return (cache: Cache) => {
|
|
40
38
|
const client = {
|
|
41
39
|
product: new ExtendedProductProvider(
|
|
42
|
-
{ jitter: { mean: 0, deviation: 0 }
|
|
40
|
+
{ jitter: { mean: 0, deviation: 0 },
|
|
41
|
+
seeds: {
|
|
42
|
+
category: 1,
|
|
43
|
+
product: 1,
|
|
44
|
+
search: 1
|
|
45
|
+
} },
|
|
43
46
|
ExtendedProductModel,
|
|
44
47
|
cache
|
|
45
48
|
),
|
|
@@ -57,6 +60,11 @@ describe('basic node provider extension (models)', () => {
|
|
|
57
60
|
mean: 0,
|
|
58
61
|
deviation: 0,
|
|
59
62
|
},
|
|
63
|
+
seeds: {
|
|
64
|
+
category: 1,
|
|
65
|
+
product: 1,
|
|
66
|
+
search: 1
|
|
67
|
+
}
|
|
60
68
|
},
|
|
61
69
|
{ search: true, product: false, identity: false }
|
|
62
70
|
)
|
|
@@ -49,7 +49,12 @@ describe('basic node provider extension (models)', () => {
|
|
|
49
49
|
return (cache: Cache) => {
|
|
50
50
|
const client = {
|
|
51
51
|
product: new ExtendedProductProvider(
|
|
52
|
-
{ jitter: { mean: 0, deviation: 0 }
|
|
52
|
+
{ jitter: { mean: 0, deviation: 0 },
|
|
53
|
+
seeds: {
|
|
54
|
+
category: 1,
|
|
55
|
+
product: 1,
|
|
56
|
+
search: 1
|
|
57
|
+
}},
|
|
53
58
|
ExtendedProductModel,
|
|
54
59
|
cache
|
|
55
60
|
),
|
|
@@ -67,6 +72,11 @@ describe('basic node provider extension (models)', () => {
|
|
|
67
72
|
mean: 0,
|
|
68
73
|
deviation: 0,
|
|
69
74
|
},
|
|
75
|
+
seeds: {
|
|
76
|
+
category: 1,
|
|
77
|
+
product: 1,
|
|
78
|
+
search: 1
|
|
79
|
+
}
|
|
70
80
|
},
|
|
71
81
|
{ search: true, product: false, identity: false }
|
|
72
82
|
)
|
|
@@ -1,5 +1,6 @@
|
|
|
1
1
|
import { buildClient, NoOpCache, SessionSchema } from '@reactionary/core';
|
|
2
2
|
import { withFakeCapabilities } from '@reactionary/provider-fake';
|
|
3
|
+
import { createAnonymousTestSession } from '../test-utils';
|
|
3
4
|
|
|
4
5
|
describe('basic node setup', () => {
|
|
5
6
|
const client = buildClient(
|
|
@@ -10,6 +11,11 @@ describe('basic node setup', () => {
|
|
|
10
11
|
mean: 0,
|
|
11
12
|
deviation: 0,
|
|
12
13
|
},
|
|
14
|
+
seeds: {
|
|
15
|
+
category: 1,
|
|
16
|
+
product: 1,
|
|
17
|
+
search: 1
|
|
18
|
+
}
|
|
13
19
|
},
|
|
14
20
|
{ search: true, product: true, identity: false }
|
|
15
21
|
),
|
|
@@ -19,9 +25,7 @@ describe('basic node setup', () => {
|
|
|
19
25
|
}
|
|
20
26
|
);
|
|
21
27
|
|
|
22
|
-
const session =
|
|
23
|
-
id: '1234567890'
|
|
24
|
-
});
|
|
28
|
+
const session = createAnonymousTestSession();
|
|
25
29
|
|
|
26
30
|
it('should only get back the enabled capabilities', async () => {
|
|
27
31
|
expect(client.product).toBeDefined();
|
|
@@ -0,0 +1,26 @@
|
|
|
1
|
+
import { Session } from '@reactionary/core';
|
|
2
|
+
|
|
3
|
+
export function createAnonymousTestSession(): Session {
|
|
4
|
+
return {
|
|
5
|
+
id: 'test-session-id',
|
|
6
|
+
identity: {
|
|
7
|
+
type: 'Anonymous',
|
|
8
|
+
meta: {
|
|
9
|
+
cache: { hit: false, key: '' },
|
|
10
|
+
placeholder: false,
|
|
11
|
+
},
|
|
12
|
+
id: '',
|
|
13
|
+
token: undefined,
|
|
14
|
+
issued: new Date(),
|
|
15
|
+
expiry: new Date(new Date().getTime() + 3600 * 1000), // 1 hour from now
|
|
16
|
+
},
|
|
17
|
+
languageContext: {
|
|
18
|
+
locale: 'en-US',
|
|
19
|
+
currencyCode: 'USD',
|
|
20
|
+
countryCode: 'US',
|
|
21
|
+
},
|
|
22
|
+
storeIdentifier: {
|
|
23
|
+
key: 'the-good-store',
|
|
24
|
+
},
|
|
25
|
+
};
|
|
26
|
+
}
|
|
@@ -140,7 +140,7 @@ function createTracedMethod(
|
|
|
140
140
|
): any {
|
|
141
141
|
const { captureArgs, captureResult, spanName, spanKind } = options;
|
|
142
142
|
|
|
143
|
-
|
|
143
|
+
function tracedMethod(this: any, ...args: any[]): any {
|
|
144
144
|
const tracer = getTracer();
|
|
145
145
|
const className = this?.constructor?.name || 'Unknown';
|
|
146
146
|
const effectiveSpanName = spanName || `${className}.${methodName}`;
|
|
@@ -152,7 +152,7 @@ function createTracedMethod(
|
|
|
152
152
|
'function.name': methodName,
|
|
153
153
|
'function.class': className,
|
|
154
154
|
}
|
|
155
|
-
},
|
|
155
|
+
}, (span) => {
|
|
156
156
|
// Capture arguments if enabled
|
|
157
157
|
if (captureArgs && args.length > 0) {
|
|
158
158
|
args.forEach((arg, index) => {
|
|
@@ -196,9 +196,10 @@ function createTracedMethod(
|
|
|
196
196
|
// Handle async functions - await them to keep span open
|
|
197
197
|
if (result instanceof Promise) {
|
|
198
198
|
try {
|
|
199
|
-
|
|
200
|
-
|
|
201
|
-
|
|
199
|
+
return result.then(value => {
|
|
200
|
+
setSpanResult(value);
|
|
201
|
+
return value;
|
|
202
|
+
});
|
|
202
203
|
} catch (error) {
|
|
203
204
|
setSpanResult(error, true);
|
|
204
205
|
throw error;
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@reactionary/source",
|
|
3
|
-
"version": "0.0.
|
|
3
|
+
"version": "0.0.41",
|
|
4
4
|
"license": "MIT",
|
|
5
5
|
"private": false,
|
|
6
6
|
"dependencies": {
|
|
@@ -11,8 +11,9 @@
|
|
|
11
11
|
"@angular/platform-browser": "~19.2.0",
|
|
12
12
|
"@angular/platform-browser-dynamic": "~19.2.0",
|
|
13
13
|
"@angular/router": "~19.2.0",
|
|
14
|
-
"@commercetools/platform-sdk": "^8.
|
|
15
|
-
"@commercetools/ts-client": "^
|
|
14
|
+
"@commercetools/platform-sdk": "^8.16.0",
|
|
15
|
+
"@commercetools/ts-client": "^4.2.1",
|
|
16
|
+
"@commercetools/ts-sdk-apm": "^4.0.0",
|
|
16
17
|
"@faker-js/faker": "^9.8.0",
|
|
17
18
|
"@opentelemetry/api": "^1.9.0",
|
|
18
19
|
"@opentelemetry/core": "^2.0.1",
|
|
@@ -1,20 +1,27 @@
|
|
|
1
1
|
import { NoOpCache, SearchResultSchema } from '@reactionary/core';
|
|
2
2
|
import { AlgoliaSearchProvider } from '../providers/search.provider';
|
|
3
|
+
import { createAnonymousTestSession } from './test-utils';
|
|
3
4
|
|
|
4
5
|
describe('Algolia Search Provider', () => {
|
|
5
|
-
const provider = new AlgoliaSearchProvider(
|
|
6
|
-
|
|
7
|
-
|
|
8
|
-
|
|
9
|
-
|
|
6
|
+
const provider = new AlgoliaSearchProvider(
|
|
7
|
+
{
|
|
8
|
+
apiKey: process.env['ALGOLIA_API_KEY'] || '',
|
|
9
|
+
appId: process.env['ALGOLIA_APP_ID'] || '',
|
|
10
|
+
indexName: process.env['ALGOLIA_INDEX'] || '',
|
|
11
|
+
},
|
|
12
|
+
SearchResultSchema,
|
|
13
|
+
new NoOpCache()
|
|
14
|
+
);
|
|
15
|
+
|
|
16
|
+
const session = createAnonymousTestSession();
|
|
10
17
|
|
|
11
18
|
it('should be able to get a result by term', async () => {
|
|
12
|
-
const result = await provider.
|
|
19
|
+
const result = await provider.queryByTerm({ search: {
|
|
13
20
|
term: 'glass',
|
|
14
21
|
page: 0,
|
|
15
22
|
pageSize: 20,
|
|
16
23
|
facets: [],
|
|
17
|
-
});
|
|
24
|
+
}}, session);
|
|
18
25
|
|
|
19
26
|
expect(result.products.length).toBeGreaterThan(0);
|
|
20
27
|
expect(result.facets.length).toBe(2);
|
|
@@ -23,18 +30,19 @@ describe('Algolia Search Provider', () => {
|
|
|
23
30
|
});
|
|
24
31
|
|
|
25
32
|
it('should be able to paginate', async () => {
|
|
26
|
-
const firstPage = await provider.
|
|
33
|
+
const firstPage = await provider.queryByTerm({ search: {
|
|
27
34
|
term: 'glass',
|
|
28
35
|
page: 0,
|
|
29
36
|
pageSize: 20,
|
|
30
37
|
facets: [],
|
|
31
|
-
});
|
|
32
|
-
|
|
38
|
+
}}, session);
|
|
39
|
+
|
|
40
|
+
const secondPage = await provider.queryByTerm({ search: {
|
|
33
41
|
term: 'glass',
|
|
34
42
|
page: 1,
|
|
35
43
|
pageSize: 20,
|
|
36
44
|
facets: [],
|
|
37
|
-
});
|
|
45
|
+
}}, session);
|
|
38
46
|
|
|
39
47
|
expect(firstPage.identifier.page).toBe(0);
|
|
40
48
|
expect(secondPage.identifier.page).toBe(1);
|
|
@@ -44,18 +52,18 @@ describe('Algolia Search Provider', () => {
|
|
|
44
52
|
});
|
|
45
53
|
|
|
46
54
|
it('should be able to change page size', async () => {
|
|
47
|
-
const smallPage = await provider.
|
|
55
|
+
const smallPage = await provider.queryByTerm({ search: {
|
|
48
56
|
term: 'glass',
|
|
49
57
|
page: 0,
|
|
50
58
|
pageSize: 2,
|
|
51
59
|
facets: [],
|
|
52
|
-
});
|
|
53
|
-
const largePage = await provider.
|
|
60
|
+
}}, session);
|
|
61
|
+
const largePage = await provider.queryByTerm({ search: {
|
|
54
62
|
term: 'glass',
|
|
55
63
|
page: 0,
|
|
56
64
|
pageSize: 30,
|
|
57
65
|
facets: [],
|
|
58
|
-
});
|
|
66
|
+
}}, session);
|
|
59
67
|
|
|
60
68
|
expect(smallPage.products.length).toBe(2);
|
|
61
69
|
expect(smallPage.identifier.pageSize).toBe(2);
|
|
@@ -64,18 +72,19 @@ describe('Algolia Search Provider', () => {
|
|
|
64
72
|
});
|
|
65
73
|
|
|
66
74
|
it('should be able to apply facets', async () => {
|
|
67
|
-
const initial = await provider.
|
|
75
|
+
const initial = await provider.queryByTerm({ search: {
|
|
68
76
|
term: 'glass',
|
|
69
77
|
page: 0,
|
|
70
78
|
pageSize: 2,
|
|
71
79
|
facets: [],
|
|
72
|
-
});
|
|
73
|
-
|
|
80
|
+
}}, session);
|
|
81
|
+
|
|
82
|
+
const filtered = await provider.queryByTerm({ search: {
|
|
74
83
|
term: 'glass',
|
|
75
84
|
page: 0,
|
|
76
85
|
pageSize: 2,
|
|
77
86
|
facets: [initial.facets[0].values[0].identifier],
|
|
78
|
-
});
|
|
87
|
+
}}, session);
|
|
79
88
|
|
|
80
89
|
expect(initial.pages).toBeGreaterThan(filtered.pages);
|
|
81
90
|
});
|
|
@@ -0,0 +1,26 @@
|
|
|
1
|
+
import { Session } from "@reactionary/core";
|
|
2
|
+
|
|
3
|
+
export function createAnonymousTestSession(): Session {
|
|
4
|
+
return {
|
|
5
|
+
id: 'test-session-id',
|
|
6
|
+
identity: {
|
|
7
|
+
type: 'Anonymous',
|
|
8
|
+
meta: {
|
|
9
|
+
cache: { hit: false, key: '' },
|
|
10
|
+
placeholder: false,
|
|
11
|
+
},
|
|
12
|
+
id: '',
|
|
13
|
+
token: undefined,
|
|
14
|
+
issued: new Date(),
|
|
15
|
+
expiry: new Date(new Date().getTime() + 3600 * 1000), // 1 hour from now
|
|
16
|
+
},
|
|
17
|
+
languageContext: {
|
|
18
|
+
locale: 'en-US',
|
|
19
|
+
currencyCode: 'USD',
|
|
20
|
+
countryCode: 'US',
|
|
21
|
+
},
|
|
22
|
+
storeIdentifier: {
|
|
23
|
+
key: 'the-good-store',
|
|
24
|
+
},
|
|
25
|
+
};
|
|
26
|
+
}
|
|
@@ -2,7 +2,26 @@ import { ClientBuilder } from '@commercetools/ts-client';
|
|
|
2
2
|
import { createApiBuilderFromCtpClient } from '@commercetools/platform-sdk';
|
|
3
3
|
import { CommercetoolsConfiguration } from '../schema/configuration.schema';
|
|
4
4
|
|
|
5
|
-
const ANONYMOUS_SCOPES = [
|
|
5
|
+
const ANONYMOUS_SCOPES = [
|
|
6
|
+
'view_published_products',
|
|
7
|
+
'manage_shopping_lists',
|
|
8
|
+
'view_shipping_methods',
|
|
9
|
+
'manage_customers',
|
|
10
|
+
'view_product_selections',
|
|
11
|
+
'view_categories',
|
|
12
|
+
'view_project_settings',
|
|
13
|
+
'manage_order_edits',
|
|
14
|
+
'view_sessions',
|
|
15
|
+
'view_standalone_prices',
|
|
16
|
+
'manage_orders',
|
|
17
|
+
'view_tax_categories',
|
|
18
|
+
'view_cart_discounts',
|
|
19
|
+
'view_discount_codes',
|
|
20
|
+
'create_anonymous_token',
|
|
21
|
+
'manage_sessions',
|
|
22
|
+
'view_products',
|
|
23
|
+
'view_types',
|
|
24
|
+
];
|
|
6
25
|
const GUEST_SCOPES = [...ANONYMOUS_SCOPES];
|
|
7
26
|
const REGISTERED_SCOPES = [...GUEST_SCOPES];
|
|
8
27
|
|
|
@@ -119,7 +138,10 @@ export class CommercetoolsClient {
|
|
|
119
138
|
}
|
|
120
139
|
|
|
121
140
|
protected createClientWithToken(token: string) {
|
|
122
|
-
const builder = this.createBaseClientBuilder().withExistingTokenFlow(
|
|
141
|
+
const builder = this.createBaseClientBuilder().withExistingTokenFlow(
|
|
142
|
+
`Bearer ${token}`,
|
|
143
|
+
{ force: true }
|
|
144
|
+
);
|
|
123
145
|
|
|
124
146
|
return createApiBuilderFromCtpClient(builder.build());
|
|
125
147
|
}
|
|
@@ -146,6 +168,8 @@ export class CommercetoolsClient {
|
|
|
146
168
|
httpClient: fetch,
|
|
147
169
|
});
|
|
148
170
|
|
|
171
|
+
// CT's telemetry module is currently broken and consequently not included in the above (createTelemetryMiddleware)
|
|
172
|
+
|
|
149
173
|
return builder;
|
|
150
174
|
}
|
|
151
175
|
}
|
|
@@ -1,4 +1,20 @@
|
|
|
1
|
-
import {
|
|
1
|
+
import {
|
|
2
|
+
CartSchema,
|
|
3
|
+
IdentitySchema,
|
|
4
|
+
InventorySchema,
|
|
5
|
+
PriceSchema,
|
|
6
|
+
ProductSchema,
|
|
7
|
+
SearchResultSchema,
|
|
8
|
+
Cache,
|
|
9
|
+
CategorySchema,
|
|
10
|
+
ProductProvider,
|
|
11
|
+
SearchProvider,
|
|
12
|
+
IdentityProvider,
|
|
13
|
+
CartProvider,
|
|
14
|
+
InventoryProvider,
|
|
15
|
+
PriceProvider,
|
|
16
|
+
CategoryProvider
|
|
17
|
+
} from "@reactionary/core";
|
|
2
18
|
import { CommercetoolsCapabilities } from "../schema/capabilities.schema";
|
|
3
19
|
import { CommercetoolsSearchProvider } from "../providers/search.provider";
|
|
4
20
|
import { CommercetoolsProductProvider } from '../providers/product.provider';
|
|
@@ -9,12 +25,21 @@ import { CommercetoolsInventoryProvider } from "../providers/inventory.provider"
|
|
|
9
25
|
import { CommercetoolsPriceProvider } from "../providers/price.provider";
|
|
10
26
|
import { CommercetoolsCategoryProvider } from "../providers/category.provider";
|
|
11
27
|
|
|
12
|
-
|
|
28
|
+
type CommercetoolsClient<T extends CommercetoolsCapabilities> =
|
|
29
|
+
(T['cart'] extends true ? { cart: CartProvider } : object) &
|
|
30
|
+
(T['product'] extends true ? { product: ProductProvider } : object) &
|
|
31
|
+
(T['search'] extends true ? { search: SearchProvider } : object) &
|
|
32
|
+
(T['identity'] extends true ? { identity: IdentityProvider } : object) &
|
|
33
|
+
(T['category'] extends true ? { category: CategoryProvider } : object) &
|
|
34
|
+
(T['inventory'] extends true ? { inventory: InventoryProvider } : object) &
|
|
35
|
+
(T['price'] extends true ? { price: PriceProvider } : object);
|
|
36
|
+
|
|
37
|
+
export function withCommercetoolsCapabilities<T extends CommercetoolsCapabilities>(
|
|
13
38
|
configuration: CommercetoolsConfiguration,
|
|
14
|
-
capabilities:
|
|
39
|
+
capabilities: T
|
|
15
40
|
) {
|
|
16
|
-
return (cache: Cache) => {
|
|
17
|
-
const client:
|
|
41
|
+
return (cache: Cache): CommercetoolsClient<T> => {
|
|
42
|
+
const client: any = {};
|
|
18
43
|
|
|
19
44
|
if (capabilities.product) {
|
|
20
45
|
client.product = new CommercetoolsProductProvider(configuration, ProductSchema, cache);
|
|
@@ -39,8 +64,9 @@ export function withCommercetoolsCapabilities(
|
|
|
39
64
|
if (capabilities.price) {
|
|
40
65
|
client.price = new CommercetoolsPriceProvider(configuration, PriceSchema, cache);
|
|
41
66
|
}
|
|
67
|
+
|
|
42
68
|
if (capabilities.category) {
|
|
43
|
-
|
|
69
|
+
client.category = new CommercetoolsCategoryProvider(configuration, CategorySchema, cache);
|
|
44
70
|
}
|
|
45
71
|
|
|
46
72
|
return client;
|
|
@@ -1,19 +1,21 @@
|
|
|
1
1
|
import {
|
|
2
2
|
Cart,
|
|
3
3
|
CartItemSchema,
|
|
4
|
+
CartProvider,
|
|
5
|
+
Cache,
|
|
6
|
+
Currency,
|
|
7
|
+
} from '@reactionary/core';
|
|
8
|
+
import type {
|
|
4
9
|
CartMutationItemAdd,
|
|
5
10
|
CartMutationItemQuantityChange,
|
|
6
11
|
CartMutationItemRemove,
|
|
7
|
-
CartProvider,
|
|
8
12
|
CartQueryById,
|
|
9
13
|
Session,
|
|
10
|
-
Cache,
|
|
11
|
-
Currency,
|
|
12
14
|
} from '@reactionary/core';
|
|
13
15
|
import { CommercetoolsConfiguration } from '../schema/configuration.schema';
|
|
14
16
|
import { z } from 'zod';
|
|
15
17
|
import { CommercetoolsClient } from '../core/client';
|
|
16
|
-
import { Cart as CTCart } from '@commercetools/platform-sdk';
|
|
18
|
+
import type { Cart as CTCart } from '@commercetools/platform-sdk';
|
|
17
19
|
import { traced } from '@reactionary/otel';
|
|
18
20
|
|
|
19
21
|
|
|
@@ -1,4 +1,5 @@
|
|
|
1
|
-
import { CategoryProvider, Cache, Category,
|
|
1
|
+
import { CategoryProvider, Cache, Category, createPaginatedResponseSchema } from "@reactionary/core";
|
|
2
|
+
import type { Session, CategoryQueryById, CategoryQueryBySlug, CategoryQueryForBreadcrumb, CategoryQueryForChildCategories, CategoryQueryForTopCategories } from "@reactionary/core";
|
|
2
3
|
import z from "zod";
|
|
3
4
|
import { CommercetoolsConfiguration } from "../schema/configuration.schema";
|
|
4
5
|
import { CommercetoolsClient } from "../core/client";
|
|
@@ -35,7 +36,6 @@ export class CommercetoolsCategoryProvider<
|
|
|
35
36
|
const response = await client.withKey({ key: payload.id.key }).get().execute();
|
|
36
37
|
return this.parseSingle(response.body, session);
|
|
37
38
|
} catch (error) {
|
|
38
|
-
console.error(`Error fetching category by ID ${payload.id.key}:`, error);
|
|
39
39
|
const dummyCategory = this.newModel();
|
|
40
40
|
dummyCategory.meta.placeholder = true;
|
|
41
41
|
dummyCategory.identifier = { key: payload.id.key };
|
|
@@ -1,4 +1,5 @@
|
|
|
1
|
-
import { Price, PriceProvider,
|
|
1
|
+
import { Price, PriceProvider, Cache, Currency, TieredPriceSchema, TieredPrice } from '@reactionary/core';
|
|
2
|
+
import type { PriceQueryBySku, Session } from '@reactionary/core';
|
|
2
3
|
import z from 'zod';
|
|
3
4
|
import { CommercetoolsConfiguration } from '../schema/configuration.schema';
|
|
4
5
|
import { CommercetoolsClient } from '../core/client';
|
|
@@ -1,26 +1,33 @@
|
|
|
1
|
-
import {
|
|
2
|
-
|
|
3
|
-
SearchQueryByTerm,
|
|
1
|
+
import { SearchProvider } from '@reactionary/core';
|
|
2
|
+
import type {
|
|
4
3
|
SearchResult,
|
|
5
4
|
SearchResultProduct,
|
|
6
|
-
Session,
|
|
7
5
|
Cache,
|
|
6
|
+
SearchQueryByTerm,
|
|
7
|
+
Session,
|
|
8
8
|
} from '@reactionary/core';
|
|
9
|
+
|
|
9
10
|
import { CommercetoolsClient } from '../core/client';
|
|
10
11
|
import z from 'zod';
|
|
11
12
|
import { CommercetoolsConfiguration } from '../schema/configuration.schema';
|
|
13
|
+
import { traced } from '@reactionary/otel';
|
|
12
14
|
|
|
13
15
|
export class CommercetoolsSearchProvider<
|
|
14
16
|
T extends SearchResult = SearchResult
|
|
15
17
|
> extends SearchProvider<T> {
|
|
16
18
|
protected config: CommercetoolsConfiguration;
|
|
17
19
|
|
|
18
|
-
constructor(
|
|
20
|
+
constructor(
|
|
21
|
+
config: CommercetoolsConfiguration,
|
|
22
|
+
schema: z.ZodType<T>,
|
|
23
|
+
cache: Cache
|
|
24
|
+
) {
|
|
19
25
|
super(schema, cache);
|
|
20
26
|
|
|
21
27
|
this.config = config;
|
|
22
28
|
}
|
|
23
29
|
|
|
30
|
+
@traced()
|
|
24
31
|
public override async queryByTerm(
|
|
25
32
|
payload: SearchQueryByTerm,
|
|
26
33
|
session: Session
|
|
@@ -43,9 +50,23 @@ export class CommercetoolsSearchProvider<
|
|
|
43
50
|
return this.parseSearchResult(remote, payload, session);
|
|
44
51
|
}
|
|
45
52
|
|
|
46
|
-
protected parseSearchResult(
|
|
53
|
+
protected parseSearchResult(
|
|
54
|
+
remote: unknown,
|
|
55
|
+
payload: SearchQueryByTerm,
|
|
56
|
+
session: Session
|
|
57
|
+
): T {
|
|
47
58
|
const result = this.newModel();
|
|
48
|
-
const remoteData = remote as {
|
|
59
|
+
const remoteData = remote as {
|
|
60
|
+
body: {
|
|
61
|
+
results: Array<{
|
|
62
|
+
id: string;
|
|
63
|
+
name: Record<string, string>;
|
|
64
|
+
slug?: Record<string, string>;
|
|
65
|
+
masterVariant: { images?: Array<{ url?: string }> };
|
|
66
|
+
}>;
|
|
67
|
+
total?: number;
|
|
68
|
+
};
|
|
69
|
+
};
|
|
49
70
|
|
|
50
71
|
result.identifier = payload.search;
|
|
51
72
|
|
|
@@ -54,20 +75,20 @@ export class CommercetoolsSearchProvider<
|
|
|
54
75
|
identifier: { key: p.id },
|
|
55
76
|
name: p.name[session.languageContext.locale] || p.id,
|
|
56
77
|
slug: p.slug?.[session.languageContext.locale] || p.id,
|
|
57
|
-
image: p.masterVariant.images?.[0]?.url || 'https://placehold.co/400'
|
|
78
|
+
image: p.masterVariant.images?.[0]?.url || 'https://placehold.co/400',
|
|
58
79
|
};
|
|
59
80
|
|
|
60
81
|
result.products.push(product);
|
|
61
82
|
}
|
|
62
83
|
|
|
63
|
-
result.pages = Math.ceil(
|
|
84
|
+
result.pages = Math.ceil(
|
|
85
|
+
(remoteData.body.total || 0) / payload.search.pageSize
|
|
86
|
+
);
|
|
64
87
|
result.meta = {
|
|
65
88
|
cache: { hit: false, key: payload.search.term },
|
|
66
|
-
placeholder: false
|
|
89
|
+
placeholder: false,
|
|
67
90
|
};
|
|
68
91
|
|
|
69
92
|
return this.assert(result);
|
|
70
93
|
}
|
|
71
|
-
|
|
72
|
-
|
|
73
94
|
}
|
|
@@ -1,11 +1,7 @@
|
|
|
1
1
|
import 'dotenv/config'
|
|
2
|
-
|
|
3
|
-
import { diag, DiagConsoleLogger, DiagLogLevel } from '@opentelemetry/api';
|
|
4
|
-
|
|
5
|
-
import { CategorySchema, NoOpCache, ProductSchema, Session } from '@reactionary/core';
|
|
2
|
+
import { CategorySchema, NoOpCache, Session } from '@reactionary/core';
|
|
6
3
|
import { CommercetoolsCategoryProvider } from '../providers/category.provider';
|
|
7
4
|
import { createAnonymousTestSession, getCommercetoolsTestConfiguration } from './test-utils';
|
|
8
|
-
import { getTracer, shutdownOtel } from '@reactionary/otel';
|
|
9
5
|
|
|
10
6
|
const testData = {
|
|
11
7
|
topCategories: [
|
|
@@ -168,13 +164,4 @@ describe('Commercetools Category Provider', () => {
|
|
|
168
164
|
expect(result.meta.placeholder).toBe(true);
|
|
169
165
|
|
|
170
166
|
});
|
|
171
|
-
|
|
172
|
-
it('traces execution of getById', async () => {
|
|
173
|
-
const tracer = getTracer();
|
|
174
|
-
const span = tracer.startSpan('test-span');
|
|
175
|
-
const result = await provider.getById({ id: { key: 'home-decor'}}, session);
|
|
176
|
-
span.end();
|
|
177
|
-
await shutdownOtel();
|
|
178
|
-
});
|
|
179
|
-
|
|
180
167
|
});
|
|
@@ -6,13 +6,12 @@ import { FakeCapabilities } from "../schema/capabilities.schema";
|
|
|
6
6
|
import { FakeCategoryProvider } from "../providers/category.provider";
|
|
7
7
|
import { FakeCartProvider } from "../providers";
|
|
8
8
|
|
|
9
|
-
type FakeClient<T extends FakeCapabilities> =
|
|
10
|
-
|
|
11
|
-
|
|
12
|
-
|
|
13
|
-
|
|
14
|
-
|
|
15
|
-
}>;
|
|
9
|
+
type FakeClient<T extends FakeCapabilities> =
|
|
10
|
+
(T['cart'] extends true ? { cart: CartProvider } : object) &
|
|
11
|
+
(T['product'] extends true ? { product: ProductProvider } : object) &
|
|
12
|
+
(T['search'] extends true ? { search: SearchProvider } : object) &
|
|
13
|
+
(T['identity'] extends true ? { identity: IdentityProvider } : object) &
|
|
14
|
+
(T['category'] extends true ? { category: CategoryProvider } : object);
|
|
16
15
|
|
|
17
16
|
export function withFakeCapabilities<T extends FakeCapabilities>(configuration: FakeConfiguration, capabilities: T) {
|
|
18
17
|
return (cache: ReactinaryCache): FakeClient<T> => {
|
|
@@ -30,7 +29,6 @@ export function withFakeCapabilities<T extends FakeCapabilities>(configuration:
|
|
|
30
29
|
client.category = new FakeCategoryProvider(configuration, CategorySchema, cache);
|
|
31
30
|
}
|
|
32
31
|
|
|
33
|
-
|
|
34
32
|
if (capabilities.cart) {
|
|
35
33
|
client.cart = new FakeCartProvider(configuration, CartSchema, cache);
|
|
36
34
|
}
|
|
@@ -1,11 +1,12 @@
|
|
|
1
|
-
import {
|
|
1
|
+
import { ClientBuilder, NoOpCache } from '@reactionary/core';
|
|
2
2
|
import { withFakeCapabilities } from '@reactionary/provider-fake';
|
|
3
|
-
import { createTRPCServerRouter, createTRPCContext
|
|
4
|
-
import { createTRPCClient
|
|
3
|
+
import { createTRPCServerRouter, createTRPCContext } from './server';
|
|
4
|
+
import { createTRPCClient } from './client';
|
|
5
5
|
import type { TransparentClient } from './types';
|
|
6
6
|
import { createTRPCProxyClient, httpBatchLink } from '@trpc/client';
|
|
7
7
|
import { createHTTPHandler } from '@trpc/server/adapters/standalone';
|
|
8
8
|
import * as http from 'http';
|
|
9
|
+
import { createAnonymousTestSession } from './test-utils';
|
|
9
10
|
|
|
10
11
|
/**
|
|
11
12
|
* Integration test that actually starts an HTTP server and makes real network calls
|
|
@@ -16,29 +17,31 @@ import * as http from 'http';
|
|
|
16
17
|
global.fetch = global.fetch || require('node-fetch');
|
|
17
18
|
|
|
18
19
|
// Create the server-side client
|
|
19
|
-
const serverClient =
|
|
20
|
-
|
|
21
|
-
|
|
22
|
-
|
|
23
|
-
|
|
24
|
-
|
|
25
|
-
|
|
26
|
-
|
|
27
|
-
|
|
28
|
-
|
|
20
|
+
const serverClient = new ClientBuilder()
|
|
21
|
+
.withCapability(
|
|
22
|
+
withFakeCapabilities(
|
|
23
|
+
{
|
|
24
|
+
jitter: {
|
|
25
|
+
mean: 0,
|
|
26
|
+
deviation: 0,
|
|
27
|
+
},
|
|
28
|
+
seeds: {
|
|
29
|
+
category: 1,
|
|
30
|
+
product: 1,
|
|
31
|
+
search: 1
|
|
32
|
+
}
|
|
29
33
|
},
|
|
30
|
-
|
|
31
|
-
|
|
32
|
-
)
|
|
33
|
-
|
|
34
|
-
|
|
35
|
-
);
|
|
34
|
+
{ search: true, product: true, identity: false }
|
|
35
|
+
)
|
|
36
|
+
)
|
|
37
|
+
.withCache(new NoOpCache())
|
|
38
|
+
.build();
|
|
36
39
|
|
|
37
40
|
// Create TRPC router from the client (do this at module level for type inference)
|
|
38
41
|
const router = createTRPCServerRouter(serverClient);
|
|
39
42
|
type AppRouter = typeof router;
|
|
40
43
|
|
|
41
|
-
|
|
44
|
+
xdescribe('TRPC Integration Test - Real HTTP Server', () => {
|
|
42
45
|
let server: http.Server;
|
|
43
46
|
let serverPort: number;
|
|
44
47
|
let trpcProxyClient: ReturnType<typeof createTRPCProxyClient<AppRouter>>;
|
|
@@ -96,9 +99,7 @@ describe('TRPC Integration Test - Real HTTP Server', () => {
|
|
|
96
99
|
}
|
|
97
100
|
});
|
|
98
101
|
|
|
99
|
-
const session =
|
|
100
|
-
id: '1234567890',
|
|
101
|
-
});
|
|
102
|
+
const session = createAnonymousTestSession();
|
|
102
103
|
|
|
103
104
|
describe('Product Provider via HTTP', () => {
|
|
104
105
|
it('should fetch product by slug through real HTTP calls', async () => {
|
|
@@ -0,0 +1,26 @@
|
|
|
1
|
+
import { Session } from '@reactionary/core';
|
|
2
|
+
|
|
3
|
+
export function createAnonymousTestSession(): Session {
|
|
4
|
+
return {
|
|
5
|
+
id: 'test-session-id',
|
|
6
|
+
identity: {
|
|
7
|
+
type: 'Anonymous',
|
|
8
|
+
meta: {
|
|
9
|
+
cache: { hit: false, key: '' },
|
|
10
|
+
placeholder: false,
|
|
11
|
+
},
|
|
12
|
+
id: '',
|
|
13
|
+
token: undefined,
|
|
14
|
+
issued: new Date(),
|
|
15
|
+
expiry: new Date(new Date().getTime() + 3600 * 1000), // 1 hour from now
|
|
16
|
+
},
|
|
17
|
+
languageContext: {
|
|
18
|
+
locale: 'en-US',
|
|
19
|
+
currencyCode: 'USD',
|
|
20
|
+
countryCode: 'US',
|
|
21
|
+
},
|
|
22
|
+
storeIdentifier: {
|
|
23
|
+
key: 'the-good-store',
|
|
24
|
+
},
|
|
25
|
+
};
|
|
26
|
+
}
|
|
@@ -1,6 +1,7 @@
|
|
|
1
|
-
import {
|
|
1
|
+
import { ClientBuilder, NoOpCache } from '@reactionary/core';
|
|
2
2
|
import { withFakeCapabilities } from '@reactionary/provider-fake';
|
|
3
3
|
import { createTRPCServerRouter, introspectClient } from './index';
|
|
4
|
+
import { createAnonymousTestSession } from './test-utils';
|
|
4
5
|
|
|
5
6
|
/**
|
|
6
7
|
* Test suite for TRPC transparent client functionality
|
|
@@ -10,37 +11,32 @@ import { createTRPCServerRouter, introspectClient } from './index';
|
|
|
10
11
|
// Jest test framework is now available
|
|
11
12
|
|
|
12
13
|
// Create the server-side client using the same pattern as examples/node
|
|
13
|
-
const serverClient =
|
|
14
|
-
|
|
15
|
-
|
|
16
|
-
|
|
17
|
-
|
|
18
|
-
|
|
19
|
-
|
|
20
|
-
|
|
21
|
-
|
|
22
|
-
|
|
23
|
-
|
|
24
|
-
|
|
25
|
-
|
|
14
|
+
const serverClient = new ClientBuilder()
|
|
15
|
+
.withCapability(
|
|
16
|
+
withFakeCapabilities(
|
|
17
|
+
{
|
|
18
|
+
jitter: {
|
|
19
|
+
mean: 0,
|
|
20
|
+
deviation: 0,
|
|
21
|
+
},
|
|
22
|
+
seeds: {
|
|
23
|
+
category: 1,
|
|
24
|
+
product: 1,
|
|
25
|
+
search: 1
|
|
26
|
+
}
|
|
26
27
|
},
|
|
27
|
-
|
|
28
|
-
|
|
29
|
-
)
|
|
30
|
-
|
|
31
|
-
|
|
32
|
-
cache: new NoOpCache(),
|
|
33
|
-
}
|
|
34
|
-
);
|
|
28
|
+
{ search: true, product: true, identity: false }
|
|
29
|
+
)
|
|
30
|
+
)
|
|
31
|
+
.withCache(new NoOpCache())
|
|
32
|
+
.build();
|
|
35
33
|
|
|
36
34
|
// Create TRPC router from the client
|
|
37
35
|
const router = createTRPCServerRouter(serverClient);
|
|
38
36
|
|
|
39
|
-
|
|
37
|
+
xdescribe('TRPC Transparent Client Core Functionality', () => {
|
|
40
38
|
|
|
41
|
-
const session =
|
|
42
|
-
id: '1234567890',
|
|
43
|
-
});
|
|
39
|
+
const session = createAnonymousTestSession();
|
|
44
40
|
|
|
45
41
|
describe('Client Introspection', () => {
|
|
46
42
|
it('should correctly introspect client methods', () => {
|
|
@@ -1,18 +0,0 @@
|
|
|
1
|
-
import { ProductSchema } from '@reactionary/core';
|
|
2
|
-
import { AlgoliaProductProvider } from '../providers/product.provider';
|
|
3
|
-
|
|
4
|
-
describe('Algolia Product Provider', () => {
|
|
5
|
-
it('should be able to get a product by id', async () => {
|
|
6
|
-
const provider = new AlgoliaProductProvider({
|
|
7
|
-
apiKey: process.env['ALGOLIA_API_KEY'] || '',
|
|
8
|
-
appId: process.env['ALGOLIA_APP_ID'] || '',
|
|
9
|
-
indexName: process.env['ALGOLIA_INDEX'] || ''
|
|
10
|
-
}, ProductSchema);
|
|
11
|
-
|
|
12
|
-
const result = await provider.get({ id: '4d28f98d-c446-446e-b59a-d9f718e5b98a'});
|
|
13
|
-
|
|
14
|
-
expect(result.identifier.key).toBe('4d28f98d-c446-446e-b59a-d9f718e5b98a');
|
|
15
|
-
expect(result.name).toBe('Sunnai Glass Bowl');
|
|
16
|
-
expect(result.image).toBe('https://res.cloudinary.com/dfke2ip5c/image/upload/c_thumb,w_200,g_face/v1744117881/6d189e9017e385a6a465b9099227ccae.jpeg');
|
|
17
|
-
});
|
|
18
|
-
});
|