@quillmark/quiver 0.29.0 → 0.30.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/MIGRATION.md +16 -0
- package/README.md +5 -18
- package/dist/build.d.ts +9 -9
- package/dist/build.js +31 -38
- package/dist/built-loader.d.ts +12 -26
- package/dist/built-loader.js +176 -189
- package/dist/bundle.d.ts +0 -16
- package/dist/bundle.js +4 -7
- package/dist/format.d.ts +11 -17
- package/dist/format.js +11 -17
- package/dist/node.d.ts +1 -1
- package/dist/node.js +14 -3
- package/dist/quiver.d.ts +3 -16
- package/dist/quiver.js +7 -24
- package/dist/signature.d.ts +6 -0
- package/dist/signature.js +13 -0
- package/package.json +2 -2
- package/dist/digest.d.ts +0 -23
- package/dist/digest.js +0 -29
- package/dist/transports/fs-built-transport.d.ts +0 -14
- package/dist/transports/fs-built-transport.js +0 -36
- package/dist/transports/http-transport.d.ts +0 -12
- package/dist/transports/http-transport.js +0 -87
- package/dist/transports/memory-transport.d.ts +0 -16
- package/dist/transports/memory-transport.js +0 -35
package/dist/quiver.d.ts
CHANGED
|
@@ -36,33 +36,20 @@ export declare class Quiver {
|
|
|
36
36
|
* environments. `file://` URLs are rejected — to load build output from
|
|
37
37
|
* disk in Node, use `fromBuiltDir(path)` from `@quillmark/quiver/node`.
|
|
38
38
|
*
|
|
39
|
-
* `seed` answers for the artifact bytes the caller already holds, keyed by
|
|
40
|
-
* artifact-relative path; the URL serves the rest. Seeding `latest.json`
|
|
41
|
-
* settles which catalog this process reads at deploy time rather than at
|
|
42
|
-
* cache-revalidation time. Seeded bytes are digest-checked as fetched ones
|
|
43
|
-
* are.
|
|
44
|
-
*
|
|
45
39
|
* Throws `transport_error` on network/HTTP failure, `quiver_invalid`
|
|
46
40
|
* on format errors.
|
|
47
41
|
*/
|
|
48
|
-
static fromBuiltUrl(url: string
|
|
49
|
-
seed?: ReadonlyMap<string, Uint8Array>;
|
|
50
|
-
}): Promise<Quiver>;
|
|
42
|
+
static fromBuiltUrl(url: string): Promise<Quiver>;
|
|
51
43
|
/**
|
|
52
44
|
* Browser-safe factory. Loads build output from the bytes themselves, keyed
|
|
53
|
-
* by artifact-relative path (`
|
|
54
|
-
*
|
|
55
|
-
* and `fromBuiltDir` reads back.
|
|
45
|
+
* by artifact-relative path (`quiver.json`, `<name>@<x.y.z>.<digest>.zip`,
|
|
46
|
+
* `fonts/<hash>`) — the shape `build` writes and `fromBuiltDir` reads back.
|
|
56
47
|
*
|
|
57
48
|
* Nothing is fetched, so a runtime whose artifact is not on a path it can
|
|
58
49
|
* read (a serverless function, a bundler that inlines it, a test) reaches
|
|
59
50
|
* one without fetching its own static output back over its own load
|
|
60
51
|
* balancer.
|
|
61
52
|
*
|
|
62
|
-
* The map must carry the whole artifact; a missing path is a
|
|
63
|
-
* `transport_error` naming it. To hold part and fetch the rest, pass the map
|
|
64
|
-
* as `fromBuiltUrl`'s `seed`.
|
|
65
|
-
*
|
|
66
53
|
* Throws `quiver_invalid` on format errors, `transport_error` on a path the
|
|
67
54
|
* map does not carry.
|
|
68
55
|
*/
|
package/dist/quiver.js
CHANGED
|
@@ -51,49 +51,32 @@ export class Quiver {
|
|
|
51
51
|
* environments. `file://` URLs are rejected — to load build output from
|
|
52
52
|
* disk in Node, use `fromBuiltDir(path)` from `@quillmark/quiver/node`.
|
|
53
53
|
*
|
|
54
|
-
* `seed` answers for the artifact bytes the caller already holds, keyed by
|
|
55
|
-
* artifact-relative path; the URL serves the rest. Seeding `latest.json`
|
|
56
|
-
* settles which catalog this process reads at deploy time rather than at
|
|
57
|
-
* cache-revalidation time. Seeded bytes are digest-checked as fetched ones
|
|
58
|
-
* are.
|
|
59
|
-
*
|
|
60
54
|
* Throws `transport_error` on network/HTTP failure, `quiver_invalid`
|
|
61
55
|
* on format errors.
|
|
62
56
|
*/
|
|
63
|
-
static async fromBuiltUrl(url
|
|
57
|
+
static async fromBuiltUrl(url) {
|
|
64
58
|
if (url.startsWith('file://')) {
|
|
65
59
|
throw new QuiverError('transport_error', `Quiver.fromBuiltUrl requires an http(s):// or origin-relative URL; got "${url}". For local build output, use import { fromBuiltDir } from '@quillmark/quiver/node'.`);
|
|
66
60
|
}
|
|
67
|
-
const {
|
|
68
|
-
|
|
69
|
-
const http = new HttpTransport(url);
|
|
70
|
-
if (opts?.seed === undefined)
|
|
71
|
-
return loadBuiltQuiver(http);
|
|
72
|
-
const { MemoryTransport } = await import('./transports/memory-transport.js');
|
|
73
|
-
return loadBuiltQuiver(new MemoryTransport(opts.seed, http));
|
|
61
|
+
const { httpReader, loadBuiltQuiver } = await import('./built-loader.js');
|
|
62
|
+
return loadBuiltQuiver(httpReader(url));
|
|
74
63
|
}
|
|
75
64
|
/**
|
|
76
65
|
* Browser-safe factory. Loads build output from the bytes themselves, keyed
|
|
77
|
-
* by artifact-relative path (`
|
|
78
|
-
*
|
|
79
|
-
* and `fromBuiltDir` reads back.
|
|
66
|
+
* by artifact-relative path (`quiver.json`, `<name>@<x.y.z>.<digest>.zip`,
|
|
67
|
+
* `fonts/<hash>`) — the shape `build` writes and `fromBuiltDir` reads back.
|
|
80
68
|
*
|
|
81
69
|
* Nothing is fetched, so a runtime whose artifact is not on a path it can
|
|
82
70
|
* read (a serverless function, a bundler that inlines it, a test) reaches
|
|
83
71
|
* one without fetching its own static output back over its own load
|
|
84
72
|
* balancer.
|
|
85
73
|
*
|
|
86
|
-
* The map must carry the whole artifact; a missing path is a
|
|
87
|
-
* `transport_error` naming it. To hold part and fetch the rest, pass the map
|
|
88
|
-
* as `fromBuiltUrl`'s `seed`.
|
|
89
|
-
*
|
|
90
74
|
* Throws `quiver_invalid` on format errors, `transport_error` on a path the
|
|
91
75
|
* map does not carry.
|
|
92
76
|
*/
|
|
93
77
|
static async fromBuiltFiles(files) {
|
|
94
|
-
const {
|
|
95
|
-
|
|
96
|
-
return loadBuiltQuiver(new MemoryTransport(files));
|
|
78
|
+
const { filesReader, loadBuiltQuiver } = await import('./built-loader.js');
|
|
79
|
+
return loadBuiltQuiver(filesReader(files));
|
|
97
80
|
}
|
|
98
81
|
/** Returns all known quill names, sorted lexicographically. */
|
|
99
82
|
quillNames() {
|
|
@@ -0,0 +1,6 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* TrueType, OpenType or a collection of either; WOFF or WOFF2 where `path` names one.
|
|
3
|
+
* The engine parses sfnt alone, so a `.ttf` holding WOFF bytes is refused.
|
|
4
|
+
*/
|
|
5
|
+
export declare const isFont: (bytes: Uint8Array, path: string) => boolean;
|
|
6
|
+
export declare const isZip: (bytes: Uint8Array) => boolean;
|
|
@@ -0,0 +1,13 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* The leading bytes of what an artifact holds. A host answering a missing name with a
|
|
3
|
+
* page of its own answers 200, and nothing else read here tells that page from a font.
|
|
4
|
+
*/
|
|
5
|
+
const SFNT = ['\0\x01\0\0', 'OTTO', 'true', 'ttcf'];
|
|
6
|
+
const WOFF = ['wOFF', 'wOF2'];
|
|
7
|
+
const opens = (bytes, magic) => bytes.length >= magic.length && [...magic].every((c, i) => bytes[i] === c.charCodeAt(0));
|
|
8
|
+
/**
|
|
9
|
+
* TrueType, OpenType or a collection of either; WOFF or WOFF2 where `path` names one.
|
|
10
|
+
* The engine parses sfnt alone, so a `.ttf` holding WOFF bytes is refused.
|
|
11
|
+
*/
|
|
12
|
+
export const isFont = (bytes, path) => (/\.woff2?$/i.test(path) ? WOFF : SFNT).some((magic) => opens(bytes, magic));
|
|
13
|
+
export const isZip = (bytes) => opens(bytes, 'PK\x03\x04');
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@quillmark/quiver",
|
|
3
|
-
"version": "0.
|
|
3
|
+
"version": "0.30.0",
|
|
4
4
|
"description": "A quiver of Quillmark quills: load one, pack one, hand out quills.",
|
|
5
5
|
"type": "module",
|
|
6
6
|
"license": "Apache-2.0",
|
|
@@ -42,7 +42,7 @@
|
|
|
42
42
|
"NOTICE"
|
|
43
43
|
],
|
|
44
44
|
"peerDependencies": {
|
|
45
|
-
"@quillmark/wasm": ">=0.
|
|
45
|
+
"@quillmark/wasm": ">=0.116.0-0"
|
|
46
46
|
},
|
|
47
47
|
"dependencies": {
|
|
48
48
|
"fflate": "^0.8.2",
|
package/dist/digest.d.ts
DELETED
|
@@ -1,23 +0,0 @@
|
|
|
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: 128 bits of SHA-256.
|
|
13
|
-
*
|
|
14
|
-
* The width answers a chosen prefix, not a collision. Where the manifest is trusted
|
|
15
|
-
* and the bundles are not — the seeded deployment, where the two come from different
|
|
16
|
-
* places by design — this is the only thing between a served zip and the quill it
|
|
17
|
-
* claims to be, and a quill is a template the backend executes. Grinding padding until
|
|
18
|
-
* SHA-256 lands on a chosen prefix costs hours of one GPU at 48 bits and is out of
|
|
19
|
-
* reach at 128.
|
|
20
|
-
*/
|
|
21
|
-
export declare const NAME_DIGEST_LENGTH = 32;
|
|
22
|
-
/** SHA-256 over `bytes` as lowercase hex, or undefined where no digest exists. */
|
|
23
|
-
export declare function sha256Hex(bytes: Uint8Array): Promise<string | undefined>;
|
package/dist/digest.js
DELETED
|
@@ -1,29 +0,0 @@
|
|
|
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: 128 bits of SHA-256.
|
|
13
|
-
*
|
|
14
|
-
* The width answers a chosen prefix, not a collision. Where the manifest is trusted
|
|
15
|
-
* and the bundles are not — the seeded deployment, where the two come from different
|
|
16
|
-
* places by design — this is the only thing between a served zip and the quill it
|
|
17
|
-
* claims to be, and a quill is a template the backend executes. Grinding padding until
|
|
18
|
-
* SHA-256 lands on a chosen prefix costs hours of one GPU at 48 bits and is out of
|
|
19
|
-
* reach at 128.
|
|
20
|
-
*/
|
|
21
|
-
export const NAME_DIGEST_LENGTH = 32;
|
|
22
|
-
/** SHA-256 over `bytes` as lowercase hex, or undefined where no digest exists. */
|
|
23
|
-
export async function sha256Hex(bytes) {
|
|
24
|
-
const subtle = globalThis.crypto?.subtle;
|
|
25
|
-
if (subtle === undefined)
|
|
26
|
-
return undefined;
|
|
27
|
-
const digest = await subtle.digest('SHA-256', bytes);
|
|
28
|
-
return [...new Uint8Array(digest)].map((b) => b.toString(16).padStart(2, '0')).join('');
|
|
29
|
-
}
|
|
@@ -1,14 +0,0 @@
|
|
|
1
|
-
/**
|
|
2
|
-
* FsBuiltTransport — Node-only built-quiver transport that reads packed
|
|
3
|
-
* artifacts from the local filesystem.
|
|
4
|
-
* Internal; not exported from index.ts.
|
|
5
|
-
*
|
|
6
|
-
* Static `node:*` imports — this module must never be reached from browser
|
|
7
|
-
* bundles. It is loaded lazily by `fromBuiltDir` in `./node.js`.
|
|
8
|
-
*/
|
|
9
|
-
import type { BuiltTransport } from '../built-loader.js';
|
|
10
|
-
export declare class FsBuiltTransport implements BuiltTransport {
|
|
11
|
-
private readonly rootDir;
|
|
12
|
-
constructor(rootDir: string);
|
|
13
|
-
fetchBytes(relativePath: string): Promise<Uint8Array>;
|
|
14
|
-
}
|
|
@@ -1,36 +0,0 @@
|
|
|
1
|
-
/**
|
|
2
|
-
* FsBuiltTransport — Node-only built-quiver transport that reads packed
|
|
3
|
-
* artifacts from the local filesystem.
|
|
4
|
-
* Internal; not exported from index.ts.
|
|
5
|
-
*
|
|
6
|
-
* Static `node:*` imports — this module must never be reached from browser
|
|
7
|
-
* bundles. It is loaded lazily by `fromBuiltDir` in `./node.js`.
|
|
8
|
-
*/
|
|
9
|
-
import { readFile } from 'node:fs/promises';
|
|
10
|
-
import { join, isAbsolute, normalize, sep } from 'node:path';
|
|
11
|
-
import { QuiverError } from '../errors.js';
|
|
12
|
-
export class FsBuiltTransport {
|
|
13
|
-
rootDir;
|
|
14
|
-
constructor(rootDir) {
|
|
15
|
-
this.rootDir = rootDir;
|
|
16
|
-
}
|
|
17
|
-
// No cache sits between this and the bytes, and a file is read at the size it
|
|
18
|
-
// is, so `FetchOptions` has nothing to act on here.
|
|
19
|
-
async fetchBytes(relativePath) {
|
|
20
|
-
if (isAbsolute(relativePath)) {
|
|
21
|
-
throw new QuiverError('transport_error', `FsBuiltTransport: absolute paths are not allowed: "${relativePath}"`);
|
|
22
|
-
}
|
|
23
|
-
const normalized = normalize(relativePath);
|
|
24
|
-
if (normalized.startsWith('..') || normalized.split(sep).includes('..')) {
|
|
25
|
-
throw new QuiverError('transport_error', `FsBuiltTransport: path escapes root: "${relativePath}"`);
|
|
26
|
-
}
|
|
27
|
-
const filePath = join(this.rootDir, normalized);
|
|
28
|
-
try {
|
|
29
|
-
const buf = await readFile(filePath);
|
|
30
|
-
return new Uint8Array(buf.buffer, buf.byteOffset, buf.byteLength);
|
|
31
|
-
}
|
|
32
|
-
catch (err) {
|
|
33
|
-
throw new QuiverError('transport_error', `Failed to read "${filePath}": ${err.message}`, { cause: err });
|
|
34
|
-
}
|
|
35
|
-
}
|
|
36
|
-
}
|
|
@@ -1,12 +0,0 @@
|
|
|
1
|
-
/**
|
|
2
|
-
* HttpTransport — browser-safe built-quiver transport that fetches via HTTP.
|
|
3
|
-
* Internal; not exported from index.ts.
|
|
4
|
-
*
|
|
5
|
-
* Uses globalThis.fetch — no node: imports at any level.
|
|
6
|
-
*/
|
|
7
|
-
import type { BuiltTransport, FetchOptions } from '../built-loader.js';
|
|
8
|
-
export declare class HttpTransport implements BuiltTransport {
|
|
9
|
-
private readonly baseUrl;
|
|
10
|
-
constructor(baseUrl: string);
|
|
11
|
-
fetchBytes(relativePath: string, opts: FetchOptions): Promise<Uint8Array>;
|
|
12
|
-
}
|
|
@@ -1,87 +0,0 @@
|
|
|
1
|
-
/**
|
|
2
|
-
* HttpTransport — browser-safe built-quiver transport that fetches via HTTP.
|
|
3
|
-
* Internal; not exported from index.ts.
|
|
4
|
-
*
|
|
5
|
-
* Uses globalThis.fetch — no node: imports at any level.
|
|
6
|
-
*/
|
|
7
|
-
import { QuiverError } from '../errors.js';
|
|
8
|
-
/** Over the ceiling is `quiver_invalid`, not `transport_error`: a retry fetches the same
|
|
9
|
-
* response, so there is nothing for an evicting cache to fix. */
|
|
10
|
-
function oversized(url, max, says) {
|
|
11
|
-
return new QuiverError('quiver_invalid', `"${url}" ${says} the ${max} bytes a fetch may read`);
|
|
12
|
-
}
|
|
13
|
-
/** The body chunk by chunk, refused past the ceiling. Cancelling the reader terminates
|
|
14
|
-
* the fetch, so nothing more of a refused response is pulled off the socket. */
|
|
15
|
-
async function readCapped(body, url, max) {
|
|
16
|
-
const reader = body.getReader();
|
|
17
|
-
const chunks = [];
|
|
18
|
-
let total = 0;
|
|
19
|
-
for (;;) {
|
|
20
|
-
const { done, value } = await reader.read();
|
|
21
|
-
if (done)
|
|
22
|
-
break;
|
|
23
|
-
total += value.byteLength;
|
|
24
|
-
if (total > max) {
|
|
25
|
-
await reader.cancel();
|
|
26
|
-
throw oversized(url, max, 'is over');
|
|
27
|
-
}
|
|
28
|
-
chunks.push(value);
|
|
29
|
-
}
|
|
30
|
-
const bytes = new Uint8Array(total);
|
|
31
|
-
let at = 0;
|
|
32
|
-
for (const chunk of chunks) {
|
|
33
|
-
bytes.set(chunk, at);
|
|
34
|
-
at += chunk.byteLength;
|
|
35
|
-
}
|
|
36
|
-
return bytes;
|
|
37
|
-
}
|
|
38
|
-
export class HttpTransport {
|
|
39
|
-
baseUrl;
|
|
40
|
-
constructor(baseUrl) {
|
|
41
|
-
this.baseUrl = baseUrl.endsWith('/') ? baseUrl : `${baseUrl}/`;
|
|
42
|
-
}
|
|
43
|
-
async fetchBytes(relativePath, opts) {
|
|
44
|
-
const cleanPath = relativePath.startsWith('/') ? relativePath.slice(1) : relativePath;
|
|
45
|
-
const url = `${this.baseUrl}${cleanPath}`;
|
|
46
|
-
let response;
|
|
47
|
-
try {
|
|
48
|
-
// `no-cache` revalidates with the origin rather than skipping the cache: a 304
|
|
49
|
-
// still serves from disk. `force-cache` takes a cached response whatever its
|
|
50
|
-
// age, which a digest-carrying name is entitled to; a stale one fails
|
|
51
|
-
// verification upstream as `transport_error`, which an evicting cache clears.
|
|
52
|
-
response = await globalThis.fetch(url, {
|
|
53
|
-
cache: opts.revalidate ? 'no-cache' : 'force-cache'
|
|
54
|
-
});
|
|
55
|
-
}
|
|
56
|
-
catch (err) {
|
|
57
|
-
throw new QuiverError('transport_error', `Network error fetching "${url}": ${err.message}`, { cause: err });
|
|
58
|
-
}
|
|
59
|
-
if (!response.ok) {
|
|
60
|
-
throw new QuiverError('transport_error', `HTTP ${response.status} fetching "${url}"`);
|
|
61
|
-
}
|
|
62
|
-
// A fast reject; the running total below is what holds the ceiling. `Content-Length`
|
|
63
|
-
// is the encoded length, so under `Content-Encoding` it under-reports; a missing one is
|
|
64
|
-
// 0 and an unparseable one NaN, neither over the ceiling, so both accept early.
|
|
65
|
-
const declared = Number(response.headers.get('content-length'));
|
|
66
|
-
if (declared > opts.maxBytes) {
|
|
67
|
-
throw oversized(url, opts.maxBytes, `states ${declared} bytes, over`);
|
|
68
|
-
}
|
|
69
|
-
try {
|
|
70
|
-
// A host that hands back no stream is measured after the fact rather than
|
|
71
|
-
// refused: the degraded check still bounds what reaches the unpack budget,
|
|
72
|
-
// and refusing outright would cost hosts that work today.
|
|
73
|
-
if (response.body === null) {
|
|
74
|
-
const buffer = await response.arrayBuffer();
|
|
75
|
-
if (buffer.byteLength > opts.maxBytes)
|
|
76
|
-
throw oversized(url, opts.maxBytes, 'is over');
|
|
77
|
-
return new Uint8Array(buffer);
|
|
78
|
-
}
|
|
79
|
-
return await readCapped(response.body, url, opts.maxBytes);
|
|
80
|
-
}
|
|
81
|
-
catch (err) {
|
|
82
|
-
if (err instanceof QuiverError)
|
|
83
|
-
throw err;
|
|
84
|
-
throw new QuiverError('transport_error', `Network error reading "${url}": ${err.message}`, { cause: err });
|
|
85
|
-
}
|
|
86
|
-
}
|
|
87
|
-
}
|
|
@@ -1,16 +0,0 @@
|
|
|
1
|
-
/**
|
|
2
|
-
* MemoryTransport — built-quiver transport over bytes the caller already holds,
|
|
3
|
-
* falling through to another transport for what the map does not carry.
|
|
4
|
-
* Internal; not exported from index.ts.
|
|
5
|
-
*
|
|
6
|
-
* Browser-safe: no `node:` imports at any level.
|
|
7
|
-
*/
|
|
8
|
-
import type { BuiltTransport, FetchOptions } from '../built-loader.js';
|
|
9
|
-
export declare class MemoryTransport implements BuiltTransport {
|
|
10
|
-
private readonly fallback?;
|
|
11
|
-
private readonly files;
|
|
12
|
-
constructor(files: ReadonlyMap<string, Uint8Array>, fallback?: BuiltTransport | undefined);
|
|
13
|
-
/** The map answers first. `FetchOptions` reaches only the fallback: a held byte has no
|
|
14
|
-
* cache to revalidate against and no arrival to refuse. */
|
|
15
|
-
fetchBytes(relativePath: string, opts: FetchOptions): Promise<Uint8Array>;
|
|
16
|
-
}
|
|
@@ -1,35 +0,0 @@
|
|
|
1
|
-
/**
|
|
2
|
-
* MemoryTransport — built-quiver transport over bytes the caller already holds,
|
|
3
|
-
* falling through to another transport for what the map does not carry.
|
|
4
|
-
* Internal; not exported from index.ts.
|
|
5
|
-
*
|
|
6
|
-
* Browser-safe: no `node:` imports at any level.
|
|
7
|
-
*/
|
|
8
|
-
import { QuiverError } from '../errors.js';
|
|
9
|
-
/** Keys are artifact-relative, as the loader asks for them; a leading `./` or `/` is stripped. */
|
|
10
|
-
function normalizeKeys(files) {
|
|
11
|
-
const out = new Map();
|
|
12
|
-
for (const [key, bytes] of files) {
|
|
13
|
-
out.set(key.replace(/^\.?\//, ''), bytes);
|
|
14
|
-
}
|
|
15
|
-
return out;
|
|
16
|
-
}
|
|
17
|
-
export class MemoryTransport {
|
|
18
|
-
fallback;
|
|
19
|
-
files;
|
|
20
|
-
constructor(files, fallback) {
|
|
21
|
-
this.fallback = fallback;
|
|
22
|
-
this.files = normalizeKeys(files);
|
|
23
|
-
}
|
|
24
|
-
/** The map answers first. `FetchOptions` reaches only the fallback: a held byte has no
|
|
25
|
-
* cache to revalidate against and no arrival to refuse. */
|
|
26
|
-
async fetchBytes(relativePath, opts) {
|
|
27
|
-
const bytes = this.files.get(relativePath);
|
|
28
|
-
if (bytes !== undefined)
|
|
29
|
-
return bytes;
|
|
30
|
-
if (this.fallback === undefined) {
|
|
31
|
-
throw new QuiverError('transport_error', `No bytes held for "${relativePath}". Quiver.fromBuiltFiles needs the whole artifact; to hold part and fetch the rest, pass the map as Quiver.fromBuiltUrl's \`seed\`.`);
|
|
32
|
-
}
|
|
33
|
-
return this.fallback.fetchBytes(relativePath, opts);
|
|
34
|
-
}
|
|
35
|
-
}
|