@quillmark/quiver 0.13.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 CHANGED
@@ -29,6 +29,24 @@ This keeps the author flow to a single command (`npm publish` or `git tag`)
29
29
  and puts the deployment-topology decision where it belongs: with the
30
30
  consumer.
31
31
 
32
+ ## The render model
33
+
34
+ `@quillmark/quiver` produces quills; `@quillmark/wasm` renders them.
35
+
36
+ - `quiver.getQuill(ref)` returns a core `Quill` — engine-free, portable data,
37
+ good for schema inspection, validation, blueprint access, and seeding. It is
38
+ the **only** way to obtain a quill from a quiver.
39
+ - `const engine = new Engine()` (from the `@quillmark/wasm` root) renders it:
40
+ `await engine.render(quill, doc, opts?)`. The Engine routes on
41
+ `quill.backendId`, lazily loads that backend, clones the quill and document
42
+ into the backend's WASM memory, renders, and frees the clones — so a core
43
+ `Quill` passes straight to `engine.render` with no boundary-crossing step.
44
+
45
+ `Engine.render`, `open`, `supportedFormats`, and `supportsCanvas` are all
46
+ **async** — always `await` them. The canonical `Quill`/`Document`/`Engine`
47
+ types are not re-exported from `@quillmark/quiver`; import them straight from
48
+ the `@quillmark/wasm` peer dependency, which is the single source of truth.
49
+
32
50
  ## Authoring a quiver
33
51
 
34
52
  Lay out the source per the spec, then publish to npm (or push a git tag):
@@ -50,6 +68,14 @@ consumer's build. The harness uses `node:test` (built into Node 18+); no extra
50
68
  test-runner dependency required. If you prefer vitest/jest/mocha, write a
51
69
  12-line loop against the main API instead.
52
70
 
71
+ ```ts
72
+ // quiver.test.ts — run with: node --test
73
+ import { Engine } from "@quillmark/wasm";
74
+ import { runQuiverTests } from "@quillmark/quiver/testing";
75
+
76
+ runQuiverTests(import.meta.url, new Engine());
77
+ ```
78
+
53
79
  ## Manual validation (rendering samples)
54
80
 
55
81
  The CI harness proves every quill _compiles_; it does not produce output a
@@ -58,11 +84,11 @@ human can look at. To eyeball real renders, run the
58
84
 
59
85
  ```ts
60
86
  // scripts/preview.ts — run with: node --experimental-strip-types scripts/preview.ts
61
- import { Quillmark } from "@quillmark/wasm";
87
+ import { Engine } from "@quillmark/wasm";
62
88
  import { renderQuiverSamples } from "@quillmark/quiver/preview";
63
89
 
64
90
  await renderQuiverSamples(import.meta.url, {
65
- engine: new Quillmark(),
91
+ engine: new Engine(),
66
92
  });
67
93
  // → writes ./preview/<name>@<version>.<fmt> + index.html
68
94
  ```
@@ -82,7 +108,7 @@ quill name or canonical ref):
82
108
 
83
109
  ```ts
84
110
  await renderQuiverSamples(import.meta.url, {
85
- engine: new Quillmark(),
111
+ engine: new Engine(),
86
112
  exclude: ["broken-quill"], // or: include: ["memo@1.0.0"]
87
113
  });
88
114
  ```
