@reactionary/medusa 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.
- package/README.md +11 -0
- package/capabilities/cart.capability.js +517 -0
- package/capabilities/category.capability.js +216 -0
- package/capabilities/checkout.capability.js +533 -0
- package/capabilities/identity.capability.js +128 -0
- package/capabilities/inventory.capability.js +142 -0
- package/capabilities/order-search.capability.js +161 -0
- package/capabilities/order.capability.js +179 -0
- package/capabilities/price.capability.js +120 -0
- package/capabilities/product-associations.capability.js +148 -0
- package/capabilities/product-recommendations.capability.js +116 -0
- package/capabilities/product-search.capability.js +177 -0
- package/capabilities/product.capability.js +252 -0
- package/capabilities/profile.capability.js +333 -0
- package/core/client.js +269 -0
- package/core/initialize.js +290 -0
- package/core/initialize.types.js +13 -0
- package/factories/cart/cart.factory.js +17 -0
- package/factories/category/category.factory.js +17 -0
- package/factories/checkout/checkout.factory.js +22 -0
- package/factories/identity/identity.factory.js +12 -0
- package/factories/index.js +12 -0
- package/factories/inventory/inventory.factory.js +12 -0
- package/factories/order/order.factory.js +12 -0
- package/factories/order-search/order-search.factory.js +12 -0
- package/factories/price/price.factory.js +12 -0
- package/factories/product/product.factory.js +12 -0
- package/factories/product-associations/product-associations.factory.js +12 -0
- package/factories/product-search/product-search.factory.js +12 -0
- package/factories/profile/profile.factory.js +12 -0
- package/index.js +17 -0
- package/package.json +17 -0
- package/schema/capabilities.schema.js +43 -0
- package/schema/configuration.schema.js +11 -0
- package/schema/medusa.schema.js +26 -0
- package/src/capabilities/cart.capability.d.ts +100 -0
- package/src/capabilities/category.capability.d.ts +36 -0
- package/src/capabilities/checkout.capability.d.ts +88 -0
- package/src/capabilities/identity.capability.d.ts +13 -0
- package/src/capabilities/inventory.capability.d.ts +21 -0
- package/src/capabilities/order-search.capability.d.ts +53 -0
- package/src/capabilities/order.capability.d.ts +49 -0
- package/src/capabilities/price.capability.d.ts +16 -0
- package/src/capabilities/product-associations.capability.d.ts +19 -0
- package/src/capabilities/product-recommendations.capability.d.ts +41 -0
- package/src/capabilities/product-search.capability.d.ts +45 -0
- package/src/capabilities/product.capability.d.ts +32 -0
- package/src/capabilities/profile.capability.d.ts +39 -0
- package/src/core/client.d.ts +81 -0
- package/src/core/initialize.d.ts +5 -0
- package/src/core/initialize.types.d.ts +108 -0
- package/src/factories/cart/cart.factory.d.ts +9 -0
- package/src/factories/category/category.factory.d.ts +9 -0
- package/src/factories/checkout/checkout.factory.d.ts +11 -0
- package/src/factories/identity/identity.factory.d.ts +7 -0
- package/src/factories/index.d.ts +12 -0
- package/src/factories/inventory/inventory.factory.d.ts +7 -0
- package/src/factories/order/order.factory.d.ts +7 -0
- package/src/factories/order-search/order-search.factory.d.ts +7 -0
- package/src/factories/price/price.factory.d.ts +9 -0
- package/src/factories/product/product.factory.d.ts +7 -0
- package/src/factories/product-associations/product-associations.factory.d.ts +7 -0
- package/src/factories/product-search/product-search.factory.d.ts +7 -0
- package/src/factories/profile/profile.factory.d.ts +7 -0
- package/src/index.d.ts +17 -0
- package/src/schema/capabilities.schema.d.ts +116 -0
- package/src/schema/configuration.schema.d.ts +9 -0
- package/src/schema/medusa.schema.d.ts +33 -0
- package/src/test/test-utils.d.ts +2 -0
- package/src/utils/medusa-helpers.d.ts +35 -0
- package/test/test-utils.js +12 -0
- package/utils/medusa-helpers.js +72 -0
|
@@ -0,0 +1,148 @@
|
|
|
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
|
+
ProductAssociationsCapability,
|
|
14
|
+
Reactionary,
|
|
15
|
+
ImageSchema,
|
|
16
|
+
ProductSearchResultItemVariantSchema,
|
|
17
|
+
ProductVariantIdentifierSchema,
|
|
18
|
+
success
|
|
19
|
+
} from "@reactionary/core";
|
|
20
|
+
class MedusaProductAssociationsCapability extends ProductAssociationsCapability {
|
|
21
|
+
config;
|
|
22
|
+
medusa;
|
|
23
|
+
factory;
|
|
24
|
+
constructor(config, cache, context, medusa, factory) {
|
|
25
|
+
super(cache, context);
|
|
26
|
+
this.config = config;
|
|
27
|
+
this.medusa = medusa;
|
|
28
|
+
this.factory = factory;
|
|
29
|
+
}
|
|
30
|
+
async fetchAssociatedProductsFor(productKey, maxNumberOfAssociations, attributeName) {
|
|
31
|
+
const client = await this.medusa.getClient();
|
|
32
|
+
const productResponse = await client.store.product.list({
|
|
33
|
+
external_id: productKey.key,
|
|
34
|
+
limit: 1,
|
|
35
|
+
offset: 0,
|
|
36
|
+
fields: "metadata.*,external_id"
|
|
37
|
+
});
|
|
38
|
+
let product;
|
|
39
|
+
if (!productResponse.products || productResponse.products.length === 0)
|
|
40
|
+
return [];
|
|
41
|
+
else
|
|
42
|
+
product = productResponse.products[0];
|
|
43
|
+
if (!product) {
|
|
44
|
+
return [];
|
|
45
|
+
}
|
|
46
|
+
const associationsMetadata = (product.metadata?.[attributeName] || "").split(";");
|
|
47
|
+
if (!associationsMetadata || associationsMetadata.length === 0 || associationsMetadata[0] === "") {
|
|
48
|
+
return [];
|
|
49
|
+
}
|
|
50
|
+
const associatedProductIds = associationsMetadata.slice(0, maxNumberOfAssociations);
|
|
51
|
+
const associatedProductsResponse = await client.store.product.list({
|
|
52
|
+
external_id: associatedProductIds,
|
|
53
|
+
fields: "+metadata.*,+external_id",
|
|
54
|
+
offset: 0,
|
|
55
|
+
limit: maxNumberOfAssociations
|
|
56
|
+
});
|
|
57
|
+
return associatedProductsResponse.products.map((product2) => this.parseSingle(product2));
|
|
58
|
+
}
|
|
59
|
+
async getAccessories(query) {
|
|
60
|
+
const associatedProducts = await this.fetchAssociatedProductsFor(query.forProduct, query.numberOfAccessories || 4, "reactionaryaccessories");
|
|
61
|
+
const result = associatedProducts.map((product) => ({
|
|
62
|
+
associationIdentifier: {
|
|
63
|
+
key: `${query.forProduct.key}-accessory-${product.identifier.key}`
|
|
64
|
+
},
|
|
65
|
+
associationReturnType: "productSearchResultItem",
|
|
66
|
+
product
|
|
67
|
+
}));
|
|
68
|
+
return success(result.map((x) => this.factory.parseAssociation(this.context, x)));
|
|
69
|
+
}
|
|
70
|
+
async getSpareparts(query) {
|
|
71
|
+
const associatedProducts = await this.fetchAssociatedProductsFor(query.forProduct, query.numberOfSpareparts || 4, "reactionaryspareparts");
|
|
72
|
+
const result = associatedProducts.map((product) => ({
|
|
73
|
+
associationIdentifier: {
|
|
74
|
+
key: `${query.forProduct.key}-sparepart-${product.identifier.key}`
|
|
75
|
+
},
|
|
76
|
+
associationReturnType: "productSearchResultItem",
|
|
77
|
+
product
|
|
78
|
+
}));
|
|
79
|
+
return success(result.map((x) => this.factory.parseAssociation(this.context, x)));
|
|
80
|
+
}
|
|
81
|
+
async getReplacements(query) {
|
|
82
|
+
const associatedProducts = await this.fetchAssociatedProductsFor(query.forProduct, query.numberOfReplacements || 4, "reactionaryreplacements");
|
|
83
|
+
const result = associatedProducts.map((product) => ({
|
|
84
|
+
associationIdentifier: {
|
|
85
|
+
key: `${query.forProduct.key}-replacement-${product.identifier.key}`
|
|
86
|
+
},
|
|
87
|
+
associationReturnType: "productSearchResultItem",
|
|
88
|
+
product
|
|
89
|
+
}));
|
|
90
|
+
return success(result.map((x) => this.factory.parseAssociation(this.context, x)));
|
|
91
|
+
}
|
|
92
|
+
parseSingle(_body) {
|
|
93
|
+
const heroVariant = _body.variants?.[0];
|
|
94
|
+
const identifier = { key: _body.external_id || _body.id };
|
|
95
|
+
const slug = _body.handle;
|
|
96
|
+
const name = heroVariant?.title || _body.title;
|
|
97
|
+
const variants = [];
|
|
98
|
+
if (heroVariant) {
|
|
99
|
+
variants.push(this.parseVariant(heroVariant, _body));
|
|
100
|
+
}
|
|
101
|
+
const result = {
|
|
102
|
+
identifier,
|
|
103
|
+
name,
|
|
104
|
+
slug,
|
|
105
|
+
variants
|
|
106
|
+
};
|
|
107
|
+
return result;
|
|
108
|
+
}
|
|
109
|
+
parseVariant(variant, product) {
|
|
110
|
+
const img = ImageSchema.parse({
|
|
111
|
+
sourceUrl: product.images?.[0].url ?? "",
|
|
112
|
+
altText: product.title || void 0
|
|
113
|
+
});
|
|
114
|
+
return ProductSearchResultItemVariantSchema.parse({
|
|
115
|
+
variant: ProductVariantIdentifierSchema.parse({
|
|
116
|
+
sku: variant.sku || ""
|
|
117
|
+
}),
|
|
118
|
+
image: img
|
|
119
|
+
});
|
|
120
|
+
}
|
|
121
|
+
}
|
|
122
|
+
__decorateClass([
|
|
123
|
+
Reactionary({
|
|
124
|
+
cache: true,
|
|
125
|
+
cacheTimeToLiveInSeconds: 300,
|
|
126
|
+
currencyDependentCaching: false,
|
|
127
|
+
localeDependentCaching: false
|
|
128
|
+
})
|
|
129
|
+
], MedusaProductAssociationsCapability.prototype, "getAccessories", 1);
|
|
130
|
+
__decorateClass([
|
|
131
|
+
Reactionary({
|
|
132
|
+
cache: true,
|
|
133
|
+
cacheTimeToLiveInSeconds: 300,
|
|
134
|
+
currencyDependentCaching: false,
|
|
135
|
+
localeDependentCaching: false
|
|
136
|
+
})
|
|
137
|
+
], MedusaProductAssociationsCapability.prototype, "getSpareparts", 1);
|
|
138
|
+
__decorateClass([
|
|
139
|
+
Reactionary({
|
|
140
|
+
cache: true,
|
|
141
|
+
cacheTimeToLiveInSeconds: 300,
|
|
142
|
+
currencyDependentCaching: false,
|
|
143
|
+
localeDependentCaching: false
|
|
144
|
+
})
|
|
145
|
+
], MedusaProductAssociationsCapability.prototype, "getReplacements", 1);
|
|
146
|
+
export {
|
|
147
|
+
MedusaProductAssociationsCapability
|
|
148
|
+
};
|
|
@@ -0,0 +1,116 @@
|
|
|
1
|
+
import {
|
|
2
|
+
error,
|
|
3
|
+
ImageSchema,
|
|
4
|
+
ProductRecommendationsCapability,
|
|
5
|
+
ProductSearchResultItemVariantSchema,
|
|
6
|
+
ProductVariantIdentifierSchema,
|
|
7
|
+
success
|
|
8
|
+
} from "@reactionary/core";
|
|
9
|
+
import createDebug from "debug";
|
|
10
|
+
const debug = createDebug("reactionary:medusa:product-recommendations");
|
|
11
|
+
class MedusaProductRecommendationsCapability extends ProductRecommendationsCapability {
|
|
12
|
+
config;
|
|
13
|
+
medusaApi;
|
|
14
|
+
constructor(config, cache, context, medusaApi) {
|
|
15
|
+
super(cache, context);
|
|
16
|
+
this.config = config;
|
|
17
|
+
this.medusaApi = medusaApi;
|
|
18
|
+
}
|
|
19
|
+
getCollectionPayload(query, collection) {
|
|
20
|
+
return {
|
|
21
|
+
collection_id: [collection.id],
|
|
22
|
+
limit: query.numberOfRecommendations,
|
|
23
|
+
fields: "+variants.id,+variants.sku,+external_id"
|
|
24
|
+
};
|
|
25
|
+
}
|
|
26
|
+
/**
|
|
27
|
+
* Get product recommendations from a Medusa collection
|
|
28
|
+
*
|
|
29
|
+
* This method fetches products from a specific Medusa collection by name or handle.
|
|
30
|
+
* It's useful for displaying curated product lists like "Featured Products",
|
|
31
|
+
* "New Arrivals", "Best Sellers", etc.
|
|
32
|
+
*/
|
|
33
|
+
async getCollection(query) {
|
|
34
|
+
const client = await this.medusaApi.getClient();
|
|
35
|
+
try {
|
|
36
|
+
if (debug.enabled) {
|
|
37
|
+
debug(`Fetching collection: ${query.collectionName}`);
|
|
38
|
+
}
|
|
39
|
+
const collectionsResponse = await client.store.collection.list({
|
|
40
|
+
handle: query.collectionName,
|
|
41
|
+
limit: 1
|
|
42
|
+
});
|
|
43
|
+
if (!collectionsResponse.collections || collectionsResponse.collections.length === 0) {
|
|
44
|
+
if (debug.enabled) {
|
|
45
|
+
debug(`Collection not found: ${query.collectionName}`);
|
|
46
|
+
}
|
|
47
|
+
return error({
|
|
48
|
+
type: "NotFound",
|
|
49
|
+
identifier: query.collectionName
|
|
50
|
+
});
|
|
51
|
+
}
|
|
52
|
+
const collection = collectionsResponse.collections[0];
|
|
53
|
+
if (debug.enabled) {
|
|
54
|
+
debug(`Found collection: ${collection.title} (${collection.id})`);
|
|
55
|
+
}
|
|
56
|
+
const productsResponse = await client.store.product.list(this.getCollectionPayload(query, collection));
|
|
57
|
+
if (debug.enabled) {
|
|
58
|
+
debug(`Found ${productsResponse.products.length} products in collection`);
|
|
59
|
+
}
|
|
60
|
+
const recommendations = [];
|
|
61
|
+
for (const productRes of productsResponse.products) {
|
|
62
|
+
const product = this.parseSearchResultItem(productRes);
|
|
63
|
+
recommendations.push({
|
|
64
|
+
recommendationIdentifier: {
|
|
65
|
+
key: `${collection.id}_${productRes.id}`,
|
|
66
|
+
algorithm: "collection"
|
|
67
|
+
},
|
|
68
|
+
recommendationReturnType: "productSearchResultItem",
|
|
69
|
+
product
|
|
70
|
+
});
|
|
71
|
+
}
|
|
72
|
+
if (debug.enabled) {
|
|
73
|
+
debug(`Returning ${recommendations.length} recommendations`);
|
|
74
|
+
}
|
|
75
|
+
return success(recommendations);
|
|
76
|
+
} catch (error2) {
|
|
77
|
+
if (debug.enabled) {
|
|
78
|
+
debug(`Error fetching collection recommendations: %O`, error2);
|
|
79
|
+
}
|
|
80
|
+
console.error("Error fetching collection recommendations:", error2);
|
|
81
|
+
return success([]);
|
|
82
|
+
}
|
|
83
|
+
}
|
|
84
|
+
parseSearchResultItem(_body) {
|
|
85
|
+
const heroVariant = _body.variants?.[0];
|
|
86
|
+
const identifier = { key: _body.external_id || _body.id };
|
|
87
|
+
const slug = _body.handle;
|
|
88
|
+
const name = heroVariant?.title || _body.title;
|
|
89
|
+
const variants = [];
|
|
90
|
+
if (heroVariant) {
|
|
91
|
+
variants.push(this.parseVariant(heroVariant, _body));
|
|
92
|
+
}
|
|
93
|
+
const result = {
|
|
94
|
+
identifier,
|
|
95
|
+
name,
|
|
96
|
+
slug,
|
|
97
|
+
variants
|
|
98
|
+
};
|
|
99
|
+
return result;
|
|
100
|
+
}
|
|
101
|
+
parseVariant(variant, product) {
|
|
102
|
+
const img = ImageSchema.parse({
|
|
103
|
+
sourceUrl: product.images?.[0].url ?? "",
|
|
104
|
+
altText: product.title || void 0
|
|
105
|
+
});
|
|
106
|
+
return ProductSearchResultItemVariantSchema.parse({
|
|
107
|
+
variant: ProductVariantIdentifierSchema.parse({
|
|
108
|
+
sku: variant.sku || ""
|
|
109
|
+
}),
|
|
110
|
+
image: img
|
|
111
|
+
});
|
|
112
|
+
}
|
|
113
|
+
}
|
|
114
|
+
export {
|
|
115
|
+
MedusaProductRecommendationsCapability
|
|
116
|
+
};
|
|
@@ -0,0 +1,177 @@
|
|
|
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
|
+
ProductSearchCapability,
|
|
14
|
+
ProductSearchQueryByTermSchema,
|
|
15
|
+
ImageSchema,
|
|
16
|
+
ProductVariantIdentifierSchema,
|
|
17
|
+
ProductVariantOptionSchema,
|
|
18
|
+
ProductOptionIdentifierSchema,
|
|
19
|
+
ProductSearchResultItemVariantSchema,
|
|
20
|
+
Reactionary,
|
|
21
|
+
ProductSearchResultSchema,
|
|
22
|
+
FacetValueIdentifierSchema,
|
|
23
|
+
FacetIdentifierSchema,
|
|
24
|
+
success
|
|
25
|
+
} from "@reactionary/core";
|
|
26
|
+
import createDebug from "debug";
|
|
27
|
+
const debug = createDebug("reactionary:medusa:search");
|
|
28
|
+
class MedusaProductSearchCapability extends ProductSearchCapability {
|
|
29
|
+
constructor(config, cache, context, medusaApi, factory) {
|
|
30
|
+
super(cache, context);
|
|
31
|
+
this.medusaApi = medusaApi;
|
|
32
|
+
this.config = config;
|
|
33
|
+
this.factory = factory;
|
|
34
|
+
}
|
|
35
|
+
config;
|
|
36
|
+
factory;
|
|
37
|
+
async resolveCategoryIdByExternalId(externalId) {
|
|
38
|
+
const sdk = await this.medusaApi.getClient();
|
|
39
|
+
let offset = 0;
|
|
40
|
+
const limit = 50;
|
|
41
|
+
let candidate = void 0;
|
|
42
|
+
while (true) {
|
|
43
|
+
try {
|
|
44
|
+
const categoryResult = await sdk.store.category.list({
|
|
45
|
+
offset,
|
|
46
|
+
limit
|
|
47
|
+
});
|
|
48
|
+
if (categoryResult.product_categories.length === 0) {
|
|
49
|
+
break;
|
|
50
|
+
}
|
|
51
|
+
candidate = categoryResult.product_categories.find(
|
|
52
|
+
(cat) => cat.metadata?.["external_id"] === externalId
|
|
53
|
+
);
|
|
54
|
+
if (candidate) {
|
|
55
|
+
break;
|
|
56
|
+
}
|
|
57
|
+
offset += limit;
|
|
58
|
+
} catch (error) {
|
|
59
|
+
throw new Error(
|
|
60
|
+
"Category not found " + externalId + " due to error: " + error
|
|
61
|
+
);
|
|
62
|
+
break;
|
|
63
|
+
}
|
|
64
|
+
}
|
|
65
|
+
return candidate || null;
|
|
66
|
+
}
|
|
67
|
+
queryByTermPayload(payload, categoryIdToFind) {
|
|
68
|
+
const finalSearch = (payload.search.term || "").trim().replace("*", "");
|
|
69
|
+
return {
|
|
70
|
+
q: finalSearch,
|
|
71
|
+
...categoryIdToFind ? { category_id: categoryIdToFind } : {},
|
|
72
|
+
limit: payload.search.paginationOptions.pageSize,
|
|
73
|
+
fields: "+metadata.*,+external_id",
|
|
74
|
+
offset: (payload.search.paginationOptions.pageNumber - 1) * payload.search.paginationOptions.pageSize
|
|
75
|
+
};
|
|
76
|
+
}
|
|
77
|
+
async queryByTerm(payload) {
|
|
78
|
+
const client = await this.medusaApi.getClient();
|
|
79
|
+
let categoryIdToFind = null;
|
|
80
|
+
if (payload.search.categoryFilter?.key) {
|
|
81
|
+
debug(
|
|
82
|
+
`Resolving category filter for key: ${payload.search.categoryFilter.key}`
|
|
83
|
+
);
|
|
84
|
+
const category = await this.resolveCategoryIdByExternalId(
|
|
85
|
+
payload.search.categoryFilter.key
|
|
86
|
+
);
|
|
87
|
+
if (category) {
|
|
88
|
+
categoryIdToFind = category.id;
|
|
89
|
+
debug(
|
|
90
|
+
`Resolved category filter key ${payload.search.categoryFilter.key} to id: ${categoryIdToFind}`
|
|
91
|
+
);
|
|
92
|
+
} else {
|
|
93
|
+
debug(
|
|
94
|
+
`Could not resolve category filter for key: ${payload.search.categoryFilter.key}`
|
|
95
|
+
);
|
|
96
|
+
}
|
|
97
|
+
}
|
|
98
|
+
const response = await client.store.product.list(this.queryByTermPayload(payload, categoryIdToFind));
|
|
99
|
+
const result = this.parsePaginatedResult(response, payload);
|
|
100
|
+
if (debug.enabled) {
|
|
101
|
+
debug(
|
|
102
|
+
`Search for term "${payload.search.term}" returned ${response.products.length} products (page ${payload.search.paginationOptions.pageNumber} of ${result.totalPages})`
|
|
103
|
+
);
|
|
104
|
+
}
|
|
105
|
+
return success(this.factory.parseSearchResult(this.context, result, payload));
|
|
106
|
+
}
|
|
107
|
+
parsePaginatedResult(remote, query) {
|
|
108
|
+
const products = remote.products.map(
|
|
109
|
+
(p) => this.parseSingle(p)
|
|
110
|
+
);
|
|
111
|
+
const result = {
|
|
112
|
+
identifier: {
|
|
113
|
+
...query.search
|
|
114
|
+
},
|
|
115
|
+
pageNumber: (Math.ceil(remote.offset / remote.limit) || 0) + 1,
|
|
116
|
+
pageSize: remote.limit,
|
|
117
|
+
totalCount: remote.count,
|
|
118
|
+
totalPages: Math.ceil(remote.count / remote.limit || 0) + 1,
|
|
119
|
+
items: products,
|
|
120
|
+
facets: []
|
|
121
|
+
};
|
|
122
|
+
result.facets = [];
|
|
123
|
+
return result;
|
|
124
|
+
}
|
|
125
|
+
parseSingle(_body) {
|
|
126
|
+
const heroVariant = _body.variants?.[0];
|
|
127
|
+
const identifier = { key: _body.external_id || _body.id };
|
|
128
|
+
const slug = _body.handle;
|
|
129
|
+
const name = heroVariant?.title || _body.title;
|
|
130
|
+
const variants = [];
|
|
131
|
+
if (heroVariant) {
|
|
132
|
+
variants.push(this.parseVariant(heroVariant, _body));
|
|
133
|
+
}
|
|
134
|
+
const result = {
|
|
135
|
+
identifier,
|
|
136
|
+
name,
|
|
137
|
+
slug,
|
|
138
|
+
variants
|
|
139
|
+
};
|
|
140
|
+
return result;
|
|
141
|
+
}
|
|
142
|
+
parseVariant(variant, product) {
|
|
143
|
+
const img = ImageSchema.parse({
|
|
144
|
+
sourceUrl: product.images?.[0].url ?? "",
|
|
145
|
+
altText: product.title || void 0
|
|
146
|
+
});
|
|
147
|
+
return ProductSearchResultItemVariantSchema.parse({
|
|
148
|
+
variant: ProductVariantIdentifierSchema.parse({
|
|
149
|
+
sku: variant.sku || ""
|
|
150
|
+
}),
|
|
151
|
+
image: img
|
|
152
|
+
});
|
|
153
|
+
}
|
|
154
|
+
async createCategoryNavigationFilter(payload) {
|
|
155
|
+
const facetIdentifier = FacetIdentifierSchema.parse({
|
|
156
|
+
key: "categories"
|
|
157
|
+
});
|
|
158
|
+
const facetValueIdentifier = FacetValueIdentifierSchema.parse({
|
|
159
|
+
facet: facetIdentifier,
|
|
160
|
+
key: payload.categoryPath[payload.categoryPath.length - 1].identifier.key
|
|
161
|
+
});
|
|
162
|
+
return success(facetValueIdentifier);
|
|
163
|
+
}
|
|
164
|
+
}
|
|
165
|
+
__decorateClass([
|
|
166
|
+
Reactionary({
|
|
167
|
+
inputSchema: ProductSearchQueryByTermSchema,
|
|
168
|
+
outputSchema: ProductSearchResultSchema,
|
|
169
|
+
cache: true,
|
|
170
|
+
cacheTimeToLiveInSeconds: 300,
|
|
171
|
+
currencyDependentCaching: false,
|
|
172
|
+
localeDependentCaching: true
|
|
173
|
+
})
|
|
174
|
+
], MedusaProductSearchCapability.prototype, "queryByTerm", 1);
|
|
175
|
+
export {
|
|
176
|
+
MedusaProductSearchCapability
|
|
177
|
+
};
|
|
@@ -0,0 +1,252 @@
|
|
|
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
|
+
CategoryIdentifierSchema,
|
|
14
|
+
ProductIdentifierSchema,
|
|
15
|
+
ProductCapability,
|
|
16
|
+
ProductQueryByIdSchema,
|
|
17
|
+
ProductQueryBySKUSchema,
|
|
18
|
+
ProductQueryBySlugSchema,
|
|
19
|
+
ProductSchema,
|
|
20
|
+
Reactionary,
|
|
21
|
+
success,
|
|
22
|
+
error
|
|
23
|
+
} from "@reactionary/core";
|
|
24
|
+
import createDebug from "debug";
|
|
25
|
+
const debug = createDebug("reactionary:medusa:product");
|
|
26
|
+
class MedusaProductCapability extends ProductCapability {
|
|
27
|
+
constructor(config, cache, context, medusaApi, factory) {
|
|
28
|
+
super(cache, context);
|
|
29
|
+
this.medusaApi = medusaApi;
|
|
30
|
+
this.config = config;
|
|
31
|
+
this.factory = factory;
|
|
32
|
+
}
|
|
33
|
+
config;
|
|
34
|
+
alwaysIncludedFields = ["+metadata.*", "+categories.metadata.*", "+external_id"];
|
|
35
|
+
factory;
|
|
36
|
+
getByIdPayload(payload) {
|
|
37
|
+
return {
|
|
38
|
+
external_id: payload.identifier.key,
|
|
39
|
+
limit: 1,
|
|
40
|
+
offset: 0,
|
|
41
|
+
fields: this.alwaysIncludedFields.join(",")
|
|
42
|
+
};
|
|
43
|
+
}
|
|
44
|
+
async getById(payload) {
|
|
45
|
+
const client = await this.medusaApi.getClient();
|
|
46
|
+
if (debug.enabled) {
|
|
47
|
+
debug(`Fetching product by ID: ${payload.identifier.key}`);
|
|
48
|
+
}
|
|
49
|
+
const response = await client.store.product.list(
|
|
50
|
+
this.getByIdPayload(payload)
|
|
51
|
+
);
|
|
52
|
+
if (response.count === 0) {
|
|
53
|
+
return error({
|
|
54
|
+
type: "NotFound",
|
|
55
|
+
identifier: payload
|
|
56
|
+
});
|
|
57
|
+
}
|
|
58
|
+
return success(this.parseSingle(response.products[0]));
|
|
59
|
+
}
|
|
60
|
+
getBySlugPayload(payload) {
|
|
61
|
+
return {
|
|
62
|
+
handle: payload.slug,
|
|
63
|
+
limit: 1,
|
|
64
|
+
offset: 0,
|
|
65
|
+
fields: this.alwaysIncludedFields.join(",")
|
|
66
|
+
};
|
|
67
|
+
}
|
|
68
|
+
async getBySlug(payload) {
|
|
69
|
+
const client = await this.medusaApi.getClient();
|
|
70
|
+
if (debug.enabled) {
|
|
71
|
+
debug(`Fetching product by slug: ${payload.slug}`);
|
|
72
|
+
}
|
|
73
|
+
const response = await client.store.product.list(this.getBySlugPayload(payload));
|
|
74
|
+
if (debug.enabled) {
|
|
75
|
+
debug(`Found ${response.count} products for slug: ${payload.slug}`);
|
|
76
|
+
}
|
|
77
|
+
if (response.count === 0) {
|
|
78
|
+
return error({
|
|
79
|
+
type: "NotFound",
|
|
80
|
+
identifier: payload
|
|
81
|
+
});
|
|
82
|
+
}
|
|
83
|
+
return success(this.parseSingle(response.products[0]));
|
|
84
|
+
}
|
|
85
|
+
async getBySKU(payload) {
|
|
86
|
+
if (debug.enabled) {
|
|
87
|
+
debug(`Fetching product by SKU: ${Array.isArray(payload) ? payload.join(", ") : payload}`);
|
|
88
|
+
}
|
|
89
|
+
const sku = payload.variant.sku;
|
|
90
|
+
const product = await this.medusaApi.resolveProductForSKU(sku);
|
|
91
|
+
const variant = product.variants?.find((v) => v.sku === sku);
|
|
92
|
+
if (!variant) {
|
|
93
|
+
throw new Error(`Variant with SKU ${sku} not found`);
|
|
94
|
+
}
|
|
95
|
+
product.variants = product.variants?.filter((v) => v.sku === sku).concat(product.variants?.filter((v) => v.sku !== sku)) || [];
|
|
96
|
+
return success(this.parseSingle(product));
|
|
97
|
+
}
|
|
98
|
+
parseSingle(_body) {
|
|
99
|
+
const identifier = ProductIdentifierSchema.parse({ key: _body.external_id || _body.id });
|
|
100
|
+
const name = _body.title;
|
|
101
|
+
const slug = _body.handle;
|
|
102
|
+
const description = _body.description || "" || _body.subtitle || "";
|
|
103
|
+
const parentCategories = [];
|
|
104
|
+
parentCategories.push(
|
|
105
|
+
..._body.categories?.map((cat) => cat.metadata?.["external_id"]).map((id) => CategoryIdentifierSchema.parse({ key: id || "" })) || []
|
|
106
|
+
);
|
|
107
|
+
const sharedAttributes = this.parseAttributes(_body);
|
|
108
|
+
if (!_body.variants) {
|
|
109
|
+
debug("Product has no variants", _body);
|
|
110
|
+
throw new Error("Product has no variants " + _body.external_id);
|
|
111
|
+
}
|
|
112
|
+
const mainVariant = this.parseVariant(_body.variants[0], _body);
|
|
113
|
+
const otherVariants = [];
|
|
114
|
+
if (_body.variants.length > 1) {
|
|
115
|
+
otherVariants.push(
|
|
116
|
+
..._body.variants.slice(1).map((variant) => this.parseVariant(variant, _body))
|
|
117
|
+
);
|
|
118
|
+
}
|
|
119
|
+
const result = {
|
|
120
|
+
brand: "",
|
|
121
|
+
description,
|
|
122
|
+
identifier,
|
|
123
|
+
longDescription: "",
|
|
124
|
+
mainVariant,
|
|
125
|
+
manufacturer: "",
|
|
126
|
+
name,
|
|
127
|
+
options: [],
|
|
128
|
+
parentCategories,
|
|
129
|
+
published: true,
|
|
130
|
+
sharedAttributes,
|
|
131
|
+
slug,
|
|
132
|
+
variants: otherVariants
|
|
133
|
+
};
|
|
134
|
+
return this.factory.parseProduct(this.context, result);
|
|
135
|
+
}
|
|
136
|
+
parseVariant(variant, product) {
|
|
137
|
+
const options = (variant.options ?? []).map((option) => {
|
|
138
|
+
const optionId = { key: option.option_id || "" };
|
|
139
|
+
const title = option.option?.title || "?";
|
|
140
|
+
const valueIdentifier = { key: option.option_id || "", option: optionId };
|
|
141
|
+
const value = option.value || "";
|
|
142
|
+
const result2 = {
|
|
143
|
+
identifier: optionId,
|
|
144
|
+
name: title,
|
|
145
|
+
value: {
|
|
146
|
+
identifier: valueIdentifier,
|
|
147
|
+
label: value
|
|
148
|
+
}
|
|
149
|
+
};
|
|
150
|
+
return result2;
|
|
151
|
+
});
|
|
152
|
+
const result = {
|
|
153
|
+
identifier: {
|
|
154
|
+
sku: variant.sku || ""
|
|
155
|
+
},
|
|
156
|
+
name: variant.title || product.title,
|
|
157
|
+
upc: variant.upc || "",
|
|
158
|
+
ean: variant.ean || "",
|
|
159
|
+
images: (product.images || []).map((img) => {
|
|
160
|
+
return {
|
|
161
|
+
sourceUrl: img.url,
|
|
162
|
+
altText: variant.title || product.title || ""
|
|
163
|
+
};
|
|
164
|
+
}),
|
|
165
|
+
options,
|
|
166
|
+
gtin: variant.ean || "",
|
|
167
|
+
barcode: variant.ean || ""
|
|
168
|
+
};
|
|
169
|
+
return result;
|
|
170
|
+
}
|
|
171
|
+
createSynthAttribute(key, name, value) {
|
|
172
|
+
const attributeIdentifier = { key };
|
|
173
|
+
const valueIdentifier = { key: `${key}-${value}` };
|
|
174
|
+
const attribute = {
|
|
175
|
+
identifier: { key },
|
|
176
|
+
name,
|
|
177
|
+
group: "",
|
|
178
|
+
values: [
|
|
179
|
+
{
|
|
180
|
+
identifier: valueIdentifier,
|
|
181
|
+
label: String(value),
|
|
182
|
+
value
|
|
183
|
+
}
|
|
184
|
+
]
|
|
185
|
+
};
|
|
186
|
+
return attribute;
|
|
187
|
+
}
|
|
188
|
+
parseAttributes(_body) {
|
|
189
|
+
const sharedAttributes = [];
|
|
190
|
+
if (_body.origin_country) {
|
|
191
|
+
sharedAttributes.push(this.createSynthAttribute("origin_country", "Origin Country", _body.origin_country));
|
|
192
|
+
}
|
|
193
|
+
if (_body.height) {
|
|
194
|
+
sharedAttributes.push(this.createSynthAttribute("height", "Height", String(_body.height)));
|
|
195
|
+
}
|
|
196
|
+
if (_body.weight) {
|
|
197
|
+
sharedAttributes.push(this.createSynthAttribute("weight", "Weight", String(_body.weight)));
|
|
198
|
+
}
|
|
199
|
+
if (_body.length) {
|
|
200
|
+
sharedAttributes.push(this.createSynthAttribute("length", "Length", String(_body.length)));
|
|
201
|
+
}
|
|
202
|
+
if (_body.width) {
|
|
203
|
+
sharedAttributes.push(this.createSynthAttribute("width", "Width", String(_body.width)));
|
|
204
|
+
}
|
|
205
|
+
if (_body.material) {
|
|
206
|
+
sharedAttributes.push(this.createSynthAttribute("material", "Material", _body.material));
|
|
207
|
+
}
|
|
208
|
+
if (_body.metadata) {
|
|
209
|
+
const keysToExclude = ["reactionaryaccessories", "reactionaryreplacements", "reactionaryspareparts"];
|
|
210
|
+
for (const [key, value] of Object.entries(_body.metadata)) {
|
|
211
|
+
if (keysToExclude.includes(key)) {
|
|
212
|
+
continue;
|
|
213
|
+
}
|
|
214
|
+
sharedAttributes.push(this.createSynthAttribute(key, key, String(value)));
|
|
215
|
+
}
|
|
216
|
+
}
|
|
217
|
+
return sharedAttributes;
|
|
218
|
+
}
|
|
219
|
+
}
|
|
220
|
+
__decorateClass([
|
|
221
|
+
Reactionary({
|
|
222
|
+
inputSchema: ProductQueryByIdSchema,
|
|
223
|
+
outputSchema: ProductSchema,
|
|
224
|
+
cache: true,
|
|
225
|
+
cacheTimeToLiveInSeconds: 300,
|
|
226
|
+
currencyDependentCaching: false,
|
|
227
|
+
localeDependentCaching: true
|
|
228
|
+
})
|
|
229
|
+
], MedusaProductCapability.prototype, "getById", 1);
|
|
230
|
+
__decorateClass([
|
|
231
|
+
Reactionary({
|
|
232
|
+
inputSchema: ProductQueryBySlugSchema,
|
|
233
|
+
outputSchema: ProductSchema,
|
|
234
|
+
cache: true,
|
|
235
|
+
cacheTimeToLiveInSeconds: 300,
|
|
236
|
+
currencyDependentCaching: false,
|
|
237
|
+
localeDependentCaching: true
|
|
238
|
+
})
|
|
239
|
+
], MedusaProductCapability.prototype, "getBySlug", 1);
|
|
240
|
+
__decorateClass([
|
|
241
|
+
Reactionary({
|
|
242
|
+
inputSchema: ProductQueryBySKUSchema,
|
|
243
|
+
outputSchema: ProductSchema,
|
|
244
|
+
cache: true,
|
|
245
|
+
cacheTimeToLiveInSeconds: 300,
|
|
246
|
+
currencyDependentCaching: false,
|
|
247
|
+
localeDependentCaching: true
|
|
248
|
+
})
|
|
249
|
+
], MedusaProductCapability.prototype, "getBySKU", 1);
|
|
250
|
+
export {
|
|
251
|
+
MedusaProductCapability
|
|
252
|
+
};
|