@quillmark/quiver 0.15.0 → 0.17.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/LICENSE +201 -21
- package/NOTICE +2 -0
- package/README.md +87 -201
- package/dist/bin/quiver.d.ts +4 -4
- package/dist/bin/quiver.js +28 -60
- package/dist/build.d.ts +12 -12
- package/dist/build.js +75 -32
- package/dist/built-loader.d.ts +17 -11
- package/dist/built-loader.js +116 -99
- package/dist/bundle.js +1 -1
- package/dist/digest.d.ts +19 -0
- package/dist/digest.js +25 -0
- package/dist/errors.d.ts +1 -1
- package/dist/errors.js +1 -1
- package/dist/index.d.ts +3 -3
- package/dist/index.js +4 -5
- package/dist/node.d.ts +47 -76
- package/dist/node.js +59 -64
- package/dist/quiver-yaml.js +19 -19
- package/dist/quiver.d.ts +25 -27
- package/dist/quiver.js +46 -95
- package/dist/ref.d.ts +3 -3
- package/dist/ref.js +15 -13
- package/dist/semver.d.ts +5 -1
- package/dist/semver.js +9 -10
- package/dist/source-loader.d.ts +3 -3
- package/dist/source-loader.js +26 -26
- package/dist/transports/fs-built-transport.d.ts +2 -2
- package/dist/transports/fs-built-transport.js +10 -8
- package/dist/transports/http-transport.d.ts +2 -2
- package/dist/transports/http-transport.js +11 -9
- package/package.json +66 -72
- package/dist/preview.d.ts +0 -81
- package/dist/preview.js +0 -208
- package/dist/testing.d.ts +0 -28
- package/dist/testing.js +0 -66
package/dist/built-loader.js
CHANGED
|
@@ -1,34 +1,54 @@
|
|
|
1
1
|
/**
|
|
2
|
-
* Built-quiver loader
|
|
3
|
-
*
|
|
2
|
+
* Built-quiver loader: reads a packed artifact through a transport, validates
|
|
3
|
+
* its pointer and manifest, and hands back a Quiver whose loader fetches
|
|
4
|
+
* bundles and fonts on demand, each checked against the digest in its name.
|
|
4
5
|
*
|
|
5
|
-
*
|
|
6
|
-
|
|
7
|
-
|
|
6
|
+
* Package-internal, and browser-safe: no static `node:` imports at any level.
|
|
7
|
+
*/
|
|
8
|
+
import { QuiverError } from './errors.js';
|
|
9
|
+
import { unpackFiles } from './bundle.js';
|
|
10
|
+
import { isCanonicalSemver, compareSemver } from './semver.js';
|
|
11
|
+
import { NAME_DIGEST_LENGTH, sha256Hex } from './digest.js';
|
|
12
|
+
import { createQuiver } from './quiver.js';
|
|
13
|
+
/**
|
|
14
|
+
* Fetch, then check the bytes against the digest their name carries. That
|
|
15
|
+
* check is what makes "content-addressed, safe to cache forever" a property
|
|
16
|
+
* rather than a hope: it catches a corrupted CDN object, a partial sync, and a
|
|
17
|
+
* name reused across releases. A mismatch is a transport failure (the bytes
|
|
18
|
+
* that arrived are not the bytes asked for), so the caches that evict on error
|
|
19
|
+
* let a retry succeed.
|
|
8
20
|
*
|
|
9
|
-
*
|
|
21
|
+
* Where no digest primitive exists (a page served over plain http, which is
|
|
22
|
+
* not a secure context) the fetch passes through unchecked.
|
|
10
23
|
*/
|
|
11
|
-
|
|
12
|
-
|
|
13
|
-
|
|
14
|
-
|
|
15
|
-
|
|
16
|
-
const MANIFEST_FILENAME_RE = /^manifest\.[0-9a-f]+\.json$/;
|
|
17
|
-
const BUNDLE_FILENAME_RE = /^[A-Za-z0-9_.-]+@[0-9]+\.[0-9]+\.[0-9]+\.[0-9a-f]+\.zip$/;
|
|
18
|
-
const FONT_HASH_RE = /^[0-9a-f]{32}$/;
|
|
19
|
-
function validateManifestFileName(name) {
|
|
20
|
-
if (!MANIFEST_FILENAME_RE.test(name)) {
|
|
21
|
-
throw new QuiverError("quiver_invalid", `Pointer manifest filename is invalid: "${name}"`);
|
|
24
|
+
async function fetchVerified(transport, path, expected) {
|
|
25
|
+
const bytes = await transport.fetchBytes(path);
|
|
26
|
+
const actual = await sha256Hex(bytes);
|
|
27
|
+
if (actual !== undefined && !actual.startsWith(expected)) {
|
|
28
|
+
throw new QuiverError('transport_error', `Digest mismatch for "${path}": the bytes hash to ${actual.slice(0, expected.length)}, not ${expected}`);
|
|
22
29
|
}
|
|
30
|
+
return bytes;
|
|
23
31
|
}
|
|
24
|
-
|
|
25
|
-
|
|
26
|
-
|
|
27
|
-
|
|
32
|
+
// ─── Path validation ──────────────────────────────────────────────────────────
|
|
33
|
+
// Each name carries the digest of what it names; the capture group is the
|
|
34
|
+
// digest the fetch is checked against, so path validation and verification
|
|
35
|
+
// read the same character span. `build` writes exactly NAME_DIGEST_LENGTH
|
|
36
|
+
// chars for bundles and manifests and the full 64 for store entries.
|
|
37
|
+
const DIGEST = `[0-9a-f]{${NAME_DIGEST_LENGTH},}`;
|
|
38
|
+
const MANIFEST_FILENAME_RE = new RegExp(`^manifest\\.(${DIGEST})\\.json$`);
|
|
39
|
+
const BUNDLE_FILENAME_RE = new RegExp(`^[A-Za-z0-9_.-]+@[0-9]+\\.[0-9]+\\.[0-9]+\\.(${DIGEST})\\.zip$`);
|
|
40
|
+
const FONT_HASH_RE = /^[0-9a-f]{64}$/;
|
|
41
|
+
/** The digest a content-addressed filename carries. Throws `quiver_invalid`. */
|
|
42
|
+
function digestOfName(re, name, what) {
|
|
43
|
+
const match = re.exec(name);
|
|
44
|
+
if (match === null) {
|
|
45
|
+
throw new QuiverError('quiver_invalid', `${what} is invalid: "${name}"`);
|
|
46
|
+
}
|
|
47
|
+
return match[1];
|
|
28
48
|
}
|
|
29
49
|
function validateFontHash(hash, context) {
|
|
30
50
|
if (!FONT_HASH_RE.test(hash)) {
|
|
31
|
-
throw new QuiverError(
|
|
51
|
+
throw new QuiverError('quiver_invalid', `${context}: font hash is invalid: "${hash}"`);
|
|
32
52
|
}
|
|
33
53
|
}
|
|
34
54
|
// ─── BuiltLoader implementation ─────────────────────────────────────────────
|
|
@@ -44,16 +64,11 @@ class BuiltLoader {
|
|
|
44
64
|
this.index = index;
|
|
45
65
|
}
|
|
46
66
|
async loadTree(name, version) {
|
|
67
|
+
// The catalog the outer Quiver resolves against derives from this index,
|
|
68
|
+
// so every ref reaching here has an entry.
|
|
47
69
|
const entry = this.index.get(`${name}@${version}`);
|
|
48
|
-
// If entry is missing, the outer Quiver.loadTree gate already validated
|
|
49
|
-
// that this name/version is in the catalog; trust that gate and fall
|
|
50
|
-
// through. The transport will surface a transport_error naturally.
|
|
51
|
-
// (Defensive: this should not be reachable in normal operation.)
|
|
52
|
-
if (!entry) {
|
|
53
|
-
throw new QuiverError("transport_error", `Quill "${name}@${version}" not found in built-quiver manifest`, { version, ref: `${name}@${version}` });
|
|
54
|
-
}
|
|
55
70
|
// 1. Fetch + unpack bundle zip.
|
|
56
|
-
const zipBytes = await this.transport.
|
|
71
|
+
const zipBytes = await fetchVerified(this.transport, entry.bundle, digestOfName(BUNDLE_FILENAME_RE, entry.bundle, 'bundle filename'));
|
|
57
72
|
const files = unpackFiles(zipBytes);
|
|
58
73
|
// 2. Rehydrate fonts from store (coalesced).
|
|
59
74
|
const fontEntries = Object.entries(entry.fonts);
|
|
@@ -71,9 +86,7 @@ class BuiltLoader {
|
|
|
71
86
|
fetchFont(hash) {
|
|
72
87
|
let promise = this.fontCache.get(hash);
|
|
73
88
|
if (!promise) {
|
|
74
|
-
promise = this.transport
|
|
75
|
-
.fetchBytes(`store/${hash}`)
|
|
76
|
-
.catch((err) => {
|
|
89
|
+
promise = fetchVerified(this.transport, `store/${hash}`, hash).catch((err) => {
|
|
77
90
|
this.fontCache.delete(hash);
|
|
78
91
|
throw err;
|
|
79
92
|
});
|
|
@@ -86,7 +99,7 @@ class BuiltLoader {
|
|
|
86
99
|
function assertNoUnknownKeys(obj, allowed, context) {
|
|
87
100
|
for (const key of Object.keys(obj)) {
|
|
88
101
|
if (!allowed.includes(key)) {
|
|
89
|
-
throw new QuiverError(
|
|
102
|
+
throw new QuiverError('quiver_invalid', `${context}: unknown field "${key}"`);
|
|
90
103
|
}
|
|
91
104
|
}
|
|
92
105
|
}
|
|
@@ -96,17 +109,17 @@ function parsePointer(raw) {
|
|
|
96
109
|
parsed = JSON.parse(raw);
|
|
97
110
|
}
|
|
98
111
|
catch {
|
|
99
|
-
throw new QuiverError(
|
|
112
|
+
throw new QuiverError('quiver_invalid', 'latest.json contains invalid JSON');
|
|
100
113
|
}
|
|
101
|
-
if (typeof parsed !==
|
|
102
|
-
throw new QuiverError(
|
|
114
|
+
if (typeof parsed !== 'object' || parsed === null || Array.isArray(parsed)) {
|
|
115
|
+
throw new QuiverError('quiver_invalid', 'latest.json must be a JSON object');
|
|
103
116
|
}
|
|
104
117
|
const obj = parsed;
|
|
105
|
-
assertNoUnknownKeys(obj, [
|
|
106
|
-
if (typeof obj[
|
|
107
|
-
throw new QuiverError(
|
|
118
|
+
assertNoUnknownKeys(obj, ['manifest'], 'latest.json');
|
|
119
|
+
if (typeof obj['manifest'] !== 'string' || obj['manifest'].length === 0) {
|
|
120
|
+
throw new QuiverError('quiver_invalid', 'latest.json must have a non-empty string "manifest" field');
|
|
108
121
|
}
|
|
109
|
-
return obj[
|
|
122
|
+
return obj['manifest'];
|
|
110
123
|
}
|
|
111
124
|
function parseManifest(raw) {
|
|
112
125
|
let parsed;
|
|
@@ -114,119 +127,123 @@ function parseManifest(raw) {
|
|
|
114
127
|
parsed = JSON.parse(raw);
|
|
115
128
|
}
|
|
116
129
|
catch {
|
|
117
|
-
throw new QuiverError(
|
|
130
|
+
throw new QuiverError('quiver_invalid', 'Manifest file contains invalid JSON');
|
|
118
131
|
}
|
|
119
|
-
if (typeof parsed !==
|
|
120
|
-
throw new QuiverError(
|
|
132
|
+
if (typeof parsed !== 'object' || parsed === null || Array.isArray(parsed)) {
|
|
133
|
+
throw new QuiverError('quiver_invalid', 'Manifest must be a JSON object');
|
|
121
134
|
}
|
|
122
135
|
const obj = parsed;
|
|
123
|
-
assertNoUnknownKeys(obj, [
|
|
124
|
-
if (obj[
|
|
125
|
-
throw new QuiverError(
|
|
136
|
+
assertNoUnknownKeys(obj, ['version', 'name', 'quills'], 'manifest');
|
|
137
|
+
if (obj['version'] !== 1) {
|
|
138
|
+
throw new QuiverError('quiver_invalid', `Manifest version must be 1, got ${String(obj['version'])}`);
|
|
126
139
|
}
|
|
127
|
-
if (typeof obj[
|
|
128
|
-
throw new QuiverError(
|
|
140
|
+
if (typeof obj['name'] !== 'string' || obj['name'].length === 0) {
|
|
141
|
+
throw new QuiverError('quiver_invalid', 'Manifest must have a non-empty string "name" field');
|
|
129
142
|
}
|
|
130
|
-
if (!Array.isArray(obj[
|
|
131
|
-
throw new QuiverError(
|
|
143
|
+
if (!Array.isArray(obj['quills'])) {
|
|
144
|
+
throw new QuiverError('quiver_invalid', 'Manifest must have a "quills" array');
|
|
132
145
|
}
|
|
133
146
|
const quills = [];
|
|
134
|
-
for (let i = 0; i < obj[
|
|
135
|
-
const entry = obj[
|
|
136
|
-
if (typeof entry !==
|
|
137
|
-
throw new QuiverError(
|
|
147
|
+
for (let i = 0; i < obj['quills'].length; i++) {
|
|
148
|
+
const entry = obj['quills'][i];
|
|
149
|
+
if (typeof entry !== 'object' || entry === null || Array.isArray(entry)) {
|
|
150
|
+
throw new QuiverError('quiver_invalid', `manifest.quills[${i}] must be an object`);
|
|
138
151
|
}
|
|
139
152
|
const e = entry;
|
|
140
|
-
assertNoUnknownKeys(e, [
|
|
141
|
-
if (typeof e[
|
|
142
|
-
throw new QuiverError(
|
|
153
|
+
assertNoUnknownKeys(e, ['name', 'version', 'bundle', 'fonts'], `manifest.quills[${i}]`);
|
|
154
|
+
if (typeof e['name'] !== 'string' || e['name'].length === 0) {
|
|
155
|
+
throw new QuiverError('quiver_invalid', `manifest.quills[${i}].name must be a non-empty string`);
|
|
143
156
|
}
|
|
144
|
-
if (typeof e[
|
|
145
|
-
|
|
146
|
-
throw new QuiverError("quiver_invalid", `manifest.quills[${i}].version must be canonical semver (x.y.z), got "${String(e["version"])}"`);
|
|
157
|
+
if (typeof e['version'] !== 'string' || !isCanonicalSemver(e['version'])) {
|
|
158
|
+
throw new QuiverError('quiver_invalid', `manifest.quills[${i}].version must be canonical semver (x.y.z), got "${String(e['version'])}"`);
|
|
147
159
|
}
|
|
148
|
-
if (typeof e[
|
|
149
|
-
|
|
150
|
-
throw new QuiverError("quiver_invalid", `manifest.quills[${i}].bundle must be a non-empty string`);
|
|
160
|
+
if (typeof e['bundle'] !== 'string' || e['bundle'].length === 0) {
|
|
161
|
+
throw new QuiverError('quiver_invalid', `manifest.quills[${i}].bundle must be a non-empty string`);
|
|
151
162
|
}
|
|
152
|
-
|
|
153
|
-
if (typeof e[
|
|
154
|
-
|
|
155
|
-
Array.isArray(e["fonts"])) {
|
|
156
|
-
throw new QuiverError("quiver_invalid", `manifest.quills[${i}].fonts must be an object`);
|
|
163
|
+
digestOfName(BUNDLE_FILENAME_RE, e['bundle'], `manifest.quills[${i}].bundle filename`);
|
|
164
|
+
if (typeof e['fonts'] !== 'object' || e['fonts'] === null || Array.isArray(e['fonts'])) {
|
|
165
|
+
throw new QuiverError('quiver_invalid', `manifest.quills[${i}].fonts must be an object`);
|
|
157
166
|
}
|
|
158
|
-
const fonts = e[
|
|
167
|
+
const fonts = e['fonts'];
|
|
159
168
|
for (const [k, v] of Object.entries(fonts)) {
|
|
160
|
-
if (typeof v !==
|
|
161
|
-
throw new QuiverError(
|
|
169
|
+
if (typeof v !== 'string') {
|
|
170
|
+
throw new QuiverError('quiver_invalid', `manifest.quills[${i}].fonts["${k}"] must be a string`);
|
|
162
171
|
}
|
|
163
172
|
validateFontHash(v, `manifest.quills[${i}].fonts["${k}"]`);
|
|
164
173
|
}
|
|
165
174
|
quills.push({
|
|
166
|
-
name: e[
|
|
167
|
-
version: e[
|
|
168
|
-
bundle: e[
|
|
169
|
-
fonts: fonts
|
|
175
|
+
name: e['name'],
|
|
176
|
+
version: e['version'],
|
|
177
|
+
bundle: e['bundle'],
|
|
178
|
+
fonts: fonts
|
|
170
179
|
});
|
|
171
180
|
}
|
|
172
181
|
return {
|
|
173
182
|
version: 1,
|
|
174
|
-
name: obj[
|
|
175
|
-
quills
|
|
183
|
+
name: obj['name'],
|
|
184
|
+
quills
|
|
176
185
|
};
|
|
177
186
|
}
|
|
187
|
+
// ─── Catalog assembly ─────────────────────────────────────────────────────────
|
|
188
|
+
/**
|
|
189
|
+
* Name → versions (descending), derived from the index. One structure backs
|
|
190
|
+
* both the loader's lookups and the Quiver's catalog, so the two cannot
|
|
191
|
+
* disagree about what the manifest holds.
|
|
192
|
+
*/
|
|
193
|
+
function catalogOf(index) {
|
|
194
|
+
const catalog = new Map();
|
|
195
|
+
for (const entry of index.values()) {
|
|
196
|
+
const versions = catalog.get(entry.name) ?? [];
|
|
197
|
+
versions.push(entry.version);
|
|
198
|
+
catalog.set(entry.name, versions);
|
|
199
|
+
}
|
|
200
|
+
for (const versions of catalog.values()) {
|
|
201
|
+
versions.sort((a, b) => compareSemver(b, a));
|
|
202
|
+
}
|
|
203
|
+
return catalog;
|
|
204
|
+
}
|
|
178
205
|
// ─── Main entry point ─────────────────────────────────────────────────────────
|
|
179
206
|
/**
|
|
180
207
|
* Load a build-output quiver via the given transport.
|
|
181
208
|
*
|
|
182
|
-
* 1. Fetches
|
|
209
|
+
* 1. Fetches latest.json (pointer) and parses it.
|
|
183
210
|
* 2. Fetches the manifest file it points to and validates it.
|
|
184
211
|
* 3. Builds a catalog from manifest entries (versions sorted descending).
|
|
185
212
|
* 4. Returns a Quiver instance backed by a BuiltLoader.
|
|
186
213
|
*/
|
|
187
214
|
export async function loadBuiltQuiver(transport) {
|
|
188
|
-
// 1. Fetch and parse pointer.
|
|
215
|
+
// 1. Fetch and parse pointer. It is the one name that is not
|
|
216
|
+
// content-addressed, so it is also the one fetch that must revalidate.
|
|
189
217
|
let pointerBytes;
|
|
190
218
|
try {
|
|
191
|
-
pointerBytes = await transport.fetchBytes(
|
|
219
|
+
pointerBytes = await transport.fetchBytes('latest.json', { revalidate: true });
|
|
192
220
|
}
|
|
193
221
|
catch (err) {
|
|
194
222
|
if (err instanceof QuiverError)
|
|
195
223
|
throw err;
|
|
196
|
-
throw new QuiverError(
|
|
224
|
+
throw new QuiverError('transport_error', `Failed to fetch latest.json: ${err.message}`, { cause: err });
|
|
197
225
|
}
|
|
198
226
|
const manifestFileName = parsePointer(new TextDecoder().decode(pointerBytes));
|
|
199
|
-
|
|
227
|
+
const manifestDigest = digestOfName(MANIFEST_FILENAME_RE, manifestFileName, 'Pointer manifest filename');
|
|
200
228
|
// 2. Fetch and parse manifest.
|
|
201
229
|
let manifestBytes;
|
|
202
230
|
try {
|
|
203
|
-
manifestBytes = await transport
|
|
231
|
+
manifestBytes = await fetchVerified(transport, manifestFileName, manifestDigest);
|
|
204
232
|
}
|
|
205
233
|
catch (err) {
|
|
206
234
|
if (err instanceof QuiverError)
|
|
207
235
|
throw err;
|
|
208
|
-
throw new QuiverError(
|
|
236
|
+
throw new QuiverError('transport_error', `Failed to fetch manifest "${manifestFileName}": ${err.message}`, { cause: err });
|
|
209
237
|
}
|
|
238
|
+
// 3–4. Validate manifest + assemble catalog/loader.
|
|
210
239
|
const manifest = parseManifest(new TextDecoder().decode(manifestBytes));
|
|
211
|
-
// 3. Build catalog: name → versions sorted descending.
|
|
212
|
-
// Also build index map: "name@version" → entry (with duplicate detection).
|
|
213
|
-
const catalogRaw = new Map();
|
|
214
240
|
const index = new Map();
|
|
215
241
|
for (const entry of manifest.quills) {
|
|
216
242
|
const key = `${entry.name}@${entry.version}`;
|
|
217
243
|
if (index.has(key)) {
|
|
218
|
-
throw new QuiverError(
|
|
244
|
+
throw new QuiverError('quiver_invalid', `Duplicate quill entry in manifest: "${key}"`);
|
|
219
245
|
}
|
|
220
246
|
index.set(key, entry);
|
|
221
|
-
const versions = catalogRaw.get(entry.name) ?? [];
|
|
222
|
-
versions.push(entry.version);
|
|
223
|
-
catalogRaw.set(entry.name, versions);
|
|
224
|
-
}
|
|
225
|
-
for (const [, versions] of catalogRaw) {
|
|
226
|
-
versions.sort((a, b) => compareSemver(b, a));
|
|
227
247
|
}
|
|
228
|
-
|
|
229
|
-
const loader = new BuiltLoader(transport, index);
|
|
230
|
-
// 5. Return Quiver via internal factory.
|
|
231
|
-
return Quiver._fromLoader(manifest.name, catalogRaw, loader);
|
|
248
|
+
return createQuiver(manifest.name, catalogOf(index), new BuiltLoader(transport, index));
|
|
232
249
|
}
|
package/dist/bundle.js
CHANGED
package/dist/digest.d.ts
ADDED
|
@@ -0,0 +1,19 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Content addressing. Browser-safe.
|
|
3
|
+
*
|
|
4
|
+
* `crypto.subtle` is the one digest primitive a browser and Node both expose,
|
|
5
|
+
* and it exists only in a secure context: a page served over plain `http://`
|
|
6
|
+
* to something other than localhost has no digest at all. Verification
|
|
7
|
+
* degrades to a no-op there rather than failing every fetch, so `undefined`
|
|
8
|
+
* means "cannot check", never "check failed". Node and any https page always
|
|
9
|
+
* have one.
|
|
10
|
+
*/
|
|
11
|
+
/**
|
|
12
|
+
* Digest width in the names `build` writes, in hex chars: 48 bits of SHA-256.
|
|
13
|
+
* Wide enough that a manifest name colliding with a prior release's, which
|
|
14
|
+
* under immutable CDN caching serves the old catalog forever, is not a birthday
|
|
15
|
+
* problem within any release count a quiver will see.
|
|
16
|
+
*/
|
|
17
|
+
export declare const NAME_DIGEST_LENGTH = 12;
|
|
18
|
+
/** SHA-256 over `bytes` as lowercase hex, or undefined where no digest exists. */
|
|
19
|
+
export declare function sha256Hex(bytes: Uint8Array): Promise<string | undefined>;
|
package/dist/digest.js
ADDED
|
@@ -0,0 +1,25 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Content addressing. Browser-safe.
|
|
3
|
+
*
|
|
4
|
+
* `crypto.subtle` is the one digest primitive a browser and Node both expose,
|
|
5
|
+
* and it exists only in a secure context: a page served over plain `http://`
|
|
6
|
+
* to something other than localhost has no digest at all. Verification
|
|
7
|
+
* degrades to a no-op there rather than failing every fetch, so `undefined`
|
|
8
|
+
* means "cannot check", never "check failed". Node and any https page always
|
|
9
|
+
* have one.
|
|
10
|
+
*/
|
|
11
|
+
/**
|
|
12
|
+
* Digest width in the names `build` writes, in hex chars: 48 bits of SHA-256.
|
|
13
|
+
* Wide enough that a manifest name colliding with a prior release's, which
|
|
14
|
+
* under immutable CDN caching serves the old catalog forever, is not a birthday
|
|
15
|
+
* problem within any release count a quiver will see.
|
|
16
|
+
*/
|
|
17
|
+
export const NAME_DIGEST_LENGTH = 12;
|
|
18
|
+
/** SHA-256 over `bytes` as lowercase hex, or undefined where no digest exists. */
|
|
19
|
+
export async function sha256Hex(bytes) {
|
|
20
|
+
const subtle = globalThis.crypto?.subtle;
|
|
21
|
+
if (subtle === undefined)
|
|
22
|
+
return undefined;
|
|
23
|
+
const digest = await subtle.digest('SHA-256', bytes);
|
|
24
|
+
return [...new Uint8Array(digest)].map((b) => b.toString(16).padStart(2, '0')).join('');
|
|
25
|
+
}
|
package/dist/errors.d.ts
CHANGED
|
@@ -1,4 +1,4 @@
|
|
|
1
|
-
export type QuiverErrorCode =
|
|
1
|
+
export type QuiverErrorCode = 'invalid_ref' | 'quill_not_found' | 'quiver_invalid' | 'transport_error';
|
|
2
2
|
export declare class QuiverError extends Error {
|
|
3
3
|
readonly code: QuiverErrorCode;
|
|
4
4
|
/** Offending ref string, when available. */
|
package/dist/errors.js
CHANGED
|
@@ -8,7 +8,7 @@ export class QuiverError extends Error {
|
|
|
8
8
|
quiverName;
|
|
9
9
|
constructor(code, message, options) {
|
|
10
10
|
super(message, { cause: options?.cause });
|
|
11
|
-
this.name =
|
|
11
|
+
this.name = 'QuiverError';
|
|
12
12
|
this.code = code;
|
|
13
13
|
this.ref = options?.ref;
|
|
14
14
|
this.version = options?.version;
|
package/dist/index.d.ts
CHANGED
|
@@ -1,3 +1,3 @@
|
|
|
1
|
-
export { QuiverError } from
|
|
2
|
-
export type { QuiverErrorCode } from
|
|
3
|
-
export { Quiver } from
|
|
1
|
+
export { QuiverError } from './errors.js';
|
|
2
|
+
export type { QuiverErrorCode } from './errors.js';
|
|
3
|
+
export { Quiver } from './quiver.js';
|
package/dist/index.js
CHANGED
|
@@ -1,10 +1,9 @@
|
|
|
1
1
|
// Main browser-safe entrypoint.
|
|
2
2
|
//
|
|
3
|
-
// Exposes only browser-safe surface. Node-only factories
|
|
4
|
-
//
|
|
5
|
-
|
|
6
|
-
export {
|
|
7
|
-
export { Quiver } from "./quiver.js";
|
|
3
|
+
// Exposes only browser-safe surface. The Node-only factories (`fromDir`,
|
|
4
|
+
// `fromBuiltDir`, `build`) live at `@quillmark/quiver/node`.
|
|
5
|
+
export { QuiverError } from './errors.js';
|
|
6
|
+
export { Quiver } from './quiver.js';
|
|
8
7
|
// The canonical API (`Quill`, `Document`, `Engine`, `RenderResult`, …) is not
|
|
9
8
|
// re-exported: import it straight from the `@quillmark/wasm` peer dependency,
|
|
10
9
|
// which is the single source of truth. `quiver.getQuill(ref)` returns a core
|
package/dist/node.d.ts
CHANGED
|
@@ -1,81 +1,52 @@
|
|
|
1
1
|
/**
|
|
2
|
-
* Node-only entrypoint
|
|
2
|
+
* Node-only entrypoint: the factories that read a filesystem, as free
|
|
3
|
+
* functions. Each returns a `Quiver`; the class itself is the browser-safe one
|
|
4
|
+
* from the main entry, unmodified. Importing this module installs nothing, so
|
|
5
|
+
* the import path is the whole contract and a bundler drops what is not called.
|
|
3
6
|
*
|
|
4
|
-
*
|
|
5
|
-
*
|
|
6
|
-
*
|
|
7
|
-
*
|
|
7
|
+
* Bundler note: this entry pulls in `./source-loader.js`, `./build.js`, and
|
|
8
|
+
* `./transports/fs-built-transport.js`, all of which statically import `node:*`
|
|
9
|
+
* builtins. Browser bundles must never reach it. The main entry (`./index.js`)
|
|
10
|
+
* makes no static or dynamic reference to it.
|
|
11
|
+
*/
|
|
12
|
+
import { Quiver } from './quiver.js';
|
|
13
|
+
/**
|
|
14
|
+
* Reads a Source Quiver from a local directory containing `Quiver.yaml` and
|
|
15
|
+
* `quills/<name>/<version>/Quill.yaml` entries.
|
|
16
|
+
*
|
|
17
|
+
* Also accepts an `import.meta.url`-style `file://` URL; the URL's parent
|
|
18
|
+
* directory is used as the source root.
|
|
19
|
+
*
|
|
20
|
+
* A quiver installed from npm is a directory like any other:
|
|
21
|
+
* `dirname(createRequire(import.meta.url).resolve('<pkg>/Quiver.yaml'))` is its
|
|
22
|
+
* root, resolved from the caller, whose dependencies are reachable from the
|
|
23
|
+
* caller alone under an isolated `node_modules` layout.
|
|
24
|
+
*
|
|
25
|
+
* Throws `quiver_invalid` on schema violations, `transport_error` on I/O
|
|
26
|
+
* failure.
|
|
27
|
+
*/
|
|
28
|
+
export declare function fromDir(pathOrFileUrl: string): Promise<Quiver>;
|
|
29
|
+
/**
|
|
30
|
+
* Loads a packed (build-output) quiver from a local directory containing
|
|
31
|
+
* `latest.json` and the manifest/bundle/store files written by `build`.
|
|
32
|
+
* Symmetric to `Quiver.fromBuiltUrl(url)` but reads from disk instead of HTTP:
|
|
33
|
+
* no network required.
|
|
34
|
+
*
|
|
35
|
+
* Use this for server-side runtime when a packed artifact ships in the
|
|
36
|
+
* deployment image; consumers can keep source quivers as devDependencies and
|
|
37
|
+
* avoid self-fetching over their own load balancer.
|
|
8
38
|
*
|
|
9
|
-
*
|
|
10
|
-
|
|
11
|
-
|
|
12
|
-
|
|
13
|
-
*
|
|
39
|
+
* Throws `quiver_invalid` on format errors, `transport_error` on I/O failure.
|
|
40
|
+
*/
|
|
41
|
+
export declare function fromBuiltDir(dirPath: string): Promise<Quiver>;
|
|
42
|
+
/**
|
|
43
|
+
* Reads the Source Quiver at sourceDir, validates it, and writes the runtime
|
|
44
|
+
* build artifact to outDir, which the build clears first and therefore owns.
|
|
14
45
|
*
|
|
15
|
-
*
|
|
16
|
-
*
|
|
17
|
-
* statically import `node:*` builtins. Browser bundles must never reach this
|
|
18
|
-
* module. The main entry (`./index.js`) makes no static or dynamic reference
|
|
19
|
-
* to it.
|
|
46
|
+
* Throws `quiver_invalid` on source validation failures, `transport_error` on
|
|
47
|
+
* I/O failures and on an outDir holding the source quiver or the cwd.
|
|
20
48
|
*/
|
|
21
|
-
|
|
22
|
-
|
|
23
|
-
|
|
24
|
-
|
|
25
|
-
* Resolves an npm specifier against `node_modules` and loads the source
|
|
26
|
-
* layout at the package root. The resolved package must have `Quiver.yaml`
|
|
27
|
-
* at its root.
|
|
28
|
-
*
|
|
29
|
-
* Throws `transport_error` on resolution/I/O failure, `quiver_invalid` on
|
|
30
|
-
* schema violations.
|
|
31
|
-
*/
|
|
32
|
-
fromPackage(specifier: string): Promise<Base>;
|
|
33
|
-
/**
|
|
34
|
-
* Reads a Source Quiver from a local directory containing `Quiver.yaml`
|
|
35
|
-
* and `quills/<name>/<version>/Quill.yaml` entries.
|
|
36
|
-
*
|
|
37
|
-
* Also accepts `import.meta.url`-style `file://` URLs as a convenience for
|
|
38
|
-
* tests; the URL's parent directory is used as the source root.
|
|
39
|
-
*
|
|
40
|
-
* Throws `quiver_invalid` on schema violations, `transport_error` on I/O
|
|
41
|
-
* failure.
|
|
42
|
-
*/
|
|
43
|
-
fromDir(pathOrFileUrl: string): Promise<Base>;
|
|
44
|
-
/**
|
|
45
|
-
* Loads a packed (build-output) quiver from a local directory containing
|
|
46
|
-
* `Quiver.json` and the manifest/bundle/store files written by
|
|
47
|
-
* `Quiver.build`. Symmetric to `fromBuiltUrl(url)` but reads from disk
|
|
48
|
-
* instead of HTTP — no network required.
|
|
49
|
-
*
|
|
50
|
-
* Use this for server-side runtime when a packed artifact ships in the
|
|
51
|
-
* deployment image; consumers can keep source quivers as devDependencies
|
|
52
|
-
* and avoid self-fetching over their own load balancer.
|
|
53
|
-
*
|
|
54
|
-
* Throws `quiver_invalid` on format errors, `transport_error` on I/O
|
|
55
|
-
* failure.
|
|
56
|
-
*/
|
|
57
|
-
fromBuiltDir(dirPath: string): Promise<Base>;
|
|
58
|
-
/**
|
|
59
|
-
* Reads the Source Quiver at sourceDir, validates it, and writes the
|
|
60
|
-
* runtime build artifact to outDir.
|
|
61
|
-
*
|
|
62
|
-
* Throws `quiver_invalid` on source validation failures, `transport_error`
|
|
63
|
-
* on I/O failures.
|
|
64
|
-
*/
|
|
65
|
-
build(sourceDir: string, outDir: string, opts?: BuildOptions): Promise<void>;
|
|
66
|
-
/**
|
|
67
|
-
* Resolves an npm specifier against `node_modules` and builds the source
|
|
68
|
-
* layout at the package root. The resolved package must have `Quiver.yaml`
|
|
69
|
-
* at its root. Symmetric to `fromPackage` but writes a runtime build
|
|
70
|
-
* artifact to outDir instead of loading.
|
|
71
|
-
*
|
|
72
|
-
* Throws `transport_error` on resolution/I/O failure, `quiver_invalid` on
|
|
73
|
-
* source validation failures.
|
|
74
|
-
*/
|
|
75
|
-
buildPackage(specifier: string, outDir: string, opts?: BuildOptions): Promise<void>;
|
|
76
|
-
};
|
|
77
|
-
export type Quiver = Base;
|
|
78
|
-
export declare const Quiver: typeof Base & NodeQuiverStatics;
|
|
79
|
-
export { QuiverError } from "./errors.js";
|
|
80
|
-
export type { QuiverErrorCode } from "./errors.js";
|
|
81
|
-
export type { BuildOptions } from "./build.js";
|
|
49
|
+
export declare function build(sourceDir: string, outDir: string): Promise<void>;
|
|
50
|
+
export { Quiver } from './quiver.js';
|
|
51
|
+
export { QuiverError } from './errors.js';
|
|
52
|
+
export type { QuiverErrorCode } from './errors.js';
|