medusa-product-helper 0.0.16 → 0.0.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 (26) hide show
  1. package/.medusa/server/src/admin/index.js +59 -117
  2. package/.medusa/server/src/admin/index.mjs +59 -117
  3. package/.medusa/server/src/api/store/product-helper/products/route.js +137 -7
  4. package/.medusa/server/src/api/store/product-helper/products/validators.js +11 -1
  5. package/.medusa/server/src/providers/filter-providers/availability-provider.js +53 -67
  6. package/.medusa/server/src/providers/filter-providers/base-filter-provider.js +1 -19
  7. package/.medusa/server/src/providers/filter-providers/base-product-provider.js +37 -100
  8. package/.medusa/server/src/providers/filter-providers/category-provider.js +15 -34
  9. package/.medusa/server/src/providers/filter-providers/collection-provider.js +15 -32
  10. package/.medusa/server/src/providers/filter-providers/index.js +13 -49
  11. package/.medusa/server/src/providers/filter-providers/metadata-provider.js +43 -58
  12. package/.medusa/server/src/providers/filter-providers/price-range-provider.js +66 -79
  13. package/.medusa/server/src/providers/filter-providers/promotion-provider.js +106 -169
  14. package/.medusa/server/src/providers/filter-providers/promotion-window-provider.js +53 -93
  15. package/.medusa/server/src/providers/filter-providers/rating-provider.js +47 -70
  16. package/.medusa/server/src/services/dynamic-filter-service.js +822 -736
  17. package/.medusa/server/src/services/filter-provider-loader.js +91 -139
  18. package/.medusa/server/src/services/filter-provider-registry.js +8 -107
  19. package/.medusa/server/src/services/product-filter-service.js +183 -172
  20. package/.medusa/server/src/shared/product-metadata/utils.js +66 -116
  21. package/.medusa/server/src/utils/query-builders/product-filters.js +89 -111
  22. package/.medusa/server/src/utils/query-parser.js +24 -76
  23. package/.medusa/server/src/workflows/add-to-wishlist.js +12 -26
  24. package/.medusa/server/src/workflows/get-wishlist.js +53 -51
  25. package/.medusa/server/src/workflows/remove-from-wishlist.js +3 -8
  26. package/package.json +1 -1
@@ -6,45 +6,35 @@ const react = require("react");
6
6
  const reactQuery = require("@tanstack/react-query");
7
7
  const METADATA_FIELD_TYPES = ["number", "text", "file", "bool"];
8
8
  const VALID_FIELD_TYPES = new Set(METADATA_FIELD_TYPES);
9
+ const BOOLEAN_TRUES = /* @__PURE__ */ new Set(["true", "1", "yes", "y", "on"]);
10
+ const BOOLEAN_FALSES = /* @__PURE__ */ new Set(["false", "0", "no", "n", "off"]);
9
11
  function normalizeMetadataDescriptors(input) {
10
- if (!Array.isArray(input)) {
11
- return [];
12
- }
12
+ if (!Array.isArray(input)) return [];
13
13
  const seenKeys = /* @__PURE__ */ new Set();
14
- const normalized = [];
15
- for (const item of input) {
16
- if (!item || typeof item !== "object") {
17
- continue;
18
- }
19
- const key = getNormalizedKey(item.key);
20
- if (!key || seenKeys.has(key)) {
21
- continue;
22
- }
23
- const type = getNormalizedType(item.type);
24
- if (!type) {
25
- continue;
26
- }
27
- const label = getNormalizedLabel(item.label);
28
- const filterable = typeof item.filterable === "boolean" ? item.filterable : Boolean(item.filterable);
29
- normalized.push({
14
+ return input.filter(
15
+ (item) => item && typeof item === "object"
16
+ ).map((item) => {
17
+ const key = normalizeKey(item.key);
18
+ const type = normalizeType(item.type);
19
+ const label = normalizeLabel(item.label);
20
+ const filterable = !!item.filterable;
21
+ return { key, type, label, filterable };
22
+ }).filter(({ key, type }) => key && type && !seenKeys.has(key)).map(({ key, type, label, filterable }) => {
23
+ seenKeys.add(key);
24
+ return {
30
25
  key,
31
26
  type,
32
- ...label ? { label } : {},
33
- ...filterable ? { filterable: true } : {}
34
- });
35
- seenKeys.add(key);
36
- }
37
- return normalized;
27
+ ...label && { label },
28
+ ...filterable && { filterable: true }
29
+ };
30
+ });
38
31
  }
