identifier-js 0.0.13 → 0.0.15

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/readme.md CHANGED
@@ -2,109 +2,478 @@
2
2
 
3
3
  title: Identifier JS
4
4
 
5
- description: A RFC3986 / RFC3987 compliant fast parser/validator/resolver/composer for NodeJS and browser.
5
+ description: An RFC 3986 and RFC 3987 parser, validator, and reference resolver for Node.js and browser bundles.
6
6
 
7
7
  ---
8
8
 
9
- ## Overview
9
+ # Identifier JS
10
10
 
11
- A fully RFC [3986](https://datatracker.ietf.org/doc/html/rfc3986.html)/[3897](https://datatracker.ietf.org/doc/html/rfc3987.html) compliant URI/IRI parser, validator, resolver and composer, along with other identifier utilities. This library implements the following [IANA registered](https://www.iana.org/assignments/uri-schemes/uri-schemes.xhtml) schemes:
12
- - `http` defined by [RFC9110, Section 4.2.1](https://datatracker.ietf.org/doc/html/rfc9110#section-4.2.1)
13
- - `https` defined by [RFC9110, Section 4.2.2](https://datatracker.ietf.org/doc/html/rfc9110#section-4.2.2)
14
- - `ws` defined by [RFC6455, Section 3](https://datatracker.ietf.org/doc/html/rfc6455#section-3)
15
- - `wss` defined by [RFC6455, Section 3](https://datatracker.ietf.org/doc/html/rfc6455#section-3)
16
- - `file` defined by [RFC8089, Section 2](https://datatracker.ietf.org/doc/html/rfc8089#section-2)
11
+ `identifier-js` is a fully RFC [3986](https://www.rfc-editor.org/rfc/rfc3986) and RFC [3987](https://www.rfc-editor.org/rfc/rfc3987) compliant URI/IRI parser, validator, resolver, and composer. It provides:
17
12
 
18
- Other schemes that are `IANA registered` schemes and compliant with the generic `URI` or `IRI` syntax are also supported. As for the identifiers that are not `IANA registered`, but compliant with the generic `URI` or `IRI` syntax, the preferred schemes are `uri` and respectively `iri`.
13
+ - URI and IRI validation;
14
+ - parsed identifier components;
15
+ - RFC 3986 reference resolution and dot-segment removal;
16
+ - relative-reference generation with round-trip guarantees for supported forms;
17
+ - UUID and UUIDv4 lexical validation;
18
+ - lazily compiled and cached regular expressions.
19
19
 
20
- ## Install
21
-
22
- ### Requirements
20
+ The package is synchronous, CommonJS, and supports Node.js 18 or newer. Browser use requires a bundler or runtime that supports CommonJS dependencies and Unicode regular expressions.
23
21
 
24
- - `regular expression with unicode support`, available in modern browsers and `NodeJS 18+`
22
+ ## Install
25
23
 
26
24
  ```bash title="console"
27
- npm i identifier-js
25
+ npm install identifier-js
28
26
  ```
29
27
 
30
28
  ## API
31
29
 
32
- Exports are documented below.
30
+ Every validator returns `true` or throws at the first detected violation. Parsing and reference operations also throw when their input does not satisfy the required grammar.
31
+
32
+ ### Validate URI syntax
33
+
34
+ Validate a complete URI, a URI reference, or an absolute URI without a fragment.
35
+
36
+ <details>
37
+ <summary><strong>API and examples</strong></summary>
38
+
39
+ ```ts
40
+ isUri(value: string): true
41
+ isUriReference(value: string): true
42
+ isAbsoluteUri(value: string): true
43
+ ```
44
+
45
+ ```js
46
+ const { isUri, isUriReference, isAbsoluteUri } = require('identifier-js');
47
+
48
+ console.log(isUri('https://example.com/path?query#fragment')); // true
49
+ console.log(isUriReference('../asset?version=2')); // true
50
+ console.log(isAbsoluteUri('https://example.com/path?query')); // true
51
+ ```
52
+
53
+ An `absolute-URI` is the fragment-free grammar defined by RFC 3986. Use `isUri` when a fragment is allowed.
54
+
55
+ </details>
56
+
57
+ ### Parse URI components
58
+
59
+ Parse URI syntax into scheme, authority, userinfo, host, port, path, query, and fragment components.
60
+
61
+ <details>
62
+ <summary><strong>API and examples</strong></summary>
63
+
64
+ ```ts
65
+ parseUri(value: string): IdentifierComponents
66
+ parseUriReference(value: string): RelativeIdentifierComponents
67
+ parseAbsoluteUri(value: string): AbsoluteIdentifierComponents
68
+ ```
69
+
70
+ ```js
71
+ const { parseUri } = require('identifier-js');
72
+
73
+ console.log(parseUri('https://user@example.com:8443/a?b#c'));
74
+ // {
75
+ // scheme: 'https',
76
+ // authority: 'user@example.com:8443',
77
+ // userinfo: 'user',
78
+ // host: 'example.com',
79
+ // port: '8443',
80
+ // path: '/a',
81
+ // query: 'b',
82
+ // fragment: 'c'
83
+ // }
84
+ ```
85
+
86
+ Absent optional components are returned as `undefined`. Present but empty query and fragment components are returned as empty strings.
87
+
88
+ </details>
89
+
90
+ ### Validate IRI syntax
91
+
92
+ Validate Unicode-capable identifiers using RFC 3987 grammar.
93
+
94
+ <details>
95
+ <summary><strong>API and examples</strong></summary>
96
+
97
+ ```ts
98
+ isIri(value: string): true
99
+ isIriReference(value: string): true
100
+ isAbsoluteIri(value: string): true
101
+ ```
102
+
103
+ ```js
104
+ const { isIri, isIriReference } = require('identifier-js');
105
+
106
+ console.log(isIri('https://例え.テスト/資料?項目=値#概要')); // true
107
+ console.log(isIriReference('../résumé')); // true
108
+ ```
109
+
110
+ IRI support permits RFC 3987 Unicode ranges in applicable components. Complete IDNA processing and conversion to a transport URI are separate responsibilities.
111
+
112
+ </details>
113
+
114
+ ### Parse IRI components
115
+
116
+ Parse an IRI while preserving its Unicode component values.
117
+
118
+ <details>
119
+ <summary><strong>API and examples</strong></summary>
33
120
 
34
- ### URI (RFC 3986)
121
+ ```ts
122
+ parseIri(value: string): IdentifierComponents
123
+ parseIriReference(value: string): RelativeIdentifierComponents
124
+ parseAbsoluteIri(value: string): AbsoluteIdentifierComponents
125
+ ```
126
+
127
+ ```js
128
+ const { parseIri } = require('identifier-js');
129
+
130
+ console.log(parseIri('https://usér@例え.テスト:8443/résumé?lang=fr#profil'));
131
+ // {
132
+ // scheme: 'https',
133
+ // authority: 'usér@例え.テスト:8443',
134
+ // userinfo: 'usér',
135
+ // host: '例え.テスト',
136
+ // port: '8443',
137
+ // path: '/résumé',
138
+ // query: 'lang=fr',
139
+ // fragment: 'profil'
140
+ // }
141
+ ```
142
+
143
+ </details>
144
+
145
+ ### Resolve a reference
146
+
147
+ Resolve a URI or IRI reference against an absolute base using RFC 3986 §5.
148
+
149
+ <details>
150
+ <summary><strong>API and examples</strong></summary>
151
+
152
+ ```ts
153
+ resolveReference(
154
+ reference: string,
155
+ base: string,
156
+ strict?: boolean,
157
+ returnParts?: boolean
158
+ ): string | Record<string, string | undefined>
159
+ ```
35
160
 
36
- #### Validation
161
+ ```js
162
+ const { resolveReference } = require('identifier-js');
37
163
 
38
- Validate a URI:
39
- - isUri: (value: string) => boolean
164
+ console.log(resolveReference('../images/logo.svg', 'https://example.com/docs/api/page'));
165
+ // https://example.com/docs/images/logo.svg
40
166
 
41
- Validate a URI reference (absolute or relative):
42
- - isUriReference: (value: string) => boolean
167
+ console.log(resolveReference('?page=2', 'https://example.com/items?page=1#current'));
168
+ // https://example.com/items?page=2
169
+ ```
170
+
171
+ `strict` defaults to `true`. In strict mode, a reference containing a scheme replaces the base identifier even when both schemes are equal. `returnParts` defaults to `false`; when enabled at runtime, the function returns the resolved component object.
172
+
173
+ Empty authorities, queries, and fragments are preserved during recomposition.
43
174
 
44
- Validate an absolute URI (must include scheme):
45
- - isAbsoluteUri: (value: string) => boolean
175
+ </details>
46
176
 
47
- #### Parsing
177
+ ### Produce absolute and relative forms
48
178
 
49
- Parse a URI into structured components:
50
- - parseUri: (value: string) => IdentifierComponents
179
+ Remove a base fragment or derive a relative reference that resolves back to a target.
51
180
 
52
- Parse a URI reference into structured components:
53
- - parseUriReference: (value: string) => RelativeIdentifierComponents
181
+ <details>
182
+ <summary><strong>API and examples</strong></summary>
183
+
184
+ ```ts
185
+ toAbsoluteReference(reference: string): string
186
+ toRelativeReference(target: string, base: string): string
187
+ ```
188
+
189
+ ```js
190
+ const { toAbsoluteReference, toRelativeReference } = require('identifier-js');
191
+
192
+ console.log(toAbsoluteReference('https://example.com/a/../b#section'));
193
+ // https://example.com/b
194
+
195
+ const target = 'https://example.com/docs/images/logo.svg';
196
+ const base = 'https://example.com/docs/api/page';
197
+ const relative = toRelativeReference(target, base);
198
+ console.log(relative); // ../images/logo.svg
199
+ ```
54
200
 
55
- Parse an absolute URI (must include scheme):
56
- - parseAbsoluteUri: (value: string) => AbsoluteIdentifierComponents
201
+ 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.
57
202
 
58
- ### IRI (RFC 3987)
203
+ </details>
59
204
 
60
- #### Validation
205
+ ### Normalization status
206
+
207
+ `normalizeReference` is reserved for future normalization policy and currently returns its input unchanged.
208
+
209
+ <details>
210
+ <summary><strong>Current behavior and research</strong></summary>
211
+
212
+ ```ts
213
+ normalizeReference(reference: string): string
214
+ ```
215
+
216
+ ```js
217
+ const { normalizeReference } = require('identifier-js');
218
+
219
+ console.log(normalizeReference('HTTP://Example.COM/a/../b'));
220
+ // HTTP://Example.COM/a/../b
221
+ ```
222
+
223
+ URI/IRI normalization has multiple standards-defined levels and scheme-specific tradeoffs. See [`normalization.md`](normalization.md) for implementation options, unsafe transformations, suggested profiles, and acceptance scenarios.
224
+
225
+ </details>
226
+
227
+ ### Validate UUID text
228
+
229
+ Validate the canonical UUID text shape or the stricter UUIDv4 version and variant fields.
230
+
231
+ <details>
232
+ <summary><strong>API and examples</strong></summary>
233
+
234
+ ```ts
235
+ isUUID(value: string): true
236
+ isUUIDv4(value: string): true
237
+ ```
238
+
239
+ ```js
240
+ const { isUUID, isUUIDv4 } = require('identifier-js');
241
+
242
+ console.log(isUUID('99c17cbb-656f-564a-940f-1a4568f03487')); // true
243
+ console.log(isUUIDv4('123e4567-e89b-42d3-9456-426614174000')); // true
244
+ ```
61
245
 
62
- Validate an IRI:
63
- - isIri: (value: string) => boolean
246
+ `isUUID` validates the `8-4-4-4-12` hexadecimal layout without restricting the version or variant fields. `isUUIDv4` requires version `4` and the RFC variant nibble `8`, `9`, `a`, or `b`.
64
247
 
65
- Validate an IRI reference (absolute or relative):
66
- - isIriReference: (value: string) => boolean
248
+ </details>
67
249
 
68
- Validate an absolute IRI (must include scheme):
69
- - isAbsoluteIri: (value: string) => boolean
250
+ ## Processing model
70
251
 
71
- #### Parsing
252
+ The parser builds its validation logic from declarative RFC grammar fragments:
72
253
 
73
- Parse an IRI into structured components:
74
- - parseIri: (value: string) => IdentifierComponents
254
+ 1. Select generic URI/IRI rules or the package's scheme-specific hostname policy.
255
+ 2. Recursively expand grammar references through `url-templates`.
256
+ 3. Add named capture groups when parsing is requested.
257
+ 4. Compile the complete expression with Unicode support.
258
+ 5. Cache the expression by operation, grammar rule, and scheme-policy class.
259
+ 6. Validate with `RegExp.test()` or parse with `RegExp.exec()`.
260
+ 7. Resolve references by component inheritance, path merging, dot-segment removal, and definedness-preserving recomposition.
75
261
 
76
- Parse an IRI reference into structured components:
77
- - parseIriReference: (value: string) => RelativeIdentifierComponents
262
+ <details>
263
+ <summary><strong>Lazy compilation and cache behavior</strong></summary>
78
264
 
79
- Parse an absolute IRI (must include scheme):
80
- - parseAbsoluteIri: (value: string) => AbsoluteIdentifierComponents
265
+ Parsing and validation use separate cached expressions because parsing requires named groups and validation does not. Generic and scheme-specific identifiers also use separate entries.
81
266
 
82
- ### Reference Utilities (URI/IRI)
267
+ The first call for an operation/rule/policy combination includes recursive grammar expansion and regular-expression compilation. Later calls reuse the cached expression and are considerably faster. No regular expressions are generated during package import.
83
268
 
84
- Normalize a URI or IRI reference:
85
- - normalizeReference: (reference: string) => string
269
+ </details>
86
270
 
87
- Resolve a reference against a base identifier:
88
- - resolveReference: (reference: string, base: string, strict?: boolean, returnParts?: boolean) => string
271
+ <details>
272
+ <summary><strong>Scheme-specific hostname policy</strong></summary>
89
273
 
90
- **Note:**
91
- - strict (default: `true`) enables strict resolution behavior.
92
- - returnParts (default: `false`) returns structured components instead of a string.
274
+ The following schemes trigger DNS-style ASCII or Unicode label rules instead of the fully generic `reg-name` grammar:
93
275
 
94
- Convert a reference into absolute form:
95
- - toAbsoluteReference: (reference: string) => string
276
+ - `http`
277
+ - `https`
278
+ - `ws`
279
+ - `wss`
280
+ - `file`
281
+
282
+ Matching is case-insensitive. Other valid schemes use generic RFC 3986/3987 registered-name syntax.
283
+
284
+ This policy validates label shape and selected Unicode character classes. It is not complete IDNA processing and does not replace normalization, contextual, bidi, registry, or Punycode validation.
285
+
286
+ </details>
287
+
288
+ ## Real-world use cases
289
+
290
+ ### Follow HTTP redirects and resource locations
291
+
292
+ HTTP `Location` values are URI references and can be relative to the original target URI.
293
+
294
+ <details>
295
+ <summary><strong>Example and context</strong></summary>
296
+
297
+ ```js
298
+ const { resolveReference } = require('identifier-js');
299
+
300
+ const requestUrl = 'https://example.com/account/profile';
301
+ const location = '../login?return=profile';
302
+ console.log(resolveReference(location, requestUrl));
303
+ // https://example.com/login?return=profile
304
+ ```
305
+
306
+ RFC 9110 uses URI references in `Location`, `Content-Location`, and `Referer`. Correct resolution requires component parsing, path merging, query inheritance rules, and dot-segment removal.
307
+
308
+ </details>
309
+
310
+ ### Keep document trees portable
311
+
312
+ Relative references let documents and assets move together without rewriting every internal link.
313
+
314
+ <details>
315
+ <summary><strong>Example and context</strong></summary>
316
+
317
+ ```js
318
+ const { resolveReference, toRelativeReference } = require('identifier-js');
319
+
320
+ const documentUrl = 'https://example.com/manual/chapters/intro.html';
321
+ const imageUrl = 'https://example.com/manual/images/diagram.svg';
322
+ const relative = toRelativeReference(imageUrl, documentUrl);
323
+ console.log(relative); // ../images/diagram.svg
324
+ console.log(resolveReference(relative, documentUrl)); // original image URL
325
+ ```
326
+
327
+ RFC 3986 identifies portable hypertext document trees as a central use for relative references.
328
+
329
+ </details>
330
+
331
+ ### Process internationalized identifiers
332
+
333
+ IRI parsing preserves native-script host, path, query, and fragment text for interfaces and internationalized content.
334
+
335
+ <details>
336
+ <summary><strong>Example and context</strong></summary>
337
+
338
+ ```js
339
+ const { parseIri } = require('identifier-js');
340
+
341
+ const parts = parseIri('https://例え.テスト/検索?q=資料#結果');
342
+ console.log(parts.host); // 例え.テスト
343
+ console.log(parts.path); // /検索
344
+ ```
345
+
346
+ RFC 3987 uses IRIs for internationalized identification while requiring mapping to URIs when a protocol accepts only URI syntax. Cache lookup, browser history, XML identifiers, and indexing are cited comparison contexts.
347
+
348
+ </details>
349
+
350
+ ### Validate and route protocol identifiers
351
+
352
+ Component parsing supports policy decisions without unsafe string splitting.
353
+
354
+ <details>
355
+ <summary><strong>Example and context</strong></summary>
356
+
357
+ ```js
358
+ const { parseUri } = require('identifier-js');
359
+
360
+ const parts = parseUri('wss://example.com:8443/events?channel=updates');
361
+ console.log(parts.scheme); // wss
362
+ console.log(parts.host); // example.com
363
+ console.log(parts.port); // 8443
364
+ console.log(parts.path); // /events
365
+ ```
366
+
367
+ Applications can inspect scheme, authority, path, and query before selecting a connector, enforcing an allowlist, constructing an HTTP request target, or routing to a service. Protocol-specific security and semantic validation remains the application's responsibility.
368
+
369
+ </details>
370
+
371
+ ### Validate externally supplied UUID text
372
+
373
+ Lexical UUID checks are useful at API, configuration, and storage boundaries.
374
+
375
+ <details>
376
+ <summary><strong>Example and context</strong></summary>
377
+
378
+ ```js
379
+ const { isUUIDv4 } = require('identifier-js');
380
+
381
+ try {
382
+ isUUIDv4('123e4567-e89b-42d3-9456-426614174000');
383
+ console.log('valid UUIDv4');
384
+ } catch (error) {
385
+ console.error(error.message);
386
+ }
387
+ ```
388
+
389
+ RFC 9562 lists database keys, filenames, system identifiers, and transaction identifiers among common UUID uses. UUID validation does not establish authorization, unpredictability, uniqueness, or safe use as a capability token.
390
+
391
+ </details>
392
+
393
+ ## Intentional behavior and limitations
394
+
395
+ <details>
396
+ <summary><strong>Validation and parsing boundaries</strong></summary>
397
+
398
+ - Validators return `true` or throw; they do not return `false`.
399
+ - URI functions reject non-ASCII characters where RFC 3986 permits only URI syntax. Use the IRI functions for RFC 3987 Unicode ranges.
400
+ - `absolute-URI` and `absolute-IRI` exclude fragments by definition. The complete `URI` and `IRI` functions permit fragments.
401
+ - Scheme-specific processing currently specializes hostname syntax; it does not implement every protocol rule for HTTP, WebSocket, or `file` identifiers.
402
+ - Scheme-specific Unicode labels are not complete IDNA validation.
403
+ - Ports are restricted to an empty value or the numeric range 0–65535. Generic RFC 3986 syntax itself permits any sequence of digits.
404
+ - Generic registered names may contain syntax that DNS-style hostnames reject.
405
+ - Parsing separates components before any application-level percent decoding.
406
+ - The library does not perform network, DNS, filesystem, registry, or authorization checks.
407
+
408
+ </details>
409
+
410
+ <details>
411
+ <summary><strong>Resolution and conversion boundaries</strong></summary>
412
+
413
+ - Resolution uses IRI grammar, so Unicode references and bases are accepted.
414
+ - Empty authorities, queries, and fragments remain distinct from absent components.
415
+ - `strict = false` enables RFC 3986 backward-compatible handling when a reference repeats the base scheme.
416
+ - `toAbsoluteReference` requires an identifier containing a scheme and removes its fragment through empty-reference resolution.
417
+ - `toRelativeReference` compares scheme and authority text exactly; it does not normalize them first.
418
+ - `toRelativeReference` may return an absolute target when a rootless relative path cannot preserve identity.
419
+ - `normalizeReference` is intentionally an identity function until a normalization contract is selected.
420
+
421
+ </details>
422
+
423
+ <details>
424
+ <summary><strong>UUID boundaries</strong></summary>
425
+
426
+ - `isUUID` validates canonical hexadecimal layout only; it does not enforce a known version or the RFC variant.
427
+ - `isUUIDv4` validates the version and variant fields but does not assess random-number quality.
428
+ - A syntactically valid UUID is not proof of uniqueness, integrity, authenticity, or authorization.
429
+
430
+ </details>
431
+
432
+ ## Tests
433
+
434
+ The current suite contains 418 active tests covering URI/IRI validation and parsing, scheme-specific hosts, IPv4, IPv6, ports, UUIDs, RFC 3986 resolution examples, empty components, absolute conversion, and relative-reference round trips.
435
+
436
+ The reference-conversion changes were additionally checked against 2,646 combinations of paths, absent/empty/non-empty queries, and absent/empty/non-empty fragments.
437
+
438
+ <details>
439
+ <summary><strong>Tests</strong></summary>
440
+
441
+ The test suite and supporting ABNF source documents are maintained separately as public workspace data, so they are not included in the package or canonical repository. Users and contributors who need them can materialize them into a cloned repository with [gh-workspace-data](https://github.com/SorinGFS/gh-workspace-data).
442
+
443
+ Install the GitHub CLI extension once:
444
+
445
+ ```sh
446
+ gh extension install SorinGFS/gh-workspace-data
447
+ ```
448
+
449
+ Then run the workspace-data commands from the repository:
450
+
451
+ ```sh
452
+ gh workspace-data init
453
+ gh workspace-data load
454
+ ```
455
+
456
+ The tests are materialized as ordinary local files under `#/public/tests/`, and the supporting documents are available under `#/public/docs/`. Both remain excluded from the canonical Git repository.
457
+
458
+ Run the materialized suite with:
459
+
460
+ ```sh
461
+ npm test
462
+ ```
96
463
 
97
- Compute a relative reference from base to target:
98
- - toRelativeReference: (target: string, base: string) => string
464
+ When Vitest is not yet declared, the first run installs it and replaces the one-time bootstrap command with the materialized test runner. Later runs invoke that runner directly.
99
465
 
100
- ### UUID
466
+ </details>
101
467
 
102
- Validate a UUID (any version):
103
- - isUUID: (value: string) => boolean
468
+ ## Authoritative references
104
469
 
105
- Validate a UUID version 4:
106
- - isUUIDv4: (value: string) => boolean
470
+ - [RFC 3986 Uniform Resource Identifier: Generic Syntax](https://www.rfc-editor.org/rfc/rfc3986)
471
+ - [RFC 3987 Internationalized Resource Identifiers](https://www.rfc-editor.org/rfc/rfc3987)
472
+ - [RFC 9110 — HTTP Semantics](https://www.rfc-editor.org/rfc/rfc9110)
473
+ - [RFC 6455 — The WebSocket Protocol](https://www.rfc-editor.org/rfc/rfc6455)
474
+ - [RFC 8089 — The `file` URI Scheme](https://www.rfc-editor.org/rfc/rfc8089)
475
+ - [RFC 9562 — Universally Unique IDentifiers](https://www.rfc-editor.org/rfc/rfc9562)
107
476
 
108
- ## Testing
477
+ ## Disclaimer
109
478
 
110
- - npm test
479
+ Validation establishes conformance with this implementation's syntax and policy. It does not establish that an identifier is registered, reachable, trustworthy, safe to dereference, or appropriate for a particular protocol operation.