@reactionary/magento 0.0.1 → 0.10.19

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 (62) hide show
  1. package/LICENSE +21 -0
  2. package/capabilities/cart.capability.js +22 -22
  3. package/capabilities/category.capability.js +10 -9
  4. package/capabilities/checkout.capability.js +308 -0
  5. package/capabilities/identity.capability.js +31 -16
  6. package/capabilities/inventory.capability.js +2 -2
  7. package/capabilities/order-search.capability.js +150 -0
  8. package/capabilities/order.capability.js +51 -0
  9. package/capabilities/price.capability.js +2 -2
  10. package/capabilities/product-associations.capability.js +118 -0
  11. package/capabilities/product-recommendations.capability.js +148 -0
  12. package/capabilities/product-reviews.capability.js +182 -0
  13. package/capabilities/product-search.capability.js +2 -2
  14. package/capabilities/product.capability.js +6 -5
  15. package/capabilities/profile.capability.js +283 -0
  16. package/core/client.js +324 -24
  17. package/core/initialize.js +150 -2
  18. package/core/product-reviews.graphql.js +72 -0
  19. package/factories/category/category.factory.js +1 -1
  20. package/factories/checkout/checkout.factory.js +203 -0
  21. package/factories/index.js +6 -0
  22. package/factories/order/order.factory.js +115 -0
  23. package/factories/order-search/order-search.factory.js +79 -0
  24. package/factories/product/product.factory.js +23 -18
  25. package/factories/product-associations/product-associations.factory.js +25 -0
  26. package/factories/product-reviews/product-reviews.factory.js +65 -0
  27. package/factories/product-search/product-search.factory.js +3 -4
  28. package/factories/profile/profile.factory.js +61 -0
  29. package/index.js +9 -0
  30. package/package.json +11 -6
  31. package/schema/capabilities.schema.js +16 -2
  32. package/schema/configuration.schema.js +5 -1
  33. package/src/capabilities/cart.capability.d.ts +5 -0
  34. package/src/capabilities/checkout.capability.d.ts +30 -0
  35. package/src/capabilities/order-search.capability.d.ts +16 -0
  36. package/src/capabilities/order.capability.d.ts +12 -0
  37. package/src/capabilities/product-associations.capability.d.ts +23 -0
  38. package/src/capabilities/product-recommendations.capability.d.ts +24 -0
  39. package/src/capabilities/product-reviews.capability.d.ts +35 -0
  40. package/src/capabilities/profile.capability.d.ts +24 -0
  41. package/src/core/client.d.ts +77 -2
  42. package/src/core/initialize.types.d.ts +40 -7
  43. package/src/core/product-reviews.graphql.d.ts +10 -0
  44. package/src/factories/category/category.factory.d.ts +2 -2
  45. package/src/factories/checkout/checkout.factory.d.ts +32 -0
  46. package/src/factories/index.d.ts +6 -0
  47. package/src/factories/order/order.factory.d.ts +77 -0
  48. package/src/factories/order-search/order-search.factory.d.ts +43 -0
  49. package/src/factories/product/product.factory.d.ts +2 -1
  50. package/src/factories/product-associations/product-associations.factory.d.ts +15 -0
  51. package/src/factories/product-reviews/product-reviews.factory.d.ts +40 -0
  52. package/src/factories/profile/profile.factory.d.ts +43 -0
  53. package/src/index.d.ts +9 -0
  54. package/src/schema/capabilities.schema.d.ts +50 -2
  55. package/src/schema/configuration.schema.d.ts +7 -0
  56. package/src/schema/magento.types.d.ts +219 -0
  57. package/src/utils/magento-product-lookup.d.ts +15 -0
  58. package/src/utils/magento-product.d.ts +36 -0
  59. package/src/utils/magento-review.d.ts +20 -0
  60. package/utils/magento-product-lookup.js +76 -0
  61. package/utils/magento-product.js +110 -0
  62. package/utils/magento-review.js +34 -0
