medusa-product-helper 0.0.13 → 0.0.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.
Files changed (22) hide show
  1. package/.medusa/server/src/api/store/product-helper/products/route.js +76 -0
  2. package/.medusa/server/src/api/store/product-helper/products/validators.js +2 -1
  3. package/.medusa/server/src/config/product-helper-options.js +15 -1
  4. package/.medusa/server/src/index.js +101 -0
  5. package/.medusa/server/src/providers/filter-providers/availability-provider.js +96 -0
  6. package/.medusa/server/src/providers/filter-providers/base-filter-provider.js +32 -0
  7. package/.medusa/server/src/providers/filter-providers/base-product-provider.js +122 -0
  8. package/.medusa/server/src/providers/filter-providers/category-provider.js +55 -0
  9. package/.medusa/server/src/providers/filter-providers/collection-provider.js +53 -0
  10. package/.medusa/server/src/providers/filter-providers/index.js +94 -0
  11. package/.medusa/server/src/providers/filter-providers/metadata-provider.js +88 -0
  12. package/.medusa/server/src/providers/filter-providers/price-range-provider.js +108 -0
  13. package/.medusa/server/src/providers/filter-providers/promotion-provider.js +197 -0
  14. package/.medusa/server/src/providers/filter-providers/promotion-window-provider.js +125 -0
  15. package/.medusa/server/src/providers/filter-providers/rating-provider.js +92 -0
  16. package/.medusa/server/src/services/dynamic-filter-service.js +814 -0
  17. package/.medusa/server/src/services/filter-provider-loader.js +155 -0
  18. package/.medusa/server/src/services/filter-provider-registry.js +142 -0
  19. package/.medusa/server/src/services/product-filter-service.js +230 -0
  20. package/.medusa/server/src/utils/query-parser.js +103 -0
  21. package/README.md +89 -0
  22. package/package.json +3 -3
