@quillmark/wasm 0.101.0 → 0.102.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/CHANGELOG.md +105 -0
- package/README.md +68 -15
- package/backends/pdfform/wasm.d.ts +232 -68
- package/backends/pdfform/wasm.js +2980 -8
- package/backends/pdfform/wasm_bg.wasm +0 -0
- package/backends/pdfform/wasm_bg.wasm.d.ts +8 -8
- package/backends/typst/wasm.d.ts +232 -68
- package/backends/typst/wasm.js +3004 -8
- package/backends/typst/wasm_bg.wasm +0 -0
- package/backends/typst/wasm_bg.wasm.d.ts +8 -8
- package/core/wasm.d.ts +199 -59
- package/core/wasm.js +2471 -8
- package/core/wasm_bg.wasm +0 -0
- package/core/wasm_bg.wasm.d.ts +7 -7
- package/package.json +7 -4
- package/runtime/env-node.js +20 -0
- package/runtime/env-web.js +19 -0
- package/runtime/runtime.d.ts +60 -26
- package/runtime/runtime.js +200 -41
- package/runtime/uninit.js +57 -0
- package/backends/pdfform/wasm_bg.js +0 -2863
- package/backends/typst/wasm_bg.js +0 -2887
- package/core/wasm_bg.js +0 -2359
package/runtime/runtime.js
CHANGED
|
@@ -52,6 +52,10 @@
|
|
|
52
52
|
// verbatim" case (`Quill === CoreQuill`) is the executable guard for this
|
|
53
53
|
// invariant.
|
|
54
54
|
//
|
|
55
|
+
// It is also why the pre-init guard sits inside the generated builds instead
|
|
56
|
+
// of here: nothing may stand between a consumer and these classes
|
|
57
|
+
// (runtime/uninit.js).
|
|
58
|
+
//
|
|
55
59
|
// The identity is what makes `instanceof` the whole membership test: a handle
|
|
56
60
|
// either belongs to this copy's classes or it belongs to another copy, and the
|
|
57
61
|
// second is always a consumer bug. `Engine` is NOT duck-typed on its inputs; it
|
|
@@ -60,8 +64,16 @@
|
|
|
60
64
|
// Imported (not bare re-exported) so `Quill` is a local binding this module can
|
|
61
65
|
// augment: `quill.writer(doc)` is patched onto its prototype below. The
|
|
62
66
|
// re-export keeps the identity: the exported `Quill` IS the core class.
|
|
63
|
-
|
|
64
|
-
|
|
67
|
+
//
|
|
68
|
+
// The default import is the core build's generated instantiation entry
|
|
69
|
+
// (`--target web`); `init` below is the only thing that calls it.
|
|
70
|
+
import initCore, { Quill, Document } from '../core/wasm.js';
|
|
71
|
+
export { Quill, Document };
|
|
72
|
+
// The wasm byte source, resolved per environment by package.json's `imports`
|
|
73
|
+
// map: a pass-through in a browser (the glue fetches and streams the URL
|
|
74
|
+
// itself), a `node:fs` read under Node, whose `fetch` rejects `file:` URLs.
|
|
75
|
+
// Resolution-time, so `node:fs` never enters a browser graph.
|
|
76
|
+
import { toModuleSource } from '#quillmark-env';
|
|
65
77
|
// The document-free content codec: re-exported verbatim from the core build so
|
|
66
78
|
// the runtime subpath exposes `exportMarkdown(body)` (the on-demand markdown
|
|
67
79
|
// projection), `importMarkdown`, and the position-mapping pair (`rebase`,
|
|
@@ -72,6 +84,100 @@ export { importMarkdown, exportMarkdown, rebase, mapPos } from '../core/wasm.js'
|
|
|
72
84
|
// segments instead of reverse-engineering the grammar.
|
|
73
85
|
export { parseDocPath, formatDocPath } from '../core/wasm.js';
|
|
74
86
|
|
|
87
|
+
// ── Initialization ──────────────────────────────────────────────────────────
|
|
88
|
+
// The builds are `--target web`: they export their classes synchronously but
|
|
89
|
+
// carry no wasm instance until something instantiates them. This module owns
|
|
90
|
+
// that for core, behind one awaited gate; `Engine` owns it for the backends,
|
|
91
|
+
// inside their lazy load, so a consumer never initializes a backend by hand.
|
|
92
|
+
//
|
|
93
|
+
// The gate is the shape the lazy-backend idiom (§ DEFAULT_BACKENDS) takes when
|
|
94
|
+
// the surface it guards cannot be async: `Quill.fromTree` and `seedDocument`
|
|
95
|
+
// are sync and static, so there is nowhere to hide an await except in front.
|
|
96
|
+
//
|
|
97
|
+
// Reaching core before the gate resolves is not a silent wrong answer: the
|
|
98
|
+
// build is patched to throw `runtime::not_initialized` naming the fix
|
|
99
|
+
// (runtime/uninit.js).
|
|
100
|
+
|
|
101
|
+
/** The in-flight or settled core instantiation. The memo is the PROMISE, not a
|
|
102
|
+
* boolean, so concurrent callers share one instantiation instead of racing. */
|
|
103
|
+
let coreInit;
|
|
104
|
+
/** The source `init` was first called with; the conflict check reads it. */
|
|
105
|
+
let coreInitSource;
|
|
106
|
+
|
|
107
|
+
/**
|
|
108
|
+
* Instantiate the core WASM build. Call once at startup, before any other
|
|
109
|
+
* export is used; extra calls are free.
|
|
110
|
+
*
|
|
111
|
+
* ```js
|
|
112
|
+
* import { init, Quill, Engine } from '@quillmark/wasm';
|
|
113
|
+
* await init();
|
|
114
|
+
* ```
|
|
115
|
+
*
|
|
116
|
+
* Identical in every environment: in a browser the binary is fetched and
|
|
117
|
+
* streamed, under Node it is read off disk, and the call site is the same line.
|
|
118
|
+
*
|
|
119
|
+
* Idempotent and concurrency-safe: every call returns the same promise, so
|
|
120
|
+
* `await init()` at each of several entry points costs one instantiation. A
|
|
121
|
+
* failed init clears the memo, so a retry is possible.
|
|
122
|
+
*
|
|
123
|
+
* @param {import('../core/wasm.js').InitInput} [source] override the binary's
|
|
124
|
+
* source (bytes, a `Response`, a `WebAssembly.Module`, a URL) for hosts that
|
|
125
|
+
* route assets themselves or embed the binary. Pass it on the FIRST call; a
|
|
126
|
+
* later call passing a *different* source throws `runtime::init_conflict`
|
|
127
|
+
* rather than silently ignoring it. Passing the same value again is fine, so
|
|
128
|
+
* several entry points may each `await init(BYTES)` against one constant.
|
|
129
|
+
* @returns {Promise<void>} resolves when the sync surface is usable
|
|
130
|
+
*/
|
|
131
|
+
export function init(source) {
|
|
132
|
+
if (coreInit) {
|
|
133
|
+
if (source !== undefined && source !== coreInitSource) {
|
|
134
|
+
throw quillmarkError(
|
|
135
|
+
'runtime::init_conflict',
|
|
136
|
+
'init(source): core is already initializing or initialized from a different source.',
|
|
137
|
+
'Pass a source on the first call only, or pass the same value every time.'
|
|
138
|
+
);
|
|
139
|
+
}
|
|
140
|
+
return coreInit;
|
|
141
|
+
}
|
|
142
|
+
coreInitSource = source;
|
|
143
|
+
// Assign before the first await so a synchronous second call sees the memo.
|
|
144
|
+
coreInit = instantiateCore(source).catch((err) => {
|
|
145
|
+
// Self-heal, as `#resolveBackend` does: one transient failure (a 404, an
|
|
146
|
+
// offline fetch) must not poison every later attempt.
|
|
147
|
+
coreInit = undefined;
|
|
148
|
+
coreInitSource = undefined;
|
|
149
|
+
throw err;
|
|
150
|
+
});
|
|
151
|
+
return coreInit;
|
|
152
|
+
}
|
|
153
|
+
|
|
154
|
+
/**
|
|
155
|
+
* @param {import('../core/wasm.js').InitInput | undefined} source
|
|
156
|
+
* @returns {Promise<void>}
|
|
157
|
+
*/
|
|
158
|
+
async function instantiateCore(source) {
|
|
159
|
+
// The literal `new URL(..., import.meta.url)` form: every bundler rewrites it
|
|
160
|
+
// into an emitted asset, and unbundled browsers and Node resolve it against
|
|
161
|
+
// the shipped package layout.
|
|
162
|
+
const resolved = await toModuleSource(
|
|
163
|
+
source ?? new URL('../core/wasm_bg.wasm', import.meta.url)
|
|
164
|
+
);
|
|
165
|
+
try {
|
|
166
|
+
await initCore({ module_or_path: resolved });
|
|
167
|
+
} catch (cause) {
|
|
168
|
+
throw Object.assign(
|
|
169
|
+
quillmarkError(
|
|
170
|
+
'runtime::init_failed',
|
|
171
|
+
`init(): could not load or instantiate the core WASM binary: ${
|
|
172
|
+
/** @type {any} */ (cause)?.message ?? cause
|
|
173
|
+
}`,
|
|
174
|
+
"The binary ships beside the package files; check the network tab for a 404 or an HTML response. Under Vite's dev server, dependency pre-bundling moves the package away from it: add optimizeDeps: { exclude: ['@quillmark/wasm'] }."
|
|
175
|
+
),
|
|
176
|
+
{ cause }
|
|
177
|
+
);
|
|
178
|
+
}
|
|
179
|
+
}
|
|
180
|
+
|
|
75
181
|
// ── The main-card address ───────────────────────────────────────────────────
|
|
76
182
|
/**
|
|
77
183
|
* The main card's address: the default target of the card-scoped verbs
|
|
@@ -143,7 +249,7 @@ function quillmarkError(code, message, hint) {
|
|
|
143
249
|
// front-run it with a `QuillmarkError` that names both.
|
|
144
250
|
//
|
|
145
251
|
// They also cover the seams with NO `_assertClass` to front-run: `Engine` and
|
|
146
|
-
// `LiveSession.
|
|
252
|
+
// `LiveSession.update` cross into backend memory as data (`toTree`/`toJson`), so
|
|
147
253
|
// a foreign handle there would silently work, at the price of the round-trip
|
|
148
254
|
// above and a quill clone cache split per copy.
|
|
149
255
|
//
|
|
@@ -391,11 +497,51 @@ export function isUnknownIsland(island) {
|
|
|
391
497
|
return typeof island?.type === 'string' && !KNOWN_ISLAND_TYPES.has(island.type);
|
|
392
498
|
}
|
|
393
499
|
|
|
500
|
+
/**
|
|
501
|
+
* Build a `load` thunk: dynamic-import a backend build, then instantiate it.
|
|
502
|
+
*
|
|
503
|
+
* Under `--target web` a freshly imported build is inert (the import resolves
|
|
504
|
+
* before there is a wasm instance behind the classes), so instantiation is part
|
|
505
|
+
* of loading and the consumer never sees it. Memoized at MODULE scope, not per
|
|
506
|
+
* `Engine`: two engines issuing their first render concurrently must share one
|
|
507
|
+
* instantiation, and the generated entry's own `wasm !== undefined` guard only
|
|
508
|
+
* catches a call that arrives after one finished, not one already in flight.
|
|
509
|
+
*
|
|
510
|
+
* @param {string} id backend id, for the failure message
|
|
511
|
+
* @param {() => Promise<any>} importThunk the dynamic `import()`
|
|
512
|
+
* @param {() => URL} wasmUrl the build's binary, resolved at call time
|
|
513
|
+
* @returns {() => Promise<any>} resolves to a ready-to-use module
|
|
514
|
+
*/
|
|
515
|
+
function backendLoad(id, importThunk, wasmUrl) {
|
|
516
|
+
/** @type {Promise<any> | undefined} */
|
|
517
|
+
let loaded;
|
|
518
|
+
return () =>
|
|
519
|
+
(loaded ??= (async () => {
|
|
520
|
+
const mod = await importThunk();
|
|
521
|
+
await mod.default({ module_or_path: await toModuleSource(wasmUrl()) });
|
|
522
|
+
return mod;
|
|
523
|
+
})().catch((cause) => {
|
|
524
|
+
// Self-heal, as core's `init` does.
|
|
525
|
+
loaded = undefined;
|
|
526
|
+
throw Object.assign(
|
|
527
|
+
quillmarkError(
|
|
528
|
+
'runtime::backend_load_failed',
|
|
529
|
+
`Engine: could not load the '${id}' backend: ${
|
|
530
|
+
/** @type {any} */ (cause)?.message ?? cause
|
|
531
|
+
}`,
|
|
532
|
+
'The backend binary ships beside the package files; check the network tab for a 404 or an HTML response.'
|
|
533
|
+
),
|
|
534
|
+
{ cause }
|
|
535
|
+
);
|
|
536
|
+
}));
|
|
537
|
+
}
|
|
538
|
+
|
|
394
539
|
// Backend builds are NEVER statically imported here: that would pull a
|
|
395
540
|
// multi-MB binary into the eager graph and defeat lazy loading. Each entry is a
|
|
396
|
-
// DESCRIPTOR: `load` is a thunk
|
|
397
|
-
//
|
|
398
|
-
//
|
|
541
|
+
// DESCRIPTOR: `load` is a thunk that dynamically imports a backend's chunk and
|
|
542
|
+
// instantiates it, so the binary is fetched only when something actually renders
|
|
543
|
+
// against that backend and is ready to use when the promise resolves;
|
|
544
|
+
// `formats`/`canvas` are the REQUIRED static capability manifest so the
|
|
399
545
|
// cheap probes (`supportedFormats`/`supportsCanvas`) ALWAYS answer without
|
|
400
546
|
// loading the binary or cloning the quill. The manifest values are verified
|
|
401
547
|
// against each backend's Rust source (`crates/backends/<id>/src/lib.rs`
|
|
@@ -405,12 +551,20 @@ export function isUnknownIsland(island) {
|
|
|
405
551
|
// format list includes a visual-page format (`svg` or `png`).
|
|
406
552
|
const DEFAULT_BACKENDS = {
|
|
407
553
|
typst: {
|
|
408
|
-
load: (
|
|
554
|
+
load: backendLoad(
|
|
555
|
+
'typst',
|
|
556
|
+
() => import('../backends/typst/wasm.js'),
|
|
557
|
+
() => new URL('../backends/typst/wasm_bg.wasm', import.meta.url)
|
|
558
|
+
),
|
|
409
559
|
formats: ['pdf', 'svg', 'png'], // crates/backends/typst/src/lib.rs SUPPORTED_FORMATS
|
|
410
560
|
canvas: true // has svg/png → formats_support_canvas == true
|
|
411
561
|
},
|
|
412
562
|
pdfform: {
|
|
413
|
-
load: (
|
|
563
|
+
load: backendLoad(
|
|
564
|
+
'pdfform',
|
|
565
|
+
() => import('../backends/pdfform/wasm.js'),
|
|
566
|
+
() => new URL('../backends/pdfform/wasm_bg.wasm', import.meta.url)
|
|
567
|
+
),
|
|
414
568
|
// crates/backends/pdfform/src/lib.rs SUPPORTED_FORMATS == [Pdf, Svg, Png]
|
|
415
569
|
formats: ['pdf', 'svg', 'png'],
|
|
416
570
|
canvas: true // has svg/png → formats_support_canvas == true
|
|
@@ -475,6 +629,10 @@ export class Engine {
|
|
|
475
629
|
* `supportedFormats`/`supportsCanvas` always free (no binary load, no quill
|
|
476
630
|
* clone). Malformed entries throw here, at construction. The default
|
|
477
631
|
* registry maps `"typst"` to the bundled Typst build.
|
|
632
|
+
*
|
|
633
|
+
* `load` resolves to a READY module: a registrant shipping its own
|
|
634
|
+
* `--target web` build instantiates inside the thunk. More than one
|
|
635
|
+
* `Engine` may call it, so memoize (the built-ins do, at module scope).
|
|
478
636
|
*/
|
|
479
637
|
constructor(options) {
|
|
480
638
|
const merged = { ...DEFAULT_BACKENDS, ...(options?.backends ?? {}) };
|
|
@@ -641,8 +799,8 @@ export class Engine {
|
|
|
641
799
|
}
|
|
642
800
|
|
|
643
801
|
/**
|
|
644
|
-
* Open a live render session (canvas preview / per-page paint / `
|
|
645
|
-
* The session is self-contained (it retains what it needs for `
|
|
802
|
+
* Open a live render session (canvas preview / per-page paint / `update`).
|
|
803
|
+
* The session is self-contained (it retains what it needs for `update`), so
|
|
646
804
|
* the transient quill and document clones are freed before this returns;
|
|
647
805
|
* the caller owns the returned session and must `.free()` it. The `quill`
|
|
648
806
|
* and `doc` handles are read synchronously before the first await, so the
|
|
@@ -693,15 +851,15 @@ export class Engine {
|
|
|
693
851
|
|
|
694
852
|
/**
|
|
695
853
|
* Thin wrapper over a backend's live render session. Reads serve the current
|
|
696
|
-
* compile; `
|
|
854
|
+
* compile; `update(doc)` recompiles in place (transactional: on throw, reads
|
|
697
855
|
* keep serving the last-good compile). The quill/document clones it was
|
|
698
|
-
* opened from have already been freed: the session retains what `
|
|
856
|
+
* opened from have already been freed: the session retains what `update`
|
|
699
857
|
* needs.
|
|
700
858
|
*
|
|
701
859
|
* Geometry reads (`regions`, `positionAt`, `locate`) resolve against the
|
|
702
860
|
* current compile; anchoring a caret or selection across edits is the editor's
|
|
703
861
|
* job (its own transaction mapping): re-read geometry after each committed
|
|
704
|
-
* `
|
|
862
|
+
* `update`.
|
|
705
863
|
*
|
|
706
864
|
* `paint` writes a COMPLETE page raster (all content visible, no caller-side
|
|
707
865
|
* compositing) for every backend that supports canvas (Typst rasterizes
|
|
@@ -709,8 +867,8 @@ export class Engine {
|
|
|
709
867
|
*/
|
|
710
868
|
export class LiveSession {
|
|
711
869
|
/**
|
|
712
|
-
* @param {{ pageCount: number, backendId: string, supportsCanvas: boolean, warnings: any[],
|
|
713
|
-
* @param {{ Document: { fromJson(json: string): any } }} mod the session's backend build, used to materialize `
|
|
870
|
+
* @param {{ pageCount: number, backendId: string, supportsCanvas: boolean, warnings: any[], update: Function, render: Function, regions: Function, pageSize: Function, paint: Function, free: Function }} inner backend-build LiveSession (typst or pdfform)
|
|
871
|
+
* @param {{ Document: { fromJson(json: string): any } }} mod the session's backend build, used to materialize `update` documents in its linear memory
|
|
714
872
|
*/
|
|
715
873
|
constructor(inner, mod) {
|
|
716
874
|
this.#inner = inner;
|
|
@@ -726,12 +884,12 @@ export class LiveSession {
|
|
|
726
884
|
* @param {Document} doc
|
|
727
885
|
* @returns {import('./runtime.d.ts').ChangeSet}
|
|
728
886
|
*/
|
|
729
|
-
|
|
730
|
-
requireLocalDoc(doc, 'session.
|
|
887
|
+
update(doc) {
|
|
888
|
+
requireLocalDoc(doc, 'session.update(doc)');
|
|
731
889
|
let backendDoc = null;
|
|
732
890
|
try {
|
|
733
891
|
backendDoc = this.#mod.Document.fromJson(doc.toJson());
|
|
734
|
-
return this.#inner.
|
|
892
|
+
return this.#inner.update(backendDoc);
|
|
735
893
|
} finally {
|
|
736
894
|
backendDoc?.free();
|
|
737
895
|
}
|
|
@@ -907,14 +1065,15 @@ export class DocumentWriter {
|
|
|
907
1065
|
return this.#doc._commitFields(this.#quill, MAIN_CARD_ADDR, fields);
|
|
908
1066
|
}
|
|
909
1067
|
/**
|
|
910
|
-
*
|
|
911
|
-
*
|
|
912
|
-
*
|
|
1068
|
+
* Revise the main body from markdown (edit semantics: surviving anchors
|
|
1069
|
+
* rebase), returning the text {@link Delta}. The content lane's `revise`
|
|
1070
|
+
* reached through the writer: a body carries no field schema, so there is
|
|
1071
|
+
* nothing for a typed verb to type, and the receipt is the content lane's.
|
|
913
1072
|
* @param {string} markdown
|
|
914
|
-
* @returns {
|
|
1073
|
+
* @returns {import('../core/wasm.js').Delta}
|
|
915
1074
|
*/
|
|
916
|
-
|
|
917
|
-
this.#doc.revise({}, markdown);
|
|
1075
|
+
reviseBody(markdown) {
|
|
1076
|
+
return this.#doc.revise({}, markdown);
|
|
918
1077
|
}
|
|
919
1078
|
/**
|
|
920
1079
|
* Revise the content main-card field `name` from authored text: typed *and*
|
|
@@ -1033,13 +1192,13 @@ export class CardWriter {
|
|
|
1033
1192
|
return this.#doc._commitFields(this.#quill, { card: this.#index }, fields);
|
|
1034
1193
|
}
|
|
1035
1194
|
/**
|
|
1036
|
-
*
|
|
1037
|
-
* the card twin of {@link DocumentWriter.
|
|
1195
|
+
* Revise this card's body from markdown (edit semantics), returning the text
|
|
1196
|
+
* {@link Delta}: the card twin of {@link DocumentWriter.reviseBody}.
|
|
1038
1197
|
* @param {string} markdown
|
|
1039
|
-
* @returns {
|
|
1198
|
+
* @returns {import('../core/wasm.js').Delta}
|
|
1040
1199
|
*/
|
|
1041
|
-
|
|
1042
|
-
this.#doc.revise({ card: this.#index }, markdown);
|
|
1200
|
+
reviseBody(markdown) {
|
|
1201
|
+
return this.#doc.revise({ card: this.#index }, markdown);
|
|
1043
1202
|
}
|
|
1044
1203
|
/**
|
|
1045
1204
|
* Revise the content field `name` on this card from authored text: typed *and*
|
|
@@ -1084,7 +1243,7 @@ Quill.prototype.writer = function writer(doc) {
|
|
|
1084
1243
|
// interpret by declared type: a richtext field to markdown, a plaintext field to
|
|
1085
1244
|
// its literal text, every other type verbatim, and an unknown name throws
|
|
1086
1245
|
// `UnknownField`. A field's markdown lives here, not on the body-only
|
|
1087
|
-
// `
|
|
1246
|
+
// `bodyMarkdown`. Like the writer classes these hold the caller's handles by
|
|
1088
1247
|
// reference, own no WASM object, and have nothing to `free()`.
|
|
1089
1248
|
|
|
1090
1249
|
/**
|
|
@@ -1115,7 +1274,7 @@ export class DocumentReader {
|
|
|
1115
1274
|
* to markdown, every other type verbatim. A bare string is `Addr` shorthand
|
|
1116
1275
|
* for `{ field }`; an absent `addr.field` reads the body markdown. `undefined`
|
|
1117
1276
|
* for an absent field; throws `UnknownField` for a name the schema does not
|
|
1118
|
-
* declare, `
|
|
1277
|
+
* declare, `FieldDecode` for a richtext field holding an undecodable
|
|
1119
1278
|
* value, and `IndexOutOfRange` for a bad `addr.card`.
|
|
1120
1279
|
* @param {import('../core/wasm.js').Addr | string} addr
|
|
1121
1280
|
* @returns {unknown}
|
|
@@ -1124,14 +1283,14 @@ export class DocumentReader {
|
|
|
1124
1283
|
return this.#doc._readerGet(this.#quill, addr);
|
|
1125
1284
|
}
|
|
1126
1285
|
/**
|
|
1127
|
-
* Read the content field at `addr` as its canonical `Content
|
|
1128
|
-
*
|
|
1286
|
+
* Read the content field at `addr` as its canonical `Content`: the
|
|
1287
|
+
* `Content` twin of {@link get}, which projects. Decodes through the codec the
|
|
1129
1288
|
* declared type names (`richtext` as markdown, `plaintext` as literal text),
|
|
1130
|
-
* so a field the writer committed as a
|
|
1289
|
+
* so a field the writer committed as a `Content` and one a markdown parse left
|
|
1131
1290
|
* as an authored string read back the same; no branching on how the
|
|
1132
|
-
* document was built. An absent `addr.field` reads the body
|
|
1291
|
+
* document was built. An absent `addr.field` reads the body `Content`.
|
|
1133
1292
|
* `undefined` for an absent field; throws `UnknownField`, `FieldNotContent`
|
|
1134
|
-
* for a type that is not a content leaf, `
|
|
1293
|
+
* for a type that is not a content leaf, `FieldDecode` for an undecodable
|
|
1135
1294
|
* value, and `IndexOutOfRange` for a bad `addr.card`.
|
|
1136
1295
|
* @param {import('../core/wasm.js').Addr | string} addr
|
|
1137
1296
|
* @returns {import('../core/wasm.js').Content | undefined}
|
|
@@ -1144,7 +1303,7 @@ export class DocumentReader {
|
|
|
1144
1303
|
* format fact, not a schema fact). Equivalent to `get({})`.
|
|
1145
1304
|
* @returns {string}
|
|
1146
1305
|
*/
|
|
1147
|
-
|
|
1306
|
+
bodyMarkdown() {
|
|
1148
1307
|
return this.#doc._readerGet(this.#quill, {});
|
|
1149
1308
|
}
|
|
1150
1309
|
/**
|
|
@@ -1163,7 +1322,7 @@ export class DocumentReader {
|
|
|
1163
1322
|
|
|
1164
1323
|
/**
|
|
1165
1324
|
* A single composable card bound to its {@link Quill} for typed reads, from
|
|
1166
|
-
* {@link DocumentReader.card}. Same `get` / `
|
|
1325
|
+
* {@link DocumentReader.card}. Same `get` / `bodyMarkdown` verbs as
|
|
1167
1326
|
* {@link DocumentReader}, reading the card at its bound index.
|
|
1168
1327
|
*/
|
|
1169
1328
|
export class CardReader {
|
|
@@ -1206,7 +1365,7 @@ export class CardReader {
|
|
|
1206
1365
|
}
|
|
1207
1366
|
/**
|
|
1208
1367
|
* Read the content field `name` on this card as its canonical `Content`
|
|
1209
|
-
*
|
|
1368
|
+
* `Content`: the card twin of {@link DocumentReader.getContent}.
|
|
1210
1369
|
* @param {string} name
|
|
1211
1370
|
* @returns {import('../core/wasm.js').Content | undefined}
|
|
1212
1371
|
*/
|
|
@@ -1214,10 +1373,10 @@ export class CardReader {
|
|
|
1214
1373
|
return this.#doc._readerGetContent(this.#quill, { card: this.#index, field: name });
|
|
1215
1374
|
}
|
|
1216
1375
|
/**
|
|
1217
|
-
* This card's body markdown: the card twin of {@link DocumentReader.
|
|
1376
|
+
* This card's body markdown: the card twin of {@link DocumentReader.bodyMarkdown}.
|
|
1218
1377
|
* @returns {string}
|
|
1219
1378
|
*/
|
|
1220
|
-
|
|
1379
|
+
bodyMarkdown() {
|
|
1221
1380
|
return this.#doc._readerGet(this.#quill, { card: this.#index });
|
|
1222
1381
|
}
|
|
1223
1382
|
}
|
|
@@ -0,0 +1,57 @@
|
|
|
1
|
+
// The pre-initialization sentinel.
|
|
2
|
+
//
|
|
3
|
+
// wasm-bindgen's `--target web` glue holds its instance exports in one
|
|
4
|
+
// module-level binding (`let wasmModule, wasm;`) that every generated path
|
|
5
|
+
// reads through: constructors, statics, methods, free functions. Until
|
|
6
|
+
// instantiation assigns it, that binding is `undefined`, so a consumer who
|
|
7
|
+
// skipped `await init()` gets `Cannot read properties of undefined (reading
|
|
8
|
+
// 'quill_fromTree')` from inside generated code.
|
|
9
|
+
//
|
|
10
|
+
// `scripts/build-wasm.sh` (`guard_wasm_js`) patches the binding to start as
|
|
11
|
+
// this sentinel, so the same access throws a `QuillmarkError` naming the cause
|
|
12
|
+
// and the fix. The patch is three line edits per variant, shape-asserted before
|
|
13
|
+
// it applies; the logic lives here rather than inside the awk program.
|
|
14
|
+
//
|
|
15
|
+
// It sits in the generated build, not in the runtime layer, because the
|
|
16
|
+
// canonical invariant (`runtime.js` § "CANONICAL INVARIANT") requires the root
|
|
17
|
+
// to re-export `Quill`/`Document` VERBATIM: no wrapper, subclass, or proxy may
|
|
18
|
+
// stand between a consumer and those classes. Prototype patching preserves
|
|
19
|
+
// identity but cannot cover a public constructor (`new Document(...)`) without
|
|
20
|
+
// wrapping the class. The binding they all read through is the one place that
|
|
21
|
+
// covers every path and touches no class.
|
|
22
|
+
|
|
23
|
+
/**
|
|
24
|
+
* A stand-in for a wasm build's exports that throws on use. Every property read
|
|
25
|
+
* throws except the marker the patched init guards test, and `then`,
|
|
26
|
+
* `constructor`, and symbols, which return `undefined`: an incidental `await`
|
|
27
|
+
* or `util.inspect` reports nothing rather than throwing somewhere unrelated to
|
|
28
|
+
* the cause.
|
|
29
|
+
*
|
|
30
|
+
* @param {string} message what a consumer sees when they reach a build early
|
|
31
|
+
* @param {string} hint the fix, as a `Diagnostic.hint`
|
|
32
|
+
* @returns {any} the sentinel
|
|
33
|
+
*/
|
|
34
|
+
export function uninitSentinel(message, hint) {
|
|
35
|
+
return new Proxy(
|
|
36
|
+
{},
|
|
37
|
+
{
|
|
38
|
+
get(_target, prop) {
|
|
39
|
+
if (prop === UNINIT) return true;
|
|
40
|
+
if (prop === 'then' || prop === 'constructor' || typeof prop === 'symbol') {
|
|
41
|
+
return undefined;
|
|
42
|
+
}
|
|
43
|
+
const err = /** @type {any} */ (new Error(message));
|
|
44
|
+
err.diagnostics = [
|
|
45
|
+
{ severity: 'error', code: 'runtime::not_initialized', message, hint }
|
|
46
|
+
];
|
|
47
|
+
throw err;
|
|
48
|
+
}
|
|
49
|
+
}
|
|
50
|
+
);
|
|
51
|
+
}
|
|
52
|
+
|
|
53
|
+
/**
|
|
54
|
+
* The marker the patched `if (wasm !== undefined)` guards read to tell a
|
|
55
|
+
* sentinel from real instance exports, which never carry it.
|
|
56
|
+
*/
|
|
57
|
+
export const UNINIT = '__quillmarkUninit';
|