@reactionary/source 0.0.37 → 0.0.39

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 (93) hide show
  1. package/.env-template +10 -0
  2. package/.github/workflows/pull-request.yml +5 -3
  3. package/core/package.json +1 -2
  4. package/core/src/client/client.ts +4 -2
  5. package/core/src/index.ts +3 -9
  6. package/core/src/providers/analytics.provider.ts +6 -1
  7. package/core/src/providers/base.provider.ts +33 -3
  8. package/core/src/providers/cart.provider.ts +7 -1
  9. package/core/src/providers/category.provider.ts +91 -0
  10. package/core/src/providers/identity.provider.ts +5 -1
  11. package/core/src/providers/inventory.provider.ts +4 -0
  12. package/core/src/providers/price.provider.ts +54 -0
  13. package/core/src/providers/product.provider.ts +4 -0
  14. package/core/src/providers/search.provider.ts +6 -0
  15. package/core/src/schemas/capabilities.schema.ts +3 -2
  16. package/core/src/schemas/models/base.model.ts +42 -1
  17. package/core/src/schemas/models/cart.model.ts +27 -3
  18. package/core/src/schemas/models/category.model.ts +23 -0
  19. package/core/src/schemas/models/identifiers.model.ts +29 -1
  20. package/core/src/schemas/models/inventory.model.ts +6 -2
  21. package/core/src/schemas/models/price.model.ts +11 -3
  22. package/core/src/schemas/models/search.model.ts +4 -2
  23. package/core/src/schemas/queries/category.query.ts +32 -0
  24. package/core/src/schemas/queries/index.ts +9 -0
  25. package/core/src/schemas/queries/inventory.query.ts +18 -3
  26. package/core/src/schemas/session.schema.ts +13 -2
  27. package/examples/next/.swcrc +30 -0
  28. package/examples/next/eslint.config.mjs +21 -0
  29. package/examples/next/index.d.ts +6 -0
  30. package/examples/next/next-env.d.ts +5 -0
  31. package/examples/next/next.config.js +20 -0
  32. package/examples/next/project.json +9 -0
  33. package/examples/next/public/.gitkeep +0 -0
  34. package/examples/next/public/favicon.ico +0 -0
  35. package/examples/next/src/app/global.css +0 -0
  36. package/examples/next/src/app/layout.tsx +18 -0
  37. package/examples/next/src/app/page.module.scss +2 -0
  38. package/examples/next/src/app/page.tsx +51 -0
  39. package/examples/next/src/instrumentation.ts +9 -0
  40. package/examples/next/tsconfig.json +44 -0
  41. package/examples/node/src/basic/basic-node-provider-model-extension.spec.ts +0 -1
  42. package/examples/node/src/basic/basic-node-setup.spec.ts +0 -1
  43. package/otel/README.md +152 -172
  44. package/otel/package.json +0 -1
  45. package/otel/src/index.ts +15 -5
  46. package/otel/src/metrics.ts +3 -3
  47. package/otel/src/test/otel.spec.ts +8 -0
  48. package/otel/src/trace-decorator.ts +87 -108
  49. package/otel/src/tracer.ts +3 -3
  50. package/package.json +2 -1
  51. package/providers/commercetools/package.json +1 -0
  52. package/providers/commercetools/src/core/initialize.ts +7 -3
  53. package/providers/commercetools/src/providers/cart.provider.ts +84 -8
  54. package/providers/commercetools/src/providers/category.provider.ts +244 -0
  55. package/providers/commercetools/src/providers/index.ts +7 -0
  56. package/providers/commercetools/src/providers/inventory.provider.ts +31 -14
  57. package/providers/commercetools/src/providers/price.provider.ts +74 -18
  58. package/providers/commercetools/src/providers/product.provider.ts +19 -15
  59. package/providers/commercetools/src/providers/search.provider.ts +9 -7
  60. package/providers/commercetools/src/schema/capabilities.schema.ts +2 -1
  61. package/providers/commercetools/src/schema/configuration.schema.ts +1 -1
  62. package/providers/commercetools/src/test/cart.provider.spec.ts +119 -0
  63. package/providers/commercetools/src/test/category.provider.spec.ts +180 -0
  64. package/providers/commercetools/src/test/price.provider.spec.ts +80 -0
  65. package/providers/commercetools/src/test/product.provider.spec.ts +29 -14
  66. package/providers/commercetools/src/test/search.provider.spec.ts +51 -9
  67. package/providers/commercetools/src/test/test-utils.ts +35 -0
  68. package/providers/commercetools/tsconfig.lib.json +1 -1
  69. package/providers/fake/jest.config.ts +10 -0
  70. package/providers/fake/src/core/initialize.ts +15 -1
  71. package/providers/fake/src/index.ts +2 -9
  72. package/providers/fake/src/providers/cart.provider.ts +74 -15
  73. package/providers/fake/src/providers/category.provider.ts +152 -0
  74. package/providers/fake/src/providers/index.ts +8 -0
  75. package/providers/fake/src/providers/inventory.provider.ts +23 -9
  76. package/providers/fake/src/providers/price.provider.ts +46 -6
  77. package/providers/fake/src/providers/search.provider.ts +13 -4
  78. package/providers/fake/src/schema/capabilities.schema.ts +4 -2
  79. package/providers/fake/src/schema/configuration.schema.ts +5 -0
  80. package/providers/fake/src/test/cart.provider.spec.ts +126 -0
  81. package/providers/fake/src/test/category.provider.spec.ts +134 -0
  82. package/providers/fake/src/test/price.provider.spec.ts +80 -0
  83. package/providers/fake/src/test/test-utils.ts +42 -0
  84. package/providers/fake/tsconfig.json +4 -0
  85. package/providers/fake/tsconfig.lib.json +3 -1
  86. package/providers/fake/tsconfig.spec.json +16 -0
  87. package/trpc/package.json +1 -2
  88. package/trpc/src/client.ts +1 -3
  89. package/trpc/src/integration.spec.ts +16 -10
  90. package/trpc/src/transparent-client.spec.ts +23 -17
  91. package/tsconfig.base.json +2 -0
  92. package/core/src/decorators/trpc.decorators.ts +0 -144
  93. package/otel/src/sdk.ts +0 -57
