@entelekheia/ref-id 0.2.0 → 0.4.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 +72 -6
- package/dist/canonical.d.ts +17 -0
- package/dist/canonical.js +33 -0
- package/dist/canonical.js.map +1 -0
- package/dist/digest.js +2 -2
- package/dist/digest.js.map +1 -1
- package/dist/hash.browser.d.ts +9 -0
- package/dist/hash.browser.js +27 -0
- package/dist/hash.browser.js.map +1 -0
- package/dist/hash.d.ts +10 -0
- package/dist/hash.js +34 -0
- package/dist/hash.js.map +1 -0
- package/dist/hash.node.d.ts +7 -0
- package/dist/hash.node.js +12 -0
- package/dist/hash.node.js.map +1 -0
- package/dist/index.browser.d.ts +11 -0
- package/dist/index.browser.js +41 -0
- package/dist/index.browser.js.map +1 -0
- package/dist/index.d.ts +4 -1
- package/dist/index.js +15 -1
- package/dist/index.js.map +1 -1
- package/dist/relations.d.ts +29 -0
- package/dist/relations.js +135 -0
- package/dist/relations.js.map +1 -0
- package/dist/spec.browser.d.ts +12 -0
- package/dist/spec.browser.js +22 -0
- package/dist/spec.browser.js.map +1 -0
- package/dist/spec.d.ts +31 -11
- package/dist/spec.js +39 -44
- package/dist/spec.js.map +1 -1
- package/dist/spec.node.d.ts +11 -0
- package/dist/spec.node.js +49 -0
- package/dist/spec.node.js.map +1 -0
- package/dist/validators.js +54 -1
- package/dist/validators.js.map +1 -1
- package/package.json +8 -2
- package/spec/ref-id.json +2465 -1752
- package/spec/ref-id.json.sha256 +1 -1
package/README.md
CHANGED
|
@@ -7,7 +7,20 @@ The package embeds `spec/ref-id.json` and is held to it by that file's conforman
|
|
|
7
7
|
ref:[<version>:]<type>:<locator>[;<qualifier>=<value>]*[#<declared-name-path>[;<refinement>=<value>]*]
|
|
8
8
|
```
|
|
9
9
|
|
|
10
|
-
|
|
10
|
+
```ts
|
|
11
|
+
import { parse } from "@entelekheia/ref-id"
|
|
12
|
+
|
|
13
|
+
parse("ref:pkg:npm/@acme/scanner-core@0.1.0#Observation")
|
|
14
|
+
// => {
|
|
15
|
+
// status: "ok",
|
|
16
|
+
// type: "pkg",
|
|
17
|
+
// locator: "npm/@acme/scanner-core@0.1.0",
|
|
18
|
+
// fragment: { path: "Observation", refinements: [] },
|
|
19
|
+
// delegated: "pkg:npm/@acme/scanner-core@0.1.0",
|
|
20
|
+
// canonical: "pkg:npm/%40acme/scanner-core@0.1.0",
|
|
21
|
+
// ...
|
|
22
|
+
// }
|
|
23
|
+
```
|
|
11
24
|
|
|
12
25
|
## Why
|
|
13
26
|
|
|
@@ -25,13 +38,66 @@ npm install @entelekheia/ref-id
|
|
|
25
38
|
|
|
26
39
|
## Usage
|
|
27
40
|
|
|
28
|
-
The entry points are `parse`, `serialise`, `digest` and `
|
|
29
|
-
vectors in `spec/ref-id.json` (`parse`, `roundtrip`, `digest`, `envelope
|
|
30
|
-
and degrades to `uncovered`; it never throws.
|
|
41
|
+
The entry points are `parse`, `serialise`, `digest`, `validateEnvelope`, `samePackage` and `covers`; their
|
|
42
|
+
contracts are the vectors in `spec/ref-id.json` (`parse`, `roundtrip`, `digest`, `envelope`,
|
|
43
|
+
`comparison`). An unknown locator type parses and degrades to `uncovered`; it never throws.
|
|
44
|
+
|
|
45
|
+
Three questions get three answers, and they are not interchangeable:
|
|
46
|
+
|
|
47
|
+
```ts
|
|
48
|
+
import { covers, sameIdentifier, samePackage } from "@entelekheia/ref-id"
|
|
49
|
+
|
|
50
|
+
sameIdentifier("pkg:npm/x@1.0.0", "pkg:npm/x@2.0.0") // false — two identifiers, compared byte for byte
|
|
51
|
+
samePackage("ref:pkg:npm/x@1.0.0", "ref:pkg:npm/x@2.0.0") // true — one released thing, two versions
|
|
52
|
+
covers("ref:pkg:npm/x", "ref:pkg:npm/x@1.0.0") // true — the general covers the specific
|
|
53
|
+
covers("ref:pkg:npm/x@1.0.0", "ref:pkg:npm/x") // false — and never the reverse
|
|
54
|
+
```
|
|
55
|
+
|
|
56
|
+
`sameIdentifier` is what a digest and an envelope are built on, so it stays strict. `samePackage` is
|
|
57
|
+
symmetric and ignores the locator's version, and only where the type declares it carries one — a mailbox's
|
|
58
|
+
`@` and a served model's quantisation key are not versions. `covers` is **asymmetric**: what the first
|
|
59
|
+
identifier leaves undeclared, the second may declare freely; what the first declares, the second must
|
|
60
|
+
declare identically. That is what makes a partial identifier a query over a store keyed by identifier.
|
|
61
|
+
|
|
62
|
+
## Environments
|
|
63
|
+
|
|
64
|
+
**Node.js 22 or later, and any browser reached through a bundler.** The package ships two builds of one
|
|
65
|
+
source, and `exports` picks between them: a bundler that honours the `browser` condition takes the browser
|
|
66
|
+
build, everything else takes the default. Nothing to configure, and the public API is the same either way.
|
|
67
|
+
|
|
68
|
+
| | Node | Browser |
|
|
69
|
+
|---|---|---|
|
|
70
|
+
| Where the specification comes from | `spec/ref-id.json`, read from disk on first use | a constant compiled into the build |
|
|
71
|
+
| When its integrity is established | at load — the bytes are hashed and compared with the sidecar, and a mismatch throws | at build — the generator refuses to emit from a specification that fails its sidecar, and a staleness check refuses a compiled constant that no longer matches the file |
|
|
72
|
+
| sha256 for `digest()` | Node's `crypto` | `@noble/hashes` (pure JavaScript, no dependencies) |
|
|
73
|
+
|
|
74
|
+
**What the browser build gives up**, stated rather than discovered:
|
|
75
|
+
|
|
76
|
+
- **No integrity check at runtime.** There is no file to compare the constant against, and hashing it
|
|
77
|
+
against a digest compiled from the same source in the same build would be a check that cannot fail —
|
|
78
|
+
which reads as a guarantee and is a tautology. The build exports `SPEC_DIGEST`, the digest
|
|
79
|
+
`spec/ref-id.json` carried when the constant was generated; it is a statement about provenance, not a
|
|
80
|
+
verification, and it is labelled as one.
|
|
81
|
+
- **No `loadSpecFrom`.** It takes a directory to read a specification and its sidecar from. A browser has
|
|
82
|
+
neither, so the browser build does not export it; every other export is present and identical.
|
|
83
|
+
**TypeScript will not warn you about that on the default configuration.** Under
|
|
84
|
+
`"moduleResolution": "bundler"` without `customConditions`, the compiler resolves types through the
|
|
85
|
+
default condition — which does declare `loadSpecFrom` — while the bundler picks the browser build at
|
|
86
|
+
build time. `tsc --noEmit` then passes on an import that fails when the bundle is produced. Add
|
|
87
|
+
`"customConditions": ["browser"]` to your `tsconfig.json` and the compiler sees the same surface your
|
|
88
|
+
bundler does.
|
|
89
|
+
- **`digest()` is sha256 only.** The algorithm is specification data, and the browser implementation
|
|
90
|
+
honours `sha256` alone: a specification naming another one throws a `SpecVersionError` naming it, where
|
|
91
|
+
Node would honour whatever its own `crypto` honours.
|
|
92
|
+
- **The compiled-in specification costs bundle size.** It is the whole published file, including the parts
|
|
93
|
+
a given consumer never reads.
|
|
31
94
|
|
|
32
|
-
|
|
95
|
+
The two builds are held to each other by `npm run test:differential`, which runs the browser build as a
|
|
96
|
+
fourth implementation beside Node, Rust and Swift over every input the specification names, and fails on
|
|
97
|
+
the first disagreement. `npm run build` additionally walks the emitted browser module graph and refuses it
|
|
98
|
+
if any Node builtin — or anything resolving a path against a module's own location — survives into it.
|
|
33
99
|
|
|
34
|
-
|
|
100
|
+
Runtime dependencies: `packageurl-js` and `@noble/hashes`, both pure JavaScript. No native modules.
|
|
35
101
|
|
|
36
102
|
## License
|
|
37
103
|
|
|
@@ -0,0 +1,17 @@
|
|
|
1
|
+
import type { ParseResult } from "./types.ts";
|
|
2
|
+
/**
|
|
3
|
+
* The canonical form of an identifier: the same identifier with its qualifiers sorted by key.
|
|
4
|
+
*
|
|
5
|
+
* Sorting is by UTF-16 code unit, the order this specification already uses for its own file, and it is
|
|
6
|
+
* the cheapest canonicalisation rather than the point. What is load-bearing is that one deterministic
|
|
7
|
+
* order exists, so any two implementations reach the same answer about whether two identifiers are one.
|
|
8
|
+
*
|
|
9
|
+
* Every other part is left exactly as parsed. **Refinements are not sorted** — they sit on the fragment
|
|
10
|
+
* side and are positional, `lines=1,20` being a range, so reordering them would change what is named.
|
|
11
|
+
*
|
|
12
|
+
* A malformed identifier has no canonical form and is refused, for the reason `serialise` gives: there is
|
|
13
|
+
* no faithful way to write back a string whose failing part was never decomposed.
|
|
14
|
+
*/
|
|
15
|
+
export declare function canonical(identifier: string | ParseResult): string;
|
|
16
|
+
/** Whether two identifiers name one thing. Order of qualifiers does not distinguish; everything else does. */
|
|
17
|
+
export declare function sameIdentifier(a: string | ParseResult, b: string | ParseResult): boolean;
|
|
@@ -0,0 +1,33 @@
|
|
|
1
|
+
// SPDX-License-Identifier: Apache-2.0
|
|
2
|
+
//
|
|
3
|
+
// The form two identifiers are compared in. `;a=1;b=2` and `;b=2;a=1` name one thing, so identity is
|
|
4
|
+
// judged on this rather than on the bytes a producer happened to write.
|
|
5
|
+
import { parse } from "./parse.js";
|
|
6
|
+
import { serialise } from "./serialise.js";
|
|
7
|
+
import { loadSpec, status } from "./spec.js";
|
|
8
|
+
/**
|
|
9
|
+
* The canonical form of an identifier: the same identifier with its qualifiers sorted by key.
|
|
10
|
+
*
|
|
11
|
+
* Sorting is by UTF-16 code unit, the order this specification already uses for its own file, and it is
|
|
12
|
+
* the cheapest canonicalisation rather than the point. What is load-bearing is that one deterministic
|
|
13
|
+
* order exists, so any two implementations reach the same answer about whether two identifiers are one.
|
|
14
|
+
*
|
|
15
|
+
* Every other part is left exactly as parsed. **Refinements are not sorted** — they sit on the fragment
|
|
16
|
+
* side and are positional, `lines=1,20` being a range, so reordering them would change what is named.
|
|
17
|
+
*
|
|
18
|
+
* A malformed identifier has no canonical form and is refused, for the reason `serialise` gives: there is
|
|
19
|
+
* no faithful way to write back a string whose failing part was never decomposed.
|
|
20
|
+
*/
|
|
21
|
+
export function canonical(identifier) {
|
|
22
|
+
const spec = loadSpec();
|
|
23
|
+
const parsed = typeof identifier === "string" ? parse(identifier) : identifier;
|
|
24
|
+
if (parsed.status === status(spec, "malformed"))
|
|
25
|
+
return serialise(parsed); // throws, naming the failing part
|
|
26
|
+
const sorted = [...parsed.qualifiers].sort(([a], [b]) => (a < b ? -1 : a > b ? 1 : 0));
|
|
27
|
+
return serialise({ ...parsed, qualifiers: sorted });
|
|
28
|
+
}
|
|
29
|
+
/** Whether two identifiers name one thing. Order of qualifiers does not distinguish; everything else does. */
|
|
30
|
+
export function sameIdentifier(a, b) {
|
|
31
|
+
return canonical(a) === canonical(b);
|
|
32
|
+
}
|
|
33
|
+
//# sourceMappingURL=canonical.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"canonical.js","sourceRoot":"","sources":["../src/canonical.ts"],"names":[],"mappings":"AAAA,sCAAsC;AACtC,EAAE;AACF,qGAAqG;AACrG,wEAAwE;AAExE,OAAO,EAAE,KAAK,EAAE,MAAM,YAAY,CAAA;AAClC,OAAO,EAAE,SAAS,EAAE,MAAM,gBAAgB,CAAA;AAC1C,OAAO,EAAE,QAAQ,EAAE,MAAM,EAAE,MAAM,WAAW,CAAA;AAG5C;;;;;;;;;;;;GAYG;AACH,MAAM,UAAU,SAAS,CAAC,UAAgC;IACxD,MAAM,IAAI,GAAG,QAAQ,EAAE,CAAA;IACvB,MAAM,MAAM,GAAG,OAAO,UAAU,KAAK,QAAQ,CAAC,CAAC,CAAC,KAAK,CAAC,UAAU,CAAC,CAAC,CAAC,CAAC,UAAU,CAAA;IAC9E,IAAI,MAAM,CAAC,MAAM,KAAK,MAAM,CAAC,IAAI,EAAE,WAAW,CAAC;QAAE,OAAO,SAAS,CAAC,MAAM,CAAC,CAAA,CAAC,kCAAkC;IAC5G,MAAM,MAAM,GAAG,CAAC,GAAG,MAAM,CAAC,UAAU,CAAC,CAAC,IAAI,CAAC,CAAC,CAAC,CAAC,CAAC,EAAE,CAAC,CAAC,CAAC,EAAE,EAAE,CAAC,CAAC,CAAC,GAAG,CAAC,CAAC,CAAC,CAAC,CAAC,CAAC,CAAC,CAAC,CAAC,CAAC,GAAG,CAAC,CAAC,CAAC,CAAC,CAAC,CAAC,CAAC,CAAC,CAAC,CAAC,CAAC,CAAA;IACtF,OAAO,SAAS,CAAC,EAAE,GAAG,MAAM,EAAE,UAAU,EAAE,MAAM,EAAE,CAAC,CAAA;AACrD,CAAC;AAED,8GAA8G;AAC9G,MAAM,UAAU,cAAc,CAAC,CAAuB,EAAE,CAAuB;IAC7E,OAAO,SAAS,CAAC,CAAC,CAAC,KAAK,SAAS,CAAC,CAAC,CAAC,CAAA;AACtC,CAAC"}
|
package/dist/digest.js
CHANGED
|
@@ -3,7 +3,7 @@
|
|
|
3
3
|
// sha256 over the UTF-8 bytes of the joined identifier strings, per `spec.digest`: declared order,
|
|
4
4
|
// no deduplication, and a refusal for a member that carries the join character — without it two
|
|
5
5
|
// different sequences could share one digest, and the envelope invariant would admit both.
|
|
6
|
-
import {
|
|
6
|
+
import { hashHex } from "./hash.js";
|
|
7
7
|
import { DigestError } from "./errors.js";
|
|
8
8
|
import { FIELD } from "./grammar.js";
|
|
9
9
|
import { loadSpec, part } from "./spec.js";
|
|
@@ -20,7 +20,7 @@ export function digest(members) {
|
|
|
20
20
|
}
|
|
21
21
|
}
|
|
22
22
|
const joined = snapshot.join(spec.digest.join);
|
|
23
|
-
const hex =
|
|
23
|
+
const hex = hashHex(spec.digest.algorithm, spec.digest.encoding, joined);
|
|
24
24
|
return `${spec.digest.algorithm}${FIELD}${hex}`;
|
|
25
25
|
}
|
|
26
26
|
//# sourceMappingURL=digest.js.map
|
package/dist/digest.js.map
CHANGED
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"digest.js","sourceRoot":"","sources":["../src/digest.ts"],"names":[],"mappings":"AAAA,sCAAsC;AACtC,EAAE;AACF,mGAAmG;AACnG,gGAAgG;AAChG,2FAA2F;AAE3F,OAAO,EAAE,
|
|
1
|
+
{"version":3,"file":"digest.js","sourceRoot":"","sources":["../src/digest.ts"],"names":[],"mappings":"AAAA,sCAAsC;AACtC,EAAE;AACF,mGAAmG;AACnG,gGAAgG;AAChG,2FAA2F;AAE3F,OAAO,EAAE,OAAO,EAAE,MAAM,WAAW,CAAA;AACnC,OAAO,EAAE,WAAW,EAAE,MAAM,aAAa,CAAA;AACzC,OAAO,EAAE,KAAK,EAAE,MAAM,cAAc,CAAA;AACpC,OAAO,EAAE,QAAQ,EAAE,IAAI,EAAE,MAAM,WAAW,CAAA;AAE1C,2EAA2E;AAC3E,MAAM,UAAU,MAAM,CAAC,OAA0B;IAC/C,MAAM,IAAI,GAAG,QAAQ,EAAE,CAAA;IACvB,IAAI,CAAC,KAAK,CAAC,OAAO,CAAC,OAAO,CAAC,EAAE,CAAC;QAC5B,MAAM,IAAI,WAAW,CAAC,IAAI,CAAC,IAAI,EAAE,QAAQ,CAAC,EAAE,qCAAqC,CAAC,CAAA;IACpF,CAAC;IACD,MAAM,QAAQ,GAAG,KAAK,CAAC,IAAI,CAAC,OAAO,CAAC,CAAA;IACpC,KAAK,MAAM,MAAM,IAAI,QAAQ,EAAE,CAAC;QAC9B,IAAI,OAAO,MAAM,KAAK,QAAQ,IAAI,MAAM,CAAC,QAAQ,CAAC,IAAI,CAAC,MAAM,CAAC,IAAI,CAAC,EAAE,CAAC;YACpE,MAAM,IAAI,WAAW,CAAC,IAAI,CAAC,IAAI,EAAE,QAAQ,CAAC,EAAE,kEAAkE,CAAC,CAAA;QACjH,CAAC;IACH,CAAC;IACD,MAAM,MAAM,GAAG,QAAQ,CAAC,IAAI,CAAC,IAAI,CAAC,MAAM,CAAC,IAAI,CAAC,CAAA;IAC9C,MAAM,GAAG,GAAG,OAAO,CAAC,IAAI,CAAC,MAAM,CAAC,SAAS,EAAE,IAAI,CAAC,MAAM,CAAC,QAAQ,EAAE,MAAM,CAAC,CAAA;IACxE,OAAO,GAAG,IAAI,CAAC,MAAM,CAAC,SAAS,GAAG,KAAK,GAAG,GAAG,EAAE,CAAA;AACjD,CAAC"}
|
|
@@ -0,0 +1,9 @@
|
|
|
1
|
+
import type { HashHex } from "./hash.ts";
|
|
2
|
+
/**
|
|
3
|
+
* sha256 only, and it refuses the rest by name.
|
|
4
|
+
*
|
|
5
|
+
* Refusing rather than substituting is the same contract `status()` and `part()` hold: the code names
|
|
6
|
+
* what it can do, the spec owns the list, and a spec naming something this build cannot honour is a
|
|
7
|
+
* version mismatch reported as one — never a different answer computed quietly.
|
|
8
|
+
*/
|
|
9
|
+
export declare const hashHexBrowser: HashHex;
|
|
@@ -0,0 +1,27 @@
|
|
|
1
|
+
// SPDX-License-Identifier: Apache-2.0
|
|
2
|
+
//
|
|
3
|
+
// The browser hash: @noble/hashes, a pure-JavaScript sha256 with no dependencies and no native
|
|
4
|
+
// runtime. Installed by index.browser.ts; index.ts installs hash.node.ts instead and never imports
|
|
5
|
+
// this file, so neither implementation reaches the other's build.
|
|
6
|
+
import { sha256 } from "@noble/hashes/sha2.js";
|
|
7
|
+
import { bytesToHex, utf8ToBytes } from "@noble/hashes/utils.js";
|
|
8
|
+
import { SpecVersionError } from "./spec.js";
|
|
9
|
+
/** The encodings this implementation can read a member string as. Both spellings of UTF-8 are accepted. */
|
|
10
|
+
const ENCODINGS = new Set(["utf-8", "utf8"]);
|
|
11
|
+
/**
|
|
12
|
+
* sha256 only, and it refuses the rest by name.
|
|
13
|
+
*
|
|
14
|
+
* Refusing rather than substituting is the same contract `status()` and `part()` hold: the code names
|
|
15
|
+
* what it can do, the spec owns the list, and a spec naming something this build cannot honour is a
|
|
16
|
+
* version mismatch reported as one — never a different answer computed quietly.
|
|
17
|
+
*/
|
|
18
|
+
export const hashHexBrowser = (algorithm, encoding, text) => {
|
|
19
|
+
if (algorithm !== "sha256") {
|
|
20
|
+
throw new SpecVersionError(`the browser build implements sha256 only; spec.digest.algorithm declares "${algorithm}"`);
|
|
21
|
+
}
|
|
22
|
+
if (!ENCODINGS.has(encoding)) {
|
|
23
|
+
throw new SpecVersionError(`the browser build reads members as UTF-8 only; spec.digest.encoding declares "${encoding}"`);
|
|
24
|
+
}
|
|
25
|
+
return bytesToHex(sha256(utf8ToBytes(text)));
|
|
26
|
+
};
|
|
27
|
+
//# sourceMappingURL=hash.browser.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"hash.browser.js","sourceRoot":"","sources":["../src/hash.browser.ts"],"names":[],"mappings":"AAAA,sCAAsC;AACtC,EAAE;AACF,+FAA+F;AAC/F,mGAAmG;AACnG,kEAAkE;AAElE,OAAO,EAAE,MAAM,EAAE,MAAM,uBAAuB,CAAA;AAC9C,OAAO,EAAE,UAAU,EAAE,WAAW,EAAE,MAAM,wBAAwB,CAAA;AAEhE,OAAO,EAAE,gBAAgB,EAAE,MAAM,WAAW,CAAA;AAE5C,2GAA2G;AAC3G,MAAM,SAAS,GAAG,IAAI,GAAG,CAAC,CAAC,OAAO,EAAE,MAAM,CAAC,CAAC,CAAA;AAE5C;;;;;;GAMG;AACH,MAAM,CAAC,MAAM,cAAc,GAAY,CAAC,SAAS,EAAE,QAAQ,EAAE,IAAI,EAAE,EAAE;IACnE,IAAI,SAAS,KAAK,QAAQ,EAAE,CAAC;QAC3B,MAAM,IAAI,gBAAgB,CACxB,6EAA6E,SAAS,GAAG,CAC1F,CAAA;IACH,CAAC;IACD,IAAI,CAAC,SAAS,CAAC,GAAG,CAAC,QAAQ,CAAC,EAAE,CAAC;QAC7B,MAAM,IAAI,gBAAgB,CACxB,iFAAiF,QAAQ,GAAG,CAC7F,CAAA;IACH,CAAC;IACD,OAAO,UAAU,CAAC,MAAM,CAAC,WAAW,CAAC,IAAI,CAAC,CAAC,CAAC,CAAA;AAC9C,CAAC,CAAA"}
|
package/dist/hash.d.ts
ADDED
|
@@ -0,0 +1,10 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Hashes `text` under a spec-declared algorithm and encoding, returning lowercase hex. The algorithm
|
|
3
|
+
* and encoding are passed through from `spec.digest` rather than fixed here: the spec owns the choice,
|
|
4
|
+
* and an implementation that cannot honour it says so instead of substituting one it can.
|
|
5
|
+
*/
|
|
6
|
+
export type HashHex = (algorithm: string, encoding: string, text: string) => string;
|
|
7
|
+
/** Installs the hash `digest()` uses. Called exactly once, by an entry point. */
|
|
8
|
+
export declare function installHash(hash: HashHex): void;
|
|
9
|
+
/** Hashes through the installed implementation. */
|
|
10
|
+
export declare function hashHex(algorithm: string, encoding: string, text: string): string;
|
package/dist/hash.js
ADDED
|
@@ -0,0 +1,34 @@
|
|
|
1
|
+
// SPDX-License-Identifier: Apache-2.0
|
|
2
|
+
//
|
|
3
|
+
// The seam through which the digest's hash function reaches this package, for the same reason
|
|
4
|
+
// spec.ts has one: `node:crypto` is not available in a browser, and `digest()` is synchronous.
|
|
5
|
+
//
|
|
6
|
+
// WHY NOT WebCrypto. `crypto.subtle.digest` is the only hash the web platform publishes and it is
|
|
7
|
+
// asynchronous, so using it would make `digest()` and `validateEnvelope()` return promises — a change
|
|
8
|
+
// to the public API, which Plan-004 puts out of scope, and one that would leave the two builds with
|
|
9
|
+
// different surfaces to compare. Blocking on it is not available either: `Atomics.wait` throws on a
|
|
10
|
+
// browser's main thread by specification. So the browser build carries a synchronous implementation
|
|
11
|
+
// instead (hash.browser.ts, @noble/hashes), and Node keeps `node:crypto` (hash.node.ts).
|
|
12
|
+
//
|
|
13
|
+
// WHERE THE TWO CAN DIVERGE, SAID OUT LOUD. The algorithm is spec data (`spec.digest.algorithm`), and
|
|
14
|
+
// the two implementations do not honour the same set: Node honours everything `node:crypto` does,
|
|
15
|
+
// the browser honours sha256 alone. A specification that moved to another algorithm would therefore
|
|
16
|
+
// keep working in Node and throw a SpecVersionError in the browser. That is disagreement discovered
|
|
17
|
+
// loudly, at the first call, naming the algorithm — the failure mode Plan-004 rejects is the silent
|
|
18
|
+
// one, where both builds answer and answer differently.
|
|
19
|
+
import { SpecVersionError } from "./spec.js";
|
|
20
|
+
let implementation;
|
|
21
|
+
/** Installs the hash `digest()` uses. Called exactly once, by an entry point. */
|
|
22
|
+
export function installHash(hash) {
|
|
23
|
+
implementation = hash;
|
|
24
|
+
}
|
|
25
|
+
/** Hashes through the installed implementation. */
|
|
26
|
+
export function hashHex(algorithm, encoding, text) {
|
|
27
|
+
if (!implementation) {
|
|
28
|
+
// Same unreachable-by-accident case as loadSpec(): `exports` publishes no deep path into this
|
|
29
|
+
// package, so arriving here means a module was imported directly rather than an entry point.
|
|
30
|
+
throw new SpecVersionError("no hash implementation is installed; import this package through its entry point rather than a module inside it");
|
|
31
|
+
}
|
|
32
|
+
return implementation(algorithm, encoding, text);
|
|
33
|
+
}
|
|
34
|
+
//# sourceMappingURL=hash.js.map
|
package/dist/hash.js.map
ADDED
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"hash.js","sourceRoot":"","sources":["../src/hash.ts"],"names":[],"mappings":"AAAA,sCAAsC;AACtC,EAAE;AACF,8FAA8F;AAC9F,+FAA+F;AAC/F,EAAE;AACF,kGAAkG;AAClG,sGAAsG;AACtG,oGAAoG;AACpG,oGAAoG;AACpG,oGAAoG;AACpG,yFAAyF;AACzF,EAAE;AACF,sGAAsG;AACtG,kGAAkG;AAClG,oGAAoG;AACpG,oGAAoG;AACpG,oGAAoG;AACpG,wDAAwD;AAExD,OAAO,EAAE,gBAAgB,EAAE,MAAM,WAAW,CAAA;AAS5C,IAAI,cAAmC,CAAA;AAEvC,iFAAiF;AACjF,MAAM,UAAU,WAAW,CAAC,IAAa;IACvC,cAAc,GAAG,IAAI,CAAA;AACvB,CAAC;AAED,mDAAmD;AACnD,MAAM,UAAU,OAAO,CAAC,SAAiB,EAAE,QAAgB,EAAE,IAAY;IACvE,IAAI,CAAC,cAAc,EAAE,CAAC;QACpB,8FAA8F;QAC9F,6FAA6F;QAC7F,MAAM,IAAI,gBAAgB,CACxB,iHAAiH,CAClH,CAAA;IACH,CAAC;IACD,OAAO,cAAc,CAAC,SAAS,EAAE,QAAQ,EAAE,IAAI,CAAC,CAAA;AAClD,CAAC"}
|
|
@@ -0,0 +1,7 @@
|
|
|
1
|
+
import type { HashHex } from "./hash.ts";
|
|
2
|
+
/**
|
|
3
|
+
* Whatever `node:crypto` honours, honoured. No allow-list of algorithms is imposed here: this is the
|
|
4
|
+
* behaviour the package already had, and narrowing it would refuse specs Node can serve in order to
|
|
5
|
+
* match a limit that belongs to the other build.
|
|
6
|
+
*/
|
|
7
|
+
export declare const hashHexNode: HashHex;
|
|
@@ -0,0 +1,12 @@
|
|
|
1
|
+
// SPDX-License-Identifier: Apache-2.0
|
|
2
|
+
//
|
|
3
|
+
// The Node hash: `node:crypto`, exactly as digest.ts called it before the seam existed. Installed by
|
|
4
|
+
// index.ts; index.browser.ts installs hash.browser.ts instead and never imports this file.
|
|
5
|
+
import { createHash } from "node:crypto";
|
|
6
|
+
/**
|
|
7
|
+
* Whatever `node:crypto` honours, honoured. No allow-list of algorithms is imposed here: this is the
|
|
8
|
+
* behaviour the package already had, and narrowing it would refuse specs Node can serve in order to
|
|
9
|
+
* match a limit that belongs to the other build.
|
|
10
|
+
*/
|
|
11
|
+
export const hashHexNode = (algorithm, encoding, text) => createHash(algorithm).update(text, encoding).digest("hex");
|
|
12
|
+
//# sourceMappingURL=hash.node.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"hash.node.js","sourceRoot":"","sources":["../src/hash.node.ts"],"names":[],"mappings":"AAAA,sCAAsC;AACtC,EAAE;AACF,qGAAqG;AACrG,2FAA2F;AAE3F,OAAO,EAAE,UAAU,EAAE,MAAM,aAAa,CAAA;AAGxC;;;;GAIG;AACH,MAAM,CAAC,MAAM,WAAW,GAAY,CAAC,SAAS,EAAE,QAAQ,EAAE,IAAI,EAAE,EAAE,CAChE,UAAU,CAAC,SAAS,CAAC,CAAC,MAAM,CAAC,IAAI,EAAE,QAA0B,CAAC,CAAC,MAAM,CAAC,KAAK,CAAC,CAAA"}
|
|
@@ -0,0 +1,11 @@
|
|
|
1
|
+
export { build } from "./build.ts";
|
|
2
|
+
export { canonical, sameIdentifier } from "./canonical.ts";
|
|
3
|
+
export { digest } from "./digest.ts";
|
|
4
|
+
export { validateEnvelope } from "./envelope.ts";
|
|
5
|
+
export { BuildError, DigestError, RefIdError, SerialiseError } from "./errors.ts";
|
|
6
|
+
export { parse } from "./parse.ts";
|
|
7
|
+
export { covers, samePackage } from "./relations.ts";
|
|
8
|
+
export { serialise } from "./serialise.ts";
|
|
9
|
+
export { canonicalise, loadSpec, SpecIntegrityError, SpecVersionError, type RefIdSpec } from "./spec.ts";
|
|
10
|
+
export { SPEC_DIGEST } from "./spec.browser.ts";
|
|
11
|
+
export type { BuildParts, EnvelopeResult, NestedQualifierValue, Pair, ParsedFragment, ParseResult, ParseStatus } from "./types.ts";
|
|
@@ -0,0 +1,41 @@
|
|
|
1
|
+
// SPDX-License-Identifier: Apache-2.0
|
|
2
|
+
//
|
|
3
|
+
// The browser entry point (Plan-004, Track 2). Served through the `browser` condition of this
|
|
4
|
+
// package's `exports`, so a bundler picks it up and a consumer changes nothing.
|
|
5
|
+
//
|
|
6
|
+
// It differs from index.ts in exactly two lines — the spec source and the hash it installs — and in
|
|
7
|
+
// one export it does not carry (`loadSpecFrom`, which takes a directory to read from). What it buys
|
|
8
|
+
// is a module graph that reaches no Node builtin and nothing that resolves a path against the
|
|
9
|
+
// module's own location, which scripts/check-browser-purity.mjs proves over the built closure rather
|
|
10
|
+
// than by reading.
|
|
11
|
+
//
|
|
12
|
+
// THE TWO NAMES ARE SPELLED OUT NOWHERE IN THIS FILE, ON PURPOSE. Plan-004's success criteria grep
|
|
13
|
+
// this emitted module for the builtin prefix and for the meta-URL expression, and a comment carrying
|
|
14
|
+
// either literal turns that criterion red while describing their absence. The honest check is the
|
|
15
|
+
// closure walk, which reads code rather than prose; this file simply refuses to lie to the grep.
|
|
16
|
+
//
|
|
17
|
+
// The spec here is a constant that scripts/gen-spec.mjs compiled in, having first verified
|
|
18
|
+
// spec/ref-id.json against its sidecar. Nothing recomputes that digest at import time: it would hash
|
|
19
|
+
// a constant against a digest compiled from it in the same build, a check that cannot fail. The
|
|
20
|
+
// guarantee lives in the generator and in the staleness guard that regenerates and diffs
|
|
21
|
+
// (Plan-004's Decision Log).
|
|
22
|
+
import { installHash } from "./hash.js";
|
|
23
|
+
import { hashHexBrowser } from "./hash.browser.js";
|
|
24
|
+
import { installSpecSource } from "./spec.js";
|
|
25
|
+
import { SPEC } from "./spec.browser.js";
|
|
26
|
+
installSpecSource(() => SPEC);
|
|
27
|
+
installHash(hashHexBrowser);
|
|
28
|
+
export { build } from "./build.js";
|
|
29
|
+
export { canonical, sameIdentifier } from "./canonical.js";
|
|
30
|
+
export { digest } from "./digest.js";
|
|
31
|
+
export { validateEnvelope } from "./envelope.js";
|
|
32
|
+
export { BuildError, DigestError, RefIdError, SerialiseError } from "./errors.js";
|
|
33
|
+
export { parse } from "./parse.js";
|
|
34
|
+
export { covers, samePackage } from "./relations.js";
|
|
35
|
+
export { serialise } from "./serialise.js";
|
|
36
|
+
export { canonicalise, loadSpec, SpecIntegrityError, SpecVersionError } from "./spec.js";
|
|
37
|
+
// The digest spec/ref-id.json carried when this build's constant was generated. A statement about
|
|
38
|
+
// provenance, not a verification — spec.browser.ts says so at length, and re-exporting it here does
|
|
39
|
+
// not make it one.
|
|
40
|
+
export { SPEC_DIGEST } from "./spec.browser.js";
|
|
41
|
+
//# sourceMappingURL=index.browser.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"index.browser.js","sourceRoot":"","sources":["../src/index.browser.ts"],"names":[],"mappings":"AAAA,sCAAsC;AACtC,EAAE;AACF,8FAA8F;AAC9F,gFAAgF;AAChF,EAAE;AACF,oGAAoG;AACpG,oGAAoG;AACpG,8FAA8F;AAC9F,qGAAqG;AACrG,mBAAmB;AACnB,EAAE;AACF,mGAAmG;AACnG,qGAAqG;AACrG,kGAAkG;AAClG,iGAAiG;AACjG,EAAE;AACF,2FAA2F;AAC3F,qGAAqG;AACrG,gGAAgG;AAChG,yFAAyF;AACzF,6BAA6B;AAE7B,OAAO,EAAE,WAAW,EAAE,MAAM,WAAW,CAAA;AACvC,OAAO,EAAE,cAAc,EAAE,MAAM,mBAAmB,CAAA;AAClD,OAAO,EAAE,iBAAiB,EAAE,MAAM,WAAW,CAAA;AAC7C,OAAO,EAAE,IAAI,EAAE,MAAM,mBAAmB,CAAA;AAExC,iBAAiB,CAAC,GAAG,EAAE,CAAC,IAAI,CAAC,CAAA;AAC7B,WAAW,CAAC,cAAc,CAAC,CAAA;AAE3B,OAAO,EAAE,KAAK,EAAE,MAAM,YAAY,CAAA;AAClC,OAAO,EAAE,SAAS,EAAE,cAAc,EAAE,MAAM,gBAAgB,CAAA;AAC1D,OAAO,EAAE,MAAM,EAAE,MAAM,aAAa,CAAA;AACpC,OAAO,EAAE,gBAAgB,EAAE,MAAM,eAAe,CAAA;AAChD,OAAO,EAAE,UAAU,EAAE,WAAW,EAAE,UAAU,EAAE,cAAc,EAAE,MAAM,aAAa,CAAA;AACjF,OAAO,EAAE,KAAK,EAAE,MAAM,YAAY,CAAA;AAClC,OAAO,EAAE,MAAM,EAAE,WAAW,EAAE,MAAM,gBAAgB,CAAA;AACpD,OAAO,EAAE,SAAS,EAAE,MAAM,gBAAgB,CAAA;AAC1C,OAAO,EAAE,YAAY,EAAE,QAAQ,EAAE,kBAAkB,EAAE,gBAAgB,EAAkB,MAAM,WAAW,CAAA;AACxG,kGAAkG;AAClG,oGAAoG;AACpG,mBAAmB;AACnB,OAAO,EAAE,WAAW,EAAE,MAAM,mBAAmB,CAAA"}
|
package/dist/index.d.ts
CHANGED
|
@@ -1,8 +1,11 @@
|
|
|
1
1
|
export { build } from "./build.ts";
|
|
2
|
+
export { canonical, sameIdentifier } from "./canonical.ts";
|
|
2
3
|
export { digest } from "./digest.ts";
|
|
3
4
|
export { validateEnvelope } from "./envelope.ts";
|
|
4
5
|
export { BuildError, DigestError, RefIdError, SerialiseError } from "./errors.ts";
|
|
5
6
|
export { parse } from "./parse.ts";
|
|
7
|
+
export { covers, samePackage } from "./relations.ts";
|
|
6
8
|
export { serialise } from "./serialise.ts";
|
|
7
|
-
export { canonicalise, loadSpec,
|
|
9
|
+
export { canonicalise, loadSpec, SpecIntegrityError, SpecVersionError, type RefIdSpec } from "./spec.ts";
|
|
10
|
+
export { loadSpecFrom } from "./spec.node.ts";
|
|
8
11
|
export type { BuildParts, EnvelopeResult, NestedQualifierValue, Pair, ParsedFragment, ParseResult, ParseStatus } from "./types.ts";
|
package/dist/index.js
CHANGED
|
@@ -1,9 +1,23 @@
|
|
|
1
1
|
// SPDX-License-Identifier: Apache-2.0
|
|
2
|
+
//
|
|
3
|
+
// The Node entry point. It differs from index.browser.ts in exactly two lines: which spec source and
|
|
4
|
+
// which hash it installs. Everything below is the same public surface, served by the same modules.
|
|
5
|
+
import { installHash } from "./hash.js";
|
|
6
|
+
import { hashHexNode } from "./hash.node.js";
|
|
7
|
+
import { installSpecSource } from "./spec.js";
|
|
8
|
+
import { loadSpecFrom, loadSpecFromDisk } from "./spec.node.js";
|
|
9
|
+
installSpecSource(loadSpecFromDisk);
|
|
10
|
+
installHash(hashHexNode);
|
|
2
11
|
export { build } from "./build.js";
|
|
12
|
+
export { canonical, sameIdentifier } from "./canonical.js";
|
|
3
13
|
export { digest } from "./digest.js";
|
|
4
14
|
export { validateEnvelope } from "./envelope.js";
|
|
5
15
|
export { BuildError, DigestError, RefIdError, SerialiseError } from "./errors.js";
|
|
6
16
|
export { parse } from "./parse.js";
|
|
17
|
+
export { covers, samePackage } from "./relations.js";
|
|
7
18
|
export { serialise } from "./serialise.js";
|
|
8
|
-
export { canonicalise, loadSpec,
|
|
19
|
+
export { canonicalise, loadSpec, SpecIntegrityError, SpecVersionError } from "./spec.js";
|
|
20
|
+
// Disk-only, and therefore the one export the browser build does not carry: it takes a directory to
|
|
21
|
+
// read a spec + sidecar pair from, which is a thing a browser does not have.
|
|
22
|
+
export { loadSpecFrom } from "./spec.node.js";
|
|
9
23
|
//# sourceMappingURL=index.js.map
|
package/dist/index.js.map
CHANGED
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"index.js","sourceRoot":"","sources":["../src/index.ts"],"names":[],"mappings":"AAAA,sCAAsC;
|
|
1
|
+
{"version":3,"file":"index.js","sourceRoot":"","sources":["../src/index.ts"],"names":[],"mappings":"AAAA,sCAAsC;AACtC,EAAE;AACF,qGAAqG;AACrG,mGAAmG;AAEnG,OAAO,EAAE,WAAW,EAAE,MAAM,WAAW,CAAA;AACvC,OAAO,EAAE,WAAW,EAAE,MAAM,gBAAgB,CAAA;AAC5C,OAAO,EAAE,iBAAiB,EAAE,MAAM,WAAW,CAAA;AAC7C,OAAO,EAAE,YAAY,EAAE,gBAAgB,EAAE,MAAM,gBAAgB,CAAA;AAE/D,iBAAiB,CAAC,gBAAgB,CAAC,CAAA;AACnC,WAAW,CAAC,WAAW,CAAC,CAAA;AAExB,OAAO,EAAE,KAAK,EAAE,MAAM,YAAY,CAAA;AAClC,OAAO,EAAE,SAAS,EAAE,cAAc,EAAE,MAAM,gBAAgB,CAAA;AAC1D,OAAO,EAAE,MAAM,EAAE,MAAM,aAAa,CAAA;AACpC,OAAO,EAAE,gBAAgB,EAAE,MAAM,eAAe,CAAA;AAChD,OAAO,EAAE,UAAU,EAAE,WAAW,EAAE,UAAU,EAAE,cAAc,EAAE,MAAM,aAAa,CAAA;AACjF,OAAO,EAAE,KAAK,EAAE,MAAM,YAAY,CAAA;AAClC,OAAO,EAAE,MAAM,EAAE,WAAW,EAAE,MAAM,gBAAgB,CAAA;AACpD,OAAO,EAAE,SAAS,EAAE,MAAM,gBAAgB,CAAA;AAC1C,OAAO,EAAE,YAAY,EAAE,QAAQ,EAAE,kBAAkB,EAAE,gBAAgB,EAAkB,MAAM,WAAW,CAAA;AACxG,oGAAoG;AACpG,6EAA6E;AAC7E,OAAO,EAAE,YAAY,EAAE,MAAM,gBAAgB,CAAA"}
|
|
@@ -0,0 +1,29 @@
|
|
|
1
|
+
import type { ParseResult } from "./types.ts";
|
|
2
|
+
/**
|
|
3
|
+
* Whether two identifiers name the same released thing, at whatever version each declares.
|
|
4
|
+
*
|
|
5
|
+
* Symmetric, and version-blind in exactly one place: the locator of a type that declares it carries a
|
|
6
|
+
* version. Everything else still distinguishes — the declared name, its refinements, and every
|
|
7
|
+
* qualifier — because two readings taken at different moments are two readings of one trait rather than
|
|
8
|
+
* one reading, and collapsing them is the defect `when` exists to prevent.
|
|
9
|
+
*
|
|
10
|
+
* A malformed identifier, and one at an identifier version this package does not implement, name nothing
|
|
11
|
+
* here and so are the same as nothing, including themselves.
|
|
12
|
+
*/
|
|
13
|
+
export declare function samePackage(a: string | ParseResult, b: string | ParseResult): boolean;
|
|
14
|
+
/**
|
|
15
|
+
* Whether the first identifier is the second with less declared — the general covering the specific.
|
|
16
|
+
*
|
|
17
|
+
* **Asymmetric, and the direction is the whole point.** `pkg:npm/x` covers `pkg:npm/x@1.0.0`, and
|
|
18
|
+
* `pkg:npm/x@1.0.0` does not cover `pkg:npm/x`: a name that declares no version stands for every version
|
|
19
|
+
* of itself, while a name that declares one stands for that version alone. The same holds for the
|
|
20
|
+
* declared name, for each of its refinements, and for each qualifier — what the first leaves unsaid, the
|
|
21
|
+
* second may say freely; what the first says, the second must say identically.
|
|
22
|
+
*
|
|
23
|
+
* This is what makes a partial identifier a query. A store keyed by identifier answers "every reading of
|
|
24
|
+
* this trait" by asking which of its keys a trait-only identifier covers, with no query language at all.
|
|
25
|
+
*
|
|
26
|
+
* Every identifier this package vouches for covers itself; one it does not — malformed, or at an
|
|
27
|
+
* identifier version it does not implement — covers nothing, itself included.
|
|
28
|
+
*/
|
|
29
|
+
export declare function covers(general: string | ParseResult, specific: string | ParseResult): boolean;
|
|
@@ -0,0 +1,135 @@
|
|
|
1
|
+
// SPDX-License-Identifier: Apache-2.0
|
|
2
|
+
//
|
|
3
|
+
// The two questions equality cannot answer. `sameIdentifier` says whether two strings name one thing,
|
|
4
|
+
// which is what a digest and an envelope need and is deliberately strict. A store wants two other
|
|
5
|
+
// answers: whether two identifiers name the same released thing at different versions, and whether a
|
|
6
|
+
// partial identifier stands for a whole family of complete ones. Neither is equality, and neither can be
|
|
7
|
+
// expressed by relaxing it, because they disagree with each other on direction.
|
|
8
|
+
import { parse } from "./parse.js";
|
|
9
|
+
import { loadSpec, status } from "./spec.js";
|
|
10
|
+
/**
|
|
11
|
+
* The locator without its version, and the version it carried.
|
|
12
|
+
*
|
|
13
|
+
* **Whether a locator carries a version at all is the type's business, not this function's**, so it is
|
|
14
|
+
* read from `dispatch.<type>.versionTail` rather than guessed from the punctuation. An `@` means a
|
|
15
|
+
* released version in a Package URL and a host path; it separates a mailbox from its domain, keys a
|
|
16
|
+
* quantisation on a served model, and appears in neither a telephone number nor an article number.
|
|
17
|
+
* Guessing reported two different mailboxes as one thing.
|
|
18
|
+
*
|
|
19
|
+
* Within a type that does carry one, an `@` that opens a segment — preceded by `/`, or first — belongs
|
|
20
|
+
* to a namespace, so `npm/@acme/x` has no version. An `@` inside a segment closes the name: the version
|
|
21
|
+
* runs from it to the next `/`, and whatever follows that `/` is a path inside the named thing.
|
|
22
|
+
*
|
|
23
|
+
* **The path stays in the stem, and only the version leaves it.** The stem answers *which thing*, and a
|
|
24
|
+
* file at two releases is one file — so `npm/x@1.0.0/docs/guide.md` and `npm/x@2.0.0/docs/guide.md`
|
|
25
|
+
* share the stem `npm/x/docs/guide.md` and are the same package, while two files in one release do not.
|
|
26
|
+
*/
|
|
27
|
+
function split(spec, type, locator) {
|
|
28
|
+
if (!spec.dispatch[type]?.versionTail)
|
|
29
|
+
return { stem: locator };
|
|
30
|
+
for (let index = 1; index < locator.length; index += 1) {
|
|
31
|
+
if (locator[index] !== "@" || locator[index - 1] === "/")
|
|
32
|
+
continue;
|
|
33
|
+
const slash = locator.indexOf("/", index);
|
|
34
|
+
if (slash < 0)
|
|
35
|
+
return { stem: locator.slice(0, index), version: locator.slice(index + 1) };
|
|
36
|
+
return { stem: locator.slice(0, index) + locator.slice(slash), version: locator.slice(index + 1, slash) };
|
|
37
|
+
}
|
|
38
|
+
return { stem: locator };
|
|
39
|
+
}
|
|
40
|
+
/**
|
|
41
|
+
* Whether the general identifier's stem reaches the specific one's.
|
|
42
|
+
*
|
|
43
|
+
* Equal stems name one thing. Otherwise the general one covers the specific when its stem is a whole
|
|
44
|
+
* **segment** prefix of it — `acme-tools` reaching `acme-tools/docs/guide.md`. The segment boundary is
|
|
45
|
+
* the whole of the rule: a bare string prefix would make `acme-tools` cover `acme-tools-extra`, two
|
|
46
|
+
* corpora that share nothing but their first characters.
|
|
47
|
+
*/
|
|
48
|
+
function stemReaches(general, specific) {
|
|
49
|
+
return general === specific || specific.startsWith(`${general}/`);
|
|
50
|
+
}
|
|
51
|
+
/**
|
|
52
|
+
* The identifier, when this package vouches for how it was decomposed.
|
|
53
|
+
*
|
|
54
|
+
* `ok` and `uncovered` both carry a decomposition this grammar produced — an uncovered type parses, and
|
|
55
|
+
* only its locator's own grammar is unknown. `malformed` has no decomposition to compare, and
|
|
56
|
+
* `unsupported` has one produced by the wrong grammar: a later identifier version is minted for a change
|
|
57
|
+
* to normalisation, percent-encoding, separators or shape, so its parts read through this version's
|
|
58
|
+
* expression are not reliably its parts.
|
|
59
|
+
*/
|
|
60
|
+
function read(identifier) {
|
|
61
|
+
const spec = loadSpec();
|
|
62
|
+
const parsed = typeof identifier === "string" ? parse(identifier) : identifier;
|
|
63
|
+
const usable = [status(spec, "ok"), status(spec, "uncovered")];
|
|
64
|
+
return usable.includes(parsed.status) ? parsed : undefined;
|
|
65
|
+
}
|
|
66
|
+
/** Whether every pair the first declares appears identically in the second. */
|
|
67
|
+
function subsumes(theirs, mine) {
|
|
68
|
+
const map = new Map(theirs);
|
|
69
|
+
return mine.every(([key, value]) => map.get(key) === value);
|
|
70
|
+
}
|
|
71
|
+
/** Whether two pair lists hold the same pairs, order aside. */
|
|
72
|
+
function equal(a, b) {
|
|
73
|
+
return a.length === b.length && subsumes(b, a);
|
|
74
|
+
}
|
|
75
|
+
/**
|
|
76
|
+
* Whether two identifiers name the same released thing, at whatever version each declares.
|
|
77
|
+
*
|
|
78
|
+
* Symmetric, and version-blind in exactly one place: the locator of a type that declares it carries a
|
|
79
|
+
* version. Everything else still distinguishes — the declared name, its refinements, and every
|
|
80
|
+
* qualifier — because two readings taken at different moments are two readings of one trait rather than
|
|
81
|
+
* one reading, and collapsing them is the defect `when` exists to prevent.
|
|
82
|
+
*
|
|
83
|
+
* A malformed identifier, and one at an identifier version this package does not implement, name nothing
|
|
84
|
+
* here and so are the same as nothing, including themselves.
|
|
85
|
+
*/
|
|
86
|
+
export function samePackage(a, b) {
|
|
87
|
+
const spec = loadSpec();
|
|
88
|
+
const [x, y] = [read(a), read(b)];
|
|
89
|
+
if (!x || !y)
|
|
90
|
+
return false;
|
|
91
|
+
if (x.type !== y.type || x.version !== y.version)
|
|
92
|
+
return false;
|
|
93
|
+
if (split(spec, x.type, x.locator).stem !== split(spec, y.type, y.locator).stem)
|
|
94
|
+
return false;
|
|
95
|
+
if ((x.fragment?.path ?? null) !== (y.fragment?.path ?? null))
|
|
96
|
+
return false;
|
|
97
|
+
if (!equal(x.fragment?.refinements ?? [], y.fragment?.refinements ?? []))
|
|
98
|
+
return false;
|
|
99
|
+
return equal(x.qualifiers, y.qualifiers);
|
|
100
|
+
}
|
|
101
|
+
/**
|
|
102
|
+
* Whether the first identifier is the second with less declared — the general covering the specific.
|
|
103
|
+
*
|
|
104
|
+
* **Asymmetric, and the direction is the whole point.** `pkg:npm/x` covers `pkg:npm/x@1.0.0`, and
|
|
105
|
+
* `pkg:npm/x@1.0.0` does not cover `pkg:npm/x`: a name that declares no version stands for every version
|
|
106
|
+
* of itself, while a name that declares one stands for that version alone. The same holds for the
|
|
107
|
+
* declared name, for each of its refinements, and for each qualifier — what the first leaves unsaid, the
|
|
108
|
+
* second may say freely; what the first says, the second must say identically.
|
|
109
|
+
*
|
|
110
|
+
* This is what makes a partial identifier a query. A store keyed by identifier answers "every reading of
|
|
111
|
+
* this trait" by asking which of its keys a trait-only identifier covers, with no query language at all.
|
|
112
|
+
*
|
|
113
|
+
* Every identifier this package vouches for covers itself; one it does not — malformed, or at an
|
|
114
|
+
* identifier version it does not implement — covers nothing, itself included.
|
|
115
|
+
*/
|
|
116
|
+
export function covers(general, specific) {
|
|
117
|
+
const spec = loadSpec();
|
|
118
|
+
const [x, y] = [read(general), read(specific)];
|
|
119
|
+
if (!x || !y)
|
|
120
|
+
return false;
|
|
121
|
+
if (x.type !== y.type || x.version !== y.version)
|
|
122
|
+
return false;
|
|
123
|
+
const [gen, spe] = [split(spec, x.type, x.locator), split(spec, y.type, y.locator)];
|
|
124
|
+
if (!stemReaches(gen.stem, spe.stem))
|
|
125
|
+
return false;
|
|
126
|
+
if (gen.version !== undefined && gen.version !== spe.version)
|
|
127
|
+
return false;
|
|
128
|
+
const path = x.fragment?.path;
|
|
129
|
+
if (path !== undefined && path !== y.fragment?.path)
|
|
130
|
+
return false;
|
|
131
|
+
if (!subsumes(y.fragment?.refinements ?? [], x.fragment?.refinements ?? []))
|
|
132
|
+
return false;
|
|
133
|
+
return subsumes(y.qualifiers, x.qualifiers);
|
|
134
|
+
}
|
|
135
|
+
//# sourceMappingURL=relations.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"relations.js","sourceRoot":"","sources":["../src/relations.ts"],"names":[],"mappings":"AAAA,sCAAsC;AACtC,EAAE;AACF,sGAAsG;AACtG,kGAAkG;AAClG,qGAAqG;AACrG,yGAAyG;AACzG,gFAAgF;AAEhF,OAAO,EAAE,KAAK,EAAE,MAAM,YAAY,CAAA;AAClC,OAAO,EAAE,QAAQ,EAAE,MAAM,EAAkB,MAAM,WAAW,CAAA;AAG5D;;;;;;;;;;;;;;;;GAgBG;AACH,SAAS,KAAK,CAAC,IAAe,EAAE,IAAY,EAAE,OAAe;IAC3D,IAAI,CAAC,IAAI,CAAC,QAAQ,CAAC,IAAI,CAAC,EAAE,WAAW;QAAE,OAAO,EAAE,IAAI,EAAE,OAAO,EAAE,CAAA;IAC/D,KAAK,IAAI,KAAK,GAAG,CAAC,EAAE,KAAK,GAAG,OAAO,CAAC,MAAM,EAAE,KAAK,IAAI,CAAC,EAAE,CAAC;QACvD,IAAI,OAAO,CAAC,KAAK,CAAC,KAAK,GAAG,IAAI,OAAO,CAAC,KAAK,GAAG,CAAC,CAAC,KAAK,GAAG;YAAE,SAAQ;QAClE,MAAM,KAAK,GAAG,OAAO,CAAC,OAAO,CAAC,GAAG,EAAE,KAAK,CAAC,CAAA;QACzC,IAAI,KAAK,GAAG,CAAC;YAAE,OAAO,EAAE,IAAI,EAAE,OAAO,CAAC,KAAK,CAAC,CAAC,EAAE,KAAK,CAAC,EAAE,OAAO,EAAE,OAAO,CAAC,KAAK,CAAC,KAAK,GAAG,CAAC,CAAC,EAAE,CAAA;QAC1F,OAAO,EAAE,IAAI,EAAE,OAAO,CAAC,KAAK,CAAC,CAAC,EAAE,KAAK,CAAC,GAAG,OAAO,CAAC,KAAK,CAAC,KAAK,CAAC,EAAE,OAAO,EAAE,OAAO,CAAC,KAAK,CAAC,KAAK,GAAG,CAAC,EAAE,KAAK,CAAC,EAAE,CAAA;IAC3G,CAAC;IACD,OAAO,EAAE,IAAI,EAAE,OAAO,EAAE,CAAA;AAC1B,CAAC;AAED;;;;;;;GAOG;AACH,SAAS,WAAW,CAAC,OAAe,EAAE,QAAgB;IACpD,OAAO,OAAO,KAAK,QAAQ,IAAI,QAAQ,CAAC,UAAU,CAAC,GAAG,OAAO,GAAG,CAAC,CAAA;AACnE,CAAC;AAED;;;;;;;;GAQG;AACH,SAAS,IAAI,CAAC,UAAgC;IAC5C,MAAM,IAAI,GAAG,QAAQ,EAAE,CAAA;IACvB,MAAM,MAAM,GAAG,OAAO,UAAU,KAAK,QAAQ,CAAC,CAAC,CAAC,KAAK,CAAC,UAAU,CAAC,CAAC,CAAC,CAAC,UAAU,CAAA;IAC9E,MAAM,MAAM,GAAG,CAAC,MAAM,CAAC,IAAI,EAAE,IAAI,CAAC,EAAE,MAAM,CAAC,IAAI,EAAE,WAAW,CAAC,CAAC,CAAA;IAC9D,OAAO,MAAM,CAAC,QAAQ,CAAC,MAAM,CAAC,MAAM,CAAC,CAAC,CAAC,CAAC,MAAM,CAAC,CAAC,CAAC,SAAS,CAAA;AAC5D,CAAC;AAED,+EAA+E;AAC/E,SAAS,QAAQ,CAAC,MAAc,EAAE,IAAY;IAC5C,MAAM,GAAG,GAAG,IAAI,GAAG,CAAC,MAAM,CAAC,CAAA;IAC3B,OAAO,IAAI,CAAC,KAAK,CAAC,CAAC,CAAC,GAAG,EAAE,KAAK,CAAC,EAAE,EAAE,CAAC,GAAG,CAAC,GAAG,CAAC,GAAG,CAAC,KAAK,KAAK,CAAC,CAAA;AAC7D,CAAC;AAED,+DAA+D;AAC/D,SAAS,KAAK,CAAC,CAAS,EAAE,CAAS;IACjC,OAAO,CAAC,CAAC,MAAM,KAAK,CAAC,CAAC,MAAM,IAAI,QAAQ,CAAC,CAAC,EAAE,CAAC,CAAC,CAAA;AAChD,CAAC;AAED;;;;;;;;;;GAUG;AACH,MAAM,UAAU,WAAW,CAAC,CAAuB,EAAE,CAAuB;IAC1E,MAAM,IAAI,GAAG,QAAQ,EAAE,CAAA;IACvB,MAAM,CAAC,CAAC,EAAE,CAAC,CAAC,GAAG,CAAC,IAAI,CAAC,CAAC,CAAC,EAAE,IAAI,CAAC,CAAC,CAAC,CAAC,CAAA;IACjC,IAAI,CAAC,CAAC,IAAI,CAAC,CAAC;QAAE,OAAO,KAAK,CAAA;IAC1B,IAAI,CAAC,CAAC,IAAI,KAAK,CAAC,CAAC,IAAI,IAAI,CAAC,CAAC,OAAO,KAAK,CAAC,CAAC,OAAO;QAAE,OAAO,KAAK,CAAA;IAC9D,IAAI,KAAK,CAAC,IAAI,EAAE,CAAC,CAAC,IAAI,EAAE,CAAC,CAAC,OAAO,CAAC,CAAC,IAAI,KAAK,KAAK,CAAC,IAAI,EAAE,CAAC,CAAC,IAAI,EAAE,CAAC,CAAC,OAAO,CAAC,CAAC,IAAI;QAAE,OAAO,KAAK,CAAA;IAC7F,IAAI,CAAC,CAAC,CAAC,QAAQ,EAAE,IAAI,IAAI,IAAI,CAAC,KAAK,CAAC,CAAC,CAAC,QAAQ,EAAE,IAAI,IAAI,IAAI,CAAC;QAAE,OAAO,KAAK,CAAA;IAC3E,IAAI,CAAC,KAAK,CAAC,CAAC,CAAC,QAAQ,EAAE,WAAW,IAAI,EAAE,EAAE,CAAC,CAAC,QAAQ,EAAE,WAAW,IAAI,EAAE,CAAC;QAAE,OAAO,KAAK,CAAA;IACtF,OAAO,KAAK,CAAC,CAAC,CAAC,UAAU,EAAE,CAAC,CAAC,UAAU,CAAC,CAAA;AAC1C,CAAC;AAED;;;;;;;;;;;;;;GAcG;AACH,MAAM,UAAU,MAAM,CAAC,OAA6B,EAAE,QAA8B;IAClF,MAAM,IAAI,GAAG,QAAQ,EAAE,CAAA;IACvB,MAAM,CAAC,CAAC,EAAE,CAAC,CAAC,GAAG,CAAC,IAAI,CAAC,OAAO,CAAC,EAAE,IAAI,CAAC,QAAQ,CAAC,CAAC,CAAA;IAC9C,IAAI,CAAC,CAAC,IAAI,CAAC,CAAC;QAAE,OAAO,KAAK,CAAA;IAC1B,IAAI,CAAC,CAAC,IAAI,KAAK,CAAC,CAAC,IAAI,IAAI,CAAC,CAAC,OAAO,KAAK,CAAC,CAAC,OAAO;QAAE,OAAO,KAAK,CAAA;IAE9D,MAAM,CAAC,GAAG,EAAE,GAAG,CAAC,GAAG,CAAC,KAAK,CAAC,IAAI,EAAE,CAAC,CAAC,IAAI,EAAE,CAAC,CAAC,OAAO,CAAC,EAAE,KAAK,CAAC,IAAI,EAAE,CAAC,CAAC,IAAI,EAAE,CAAC,CAAC,OAAO,CAAC,CAAC,CAAA;IACnF,IAAI,CAAC,WAAW,CAAC,GAAG,CAAC,IAAI,EAAE,GAAG,CAAC,IAAI,CAAC;QAAE,OAAO,KAAK,CAAA;IAClD,IAAI,GAAG,CAAC,OAAO,KAAK,SAAS,IAAI,GAAG,CAAC,OAAO,KAAK,GAAG,CAAC,OAAO;QAAE,OAAO,KAAK,CAAA;IAE1E,MAAM,IAAI,GAAG,CAAC,CAAC,QAAQ,EAAE,IAAI,CAAA;IAC7B,IAAI,IAAI,KAAK,SAAS,IAAI,IAAI,KAAK,CAAC,CAAC,QAAQ,EAAE,IAAI;QAAE,OAAO,KAAK,CAAA;IACjE,IAAI,CAAC,QAAQ,CAAC,CAAC,CAAC,QAAQ,EAAE,WAAW,IAAI,EAAE,EAAE,CAAC,CAAC,QAAQ,EAAE,WAAW,IAAI,EAAE,CAAC;QAAE,OAAO,KAAK,CAAA;IAEzF,OAAO,QAAQ,CAAC,CAAC,CAAC,UAAU,EAAE,CAAC,CAAC,UAAU,CAAC,CAAA;AAC7C,CAAC"}
|
|
@@ -0,0 +1,12 @@
|
|
|
1
|
+
import type { RefIdSpec } from "./spec.ts";
|
|
2
|
+
/**
|
|
3
|
+
* The sidecar digest `spec/ref-id.json.sha256` carried at the moment this file was generated.
|
|
4
|
+
*
|
|
5
|
+
* This is a STATEMENT about provenance, not a verification: nothing in this module recomputes it,
|
|
6
|
+
* and there is no live spec/ref-id.json here to recompute it against. The guarantee that this digest
|
|
7
|
+
* is the one that was actually verified belongs to the generator (scripts/gen-spec.mjs, which refuses
|
|
8
|
+
* to emit from a spec that fails its sidecar) and to the staleness guard that regenerates and diffs
|
|
9
|
+
* this file in CI. Reading this field as a check performed at import time would be a mistake.
|
|
10
|
+
*/
|
|
11
|
+
export declare const SPEC_DIGEST: string;
|
|
12
|
+
export declare const SPEC: RefIdSpec;
|