@takazudo/zfb-md-wasm 2.21.1 → 2.22.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/README.md CHANGED
@@ -665,6 +665,33 @@ The root import remains the compatibility choice for code that also calls
665
665
  `compile`, and the existing highlight import remains compatible. This is also
666
666
  how the package's vitest suite and parse benchmark exercise the built wasm.
667
667
 
668
+ ## Cloudflare Workers (workerd)
669
+
670
+ Only `./parse` and `./highlight` select `workerd` entries; the root and
671
+ `./render` do not. In a Worker, use the same public subpath as in Node:
672
+
673
+ ```ts
674
+ import { parseToAst } from "@takazudo/zfb-md-wasm/parse";
675
+
676
+ export default {
677
+ async fetch(request: Request): Promise<Response> {
678
+ const source = new URL(request.url).searchParams.get("source") ?? "# Hello";
679
+ return Response.json(await parseToAst(source, { filename: "post.md" }));
680
+ },
681
+ };
682
+ ```
683
+
684
+ Wrangler's default module rules handle the static `.wasm` imports without
685
+ extra configuration. For a bundled Worker in Miniflare, use
686
+ `modulesRules: [{ type: "CompiledWasm", include: ["**/*.wasm"] }]`.
687
+ `@cloudflare/vite-plugin` has not been tested.
688
+
689
+ After a trap, each recovery creates a fresh glue closure against the same
690
+ compiled module. The next call uses the replacement instance. A further trap
691
+ after 16 successful recoveries makes that entry terminal and throws
692
+ `ZfbMdWasmTrapRecoveryLimitError` on that and later calls; recycle the Worker
693
+ isolate. Parse and highlight keep independent recovery state.
694
+
668
695
  ## Parity guarantee & limitations
669
696
 
670
697
  Output matches zfb's native pipeline on a fixed fixture corpus (the parity
