@reactionary/provider-medusa 0.3.14 → 0.3.16
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 +1 -1
- package/core/initialize.js +4 -0
- package/index.js +1 -0
- package/package.json +2 -2
- package/providers/product-associations.provider.js +144 -0
- package/providers/product-recommendations.provider.js +2 -2
- package/providers/product-search.provider.js +2 -1
- package/providers/product.provider.js +22 -15
- package/schema/capabilities.schema.js +2 -1
- package/src/index.d.ts +1 -0
- package/src/providers/product-associations.provider.d.ts +16 -0
- package/src/providers/product.provider.d.ts +1 -0
- package/src/schema/capabilities.schema.d.ts +1 -0
package/core/client.js
CHANGED
|
@@ -122,7 +122,7 @@ class MedusaAPI {
|
|
|
122
122
|
const productsResponse = await adminClient.admin.product.list({
|
|
123
123
|
limit: 1,
|
|
124
124
|
offset: 0,
|
|
125
|
-
fields: "+metadata.*,+categories.metadata
|
|
125
|
+
fields: "+metadata.*,+categories.metadata.*,+external_id",
|
|
126
126
|
variants: {
|
|
127
127
|
$or: [{ ean: sku }, { upc: sku }, { barcode: sku }]
|
|
128
128
|
}
|
package/core/initialize.js
CHANGED
|
@@ -9,6 +9,7 @@ import { MedusaPriceProvider } from "../providers/price.provider.js";
|
|
|
9
9
|
import { MedusaSearchProvider } from "../providers/product-search.provider.js";
|
|
10
10
|
import { MedusaProductRecommendationsProvider } from "../providers/product-recommendations.provider.js";
|
|
11
11
|
import { MedusaProductProvider } from "../providers/product.provider.js";
|
|
12
|
+
import { MedusaProductAssociationsProvider } from "../providers/product-associations.provider.js";
|
|
12
13
|
import { MedusaProfileProvider } from "../providers/profile.provider.js";
|
|
13
14
|
import { MedusaCapabilitiesSchema } from "../schema/capabilities.schema.js";
|
|
14
15
|
import { MedusaConfigurationSchema } from "../schema/configuration.schema.js";
|
|
@@ -55,6 +56,9 @@ function withMedusaCapabilities(configuration, capabilities) {
|
|
|
55
56
|
if (caps.orderSearch) {
|
|
56
57
|
client.orderSearch = new MedusaOrderSearchProvider(configuration, cache, context, medusaApi);
|
|
57
58
|
}
|
|
59
|
+
if (caps.productAssociations) {
|
|
60
|
+
client.productAssociations = new MedusaProductAssociationsProvider(configuration, cache, context, medusaApi);
|
|
61
|
+
}
|
|
58
62
|
return client;
|
|
59
63
|
};
|
|
60
64
|
}
|
package/index.js
CHANGED
|
@@ -11,4 +11,5 @@ export * from "./providers/order-search.provider.js";
|
|
|
11
11
|
export * from "./providers/price.provider.js";
|
|
12
12
|
export * from "./providers/product-search.provider.js";
|
|
13
13
|
export * from "./providers/product-recommendations.provider.js";
|
|
14
|
+
export * from "./providers/product-associations.provider.js";
|
|
14
15
|
export * from "./providers/profile.provider.js";
|
package/package.json
CHANGED
|
@@ -1,12 +1,12 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@reactionary/provider-medusa",
|
|
3
|
-
"version": "0.3.
|
|
3
|
+
"version": "0.3.16",
|
|
4
4
|
"type": "module",
|
|
5
5
|
"main": "index.js",
|
|
6
6
|
"types": "src/index.d.ts",
|
|
7
7
|
"dependencies": {
|
|
8
8
|
"@medusajs/js-sdk": "^2.13.0",
|
|
9
|
-
"@reactionary/core": "0.3.
|
|
9
|
+
"@reactionary/core": "0.3.16",
|
|
10
10
|
"debug": "^4.4.3",
|
|
11
11
|
"@medusajs/types": "^2.11.0",
|
|
12
12
|
"zod": "4.1.9"
|
|
@@ -0,0 +1,144 @@
|
|
|
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
|
+
ProductAssociationsProvider,
|
|
14
|
+
Reactionary,
|
|
15
|
+
ImageSchema,
|
|
16
|
+
ProductSearchResultItemVariantSchema,
|
|
17
|
+
ProductVariantIdentifierSchema,
|
|
18
|
+
success
|
|
19
|
+
} from "@reactionary/core";
|
|
20
|
+
class MedusaProductAssociationsProvider extends ProductAssociationsProvider {
|
|
21
|
+
constructor(config, cache, context, medusa) {
|
|
22
|
+
super(cache, context);
|
|
23
|
+
this.config = config;
|
|
24
|
+
this.medusa = medusa;
|
|
25
|
+
}
|
|
26
|
+
async fetchAssociatedProductsFor(productKey, maxNumberOfAssociations, attributeName) {
|
|
27
|
+
const client = await this.medusa.getClient();
|
|
28
|
+
const productResponse = await client.store.product.list({
|
|
29
|
+
external_id: productKey.key,
|
|
30
|
+
limit: 1,
|
|
31
|
+
offset: 0,
|
|
32
|
+
fields: "metadata.*,external_id"
|
|
33
|
+
});
|
|
34
|
+
let product;
|
|
35
|
+
if (!productResponse.products || productResponse.products.length === 0)
|
|
36
|
+
return [];
|
|
37
|
+
else
|
|
38
|
+
product = productResponse.products[0];
|
|
39
|
+
if (!product) {
|
|
40
|
+
return [];
|
|
41
|
+
}
|
|
42
|
+
const associationsMetadata = (product.metadata?.[attributeName] || "").split(";");
|
|
43
|
+
if (!associationsMetadata || associationsMetadata.length === 0 || associationsMetadata[0] === "") {
|
|
44
|
+
return [];
|
|
45
|
+
}
|
|
46
|
+
const associatedProductIds = associationsMetadata.slice(0, maxNumberOfAssociations);
|
|
47
|
+
const associatedProductsResponse = await client.store.product.list({
|
|
48
|
+
external_id: associatedProductIds,
|
|
49
|
+
fields: "+metadata.*,+external_id",
|
|
50
|
+
offset: 0,
|
|
51
|
+
limit: maxNumberOfAssociations
|
|
52
|
+
});
|
|
53
|
+
return associatedProductsResponse.products.map((product2) => this.parseSingle(product2));
|
|
54
|
+
}
|
|
55
|
+
async getAccessories(query) {
|
|
56
|
+
const associatedProducts = await this.fetchAssociatedProductsFor(query.forProduct, query.numberOfAccessories || 4, "reactionaryaccessories");
|
|
57
|
+
const result = associatedProducts.map((product) => ({
|
|
58
|
+
associationIdentifier: {
|
|
59
|
+
key: `${query.forProduct.key}-accessory-${product.identifier.key}`
|
|
60
|
+
},
|
|
61
|
+
associationReturnType: "productSearchResultItem",
|
|
62
|
+
product
|
|
63
|
+
}));
|
|
64
|
+
return success(result);
|
|
65
|
+
}
|
|
66
|
+
async getSpareparts(query) {
|
|
67
|
+
const associatedProducts = await this.fetchAssociatedProductsFor(query.forProduct, query.numberOfSpareparts || 4, "reactionaryspareparts");
|
|
68
|
+
const result = associatedProducts.map((product) => ({
|
|
69
|
+
associationIdentifier: {
|
|
70
|
+
key: `${query.forProduct.key}-sparepart-${product.identifier.key}`
|
|
71
|
+
},
|
|
72
|
+
associationReturnType: "productSearchResultItem",
|
|
73
|
+
product
|
|
74
|
+
}));
|
|
75
|
+
return success(result);
|
|
76
|
+
}
|
|
77
|
+
async getReplacements(query) {
|
|
78
|
+
const associatedProducts = await this.fetchAssociatedProductsFor(query.forProduct, query.numberOfReplacements || 4, "reactionaryreplacements");
|
|
79
|
+
const result = associatedProducts.map((product) => ({
|
|
80
|
+
associationIdentifier: {
|
|
81
|
+
key: `${query.forProduct.key}-replacement-${product.identifier.key}`
|
|
82
|
+
},
|
|
83
|
+
associationReturnType: "productSearchResultItem",
|
|
84
|
+
product
|
|
85
|
+
}));
|
|
86
|
+
return success(result);
|
|
87
|
+
}
|
|
88
|
+
parseSingle(_body) {
|
|
89
|
+
const heroVariant = _body.variants?.[0];
|
|
90
|
+
const identifier = { key: _body.external_id || _body.id };
|
|
91
|
+
const slug = _body.handle;
|
|
92
|
+
const name = heroVariant?.title || _body.title;
|
|
93
|
+
const variants = [];
|
|
94
|
+
if (heroVariant) {
|
|
95
|
+
variants.push(this.parseVariant(heroVariant, _body));
|
|
96
|
+
}
|
|
97
|
+
const result = {
|
|
98
|
+
identifier,
|
|
99
|
+
name,
|
|
100
|
+
slug,
|
|
101
|
+
variants
|
|
102
|
+
};
|
|
103
|
+
return result;
|
|
104
|
+
}
|
|
105
|
+
parseVariant(variant, product) {
|
|
106
|
+
const img = ImageSchema.parse({
|
|
107
|
+
sourceUrl: product.images?.[0].url ?? "",
|
|
108
|
+
altText: product.title || void 0
|
|
109
|
+
});
|
|
110
|
+
return ProductSearchResultItemVariantSchema.parse({
|
|
111
|
+
variant: ProductVariantIdentifierSchema.parse({
|
|
112
|
+
sku: variant.sku || ""
|
|
113
|
+
}),
|
|
114
|
+
image: img
|
|
115
|
+
});
|
|
116
|
+
}
|
|
117
|
+
}
|
|
118
|
+
__decorateClass([
|
|
119
|
+
Reactionary({
|
|
120
|
+
cache: true,
|
|
121
|
+
cacheTimeToLiveInSeconds: 300,
|
|
122
|
+
currencyDependentCaching: false,
|
|
123
|
+
localeDependentCaching: false
|
|
124
|
+
})
|
|
125
|
+
], MedusaProductAssociationsProvider.prototype, "getAccessories", 1);
|
|
126
|
+
__decorateClass([
|
|
127
|
+
Reactionary({
|
|
128
|
+
cache: true,
|
|
129
|
+
cacheTimeToLiveInSeconds: 300,
|
|
130
|
+
currencyDependentCaching: false,
|
|
131
|
+
localeDependentCaching: false
|
|
132
|
+
})
|
|
133
|
+
], MedusaProductAssociationsProvider.prototype, "getSpareparts", 1);
|
|
134
|
+
__decorateClass([
|
|
135
|
+
Reactionary({
|
|
136
|
+
cache: true,
|
|
137
|
+
cacheTimeToLiveInSeconds: 300,
|
|
138
|
+
currencyDependentCaching: false,
|
|
139
|
+
localeDependentCaching: false
|
|
140
|
+
})
|
|
141
|
+
], MedusaProductAssociationsProvider.prototype, "getReplacements", 1);
|
|
142
|
+
export {
|
|
143
|
+
MedusaProductAssociationsProvider
|
|
144
|
+
};
|
|
@@ -47,7 +47,7 @@ class MedusaProductRecommendationsProvider extends ProductRecommendationsProvide
|
|
|
47
47
|
const productsResponse = await client.store.product.list({
|
|
48
48
|
collection_id: [collection.id],
|
|
49
49
|
limit: query.numberOfRecommendations,
|
|
50
|
-
fields: "+variants.id,+variants.sku"
|
|
50
|
+
fields: "+variants.id,+variants.sku,+external_id"
|
|
51
51
|
});
|
|
52
52
|
if (debug.enabled) {
|
|
53
53
|
debug(`Found ${productsResponse.products.length} products in collection`);
|
|
@@ -78,7 +78,7 @@ class MedusaProductRecommendationsProvider extends ProductRecommendationsProvide
|
|
|
78
78
|
}
|
|
79
79
|
parseSearchResultItem(_body) {
|
|
80
80
|
const heroVariant = _body.variants?.[0];
|
|
81
|
-
const identifier = { key: _body.id };
|
|
81
|
+
const identifier = { key: _body.external_id || _body.id };
|
|
82
82
|
const slug = _body.handle;
|
|
83
83
|
const name = heroVariant?.title || _body.title;
|
|
84
84
|
const variants = [];
|
|
@@ -87,6 +87,7 @@ class MedusaSearchProvider extends ProductSearchProvider {
|
|
|
87
87
|
q: finalSearch,
|
|
88
88
|
...categoryIdToFind ? { category_id: categoryIdToFind } : {},
|
|
89
89
|
limit: payload.search.paginationOptions.pageSize,
|
|
90
|
+
fields: "+metadata.*,+external_id",
|
|
90
91
|
offset: (payload.search.paginationOptions.pageNumber - 1) * payload.search.paginationOptions.pageSize
|
|
91
92
|
});
|
|
92
93
|
const result = this.parsePaginatedResult(response);
|
|
@@ -126,7 +127,7 @@ class MedusaSearchProvider extends ProductSearchProvider {
|
|
|
126
127
|
}
|
|
127
128
|
parseSingle(_body) {
|
|
128
129
|
const heroVariant = _body.variants?.[0];
|
|
129
|
-
const identifier = { key: _body.id };
|
|
130
|
+
const identifier = { key: _body.external_id || _body.id };
|
|
130
131
|
const slug = _body.handle;
|
|
131
132
|
const name = heroVariant?.title || _body.title;
|
|
132
133
|
const variants = [];
|
|
@@ -27,6 +27,7 @@ class MedusaProductProvider extends ProductProvider {
|
|
|
27
27
|
constructor(config, cache, context, medusaApi) {
|
|
28
28
|
super(cache, context);
|
|
29
29
|
this.medusaApi = medusaApi;
|
|
30
|
+
this.alwaysIncludedFields = ["+metadata.*", "+categories.metadata.*", "+external_id"];
|
|
30
31
|
this.config = config;
|
|
31
32
|
}
|
|
32
33
|
async getById(payload) {
|
|
@@ -34,18 +35,21 @@ class MedusaProductProvider extends ProductProvider {
|
|
|
34
35
|
if (debug.enabled) {
|
|
35
36
|
debug(`Fetching product by ID: ${payload.identifier.key}`);
|
|
36
37
|
}
|
|
37
|
-
|
|
38
|
-
|
|
39
|
-
|
|
40
|
-
|
|
41
|
-
|
|
42
|
-
|
|
43
|
-
if (debug.enabled) {
|
|
44
|
-
debug(`Product with ID: ${payload.identifier.key} not found, returning empty product. Error %O `, error2);
|
|
38
|
+
const response = await client.store.product.list(
|
|
39
|
+
{
|
|
40
|
+
external_id: payload.identifier.key,
|
|
41
|
+
limit: 1,
|
|
42
|
+
offset: 0,
|
|
43
|
+
fields: this.alwaysIncludedFields.join(",")
|
|
45
44
|
}
|
|
46
|
-
|
|
45
|
+
);
|
|
46
|
+
if (response.count === 0) {
|
|
47
|
+
return error({
|
|
48
|
+
type: "NotFound",
|
|
49
|
+
identifier: payload
|
|
50
|
+
});
|
|
47
51
|
}
|
|
48
|
-
return success(this.parseSingle(response.
|
|
52
|
+
return success(this.parseSingle(response.products[0]));
|
|
49
53
|
}
|
|
50
54
|
async getBySlug(payload) {
|
|
51
55
|
const client = await this.medusaApi.getClient();
|
|
@@ -56,7 +60,7 @@ class MedusaProductProvider extends ProductProvider {
|
|
|
56
60
|
handle: payload.slug,
|
|
57
61
|
limit: 1,
|
|
58
62
|
offset: 0,
|
|
59
|
-
fields: "
|
|
63
|
+
fields: this.alwaysIncludedFields.join(",")
|
|
60
64
|
});
|
|
61
65
|
if (debug.enabled) {
|
|
62
66
|
debug(`Found ${response.count} products for slug: ${payload.slug}`);
|
|
@@ -79,12 +83,11 @@ class MedusaProductProvider extends ProductProvider {
|
|
|
79
83
|
if (!variant) {
|
|
80
84
|
throw new Error(`Variant with SKU ${sku} not found`);
|
|
81
85
|
}
|
|
82
|
-
product.variants = [];
|
|
83
|
-
product.variants.push(variant);
|
|
86
|
+
product.variants = product.variants?.filter((v) => v.sku === sku).concat(product.variants?.filter((v) => v.sku !== sku)) || [];
|
|
84
87
|
return success(this.parseSingle(product));
|
|
85
88
|
}
|
|
86
89
|
parseSingle(_body) {
|
|
87
|
-
const identifier = ProductIdentifierSchema.parse({ key: _body.id });
|
|
90
|
+
const identifier = ProductIdentifierSchema.parse({ key: _body.external_id || _body.id });
|
|
88
91
|
const name = _body.title;
|
|
89
92
|
const slug = _body.handle;
|
|
90
93
|
const description = _body.description || "" || _body.subtitle || "";
|
|
@@ -95,7 +98,7 @@ class MedusaProductProvider extends ProductProvider {
|
|
|
95
98
|
const sharedAttributes = this.parseAttributes(_body);
|
|
96
99
|
if (!_body.variants) {
|
|
97
100
|
debug("Product has no variants", _body);
|
|
98
|
-
throw new Error("Product has no variants " + _body.
|
|
101
|
+
throw new Error("Product has no variants " + _body.external_id);
|
|
99
102
|
}
|
|
100
103
|
const mainVariant = this.parseVariant(_body.variants[0], _body);
|
|
101
104
|
const otherVariants = [];
|
|
@@ -194,7 +197,11 @@ class MedusaProductProvider extends ProductProvider {
|
|
|
194
197
|
sharedAttributes.push(this.createSynthAttribute("material", "Material", _body.material));
|
|
195
198
|
}
|
|
196
199
|
if (_body.metadata) {
|
|
200
|
+
const keysToExclude = ["reactionaryaccessories", "reactionaryreplacements", "reactionaryspareparts"];
|
|
197
201
|
for (const [key, value] of Object.entries(_body.metadata)) {
|
|
202
|
+
if (keysToExclude.includes(key)) {
|
|
203
|
+
continue;
|
|
204
|
+
}
|
|
198
205
|
sharedAttributes.push(this.createSynthAttribute(key, key, String(value)));
|
|
199
206
|
}
|
|
200
207
|
}
|
package/src/index.d.ts
CHANGED
|
@@ -11,4 +11,5 @@ export * from './providers/order-search.provider.js';
|
|
|
11
11
|
export * from './providers/price.provider.js';
|
|
12
12
|
export * from './providers/product-search.provider.js';
|
|
13
13
|
export * from './providers/product-recommendations.provider.js';
|
|
14
|
+
export * from './providers/product-associations.provider.js';
|
|
14
15
|
export * from './providers/profile.provider.js';
|
|
@@ -0,0 +1,16 @@
|
|
|
1
|
+
import { ProductAssociationsProvider } from '@reactionary/core';
|
|
2
|
+
import type { ProductIdentifier, ProductAssociation, ProductSearchResultItem, ProductSearchResultItemVariant, ProductAssociationsGetAccessoriesQuery, ProductAssociationsGetSparepartsQuery, ProductAssociationsGetReplacementsQuery, Result, RequestContext, Cache } from '@reactionary/core';
|
|
3
|
+
import type { MedusaConfiguration } from '../schema/configuration.schema.js';
|
|
4
|
+
import type { MedusaAPI } from '../core/client.js';
|
|
5
|
+
import type { StoreProduct, StoreProductVariant } from '@medusajs/types';
|
|
6
|
+
export declare class MedusaProductAssociationsProvider extends ProductAssociationsProvider {
|
|
7
|
+
protected config: MedusaConfiguration;
|
|
8
|
+
protected medusa: MedusaAPI;
|
|
9
|
+
constructor(config: MedusaConfiguration, cache: Cache, context: RequestContext, medusa: MedusaAPI);
|
|
10
|
+
protected fetchAssociatedProductsFor(productKey: ProductIdentifier, maxNumberOfAssociations: number, attributeName: string): Promise<ProductSearchResultItem[]>;
|
|
11
|
+
getAccessories(query: ProductAssociationsGetAccessoriesQuery): Promise<Result<ProductAssociation[]>>;
|
|
12
|
+
getSpareparts(query: ProductAssociationsGetSparepartsQuery): Promise<Result<ProductAssociation[]>>;
|
|
13
|
+
getReplacements(query: ProductAssociationsGetReplacementsQuery): Promise<Result<ProductAssociation[]>>;
|
|
14
|
+
protected parseSingle(_body: StoreProduct): ProductSearchResultItem;
|
|
15
|
+
protected parseVariant(variant: StoreProductVariant, product: StoreProduct): ProductSearchResultItemVariant;
|
|
16
|
+
}
|
|
@@ -6,6 +6,7 @@ import type { StoreProduct, StoreProductVariant } from '@medusajs/types';
|
|
|
6
6
|
export declare class MedusaProductProvider extends ProductProvider {
|
|
7
7
|
medusaApi: MedusaAPI;
|
|
8
8
|
protected config: MedusaConfiguration;
|
|
9
|
+
protected alwaysIncludedFields: string[];
|
|
9
10
|
constructor(config: MedusaConfiguration, cache: Cache, context: RequestContext, medusaApi: MedusaAPI);
|
|
10
11
|
getById(payload: ProductQueryById): Promise<Result<Product>>;
|
|
11
12
|
getBySlug(payload: ProductQueryBySlug): Promise<Result<Product, NotFoundError>>;
|
|
@@ -10,6 +10,7 @@ export declare const MedusaCapabilitiesSchema: z.ZodObject<{
|
|
|
10
10
|
category: z.ZodOptional<z.ZodBoolean>;
|
|
11
11
|
profile: z.ZodOptional<z.ZodBoolean>;
|
|
12
12
|
productSearch: z.ZodOptional<z.ZodBoolean>;
|
|
13
|
+
productAssociations: z.ZodOptional<z.ZodBoolean>;
|
|
13
14
|
productRecommendations: z.ZodOptional<z.ZodBoolean>;
|
|
14
15
|
orderSearch: z.ZodOptional<z.ZodBoolean>;
|
|
15
16
|
}, z.core.$loose>;
|