@quillmark/quiver 0.15.0 → 0.16.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 +47 -0
- package/dist/build.d.ts +1 -1
- package/dist/build.js +3 -3
- package/dist/built-loader.d.ts +7 -1
- package/dist/built-loader.js +45 -30
- package/dist/node.d.ts +1 -1
- package/dist/quiver.d.ts +10 -0
- package/dist/quiver.js +18 -0
- package/package.json +2 -2
package/README.md
CHANGED
|
@@ -192,6 +192,53 @@ const quill = await quiver.getQuill(doc.quillRef);
|
|
|
192
192
|
const result = await engine.render(quill, doc, { format: "pdf" });
|
|
193
193
|
```
|
|
194
194
|
|
|
195
|
+
## SSR seeding (skip the `latest.json` pointer)
|
|
196
|
+
|
|
197
|
+
`Quiver.fromBuiltUrl(url)` first fetches `<url>/latest.json` — a stable-named,
|
|
198
|
+
non-content-addressed pointer to the current manifest — before fetching the
|
|
199
|
+
manifest itself. Because that one filename is stable, a CDN edge or browser
|
|
200
|
+
cache can serve a **stale pointer** after a release and silently pin the
|
|
201
|
+
client to the old catalog, with no error. Per-host cache headers only fix this
|
|
202
|
+
one serving layer at a time.
|
|
203
|
+
|
|
204
|
+
If you already hold the manifest bytes at build time — a common case for SSR
|
|
205
|
+
consumers, which read the built artifact during their own build — seed the
|
|
206
|
+
catalog from them directly with `Quiver.fromManifest`. It never requests
|
|
207
|
+
`latest.json`; bundles and fonts are still fetched lazily and
|
|
208
|
+
content-addressed, relative to `baseUrl`, exactly as with `fromBuiltUrl`:
|
|
209
|
+
|
|
210
|
+
```ts
|
|
211
|
+
// Server build step — read the manifest the build wrote.
|
|
212
|
+
// build-output/latest.json → { "manifest": "manifest.<hash>.json" }
|
|
213
|
+
import { readFile } from "node:fs/promises";
|
|
214
|
+
|
|
215
|
+
const { manifest } = JSON.parse(
|
|
216
|
+
await readFile("./public/quivers/my-quiver/latest.json", "utf8"),
|
|
217
|
+
);
|
|
218
|
+
const manifestBytes = new Uint8Array(
|
|
219
|
+
await readFile(`./public/quivers/my-quiver/${manifest}`),
|
|
220
|
+
);
|
|
221
|
+
// Inline manifestBytes into the page payload (e.g. base64) for the client.
|
|
222
|
+
```
|
|
223
|
+
|
|
224
|
+
```ts
|
|
225
|
+
// Browser / SSR runtime — seed from the bytes you shipped, no pointer fetch.
|
|
226
|
+
import { Engine, Document } from "@quillmark/wasm";
|
|
227
|
+
import { Quiver } from "@quillmark/quiver";
|
|
228
|
+
|
|
229
|
+
const quiver = await Quiver.fromManifest("/quivers/my-quiver/", manifestBytes);
|
|
230
|
+
const engine = new Engine();
|
|
231
|
+
|
|
232
|
+
const doc = Document.fromMarkdown(markdownString);
|
|
233
|
+
const quill = await quiver.getQuill(doc.quillRef);
|
|
234
|
+
const result = await engine.render(quill, doc, { format: "pdf" });
|
|
235
|
+
```
|
|
236
|
+
|
|
237
|
+
`fromManifest` is browser-safe (no `node:*` imports) and shares
|
|
238
|
+
`fromBuiltUrl`'s error semantics: `quiver_invalid` for malformed manifest
|
|
239
|
+
bytes, `transport_error` for a `file://` `baseUrl` or a later bundle-fetch
|
|
240
|
+
failure.
|
|
241
|
+
|
|
195
242
|
## Server-side runtime (Node, packed artifact on disk)
|
|
196
243
|
|
|
197
244
|
For server-side rendering where the packed artifact ships in the deployment
|
package/dist/build.d.ts
CHANGED
|
@@ -12,7 +12,7 @@ export type BuildOptions = Record<string, never>;
|
|
|
12
12
|
*
|
|
13
13
|
* Output layout:
|
|
14
14
|
* outDir/
|
|
15
|
-
*
|
|
15
|
+
* latest.json # stable pointer to the current manifest
|
|
16
16
|
* manifest.<md5prefix6>.json # hashed manifest
|
|
17
17
|
* <name>@<version>.<md5>.zip # one bundle per quill
|
|
18
18
|
* store/
|
package/dist/build.js
CHANGED
|
@@ -14,7 +14,7 @@ const FONT_EXT = /\.(ttf|otf|woff|woff2)$/i;
|
|
|
14
14
|
*
|
|
15
15
|
* Output layout:
|
|
16
16
|
* outDir/
|
|
17
|
-
*
|
|
17
|
+
* latest.json # stable pointer to the current manifest
|
|
18
18
|
* manifest.<md5prefix6>.json # hashed manifest
|
|
19
19
|
* <name>@<version>.<md5>.zip # one bundle per quill
|
|
20
20
|
* store/
|
|
@@ -108,9 +108,9 @@ export async function buildQuiver(sourceDir, outDir, _opts) {
|
|
|
108
108
|
catch (err) {
|
|
109
109
|
throw new QuiverError("transport_error", `Failed to write manifest "${manifestPath}": ${err.message}`, { cause: err });
|
|
110
110
|
}
|
|
111
|
-
// 9–10. Write stable pointer
|
|
111
|
+
// 9–10. Write stable pointer latest.json.
|
|
112
112
|
const pointer = { manifest: manifestFileName };
|
|
113
|
-
const pointerPath = join(outDir, "
|
|
113
|
+
const pointerPath = join(outDir, "latest.json");
|
|
114
114
|
try {
|
|
115
115
|
await writeFile(pointerPath, JSON.stringify(pointer), "utf-8");
|
|
116
116
|
}
|
package/dist/built-loader.d.ts
CHANGED
|
@@ -19,9 +19,15 @@ export interface BuiltTransport {
|
|
|
19
19
|
/**
|
|
20
20
|
* Load a build-output quiver via the given transport.
|
|
21
21
|
*
|
|
22
|
-
* 1. Fetches
|
|
22
|
+
* 1. Fetches latest.json (pointer) and parses it.
|
|
23
23
|
* 2. Fetches the manifest file it points to and validates it.
|
|
24
24
|
* 3. Builds a catalog from manifest entries (versions sorted descending).
|
|
25
25
|
* 4. Returns a Quiver instance backed by a BuiltLoader.
|
|
26
26
|
*/
|
|
27
27
|
export declare function loadBuiltQuiver(transport: BuiltTransport): Promise<Quiver>;
|
|
28
|
+
/**
|
|
29
|
+
* Seed a quiver from caller-provided manifest bytes, skipping the latest.json
|
|
30
|
+
* pointer fetch. `transport` is used only for lazy bundle/font fetches.
|
|
31
|
+
* Throws `quiver_invalid` on malformed bytes.
|
|
32
|
+
*/
|
|
33
|
+
export declare function seedBuiltQuiver(transport: BuiltTransport, manifestBytes: Uint8Array): Quiver;
|
package/dist/built-loader.js
CHANGED
|
@@ -96,15 +96,15 @@ function parsePointer(raw) {
|
|
|
96
96
|
parsed = JSON.parse(raw);
|
|
97
97
|
}
|
|
98
98
|
catch {
|
|
99
|
-
throw new QuiverError("quiver_invalid", "
|
|
99
|
+
throw new QuiverError("quiver_invalid", "latest.json contains invalid JSON");
|
|
100
100
|
}
|
|
101
101
|
if (typeof parsed !== "object" || parsed === null || Array.isArray(parsed)) {
|
|
102
|
-
throw new QuiverError("quiver_invalid", "
|
|
102
|
+
throw new QuiverError("quiver_invalid", "latest.json must be a JSON object");
|
|
103
103
|
}
|
|
104
104
|
const obj = parsed;
|
|
105
|
-
assertNoUnknownKeys(obj, ["manifest"], "
|
|
105
|
+
assertNoUnknownKeys(obj, ["manifest"], "latest.json");
|
|
106
106
|
if (typeof obj["manifest"] !== "string" || obj["manifest"].length === 0) {
|
|
107
|
-
throw new QuiverError("quiver_invalid", '
|
|
107
|
+
throw new QuiverError("quiver_invalid", 'latest.json must have a non-empty string "manifest" field');
|
|
108
108
|
}
|
|
109
109
|
return obj["manifest"];
|
|
110
110
|
}
|
|
@@ -175,11 +175,38 @@ function parseManifest(raw) {
|
|
|
175
175
|
quills,
|
|
176
176
|
};
|
|
177
177
|
}
|
|
178
|
-
// ───
|
|
178
|
+
// ─── Catalog assembly ─────────────────────────────────────────────────────────
|
|
179
|
+
/**
|
|
180
|
+
* Validate manifest bytes (`quiver_invalid` on format errors) and assemble a
|
|
181
|
+
* Quiver backed by a BuiltLoader over `transport`. Shared by `loadBuiltQuiver`
|
|
182
|
+
* (pointer-following) and `seedBuiltQuiver` (seed).
|
|
183
|
+
*/
|
|
184
|
+
function buildQuiverFromManifestBytes(transport, manifestBytes) {
|
|
185
|
+
const manifest = parseManifest(new TextDecoder().decode(manifestBytes));
|
|
186
|
+
// catalog: name → versions (desc); index: "name@version" → entry (dedup).
|
|
187
|
+
const catalogRaw = new Map();
|
|
188
|
+
const index = new Map();
|
|
189
|
+
for (const entry of manifest.quills) {
|
|
190
|
+
const key = `${entry.name}@${entry.version}`;
|
|
191
|
+
if (index.has(key)) {
|
|
192
|
+
throw new QuiverError("quiver_invalid", `Duplicate quill entry in manifest: "${key}"`);
|
|
193
|
+
}
|
|
194
|
+
index.set(key, entry);
|
|
195
|
+
const versions = catalogRaw.get(entry.name) ?? [];
|
|
196
|
+
versions.push(entry.version);
|
|
197
|
+
catalogRaw.set(entry.name, versions);
|
|
198
|
+
}
|
|
199
|
+
for (const [, versions] of catalogRaw) {
|
|
200
|
+
versions.sort((a, b) => compareSemver(b, a));
|
|
201
|
+
}
|
|
202
|
+
const loader = new BuiltLoader(transport, index);
|
|
203
|
+
return Quiver._fromLoader(manifest.name, catalogRaw, loader);
|
|
204
|
+
}
|
|
205
|
+
// ─── Main entry points ────────────────────────────────────────────────────────
|
|
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.
|
|
@@ -188,12 +215,12 @@ export async function loadBuiltQuiver(transport) {
|
|
|
188
215
|
// 1. Fetch and parse pointer.
|
|
189
216
|
let pointerBytes;
|
|
190
217
|
try {
|
|
191
|
-
pointerBytes = await transport.fetchBytes("
|
|
218
|
+
pointerBytes = await transport.fetchBytes("latest.json");
|
|
192
219
|
}
|
|
193
220
|
catch (err) {
|
|
194
221
|
if (err instanceof QuiverError)
|
|
195
222
|
throw err;
|
|
196
|
-
throw new QuiverError("transport_error", `Failed to fetch
|
|
223
|
+
throw new QuiverError("transport_error", `Failed to fetch latest.json: ${err.message}`, { cause: err });
|
|
197
224
|
}
|
|
198
225
|
const manifestFileName = parsePointer(new TextDecoder().decode(pointerBytes));
|
|
199
226
|
validateManifestFileName(manifestFileName);
|
|
@@ -207,26 +234,14 @@ export async function loadBuiltQuiver(transport) {
|
|
|
207
234
|
throw err;
|
|
208
235
|
throw new QuiverError("transport_error", `Failed to fetch manifest "${manifestFileName}": ${err.message}`, { cause: err });
|
|
209
236
|
}
|
|
210
|
-
|
|
211
|
-
|
|
212
|
-
|
|
213
|
-
|
|
214
|
-
|
|
215
|
-
|
|
216
|
-
|
|
217
|
-
|
|
218
|
-
|
|
219
|
-
|
|
220
|
-
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
|
-
}
|
|
228
|
-
// 4. Build loader.
|
|
229
|
-
const loader = new BuiltLoader(transport, index);
|
|
230
|
-
// 5. Return Quiver via internal factory.
|
|
231
|
-
return Quiver._fromLoader(manifest.name, catalogRaw, loader);
|
|
237
|
+
// 3–4. Validate manifest + assemble catalog/loader.
|
|
238
|
+
return buildQuiverFromManifestBytes(transport, manifestBytes);
|
|
239
|
+
}
|
|
240
|
+
/**
|
|
241
|
+
* Seed a quiver from caller-provided manifest bytes, skipping the latest.json
|
|
242
|
+
* pointer fetch. `transport` is used only for lazy bundle/font fetches.
|
|
243
|
+
* Throws `quiver_invalid` on malformed bytes.
|
|
244
|
+
*/
|
|
245
|
+
export function seedBuiltQuiver(transport, manifestBytes) {
|
|
246
|
+
return buildQuiverFromManifestBytes(transport, manifestBytes);
|
|
232
247
|
}
|
package/dist/node.d.ts
CHANGED
|
@@ -43,7 +43,7 @@ type NodeQuiverStatics = {
|
|
|
43
43
|
fromDir(pathOrFileUrl: string): Promise<Base>;
|
|
44
44
|
/**
|
|
45
45
|
* Loads a packed (build-output) quiver from a local directory containing
|
|
46
|
-
* `
|
|
46
|
+
* `latest.json` and the manifest/bundle/store files written by
|
|
47
47
|
* `Quiver.build`. Symmetric to `fromBuiltUrl(url)` but reads from disk
|
|
48
48
|
* instead of HTTP — no network required.
|
|
49
49
|
*
|
package/dist/quiver.d.ts
CHANGED
|
@@ -42,6 +42,16 @@ export declare class Quiver {
|
|
|
42
42
|
* on format errors.
|
|
43
43
|
*/
|
|
44
44
|
static fromBuiltUrl(url: string): Promise<Quiver>;
|
|
45
|
+
/**
|
|
46
|
+
* Browser-safe factory. Seeds the catalog from caller-provided manifest
|
|
47
|
+
* bytes — never fetches `latest.json` — then fetches bundles lazily and
|
|
48
|
+
* content-addressed, relative to `baseUrl`, like `fromBuiltUrl`. For SSR
|
|
49
|
+
* consumers that already hold the manifest at build time.
|
|
50
|
+
*
|
|
51
|
+
* Throws `quiver_invalid` on malformed manifest bytes, `transport_error`
|
|
52
|
+
* on a `file://` baseUrl or a later bundle fetch failure.
|
|
53
|
+
*/
|
|
54
|
+
static fromManifest(baseUrl: string, manifestBytes: Uint8Array): Promise<Quiver>;
|
|
45
55
|
/** Returns all known quill names, sorted lexicographically. */
|
|
46
56
|
quillNames(): string[];
|
|
47
57
|
/**
|
package/dist/quiver.js
CHANGED
|
@@ -69,6 +69,24 @@ export class Quiver {
|
|
|
69
69
|
const transport = new HttpTransport(url);
|
|
70
70
|
return loadBuiltQuiver(transport);
|
|
71
71
|
}
|
|
72
|
+
/**
|
|
73
|
+
* Browser-safe factory. Seeds the catalog from caller-provided manifest
|
|
74
|
+
* bytes — never fetches `latest.json` — then fetches bundles lazily and
|
|
75
|
+
* content-addressed, relative to `baseUrl`, like `fromBuiltUrl`. For SSR
|
|
76
|
+
* consumers that already hold the manifest at build time.
|
|
77
|
+
*
|
|
78
|
+
* Throws `quiver_invalid` on malformed manifest bytes, `transport_error`
|
|
79
|
+
* on a `file://` baseUrl or a later bundle fetch failure.
|
|
80
|
+
*/
|
|
81
|
+
static async fromManifest(baseUrl, manifestBytes) {
|
|
82
|
+
if (baseUrl.startsWith("file://")) {
|
|
83
|
+
throw new QuiverError("transport_error", `Quiver.fromManifest requires an http(s):// or origin-relative baseUrl; got "${baseUrl}". For local build output, use Quiver.fromBuiltDir from @quillmark/quiver/node.`);
|
|
84
|
+
}
|
|
85
|
+
const { HttpTransport } = await import("./transports/http-transport.js");
|
|
86
|
+
const { seedBuiltQuiver } = await import("./built-loader.js");
|
|
87
|
+
const transport = new HttpTransport(baseUrl);
|
|
88
|
+
return seedBuiltQuiver(transport, manifestBytes);
|
|
89
|
+
}
|
|
72
90
|
/** Returns all known quill names, sorted lexicographically. */
|
|
73
91
|
quillNames() {
|
|
74
92
|
return [...this.#catalog.keys()].sort();
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@quillmark/quiver",
|
|
3
|
-
"version": "0.
|
|
3
|
+
"version": "0.16.0",
|
|
4
4
|
"description": "Quiver registry and build tooling for Quillmark",
|
|
5
5
|
"type": "module",
|
|
6
6
|
"license": "MIT",
|
|
@@ -57,7 +57,7 @@
|
|
|
57
57
|
"yaml": "^2.8.3"
|
|
58
58
|
},
|
|
59
59
|
"devDependencies": {
|
|
60
|
-
"@quillmark/wasm": "0.
|
|
60
|
+
"@quillmark/wasm": "0.91.0",
|
|
61
61
|
"@types/node": "^25.3.3",
|
|
62
62
|
"typescript": "^5.9.3",
|
|
63
63
|
"vitest": "^4.0.18"
|