@@ -0,0 +1,72 @@
1
+ const REVIEW_FIELDS = `
2
+ average_rating
3
+ created_at
4
+ nickname
5
+ summary
6
+ text
7
+ ratings_breakdown {
8
+ name
9
+ value
10
+ }
11
+ `;
12
+ const PRODUCT_RATING_SUMMARY_QUERY = `
13
+ query ReactionaryProductRatingSummary($sku: String!) {
14
+ products(filter: { sku: { eq: $sku } }, pageSize: 1, currentPage: 1) {
15
+ items {
16
+ sku
17
+ rating_summary
18
+ review_count
19
+ }
20
+ }
21
+ }
22
+ `;
23
+ const PRODUCT_REVIEWS_QUERY = `
24
+ query ReactionaryProductReviews($sku: String!, $pageSize: Int!, $currentPage: Int!) {
25
+ products(filter: { sku: { eq: $sku } }, pageSize: 1, currentPage: 1) {
26
+ items {
27
+ sku
28
+ rating_summary
29
+ review_count
30
+ reviews(pageSize: $pageSize, currentPage: $currentPage) {
31
+ items {
32
+ ${REVIEW_FIELDS}
33
+ }
34
+ page_info {
35
+ page_size
36
+ current_page
37
+ total_pages
38
+ }
39
+ }
40
+ }
41
+ }
42
+ }
43
+ `;
44
+ const PRODUCT_REVIEW_RATINGS_METADATA_QUERY = `
45
+ query ReactionaryProductReviewRatingsMetadata {
46
+ productReviewRatingsMetadata {
47
+ items {
48
+ id
49
+ name
50
+ values {
51
+ value_id
52
+ value
53
+ }
54
+ }
55
+ }
56
+ }
57
+ `;
58
+ const CREATE_PRODUCT_REVIEW_MUTATION = `
59
+ mutation ReactionaryCreateProductReview($input: CreateProductReviewInput!) {
60
+ createProductReview(input: $input) {
61
+ review {
62
+ ${REVIEW_FIELDS}
63
+ }
64
+ }
65
+ }
66
+ `;
67
+ export {
68
+ CREATE_PRODUCT_REVIEW_MUTATION,
69
+ PRODUCT_RATING_SUMMARY_QUERY,
70
+ PRODUCT_REVIEWS_QUERY,
71
+ PRODUCT_REVIEW_RATINGS_METADATA_QUERY
72
+ };
@@ -29,7 +29,7 @@ class MagentoCategoryFactory {
29
29
  };
30
30
  return this.categorySchema.parse(result);
31
31
  }