39
32
  function buildInitialFormState(descriptors, metadata) {
40
- return descriptors.reduce(
41
- (acc, descriptor) => {
42
- const currentValue = metadata && typeof metadata === "object" ? metadata[descriptor.key] : void 0;
43
- acc[descriptor.key] = normalizeFormValue(descriptor, currentValue);
44
- return acc;
45
- },
46
- {}
47
- );
33
+ const base = metadata && typeof metadata === "object" ? metadata : {};
34
+ return descriptors.reduce((acc, descriptor) => {
35
+ acc[descriptor.key] = normalizeFormValue(descriptor, base[descriptor.key]);
36
+ return acc;
37
+ }, {});
48
38
  }
49
39
  function buildMetadataPayload({
50
40
  descriptors,
@@ -53,13 +43,12 @@ function buildMetadataPayload({
53
43
  }) {
54
44
  const base = originalMetadata && typeof originalMetadata === "object" ? { ...originalMetadata } : {};
55
45
  descriptors.forEach((descriptor) => {
56
- const rawValue = values[descriptor.key];
57
- const coerced = coerceMetadataValue(descriptor, rawValue);
58
- if (typeof coerced === "undefined") {
46
+ const coerced = coerceMetadataValue(descriptor, values[descriptor.key]);
47
+ if (coerced === void 0) {
59
48
  delete base[descriptor.key];
60
- return;
49
+ } else {
50
+ base[descriptor.key] = coerced;
61
51
  }
62
- base[descriptor.key] = coerced;
63
52
  });
64
53
  return base;
65
54
  }
@@ -70,38 +59,27 @@ function hasMetadataChanges({
70
59
  }) {
71
60
  const next = buildMetadataPayload({ descriptors, values, originalMetadata });
72
61
  const prev = originalMetadata && typeof originalMetadata === "object" ? originalMetadata : {};
73
- return descriptors.some((descriptor) => {
74
- const prevValue = prev[descriptor.key];
75
- const nextValue = next[descriptor.key];
76
- return !isDeepEqual(prevValue, nextValue);
77
- });
62
+ return descriptors.some(({ key }) => !isDeepEqual(prev[key], next[key]));
78
63
  }
79
64
  function validateValueForDescriptor(descriptor, value) {
80
65
  if (descriptor.type === "number") {
81
- if (value === "" || value === null || typeof value === "undefined") {
82
- return void 0;
83
- }
84
- const numericValue = typeof value === "number" ? value : Number(String(value).trim());
85
- if (Number.isNaN(numericValue)) {
86
- return "Enter a valid number";
87
- }
66
+ if (value == null || value === "") return void 0;
67
+ const num = typeof value === "number" ? value : Number(String(value).trim());
68
+ return isNaN(num) ? "Enter a valid number" : void 0;
88
69
  }
89
70
  if (descriptor.type === "file") {
90
- if (!value) {
91
- return void 0;
92
- }
71
+ if (!value) return void 0;
93
72
  try {
94
73
  new URL(String(value).trim());
95
- } catch (err) {
74
+ return void 0;
75
+ } catch {
96
76
  return "Enter a valid URL";
97
77
  }
98
78
  }
99
79
  return void 0;
100
80
  }
101
81
  function normalizeFormValue(descriptor, currentValue) {
102
- if (descriptor.type === "bool") {
103
- return Boolean(currentValue);
104
- }
82
+ if (descriptor.type === "bool") return Boolean(currentValue);
105
83
  if ((descriptor.type === "number" || descriptor.type === "text") && typeof currentValue === "number") {
106
84
  return currentValue.toString();
107
85
  }
@@ -111,78 +89,42 @@ function normalizeFormValue(descriptor, currentValue) {
111
89
  return "";
112
90
  }
113
91
  function coerceMetadataValue(descriptor, value) {
114
- if (value === "" || value === null || typeof value === "undefined") {
115
- return void 0;
116
- }
92
+ if (value == null || value === "") return void 0;
117
93
  if (descriptor.type === "bool") {
118
- if (typeof value === "boolean") {
119
- return value;
120
- }
121
- if (typeof value === "number") {
122
- return value !== 0;
123
- }
124
- if (typeof value === "string") {
125
- const normalized = value.trim().toLowerCase();
126
- if (!normalized) {
127
- return void 0;
128
- }
129
- if (["true", "1", "yes", "y", "on"].includes(normalized)) {
130
- return true;
131
- }
132
- if (["false", "0", "no", "n", "off"].includes(normalized)) {
133
- return false;
134
- }
135
- }
94
+ if (typeof value === "boolean") return value;
95
+ if (typeof value === "number") return value !== 0;
96
+ const normalized = String(value).trim().toLowerCase();
97
+ if (!normalized) return void 0;
98
+ if (BOOLEAN_TRUES.has(normalized)) return true;
99
+ if (BOOLEAN_FALSES.has(normalized)) return false;
136
100
  return Boolean(value);
137
101
  }
138
102
  if (descriptor.type === "number") {
139
- if (typeof value === "number") {
140
- return value;
141
- }
142
- const parsed = Number(String(value).trim());
143
- return Number.isNaN(parsed) ? void 0 : parsed;
103
+ const num = typeof value === "number" ? value : Number(String(value).trim());
104
+ return isNaN(num) ? void 0 : num;
144
105
  }
145
106
  return String(value).trim();
146
107
  }
147
- function getNormalizedKey(value) {
148
- if (typeof value !== "string") {
149
- return null;
150
- }
151
- const trimmed = value.trim();
152
- return trimmed.length ? trimmed : null;
108
+ function normalizeKey(value) {
109
+ return typeof value === "string" ? value.trim() || void 0 : void 0;
153
110
  }
154
- function getNormalizedType(value) {
155
- if (typeof value !== "string") {
156
- return null;
157
- }
111
+ function normalizeType(value) {
112
+ if (typeof value !== "string") return void 0;
158
113
  const type = value.trim().toLowerCase();
159
- return VALID_FIELD_TYPES.has(type) ? type : null;
114
+ return VALID_FIELD_TYPES.has(type) ? type : void 0;
160
115
  }
161
- function getNormalizedLabel(value) {
162
- if (typeof value !== "string") {
163
- return void 0;
164
- }
165
- const trimmed = value.trim();
166
- return trimmed.length ? trimmed : void 0;
116
+ function normalizeLabel(value) {
117
+ return typeof value === "string" ? value.trim() || void 0 : void 0;
167
118
  }
168
119
  function isDeepEqual(a, b) {
169
- if (a === b) {
170
- return true;
171
- }
172
- if (typeof a === "object" && typeof b === "object" && a !== null && b !== null) {
173
- const aKeys = Object.keys(a);
174
- const bKeys = Object.keys(b);
175
- if (aKeys.length !== bKeys.length) {
176
- return false;
177
- }
178
- return aKeys.every(
179
- (key) => isDeepEqual(
180
- a[key],
181
- b[key]
182
- )
183
- );
184
- }
185
- return false;
120
+ if (a === b) return true;
121
+ if (!a || !b || typeof a !== "object" || typeof b !== "object") return false;
122
+ const aKeys = Object.keys(a);
123
+ const bKeys = Object.keys(b);
124
+ if (aKeys.length !== bKeys.length) return false;
125
+ return aKeys.every(
126
+ (key) => isDeepEqual(a[key], b[key])
127
+ );
186
128
  }
187
129
  const CONFIG_ENDPOINT = "/admin/product-metadata-config";
188
130
  const QUERY_KEY = ["medusa-product-helper", "metadata-config"];
@@ -5,45 +5,35 @@ import { useState, useEffect, useMemo, useRef } from "react";
5
5
  import { useQuery, useQueryClient } from "@tanstack/react-query";
6
6
  const METADATA_FIELD_TYPES = ["number", "text", "file", "bool"];
7
7
  const VALID_FIELD_TYPES = new Set(METADATA_FIELD_TYPES);
8
+ const BOOLEAN_TRUES = /* @__PURE__ */ new Set(["true", "1", "yes", "y", "on"]);
9
+ const BOOLEAN_FALSES = /* @__PURE__ */ new Set(["false", "0", "no", "n", "off"]);
8
10
  function normalizeMetadataDescriptors(input) {
9
- if (!Array.isArray(input)) {
10
- return [];
11
- }
11
+ if (!Array.isArray(input)) return [];
12
12
  const seenKeys = /* @__PURE__ */ new Set();
13
- const normalized = [];
14
- for (const item of input) {
15
- if (!item || typeof item !== "object") {
16
- continue;
17
- }
18
- const key = getNormalizedKey(item.key);
19
- if (!key || seenKeys.has(key)) {
20
- continue;
21
- }
22
- const type = getNormalizedType(item.type);
23
- if (!type) {
24
- continue;
25
- }
26
- const label = getNormalizedLabel(item.label);
27
- const filterable = typeof item.filterable === "boolean" ? item.filterable : Boolean(item.filterable);
28
- normalized.push({
13
+ return input.filter(
14
+ (item) => item && typeof item === "object"
15
+ ).map((item) => {
16
+ const key = normalizeKey(item.key);
17
+ const type = normalizeType(item.type);
18
+ const label = normalizeLabel(item.label);
19
+ const filterable = !!item.filterable;
20
+ return { key, type, label, filterable };
21
+ }).filter(({ key, type }) => key && type && !seenKeys.has(key)).map(({ key, type, label, filterable }) => {
22
+ seenKeys.add(key);
23
+ return {
29
24
  key,
30
25
  type,
31
- ...label ? { label } : {},
32
- ...filterable ? { filterable: true } : {}
33
- });
34
- seenKeys.add(key);
35
- }
36
- return normalized;
26
+ ...label && { label },
27
+ ...filterable && { filterable: true }
28
+ };
29
+ });
37
30
  }
38
31
  function buildInitialFormState(descriptors, metadata) {
39
- return descriptors.reduce(
40
- (acc, descriptor) => {
41
- const currentValue = metadata && typeof metadata === "object" ? metadata[descriptor.key] : void 0;
42
- acc[descriptor.key] = normalizeFormValue(descriptor, currentValue);
43
- return acc;
44
- },
45
- {}
46
- );
32
+ const base = metadata && typeof metadata === "object" ? metadata : {};
33
+ return descriptors.reduce((acc, descriptor) => {
34
+ acc[descriptor.key] = normalizeFormValue(descriptor, base[descriptor.key]);
35
+ return acc;
36
+ }, {});
47
37
  }
48
38
  function buildMetadataPayload({
49
39
  descriptors,
@@ -52,13 +42,12 @@ function buildMetadataPayload({
52
42
  }) {
53
43
  const base = originalMetadata && typeof originalMetadata === "object" ? { ...originalMetadata } : {};
54
44
  descriptors.forEach((descriptor) => {
55
- const rawValue = values[descriptor.key];
56
- const coerced = coerceMetadataValue(descriptor, rawValue);
57
- if (typeof coerced === "undefined") {
45
+ const coerced = coerceMetadataValue(descriptor, values[descriptor.key]);
46
+ if (coerced === void 0) {
58
47
  delete base[descriptor.key];
59
- return;
48
+ } else {
49
+ base[descriptor.key] = coerced;
60
50
  }
61
- base[descriptor.key] = coerced;
62
51
  });
63
52
  return base;
64
53
  }
@@ -69,38 +58,27 @@ function hasMetadataChanges({
69
58
  }) {
70
59
  const next = buildMetadataPayload({ descriptors, values, originalMetadata });
71
60
  const prev = originalMetadata && typeof originalMetadata === "object" ? originalMetadata : {};
72
- return descriptors.some((descriptor) => {
73
- const prevValue = prev[descriptor.key];
74
- const nextValue = next[descriptor.key];
75
- return !isDeepEqual(prevValue, nextValue);
76
- });
61
+ return descriptors.some(({ key }) => !isDeepEqual(prev[key], next[key]));
77
62
  }
78
63
  function validateValueForDescriptor(descriptor, value) {
79
64
  if (descriptor.type === "number") {
80
- if (value === "" || value === null || typeof value === "undefined") {
81
- return void 0;
82
- }
83
- const numericValue = typeof value === "number" ? value : Number(String(value).trim());
84
- if (Number.isNaN(numericValue)) {
85
- return "Enter a valid number";
86
- }
65
+ if (value == null || value === "") return void 0;
66
+ const num = typeof value === "number" ? value : Number(String(value).trim());
67
+ return isNaN(num) ? "Enter a valid number" : void 0;
87
68
  }
88
69
  if (descriptor.type === "file") {
89
- if (!value) {
90
- return void 0;
91
- }
70
+ if (!value) return void 0;
92
71
  try {
93
72
  new URL(String(value).trim());
94
- } catch (err) {
73
+ return void 0;
74
+ } catch {
95
75
  return "Enter a valid URL";
96
76
  }
97
77
  }
98
78
  return void 0;
99
79
  }
100
80
  function normalizeFormValue(descriptor, currentValue) {
101
- if (descriptor.type === "bool") {
102
- return Boolean(currentValue);
103
- }
81
+ if (descriptor.type === "bool") return Boolean(currentValue);
104
82
  if ((descriptor.type === "number" || descriptor.type === "text") && typeof currentValue === "number") {
105
83
  return currentValue.toString();
106
84
  }
@@ -110,78 +88,42 @@ function normalizeFormValue(descriptor, currentValue) {
110
88
  return "";
111
89
  }
112
90
  function coerceMetadataValue(descriptor, value) {
113
- if (value === "" || value === null || typeof value === "undefined") {
114
- return void 0;
115
- }
91
+ if (value == null || value === "") return void 0;
116
92
  if (descriptor.type === "bool") {
117
- if (typeof value === "boolean") {
118
- return value;
119
- }
120
- if (typeof value === "number") {
121
- return value !== 0;
122
- }
123
- if (typeof value === "string") {
124
- const normalized = value.trim().toLowerCase();
125
- if (!normalized) {
126
- return void 0;
127
- }
128
- if (["true", "1", "yes", "y", "on"].includes(normalized)) {
129
- return true;
130
- }
131
- if (["false", "0", "no", "n", "off"].includes(normalized)) {
132
- return false;
133
- }
134
- }
93
+ if (typeof value === "boolean") return value;
94
+ if (typeof value === "number") return value !== 0;
95
+ const normalized = String(value).trim().toLowerCase();
96
+ if (!normalized) return void 0;
97
+ if (BOOLEAN_TRUES.has(normalized)) return true;
98
+ if (BOOLEAN_FALSES.has(normalized)) return false;
135
99
  return Boolean(value);
136
100
  }
137
101
  if (descriptor.type === "number") {
138
- if (typeof value === "number") {
139
- return value;
140
- }
141
- const parsed = Number(String(value).trim());
142
- return Number.isNaN(parsed) ? void 0 : parsed;
102
+ const num = typeof value === "number" ? value : Number(String(value).trim());
103
+ return isNaN(num) ? void 0 : num;
143
104
  }
144
105
  return String(value).trim();
145
106
  }
146
- function getNormalizedKey(value) {
147
- if (typeof value !== "string") {
148
- return null;
149
- }
150
- const trimmed = value.trim();
151
- return trimmed.length ? trimmed : null;
107
+ function normalizeKey(value) {
108
+ return typeof value === "string" ? value.trim() || void 0 : void 0;
152
109
  }
153
- function getNormalizedType(value) {
154
- if (typeof value !== "string") {
155
- return null;
156
- }
110
+ function normalizeType(value) {
111
+ if (typeof value !== "string") return void 0;
157
112
  const type = value.trim().toLowerCase();
158
- return VALID_FIELD_TYPES.has(type) ? type : null;
113
+ return VALID_FIELD_TYPES.has(type) ? type : void 0;
159
114
  }
160
- function getNormalizedLabel(value) {
161
- if (typeof value !== "string") {
162
- return void 0;
163
- }
164
- const trimmed = value.trim();
165
- return trimmed.length ? trimmed : void 0;
115
+ function normalizeLabel(value) {
116
+ return typeof value === "string" ? value.trim() || void 0 : void 0;
166
117
  }
167
118
  function isDeepEqual(a, b) {
168
- if (a === b) {
169
- return true;
170
- }
171
- if (typeof a === "object" && typeof b === "object" && a !== null && b !== null) {
172
- const aKeys = Object.keys(a);
173
- const bKeys = Object.keys(b);
174
- if (aKeys.length !== bKeys.length) {
175
- return false;
176
- }
177
- return aKeys.every(
178
- (key) => isDeepEqual(
179
- a[key],
180
- b[key]
181
- )
182
- );
183
- }
184
- return false;
119
+ if (a === b) return true;
120
+ if (!a || !b || typeof a !== "object" || typeof b !== "object") return false;
121
+ const aKeys = Object.keys(a);
122
+ const bKeys = Object.keys(b);
123
+ if (aKeys.length !== bKeys.length) return false;
124
+ return aKeys.every(
125
+ (key) => isDeepEqual(a[key], b[key])
126
+ );
185
127
  }
186
128
  const CONFIG_ENDPOINT = "/admin/product-metadata-config";
187
129
  const QUERY_KEY = ["medusa-product-helper", "metadata-config"];
@@ -35,6 +35,90 @@ const GET = async (req, res) => {
35
35
  // Validate query parameters using zod schema
36
36
  // This ensures type safety and catches invalid inputs early
37
37
  const validatedQuery = validators_1.StoreProductHelperFilterQuerySchema.parse(normalizedQuery);
38
+ // Extract region_id (required field)
39
+ const regionId = validatedQuery.region_id;
40
+ if (!regionId) {
41
+ res.status(400).json({
42
+ message: "region_id is required",
43
+ });
44
+ return;
45
+ }
46
+ // Fetch region to get currency_code
47
+ const query = req.scope.resolve(utils_1.ContainerRegistrationKeys.QUERY);
48
+ const regionResult = await query.graph({
49
+ entity: "region",
50
+ fields: ["id", "currency_code"],
51
+ filters: { id: regionId },
52
+ });
53
+ console.log(`[ProductHelperRoute] Region fetch result:`, {
54
+ hasData: !!regionResult.data,
55
+ dataLength: Array.isArray(regionResult.data) ? regionResult.data.length : 0,
56
+ regionId
57
+ });
58
+ const region = Array.isArray(regionResult.data) ? regionResult.data[0] : null;
59
+ if (!region) {
60
+ console.error(`[ProductHelperRoute] Region not found: ${regionId}`);
61
+ res.status(404).json({
62
+ message: `Region with id ${regionId} not found`,
63
+ });
64
+ return;
65
+ }
66
+ // Extract currency_code and validate it
67
+ const currencyCode = region.currency_code;
68
+ console.log(`[ProductHelperRoute] Region currency_code:`, {
69
+ currencyCode,
70
+ type: typeof currencyCode,
71
+ trimmed: typeof currencyCode === "string" ? currencyCode.trim() : null
72
+ });
73
+ if (!currencyCode || typeof currencyCode !== "string" || currencyCode.trim().length === 0) {
74
+ console.error(`[ProductHelperRoute] Invalid currency_code for region ${regionId}:`, currencyCode);
75
+ res.status(400).json({
76
+ message: `Region with id ${regionId} has no valid currency_code`,
77
+ });
78
+ return;
79
+ }
80
+ const trimmedCurrencyCode = currencyCode.trim();
81
+ // Log req.pricingContext if it exists
82
+ if (req.pricingContext) {
83
+ console.log(`[ProductHelperRoute] req.pricingContext exists:`, {
84
+ hasCurrencyCode: !!req.pricingContext.currency_code,
85
+ currencyCode: req.pricingContext.currency_code,
86
+ keys: Object.keys(req.pricingContext)
87
+ });
88
+ }
89
+ else {
90
+ console.log(`[ProductHelperRoute] req.pricingContext does not exist`);
91
+ }
92
+ // Build pricing context with region_id and currency_code
93
+ // Don't trust req.pricingContext - always build fresh with currency_code from region
94
+ // Only preserve other properties from req.pricingContext if currency_code is valid
95
+ const pricingContext = {
96
+ region_id: regionId,
97
+ currency_code: trimmedCurrencyCode, // Always set currency_code from region first
98
+ };
99
+ // Only spread req.pricingContext if it has a valid currency_code
100
+ // Otherwise, we might inherit an invalid or missing currency_code
101
+ if (req.pricingContext) {
102
+ const existingCurrencyCode = req.pricingContext.currency_code;
103
+ if (typeof existingCurrencyCode === "string" && existingCurrencyCode.trim().length > 0) {
104
+ // Preserve other properties from req.pricingContext, but use our currency_code
105
+ const existingContext = req.pricingContext;
106
+ Object.keys(existingContext).forEach(key => {
107
+ if (key !== "currency_code" && key !== "region_id") {
108
+ pricingContext[key] = existingContext[key];
109
+ }
110
+ });
111
+ console.log(`[ProductHelperRoute] Preserved properties from req.pricingContext (excluding currency_code and region_id)`);
112
+ }
113
+ else {
114
+ console.warn(`[ProductHelperRoute] req.pricingContext exists but has invalid currency_code, ignoring it`);
115
+ }
116
+ }
117
+ console.log(`[ProductHelperRoute] Final pricing context:`, {
118
+ region_id: pricingContext.region_id,
119
+ currency_code: pricingContext.currency_code,
120
+ keys: Object.keys(pricingContext)
121
+ });
38
122
  // Resolve plugin options
39
123
  const configModule = req.scope.resolve(utils_1.ContainerRegistrationKeys.CONFIG_MODULE);
40
124
  const options = (0, product_helper_options_1.resolveProductHelperOptions)(configModule);
@@ -44,16 +128,62 @@ const GET = async (req, res) => {
44
128
  const { products, count, metadata } = await filterService.applyFilterPlanWithMedusaContext({
45
129
  queryConfig: req.queryConfig,
46
130
  filterableFields: req.filterableFields || {},
47
- pricingContext: req.pricingContext,
48
- query: validatedQuery,
131
+ pricingContext,
132
+ query: { ...validatedQuery, currency_code: currencyCode },
49
133
  }, options);
50
- // Return response matching Medusa's format
51
- res.json({
134
+ // Calculate min_price and max_price if price_range=true
135
+ // Only consider prices in the selected region's currency
136
+ let minPrice = null;
137
+ let maxPrice = null;
138
+ if (validatedQuery.price_range === true) {
139
+ for (const product of products) {
140
+ if (!product.variants || !Array.isArray(product.variants)) {
141
+ continue;
142
+ }
143
+ for (const variant of product.variants) {
144
+ // Prioritize calculated_price if available (includes price list overrides)
145
+ let priceToUse;
146
+ if (variant.calculated_price?.calculated_amount !== undefined &&
147
+ variant.calculated_price?.currency_code === currencyCode) {
148
+ priceToUse = variant.calculated_price.calculated_amount;
149
+ }
150
+ else if (variant.prices && Array.isArray(variant.prices)) {
151
+ // Find price matching the selected currency
152
+ const matchingPrice = variant.prices.find((p) => p.currency_code === currencyCode);
153
+ if (matchingPrice?.amount !== undefined) {
154
+ priceToUse = matchingPrice.amount;
155
+ }
156
+ }
157
+ if (priceToUse !== undefined && typeof priceToUse === "number") {
158
+ if (minPrice === null || priceToUse < minPrice) {
159
+ minPrice = priceToUse;
160
+ }
161
+ if (maxPrice === null || priceToUse > maxPrice) {
162
+ maxPrice = priceToUse;
163
+ }
164
+ }
165
+ }
166
+ }
167
+ }
168
+ // Build response object
169
+ const response = {
52
170
  products: products,
53
171
  count: metadata?.count || count || products.length,
54
172
  offset: metadata?.skip || 0,
55
- limit: metadata?.take || products.length,
56
- });
173
+ // Ensure limit is never 0 - use metadata take, validated query limit, or default to 20
174
+ limit: (metadata?.take && metadata.take > 0)
175
+ ? metadata.take
176
+ : (validatedQuery.limit && validatedQuery.limit > 0)
177
+ ? validatedQuery.limit
178
+ : (products.length > 0 ? products.length : 20),
179
+ };
180
+ // Add price range fields if price_range=true
181
+ if (validatedQuery.price_range === true) {
182
+ response.min_price = minPrice;
183
+ response.max_price = maxPrice;
184
+ }
185
+ // Return response matching Medusa's format
186
+ res.json(response);
57
187
  }
58
188
  catch (error) {
59
189
  // Handle validation errors
@@ -73,4 +203,4 @@ const GET = async (req, res) => {
73
203
  }
74
204
  };
75
205
  exports.GET = GET;
76
- //# sourceMappingURL=data:application/json;base64,eyJ2ZXJzaW9uIjozLCJmaWxlIjoicm91dGUuanMiLCJzb3VyY2VSb290IjoiIiwic291cmNlcyI6WyIuLi8uLi8uLi8uLi8uLi8uLi8uLi9zcmMvYXBpL3N0b3JlL3Byb2R1Y3QtaGVscGVyL3Byb2R1Y3RzL3JvdXRlLnRzIl0sIm5hbWVzIjpbXSwibWFwcGluZ3MiOiI7OztBQUVBLHFEQUFxRTtBQUNyRSxzRkFBdUY7QUFDdkYsd0ZBQWtGO0FBQ2xGLGlFQUFxRTtBQUNyRSw2Q0FFcUI7QUFnQnJCOzs7Ozs7Ozs7Ozs7Ozs7Ozs7O0dBbUJHO0FBQ0ksTUFBTSxHQUFHLEdBQUcsS0FBSyxFQUN0QixHQUF1QixFQUN2QixHQUF1RCxFQUN2RCxFQUFFO0lBQ0YsSUFBSSxDQUFDO1FBQ0gseUVBQXlFO1FBQ3pFLDhEQUE4RDtRQUM5RCxNQUFNLFFBQVEsR0FBRyxHQUFHLENBQUMsS0FBSyxJQUFJLEVBQUUsQ0FBQTtRQUNoQyxNQUFNLGVBQWUsR0FBRyxJQUFBLG1DQUFvQixFQUMxQyxRQUFtQyxDQUNwQyxDQUFBO1FBRUQsNkNBQTZDO1FBQzdDLDREQUE0RDtRQUM1RCxNQUFNLGNBQWMsR0FBRyxnREFBbUMsQ0FBQyxLQUFLLENBQzlELGVBQWUsQ0FDaEIsQ0FBQTtRQUVELHlCQUF5QjtRQUN6QixNQUFNLFlBQVksR0FBRyxHQUFHLENBQUMsS0FBSyxDQUFDLE9BQU8sQ0FBQyxpQ0FBeUIsQ0FBQyxhQUFhLENBQUMsQ0FBQTtRQUMvRSxNQUFNLE9BQU8sR0FBRyxJQUFBLG9EQUEyQixFQUFDLFlBQVksQ0FBQyxDQUFBO1FBRXpELDJDQUEyQztRQUMzQyxtRUFBbUU7UUFDbkUsTUFBTSxhQUFhLEdBQUcsSUFBSSw2Q0FBb0IsQ0FBQyxHQUFHLENBQUMsS0FBSyxDQUFDLENBQUE7UUFDekQsTUFBTSxFQUFFLFFBQVEsRUFBRSxLQUFLLEVBQUUsUUFBUSxFQUFFLEdBQUcsTUFBTSxhQUFhLENBQUMsZ0NBQWdDLENBQ3hGO1lBQ0UsV0FBVyxFQUFFLEdBQUcsQ0FBQyxXQUFXO1lBQzVCLGdCQUFnQixFQUFFLEdBQUcsQ0FBQyxnQkFBZ0IsSUFBSSxFQUFFO1lBQzVDLGNBQWMsRUFBRSxHQUFHLENBQUMsY0FBYztZQUNsQyxLQUFLLEVBQUUsY0FBeUM7U0FDakQsRUFDRCxPQUFPLENBQ1IsQ0FBQTtRQUVELDJDQUEyQztRQUMzQyxHQUFHLENBQUMsSUFBSSxDQUFDO1lBQ1AsUUFBUSxFQUFFLFFBQW9DO1lBQzlDLEtBQUssRUFBRSxRQUFRLEVBQUUsS0FBSyxJQUFJLEtBQUssSUFBSSxRQUFRLENBQUMsTUFBTTtZQUNsRCxNQUFNLEVBQUUsUUFBUSxFQUFFLElBQUksSUFBSSxDQUFDO1lBQzNCLEtBQUssRUFBRSxRQUFRLEVBQUUsSUFBSSxJQUFJLFFBQVEsQ0FBQyxNQUFNO1NBQ3pDLENBQUMsQ0FBQTtJQUNKLENBQUM7SUFBQyxPQUFPLEtBQUssRUFBRSxDQUFDO1FBQ2YsMkJBQTJCO1FBQzNCLElBQUksS0FBSyxJQUFJLE9BQU8sS0FBSyxLQUFLLFFBQVEsSUFBSSxRQUFRLElBQUksS0FBSyxFQUFFLENBQUM7WUFDNUQsdUJBQXVCO1lBQ3ZCLEdBQUcsQ0FBQyxNQUFNLENBQUMsR0FBRyxDQUFDLENBQUMsSUFBSSxDQUFDO2dCQUNuQixPQUFPLEVBQUUsMEJBQTBCO2dCQUNuQyxNQUFNLEVBQUcsS0FBK0IsQ0FBQyxNQUFNO2FBQ0MsQ0FBQyxDQUFBO1lBQ25ELE9BQU07UUFDUixDQUFDO1FBRUQsc0JBQXNCO1FBQ3RCLE1BQU0sWUFBWSxHQUNoQixLQUFLLFlBQVksS0FBSyxDQUFDLENBQUMsQ0FBQyxLQUFLLENBQUMsT0FBTyxDQUFDLENBQUMsQ0FBQyx1QkFBdUIsQ0FBQTtRQUNsRSxHQUFHLENBQUMsTUFBTSxDQUFDLEdBQUcsQ0FBQyxDQUFDLElBQUksQ0FBQztZQUNuQixPQUFPLEVBQUUsWUFBWTtTQUMyQixDQUFDLENBQUE7SUFDckQsQ0FBQztBQUNILENBQUMsQ0FBQTtBQTVEWSxRQUFBLEdBQUcsT0E0RGYifQ==
206
+ //# sourceMappingURL=data:application/json;base64,eyJ2ZXJzaW9uIjozLCJmaWxlIjoicm91dGUuanMiLCJzb3VyY2VSb290IjoiIiwic291cmNlcyI6WyIuLi8uLi8uLi8uLi8uLi8uLi8uLi9zcmMvYXBpL3N0b3JlL3Byb2R1Y3QtaGVscGVyL3Byb2R1Y3RzL3JvdXRlLnRzIl0sIm5hbWVzIjpbXSwibWFwcGluZ3MiOiI7OztBQUVBLHFEQUFxRTtBQUVyRSxzRkFBdUY7QUFDdkYsd0ZBQWtGO0FBQ2xGLGlFQUFxRTtBQUNyRSw2Q0FFcUI7QUFnQnJCOzs7Ozs7Ozs7Ozs7Ozs7Ozs7O0dBbUJHO0FBQ0ksTUFBTSxHQUFHLEdBQUcsS0FBSyxFQUN0QixHQUF1QixFQUN2QixHQUF1RCxFQUN2RCxFQUFFO0lBQ0YsSUFBSSxDQUFDO1FBQ0gseUVBQXlFO1FBQ3pFLDhEQUE4RDtRQUM5RCxNQUFNLFFBQVEsR0FBRyxHQUFHLENBQUMsS0FBSyxJQUFJLEVBQUUsQ0FBQTtRQUNoQyxNQUFNLGVBQWUsR0FBRyxJQUFBLG1DQUFvQixFQUMxQyxRQUFtQyxDQUNwQyxDQUFBO1FBRUQsNkNBQTZDO1FBQzdDLDREQUE0RDtRQUM1RCxNQUFNLGNBQWMsR0FBRyxnREFBbUMsQ0FBQyxLQUFLLENBQzlELGVBQWUsQ0FDaEIsQ0FBQTtRQUVELHFDQUFxQztRQUNyQyxNQUFNLFFBQVEsR0FBRyxjQUFjLENBQUMsU0FBUyxDQUFBO1FBQ3pDLElBQUksQ0FBQyxRQUFRLEVBQUUsQ0FBQztZQUNkLEdBQUcsQ0FBQyxNQUFNLENBQUMsR0FBRyxDQUFDLENBQUMsSUFBSSxDQUFDO2dCQUNuQixPQUFPLEVBQUUsdUJBQXVCO2FBQ2dCLENBQUMsQ0FBQTtZQUNuRCxPQUFNO1FBQ1IsQ0FBQztRQUVELG9DQUFvQztRQUNwQyxNQUFNLEtBQUssR0FBRyxHQUFHLENBQUMsS0FBSyxDQUFDLE9BQU8sQ0FBUSxpQ0FBeUIsQ0FBQyxLQUFLLENBQUMsQ0FBQTtRQUN2RSxNQUFNLFlBQVksR0FBRyxNQUFNLEtBQUssQ0FBQyxLQUFLLENBQUM7WUFDckMsTUFBTSxFQUFFLFFBQVE7WUFDaEIs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