@demlik/tea 0.5.1 → 0.7.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/dist/agent/index.d.ts +6 -5
- package/dist/agent/index.js +10 -10
- package/dist/authed-call/index.d.ts +2 -2
- package/dist/authed-call/index.js +7 -7
- package/dist/await-terminal/index.d.ts +3 -2
- package/dist/await-terminal/index.js +2 -2
- package/dist/batch-window/index.d.ts +4 -3
- package/dist/batch-window/index.js +3 -3
- package/dist/cache/index.d.ts +2 -2
- package/dist/cache/index.js +4 -4
- package/dist/chart/index.d.ts +934 -0
- package/dist/chart/index.js +244 -0
- package/dist/chart/index.js.map +1 -0
- package/dist/{chunk-2UGTDYOK.js → chunk-3JU6657S.js} +3 -3
- package/dist/{chunk-2UGTDYOK.js.map → chunk-3JU6657S.js.map} +1 -1
- package/dist/{chunk-AZ3NHUPD.js → chunk-4KTWLPL5.js} +3 -3
- package/dist/{chunk-AZ3NHUPD.js.map → chunk-4KTWLPL5.js.map} +1 -1
- package/dist/{chunk-ZH454CR5.js → chunk-5EHLEUTL.js} +24 -4
- package/dist/chunk-5EHLEUTL.js.map +1 -0
- package/dist/{chunk-WF46FABO.js → chunk-7SVIRMC6.js} +3 -3
- package/dist/{chunk-WF46FABO.js.map → chunk-7SVIRMC6.js.map} +1 -1
- package/dist/{chunk-XMHZ44QC.js → chunk-7V4EVLZA.js} +4 -4
- package/dist/{chunk-XMHZ44QC.js.map → chunk-7V4EVLZA.js.map} +1 -1
- package/dist/{chunk-IVHQCVFX.js → chunk-A56OR5RF.js} +3 -3
- package/dist/{chunk-IVHQCVFX.js.map → chunk-A56OR5RF.js.map} +1 -1
- package/dist/{chunk-O7ED34JH.js → chunk-B5VXOFMZ.js} +47 -15
- package/dist/chunk-B5VXOFMZ.js.map +1 -0
- package/dist/{chunk-EC6FO42Q.js → chunk-EUF7XOEE.js} +6 -6
- package/dist/{chunk-EC6FO42Q.js.map → chunk-EUF7XOEE.js.map} +1 -1
- package/dist/{chunk-XOAP3LOT.js → chunk-I5OV65MS.js} +3 -3
- package/dist/{chunk-XOAP3LOT.js.map → chunk-I5OV65MS.js.map} +1 -1
- package/dist/{chunk-ASX7PL2K.js → chunk-JGVL3WPL.js} +3 -3
- package/dist/{chunk-ASX7PL2K.js.map → chunk-JGVL3WPL.js.map} +1 -1
- package/dist/{chunk-S6Y3RNX4.js → chunk-JP5D6GSL.js} +4 -4
- package/dist/{chunk-S6Y3RNX4.js.map → chunk-JP5D6GSL.js.map} +1 -1
- package/dist/{chunk-ERYY5P6W.js → chunk-ND3DH6KM.js} +3 -3
- package/dist/{chunk-ERYY5P6W.js.map → chunk-ND3DH6KM.js.map} +1 -1
- package/dist/{chunk-A7USKARI.js → chunk-SUST46KO.js} +3 -3
- package/dist/{chunk-A7USKARI.js.map → chunk-SUST46KO.js.map} +1 -1
- package/dist/{chunk-2KBEIGYP.js → chunk-TP5UPGLY.js} +3 -3
- package/dist/{chunk-2KBEIGYP.js.map → chunk-TP5UPGLY.js.map} +1 -1
- package/dist/{chunk-66S2KEQQ.js → chunk-WLGO64WC.js} +4 -4
- package/dist/{chunk-66S2KEQQ.js.map → chunk-WLGO64WC.js.map} +1 -1
- package/dist/{chunk-UV7VCTNC.js → chunk-ZF6WDK2D.js} +7 -7
- package/dist/{chunk-UV7VCTNC.js.map → chunk-ZF6WDK2D.js.map} +1 -1
- package/dist/{core-JPHsMZVB.d.ts → core-Bjc0bS8j.d.ts} +37 -1
- package/dist/deadline/index.d.ts +2 -2
- package/dist/deadline/index.js +3 -3
- package/dist/do/index.d.ts +7 -6
- package/dist/do/index.js +10 -10
- package/dist/{durable-effects-MRnCBCxV.d.ts → durable-effects-D8SqU_if.d.ts} +1 -1
- package/dist/extension/index.d.ts +3 -2
- package/dist/extension/index.js +3 -3
- package/dist/extension/react.js +3 -3
- package/dist/extension/subs/index.d.ts +2 -2
- package/dist/fan-out/index.d.ts +3 -2
- package/dist/idempotent-intake/index.d.ts +3 -2
- package/dist/index.d.ts +5 -4
- package/dist/index.js +2 -2
- package/dist/llm-call/index.d.ts +2 -2
- package/dist/llm-call/index.js +7 -7
- package/dist/{machine-nGXCMyHn.d.ts → machine-0OmPvsy_.d.ts} +1 -1
- package/dist/machine-viz/index.d.ts +1 -1
- package/dist/machine-viz/index.js +2 -2
- package/dist/mem/index.d.ts +3 -2
- package/dist/monitored-run/index.d.ts +4 -3
- package/dist/monitored-run/index.js +5 -5
- package/dist/node/index.d.ts +3 -2
- package/dist/paginated-walk/index.d.ts +2 -2
- package/dist/paginated-walk/index.js +7 -7
- package/dist/parity/index.d.ts +31 -2
- package/dist/parity/index.js +3 -3
- package/dist/parity/index.js.map +1 -1
- package/dist/pbt/arbitraries/index.d.ts +1 -1
- package/dist/pbt/index.d.ts +1 -1
- package/dist/pbt/index.js +3 -3
- package/dist/pbt/runners/index.d.ts +1 -1
- package/dist/pbt/runners/index.js +3 -3
- package/dist/poller/index.d.ts +28 -7
- package/dist/poller/index.js +3 -3
- package/dist/poller/index.js.map +1 -1
- package/dist/prediction/index.d.ts +1 -1
- package/dist/prediction/index.js +2 -2
- package/dist/pure/index.d.ts +1 -1
- package/dist/pure/index.js +2 -2
- package/dist/react/index.d.ts +3 -2
- package/dist/react/index.js +2 -2
- package/dist/reconciler/index.d.ts +2 -2
- package/dist/reconciler/index.js +7 -7
- package/dist/recorder/index.d.ts +3 -2
- package/dist/resilient-call/index.d.ts +2 -2
- package/dist/resilient-call/index.js +6 -6
- package/dist/{runtime-types-BbBq4LWo.d.ts → runtime-types-Bxc637bp.d.ts} +53 -2
- package/dist/saga/index.d.ts +3 -2
- package/dist/snapshot/index.d.ts +1 -1
- package/dist/snapshot/index.js +3 -3
- package/dist/subs/index.d.ts +4 -3
- package/dist/subs/index.js +3 -3
- package/dist/testing/index.d.ts +3 -2
- package/dist/testing/index.js +2 -2
- package/dist/throttled-input/index.d.ts +2 -2
- package/dist/throttled-input/index.js +6 -6
- package/dist/token-refresh/index.d.ts +1 -1
- package/dist/token-refresh/index.js +3 -3
- package/dist/trace-replay/index.d.ts +3 -2
- package/dist/trace-replay/index.js +3 -3
- package/dist/{types-BMZ7IMrd.d.ts → types-D7u9DkH7.d.ts} +1 -1
- package/dist/with-deadline/index.d.ts +1 -1
- package/dist/with-deadline/index.js +2 -2
- package/dist/with-resilience/index.d.ts +2 -2
- package/dist/with-resilience/index.js +6 -6
- package/dist/with-telemetry/index.d.ts +1 -1
- package/dist/with-telemetry/index.js +2 -2
- package/dist/work-queue/adapter.d.ts +3 -2
- package/dist/work-queue/index.d.ts +3 -2
- package/dist/work-queue/ops.d.ts +3 -2
- package/dist/workflow/index.d.ts +6 -4
- package/dist/workflow/index.js +1 -1
- package/dist/workflow/index.js.map +1 -1
- package/package.json +8 -1
- package/dist/chunk-O7ED34JH.js.map +0 -1
- package/dist/chunk-ZH454CR5.js.map +0 -1
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"sources":["../src/observability.ts","../src/runtime-types.ts","../src/run.ts"],"names":["opts"],"mappings":"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","file":"chunk-5EHLEUTL.js","sourcesContent":["import type { BootingRuntime } from \"./runtime-types\";\n\n// === historyTracker: composable observability helper ===\n//\n// A bounded ring buffer of recent `(msg, state)` transitions, built on the\n// Runtime's public `observe` API. The substrate itself stays minimal — it has\n// no notion of history, no opt to enable one, no method on the Runtime\n// interface to read one. Anything that wants \"recent transitions\" composes via\n// this helper.\n//\n// Why not put this on the Runtime interface as `runtime.history()`? Because\n// history is derivable. Putting it on the Runtime privileges one use case\n// (debug inspectors) at the substrate level and grows the interface for\n// everyone; the composition shape stays out of the dispatch loop entirely, so\n// the substrate's hot path pays no cost for a feature it doesn't need.\n\nexport interface HistoryTracker<S, M extends { type: string }> {\n /**\n * Snapshot of recorded transitions, oldest first. Each entry is\n * `{ msg, state }`: `msg` is `null` for the boot transition (recorded via\n * `onBoot`) and the applied Msg for every other (recorded via `observe`).\n *\n * Returns a shallow copy: the array is fresh on every call (callers may\n * iterate, slice, or replay without affecting the tracker's buffer).\n * Entry values are references to the originals (TEA states/msgs are\n * conventionally immutable; do not mutate them if your S/M is not).\n */\n snapshot(): readonly { readonly msg: M | null; readonly state: S }[];\n /**\n * Detach the underlying observer. After `stop()`:\n * - No new transitions are recorded.\n * - `snapshot()` continues to work and returns the buffer's contents\n * at the moment of stop (useful for post-hoc inspection in tests).\n * - The tracker holds no references that would prevent GC of the\n * `runtime` argument other than the entries already buffered.\n *\n * Idempotent — subsequent calls are no-ops.\n */\n stop(): void;\n}\n\n/**\n * Create a bounded history tracker over a Runtime. The tracker subscribes to\n * the runtime via `observe(...)` and retains the last `size` `(msg, state)`\n * transitions in a FIFO ring buffer.\n *\n * @param runtime Any `BootingRuntime<S, M>` (a full `Runtime` satisfies it).\n * The tracker uses only `observe`, which is total before boot —\n * attach it to the synchronous `run()` handle to record the\n * boot transition.\n * @param size Buffer cap. `size <= 0` produces an inert no-op tracker:\n * no observer is attached to the runtime, `snapshot()` is\n * always `[]`, and `stop()` is a no-op.\n */\nexport function historyTracker<S, M extends { type: string }>(\n runtime: BootingRuntime<S, M>,\n size: number,\n): HistoryTracker<S, M> {\n const buffer: { msg: M | null; state: S }[] = [];\n let stopped = false;\n\n const push = (entry: { msg: M | null; state: S }): void => {\n buffer.push(entry);\n if (buffer.length > size) buffer.shift();\n };\n\n // The boot transition (the `{ msg: null, state }` head entry) arrives via\n // `onBoot`; applied transitions arrive via `observe` with a total `msg`. The\n // public snapshot shape (`msg: M | null`) is unchanged — boot is still\n // `msg: null`.\n const unobserve =\n size <= 0\n ? (): void => {}\n : runtime.observe((msg, state) => {\n push({ msg, state });\n });\n const unboot =\n size <= 0\n ? (): void => {}\n : runtime.onBoot((state) => {\n push({ msg: null, state });\n });\n\n return {\n snapshot() {\n // Shallow copy — callers may freely iterate or mutate the returned\n // array. Entry values are references; see HistoryTracker docs.\n return buffer.slice();\n },\n stop() {\n if (stopped) return;\n stopped = true;\n unobserve();\n unboot();\n },\n };\n}\n","/**\n * @demlik/tea runtime interface surface + pure helpers — the public types,\n * interfaces, and construction/composition helpers the runtime (`./run`)\n * implements against: `Store`, the error-sink and supervision contracts, the\n * `RuntimeRef` / `BootingRuntime` / `Runtime` handle hierarchy, `definePort`, the\n * identity-typed constructors `defineMachine` / `asReducer`, and the pure tools\n * `replay` (compose without running) and `tryInterpret` (Railway).\n */\n\nimport { Result } from \"better-result\";\nimport type {\n InterpretDetached,\n Machine,\n Port,\n PortEmitter,\n Reducer,\n Sub,\n Transitions,\n} from \"./pure/core\";\nimport {\n __DEV__,\n assertPureResult,\n type Cmd,\n deepFreeze,\n depsInactive,\n detectUpdateForm,\n foldUpdates,\n lookupCell,\n NoCellError,\n structuralHash,\n type UpdateForm,\n} from \"./pure/core\";\n\n// The nominal brand minted ONLY through the validated construction path\n// (`asReducer` / `defineMachine`). A raw record of handlers is a structural\n// `Reducer<S, M, C>`; a branded one has passed through the single minting\n// boundary. The brand is an *optional* phantom so the structural annotation form\n// (`const update: Reducer<...> = { ... }`) keeps accepting plain object literals\n// — the guard that rejects an async reducer is `SyncReturn`'s non-thenable\n// return, enforced on every cell.\ndeclare const ReducerBrand: unique symbol;\n\n// A `Reducer` (or `Transitions`) minted through `asReducer` / `defineMachine`.\n// Carries the phantom brand; otherwise identical to its structural counterpart.\nexport type Branded<T> = T & { readonly [ReducerBrand]: true };\n\n/**\n * Compile-time exhaustiveness assertion for default branches of switches over\n * discriminated unions narrowed by hand (Sub handlers, message-bridge\n * dispatchers, port fanouts) — TS narrows the operand to `never` only if every\n * variant is covered, so adding one produces a compile error at the `absurd(x)`\n * site. Strengthens invariant 7 (identity is explicit).\n *\n * @example\n * switch (msg.type) {\n * case \"a\": return ...;\n * case \"b\": return ...;\n * default: return absurd(msg); // compile error if a \"c\" is added\n * }\n */\nexport function absurd(x: never): never {\n throw new Error(`unreachable: ${JSON.stringify(x)}`);\n}\n\n// The explicit \"this cell ignores this msg\" helper. Naming the no-op forces the\n// author to *decide* \"this cell does nothing\" (a real decision), distinct from\n// \"I forgot to write this cell\" (which the mapped type rejects); an implicit\n// wildcard would silently swallow a future Msg variant that should be handled.\n// Assignable to any Transitions cell regardless of S/M/C concrete types.\nexport const noop = <S, M, C extends Cmd>(\n state: S,\n _msg: M,\n): readonly [S, readonly C[]] => [state, []];\n\n// Module-level registry — process-scope, identity-by-name. The runtime witness\n// that every port name in this process is unique. Cleared only by the test-only\n// `__resetPortRegistry()` helper.\nconst definedPortNames = new Set<string>();\n\n/**\n * Thrown by `definePort` when a name has already been registered in the current\n * process. Symmetric with `SubIdCollisionError` (thrown by `reconcileSubs`) —\n * both mechanize invariant 7 (identity is explicit) at the runtime layer the\n * type system cannot reach (string names compared at runtime).\n */\nexport class PortNameCollisionError extends Error {\n override readonly name = \"PortNameCollisionError\";\n readonly _tag = \"PortNameCollisionError\" as const;\n constructor(portName: string) {\n super(\n `definePort: a port named \"${portName}\" was already defined. ` +\n `Each definePort call must use a unique name. ` +\n `If two modules need the same port, export it from one module and import it.`,\n );\n }\n}\n\n/**\n * Thrown by `reconcileSubs` when, within ONE desired subscription set, two subs\n * share an `id` but declare different `type`s — a silent bug class the type\n * system cannot reach (ids are strings compared at runtime). The symmetric twin\n * of `PortNameCollisionError`: both mechanize invariant 7 (identity is explicit)\n * at the runtime layer. Same id across transitions is the no-churn case and does\n * NOT throw; only a within-set type conflict does.\n */\nexport class SubIdCollisionError extends Error {\n override readonly name = \"SubIdCollisionError\";\n readonly _tag = \"SubIdCollisionError\" as const;\n constructor(id: string, declaredType: string, conflictingType: string) {\n super(\n `@demlik/tea: Sub.id collision: id=\"${id}\" declared as ` +\n `type=\"${declaredType}\" and type=\"${conflictingType}\"`,\n );\n }\n}\n\n/**\n * Test-only escape hatch — clears the module-level port-name registry so the\n * collision assert in `definePort` doesn't false-positive across vitest's\n * non-isolated runs. Production code MUST NOT call this.\n *\n * @internal test-only\n */\nexport function __resetPortRegistry(): void {\n definedPortNames.clear();\n}\n\n/**\n * Which otherwise-unattributable runtime path produced an error. Every fanout is\n * throw-isolated so one bad consumer never strands its siblings, and the\n * isolated throw routes to the `OnError` sink rather than a bare `console.error`.\n *\n * - `\"follow-up\"` — a follow-up Msg an interpret handler returned rejected when\n * re-dispatched (the original dispatcher already resolved, so no caller).\n * A rejection caused by `stop()`'s own teardown is NOT this phase — see\n * `\"discard\"`.\n * - `\"stop-save\"` — the final `store.save(state)` inside `stop()` threw\n * (`stop()` resolves regardless, so without the sink this was silent loss).\n * - `\"reduce\"` — the pure `update` (reducer) threw synchronously; the configured\n * `Supervision` strategy decides what happens next, but the throw is surfaced\n * here as data first.\n * - `\"listener\"` — a `runtime.subscribe(...)` listener threw during fanout.\n * - `\"observer\"` — a `runtime.observe(...)` observer threw during fanout.\n * - `\"event\"` — the `events` projector or an `on(type, ...)` handler threw.\n * - `\"boot\"` — an `onBoot` handler threw (boot fanout, or a late registration).\n * - `\"port-emit\"` — a `subscribePort` listener threw during a port emission.\n * - `\"sub-cleanup\"` — a subscription's cleanup threw (reconcile-removal or\n * `stop()` teardown).\n * - `\"discard\"` — work lost to a teardown the host asked for. Two witnesses,\n * both carrying a `RuntimeDiscardNotice`: `stop()` was called while interpret\n * handlers were still awaiting (`RuntimeDiscardedError`), and a Msg that\n * arrived DURING `stop()`'s drain and so could not be delivered\n * (`DispatchDiscardedError`), and teardown work that did not settle inside\n * `disposeTimeoutMs` (`DisposeTimeoutNotice`). The one phase that is a\n * LIFECYCLE report rather than a failure: legal, merely lossy.\n * - `\"identity-drop\"` — the `Identity` filter dropped a message addressed to a\n * different instance (`IdentityDropNotice`). Its own phase rather than\n * `\"discard\"`: the loss is the FILTER WORKING, not a teardown, and a host\n * routing it (a metric, a 409 back to the caller) wants it apart from\n * unmount-time noise. Warn-only, like every `RuntimeDiscardNotice`.\n *\n * The phase never decides fatality — the ERROR CLASS does (`RuntimeDiscardNotice`\n * warns, everything else rethrows). A phase is attached by the report site, so a\n * consumer sink that throws while handling a `\"discard\"` report inherits that\n * phase; keying the warn-only path on it would make the sink's own defect vanish.\n */\nexport type RuntimeErrorPhase =\n | \"follow-up\"\n | \"stop-save\"\n | \"reduce\"\n | \"listener\"\n | \"observer\"\n | \"event\"\n | \"boot\"\n | \"port-emit\"\n | \"sub-cleanup\"\n | \"discard\"\n | \"identity-drop\";\n\n/** Context handed to an `OnError` sink alongside the error itself. */\nexport interface RuntimeErrorContext {\n readonly phase: RuntimeErrorPhase;\n}\n\n/**\n * Sink for runtime failures that have no caller to reject at. Configured via\n * `run(machine, { onError })`. Should be total — a throw inside the sink is\n * itself surfaced via the default sink, so it can never re-introduce a silent\n * failure.\n */\nexport type OnError = (error: unknown, context: RuntimeErrorContext) => void;\n\n// === Supervision: declared policy for a reducer throw ===\n//\n// A throw inside the pure `update` is a synchronous throw (the reentrancy brand\n// makes a reducer provably synchronous), catchable in the dispatch loop — the\n// seam Akka supervisor strategies / Erlang-OTP supervision trees occupy: \"let it\n// crash\" becomes CONFIG. Every strategy routes the failure to `onError` with\n// `phase: \"reduce\"` FIRST (invariant 6), then differs in what happens next:\n//\n// - `stop` — halt (SAFE DEFAULT). State is NOT advanced; the dispatch\n// promise rejects; every subsequent dispatch rejects. Never a\n// silent resume.\n// - `escalate`— surface + propagate: the dispatch promise rejects so the\n// failure bubbles to a parent supervisor. The runtime stays live.\n// - `restart` — re-init from a host-provided last-known-good state and keep\n// folding. The core owns no snapshot logic — the host supplies\n// `rehydrate()`, the core installs its result and continues.\n\n/** The three declared reducer-throw supervision strategies. */\nexport type SupervisionStrategy = \"stop\" | \"escalate\" | \"restart\";\n\n/**\n * Declared supervision policy for a reducer (`update`) throw, at\n * `run(machine, { supervision })`. The bare-string shorthands (`\"stop\"` /\n * `\"escalate\"`) are accepted for the two strategies that need no host data;\n * `restart` MUST be the object form because it carries the `rehydrate` callback,\n * which MUST return a valid `S` synchronously (the reducer is synchronous, so\n * recovery is too — no suspension across the single-writer slot).\n */\nexport type Supervision<S, M extends { type: string }> =\n | \"stop\"\n | \"escalate\"\n | { readonly strategy: \"stop\" }\n | { readonly strategy: \"escalate\" }\n | {\n readonly strategy: \"restart\";\n /**\n * Host-provided rehydration to last-known-good state. Invoked when the\n * reducer throws; its return value becomes the new state and the transition\n * continues from there. Receives the pre-throw `state`, the `msg` that\n * triggered the throw, and the thrown `error` so the host can route by\n * cause.\n */\n readonly rehydrate: (state: S, msg: M, error: unknown) => S;\n };\n\n/**\n * Raised by `idle()` when the quiescence wait hits its iteration cap without the\n * dispatch tail stabilizing — `idle()` REJECTS rather than silently resolving,\n * so a livelocking machine surfaces instead of masquerading as quiescent.\n * Mechanizes invariant 6 at the runtime layer, the role `PortNameCollisionError`\n * plays for invariant 7.\n */\nexport class QuiescenceTimeoutError extends Error {\n override readonly name = \"QuiescenceTimeoutError\";\n readonly _tag = \"QuiescenceTimeoutError\" as const;\n constructor(public readonly iterations: number) {\n super(\n `@demlik/tea: idle() did not reach quiescence after ${iterations} ` +\n `iterations — the dispatch tail kept advancing. The machine is ` +\n `likely livelocking (an interpret handler enqueues a follow-up Msg ` +\n `that enqueues another, without end). Bound the follow-up chain in ` +\n `the reducer, or stop the runtime.`,\n );\n }\n}\n\n/**\n * Base of the LOSSY-BUT-LEGAL teardown facts: work the host discarded by letting\n * go of a runtime that still had something outstanding. Reported under\n * `phase: \"discard\"`, never raised.\n *\n * It exists so \"is this fatal?\" has ONE definition — `error instanceof\n * RuntimeDiscardNotice` — that the default sink can ask about any error from any\n * site. The phase cannot answer it: a report site attaches the phase, so a\n * consumer sink that throws while handling a `\"discard\"` report carries that\n * phase into the sink-failure path, and a phase-keyed warn branch would swallow\n * the sink's own defect. Sub-classing is how a new teardown fact opts into the\n * warn-only treatment; nothing else can.\n */\nexport abstract class RuntimeDiscardNotice extends Error {}\n\n/**\n * Reported to the `OnError` sink under `phase: \"discard\"` when `stop()` is\n * called while `interpret` handlers are still awaiting. The host is dropping\n * its only handle on that work: `stop()` drains the tail, but the listeners,\n * observers and event handlers that would have consumed the resulting\n * transitions are gone, so the Cmds' results reach nobody.\n *\n * The motivating shape is `@demlik/tea/react`'s `useMachine`, which memoizes on\n * `[machine, ctx, store]` — a ctx re-derived mid-flight replaces the runtime,\n * and the in-flight mutation's response arrives for a runtime the UI no longer\n * renders, silently rewinding the visible state. Never a throw: tearing a\n * runtime down mid-flight is legal (a navigation does it every time), so this\n * is surfaced as data, not raised. Mechanizes invariant 6 (no silent failures)\n * at the teardown seam, the role `QuiescenceTimeoutError` plays for `idle()`.\n */\nexport class RuntimeDiscardedError extends RuntimeDiscardNotice {\n override readonly name = \"RuntimeDiscardedError\";\n readonly _tag = \"RuntimeDiscardedError\" as const;\n constructor(public readonly pendingCmds: number) {\n super(\n `@demlik/tea: runtime stopped with ${pendingCmds} Cmd(s) in flight — ` +\n `their results will reach no listener. In React this is usually a ` +\n `ctx-identity change rebuilding useMachine's runtime mid-flight: ` +\n `stabilize the ctx (useMemo / useState) so the runtime outlives the ` +\n `Cmds it started. An intentional teardown (unmount, navigation) can ` +\n `silence this with a run({ onError }) sink that ignores ` +\n `phase: \"discard\".`,\n );\n }\n}\n\n/**\n * Reported to the `OnError` sink under `phase: \"identity-drop\"` when the\n * `Identity` filter drops a message addressed to a different instance.\n *\n * The drop itself is the feature — it is what lets a reducer stop repeating\n * `if (msg.runId !== state.runId) return`. What was missing is that it was\n * INVISIBLE: the dispatch resolved exactly like an applied one, so a reusable\n * Durable Object serving run A and then run B lost every run-B message and\n * reported success to each caller. The comment claiming \"the one observable\n * enforcement point\" described this notice before it existed.\n *\n * Never fatal (`RuntimeDiscardNotice`): a mis-addressed message is a normal\n * consequence of instance reuse, not a defect. A host that wants to act on it\n * — a metric, a 409 to the caller, a re-route — reads `phase: \"identity-drop\"`\n * in its own sink.\n */\nexport class IdentityDropNotice extends RuntimeDiscardNotice {\n override readonly name = \"IdentityDropNotice\";\n readonly _tag = \"IdentityDropNotice\" as const;\n constructor(public readonly msgType: string) {\n super(\n `@demlik/tea: Msg \"${msgType}\" was addressed to a different instance ` +\n `than this state owns and was dropped before \\`update\\` ran — the ` +\n `machine's \\`identity\\` filter working as declared. If this Msg was ` +\n `meant for THIS instance, its \\`ofMsg\\` projection disagrees with ` +\n `\\`ofState\\`; if the instance is being reused across runs, route the ` +\n `message to the run that owns it.`,\n );\n }\n}\n\n/**\n * Reported to the `OnError` sink under `phase: \"discard\"` when `stop()`'s wait\n * for async teardown work hits `disposeTimeoutMs`.\n *\n * `stop()` awaits the disposals it started so `await runtime.stop()` can mean\n * \"safe to evict\". A `release` that never settles must not turn that guarantee\n * into a host that never shuts down, so the wait is bounded and expiry is\n * REPORTED rather than silently tolerated — the resource is genuinely still\n * open at that point, and only the host can decide what that costs.\n */\nexport class DisposeTimeoutNotice extends RuntimeDiscardNotice {\n override readonly name = \"DisposeTimeoutNotice\";\n readonly _tag = \"DisposeTimeoutNotice\" as const;\n constructor(\n public readonly pendingDisposals: number,\n public readonly timeoutMs: number,\n ) {\n super(\n `@demlik/tea: stop() gave up waiting for ${pendingDisposals} async ` +\n `teardown(s) after ${timeoutMs}ms — the resources they release may ` +\n `still be open. Make \\`release\\` / the Sub cleanup settle (bound its ` +\n `own IO), or raise run({ disposeTimeoutMs }).`,\n );\n }\n}\n\n/**\n * The rejection of a dispatch that arrived DURING `stop()`'s drain — an in-flight\n * interpret handler's follow-up Msg, a detached handler's terminal Msg, or a Sub\n * that is still live because subs are torn down only after the drain. The stop\n * barrier is absolute (the Msg is refused, never folded), so this names the loss.\n *\n * It is the SECOND witness of `RuntimeDiscardedError`'s fact, and it is why the\n * drain window is a distinct runtime state rather than a plain `stopped` flag:\n * a Msg refused inside the window was discarded BY the teardown, while the same\n * refusal after `stop()` has returned is a consumer dispatching into a runtime it\n * already retired — a real error, and still loud (`phase: \"follow-up\"`, or the\n * caller's own rejection). The distinction is the state at refusal time, never\n * the message text.\n */\nexport class DispatchDiscardedError extends RuntimeDiscardNotice {\n override readonly name = \"DispatchDiscardedError\";\n readonly _tag = \"DispatchDiscardedError\" as const;\n constructor(public readonly msgType: string) {\n super(\n `@demlik/tea: Msg \"${msgType}\" arrived while the runtime was stopping ` +\n `and was discarded — stop() refuses new work so the drain terminates. ` +\n `It is the result of a Cmd that outlived its runtime; keep the runtime ` +\n `alive until the Cmd settles (in React: stabilize useMachine's ctx) if ` +\n `the transition matters.`,\n );\n }\n}\n\n/**\n * Define a typed port. `name` is metadata (devtools / debug logs); port identity\n * is by reference. Each definePort call must use a unique `name` — a second call\n * with the same name throws `PortNameCollisionError`. Use ports for \"data leaving\n * the runtime selectively\" (cursor announcements, DOM-mutation broadcasts,\n * telemetry) that should NOT be folded into State. Strengthens invariant 7.\n */\nexport function definePort<T>(name: string): Port<T> {\n // HMR skip — Vite / dev servers re-import the module on hot reload, which\n // would otherwise false-positive on every definePort across reloads. The\n // try/catch is load-bearing for service-worker bundles: Vite polyfills\n // `import.meta.url` via `document.baseURI`, which throws ReferenceError in\n // an MV3 service worker. A throw here would abort SW install (Chrome\n // surfaces it as \"registration failed status 15\"). Catching it = \"no HMR\n // detected\" = the collision check applies, which is the correct SW\n // behavior (SWs reload via chrome.runtime.reload, not module HMR).\n let isHmr = false;\n try {\n const meta = import.meta as { hot?: unknown };\n isHmr = meta?.hot !== undefined && meta?.hot !== null;\n } catch {\n isHmr = false;\n }\n if (!isHmr && definedPortNames.has(name)) {\n throw new PortNameCollisionError(name);\n }\n definedPortNames.add(name);\n return { __brand: \"port\", name } as Port<T>;\n}\n\n// === Store: pluggable persistence adapter ===\n//\n// `load()` returns `unknown` because storage genuinely doesn't know `S` —\n// chrome.storage returns whatever JSON lives at the key, DurableObjectStorage\n// returns whatever bytes were serialized. Casting raw storage to `S` at the\n// adapter boundary is the exact `as S` move forbidden by invariant 8\n// everywhere else in the substrate. `load()` returning `unknown` makes the\n// substrate stop pretending.\n//\n// `migrate(raw)` is the boundary parse. Returns `S` on a recognized shape —\n// the substrate passes it to `init` as `loaded`. Returns `null` on any\n// unrecognized shape — the substrate passes `null` to `init`, fresh-boot\n// path. Must NOT throw: if a future shape is unrecognizable, returning\n// `null` boots clean from a known-good path. Throwing here would\n// indistinguishably collapse \"storage corruption\" and \"schema migration not\n// yet written\" — both are runtime decisions, not panics.\n//\n// Strengthens invariant 8 (the boundary parses; the core trusts) — the\n// substrate now enforces the parse instead of relying on every adapter\n// remembering to.\nexport interface Store<S> {\n load(): Promise<unknown>;\n save(state: S): Promise<void>;\n migrate(raw: unknown): S | null;\n}\n\n// === Schema-derived migrate: single-source the durable State ===\n//\n// The boundary parse `migrate(raw)` is two jobs glued together:\n// 1. STRUCTURAL VALIDATION — \"is this the current shape?\" 100% derivable\n// from the State type, pure boilerplate, the part that drifts when a\n// field is added to a variant but the hand-written parser isn't updated.\n// 2. VERSION MIGRATION — \"upgrade an OLD shape to the current one\" (default a\n// missing field, drop a dead one, remap a removed enum). Genuine logic.\n// Encodes decisions about how past rows map forward. NOT derivable.\n//\n// `schemaMigrate` derives job 1 from a schema and isolates job 2 in a thin,\n// explicit `upcast` run BEFORE the parse. When the State type is itself\n// `Schema<S>`-inferred (`type S = SchemaOutput<typeof schema>`), the type and\n// the validator are the SAME declaration — you cannot add a field to the type\n// without it being in the schema, so the parse always enforces the current\n// shape. The `as S` an adapter would otherwise write on `safeParse().data` is\n// deleted: the schema's output IS `S`.\n//\n// The kernel stays library-agnostic — `Schema<S>` is the minimal Standard-\n// Schema-shaped surface (`safeParse`) that both zod 3 and zod 4 satisfy. No\n// validator is imported here; the consumer supplies one.\n//\n// Strengthens invariant 8 (the boundary parses; the core trusts) and invariant\n// 1 (state is a value — one declaration sources both type and parser).\nexport interface Schema<S> {\n safeParse(raw: unknown): { success: true; data: S } | { success: false };\n}\n\n/**\n * Build a `Store.migrate` from a schema (job 1) and an optional thin `upcast`\n * (job 2). `upcast` maps a recognized-but-OLD raw shape forward into the shape\n * the schema validates; it defaults to identity (no version migration yet).\n *\n * Never throws — a shape the schema rejects returns `null`, the substrate's\n * fresh-boot path, per the `Store.migrate` contract. An `upcast` that itself\n * throws on a corrupt blob is caught and collapses to `null` (same posture).\n */\nexport function schemaMigrate<S>(\n schema: Schema<S>,\n upcast: (raw: unknown) => unknown = (raw) => raw,\n): (raw: unknown) => S | null {\n return (raw: unknown): S | null => {\n let migrated: unknown;\n try {\n migrated = upcast(raw);\n } catch {\n return null;\n }\n const parsed = schema.safeParse(migrated);\n return parsed.success ? parsed.data : null;\n };\n}\n\n// === DispatchSettle: how far a dispatch awaits ===\n//\n// The default is the SAFE one, `\"quiescent\"` — `dispatch` resolves only once the\n// entire transitive follow-up chain has drained (the same drain `idle()`\n// performs). Were the default one transition, an `interpret` follow-up Msg would\n// enqueue a fresh transition fire-and-forget relative to the original\n// `dispatch`, resolving \"early\". The rare single-step case opts in with `\"once\"`\n// (or the `dispatchOnce` convenience).\nexport type DispatchSettle = \"quiescent\" | \"once\";\n\n// === RuntimeRef<M>: typed sibling-runtime handle ===\n//\n// Exposes only the inbox of a Runtime — `dispatch` / `dispatchOnce`. Use it as\n// the field type when one runtime holds a *sibling* (composition by reduction\n// across orthogonal lifecycles — invariant 5): the holder learns the Msg shape\n// it can send, nothing about the referenced runtime's State/Cmd/Sub/Ctx/\n// observers/Port fanout. `Runtime<S, M>` extends `RuntimeRef<M>` structurally.\nexport interface RuntimeRef<M extends { type: string }> {\n /**\n * Put a Msg in the runtime's inbox and resolve once processed. Runs to\n * QUIESCENCE by default: settles only after the dispatched Msg AND every\n * transitive interpret follow-up has drained. Pass `{ settle: \"once\" }` (or\n * call `dispatchOnce`) for the single-step case.\n *\n * Rejection ordering: the ONE dispatched transition's own failure surfaces\n * first, here. If it succeeds but the follow-up chain never stabilizes, the\n * quiescent drain rejects with `QuiescenceTimeoutError`. Follow-up Msg\n * rejections themselves route to the `onError` sink, never here.\n */\n dispatch(msg: M, opts?: { readonly settle?: DispatchSettle }): Promise<void>;\n /**\n * Dispatch `msg` and resolve after exactly ONE transition's effects settle,\n * WITHOUT draining the follow-up chain — equivalent to\n * `dispatch(msg, { settle: \"once\" })`. Prefer plain `dispatch` unless you will\n * `await runtime.idle()` yourself; the un-awaited follow-up chain is the\n * footgun the quiescent default removed.\n */\n dispatchOnce(msg: M): Promise<void>;\n}\n\n// === BootingRuntime: handle returned SYNCHRONOUSLY from run() ===\n//\n// `run()` returns a `BootingRuntime<S, M>` the instant it is called, while boot\n// is still in flight. It exposes exactly the surface TOTAL before boot: queue a\n// dispatch, subscribe, observe, wire a Port, stop. What you may NOT do is read\n// State (`getState`) or wait for quiescence (`idle`) — those need the initial\n// State to exist, and are the difference between a BootingRuntime and a\n// `Runtime`, enforced at the type level so \"read State before boot\" is a COMPILE\n// error, not a runtime throw.\n//\n// `subscribe` is the React-shaped change notifier (zero-arg, paired with\n// `getState()`). `observe` is the devtools-shaped trace hook — `(msg, state)`\n// for every APPLIED transition; boot is delivered via `onBoot` instead, so\n// `observe`'s `msg` is total (never `null`). `on(type, handler)` is the SEMANTIC\n// event channel: only the public, `type`-narrowed events a machine projects,\n// never its PRIVATE Msg vocabulary. `E` defaults to `never` (no projector → `on`\n// uncallable).\nexport interface BootingRuntime<\n S,\n M extends { type: string },\n E extends { type: string } = never,\n> extends RuntimeRef<M> {\n dispatch(msg: M, opts?: { readonly settle?: DispatchSettle }): Promise<void>;\n dispatchOnce(msg: M): Promise<void>;\n subscribe(listener: () => void): () => void;\n observe(observer: (msg: M, state: S) => void): () => void;\n /**\n * Subscribe to the INITIAL State — the boot transition. Fires exactly once:\n * immediately if boot already completed (so a late subscriber never misses\n * it), otherwise on the boot fanout. Returns a cleanup (a no-op once fired).\n */\n onBoot(handler: (state: S) => void): () => void;\n /**\n * Subscribe to a SEMANTIC event of `type`. The handler receives exactly the\n * `E` member whose `type` matches `K`, so a consumer never touches another\n * event's shape and never references the machine's PRIVATE Msg names. Multiple\n * handlers per type; fanout is synchronous and throw-isolated. Fires only when\n * the machine's `events` projector (wired on `run`) emits that type.\n */\n on<K extends E[\"type\"]>(\n type: K,\n handler: (event: Extract<E, { type: K }>) => void,\n ): () => void;\n /**\n * Subscribe to a typed Port. Multiple listeners per port; fanout is synchronous\n * and isolated. Ports are for \"data leaving the runtime selectively\" (see\n * `definePort`) — distinct from `observe` (every transition) and State.\n */\n subscribePort<T>(port: Port<T>, listener: (value: T) => void): () => void;\n /**\n * Emit a value on a Port from OUTSIDE an interpret handler — same synchronous\n * fanout as `ctx.emit`. The canonical use is `observe`-driven emission (one\n * runtime publishing a state-derived signal another subscribes to via\n * `subscribePort`) without spawning a \"tell the outside world\" Cmd per\n * transition.\n */\n emitPort<T>(port: Port<T>, value: T): void;\n /**\n * Resolves to the booted `Runtime<S, M>` after boot completes (or rejects with\n * the boot error). This is the ONLY way to obtain a `Runtime` — and a\n * `Runtime` is the only handle whose `getState()` is total. Awaiting `ready` is\n * the single gate between \"boot in flight\" and \"State exists\":\n *\n * ```ts\n * const runtime = await run(machine, opts).ready;\n * runtime.getState(); // total — never throws-before-boot\n * ```\n *\n * Idempotent — the same settled promise, resolving to the same `Runtime`, comes\n * back on every read. Expresses canon §2.3 (boot is a named, awaitable moment).\n */\n ready: Promise<Runtime<S, M, E>>;\n stop(): Promise<void>;\n}\n\n// === Runtime: the BOOTED handle `ready` resolves to ===\n//\n// A `BootingRuntime<S, M>` whose boot has completed. It adds the two members\n// only meaningful once the initial State exists — `getState()` (total) and\n// `idle()` (quiescence) — and narrows `ready` to resolve to itself. You never\n// construct one directly; you obtain it via `await bootingRuntime.ready`, which\n// makes \"read State before boot\" unrepresentable rather than merely discouraged.\nexport interface Runtime<\n S,\n M extends { type: string },\n E extends { type: string } = never,\n> extends BootingRuntime<S, M, E> {\n /**\n * The current State. TOTAL — never throws. Obtaining a `Runtime` requires\n * awaiting `ready`, which only resolves AFTER boot has run `init` and set the\n * initial State.\n */\n getState(): S;\n /** Resolves to this same `Runtime` once boot completes. Idempotent. */\n ready: Promise<Runtime<S, M, E>>;\n /**\n * Resolves once the runtime has reached QUIESCENCE — every dispatched Msg AND\n * every transitive interpret follow-up has been processed, with no further step\n * pending on the tail. Since plain `dispatch` already runs to quiescence, this\n * is mainly for follow-ups left by a `dispatchOnce` single step, or a chain\n * kicked off by a Sub / boot `init` cmds with no `dispatch` to await.\n *\n * Idempotent and re-entrant-safe. Tail rejections are NOT surfaced here (a\n * failing dispatch surfaces on its OWN promise; follow-up rejections route to\n * `onError`). The one rejection `idle()` produces is `QuiescenceTimeoutError`\n * on hitting the iteration cap — so a livelock stays distinguishable from a\n * genuine quiesce (invariant 6). A poll is still correct when waiting on an\n * EXTERNAL event the runtime cannot enqueue itself.\n */\n idle(): Promise<void>;\n /**\n * The terminal State of the run, or `undefined` while in flight — \"terminal\"\n * per the `terminal` predicate passed to `run()` (no predicate → never\n * terminal → always `undefined`). The first-class result read: the run's\n * product off the State the machine already owns, NOT scraped off the `observe`\n * firehose by matching an internal Msg name. Total — never throws.\n */\n result(): S | undefined;\n /**\n * Resolves with the terminal State the first time the run reaches one (per the\n * `terminal` predicate). If ALREADY terminal, resolves immediately; otherwise\n * on the transition that first makes `terminal` hold. The awaitable companion\n * to `result()`. With no predicate this never resolves. Idempotent and\n * multi-caller safe.\n */\n done(): Promise<S>;\n}\n\n// === defineMachine: identity-typed pass-through ===\n//\n// Overloads enforce exactly one `update` shape per call so consumers see a\n// concrete type, not the internal union. Runtime is identity — `run` branches on\n// the structural shape of `update` (Reducer = record of functions; Transitions =\n// record of records) when dispatching, so overload resolution picks the right\n// branch by value shape. Strengthens invariant 2 (record forms have no\n// fall-through default) and invariant 7 (the variant set is load-bearing).\n\n// Transitions-form overload — 2D table keyed by State.type then Msg.type.\n// Declared FIRST (most specific). The S constraint is a conditional (`S extends\n// { type: string } ? Transitions<...> : never`) not a generic-parameter\n// constraint, so `S` stays unconstrained at the signature and the public\n// `Parameters<typeof defineMachine<NonDiscriminatedS, ...>>` pattern skips this\n// overload (update becomes `never`) instead of failing.\nexport function defineMachine<\n S,\n M extends { type: string },\n C extends Cmd,\n U extends Sub,\n Ctx,\n>(\n m: Omit<Machine<S, M, C, U, Ctx>, \"update\"> & {\n // `[S]` tuple-wrap disables distributive conditional behavior — see\n // the comment on `Machine.update` for why distribution is wrong here.\n update: [S] extends [{ type: string }] ? Transitions<S, M, C> : never;\n },\n): Machine<S, M, C, U, Ctx>;\n// Reducer-form overload — flat record keyed by Msg.type.\nexport function defineMachine<\n S,\n M extends { type: string },\n C extends Cmd,\n U extends Sub,\n Ctx,\n>(\n m: Omit<Machine<S, M, C, U, Ctx>, \"update\"> & {\n update: Reducer<S, M, C>;\n },\n): Machine<S, M, C, U, Ctx>;\n// Implementation signature — accepts both via the union on `Machine.update`.\nexport function defineMachine<\n S,\n M extends { type: string },\n C extends Cmd,\n U extends Sub,\n Ctx,\n>(m: Machine<S, M, C, U, Ctx>): Machine<S, M, C, U, Ctx> {\n // Tag the update form ONCE, here at the typed construction boundary, so no\n // downstream reader re-derives it structurally. Non-enumerable so it does not\n // serialize, show up in `Object.keys`, or collide with a Msg.type key.\n // Idempotent under re-wrap (`defineMachine(defineMachine(m))`).\n if (m.__form === undefined) {\n Object.defineProperty(m, \"__form\", {\n value: detectUpdateForm(m.update as object),\n enumerable: false,\n writable: false,\n configurable: true,\n });\n }\n return m;\n}\n\n// === asReducer: the validated minting path for a reducer ===\n//\n// Turns a raw record of handlers into a branded `Reducer<S, M, C>`. The\n// parameter type's every cell returns the non-thenable `SyncReturn<S, C>`, so\n// the reentrancy guard fires HERE, at construction: an `async` cell returns\n// `Promise<...>`, not assignable to `SyncReturn`, and `tsc` rejects it. PURE —\n// identity at runtime (the brand is phantom). `defineMachine` brands its\n// `update` internally the same way, so this exists for the standalone `const\n// update = asReducer<...>({ ... })` form.\nexport function asReducer<S, M extends { type: string }, C extends Cmd>(\n reducer: Reducer<S, M, C>,\n): Branded<Reducer<S, M, C>> {\n return reducer as Branded<Reducer<S, M, C>>;\n}\n\n// === CtxArg<Ctx>: the `ctx` field of `run`'s opts, conditionally optional ===\n//\n// A PURE machine reads nothing from `ctx` (its `Ctx` is `NoCtx` /\n// `Record<string, never>` / `unknown`); forcing `ctx: {}` is ceremony for a\n// value the type already pins as empty. So `ctx` is CONDITIONALLY optional: when\n// `{}` satisfies `Ctx` it may be OMITTED (defaulted to `{}`), so a pure reducer\n// runs as `run(machine)`; when `Ctx` carries a field a handler reads, `ctx` stays\n// REQUIRED. `[Record<never, never>] extends [Ctx]` reads as \"is `{}` assignable\n// to `Ctx`\" — true for the context-free shapes, false for `{ db: … }`; the\n// tuple-wrap disables distributive conditional behavior.\nexport type CtxArg<Ctx> = [Record<never, never>] extends [Ctx]\n ? { ctx?: Ctx }\n : { ctx: Ctx };\n\n// === replay: pure unit-test helper ===\n//\n// Composes `init(loaded ?? null, ctx)` then `update(state, msg)` for each msg.\n// Returns the final state plus the cmds that *would* have been emitted and the\n// subs that *would* have been desired at the final state. It does NOT call any\n// `interpret[type]` handler, does NOT touch `Store`, and does NOT start any\n// subscription. `subscriptions` IS called to derive `subs` — that lets tests\n// assert what would be wired up without actually wiring it.\n//\n// Dep-keyed Subs are reported in `depSubs`: for each `machine.subs` entry\n// active at the FINAL state (its `deps` non-null), the entry's `index` and the\n// derived `id` (`structuralHash(deps)`) — so a test can assert \"the deadline +\n// checkpoint + bridge dep-subs are armed in `running`\" without wiring any\n// source. Same intent as `subs` for the manual path: assert the desired set\n// purely. `source` is NEVER called (replay starts no subscription).\nexport function replay<\n S,\n M extends { type: string },\n C extends Cmd,\n U extends Sub,\n Ctx,\n>(\n machine: Machine<S, M, C, U, Ctx>,\n opts: { msgs: readonly M[]; ctx: Ctx; loaded?: S | null },\n): {\n state: S;\n cmds: C[];\n subs: U[];\n depSubs: { index: number; id: string }[];\n} {\n // Coerce undefined → null so `replay` with `loaded: undefined` calls\n // `init(null, ctx)`.\n const loaded = opts.loaded ?? null;\n const [initialState, initCmds] = machine.init(loaded, opts.ctx);\n\n if (loaded !== null && initCmds.length > 0) {\n throw new Error(\n \"TEA contract violation: machine.init must return [state, []] (no Cmds) \" +\n \"when `loaded` is non-null. Init's rehydrate branch is a pure state \" +\n \"passthrough — the migration/parse boundary, not the boot-effect hook.\\n\\n\" +\n \"Routes for boot effects:\\n\" +\n \" - Stateless infrastructure (e.g. chrome.alarms ensure) → host module \" +\n \"top level, outside TEA.\\n\" +\n \" - State-conditional resume (e.g. re-attach a session) → a `boot` Msg \" +\n \"dispatched once from the host after `run(...)` returns, handled in update.\\n\\n\" +\n \"See .patterns/tea/tea-invariants.md (Invariant 2).\",\n );\n }\n\n // `replay` and `foldMsgs` share ONE internal fold (`foldUpdates`), keyed on\n // `formOf` — the same reader `run` uses — so all three agree on the\n // reducer-vs-transitions form by construction.\n const { state, cmds: foldedCmds } = foldUpdates<S, M, C>(\n machine,\n initialState,\n opts.msgs,\n );\n const cmds: C[] = [...initCmds, ...foldedCmds];\n\n const subs: U[] = machine.subscriptions\n ? [...machine.subscriptions(state)]\n : [];\n\n // The dep-keyed desired set at the final state — `deps` only, never `source`.\n const depSubs: { index: number; id: string }[] = [];\n if (machine.subs) {\n for (const [index, entry] of machine.subs.entries()) {\n const deps = entry.deps(state);\n // Same gate the runtime reconciles on (`depsInactive`), not a second\n // spelling of it — a `deps` returning `undefined` means inactive here too.\n if (!depsInactive(deps))\n depSubs.push({ index, id: structuralHash(deps) });\n }\n }\n\n return { state, cmds, subs, depSubs };\n}\n\n// === wrapDetached: the typed Cmd→Msg edge for a detached interpret handler ===\n//\n// Adapts an `InterpretDetached<C, Allowed, Ctx>` — a handler that detaches its\n// long-running work (`ctx.waitUntil(...)`) and fires its TERMINAL Msg through\n// the kernel-injected `dispatch` — into a plain `Interpret` cell that drops into\n// the `interpret` dictionary unchanged. The point is the NARROWING: `Allowed`\n// is the subset of the Msg union this Cmd is permitted to produce, and the\n// `dispatch` the handler sees is typed `(msg: Allowed) => void`. A wrong /\n// typo'd terminal Msg from the detached site then fails to compile (rung 2),\n// where today it reaches for a host-wired `ctx.dispatch` typed to the full\n// union and compiles silently (rung 5 — the allowed set on a comment).\n//\n// `Allowed extends M` is the constraint that makes this SOUND at the kernel:\n// the kernel injects the WIDE `dispatch: (msg: M) => void`, the handler only\n// ever calls it with `Allowed` values (its own narrowed signature enforces\n// that), and every `Allowed` is a valid `M`. So passing the wide dispatch into\n// the narrow-expecting handler is type-correct in one direction (the values the\n// handler produces are all `M`) — the narrowing constrains the AUTHOR, not the\n// kernel. No `as` on data: the only widen is the function reference itself\n// (`dispatch` accepting `M` is usable where one accepting `Allowed` is wanted\n// because `Allowed extends M` — contravariant parameter, sound here because the\n// handler never sees a non-`Allowed` value), expressed by the explicit\n// `Allowed`/`M` generic relation, not a cast.\n//\n// Opt-in / additive: a leaf handler that resolves on its own keeps returning\n// `Promise<M | void>` and never touches `wrapDetached`. Only the detached sites\n// adopt it. The wrapped result is structurally a plain cell, so the `interpret`\n// map type is unchanged.\n//\n// Strengthens invariant 3 (effects are data — the detached effect's terminal\n// result feeds back as a Msg, now a TYPED edge) and invariant 7 (identity is\n// explicit — a Cmd's allowed result-Msg set is load-bearing at the type level).\nexport function wrapDetached<\n C extends Cmd,\n M extends { type: string },\n Allowed extends M,\n Ctx,\n>(\n handler: InterpretDetached<C, Allowed, Ctx>,\n): (\n cmd: C,\n ctx: Ctx & PortEmitter,\n dispatch?: (msg: M) => void,\n) => Promise<void> {\n // The injected `dispatch` accepts the full `M`. The handler's signature only\n // lets it call `dispatch` with `Allowed` (⊆ M) values, so handing it the\n // wide fn is sound: `(msg: M) => void` is callable wherever\n // `(msg: Allowed) => void` is wanted, and every value the handler passes is a\n // valid `M`. No data cast — the relation is carried by `Allowed extends M`.\n //\n // `dispatch` is OPTIONAL on the signature to match the `Interpret` cell type\n // (additive — see `Interpret`). The kernel ALWAYS passes it from\n // `runInterpret`; a detached handler invoked WITHOUT a dispatch (only a\n // mis-wired direct caller could do this) has no way to fire its terminal Msg,\n // so we fail loudly (rung 4) rather than silently dropping the seam's result.\n return (cmd, ctx, dispatch) => {\n if (dispatch === undefined) {\n throw new Error(\n \"@demlik/tea: a wrapDetached handler was invoked without the injected dispatch. \" +\n \"The kernel always supplies it; call the handler through the runtime, not directly.\",\n );\n }\n return handler(cmd, ctx, dispatch);\n };\n}\n\n// === tryApplyCell / tryFoldMsgs: refusal as DATA, not as a throw ===\n//\n// `applyCell` and `foldMsgs` throw `NoCellError` when a Msg has no cell. That\n// is right for the runtime — `run`'s dispatch loop wants the throw so the\n// error sink and supervision see it. It is wrong for a caller that drives a\n// machine itself and treats \"this Msg does not apply here\" as an ORDINARY\n// answer: an interactive tool offering the next move, a validator replaying a\n// persisted log. Those callers were writing\n// `try { applyCell(...) } catch (e) { if (e instanceof NoCellError) ... }`\n// around every step — control flow through the exception channel, and a `catch`\n// wide enough to swallow a genuine bug thrown from inside a cell.\n//\n// So: the same operations with the refusal in the return type. The idiom is\n// `better-result`, exactly as `tryInterpret` below uses it — the package's one\n// runtime dependency, already the house Railway spelling.\n//\n// PLACEMENT: these live here and not in `pure/core` because `pure/core` is the\n// runtime-free leaf and must import NOTHING — `better-result` included (see its\n// header). The pure half of the work is the shared `lookupCell` primitive,\n// which DOES live there; these are the `Result` skins over it. `applyCell` and\n// `tryApplyCell` therefore select the same cell by construction, not by two\n// copies of the form-branching agreeing by luck.\n//\n// NOT a general try/catch: a cell that THROWS from inside its own body is a bug\n// in the machine, and it propagates. Only the ABSENCE of a cell is data here.\n\n/**\n * `applyCell` with the missing-cell case in the return type.\n *\n * `Ok` carries the cell's `[nextState, cmds]` verbatim; `Err` carries the same\n * `NoCellError` (msg.type + state name) `applyCell` would have thrown. Dispatch\n * inside `run` keeps using the throwing `applyCell` — this is for callers that\n * step a machine themselves.\n */\nexport function tryApplyCell<S, M extends { type: string }, C extends Cmd>(\n machine: { update: object; __form?: UpdateForm },\n state: S,\n msg: M,\n): Result<readonly [S, readonly C[]], NoCellError> {\n const { cell, stateName } = lookupCell<S, M, C>(machine, state, msg);\n if (cell === undefined) {\n return Result.err(new NoCellError(msg.type, stateName));\n }\n return Result.ok(cell(state, msg));\n}\n\n/**\n * The refusal `tryFoldMsgs` reports: WHICH msg in the log had no cell, where.\n *\n * A plain record, not a new `Error` subclass — it is a `Result` payload, never\n * thrown, and the actual error is the existing `NoCellError` it carries. The\n * package's thrown-error idiom (`class extends Error` + `_tag`, pinned by\n * `error-idiom.test.ts`) applies to errors raised at the runtime edge; this one\n * is never raised, so adding a class would state a promise it does not make.\n */\nexport interface FoldRefusal<M> {\n /** Index into the `msgs` array the fold was given. */\n readonly index: number;\n /** The offending Msg itself — so the caller can print it, not just point. */\n readonly msg: M;\n /** The underlying cell-lookup failure, carrying msg.type + state name. */\n readonly error: NoCellError;\n}\n\n/**\n * `foldMsgs` with the missing-cell case in the return type, INCLUDING which\n * message failed.\n *\n * The motivating use is \"this persisted log does not replay — tell the user\n * where\". A bare `Result<S, NoCellError>` cannot: the error names the msg.type\n * and the state, but a log usually contains that type many times, so the\n * INDEX is the load-bearing fact. The fold stops at the first refusal (state\n * past that point is not defined) and returns it.\n *\n * Shares `foldMsgs`' dev-mode discipline: `deepFreeze` the input state,\n * `assertPureResult` the cell's return. Both compile out of production.\n */\nexport function tryFoldMsgs<S, M extends { type: string }, C extends Cmd>(\n machine: { update: object; __form?: UpdateForm },\n base: S,\n msgs: readonly M[],\n): Result<S, FoldRefusal<M>> {\n let state = base;\n for (const [index, msg] of msgs.entries()) {\n if (__DEV__) deepFreeze(state);\n const stepped = tryApplyCell<S, M, C>(machine, state, msg);\n if (Result.isError(stepped)) {\n return Result.err({ index, msg, error: stepped.error });\n }\n if (__DEV__) assertPureResult(stepped.value, msg.type);\n state = stepped.value[0];\n }\n return Result.ok(state);\n}\n\n// === tryInterpret: Railway sugar over `Result.tryPromise` ===\n//\n// Wraps a fallible `(cmd, ctx) => Promise<Ok>` into a handler for\n// `interpret[type]`: on success resolves `onOk(value, cmd)`, on rejection\n// `onErr(error, cmd)`. It NEVER rejects (assuming `onOk`/`onErr` are total).\n//\n// We use the `{try, catch: (e) => e}` form (not the one-arg thunk) so the\n// original error passes through untouched — the one-arg form wraps errors in\n// `UnhandledException`, which would break `instanceof` checks inside `onErr`.\nexport function tryInterpret<C extends Cmd, Ok, M, Ctx>(\n work: (cmd: C, ctx: Ctx) => Promise<Ok>,\n onOk: (value: Ok, cmd: C) => M,\n onErr: (error: unknown, cmd: C) => M,\n): (cmd: C, ctx: Ctx) => Promise<M> {\n return async (cmd, ctx) => {\n const result = await Result.tryPromise({\n try: () => work(cmd, ctx),\n catch: (error: unknown): unknown => error,\n });\n return result.match({\n ok: (value) => onOk(value, cmd),\n err: (error) => onErr(error, cmd),\n });\n };\n}\n","/**\n * @demlik/tea runtime — `run`: boot + the serial dispatch loop. The pure helpers\n * that compose against a machine without running it (`replay`, `tryInterpret`)\n * live in `./runtime-types`.\n */\n\nimport type {\n Dispose,\n Interpret,\n Machine,\n Port,\n PortEmitter,\n Sub,\n} from \"./pure/core\";\nimport {\n applyCellChecked,\n type Cmd,\n depsInactive,\n structuralHash,\n} from \"./pure/core\";\nimport type {\n BootingRuntime,\n CtxArg,\n DispatchSettle,\n OnError,\n Runtime,\n RuntimeErrorContext,\n RuntimeErrorPhase,\n Store,\n Supervision,\n} from \"./runtime-types\";\nimport {\n DispatchDiscardedError,\n DisposeTimeoutNotice,\n IdentityDropNotice,\n QuiescenceTimeoutError,\n RuntimeDiscardedError,\n RuntimeDiscardNotice,\n SubIdCollisionError,\n} from \"./runtime-types\";\n\n// Default `onError` sink: re-throw on a fresh macrotask so the failure reaches\n// the host's global error handler instead of vanishing — surface, not swallow\n// (invariant 6).\n//\n// A `RuntimeDiscardNotice` is NOT a failure of the runtime's own contract — a\n// host tearing a runtime down with Cmds in flight (a React ctx-identity change,\n// a navigation) is legal, merely lossy. Rethrowing it would make every\n// unmount-during-fetch an uncaught error for consumers who never configured a\n// sink, so the default WARNS: loud enough that the silent discard #365 describes\n// cannot happen again, never fatal. A configured `onError` sees `\"discard\"` like\n// any other phase and can route or ignore it.\n//\n// The branch keys on the ERROR CLASS, never on `context.phase`: the phase is\n// attached by the report site, and `reportError` hands a THROWING consumer sink's\n// own error back here with the phase it was handling — so a phase-keyed branch\n// would warn away a broken sink and re-create the very silent failure this\n// mechanism exists to remove. The class is the one thing a sink's own defect\n// cannot forge.\nfunction defaultOnError(error: unknown, _context: RuntimeErrorContext): void {\n if (error instanceof RuntimeDiscardNotice) {\n console.warn(error);\n return;\n }\n setTimeout(() => {\n throw error;\n }, 0);\n}\n\n// Normalize `Supervision` (bare string or object) so the dispatch loop branches\n// on `.strategy` once. Default `stop`. Derived from `Supervision` — the object\n// arms are exactly its non-string members, so the restart `rehydrate` payload\n// has a single source of truth.\ntype NormalizedSupervision<S, M extends { type: string }> = Extract<\n Supervision<S, M>,\n object\n>;\n\nfunction normalizeSupervision<S, M extends { type: string }>(\n supervision: Supervision<S, M> | undefined,\n): NormalizedSupervision<S, M> {\n if (supervision === undefined) return { strategy: \"stop\" };\n if (typeof supervision === \"string\") return { strategy: supervision };\n return supervision;\n}\n\n// === run ===\n//\n// Returns a `BootingRuntime<S, M>` synchronously; boot runs as the FIRST entry\n// on the serial dispatch tail, awaited implicitly by every public method.\n//\n// Save-then-effects ordering is structural: every transition mutates state,\n// awaits `store.save(newState)`, then reconciles subscriptions, then runs\n// `interpret` for emitted cmds, then fires external listeners. A throw in any\n// effect phase leaves the persisted state ahead of the host's belief about\n// what executed — the Railway discipline (`tryInterpret` in handlers) makes\n// that safe in practice.\nexport function run<\n S,\n M extends { type: string },\n C extends Cmd,\n U extends Sub,\n Ctx,\n E extends { type: string } = never,\n>(\n machine: Machine<S, M, C, U, Ctx>,\n opts: CtxArg<Ctx> & {\n store?: Store<S>;\n onError?: OnError;\n /**\n * The SEMANTIC event projector. Maps one APPLIED transition `(msg, state)`\n * to zero-or-more public events of `E`; `[]` skips the transition. Maps the\n * machine's PRIVATE Msg vocabulary to NAMED events — the private names never\n * reach `on`'s `E` surface. Omit → `E = never` and `on` is uncallable. PURE.\n */\n events?: (msg: M, state: S) => readonly E[];\n /**\n * Declared policy for a reducer (`update`) throw. Always surfaced via\n * `onError` (`phase: \"reduce\"`); the strategy decides what the runtime does\n * next. Defaults to `\"stop\"`. See `Supervision`.\n */\n supervision?: Supervision<S, M>;\n /**\n * The run-terminality predicate — makes the run's outcome first-class. Fed to\n * `Runtime.result()` and `Runtime.done()`. PURE. Omit → never terminal.\n */\n terminal?: (state: S) => boolean;\n /**\n * How long `stop()` waits for teardown work that returned a Promise (an\n * async `release` in `defineManagedResource`, an async Sub cleanup) before\n * giving up on it. Defaults to 5_000ms.\n *\n * `stop()` awaits those disposals so a host doing\n * `await runtime.stop(); env.evict()` cannot drop the isolate mid-release —\n * the leak the managed-resource battery exists to prevent, relocated to\n * shutdown. The bound is what keeps a release that never settles from\n * hanging the host: on expiry `stop()` reports a `DisposeTimeoutNotice`\n * (warn-only, like every `RuntimeDiscardNotice`) and resolves anyway,\n * because `stop()` resolving is a contract.\n */\n disposeTimeoutMs?: number;\n /**\n * Iteration cap for `idle()`'s quiescence wait. Defaults to 100_000. Test\n * seam only. Production code must not set it.\n *\n * @internal test-only\n */\n __idleCap?: number;\n },\n): BootingRuntime<S, M, E> {\n const { store } = opts;\n // `ctx` is conditionally optional (see `CtxArg`); default the nullish case to\n // `{}` so the augmented-ctx spread and `init(loaded, ctx)` get a value.\n const ctx = (opts.ctx ?? {}) as Ctx;\n const idleCap = opts.__idleCap ?? 100_000;\n const disposeTimeoutMs = opts.disposeTimeoutMs ?? 5_000;\n // No projector → `E = never` and `on` is uncallable.\n const projectEvents: (msg: M, state: S) => readonly E[] =\n opts.events ?? (() => []);\n // No predicate → never terminal (`result()` always `undefined`).\n const isTerminal: (state: S) => boolean = opts.terminal ?? (() => false);\n // Absent sink → `defaultOnError` (invariant 6).\n const onError: OnError = opts.onError ?? defaultOnError;\n // A throwing sink routes THAT throw through `defaultOnError` so it can't\n // re-create a silent failure. It arrives with the context the sink was\n // handling, so `defaultOnError` must not decide fatality from the phase — the\n // sink's own error is not the runtime's teardown notice, whatever phase it\n // inherits.\n const reportError = (error: unknown, context: RuntimeErrorContext): void => {\n try {\n onError(error, context);\n } catch (sinkError) {\n defaultOnError(sinkError, context);\n }\n };\n\n // Throw-isolated fanout: run `fn` for every item, routing any throw to the\n // sink under `phase` so one bad consumer never strands its siblings\n // (invariant 6). The single home for that isolation discipline — every\n // synchronous listener/observer/handler fanout goes through here.\n function fanout<T>(\n items: Iterable<T>,\n phase: RuntimeErrorPhase,\n fn: (item: T) => void,\n ): void {\n for (const item of items) {\n try {\n fn(item);\n } catch (err) {\n reportError(err, { phase });\n }\n }\n }\n const supervision = normalizeSupervision<S, M>(opts.supervision);\n\n // `state` is late-initialized inside the boot step (the head of the tail).\n let state: S | undefined;\n let bootError: unknown = null;\n // The dispatch gate. It only ever ADVANCES — `\"open\"` → `\"draining\"` →\n // `\"closed\"` — so the discard window can never re-open under a second `stop()`.\n //\n // \"open\" — normal operation; `enqueueDispatch` accepts.\n // \"draining\" — inside `stop()`, before the tail has settled. New work is\n // still refused (the stop barrier is absolute, and refusing is\n // what makes the drain terminate), but a Msg refused HERE was\n // discarded BY the teardown: it is an in-flight Cmd's follow-up,\n // a detached handler's terminal Msg, or a Sub that is still live\n // because subs are torn down only after the drain. So the\n // rejection is a `DispatchDiscardedError` and it reports under\n // `phase: \"discard\"` — lossy, legal, warn-only.\n // \"closed\" — halted: a `stop` supervision halt, or `stop()` has returned.\n // A dispatch refused here is a consumer using a runtime it\n // already retired — a real error, and it stays loud.\n let gate: \"open\" | \"draining\" | \"closed\" = \"open\";\n // Interpret handlers currently awaiting — \"how many Cmds are in flight?\".\n // Maintained in `runInterpret` (its only writer) and read by `stop()` to make\n // a mid-flight teardown LOUD instead of silent (issue #365). Not a second\n // representation of the serial `tail`: the tail is a promise chain, which\n // cannot be asked synchronously whether it has outstanding work.\n let inFlightCmds = 0;\n\n const subRegistry = new Map<string, Dispose>();\n // Dep-keyed Sub registry. One slot per `machine.subs[i]`, keyed by the entry's\n // array index (its stable identity across reconciles — a dep-keyed Sub has no\n // author-supplied id). `runningId` is the `structuralHash(deps)` of the live\n // source; an absent slot means the entry is currently inactive (its `deps`\n // returned null). The same dispose-on-change / dispose-on-null machinery the\n // manual Sub path uses, with the id DERIVED instead of author-supplied.\n const depSubRegistry = new Map<\n number,\n { runningId: string; dispose: Dispose }\n >();\n const listeners = new Set<() => void>();\n // Observers get (msg, state) for every APPLIED transition (boot goes via `onBoot`).\n const observers = new Set<(msg: M, state: S) => void>();\n // `booted` lets an `onBoot` handler registered AFTER boot fire immediately.\n const bootHandlers = new Set<(state: S) => void>();\n let booted = false;\n // Keyed by event `type`, stored monomorphically (per-type safety at `on`).\n const eventHandlers = new Map<string, Set<(event: E) => void>>();\n // `done()` waiters — the first terminal transition drains them.\n const doneWaiters = new Set<(state: S) => void>();\n\n // Keyed by Port reference (identity is by-reference per Elm); per-port type\n // safety is enforced at the call sites.\n const portRegistry = new Map<Port<unknown>, Set<(value: unknown) => void>>();\n\n // `emit` injected onto ctx: synchronous fanout, no-op on no subscribers,\n // listener throws isolated + routed to the sink (invariant 6).\n function portEmit<T>(port: Port<T>, value: T): void {\n const subscribers = portRegistry.get(port as Port<unknown>);\n if (!subscribers || subscribers.size === 0) return;\n fanout(subscribers, \"port-emit\", (listener) => listener(value));\n }\n\n // Spread into a fresh object so handlers get a Ctx & PortEmitter without\n // mutating the caller's ctx (which may be shared across runtimes / tests).\n const augmentedCtx: Ctx & PortEmitter = { ...ctx, emit: portEmit };\n\n // Every step chains onto `tail` — the single concurrency gate.\n let tail: Promise<void> = Promise.resolve();\n\n // Tear down each named sub: run its cleanup (throws isolated + routed to the\n // sink under `\"sub-cleanup\"`), then drop it from the registry regardless of a\n // cleanup throw. The single home for the cleanup-and-delete discipline shared\n // by `reconcileSubs`'s removal pass and `stop()`.\n function stopSubs(ids: Iterable<string>): void {\n for (const id of ids) {\n const cleanup = subRegistry.get(id);\n if (cleanup === undefined) continue;\n try {\n trackDisposal(cleanup());\n } catch (err) {\n reportError(err, { phase: \"sub-cleanup\" });\n }\n subRegistry.delete(id);\n }\n }\n\n // Teardown work that returned a Promise and has not settled. A cleanup /\n // `Dispose` may be async (`defineManagedResource`'s `release` is typed\n // `void | Promise<void>` precisely so a flush can be awaited), and the\n // reconcile pass CANNOT await it — reconcile runs inside the synchronous\n // transition path (invariant 2). So every async disposal is remembered here\n // the moment it starts, wherever it started, and `stop()` drains the set\n // before resolving: `await runtime.stop()` then means \"teardown is done\", which\n // is the only reading a host evicting an isolate can act on.\n //\n // The rejection is attached HERE, once, so a failing teardown reaches the sink\n // under `\"sub-cleanup\"` (the same phase a synchronous cleanup throw uses)\n // instead of a `console.warn` at each battery, and can never become an\n // unhandled rejection.\n const pendingDisposals = new Set<Promise<void>>();\n\n function trackDisposal(result: void | Promise<void>): void {\n if (!(result instanceof Promise)) return;\n const settling: Promise<void> = result\n .catch((err) => {\n reportError(err, { phase: \"sub-cleanup\" });\n })\n .finally(() => {\n pendingDisposals.delete(settling);\n });\n pendingDisposals.add(settling);\n }\n\n /**\n * Wait for the disposals started so far, bounded by `disposeTimeoutMs`. On\n * expiry report a `DisposeTimeoutNotice` and return anyway — `stop()` resolves\n * regardless (contract), and a release that never settles must not become a\n * host that never shuts down. Warn-only, like every `RuntimeDiscardNotice`:\n * the host asked for this teardown, the loss is legal, and it is now visible.\n */\n async function drainDisposals(): Promise<void> {\n if (pendingDisposals.size === 0) return;\n const outstanding = [...pendingDisposals];\n let timer: ReturnType<typeof setTimeout> | undefined;\n try {\n const outcome = await Promise.race([\n Promise.all(outstanding).then(() => \"settled\" as const),\n new Promise<\"timeout\">((resolve) => {\n timer = setTimeout(() => resolve(\"timeout\"), disposeTimeoutMs);\n }),\n ]);\n if (outcome === \"timeout\") {\n reportError(\n new DisposeTimeoutNotice(outstanding.length, disposeTimeoutMs),\n { phase: \"discard\" },\n );\n }\n } finally {\n if (timer !== undefined) clearTimeout(timer);\n }\n }\n\n // Dispose each named dep-keyed slot: run its `Dispose` (throws isolated +\n // routed to the sink under `\"sub-cleanup\"`, exactly as `stopSubs` does for the\n // manual path), then drop the slot regardless of a throw. The single home for\n // the dispose-and-delete discipline shared by `reconcileDepSubs`'s teardown /\n // re-arm passes and `stop()`.\n function disposeDepSubs(indices: Iterable<number>): void {\n for (const index of indices) {\n const running = depSubRegistry.get(index);\n if (running === undefined) continue;\n try {\n trackDisposal(running.dispose());\n } catch (err) {\n reportError(err, { phase: \"sub-cleanup\" });\n }\n depSubRegistry.delete(index);\n }\n }\n\n /**\n * Reconcile the dep-keyed Subs (`machine.subs`) against `state`. Runs as the\n * first pass of `reconcileSubs`, sharing the dispose-on-change /\n * dispose-on-null machinery with the manual Sub path. For each entry:\n *\n * - `deps(state)` is nullish (`depsInactive`) → the Sub is inactive in this\n * state; dispose it if it was running.\n * - otherwise → `id = structuralHash(deps)`; if no source is running for\n * this entry, or the running id differs (re-arm), dispose the old source\n * and run `source(state, dispatch, ctx)` to open a fresh one (the entry\n * re-derives its own typed slice from `state`).\n * - Unchanged id → leave the source running (the no-churn case).\n *\n * Throws are collected and returned rather than thrown here (same contract as\n * the manual path) so one bad entry doesn't strand the others. That covers\n * `deps` and the hash, not just `source`: `deps` is user code on exactly the\n * same footing, and an unguarded throw there stranded every LATER entry AND\n * the manual `subscriptions` aggregate, which is only reached after this loop.\n * A machine that declares no `subs` returns immediately — the pass is inert.\n */\n function reconcileDepSubs(): unknown {\n let firstError: unknown = null;\n const subs = machine.subs;\n if (!subs) return firstError;\n for (const [i, entry] of subs.entries()) {\n // `deps` + `structuralHash` in ONE guarded step: both are pure user-data\n // reads whose failure means \"this entry's slice is unknowable\", and the\n // recovery is identical — remember the error, leave the slot untouched,\n // keep reconciling the siblings.\n let deps: unknown;\n let id: string;\n try {\n deps = entry.deps(state as S);\n if (depsInactive(deps)) {\n // Inactive in this state — tear down if running.\n disposeDepSubs([i]);\n continue;\n }\n id = structuralHash(deps);\n } catch (err) {\n if (firstError === null) firstError = err;\n continue;\n }\n\n const running = depSubRegistry.get(i);\n if (running !== undefined && running.runningId === id) {\n // No-churn case: same deps → leave the source running.\n continue;\n }\n // Re-arm (id changed) or first arm — dispose the stale source first.\n disposeDepSubs([i]);\n try {\n // `dispatchUnawaited`, never the raw `enqueueDispatch`: a source's\n // dispatch has no caller to reject at, and the gate rejects during\n // teardown — handing it the raw promise-returning form turned every\n // Sub that fired while `stop()` drained into an unhandled rejection\n // that bypassed `onError` entirely.\n const dispose = entry.source(state as S, dispatchUnawaited, ctx);\n depSubRegistry.set(i, { runningId: id, dispose });\n } catch (err) {\n if (firstError === null) firstError = err;\n // Do NOT register; continue so other dep-keyed sources still arm.\n }\n }\n return firstError;\n }\n\n /**\n * Reconcile subscriptions against `state`, after every save. Same id old+new →\n * leave running; removed id → cleanup (throws isolated, routed to the sink);\n * new id → start (throw remembered and re-thrown after the loop so all other\n * new subs still register).\n */\n function reconcileSubs(): void {\n // Dep-keyed pass FIRST, then the manual aggregate — ONE reconcile pass over\n // both paths. Its start error is remembered (it happened first) and thrown\n // after the manual pass so a bad dep-keyed source never strands the manual\n // subs, and vice versa.\n const depError = reconcileDepSubs();\n if (!machine.subscriptions) {\n if (depError !== null) throw depError;\n return;\n }\n const desired = machine.subscriptions(state as S);\n const desiredIds = new Set<string>();\n\n // Collision assert: within one desired set, two subs sharing an id but\n // declaring different types is a silent bug class. (Same id across\n // transitions is the no-churn case and MUST NOT throw.)\n const desiredTypeById = new Map<string, string>();\n for (const sub of desired) {\n const existing = desiredTypeById.get(sub.id);\n if (existing !== undefined && existing !== sub.type) {\n throw new SubIdCollisionError(sub.id, existing, sub.type);\n }\n desiredTypeById.set(sub.id, sub.type);\n }\n\n // Removals first — anything in the registry not in `desired` should stop.\n for (const sub of desired) desiredIds.add(sub.id);\n stopSubs([...subRegistry.keys()].filter((id) => !desiredIds.has(id)));\n\n // Additions — anything in `desired` not in the registry should start.\n let firstStartError: unknown = depError;\n for (const sub of desired) {\n if (subRegistry.has(sub.id)) continue;\n const handler = machine.subscribe?.[sub.type as U[\"type\"]];\n if (!handler) {\n // No handler for this sub type — programmer error; skip.\n continue;\n }\n try {\n // Same reasoning as the dep-keyed source above: a subscribe handler's\n // dispatch is unawaited by construction, so it gets the wrapped form.\n const cleanup = handler(\n sub as Extract<U, { type: U[\"type\"] }>,\n ctx,\n dispatchUnawaited,\n );\n subRegistry.set(sub.id, cleanup);\n } catch (err) {\n if (firstStartError === null) firstStartError = err;\n // Do NOT register; continue to next sub so other starts still run.\n }\n }\n if (firstStartError !== null) throw firstStartError;\n }\n\n // `interpret` is optional when `C extends Cmd<never>`; default a missing map to\n // `{}` — the per-cmd `if (!handler) continue` preserves invariant-6 forward\n // progress for a miswired consumer.\n const interpretMap: Interpret<M, C, Ctx> =\n (machine as { interpret?: Interpret<M, C, Ctx> }).interpret ??\n ({} as Interpret<M, C, Ctx>);\n\n // The ONE `(msg) => void` handed to every producer that cannot await its own\n // dispatch: a detached interpret handler's `ctx.waitUntil(...)` tail, a\n // dep-keyed Sub's `source`, a `subscribe[type]` handler. Same serial-tail\n // enqueue every other dispatch uses (`enqueueDispatch` chains on `tail`),\n // wrapped so the rejection — which has no caller, the original dispatcher\n // having already resolved — routes to the sink with the phase DERIVED from\n // the error class (`reportUndelivered`).\n //\n // Handing any of those sites the raw `enqueueDispatch` is the defect this\n // shape exists to prevent: its promise REJECTS whenever the gate is shut, so\n // a Sub that fires during `stop()`'s drain produced an unhandled rejection\n // that reached the host's global handler while `onError` saw nothing. A\n // handler authored via `wrapDetached` receives a NARROWED view of this fn\n // (only its declared result-Msg set); a plain leaf handler ignores it.\n const dispatchUnawaited = (msg: M): void => {\n enqueueDispatch(msg).catch(reportUndelivered);\n };\n\n /**\n * Report a re-dispatch that nobody can await. The phase is DERIVED from the\n * rejection the gate produced, so the two call sites cannot drift: a\n * `DispatchDiscardedError` means the teardown refused the Msg (`\"discard\"` —\n * warn-only), anything else is a genuine failure of the follow-up itself\n * (`\"follow-up\"` — the default sink rethrows it).\n */\n function reportUndelivered(error: unknown): void {\n reportError(error, {\n phase: error instanceof DispatchDiscardedError ? \"discard\" : \"follow-up\",\n });\n }\n\n /**\n * Count an interpret handler's work as in flight for exactly the lifetime of\n * its promise. The ONE writer of `inFlightCmds`, so \"how many Cmds are\n * outstanding?\" has a single definition; `finally`-balanced, so a rejecting\n * handler can never strand the count above zero. A handler that throws\n * SYNCHRONOUSLY never reaches here, which is correct — it was never in\n * flight.\n */\n function trackInFlight<T>(work: T | Promise<T>): Promise<T> {\n inFlightCmds++;\n return Promise.resolve(work).finally(() => {\n inFlightCmds--;\n });\n }\n\n /**\n * Run `interpret` for each emitted cmd. A returned follow-up Msg is enqueued\n * onto the tail (NOT dispatched re-entrantly). The first error stops further\n * handlers in this transition.\n */\n async function runInterpret(cmds: readonly C[]): Promise<void> {\n for (const cmd of cmds) {\n const handler = interpretMap[cmd.type as C[\"type\"]];\n if (!handler) continue;\n const follow = await trackInFlight(\n handler(\n cmd as Extract<C, { type: C[\"type\"] }>,\n augmentedCtx,\n dispatchUnawaited,\n ),\n );\n if (follow !== undefined && follow !== null) {\n // The follow-up's rejection has no caller (the original dispatcher\n // resolved), so route it to the sink (invariant 6); name a failure Msg\n // via `tryInterpret` to fold it back into state.\n enqueueDispatch(follow as M).catch(reportUndelivered);\n }\n }\n }\n\n // Throws isolated so one bad listener does not strand the others.\n function fireListeners(): void {\n fanout(listeners, \"listener\", (listener) => listener());\n }\n\n // After `fireListeners` so observers see what subscribers see. APPLIED\n // transitions only — `msg` is total (boot routes to `fireBoot`).\n function fireObservers(msg: M): void {\n if (observers.size === 0 || state === undefined) return;\n const snapshot = state;\n fanout(observers, \"observer\", (observer) => observer(msg, snapshot));\n }\n\n // Project the transition to semantic events and fan each to its `on(...)`\n // handlers; a throw in the projector OR a handler is isolated.\n function fireEvents(msg: M): void {\n if (eventHandlers.size === 0 || state === undefined) return;\n let events: readonly E[];\n try {\n events = projectEvents(msg, state);\n } catch (err) {\n reportError(err, { phase: \"event\" });\n return;\n }\n for (const event of events) {\n const bucket = eventHandlers.get(event.type);\n if (bucket === undefined) continue;\n fanout(bucket, \"event\", (handler) => handler(event));\n }\n }\n\n // Fire every `onBoot` handler with the initial State ONCE and flip `booted`.\n function fireBoot(): void {\n booted = true;\n if (bootHandlers.size === 0 || state === undefined) return;\n const snapshot = state;\n fanout(bootHandlers, \"boot\", (handler) => handler(snapshot));\n }\n\n // Resolve every parked `done()` waiter iff the just-folded State is terminal —\n // so `done()` settles on the same transition `result()` first returns it.\n function settleDoneWaiters(): void {\n if (doneWaiters.size === 0 || state === undefined) return;\n if (!isTerminal(state)) return;\n const snapshot = state;\n const parked = [...doneWaiters];\n doneWaiters.clear();\n for (const resolve of parked) resolve(snapshot);\n }\n\n // The post-transition commit tail: install state, then save → reconcile subs →\n // interpret → fire fanout → settle done-waiters. Save-before-effects is the\n // hard ordering; tests pin it. The normal transition and the `restart`\n // supervision branch both end here.\n async function commit(next: S, msg: M, cmds: readonly C[]): Promise<void> {\n state = next;\n if (store) await store.save(next);\n reconcileSubs();\n await runInterpret(cmds);\n fireListeners();\n fireObservers(msg);\n fireEvents(msg);\n settleDoneWaiters();\n }\n\n /**\n * Instance-identity filter: is this message addressed to a DIFFERENT instance\n * than the one this state owns? When the machine declares an `identity`, a\n * `true` here drops the message before `update` runs, so the reducer never\n * sees a foreign-instance message and no cell needs a\n * `msg.runId !== state.runId` guard.\n *\n * A message with no identity (`ofMsg` → undefined) is identity-agnostic and\n * always proceeds. `ofState` → undefined means this instance has no identity\n * to defend yet (an `idle` state before any run is established), so an\n * addressed message there is identity-ESTABLISHING, not foreign. Drop only\n * when both identities are present and differ.\n *\n * Pure and synchronous; a throw is the caller's to supervise (see\n * `stepDispatch`).\n */\n function isMisaddressed(msg: M, current: S): boolean {\n if (machine.identity === undefined) return false;\n const addressed = machine.identity.ofMsg(msg);\n if (addressed === undefined) return false;\n const own = machine.identity.ofState(current);\n if (own === undefined) return false;\n return structuralHash(addressed) !== structuralHash(own);\n }\n\n /**\n * One full transition: update → save → reconcile subs → interpret → fire.\n * Save-before-effects is the hard ordering; tests pin it. A `save` throw\n * propagates after the in-memory `state` is advanced; reconcile/interpret\n * throws after `save` has succeeded.\n */\n async function stepDispatch(msg: M): Promise<void> {\n if (state === undefined) {\n throw new Error(\"@demlik/tea: runtime not booted\");\n }\n // The identity filter and the reducer are ONE synchronous user-code step,\n // and they are protected as one. The projections used to run above this\n // `try`, so a throwing `ofMsg` / `ofState` was neither reported nor\n // supervised while an identical throw one line later was both — and since\n // `structuralHash` throws on a bigint or a non-plain value, a snowflake-style\n // run id put EVERY dispatch on that unprotected path.\n //\n // The reentrancy brand guarantees this whole step cannot suspend, so a throw\n // here is clean with `state` still pre-transition; it routes to the declared\n // supervision strategy under `phase: \"reduce\"` — the transition's own\n // synchronous code failed, whichever half of it that was. Folding goes\n // through `applyCellChecked` — the single dev-checked\n // reducer-vs-transitions primitive `foldUpdates` also folds through (see\n // `pure/core.ts`).\n let next: S;\n let cmds: readonly C[];\n try {\n if (isMisaddressed(msg, state)) {\n // The drop advances nothing: no save, no reconcile, no interpret, no\n // listener/observer/event fire, no done-waiter settle — and it RESOLVES\n // the dispatch, because a mis-addressed message is not the caller's\n // error. That is exactly why it must be reported: without this the\n // caller cannot tell \"applied\" from \"silently discarded\", and a reusable\n // Durable Object serving run A then run B lost run B while reporting\n // success. `IdentityDropNotice` is a `RuntimeDiscardNotice` — warn by\n // default, never fatal (invariant 6: the enforcement point is\n // observable, which is what makes it ONE point).\n reportError(new IdentityDropNotice(msg.type), {\n phase: \"identity-drop\",\n });\n return;\n }\n [next, cmds] = applyCellChecked<S, M, C>(machine, state, msg);\n } catch (reduceError) {\n // Invariant 6 — surface the failure as data FIRST, for every strategy.\n reportError(reduceError, { phase: \"reduce\" });\n switch (supervision.strategy) {\n case \"restart\": {\n // Host supplies last-known-good state; core installs it and KEEPS\n // FOLDING from there with no cmds (the throwing reduce produced none).\n // A throw inside `rehydrate` is NOT caught — it surfaces to the caller.\n await commit(supervision.rehydrate(state, msg, reduceError), msg, []);\n return;\n }\n case \"escalate\":\n // Surface + propagate; the runtime stays live for a parent supervisor.\n throw reduceError;\n default:\n // `stop` (safe default): halt. State is NOT advanced; propagate so THIS\n // dispatch also rejects — the halt is observable, never a silent resume.\n gate = \"closed\";\n throw reduceError;\n }\n }\n await commit(next, msg, cmds);\n }\n\n // Boot has two phases. With NO store, `loaded = null` without an await — run\n // `init(null, ctx)` IMMEDIATELY so `getState()` is observable synchronously\n // (what `useSyncExternalStore` consumers need to render the first commit\n // without a flicker). The async remainder runs in `stepBootEffects` as the\n // first tail entry. With a store we cannot `init` synchronously (we'd invent a\n // loaded value), so the full path runs there; `store.load` throws propagate\n // via the boot promise.\n let pendingInitCmds: readonly C[] = [];\n if (!store) {\n const [initialState, initCmds] = machine.init(null, ctx);\n state = initialState;\n pendingInitCmds = initCmds;\n }\n\n async function stepBootEffects(): Promise<void> {\n if (store) {\n // Boundary parse (invariant 8): `store.load()` returns `unknown`;\n // `store.migrate(raw)` is the required parse — `S` on recognized shape,\n // `null` on unrecognized (boots fresh). `migrate` MUST NOT throw; if it\n // does we surface via the boot promise (same as a `load` throw).\n const raw = await store.load();\n const parsed = store.migrate(raw);\n const [initialState, initCmds] = machine.init(parsed, ctx);\n state = initialState;\n pendingInitCmds = initCmds;\n await store.save(state);\n }\n reconcileSubs();\n await runInterpret(pendingInitCmds);\n fireListeners();\n // Boot has no applied Msg — deliver the initial State via `onBoot`, not\n // `observe`, and project no semantic event.\n fireBoot();\n // A rehydrated boot can land terminal — settle any pre-boot `done()` waiter.\n settleDoneWaiters();\n }\n\n /**\n * Enqueue a dispatch on the tail — the single gate re-entrant interpret /\n * subscribe calls also go through. Rejects when the gate is not open (with a\n * `DispatchDiscardedError` while `stop()` drains, a plain stopped Error once it\n * has), boot failed, or the reducer / save / sub start / interpret throws.\n */\n function enqueueDispatch(msg: M): Promise<void> {\n if (gate !== \"open\") {\n // The gate's state at refusal time IS the classification — the report\n // sites read the error class, never the message.\n return Promise.reject(\n gate === \"draining\"\n ? new DispatchDiscardedError(msg.type)\n : new Error(\"@demlik/tea: runtime stopped\"),\n );\n }\n const next = tail.then(() => {\n if (bootError !== null) throw bootError;\n return stepDispatch(msg);\n });\n // Swallow the rejection on the tail so one failing dispatch does NOT poison\n // every subsequent one. The original `next` still rejects for the caller.\n tail = next.catch(() => {});\n return next;\n }\n\n /**\n * Drain the serial tail to quiescence. Every interpret follow-up calls\n * `enqueueDispatch`, which reassigns `tail` SYNCHRONOUSLY before the parent\n * step resolves, so awaiting the current `tail` and re-reading it catches every\n * transitively enqueued follow-up; loop until the reference is stable. Bounded\n * by `idleCap` — on cap REJECT with `QuiescenceTimeoutError` (never a silent\n * resolve), keeping a livelock distinguishable from quiescence (invariant 6).\n */\n async function drainToQuiescence(): Promise<void> {\n for (let i = 0; i < idleCap; i++) {\n const observed = tail;\n await observed;\n if (tail === observed) return;\n }\n throw new QuiescenceTimeoutError(idleCap);\n }\n\n /**\n * The public `dispatch`: run-to-quiescence by default. Await the Msg's OWN\n * transition first (so a reducer / save / interpret throw on THIS Msg surfaces\n * before any drain), then — unless `{ settle: \"once\" }` — drain the transitive\n * follow-up chain.\n */\n async function dispatchToQuiescence(\n msg: M,\n opts?: { readonly settle?: DispatchSettle },\n ): Promise<void> {\n await enqueueDispatch(msg);\n if (opts?.settle === \"once\") return;\n await drainToQuiescence();\n }\n\n // `bootPromise` is the un-swallowed promise `runtime.ready` chains off (so\n // callers see the boot error directly); `tail` gets the swallowed branch so a\n // boot failure does NOT poison every subsequent dispatch's chain (each surfaces\n // `bootError` in `enqueueDispatch`).\n const bootPromise = stepBootEffects();\n tail = bootPromise.catch((err) => {\n bootError = err;\n });\n\n // `ready` chains off `bootPromise` so a failed boot never hands out a `Runtime`\n // (keeping `getState()` total). The `runtime` forward reference resolves at\n // `.then` time, after the literal below initializes. Idempotent.\n const readyPromise: Promise<Runtime<S, M, E>> = bootPromise.then(\n () => runtime,\n );\n\n const runtime: Runtime<S, M, E> = {\n dispatch: dispatchToQuiescence,\n dispatchOnce: enqueueDispatch,\n getState(): S {\n // TOTAL. A `Runtime` is only obtainable by awaiting `ready`, which resolves\n // AFTER boot set `state`; a failed boot rejects `ready`, so no `Runtime` is\n // handed out then. The cast encodes that invariant.\n return state as S;\n },\n subscribe(listener: () => void): () => void {\n listeners.add(listener);\n return () => {\n listeners.delete(listener);\n };\n },\n observe(observer: (msg: M, state: S) => void): () => void {\n observers.add(observer);\n return () => {\n observers.delete(observer);\n };\n },\n onBoot(handler: (state: S) => void): () => void {\n // Already booted → fire immediately (cleanup is a no-op) so a late\n // subscriber never misses the one-shot boot.\n if (booted && state !== undefined) {\n const snapshot = state;\n fanout([handler], \"boot\", (h) => h(snapshot));\n return () => {};\n }\n bootHandlers.add(handler);\n return () => {\n bootHandlers.delete(handler);\n };\n },\n on<K extends E[\"type\"]>(\n type: K,\n handler: (event: Extract<E, { type: K }>) => void,\n ): () => void {\n // Monomorphic registry; per-type narrowing lives at THIS call site, and\n // `fireEvents` only routes an event to its own bucket, so the erased call\n // is sound.\n let bucket = eventHandlers.get(type);\n if (bucket === undefined) {\n bucket = new Set<(event: E) => void>();\n eventHandlers.set(type, bucket);\n }\n const erased = handler as (event: E) => void;\n bucket.add(erased);\n return () => {\n const set = eventHandlers.get(type);\n if (set === undefined) return;\n set.delete(erased);\n if (set.size === 0) eventHandlers.delete(type);\n };\n },\n subscribePort<T>(port: Port<T>, listener: (value: T) => void): () => void {\n const key = port as Port<unknown>;\n let bucket = portRegistry.get(key);\n if (!bucket) {\n bucket = new Set<(value: unknown) => void>();\n portRegistry.set(key, bucket);\n }\n // Bucket stores `(value: unknown) => void`; per-port type safety at this site.\n const erased = listener as (value: unknown) => void;\n bucket.add(erased);\n return () => {\n const set = portRegistry.get(key);\n if (!set) return;\n set.delete(erased);\n if (set.size === 0) portRegistry.delete(key);\n };\n },\n emitPort<T>(port: Port<T>, value: T): void {\n // Lets `observe`-driven Port emission work without a per-transition Cmd.\n portEmit(port, value);\n },\n ready: readyPromise,\n idle(): Promise<void> {\n return drainToQuiescence();\n },\n result(): S | undefined {\n // TOTAL. No predicate → `isTerminal` is `() => false`, so a non-terminating\n // machine reads `undefined` forever.\n const current = state as S;\n return isTerminal(current) ? current : undefined;\n },\n done(): Promise<S> {\n // Already terminal → resolve at once (never parks, so it can't miss the\n // terminal transition). Otherwise park a resolver for `settleDoneWaiters`.\n const current = state as S;\n if (isTerminal(current)) return Promise.resolve(current);\n return new Promise<S>((resolve) => {\n doneWaiters.add(resolve);\n });\n },\n async stop(): Promise<void> {\n // Open the discard window unless the gate is already `\"closed\"` (a\n // supervision halt, or a redundant second `stop()`): the gate only\n // advances, so a Msg refused after a halt or after `stop()` returned stays\n // a loud error rather than being re-labelled a teardown discard.\n if (gate === \"open\") gate = \"draining\";\n // Report BEFORE the drain: after `await tail` the count is zero by\n // construction, and the fact worth surfacing is what was outstanding at\n // the moment the host let go. `stop()` drains, but every consumer of the\n // resulting transitions (listeners, observers, event handlers) is being\n // torn down with it, so those Cmds' results reach nobody (issue #365).\n if (inFlightCmds > 0) {\n reportError(new RuntimeDiscardedError(inFlightCmds), {\n phase: \"discard\",\n });\n }\n // Drain in-flight work. Tail rejections were swallowed at enqueue time, so\n // this await always resolves. The drain TERMINATES because the gate refuses\n // the follow-ups those handlers return — allowing them in instead would let\n // a handler chain extend the tail without end.\n await tail;\n // The barrier is now absolute: everything that was in flight has settled,\n // so a dispatch from here on is a consumer using a retired runtime.\n gate = \"closed\";\n // Run every active sub cleanup; throws isolated and routed to the sink.\n stopSubs([...subRegistry.keys()]);\n // Dispose every live dep-keyed source too (same throw-isolation).\n disposeDepSubs([...depSubRegistry.keys()]);\n // …then WAIT for the teardown work that is still settling — the ones\n // above, plus any async cleanup a mid-run reconcile started. `stop()`\n // resolving has to mean \"teardown is done\", or a host doing\n // `await stop(); env.evict()` drops the isolate mid-release and re-creates\n // the exact leak the managed-resource battery exists to prevent. Bounded\n // by `disposeTimeoutMs` so a release that never settles cannot hold the\n // host open (see `drainDisposals`).\n await drainDisposals();\n // Flush final state. A save throw here does not reject `stop()` (contract:\n // resolves regardless) but IS loss of the last write, so route it to the\n // sink rather than swallowing it (invariant 6).\n if (store && state !== undefined && bootError === null) {\n try {\n await store.save(state);\n } catch (error) {\n reportError(error, { phase: \"stop-save\" });\n }\n }\n },\n };\n\n return runtime;\n}\n"]}
|
|
@@ -1,4 +1,4 @@
|
|
|
1
|
-
import { fromInterval } from './chunk-
|
|
1
|
+
import { fromInterval } from './chunk-3JU6657S.js';
|
|
2
2
|
|
|
3
3
|
// src/cache/index.ts
|
|
4
4
|
function initCache() {
|
|
@@ -48,5 +48,5 @@ function cacheEvictionSub(id, everyMs) {
|
|
|
48
48
|
var cacheEvictionSubscribe = fromInterval((sub) => cacheEvictMsg(sub.id));
|
|
49
49
|
|
|
50
50
|
export { cacheEvictMsg, cacheEvictionSub, cacheEvictionSubscribe, evictExpired, get, has, initCache, remove, set };
|
|
51
|
-
//# sourceMappingURL=chunk-
|
|
52
|
-
//# sourceMappingURL=chunk-
|
|
51
|
+
//# sourceMappingURL=chunk-7SVIRMC6.js.map
|
|
52
|
+
//# sourceMappingURL=chunk-7SVIRMC6.js.map
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"sources":["../src/cache/index.ts"],"names":[],"mappings":";;;AAuEO,SAAS,SAAA,GAA4B;AAC1C,EAAA,OAAO,EAAE,OAAA,EAAS,EAAC,EAAE;AACvB;AAQA,SAAS,SAAA,CAAa,OAAsB,KAAA,EAAwB;AAClE,EAAA,OAAO,SAAS,KAAA,CAAM,WAAA;AACxB;AAQO,SAAS,GAAA,CACd,KAAA,EACA,GAAA,EACA,KAAA,EACe;AACf,EAAA,MAAM,KAAA,GAAQ,KAAA,CAAM,OAAA,CAAQ,GAAG,CAAA;AAC/B,EAAA,IAAI,UAAU,MAAA,IAAa,SAAA,CAAU,KAAA,EAAO,KAAK,GAAG,OAAO,MAAA;AAC3D,EAAA,OAAO,KAAA,CAAM,KAAA;AACf;AAMO,SAAS,GAAA,CACd,KAAA,EACA,GAAA,EACA,KAAA,EACS;AACT,EAAA,MAAM,KAAA,GAAQ,KAAA,CAAM,OAAA,CAAQ,GAAG,CAAA;AAC/B,EAAA,IAAI,KAAA,KAAU,QAAW,OAAO,KAAA;AAChC,EAAA,OAAO,CAAC,SAAA,CAAU,KAAA,EAAO,KAAK,CAAA;AAChC;AAQO,SAAS,GAAA,CACd,KAAA,EACA,GAAA,EACA,KAAA,EACA,OACA,KAAA,EACa;AACb,EAAA,MAAM,WAAA,GAA6C,EAAE,GAAG,KAAA,CAAM,OAAA,EAAQ;AACtE,EAAA,WAAA,CAAY,GAAG,CAAA,GAAI,EAAE,KAAA,EAAO,WAAA,EAAa,QAAQ,KAAA,EAAM;AACvD,EAAA,OAAO,EAAE,GAAG,KAAA,EAAO,OAAA,EAAS,WAAA,EAAY;AAC1C;AAOO,SAAS,MAAA,CAAU,OAAoB,GAAA,EAA0B;AACtE,EAAA,MAAM,WAAA,GAA6C,EAAE,GAAG,KAAA,CAAM,OAAA,EAAQ;AACtE,EAAA,OAAO,YAAY,GAAG,CAAA;AACtB,EAAA,OAAO,EAAE,GAAG,KAAA,EAAO,OAAA,EAAS,WAAA,EAAY;AAC1C;AASO,SAAS,YAAA,CACd,OACA,KAAA,EACa;AACb,EAAA,MAAM,cAA6C,EAAC;AACpD,EAAA,KAAA,MAAW,CAAC,KAAK,KAAK,CAAA,IAAK,OAAO,OAAA,CAAQ,KAAA,CAAM,OAAO,CAAA,EAAG;AACxD,IAAA,IAAI,SAAA,CAAU,KAAA,EAAO,KAAK,CAAA,EAAG;AAC7B,IAAA,WAAA,CAAY,GAAG,CAAA,GAAI,KAAA;AAAA,EACrB;AACA,EAAA,OAAO,EAAE,GAAG,KAAA,EAAO,OAAA,EAAS,WAAA,EAAY;AAC1C;AAmCO,SAAS,cAAc,EAAA,EAA2B;AACvD,EAAA,OAAO,EAAE,IAAA,EAAM,aAAA,EAAe,EAAA,EAAG;AACnC;AAqBO,SAAS,gBAAA,CACd,IACA,OAAA,EACkB;AAClB,EAAA,OAAO;AAAA,IACL,EAAA;AAAA,IACA,IAAA,EAAM,OAAA;AAAA,IACN,UAAA,EAAY;AAAA,GACd;AACF;AAcO,IAAM,yBAIT,YAAA,CAAa,CAAC,QAAQ,aAAA,CAAc,GAAA,CAAI,EAAE,CAAC","file":"chunk-WF46FABO.js","sourcesContent":["/**\n * @demlik/tea/cache — a TTL cache as pure state, PLUS a periodic eviction Sub.\n *\n * Same pure-state-ops shape as `@demlik/tea/idempotency` and\n * `@demlik/tea/work-queue`: a state type plus pure transition functions. Host-\n * agnostic by construction — no `Date.now()`, no timers, no I/O inside the ops.\n * The caller injects the clock (`nowMs`) and folds the result through its own\n * reducer, keeping the store inside TEA's invariant 2 (transitions are pure:\n * same inputs → same outputs) so a consumer can exercise it with `replay`\n * without a fake clock.\n *\n * Every op returns a NEW cache value and never mutates its input (invariant 1 —\n * state is a value, not a place). `get` / `has` are read-shaped; `set` /\n * `remove` / `evictExpired` return a new cache.\n *\n * **Distinct from `/idempotency`.** Both are TTL-bounded key maps, but they\n * answer different questions and so carry different shapes:\n *\n * - `/idempotency` is DEDUPE-focused — \"have I already processed this key?\"\n * TTL is a single store-level policy (`ttlMs`), each entry stores the\n * `atMs` it was remembered at, and expiry is derived (`now - atMs >=\n * ttlMs`). It carries an `order` ledger + `capacity` because the use case\n * (webhook receivers, retried messages) needs bounded LRU eviction.\n * - `/cache` is RETRIEVAL-focused — \"what is the value for this key right\n * now?\" TTL is set PER ENTRY at write time: `set(key, value, nowMs,\n * ttlMs)` bakes the absolute `expiresAtMs = nowMs + ttlMs` into the entry.\n * Different keys can carry different lifetimes (a hot key cached for 1s, a\n * cold one for 1h) — the dimension idempotency's single store-level\n * `ttlMs` cannot express. No `order`, no `capacity`: a cache bounds itself\n * by TTL + periodic eviction, not by LRU count.\n *\n * The eviction Sub is the \"eviction is a Msg\" half: a `setInterval`-shaped Sub\n * (composed directly on `@demlik/tea/subs`' `fromInterval`) that periodically\n * dispatches a `cache_evict` Msg the consumer's reducer handles by calling\n * `evictExpired`. Time still never enters the reducer — the Sub fires the Msg,\n * the reducer reads `nowMs` off the Msg (the consumer stamps it at the Sub\n * boundary), and `evictExpired` does the pure drop.\n *\n * NOT exported from the package root — reached via the `@demlik/tea/cache`\n * subpath, same one-shape-per-package rule as `work-queue`, `idempotency`, and\n * `rate-limit`.\n */\n\nimport type { Sub } from \"../index\";\nimport { fromInterval } from \"../subs\";\nimport type { SubscribeHandler } from \"../subs/types\";\n\n/**\n * A cached entry: the `value` and the absolute clock reading `expiresAtMs` it\n * expires at. Unlike `/idempotency`'s entry (which stores `atMs` and derives\n * expiry from a store-level `ttlMs`), the expiry instant is baked in at write\n * time — so per-entry TTLs are a first-class capability. Both fields are plain\n * data so the whole cache is trivially JSON-serializable into a consumer's\n * `Store<S>`.\n */\nexport interface CacheEntry<V> {\n readonly value: V;\n readonly expiresAtMs: number;\n}\n\n/**\n * The cache: a map of `key → entry`. No `order` ledger and no `capacity` (cf.\n * `/idempotency`) — a TTL cache bounds itself by expiry + periodic eviction,\n * not by LRU count. Each entry carries its own `expiresAtMs`, so the cache\n * needs no store-level TTL policy field.\n */\nexport interface TtlCache<V> {\n readonly entries: Readonly<Record<string, CacheEntry<V>>>;\n}\n\n/** Create an empty cache. */\nexport function initCache<V>(): TtlCache<V> {\n return { entries: {} };\n}\n\n/**\n * Whether `entry` is expired at `nowMs`. Half-open: an entry whose\n * `expiresAtMs` exactly equals `nowMs` is expired (`nowMs >= expiresAtMs`),\n * matching `/idempotency` and `/rate-limit`'s window-edge cutoff — a value\n * cached at `t` with a `ttlMs` lifetime is gone at exactly `t + ttlMs`.\n */\nfunction isExpired<V>(entry: CacheEntry<V>, nowMs: number): boolean {\n return nowMs >= entry.expiresAtMs;\n}\n\n/**\n * The cached `value` for `key` iff present AND unexpired at `nowMs`, else\n * `undefined`. An expired entry reads as absent even though it is physically\n * still in `entries` until the next `evictExpired` (or an overwriting `set`)\n * drops it. PURE — reads only, never mutates.\n */\nexport function get<V>(\n cache: TtlCache<V>,\n key: string,\n nowMs: number,\n): V | undefined {\n const entry = cache.entries[key];\n if (entry === undefined || isExpired(entry, nowMs)) return undefined;\n return entry.value;\n}\n\n/**\n * True iff `key` is present AND unexpired at `nowMs`. The boolean sibling of\n * `get` for callers that only need presence, not the value. PURE.\n */\nexport function has<V>(\n cache: TtlCache<V>,\n key: string,\n nowMs: number,\n): boolean {\n const entry = cache.entries[key];\n if (entry === undefined) return false;\n return !isExpired(entry, nowMs);\n}\n\n/**\n * Write `key → value` with an absolute expiry of `nowMs + ttlMs`. Re-`set`ting\n * an existing key overwrites both the value and the expiry — the TTL clock\n * restarts from the new `nowMs`. PURE — returns a new cache; the input is\n * untouched (a fresh shallow copy of `entries`).\n */\nexport function set<V>(\n cache: TtlCache<V>,\n key: string,\n value: V,\n nowMs: number,\n ttlMs: number,\n): TtlCache<V> {\n const nextEntries: Record<string, CacheEntry<V>> = { ...cache.entries };\n nextEntries[key] = { value, expiresAtMs: nowMs + ttlMs };\n return { ...cache, entries: nextEntries };\n}\n\n/**\n * Drop `key` regardless of expiry. PURE — returns a new cache; the input is\n * untouched. Removing an absent key returns an equivalent cache (the shallow\n * copy still happens, but no entry is dropped).\n */\nexport function remove<V>(cache: TtlCache<V>, key: string): TtlCache<V> {\n const nextEntries: Record<string, CacheEntry<V>> = { ...cache.entries };\n delete nextEntries[key];\n return { ...cache, entries: nextEntries };\n}\n\n/**\n * Physically drop every entry expired at `nowMs` (`nowMs >= expiresAtMs`).\n * Returns a NEW cache with the survivors. This is the pure work the eviction\n * Sub's `cache_evict` Msg triggers — `get` / `has` already treat expired\n * entries as absent, so eviction is purely a memory-reclaim pass, not a\n * correctness one. PURE — the input cache is never mutated.\n */\nexport function evictExpired<V>(\n cache: TtlCache<V>,\n nowMs: number,\n): TtlCache<V> {\n const nextEntries: Record<string, CacheEntry<V>> = {};\n for (const [key, entry] of Object.entries(cache.entries)) {\n if (isExpired(entry, nowMs)) continue;\n nextEntries[key] = entry;\n }\n return { ...cache, entries: nextEntries };\n}\n\n// ---------------------------------------------------------------------------\n// Eviction Sub — \"eviction is a Msg\".\n//\n// A periodic timer Sub that dispatches `cache_evict` so the consumer's reducer\n// can call `evictExpired`. It is `fromInterval` specialized to a fixed Msg\n// shape — the factory carries the interval ON the Sub (per the substrate's\n// reconcile-by-id contract) and dispatches a `cache_evict` Msg tagged with the\n// Sub's own `id`, so a consumer running several caches can route each tick to\n// the right cache by `msg.id`.\n//\n// Mirrors `fromInterval` exactly (factory + SubscribeHandler cell + exported\n// Msg constructor); it does NOT reimplement the timer lifecycle — it composes\n// directly on `fromInterval`, which owns `setInterval` / `clearInterval`. That\n// keeps the timer-handle bookkeeping (and its cleanup) in one place and this\n// module purely declarative.\n//\n// Strengthens invariant 4 (external time is a Sub, reconciled by id) and\n// invariant 9 (named, small Sub surface composed on the shared factory).\n// ---------------------------------------------------------------------------\n\n/**\n * The Msg the eviction Sub dispatches each tick. `id` is the Sub's own id so a\n * consumer with multiple caches can route the tick to the matching cache. The\n * reducer reads `Date.now()`-free time by stamping `nowMs` itself at the Sub\n * boundary if needed; the canonical reducer cell is\n * `cache_evict: (s) => [{ ...s, cache: evictExpired(s.cache, nowMs) }, []]`.\n */\nexport interface CacheEvictMsg {\n readonly type: \"cache_evict\";\n readonly id: string;\n}\n\n/** Construct a `cache_evict` Msg for the cache identified by `id`. */\nexport function cacheEvictMsg(id: string): CacheEvictMsg {\n return { type: \"cache_evict\", id };\n}\n\n/**\n * The Sub shape the eviction factory installs: a `setInterval`-shaped Sub\n * carrying its tick period (`intervalMs`) per the substrate's\n * reconcile-by-id-with-data-on-the-Sub contract. `type: \"cache\"` is the\n * lowercase source-noun Sub convention (canon §2.5 / naming-style).\n */\nexport type CacheEvictionSub = Sub<\"cache\"> & { readonly intervalMs: number };\n\n/**\n * Declare an eviction Sub for the cache identified by `id`, ticking every\n * `everyMs`. Drop the result into `subscriptions(state)`:\n *\n * subscriptions: (s) => [cacheEvictionSub(\"session-cache\", 30_000)]\n *\n * Same id ⇒ same running interval across transitions (no reconcile churn);\n * changing `everyMs` alone will NOT re-arm the timer (the substrate leaves a\n * same-id Sub running) — change the `id` too if the period must change. This\n * mirrors `fromInterval`'s data-on-the-Sub tradeoff exactly.\n */\nexport function cacheEvictionSub(\n id: string,\n everyMs: number,\n): CacheEvictionSub {\n return {\n id: id as CacheEvictionSub[\"id\"],\n type: \"cache\",\n intervalMs: everyMs,\n };\n}\n\n/**\n * The `subscribe` cell for the eviction Sub. Assign it to\n * `machine.subscribe.cache`:\n *\n * subscribe: { cache: cacheEvictionSubscribe }\n *\n * Composed directly on `fromInterval` — it owns the `setInterval` /\n * `clearInterval` lifecycle; this cell only pins the Msg the tick dispatches\n * (`cacheEvictMsg(sub.id)`). The cleanup `fromInterval` returns stops the\n * interval when the Sub is reconciled out (the cache's state exits the phase\n * that declared it).\n */\nexport const cacheEvictionSubscribe: SubscribeHandler<\n CacheEvictionSub,\n CacheEvictMsg,\n unknown\n> = fromInterval((sub) => cacheEvictMsg(sub.id));\n"]}
|
|
1
|
+
{"version":3,"sources":["../src/cache/index.ts"],"names":[],"mappings":";;;AAuEO,SAAS,SAAA,GAA4B;AAC1C,EAAA,OAAO,EAAE,OAAA,EAAS,EAAC,EAAE;AACvB;AAQA,SAAS,SAAA,CAAa,OAAsB,KAAA,EAAwB;AAClE,EAAA,OAAO,SAAS,KAAA,CAAM,WAAA;AACxB;AAQO,SAAS,GAAA,CACd,KAAA,EACA,GAAA,EACA,KAAA,EACe;AACf,EAAA,MAAM,KAAA,GAAQ,KAAA,CAAM,OAAA,CAAQ,GAAG,CAAA;AAC/B,EAAA,IAAI,UAAU,MAAA,IAAa,SAAA,CAAU,KAAA,EAAO,KAAK,GAAG,OAAO,MAAA;AAC3D,EAAA,OAAO,KAAA,CAAM,KAAA;AACf;AAMO,SAAS,GAAA,CACd,KAAA,EACA,GAAA,EACA,KAAA,EACS;AACT,EAAA,MAAM,KAAA,GAAQ,KAAA,CAAM,OAAA,CAAQ,GAAG,CAAA;AAC/B,EAAA,IAAI,KAAA,KAAU,QAAW,OAAO,KAAA;AAChC,EAAA,OAAO,CAAC,SAAA,CAAU,KAAA,EAAO,KAAK,CAAA;AAChC;AAQO,SAAS,GAAA,CACd,KAAA,EACA,GAAA,EACA,KAAA,EACA,OACA,KAAA,EACa;AACb,EAAA,MAAM,WAAA,GAA6C,EAAE,GAAG,KAAA,CAAM,OAAA,EAAQ;AACtE,EAAA,WAAA,CAAY,GAAG,CAAA,GAAI,EAAE,KAAA,EAAO,WAAA,EAAa,QAAQ,KAAA,EAAM;AACvD,EAAA,OAAO,EAAE,GAAG,KAAA,EAAO,OAAA,EAAS,WAAA,EAAY;AAC1C;AAOO,SAAS,MAAA,CAAU,OAAoB,GAAA,EAA0B;AACtE,EAAA,MAAM,WAAA,GAA6C,EAAE,GAAG,KAAA,CAAM,OAAA,EAAQ;AACtE,EAAA,OAAO,YAAY,GAAG,CAAA;AACtB,EAAA,OAAO,EAAE,GAAG,KAAA,EAAO,OAAA,EAAS,WAAA,EAAY;AAC1C;AASO,SAAS,YAAA,CACd,OACA,KAAA,EACa;AACb,EAAA,MAAM,cAA6C,EAAC;AACpD,EAAA,KAAA,MAAW,CAAC,KAAK,KAAK,CAAA,IAAK,OAAO,OAAA,CAAQ,KAAA,CAAM,OAAO,CAAA,EAAG;AACxD,IAAA,IAAI,SAAA,CAAU,KAAA,EAAO,KAAK,CAAA,EAAG;AAC7B,IAAA,WAAA,CAAY,GAAG,CAAA,GAAI,KAAA;AAAA,EACrB;AACA,EAAA,OAAO,EAAE,GAAG,KAAA,EAAO,OAAA,EAAS,WAAA,EAAY;AAC1C;AAmCO,SAAS,cAAc,EAAA,EAA2B;AACvD,EAAA,OAAO,EAAE,IAAA,EAAM,aAAA,EAAe,EAAA,EAAG;AACnC;AAqBO,SAAS,gBAAA,CACd,IACA,OAAA,EACkB;AAClB,EAAA,OAAO;AAAA,IACL,EAAA;AAAA,IACA,IAAA,EAAM,OAAA;AAAA,IACN,UAAA,EAAY;AAAA,GACd;AACF;AAcO,IAAM,yBAIT,YAAA,CAAa,CAAC,QAAQ,aAAA,CAAc,GAAA,CAAI,EAAE,CAAC","file":"chunk-7SVIRMC6.js","sourcesContent":["/**\n * @demlik/tea/cache — a TTL cache as pure state, PLUS a periodic eviction Sub.\n *\n * Same pure-state-ops shape as `@demlik/tea/idempotency` and\n * `@demlik/tea/work-queue`: a state type plus pure transition functions. Host-\n * agnostic by construction — no `Date.now()`, no timers, no I/O inside the ops.\n * The caller injects the clock (`nowMs`) and folds the result through its own\n * reducer, keeping the store inside TEA's invariant 2 (transitions are pure:\n * same inputs → same outputs) so a consumer can exercise it with `replay`\n * without a fake clock.\n *\n * Every op returns a NEW cache value and never mutates its input (invariant 1 —\n * state is a value, not a place). `get` / `has` are read-shaped; `set` /\n * `remove` / `evictExpired` return a new cache.\n *\n * **Distinct from `/idempotency`.** Both are TTL-bounded key maps, but they\n * answer different questions and so carry different shapes:\n *\n * - `/idempotency` is DEDUPE-focused — \"have I already processed this key?\"\n * TTL is a single store-level policy (`ttlMs`), each entry stores the\n * `atMs` it was remembered at, and expiry is derived (`now - atMs >=\n * ttlMs`). It carries an `order` ledger + `capacity` because the use case\n * (webhook receivers, retried messages) needs bounded LRU eviction.\n * - `/cache` is RETRIEVAL-focused — \"what is the value for this key right\n * now?\" TTL is set PER ENTRY at write time: `set(key, value, nowMs,\n * ttlMs)` bakes the absolute `expiresAtMs = nowMs + ttlMs` into the entry.\n * Different keys can carry different lifetimes (a hot key cached for 1s, a\n * cold one for 1h) — the dimension idempotency's single store-level\n * `ttlMs` cannot express. No `order`, no `capacity`: a cache bounds itself\n * by TTL + periodic eviction, not by LRU count.\n *\n * The eviction Sub is the \"eviction is a Msg\" half: a `setInterval`-shaped Sub\n * (composed directly on `@demlik/tea/subs`' `fromInterval`) that periodically\n * dispatches a `cache_evict` Msg the consumer's reducer handles by calling\n * `evictExpired`. Time still never enters the reducer — the Sub fires the Msg,\n * the reducer reads `nowMs` off the Msg (the consumer stamps it at the Sub\n * boundary), and `evictExpired` does the pure drop.\n *\n * NOT exported from the package root — reached via the `@demlik/tea/cache`\n * subpath, same one-shape-per-package rule as `work-queue`, `idempotency`, and\n * `rate-limit`.\n */\n\nimport type { Sub } from \"../index\";\nimport { fromInterval } from \"../subs\";\nimport type { SubscribeHandler } from \"../subs/types\";\n\n/**\n * A cached entry: the `value` and the absolute clock reading `expiresAtMs` it\n * expires at. Unlike `/idempotency`'s entry (which stores `atMs` and derives\n * expiry from a store-level `ttlMs`), the expiry instant is baked in at write\n * time — so per-entry TTLs are a first-class capability. Both fields are plain\n * data so the whole cache is trivially JSON-serializable into a consumer's\n * `Store<S>`.\n */\nexport interface CacheEntry<V> {\n readonly value: V;\n readonly expiresAtMs: number;\n}\n\n/**\n * The cache: a map of `key → entry`. No `order` ledger and no `capacity` (cf.\n * `/idempotency`) — a TTL cache bounds itself by expiry + periodic eviction,\n * not by LRU count. Each entry carries its own `expiresAtMs`, so the cache\n * needs no store-level TTL policy field.\n */\nexport interface TtlCache<V> {\n readonly entries: Readonly<Record<string, CacheEntry<V>>>;\n}\n\n/** Create an empty cache. */\nexport function initCache<V>(): TtlCache<V> {\n return { entries: {} };\n}\n\n/**\n * Whether `entry` is expired at `nowMs`. Half-open: an entry whose\n * `expiresAtMs` exactly equals `nowMs` is expired (`nowMs >= expiresAtMs`),\n * matching `/idempotency` and `/rate-limit`'s window-edge cutoff — a value\n * cached at `t` with a `ttlMs` lifetime is gone at exactly `t + ttlMs`.\n */\nfunction isExpired<V>(entry: CacheEntry<V>, nowMs: number): boolean {\n return nowMs >= entry.expiresAtMs;\n}\n\n/**\n * The cached `value` for `key` iff present AND unexpired at `nowMs`, else\n * `undefined`. An expired entry reads as absent even though it is physically\n * still in `entries` until the next `evictExpired` (or an overwriting `set`)\n * drops it. PURE — reads only, never mutates.\n */\nexport function get<V>(\n cache: TtlCache<V>,\n key: string,\n nowMs: number,\n): V | undefined {\n const entry = cache.entries[key];\n if (entry === undefined || isExpired(entry, nowMs)) return undefined;\n return entry.value;\n}\n\n/**\n * True iff `key` is present AND unexpired at `nowMs`. The boolean sibling of\n * `get` for callers that only need presence, not the value. PURE.\n */\nexport function has<V>(\n cache: TtlCache<V>,\n key: string,\n nowMs: number,\n): boolean {\n const entry = cache.entries[key];\n if (entry === undefined) return false;\n return !isExpired(entry, nowMs);\n}\n\n/**\n * Write `key → value` with an absolute expiry of `nowMs + ttlMs`. Re-`set`ting\n * an existing key overwrites both the value and the expiry — the TTL clock\n * restarts from the new `nowMs`. PURE — returns a new cache; the input is\n * untouched (a fresh shallow copy of `entries`).\n */\nexport function set<V>(\n cache: TtlCache<V>,\n key: string,\n value: V,\n nowMs: number,\n ttlMs: number,\n): TtlCache<V> {\n const nextEntries: Record<string, CacheEntry<V>> = { ...cache.entries };\n nextEntries[key] = { value, expiresAtMs: nowMs + ttlMs };\n return { ...cache, entries: nextEntries };\n}\n\n/**\n * Drop `key` regardless of expiry. PURE — returns a new cache; the input is\n * untouched. Removing an absent key returns an equivalent cache (the shallow\n * copy still happens, but no entry is dropped).\n */\nexport function remove<V>(cache: TtlCache<V>, key: string): TtlCache<V> {\n const nextEntries: Record<string, CacheEntry<V>> = { ...cache.entries };\n delete nextEntries[key];\n return { ...cache, entries: nextEntries };\n}\n\n/**\n * Physically drop every entry expired at `nowMs` (`nowMs >= expiresAtMs`).\n * Returns a NEW cache with the survivors. This is the pure work the eviction\n * Sub's `cache_evict` Msg triggers — `get` / `has` already treat expired\n * entries as absent, so eviction is purely a memory-reclaim pass, not a\n * correctness one. PURE — the input cache is never mutated.\n */\nexport function evictExpired<V>(\n cache: TtlCache<V>,\n nowMs: number,\n): TtlCache<V> {\n const nextEntries: Record<string, CacheEntry<V>> = {};\n for (const [key, entry] of Object.entries(cache.entries)) {\n if (isExpired(entry, nowMs)) continue;\n nextEntries[key] = entry;\n }\n return { ...cache, entries: nextEntries };\n}\n\n// ---------------------------------------------------------------------------\n// Eviction Sub — \"eviction is a Msg\".\n//\n// A periodic timer Sub that dispatches `cache_evict` so the consumer's reducer\n// can call `evictExpired`. It is `fromInterval` specialized to a fixed Msg\n// shape — the factory carries the interval ON the Sub (per the substrate's\n// reconcile-by-id contract) and dispatches a `cache_evict` Msg tagged with the\n// Sub's own `id`, so a consumer running several caches can route each tick to\n// the right cache by `msg.id`.\n//\n// Mirrors `fromInterval` exactly (factory + SubscribeHandler cell + exported\n// Msg constructor); it does NOT reimplement the timer lifecycle — it composes\n// directly on `fromInterval`, which owns `setInterval` / `clearInterval`. That\n// keeps the timer-handle bookkeeping (and its cleanup) in one place and this\n// module purely declarative.\n//\n// Strengthens invariant 4 (external time is a Sub, reconciled by id) and\n// invariant 9 (named, small Sub surface composed on the shared factory).\n// ---------------------------------------------------------------------------\n\n/**\n * The Msg the eviction Sub dispatches each tick. `id` is the Sub's own id so a\n * consumer with multiple caches can route the tick to the matching cache. The\n * reducer reads `Date.now()`-free time by stamping `nowMs` itself at the Sub\n * boundary if needed; the canonical reducer cell is\n * `cache_evict: (s) => [{ ...s, cache: evictExpired(s.cache, nowMs) }, []]`.\n */\nexport interface CacheEvictMsg {\n readonly type: \"cache_evict\";\n readonly id: string;\n}\n\n/** Construct a `cache_evict` Msg for the cache identified by `id`. */\nexport function cacheEvictMsg(id: string): CacheEvictMsg {\n return { type: \"cache_evict\", id };\n}\n\n/**\n * The Sub shape the eviction factory installs: a `setInterval`-shaped Sub\n * carrying its tick period (`intervalMs`) per the substrate's\n * reconcile-by-id-with-data-on-the-Sub contract. `type: \"cache\"` is the\n * lowercase source-noun Sub convention (canon §2.5 / naming-style).\n */\nexport type CacheEvictionSub = Sub<\"cache\"> & { readonly intervalMs: number };\n\n/**\n * Declare an eviction Sub for the cache identified by `id`, ticking every\n * `everyMs`. Drop the result into `subscriptions(state)`:\n *\n * subscriptions: (s) => [cacheEvictionSub(\"session-cache\", 30_000)]\n *\n * Same id ⇒ same running interval across transitions (no reconcile churn);\n * changing `everyMs` alone will NOT re-arm the timer (the substrate leaves a\n * same-id Sub running) — change the `id` too if the period must change. This\n * mirrors `fromInterval`'s data-on-the-Sub tradeoff exactly.\n */\nexport function cacheEvictionSub(\n id: string,\n everyMs: number,\n): CacheEvictionSub {\n return {\n id: id as CacheEvictionSub[\"id\"],\n type: \"cache\",\n intervalMs: everyMs,\n };\n}\n\n/**\n * The `subscribe` cell for the eviction Sub. Assign it to\n * `machine.subscribe.cache`:\n *\n * subscribe: { cache: cacheEvictionSubscribe }\n *\n * Composed directly on `fromInterval` — it owns the `setInterval` /\n * `clearInterval` lifecycle; this cell only pins the Msg the tick dispatches\n * (`cacheEvictMsg(sub.id)`). The cleanup `fromInterval` returns stops the\n * interval when the Sub is reconciled out (the cache's state exits the phase\n * that declared it).\n */\nexport const cacheEvictionSubscribe: SubscribeHandler<\n CacheEvictionSub,\n CacheEvictMsg,\n unknown\n> = fromInterval((sub) => cacheEvictMsg(sub.id));\n"]}
|
|
@@ -1,5 +1,5 @@
|
|
|
1
|
-
import { tryInterpret } from './chunk-
|
|
2
|
-
import { Cmd } from './chunk-
|
|
1
|
+
import { tryInterpret } from './chunk-5EHLEUTL.js';
|
|
2
|
+
import { Cmd } from './chunk-B5VXOFMZ.js';
|
|
3
3
|
|
|
4
4
|
// src/snapshot/index.ts
|
|
5
5
|
var DEFAULT_SNAPSHOT_KEY = "@@snapshot";
|
|
@@ -89,5 +89,5 @@ function createSnapshot(config) {
|
|
|
89
89
|
}
|
|
90
90
|
|
|
91
91
|
export { createSnapshot, snapshotFailed, snapshotLoadFailed, snapshotLoaded, snapshotSaved };
|
|
92
|
-
//# sourceMappingURL=chunk-
|
|
93
|
-
//# sourceMappingURL=chunk-
|
|
92
|
+
//# sourceMappingURL=chunk-7V4EVLZA.js.map
|
|
93
|
+
//# sourceMappingURL=chunk-7V4EVLZA.js.map
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"sources":["../src/snapshot/index.ts"],"names":[],"mappings":"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","file":"chunk-XMHZ44QC.js","sourcesContent":["/**\n * @demlik/tea/snapshot — periodic state checkpoint to a host store (R2/KV-shaped).\n *\n * The problem it solves: a long-running machine (a multi-stage run, a paginated\n * crawl, a saga) lives in TEA's in-memory `State` and is persisted as ONE blob\n * by the substrate's `Store<S>` on every transition. That blob is fine for\n * crash recovery of the *runtime* — but it is NOT a coarse, named checkpoint a\n * human (or a separate recovery job) can point a restart at. `snapshot` adds\n * the \"every N units of progress, write a durable checkpoint to R2/KV\"\n * discipline on top, so a crashed long run restarts from the last checkpoint\n * instead of from zero (the field-guide pain row).\n *\n * Shape: this is the same knob contract every L2 composition follows —\n * `createSnapshot(config)` hands back a `{ init, <verbs>, handlers }` bundle you\n * splice into your machine. The state it owns is a Model slice (durable +\n * replayable, the two non-negotiables); the verbs are PURE decisions; the I/O\n * (the actual `put` to the store) lives only in `handlers`.\n *\n * It is a LEAF module: it imports only the core substrate (`../index`, for\n * `Cmd` / `Interpret`) and no sibling subpath. (It does NOT reuse\n * `@demlik/tea/retry-backoff` — that is an even-leafier utility that depends on\n * nothing at all, not even the core; the kinship here is \"leaf with injected\n * time\", not a shared dependency.)\n * Nothing here reads the clock or the RNG on its own behalf — time enters as an\n * `at`/`now` parameter at the verb boundary and the store I/O is realized only\n * when the runtime performs the emitted `Cmd`. That keeps every verb\n * deterministic (invariant 2) and the checkpoint write a `Cmd` the runtime\n * interprets (invariant 3).\n *\n * The Store port is deliberately ABSTRACT — `{ get, put }`, the lowest common\n * shape of R2 and KV — so this module never imports `@cloudflare/workers-types`\n * and can be checkpointed to anything (R2, KV, a DO sub-key, an in-memory map in\n * tests). The host binds the concrete adapter at `handlers({ store })`.\n *\n * Typical wiring:\n *\n * const snap = createSnapshot<RunState>({ every: 25, key: \"run/checkpoint\" });\n *\n * // machine state:\n * type State = { run: RunState; snapshot: SnapshotState };\n * init: (loaded) => loaded ? [loaded, []] : [{ run: initRun(), snapshot: snap.init() }, []],\n *\n * // in `update`, on each progress Msg, fold the snapshot slice:\n * progress: (s, msg) => {\n * const run = advance(s.run, msg);\n * const [snapshot, cmds] = snap.record(s.snapshot, run, msg.at);\n * return [{ ...s, run, snapshot }, cmds];\n * },\n * // the write completed → durable ack folds back through update:\n * snapshot_saved: (s, msg) => {\n * const [snapshot, cmds] = snap.confirm(s.snapshot, msg);\n * return [{ ...s, snapshot }, cmds];\n * },\n *\n * // RECOVERY — the half that makes a checkpoint a restart point, not just a\n * // backup. A `boot` Msg the host dispatches once after `run(...)` asks the\n * // store for the durable checkpoint; the answer folds the recovered payload\n * // back into the run slice:\n * boot: (s) => snap.requestLoad(s), // emits snapshot_load Cmd\n * snapshot_loaded: (s, msg) => {\n * if (msg.payload === null) return [s, []]; // no checkpoint → keep fresh init\n * const [snapshot, cmds] = snap.boot(s.snapshot);\n * return [{ ...s, run: msg.payload, snapshot }, cmds];\n * },\n * snapshot_load_failed: (s) => [s, []], // errors are data — decide here\n *\n * // splice the I/O — both effect cells (write + load):\n * interpret: { ...snap.handlers({ store: r2Adapter }) },\n */\n\nimport { Cmd, type Interpret, tryInterpret } from \"../index\";\n\n// ─────────────────────────────────────────────────────────────────────────────\n// Config — the knob.\n// ─────────────────────────────────────────────────────────────────────────────\n\n/**\n * The snapshot knob.\n *\n * `every` is the cadence: a checkpoint is written once `every` units of\n * progress have accumulated since the last one (see `record`). It is the\n * `snapshotEvery` field `monitored-run` threads through to this module.\n *\n * `key` is the store key the checkpoint is written under. A single machine\n * keeps a single rolling checkpoint, so the key is fixed at config time rather\n * than passed per write — the latest checkpoint overwrites the previous one\n * (R2/KV are last-write-wins on a key). Defaults to `\"@@snapshot\"`, mirroring\n * `doStore`'s `@@state` default-key convention.\n */\nexport interface SnapshotConfig {\n /**\n * Write a checkpoint once this many progress units have accumulated since the\n * last write. `every: 1` checkpoints on every `record`; a value `<= 0` is\n * treated as `1` (every progress unit triggers a write) — a non-positive\n * cadence has no other sensible meaning, and clamping keeps `record` from\n * never checkpointing on a misconfigured `0`.\n */\n readonly every: number;\n /** Store key the rolling checkpoint is written under. Defaults to `\"@@snapshot\"`. */\n readonly key?: string;\n}\n\nconst DEFAULT_SNAPSHOT_KEY = \"@@snapshot\";\n\n// ─────────────────────────────────────────────────────────────────────────────\n// Slice — the Model field this knob owns.\n// ─────────────────────────────────────────────────────────────────────────────\n\n/**\n * The snapshot bookkeeping slice. Plain data — JSON-serializable so it rides\n * inside the consumer's `Store<S>` blob and survives DO eviction / reload (the\n * \"durable\" non-negotiable). It carries NO payload: the thing being\n * checkpointed lives elsewhere in the machine's state; this slice only tracks\n * *when* to checkpoint and *what has been acknowledged durable*.\n *\n * - `sinceLast` — progress units accumulated since the last checkpoint\n * DECISION (reset to 0 the moment `record` decides to write, not when the\n * write lands). The cadence counter.\n * - `seq` — monotonic checkpoint sequence. Bumped each time a write is\n * emitted, so the write Cmd and its later acknowledgement can be matched\n * even if several are in flight. The \"version\" of the latest emitted\n * checkpoint.\n * - `lastSavedSeq` — the highest `seq` whose write the host has confirmed\n * durable (via `confirm`). `null` until the first confirmed write. The gap\n * `seq - lastSavedSeq` is the \"writes emitted but not yet acknowledged\"\n * depth — observable for backpressure / health.\n * - `lastSavedAt` — the `at` of the most recent confirmed write (epoch ms),\n * `null` until the first. Carried for observability, never compared inside\n * a verb (time is injected, never read here).\n */\nexport interface SnapshotState {\n readonly sinceLast: number;\n readonly seq: number;\n readonly lastSavedSeq: number | null;\n readonly lastSavedAt: number | null;\n}\n\n// ─────────────────────────────────────────────────────────────────────────────\n// Store port — abstract R2/KV-shaped checkpoint sink.\n// ─────────────────────────────────────────────────────────────────────────────\n\n/**\n * The abstract checkpoint sink — the intersection of R2's and KV's surface,\n * narrowed to exactly the two methods a rolling checkpoint needs. Deliberately\n * NOT the substrate's `Store<S>` (`{ load, save, migrate }`): that is the\n * runtime's whole-state persistence boundary keyed at a fixed cell, whereas a\n * checkpoint store is keyed, value-typed, and host-supplied.\n *\n * - `put(key, value)` — write the checkpoint. Last-write-wins on `key`.\n * - `get(key)` — read the latest checkpoint back, or `null` if none. Used by\n * a recovery path to seed `init`'s `loaded` from the durable checkpoint\n * when the runtime's own `Store<S>` blob is gone.\n *\n * No `@cloudflare/workers-types` import — an R2 bucket, a KV namespace, a DO\n * sub-key, or an in-memory `Map` all satisfy this shape with a 3-line adapter\n * at the host.\n */\nexport interface SnapshotStore<V> {\n get(key: string): Promise<V | null>;\n put(key: string, value: V): Promise<void>;\n}\n\n/**\n * The ports `handlers` binds. Just the store today — named so a future port\n * (a metrics sink, a clock) can grow without breaking the `handlers(ports)`\n * call shape.\n */\nexport interface SnapshotPorts<V> {\n readonly store: SnapshotStore<V>;\n}\n\n// ─────────────────────────────────────────────────────────────────────────────\n// Cmd + Msg — the write effect and its durable acknowledgement.\n// ─────────────────────────────────────────────────────────────────────────────\n\n/**\n * The checkpoint-write Cmd a `record` / `force` decision emits. Plain data\n * (invariant 1 — Cmds are data, not closures): it carries the `payload` to\n * write, the `key` to write it under, the `seq` it represents, and the `at` it\n * was decided. The handler performs the `put` and routes the result back to a\n * `confirm` / fail Msg.\n *\n * `payload` is `V` — the consumer's serializable checkpoint value (the run\n * state, the cursor, whatever the machine wants restartable). The verb takes it\n * as an argument so the snapshot slice never has to hold a copy of the\n * machine's domain state.\n */\nexport type SnapshotWriteCmd<V> = Cmd<\"snapshot_write\"> & {\n readonly key: string;\n readonly seq: number;\n readonly at: number;\n readonly payload: V;\n};\n\n/**\n * The Msg the write handler dispatches on a SUCCESSFUL `put`. Fold it back\n * through `confirm(state, msg)` to advance `lastSavedSeq` / `lastSavedAt`. The\n * `seq` matches the emitting Cmd so a late acknowledgement of an older write\n * can never regress a newer confirmed one.\n */\nexport type SnapshotSavedMsg = {\n readonly type: \"snapshot_saved\";\n readonly seq: number;\n readonly at: number;\n};\n\n/**\n * The Msg the write handler dispatches on a FAILED `put`. Errors are data\n * (never swallowed): the consumer's reducer decides whether a failed checkpoint\n * is fatal, retryable (compose `@demlik/tea/retry-backoff`), or merely logged.\n * `seq` identifies which checkpoint failed; `error` is carried, never\n * interpreted here.\n */\nexport type SnapshotFailedMsg = {\n readonly type: \"snapshot_failed\";\n readonly seq: number;\n readonly error: unknown;\n};\n\n/**\n * The checkpoint-READ Cmd a `requestLoad` decision emits — the recovery half of\n * the module. Plain data (invariant 1): it carries only the `key` to read. The\n * handler performs `store.get(key)` and routes the result back to a\n * `snapshot_loaded` (Ok, payload-or-null) / `snapshot_load_failed` (Err) Msg.\n *\n * Distinct from `snapshot_write`: a write carries a payload OUT to the store; a\n * load asks the store to hand a payload BACK. The consumer dispatches the\n * triggering Msg once on resume (a `boot` Msg, never `init`'s rehydrate branch —\n * invariant 2), folds the recovered payload into its run slice, then continues.\n */\nexport type SnapshotLoadCmd = Cmd<\"snapshot_load\"> & {\n readonly key: string;\n};\n\n/**\n * The Msg the load handler dispatches when `store.get` RESOLVES. `payload` is\n * the recovered checkpoint value, or `null` when the store has no checkpoint\n * under `key` (fresh boot — never checkpointed, or a wiped store). The consumer\n * folds this into `init`'s loaded branch: `payload === null` → keep the fresh\n * `init` state; non-null → seed the run slice from the durable checkpoint and\n * `boot` the snapshot slice's cadence counter.\n *\n * A `null` payload is NOT an error — \"no checkpoint yet\" is a normal first-run\n * state, distinct from \"the read failed\" (`snapshot_load_failed`).\n */\nexport type SnapshotLoadedMsg<V> = {\n readonly type: \"snapshot_loaded\";\n readonly payload: V | null;\n};\n\n/**\n * The Msg the load handler dispatches when `store.get` REJECTS. Errors are data\n * (never swallowed — the field-guide rule): a failed recovery read is a visible\n * signal the consumer's reducer decides on (fatal-abort, retry via\n * `@demlik/tea/retry-backoff`, or boot-fresh-and-log). `error` is carried, never\n * interpreted here. Kept separate from `snapshot_loaded`'s `null` so \"the store\n * has no checkpoint\" can never masquerade as \"the read failed\".\n */\nexport type SnapshotLoadFailedMsg = {\n readonly type: \"snapshot_load_failed\";\n readonly error: unknown;\n};\n\n/** Construct a `snapshot_saved` Msg. Shared by the handler and tests so the wire shape can't drift. */\nexport function snapshotSaved(seq: number, at: number): SnapshotSavedMsg {\n return { type: \"snapshot_saved\", seq, at };\n}\n\n/** Construct a `snapshot_failed` Msg. Shared by the handler and tests so the wire shape can't drift. */\nexport function snapshotFailed(seq: number, error: unknown): SnapshotFailedMsg {\n return { type: \"snapshot_failed\", seq, error };\n}\n\n/** Construct a `snapshot_loaded` Msg. Shared by the handler and tests so the wire shape can't drift. */\nexport function snapshotLoaded<V>(payload: V | null): SnapshotLoadedMsg<V> {\n return { type: \"snapshot_loaded\", payload };\n}\n\n/** Construct a `snapshot_load_failed` Msg. Shared by the handler and tests so the wire shape can't drift. */\nexport function snapshotLoadFailed(error: unknown): SnapshotLoadFailedMsg {\n return { type: \"snapshot_load_failed\", error };\n}\n\n// ─────────────────────────────────────────────────────────────────────────────\n// The combinator.\n// ─────────────────────────────────────────────────────────────────────────────\n\n/**\n * The bundle `createSnapshot` returns — the uniform knob contract. `subs` is\n * omitted: a checkpoint is driven by progress (a `record` call from the\n * consumer's reducer), not by a timer, so this module has no subscriptions.\n */\nexport interface SnapshotKnob<V> {\n /** The initial slice — zero progress, no checkpoint emitted or confirmed yet. */\n init(): SnapshotState;\n /**\n * Account one unit of progress. PURE. Increments `sinceLast`; once the\n * accumulated count reaches the configured cadence, DECIDES to checkpoint:\n * resets `sinceLast` to 0, bumps `seq`, and emits a single `snapshot_write`\n * Cmd carrying `payload` + `key` + the new `seq` + `at`. Below the cadence it\n * returns the bumped slice and NO Cmd.\n *\n * `payload` is the checkpoint value to persist (the consumer's domain state);\n * `at` is the caller-stamped time the progress occurred (epoch ms) — injected,\n * never read from the clock here.\n */\n record(\n state: SnapshotState,\n payload: V,\n at: number,\n ): readonly [SnapshotState, readonly Cmd[]];\n /**\n * Force a checkpoint regardless of the cadence counter. PURE. Always bumps\n * `seq`, resets `sinceLast`, and emits the write Cmd. Use on a phase boundary\n * the cadence would otherwise miss — e.g. \"the run just finished, checkpoint\n * the terminal state now\" — so the last durable checkpoint is the final state,\n * not whatever the modulo happened to land on.\n */\n force(\n state: SnapshotState,\n payload: V,\n at: number,\n ): readonly [SnapshotState, readonly Cmd[]];\n /**\n * Record a confirmed durable write. PURE. Advances `lastSavedSeq` /\n * `lastSavedAt` to the acknowledged `seq` / `at`, but only forward: an\n * out-of-order acknowledgement of an OLDER `seq` (a slow write landing after a\n * newer one) is ignored, so the confirmed watermark is monotonic. Fold the\n * `snapshot_saved` Msg here.\n *\n * Returns the reducer-cell shape `readonly [SnapshotState, readonly Cmd[]]`\n * (with `Cmd.none` — an ack emits nothing) for uniformity with every sibling\n * knob, so it drops straight into a `snapshot_saved` cell without the caller\n * re-wrapping a bare State.\n */\n confirm(\n state: SnapshotState,\n msg: SnapshotSavedMsg,\n ): readonly [SnapshotState, readonly Cmd[]];\n /**\n * Resume after a reload. PURE. Resets `sinceLast` to 0 because any\n * un-checkpointed progress accumulated before the crash is moot — recovery\n * resumes from the last DURABLE checkpoint (`get` from the store), so the\n * cadence counter starts fresh from there. `seq` / `lastSavedSeq` /\n * `lastSavedAt` are preserved (they describe what is durable, which a reload\n * does not change). Call from a `boot` Msg the host dispatches once after\n * `run(...)`, never from `init`'s rehydrate branch (invariant 2).\n *\n * Returns the reducer-cell shape `readonly [SnapshotState, readonly Cmd[]]`\n * (with `Cmd.none` — a resume emits nothing) for uniformity with every sibling\n * knob, so it drops straight into a `snapshot_loaded` cell without the caller\n * re-wrapping a bare State.\n */\n boot(state: SnapshotState): readonly [SnapshotState, readonly Cmd[]];\n /**\n * Ask the store for the durable checkpoint. PURE. Emits a single\n * `snapshot_load` Cmd carrying the configured `key`; returns the slice\n * UNCHANGED (a read decides nothing about the cadence bookkeeping — only the\n * later `snapshot_loaded` fold, via `boot`, touches the slice). This is the\n * entry point of the recovery loop: the consumer dispatches a `boot` Msg once\n * after `run(...)`, folds `requestLoad` here, and the emitted Cmd's handler\n * reads `store.get(key)` and dispatches `snapshot_loaded` / `snapshot_load_failed`.\n *\n * Returns `readonly [SnapshotState, readonly Cmd[]]` (the reducer-cell shape)\n * so it drops straight into a `boot: (s) => snap.requestLoad(s)` cell.\n */\n requestLoad(state: SnapshotState): readonly [SnapshotState, readonly Cmd[]];\n /**\n * The `interpret` map for this knob's two effect Cmds:\n *\n * - `snapshot_write` — binds the store and performs the `put`, routing\n * Ok → `snapshot_saved` / Err → `snapshot_failed`.\n * - `snapshot_load` — binds the store and performs the `get`, routing\n * Ok → `snapshot_loaded` (payload-or-null) / Err → `snapshot_load_failed`.\n *\n * Railway throughout — neither effect throws into the runtime; the outcome is\n * always a Msg. Spread into your machine's `interpret`.\n */\n handlers(\n ports: SnapshotPorts<V>,\n ): Interpret<\n | SnapshotSavedMsg\n | SnapshotFailedMsg\n | SnapshotLoadedMsg<V>\n | SnapshotLoadFailedMsg,\n SnapshotWriteCmd<V> | SnapshotLoadCmd,\n unknown\n >;\n}\n\n/**\n * Create a snapshot knob from a `SnapshotConfig`. The returned bundle is spliced\n * into a consumer machine: `init()` seeds the slice, `record` / `force` are\n * folded in reducer cells, `confirm` / `boot` handle the acknowledgement +\n * resume Msgs, and `handlers({ store })` supplies the write `interpret` cell.\n */\nexport function createSnapshot<V>(config: SnapshotConfig): SnapshotKnob<V> {\n // Clamp the cadence to >= 1 at construction so `record`'s threshold check is\n // branchless and a misconfigured `0` / negative `every` checkpoints on every\n // progress unit instead of never.\n const every = config.every >= 1 ? config.every : 1;\n const key = config.key ?? DEFAULT_SNAPSHOT_KEY;\n\n /**\n * Emit the write Cmd for a checkpoint decision. Shared by `record`'s\n * cadence-hit branch and `force`. Bumps `seq`, resets `sinceLast`, returns the\n * new slice + the single `snapshot_write` Cmd. The input slice is never\n * mutated (invariant 1).\n */\n function emitWrite(\n state: SnapshotState,\n payload: V,\n at: number,\n ): readonly [SnapshotState, readonly Cmd[]] {\n const seq = state.seq + 1;\n const next: SnapshotState = { ...state, sinceLast: 0, seq };\n const cmd: SnapshotWriteCmd<V> = {\n type: \"snapshot_write\",\n key,\n seq,\n at,\n payload,\n };\n return [next, [cmd]];\n }\n\n return {\n init(): SnapshotState {\n return { sinceLast: 0, seq: 0, lastSavedSeq: null, lastSavedAt: null };\n },\n\n record(state, payload, at) {\n const sinceLast = state.sinceLast + 1;\n // Below the cadence: accumulate, emit nothing.\n if (sinceLast < every) {\n return [{ ...state, sinceLast }, Cmd.none];\n }\n // Cadence reached: checkpoint. `emitWrite` resets the counter + bumps seq.\n return emitWrite(state, payload, at);\n },\n\n force(state, payload, at) {\n return emitWrite(state, payload, at);\n },\n\n confirm(state, msg) {\n // Forward-only watermark: ignore a stale ack of an older seq so a slow\n // write landing after a newer one can't regress `lastSavedSeq`.\n if (state.lastSavedSeq !== null && msg.seq <= state.lastSavedSeq) {\n return [state, Cmd.none];\n }\n return [\n { ...state, lastSavedSeq: msg.seq, lastSavedAt: msg.at },\n Cmd.none,\n ];\n },\n\n boot(state) {\n return [{ ...state, sinceLast: 0 }, Cmd.none];\n },\n\n requestLoad(state) {\n // A read decides nothing about the cadence bookkeeping — return the slice\n // unchanged and emit the single read Cmd. The recovered payload folds back\n // through `snapshot_loaded` (the consumer's reducer), not here.\n const cmd: SnapshotLoadCmd = { type: \"snapshot_load\", key };\n return [state, [cmd]];\n },\n\n handlers(ports) {\n const { store } = ports;\n return {\n // The write effect. Performs the `put`; on success the saved `seq`/`at`\n // ride the `snapshot_saved` Msg back into `confirm`; on failure the\n // error rides `snapshot_failed`. Railway via the core's `tryInterpret`\n // (matching every other module's handler) — the handler always resolves\n // with a Msg, never rejects into the runtime.\n snapshot_write: tryInterpret<\n SnapshotWriteCmd<V>,\n void,\n SnapshotSavedMsg | SnapshotFailedMsg,\n unknown\n >(\n (cmd) => store.put(cmd.key, cmd.payload),\n (_value, cmd) => snapshotSaved(cmd.seq, cmd.at),\n (error, cmd) => snapshotFailed(cmd.seq, error),\n ),\n // The read effect — the recovery half. Performs the `get`; on success\n // the recovered payload (or `null` when no checkpoint exists) rides the\n // `snapshot_loaded` Msg the consumer folds into `init`'s loaded branch;\n // on failure the error rides `snapshot_load_failed`. Same Railway\n // discipline via `tryInterpret` — the read never rejects into the runtime.\n snapshot_load: tryInterpret<\n SnapshotLoadCmd,\n V | null,\n SnapshotLoadedMsg<V> | SnapshotLoadFailedMsg,\n unknown\n >(\n (cmd) => store.get(cmd.key),\n (payload) => snapshotLoaded(payload),\n (error) => snapshotLoadFailed(error),\n ),\n };\n },\n };\n}\n"]}
|
|
1
|
+
{"version":3,"sources":["../src/snapshot/index.ts"],"names":[],"mappings":"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","file":"chunk-7V4EVLZA.js","sourcesContent":["/**\n * @demlik/tea/snapshot — periodic state checkpoint to a host store (R2/KV-shaped).\n *\n * The problem it solves: a long-running machine (a multi-stage run, a paginated\n * crawl, a saga) lives in TEA's in-memory `State` and is persisted as ONE blob\n * by the substrate's `Store<S>` on every transition. That blob is fine for\n * crash recovery of the *runtime* — but it is NOT a coarse, named checkpoint a\n * human (or a separate recovery job) can point a restart at. `snapshot` adds\n * the \"every N units of progress, write a durable checkpoint to R2/KV\"\n * discipline on top, so a crashed long run restarts from the last checkpoint\n * instead of from zero (the field-guide pain row).\n *\n * Shape: this is the same knob contract every L2 composition follows —\n * `createSnapshot(config)` hands back a `{ init, <verbs>, handlers }` bundle you\n * splice into your machine. The state it owns is a Model slice (durable +\n * replayable, the two non-negotiables); the verbs are PURE decisions; the I/O\n * (the actual `put` to the store) lives only in `handlers`.\n *\n * It is a LEAF module: it imports only the core substrate (`../index`, for\n * `Cmd` / `Interpret`) and no sibling subpath. (It does NOT reuse\n * `@demlik/tea/retry-backoff` — that is an even-leafier utility that depends on\n * nothing at all, not even the core; the kinship here is \"leaf with injected\n * time\", not a shared dependency.)\n * Nothing here reads the clock or the RNG on its own behalf — time enters as an\n * `at`/`now` parameter at the verb boundary and the store I/O is realized only\n * when the runtime performs the emitted `Cmd`. That keeps every verb\n * deterministic (invariant 2) and the checkpoint write a `Cmd` the runtime\n * interprets (invariant 3).\n *\n * The Store port is deliberately ABSTRACT — `{ get, put }`, the lowest common\n * shape of R2 and KV — so this module never imports `@cloudflare/workers-types`\n * and can be checkpointed to anything (R2, KV, a DO sub-key, an in-memory map in\n * tests). The host binds the concrete adapter at `handlers({ store })`.\n *\n * Typical wiring:\n *\n * const snap = createSnapshot<RunState>({ every: 25, key: \"run/checkpoint\" });\n *\n * // machine state:\n * type State = { run: RunState; snapshot: SnapshotState };\n * init: (loaded) => loaded ? [loaded, []] : [{ run: initRun(), snapshot: snap.init() }, []],\n *\n * // in `update`, on each progress Msg, fold the snapshot slice:\n * progress: (s, msg) => {\n * const run = advance(s.run, msg);\n * const [snapshot, cmds] = snap.record(s.snapshot, run, msg.at);\n * return [{ ...s, run, snapshot }, cmds];\n * },\n * // the write completed → durable ack folds back through update:\n * snapshot_saved: (s, msg) => {\n * const [snapshot, cmds] = snap.confirm(s.snapshot, msg);\n * return [{ ...s, snapshot }, cmds];\n * },\n *\n * // RECOVERY — the half that makes a checkpoint a restart point, not just a\n * // backup. A `boot` Msg the host dispatches once after `run(...)` asks the\n * // store for the durable checkpoint; the answer folds the recovered payload\n * // back into the run slice:\n * boot: (s) => snap.requestLoad(s), // emits snapshot_load Cmd\n * snapshot_loaded: (s, msg) => {\n * if (msg.payload === null) return [s, []]; // no checkpoint → keep fresh init\n * const [snapshot, cmds] = snap.boot(s.snapshot);\n * return [{ ...s, run: msg.payload, snapshot }, cmds];\n * },\n * snapshot_load_failed: (s) => [s, []], // errors are data — decide here\n *\n * // splice the I/O — both effect cells (write + load):\n * interpret: { ...snap.handlers({ store: r2Adapter }) },\n */\n\nimport { Cmd, type Interpret, tryInterpret } from \"../index\";\n\n// ─────────────────────────────────────────────────────────────────────────────\n// Config — the knob.\n// ─────────────────────────────────────────────────────────────────────────────\n\n/**\n * The snapshot knob.\n *\n * `every` is the cadence: a checkpoint is written once `every` units of\n * progress have accumulated since the last one (see `record`). It is the\n * `snapshotEvery` field `monitored-run` threads through to this module.\n *\n * `key` is the store key the checkpoint is written under. A single machine\n * keeps a single rolling checkpoint, so the key is fixed at config time rather\n * than passed per write — the latest checkpoint overwrites the previous one\n * (R2/KV are last-write-wins on a key). Defaults to `\"@@snapshot\"`, mirroring\n * `doStore`'s `@@state` default-key convention.\n */\nexport interface SnapshotConfig {\n /**\n * Write a checkpoint once this many progress units have accumulated since the\n * last write. `every: 1` checkpoints on every `record`; a value `<= 0` is\n * treated as `1` (every progress unit triggers a write) — a non-positive\n * cadence has no other sensible meaning, and clamping keeps `record` from\n * never checkpointing on a misconfigured `0`.\n */\n readonly every: number;\n /** Store key the rolling checkpoint is written under. Defaults to `\"@@snapshot\"`. */\n readonly key?: string;\n}\n\nconst DEFAULT_SNAPSHOT_KEY = \"@@snapshot\";\n\n// ─────────────────────────────────────────────────────────────────────────────\n// Slice — the Model field this knob owns.\n// ─────────────────────────────────────────────────────────────────────────────\n\n/**\n * The snapshot bookkeeping slice. Plain data — JSON-serializable so it rides\n * inside the consumer's `Store<S>` blob and survives DO eviction / reload (the\n * \"durable\" non-negotiable). It carries NO payload: the thing being\n * checkpointed lives elsewhere in the machine's state; this slice only tracks\n * *when* to checkpoint and *what has been acknowledged durable*.\n *\n * - `sinceLast` — progress units accumulated since the last checkpoint\n * DECISION (reset to 0 the moment `record` decides to write, not when the\n * write lands). The cadence counter.\n * - `seq` — monotonic checkpoint sequence. Bumped each time a write is\n * emitted, so the write Cmd and its later acknowledgement can be matched\n * even if several are in flight. The \"version\" of the latest emitted\n * checkpoint.\n * - `lastSavedSeq` — the highest `seq` whose write the host has confirmed\n * durable (via `confirm`). `null` until the first confirmed write. The gap\n * `seq - lastSavedSeq` is the \"writes emitted but not yet acknowledged\"\n * depth — observable for backpressure / health.\n * - `lastSavedAt` — the `at` of the most recent confirmed write (epoch ms),\n * `null` until the first. Carried for observability, never compared inside\n * a verb (time is injected, never read here).\n */\nexport interface SnapshotState {\n readonly sinceLast: number;\n readonly seq: number;\n readonly lastSavedSeq: number | null;\n readonly lastSavedAt: number | null;\n}\n\n// ─────────────────────────────────────────────────────────────────────────────\n// Store port — abstract R2/KV-shaped checkpoint sink.\n// ─────────────────────────────────────────────────────────────────────────────\n\n/**\n * The abstract checkpoint sink — the intersection of R2's and KV's surface,\n * narrowed to exactly the two methods a rolling checkpoint needs. Deliberately\n * NOT the substrate's `Store<S>` (`{ load, save, migrate }`): that is the\n * runtime's whole-state persistence boundary keyed at a fixed cell, whereas a\n * checkpoint store is keyed, value-typed, and host-supplied.\n *\n * - `put(key, value)` — write the checkpoint. Last-write-wins on `key`.\n * - `get(key)` — read the latest checkpoint back, or `null` if none. Used by\n * a recovery path to seed `init`'s `loaded` from the durable checkpoint\n * when the runtime's own `Store<S>` blob is gone.\n *\n * No `@cloudflare/workers-types` import — an R2 bucket, a KV namespace, a DO\n * sub-key, or an in-memory `Map` all satisfy this shape with a 3-line adapter\n * at the host.\n */\nexport interface SnapshotStore<V> {\n get(key: string): Promise<V | null>;\n put(key: string, value: V): Promise<void>;\n}\n\n/**\n * The ports `handlers` binds. Just the store today — named so a future port\n * (a metrics sink, a clock) can grow without breaking the `handlers(ports)`\n * call shape.\n */\nexport interface SnapshotPorts<V> {\n readonly store: SnapshotStore<V>;\n}\n\n// ─────────────────────────────────────────────────────────────────────────────\n// Cmd + Msg — the write effect and its durable acknowledgement.\n// ─────────────────────────────────────────────────────────────────────────────\n\n/**\n * The checkpoint-write Cmd a `record` / `force` decision emits. Plain data\n * (invariant 1 — Cmds are data, not closures): it carries the `payload` to\n * write, the `key` to write it under, the `seq` it represents, and the `at` it\n * was decided. The handler performs the `put` and routes the result back to a\n * `confirm` / fail Msg.\n *\n * `payload` is `V` — the consumer's serializable checkpoint value (the run\n * state, the cursor, whatever the machine wants restartable). The verb takes it\n * as an argument so the snapshot slice never has to hold a copy of the\n * machine's domain state.\n */\nexport type SnapshotWriteCmd<V> = Cmd<\"snapshot_write\"> & {\n readonly key: string;\n readonly seq: number;\n readonly at: number;\n readonly payload: V;\n};\n\n/**\n * The Msg the write handler dispatches on a SUCCESSFUL `put`. Fold it back\n * through `confirm(state, msg)` to advance `lastSavedSeq` / `lastSavedAt`. The\n * `seq` matches the emitting Cmd so a late acknowledgement of an older write\n * can never regress a newer confirmed one.\n */\nexport type SnapshotSavedMsg = {\n readonly type: \"snapshot_saved\";\n readonly seq: number;\n readonly at: number;\n};\n\n/**\n * The Msg the write handler dispatches on a FAILED `put`. Errors are data\n * (never swallowed): the consumer's reducer decides whether a failed checkpoint\n * is fatal, retryable (compose `@demlik/tea/retry-backoff`), or merely logged.\n * `seq` identifies which checkpoint failed; `error` is carried, never\n * interpreted here.\n */\nexport type SnapshotFailedMsg = {\n readonly type: \"snapshot_failed\";\n readonly seq: number;\n readonly error: unknown;\n};\n\n/**\n * The checkpoint-READ Cmd a `requestLoad` decision emits — the recovery half of\n * the module. Plain data (invariant 1): it carries only the `key` to read. The\n * handler performs `store.get(key)` and routes the result back to a\n * `snapshot_loaded` (Ok, payload-or-null) / `snapshot_load_failed` (Err) Msg.\n *\n * Distinct from `snapshot_write`: a write carries a payload OUT to the store; a\n * load asks the store to hand a payload BACK. The consumer dispatches the\n * triggering Msg once on resume (a `boot` Msg, never `init`'s rehydrate branch —\n * invariant 2), folds the recovered payload into its run slice, then continues.\n */\nexport type SnapshotLoadCmd = Cmd<\"snapshot_load\"> & {\n readonly key: string;\n};\n\n/**\n * The Msg the load handler dispatches when `store.get` RESOLVES. `payload` is\n * the recovered checkpoint value, or `null` when the store has no checkpoint\n * under `key` (fresh boot — never checkpointed, or a wiped store). The consumer\n * folds this into `init`'s loaded branch: `payload === null` → keep the fresh\n * `init` state; non-null → seed the run slice from the durable checkpoint and\n * `boot` the snapshot slice's cadence counter.\n *\n * A `null` payload is NOT an error — \"no checkpoint yet\" is a normal first-run\n * state, distinct from \"the read failed\" (`snapshot_load_failed`).\n */\nexport type SnapshotLoadedMsg<V> = {\n readonly type: \"snapshot_loaded\";\n readonly payload: V | null;\n};\n\n/**\n * The Msg the load handler dispatches when `store.get` REJECTS. Errors are data\n * (never swallowed — the field-guide rule): a failed recovery read is a visible\n * signal the consumer's reducer decides on (fatal-abort, retry via\n * `@demlik/tea/retry-backoff`, or boot-fresh-and-log). `error` is carried, never\n * interpreted here. Kept separate from `snapshot_loaded`'s `null` so \"the store\n * has no checkpoint\" can never masquerade as \"the read failed\".\n */\nexport type SnapshotLoadFailedMsg = {\n readonly type: \"snapshot_load_failed\";\n readonly error: unknown;\n};\n\n/** Construct a `snapshot_saved` Msg. Shared by the handler and tests so the wire shape can't drift. */\nexport function snapshotSaved(seq: number, at: number): SnapshotSavedMsg {\n return { type: \"snapshot_saved\", seq, at };\n}\n\n/** Construct a `snapshot_failed` Msg. Shared by the handler and tests so the wire shape can't drift. */\nexport function snapshotFailed(seq: number, error: unknown): SnapshotFailedMsg {\n return { type: \"snapshot_failed\", seq, error };\n}\n\n/** Construct a `snapshot_loaded` Msg. Shared by the handler and tests so the wire shape can't drift. */\nexport function snapshotLoaded<V>(payload: V | null): SnapshotLoadedMsg<V> {\n return { type: \"snapshot_loaded\", payload };\n}\n\n/** Construct a `snapshot_load_failed` Msg. Shared by the handler and tests so the wire shape can't drift. */\nexport function snapshotLoadFailed(error: unknown): SnapshotLoadFailedMsg {\n return { type: \"snapshot_load_failed\", error };\n}\n\n// ─────────────────────────────────────────────────────────────────────────────\n// The combinator.\n// ─────────────────────────────────────────────────────────────────────────────\n\n/**\n * The bundle `createSnapshot` returns — the uniform knob contract. `subs` is\n * omitted: a checkpoint is driven by progress (a `record` call from the\n * consumer's reducer), not by a timer, so this module has no subscriptions.\n */\nexport interface SnapshotKnob<V> {\n /** The initial slice — zero progress, no checkpoint emitted or confirmed yet. */\n init(): SnapshotState;\n /**\n * Account one unit of progress. PURE. Increments `sinceLast`; once the\n * accumulated count reaches the configured cadence, DECIDES to checkpoint:\n * resets `sinceLast` to 0, bumps `seq`, and emits a single `snapshot_write`\n * Cmd carrying `payload` + `key` + the new `seq` + `at`. Below the cadence it\n * returns the bumped slice and NO Cmd.\n *\n * `payload` is the checkpoint value to persist (the consumer's domain state);\n * `at` is the caller-stamped time the progress occurred (epoch ms) — injected,\n * never read from the clock here.\n */\n record(\n state: SnapshotState,\n payload: V,\n at: number,\n ): readonly [SnapshotState, readonly Cmd[]];\n /**\n * Force a checkpoint regardless of the cadence counter. PURE. Always bumps\n * `seq`, resets `sinceLast`, and emits the write Cmd. Use on a phase boundary\n * the cadence would otherwise miss — e.g. \"the run just finished, checkpoint\n * the terminal state now\" — so the last durable checkpoint is the final state,\n * not whatever the modulo happened to land on.\n */\n force(\n state: SnapshotState,\n payload: V,\n at: number,\n ): readonly [SnapshotState, readonly Cmd[]];\n /**\n * Record a confirmed durable write. PURE. Advances `lastSavedSeq` /\n * `lastSavedAt` to the acknowledged `seq` / `at`, but only forward: an\n * out-of-order acknowledgement of an OLDER `seq` (a slow write landing after a\n * newer one) is ignored, so the confirmed watermark is monotonic. Fold the\n * `snapshot_saved` Msg here.\n *\n * Returns the reducer-cell shape `readonly [SnapshotState, readonly Cmd[]]`\n * (with `Cmd.none` — an ack emits nothing) for uniformity with every sibling\n * knob, so it drops straight into a `snapshot_saved` cell without the caller\n * re-wrapping a bare State.\n */\n confirm(\n state: SnapshotState,\n msg: SnapshotSavedMsg,\n ): readonly [SnapshotState, readonly Cmd[]];\n /**\n * Resume after a reload. PURE. Resets `sinceLast` to 0 because any\n * un-checkpointed progress accumulated before the crash is moot — recovery\n * resumes from the last DURABLE checkpoint (`get` from the store), so the\n * cadence counter starts fresh from there. `seq` / `lastSavedSeq` /\n * `lastSavedAt` are preserved (they describe what is durable, which a reload\n * does not change). Call from a `boot` Msg the host dispatches once after\n * `run(...)`, never from `init`'s rehydrate branch (invariant 2).\n *\n * Returns the reducer-cell shape `readonly [SnapshotState, readonly Cmd[]]`\n * (with `Cmd.none` — a resume emits nothing) for uniformity with every sibling\n * knob, so it drops straight into a `snapshot_loaded` cell without the caller\n * re-wrapping a bare State.\n */\n boot(state: SnapshotState): readonly [SnapshotState, readonly Cmd[]];\n /**\n * Ask the store for the durable checkpoint. PURE. Emits a single\n * `snapshot_load` Cmd carrying the configured `key`; returns the slice\n * UNCHANGED (a read decides nothing about the cadence bookkeeping — only the\n * later `snapshot_loaded` fold, via `boot`, touches the slice). This is the\n * entry point of the recovery loop: the consumer dispatches a `boot` Msg once\n * after `run(...)`, folds `requestLoad` here, and the emitted Cmd's handler\n * reads `store.get(key)` and dispatches `snapshot_loaded` / `snapshot_load_failed`.\n *\n * Returns `readonly [SnapshotState, readonly Cmd[]]` (the reducer-cell shape)\n * so it drops straight into a `boot: (s) => snap.requestLoad(s)` cell.\n */\n requestLoad(state: SnapshotState): readonly [SnapshotState, readonly Cmd[]];\n /**\n * The `interpret` map for this knob's two effect Cmds:\n *\n * - `snapshot_write` — binds the store and performs the `put`, routing\n * Ok → `snapshot_saved` / Err → `snapshot_failed`.\n * - `snapshot_load` — binds the store and performs the `get`, routing\n * Ok → `snapshot_loaded` (payload-or-null) / Err → `snapshot_load_failed`.\n *\n * Railway throughout — neither effect throws into the runtime; the outcome is\n * always a Msg. Spread into your machine's `interpret`.\n */\n handlers(\n ports: SnapshotPorts<V>,\n ): Interpret<\n | SnapshotSavedMsg\n | SnapshotFailedMsg\n | SnapshotLoadedMsg<V>\n | SnapshotLoadFailedMsg,\n SnapshotWriteCmd<V> | SnapshotLoadCmd,\n unknown\n >;\n}\n\n/**\n * Create a snapshot knob from a `SnapshotConfig`. The returned bundle is spliced\n * into a consumer machine: `init()` seeds the slice, `record` / `force` are\n * folded in reducer cells, `confirm` / `boot` handle the acknowledgement +\n * resume Msgs, and `handlers({ store })` supplies the write `interpret` cell.\n */\nexport function createSnapshot<V>(config: SnapshotConfig): SnapshotKnob<V> {\n // Clamp the cadence to >= 1 at construction so `record`'s threshold check is\n // branchless and a misconfigured `0` / negative `every` checkpoints on every\n // progress unit instead of never.\n const every = config.every >= 1 ? config.every : 1;\n const key = config.key ?? DEFAULT_SNAPSHOT_KEY;\n\n /**\n * Emit the write Cmd for a checkpoint decision. Shared by `record`'s\n * cadence-hit branch and `force`. Bumps `seq`, resets `sinceLast`, returns the\n * new slice + the single `snapshot_write` Cmd. The input slice is never\n * mutated (invariant 1).\n */\n function emitWrite(\n state: SnapshotState,\n payload: V,\n at: number,\n ): readonly [SnapshotState, readonly Cmd[]] {\n const seq = state.seq + 1;\n const next: SnapshotState = { ...state, sinceLast: 0, seq };\n const cmd: SnapshotWriteCmd<V> = {\n type: \"snapshot_write\",\n key,\n seq,\n at,\n payload,\n };\n return [next, [cmd]];\n }\n\n return {\n init(): SnapshotState {\n return { sinceLast: 0, seq: 0, lastSavedSeq: null, lastSavedAt: null };\n },\n\n record(state, payload, at) {\n const sinceLast = state.sinceLast + 1;\n // Below the cadence: accumulate, emit nothing.\n if (sinceLast < every) {\n return [{ ...state, sinceLast }, Cmd.none];\n }\n // Cadence reached: checkpoint. `emitWrite` resets the counter + bumps seq.\n return emitWrite(state, payload, at);\n },\n\n force(state, payload, at) {\n return emitWrite(state, payload, at);\n },\n\n confirm(state, msg) {\n // Forward-only watermark: ignore a stale ack of an older seq so a slow\n // write landing after a newer one can't regress `lastSavedSeq`.\n if (state.lastSavedSeq !== null && msg.seq <= state.lastSavedSeq) {\n return [state, Cmd.none];\n }\n return [\n { ...state, lastSavedSeq: msg.seq, lastSavedAt: msg.at },\n Cmd.none,\n ];\n },\n\n boot(state) {\n return [{ ...state, sinceLast: 0 }, Cmd.none];\n },\n\n requestLoad(state) {\n // A read decides nothing about the cadence bookkeeping — return the slice\n // unchanged and emit the single read Cmd. The recovered payload folds back\n // through `snapshot_loaded` (the consumer's reducer), not here.\n const cmd: SnapshotLoadCmd = { type: \"snapshot_load\", key };\n return [state, [cmd]];\n },\n\n handlers(ports) {\n const { store } = ports;\n return {\n // The write effect. Performs the `put`; on success the saved `seq`/`at`\n // ride the `snapshot_saved` Msg back into `confirm`; on failure the\n // error rides `snapshot_failed`. Railway via the core's `tryInterpret`\n // (matching every other module's handler) — the handler always resolves\n // with a Msg, never rejects into the runtime.\n snapshot_write: tryInterpret<\n SnapshotWriteCmd<V>,\n void,\n SnapshotSavedMsg | SnapshotFailedMsg,\n unknown\n >(\n (cmd) => store.put(cmd.key, cmd.payload),\n (_value, cmd) => snapshotSaved(cmd.seq, cmd.at),\n (error, cmd) => snapshotFailed(cmd.seq, error),\n ),\n // The read effect — the recovery half. Performs the `get`; on success\n // the recovered payload (or `null` when no checkpoint exists) rides the\n // `snapshot_loaded` Msg the consumer folds into `init`'s loaded branch;\n // on failure the error rides `snapshot_load_failed`. Same Railway\n // discipline via `tryInterpret` — the read never rejects into the runtime.\n snapshot_load: tryInterpret<\n SnapshotLoadCmd,\n V | null,\n SnapshotLoadedMsg<V> | SnapshotLoadFailedMsg,\n unknown\n >(\n (cmd) => store.get(cmd.key),\n (payload) => snapshotLoaded(payload),\n (error) => snapshotLoadFailed(error),\n ),\n };\n },\n };\n}\n"]}
|
|
@@ -1,5 +1,5 @@
|
|
|
1
1
|
import { describeError } from './chunk-G5E74IJB.js';
|
|
2
|
-
import { historyTracker } from './chunk-
|
|
2
|
+
import { historyTracker } from './chunk-5EHLEUTL.js';
|
|
3
3
|
|
|
4
4
|
// src/extension/bridge.ts
|
|
5
5
|
function channelTypes(channel) {
|
|
@@ -180,5 +180,5 @@ function bridgeTabClient({
|
|
|
180
180
|
}
|
|
181
181
|
|
|
182
182
|
export { bridgeClient, bridgeRuntime, bridgeTabClient, passThroughMsg };
|
|
183
|
-
//# sourceMappingURL=chunk-
|
|
184
|
-
//# sourceMappingURL=chunk-
|
|
183
|
+
//# sourceMappingURL=chunk-A56OR5RF.js.map
|
|
184
|
+
//# sourceMappingURL=chunk-A56OR5RF.js.map
|