32
- parseCategoryPaginatedResult(context, data) {
32
+ parseCategoryPaginatedResult(context, data, _query) {
33
33
  const items = (data.items || []).map((c) => this.parseCategory(context, c));
34
34
  const totalCount = data.total_count ?? 0;
35
35
  const totalPages = Math.max(1, Math.ceil(totalCount / data.pageSize));
@@ -0,0 +1,203 @@
1
+ import {
2
+ AddressIdentifierSchema,
3
+ MonetaryAmountSchema,
4
+ ShippingMethodIdentifierSchema
5
+ } from "@reactionary/core";
6
+ function encodeShippingMethodKey(carrierCode, methodCode) {
7
+ return `${carrierCode}_${methodCode}`;
8
+ }
9
+ class MagentoCheckoutFactory {
10
+ checkoutSchema;
11
+ shippingMethodSchema;
12
+ paymentMethodSchema;
13
+ config;
14
+ constructor(checkoutSchema, shippingMethodSchema, paymentMethodSchema, config) {
15
+ this.checkoutSchema = checkoutSchema;
16
+ this.shippingMethodSchema = shippingMethodSchema;
17
+ this.paymentMethodSchema = paymentMethodSchema;
18
+ this.config = config;
19
+ }
20
+ resolveCurrency(data) {
21
+ const code = data.totals?.quote_currency_code || data.cart.quote_currency_code || this.config.defaultCurrency || "USD";
22
+ return code;
23
+ }
24
+ parseAddress(address) {
25
+ const street = address.street ?? [];
26
+ return {
27
+ identifier: AddressIdentifierSchema.parse({
28
+ nickName: address.company || ""
29
+ }),
30
+ firstName: address.firstname || "",
31
+ lastName: address.lastname || "",
32
+ streetAddress: street[0] || "",
33
+ streetNumber: street[1] || "",
34
+ city: address.city || "",
35
+ region: address.region || "",
36
+ postalCode: address.postcode || "",
37
+ countryCode: address.country_id || ""
38
+ };
39
+ }
40
+ parseCostBreakdown(data, currency) {
41
+ const source = data.totals ?? data.cart;
42
+ return {
43
+ totalProductPrice: {
44
+ value: source.subtotal ?? source.base_subtotal ?? 0,
45
+ currency
46
+ },
47
+ grandTotal: {
48
+ value: source.grand_total ?? source.base_grand_total ?? 0,
49
+ currency
50
+ },
51
+ totalTax: {
52
+ value: source.tax_amount ?? source.base_tax_amount ?? 0,
53
+ currency
54
+ },
55
+ totalShipping: {
56
+ value: source.shipping_amount ?? source.base_shipping_amount ?? 0,
57
+ currency
58
+ },
59
+ totalDiscount: {
60
+ value: Math.abs(source.discount_amount ?? source.base_discount_amount ?? 0),
61
+ currency
62
+ },
63
+ totalSurcharge: {
64
+ value: 0,
65
+ currency
66
+ }
67
+ };
68
+ }
69
+ parseItemPrice(item, currency) {
70
+ const qty = item.qty ?? 0;
71
+ return {
72
+ unitPrice: {
73
+ value: item.price ?? 0,
74
+ currency
75
+ },
76
+ unitDiscount: {
77
+ value: item.discount_amount ? Math.abs(item.discount_amount / (qty || 1)) : 0,
78
+ currency
79
+ },
80
+ totalPrice: {
81
+ value: item.row_total !== void 0 ? item.row_total : (item.price || 0) * (qty || 0),
82
+ currency
83
+ },
84
+ totalDiscount: {
85
+ value: Math.abs(item.discount_amount || 0),
86
+ currency
87
+ }
88
+ };
89
+ }
90
+ parseCheckoutItem(item, currency) {
91
+ return {
92
+ identifier: {
93
+ key: String(item.item_id)
94
+ },
95
+ variant: {
96
+ sku: item.sku
97
+ },
98
+ quantity: item.qty ?? 0,
99
+ price: this.parseItemPrice(item, currency)
100
+ };
101
+ }
102
+ parsePaymentInstruction(stored) {
103
+ return {
104
+ identifier: { key: stored.key },
105
+ amount: {
106
+ value: stored.amountValue,
107
+ currency: stored.amountCurrency
108
+ },
109
+ paymentMethod: {
110
+ method: stored.method,
111
+ name: stored.name,
112
+ paymentProcessor: stored.paymentProcessor
113
+ },
114
+ protocolData: stored.protocolData,
115
+ status: "pending"
116
+ };
117
+ }
118
+ parseShippingInstruction(state) {
119
+ if (!state.shippingInstruction) {
120
+ return void 0;
121
+ }
122
+ return {
123
+ shippingMethod: { key: state.shippingInstruction.shippingMethodKey },
124
+ pickupPoint: state.shippingInstruction.pickupPoint,
125
+ instructions: state.shippingInstruction.instructions,
126
+ consentForUnattendedDelivery: state.shippingInstruction.consentForUnattendedDelivery
127
+ };
128
+ }
129
+ parseCheckout(_context, data) {
130
+ const checkoutData = data;
131
+ const cart = checkoutData.cart;
132
+ const state = checkoutData.state ?? {};
133
+ const currency = this.resolveCurrency(checkoutData);
134
+ const key = checkoutData.requestedKey || cart.masked_id || String(cart.id || "");
135
+ const items = (cart.items || []).map(
136
+ (item) => this.parseCheckoutItem(item, currency)
137
+ );
138
+ const billingAddress = state.billingAddress ? this.parseAddress(state.billingAddress) : void 0;
139
+ const shippingAddress = state.shippingAddress ? this.parseAddress(state.shippingAddress) : void 0;
140
+ const paymentInstructions = (state.paymentInstructions ?? []).map(
141
+ (pi) => this.parsePaymentInstruction(pi)
142
+ );
143
+ const readyForFinalization = Boolean(
144
+ state.shippingInstruction && paymentInstructions.length > 0 && (billingAddress || shippingAddress)
145
+ );
146
+ const pointOfContact = {
147
+ email: state.email || cart.customer?.email || "",
148
+ phone: state.phone
149
+ };
150
+ const result = {
151
+ identifier: { key },
152
+ originalCartReference: { key },
153
+ resultingOrder: state.orderId ? { key: state.orderId } : void 0,
154
+ items,
155
+ price: this.parseCostBreakdown(checkoutData, currency),
156
+ name: cart.name || "",
157
+ description: cart.description || "",
158
+ pointOfContact,
159
+ billingAddress,
160
+ shippingAddress,
161
+ shippingInstruction: this.parseShippingInstruction(state),
162
+ paymentInstructions,
163
+ readyForFinalization
164
+ };
165
+ return this.checkoutSchema.parse(result);
166
+ }
167
+ parseShippingMethod(_context, data) {
168
+ const method = data;
169
+ const currency = this.config.defaultCurrency || "USD";
170
+ const sm = {
171
+ identifier: ShippingMethodIdentifierSchema.parse({
172
+ key: encodeShippingMethodKey(method.carrier_code, method.method_code)
173
+ }),
174
+ name: method.method_title || method.method_code,
175
+ description: method.carrier_title || "",
176
+ price: MonetaryAmountSchema.parse({
177
+ value: method.amount ?? 0,
178
+ currency
179
+ }),
180
+ deliveryTime: "",
181
+ carrier: method.carrier_code
182
+ };
183
+ return this.shippingMethodSchema.parse(sm);
184
+ }
185
+ parsePaymentMethod(_context, data) {
186
+ const method = data;
187
+ const pm = {
188
+ identifier: {
189
+ method: method.code,
190
+ name: method.title,
191
+ paymentProcessor: method.code
192
+ },
193
+ logo: void 0,
194
+ description: method.title,
195
+ isPunchOut: false
196
+ };
197
+ return this.paymentMethodSchema.parse(pm);
198
+ }
199
+ }
200
+ export {
201
+ MagentoCheckoutFactory,
202
+ encodeShippingMethodKey
203
+ };
@@ -5,3 +5,9 @@ export * from "./inventory/inventory.factory.js";
5
5
  export * from "./price/price.factory.js";
6
6
  export * from "./product/product.factory.js";
7
7
  export * from "./product-search/product-search.factory.js";
8
+ export * from "./profile/profile.factory.js";
9
+ export * from "./order-search/order-search.factory.js";
10
+ export * from "./order/order.factory.js";
11
+ export * from "./checkout/checkout.factory.js";
12
+ export * from "./product-associations/product-associations.factory.js";
13
+ export * from "./product-reviews/product-reviews.factory.js";
@@ -0,0 +1,115 @@
1
+ import {
2
+ AddressIdentifierSchema
3
+ } from "@reactionary/core";
4
+ import { mapMagentoOrderStatus } from "../order-search/order-search.factory.js";
5
+ function mapMagentoPaymentStatus(state) {
6
+ switch (state) {
7
+ case "complete":
8
+ case "closed":
9
+ return "captured";
10
+ case "processing":
11
+ return "authorized";
12
+ case "canceled":
13
+ return "canceled";
14
+ default:
15
+ return "pending";
16
+ }
17
+ }
18
+ class MagentoOrderFactory {
19
+ orderSchema;
20
+ constructor(orderSchema) {
21
+ this.orderSchema = orderSchema;
22
+ }
23
+ parseAddress(address) {
24
+ if (!address) return void 0;
25
+ const street = address.street ?? [];
26
+ return {
27
+ identifier: AddressIdentifierSchema.parse({
28
+ nickName: address.entity_id ? String(address.entity_id) : "address"
29
+ }),
30
+ firstName: address.firstname || "",
31
+ lastName: address.lastname || "",
32
+ streetAddress: street[0] || "",
33
+ streetNumber: street[1] || "",
34
+ city: address.city || "",
35
+ region: address.region || "",
36
+ postalCode: address.postcode || "",
37
+ countryCode: address.country_id || ""
38
+ };
39
+ }
40
+ parseOrderItem(order, item, currency) {
41
+ const unitPrice = item.price ?? 0;
42
+ const totalPrice = item.row_total ?? unitPrice * (item.qty_ordered ?? 1);
43
+ const price = {
44
+ unitPrice: { value: unitPrice, currency },
45
+ totalPrice: { value: totalPrice, currency },
46
+ unitDiscount: { value: 0, currency },
47
+ totalDiscount: { value: 0, currency }
48
+ };
49
+ return {
50
+ identifier: { key: String(item.item_id) },
51
+ variant: { sku: item.sku },
52
+ quantity: item.qty_ordered ?? 0,
53
+ price,
54
+ inventoryStatus: order.state === "complete" ? "Allocated" : "NotAllocated"
55
+ };
56
+ }
57
+ parseCostBreakdown(order, currency) {
58
+ return {
59
+ grandTotal: { value: order.grand_total ?? 0, currency },
60
+ totalProductPrice: { value: order.subtotal ?? 0, currency },
61
+ totalShipping: { value: order.shipping_amount ?? 0, currency },
62
+ totalTax: { value: order.tax_amount ?? 0, currency },
63
+ totalDiscount: { value: Math.abs(order.discount_amount ?? 0), currency },
64
+ totalSurcharge: { value: 0, currency }
65
+ };
66
+ }
67
+ parsePaymentInstruction(order, currency) {
68
+ const method = order.payment?.method ?? "unknown";
69
+ return {
70
+ identifier: { key: String(order.entity_id) },
71
+ amount: { value: order.payment?.amount_ordered ?? order.grand_total ?? 0, currency },
72
+ paymentMethod: { method, name: method, paymentProcessor: method },
73
+ protocolData: [],
74
+ status: mapMagentoPaymentStatus(order.state)
75
+ };
76
+ }
77
+ parseShippingMethod(order, method, currency) {
78
+ if (!method && !order.shipping_description) return void 0;
79
+ return {
80
+ identifier: { key: method ?? "default" },
81
+ name: order.shipping_description ?? method ?? "",
82
+ description: order.shipping_description ?? "",
83
+ price: { value: order.shipping_amount ?? 0, currency },
84
+ deliveryTime: ""
85
+ };
86
+ }
87
+ parseOrder(_context, data) {
88
+ const order = data;
89
+ const currency = (order.order_currency_code || "USD").toUpperCase();
90
+ const orderStatus = mapMagentoOrderStatus(order.status);
91
+ const inventoryStatus = order.state === "complete" ? "Allocated" : "NotAllocated";
92
+ const userId = { userId: order.customer_id ? String(order.customer_id) : "" };
93
+ const shippingAssignment = order.extension_attributes?.shipping_assignments?.[0]?.shipping;
94
+ const result = {
95
+ identifier: { key: String(order.entity_id) },
96
+ userId,
97
+ items: (order.items ?? []).map((item) => this.parseOrderItem(order, item, currency)),
98
+ price: this.parseCostBreakdown(order, currency),
99
+ orderStatus,
100
+ inventoryStatus,
101
+ paymentInstructions: order.payment ? [this.parsePaymentInstruction(order, currency)] : [],
102
+ shippingAddress: this.parseAddress(shippingAssignment?.address ?? order.billing_address),
103
+ billingAddress: this.parseAddress(order.billing_address),
104
+ shippingMethod: this.parseShippingMethod(order, shippingAssignment?.method, currency),
105
+ orderDate: new Date(order.created_at || Date.now()).toISOString(),
106
+ incrementId: order.increment_id,
107
+ customerEmail: order.customer_email || order.billing_address?.email,
108
+ customerPhone: order.billing_address?.telephone
109
+ };
110
+ return this.orderSchema.parse(result);
111
+ }
112
+ }
113
+ export {
114
+ MagentoOrderFactory
115
+ };
@@ -0,0 +1,79 @@
1
+ import {
2
+ AddressIdentifierSchema
3
+ } from "@reactionary/core";
4
+ function mapMagentoOrderStatus(status) {
5
+ switch (status) {
6
+ case "processing":
7
+ return "ReleasedToFulfillment";
8
+ case "complete":
9
+ case "closed":
10
+ return "Shipped";
11
+ case "canceled":
12
+ return "Cancelled";
13
+ default:
14
+ return "AwaitingPayment";
15
+ }
16
+ }
17
+ class MagentoOrderSearchFactory {
18
+ orderSearchResultSchema;
19
+ constructor(orderSearchResultSchema) {
20
+ this.orderSearchResultSchema = orderSearchResultSchema;
21
+ }
22
+ parseAddress(address) {
23
+ const street = address.street ?? [];
24
+ return {
25
+ identifier: AddressIdentifierSchema.parse({
26
+ nickName: address.entity_id ? String(address.entity_id) : "billing"
27
+ }),
28
+ firstName: address.firstname || "",
29
+ lastName: address.lastname || "",
30
+ streetAddress: street[0] || "",
31
+ streetNumber: street[1] || "",
32
+ city: address.city || "",
33
+ region: address.region || "",
34
+ postalCode: address.postcode || "",
35
+ countryCode: address.country_id || ""
36
+ };
37
+ }
38
+ parseOrderSearchResultItem(_context, order) {
39
+ const totalAmount = {
40
+ currency: (order.order_currency_code || "USD").toUpperCase(),
41
+ value: order.grand_total ?? 0
42
+ };
43
+ const inventoryStatus = order.state === "complete" ? "Allocated" : "NotAllocated";
44
+ const userId = {
45
+ userId: order.customer_id ? String(order.customer_id) : ""
46
+ };
47
+ return {
48
+ identifier: { key: String(order.entity_id) },
49
+ userId,
50
+ customerName: `${order.customer_firstname ?? ""} ${order.customer_lastname ?? ""}`.trim(),
51
+ shippingAddress: order.billing_address ? this.parseAddress(order.billing_address) : void 0,
52
+ orderDate: new Date(order.created_at || Date.now()).toISOString(),
53
+ orderStatus: mapMagentoOrderStatus(order.status),
54
+ inventoryStatus,
55
+ totalAmount
56
+ };
57
+ }
58
+ parseOrderSearchResult(context, data, query) {
59
+ const response = data;
60
+ const items = response.items ?? [];
61
+ const pageSize = response.search_criteria?.page_size || query.search.paginationOptions.pageSize;
62
+ const pageNumber = response.search_criteria?.current_page || query.search.paginationOptions.pageNumber;
63
+ const totalCount = response.total_count ?? items.length;
64
+ const identifier = { ...query.search };
65
+ const result = {
66
+ identifier,
67
+ pageNumber,
68
+ pageSize,
69
+ totalCount,
70
+ totalPages: pageSize > 0 ? Math.ceil(totalCount / pageSize) : 0,
71
+ items: items.map((o) => this.parseOrderSearchResultItem(context, o))
72
+ };
73
+ return this.orderSearchResultSchema.parse(result);
74
+ }
75
+ }
76
+ export {
77
+ MagentoOrderSearchFactory,
78
+ mapMagentoOrderStatus
79
+ };
@@ -2,12 +2,11 @@ import {
2
2
  CategoryIdentifierSchema,
3
3
  ProductIdentifierSchema
4
4
  } from "@reactionary/core";
5
+ import { getProductKey, parseDamImages } from "../../utils/magento-product.js";
5
6
  function getCustomAttribute(product, code) {
6
- if (!product.custom_attributes)
7
- return void 0;
7
+ if (!product.custom_attributes) return void 0;
8
8
  const found = product.custom_attributes.find((a) => a.attribute_code === code);
9
- if (found?.value === null || found?.value === void 0)
10
- return void 0;
9
+ if (found?.value === null || found?.value === void 0) return void 0;
11
10
  return String(found.value);
12
11
  }
13
12
  function normalizeBaseUrl(baseUrl) {
@@ -26,11 +25,11 @@ class MagentoProductFactory {
26
25
  this.config = config;
27
26
  this.productSchema = productSchema;
28
27
  }
28
+ config;
29
29
  productSchema;
30
30
  parseProduct(_context, data) {
31
31
  const sku = data.sku;
32
- const idKey = data.id !== void 0 ? String(data.id) : sku;
33
- const identifier = ProductIdentifierSchema.parse({ key: idKey });
32
+ const identifier = ProductIdentifierSchema.parse({ key: getProductKey(data) });
34
33
  const name = data.name || sku;
35
34
  const slug = getCustomAttribute(data, "url_key") ?? getCustomAttribute(data, "url_path") ?? "";
36
35
  const description = getCustomAttribute(data, "description") ?? getCustomAttribute(data, "short_description") ?? "";
@@ -59,10 +58,7 @@ class MagentoProductFactory {
59
58
  const sku = product.sku;
60
59
  const ean = getCustomAttribute(product, "ean") ?? getCustomAttribute(product, "gtin") ?? getCustomAttribute(product, "barcode") ?? "";
61
60
  const upc = getCustomAttribute(product, "upc") ?? "";
62
- const images = (product.media_gallery_entries ?? []).filter((m) => m.file.length > 0).map((m) => ({
63
- sourceUrl: buildMagentoImageUrl(this.config, m.file),
64
- altText: product.name || sku
65
- }));
61
+ const images = this.parseImages(product);
66
62
  const options = [];
67
63
  return {
68
64
  identifier: { sku },
@@ -75,6 +71,19 @@ class MagentoProductFactory {
75
71
  barcode: ean
76
72
  };
77
73
  }
74
+ parseImages(product) {
75
+ const altText = product.name || product.sku;
76
+ if (this.config.mediaSource === "EXTERNAL") {
77
+ return parseDamImages(
78
+ getCustomAttribute(product, "original_dam_reference"),
79
+ altText
80
+ );
81
+ }
82
+ return (product.media_gallery_entries ?? []).filter((m) => m.file.length > 0).map((m) => ({
83
+ sourceUrl: buildMagentoImageUrl(this.config, m.file),
84
+ altText
85
+ }));
86
+ }
78
87
  createSynthAttribute(key, name, value) {
79
88
  const attributeIdentifier = { key };
80
89
  const valueIdentifier = { key: `${key}-${value}` };
@@ -93,17 +102,13 @@ class MagentoProductFactory {
93
102
  }
94
103
  parseAttributes(product) {
95
104
  const sharedAttributes = [];
96
- if (!product.custom_attributes)
97
- return sharedAttributes;
105
+ if (!product.custom_attributes) return sharedAttributes;
98
106
  for (const entry of product.custom_attributes) {
99
107
  const key = entry.attribute_code;
100
108
  const value = entry.value;
101
- if (!key)
102
- continue;
103
- if (key === "description" || key === "short_description")
104
- continue;
105
- if (value === null || value === void 0)
106
- continue;
109
+ if (!key) continue;
110
+ if (key === "description" || key === "short_description") continue;
111
+ if (value === null || value === void 0) continue;
107
112
  const stringValue = typeof value === "string" || typeof value === "number" || typeof value === "boolean" ? String(value) : JSON.stringify(value);
108
113
  sharedAttributes.push(this.createSynthAttribute(key, key, stringValue));
109
114
  }
@@ -0,0 +1,25 @@
1
+ import {
2
+ } from "@reactionary/core";
3
+ import { buildProductSearchResultItem } from "../../utils/magento-product.js";
4
+ class MagentoProductAssociationsFactory {
5
+ productAssociationSchema;
6
+ config;
7
+ constructor(productAssociationSchema, config) {
8
+ this.productAssociationSchema = productAssociationSchema;
9
+ this.config = config;
10
+ }
11
+ parseProduct(_context, product) {
12
+ return buildProductSearchResultItem(this.config, product);
13
+ }
14
+ parseAssociation(context, data) {
15
+ const result = {
16
+ associationIdentifier: data.identifier,
17
+ associationReturnType: "productSearchResultItem",
18
+ product: this.parseProduct(context, data.product)
19
+ };
20
+ return this.productAssociationSchema.parse(result);
21
+ }
22
+ }
23
+ export {
24
+ MagentoProductAssociationsFactory
25
+ };
@@ -0,0 +1,65 @@
1
+ import {
2
+ buildProductReviewKey,
3
+ ratingPercentToStars,
4
+ toIsoTimestamp
5
+ } from "../../utils/magento-review.js";
6
+ class MagentoProductReviewsFactory {
7
+ ratingSummarySchema;
8
+ reviewSchema;
9
+ reviewPaginatedSchema;
10
+ constructor(ratingSummarySchema, reviewSchema, reviewPaginatedSchema) {
11
+ this.ratingSummarySchema = ratingSummarySchema;
12
+ this.reviewSchema = reviewSchema;
13
+ this.reviewPaginatedSchema = reviewPaginatedSchema;
14
+ }
15
+ /**
16
+ * Magento's GraphQL schema offers no per-star breakdown, so the distribution
17
+ * is left undefined rather than reconstructed from the current page.
18
+ */
19
+ parseRatingSummary(_context, data) {
20
+ return this.ratingSummarySchema.parse({
21
+ identifier: {
22
+ product: data.product
23
+ },
24
+ averageRating: ratingPercentToStars(data.ratingSummary),
25
+ totalRatings: data.reviewCount ?? void 0,
26
+ ratingDistribution: void 0
27
+ });
28
+ }
29
+ /**
30
+ * Magento does not expose whether a review came from a confirmed purchase, so
31
+ * `verified` is always false.
32
+ */
33
+ parseReview(_context, data) {
34
+ return this.reviewSchema.parse({
35
+ identifier: {
36
+ key: buildProductReviewKey(data.sku, data.review)
37
+ },
38
+ product: data.product,
39
+ authorName: data.review.nickname || "Anonymous",
40
+ rating: ratingPercentToStars(data.review.average_rating),
41
+ title: data.review.summary ?? "",
42
+ content: data.review.text ?? "",
43
+ createdAt: toIsoTimestamp(data.review.created_at),
44
+ verified: false
45
+ });
46
+ }
47
+ parseReviewPaginatedResult(context, data) {
48
+ return this.reviewPaginatedSchema.parse({
49
+ items: data.reviews.map(
50
+ (review) => this.parseReview(context, {
51
+ product: data.product,
52
+ sku: data.sku,
53
+ review
54
+ })
55
+ ),
56
+ totalCount: data.totalCount,
57
+ pageSize: data.pageSize,
58
+ pageNumber: data.pageNumber,
59
+ totalPages: Math.ceil(data.totalCount / Math.max(data.pageSize, 1))
60
+ });
61
+ }
62
+ }
63
+ export {
64
+ MagentoProductReviewsFactory
65
+ };
@@ -4,11 +4,9 @@ import {
4
4
  ProductVariantIdentifierSchema
5
5
  } from "@reactionary/core";
6
6
  function getCustomAttribute(product, code) {
7
- if (!product.custom_attributes)
8
- return void 0;
7
+ if (!product.custom_attributes) return void 0;
9
8
  const found = product.custom_attributes.find((a) => a.attribute_code === code);
10
- if (found?.value === null || found?.value === void 0)
11
- return void 0;
9
+ if (found?.value === null || found?.value === void 0) return void 0;
12
10
  return String(found.value);
13
11
  }
14
12
  function normalizeBaseUrl(baseUrl) {
@@ -27,6 +25,7 @@ class MagentoProductSearchFactory {
27
25
  this.config = config;
28
26
  this.productSearchResultSchema = productSearchResultSchema;
29
27
  }
28
+ config;
30
29
  productSearchResultSchema;
31
30
  parseSearchResult(context, data, query) {
32
31
  const pageSize = query.search.paginationOptions.pageSize;