flux-md 0.16.2 → 0.18.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 +950 -0
- package/README.md +111 -82
- package/dist/block-props.d.ts +18 -0
- package/dist/block-props.js +75 -0
- package/dist/client.d.ts +311 -0
- package/{src/client.ts → dist/client.js} +143 -325
- package/dist/dom.d.ts +110 -0
- package/dist/dom.js +576 -0
- package/dist/element.d.ts +20 -0
- package/dist/element.js +287 -0
- package/dist/hi.d.ts +12 -0
- package/{src/hi.ts → dist/hi.js} +42 -74
- package/dist/html-to-react.d.ts +40 -0
- package/dist/html-to-react.js +344 -0
- package/{src/index.ts → dist/index.d.ts} +5 -25
- package/dist/index.js +16 -0
- package/dist/morph.d.ts +28 -0
- package/dist/morph.js +166 -0
- package/dist/react.d.ts +204 -0
- package/dist/react.js +490 -0
- package/dist/renderers/CodeBlock.d.ts +7 -0
- package/dist/renderers/CodeBlock.js +75 -0
- package/dist/renderers/Math.d.ts +14 -0
- package/dist/renderers/Math.js +15 -0
- package/dist/renderers/Mermaid.d.ts +13 -0
- package/dist/renderers/Mermaid.js +15 -0
- package/dist/server-react.d.ts +32 -0
- package/dist/server-react.js +48 -0
- package/dist/server.d.ts +31 -0
- package/dist/server.js +81 -0
- package/{src/solid.tsx → dist/solid.d.ts} +19 -95
- package/dist/solid.js +54 -0
- package/dist/svelte.d.ts +80 -0
- package/dist/svelte.js +59 -0
- package/dist/types-core.d.ts +382 -0
- package/dist/types-core.js +0 -0
- package/{src/types-react.ts → dist/types-react.d.ts} +0 -1
- package/dist/types-react.js +0 -0
- package/dist/types.d.ts +2 -0
- package/dist/types.js +2 -0
- package/dist/vue.d.ts +94 -0
- package/dist/vue.js +79 -0
- package/{src → dist}/wasm/flux_md_core.d.ts +18 -6
- package/{src → dist}/wasm/flux_md_core.js +50 -55
- package/dist/wasm/flux_md_core_bg.wasm +0 -0
- package/{src → dist}/wasm/flux_md_core_bg.wasm.d.ts +1 -0
- package/dist/worker-core.d.ts +65 -0
- package/dist/worker-core.js +154 -0
- package/dist/worker.d.ts +1 -0
- package/dist/worker.js +48 -0
- package/package.json +25 -19
- package/src/block-props.ts +0 -141
- package/src/dom.ts +0 -946
- package/src/element.ts +0 -400
- package/src/html-to-react.ts +0 -455
- package/src/morph.ts +0 -253
- package/src/react.tsx +0 -1020
- package/src/renderers/CodeBlock.tsx +0 -116
- package/src/renderers/Math.tsx +0 -28
- package/src/renderers/Mermaid.tsx +0 -27
- package/src/server.tsx +0 -221
- package/src/svelte.ts +0 -179
- package/src/types-core.ts +0 -398
- package/src/types.ts +0 -7
- package/src/vue.ts +0 -184
- package/src/wasm/flux_md_core_bg.wasm +0 -0
- package/src/worker-core.ts +0 -174
- package/src/worker.ts +0 -72
- /package/{src → dist}/styles.css +0 -0
package/src/worker-core.ts
DELETED
|
@@ -1,174 +0,0 @@
|
|
|
1
|
-
import type { FromWorker, ParserConfig, Patch, ToWorker } from "./types-core";
|
|
2
|
-
|
|
3
|
-
/** The slice of `FluxParser` the worker drives — narrowed to an interface so the
|
|
4
|
-
* message/readiness state machine is unit-testable with a fake parser, no WASM.
|
|
5
|
-
* (Same testability move as {@link FluxPool} taking an injected worker factory.) */
|
|
6
|
-
export interface ParserLike {
|
|
7
|
-
append(chunk: string): Patch;
|
|
8
|
-
finalize(): Patch;
|
|
9
|
-
free(): void;
|
|
10
|
-
retainedBytes(): number;
|
|
11
|
-
}
|
|
12
|
-
|
|
13
|
-
/** Dependencies injected into {@link WorkerCore}, isolating it from the worker
|
|
14
|
-
* globals (`self`, `queueMicrotask`) and the WASM module so it can be tested. */
|
|
15
|
-
export interface WorkerCoreDeps {
|
|
16
|
-
/** Create + configure a parser for a stream (prod: `new FluxParser()` + setX). */
|
|
17
|
-
makeParser(config: ParserConfig | undefined): ParserLike;
|
|
18
|
-
/** Post a message to the main thread (prod: `self.postMessage`). */
|
|
19
|
-
post(msg: FromWorker): void;
|
|
20
|
-
/** Current WASM heap size in bytes, reported on each patch (0 if unknown). */
|
|
21
|
-
memBytes(): number;
|
|
22
|
-
/** Defer a flush to a future microtask (prod: `queueMicrotask`). */
|
|
23
|
-
schedule(fn: () => void): void;
|
|
24
|
-
}
|
|
25
|
-
|
|
26
|
-
/**
|
|
27
|
-
* The worker's message reducer + WASM-readiness gate, extracted from the worker
|
|
28
|
-
* shell so its trickiest invariant is testable without a real Worker or WASM.
|
|
29
|
-
*
|
|
30
|
-
* **The invariant:** WASM `init()` is async, and the client does NOT wait for
|
|
31
|
-
* readiness before appending — so chunks can arrive first. A parser must never
|
|
32
|
-
* be constructed before init resolves (`new FluxParser()` against an
|
|
33
|
-
* uninitialized module throws `fluxparser_new of undefined` and silently drops
|
|
34
|
-
* that chunk). So while `ready` is false, appends only accumulate in `pending`
|
|
35
|
-
* (scheduleFlush is a no-op) and `finalize` is deferred; {@link markReady}
|
|
36
|
-
* drains both — appends first (creating each parser + applying buffered text),
|
|
37
|
-
* then any deferred finalize — once init has completed.
|
|
38
|
-
*/
|
|
39
|
-
export class WorkerCore {
|
|
40
|
-
// One parser per stream id; WASM is loaded once and shared by all of them.
|
|
41
|
-
private parsers = new Map<number, ParserLike>();
|
|
42
|
-
private config = new Map<number, ParserConfig>();
|
|
43
|
-
private pending = new Map<number, string>();
|
|
44
|
-
private totalAppended = new Map<number, number>();
|
|
45
|
-
private finalizePending = new Set<number>();
|
|
46
|
-
private flushScheduled = false;
|
|
47
|
-
private ready = false;
|
|
48
|
-
|
|
49
|
-
constructor(private deps: WorkerCoreDeps) {}
|
|
50
|
-
|
|
51
|
-
/** Handle one message from the main thread (append/finalize/reset/dispose). */
|
|
52
|
-
handle(msg: ToWorker): void {
|
|
53
|
-
const id = msg.streamId;
|
|
54
|
-
// Stash any per-stream config carried on the first message (FIFO guarantees
|
|
55
|
-
// it arrives before the parser is created in flush/finalize).
|
|
56
|
-
if ((msg.type === "append" || msg.type === "finalize") && msg.config) {
|
|
57
|
-
this.config.set(id, msg.config);
|
|
58
|
-
}
|
|
59
|
-
switch (msg.type) {
|
|
60
|
-
case "append":
|
|
61
|
-
this.pending.set(id, (this.pending.get(id) ?? "") + msg.chunk);
|
|
62
|
-
this.scheduleFlush();
|
|
63
|
-
break;
|
|
64
|
-
case "finalize":
|
|
65
|
-
// Before WASM is ready, defer: markReady() finalizes it after init (the
|
|
66
|
-
// buffered input is drained first). Otherwise finalize now.
|
|
67
|
-
if (!this.ready) this.finalizePending.add(id);
|
|
68
|
-
else this.doFinalize(id);
|
|
69
|
-
break;
|
|
70
|
-
case "reset":
|
|
71
|
-
// Free and recreate lazily on the next append — same stream id, **same
|
|
72
|
-
// config** (kept). The client resets its local state synchronously.
|
|
73
|
-
this.parsers.get(id)?.free();
|
|
74
|
-
this.parsers.delete(id);
|
|
75
|
-
this.pending.delete(id);
|
|
76
|
-
this.finalizePending.delete(id); // a reset cancels a not-yet-run early finalize
|
|
77
|
-
this.totalAppended.delete(id);
|
|
78
|
-
break;
|
|
79
|
-
case "dispose":
|
|
80
|
-
this.dispose(id);
|
|
81
|
-
break;
|
|
82
|
-
}
|
|
83
|
-
}
|
|
84
|
-
|
|
85
|
-
/** Called once WASM init resolves: open the gate and drain what was buffered. */
|
|
86
|
-
markReady(): void {
|
|
87
|
-
this.ready = true;
|
|
88
|
-
this.deps.post({ type: "ready" });
|
|
89
|
-
// Order matters: flush appends first (creating each parser + applying
|
|
90
|
-
// buffered text), then finalize any stream that already requested it.
|
|
91
|
-
if (this.pending.size > 0) this.flush();
|
|
92
|
-
if (this.finalizePending.size > 0) {
|
|
93
|
-
for (const id of this.finalizePending) this.doFinalize(id);
|
|
94
|
-
this.finalizePending.clear();
|
|
95
|
-
}
|
|
96
|
-
}
|
|
97
|
-
|
|
98
|
-
private getOrCreate(streamId: number): ParserLike {
|
|
99
|
-
let p = this.parsers.get(streamId);
|
|
100
|
-
if (!p) {
|
|
101
|
-
p = this.deps.makeParser(this.config.get(streamId));
|
|
102
|
-
this.parsers.set(streamId, p);
|
|
103
|
-
}
|
|
104
|
-
return p;
|
|
105
|
-
}
|
|
106
|
-
|
|
107
|
-
private dispose(streamId: number): void {
|
|
108
|
-
this.parsers.get(streamId)?.free();
|
|
109
|
-
this.parsers.delete(streamId);
|
|
110
|
-
this.config.delete(streamId);
|
|
111
|
-
this.pending.delete(streamId);
|
|
112
|
-
this.finalizePending.delete(streamId);
|
|
113
|
-
this.totalAppended.delete(streamId);
|
|
114
|
-
}
|
|
115
|
-
|
|
116
|
-
private emitPatch(streamId: number, patch: Patch, parser: ParserLike, parseMicros: number): void {
|
|
117
|
-
this.deps.post({
|
|
118
|
-
type: "patch",
|
|
119
|
-
streamId,
|
|
120
|
-
patch,
|
|
121
|
-
appendedBytes: this.totalAppended.get(streamId) ?? 0,
|
|
122
|
-
parseMicros,
|
|
123
|
-
retainedBytes: parser.retainedBytes(),
|
|
124
|
-
wasmMemoryBytes: this.deps.memBytes(),
|
|
125
|
-
});
|
|
126
|
-
}
|
|
127
|
-
|
|
128
|
-
private scheduleFlush(): void {
|
|
129
|
-
if (this.flushScheduled || !this.ready) return; // before ready, input just accumulates in `pending`
|
|
130
|
-
this.flushScheduled = true;
|
|
131
|
-
this.deps.schedule(() => this.flush());
|
|
132
|
-
}
|
|
133
|
-
|
|
134
|
-
private flush(): void {
|
|
135
|
-
this.flushScheduled = false;
|
|
136
|
-
if (!this.ready || this.pending.size === 0) return; // buffer stays put until WASM is ready
|
|
137
|
-
// Process every stream with buffered input this microtask.
|
|
138
|
-
for (const [streamId, chunk] of this.pending) {
|
|
139
|
-
this.pending.delete(streamId);
|
|
140
|
-
if (chunk.length === 0) continue;
|
|
141
|
-
const t0 = performance.now();
|
|
142
|
-
try {
|
|
143
|
-
// getOrCreate (→ makeParser) is inside the try: with `ready` gating it
|
|
144
|
-
// can't hit the init race, but any other construction failure becomes a
|
|
145
|
-
// posted error rather than an uncaught exception that kills the worker.
|
|
146
|
-
const parser = this.getOrCreate(streamId);
|
|
147
|
-
const patch = parser.append(chunk) as Patch;
|
|
148
|
-
const dt = (performance.now() - t0) * 1000;
|
|
149
|
-
this.totalAppended.set(streamId, (this.totalAppended.get(streamId) ?? 0) + chunk.length);
|
|
150
|
-
this.emitPatch(streamId, patch, parser, dt);
|
|
151
|
-
} catch (e: unknown) {
|
|
152
|
-
this.deps.post({ type: "error", streamId, message: e instanceof Error ? e.message : String(e) });
|
|
153
|
-
}
|
|
154
|
-
}
|
|
155
|
-
}
|
|
156
|
-
|
|
157
|
-
// Drain a stream's buffered input (if any), then finalize its parser. Shared by
|
|
158
|
-
// the `finalize` message path and markReady()'s post-ready drain.
|
|
159
|
-
private doFinalize(streamId: number): void {
|
|
160
|
-
const buffered = this.pending.get(streamId);
|
|
161
|
-
this.pending.delete(streamId);
|
|
162
|
-
try {
|
|
163
|
-
const parser = this.getOrCreate(streamId);
|
|
164
|
-
if (buffered && buffered.length > 0) {
|
|
165
|
-
parser.append(buffered);
|
|
166
|
-
this.totalAppended.set(streamId, (this.totalAppended.get(streamId) ?? 0) + buffered.length);
|
|
167
|
-
}
|
|
168
|
-
const patch = parser.finalize() as Patch;
|
|
169
|
-
this.emitPatch(streamId, patch, parser, 0);
|
|
170
|
-
} catch (e: unknown) {
|
|
171
|
-
this.deps.post({ type: "error", streamId, message: e instanceof Error ? e.message : String(e) });
|
|
172
|
-
}
|
|
173
|
-
}
|
|
174
|
-
}
|
package/src/worker.ts
DELETED
|
@@ -1,72 +0,0 @@
|
|
|
1
|
-
/// <reference lib="webworker" />
|
|
2
|
-
import init, { FluxParser } from "./wasm/flux_md_core.js";
|
|
3
|
-
import type { ParserConfig } from "./types";
|
|
4
|
-
import { WorkerCore, type ParserLike } from "./worker-core";
|
|
5
|
-
|
|
6
|
-
// Resolve the WASM asset with the *web-standard* `new URL(asset,
|
|
7
|
-
// import.meta.url)` pattern (not Vite's `?url` suffix), so the package works in
|
|
8
|
-
// any bundler with asset-module support — Vite, webpack 5, Rollup, Parcel, and
|
|
9
|
-
// Next.js (Turbopack/webpack). wasm-bindgen's init() fetches a URL instance.
|
|
10
|
-
const wasmUrl = new URL("./wasm/flux_md_core_bg.wasm", import.meta.url);
|
|
11
|
-
|
|
12
|
-
const ctx: DedicatedWorkerGlobalScope = self as unknown as DedicatedWorkerGlobalScope;
|
|
13
|
-
|
|
14
|
-
// Captured from init() so we can report WASM-side memory usage on each patch.
|
|
15
|
-
let wasmExports: { memory?: { buffer?: ArrayBufferLike } } | null = null;
|
|
16
|
-
|
|
17
|
-
// The message/readiness state machine lives in WorkerCore (testable without
|
|
18
|
-
// WASM); this shell injects the WASM-specific dependencies.
|
|
19
|
-
const core = new WorkerCore({
|
|
20
|
-
// Create + configure a parser for a stream. Omitted config fields fall back to
|
|
21
|
-
// the library defaults — autolinks + alerts on (LLM output is full of bare
|
|
22
|
-
// URLs and `> [!NOTE]` blocks), raw HTML escaped, footnotes/math off.
|
|
23
|
-
makeParser(c: ParserConfig | undefined): ParserLike {
|
|
24
|
-
const p = new FluxParser();
|
|
25
|
-
p.setGfmAutolinks(c?.gfmAutolinks ?? true);
|
|
26
|
-
p.setGfmAlerts(c?.gfmAlerts ?? true);
|
|
27
|
-
p.setGfmFootnotes(c?.gfmFootnotes ?? false);
|
|
28
|
-
p.setGfmMath(c?.gfmMath ?? false);
|
|
29
|
-
p.setDirAuto(c?.dirAuto ?? false);
|
|
30
|
-
p.setA11y(c?.a11y ?? false);
|
|
31
|
-
p.setUnsafeHtml(c?.unsafeHtml ?? false);
|
|
32
|
-
p.setComponentTags(c?.componentTags ?? []);
|
|
33
|
-
p.setInlineComponentTags(c?.inlineComponentTags ?? []);
|
|
34
|
-
// Engage the safe raw-HTML sanitizer when either list is provided (even []).
|
|
35
|
-
p.setHtmlSanitize(
|
|
36
|
-
c?.htmlAllowlist !== undefined || c?.dropHtmlTags !== undefined,
|
|
37
|
-
c?.htmlAllowlist ?? [],
|
|
38
|
-
c?.dropHtmlTags ?? [],
|
|
39
|
-
);
|
|
40
|
-
p.setBlockData(c?.blockData ?? false);
|
|
41
|
-
return p;
|
|
42
|
-
},
|
|
43
|
-
post: (msg) => ctx.postMessage(msg),
|
|
44
|
-
memBytes: () => {
|
|
45
|
-
try {
|
|
46
|
-
return (wasmExports?.memory?.buffer?.byteLength as number) ?? 0;
|
|
47
|
-
} catch {
|
|
48
|
-
return 0;
|
|
49
|
-
}
|
|
50
|
-
},
|
|
51
|
-
schedule: (fn) => queueMicrotask(fn),
|
|
52
|
-
});
|
|
53
|
-
|
|
54
|
-
ctx.addEventListener("message", (ev: MessageEvent) => core.handle(ev.data));
|
|
55
|
-
|
|
56
|
-
async function setup() {
|
|
57
|
-
// init() returns the wasm-bindgen instance; capture its `.memory` export for
|
|
58
|
-
// the per-patch memory metric. No parser yet — they are created per stream,
|
|
59
|
-
// on demand, only after markReady() opens the gate.
|
|
60
|
-
try {
|
|
61
|
-
wasmExports = await init({ module_or_path: wasmUrl });
|
|
62
|
-
core.markReady();
|
|
63
|
-
} catch (e: unknown) {
|
|
64
|
-
// WASM failed to load/instantiate: this worker can never parse anything.
|
|
65
|
-
// Report it so the pool rejects whenReady() (rather than hanging forever)
|
|
66
|
-
// and clients' onError fires. streamId is irrelevant for a worker-level
|
|
67
|
-
// failure — the pool routes a fatal error to every stream it hosts.
|
|
68
|
-
ctx.postMessage({ type: "error", streamId: -1, message: e instanceof Error ? e.message : String(e), fatal: true });
|
|
69
|
-
}
|
|
70
|
-
}
|
|
71
|
-
|
|
72
|
-
setup();
|
/package/{src → dist}/styles.css
RENAMED
|
File without changes
|