@quillmark/quiver 0.2.0 → 0.4.0

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
package/README.md CHANGED
@@ -1,6 +1,6 @@
1
1
  # @quillmark/quiver
2
2
 
3
- Quiver registry and packaging for Quillmark — load, compose, and pack collections of quills for rendering with `@quillmark/wasm`.
3
+ Load and build collections of quills for rendering with `@quillmark/wasm`.
4
4
 
5
5
  ## Install
6
6
 
@@ -8,69 +8,122 @@ Quiver registry and packaging for Quillmark — load, compose, and pack collecti
8
8
  npm install @quillmark/quiver @quillmark/wasm
9
9
  ```
10
10
 
11
- ## Quick start
11
+ ## Distribution model
12
+
13
+ A Quiver has one authored shape: the **source layout** (`Quiver.yaml` at the
14
+ package root, quills under `quills/<name>/<x.y.z>/`). Authors publish it as
15
+ an npm package. Consumers decide how to consume it:
16
+
17
+ - **Node consumers** load the source layout directly with `Quiver.fromPackage`.
18
+ - **Browser consumers** run `Quiver.build(...)` as a build step and serve the
19
+ output as static assets, loading it with `Quiver.fromBuilt`.
20
+
21
+ Each loader names exactly what it loads: `fromPackage` and `fromDir` always
22
+ read source layouts; `fromBuilt` always reads build output over HTTP/HTTPS.
23
+ No auto-detection, no branching on artifact shape.
24
+
25
+ This keeps the author flow to a single command (`npm publish` or `git tag`)
26
+ and puts the deployment-topology decision where it belongs: with the
27
+ consumer.
28
+
29
+ ## Authoring a quiver
30
+
31
+ Lay out the source per the spec, then publish to npm (or push a git tag):
32
+
33
+ ```
34
+ my-quiver/
35
+ Quiver.yaml
36
+ quills/
37
+ <name>/<x.y.z>/
38
+ Quill.yaml
39
+ ...
40
+ package.json
41
+ ```
42
+
43
+ Recommended CI: use the bundled `@quillmark/quiver/testing` harness — it
44
+ loads with `Quiver.fromDir` and exercises every quill so validation errors
45
+ surface on publish, not on the consumer's build. The harness uses
46
+ `node:test` (built into Node 18+); no extra test-runner dependency
47
+ required. If you prefer vitest/jest/mocha, write a 12-line loop against
48
+ the main API instead.
49
+
50
+ ## Consuming a quiver (Node)
12
51
 
13
52
  ```ts
14
53
  import { Quillmark, Document } from "@quillmark/wasm";
15
- import { Quiver, QuiverRegistry } from "@quillmark/quiver/node";
16
-
17
- // 1. Load a source quiver from disk (Node.js only)
18
- const quiver = await Quiver.fromSourceDir("./my-quiver");
54
+ import { Quiver } from "@quillmark/quiver/node";
19
55
 
20
- // 2. Build a registry with one or more quivers
21
56
  const engine = new Quillmark();
22
- const registry = new QuiverRegistry({ engine, quivers: [quiver] });
57
+ const quiver = await Quiver.fromPackage("@org/my-quiver");
23
58
 
24
- // 3. Resolve a ref, obtain a render-ready quill, and render
25
59
  const doc = Document.fromMarkdown(markdownString);
26
- const canonicalRef = await registry.resolve(doc.quillRef);
27
- const quill = await registry.getQuill(canonicalRef);
60
+ const quill = await quiver.getQuill(doc.quillRef, { engine });
28
61
  const result = quill.render(doc, { format: "pdf" });
29
62
  ```
30
63
 
31
- ## HTTP (browser / CDN)
64
+ `getQuill` accepts both selector refs (`"memo"`, `"memo@1"`) and canonical
65
+ refs (`"memo@1.0.0"`). It resolves the selector, materializes the quill via
66
+ `engine.quill(tree)`, and caches per (engine, canonical-ref). Concurrent
67
+ calls for the same ref share a single load.
32
68
 
33
- ```ts
34
- import { Quiver, QuiverRegistry } from "@quillmark/quiver";
69
+ If you only need the canonical ref (without materializing), use `resolve`:
35
70
 
36
- const quiver = await Quiver.fromHttp("https://cdn.example.com/my-quiver");
37
- const registry = new QuiverRegistry({ engine, quivers: [quiver] });
71
+ ```ts
72
+ const canonicalRef = await quiver.resolve("memo"); // "memo@1.1.0"
38
73
  ```
