@quillmark/wasm 0.96.0 → 0.98.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/core/wasm_bg.js CHANGED
@@ -131,7 +131,7 @@ export class Document {
131
131
  /**
132
132
  * Interpreted read at `addr`, resolving the field's declared `type` from
133
133
  * `quill` — the stable ABI under the runtime `reader.get` / `reader.card(i).get`.
134
- * The schema-plane twin of the quill-free [`get`](Self::get): a `richtext`
134
+ * The schema-plane twin of the quill-free [`getStored`](Self::get_stored): a `richtext`
135
135
  * field returns its markdown projection, every other declared type its
136
136
  * canonical value verbatim, so a consumer holding the quill reads by field
137
137
  * meaning rather than by wire shape.
@@ -176,7 +176,7 @@ export class Document {
176
176
  * surviving anchors rebase (as [`revise`](Self::revise)), then the diffed
177
177
  * result is schema-conformed, so a `richtext(inline)` field rejects a
178
178
  * multi-block result with `edit::field_richtext_not_inline`. Returns the
179
- * text [`Delta`].
179
+ * text `Delta`.
180
180
  *
181
181
  * `addr` must name a field (a bare string is `{ field }`); a body address
182
182
  * throws (a body carries no field schema — use [`revise`](Self::revise)). A
@@ -290,10 +290,10 @@ export class Document {
290
290
  return ret >>> 0;
291
291
  }
292
292
  /**
293
- * The index of the first composable card whose `$id` equals `id`, or
294
- * `undefined` when none carries it. Resolves the canonical durable address
293
+ * The index of the composable card whose `$id` equals `id`, or
294
+ * `undefined` when none carries it. Resolves the durable card handle
295
295
  * without a hand-rolled scan over [`cards`](Self::cards); `$id` is
296
- * non-unique by design, so the first match wins.
296
+ * unique per document, so at most one card matches.
297
297
  * @param {string} id
298
298
  * @returns {number | undefined}
299
299
  */
@@ -457,33 +457,6 @@ export class Document {
457
457
  wasm.__wbindgen_add_to_stack_pointer(16);
458
458
  }
459
459
  }
