@reactionary/commercetools 0.6.3

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 (104) hide show
  1. package/README.md +11 -0
  2. package/capabilities/cart.capability.js +324 -0
  3. package/capabilities/category.capability.js +198 -0
  4. package/capabilities/checkout.capability.js +374 -0
  5. package/capabilities/identity.capability.js +95 -0
  6. package/capabilities/index.js +15 -0
  7. package/capabilities/inventory.capability.js +63 -0
  8. package/capabilities/order-search.capability.js +106 -0
  9. package/capabilities/order.capability.js +48 -0
  10. package/capabilities/price.capability.js +114 -0
  11. package/capabilities/product-associations.capability.js +120 -0
  12. package/capabilities/product-list.capability.js +393 -0
  13. package/capabilities/product-reviews.capability.js +193 -0
  14. package/capabilities/product-search.capability.js +297 -0
  15. package/capabilities/product.capability.js +103 -0
  16. package/capabilities/profile.capability.js +337 -0
  17. package/capabilities/store.capability.js +51 -0
  18. package/core/capability-descriptors.js +274 -0
  19. package/core/client.js +318 -0
  20. package/core/initialize.js +47 -0
  21. package/core/initialize.types.js +8 -0
  22. package/core/token-cache.js +36 -0
  23. package/factories/cart/cart.factory.js +110 -0
  24. package/factories/category/category.factory.js +40 -0
  25. package/factories/checkout/checkout-initializer-overrides.example.js +67 -0
  26. package/factories/checkout/checkout.factory.js +235 -0
  27. package/factories/identity/identity.factory.js +14 -0
  28. package/factories/inventory/inventory.factory.js +30 -0
  29. package/factories/order/order.factory.js +114 -0
  30. package/factories/order-search/order-search.factory.js +68 -0
  31. package/factories/price/price.factory.js +52 -0
  32. package/factories/product/product-capability-custom-method-only.example.js +38 -0
  33. package/factories/product/product-capability-schema-signature-extension.example.js +46 -0
  34. package/factories/product/product-factory-baseline.example.js +10 -0
  35. package/factories/product/product-factory-schema-and-parse-extension.example.js +25 -0
  36. package/factories/product/product-factory-schema-extension.example.js +18 -0
  37. package/factories/product/product-initializer-factory-extension.example.js +33 -0
  38. package/factories/product/product.factory.js +151 -0
  39. package/factories/product/utils.example.js +8 -0
  40. package/factories/product-associations/product-associations.factory.js +63 -0
  41. package/factories/product-list/product-list.factory.js +65 -0
  42. package/factories/product-reviews/product-reviews.factory.js +42 -0
  43. package/factories/product-search/product-search.factory.js +114 -0
  44. package/factories/profile/profile.factory.js +62 -0
  45. package/factories/store/store.factory.js +29 -0
  46. package/index.js +28 -0
  47. package/package.json +17 -0
  48. package/schema/capabilities.schema.js +46 -0
  49. package/schema/commercetools.schema.js +30 -0
  50. package/schema/configuration.schema.js +19 -0
  51. package/schema/session.schema.js +9 -0
  52. package/src/capabilities/cart.capability.d.ts +34 -0
  53. package/src/capabilities/category.capability.d.ts +47 -0
  54. package/src/capabilities/checkout.capability.d.ts +39 -0
  55. package/src/capabilities/identity.capability.d.ts +14 -0
  56. package/src/capabilities/index.d.ts +15 -0
  57. package/src/capabilities/inventory.capability.d.ts +13 -0
  58. package/src/capabilities/order-search.capability.d.ts +13 -0
  59. package/src/capabilities/order.capability.d.ts +13 -0
  60. package/src/capabilities/price.capability.d.ts +18 -0
  61. package/src/capabilities/product-associations.capability.d.ts +17 -0
  62. package/src/capabilities/product-list.capability.d.ts +29 -0
  63. package/src/capabilities/product-reviews.capability.d.ts +18 -0
  64. package/src/capabilities/product-search.capability.d.ts +82 -0
  65. package/src/capabilities/product.capability.d.ts +17 -0
  66. package/src/capabilities/profile.capability.d.ts +30 -0
  67. package/src/capabilities/store.capability.d.ts +13 -0
  68. package/src/core/capability-descriptors.d.ts +16 -0
  69. package/src/core/client.d.ts +55 -0
  70. package/src/core/initialize.d.ts +5 -0
  71. package/src/core/initialize.types.d.ts +118 -0
  72. package/src/core/token-cache.d.ts +9 -0
  73. package/src/factories/cart/cart.factory.d.ts +14 -0
  74. package/src/factories/category/category.factory.d.ts +11 -0
  75. package/src/factories/checkout/checkout-initializer-overrides.example.d.ts +1 -0
  76. package/src/factories/checkout/checkout.factory.d.ts +18 -0
  77. package/src/factories/identity/identity.factory.d.ts +8 -0
  78. package/src/factories/inventory/inventory.factory.d.ts +9 -0
  79. package/src/factories/order/order.factory.d.ts +9 -0
  80. package/src/factories/order-search/order-search.factory.d.ts +11 -0
  81. package/src/factories/price/price.factory.d.ts +11 -0
  82. package/src/factories/product/product-capability-custom-method-only.example.d.ts +1 -0
  83. package/src/factories/product/product-capability-schema-signature-extension.example.d.ts +1 -0
  84. package/src/factories/product/product-factory-baseline.example.d.ts +1 -0
  85. package/src/factories/product/product-factory-schema-and-parse-extension.example.d.ts +1 -0
  86. package/src/factories/product/product-factory-schema-extension.example.d.ts +1 -0
  87. package/src/factories/product/product-initializer-factory-extension.example.d.ts +1 -0
  88. package/src/factories/product/product.factory.d.ts +18 -0
  89. package/src/factories/product/utils.example.d.ts +4 -0
  90. package/src/factories/product-associations/product-associations.factory.d.ts +15 -0
  91. package/src/factories/product-list/product-list.factory.d.ts +17 -0
  92. package/src/factories/product-reviews/product-reviews.factory.d.ts +14 -0
  93. package/src/factories/product-search/product-search.factory.d.ts +13 -0
  94. package/src/factories/profile/profile.factory.d.ts +11 -0
  95. package/src/factories/store/store.factory.d.ts +9 -0
  96. package/src/index.d.ts +28 -0
  97. package/src/schema/capabilities.schema.d.ts +104 -0
  98. package/src/schema/commercetools.schema.d.ts +30 -0
  99. package/src/schema/configuration.schema.d.ts +30 -0
  100. package/src/schema/session.schema.d.ts +7 -0
  101. package/src/test/client-builder-merge-extensions.example.d.ts +1 -0
  102. package/src/test/test-utils.d.ts +27 -0
  103. package/test/client-builder-merge-extensions.example.js +94 -0
  104. package/test/test-utils.js +27 -0