39
74
 
40
- ## Packed directory (Node.js)
75
+ ## Consuming a quiver (browser)
76
+
77
+ Browsers cannot read the source layout directly, so build at deploy time and
78
+ serve the output as static files:
41
79
 
42
80
  ```ts
81
+ // build script (Node) — typically wired into your existing build pipeline
43
82
  import { Quiver } from "@quillmark/quiver/node";
44
83
 
45
- const quiver = await Quiver.fromPackedDir("./dist/my-quiver");
84
+ await Quiver.build(
85
+ "./node_modules/@org/my-quiver",
86
+ "./public/quivers/my-quiver",
87
+ );
46
88
  ```
47
89
 
48
- ## Pack a source quiver
49
-
50
90
  ```ts
51
- import { Quiver } from "@quillmark/quiver/node";
91
+ // browser runtime
92
+ import { Quiver } from "@quillmark/quiver";
52
93
 
53
- await Quiver.pack("./my-quiver", "./dist/my-quiver");
94
+ const quiver = await Quiver.fromBuilt("/quivers/my-quiver/");
95
+ const quill = await quiver.getQuill(doc.quillRef, { engine });
54
96
  ```
55
97
 
56
- ## Warm (prefetch all quills)
98
+ ## Advanced: pre-built distribution to a CDN
99
+
100
+ If you need to ship the runtime artifact directly (e.g. consumers cannot run
101
+ a Node build step), publish `Quiver.build` output to a CDN and have
102
+ consumers point `fromBuilt` at the CDN URL:
57
103
 
58
104
  ```ts
59
- await registry.warm();
60
- ```
105
+ import { Quiver } from "@quillmark/quiver/node";
61
106
 
62
- ## Multi-quiver composition
107
+ await Quiver.build("./my-quiver", "./dist/my-quiver");
108
+ // upload ./dist/my-quiver to https://cdn.example.com/quivers/my-quiver/
109
+ const quiver = await Quiver.fromBuilt("https://cdn.example.com/quivers/my-quiver/");
110
+ ```
63
111
 
64
- Quivers are scanned in order. The first quiver with any matching candidate wins;
65
- the highest matching version within that quiver is returned.
112
+ ## Warm (prefetch all quill trees)
66
113
 
67
114
  ```ts
68
- const registry = new QuiverRegistry({
69
- engine,
70
- quivers: [primaryQuiver, fallbackQuiver],
71
- });
115
+ await quiver.warm();
72
116
  ```
73
117
 
118
+ `warm()` is network-only: it fetches every quill's tree and caches them.
119
+ It does not require an engine and does not materialize Quill instances —
120
+ that happens lazily on the first `getQuill` call, which is microseconds.
121
+ A subsequent `getQuill` reuses the cached tree, skipping the fetch.
122
+
123
+ Once a tree has been turned into a Quill, the cached tree is dropped so
124
+ its bytes can be GC'd — the materialized Quill is the runtime artifact.
125
+ Calling `warm()` again refills the tree cache.
126
+
74
127
  ## Error handling
75
128
 
76
129
  All errors are instances of `QuiverError` with a `code` field.
@@ -79,7 +132,7 @@ All errors are instances of `QuiverError` with a `code` field.
79
132
  import { QuiverError } from "@quillmark/quiver";
80
133
 
81
134
  try {
82
- const canonicalRef = await registry.resolve("unknown_quill");
135
+ await quiver.resolve("unknown_quill");
83
136
  } catch (err) {
84
137
  if (err instanceof QuiverError) {
85
138
  console.error(err.code); // e.g. "quill_not_found"
@@ -89,8 +142,8 @@ try {
89
142
  }
90
143
  ```
91
144
 
92
- Error codes: `invalid_ref`, `quill_not_found`, `quiver_invalid`, `transport_error`, `quiver_collision`.
145
+ Error codes: `invalid_ref`, `quill_not_found`, `quiver_invalid`, `transport_error`.
93
146
 
94
147
  ## Full specification
95
148
 
