@quillmark/wasm 0.55.0 → 0.58.2-rc.4

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,177 +1,74 @@
1
1
  # Quillmark WASM
2
2
 
3
- WebAssembly bindings for the Quillmark markdown rendering engine.
3
+ WebAssembly bindings for Quillmark.
4
4
 
5
5
  Maintained by [TTQ](https://tonguetoquill.com).
6
6
 
7
7
  ## Overview
8
8
 
9
- This crate provides WASM bindings for Quillmark, enabling use in web browsers, Node.js, and other JavaScript/TypeScript environments. All data exchange uses JSON serialization, and JavaScript is responsible for all I/O operations.
9
+ Use Quillmark in browsers/Node.js with explicit in-memory trees (`Map<string, Uint8Array>` / `Record<string, Uint8Array>`).
10
10
 
11
- ## Building
12
-
13
- ### For Web (bundler)
11
+ ## Build
14
12
 
15
13
  ```bash
16
14
  wasm-pack build --target bundler --scope quillmark
17
15
  ```
18
16
 
19
- ### For Node.js
20
-
21
- ```bash
22
- wasm-pack build --target nodejs --scope quillmark
23
- ```
24
-
25
- ### All targets
26
-
27
- ```bash
28
- bash scripts/build-wasm.sh
29
- ```
30
-
31
- ## Testing
32
-
33
- Minimal smoke tests validate the core WASM functionality:
17
+ ## Test
34
18
 
35
19
  ```bash
36
- # Build WASM module first
37
20
  bash scripts/build-wasm.sh
38
-
39
- # Run tests
40
- cd quillmark-wasm
21
+ cd crates/bindings/wasm
41
22
  npm install
42
23
  npm test
43
24
  ```
44
25
 
45
- The test suite includes:
46
- - `basic.test.js` - Core WASM API functionality tests
47
- - `resolve.test.js` - Quill version resolution against the WASM engine
48
-
49
26
  ## Usage
50
27
 
51
- ```typescript
52
- import { Quillmark } from '@quillmark-test/wasm';
28
+ ```ts
29
+ import { Document, Quillmark } from "@quillmark-test/wasm";
30
+
31
+ const engine = new Quillmark();
32
+ const quill = engine.quill(tree);
53
33
 
54
- // Step 1: Parse markdown
55
34
  const markdown = `---
56
- title: My Document
57
- author: Alice
58
35
  QUILL: my_quill
36
+ title: My Document
59
37
  ---
60
38
 
61
- # Hello World
62
-
63
- This is my document.
64
- `;
39
+ # Hello`;
65
40
 
66
- const parsed = Quillmark.parseMarkdown(markdown);
67
-
68
- // Step 2: Create engine and register Quill
69
- const engine = new Quillmark();
70
-
71
- const quillJson = {
72
- files: {
73
- 'Quill.yaml': {
74
- contents: 'Quill:\n name: my_quill\n version: "1.0"\n backend: typst\n plate_file: plate.typ\n description: My template\n'
75
- },
76
- 'plate.typ': {
77
- contents: '= {{ title }}\n\n{{ body | Content }}'
78
- }
79
- }
80
- };
81
-
82
- engine.registerQuill(quillJson);
83
-
84
- // Step 3: Get Quill info (optional)
85
- const info = engine.getQuillInfo('my-quill');
86
- console.log('Supported formats:', info.supportedFormats);
87
- console.log('Schema YAML:', info.schema);
88
-
89
- // Step 4: Render
90
- const result = engine.render(parsed, { format: 'pdf' });
91
-
92
- // Access the PDF bytes
93
- const pdfArtifact = result.artifacts[0];
94
- const blob = new Blob([pdfArtifact.bytes], { type: pdfArtifact.mimeType });
95
- const url = URL.createObjectURL(blob);
96
- window.open(url);
41
+ const parsed = Document.fromMarkdown(markdown);
42
+ const result = quill.render(parsed, { format: "pdf" });
97
43
  ```
98
44
 
99
45
  ## API
100
46
 
101
- The `Quillmark` class provides the following methods:
102
-
103
- ### Workflow Methods
104
-
105
- The main workflow for rendering documents:
106
-
107
- - `static parseMarkdown(markdown)` - Parse markdown into a ParsedDocument (Step 1)
108
- - `registerQuill(quillJson)` - Register a Quill template bundle from JSON (Step 2)
109
- - `render(parsedDoc, options)` - Render a ParsedDocument to final artifacts using the required `QUILL` reference parsed from the document (Step 4)
110
-
111
- ### Utility Methods
112
-
113
- Additional methods for managing the engine and debugging:
114
-
115
- - `new Quillmark()` - Create a new engine instance
116
- - `renderQuill(RenderOptions, markdown)` - Load markdown and map it onto an internally fetched Quill, resolving to `RenderResult` including output format, the artifact byte slice buffer, and time to render
117
- - `processPlate(quillRef, markdown)` - Debug helper that processes markdown through the template engine and returns the intermediate template source code (e.g., Typst, LaTeX) without compiling to final artifacts. Useful for inspecting template output during development.
118
- - `fetchQuillInfo(quillRef)` - Fetches metadata and schema about an available Quill from the configured registry without loading the full filesystem or rendering context.
119
- - `listQuills()` - List all registered Quill names
120
- - `unregisterQuill(name)` - Unregister a Quill to free memory
47
+ ### `new Quillmark()`
48
+ Create engine.
121
49
 
122
- ### Render Options
50
+ ### `engine.quill(tree)`
51
+ Build + validate + attach backend. Returns a render-ready `Quill`.
123
52
 
124
- ```typescript
125
- type RenderOptions = {
126
- format?: 'pdf' | 'svg' | 'txt'
127
- assets?: Record<string, Uint8Array | number[]>
128
- }
129
- ```
130
-
131
- ### ParsedDocument
132
-
133
- Returned by `parseMarkdown()`:
134
-
135
- ```typescript
136
- {
137
- fields: object, // YAML frontmatter fields
138
- quillRef: string // Quill reference from required QUILL field
139
- }
140
- ```
141
-
142
- ### QuillInfo
143
-
144
- Returned by `getQuillInfo()`:
145
-
146
- ```typescript
147
- {
148
- name: string,
149
- backend: string, // e.g., "typst"
150
- metadata: object, // Quill metadata from Quill.yaml
151
- example?: string, // Example markdown (if available)
152
- schema: string, // Public schema YAML text
153
- supportedFormats: Array<'pdf' | 'svg' | 'txt'> // Formats this backend supports
154
- }
155
- ```
53
+ ### `Document.fromMarkdown(markdown)`
54
+ Parse markdown to parsed document.
156
55
 
157
- ## WASM Boundary Types
56
+ ### `doc.toMarkdown()`
57
+ Emit canonical Quillmark Markdown. Type-fidelity round-trip safe:
58
+ `Document.fromMarkdown(doc.toMarkdown())` returns a document equal to `doc`.
158
59
 
159
- Data crossing the JavaScript ↔ WebAssembly boundary:
60
+ ### `quill.render(parsed, opts?)`
61
+ Render with a pre-parsed `Document`.
160
62
 
161
- - **Enums**: Serialized as lowercase strings (`"pdf"`, `"svg"`, `"txt"`)
162
- - **Binary data**: `Vec<u8>` maps to `Uint8Array`
163
- - **Collections**: `Vec<T>` maps to JS arrays; object types use plain JS objects `{}`
164
- - **Option**: `Option<T>` maps to `T | null`
165
- - **Errors**: Thrown as exceptions using `SerializableDiagnostic` from core, containing structured diagnostic information (severity, message, location, hint, source chain)
63
+ ### `quill.open(parsed)` + `session.render(opts?)`
64
+ Open once, render all or selected pages (`opts.pages`).
166
65
 
167
- ## Design Principles
66
+ ## Notes
168
67
 
169
- - **JSON-Only Data Exchange**: All structured data uses `serde-wasm-bindgen`
170
- - **JavaScript Handles I/O**: WASM layer only handles rendering
171
- - **Synchronous Operations**: Rendering is fast enough (<100ms typically)
172
- - **No File System Abstractions**: JavaScript prepares all data
173
- - **Error Delegation**: Error handling delegated to core types (`SerializableDiagnostic`) for consistency with Python bindings
68
+ - Parsed markdown requires top-level `QUILL` in frontmatter.
69
+ - QUILL mismatch during `quill.render(parsed)` is a warning (`quill::ref_mismatch`), not an error.
70
+ - Output schema APIs are no longer engine-level in WASM.
174
71
 
175
72
  ## License
176
73
 
177
- Licensed under the Apache License, Version 2.0.
74
+ Apache-2.0
package/bundler/wasm.d.ts CHANGED
@@ -6,7 +6,11 @@ export interface Artifact {
6
6
  mimeType: string;
7
7
  }
8
8
 
9
- export interface CompileOptions {}
9
+ export interface Card {
10
+ tag: string;
11
+ fields: Record<string, unknown>;
12
+ body: string;
13
+ }
10
14
 
11
15
  export interface Diagnostic {
12
16
  severity: Severity;
@@ -23,31 +27,10 @@ export interface Location {
23
27
  column: number;
24
28
  }
25
29
 
26
- export interface ParsedDocument {
27
- fields: Record<string, any>;
28
- quillRef: string;
29
- }
30
-
31
- export interface QuillInfo {
32
- name: string;
33
- backend: string;
34
- metadata: Record<string, any>;
35
- example?: string;
36
- schema: string;
37
- defaults: Record<string, any>;
38
- examples: Record<string, any[]>;
39
- supportedFormats: OutputFormat[];
40
- }
41
-
42
30
  export interface RenderOptions {
43
31
  format?: OutputFormat;
44
- assets?: Record<string, Uint8Array | number[]>;
45
- ppi?: number;
46
- }
47
-
48
- export interface RenderPagesOptions {
49
- format: OutputFormat;
50
32
  ppi?: number;
33
+ pages?: number[];
51
34
  }
52
35
 
53
36
  export interface RenderResult {
@@ -62,108 +45,220 @@ export type OutputFormat = "pdf" | "svg" | "txt" | "png";
62
45
  export type Severity = "error" | "warning" | "note";
63
46
 
64
47
 
65
- export class CompiledDocument {
48
+ /**
49
+ * Typed in-memory Quillmark document.
50
+ *
51
+ * Created via `Document.fromMarkdown(markdown)`. Exposes:
52
+ * - `quillRef` (string)
53
+ * - `frontmatter` (JS object/Record)
54
+ * - `body` (string)
55
+ * - `cards` (array of Card objects)
56
+ * - `warnings` (array of Diagnostic objects)
57
+ *
58
+ * `toMarkdown()` emits canonical Quillmark Markdown that round-trips back to
59
+ * an equal `Document` by value and by type.
60
+ */
61
+ export class Document {
66
62
  private constructor();
67
63
  free(): void;
68
64
  [Symbol.dispose](): void;
69
65
  /**
70
- * Render selected pages. `pages = null/undefined` renders all pages.
66
+ * Parse markdown into a typed Document.
67
+ *
68
+ * Returns the document with any parse-time warnings accessible via `.warnings`.
69
+ * Throws on parse errors.
71
70
  */
72
- renderPages(pages: Uint32Array | null | undefined, opts: RenderPagesOptions): RenderResult;
71
+ static fromMarkdown(markdown: string): Document;
73
72
  /**
74
- * Number of pages in this compiled document.
73
+ * Insert a card at the given index.
74
+ *
75
+ * `index` must be in `0..=cards.length`. Out-of-range throws an `Error`.
76
+ *
77
+ * Mutators never modify `warnings`.
75
78
  */
76
- readonly pageCount: number;
77
- }
78
-
79
- /**
80
- * Quillmark WASM Engine
81
- *
82
- * Create once, register Quills, render markdown. That's it.
83
- */
84
- export class Quillmark {
85
- free(): void;
86
- [Symbol.dispose](): void;
79
+ insertCard(index: number, card: any): void;
87
80
  /**
88
- * Compile a parsed document into an opaque compiled document handle.
81
+ * Move the card at `from` to position `to`.
82
+ *
83
+ * `from == to` is a no-op. Both indices must be in `0..cards.length`.
84
+ * Out-of-range throws an `Error`.
85
+ *
86
+ * Mutators never modify `warnings`.
89
87
  */
90
- compile(parsed: ParsedDocument, opts?: CompileOptions | null): CompiledDocument;
88
+ moveCard(from: number, to: number): void;
91
89
  /**
92
- * Compile markdown to JSON data without rendering artifacts.
90
+ * Append a card to the end of the card list.
91
+ *
92
+ * `card` must be a JS object with a `tag` string field and optional
93
+ * `fields` (object) and `body` (string).
93
94
  *
94
- * This exposes the intermediate data structure that would be passed to the backend.
95
- * Useful for debugging and validation.
95
+ * Throws an `Error` if `card.tag` is not a valid tag name.
96
+ *
97
+ * Mutators never modify `warnings`.
96
98
  */
97
- compileData(markdown: string): any;
99
+ pushCard(card: any): void;
98
100
  /**
99
- * Perform a dry run validation without backend compilation.
100
- *
101
- * Executes parsing, schema validation, and template composition to
102
- * surface input errors quickly. Returns successfully on valid input,
103
- * or throws an error with diagnostic payload on failure.
101
+ * Remove the card at `index` and return it, or `undefined` if out of range.
104
102
  *
105
- * The quill name is read from the markdown's required QUILL tag.
103
+ * Mutators never modify `warnings`.
104
+ */
105
+ removeCard(index: number): any;
106
+ /**
107
+ * Remove a frontmatter field, returning the removed value or `undefined`.
106
108
  *
107
- * This is useful for fast feedback loops in LLM-driven document generation.
109
+ * Mutators never modify `warnings`.
108
110
  */
109
- dryRun(markdown: string): void;
111
+ removeField(name: string): any;
110
112
  /**
111
- * Get shallow information about a registered Quill
113
+ * Replace the global Markdown body.
112
114
  *
113
- * This returns metadata, backend info, field schemas, and supported formats
114
- * that consumers need to configure render options for the next step.
115
+ * Mutators never modify `warnings`.
115
116
  */
116
- getQuillInfo(name: string): QuillInfo;
117
+ replaceBody(body: string): void;
117
118
  /**
118
- * Get the public YAML schema contract for a registered quill.
119
+ * Set a frontmatter field.
120
+ *
121
+ * Throws an `Error` whose message includes the `EditError` variant name and
122
+ * details if `name` is reserved (`BODY`, `CARDS`, `QUILL`, `CARD`) or does
123
+ * not match `[a-z_][a-z0-9_]*`.
124
+ *
125
+ * Mutators never modify `warnings`.
119
126
  */
120
- getQuillSchema(name: string): string;
127
+ setField(name: string, value: any): void;
121
128
  /**
122
- * List registered Quills with their exact versions
129
+ * Replace the QUILL reference string.
130
+ *
131
+ * Throws if `ref_str` is not a valid `QuillReference`.
123
132
  *
124
- * Returns strings in the format "name@version" (e.g. "resume-template@2.1.0")
133
+ * Mutators never modify `warnings`.
125
134
  */
126
- listQuills(): string[];
135
+ setQuillRef(ref_str: string): void;
127
136
  /**
128
- * JavaScript constructor: `new Quillmark()`
137
+ * Emit canonical Quillmark Markdown.
138
+ *
139
+ * Returns the document serialised as a Quillmark Markdown string.
140
+ * The output is type-fidelity round-trip safe: re-parsing the result
141
+ * produces a `Document` equal to `self` by value and by type.
129
142
  */
130
- constructor();
143
+ toMarkdown(): string;
131
144
  /**
132
- * Parse markdown into a ParsedDocument
145
+ * Replace the body of the card at `index`.
146
+ *
147
+ * Throws if `index` is out of range.
133
148
  *
134
- * This is the first step in the workflow. The returned ParsedDocument contains
135
- * the parsed YAML frontmatter fields and the quill_ref from QUILL.
149
+ * Mutators never modify `warnings`.
136
150
  */
137
- static parseMarkdown(markdown: string): ParsedDocument;
151
+ updateCardBody(index: number, body: string): void;
138
152
  /**
139
- * Register a Quill template bundle
153
+ * Update a field on the card at `index`.
140
154
  *
141
- * Accepts either a JSON string or a JsValue object representing the Quill file tree.
142
- * Validation happens automatically on registration.
155
+ * Convenience method: equivalent to `doc.card_mut(index)?.set_field(name, value)`.
156
+ *
157
+ * Throws if `index` is out of range, `name` is reserved or invalid, or
158
+ * `value` cannot be serialized.
159
+ *
160
+ * Mutators never modify `warnings`.
143
161
  */
144
- registerQuill(quill_json: any): QuillInfo;
162
+ updateCardField(index: number, name: string, value: any): void;
145
163
  /**
146
- * Render a ParsedDocument to final artifacts (PDF, SVG, TXT)
164
+ * Global Markdown body between frontmatter and the first card.
165
+ *
166
+ * Trailing newlines are stripped — those are structural separators in
167
+ * the Markdown wire format, not content the consumer wrote.
147
168
  *
148
- * Uses the Quill specified in the ParsedDocument's quill_ref field.
169
+ * Empty string when no body is present.
170
+ */
171
+ readonly body: string;
172
+ /**
173
+ * Ordered list of card blocks as JS objects with `tag`, `fields`, and `body`.
149
174
  */
150
- render(parsed: ParsedDocument, opts: RenderOptions): RenderResult;
175
+ readonly cards: any;
151
176
  /**
152
- * Resolve a Quill reference to a registered Quill, or null if not available
177
+ * Typed YAML frontmatter fields as a JS object (no QUILL, BODY, or CARDS keys).
178
+ */
179
+ readonly frontmatter: any;
180
+ /**
181
+ * The QUILL reference string (e.g. `"usaf_memo@0.1"`).
182
+ */
183
+ readonly quillRef: string;
184
+ /**
185
+ * Non-fatal parse-time warnings as a JS array of Diagnostic objects.
186
+ */
187
+ readonly warnings: any;
188
+ }
189
+
190
+ /**
191
+ * Opaque, shareable Quill handle.
192
+ */
193
+ export class Quill {
194
+ private constructor();
195
+ free(): void;
196
+ [Symbol.dispose](): void;
197
+ /**
198
+ * Open an iterative render session for page-selective rendering.
199
+ */
200
+ open(doc: Document): RenderSession;
201
+ /**
202
+ * Project a document through this quill's schema.
203
+ *
204
+ * Returns a plain JS object (not a class) that is immediately
205
+ * `JSON.stringify`-able. The shape mirrors [`FormProjection`]:
206
+ *
207
+ * ```json
208
+ * {
209
+ * "main": { "schema": {...}, "values": { "field": {...} } },
210
+ * "cards": [ ... ],
211
+ * "diagnostics": [ ... ]
212
+ * }
213
+ * ```
153
214
  *
154
- * Accepts a quill reference string like "resume-template", "resume-template@2",
155
- * or "resume-template@2.1.0". Returns QuillInfo if the engine can resolve it
156
- * locally, or null if an external fetch is needed.
215
+ * **Snapshot semantics.** This is a read-only snapshot of the document
216
+ * at call time. Subsequent edits to `doc` require calling `projectForm`
217
+ * again.
218
+ *
219
+ * [`FormProjection`]: quillmark::form::FormProjection
220
+ */
221
+ projectForm(doc: Document): any;
222
+ /**
223
+ * Render a document to final artifacts.
224
+ */
225
+ render(doc: Document, opts?: RenderOptions | null): RenderResult;
226
+ /**
227
+ * The resolved backend identifier (e.g. `"typst"`).
157
228
  */
158
- resolveQuill(quill_ref: string): any;
229
+ readonly backendId: string;
230
+ }
231
+
232
+ /**
233
+ * Quillmark WASM Engine
234
+ */
235
+ export class Quillmark {
236
+ free(): void;
237
+ [Symbol.dispose](): void;
238
+ /**
239
+ * JavaScript constructor: `new Quillmark()`
240
+ */
241
+ constructor();
159
242
  /**
160
- * Unregister a Quill by name or specific version
243
+ * Load a quill from a file tree and attach the appropriate backend.
161
244
  *
162
- * If a base name is provided (e.g., "my-quill"), all versions of that quill are freed.
163
- * If a versioned name is provided (e.g., "my-quill@2.1.0"), only that specific version is freed.
164
- * Returns true if something was unregistered, false if not found.
245
+ * The tree must be a `Map<string, Uint8Array>`.
165
246
  */
166
- unregisterQuill(name_or_ref: string): boolean;
247
+ quill(tree: any): Quill;
248
+ }
249
+
250
+ export class RenderSession {
251
+ private constructor();
252
+ free(): void;
253
+ [Symbol.dispose](): void;
254
+ /**
255
+ * Render all or selected pages from this session.
256
+ */
257
+ render(opts?: RenderOptions | null): RenderResult;
258
+ /**
259
+ * Number of pages in this render session.
260
+ */
261
+ readonly pageCount: number;
167
262
  }
168
263
 
169
264
  /**
package/bundler/wasm.js CHANGED
@@ -1,9 +1,9 @@
1
1
  /* @ts-self-types="./wasm.d.ts" */
2
-
3
2
  import * as wasm from "./wasm_bg.wasm";
4
3
  import { __wbg_set_wasm } from "./wasm_bg.js";
4
+
5
5
  __wbg_set_wasm(wasm);
6
6
  wasm.__wbindgen_start();
7
7
  export {
8
- CompiledDocument, Quillmark, init
8
+ Document, Quill, Quillmark, RenderSession, init
9
9
  } from "./wasm_bg.js";