identifier-js 0.3.0 → 0.4.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/index.d.ts CHANGED
@@ -90,6 +90,17 @@ export type AbsoluteIdentifierComponents = {
90
90
  query?: string;
91
91
  };
92
92
 
93
- export type ParsedIdentifierComponents = IdentifierComponents & NormalizableReference;
94
- export type ParsedRelativeIdentifierComponents = RelativeIdentifierComponents & NormalizableReference;
95
- export type ParsedAbsoluteIdentifierComponents = AbsoluteIdentifierComponents & NormalizableReference;
93
+ type UrnIdentifierComponents = {
94
+ scheme: string;
95
+ nid: string;
96
+ nss: string;
97
+ rComponent?: string;
98
+ qComponent?: string;
99
+ fComponent?: string;
100
+ };
101
+
102
+ type AbsoluteUrnIdentifierComponents = Omit<UrnIdentifierComponents, 'fComponent'>;
103
+
104
+ export type ParsedIdentifierComponents = (IdentifierComponents | UrnIdentifierComponents) & NormalizableReference;
105
+ export type ParsedRelativeIdentifierComponents = (RelativeIdentifierComponents | UrnIdentifierComponents) & NormalizableReference;
106
+ export type ParsedAbsoluteIdentifierComponents = (AbsoluteIdentifierComponents | AbsoluteUrnIdentifierComponents) & NormalizableReference;
package/index.js CHANGED
@@ -1,6 +1,6 @@
1
1
  'use strict';
2
- // Parse, validate, normalize, resolve, and convert RFC 3986 URI and RFC 3987 IRI references.
3
- // a valid URI is always a valid IRI
2
+ // Parse, validate, normalize, resolve, and convert RFC 3986 URI, RFC 3987 IRI, and RFC 8141 URN references.
3
+ // A valid URI is always a valid IRI, subject to every implemented scheme's more specific grammar.
4
4
  const { recursiveCompile } = require('url-templates');
5
5
  const patterns = new Map();
6
6
  const implemented_schemes = '(?:[hH][tT][tT][pP][sS]?|[wW][sS][sS]?|[fF][iI][lL][eE])';
@@ -79,6 +79,23 @@ const iriRules = {
79
79
  iprivate: '[\\uE000-\\uF8FF\\u{F0000}-\\u{FFFFD}\\u{100000}-\\u{10FFFD}]',
80
80
  ucschar: '[\\xA0-\\uD7FF\\uF900-\\uFDCF\\uFDF0-\\uFFEF\\u{10000}-\\u{1FFFD}\\u{20000}-\\u{2FFFD}\\u{30000}-\\u{3FFFD}\\u{40000}-\\u{4FFFD}\\u{50000}-\\u{5FFFD}\\u{60000}-\\u{6FFFD}\\u{70000}-\\u{7FFFD}\\u{80000}-\\u{8FFFD}\\u{90000}-\\u{9FFFD}\\u{A0000}-\\u{AFFFD}\\u{B0000}-\\u{BFFFD}\\u{C0000}-\\u{CFFFD}\\u{D0000}-\\u{DFFFD}\\u{E1000}-\\u{EFFFD}]',
81
81
  };
82
+ // Define RFC 8141 productions and URI/IRI root overrides for the conditional URN profile.
83
+ const urnRules = {
84
+ scheme: '[uU][rR][nN]',
85
+ URI: '{namestring}',
86
+ absolute_URI: '{assigned_name}(?:{rq_components})?',
87
+ IRI: '{URI}',
88
+ absolute_IRI: '{absolute_URI}',
89
+ namestring: '{assigned_name}(?:{rq_components})?(?:#{f_component})?',
90
+ assigned_name: '{scheme}:{NID}:{NSS}',
91
+ NID: '{alpha_digit}{ldh}{0,30}{alpha_digit}',
92
+ ldh: '(?:{alpha_digit}|-)',
93
+ NSS: '{pchar}(?:{pchar}|/)*',
94
+ rq_components: '(?:[?][+]{r_component})?(?:[?]={q_component})?',
95
+ r_component: '{pchar}(?:{pchar}|/|[?](?!=))*',
96
+ q_component: '{pchar}(?:{pchar}|/|[?])*',
97
+ f_component: '{fragment}',
98
+ };
82
99
  // Reuse the grammar repertoires when selecting URI octets safe for IRI output.
