@63klabs/cache-data 1.3.8 → 1.3.9

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.
@@ -107,7 +107,11 @@ class AppConfig {
107
107
  static _ssmParameters = null;
108
108
 
109
109
  /**
110
- * Initialize the Config class
110
+ * Initialize the Config class with asynchronous parallel execution.
111
+ *
112
+ * This method returns immediately (synchronously) while initialization operations
113
+ * execute asynchronously in parallel. Use AppConfig.promise() to wait for all
114
+ * initialization to complete before accessing initialized configuration.
111
115
  *
112
116
  * @param {object} options Configuration options
113
117
  * @param {object} options.settings Application settings retrieved by Config.settings()
@@ -124,8 +128,10 @@ class AppConfig {
124
128
  * @param {object} options.responses.rssResponses
125
129
  * @param {object} options.responses.textResponses
126
130
  * @param {object} options.ssmParameters Parameter Store
127
- * @returns {Promise<void>}
131
+ * @param {boolean} [options.debug=false] Enable debug logging
132
+ * @returns {boolean} True if initialization started successfully, false on synchronous error
128
133
  * @example
134
+ * // Initialize configuration (returns immediately)
129
135
  * const { Config } = require("./config");
130
136
  * Config.init({
131
137
  * settings: {
@@ -143,48 +149,100 @@ class AppConfig {
143
149
  * }
144
150
  * }
145
151
  * });
152
+ *
153
+ * // Wait for all initialization to complete
154
+ * await Config.promise();
155
+ *
156
+ * // Now safe to access initialized configuration
157
+ * const settings = Config.settings();
158
+ * const conn = Config.getConn('myConnection');
146
159
  */
147
160
  static init(options = {}) {
148
161
 
149
- try {
162
+ try {
150
163
 
151
- const debug = (options?.debug === true);
152
- if (debug) {
153
- DebugAndLog.debug("Config Init in debug mode");
154
- }
164
+ const debug = (options?.debug === true);
165
+ if (debug) {
166
+ DebugAndLog.debug("Config Init in debug mode");
167
+ }
155
168
 
156
- if (options.settings) {
157
- AppConfig._settings = options.settings;
158
- if (debug) { DebugAndLog.debug("Settings initialized", AppConfig._settings); }
159
- }
169
+ if (options.settings) {
170
+ const settingsPromise = new Promise((resolve) => {
171
+ try {
172
+ AppConfig._settings = options.settings;
173
+ if (debug) {
174
+ DebugAndLog.debug("Settings initialized", AppConfig._settings);
175
+ }
176
+ resolve(true);
177
+ } catch (error) {
178
+ DebugAndLog.error(`Settings initialization failed: ${error.message}`, error.stack);
179
+ resolve(false);
180
+ }
181
+ });
182
+ AppConfig.add(settingsPromise);
183
+ }
160
184
 
161
- if (options.connections) {
162
- AppConfig._connections = new Connections(options.connections);
163
- if (debug) { DebugAndLog.debug("Connections initialized", AppConfig._connections.info()); }
164
- }
185
+ if (options.connections) {
186
+ const connectionsPromise = new Promise((resolve) => {
187
+ try {
188
+ AppConfig._connections = new Connections(options.connections);
189
+ if (debug) {
190
+ DebugAndLog.debug("Connections initialized", AppConfig._connections.info());
191
+ }
192
+ resolve(true);
193
+ } catch (error) {
194
+ DebugAndLog.error(`Connections initialization failed: ${error.message}`, error.stack);
195
+ resolve(false);
196
+ }
197
+ });
198
+ AppConfig.add(connectionsPromise);
199
+ }
165
200
 
166
- if (options.validations) {
167
- ClientRequest.init(options.validations);
168
- if (debug) { DebugAndLog.debug("ClientRequest initialized", ClientRequest.info()); }
169
- }
201
+ if (options.validations) {
202
+ const validationsPromise = new Promise((resolve) => {
203
+ try {
204
+ ClientRequest.init(options.validations);
205
+ if (debug) {
206
+ DebugAndLog.debug("ClientRequest initialized", ClientRequest.info());
207
+ }
208
+ resolve(true);
209
+ } catch (error) {
210
+ DebugAndLog.error(`ClientRequest initialization failed: ${error.message}`, error.stack);
211
+ resolve(false);
212
+ }
213
+ });
214
+ AppConfig.add(validationsPromise);
215
+ }
170
216
 
171
- if (options.responses) {
172
- Response.init(options.responses);
173
- if (debug) { DebugAndLog.debug("Response initialized", Response.info()); }
174
- }
217
+ if (options.responses) {
218
+ const responsesPromise = new Promise((resolve) => {
219
+ try {
220
+ Response.init(options.responses);
221
+ if (debug) {
222
+ DebugAndLog.debug("Response initialized", Response.info());
223
+ }
224
+ resolve(true);
225
+ } catch (error) {
226
+ DebugAndLog.error(`Response initialization failed: ${error.message}`, error.stack);
227
+ resolve(false);
228
+ }
229
+ });
230
+ AppConfig.add(responsesPromise);
231
+ }
175
232
 
176
- if (options.ssmParameters) {
177
- AppConfig._ssmParameters = AppConfig._initParameters(options.ssmParameters);
178
- AppConfig.add(AppConfig._ssmParameters);
179
- }
233
+ if (options.ssmParameters) {
234
+ AppConfig._ssmParameters = AppConfig._initParameters(options.ssmParameters);
235
+ AppConfig.add(AppConfig._ssmParameters);
236
+ }
180
237
 
181
- return true;
238
+ return true;
182
239
 
183
- } catch (error) {
184
- DebugAndLog.error(`Could not initialize Config ${error.message}`, error.stack);
185
- return false;
240
+ } catch (error) {
241
+ DebugAndLog.error(`Could not initialize Config ${error.message}`, error.stack);
242
+ return false;
243
+ }
186
244
  }
187
- };
245
+ ;
188
246
 
189
247
  /**
190
248
  * Add a promise to AppConfig. Use AppConfig.promise() to ensure all are resolved.
@@ -299,13 +357,13 @@ class AppConfig {
299
357
 
300
358
  /**
301
359
  *
302
- * @returns {Promise} A promise that resolves when the Config class has finished initializing
360
+ * @returns {Promise<array>} A promise that resolves when the Config class has finished initializing
303
361
  */
304
362
  static promise() {
305
363
  if (AppConfig._promise !== null ) { // Backwards compatibility
306
364
  AppConfig._promises.push(AppConfig._promise);
307
365
  }
308
- return Promise.all[AppConfig._promises];
366
+ return Promise.all(AppConfig._promises);
309
367
  };
310
368
 
311
369
 
@@ -473,8 +531,11 @@ module.exports = {
473
531
  nodeVerMinor,
474
532
  nodeVerMajorMinor,
475
533
  AWS,
534
+ Aws: AWS,
476
535
  AWSXRay,
536
+ AwsXRay: AWSXRay, // Alias
477
537
  APIRequest,
538
+ ApiRequest: APIRequest, // Alias
478
539
  ImmutableObject,
479
540
  Timer,
480
541
  DebugAndLog,
@@ -489,6 +550,7 @@ module.exports = {
489
550
  AppConfig,
490
551
  _ConfigSuperClass: AppConfig, // Alias
491
552
  CachedSSMParameter,
553
+ CachedSsmParameter: CachedSSMParameter, // Alias
492
554
  CachedSecret,
493
555
  CachedParameterSecret,
494
556
  CachedParameterSecrets,
@@ -0,0 +1,66 @@
1
+ /**
2
+ * ValidationExecutor - Executes validation functions with appropriate interfaces
3
+ *
4
+ * This class provides static methods for executing validation functions with either
5
+ * single-parameter or multi-parameter interfaces. It handles errors gracefully and
6
+ * logs validation failures.
7
+ *
8
+ * @private
9
+ * @class ValidationExecutor
10
+ */
11
+ class ValidationExecutor {
12
+ /**
13
+ * Execute validation function with appropriate interface.
14
+ *
15
+ * Determines whether to pass a single value or an object based on the number
16
+ * of parameters specified. Handles validation errors gracefully by catching
17
+ * exceptions and logging them.
18
+ *
19
+ * @param {Function} validateFn - Validation function to execute
20
+ * @param {Array<string>} paramNames - Parameter names to validate
21
+ * @param {Object} paramValues - All parameter values available
22
+ * @returns {boolean} True if validation passes, false if fails or throws
23
+ *
24
+ * @example
25
+ * // Single parameter validation
26
+ * const isValid = ValidationExecutor.execute(
27
+ * (value) => value.length > 0,
28
+ * ['id'],
29
+ * { id: '123' }
30
+ * );
31
+ *
32
+ * @example
33
+ * // Multi-parameter validation
34
+ * const isValid = ValidationExecutor.execute(
35
+ * ({page, limit}) => page >= 1 && limit >= 1 && limit <= 100,
36
+ * ['page', 'limit'],
37
+ * { page: 1, limit: 10 }
38
+ * );
39
+ */
40
+ static execute(validateFn, paramNames, paramValues) {
41
+ try {
42
+ if (paramNames.length === 1) {
43
+ // Single parameter: pass value directly
44
+ const value = paramValues[paramNames[0]];
45
+ return validateFn(value);
46
+ } else {
47
+ // Multiple parameters: pass object
48
+ const paramObj = {};
49
+ for (const name of paramNames) {
50
+ paramObj[name] = paramValues[name];
51
+ }
52
+ return validateFn(paramObj);
53
+ }
54
+ } catch (error) {
55
+ // Log error and treat as validation failure
56
+ const tools = require("../tools/index.js");
57
+ tools.DebugAndLog.error(
58
+ `Validation function threw error for parameters [${paramNames.join(", ")}]: ${error?.message || "Unknown error"}`,
59
+ error?.stack
60
+ );
61
+ return false;
62
+ }
63
+ }
64
+ }
65
+
66
+ module.exports = ValidationExecutor;
@@ -0,0 +1,405 @@
1
+ const DebugAndLog = require('../tools/DebugAndLog.class');
2
+
3
+ /**
4
+ * Internal class for matching validation rules to request parameters.
5
+ * Implements a four-tier priority system for validation rule resolution:
6
+ * 1. Method-and-route match (BY_ROUTE with "METHOD:route")
7
+ * 2. Route-only match (BY_ROUTE with "route")
8
+ * 3. Method-only match (BY_METHOD with "METHOD")
9
+ * 4. Global parameter name
10
+ *
11
+ * @private
12
+ * @example
13
+ * // Internal use only - not exposed in public API
14
+ * const matcher = new ValidationMatcher(
15
+ * paramValidations,
16
+ * 'GET',
17
+ * '/product/{id}'
18
+ * );
19
+ * const rule = matcher.findValidationRule('id');
20
+ */
21
+ class ValidationMatcher {
22
+ #paramValidations;
23
+ #httpMethod;
24
+ #resourcePath;
25
+ #normalizedResourcePath;
26
+ #cachedPatterns;
27
+
28
+ /**
29
+ * Creates a new ValidationMatcher instance.
30
+ *
31
+ * @param {Object} paramValidations - Parameter validation configuration
32
+ * @param {string} httpMethod - HTTP method of request (e.g., 'GET', 'POST')
33
+ * @param {string} resourcePath - Resource path template (e.g., '/product/{id}')
34
+ */
35
+ constructor(paramValidations, httpMethod, resourcePath) {
36
+ this.#paramValidations = paramValidations || {};
37
+ this.#httpMethod = (httpMethod || '').toUpperCase();
38
+ this.#resourcePath = resourcePath || '';
39
+ this.#normalizedResourcePath = this.#normalizeRoute(this.#resourcePath);
40
+ this.#cachedPatterns = new Map();
41
+
42
+ // >! Cache normalized patterns during initialization for performance
43
+ this.#cacheNormalizedPatterns();
44
+ }
45
+
46
+ /**
47
+ * Find the best matching validation rule for a parameter.
48
+ * Uses four-tier priority resolution:
49
+ * 1. Method-and-route match (BY_ROUTE with "METHOD:route")
50
+ * 2. Route-only match (BY_ROUTE with "route")
51
+ * 3. Method-only match (BY_METHOD with "METHOD")
52
+ * 4. Global parameter name
53
+ *
54
+ * @param {string} paramName - Parameter name to find validation for
55
+ * @returns {{validate: Function, params: Array<string>}|null} Validation rule with function and parameter list, or null if no match
56
+ * @example
57
+ * const rule = matcher.findValidationRule('id');
58
+ * if (rule) {
59
+ * const isValid = rule.validate(paramValue);
60
+ * }
61
+ */
62
+ findValidationRule(paramName) {
63
+ // >! Early exit on first match to minimize pattern comparisons
64
+ // Priority 1: Method-and-route match
65
+ const methodRouteMatch = this.#findMethodRouteMatch(paramName);
66
+ if (methodRouteMatch) return methodRouteMatch;
67
+
68
+ // Priority 2: Route-only match
69
+ const routeMatch = this.#findRouteMatch(paramName);
70
+ if (routeMatch) return routeMatch;
71
+
72
+ // Priority 3: Method-only match
73
+ const methodMatch = this.#findMethodMatch(paramName);
74
+ if (methodMatch) return methodMatch;
75
+
76
+ // Priority 4: Global parameter name
77
+ return this.#findGlobalMatch(paramName);
78
+ }
79
+
80
+ /**
81
+ * Normalize route by removing leading/trailing slashes and converting to lowercase.
82
+ *
83
+ * @private
84
+ * @param {string} route - Route to normalize
85
+ * @returns {string} Normalized route
86
+ */
87
+ #normalizeRoute(route) {
88
+ if (!route || typeof route !== 'string') {
89
+ return '';
90
+ }
91
+ return route.replace(/^\/|\/$/g, '').toLowerCase();
92
+ }
93
+
94
+ /**
95
+ * Cache normalized patterns during initialization for performance optimization.
96
+ *
97
+ * @private
98
+ */
99
+ #cacheNormalizedPatterns() {
100
+ // Cache BY_ROUTE patterns
101
+ if (this.#paramValidations.BY_ROUTE && Array.isArray(this.#paramValidations.BY_ROUTE)) {
102
+ for (const rule of this.#paramValidations.BY_ROUTE) {
103
+ if (rule.route && typeof rule.route === 'string') {
104
+ // Extract route part (remove method prefix if present)
105
+ let routePart = rule.route;
106
+ if (routePart.includes(':')) {
107
+ const parts = routePart.split(':', 2);
108
+ routePart = parts[1] || '';
109
+ }
110
+
111
+ // Remove query parameter specification for pattern matching
112
+ const routeWithoutQuery = routePart.split('?')[0];
113
+ const normalized = this.#normalizeRoute(routeWithoutQuery);
114
+ this.#cachedPatterns.set(rule.route, normalized);
115
+ }
116
+ }
117
+ }
118
+ }
119
+
120
+ /**
121
+ * Check if a route pattern matches the request route.
122
+ * Supports placeholder matching for any number of placeholders (e.g., {id}, {userId}, {postId}).
123
+ * Performs segment-by-segment comparison with placeholder detection.
124
+ *
125
+ * @private
126
+ * @param {string} pattern - Route pattern to match (e.g., 'users/{userId}/posts/{postId}')
127
+ * @param {string} requestRoute - Normalized request route (e.g., 'users/123/posts/456')
128
+ * @returns {boolean} True if pattern matches request route, false otherwise
129
+ * @example
130
+ * // Single placeholder
131
+ * #routeMatches('product/{id}', 'product/123') // true
132
+ *
133
+ * @example
134
+ * // Multiple placeholders
135
+ * #routeMatches('users/{userId}/posts/{postId}', 'users/123/posts/456') // true
136
+ *
137
+ * @example
138
+ * // Segment count mismatch
139
+ * #routeMatches('product/{id}', 'product/123/extra') // false
140
+ */
141
+ #routeMatches(pattern, requestRoute) {
142
+ if (!pattern || !requestRoute) {
143
+ return false;
144
+ }
145
+
146
+ // Split into segments
147
+ const patternSegments = pattern.split('/').filter(s => s.length > 0);
148
+ const requestSegments = requestRoute.split('/').filter(s => s.length > 0);
149
+
150
+ // Segment count must match
151
+ if (patternSegments.length !== requestSegments.length) {
152
+ return false;
153
+ }
154
+
155
+ // >! Check each segment - placeholders match any value
156
+ for (let i = 0; i < patternSegments.length; i++) {
157
+ const patternSeg = patternSegments[i];
158
+ const requestSeg = requestSegments[i];
159
+
160
+ // Check if pattern segment is a placeholder
161
+ if (patternSeg.startsWith('{') && patternSeg.endsWith('}')) {
162
+ continue; // Placeholder matches any value
163
+ }
164
+
165
+ // Exact match (case-insensitive)
166
+ if (patternSeg.toLowerCase() !== requestSeg.toLowerCase()) {
167
+ return false;
168
+ }
169
+ }
170
+
171
+ return true;
172
+ }
173
+
174
+ /**
175
+ * Find method-and-route match (Priority 1: METHOD:route).
176
+ * Matches validation rules with method prefix (e.g., 'POST:product/{id}').
177
+ * Extracts method and route parts, validates both match the request.
178
+ *
179
+ * @private
180
+ * @param {string} paramName - Parameter name to find validation for
181
+ * @returns {{validate: Function, params: Array<string>}|null} Validation rule with function and parameter list, or null if no match
182
+ * @example
183
+ * // Matches 'POST:product/{id}' for POST request to /product/123
184
+ * #findMethodRouteMatch('id') // { validate: Function, params: ['id'] }
185
+ *
186
+ * @example
187
+ * // Does not match 'GET:product/{id}' for POST request
188
+ * #findMethodRouteMatch('id') // null
189
+ */
190
+ #findMethodRouteMatch(paramName) {
191
+ if (!this.#paramValidations.BY_ROUTE || !Array.isArray(this.#paramValidations.BY_ROUTE)) {
192
+ return null;
193
+ }
194
+
195
+ for (const rule of this.#paramValidations.BY_ROUTE) {
196
+ if (!rule.route || typeof rule.route !== 'string' || !rule.validate) {
197
+ continue;
198
+ }
199
+
200
+ // Check if route includes method prefix
201
+ if (!rule.route.includes(':')) {
202
+ continue;
203
+ }
204
+
205
+ const [method, routePart] = rule.route.split(':', 2);
206
+
207
+ // >! Match method case-insensitively
208
+ if (method.toUpperCase() !== this.#httpMethod) {
209
+ continue;
210
+ }
211
+
212
+ // Extract route pattern (without query params)
213
+ const routeWithoutQuery = routePart.split('?')[0];
214
+ const normalizedPattern = this.#cachedPatterns.get(rule.route) || this.#normalizeRoute(routeWithoutQuery);
215
+
216
+ // Check if route matches
217
+ if (!this.#routeMatches(normalizedPattern, this.#normalizedResourcePath)) {
218
+ continue;
219
+ }
220
+
221
+ // Extract parameter names from route pattern
222
+ const params = this.#extractParamNames(rule.route);
223
+
224
+ // Check if this rule applies to the parameter
225
+ if (params.length === 0 || params.includes(paramName)) {
226
+ return {
227
+ validate: rule.validate,
228
+ params: params.length > 0 ? params : [paramName]
229
+ };
230
+ }
231
+ }
232
+
233
+ return null;
234
+ }
235
+
236
+ /**
237
+ * Find route-only match (Priority 2: route).
238
+ *
239
+ * @private
240
+ * @param {string} paramName - Parameter name
241
+ * @returns {{validate: Function, params: Array<string>}|null} Validation rule or null
242
+ */
243
+ #findRouteMatch(paramName) {
244
+ if (!this.#paramValidations.BY_ROUTE || !Array.isArray(this.#paramValidations.BY_ROUTE)) {
245
+ return null;
246
+ }
247
+
248
+ for (const rule of this.#paramValidations.BY_ROUTE) {
249
+ if (!rule.route || typeof rule.route !== 'string' || !rule.validate) {
250
+ continue;
251
+ }
252
+
253
+ // Skip method-and-route patterns
254
+ if (rule.route.includes(':')) {
255
+ continue;
256
+ }
257
+
258
+ // Extract route pattern (without query params)
259
+ const routeWithoutQuery = rule.route.split('?')[0];
260
+ const normalizedPattern = this.#cachedPatterns.get(rule.route) || this.#normalizeRoute(routeWithoutQuery);
261
+
262
+ // Check if route matches
263
+ if (!this.#routeMatches(normalizedPattern, this.#normalizedResourcePath)) {
264
+ continue;
265
+ }
266
+
267
+ // Extract parameter names from route pattern
268
+ const params = this.#extractParamNames(rule.route);
269
+
270
+ // Check if this rule applies to the parameter
271
+ if (params.length === 0 || params.includes(paramName)) {
272
+ return {
273
+ validate: rule.validate,
274
+ params: params.length > 0 ? params : [paramName]
275
+ };
276
+ }
277
+ }
278
+
279
+ return null;
280
+ }
281
+
282
+ /**
283
+ * Find method-only match (Priority 3: METHOD).
284
+ *
285
+ * @private
286
+ * @param {string} paramName - Parameter name
287
+ * @returns {{validate: Function, params: Array<string>}|null} Validation rule or null
288
+ */
289
+ #findMethodMatch(paramName) {
290
+ if (!this.#paramValidations.BY_METHOD || !Array.isArray(this.#paramValidations.BY_METHOD)) {
291
+ return null;
292
+ }
293
+
294
+ for (const rule of this.#paramValidations.BY_METHOD) {
295
+ if (!rule.method || typeof rule.method !== 'string' || !rule.validate) {
296
+ continue;
297
+ }
298
+
299
+ // >! Match method case-insensitively
300
+ if (rule.method.toUpperCase() === this.#httpMethod) {
301
+ return {
302
+ validate: rule.validate,
303
+ params: [paramName]
304
+ };
305
+ }
306
+ }
307
+
308
+ return null;
309
+ }
310
+
311
+ /**
312
+ * Find global parameter match (Priority 4: global parameter name).
313
+ *
314
+ * @private
315
+ * @param {string} paramName - Parameter name
316
+ * @returns {{validate: Function, params: Array<string>}|null} Validation rule or null
317
+ */
318
+ #findGlobalMatch(paramName) {
319
+ // Check if parameter has a global validation function
320
+ if (this.#paramValidations[paramName] && typeof this.#paramValidations[paramName] === 'function') {
321
+ return {
322
+ validate: this.#paramValidations[paramName],
323
+ params: [paramName]
324
+ };
325
+ }
326
+
327
+ return null;
328
+ }
329
+
330
+ /**
331
+ * Extract parameter names from route pattern.
332
+ * Extracts both path parameters (from {}) and query parameters (after ?).
333
+ * When ? is present, query parameters are added to path parameters.
334
+ * When ? is absent, only path parameters in {} are extracted.
335
+ *
336
+ * @private
337
+ * @param {string} routePattern - Route pattern to parse (e.g., 'product/{id}?key', 'POST:users/{userId}/posts/{postId}')
338
+ * @returns {Array<string>} Array of unique parameter names to validate
339
+ * @example
340
+ * // Path parameters only (no ? specification)
341
+ * #extractParamNames('product/{id}') // ['id']
342
+ *
343
+ * @example
344
+ * // Multiple path parameters (no ? specification)
345
+ * #extractParamNames('users/{userId}/posts/{postId}') // ['userId', 'postId']
346
+ *
347
+ * @example
348
+ * // Query parameters only (? specification, no path params)
349
+ * #extractParamNames('search?query,limit') // ['query', 'limit']
350
+ *
351
+ * @example
352
+ * // Path and query parameters combined
353
+ * #extractParamNames('POST:product/{id}?key') // ['id', 'key'] - both path and query params
354
+ *
355
+ * @example
356
+ * // Multiple path and query parameters
357
+ * #extractParamNames('users/{userId}/posts/{postId}?includeProfile') // ['userId', 'postId', 'includeProfile']
358
+ *
359
+ * @example
360
+ * // Duplicate parameters removed
361
+ * #extractParamNames('product/{id}?id,key') // ['id', 'key'] (not ['id', 'id', 'key'])
362
+ */
363
+ #extractParamNames(routePattern) {
364
+ if (!routePattern || typeof routePattern !== 'string') {
365
+ return [];
366
+ }
367
+
368
+ const params = [];
369
+
370
+ // Remove method prefix if present
371
+ let pattern = routePattern;
372
+ if (pattern.includes(':')) {
373
+ const parts = pattern.split(':', 2);
374
+ pattern = parts[1] || '';
375
+ }
376
+
377
+ // Split route and query parts
378
+ const [routePart, queryPart] = pattern.split('?', 2);
379
+
380
+ // >! Extract path parameters from {} placeholders
381
+ if (routePart) {
382
+ const pathParamMatches = routePart.match(/\{([^}]+)\}/g);
383
+ if (pathParamMatches) {
384
+ for (const match of pathParamMatches) {
385
+ // Remove braces and add to params
386
+ const paramName = match.slice(1, -1).trim();
387
+ if (paramName.length > 0) {
388
+ params.push(paramName);
389
+ }
390
+ }
391
+ }
392
+ }
393
+
394
+ // >! Add query parameters to the list
395
+ if (queryPart) {
396
+ const queryParams = queryPart.split(',').map(p => p.trim()).filter(p => p.length > 0);
397
+ params.push(...queryParams);
398
+ }
399
+
400
+ // >! Remove duplicates while preserving order
401
+ return [...new Set(params)];
402
+ }
403
+ }
404
+
405
+ module.exports = ValidationMatcher;