@peter.naydenov/url-pattern 1.0.5 → 1.0.6

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/CHANGELOG.md ADDED
@@ -0,0 +1,69 @@
1
+ ## Release History
2
+
3
+
4
+
5
+ ### 1.0.6 (2026-07-14)
6
+ - [x] **🔴 `CHANGELOG.md` is now actually published**. The `.npmignore` had a `!Changelog.md` exception, but the actual file is `CHANGELOG.md`. On case-sensitive filesystems (Linux, the npm registry) the exception didn't match, so the changelog was silently excluded from the published tarball. Renamed the exception to `!CHANGELOG.md` and added `CHANGELOG.md` to the `files` allowlist so it ships regardless of the ignore rules;
7
+ - [x] **Regex patterns no longer poison the global `DEFAULT_OPTIONS`**. `makePatternFromRegex` was returning `options: DEFAULT_OPTIONS` (the shared global), so the constructor's `Object.freeze(compiled.options)` silently froze the global for every other pattern. Now regex patterns get a fresh copy (`{ ...DEFAULT_OPTIONS }`) and the global stays mutable for consumers that import it;
8
+ - [x] `vitest.config.js` no longer excludes `src/main.js` from coverage — that was excluding the entire source from the coverage report (`src/main.js` is the only file in `src/`);
9
+ - [x] `tsconfig.json` updated to `module: "ESNext"` (was `"commonjs"`) so type checking matches the actual ESM source;
10
+ - [x] Removed the now-redundant `src/` entry from `.npmignore` — `src` is in the `files` allowlist and the user wants it published as the primary entry;
11
+ - [x] **README**: License link fixed (was pointing to a non-existent `git-url-pattern` repo, now points to the real `url-pattern` one); the "Match a URL with optional segments" example now uses a named optional segment (`/:version`) instead of a literal one, so the section title actually matches the example; the `pattern.compiled` docs now mention the `keys` field present on regex-based patterns; the `pattern.stringify` docs now warn that it throws on missing required values; the redundant "## URL Patterns" stub section was removed (the link to PATTERNS.md is still in the "Links" section); minor wording fixes (tagline, "ES6" → "ES modules", "this line" → "this code").
12
+
13
+
14
+ ### 1.0.5 (2026-07-10)
15
+ - [x] **🔴 CJS dist file rename: `url-pattern.cjs.js` → `url-pattern.cjs`**. The previous filename ended in `.js`, so Node tried to load it as an ES module (because the package's `package.json` has `"type": "module"`), but the file used CommonJS syntax (`exports`, `module.exports`). The result was a silent failure: `require('@peter.naydenov/url-pattern')` returned an empty object and `new UrlPattern(path)` threw `TypeError: UrlPattern is not a constructor`. The new `.cjs` extension forces Node to treat it as CJS regardless of the package's `type` field;
16
+ - [x] **Source is now the primary entry point**. `package.json` `main`, `module`, `exports[import]`, and `exports[default]` all point to `./src/main.js` — ESM consumers and bundlers (Vite, webpack, Rollup, esbuild) get the source directly, no transpilation in between. CJS consumers still get the pre-built `dist/url-pattern.cjs`. The `dist/` directory is preserved for the UMD browser bundle and the CJS Node bundle;
17
+
18
+
19
+
20
+ ### 1.0.4 (2026-07-10)
21
+ - [x] Added `wildcardName` option (default `'_'`) so the wildcard result key can be renamed and no longer silently collides with a named segment that also uses `_`;
22
+ - [x] `pattern.compiled` and `pattern.compiled.options` are now frozen — accidental mutation of either the top-level state or the nested options throws in strict mode instead of silently desynchronising the cached regex;
23
+ - [x] `g` and `y` flags are now stripped from user-provided regexes when constructing a pattern — they were incompatible with the anchored, single-match contract and made subsequent `match()` calls return `null` because `exec` advanced `lastIndex`. Other flags (`i`, `m`, `s`, `d`, `u`) are preserved and the original regex object is left untouched;
24
+ - [x] `pattern.match()` now throws a `TypeError` on non-string input (number, `null`, `undefined`, object, array) instead of silently coercing with `String()`;
25
+ - [x] Fixed the escape handler's LITERAL branch — it was missing a `continue` and could fall through with stale state, throwing "no segment name" on valid input such as `/:a\:b`;
26
+ - [x] `isAbsentValue` now treats `NaN` as missing, so a stray numeric `NaN` value is omitted from optional groups instead of being stringified to the literal `"NaN"` in a generated URL;
27
+ - [x] `PATTERNS.md` escape section rewritten to match actual behaviour (`\` only escapes regex metacharacters — `:` cannot be escaped, use `\*`, `\(`, `\)`, `\.` for literal matches);
28
+ - [x] README "TypeScript definitions" link fixed (pointed to the non-existent `types/index.d.ts` — now points to `types/main.d.ts`);
29
+ - [x] README "Notes" section added documenting the wildcard-key collision, how `wildcardName` resolves it, and how `g`/`y` regex flags are handled;
30
+ - [x] `types/main.d.ts` tightened: `ParsedSegment.type` and `SegmentName.type` are now literal unions (`'named' | 'wildcard' | 'literal'`) instead of `string`;
31
+
32
+
33
+ ### 1.0.3 (2026-07-10)
34
+ - [x] Default export is now the factory `urlPattern` function, so `import urlPattern from '@peter.naydenov/url-pattern'` and `require('@peter.naydenov/url-pattern')` work without `new`;
35
+ - [x] Implemented `segmentNameEndChar` option so the README example with `{name}` segment syntax parses correctly;
36
+ - [x] Fixed character-class escaping in `segmentValueCharset` — `-`, `]`, `\`, `^` are now always treated as literals, preventing silent regex breakage for custom charsets;
37
+ - [x] Fixed separate optional groups — patterns like `/api(/:a)(/:b)` now produce two independent optional blocks instead of being merged into one;
38
+ - [x] Parser now throws on malformed patterns (trailing `:` without a name, unclosed `(`, unmatched `)`);
39
+ - [x] Default `segmentNameCharset` now includes `_` so `:user_id` parses as a single name;
40
+ - [x] `stringify` now treats an empty array as a missing value for optional segments;
41
+ - [x] Documented `pattern.compiled` introspection field (the old `compile()` method never existed);
42
+ - [x] README and `types/main.d.ts` updated to match the source: `segmentNameCharset` / `segmentValueCharset` defaults use the expanded form, `segmentNameEndChar` is declared, and the wildcard example now shows `_` as the result key;
43
+ - [x] Wildcard (`*`) no longer drops its capture key when the matched suffix is empty — `/v1.2/*` matching `/v1.2/` now correctly returns `{ _: '' }` instead of `{}`;
44
+ - [x] Escape handler no longer consumes the optional-group close delimiter — `\ )` is now treated as a literal `)` (backslash escapes only regex metacharacters, not arbitrary characters), so patterns like `/api(/:foo\))` parse and match correctly;
45
+ - [x] `makePatternFromRegex` now omits keys from unmatched optional groups — `{id: null}` is no longer returned; the key is omitted entirely, consistent with string-pattern behaviour;
46
+ - [x] Duplicate optional groups with the same segment name no longer return `null` in the result array when one group is unmatched — `{a: ['x', null]}` is now `{a: ['x']}`;
47
+
48
+
49
+
50
+ ### 1.0.2 (2026-06-04)
51
+ - [x] Moving from eslint to oxidize;
52
+
53
+
54
+
55
+ ### 1.0.1 (2026-05-14)
56
+ - [x] Fixed type definitions output path;
57
+ - [x] Cleaned up stale build artifacts;
58
+ - [x] Updated rollup config to output minified UMD bundle;
59
+
60
+
61
+
62
+ ### 1.0.0 (2026-05-14)
63
+ - [x] Initial release of URL Pattern library;
64
+ - [x] Pattern matching with named segments (`:name`);
65
+ - [x] Optional segment support (`(segment)`);
66
+ - [x] Wildcard support (`*`);
67
+ - [x] Stringify method to generate URLs from data;
68
+ - [x] Customizable options for segment characters and charsets;
69
+ - [x] TypeScript support;
@@ -404,7 +404,12 @@ const makePatternFromRegex = (regex, keys = []) => {
404
404
  regexObj,
405
405
  segments: [],
406
406
  segmentNames: keys.map((name, index) => ({ name, index, type: 'named' })),
407
- options: DEFAULT_OPTIONS,
407
+ // Use a fresh copy of DEFAULT_OPTIONS rather than the shared reference.
408
+ // The UrlPattern constructor freezes `compiled.options` for read-only
409
+ // semantics; if we returned the shared global, freezing one pattern's
410
+ // `compiled.options` would silently freeze the global for every other
411
+ // pattern created afterwards.
412
+ options: { ...DEFAULT_OPTIONS },
408
413
  isRegex: true,
409
414
  keys
410
415
  };
@@ -400,7 +400,12 @@ const makePatternFromRegex = (regex, keys = []) => {
400
400
  regexObj,
401
401
  segments: [],
402
402
  segmentNames: keys.map((name, index) => ({ name, index, type: 'named' })),
403
- options: DEFAULT_OPTIONS,
403
+ // Use a fresh copy of DEFAULT_OPTIONS rather than the shared reference.
404
+ // The UrlPattern constructor freezes `compiled.options` for read-only
405
+ // semantics; if we returned the shared global, freezing one pattern's
406
+ // `compiled.options` would silently freeze the global for every other
407
+ // pattern created afterwards.
408
+ options: { ...DEFAULT_OPTIONS },
404
409
  isRegex: true,
405
410
  keys
406
411
  };
@@ -1 +1 @@
1
- !function(e,t){"object"==typeof exports&&"undefined"!=typeof module?t(exports):"function"==typeof define&&define.amd?define(["exports"],t):t((e="undefined"!=typeof globalThis?globalThis:e||self).urlPattern={})}(this,function(e){"use strict";const t={escapeChar:"\\",segmentNameStartChar:":",segmentNameEndChar:void 0,segmentNameCharset:"abcdefghijklmnopqrstuvwxyzABCDEFGHIJKLMNOPQRSTUVWXYZ0123456789_",segmentValueCharset:"abcdefghijklmnopqrstuvwxyzABCDEFGHIJKLMNOPQRSTUVWXYZ0123456789-_~ %",optionalSegmentStartChar:"(",optionalSegmentEndChar:")",wildcardChar:"*",wildcardName:"_"},n=e=>e.replace(/[.*+?^${}()|[\]\\]/g,"\\$&"),r=e=>{let t=e.replace(/\\/g,"\\\\");return t=t.replace(/\]/g,"\\]"),t=t.replace(/\^/g,"\\^"),t=t.replace(/-/g,"\\-"),t},a=(e,t,n)=>{const r=[n.escapeChar,n.optionalSegmentStartChar,n.optionalSegmentEndChar,n.wildcardChar,n.segmentNameStartChar];let a=e.length;for(let n=0;n<r.length;n++){const o=r[n];if(!o)continue;const i=e.indexOf(o,t);-1!==i&&i<a&&(a=i)}return a},o=e=>null==e||""===e||(!("number"!=typeof e||!Number.isNaN(e))||!(!Array.isArray(e)||0!==e.length)),i=(e,o={})=>{const i=((e={})=>({...t,...e}))(o),s=((e,t)=>{const o=[];let i=0,s=!1,l=0,p=0;for(;i<e.length;){const d=e[i];if(d===t.escapeChar){if(i+1>=e.length)throw new Error(`Invalid pattern: '\\' at position ${i} has nothing to escape`);const t=e[i+1];if("^$.*+?()[]{}|\\".includes(t)){o.push({type:"literal",name:t,regex:n(t),optional:s,optionalGroupId:s?l:void 0}),i+=2;continue}o.push({type:"literal",name:"\\",regex:"\\\\",optional:s,optionalGroupId:s?l:void 0}),i+=1;continue}if(d===t.optionalSegmentStartChar){s=!0,l++,p++,i++;continue}if(d===t.optionalSegmentEndChar){if(0===p)throw new Error(`Invalid pattern: unmatched '${d}' at position ${i}`);s=!1,p--,i++;continue}if(d===t.wildcardChar){o.push({type:"wildcard",name:t.wildcardName,regex:".*",optional:s,optionalGroupId:s?l:void 0}),i++;continue}if(d===t.segmentNameStartChar&&i+1<e.length){const n=e.slice(i+1);let a=0;const p=t.segmentNameCharset||"",c=t.segmentNameEndChar;for(let e=0;e<n.length&&(!c||n[e]!==c)&&p.includes(n[e]);e++)a=e+1;const m=n.slice(0,a),g=!(!c||n[a]!==c);if(m.length>0){const e=t.segmentValueCharset||"",n=`([${r(e)}]+)`;o.push({type:"named",name:m,regex:n,optional:s,optionalGroupId:s?l:void 0}),i+=1+a+(g?1:0);continue}throw new Error(`Invalid pattern: '${d}' at position ${i} has no segment name`)}const c=a(e,i,t);if(c>i){const t=e.slice(i,c);o.push({type:"literal",name:t,regex:n(t),optional:s,optionalGroupId:s?l:void 0}),i=c;continue}throw new Error(`Invalid pattern: '${d}' at position ${i} has no segment name`)}if(0!==p)throw new Error(`Invalid pattern: unclosed '${t.optionalSegmentStartChar}'`);return o})(e,i),{regex:l,segmentNames:p}=((e,t)=>{let n="^",r=0;const a=[];let o=0;for(;o<e.length;){const i=e[o];if(i.optional){let s="",l=o;const p=i.optionalGroupId;for(;l<e.length&&e[l].optional&&e[l].optionalGroupId===p;){const n=e[l];"wildcard"===n.type?(s+="(.*)",a.push({name:t.wildcardName,index:r,type:"wildcard"}),r++):"named"===n.type?(s+=n.regex,a.push({name:n.name,index:r,type:"named"}),r++):s+=n.regex,l++}n+=`(?:${s})?`,o=l;continue}"wildcard"===i.type?(n+="(.*)",a.push({name:t.wildcardName,index:r,type:"wildcard"}),r++):"named"===i.type?(n+=i.regex,a.push({name:i.name,index:r,type:"named"}),r++):n+=i.regex,o++}return n+="$",{regex:n,segmentNames:a}})(s,i);return{regex:l,regexObj:new RegExp(l),segments:s,segmentNames:p,options:i,isRegex:!1,pattern:e}},s=(e,n=[])=>{const r=e.flags.replace(/[gy]/g,""),a=r===e.flags?e:new RegExp(e.source,r);return{regex:e.source,regexObj:a,segments:[],segmentNames:n.map((e,t)=>({name:e,index:t,type:"named"})),options:t,isRegex:!0,keys:n}},l=(e,t)=>{if("string"!=typeof t)throw new TypeError("pattern.match() requires a string, got "+typeof t);const n=e.regexObj.exec(t);if(!n)return null;if(e.isRegex){if(e.keys&&e.keys.length>0){const t={};return e.keys.forEach((e,r)=>{const a=n[r+1];void 0!==a&&(t[e]=a)}),t}return n.slice(1)}const r={},a=new Set,o=new Set;for(const t of e.segmentNames)"wildcard"===t.type&&o.add(t.name);for(let t=0;t<e.segmentNames.length;t++){const o=e.segmentNames[t],i=o.index+1 in n?n[o.index+1]:"",s=void 0===i?"":i;a.has(o.name)?(Array.isArray(r[o.name])||(r[o.name]=[r[o.name]]),r[o.name].push(s)):(a.add(o.name),r[o.name]=s)}for(const e in r){const t=r[e];if(Array.isArray(t)){const n=t.filter(t=>""!==t||o.has(e));0===n.length?delete r[e]:r[e]=n}else""!==t||o.has(e)||delete r[e]}return r},p=(e,t={})=>{if(e.isRegex)throw new Error("Cannot stringify a pattern created from regex");let n="",r=0;for(;r<e.segments.length;){const a=e.segments[r];if(a.optional){let i="",s=r;const l=a.optionalGroupId;for(;s<e.segments.length&&e.segments[s].optional&&e.segments[s].optionalGroupId===l;){const n=e.segments[s];if("literal"===n.type)i+=n.name;else if("named"===n.type){const e=t[n.name];if(o(e)){i="";break}i+=Array.isArray(e)?e.join("/"):e}else if("wildcard"===n.type){const n=t[e.options.wildcardName];if(o(n)){i="";break}i+=Array.isArray(n)?n.join("/"):n}s++}""!==i&&(n+=i),r===s?r++:r=s;continue}if("literal"===a.type)n+=a.name;else if("named"===a.type){const e=t[a.name];if(o(e))throw new Error(`Missing required value for segment: ${a.name}`);n+=Array.isArray(e)?e.join("/"):e}else if("wildcard"===a.type){const r=t[e.options.wildcardName];if(null==r)throw new Error("Missing required wildcard value");n+=Array.isArray(r)?r.join("/"):r}r++}return n};class d{constructor(e,t={}){if(e instanceof RegExp){const n=Array.isArray(t)?t:[];this.compiled=s(e,n)}else this.compiled=i(e,t);Object.freeze(this.compiled.options),Object.freeze(this.compiled)}match(e){return l(this.compiled,e)}stringify(e){return p(this.compiled,e)}}const c=(e,t={})=>new d(e,t);e.DEFAULT_OPTIONS=t,e.UrlPattern=d,e.default=c,e.makePattern=i,e.makePatternFromRegex=s,e.match=l,e.stringify=p,e.urlPattern=c,Object.defineProperty(e,"__esModule",{value:!0})});
1
+ !function(e,t){"object"==typeof exports&&"undefined"!=typeof module?t(exports):"function"==typeof define&&define.amd?define(["exports"],t):t((e="undefined"!=typeof globalThis?globalThis:e||self).urlPattern={})}(this,function(e){"use strict";const t={escapeChar:"\\",segmentNameStartChar:":",segmentNameEndChar:void 0,segmentNameCharset:"abcdefghijklmnopqrstuvwxyzABCDEFGHIJKLMNOPQRSTUVWXYZ0123456789_",segmentValueCharset:"abcdefghijklmnopqrstuvwxyzABCDEFGHIJKLMNOPQRSTUVWXYZ0123456789-_~ %",optionalSegmentStartChar:"(",optionalSegmentEndChar:")",wildcardChar:"*",wildcardName:"_"},n=e=>e.replace(/[.*+?^${}()|[\]\\]/g,"\\$&"),r=e=>{let t=e.replace(/\\/g,"\\\\");return t=t.replace(/\]/g,"\\]"),t=t.replace(/\^/g,"\\^"),t=t.replace(/-/g,"\\-"),t},a=(e,t,n)=>{const r=[n.escapeChar,n.optionalSegmentStartChar,n.optionalSegmentEndChar,n.wildcardChar,n.segmentNameStartChar];let a=e.length;for(let n=0;n<r.length;n++){const o=r[n];if(!o)continue;const i=e.indexOf(o,t);-1!==i&&i<a&&(a=i)}return a},o=e=>null==e||""===e||(!("number"!=typeof e||!Number.isNaN(e))||!(!Array.isArray(e)||0!==e.length)),i=(e,o={})=>{const i=((e={})=>({...t,...e}))(o),s=((e,t)=>{const o=[];let i=0,s=!1,l=0,p=0;for(;i<e.length;){const d=e[i];if(d===t.escapeChar){if(i+1>=e.length)throw new Error(`Invalid pattern: '\\' at position ${i} has nothing to escape`);const t=e[i+1];if("^$.*+?()[]{}|\\".includes(t)){o.push({type:"literal",name:t,regex:n(t),optional:s,optionalGroupId:s?l:void 0}),i+=2;continue}o.push({type:"literal",name:"\\",regex:"\\\\",optional:s,optionalGroupId:s?l:void 0}),i+=1;continue}if(d===t.optionalSegmentStartChar){s=!0,l++,p++,i++;continue}if(d===t.optionalSegmentEndChar){if(0===p)throw new Error(`Invalid pattern: unmatched '${d}' at position ${i}`);s=!1,p--,i++;continue}if(d===t.wildcardChar){o.push({type:"wildcard",name:t.wildcardName,regex:".*",optional:s,optionalGroupId:s?l:void 0}),i++;continue}if(d===t.segmentNameStartChar&&i+1<e.length){const n=e.slice(i+1);let a=0;const p=t.segmentNameCharset||"",c=t.segmentNameEndChar;for(let e=0;e<n.length&&(!c||n[e]!==c)&&p.includes(n[e]);e++)a=e+1;const m=n.slice(0,a),g=!(!c||n[a]!==c);if(m.length>0){const e=t.segmentValueCharset||"",n=`([${r(e)}]+)`;o.push({type:"named",name:m,regex:n,optional:s,optionalGroupId:s?l:void 0}),i+=1+a+(g?1:0);continue}throw new Error(`Invalid pattern: '${d}' at position ${i} has no segment name`)}const c=a(e,i,t);if(c>i){const t=e.slice(i,c);o.push({type:"literal",name:t,regex:n(t),optional:s,optionalGroupId:s?l:void 0}),i=c;continue}throw new Error(`Invalid pattern: '${d}' at position ${i} has no segment name`)}if(0!==p)throw new Error(`Invalid pattern: unclosed '${t.optionalSegmentStartChar}'`);return o})(e,i),{regex:l,segmentNames:p}=((e,t)=>{let n="^",r=0;const a=[];let o=0;for(;o<e.length;){const i=e[o];if(i.optional){let s="",l=o;const p=i.optionalGroupId;for(;l<e.length&&e[l].optional&&e[l].optionalGroupId===p;){const n=e[l];"wildcard"===n.type?(s+="(.*)",a.push({name:t.wildcardName,index:r,type:"wildcard"}),r++):"named"===n.type?(s+=n.regex,a.push({name:n.name,index:r,type:"named"}),r++):s+=n.regex,l++}n+=`(?:${s})?`,o=l;continue}"wildcard"===i.type?(n+="(.*)",a.push({name:t.wildcardName,index:r,type:"wildcard"}),r++):"named"===i.type?(n+=i.regex,a.push({name:i.name,index:r,type:"named"}),r++):n+=i.regex,o++}return n+="$",{regex:n,segmentNames:a}})(s,i);return{regex:l,regexObj:new RegExp(l),segments:s,segmentNames:p,options:i,isRegex:!1,pattern:e}},s=(e,n=[])=>{const r=e.flags.replace(/[gy]/g,""),a=r===e.flags?e:new RegExp(e.source,r);return{regex:e.source,regexObj:a,segments:[],segmentNames:n.map((e,t)=>({name:e,index:t,type:"named"})),options:{...t},isRegex:!0,keys:n}},l=(e,t)=>{if("string"!=typeof t)throw new TypeError("pattern.match() requires a string, got "+typeof t);const n=e.regexObj.exec(t);if(!n)return null;if(e.isRegex){if(e.keys&&e.keys.length>0){const t={};return e.keys.forEach((e,r)=>{const a=n[r+1];void 0!==a&&(t[e]=a)}),t}return n.slice(1)}const r={},a=new Set,o=new Set;for(const t of e.segmentNames)"wildcard"===t.type&&o.add(t.name);for(let t=0;t<e.segmentNames.length;t++){const o=e.segmentNames[t],i=o.index+1 in n?n[o.index+1]:"",s=void 0===i?"":i;a.has(o.name)?(Array.isArray(r[o.name])||(r[o.name]=[r[o.name]]),r[o.name].push(s)):(a.add(o.name),r[o.name]=s)}for(const e in r){const t=r[e];if(Array.isArray(t)){const n=t.filter(t=>""!==t||o.has(e));0===n.length?delete r[e]:r[e]=n}else""!==t||o.has(e)||delete r[e]}return r},p=(e,t={})=>{if(e.isRegex)throw new Error("Cannot stringify a pattern created from regex");let n="",r=0;for(;r<e.segments.length;){const a=e.segments[r];if(a.optional){let i="",s=r;const l=a.optionalGroupId;for(;s<e.segments.length&&e.segments[s].optional&&e.segments[s].optionalGroupId===l;){const n=e.segments[s];if("literal"===n.type)i+=n.name;else if("named"===n.type){const e=t[n.name];if(o(e)){i="";break}i+=Array.isArray(e)?e.join("/"):e}else if("wildcard"===n.type){const n=t[e.options.wildcardName];if(o(n)){i="";break}i+=Array.isArray(n)?n.join("/"):n}s++}""!==i&&(n+=i),r===s?r++:r=s;continue}if("literal"===a.type)n+=a.name;else if("named"===a.type){const e=t[a.name];if(o(e))throw new Error(`Missing required value for segment: ${a.name}`);n+=Array.isArray(e)?e.join("/"):e}else if("wildcard"===a.type){const r=t[e.options.wildcardName];if(null==r)throw new Error("Missing required wildcard value");n+=Array.isArray(r)?r.join("/"):r}r++}return n};class d{constructor(e,t={}){if(e instanceof RegExp){const n=Array.isArray(t)?t:[];this.compiled=s(e,n)}else this.compiled=i(e,t);Object.freeze(this.compiled.options),Object.freeze(this.compiled)}match(e){return l(this.compiled,e)}stringify(e){return p(this.compiled,e)}}const c=(e,t={})=>new d(e,t);e.DEFAULT_OPTIONS=t,e.UrlPattern=d,e.default=c,e.makePattern=i,e.makePatternFromRegex=s,e.match=l,e.stringify=p,e.urlPattern=c,Object.defineProperty(e,"__esModule",{value:!0})});
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@peter.naydenov/url-pattern",
3
- "version": "1.0.5",
3
+ "version": "1.0.6",
4
4
  "description": "Matching patterns for urls and other strings. Turn strings into data or data into strings.",
5
5
  "type": "module",
6
6
  "main": "./src/main.js",
@@ -9,7 +9,8 @@
9
9
  "files": [
10
10
  "dist",
11
11
  "types",
12
- "src"
12
+ "src",
13
+ "CHANGELOG.md"
13
14
  ],
14
15
  "exports": {
15
16
  ".": {
package/readme.md CHANGED
@@ -6,7 +6,7 @@
6
6
  [![GitHub top language](https://img.shields.io/github/languages/top/PeterNaydenov/url-pattern.svg)](https://github.com/PeterNaydenov/url-pattern)
7
7
  [![npm bundle size](https://img.shields.io/bundlejs/size/@peter.naydenov/url-pattern.svg)](https://www.npmjs.com/package/@peter.naydenov/url-pattern)
8
8
 
9
- Easier than regex string matching patterns for urls and other strings. Turn strings into data or data into strings.
9
+ String-matching patterns for URLs and other strings — easier than regex. Turn strings into data or data into strings.
10
10
 
11
11
  ## Install
12
12
 
@@ -14,13 +14,13 @@ Easier than regex string matching patterns for urls and other strings. Turn stri
14
14
  npm install @peter.naydenov/url-pattern
15
15
  ```
16
16
 
17
- Once it has been installed, it can be used by writing this line of JavaScript:
17
+ Once it has been installed, it can be used with one of the following:
18
18
 
19
19
  ```js
20
- // if you are using ES6:
20
+ // if you are using ES modules:
21
21
  import urlPattern from '@peter.naydenov/url-pattern'
22
22
 
23
- // if you are using commonJS:
23
+ // if you are using CommonJS:
24
24
  const urlPattern = require ( '@peter.naydenov/url-pattern' )
25
25
  ```
26
26
 
@@ -43,10 +43,10 @@ console.log ( result )
43
43
  ```js
44
44
  import urlPattern from '@peter.naydenov/url-pattern'
45
45
 
46
- const pattern = urlPattern ( '/api/(v1)/users/:id' )
47
- const result = pattern.match ( '/api/v1/users/456' )
48
-
49
- console.log ( result )
46
+ const pattern = urlPattern ( '/api(/:version)/users/:id' )
47
+ console.log ( pattern.match ( '/api/v1/users/456' ) )
48
+ // Output: { version: 'v1', id: '456' }
49
+ console.log ( pattern.match ( '/api/users/456' ) )
50
50
  // Output: { id: '456' }
51
51
  ```
52
52
 
@@ -89,11 +89,6 @@ const result = pattern.match ( '/user/john/post/123' )
89
89
  console.log ( result )
90
90
  // Output: { username: 'john', postId: '123' }
91
91
  ```
92
- ## URL Patterns
93
-
94
- See [supported URL pattern types with examples](./PATTERNS.md).
95
-
96
-
97
92
 
98
93
  ## API Reference
99
94
 
@@ -124,11 +119,11 @@ Matches a string against the pattern and returns an object with captured values,
124
119
 
125
120
  #### `pattern.stringify(data)`
126
121
 
127
- Generates a URL string from provided data object.
122
+ Generates a URL string from provided data object. Throws an `Error` when a required segment is missing from `data` (e.g. a required `:id` has no matching `id` property).
128
123
 
129
124
  #### `pattern.compiled`
130
125
 
131
- Read-only object exposing the internal compiled state: `regex` (regex source), `regexObj` (compiled `RegExp`), `segments` (parsed segments array), `segmentNames` (segment name → capture-group mappings), `options` (merged options), `isRegex` (whether the pattern was created from a regex), and `pattern` (the original pattern string). The object is frozen — any attempt to mutate it will throw in strict mode (or fail silently elsewhere). Useful for introspection; do not mutate.
126
+ Read-only object exposing the internal compiled state: `regex` (regex source), `regexObj` (compiled `RegExp`), `segments` (parsed segments array), `segmentNames` (segment name → capture-group mappings), `options` (merged options), `isRegex` (whether the pattern was created from a regex), and `pattern` (the original pattern string). For regex-based patterns, `keys` is also present (the list of capture-group key names). The object is frozen — any attempt to mutate it will throw in strict mode (or fail silently elsewhere). Useful for introspection; do not mutate.
132
127
 
133
128
  ## Notes
134
129
 
@@ -162,4 +157,4 @@ When you pass a `RegExp` to `urlPattern`, the `g` and `y` flags are stripped. Th
162
157
 
163
158
  ## License
164
159
 
165
- '@peter.naydenov/url-pattern' is released under the [MIT License](https://github.com/PeterNaydenov/git-url-pattern/blob/main/LICENSE).
160
+ '@peter.naydenov/url-pattern' is released under the [MIT License](https://github.com/PeterNaydenov/url-pattern/blob/main/LICENSE).
package/src/main.js CHANGED
@@ -400,7 +400,12 @@ const makePatternFromRegex = (regex, keys = []) => {
400
400
  regexObj,
401
401
  segments: [],
402
402
  segmentNames: keys.map((name, index) => ({ name, index, type: 'named' })),
403
- options: DEFAULT_OPTIONS,
403
+ // Use a fresh copy of DEFAULT_OPTIONS rather than the shared reference.
404
+ // The UrlPattern constructor freezes `compiled.options` for read-only
405
+ // semantics; if we returned the shared global, freezing one pattern's
406
+ // `compiled.options` would silently freeze the global for every other
407
+ // pattern created afterwards.
408
+ options: { ...DEFAULT_OPTIONS },
404
409
  isRegex: true,
405
410
  keys
406
411
  };