@@ -0,0 +1,155 @@
1
+ "use strict";
2
+ Object.defineProperty(exports, "__esModule", { value: true });
3
+ exports.loadFilterProviders = loadFilterProviders;
4
+ const base_filter_provider_1 = require("../providers/filter-providers/base-filter-provider");
5
+ /**
6
+ * Load filter providers from configuration.
7
+ *
8
+ * This function loads custom filter providers from file paths or module references.
9
+ * Supports both:
10
+ * - File paths (relative to baseDir or absolute)
11
+ * - Module references (npm package names)
12
+ *
13
+ * @param configs - Array of filter provider configurations
14
+ * @param options - Loading options
15
+ * @returns Array of instantiated filter provider instances
16
+ * @throws Error if provider cannot be loaded or instantiated
17
+ *
18
+ * @example
19
+ * ```ts
20
+ * // Load from file path
21
+ * const providers = await loadFilterProviders([
22
+ * "./src/providers/margin-provider.ts"
23
+ * ])
24
+ *
25
+ * // Load from module
26
+ * const providers = await loadFilterProviders([
27
+ * "@my-org/custom-filters/margin-provider"
28
+ * ])
29
+ *
30
+ * // Load with options
31
+ * const providers = await loadFilterProviders([
32
+ * { path: "./src/providers/margin-provider.ts", options: { minMargin: 10 } }
33
+ * ])
34
+ * ```
35
+ */
36
+ async function loadFilterProviders(configs, options = {}) {
37
+ const providers = [];
38
+ const baseDir = options.baseDir || process.cwd();
39
+ for (const config of configs) {
40
+ try {
41
+ let providerModule;
42
+ let providerPath;
43
+ // Resolve config to path
44
+ if (typeof config === "string") {
45
+ providerPath = config;
46
+ }
47
+ else {
48
+ providerPath = config.path;
49
+ }
50
+ // Try to load as module first (npm package)
51
+ try {
52
+ providerModule = await import(providerPath);
53
+ }
54
+ catch (moduleError) {
55
+ // If module import fails, try as file path
56
+ const path = require("path");
57
+ const fs = require("fs");
58
+ // Resolve absolute path
59
+ const absolutePath = path.isAbsolute(providerPath)
60
+ ? providerPath
61
+ : path.resolve(baseDir, providerPath);
62
+ // Check if file exists
63
+ if (!fs.existsSync(absolutePath)) {
64
+ throw new Error(`Filter provider file not found: ${absolutePath}`);
65
+ }
66
+ // Try dynamic import with file:// protocol for absolute paths
67
+ try {
68
+ providerModule = await import(absolutePath);
69
+ }
70
+ catch (fileError) {
71
+ // Try with require for CommonJS modules
72
+ try {
73
+ providerModule = require(absolutePath);
74
+ }
75
+ catch (requireError) {
76
+ throw new Error(`Failed to load filter provider from ${providerPath}: ${String(moduleError)}`);
77
+ }
78
+ }
79
+ }
80
+ // Extract provider class from module
81
+ // Support both default export and named export
82
+ let ProviderClass;
83
+ if (providerModule && typeof providerModule === "object") {
84
+ // Try default export first
85
+ if ("default" in providerModule &&
86
+ typeof providerModule.default === "function") {
87
+ ProviderClass = providerModule.default;
88
+ }
89
+ else {
90
+ // Try to find a class that extends BaseFilterProvider
91
+ // Look for exported classes
92
+ for (const [key, value] of Object.entries(providerModule)) {
93
+ if (typeof value === "function" &&
94
+ value.prototype &&
95
+ Object.getPrototypeOf(value.prototype)?.constructor ===
96
+ base_filter_provider_1.BaseFilterProvider) {
97
+ ProviderClass = value;
98
+ break;
99
+ }
100
+ }
101
+ }
102
+ }
103
+ if (!ProviderClass) {
104
+ throw new Error(`No filter provider class found in ${providerPath}. ` +
105
+ `Expected a class extending BaseFilterProvider as default export or named export.`);
106
+ }
107
+ // Validate that it's a BaseFilterProvider subclass
108
+ // Check for required static properties
109
+ if (!("identifier" in ProviderClass) ||
110
+ typeof ProviderClass.identifier !== "string") {
111
+ throw new Error(`Class in ${providerPath} does not appear to extend BaseFilterProvider. ` +
112
+ `Missing static 'identifier' property.`);
113
+ }
114
+ // Check for displayName
115
+ if (!("displayName" in ProviderClass) ||
116
+ typeof ProviderClass.displayName !== "string") {
117
+ throw new Error(`Class in ${providerPath} does not appear to extend BaseFilterProvider. ` +
118
+ `Missing static 'displayName' property.`);
119
+ }
120
+ // Instantiate provider
121
+ // If config has options, pass them to constructor (if supported)
122
+ const providerConfig = typeof config === "object" && config.options
123
+ ? config.options
124
+ : undefined;
125
+ // Instantiate provider
126
+ // We've validated that ProviderClass extends BaseFilterProvider and has required properties
127
+ // TypeScript sees it as abstract, but at runtime it's a concrete class
128
+ // Use type assertion to allow instantiation
129
+ let provider;
130
+ if (providerConfig) {
131
+ // Try to instantiate with options
132
+ try {
133
+ // @ts-expect-error - ProviderClass is validated at runtime to be a concrete subclass
134
+ provider = new ProviderClass(providerConfig);
135
+ }
136
+ catch {
137
+ // If constructor doesn't accept options, instantiate without
138
+ // @ts-expect-error - ProviderClass is validated at runtime to be a concrete subclass
139
+ provider = new ProviderClass();
140
+ }
141
+ }
142
+ else {
143
+ // @ts-expect-error - ProviderClass is validated at runtime to be a concrete subclass
144
+ provider = new ProviderClass();
145
+ }
146
+ providers.push(provider);
147
+ }
148
+ catch (error) {
149
+ const errorMessage = error instanceof Error ? error.message : String(error);
150
+ throw new Error(`Failed to load filter provider from config ${JSON.stringify(config)}: ${errorMessage}`);
151
+ }
152
+ }
153
+ return providers;
154
+ }
155
+ //# sourceMappingURL=data:application/json;base64,eyJ2ZXJzaW9uIjozLCJmaWxlIjoiZmlsdGVyLXByb3ZpZGVyLWxvYWRlci5qcyIsInNvdXJjZVJvb3QiOiIiLCJzb3VyY2VzIjpbIi4uLy4uLy4uLy4uL3NyYy9zZXJ2aWNlcy9maWx0ZXItcHJvdmlkZXItbG9hZGVyLnRzIl0sIm5hbWVzIjpbXSwibWFwcGluZ3MiOiI7O0FBNkNBLGtEQXNKQztBQW5NRCw2RkFBdUY7QUFjdkY7Ozs7Ozs7Ozs7Ozs7Ozs7Ozs7Ozs7Ozs7Ozs7OztHQThCRztBQUNJLEtBQUssVUFBVSxtQkFBbUIsQ0FDdkMsT0FBK0IsRUFDL0IsVUFBc0MsRUFBRTtJQUV4QyxNQUFNLFNBQVMsR0FBeUIsRUFBRSxDQUFBO0lBQzFDLE1BQU0sT0FBTyxHQUFHLE9BQU8sQ0FBQyxPQUFPLElBQUksT0FBTyxDQUFDLEdBQUcsRUFBRSxDQUFBO0lBRWhELEtBQUssTUFBTSxNQUFNLElBQUksT0FBTyxFQUFFLENBQUM7UUFDN0IsSUFBSSxDQUFDO1lBQ0gsSUFBSSxjQUF1QixDQUFBO1lBQzNCLElBQUksWUFBb0IsQ0FBQTtZQUV4Qix5QkFBeUI7WUFDekIsSUFBSSxPQUFPLE1BQU0sS0FBSyxRQUFRLEVBQUUsQ0FBQztnQkFDL0IsWUFBWSxHQUFHLE1BQU0sQ0FBQTtZQUN2QixDQUFDO2lCQUFNLENBQUM7Z0JBQ04sWUFBWSxHQUFHLE1BQU0sQ0FBQyxJQUFJLENBQUE7WUFDNUIsQ0FBQztZQUVELDRDQUE0QztZQUM1QyxJQUFJLENBQUM7Z0JBQ0gsY0FBYyxHQUFHLE1BQU0sTUFBTSxDQUFDLFlBQVksQ0FBQyxDQUFBO1lBQzdDLENBQUM7WUFBQyxPQUFPLFdBQVcsRUFBRSxDQUFDO2dCQUNyQiwyQ0FBMkM7Z0JBQzNDLE1BQU0sSUFBSSxHQUFHLE9BQU8sQ0FBQyxNQUFNLENBQUMsQ0FBQTtnQkFDNUIsTUFBTSxFQUFFLEdBQUcsT0FBTyxDQUFDLElBQUksQ0FBQyxDQUFBO2dCQUV4Qix3QkFBd0I7Z0JBQ3hCLE1BQU0sWUFBWSxHQUFHLElBQUksQ0FBQyxVQUFVLENBQUMsWUFBWSxD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
@@ -0,0 +1,142 @@
1
+ "use strict";
2
+ Object.defineProperty(exports, "__esModule", { value: true });
3
+ exports.FilterProviderRegistry = void 0;
4
+ /**
5
+ * Service for managing filter provider registration and retrieval.
6
+ *
7
+ * The registry maintains a map of filter providers keyed by their identifiers,
8
+ * ensuring uniqueness and providing fast lookup. Providers are registered
9
+ * at plugin initialization and can be retrieved by their identifier when
10
+ * applying filters.
11
+ *
12
+ * @example
13
+ * ```ts
14
+ * const registry = new FilterProviderRegistry()
15
+ * registry.register(new CategoryFilterProvider())
16
+ *
17
+ * const provider = registry.get("category_id")
18
+ * if (provider) {
19
+ * const filters = provider.apply({}, ["cat_123"])
20
+ * }
21
+ * ```
22
+ */
23
+ class FilterProviderRegistry {
24
+ constructor() {
25
+ this.providers = new Map();
26
+ }
27
+ /**
28
+ * Register a filter provider.
29
+ *
30
+ * Throws an error if a provider with the same identifier is already registered.
31
+ *
32
+ * @param provider - Filter provider instance to register
33
+ * @throws Error if provider identifier is already registered
34
+ *
35
+ * @example
36
+ * ```ts
37
+ * registry.register(new CategoryFilterProvider())
38
+ * ```
39
+ */
40
+ register(provider) {
41
+ const identifier = provider.constructor
42
+ .identifier;
43
+ if (!identifier) {
44
+ throw new Error("Filter provider must have a static 'identifier' property");
45
+ }
46
+ if (this.providers.has(identifier)) {
47
+ const existingProvider = this.providers.get(identifier);
48
+ const existingDisplayName = (existingProvider?.constructor)
49
+ .displayName || "Unknown";
50
+ throw new Error(`Filter provider with identifier "${identifier}" (${existingDisplayName}) is already registered. ` +
51
+ `Cannot register ${provider.constructor.displayName || "Unknown"}.`);
52
+ }
53
+ this.providers.set(identifier, provider);
54
+ }
55
+ /**
56
+ * Get a provider by identifier.
57
+ *
58
+ * @param identifier - Provider identifier to look up
59
+ * @returns Provider instance if found, undefined otherwise
60
+ *
61
+ * @example
62
+ * ```ts
63
+ * const provider = registry.get("category_id")
64
+ * if (provider) {
65
+ * // Use provider
66
+ * }
67
+ * ```
68
+ */
69
+ get(identifier) {
70
+ return this.providers.get(identifier);
71
+ }
72
+ /**
73
+ * Check if a provider is registered.
74
+ *
75
+ * @param identifier - Provider identifier to check
76
+ * @returns true if provider is registered, false otherwise
77
+ *
78
+ * @example
79
+ * ```ts
80
+ * if (registry.has("category_id")) {
81
+ * // Provider exists
82
+ * }
83
+ * ```
84
+ */
85
+ has(identifier) {
86
+ return this.providers.has(identifier);
87
+ }
88
+ /**
89
+ * Get all registered providers.
90
+ *
91
+ * @returns Array of all registered provider instances
92
+ *
93
+ * @example
94
+ * ```ts
95
+ * const allProviders = registry.getAll()
96
+ * allProviders.forEach(provider => {
97
+ * console.log(provider.constructor.identifier)
98
+ * })
99
+ * ```
100
+ */
101
+ getAll() {
102
+ return Array.from(this.providers.values());
103
+ }
104
+ /**
105
+ * Get all provider identifiers.
106
+ *
107
+ * @returns Array of all registered provider identifiers
108
+ *
109
+ * @example
110
+ * ```ts
111
+ * const identifiers = registry.getIdentifiers()
112
+ * // ["category_id", "collection_id", "price_range", ...]
113
+ * ```
114
+ */
115
+ getIdentifiers() {
116
+ return Array.from(this.providers.keys());
117
+ }
118
+ /**
119
+ * Clear all registered providers.
120
+ *
121
+ * Useful for testing or resetting the registry state.
122
+ *
123
+ * @example
124
+ * ```ts
125
+ * registry.clear()
126
+ * // All providers removed
127
+ * ```
128
+ */
129
+ clear() {
130
+ this.providers.clear();
131
+ }
132
+ /**
133
+ * Get the number of registered providers.
134
+ *
135
+ * @returns Number of registered providers
136
+ */
137
+ size() {
138
+ return this.providers.size;
139
+ }
140
+ }
141
+ exports.FilterProviderRegistry = FilterProviderRegistry;
142
+ //# sourceMappingURL=data:application/json;base64,eyJ2ZXJzaW9uIjozLCJmaWxlIjoiZmlsdGVyLXByb3ZpZGVyLXJlZ2lzdHJ5LmpzIiwic291cmNlUm9vdCI6IiIsInNvdXJjZXMiOlsiLi4vLi4vLi4vLi4vc3JjL3NlcnZpY2VzL2ZpbHRlci1wcm92aWRlci1yZWdpc3RyeS50cyJdLCJuYW1lcyI6W10sIm1hcHBpbmdzIjoiOzs7QUFFQTs7Ozs7Ozs7Ozs7Ozs7Ozs7O0dBa0JHO0FBQ0gsTUFBYSxzQkFBc0I7SUFBbkM7UUFDVSxjQUFTLEdBQW9DLElBQUksR0FBRyxFQUFFLENBQUE7SUFrSWhFLENBQUM7SUFoSUM7Ozs7Ozs7Ozs7OztPQVlHO0lBQ0gsUUFBUSxDQUFDLFFBQTRCO1FBQ25DLE1BQU0sVUFBVSxHQUFJLFFBQVEsQ0FBQyxXQUF5QzthQUNuRSxVQUFVLENBQUE7UUFFYixJQUFJLENBQUMsVUFBVSxFQUFFLENBQUM7WUFDaEIsTUFBTSxJQUFJLEtBQUssQ0FDYiwwREFBMEQsQ0FDM0QsQ0FBQTtRQUNILENBQUM7UUFFRCxJQUFJLElBQUksQ0FBQyxTQUFTLENBQUMsR0FBRyxDQUFDLFVBQVUsQ0FBQyxFQUFFLENBQUM7WUFDbkMsTUFBTSxnQkFBZ0IsR0FBRyxJQUFJLENBQUMsU0FBUyxDQUFDLEdBQUcsQ0FBQyxVQUFVLENBQUMsQ0FBQTtZQUN2RCxNQUFNLG1CQUFtQixHQUN2QixDQUFDLGdCQUFnQixFQUFFLFdBQXlDLENBQUE7aUJBQ3pELFdBQVcsSUFBSSxTQUFTLENBQUE7WUFFN0IsTUFBTSxJQUFJLEtBQUssQ0FDYixvQ0FBb0MsVUFBVSxNQUFNLG1CQUFtQiwyQkFBMkI7Z0JBQ2hHLG1CQUFvQixRQUFRLENBQUMsV0FBeUMsQ0FBQyxXQUFXLElBQUksU0FBUyxHQUFHLENBQ3JHLENBQUE7UUFDSCxDQUFDO1FBRUQsSUFBSSxDQUFDLFNBQVMsQ0FBQyxHQUFHLENBQUMsVUFBVSxFQUFFLFFBQVEsQ0FBQyxDQUFBO0lBQzFDLENBQUM7SUFFRDs7Ozs7Ozs7Ozs7OztPQWFHO0lBQ0gsR0FBRyxDQUFDLFVBQWtCO1FBQ3BCLE9BQU8sSUFBSSxDQUFDLFNBQVMsQ0FBQyxHQUFHLENBQUMsVUFBVSxDQUFDLENBQUE7SUFDdkMsQ0FBQztJQUVEOzs7Ozs7Ozs7Ozs7T0FZRztJQUNILEdBQUcsQ0FBQyxVQUFrQjtRQUNwQixPQUFPLElBQUksQ0FBQyxTQUFTLENBQUMsR0FBRyxDQUFDLFVBQVUsQ0FBQyxDQUFBO0lBQ3ZDLENBQUM7SUFFRDs7Ozs7Ozs7Ozs7O09BWUc7SUFDSCxNQUFNO1FBQ0osT0FBTyxLQUFLLENBQUMsSUFBSSxDQUFDLElBQUksQ0FBQyxTQUFTLENBQUMsTUFBTSxFQUFFLENBQUMsQ0FBQTtJQUM1QyxDQUFDO0lBRUQ7Ozs7Ozs7Ozs7T0FVRztJQUNILGNBQWM7UUFDWixPQUFPLEtBQUssQ0FBQyxJQUFJLENBQUMsSUFBSSxDQUFDLFNBQVMsQ0FBQyxJQUFJLEVBQUUsQ0FBQyxDQUFBO0lBQzFDLENBQUM7SUFFRDs7Ozs7Ozs7OztPQVVHO0lBQ0gsS0FBSztRQUNILElBQUksQ0FBQyxTQUFTLENBQUMsS0FBSyxFQUFFLENBQUE7SUFDeEIsQ0FBQztJQUVEOzs7O09BSUc7SUFDSCxJQUFJO1FBQ0YsT0FBTyxJQUFJLENBQUMsU0FBUyxDQUFDLElBQUksQ0FBQTtJQUM1QixDQUFDO0NBQ0Y7QUFuSUQsd0RBbUlDIn0=
@@ -0,0 +1,230 @@
1
+ "use strict";
2
+ Object.defineProperty(exports, "__esModule", { value: true });
3
+ exports.ProductFilterService = void 0;
4
+ const dynamic_filter_service_1 = require("./dynamic-filter-service");
5
+ const index_1 = require("../index");
6
+ /**
7
+ * Service that integrates DynamicFilterService with the existing ProductFilterPlan structure.
8
+ *
9
+ * This service bridges the gap between the legacy filter plan system and the new
10
+ * provider-based filter system, maintaining backward compatibility while enabling
11
+ * the new provider architecture.
12
+ *
13
+ * @example
14
+ * ```ts
15
+ * const service = new ProductFilterService(container)
16
+ * const result = await service.applyFilterPlan(plan, options)
17
+ * ```
18
+ */
19
+ class ProductFilterService {
20
+ constructor(container) {
21
+ // Try to resolve DynamicFilterService from container, create if not found
22
+ try {
23
+ this.dynamicFilterService = container.resolve(index_1.DYNAMIC_FILTER_SERVICE);
24
+ }
25
+ catch {
26
+ // Service not registered, create new instance
27
+ this.dynamicFilterService = new dynamic_filter_service_1.DynamicFilterService(container);
28
+ }
29
+ }
30
+ /**
31
+ * Apply filters using Medusa's request context (queryConfig, filterableFields, pricingContext).
32
+ *
33
+ * This method integrates with Medusa's official store products API format,
34
+ * merging custom filter providers with Medusa's built-in filterableFields.
35
+ *
36
+ * @param context - Medusa request context
37
+ * @param options - Plugin options
38
+ * @returns Products, count, and metadata matching Medusa's format
39
+ */
40
+ async applyFilterPlanWithMedusaContext(context, options) {
41
+ // Extract custom filter parameters from query (for price_range, etc.)
42
+ const customFilterParams = {};
43
+ // Parse price_range from query if present (price_range[min], price_range[max], price_range[currency_code])
44
+ if (context.query?.price_range && typeof context.query.price_range === "object" && !Array.isArray(context.query.price_range)) {
45
+ const priceRange = context.query.price_range;
46
+ const min = typeof priceRange.min === "string" || typeof priceRange.min === "number"
47
+ ? (typeof priceRange.min === "string" ? Number(priceRange.min) : priceRange.min)
48
+ : undefined;
49
+ const max = typeof priceRange.max === "string" || typeof priceRange.max === "number"
50
+ ? (typeof priceRange.max === "string" ? Number(priceRange.max) : priceRange.max)
51
+ : undefined;
52
+ if ((min !== undefined && !Number.isNaN(min)) || (max !== undefined && !Number.isNaN(max))) {
53
+ customFilterParams.price_range = {
54
+ min: min !== undefined && !Number.isNaN(min) ? min : undefined,
55
+ max: max !== undefined && !Number.isNaN(max) ? max : undefined,
56
+ currency_code: typeof priceRange.currency_code === "string" ? priceRange.currency_code : undefined,
57
+ };
58
+ }
59
+ }
60
+ // Parse promotion from query if present (promotion[min_discount_percentage], promotion[promotion_type], etc.)
61
+ if (context.query?.promotion && typeof context.query.promotion === "object" && !Array.isArray(context.query.promotion)) {
62
+ const promotion = context.query.promotion;
63
+ const promotionFilter = {};
64
+ // Parse min_discount_percentage
65
+ if (promotion.min_discount_percentage !== undefined) {
66
+ const minDiscount = typeof promotion.min_discount_percentage === "string"
67
+ ? Number(promotion.min_discount_percentage)
68
+ : typeof promotion.min_discount_percentage === "number"
69
+ ? promotion.min_discount_percentage
70
+ : undefined;
71
+ if (minDiscount !== undefined && !Number.isNaN(minDiscount) && minDiscount >= 0 && minDiscount <= 100) {
72
+ promotionFilter.min_discount_percentage = minDiscount;
73
+ }
74
+ }
75
+ // Parse promotion_type
76
+ if (promotion.promotion_type !== undefined && typeof promotion.promotion_type === "string") {
77
+ promotionFilter.promotion_type = promotion.promotion_type;
78
+ }
79
+ // Parse starts_at
80
+ if (promotion.starts_at !== undefined) {
81
+ if (promotion.starts_at instanceof Date) {
82
+ promotionFilter.starts_at = promotion.starts_at;
83
+ }
84
+ else if (typeof promotion.starts_at === "string") {
85
+ const startsAt = new Date(promotion.starts_at);
86
+ if (!Number.isNaN(startsAt.getTime())) {
87
+ promotionFilter.starts_at = startsAt;
88
+ }
89
+ }
90
+ }
91
+ // Parse ends_at
92
+ if (promotion.ends_at !== undefined) {
93
+ if (promotion.ends_at instanceof Date) {
94
+ promotionFilter.ends_at = promotion.ends_at;
95
+ }
96
+ else if (typeof promotion.ends_at === "string") {
97
+ const endsAt = new Date(promotion.ends_at);
98
+ if (!Number.isNaN(endsAt.getTime())) {
99
+ promotionFilter.ends_at = endsAt;
100
+ }
101
+ }
102
+ }
103
+ // Parse status
104
+ if (promotion.status !== undefined && typeof promotion.status === "string") {
105
+ promotionFilter.status = promotion.status;
106
+ }
107
+ // Parse include_open_ended
108
+ if (promotion.include_open_ended !== undefined) {
109
+ if (typeof promotion.include_open_ended === "boolean") {
110
+ promotionFilter.include_open_ended = promotion.include_open_ended;
111
+ }
112
+ else if (typeof promotion.include_open_ended === "string") {
113
+ promotionFilter.include_open_ended =
114
+ promotion.include_open_ended === "true" || promotion.include_open_ended === "1";
115
+ }
116
+ }
117
+ // Only add promotion filter if at least one field is present
118
+ if (promotionFilter.min_discount_percentage !== undefined ||
119
+ promotionFilter.promotion_type !== undefined ||
120
+ promotionFilter.starts_at !== undefined ||
121
+ promotionFilter.ends_at !== undefined ||
122
+ promotionFilter.status !== undefined ||
123
+ promotionFilter.include_open_ended !== undefined) {
124
+ customFilterParams.promotion = promotionFilter;
125
+ }
126
+ }
127
+ // Merge Medusa's filterableFields with custom filter params
128
+ // Custom filters take precedence for overlapping keys
129
+ const filterParams = {
130
+ ...context.filterableFields,
131
+ ...customFilterParams,
132
+ };
133
+ // Apply filters using DynamicFilterService
134
+ // Pass fields from queryConfig if available, otherwise undefined (will default to ["*"])
135
+ // This ensures all relations (variants, options, images, collection, type) are included
136
+ const applyOptions = {
137
+ filterParams,
138
+ options,
139
+ pagination: context.queryConfig?.pagination ? {
140
+ offset: context.queryConfig.pagination.skip,
141
+ limit: context.queryConfig.pagination.take,
142
+ } : undefined,
143
+ projection: context.queryConfig?.fields && context.queryConfig.fields.length > 0 ? {
144
+ fields: context.queryConfig.fields,
145
+ } : undefined, // undefined will default to ["*"] in DynamicFilterService
146
+ context: context.pricingContext ? {
147
+ pricingContext: context.pricingContext,
148
+ } : undefined,
149
+ };
150
+ return await this.dynamicFilterService.applyFilters(applyOptions);
151
+ }
152
+ /**
153
+ * Apply a ProductFilterPlan using the dynamic filter provider system.
154
+ *
155
+ * Converts the legacy filter plan structure into provider-compatible
156
+ * filter parameters and applies them using DynamicFilterService.
157
+ *
158
+ * @param plan - Product filter plan from buildProductFilterPlan()
159
+ * @param options - Plugin options
160
+ * @returns Products and count matching the filters
161
+ */
162
+ async applyFilterPlan(plan, options) {
163
+ // Convert filter plan to provider-compatible filter parameters
164
+ const filterParams = {};
165
+ // Base filters (id, handle, tags, sales_channel_id)
166
+ if (Object.keys(plan.baseFilters).length > 0) {
167
+ filterParams.base_product = plan.baseFilters;
168
+ }
169
+ // Category filter
170
+ if (plan.baseFilters.categories) {
171
+ const categories = plan.baseFilters.categories;
172
+ if (categories.id && categories.id.length > 0) {
173
+ filterParams.category_id = categories.id;
174
+ }
175
+ }
176
+ // Collection filter
177
+ if (plan.baseFilters.collection_id) {
178
+ filterParams.collection_id = plan.baseFilters.collection_id;
179
+ }
180
+ // Metadata filters
181
+ if (Object.keys(plan.metadataFilters).length > 0) {
182
+ filterParams.metadata = plan.metadataFilters;
183
+ }
184
+ // Price range filter
185
+ if (plan.priceRange) {
186
+ filterParams.price_range = {
187
+ min: plan.priceRange.min,
188
+ max: plan.priceRange.max,
189
+ currency_code: plan.priceRange.currency_code,
190
+ };
191
+ }
192
+ // Promotion window filter
193
+ if (plan.promotionWindow) {
194
+ filterParams.promotion_window = {
195
+ start: plan.promotionWindow.range?.start,
196
+ end: plan.promotionWindow.range?.end,
197
+ activeOn: plan.promotionWindow.activeOn,
198
+ includeOpen: plan.promotionWindow.includeOpen,
199
+ };
200
+ }
201
+ // Availability filter
202
+ if (plan.availability) {
203
+ filterParams.availability = {
204
+ statuses: plan.availability.statuses,
205
+ include_preorder: plan.availability.include_preorder,
206
+ include_backorder: plan.availability.include_backorder,
207
+ include_gift_cards: plan.availability.include_gift_cards,
208
+ publishable_only: plan.availability.publishable_only,
209
+ };
210
+ }
211
+ // Rating filter
212
+ if (plan.rating && plan.rating.enabled) {
213
+ filterParams.rating = {
214
+ min: plan.rating.min,
215
+ max: plan.rating.max,
216
+ require_reviews: plan.rating.require_reviews,
217
+ };
218
+ }
219
+ // Apply filters using DynamicFilterService
220
+ const applyOptions = {
221
+ filterParams,
222
+ options,
223
+ pagination: plan.pagination,
224
+ projection: plan.projection,
225
+ };
226
+ return await this.dynamicFilterService.applyFilters(applyOptions);
227
+ }
228
+ }
229
+ exports.ProductFilterService = ProductFilterService;
230
+ //# sourceMappingURL=data:application/json;base64,eyJ2ZXJzaW9uIjozLCJmaWxlIjoicHJvZHVjdC1maWx0ZXItc2VydmljZS5qcyIsInNvdXJjZVJvb3QiOiIiLCJzb3VyY2VzIjpbIi4uLy4uLy4uLy4uL3NyYy9zZXJ2aWNlcy9wcm9kdWN0LWZpbHRlci1zZXJ2aWNlLnRzIl0sIm5hbWVzIjpbXSwibWFwcGluZ3MiOiI7OztBQUNBLHFFQUF5RjtBQUN6RixvQ0FBaUQ7QUFJakQ7Ozs7Ozs7Ozs7OztHQVlHO0FBQ0gsTUFBYSxvQkFBb0I7SUFHL0IsWUFBWSxTQUEwQjtRQUNwQywwRUFBMEU7UUFDMUUsSUFBSSxDQUFDO1lBQ0gsSUFBSSxDQUFDLG9CQUFvQixHQUFHLFNBQVMsQ0FBQyxPQUFPLENBQzNDLDhCQUFzQixDQUN2QixDQUFBO1FBQ0gsQ0FBQztRQUFDLE1BQU0sQ0FBQztZQUNQLDhDQUE4QztZQUM5QyxJQUFJLENBQUMsb0JBQW9CLEdBQUcsSUFBSSw2Q0FBb0IsQ0FBQyxTQUFTLENBQUMsQ0FBQTtRQUNqRSxDQUFDO0lBQ0gsQ0FBQztJQUVEOzs7Ozs7Ozs7T0FTRztJQUNILEtBQUssQ0FBQyxnQ0FBZ0MsQ0FDcEMsT0FXQyxFQUNELE9BQTZCO1FBRTdCLHNFQUFzRTtRQUN0RSxNQUFNLGtCQUFrQixHQUE0QixFQUFFLENBQUE7UUFFdEQsMkdBQTJHO1FBQzNHLElBQUksT0FBTyxDQUFDLEtBQUssRUFBRSxXQUFXLElBQUksT0FBTyxPQUFPLENBQUMsS0FBSyxDQUFDLFdBQVcsS0FBSyxRQUFRLElBQUksQ0FBQyxLQUFLLENBQUMsT0FBTyxDQUFDLE9BQU8sQ0FBQyxLQUFLLENBQUMsV0FBVyxDQUFDLEVBQUUsQ0FBQztZQUM3SCxNQUFNLFVBQVUsR0FBRyxPQUFPLENBQUMsS0FBSyxDQUFDLFdBQXVFLENBQUE7WUFDeEcsTUFBTSxHQUFHLEdBQUcsT0FBTyxVQUFVLENBQUMsR0FBRyxLQUFLLFFBQVEsSUFBSSxPQUFPLFVBQVUsQ0FBQyxHQUFHLEtBQUssUUFBUTtnQkFDbEYsQ0FBQyxDQUFDLENBQUMsT0FBTyxVQUFVLENBQUMsR0FB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
@@ -0,0 +1,103 @@
1
+ "use strict";
2
+ /**
3
+ * Query Parameter Parser Utility
4
+ *
5
+ * Normalizes query parameters from both formats:
6
+ * - Bracketed flat: `price_range[min]=10&price_range[max]=100`
7
+ * - Nested objects: `{ price_range: { min: 10, max: 100 } }`
8
+ *
9
+ * This ensures consistent handling regardless of how the HTTP server
10
+ * or query parser processes the query string.
11
+ */
12
+ Object.defineProperty(exports, "__esModule", { value: true });
13
+ exports.normalizeQueryParams = normalizeQueryParams;
14
+ /**
15
+ * Parse a query parameter key that may be in bracketed format.
16
+ *
17
+ * Examples:
18
+ * - `price_range[min]` → `{ base: "price_range", key: "min" }`
19
+ * - `promotion[min_discount_percentage]` → `{ base: "promotion", key: "min_discount_percentage" }`
20
+ * - `category_id` → `{ base: "category_id", key: null }`
21
+ *
22
+ * @param key - Query parameter key (may be bracketed or flat)
23
+ * @returns Object with base key and nested key (if bracketed)
24
+ */
25
+ function parseBracketedKey(key) {
26
+ const bracketMatch = key.match(/^(.+)\[([^\]]+)\]$/);
27
+ if (bracketMatch) {
28
+ return {
29
+ base: bracketMatch[1],
30
+ key: bracketMatch[2],
31
+ };
32
+ }
33
+ return {
34
+ base: key,
35
+ key: null,
36
+ };
37
+ }
38
+ /**
39
+ * Normalize query parameters to handle both bracketed and nested formats.
40
+ *
41
+ * Converts bracketed flat keys like `price_range[min]=10` into nested objects
42
+ * like `{ price_range: { min: 10 } }`.
43
+ *
44
+ * Also handles already-nested objects, preserving them as-is.
45
+ *
46
+ * @param query - Raw query object (may contain bracketed keys or nested objects)
47
+ * @returns Normalized query object with nested structure
48
+ *
49
+ * @example
50
+ * ```ts
51
+ * // Input: { "price_range[min]": "10", "price_range[max]": "100" }
52
+ * // Output: { price_range: { min: "10", max: "100" } }
53
+ *
54
+ * // Input: { price_range: { min: 10, max: 100 } }
55
+ * // Output: { price_range: { min: 10, max: 100 } } (unchanged)
56
+ * ```
57
+ */
58
+ function normalizeQueryParams(query) {
59
+ const normalized = {};
60
+ const nestedKeys = {};
61
+ // First pass: collect all bracketed keys
62
+ for (const [key, value] of Object.entries(query)) {
63
+ const parsed = parseBracketedKey(key);
64
+ if (parsed.key !== null) {
65
+ // This is a bracketed key (e.g., price_range[min])
66
+ if (!nestedKeys[parsed.base]) {
67
+ nestedKeys[parsed.base] = {};
68
+ }
69
+ nestedKeys[parsed.base][parsed.key] = value;
70
+ }
71
+ else {
72
+ // This is a flat key (e.g., category_id)
73
+ // Check if it's already a nested object
74
+ if (value !== null &&
75
+ typeof value === "object" &&
76
+ !Array.isArray(value) &&
77
+ Object.getPrototypeOf(value) === Object.prototype) {
78
+ // Already nested, keep as-is
79
+ normalized[parsed.base] = value;
80
+ }
81
+ else {
82
+ // Flat value, add directly
83
+ normalized[parsed.base] = value;
84
+ }
85
+ }
86
+ }
87
+ // Second pass: merge nested keys (from bracketed format) with existing nested objects
88
+ for (const [baseKey, nestedValues] of Object.entries(nestedKeys)) {
89
+ if (baseKey in normalized && typeof normalized[baseKey] === "object" && normalized[baseKey] !== null && !Array.isArray(normalized[baseKey])) {
90
+ // Merge with existing nested object
91
+ normalized[baseKey] = {
92
+ ...normalized[baseKey],
93
+ ...nestedValues,
94
+ };
95
+ }
96
+ else {
97
+ // Create new nested object
98
+ normalized[baseKey] = nestedValues;
99
+ }
100
+ }
101
+ return normalized;
102
+ }
103
+ //# sourceMappingURL=data:application/json;base64,eyJ2ZXJzaW9uIjozLCJmaWxlIjoicXVlcnktcGFyc2VyLmpzIiwic291cmNlUm9vdCI6IiIsInNvdXJjZXMiOlsiLi4vLi4vLi4vLi4vc3JjL3V0aWxzL3F1ZXJ5LXBhcnNlci50cyJdLCJuYW1lcyI6W10sIm1hcHBpbmdzIjoiO0FBQUE7Ozs7Ozs7OztHQVNHOztBQStDSCxvREFpREM7QUE5RkQ7Ozs7Ozs7Ozs7R0FVRztBQUNILFNBQVMsaUJBQWlCLENBQUMsR0FBVztJQUNwQyxNQUFNLFlBQVksR0FBRyxHQUFHLENBQUMsS0FBSyxDQUFDLG9CQUFvQixDQUFDLENBQUE7SUFDcEQsSUFBSSxZQUFZLEVBQUUsQ0FBQztRQUNqQixPQUFPO1lBQ0wsSUFBSSxFQUFFLFlBQVksQ0FBQyxDQUFDLENBQUM7WUFDckIsR0FBRyxFQUFFLFlBQVksQ0FBQyxDQUFDLENBQUM7U0FDckIsQ0FBQTtJQUNILENBQUM7SUFDRCxPQUFPO1FBQ0wsSUFBSSxFQUFFLEdBQUc7UUFDVCxHQUFHLEVBQUUsSUFBSTtLQUNWLENBQUE7QUFDSCxDQUFDO0FBRUQ7Ozs7Ozs7Ozs7Ozs7Ozs7Ozs7R0FtQkc7QUFDSCxTQUFnQixvQkFBb0IsQ0FDbEMsS0FBOEI7SUFFOUIsTUFBTSxVQUFVLEdBQTRCLEVBQUUsQ0FBQTtJQUM5QyxNQUFNLFVBQVUsR0FBNEMsRUFBRSxDQUFBO0lBRTlELHlDQUF5QztJQUN6QyxLQUFLLE1BQU0sQ0FBQyxHQUFHLEVBQUUsS0FBSyxDQUFDLElBQUksTUFBTSxDQUFDLE9BQU8sQ0FBQyxLQUFLLENBQUMsRUFBRSxDQUFDO1FBQ2pELE1BQU0sTUFBTSxHQUFHLGlCQUFpQixDQUFDLEdBQUcsQ0FBQyxDQUFBO1FBRXJDLElBQUksTUFBTSxDQUFDLEdBQUcsS0FBSyxJQUFJLEVBQUUsQ0FBQztZQUN4QixtREFBbUQ7WUFDbkQsSUFBSSxDQUFDLFVBQVUsQ0FBQyxNQUFNLENBQUMsSUFBSSxDQUFDLEVBQUUsQ0FBQztnQkFDN0IsVUFBVSxDQUFDLE1BQU0sQ0FBQyxJQUFJLENBQUMsR0FBRyxFQUFFLENBQUE7WUFDOUIsQ0FBQztZQUNELFVBQVUsQ0FBQyxNQUFNLENBQUMsSUFBSSxDQUFDLENBQUMsTUFBTSxDQUFDLEdBQUcsQ0FBQyxHQUFHLEtBQUssQ0FBQTtRQUM3QyxDQUFDO2FBQU0sQ0FBQztZQUNOLHlDQUF5QztZQUN6Qyx3Q0FBd0M7WUFDeEMsSUFDRSxLQUFLLEtBQUssSUFBSTtnQkFDZCxPQUFPLEtBQUssS0FBSyxRQUFRO2dCQUN6QixDQUFDLEtBQUssQ0FBQyxPQUFPLENBQUMsS0FBSyxDQUFDO2dCQUNyQixNQUFNLENBQUMsY0FBYyxDQUFDLEtBQUssQ0FBQyxLQUFLLE1BQU0sQ0FBQyxTQUFTLEVBQ2pELENBQUM7Z0JBQ0QsNkJBQTZCO2dCQUM3QixVQUFVLENBQUMsTUFBTSxDQUFDLElBQUksQ0FBQyxHQUFHLEtBQUssQ0FBQTtZQUNqQyxDQUFDO2lCQUFNLENBQUM7Z0JBQ04sMkJBQTJCO2dCQUMzQixVQUFVLENBQUMsTUFBTSxDQUFDLElBQUksQ0FBQyxHQUFHLEtBQUssQ0FBQTtZQUNqQyxDQUFDO1FBQ0gsQ0FBQztJQUNILENBQUM7SUFFRCxzRkFBc0Y7SUFDdEYsS0FBSyxNQUFNLENBQUMsT0FBTyxFQUFFLFlBQVksQ0FBQyxJQUFJLE1BQU0sQ0FBQyxPQUFPLENBQUMsVUFBVSxDQUFDLEVBQUUsQ0FBQztRQUNqRSxJQUFJLE9BQU8sSUFBSSxVQUFVLElBQUksT0FBTyxVQUFVLENBQUMsT0FBTyxDQUFDLEtBQUssUUFBUSxJQUFJLFVBQVUsQ0FBQyxPQUFPLENBQUMsS0FBSyxJQUFJLElBQUksQ0FBQyxLQUFLLENBQUMsT0FBTyxDQUFDLFVBQVUsQ0FBQyxPQUFPLENBQUMsQ0FBQyxFQUFFLENBQUM7WUFDNUksb0NBQW9DO1lBQ3BDLFVBQVUsQ0FBQyxPQUFPLENBQUMsR0FBRztnQkFDcEIsR0FBSSxVQUFVLENBQUMsT0FBTyxDQUE2QjtnQkFDbkQsR0FBRyxZQUFZO2FBQ2hCLENBQUE7UUFDSCxDQUFDO2FBQU0sQ0FBQztZQUNOLDJCQUEyQjtZQUMzQixVQUFVLENBQUMsT0FBTyxDQUFDLEdBQUcsWUFBWSxDQUFBO1FBQ3BDLENBQUM7SUFDSCxDQUFDO0lBRUQsT0FBTyxVQUFVLENBQUE7QUFDbkIsQ0FBQyJ9