96
- See [PROGRAM.md](./PROGRAM.md) for the complete API surface, packed format specification, and design decisions.
149
+ See [PROGRAM.md](./PROGRAM.md) for the complete API surface, runtime artifact format specification, and design decisions.
@@ -0,0 +1,25 @@
1
+ /**
2
+ * Build logic — internal, Node-only.
3
+ *
4
+ * All Node.js built-in imports are done dynamically inside `buildQuiver`
5
+ * so that a type-only import of `BuildOptions` from `src/index.ts` does
6
+ * NOT pull `node:fs` or `node:crypto` into browser bundles.
7
+ */
8
+ /** Reserved for future build options (e.g. compression level, filters). */
9
+ export type BuildOptions = Record<string, never>;
10
+ /**
11
+ * Reads a Source Quiver, validates it, and writes the build output to outDir.
12
+ *
13
+ * Output layout:
14
+ * outDir/
15
+ * Quiver.json # stable pointer
16
+ * manifest.<md5prefix6>.json # hashed manifest
17
+ * <name>@<version>.<md5>.zip # one bundle per quill
18
+ * store/
19
+ * <md5> # dehydrated font bytes (full hash, no ext)
20
+ *
21
+ * Throws:
22
+ * - `quiver_invalid` on source validation failures (propagated from scanner)
23
+ * - `transport_error` on I/O failures
24
+ */
25
+ export declare function buildQuiver(sourceDir: string, outDir: string, _opts?: BuildOptions): Promise<void>;
@@ -1,16 +1,16 @@
1
1
  /**
2
- * Pack logic — internal, Node-only.
2
+ * Build logic — internal, Node-only.
3
3
  *
4
- * All Node.js built-in imports are done dynamically inside `packQuiver` so
5
- * that a type-only import of `PackOptions` from `src/index.ts` does NOT
6
- * pull `node:fs` or `node:crypto` into browser bundles.
4
+ * All Node.js built-in imports are done dynamically inside `buildQuiver`
5
+ * so that a type-only import of `BuildOptions` from `src/index.ts` does
6
+ * NOT pull `node:fs` or `node:crypto` into browser bundles.
7
7
  */
8
8
  import { QuiverError } from "./errors.js";
9
9
  import { packFiles } from "./bundle.js";
10
- /** Font file extensions recognised by the packer (case-insensitive). */
10
+ /** Font file extensions recognised by the builder (case-insensitive). */
11
11
  const FONT_EXT = /\.(ttf|otf|woff|woff2)$/i;
12
12
  /**
13
- * Reads a Source Quiver, validates it, and writes a Packed Quiver to outDir.
13
+ * Reads a Source Quiver, validates it, and writes the build output to outDir.
14
14
  *
15
15
  * Output layout:
16
16
  * outDir/
@@ -24,7 +24,7 @@ const FONT_EXT = /\.(ttf|otf|woff|woff2)$/i;
24
24
  * - `quiver_invalid` on source validation failures (propagated from scanner)
25
25
  * - `transport_error` on I/O failures
26
26
  */
