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,125 @@
1
+ "use strict";
2
+ Object.defineProperty(exports, "__esModule", { value: true });
3
+ exports.PromotionWindowFilterProvider = void 0;
4
+ const base_filter_provider_1 = require("./base-filter-provider");
5
+ /**
6
+ * Filter provider for filtering products by promotion window.
7
+ *
8
+ * Handles promotion date range filtering based on metadata keys
9
+ * configured in plugin options. Supports filtering by:
10
+ * - Promotion start/end dates
11
+ * - Active on a specific date
12
+ * - Including open-ended promotions
13
+ *
14
+ * @example
15
+ * ```ts
16
+ * { promotion_window: { start: "2024-01-01", end: "2024-12-31" } }
17
+ * ```
18
+ */
19
+ class PromotionWindowFilterProvider extends base_filter_provider_1.BaseFilterProvider {
20
+ apply(filters, value, context) {
21
+ if (!value || typeof value !== "object" || Array.isArray(value)) {
22
+ return filters;
23
+ }
24
+ const promotionWindow = value;
25
+ // Get metadata keys from options
26
+ const startKey = context?.options?.promotion_window?.start_metadata_key || "promotion_start";
27
+ const endKey = context?.options?.promotion_window?.end_metadata_key || "promotion_end";
28
+ const includeOpen = promotionWindow.includeOpen !== undefined
29
+ ? promotionWindow.includeOpen
30
+ : context?.options?.promotion_window?.treat_open_ended_as_active ?? true;
31
+ // Build metadata filter for promotion window
32
+ // Note: Medusa's RemoteQuery doesn't support MongoDB-style operators ($gte, $lte, $or)
33
+ // For date range filtering in metadata, we can only do exact matches or array matching
34
+ // Date range filtering will need to be handled via post-query filtering or a different approach
35
+ // For now, we'll store the promotion window criteria in the filter context
36
+ // and let the service layer handle date comparisons after fetching results
37
+ // This is a limitation of Medusa's query system for metadata date filtering
38
+ const metadataFilters = {};
39
+ if (promotionWindow.start || promotionWindow.end) {
40
+ // Store date range in metadata - exact match only (limitation)
41
+ // Actual date range filtering should be done post-query
42
+ if (promotionWindow.start) {
43
+ const startDate = promotionWindow.start instanceof Date
44
+ ? promotionWindow.start
45
+ : new Date(promotionWindow.start);
46
+ // Store as ISO string for exact matching
47
+ // Note: This only matches exact dates, not ranges
48
+ metadataFilters[startKey] = startDate.toISOString();
49
+ }
50
+ if (promotionWindow.end) {
51
+ const endDate = promotionWindow.end instanceof Date
52
+ ? promotionWindow.end
53
+ : new Date(promotionWindow.end);
54
+ // Store as ISO string for exact matching
55
+ metadataFilters[endKey] = endDate.toISOString();
56
+ }
57
+ }
58
+ else if (promotionWindow.activeOn) {
59
+ // Filter by active on specific date - exact match only
60
+ const activeDate = promotionWindow.activeOn instanceof Date
61
+ ? promotionWindow.activeOn
62
+ : new Date(promotionWindow.activeOn);
63
+ // Store active date for exact matching
64
+ // Note: Full date range logic requires post-query filtering
65
+ metadataFilters[startKey] = activeDate.toISOString();
66
+ if (!includeOpen) {
67
+ metadataFilters[endKey] = activeDate.toISOString();
68
+ }
69
+ // Note: Open-ended promotion logic requires post-query filtering
70
+ }
71
+ else {
72
+ // No promotion window filter
73
+ return filters;
74
+ }
75
+ // Log warning about date range filtering limitation
76
+ console.warn(`[PromotionWindowFilterProvider] Date range filtering in metadata is limited. ` +
77
+ `Only exact date matches are supported. For date ranges, consider post-query filtering.`);
78
+ // Merge with existing metadata filters
79
+ const existingMetadata = filters.metadata || {};
80
+ return {
81
+ ...filters,
82
+ metadata: {
83
+ ...existingMetadata,
84
+ ...metadataFilters,
85
+ },
86
+ };
87
+ }
88
+ validate(value) {
89
+ if (value === undefined || value === null) {
90
+ return;
91
+ }
92
+ if (typeof value !== "object" || Array.isArray(value)) {
93
+ throw new Error("promotion_window must be an object");
94
+ }
95
+ const promotionWindow = value;
96
+ if (promotionWindow.start !== undefined) {
97
+ if (!(promotionWindow.start instanceof Date) &&
98
+ typeof promotionWindow.start !== "string") {
99
+ throw new Error("promotion_window.start must be a Date or ISO string");
100
+ }
101
+ }
102
+ if (promotionWindow.end !== undefined) {
103
+ if (!(promotionWindow.end instanceof Date) &&
104
+ typeof promotionWindow.end !== "string") {
105
+ throw new Error("promotion_window.end must be a Date or ISO string");
106
+ }
107
+ }
108
+ if (promotionWindow.activeOn !== undefined) {
109
+ if (!(promotionWindow.activeOn instanceof Date) &&
110
+ typeof promotionWindow.activeOn !== "string") {
111
+ throw new Error("promotion_window.activeOn must be a Date or ISO string");
112
+ }
113
+ }
114
+ if (promotionWindow.includeOpen !== undefined) {
115
+ if (typeof promotionWindow.includeOpen !== "boolean") {
116
+ throw new Error("promotion_window.includeOpen must be a boolean");
117
+ }
118
+ }
119
+ }
120
+ }
121
+ exports.PromotionWindowFilterProvider = PromotionWindowFilterProvider;
122
+ PromotionWindowFilterProvider.identifier = "promotion_window";
123
+ PromotionWindowFilterProvider.displayName = "Promotion Window Filter";
124
+ PromotionWindowFilterProvider.description = "Filters products by promotion date windows using metadata";
125
+ //# sourceMappingURL=data:application/json;base64,eyJ2ZXJzaW9uIjozLCJmaWxlIjoicHJvbW90aW9uLXdpbmRvdy1wcm92aWRlci5qcyIsInNvdXJjZVJvb3QiOiIiLCJzb3VyY2VzIjpbIi4uLy4uLy4uLy4uLy4uL3NyYy9wcm92aWRlcnMvZmlsdGVyLXByb3ZpZGVycy9wcm9tb3Rpb24td2luZG93LXByb3ZpZGVyLnRzIl0sIm5hbWVzIjpbXSwibWFwcGluZ3MiOiI7OztBQUFBLGlFQUErRTtBQUUvRTs7Ozs7Ozs7Ozs7OztHQWFHO0FBQ0gsTUFBYSw2QkFBOEIsU0FBUSx5Q0FBa0I7SUFNbkUsS0FBSyxDQUNILE9BQWdDLEVBQ2hDLEtBQWMsRUFDZCxPQUF1QjtRQUV2QixJQUFJLENBQUMsS0FBSyxJQUFJLE9BQU8sS0FBSyxLQUFLLFFBQVEsSUFBSSxLQUFLLENBQUMsT0FBTyxDQUFDLEtBQUssQ0FBQyxFQUFFLENBQUM7WUFDaEUsT0FBTyxPQUFPLENBQUE7UUFDaEIsQ0FBQztRQUVELE1BQU0sZUFBZSxHQUFHLEtBS3ZCLENBQUE7UUFFRCxpQ0FBaUM7UUFDakMsTUFBTSxRQUFRLEdBQ1osT0FBTyxFQUFFLE9BQU8sRUFBRSxnQkFBZ0IsRUFBRSxrQkFBa0IsSUFBSSxpQkFBaUIsQ0FBQTtRQUM3RSxNQUFNLE1BQU0sR0FDVixPQUFPLEVBQUUsT0FBTyxFQUFFLGdCQUFnQixFQUFFLGdCQUFnQixJQUFJLGVBQWUsQ0FBQTtRQUN6RSxNQUFNLFdBQVcsR0FDZixlQUFlLENBQUMsV0FBVyxLQUFLLFNBQVM7WUFDdkMsQ0FBQyxDQUFDLGVBQWUsQ0FBQyxXQUFXO1lBQzdCLENBQUMsQ0FBQyxPQUFPLEVBQUUsT0FBTyxFQUFFLGdCQUFnQixFQUFFLDBCQUEwQixJQUFJLElBQUksQ0FBQTtRQUU1RSw2Q0FBNkM7UUFDN0MsdUZBQXVGO1FBQ3ZGLHVGQUF1RjtRQUN2RixnR0FBZ0c7UUFFaEcsMkVBQTJFO1FBQzNFLDJFQUEyRTtRQUMzRSw0RUFBNEU7UUFFNUUsTUFBTSxlQUFlLEdBQTRCLEVBQUUsQ0FBQTtRQUVuRCxJQUFJLGVBQWUsQ0FBQyxL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
@@ -0,0 +1,92 @@
1
+ "use strict";
2
+ Object.defineProperty(exports, "__esModule", { value: true });
3
+ exports.RatingFilterProvider = void 0;
4
+ const base_filter_provider_1 = require("./base-filter-provider");
5
+ /**
6
+ * Filter provider for filtering products by rating.
7
+ *
8
+ * Handles rating filtering with min/max values. Only applies
9
+ * if rating is enabled in plugin options. Requires reviews
10
+ * if configured.
11
+ *
12
+ * @example
13
+ * ```ts
14
+ * { rating: { min: 4, max: 5, require_reviews: true } }
15
+ * ```
16
+ */
17
+ class RatingFilterProvider extends base_filter_provider_1.BaseFilterProvider {
18
+ apply(filters, value, context) {
19
+ // Check if rating is enabled
20
+ if (!context?.options?.rating?.enabled) {
21
+ return filters;
22
+ }
23
+ if (!value || typeof value !== "object" || Array.isArray(value)) {
24
+ return filters;
25
+ }
26
+ const rating = value;
27
+ // Get defaults from options
28
+ const min = rating.min !== undefined
29
+ ? rating.min
30
+ : context.options.rating.min ?? 0;
31
+ const max = rating.max !== undefined
32
+ ? rating.max
33
+ : context.options.rating.max ?? 5;
34
+ const requireReviews = rating.require_reviews !== undefined
35
+ ? rating.require_reviews
36
+ : context.options.rating.require_reviews ?? false;
37
+ // Build rating filter
38
+ // Note: Medusa's RemoteQuery doesn't support comparison operators ($gt, $lt, etc.)
39
+ // We can only filter by exact values or use post-query filtering for ranges
40
+ // For rating range filtering, we need to:
41
+ // 1. Filter products that have reviews (if requireReviews is true)
42
+ // 2. Calculate average_rating post-query and filter by range
43
+ const ratingFilters = {};
44
+ if (requireReviews) {
45
+ // Filter products that have at least one review
46
+ // Since we can't use $gt, we'll filter for products with count > 0
47
+ // This is a limitation - exact filtering requires post-query processing
48
+ // For now, we'll skip this filter and handle it post-query
49
+ console.warn(`[RatingFilterProvider] Rating range filtering (min: ${min}, max: ${max}) ` +
50
+ `requires post-query filtering. Medusa's query system doesn't support comparison operators.`);
51
+ }
52
+ // Note: Rating range filtering (min/max) requires:
53
+ // 1. Fetching products with total_rating_count and total_rating_sum
54
+ // 2. Calculating average_rating = total_rating_sum / total_rating_count
55
+ // 3. Filtering results post-query by the calculated average
56
+ // Return filters unchanged - rating filtering will be handled post-query
57
+ // This is a known limitation of Medusa's query system
58
+ return filters;
59
+ }
60
+ validate(value) {
61
+ if (value === undefined || value === null) {
62
+ return;
63
+ }
64
+ if (typeof value !== "object" || Array.isArray(value)) {
65
+ throw new Error("rating must be an object with min and/or max");
66
+ }
67
+ const rating = value;
68
+ if (rating.min !== undefined) {
69
+ if (typeof rating.min !== "number" || rating.min < 0 || rating.min > 5) {
70
+ throw new Error("rating.min must be a number between 0 and 5");
71
+ }
72
+ }
73
+ if (rating.max !== undefined) {
74
+ if (typeof rating.max !== "number" || rating.max < 0 || rating.max > 5) {
75
+ throw new Error("rating.max must be a number between 0 and 5");
76
+ }
77
+ }
78
+ if (rating.min !== undefined && rating.max !== undefined && rating.min > rating.max) {
79
+ throw new Error("rating.min must be less than or equal to rating.max");
80
+ }
81
+ if (rating.require_reviews !== undefined) {
82
+ if (typeof rating.require_reviews !== "boolean") {
83
+ throw new Error("rating.require_reviews must be a boolean");
84
+ }
85
+ }
86
+ }
87
+ }
88
+ exports.RatingFilterProvider = RatingFilterProvider;
89
+ RatingFilterProvider.identifier = "rating";
90
+ RatingFilterProvider.displayName = "Rating Filter";
91
+ RatingFilterProvider.description = "Filters products by rating (min/max)";
92
+ //# sourceMappingURL=data:application/json;base64,eyJ2ZXJzaW9uIjozLCJmaWxlIjoicmF0aW5nLXByb3ZpZGVyLmpzIiwic291cmNlUm9vdCI6IiIsInNvdXJjZXMiOlsiLi4vLi4vLi4vLi4vLi4vc3JjL3Byb3ZpZGVycy9maWx0ZXItcHJvdmlkZXJzL3JhdGluZy1wcm92aWRlci50cyJdLCJuYW1lcyI6W10sIm1hcHBpbmdzIjoiOzs7QUFBQSxpRUFBK0U7QUFFL0U7Ozs7Ozs7Ozs7O0dBV0c7QUFDSCxNQUFhLG9CQUFxQixTQUFRLHlDQUFrQjtJQUsxRCxLQUFLLENBQ0gsT0FBZ0MsRUFDaEMsS0FBYyxFQUNkLE9BQXVCO1FBRXZCLDZCQUE2QjtRQUM3QixJQUFJLENBQUMsT0FBTyxFQUFFLE9BQU8sRUFBRSxNQUFNLEVBQUUsT0FBTyxFQUFFLENBQUM7WUFDdkMsT0FBTyxPQUFPLENBQUE7UUFDaEIsQ0FBQztRQUVELElBQUksQ0FBQyxLQUFLLElBQUksT0FBTyxLQUFLLEtBQUssUUFBUSxJQUFJLEtBQUssQ0FBQyxPQUFPLENBQUMsS0FBSyxDQUFDLEVBQUUsQ0FBQztZQUNoRSxPQUFPLE9BQU8sQ0FBQTtRQUNoQixDQUFDO1FBRUQsTUFBTSxNQUFNLEdBQUcsS0FJZCxDQUFBO1FBRUQsNEJBQTRCO1FBQzVCLE1BQU0sR0FBRyxHQUNQLE1BQU0sQ0FBQyxHQUFHLEtBQUssU0FBUztZQUN0QixDQUFDLENBQUMsTUFBTSxDQUFDLEdBQUc7WUFDWixDQUFDLENBQUMsT0FBTyxDQUFDLE9BQU8sQ0FBQyxNQUFNLENBQUMsR0FBRyxJQUFJLENBQUMsQ0FBQTtRQUVyQyxNQUFNLEdBQUcsR0FDUCxNQUFNLENBQUMsR0FBRyxLQUFLLFNBQVM7WUFDdEIsQ0FBQyxDQUFDLE1BQU0sQ0FBQyxHQUFHO1lBQ1osQ0FBQyxDQUFDLE9BQU8sQ0FBQyxPQUFPLENBQUMsTUFBTSxDQUFDLEdBQUcsSUFBSSxDQUFDLENBQUE7UUFFckMsTUFBTSxjQUFjLEdBQ2xCLE1BQU0sQ0FBQyxlQUFlLEtBQUssU0FBUztZQUNsQyxDQUFDLENBQUMsTUFBTSxDQUFDLGVBQWU7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