@quillmark/wasm 0.102.0 → 0.104.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 +170 -0
- package/README.md +40 -27
- package/backends/pdfform/wasm.d.ts +60 -6
- package/backends/pdfform/wasm.js +75 -4
- package/backends/pdfform/wasm_bg.wasm +0 -0
- package/backends/pdfform/wasm_bg.wasm.d.ts +2 -0
- package/backends/typst/wasm.d.ts +60 -6
- package/backends/typst/wasm.js +75 -4
- package/backends/typst/wasm_bg.wasm +0 -0
- package/backends/typst/wasm_bg.wasm.d.ts +2 -0
- package/core/wasm.d.ts +59 -5
- package/core/wasm.js +75 -4
- package/core/wasm_bg.wasm +0 -0
- package/core/wasm_bg.wasm.d.ts +2 -0
- package/package.json +2 -2
- package/runtime/runtime.d.ts +68 -34
- package/runtime/runtime.js +131 -66
- package/runtime/uninit.js +0 -57
package/runtime/runtime.js
CHANGED
|
@@ -2,15 +2,17 @@
|
|
|
2
2
|
//
|
|
3
3
|
// @quillmark/wasm/runtime: the canonical consumer API.
|
|
4
4
|
//
|
|
5
|
-
// Consumers
|
|
6
|
-
// the build-specific subpaths. The package
|
|
7
|
-
// SEPARATE linear memories: a Typst-less
|
|
8
|
-
// the canonical home of `Quill`/`Document`,
|
|
9
|
-
// backend (`backends/typst/` today; more
|
|
10
|
-
// handle from one memory cannot be used by
|
|
11
|
-
// and is exposed at the package root
|
|
5
|
+
// Consumers reach `Quill` and `Document` through `await init()` and `Engine`
|
|
6
|
+
// as a static export, and never touch the build-specific subpaths. The package
|
|
7
|
+
// ships multiple WASM binaries with SEPARATE linear memories: a Typst-less
|
|
8
|
+
// `core` build (small, eager) that is the canonical home of `Quill`/`Document`,
|
|
9
|
+
// and one private backend binary per backend (`backends/typst/` today; more
|
|
10
|
+
// later) that carries an engine. A handle from one memory cannot be used by
|
|
11
|
+
// another. This module hides that seam and is exposed at the package root
|
|
12
|
+
// (`@quillmark/wasm`):
|
|
12
13
|
//
|
|
13
|
-
// - `Quill` and `Document` ARE the core build's classes,
|
|
14
|
+
// - `Quill` and `Document` ARE the core build's classes, handed out by the
|
|
15
|
+
// gate (§ "Initialization"). They
|
|
14
16
|
// hold the canonical data and the full sync surface (schema / validate /
|
|
15
17
|
// seed / mutate / toJson / toTree). No backend is loaded to use them, so
|
|
16
18
|
// the editor/validation path never pays for a multi-MB backend binary.
|
|
@@ -38,51 +40,42 @@
|
|
|
38
40
|
// The cross-memory crossing is therefore invisible: a consumer hands canonical
|
|
39
41
|
// `Quill`/`Document` to `engine.render(...)` and gets a `RenderResult` back.
|
|
40
42
|
|
|
41
|
-
// ── CANONICAL INVARIANT:
|
|
42
|
-
// The
|
|
43
|
-
//
|
|
44
|
-
//
|
|
45
|
-
//
|
|
46
|
-
//
|
|
47
|
-
// (`toTree`/`toJson`).
|
|
43
|
+
// ── CANONICAL INVARIANT: hand out the core build's classes, never wrap ──────
|
|
44
|
+
// The `Quill`/`Document` a consumer holds ARE the core build's classes, NOT
|
|
45
|
+
// subclasses or wrappers. `init` resolves to them (§ "Initialization"); which
|
|
46
|
+
// door they come through changes nothing about the identity. The only boundary
|
|
47
|
+
// that needs crossing is core→backend (a separate WASM memory), which `Engine`
|
|
48
|
+
// does internally as data (`toTree`/`toJson`).
|
|
48
49
|
//
|
|
49
|
-
// Do NOT
|
|
50
|
-
//
|
|
51
|
-
//
|
|
52
|
-
//
|
|
53
|
-
// invariant.
|
|
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).
|
|
50
|
+
// Do NOT hand out a wrapper: that breaks the identity and turns a structural
|
|
51
|
+
// fact into a converted type (a breaking design change, not a refactor). The
|
|
52
|
+
// `runtime.test.js` "hands out the internal core build classes verbatim" case
|
|
53
|
+
// (`Quill === CoreQuill`) is the executable guard for this invariant.
|
|
58
54
|
//
|
|
59
55
|
// The identity is what makes `instanceof` the whole membership test: a handle
|
|
60
56
|
// either belongs to this copy's classes or it belongs to another copy, and the
|
|
61
57
|
// second is always a consumer bug. `Engine` is NOT duck-typed on its inputs; it
|
|
62
58
|
// checks them. See § "Handles from another copy" below.
|
|
63
59
|
//
|
|
64
|
-
//
|
|
65
|
-
//
|
|
66
|
-
// re-export keeps the identity: the exported `Quill` IS the core class.
|
|
60
|
+
// Local bindings, so this module can augment them: `quill.writer(doc)` is
|
|
61
|
+
// patched onto the prototype below, and `instanceof` reads them directly.
|
|
67
62
|
//
|
|
68
63
|
// The default import is the core build's generated instantiation entry
|
|
69
64
|
// (`--target web`); `init` below is the only thing that calls it.
|
|
70
65
|
import initCore, { Quill, Document } from '../core/wasm.js';
|
|
71
|
-
export { Quill, Document };
|
|
72
66
|
// The wasm byte source, resolved per environment by package.json's `imports`
|
|
73
67
|
// map: a pass-through in a browser (the glue fetches and streams the URL
|
|
74
68
|
// itself), a `node:fs` read under Node, whose `fetch` rejects `file:` URLs.
|
|
75
69
|
// Resolution-time, so `node:fs` never enters a browser graph.
|
|
76
70
|
import { toModuleSource } from '#quillmark-env';
|
|
77
|
-
// The document-free content codec:
|
|
78
|
-
//
|
|
79
|
-
//
|
|
80
|
-
|
|
81
|
-
export { importMarkdown, exportMarkdown, rebase, mapPos } from '../core/wasm.js';
|
|
71
|
+
// The document-free content codec: `exportMarkdown(body)` (the on-demand
|
|
72
|
+
// markdown projection), `importMarkdown`, and the position-mapping pair
|
|
73
|
+
// (`rebase`, `mapPos`).
|
|
74
|
+
import { importMarkdown, exportMarkdown, rebase, mapPos } from '../core/wasm.js';
|
|
82
75
|
// The document-model path parser/serializer: `parseDocPath(str) => DocPathSeg[]`
|
|
83
76
|
// and its inverse `formatDocPath`, so a consumer routes on `Diagnostic.path`
|
|
84
77
|
// segments instead of reverse-engineering the grammar.
|
|
85
|
-
|
|
78
|
+
import { parseDocPath, formatDocPath } from '../core/wasm.js';
|
|
86
79
|
|
|
87
80
|
// ── Initialization ──────────────────────────────────────────────────────────
|
|
88
81
|
// The builds are `--target web`: they export their classes synchronously but
|
|
@@ -90,64 +83,135 @@ export { parseDocPath, formatDocPath } from '../core/wasm.js';
|
|
|
90
83
|
// that for core, behind one awaited gate; `Engine` owns it for the backends,
|
|
91
84
|
// inside their lazy load, so a consumer never initializes a backend by hand.
|
|
92
85
|
//
|
|
86
|
+
// THE GATE IS THE ONLY DOOR. `init` resolves to the core surface, and this
|
|
87
|
+
// module exports none of it statically, so a handle is unobtainable without
|
|
88
|
+
// having awaited: the precondition is structural rather than a convention the
|
|
89
|
+
// caller has to know. package.json's `exports` map carries exactly one entry,
|
|
90
|
+
// so there is no subpath around the gate either.
|
|
91
|
+
//
|
|
93
92
|
// The gate is the shape the lazy-backend idiom (§ DEFAULT_BACKENDS) takes when
|
|
94
|
-
// the surface it guards cannot be async: `Quill.fromTree` and
|
|
95
|
-
//
|
|
93
|
+
// the surface it guards cannot be async: `Quill.fromTree` and
|
|
94
|
+
// `quill.seedDocument` return synchronously, so there is nowhere to hide an
|
|
95
|
+
// await except in front.
|
|
96
|
+
//
|
|
97
|
+
// WHAT STAYS A STATIC EXPORT is what needs no instance. `MAIN_CARD_ADDR`, the
|
|
98
|
+
// open-set guards and `isQuillmarkError` are pure JS over plain objects; gating
|
|
99
|
+
// them would cost a consumer of one an await it has no use for.
|
|
96
100
|
//
|
|
97
|
-
//
|
|
98
|
-
//
|
|
99
|
-
// (
|
|
101
|
+
// `Engine`, `LiveSession` and the four writer/reader classes stay static too,
|
|
102
|
+
// gated by their ARGUMENTS rather than by the door. Every `Engine` verb takes a
|
|
103
|
+
// `Quill` first (`#backendOf` is the single reader) and the writer/reader
|
|
104
|
+
// constructors take both handles, so a caller who has not awaited cannot
|
|
105
|
+
// produce an argument to call them with. The two constructors taking no handle
|
|
106
|
+
// reach no wasm: `new Engine()` validates a descriptor map, and a `LiveSession`
|
|
107
|
+
// forwards to the backend session `engine.open` is the sole source of. None of
|
|
108
|
+
// the six carries a static method, the one member shape an argument cannot
|
|
109
|
+
// gate. `gate.test.js` is the executable guard, driving the whole static
|
|
110
|
+
// surface before `init`. Holding them out of the gate keeps them tree-shakable,
|
|
111
|
+
// so the editor path drops the dispatcher it never calls.
|
|
112
|
+
//
|
|
113
|
+
// FAILURE DELIVERY follows the FUNCTION kind, not the failure kind: a sync verb
|
|
114
|
+
// throws, a promise-returning verb rejects, and nothing does both. A
|
|
115
|
+
// programming error reached through a promise-returning verb
|
|
116
|
+
// (`runtime::foreign_handle` inside `Engine.render`) rejects like any other.
|
|
117
|
+
// `init` is the one promise-returning export not declared `async`, because the
|
|
118
|
+
// memo is returned by identity; its conflict guard rejects explicitly to hold
|
|
119
|
+
// the rule, which the return type cannot declare and a `.catch` would not see.
|
|
100
120
|
|
|
101
|
-
/**
|
|
102
|
-
*
|
|
121
|
+
/**
|
|
122
|
+
* The gated surface: the core build's values, which are exactly the ones its
|
|
123
|
+
* instance stands behind. Frozen and built at module scope, because the classes
|
|
124
|
+
* and functions themselves resolve synchronously (only the instance behind them
|
|
125
|
+
* is late), so there is nothing to defer and no per-call allocation.
|
|
126
|
+
*
|
|
127
|
+
* Membership is derived, not chosen: it is the core build's exports minus its
|
|
128
|
+
* instantiation machinery (`default`, `initSync`, and the start-section
|
|
129
|
+
* `start`), which `init` owns and no consumer calls. `init.test.js` § "the
|
|
130
|
+
* gated surface" computes that set and pins it, so a new core export that never
|
|
131
|
+
* reaches here fails there rather than going missing.
|
|
132
|
+
* @type {import('./runtime.js').CoreSurface}
|
|
133
|
+
*/
|
|
134
|
+
const CORE_SURFACE = Object.freeze({
|
|
135
|
+
Quill,
|
|
136
|
+
Document,
|
|
137
|
+
importMarkdown,
|
|
138
|
+
exportMarkdown,
|
|
139
|
+
rebase,
|
|
140
|
+
mapPos,
|
|
141
|
+
parseDocPath,
|
|
142
|
+
formatDocPath
|
|
143
|
+
});
|
|
144
|
+
|
|
145
|
+
/** The in-flight or settled core instantiation, resolving to `CORE_SURFACE`.
|
|
146
|
+
* The memo is the PROMISE, not a boolean, so concurrent callers share one
|
|
147
|
+
* instantiation instead of racing.
|
|
148
|
+
* @type {Promise<import('./runtime.js').CoreSurface> | undefined} */
|
|
103
149
|
let coreInit;
|
|
104
150
|
/** The source `init` was first called with; the conflict check reads it. */
|
|
105
151
|
let coreInitSource;
|
|
106
152
|
|
|
107
153
|
/**
|
|
108
|
-
* Instantiate the core WASM build
|
|
109
|
-
* export is used; extra calls are free.
|
|
154
|
+
* Instantiate the core WASM build and resolve to its surface.
|
|
110
155
|
*
|
|
111
156
|
* ```js
|
|
112
|
-
* import { init
|
|
113
|
-
* await init();
|
|
157
|
+
* import { init } from '@quillmark/wasm';
|
|
158
|
+
* const { Quill, Document } = await init();
|
|
114
159
|
* ```
|
|
115
160
|
*
|
|
161
|
+
* The classes and the free functions come from here and nowhere else, so the
|
|
162
|
+
* pre-init mistake is not expressible. Destructure at each entry point (route
|
|
163
|
+
* loader, hydration path, worker) rather than threading one result around: the
|
|
164
|
+
* gate is memoized, so every await after the first is free.
|
|
165
|
+
*
|
|
116
166
|
* Identical in every environment: in a browser the binary is fetched and
|
|
117
167
|
* streamed, under Node it is read off disk, and the call site is the same line.
|
|
118
168
|
*
|
|
119
|
-
* Idempotent and concurrency-safe: every call returns the same
|
|
120
|
-
*
|
|
121
|
-
*
|
|
169
|
+
* Idempotent and concurrency-safe: every non-conflicting call returns the same
|
|
170
|
+
* promise, so several entry points cost one instantiation. A failed init clears
|
|
171
|
+
* the memo, so a retry is possible.
|
|
172
|
+
*
|
|
173
|
+
* Both failures reject (§ "Initialization", FAILURE DELIVERY): one `catch`
|
|
174
|
+
* around `await init(...)` covers `runtime::init_conflict` and
|
|
175
|
+
* `runtime::init_failed` alike.
|
|
122
176
|
*
|
|
123
177
|
* @param {import('../core/wasm.js').InitInput} [source] override the binary's
|
|
124
178
|
* source (bytes, a `Response`, a `WebAssembly.Module`, a URL) for hosts that
|
|
125
179
|
* route assets themselves or embed the binary. Pass it on the FIRST call; a
|
|
126
|
-
* later call passing a *different* source
|
|
127
|
-
* rather than silently ignoring it. Passing the same
|
|
128
|
-
* several entry points may each `await init(BYTES)`
|
|
129
|
-
*
|
|
180
|
+
* later call passing a *different* source rejects with
|
|
181
|
+
* `runtime::init_conflict` rather than silently ignoring it. Passing the same
|
|
182
|
+
* value again is fine, so several entry points may each `await init(BYTES)`
|
|
183
|
+
* against one constant.
|
|
184
|
+
* @returns {Promise<import('./runtime.js').CoreSurface>} the core surface, once
|
|
185
|
+
* its instance is live
|
|
130
186
|
*/
|
|
131
187
|
export function init(source) {
|
|
132
188
|
if (coreInit) {
|
|
133
189
|
if (source !== undefined && source !== coreInitSource) {
|
|
134
|
-
throw
|
|
135
|
-
|
|
136
|
-
|
|
137
|
-
|
|
190
|
+
// A rejection, not a throw: this is the one failure on the
|
|
191
|
+
// promise-returning surface that could land on the caller's stack, where
|
|
192
|
+
// `init(BYTES).catch(…)` would not see it.
|
|
193
|
+
return Promise.reject(
|
|
194
|
+
quillmarkError(
|
|
195
|
+
'runtime::init_conflict',
|
|
196
|
+
'init(source): core is already initializing or initialized from a different source.',
|
|
197
|
+
'Pass a source on the first call only, or pass the same value every time.'
|
|
198
|
+
)
|
|
138
199
|
);
|
|
139
200
|
}
|
|
140
201
|
return coreInit;
|
|
141
202
|
}
|
|
142
203
|
coreInitSource = source;
|
|
143
204
|
// Assign before the first await so a synchronous second call sees the memo.
|
|
144
|
-
coreInit = instantiateCore(source).
|
|
145
|
-
|
|
146
|
-
|
|
147
|
-
|
|
148
|
-
|
|
149
|
-
|
|
150
|
-
|
|
205
|
+
coreInit = instantiateCore(source).then(
|
|
206
|
+
() => CORE_SURFACE,
|
|
207
|
+
(err) => {
|
|
208
|
+
// Self-heal, as `#resolveBackend` does: one transient failure (a 404, an
|
|
209
|
+
// offline fetch) must not poison every later attempt.
|
|
210
|
+
coreInit = undefined;
|
|
211
|
+
coreInitSource = undefined;
|
|
212
|
+
throw err;
|
|
213
|
+
}
|
|
214
|
+
);
|
|
151
215
|
return coreInit;
|
|
152
216
|
}
|
|
153
217
|
|
|
@@ -452,9 +516,10 @@ export function isListItemContainer(container) {
|
|
|
452
516
|
// A predicate rather than an exported name list, because the known tables below
|
|
453
517
|
// are upstream's business. They are pinned against the Rust source
|
|
454
518
|
// (`Content::RESERVED_*` and `KnownIslandType`) by the
|
|
455
|
-
// `
|
|
456
|
-
// `crates/
|
|
457
|
-
// the TS unions in `crates/bindings/wasm/src/engine.rs`
|
|
519
|
+
// `js_known_name_tables_match_the_rust_open_sets` drift-guard test in
|
|
520
|
+
// `crates/bindings/wasm/tests/known_names_drift.rs`: adding a built-in means
|
|
521
|
+
// editing there, here, and the TS unions in `crates/bindings/wasm/src/engine.rs`
|
|
522
|
+
// in one commit.
|
|
458
523
|
//
|
|
459
524
|
// These classify unknown *tags*, not unknown *payloads on known tags*. A future
|
|
460
525
|
// `kind: "footnote"` with a sibling `ref` loses `ref` at a consumer that predates
|
package/runtime/uninit.js
DELETED
|
@@ -1,57 +0,0 @@
|
|
|
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';
|