@@ -0,0 +1,244 @@
1
+ import { CategoryProvider, Cache, Category, Session, CategoryIdentifier, PaginationOptions, createPaginatedResponseSchema, CategoryQueryById, CategoryQueryBySlug, CategoryQueryForBreadcrumb, CategoryQueryForChildCategories, CategoryQueryForTopCategories } from "@reactionary/core";
2
+ import z from "zod";
3
+ import { CommercetoolsConfiguration } from "../schema/configuration.schema";
4
+ import { CommercetoolsClient } from "../core/client";
5
+ import { ByProjectKeyCategoriesRequestBuilder, CategoryPagedQueryResponse, Category as CTCategory } from "@commercetools/platform-sdk";
6
+ import { traced } from "@reactionary/otel";
7
+
8
+ export class CommercetoolsCategoryProvider<
9
+ T extends Category = Category,
10
+ > extends CategoryProvider<T> {
11
+
12
+ protected config: CommercetoolsConfiguration;
13
+
14
+ constructor(config: CommercetoolsConfiguration, schema: z.ZodType<T>, cache: Cache) {
15
+ super(schema, cache);
16
+
17
+ this.config = config;
18
+ }
19
+
20
+ protected getClient(session: Session): ByProjectKeyCategoriesRequestBuilder {
21
+ const client = new CommercetoolsClient(this.config).getClient(session.identity.token).withProjectKey({ projectKey: this.config.projectKey }).categories();
22
+ return client;
23
+ }
24
+
25
+ /**
26
+ * Look it up by the category ID (key in commercetools), and if not there, return a placeholder.
27
+ * @param id
28
+ * @param session
29
+ * @returns
30
+ */
31
+ @traced()
32
+ public override async getById(payload: CategoryQueryById, session: Session): Promise<T> {
33
+ const client = this.getClient(session);
34
+ try {
35
+ const response = await client.withKey({ key: payload.id.key }).get().execute();
36
+ return this.parseSingle(response.body, session);
37
+ } catch (error) {
38
+ console.error(`Error fetching category by ID ${payload.id.key}:`, error);
39
+ const dummyCategory = this.newModel();
40
+ dummyCategory.meta.placeholder = true;
41
+ dummyCategory.identifier = { key: payload.id.key };
42
+ return dummyCategory;
43
+ }
44
+ }
45
+
46
+ /**
47
+ * Resolve the category by slug, in the users current locale.
48
+ * @param slug
49
+ * @param session
50
+ * @returns
51
+ */
52
+ @traced()
53
+ public override async getBySlug(payload: CategoryQueryBySlug, session: Session): Promise<T | null> {
54
+ const client = this.getClient(session);
55
+ try {
56
+ const response = await client.get({
57
+ queryArgs: {
58
+ where: `slug(${session.languageContext.locale}=:slug)`,
59
+ 'var.slug': payload.slug,
60
+ storeProjection: session.storeIdentifier.key ,
61
+ limit: 1,
62
+ withTotal: false,
63
+ }
64
+ }).execute();
65
+ if (response.body.results.length === 0) {
66
+ return null;
67
+ }
68
+ return this.parseSingle(response.body.results[0], session);
69
+ } catch (error) {
70
+ console.error(`Error fetching category by slug:`, error);
71
+ return null;
72
+ }
73
+ }
74
+
75
+ /**
76
+ * Returns the breadcrumb path to the category, i.e. all parents up to the root.
77
+ * The returned order is from root to leaf.
78
+ * @param id
79
+ * @param session
80
+ * @returns
81
+ */
82
+ @traced()
83
+ public override async getBreadcrumbPathToCategory(payload: CategoryQueryForBreadcrumb, session: Session): Promise<T[]> {
84
+ const client = this.getClient(session);
85
+ const path: T[] = [];
86
+ try {
87
+ const response = await client.withKey({ key: payload.id.key }).get({
88
+ queryArgs: {
89
+ expand: 'ancestors[*]'
90
+ }
91
+ }).execute();
92
+
93
+ const category = this.parseSingle(response.body, session);
94
+ for(const anc of response.body.ancestors || []) {
95
+ if (anc.obj) {
96
+ const parsedAnc = this.parseSingle(anc.obj, session);
97
+ path.push(parsedAnc);
98
+ }
99
+ };
100
+ path.push(category);
101
+ } catch (error) {
102
+ console.error(`Error fetching category path for ${payload.id.key}:`, error);
103
+ }
104
+ return path;
105
+ }
106
+
107
+
108
+ /**
109
+ * Returns a page of child categories for the given parent category ID.
110
+ * You must provide the pagination options to control the size of the result set.
111
+ * If you expect your frontend will load many many categories, consider adding a "load more" button, or lazy load the next page.
112
+ *
113
+ * This is cached by ID + page number + page size + locale + store
114
+ * @param id
115
+ * @param paginationOptions
116
+ * @param session
117
+ * @returns
118
+ */
119
+ @traced()
120
+ public override async findChildCategories(payload: CategoryQueryForChildCategories, session: Session) {
121
+
122
+ // ok, so for Commercetools we can't actually query by the parents key, so we have to first resolve the key to an ID, then query by that.
123
+ // This is a bit of a pain, but we can cache the result of the first lookup for a short period to mitigate it.
124
+
125
+ const client = this.getClient(session);
126
+
127
+ try {
128
+ const parentCategory = await client.withKey({ key: payload.parentId.key }).get().execute();
129
+
130
+ // it is true, we could just do a withKey and get the children directly, but that would not be paginated.
131
+ // So we do it the hard way, and query by parent ID instead.
132
+ // This also means we can sort the results, which is nice.
133
+
134
+ const response = await client.get({
135
+ queryArgs: {
136
+ where: 'parent(id = :parentId)',
137
+ 'var.parentId': parentCategory.body.id,
138
+ limit: payload.paginationOptions.pageSize,
139
+ offset: (payload.paginationOptions.pageNumber - 1) * payload.paginationOptions.pageSize,
140
+ sort: 'orderHint asc',
141
+ storeProjection: session.storeIdentifier.key ,
142
+ },
143
+ })
144
+ .execute();
145
+
146
+ const result = this.parsePaginatedResult(response.body, session);
147
+ result.meta = {
148
+ cache: { hit: false, key: this.generateCacheKeyPaginatedResult('children-of-' + payload.parentId.key, result, session) },
149
+ placeholder: false
150
+ };
151
+ return result;
152
+ } catch (error) {
153
+ console.error(`Error fetching category path for ${payload.parentId.key}:`, error);
154
+ }
155
+ return createPaginatedResponseSchema(this.schema).parse({});
156
+ }
157
+
158
+ @traced()
159
+ public override async findTopCategories(payload: CategoryQueryForTopCategories, session: Session) {
160
+
161
+ const client = this.getClient(session);
162
+ try {
163
+ const response = await client.get({
164
+ queryArgs: {
165
+ where: 'parent is not defined',
166
+ limit: payload.paginationOptions.pageSize,
167
+ offset: (payload.paginationOptions.pageNumber - 1) * payload.paginationOptions.pageSize,
168
+ sort: 'orderHint asc',
169
+ storeProjection: session.storeIdentifier.key ,
170
+ },
171
+ })
172
+ .execute();
173
+
174
+ const result = this.parsePaginatedResult(response.body, session);
175
+ result.meta = {
176
+ cache: { hit: false, key: this.generateCacheKeyPaginatedResult('top', result, session) },
177
+ placeholder: false
178
+ };
179
+ return result;
180
+ } catch (error) {
181
+ console.error(`Error fetching category top categories:`, error);
182
+ }
183
+ return createPaginatedResponseSchema(this.schema).parse({});
184
+ }
185
+
186
+
187
+
188
+
189
+ /**
190
+ * Handler for parsing a response from a remote provider and converting it
191
+ * into the typed domain model.
192
+ */
193
+ @traced()
194
+ protected override parseSingle(_body: unknown, session: Session): T {
195
+ const body = _body as CTCategory;
196
+ const languageContext = session.languageContext;
197
+
198
+ const model = this.newModel();
199
+
200
+ model.identifier = { key: body.key! };
201
+ model.name = body.name[languageContext.locale] || 'No Name';
202
+ model.slug = body.slug ? (body.slug[languageContext.locale] || '') : '';
203
+ model.text = body.description ? (body.description[languageContext.locale] || '') : '';
204
+
205
+ model.parentCategory = body.parent && body.parent.obj && body.parent.obj?.key ? { key: body.parent.obj.key } : undefined;
206
+
207
+ model.images = (body.assets || []).filter(asset => asset.sources.length > 0).filter(x => x.sources[0].contentType?.startsWith('image/')).map( (asset) => {
208
+ return {
209
+ sourceUrl: asset.sources[0].uri,
210
+ altText: asset.description?.[languageContext.locale] || asset.name[languageContext.locale] || '',
211
+ height: asset.sources[0].dimensions?.h || 0,
212
+ width: asset.sources[0].dimensions?.w || 0,
213
+ }
214
+ });
215
+
216
+ model.meta = {
217
+ cache: { hit: false, key: this.generateCacheKeySingle(model.identifier, session) },
218
+ placeholder: false
219
+ };
220
+
221
+ return this.assert(model);
222
+ }
223
+
224
+
225
+ @traced()
226
+ protected override parsePaginatedResult(_body: unknown, session: Session) {
227
+ const body = _body as CategoryPagedQueryResponse;
228
+
229
+ const items = body.results.map(x => this.parseSingle(x, session));
230
+
231
+ const result = createPaginatedResponseSchema(this.schema).parse({
232
+ meta: {
233
+ cache: { hit: false, key: 'unknown' },
234
+ placeholder: false
235
+ },
236
+ pageNumber: Math.floor(body.offset / body.count) + 1,
237
+ pageSize: body.count,
238
+ totalCount: body.total || 0,
239
+ totalPages: Math.ceil((body.total ?? 0) / body.count),
240
+ items: items
241
+ });
242
+ return result;
243
+ }
244
+ }
@@ -0,0 +1,7 @@
1
+ export * from './cart.provider';
2
+ export * from './category.provider';
3
+ export * from './identity.provider';
4
+ export * from './inventory.provider';
5
+ export * from './price.provider';
6
+ export * from './product.provider';
7
+ export * from './search.provider';
@@ -4,11 +4,12 @@ import {
4
4
  InventoryQuery,
5
5
  Session,
6
6
  Cache,
7
+ LanguageContext,
7
8
  } from '@reactionary/core';
