@peter.naydenov/url-pattern 1.0.0 → 1.0.1

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/dist/index.js CHANGED
@@ -2,7 +2,6 @@
2
2
  * @fileoverview URL pattern matching library
3
3
  * @module url-pattern
4
4
  */
5
-
6
5
  /**
7
6
  * @typedef {Object} UrlPatternOptions
8
7
  * @property {string} [escapeChar='\\'] - Character used for escaping special characters
@@ -13,7 +12,6 @@
13
12
  * @property {string} [optionalSegmentEndChar=')'] - Character that ends an optional segment
14
13
  * @property {string} [wildcardChar='*'] - Character that denotes a wildcard
15
14
  */
16
-
17
15
  /**
18
16
  * @typedef {Object} ParsedSegment
19
17
  * @property {string} name - Segment name
@@ -21,14 +19,12 @@
21
19
  * @property {boolean} [optional=false] - Whether the segment is optional
22
20
  * @property {string} regex - Compiled regex string
23
21
  */
24
-
25
22
  /**
26
23
  * @typedef {Object} SegmentName
27
24
  * @property {string} name - Segment name
28
25
  * @property {number} index - Capture group index
29
26
  * @property {string} type - Segment type ('named' | 'wildcard')
30
27
  */
31
-
32
28
  /**
33
29
  * @typedef {Object} CompiledPattern
34
30
  * @property {string} regex - Compiled regex string
@@ -40,38 +36,34 @@
40
36
  * @property {string} [pattern] - Original pattern string
41
37
  * @property {Array<string>} [keys] - Keys for regex patterns
42
38
  */
43
-
44
39
  /**
45
40
  * Default options for URL pattern matching
46
41
  * @type {UrlPatternOptions}
47
42
  */
48
43
  const DEFAULT_OPTIONS = {
49
- escapeChar: '\\',
50
- segmentNameStartChar: ':',
51
- segmentNameCharset: 'abcdefghijklmnopqrstuvwxyzABCDEFGHIJKLMNOPQRSTUVWXYZ0123456789',
52
- segmentValueCharset: 'abcdefghijklmnopqrstuvwxyzABCDEFGHIJKLMNOPQRSTUVWXYZ0123456789-_~ %',
53
- optionalSegmentStartChar: '(',
54
- optionalSegmentEndChar: ')',
55
- wildcardChar: '*'
44
+ escapeChar: '\\',
45
+ segmentNameStartChar: ':',
46
+ segmentNameCharset: 'abcdefghijklmnopqrstuvwxyzABCDEFGHIJKLMNOPQRSTUVWXYZ0123456789',
47
+ segmentValueCharset: 'abcdefghijklmnopqrstuvwxyzABCDEFGHIJKLMNOPQRSTUVWXYZ0123456789-_~ %',
48
+ optionalSegmentStartChar: '(',
49
+ optionalSegmentEndChar: ')',
50
+ wildcardChar: '*'
56
51
  };
57
-
58
52
  /**
59
53
  * Escapes special regex characters in a string
60
54
  * @param {string} str - String to escape
61
55
  * @returns {string} Escaped string
62
56
  */
63
57
  const escapeRegex = (str) => str.replace(/[.*+?^${}()|[\]\\]/g, '\\$&');
64
-
65
58
  /**
66
59
  * Merges default options with user provided options
67
60
  * @param {UrlPatternOptions} [userOptions={}] - User provided options
68
61
  * @returns {UrlPatternOptions} Merged options
69
62
  */
70
63
  const mergeOptions = (userOptions = {}) => ({
71
- ...DEFAULT_OPTIONS,
72
- ...userOptions
64
+ ...DEFAULT_OPTIONS,
65
+ ...userOptions
73
66
  });
74
-
75
67
  /**
76
68
  * Finds the position of the next special character in the pattern
77
69
  * @param {string} pattern - Pattern string
@@ -80,28 +72,25 @@ const mergeOptions = (userOptions = {}) => ({
80
72
  * @returns {number} Position of next special character
81
73
  */
