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.
- package/.medusa/server/src/api/store/product-helper/products/route.js +76 -0
- package/.medusa/server/src/api/store/product-helper/products/validators.js +2 -1
- package/.medusa/server/src/config/product-helper-options.js +15 -1
- package/.medusa/server/src/index.js +101 -0
- package/.medusa/server/src/providers/filter-providers/availability-provider.js +96 -0
- package/.medusa/server/src/providers/filter-providers/base-filter-provider.js +32 -0
- package/.medusa/server/src/providers/filter-providers/base-product-provider.js +122 -0
- package/.medusa/server/src/providers/filter-providers/category-provider.js +55 -0
- package/.medusa/server/src/providers/filter-providers/collection-provider.js +53 -0
- package/.medusa/server/src/providers/filter-providers/index.js +94 -0
- package/.medusa/server/src/providers/filter-providers/metadata-provider.js +88 -0
- package/.medusa/server/src/providers/filter-providers/price-range-provider.js +108 -0
- package/.medusa/server/src/providers/filter-providers/promotion-provider.js +197 -0
- package/.medusa/server/src/providers/filter-providers/promotion-window-provider.js +125 -0
- package/.medusa/server/src/providers/filter-providers/rating-provider.js +92 -0
- package/.medusa/server/src/services/dynamic-filter-service.js +814 -0
- package/.medusa/server/src/services/filter-provider-loader.js +155 -0
- package/.medusa/server/src/services/filter-provider-registry.js +142 -0
- package/.medusa/server/src/services/product-filter-service.js +230 -0
- package/.medusa/server/src/utils/query-parser.js +103 -0
- package/README.md +89 -0
- 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
|