8
9
  import z from 'zod';
9
10
  import { CommercetoolsConfiguration } from '../schema/configuration.schema';
10
11
  import { CommercetoolsClient } from '../core/client';
11
-
12
+ import { InventoryEntry as CTInventory } from '@commercetools/platform-sdk';
12
13
  export class CommercetoolsInventoryProvider<
13
14
  T extends Inventory = Inventory
14
15
  > extends InventoryProvider<T> {
@@ -37,24 +38,40 @@ export class CommercetoolsInventoryProvider<
37
38
  .inventory()
38
39
  .get({
39
40
  queryArgs: {
40
- where: 'sku=:sku',
41
- 'var.sku': payload.sku,
41
+ where: `sku=${payload.sku}`,
42
42
  },
43
43
  })
44
44
  .execute();
45
45
 
46
- const base = this.newModel();
46
+ return this.parseSingle(remote.body, session);
47
+ }
48
+
49
+ protected override parseSingle(_body: unknown, session: Session): T {
50
+ const body = _body as CTInventory;
51
+ const model = this.newModel();
47
52
 
48
- if (remote.body.results.length > 0) {
49
- const inv = remote.body.results[0];
50
- base.quantity = inv.availableQuantity;
51
- }
53
+ model.identifier = {
54
+ sku: { key: body.sku },
55
+ channelId: {
56
+ key: body.supplyChannel?.id || 'online'
57
+ },
58
+ };
59
+ model.sku = body.sku;
60
+ model.quantity = body.availableQuantity;
52
61
 
53
- base.meta = {
54
- cache: { hit: false, key: payload.sku },
55
- placeholder: false
56
- };
62
+ if (model.quantity > 0 ) {
63
+ model.status = 'inStock';
64
+ } else {
65
+ model.status = 'outOfStock';
66
+ }
57
67
 
58
- return this.assert(base);
68
+ model.meta = {
69
+ cache: { hit: false, key: this.generateCacheKeySingle(model.identifier, session) },
70
+ placeholder: false
71
+ };
72
+
73
+ return this.assert(model);
59
74
  }
