@reactionary/provider-commercetools 0.3.15 → 0.3.17

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/client.js CHANGED
@@ -10,12 +10,14 @@ import {
10
10
  import * as crypto from "crypto";
11
11
  import createDebug from "debug";
12
12
  import { RequestContextTokenCache } from "./token-cache.js";
13
+ import { CommercetoolsSessionSchema } from "../schema/session.schema.js";
13
14
  const debug = createDebug("reactionary:commercetools");
15
+ const PROVIDER_SESSION_KEY = "COMMERCETOOLS_PROVIDER";
14
16
  class CommercetoolsAPI {
15
17
  constructor(config, context) {
16
18
  this.config = config;
17
19
  this.context = context;
18
- this.cache = new RequestContextTokenCache(this.context);
20
+ this.tokenCache = new RequestContextTokenCache(this.context, PROVIDER_SESSION_KEY);
19
21
  }
20
22
  async getClient() {
21
23
  if (!this.client) {
@@ -29,6 +31,77 @@ class CommercetoolsAPI {
29
31
  }
30
32
  return this.adminClient;
31
33
  }
34
+ getSessionData() {
35
+ return this.context.session[PROVIDER_SESSION_KEY] ? this.context.session[PROVIDER_SESSION_KEY] : CommercetoolsSessionSchema.parse({});
36
+ }
37
+ setSessionData(sessionData) {
38
+ const existingData = this.context.session[PROVIDER_SESSION_KEY];
39
+ this.context.session[PROVIDER_SESSION_KEY] = {
40
+ ...existingData,
41
+ ...sessionData
42
+ };
43
+ }
44
+ /**
45
+ * Only caches it pr session for now...... but still better than every call
46
+ * @param key
47
+ * @returns
48
+ */
49
+ async resolveChannelIdByKey(key) {
50
+ const sessionData = this.getSessionData();
51
+ const cacheKey = `___channel_${key}`;
52
+ const cachedValue = sessionData[cacheKey];
53
+ if (cachedValue) {
54
+ if (debug.enabled) {
55
+ debug(`Resolved channel ${key} from cache`);
56
+ }
57
+ return cachedValue + "";
58
+ }
59
+ const client = await this.getAdminClient();
60
+ const response = await client.withProjectKey({ projectKey: this.config.projectKey }).channels().withKey({ key }).get().execute();
61
+ const channel = response.body;
62
+ this.setSessionData({
63
+ cacheKey: channel.id
64
+ });
65
+ if (debug.enabled) {
66
+ debug(`Resolved channel ${key} from API and cached it`);
67
+ }
68
+ return channel.id;
69
+ }
70
+ /**
71
+ * Only caches it pr session for now...... but still better than every call
72
+ * @param key
73
+ * @returns
74
+ */
75
+ async resolveChannelIdByRole(role) {
76
+ const sessionData = this.getSessionData();
77
+ const cacheKey = `___channel_role_${role}`;
78
+ const cachedValue = sessionData[cacheKey];
79
+ if (cachedValue) {
80
+ if (debug.enabled) {
81
+ debug(`Resolved channel ${role} from cache`);
82
+ }
83
+ return cachedValue + "";
84
+ }
85
+ const client = await this.getAdminClient();
86
+ const response = await client.withProjectKey({ projectKey: this.config.projectKey }).channels().get({
87
+ queryArgs: {
88
+ where: `roles contains any (:role)`,
89
+ "var.role": role
90
+ }
91
+ }).execute();
92
+ const channels = response.body;
93
+ if (channels.results.length === 0) {
94
+ throw new Error(`No channel found with role ${role}`);
95
+ }
96
+ const channel = channels.results[0];
97
+ this.setSessionData({
98
+ [cacheKey]: channel.id
99
+ });
100
+ if (debug.enabled) {
101
+ debug(`Resolved channel ${role} from API and cached it`);
102
+ }
103
+ return channel.id;
104
+ }
32
105
  async createAdminClient() {
33
106
  let builder = this.createBaseClientBuilder();
34
107
  builder = builder.withAnonymousSessionFlow({
@@ -42,11 +115,11 @@ class CommercetoolsAPI {
42
115
  return createApiBuilderFromCtpClient(builder.build());
43
116
  }
44
117
  async createClient() {
45
- let session = await this.cache.get();
118
+ let session = await this.tokenCache.get();
46
119
  const isNewSession = !session || !session.refreshToken;
47
120
  if (isNewSession) {
48
121
  await this.becomeGuest();
49
- session = await this.cache.get();
122
+ session = await this.tokenCache.get();
50
123
  }
51
124
  let builder = this.createBaseClientBuilder();
52
125
  builder = builder.withRefreshTokenFlow({
@@ -57,7 +130,7 @@ class CommercetoolsAPI {
57
130
  host: this.config.authUrl,
58
131
  projectKey: this.config.projectKey,
59
132
  refreshToken: session?.refreshToken || "",
60
- tokenCache: this.cache
133
+ tokenCache: this.tokenCache
61
134
  });
62
135
  return createApiBuilderFromCtpClient(builder.build());
63
136
  }
@@ -92,7 +165,7 @@ class CommercetoolsAPI {
92
165
  clientSecret: this.config.clientSecret,
93
166
  user: { username, password }
94
167
  },
95
- tokenCache: this.cache,
168
+ tokenCache: this.tokenCache,
96
169
  scopes: this.config.scopes
97
170
  });
98
171
  const loginClient = createApiBuilderFromCtpClient(loginBuilder.build());
@@ -111,7 +184,7 @@ class CommercetoolsAPI {
111
184
  });
112
185
  }
113
186
  async logout() {
114
- await this.cache.set({ token: "", refreshToken: "", expirationTime: 0 });
187
+ await this.tokenCache.set({ token: "", refreshToken: "", expirationTime: 0 });
115
188
  const identity = {
116
189
  type: "Anonymous"
117
190
  };
@@ -119,7 +192,7 @@ class CommercetoolsAPI {
119
192
  }
120
193
  // FIXME: This can fail if the short-lived access token has expired. In other words, probably missing a token refresh.
121
194
  async introspect() {
122
- const session = await this.cache.get();
195
+ const session = await this.tokenCache.get();
123
196
  if (!session || !session.token) {
124
197
  const identity = {
125
198
  type: "Anonymous"
@@ -191,7 +264,7 @@ class CommercetoolsAPI {
191
264
  }
192
265
  );
193
266
  const result = await response.json();
194
- this.cache.set({
267
+ this.tokenCache.set({
195
268
  expirationTime: Date.now() + Number(result.expires_in) * 1e3 - 5 * 60 * 1e3,
196
269
  token: result.access_token,
197
270
  refreshToken: result.refresh_token
@@ -235,5 +308,6 @@ class CommercetoolsAPI {
235
308
  }
236
309
  }
237
310
  export {
238
- CommercetoolsAPI
311
+ CommercetoolsAPI,
312
+ PROVIDER_SESSION_KEY
239
313
  };
@@ -18,7 +18,8 @@ import {
18
18
  CommercetoolsProfileProvider,
19
19
  CommercetoolsStoreProvider,
20
20
  CommercetoolsProductReviewsProvider,
21
- CommercetoolsProductAssociationsProvider
21
+ CommercetoolsProductAssociationsProvider,
22
+ CommercetoolsProductListProvider
22
23
  } from "../providers/index.js";
23
24
  import { CommercetoolsAPI } from "./client.js";
24
25
  function withCommercetoolsCapabilities(configuration, capabilities) {
@@ -59,6 +60,14 @@ function withCommercetoolsCapabilities(configuration, capabilities) {
59
60
  commercetoolsApi
60
61
  );
61
62
  }
63
+ if (caps.productList) {
64
+ client.productList = new CommercetoolsProductListProvider(
65
+ config,
66
+ cache,
67
+ context,
68
+ commercetoolsApi
69
+ );
70
+ }
62
71
  if (caps.productReviews) {
63
72
  client.productReviews = new CommercetoolsProductReviewsProvider(
64
73
  config,
@@ -1,12 +1,12 @@
1
1
  import { CommercetoolsSessionSchema } from "../schema/session.schema.js";
2
- const PROVIDER_COMMERCETOOLS_SESSION_KEY = "PROVIDER_COMMERCETOOLS";
3
2
  class RequestContextTokenCache {
4
- constructor(context) {
3
+ constructor(context, sessionProviderKey) {
5
4
  this.context = context;
5
+ this.sessionProviderKey = sessionProviderKey;
6
6
  }
7
7
  async get(tokenCacheOptions) {
8
8
  const session = CommercetoolsSessionSchema.parse(
9
- this.context.session[PROVIDER_COMMERCETOOLS_SESSION_KEY] || {}
9
+ this.context.session[this.sessionProviderKey] || {}
10
10
  );
11
11
  if (!session) {
12
12
  return {
@@ -23,15 +23,14 @@ class RequestContextTokenCache {
23
23
  }
24
24
  async set(cache, tokenCacheOptions) {
25
25
  const session = CommercetoolsSessionSchema.parse(
26
- this.context.session[PROVIDER_COMMERCETOOLS_SESSION_KEY] || {}
26
+ this.context.session[this.sessionProviderKey] || {}
27
27
  );
28
- this.context.session[PROVIDER_COMMERCETOOLS_SESSION_KEY] = session;
28
+ this.context.session[this.sessionProviderKey] = session;
29
29
  session.refreshToken = cache.refreshToken;
30
30
  session.token = cache.token;
31
31
  session.expirationTime = cache.expirationTime;
32
32
  }
33
33
  }
34
34
  export {
35
- PROVIDER_COMMERCETOOLS_SESSION_KEY,
36
35
  RequestContextTokenCache
37
36
  };
package/package.json CHANGED
@@ -1,10 +1,10 @@
1
1
  {
2
2
  "name": "@reactionary/provider-commercetools",
3
- "version": "0.3.15",
3
+ "version": "0.3.17",
4
4
  "main": "index.js",
5
5
  "types": "src/index.d.ts",
6
6
  "dependencies": {
7
- "@reactionary/core": "0.3.15",
7
+ "@reactionary/core": "0.3.17",
8
8
  "debug": "^4.4.3",
9
9
  "zod": "4.1.9",
10
10
  "@commercetools/ts-client": "^4.2.1",
@@ -5,6 +5,7 @@ export * from "./inventory.provider.js";
5
5
  export * from "./order-search.provider.js";
6
6
  export * from "./price.provider.js";
7
7
  export * from "./product-associations.provider.js";
8
+ export * from "./product-list.provider.js";
8
9
  export * from "./product.provider.js";
9
10
  export * from "./product-reviews.provider.js";
10
11
  export * from "./profile.provider.js";
@@ -23,8 +23,7 @@ class CommercetoolsInventoryProvider extends InventoryProvider {
23
23
  async getBySKU(payload) {
24
24
  const client = await this.getClient();
25
25
  try {
26
- const channel = await client.channels().withKey({ key: payload.fulfilmentCenter.key }).get().execute();
27
- const channelId = channel.body.id;
26
+ const channelId = await this.commercetools.resolveChannelIdByKey(payload.fulfilmentCenter.key);
28
27
  const remote = await client.inventory().get({
29
28
  queryArgs: {
30
29
  where: "sku=:sku AND supplyChannel(id=:channel)",
@@ -66,6 +65,10 @@ class CommercetoolsInventoryProvider extends InventoryProvider {
66
65
  }
67
66
  __decorateClass([
68
67
  Reactionary({
68
+ cache: true,
69
+ cacheTimeToLiveInSeconds: 300,
70
+ currencyDependentCaching: false,
71
+ localeDependentCaching: false,
69
72
  inputSchema: InventoryQueryBySKUSchema,
70
73
  outputSchema: InventorySchema
71
74
  })
@@ -25,7 +25,12 @@ class CommercetoolsPriceProvider extends PriceProvider {
25
25
  }
26
26
  async getCustomerPrice(payload) {
27
27
  const client = await this.getClient();
28
- const priceChannelId = "ee6e75e9-c9ab-4e2f-85f1-d8c734d0cb86";
28
+ let priceChannelId;
29
+ if (this.config.customerPriceChannelKey) {
30
+ priceChannelId = await this.commercetools.resolveChannelIdByKey(this.config.customerPriceChannelKey);
31
+ } else {
32
+ priceChannelId = await this.commercetools.resolveChannelIdByRole("Primary");
33
+ }
29
34
  const response = await client.productProjections().get({
30
35
  queryArgs: {
31
36
  staged: false,
@@ -46,7 +51,12 @@ class CommercetoolsPriceProvider extends PriceProvider {
46
51
  }
47
52
  async getListPrice(payload) {
48
53
  const client = await this.getClient();
49
- const priceChannelId = "ee6e75e9-c9ab-4e2f-85f1-d8c734d0cb86";
54
+ let priceChannelId;
55
+ if (this.config.listPriceChannelKey) {
56
+ priceChannelId = await this.commercetools.resolveChannelIdByKey(this.config.listPriceChannelKey);
57
+ } else {
58
+ priceChannelId = await this.commercetools.resolveChannelIdByRole("Primary");
59
+ }
50
60
  const response = await client.productProjections().get({
51
61
  queryArgs: {
52
62
  staged: false,
@@ -79,8 +89,12 @@ class CommercetoolsPriceProvider extends PriceProvider {
79
89
  value: price.value.centAmount / 100,
80
90
  currency: price.value.currencyCode
81
91
  };
92
+ let isOnSale = false;
82
93
  if (options.includeDiscounts) {
83
94
  const discountedPrice = price.discounted?.value || price.value;
95
+ if (price.discounted) {
96
+ isOnSale = true;
97
+ }
84
98
  unitPrice = {
85
99
  value: discountedPrice.centAmount / 100,
86
100
  currency: price.value.currencyCode
@@ -94,23 +108,11 @@ class CommercetoolsPriceProvider extends PriceProvider {
94
108
  const result = {
95
109
  identifier,
96
110
  tieredPrices: [],
97
- unitPrice
111
+ unitPrice,
112
+ onSale: isOnSale
98
113
  };
99
114
  return result;
100
115
  }
101
- async getChannels() {
102
- const adminClient = await this.commercetools.getAdminClient();
103
- const offerPriceChannelPromise = adminClient.withProjectKey({ projectKey: this.config.projectKey }).channels().withKey({ key: "Offer Price" }).get().execute();
104
- const listPriceChannelPromise = adminClient.withProjectKey({ projectKey: this.config.projectKey }).channels().withKey({ key: "List Price" }).get().execute();
105
- const [offerChannel, listChannel] = await Promise.all([
106
- offerPriceChannelPromise,
107
- listPriceChannelPromise
108
- ]);
109
- return {
110
- offer: offerChannel.body.id,
111
- list: listChannel.body.id
112
- };
113
- }
114
116
  }
115
117
  __decorateClass([
116
118
  Reactionary({
@@ -0,0 +1,389 @@
1
+ var __defProp = Object.defineProperty;
2
+ var __getOwnPropDesc = Object.getOwnPropertyDescriptor;
3
+ var __decorateClass = (decorators, target, key, kind) => {
4
+ var result = kind > 1 ? void 0 : kind ? __getOwnPropDesc(target, key) : target;
5
+ for (var i = decorators.length - 1, decorator; i >= 0; i--)
6
+ if (decorator = decorators[i])
7
+ result = (kind ? decorator(target, key, result) : decorator(result)) || result;
8
+ if (kind && result)
9
+ __defProp(target, key, result);
10
+ return result;
11
+ };
12
+ import {
13
+ error,
14
+ ProductListItemMutationCreateSchema,
15
+ ProductListItemMutationDeleteSchema,
16
+ ProductListItemMutationUpdateSchema,
17
+ ProductListItemPaginatedResultsSchema,
18
+ ProductListItemQuerySchema,
19
+ ProductListItemSchema,
20
+ ProductListMutationCreateSchema,
21
+ ProductListMutationDeleteSchema,
22
+ ProductListMutationUpdateSchema,
23
+ ProductListPaginatedResultsSchema,
24
+ ProductListProvider,
25
+ ProductListQueryByIdSchema,
26
+ ProductListQuerySchema,
27
+ ProductListSchema,
28
+ Reactionary,
29
+ success
30
+ } from "@reactionary/core";
31
+ class CommercetoolsProductListProvider extends ProductListProvider {
32
+ constructor(config, cache, context, commercetools) {
33
+ super(cache, context);
34
+ this.config = config;
35
+ this.commercetools = commercetools;
36
+ }
37
+ async getClient() {
38
+ const client = await this.commercetools.getClient();
39
+ return client.withProjectKey({ projectKey: this.config.projectKey }).me();
40
+ }
41
+ async getById(payload) {
42
+ try {
43
+ const client = await this.getClient();
44
+ const response = await client.shoppingLists().withId({ ID: payload.identifier.key }).get().execute();
45
+ return success(this.parseSingle(response.body));
46
+ } catch (err) {
47
+ if (err.statusCode === 404) {
48
+ return error({
49
+ type: "NotFound",
50
+ identifier: payload
51
+ });
52
+ } else {
53
+ throw err;
54
+ }
55
+ }
56
+ }
57
+ async queryLists(payload) {
58
+ if (this.context.session.identityContext?.identity?.type !== "Registered") {
59
+ return success({
60
+ items: [],
61
+ pageNumber: 1,
62
+ pageSize: payload.search.paginationOptions.pageSize,
63
+ totalCount: 0,
64
+ totalPages: 0,
65
+ identifier: payload.search
66
+ });
67
+ }
68
+ const client = await this.getClient();
69
+ const response = await client.shoppingLists().get({
70
+ queryArgs: {
71
+ where: `custom(fields(listType=:listType))`,
72
+ sort: "createdAt desc",
73
+ "var.listType": payload.search.listType,
74
+ limit: payload.search.paginationOptions.pageSize,
75
+ offset: (payload.search.paginationOptions.pageNumber - 1) * payload.search.paginationOptions.pageSize
76
+ }
77
+ }).execute();
78
+ return success({
79
+ items: response.body.results.map((list) => this.parseSingle(list)),
80
+ pageNumber: payload.search.paginationOptions.pageNumber,
81
+ pageSize: payload.search.paginationOptions.pageSize,
82
+ totalCount: response.body.total || 0,
83
+ totalPages: Math.ceil((response.body.total || 0) / payload.search.paginationOptions.pageSize),
84
+ identifier: payload.search
85
+ });
86
+ }
87
+ async addList(mutation) {
88
+ const client = await this.getClient();
89
+ const localeString = this.getLocaleString();
90
+ if (this.context.session.identityContext?.identity?.type !== "Registered") {
91
+ return error({
92
+ type: "InvalidInput",
93
+ error: "Only registered users can have product lists"
94
+ });
95
+ }
96
+ const draft = {
97
+ name: { [localeString]: mutation.list.name },
98
+ description: mutation.list.description ? { [localeString]: mutation.list.description } : void 0,
99
+ custom: {
100
+ type: {
101
+ key: "reactionaryShoppingList",
102
+ typeId: "type"
103
+ },
104
+ fields: {
105
+ listType: mutation.list.type,
106
+ imageUrl: mutation.list.image ? mutation.list.image.sourceUrl : void 0,
107
+ publishedDate: mutation.list.publishDate ? new Date(mutation.list.publishDate) : void 0,
108
+ published: mutation.list.published || true
109
+ }
110
+ }
111
+ };
112
+ const response = await client.shoppingLists().post({
113
+ body: draft
114
+ }).execute();
115
+ return success(this.parseSingle(response.body));
116
+ }
117
+ async updateList(mutation) {
118
+ const client = await this.getClient();
119
+ const actions = [];
120
+ const localeString = this.getLocaleString();
121
+ if (mutation.name) {
122
+ actions.push({
123
+ action: "changeName",
124
+ name: { [localeString]: mutation.name }
125
+ });
126
+ }
127
+ if (mutation.description !== void 0) {
128
+ actions.push({
129
+ action: "setDescription",
130
+ description: mutation.description ? { [localeString]: mutation.description } : void 0
131
+ });
132
+ }
133
+ if (mutation.published) {
134
+ actions.push({
135
+ action: "setCustomField",
136
+ name: "published",
137
+ value: mutation.published
138
+ });
139
+ }
140
+ if (mutation.publishDate) {
141
+ actions.push({
142
+ action: "setCustomField",
143
+ name: "publishedDate",
144
+ value: new Date(mutation.publishDate)
145
+ });
146
+ }
147
+ if (mutation.image) {
148
+ actions.push({
149
+ action: "setCustomField",
150
+ name: "imageUrl",
151
+ value: mutation.image.sourceUrl
152
+ });
153
+ }
154
+ const update = {
155
+ version: 0,
156
+ // The auto-correcting middleware will deal with the version
157
+ actions
158
+ };
159
+ const response = await client.shoppingLists().withId({ ID: mutation.list.key }).post({ body: update }).execute();
160
+ return success(this.parseSingle(response.body));
161
+ }
162
+ async deleteList(mutation) {
163
+ const client = await this.getClient();
164
+ const newestVersion = await client.shoppingLists().withId({ ID: mutation.list.key }).get().execute().then((response) => response.body.version);
165
+ await client.shoppingLists().withId({ ID: mutation.list.key }).delete({
166
+ queryArgs: {
167
+ version: newestVersion
168
+ }
169
+ }).execute();
170
+ return success(void 0);
171
+ }
172
+ async queryListItems(query) {
173
+ const client = await this.getClient();
174
+ const response = await client.shoppingLists().withId({ ID: query.search.list.key }).get({
175
+ queryArgs: { expand: "lineItems[*].variant" }
176
+ }).execute();
177
+ const items = response.body.lineItems.map((lineItem) => this.parseProductListItem(query.search.list, lineItem));
178
+ return success({
179
+ items: items.slice((query.search.paginationOptions.pageNumber - 1) * query.search.paginationOptions.pageSize, query.search.paginationOptions.pageNumber * query.search.paginationOptions.pageSize),
180
+ identifier: query.search,
181
+ pageNumber: query.search.paginationOptions.pageNumber,
182
+ pageSize: query.search.paginationOptions.pageSize,
183
+ totalCount: items.length,
184
+ totalPages: Math.ceil(items.length / query.search.paginationOptions.pageSize)
185
+ });
186
+ }
187
+ async addItem(mutation) {
188
+ const client = await this.getClient();
189
+ const lineItemDraft = {
190
+ action: "addLineItem",
191
+ sku: mutation.listItem.variant.sku,
192
+ quantity: mutation.listItem.quantity,
193
+ custom: {
194
+ type: {
195
+ key: "reactionaryShoppingListItem",
196
+ typeId: "type"
197
+ },
198
+ fields: {
199
+ notes: mutation.listItem.notes || "",
200
+ order: mutation.listItem.order || 1
201
+ }
202
+ }
203
+ };
204
+ const update = {
205
+ version: 0,
206
+ // The auto-correcting middleware will deal with the version
207
+ actions: [lineItemDraft]
208
+ };
209
+ const response = await client.shoppingLists().withId({ ID: mutation.list.key }).post({ body: update, queryArgs: { expand: "lineItems[*].variant" } }).execute();
210
+ const lastItem = response.body.lineItems[response.body.lineItems.length - 1];
211
+ if (lastItem.variant?.sku !== mutation.listItem.variant.sku) {
212
+ throw new Error("The added line item is not the last item in the list, cannot reliably determine the identifier of the added item");
213
+ }
214
+ return success(this.parseProductListItem(mutation.list, lastItem));
215
+ }
216
+ async deleteItem(mutation) {
217
+ const client = await this.getClient();
218
+ const update = {
219
+ version: 0,
220
+ // The auto-correcting middleware will deal with the version
221
+ actions: [{
222
+ action: "removeLineItem",
223
+ lineItemId: mutation.listItem.key
224
+ }]
225
+ };
226
+ await client.shoppingLists().withId({ ID: mutation.listItem.list.key }).post({ body: update }).execute();
227
+ return success(void 0);
228
+ }
229
+ async updateItem(mutation) {
230
+ const client = await this.getClient();
231
+ const actions = [];
232
+ if (mutation.quantity !== void 0) {
233
+ actions.push({
234
+ action: "changeLineItemQuantity",
235
+ lineItemId: mutation.listItem.key,
236
+ quantity: mutation.quantity
237
+ });
238
+ }
239
+ if (mutation.notes !== void 0) {
240
+ actions.push({
241
+ action: "setLineItemCustomField",
242
+ lineItemId: mutation.listItem.key,
243
+ name: "notes",
244
+ value: mutation.notes
245
+ });
246
+ }
247
+ if (mutation.order !== void 0) {
248
+ actions.push({
249
+ action: "setLineItemCustomField",
250
+ lineItemId: mutation.listItem.key,
251
+ name: "order",
252
+ value: mutation.order
253
+ });
254
+ }
255
+ const update = {
256
+ version: 0,
257
+ actions
258
+ };
259
+ const response = await client.shoppingLists().withId({ ID: mutation.listItem.list.key }).post({ body: update, queryArgs: { expand: "lineItems[*].variant" } }).execute();
260
+ const updatedLineItem = response.body.lineItems.find((item) => item.id === mutation.listItem.key);
261
+ if (!updatedLineItem) {
262
+ throw new Error("Failed to find the updated line item in response");
263
+ }
264
+ return success(this.parseProductListItem(mutation.listItem.list, updatedLineItem));
265
+ }
266
+ /**
267
+ * It is not clear to me, why i'd want my CUSTOMER defined resources to be localized, since that means, if he changes visual language,
268
+ * the names of his lists will disappear, since they are not translated. But maybe there are usecases for this, so we should support it, but default to english.
269
+ **/
270
+ getLocaleString() {
271
+ return "en";
272
+ }
273
+ parseSingle(list) {
274
+ const localeString = this.getLocaleString();
275
+ const listType = list.custom?.fields["listType"] || "favorite";
276
+ const image = list.custom?.fields["imageUrl"];
277
+ const published = list.custom?.fields["published"] && true;
278
+ const publishDateDate = list.custom?.fields["publishedDate"];
279
+ let publishedDate;
280
+ if (publishDateDate) {
281
+ publishedDate = new Date(publishDateDate).toISOString();
282
+ }
283
+ return {
284
+ identifier: {
285
+ listType,
286
+ key: list.id
287
+ },
288
+ type: listType,
289
+ name: list.name[localeString] || "Unnamed List",
290
+ description: list.description?.[localeString] || "",
291
+ published,
292
+ publishDate: publishedDate,
293
+ image: {
294
+ sourceUrl: image || "",
295
+ altText: list.name[localeString] || "List Image"
296
+ }
297
+ };
298
+ }
299
+ parseProductListItem(listIdentifier, lineItem) {
300
+ const localeString = this.getLocaleString();
301
+ return {
302
+ identifier: {
303
+ list: listIdentifier,
304
+ key: lineItem.id
305
+ },
306
+ variant: {
307
+ sku: lineItem.variant?.sku || ""
308
+ },
309
+ quantity: lineItem.quantity,
310
+ notes: lineItem.custom?.fields["notes"] || "",
311
+ order: lineItem.custom?.fields["order"] || 1
312
+ // Commercetools doesn't have explicit ordering
313
+ };
314
+ }
315
+ }
316
+ __decorateClass([
317
+ Reactionary({
318
+ cache: true,
319
+ cacheTimeToLiveInSeconds: 300,
320
+ currencyDependentCaching: false,
321
+ localeDependentCaching: false,
322
+ inputSchema: ProductListQueryByIdSchema,
323
+ outputSchema: ProductListSchema
324
+ })
325
+ ], CommercetoolsProductListProvider.prototype, "getById", 1);
326
+ __decorateClass([
327
+ Reactionary({
328
+ cache: true,
329
+ cacheTimeToLiveInSeconds: 300,
330
+ currencyDependentCaching: false,
331
+ localeDependentCaching: false,
332
+ inputSchema: ProductListQuerySchema,
333
+ outputSchema: ProductListPaginatedResultsSchema
334
+ })
335
+ ], CommercetoolsProductListProvider.prototype, "queryLists", 1);
336
+ __decorateClass([
337
+ Reactionary({
338
+ cache: false,
339
+ // Mutations should not be cached
340
+ inputSchema: ProductListMutationCreateSchema,
341
+ outputSchema: ProductListSchema
342
+ })
343
+ ], CommercetoolsProductListProvider.prototype, "addList", 1);
344
+ __decorateClass([
345
+ Reactionary({
346
+ cache: false,
347
+ inputSchema: ProductListMutationUpdateSchema,
348
+ outputSchema: ProductListSchema
349
+ })
350
+ ], CommercetoolsProductListProvider.prototype, "updateList", 1);
351
+ __decorateClass([
352
+ Reactionary({
353
+ cache: false,
354
+ inputSchema: ProductListMutationDeleteSchema
355
+ })
356
+ ], CommercetoolsProductListProvider.prototype, "deleteList", 1);
357
+ __decorateClass([
358
+ Reactionary({
359
+ cache: true,
360
+ cacheTimeToLiveInSeconds: 300,
361
+ currencyDependentCaching: false,
362
+ localeDependentCaching: false,
363
+ inputSchema: ProductListItemQuerySchema,
364
+ outputSchema: ProductListItemPaginatedResultsSchema
365
+ })
366
+ ], CommercetoolsProductListProvider.prototype, "queryListItems", 1);
367
+ __decorateClass([
368
+ Reactionary({
369
+ cache: false,
370
+ inputSchema: ProductListItemMutationCreateSchema,
371
+ outputSchema: ProductListItemSchema
372
+ })
373
+ ], CommercetoolsProductListProvider.prototype, "addItem", 1);
374
+ __decorateClass([
375
+ Reactionary({
376
+ cache: false,
377
+ inputSchema: ProductListItemMutationDeleteSchema
378
+ })
379
+ ], CommercetoolsProductListProvider.prototype, "deleteItem", 1);
380
+ __decorateClass([
381
+ Reactionary({
382
+ cache: false,
383
+ inputSchema: ProductListItemMutationUpdateSchema,
384
+ outputSchema: ProductListItemSchema
385
+ })
386
+ ], CommercetoolsProductListProvider.prototype, "updateItem", 1);
387
+ export {
388
+ CommercetoolsProductListProvider
389
+ };
@@ -4,6 +4,7 @@ const CommercetoolsCapabilitiesSchema = CapabilitiesSchema.pick({
4
4
  productSearch: true,
5
5
  productAssociations: true,
6
6
  productReviews: true,
7
+ productList: true,
7
8
  identity: true,
8
9
  cart: true,
9
10
  checkout: true,
@@ -7,8 +7,12 @@ const CommercetoolsConfigurationSchema = z.looseObject({
7
7
  clientId: z.string(),
8
8
  clientSecret: z.string(),
9
9
  scopes: z.array(z.string()).default(() => []),
10
+ adminClientId: z.string().optional(),
11
+ adminClientSecret: z.string().optional(),
10
12
  paymentMethods: PaymentMethodSchema.array().optional().default(() => []),
11
- facetFieldsForSearch: z.array(z.string()).default(() => [])
13
+ facetFieldsForSearch: z.array(z.string()).default(() => []),
14
+ listPriceChannelKey: z.string().optional(),
15
+ customerPriceChannelKey: z.string().optional()
12
16
  });
13
17
  export {
14
18
  CommercetoolsConfigurationSchema
@@ -3,15 +3,31 @@ import { type ApiRoot } from '@commercetools/platform-sdk';
3
3
  import type { CommercetoolsConfiguration } from '../schema/configuration.schema.js';
4
4
  import { type AnonymousIdentity, type GuestIdentity, type RegisteredIdentity, type RequestContext } from '@reactionary/core';
5
5
  import { RequestContextTokenCache } from './token-cache.js';
6
+ import { type CommercetoolsSession } from '../schema/session.schema.js';
7
+ export declare const PROVIDER_SESSION_KEY = "COMMERCETOOLS_PROVIDER";
6
8
  export declare class CommercetoolsAPI {
7
9
  protected config: CommercetoolsConfiguration;
8
10
  protected context: RequestContext;
9
- protected cache: RequestContextTokenCache;
11
+ protected tokenCache: RequestContextTokenCache;
10
12
  protected client: Promise<ApiRoot> | undefined;
11
13
  protected adminClient: Promise<ApiRoot> | undefined;
12
14
  constructor(config: CommercetoolsConfiguration, context: RequestContext);
13
15
  getClient(): Promise<ApiRoot>;
14
16
  getAdminClient(): Promise<ApiRoot>;
17
+ getSessionData(): CommercetoolsSession;
18
+ setSessionData(sessionData: Partial<CommercetoolsSession>): void;
19
+ /**
20
+ * Only caches it pr session for now...... but still better than every call
21
+ * @param key
22
+ * @returns
23
+ */
24
+ resolveChannelIdByKey(key: string): Promise<string>;
25
+ /**
26
+ * Only caches it pr session for now...... but still better than every call
27
+ * @param key
28
+ * @returns
29
+ */
30
+ resolveChannelIdByRole(role: string): Promise<string>;
15
31
  protected createAdminClient(): Promise<ApiRoot>;
16
32
  protected createClient(): Promise<ApiRoot>;
17
33
  register(username: string, password: string): Promise<{
@@ -1,9 +1,9 @@
1
1
  import type { TokenCache, TokenCacheOptions, TokenStore } from "@commercetools/ts-client";
2
2
  import type { RequestContext } from "@reactionary/core";
3
- export declare const PROVIDER_COMMERCETOOLS_SESSION_KEY = "PROVIDER_COMMERCETOOLS";
4
3
  export declare class RequestContextTokenCache implements TokenCache {
5
4
  protected context: RequestContext;
6
- constructor(context: RequestContext);
5
+ protected sessionProviderKey: string;
6
+ constructor(context: RequestContext, sessionProviderKey: string);
7
7
  get(tokenCacheOptions?: TokenCacheOptions): Promise<TokenStore | undefined>;
8
8
  set(cache: TokenStore, tokenCacheOptions?: TokenCacheOptions): Promise<void>;
9
9
  }
@@ -5,6 +5,7 @@ export * from './inventory.provider.js';
5
5
  export * from './order-search.provider.js';
6
6
  export * from './price.provider.js';
7
7
  export * from './product-associations.provider.js';
8
+ export * from './product-list.provider.js';
8
9
  export * from './product.provider.js';
9
10
  export * from './product-reviews.provider.js';
10
11
  export * from './profile.provider.js';
@@ -12,8 +12,4 @@ export declare class CommercetoolsPriceProvider extends PriceProvider {
12
12
  protected parseSingle(_body: unknown, options?: {
13
13
  includeDiscounts: boolean;
14
14
  }): Price;
15
- protected getChannels(): Promise<{
16
- offer: string;
17
- list: string;
18
- }>;
19
15
  }
@@ -0,0 +1,27 @@
1
+ import type { ShoppingList, ShoppingListLineItem } from '@commercetools/platform-sdk';
2
+ import type { Cache, ProductList, ProductListIdentifier, ProductListItem, ProductListItemMutationCreate, ProductListItemMutationDelete, ProductListItemMutationUpdate, ProductListItemPaginatedResult, ProductListItemsQuery, ProductListMutationCreate, ProductListMutationDelete, ProductListMutationUpdate, ProductListPaginatedResult, ProductListQuery, ProductListQueryById, RequestContext, Result } from '@reactionary/core';
3
+ import { ProductListProvider } from '@reactionary/core';
4
+ import type { CommercetoolsAPI } from '../core/client.js';
5
+ import type { CommercetoolsConfiguration } from '../schema/configuration.schema.js';
6
+ export declare class CommercetoolsProductListProvider extends ProductListProvider {
7
+ protected config: CommercetoolsConfiguration;
8
+ protected commercetools: CommercetoolsAPI;
9
+ constructor(config: CommercetoolsConfiguration, cache: Cache, context: RequestContext, commercetools: CommercetoolsAPI);
10
+ protected getClient(): Promise<import("@commercetools/platform-sdk").ByProjectKeyMeRequestBuilder>;
11
+ getById(payload: ProductListQueryById): Promise<Result<ProductList>>;
12
+ queryLists(payload: ProductListQuery): Promise<Result<ProductListPaginatedResult>>;
13
+ addList(mutation: ProductListMutationCreate): Promise<Result<ProductList>>;
14
+ updateList(mutation: ProductListMutationUpdate): Promise<Result<ProductList>>;
15
+ deleteList(mutation: ProductListMutationDelete): Promise<Result<void>>;
16
+ queryListItems(query: ProductListItemsQuery): Promise<Result<ProductListItemPaginatedResult>>;
17
+ addItem(mutation: ProductListItemMutationCreate): Promise<Result<ProductListItem>>;
18
+ deleteItem(mutation: ProductListItemMutationDelete): Promise<Result<void>>;
19
+ updateItem(mutation: ProductListItemMutationUpdate): Promise<Result<ProductListItem>>;
20
+ /**
21
+ * It is not clear to me, why i'd want my CUSTOMER defined resources to be localized, since that means, if he changes visual language,
22
+ * the names of his lists will disappear, since they are not translated. But maybe there are usecases for this, so we should support it, but default to english.
23
+ **/
24
+ protected getLocaleString(): string;
25
+ protected parseSingle(list: ShoppingList): ProductList;
26
+ protected parseProductListItem(listIdentifier: ProductListIdentifier, lineItem: ShoppingListLineItem): ProductListItem;
27
+ }
@@ -13,6 +13,7 @@ export declare const CommercetoolsCapabilitiesSchema: z.ZodObject<{
13
13
  productSearch: z.ZodOptional<z.ZodBoolean>;
14
14
  productAssociations: z.ZodOptional<z.ZodBoolean>;
15
15
  productReviews: z.ZodOptional<z.ZodBoolean>;
16
+ productList: z.ZodOptional<z.ZodBoolean>;
16
17
  orderSearch: z.ZodOptional<z.ZodBoolean>;
17
18
  }, z.core.$loose>;
18
19
  export type CommercetoolsCapabilities = z.infer<typeof CommercetoolsCapabilitiesSchema>;
@@ -6,6 +6,8 @@ export declare const CommercetoolsConfigurationSchema: z.ZodObject<{
6
6
  clientId: z.ZodString;
7
7
  clientSecret: z.ZodString;
8
8
  scopes: z.ZodDefault<z.ZodArray<z.ZodString>>;
9
+ adminClientId: z.ZodOptional<z.ZodString>;
10
+ adminClientSecret: z.ZodOptional<z.ZodString>;
9
11
  paymentMethods: z.ZodDefault<z.ZodOptional<z.ZodArray<z.ZodObject<{
10
12
  identifier: z.ZodObject<{
11
13
  method: z.ZodString;
@@ -22,5 +24,7 @@ export declare const CommercetoolsConfigurationSchema: z.ZodObject<{
22
24
  isPunchOut: z.ZodBoolean;
23
25
  }, z.core.$loose>>>>;
24
26
  facetFieldsForSearch: z.ZodDefault<z.ZodArray<z.ZodString>>;
27
+ listPriceChannelKey: z.ZodOptional<z.ZodString>;
28
+ customerPriceChannelKey: z.ZodOptional<z.ZodString>;
25
29
  }, z.core.$loose>;
26
30
  export type CommercetoolsConfiguration = z.infer<typeof CommercetoolsConfigurationSchema>;
@@ -4,3 +4,4 @@ export declare const CommercetoolsSessionSchema: z.ZodObject<{
4
4
  refreshToken: z.ZodOptional<z.ZodString>;
5
5
  expirationTime: z.ZodDefault<z.ZodNumber>;
6
6
  }, z.core.$loose>;
7
+ export type CommercetoolsSession = z.infer<typeof CommercetoolsSessionSchema>;