@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 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
  }
@@ -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.14",
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.14",
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
- let response;
38
- try {
39
- response = await client.store.product.retrieve(payload.identifier.key, {
40
- fields: "+metadata,+categories.metadata.*"
41
- });
42
- } catch (error2) {
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
- return success(this.createEmptyProduct(payload.identifier.key));
45
+ );
46
+ if (response.count === 0) {
47
+ return error({
48
+ type: "NotFound",
49
+ identifier: payload
50
+ });
47
51
  }
48
- return success(this.parseSingle(response.product));
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: "+metadata.*"
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.id);
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
  }
@@ -11,7 +11,8 @@ const MedusaCapabilitiesSchema = CapabilitiesSchema.pick({
11
11
  orderSearch: true,
12
12
  inventory: true,
13
13
  identity: true,
14
- profile: true
14
+ profile: true,
15
+ productAssociations: true
15
16
  }).partial();
16
17
  export {
17
18
  MedusaCapabilitiesSchema
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>;