60
- }
75
+
76
+
77
+ }
@@ -1,8 +1,8 @@
1
- import { Price, PriceProvider, PriceQueryBySku, Session, Cache } from '@reactionary/core';
1
+ import { Price, PriceProvider, PriceQueryBySku, Session, Cache, Currency, TieredPriceSchema, MonetaryAmountSchema, TieredPrice } from '@reactionary/core';
2
2
  import z from 'zod';
3
3
  import { CommercetoolsConfiguration } from '../schema/configuration.schema';
4
4
  import { CommercetoolsClient } from '../core/client';
5
-
5
+ import { StandalonePrice as CTPrice } from '@commercetools/platform-sdk';
6
6
  export class CommercetoolsPriceProvider<
7
7
  T extends Price = Price
8
8
  > extends PriceProvider<T> {
@@ -14,48 +14,104 @@ export class CommercetoolsPriceProvider<
14
14
  this.config = config;
15
15
  }
16
16
 
17
+ public getClient(session: Session) {
18
+ return new CommercetoolsClient(this.config).getClient(session.identity?.token).withProjectKey({ projectKey: this.config.projectKey }).standalonePrices();
19
+ }
20
+
21
+
22
+ public override async getBySKUs(payload: PriceQueryBySku[], session: Session): Promise<T[]> {
23
+
24
+ const client = this.getClient(session);
25
+
26
+ // AND (validFrom is not defined OR validFrom <= now()) AND (validUntil is not defined OR validUntil >= now())
27
+ const queryArgs = {
28
+ where: 'sku in (:skus)',
29
+ 'var.skus': payload.map(p => p.sku.key),
30
+ };
31
+
32
+
33
+ const response = await client.get({
34
+ queryArgs,
35
+ }).execute();
36
+
37
+ const result = [];
38
+ for(const p of payload) {
39
+ const matched = response.body.results.filter(x => x.sku === p.sku.key && x.value.currencyCode === session.languageContext.currencyCode);
40
+ if (matched && matched.length > 0) {
41
+ result.push(this.parseSingle(matched[0], session));
42
+ } else {
43
+ result.push(this.getEmptyPriceResult(p.sku.key, session.languageContext.currencyCode ));
44
+ }
45
+ }
46
+
47
+ return result;
48
+ }
49
+
50
+
17
51
  public override async getBySKU(
18
52
  payload: PriceQueryBySku,
19
53
  session: Session
20
54
  ): Promise<T> {
21
- const client = new CommercetoolsClient(this.config).getClient(
22
- session.identity?.token
23
- );
24
-
55
+ const client = this.getClient(session);
56
+ // AND (validFrom is not defined OR validFrom <= now()) AND (validUntil is not defined OR validUntil >= now())
25
57
  const queryArgs = {
26
58
  where: 'sku=:sku',
27
59
  'var.sku': payload.sku.key,
28
60
  };
29
61
 
30
62
  const remote = await client
31
- .withProjectKey({ projectKey: this.config.projectKey })
32
- .standalonePrices()
33
63
  .get({
34
64
  queryArgs,
35
65
  })
36
66
  .execute();
37
67
 
68
+
69
+ const matched = remote.body.results.filter(x => x.value.currencyCode === session.languageContext.currencyCode);
70
+ if (matched && matched.length > 0) {
71
+ return this.parseSingle(matched[0], session);
72
+ }
73
+ return this.getEmptyPriceResult(payload.sku.key, session.languageContext.currencyCode );
74
+ }
75
+
76
+
77
+
78
+ protected override parseSingle(_body: unknown, session: Session): T {
79
+ const body = _body as CTPrice;
80
+
38
81
  const base = this.newModel();
39
-
40
- if (remote.body.results.length > 0) {
41
- const matched = remote.body.results[0];
42
- base.value = {
43
- cents: matched.value.centAmount,
44
- currency: 'USD',
45
- };
82
+
83
+ base.unitPrice = {
84
+ value: (body.value.centAmount / 100),
85
+ currency: body.value.currencyCode as Currency,
86
+ };
87
+
88
+ if (body.tiers && body.tiers.length > 0) {
89
+ const p = body.tiers.map(x => {
90
+ const tp: TieredPrice = TieredPriceSchema.parse({});
91
+ tp.minimumQuantity = x.minimumQuantity;
92
+ tp.price = {
93
+ value: (x.value.centAmount / 100),
94
+ currency: x.value.currencyCode as Currency,
95
+ };
96
+ return tp;
97
+ });
98
+ base.tieredPrices = p;
46
99
  }
47
100
 
48
101
  base.identifier = {
49
102
  sku: {
50
- key: payload.sku.key
103
+ key: body.sku
51
104
  }
52
105
  };
53
106
 
54
107
  base.meta = {
55
- cache: { hit: false, key: payload.sku.key },
108
+ cache: { hit: false, key: this.generateCacheKeySingle(base.identifier, session) },
56
109
  placeholder: false
57
110
  };
58
111
 
59
112
  return this.assert(base);
113
+
60
114
  }
61
- }
115
+
116
+
117
+ }
@@ -22,31 +22,31 @@ export class CommercetoolsProductProvider<
22
22
  this.config = config;