package/core/client.js ADDED
@@ -0,0 +1,318 @@
1
+ import { ClientBuilder } from "@commercetools/ts-client";
2
+ import {
3
+ createApiBuilderFromCtpClient
4
+ } from "@commercetools/platform-sdk";
5
+ import { randomUUID } from "crypto";
6
+ import {
7
+ AnonymousIdentitySchema,
8
+ RegisteredIdentitySchema
9
+ } from "@reactionary/core";
10
+ import * as crypto from "crypto";
11
+ import createDebug from "debug";
12
+ import { RequestContextTokenCache } from "./token-cache.js";
13
+ import { CommercetoolsSessionSchema } from "../schema/session.schema.js";
14
+ const debug = createDebug("reactionary:commercetools");
15
+ const PROVIDER_SESSION_KEY = "COMMERCETOOLS_PROVIDER";
16
+ class CommercetoolsAPI {
17
+ config;
18
+ context;
19
+ tokenCache;
20
+ client;
21
+ adminClient;
22
+ constructor(config, context) {
23
+ this.config = config;
24
+ this.context = context;
25
+ this.tokenCache = new RequestContextTokenCache(this.context, PROVIDER_SESSION_KEY);
26
+ }
27
+ async getClient() {
28
+ if (!this.client) {
29
+ this.client = this.createClient();
30
+ }
31
+ return this.client;
32
+ }
33
+ async getAdminClient() {
34
+ if (!this.adminClient) {
35
+ this.adminClient = this.createAdminClient();
36
+ }
37
+ return this.adminClient;
38
+ }
39
+ getSessionData() {
40
+ return this.context.session[PROVIDER_SESSION_KEY] ? this.context.session[PROVIDER_SESSION_KEY] : CommercetoolsSessionSchema.parse({});
41
+ }
42
+ setSessionData(sessionData) {
43
+ const existingData = this.context.session[PROVIDER_SESSION_KEY];
44
+ this.context.session[PROVIDER_SESSION_KEY] = {
45
+ ...existingData,
46
+ ...sessionData
47
+ };
48
+ }
49
+ /**
50
+ * Only caches it pr session for now...... but still better than every call
51
+ * @param key
52
+ * @returns
53
+ */
54
+ async resolveChannelIdByKey(key) {
55
+ const sessionData = this.getSessionData();
56
+ const cacheKey = `___channel_${key}`;
57
+ const cachedValue = sessionData[cacheKey];
58
+ if (cachedValue) {
59
+ if (debug.enabled) {
60
+ debug(`Resolved channel ${key} from cache`);
61
+ }
62
+ return cachedValue + "";
63
+ }
64
+ const client = await this.getAdminClient();
65
+ const response = await client.withProjectKey({ projectKey: this.config.projectKey }).channels().withKey({ key }).get().execute();
66
+ const channel = response.body;
67
+ this.setSessionData({
68
+ cacheKey: channel.id
69
+ });
70
+ if (debug.enabled) {
71
+ debug(`Resolved channel ${key} from API and cached it`);
72
+ }
73
+ return channel.id;
74
+ }
75
+ /**
76
+ * Only caches it pr session for now...... but still better than every call
77
+ * @param key
78
+ * @returns
79
+ */
80
+ async resolveChannelIdByRole(role) {
81
+ const sessionData = this.getSessionData();
82
+ const cacheKey = `___channel_role_${role}`;
83
+ const cachedValue = sessionData[cacheKey];
84
+ if (cachedValue) {
85
+ if (debug.enabled) {
86
+ debug(`Resolved channel ${role} from cache`);
87
+ }
88
+ return cachedValue + "";
89
+ }
90
+ const client = await this.getAdminClient();
91
+ const response = await client.withProjectKey({ projectKey: this.config.projectKey }).channels().get({
92
+ queryArgs: {
93
+ where: `roles contains any (:role)`,
94
+ "var.role": role
95
+ }
96
+ }).execute();
97
+ const channels = response.body;
98
+ if (channels.results.length === 0) {
99
+ throw new Error(`No channel found with role ${role}`);
100
+ }
101
+ const channel = channels.results[0];
102
+ this.setSessionData({
103
+ [cacheKey]: channel.id
104
+ });
105
+ if (debug.enabled) {
106
+ debug(`Resolved channel ${role} from API and cached it`);
107
+ }
108
+ return channel.id;
109
+ }
110
+ async createAdminClient() {
111
+ let builder = this.createBaseClientBuilder();
112
+ builder = builder.withAnonymousSessionFlow({
113
+ credentials: {
114
+ clientId: this.config.clientId,
115
+ clientSecret: this.config.clientSecret
116
+ },
117
+ host: this.config.authUrl,
118
+ projectKey: this.config.projectKey
119
+ });
120
+ return createApiBuilderFromCtpClient(builder.build());
121
+ }
122
+ async createClient() {
123
+ let session = await this.tokenCache.get();
124
+ const isNewSession = !session || !session.refreshToken;
125
+ if (isNewSession) {
126
+ await this.becomeGuest();
127
+ session = await this.tokenCache.get();
128
+ }
129
+ let builder = this.createBaseClientBuilder();
130
+ builder = builder.withRefreshTokenFlow({
131
+ credentials: {
132
+ clientId: this.config.clientId,
133
+ clientSecret: this.config.clientSecret
134
+ },
135
+ host: this.config.authUrl,
136
+ projectKey: this.config.projectKey,
137
+ refreshToken: session?.refreshToken || "",
138
+ tokenCache: this.tokenCache
139
+ });
140
+ return createApiBuilderFromCtpClient(builder.build());
141
+ }
142
+ async register(username, password) {
143
+ const registrationBuilder = this.createBaseClientBuilder().withAnonymousSessionFlow({
144
+ host: this.config.authUrl,
145
+ projectKey: this.config.projectKey,
146
+ credentials: {
147
+ clientId: this.config.clientId,
148
+ clientSecret: this.config.clientSecret
149
+ },
150
+ scopes: this.config.scopes
151
+ });
152
+ const registrationClient = createApiBuilderFromCtpClient(
153
+ registrationBuilder.build()
154
+ );
155
+ const registration = await registrationClient.withProjectKey({ projectKey: this.config.projectKey }).me().signup().post({
156
+ body: {
157
+ email: username,
158
+ password
159
+ }
160
+ }).execute();
161
+ const login = await this.login(username, password);
162
+ return login;
163
+ }
164
+ async login(username, password) {
165
+ const loginBuilder = this.createBaseClientBuilder().withPasswordFlow({
166
+ host: this.config.authUrl,
167
+ projectKey: this.config.projectKey,
168
+ credentials: {
169
+ clientId: this.config.clientId,
170
+ clientSecret: this.config.clientSecret,
171
+ user: { username, password }
172
+ },
173
+ tokenCache: this.tokenCache,
174
+ scopes: this.config.scopes
175
+ });
176
+ const loginClient = createApiBuilderFromCtpClient(loginBuilder.build());
177
+ const login = await loginClient.withProjectKey({ projectKey: this.config.projectKey }).me().login().post({
178
+ body: {
179
+ email: username,
180
+ password
181
+ }
182
+ }).execute();
183
+ const self = await loginClient.withProjectKey({ projectKey: this.config.projectKey }).me().get({}).execute();
184
+ return RegisteredIdentitySchema.parse({
185
+ type: "Registered",
186
+ id: {
187
+ userId: self.body.id
188
+ }
189
+ });
190
+ }
191
+ async logout() {
192
+ await this.tokenCache.set({ token: "", refreshToken: "", expirationTime: 0 });
193
+ const identity = {
194
+ type: "Anonymous"
195
+ };
196
+ return identity;
197
+ }
198
+ // FIXME: This can fail if the short-lived access token has expired. In other words, probably missing a token refresh.
199
+ async introspect() {
200
+ const session = await this.tokenCache.get();
201
+ if (!session || !session.token) {
202
+ const identity = {
203
+ type: "Anonymous"
204
+ };
205
+ return identity;
206
+ }
207
+ const authHeader = "Basic " + Buffer.from(
208
+ `${this.config.clientId}:${this.config.clientSecret}`
209
+ ).toString("base64");
210
+ const introspectionUrl = `${this.config.authUrl}/oauth/introspect`;
211
+ const response = await fetch(introspectionUrl, {
212
+ method: "POST",
213
+ headers: {
214
+ Authorization: authHeader,
215
+ "Content-Type": "application/x-www-form-urlencoded"
216
+ },
217
+ body: new URLSearchParams({
218
+ token: session.token
219
+ })
220
+ });
221
+ const body = await response.json();
222
+ if (!body) {
223
+ return AnonymousIdentitySchema.parse({});
224
+ }
225
+ const scopes = body.scope + "";
226
+ if (scopes.indexOf("anonymous_id") > -1) {
227
+ const s = scopes.split(" ");
228
+ const idScope = s.find((x) => x.startsWith("anonymous_id"));
229
+ const id = idScope?.split(":")[1] || "";
230
+ const identity = {
231
+ id: {
232
+ userId: id
233
+ },
234
+ type: "Guest"
235
+ };
236
+ return identity;
237
+ }
238
+ if (scopes.indexOf("customer_id") > -1) {
239
+ const s = scopes.split(" ");
240
+ const idScope = s.find((x) => x.startsWith("customer_id"));
241
+ const id = idScope?.split(":")[1] || "";
242
+ const identity = {
243
+ id: {
244
+ userId: id
245
+ },
246
+ type: "Registered"
247
+ };
248
+ return identity;
249
+ }
250
+ return {
251
+ type: "Anonymous"
252
+ };
253
+ }
254
+ async becomeGuest() {
255
+ const credentials = Buffer.from(
256
+ `${this.config.clientId}:${this.config.clientSecret}`
257
+ ).toString("base64");
258
+ const response = await fetch(
259
+ `${this.config.authUrl}/oauth/${this.config.projectKey}/anonymous/token?grant_type=client_credentials`,
260
+ {
261
+ method: "POST",
262
+ headers: {
263
+ Authorization: `Basic ${credentials}`,
264
+ "Content-Type": "application/x-www-form-urlencoded"
265
+ },
266
+ body: new URLSearchParams({
267
+ grant_type: "client_credentials"
268
+ })
269
+ }
270
+ );
271
+ const result = await response.json();
272
+ this.tokenCache.set({
273
+ expirationTime: Date.now() + Number(result.expires_in) * 1e3 - 5 * 60 * 1e3,
274
+ token: result.access_token,
275
+ refreshToken: result.refresh_token
276
+ });
277
+ }
278
+ createBaseClientBuilder() {
279
+ let builder = new ClientBuilder().withProjectKey(this.config.projectKey).withQueueMiddleware({
280
+ concurrency: 20
281
+ }).withConcurrentModificationMiddleware({
282
+ concurrentModificationHandlerFn: (version, request) => {
283
+ console.log(
284
+ `Concurrent modification error, retry with version ${version}`
285
+ );
286
+ const body = request.body;
287
+ body["version"] = version;
288
+ return Promise.resolve(body);
289
+ }
290
+ }).withHttpMiddleware({
291
+ retryConfig: {
292
+ backoff: true,
293
+ maxRetries: 3,
294
+ retryDelay: 500,
295
+ retryOnAbort: true,
296
+ retryCodes: [500, 429, 420],
297
+ maxDelay: 5e3
298
+ },
299
+ enableRetry: true,
300
+ includeResponseHeaders: true,
301
+ maskSensitiveHeaderData: false,
302
+ host: this.config.apiUrl,
303
+ httpClient: fetch
304
+ });
305
+ const correlationId = this.context.correlationId || "REACTIONARY-" + (typeof crypto !== "undefined" && "randomUUID" in crypto ? crypto.randomUUID() : randomUUID());
306
+ builder = builder.withCorrelationIdMiddleware({
307
+ generate: () => correlationId
308
+ });
309
+ if (debug.enabled) {
310
+ builder = builder.withLoggerMiddleware();
311
+ }
312
+ return builder;
313
+ }
314
+ }
315
+ export {
316
+ CommercetoolsAPI,
317
+ PROVIDER_SESSION_KEY
318
+ };
@@ -0,0 +1,47 @@
1
+ import {
2
+ CommercetoolsCapabilitiesSchema
3
+ } from "../schema/capabilities.schema.js";
4
+ import {
5
+ CommercetoolsConfigurationSchema
6
+ } from "../schema/configuration.schema.js";
7
+ import { CommercetoolsAPI } from "./client.js";
8
+ import {
9
+ capabilityDescriptors,
10
+ capabilityKeys
11
+ } from "./capability-descriptors.js";
12
+ import {
13
+ resolveCapabilityWithFactory
14
+ } from "./initialize.types.js";
15
+ function withCommercetoolsCapabilities(configuration, capabilities) {
16
+ return (cache, context) => {
17
+ const client = {};
18
+ const config = CommercetoolsConfigurationSchema.parse(configuration);
19
+ const caps = CommercetoolsCapabilitiesSchema.parse(capabilities);
20
+ const commercetoolsApi = new CommercetoolsAPI(config, context);
21
+ const buildCapabilityArgs = (factory) => ({
22
+ cache,
23
+ context,
24
+ config,
25
+ commercetoolsApi,
26
+ factory
27
+ });
28
+ for (const key of capabilityKeys) {
29
+ const descriptor = capabilityDescriptors[key];
30
+ if (!descriptor.isEnabled(caps)) {
31
+ continue;
32
+ }
33
+ client[key] = resolveCapabilityWithFactory(
34
+ descriptor.getOverride(capabilities),
35
+ {
36
+ factory: descriptor.createDefaultFactory(),
37
+ capability: descriptor.createDefaultCapability
38
+ },
39
+ buildCapabilityArgs
40
+ );
41
+ }
42
+ return client;
43
+ };
44
+ }
45
+ export {
46
+ withCommercetoolsCapabilities
47
+ };
@@ -0,0 +1,8 @@
1
+ function resolveCapabilityWithFactory(capability, defaults, buildCapabilityArgs) {
2
+ const factory = capability?.factory ?? defaults.factory;
3
+ const capabilityFactory = capability?.capability ?? defaults.capability;
4
+ return capabilityFactory(buildCapabilityArgs(factory));
5
+ }
6
+ export {
7
+ resolveCapabilityWithFactory
8
+ };
@@ -0,0 +1,36 @@
1
+ import { CommercetoolsSessionSchema } from "../schema/session.schema.js";
2
+ class RequestContextTokenCache {
3
+ constructor(context, sessionProviderKey) {
4
+ this.context = context;
5
+ this.sessionProviderKey = sessionProviderKey;
6
+ }
7
+ async get(tokenCacheOptions) {
8
+ const session = CommercetoolsSessionSchema.parse(
9
+ this.context.session[this.sessionProviderKey] || {}
10
+ );
11
+ if (!session) {
12
+ return {
13
+ refreshToken: void 0,
14
+ token: "",
15
+ expirationTime: (/* @__PURE__ */ new Date()).getTime()
16
+ };
17
+ }
18
+ return {
19
+ refreshToken: session.refreshToken,
20
+ token: session.token,
21
+ expirationTime: session.expirationTime
22
+ };
23
+ }
24
+ async set(cache, tokenCacheOptions) {
25
+ const session = CommercetoolsSessionSchema.parse(
26
+ this.context.session[this.sessionProviderKey] || {}
27
+ );
28
+ this.context.session[this.sessionProviderKey] = session;
29
+ session.refreshToken = cache.refreshToken;
30
+ session.token = cache.token;
31
+ session.expirationTime = cache.expirationTime;
32
+ }
33
+ }
34
+ export {
35
+ RequestContextTokenCache
36
+ };
@@ -0,0 +1,110 @@
1
+ import {
2
+ CartItemSchema
3
+ } from "@reactionary/core";
4
+ class CommercetoolsCartFactory {
5
+ cartSchema;
6
+ cartIdentifierSchema;
7
+ constructor(cartSchema, cartIdentifierSchema) {
8
+ this.cartSchema = cartSchema;
9
+ this.cartIdentifierSchema = cartIdentifierSchema;
10
+ }
11
+ parseCartIdentifier(_context, data) {
12
+ return this.cartIdentifierSchema.parse({
13
+ key: data.key || ""
14
+ });
15
+ }
16
+ parseCart(context, data) {
17
+ const identifier = this.parseCartIdentifier(context, {
18
+ key: data.id
19
+ });
20
+ const grandTotal = data.totalPrice.centAmount || 0;
21
+ const shippingTotal = data.shippingInfo?.price.centAmount || 0;
22
+ const productTotal = grandTotal - shippingTotal;
23
+ const taxTotal = data.taxedPrice?.totalTax?.centAmount || 0;
24
+ const discountTotal = data.discountOnTotalPrice?.discountedAmount.centAmount || 0;
25
+ const surchargeTotal = 0;
26
+ const currency = data.totalPrice.currencyCode;
27
+ const price = {
28
+ totalTax: {
29
+ value: taxTotal / 100,
30
+ currency
31
+ },
32
+ totalDiscount: {
33
+ value: discountTotal / 100,
34
+ currency
35
+ },
36
+ totalSurcharge: {
37
+ value: surchargeTotal / 100,
38
+ currency
39
+ },
40
+ totalShipping: {
41
+ value: shippingTotal / 100,
42
+ currency
43
+ },
44
+ totalProductPrice: {
45
+ value: productTotal / 100,
46
+ currency
47
+ },
48
+ grandTotal: {
49
+ value: grandTotal / 100,
50
+ currency
51
+ }
52
+ };
53
+ const items = [];
54
+ for (const lineItem of data.lineItems) {
55
+ items.push(this.parseCartItem(lineItem));
56
+ }
57
+ const result = {
58
+ identifier,
59
+ userId: {
60
+ userId: "???"
61
+ },
62
+ name: data.custom?.fields["name"] || "",
63
+ description: data.custom?.fields["description"] || "",
64
+ price,
65
+ items,
66
+ appliedPromotions: []
67
+ };
68
+ return this.cartSchema.parse(result);
69
+ }
70
+ parseCartItem(lineItem) {
71
+ const unitPrice = lineItem.price.value.centAmount;
72
+ const totalPrice = lineItem.totalPrice.centAmount || 0;
73
+ const totalDiscount = lineItem.price.discounted?.value.centAmount || 0;
74
+ const unitDiscount = totalDiscount / lineItem.quantity;
75
+ const currency = lineItem.price.value.currencyCode.toUpperCase();
76
+ return CartItemSchema.parse({
77
+ identifier: {
78
+ key: lineItem.id
79
+ },
80
+ product: {
81
+ key: lineItem.productId
82
+ },
83
+ variant: {
84
+ sku: lineItem.variant.sku || ""
85
+ },
86
+ quantity: lineItem.quantity,
87
+ price: {
88
+ unitPrice: {
89
+ value: unitPrice / 100,
90
+ currency
91
+ },
92
+ unitDiscount: {
93
+ value: unitDiscount / 100,
94
+ currency
95
+ },
96
+ totalPrice: {
97
+ value: totalPrice / 100,
98
+ currency
99
+ },
100
+ totalDiscount: {
101
+ value: totalDiscount / 100,
102
+ currency
103
+ }
104
+ }
105
+ });
106
+ }
107
+ }
108
+ export {
109
+ CommercetoolsCartFactory
110
+ };
@@ -0,0 +1,40 @@
1
+ import {
2
+ } from "@reactionary/core";
3
+ class CommercetoolsCategoryFactory {
4
+ categorySchema;
5
+ categoryPaginatedResultSchema;
6
+ constructor(categorySchema, categoryPaginatedResultSchema) {
7
+ this.categorySchema = categorySchema;
8
+ this.categoryPaginatedResultSchema = categoryPaginatedResultSchema;
9
+ }
10
+ parseCategory(context, data) {
11
+ const identifier = { key: data.key || "" };
12
+ const result = {
13
+ identifier,
14
+ name: data.name[context.languageContext.locale] || "No Name",
15
+ slug: data.slug ? data.slug[context.languageContext.locale] || "" : "",
16
+ text: data.description ? data.description[context.languageContext.locale] || "" : "",
17
+ parentCategory: data.parent && data.parent.obj && data.parent.obj?.key ? { key: data.parent.obj.key } : void 0,
18
+ images: (data.assets || []).filter((asset) => asset.sources.length > 0).filter((asset) => asset.sources[0].contentType?.startsWith("image/")).map((asset) => ({
19
+ sourceUrl: asset.sources[0].uri,
20
+ altText: asset.description?.[context.languageContext.locale] || asset.name[context.languageContext.locale] || "",
21
+ height: asset.sources[0].dimensions?.h || 0,
22
+ width: asset.sources[0].dimensions?.w || 0
23
+ }))
24
+ };
25
+ return this.categorySchema.parse(result);
26
+ }
27
+ parseCategoryPaginatedResult(context, data) {
28
+ const result = {
29
+ pageNumber: Math.floor(data.offset / data.count) + 1,
30
+ pageSize: data.count,
31
+ totalCount: data.total || 0,
32
+ totalPages: Math.ceil((data.total ?? 0) / data.count),
33
+ items: data.results.map((category) => this.parseCategory(context, category))
34
+ };
35
+ return this.categoryPaginatedResultSchema.parse(result);
36
+ }
37
+ }
38
+ export {
39
+ CommercetoolsCategoryFactory
40
+ };
@@ -0,0 +1,67 @@
1
+ import {
2
+ CheckoutSchema,
3
+ PaymentMethodSchema,
4
+ ShippingMethodSchema,
5
+ unwrapValue
6
+ } from "@reactionary/core";
7
+ import { withCommercetoolsCapabilities } from "../../core/initialize.js";
8
+ import { CommercetoolsCheckoutCapability } from "../../capabilities/checkout.capability.js";
9
+ import { assertNotAny, assertType } from "../product/utils.example.js";
10
+ import { CommercetoolsCheckoutFactory } from "./checkout.factory.js";
11
+ import * as z from "zod";
12
+ const cache = {};
13
+ const context = {};
14
+ const config = {};
15
+ const ExtendedCheckoutSchema = CheckoutSchema.safeExtend({
16
+ extendedValue: z.string().default("")
17
+ });
18
+ class ExtendedCommercetoolsCheckoutFactory extends CommercetoolsCheckoutFactory {
19
+ constructor() {
20
+ super(ExtendedCheckoutSchema, ShippingMethodSchema, PaymentMethodSchema);
21
+ }
22
+ parseCheckout(context2, data) {
23
+ const base = super.parseCheckout(context2, data);
24
+ return {
25
+ ...base,
26
+ extendedValue: "from-factory"
27
+ };
28
+ }
29
+ }
30
+ class ExtendedCommercetoolsCheckoutCapability extends CommercetoolsCheckoutCapability {
31
+ async getByIdOrThrow(payload) {
32
+ const result = await this.getById(payload);
33
+ return unwrapValue(result);
34
+ }
35
+ }
36
+ const extendedFactory = new ExtendedCommercetoolsCheckoutFactory();
37
+ const capabilityFactory = withCommercetoolsCapabilities(config, {
38
+ checkout: {
39
+ enabled: true,
40
+ factory: extendedFactory,
41
+ capability: ({ cache: cache2, context: context2, config: config2, commercetoolsApi }) => new ExtendedCommercetoolsCheckoutCapability(
42
+ config2,
43
+ cache2,
44
+ context2,
45
+ commercetoolsApi,
46
+ extendedFactory
47
+ )
48
+ }
49
+ });
50
+ const client = capabilityFactory(cache, context);
51
+ client.checkout.getById({
52
+ identifier: { key: "checkout-1" }
53
+ }).then((result) => {
54
+ assertNotAny(result);
55
+ if (result.success) {
56
+ assertNotAny(result.value);
57
+ assertNotAny(result.value.extendedValue);
58
+ assertType(result.value.extendedValue);
59
+ }
60
+ });
61
+ client.checkout.getByIdOrThrow({
62
+ identifier: { key: "checkout-2" }
63
+ }).then((checkout) => {
64
+ assertNotAny(checkout);
65
+ assertNotAny(checkout.extendedValue);
66
+ assertType(checkout.extendedValue);
67
+ });