@@ -0,0 +1,11 @@
1
+ export declare const init: () => Promise<void>, highlightCode: (code: string, options: import("./types.js").HighlightCodeOptions) => Promise<import("./types.js").HighlightCodeResult>, version: () => Promise<string>, __forceTrapForTests: () => Promise<void>, __getTrapRecoveryStateForTests: () => {
2
+ compiledModuleLoads: number;
3
+ currentGeneration: number;
4
+ freshInstanceStarts: number;
5
+ glueImportAttempts: number;
6
+ maxTrapRecoveries: number;
7
+ trapRecoveriesStarted: number;
8
+ terminal: boolean;
9
+ };
10
+ export { ZfbMdWasmTrapError, ZfbMdWasmTrapRecoveryLimitError } from "./runtime-static.js";
11
+ export type { HighlightRole, HighlightCodeOptions, HighlightCodeResult, HighlightDiagnostic, HighlightDiagnosticSource, } from "./types.js";
@@ -0,0 +1,7 @@
1
+ import wasmModule from "./wasm-highlight/zfb_md_wasm_highlight_bg.wasm";
2
+ import { createGlue } from "./wasm-highlight/zfb_md_wasm_highlight_glue.zfb-factory.mjs";
3
+ import { createStaticWasmApi } from "./runtime-static.js";
4
+ const api = createStaticWasmApi({ module: wasmModule, createGlue });
5
+ export const { init, highlightCode, version, __forceTrapForTests, __getTrapRecoveryStateForTests } = api;
6
+ export { ZfbMdWasmTrapError, ZfbMdWasmTrapRecoveryLimitError } from "./runtime-static.js";
7
+ //# sourceMappingURL=highlight-workerd.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"highlight-workerd.js","sourceRoot":"","sources":["../src/highlight-workerd.ts"],"names":[],"mappings":"AAAA,OAAO,UAAU,MAAM,gDAAgD,CAAC;AACxE,OAAO,EAAE,UAAU,EAAE,MAAM,6DAA6D,CAAC;AACzF,OAAO,EAAE,mBAAmB,EAAE,MAAM,qBAAqB,CAAC;AAE1D,MAAM,GAAG,GAAG,mBAAmB,CAAC,EAAE,MAAM,EAAE,UAAgC,EAAE,UAAU,EAAE,CAAC,CAAC;AAE1F,MAAM,CAAC,MAAM,EAAE,IAAI,EAAE,aAAa,EAAE,OAAO,EAAE,mBAAmB,EAAE,8BAA8B,EAAE,GAChG,GAAG,CAAC;AAEN,OAAO,EAAE,kBAAkB,EAAE,+BAA+B,EAAE,MAAM,qBAAqB,CAAC","sourcesContent":["import wasmModule from \"./wasm-highlight/zfb_md_wasm_highlight_bg.wasm\";\nimport { createGlue } from \"./wasm-highlight/zfb_md_wasm_highlight_glue.zfb-factory.mjs\";\nimport { createStaticWasmApi } from \"./runtime-static.js\";\n\nconst api = createStaticWasmApi({ module: wasmModule as WebAssembly.Module, createGlue });\n\nexport const { init, highlightCode, version, __forceTrapForTests, __getTrapRecoveryStateForTests } =\n api;\n\nexport { ZfbMdWasmTrapError, ZfbMdWasmTrapRecoveryLimitError } from \"./runtime-static.js\";\n\nexport type {\n HighlightRole,\n HighlightCodeOptions,\n HighlightCodeResult,\n HighlightDiagnostic,\n HighlightDiagnosticSource,\n} from \"./types.js\";\n"]}
@@ -0,0 +1,12 @@
1
+ export declare const init: () => Promise<void>, parseToAst: (source: string, options?: import("./types.js").ParseToAstOptions) => Promise<import("./types.js").ParseToAstResult>, version: () => Promise<string>, __forceTrapForTests: () => Promise<void>, __getTrapRecoveryStateForTests: () => {
2
+ compiledModuleLoads: number;
3
+ currentGeneration: number;
4
+ freshInstanceStarts: number;
5
+ glueImportAttempts: number;
6
+ maxTrapRecoveries: number;
7
+ trapRecoveriesStarted: number;
8
+ terminal: boolean;
9
+ };
10
+ export { MdastAdapterError, toMdastRoot } from "./mdast.js";
11
+ export { ZfbMdWasmTrapError, ZfbMdWasmTrapRecoveryLimitError } from "./runtime-static.js";
12
+ export type { ParseToAstResult, ParseToAstOptions, ParseDialect, FrontmatterPolicy, ParsePipelineOptions, Diagnostic, DiagnosticSource, AstPoint, AstPosition, RawMdastData, MarkdownRsStop, MdastNode, MdastRoot, UnknownMdastNode, Root, Paragraph, Heading, ThematicBreak, Blockquote, List, ListItem, Html, Code, Definition, Text, DirectiveNodeBase, ContainerDirective, LeafDirective, TextDirective, Emphasis, Strong, InlineCode, Break, Link, Image, ReferenceKind, LinkReference, ImageReference, FootnoteDefinition, FootnoteReference, TableAlign, Table, TableRow, TableCell, Delete, Yaml, MdxFlowExpression, MdxTextExpression, MdxJsxFlowElement, MdxJsxTextElement, MdxJsxAttributeContent, MdxJsxAttribute, MdxJsxAttributeValueExpression, MdxJsxExpressionAttribute, } from "./types.js";
@@ -0,0 +1,8 @@
1
+ import wasmModule from "./wasm-parse/zfb_md_wasm_parse_bg.wasm";
2
+ import { createGlue } from "./wasm-parse/zfb_md_wasm_parse_glue.zfb-factory.mjs";
3
+ import { createStaticWasmApi } from "./runtime-static.js";
4
+ const api = createStaticWasmApi({ module: wasmModule, createGlue });
5
+ export const { init, parseToAst, version, __forceTrapForTests, __getTrapRecoveryStateForTests } = api;
6
+ export { MdastAdapterError, toMdastRoot } from "./mdast.js";
7
+ export { ZfbMdWasmTrapError, ZfbMdWasmTrapRecoveryLimitError } from "./runtime-static.js";
8
+ //# sourceMappingURL=parse-workerd.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"parse-workerd.js","sourceRoot":"","sources":["../src/parse-workerd.ts"],"names":[],"mappings":"AAAA,OAAO,UAAU,MAAM,wCAAwC,CAAC;AAChE,OAAO,EAAE,UAAU,EAAE,MAAM,qDAAqD,CAAC;AACjF,OAAO,EAAE,mBAAmB,EAAE,MAAM,qBAAqB,CAAC;AAE1D,MAAM,GAAG,GAAG,mBAAmB,CAAC,EAAE,MAAM,EAAE,UAAgC,EAAE,UAAU,EAAE,CAAC,CAAC;AAE1F,MAAM,CAAC,MAAM,EAAE,IAAI,EAAE,UAAU,EAAE,OAAO,EAAE,mBAAmB,EAAE,8BAA8B,EAAE,GAC7F,GAAG,CAAC;AAEN,OAAO,EAAE,iBAAiB,EAAE,WAAW,EAAE,MAAM,YAAY,CAAC;AAC5D,OAAO,EAAE,kBAAkB,EAAE,+BAA+B,EAAE,MAAM,qBAAqB,CAAC","sourcesContent":["import wasmModule from \"./wasm-parse/zfb_md_wasm_parse_bg.wasm\";\nimport { createGlue } from \"./wasm-parse/zfb_md_wasm_parse_glue.zfb-factory.mjs\";\nimport { createStaticWasmApi } from \"./runtime-static.js\";\n\nconst api = createStaticWasmApi({ module: wasmModule as WebAssembly.Module, createGlue });\n\nexport const { init, parseToAst, version, __forceTrapForTests, __getTrapRecoveryStateForTests } =\n api;\n\nexport { MdastAdapterError, toMdastRoot } from \"./mdast.js\";\nexport { ZfbMdWasmTrapError, ZfbMdWasmTrapRecoveryLimitError } from \"./runtime-static.js\";\nexport type {\n ParseToAstResult,\n ParseToAstOptions,\n ParseDialect,\n FrontmatterPolicy,\n ParsePipelineOptions,\n Diagnostic,\n DiagnosticSource,\n AstPoint,\n AstPosition,\n RawMdastData,\n MarkdownRsStop,\n MdastNode,\n MdastRoot,\n UnknownMdastNode,\n Root,\n Paragraph,\n Heading,\n ThematicBreak,\n Blockquote,\n List,\n ListItem,\n Html,\n Code,\n Definition,\n Text,\n DirectiveNodeBase,\n ContainerDirective,\n LeafDirective,\n TextDirective,\n Emphasis,\n Strong,\n InlineCode,\n Break,\n Link,\n Image,\n ReferenceKind,\n LinkReference,\n ImageReference,\n FootnoteDefinition,\n FootnoteReference,\n TableAlign,\n Table,\n TableRow,\n TableCell,\n Delete,\n Yaml,\n MdxFlowExpression,\n MdxTextExpression,\n MdxJsxFlowElement,\n MdxJsxTextElement,\n MdxJsxAttributeContent,\n MdxJsxAttribute,\n MdxJsxAttributeValueExpression,\n MdxJsxExpressionAttribute,\n} from \"./types.js\";\n"]}
@@ -0,0 +1,41 @@
1
+ import type { CompileResult, HighlightCodeOptions, HighlightCodeResult, ParseToAstOptions, ParseToAstResult, RenderHtmlResult, ZfbMdWasmOptions } from "./types.js";
2
+ export interface WasmGlueModule {
3
+ initSync(input?: {
4
+ module: WebAssembly.Module;
5
+ }): unknown;
6
+ compile?(source: string, optionsJson: string): string;
7
+ renderHtml?(source: string, optionsJson: string): string;
8
+ parseToAst?(source: string, optionsJson: string): string;
9
+ highlightCode?(code: string, optionsJson: string): string;
10
+ version(): string;
11
+ __forceTrapForTests(): void;
12
+ }
13
+ export declare class ZfbMdWasmTrapError extends Error {
14
+ constructor(cause: unknown);
15
+ }
16
+ export declare class ZfbMdWasmTrapRecoveryLimitError extends Error {
17
+ constructor(maxRecoveries: number, cause: unknown);
18
+ }
19
+ export interface WasmInstanceStrategy {
20
+ getModule(onLoad: () => void): Promise<WebAssembly.Module>;
21
+ createGlue(generation: number, attempt: number): WasmGlueModule | Promise<WasmGlueModule>;
22
+ }
23
+ /** Shared instance lifecycle and bounded trap recovery for URL and static entries. */
24
+ export declare function createWasmApiFromStrategy(strategy: WasmInstanceStrategy): {
25
+ init: () => Promise<void>;
26
+ compile: (source: string, options?: ZfbMdWasmOptions) => Promise<CompileResult>;
27
+ renderHtml: (source: string, options?: ZfbMdWasmOptions) => Promise<RenderHtmlResult>;
28
+ parseToAst: (source: string, options?: ParseToAstOptions) => Promise<ParseToAstResult>;
29
+ highlightCode: (code: string, options: HighlightCodeOptions) => Promise<HighlightCodeResult>;
30
+ version: () => Promise<string>;
31
+ __forceTrapForTests: () => Promise<void>;
32
+ __getTrapRecoveryStateForTests: () => {
33
+ compiledModuleLoads: number;
34
+ currentGeneration: number;
35
+ freshInstanceStarts: number;
36
+ glueImportAttempts: number;
37
+ maxTrapRecoveries: number;
38
+ trapRecoveriesStarted: number;
39
+ terminal: boolean;
40
+ };
41
+ };
@@ -0,0 +1,174 @@
1
+ export class ZfbMdWasmTrapError extends Error {
2
+ constructor(cause) {
3
+ super("zfb-md-wasm: the wasm instance trapped (a Rust panic or internal fault) and has been " +
4
+ "automatically re-instantiated. This is always a bug in zfb-md-wasm -- please report it " +
5
+ "with the input that triggered it.");
6
+ this.name = "ZfbMdWasmTrapError";
7
+ this.cause = cause;
8
+ }
9
+ }
10
+ export class ZfbMdWasmTrapRecoveryLimitError extends Error {
11
+ constructor(maxRecoveries, cause) {
12
+ super(`zfb-md-wasm: wasm trap recovery limit reached after ${maxRecoveries} ` +
13
+ `successful re-instantiations. Further automatic recovery is disabled to avoid ` +
14
+ `unbounded recovery resource growth. Reload the JS realm before using zfb-md-wasm ` +
15
+ `again, and please report the input that triggered the repeated traps.`);
16
+ this.name = "ZfbMdWasmTrapRecoveryLimitError";
17
+ this.cause = cause;
18
+ }
19
+ }
20
+ /** Shared instance lifecycle and bounded trap recovery for URL and static entries. */
21
+ export function createWasmApiFromStrategy(strategy) {
22
+ const MAX_TRAP_RECOVERIES = 16;
23
+ let currentGeneration = 0;
24
+ let trapRecoveriesStarted = 0;
25
+ let freshInstanceStarts = 0;
26
+ // For the static strategy, glueImportAttempts counts createGlue calls.
27
+ let glueImportAttempts = 0;
28
+ let compiledModuleLoads = 0;
29
+ let terminalTrapRecoveryError;
30
+ /**
31
+ * Every fresh instance attempt creates new wasm-bindgen glue. The attempt
32
+ * counter is independent of the trap generation: transient glue/initSync
33
+ * failures must not consume the bounded trap-recovery budget.
34
+ */
35
+ async function freshInstance(generation) {
36
+ freshInstanceStarts += 1;
37
+ glueImportAttempts += 1;
38
+ const [module, glue] = await Promise.all([
39
+ strategy.getModule(() => {
40
+ compiledModuleLoads += 1;
41
+ }),
42
+ // A synchronous factory throw is a retryable instance rejection.
43
+ Promise.resolve().then(() => strategy.createGlue(generation, glueImportAttempts)),
44
+ ]);
45
+ glue.initSync({ module });
46
+ return { generation, glue };
47
+ }
48
+ let instancePromise;
49
+ function startInstance(generation) {
50
+ const attempt = freshInstance(generation);
51
+ instancePromise = attempt;
52
+ void attempt.catch(() => {
53
+ // Trap recovery can replace the installed instance promise. Never let a
54
+ // stale initialization rejection clear that newer attempt.
55
+ if (instancePromise === attempt) {
56
+ instancePromise = undefined;
57
+ }
58
+ });
59
+ return attempt;
60
+ }
61
+ function getInstance() {
62
+ if (terminalTrapRecoveryError) {
63
+ return Promise.reject(terminalTrapRecoveryError);
64
+ }
65
+ if (!instancePromise) {
66
+ return startInstance(currentGeneration);
67
+ }
68
+ return instancePromise;
69
+ }
70
+ function isTrap(err) {
71
+ return typeof WebAssembly !== "undefined" && err instanceof WebAssembly.RuntimeError;
72
+ }
73
+ async function recoverAfterTrap(observedGeneration, cause) {
74
+ if (terminalTrapRecoveryError) {
75
+ throw terminalTrapRecoveryError;
76
+ }
77
+ // CAS-style single-flight: reporters for an already-replaced generation
78
+ // await its replacement instead of creating another glue instance.
79
+ if (observedGeneration !== currentGeneration) {
80
+ await getInstance();
81
+ return;
82
+ }
83
+ if (trapRecoveriesStarted >= MAX_TRAP_RECOVERIES) {
84
+ terminalTrapRecoveryError = new ZfbMdWasmTrapRecoveryLimitError(MAX_TRAP_RECOVERIES, cause);
85
+ instancePromise = undefined;
86
+ throw terminalTrapRecoveryError;
87
+ }
88
+ trapRecoveriesStarted += 1;
89
+ currentGeneration += 1;
90
+ await startInstance(currentGeneration);
91
+ }
92
+ async function callWasm(fn) {
93
+ const instance = await getInstance();
94
+ try {
95
+ return fn(instance);
96
+ }
97
+ catch (err) {
98
+ if (!isTrap(err)) {
99
+ throw err;
100
+ }
101
+ await recoverAfterTrap(instance.generation, err);
102
+ throw new ZfbMdWasmTrapError(err);
103
+ }
104
+ }
105
+ async function init() {
106
+ await getInstance();
107
+ }
108
+ // Public entry surfaces make missing calls unreachable. This guard keeps
109
+ // structural glue mismatches artifact-neutral and actionable.
110
+ function requireCapability(fn, name) {
111
+ if (!fn) {
112
+ throw new Error(`zfb-md-wasm: ${name}() is not available in this wasm artifact. ` +
113
+ `Import an entry whose artifact provides that capability.`);
114
+ }
115
+ return fn;
116
+ }
117
+ async function compile(source, options = {}) {
118
+ const optionsJson = JSON.stringify(options);
119
+ const json = await callWasm(({ glue }) => requireCapability(glue.compile, "compile").call(glue, source, optionsJson));
120
+ return JSON.parse(json);
121
+ }
122
+ async function renderHtml(source, options = {}) {
123
+ const optionsJson = JSON.stringify(options);
124
+ const json = await callWasm(({ glue }) => requireCapability(glue.renderHtml, "renderHtml").call(glue, source, optionsJson));
125
+ return JSON.parse(json);
126
+ }
127
+ /**
128
+ * Parse markdown/MDX into a raw mdast tree (zfb#1857, epic zfb#1854).
129
+ * The `parseToAst + JSON.parse` round trip here IS the product cost the
130
+ * epic's benchmark measures against remark-parse. See `types.ts`'s
131
+ * `ParseToAstResult`/`MdastNode` docs for the result shape and the
132
+ * UTF-16 position contract.
133
+ */
134
+ async function parseToAst(source, options = {}) {
135
+ const optionsJson = JSON.stringify(options);
136
+ const json = await callWasm(({ glue }) => requireCapability(glue.parseToAst, "parseToAst").call(glue, source, optionsJson));
137
+ return JSON.parse(json);
138
+ }
139
+ async function highlightCode(code, options) {
140
+ const optionsJson = JSON.stringify(options);
141
+ const json = await callWasm(({ glue }) => requireCapability(glue.highlightCode, "highlightCode").call(glue, code, optionsJson));
142
+ return JSON.parse(json);
143
+ }
144
+ async function version() {
145
+ return callWasm(({ glue }) => glue.version());
146
+ }
147
+ /** @internal Test-only hook that forces the current instance to trap. */
148
+ async function __forceTrapForTests() {
149
+ await callWasm(({ glue }) => glue.__forceTrapForTests());
150
+ }
151
+ /** @internal Test-only observability for the bounded recovery contract. */
152
+ function __getTrapRecoveryStateForTests() {
153
+ return {
154
+ compiledModuleLoads,
155
+ currentGeneration,
156
+ freshInstanceStarts,
157
+ glueImportAttempts,
158
+ maxTrapRecoveries: MAX_TRAP_RECOVERIES,
159
+ trapRecoveriesStarted,
160
+ terminal: !!terminalTrapRecoveryError,
161
+ };
162
+ }
163
+ return {
164
+ init,
165
+ compile,
166
+ renderHtml,
167
+ parseToAst,
168
+ highlightCode,
169
+ version,
170
+ __forceTrapForTests,
171
+ __getTrapRecoveryStateForTests,
172
+ };
173
+ }
174
+ //# sourceMappingURL=runtime-core.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"runtime-core.js","sourceRoot":"","sources":["../src/runtime-core.ts"],"names":[],"mappings":"AA8BA,MAAM,OAAO,kBAAmB,SAAQ,KAAK;IAC3C,YAAY,KAAc;QACxB,KAAK,CACH,uFAAuF;YACrF,yFAAyF;YACzF,mCAAmC,CACtC,CAAC;QACF,IAAI,CAAC,IAAI,GAAG,oBAAoB,CAAC;QACjC,IAAI,CAAC,KAAK,GAAG,KAAK,CAAC;IACrB,CAAC;CACF;AAED,MAAM,OAAO,+BAAgC,SAAQ,KAAK;IACxD,YAAY,aAAqB,EAAE,KAAc;QAC/C,KAAK,CACH,uDAAuD,aAAa,GAAG;YACrE,gFAAgF;YAChF,mFAAmF;YACnF,uEAAuE,CAC1E,CAAC;QACF,IAAI,CAAC,IAAI,GAAG,iCAAiC,CAAC;QAC9C,IAAI,CAAC,KAAK,GAAG,KAAK,CAAC;IACrB,CAAC;CACF;AAOD,sFAAsF;AACtF,MAAM,UAAU,yBAAyB,CAAC,QAA8B;IACtE,MAAM,mBAAmB,GAAG,EAAE,CAAC;IAE/B,IAAI,iBAAiB,GAAG,CAAC,CAAC;IAC1B,IAAI,qBAAqB,GAAG,CAAC,CAAC;IAC9B,IAAI,mBAAmB,GAAG,CAAC,CAAC;IAC5B,uEAAuE;IACvE,IAAI,kBAAkB,GAAG,CAAC,CAAC;IAC3B,IAAI,mBAAmB,GAAG,CAAC,CAAC;IAC5B,IAAI,yBAAsE,CAAC;IAO3E;;;;OAIG;IACH,KAAK,UAAU,aAAa,CAAC,UAAkB;QAC7C,mBAAmB,IAAI,CAAC,CAAC;QACzB,kBAAkB,IAAI,CAAC,CAAC;QACxB,MAAM,CAAC,MAAM,EAAE,IAAI,CAAC,GAAG,MAAM,OAAO,CAAC,GAAG,CAAC;YACvC,QAAQ,CAAC,SAAS,CAAC,GAAG,EAAE;gBACtB,mBAAmB,IAAI,CAAC,CAAC;YAC3B,CAAC,CAAC;YACF,iEAAiE;YACjE,OAAO,CAAC,OAAO,EAAE,CAAC,IAAI,CAAC,GAAG,EAAE,CAAC,QAAQ,CAAC,UAAU,CAAC,UAAU,EAAE,kBAAkB,CAAC,CAAC;SAClF,CAAC,CAAC;QACH,IAAI,CAAC,QAAQ,CAAC,EAAE,MAAM,EAAE,CAAC,CAAC;QAC1B,OAAO,EAAE,UAAU,EAAE,IAAI,EAAE,CAAC;IAC9B,CAAC;IAED,IAAI,eAA8C,CAAC;IAEnD,SAAS,aAAa,CAAC,UAAkB;QACvC,MAAM,OAAO,GAAG,aAAa,CAAC,UAAU,CAAC,CAAC;QAC1C,eAAe,GAAG,OAAO,CAAC;QAC1B,KAAK,OAAO,CAAC,KAAK,CAAC,GAAG,EAAE;YACtB,wEAAwE;YACxE,2DAA2D;YAC3D,IAAI,eAAe,KAAK,OAAO,EAAE,CAAC;gBAChC,eAAe,GAAG,SAAS,CAAC;YAC9B,CAAC;QACH,CAAC,CAAC,CAAC;QACH,OAAO,OAAO,CAAC;IACjB,CAAC;IAED,SAAS,WAAW;QAClB,IAAI,yBAAyB,EAAE,CAAC;YAC9B,OAAO,OAAO,CAAC,MAAM,CAAC,yBAAyB,CAAC,CAAC;QACnD,CAAC;QACD,IAAI,CAAC,eAAe,EAAE,CAAC;YACrB,OAAO,aAAa,CAAC,iBAAiB,CAAC,CAAC;QAC1C,CAAC;QACD,OAAO,eAAe,CAAC;IACzB,CAAC;IAED,SAAS,MAAM,CAAC,GAAY;QAC1B,OAAO,OAAO,WAAW,KAAK,WAAW,IAAI,GAAG,YAAY,WAAW,CAAC,YAAY,CAAC;IACvF,CAAC;IAED,KAAK,UAAU,gBAAgB,CAAC,kBAA0B,EAAE,KAAc;QACxE,IAAI,yBAAyB,EAAE,CAAC;YAC9B,MAAM,yBAAyB,CAAC;QAClC,CAAC;QAED,wEAAwE;QACxE,mEAAmE;QACnE,IAAI,kBAAkB,KAAK,iBAAiB,EAAE,CAAC;YAC7C,MAAM,WAAW,EAAE,CAAC;YACpB,OAAO;QACT,CAAC;QAED,IAAI,qBAAqB,IAAI,mBAAmB,EAAE,CAAC;YACjD,yBAAyB,GAAG,IAAI,+BAA+B,CAAC,mBAAmB,EAAE,KAAK,CAAC,CAAC;YAC5F,eAAe,GAAG,SAAS,CAAC;YAC5B,MAAM,yBAAyB,CAAC;QAClC,CAAC;QAED,qBAAqB,IAAI,CAAC,CAAC;QAC3B,iBAAiB,IAAI,CAAC,CAAC;QACvB,MAAM,aAAa,CAAC,iBAAiB,CAAC,CAAC;IACzC,CAAC;IAED,KAAK,UAAU,QAAQ,CAAI,EAA6B;QACtD,MAAM,QAAQ,GAAG,MAAM,WAAW,EAAE,CAAC;QACrC,IAAI,CAAC;YACH,OAAO,EAAE,CAAC,QAAQ,CAAC,CAAC;QACtB,CAAC;QAAC,OAAO,GAAG,EAAE,CAAC;YACb,IAAI,CAAC,MAAM,CAAC,GAAG,CAAC,EAAE,CAAC;gBACjB,MAAM,GAAG,CAAC;YACZ,CAAC;YACD,MAAM,gBAAgB,CAAC,QAAQ,CAAC,UAAU,EAAE,GAAG,CAAC,CAAC;YACjD,MAAM,IAAI,kBAAkB,CAAC,GAAG,CAAC,CAAC;QACpC,CAAC;IACH,CAAC;IAED,KAAK,UAAU,IAAI;QACjB,MAAM,WAAW,EAAE,CAAC;IACtB,CAAC;IAED,yEAAyE;IACzE,8DAA8D;IAC9D,SAAS,iBAAiB,CAAI,EAAiB,EAAE,IAAY;QAC3D,IAAI,CAAC,EAAE,EAAE,CAAC;YACR,MAAM,IAAI,KAAK,CACb,gBAAgB,IAAI,6CAA6C;gBAC/D,0DAA0D,CAC7D,CAAC;QACJ,CAAC;QACD,OAAO,EAAE,CAAC;IACZ,CAAC;IAED,KAAK,UAAU,OAAO,CAAC,MAAc,EAAE,UAA4B,EAAE;QACnE,MAAM,WAAW,GAAG,IAAI,CAAC,SAAS,CAAC,OAAO,CAAC,CAAC;QAC5C,MAAM,IAAI,GAAG,MAAM,QAAQ,CAAC,CAAC,EAAE,IAAI,EAAE,EAAE,EAAE,CACvC,iBAAiB,CAAC,IAAI,CAAC,OAAO,EAAE,SAAS,CAAC,CAAC,IAAI,CAAC,IAAI,EAAE,MAAM,EAAE,WAAW,CAAC,CAC3E,CAAC;QACF,OAAO,IAAI,CAAC,KAAK,CAAC,IAAI,CAAkB,CAAC;IAC3C,CAAC;IAED,KAAK,UAAU,UAAU,CACvB,MAAc,EACd,UAA4B,EAAE;QAE9B,MAAM,WAAW,GAAG,IAAI,CAAC,SAAS,CAAC,OAAO,CAAC,CAAC;QAC5C,MAAM,IAAI,GAAG,MAAM,QAAQ,CAAC,CAAC,EAAE,IAAI,EAAE,EAAE,EAAE,CACvC,iBAAiB,CAAC,IAAI,CAAC,UAAU,EAAE,YAAY,CAAC,CAAC,IAAI,CAAC,IAAI,EAAE,MAAM,EAAE,WAAW,CAAC,CACjF,CAAC;QACF,OAAO,IAAI,CAAC,KAAK,CAAC,IAAI,CAAqB,CAAC;IAC9C,CAAC;IAED;;;;;;OAMG;IACH,KAAK,UAAU,UAAU,CACvB,MAAc,EACd,UAA6B,EAAE;QAE/B,MAAM,WAAW,GAAG,IAAI,CAAC,SAAS,CAAC,OAAO,CAAC,CAAC;QAC5C,MAAM,IAAI,GAAG,MAAM,QAAQ,CAAC,CAAC,EAAE,IAAI,EAAE,EAAE,EAAE,CACvC,iBAAiB,CAAC,IAAI,CAAC,UAAU,EAAE,YAAY,CAAC,CAAC,IAAI,CAAC,IAAI,EAAE,MAAM,EAAE,WAAW,CAAC,CACjF,CAAC;QACF,OAAO,IAAI,CAAC,KAAK,CAAC,IAAI,CAAqB,CAAC;IAC9C,CAAC;IAED,KAAK,UAAU,aAAa,CAC1B,IAAY,EACZ,OAA6B;QAE7B,MAAM,WAAW,GAAG,IAAI,CAAC,SAAS,CAAC,OAAO,CAAC,CAAC;QAC5C,MAAM,IAAI,GAAG,MAAM,QAAQ,CAAC,CAAC,EAAE,IAAI,EAAE,EAAE,EAAE,CACvC,iBAAiB,CAAC,IAAI,CAAC,aAAa,EAAE,eAAe,CAAC,CAAC,IAAI,CAAC,IAAI,EAAE,IAAI,EAAE,WAAW,CAAC,CACrF,CAAC;QACF,OAAO,IAAI,CAAC,KAAK,CAAC,IAAI,CAAwB,CAAC;IACjD,CAAC;IAED,KAAK,UAAU,OAAO;QACpB,OAAO,QAAQ,CAAC,CAAC,EAAE,IAAI,EAAE,EAAE,EAAE,CAAC,IAAI,CAAC,OAAO,EAAE,CAAC,CAAC;IAChD,CAAC;IAED,yEAAyE;IACzE,KAAK,UAAU,mBAAmB;QAChC,MAAM,QAAQ,CAAC,CAAC,EAAE,IAAI,EAAE,EAAE,EAAE,CAAC,IAAI,CAAC,mBAAmB,EAAE,CAAC,CAAC;IAC3D,CAAC;IAED,2EAA2E;IAC3E,SAAS,8BAA8B;QASrC,OAAO;YACL,mBAAmB;YACnB,iBAAiB;YACjB,mBAAmB;YACnB,kBAAkB;YAClB,iBAAiB,EAAE,mBAAmB;YACtC,qBAAqB;YACrB,QAAQ,EAAE,CAAC,CAAC,yBAAyB;SACtC,CAAC;IACJ,CAAC;IAED,OAAO;QACL,IAAI;QACJ,OAAO;QACP,UAAU;QACV,UAAU;QACV,aAAa;QACb,OAAO;QACP,mBAAmB;QACnB,8BAA8B;KAC/B,CAAC;AACJ,CAAC","sourcesContent":["import type {\n CompileResult,\n HighlightCodeOptions,\n HighlightCodeResult,\n ParseToAstOptions,\n ParseToAstResult,\n RenderHtmlResult,\n ZfbMdWasmOptions,\n} from \"./types.js\";\n\nexport interface WasmGlueModule {\n // Keep this deliberately structural instead of importing the generated\n // glue's declaration file. The browser entry imports that file as a *URL\n // resource*, while the direct entry imports it dynamically. Both paths\n // nevertheless use the same wasm-bindgen surface and recovery\n // implementation below.\n //\n // Capability functions are structural and optional because each singleton\n // artifact's wasm-bindgen glue contains only its selected Rust export.\n // Public entries expose only the matching calls from `createWasmApi`.\n initSync(input?: { module: WebAssembly.Module }): unknown;\n compile?(source: string, optionsJson: string): string;\n renderHtml?(source: string, optionsJson: string): string;\n // Raw-mdast export (zfb#1857, epic zfb#1854).\n parseToAst?(source: string, optionsJson: string): string;\n highlightCode?(code: string, optionsJson: string): string;\n version(): string;\n __forceTrapForTests(): void;\n}\n\nexport class ZfbMdWasmTrapError extends Error {\n constructor(cause: unknown) {\n super(\n \"zfb-md-wasm: the wasm instance trapped (a Rust panic or internal fault) and has been \" +\n \"automatically re-instantiated. This is always a bug in zfb-md-wasm -- please report it \" +\n \"with the input that triggered it.\",\n );\n this.name = \"ZfbMdWasmTrapError\";\n this.cause = cause;\n }\n}\n\nexport class ZfbMdWasmTrapRecoveryLimitError extends Error {\n constructor(maxRecoveries: number, cause: unknown) {\n super(\n `zfb-md-wasm: wasm trap recovery limit reached after ${maxRecoveries} ` +\n `successful re-instantiations. Further automatic recovery is disabled to avoid ` +\n `unbounded recovery resource growth. Reload the JS realm before using zfb-md-wasm ` +\n `again, and please report the input that triggered the repeated traps.`,\n );\n this.name = \"ZfbMdWasmTrapRecoveryLimitError\";\n this.cause = cause;\n }\n}\n\nexport interface WasmInstanceStrategy {\n getModule(onLoad: () => void): Promise<WebAssembly.Module>;\n createGlue(generation: number, attempt: number): WasmGlueModule | Promise<WasmGlueModule>;\n}\n\n/** Shared instance lifecycle and bounded trap recovery for URL and static entries. */\nexport function createWasmApiFromStrategy(strategy: WasmInstanceStrategy) {\n const MAX_TRAP_RECOVERIES = 16;\n\n let currentGeneration = 0;\n let trapRecoveriesStarted = 0;\n let freshInstanceStarts = 0;\n // For the static strategy, glueImportAttempts counts createGlue calls.\n let glueImportAttempts = 0;\n let compiledModuleLoads = 0;\n let terminalTrapRecoveryError: ZfbMdWasmTrapRecoveryLimitError | undefined;\n\n interface Instance {\n generation: number;\n glue: WasmGlueModule;\n }\n\n /**\n * Every fresh instance attempt creates new wasm-bindgen glue. The attempt\n * counter is independent of the trap generation: transient glue/initSync\n * failures must not consume the bounded trap-recovery budget.\n */\n async function freshInstance(generation: number): Promise<Instance> {\n freshInstanceStarts += 1;\n glueImportAttempts += 1;\n const [module, glue] = await Promise.all([\n strategy.getModule(() => {\n compiledModuleLoads += 1;\n }),\n // A synchronous factory throw is a retryable instance rejection.\n Promise.resolve().then(() => strategy.createGlue(generation, glueImportAttempts)),\n ]);\n glue.initSync({ module });\n return { generation, glue };\n }\n\n let instancePromise: Promise<Instance> | undefined;\n\n function startInstance(generation: number): Promise<Instance> {\n const attempt = freshInstance(generation);\n instancePromise = attempt;\n void attempt.catch(() => {\n // Trap recovery can replace the installed instance promise. Never let a\n // stale initialization rejection clear that newer attempt.\n if (instancePromise === attempt) {\n instancePromise = undefined;\n }\n });\n return attempt;\n }\n\n function getInstance(): Promise<Instance> {\n if (terminalTrapRecoveryError) {\n return Promise.reject(terminalTrapRecoveryError);\n }\n if (!instancePromise) {\n return startInstance(currentGeneration);\n }\n return instancePromise;\n }\n\n function isTrap(err: unknown): boolean {\n return typeof WebAssembly !== \"undefined\" && err instanceof WebAssembly.RuntimeError;\n }\n\n async function recoverAfterTrap(observedGeneration: number, cause: unknown): Promise<void> {\n if (terminalTrapRecoveryError) {\n throw terminalTrapRecoveryError;\n }\n\n // CAS-style single-flight: reporters for an already-replaced generation\n // await its replacement instead of creating another glue instance.\n if (observedGeneration !== currentGeneration) {\n await getInstance();\n return;\n }\n\n if (trapRecoveriesStarted >= MAX_TRAP_RECOVERIES) {\n terminalTrapRecoveryError = new ZfbMdWasmTrapRecoveryLimitError(MAX_TRAP_RECOVERIES, cause);\n instancePromise = undefined;\n throw terminalTrapRecoveryError;\n }\n\n trapRecoveriesStarted += 1;\n currentGeneration += 1;\n await startInstance(currentGeneration);\n }\n\n async function callWasm<T>(fn: (instance: Instance) => T): Promise<T> {\n const instance = await getInstance();\n try {\n return fn(instance);\n } catch (err) {\n if (!isTrap(err)) {\n throw err;\n }\n await recoverAfterTrap(instance.generation, err);\n throw new ZfbMdWasmTrapError(err);\n }\n }\n\n async function init(): Promise<void> {\n await getInstance();\n }\n\n // Public entry surfaces make missing calls unreachable. This guard keeps\n // structural glue mismatches artifact-neutral and actionable.\n function requireCapability<T>(fn: T | undefined, name: string): T {\n if (!fn) {\n throw new Error(\n `zfb-md-wasm: ${name}() is not available in this wasm artifact. ` +\n `Import an entry whose artifact provides that capability.`,\n );\n }\n return fn;\n }\n\n async function compile(source: string, options: ZfbMdWasmOptions = {}): Promise<CompileResult> {\n const optionsJson = JSON.stringify(options);\n const json = await callWasm(({ glue }) =>\n requireCapability(glue.compile, \"compile\").call(glue, source, optionsJson),\n );\n return JSON.parse(json) as CompileResult;\n }\n\n async function renderHtml(\n source: string,\n options: ZfbMdWasmOptions = {},\n ): Promise<RenderHtmlResult> {\n const optionsJson = JSON.stringify(options);\n const json = await callWasm(({ glue }) =>\n requireCapability(glue.renderHtml, \"renderHtml\").call(glue, source, optionsJson),\n );\n return JSON.parse(json) as RenderHtmlResult;\n }\n\n /**\n * Parse markdown/MDX into a raw mdast tree (zfb#1857, epic zfb#1854).\n * The `parseToAst + JSON.parse` round trip here IS the product cost the\n * epic's benchmark measures against remark-parse. See `types.ts`'s\n * `ParseToAstResult`/`MdastNode` docs for the result shape and the\n * UTF-16 position contract.\n */\n async function parseToAst(\n source: string,\n options: ParseToAstOptions = {},\n ): Promise<ParseToAstResult> {\n const optionsJson = JSON.stringify(options);\n const json = await callWasm(({ glue }) =>\n requireCapability(glue.parseToAst, \"parseToAst\").call(glue, source, optionsJson),\n );\n return JSON.parse(json) as ParseToAstResult;\n }\n\n async function highlightCode(\n code: string,\n options: HighlightCodeOptions,\n ): Promise<HighlightCodeResult> {\n const optionsJson = JSON.stringify(options);\n const json = await callWasm(({ glue }) =>\n requireCapability(glue.highlightCode, \"highlightCode\").call(glue, code, optionsJson),\n );\n return JSON.parse(json) as HighlightCodeResult;\n }\n\n async function version(): Promise<string> {\n return callWasm(({ glue }) => glue.version());\n }\n\n /** @internal Test-only hook that forces the current instance to trap. */\n async function __forceTrapForTests(): Promise<void> {\n await callWasm(({ glue }) => glue.__forceTrapForTests());\n }\n\n /** @internal Test-only observability for the bounded recovery contract. */\n function __getTrapRecoveryStateForTests(): {\n compiledModuleLoads: number;\n currentGeneration: number;\n freshInstanceStarts: number;\n glueImportAttempts: number;\n maxTrapRecoveries: number;\n trapRecoveriesStarted: number;\n terminal: boolean;\n } {\n return {\n compiledModuleLoads,\n currentGeneration,\n freshInstanceStarts,\n glueImportAttempts,\n maxTrapRecoveries: MAX_TRAP_RECOVERIES,\n trapRecoveriesStarted,\n terminal: !!terminalTrapRecoveryError,\n };\n }\n\n return {\n init,\n compile,\n renderHtml,\n parseToAst,\n highlightCode,\n version,\n __forceTrapForTests,\n __getTrapRecoveryStateForTests,\n };\n}\n"]}
@@ -0,0 +1,25 @@
1
+ import type { WasmGlueModule } from "./runtime-core.js";
2
+ export { ZfbMdWasmTrapError, ZfbMdWasmTrapRecoveryLimitError } from "./runtime-core.js";
3
+ export interface StaticWasmResourceConfig {
4
+ module: WebAssembly.Module | (() => WebAssembly.Module | Promise<WebAssembly.Module>);
5
+ createGlue(): WasmGlueModule;
6
+ }
7
+ /** Create fresh glue instances around one statically supplied compiled module. */
8
+ export declare function createStaticWasmApi({ module, createGlue }: StaticWasmResourceConfig): {
9
+ init: () => Promise<void>;
10
+ compile: (source: string, options?: import("./types.js").ZfbMdWasmOptions) => Promise<import("./types.js").CompileResult>;
11
+ renderHtml: (source: string, options?: import("./types.js").ZfbMdWasmOptions) => Promise<import("./types.js").RenderHtmlResult>;
12
+ parseToAst: (source: string, options?: import("./types.js").ParseToAstOptions) => Promise<import("./types.js").ParseToAstResult>;
13
+ highlightCode: (code: string, options: import("./types.js").HighlightCodeOptions) => Promise<import("./types.js").HighlightCodeResult>;
14
+ version: () => Promise<string>;
15
+ __forceTrapForTests: () => Promise<void>;
16
+ __getTrapRecoveryStateForTests: () => {
17
+ compiledModuleLoads: number;
18
+ currentGeneration: number;
19
+ freshInstanceStarts: number;
20
+ glueImportAttempts: number;
21
+ maxTrapRecoveries: number;
22
+ trapRecoveriesStarted: number;
23
+ terminal: boolean;
24
+ };
25
+ };
@@ -0,0 +1,23 @@
1
+ import { createWasmApiFromStrategy } from "./runtime-core.js";
2
+ export { ZfbMdWasmTrapError, ZfbMdWasmTrapRecoveryLimitError } from "./runtime-core.js";
3
+ /** Create fresh glue instances around one statically supplied compiled module. */
4
+ export function createStaticWasmApi({ module, createGlue }) {
5
+ let modulePromise;
6
+ function getModule(onLoad) {
7
+ if (!modulePromise) {
8
+ onLoad();
9
+ const attempt = Promise.resolve().then(() => typeof module === "function" ? module() : module);
10
+ modulePromise = attempt;
11
+ void attempt.catch(() => {
12
+ if (modulePromise === attempt)
13
+ modulePromise = undefined;
14
+ });
15
+ }
16
+ return modulePromise;
17
+ }
18
+ return createWasmApiFromStrategy({
19
+ getModule,
20
+ createGlue,
21
+ });
22
+ }
23
+ //# sourceMappingURL=runtime-static.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"runtime-static.js","sourceRoot":"","sources":["../src/runtime-static.ts"],"names":[],"mappings":"AAAA,OAAO,EAAE,yBAAyB,EAAE,MAAM,mBAAmB,CAAC;AAG9D,OAAO,EAAE,kBAAkB,EAAE,+BAA+B,EAAE,MAAM,mBAAmB,CAAC;AAOxF,kFAAkF;AAClF,MAAM,UAAU,mBAAmB,CAAC,EAAE,MAAM,EAAE,UAAU,EAA4B;IAClF,IAAI,aAAsD,CAAC;IAC3D,SAAS,SAAS,CAAC,MAAkB;QACnC,IAAI,CAAC,aAAa,EAAE,CAAC;YACnB,MAAM,EAAE,CAAC;YACT,MAAM,OAAO,GAAG,OAAO,CAAC,OAAO,EAAE,CAAC,IAAI,CAAC,GAAG,EAAE,CAC1C,OAAO,MAAM,KAAK,UAAU,CAAC,CAAC,CAAC,MAAM,EAAE,CAAC,CAAC,CAAC,MAAM,CACjD,CAAC;YACF,aAAa,GAAG,OAAO,CAAC;YACxB,KAAK,OAAO,CAAC,KAAK,CAAC,GAAG,EAAE;gBACtB,IAAI,aAAa,KAAK,OAAO;oBAAE,aAAa,GAAG,SAAS,CAAC;YAC3D,CAAC,CAAC,CAAC;QACL,CAAC;QACD,OAAO,aAAa,CAAC;IACvB,CAAC;IAED,OAAO,yBAAyB,CAAC;QAC/B,SAAS;QACT,UAAU;KACX,CAAC,CAAC;AACL,CAAC","sourcesContent":["import { createWasmApiFromStrategy } from \"./runtime-core.js\";\nimport type { WasmGlueModule } from \"./runtime-core.js\";\n\nexport { ZfbMdWasmTrapError, ZfbMdWasmTrapRecoveryLimitError } from \"./runtime-core.js\";\n\nexport interface StaticWasmResourceConfig {\n module: WebAssembly.Module | (() => WebAssembly.Module | Promise<WebAssembly.Module>);\n createGlue(): WasmGlueModule;\n}\n\n/** Create fresh glue instances around one statically supplied compiled module. */\nexport function createStaticWasmApi({ module, createGlue }: StaticWasmResourceConfig) {\n let modulePromise: Promise<WebAssembly.Module> | undefined;\n function getModule(onLoad: () => void): Promise<WebAssembly.Module> {\n if (!modulePromise) {\n onLoad();\n const attempt = Promise.resolve().then(() =>\n typeof module === \"function\" ? module() : module,\n );\n modulePromise = attempt;\n void attempt.catch(() => {\n if (modulePromise === attempt) modulePromise = undefined;\n });\n }\n return modulePromise;\n }\n\n return createWasmApiFromStrategy({\n getModule,\n createGlue,\n });\n}\n"]}
package/dist/runtime.d.ts CHANGED
@@ -1,15 +1,5 @@
1
- import type { CompileResult, HighlightCodeOptions, HighlightCodeResult, ParseToAstOptions, ParseToAstResult, RenderHtmlResult, ZfbMdWasmOptions } from "./types.js";
2
- interface WasmGlueModule {
3
- initSync(input?: {
4
- module: WebAssembly.Module;
5
- }): unknown;
6
- compile?(source: string, optionsJson: string): string;
7
- renderHtml?(source: string, optionsJson: string): string;
8
- parseToAst?(source: string, optionsJson: string): string;
9
- highlightCode?(code: string, optionsJson: string): string;
10
- version(): string;
11
- __forceTrapForTests(): void;
12
- }
1
+ import type { WasmGlueModule } from "./runtime-core.js";
2
+ export { ZfbMdWasmTrapError, ZfbMdWasmTrapRecoveryLimitError } from "./runtime-core.js";
13
3
  export interface WasmResourceConfig {
14
4
  glueUrl: URL;
15
5
  loadWasmBytes(): Promise<ArrayBuffer>;
@@ -18,18 +8,13 @@ export interface WasmResourceConfig {
18
8
  /** @internal Deterministic test seam; production dynamically imports the glue URL. */
19
9
  importGlue?(specifier: string): Promise<WasmGlueModule>;
20
10
  }
21
- export declare class ZfbMdWasmTrapError extends Error {
22
- constructor(cause: unknown);
23
- }
24
- export declare class ZfbMdWasmTrapRecoveryLimitError extends Error {
25
- constructor(maxRecoveries: number, cause: unknown);
26
- }
11
+ /** URL loading strategy used by the existing browser and direct entries. */
27
12
  export declare function createWasmApi({ glueUrl, loadWasmBytes, compileWasm, importGlue, }: WasmResourceConfig): {
28
13
  init: () => Promise<void>;
29
- compile: (source: string, options?: ZfbMdWasmOptions) => Promise<CompileResult>;
30
- renderHtml: (source: string, options?: ZfbMdWasmOptions) => Promise<RenderHtmlResult>;
31
- parseToAst: (source: string, options?: ParseToAstOptions) => Promise<ParseToAstResult>;
32
- highlightCode: (code: string, options: HighlightCodeOptions) => Promise<HighlightCodeResult>;
14
+ compile: (source: string, options?: import("./types.js").ZfbMdWasmOptions) => Promise<import("./types.js").CompileResult>;
15
+ renderHtml: (source: string, options?: import("./types.js").ZfbMdWasmOptions) => Promise<import("./types.js").RenderHtmlResult>;
16
+ parseToAst: (source: string, options?: import("./types.js").ParseToAstOptions) => Promise<import("./types.js").ParseToAstResult>;
17
+ highlightCode: (code: string, options: import("./types.js").HighlightCodeOptions) => Promise<import("./types.js").HighlightCodeResult>;
33
18
  version: () => Promise<string>;
34
19
  __forceTrapForTests: () => Promise<void>;
35
20
  __getTrapRecoveryStateForTests: () => {
@@ -42,4 +27,3 @@ export declare function createWasmApi({ glueUrl, loadWasmBytes, compileWasm, imp
42
27
  terminal: boolean;
43
28
  };
44
29
  };
45
- export {};
package/dist/runtime.js CHANGED
@@ -1,191 +1,29 @@
1
- export class ZfbMdWasmTrapError extends Error {
2
- constructor(cause) {
3
- super("zfb-md-wasm: the wasm instance trapped (a Rust panic or internal fault) and has been " +
4
- "automatically re-instantiated. This is always a bug in zfb-md-wasm -- please report it " +
5
- "with the input that triggered it.");
6
- this.name = "ZfbMdWasmTrapError";
7
- this.cause = cause;
8
- }
9
- }
10
- export class ZfbMdWasmTrapRecoveryLimitError extends Error {
11
- constructor(maxRecoveries, cause) {
12
- super(`zfb-md-wasm: wasm trap recovery limit reached after ${maxRecoveries} ` +
13
- `successful re-instantiations. Further automatic recovery is disabled to avoid ` +
14
- `unbounded ES module record growth. Reload the JS realm before using zfb-md-wasm ` +
15
- `again, and please report the input that triggered the repeated traps.`);
16
- this.name = "ZfbMdWasmTrapRecoveryLimitError";
17
- this.cause = cause;
18
- }
19
- }
1
+ import { createWasmApiFromStrategy } from "./runtime-core.js";
2
+ export { ZfbMdWasmTrapError, ZfbMdWasmTrapRecoveryLimitError } from "./runtime-core.js";
3
+ /** URL loading strategy used by the existing browser and direct entries. */
20
4
  export function createWasmApi({ glueUrl, loadWasmBytes, compileWasm = (bytes) => WebAssembly.compile(bytes), importGlue = (specifier) => import(/* @vite-ignore */ specifier), }) {
21
5
  let compiledModulePromise;
22
- let compiledModuleLoads = 0;
23
- function getCompiledModule() {
6
+ function getModule(onLoad) {
24
7
  if (!compiledModulePromise) {
25
- compiledModuleLoads += 1;
8
+ onLoad();
26
9
  const attempt = Promise.resolve().then(loadWasmBytes).then(compileWasm);
27
10
  compiledModulePromise = attempt;
28
11
  void attempt.catch(() => {
29
- // A rejected attempt must be retryable, but it must not erase a newer
30
- // attempt if its rejection cleanup runs late.
31
- if (compiledModulePromise === attempt) {
12
+ // Only the rejected attempt may clear its own cache slot.
13
+ if (compiledModulePromise === attempt)
32
14
  compiledModulePromise = undefined;
33
- }
34
15
  });
35
16
  }
36
17
  return compiledModulePromise;
37
18
  }
38
- const MAX_TRAP_RECOVERIES = 16;
39
- let currentGeneration = 0;
40
- let trapRecoveriesStarted = 0;
41
- let freshInstanceStarts = 0;
42
- let glueImportAttempts = 0;
43
- let terminalTrapRecoveryError;
44
- /**
45
- * Every fresh instance attempt creates a new wasm-bindgen glue module
46
- * record. The import-attempt nonce is deliberately independent of the trap
47
- * generation: transient import/initSync failures need a new module record
48
- * without consuming the bounded trap-recovery budget.
49
- */
50
- async function freshInstance(generation) {
51
- freshInstanceStarts += 1;
52
- glueImportAttempts += 1;
53
- const glueSpecifier = new URL(glueUrl.href);
54
- glueSpecifier.searchParams.set("zfbMdWasmGen", String(generation));
55
- glueSpecifier.searchParams.set("zfbMdWasmAttempt", String(glueImportAttempts));
56
- const [module, glue] = await Promise.all([
57
- getCompiledModule(),
58
- // Deferring the injected function also converts a synchronous test-seam
59
- // throw into the rejection handled by the retryable instance cache.
60
- Promise.resolve().then(() => importGlue(glueSpecifier.href)),
61
- ]);
62
- glue.initSync({ module });
63
- return { generation, glue };
64
- }
65
- let instancePromise;
66
- function startInstance(generation) {
67
- const attempt = freshInstance(generation);
68
- instancePromise = attempt;
69
- void attempt.catch(() => {
70
- // Trap recovery can replace the installed instance promise. Never let a
71
- // stale initialization rejection clear that newer attempt.
72
- if (instancePromise === attempt) {
73
- instancePromise = undefined;
74
- }
75
- });
76
- return attempt;
77
- }
78
- function getInstance() {
79
- if (terminalTrapRecoveryError) {
80
- return Promise.reject(terminalTrapRecoveryError);
81
- }
82
- if (!instancePromise) {
83
- return startInstance(currentGeneration);
84
- }
85
- return instancePromise;
86
- }
87
- function isTrap(err) {
88
- return typeof WebAssembly !== "undefined" && err instanceof WebAssembly.RuntimeError;
89
- }
90
- async function recoverAfterTrap(observedGeneration, cause) {
91
- if (terminalTrapRecoveryError) {
92
- throw terminalTrapRecoveryError;
93
- }
94
- // CAS-style single-flight: reporters for an already-replaced generation
95
- // await its replacement instead of creating another glue module record.
96
- if (observedGeneration !== currentGeneration) {
97
- await getInstance();
98
- return;
99
- }
100
- if (trapRecoveriesStarted >= MAX_TRAP_RECOVERIES) {
101
- terminalTrapRecoveryError = new ZfbMdWasmTrapRecoveryLimitError(MAX_TRAP_RECOVERIES, cause);
102
- instancePromise = undefined;
103
- throw terminalTrapRecoveryError;
104
- }
105
- trapRecoveriesStarted += 1;
106
- currentGeneration += 1;
107
- await startInstance(currentGeneration);
108
- }
109
- async function callWasm(fn) {
110
- const instance = await getInstance();
111
- try {
112
- return fn(instance);
113
- }
114
- catch (err) {
115
- if (!isTrap(err)) {
116
- throw err;
117
- }
118
- await recoverAfterTrap(instance.generation, err);
119
- throw new ZfbMdWasmTrapError(err);
120
- }
121
- }
122
- async function init() {
123
- await getInstance();
124
- }
125
- // Public entry surfaces make missing calls unreachable. This guard keeps
126
- // structural glue mismatches artifact-neutral and actionable.
127
- function requireCapability(fn, name) {
128
- if (!fn) {
129
- throw new Error(`zfb-md-wasm: ${name}() is not available in this wasm artifact. ` +
130
- `Import an entry whose artifact provides that capability.`);
131
- }
132
- return fn;
133
- }
134
- async function compile(source, options = {}) {
135
- const optionsJson = JSON.stringify(options);
136
- const json = await callWasm(({ glue }) => requireCapability(glue.compile, "compile").call(glue, source, optionsJson));
137
- return JSON.parse(json);
138
- }
139
- async function renderHtml(source, options = {}) {
140
- const optionsJson = JSON.stringify(options);
141
- const json = await callWasm(({ glue }) => requireCapability(glue.renderHtml, "renderHtml").call(glue, source, optionsJson));
142
- return JSON.parse(json);
143
- }
144
- /**
145
- * Parse markdown/MDX into a raw mdast tree (zfb#1857, epic zfb#1854).
146
- * The `parseToAst + JSON.parse` round trip here IS the product cost the
147
- * epic's benchmark measures against remark-parse. See `types.ts`'s
148
- * `ParseToAstResult`/`MdastNode` docs for the result shape and the
149
- * UTF-16 position contract.
150
- */
151
- async function parseToAst(source, options = {}) {
152
- const optionsJson = JSON.stringify(options);
153
- const json = await callWasm(({ glue }) => requireCapability(glue.parseToAst, "parseToAst").call(glue, source, optionsJson));
154
- return JSON.parse(json);
155
- }
156
- async function highlightCode(code, options) {
157
- const optionsJson = JSON.stringify(options);
158
- const json = await callWasm(({ glue }) => requireCapability(glue.highlightCode, "highlightCode").call(glue, code, optionsJson));
159
- return JSON.parse(json);
160
- }
161
- async function version() {
162
- return callWasm(({ glue }) => glue.version());
163
- }
164
- /** @internal Test-only hook that forces the current instance to trap. */
165
- async function __forceTrapForTests() {
166
- await callWasm(({ glue }) => glue.__forceTrapForTests());
167
- }
168
- /** @internal Test-only observability for the bounded recovery contract. */
169
- function __getTrapRecoveryStateForTests() {
170
- return {
171
- compiledModuleLoads,
172
- currentGeneration,
173
- freshInstanceStarts,
174
- glueImportAttempts,
175
- maxTrapRecoveries: MAX_TRAP_RECOVERIES,
176
- trapRecoveriesStarted,
177
- terminal: !!terminalTrapRecoveryError,
178
- };
179
- }
180
- return {
181
- init,
182
- compile,
183
- renderHtml,
184
- parseToAst,
185
- highlightCode,
186
- version,
187
- __forceTrapForTests,
188
- __getTrapRecoveryStateForTests,
189
- };
19
+ return createWasmApiFromStrategy({
20
+ getModule,
21
+ createGlue(generation, attempt) {
22
+ const glueSpecifier = new URL(glueUrl.href);
23
+ glueSpecifier.searchParams.set("zfbMdWasmGen", String(generation));
24
+ glueSpecifier.searchParams.set("zfbMdWasmAttempt", String(attempt));
25
+ return importGlue(glueSpecifier.href);
26
+ },
27
+ });
190
28
  }
191
29
  //# sourceMappingURL=runtime.js.map
@@ -1 +1 @@
1
- {"version":3,"file":"runtime.js","sourceRoot":"","sources":["../src/runtime.ts"],"names":[],"mappings":"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","sourcesContent":["import type {\n CompileResult,\n HighlightCodeOptions,\n HighlightCodeResult,\n ParseToAstOptions,\n ParseToAstResult,\n RenderHtmlResult,\n ZfbMdWasmOptions,\n} from \"./types.js\";\n\ninterface WasmGlueModule {\n // Keep this deliberately structural instead of importing the generated\n // glue's declaration file. The browser entry imports that file as a *URL\n // resource*, while the direct entry imports it dynamically. Both paths\n // nevertheless use the same wasm-bindgen surface and recovery\n // implementation below.\n //\n // Capability functions are structural and optional because each singleton\n // artifact's wasm-bindgen glue contains only its selected Rust export.\n // Public entries expose only the matching calls from `createWasmApi`.\n initSync(input?: { module: WebAssembly.Module }): unknown;\n compile?(source: string, optionsJson: string): string;\n renderHtml?(source: string, optionsJson: string): string;\n // Raw-mdast export (zfb#1857, epic zfb#1854).\n parseToAst?(source: string, optionsJson: string): string;\n highlightCode?(code: string, optionsJson: string): string;\n version(): string;\n __forceTrapForTests(): void;\n}\n\nexport interface WasmResourceConfig {\n glueUrl: URL;\n loadWasmBytes(): Promise<ArrayBuffer>;\n /** @internal Deterministic test seam; production uses WebAssembly.compile. */\n compileWasm?(bytes: ArrayBuffer): Promise<WebAssembly.Module>;\n /** @internal Deterministic test seam; production dynamically imports the glue URL. */\n importGlue?(specifier: string): Promise<WasmGlueModule>;\n}\n\nexport class ZfbMdWasmTrapError extends Error {\n constructor(cause: unknown) {\n super(\n \"zfb-md-wasm: the wasm instance trapped (a Rust panic or internal fault) and has been \" +\n \"automatically re-instantiated. This is always a bug in zfb-md-wasm -- please report it \" +\n \"with the input that triggered it.\",\n );\n this.name = \"ZfbMdWasmTrapError\";\n this.cause = cause;\n }\n}\n\nexport class ZfbMdWasmTrapRecoveryLimitError extends Error {\n constructor(maxRecoveries: number, cause: unknown) {\n super(\n `zfb-md-wasm: wasm trap recovery limit reached after ${maxRecoveries} ` +\n `successful re-instantiations. Further automatic recovery is disabled to avoid ` +\n `unbounded ES module record growth. Reload the JS realm before using zfb-md-wasm ` +\n `again, and please report the input that triggered the repeated traps.`,\n );\n this.name = \"ZfbMdWasmTrapRecoveryLimitError\";\n this.cause = cause;\n }\n}\n\nexport function createWasmApi({\n glueUrl,\n loadWasmBytes,\n compileWasm = (bytes) => WebAssembly.compile(bytes),\n importGlue = (specifier) => import(/* @vite-ignore */ specifier) as Promise<WasmGlueModule>,\n}: WasmResourceConfig) {\n let compiledModulePromise: Promise<WebAssembly.Module> | undefined;\n let compiledModuleLoads = 0;\n\n function getCompiledModule(): Promise<WebAssembly.Module> {\n if (!compiledModulePromise) {\n compiledModuleLoads += 1;\n const attempt = Promise.resolve().then(loadWasmBytes).then(compileWasm);\n compiledModulePromise = attempt;\n void attempt.catch(() => {\n // A rejected attempt must be retryable, but it must not erase a newer\n // attempt if its rejection cleanup runs late.\n if (compiledModulePromise === attempt) {\n compiledModulePromise = undefined;\n }\n });\n }\n return compiledModulePromise;\n }\n\n const MAX_TRAP_RECOVERIES = 16;\n\n let currentGeneration = 0;\n let trapRecoveriesStarted = 0;\n let freshInstanceStarts = 0;\n let glueImportAttempts = 0;\n let terminalTrapRecoveryError: ZfbMdWasmTrapRecoveryLimitError | undefined;\n\n interface Instance {\n generation: number;\n glue: WasmGlueModule;\n }\n\n /**\n * Every fresh instance attempt creates a new wasm-bindgen glue module\n * record. The import-attempt nonce is deliberately independent of the trap\n * generation: transient import/initSync failures need a new module record\n * without consuming the bounded trap-recovery budget.\n */\n async function freshInstance(generation: number): Promise<Instance> {\n freshInstanceStarts += 1;\n glueImportAttempts += 1;\n const glueSpecifier = new URL(glueUrl.href);\n glueSpecifier.searchParams.set(\"zfbMdWasmGen\", String(generation));\n glueSpecifier.searchParams.set(\"zfbMdWasmAttempt\", String(glueImportAttempts));\n const [module, glue] = await Promise.all([\n getCompiledModule(),\n // Deferring the injected function also converts a synchronous test-seam\n // throw into the rejection handled by the retryable instance cache.\n Promise.resolve().then(() => importGlue(glueSpecifier.href)),\n ]);\n glue.initSync({ module });\n return { generation, glue };\n }\n\n let instancePromise: Promise<Instance> | undefined;\n\n function startInstance(generation: number): Promise<Instance> {\n const attempt = freshInstance(generation);\n instancePromise = attempt;\n void attempt.catch(() => {\n // Trap recovery can replace the installed instance promise. Never let a\n // stale initialization rejection clear that newer attempt.\n if (instancePromise === attempt) {\n instancePromise = undefined;\n }\n });\n return attempt;\n }\n\n function getInstance(): Promise<Instance> {\n if (terminalTrapRecoveryError) {\n return Promise.reject(terminalTrapRecoveryError);\n }\n if (!instancePromise) {\n return startInstance(currentGeneration);\n }\n return instancePromise;\n }\n\n function isTrap(err: unknown): boolean {\n return typeof WebAssembly !== \"undefined\" && err instanceof WebAssembly.RuntimeError;\n }\n\n async function recoverAfterTrap(observedGeneration: number, cause: unknown): Promise<void> {\n if (terminalTrapRecoveryError) {\n throw terminalTrapRecoveryError;\n }\n\n // CAS-style single-flight: reporters for an already-replaced generation\n // await its replacement instead of creating another glue module record.\n if (observedGeneration !== currentGeneration) {\n await getInstance();\n return;\n }\n\n if (trapRecoveriesStarted >= MAX_TRAP_RECOVERIES) {\n terminalTrapRecoveryError = new ZfbMdWasmTrapRecoveryLimitError(MAX_TRAP_RECOVERIES, cause);\n instancePromise = undefined;\n throw terminalTrapRecoveryError;\n }\n\n trapRecoveriesStarted += 1;\n currentGeneration += 1;\n await startInstance(currentGeneration);\n }\n\n async function callWasm<T>(fn: (instance: Instance) => T): Promise<T> {\n const instance = await getInstance();\n try {\n return fn(instance);\n } catch (err) {\n if (!isTrap(err)) {\n throw err;\n }\n await recoverAfterTrap(instance.generation, err);\n throw new ZfbMdWasmTrapError(err);\n }\n }\n\n async function init(): Promise<void> {\n await getInstance();\n }\n\n // Public entry surfaces make missing calls unreachable. This guard keeps\n // structural glue mismatches artifact-neutral and actionable.\n function requireCapability<T>(fn: T | undefined, name: string): T {\n if (!fn) {\n throw new Error(\n `zfb-md-wasm: ${name}() is not available in this wasm artifact. ` +\n `Import an entry whose artifact provides that capability.`,\n );\n }\n return fn;\n }\n\n async function compile(source: string, options: ZfbMdWasmOptions = {}): Promise<CompileResult> {\n const optionsJson = JSON.stringify(options);\n const json = await callWasm(({ glue }) =>\n requireCapability(glue.compile, \"compile\").call(glue, source, optionsJson),\n );\n return JSON.parse(json) as CompileResult;\n }\n\n async function renderHtml(\n source: string,\n options: ZfbMdWasmOptions = {},\n ): Promise<RenderHtmlResult> {\n const optionsJson = JSON.stringify(options);\n const json = await callWasm(({ glue }) =>\n requireCapability(glue.renderHtml, \"renderHtml\").call(glue, source, optionsJson),\n );\n return JSON.parse(json) as RenderHtmlResult;\n }\n\n /**\n * Parse markdown/MDX into a raw mdast tree (zfb#1857, epic zfb#1854).\n * The `parseToAst + JSON.parse` round trip here IS the product cost the\n * epic's benchmark measures against remark-parse. See `types.ts`'s\n * `ParseToAstResult`/`MdastNode` docs for the result shape and the\n * UTF-16 position contract.\n */\n async function parseToAst(\n source: string,\n options: ParseToAstOptions = {},\n ): Promise<ParseToAstResult> {\n const optionsJson = JSON.stringify(options);\n const json = await callWasm(({ glue }) =>\n requireCapability(glue.parseToAst, \"parseToAst\").call(glue, source, optionsJson),\n );\n return JSON.parse(json) as ParseToAstResult;\n }\n\n async function highlightCode(\n code: string,\n options: HighlightCodeOptions,\n ): Promise<HighlightCodeResult> {\n const optionsJson = JSON.stringify(options);\n const json = await callWasm(({ glue }) =>\n requireCapability(glue.highlightCode, \"highlightCode\").call(glue, code, optionsJson),\n );\n return JSON.parse(json) as HighlightCodeResult;\n }\n\n async function version(): Promise<string> {\n return callWasm(({ glue }) => glue.version());\n }\n\n /** @internal Test-only hook that forces the current instance to trap. */\n async function __forceTrapForTests(): Promise<void> {\n await callWasm(({ glue }) => glue.__forceTrapForTests());\n }\n\n /** @internal Test-only observability for the bounded recovery contract. */\n function __getTrapRecoveryStateForTests(): {\n compiledModuleLoads: number;\n currentGeneration: number;\n freshInstanceStarts: number;\n glueImportAttempts: number;\n maxTrapRecoveries: number;\n trapRecoveriesStarted: number;\n terminal: boolean;\n } {\n return {\n compiledModuleLoads,\n currentGeneration,\n freshInstanceStarts,\n glueImportAttempts,\n maxTrapRecoveries: MAX_TRAP_RECOVERIES,\n trapRecoveriesStarted,\n terminal: !!terminalTrapRecoveryError,\n };\n }\n\n return {\n init,\n compile,\n renderHtml,\n parseToAst,\n highlightCode,\n version,\n __forceTrapForTests,\n __getTrapRecoveryStateForTests,\n };\n}\n"]}
1
+ {"version":3,"file":"runtime.js","sourceRoot":"","sources":["../src/runtime.ts"],"names":[],"mappings":"AAAA,OAAO,EAAE,yBAAyB,EAAE,MAAM,mBAAmB,CAAC;AAG9D,OAAO,EAAE,kBAAkB,EAAE,+BAA+B,EAAE,MAAM,mBAAmB,CAAC;AAWxF,4EAA4E;AAC5E,MAAM,UAAU,aAAa,CAAC,EAC5B,OAAO,EACP,aAAa,EACb,WAAW,GAAG,CAAC,KAAK,EAAE,EAAE,CAAC,WAAW,CAAC,OAAO,CAAC,KAAK,CAAC,EACnD,UAAU,GAAG,CAAC,SAAS,EAAE,EAAE,CAAC,MAAM,CAAC,kBAAkB,CAAC,SAAS,CAA4B,GACxE;IACnB,IAAI,qBAA8D,CAAC;IACnE,SAAS,SAAS,CAAC,MAAkB;QACnC,IAAI,CAAC,qBAAqB,EAAE,CAAC;YAC3B,MAAM,EAAE,CAAC;YACT,MAAM,OAAO,GAAG,OAAO,CAAC,OAAO,EAAE,CAAC,IAAI,CAAC,aAAa,CAAC,CAAC,IAAI,CAAC,WAAW,CAAC,CAAC;YACxE,qBAAqB,GAAG,OAAO,CAAC;YAChC,KAAK,OAAO,CAAC,KAAK,CAAC,GAAG,EAAE;gBACtB,0DAA0D;gBAC1D,IAAI,qBAAqB,KAAK,OAAO;oBAAE,qBAAqB,GAAG,SAAS,CAAC;YAC3E,CAAC,CAAC,CAAC;QACL,CAAC;QACD,OAAO,qBAAqB,CAAC;IAC/B,CAAC;IAED,OAAO,yBAAyB,CAAC;QAC/B,SAAS;QACT,UAAU,CAAC,UAAU,EAAE,OAAO;YAC5B,MAAM,aAAa,GAAG,IAAI,GAAG,CAAC,OAAO,CAAC,IAAI,CAAC,CAAC;YAC5C,aAAa,CAAC,YAAY,CAAC,GAAG,CAAC,cAAc,EAAE,MAAM,CAAC,UAAU,CAAC,CAAC,CAAC;YACnE,aAAa,CAAC,YAAY,CAAC,GAAG,CAAC,kBAAkB,EAAE,MAAM,CAAC,OAAO,CAAC,CAAC,CAAC;YACpE,OAAO,UAAU,CAAC,aAAa,CAAC,IAAI,CAAC,CAAC;QACxC,CAAC;KACF,CAAC,CAAC;AACL,CAAC","sourcesContent":["import { createWasmApiFromStrategy } from \"./runtime-core.js\";\nimport type { WasmGlueModule } from \"./runtime-core.js\";\n\nexport { ZfbMdWasmTrapError, ZfbMdWasmTrapRecoveryLimitError } from \"./runtime-core.js\";\n\nexport interface WasmResourceConfig {\n glueUrl: URL;\n loadWasmBytes(): Promise<ArrayBuffer>;\n /** @internal Deterministic test seam; production uses WebAssembly.compile. */\n compileWasm?(bytes: ArrayBuffer): Promise<WebAssembly.Module>;\n /** @internal Deterministic test seam; production dynamically imports the glue URL. */\n importGlue?(specifier: string): Promise<WasmGlueModule>;\n}\n\n/** URL loading strategy used by the existing browser and direct entries. */\nexport function createWasmApi({\n glueUrl,\n loadWasmBytes,\n compileWasm = (bytes) => WebAssembly.compile(bytes),\n importGlue = (specifier) => import(/* @vite-ignore */ specifier) as Promise<WasmGlueModule>,\n}: WasmResourceConfig) {\n let compiledModulePromise: Promise<WebAssembly.Module> | undefined;\n function getModule(onLoad: () => void): Promise<WebAssembly.Module> {\n if (!compiledModulePromise) {\n onLoad();\n const attempt = Promise.resolve().then(loadWasmBytes).then(compileWasm);\n compiledModulePromise = attempt;\n void attempt.catch(() => {\n // Only the rejected attempt may clear its own cache slot.\n if (compiledModulePromise === attempt) compiledModulePromise = undefined;\n });\n }\n return compiledModulePromise;\n }\n\n return createWasmApiFromStrategy({\n getModule,\n createGlue(generation, attempt) {\n const glueSpecifier = new URL(glueUrl.href);\n glueSpecifier.searchParams.set(\"zfbMdWasmGen\", String(generation));\n glueSpecifier.searchParams.set(\"zfbMdWasmAttempt\", String(attempt));\n return importGlue(glueSpecifier.href);\n },\n });\n}\n"]}
Binary file
@@ -0,0 +1,6 @@
1
+ export function createGlue(): {
2
+ initSync(input: { module: WebAssembly.Module }): unknown;
3
+ highlightCode(code: string, optionsJson: string): string;
4
+ version(): string;
5
+ __forceTrapForTests(): void;
6
+ };
@@ -0,0 +1,201 @@
1
+ export function createGlue() {
2
+ /* @ts-self-types="./zfb_md_wasm_highlight.d.ts" */
3
+
4
+ /**
5
+ * Deliberately trap the current WebAssembly instance for wrapper recovery
6
+ * tests. This is not part of the typed package API; the JavaScript wrapper
7
+ * exposes it only through its internal test hook.
8
+ *
9
+ * A Rust panic is the reliable cross-engine representation of the real
10
+ * failure this hook needs to exercise. Calling a generated raw C-ABI export
11
+ * with fabricated pointers could instead enter undefined Rust-level work and
12
+ * hang Chromium before reaching the wasm trap boundary.
13
+ */
14
+ function __forceTrapForTests() {
15
+ wasm.__forceTrapForTests();
16
+ }
17
+
18
+ /**
19
+ * Highlight arbitrary source into semantic class-mode HTML without requiring
20
+ * a Markdown fence. Exported to JavaScript as `highlightCode`.
21
+ *
22
+ * Returns `{ "html": string|null, "diagnostics": HighlightDiagnostic[] }`.
23
+ * Invalid options are structured errors; an unknown non-empty language or a
24
+ * tokenizer fallback returns escaped wrapper markup plus a warning.
25
+ * @param {string} code
26
+ * @param {string} options_json
27
+ * @returns {string}
28
+ */
29
+ function highlightCode(code, options_json) {
30
+ let deferred3_0;
31
+ let deferred3_1;
32
+ try {
33
+ const retptr = wasm.__wbindgen_add_to_stack_pointer(-16);
34
+ const ptr0 = passStringToWasm0(code, wasm.__wbindgen_export, wasm.__wbindgen_export2);
35
+ const len0 = WASM_VECTOR_LEN;
36
+ const ptr1 = passStringToWasm0(options_json, wasm.__wbindgen_export, wasm.__wbindgen_export2);
37
+ const len1 = WASM_VECTOR_LEN;
38
+ wasm.highlightCode(retptr, ptr0, len0, ptr1, len1);
39
+ var r0 = getDataViewMemory0().getInt32(retptr + 4 * 0, true);
40
+ var r1 = getDataViewMemory0().getInt32(retptr + 4 * 1, true);
41
+ deferred3_0 = r0;
42
+ deferred3_1 = r1;
43
+ return getStringFromWasm0(r0, r1);
44
+ } finally {
45
+ wasm.__wbindgen_add_to_stack_pointer(16);
46
+ wasm.__wbindgen_export3(deferred3_0, deferred3_1, 1);
47
+ }
48
+ }
49
+
50
+ /**
51
+ * This package's release version, stamped by CI at compile time.
52
+ *
53
+ * Development builds without `ZFB_RELEASE_VERSION` fall back to this crate's
54
+ * manifest version (`CARGO_PKG_VERSION`).
55
+ * @returns {string}
56
+ */
57
+ function version() {
58
+ let deferred1_0;
59
+ let deferred1_1;
60
+ try {
61
+ const retptr = wasm.__wbindgen_add_to_stack_pointer(-16);
62
+ wasm.version(retptr);
63
+ var r0 = getDataViewMemory0().getInt32(retptr + 4 * 0, true);
64
+ var r1 = getDataViewMemory0().getInt32(retptr + 4 * 1, true);
65
+ deferred1_0 = r0;
66
+ deferred1_1 = r1;
67
+ return getStringFromWasm0(r0, r1);
68
+ } finally {
69
+ wasm.__wbindgen_add_to_stack_pointer(16);
70
+ wasm.__wbindgen_export3(deferred1_0, deferred1_1, 1);
71
+ }
72
+ }
73
+ function __wbg_get_imports() {
74
+ const import0 = {
75
+ __proto__: null,
76
+ };
77
+ return {
78
+ __proto__: null,
79
+ "./zfb_md_wasm_highlight_bg.js": import0,
80
+ };
81
+ }
82
+
83
+ let cachedDataViewMemory0 = null;
84
+ function getDataViewMemory0() {
85
+ if (cachedDataViewMemory0 === null || cachedDataViewMemory0.buffer.detached === true || (cachedDataViewMemory0.buffer.detached === undefined && cachedDataViewMemory0.buffer !== wasm.memory.buffer)) {
86
+ cachedDataViewMemory0 = new DataView(wasm.memory.buffer);
87
+ }
88
+ return cachedDataViewMemory0;
89
+ }
90
+
91
+ function getStringFromWasm0(ptr, len) {
92
+ return decodeText(ptr >>> 0, len);
93
+ }
94
+
95
+ let cachedUint8ArrayMemory0 = null;
96
+ function getUint8ArrayMemory0() {
97
+ if (cachedUint8ArrayMemory0 === null || cachedUint8ArrayMemory0.byteLength === 0) {
98
+ cachedUint8ArrayMemory0 = new Uint8Array(wasm.memory.buffer);
99
+ }
100
+ return cachedUint8ArrayMemory0;
101
+ }
102
+
103
+ function passStringToWasm0(arg, malloc, realloc) {
104
+ if (realloc === undefined) {
105
+ const buf = cachedTextEncoder.encode(arg);
106
+ const ptr = malloc(buf.length, 1) >>> 0;
107
+ getUint8ArrayMemory0().subarray(ptr, ptr + buf.length).set(buf);
108
+ WASM_VECTOR_LEN = buf.length;
109
+ return ptr;
110
+ }
111
+
112
+ let len = arg.length;
113
+ let ptr = malloc(len, 1) >>> 0;
114
+
115
+ const mem = getUint8ArrayMemory0();
116
+
117
+ let offset = 0;
118
+
119
+ for (; offset < len; offset++) {
120
+ const code = arg.charCodeAt(offset);
121
+ if (code > 0x7F) break;
122
+ mem[ptr + offset] = code;
123
+ }
124
+ if (offset !== len) {
125
+ if (offset !== 0) {
126
+ arg = arg.slice(offset);
127
+ }
128
+ ptr = realloc(ptr, len, len = offset + arg.length * 3, 1) >>> 0;
129
+ const view = getUint8ArrayMemory0().subarray(ptr + offset, ptr + len);
130
+ const ret = cachedTextEncoder.encodeInto(arg, view);
131
+
132
+ offset += ret.written;
133
+ ptr = realloc(ptr, len, offset, 1) >>> 0;
134
+ }
135
+
136
+ WASM_VECTOR_LEN = offset;
137
+ return ptr;
138
+ }
139
+
140
+ let cachedTextDecoder = new TextDecoder('utf-8', { ignoreBOM: true, fatal: true });
141
+ cachedTextDecoder.decode();
142
+ const MAX_SAFARI_DECODE_BYTES = 2146435072;
143
+ let numBytesDecoded = 0;
144
+ function decodeText(ptr, len) {
145
+ numBytesDecoded += len;
146
+ if (numBytesDecoded >= MAX_SAFARI_DECODE_BYTES) {
147
+ cachedTextDecoder = new TextDecoder('utf-8', { ignoreBOM: true, fatal: true });
148
+ cachedTextDecoder.decode();
149
+ numBytesDecoded = len;
150
+ }
151
+ return cachedTextDecoder.decode(getUint8ArrayMemory0().subarray(ptr, ptr + len));
152
+ }
153
+
154
+ const cachedTextEncoder = new TextEncoder();
155
+
156
+ if (!('encodeInto' in cachedTextEncoder)) {
157
+ cachedTextEncoder.encodeInto = function (arg, view) {
158
+ const buf = cachedTextEncoder.encode(arg);
159
+ view.set(buf);
160
+ return {
161
+ read: arg.length,
162
+ written: buf.length
163
+ };
164
+ };
165
+ }
166
+
167
+ let WASM_VECTOR_LEN = 0;
168
+
169
+ let wasmModule, wasmInstance, wasm;
170
+ function __wbg_finalize_init(instance, module) {
171
+ wasmInstance = instance;
172
+ wasm = instance.exports;
173
+ wasmModule = module;
174
+ cachedDataViewMemory0 = null;
175
+ cachedUint8ArrayMemory0 = null;
176
+ return wasm;
177
+ }
178
+
179
+
180
+
181
+ function initSync(module) {
182
+ if (wasm !== undefined) return wasm;
183
+
184
+
185
+ if (module !== undefined) {
186
+ if (Object.getPrototypeOf(module) === Object.prototype) {
187
+ ({module} = module)
188
+ } else {
189
+ console.warn('using deprecated parameters for `initSync()`; pass a single object instead')
190
+ }
191
+ }
192
+
193
+ const imports = __wbg_get_imports();
194
+ if (!(module instanceof WebAssembly.Module)) {
195
+ module = new WebAssembly.Module(module);
196
+ }
197
+ const instance = new WebAssembly.Instance(module, imports);
198
+ return __wbg_finalize_init(instance, module);
199
+ }
200
+ return { initSync, highlightCode, version, __forceTrapForTests };
201
+ }
@@ -0,0 +1,6 @@
1
+ export function createGlue(): {
2
+ initSync(input: { module: WebAssembly.Module }): unknown;
3
+ parseToAst(source: string, optionsJson: string): string;
4
+ version(): string;
5
+ __forceTrapForTests(): void;
6
+ };
@@ -0,0 +1,242 @@
1
+ export function createGlue() {
2
+ /* @ts-self-types="./zfb_md_wasm_parse.d.ts" */
3
+
4
+ /**
5
+ * Deliberately trap the current WebAssembly instance for wrapper recovery
6
+ * tests. This is not part of the typed package API; the JavaScript wrapper
7
+ * exposes it only through its internal test hook.
8
+ *
9
+ * A Rust panic is the reliable cross-engine representation of the real
10
+ * failure this hook needs to exercise. Calling a generated raw C-ABI export
11
+ * with fabricated pointers could instead enter undefined Rust-level work and
12
+ * hang Chromium before reaching the wasm trap boundary.
13
+ */
14
+ function __forceTrapForTests() {
15
+ wasm.__forceTrapForTests();
16
+ }
17
+
18
+ /**
19
+ * Parse markdown/MDX source into a serialized RAW mdast tree — a
20
+ * supported API tier (zfb#1857, decided GO by epic zfb#1854 / zfb#1828).
21
+ * Exported to JS as `parseToAst`.
22
+ *
23
+ * Returns a JSON string: `{ "ast": mdast|null, "frontmatter": json,
24
+ * "diagnostics": Diagnostic[] }`. The tree is raw markdown-rs parser output
25
+ * converted through its serde shape into an open carrier, optionally
26
+ * composed with generic directives (post-frontmatter-strip,
27
+ * PRE-zfb-visitors — mdast, not hast, and
28
+ * unrecognized/custom constructs such as MDX JSX elements survive as typed
29
+ * nodes rather than being dropped), serialized in the unist shape via
30
+ * markdown-rs's `serde` feature, with every `position` shifted back into
31
+ * original-source coordinates (see [`shift_interop_positions`]). Its
32
+ * distinct closed options document accepts only `filename`, `dialect`,
33
+ * `directives`, and `pipeline.gfm`; compile/visitor/serializer knobs are
34
+ * rejected rather than silently ignored. A missing filename defaults to
35
+ * `<anonymous>.mdx`.
36
+ * Otherwise `.md` infers Markdown and `.mdx` infers MDX; an explicit dialect
37
+ * overrides either valid extension without waiving the extension gate.
38
+ *
39
+ * ## Position contract: UTF-16 code units (decided zfb#1856)
40
+ *
41
+ * `position.offset` and `position.column` are UTF-16 code-unit indices —
42
+ * remark/unist convention, and what `String.prototype.slice`,
43
+ * `mdast-util-to-hast`, and consumer mdast plugins already index by.
44
+ * `position.line` is unit-agnostic and needs no conversion. This matters on
45
+ * non-ASCII sources: a scalar value outside the Basic Multilingual Plane
46
+ * (most emoji) is 1 UTF-16 code unit different from its UTF-8 byte width
47
+ * (2 UTF-16 units vs. usually 4 bytes) — see [`Utf16Positions`] for the
48
+ * conversion and `tests/parse_to_ast.rs`'s
49
+ * `utf16_code_unit_semantics_are_pinned_on_non_ascii` for the pin. Pure-
50
+ * ASCII sources take a fast path that skips the conversion entirely (byte
51
+ * units already equal UTF-16 units there).
52
+ *
53
+ * ## Documented divergences from remark-parse / remark-mdx
54
+ *
55
+ * - `mdxJsxAttribute` records carry no `position` — markdown-rs does not
56
+ * model attribute positions.
57
+ * - Top-level `import`/`export` degrade to paragraphs (no `mdxjsEsm` nodes
58
+ * — markdown-rs's `mdx_esm_parse` needs a JS ESM parser the wasm boundary
59
+ * cannot host), and MDX expressions carry no estree data (the default
60
+ * aggressive mode validates braces only, unlike remark-mdx's acorn pass).
61
+ * Consumers needing remark-mdx-equivalent ESM/estree keep remark for
62
+ * those documents.
63
+ * - `_markdownRsStops` (on MDX expression/ESM nodes) is markdown-rs-
64
+ * internal re-parse bookkeeping: internal, unstable, and BYTE-based (NOT
65
+ * UTF-16 like `position`) — never slice a string with it.
66
+ * @param {string} source
67
+ * @param {string} options_json
68
+ * @returns {string}
69
+ */
70
+ function parseToAst(source, options_json) {
71
+ let deferred3_0;
72
+ let deferred3_1;
73
+ try {
74
+ const retptr = wasm.__wbindgen_add_to_stack_pointer(-16);
75
+ const ptr0 = passStringToWasm0(source, wasm.__wbindgen_export, wasm.__wbindgen_export2);
76
+ const len0 = WASM_VECTOR_LEN;
77
+ const ptr1 = passStringToWasm0(options_json, wasm.__wbindgen_export, wasm.__wbindgen_export2);
78
+ const len1 = WASM_VECTOR_LEN;
79
+ wasm.parseToAst(retptr, ptr0, len0, ptr1, len1);
80
+ var r0 = getDataViewMemory0().getInt32(retptr + 4 * 0, true);
81
+ var r1 = getDataViewMemory0().getInt32(retptr + 4 * 1, true);
82
+ deferred3_0 = r0;
83
+ deferred3_1 = r1;
84
+ return getStringFromWasm0(r0, r1);
85
+ } finally {
86
+ wasm.__wbindgen_add_to_stack_pointer(16);
87
+ wasm.__wbindgen_export3(deferred3_0, deferred3_1, 1);
88
+ }
89
+ }
90
+
91
+ /**
92
+ * This package's release version, stamped by CI at compile time.
93
+ *
94
+ * Development builds without `ZFB_RELEASE_VERSION` fall back to this crate's
95
+ * manifest version (`CARGO_PKG_VERSION`).
96
+ * @returns {string}
97
+ */
98
+ function version() {
99
+ let deferred1_0;
100
+ let deferred1_1;
101
+ try {
102
+ const retptr = wasm.__wbindgen_add_to_stack_pointer(-16);
103
+ wasm.version(retptr);
104
+ var r0 = getDataViewMemory0().getInt32(retptr + 4 * 0, true);
105
+ var r1 = getDataViewMemory0().getInt32(retptr + 4 * 1, true);
106
+ deferred1_0 = r0;
107
+ deferred1_1 = r1;
108
+ return getStringFromWasm0(r0, r1);
109
+ } finally {
110
+ wasm.__wbindgen_add_to_stack_pointer(16);
111
+ wasm.__wbindgen_export3(deferred1_0, deferred1_1, 1);
112
+ }
113
+ }
114
+ function __wbg_get_imports() {
115
+ const import0 = {
116
+ __proto__: null,
117
+ };
118
+ return {
119
+ __proto__: null,
120
+ "./zfb_md_wasm_parse_bg.js": import0,
121
+ };
122
+ }
123
+
124
+ let cachedDataViewMemory0 = null;
125
+ function getDataViewMemory0() {
126
+ if (cachedDataViewMemory0 === null || cachedDataViewMemory0.buffer.detached === true || (cachedDataViewMemory0.buffer.detached === undefined && cachedDataViewMemory0.buffer !== wasm.memory.buffer)) {
127
+ cachedDataViewMemory0 = new DataView(wasm.memory.buffer);
128
+ }
129
+ return cachedDataViewMemory0;
130
+ }
131
+
132
+ function getStringFromWasm0(ptr, len) {
133
+ return decodeText(ptr >>> 0, len);
134
+ }
135
+
136
+ let cachedUint8ArrayMemory0 = null;
137
+ function getUint8ArrayMemory0() {
138
+ if (cachedUint8ArrayMemory0 === null || cachedUint8ArrayMemory0.byteLength === 0) {
139
+ cachedUint8ArrayMemory0 = new Uint8Array(wasm.memory.buffer);
140
+ }
141
+ return cachedUint8ArrayMemory0;
142
+ }
143
+
144
+ function passStringToWasm0(arg, malloc, realloc) {
145
+ if (realloc === undefined) {
146
+ const buf = cachedTextEncoder.encode(arg);
147
+ const ptr = malloc(buf.length, 1) >>> 0;
148
+ getUint8ArrayMemory0().subarray(ptr, ptr + buf.length).set(buf);
149
+ WASM_VECTOR_LEN = buf.length;
150
+ return ptr;
151
+ }
152
+
153
+ let len = arg.length;
154
+ let ptr = malloc(len, 1) >>> 0;
155
+
156
+ const mem = getUint8ArrayMemory0();
157
+
158
+ let offset = 0;
159
+
160
+ for (; offset < len; offset++) {
161
+ const code = arg.charCodeAt(offset);
162
+ if (code > 0x7F) break;
163
+ mem[ptr + offset] = code;
164
+ }
165
+ if (offset !== len) {
166
+ if (offset !== 0) {
167
+ arg = arg.slice(offset);
168
+ }
169
+ ptr = realloc(ptr, len, len = offset + arg.length * 3, 1) >>> 0;
170
+ const view = getUint8ArrayMemory0().subarray(ptr + offset, ptr + len);
171
+ const ret = cachedTextEncoder.encodeInto(arg, view);
172
+
173
+ offset += ret.written;
174
+ ptr = realloc(ptr, len, offset, 1) >>> 0;
175
+ }
176
+
177
+ WASM_VECTOR_LEN = offset;
178
+ return ptr;
179
+ }
180
+
181
+ let cachedTextDecoder = new TextDecoder('utf-8', { ignoreBOM: true, fatal: true });
182
+ cachedTextDecoder.decode();
183
+ const MAX_SAFARI_DECODE_BYTES = 2146435072;
184
+ let numBytesDecoded = 0;
185
+ function decodeText(ptr, len) {
186
+ numBytesDecoded += len;
187
+ if (numBytesDecoded >= MAX_SAFARI_DECODE_BYTES) {
188
+ cachedTextDecoder = new TextDecoder('utf-8', { ignoreBOM: true, fatal: true });
189
+ cachedTextDecoder.decode();
190
+ numBytesDecoded = len;
191
+ }
192
+ return cachedTextDecoder.decode(getUint8ArrayMemory0().subarray(ptr, ptr + len));
193
+ }
194
+
195
+ const cachedTextEncoder = new TextEncoder();
196
+
197
+ if (!('encodeInto' in cachedTextEncoder)) {
198
+ cachedTextEncoder.encodeInto = function (arg, view) {
199
+ const buf = cachedTextEncoder.encode(arg);
200
+ view.set(buf);
201
+ return {
202
+ read: arg.length,
203
+ written: buf.length
204
+ };
205
+ };
206
+ }
207
+
208
+ let WASM_VECTOR_LEN = 0;
209
+
210
+ let wasmModule, wasmInstance, wasm;
211
+ function __wbg_finalize_init(instance, module) {
212
+ wasmInstance = instance;
213
+ wasm = instance.exports;
214
+ wasmModule = module;
215
+ cachedDataViewMemory0 = null;
216
+ cachedUint8ArrayMemory0 = null;
217
+ return wasm;
218
+ }
219
+
220
+
221
+
222
+ function initSync(module) {
223
+ if (wasm !== undefined) return wasm;
224
+
225
+
226
+ if (module !== undefined) {
227
+ if (Object.getPrototypeOf(module) === Object.prototype) {
228
+ ({module} = module)
229
+ } else {
230
+ console.warn('using deprecated parameters for `initSync()`; pass a single object instead')
231
+ }
232
+ }
233
+
234
+ const imports = __wbg_get_imports();
235
+ if (!(module instanceof WebAssembly.Module)) {
236
+ module = new WebAssembly.Module(module);
237
+ }
238
+ const instance = new WebAssembly.Instance(module, imports);
239
+ return __wbg_finalize_init(instance, module);
240
+ }
241
+ return { initSync, parseToAst, version, __forceTrapForTests };
242
+ }
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@takazudo/zfb-md-wasm",
3
- "version": "2.21.1",
3
+ "version": "2.22.1",
4
4
  "private": false,
5
5
  "type": "module",
6
6
  "description": "zfb's md/mdx -> JS/HTML conversion pipeline compiled to WebAssembly, for browser-side dynamic conversion (CMS live preview with parity to zfb's production output).",
@@ -35,6 +35,7 @@
35
35
  },
36
36
  "./highlight": {
37
37
  "types": "./dist/highlight.d.ts",
38
+ "workerd": "./dist/highlight-workerd.js",
38
39
  "browser": "./dist/highlight-browser.js",
39
40
  "default": "./dist/highlight.js"
40
41
  },
@@ -45,6 +46,7 @@
45
46
  },
46
47
  "./parse": {
47
48
  "types": "./dist/parse.d.ts",
49
+ "workerd": "./dist/parse-workerd.js",
48
50
  "browser": "./dist/parse-browser.js",
49
51
  "default": "./dist/parse.js"
50
52
  }
@@ -71,10 +73,11 @@
71
73
  "binaryen": "130.0.0",
72
74
  "esbuild": "0.27.7",
73
75
  "mdast-util-to-hast": "^13.2.1",
76
+ "miniflare": "4.20260424.0",
77
+ "remark-directive": "^4.0.0",
74
78
  "remark-gfm": "^4.0.1",
75
79
  "remark-mdx": "^3.1.1",
76
80
  "remark-parse": "^11.0.0",
77
- "remark-directive": "^4.0.0",
78
81
  "typescript": "^5.9.0",
79
82
  "unified": "^11.0.5",
80
83
  "unist-util-visit": "^5.0.0",