@entelekheia/ref-id 0.1.0
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 +38 -0
- package/dist/build.d.ts +3 -0
- package/dist/build.js +138 -0
- package/dist/build.js.map +1 -0
- package/dist/digest.d.ts +2 -0
- package/dist/digest.js +26 -0
- package/dist/digest.js.map +1 -0
- package/dist/encoding.d.ts +18 -0
- package/dist/encoding.js +47 -0
- package/dist/encoding.js.map +1 -0
- package/dist/envelope.d.ts +3 -0
- package/dist/envelope.js +67 -0
- package/dist/envelope.js.map +1 -0
- package/dist/errors.d.ts +17 -0
- package/dist/errors.js +34 -0
- package/dist/errors.js.map +1 -0
- package/dist/grammar.d.ts +28 -0
- package/dist/grammar.js +90 -0
- package/dist/grammar.js.map +1 -0
- package/dist/index.d.ts +8 -0
- package/dist/index.js +9 -0
- package/dist/index.js.map +1 -0
- package/dist/parse.d.ts +3 -0
- package/dist/parse.js +231 -0
- package/dist/parse.js.map +1 -0
- package/dist/serialise.d.ts +3 -0
- package/dist/serialise.js +36 -0
- package/dist/serialise.js.map +1 -0
- package/dist/spec.d.ts +142 -0
- package/dist/spec.js +116 -0
- package/dist/spec.js.map +1 -0
- package/dist/types.d.ts +48 -0
- package/dist/types.js +3 -0
- package/dist/types.js.map +1 -0
- package/dist/validators.d.ts +19 -0
- package/dist/validators.js +87 -0
- package/dist/validators.js.map +1 -0
- package/package.json +45 -0
- package/spec/ref-id.json +1488 -0
- package/spec/ref-id.json.sha256 +1 -0
package/dist/parse.js
ADDED
|
@@ -0,0 +1,231 @@
|
|
|
1
|
+
// SPDX-License-Identifier: Apache-2.0
|
|
2
|
+
//
|
|
3
|
+
// Decomposes a `ref:` identifier string against the loaded spec. Order of validation and
|
|
4
|
+
// delegation to owning validators is the behaviour ADR-0001 keeps in code; every table, pattern
|
|
5
|
+
// and key it consults comes from the spec object, never restated here.
|
|
6
|
+
//
|
|
7
|
+
// Order, and the precedence it produces: grammar → version → state and fragment decomposition,
|
|
8
|
+
// including the repeated-key policy → (unsupported version stops here, decomposed and unvalidated)
|
|
9
|
+
// → the two sides must not trade keys → qualifier values against their forms → refinement values
|
|
10
|
+
// against their patterns and range constraints → dispatch: unknown type is `uncovered`, else the
|
|
11
|
+
// locator goes to its validator. So a validator failure (`malformed` with the part) outranks
|
|
12
|
+
// `uncovered`, which outranks `ok`.
|
|
13
|
+
import { decodeReserved, tableFor } from "./encoding.js";
|
|
14
|
+
import { fragmentPairGrammar, pattern, schemePrefix, statePairGrammar, topLevelGrammar } from "./grammar.js";
|
|
15
|
+
import { loadSpec, part, status } from "./spec.js";
|
|
16
|
+
import { assertImplemented, delegatedString, rangeHolds, validateLocator } from "./validators.js";
|
|
17
|
+
function malformed(spec, input, failedPart, base) {
|
|
18
|
+
const result = {
|
|
19
|
+
input,
|
|
20
|
+
status: status(spec, "malformed"),
|
|
21
|
+
version: base.version ?? spec.version.default,
|
|
22
|
+
explicitVersion: base.explicitVersion ?? false,
|
|
23
|
+
type: base.type ?? "",
|
|
24
|
+
locator: base.locator ?? "",
|
|
25
|
+
qualifiers: base.qualifiers ?? [],
|
|
26
|
+
fragment: base.fragment ?? null,
|
|
27
|
+
part: part(spec, failedPart),
|
|
28
|
+
};
|
|
29
|
+
if (base.versionText !== undefined) {
|
|
30
|
+
result.versionText = base.versionText;
|
|
31
|
+
}
|
|
32
|
+
return result;
|
|
33
|
+
}
|
|
34
|
+
/** Splits `key=value` segments with the given pair grammar; a repeated key is malformed at that key. */
|
|
35
|
+
function decomposePairs(spec, grammar, segments, failedPart) {
|
|
36
|
+
const pairs = [];
|
|
37
|
+
const seen = new Set();
|
|
38
|
+
for (const segment of segments) {
|
|
39
|
+
const match = grammar.exec(segment);
|
|
40
|
+
if (!match?.groups) {
|
|
41
|
+
return { ok: false, part: failedPart };
|
|
42
|
+
}
|
|
43
|
+
const key = match.groups.key ?? "";
|
|
44
|
+
if (seen.has(key)) {
|
|
45
|
+
return { ok: false, part: key };
|
|
46
|
+
}
|
|
47
|
+
seen.add(key);
|
|
48
|
+
pairs.push([key, match.groups.value ?? ""]);
|
|
49
|
+
}
|
|
50
|
+
return { ok: true, value: pairs };
|
|
51
|
+
}
|
|
52
|
+
function decomposeState(spec, raw) {
|
|
53
|
+
return decomposePairs(spec, statePairGrammar(spec), raw.split(spec.grammar.state.separator), "state");
|
|
54
|
+
}
|
|
55
|
+
function decomposeFragment(spec, raw) {
|
|
56
|
+
const segments = raw.split(spec.grammar.fragment.separator);
|
|
57
|
+
const path = segments[0] ?? "";
|
|
58
|
+
if (path === "") {
|
|
59
|
+
return { ok: false, part: "fragment" };
|
|
60
|
+
}
|
|
61
|
+
const refinements = decomposePairs(spec, fragmentPairGrammar(spec), segments.slice(1), "fragment");
|
|
62
|
+
if (!refinements.ok) {
|
|
63
|
+
return refinements;
|
|
64
|
+
}
|
|
65
|
+
return { ok: true, value: { path, refinements: refinements.value } };
|
|
66
|
+
}
|
|
67
|
+
/** True when every `%` in the value begins one of the table's percent-forms — the strict nested encoding. */
|
|
68
|
+
function strictlyEncoded(value, table) {
|
|
69
|
+
const forms = Object.values(table);
|
|
70
|
+
let at = value.indexOf("%");
|
|
71
|
+
while (at !== -1) {
|
|
72
|
+
if (!forms.some((form) => value.startsWith(form, at))) {
|
|
73
|
+
return false;
|
|
74
|
+
}
|
|
75
|
+
at = value.indexOf("%", at + 1);
|
|
76
|
+
}
|
|
77
|
+
return true;
|
|
78
|
+
}
|
|
79
|
+
/** A qualifier value that nests an identifier: strictly encoded, decoded with the form's table, parsed one level down. */
|
|
80
|
+
function tryNested(spec, form, value, depth) {
|
|
81
|
+
const maxDepth = form.depth ?? 0;
|
|
82
|
+
if (depth >= maxDepth || !value.startsWith(schemePrefix(spec))) {
|
|
83
|
+
return undefined;
|
|
84
|
+
}
|
|
85
|
+
const table = tableFor(spec, form);
|
|
86
|
+
if (!strictlyEncoded(value, table)) {
|
|
87
|
+
return undefined;
|
|
88
|
+
}
|
|
89
|
+
const decoded = decodeReserved(value, table);
|
|
90
|
+
const inner = parseInternal(spec, decoded, depth + 1);
|
|
91
|
+
// A nested identifier stays readable whatever its type or version; only a malformed one is refused.
|
|
92
|
+
return inner.status === status(spec, "malformed") ? undefined : decoded;
|
|
93
|
+
}
|
|
94
|
+
function matchForms(spec, formNames, value, depth) {
|
|
95
|
+
for (const formName of formNames) {
|
|
96
|
+
const form = spec.forms[formName];
|
|
97
|
+
if (!form) {
|
|
98
|
+
continue;
|
|
99
|
+
}
|
|
100
|
+
if (form.nested) {
|
|
101
|
+
const nested = tryNested(spec, form, value, depth);
|
|
102
|
+
if (nested !== undefined) {
|
|
103
|
+
return { matches: true, nested };
|
|
104
|
+
}
|
|
105
|
+
continue;
|
|
106
|
+
}
|
|
107
|
+
if (form.pattern && pattern(spec, form.pattern).test(value)) {
|
|
108
|
+
return { matches: true };
|
|
109
|
+
}
|
|
110
|
+
}
|
|
111
|
+
return { matches: false };
|
|
112
|
+
}
|
|
113
|
+
function parseInternal(spec, input, depth) {
|
|
114
|
+
const match = topLevelGrammar(spec).exec(input);
|
|
115
|
+
if (!match?.groups) {
|
|
116
|
+
return malformed(spec, input, "grammar", {});
|
|
117
|
+
}
|
|
118
|
+
const { version: versionText, type = "", locator = "", state, fragment: fragmentRaw } = match.groups;
|
|
119
|
+
const explicitVersion = versionText !== undefined;
|
|
120
|
+
const version = explicitVersion ? Number(versionText) : spec.version.default;
|
|
121
|
+
const head = { version, explicitVersion, type, locator };
|
|
122
|
+
if (explicitVersion) {
|
|
123
|
+
head.versionText = versionText;
|
|
124
|
+
}
|
|
125
|
+
let qualifiers = [];
|
|
126
|
+
if (state !== undefined) {
|
|
127
|
+
const decomposed = decomposeState(spec, state);
|
|
128
|
+
if (!decomposed.ok) {
|
|
129
|
+
return malformed(spec, input, decomposed.part, head);
|
|
130
|
+
}
|
|
131
|
+
qualifiers = decomposed.value;
|
|
132
|
+
}
|
|
133
|
+
head.qualifiers = qualifiers;
|
|
134
|
+
let fragment = null;
|
|
135
|
+
if (fragmentRaw !== undefined) {
|
|
136
|
+
const decomposed = decomposeFragment(spec, fragmentRaw);
|
|
137
|
+
if (!decomposed.ok) {
|
|
138
|
+
return malformed(spec, input, decomposed.part, head);
|
|
139
|
+
}
|
|
140
|
+
fragment = decomposed.value;
|
|
141
|
+
}
|
|
142
|
+
head.fragment = fragment;
|
|
143
|
+
const base = {
|
|
144
|
+
input,
|
|
145
|
+
status: status(spec, "ok"),
|
|
146
|
+
version,
|
|
147
|
+
explicitVersion,
|
|
148
|
+
type,
|
|
149
|
+
locator,
|
|
150
|
+
qualifiers,
|
|
151
|
+
fragment,
|
|
152
|
+
};
|
|
153
|
+
if (explicitVersion) {
|
|
154
|
+
base.versionText = versionText;
|
|
155
|
+
}
|
|
156
|
+
const delegated = delegatedString(spec, type, locator);
|
|
157
|
+
if (delegated !== undefined) {
|
|
158
|
+
base.delegated = delegated;
|
|
159
|
+
}
|
|
160
|
+
if (!spec.version.supported.includes(version)) {
|
|
161
|
+
return { ...base, status: status(spec, "unsupported") };
|
|
162
|
+
}
|
|
163
|
+
// Qualifiers say which state; refinements say which part. The two sides never trade contents.
|
|
164
|
+
for (const [key] of qualifiers) {
|
|
165
|
+
if (Object.hasOwn(spec.refinements, key)) {
|
|
166
|
+
return malformed(spec, input, key, head);
|
|
167
|
+
}
|
|
168
|
+
}
|
|
169
|
+
if (fragment) {
|
|
170
|
+
for (const [key] of fragment.refinements) {
|
|
171
|
+
if (Object.hasOwn(spec.qualifiers, key)) {
|
|
172
|
+
return malformed(spec, input, key, head);
|
|
173
|
+
}
|
|
174
|
+
}
|
|
175
|
+
}
|
|
176
|
+
const nested = {};
|
|
177
|
+
for (const [key, value] of qualifiers) {
|
|
178
|
+
const declared = spec.qualifiers[key];
|
|
179
|
+
if (!declared) {
|
|
180
|
+
continue; // spec.grammar.state.unknownKey — carried through untouched, asserted in validators.ts
|
|
181
|
+
}
|
|
182
|
+
const result = matchForms(spec, declared.forms, value, depth);
|
|
183
|
+
if (!result.matches) {
|
|
184
|
+
return malformed(spec, input, key, head);
|
|
185
|
+
}
|
|
186
|
+
if (result.nested !== undefined) {
|
|
187
|
+
nested[key] = result.nested;
|
|
188
|
+
}
|
|
189
|
+
}
|
|
190
|
+
if (Object.keys(nested).length > 0) {
|
|
191
|
+
base.nested = nested;
|
|
192
|
+
}
|
|
193
|
+
if (fragment) {
|
|
194
|
+
for (const [key, value] of fragment.refinements) {
|
|
195
|
+
const declared = spec.refinements[key];
|
|
196
|
+
if (!declared) {
|
|
197
|
+
continue; // spec.unknownRefinement — carried through untouched, asserted in validators.ts
|
|
198
|
+
}
|
|
199
|
+
if (!pattern(spec, declared.pattern).test(value) || !rangeHolds(declared, value)) {
|
|
200
|
+
return malformed(spec, input, key, head);
|
|
201
|
+
}
|
|
202
|
+
}
|
|
203
|
+
}
|
|
204
|
+
const entry = spec.dispatch[type];
|
|
205
|
+
if (!entry || delegated === undefined) {
|
|
206
|
+
return { ...base, status: spec.unknownType };
|
|
207
|
+
}
|
|
208
|
+
const validation = validateLocator(spec, entry, delegated);
|
|
209
|
+
if (validation === undefined) {
|
|
210
|
+
return { ...base, status: spec.unknownType }; // a validator this package does not implement
|
|
211
|
+
}
|
|
212
|
+
if (!validation.ok) {
|
|
213
|
+
return malformed(spec, input, "locator", head);
|
|
214
|
+
}
|
|
215
|
+
if (validation.canonical !== undefined) {
|
|
216
|
+
base.canonical = validation.canonical;
|
|
217
|
+
}
|
|
218
|
+
return base;
|
|
219
|
+
}
|
|
220
|
+
/** Parses a `ref:` identifier string against the loaded spec. Never throws for an identifier problem. */
|
|
221
|
+
export function parse(input) {
|
|
222
|
+
const spec = loadSpec();
|
|
223
|
+
assertImplemented(spec);
|
|
224
|
+
if (typeof input !== "string") {
|
|
225
|
+
// Anything that is not a string is malformed at the grammar; it is not converted, because a
|
|
226
|
+
// hostile object's conversion can itself throw, and nothing here may throw for an identifier.
|
|
227
|
+
return malformed(spec, "", "grammar", {});
|
|
228
|
+
}
|
|
229
|
+
return parseInternal(spec, input, 0);
|
|
230
|
+
}
|
|
231
|
+
//# sourceMappingURL=parse.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"parse.js","sourceRoot":"","sources":["../src/parse.ts"],"names":[],"mappings":"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"}
|
|
@@ -0,0 +1,36 @@
|
|
|
1
|
+
// SPDX-License-Identifier: Apache-2.0
|
|
2
|
+
//
|
|
3
|
+
// Reassembles a ParseResult into the exact bytes it was parsed from — the inverse of parse.ts's
|
|
4
|
+
// structural decomposition. Every part is emitted verbatim: the locator, the qualifier values
|
|
5
|
+
// (known and unknown keys alike), the fragment path and its refinements.
|
|
6
|
+
import { SerialiseError } from "./errors.js";
|
|
7
|
+
import { FIELD, FRAGMENT_INTRODUCER, PAIR, schemePrefix } from "./grammar.js";
|
|
8
|
+
import { loadSpec, part, status } from "./spec.js";
|
|
9
|
+
function pairs(entries, separator) {
|
|
10
|
+
return entries.map(([key, value]) => `${key}${PAIR}${value}`).join(separator);
|
|
11
|
+
}
|
|
12
|
+
/** `serialise(parse(s)) === s` for every parseable input, including uncovered and unsupported ones. A malformed result has no faithful form and is refused. */
|
|
13
|
+
export function serialise(parsed) {
|
|
14
|
+
const spec = loadSpec();
|
|
15
|
+
if (parsed.status === status(spec, "malformed")) {
|
|
16
|
+
throw new SerialiseError(part(spec, parsed.part ?? "grammar"), "a malformed identifier cannot be serialised without losing the part that failed");
|
|
17
|
+
}
|
|
18
|
+
let out = schemePrefix(spec);
|
|
19
|
+
if (parsed.explicitVersion) {
|
|
20
|
+
out += `${parsed.versionText ?? String(parsed.version)}${FIELD}`;
|
|
21
|
+
}
|
|
22
|
+
out += `${parsed.type}${FIELD}${parsed.locator}`;
|
|
23
|
+
if (parsed.qualifiers.length > 0) {
|
|
24
|
+
const separator = spec.grammar.state.separator;
|
|
25
|
+
out += `${separator}${pairs(parsed.qualifiers, separator)}`;
|
|
26
|
+
}
|
|
27
|
+
if (parsed.fragment) {
|
|
28
|
+
out += `${FRAGMENT_INTRODUCER}${parsed.fragment.path}`;
|
|
29
|
+
if (parsed.fragment.refinements.length > 0) {
|
|
30
|
+
const separator = spec.grammar.fragment.separator;
|
|
31
|
+
out += `${separator}${pairs(parsed.fragment.refinements, separator)}`;
|
|
32
|
+
}
|
|
33
|
+
}
|
|
34
|
+
return out;
|
|
35
|
+
}
|
|
36
|
+
//# sourceMappingURL=serialise.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"serialise.js","sourceRoot":"","sources":["../src/serialise.ts"],"names":[],"mappings":"AAAA,sCAAsC;AACtC,EAAE;AACF,gGAAgG;AAChG,8FAA8F;AAC9F,yEAAyE;AAEzE,OAAO,EAAE,cAAc,EAAE,MAAM,aAAa,CAAA;AAC5C,OAAO,EAAE,KAAK,EAAE,mBAAmB,EAAE,IAAI,EAAE,YAAY,EAAE,MAAM,cAAc,CAAA;AAC7E,OAAO,EAAE,QAAQ,EAAE,IAAI,EAAE,MAAM,EAAE,MAAM,WAAW,CAAA;AAGlD,SAAS,KAAK,CAAC,OAAwB,EAAE,SAAiB;IACxD,OAAO,OAAO,CAAC,GAAG,CAAC,CAAC,CAAC,GAAG,EAAE,KAAK,CAAC,EAAE,EAAE,CAAC,GAAG,GAAG,GAAG,IAAI,GAAG,KAAK,EAAE,CAAC,CAAC,IAAI,CAAC,SAAS,CAAC,CAAA;AAC/E,CAAC;AAED,+JAA+J;AAC/J,MAAM,UAAU,SAAS,CAAC,MAAmB;IAC3C,MAAM,IAAI,GAAG,QAAQ,EAAE,CAAA;IACvB,IAAI,MAAM,CAAC,MAAM,KAAK,MAAM,CAAC,IAAI,EAAE,WAAW,CAAC,EAAE,CAAC;QAChD,MAAM,IAAI,cAAc,CAAC,IAAI,CAAC,IAAI,EAAE,MAAM,CAAC,IAAI,IAAI,SAAS,CAAC,EAAE,iFAAiF,CAAC,CAAA;IACnJ,CAAC;IAED,IAAI,GAAG,GAAG,YAAY,CAAC,IAAI,CAAC,CAAA;IAC5B,IAAI,MAAM,CAAC,eAAe,EAAE,CAAC;QAC3B,GAAG,IAAI,GAAG,MAAM,CAAC,WAAW,IAAI,MAAM,CAAC,MAAM,CAAC,OAAO,CAAC,GAAG,KAAK,EAAE,CAAA;IAClE,CAAC;IACD,GAAG,IAAI,GAAG,MAAM,CAAC,IAAI,GAAG,KAAK,GAAG,MAAM,CAAC,OAAO,EAAE,CAAA;IAEhD,IAAI,MAAM,CAAC,UAAU,CAAC,MAAM,GAAG,CAAC,EAAE,CAAC;QACjC,MAAM,SAAS,GAAG,IAAI,CAAC,OAAO,CAAC,KAAK,CAAC,SAAS,CAAA;QAC9C,GAAG,IAAI,GAAG,SAAS,GAAG,KAAK,CAAC,MAAM,CAAC,UAAU,EAAE,SAAS,CAAC,EAAE,CAAA;IAC7D,CAAC;IAED,IAAI,MAAM,CAAC,QAAQ,EAAE,CAAC;QACpB,GAAG,IAAI,GAAG,mBAAmB,GAAG,MAAM,CAAC,QAAQ,CAAC,IAAI,EAAE,CAAA;QACtD,IAAI,MAAM,CAAC,QAAQ,CAAC,WAAW,CAAC,MAAM,GAAG,CAAC,EAAE,CAAC;YAC3C,MAAM,SAAS,GAAG,IAAI,CAAC,OAAO,CAAC,QAAQ,CAAC,SAAS,CAAA;YACjD,GAAG,IAAI,GAAG,SAAS,GAAG,KAAK,CAAC,MAAM,CAAC,QAAQ,CAAC,WAAW,EAAE,SAAS,CAAC,EAAE,CAAA;QACvE,CAAC;IACH,CAAC;IAED,OAAO,GAAG,CAAA;AACZ,CAAC"}
|
package/dist/spec.d.ts
ADDED
|
@@ -0,0 +1,142 @@
|
|
|
1
|
+
/** Thrown when the loaded spec's bytes do not match its sidecar digest. */
|
|
2
|
+
export declare class SpecIntegrityError extends Error {
|
|
3
|
+
constructor(message: string);
|
|
4
|
+
}
|
|
5
|
+
/** Thrown when the loaded spec declares a specVersion major, or a vocabulary, this package does not support. */
|
|
6
|
+
export declare class SpecVersionError extends Error {
|
|
7
|
+
constructor(message: string);
|
|
8
|
+
}
|
|
9
|
+
/**
|
|
10
|
+
* The `ref:` specification, as published in `spec/ref-id.json`. Typed only as far as the code
|
|
11
|
+
* reads it; the shapes of the tables and of `vectors` are spec data, read dynamically rather than
|
|
12
|
+
* mirrored into a second declaration.
|
|
13
|
+
*/
|
|
14
|
+
export interface RefIdSpec {
|
|
15
|
+
specVersion: string;
|
|
16
|
+
scheme: string;
|
|
17
|
+
grammar: {
|
|
18
|
+
dialect: string;
|
|
19
|
+
expression: string;
|
|
20
|
+
groups: string[];
|
|
21
|
+
adaptations: Record<string, {
|
|
22
|
+
replace: [string, string][];
|
|
23
|
+
}>;
|
|
24
|
+
adaptationsApplyTo: string;
|
|
25
|
+
anchors: string;
|
|
26
|
+
state: {
|
|
27
|
+
separator: string;
|
|
28
|
+
pair: string;
|
|
29
|
+
unknownKey: string;
|
|
30
|
+
repeatedKey: string;
|
|
31
|
+
};
|
|
32
|
+
fragment: {
|
|
33
|
+
separator: string;
|
|
34
|
+
pair: string;
|
|
35
|
+
path: string;
|
|
36
|
+
repeatedKey: string;
|
|
37
|
+
};
|
|
38
|
+
};
|
|
39
|
+
version: {
|
|
40
|
+
default: number;
|
|
41
|
+
supported: number[];
|
|
42
|
+
mints: string[];
|
|
43
|
+
note: string;
|
|
44
|
+
};
|
|
45
|
+
encoding: Record<string, {
|
|
46
|
+
reserved: string[];
|
|
47
|
+
table?: Record<string, string>;
|
|
48
|
+
note?: string;
|
|
49
|
+
}>;
|
|
50
|
+
dispatch: Record<string, {
|
|
51
|
+
locator: string;
|
|
52
|
+
validator: string;
|
|
53
|
+
delegate: string;
|
|
54
|
+
pattern?: string;
|
|
55
|
+
reference?: string;
|
|
56
|
+
declaredBy?: string;
|
|
57
|
+
}>;
|
|
58
|
+
delegation: Record<string, string>;
|
|
59
|
+
statuses: string[];
|
|
60
|
+
parts: string[];
|
|
61
|
+
unknownType: string;
|
|
62
|
+
roles: Record<string, string[]>;
|
|
63
|
+
qualifiers: Record<string, {
|
|
64
|
+
role: string;
|
|
65
|
+
forms: string[];
|
|
66
|
+
}>;
|
|
67
|
+
forms: Record<string, {
|
|
68
|
+
pattern?: string;
|
|
69
|
+
reference?: string;
|
|
70
|
+
nested?: boolean;
|
|
71
|
+
depth?: number;
|
|
72
|
+
encoding?: string;
|
|
73
|
+
digest?: boolean;
|
|
74
|
+
}>;
|
|
75
|
+
refinements: Record<string, {
|
|
76
|
+
pattern: string;
|
|
77
|
+
range?: string;
|
|
78
|
+
boundSeparator?: string;
|
|
79
|
+
reference?: string;
|
|
80
|
+
}>;
|
|
81
|
+
unknownRefinement: string;
|
|
82
|
+
resolutionStates: string[];
|
|
83
|
+
stateLevels: string[];
|
|
84
|
+
fragmentGrammars: Record<string, {
|
|
85
|
+
declaredName: string;
|
|
86
|
+
tieBreak: string;
|
|
87
|
+
}>;
|
|
88
|
+
digest: {
|
|
89
|
+
algorithm: string;
|
|
90
|
+
over: string;
|
|
91
|
+
order: string;
|
|
92
|
+
join: string;
|
|
93
|
+
encoding: string;
|
|
94
|
+
dedupe: boolean;
|
|
95
|
+
output: string;
|
|
96
|
+
};
|
|
97
|
+
canonicalisation: {
|
|
98
|
+
form: string;
|
|
99
|
+
rules: string[];
|
|
100
|
+
sidecar: string;
|
|
101
|
+
};
|
|
102
|
+
envelope: {
|
|
103
|
+
fields: Record<string, string>;
|
|
104
|
+
selfReference: string;
|
|
105
|
+
setsField: string;
|
|
106
|
+
digestForms: string;
|
|
107
|
+
invariant: string;
|
|
108
|
+
onFailure: string;
|
|
109
|
+
};
|
|
110
|
+
vectors: {
|
|
111
|
+
parse: unknown[];
|
|
112
|
+
roundtrip: string[];
|
|
113
|
+
build: unknown[];
|
|
114
|
+
digest: unknown[];
|
|
115
|
+
envelope: unknown[];
|
|
116
|
+
};
|
|
117
|
+
}
|
|
118
|
+
/**
|
|
119
|
+
* Canonical serialisation per `spec.canonicalisation.rules`: object keys sorted by UTF-16 code
|
|
120
|
+
* unit, no whitespace outside strings, strings escaped as `JSON.stringify` does, numbers
|
|
121
|
+
* restricted to integers.
|
|
122
|
+
*/
|
|
123
|
+
export declare function canonicalise(value: unknown): string;
|
|
124
|
+
/**
|
|
125
|
+
* A status name this code needs, checked against the vocabulary the spec declares. Code names a
|
|
126
|
+
* status because it has to choose one; the spec owns the list, and naming one the spec lacks is a
|
|
127
|
+
* version mismatch, reported as such rather than silently emitted.
|
|
128
|
+
*/
|
|
129
|
+
export declare function status(spec: RefIdSpec, name: string): string;
|
|
130
|
+
/**
|
|
131
|
+
* A part name this code needs, checked against the vocabulary the spec declares: one of `spec.parts`,
|
|
132
|
+
* or a declared qualifier or refinement key (the two placeholders in that list). Same contract as
|
|
133
|
+
* `status()`: naming a part the spec lacks is a version mismatch, reported rather than emitted.
|
|
134
|
+
*/
|
|
135
|
+
export declare function part(spec: RefIdSpec, name: string): string;
|
|
136
|
+
/**
|
|
137
|
+
* Loads and validates a spec + sidecar pair from an arbitrary directory. Exposed for the
|
|
138
|
+
* spec-integrity tests; `loadSpec()` is the entry point every other module uses.
|
|
139
|
+
*/
|
|
140
|
+
export declare function loadSpecFrom(dir: string): RefIdSpec;
|
|
141
|
+
/** The validated spec object, cached after the first successful load. */
|
|
142
|
+
export declare function loadSpec(): RefIdSpec;
|
package/dist/spec.js
ADDED
|
@@ -0,0 +1,116 @@
|
|
|
1
|
+
// SPDX-License-Identifier: Apache-2.0
|
|
2
|
+
//
|
|
3
|
+
// Loads spec/ref-id.json, verifies its digest against the sidecar, and checks the specVersion
|
|
4
|
+
// major. Behaviour only — the spec's own data is never restated here.
|
|
5
|
+
import { readFileSync } from "node:fs";
|
|
6
|
+
import { createHash } from "node:crypto";
|
|
7
|
+
/** Thrown when the loaded spec's bytes do not match its sidecar digest. */
|
|
8
|
+
export class SpecIntegrityError extends Error {
|
|
9
|
+
constructor(message) {
|
|
10
|
+
super(message);
|
|
11
|
+
this.name = "SpecIntegrityError";
|
|
12
|
+
}
|
|
13
|
+
}
|
|
14
|
+
/** Thrown when the loaded spec declares a specVersion major, or a vocabulary, this package does not support. */
|
|
15
|
+
export class SpecVersionError extends Error {
|
|
16
|
+
constructor(message) {
|
|
17
|
+
super(message);
|
|
18
|
+
this.name = "SpecVersionError";
|
|
19
|
+
}
|
|
20
|
+
}
|
|
21
|
+
/**
|
|
22
|
+
* Canonical serialisation per `spec.canonicalisation.rules`: object keys sorted by UTF-16 code
|
|
23
|
+
* unit, no whitespace outside strings, strings escaped as `JSON.stringify` does, numbers
|
|
24
|
+
* restricted to integers.
|
|
25
|
+
*/
|
|
26
|
+
export function canonicalise(value) {
|
|
27
|
+
if (value === null) {
|
|
28
|
+
return "null";
|
|
29
|
+
}
|
|
30
|
+
if (typeof value === "boolean") {
|
|
31
|
+
return value ? "true" : "false";
|
|
32
|
+
}
|
|
33
|
+
if (typeof value === "number") {
|
|
34
|
+
if (!Number.isInteger(value)) {
|
|
35
|
+
throw new Error(`canonicalisation covers integers only, got ${value}`);
|
|
36
|
+
}
|
|
37
|
+
return String(value);
|
|
38
|
+
}
|
|
39
|
+
if (typeof value === "string") {
|
|
40
|
+
return JSON.stringify(value);
|
|
41
|
+
}
|
|
42
|
+
if (Array.isArray(value)) {
|
|
43
|
+
return `[${value.map((item) => canonicalise(item)).join(",")}]`;
|
|
44
|
+
}
|
|
45
|
+
if (typeof value === "object") {
|
|
46
|
+
const record = value;
|
|
47
|
+
const keys = Object.keys(record).sort();
|
|
48
|
+
return `{${keys.map((key) => `${JSON.stringify(key)}:${canonicalise(record[key])}`).join(",")}}`;
|
|
49
|
+
}
|
|
50
|
+
throw new Error(`value of type ${typeof value} has no canonical form`);
|
|
51
|
+
}
|
|
52
|
+
/** The major version this package was built against. Not spec data — the package's own contract. */
|
|
53
|
+
const SUPPORTED_SPEC_MAJOR = "1";
|
|
54
|
+
function readText(location) {
|
|
55
|
+
return readFileSync(location, "utf8");
|
|
56
|
+
}
|
|
57
|
+
function validate(rawJson, rawSidecar) {
|
|
58
|
+
const parsed = JSON.parse(rawJson);
|
|
59
|
+
const canonical = canonicalise(parsed);
|
|
60
|
+
const computed = createHash("sha256").update(canonical, "utf8").digest("hex");
|
|
61
|
+
const expected = rawSidecar.trim();
|
|
62
|
+
if (computed !== expected) {
|
|
63
|
+
throw new SpecIntegrityError(`spec/ref-id.json does not match its sidecar digest (expected ${expected}, computed ${computed})`);
|
|
64
|
+
}
|
|
65
|
+
const spec = parsed;
|
|
66
|
+
const major = String(spec.specVersion).split(".")[0];
|
|
67
|
+
if (major !== SUPPORTED_SPEC_MAJOR) {
|
|
68
|
+
throw new SpecVersionError(`spec/ref-id.json declares specVersion ${spec.specVersion}; this package supports major ${SUPPORTED_SPEC_MAJOR}`);
|
|
69
|
+
}
|
|
70
|
+
return spec;
|
|
71
|
+
}
|
|
72
|
+
/**
|
|
73
|
+
* A status name this code needs, checked against the vocabulary the spec declares. Code names a
|
|
74
|
+
* status because it has to choose one; the spec owns the list, and naming one the spec lacks is a
|
|
75
|
+
* version mismatch, reported as such rather than silently emitted.
|
|
76
|
+
*/
|
|
77
|
+
export function status(spec, name) {
|
|
78
|
+
if (!spec.statuses.includes(name)) {
|
|
79
|
+
throw new SpecVersionError(`this package names the status "${name}", which spec ${spec.specVersion} does not declare`);
|
|
80
|
+
}
|
|
81
|
+
return name;
|
|
82
|
+
}
|
|
83
|
+
/**
|
|
84
|
+
* A part name this code needs, checked against the vocabulary the spec declares: one of `spec.parts`,
|
|
85
|
+
* or a declared qualifier or refinement key (the two placeholders in that list). Same contract as
|
|
86
|
+
* `status()`: naming a part the spec lacks is a version mismatch, reported rather than emitted.
|
|
87
|
+
*/
|
|
88
|
+
export function part(spec, name) {
|
|
89
|
+
if (spec.parts.includes(name) || Object.hasOwn(spec.qualifiers, name) || Object.hasOwn(spec.refinements, name)) {
|
|
90
|
+
return name;
|
|
91
|
+
}
|
|
92
|
+
throw new SpecVersionError(`this package names the part "${name}", which spec ${spec.specVersion} does not declare`);
|
|
93
|
+
}
|
|
94
|
+
/**
|
|
95
|
+
* Loads and validates a spec + sidecar pair from an arbitrary directory. Exposed for the
|
|
96
|
+
* spec-integrity tests; `loadSpec()` is the entry point every other module uses.
|
|
97
|
+
*/
|
|
98
|
+
export function loadSpecFrom(dir) {
|
|
99
|
+
const raw = readText(`${dir}/ref-id.json`);
|
|
100
|
+
const sidecar = readText(`${dir}/ref-id.json.sha256`);
|
|
101
|
+
return validate(raw, sidecar);
|
|
102
|
+
}
|
|
103
|
+
let cached;
|
|
104
|
+
/** The validated spec object, cached after the first successful load. */
|
|
105
|
+
export function loadSpec() {
|
|
106
|
+
if (cached) {
|
|
107
|
+
return cached;
|
|
108
|
+
}
|
|
109
|
+
const jsonUrl = new URL("../spec/ref-id.json", import.meta.url);
|
|
110
|
+
const sidecarUrl = new URL("../spec/ref-id.json.sha256", import.meta.url);
|
|
111
|
+
const raw = readText(jsonUrl);
|
|
112
|
+
const sidecar = readText(sidecarUrl);
|
|
113
|
+
cached = validate(raw, sidecar);
|
|
114
|
+
return cached;
|
|
115
|
+
}
|
|
116
|
+
//# sourceMappingURL=spec.js.map
|
package/dist/spec.js.map
ADDED
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"spec.js","sourceRoot":"","sources":["../src/spec.ts"],"names":[],"mappings":"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"}
|
package/dist/types.d.ts
ADDED
|
@@ -0,0 +1,48 @@
|
|
|
1
|
+
/** A `key=value` pair, in the order it appeared in the input, values kept verbatim. */
|
|
2
|
+
export type Pair = [key: string, value: string];
|
|
3
|
+
/** One of `spec.statuses` — the vocabulary is data, so the type is open. */
|
|
4
|
+
export type ParseStatus = string;
|
|
5
|
+
export interface ParsedFragment {
|
|
6
|
+
path: string;
|
|
7
|
+
refinements: Pair[];
|
|
8
|
+
}
|
|
9
|
+
export interface ParseResult {
|
|
10
|
+
input: string;
|
|
11
|
+
status: ParseStatus;
|
|
12
|
+
version: number;
|
|
13
|
+
explicitVersion: boolean;
|
|
14
|
+
/** The version token as written, when explicit — so that serialising returns the same bytes. */
|
|
15
|
+
versionText?: string;
|
|
16
|
+
type: string;
|
|
17
|
+
/** The captured group, verbatim. */
|
|
18
|
+
locator: string;
|
|
19
|
+
/** The string handed to the owning validator, formed per `dispatch.<type>.delegate`; absent when the type is not dispatched. */
|
|
20
|
+
delegated?: string;
|
|
21
|
+
/** The validator's own canonical spelling of `delegated`, when the format defines one. Informational; identity is the string. */
|
|
22
|
+
canonical?: string;
|
|
23
|
+
qualifiers: Pair[];
|
|
24
|
+
/** The decoded identifier behind each qualifier value that nests one. */
|
|
25
|
+
nested?: Record<string, string>;
|
|
26
|
+
fragment: ParsedFragment | null;
|
|
27
|
+
/** On `malformed`: which part failed — one of `spec.parts`. */
|
|
28
|
+
part?: string;
|
|
29
|
+
}
|
|
30
|
+
export type NestedQualifierValue = {
|
|
31
|
+
nested: string;
|
|
32
|
+
};
|
|
33
|
+
export interface BuildParts {
|
|
34
|
+
type: string;
|
|
35
|
+
/** For a type-prefixed dispatch, either the bare group (`npm/x@1.0.0`) or the intact format string (`pkg:npm/x@1.0.0`). */
|
|
36
|
+
locator: string;
|
|
37
|
+
qualifiers?: [key: string, value: string | NestedQualifierValue][];
|
|
38
|
+
fragment?: string | {
|
|
39
|
+
path: string;
|
|
40
|
+
refinements?: Pair[];
|
|
41
|
+
};
|
|
42
|
+
/** Never reaches the identifier — location is an attribute of the node, not its name. */
|
|
43
|
+
location?: unknown;
|
|
44
|
+
}
|
|
45
|
+
export interface EnvelopeResult {
|
|
46
|
+
admissible: boolean;
|
|
47
|
+
reason?: string;
|
|
48
|
+
}
|
package/dist/types.js
ADDED
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"types.js","sourceRoot":"","sources":["../src/types.ts"],"names":[],"mappings":"AAAA,sCAAsC"}
|
|
@@ -0,0 +1,19 @@
|
|
|
1
|
+
import { type RefIdSpec } from "./spec.ts";
|
|
2
|
+
export type Validation = {
|
|
3
|
+
ok: true;
|
|
4
|
+
canonical?: string;
|
|
5
|
+
} | {
|
|
6
|
+
ok: false;
|
|
7
|
+
};
|
|
8
|
+
type DispatchEntry = RefIdSpec["dispatch"][string];
|
|
9
|
+
/** Refuses a spec whose declared policies or range names are ones this package does not implement. */
|
|
10
|
+
export declare function assertImplemented(spec: RefIdSpec): void;
|
|
11
|
+
/** The string handed to the validator, or undefined when the type is not dispatched or its delegate mode is unknown here. */
|
|
12
|
+
export declare function delegatedString(spec: RefIdSpec, type: string, locator: string): string | undefined;
|
|
13
|
+
/** True when a producer may pass the intact format string (`pkg:npm/x`) as the locator of this type. */
|
|
14
|
+
export declare function foldsType(spec: RefIdSpec, type: string): boolean;
|
|
15
|
+
/** Runs the owning validator, or undefined when the spec names one this package does not implement. */
|
|
16
|
+
export declare function validateLocator(spec: RefIdSpec, entry: DispatchEntry, delegated: string): Validation | undefined;
|
|
17
|
+
/** True when the value satisfies the refinement's declared range constraint (or it declares none). */
|
|
18
|
+
export declare function rangeHolds(refinement: RefIdSpec["refinements"][string], value: string): boolean;
|
|
19
|
+
export {};
|