23
23
  }
24
24
 
25
+ public getClient(session: Session) {
26
+ return new CommercetoolsClient(this.config).getClient(session.identity?.token).withProjectKey({ projectKey: this.config.projectKey }).productProjections();
27
+ }
28
+
25
29
  public override async getById(
26
30
  payload: ProductQueryById,
27
- _session: Session
31
+ session: Session
28
32
  ): Promise<T> {
29
- const client = new CommercetoolsClient(this.config).createAnonymousClient();
33
+ const client = this.getClient(session);
30
34
 
31
35
  const remote = await client
32
- .withProjectKey({ projectKey: this.config.projectKey })
33
- .productProjections()
34
36
  .withId({ ID: payload.id })
35
37
  .get()
36
38
  .execute();
37
39
 
38
- return this.parse(remote.body);
40
+ return this.parseSingle(remote.body, session);
39
41
  }
40
42
 
41
43
  public override async getBySlug(
42
44
  payload: ProductQueryBySlug,
43
- _session: Session
45
+ session: Session
44
46
  ): Promise<T> {
45
- const client = new CommercetoolsClient(this.config).createAnonymousClient();
47
+ const client = this.getClient(session);
46
48
 
47
49
  const remote = await client
48
- .withProjectKey({ projectKey: this.config.projectKey })
49
- .productProjections()
50
50
  .get({
51
51
  queryArgs: {
52
52
  where: 'slug(en-US = :slug)',
@@ -59,18 +59,19 @@ export class CommercetoolsProductProvider<
59
59
  throw new Error(`Product with slug '${payload.slug}' not found`);
60
60
  }
61
61
 
62
- return this.parse(remote.body.results[0]);
62
+ return this.parseSingle(remote.body.results[0], session);
63
63
  }
64
64
 
65
- protected parse(data: ProductProjection): T {
65
+ protected override parseSingle(dataIn: unknown, session: Session): T {
66
+ const data = dataIn as ProductProjection;
66
67
  const base = this.newModel();
67
68
 
68
69
  base.identifier = { key: data.id };
69
- base.name = data.name['en-US'];
70
- base.slug = data.slug['en-US'];
70
+ base.name = data.name[session.languageContext.locale];
71
+ base.slug = data.slug[session.languageContext.locale];
71
72
 
72
73
  if (data.description) {
73
- base.description = data.description['en-US'];
74
+ base.description = data.description[session.languageContext.locale];
74
75
  }
75
76
 
76
77
  if (data.masterVariant.images && data.masterVariant.images.length > 0) {
@@ -91,10 +92,13 @@ export class CommercetoolsProductProvider<
91
92
  }
92
93
 
93
94
  base.meta = {
94
- cache: { hit: false, key: data.id },
95
+ cache: { hit: false, key: this.generateCacheKeySingle(base.identifier, session) },
95
96
  placeholder: false
96
97
  };
97
98
 
98
99
  return this.assert(base);
99
100
  }
101
+
102
+
103
+
100
104
  }
@@ -23,7 +23,7 @@ export class CommercetoolsSearchProvider<
23
23
 
24
24
  public override async queryByTerm(
25
25
  payload: SearchQueryByTerm,
26
- _session: Session
26
+ session: Session
27
27
  ): Promise<T> {
28
28
  const client = new CommercetoolsClient(this.config).createAnonymousClient();
29
29
 
@@ -34,16 +34,16 @@ export class CommercetoolsSearchProvider<
34
34
  .get({
35
35
  queryArgs: {
36
36
  limit: payload.search.pageSize,
37
- offset: payload.search.pageSize * payload.search.page,
38
- ['text.en-US']: payload.search.term,
37
+ offset: (payload.search.page - 1) * payload.search.pageSize,
38
+ [`text.${session.languageContext.locale}`]: payload.search.term,
39
39
  },
40
40
  })
41
41
  .execute();
42
42
 
43
- return this.parseSearchResult(remote, payload);
43
+ return this.parseSearchResult(remote, payload, session);
44
44
  }
45
45
 
46
- protected parseSearchResult(remote: unknown, payload: SearchQueryByTerm): T {
46
+ protected parseSearchResult(remote: unknown, payload: SearchQueryByTerm, session: Session): T {
47
47
  const result = this.newModel();
48
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 } };
49
49
 
@@ -52,8 +52,8 @@ export class CommercetoolsSearchProvider<
52
52
  for (const p of remoteData.body.results) {
53
53
  const product: SearchResultProduct = {
54
54
  identifier: { key: p.id },
55
- name: p.name['en-US'],
56
- slug: p.slug?.['en-US'] || p.id,
55
+ name: p.name[session.languageContext.locale] || p.id,
56
+ slug: p.slug?.[session.languageContext.locale] || p.id,
57
57
  image: p.masterVariant.images?.[0]?.url || 'https://placehold.co/400'
58
58
  };
59
59
 
@@ -68,4 +68,6 @@ export class CommercetoolsSearchProvider<
68
68
 
69
69
  return this.assert(result);
70
70
  }
71
+
72
+
71
73
  }
@@ -7,7 +7,8 @@ export const CommercetoolsCapabilitiesSchema = CapabilitiesSchema.pick({
7
7
  identity: true,
8
8
  cart: true,
9
9
  inventory: true,
10
- price: true
10
+ price: true,
11
+ category: true,
11
12
  }).partial();
12
13
 
13
14
  export type CommercetoolsCapabilities = z.infer<typeof CommercetoolsCapabilitiesSchema>;
@@ -8,4 +8,4 @@ export const CommercetoolsConfigurationSchema = z.looseInterface({
8
8
  clientSecret: z.string()
9
9
  });
10
10
 
11
- export type CommercetoolsConfiguration = z.infer<typeof CommercetoolsConfigurationSchema>;
11
+ export type CommercetoolsConfiguration = z.infer<typeof CommercetoolsConfigurationSchema>;