@demlik/tea 0.21.0 → 0.22.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.
Files changed (126) hide show
  1. package/dist/agent/index.d.ts +32 -19
  2. package/dist/agent/index.js +12 -10
  3. package/dist/{chunk-VM7PMFV5.js → chunk-2A5XTKQQ.js} +142 -19
  4. package/dist/chunk-2A5XTKQQ.js.map +1 -0
  5. package/dist/{chunk-EB5OMOXX.js → chunk-7K7SEYCH.js} +16 -15
  6. package/dist/chunk-7K7SEYCH.js.map +1 -0
  7. package/dist/{chunk-GAVLND5C.js → chunk-A3QPMX5M.js} +17 -10
  8. package/dist/chunk-A3QPMX5M.js.map +1 -0
  9. package/dist/{chunk-VHKDUBXC.js → chunk-BVFS3ICC.js} +3 -3
  10. package/dist/{chunk-VHKDUBXC.js.map → chunk-BVFS3ICC.js.map} +1 -1
  11. package/dist/{chunk-4I2LCQKB.js → chunk-CPE5ZQXQ.js} +3 -3
  12. package/dist/{chunk-4I2LCQKB.js.map → chunk-CPE5ZQXQ.js.map} +1 -1
  13. package/dist/chunk-DEAPNMKO.js +11 -0
  14. package/dist/chunk-DEAPNMKO.js.map +1 -0
  15. package/dist/{chunk-BFSZ4L6Y.js → chunk-ETRNSJCR.js} +4 -4
  16. package/dist/{chunk-BFSZ4L6Y.js.map → chunk-ETRNSJCR.js.map} +1 -1
  17. package/dist/chunk-FXWDDNGD.js +98 -0
  18. package/dist/chunk-FXWDDNGD.js.map +1 -0
  19. package/dist/chunk-GFGOQIEC.js +10 -0
  20. package/dist/chunk-GFGOQIEC.js.map +1 -0
  21. package/dist/{chunk-YDF6P74H.js → chunk-IAHEYPYJ.js} +3 -3
  22. package/dist/{chunk-YDF6P74H.js.map → chunk-IAHEYPYJ.js.map} +1 -1
  23. package/dist/{chunk-YCJPLPQQ.js → chunk-J5JNLXO7.js} +3 -3
  24. package/dist/{chunk-YCJPLPQQ.js.map → chunk-J5JNLXO7.js.map} +1 -1
  25. package/dist/{chunk-MBQR3LFW.js → chunk-NFOGYDRS.js} +5 -5
  26. package/dist/{chunk-MBQR3LFW.js.map → chunk-NFOGYDRS.js.map} +1 -1
  27. package/dist/{chunk-Z7IL735U.js → chunk-R2YNDRGL.js} +3 -3
  28. package/dist/{chunk-Z7IL735U.js.map → chunk-R2YNDRGL.js.map} +1 -1
  29. package/dist/{chunk-ZV4WGW74.js → chunk-R5NXOPL2.js} +3 -3
  30. package/dist/{chunk-ZV4WGW74.js.map → chunk-R5NXOPL2.js.map} +1 -1
  31. package/dist/chunk-RMBZ7YH7.js +52 -0
  32. package/dist/chunk-RMBZ7YH7.js.map +1 -0
  33. package/dist/{chunk-HU3Y2AZY.js → chunk-SHXUSTHM.js} +62 -15
  34. package/dist/chunk-SHXUSTHM.js.map +1 -0
  35. package/dist/{chunk-QIE4Y6BK.js → chunk-UGTC33VH.js} +4 -4
  36. package/dist/{chunk-QIE4Y6BK.js.map → chunk-UGTC33VH.js.map} +1 -1
  37. package/dist/{chunk-6NIQIUGR.js → chunk-VH5MYJ2K.js} +4 -4
  38. package/dist/{chunk-6NIQIUGR.js.map → chunk-VH5MYJ2K.js.map} +1 -1
  39. package/dist/{chunk-W6XWI6QE.js → chunk-WZEDIUWR.js} +8 -7
  40. package/dist/chunk-WZEDIUWR.js.map +1 -0
  41. package/dist/{chunk-TMYUDZLE.js → chunk-ZRRET3RP.js} +11 -11
  42. package/dist/chunk-ZRRET3RP.js.map +1 -0
  43. package/dist/{core-BKJW1Kmq.d.ts → core-DNDAEhO2.d.ts} +7 -10
  44. package/dist/devtools/index.d.ts +4 -4
  45. package/dist/do/index.d.ts +98 -35
  46. package/dist/do/index.js +40 -21
  47. package/dist/do/index.js.map +1 -1
  48. package/dist/{drive-loop-Dc6Z8Vbs.d.ts → drive-loop-DlOEv49c.d.ts} +2 -2
  49. package/dist/{durable-effects-CgrLNccA.d.ts → durable-effects-wLWEdy9H.d.ts} +1 -1
  50. package/dist/effect/index.d.ts +52 -26
  51. package/dist/effect/index.js +7 -31
  52. package/dist/effect/index.js.map +1 -1
  53. package/dist/extension/index.d.ts +3 -3
  54. package/dist/extension/index.js +2 -2
  55. package/dist/flow/index.d.ts +13 -13
  56. package/dist/flow/index.js +13 -11
  57. package/dist/flow/index.js.map +1 -1
  58. package/dist/idempotency/index.d.ts +3 -3
  59. package/dist/idempotency/index.js +3 -3
  60. package/dist/{index-1hit3dJ9.d.ts → index-9P1PaXIV.d.ts} +2 -2
  61. package/dist/{index-Cuy1_xTh.d.ts → index-BBLI2WWN.d.ts} +1 -1
  62. package/dist/{index-Z6Uw7C9t.d.ts → index-C1WhBZ6a.d.ts} +4 -5
  63. package/dist/{index-B-VC4qjx.d.ts → index-C92SgNWz.d.ts} +2 -2
  64. package/dist/{index-CYrUWi1c.d.ts → index-CAsUE_d1.d.ts} +4 -4
  65. package/dist/{index-BjwRcjBi.d.ts → index-CO34esWT.d.ts} +2 -2
  66. package/dist/{index-DB8Yn0-K.d.ts → index-COeX9Bmz.d.ts} +11 -9
  67. package/dist/{index-Diyo8WBO.d.ts → index-Cg0QXnzf.d.ts} +2 -2
  68. package/dist/index.d.ts +5 -5
  69. package/dist/index.js +2 -2
  70. package/dist/jev/index.d.ts +8 -8
  71. package/dist/jev/index.js +5 -5
  72. package/dist/laws-DTtCDDlU.d.ts +135 -0
  73. package/dist/loop-DReKGV1X.d.ts +70 -0
  74. package/dist/{machine-BuPTS1QH.d.ts → machine-C0WhOAyB.d.ts} +5 -5
  75. package/dist/machine-viz/index.d.ts +1 -1
  76. package/dist/machine-viz/index.js +2 -2
  77. package/dist/mem/index.d.ts +4 -3
  78. package/dist/mem/index.js +2 -2
  79. package/dist/mem/index.js.map +1 -1
  80. package/dist/node/index.d.ts +57 -9
  81. package/dist/node/index.js +175 -68
  82. package/dist/node/index.js.map +1 -1
  83. package/dist/otel/index.d.ts +28 -12
  84. package/dist/otel/index.js +25 -21
  85. package/dist/otel/index.js.map +1 -1
  86. package/dist/paginate/index.d.ts +4 -4
  87. package/dist/paginate/index.js +5 -5
  88. package/dist/parity/index.d.ts +11 -15
  89. package/dist/parity/index.js +4 -9
  90. package/dist/parity/index.js.map +1 -1
  91. package/dist/pbt/index.d.ts +279 -67
  92. package/dist/pbt/index.js +299 -58
  93. package/dist/pbt/index.js.map +1 -1
  94. package/dist/persistence/index.d.ts +10 -12
  95. package/dist/persistence/index.js +4 -4
  96. package/dist/promise/index.d.ts +95 -13
  97. package/dist/promise/index.js +6 -4
  98. package/dist/react/index.d.ts +34 -22
  99. package/dist/react/index.js +271 -42
  100. package/dist/react/index.js.map +1 -1
  101. package/dist/resilience/index.d.ts +6 -6
  102. package/dist/resilience/index.js +5 -5
  103. package/dist/{run-D0sxbyno.d.ts → run-Cnw4qXj2.d.ts} +86 -17
  104. package/dist/{runtime-types-BPKp2bGo.d.ts → runtime-types-CP-ykwgf.d.ts} +120 -31
  105. package/dist/testing/effect/index.d.ts +162 -9
  106. package/dist/testing/effect/index.js +110 -10
  107. package/dist/testing/effect/index.js.map +1 -1
  108. package/dist/testing/index.d.ts +6 -8
  109. package/dist/testing/index.js +4 -24
  110. package/dist/testing/index.js.map +1 -1
  111. package/dist/testing/promise/index.d.ts +4 -4
  112. package/dist/testing/promise/index.js +5 -5
  113. package/dist/timing/index.d.ts +4 -4
  114. package/dist/timing/index.js +4 -4
  115. package/dist/{types-BiXj7k_k.d.ts → types-S53WDy_h.d.ts} +1 -1
  116. package/dist/work-queue/index.d.ts +4 -4
  117. package/dist/work-queue/index.js +2 -2
  118. package/package.json +18 -18
  119. package/dist/chunk-7Z2IDZ7R.js +0 -34
  120. package/dist/chunk-7Z2IDZ7R.js.map +0 -1
  121. package/dist/chunk-EB5OMOXX.js.map +0 -1
  122. package/dist/chunk-GAVLND5C.js.map +0 -1
  123. package/dist/chunk-HU3Y2AZY.js.map +0 -1
  124. package/dist/chunk-TMYUDZLE.js.map +0 -1
  125. package/dist/chunk-VM7PMFV5.js.map +0 -1
  126. package/dist/chunk-W6XWI6QE.js.map +0 -1
