identifier-js 0.0.1 → 0.0.3

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 ADDED
@@ -0,0 +1,73 @@
1
+ /** @throws {Error} If the UUID is invalid. */
2
+ export const isUUID: (string: string) => true;
3
+ /** @throws {Error} If the UUID-v4 is invalid. */
4
+ export const isUUIDv4: (string: string) => true;
5
+
6
+ /** @throws {Error} If the URI is invalid. */
7
+ export const isUri: (uri: string) => true;
8
+ /** @throws {Error} If the URI-reference is invalid. */
9
+ export const isUriReference: (uriReference: string) => true;
10
+ /** @throws {Error} If the absolute-URI is invalid. */
11
+ export const isAbsoluteUri: (uri: string) => true;
12
+
13
+ /** @throws {Error} If the URI is invalid. */
14
+ export const parseUri: (uri: string) => IdentifierComponents;
15
+ /** @throws {Error} If the URI-reference is invalid. */
16
+ export const parseUriReference: (uriReference: string) => RelativeIdentifierComponents;
17
+ /** @throws {Error} If the absolute-URI is invalid. */
18
+ export const parseAbsoluteUri: (uri: string) => AbsoluteIdentifierComponents;
19
+
20
+ /** @throws {Error} If the IRI is invalid. */
21
+ export const isIri: (iri: string) => true;
22
+ /** @throws {Error} If the IRI-reference is invalid. */
23
+ export const isIriReference: (iriReference: string) => true;
24
+ /** @throws {Error} If the absolute-IRI is invalid. */
25
+ export const isAbsoluteIri: (iri: string) => true;
26
+
27
+ /** @throws {Error} If the IRI is invalid. */
28
+ export const parseIri: (iri: string) => IdentifierComponents;
29
+ /** @throws {Error} If the IRI-reference is invalid. */
30
+ export const parseIriReference: (iriReference: string) => RelativeIdentifierComponents;
31
+ /** @throws {Error} If the absolute-IRI is invalid. */
32
+ export const parseAbsoluteIri: (iri: string) => AbsoluteIdentifierComponents;
33
+
34
+ /** @throws {Error} If the reference is invalid. */
35
+ export const normalizeReference: (reference: string) => string;
36
+ /** @throws {Error} If the base or the reference is invalid. */
37
+ export const resolveReference: (reference: string, base: string, strict?: boolean, returnParts?: boolean) => string;
38
+ /** @throws {Error} If the reference is invalid. */
39
+ export const toAbsoluteReference: (reference: string) => string;
40
+ /** @throws {Error} If the base or the reference is invalid. */
41
+ export const toRelativeReference: (target: string, base: string) => string;
42
+
43
+ type IdentifierComponents = {
44
+ scheme: string;
45
+ authority: string;
46
+ userinfo?: string;
47
+ host: string;
48
+ port?: string;
49
+ path: string;
50
+ query?: string;
51
+ fragment?: string;
52
+ };
53
+
54
+ type RelativeIdentifierComponents = {
55
+ scheme?: string;
56
+ authority?: string;
57
+ userinfo?: string;
58
+ host?: string;
59
+ port?: string;
60
+ path: string;
61
+ query?: string;
62
+ fragment?: string;
63
+ };
64
+
65
+ type AbsoluteIdentifierComponents = {
66
+ scheme: string;
67
+ authority: string;
68
+ userinfo?: string;
69
+ host: string;
70
+ port?: string;
71
+ path: string;
72
+ query?: string;
73
+ };
package/index.js ADDED
@@ -0,0 +1,273 @@
1
+ 'use strict';
2
+ // a valid URI is always a valid IRI
3
+ const { recursiveCompile } = require('url-templates');
4
+ const patterns = new Map();
5
+ // RFC3986/RFC3987 common rules
6
+ const commonRules = {
7
+ scheme: '[a-zA-Z][a-zA-Z0-9+.-]*',
8
+ port: '[0-9]*',
9
+ IP_literal: '\\[(?:{IPv6address}|{IPvFuture})\\]',
10
+ IPv6address: '(?:(?:{h16}:){6}{ls32}|::(?:{h16}:){5}{ls32}|(?:(?:{h16})?)::(?:{h16}:){4}{ls32}|(?:(?:{h16}:)?{h16})?::(?:{h16}:){3}{ls32}|(?:(?:{h16}:){0,2}{h16})?::(?:{h16}:){2}{ls32}|(?:(?:{h16}:){0,3}{h16})?::(?:{h16}:){1}{ls32}|(?:(?:{h16}:){0,4}{h16})?::{ls32}|(?:(?:{h16}:){0,5}{h16})?::{h16}|(?:(?:{h16}:){0,6}{h16})?::)',
11
+ ls32: '(?:{h16}:{h16}|{IPv4address})',
12
+ h16: '{hexdig}{1,4}',
13
+ IPv4address: '(?:{dec_octet}\\.){3}{dec_octet}',
14
+ dec_octet: '(?:25[0-5]|2[0-4][0-9]|[01]?[0-9][0-9]?)',
15
+ IPvFuture: 'v{hexdig}+\\.(?:{unreserved}|{sub_delims}|:)+',
16
+ unreserved: '[a-zA-Z0-9_.~-]',
17
+ reserved: '(?:{gen_delims}|{sub_delims})',
18
+ pct_encoded: '%{hexdig}{2}',
19
+ gen_delims: '[:/?#[\\]@]',
20
+ sub_delims: "[!&'()*+,;=$]",
21
+ hexdig: '[0-9A-Fa-f]',
22
+ UUID: '[0-9a-fA-F]{8}-[0-9a-fA-F]{4}-[0-9a-fA-F]{4}-[0-9a-fA-F]{4}-[0-9a-fA-F]{12}',
23
+ UUID_v4: '[0-9a-fA-F]{8}-[0-9a-fA-F]{4}-4[0-9a-fA-F]{3}-[89abAB][0-9a-fA-F]{3}-[0-9a-fA-F]{12}',
24
+ };
25
+ // RFC3986 rules
26
+ const uriRules = {
27
+ URI_reference: '(?:{URI}|{relative_ref})',
28
+ URI: '{absolute_URI}(?:#{fragment})?',
29
+ absolute_URI: '{scheme}:{hier_part}(?:\\?{query})?',
30
+ relative_ref: '{relative_part}(?:\\?{query})?(?:#{fragment})?',
31
+ hier_part: '(?:\/\/{authority}{path_abempty}|{path_absolute}|{path_rootless}|{path_empty})',
32
+ relative_part: '(?:\/\/{authority}{path_abempty}|{path_absolute}|{path_noscheme}|{path_empty})',
33
+ authority: '(?:{userinfo}@)?{host}(?::{port})?',
34
+ host: '(?:{IP_literal}|{IPv4address}|{reg_name})',
35
+ userinfo: '(?:{unreserved}|{pct_encoded}|{sub_delims}|:)*',
36
+ reg_name: '(?:{unreserved}|{pct_encoded}|{sub_delims})*',
37
+ path: '(?:{path_abempty}|{path_absolute}|{path_noscheme}|{path_rootless}|{path_empty})',
38
+ path_abempty: '(?:\/{segment})*',
39
+ path_absolute: '\/(?:{segment_nz}(?:\/{segment})*)?',
40
+ path_noscheme: '{segment_nz_nc}(?:\/{segment})*',
41
+ path_rootless: '{segment_nz}(?:\/{segment})*',
42
+ path_empty: '',
43
+ segment: '{pchar}*',
44
+ segment_nz: '{pchar}+',
45
+ segment_nz_nc: '(?:{unreserved}|{pct_encoded}|{sub_delims}|@)+',
46
+ query: '(?:{pchar}|\/|\\?)*',
47
+ fragment: '(?:{pchar}|\/|\\?)*',
48
+ pchar: '(?:{unreserved}|{pct_encoded}|{sub_delims}|:|@)',
49
+ };
50
+ // RFC3987 rules
51
+ const iriRules = {
52
+ IRI_reference: '(?:{IRI}|{irelative_ref})',
53
+ IRI: '{absolute_IRI}(?:#{ifragment})?',
54
+ absolute_IRI: '{scheme}:{ihier_part}(?:\\?{iquery})?',
55
+ irelative_ref: '(?:{irelative_part}(?:\\?{iquery})?(?:#{ifragment})?)',
56
+ ihier_part: '(?:\/\/{iauthority}{ipath_abempty}|{ipath_absolute}|{ipath_rootless}|{ipath_empty})',
57
+ irelative_part: '(?:\/\/{iauthority}{ipath_abempty}|{ipath_absolute}|{ipath_noscheme}|{ipath_empty})',
58
+ iauthority: '(?:{iuserinfo}@)?{ihost}(?::{port})?',
59
+ iuserinfo: '(?:{iunreserved}|{pct_encoded}|{sub_delims}|:)*',
60
+ ihost: '(?:{IP_literal}|{IPv4address}|{ireg_name})',
61
+ ireg_name: '(?:{iunreserved}|{pct_encoded}|{sub_delims})*',
62
+ ipath: '(?:{ipath_abempty}|{ipath_absolute}|{ipath_noscheme}|{ipath_rootless}|{ipath_empty})',
63
+ ipath_empty: '',
64
+ ipath_rootless: '{isegment_nz}(?:\/{isegment})*',
65
+ ipath_noscheme: '{isegment_nz_nc}(?:\/{isegment})*',
66
+ ipath_absolute: '\/(?:{isegment_nz}(?:\/{isegment})*)?',
67
+ ipath_abempty: '(?:\/{isegment})*',
68
+ isegment_nz_nc: '(?:{iunreserved}|{pct_encoded}|{sub_delims}|@)+',
69
+ isegment_nz: '{ipchar}+',
70
+ isegment: '{ipchar}*',
71
+ iquery: '(?:{ipchar}|{iprivate}|\/|\\?)*',
72
+ ifragment: '(?:{ipchar}|\/|\\?)*',
73
+ ipchar: '(?:{iunreserved}|{pct_encoded}|{sub_delims}|:|@)',
74
+ iunreserved: '(?:[a-zA-Z0-9._~-]|{ucschar})',
75
+ iprivate: '[\\uE000-\\uF8FF\\u{F0000}-\\u{FFFFD}\\u{100000}-\\u{10FFFD}]',
76
+ 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}]',
77
+ };
78
+ // pattern RFC group names
79
+ const groupNames = {
80
+ scheme: 'scheme',
81
+ port: 'port',
82
+ authority: 'authority',
83
+ host: 'host',
84
+ userinfo: 'userinfo',
85
+ query: 'query',
86
+ fragment: 'fragment',
87
+ iauthority: 'authority',
88
+ ihost: 'host',
89
+ iuserinfo: 'userinfo',
90
+ iquery: 'query',
91
+ ifragment: 'fragment',
92
+ path_abempty: 'path',
93
+ path_absolute: 'path',
94
+ path_noscheme: 'path',
95
+ path_rootless: 'path',
96
+ path_empty: 'path',
97
+ ipath_abempty: 'path',
98
+ ipath_absolute: 'path',
99
+ ipath_noscheme: 'path',
100
+ ipath_rootless: 'path',
101
+ ipath_empty: 'path',
102
+ };
103
+ // all rules into one map
104
+ const rules = Object.assign({}, commonRules, uriRules, iriRules);
105
+ const addNames = (key) => (groupNames[key] ? `(?<${groupNames[key]}>${rules[key]})` : rules[key]);
106
+ // parse (slower, it uses regex.exec and includes named capture groups)
107
+ const parse = (string, rule) => {
108
+ if (typeof string !== 'string') throw new TypeError(`Invalid ${rule.replace('_', '-')} type: must be a string.`);
109
+ if (!patterns.has('_' + rule)) patterns.set('_' + rule, new RegExp(`^${recursiveCompile(rules, rule, addNames)}$`, 'u'));
110
+ const match = patterns.get('_' + rule).exec(string);
111
+ if (match) return match.groups;
112
+ throw new SyntaxError(`Invalid ${rule.replace('_', '-')}: ${string}`);
113
+ };
114
+ // validate (faster, it uses regex.test and does not include named capture groups)
115
+ const validate = (string, rule) => {
116
+ if (typeof string !== 'string') throw new TypeError(`Invalid ${rule.replace('_', '-')} type: must be a string.`);
117
+ if (!patterns.has(rule)) patterns.set(rule, new RegExp(`^${recursiveCompile(rules, rule)}$`, 'u'));
118
+ if (patterns.get(rule).test(string)) return true;
119
+ throw new SyntaxError(`Invalid ${rule.replace('_', '-')}: ${string}`);
120
+ };
121
+ // compose as per RFC 3986 Section 5.3 (component recomposition)
122
+ function compose(parts = {}) {
123
+ let result = '';
124
+ if (parts.scheme) result += parts.scheme + ':';
125
+ if (parts.authority) result += '//' + parts.authority;
126
+ result += parts.path || '';
127
+ if (parts.query) result += '?' + parts.query;
128
+ if (parts.fragment) result += '#' + parts.fragment;
129
+ return result;
130
+ }
131
+ // remove dot segments algorithm per RFC 3986 Section 5.2.4 (loop and replace)
132
+ function removeDotSegments(path) {
133
+ const output = [];
134
+ let input = path;
135
+ while (input.length > 0) {
136
+ if (input.startsWith('../')) input = input.slice(3);
137
+ else if (input.startsWith('./')) input = input.slice(2);
138
+ else if (input.startsWith('/./')) input = input.replace('/./', '/');
139
+ else if (input === '/.') input = '/';
140
+ else if (input.startsWith('/../')) {
141
+ input = input.replace('/../', '/');
142
+ if (output.length) output.pop();
143
+ } else if (input === '/..') {
144
+ input = '/';
145
+ if (output.length) output.pop();
146
+ } else if (input === '.' || input === '..') {
147
+ input = '';
148
+ } else {
149
+ // move first segment from input to output
150
+ let seg = '';
151
+ if (input[0] === '/') {
152
+ // keep leading '/'
153
+ const idx = input.indexOf('/', 1);
154
+ if (idx === -1) {
155
+ seg = input;
156
+ input = '';
157
+ } else {
158
+ seg = input.slice(0, idx);
159
+ input = input.slice(idx);
160
+ }
161
+ } else {
162
+ const idx = input.indexOf('/');
163
+ if (idx === -1) {
164
+ seg = input;
165
+ input = '';
166
+ } else {
167
+ seg = input.slice(0, idx);
168
+ input = input.slice(idx);
169
+ }
170
+ }
171
+ output.push(seg);
172
+ }
173
+ }
174
+ return output.join('');
175
+ }
176
+ // resolve as per RFC https://datatracker.ietf.org/doc/html/rfc3986#section-5.2
177
+ function resolveReference(reference, base, strict = true, parts = false) {
178
+ let B;
179
+ if (typeof base === 'string') {
180
+ B = parse(base, 'IRI');
181
+ } else {
182
+ B = Object.assign({}, base);
183
+ }
184
+ if (!B.scheme) throw new Error('Expected an URI/IRI (with scheme) as base.');
185
+
186
+ let R;
187
+ if (typeof reference === 'string') {
188
+ R = parse(reference, 'IRI_reference');
189
+ } else {
190
+ R = Object.assign({}, reference);
191
+ }
192
+
193
+ let T;
194
+ if (R.scheme && (strict || R.scheme !== B.scheme)) {
195
+ T = R;
196
+ } else {
197
+ T = {};
198
+ T.scheme = B.scheme;
199
+ if (R.authority !== undefined && R.authority !== null) {
200
+ T.authority = R.authority;
201
+ T.path = R.path;
202
+ T.query = R.query;
203
+ } else {
204
+ T.authority = B.authority;
205
+ if (R.path && R.path.length > 0) {
206
+ if (R.path.startsWith('/')) {
207
+ T.path = R.path;
208
+ } else if (B.authority !== undefined && B.authority !== null && (!B.path || B.path.length === 0)) {
209
+ T.path = '/' + R.path;
210
+ } else {
211
+ // merge base path and ref path
212
+ const idx = B.path ? B.path.lastIndexOf('/') : -1;
213
+ const prefix = idx !== -1 ? B.path.slice(0, idx + 1) : '';
214
+ T.path = prefix + R.path;
215
+ }
216
+ T.query = R.query;
217
+ } else {
218
+ T.path = B.path;
219
+ if (R.query !== undefined && R.query !== null) T.query = R.query;
220
+ else T.query = B.query;
221
+ }
222
+ }
223
+ T.fragment = R.fragment;
224
+ }
225
+ T.path = removeDotSegments(T.path || '');
226
+ if (parts) return T;
227
+ return compose(T);
228
+ }
229
+ // reverse logic for resolve
230
+ const toRelativeReference = (target, base) => {
231
+ const B = parse(base, 'absolute_IRI');
232
+ const T = parse(target, 'IRI');
233
+ if (T.scheme !== B.scheme || T.authority !== B.authority) return target;
234
+ let result;
235
+ if (B.path === T.path) {
236
+ result = '';
237
+ } else {
238
+ const baseSegments = B.path.split('/');
239
+ const targetSegments = T.path.split('/');
240
+ let position = 0;
241
+ while (baseSegments[position] === targetSegments[position] && position < baseSegments.length - 1 && position < targetSegments.length - 1) {
242
+ position++;
243
+ }
244
+ const segments = [];
245
+ for (let index = position + 1; index < baseSegments.length; index++) segments.push('..');
246
+ for (let index = position; index < targetSegments.length; index++) segments.push(targetSegments[index]);
247
+ result = segments.join('/');
248
+ }
249
+ if (T.query !== undefined) result += `?${T.query}`;
250
+ if (T.fragment !== undefined) result += `#${T.fragment}`;
251
+ return result;
252
+ };
253
+ // export
254
+ module.exports = {
255
+ isUUID: (string) => validate(string, 'UUID'),
256
+ isUUIDv4: (string) => validate(string, 'UUID_v4'),
257
+ isUri: (string) => validate(string, 'URI'),
258
+ isUriReference: (string) => validate(string, 'URI_reference'),
259
+ isAbsoluteUri: (string) => validate(string, 'absolute_URI'),
260
+ parseUri: (string) => parse(string, 'URI'),
261
+ parseUriReference: (string) => parse(string, 'URI_reference'),
262
+ parseAbsoluteUri: (string) => parse(string, 'absolute_URI'),
263
+ isIri: (string) => validate(string, 'IRI'),
264
+ isIriReference: (string) => validate(string, 'IRI_reference'),
265
+ isAbsoluteIri: (string) => validate(string, 'absolute_IRI'),
266
+ parseIri: (string) => parse(string, 'IRI'),
267
+ parseIriReference: (string) => parse(string, 'IRI_reference'),
268
+ parseAbsoluteIri: (string) => parse(string, 'absolute_IRI'),
269
+ resolveReference, // not yet decided if the return should be normalized or not, and in what extent
270
+ normalizeReference: (string) => string, // not done yet
271
+ toAbsoluteReference: (string) => resolveReference('', string),
272
+ toRelativeReference,
273
+ };
package/package.json CHANGED
@@ -1,35 +1,43 @@
1
- {
2
- "name": "identifier-js",
3
- "version": "0.0.1",
4
- "description": "A RFC3986 / RFC3987 compliant fast parser/validator/resolver/composer for NodeJS and browser.",
5
- "keywords": [
6
- "IRI",
7
- "URI",
8
- "IRI-reference",
9
- "URI-reference",
10
- "ipv4",
11
- "ipv6",
12
- "uuid",
13
- "parser",
14
- "validator",
15
- "RFC",
16
- "3987",
17
- "RFC",
18
- "3986"
19
- ],
20
- "homepage": "https://github.com/SorinGFS/identifier-js#readme",
21
- "bugs": {
22
- "url": "https://github.com/SorinGFS/identifier-js/issues"
23
- },
24
- "repository": {
25
- "type": "git",
26
- "url": "git+https://github.com/SorinGFS/identifier-js.git"
27
- },
28
- "license": "MIT",
29
- "author": "SorinGFS",
30
- "type": "commonjs",
31
- "main": "index.js",
32
- "scripts": {
33
- "test": "echo \"Error: no test specified\" && exit 1"
34
- }
35
- }
1
+ {
2
+ "name": "identifier-js",
3
+ "version": "0.0.3",
4
+ "description": "A RFC3986 / RFC3987 compliant fast parser/validator/resolver/composer for NodeJS and browser.",
5
+ "keywords": [
6
+ "IRI",
7
+ "URI",
8
+ "IRI-reference",
9
+ "URI-reference",
10
+ "ipv4",
11
+ "ipv6",
12
+ "uuid",
13
+ "parser",
14
+ "validator",
15
+ "RFC",
16
+ "3987",
17
+ "RFC",
18
+ "3986"
19
+ ],
20
+ "homepage": "https://github.com/SorinGFS/identifier-js#readme",
21
+ "bugs": {
22
+ "url": "https://github.com/SorinGFS/identifier-js/issues"
23
+ },
24
+ "repository": {
25
+ "type": "git",
26
+ "url": "git+https://github.com/SorinGFS/identifier-js.git"
27
+ },
28
+ "license": "MIT",
29
+ "author": "SorinGFS",
30
+ "type": "commonjs",
31
+ "main": "index.js",
32
+ "scripts": {
33
+ "update-deps": "npx npm-check-updates -u && npm install",
34
+ "test": "node tests/vitest-setup && npm test"
35
+ },
36
+ "dependencies": {
37
+ "url-templates": "^1.0.4"
38
+ },
39
+ "engines": {
40
+ "node": ">=18.0.0"
41
+ },
42
+ "engineStrict": true
43
+ }
package/readme.md CHANGED
@@ -12,6 +12,92 @@ A fully RFC [3986](https://datatracker.ietf.org/doc/html/rfc3986.html)/[3897](ht
12
12
 
13
13
  ## Install
14
14
 
15
+ ### Requirements
16
+
17
+ - `regular expression with unicode support`, available in modern browsers and `NodeJS 18+`
18
+
15
19
  ```bash title="console"
16
20
  npm i identifier-js
17
21
  ```
22
+
23
+ ## API
24
+
25
+ Exports are documented below.
26
+
27
+ ### URI (RFC 3986)
28
+
29
+ #### Validation
30
+
31
+ Validate a URI:
32
+ - isUri: (value: string) => boolean
33
+
34
+ Validate a URI reference (absolute or relative):
35
+ - isUriReference: (value: string) => boolean
36
+
37
+ Validate an absolute URI (must include scheme):
38
+ - isAbsoluteUri: (value: string) => boolean
39
+
40
+ #### Parsing
41
+
42
+ Parse a URI into structured components:
43
+ - parseUri: (value: string) => IdentifierComponents
44
+
45
+ Parse a URI reference into structured components:
46
+ - parseUriReference: (value: string) => RelativeIdentifierComponents
47
+
48
+ Parse an absolute URI (must include scheme):
49
+ - parseAbsoluteUri: (value: string) => AbsoluteIdentifierComponents
50
+
51
+ ### IRI (RFC 3987)
52
+
53
+ #### Validation
54
+
55
+ Validate an IRI:
56
+ - isIri: (value: string) => boolean
57
+
58
+ Validate an IRI reference (absolute or relative):
59
+ - isIriReference: (value: string) => boolean
60
+
61
+ Validate an absolute IRI (must include scheme):
62
+ - isAbsoluteIri: (value: string) => boolean
63
+
64
+ #### Parsing
65
+
66
+ Parse an IRI into structured components:
67
+ - parseIri: (value: string) => IdentifierComponents
68
+
69
+ Parse an IRI reference into structured components:
70
+ - parseIriReference: (value: string) => RelativeIdentifierComponents
71
+
72
+ Parse an absolute IRI (must include scheme):
73
+ - parseAbsoluteIri: (value: string) => AbsoluteIdentifierComponents
74
+
75
+ ### Reference Utilities (URI/IRI)
76
+
77
+ Normalize a URI or IRI reference:
78
+ - normalizeReference: (reference: string) => string
79
+
80
+ Resolve a reference against a base identifier:
81
+ - resolveReference: (reference: string, base: string, strict?: boolean, returnParts?: boolean) => string
82
+
83
+ **Note:**
84
+ - strict enables strict resolution behavior.
85
+ - returnParts returns structured components instead of a string.
86
+
87
+ Convert a reference into absolute form:
88
+ - toAbsoluteReference: (reference: string) => string
89
+
90
+ Compute a relative reference from base to target:
91
+ - toRelativeReference: (target: string, base: string) => string
92
+
93
+ ### UUID
94
+
95
+ Validate a UUID (any version):
96
+ - isUUID: (value: string) => boolean
97
+
98
+ Validate a UUID version 4:
99
+ - isUUIDv4: (value: string) => boolean
100
+
101
+ ## Testing
102
+
103
+ - npm test