82
74
  const findNextSpecialChar = (pattern, start, options) => {
83
- const chars = [
84
- options.escapeChar,
85
- options.optionalSegmentStartChar,
86
- options.optionalSegmentEndChar,
87
- options.wildcardChar,
88
- options.segmentNameStartChar
89
- ];
90
-
91
- let minPos = pattern.length;
92
-
93
- for (let i = 0; i < chars.length; i++) {
94
- const char = chars[i];
95
- if (!char) continue;
96
- const pos = pattern.indexOf(char, start);
97
- if (pos !== -1 && pos < minPos) {
98
- minPos = pos;
75
+ const chars = [
76
+ options.escapeChar,
77
+ options.optionalSegmentStartChar,
78
+ options.optionalSegmentEndChar,
79
+ options.wildcardChar,
80
+ options.segmentNameStartChar
81
+ ];
82
+ let minPos = pattern.length;
83
+ for (let i = 0; i < chars.length; i++) {
84
+ const char = chars[i];
85
+ if (!char)
86
+ continue;
87
+ const pos = pattern.indexOf(char, start);
88
+ if (pos !== -1 && pos < minPos) {
89
+ minPos = pos;
90
+ }
99
91
  }
100
- }
101
-
102
- return minPos;
92
+ return minPos;
103
93
  };
104
-
105
94
  /**
106
95
  * Parses a pattern string into segments
107
96
  * @param {string} pattern - Pattern string to parse
@@ -109,101 +98,86 @@ const findNextSpecialChar = (pattern, start, options) => {
109
98
  * @returns {Array<ParsedSegment>} Parsed segments
110
99
  */
111
100
  const parsePattern = (pattern, options) => {
112
- const segments = [];
113
- let i = 0;
114
- let inOptional = false;
115
-
116
- while (i < pattern.length) {
117
- const char = pattern[i];
118
-
119
- if (char === options.escapeChar && i + 1 < pattern.length) {
120
- segments.push({
121
- type: 'literal',
122
- name: pattern[i + 1],
123
- regex: escapeRegex(pattern[i + 1]),
124
- optional: inOptional
125
- });
126
- i += 2;
127
- continue;
128
- }
129
-
130
- if (char === options.optionalSegmentStartChar) {
131
- inOptional = true;
132
- i++;
133
- continue;
134
- }
135
-
136
- if (char === options.optionalSegmentEndChar) {
137
- inOptional = false;
138
- i++;
139
- continue;
140
- }
141
-
142
- if (char === options.wildcardChar) {
143
- segments.push({
144
- type: 'wildcard',
145
- name: '_',
146
- regex: '.*',
147
- optional: inOptional
148
- });
149
- i++;
150
- continue;
151
- }
152
-
153
- if (char === options.segmentNameStartChar && i + 1 < pattern.length) {
154
- const remaining = pattern.slice(i + 1);
155
- let nameEnd = 0;
156
- const charset = options.segmentNameCharset || '';
157
-
158
- for (let j = 0; j < remaining.length; j++) {
159
- if (!charset.includes(remaining[j])) {
160
- break;
101
+ const segments = [];
102
+ let i = 0;
103
+ let inOptional = false;
104
+ while (i < pattern.length) {
105
+ const char = pattern[i];
106
+ if (char === options.escapeChar && i + 1 < pattern.length) {
107
+ segments.push({
108
+ type: 'literal',
109
+ name: pattern[i + 1],
110
+ regex: escapeRegex(pattern[i + 1]),
111
+ optional: inOptional
112
+ });
113
+ i += 2;
114
+ continue;
161
115
  }
162
- nameEnd = j + 1;
163
- }
164
-
165
- const name = remaining.slice(0, nameEnd);
166
-
167
- if (name.length > 0) {
168
- let valueCharset = options.segmentValueCharset || '';
169
- if (valueCharset.includes('-') && valueCharset.indexOf('-') > 0 && valueCharset.indexOf('-') < valueCharset.length - 1) {
170
- valueCharset = valueCharset.replace(/-/g, '');
171
- valueCharset += '-';
116
+ if (char === options.optionalSegmentStartChar) {
117
+ inOptional = true;
118
+ i++;
119
+ continue;
172
120
  }
173
- const escapedValueCharset = valueCharset.replace(/\]/g, '\\]');
174
- const valueRegex = `([${escapedValueCharset}]+)`;
175
-
176
- segments.push({
177
- type: 'named',
178
- name,
179
- regex: valueRegex,
180
- optional: inOptional
181
- });
182
- i += 1 + nameEnd;
183
- continue;
184
- }
185
- }
186
-
187
- const literalEnd = findNextSpecialChar(pattern, i, options);
188
-
189
- if (literalEnd > i) {
190
- const literal = pattern.slice(i, literalEnd);
191
- segments.push({
192
- type: 'literal',
193
- name: literal,
194
- regex: escapeRegex(literal),
195
- optional: inOptional
196
- });
197
- i = literalEnd;
198
- continue;
121
+ if (char === options.optionalSegmentEndChar) {
122
+ inOptional = false;
123
+ i++;
124
+ continue;
125
+ }
126
+ if (char === options.wildcardChar) {
127
+ segments.push({
128
+ type: 'wildcard',
129
+ name: '_',
130
+ regex: '.*',
131
+ optional: inOptional
132
+ });
133
+ i++;
134
+ continue;
135
+ }
136
+ if (char === options.segmentNameStartChar && i + 1 < pattern.length) {
137
+ const remaining = pattern.slice(i + 1);
138
+ let nameEnd = 0;
139
+ const charset = options.segmentNameCharset || '';
140
+ for (let j = 0; j < remaining.length; j++) {
141
+ if (!charset.includes(remaining[j])) {
142
+ break;
143
+ }
144
+ nameEnd = j + 1;
145
+ }
146
+ const name = remaining.slice(0, nameEnd);
147
+ if (name.length > 0) {
148
+ let valueCharset = options.segmentValueCharset || '';
149
+ if (valueCharset.includes('-') && valueCharset.indexOf('-') > 0 && valueCharset.indexOf('-') < valueCharset.length - 1) {
150
+ valueCharset = valueCharset.replace(/-/g, '');
151
+ valueCharset += '-';
152
+ }
153
+ const escapedValueCharset = valueCharset.replace(/\]/g, '\\]');
154
+ const valueRegex = `([${escapedValueCharset}]+)`;
155
+ segments.push({
156
+ type: 'named',
157
+ name,
158
+ regex: valueRegex,
159
+ optional: inOptional
160
+ });
161
+ i += 1 + nameEnd;
162
+ continue;
163
+ }
164
+ }
165
+ const literalEnd = findNextSpecialChar(pattern, i, options);
166
+ if (literalEnd > i) {
167
+ const literal = pattern.slice(i, literalEnd);
168
+ segments.push({
169
+ type: 'literal',
170
+ name: literal,
171
+ regex: escapeRegex(literal),
172
+ optional: inOptional
173
+ });
174
+ i = literalEnd;
175
+ continue;
176
+ }
177
+ i++;
199
178
  }
200
-
201
- i++;
202
- }
203
-
204
- return segments;
179
+ return segments;
205
180
  };
206
-
207
181
  /**
208
182
  * Compiles segments into a regex pattern
209
183
  * @param {Array<ParsedSegment>} segments - Parsed segments
@@ -211,62 +185,55 @@ const parsePattern = (pattern, options) => {
211
185
  * @returns {{regex: string, segmentNames: Array<SegmentName>}} Compiled regex and segment names
212
186
  */
213
187
  const compileRegex = (segments, options) => {
214
- let regex = '^';
215
- let groupIndex = 0;
216
- /** @type {Array<SegmentName>} */
217
- const segmentNames = [];
218
- let i = 0;
219
-
220
- while (i < segments.length) {
221
- const segment = segments[i];
222
-
223
- if (segment.optional) {
224
- let optionalPart = '';
225
- let j = i;
226
-
227
- while (j < segments.length && segments[j].optional) {
228
- const seg = segments[j];
229
-
230
- if (seg.type === 'wildcard') {
231
- optionalPart += '(.*)';
232
- segmentNames.push({ name: '_', index: groupIndex, type: 'wildcard' });
233
- groupIndex++;
234
- } else if (seg.type === 'named') {
235
- optionalPart += seg.regex;
236
- segmentNames.push({ name: seg.name, index: groupIndex, type: 'named' });
237
- groupIndex++;
238
- } else {
239
- optionalPart += seg.regex;
188
+ let regex = '^';
189
+ let groupIndex = 0;
190
+ /** @type {Array<SegmentName>} */
191
+ const segmentNames = [];
192
+ let i = 0;
193
+ while (i < segments.length) {
194
+ const segment = segments[i];
195
+ if (segment.optional) {
196
+ let optionalPart = '';
197
+ let j = i;
198
+ while (j < segments.length && segments[j].optional) {
199
+ const seg = segments[j];
200
+ if (seg.type === 'wildcard') {
201
+ optionalPart += '(.*)';
202
+ segmentNames.push({ name: '_', index: groupIndex, type: 'wildcard' });
203
+ groupIndex++;
204
+ }
205
+ else if (seg.type === 'named') {
206
+ optionalPart += seg.regex;
207
+ segmentNames.push({ name: seg.name, index: groupIndex, type: 'named' });
208
+ groupIndex++;
209
+ }
210
+ else {
211
+ optionalPart += seg.regex;
212
+ }
213
+ j++;
214
+ }
215
+ regex += `(?:${optionalPart})?`;
216
+ i = j;
217
+ continue;
240
218
  }
241
-
242
- j++;
243
- }
244
-
245
- regex += `(?:${optionalPart})?`;
246
- i = j;
247
- continue;
248
- }
249
-
250
- if (segment.type === 'wildcard') {
251
- regex += '(.*)';
252
- segmentNames.push({ name: '_', index: groupIndex, type: 'wildcard' });
253
- groupIndex++;
254
- } else if (segment.type === 'named') {
255
- regex += segment.regex;
256
- segmentNames.push({ name: segment.name, index: groupIndex, type: 'named' });
257
- groupIndex++;
258
- } else {
259
- regex += segment.regex;
219
+ if (segment.type === 'wildcard') {
220
+ regex += '(.*)';
221
+ segmentNames.push({ name: '_', index: groupIndex, type: 'wildcard' });
222
+ groupIndex++;
223
+ }
224
+ else if (segment.type === 'named') {
225
+ regex += segment.regex;
226
+ segmentNames.push({ name: segment.name, index: groupIndex, type: 'named' });
227
+ groupIndex++;
228
+ }
229
+ else {
230
+ regex += segment.regex;
231
+ }
232
+ i++;
260
233
  }
261
-
262
- i++;
263
- }
264
-
265
- regex += '$';
266
-
267
- return { regex, segmentNames };
234
+ regex += '$';
235
+ return { regex, segmentNames };
268
236
  };
269
-
270
237
  /**
271
238
  * Creates a compiled pattern from a string
272
239
  * @param {string} pattern - Pattern string
@@ -274,21 +241,19 @@ const compileRegex = (segments, options) => {
274
241
  * @returns {CompiledPattern} Compiled pattern
275
242
  */
276
243
  const makePattern = (pattern, options = {}) => {
277
- const mergedOptions = mergeOptions(options);
278
- const segments = parsePattern(pattern, mergedOptions);
279
- const { regex, segmentNames } = compileRegex(segments, mergedOptions);
280
-
281
- return {
282
- regex,
283
- regexObj: new RegExp(regex),
284
- segments,
285
- segmentNames,
286
- options: mergedOptions,
287
- isRegex: false,
288
- pattern
289
- };
244
+ const mergedOptions = mergeOptions(options);
245
+ const segments = parsePattern(pattern, mergedOptions);
246
+ const { regex, segmentNames } = compileRegex(segments, mergedOptions);
247
+ return {
248
+ regex,
249
+ regexObj: new RegExp(regex),
250
+ segments,
251
+ segmentNames,
252
+ options: mergedOptions,
253
+ isRegex: false,
254
+ pattern
255
+ };
290
256
  };
291
-
292
257
  /**
293
258
  * Creates a compiled pattern from a regex
294
259
  * @param {RegExp} regex - Regex pattern
@@ -296,17 +261,16 @@ const makePattern = (pattern, options = {}) => {
296
261
  * @returns {CompiledPattern} Compiled pattern
297
262
  */
298
263
  const makePatternFromRegex = (regex, keys = []) => {
299
- return {
300
- regex: regex.source,
301
- regexObj: regex,
302
- segments: [],
303
- segmentNames: keys.map((name, index) => ({ name, index, type: 'named' })),
304
- options: DEFAULT_OPTIONS,
305
- isRegex: true,
306
- keys
307
- };
264
+ return {
265
+ regex: regex.source,
266
+ regexObj: regex,
267
+ segments: [],
268
+ segmentNames: keys.map((name, index) => ({ name, index, type: 'named' })),
269
+ options: DEFAULT_OPTIONS,
270
+ isRegex: true,
271
+ keys
272
+ };
308
273
  };
309
-
310
274
  /**
311
275
  * Matches a string against a compiled pattern
312
276
  * @param {CompiledPattern} compiled - Compiled pattern
@@ -314,51 +278,44 @@ const makePatternFromRegex = (regex, keys = []) => {
314
278
  * @returns {Object|null} Extracted values or null if no match
315
279
  */
316
280
  const match = (compiled, str) => {
317
- const matchResult = compiled.regexObj.exec(str);
318
-
319
- if (!matchResult) {
320
- return null;
321
- }
322
-
323
- if (compiled.isRegex) {
324
- if (compiled.keys && compiled.keys.length > 0) {
325
- const result = {};
326
- compiled.keys.forEach((key, index) => {
327
- const val = matchResult[index + 1];
328
- result[key] = val !== undefined ? val : null;
329
- });
330
- return result;
281
+ const matchResult = compiled.regexObj.exec(str);
282
+ if (!matchResult) {
283
+ return null;
284
+ }
285
+ if (compiled.isRegex) {
286
+ if (compiled.keys && compiled.keys.length > 0) {
287
+ const result = {};
288
+ compiled.keys.forEach((key, index) => {
289
+ const val = matchResult[index + 1];
290
+ result[key] = val !== undefined ? val : null;
291
+ });
292
+ return result;
293
+ }
294
+ return matchResult.slice(1);
331
295
  }
332
- return matchResult.slice(1);
333
- }
334
-
335
- const result = {};
336
- const usedNames = new Set();
337
-
338
- for (let i = 0; i < compiled.segmentNames.length; i++) {
339
- const segInfo = compiled.segmentNames[i];
340
- const value = matchResult[segInfo.index + 1] || '';
341
-
342
- if (usedNames.has(segInfo.name)) {
343
- if (!Array.isArray(result[segInfo.name])) {
344
- result[segInfo.name] = [result[segInfo.name]];
345
- }
346
- result[segInfo.name].push(value);
347
- } else {
348
- usedNames.add(segInfo.name);
349
- result[segInfo.name] = value;
296
+ const result = {};
297
+ const usedNames = new Set();
298
+ for (let i = 0; i < compiled.segmentNames.length; i++) {
299
+ const segInfo = compiled.segmentNames[i];
300
+ const value = matchResult[segInfo.index + 1] || '';
301
+ if (usedNames.has(segInfo.name)) {
302
+ if (!Array.isArray(result[segInfo.name])) {
303
+ result[segInfo.name] = [result[segInfo.name]];
304
+ }
305
+ result[segInfo.name].push(value);
306
+ }
307
+ else {
308
+ usedNames.add(segInfo.name);
309
+ result[segInfo.name] = value;
310
+ }
350
311
  }
351
- }
352
-
353
- for (const key in result) {
354
- if (result[key] === '') {
355
- delete result[key];
312
+ for (const key in result) {
313
+ if (result[key] === '') {
314
+ delete result[key];
315
+ }
356
316
  }
357
- }
358
-
359
- return result;
317
+ return result;
360
318
  };
361
-
362
319
  /**
363
320
  * Stringifies a pattern with given values
364
321
  * @param {CompiledPattern} compiled - Compiled pattern
@@ -367,118 +324,111 @@ const match = (compiled, str) => {
367
324
  * @throws {Error} If required values are missing
368
325
  */
369
326
  const stringify = (compiled, values = {}) => {
370
- if (compiled.isRegex) {
371
- throw new Error('Cannot stringify a pattern created from regex');
372
- }
373
-
374
- let result = '';
375
- let i = 0;
376
-
377
- while (i < compiled.segments.length) {
378
- const segment = compiled.segments[i];
379
-
380
- if (segment.optional) {
381
- let optionalPart = '';
382
- let j = i;
383
-
384
- while (j < compiled.segments.length && compiled.segments[j].optional) {
385
- const seg = compiled.segments[j];
386
-
387
- if (seg.type === 'literal') {
388
- optionalPart += seg.name;
389
- } else if (seg.type === 'named') {
390
- const val = values[seg.name];
391
- if (val !== undefined && val !== null && val !== '') {
392
- optionalPart += Array.isArray(val) ? val.join('/') : val;
393
- } else {
394
- optionalPart = '';
395
- break;
396
- }
397
- } else if (seg.type === 'wildcard') {
398
- const val = values._;
399
- if (val !== undefined && val !== null && val !== '') {
400
- optionalPart += Array.isArray(val) ? val.join('/') : val;
401
- } else {
402
- optionalPart = '';
403
- break;
404
- }
327
+ if (compiled.isRegex) {
328
+ throw new Error('Cannot stringify a pattern created from regex');
329
+ }
330
+ let result = '';
331
+ let i = 0;
332
+ while (i < compiled.segments.length) {
333
+ const segment = compiled.segments[i];
334
+ if (segment.optional) {
335
+ let optionalPart = '';
336
+ let j = i;
337
+ while (j < compiled.segments.length && compiled.segments[j].optional) {
338
+ const seg = compiled.segments[j];
339
+ if (seg.type === 'literal') {
340
+ optionalPart += seg.name;
341
+ }
342
+ else if (seg.type === 'named') {
343
+ const val = values[seg.name];
344
+ if (val !== undefined && val !== null && val !== '') {
345
+ optionalPart += Array.isArray(val) ? val.join('/') : val;
346
+ }
347
+ else {
348
+ optionalPart = '';
349
+ break;
350
+ }
351
+ }
352
+ else if (seg.type === 'wildcard') {
353
+ const val = values._;
354
+ if (val !== undefined && val !== null && val !== '') {
355
+ optionalPart += Array.isArray(val) ? val.join('/') : val;
356
+ }
357
+ else {
358
+ optionalPart = '';
359
+ break;
360
+ }
361
+ }
362
+ j++;
363
+ }
364
+ if (optionalPart !== '') {
365
+ result += optionalPart;
366
+ }
367
+ if (i === j) {
368
+ i++;
369
+ }
370
+ else {
371
+ i = j;
372
+ }
373
+ continue;
374
+ }
375
+ if (segment.type === 'literal') {
376
+ result += segment.name;
377
+ }
378
+ else if (segment.type === 'named') {
379
+ const value = values[segment.name];
380
+ if (value === undefined || value === null || value === '') {
381
+ throw new Error(`Missing required value for segment: ${segment.name}`);
382
+ }
383
+ result += Array.isArray(value) ? value.join('/') : value;
384
+ }
385
+ else if (segment.type === 'wildcard') {
386
+ const value = values._;
387
+ if (value === undefined || value === null || value === '') {
388
+ throw new Error('Missing required wildcard value');
389
+ }
390
+ result += Array.isArray(value) ? value.join('/') : value;
405
391
  }
406
-
407
- j++;
408
- }
409
-
410
- if (optionalPart !== '') {
411
- result += optionalPart;
412
- }
413
-
414
- if (i === j) {
415
392
  i++;
416
- } else {
417
- i = j;
418
- }
419
- continue;
420
393
  }
421
-
422
- if (segment.type === 'literal') {
423
- result += segment.name;
424
- } else if (segment.type === 'named') {
425
- const value = values[segment.name];
426
- if (value === undefined || value === null || value === '') {
427
- throw new Error(`Missing required value for segment: ${segment.name}`);
428
- }
429
- result += Array.isArray(value) ? value.join('/') : value;
430
- } else if (segment.type === 'wildcard') {
431
- const value = values._;
432
- if (value === undefined || value === null || value === '') {
433
- throw new Error('Missing required wildcard value');
434
- }
435
- result += Array.isArray(value) ? value.join('/') : value;
436
- }
437
-
438
- i++;
439
- }
440
-
441
- return result;
394
+ return result;
442
395
  };
443
-
444
396
  /**
445
397
  * UrlPattern class for matching and generating URLs
446
398
  */
447
399
  class UrlPattern {
448
- /**
449
- * @param {string|RegExp} pattern - Pattern string or regex
450
- * @param {UrlPatternOptions|Array<string>} [options={}] - Options or keys (for regex)
451
- */
452
- constructor(pattern, options = {}) {
453
- if (pattern instanceof RegExp) {
454
- const keys = Array.isArray(options) ? options : [];
455
- /** @type {CompiledPattern} */
456
- this.compiled = makePatternFromRegex(pattern, keys);
457
- } else {
458
- /** @type {CompiledPattern} */
459
- this.compiled = makePattern(pattern, /** @type {UrlPatternOptions} */ (options));
400
+ /**
401
+ * @param {string|RegExp} pattern - Pattern string or regex
402
+ * @param {UrlPatternOptions|Array<string>} [options={}] - Options or keys (for regex)
403
+ */
404
+ constructor(pattern, options = {}) {
405
+ if (pattern instanceof RegExp) {
406
+ const keys = Array.isArray(options) ? options : [];
407
+ /** @type {CompiledPattern} */
408
+ this.compiled = makePatternFromRegex(pattern, keys);
409
+ }
410
+ else {
411
+ /** @type {CompiledPattern} */
412
+ this.compiled = makePattern(pattern, /** @type {UrlPatternOptions} */ (options));
413
+ }
414
+ }
415
+ /**
416
+ * Match a string against the pattern
417
+ * @param {string} str - String to match
418
+ * @returns {Object|null} Extracted values or null if no match
419
+ */
420
+ match(str) {
421
+ return match(this.compiled, str);
422
+ }
423
+ /**
424
+ * Generate a string from the pattern
425
+ * @param {Object} [values={}] - Values to stringify
426
+ * @returns {string} Generated string
427
+ */
428
+ stringify(values) {
429
+ return stringify(this.compiled, values);
460
430
  }
461
- }
462
-
463
- /**
464
- * Match a string against the pattern
465
- * @param {string} str - String to match
466
- * @returns {Object|null} Extracted values or null if no match
467
- */
468
- match(str) {
469
- return match(this.compiled, str);
470
- }
471
-
472
- /**
473
- * Generate a string from the pattern
474
- * @param {Object} [values={}] - Values to stringify
475
- * @returns {string} Generated string
476
- */
477
- stringify(values) {
478
- return stringify(this.compiled, values);
479
- }
480
431
  }
481
-
482
432
  /**
483
433
  * Creates a new UrlPattern instance (functional API)
484
434
  * @param {string|RegExp} pattern - Pattern string or regex
@@ -486,8 +436,7 @@ class UrlPattern {
486
436
  * @returns {UrlPattern} UrlPattern instance
487
437
  */
488
438
  const urlPattern = (pattern, options = {}) => {
489
- return new UrlPattern(pattern, options);
439
+ return new UrlPattern(pattern, options);
490
440
  };
491
-
492
441
  export { UrlPattern, urlPattern, makePattern, makePatternFromRegex, match, stringify, DEFAULT_OPTIONS };
493
442
  export default UrlPattern;