brookmd 0.23.2 → 0.25.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 +104 -0
- package/README.md +83 -3
- package/dist/client.d.ts +99 -2
- package/dist/client.js +287 -26
- package/dist/dom.js +4 -1
- package/dist/html-to-react.js +29 -1
- package/dist/react.d.ts +54 -12
- package/dist/react.js +129 -20
- package/dist/server-react.js +2 -1
- package/dist/types-core.d.ts +12 -1
- package/dist/types-react.d.ts +32 -4
- package/dist/warn.d.ts +9 -0
- package/dist/warn.js +19 -0
- package/dist/wasm/brook_md_core_bg.wasm +0 -0
- package/package.json +1 -1
package/CHANGELOG.md
CHANGED
|
@@ -4,6 +4,110 @@ Notable changes to brookmd (formerly `flux-md`). Format based on
|
|
|
4
4
|
[Keep a Changelog](https://keepachangelog.com/); this project aims to follow
|
|
5
5
|
[Semantic Versioning](https://semver.org/).
|
|
6
6
|
|
|
7
|
+
## 0.25.0 — 2026-07-26
|
|
8
|
+
|
|
9
|
+
Fixes a crash class where a `components` override could be invoked with the
|
|
10
|
+
wrong prop shape and take the entire document down with it. If you pass
|
|
11
|
+
`components` and read `props.block` in any of them, upgrade.
|
|
12
|
+
|
|
13
|
+
### Fixed
|
|
14
|
+
|
|
15
|
+
- **A `components` override no longer receives two incompatible prop shapes
|
|
16
|
+
without warning.** The map is consulted by two dispatchers: the block-kind
|
|
17
|
+
dispatcher (which supplies `BlockComponentProps`, including `block`) and the
|
|
18
|
+
element-name walker that powers `a`/`code`/`table` overrides (which supplies
|
|
19
|
+
attributes + `children` and **no `block`**). The same key reaches both — an
|
|
20
|
+
`inlineComponentTags` chip, or a `componentTags` tag nested inside a list item
|
|
21
|
+
or blockquote — so an override reading `props.block.…` threw
|
|
22
|
+
`can't access property "kind", block is undefined`, intermittently, depending
|
|
23
|
+
on where the model put the tag. Three defenses now apply: block-kind keys are
|
|
24
|
+
typed to `BlockComponentProps` (a mismatched override is a compile error), a
|
|
25
|
+
raw element whose name collides with a block-kind key is never dispatched to
|
|
26
|
+
that override, and every block renders inside its own error boundary.
|
|
27
|
+
- **A throwing override costs one block, not the document.** React unmounts the
|
|
28
|
+
whole tree on an uncaught render error, so a single bad override blanked the
|
|
29
|
+
page. Each block now has its own boundary; the failed block is skipped and
|
|
30
|
+
retried as soon as its HTML changes, so a streaming-tail failure heals itself
|
|
31
|
+
when the block settles.
|
|
32
|
+
- **The parser no longer emits a component tag it will retract.** A component
|
|
33
|
+
open tag was recognized as soon as it looked whole-line, but during streaming
|
|
34
|
+
end-of-buffer is not end-of-line — so `> <Thinking>x</Thinking>` rendered a
|
|
35
|
+
raw `<Thinking>` element for one tick before settling to escaped text. That
|
|
36
|
+
transient raw element is what reached overrides with the wrong props. A
|
|
37
|
+
component tag now opens a block only once its line is known to be complete.
|
|
38
|
+
- **Recovery no longer loses the parser config.** The worker keeps config per
|
|
39
|
+
stream id, so the one-shot recovery re-feed landed on a worker that had never
|
|
40
|
+
seen it while `configSent` stayed latched — the healed parser was silently
|
|
41
|
+
rebuilt with library defaults, dropping `componentTags`, `blockData`,
|
|
42
|
+
`gfmMath` and the whole `kind.data` structured channel for the rest of the
|
|
43
|
+
session.
|
|
44
|
+
- **A terminal worker failure no longer corrupts the document.** The store kept
|
|
45
|
+
the dead generation's blocks while the fresh parser renumbered from zero, so
|
|
46
|
+
the next append merged two generations under colliding ids (duplicate React
|
|
47
|
+
keys, silently overwritten blocks, a shrinking document). The generation now
|
|
48
|
+
restarts cleanly, with a one-time warning. Transient failures still heal
|
|
49
|
+
invisibly through recovery, unchanged.
|
|
50
|
+
- **Per-block error containment is free for committed blocks.** The boundary
|
|
51
|
+
lives inside the per-block memo, so a settled document re-renders no
|
|
52
|
+
boundaries when the streaming tail patches — a new React-side complexity gate
|
|
53
|
+
(`test/boundary-linearity.test.tsx`) pins this, counting work rather than
|
|
54
|
+
timing it, mirroring the Rust `scaling` gate.
|
|
55
|
+
- `applyPatch` and the stale-view merge now drop a malformed entry rather than
|
|
56
|
+
publishing a hole into the snapshot, and both renderers skip a block with no
|
|
57
|
+
`kind` instead of dereferencing it.
|
|
58
|
+
|
|
59
|
+
### Added
|
|
60
|
+
|
|
61
|
+
- **`onBlockError`** on `<BrookMarkdown>` — fires when a block's render throws,
|
|
62
|
+
with `{ blockId, kind, componentKeys, html }`. Without it the same detail goes
|
|
63
|
+
to `console.error`.
|
|
64
|
+
|
|
65
|
+
### Changed
|
|
66
|
+
|
|
67
|
+
- `Components` is now a mapped type: block-kind keys (`CodeBlock`, `Table`,
|
|
68
|
+
`Alert`, …) are typed to `BlockComponentProps`; every other key stays
|
|
69
|
+
permissive. Type-only, but it will surface existing mismatches at compile time.
|
|
70
|
+
- Requires `brookmd-core` 0.24.0 (the streaming component-tag fix above).
|
|
71
|
+
- `onBlockError` is identity-sensitive like `components` / `onRenderMetrics` —
|
|
72
|
+
hoist or memoize it, or every block re-renders on every patch.
|
|
73
|
+
|
|
74
|
+
## 0.24.0 — 2026-07-24
|
|
75
|
+
|
|
76
|
+
### Added
|
|
77
|
+
|
|
78
|
+
- **Worker load failures are now detected and self-heal.** A worker script
|
|
79
|
+
that fails to load (e.g. a stale hashed worker URL held by an already-open
|
|
80
|
+
tab after a redeploy) fires a DOM `error` event instead of ever posting a
|
|
81
|
+
message; previously nothing listened, so the client waited forever and the
|
|
82
|
+
container stayed permanently empty with no console output. The pool now
|
|
83
|
+
listens for `error`/`messageerror`, arms a per-worker boot deadline
|
|
84
|
+
(default 20s, configurable/disable-able via the `BrookPool` options), and
|
|
85
|
+
routes every fatal trigger — WASM init failure, load error, deserialization
|
|
86
|
+
error, deadline — through one idempotent failure path: pending waiters
|
|
87
|
+
reject, each affected client's `onError` fires, and the dead worker is
|
|
88
|
+
terminated and evicted so the pool capacity recovers.
|
|
89
|
+
- **One-shot automatic recovery.** A client whose worker dies transiently
|
|
90
|
+
heals invisibly: the driven document accumulates in a recovery buffer (both
|
|
91
|
+
`setContent` and `append`/`pipeFrom` modes) and is re-fed once to a fresh
|
|
92
|
+
worker through the preserved-view swap path, so the rendered view never
|
|
93
|
+
blanks and in-flight chunks are folded in safely. Recovery re-arms only
|
|
94
|
+
when the caller advances the content, so a document that deterministically
|
|
95
|
+
crashes the parser surfaces an error after exactly one retry instead of
|
|
96
|
+
respawning workers. Opt out with `new BrookClient({ recovery: false })`
|
|
97
|
+
for memory-sensitive giant documents.
|
|
98
|
+
- **`client.failed: Error | null`** — synchronous getter for terminal worker
|
|
99
|
+
failure (stays `null` through a successful invisible heal), for rendering a
|
|
100
|
+
degraded fallback.
|
|
101
|
+
- **React hooks error surface.** `useBrookStream`'s `onError` now also
|
|
102
|
+
receives worker-level errors (with a `fatal` flag on the `Error`), and
|
|
103
|
+
`useBrookMarkdownString` gains an `onError` option; both default to
|
|
104
|
+
`console.error`.
|
|
105
|
+
|
|
106
|
+
### Fixed
|
|
107
|
+
|
|
108
|
+
- Fatally failed workers no longer leak their per-stream handler-map entries
|
|
109
|
+
in the pool.
|
|
110
|
+
|
|
7
111
|
## 0.23.2 — 2026-07-22
|
|
8
112
|
|
|
9
113
|
### Fixed
|
package/README.md
CHANGED
|
@@ -555,7 +555,9 @@ class BrookClient {
|
|
|
555
555
|
config?: ParserConfig;
|
|
556
556
|
onError?: (err: { message: string; fatal?: boolean }) => void; // worker/parse + WASM-init errors
|
|
557
557
|
onBlock?: (block: Block) => void; // fires once per block as it commits
|
|
558
|
+
recovery?: boolean; // auto-heal a transient worker death (default true)
|
|
558
559
|
});
|
|
560
|
+
get failed(): Error | null; // terminal worker failure, else null (null through heals)
|
|
559
561
|
append(chunk: string): void; // queue text for parsing
|
|
560
562
|
pipeFrom( // read → append → finalize
|
|
561
563
|
src: ReadableStream<Uint8Array> | Response | AsyncIterable<string>,
|
|
@@ -591,6 +593,16 @@ failure (`{ fatal: true }`); without it, errors are only `console.error`'d and a
|
|
|
591
593
|
load failure surfaces as a rejected `whenReady()`. Pass `onBlock` to run a side
|
|
592
594
|
effect each time a block commits (e.g. lazy-highlight a finished code block).
|
|
593
595
|
|
|
596
|
+
A **transient worker death** heals invisibly by default: if a worker dies
|
|
597
|
+
mid-stream (e.g. a stale hashed worker URL 404s after a redeploy), the client
|
|
598
|
+
buffers the driven document, re-acquires a fresh worker, and re-feeds it once —
|
|
599
|
+
the view stays on screen and `onError` does **not** fire. Only if the replacement
|
|
600
|
+
also dies is the failure terminal (`onError` with `{ fatal: true }`, and
|
|
601
|
+
`client.failed` becomes the `Error`; it is `null` while healthy and through a
|
|
602
|
+
successful heal). Set `recovery: false` to disable the buffer and auto-recovery
|
|
603
|
+
(a fatal death is then immediately terminal) — worth it for memory-sensitive,
|
|
604
|
+
very large documents where retaining the full source is undesirable.
|
|
605
|
+
|
|
594
606
|
#### Per-stream config
|
|
595
607
|
|
|
596
608
|
```ts
|
|
@@ -754,6 +766,33 @@ block. The component receives [`BlockComponentProps`](#types): `{ block, html,
|
|
|
754
766
|
open, speculative }`, plus `text`/`language` for code/math blocks (the alert
|
|
755
767
|
type is at `block.kind.data.kind`).
|
|
756
768
|
|
|
769
|
+
> **One map, two prop contracts — the single biggest footgun.** The keys above
|
|
770
|
+
> are looked up by TWO dispatchers. The block-kind dispatcher passes
|
|
771
|
+
> `BlockComponentProps` (with `block`); the element dispatcher, which is what
|
|
772
|
+
> makes `a` / `code` / `table` overrides work, passes **the element's attributes
|
|
773
|
+
> and `children` only — no `block`**. The same name can hit both: an
|
|
774
|
+
> `inlineComponentTags` chip, or a `componentTags` tag that lands inside a list
|
|
775
|
+
> item or blockquote (where it is a real *nested* Component block, rendered as an
|
|
776
|
+
> element inside its container's HTML), takes the element path. So an override
|
|
777
|
+
> that reads `props.block.…` throws `can't access property "kind", block is
|
|
778
|
+
> undefined` for those occurrences — intermittently, because it depends on where
|
|
779
|
+
> the model happened to put the tag.
|
|
780
|
+
>
|
|
781
|
+
> Write any override for a name that can appear in both positions defensively:
|
|
782
|
+
>
|
|
783
|
+
> ```tsx
|
|
784
|
+
> const Thinking = ({ block, children }) =>
|
|
785
|
+
> block ? <Panel data={block.kind.data}>{children}</Panel> : <span>{children}</span>;
|
|
786
|
+
> ```
|
|
787
|
+
>
|
|
788
|
+
> Three things make this survivable rather than fatal: block-kind keys are typed
|
|
789
|
+
> to `BlockComponentProps`, so a mismatched override is a **compile** error; a raw
|
|
790
|
+
> element whose name collides with a block-kind key (`<Table>`, `<Alert>`… — only
|
|
791
|
+
> reachable with raw-HTML passthrough on) is never dispatched to that override;
|
|
792
|
+
> and every block renders inside its own **error boundary**, so a throwing
|
|
793
|
+
> override costs that one block instead of unmounting the document. Wire
|
|
794
|
+
> [`onBlockError`](#onblockerror) to see them.
|
|
795
|
+
|
|
757
796
|
Rules worth knowing:
|
|
758
797
|
|
|
759
798
|
- **There is no `node` prop / no hast tree.** Introspect via `className` /
|
|
@@ -916,9 +955,23 @@ tags only). It works everywhere inline content does — **including table cells*
|
|
|
916
955
|
Tag names match **case-sensitively** and dispatch verbatim to `components[tag]`
|
|
917
956
|
(`<tik>`→`components.tik`, `<Cite>`→`components.Cite`). The
|
|
918
957
|
two lists are independent: list a tag under `componentTags` for blocks,
|
|
919
|
-
`inlineComponentTags` for inline, or both for both.
|
|
920
|
-
|
|
921
|
-
|
|
958
|
+
`inlineComponentTags` for inline, or both for both.
|
|
959
|
+
|
|
960
|
+
Where an allowlisted tag actually lands:
|
|
961
|
+
|
|
962
|
+
| Position | Result |
|
|
963
|
+
| --- | --- |
|
|
964
|
+
| Own line, top level | block `Component` — override gets `BlockComponentProps` |
|
|
965
|
+
| Own line inside a list item / blockquote | real **nested** Component block, emitted as an element inside the container's HTML — the override is dispatched by **element name**, so it gets attributes + `children` and **no `block`** |
|
|
966
|
+
| Mid-paragraph, listed in `inlineComponentTags` | inline element — attributes + `children`, no `block` |
|
|
967
|
+
| Mid-paragraph, NOT listed in `inlineComponentTags` | escaped text |
|
|
968
|
+
| Inside a table cell | escaped text (cells are inline-only) |
|
|
969
|
+
|
|
970
|
+
An allowlisted tag in a position that is not supported degrades **inertly**
|
|
971
|
+
(escaped) — it never consumes surrounding content. But note rows 2 and 3: those
|
|
972
|
+
DO render, through the element path, which is why an override that reads
|
|
973
|
+
`props.block` must guard for its absence (see [the two prop
|
|
974
|
+
contracts](#custom-components--overrides)).
|
|
922
975
|
|
|
923
976
|
> **Link-bridge alternative.** Before `inlineComponentTags`, the way to get an
|
|
924
977
|
> inline custom element was the link bridge: emit `[$AAPL](tik://AAPL)` and
|
|
@@ -1257,6 +1310,33 @@ re-snapping during streaming, so treat smooth following there as best-effort.
|
|
|
1257
1310
|
> is the *shared* worker's heap — clients on the same worker report the same
|
|
1258
1311
|
> value. Aggregate with `Math.max`, not a sum.
|
|
1259
1312
|
|
|
1313
|
+
<a id="onblockerror"></a>
|
|
1314
|
+
|
|
1315
|
+
### When a block fails to render — `onBlockError`
|
|
1316
|
+
|
|
1317
|
+
Every block renders inside its own error boundary. React's default response to
|
|
1318
|
+
an uncaught render error is to unmount the **entire** tree, so before this a
|
|
1319
|
+
single throwing `components` override blanked the whole document. Now the
|
|
1320
|
+
failure costs that one block: the rest of the stream keeps rendering, and the
|
|
1321
|
+
block is retried as soon as its HTML changes (so a streaming-tail failure heals
|
|
1322
|
+
itself when the block settles).
|
|
1323
|
+
|
|
1324
|
+
```tsx
|
|
1325
|
+
<BrookMarkdown
|
|
1326
|
+
client={client}
|
|
1327
|
+
components={components}
|
|
1328
|
+
onBlockError={(err, { blockId, kind, componentKeys, html }) => {
|
|
1329
|
+
reportToSentry(err, { blockId, kind, componentKeys, html });
|
|
1330
|
+
}}
|
|
1331
|
+
/>
|
|
1332
|
+
```
|
|
1333
|
+
|
|
1334
|
+
`componentKeys` is the override map's keys — the shortlist of suspects — and
|
|
1335
|
+
`html` is the first 200 chars of the block that failed. Without the hook the same
|
|
1336
|
+
detail goes to `console.error`. The overwhelmingly common cause is an override
|
|
1337
|
+
reading `props.block.…` on the element path, where there is no `block`; see [the
|
|
1338
|
+
two prop contracts](#custom-components--overrides).
|
|
1339
|
+
|
|
1260
1340
|
## Architecture
|
|
1261
1341
|
|
|
1262
1342
|
```
|
package/dist/client.d.ts
CHANGED
|
@@ -30,7 +30,9 @@ export declare function applyPatch(store: BlockStore, patch: Patch): void;
|
|
|
30
30
|
interface PoolWorker {
|
|
31
31
|
worker: WorkerLike;
|
|
32
32
|
ready: boolean;
|
|
33
|
-
/** Set once
|
|
33
|
+
/** Set once the worker fails fatally (WASM init, a DOM load `error`, a
|
|
34
|
+
* `messageerror`, or the boot deadline); whenWorkerReady rejects with this
|
|
35
|
+
* thereafter. */
|
|
34
36
|
failed: Error | null;
|
|
35
37
|
streamCount: number;
|
|
36
38
|
/** Live stream ids on this worker — so a fatal failure can notify each one. */
|
|
@@ -39,6 +41,10 @@ interface PoolWorker {
|
|
|
39
41
|
resolve: () => void;
|
|
40
42
|
reject: (e: Error) => void;
|
|
41
43
|
}>;
|
|
44
|
+
/** Handle for the boot deadline that fails a worker which never reports ready.
|
|
45
|
+
* Opaque (a `number` in the browser, a `Timeout` in Node/bun, or a test
|
|
46
|
+
* fake's id) — cleared on ready, on failure, and on pool disposal. */
|
|
47
|
+
bootTimer: unknown;
|
|
42
48
|
}
|
|
43
49
|
/**
|
|
44
50
|
* A pool of Web Workers, each multiplexing many `BrookParser`s keyed by stream
|
|
@@ -59,7 +65,14 @@ export declare class BrookPool {
|
|
|
59
65
|
private workers;
|
|
60
66
|
private handlers;
|
|
61
67
|
private nextStreamId;
|
|
62
|
-
|
|
68
|
+
private bootTimeoutMs;
|
|
69
|
+
private startTimer;
|
|
70
|
+
private cancelTimer;
|
|
71
|
+
constructor(factory: () => WorkerLike, cap: number, options?: {
|
|
72
|
+
bootTimeoutMs?: number;
|
|
73
|
+
setTimeout?: (fn: () => void, ms: number) => unknown;
|
|
74
|
+
clearTimeout?: (handle: unknown) => void;
|
|
75
|
+
});
|
|
63
76
|
/** Reserve a stream id and assign a worker, registering its message handler. */
|
|
64
77
|
acquire(handler: (msg: FromWorker) => void): {
|
|
65
78
|
streamId: number;
|
|
@@ -88,9 +101,30 @@ export declare class BrookPool {
|
|
|
88
101
|
/** Terminate every worker (test teardown / full shutdown). */
|
|
89
102
|
disposeAll(): void;
|
|
90
103
|
get workerCount(): number;
|
|
104
|
+
/** Live stream→handler registrations. Introspection for tests/diagnostics —
|
|
105
|
+
* a fatal failure reaps the dead worker's entries, so this must not grow
|
|
106
|
+
* across a worker death + recovery cycle. */
|
|
107
|
+
get handlerCount(): number;
|
|
91
108
|
private pick;
|
|
92
109
|
private create;
|
|
110
|
+
private startBootTimer;
|
|
111
|
+
private clearBootTimer;
|
|
93
112
|
private onMessage;
|
|
113
|
+
/**
|
|
114
|
+
* Idempotent fatal-failure handler shared by every trigger: an in-band
|
|
115
|
+
* `{type:"error",fatal:true}` (WASM init), a DOM load `error`, a
|
|
116
|
+
* `messageerror`, and the boot deadline. First cause wins; later calls no-op.
|
|
117
|
+
*
|
|
118
|
+
* A fatally failed worker dooms every stream on it. Reject anyone awaiting
|
|
119
|
+
* readiness, then dispatch a synthetic fatal error to each live stream so its
|
|
120
|
+
* client's `onError` fires exactly as for a WASM-init fatal (the message
|
|
121
|
+
* carries no real streamId to route by). Finally evict the worker: terminate
|
|
122
|
+
* it and drop it from the pool — a dead worker can never parse again, so
|
|
123
|
+
* retaining it would leak an OS thread per failure and keep counting against
|
|
124
|
+
* `cap` until pick()'s cap branch dies and spawns workers unbounded. Reaping
|
|
125
|
+
* restores the cap and lets a fresh worker be made.
|
|
126
|
+
*/
|
|
127
|
+
private fail;
|
|
94
128
|
private dispatch;
|
|
95
129
|
}
|
|
96
130
|
/** The process-wide default pool every `BrookClient` shares unless given one. */
|
|
@@ -122,6 +156,9 @@ export declare class BrookClient {
|
|
|
122
156
|
private streamId;
|
|
123
157
|
private config?;
|
|
124
158
|
private configSent;
|
|
159
|
+
/** Set when ensureAcquired rebound this stream onto a fresh worker+parser
|
|
160
|
+
* after a fatal failure; consumed by the next content-bearing op. */
|
|
161
|
+
private pendingRebind;
|
|
125
162
|
private listeners;
|
|
126
163
|
private store;
|
|
127
164
|
private onError?;
|
|
@@ -129,6 +166,11 @@ export declare class BrookClient {
|
|
|
129
166
|
private attached;
|
|
130
167
|
private lastContent;
|
|
131
168
|
private contentDone;
|
|
169
|
+
private failedError;
|
|
170
|
+
private recovery;
|
|
171
|
+
private recoveryBuffer;
|
|
172
|
+
private recoveredLen;
|
|
173
|
+
private recoveryAttempted;
|
|
132
174
|
private coalesce;
|
|
133
175
|
private rafHandle;
|
|
134
176
|
private finalizePending;
|
|
@@ -137,6 +179,9 @@ export declare class BrookClient {
|
|
|
137
179
|
private staleTrimmed;
|
|
138
180
|
private idNamespace;
|
|
139
181
|
private mergeCache;
|
|
182
|
+
/** Set by mergeStale when it had to compact a hole out of the view, so
|
|
183
|
+
* getSnapshot skips caching a view whose indices no longer track `base`. */
|
|
184
|
+
private mergeDropped;
|
|
140
185
|
private appendedBytes;
|
|
141
186
|
private patchCount;
|
|
142
187
|
private totalParseMicros;
|
|
@@ -170,6 +215,14 @@ export declare class BrookClient {
|
|
|
170
215
|
* stream-completion (finalize) patch always flushes synchronously, and a
|
|
171
216
|
* pending frame is cancelled on `reset()`/`destroy()`. No effect when
|
|
172
217
|
* `requestAnimationFrame` is unavailable (e.g. SSR) — emits stay synchronous.
|
|
218
|
+
* @param options.recovery opt-out (default `true`): transparently heal a
|
|
219
|
+
* TRANSIENT worker death. The client buffers the full driven document and, on
|
|
220
|
+
* a fatal worker failure, re-acquires a fresh worker and re-feeds it exactly
|
|
221
|
+
* once — the displayed view stays on screen, so a worker that 404s after a
|
|
222
|
+
* redeploy (or otherwise dies mid-stream) recovers invisibly instead of
|
|
223
|
+
* freezing the render. If the replacement ALSO dies the error surfaces
|
|
224
|
+
* (`failed` / `onError`). Set `false` to disable both the buffering and the
|
|
225
|
+
* auto-recovery — a fatal failure then goes straight to terminal.
|
|
173
226
|
*/
|
|
174
227
|
constructor(options?: {
|
|
175
228
|
pool?: BrookPool;
|
|
@@ -180,6 +233,7 @@ export declare class BrookClient {
|
|
|
180
233
|
}) => void;
|
|
181
234
|
onBlock?: (block: Block) => void;
|
|
182
235
|
coalesce?: boolean;
|
|
236
|
+
recovery?: boolean;
|
|
183
237
|
});
|
|
184
238
|
/**
|
|
185
239
|
* Lazily reserve this client's stream id and bind it to a pool worker. The
|
|
@@ -197,7 +251,27 @@ export declare class BrookClient {
|
|
|
197
251
|
* multiplexing (pick() is unchanged and remains the only path to create()).
|
|
198
252
|
*/
|
|
199
253
|
private ensureAcquired;
|
|
254
|
+
/**
|
|
255
|
+
* A worker rebind left the store holding a dead generation's blocks while the
|
|
256
|
+
* fresh parser restarts ids at 0. Called by the ops that actually feed the
|
|
257
|
+
* parser, before they do. Recovery's re-feed handles this itself (it re-parses
|
|
258
|
+
* the whole document over a preserved view) and clears the flag; this is the
|
|
259
|
+
* path where recovery is off or already spent, where the honest outcome is a
|
|
260
|
+
* clean restart rather than two generations interleaved under colliding ids.
|
|
261
|
+
*/
|
|
262
|
+
private settleRebind;
|
|
200
263
|
get ready(): boolean;
|
|
264
|
+
/**
|
|
265
|
+
* The fatal error that killed this client's worker, or `null` if healthy.
|
|
266
|
+
*
|
|
267
|
+
* Non-null only once a failure is TERMINAL: a worker that died with recovery
|
|
268
|
+
* off or nothing buffered to re-feed, or a client (either mode) whose one-shot
|
|
269
|
+
* auto-recovery re-feed ALSO hit a dying worker. It stays `null` throughout a
|
|
270
|
+
* successful transient recovery (the death heals invisibly) and is cleared
|
|
271
|
+
* again by {@link reset}. Pairs with `onError`, which fires on the same
|
|
272
|
+
* terminal failure.
|
|
273
|
+
*/
|
|
274
|
+
get failed(): Error | null;
|
|
201
275
|
whenReady(): Promise<void>;
|
|
202
276
|
private firstConfig;
|
|
203
277
|
append(chunk: string): void;
|
|
@@ -354,6 +428,29 @@ export declare class BrookClient {
|
|
|
354
428
|
*/
|
|
355
429
|
toPlaintext(): string;
|
|
356
430
|
private onMessage;
|
|
431
|
+
private reportError;
|
|
432
|
+
/**
|
|
433
|
+
* One-shot self-heal after a transient worker death, for BOTH drive modes.
|
|
434
|
+
* The dead worker was already evicted, so redriving the buffered document
|
|
435
|
+
* re-acquires a FRESH worker (ensureAcquired re-acquires because the old
|
|
436
|
+
* `pw.failed` is set). Reads the buffer at EXECUTION time, so a chunk that
|
|
437
|
+
* interleaved ahead of this microtask is included. Deliberately does NOT route
|
|
438
|
+
* through setContent(): that would stamp `lastContent`, flipping an append-mode
|
|
439
|
+
* client into setContent mode, and a second death would then re-feed a stale
|
|
440
|
+
* `lastContent` missing post-recovery chunks. `recoveryAttempted` is NOT reset
|
|
441
|
+
* here — if the replacement also dies before healing, the fatal path sees the
|
|
442
|
+
* flag still set and surfaces the error instead of looping.
|
|
443
|
+
*/
|
|
444
|
+
private recover;
|
|
445
|
+
/**
|
|
446
|
+
* Rebuild the parser onto a fresh worker and re-feed `doc` as one atomic
|
|
447
|
+
* append (re-accumulating recoveryBuffer). Keeps the displayed view on screen
|
|
448
|
+
* across the swap by softReset-ing when something is rendered, so the document
|
|
449
|
+
* never blanks; falls back to a bare resetParser when the store is empty.
|
|
450
|
+
* Uses resetParser / softReset (NOT reset(), which would clear the one-shot
|
|
451
|
+
* recovery guards mid-heal). Re-finalizes when the buffered doc was finalized.
|
|
452
|
+
*/
|
|
453
|
+
private refeed;
|
|
357
454
|
/**
|
|
358
455
|
* Notify subscribers of a new snapshot.
|
|
359
456
|
*
|