@quillmark/wasm 0.88.0-rc.1 → 0.89.1-rc.1
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/CHANGELOG.md +126 -10
- package/README.md +83 -30
- package/{bundler → backends/typst}/wasm.d.ts +64 -25
- package/{bundler → backends/typst}/wasm_bg.js +124 -57
- package/{bundler → backends/typst}/wasm_bg.wasm +0 -0
- package/{bundler → backends/typst}/wasm_bg.wasm.d.ts +6 -4
- package/core/wasm.d.ts +439 -0
- package/core/wasm.js +9 -0
- package/core/wasm_bg.js +1496 -0
- package/core/wasm_bg.wasm +0 -0
- package/core/wasm_bg.wasm.d.ts +62 -0
- package/package.json +14 -14
- package/runtime/runtime.d.ts +212 -0
- package/runtime/runtime.js +378 -0
- /package/{bundler → backends/typst}/wasm.js +0 -0
package/CHANGELOG.md
CHANGED
|
@@ -1,19 +1,117 @@
|
|
|
1
1
|
# Changelog
|
|
2
2
|
|
|
3
|
-
## v0.
|
|
4
|
-
|
|
5
|
-
-
|
|
3
|
+
## v0.89.1-rc.1 - 2026-06-10
|
|
4
|
+
|
|
5
|
+
- feat(wasm)!: 0.90 canonical API — engine-free Quill, single root export, typed errors; Python parity (#713)
|
|
6
|
+
- Proposal: WASM bindings split (core + render) via backend-decoupled Quill (#710)
|
|
7
|
+
- Add version selector matching and mismatch warnings (#708)
|
|
8
|
+
- docs: density-optimization pass on user-facing docs (#703)
|
|
9
|
+
- Remove role annotation from root block metadata header (#707)
|
|
10
|
+
- canon: audit and correct all prose/canon/ docs (#704)
|
|
11
|
+
- Fix makeCard fields/body typed as required in WASM .d.ts (#702)
|
|
6
12
|
- Update CLAUDE.md
|
|
7
|
-
- Expose seedMain/seedCard to WASM + Python bindings (#696)
|
|
8
|
-
- docs: note that released migration guides are era-accurate and immutable (#695)
|
|
9
|
-
- Remove form-view projection; add Quill::validate (#694)
|
|
10
|
-
- Remove example() reference document, fold into seeding (#693)
|
|
11
|
-
- Document seeding (example → absent) + block-scalar prescan fix + commitment-ladder docs (#691)
|
|
12
|
-
- docs(canon): dedup field-resolution semantics into SCHEMAS (#692)
|
|
13
13
|
|
|
14
14
|
|
|
15
15
|
## Unreleased
|
|
16
16
|
|
|
17
|
+
- **Breaking (Rust API + bindings):** `Quill` is now engine-free, validated
|
|
18
|
+
data. It no longer holds a backend; the `Quillmark` engine becomes a backend
|
|
19
|
+
registry + render dispatcher. Rendering and capability move onto the engine:
|
|
20
|
+
`render` / `open` / `supported_formats` / `supports_canvas` take `&quill`
|
|
21
|
+
(JS: `engine.render(quill, doc)` etc.). The `engine.quill` / `quill_from_path`
|
|
22
|
+
factory is removed — construct with `Quill::from_tree` (JS `Quill.fromTree`)
|
|
23
|
+
or `quillmark::quill_from_path`. The backend-existence
|
|
24
|
+
check moves from load time to render time (`UnsupportedBackend` now surfaces
|
|
25
|
+
from the first engine call). `supportedFormats` leaves `Quill.metadata` (now
|
|
26
|
+
pure config) for `engine.supportedFormats(quill)`. `Backend` gains a
|
|
27
|
+
`supports_canvas()` capability method (default `false`; Typst `true`),
|
|
28
|
+
retiring the `backend_id == "typst"` magic string. See
|
|
29
|
+
[migration guide](docs/migrations/0.89-to-0.90.md).
|
|
30
|
+
- **Breaking (WASM/JS types):** `QuillMetadata` drops its `[key: string]: unknown`
|
|
31
|
+
index signature. Code reading removed or unknown metadata properties (e.g.
|
|
32
|
+
`quill.metadata.supportedFormats`) now fails at compile time with "Property
|
|
33
|
+
does not exist" instead of silently returning `undefined` at runtime. Cast to
|
|
34
|
+
`Record<string, unknown>` to reach extra `quill:` YAML keys if needed.
|
|
35
|
+
- **Breaking (Python API):** the Python binding adopts the engine-free shape.
|
|
36
|
+
Render and capability move onto the `Quillmark` engine, taking a quill:
|
|
37
|
+
`engine.render(quill, doc)` / `engine.open(quill, doc)` /
|
|
38
|
+
`engine.supported_formats(quill)` / `engine.supports_canvas(quill)` (were
|
|
39
|
+
`quill.render(doc)` etc.). `Quill.from_path(path)` replaces
|
|
40
|
+
`Quillmark.quill_from_path(path)` — the engine is no longer a loader, and the
|
|
41
|
+
loaded `Quill` is engine-free. `quill.metadata` no longer contains
|
|
42
|
+
`supportedFormats` (read `engine.supported_formats(quill)`) and is now a pure,
|
|
43
|
+
infallible config read. Backend resolution moves from load to render time:
|
|
44
|
+
`UnsupportedBackend` surfaces from the first engine call, not from `from_path`.
|
|
45
|
+
See the [migration guide](docs/migrations/0.89-to-0.90.md#python).
|
|
46
|
+
- **Breaking (Rust API):** `QuillSource` and the orchestration `Quill` collapse
|
|
47
|
+
into one core type, `quillmark_core::Quill` (held by value; the vestigial
|
|
48
|
+
`Arc` is dropped). `Backend::open` now takes `&Quill`; the consumer methods
|
|
49
|
+
and the `seed` module move into core; `quill.source()` is gone
|
|
50
|
+
(`quill.config()` is direct). Bindings already hid `QuillSource`, so JS/Python
|
|
51
|
+
consumers are unaffected by the rename.
|
|
52
|
+
- **WASM packaging (single root export):** the root `@quillmark/wasm` import is
|
|
53
|
+
now a hand-written **canonical layer** (`pkg/runtime/`) — it re-exports the
|
|
54
|
+
Typst-less core's `Quill` / `Document` **verbatim** (same classes, no wrappers)
|
|
55
|
+
and adds an async **`Engine`** (`render` / `open` / `supportedFormats` /
|
|
56
|
+
`supportsCanvas`) as the canonical render API. The package `exports` map has
|
|
57
|
+
exactly **one** public entry point, `.` (the canonical layer); the old
|
|
58
|
+
`./render` and `./core` subpath exports are both **removed**. Engine-free
|
|
59
|
+
editor/validation code (`Quill.fromTree`, `Document.fromMarkdown`) still loads
|
|
60
|
+
only the small internal core binary (~0.66 MB gzip) — no backend is loaded
|
|
61
|
+
until you render. The Typst backend binary is **private**
|
|
62
|
+
(`pkg/backends/typst/`, not in the `exports` map): the `Engine`
|
|
63
|
+
lazy-`import()`s it on first render, clones the quill/document into its memory as
|
|
64
|
+
data (`Quill.toTree` → `fromTree`, `doc.toJson` → `fromJson`), and manages
|
|
65
|
+
those clones internally (the validated quill clone is cached per instance;
|
|
66
|
+
per-render document clones are freed) — consumers never import the backend or
|
|
67
|
+
cross a WASM memory boundary themselves. `Quill.toTree()` is added to core for that crossing. A release-time
|
|
68
|
+
size budget still guards the core artifact against Typst regressions.
|
|
69
|
+
- **WASM `Engine` (descriptor-only backend registry):** `new Engine({ backends })`
|
|
70
|
+
takes backend entries in **descriptor form only** — `{ load, formats, canvas }`
|
|
71
|
+
with `formats` and `canvas` **required**. The constructor validates each entry
|
|
72
|
+
and throws (naming the backend id) at construction. The capability probes
|
|
73
|
+
`supportedFormats` / `supportsCanvas` answer from this required manifest
|
|
74
|
+
**unconditionally**, never loading a backend binary or cloning the quill. The
|
|
75
|
+
bare-thunk loader form and its load+clone fallback path are removed.
|
|
76
|
+
- **WASM `Engine` (no invalidation API):** the unreleased
|
|
77
|
+
`Engine.invalidate(quill)` / `invalidateAll()` methods are removed before
|
|
78
|
+
release. The backend-clone cache is keyed on the canonical `Quill` instance in
|
|
79
|
+
a `WeakMap`; a quill's contents never change after construction, so the only
|
|
80
|
+
invalidation semantic is to drop/replace the instance (the clone is freed with
|
|
81
|
+
it via the `WeakMap` + wasm-bindgen weak-refs). An explicit invalidation API
|
|
82
|
+
will ship with its first real consumer. The load-bearing invariant — a
|
|
83
|
+
canonical ref is immutable content within a runtime's lifespan — is now
|
|
84
|
+
recorded in `prose/canon/VERSIONING.md` (Ref Immutability).
|
|
85
|
+
- **WASM `Engine` (session/canvas surface marked experimental):** `Engine.open`,
|
|
86
|
+
`RenderSession`, `paint`, `PaintOptions`, `PaintResult`, `PageSize`, and the
|
|
87
|
+
`supportsCanvas` probe are tagged `@experimental` in the shipped types and
|
|
88
|
+
README: they ship ahead of their first production consumer (the designed
|
|
89
|
+
canvas live-preview path) and may change shape in any 0.x release.
|
|
90
|
+
`Engine.render` and `supportedFormats` are the stable surface.
|
|
91
|
+
- **WASM (typed error contract):** the root exports `QuillmarkError` — a
|
|
92
|
+
structural interface (`Error & { diagnostics: Diagnostic[] }`) naming the
|
|
93
|
+
shape every fallible method already throws — and an `isQuillmarkError(e)`
|
|
94
|
+
guard to narrow caught `unknown`s. No runtime behavior change: the WASM
|
|
95
|
+
layer still throws a plain `Error` with `diagnostics` attached (there is
|
|
96
|
+
deliberately no error class — a structural check works across builds and
|
|
97
|
+
WASM instances). Consumers can delete their hand-rolled
|
|
98
|
+
`.diagnostics`-extraction casts.
|
|
99
|
+
- **Breaking (Rust API + bindings):** a document's `$quill` reference is now
|
|
100
|
+
**enforced** against the loaded quill. Rendering with a quill whose *name*
|
|
101
|
+
differs (`quill::name_mismatch`) or whose *version* falls outside the selector
|
|
102
|
+
(`quill::version_mismatch`) is a hard error via the new
|
|
103
|
+
`RenderError::QuillMismatch`, in both `render` and `dry_run`. Previously a name
|
|
104
|
+
mismatch was only the `quill::ref_mismatch` warning and the version selector
|
|
105
|
+
was unchecked. See [migration guide](docs/migrations/0.88-to-0.89.md).
|
|
106
|
+
- **Fix (WASM bindings):** `Document.makeCard`'s generated TypeScript now marks
|
|
107
|
+
`fields` (and `body`) as optional (`fields?: Record<string, unknown>`,
|
|
108
|
+
`body?: string`), matching the doc comment and runtime behavior. They were
|
|
109
|
+
typed as required because `unchecked_param_type` drops the `?` marker; the
|
|
110
|
+
bindings now use `unchecked_optional_param_type`. Callers can build a bare
|
|
111
|
+
card with `Document.makeCard('kind')`.
|
|
112
|
+
|
|
113
|
+
## v0.88.0 - 2026-06-05
|
|
114
|
+
|
|
17
115
|
- **Breaking (bindings + Rust API):** a single canonical **`Card` wire shape** now
|
|
18
116
|
flows in *both* directions. Core owns it as `quillmark_core::CardWire` (with
|
|
19
117
|
`From<&Card>` / `TryFrom<CardWire>`); the WASM/Python bindings serialize and
|
|
@@ -31,7 +129,6 @@
|
|
|
31
129
|
`Result<(), EditError>` and, with `insert_card`, validates that the card's
|
|
32
130
|
`$kind` is a valid, non-reserved composable kind — the cards-list invariant is
|
|
33
131
|
enforced at the edit op rather than incidentally at `Card::new`.
|
|
34
|
-
|
|
35
132
|
- **Breaking (bindings + Rust API):** the schema-aware **form view is removed**.
|
|
36
133
|
`Quill::form` / `Quill::blank_main` / `Quill::blank_card` (and the
|
|
37
134
|
`quill.form` / `blankMain` / `blankCard` bindings) are gone, along with the
|
|
@@ -62,6 +159,25 @@
|
|
|
62
159
|
authoring surface is `blueprint()`), so the projection collapses into the
|
|
63
160
|
seed: internally the `FillSource` fork in blueprint emission is gone and the
|
|
64
161
|
blueprint always renders `default:` else the `<must-fill>` sentinel.
|
|
162
|
+
- **wasm:** lower the npm package `engines.node` floor from `>=24` to `>=22`.
|
|
163
|
+
The runtime never required 24 — `--weak-refs` needs only Node 14.6+, and the
|
|
164
|
+
`using` sugar that motivated the 24 floor is optional (a `try` / `finally`
|
|
165
|
+
fallback covers Node 22). The aggressive floor hard-blocked installs on Node
|
|
166
|
+
22 CI/dev images under `engine-strict`.
|
|
167
|
+
- **wasm:** `Document.makeCard(kind, fields?, body?)` now types `fields` as
|
|
168
|
+
optional in the generated `.d.ts` (was required, contradicting its docs);
|
|
169
|
+
omitting it yields an empty field map, as before.
|
|
170
|
+
- **docs:** fix the `Quill.schema` getter doc — the returned schema **includes**
|
|
171
|
+
`ui` hints (it never stripped them); the stale "ui hints stripped" wording is
|
|
172
|
+
corrected. The 0.87→0.88 migration guide now documents the `fill` flag's
|
|
173
|
+
`!fill`-placeholder semantics and clarifies that seeding is example-filled,
|
|
174
|
+
not a blank-form replacement.
|
|
175
|
+
- **blueprint:** flatten `group_fields` and drop the unused group label (#697).
|
|
176
|
+
- **docs:** document seeding (example → absent), fix a block-scalar prescan
|
|
177
|
+
bug, and add commitment-ladder docs (#691).
|
|
178
|
+
- **docs(canon):** dedup field-resolution semantics into SCHEMAS (#692); note
|
|
179
|
+
that released migration guides are era-accurate and immutable (#695); prune
|
|
180
|
+
evolutionary information from comments and canon docs (#700).
|
|
65
181
|
|
|
66
182
|
## v0.87.3 - 2026-06-04
|
|
67
183
|
|
package/README.md
CHANGED
|
@@ -8,6 +8,20 @@ Maintained by [TTQ](https://tonguetoquill.com).
|
|
|
8
8
|
|
|
9
9
|
Use Quillmark in browsers/Node.js with explicit in-memory trees (`Map<string, Uint8Array>` / `Record<string, Uint8Array>`).
|
|
10
10
|
|
|
11
|
+
The package exposes **one import surface**:
|
|
12
|
+
|
|
13
|
+
- `@quillmark/wasm` (the root) — the **canonical API**: `Quill`, `Document`, and
|
|
14
|
+
an `Engine` that renders them.
|
|
15
|
+
|
|
16
|
+
`Quill` and `Document` are re-exported verbatim from the internal Typst-less
|
|
17
|
+
core build, so engine-free editor/validation code (`Quill.fromTree`,
|
|
18
|
+
`Document.fromMarkdown`) loads only that small core binary — no backend is
|
|
19
|
+
loaded until you render. The `Engine` hides everything else: each backend (Typst
|
|
20
|
+
today) is a separate, private WASM binary with its own linear memory, lazily
|
|
21
|
+
loaded on the first render. The Engine clones a `Quill` / `Document` into the
|
|
22
|
+
backend's memory as data and frees the clones — you never hold a backend object
|
|
23
|
+
or cross a memory boundary yourself.
|
|
24
|
+
|
|
11
25
|
## Build
|
|
12
26
|
|
|
13
27
|
```bash
|
|
@@ -29,10 +43,10 @@ npm test
|
|
|
29
43
|
## Usage
|
|
30
44
|
|
|
31
45
|
```ts
|
|
32
|
-
import { Document,
|
|
46
|
+
import { Document, Quill, Engine } from "@quillmark/wasm";
|
|
33
47
|
|
|
34
|
-
const
|
|
35
|
-
const
|
|
48
|
+
const quill = Quill.fromTree(tree); // engine-free: build + validate
|
|
49
|
+
const engine = new Engine(); // loads a backend lazily on first render
|
|
36
50
|
|
|
37
51
|
const markdown = `~~~
|
|
38
52
|
$quill: my_quill
|
|
@@ -43,16 +57,31 @@ title: My Document
|
|
|
43
57
|
# Hello`;
|
|
44
58
|
|
|
45
59
|
const parsed = Document.fromMarkdown(markdown);
|
|
46
|
-
const result =
|
|
60
|
+
const result = await engine.render(quill, parsed, { format: "pdf" });
|
|
47
61
|
```
|
|
48
62
|
|
|
49
63
|
## API
|
|
50
64
|
|
|
51
|
-
### `new
|
|
52
|
-
Create
|
|
53
|
-
|
|
54
|
-
|
|
55
|
-
|
|
65
|
+
### `new Engine(options?)`
|
|
66
|
+
Create the render dispatcher. Routes each quill to its backend by
|
|
67
|
+
`quill.backendId`, lazily loads that backend binary, and renders — cloning the
|
|
68
|
+
quill/document into the backend's memory and freeing the clones internally.
|
|
69
|
+
`render`, `open`, `supportedFormats`, and `supportsCanvas` are **async** (the
|
|
70
|
+
first call may load a backend). Pass `{ backends }` to register or override
|
|
71
|
+
backend descriptors. Each entry is a descriptor
|
|
72
|
+
(`{ [backendId]: { load, formats, canvas } }`) where `load` is the lazy thunk
|
|
73
|
+
returning the backend module and `formats`/`canvas` are the **required** static
|
|
74
|
+
capability manifest. A malformed descriptor throws at `new Engine(...)`, naming
|
|
75
|
+
the backend id.
|
|
76
|
+
|
|
77
|
+
**Capability probes are always free.** `supportedFormats` and `supportsCanvas`
|
|
78
|
+
depend only on `quill.backendId`, and answer from the descriptor's required
|
|
79
|
+
`formats`/`canvas` manifest — never loading the multi-MB backend binary and
|
|
80
|
+
never cloning the quill. Use them as non-failing pre-render probes.
|
|
81
|
+
|
|
82
|
+
### `Quill.fromTree(tree)`
|
|
83
|
+
Build + validate a `Quill` from an in-memory tree. Pure — the declared backend
|
|
84
|
+
is resolved at render time, not here. Loads no backend binary.
|
|
56
85
|
|
|
57
86
|
### `Document.fromMarkdown(markdown)`
|
|
58
87
|
Parse markdown to a parsed document. Throws a JS `Error` (with `.diagnostics`
|
|
@@ -206,18 +235,25 @@ There is one `Card` shape in both directions — `pushCard` / `insertCard` take
|
|
|
206
235
|
exactly what `cards` / `removeCard` / `seedCard` return. Build a fresh card
|
|
207
236
|
from a flat field map with `Document.makeCard(kind, fields?, body?)`.
|
|
208
237
|
|
|
209
|
-
### `
|
|
238
|
+
### `engine.render(quill, parsed, opts?)` vs. `engine.open(quill, parsed)`
|
|
239
|
+
|
|
240
|
+
> **Experimental:** the entire session surface — `engine.open`,
|
|
241
|
+
> `RenderSession`, `paint`, `PaintOptions`, `PaintResult`, `PageSize`, and the
|
|
242
|
+
> `supportsCanvas` probe — ships ahead of its first production consumer and
|
|
243
|
+
> may change shape in any 0.x release. `engine.render` is the stable path.
|
|
210
244
|
|
|
211
|
-
Use **`
|
|
212
|
-
artifacts, done. Use **`RenderSession`** (returned by `
|
|
213
|
-
reactive previews where
|
|
214
|
-
|
|
215
|
-
calls skip recompilation. Don't open
|
|
245
|
+
Use **`engine.render`** for one-shot exports (PDF/SVG/PNG) — compiles, emits
|
|
246
|
+
artifacts, done. Use **`RenderSession`** (returned by `engine.open`) for
|
|
247
|
+
reactive previews where
|
|
248
|
+
you'll paint or re-emit pages multiple times: the session retains the compiled
|
|
249
|
+
snapshot so subsequent `paint` / `render` calls skip recompilation. Don't open
|
|
250
|
+
a session per export.
|
|
216
251
|
|
|
217
|
-
### `
|
|
218
|
-
Render
|
|
252
|
+
### `engine.render(quill, parsed, opts?)`
|
|
253
|
+
Render a pre-parsed `Document` against `quill`. Throws `UnsupportedBackend` if
|
|
254
|
+
no registered backend matches the quill's declared backend.
|
|
219
255
|
|
|
220
|
-
### `
|
|
256
|
+
### `engine.open(quill, parsed)` + `session.render(opts?)`
|
|
221
257
|
Open once, render all or selected pages (`opts.pages`).
|
|
222
258
|
|
|
223
259
|
The session also exposes `pageCount`, `backendId`, `supportsCanvas`,
|
|
@@ -273,8 +309,8 @@ canvas.style.height = `${result.layoutHeight}px`;
|
|
|
273
309
|
`densityScale`. Loading the WASM module inside a Worker is the host's
|
|
274
310
|
responsibility.
|
|
275
311
|
- Backend support: gated by `supportsCanvas`. Probe upfront with
|
|
276
|
-
`
|
|
277
|
-
canvas-based UI; the throw on `paint` / `pageSize` remains the
|
|
312
|
+
`engine.supportsCanvas(quill)` (or `session.supportsCanvas`) before mounting
|
|
313
|
+
a canvas-based UI; the throw on `paint` / `pageSize` remains the
|
|
278
314
|
enforcement contract and includes the resolved `backendId` for
|
|
279
315
|
debugging.
|
|
280
316
|
|
|
@@ -287,7 +323,7 @@ A field's *cell* is inferred from whether its schema declares a `default:`:
|
|
|
287
323
|
(`validation::field_absent`) — the render path zero-fills it
|
|
288
324
|
silently. A surviving `<must-fill>` sentinel is fatal
|
|
289
325
|
(`validation::must_fill_sentinel`). Partial documents are
|
|
290
|
-
first-class; `
|
|
326
|
+
first-class; `engine.render(quill, doc)` only throws for malformed input.
|
|
291
327
|
- **Endorsed** (with `default:`) — `quill.blueprint` renders the
|
|
292
328
|
default value followed by a `; delete-ok` annotation, and the default
|
|
293
329
|
is used when the document omits the field.
|
|
@@ -299,12 +335,28 @@ code covers unreplaced sentinels.
|
|
|
299
335
|
|
|
300
336
|
### Errors
|
|
301
337
|
|
|
302
|
-
Every method that can fail throws a JS `Error` with
|
|
338
|
+
Every method that can fail throws a **`QuillmarkError`** — a JS `Error` with
|
|
339
|
+
`.diagnostics` attached. The type and a guard are exported from the root:
|
|
303
340
|
|
|
304
341
|
```ts
|
|
305
|
-
{
|
|
342
|
+
import { isQuillmarkError, type QuillmarkError } from "@quillmark/wasm";
|
|
343
|
+
|
|
344
|
+
try {
|
|
345
|
+
const result = await engine.render(quill, doc);
|
|
346
|
+
} catch (e) {
|
|
347
|
+
if (isQuillmarkError(e)) {
|
|
348
|
+
for (const d of e.diagnostics) console.error(d.severity, d.message);
|
|
349
|
+
} else {
|
|
350
|
+
throw e; // not a quillmark failure — programming error, re-throw
|
|
351
|
+
}
|
|
352
|
+
}
|
|
306
353
|
```
|
|
307
354
|
|
|
355
|
+
`QuillmarkError` is a **structural interface, not a class** — the WASM layer
|
|
356
|
+
throws a real `Error` and attaches the property, so there is no constructor to
|
|
357
|
+
`instanceof` against; narrow with `isQuillmarkError` (which also works on
|
|
358
|
+
errors from any build or WASM instance in the page).
|
|
359
|
+
|
|
308
360
|
`diagnostics` is always non-empty — length 1 for most failures, length N for
|
|
309
361
|
backend compilation errors. `message` is derived from `diagnostics`
|
|
310
362
|
(`diagnostics[0].message` for single-diagnostic errors; an aggregate
|
|
@@ -319,7 +371,7 @@ compilation failures. The same shape applies to every throw site:
|
|
|
319
371
|
variants (`InvalidFieldName`, `InvalidKindName`, `ReservedKind`,
|
|
320
372
|
`IndexOutOfRange`) appear in `diagnostics[0].message` with the
|
|
321
373
|
`[EditError::<Variant>]` prefix.
|
|
322
|
-
- `
|
|
374
|
+
- `engine.render` / `session.render` — backend compilation failures and
|
|
323
375
|
validation errors.
|
|
324
376
|
|
|
325
377
|
### Lifecycle
|
|
@@ -329,15 +381,16 @@ The wasm bindings are built with `--weak-refs`, so dropped `Document`,
|
|
|
329
381
|
without manual `.free()` discipline. `.free()` is still emitted as an eager
|
|
330
382
|
teardown hook for callers that want deterministic release.
|
|
331
383
|
|
|
332
|
-
The package
|
|
333
|
-
evergreen browsers; `--weak-refs` itself only needs Node 14.6
|
|
334
|
-
|
|
384
|
+
The package floor is Node 22+ (`engines: { node: ">=22" }`) and current
|
|
385
|
+
evergreen browsers; `--weak-refs` itself only needs Node 14.6+. The `using`
|
|
386
|
+
sugar shown below ([explicit resource management][erm]) needs Node 24, but is
|
|
387
|
+
optional — the `try` / `finally` fallback runs on the Node 22 floor.
|
|
335
388
|
|
|
336
389
|
For environments where `using` (the [explicit resource management][erm]
|
|
337
390
|
proposal) hasn't landed, use an explicit `try` / `finally`:
|
|
338
391
|
|
|
339
392
|
```ts
|
|
340
|
-
const session =
|
|
393
|
+
const session = engine.open(quill, doc);
|
|
341
394
|
try {
|
|
342
395
|
for (let p = 0; p < session.pageCount; p++) {
|
|
343
396
|
session.paint(ctx, p);
|
|
@@ -355,8 +408,8 @@ try {
|
|
|
355
408
|
the legacy `~~~card-yaml` opener is still accepted but non-canonical)
|
|
356
409
|
with a `$quill` system-metadata line. Empty input surfaces a dedicated
|
|
357
410
|
"Empty markdown input cannot be parsed" message.
|
|
358
|
-
-
|
|
359
|
-
- Output schema APIs
|
|
411
|
+
- A `$quill` mismatch during `engine.render(quill, parsed)` is a thrown error, not a warning: rendering with a quill whose *name* differs (`quill::name_mismatch`) or whose *version* falls outside the selector (`quill::version_mismatch`) is rejected.
|
|
412
|
+
- Output schema APIs live on `Quill`, not the engine.
|
|
360
413
|
|
|
361
414
|
## Changelog
|
|
362
415
|
|
|
@@ -164,8 +164,9 @@ export interface QuillSchema {
|
|
|
164
164
|
|
|
165
165
|
/**
|
|
166
166
|
* Identity snapshot mirroring the `quill:` section of `Quill.yaml`.
|
|
167
|
-
* The schema lives on `Quill.schema
|
|
168
|
-
*
|
|
167
|
+
* The schema lives on `Quill.schema`; the backend's output formats are a
|
|
168
|
+
* resolved-backend capability read from the engine (`Quillmark.supportedFormats`),
|
|
169
|
+
* not part of this pure-config snapshot.
|
|
169
170
|
*/
|
|
170
171
|
export interface QuillMetadata {
|
|
171
172
|
name: string;
|
|
@@ -173,8 +174,6 @@ export interface QuillMetadata {
|
|
|
173
174
|
backend: string;
|
|
174
175
|
author: string;
|
|
175
176
|
description: string;
|
|
176
|
-
supportedFormats: OutputFormat[];
|
|
177
|
-
[key: string]: unknown;
|
|
178
177
|
}
|
|
179
178
|
|
|
180
179
|
|
|
@@ -282,7 +281,7 @@ export class Document {
|
|
|
282
281
|
* insertion order); `body` defaults to `""`. Kind validity is checked by
|
|
283
282
|
* `pushCard` / `insertCard`, not here.
|
|
284
283
|
*/
|
|
285
|
-
static makeCard(kind: string, fields
|
|
284
|
+
static makeCard(kind: string, fields?: Record<string, unknown>, body?: string): Card;
|
|
286
285
|
/**
|
|
287
286
|
* Move the card at `from` to position `to`. `from == to` is a no-op.
|
|
288
287
|
*/
|
|
@@ -445,8 +444,16 @@ export class Quill {
|
|
|
445
444
|
private constructor();
|
|
446
445
|
free(): void;
|
|
447
446
|
[Symbol.dispose](): void;
|
|
448
|
-
|
|
449
|
-
|
|
447
|
+
/**
|
|
448
|
+
* Build a quill from a file tree. Pure — no backend, no engine; the
|
|
449
|
+
* declared backend is resolved later, at render time.
|
|
450
|
+
*
|
|
451
|
+
* Accepts either a `Map<string, Uint8Array>` or a plain object
|
|
452
|
+
* (`Record<string, Uint8Array>`). Plain objects are walked via
|
|
453
|
+
* `Object.entries` at the boundary; the Rust side sees a single
|
|
454
|
+
* canonical shape.
|
|
455
|
+
*/
|
|
456
|
+
static fromTree(tree: Map<string, Uint8Array>): Quill;
|
|
450
457
|
/**
|
|
451
458
|
* Seed a starter composable `Card` of the given kind (carries `$kind`),
|
|
452
459
|
* committing its fields' `example:` values and leaving every other field
|
|
@@ -471,6 +478,19 @@ export class Quill {
|
|
|
471
478
|
* `Card` shape as the `Document.main` getter.
|
|
472
479
|
*/
|
|
473
480
|
seedMain(): Card;
|
|
481
|
+
/**
|
|
482
|
+
* Flatten this quill back into its canonical file tree — the inverse of
|
|
483
|
+
* [`fromTree`](Self::from_tree). Round-trips: `Quill.fromTree(q.toTree())`
|
|
484
|
+
* reproduces an equivalent quill.
|
|
485
|
+
*
|
|
486
|
+
* This is how a quill crosses a WASM linear-memory boundary as data: a
|
|
487
|
+
* `Quill` built in one build (e.g. the Typst-less `@quillmark/wasm/core`)
|
|
488
|
+
* cannot be passed to an engine in another (separate linear memories), so
|
|
489
|
+
* `@quillmark/wasm/runtime` re-feeds this tree to the backend build's
|
|
490
|
+
* `Quill.fromTree` on demand. Keys are `"/"`-joined relative paths,
|
|
491
|
+
* matching what `fromTree` accepts.
|
|
492
|
+
*/
|
|
493
|
+
toTree(): Map<string, Uint8Array>;
|
|
474
494
|
/**
|
|
475
495
|
* Validate `doc` against this quill's schema, returning every diagnostic
|
|
476
496
|
* (an empty array when the document is valid).
|
|
@@ -484,40 +504,58 @@ export class Quill {
|
|
|
484
504
|
*/
|
|
485
505
|
validate(doc: Document): Diagnostic[];
|
|
486
506
|
/**
|
|
487
|
-
* The
|
|
507
|
+
* The *declared* backend identifier (`config.backend`, e.g. `"typst"`).
|
|
508
|
+
* Intent, not a resolved capability — capability (`supportedFormats` /
|
|
509
|
+
* `supportsCanvas`) is read from the engine.
|
|
488
510
|
*/
|
|
489
511
|
readonly backendId: string;
|
|
490
512
|
readonly blueprint: string;
|
|
491
513
|
/**
|
|
492
|
-
* Identity snapshot of the `quill:` section of `Quill.yaml
|
|
493
|
-
* `
|
|
514
|
+
* Identity snapshot of the `quill:` section of `Quill.yaml` plus any extra
|
|
515
|
+
* `quill:` keys. Pure config — the backend's output formats are a
|
|
516
|
+
* resolved-backend capability read from the engine
|
|
517
|
+
* (`Quillmark.supportedFormats`), not part of this snapshot.
|
|
494
518
|
*/
|
|
495
519
|
readonly metadata: QuillMetadata;
|
|
496
520
|
/**
|
|
497
|
-
* Document schema
|
|
521
|
+
* Document schema for the quill: the user-fillable fields plus their
|
|
522
|
+
* `ui` hints (group / order / showWhen). The single field-metadata
|
|
523
|
+
* surface — drives form editors and LLM/MCP consumers alike. Returns the
|
|
524
|
+
* `QuillSchema` shape.
|
|
498
525
|
*/
|
|
499
526
|
readonly schema: QuillSchema;
|
|
500
|
-
/**
|
|
501
|
-
* `true` iff `RenderSession.paint` and `RenderSession.pageSize` will
|
|
502
|
-
* succeed for sessions opened by this quill. Use as a precondition
|
|
503
|
-
* probe before mounting a canvas-based preview UI.
|
|
504
|
-
*/
|
|
505
|
-
readonly supportsCanvas: boolean;
|
|
506
527
|
}
|
|
507
528
|
|
|
529
|
+
/**
|
|
530
|
+
* Render engine: a backend registry and render dispatcher. Render build only —
|
|
531
|
+
* the core build constructs and validates quills without it.
|
|
532
|
+
*/
|
|
508
533
|
export class Quillmark {
|
|
509
534
|
free(): void;
|
|
510
535
|
[Symbol.dispose](): void;
|
|
511
536
|
constructor();
|
|
512
537
|
/**
|
|
513
|
-
*
|
|
514
|
-
|
|
515
|
-
|
|
516
|
-
|
|
517
|
-
* `
|
|
518
|
-
*
|
|
538
|
+
* Open an iterative render session for `doc` against `quill`'s backend.
|
|
539
|
+
*/
|
|
540
|
+
open(quill: Quill, doc: Document): RenderSession;
|
|
541
|
+
/**
|
|
542
|
+
* Render `doc` against `quill` in one shot. Convenience over `open` +
|
|
543
|
+
* `RenderSession.render`: an unset `output_format` falls back to the
|
|
544
|
+
* backend's first supported format.
|
|
545
|
+
*/
|
|
546
|
+
render(quill: Quill, doc: Document, opts?: RenderOptions | null): RenderResult;
|
|
547
|
+
/**
|
|
548
|
+
* The output formats `quill`'s backend can emit. Static capability —
|
|
549
|
+
* resolves the backend but compiles nothing. Throws `UnsupportedBackend`
|
|
550
|
+
* if no registered backend matches the quill's declared backend.
|
|
551
|
+
*/
|
|
552
|
+
supportedFormats(quill: Quill): OutputFormat[];
|
|
553
|
+
/**
|
|
554
|
+
* `true` iff `quill`'s backend can paint sessions to a canvas. Asked of
|
|
555
|
+
* the real backend; `false` when the backend is unsupported or non-canvas.
|
|
556
|
+
* Use as a precondition probe before mounting a canvas-based preview UI.
|
|
519
557
|
*/
|
|
520
|
-
quill
|
|
558
|
+
supportsCanvas(quill: Quill): boolean;
|
|
521
559
|
}
|
|
522
560
|
|
|
523
561
|
/**
|
|
@@ -555,7 +593,8 @@ export class RenderSession {
|
|
|
555
593
|
readonly backendId: string;
|
|
556
594
|
readonly pageCount: number;
|
|
557
595
|
/**
|
|
558
|
-
* `true` iff `paint` and `pageSize` will succeed for this session.
|
|
596
|
+
* `true` iff `paint` and `pageSize` will succeed for this session. The
|
|
597
|
+
* backend's canvas capability, captured at open time.
|
|
559
598
|
*/
|
|
560
599
|
readonly supportsCanvas: boolean;
|
|
561
600
|
/**
|