@demlik/tea 0.5.0 → 0.6.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/LICENSE +1 -1
- package/dist/agent/index.d.ts +5 -5
- package/dist/agent/index.js +10 -10
- package/dist/authed-call/index.d.ts +3 -3
- package/dist/authed-call/index.js +7 -7
- package/dist/await-terminal/index.d.ts +2 -2
- package/dist/await-terminal/index.js +3 -2
- package/dist/await-terminal/index.js.map +1 -1
- package/dist/batch-window/index.d.ts +4 -4
- 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/{chunk-PUTHY7LR.js → chunk-2KBEIGYP.js} +3 -3
- package/dist/{chunk-PUTHY7LR.js.map → chunk-2KBEIGYP.js.map} +1 -1
- package/dist/{chunk-QKUWDC4P.js → chunk-2UGTDYOK.js} +3 -3
- package/dist/{chunk-QKUWDC4P.js.map → chunk-2UGTDYOK.js.map} +1 -1
- package/dist/{chunk-ZILCH3WS.js → chunk-66S2KEQQ.js} +4 -4
- package/dist/{chunk-ZILCH3WS.js.map → chunk-66S2KEQQ.js.map} +1 -1
- package/dist/{chunk-362SZ6CS.js → chunk-A7USKARI.js} +3 -3
- package/dist/{chunk-362SZ6CS.js.map → chunk-A7USKARI.js.map} +1 -1
- package/dist/{chunk-ZT3MGQB7.js → chunk-ASX7PL2K.js} +3 -3
- package/dist/{chunk-ZT3MGQB7.js.map → chunk-ASX7PL2K.js.map} +1 -1
- package/dist/{chunk-62QBHM3T.js → chunk-AZ3NHUPD.js} +3 -3
- package/dist/{chunk-62QBHM3T.js.map → chunk-AZ3NHUPD.js.map} +1 -1
- package/dist/{chunk-7FYQGDMF.js → chunk-AZZLE7XR.js} +3 -3
- package/dist/{chunk-7FYQGDMF.js.map → chunk-AZZLE7XR.js.map} +1 -1
- package/dist/{chunk-W2S4HK7J.js → chunk-EC6FO42Q.js} +6 -6
- package/dist/{chunk-W2S4HK7J.js.map → chunk-EC6FO42Q.js.map} +1 -1
- package/dist/{chunk-T6HDAPMR.js → chunk-ERYY5P6W.js} +3 -3
- package/dist/{chunk-T6HDAPMR.js.map → chunk-ERYY5P6W.js.map} +1 -1
- package/dist/{chunk-CIDS43FH.js → chunk-IVHQCVFX.js} +3 -3
- package/dist/{chunk-CIDS43FH.js.map → chunk-IVHQCVFX.js.map} +1 -1
- package/dist/{chunk-DBOJ2UC6.js → chunk-O7ED34JH.js} +4 -2
- package/dist/chunk-O7ED34JH.js.map +1 -0
- package/dist/{chunk-LFW66IIV.js → chunk-S6Y3RNX4.js} +4 -4
- package/dist/{chunk-LFW66IIV.js.map → chunk-S6Y3RNX4.js.map} +1 -1
- package/dist/{chunk-KEFMI24G.js → chunk-UV7VCTNC.js} +7 -7
- package/dist/{chunk-KEFMI24G.js.map → chunk-UV7VCTNC.js.map} +1 -1
- package/dist/{chunk-7UU5P2CX.js → chunk-WF46FABO.js} +3 -3
- package/dist/{chunk-7UU5P2CX.js.map → chunk-WF46FABO.js.map} +1 -1
- package/dist/{chunk-KWPB6BM6.js → chunk-XMHZ44QC.js} +4 -4
- package/dist/{chunk-KWPB6BM6.js.map → chunk-XMHZ44QC.js.map} +1 -1
- package/dist/{chunk-7A74NSG4.js → chunk-XOAP3LOT.js} +3 -3
- package/dist/{chunk-7A74NSG4.js.map → chunk-XOAP3LOT.js.map} +1 -1
- package/dist/{chunk-PBAYOJJO.js → chunk-ZH454CR5.js} +9 -3
- package/dist/chunk-ZH454CR5.js.map +1 -0
- package/dist/{core-DPYOyOJv.d.ts → core-CUN5HDKL.d.ts} +16 -1
- package/dist/deadline/index.d.ts +2 -2
- package/dist/deadline/index.js +3 -3
- package/dist/do/index.d.ts +9 -9
- package/dist/do/index.js +10 -10
- package/dist/do/index.js.map +1 -1
- package/dist/{durable-effects-B0YpoCHg.d.ts → durable-effects-CXMOi-sp.d.ts} +2 -2
- package/dist/extension/index.d.ts +2 -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/extension/test-utils.d.ts +1 -1
- package/dist/extension/test-utils.js.map +1 -1
- package/dist/fan-out/index.d.ts +2 -2
- package/dist/idempotent-intake/index.d.ts +2 -2
- package/dist/index.d.ts +4 -4
- package/dist/index.js +2 -2
- package/dist/llm-call/index.d.ts +3 -3
- package/dist/llm-call/index.js +7 -7
- package/dist/{machine-Q_Bsm7Vu.d.ts → machine-CoWpvp8x.d.ts} +3 -3
- package/dist/machine-viz/index.d.ts +1 -1
- package/dist/machine-viz/index.js +2 -2
- package/dist/mem/index.d.ts +2 -2
- package/dist/monitored-run/index.d.ts +4 -4
- package/dist/monitored-run/index.js +5 -5
- package/dist/node/index.d.ts +2 -2
- package/dist/paginated-walk/index.d.ts +3 -3
- package/dist/paginated-walk/index.js +7 -7
- package/dist/parity/index.d.ts +30 -2
- package/dist/parity/index.js +3 -3
- package/dist/parity/index.js.map +1 -1
- package/dist/pbt/arbitraries/index.d.ts +3 -3
- package/dist/pbt/arbitraries/index.js +1 -1
- package/dist/pbt/index.d.ts +1 -1
- package/dist/pbt/index.js +4 -4
- package/dist/pbt/runners/index.d.ts +1 -1
- package/dist/pbt/runners/index.js +3 -3
- package/dist/poller/index.d.ts +3 -3
- package/dist/poller/index.js +3 -3
- 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 +2 -2
- package/dist/react/index.js +2 -2
- package/dist/reconciler/index.d.ts +3 -3
- package/dist/reconciler/index.js +7 -7
- package/dist/recorder/index.d.ts +2 -2
- package/dist/resilient-call/index.d.ts +2 -2
- package/dist/resilient-call/index.js +6 -6
- package/dist/retry-to-success/index.js +2 -0
- package/dist/retry-to-success/index.js.map +1 -1
- package/dist/{runtime-types-uBuVvZqa.d.ts → runtime-types-BD3ZFfdw.d.ts} +2 -2
- package/dist/saga/index.d.ts +2 -2
- package/dist/snapshot/index.d.ts +1 -1
- package/dist/snapshot/index.js +3 -3
- package/dist/subs/index.d.ts +3 -3
- package/dist/subs/index.js +3 -3
- package/dist/testing/index.d.ts +2 -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 +2 -2
- package/dist/trace-replay/index.js +3 -3
- package/dist/{types-C6mXL9wA.d.ts → types-CjAkI83U.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 +2 -2
- package/dist/work-queue/index.d.ts +3 -3
- package/dist/work-queue/index.js.map +1 -1
- package/dist/work-queue/ops.d.ts +2 -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 +12 -4
- package/dist/chunk-DBOJ2UC6.js.map +0 -1
- package/dist/chunk-PBAYOJJO.js.map +0 -1
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"sources":["../src/subs/define-listener.ts","../src/subs/from-broadcast-channel.ts","../src/subs/from-event-source.ts","../src/subs/from-event-target.ts","../src/subs/from-interval.ts","../src/subs/from-port.ts","../src/subs/from-reconnecting-web-socket.ts","../src/subs/from-web-socket.ts","../src/subs/managed-resource.ts","../src/subs/transport.ts"],"names":[],"mappings":"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","file":"chunk-QKUWDC4P.js","sourcesContent":["// ---------------------------------------------------------------------------\n// defineListener — listener-as-resource. The kernel primitive that turns an\n// imperative `add`/`remove` listener pair into a substrate-owned Sub whose\n// cleanup is MANDATORY and DERIVED — a forgotten or no-op disposer is\n// unrepresentable.\n//\n// The leak it kills: every author who wires a raw listener hand-rolls the SAME\n// skeleton —\n//\n// return (sub, _ctx, dispatch) => {\n// const listener = (e) => { const m = msgFn(e, sub); if (m !== null) dispatch(m); };\n// target.addListener(listener);\n// return () => target.removeListener(listener); // ← author-driven\n// };\n//\n// — and each re-write can SILENTLY LEAK two ways the type system never sees:\n//\n// 1. The disposer is a no-op (`return () => {}`) — the listener stays armed\n// forever. Nothing forces the returned cleanup to undo the add.\n// 2. The disposer calls `remove` with a DIFFERENT function than `add`\n// received (a fresh closure, a re-bound `this`) — the original listener\n// is never removed; it leaks while a phantom remove succeeds silently.\n//\n// `fromBroadcastChannel`, `fromEventTarget`, and every host-side bridge\n// hand-roll this skeleton today. They happen to be correct — but the\n// correctness is convention (rung 5), not structure. `defineListener` moves it\n// to rung 1: the listener the substrate builds is the SAME reference handed to\n// both `add` and `remove`, and the cleanup IS `() => remove(thatListener, ...)`.\n// You cannot write a no-op disposer because you do not write the disposer at\n// all.\n//\n// Shape:\n//\n// // Args is the tuple chrome invokes the listener with — here a single\n// // [Alarm]. The substrate builds ONE listener of this exact native shape.\n// const fromChromeAlarm = defineListener<[chrome.alarms.Alarm], S>({\n// add: (l) => chrome.alarms.onAlarm.addListener(l),\n// remove: (l) => chrome.alarms.onAlarm.removeListener(l),\n// });\n//\n// // ...then at the subscribe cell, supply the Msg projection:\n// subscribe: { tick: fromChromeAlarm<M>((sub, alarm) => ({ type: \"tick\", ... })) }\n//\n// - `add`/`remove` receive `(listener, sub, ctx)`. The `listener` is the\n// EXACT function the substrate built; pass it straight to the platform's\n// addListener/removeListener — NO wrapper. A wrapper inside `add` would\n// break reference identity (the platform stores the wrapper, but the\n// substrate hands `remove` the unwrapped listener → silent leak). Carrying\n// the platform's native `Args` tuple on the listener is what lets it be\n// registered directly. `sub`/`ctx` let the target be pulled from `ctx` or\n// keyed on the `sub` (the `getTarget()`-from-ctx shape fromPort needs).\n//\n// - `msgFn(sub, ...args) => M | null` — the universal drop-on-null filter\n// every factory in this subpath carries. `null` drops the dispatch; the\n// listener stays armed. `sub` is first (always present); the platform args\n// follow in their native order.\n//\n// Generalizes the `from*` pattern: a closure-owned disposer makes a forgotten\n// cleanup a syntax error, and this brings it to every host/satellite.\n//\n// Strengthens invariant 4 (external events enter as Subs; the lifecycle —\n// add on subscribe, remove on cleanup — is owned by the substrate, never the\n// author) and invariant 6 (no silent looseness: a leaked listener is a silent\n// failure, and this primitive removes the way to write one).\n// ---------------------------------------------------------------------------\n\nimport type { Sub } from \"../index\";\nimport { dispatchIfPresent, type SubscribeHandler } from \"./types\";\n\n/**\n * The imperative listener target, expressed as the `add`/`remove` pair the\n * substrate pairs into a reconciled resource. Both halves receive the SAME\n * `listener` reference — the substrate builds it once — so `remove` can never\n * be handed a different function than `add` saw.\n *\n * `add`/`remove` also receive `(sub, ctx)` so the target may be resolved from\n * `ctx` (a runtime, a port, a DOM node looked up lazily) or keyed on the Sub\n * (an alarm name, a channel name). Resolving the target inside `add` and\n * closing over nothing but the listener keeps a target swap from leaking the\n * old target's listener — same rationale as `fromEventTarget`'s captured\n * target.\n *\n * `Args` is the tuple the platform invokes the listener with — `[Alarm]` for\n * `chrome.alarms.onAlarm`, `[message, sender, sendResponse]` for\n * `chrome.runtime.onMessage`, `[changes, areaName]` for\n * `chrome.storage.onChanged`. The substrate builds ONE function of this exact\n * shape and threads the IDENTICAL reference to both `add` and `remove` — so a\n * platform that matches `removeListener` by reference (every chrome surface)\n * removes exactly what it added. A consumer that wrapped the listener inside\n * `add` would break reference identity (leak #2 above); carrying the native\n * `Args` on the listener itself removes the need for any wrapper.\n */\nexport interface ListenerTarget<\n Args extends readonly unknown[],\n S extends Sub,\n Ctx,\n> {\n add: (listener: (...args: Args) => void, sub: S, ctx: Ctx) => void;\n remove: (listener: (...args: Args) => void, sub: S, ctx: Ctx) => void;\n}\n\n/**\n * Build a listener-backed Sub factory whose cleanup is mandatory and derived.\n *\n * `Args`, `S`, `Ctx` are fixed when the target is defined — they describe the\n * one listener topology this factory owns (the platform arg tuple, which Sub\n * shape it filters on, what context resolves it). `M` is supplied at the\n * subscribe cell because the same listener feeds different Msgs in different\n * machines. Pinning `S` here (rather than at the `msgFn` step) lets\n * `add`/`remove` read the concrete Sub's fields — `sub.alarmName`,\n * `sub.channel` — with NO cast at the substrate edge: the narrowing happens in\n * author-land where the Sub shape is in scope.\n *\n * The returned function is a `SubscribeHandler` factory in the exact shape of\n * every other `@demlik/tea/subs` battery: supply the `msgFn` projection, get a\n * `(sub, ctx, dispatch) => cleanup` handler assignable to `machine.subscribe`.\n * The cleanup the author NEVER writes is `() => remove(listener, sub, ctx)`,\n * with `listener` the identical reference passed to `add`.\n */\nexport function defineListener<\n Args extends readonly unknown[],\n S extends Sub = Sub,\n Ctx = unknown,\n>(\n target: ListenerTarget<Args, S, Ctx>,\n): <M>(\n msgFn: (sub: S, ...args: Args) => M | null,\n) => SubscribeHandler<S, M, Ctx> {\n return <M>(\n msgFn: (sub: S, ...args: Args) => M | null,\n ): SubscribeHandler<S, M, Ctx> => {\n return (sub, ctx, dispatch) => {\n const listener = (...args: Args): void => {\n dispatchIfPresent(dispatch, msgFn(sub, ...args));\n };\n // ONE listener, identical reference to both halves — chrome's\n // removeListener matches by reference, so this removes exactly what\n // add registered. No wrapper, no WeakMap, no reference drift.\n target.add(listener, sub, ctx);\n return () => {\n target.remove(listener, sub, ctx);\n };\n };\n };\n}\n","// ---------------------------------------------------------------------------\n// fromBroadcastChannel — universal `BroadcastChannel`-shaped Sub factory.\n//\n// `BroadcastChannel` is a same-origin pub/sub primitive available in\n// every modern browser context (window, worker, service worker) AND in\n// Cloudflare Workers (with origin scoped to the worker isolate). The\n// constructor opens the channel; the `message` event fires whenever\n// another participant on the same channel name posts a message; the\n// `close()` method releases the OS resource.\n//\n// The factory carries `channelName` on the Sub itself (not in the\n// closure) so the same factory instance handles every BroadcastChannel\n// Sub in a machine — one Sub per channel, named by id, sharing the\n// factory's lifecycle code. Renaming a channel requires a new Sub.id\n// (the reconcile pass otherwise leaves the existing channel open).\n//\n// `msgFn` returns `M | null` — same drop-on-null pattern as\n// `fromEventTarget`. The MessageEvent's `data` is `unknown` from the\n// platform's perspective; the caller decides whether the payload is a\n// recognizable shape, and returns `null` to drop unknown messages\n// without polluting the reducer with parse-discard cells.\n//\n// Cleanup calls `channel.close()`, which removes the listener AND\n// releases the channel reference — `removeEventListener` alone would\n// leak the BroadcastChannel handle. The pair is intentional.\n// ---------------------------------------------------------------------------\n\nimport type { Sub } from \"../index\";\nimport type {\n MinimalBroadcastChannelCtor,\n MinimalMessageEvent,\n} from \"./platform\";\nimport { dispatchIfPresent, type SubscribeHandler } from \"./types\";\n\ntype BroadcastSubData = { channelName: string };\n\ndeclare const BroadcastChannel: MinimalBroadcastChannelCtor;\n\nexport function fromBroadcastChannel<S extends Sub & BroadcastSubData, M>(\n msgFn: (event: MinimalMessageEvent, sub: S) => M | null,\n): SubscribeHandler<S, M, unknown> {\n return (sub, _ctx, dispatch) => {\n const channel = new BroadcastChannel(sub.channelName);\n const listener = (event: MinimalMessageEvent): void => {\n dispatchIfPresent(dispatch, msgFn(event, sub));\n };\n channel.addEventListener(\"message\", listener);\n return () => {\n channel.removeEventListener(\"message\", listener);\n channel.close();\n };\n };\n}\n","// ---------------------------------------------------------------------------\n// fromEventSource — universal `EventSource` (server-sent events) Sub\n// factory.\n//\n// SSE is the asymmetric streaming protocol used by the agent log /\n// audit log pipelines: HTTP-shaped open, server pushes events forever,\n// client receives until close. `EventSource` is the standard browser\n// API; the constructor opens the connection, `message` fires per\n// server-pushed event, `error` fires on disconnect or non-2xx, `open`\n// fires once on initial handshake, `close()` tears the connection\n// down.\n//\n// Three callbacks because three observable events ARE three concerns\n// the caller routinely cares about:\n//\n// - `onMessage(data, sub)` — required. The SSE payload's `data` field\n// is `string` from the platform; the caller decides parse + Msg\n// shape. Returns `M | null`; null drops the dispatch.\n//\n// - `onError(event, sub)` — optional. SSE's `error` event fires both\n// on transient network drops (the browser auto-reconnects) AND on\n// permanent failures. Most consumers want to surface ONE of these\n// as a Msg (e.g. \"stream-disconnected\") without folding both into\n// `onMessage`'s discriminant. Omitting the callback drops error\n// events silently.\n//\n// - `onOpen(sub)` — optional. The connection's initial handshake\n// succeeded. Useful for \"show 'connected' indicator\" Msgs. Omitting\n// drops open events silently. NOT fired on auto-reconnect by every\n// browser implementation — treat the event as best-effort.\n//\n// `url` lives on the Sub itself (`EventSourceSubData`). Same id +\n// different url = the substrate's reconcile pass leaves the existing\n// connection alone (the URL change is silent). Changing the URL\n// requires a new Sub.id.\n//\n// Cleanup is `close()` + `removeEventListener` for all three —\n// belt-and-suspenders against EventSource implementations that\n// dispatch events into already-closed sources.\n// ---------------------------------------------------------------------------\n\nimport type { Sub } from \"../index\";\nimport type {\n MinimalEvent,\n MinimalEventSourceCtor,\n MinimalMessageEvent,\n} from \"./platform\";\nimport { dispatchIfPresent, type SubscribeHandler } from \"./types\";\n\ntype EventSourceSubData = { url: string };\n\ndeclare const EventSource: MinimalEventSourceCtor;\n\nexport interface EventSourceFactoryOpts<S, M> {\n onMessage: (data: string, sub: S) => M | null;\n onError?: (event: MinimalEvent, sub: S) => M | null;\n onOpen?: (sub: S) => M | null;\n}\n\nexport function fromEventSource<S extends Sub & EventSourceSubData, M>(\n opts: EventSourceFactoryOpts<S, M>,\n): SubscribeHandler<S, M, unknown> {\n return (sub, _ctx, dispatch) => {\n const source = new EventSource(sub.url);\n\n const messageListener = (event: MinimalMessageEvent): void => {\n // The SSE `data` payload is a string per the platform contract.\n // Cast at the boundary: callers express `(data: string) => M | null`\n // and never see `unknown` flowing in from the platform global.\n dispatchIfPresent(dispatch, opts.onMessage(event.data as string, sub));\n };\n source.addEventListener(\"message\", messageListener);\n\n const onError = opts.onError;\n const errorListener = onError\n ? (event: MinimalEvent): void => {\n dispatchIfPresent(dispatch, onError(event, sub));\n }\n : undefined;\n if (errorListener) source.addEventListener(\"error\", errorListener);\n\n const onOpen = opts.onOpen;\n const openListener = onOpen\n ? (): void => {\n dispatchIfPresent(dispatch, onOpen(sub));\n }\n : undefined;\n if (openListener) source.addEventListener(\"open\", openListener);\n\n return () => {\n source.removeEventListener(\"message\", messageListener);\n if (errorListener) source.removeEventListener(\"error\", errorListener);\n if (openListener) source.removeEventListener(\"open\", openListener);\n source.close();\n };\n };\n}\n","// ---------------------------------------------------------------------------\n// fromEventTarget — universal addEventListener/removeEventListener Sub\n// factory over any `EventTarget` (DOM nodes, `window`, `document`,\n// `BroadcastChannel`, `MessagePort`, `Worker`, `ServiceWorker`, etc.).\n//\n// Two non-obvious shape choices:\n//\n// - `getTarget: () => EventTarget` is a thunk, not a direct value. The\n// factory MUST work in environments where the target is not available\n// at module-load time — service workers run before any `document`\n// exists; some targets are constructed lazily from `ctx`. Deferring\n// the lookup to subscribe time also means a state-driven swap of the\n// target requires only a new Sub.id; the factory pulls the current\n// target on subscribe.\n//\n// - `msgFn` may return `null` — that emission is *dropped*. The event\n// fired, the factory called `msgFn`, but the caller decided \"this\n// event is not interesting given current state.\" No dispatch is made;\n// the listener stays armed for the next event. This is the explicit\n// filter point at the Sub boundary — the alternative (filtering inside\n// the reducer via a noop cell) costs a transition + reconcile pass per\n// filtered event.\n//\n// Cleanup uses the SAME `getTarget()` instance captured at subscribe time\n// so a target swap mid-Sub doesn't leak the listener on the old target.\n// We close over the resolved target rather than calling `getTarget()`\n// again at cleanup — the alternative (re-resolving) is the cause of\n// silent listener leaks in long-lived runtimes.\n// ---------------------------------------------------------------------------\n\nimport type { Sub } from \"../index\";\nimport type { MinimalEvent, MinimalEventTarget } from \"./platform\";\nimport { dispatchIfPresent, type SubscribeHandler } from \"./types\";\n\nexport function fromEventTarget<S extends Sub, M>(\n getTarget: () => MinimalEventTarget,\n eventName: string,\n msgFn: (event: MinimalEvent, sub: S) => M | null,\n): SubscribeHandler<S, M, unknown> {\n return (sub, _ctx, dispatch) => {\n const target = getTarget();\n const listener = (event: MinimalEvent): void => {\n dispatchIfPresent(dispatch, msgFn(event, sub));\n };\n target.addEventListener(eventName, listener);\n return () => {\n target.removeEventListener(eventName, listener);\n };\n };\n}\n","// ---------------------------------------------------------------------------\n// fromInterval — universal `setInterval`-shaped Sub factory.\n//\n// The recurring shape at every `subscribe[type]` cell that wraps a periodic\n// timer is the same five lines: `setInterval` the dispatch, return a\n// `clearInterval` cleanup. The factory absorbs both halves of the lifecycle\n// (subscribe + cleanup); the caller keeps the intent — which Sub variant,\n// which Msg to dispatch.\n//\n// Timing data (`intervalMs`) lives on the Sub itself, not in the closure.\n// The substrate already reconciles Subs by id on every transition; carrying\n// the period on the Sub means a `subscriptions(state)` function that\n// returns `{ id, type, intervalMs: 30_000 }` in one state and the same id\n// with `intervalMs: 60_000` later... will NOT re-wire the interval (the id\n// is unchanged, the reconcile pass leaves it running). That's a tradeoff\n// the substrate's reconcile contract pins, not this factory — if you want\n// the interval to change, change the Sub's id too.\n//\n// Strengthens invariant 9 (named, small surface) and invariant 2 (the Sub\n// carries the data; the lifecycle is in the factory; the reducer never\n// sees the timer).\n// ---------------------------------------------------------------------------\n\nimport type { Sub } from \"../index\";\nimport type { SubscribeHandler } from \"./types\";\n\ntype IntervalSubData = { intervalMs: number };\n\nexport function fromInterval<S extends Sub & IntervalSubData, M>(\n msgFn: (sub: S) => M,\n): SubscribeHandler<S, M, unknown> {\n return (sub, _ctx, dispatch) => {\n const handle = setInterval(() => {\n dispatch(msgFn(sub));\n }, sub.intervalMs);\n return () => {\n clearInterval(handle);\n };\n };\n}\n","// ---------------------------------------------------------------------------\n// fromPort — universal `Runtime.subscribePort`-shaped Sub factory.\n//\n// The canonical cross-runtime coordination pattern in @demlik/tea: one\n// runtime emits to a typed Port; another runtime's machine declares an\n// `audit-idle`-shaped Sub whose subscribe handler attaches to the\n// publisher's Port. The recurring 4-line shape at every such cell:\n//\n// \"audit-idle\": (_sub, ctx, dispatch) =>\n// ctx.auditRuntime.subscribePort(auditIdlePort, (value) => {\n// dispatch(buildMsg(value));\n// });\n//\n// The factory absorbs the cleanup-returning subscribePort call AND the\n// dispatch-on-emit wiring. The caller keeps the three pieces of intent:\n//\n// 1. `selectRuntime(ctx)` — which sibling Runtime publishes the Port.\n// This is a selector (not a direct ctx.auditRuntime reference) so\n// the factory does NOT pin the ctx shape — any machine whose ctx\n// exposes some Runtime<...> reference is compatible by selecting it.\n// A direct `Runtime` parameter would have made the factory shape\n// depend on having that Runtime in scope at machine-definition\n// time, which is a worse match for the \"ctx is the boundary\"\n// contract this substrate already commits to.\n//\n// 2. `port: Port<T>` — the typed channel itself. Identity is by\n// reference (the substrate enforces unique names at definePort\n// time); pass the same `Port<T>` value the publisher emits to.\n//\n// 3. `msgFn(value, sub) => M | null` — the value-to-Msg mapping, with\n// drop-on-null semantics matching every other factory in this\n// subpath. The published value's type `T` flows through to `msgFn`'s\n// first param so the caller never re-asserts the payload shape.\n//\n// Strengthens invariant 9 (the cross-runtime Port topology is named at\n// one factory site instead of redrawn at every subscribe cell).\n// ---------------------------------------------------------------------------\n\nimport type { Port, Runtime, Sub } from \"../index\";\nimport { dispatchIfPresent, type SubscribeHandler } from \"./types\";\n\n// `RS` / `RM` carry the sibling Runtime's concrete State and Msg through the\n// selector: a caller that selects a `Runtime<AuditState, AuditMsg>` keeps that\n// type at the seam instead of collapsing it to `Runtime<unknown, {type:string}>`.\n// They default to the prior broad pair so existing call sites that select a\n// less-specific Runtime still compile — the factory still only ever calls\n// `subscribePort`, so it does not read RS/RM, but the boundary no longer\n// hardcodes the widest type when the caller knows a narrower one.\nexport function fromPort<\n S extends Sub,\n M,\n Ctx,\n T,\n RS = unknown,\n RM extends { type: string } = { type: string },\n>(\n selectRuntime: (ctx: Ctx) => Runtime<RS, RM>,\n port: Port<T>,\n msgFn: (value: T, sub: S) => M | null,\n): SubscribeHandler<S, M, Ctx> {\n return (sub, ctx, dispatch) => {\n const runtime = selectRuntime(ctx);\n return runtime.subscribePort(port, (value) => {\n dispatchIfPresent(dispatch, msgFn(value, sub));\n });\n };\n}\n","// ---------------------------------------------------------------------------\n// fromReconnectingWebSocket — a `WebSocket` Sub factory that survives drops.\n//\n// `fromWebSocket` is the SSE-sibling ONE-SHOT factory: its cleanup `close()`s\n// the socket and it never re-opens on an unexpected drop (issue #188). That is\n// the right shape for a connection the caller manages, but any tea client over\n// an unreliable transport — games, presence, collab — needs the connection to\n// recover transparently, without a page reload. Rather than re-roll a backoff\n// loop in every consumer (vortex's `ReconnectingSocket` is the proven shape,\n// services/vortex/src/reconnecting-socket.ts), this is the reconnecting variant\n// folded into the substrate's Sub surface.\n//\n// The reconnect contract, mirroring vortex's wrapper:\n//\n// - On an UNEXPECTED close (`onclose` the substrate didn't initiate) it\n// re-opens after a CAPPED exponential backoff: `min(base * 2^attempt, max)`.\n// A successful handshake resets `attempt` to 0, so the next drop retries\n// fast again.\n//\n// - A CALLER-INITIATED close (the Sub's cleanup, e.g. the machine\n// transitioned the Sub away) suppresses reconnect: handlers are detached\n// BEFORE `close(1000)` so the teardown can't fire `onClose` or schedule a\n// reconnect. After cleanup the factory never re-opens.\n//\n// - `connect` and `schedule` are INJECTABLE (defaulting to the global\n// `WebSocket` and `setTimeout`/`clearTimeout`), the test seam that lets the\n// reconnect/backoff behavior be unit-tested with a fake socket + a manual\n// clock — no DOM, no real network, no timers — exactly as vortex's wrapper\n// is tested.\n//\n// Callback contract (same `M | null` \"null drops the dispatch\" shape as\n// `fromWebSocket`, plus one reconnect-specific hook):\n//\n// - `onMessage(data, sub)` — required. Raw `unknown` frame; the caller parses.\n// - `onOpen(sub)` — optional. Fires on EVERY successful handshake — the first\n// open AND every reconnect — so a \"connected\" indicator stays accurate.\n// - `onError(event, sub)` — optional. The `error` event; `close` still drives\n// the reconnect (one path), so this is purely a notification.\n// - `onClose(code, reason, sub)` — optional. Fires on every close the\n// substrate observes (a drop that will be retried, too) — NOT on the\n// caller's own teardown, whose handlers are detached first.\n// - `onReconnect(attempt, sub)` — optional. Fires when a RE-open (any open\n// after the first) succeeds, carrying the 1-based reconnect count. This is\n// the resync hook: the place to dispatch a \"re-fetch the authoritative\n// snapshot, discard predicted state\" Msg after the transport recovered.\n//\n// Like `fromWebSocket`, `wsUrl` lives on the Sub (`WebSocketSubData`); every\n// reconnect reads `sub.wsUrl` afresh, so a reconcile that re-subscribes with a\n// new url reconnects to the new endpoint.\n// ---------------------------------------------------------------------------\n\nimport type { Sub } from \"../index\";\nimport type { WebSocketSubData } from \"./from-web-socket\";\nimport type {\n MinimalEvent,\n MinimalWebSocket,\n MinimalWebSocketCtor,\n} from \"./platform\";\nimport { dispatchIfPresent, type SubscribeHandler } from \"./types\";\n\ndeclare const WebSocket: MinimalWebSocketCtor;\n\n/** Cancels a scheduled reconnect timer (the inverse of `schedule`). */\nexport type CancelTimer = () => void;\n\nexport interface ReconnectingWebSocketFactoryOpts<S, M> {\n onMessage: (data: unknown, sub: S) => M | null;\n onOpen?: (sub: S) => M | null;\n onError?: (event: MinimalEvent, sub: S) => M | null;\n onClose?: (code: number, reason: string, sub: S) => M | null;\n /**\n * Fires when a reconnection (any open after the first) succeeds, carrying the\n * 1-based reconnect count — the resync hook. Omitting drops reconnect events\n * silently.\n */\n onReconnect?: (attempt: number, sub: S) => M | null;\n /** First backoff delay in ms (doubles each failed attempt). Default 250. */\n backoffBaseMs?: number;\n /** Backoff ceiling in ms. Default 5000. */\n backoffMaxMs?: number;\n /** Inject the socket constructor (defaults to the global `WebSocket`); the test seam. */\n connect?: (url: string) => MinimalWebSocket;\n /** Inject the timer (defaults to `setTimeout`/`clearTimeout`); the test seam. */\n schedule?: (fn: () => void, ms: number) => CancelTimer;\n}\n\nconst defaultConnect = (url: string): MinimalWebSocket => new WebSocket(url);\n\nconst defaultSchedule = (fn: () => void, ms: number): CancelTimer => {\n const handle = setTimeout(fn, ms);\n return () => clearTimeout(handle);\n};\n\nexport function fromReconnectingWebSocket<S extends Sub & WebSocketSubData, M>(\n opts: ReconnectingWebSocketFactoryOpts<S, M>,\n): SubscribeHandler<S, M, unknown> {\n const backoffBaseMs = opts.backoffBaseMs ?? 250;\n const backoffMaxMs = opts.backoffMaxMs ?? 5000;\n const connect = opts.connect ?? defaultConnect;\n const schedule = opts.schedule ?? defaultSchedule;\n\n const { onMessage, onOpen, onError, onClose, onReconnect } = opts;\n\n return (sub, _ctx, dispatch) => {\n let ws: MinimalWebSocket | null = null;\n let attempt = 0;\n let opened = false;\n let closedByCaller = false;\n let cancelReconnect: CancelTimer | null = null;\n\n const emit = (msg: M | null): void => dispatchIfPresent(dispatch, msg);\n\n const handleClose = (code: number, reason: string): void => {\n ws = null;\n if (onClose) emit(onClose(code, reason, sub));\n // A teardown the substrate initiated detaches handlers first, so this\n // path only runs for an UNEXPECTED close — reconnect with capped backoff.\n if (closedByCaller) return;\n const delay = Math.min(backoffBaseMs * 2 ** attempt, backoffMaxMs);\n attempt += 1;\n cancelReconnect = schedule(() => {\n cancelReconnect = null;\n if (!closedByCaller) open();\n }, delay);\n };\n\n function open(): void {\n const socket = connect(sub.wsUrl);\n ws = socket;\n\n socket.onopen = (): void => {\n // `opened` BEFORE this open marks it a reconnect; `attempt` is the\n // pending count for the drop that triggered it (1-based once we reset).\n const reconnected = opened;\n const reconnectCount = attempt;\n attempt = 0;\n opened = true;\n if (onOpen) emit(onOpen(sub));\n if (reconnected && onReconnect) emit(onReconnect(reconnectCount, sub));\n };\n\n socket.onmessage = (event): void => {\n emit(onMessage(event.data, sub));\n };\n\n // `error` precedes `close`; let `close` drive the reconnect so there is\n // one path. The handler is still wired (as a no-op when the caller didn't\n // supply `onError`) so a platform error event has a sink.\n socket.onerror = onError\n ? (event): void => emit(onError(event, sub))\n : (): void => {};\n\n socket.onclose = (event): void => {\n handleClose(event.code, event.reason);\n };\n }\n\n open();\n\n return () => {\n // Deliberate teardown: cancel any pending reconnect, detach handlers so\n // the cleanup `close()` can't fire `onClose` or schedule a reconnect,\n // then close the live socket. After this the factory never re-opens.\n closedByCaller = true;\n if (cancelReconnect !== null) {\n cancelReconnect();\n cancelReconnect = null;\n }\n const socket = ws;\n ws = null;\n if (socket !== null) {\n socket.onopen = null;\n socket.onmessage = null;\n socket.onerror = null;\n socket.onclose = null;\n // 1000 = \"normal closure\" per RFC 6455 §7.4.1.\n socket.close(1000);\n }\n };\n };\n}\n","// ---------------------------------------------------------------------------\n// fromWebSocket — universal `WebSocket` Sub factory. The symmetric sibling\n// of `fromEventSource`.\n//\n// WebSocket is the bidirectional streaming protocol used wherever the\n// server pushes events AND the client pushes commands on the same\n// connection. The constructor opens the connection, `onopen` fires once\n// on handshake, `onmessage` fires per server-pushed frame, `onerror`\n// fires on transient or permanent failure, `onclose` fires once on\n// teardown carrying `code` + `reason`. `close(code)` initiates a clean\n// shutdown from the client side.\n//\n// Four callbacks because four observable events ARE four concerns the\n// caller routinely cares about. The Sub's `wsUrl` lives on the Sub itself\n// (`WebSocketSubData`) — same id + different `wsUrl` is silent under the\n// reconcile pass, identical to `fromEventSource`'s `url` contract.\n//\n// - `onMessage(data, sub)` — required. The platform delivers `data` as\n// `string | Blob | ArrayBuffer` depending on `binaryType` and the\n// server's frame type. The factory does NOT bake in a parse contract:\n// the callback receives the raw `unknown` and decides parse + Msg\n// shape. Returns `M | null`; null drops the dispatch.\n//\n// - `onOpen(sub)` — optional. Connection handshake succeeded. Useful\n// for \"show 'connected' indicator\" Msgs. Omitting drops open events\n// silently.\n//\n// - `onError(event, sub)` — optional. WebSocket's `error` event fires\n// on transient drops AND permanent failures; like SSE, most consumers\n// want one Msg (\"connection-errored\") without folding both into\n// `onMessage`'s discriminant. Omitting drops error events silently.\n//\n// - `onClose(code, reason, sub)` — optional. The connection is gone.\n// `code` is the WebSocket close code (1000 = clean, 1006 = abnormal,\n// etc.). `reason` is the server-supplied string (often empty).\n// Omitting drops close events silently.\n//\n// Cleanup is `close(1000)` — the IANA-registered clean-shutdown code.\n// The factory intentionally does NOT bake in registry semantics (e.g.\n// the extension's `ctx.wsRegistry` so `interpret.send_ws` can reach the\n// live socket): that's consumer glue. A consumer that needs the live\n// socket for outbound sends wires it from inside the subscribe handler\n// or keeps a hand-written Sub alongside this factory.\n// ---------------------------------------------------------------------------\n\nimport type { Sub } from \"../index\";\nimport type {\n MinimalCloseEvent,\n MinimalEvent,\n MinimalMessageEvent,\n MinimalWebSocketCtor,\n} from \"./platform\";\nimport { dispatchIfPresent, type SubscribeHandler } from \"./types\";\n\nexport type WebSocketSubData = { wsUrl: string };\n\ndeclare const WebSocket: MinimalWebSocketCtor;\n\nexport interface WebSocketFactoryOpts<S, M> {\n onMessage: (data: unknown, sub: S) => M | null;\n onOpen?: (sub: S) => M | null;\n onError?: (event: MinimalEvent, sub: S) => M | null;\n onClose?: (code: number, reason: string, sub: S) => M | null;\n}\n\nexport function fromWebSocket<S extends Sub & WebSocketSubData, M>(\n opts: WebSocketFactoryOpts<S, M>,\n): SubscribeHandler<S, M, unknown> {\n return (sub, _ctx, dispatch) => {\n const ws = new WebSocket(sub.wsUrl);\n\n ws.onmessage = (event: MinimalMessageEvent): void => {\n // The platform delivers `data` as `unknown` — the factory hands it\n // through untouched and the caller chooses the parse strategy.\n dispatchIfPresent(dispatch, opts.onMessage(event.data, sub));\n };\n\n const onOpen = opts.onOpen;\n if (onOpen) {\n ws.onopen = (): void => {\n dispatchIfPresent(dispatch, onOpen(sub));\n };\n }\n\n const onError = opts.onError;\n if (onError) {\n ws.onerror = (event: MinimalEvent): void => {\n dispatchIfPresent(dispatch, onError(event, sub));\n };\n }\n\n const onClose = opts.onClose;\n if (onClose) {\n ws.onclose = (event: MinimalCloseEvent): void => {\n dispatchIfPresent(dispatch, onClose(event.code, event.reason, sub));\n };\n }\n\n return () => {\n // Detach the lifecycle handlers BEFORE close() so the consumer's\n // `onClose` Msg isn't fired from a teardown the substrate initiated.\n // Mirrors fromEventSource's removeEventListener pass.\n ws.onmessage = null;\n ws.onopen = null;\n ws.onerror = null;\n ws.onclose = null;\n // 1000 = \"normal closure\" per RFC 6455 §7.4.1.\n ws.close(1000);\n };\n };\n}\n","// ---------------------------------------------------------------------------\n// defineManagedResource — a Model-gated resource with a MANDATORY release.\n//\n// foldkit's ManagedResource, ported. The recurring shape: a resource whose\n// lifetime is \"while the machine is in phase X, hold Y open; on leaving X,\n// tear Y down\" — a checkpoint store, a span, a lease, a pooled connection.\n// Rule 9 says that is a Sub the substrate reconciles, NOT a start/end Cmd\n// pair hand-driven across cells. Hand-driving leaks: every transition cell\n// has to remember to end-then-start, and one missed cell leaks the resource\n// forever (the checkpoint-namespace leak this battery was built to kill).\n//\n// The author writes three things — `{ name, acquire, release }` — and gets\n// back a `.sub(key)` to drop into `subscriptions(state)` plus a `.subscribe`\n// handler to drop into the machine's `subscribe` record. The substrate's\n// existing `reconcileSubs` (packages/tea/src/run.ts) does the rest:\n// - sub APPEARS in the desired set → handler runs → `acquire(key, ctx)`.\n// - sub DISAPPEARS (phase left) → cleanup runs → `release(handle)`.\n// - sub KEY changes (id changes) → old cleanup (release) + new acquire.\n// Same reconcile mechanism `fromTransport` rides for open/close — no new\n// kernel hook.\n//\n// THE ACQUIRE-AS-SUCCESS-VALUE DISCIPLINE\n// ----------------------------------------\n// `release` receives ONLY what `acquire` returned (the Handle). Nothing else\n// is in scope. So:\n// - An `acquire` that throws never produces a Handle, so `release` is never\n// called with a half-built resource — an interrupted acquire can't dangle.\n// - Whatever `release` needs to tear down must be captured INTO the Handle\n// by `acquire`. The Handle is the closure over the live resource.\n// `release` is NOT optional in the type. A managed resource without a teardown\n// is just a Cmd (Rule 5); the whole point of this battery is the guaranteed\n// teardown, so the type forces the author to write one.\n//\n// NEUTRAL MECHANISM — no policy. The battery knows `acquire`, `release`,\n// `name`, `key`. It does not know what a checkpoint, a span, or a socket is.\n// Could a reasonable machine want the opposite of any choice here? The only\n// choices are \"release is mandatory\" (yes — that's the contract) and \"release\n// runs on disappear OR key-change\" (yes — that's reconcile semantics, shared\n// with every other Sub). No Cloudflare, no audit domain.\n//\n// Strengthens invariant 4 (external lifecycle owned by the substrate — the\n// acquire/release pair rides reconcileSubs, never hand-driven by a cell) and\n// invariant 6 (no silent looseness — every live resource is a Sub in the\n// desired set, visible in the Sub topology, and an unroutable one throws).\n// ---------------------------------------------------------------------------\n\nimport type { DepKeyedSub, Dispose, Sub, SubId } from \"../index\";\nimport { structuralHash, subId } from \"../index\";\n\n/**\n * The Sub the battery builds. The consumer's `Sub` union includes\n * `ManagedResourceSub<TKey>` directly — the substrate reconciles by `id` and\n * `type` like any other Sub.\n */\nexport interface ManagedResourceSub<TKey> extends Sub<\"managed_resource\"> {\n /** The lifetime key. Stable while the resource should be held; a change\n * retires the old resource (release) and acquires a fresh one. */\n readonly key: TKey;\n}\n\n/**\n * A battery paired with a state-gate. Self-contained: it knows its `name`\n * (for the unique-name assert), how to derive its live Sub from a state\n * (`subFor`), how to handle its own `subscribe`, and whether it `owns` a given\n * Sub id.\n *\n * Generic in the consumer state `S`, the shared lifetime key `TKey`, and the\n * shared `Ctx`. `Handle` is the ONLY thing erased — each battery's handle type\n * differs (a checkpoint saver vs. a tool-call bridge) but never crosses this\n * interface, so a `combineManagedResources` array holds entries of\n * heterogeneous handle types with no cast. The sub type stays\n * `ManagedResourceSub<TKey>` end to end, so the substrate's narrowed Sub flows\n * straight in.\n */\nexport interface GatedManagedResource<S, TKey, Ctx> {\n /** The owning battery's `name`. The combinator asserts these are unique. */\n readonly name: string;\n\n /**\n * Derive this resource's live Sub for a given state: the battery's\n * `.sub(key)` when `when(state)` returns a key, or `null` when it returns\n * `null` (the resource should be torn down in that state).\n */\n readonly subFor: (state: S) => ManagedResourceSub<TKey> | null;\n\n /**\n * This battery's own `.subscribe`. The combinator routes an incoming\n * `managed_resource` Sub to the entry whose `owns(sub)` is true, then calls\n * this.\n */\n readonly subscribe: (\n sub: ManagedResourceSub<TKey>,\n ctx: Ctx,\n dispatch: (msg: never) => void,\n ) => Dispose;\n\n /**\n * Does this entry own the given Sub? `sub.id === subIdFor(sub.key)` encodes\n * the battery `name`, so a match is unambiguous.\n */\n readonly owns: (sub: ManagedResourceSub<TKey>) => boolean;\n}\n\n/**\n * What `combineManagedResources` returns: the single managed-resource\n * `subscribe` cell for the machine's `subscribe` record, and a `subs(state)`\n * builder for `subscriptions(state)`.\n */\nexport interface CombinedManagedResources<S, TKey, Ctx> {\n /**\n * Map every entry through its `when(state)`, drop the `null`s, and build the\n * live Subs. Spread into `subscriptions(state)`. The derived list and the\n * `subscribe` routing share one source — the entries — so they never drift.\n */\n readonly subs: (state: S) => ManagedResourceSub<TKey>[];\n\n /**\n * The SINGLE `subscribe.managed_resource` handler. Routes an incoming Sub to\n * the owning battery by matching `sub.id === battery.subIdFor(sub.key)` (the\n * id encodes the battery `name`). Throws loudly on an unrouted id — a wiring\n * bug, never a silent no-op resource.\n */\n readonly subscribe: (\n sub: ManagedResourceSub<TKey>,\n ctx: Ctx,\n dispatch: (msg: never) => void,\n ) => Dispose;\n}\n\nexport interface DefineManagedResourceOpts<TKey, Handle, Ctx> {\n /**\n * A short, stable name. The Sub id is `managed:${name}:${stringify(key)}`,\n * so two managed resources in one machine never collide and neither\n * collides with a `fromTransport` Sub keyed on the same identity.\n */\n readonly name: string;\n\n /**\n * Build the resource. Runs when the Sub appears in `subscriptions(state)`.\n * The returned Handle is the ONLY thing `release` receives — capture\n * everything teardown needs into it. If `acquire` throws, the substrate's\n * reconcile does NOT register the Sub and `release` is never called (no\n * dangling half-built resource).\n */\n readonly acquire: (key: TKey, ctx: Ctx) => Handle;\n\n /**\n * Tear the resource down. Runs when the Sub leaves the desired set (phase\n * exited) or when its key changes. MANDATORY — the guaranteed teardown is\n * the reason this battery exists.\n *\n * May be sync or async, and an async one is really awaited: the promise is\n * returned to the substrate, which tracks it and drains it inside `stop()`\n * (bounded by `run({ disposeTimeoutMs })`). So `await runtime.stop()` means\n * the release finished — the reading a host evicting an isolate acts on.\n * MID-RUN teardown (a phase exit, a key change) is still fire-and-forget\n * (Rule 2): the reconcile pass is synchronous by construction, so it starts\n * the release and moves on. A rejection is never a Msg — it reaches the\n * runtime's `onError` sink under `phase: \"sub-cleanup\"`.\n */\n readonly release: (handle: Handle) => void | Promise<void>;\n}\n\n/**\n * What the battery returns: a `.sub(key)` builder for `subscriptions`, the\n * `.subscribe` handler for the machine's `subscribe` record, a `.get(key)`\n * accessor so Cmd handlers can reach the live Handle while the resource is\n * held, and a `.subIdFor(key)` for tests.\n */\nexport interface ManagedResourceBattery<TKey, Handle, Ctx> {\n /**\n * Build the Sub for `subscriptions(state)`. The consumer calls this from\n * inside their `subscriptions` cell for the phases where the resource\n * should be live.\n */\n readonly sub: (key: TKey) => ManagedResourceSub<TKey>;\n\n /**\n * Pair this battery with a state-gate, producing a self-contained entry for\n * `combineManagedResources`. `when(state)` returns the resource's KEY when\n * it should be live in that state, or `null` when it should be torn down.\n *\n * Folding \"is it active?\" and \"what key?\" into ONE declaration at the\n * battery's own definition site is the point: a central `subscriptions(state)`\n * list can silently forget to gate a new phase, but a per-resource gate\n * travels with the resource. The combinator derives BOTH the active-sub list\n * and the subscribe routing from these entries, so the two can never disagree.\n *\n * Generic in the consumer's state `S` (each `.gated` call binds its own `S`).\n * The returned entry erases this battery's `Handle` — `combineManagedResources`\n * holds entries of heterogeneous handle types with no cast — while keeping\n * `TKey` and `Ctx` so the substrate's narrowed `ManagedResourceSub<TKey>`\n * flows straight into the entry's `subscribe`.\n */\n readonly gated: <S>(\n when: (state: S) => TKey | null,\n ) => GatedManagedResource<S, TKey, Ctx>;\n\n /**\n * Dep-keyed entry for the machine's `subs` array. `when(state)` returns the\n * resource's KEY when it should be held, or `null` when it should be torn\n * down — the exact gate `.gated` takes, now producing a `DepKeyedSub` the\n * kernel folds directly instead of an entry the `combineManagedResources`\n * router multiplexes.\n *\n * This is the simplification the dep-keyed kernel unlocks: two managed\n * resources used to COLLIDE on the single `subscribe.managed_resource` key\n * (both subs share `type: \"managed_resource\"`), forcing\n * `combineManagedResources` to route by SubId. A dep-keyed Sub has its OWN\n * reconcile slot (its array position), so there is no shared `type` to route\n * — each resource is one `subs` entry, no combinator, no router. `acquire` /\n * `release` still ride the kernel's dispose-on-change / dispose-on-null\n * reconcile, now via the dep-keyed source's `Dispose` instead of the\n * `subscribe` cell's cleanup.\n */\n readonly depKeyed: <S>(\n when: (state: S) => TKey | null,\n ) => DepKeyedSub<S, never, Ctx>;\n\n /**\n * The `subscribe.managed_resource` handler. Drop straight into the\n * machine's `subscribe` record. Runs `acquire` on install, holds the\n * Handle in the battery's handle table, and returns a cleanup that runs\n * `release` (and forgets the Handle) on reconcile-out.\n */\n readonly subscribe: (\n sub: ManagedResourceSub<TKey>,\n ctx: Ctx,\n // The managed resource dispatches no Msgs — acquire/release are pure\n // lifecycle. `dispatch` is accepted to match the substrate's\n // `subscribe[type]` shape and is intentionally unused.\n dispatch: (msg: never) => void,\n ) => Dispose;\n\n /**\n * Accessor for the live Handle, keyed on `key`. Returns `undefined` when\n * the resource is not currently held (no active sub for that key) — the\n * caller decides whether that is an error. A Cmd handler wires its\n * `ctx.getX(key)` through this, so the handler reads the SAME owner the\n * reconciler holds, never a hand-built duplicate.\n */\n readonly get: (key: TKey) => Handle | undefined;\n\n /** Pre-derived subId for `(key)` — handy for tests. */\n readonly subIdFor: (key: TKey) => SubId;\n}\n\n/**\n * Build the battery. One call per managed resource at the machine's host\n * file.\n *\n * @example\n * const checkpointStore = defineManagedResource<RunId, CheckpointSaver, Ctx>({\n * name: \"checkpoint\",\n * acquire: (runId, ctx) =>\n * new CheckpointSaver({ storage: ctx.storage, bucket: ctx.bucket, runId }),\n * release: (saver) => saver.clear(),\n * });\n *\n * // In the machine:\n * subscriptions: (state) =>\n * state.type === \"running\" ? [checkpointStore.sub(state.runId)] : [],\n * subscribe: { managed_resource: checkpointStore.subscribe },\n *\n * // In interpret (read the live handle the reconciler holds):\n * start_graph: async (cmd, ctx) => {\n * const saver = checkpointStore.get(cmd.runId); // same owner, no rebuild\n * ...\n * }\n */\nexport function defineManagedResource<TKey, Handle, Ctx>(\n opts: DefineManagedResourceOpts<TKey, Handle, Ctx>,\n): ManagedResourceBattery<TKey, Handle, Ctx> {\n // Handle table — one live resource per key. Lives in this closure for the\n // lifetime of the battery (the machine's lifetime). Interpret-local; never\n // crosses into reducer state. Same shape as `fromTransport`'s `live` map.\n const held = new Map<string, Handle>();\n\n // Single-sourced on the kernel's `structuralHash`: `\"1\"` (string) and `1`\n // (number) render distinctly, so a key collision can't return/release the\n // wrong held handle; non-JSON keys throw loudly instead of silently returning\n // undefined.\n const keyString = (k: TKey): string => structuralHash(k);\n\n const subIdFor = (key: TKey): SubId =>\n subId(`managed:${opts.name}:${keyString(key)}`);\n\n const subBuilder = (key: TKey): ManagedResourceSub<TKey> => ({\n id: subIdFor(key),\n type: \"managed_resource\",\n key,\n });\n\n const subscribeHandler = (\n sub: ManagedResourceSub<TKey>,\n ctx: Ctx,\n _dispatch: (msg: never) => void,\n ): Dispose => {\n // Acquire. A throw here propagates out of reconcileSubs (the substrate\n // records it and re-throws after the diff pass) and the Sub is NOT\n // registered — so no cleanup runs and `held` stays clean. The\n // acquire-as-success-value discipline: no Handle, no release.\n const handle = opts.acquire(sub.key, ctx);\n const k = keyString(sub.key);\n held.set(k, handle);\n\n // Cleanup runs when the substrate's reconcileSubs leaves the phase OR\n // the key changes. Forget the Handle first so a concurrent `get` after\n // teardown begins sees `undefined`, then release.\n //\n // An async `release` is RETURNED, not swallowed: the substrate tracks the\n // promise and `stop()` awaits it (bounded), so `await runtime.stop()` means\n // the release finished. Attaching a `.catch` here and returning `void` was\n // the defect — a host doing `await stop(); env.evict()` dropped the isolate\n // mid-release, which is the leak this battery exists to prevent, moved to\n // shutdown. A rejection now reaches the runtime's `onError` under\n // `phase: \"sub-cleanup\"` instead of a `console.warn` nobody configured.\n return () => {\n held.delete(k);\n return opts.release(handle);\n };\n };\n\n return {\n sub: subBuilder,\n\n // Pair the battery with a state-gate. `TKey` / `Handle` / `Ctx` are in\n // scope here, so `subFor` builds the fully-typed sub and `subscribe`\n // forwards straight to the battery's own typed `subscribe`. The entry\n // erases ONLY `Handle` for the combinator (`TKey` and `Ctx` stay), so the\n // substrate's narrowed `ManagedResourceSub<TKey>` flows in with no cast.\n gated: <S>(\n when: (state: S) => TKey | null,\n ): GatedManagedResource<S, TKey, Ctx> => ({\n name: opts.name,\n owns: (sub) => sub.id === subIdFor(sub.key),\n subFor: (state) => {\n const key = when(state);\n return key === null ? null : subBuilder(key);\n },\n subscribe: subscribeHandler,\n }),\n\n // Dep-keyed entry. `when` is the gate (the deps = the KEY); the source runs\n // the SAME `acquire` → hold → `release`-on-dispose lifecycle the `subscribe`\n // cell does, now driven by the kernel's dep-keyed reconcile. The Msg type is\n // `never` (a managed resource dispatches nothing); the ignored `_dispatch`\n // matches the source signature.\n depKeyed: <S>(\n when: (state: S) => TKey | null,\n ): DepKeyedSub<S, never, Ctx> => ({\n deps: when,\n source: (state, _dispatch, ctx) => {\n const key = when(state);\n if (key === null) return () => {};\n return subscribeHandler(subBuilder(key), ctx, () => {});\n },\n }),\n\n subscribe: subscribeHandler,\n\n get: (key) => held.get(keyString(key)),\n\n subIdFor,\n };\n}\n\n// ---------------------------------------------------------------------------\n// combineManagedResources — fold N gated batteries into ONE subscribe cell + a\n// derived active-sub list.\n//\n// Two managed resources colliding on the single `subscribe.managed_resource`\n// key forced the machine to hand-roll a router (`if (sub.id === X.subIdFor(...))`).\n// A third copy-pastes another `if`. That router is glue that belongs in the\n// substrate, not the machine. AND the resources' active-phase gating lived in\n// a central `subscriptions(state)` list a new phase could silently forget.\n//\n// This combinator closes both gaps. Each entry carries its OWN gate at its\n// definition site (`battery.gated((s) => active(s) ? key : null)`), and the\n// combinator derives BOTH the active-sub list (`subs`) and the routing\n// (`subscribe`) from the same entries — so they can never disagree, and a new\n// phase that the gate doesn't cover simply produces no sub (no silent leak of\n// the central-list-forgot kind).\n//\n// NEUTRAL MECHANISM — no domain knowledge. The combinator knows `name` (for\n// the unique assert), `owns` (for routing), `subFor` (for the list), and\n// `subscribe` (to forward). It does not know what a checkpoint, a bridge, or a\n// Cloudflare anything is.\n//\n// Strengthens invariant 4 (external lifecycle owned by the substrate — both\n// the list and the routing are derived, never hand-driven) and invariant 6 (no\n// silent looseness — ONE managed-resource cell, and an unrouted id throws\n// loudly instead of silently installing a no-op).\n// ---------------------------------------------------------------------------\nexport function combineManagedResources<S, TKey, Ctx>(\n entries: readonly GatedManagedResource<S, TKey, Ctx>[],\n): CombinedManagedResources<S, TKey, Ctx> {\n // Assert unique names at construction — two batteries with the same `name`\n // collide on Sub id (`managed:${name}:${key}`), so the router could not tell\n // them apart. Throw immediately (mirrors foldkit's `combineSubscriptions` on\n // a duplicate key) rather than mis-route at runtime.\n const seen = new Set<string>();\n for (const entry of entries) {\n if (seen.has(entry.name)) {\n throw new Error(\n `[combineManagedResources] duplicate managed-resource name \"${entry.name}\" — names must be unique (the Sub id encodes the name; two batteries sharing it collide)`,\n );\n }\n seen.add(entry.name);\n }\n\n return {\n subs: (state) => {\n const live: ManagedResourceSub<TKey>[] = [];\n for (const entry of entries) {\n const sub = entry.subFor(state);\n if (sub !== null) live.push(sub);\n }\n return live;\n },\n\n subscribe: (sub, ctx, dispatch) => {\n const owner = entries.find((entry) => entry.owns(sub));\n if (owner === undefined) {\n // No battery owns this managed-resource SubId — a wiring bug. Surface\n // loudly rather than silently install a no-op resource (invariant 6).\n throw new Error(\n `[combineManagedResources] no battery for sub id ${sub.id}`,\n );\n }\n return owner.subscribe(sub, ctx, dispatch);\n },\n };\n}\n","// ---------------------------------------------------------------------------\n// fromTransport — the SEAM battery.\n//\n// At every seam between two TEA-shaped processes (a DO ↔ its hands container,\n// a SW background ↔ a content script, a parent ↔ a child runtime), there is\n// the SAME hand-rolled boilerplate:\n//\n// 1. The inbound message stream — a Sub keyed on a phase, opened when the\n// machine enters the phase, closed when the machine leaves it.\n// 2. A transport-close detector — when the underlying channel drops, a\n// single Msg is dispatched (`*_lost`). NOT a Sub the consumer\n// re-publishes; the lifetime IS the inbound Sub's lifetime.\n// 3. The outbound side — a Cmd whose handler hands a value to the live\n// transport, looked up by an interpret-local handle table keyed on the\n// phase's run-identity.\n//\n// Every hand-rolled seam (`hands-inbound.ts`, `forwardOutbound`, the\n// `client_disconnected` Msg arm, the WS handle map) is the same three\n// pieces with different vocabularies. The battery bakes those three in.\n//\n// What the author writes:\n//\n// 1. The vocabulary (Inbound / Outbound types).\n// 2. ONE call: `fromTransport({ name, openTransport, parseInbound, ... })`.\n//\n// What the author NEVER writes again:\n//\n// - The `Sub` shape, the `subscribe` cell, the `cleanup` Promise dance.\n// - The transport-close → `*_lost` Msg wiring.\n// - The \"find the live socket from inside an interpret handler\" lookup.\n// - The keepalive / heartbeat / sequence-number ceremony (none of those\n// belong in the machine — that's Rule 9: lifetime-bound resources are\n// Subs, not state-machine cells).\n//\n// Host-pluggable: the battery takes a `TransportFactory`, not a concrete\n// WebSocket. A Durable Object passes a workerd `WebSocket` adapter; a worker\n// thread can pass a `MessagePort` adapter; an in-process test can pass a\n// stub. Same battery; three different hosts.\n//\n// Distinct from `fromWebSocket` / `fromReconnectingWebSocket` in this same\n// subpath, which own a CONCRETE `WebSocket` and are inbound-only. This battery\n// is transport-agnostic and duplex: it also owns the outbound handle table, so\n// a Cmd handler can reach the live channel without a hand-written registry.\n//\n// Strengthens invariant 4 (external events / observation enter as Subs;\n// lifecycle owned by the substrate — the inbound stream, the close\n// detection, AND the outbound handle are all owned by the battery's Sub\n// lifecycle, not hand-driven by the consumer's cells).\n// ---------------------------------------------------------------------------\n\nimport type { DepKeyedSub, Sub, SubId } from \"../index\";\nimport { structuralHash, subId } from \"../index\";\n\n/**\n * Duplex transport. The host (DO, worker thread, in-process bus) constructs\n * one from whatever the platform offers. `fromTransport` does not know what\n * a WebSocket is — it knows `send`, `onMessage`, `onClose`.\n */\nexport interface Transport {\n /** Send a string frame. Best-effort; failures are logged at the boundary. */\n send(data: string): void;\n /**\n * Subscribe to inbound frames. Returns a cleanup that removes the listener.\n * The battery wires this in its Sub's subscribe handler; cleanup runs when\n * the phase exits (the substrate's reconcileSubs).\n */\n onMessage(listener: (data: string) => void): () => void;\n /**\n * Subscribe to a one-shot close. Returns a cleanup that removes the\n * listener. The battery dispatches `lostMsg` on close.\n */\n onClose(listener: () => void): () => void;\n /** Close the underlying channel cleanly. Called by the Sub cleanup. */\n close(): void;\n}\n\n/**\n * Factory the consumer wires to a platform-specific transport. The battery\n * passes the runtime-derived key (`runId`, or whatever identity is on the\n * phase) so per-run transports can be addressed.\n */\nexport type TransportFactory<K> = (key: K) => Transport;\n\n/**\n * The Sub the battery builds. The consumer's `Sub` union includes\n * `TransportSub<TKey>` directly — no wrapping needed, the substrate\n * reconciles by `id` and `type` like any other Sub.\n */\nexport interface TransportSub<TKey> extends Sub<\"transport\"> {\n /** The phase / run identity. Stable for the lifetime of the seam. */\n readonly key: TKey;\n}\n\nexport interface FromTransportOpts<TKey, Inbound, Outbound, M, Ctx> {\n /**\n * A short, stable name for this seam. The Sub id is derived from\n * `${name}:${stringify(key)}` so two seams in the same machine (e.g.\n * `hands` + `worker`) never collide.\n */\n readonly name: string;\n\n /**\n * Build the transport. The battery owns the lifetime; the factory is the\n * platform-specific constructor (`new WebSocket(...)` adapter on a DO,\n * `MessagePort` adapter in a worker, in-memory stub in tests).\n */\n readonly openTransport: (key: TKey, ctx: Ctx) => Transport;\n\n /**\n * Parse one inbound frame. The battery hands `unknown` through (boundary\n * parse is the consumer's responsibility per invariant 8). Returning\n * `null` drops the frame silently — useful for frames the seam doesn't\n * recognize at this layer (e.g. transport-level keepalive).\n */\n readonly parseInbound: (raw: string, key: TKey) => Inbound | null;\n\n /**\n * Map a parsed inbound to a domain Msg. Returning `null` drops the\n * dispatch (e.g. a transport-level pong that doesn't change domain state).\n */\n readonly onInbound: (inbound: Inbound, key: TKey) => M | null;\n\n /**\n * The Msg dispatched when the transport closes (peer gone, network\n * dropped, normal close). ONE Msg arm, not a Sub, not a heartbeat-derived\n * deadline — the transport's own close signal is the truth.\n */\n readonly lostMsg: (key: TKey) => M;\n\n /**\n * Serialize an outbound. Called by the Cmd handler the battery wires.\n * Pure data → string at the seam boundary.\n */\n readonly serializeOutbound: (outbound: Outbound) => string;\n}\n\n/**\n * What the battery returns: a Sub builder (for `subscriptions`), the Sub's\n * `subscribe` handler (for `subscribe.transport`), and a `send(key, outbound)`\n * helper the consumer's Cmd handler calls. Plus a `subIdFor` factory the\n * consumer can use when keying related Subs (e.g. a deadline) to the same seam\n * identity.\n */\nexport interface TransportBattery<TKey, Outbound, M, Ctx> {\n /**\n * Build the Sub for `subscriptions(state)`. The consumer calls this from\n * inside their `subscriptions` cell when the seam should be open.\n */\n readonly sub: (key: TKey) => TransportSub<TKey>;\n\n /**\n * The `subscribe.transport` handler. Drop straight into the machine's\n * `subscribe` record — the battery wires inbound + close in one shot.\n */\n readonly subscribe: (\n sub: TransportSub<TKey>,\n ctx: Ctx,\n dispatch: (msg: M) => void,\n ) => () => void;\n\n /**\n * Outbound send. The Cmd handler calls this; the battery's handle table\n * (built when subscribe ran) routes to the live transport. If the seam\n * is closed (no active sub), the send is dropped honestly (logged, not\n * thrown — the reducer already moved past caring, same shape as Rule 2\n * fire-and-forget Cmds).\n */\n readonly send: (key: TKey, outbound: Outbound) => void;\n\n /** Pre-derived subId for `(key)` — handy for deadline Subs keyed to the same seam. */\n readonly subIdFor: (key: TKey) => SubId;\n\n /**\n * Pair this seam with a state-gate, producing a dep-keyed Sub for the\n * machine's `subs` array. `when(state)` returns the seam's KEY when it\n * should be open, or `null` when it should be torn down. The kernel derives\n * the id from `structuralHash(key)` and reconciles the seam's lifetime — so\n * the author neither lists this Sub in a central `subscriptions(state)` nor\n * writes its id. Replaces `subscriptions: (s) => active(s) ? [hands.sub(...)] : []`\n * + `subscribe: { transport: hands.subscribe }` with one `subs` entry.\n *\n * The `source` closes over the battery's own `subscribe` (open inbound +\n * close, wire the handle table) using the same `ctx` the kernel passes every\n * Sub source.\n */\n readonly depKeyed: <S>(\n when: (state: S) => TKey | null,\n ) => DepKeyedSub<S, M, Ctx>;\n}\n\n/**\n * Build the battery. One call per seam at the machine's host file.\n *\n * @example\n * const handsSeam = fromTransport<RunId, HandsInbound, HandsOutbound, Msg, Ctx>({\n * name: \"hands\",\n * openTransport: (runId, ctx) => ctx.openHandsWs(runId),\n * parseInbound: (raw) => parseHandsInbound(JSON.parse(raw)),\n * onInbound: (inbound, runId) => ({ type: \"hands_said\", runId, inbound }),\n * lostMsg: (runId) => ({ type: \"hands_lost\", runId }),\n * serializeOutbound: (out) => JSON.stringify(out),\n * });\n *\n * // In the machine:\n * subscriptions: (state) => state.type === \"auditing\" ? [handsSeam.sub(state.runId)] : [],\n * subscribe: { transport: handsSeam.subscribe },\n *\n * // In interpret:\n * send_hands: async (cmd) => { handsSeam.send(cmd.runId, cmd.outbound); },\n */\nexport function fromTransport<TKey, Inbound, Outbound, M, Ctx>(\n opts: FromTransportOpts<TKey, Inbound, Outbound, M, Ctx>,\n): TransportBattery<TKey, Outbound, M, Ctx> {\n // Handle table — one live transport per key. Lives in this closure for the\n // lifetime of the battery (i.e. the machine's lifetime). Interpret-local;\n // never crosses into reducer state. The same shape as a brain's\n // `pending: Map<callId, deferred>`: imperative handles in a Map keyed on\n // run-identity, not on durable state.\n const live = new Map<string, Transport>();\n\n // Single-sourced on the kernel's `structuralHash`: `\"1\"` (string) and `1`\n // (number) render distinctly, so a key collision can't mis-route an outbound\n // send or overwrite the wrong live transport; non-JSON keys throw loudly.\n const keyString = (k: TKey): string => structuralHash(k);\n\n const subIdFor = (key: TKey): SubId =>\n subId(`${opts.name}:${keyString(key)}`);\n\n const sub = (key: TKey): TransportSub<TKey> => ({\n id: subIdFor(key),\n type: \"transport\",\n key,\n });\n\n const subscribe: TransportBattery<TKey, Outbound, M, Ctx>[\"subscribe\"] = (\n s,\n ctx,\n dispatch,\n ) => {\n // Open the transport. The battery owns the lifetime.\n const t = opts.openTransport(s.key, ctx);\n const k = keyString(s.key);\n\n // ACQUIRE-AS-SUCCESS-VALUE: wire EVERYTHING first, publish to the handle\n // table last. An adapter over an already-CLOSING socket throws while\n // wiring; when `live.set` ran first, that left the transport in the table\n // with NO sub registered — so no cleanup ever ran, `send` wrote into a\n // half-wired seam with no inbound listener, and every subsequent reconcile\n // opened another one: an unbounded leak from one recoverable throw. On the\n // way out we undo exactly what we did (drop the listeners, close the\n // transport) and rethrow, so the substrate records the failure and the\n // battery holds nothing. Same discipline `defineManagedResource` states in\n // its own header — no Handle, no release.\n let offMessage: (() => void) | undefined;\n let offClose: (() => void) | undefined;\n try {\n // Wire inbound. Parse boundary lives here per invariant 8.\n offMessage = t.onMessage((raw) => {\n const parsed = opts.parseInbound(raw, s.key);\n if (parsed === null) return;\n const msg = opts.onInbound(parsed, s.key);\n if (msg === null) return;\n dispatch(msg);\n });\n\n // Wire close. ONE Msg arm — `*_lost`. No heartbeat, no sequence\n // numbers, no \"is the peer still alive\" derivation: the transport's\n // own close IS the liveness signal.\n offClose = t.onClose(() => {\n dispatch(opts.lostMsg(s.key));\n });\n } catch (err) {\n offMessage?.();\n offClose?.();\n try {\n t.close();\n } catch (closeErr) {\n // Boundary log: the wiring failure is the fact worth surfacing, and it\n // is about to be rethrown. A close that also fails must not replace it.\n console.warn(\n `[fromTransport:${opts.name}] close during failed wiring failed`,\n closeErr,\n );\n }\n throw err;\n }\n\n live.set(k, t);\n\n // Cleanup runs when the substrate's reconcileSubs leaves the phase.\n return () => {\n offMessage?.();\n offClose?.();\n live.delete(k);\n try {\n t.close();\n } catch (err) {\n // Boundary log: cleanup-time close failures are fire-and-forget\n // (Rule 2). The reducer is already past caring.\n console.warn(`[fromTransport:${opts.name}] close failed`, err);\n }\n };\n };\n\n return {\n sub,\n subscribe,\n\n send: (key, outbound) => {\n const t = live.get(keyString(key));\n if (t === undefined) {\n // Honest drop. The reducer's Cmd burst can race a `*_lost`\n // transition; the Cmd is fire-and-forget at that point.\n console.warn(\n `[fromTransport:${opts.name}] send dropped — no live transport for key`,\n keyString(key),\n );\n return;\n }\n t.send(opts.serializeOutbound(outbound));\n },\n\n subIdFor,\n\n // Dep-keyed entry: `when` is the gate (the deps slice → the seam key);\n // `source` re-runs `when(state)` to recover the typed key and opens the seam\n // via the battery's own `subscribe`. The kernel only calls `source` when\n // `deps(state)` was non-null, so `when` is non-null here; the key type never\n // escapes the entry (cast-free existential).\n depKeyed: <S>(when: (state: S) => TKey | null): DepKeyedSub<S, M, Ctx> => ({\n deps: when,\n source: (state, dispatch, ctx) => {\n const key = when(state);\n if (key === null) return () => {};\n return subscribe(sub(key), ctx, dispatch);\n },\n }),\n };\n}\n"]}
|
|
1
|
+
{"version":3,"sources":["../src/subs/define-listener.ts","../src/subs/from-broadcast-channel.ts","../src/subs/from-event-source.ts","../src/subs/from-event-target.ts","../src/subs/from-interval.ts","../src/subs/from-port.ts","../src/subs/from-reconnecting-web-socket.ts","../src/subs/from-web-socket.ts","../src/subs/managed-resource.ts","../src/subs/transport.ts"],"names":[],"mappings":"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","file":"chunk-2UGTDYOK.js","sourcesContent":["// ---------------------------------------------------------------------------\n// defineListener — listener-as-resource. The kernel primitive that turns an\n// imperative `add`/`remove` listener pair into a substrate-owned Sub whose\n// cleanup is MANDATORY and DERIVED — a forgotten or no-op disposer is\n// unrepresentable.\n//\n// The leak it kills: every author who wires a raw listener hand-rolls the SAME\n// skeleton —\n//\n// return (sub, _ctx, dispatch) => {\n// const listener = (e) => { const m = msgFn(e, sub); if (m !== null) dispatch(m); };\n// target.addListener(listener);\n// return () => target.removeListener(listener); // ← author-driven\n// };\n//\n// — and each re-write can SILENTLY LEAK two ways the type system never sees:\n//\n// 1. The disposer is a no-op (`return () => {}`) — the listener stays armed\n// forever. Nothing forces the returned cleanup to undo the add.\n// 2. The disposer calls `remove` with a DIFFERENT function than `add`\n// received (a fresh closure, a re-bound `this`) — the original listener\n// is never removed; it leaks while a phantom remove succeeds silently.\n//\n// `fromBroadcastChannel`, `fromEventTarget`, and every host-side bridge\n// hand-roll this skeleton today. They happen to be correct — but the\n// correctness is convention (rung 5), not structure. `defineListener` moves it\n// to rung 1: the listener the substrate builds is the SAME reference handed to\n// both `add` and `remove`, and the cleanup IS `() => remove(thatListener, ...)`.\n// You cannot write a no-op disposer because you do not write the disposer at\n// all.\n//\n// Shape:\n//\n// // Args is the tuple chrome invokes the listener with — here a single\n// // [Alarm]. The substrate builds ONE listener of this exact native shape.\n// const fromChromeAlarm = defineListener<[chrome.alarms.Alarm], S>({\n// add: (l) => chrome.alarms.onAlarm.addListener(l),\n// remove: (l) => chrome.alarms.onAlarm.removeListener(l),\n// });\n//\n// // ...then at the subscribe cell, supply the Msg projection:\n// subscribe: { tick: fromChromeAlarm<M>((sub, alarm) => ({ type: \"tick\", ... })) }\n//\n// - `add`/`remove` receive `(listener, sub, ctx)`. The `listener` is the\n// EXACT function the substrate built; pass it straight to the platform's\n// addListener/removeListener — NO wrapper. A wrapper inside `add` would\n// break reference identity (the platform stores the wrapper, but the\n// substrate hands `remove` the unwrapped listener → silent leak). Carrying\n// the platform's native `Args` tuple on the listener is what lets it be\n// registered directly. `sub`/`ctx` let the target be pulled from `ctx` or\n// keyed on the `sub` (the `getTarget()`-from-ctx shape fromPort needs).\n//\n// - `msgFn(sub, ...args) => M | null` — the universal drop-on-null filter\n// every factory in this subpath carries. `null` drops the dispatch; the\n// listener stays armed. `sub` is first (always present); the platform args\n// follow in their native order.\n//\n// Generalizes the `from*` pattern: a closure-owned disposer makes a forgotten\n// cleanup a syntax error, and this brings it to every host/satellite.\n//\n// Strengthens invariant 4 (external events enter as Subs; the lifecycle —\n// add on subscribe, remove on cleanup — is owned by the substrate, never the\n// author) and invariant 6 (no silent looseness: a leaked listener is a silent\n// failure, and this primitive removes the way to write one).\n// ---------------------------------------------------------------------------\n\nimport type { Sub } from \"../index\";\nimport { dispatchIfPresent, type SubscribeHandler } from \"./types\";\n\n/**\n * The imperative listener target, expressed as the `add`/`remove` pair the\n * substrate pairs into a reconciled resource. Both halves receive the SAME\n * `listener` reference — the substrate builds it once — so `remove` can never\n * be handed a different function than `add` saw.\n *\n * `add`/`remove` also receive `(sub, ctx)` so the target may be resolved from\n * `ctx` (a runtime, a port, a DOM node looked up lazily) or keyed on the Sub\n * (an alarm name, a channel name). Resolving the target inside `add` and\n * closing over nothing but the listener keeps a target swap from leaking the\n * old target's listener — same rationale as `fromEventTarget`'s captured\n * target.\n *\n * `Args` is the tuple the platform invokes the listener with — `[Alarm]` for\n * `chrome.alarms.onAlarm`, `[message, sender, sendResponse]` for\n * `chrome.runtime.onMessage`, `[changes, areaName]` for\n * `chrome.storage.onChanged`. The substrate builds ONE function of this exact\n * shape and threads the IDENTICAL reference to both `add` and `remove` — so a\n * platform that matches `removeListener` by reference (every chrome surface)\n * removes exactly what it added. A consumer that wrapped the listener inside\n * `add` would break reference identity (leak #2 above); carrying the native\n * `Args` on the listener itself removes the need for any wrapper.\n */\nexport interface ListenerTarget<\n Args extends readonly unknown[],\n S extends Sub,\n Ctx,\n> {\n add: (listener: (...args: Args) => void, sub: S, ctx: Ctx) => void;\n remove: (listener: (...args: Args) => void, sub: S, ctx: Ctx) => void;\n}\n\n/**\n * Build a listener-backed Sub factory whose cleanup is mandatory and derived.\n *\n * `Args`, `S`, `Ctx` are fixed when the target is defined — they describe the\n * one listener topology this factory owns (the platform arg tuple, which Sub\n * shape it filters on, what context resolves it). `M` is supplied at the\n * subscribe cell because the same listener feeds different Msgs in different\n * machines. Pinning `S` here (rather than at the `msgFn` step) lets\n * `add`/`remove` read the concrete Sub's fields — `sub.alarmName`,\n * `sub.channel` — with NO cast at the substrate edge: the narrowing happens in\n * author-land where the Sub shape is in scope.\n *\n * The returned function is a `SubscribeHandler` factory in the exact shape of\n * every other `@demlik/tea/subs` battery: supply the `msgFn` projection, get a\n * `(sub, ctx, dispatch) => cleanup` handler assignable to `machine.subscribe`.\n * The cleanup the author NEVER writes is `() => remove(listener, sub, ctx)`,\n * with `listener` the identical reference passed to `add`.\n */\nexport function defineListener<\n Args extends readonly unknown[],\n S extends Sub = Sub,\n Ctx = unknown,\n>(\n target: ListenerTarget<Args, S, Ctx>,\n): <M>(\n msgFn: (sub: S, ...args: Args) => M | null,\n) => SubscribeHandler<S, M, Ctx> {\n return <M>(\n msgFn: (sub: S, ...args: Args) => M | null,\n ): SubscribeHandler<S, M, Ctx> => {\n return (sub, ctx, dispatch) => {\n const listener = (...args: Args): void => {\n dispatchIfPresent(dispatch, msgFn(sub, ...args));\n };\n // ONE listener, identical reference to both halves — chrome's\n // removeListener matches by reference, so this removes exactly what\n // add registered. No wrapper, no WeakMap, no reference drift.\n target.add(listener, sub, ctx);\n return () => {\n target.remove(listener, sub, ctx);\n };\n };\n };\n}\n","// ---------------------------------------------------------------------------\n// fromBroadcastChannel — universal `BroadcastChannel`-shaped Sub factory.\n//\n// `BroadcastChannel` is a same-origin pub/sub primitive available in\n// every modern browser context (window, worker, service worker) AND in\n// Cloudflare Workers (with origin scoped to the worker isolate). The\n// constructor opens the channel; the `message` event fires whenever\n// another participant on the same channel name posts a message; the\n// `close()` method releases the OS resource.\n//\n// The factory carries `channelName` on the Sub itself (not in the\n// closure) so the same factory instance handles every BroadcastChannel\n// Sub in a machine — one Sub per channel, named by id, sharing the\n// factory's lifecycle code. Renaming a channel requires a new Sub.id\n// (the reconcile pass otherwise leaves the existing channel open).\n//\n// `msgFn` returns `M | null` — same drop-on-null pattern as\n// `fromEventTarget`. The MessageEvent's `data` is `unknown` from the\n// platform's perspective; the caller decides whether the payload is a\n// recognizable shape, and returns `null` to drop unknown messages\n// without polluting the reducer with parse-discard cells.\n//\n// Cleanup calls `channel.close()`, which removes the listener AND\n// releases the channel reference — `removeEventListener` alone would\n// leak the BroadcastChannel handle. The pair is intentional.\n// ---------------------------------------------------------------------------\n\nimport type { Sub } from \"../index\";\nimport type {\n MinimalBroadcastChannelCtor,\n MinimalMessageEvent,\n} from \"./platform\";\nimport { dispatchIfPresent, type SubscribeHandler } from \"./types\";\n\ntype BroadcastSubData = { channelName: string };\n\ndeclare const BroadcastChannel: MinimalBroadcastChannelCtor;\n\nexport function fromBroadcastChannel<S extends Sub & BroadcastSubData, M>(\n msgFn: (event: MinimalMessageEvent, sub: S) => M | null,\n): SubscribeHandler<S, M, unknown> {\n return (sub, _ctx, dispatch) => {\n const channel = new BroadcastChannel(sub.channelName);\n const listener = (event: MinimalMessageEvent): void => {\n dispatchIfPresent(dispatch, msgFn(event, sub));\n };\n channel.addEventListener(\"message\", listener);\n return () => {\n channel.removeEventListener(\"message\", listener);\n channel.close();\n };\n };\n}\n","// ---------------------------------------------------------------------------\n// fromEventSource — universal `EventSource` (server-sent events) Sub\n// factory.\n//\n// SSE is the asymmetric streaming protocol used by the agent log /\n// audit log pipelines: HTTP-shaped open, server pushes events forever,\n// client receives until close. `EventSource` is the standard browser\n// API; the constructor opens the connection, `message` fires per\n// server-pushed event, `error` fires on disconnect or non-2xx, `open`\n// fires once on initial handshake, `close()` tears the connection\n// down.\n//\n// Three callbacks because three observable events ARE three concerns\n// the caller routinely cares about:\n//\n// - `onMessage(data, sub)` — required. The SSE payload's `data` field\n// is `string` from the platform; the caller decides parse + Msg\n// shape. Returns `M | null`; null drops the dispatch.\n//\n// - `onError(event, sub)` — optional. SSE's `error` event fires both\n// on transient network drops (the browser auto-reconnects) AND on\n// permanent failures. Most consumers want to surface ONE of these\n// as a Msg (e.g. \"stream-disconnected\") without folding both into\n// `onMessage`'s discriminant. Omitting the callback drops error\n// events silently.\n//\n// - `onOpen(sub)` — optional. The connection's initial handshake\n// succeeded. Useful for \"show 'connected' indicator\" Msgs. Omitting\n// drops open events silently. NOT fired on auto-reconnect by every\n// browser implementation — treat the event as best-effort.\n//\n// `url` lives on the Sub itself (`EventSourceSubData`). Same id +\n// different url = the substrate's reconcile pass leaves the existing\n// connection alone (the URL change is silent). Changing the URL\n// requires a new Sub.id.\n//\n// Cleanup is `close()` + `removeEventListener` for all three —\n// belt-and-suspenders against EventSource implementations that\n// dispatch events into already-closed sources.\n// ---------------------------------------------------------------------------\n\nimport type { Sub } from \"../index\";\nimport type {\n MinimalEvent,\n MinimalEventSourceCtor,\n MinimalMessageEvent,\n} from \"./platform\";\nimport { dispatchIfPresent, type SubscribeHandler } from \"./types\";\n\ntype EventSourceSubData = { url: string };\n\ndeclare const EventSource: MinimalEventSourceCtor;\n\nexport interface EventSourceFactoryOpts<S, M> {\n onMessage: (data: string, sub: S) => M | null;\n onError?: (event: MinimalEvent, sub: S) => M | null;\n onOpen?: (sub: S) => M | null;\n}\n\nexport function fromEventSource<S extends Sub & EventSourceSubData, M>(\n opts: EventSourceFactoryOpts<S, M>,\n): SubscribeHandler<S, M, unknown> {\n return (sub, _ctx, dispatch) => {\n const source = new EventSource(sub.url);\n\n const messageListener = (event: MinimalMessageEvent): void => {\n // The SSE `data` payload is a string per the platform contract.\n // Cast at the boundary: callers express `(data: string) => M | null`\n // and never see `unknown` flowing in from the platform global.\n dispatchIfPresent(dispatch, opts.onMessage(event.data as string, sub));\n };\n source.addEventListener(\"message\", messageListener);\n\n const onError = opts.onError;\n const errorListener = onError\n ? (event: MinimalEvent): void => {\n dispatchIfPresent(dispatch, onError(event, sub));\n }\n : undefined;\n if (errorListener) source.addEventListener(\"error\", errorListener);\n\n const onOpen = opts.onOpen;\n const openListener = onOpen\n ? (): void => {\n dispatchIfPresent(dispatch, onOpen(sub));\n }\n : undefined;\n if (openListener) source.addEventListener(\"open\", openListener);\n\n return () => {\n source.removeEventListener(\"message\", messageListener);\n if (errorListener) source.removeEventListener(\"error\", errorListener);\n if (openListener) source.removeEventListener(\"open\", openListener);\n source.close();\n };\n };\n}\n","// ---------------------------------------------------------------------------\n// fromEventTarget — universal addEventListener/removeEventListener Sub\n// factory over any `EventTarget` (DOM nodes, `window`, `document`,\n// `BroadcastChannel`, `MessagePort`, `Worker`, `ServiceWorker`, etc.).\n//\n// Two non-obvious shape choices:\n//\n// - `getTarget: () => EventTarget` is a thunk, not a direct value. The\n// factory MUST work in environments where the target is not available\n// at module-load time — service workers run before any `document`\n// exists; some targets are constructed lazily from `ctx`. Deferring\n// the lookup to subscribe time also means a state-driven swap of the\n// target requires only a new Sub.id; the factory pulls the current\n// target on subscribe.\n//\n// - `msgFn` may return `null` — that emission is *dropped*. The event\n// fired, the factory called `msgFn`, but the caller decided \"this\n// event is not interesting given current state.\" No dispatch is made;\n// the listener stays armed for the next event. This is the explicit\n// filter point at the Sub boundary — the alternative (filtering inside\n// the reducer via a noop cell) costs a transition + reconcile pass per\n// filtered event.\n//\n// Cleanup uses the SAME `getTarget()` instance captured at subscribe time\n// so a target swap mid-Sub doesn't leak the listener on the old target.\n// We close over the resolved target rather than calling `getTarget()`\n// again at cleanup — the alternative (re-resolving) is the cause of\n// silent listener leaks in long-lived runtimes.\n// ---------------------------------------------------------------------------\n\nimport type { Sub } from \"../index\";\nimport type { MinimalEvent, MinimalEventTarget } from \"./platform\";\nimport { dispatchIfPresent, type SubscribeHandler } from \"./types\";\n\nexport function fromEventTarget<S extends Sub, M>(\n getTarget: () => MinimalEventTarget,\n eventName: string,\n msgFn: (event: MinimalEvent, sub: S) => M | null,\n): SubscribeHandler<S, M, unknown> {\n return (sub, _ctx, dispatch) => {\n const target = getTarget();\n const listener = (event: MinimalEvent): void => {\n dispatchIfPresent(dispatch, msgFn(event, sub));\n };\n target.addEventListener(eventName, listener);\n return () => {\n target.removeEventListener(eventName, listener);\n };\n };\n}\n","// ---------------------------------------------------------------------------\n// fromInterval — universal `setInterval`-shaped Sub factory.\n//\n// The recurring shape at every `subscribe[type]` cell that wraps a periodic\n// timer is the same five lines: `setInterval` the dispatch, return a\n// `clearInterval` cleanup. The factory absorbs both halves of the lifecycle\n// (subscribe + cleanup); the caller keeps the intent — which Sub variant,\n// which Msg to dispatch.\n//\n// Timing data (`intervalMs`) lives on the Sub itself, not in the closure.\n// The substrate already reconciles Subs by id on every transition; carrying\n// the period on the Sub means a `subscriptions(state)` function that\n// returns `{ id, type, intervalMs: 30_000 }` in one state and the same id\n// with `intervalMs: 60_000` later... will NOT re-wire the interval (the id\n// is unchanged, the reconcile pass leaves it running). That's a tradeoff\n// the substrate's reconcile contract pins, not this factory — if you want\n// the interval to change, change the Sub's id too.\n//\n// Strengthens invariant 9 (named, small surface) and invariant 2 (the Sub\n// carries the data; the lifecycle is in the factory; the reducer never\n// sees the timer).\n// ---------------------------------------------------------------------------\n\nimport type { Sub } from \"../index\";\nimport type { SubscribeHandler } from \"./types\";\n\ntype IntervalSubData = { intervalMs: number };\n\nexport function fromInterval<S extends Sub & IntervalSubData, M>(\n msgFn: (sub: S) => M,\n): SubscribeHandler<S, M, unknown> {\n return (sub, _ctx, dispatch) => {\n const handle = setInterval(() => {\n dispatch(msgFn(sub));\n }, sub.intervalMs);\n return () => {\n clearInterval(handle);\n };\n };\n}\n","// ---------------------------------------------------------------------------\n// fromPort — universal `Runtime.subscribePort`-shaped Sub factory.\n//\n// The canonical cross-runtime coordination pattern in @demlik/tea: one\n// runtime emits to a typed Port; another runtime's machine declares an\n// `audit-idle`-shaped Sub whose subscribe handler attaches to the\n// publisher's Port. The recurring 4-line shape at every such cell:\n//\n// \"audit-idle\": (_sub, ctx, dispatch) =>\n// ctx.auditRuntime.subscribePort(auditIdlePort, (value) => {\n// dispatch(buildMsg(value));\n// });\n//\n// The factory absorbs the cleanup-returning subscribePort call AND the\n// dispatch-on-emit wiring. The caller keeps the three pieces of intent:\n//\n// 1. `selectRuntime(ctx)` — which sibling Runtime publishes the Port.\n// This is a selector (not a direct ctx.auditRuntime reference) so\n// the factory does NOT pin the ctx shape — any machine whose ctx\n// exposes some Runtime<...> reference is compatible by selecting it.\n// A direct `Runtime` parameter would have made the factory shape\n// depend on having that Runtime in scope at machine-definition\n// time, which is a worse match for the \"ctx is the boundary\"\n// contract this substrate already commits to.\n//\n// 2. `port: Port<T>` — the typed channel itself. Identity is by\n// reference (the substrate enforces unique names at definePort\n// time); pass the same `Port<T>` value the publisher emits to.\n//\n// 3. `msgFn(value, sub) => M | null` — the value-to-Msg mapping, with\n// drop-on-null semantics matching every other factory in this\n// subpath. The published value's type `T` flows through to `msgFn`'s\n// first param so the caller never re-asserts the payload shape.\n//\n// Strengthens invariant 9 (the cross-runtime Port topology is named at\n// one factory site instead of redrawn at every subscribe cell).\n// ---------------------------------------------------------------------------\n\nimport type { Port, Runtime, Sub } from \"../index\";\nimport { dispatchIfPresent, type SubscribeHandler } from \"./types\";\n\n// `RS` / `RM` carry the sibling Runtime's concrete State and Msg through the\n// selector: a caller that selects a `Runtime<AuditState, AuditMsg>` keeps that\n// type at the seam instead of collapsing it to `Runtime<unknown, {type:string}>`.\n// They default to the prior broad pair so existing call sites that select a\n// less-specific Runtime still compile — the factory still only ever calls\n// `subscribePort`, so it does not read RS/RM, but the boundary no longer\n// hardcodes the widest type when the caller knows a narrower one.\nexport function fromPort<\n S extends Sub,\n M,\n Ctx,\n T,\n RS = unknown,\n RM extends { type: string } = { type: string },\n>(\n selectRuntime: (ctx: Ctx) => Runtime<RS, RM>,\n port: Port<T>,\n msgFn: (value: T, sub: S) => M | null,\n): SubscribeHandler<S, M, Ctx> {\n return (sub, ctx, dispatch) => {\n const runtime = selectRuntime(ctx);\n return runtime.subscribePort(port, (value) => {\n dispatchIfPresent(dispatch, msgFn(value, sub));\n });\n };\n}\n","// ---------------------------------------------------------------------------\n// fromReconnectingWebSocket — a `WebSocket` Sub factory that survives drops.\n//\n// `fromWebSocket` is the SSE-sibling ONE-SHOT factory: its cleanup `close()`s\n// the socket and it never re-opens on an unexpected drop (issue #188). That is\n// the right shape for a connection the caller manages, but any tea client over\n// an unreliable transport — games, presence, collab — needs the connection to\n// recover transparently, without a page reload. Rather than re-roll a backoff\n// loop in every consumer (vortex's `ReconnectingSocket` is the proven shape,\n// services/vortex/src/reconnecting-socket.ts), this is the reconnecting variant\n// folded into the substrate's Sub surface.\n//\n// The reconnect contract, mirroring vortex's wrapper:\n//\n// - On an UNEXPECTED close (`onclose` the substrate didn't initiate) it\n// re-opens after a CAPPED exponential backoff: `min(base * 2^attempt, max)`.\n// A successful handshake resets `attempt` to 0, so the next drop retries\n// fast again.\n//\n// - A CALLER-INITIATED close (the Sub's cleanup, e.g. the machine\n// transitioned the Sub away) suppresses reconnect: handlers are detached\n// BEFORE `close(1000)` so the teardown can't fire `onClose` or schedule a\n// reconnect. After cleanup the factory never re-opens.\n//\n// - `connect` and `schedule` are INJECTABLE (defaulting to the global\n// `WebSocket` and `setTimeout`/`clearTimeout`), the test seam that lets the\n// reconnect/backoff behavior be unit-tested with a fake socket + a manual\n// clock — no DOM, no real network, no timers — exactly as vortex's wrapper\n// is tested.\n//\n// Callback contract (same `M | null` \"null drops the dispatch\" shape as\n// `fromWebSocket`, plus one reconnect-specific hook):\n//\n// - `onMessage(data, sub)` — required. Raw `unknown` frame; the caller parses.\n// - `onOpen(sub)` — optional. Fires on EVERY successful handshake — the first\n// open AND every reconnect — so a \"connected\" indicator stays accurate.\n// - `onError(event, sub)` — optional. The `error` event; `close` still drives\n// the reconnect (one path), so this is purely a notification.\n// - `onClose(code, reason, sub)` — optional. Fires on every close the\n// substrate observes (a drop that will be retried, too) — NOT on the\n// caller's own teardown, whose handlers are detached first.\n// - `onReconnect(attempt, sub)` — optional. Fires when a RE-open (any open\n// after the first) succeeds, carrying the 1-based reconnect count. This is\n// the resync hook: the place to dispatch a \"re-fetch the authoritative\n// snapshot, discard predicted state\" Msg after the transport recovered.\n//\n// Like `fromWebSocket`, `wsUrl` lives on the Sub (`WebSocketSubData`); every\n// reconnect reads `sub.wsUrl` afresh, so a reconcile that re-subscribes with a\n// new url reconnects to the new endpoint.\n// ---------------------------------------------------------------------------\n\nimport type { Sub } from \"../index\";\nimport type { WebSocketSubData } from \"./from-web-socket\";\nimport type {\n MinimalEvent,\n MinimalWebSocket,\n MinimalWebSocketCtor,\n} from \"./platform\";\nimport { dispatchIfPresent, type SubscribeHandler } from \"./types\";\n\ndeclare const WebSocket: MinimalWebSocketCtor;\n\n/** Cancels a scheduled reconnect timer (the inverse of `schedule`). */\nexport type CancelTimer = () => void;\n\nexport interface ReconnectingWebSocketFactoryOpts<S, M> {\n onMessage: (data: unknown, sub: S) => M | null;\n onOpen?: (sub: S) => M | null;\n onError?: (event: MinimalEvent, sub: S) => M | null;\n onClose?: (code: number, reason: string, sub: S) => M | null;\n /**\n * Fires when a reconnection (any open after the first) succeeds, carrying the\n * 1-based reconnect count — the resync hook. Omitting drops reconnect events\n * silently.\n */\n onReconnect?: (attempt: number, sub: S) => M | null;\n /** First backoff delay in ms (doubles each failed attempt). Default 250. */\n backoffBaseMs?: number;\n /** Backoff ceiling in ms. Default 5000. */\n backoffMaxMs?: number;\n /** Inject the socket constructor (defaults to the global `WebSocket`); the test seam. */\n connect?: (url: string) => MinimalWebSocket;\n /** Inject the timer (defaults to `setTimeout`/`clearTimeout`); the test seam. */\n schedule?: (fn: () => void, ms: number) => CancelTimer;\n}\n\nconst defaultConnect = (url: string): MinimalWebSocket => new WebSocket(url);\n\nconst defaultSchedule = (fn: () => void, ms: number): CancelTimer => {\n const handle = setTimeout(fn, ms);\n return () => clearTimeout(handle);\n};\n\nexport function fromReconnectingWebSocket<S extends Sub & WebSocketSubData, M>(\n opts: ReconnectingWebSocketFactoryOpts<S, M>,\n): SubscribeHandler<S, M, unknown> {\n const backoffBaseMs = opts.backoffBaseMs ?? 250;\n const backoffMaxMs = opts.backoffMaxMs ?? 5000;\n const connect = opts.connect ?? defaultConnect;\n const schedule = opts.schedule ?? defaultSchedule;\n\n const { onMessage, onOpen, onError, onClose, onReconnect } = opts;\n\n return (sub, _ctx, dispatch) => {\n let ws: MinimalWebSocket | null = null;\n let attempt = 0;\n let opened = false;\n let closedByCaller = false;\n let cancelReconnect: CancelTimer | null = null;\n\n const emit = (msg: M | null): void => dispatchIfPresent(dispatch, msg);\n\n const handleClose = (code: number, reason: string): void => {\n ws = null;\n if (onClose) emit(onClose(code, reason, sub));\n // A teardown the substrate initiated detaches handlers first, so this\n // path only runs for an UNEXPECTED close — reconnect with capped backoff.\n if (closedByCaller) return;\n const delay = Math.min(backoffBaseMs * 2 ** attempt, backoffMaxMs);\n attempt += 1;\n cancelReconnect = schedule(() => {\n cancelReconnect = null;\n if (!closedByCaller) open();\n }, delay);\n };\n\n function open(): void {\n const socket = connect(sub.wsUrl);\n ws = socket;\n\n socket.onopen = (): void => {\n // `opened` BEFORE this open marks it a reconnect; `attempt` is the\n // pending count for the drop that triggered it (1-based once we reset).\n const reconnected = opened;\n const reconnectCount = attempt;\n attempt = 0;\n opened = true;\n if (onOpen) emit(onOpen(sub));\n if (reconnected && onReconnect) emit(onReconnect(reconnectCount, sub));\n };\n\n socket.onmessage = (event): void => {\n emit(onMessage(event.data, sub));\n };\n\n // `error` precedes `close`; let `close` drive the reconnect so there is\n // one path. The handler is still wired (as a no-op when the caller didn't\n // supply `onError`) so a platform error event has a sink.\n socket.onerror = onError\n ? (event): void => emit(onError(event, sub))\n : (): void => {};\n\n socket.onclose = (event): void => {\n handleClose(event.code, event.reason);\n };\n }\n\n open();\n\n return () => {\n // Deliberate teardown: cancel any pending reconnect, detach handlers so\n // the cleanup `close()` can't fire `onClose` or schedule a reconnect,\n // then close the live socket. After this the factory never re-opens.\n closedByCaller = true;\n if (cancelReconnect !== null) {\n cancelReconnect();\n cancelReconnect = null;\n }\n const socket = ws;\n ws = null;\n if (socket !== null) {\n socket.onopen = null;\n socket.onmessage = null;\n socket.onerror = null;\n socket.onclose = null;\n // 1000 = \"normal closure\" per RFC 6455 §7.4.1.\n socket.close(1000);\n }\n };\n };\n}\n","// ---------------------------------------------------------------------------\n// fromWebSocket — universal `WebSocket` Sub factory. The symmetric sibling\n// of `fromEventSource`.\n//\n// WebSocket is the bidirectional streaming protocol used wherever the\n// server pushes events AND the client pushes commands on the same\n// connection. The constructor opens the connection, `onopen` fires once\n// on handshake, `onmessage` fires per server-pushed frame, `onerror`\n// fires on transient or permanent failure, `onclose` fires once on\n// teardown carrying `code` + `reason`. `close(code)` initiates a clean\n// shutdown from the client side.\n//\n// Four callbacks because four observable events ARE four concerns the\n// caller routinely cares about. The Sub's `wsUrl` lives on the Sub itself\n// (`WebSocketSubData`) — same id + different `wsUrl` is silent under the\n// reconcile pass, identical to `fromEventSource`'s `url` contract.\n//\n// - `onMessage(data, sub)` — required. The platform delivers `data` as\n// `string | Blob | ArrayBuffer` depending on `binaryType` and the\n// server's frame type. The factory does NOT bake in a parse contract:\n// the callback receives the raw `unknown` and decides parse + Msg\n// shape. Returns `M | null`; null drops the dispatch.\n//\n// - `onOpen(sub)` — optional. Connection handshake succeeded. Useful\n// for \"show 'connected' indicator\" Msgs. Omitting drops open events\n// silently.\n//\n// - `onError(event, sub)` — optional. WebSocket's `error` event fires\n// on transient drops AND permanent failures; like SSE, most consumers\n// want one Msg (\"connection-errored\") without folding both into\n// `onMessage`'s discriminant. Omitting drops error events silently.\n//\n// - `onClose(code, reason, sub)` — optional. The connection is gone.\n// `code` is the WebSocket close code (1000 = clean, 1006 = abnormal,\n// etc.). `reason` is the server-supplied string (often empty).\n// Omitting drops close events silently.\n//\n// Cleanup is `close(1000)` — the IANA-registered clean-shutdown code.\n// The factory intentionally does NOT bake in registry semantics (e.g.\n// the extension's `ctx.wsRegistry` so `interpret.send_ws` can reach the\n// live socket): that's consumer glue. A consumer that needs the live\n// socket for outbound sends wires it from inside the subscribe handler\n// or keeps a hand-written Sub alongside this factory.\n// ---------------------------------------------------------------------------\n\nimport type { Sub } from \"../index\";\nimport type {\n MinimalCloseEvent,\n MinimalEvent,\n MinimalMessageEvent,\n MinimalWebSocketCtor,\n} from \"./platform\";\nimport { dispatchIfPresent, type SubscribeHandler } from \"./types\";\n\nexport type WebSocketSubData = { wsUrl: string };\n\ndeclare const WebSocket: MinimalWebSocketCtor;\n\nexport interface WebSocketFactoryOpts<S, M> {\n onMessage: (data: unknown, sub: S) => M | null;\n onOpen?: (sub: S) => M | null;\n onError?: (event: MinimalEvent, sub: S) => M | null;\n onClose?: (code: number, reason: string, sub: S) => M | null;\n}\n\nexport function fromWebSocket<S extends Sub & WebSocketSubData, M>(\n opts: WebSocketFactoryOpts<S, M>,\n): SubscribeHandler<S, M, unknown> {\n return (sub, _ctx, dispatch) => {\n const ws = new WebSocket(sub.wsUrl);\n\n ws.onmessage = (event: MinimalMessageEvent): void => {\n // The platform delivers `data` as `unknown` — the factory hands it\n // through untouched and the caller chooses the parse strategy.\n dispatchIfPresent(dispatch, opts.onMessage(event.data, sub));\n };\n\n const onOpen = opts.onOpen;\n if (onOpen) {\n ws.onopen = (): void => {\n dispatchIfPresent(dispatch, onOpen(sub));\n };\n }\n\n const onError = opts.onError;\n if (onError) {\n ws.onerror = (event: MinimalEvent): void => {\n dispatchIfPresent(dispatch, onError(event, sub));\n };\n }\n\n const onClose = opts.onClose;\n if (onClose) {\n ws.onclose = (event: MinimalCloseEvent): void => {\n dispatchIfPresent(dispatch, onClose(event.code, event.reason, sub));\n };\n }\n\n return () => {\n // Detach the lifecycle handlers BEFORE close() so the consumer's\n // `onClose` Msg isn't fired from a teardown the substrate initiated.\n // Mirrors fromEventSource's removeEventListener pass.\n ws.onmessage = null;\n ws.onopen = null;\n ws.onerror = null;\n ws.onclose = null;\n // 1000 = \"normal closure\" per RFC 6455 §7.4.1.\n ws.close(1000);\n };\n };\n}\n","// ---------------------------------------------------------------------------\n// defineManagedResource — a Model-gated resource with a MANDATORY release.\n//\n// foldkit's ManagedResource, ported. The recurring shape: a resource whose\n// lifetime is \"while the machine is in phase X, hold Y open; on leaving X,\n// tear Y down\" — a checkpoint store, a span, a lease, a pooled connection.\n// Rule 9 says that is a Sub the substrate reconciles, NOT a start/end Cmd\n// pair hand-driven across cells. Hand-driving leaks: every transition cell\n// has to remember to end-then-start, and one missed cell leaks the resource\n// forever (the checkpoint-namespace leak this battery was built to kill).\n//\n// The author writes three things — `{ name, acquire, release }` — and gets\n// back a `.sub(key)` to drop into `subscriptions(state)` plus a `.subscribe`\n// handler to drop into the machine's `subscribe` record. The substrate's\n// existing `reconcileSubs` (packages/tea/src/run.ts) does the rest:\n// - sub APPEARS in the desired set → handler runs → `acquire(key, ctx)`.\n// - sub DISAPPEARS (phase left) → cleanup runs → `release(handle)`.\n// - sub KEY changes (id changes) → old cleanup (release) + new acquire.\n// Same reconcile mechanism `fromTransport` rides for open/close — no new\n// kernel hook.\n//\n// THE ACQUIRE-AS-SUCCESS-VALUE DISCIPLINE\n// ----------------------------------------\n// `release` receives ONLY what `acquire` returned (the Handle). Nothing else\n// is in scope. So:\n// - An `acquire` that throws never produces a Handle, so `release` is never\n// called with a half-built resource — an interrupted acquire can't dangle.\n// - Whatever `release` needs to tear down must be captured INTO the Handle\n// by `acquire`. The Handle is the closure over the live resource.\n// `release` is NOT optional in the type. A managed resource without a teardown\n// is just a Cmd (Rule 5); the whole point of this battery is the guaranteed\n// teardown, so the type forces the author to write one.\n//\n// NEUTRAL MECHANISM — no policy. The battery knows `acquire`, `release`,\n// `name`, `key`. It does not know what a checkpoint, a span, or a socket is.\n// Could a reasonable machine want the opposite of any choice here? The only\n// choices are \"release is mandatory\" (yes — that's the contract) and \"release\n// runs on disappear OR key-change\" (yes — that's reconcile semantics, shared\n// with every other Sub). No Cloudflare, no audit domain.\n//\n// Strengthens invariant 4 (external lifecycle owned by the substrate — the\n// acquire/release pair rides reconcileSubs, never hand-driven by a cell) and\n// invariant 6 (no silent looseness — every live resource is a Sub in the\n// desired set, visible in the Sub topology, and an unroutable one throws).\n// ---------------------------------------------------------------------------\n\nimport type { DepKeyedSub, Dispose, Sub, SubId } from \"../index\";\nimport { structuralHash, subId } from \"../index\";\n\n/**\n * The Sub the battery builds. The consumer's `Sub` union includes\n * `ManagedResourceSub<TKey>` directly — the substrate reconciles by `id` and\n * `type` like any other Sub.\n */\nexport interface ManagedResourceSub<TKey> extends Sub<\"managed_resource\"> {\n /** The lifetime key. Stable while the resource should be held; a change\n * retires the old resource (release) and acquires a fresh one. */\n readonly key: TKey;\n}\n\n/**\n * A battery paired with a state-gate. Self-contained: it knows its `name`\n * (for the unique-name assert), how to derive its live Sub from a state\n * (`subFor`), how to handle its own `subscribe`, and whether it `owns` a given\n * Sub id.\n *\n * Generic in the consumer state `S`, the shared lifetime key `TKey`, and the\n * shared `Ctx`. `Handle` is the ONLY thing erased — each battery's handle type\n * differs (a checkpoint saver vs. a tool-call bridge) but never crosses this\n * interface, so a `combineManagedResources` array holds entries of\n * heterogeneous handle types with no cast. The sub type stays\n * `ManagedResourceSub<TKey>` end to end, so the substrate's narrowed Sub flows\n * straight in.\n */\nexport interface GatedManagedResource<S, TKey, Ctx> {\n /** The owning battery's `name`. The combinator asserts these are unique. */\n readonly name: string;\n\n /**\n * Derive this resource's live Sub for a given state: the battery's\n * `.sub(key)` when `when(state)` returns a key, or `null` when it returns\n * `null` (the resource should be torn down in that state).\n */\n readonly subFor: (state: S) => ManagedResourceSub<TKey> | null;\n\n /**\n * This battery's own `.subscribe`. The combinator routes an incoming\n * `managed_resource` Sub to the entry whose `owns(sub)` is true, then calls\n * this.\n */\n readonly subscribe: (\n sub: ManagedResourceSub<TKey>,\n ctx: Ctx,\n dispatch: (msg: never) => void,\n ) => Dispose;\n\n /**\n * Does this entry own the given Sub? `sub.id === subIdFor(sub.key)` encodes\n * the battery `name`, so a match is unambiguous.\n */\n readonly owns: (sub: ManagedResourceSub<TKey>) => boolean;\n}\n\n/**\n * What `combineManagedResources` returns: the single managed-resource\n * `subscribe` cell for the machine's `subscribe` record, and a `subs(state)`\n * builder for `subscriptions(state)`.\n */\nexport interface CombinedManagedResources<S, TKey, Ctx> {\n /**\n * Map every entry through its `when(state)`, drop the `null`s, and build the\n * live Subs. Spread into `subscriptions(state)`. The derived list and the\n * `subscribe` routing share one source — the entries — so they never drift.\n */\n readonly subs: (state: S) => ManagedResourceSub<TKey>[];\n\n /**\n * The SINGLE `subscribe.managed_resource` handler. Routes an incoming Sub to\n * the owning battery by matching `sub.id === battery.subIdFor(sub.key)` (the\n * id encodes the battery `name`). Throws loudly on an unrouted id — a wiring\n * bug, never a silent no-op resource.\n */\n readonly subscribe: (\n sub: ManagedResourceSub<TKey>,\n ctx: Ctx,\n dispatch: (msg: never) => void,\n ) => Dispose;\n}\n\nexport interface DefineManagedResourceOpts<TKey, Handle, Ctx> {\n /**\n * A short, stable name. The Sub id is `managed:${name}:${stringify(key)}`,\n * so two managed resources in one machine never collide and neither\n * collides with a `fromTransport` Sub keyed on the same identity.\n */\n readonly name: string;\n\n /**\n * Build the resource. Runs when the Sub appears in `subscriptions(state)`.\n * The returned Handle is the ONLY thing `release` receives — capture\n * everything teardown needs into it. If `acquire` throws, the substrate's\n * reconcile does NOT register the Sub and `release` is never called (no\n * dangling half-built resource).\n */\n readonly acquire: (key: TKey, ctx: Ctx) => Handle;\n\n /**\n * Tear the resource down. Runs when the Sub leaves the desired set (phase\n * exited) or when its key changes. MANDATORY — the guaranteed teardown is\n * the reason this battery exists.\n *\n * May be sync or async, and an async one is really awaited: the promise is\n * returned to the substrate, which tracks it and drains it inside `stop()`\n * (bounded by `run({ disposeTimeoutMs })`). So `await runtime.stop()` means\n * the release finished — the reading a host evicting an isolate acts on.\n * MID-RUN teardown (a phase exit, a key change) is still fire-and-forget\n * (Rule 2): the reconcile pass is synchronous by construction, so it starts\n * the release and moves on. A rejection is never a Msg — it reaches the\n * runtime's `onError` sink under `phase: \"sub-cleanup\"`.\n */\n readonly release: (handle: Handle) => void | Promise<void>;\n}\n\n/**\n * What the battery returns: a `.sub(key)` builder for `subscriptions`, the\n * `.subscribe` handler for the machine's `subscribe` record, a `.get(key)`\n * accessor so Cmd handlers can reach the live Handle while the resource is\n * held, and a `.subIdFor(key)` for tests.\n */\nexport interface ManagedResourceBattery<TKey, Handle, Ctx> {\n /**\n * Build the Sub for `subscriptions(state)`. The consumer calls this from\n * inside their `subscriptions` cell for the phases where the resource\n * should be live.\n */\n readonly sub: (key: TKey) => ManagedResourceSub<TKey>;\n\n /**\n * Pair this battery with a state-gate, producing a self-contained entry for\n * `combineManagedResources`. `when(state)` returns the resource's KEY when\n * it should be live in that state, or `null` when it should be torn down.\n *\n * Folding \"is it active?\" and \"what key?\" into ONE declaration at the\n * battery's own definition site is the point: a central `subscriptions(state)`\n * list can silently forget to gate a new phase, but a per-resource gate\n * travels with the resource. The combinator derives BOTH the active-sub list\n * and the subscribe routing from these entries, so the two can never disagree.\n *\n * Generic in the consumer's state `S` (each `.gated` call binds its own `S`).\n * The returned entry erases this battery's `Handle` — `combineManagedResources`\n * holds entries of heterogeneous handle types with no cast — while keeping\n * `TKey` and `Ctx` so the substrate's narrowed `ManagedResourceSub<TKey>`\n * flows straight into the entry's `subscribe`.\n */\n readonly gated: <S>(\n when: (state: S) => TKey | null,\n ) => GatedManagedResource<S, TKey, Ctx>;\n\n /**\n * Dep-keyed entry for the machine's `subs` array. `when(state)` returns the\n * resource's KEY when it should be held, or `null` when it should be torn\n * down — the exact gate `.gated` takes, now producing a `DepKeyedSub` the\n * kernel folds directly instead of an entry the `combineManagedResources`\n * router multiplexes.\n *\n * This is the simplification the dep-keyed kernel unlocks: two managed\n * resources used to COLLIDE on the single `subscribe.managed_resource` key\n * (both subs share `type: \"managed_resource\"`), forcing\n * `combineManagedResources` to route by SubId. A dep-keyed Sub has its OWN\n * reconcile slot (its array position), so there is no shared `type` to route\n * — each resource is one `subs` entry, no combinator, no router. `acquire` /\n * `release` still ride the kernel's dispose-on-change / dispose-on-null\n * reconcile, now via the dep-keyed source's `Dispose` instead of the\n * `subscribe` cell's cleanup.\n */\n readonly depKeyed: <S>(\n when: (state: S) => TKey | null,\n ) => DepKeyedSub<S, never, Ctx>;\n\n /**\n * The `subscribe.managed_resource` handler. Drop straight into the\n * machine's `subscribe` record. Runs `acquire` on install, holds the\n * Handle in the battery's handle table, and returns a cleanup that runs\n * `release` (and forgets the Handle) on reconcile-out.\n */\n readonly subscribe: (\n sub: ManagedResourceSub<TKey>,\n ctx: Ctx,\n // The managed resource dispatches no Msgs — acquire/release are pure\n // lifecycle. `dispatch` is accepted to match the substrate's\n // `subscribe[type]` shape and is intentionally unused.\n dispatch: (msg: never) => void,\n ) => Dispose;\n\n /**\n * Accessor for the live Handle, keyed on `key`. Returns `undefined` when\n * the resource is not currently held (no active sub for that key) — the\n * caller decides whether that is an error. A Cmd handler wires its\n * `ctx.getX(key)` through this, so the handler reads the SAME owner the\n * reconciler holds, never a hand-built duplicate.\n */\n readonly get: (key: TKey) => Handle | undefined;\n\n /** Pre-derived subId for `(key)` — handy for tests. */\n readonly subIdFor: (key: TKey) => SubId;\n}\n\n/**\n * Build the battery. One call per managed resource at the machine's host\n * file.\n *\n * @example\n * const checkpointStore = defineManagedResource<RunId, CheckpointSaver, Ctx>({\n * name: \"checkpoint\",\n * acquire: (runId, ctx) =>\n * new CheckpointSaver({ storage: ctx.storage, bucket: ctx.bucket, runId }),\n * release: (saver) => saver.clear(),\n * });\n *\n * // In the machine:\n * subscriptions: (state) =>\n * state.type === \"running\" ? [checkpointStore.sub(state.runId)] : [],\n * subscribe: { managed_resource: checkpointStore.subscribe },\n *\n * // In interpret (read the live handle the reconciler holds):\n * start_graph: async (cmd, ctx) => {\n * const saver = checkpointStore.get(cmd.runId); // same owner, no rebuild\n * ...\n * }\n */\nexport function defineManagedResource<TKey, Handle, Ctx>(\n opts: DefineManagedResourceOpts<TKey, Handle, Ctx>,\n): ManagedResourceBattery<TKey, Handle, Ctx> {\n // Handle table — one live resource per key. Lives in this closure for the\n // lifetime of the battery (the machine's lifetime). Interpret-local; never\n // crosses into reducer state. Same shape as `fromTransport`'s `live` map.\n const held = new Map<string, Handle>();\n\n // Single-sourced on the kernel's `structuralHash`: `\"1\"` (string) and `1`\n // (number) render distinctly, so a key collision can't return/release the\n // wrong held handle; non-JSON keys throw loudly instead of silently returning\n // undefined.\n const keyString = (k: TKey): string => structuralHash(k);\n\n const subIdFor = (key: TKey): SubId =>\n subId(`managed:${opts.name}:${keyString(key)}`);\n\n const subBuilder = (key: TKey): ManagedResourceSub<TKey> => ({\n id: subIdFor(key),\n type: \"managed_resource\",\n key,\n });\n\n const subscribeHandler = (\n sub: ManagedResourceSub<TKey>,\n ctx: Ctx,\n _dispatch: (msg: never) => void,\n ): Dispose => {\n // Acquire. A throw here propagates out of reconcileSubs (the substrate\n // records it and re-throws after the diff pass) and the Sub is NOT\n // registered — so no cleanup runs and `held` stays clean. The\n // acquire-as-success-value discipline: no Handle, no release.\n const handle = opts.acquire(sub.key, ctx);\n const k = keyString(sub.key);\n held.set(k, handle);\n\n // Cleanup runs when the substrate's reconcileSubs leaves the phase OR\n // the key changes. Forget the Handle first so a concurrent `get` after\n // teardown begins sees `undefined`, then release.\n //\n // An async `release` is RETURNED, not swallowed: the substrate tracks the\n // promise and `stop()` awaits it (bounded), so `await runtime.stop()` means\n // the release finished. Attaching a `.catch` here and returning `void` was\n // the defect — a host doing `await stop(); env.evict()` dropped the isolate\n // mid-release, which is the leak this battery exists to prevent, moved to\n // shutdown. A rejection now reaches the runtime's `onError` under\n // `phase: \"sub-cleanup\"` instead of a `console.warn` nobody configured.\n return () => {\n held.delete(k);\n return opts.release(handle);\n };\n };\n\n return {\n sub: subBuilder,\n\n // Pair the battery with a state-gate. `TKey` / `Handle` / `Ctx` are in\n // scope here, so `subFor` builds the fully-typed sub and `subscribe`\n // forwards straight to the battery's own typed `subscribe`. The entry\n // erases ONLY `Handle` for the combinator (`TKey` and `Ctx` stay), so the\n // substrate's narrowed `ManagedResourceSub<TKey>` flows in with no cast.\n gated: <S>(\n when: (state: S) => TKey | null,\n ): GatedManagedResource<S, TKey, Ctx> => ({\n name: opts.name,\n owns: (sub) => sub.id === subIdFor(sub.key),\n subFor: (state) => {\n const key = when(state);\n return key === null ? null : subBuilder(key);\n },\n subscribe: subscribeHandler,\n }),\n\n // Dep-keyed entry. `when` is the gate (the deps = the KEY); the source runs\n // the SAME `acquire` → hold → `release`-on-dispose lifecycle the `subscribe`\n // cell does, now driven by the kernel's dep-keyed reconcile. The Msg type is\n // `never` (a managed resource dispatches nothing); the ignored `_dispatch`\n // matches the source signature.\n depKeyed: <S>(\n when: (state: S) => TKey | null,\n ): DepKeyedSub<S, never, Ctx> => ({\n deps: when,\n source: (state, _dispatch, ctx) => {\n const key = when(state);\n if (key === null) return () => {};\n return subscribeHandler(subBuilder(key), ctx, () => {});\n },\n }),\n\n subscribe: subscribeHandler,\n\n get: (key) => held.get(keyString(key)),\n\n subIdFor,\n };\n}\n\n// ---------------------------------------------------------------------------\n// combineManagedResources — fold N gated batteries into ONE subscribe cell + a\n// derived active-sub list.\n//\n// Two managed resources colliding on the single `subscribe.managed_resource`\n// key forced the machine to hand-roll a router (`if (sub.id === X.subIdFor(...))`).\n// A third copy-pastes another `if`. That router is glue that belongs in the\n// substrate, not the machine. AND the resources' active-phase gating lived in\n// a central `subscriptions(state)` list a new phase could silently forget.\n//\n// This combinator closes both gaps. Each entry carries its OWN gate at its\n// definition site (`battery.gated((s) => active(s) ? key : null)`), and the\n// combinator derives BOTH the active-sub list (`subs`) and the routing\n// (`subscribe`) from the same entries — so they can never disagree, and a new\n// phase that the gate doesn't cover simply produces no sub (no silent leak of\n// the central-list-forgot kind).\n//\n// NEUTRAL MECHANISM — no domain knowledge. The combinator knows `name` (for\n// the unique assert), `owns` (for routing), `subFor` (for the list), and\n// `subscribe` (to forward). It does not know what a checkpoint, a bridge, or a\n// Cloudflare anything is.\n//\n// Strengthens invariant 4 (external lifecycle owned by the substrate — both\n// the list and the routing are derived, never hand-driven) and invariant 6 (no\n// silent looseness — ONE managed-resource cell, and an unrouted id throws\n// loudly instead of silently installing a no-op).\n// ---------------------------------------------------------------------------\nexport function combineManagedResources<S, TKey, Ctx>(\n entries: readonly GatedManagedResource<S, TKey, Ctx>[],\n): CombinedManagedResources<S, TKey, Ctx> {\n // Assert unique names at construction — two batteries with the same `name`\n // collide on Sub id (`managed:${name}:${key}`), so the router could not tell\n // them apart. Throw immediately (mirrors foldkit's `combineSubscriptions` on\n // a duplicate key) rather than mis-route at runtime.\n const seen = new Set<string>();\n for (const entry of entries) {\n if (seen.has(entry.name)) {\n throw new Error(\n `[combineManagedResources] duplicate managed-resource name \"${entry.name}\" — names must be unique (the Sub id encodes the name; two batteries sharing it collide)`,\n );\n }\n seen.add(entry.name);\n }\n\n return {\n subs: (state) => {\n const live: ManagedResourceSub<TKey>[] = [];\n for (const entry of entries) {\n const sub = entry.subFor(state);\n if (sub !== null) live.push(sub);\n }\n return live;\n },\n\n subscribe: (sub, ctx, dispatch) => {\n const owner = entries.find((entry) => entry.owns(sub));\n if (owner === undefined) {\n // No battery owns this managed-resource SubId — a wiring bug. Surface\n // loudly rather than silently install a no-op resource (invariant 6).\n throw new Error(\n `[combineManagedResources] no battery for sub id ${sub.id}`,\n );\n }\n return owner.subscribe(sub, ctx, dispatch);\n },\n };\n}\n","// ---------------------------------------------------------------------------\n// fromTransport — the SEAM battery.\n//\n// At every seam between two TEA-shaped processes (a DO ↔ its hands container,\n// a SW background ↔ a content script, a parent ↔ a child runtime), there is\n// the SAME hand-rolled boilerplate:\n//\n// 1. The inbound message stream — a Sub keyed on a phase, opened when the\n// machine enters the phase, closed when the machine leaves it.\n// 2. A transport-close detector — when the underlying channel drops, a\n// single Msg is dispatched (`*_lost`). NOT a Sub the consumer\n// re-publishes; the lifetime IS the inbound Sub's lifetime.\n// 3. The outbound side — a Cmd whose handler hands a value to the live\n// transport, looked up by an interpret-local handle table keyed on the\n// phase's run-identity.\n//\n// Every hand-rolled seam (`hands-inbound.ts`, `forwardOutbound`, the\n// `client_disconnected` Msg arm, the WS handle map) is the same three\n// pieces with different vocabularies. The battery bakes those three in.\n//\n// What the author writes:\n//\n// 1. The vocabulary (Inbound / Outbound types).\n// 2. ONE call: `fromTransport({ name, openTransport, parseInbound, ... })`.\n//\n// What the author NEVER writes again:\n//\n// - The `Sub` shape, the `subscribe` cell, the `cleanup` Promise dance.\n// - The transport-close → `*_lost` Msg wiring.\n// - The \"find the live socket from inside an interpret handler\" lookup.\n// - The keepalive / heartbeat / sequence-number ceremony (none of those\n// belong in the machine — that's Rule 9: lifetime-bound resources are\n// Subs, not state-machine cells).\n//\n// Host-pluggable: the battery takes a `TransportFactory`, not a concrete\n// WebSocket. A Durable Object passes a workerd `WebSocket` adapter; a worker\n// thread can pass a `MessagePort` adapter; an in-process test can pass a\n// stub. Same battery; three different hosts.\n//\n// Distinct from `fromWebSocket` / `fromReconnectingWebSocket` in this same\n// subpath, which own a CONCRETE `WebSocket` and are inbound-only. This battery\n// is transport-agnostic and duplex: it also owns the outbound handle table, so\n// a Cmd handler can reach the live channel without a hand-written registry.\n//\n// Strengthens invariant 4 (external events / observation enter as Subs;\n// lifecycle owned by the substrate — the inbound stream, the close\n// detection, AND the outbound handle are all owned by the battery's Sub\n// lifecycle, not hand-driven by the consumer's cells).\n// ---------------------------------------------------------------------------\n\nimport type { DepKeyedSub, Sub, SubId } from \"../index\";\nimport { structuralHash, subId } from \"../index\";\n\n/**\n * Duplex transport. The host (DO, worker thread, in-process bus) constructs\n * one from whatever the platform offers. `fromTransport` does not know what\n * a WebSocket is — it knows `send`, `onMessage`, `onClose`.\n */\nexport interface Transport {\n /** Send a string frame. Best-effort; failures are logged at the boundary. */\n send(data: string): void;\n /**\n * Subscribe to inbound frames. Returns a cleanup that removes the listener.\n * The battery wires this in its Sub's subscribe handler; cleanup runs when\n * the phase exits (the substrate's reconcileSubs).\n */\n onMessage(listener: (data: string) => void): () => void;\n /**\n * Subscribe to a one-shot close. Returns a cleanup that removes the\n * listener. The battery dispatches `lostMsg` on close.\n */\n onClose(listener: () => void): () => void;\n /** Close the underlying channel cleanly. Called by the Sub cleanup. */\n close(): void;\n}\n\n/**\n * Factory the consumer wires to a platform-specific transport. The battery\n * passes the runtime-derived key (`runId`, or whatever identity is on the\n * phase) so per-run transports can be addressed.\n */\nexport type TransportFactory<K> = (key: K) => Transport;\n\n/**\n * The Sub the battery builds. The consumer's `Sub` union includes\n * `TransportSub<TKey>` directly — no wrapping needed, the substrate\n * reconciles by `id` and `type` like any other Sub.\n */\nexport interface TransportSub<TKey> extends Sub<\"transport\"> {\n /** The phase / run identity. Stable for the lifetime of the seam. */\n readonly key: TKey;\n}\n\nexport interface FromTransportOpts<TKey, Inbound, Outbound, M, Ctx> {\n /**\n * A short, stable name for this seam. The Sub id is derived from\n * `${name}:${stringify(key)}` so two seams in the same machine (e.g.\n * `hands` + `worker`) never collide.\n */\n readonly name: string;\n\n /**\n * Build the transport. The battery owns the lifetime; the factory is the\n * platform-specific constructor (`new WebSocket(...)` adapter on a DO,\n * `MessagePort` adapter in a worker, in-memory stub in tests).\n */\n readonly openTransport: (key: TKey, ctx: Ctx) => Transport;\n\n /**\n * Parse one inbound frame. The battery hands `unknown` through (boundary\n * parse is the consumer's responsibility per invariant 8). Returning\n * `null` drops the frame silently — useful for frames the seam doesn't\n * recognize at this layer (e.g. transport-level keepalive).\n */\n readonly parseInbound: (raw: string, key: TKey) => Inbound | null;\n\n /**\n * Map a parsed inbound to a domain Msg. Returning `null` drops the\n * dispatch (e.g. a transport-level pong that doesn't change domain state).\n */\n readonly onInbound: (inbound: Inbound, key: TKey) => M | null;\n\n /**\n * The Msg dispatched when the transport closes (peer gone, network\n * dropped, normal close). ONE Msg arm, not a Sub, not a heartbeat-derived\n * deadline — the transport's own close signal is the truth.\n */\n readonly lostMsg: (key: TKey) => M;\n\n /**\n * Serialize an outbound. Called by the Cmd handler the battery wires.\n * Pure data → string at the seam boundary.\n */\n readonly serializeOutbound: (outbound: Outbound) => string;\n}\n\n/**\n * What the battery returns: a Sub builder (for `subscriptions`), the Sub's\n * `subscribe` handler (for `subscribe.transport`), and a `send(key, outbound)`\n * helper the consumer's Cmd handler calls. Plus a `subIdFor` factory the\n * consumer can use when keying related Subs (e.g. a deadline) to the same seam\n * identity.\n */\nexport interface TransportBattery<TKey, Outbound, M, Ctx> {\n /**\n * Build the Sub for `subscriptions(state)`. The consumer calls this from\n * inside their `subscriptions` cell when the seam should be open.\n */\n readonly sub: (key: TKey) => TransportSub<TKey>;\n\n /**\n * The `subscribe.transport` handler. Drop straight into the machine's\n * `subscribe` record — the battery wires inbound + close in one shot.\n */\n readonly subscribe: (\n sub: TransportSub<TKey>,\n ctx: Ctx,\n dispatch: (msg: M) => void,\n ) => () => void;\n\n /**\n * Outbound send. The Cmd handler calls this; the battery's handle table\n * (built when subscribe ran) routes to the live transport. If the seam\n * is closed (no active sub), the send is dropped honestly (logged, not\n * thrown — the reducer already moved past caring, same shape as Rule 2\n * fire-and-forget Cmds).\n */\n readonly send: (key: TKey, outbound: Outbound) => void;\n\n /** Pre-derived subId for `(key)` — handy for deadline Subs keyed to the same seam. */\n readonly subIdFor: (key: TKey) => SubId;\n\n /**\n * Pair this seam with a state-gate, producing a dep-keyed Sub for the\n * machine's `subs` array. `when(state)` returns the seam's KEY when it\n * should be open, or `null` when it should be torn down. The kernel derives\n * the id from `structuralHash(key)` and reconciles the seam's lifetime — so\n * the author neither lists this Sub in a central `subscriptions(state)` nor\n * writes its id. Replaces `subscriptions: (s) => active(s) ? [hands.sub(...)] : []`\n * + `subscribe: { transport: hands.subscribe }` with one `subs` entry.\n *\n * The `source` closes over the battery's own `subscribe` (open inbound +\n * close, wire the handle table) using the same `ctx` the kernel passes every\n * Sub source.\n */\n readonly depKeyed: <S>(\n when: (state: S) => TKey | null,\n ) => DepKeyedSub<S, M, Ctx>;\n}\n\n/**\n * Build the battery. One call per seam at the machine's host file.\n *\n * @example\n * const handsSeam = fromTransport<RunId, HandsInbound, HandsOutbound, Msg, Ctx>({\n * name: \"hands\",\n * openTransport: (runId, ctx) => ctx.openHandsWs(runId),\n * parseInbound: (raw) => parseHandsInbound(JSON.parse(raw)),\n * onInbound: (inbound, runId) => ({ type: \"hands_said\", runId, inbound }),\n * lostMsg: (runId) => ({ type: \"hands_lost\", runId }),\n * serializeOutbound: (out) => JSON.stringify(out),\n * });\n *\n * // In the machine:\n * subscriptions: (state) => state.type === \"auditing\" ? [handsSeam.sub(state.runId)] : [],\n * subscribe: { transport: handsSeam.subscribe },\n *\n * // In interpret:\n * send_hands: async (cmd) => { handsSeam.send(cmd.runId, cmd.outbound); },\n */\nexport function fromTransport<TKey, Inbound, Outbound, M, Ctx>(\n opts: FromTransportOpts<TKey, Inbound, Outbound, M, Ctx>,\n): TransportBattery<TKey, Outbound, M, Ctx> {\n // Handle table — one live transport per key. Lives in this closure for the\n // lifetime of the battery (i.e. the machine's lifetime). Interpret-local;\n // never crosses into reducer state. The same shape as a brain's\n // `pending: Map<callId, deferred>`: imperative handles in a Map keyed on\n // run-identity, not on durable state.\n const live = new Map<string, Transport>();\n\n // Single-sourced on the kernel's `structuralHash`: `\"1\"` (string) and `1`\n // (number) render distinctly, so a key collision can't mis-route an outbound\n // send or overwrite the wrong live transport; non-JSON keys throw loudly.\n const keyString = (k: TKey): string => structuralHash(k);\n\n const subIdFor = (key: TKey): SubId =>\n subId(`${opts.name}:${keyString(key)}`);\n\n const sub = (key: TKey): TransportSub<TKey> => ({\n id: subIdFor(key),\n type: \"transport\",\n key,\n });\n\n const subscribe: TransportBattery<TKey, Outbound, M, Ctx>[\"subscribe\"] = (\n s,\n ctx,\n dispatch,\n ) => {\n // Open the transport. The battery owns the lifetime.\n const t = opts.openTransport(s.key, ctx);\n const k = keyString(s.key);\n\n // ACQUIRE-AS-SUCCESS-VALUE: wire EVERYTHING first, publish to the handle\n // table last. An adapter over an already-CLOSING socket throws while\n // wiring; when `live.set` ran first, that left the transport in the table\n // with NO sub registered — so no cleanup ever ran, `send` wrote into a\n // half-wired seam with no inbound listener, and every subsequent reconcile\n // opened another one: an unbounded leak from one recoverable throw. On the\n // way out we undo exactly what we did (drop the listeners, close the\n // transport) and rethrow, so the substrate records the failure and the\n // battery holds nothing. Same discipline `defineManagedResource` states in\n // its own header — no Handle, no release.\n let offMessage: (() => void) | undefined;\n let offClose: (() => void) | undefined;\n try {\n // Wire inbound. Parse boundary lives here per invariant 8.\n offMessage = t.onMessage((raw) => {\n const parsed = opts.parseInbound(raw, s.key);\n if (parsed === null) return;\n const msg = opts.onInbound(parsed, s.key);\n if (msg === null) return;\n dispatch(msg);\n });\n\n // Wire close. ONE Msg arm — `*_lost`. No heartbeat, no sequence\n // numbers, no \"is the peer still alive\" derivation: the transport's\n // own close IS the liveness signal.\n offClose = t.onClose(() => {\n dispatch(opts.lostMsg(s.key));\n });\n } catch (err) {\n offMessage?.();\n offClose?.();\n try {\n t.close();\n } catch (closeErr) {\n // Boundary log: the wiring failure is the fact worth surfacing, and it\n // is about to be rethrown. A close that also fails must not replace it.\n console.warn(\n `[fromTransport:${opts.name}] close during failed wiring failed`,\n closeErr,\n );\n }\n throw err;\n }\n\n live.set(k, t);\n\n // Cleanup runs when the substrate's reconcileSubs leaves the phase.\n return () => {\n offMessage?.();\n offClose?.();\n live.delete(k);\n try {\n t.close();\n } catch (err) {\n // Boundary log: cleanup-time close failures are fire-and-forget\n // (Rule 2). The reducer is already past caring.\n console.warn(`[fromTransport:${opts.name}] close failed`, err);\n }\n };\n };\n\n return {\n sub,\n subscribe,\n\n send: (key, outbound) => {\n const t = live.get(keyString(key));\n if (t === undefined) {\n // Honest drop. The reducer's Cmd burst can race a `*_lost`\n // transition; the Cmd is fire-and-forget at that point.\n console.warn(\n `[fromTransport:${opts.name}] send dropped — no live transport for key`,\n keyString(key),\n );\n return;\n }\n t.send(opts.serializeOutbound(outbound));\n },\n\n subIdFor,\n\n // Dep-keyed entry: `when` is the gate (the deps slice → the seam key);\n // `source` re-runs `when(state)` to recover the typed key and opens the seam\n // via the battery's own `subscribe`. The kernel only calls `source` when\n // `deps(state)` was non-null, so `when` is non-null here; the key type never\n // escapes the entry (cast-free existential).\n depKeyed: <S>(when: (state: S) => TKey | null): DepKeyedSub<S, M, Ctx> => ({\n deps: when,\n source: (state, dispatch, ctx) => {\n const key = when(state);\n if (key === null) return () => {};\n return subscribe(sub(key), ctx, dispatch);\n },\n }),\n };\n}\n"]}
|
|
@@ -1,5 +1,5 @@
|
|
|
1
|
-
import { createSnapshot } from './chunk-
|
|
2
|
-
import { deadlineSub } from './chunk-
|
|
1
|
+
import { createSnapshot } from './chunk-XMHZ44QC.js';
|
|
2
|
+
import { deadlineSub } from './chunk-ASX7PL2K.js';
|
|
3
3
|
import { queueAdapter } from './chunk-MU6MCIAC.js';
|
|
4
4
|
|
|
5
5
|
// src/monitored-run/index.ts
|
|
@@ -175,5 +175,5 @@ function liftRun(state, [slice, cmds]) {
|
|
|
175
175
|
}
|
|
176
176
|
|
|
177
177
|
export { createMonitoredRun, liftRun };
|
|
178
|
-
//# sourceMappingURL=chunk-
|
|
179
|
-
//# sourceMappingURL=chunk-
|
|
178
|
+
//# sourceMappingURL=chunk-66S2KEQQ.js.map
|
|
179
|
+
//# sourceMappingURL=chunk-66S2KEQQ.js.map
|