@@ -95,21 +121,38 @@ await renderQuiverSamples(import.meta.url, {
95
121
  ## Consuming a quiver (Node)
96
122
 
97
123
  ```ts
98
- import { Quillmark, Document } from "@quillmark/wasm";
124
+ import { Engine, Document } from "@quillmark/wasm";
99
125
  import { Quiver } from "@quillmark/quiver/node";
100
126
 
101
- const engine = new Quillmark();
102
127
  const quiver = await Quiver.fromPackage("@org/my-quiver");
128
+ const engine = new Engine();
103
129
 
104
130
  const doc = Document.fromMarkdown(markdownString);
105
- const quill = await quiver.getQuill(doc.quillRef, { engine });
106
- const result = quill.render(doc, { format: "pdf" });
131
+ const quill = await quiver.getQuill(doc.quillRef);
132
+ const result = await engine.render(quill, doc, { format: "pdf" });
107
133
  ```
108
134
 
109
135
  `getQuill` accepts both selector refs (`"memo"`, `"memo@1"`) and canonical
110
136
  refs (`"memo@1.0.0"`). It resolves the selector, materializes the quill via
111
- `engine.quill(tree)`, and caches per (engine, canonical-ref). Concurrent
112
- calls for the same ref share a single load.
137
+ `Quill.fromTree(tree)` (engine-free), and caches one instance per canonical
138
+ ref for the lifetime of the `Quiver`. Concurrent calls for the same ref
139
+ coalesce into a single load.
140
+
141
+ The returned quill lives in the core build's WASM memory and is suitable for
142
+ schema inspection, validation, blueprint access, and seeding. To render it,
143
+ hand it straight to `engine.render(quill, doc)` — the Engine clones both
144
+ handles into the backend on demand, so no separate boundary-crossing step is
145
+ needed:
146
+
147
+ ```ts
148
+ // Editor path — core only, ~0.66 MB gzip
149
+ const coreQuill = await quiver.getQuill(ref);
150
+ coreQuill.schema; // ✓ schema, validate, blueprint, seed — all fine
151
+ const doc = coreQuill.seedDocument(); // a fully-filled example document
152
+
153
+ // Render path — the same core handles render directly.
154
+ const result = await engine.render(coreQuill, doc, { format: "pdf" });
155
+ ```
113
156
 
114
157
  If you only need the canonical ref (without materializing), use `resolve`:
115
158
 
@@ -117,6 +160,10 @@ If you only need the canonical ref (without materializing), use `resolve`:
117
160
  const canonicalRef = await quiver.resolve("memo"); // "memo@1.1.0"
118
161
  ```
119
162
 
163
+ If you need the raw file tree (e.g. to drive a backend binary directly), call
164
+ `(await quiver.getQuill(ref)).toTree()` on the core `Quill` — it is I/O-free
165
+ once the quill is materialized.
166
+
120
167
  ## Consuming a quiver (browser)
121
168
 
122
169
  Browsers cannot read the source layout directly, so build at deploy time and
@@ -134,12 +181,64 @@ await Quiver.build(
134
181
 
135
182
  ```ts
136
183
  // browser runtime
184
+ import { Engine, Document } from "@quillmark/wasm";
137
185
  import { Quiver } from "@quillmark/quiver";
138
186
 
139
187
  const quiver = await Quiver.fromBuiltUrl("/quivers/my-quiver/");
140
- const quill = await quiver.getQuill(doc.quillRef, { engine });
188
+ const engine = new Engine();
189
+
190
+ const doc = Document.fromMarkdown(markdownString);
191
+ const quill = await quiver.getQuill(doc.quillRef);
192
+ const result = await engine.render(quill, doc, { format: "pdf" });
193
+ ```
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" });
141
235
  ```
142
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
+
143
242
  ## Server-side runtime (Node, packed artifact on disk)
144
243
 
145
244
  For server-side rendering where the packed artifact ships in the deployment
@@ -177,15 +276,10 @@ await quiver.warm();
177
276
 
178
277
  `warm()` is I/O-only: it loads every quill's tree (over the network for
179
278
  `fromBuiltUrl`, off the filesystem for `fromPackage` / `fromDir` /
180
- `fromBuiltDir`) and caches
181
- them. It does not require an engine and does not materialize Quill
182
- instances — that happens lazily on the first `getQuill` call, which is
183
- microseconds. A subsequent `getQuill` reuses the cached tree, skipping
184
- the load.
185
-
186
- Once a tree has been turned into a Quill, the cached tree is dropped so
187
- its bytes can be GC'd — the materialized Quill is the runtime artifact.
188
- Calling `warm()` again refills the tree cache.
279
+ `fromBuiltDir`) and caches them. It does not require an engine and does
280
+ not materialize Quill instances — that happens lazily on the first
281
+ `getQuill` call, which is microseconds. A subsequent `getQuill` reuses
282
+ the cached tree, skipping the load.
189
283
 
190
284
  ## Error handling
191
285
 