460
- /**
461
- * Read the value at `addr` — the raw stored payload value of a field (a
462
- * content object for a richtext field, a scalar/array/object otherwise), or
463
- * the **body content** when `addr.field` is absent. A bare string is `Addr`
464
- * shorthand for `{ field }`. Reads are total over the field axis: an absent
465
- * field is `undefined`; only an out-of-range `addr.card` throws
466
- * `edit::index_out_of_range`. Reads need no schema, so they live on
467
- * `Document`, not the typed writer; for the markdown projection of a
468
- * richtext value use [`getMarkdown`](Self::get_markdown).
469
- * @param {Addr | string} addr
470
- * @returns {unknown}
471
- */
472
- get(addr) {
473
- try {
474
- const retptr = wasm.__wbindgen_add_to_stack_pointer(-16);
475
- wasm.document_get(retptr, this.__wbg_ptr, addHeapObject(addr));
476
- var r0 = getDataViewMemory0().getInt32(retptr + 4 * 0, true);
477
- var r1 = getDataViewMemory0().getInt32(retptr + 4 * 1, true);
478
- var r2 = getDataViewMemory0().getInt32(retptr + 4 * 2, true);
479
- if (r2) {
480
- throw takeObject(r1);
481
- }
482
- return takeObject(r0);
483
- } finally {
484
- wasm.__wbindgen_add_to_stack_pointer(16);
485
- }
486
- }
487
460
  /**
488
461
  * The whole `$ext` map at `addr` (a card address, absent `card` = main), or
489
462
  * `undefined` when the card carries none. The fine-grained `$ext` read —
@@ -561,6 +534,35 @@ export class Document {
561
534
  wasm.__wbindgen_add_to_stack_pointer(16);
562
535
  }
563
536
  }
537
+ /**
538
+ * Read the **verbatim stored value** at `addr` — the raw payload value of a
539
+ * field (a content object for a richtext field, a scalar/array/object
540
+ * otherwise), or the **body content** when `addr.field` is absent. A bare
541
+ * string is `Addr` shorthand for `{ field }`. Reads are total over the field
542
+ * axis: an absent field is `undefined`; only an out-of-range `addr.card`
543
+ * throws `edit::index_out_of_range`. Needs no schema, so it lives on
544
+ * `Document` — the read echo of the verbatim `store*` write, distinct from
545
+ * the interpreted schema-plane [`reader.get`](Self::reader_get). For the
546
+ * markdown projection use [`getMarkdown`](Self::get_markdown) (body) or
547
+ * `reader.get` (a field's declared type).
548
+ * @param {Addr | string} addr
549
+ * @returns {unknown}
550
+ */
551
+ getStored(addr) {
552
+ try {
553
+ const retptr = wasm.__wbindgen_add_to_stack_pointer(-16);
554
+ wasm.document_getStored(retptr, this.__wbg_ptr, addHeapObject(addr));
555
+ var r0 = getDataViewMemory0().getInt32(retptr + 4 * 0, true);
556
+ var r1 = getDataViewMemory0().getInt32(retptr + 4 * 1, true);
557
+ var r2 = getDataViewMemory0().getInt32(retptr + 4 * 2, true);
558
+ if (r2) {
559
+ throw takeObject(r1);
560
+ }
561
+ return takeObject(r0);
562
+ } finally {
563
+ wasm.__wbindgen_add_to_stack_pointer(16);
564
+ }
565
+ }
564
566
  /**
565
567
  * Insert a card — the single insertion verb: `at` absent appends, a number
566
568
  * inserts at that index (must be in `0..=cards.length`). Accepts a
@@ -685,8 +687,16 @@ export class Document {
685
687
  * Build a fresh `Card` from a kind and a flat field map — the ergonomic
686
688
  * constructor for `insertCard`. `fields` is an optional
687
689
  * `Record<string, unknown>` (each entry becomes a card field, in
688
- * insertion order); `body` defaults to `""`. Kind validity is checked by
689
- * `insertCard`, not here.
690
+ * insertion order); `body` defaults to `""`.
691
+ *
692
+ * Sugar, not a required step: `insertCard` takes any `Card` object, and
693
+ * `removeCard` returns one, so a card round-trips without passing through
694
+ * here.
695
+ *
696
+ * Checks only what a detached card can decide alone: field-name grammar
697
+ * and value depth. Kind validity is positional — `main` is right for the
698
+ * root, reserved for a composable card — so `insertCard` is its gate, and
699
+ * any kind string is accepted here.
690
700
  * @param {string} kind
691
701
  * @param {Record<string, unknown>} [fields]
692
702
  * @param {string} [body]
@@ -917,7 +927,7 @@ export class Document {
917
927
  }
918
928
  /**
919
929
  * **Revise** the richtext value at `addr` from a markdown string — **edit
920
- * semantics**, the default write path, returning the text [`Delta`]. Imports
930
+ * semantics**, the default write path, returning the text `Delta`. Imports
921
931
  * the markdown, diffs it against the current value, rebases surviving
922
932
  * identity anchors, and returns the change an editor bridge maps its own
923
933
  * positions through (`mapPos`). An absent `addr.field` targets the body, an
@@ -1375,7 +1385,8 @@ export class Quill {
1375
1385
  * The resolved-value view of `doc` against this quill's schema — for every
1376
1386
  * declared field the value the render projection would use and the
1377
1387
  * `FieldSource` rung it came from (`"authored" | "default" | "zero"`), in
1378
- * one call. The card body rides the `fields` map under the `$body` key.
1388
+ * one call. The card body is a `body` sibling on its card (row `name`
1389
+ * `"body"`), never a row in `fields` — `null` when the kind enables no body.
1379
1390
  *
1380
1391
  * Value and provenance only: completeness and errors stay `validate`'s
1381
1392
  * (a consumer merges it with its own diagnostic producers regardless), and
@@ -1638,10 +1649,11 @@ export function init() {
1638
1649
  }
1639
1650
 
1640
1651
  /**
1641
- * Map a base content position through a `delta` to its new position the pure
1642
- * position-mapping codec an editor bridge composes to hold a caret stable
1643
- * across a `revise`. `assoc` decides the side of a same-position insertion
1644
- * (`"after"` moves past it). Throws on a malformed `delta`.
1652
+ * Map a base content position a USV index into `Content.text`, not a UTF-16
1653
+ * offset through a `delta` to its new USV position: the pure position-mapping
1654
+ * codec an editor bridge composes to hold a caret stable across a `revise`.
1655
+ * `assoc` decides the side of a same-position insertion (`"after"` moves past
1656
+ * it). Throws on a malformed `delta`.
1645
1657
  * @param {Delta} delta
1646
1658
  * @param {number} pos
1647
1659
  * @param {Assoc} assoc
@@ -1693,9 +1705,10 @@ export function parseDocPath(path) {
1693
1705
  /**
1694
1706
  * Rebase `markdown` onto a `base` content — the pure, document-free twin of
1695
1707
  * `revise`: cold-import + `diff_import`, returning the new `content` and the
1696
- * text `delta` (surviving anchors rebased). Use it to compute a revise without
1697
- * a document in hand; `revise(addr, md)` fuses this with the store for
1698
- * atomicity. Throws on an over-nested markdown input or a non-content `base`.
1708
+ * text `delta` (its offsets USV indices into `Content.text`, surviving anchors
1709
+ * rebased). Use it to compute a revise without a document in hand; `revise(addr,
1710
+ * md)` fuses this with the store for atomicity. Throws on an over-nested
1711
+ * markdown input or a non-content `base`.
1699
1712
  * @param {Content} base
1700
1713
  * @param {string} markdown
1701
1714
  * @returns {{ content: Content; delta: Delta }}
package/core/wasm_bg.wasm CHANGED
Binary file
@@ -21,10 +21,10 @@ export const document_formatDiagnostic: (a: number, b: number) => void;
21
21
  export const document_formatRules: (a: number) => void;
22
22
  export const document_fromJson: (a: number, b: number, c: number) => void;
23
23
  export const document_fromMarkdown: (a: number, b: number, c: number) => void;
24
- export const document_get: (a: number, b: number, c: number) => void;
25
24
  export const document_getExt: (a: number, b: number, c: number) => void;
26
25
  export const document_getExtNamespace: (a: number, b: number, c: number, d: number, e: number) => void;
27
26
  export const document_getMarkdown: (a: number, b: number, c: number) => void;
27
+ export const document_getStored: (a: number, b: number, c: number) => void;
28
28
  export const document_insertCard: (a: number, b: number, c: number, d: number) => void;
29
29
  export const document_install: (a: number, b: number, c: number, d: number) => void;
30
30
  export const document_isFill: (a: number, b: number, c: number) => void;
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@quillmark/wasm",
3
- "version": "0.96.0",
3
+ "version": "0.98.0",
4
4
  "description": "WebAssembly bindings for Quillmark, a schema-driven document engine",
5
5
  "type": "module",
6
6
  "license": "MIT OR Apache-2.0",
@@ -79,8 +79,9 @@ export type {
79
79
  } from '../core/wasm.js';
80
80
 
81
81
  // The resolved-value view — the return shape of `quill.resolve(doc)`. Value
82
- // + source rung per declared field (the body rides the fields map under
83
- // `$body`); diagnostics stay `quill.validate`, guidance stays `quill.schema`.
82
+ // + source rung per declared field (the body is a `body` sibling on its card,
83
+ // never a row in `fields`); diagnostics stay `quill.validate`, guidance stays
84
+ // `quill.schema`.
84
85
  // Declared in the core build's generated `.d.ts` via a
85
86
  // `typescript_custom_section`; re-exported here so the single public entry
86
87
  // point names them.
@@ -120,6 +121,66 @@ export interface QuillmarkError extends Error {
120
121
  */