83
100
  const uriUnreservedPattern = new RegExp(`^${commonRules.unreserved}$`);
84
101
  const iriUcscharPattern = new RegExp(`^${iriRules.ucschar}$`, 'u');
@@ -125,16 +142,28 @@ const groupNames = {
125
142
  ipath_noscheme: 'path',
126
143
  ipath_rootless: 'path',
127
144
  ipath_empty: 'path',
145
+ NID: 'nid',
146
+ NSS: 'nss',
147
+ r_component: 'rComponent',
148
+ q_component: 'qComponent',
149
+ f_component: 'fComponent',
128
150
  };
129
- // Select and merge generic, DNS-host, or empty-file-host grammar overrides.
151
+ // Keep URN parse results limited to their RFC 8141 component names.
152
+ const genericUrnGroupNames = new Set(['authority', 'userinfo', 'host', 'port', 'path', 'query', 'fragment']);
153
+ // Detect schemes for which the package implements grammar beyond generic URI/IRI syntax.
130
154
  const isSpecificScheme = (string) => new RegExp('^' + implemented_schemes + ':').test(string);
131
- const schemeProfile = (string) => (string.slice(0, 8).toLowerCase() === 'file:///' ? 'f' : isSpecificScheme(string) ? 's' : '');
132
- const rules = (profile) => Object.assign({}, commonRules, uriRules, iriRules, profile === 'f' ? emptyFileHostRules : profile ? schemeSpecificRules : {});
155
+ // Select and merge generic, DNS-host, empty-file-host, or URN grammar profiles.
156
+ const schemeProfile = (string) => (string.slice(0, 4).toLowerCase() === 'urn:' ? 'u' : string.slice(0, 8).toLowerCase() === 'file:///' ? 'f' : isSpecificScheme(string) ? 's' : '');
157
+ const rules = (profile) => Object.assign({}, commonRules, uriRules, iriRules, profile === 'u' ? urnRules : profile === 'f' ? emptyFileHostRules : profile ? schemeSpecificRules : {});
133
158
  // parse (slower, it uses regex.exec and includes named capture groups)
