@quillmark/quiver 0.12.0 → 0.13.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 +5 -10
- package/dist/bin/quiver.d.ts +2 -2
- package/dist/bin/quiver.js +10 -14
- package/dist/index.d.ts +0 -1
- package/dist/index.js +3 -0
- package/dist/node.d.ts +0 -1
- package/dist/node.js +3 -0
- package/dist/preview.d.ts +8 -18
- package/dist/preview.js +7 -9
- package/dist/quiver.d.ts +3 -3
- package/dist/testing.d.ts +4 -5
- package/dist/testing.js +4 -4
- package/package.json +3 -4
- package/PROGRAM.md +0 -512
- package/dist/engine-types.d.ts +0 -40
- package/dist/engine-types.js +0 -20
package/README.md
CHANGED
|
@@ -58,19 +58,19 @@ human can look at. To eyeball real renders, run the
|
|
|
58
58
|
|
|
59
59
|
```ts
|
|
60
60
|
// scripts/preview.ts — run with: node --experimental-strip-types scripts/preview.ts
|
|
61
|
-
import { Quillmark
|
|
61
|
+
import { Quillmark } from "@quillmark/wasm";
|
|
62
62
|
import { renderQuiverSamples } from "@quillmark/quiver/preview";
|
|
63
63
|
|
|
64
64
|
await renderQuiverSamples(import.meta.url, {
|
|
65
65
|
engine: new Quillmark(),
|
|
66
|
-
Document,
|
|
67
66
|
});
|
|
68
67
|
// → writes ./preview/<name>@<version>.<fmt> + index.html
|
|
69
68
|
```
|
|
70
69
|
|
|
71
|
-
It renders every quill's illustrative example document (
|
|
72
|
-
fully filled-out, always-renderable sample; the
|
|
73
|
-
`<must-fill>` sentinels and is not directly
|
|
70
|
+
It renders every quill's illustrative example document (seeded via
|
|
71
|
+
`quill.seedDocument()` — a fully filled-out, always-renderable sample; the
|
|
72
|
+
blueprint itself carries `<must-fill>` sentinels and is not directly
|
|
73
|
+
renderable), writes the artifacts
|
|
74
74
|
to `outDir` (default `preview/`), and emits an `index.html`
|
|
75
75
|
gallery. A `.gitignore` is written into `outDir` so the generated artifacts
|
|
76
76
|
are never accidentally committed. A quill that throws is recorded as failed —
|
|
@@ -83,7 +83,6 @@ quill name or canonical ref):
|
|
|
83
83
|
```ts
|
|
84
84
|
await renderQuiverSamples(import.meta.url, {
|
|
85
85
|
engine: new Quillmark(),
|
|
86
|
-
Document,
|
|
87
86
|
exclude: ["broken-quill"], // or: include: ["memo@1.0.0"]
|
|
88
87
|
});
|
|
89
88
|
```
|
|
@@ -207,7 +206,3 @@ try {
|
|
|
207
206
|
```
|
|
208
207
|
|
|
209
208
|
Error codes: `invalid_ref`, `quill_not_found`, `quiver_invalid`, `transport_error`.
|
|
210
|
-
|
|
211
|
-
## Full specification
|
|
212
|
-
|
|
213
|
-
See [PROGRAM.md](./PROGRAM.md) for the complete API surface, runtime artifact format specification, and design decisions.
|
package/dist/bin/quiver.d.ts
CHANGED
|
@@ -9,8 +9,8 @@
|
|
|
9
9
|
* [--include <ref>...] [--exclude <ref>...]
|
|
10
10
|
*
|
|
11
11
|
* Engine discovery (for `test` and `preview`):
|
|
12
|
-
* 1. Named
|
|
13
|
-
*
|
|
12
|
+
* 1. Named export `engine` from `quiver.config.js` at the collection root,
|
|
13
|
+
* if the file exists.
|
|
14
14
|
* 2. Auto-imported `@quillmark/wasm` from the collection's node_modules.
|
|
15
15
|
*/
|
|
16
16
|
export {};
|
package/dist/bin/quiver.js
CHANGED
|
@@ -9,8 +9,8 @@
|
|
|
9
9
|
* [--include <ref>...] [--exclude <ref>...]
|
|
10
10
|
*
|
|
11
11
|
* Engine discovery (for `test` and `preview`):
|
|
12
|
-
* 1. Named
|
|
13
|
-
*
|
|
12
|
+
* 1. Named export `engine` from `quiver.config.js` at the collection root,
|
|
13
|
+
* if the file exists.
|
|
14
14
|
* 2. Auto-imported `@quillmark/wasm` from the collection's node_modules.
|
|
15
15
|
*/
|
|
16
16
|
import { createRequire } from "node:module";
|
|
@@ -42,14 +42,11 @@ function hasFlag(name) {
|
|
|
42
42
|
// Engine discovery
|
|
43
43
|
// ---------------------------------------------------------------------------
|
|
44
44
|
async function loadEngine(cwd) {
|
|
45
|
-
// 1. Named
|
|
45
|
+
// 1. Named export from quiver.config.js, if present.
|
|
46
46
|
try {
|
|
47
47
|
const cfg = await import(pathToFileURL(join(cwd, "quiver.config.js")).href);
|
|
48
|
-
if (cfg.engine != null
|
|
49
|
-
return {
|
|
50
|
-
engine: cfg.engine,
|
|
51
|
-
Document: cfg.Document,
|
|
52
|
-
};
|
|
48
|
+
if (cfg.engine != null) {
|
|
49
|
+
return { engine: cfg.engine };
|
|
53
50
|
}
|
|
54
51
|
}
|
|
55
52
|
catch {
|
|
@@ -64,10 +61,10 @@ async function loadEngine(cwd) {
|
|
|
64
61
|
catch {
|
|
65
62
|
throw new Error("Cannot find @quillmark/wasm in this collection's node_modules.\n" +
|
|
66
63
|
" Install it: npm install @quillmark/wasm\n" +
|
|
67
|
-
" Or export { engine
|
|
64
|
+
" Or export { engine } from quiver.config.js for a custom engine.");
|
|
68
65
|
}
|
|
69
66
|
const wasm = (await import(pathToFileURL(wasmPath).href));
|
|
70
|
-
return { engine: new wasm.Quillmark()
|
|
67
|
+
return { engine: new wasm.Quillmark() };
|
|
71
68
|
}
|
|
72
69
|
// ---------------------------------------------------------------------------
|
|
73
70
|
// Commands
|
|
@@ -81,7 +78,7 @@ async function build() {
|
|
|
81
78
|
}
|
|
82
79
|
async function test() {
|
|
83
80
|
const cwd = process.cwd();
|
|
84
|
-
const { engine
|
|
81
|
+
const { engine } = await loadEngine(cwd);
|
|
85
82
|
const quiver = await Quiver.fromDir(cwd);
|
|
86
83
|
const names = quiver.quillNames();
|
|
87
84
|
if (names.length === 0) {
|
|
@@ -95,7 +92,7 @@ async function test() {
|
|
|
95
92
|
const ref = `${name}@${version}`;
|
|
96
93
|
try {
|
|
97
94
|
const quill = await quiver.getQuill(ref, { engine });
|
|
98
|
-
const doc =
|
|
95
|
+
const doc = quill.seedDocument();
|
|
99
96
|
const result = quill.render(doc);
|
|
100
97
|
if (!Array.isArray(result.artifacts) || result.artifacts.length === 0) {
|
|
101
98
|
throw new Error("example render produced no artifacts");
|
|
@@ -116,7 +113,7 @@ async function test() {
|
|
|
116
113
|
}
|
|
117
114
|
async function preview() {
|
|
118
115
|
const cwd = process.cwd();
|
|
119
|
-
const { engine
|
|
116
|
+
const { engine } = await loadEngine(cwd);
|
|
120
117
|
const outDir = flag("--out");
|
|
121
118
|
const format = flag("--format");
|
|
122
119
|
const quiet = hasFlag("--quiet");
|
|
@@ -124,7 +121,6 @@ async function preview() {
|
|
|
124
121
|
const exclude = multiFlag("--exclude");
|
|
125
122
|
await renderQuiverSamples(cwd, {
|
|
126
123
|
engine,
|
|
127
|
-
Document,
|
|
128
124
|
...(outDir !== undefined && { outDir }),
|
|
129
125
|
...(format !== undefined && { format }),
|
|
130
126
|
quiet,
|
package/dist/index.d.ts
CHANGED
package/dist/index.js
CHANGED
|
@@ -5,3 +5,6 @@
|
|
|
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.
|
package/dist/node.d.ts
CHANGED
|
@@ -79,4 +79,3 @@ export declare const Quiver: typeof Base & NodeQuiverStatics;
|
|
|
79
79
|
export { QuiverError } from "./errors.js";
|
|
80
80
|
export type { QuiverErrorCode } from "./errors.js";
|
|
81
81
|
export type { BuildOptions } from "./build.js";
|
|
82
|
-
export type { QuillmarkLike, QuillLike } from "./engine-types.js";
|
package/dist/node.js
CHANGED
|
@@ -70,3 +70,6 @@ Quiver.buildPackage = async function buildPackage(specifier, outDir, opts) {
|
|
|
70
70
|
// 3. Re-export the rest of the public surface so consumers get one import.
|
|
71
71
|
// ---------------------------------------------------------------------------
|
|
72
72
|
export { QuiverError } from "./errors.js";
|
|
73
|
+
// Engine types (`Quillmark`, `Quill`, `Document`, `RenderResult`, …) are not
|
|
74
|
+
// re-exported: import them straight from the `@quillmark/wasm` peer dependency,
|
|
75
|
+
// which is the single source of truth for the engine contract.
|
package/dist/preview.d.ts
CHANGED
|
@@ -11,37 +11,27 @@
|
|
|
11
11
|
*
|
|
12
12
|
* Usage (place a script next to your Quiver.yaml):
|
|
13
13
|
*
|
|
14
|
-
* import { Quillmark
|
|
14
|
+
* import { Quillmark } from "@quillmark/wasm";
|
|
15
15
|
* import { renderQuiverSamples } from "@quillmark/quiver/preview";
|
|
16
16
|
*
|
|
17
17
|
* await renderQuiverSamples(import.meta.url, {
|
|
18
18
|
* engine: new Quillmark(),
|
|
19
|
-
* Document,
|
|
20
19
|
* });
|
|
21
20
|
* // → open ./preview/index.html
|
|
22
21
|
*
|
|
23
|
-
* The sample document is the illustrative example
|
|
24
|
-
* quill version — a fully filled-out,
|
|
25
|
-
* blueprint itself carries `<must-fill>`
|
|
26
|
-
* Every quill always
|
|
27
|
-
* sample document.
|
|
22
|
+
* The sample document is the illustrative example seeded by
|
|
23
|
+
* `quill.seedDocument()` for each quill version — a fully filled-out,
|
|
24
|
+
* always-renderable document (the blueprint itself carries `<must-fill>`
|
|
25
|
+
* sentinels and is not renderable). Every quill always seeds an example, so no
|
|
26
|
+
* quills are skipped for lack of a sample document.
|
|
28
27
|
*
|
|
29
28
|
* A `.gitignore` is written into `outDir` so the generated artifacts are not
|
|
30
29
|
* accidentally committed.
|
|
31
30
|
*/
|
|
32
|
-
import type {
|
|
33
|
-
/**
|
|
34
|
-
* Structural shape of the `Document` class from `@quillmark/wasm`. Only the
|
|
35
|
-
* `fromMarkdown` factory is used; passing the real class satisfies this.
|
|
36
|
-
*/
|
|
37
|
-
export interface DocumentFactoryLike {
|
|
38
|
-
fromMarkdown(markdown: string): unknown;
|
|
39
|
-
}
|
|
31
|
+
import type { Quillmark } from "@quillmark/wasm";
|
|
40
32
|
export interface RenderQuiverSamplesOptions {
|
|
41
33
|
/** Quillmark engine instance (`new Quillmark()` from `@quillmark/wasm`). */
|
|
42
|
-
engine:
|
|
43
|
-
/** The `Document` class from `@quillmark/wasm`. */
|
|
44
|
-
Document: DocumentFactoryLike;
|
|
34
|
+
engine: Quillmark;
|
|
45
35
|
/** Directory to write rendered artifacts into. Default: `preview`. */
|
|
46
36
|
outDir?: string;
|
|
47
37
|
/** Force an output format (`pdf`/`svg`/`png`/`txt`). Default: engine's choice. */
|
package/dist/preview.js
CHANGED
|
@@ -11,20 +11,19 @@
|
|
|
11
11
|
*
|
|
12
12
|
* Usage (place a script next to your Quiver.yaml):
|
|
13
13
|
*
|
|
14
|
-
* import { Quillmark
|
|
14
|
+
* import { Quillmark } from "@quillmark/wasm";
|
|
15
15
|
* import { renderQuiverSamples } from "@quillmark/quiver/preview";
|
|
16
16
|
*
|
|
17
17
|
* await renderQuiverSamples(import.meta.url, {
|
|
18
18
|
* engine: new Quillmark(),
|
|
19
|
-
* Document,
|
|
20
19
|
* });
|
|
21
20
|
* // → open ./preview/index.html
|
|
22
21
|
*
|
|
23
|
-
* The sample document is the illustrative example
|
|
24
|
-
* quill version — a fully filled-out,
|
|
25
|
-
* blueprint itself carries `<must-fill>`
|
|
26
|
-
* Every quill always
|
|
27
|
-
* sample document.
|
|
22
|
+
* The sample document is the illustrative example seeded by
|
|
23
|
+
* `quill.seedDocument()` for each quill version — a fully filled-out,
|
|
24
|
+
* always-renderable document (the blueprint itself carries `<must-fill>`
|
|
25
|
+
* sentinels and is not renderable). Every quill always seeds an example, so no
|
|
26
|
+
* quills are skipped for lack of a sample document.
|
|
28
27
|
*
|
|
29
28
|
* A `.gitignore` is written into `outDir` so the generated artifacts are not
|
|
30
29
|
* accidentally committed.
|
|
@@ -89,8 +88,7 @@ async function renderOne(quiver, name, version, outDir, opts) {
|
|
|
89
88
|
let result;
|
|
90
89
|
try {
|
|
91
90
|
const quill = await quiver.getQuill(ref, { engine: opts.engine });
|
|
92
|
-
const
|
|
93
|
-
const doc = opts.Document.fromMarkdown(markdown);
|
|
91
|
+
const doc = quill.seedDocument();
|
|
94
92
|
result = quill.render(doc, opts.format ? { format: opts.format } : undefined);
|
|
95
93
|
}
|
|
96
94
|
catch (err) {
|
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 {
|
|
12
|
+
import type { Quillmark, 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>>;
|
|
@@ -86,8 +86,8 @@ export declare class Quiver {
|
|
|
86
86
|
* - propagates engine errors from engine.quill() unchanged
|
|
87
87
|
*/
|
|
88
88
|
getQuill(ref: string, opts: {
|
|
89
|
-
engine:
|
|
90
|
-
}): Promise<
|
|
89
|
+
engine: Quillmark;
|
|
90
|
+
}): Promise<Quill>;
|
|
91
91
|
/**
|
|
92
92
|
* Prefetches the tree for every quill version in this quiver. Fail-fast.
|
|
93
93
|
*
|
package/dist/testing.d.ts
CHANGED
|
@@ -7,15 +7,14 @@
|
|
|
7
7
|
*
|
|
8
8
|
* Usage (place this file next to your Quiver.yaml):
|
|
9
9
|
*
|
|
10
|
-
* import { Quillmark
|
|
10
|
+
* import { Quillmark } from "@quillmark/wasm";
|
|
11
11
|
* import { runQuiverTests } from "@quillmark/quiver/testing";
|
|
12
12
|
* const engine = new Quillmark();
|
|
13
|
-
* runQuiverTests(import.meta.url, engine
|
|
13
|
+
* runQuiverTests(import.meta.url, engine);
|
|
14
14
|
*
|
|
15
15
|
* Run with `node --test`.
|
|
16
16
|
*/
|
|
17
|
-
import type {
|
|
18
|
-
import type { DocumentFactoryLike } from "./preview.js";
|
|
17
|
+
import type { Quillmark } from "@quillmark/wasm";
|
|
19
18
|
/**
|
|
20
19
|
* Registers a `node:test` describe block that validates every quill
|
|
21
20
|
* version in the quiver at `metaUrlOrDir` against the provided engine.
|
|
@@ -27,4 +26,4 @@ import type { DocumentFactoryLike } from "./preview.js";
|
|
|
27
26
|
* all template files, engine compilation via engine.quill(tree), and a
|
|
28
27
|
* full render of each quill's example document.
|
|
29
28
|
*/
|
|
30
|
-
export declare function runQuiverTests(metaUrlOrDir: string, engine:
|
|
29
|
+
export declare function runQuiverTests(metaUrlOrDir: string, engine: Quillmark): void;
|
package/dist/testing.js
CHANGED
|
@@ -7,10 +7,10 @@
|
|
|
7
7
|
*
|
|
8
8
|
* Usage (place this file next to your Quiver.yaml):
|
|
9
9
|
*
|
|
10
|
-
* import { Quillmark
|
|
10
|
+
* import { Quillmark } from "@quillmark/wasm";
|
|
11
11
|
* import { runQuiverTests } from "@quillmark/quiver/testing";
|
|
12
12
|
* const engine = new Quillmark();
|
|
13
|
-
* runQuiverTests(import.meta.url, engine
|
|
13
|
+
* runQuiverTests(import.meta.url, engine);
|
|
14
14
|
*
|
|
15
15
|
* Run with `node --test`.
|
|
16
16
|
*/
|
|
@@ -30,7 +30,7 @@ import { Quiver } from "./node.js";
|
|
|
30
30
|
* all template files, engine compilation via engine.quill(tree), and a
|
|
31
31
|
* full render of each quill's example document.
|
|
32
32
|
*/
|
|
33
|
-
export function runQuiverTests(metaUrlOrDir, engine
|
|
33
|
+
export function runQuiverTests(metaUrlOrDir, engine) {
|
|
34
34
|
describe("Quiver", () => {
|
|
35
35
|
let quiver;
|
|
36
36
|
before(async () => {
|
|
@@ -46,7 +46,7 @@ export function runQuiverTests(metaUrlOrDir, engine, Document) {
|
|
|
46
46
|
for (const version of quiver.versionsOf(name)) {
|
|
47
47
|
const ref = `${name}@${version}`;
|
|
48
48
|
const quill = await quiver.getQuill(ref, { engine });
|
|
49
|
-
const doc =
|
|
49
|
+
const doc = quill.seedDocument();
|
|
50
50
|
const result = quill.render(doc);
|
|
51
51
|
if (!Array.isArray(result.artifacts) || result.artifacts.length === 0) {
|
|
52
52
|
throw new Error(`${ref}: example render produced no artifacts`);
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@quillmark/quiver",
|
|
3
|
-
"version": "0.
|
|
3
|
+
"version": "0.13.0",
|
|
4
4
|
"description": "Quiver registry and build tooling for Quillmark",
|
|
5
5
|
"type": "module",
|
|
6
6
|
"license": "MIT",
|
|
@@ -47,18 +47,17 @@
|
|
|
47
47
|
],
|
|
48
48
|
"files": [
|
|
49
49
|
"dist",
|
|
50
|
-
"PROGRAM.md",
|
|
51
50
|
"README.md"
|
|
52
51
|
],
|
|
53
52
|
"peerDependencies": {
|
|
54
|
-
"@quillmark/wasm": ">=0.
|
|
53
|
+
"@quillmark/wasm": ">=0.88.0"
|
|
55
54
|
},
|
|
56
55
|
"dependencies": {
|
|
57
56
|
"fflate": "^0.8.2",
|
|
58
57
|
"yaml": "^2.8.3"
|
|
59
58
|
},
|
|
60
59
|
"devDependencies": {
|
|
61
|
-
"@quillmark/wasm": "^0.
|
|
60
|
+
"@quillmark/wasm": "^0.88.0",
|
|
62
61
|
"@types/node": "^25.3.3",
|
|
63
62
|
"typescript": "^5.9.3",
|
|
64
63
|
"vitest": "^4.0.18"
|
package/PROGRAM.md
DELETED
|
@@ -1,512 +0,0 @@
|
|
|
1
|
-
# Program: Quivers Rewrite (`@quillmark/quiver`)
|
|
2
|
-
|
|
3
|
-
**Status:** Updated for upstream Quillmark Render API overhaul
|
|
4
|
-
**Audience:** Senior engineer planning and executing V1
|
|
5
|
-
**Scope:** This document applies only to the **Node/npm** package **`@quillmark/quiver`**: a smaller surface than `@quillmark/registry`, aligned with the **JavaScript/WASM** Quillmark bindings (not Python or other language bindings).
|
|
6
|
-
|
|
7
|
-
## Summary
|
|
8
|
-
|
|
9
|
-
V1 still introduces Quivers as the primary runtime abstraction, but one boundary changed materially upstream:
|
|
10
|
-
|
|
11
|
-
- Quillmark no longer owns a quill registry
|
|
12
|
-
- Rendering lives on `Quill` (`quill.render(...)`), not the engine
|
|
13
|
-
- Engine is now a backend registry + quill factory (`engine.quill(tree)` on the Quillmark WASM binding, typically `@quillmark/wasm`)
|
|
14
|
-
|
|
15
|
-
This means Quiver must own quill selection and lifecycle entirely. There is no engine `registerQuill`, `hasQuill`, or engine-level render hot path to optimize around.
|
|
16
|
-
|
|
17
|
-
---
|
|
18
|
-
|
|
19
|
-
## Core Decisions
|
|
20
|
-
|
|
21
|
-
### 1) One Authored Shape; A Build Output Derived From It
|
|
22
|
-
|
|
23
|
-
There is one user-facing shape: the **Source Quiver**.
|
|
24
|
-
|
|
25
|
-
- Human-authored, git-friendly
|
|
26
|
-
- Root `Quiver.yaml`
|
|
27
|
-
- Quills under `quills/<name>/<version>/Quill.yaml`
|
|
28
|
-
- Assets in normal source layout
|
|
29
|
-
|
|
30
|
-
A build step (`Quiver.build`) derives a **runtime artifact** from the source.
|
|
31
|
-
This artifact is not a peer "format" — it is the source layout's `dist/`
|
|
32
|
-
output, optimized for browser delivery (hashed manifest, per-quill bundle
|
|
33
|
-
zips, dehydrated shared font store, stable pointer file). Authors do not
|
|
34
|
-
version it, publish it, or check it into git.
|
|
35
|
-
|
|
36
|
-
Loaders are paired 1:1 with what they load:
|
|
37
|
-
|
|
38
|
-
- `Quiver.fromPackage(specifier)` and `Quiver.fromDir(path)` load the
|
|
39
|
-
**source** layout (Node only).
|
|
40
|
-
- `Quiver.fromBuiltUrl(url)` loads the **build output** over HTTP/HTTPS
|
|
41
|
-
(browser-safe; works in Node too).
|
|
42
|
-
- `Quiver.fromBuiltDir(path)` loads the **build output** from a local
|
|
43
|
-
directory (Node only).
|
|
44
|
-
|
|
45
|
-
There is no auto-detection: each loader's name commits to what it expects.
|
|
46
|
-
|
|
47
|
-
**Naming decisions:**
|
|
48
|
-
- The verb is `build()`. Pairs with the `fromBuilt*` family (past participle
|
|
49
|
-
= "the built one"). Avoids collision with `npm pack`, JS bundler
|
|
50
|
-
vocabulary, and the internal "bundle zips" term.
|
|
51
|
-
- Loaders for source layouts are named by **where the source lives**
|
|
52
|
-
(`Package` / `Dir`). Loaders for build output are named by **what** they
|
|
53
|
-
load (`Built`) plus **where** they load it from (`Url` / `Dir`). The
|
|
54
|
-
artifact type stays in the name; the transport disambiguates.
|
|
55
|
-
- Server-side runtime (Node) reads packed artifacts from disk via
|
|
56
|
-
`fromBuiltDir`. This is the recommended shape when the runtime artifact
|
|
57
|
-
ships in the deployment image: source quivers stay as devDependencies,
|
|
58
|
-
there is no self-fetch over a load balancer / CDN, and serverless bundle
|
|
59
|
-
sizes are minimised.
|
|
60
|
-
|
|
61
|
-
### 2) `Quiver.yaml` Is Required in Source Quivers
|
|
62
|
-
|
|
63
|
-
Source Quivers require root `Quiver.yaml` metadata.
|
|
64
|
-
|
|
65
|
-
Fields for V1:
|
|
66
|
-
|
|
67
|
-
- `name` (required) — runtime namespace identity; may differ from npm package name. Charset: alphanumeric only (`[A-Za-z0-9_-]+`).
|
|
68
|
-
- `description` (optional) — tooling-only metadata in V1; not consumed by runtime paths.
|
|
69
|
-
|
|
70
|
-
Unknown fields in `Quiver.yaml` are a **validation error** (`quiver_invalid`). Strict-by-default keeps forward compatibility explicit: any future field is additive and requires a schema bump.
|
|
71
|
-
|
|
72
|
-
`package.json.version` remains npm-channel identity; packed artifact identity remains hashed-manifest based.
|
|
73
|
-
|
|
74
|
-
### 3) `QuillSource` Becomes Quiver-Centric
|
|
75
|
-
|
|
76
|
-
- Re-express `QuillSource` concepts around Quivers
|
|
77
|
-
- Four loaders, each with one input shape and one shape-of-thing-loaded:
|
|
78
|
-
- `Quiver.fromPackage(specifier)` — Node-only; resolves an npm specifier
|
|
79
|
-
against `node_modules` and loads the source layout at the package root
|
|
80
|
-
- `Quiver.fromDir(path)` — Node-only; loads source layout from a local
|
|
81
|
-
directory. Also accepts `import.meta.url`-style `file://` URLs as a
|
|
82
|
-
convenience for tests (the URL's parent directory is used)
|
|
83
|
-
- `Quiver.fromBuiltUrl(url)` — browser-safe; loads build output from an
|
|
84
|
-
`http(s)://` or origin-relative URL
|
|
85
|
-
- `Quiver.fromBuiltDir(path)` — Node-only; loads build output from a
|
|
86
|
-
local directory (the output of `Quiver.build`)
|
|
87
|
-
- "Transport" is not a first-class concept; HTTP fetching is an internal
|
|
88
|
-
detail of `fromBuiltUrl`, filesystem access is an internal detail of
|
|
89
|
-
`fromBuiltDir`
|
|
90
|
-
|
|
91
|
-
### 4) Single-Quiver Scope (V1)
|
|
92
|
-
|
|
93
|
-
V1 has no multi-quiver composition layer. Each `Quiver` instance is
|
|
94
|
-
independent: consumers load one quiver and call `resolve` / `getQuill` /
|
|
95
|
-
`warm` directly on it. There is no `QuiverRegistry`.
|
|
96
|
-
|
|
97
|
-
If composition becomes a real use case, the additive path is a thin
|
|
98
|
-
`Quiver.compose([a, b, ...])` factory that returns a quiver-shaped
|
|
99
|
-
composite — it is intentionally out of scope for V1.
|
|
100
|
-
|
|
101
|
-
### 5) Semver Selector Rules Are Strict and Small
|
|
102
|
-
|
|
103
|
-
Supported forms:
|
|
104
|
-
|
|
105
|
-
- `name` (highest version in first-winning quiver)
|
|
106
|
-
- `name@x.y.z` exact
|
|
107
|
-
- `name@x.y` highest `x.y.*`
|
|
108
|
-
- `name@x` highest `x.*.*`
|
|
109
|
-
|
|
110
|
-
Not supported in V1:
|
|
111
|
-
|
|
112
|
-
- ranges (`>=`, `<`, etc.)
|
|
113
|
-
- npm operators (`^`, `~`)
|
|
114
|
-
- wildcards (`*`)
|
|
115
|
-
- prerelease/build metadata
|
|
116
|
-
|
|
117
|
-
Canonical version format:
|
|
118
|
-
|
|
119
|
-
- `x.y.z` only
|
|
120
|
-
- applies to quill version directories (`quills/<name>/<version>/`)
|
|
121
|
-
- non-canonical versions on disk are validation errors
|
|
122
|
-
|
|
123
|
-
Canonicalization:
|
|
124
|
-
|
|
125
|
-
- resolve selector to canonical ref once per call to `getQuill`
|
|
126
|
-
- key the tree cache by canonical ref; key the quill cache by (engine, canonical-ref)
|
|
127
|
-
|
|
128
|
-
### 6) Warm/Prefetch Is Purely a Quiver Concern
|
|
129
|
-
|
|
130
|
-
`warm()` is the network prefetch step. It fetches every quill's tree and
|
|
131
|
-
populates the per-quiver tree cache. It does **not** materialize Quill
|
|
132
|
-
instances and does **not** require an engine — `engine.quill(tree)` is
|
|
133
|
-
microseconds and runs lazily inside `getQuill`. A subsequent `getQuill`
|
|
134
|
-
reuses the cached tree, so the network fetch isn't paid twice.
|
|
135
|
-
|
|
136
|
-
Tree cache lifecycle:
|
|
137
|
-
|
|
138
|
-
- `warm()` populates the tree cache.
|
|
139
|
-
- First `getQuill(ref, { engine })` reads the tree, materializes the
|
|
140
|
-
Quill via `engine.quill(tree)`, then evicts the tree so its bytes can
|
|
141
|
-
be GC'd. The materialized Quill is what's kept (per engine).
|
|
142
|
-
- If `engine.quill` throws, the tree is retained so a retry skips the
|
|
143
|
-
network.
|
|
144
|
-
- Repeated `getQuill` on the same engine hits the per-engine quill cache
|
|
145
|
-
— no tree access at all.
|
|
146
|
-
- A subsequent `getQuill` for a different engine refetches the tree
|
|
147
|
-
(single-engine apps never pay this cost).
|
|
148
|
-
|
|
149
|
-
Other invariants:
|
|
150
|
-
|
|
151
|
-
- `resolve()` works whether or not anything is warmed.
|
|
152
|
-
- Warm semantics are identical for source-loaded and built-output-loaded
|
|
153
|
-
quivers; the loader hides the difference.
|
|
154
|
-
|
|
155
|
-
### 7) Engine Boundary: New Canonical Contract (Node / JS–WASM only)
|
|
156
|
-
|
|
157
|
-
Quillmark integration for `@quillmark/quiver` is:
|
|
158
|
-
|
|
159
|
-
1. Initialize engine once (`new Quillmark()` from `@quillmark/wasm`)
|
|
160
|
-
2. When quill bytes are ready, build a render-ready quill with `engine.quill(tree)` (`Map<string, Uint8Array>`)
|
|
161
|
-
3. Render through the returned quill: `quill.render(doc, opts?)` (`Document` — built via `Document.fromMarkdown(...)`)
|
|
162
|
-
|
|
163
|
-
Important implications:
|
|
164
|
-
|
|
165
|
-
- No engine quill registry in the JS binding; no `registerQuill`, `hasQuill`, or engine-level `render(doc)` in Quiver’s flow
|
|
166
|
-
- Quiver owns mapping from canonical ref → in-memory tree → `Quill` instance
|
|
167
|
-
- Cache optimization is in-process reuse of `Quill` instances, not registration checks
|
|
168
|
-
- Path-based loading (`quill_from_path`) exists in **other** bindings only; in Node, Quiver reads files and assembles `tree` for `engine.quill(tree)` (see the upstream `@quillmark/wasm` JS/WASM API docs)
|
|
169
|
-
|
|
170
|
-
For advanced dynamic-asset behavior, defer to Quillmark’s JS/WASM docs; the default integration path here is `engine.quill` + `quill.render`.
|
|
171
|
-
|
|
172
|
-
### 8) Markdown and Ref Parsing Boundary
|
|
173
|
-
|
|
174
|
-
- Markdown parsing does not require a quill registry: `Document.fromMarkdown(markdown)`
|
|
175
|
-
- Quiver owns ref parsing and selector resolution for its own API (`resolve`, `getQuill`, `warm`, validation)
|
|
176
|
-
- QUILL field is informational at render time; Quiver routes to the intended quill explicitly without mutating the parsed document in V1
|
|
177
|
-
|
|
178
|
-
Upstream behavior note:
|
|
179
|
-
|
|
180
|
-
- If rendering a parsed document whose `quill_ref` differs from selected quill name, render proceeds with warning `quill::ref_mismatch`
|
|
181
|
-
- Quiver should surface that warning, not suppress it. In V1, this is an intentional loud footgun detector for ref/selection drift.
|
|
182
|
-
|
|
183
|
-
### 9) Distribution Strategy
|
|
184
|
-
|
|
185
|
-
**Source-first distribution.** The published artifact is the **Source
|
|
186
|
-
Quiver** — an npm package whose root contains `Quiver.yaml`. Consumers
|
|
187
|
-
choose how to consume it:
|
|
188
|
-
|
|
189
|
-
- **Node consumers** load the source layout directly:
|
|
190
|
-
```ts
|
|
191
|
-
const quiver = await Quiver.fromPackage("@org/my-quiver");
|
|
192
|
-
```
|
|
193
|
-
- **Browser consumers** run a build step against the resolved source dir
|
|
194
|
-
and serve the output as static assets:
|
|
195
|
-
```ts
|
|
196
|
-
// build step (Node)
|
|
197
|
-
await Quiver.build("./node_modules/@org/my-quiver", "./public/quivers/my-quiver");
|
|
198
|
-
// browser runtime
|
|
199
|
-
const quiver = await Quiver.fromBuiltUrl("/quivers/my-quiver/");
|
|
200
|
-
```
|
|
201
|
-
- **Node server-side runtime consumers** also run the build step, ship the
|
|
202
|
-
packed artifact in their deployment image, and load it from disk:
|
|
203
|
-
```ts
|
|
204
|
-
await Quiver.build("./node_modules/@org/my-quiver", "./static/quills/my-quiver");
|
|
205
|
-
// server runtime
|
|
206
|
-
const quiver = await Quiver.fromBuiltDir("./static/quills/my-quiver");
|
|
207
|
-
```
|
|
208
|
-
This keeps source quivers as devDependencies and avoids self-fetching
|
|
209
|
-
through the deployment's load balancer / CDN.
|
|
210
|
-
|
|
211
|
-
Rationale:
|
|
212
|
-
|
|
213
|
-
- Author release pipeline is `npm publish` (or `git tag`). No second
|
|
214
|
-
artifact, no CDN, no hash bookkeeping outside the npm tarball.
|
|
215
|
-
- Deployment topology is the consumer's concern, not the author's.
|
|
216
|
-
- The runtime artifact is a build output of the source, not a peer
|
|
217
|
-
distribution shape (see §1).
|
|
218
|
-
|
|
219
|
-
**Pre-built distribution as a published artifact is supported but not
|
|
220
|
-
the default.** Authors who need to ship runtime-ready output directly
|
|
221
|
-
(e.g. their consumers cannot run a Node build step) may publish
|
|
222
|
-
`Quiver.build(...)` output to a CDN and instruct consumers to use
|
|
223
|
-
`Quiver.fromBuiltUrl(<cdn-url>)`. Treated as the exception.
|
|
224
|
-
|
|
225
|
-
Validation responsibility shifts left: authors should run
|
|
226
|
-
`Quiver.fromDir` and `Quiver.build` in CI so `quiver_invalid` errors
|
|
227
|
-
surface on publish, not on the consumer's build. The bundled
|
|
228
|
-
`@quillmark/quiver/testing` harness covers this.
|
|
229
|
-
|
|
230
|
-
---
|
|
231
|
-
|
|
232
|
-
## Carryover Matrix (What We Keep)
|
|
233
|
-
|
|
234
|
-
V1 intentionally retains:
|
|
235
|
-
|
|
236
|
-
1. Font dehydration as a build-output property
|
|
237
|
-
2. Consumer validation tooling for source layouts (+ optional build-parity checks)
|
|
238
|
-
3. Manifest pointer resolution for build output
|
|
239
|
-
4. HTTP/HTTPS loading via `Quiver.fromBuiltUrl`
|
|
240
|
-
5. Filesystem loading of build output via `Quiver.fromBuiltDir` for Node
|
|
241
|
-
server-side runtimes
|
|
242
|
-
6. Source layout loading as first-class dev loop (`fromPackage` / `fromDir`)
|
|
243
|
-
7. Typed errors (`QuiverError`) with quiver/source context
|
|
244
|
-
8. Concurrency coalescing for in-flight loads
|
|
245
|
-
9. Preload/fail-fast helpers where they still add value
|
|
246
|
-
|
|
247
|
-
Removed from carryover assumptions:
|
|
248
|
-
|
|
249
|
-
- Any engine-registration cache fast path (`register`/`has`) because upstream removed the capability
|
|
250
|
-
- "Transport" as a user-facing concept (folded into `fromBuiltUrl` /
|
|
251
|
-
`fromBuiltDir` internals)
|
|
252
|
-
|
|
253
|
-
---
|
|
254
|
-
|
|
255
|
-
## Explicit Trims (Surface Reduction)
|
|
256
|
-
|
|
257
|
-
V1 should trim public API where behavior can stay internal:
|
|
258
|
-
|
|
259
|
-
- Drop internal-only utility exports
|
|
260
|
-
- Keep engine payload and loader internals opaque
|
|
261
|
-
- Consolidate validation exports
|
|
262
|
-
- Avoid duplicate entry points for equivalent validation workflows
|
|
263
|
-
- Do not expose internal quill-object cache mechanisms as public contract
|
|
264
|
-
|
|
265
|
-
---
|
|
266
|
-
|
|
267
|
-
## Error Model
|
|
268
|
-
|
|
269
|
-
Single `QuiverError` class with `code: string` + contextual payload (ref, version, quiver name, underlying cause where applicable). No subclasses. Fail-fast: operations throw on first failure; no aggregate/partial-success results in V1.
|
|
270
|
-
|
|
271
|
-
V1 code catalog (closed set):
|
|
272
|
-
|
|
273
|
-
| Code | Fires when |
|
|
274
|
-
|---|---|
|
|
275
|
-
| `invalid_ref` | Malformed ref string at `resolve()`/`warm()` boundary (fails `parseQuillRef`) |
|
|
276
|
-
| `quill_not_found` | Selector did not match any quill in the quiver |
|
|
277
|
-
| `quiver_invalid` | `Quiver.yaml` or hashed manifest malformed, unknown field, non-canonical version on disk, or font/bundle hash mismatch |
|
|
278
|
-
| `transport_error` | I/O failure: missing path, HTTP non-2xx, network error, permission error. Wraps underlying cause. |
|
|
279
|
-
|
|
280
|
-
Notes:
|
|
281
|
-
- `quill_not_found` is selector-resolution failure within a quiver's catalog.
|
|
282
|
-
- `transport_error` is artifact access failure (filesystem/HTTP/network/permissions), including missing packed files and HTTP 404.
|
|
283
|
-
- Legacy categories such as `manifest_invalid`, `quill_load_failed`, and `backend_not_found` are folded into `quiver_invalid` or `transport_error` in V1.
|
|
284
|
-
|
|
285
|
-
Errors must include offending ref/version/quiver identifiers when available.
|
|
286
|
-
|
|
287
|
-
---
|
|
288
|
-
|
|
289
|
-
## Runtime + Build Model
|
|
290
|
-
|
|
291
|
-
V1 runtime loading paths:
|
|
292
|
-
|
|
293
|
-
1. `Quiver.fromPackage(specifier)` — npm package resolution; loads source
|
|
294
|
-
(authoring/dev/Node runtime)
|
|
295
|
-
2. `Quiver.fromDir(path)` — local directory; loads source (Node)
|
|
296
|
-
3. `Quiver.fromBuiltUrl(url)` — HTTP(S); loads build output (browser; also
|
|
297
|
-
works in Node)
|
|
298
|
-
4. `Quiver.fromBuiltDir(path)` — local directory; loads build output
|
|
299
|
-
(Node server-side runtime)
|
|
300
|
-
|
|
301
|
-
V1 build behavior:
|
|
302
|
-
|
|
303
|
-
- `Quiver.build(srcDir, outDir)` produces the runtime artifact from a
|
|
304
|
-
source layout
|
|
305
|
-
- output includes pointer + hashed manifest + bundles + dehydrated font store
|
|
306
|
-
(see "Runtime Artifact Format" below)
|
|
307
|
-
|
|
308
|
-
Execution behavior:
|
|
309
|
-
|
|
310
|
-
- Quiver resolves selector -> canonical ref
|
|
311
|
-
- Quiver loads/creates a render-ready `Quill` via engine factory
|
|
312
|
-
- Quiver reuses loaded quill objects by canonical ref
|
|
313
|
-
- Quiver renders through `quill.render(...)`
|
|
314
|
-
|
|
315
|
-
Caching scope:
|
|
316
|
-
|
|
317
|
-
- In V1, loaded-quill object reuse is in scope
|
|
318
|
-
- Cache eviction policy is out of scope for V1 (can be added as an additive lifecycle control later)
|
|
319
|
-
|
|
320
|
-
---
|
|
321
|
-
|
|
322
|
-
## Source Quiver Layout (normative)
|
|
323
|
-
|
|
324
|
-
```
|
|
325
|
-
<root>/
|
|
326
|
-
Quiver.yaml
|
|
327
|
-
quills/
|
|
328
|
-
<name>/
|
|
329
|
-
<version>/ # canonical x.y.z
|
|
330
|
-
Quill.yaml
|
|
331
|
-
... # quill-local templates, partials, assets, fonts
|
|
332
|
-
```
|
|
333
|
-
|
|
334
|
-
- All assets (including fonts) are **quill-local**. No quiver-level shared asset directory in V1.
|
|
335
|
-
- Non-canonical version directories are a validation error (`quiver_invalid`).
|
|
336
|
-
- Dedup of identical fonts across quills happens at pack time (into `store/<md5>`), not at the source layer.
|
|
337
|
-
|
|
338
|
-
## Runtime Artifact Format (normative)
|
|
339
|
-
|
|
340
|
-
Produced by `Quiver.build()`. Authors do not author this layout; consumers
|
|
341
|
-
do not need to inspect it. It is an implementation detail of build output,
|
|
342
|
-
specified here only because loaders must agree on its shape.
|
|
343
|
-
|
|
344
|
-
```
|
|
345
|
-
<root>/
|
|
346
|
-
Quiver.json # stable pointer, always this filename
|
|
347
|
-
manifest.<md5>.json # hashed manifest, content-addressed
|
|
348
|
-
<name>@<version>.<md5>.zip # one bundle per quill, content-addressed
|
|
349
|
-
store/
|
|
350
|
-
<md5> # raw font bytes, no extension
|
|
351
|
-
```
|
|
352
|
-
|
|
353
|
-
**Hash:** MD5 prefix-6, computed with `node:crypto` at `build()` time only (dev/tooling; not browser runtime).
|
|
354
|
-
|
|
355
|
-
**Pointer** `Quiver.json`:
|
|
356
|
-
```json
|
|
357
|
-
{ "manifest": "manifest.abc123.json" }
|
|
358
|
-
```
|
|
359
|
-
|
|
360
|
-
**Hashed manifest** `manifest.<md5>.json`:
|
|
361
|
-
```json
|
|
362
|
-
{
|
|
363
|
-
"version": 1,
|
|
364
|
-
"name": "<quiver-name>",
|
|
365
|
-
"quills": [
|
|
366
|
-
{
|
|
367
|
-
"name": "usaf_memo",
|
|
368
|
-
"version": "1.2.3",
|
|
369
|
-
"bundle": "usaf_memo@1.2.3.def456.zip",
|
|
370
|
-
"fonts": { "fonts/roboto.ttf": "md5abc", "fonts/arial.ttf": "md5def" }
|
|
371
|
-
}
|
|
372
|
-
]
|
|
373
|
-
}
|
|
374
|
-
```
|
|
375
|
-
|
|
376
|
-
**Bundle zips** contain pure quill content (`Quill.yaml` + templates + partials + non-font assets). Fonts are dehydrated at build time: their bytes live only in `store/<md5>`; their path→hash mapping lives only in the hashed manifest. Bundles do **not** embed a `fonts.json`.
|
|
377
|
-
|
|
378
|
-
Rehydration on load: the loader fetches the pointer → hashed manifest → required bundle(s) → required `store/<md5>` blobs; library reconstructs the full in-memory tree and builds a render-ready quill via `engine.quill(tree)`.
|
|
379
|
-
|
|
380
|
-
## API Surface (V1)
|
|
381
|
-
|
|
382
|
-
Single class, four loaders + one builder. Each loader has one input
|
|
383
|
-
shape and one shape-of-thing-loaded; no auto-detection.
|
|
384
|
-
|
|
385
|
-
```ts
|
|
386
|
-
class Quiver {
|
|
387
|
-
// Node-only loader: resolve an npm specifier against node_modules and
|
|
388
|
-
// load the source layout at the package root.
|
|
389
|
-
// (From `@quillmark/quiver/node`.)
|
|
390
|
-
static fromPackage(specifier: string): Promise<Quiver>;
|
|
391
|
-
|
|
392
|
-
// Node-only loader: load source layout from a local directory.
|
|
393
|
-
// Also accepts `import.meta.url`-style `file://` URLs (the URL's parent
|
|
394
|
-
// directory is used) as a convenience for tests.
|
|
395
|
-
// (From `@quillmark/quiver/node`.)
|
|
396
|
-
static fromDir(pathOrFileUrl: string): Promise<Quiver>;
|
|
397
|
-
|
|
398
|
-
// Browser-safe loader: load build output from an http(s):// or
|
|
399
|
-
// origin-relative URL. Throws `transport_error` on file:// inputs.
|
|
400
|
-
// (From `@quillmark/quiver` main.)
|
|
401
|
-
static fromBuiltUrl(url: string): Promise<Quiver>;
|
|
402
|
-
|
|
403
|
-
// Node-only loader: load build output from a local directory (the
|
|
404
|
-
// output of Quiver.build). No network. Use for server-side runtime
|
|
405
|
-
// when the packed artifact ships in the deployment image.
|
|
406
|
-
// (From `@quillmark/quiver/node`.)
|
|
407
|
-
static fromBuiltDir(dirPath: string): Promise<Quiver>;
|
|
408
|
-
|
|
409
|
-
// Node-only tooling: produce the runtime artifact from a source layout.
|
|
410
|
-
// (From `@quillmark/quiver/node`.)
|
|
411
|
-
static build(sourceDir: string, outDir: string, opts?: BuildOptions): Promise<void>;
|
|
412
|
-
|
|
413
|
-
readonly name: string; // from Quiver.yaml
|
|
414
|
-
|
|
415
|
-
// Read-only introspection and lazy tree access; also used internally by
|
|
416
|
-
// resolve/getQuill/warm.
|
|
417
|
-
quillNames(): string[]; // sorted lex
|
|
418
|
-
versionsOf(name: string): string[]; // sorted desc
|
|
419
|
-
loadTree(name: string, version: string): Promise<Map<string, Uint8Array>>;
|
|
420
|
-
|
|
421
|
-
// Selector ref -> canonical ref. Throws invalid_ref / quill_not_found.
|
|
422
|
-
resolve(ref: string): Promise<string>;
|
|
423
|
-
|
|
424
|
-
// Selector or canonical ref -> render-ready quill handle (materialized via
|
|
425
|
-
// engine.quill(tree), cached per (engine, canonical-ref)).
|
|
426
|
-
getQuill(ref: string, opts: { engine: Quillmark }): Promise<Quill>;
|
|
427
|
-
|
|
428
|
-
// Prefetches every quill tree (network-only; engine not required).
|
|
429
|
-
// Subsequent getQuill calls reuse the cached tree. Fail-fast.
|
|
430
|
-
warm(): Promise<void>;
|
|
431
|
-
}
|
|
432
|
-
|
|
433
|
-
class QuiverError extends Error {
|
|
434
|
-
code: "invalid_ref" | "quill_not_found" | "quiver_invalid" | "transport_error";
|
|
435
|
-
// plus contextual payload fields
|
|
436
|
-
}
|
|
437
|
-
```
|
|
438
|
-
|
|
439
|
-
**No render wrapper.** Callers invoke `quill.render(doc, opts?)` (and `quill.open(doc)` when needed) after `getQuill()`. Quiver never mirrors Quillmark render APIs.
|
|
440
|
-
|
|
441
|
-
**Internal (not exported):** `BuiltManifest` (runtime shape), `parseQuillRef`, in-flight coalescing state, source-vs-built layout detection.
|
|
442
|
-
|
|
443
|
-
Hot-path flow:
|
|
444
|
-
```ts
|
|
445
|
-
const doc = Document.fromMarkdown(md);
|
|
446
|
-
const quill = await quiver.getQuill(doc.quillRef, { engine });
|
|
447
|
-
const result = quill.render(doc, { format: "pdf" });
|
|
448
|
-
```
|
|
449
|
-
|
|
450
|
-
## Package Structure
|
|
451
|
-
|
|
452
|
-
**Name:** `@quillmark/quiver`
|
|
453
|
-
|
|
454
|
-
**Entrypoints:**
|
|
455
|
-
- `@quillmark/quiver` (main, browser-safe): `Quiver` class with only
|
|
456
|
-
`fromBuiltUrl` functional (Node-only loaders/builder throw
|
|
457
|
-
`transport_error` if reached in browser), `QuiverError`,
|
|
458
|
-
`QuillmarkLike`, `QuillLike`, shared types.
|
|
459
|
-
- `@quillmark/quiver/node`: adds `Quiver.fromPackage`, `Quiver.fromDir`,
|
|
460
|
-
`Quiver.fromBuiltDir`, `Quiver.build` behaviors. Single `Quiver` class —
|
|
461
|
-
Node-only factories fail fast outside Node.
|
|
462
|
-
- `@quillmark/quiver/testing` (Node-only): single export
|
|
463
|
-
`runQuiverTests(metaUrlOrDir, engine)` built on `node:test` (zero
|
|
464
|
-
external test-runner dependency). Optional convenience; users on other
|
|
465
|
-
test runners wire their own loops against the main API.
|
|
466
|
-
|
|
467
|
-
**Dependencies:**
|
|
468
|
-
- Peer: `@quillmark/wasm@>=0.86.0` with `Quillmark`, `Document.fromMarkdown`, `engine.quill(tree)`, `quill.render(doc, opts?)`, and `quill.example` (renderable sample document) APIs.
|
|
469
|
-
- Runtime: `fflate ^0.8.2` for zip read/write (Node + browser)
|
|
470
|
-
- Dev-only: `node:crypto` (MD5 hashing in `build()` — never reached at runtime)
|
|
471
|
-
- No test-runner peer dependency; `/testing` uses `node:test` (built-in)
|
|
472
|
-
|
|
473
|
-
---
|
|
474
|
-
|
|
475
|
-
## Out of Scope for V1
|
|
476
|
-
|
|
477
|
-
- Non-Node consumers of Quillmark (e.g. Python bindings, Rust CLI) as deliverables of this program — `@quillmark/quiver` is the Node/npm package only
|
|
478
|
-
- Quiver CLI (`quiver init`, etc.)
|
|
479
|
-
- prerelease semver support
|
|
480
|
-
- semver range expression support
|
|
481
|
-
- quiver-declared precedence/priority
|
|
482
|
-
- inter-quiver dependency graph in `Quiver.yaml`
|
|
483
|
-
- marketplace/discovery service
|
|
484
|
-
- advanced warm strategies beyond API-compatible hooks
|
|
485
|
-
- multi-quiver composition (single quiver per consumer in V1)
|
|
486
|
-
|
|
487
|
-
---
|
|
488
|
-
|
|
489
|
-
## Planner Questions — Resolved
|
|
490
|
-
|
|
491
|
-
All V1 planner questions resolved; implementation plan can proceed against the spec above.
|
|
492
|
-
|
|
493
|
-
1. ~~Final `Quiver` interface shape and transport factoring style~~ → Single `Quiver` class, four loaders (`fromPackage`, `fromDir`, `fromBuiltUrl`, `fromBuiltDir`) + one builder (`build`). Each loader names what it loads (source/built) and where it loads from (package/dir/url); no auto-detection.
|
|
494
|
-
2. ~~Final `Quiver.yaml` schema and unknown-field policy~~ → See §2: alphanumeric `name` and optional tooling-only `description`. Unknown fields are `quiver_invalid`.
|
|
495
|
-
3. ~~Canonical ref grammar and parser API contract~~ → Internal `parseQuillRef`, not exported. Selector syntax per §5. Throws `invalid_ref`.
|
|
496
|
-
4. ~~Exact warning policy for shadowed refs across quivers~~ → N/A in V1: no multi-quiver composition layer; each `Quiver` instance is independent (§4).
|
|
497
|
-
5. ~~Validation API shape consolidation~~ → No separate validation API. Validation errors surface as `QuiverError('quiver_invalid')` during load or `build()`.
|
|
498
|
-
6. ~~Build output directory structure~~ → See "Runtime Artifact Format (normative)".
|
|
499
|
-
7. ~~Node/browser entrypoint split~~ → See "Package Structure": main + `/node` subpath, single `Quiver` class.
|
|
500
|
-
8. ~~Final exported type names~~ → `Quiver`, `QuiverError`. Hot-path entry is `Quiver.getQuill(ref, { engine })`.
|
|
501
|
-
|
|
502
|
-
---
|
|
503
|
-
|
|
504
|
-
## Success Criteria
|
|
505
|
-
|
|
506
|
-
- A team can author and validate a Source Quiver locally with fast filesystem loops
|
|
507
|
-
- Build output can be loaded over HTTP/HTTPS with parity behavior in browser and Node
|
|
508
|
-
- Each loader names exactly what it loads — no auto-detection, no ambiguous return shape
|
|
509
|
-
- Multi-quiver resolution is deterministic and matches precedence hard-filter rules
|
|
510
|
-
- Selector behavior is predictable and explicitly documented
|
|
511
|
-
- Quiver (Node) integrates via `engine.quill(tree)` + `quill.render(...)` only (no engine quill registration path)
|
|
512
|
-
- Public API surface is smaller and clearer than `@quillmark/registry`
|
package/dist/engine-types.d.ts
DELETED
|
@@ -1,40 +0,0 @@
|
|
|
1
|
-
/**
|
|
2
|
-
* Minimal structural types matching @quillmark/wasm >=0.86.0.
|
|
3
|
-
*
|
|
4
|
-
* Shape:
|
|
5
|
-
* class Quillmark { quill(tree: Map<string, Uint8Array>): Quill }
|
|
6
|
-
* class Quill { render(doc, opts?): RenderResult; open(doc): RenderSession }
|
|
7
|
-
*
|
|
8
|
-
* The first arg to `render`/`open` is a `Document` instance (from
|
|
9
|
-
* `Document.fromMarkdown(...)`). Quiver keeps the arg typed as `unknown` so
|
|
10
|
-
* consumers (and test doubles) satisfy the contract structurally without
|
|
11
|
-
* importing from @quillmark/wasm.
|
|
12
|
-
*
|
|
13
|
-
* These types are re-exported from `index.ts` so consumers can type their
|
|
14
|
-
* own engine wrappers / test doubles against them. Quiver itself never
|
|
15
|
-
* imports from @quillmark/wasm directly.
|
|
16
|
-
*
|
|
17
|
-
* Call-site note: Quiver never invokes `render` or `open` itself; consumers do
|
|
18
|
-
* after `getQuill()`. The loose `unknown` parameter typing is intentional.
|
|
19
|
-
*/
|
|
20
|
-
export interface QuillmarkLike {
|
|
21
|
-
quill(tree: Map<string, Uint8Array>): QuillLike;
|
|
22
|
-
}
|
|
23
|
-
export interface QuillLike {
|
|
24
|
-
render(doc: unknown, opts?: unknown): unknown;
|
|
25
|
-
open?: (doc: unknown) => unknown;
|
|
26
|
-
/**
|
|
27
|
-
* Auto-generated annotated Markdown blueprint for LLM/authoring consumers.
|
|
28
|
-
* Carries `<must-fill>` sentinels for Must Fill fields, so it is *not*
|
|
29
|
-
* directly renderable — render `example` instead.
|
|
30
|
-
*/
|
|
31
|
-
blueprint: string;
|
|
32
|
-
/**
|
|
33
|
-
* The illustrative "filled-out" reference document — always renderable.
|
|
34
|
-
* Each field resolves to its `example:`, else its `default:`, else a
|
|
35
|
-
* type-empty zero value, with no `<must-fill>` sentinels. Introduced in
|
|
36
|
-
* @quillmark/wasm >=0.86.0. Used by Quiver's preview/testing helpers to
|
|
37
|
-
* render a sample artifact for each quill.
|
|
38
|
-
*/
|
|
39
|
-
example: string;
|
|
40
|
-
}
|
package/dist/engine-types.js
DELETED
|
@@ -1,20 +0,0 @@
|
|
|
1
|
-
/**
|
|
2
|
-
* Minimal structural types matching @quillmark/wasm >=0.86.0.
|
|
3
|
-
*
|
|
4
|
-
* Shape:
|
|
5
|
-
* class Quillmark { quill(tree: Map<string, Uint8Array>): Quill }
|
|
6
|
-
* class Quill { render(doc, opts?): RenderResult; open(doc): RenderSession }
|
|
7
|
-
*
|
|
8
|
-
* The first arg to `render`/`open` is a `Document` instance (from
|
|
9
|
-
* `Document.fromMarkdown(...)`). Quiver keeps the arg typed as `unknown` so
|
|
10
|
-
* consumers (and test doubles) satisfy the contract structurally without
|
|
11
|
-
* importing from @quillmark/wasm.
|
|
12
|
-
*
|
|
13
|
-
* These types are re-exported from `index.ts` so consumers can type their
|
|
14
|
-
* own engine wrappers / test doubles against them. Quiver itself never
|
|
15
|
-
* imports from @quillmark/wasm directly.
|
|
16
|
-
*
|
|
17
|
-
* Call-site note: Quiver never invokes `render` or `open` itself; consumers do
|
|
18
|
-
* after `getQuill()`. The loose `unknown` parameter typing is intentional.
|
|
19
|
-
*/
|
|
20
|
-
export {};
|