121
122
  export declare function isQuillmarkError(e: unknown): e is QuillmarkError;
122
123
 
124
+ // ── Open-set discriminant guards ────────────────────────────────────────────
125
+ // `ContentIsland.type`, `ContentMark.type`, `ContentLine.kind`, and
126
+ // `ContentContainer.container` are open sets — each union has a residual
127
+ // `{ …: string; … }` arm, so a bare discriminant check never narrows the payload
128
+ // (TS keeps the residual arm live, since a `string` can equal the literal).
129
+ // These guards are the checked narrowing path for the pinned arms; an
130
+ // unrecognized discriminant fails every guard and keeps its opaque payload. Only
131
+ // the payload-carrying arms get a guard — the bare marks
132
+ // (`strong`/`emph`/`underline`/`strike`/`code`), the payload-free lines
133
+ // (`para`/`island`/`rule`), and `quote` narrow to nothing.
134
+
135
+ import type {
136
+ ContentIsland,
137
+ TableProps,
138
+ ImageProps,
139
+ ContentMark,
140
+ ContentLine,
141
+ ContentContainer
142
+ } from '../core/wasm.js';
143
+
144
+ /** Narrow a {@link ContentIsland} to the pinned `table` arm (`props: TableProps`). */
145
+ export declare function isTableIsland(
146
+ island: ContentIsland
147
+ ): island is ContentIsland & { type: 'table'; props: TableProps };
148
+
149
+ /** Narrow a {@link ContentIsland} to the pinned `image` arm (`props: ImageProps`). */
150
+ export declare function isImageIsland(
151
+ island: ContentIsland
152
+ ): island is ContentIsland & { type: 'image'; props: ImageProps };
153
+
154
+ /** Narrow a {@link ContentMark} to the `link` arm (carries `url`). */
155
+ export declare function isLinkMark(
156
+ mark: ContentMark
157
+ ): mark is ContentMark & { type: 'link'; url: string };
158
+
159
+ /** Narrow a {@link ContentMark} to the `anchor` arm (carries `id`). */
160
+ export declare function isAnchorMark(
161
+ mark: ContentMark
162
+ ): mark is ContentMark & { type: 'anchor'; id: string };
163
+
164
+ /** Narrow a {@link ContentLine} to the `heading` arm (carries `level`). */
165
+ export declare function isHeadingLine(
166
+ line: ContentLine
167
+ ): line is ContentLine & { kind: 'heading'; level: number };
168
+
169
+ /** Narrow a {@link ContentLine} to the `code` arm (carries `lang`). */
170
+ export declare function isCodeLine(
171
+ line: ContentLine
172
+ ): line is ContentLine & { kind: 'code'; lang?: string };
173
+
174
+ /** Narrow a {@link ContentContainer} to the `list_item` arm (carries its shape). */
175
+ export declare function isListItemContainer(
176
+ container: ContentContainer
177
+ ): container is ContentContainer & {
178
+ container: 'list_item';
179
+ ordered: boolean;
180
+ start: number;
181
+ ordinal: number;
182
+ };
183
+
123
184
  // ── Canonical render-side types ─────────────────────────────────────────────
