@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.
Files changed (26) hide show
  1. package/core/src/decorators/reactionary.decorator.ts +16 -0
  2. package/core/src/index.ts +2 -0
  3. package/core/src/schemas/mutations/cart.mutation.ts +6 -6
  4. package/examples/next/src/app/page.tsx +20 -15
  5. package/examples/node/package.json +3 -1
  6. package/examples/node/src/basic/basic-node-provider-model-extension.spec.ts +13 -5
  7. package/examples/node/src/basic/basic-node-provider-query-extension.spec.ts +11 -1
  8. package/examples/node/src/basic/basic-node-setup.spec.ts +7 -3
  9. package/examples/node/src/test-utils.ts +26 -0
  10. package/otel/src/trace-decorator.ts +6 -5
  11. package/package.json +4 -3
  12. package/providers/algolia/src/test/search.provider.spec.ts +28 -19
  13. package/providers/algolia/src/test/test-utils.ts +26 -0
  14. package/providers/commercetools/package.json +1 -1
  15. package/providers/commercetools/src/core/client.ts +26 -2
  16. package/providers/commercetools/src/core/initialize.ts +32 -6
  17. package/providers/commercetools/src/providers/cart.provider.ts +6 -4
  18. package/providers/commercetools/src/providers/category.provider.ts +2 -2
  19. package/providers/commercetools/src/providers/price.provider.ts +2 -1
  20. package/providers/commercetools/src/providers/search.provider.ts +33 -12
  21. package/providers/commercetools/src/test/category.provider.spec.ts +1 -14
  22. package/providers/fake/src/core/initialize.ts +6 -8
  23. package/trpc/src/integration.spec.ts +24 -23
  24. package/trpc/src/test-utils.ts +26 -0
  25. package/trpc/src/transparent-client.spec.ts +22 -26
  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.required(),
7
- product: ProductIdentifierSchema.required(),
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.required(),
13
- item: CartItemIdentifierSchema.required()
12
+ cart: CartIdentifierSchema.nonoptional(),
13
+ item: CartItemIdentifierSchema.nonoptional()
14
14
  });
15
15
 
16
16
  export const CartMutationItemQuantityChangeSchema = BaseMutationSchema.extend({
17
- cart: CartIdentifierSchema.required(),
18
- item: CartItemIdentifierSchema.required(),
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: false, identity: false }
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?.queryByTerm({
36
- search: {
37
- facets: [],
38
- page: 0,
39
- pageSize: 12,
40
- term: 'glass',
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
- }, session);
46
+ session
47
+ );
43
48
 
44
- return <div className={styles.page}>
45
- {search?.products.map((product, index) => (
46
- <div key={index}>
47
- { product.name }
48
- </div>
49
- ))}
50
- </div>;
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
  }
@@ -2,5 +2,7 @@
2
2
  "name": "@reactionary/examples-node",
3
3
  "version": "0.0.1",
4
4
  "private": true,
5
- "dependencies": {}
5
+ "dependencies": {
6
+ "@reactionary/core": "0.0.1"
7
+ }
6
8
  }