@@ -1 +1 @@
1
- {"version":3,"sources":["../src/compose/index.ts","../src/observability.ts","../src/internal/prediction/index.ts","../src/subs/types.ts","../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-timeout.ts","../src/subs/from-web-socket.ts","../src/subs/managed-resource.ts","../src/subs/transport.ts"],"names":[],"mappings":"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","file":"chunk-VHKDUBXC.js","sourcesContent":["/**\n * @packageDocumentation\n * The composition seam — the layer between a battery and a subpath.\n *\n * A battery is pure state plus verbs over that state. A subpath holds several\n * batteries side by side in one Model. Everything between those two facts was,\n * until now, hand-written at every consumer: the one-key record rebuild that\n * threads a verb's `[slice, cmds]` result back into the host state, and the\n * statement order that decides which slice wins a derived read.\n *\n * Two helpers cover it, and neither is an optics kit. {@link liftSlice} is the\n * record rebuild with the key checked by the type system; {@link readInOrder}\n * turns a read's precedence from statement order into a value you can name,\n * export and assert on. ADR 0001 (no off-the-shelf resilience) and ADR 0014\n * (Effect's types, never its runtime values) between them settle why this is\n * forty lines here rather than a dependency.\n *\n * `docs/how-to/compose-two-battery-slices.md` wires both end to end over a\n * two-battery subpath, and its `ts` blocks are compiled and run as\n * `src/docs/how-to/compose-slices.test.ts`.\n *\n * Both are pure: no clock, no RNG, no storage, nothing captured. A `liftSlice`\n * result is a fresh record and a Cmd array, so ADR 0014's journal/hash promise\n * over a reducer's return value is untouched.\n */\n\n/**\n * Lift a battery verb's result into the host state that carries its slice.\n *\n * `liftSlice(\"resilience\", state, knob.attempt(state.resilience, …))` is the\n * `{ ...state, resilience: slice }` every consumer writes by hand, with the key\n * checked: `key` must be a key of `S`, and the slice the verb returned must be\n * assignable to the type that key carries — so naming the wrong field, or\n * threading one battery's slice into another's key, is a compile error rather\n * than a state shape nothing reads correctly.\n *\n * PURE — a fresh record and the verb's own Cmds, unchanged.\n */\nexport function liftSlice<S, K extends keyof S, C>(\n key: K,\n state: S,\n [slice, cmds]: readonly [S[K], readonly C[]],\n): readonly [S, readonly C[]] {\n // A computed-key spread widens to `S & { [k in K]: S[K] }` in TS's own\n // arithmetic, which is `S` by construction; the assertion states that rather\n // than re-deriving it. The runtime shape is exactly `S`'s.\n return [{ ...state, [key]: slice } as S, cmds];\n}\n\n/**\n * One step of a composed read: the slice it consults, under the name that slice\n * goes by. `read` returns `undefined` for \"this slice has nothing to say\",\n * which is what hands the read on to the next step.\n */\nexport interface ReadStep<In, Out> {\n /** The slice this step consults — `\"cache\"`, `\"failed\"`, `\"inFlight\"`. */\n readonly name: string;\n /** What this slice says about `input`, or `undefined` to defer. */\n readonly read: (input: In) => Out | undefined;\n}\n\n/**\n * Run a composed read through `order` and return the first answer any step\n * gives, or `absent` when every one defers.\n *\n * The point is that `order` is a VALUE. A derived read over three battery\n * slices otherwise states its precedence as the order of three `if` statements,\n * where it is invisible to the type system, unnameable in a test and one\n * innocent-looking reshuffle away from being wrong — which is exactly the\n * defect that escaped `classify-batch`'s per-battery test suites. Written as\n * data, the precedence can be exported, printed and asserted on directly.\n *\n * PURE — it evaluates `read` in order and stores nothing.\n */\nexport function readInOrder<In, Out>(\n input: In,\n order: readonly ReadStep<In, Out>[],\n absent: Out,\n): Out {\n for (const step of order) {\n const answer = step.read(input);\n if (answer !== undefined) return answer;\n }\n return absent;\n}\n","import type { BootingRuntime } from \"./runtime-types\";\n\n// === historyTracker: composable observability helper ===\n//\n// A bounded ring buffer of recent `(msg, state)` transitions, built on the\n// Runtime's public `observe` API. The substrate itself stays minimal — it has\n// no notion of history, no opt to enable one, no method on the Runtime\n// interface to read one. Anything that wants \"recent transitions\" composes via\n// this helper.\n//\n// Why not put this on the Runtime interface as `runtime.history()`? Because\n// history is derivable. Putting it on the Runtime privileges one use case\n// (debug inspectors) at the substrate level and grows the interface for\n// everyone; the composition shape stays out of the dispatch loop entirely, so\n// the substrate's hot path pays no cost for a feature it doesn't need.\n\n/**\n * A bounded log of a runtime's recent `(msg, state)` transitions, returned by\n * `historyTracker`. Use it to feed a debug inspector.\n */\nexport interface HistoryTracker<S, M extends { type: string }> {\n /**\n * Snapshot of recorded transitions, oldest first. Each entry is\n * `{ msg, state }`: `msg` is `null` for the boot transition (recorded via\n * `onBoot`) and the applied Msg for every other (recorded via `observe`).\n *\n * Returns a shallow copy: the array is fresh on every call (callers may\n * iterate, slice, or replay without affecting the tracker's buffer).\n * Entry values are references to the originals (TEA states/msgs are\n * conventionally immutable; do not mutate them if your S/M is not).\n */\n snapshot(): readonly { readonly msg: M | null; readonly state: S }[];\n /**\n * Detach the underlying observer. After `stop()`:\n * - No new transitions are recorded.\n * - `snapshot()` continues to work and returns the buffer's contents\n * at the moment of stop (useful for post-hoc inspection in tests).\n * - The tracker holds no references that would prevent GC of the\n * `runtime` argument other than the entries already buffered.\n *\n * Idempotent — subsequent calls are no-ops.\n */\n stop(): void;\n}\n\n/**\n * Create a bounded history tracker over a Runtime. The tracker subscribes to\n * the runtime via `observe(...)` and retains the last `size` `(msg, state)`\n * transitions in a FIFO ring buffer.\n *\n * @param runtime Any `BootingRuntime<S, M>` (a full `Runtime` satisfies it).\n * The tracker uses only `observe`, which is total before boot —\n * attach it to the synchronous `run()` handle to record the\n * boot transition.\n * @param size Buffer cap. `size <= 0` produces an inert no-op tracker:\n * no observer is attached to the runtime, `snapshot()` is\n * always `[]`, and `stop()` is a no-op.\n */\nexport function historyTracker<S, M extends { type: string }>(\n runtime: BootingRuntime<S, M>,\n size: number,\n): HistoryTracker<S, M> {\n const buffer: { msg: M | null; state: S }[] = [];\n let stopped = false;\n\n const push = (entry: { msg: M | null; state: S }): void => {\n buffer.push(entry);\n if (buffer.length > size) buffer.shift();\n };\n\n // The boot transition (the `{ msg: null, state }` head entry) arrives via\n // `onBoot`; applied transitions arrive via `observe` with a total `msg`. The\n // public snapshot shape (`msg: M | null`) is unchanged — boot is still\n // `msg: null`.\n const unobserve =\n size <= 0\n ? (): void => {}\n : runtime.observe((msg, state) => {\n push({ msg, state });\n });\n const unboot =\n size <= 0\n ? (): void => {}\n : runtime.onBoot((state) => {\n push({ msg: null, state });\n });\n\n return {\n snapshot() {\n // Shallow copy — callers may freely iterate or mutate the returned\n // array. Entry values are references; see HistoryTracker docs.\n return buffer.slice();\n },\n stop() {\n if (stopped) return;\n stopped = true;\n unobserve();\n unboot();\n },\n };\n}\n","/**\n * internal/prediction — the client-prediction ack primitive (epic #186, facet 2).\n * Internal since #49 — not published on any subpath of its own; the\n * client-safe barrel `src/pure/` re-exports the public surface, and the root\n * `@demlik/tea` entry publishes it.\n *\n * The contract the Gambetta/Valve authoritative-server netcode loop needs:\n * **command-seq-in → last-applied-seq-out.** A client tags each predicted\n * command/`Msg` with a monotonic `seq`; the authoritative side replies with the\n * `lastAppliedSeq` it has processed. `partitionByAck` then splits the client's\n * pending buffer into the inputs the server has ACKED (`seq <= lastAppliedSeq`)\n * and those still PENDING (`seq > lastAppliedSeq`) — the un-acked tail the\n * reconciliation pass (#214) replays over the authoritative snapshot via\n * `foldMsgs` (#211, the client-safe barrel `src/pure/`).\n *\n * Deliberately **Model-shape-agnostic** (ADR 0006): the ack is a standalone\n * value, NOT a field this module requires on the consumer's `State`. It\n * generalizes the `lastAckedSeq`-on-the-Model + `seq`-threaded-through-the-Msg\n * pattern vortex hand-rolled (epic #186). It is a LEAF module — pure and\n * runtime-free, importing nothing from the tea core or host, so it can never\n * drag `run`/the host into a client bundle.\n *\n * This is the read/ack side of the primitive; `tagSeq`/`nextSeq` are the write\n * side, letting a client mint monotonic seqs from its pending buffer alone —\n * again with no counter field on the Model.\n *\n * `reconcile` (the read side's payoff, #214) composes this ack partition with\n * the pure fold seam `foldMsgs` to perform the whole Gambetta/Valve\n * reconciliation step in one call. The `foldMsgs` import is the ONLY dependency\n * this leaf takes, and it reaches into the pure-core leaf (`../pure/core`) — NOT\n * the runtime root — so the module stays runtime-free and the client-safe\n * import-graph guard (ADR 0006, #213) still holds.\n */\n\nimport type { Cmd, Machine, Sub } from \"../../pure/core\";\nimport { foldMsgs } from \"../../pure/core\";\n\n/**\n * A monotonic, non-negative sequence number tagging one client-predicted\n * command. Monotonicity is the client's contract (mint successive seqs with\n * `nextSeq`); this module only ever *compares* seqs.\n */\nexport type Seq = number;\n\n/** A command (or `Msg`) tagged with the `seq` the client assigned it. */\nexport interface SeqTagged<T> {\n readonly seq: Seq;\n readonly value: T;\n}\n\n/** Tag a command/`Msg` with its sequence number. */\nexport const tagSeq = <T>(seq: Seq, value: T): SeqTagged<T> => ({ seq, value });\n\n/**\n * The authoritative side's acknowledgement: the highest `seq` it has applied.\n * Everything with `seq <= lastAppliedSeq` is settled; everything above is still\n * in flight. This *is* the whole \"ack from the authoritative side\" — a value\n * the consumer carries however it likes (a wire field, a message payload),\n * never a shape this module imposes on the Model.\n */\nexport interface Ack {\n readonly lastAppliedSeq: Seq;\n}\n\n/** Construct an `Ack` for a last-applied sequence number. */\nexport const ack = (lastAppliedSeq: Seq): Ack => ({ lastAppliedSeq });\n\n/**\n * The \"nothing applied yet\" cursor — the ack value for a server that has applied\n * no client input at all. Because `nextSeq` mints **0-based** seqs and\n * `partitionByAck` acks **inclusively** (`seq <= lastAppliedSeq`), the pre-first\n * cursor must sit strictly *below* seq 0: `lastAppliedSeq = 0` would falsely ack\n * the seq-0 input before the server ever saw it. `-1` is \"before any seq\", and\n * `partitionByAck(buffer, NO_ACK)` therefore leaves the whole buffer PENDING.\n *\n * This names the boundary value on the seam so no consumer has to re-discover it\n * as a magic number: it is the primitive's own design (0-based mint + inclusive\n * partition) that creates the trap, so it is the primitive's job to name the\n * escape. Pair with {@link initAck} when an {@link Ack} is wanted instead of the\n * bare `Seq`.\n */\nexport const NO_ACK: Seq = -1;\n\n/**\n * The `Ack` for a server that has applied nothing yet — `ack(NO_ACK)`. The `Ack`\n * counterpart to the {@link NO_ACK} `Seq` sentinel, for a consumer that carries\n * an `Ack` rather than a bare `lastAppliedSeq`.\n */\nexport const initAck = (): Ack => ack(NO_ACK);\n\n/** The result of splitting a seq-tagged buffer against an ack. */\nexport interface AckPartition<T> {\n /** Inputs the authoritative side has applied (`seq <= lastAppliedSeq`). */\n readonly acked: readonly SeqTagged<T>[];\n /** Inputs still in flight / un-acked (`seq > lastAppliedSeq`) — the replay tail. */\n readonly pending: readonly SeqTagged<T>[];\n}\n\n/**\n * Partition a buffer of seq-tagged commands into ACKED and PENDING against the\n * authoritative `lastAppliedSeq`. Pure and total: every item lands in exactly\n * one bucket, input order is preserved within each bucket, and the boundary\n * `seq == lastAppliedSeq` is ACKED (inclusive `<=`). Routing is by `seq` value,\n * not buffer position, so an out-of-order or duplicate-seq buffer partitions\n * correctly. Accepts either an {@link Ack} or a bare `lastAppliedSeq` number, so\n * a caller passes whichever it holds.\n */\nexport const partitionByAck = <T>(\n buffer: readonly SeqTagged<T>[],\n ackOrSeq: Ack | Seq,\n): AckPartition<T> => {\n const lastAppliedSeq =\n typeof ackOrSeq === \"number\" ? ackOrSeq : ackOrSeq.lastAppliedSeq;\n const acked: SeqTagged<T>[] = [];\n const pending: SeqTagged<T>[] = [];\n for (const item of buffer) {\n if (item.seq <= lastAppliedSeq) {\n acked.push(item);\n } else {\n pending.push(item);\n }\n }\n return { acked, pending };\n};\n\n/**\n * The next sequence number to assign: one past the highest `seq` in the buffer,\n * or `0` for an empty buffer. Lets a client mint monotonic seqs from its pending\n * buffer alone — no counter field on the Model — and is tolerant of an unordered\n * buffer (it takes the max, not the last element).\n */\nexport const nextSeq = <T>(buffer: readonly SeqTagged<T>[]): Seq =>\n buffer.reduce((max, item) => (item.seq > max ? item.seq : max), -1) + 1;\n\n/**\n * The client prediction/reconciliation helper — the Gambetta/Valve\n * authoritative-server loop's reconcile step, generalized.\n *\n * Given the latest `authoritativeState` from the server, the server's\n * `lastAppliedSeq` ack, and the client's `pending` buffer of seq-tagged inputs,\n * it returns the corrected predicted state: drop the inputs the server has\n * already applied (`seq <= lastAppliedSeq`) and replay ONLY the un-acked tail\n * (`seq > lastAppliedSeq`) over the authoritative snapshot.\n *\n * It is **composed, not re-derived** (ADR 0006): the drop is {@link partitionByAck}\n * (the ack primitive, #212) and the replay is `foldMsgs` (the pure fold seam,\n * #211) — there is no second copy of the partition logic and no second copy of\n * the fold. `lastAppliedSeq` accepts either an {@link Ack} or a bare `Seq`, so a\n * caller passes whichever it holds, matching `partitionByAck`.\n *\n * Pure and host-agnostic: it takes the `machine` (which `foldMsgs` needs to\n * dispatch `update`) but touches no `Store`, `interpret`, or subscription, and\n * imports nothing from the runtime — so it ships through the client-safe\n * umbrella without dragging `run` into a client bundle.\n */\nexport function reconcile<\n S,\n M extends { type: string },\n C extends Cmd,\n U extends Sub,\n Ctx,\n>(\n machine: Machine<S, M, C, U, Ctx>,\n authoritativeState: S,\n lastAppliedSeq: Ack | Seq,\n pending: readonly SeqTagged<M>[],\n): S {\n const { pending: unacked } = partitionByAck(pending, lastAppliedSeq);\n return foldMsgs(\n machine,\n authoritativeState,\n unacked.map((tagged) => tagged.value),\n );\n}\n","// ---------------------------------------------------------------------------\n// SubscribeHandler<S, M, Ctx> — the structural shape of one sub runner: one\n// entry of the `subscribe` table handed to `run`. Pulled out as a named type\n// so factory return types in this subpath can match the engine's `Subscribe`\n// entry without re-deriving it from the mapped type.\n//\n// The machine declares a Sub as data in `subs`; a factory in this subpath\n// returns the runner for that Sub's type, which the caller hands to `run`:\n//\n// subs: [{ type: \"audit-idle\", deps: (s) => (s.watching ? {} : null) }],\n// // …\n// run(machine, {\n// subscribe: {\n// \"audit-idle\": fromPort(\n// (ctx) => ctx.auditRuntime,\n// auditIdlePort,\n// (idle) => ({ type: \"audit:idle\", idle }),\n// ),\n// },\n// });\n//\n// The factory absorbs the lifecycle (subscribe + cleanup); the caller keeps\n// the intent (which port, which msg). A runner reads its data off `sub.deps`.\n// Strengthens invariant 9 (the surface for cross-cutting Sub topologies is\n// named, small, and exported from one subpath instead of redrawn at every\n// call site).\n// ---------------------------------------------------------------------------\n\nimport type { Dispose, Sub } from \"../index\";\n\n/**\n * A Sub runner: given the Sub, the `ctx` and a `dispatch`, it opens the\n * resource and returns a `Dispose`. Every `from*` factory returns one.\n */\nexport type SubscribeHandler<S extends Sub, M, Ctx> = (\n sub: S,\n ctx: Ctx,\n dispatch: (msg: M) => void,\n) => Dispose;\n\n// ---------------------------------------------------------------------------\n// dispatchIfPresent — the null-drop dispatch idiom every Sub factory in this\n// subpath (and the chrome/node factories) shares. A factory's callback maps a\n// platform event to `M | null`; `null` is the explicit \"drop this emission\"\n// signal — the event fired but the caller decided it isn't interesting given\n// current state, so no dispatch is made and the listener stays armed. Hoisted\n// here so the one drop-on-null rule lives once instead of re-inlined as\n// `const msg = fn(...); if (msg !== null) dispatch(msg);` at every listener.\n// ---------------------------------------------------------------------------\n\nexport function dispatchIfPresent<M>(\n dispatch: (msg: M) => void,\n msg: M | null,\n): void {\n if (msg !== null) dispatch(msg);\n}\n","// ---------------------------------------------------------------------------\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 in the `subscribe` table handed to `run`, 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 * runner 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 deps — `sub.deps.alarmName`,\n * `sub.deps.channel` — with NO cast at the substrate edge: the narrowing\n * happens in 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 Sub factory in `src/subs/`: supply the `msgFn` projection, get a\n * `(sub, ctx, dispatch) => cleanup` runner for the `subscribe` table `run`\n * takes.\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 reads `channelName` off the Sub's `deps` (not the closure)\n// so the same factory instance handles every BroadcastChannel Sub in a\n// machine — one Sub per channel, sharing the factory's lifecycle code.\n// A renamed channel is a changed deps value, so a new id: the engine\n// closes the old channel and opens the new one.\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 = { readonly channelName: string };\n\ndeclare const BroadcastChannel: MinimalBroadcastChannelCtor;\n\n/**\n * Build a Sub runner that listens on the `BroadcastChannel` named by\n * `sub.deps.channelName` and dispatches `msgFn(event, sub)` for each message.\n * Returning `null` from `msgFn` drops the event.\n */\nexport function fromBroadcastChannel<\n S extends Sub<string, BroadcastSubData>,\n M,\n>(\n msgFn: (event: MinimalMessageEvent, sub: S) => M | null,\n): SubscribeHandler<S, M, unknown> {\n return (sub, _ctx, dispatch) => {\n const channel = new BroadcastChannel(sub.deps.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 in the Sub's `deps` (`EventSourceSubData`). A changed url\n// is a changed deps value, so a new id: the engine closes the old\n// connection and opens one to the new url.\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 = { readonly url: string };\n\ndeclare const EventSource: MinimalEventSourceCtor;\n\n/**\n * Options for `fromEventSource`: map each server-sent message, error and open\n * event to a Msg, or to `null` to drop it.\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\n/**\n * Build a Sub runner that opens an `EventSource` on `sub.deps.url` and\n * dispatches a Msg for each server-sent event.\n */\nexport function fromEventSource<S extends Sub<string, EventSourceSubData>, M>(\n opts: EventSourceFactoryOpts<S, M>,\n): SubscribeHandler<S, M, unknown> {\n return (sub, _ctx, dispatch) => {\n const source = new EventSource(sub.deps.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 needs only a changed `deps` value (a restart); the factory\n// pulls the current 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\n/**\n * Build a Sub runner that listens for `eventName` on a DOM-style event target\n * and dispatches `msgFn(event, sub)` for each event.\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 runner that wraps a periodic timer is the\n// same five lines: `setInterval` the dispatch, return a `clearInterval`\n// cleanup. The factory absorbs both halves of the lifecycle (subscribe +\n// cleanup); the caller keeps the intent — which Sub type, which Msg to\n// dispatch.\n//\n// Timing data (`intervalMs`) lives in the Sub's `deps`, not in the closure.\n// The engine derives the Sub's id from its deps, so a `deps` that returns\n// `{ intervalMs: 30_000 }` in one state and `{ intervalMs: 60_000 }` later is\n// a new id: the engine stops the old interval and starts one at the new\n// period. An unchanged deps value leaves the running interval alone.\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 = { readonly intervalMs: number };\n\n/**\n * Build a Sub runner that dispatches `msgFn(sub)` every `sub.deps.intervalMs`\n * milliseconds while the Sub is on.\n */\nexport function fromInterval<S extends Sub<string, 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.deps.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 runner attaches to the publisher's Port.\n// The recurring 4-line shape at every such runner:\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 runner).\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.\n/**\n * Build a Sub runner that listens to a `Port` on another runtime and dispatches\n * `msgFn(value, sub)` for each value emitted. Use it to let one machine react\n * to another's announcements.\n */\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 in the Sub's `deps` (`WebSocketSubData`).\n// A changed url is a new id, so the engine tears this runner down (no\n// reconnect) and starts a fresh one against 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\n/**\n * Options for `fromReconnectingWebSocket`: the event-to-Msg mappers, the\n * backoff settings, and an injectable `connect` and `schedule` for tests.\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\n/**\n * Build a Sub runner that keeps a WebSocket to `sub.deps.wsUrl` open,\n * reconnecting with backoff when it drops.\n */\nexport function fromReconnectingWebSocket<\n S extends Sub<string, WebSocketSubData>,\n M,\n>(\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.deps.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// fromTimeout — universal `setTimeout`-shaped Sub factory.\n//\n// Same lifecycle shape as `fromInterval` minus the recurrence: the\n// scheduled callback fires once, the cleanup is `clearTimeout`. For the\n// common case — fire one Msg after a delay — the engine's built-in `timer`\n// Sub (`{ type: \"timer\", deps: (s) => cond ? { ms, msg } : null }`) needs no\n// runner at all; reach for this factory when the Msg is built from the Sub\n// rather than carried in its deps. Two observable consequences worth pinning:\n//\n// 1. The timer fires once per start. It does NOT re-arm while the Sub\n// stays on with the same deps — the engine leaves an unchanged Sub\n// running, and the handle is exhausted. A changed deps value (a new\n// `delayMs`, or any other field) is a new id, so the engine stops this\n// runner and starts a fresh one: that is how to re-arm.\n//\n// 2. Cleanup before the timer fires cancels the pending dispatch. This\n// is the \"cancel on state exit\" pattern: gate the Sub's `deps` on the\n// state where the timeout should be armed. When the state leaves it,\n// `deps` goes null, the engine stops the Sub, and the factory's\n// cleanup fires before the timer does.\n// ---------------------------------------------------------------------------\n\nimport type { Sub } from \"../index\";\nimport type { SubscribeHandler } from \"./types\";\n\ntype TimeoutSubData = { readonly delayMs: number };\n\n/**\n * Build a Sub runner that dispatches `msgFn(sub)` once, `sub.deps.delayMs`\n * milliseconds after the Sub turns on. Turning the Sub off first cancels it.\n */\nexport function fromTimeout<S extends Sub<string, TimeoutSubData>, M>(\n msgFn: (sub: S) => M,\n): SubscribeHandler<S, M, unknown> {\n return (sub, _ctx, dispatch) => {\n const handle = setTimeout(() => {\n dispatch(msgFn(sub));\n }, sub.deps.delayMs);\n return () => {\n clearTimeout(handle);\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. `wsUrl` lives in the Sub's `deps`\n// (`WebSocketSubData`) — a changed `wsUrl` is a new id, so the engine closes\n// the old socket and opens one to the new url, as with `fromEventSource`.\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\n/** The `deps` a WebSocket Sub carries: the `wsUrl` to connect to. */\nexport type WebSocketSubData = { readonly wsUrl: string };\n\ndeclare const WebSocket: MinimalWebSocketCtor;\n\n/**\n * Options for `fromWebSocket`: map each message, open, error and close event to\n * a Msg, or to `null` to drop it.\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\n/**\n * Build a Sub runner that opens a WebSocket to `sub.deps.wsUrl` and dispatches\n * a Msg for each socket event. It does not reconnect; use\n * `fromReconnectingWebSocket` for that.\n */\nexport function fromWebSocket<S extends Sub<string, WebSocketSubData>, M>(\n opts: WebSocketFactoryOpts<S, M>,\n): SubscribeHandler<S, M, unknown> {\n return (sub, _ctx, dispatch) => {\n const ws = new WebSocket(sub.deps.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 `.depKeyed(when)` entry for the machine's `subs` plus a `.subscribe`\n// runner for the `subscribe` table handed to `run`. The battery's `name` IS\n// its Sub type, so two managed resources in one machine are two Sub types,\n// each with its own runner — no router. The engine's reconcile does the rest:\n// - `when(state)` turns non-null → runner runs → `acquire(key, ctx)`.\n// - `when(state)` goes null → cleanup runs → `release(handle)`.\n// - the key changes (a new id) → 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 the reconcile pass, never hand-driven by a cell)\n// and invariant 6 (no silent looseness — every live resource is a running Sub,\n// visible in `replay(...).subs`).\n// ---------------------------------------------------------------------------\n\nimport type { DepKeyedSub, Dispose, Sub } from \"../index\";\nimport { structuralHash } from \"../index\";\n\n/**\n * The running Sub of a managed resource named `N`: its `deps` is the lifetime\n * key. The consumer's Sub union (`types.sub`) includes it directly.\n */\nexport type ManagedResourceSub<N extends string, TKey> = Sub<N, TKey>;\n\n/**\n * Options for `defineManagedResource`: the resource's `name`, how to `acquire`\n * it for a key, and how to `release` the handle.\n */\nexport interface DefineManagedResourceOpts<\n N extends string,\n TKey,\n Handle,\n Ctx,\n> {\n /**\n * A short, stable name — and the resource's Sub `type`. The machine's\n * `subscribe` table holds this battery's runner under it, so it must be\n * unique among the machine's Sub types.\n */\n readonly name: N;\n\n /**\n * Build the resource. Runs when the resource's `deps` turns non-null.\n * The returned Handle is the ONLY thing `release` receives — capture\n * everything teardown needs into it. If `acquire` throws, the engine does\n * NOT register the Sub and `release` is never called (no dangling\n * half-built resource).\n */\n readonly acquire: (key: TKey, ctx: Ctx) => Handle;\n\n /**\n * Tear the resource down. Runs when the key goes null (phase exited) or\n * changes. MANDATORY — the guaranteed teardown is the reason this battery\n * 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 `.depKeyed(when)` entry for the machine's\n * `subs`, the `.subscribe` runner for the `subscribe` table handed to `run`,\n * and a `.get(key)` accessor so Cmd handlers can reach the live Handle while\n * the resource is held.\n */\nexport interface ManagedResourceBattery<N extends string, TKey, Handle, Ctx> {\n /** The battery's Sub type — its `name`. */\n readonly type: N;\n\n /**\n * The `subs` entry. `when(state)` returns the resource's KEY when it should\n * be held, or `null` when it should be torn down. The key is the Sub's\n * `deps`, so the engine derives the id from it: an unchanged key leaves the\n * resource alone, a changed key releases the old one and acquires anew.\n * Plain JSON-compatible data only — the id hash throws on anything else.\n */\n readonly depKeyed: <S>(\n when: (state: S) => TKey | null,\n ) => DepKeyedSub<S, ManagedResourceSub<N, TKey>>;\n\n /**\n * The runner for this battery's Sub type — hand it to `run` as\n * `subscribe: { [battery.type]: battery.subscribe }`. Runs `acquire` on\n * start, holds the Handle in the battery's handle table, and returns a\n * cleanup that runs `release` (and forgets the Handle) on stop.\n */\n readonly subscribe: (\n sub: ManagedResourceSub<N, TKey>,\n ctx: Ctx,\n // The managed resource dispatches no Msgs — acquire/release are pure\n // lifecycle. `dispatch` is accepted to match the runner shape and is\n // intentionally unused.\n dispatch: (msg: never) => void,\n ) => Dispose;\n\n /**\n * Accessor for the live Handle, keyed on `key` (the author's key, never the\n * derived Sub id). Returns `undefined` when the resource is not currently\n * held — the caller decides whether that is an error. A Cmd handler wires\n * its `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\n/**\n * Build the battery. One call per managed resource at the machine's host\n * file.\n *\n * @example\n * ```ts\n * import {\n * defineMachine,\n * defineManagedResource,\n * type Interpret,\n * type ManagedResourceSub,\n * type Reducer,\n * } from \"@demlik/tea\";\n * import { run } from \"@demlik/tea/promise\";\n *\n * type RunId = string;\n * type State = { type: \"idle\" } | { type: \"running\"; runId: RunId };\n * type Msg = { type: \"start\"; runId: RunId } | { type: \"stop\" };\n * type Cmd = { type: \"start_graph\"; runId: RunId };\n * interface Ctx { readonly storage: Storage; readonly bucket: string }\n * declare class CheckpointSaver {\n * constructor(opts: { storage: Storage; bucket: string; runId: RunId });\n * save(step: string): Promise<void>;\n * clear(): Promise<void>;\n * }\n * declare const update: Reducer<State, Msg, Cmd>;\n * declare const ctx: Ctx;\n *\n * const checkpointStore = defineManagedResource<\"checkpoint\", 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 * const machine = defineMachine({\n * types: {\n * model: {} as State,\n * msg: {} as Msg,\n * cmd: {} as Cmd,\n * sub: {} as ManagedResourceSub<\"checkpoint\", RunId>,\n * ctx: {} as Ctx,\n * },\n * init: (loaded) => [loaded ?? { type: \"idle\" }, []],\n * update,\n * subs: [\n * checkpointStore.depKeyed((s: State) =>\n * s.type === \"running\" ? s.runId : null,\n * ),\n * ],\n * });\n *\n * // In interpret (read the live handle the reconciler holds):\n * const interpret: Interpret<Msg, Cmd, Ctx> = {\n * start_graph: async (cmd) => {\n * const saver = checkpointStore.get(cmd.runId); // same owner, no rebuild\n * await saver?.save(\"started\");\n * },\n * };\n *\n * // At run:\n * run(machine, { ctx, interpret, subscribe: { checkpoint: checkpointStore.subscribe } });\n * ```\n */\nexport function defineManagedResource<N extends string, TKey, Handle, Ctx>(\n opts: DefineManagedResourceOpts<N, TKey, Handle, Ctx>,\n): ManagedResourceBattery<N, 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 // Keyed by the author's key, because `get` is called with that key and the\n // caller cannot know the derived Sub id.\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 subscribe = (\n sub: ManagedResourceSub<N, TKey>,\n ctx: Ctx,\n _dispatch: (msg: never) => void,\n ): Dispose => {\n // Acquire. A throw here propagates out of the reconcile pass (the engine\n // records it and re-throws after the diff) and the Sub is NOT registered\n // — 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.deps, ctx);\n const k = keyString(sub.deps);\n held.set(k, handle);\n\n // Cleanup runs when the key goes null or changes. Forget the Handle first\n // so a concurrent `get` after teardown begins sees `undefined`, then\n // 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 type: opts.name,\n depKeyed: <S>(\n when: (state: S) => TKey | null,\n ): DepKeyedSub<S, ManagedResourceSub<N, TKey>> => ({\n type: opts.name,\n deps: when,\n }),\n subscribe,\n get: (key) => held.get(keyString(key)),\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 runner, 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// The seam's `name` IS its Sub type, so two seams in one machine (`hands` +\n// `worker`) are two Sub types with two runners — they never collide.\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, Dispose, Sub } from \"../index\";\nimport { structuralHash } 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 runner; cleanup runs when the seam's key\n * goes null or changes.\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 seam's key (`runId`, or whatever identity is on the phase) so\n * per-run transports can be addressed.\n */\nexport type TransportFactory<K> = (key: K) => Transport;\n\n/**\n * The running Sub of a seam named `N`: its `deps` is the seam key. The\n * consumer's Sub union (`types.sub`) includes it directly.\n */\nexport type TransportSub<N extends string, TKey> = Sub<N, TKey>;\n\n/**\n * Options for `fromTransport`: the seam's `name`, how to open the transport,\n * and how inbound values, outbound values and a dropped connection map to and\n * from Msgs.\n */\nexport interface FromTransportOpts<\n N extends string,\n TKey,\n Inbound,\n Outbound,\n M,\n Ctx,\n> {\n /**\n * A short, stable name for this seam — and its Sub `type`. The machine's\n * `subscribe` table holds the seam's runner under it, so it must be unique\n * among the machine's Sub types.\n */\n readonly name: N;\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 `.depKeyed(when)` entry for the machine's\n * `subs`, the `.subscribe` runner for the `subscribe` table handed to `run`,\n * and a `send(key, outbound)` helper the consumer's Cmd handler calls.\n */\nexport interface TransportBattery<N extends string, TKey, Outbound, M, Ctx> {\n /** The seam's Sub type — its `name`. */\n readonly type: N;\n\n /**\n * The `subs` entry. `when(state)` returns the seam's KEY when it should be\n * open, or `null` when it should be torn down. The key is the Sub's `deps`,\n * so the engine derives the id from it: an unchanged key leaves the seam\n * open, a changed key closes it and opens a fresh one. Plain\n * JSON-compatible data only — the id hash throws on anything else.\n */\n readonly depKeyed: <S>(\n when: (state: S) => TKey | null,\n ) => DepKeyedSub<S, TransportSub<N, TKey>>;\n\n /**\n * The runner for this seam's Sub type — hand it to `run` as\n * `subscribe: { [seam.type]: seam.subscribe }`. Wires inbound + close in\n * one shot and publishes the live transport to the outbound handle table.\n */\n readonly subscribe: (\n sub: TransportSub<N, TKey>,\n ctx: Ctx,\n dispatch: (msg: M) => void,\n ) => Dispose;\n\n /**\n * Outbound send, addressed by the seam's key (never the derived Sub id,\n * which the caller cannot know). The Cmd handler calls this; the battery's\n * handle table (built when the runner started) routes to the live\n * transport. If the seam is closed (no running Sub), the send is dropped\n * honestly (logged, not thrown — the reducer already moved past caring,\n * same shape as Rule 2 fire-and-forget Cmds).\n */\n readonly send: (key: TKey, outbound: Outbound) => void;\n}\n\n/**\n * Build the battery. One call per seam at the machine's host file.\n *\n * @example\n * ```ts\n * import {\n * defineMachine,\n * fromTransport,\n * type Interpret,\n * type Reducer,\n * type Transport,\n * type TransportSub,\n * } from \"@demlik/tea\";\n * import { run } from \"@demlik/tea/promise\";\n *\n * type RunId = string;\n * type HandsInbound = { kind: \"said\"; text: string };\n * type HandsOutbound = { kind: \"do\"; action: string };\n * type State = { type: \"idle\" } | { type: \"auditing\"; runId: RunId };\n * type Msg =\n * | { type: \"hands_said\"; runId: RunId; inbound: HandsInbound }\n * | { type: \"hands_lost\"; runId: RunId };\n * type Cmd = { type: \"send_hands\"; runId: RunId; outbound: HandsOutbound };\n * interface Ctx { readonly openHandsWs: (runId: RunId) => Transport }\n * declare function parseHandsInbound(json: unknown): HandsInbound | null;\n * declare const update: Reducer<State, Msg, Cmd>;\n * declare const ctx: Ctx;\n *\n * const handsSeam = fromTransport<\"hands\", 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 * const machine = defineMachine({\n * types: {\n * model: {} as State,\n * msg: {} as Msg,\n * cmd: {} as Cmd,\n * sub: {} as TransportSub<\"hands\", RunId>,\n * ctx: {} as Ctx,\n * },\n * init: (loaded) => [loaded ?? { type: \"idle\" }, []],\n * update,\n * subs: [\n * handsSeam.depKeyed((s: State) =>\n * s.type === \"auditing\" ? s.runId : null,\n * ),\n * ],\n * });\n *\n * // In interpret:\n * const interpret: Interpret<Msg, Cmd, Ctx> = {\n * send_hands: async (cmd) => { handsSeam.send(cmd.runId, cmd.outbound); },\n * };\n *\n * // At run:\n * run(machine, { ctx, interpret, subscribe: { hands: handsSeam.subscribe } });\n * ```\n */\nexport function fromTransport<\n N extends string,\n TKey,\n Inbound,\n Outbound,\n M,\n Ctx,\n>(\n opts: FromTransportOpts<N, TKey, Inbound, Outbound, M, Ctx>,\n): TransportBattery<N, 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 — and on the author's key, not the\n // derived Sub id, because `send` is addressed by that key.\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 subscribe = (\n sub: TransportSub<N, TKey>,\n ctx: Ctx,\n dispatch: (msg: M) => void,\n ): Dispose => {\n const key = sub.deps;\n // Open the transport. The battery owns the lifetime.\n const t = opts.openTransport(key, ctx);\n const k = keyString(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, key);\n if (parsed === null) return;\n const msg = opts.onInbound(parsed, 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(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 seam's key goes null or changes.\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 type: opts.name,\n\n depKeyed: <S>(\n when: (state: S) => TKey | null,\n ): DepKeyedSub<S, TransportSub<N, TKey>> => ({\n type: opts.name,\n deps: when,\n }),\n\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}\n"]}
1
+ {"version":3,"sources":["../src/compose/index.ts","../src/observability.ts","../src/internal/prediction/index.ts","../src/subs/types.ts","../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-timeout.ts","../src/subs/from-web-socket.ts","../src/subs/managed-resource.ts","../src/subs/transport.ts"],"names":[],"mappings":"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","file":"chunk-BVFS3ICC.js","sourcesContent":["/**\n * @packageDocumentation\n * The composition seam — the layer between a battery and a subpath.\n *\n * A battery is pure state plus verbs over that state. A subpath holds several\n * batteries side by side in one Model. Everything between those two facts was,\n * until now, hand-written at every consumer: the one-key record rebuild that\n * threads a verb's `[slice, cmds]` result back into the host state, and the\n * statement order that decides which slice wins a derived read.\n *\n * Two helpers cover it, and neither is an optics kit. {@link liftSlice} is the\n * record rebuild with the key checked by the type system; {@link readInOrder}\n * turns a read's precedence from statement order into a value you can name,\n * export and assert on. ADR 0001 (no off-the-shelf resilience) and ADR 0014\n * (Effect's types, never its runtime values) between them settle why this is\n * forty lines here rather than a dependency.\n *\n * `docs/how-to/compose-two-battery-slices.md` wires both end to end over a\n * two-battery subpath, and its `ts` blocks are compiled and run as\n * `src/docs/how-to/compose-slices.test.ts`.\n *\n * Both are pure: no clock, no RNG, no storage, nothing captured. A `liftSlice`\n * result is a fresh record and a Cmd array, so ADR 0014's journal/hash promise\n * over a reducer's return value is untouched.\n */\n\n/**\n * Lift a battery verb's result into the host state that carries its slice.\n *\n * `liftSlice(\"resilience\", state, knob.attempt(state.resilience, …))` is the\n * `{ ...state, resilience: slice }` every consumer writes by hand, with the key\n * checked: `key` must be a key of `S`, and the slice the verb returned must be\n * assignable to the type that key carries — so naming the wrong field, or\n * threading one battery's slice into another's key, is a compile error rather\n * than a state shape nothing reads correctly.\n *\n * PURE — a fresh record and the verb's own Cmds, unchanged.\n */\nexport function liftSlice<S, K extends keyof S, C>(\n key: K,\n state: S,\n [slice, cmds]: readonly [S[K], readonly C[]],\n): readonly [S, readonly C[]] {\n // A computed-key spread widens to `S & { [k in K]: S[K] }` in TS's own\n // arithmetic, which is `S` by construction; the assertion states that rather\n // than re-deriving it. The runtime shape is exactly `S`'s.\n return [{ ...state, [key]: slice } as S, cmds];\n}\n\n/**\n * One step of a composed read: the slice it consults, under the name that slice\n * goes by. `read` returns `undefined` for \"this slice has nothing to say\",\n * which is what hands the read on to the next step.\n */\nexport interface ReadStep<In, Out> {\n /** The slice this step consults — `\"cache\"`, `\"failed\"`, `\"inFlight\"`. */\n readonly name: string;\n /** What this slice says about `input`, or `undefined` to defer. */\n readonly read: (input: In) => Out | undefined;\n}\n\n/**\n * Run a composed read through `order` and return the first answer any step\n * gives, or `absent` when every one defers.\n *\n * The point is that `order` is a VALUE. A derived read over three battery\n * slices otherwise states its precedence as the order of three `if` statements,\n * where it is invisible to the type system, unnameable in a test and one\n * innocent-looking reshuffle away from being wrong — which is exactly the\n * defect that escaped `classify-batch`'s per-battery test suites. Written as\n * data, the precedence can be exported, printed and asserted on directly.\n *\n * PURE — it evaluates `read` in order and stores nothing.\n */\nexport function readInOrder<In, Out>(\n input: In,\n order: readonly ReadStep<In, Out>[],\n absent: Out,\n): Out {\n for (const step of order) {\n const answer = step.read(input);\n if (answer !== undefined) return answer;\n }\n return absent;\n}\n","import type { BootingRuntime } from \"./runtime-types\";\n\n// === historyTracker: composable observability helper ===\n//\n// A bounded ring buffer of recent `(msg, state)` transitions, built on the\n// Runtime's public `observe` API. The substrate itself stays minimal — it has\n// no notion of history, no opt to enable one, no method on the Runtime\n// interface to read one. Anything that wants \"recent transitions\" composes via\n// this helper.\n//\n// Why not put this on the Runtime interface as `runtime.history()`? Because\n// history is derivable. Putting it on the Runtime privileges one use case\n// (debug inspectors) at the substrate level and grows the interface for\n// everyone; the composition shape stays out of the dispatch loop entirely, so\n// the substrate's hot path pays no cost for a feature it doesn't need.\n\n/**\n * A bounded log of a runtime's recent `(msg, state)` transitions, returned by\n * `historyTracker`. Use it to feed a debug inspector.\n */\nexport interface HistoryTracker<S, M extends { type: string }> {\n /**\n * Snapshot of recorded transitions, oldest first. Each entry is\n * `{ msg, state }`: `msg` is `null` for the boot transition (recorded via\n * `onBoot`) and the applied Msg for every other (recorded via `observe`).\n *\n * Returns a shallow copy: the array is fresh on every call (callers may\n * iterate, slice, or replay without affecting the tracker's buffer).\n * Entry values are references to the originals (TEA states/msgs are\n * conventionally immutable; do not mutate them if your S/M is not).\n */\n snapshot(): readonly { readonly msg: M | null; readonly state: S }[];\n /**\n * Detach the underlying observer. After `stop()`:\n * - No new transitions are recorded.\n * - `snapshot()` continues to work and returns the buffer's contents\n * at the moment of stop (useful for post-hoc inspection in tests).\n * - The tracker holds no references that would prevent GC of the\n * `runtime` argument other than the entries already buffered.\n *\n * Idempotent — subsequent calls are no-ops.\n */\n stop(): void;\n}\n\n/**\n * Create a bounded history tracker over a Runtime. The tracker subscribes to\n * the runtime via `observe(...)` and retains the last `size` `(msg, state)`\n * transitions in a FIFO ring buffer.\n *\n * @param runtime Any `BootingRuntime<S, M>` (a full `Runtime` satisfies it).\n * The tracker uses only `observe`, which is total before boot —\n * attach it to the synchronous `run()` handle to record the\n * boot transition.\n * @param size Buffer cap. `size <= 0` produces an inert no-op tracker:\n * no observer is attached to the runtime, `snapshot()` is\n * always `[]`, and `stop()` is a no-op.\n */\nexport function historyTracker<S, M extends { type: string }>(\n runtime: BootingRuntime<S, M>,\n size: number,\n): HistoryTracker<S, M> {\n const buffer: { msg: M | null; state: S }[] = [];\n let stopped = false;\n\n const push = (entry: { msg: M | null; state: S }): void => {\n buffer.push(entry);\n if (buffer.length > size) buffer.shift();\n };\n\n // The boot transition (the `{ msg: null, state }` head entry) arrives via\n // `onBoot`; applied transitions arrive via `observe` with a total `msg`. The\n // public snapshot shape (`msg: M | null`) is unchanged — boot is still\n // `msg: null`.\n const unobserve =\n size <= 0\n ? (): void => {}\n : runtime.observe((msg, state) => {\n push({ msg, state });\n });\n const unboot =\n size <= 0\n ? (): void => {}\n : runtime.onBoot((state) => {\n push({ msg: null, state });\n });\n\n return {\n snapshot() {\n // Shallow copy — callers may freely iterate or mutate the returned\n // array. Entry values are references; see HistoryTracker docs.\n return buffer.slice();\n },\n stop() {\n if (stopped) return;\n stopped = true;\n unobserve();\n unboot();\n },\n };\n}\n","/**\n * internal/prediction — the client-prediction ack primitive (epic #186, facet 2).\n * Internal since #49 — not published on any subpath of its own; the\n * client-safe barrel `src/pure/` re-exports the public surface, and the root\n * `@demlik/tea` entry publishes it.\n *\n * The contract the Gambetta/Valve authoritative-server netcode loop needs:\n * **command-seq-in → last-applied-seq-out.** A client tags each predicted\n * command/`Msg` with a monotonic `seq`; the authoritative side replies with the\n * `lastAppliedSeq` it has processed. `partitionByAck` then splits the client's\n * pending buffer into the inputs the server has ACKED (`seq <= lastAppliedSeq`)\n * and those still PENDING (`seq > lastAppliedSeq`) — the un-acked tail the\n * reconciliation pass (#214) replays over the authoritative snapshot via\n * `foldMsgs` (#211, the client-safe barrel `src/pure/`).\n *\n * Deliberately **Model-shape-agnostic** (ADR 0006): the ack is a standalone\n * value, NOT a field this module requires on the consumer's `State`. It\n * generalizes the `lastAckedSeq`-on-the-Model + `seq`-threaded-through-the-Msg\n * pattern vortex hand-rolled (epic #186). It is a LEAF module — pure and\n * runtime-free, importing nothing from the tea core or host, so it can never\n * drag `run`/the host into a client bundle.\n *\n * This is the read/ack side of the primitive; `tagSeq`/`nextSeq` are the write\n * side, letting a client mint monotonic seqs from its pending buffer alone —\n * again with no counter field on the Model.\n *\n * `reconcile` (the read side's payoff, #214) composes this ack partition with\n * the pure fold seam `foldMsgs` to perform the whole Gambetta/Valve\n * reconciliation step in one call. The `foldMsgs` import is the ONLY dependency\n * this leaf takes, and it reaches into the pure-core leaf (`../pure/core`) — NOT\n * the runtime root — so the module stays runtime-free and the client-safe\n * import-graph guard (ADR 0006, #213) still holds.\n */\n\nimport type { Cmd, Machine, Sub } from \"../../pure/core\";\nimport { foldMsgs } from \"../../pure/core\";\n\n/**\n * A monotonic, non-negative sequence number tagging one client-predicted\n * command. Monotonicity is the client's contract (mint successive seqs with\n * `nextSeq`); this module only ever *compares* seqs.\n */\nexport type Seq = number;\n\n/** A command (or `Msg`) tagged with the `seq` the client assigned it. */\nexport interface SeqTagged<T> {\n readonly seq: Seq;\n readonly value: T;\n}\n\n/** Tag a command/`Msg` with its sequence number. */\nexport const tagSeq = <T>(seq: Seq, value: T): SeqTagged<T> => ({ seq, value });\n\n/**\n * The authoritative side's acknowledgement: the highest `seq` it has applied.\n * Everything with `seq <= lastAppliedSeq` is settled; everything above is still\n * in flight. This *is* the whole \"ack from the authoritative side\" — a value\n * the consumer carries however it likes (a wire field, a message payload),\n * never a shape this module imposes on the Model.\n */\nexport interface Ack {\n readonly lastAppliedSeq: Seq;\n}\n\n/** Construct an `Ack` for a last-applied sequence number. */\nexport const ack = (lastAppliedSeq: Seq): Ack => ({ lastAppliedSeq });\n\n/**\n * The \"nothing applied yet\" cursor — the ack value for a server that has applied\n * no client input at all. Because `nextSeq` mints **0-based** seqs and\n * `partitionByAck` acks **inclusively** (`seq <= lastAppliedSeq`), the pre-first\n * cursor must sit strictly *below* seq 0: `lastAppliedSeq = 0` would falsely ack\n * the seq-0 input before the server ever saw it. `-1` is \"before any seq\", and\n * `partitionByAck(buffer, NO_ACK)` therefore leaves the whole buffer PENDING.\n *\n * This names the boundary value on the seam so no consumer has to re-discover it\n * as a magic number: it is the primitive's own design (0-based mint + inclusive\n * partition) that creates the trap, so it is the primitive's job to name the\n * escape. Pair with {@link initAck} when an {@link Ack} is wanted instead of the\n * bare `Seq`.\n */\nexport const NO_ACK: Seq = -1;\n\n/**\n * The `Ack` for a server that has applied nothing yet — `ack(NO_ACK)`. The `Ack`\n * counterpart to the {@link NO_ACK} `Seq` sentinel, for a consumer that carries\n * an `Ack` rather than a bare `lastAppliedSeq`.\n */\nexport const initAck = (): Ack => ack(NO_ACK);\n\n/** The result of splitting a seq-tagged buffer against an ack. */\nexport interface AckPartition<T> {\n /** Inputs the authoritative side has applied (`seq <= lastAppliedSeq`). */\n readonly acked: readonly SeqTagged<T>[];\n /** Inputs still in flight / un-acked (`seq > lastAppliedSeq`) — the replay tail. */\n readonly pending: readonly SeqTagged<T>[];\n}\n\n/**\n * Partition a buffer of seq-tagged commands into ACKED and PENDING against the\n * authoritative `lastAppliedSeq`. Pure and total: every item lands in exactly\n * one bucket, input order is preserved within each bucket, and the boundary\n * `seq == lastAppliedSeq` is ACKED (inclusive `<=`). Routing is by `seq` value,\n * not buffer position, so an out-of-order or duplicate-seq buffer partitions\n * correctly. Accepts either an {@link Ack} or a bare `lastAppliedSeq` number, so\n * a caller passes whichever it holds.\n */\nexport const partitionByAck = <T>(\n buffer: readonly SeqTagged<T>[],\n ackOrSeq: Ack | Seq,\n): AckPartition<T> => {\n const lastAppliedSeq =\n typeof ackOrSeq === \"number\" ? ackOrSeq : ackOrSeq.lastAppliedSeq;\n const acked: SeqTagged<T>[] = [];\n const pending: SeqTagged<T>[] = [];\n for (const item of buffer) {\n if (item.seq <= lastAppliedSeq) {\n acked.push(item);\n } else {\n pending.push(item);\n }\n }\n return { acked, pending };\n};\n\n/**\n * The next sequence number to assign: one past the highest `seq` in the buffer,\n * or `0` for an empty buffer. Lets a client mint monotonic seqs from its pending\n * buffer alone — no counter field on the Model — and is tolerant of an unordered\n * buffer (it takes the max, not the last element).\n */\nexport const nextSeq = <T>(buffer: readonly SeqTagged<T>[]): Seq =>\n buffer.reduce((max, item) => (item.seq > max ? item.seq : max), -1) + 1;\n\n/**\n * The client prediction/reconciliation helper — the Gambetta/Valve\n * authoritative-server loop's reconcile step, generalized.\n *\n * Given the latest `authoritativeState` from the server, the server's\n * `lastAppliedSeq` ack, and the client's `pending` buffer of seq-tagged inputs,\n * it returns the corrected predicted state: drop the inputs the server has\n * already applied (`seq <= lastAppliedSeq`) and replay ONLY the un-acked tail\n * (`seq > lastAppliedSeq`) over the authoritative snapshot.\n *\n * It is **composed, not re-derived** (ADR 0006): the drop is {@link partitionByAck}\n * (the ack primitive, #212) and the replay is `foldMsgs` (the pure fold seam,\n * #211) — there is no second copy of the partition logic and no second copy of\n * the fold. `lastAppliedSeq` accepts either an {@link Ack} or a bare `Seq`, so a\n * caller passes whichever it holds, matching `partitionByAck`.\n *\n * Pure and host-agnostic: it takes the `machine` (which `foldMsgs` needs to\n * dispatch `update`) but touches no `Store`, `interpret`, or subscription, and\n * imports nothing from the runtime — so it ships through the client-safe\n * umbrella without dragging `run` into a client bundle.\n */\nexport function reconcile<\n S,\n M extends { type: string },\n C extends Cmd,\n U extends Sub,\n Ctx,\n>(\n machine: Machine<S, M, C, U, Ctx>,\n authoritativeState: S,\n lastAppliedSeq: Ack | Seq,\n pending: readonly SeqTagged<M>[],\n): S {\n const { pending: unacked } = partitionByAck(pending, lastAppliedSeq);\n return foldMsgs(\n machine,\n authoritativeState,\n unacked.map((tagged) => tagged.value),\n );\n}\n","// ---------------------------------------------------------------------------\n// SubscribeHandler<S, M, Ctx> — the structural shape of one sub runner: one\n// entry of the `subscribe` table handed to `run`. Pulled out as a named type\n// so factory return types in this subpath can match the engine's `Subscribe`\n// entry without re-deriving it from the mapped type.\n//\n// The machine declares a Sub as data in `subs`; a factory in this subpath\n// returns the runner for that Sub's type, which the caller hands to `run`:\n//\n// subs: [{ type: \"audit-idle\", deps: (s) => (s.watching ? {} : null) }],\n// // …\n// run(machine, {\n// subscribe: {\n// \"audit-idle\": fromPort(\n// (ctx) => ctx.auditRuntime,\n// auditIdlePort,\n// (idle) => ({ type: \"audit:idle\", idle }),\n// ),\n// },\n// });\n//\n// The factory absorbs the lifecycle (subscribe + cleanup); the caller keeps\n// the intent (which port, which msg). A runner reads its data off `sub.deps`.\n// Strengthens invariant 9 (the surface for cross-cutting Sub topologies is\n// named, small, and exported from one subpath instead of redrawn at every\n// call site).\n// ---------------------------------------------------------------------------\n\nimport type { Dispose, Sub } from \"../index\";\n\n/**\n * A Sub runner: given the Sub, the `ctx` and a `dispatch`, it opens the\n * resource and returns a `Dispose`. Every `from*` factory returns one.\n */\nexport type SubscribeHandler<S extends Sub, M, Ctx> = (\n sub: S,\n ctx: Ctx,\n dispatch: (msg: M) => void,\n) => Dispose;\n\n// ---------------------------------------------------------------------------\n// dispatchIfPresent — the null-drop dispatch idiom every Sub factory in this\n// subpath (and the chrome/node factories) shares. A factory's callback maps a\n// platform event to `M | null`; `null` is the explicit \"drop this emission\"\n// signal — the event fired but the caller decided it isn't interesting given\n// current state, so no dispatch is made and the listener stays armed. Hoisted\n// here so the one drop-on-null rule lives once instead of re-inlined as\n// `const msg = fn(...); if (msg !== null) dispatch(msg);` at every listener.\n// ---------------------------------------------------------------------------\n\nexport function dispatchIfPresent<M>(\n dispatch: (msg: M) => void,\n msg: M | null,\n): void {\n if (msg !== null) dispatch(msg);\n}\n","// ---------------------------------------------------------------------------\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 in the `subscribe` table handed to `run`, 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 * runner 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 deps — `sub.deps.alarmName`,\n * `sub.deps.channel` — with NO cast at the substrate edge: the narrowing\n * happens in 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 Sub factory in `src/subs/`: supply the `msgFn` projection, get a\n * `(sub, ctx, dispatch) => cleanup` runner for the `subscribe` table `run`\n * takes.\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 reads `channelName` off the Sub's `deps` (not the closure)\n// so the same factory instance handles every BroadcastChannel Sub in a\n// machine — one Sub per channel, sharing the factory's lifecycle code.\n// A renamed channel is a changed deps value, so a new id: the engine\n// closes the old channel and opens the new one.\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 = { readonly channelName: string };\n\ndeclare const BroadcastChannel: MinimalBroadcastChannelCtor;\n\n/**\n * Build a Sub runner that listens on the `BroadcastChannel` named by\n * `sub.deps.channelName` and dispatches `msgFn(event, sub)` for each message.\n * Returning `null` from `msgFn` drops the event.\n */\nexport function fromBroadcastChannel<\n S extends Sub<string, BroadcastSubData>,\n M,\n>(\n msgFn: (event: MinimalMessageEvent, sub: S) => M | null,\n): SubscribeHandler<S, M, unknown> {\n return (sub, _ctx, dispatch) => {\n const channel = new BroadcastChannel(sub.deps.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 in the Sub's `deps` (`EventSourceSubData`). A changed url\n// is a changed deps value, so a new id: the engine closes the old\n// connection and opens one to the new url.\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 = { readonly url: string };\n\ndeclare const EventSource: MinimalEventSourceCtor;\n\n/**\n * Options for `fromEventSource`: map each server-sent message, error and open\n * event to a Msg, or to `null` to drop it.\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\n/**\n * Build a Sub runner that opens an `EventSource` on `sub.deps.url` and\n * dispatches a Msg for each server-sent event.\n */\nexport function fromEventSource<S extends Sub<string, EventSourceSubData>, M>(\n opts: EventSourceFactoryOpts<S, M>,\n): SubscribeHandler<S, M, unknown> {\n return (sub, _ctx, dispatch) => {\n const source = new EventSource(sub.deps.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 needs only a changed `deps` value (a restart); the factory\n// pulls the current 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\n/**\n * Build a Sub runner that listens for `eventName` on a DOM-style event target\n * and dispatches `msgFn(event, sub)` for each event.\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 runner that wraps a periodic timer is the\n// same five lines: `setInterval` the dispatch, return a `clearInterval`\n// cleanup. The factory absorbs both halves of the lifecycle (subscribe +\n// cleanup); the caller keeps the intent — which Sub type, which Msg to\n// dispatch.\n//\n// Timing data (`intervalMs`) lives in the Sub's `deps`, not in the closure.\n// The engine derives the Sub's id from its deps, so a `deps` that returns\n// `{ intervalMs: 30_000 }` in one state and `{ intervalMs: 60_000 }` later is\n// a new id: the engine stops the old interval and starts one at the new\n// period. An unchanged deps value leaves the running interval alone.\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 = { readonly intervalMs: number };\n\n/**\n * Build a Sub runner that dispatches `msgFn(sub)` every `sub.deps.intervalMs`\n * milliseconds while the Sub is on.\n */\nexport function fromInterval<S extends Sub<string, 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.deps.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 runner attaches to the publisher's Port.\n// The recurring 4-line shape at every such runner:\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 runner).\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.\n/**\n * Build a Sub runner that listens to a `Port` on another runtime and dispatches\n * `msgFn(value, sub)` for each value emitted. Use it to let one machine react\n * to another's announcements.\n */\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 in the Sub's `deps` (`WebSocketSubData`).\n// A changed url is a new id, so the engine tears this runner down (no\n// reconnect) and starts a fresh one against 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\n/**\n * Options for `fromReconnectingWebSocket`: the event-to-Msg mappers, the\n * backoff settings, and an injectable `connect` and `schedule` for tests.\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\n/**\n * Build a Sub runner that keeps a WebSocket to `sub.deps.wsUrl` open,\n * reconnecting with backoff when it drops.\n */\nexport function fromReconnectingWebSocket<\n S extends Sub<string, WebSocketSubData>,\n M,\n>(\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.deps.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// fromTimeout — universal `setTimeout`-shaped Sub factory.\n//\n// Same lifecycle shape as `fromInterval` minus the recurrence: the\n// scheduled callback fires once, the cleanup is `clearTimeout`. For the\n// common case — fire one Msg after a delay — the engine's built-in `timer`\n// Sub (`{ type: \"timer\", deps: (s) => cond ? { ms, msg } : null }`) needs no\n// runner at all; reach for this factory when the Msg is built from the Sub\n// rather than carried in its deps. Two observable consequences worth pinning:\n//\n// 1. The timer fires once per start. It does NOT re-arm while the Sub\n// stays on with the same deps — the engine leaves an unchanged Sub\n// running, and the handle is exhausted. A changed deps value (a new\n// `delayMs`, or any other field) is a new id, so the engine stops this\n// runner and starts a fresh one: that is how to re-arm.\n//\n// 2. Cleanup before the timer fires cancels the pending dispatch. This\n// is the \"cancel on state exit\" pattern: gate the Sub's `deps` on the\n// state where the timeout should be armed. When the state leaves it,\n// `deps` goes null, the engine stops the Sub, and the factory's\n// cleanup fires before the timer does.\n// ---------------------------------------------------------------------------\n\nimport type { Sub } from \"../index\";\nimport type { SubscribeHandler } from \"./types\";\n\ntype TimeoutSubData = { readonly delayMs: number };\n\n/**\n * Build a Sub runner that dispatches `msgFn(sub)` once, `sub.deps.delayMs`\n * milliseconds after the Sub turns on. Turning the Sub off first cancels it.\n */\nexport function fromTimeout<S extends Sub<string, TimeoutSubData>, M>(\n msgFn: (sub: S) => M,\n): SubscribeHandler<S, M, unknown> {\n return (sub, _ctx, dispatch) => {\n const handle = setTimeout(() => {\n dispatch(msgFn(sub));\n }, sub.deps.delayMs);\n return () => {\n clearTimeout(handle);\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. `wsUrl` lives in the Sub's `deps`\n// (`WebSocketSubData`) — a changed `wsUrl` is a new id, so the engine closes\n// the old socket and opens one to the new url, as with `fromEventSource`.\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\n/** The `deps` a WebSocket Sub carries: the `wsUrl` to connect to. */\nexport type WebSocketSubData = { readonly wsUrl: string };\n\ndeclare const WebSocket: MinimalWebSocketCtor;\n\n/**\n * Options for `fromWebSocket`: map each message, open, error and close event to\n * a Msg, or to `null` to drop it.\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\n/**\n * Build a Sub runner that opens a WebSocket to `sub.deps.wsUrl` and dispatches\n * a Msg for each socket event. It does not reconnect; use\n * `fromReconnectingWebSocket` for that.\n */\nexport function fromWebSocket<S extends Sub<string, WebSocketSubData>, M>(\n opts: WebSocketFactoryOpts<S, M>,\n): SubscribeHandler<S, M, unknown> {\n return (sub, _ctx, dispatch) => {\n const ws = new WebSocket(sub.deps.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 `.depKeyed(when)` entry for the machine's `subs` plus a `.subscribe`\n// runner for the `subscribe` table handed to `run`. The battery's `name` IS\n// its Sub type, so two managed resources in one machine are two Sub types,\n// each with its own runner — no router. The engine's reconcile does the rest:\n// - `when(state)` turns non-null → runner runs → `acquire(key, ctx)`.\n// - `when(state)` goes null → cleanup runs → `release(handle)`.\n// - the key changes (a new id) → 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 the reconcile pass, never hand-driven by a cell)\n// and invariant 6 (no silent looseness — every live resource is a running Sub,\n// visible in `replay(...).subs`).\n// ---------------------------------------------------------------------------\n\nimport type { DepKeyedSub, Dispose, Sub } from \"../index\";\nimport { structuralHash } from \"../index\";\n\n/**\n * The running Sub of a managed resource named `N`: its `deps` is the lifetime\n * key. The consumer's Sub union (`types.sub`) includes it directly.\n */\nexport type ManagedResourceSub<N extends string, TKey> = Sub<N, TKey>;\n\n/**\n * Options for `defineManagedResource`: the resource's `name`, how to `acquire`\n * it for a key, and how to `release` the handle.\n */\nexport interface DefineManagedResourceOpts<\n N extends string,\n TKey,\n Handle,\n Ctx,\n> {\n /**\n * A short, stable name — and the resource's Sub `type`. The machine's\n * `subscribe` table holds this battery's runner under it, so it must be\n * unique among the machine's Sub types.\n */\n readonly name: N;\n\n /**\n * Build the resource. Runs when the resource's `deps` turns non-null.\n * The returned Handle is the ONLY thing `release` receives — capture\n * everything teardown needs into it. If `acquire` throws, the engine does\n * NOT register the Sub and `release` is never called (no dangling\n * half-built resource).\n */\n readonly acquire: (key: TKey, ctx: Ctx) => Handle;\n\n /**\n * Tear the resource down. Runs when the key goes null (phase exited) or\n * changes. MANDATORY — the guaranteed teardown is the reason this battery\n * 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 `.depKeyed(when)` entry for the machine's\n * `subs`, the `.subscribe` runner for the `subscribe` table handed to `run`,\n * and a `.get(key)` accessor so Cmd handlers can reach the live Handle while\n * the resource is held.\n */\nexport interface ManagedResourceBattery<N extends string, TKey, Handle, Ctx> {\n /** The battery's Sub type — its `name`. */\n readonly type: N;\n\n /**\n * The `subs` entry. `when(state)` returns the resource's KEY when it should\n * be held, or `null` when it should be torn down. The key is the Sub's\n * `deps`, so the engine derives the id from it: an unchanged key leaves the\n * resource alone, a changed key releases the old one and acquires anew.\n * Plain JSON-compatible data only — the id hash throws on anything else.\n */\n readonly depKeyed: <S>(\n when: (state: S) => TKey | null,\n ) => DepKeyedSub<S, ManagedResourceSub<N, TKey>>;\n\n /**\n * The runner for this battery's Sub type — hand it to `run` as\n * `subscribe: { [battery.type]: battery.subscribe }`. Runs `acquire` on\n * start, holds the Handle in the battery's handle table, and returns a\n * cleanup that runs `release` (and forgets the Handle) on stop.\n */\n readonly subscribe: (\n sub: ManagedResourceSub<N, TKey>,\n ctx: Ctx,\n // The managed resource dispatches no Msgs — acquire/release are pure\n // lifecycle. `dispatch` is accepted to match the runner shape and is\n // intentionally unused.\n dispatch: (msg: never) => void,\n ) => Dispose;\n\n /**\n * Accessor for the live Handle, keyed on `key` (the author's key, never the\n * derived Sub id). Returns `undefined` when the resource is not currently\n * held — the caller decides whether that is an error. A Cmd handler wires\n * its `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\n/**\n * Build the battery. One call per managed resource at the machine's host\n * file.\n *\n * @example\n * ```ts\n * import {\n * defineMachine,\n * defineManagedResource,\n * type Interpret,\n * type ManagedResourceSub,\n * type Reducer,\n * } from \"@demlik/tea\";\n * import { run } from \"@demlik/tea/promise\";\n *\n * type RunId = string;\n * type State = { type: \"idle\" } | { type: \"running\"; runId: RunId };\n * type Msg = { type: \"start\"; runId: RunId } | { type: \"stop\" };\n * type Cmd = { type: \"start_graph\"; runId: RunId };\n * interface Ctx { readonly storage: Storage; readonly bucket: string }\n * declare class CheckpointSaver {\n * constructor(opts: { storage: Storage; bucket: string; runId: RunId });\n * save(step: string): Promise<void>;\n * clear(): Promise<void>;\n * }\n * declare const update: Reducer<State, Msg, Cmd>;\n * declare const ctx: Ctx;\n *\n * const checkpointStore = defineManagedResource<\"checkpoint\", 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 * const machine = defineMachine({\n * types: {\n * model: {} as State,\n * msg: {} as Msg,\n * cmd: {} as Cmd,\n * sub: {} as ManagedResourceSub<\"checkpoint\", RunId>,\n * ctx: {} as Ctx,\n * },\n * init: (loaded) => [loaded ?? { type: \"idle\" }, []],\n * update,\n * subs: [\n * checkpointStore.depKeyed((s: State) =>\n * s.type === \"running\" ? s.runId : null,\n * ),\n * ],\n * });\n *\n * // In interpret (read the live handle the reconciler holds):\n * const interpret: Interpret<Msg, Cmd, Ctx> = {\n * start_graph: async (cmd) => {\n * const saver = checkpointStore.get(cmd.runId); // same owner, no rebuild\n * await saver?.save(\"started\");\n * },\n * };\n *\n * // At run:\n * run(machine, { ctx, interpret, subscribe: { checkpoint: checkpointStore.subscribe } });\n * ```\n */\nexport function defineManagedResource<N extends string, TKey, Handle, Ctx>(\n opts: DefineManagedResourceOpts<N, TKey, Handle, Ctx>,\n): ManagedResourceBattery<N, 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 // Keyed by the author's key, because `get` is called with that key and the\n // caller cannot know the derived Sub id.\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 subscribe = (\n sub: ManagedResourceSub<N, TKey>,\n ctx: Ctx,\n _dispatch: (msg: never) => void,\n ): Dispose => {\n // Acquire. A throw here propagates out of the reconcile pass (the engine\n // records it and re-throws after the diff) and the Sub is NOT registered\n // — 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.deps, ctx);\n const k = keyString(sub.deps);\n held.set(k, handle);\n\n // Cleanup runs when the key goes null or changes. Forget the Handle first\n // so a concurrent `get` after teardown begins sees `undefined`, then\n // 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 type: opts.name,\n depKeyed: <S>(\n when: (state: S) => TKey | null,\n ): DepKeyedSub<S, ManagedResourceSub<N, TKey>> => ({\n type: opts.name,\n deps: when,\n }),\n subscribe,\n get: (key) => held.get(keyString(key)),\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 runner, 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// The seam's `name` IS its Sub type, so two seams in one machine (`hands` +\n// `worker`) are two Sub types with two runners — they never collide.\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, Dispose, Sub } from \"../index\";\nimport { structuralHash } 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 runner; cleanup runs when the seam's key\n * goes null or changes.\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 seam's key (`runId`, or whatever identity is on the phase) so\n * per-run transports can be addressed.\n */\nexport type TransportFactory<K> = (key: K) => Transport;\n\n/**\n * The running Sub of a seam named `N`: its `deps` is the seam key. The\n * consumer's Sub union (`types.sub`) includes it directly.\n */\nexport type TransportSub<N extends string, TKey> = Sub<N, TKey>;\n\n/**\n * Options for `fromTransport`: the seam's `name`, how to open the transport,\n * and how inbound values, outbound values and a dropped connection map to and\n * from Msgs.\n */\nexport interface FromTransportOpts<\n N extends string,\n TKey,\n Inbound,\n Outbound,\n M,\n Ctx,\n> {\n /**\n * A short, stable name for this seam — and its Sub `type`. The machine's\n * `subscribe` table holds the seam's runner under it, so it must be unique\n * among the machine's Sub types.\n */\n readonly name: N;\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 `.depKeyed(when)` entry for the machine's\n * `subs`, the `.subscribe` runner for the `subscribe` table handed to `run`,\n * and a `send(key, outbound)` helper the consumer's Cmd handler calls.\n */\nexport interface TransportBattery<N extends string, TKey, Outbound, M, Ctx> {\n /** The seam's Sub type — its `name`. */\n readonly type: N;\n\n /**\n * The `subs` entry. `when(state)` returns the seam's KEY when it should be\n * open, or `null` when it should be torn down. The key is the Sub's `deps`,\n * so the engine derives the id from it: an unchanged key leaves the seam\n * open, a changed key closes it and opens a fresh one. Plain\n * JSON-compatible data only — the id hash throws on anything else.\n */\n readonly depKeyed: <S>(\n when: (state: S) => TKey | null,\n ) => DepKeyedSub<S, TransportSub<N, TKey>>;\n\n /**\n * The runner for this seam's Sub type — hand it to `run` as\n * `subscribe: { [seam.type]: seam.subscribe }`. Wires inbound + close in\n * one shot and publishes the live transport to the outbound handle table.\n */\n readonly subscribe: (\n sub: TransportSub<N, TKey>,\n ctx: Ctx,\n dispatch: (msg: M) => void,\n ) => Dispose;\n\n /**\n * Outbound send, addressed by the seam's key (never the derived Sub id,\n * which the caller cannot know). The Cmd handler calls this; the battery's\n * handle table (built when the runner started) routes to the live\n * transport. If the seam is closed (no running Sub), the send is dropped\n * honestly (logged, not thrown — the reducer already moved past caring,\n * same shape as Rule 2 fire-and-forget Cmds).\n */\n readonly send: (key: TKey, outbound: Outbound) => void;\n}\n\n/**\n * Build the battery. One call per seam at the machine's host file.\n *\n * @example\n * ```ts\n * import {\n * defineMachine,\n * fromTransport,\n * type Interpret,\n * type Reducer,\n * type Transport,\n * type TransportSub,\n * } from \"@demlik/tea\";\n * import { run } from \"@demlik/tea/promise\";\n *\n * type RunId = string;\n * type HandsInbound = { kind: \"said\"; text: string };\n * type HandsOutbound = { kind: \"do\"; action: string };\n * type State = { type: \"idle\" } | { type: \"auditing\"; runId: RunId };\n * type Msg =\n * | { type: \"hands_said\"; runId: RunId; inbound: HandsInbound }\n * | { type: \"hands_lost\"; runId: RunId };\n * type Cmd = { type: \"send_hands\"; runId: RunId; outbound: HandsOutbound };\n * interface Ctx { readonly openHandsWs: (runId: RunId) => Transport }\n * declare function parseHandsInbound(json: unknown): HandsInbound | null;\n * declare const update: Reducer<State, Msg, Cmd>;\n * declare const ctx: Ctx;\n *\n * const handsSeam = fromTransport<\"hands\", 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 * const machine = defineMachine({\n * types: {\n * model: {} as State,\n * msg: {} as Msg,\n * cmd: {} as Cmd,\n * sub: {} as TransportSub<\"hands\", RunId>,\n * ctx: {} as Ctx,\n * },\n * init: (loaded) => [loaded ?? { type: \"idle\" }, []],\n * update,\n * subs: [\n * handsSeam.depKeyed((s: State) =>\n * s.type === \"auditing\" ? s.runId : null,\n * ),\n * ],\n * });\n *\n * // In interpret:\n * const interpret: Interpret<Msg, Cmd, Ctx> = {\n * send_hands: async (cmd) => { handsSeam.send(cmd.runId, cmd.outbound); },\n * };\n *\n * // At run:\n * run(machine, { ctx, interpret, subscribe: { hands: handsSeam.subscribe } });\n * ```\n */\nexport function fromTransport<\n N extends string,\n TKey,\n Inbound,\n Outbound,\n M,\n Ctx,\n>(\n opts: FromTransportOpts<N, TKey, Inbound, Outbound, M, Ctx>,\n): TransportBattery<N, 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 — and on the author's key, not the\n // derived Sub id, because `send` is addressed by that key.\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 subscribe = (\n sub: TransportSub<N, TKey>,\n ctx: Ctx,\n dispatch: (msg: M) => void,\n ): Dispose => {\n const key = sub.deps;\n // Open the transport. The battery owns the lifetime.\n const t = opts.openTransport(key, ctx);\n const k = keyString(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, key);\n if (parsed === null) return;\n const msg = opts.onInbound(parsed, 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(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 seam's key goes null or changes.\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 type: opts.name,\n\n depKeyed: <S>(\n when: (state: S) => TKey | null,\n ): DepKeyedSub<S, TransportSub<N, TKey>> => ({\n type: opts.name,\n deps: when,\n }),\n\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}\n"]}
@@ -1,4 +1,4 @@
1
- import { createResilientCall } from './chunk-MBQR3LFW.js';
1
+ import { createResilientCall } from './chunk-NFOGYDRS.js';
2
2
 
3
3
  // src/internal/paginate/paginator/index.ts
4
4
  var defaultPaginatorPolicy = {
@@ -166,5 +166,5 @@ function liftWalk(state, [slice, cmds]) {
166
166
  }
167
167
 
168
168
  export { PAGE_KEY, createPaginatedWalk, defaultPaginatorPolicy, drain, initPaginator, isDone, liftWalk, recordPage, resume, start };
169
- //# sourceMappingURL=chunk-4I2LCQKB.js.map
170
- //# sourceMappingURL=chunk-4I2LCQKB.js.map
169
+ //# sourceMappingURL=chunk-CPE5ZQXQ.js.map
170
+ //# sourceMappingURL=chunk-CPE5ZQXQ.js.map