@@ -206,3 +300,27 @@ try {
206
300
  ```
207
301
 
208
302
  Error codes: `invalid_ref`, `quill_not_found`, `quiver_invalid`, `transport_error`.
303
+
304
+ ## Using `getQuill` vs calling `Quill.fromTree` directly
305
+
306
+ `getQuill(ref)` is the correct entry point for any code that loads a quill
307
+ through a `Quiver`. It handles ref resolution, tree fetching, `Quill.fromTree`
308
+ construction, and per-ref caching in one call. Do **not** reach for
309
+ `Quill.fromTree` directly inside a Quiver consumer:
310
+
311
+ ```ts
312
+ import { Quill } from "@quillmark/wasm";
313
+
314
+ // wrong — bypasses Quiver's cache; duplicates work getQuill already does
315
+ const tree = new Map<string, Uint8Array>();
316
+ tree.set("Quill.yaml", new TextEncoder().encode("name: memo\n..."));
317
+ // ...assemble the rest of the file tree by hand...
318
+ const quill = Quill.fromTree(tree);
319
+
320
+ // right
321
+ const quill = await quiver.getQuill(ref);
322
+ ```
323
+
324
+ `Quill.fromTree` is for code that builds quills **outside** of a Quiver (e.g.
325
+ a server route that receives a raw file tree over the network, or a test
326
+ fixture that constructs a quill from a hand-rolled in-memory tree).
@@ -42,29 +42,35 @@ function hasFlag(name) {
42
42
  // Engine discovery
43
43
  // ---------------------------------------------------------------------------
44
44
  async function loadEngine(cwd) {
45
- // 1. Named export from quiver.config.js, if present.
45
+ // Resolve @quillmark/wasm from the collection's node_modules (best-effort).
46
+ // Its `Engine` renders every quill (cloning the core Quill/Document into the
47
+ // backend itself). A quiver.config.js may export a custom `engine`.
48
+ let wasm;
49
+ try {
50
+ const req = createRequire(pathToFileURL(join(cwd, "package.json")).href);
51
+ const wasmPath = req.resolve("@quillmark/wasm");
52
+ wasm = (await import(pathToFileURL(wasmPath).href));
53
+ }
54
+ catch {
55
+ // Not installed — a quiver.config.js may still provide an engine.
56
+ }
57
+ // 1. Named `engine` export from quiver.config.js, if present.
46
58
  try {
47
59
  const cfg = await import(pathToFileURL(join(cwd, "quiver.config.js")).href);
48
60
  if (cfg.engine != null) {
49
- return { engine: cfg.engine };
61
+ return cfg.engine;
50
62
  }
51
63
  }
52
64
  catch {
53
65
  // File absent or incomplete — fall through to auto-discovery.
54
66
  }
55
- // 2. Auto-discover @quillmark/wasm from the collection's own node_modules.
56
- const req = createRequire(pathToFileURL(join(cwd, "package.json")).href);
57
- let wasmPath;
58
- try {
59
- wasmPath = req.resolve("@quillmark/wasm");
60
- }
61
- catch {
67
+ // 2. Auto-discover from @quillmark/wasm.
68
+ if (wasm == null) {
62
69
  throw new Error("Cannot find @quillmark/wasm in this collection's node_modules.\n" +
63
70
  " Install it: npm install @quillmark/wasm\n" +
64
71
  " Or export { engine } from quiver.config.js for a custom engine.");
65
72
  }
66
- const wasm = (await import(pathToFileURL(wasmPath).href));
67
- return { engine: new wasm.Quillmark() };
73
+ return new wasm.Engine();
68
74
  }
69
75
  // ---------------------------------------------------------------------------
70
76
  // Commands
@@ -78,7 +84,7 @@ async function build() {
78
84
  }
79
85
  async function test() {
80
86
  const cwd = process.cwd();
81
- const { engine } = await loadEngine(cwd);
87
+ const engine = await loadEngine(cwd);
82
88
  const quiver = await Quiver.fromDir(cwd);
83
89
  const names = quiver.quillNames();
84
90
  if (names.length === 0) {
@@ -91,9 +97,15 @@ async function test() {
91
97
  for (const version of quiver.versionsOf(name)) {
92
98
  const ref = `${name}@${version}`;
93
99
  try {
94
- const quill = await quiver.getQuill(ref, { engine });
100
+ const quill = await quiver.getQuill(ref);
95
101
  const doc = quill.seedDocument();
96
- const result = quill.render(doc);
102
+ let result;
103
+ try {
104
+ result = await engine.render(quill, doc);
105
+ }
106
+ finally {
107
+ doc.free();
108
+ }
97
109
  if (!Array.isArray(result.artifacts) || result.artifacts.length === 0) {
98
110
  throw new Error("example render produced no artifacts");
99
111
  }
@@ -113,7 +125,7 @@ async function test() {
113
125
  }
114
126
  async function preview() {
115
127
  const cwd = process.cwd();
116
- const { engine } = await loadEngine(cwd);
128
+ const engine = await loadEngine(cwd);
117
129
  const outDir = flag("--out");
118
130
  const format = flag("--format");
119
131
  const quiet = hasFlag("--quiet");
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
- * Quiver.json # stable pointer
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
- * Quiver.json # stable pointer
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 Quiver.json.
111
+ // 9–10. Write stable pointer latest.json.
112
112
  const pointer = { manifest: manifestFileName };
113
- const pointerPath = join(outDir, "Quiver.json");
113
+ const pointerPath = join(outDir, "latest.json");
114
114
  try {
115
115
  await writeFile(pointerPath, JSON.stringify(pointer), "utf-8");
116
116
  }
@@ -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 Quiver.json (pointer) and parses it.
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;
@@ -96,15 +96,15 @@ function parsePointer(raw) {
96
96
  parsed = JSON.parse(raw);
97
97
  }
98
98
  catch {
99
- throw new QuiverError("quiver_invalid", "Quiver.json contains invalid JSON");
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", "Quiver.json must be a JSON object");
102
+ throw new QuiverError("quiver_invalid", "latest.json must be a JSON object");
103
103
  }
104
104
  const obj = parsed;
105
- assertNoUnknownKeys(obj, ["manifest"], "Quiver.json");
105
+ assertNoUnknownKeys(obj, ["manifest"], "latest.json");
106
106
  if (typeof obj["manifest"] !== "string" || obj["manifest"].length === 0) {
107
- throw new QuiverError("quiver_invalid", 'Quiver.json must have a non-empty string "manifest" field');
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
- // ─── Main entry point ─────────────────────────────────────────────────────────
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 Quiver.json (pointer) and parses it.
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("Quiver.json");
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 Quiver.json: ${err.message}`, { cause: err });
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
- 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
- const index = new Map();
215
- for (const entry of manifest.quills) {
216
- const key = `${entry.name}@${entry.version}`;
217
- if (index.has(key)) {
218
- throw new QuiverError("quiver_invalid", `Duplicate quill entry in manifest: "${key}"`);
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/index.js CHANGED
@@ -5,6 +5,8 @@
5
5
  // live at `@quillmark/quiver/node`.
6
6
  export { QuiverError } from "./errors.js";
7
7
  export { Quiver } from "./quiver.js";
8
- // Engine types (`Quillmark`, `Quill`, `Document`, `RenderResult`, …) are not
9
- // re-exported: import them straight from the `@quillmark/wasm` peer dependency,
10
- // which is the single source of truth for the engine contract.
8
+ // The canonical API (`Quill`, `Document`, `Engine`, `RenderResult`, …) is not
9
+ // re-exported: import it straight from the `@quillmark/wasm` peer dependency,
10
+ // which is the single source of truth. `quiver.getQuill(ref)` returns a core
11
+ // `Quill` that an `Engine` renders directly — the engine clones it into the
12
+ // backend, so no boundary-crossing helper is needed.
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
- * `Quiver.json` and the manifest/bundle/store files written by
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/preview.d.ts CHANGED
@@ -11,12 +11,10 @@
11
11
  *
12
12
  * Usage (place a script next to your Quiver.yaml):
13
13
  *
14
- * import { Quillmark } from "@quillmark/wasm";
14
+ * import { Engine } from "@quillmark/wasm";
15
15
  * import { renderQuiverSamples } from "@quillmark/quiver/preview";
16
16
  *
17
- * await renderQuiverSamples(import.meta.url, {
18
- * engine: new Quillmark(),
19
- * });
17
+ * await renderQuiverSamples(import.meta.url, { engine: new Engine() });
20
18
  * // → open ./preview/index.html
21
19
  *
22
20
  * The sample document is the illustrative example seeded by
@@ -28,10 +26,14 @@
28
26
  * A `.gitignore` is written into `outDir` so the generated artifacts are not
29
27
  * accidentally committed.
30
28
  */
31
- import type { Quillmark } from "@quillmark/wasm";
29
+ import type { Engine } from "@quillmark/wasm";
32
30
  export interface RenderQuiverSamplesOptions {
33
- /** Quillmark engine instance (`new Quillmark()` from `@quillmark/wasm`). */
34
- engine: Quillmark;
31
+ /**
32
+ * Canonical render engine (`new Engine()` from `@quillmark/wasm`). It takes the
33
+ * core `Quill`/`Document` that quiver produces and clones them into the
34
+ * backend on demand — no build-matching or handle-crossing for the caller.
35
+ */
36
+ engine: Engine;
35
37
  /** Directory to write rendered artifacts into. Default: `preview`. */
36
38
  outDir?: string;
37
39
  /** Force an output format (`pdf`/`svg`/`png`/`txt`). Default: engine's choice. */
package/dist/preview.js CHANGED
@@ -11,12 +11,10 @@
11
11
  *
12
12
  * Usage (place a script next to your Quiver.yaml):
13
13
  *
14
- * import { Quillmark } from "@quillmark/wasm";
14
+ * import { Engine } from "@quillmark/wasm";
15
15
  * import { renderQuiverSamples } from "@quillmark/quiver/preview";
16
16
  *
17
- * await renderQuiverSamples(import.meta.url, {
18
- * engine: new Quillmark(),
19
- * });
17
+ * await renderQuiverSamples(import.meta.url, { engine: new Engine() });
20
18
  * // → open ./preview/index.html
21
19
  *
22
20
  * The sample document is the illustrative example seeded by
@@ -31,6 +29,7 @@
31
29
  import { mkdir, writeFile } from "node:fs/promises";
32
30
  import { join } from "node:path";
33
31
  import { Quiver } from "./node.js";
32
+ import { isQuillmarkError } from "@quillmark/wasm";
34
33
  /** Default directory rendered artifacts are written to. */
35
34
  const DEFAULT_OUT_DIR = "preview";
36
35
  /**
@@ -73,13 +72,12 @@ function isSelected(name, ref, opts) {
73
72
  }
74
73
  /**
75
74
  * Formats a thrown render error into one string per diagnostic. `@quillmark/wasm`
76
- * attaches a `diagnostics` array to its errors; fall back to the message when
77
- * an error carries none.
75
+ * throws `QuillmarkError`s (an `Error` carrying `diagnostics`); fall back to
76
+ * the message for anything else.
78
77
  */
79
78
  function failureReasons(err) {
80
- const diagnostics = err.diagnostics;
81
- if (Array.isArray(diagnostics) && diagnostics.length > 0) {
82
- return diagnostics.map((d) => `${d.severity}: ${d.message}`);
79
+ if (isQuillmarkError(err) && err.diagnostics.length > 0) {
80
+ return err.diagnostics.map((d) => `${d.severity}: ${d.message}`);
83
81
  }
84
82
  return [err.message];
85
83
  }
@@ -87,9 +85,16 @@ async function renderOne(quiver, name, version, outDir, opts) {
87
85
  const ref = `${name}@${version}`;
88
86
  let result;
89
87
  try {
90
- const quill = await quiver.getQuill(ref, { engine: opts.engine });
88
+ // Canonical render path: a core Quill + its seeded Document handed straight
89
+ // to the Engine, which clones them into the backend and frees the clones.
90
+ const quill = await quiver.getQuill(ref);
91
91
  const doc = quill.seedDocument();
92
- result = quill.render(doc, opts.format ? { format: opts.format } : undefined);
92
+ try {
93
+ result = await opts.engine.render(quill, doc, opts.format ? { format: opts.format } : undefined);
94
+ }
95
+ finally {
96
+ doc.free();
97
+ }
93
98
  }
94
99
  catch (err) {
95
100
  return {
package/dist/quiver.d.ts CHANGED
@@ -9,7 +9,7 @@
9
9
  * `build`) are installed on this class by `./node.js`, which is the
10
10
  * consumer's explicit opt-in to the Node API surface.
11
11
  */
12
- import type { Quillmark, Quill } from "@quillmark/wasm";
12
+ import { Quill } from "@quillmark/wasm";
13
13
  /** @internal Internal loader strategy: source or build output. */
14
14
  export interface QuiverLoader {
15
15
  loadTree(name: string, version: string): Promise<Map<string, Uint8Array>>;
@@ -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
  /**
@@ -49,16 +59,6 @@ export declare class Quiver {
49
59
  * Returns an empty array if the quill name is not in the catalog.
50
60
  */
51
61
  versionsOf(name: string): string[];
52
- /**
53
- * Lazily loads the file tree for a specific quill version.
54
- *
55
- * Returns `Map<string, Uint8Array>` suitable for `engine.quill(tree)`.
56
- * Does NOT cache the result — caching of materialized Quill instances
57
- * happens in `getQuill`.
58
- *
59
- * Throws `transport_error` if name/version not in catalog or I/O fails.
60
- */
61
- loadTree(name: string, version: string): Promise<Map<string, Uint8Array>>;
62
62
  /**
63
63
  * Resolves a selector ref → canonical ref (e.g. "memo" → "memo@1.1.0").
64
64
  *
@@ -71,29 +71,32 @@ export declare class Quiver {
71
71
  */
72
72
  resolve(ref: string): Promise<string>;
73
73
  /**
74
- * Returns a render-ready `Quill` for a ref (selector or canonical).
74
+ * Returns a `Quill` for a ref (selector or canonical).
75
+ *
76
+ * The returned quill is materialized from the `@quillmark/wasm` root export
77
+ * — it is engine-free, portable data suitable for schema inspection,
78
+ * validation, blueprint access, and document seeding.
79
+ *
80
+ * A core `Quill` renders directly: pass it to `engine.render(quill, doc)`.
75
81
  *
76
- * Selector refs (e.g. `"memo"`, `"memo@1"`) are resolved to canonical
77
- * form first. Materializes via `engine.quill(tree)` on first call;
78
- * caches per (engine, canonical-ref). Reuses a tree cached by `warm()`
79
- * (or a previous `getQuill`) so the network fetch isn't paid twice.
80
- * Concurrent calls for the same ref coalesce into a single load.
82
+ * Selector refs (e.g. `"memo"`, `"memo@1"`) are resolved to canonical form
83
+ * first. Materializes once and caches per canonical ref — concurrent calls
84
+ * coalesce into a single load.
81
85
  *
82
86
  * Throws:
83
87
  * - `invalid_ref` if ref is malformed
84
88
  * - `quill_not_found` if ref does not match any version in this quiver
85
- * - propagates I/O errors from loadTree unchanged
86
- * - propagates engine errors from engine.quill() unchanged
89
+ * - propagates I/O errors from the loader unchanged
90
+ * - propagates validation errors from Quill.fromTree() unchanged
87
91
  */
88
- getQuill(ref: string, opts: {
89
- engine: Quillmark;
90
- }): Promise<Quill>;
92
+ getQuill(ref: string): Promise<Quill>;
91
93
  /**
92
94
  * Prefetches the tree for every quill version in this quiver. Fail-fast.
93
95
  *
94
96
  * Network-bound only — does not materialize Quill instances and does not
95
97
  * require an engine. Subsequent `getQuill` calls reuse the cached trees,
96
- * skipping the fetch. Rejects on the first fetch failure.
98
+ * skipping the fetch (the tree then evicts as its quill materializes).
99
+ * Rejects on the first fetch failure.
97
100
  */
98
101
  warm(): Promise<void>;
99
102
  }
package/dist/quiver.js CHANGED
@@ -10,6 +10,7 @@
10
10
  * consumer's explicit opt-in to the Node API surface.
11
11
  */
12
12
  import { QuiverError } from "./errors.js";
13
+ import { Quill } from "@quillmark/wasm";
13
14
  import { parseQuillRef } from "./ref.js";
14
15
  import { matchesSemverSelector, chooseHighestVersion } from "./semver.js";
15
16
  export class Quiver {
@@ -17,15 +18,16 @@ export class Quiver {
17
18
  #catalog;
18
19
  #loader;
19
20
  /**
20
- * Per-engine cache of materialized quills, keyed by canonical ref.
21
- * WeakMap so engines can be GC'd; Promise values so concurrent
22
- * getQuill calls coalesce into a single materialization.
21
+ * Cache of materialized quills, keyed by canonical ref. A `Quill` is now
22
+ * engine-free, portable data (`Quill.fromTree`), so one instance per ref is
23
+ * shared across every engine. Promise values so concurrent getQuill calls
24
+ * coalesce into a single materialization.
23
25
  */
24
- #quillCache = new WeakMap();
26
+ #quillCache = new Map();
25
27
  /**
26
- * Engine-independent cache of fetched trees, keyed by canonical ref.
27
- * Populated by `warm()` and on first `getQuill` for a ref. Promise
28
- * values so concurrent fetches coalesce.
28
+ * Cache of fetched trees, keyed by canonical ref. Populated by `warm()`
29
+ * and on first `getQuill` for a ref; an entry is evicted once its quill
30
+ * materializes successfully. Promise values so concurrent fetches coalesce.
29
31
  */
30
32
  #treeCache = new Map();
31
33
  /**
@@ -67,6 +69,24 @@ export class Quiver {
67
69
  const transport = new HttpTransport(url);
68
70
  return loadBuiltQuiver(transport);
69
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
+ }
70
90
  /** Returns all known quill names, sorted lexicographically. */
71
91
  quillNames() {
72
92
  return [...this.#catalog.keys()].sort();
@@ -78,22 +98,6 @@ export class Quiver {
78
98
  versionsOf(name) {
79
99
  return [...(this.#catalog.get(name) ?? [])];
80
100
  }
81
- /**
82
- * Lazily loads the file tree for a specific quill version.
83
- *
84
- * Returns `Map<string, Uint8Array>` suitable for `engine.quill(tree)`.
85
- * Does NOT cache the result — caching of materialized Quill instances
86
- * happens in `getQuill`.
87
- *
88
- * Throws `transport_error` if name/version not in catalog or I/O fails.
89
- */
90
- async loadTree(name, version) {
91
- const versions = this.#catalog.get(name);
92
- if (!versions || !versions.includes(version)) {
93
- throw new QuiverError("transport_error", `Quill "${name}@${version}" not found in quiver "${this.name}"`, { quiverName: this.name, version, ref: `${name}@${version}` });
94
- }
95
- return this.#loader.loadTree(name, version);
96
- }
97
101
  /**
98
102
  * Resolves a selector ref → canonical ref (e.g. "memo" → "memo@1.1.0").
99
103
  *
@@ -120,63 +124,65 @@ export class Quiver {
120
124
  throw new QuiverError("quill_not_found", `No quill found for ref "${ref}" in quiver "${this.name}".`, { ref, quiverName: this.name });
121
125
  }
122
126
  /**
123
- * Returns a render-ready `Quill` for a ref (selector or canonical).
127
+ * Returns a `Quill` for a ref (selector or canonical).
128
+ *
129
+ * The returned quill is materialized from the `@quillmark/wasm` root export
130
+ * — it is engine-free, portable data suitable for schema inspection,
131
+ * validation, blueprint access, and document seeding.
124
132
  *
125
- * Selector refs (e.g. `"memo"`, `"memo@1"`) are resolved to canonical
126
- * form first. Materializes via `engine.quill(tree)` on first call;
127
- * caches per (engine, canonical-ref). Reuses a tree cached by `warm()`
128
- * (or a previous `getQuill`) so the network fetch isn't paid twice.
129
- * Concurrent calls for the same ref coalesce into a single load.
133
+ * A core `Quill` renders directly: pass it to `engine.render(quill, doc)`.
134
+ *
135
+ * Selector refs (e.g. `"memo"`, `"memo@1"`) are resolved to canonical form
136
+ * first. Materializes once and caches per canonical ref — concurrent calls
137
+ * coalesce into a single load.
130
138
  *
131
139
  * Throws:
132
140
  * - `invalid_ref` if ref is malformed
133
141
  * - `quill_not_found` if ref does not match any version in this quiver
134
- * - propagates I/O errors from loadTree unchanged
135
- * - propagates engine errors from engine.quill() unchanged
142
+ * - propagates I/O errors from the loader unchanged
143
+ * - propagates validation errors from Quill.fromTree() unchanged
136
144
  */
137
- async getQuill(ref, opts) {
145
+ async getQuill(ref) {
138
146
  const canonicalRef = await this.resolve(ref);
139
- const engine = opts.engine;
140
- let perEngine = this.#quillCache.get(engine);
141
- if (perEngine === undefined) {
142
- perEngine = new Map();
143
- this.#quillCache.set(engine, perEngine);
144
- }
145
- let entry = perEngine.get(canonicalRef);
147
+ let entry = this.#quillCache.get(canonicalRef);
146
148
  if (entry === undefined) {
147
- entry = this.#materializeQuill(canonicalRef, engine).catch((err) => {
148
- perEngine.delete(canonicalRef);
149
+ entry = this.#materializeQuill(canonicalRef).catch((err) => {
150
+ this.#quillCache.delete(canonicalRef);
149
151
  throw err;
150
152
  });
151
- perEngine.set(canonicalRef, entry);
153
+ this.#quillCache.set(canonicalRef, entry);
152
154
  }
153
155
  return entry;
154
156
  }
155
157
  /**
156
- * Internal: load tree (cached) + invoke engine.quill. Errors propagate.
157
- *
158
- * On success, evicts the tree from the cache so its bytes can be GC'd —
159
- * the materialized Quill is the runtime artifact; the tree is dead weight
160
- * once a Quill exists. On failure, the tree is retained so retries skip
161
- * the network.
158
+ * Internal: load tree (cached) + construct via Quill.fromTree. Errors
159
+ * propagate. On success the tree is evicted from `#treeCache` (the quill
160
+ * cache now holds the materialized result), so the tree is not retained
161
+ * past materialization. On `Quill.fromTree` failure the tree stays cached,
162
+ * so a retry skips the refetch.
162
163
  */
163
- async #materializeQuill(canonicalRef, engine) {
164
- const tree = await this.#getTreeCached(canonicalRef);
165
- const quill = engine.quill(tree);
166
- this.#treeCache.delete(canonicalRef);
164
+ async #materializeQuill(canonicalRef) {
165
+ const entry = this.#getTreeCached(canonicalRef);
166
+ const tree = await entry;
167
+ const quill = Quill.fromTree(tree);
168
+ // Evict by identity: only drop the entry if it is still the promise this
169
+ // materialization consumed (a concurrent refetch may have replaced it).
170
+ if (this.#treeCache.get(canonicalRef) === entry) {
171
+ this.#treeCache.delete(canonicalRef);
172
+ }
167
173
  return quill;
168
174
  }
169
175
  /**
170
- * Internal: tree cache reader. On miss, fetches via `loadTree` and stores
176
+ * Internal: tree cache reader. On miss, fetches via `#loadTree` and stores
171
177
  * the in-flight Promise. On rejection, evicts so a retry can succeed.
172
178
  */
173
- async #getTreeCached(canonicalRef) {
179
+ #getTreeCached(canonicalRef) {
174
180
  let entry = this.#treeCache.get(canonicalRef);
175
181
  if (entry === undefined) {
176
182
  const at = canonicalRef.indexOf("@");
177
183
  const name = canonicalRef.slice(0, at);
178
184
  const version = canonicalRef.slice(at + 1);
179
- entry = this.loadTree(name, version).catch((err) => {
185
+ entry = this.#loadTree(name, version).catch((err) => {
180
186
  this.#treeCache.delete(canonicalRef);
181
187
  throw err;
182
188
  });
@@ -184,12 +190,27 @@ export class Quiver {
184
190
  }
185
191
  return entry;
186
192
  }
193
+ /**
194
+ * Internal: validates name/version against the catalog, then delegates to
195
+ * the loader. Returns `Map<string, Uint8Array>` suitable for
196
+ * `Quill.fromTree(tree)`. Does NOT cache — caching lives in `#getTreeCached`.
197
+ *
198
+ * Throws `transport_error` if name/version not in catalog or I/O fails.
199
+ */
200
+ async #loadTree(name, version) {
201
+ const versions = this.#catalog.get(name);
202
+ if (!versions || !versions.includes(version)) {
203
+ throw new QuiverError("transport_error", `Quill "${name}@${version}" not found in quiver "${this.name}"`, { quiverName: this.name, version, ref: `${name}@${version}` });
204
+ }
205
+ return this.#loader.loadTree(name, version);
206
+ }
187
207
  /**
188
208
  * Prefetches the tree for every quill version in this quiver. Fail-fast.
189
209
  *
190
210
  * Network-bound only — does not materialize Quill instances and does not
191
211
  * require an engine. Subsequent `getQuill` calls reuse the cached trees,
192
- * skipping the fetch. Rejects on the first fetch failure.
212
+ * skipping the fetch (the tree then evicts as its quill materializes).
213
+ * Rejects on the first fetch failure.
193
214
  */
194
215
  async warm() {
195
216
  const promises = [];
package/dist/testing.d.ts CHANGED
@@ -7,14 +7,13 @@
7
7
  *
8
8
  * Usage (place this file next to your Quiver.yaml):
9
9
  *
10
- * import { Quillmark } from "@quillmark/wasm";
10
+ * import { Engine } from "@quillmark/wasm";
11
11
  * import { runQuiverTests } from "@quillmark/quiver/testing";
12
- * const engine = new Quillmark();
13
- * runQuiverTests(import.meta.url, engine);
12
+ * runQuiverTests(import.meta.url, new Engine());
14
13
  *
15
14
  * Run with `node --test`.
16
15
  */
17
- import type { Quillmark } from "@quillmark/wasm";
16
+ import type { Engine } from "@quillmark/wasm";
18
17
  /**
19
18
  * Registers a `node:test` describe block that validates every quill
20
19
  * version in the quiver at `metaUrlOrDir` against the provided engine.
@@ -23,7 +22,7 @@ import type { Quillmark } from "@quillmark/wasm";
23
22
  * to Quiver.yaml). Pass an absolute directory path for any other layout.
24
23
  *
25
24
  * Validation covers the full loading pipeline: Quiver.yaml, Quill.yaml,
26
- * all template files, engine compilation via engine.quill(tree), and a
27
- * full render of each quill's example document.
25
+ * all template files, quill construction via Quill.fromTree(tree), and a
26
+ * full render of each quill's example document via engine.render(quill, doc).
28
27
  */
29
- export declare function runQuiverTests(metaUrlOrDir: string, engine: Quillmark): void;
28
+ export declare function runQuiverTests(metaUrlOrDir: string, engine: Engine): void;
package/dist/testing.js CHANGED
@@ -7,10 +7,9 @@
7
7
  *
8
8
  * Usage (place this file next to your Quiver.yaml):
9
9
  *
10
- * import { Quillmark } from "@quillmark/wasm";
10
+ * import { Engine } from "@quillmark/wasm";
11
11
  * import { runQuiverTests } from "@quillmark/quiver/testing";
12
- * const engine = new Quillmark();
13
- * runQuiverTests(import.meta.url, engine);
12
+ * runQuiverTests(import.meta.url, new Engine());
14
13
  *
15
14
  * Run with `node --test`.
16
15
  */
@@ -27,8 +26,8 @@ import { Quiver } from "./node.js";
27
26
  * to Quiver.yaml). Pass an absolute directory path for any other layout.
28
27
  *
29
28
  * Validation covers the full loading pipeline: Quiver.yaml, Quill.yaml,
30
- * all template files, engine compilation via engine.quill(tree), and a
31
- * full render of each quill's example document.
29
+ * all template files, quill construction via Quill.fromTree(tree), and a
30
+ * full render of each quill's example document via engine.render(quill, doc).
32
31
  */
33
32
  export function runQuiverTests(metaUrlOrDir, engine) {
34
33
  describe("Quiver", () => {
@@ -45,9 +44,18 @@ export function runQuiverTests(metaUrlOrDir, engine) {
45
44
  for (const name of quiver.quillNames()) {
46
45
  for (const version of quiver.versionsOf(name)) {
47
46
  const ref = `${name}@${version}`;
48
- const quill = await quiver.getQuill(ref, { engine });
47
+ // The Engine takes the core Quill + its seeded Document directly and
48
+ // clones them into the backend; the doc clone is freed inside render,
49
+ // and we free our own core Document here.
50
+ const quill = await quiver.getQuill(ref);
49
51
  const doc = quill.seedDocument();
50
- const result = quill.render(doc);
52
+ let result;
53
+ try {
54
+ result = await engine.render(quill, doc);
55
+ }
56
+ finally {
57
+ doc.free();
58
+ }
51
59
  if (!Array.isArray(result.artifacts) || result.artifacts.length === 0) {
52
60
  throw new Error(`${ref}: example render produced no artifacts`);
53
61
  }
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@quillmark/quiver",
3
- "version": "0.13.0",
3
+ "version": "0.16.0",
4
4
  "description": "Quiver registry and build tooling for Quillmark",
5
5
  "type": "module",
6
6
  "license": "MIT",
@@ -20,7 +20,7 @@
20
20
  ],
21
21
  "author": "Quillmark Contributors",
22
22
  "engines": {
23
- "node": ">=24"
23
+ "node": ">=22"
24
24
  },
25
25
  "exports": {
26
26
  ".": {
@@ -50,14 +50,14 @@
50
50
  "README.md"
51
51
  ],
52
52
  "peerDependencies": {
53
- "@quillmark/wasm": ">=0.88.0"
53
+ "@quillmark/wasm": ">=0.90.0-0"
54
54
  },
55
55
  "dependencies": {
56
56
  "fflate": "^0.8.2",
57
57
  "yaml": "^2.8.3"
58
58
  },
59
59
  "devDependencies": {
60
- "@quillmark/wasm": "^0.88.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"
@@ -67,6 +67,7 @@
67
67
  },
68
68
  "scripts": {
69
69
  "build": "tsc",
70
+ "prepack": "rm -rf dist && tsc",
70
71
  "test": "vitest run",
71
72
  "lint": "tsc --noEmit"
72
73
  }