@@ -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 = SessionSchema.parse({
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 = SessionSchema.parse({
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
- async function tracedMethod(this: any, ...args: any[]): Promise<any> {
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
- }, async (span) => {
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
- const value = await result;
200
- setSpanResult(value);
201
- return value;
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.40",
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.8.0",
15
- "@commercetools/ts-client": "^3.2.2",
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
- apiKey: process.env['ALGOLIA_API_KEY'] || '',
7
- appId: process.env['ALGOLIA_APP_ID'] || '',
8
- indexName: process.env['ALGOLIA_INDEX'] || '',
9
- }, SearchResultSchema, new NoOpCache());
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.get({
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.get({
33
+ const firstPage = await provider.queryByTerm({ search: {
27
34
  term: 'glass',
28
35
  page: 0,
29
36
  pageSize: 20,
30
37
  facets: [],
31
- });
32
- const secondPage = await provider.get({
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.get({
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.get({
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.get({
75
+ const initial = await provider.queryByTerm({ search: {
68
76
  term: 'glass',
69
77
  page: 0,
70
78
  pageSize: 2,
71
79
  facets: [],
72
- });
73
- const filtered = await provider.get({
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
+ }
@@ -7,7 +7,7 @@
7
7
  "@reactionary/core": "0.0.1",
8
8
  "@reactionary/otel": "0.0.1",
9
9
  "zod": "4.1.9",
10
- "@commercetools/ts-client": "^3.2.2",
10
+ "@commercetools/ts-client": "^4.2.1",
11
11
  "@commercetools/platform-sdk": "^8.8.0"
12
12
  }
13
13
  }
@@ -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 = ['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'];
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(`Bearer ${ token }`, { force: true });
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 { CartSchema, Client, IdentitySchema, InventorySchema, PriceSchema, ProductSchema, SearchResultSchema, Cache, CategorySchema } from "@reactionary/core";
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
- export function withCommercetoolsCapabilities(
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: CommercetoolsCapabilities
39
+ capabilities: T
15
40
  ) {
16
- return (cache: Cache) => {
17
- const client: Partial<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
- client.category = new CommercetoolsCategoryProvider(configuration, CategorySchema, cache);
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, Session, CategoryIdentifier, PaginationOptions, createPaginatedResponseSchema, CategoryQueryById, CategoryQueryBySlug, CategoryQueryForBreadcrumb, CategoryQueryForChildCategories, CategoryQueryForTopCategories } from "@reactionary/core";
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, PriceQueryBySku, Session, Cache, Currency, TieredPriceSchema, MonetaryAmountSchema, TieredPrice } from '@reactionary/core';
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
- SearchProvider,
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(config: CommercetoolsConfiguration, schema: z.ZodType<T>, cache: Cache) {
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(remote: unknown, payload: SearchQueryByTerm, session: Session): T {
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 { body: { results: Array<{ id: string; name: Record<string, string>; slug?: Record<string, string>; masterVariant: { images?: Array<{ url?: string }> } }>; total?: number } };
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((remoteData.body.total || 0) / payload.search.pageSize);
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> = Partial<{
10
- cart: T['cart'] extends true ? CartProvider : never;
11
- product: T['product'] extends true ? ProductProvider : never;
12
- search: T['search'] extends true ? SearchProvider : never;
13
- identity: T['identity'] extends true ? IdentityProvider : never;
14
- category: T['category'] extends true ? CategoryProvider : never;
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 { buildClient, NoOpCache, SessionSchema } from '@reactionary/core';
1
+ import { ClientBuilder, NoOpCache } from '@reactionary/core';
2
2
  import { withFakeCapabilities } from '@reactionary/provider-fake';
3
- import { createTRPCServerRouter, createTRPCContext, type TRPCRouterFromClient } from './server';
4
- import { createTRPCClient, type TRPCClientFromRouter } from './client';
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 = buildClient(
20
- [
21
- withFakeCapabilities(
22
- {
23
- jitter: { mean: 0, deviation: 0 },
24
- seeds: {
25
- product: 12345,
26
- category: 12345,
27
- cart: 12345,
28
- search: 0
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
- { search: true, product: true, identity: false, cart: true }
32
- ),
33
- ],
34
- { cache: new NoOpCache() }
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
- describe('TRPC Integration Test - Real HTTP Server', () => {
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 = SessionSchema.parse({
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 { buildClient, NoOpCache, SessionSchema } from '@reactionary/core';
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 = buildClient(
14
- [
15
- withFakeCapabilities(
16
- {
17
- jitter: {
18
- mean: 0,
19
- deviation: 0,
20
- },
21
- seeds: {
22
- product: 12345,
23
- category: 12345,
24
- cart: 12345,
25
- search: 0
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
- { search: true, product: true, identity: false, cart: true }
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
- describe('TRPC Transparent Client Core Functionality', () => {
37
+ xdescribe('TRPC Transparent Client Core Functionality', () => {
40
38
 
41
- const session = SessionSchema.parse({
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
- });