124
185
  // These are the BACKEND-NEUTRAL render contract of the plural-backend API. They
125
186
  // are defined HERE (not re-exported from one private backend) because no single
@@ -259,7 +320,7 @@ export interface RenderResult {
259
320
  }
260
321
 
261
322
  /** Canonical contract every backend build must satisfy. The emittable formats. */
262
- export type OutputFormat = 'pdf' | 'svg' | 'txt' | 'png';
323
+ export type OutputFormat = 'pdf' | 'svg' | 'png';
263
324
 
264
325
  /**
265
326
  * Canonical contract every backend build must satisfy. Page geometry in pt.
@@ -630,7 +691,7 @@ export declare class CardWriter {
630
691
  * text, every other type its canonical value verbatim. Holds both handles by
631
692
  * reference and owns neither — nothing to `free()`.
632
693
  *
633
- * The schema authority is the point: unlike the quill-free transport `Document.get`,
694
+ * The schema authority is the point: unlike the quill-free transport `Document.getStored`,
634
695
  * a name the schema does not declare throws `UnknownField` (a typo) rather than
635
696
  * reading back `undefined`, and a content field holding a value that does not
636
697
  * decode throws `FieldRichtextDecode`. A field's markdown lives here, not on the
@@ -98,6 +98,77 @@ export function isQuillmarkError(e) {
98
98
  return e instanceof Error && Array.isArray(/** @type {any} */ (e).diagnostics);
99
99
  }
100
100
 