134
159
  const parse = (string, rule) => {
135
160
  if (typeof string !== 'string') throw new TypeError(`Invalid ${rule.replace('_', '-')} type: must be a string.`);
136
161
  const profile = schemeProfile(string);
137
- const addNames = (key) => (groupNames[key] ? `(?<${groupNames[key]}>${rules(profile)[key]})` : rules(profile)[key]);
162
+ // Select only the component captures exposed by the active grammar.
163
+ const addNames = (key) => {
164
+ const groupName = groupNames[key];
165
+ return groupName && !(profile === 'u' && genericUrnGroupNames.has(groupName)) ? `(?<${groupName}>${rules(profile)[key]})` : rules(profile)[key];
166
+ };
138
167
  const ruleId = '_' + profile + rule;
139
168
  if (!patterns.has(ruleId)) patterns.set(ruleId, new RegExp(`^${recursiveCompile(rules(profile), rule, addNames)}$`, 'u'));
140
169
  const match = patterns.get(ruleId).exec(string);
@@ -533,6 +562,13 @@ function normalizeParsedReference(parts, options = {}) {
533
562
  const { transform, mapRegName } = options;
534
563
  if (transform !== undefined && transform !== 'URI' && transform !== 'IRI') throw new TypeError('Invalid transform option: must be "URI" or "IRI".');
535
564
  if (mapRegName !== undefined && typeof mapRegName !== 'function') throw new TypeError('Invalid registered-name mapper type: must be a function.');
565
+ // Normalize captured URN fields without applying generic path or representation processing.
566
+ if (parts.nid !== undefined) {
567
+ const rComponent = parts.rComponent === undefined ? undefined : normalizePercentEncoding(parts.rComponent, false);
568
+ const qComponent = parts.qComponent === undefined ? undefined : normalizePercentEncoding(parts.qComponent, false);
569
+ const query = rComponent !== undefined ? `+${rComponent}${qComponent === undefined ? '' : `?=${qComponent}`}` : qComponent === undefined ? undefined : `=${qComponent}`;
570
+ return compose({ scheme: parts.scheme.toLowerCase(), path: `${parts.nid.toLowerCase()}:${normalizePercentEncoding(parts.nss, false)}`, query, fragment: parts.fComponent === undefined ? undefined : normalizePercentEncoding(parts.fComponent, false) });
571
+ }
536
572
  // Normalize each component independently so encoded delimiters cannot become structure.
537
573
  const scheme = parts.scheme === undefined ? undefined : parts.scheme.toLowerCase();
538
574
  const normalized = {
package/normalization.md CHANGED
@@ -1,6 +1,6 @@
1
1
  # URI and IRI normalization
2
2
 
3
- Parsed URI and IRI results expose `normalize()` for syntax-based normalization, the implemented HTTP, HTTPS, WS, and WSS scheme rules, and optional RFC 3987 URI/IRI representation transformation. The method returns a string and leaves the parsed components unchanged.
3
+ Parsed URI and IRI results expose `normalize()` for syntax-based normalization, the implemented HTTP, HTTPS, WS, WSS, and URN scheme rules, and optional RFC 3987 URI/IRI representation transformation. The method returns a string and leaves the parsed components unchanged.
4
4
 
5
5
  ## API
6
6
 
@@ -49,8 +49,8 @@ parsed.path;
49
49
  | Behavior | Implementation | Source |
50
50
  | --- | --- | --- |
51
51
  | Case normalization | Lowercase the scheme and an ASCII-only host. Uppercase hexadecimal letters in percent triplets. | [RFC 3986 §6.2.2.1](https://www.rfc-editor.org/rfc/rfc3986#section-6.2.2.1), [RFC 3987 §5.3.2.1](https://www.rfc-editor.org/rfc/rfc3987#section-5.3.2.1) |
52
- | Percent-encoded unreserved characters | Decode percent triplets representing ASCII letters, digits, `-`, `.`, `_`, or `~`. Retain percent encoding for reserved octets. | [RFC 3986 §§2.2–2.4 and 6.2.2.2](https://www.rfc-editor.org/rfc/rfc3986#section-6.2.2.2), [RFC 3987 §5.3.2.3](https://www.rfc-editor.org/rfc/rfc3987#section-5.3.2.3) |
53
- | Path segments | Apply the RFC dot-segment algorithm where the parsed reference can be normalized independently. Preserve unresolved rootless-relative path semantics. | [RFC 3986 §§5.2.4 and 6.2.2.3](https://www.rfc-editor.org/rfc/rfc3986#section-5.2.4), [RFC 3987 §5.3.2.4](https://www.rfc-editor.org/rfc/rfc3987#section-5.3.2.4) |
52
+ | Percent-encoded unreserved characters | For generic URI/IRI components, decode percent triplets representing ASCII letters, digits, `-`, `.`, `_`, or `~`. Retain percent encoding for reserved octets. URNs use the non-decoding rules below. | [RFC 3986 §§2.2–2.4 and 6.2.2.2](https://www.rfc-editor.org/rfc/rfc3986#section-6.2.2.2), [RFC 3987 §5.3.2.3](https://www.rfc-editor.org/rfc/rfc3987#section-5.3.2.3) |
53
+ | Path segments | Apply the RFC dot-segment algorithm where a generic parsed reference can be normalized independently. Preserve unresolved rootless-relative path semantics and every URN NSS segment. | [RFC 3986 §§5.2.4 and 6.2.2.3](https://www.rfc-editor.org/rfc/rfc3986#section-5.2.4), [RFC 3987 §5.3.2.4](https://www.rfc-editor.org/rfc/rfc3987#section-5.3.2.4), [RFC 8141 §§2.2 and 3.1](https://www.rfc-editor.org/rfc/rfc8141#section-3.1) |
54
54
  | Component recomposition | Emit authority, query, and fragment delimiters from component presence, including present-empty components. | [RFC 3986 §5.3](https://www.rfc-editor.org/rfc/rfc3986#section-5.3) |
55
55
  | IPv6 text | Suppress leading zeroes, compress the longest zero run with first-run tie breaking, and use lowercase hexadecimal. Known embedded-IPv4 forms use mixed notation. | [RFC 5952 §§4–5](https://www.rfc-editor.org/rfc/rfc5952#section-4) |
56
56
  | IRI-to-URI output | With `transform: 'URI'`, encode non-ASCII authority, path, query, and fragment characters as uppercase UTF-8 percent triplets. | [RFC 3987 §3.1](https://www.rfc-editor.org/rfc/rfc3987#section-3.1) |
@@ -58,6 +58,30 @@ parsed.path;
58
58
 
59
59
  Without a mapper, normalization retains the parser's host classification as an IP literal, IPv4 address, or registered name. IPvFuture literals use generic host case normalization. Existing non-ASCII IRI host text is retained unless the registered-name mapper supplies another value.
60
60
 
61
+ ## URNs
62
+
63
+ A parsed value under the case-insensitive `urn` scheme takes a separate RFC 8141 normalization path using its captured `scheme`, `nid`, `nss`, `rComponent`, `qComponent`, and `fComponent` properties.
64
+
65
+ | Input component | Output | Source |
66
+ | --- | --- | --- |
67
+ | Scheme | Convert `urn` to lowercase. | [RFC 8141 §3.1](https://www.rfc-editor.org/rfc/rfc8141#section-3.1) |
68
+ | NID | Convert ASCII letters to lowercase. | [RFC 8141 §§2.1 and 3.1](https://www.rfc-editor.org/rfc/rfc8141#section-3.1) |
69
+ | NSS | Uppercase hexadecimal letters in percent triplets without decoding any octet. Preserve literal case, slash structure, and dot segments. | [RFC 8141 §§2.2 and 3.1](https://www.rfc-editor.org/rfc/rfc8141#section-3.1) |
70
+ | r-, q-, and f-components | Retain the components and their delimiters, uppercasing hexadecimal letters in percent triplets without decoding. | [RFC 8141 §2.3](https://www.rfc-editor.org/rfc/rfc8141#section-2.3), [RFC 3986 §6.2.2.1](https://www.rfc-editor.org/rfc/rfc3986#section-6.2.2.1) |
71
+
72
+ ```js
73
+ const { parseUri } = require('identifier-js');
74
+
75
+ parseUri('URN:EXAMPLE:a%62/./b/../C?+r%2f?=q%2f#f%2f').normalize();
76
+ // urn:example:a%62/./b/../C?+r%2F?=q%2F#f%2F
77
+ ```
78
+
79
+ RFC 8141 URNs remain ASCII, including when parsed through an IRI operation. Consequently, `transform: 'URI'` and `transform: 'IRI'` produce the same URN representation, and `mapRegName` is not called because a URN has no authority or registered-name host.
80
+
81
+ For a parsed URN, the current scheme-specific fields are the normalization input. The NSS and optional-component values stay opaque except for percent-triplet letter case. The method leaves every property unchanged.
82
+
83
+ Normalization is not a URN-equivalence API. RFC 8141 equivalence compares the normalized assigned-name and ignores r-, q-, and f-components; namespace definitions can add further equivalence rules. This method instead retains those optional components in its returned string. The package does not implement generic or namespace-specific URN-equivalence comparison.
84
+
61
85
  ## HTTP and HTTPS
62
86
 
63
87
  For `http` and `https`, normalization applies the generic rules and these scheme rules:
@@ -141,6 +165,7 @@ npm test
141
165
 
142
166
  - [RFC 3986 — Uniform Resource Identifier: Generic Syntax](https://www.rfc-editor.org/rfc/rfc3986)
143
167
  - [RFC 3987 — Internationalized Resource Identifiers](https://www.rfc-editor.org/rfc/rfc3987)
168
+ - [RFC 8141 — Uniform Resource Names](https://www.rfc-editor.org/rfc/rfc8141)
144
169
  - [RFC 5952 — A Recommendation for IPv6 Address Text Representation](https://www.rfc-editor.org/rfc/rfc5952)
145
170
  - [RFC 6455 — The WebSocket Protocol](https://www.rfc-editor.org/rfc/rfc6455)
146
171
  - [RFC 9110 — HTTP Semantics](https://www.rfc-editor.org/rfc/rfc9110)
package/package.json CHANGED
@@ -1,19 +1,21 @@
1
1
  {
2
2
  "name": "identifier-js",
3
- "version": "0.3.0",
4
- "description": "A fast URI/IRI parser, validator, normalizer, resolver, and composer based on RFC 3986 and RFC 3987.",
3
+ "version": "0.4.1",
4
+ "description": "A fast RFC 3986/3987 URI/IRI parser, validator, normalizer, resolver, and composer with RFC 8141 URN syntax support.",
5
5
  "keywords": [
6
6
  "IRI",
7
7
  "URI",
8
8
  "IRI-reference",
9
9
  "URI-reference",
10
+ "URN",
10
11
  "ipv4",
11
12
  "ipv6",
12
13
  "uuid",
13
14
  "parser",
14
15
  "validator",
15
16
  "RFC3986",
16
- "RFC3987"
17
+ "RFC3987",
18
+ "RFC8141"
17
19
  ],
18
20
  "homepage": "https://github.com/SorinGFS/identifier-js#readme",
19
21
  "bugs": {
package/readme.md CHANGED
@@ -2,17 +2,17 @@
2
2
 
3
3
  title: Identifier JS
4
4
 
5
- description: An RFC 3986 and RFC 3987 parser, validator, normalizer, and reference resolver for Node.js and browser bundles.
5
+ description: RFC 3986/3987 URI and IRI tools with scheme-specific RFC 8141 URN syntax and normalization support.
6
6
 
7
7
  ---
8
8
 
9
9
  # Identifier JS
10
10
 
11
- `identifier-js` is a URI/IRI parser, validator, normalizer, resolver, and composer based on RFC [3986](https://www.rfc-editor.org/rfc/rfc3986) and RFC [3987](https://www.rfc-editor.org/rfc/rfc3987). Its recognized HTTP, WebSocket, and `file` schemes retain the documented hostname-policy restrictions below. It provides:
11
+ `identifier-js` is a URI/IRI parser, validator, normalizer, resolver, and composer based on RFC [3986](https://www.rfc-editor.org/rfc/rfc3986) and RFC [3987](https://www.rfc-editor.org/rfc/rfc3987), with scheme-specific RFC [8141](https://www.rfc-editor.org/rfc/rfc8141) URN support. Its recognized HTTP, WebSocket, and `file` schemes retain the documented hostname-policy restrictions below. It provides:
12
12
 
13
- - URI and IRI validation;
14
- - parsed identifier components;
15
- - conservative syntax normalization, recognized-scheme port/path forms, and a registered-name extension point;
13
+ - URI and IRI validation, including RFC 8141 URN namestring syntax;
14
+ - parsed generic URI/IRI components and scheme-specific URN components;
15
+ - conservative syntax normalization, recognized-scheme forms, and a registered-name extension point;
16
16
  - RFC 3986 reference resolution and dot-segment removal;
17
17
  - relative-reference generation with resolution round-trip guarantees for supported forms;
18
18
  - UUID and UUIDv4 lexical validation;
@@ -143,6 +143,45 @@ console.log(parseIri('https://usér@例え.テスト:8443/résumé?lang=fr#profi
143
143
 
144
144
  </details>
145
145
 
146
+ ### Validate and parse RFC 8141 URNs
147
+
148
+ URNs use the existing URI and IRI operations because a URN is a URI under the `urn` scheme. Values with a case-insensitive `urn:` prefix are validated against RFC 8141 namestring syntax; no separate `isUrn` or `parseUrn` API is exported.
149
+
150
+ ```text
151
+ urn:NID:NSS[?+r-component][?=q-component][#f-component]
152
+ ```
153
+
154
+ The NID contains 2–32 ASCII characters, starts and ends with a letter or digit, and permits letters, digits, or hyphens internally. The NSS begins with an RFC 3986 `pchar` and then permits `pchar` or `/`. The ordered r- and q-components also begin with `pchar` and then permit `pchar`, `/`, or `?`, while an f-component can be empty. The first `?=` sequence after an r-component starts the q-component, and any other question mark outside an optional component is rejected.
155
+
156
+ <details>
157
+ <summary><strong>API behavior and examples</strong></summary>
158
+
159
+ ```js
160
+ const { isUri, isIri, parseUri } = require('identifier-js');
161
+
162
+ const value = 'URN:Example:a%2f/../B?+service?x?=key=value#part';
163
+ console.log(isUri(value)); // true
164
+ console.log(isIri(value)); // true
165
+
166
+ const parsed = parseUri(value);
167
+ console.log(parsed.scheme); // URN
168
+ console.log(parsed.nid); // Example
169
+ console.log(parsed.nss); // a%2f/../B
170
+ console.log(parsed.rComponent); // service?x
171
+ console.log(parsed.qComponent); // key=value
172
+ console.log(parsed.fComponent); // part
173
+ console.log(parsed.normalize());
174
+ // urn:example:a%2F/../B?+service?x?=key=value#part
175
+ ```
176
+
177
+ URN parse results expose `nid`, `nss`, `rComponent`, `qComponent`, and `fComponent`. They do not expose generic `path`, `query`, or `fragment` aliases. To require a URN after parsing a value accepted as a general URI, check `parsed.scheme.toLowerCase() === 'urn'`.
178
+
179
+ URNs remain ASCII even through the IRI operations. Callers representing non-ASCII names must first encode them as UTF-8 and then percent-encode the resulting octets; lexical validation does not decode or verify those octet sequences.
180
+
181
+ Validation is deliberately lexical and namespace-independent. Success does not prove that an NID is registered or otherwise legitimate, that an NSS obeys a namespace's additional syntax and canonicalization rules, or that the name was legitimately assigned.
182
+
183
+ </details>
184
+
146
185
  ### Resolve a reference
147
186
 
148
187
  Resolve a URI or IRI reference against an absolute base using RFC 3986 §5.
@@ -173,6 +212,8 @@ console.log(resolveReference('?page=2', 'https://example.com/items?page=1#curren
173
212
 
174
213
  Empty authorities, queries, and fragments are preserved during recomposition.
175
214
 
215
+ `resolveReference` does not apply when either input uses the `urn` scheme. URN resolution services are outside this package's scope.
216
+
176
217
  </details>
177
218
 
178
219
  ### Produce absolute and relative forms
@@ -201,6 +242,8 @@ console.log(relative); // ../images/logo.svg
201
242
 
202
243
  When no safe rootless relative form can round-trip to the target, `toRelativeReference` returns the absolute target. Different schemes or authorities also return the target unchanged. Complete dot segments in either path also trigger this fallback because RFC resolution removes them. For those inputs, resolving the result produces the same identifier as resolving the target directly; lexical dot-segment spelling is not preserved.
203
244
 
245
+ `toAbsoluteReference` and `toRelativeReference` do not apply when an input uses the `urn` scheme. Relative-URN semantics are outside this package's scope.
246
+
204
247
  </details>
205
248
 
206
249
  ### Normalize parsed URI and IRI references
@@ -239,6 +282,8 @@ The method is available from `parseUri`, `parseUriReference`, `parseAbsoluteUri`
239
282
 
240
283
  Normalization implements RFC 3986 and RFC 3987 syntax normalization for scheme and host case, percent triplets, ASCII unreserved characters, path dot segments, and component recomposition. IPv6 literals use RFC 5952 text. HTTP(S) default ports and empty paths follow RFC 9110; WS(S) defaults and resource-name paths follow RFC 6455.
241
284
 
285
+ URN normalization lowercases the scheme and NID, uppercases percent-triplet hexadecimal letters without decoding, and preserves NSS case, slashes, and dot segments. The r-, q-, and f-components are retained, so normalized-string equality is not the RFC 8141 URN-equivalence procedure. Namespace-specific equivalence and URN resolution are outside this package's scope. URI/IRI transformation and registered-name mapping options do not alter authority-free, ASCII-only URNs.
286
+
242
287
  For a non-empty registered-name host, `mapRegName` receives the current host spelling before built-in normalization. The mapper exclusively owns validation, representation, and host-kind policy for its returned string. Apart from enforcing the declared string return type, this package does not check whether mapper output is non-empty, remains a registered name, introduces delimiters, resembles an IP address, or satisfies a scheme-specific hostname grammar.
243
288
 
244
289
  With `transform: 'URI'`, non-ASCII userinfo, mapper output, path, query, and fragment text becomes uppercase UTF-8 percent triplets under RFC 3987 §3.1. With `transform: 'IRI'`, eligible percent-encoded ASCII unreserved characters and strictly legal UTF-8 sequences become IRI characters under RFC 3987 §3.2; reserved, malformed, disallowed, and non-UTF-8 octets remain encoded. Private-use characters are decoded only in queries, and forbidden bidirectional formatting characters remain encoded. A mapper can supply the desired Unicode or ASCII hostname representation; this package does not enforce that policy or validate the complete normalized result.
@@ -274,11 +319,11 @@ console.log(isUUIDv4('123e4567-e89b-42d3-9456-426614174000')); // true
274
319
 
275
320
  The parser builds its validation logic from declarative RFC grammar fragments:
276
321
 
277
- 1. Select generic URI/IRI rules or the package's scheme-specific hostname policy.
322
+ 1. Select the applicable generic or scheme-specific URI/IRI syntax.
278
323
  2. Recursively expand grammar references through `url-templates`.
279
- 3. Add named capture groups when parsing is requested.
324
+ 3. Add named captures for the components exposed by the selected syntax.
280
325
  4. Compile the complete expression with Unicode support.
281
- 5. Cache the expression by operation, grammar rule, and scheme-policy class.
326
+ 5. Cache expressions by operation, grammar rule, and scheme class.
282
327
  6. Validate with `RegExp.test()` or parse with `RegExp.exec()`.
283
328
  7. Resolve references by component inheritance, path merging, dot-segment removal, and definedness-preserving recomposition.
284
329
 
@@ -302,7 +347,7 @@ The following schemes trigger DNS-style ASCII or Unicode label rules instead of
302
347
  - `wss`
303
348
  - `file`
304
349
 
305
- Matching is case-insensitive. Other valid schemes use generic RFC 3986/3987 registered-name syntax. RFC 8089's empty `file` authority is accepted when followed by an absolute path, as in `file:///path`; empty hosts remain rejected for HTTP and WebSocket schemes.
350
+ Matching is case-insensitive. Other valid schemes use generic RFC 3986/3987 registered-name syntax; URNs instead follow RFC 8141 namestring syntax. RFC 8089's empty `file` authority is accepted when followed by an absolute path, as in `file:///path`; empty hosts remain rejected for HTTP and WebSocket schemes.
306
351
 
307
352
  Parsing validates DNS-style label shape and the selected RFC 3987 Unicode character classes. A registered-name mapper runs later during optional normalization, and its returned string is not submitted to this hostname policy again.
308
353
 
@@ -418,8 +463,10 @@ RFC 9562 lists database keys, filenames, system identifiers, and transaction ide
418
463
  <details>
419
464
  <summary><strong>Validation and parsing</strong></summary>
420
465
 
421
- - Generic URI syntax follows RFC 3986 character and component grammar; recognized schemes select the documented hostname profile.
422
- - Generic IRI syntax follows the RFC 3987 Unicode extensions to URI grammar; recognized schemes select the documented hostname profile.
466
+ - Generic URI syntax follows RFC 3986 character and component grammar; HTTP, WebSocket, and `file` schemes apply the documented hostname restrictions.
467
+ - Generic IRI syntax follows the RFC 3987 Unicode extensions to URI grammar; HTTP, WebSocket, and `file` schemes apply the documented hostname restrictions.
468
+ - Values with the case-insensitive `urn` scheme follow RFC 8141 namestring syntax and expose `nid`, `nss`, `rComponent`, `qComponent`, and `fComponent` fields through the URI and IRI parsers.
469
+ - URN validation establishes generic lexical syntax only, not namespace registration, namespace-specific syntax, assignment, resolution, or equivalence.
423
470
  - Validators return `true` or throw at the first grammar violation.
424
471
  - `absolute-URI` and `absolute-IRI` use the fragment-free grammar defined by their RFCs; complete URI and IRI operations accept fragments.
425
472
  - Port syntax follows RFC 3986 `port = *DIGIT`, including empty and leading-zero values.
@@ -435,7 +482,8 @@ RFC 9562 lists database keys, filenames, system identifiers, and transaction ide
435
482
  - `strict = false` implements RFC 3986 §5.2.2 backward-compatible same-scheme handling.
436
483
  - `toAbsoluteReference` removes the fragment from an identifier containing a scheme.
437
484
  - `toRelativeReference` generates a reference whose RFC resolution equals the target resolution for supported forms.
438
- - `normalize()` implements the applicable case, percent-encoding, and path-segment rules from RFC 3986 §§6.2.2.1–6.2.2.3 and RFC 3987 §§5.3.2.1, 5.3.2.3–5.3.2.4, RFC 3987 §§3.1–3.2 URI/IRI representation transformation, RFC 5952 IPv6 text, RFC 9110 HTTP(S) port/path forms, and RFC 6455 WS(S) port/resource-name forms.
485
+ - `normalize()` implements the applicable case, percent-encoding, and path-segment rules from RFC 3986 §§6.2.2.1–6.2.2.3 and RFC 3987 §§5.3.2.1, 5.3.2.3–5.3.2.4, RFC 3987 §§3.1–3.2 URI/IRI representation transformation, RFC 5952 IPv6 text, RFC 9110 HTTP(S) port/path forms, RFC 6455 WS(S) port/resource-name forms, and RFC 8141 scheme/NID/percent-triplet normalization without NSS decoding or path reduction.
486
+ - Reference resolution and absolute/relative reference conversion do not apply to `urn` inputs; RFC 8141 URN resolution services and URN-equivalence APIs are not implemented.
439
487
 
440
488
  </details>
441
489
 
@@ -475,7 +523,7 @@ Run `gh workspace-data load` again to refresh materialized data after public-dat
475
523
 
476
524
  ### Tests
477
525
 
478
- The active suite contains 3,137 tests covering URI/IRI validation, parsing, generic normalization, bidirectional URI/IRI representation transformation, scheme-specific hosts, IPv4, IPv6, IPvFuture, ports, UUIDs, RFC 3986 resolution examples, empty components, absolute conversion, and relative-reference round trips, including 2,646 generated combinations of target/base paths, query-presence states, and target-fragment states across equivalent URI and IRI families.
526
+ The active suite contains 3,157 tests covering URI/IRI validation and parsing, RFC 8141 URN syntax and normalization, generic normalization, bidirectional URI/IRI representation transformation, scheme-specific hosts, IPv4, IPv6, IPvFuture, ports, UUIDs, RFC 3986 resolution examples, empty components, absolute conversion, and relative-reference round trips, including 2,646 generated combinations of target/base paths, query-presence states, and target-fragment states across equivalent URI and IRI families.
479
527
 
480
528
  <details>
481
529
  <summary><strong>Test details</strong></summary>
@@ -495,7 +543,7 @@ The suite uses the `node:test` module built into Node.js and requires no separat
495
543
 
496
544
  ### Benchmarks
497
545
 
498
- The materialized benchmark suite provides portable, version-aware measurements for every exported function plus isolated package loading, reporting initial-call behavior, warmed latency statistics, integer throughput, workload counts, representative inputs, and environment metadata.
546
+ The 26-scenario materialized benchmark suite provides portable, version-aware measurements for every exported function, isolated package loading, and RFC 8141 URN validation, parsing, and normalization. It reports initial-call behavior, warmed latency statistics, integer throughput, workload counts, representative inputs, and environment metadata.
499
547
 
500
548
  <details>
501
549
  <summary><strong>Benchmark details</strong></summary>
@@ -526,6 +574,7 @@ The generic coordinator delegates version-layer selection and ordered concern di
526
574
 
527
575
  - [RFC 3986 — Uniform Resource Identifier: Generic Syntax](https://www.rfc-editor.org/rfc/rfc3986)
528
576
  - [RFC 3987 — Internationalized Resource Identifiers](https://www.rfc-editor.org/rfc/rfc3987)
577
+ - [RFC 8141 — Uniform Resource Names](https://www.rfc-editor.org/rfc/rfc8141)
529
578
  - [RFC 9110 — HTTP Semantics](https://www.rfc-editor.org/rfc/rfc9110)
530
579
  - [RFC 6455 — The WebSocket Protocol](https://www.rfc-editor.org/rfc/rfc6455)
531
580
  - [RFC 8089 — The `file` URI Scheme](https://www.rfc-editor.org/rfc/rfc8089)