27
- export async function packQuiver(sourceDir, outDir, _opts) {
27
+ export async function buildQuiver(sourceDir, outDir, _opts) {
28
28
  // Dynamic imports keep this module safe to type-import from browser contexts.
29
29
  const { join } = await import("node:path");
30
30
  const { mkdir, rm, writeFile, } = await import("node:fs/promises");
@@ -1,27 +1,27 @@
1
1
  /**
2
- * Packed Quiver loader — browser-safe at module level.
2
+ * Built-quiver loader — browser-safe at module level.
3
3
  * Internal; not exported from index.ts.
4
4
  *
5
5
  * Exposes:
6
- * - PackedTransport interface (also used by FsTransport / HttpTransport)
7
- * - loadPackedQuiver(transport) → Quiver
6
+ * - BuiltTransport interface (implemented by HttpTransport)
7
+ * - loadBuiltQuiver(transport) → Quiver
8
8
  *
9
9
  * NO static node: imports — this module is safe to load in browser contexts.
10
10
  */
11
11
  import { Quiver } from "./quiver.js";
12
12
  /**
13
13
  * Transport abstraction: fetch raw bytes by relative path within the packed
14
- * artifact. Implementations are FsTransport (Node) and HttpTransport (browser).
14
+ * artifact. Sole implementation is HttpTransport (browser + Node).
15
15
  */
16
- export interface PackedTransport {
16
+ export interface BuiltTransport {
17
17
  fetchBytes(relativePath: string): Promise<Uint8Array>;
18
18
  }
19
19
  /**
20
- * Load a Packed Quiver via the given transport.
20
+ * Load a build-output quiver via the given transport.
21
21
  *
22
22
  * 1. Fetches Quiver.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
- * 4. Returns a Quiver instance backed by a PackedLoader.
25
+ * 4. Returns a Quiver instance backed by a BuiltLoader.
26
26
  */
27
- export declare function loadPackedQuiver(transport: PackedTransport): Promise<Quiver>;
27
+ export declare function loadBuiltQuiver(transport: BuiltTransport): Promise<Quiver>;
@@ -1,10 +1,10 @@
1
1
  /**
2
- * Packed Quiver loader — browser-safe at module level.
2
+ * Built-quiver loader — browser-safe at module level.
3
3
  * Internal; not exported from index.ts.
4
4
  *
5
5
  * Exposes:
6
- * - PackedTransport interface (also used by FsTransport / HttpTransport)
7
- * - loadPackedQuiver(transport) → Quiver
6
+ * - BuiltTransport interface (implemented by HttpTransport)
7
+ * - loadBuiltQuiver(transport) → Quiver
8
8
  *
9
9
  * NO static node: imports — this module is safe to load in browser contexts.
10
10
  */
@@ -31,8 +31,8 @@ function validateFontHash(hash, context) {
31
31
  throw new QuiverError("quiver_invalid", `${context}: font hash is invalid: "${hash}"`);
32
32
  }
33
33
  }
34
- // ─── PackedLoader implementation ─────────────────────────────────────────────
35
- class PackedLoader {
34
+ // ─── BuiltLoader implementation ─────────────────────────────────────────────
35
+ class BuiltLoader {
36
36
  transport;
37
37
  index;
38
38
  /** Font byte cache: hash → in-flight or resolved Promise. */
@@ -50,7 +50,7 @@ class PackedLoader {
50
50
  // through. The transport will surface a transport_error naturally.
51
51
  // (Defensive: this should not be reachable in normal operation.)
52
52
  if (!entry) {
53
- throw new QuiverError("transport_error", `Quill "${name}@${version}" not found in packed quiver manifest`, { version, ref: `${name}@${version}` });
53
+ throw new QuiverError("transport_error", `Quill "${name}@${version}" not found in built-quiver manifest`, { version, ref: `${name}@${version}` });
54
54
  }
55
55
  // 1. Fetch + unpack bundle zip.
56
56
  const zipBytes = await this.transport.fetchBytes(entry.bundle);
@@ -177,14 +177,14 @@ function parseManifest(raw) {
177
177
  }
178
178
  // ─── Main entry point ─────────────────────────────────────────────────────────
179
179
  /**
180
- * Load a Packed Quiver via the given transport.
180
+ * Load a build-output quiver via the given transport.
181
181
  *
182
182
  * 1. Fetches Quiver.json (pointer) and parses it.
183
183
  * 2. Fetches the manifest file it points to and validates it.
184
184
  * 3. Builds a catalog from manifest entries (versions sorted descending).
185
- * 4. Returns a Quiver instance backed by a PackedLoader.
185
+ * 4. Returns a Quiver instance backed by a BuiltLoader.
186
186
  */
187
- export async function loadPackedQuiver(transport) {
187
+ export async function loadBuiltQuiver(transport) {
188
188
  // 1. Fetch and parse pointer.
189
189
  let pointerBytes;
190
190
  try {
@@ -226,7 +226,7 @@ export async function loadPackedQuiver(transport) {
226
226
  versions.sort((a, b) => compareSemver(b, a));
227
227
  }
228
228
  // 4. Build loader.
229
- const loader = new PackedLoader(transport, index);
229
+ const loader = new BuiltLoader(transport, index);
230
230
  // 5. Return Quiver via internal factory.
231
231
  return Quiver._fromLoader(manifest.name, catalogRaw, loader);
232
232
  }
@@ -11,7 +11,7 @@
11
11
  * test doubles) satisfy the contract structurally.
12
12
  *
13
13
  * These types are INTERNAL — never re-exported from index.ts. They exist so
14
- * registry.ts never imports from @quillmark/wasm directly and so test doubles
14
+ * quiver.ts never imports from @quillmark/wasm directly and so test doubles
15
15
  * can satisfy the contract without pulling the real WASM module.
16
16
  *
17
17
  * Call-site note: Quiver never invokes `render` or `open` itself; consumers do
@@ -11,7 +11,7 @@
11
11
  * test doubles) satisfy the contract structurally.
12
12
  *
13
13
  * These types are INTERNAL — never re-exported from index.ts. They exist so
14
- * registry.ts never imports from @quillmark/wasm directly and so test doubles
14
+ * quiver.ts never imports from @quillmark/wasm directly and so test doubles
15
15
  * can satisfy the contract without pulling the real WASM module.
16
16
  *
17
17
  * Call-site note: Quiver never invokes `render` or `open` itself; consumers do
package/dist/errors.d.ts CHANGED
@@ -1,4 +1,4 @@
1
- export type QuiverErrorCode = "invalid_ref" | "quill_not_found" | "quiver_invalid" | "transport_error" | "quiver_collision";
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/index.d.ts CHANGED
@@ -1,5 +1,5 @@
1
1
  export { QuiverError } from "./errors.js";
2
2
  export type { QuiverErrorCode } from "./errors.js";
3
3
  export { Quiver } from "./quiver.js";
4
- export { QuiverRegistry } from "./registry.js";
5
- export type { PackOptions } from "./pack.js";
4
+ export type { BuildOptions } from "./build.js";
5
+ export type { QuillmarkLike, QuillLike } from "./engine-types.js";
package/dist/index.js CHANGED
@@ -1,4 +1,3 @@
1
1
  // Main browser-safe entrypoint.
2
2
  export { QuiverError } from "./errors.js";
3
3
  export { Quiver } from "./quiver.js";
4
- export { QuiverRegistry } from "./registry.js";
package/dist/node.js CHANGED
@@ -1,5 +1,5 @@
1
1
  // Node-only entrypoint.
2
2
  // Re-export everything from the main browser-safe entrypoint.
3
- // Node-only factories (fromSourceDir, fromPackedDir, pack) are methods on
3
+ // Node-only factories (fromPackage, fromDir, build) are methods on
4
4
  // Quiver itself — no additional exports are needed here.
5
5
  export * from "./index.js";
package/dist/quiver.d.ts CHANGED
@@ -2,10 +2,11 @@
2
2
  * Quiver — primary runtime abstraction for a collection of quills.
3
3
  *
4
4
  * Polymorphism via composition: internally stores a pluggable loader
5
- * (either source-backed or packed-backed).
5
+ * (either source-backed or build-output-backed).
6
6
  */
7
- import type { PackOptions } from "./pack.js";
8
- /** @internal Internal loader strategy: source or packed. */
7
+ import type { BuildOptions } from "./build.js";
8
+ import type { QuillmarkLike, QuillLike } from "./engine-types.js";
9
+ /** @internal Internal loader strategy: source or build output. */
9
10
  export interface QuiverLoader {
10
11
  loadTree(name: string, version: string): Promise<Map<string, Uint8Array>>;
11
12
  }
@@ -18,33 +19,40 @@ export declare class Quiver {
18
19
  * Static methods inside can still call it.
19
20
  */
20
21
  private constructor();
21
- /** @internal Used by loadPackedQuiver. Not part of the public API. */
22
+ /** @internal Used by loadBuiltQuiver. Not part of the public API. */
22
23
  static _fromLoader(name: string, catalog: Map<string, string[]>, loader: QuiverLoader): Quiver;
23
24
  /**
24
- * Node-only factory. Reads a Source Quiver from a directory containing
25
- * `Quiver.yaml` and `quills/<name>/<version>/Quill.yaml` entries.
25
+ * Node-only factory. Resolves an npm specifier against `node_modules` and
26
+ * loads the source layout at the package root.
26
27
  *
27
- * Uses dynamic import of `./source-loader.js` so that importing this module
28
- * in a browser environment does not cause a crash at module evaluation time.
28
+ * The resolved package must have `Quiver.yaml` at its root.
29
29
  *
30
- * Throws `quiver_invalid` on schema violations, `transport_error` on I/O failure.
30
+ * Throws `transport_error` on resolution/I/O failure, `quiver_invalid`
31
+ * on schema violations.
31
32
  */
32
- static fromSourceDir(path: string): Promise<Quiver>;
33
+ static fromPackage(specifier: string): Promise<Quiver>;
33
34
  /**
34
- * Node-only factory. Loads a Packed Quiver from a local directory.
35
+ * Node-only factory. Reads a Source Quiver from a local directory containing
36
+ * `Quiver.yaml` and `quills/<name>/<version>/Quill.yaml` entries.
35
37
  *
36
- * Uses dynamic imports so this module stays browser-safe at evaluation time.
38
+ * Also accepts `import.meta.url`-style `file://` URLs as a convenience for
39
+ * tests; the URL's parent directory is used as the source root.
37
40
  *
38
- * Throws `transport_error` on I/O failure, `quiver_invalid` on format errors.
41
+ * Throws `quiver_invalid` on schema violations, `transport_error` on I/O failure.
39
42
  */
40
- static fromPackedDir(path: string): Promise<Quiver>;
43
+ static fromDir(pathOrFileUrl: string): Promise<Quiver>;
41
44
  /**
42
- * Browser-safe factory. Loads a Packed Quiver from an HTTP base URL.
45
+ * Browser-safe factory. Loads build output from an HTTP/HTTPS URL.
43
46
  *
44
- * Throws `transport_error` on network/HTTP failure, `quiver_invalid` on
45
- * format errors.
47
+ * Origin-relative URLs (e.g. `/quivers/foo/`) are accepted in browser
48
+ * environments. `file://` URLs are rejected — local build output is
49
+ * not loadable in V1; serve over HTTP or use `fromPackage`/`fromDir`
50
+ * against the source.
51
+ *
52
+ * Throws `transport_error` on network/HTTP failure, `quiver_invalid`
53
+ * on format errors.
46
54
  */
47
- static fromHttp(url: string): Promise<Quiver>;
55
+ static fromBuilt(url: string): Promise<Quiver>;
48
56
  /** Returns all known quill names, sorted lexicographically. */
49
57
  quillNames(): string[];
50
58
  /**
@@ -53,22 +61,61 @@ export declare class Quiver {
53
61
  */
54
62
  versionsOf(name: string): string[];
55
63
  /**
56
- * Node-only tooling. Writes a Packed Quiver artifact to outDir.
64
+ * Node-only tooling. Reads the Source Quiver at sourceDir, validates it,
65
+ * and writes the runtime build artifact to outDir.
57
66
  *
58
- * Uses dynamic import of `./pack.js` so that this module stays browser-safe
59
- * at evaluation time.
67
+ * Uses dynamic import of `./build.js` so that this module stays
68
+ * browser-safe at evaluation time.
60
69
  *
61
70
  * Throws `quiver_invalid` on source validation failures,
62
71
  * `transport_error` on I/O failures.
63
72
  */
64
- static pack(sourceDir: string, outDir: string, opts?: PackOptions): Promise<void>;
73
+ static build(sourceDir: string, outDir: string, opts?: BuildOptions): Promise<void>;
65
74
  /**
66
75
  * Lazily loads the file tree for a specific quill version.
67
76
  *
68
77
  * Returns `Map<string, Uint8Array>` suitable for `engine.quill(tree)`.
69
- * Does NOT cache the result — caching is the registry's concern.
78
+ * Does NOT cache the result — caching of materialized Quill instances
79
+ * happens in `getQuill`.
70
80
  *
71
81
  * Throws `transport_error` if name/version not in catalog or I/O fails.
72
82
  */
73
83
  loadTree(name: string, version: string): Promise<Map<string, Uint8Array>>;
84
+ /**
85
+ * Resolves a selector ref → canonical ref (e.g. "memo" → "memo@1.1.0").
86
+ *
87
+ * Selector forms: `name`, `name@x`, `name@x.y`, `name@x.y.z`. Picks the
88
+ * highest matching version in this quiver.
89
+ *
90
+ * Throws:
91
+ * - `invalid_ref` if ref fails parseQuillRef
92
+ * - `quill_not_found` if no version matches
93
+ */
94
+ resolve(ref: string): Promise<string>;
95
+ /**
96
+ * Returns a render-ready `Quill` for a ref (selector or canonical).
97
+ *
98
+ * Selector refs (e.g. `"memo"`, `"memo@1"`) are resolved to canonical
99
+ * form first. Materializes via `engine.quill(tree)` on first call;
100
+ * caches per (engine, canonical-ref). Reuses a tree cached by `warm()`
101
+ * (or a previous `getQuill`) so the network fetch isn't paid twice.
102
+ * Concurrent calls for the same ref coalesce into a single load.
103
+ *
104
+ * Throws:
105
+ * - `invalid_ref` if ref is malformed
106
+ * - `quill_not_found` if ref does not match any version in this quiver
107
+ * - propagates I/O errors from loadTree unchanged
108
+ * - propagates engine errors from engine.quill() unchanged
109
+ */
110
+ getQuill(ref: string, opts: {
111
+ engine: QuillmarkLike;
112
+ }): Promise<QuillLike>;
113
+ /**
114
+ * Prefetches the tree for every quill version in this quiver. Fail-fast.
115
+ *
116
+ * Network-bound only — does not materialize Quill instances and does not
117
+ * require an engine. Subsequent `getQuill` calls reuse the cached trees,
118
+ * skipping the fetch. Rejects on the first fetch failure.
119
+ */
120
+ warm(): Promise<void>;
74
121
  }