101
+ // ── Open-set discriminant guards ────────────────────────────────────────────
102
+ // `ContentIsland.type`, `ContentMark.type`, `ContentLine.kind`, and
103
+ // `ContentContainer.container` are OPEN sets: each union carries a residual
104
+ // `{ …: string; … }` arm, so a bare `x.type === 'table'` check never narrows the
105
+ // payload — TS keeps the residual arm live (a `string` can be `'table'`),
106
+ // leaving `props` / the mark payload / `level` opaque at every consumer. These
107
+ // are the checked narrowing path: on the true branch the payload's pinned shape
108
+ // is asserted. Only the payload-carrying arms get a guard — an island always
109
+ // carries `props`, a `link` mark carries `url`, an `anchor` mark carries `id`, a
110
+ // `heading` line carries `level` and a `code` line `lang`, a `list_item`
111
+ // container its shape; the payload-free arms (`strong`/`emph`/`underline`/
112
+ // `strike`/`code` marks, `para`/`island`/`rule` lines, `quote`) narrow to
113
+ // nothing. An unrecognized discriminant fails every guard and keeps its opaque
114
+ // `attrs`/`props`.
115
+
116
+ /**
117
+ * @param {import('../core/wasm.js').ContentIsland} island
118
+ * @returns {island is import('../core/wasm.js').ContentIsland & { type: 'table'; props: import('../core/wasm.js').TableProps }}
119
+ */
120
+ export function isTableIsland(island) {
121
+ return island.type === 'table';
122
+ }
123
+
124
+ /**
125
+ * @param {import('../core/wasm.js').ContentIsland} island
126
+ * @returns {island is import('../core/wasm.js').ContentIsland & { type: 'image'; props: import('../core/wasm.js').ImageProps }}
127
+ */
128
+ export function isImageIsland(island) {
129
+ return island.type === 'image';
130
+ }
131
+
132
+ /**
133
+ * @param {import('../core/wasm.js').ContentMark} mark
134
+ * @returns {mark is import('../core/wasm.js').ContentMark & { type: 'link'; url: string }}
135
+ */
136
+ export function isLinkMark(mark) {
137
+ return mark.type === 'link';
138
+ }
139
+
140
+ /**
141
+ * @param {import('../core/wasm.js').ContentMark} mark
142
+ * @returns {mark is import('../core/wasm.js').ContentMark & { type: 'anchor'; id: string }}
143
+ */
144
+ export function isAnchorMark(mark) {
145
+ return mark.type === 'anchor';
146
+ }
147
+
148
+ /**
149
+ * @param {import('../core/wasm.js').ContentLine} line
150
+ * @returns {line is import('../core/wasm.js').ContentLine & { kind: 'heading'; level: number }}
151
+ */
152
+ export function isHeadingLine(line) {
153
+ return line.kind === 'heading';
154
+ }
155
+
156
+ /**
157
+ * @param {import('../core/wasm.js').ContentLine} line
158
+ * @returns {line is import('../core/wasm.js').ContentLine & { kind: 'code'; lang?: string }}
159
+ */
160
+ export function isCodeLine(line) {
161
+ return line.kind === 'code';
162
+ }
163
+
164
+ /**
165
+ * @param {import('../core/wasm.js').ContentContainer} container
166
+ * @returns {container is import('../core/wasm.js').ContentContainer & { container: 'list_item'; ordered: boolean; start: number; ordinal: number }}
167
+ */
168
+ export function isListItemContainer(container) {
169
+ return container.container === 'list_item';
170
+ }
171
+
101
172
  // Backend builds are NEVER statically imported here — that would pull a
102
173
  // multi-MB binary into the eager graph and defeat lazy loading. Each entry is a
103
174
  // DESCRIPTOR: `load` is a thunk returning a dynamic `import()` (a backend's
@@ -754,7 +825,7 @@ Quill.prototype.writer = function writer(doc) {
754
825
  };
755
826
 
756
827
  // ── Typed-reader sugar: the schema-plane read surface ──────────────────────────
757
- // The read twin of the writer above. The transport `Document.get` is schema-free
828
+ // The read twin of the writer above. The transport `Document.getStored` is schema-free
758
829
  // — a `Document` cannot say which fields are richtext, so an unknown field name
759
830
  // reads back `undefined` rather than as the typo it is. Binding the quill's
760
831
  // schema (`_readerGet` takes the handle, like the `commit*` verbs) lets one `get`