@demlik/tea 0.15.0 → 0.16.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 (115) hide show
  1. package/README.md +30 -7
  2. package/dist/agent/index.d.ts +49 -37
  3. package/dist/agent/index.js +12 -8
  4. package/dist/chunk-4LPY67E4.js +18 -0
  5. package/dist/chunk-4LPY67E4.js.map +1 -0
  6. package/dist/chunk-5XU6ZJO2.js +533 -0
  7. package/dist/chunk-5XU6ZJO2.js.map +1 -0
  8. package/dist/{chunk-AYS5N6WY.js → chunk-6NG4V5SB.js} +62 -172
  9. package/dist/chunk-6NG4V5SB.js.map +1 -0
  10. package/dist/{chunk-3B5X7SBB.js → chunk-7MGXQZPW.js} +11 -9
  11. package/dist/chunk-7MGXQZPW.js.map +1 -0
  12. package/dist/{chunk-V46HSVCK.js → chunk-BCTEOVOU.js} +3 -3
  13. package/dist/{chunk-V46HSVCK.js.map → chunk-BCTEOVOU.js.map} +1 -1
  14. package/dist/chunk-CH5KTOEZ.js +710 -0
  15. package/dist/chunk-CH5KTOEZ.js.map +1 -0
  16. package/dist/chunk-CIHSHLQX.js +64 -0
  17. package/dist/chunk-CIHSHLQX.js.map +1 -0
  18. package/dist/{chunk-TCLBCERH.js → chunk-EHUDXAKC.js} +238 -212
  19. package/dist/chunk-EHUDXAKC.js.map +1 -0
  20. package/dist/chunk-GOX2CYIM.js +380 -0
  21. package/dist/chunk-GOX2CYIM.js.map +1 -0
  22. package/dist/{chunk-22POWGFA.js → chunk-IVJP3GKJ.js} +46 -20
  23. package/dist/chunk-IVJP3GKJ.js.map +1 -0
  24. package/dist/{chunk-QPEVCJ5Q.js → chunk-MVZS5FDM.js} +13 -6
  25. package/dist/chunk-MVZS5FDM.js.map +1 -0
  26. package/dist/{chunk-VTQI2NHP.js → chunk-MYNLY3HP.js} +9 -8
  27. package/dist/chunk-MYNLY3HP.js.map +1 -0
  28. package/dist/{chunk-Q5CQTBFA.js → chunk-NSIAORRY.js} +20 -30
  29. package/dist/chunk-NSIAORRY.js.map +1 -0
  30. package/dist/{chunk-MYXFNWAJ.js → chunk-QNGYQMZB.js} +4 -4
  31. package/dist/chunk-QNGYQMZB.js.map +1 -0
  32. package/dist/chunk-WT5PBPLB.js +98 -0
  33. package/dist/chunk-WT5PBPLB.js.map +1 -0
  34. package/dist/{core-DnzqWy6u.d.ts → core-BbgtiXdg.d.ts} +231 -139
  35. package/dist/do/index.d.ts +37 -33
  36. package/dist/do/index.js +21 -14
  37. package/dist/do/index.js.map +1 -1
  38. package/dist/{durable-effects-CWzA-o2B.d.ts → durable-effects-7MTK_jnj.d.ts} +1 -1
  39. package/dist/effect/index.d.ts +151 -0
  40. package/dist/effect/index.js +98 -0
  41. package/dist/effect/index.js.map +1 -0
  42. package/dist/extension/index.d.ts +6 -7
  43. package/dist/extension/index.js +3 -2
  44. package/dist/extension/index.js.map +1 -1
  45. package/dist/flow/index.d.ts +68 -59
  46. package/dist/flow/index.js +32 -27
  47. package/dist/flow/index.js.map +1 -1
  48. package/dist/idempotency/index.d.ts +6 -7
  49. package/dist/idempotency/index.js +4 -2
  50. package/dist/{index-CSyMYDAB.d.ts → index-7vV8xmsD.d.ts} +1 -1
  51. package/dist/{index-KXqTRJyb.d.ts → index-Bz5Rh9Ga.d.ts} +19 -96
  52. package/dist/index-CjFDUBeq.d.ts +207 -0
  53. package/dist/{index-CuNO9m8i.d.ts → index-CkFYA9Y6.d.ts} +14 -16
  54. package/dist/{index-CA_7I5g9.d.ts → index-Cou86-GX.d.ts} +137 -246
  55. package/dist/{index-DrIRb_Ch.d.ts → index-DU6JpQ8G.d.ts} +1 -1
  56. package/dist/{index-HSYgG2Yt.d.ts → index-Xufl55W8.d.ts} +247 -211
  57. package/dist/{index-C2DwkjlP.d.ts → index-_dOR3xqA.d.ts} +57 -45
  58. package/dist/index.d.ts +121 -385
  59. package/dist/index.js +2 -1
  60. package/dist/jev/index.d.ts +133 -97
  61. package/dist/jev/index.js +205 -161
  62. package/dist/jev/index.js.map +1 -1
  63. package/dist/{machine-DGlPWCWI.d.ts → machine-BQzTV8On.d.ts} +171 -288
  64. package/dist/machine-viz/index.d.ts +5 -5
  65. package/dist/machine-viz/index.js +8 -4
  66. package/dist/machine-viz/index.js.map +1 -1
  67. package/dist/mem/index.d.ts +3 -4
  68. package/dist/mem/index.js +2 -1
  69. package/dist/mem/index.js.map +1 -1
  70. package/dist/node/index.d.ts +119 -78
  71. package/dist/node/index.js +52 -38
  72. package/dist/node/index.js.map +1 -1
  73. package/dist/paginate/index.d.ts +27 -22
  74. package/dist/paginate/index.js +6 -4
  75. package/dist/parity/index.d.ts +4 -5
  76. package/dist/parity/index.js +3 -2
  77. package/dist/parity/index.js.map +1 -1
  78. package/dist/pbt/index.d.ts +2 -2
  79. package/dist/pbt/index.js +2 -1
  80. package/dist/pbt/index.js.map +1 -1
  81. package/dist/persistence/index.d.ts +6 -7
  82. package/dist/persistence/index.js +5 -3
  83. package/dist/promise/index.d.ts +186 -0
  84. package/dist/promise/index.js +5 -0
  85. package/dist/promise/index.js.map +1 -0
  86. package/dist/{protocol-D8VbH-VH.d.ts → protocol-ClRZBhHQ.d.ts} +7 -8
  87. package/dist/react/index.d.ts +36 -17
  88. package/dist/react/index.js +27 -4
  89. package/dist/react/index.js.map +1 -1
  90. package/dist/resilience/index.d.ts +63 -456
  91. package/dist/resilience/index.js +37 -483
  92. package/dist/resilience/index.js.map +1 -1
  93. package/dist/{runtime-types-CMF13BBh.d.ts → runtime-types-CiVpC0HN.d.ts} +131 -388
  94. package/dist/testing/index.d.ts +22 -16
  95. package/dist/testing/index.js +15 -7
  96. package/dist/testing/index.js.map +1 -1
  97. package/dist/timing/index.d.ts +25 -22
  98. package/dist/timing/index.js +4 -3
  99. package/dist/timing/index.js.map +1 -1
  100. package/dist/{types-BseNPIhr.d.ts → types-LxZxGYda.d.ts} +2 -2
  101. package/dist/work-queue/index.d.ts +5 -6
  102. package/package.json +18 -5
  103. package/dist/chunk-22POWGFA.js.map +0 -1
  104. package/dist/chunk-3B5X7SBB.js.map +0 -1
  105. package/dist/chunk-66PY5WYF.js +0 -1816
  106. package/dist/chunk-66PY5WYF.js.map +0 -1
  107. package/dist/chunk-AYS5N6WY.js.map +0 -1
  108. package/dist/chunk-L3TND7LA.js +0 -97
  109. package/dist/chunk-L3TND7LA.js.map +0 -1
  110. package/dist/chunk-MYXFNWAJ.js.map +0 -1
  111. package/dist/chunk-Q5CQTBFA.js.map +0 -1
  112. package/dist/chunk-QPEVCJ5Q.js.map +0 -1
  113. package/dist/chunk-TCLBCERH.js.map +0 -1
  114. package/dist/chunk-VTQI2NHP.js.map +0 -1
  115. package/dist/index-ZCMhnVKN.d.ts +0 -272
package/dist/index.d.ts CHANGED
@@ -1,10 +1,9 @@
1
- import { B as BootingRuntime, a as ScopedCtxArg, S as Store, O as OnError, b as Supervision, R as Runtime } from './runtime-types-CMF13BBh.js';
2
- export { c as Branded, C as CtxArg, D as DepToken, d as DepsOf, e as DispatchDiscardedError, f as DispatchSettle, g as DisposeTimeoutNotice, h as DriveFailedError, i as DriveStalledError, j as FencedRead, F as FencedStore, k as FoldRefusal, I as IdentityDropNotice, M as MachineTypes, l as OnReleaseError, P as PortNameCollisionError, m as ProvideFailedError, n as Provided, o as ProvidedCtx, p as Provider, q as ProviderCycleError, Q as QuiescenceTimeoutError, r as RuntimeDiscardNotice, s as RuntimeDiscardedError, t as RuntimeErrorContext, u as RuntimeErrorPhase, v as RuntimeRef, w as Schema, x as Scope, y as StoreConflictError, z as SubIdCollisionError, A as SupervisionStrategy, U as UnknownProviderError, _ as __resetPortRegistry, E as absurd, G as asReducer, H as defineMachine, J as definePort, K as dep, L as isFencedStore, N as isProvided, T as layer, V as noop, W as provide, X as replay, Y as schemaMigrate, Z as settle, $ as tryApplyCell, a0 as tryFoldMsgs, a1 as tryInterpret, a2 as value, a3 as wrapDetached } from './runtime-types-CMF13BBh.js';
3
- import { C as Cmd, S as Sub, M as Machine, R as RequiredCtx, P as Port, D as Dispose, o as DepKeyedSub, c as SubId } from './core-DnzqWy6u.js';
4
- export { A as AnyCmdDef, b as CmdDef, p as CmdInput, a as CmdOf, l as CmdValue, E as ErrOf, q as ErrorsOf, r as ExhaustiveTransitions, s as Identity, I as Interpret, h as InterpretDetached, t as MachineShape, k as MalformedResult, N as NoCellError, i as NoCtx, O as OkOf, g as PortEmitter, d as Reducer, m as Requirements, f as RequirementsOf, e as Settled, u as SettledErr, v as SettledOk, w as Subscribe, x as SyncReturn, j as Tagged, n as TaggedError, T as Transitions, U as UpdateForm, y as acceptedTypes, z as acceptsOf, B as applyCell, F as applyCellChecked, G as describeMachine, H as detectUpdateForm, J as foldMsgs, K as foldUpdates, L as formOf, Q as msgKeysOf, V as structuralHash, W as subId } from './core-DnzqWy6u.js';
5
- import { S as SubscribeHandler } from './types-BseNPIhr.js';
6
- import 'better-result';
7
- import 'zod';
1
+ import { B as BootingRuntime, R as Runtime } from './runtime-types-CiVpC0HN.js';
2
+ export { b as BootedRunHandle, d as Branded, C as CtxArg, D as DispatchDiscardedError, e as DispatchSettle, f as DisposeTimeoutNotice, g as DriveFailedError, h as DriveStalledError, E as EngineRun, i as FencedRead, F as FencedStore, j as FoldRefusal, I as IdentityDropNotice, M as MachineTypes, O as OnError, P as PortNameCollisionError, Q as QuiescenceTimeoutError, c as RunHandle, k as RunOptions, l as RuntimeDiscardNotice, m as RuntimeDiscardedError, n as RuntimeErrorContext, o as RuntimeErrorPhase, p as RuntimeRef, q as Schema, S as Store, r as StoreConflictError, a as Supervision, s as SupervisionStrategy, t as TelemetryEvent, T as TelemetrySink, _ as __resetPortRegistry, u as absurd, v as asReducer, w as defineMachine, x as definePort, y as isFencedStore, z as noop, A as replay, G as schemaMigrate, H as tryApplyCell, J as tryFoldMsgs, K as tryInterpret, L as wrapDetached } from './runtime-types-CiVpC0HN.js';
3
+ import { C as Cmd, S as Sub, M as Machine, P as Port, o as DepKeyedSub, h as Dispose } from './core-BbgtiXdg.js';
4
+ export { A as AnyCmdDef, w as AsyncSchemaError, a as BuiltinSub, B as BuiltinSubType, b as CmdDef, x as CmdInput, c as CmdOf, v as CmdValue, D as DeclaredErrorsOf, u as ErrOf, E as ErrorsOf, y as ExhaustiveTransitions, z as Identity, p as Interpret, F as InterpretArg, G as InterpretCell, I as InterpretDetached, H as MachineShape, r as MalformedResult, N as NoCellError, J as NoCtx, t as OkOf, O as OkOfCmd, l as Outcome, K as OutcomeContractError, L as OutcomeHelpers, m as PortEmitter, i as Reducer, R as RunHandlers, j as Settled, f as SettledErr, e as SettledOk, n as SubId, d as Subscribe, Q as SubscribeArg, V as SyncReturn, s as Tagged, g as TaggedError, T as TimerDeps, q as TimerSub, k as Transitions, X as UndeclaredFailureError, U as UpdateForm, W as Wired, Y as acceptedTypes, Z as acceptsOf, _ as applyCell, $ as applyCellChecked, a0 as describeMachine, a1 as detectUpdateForm, a2 as foldMsgs, a3 as foldUpdates, a4 as formOf, a5 as msgKeysOf, a6 as structuralHash, a7 as subId, a8 as subIdOf } from './core-BbgtiXdg.js';
5
+ import { S as SubscribeHandler } from './types-LxZxGYda.js';
6
+ import '@standard-schema/spec';
8
7
 
9
8
  /**
10
9
  * @packageDocumentation
@@ -23,6 +22,10 @@ import 'zod';
23
22
  * (Effect's types, never its runtime values) between them settle why this is
24
23
  * forty lines here rather than a dependency.
25
24
  *
25
+ * `docs/how-to/compose-two-battery-slices.md` wires both end to end over a
26
+ * two-battery door, and its `ts` blocks are compiled and run as
27
+ * `src/docs/how-to/compose-slices.test.ts`.
28
+ *
26
29
  * Both are pure: no clock, no RNG, no storage, nothing captured. A `liftSlice`
27
30
  * result is a fresh record and a Cmd array, so ADR 0014's journal/hash promise
28
31
  * over a reducer's return value is untouched.
@@ -241,177 +244,6 @@ declare function reconcile<S, M extends {
241
244
  type: string;
242
245
  }, C extends Cmd, U extends Sub, Ctx>(machine: Machine<S, M, C, U, Ctx>, authoritativeState: S, lastAppliedSeq: Ack | Seq, pending: readonly SeqTagged<M>[]): S;
243
246
 
244
- /**
245
- * @demlik/tea runtime — `run`: boot + the serial dispatch loop. The pure helpers
246
- * that compose against a machine without running it (`replay`, `tryInterpret`)
247
- * live in `./runtime-types`.
248
- */
249
-
250
- declare function run<S, M extends {
251
- type: string;
252
- }, C extends Cmd, U extends Sub, Ctx, E extends {
253
- type: string;
254
- } = never>(machine: Machine<S, M, C, U, Ctx>, opts: ScopedCtxArg<Ctx & RequiredCtx<C>> & {
255
- store?: Store<S>;
256
- onError?: OnError;
257
- /**
258
- * The clock that stamps `at` on a `Cmd.define`d effect's settled Msg at
259
- * the interpret edge. Defaults to `Date.now`. Inject a fixed one for a
260
- * deterministic run; a `replay` log carries its own `at`s and never reads
261
- * this.
262
- */
263
- clock?: () => number;
264
- /**
265
- * The SEMANTIC event projector. Maps one APPLIED transition `(msg, state)`
266
- * to zero-or-more public events of `E`; `[]` skips the transition. Maps the
267
- * machine's PRIVATE Msg vocabulary to NAMED events — the private names never
268
- * reach `on`'s `E` surface. Omit → `E = never` and `on` is uncallable. PURE.
269
- */
270
- events?: (msg: M, state: S) => readonly E[];
271
- /**
272
- * Declared policy for a reducer (`update`) throw. Always surfaced via
273
- * `onError` (`phase: "reduce"`); the strategy decides what the runtime does
274
- * next. Defaults to `"stop"`. See `Supervision`.
275
- */
276
- supervision?: Supervision<S, M>;
277
- /**
278
- * The run-terminality predicate — makes the run's outcome first-class. Fed to
279
- * `Runtime.result()` and `Runtime.done()`. PURE. Omit → never terminal.
280
- */
281
- terminal?: (state: S) => boolean;
282
- /**
283
- * How long `stop()` waits for teardown work that returned a Promise (an
284
- * async `release` in `defineManagedResource`, an async Sub cleanup) before
285
- * giving up on it. Defaults to 5_000ms.
286
- *
287
- * `stop()` awaits those disposals so a host doing
288
- * `await runtime.stop(); env.evict()` cannot drop the isolate mid-release —
289
- * the leak the managed-resource battery exists to prevent, relocated to
290
- * shutdown. The bound is what keeps a release that never settles from
291
- * hanging the host: on expiry `stop()` reports a `DisposeTimeoutNotice`
292
- * (warn-only, like every `RuntimeDiscardNotice`) and resolves anyway,
293
- * because `stop()` resolving is a contract.
294
- */
295
- disposeTimeoutMs?: number;
296
- /**
297
- * Iteration cap for `idle()`'s quiescence wait. Defaults to 100_000. Test
298
- * seam only. Production code must not set it.
299
- *
300
- * @internal test-only
301
- */
302
- __idleCap?: number;
303
- }): BootingRuntime<S, M, E>;
304
- /**
305
- * The outside-in stop, as a PAIR. A signal is only cancellation if something can
306
- * turn "aborted" into a transition this machine understands, and the kernel has
307
- * no built-in cancel Msg any more than it has a built-in terminal set — so the
308
- * `signal` and the `cancel` that reads it are one option, present together or
309
- * absent together. A discriminated union rather than two optional fields, on the
310
- * same reasoning as `AgentCompactionConfig`: a signal with no `cancel` is a stop
311
- * button wired to nothing, and that is a state worth making unrepresentable
312
- * rather than checking for.
313
- */
314
- type DriveCancellation<S, M> = {
315
- readonly signal?: undefined;
316
- readonly cancel?: undefined;
317
- } | {
318
- /**
319
- * Abort the run through this signal. On abort the drive dispatches
320
- * `cancel`'s Msg and resolves on the State that lands — it does NOT reject,
321
- * and a cancellation is never a `DriveFailedError`. Already aborted when
322
- * the drive is called → the cancel Msg goes in place of `start`, so the
323
- * run ends without the start's effects ever firing.
324
- */
325
- readonly signal: AbortSignal;
326
- /**
327
- * The Msg that settles this machine as cancelled, chosen off the current
328
- * State exactly as `start` is. PURE. It must land a State `isTerminal`
329
- * holds for — the drive resolves on the terminal predicate, not on the
330
- * abort — and that State should be DURABLE, or a reload resumes the run
331
- * its caller stopped.
332
- */
333
- readonly cancel: (state: S) => M;
334
- };
335
- /** Options for `driveToDone`. */
336
- type DriveToDoneOptions<S, M extends {
337
- type: string;
338
- } = never> = DriveCancellation<S, M> & {
339
- /**
340
- * Marks a terminal State as a FAILURE. A State this holds for ends the drive
341
- * like any terminal one — the runtime is stopped — but the drive REJECTS with
342
- * `DriveFailedError` carrying it instead of resolving. A failed State is
343
- * terminal by definition; it need not also satisfy `isTerminal`. Omit → the
344
- * drive never rejects on State, only on a runtime error.
345
- */
346
- readonly failed?: (state: S) => boolean;
347
- };
348
- /**
349
- * Drive a machine from `start` to its terminal State in one call, then tear the
350
- * runtime down. The one-shot shape every "run this machine to done" test and
351
- * caller boundary otherwise hand-wires as six steps — `await ready`, `observe`,
352
- * park a promise, `dispatch(start)`, `getState()`, `stop()` — with the observer
353
- * leak and the forgotten `stop` those six steps invite.
354
- *
355
- * Takes the handle `run()` returns (a `Runtime` extends it, so a booted one is
356
- * accepted too), awaits `ready`, dispatches `start`, and resolves with the first
357
- * State for which `isTerminal` holds. A machine that boots already terminal (a
358
- * rehydrated finished run) resolves on its boot State and `start` is never
359
- * dispatched — a finished run has nothing to set in motion. `start` may be a
360
- * function of the boot State instead of a Msg: a machine rehydrated MID-run
361
- * needs its resume Msg, not its start Msg, and only the boot State says which
362
- * (the agent's `agent_boot` vs `agent_start` is the case in point). The
363
- * observer is detached and `stop()` awaited on EVERY exit: resolve, `failed`,
364
- * a stall, a boot or dispatch throw, the quiescence cap.
365
- *
366
- * Rejections are typed, never silent:
367
- * - `DriveFailedError<S>` when `opts.failed` marks the final State — the State
368
- * rides on `error.state`.
369
- * - `DriveStalledError<S>` when `start`'s follow-up chain quiesces on a State
370
- * that is neither terminal nor `failed` and the runtime has no live Sub and
371
- * no Cmd in flight — nothing inside it can deliver another transition, so
372
- * waiting would be a hang with the runtime leaked. The stalled State rides
373
- * on `error.state`. A machine that CAN still move is not stalled: a live Sub
374
- * (manual or dep-keyed) that delivers the terminal Msg after the dispatch
375
- * quiesces keeps the drive waiting, and it resolves on that State. What the
376
- * runtime cannot see — a dispatch from outside it after quiescence — does
377
- * not count as live; a machine that depends on one declares it as a Sub.
378
- * - `QuiescenceTimeoutError` when `start`'s follow-up chain never settles —
379
- * the SAME cap `dispatch` and `idle()` already reject on (invariant 6), not a
380
- * second clock. A livelocking machine surfaces as its own failure class.
381
- * - the boot error, or whatever `dispatch(start)` rejects with, otherwise.
382
- *
383
- * `isTerminal` is caller-supplied, exactly as `run()`'s `terminal` option is —
384
- * the kernel has no built-in terminal-set concept and this does not add one.
385
- *
386
- * Cancellation is the same story: `{ signal, cancel }` stops the drive from
387
- * outside, and it settles the run rather than escaping it. On abort the drive
388
- * dispatches `cancel`'s Msg and RESOLVES on the terminal State that transition
389
- * lands — no rejection, no `DriveFailedError`, no `AbortError`. That is the whole
390
- * point of routing a stop through the Model: the outcome is durable, so a killed
391
- * process resumes reading a run that ended instead of restarting one someone
392
- * stopped. A cancel that THROWS is the one abort that rejects: it lands
393
- * no State, so its error is what the drive has to report — and that holds
394
- * whether the `cancel` function throws synchronously or the fold it produced
395
- * throws later, since both leave the run with no State to settle on. A State the
396
- * caller's `failed` predicate marks rejects on an abort exit exactly as it does
397
- * anywhere else. In-flight effects are not recalled — a promise cannot be cancelled —
398
- * so they settle to their own end and `stop()` drains them as it always did;
399
- * keeping their results off the public channels is the machine's own business.
400
- * Omit the pair and every path here behaves exactly as it did before.
401
- *
402
- * @param handle the handle `run(machine, opts)` returned.
403
- * @param start the Msg that sets the run in motion, or a function choosing
404
- * it off the boot State. PURE.
405
- * @param isTerminal the terminal predicate over the machine's State. PURE.
406
- * @param opts an optional `failed` predicate and an optional
407
- * `{ signal, cancel }` pair (see {@link DriveToDoneOptions}).
408
- */
409
- declare function driveToDone<S, M extends {
410
- type: string;
411
- }, E extends {
412
- type: string;
413
- } = never>(handle: BootingRuntime<S, M, E>, start: M | ((booted: S) => M), isTerminal: (state: S) => boolean, opts?: DriveToDoneOptions<S, M>): Promise<S>;
414
-
415
247
  /**
416
248
  * The imperative listener target, expressed as the `add`/`remove` pair the
417
249
  * substrate pairs into a reconciled resource. Both halves receive the SAME
@@ -445,15 +277,16 @@ interface ListenerTarget<Args extends readonly unknown[], S extends Sub, Ctx> {
445
277
  * `Args`, `S`, `Ctx` are fixed when the target is defined — they describe the
446
278
  * one listener topology this factory owns (the platform arg tuple, which Sub
447
279
  * shape it filters on, what context resolves it). `M` is supplied at the
448
- * subscribe cell because the same listener feeds different Msgs in different
280
+ * runner because the same listener feeds different Msgs in different
449
281
  * machines. Pinning `S` here (rather than at the `msgFn` step) lets
450
- * `add`/`remove` read the concrete Sub's fields — `sub.alarmName`,
451
- * `sub.channel` — with NO cast at the substrate edge: the narrowing happens in
452
- * author-land where the Sub shape is in scope.
282
+ * `add`/`remove` read the concrete Sub's deps — `sub.deps.alarmName`,
283
+ * `sub.deps.channel` — with NO cast at the substrate edge: the narrowing
284
+ * happens in author-land where the Sub shape is in scope.
453
285
  *
454
286
  * The returned function is a `SubscribeHandler` factory in the exact shape of
455
287
  * every other Sub factory in `src/subs/`: supply the `msgFn` projection, get a
456
- * `(sub, ctx, dispatch) => cleanup` handler assignable to `machine.subscribe`.
288
+ * `(sub, ctx, dispatch) => cleanup` runner for the `subscribe` table `run`
289
+ * takes.
457
290
  * The cleanup the author NEVER writes is `() => remove(listener, sub, ctx)`,
458
291
  * with `listener` the identical reference passed to `add`.
459
292
  */
@@ -482,26 +315,26 @@ interface MinimalWebSocket {
482
315
  }
483
316
 
484
317
  type BroadcastSubData = {
485
- channelName: string;
318
+ readonly channelName: string;
486
319
  };
487
- declare function fromBroadcastChannel<S extends Sub & BroadcastSubData, M>(msgFn: (event: MinimalMessageEvent, sub: S) => M | null): SubscribeHandler<S, M, unknown>;
320
+ declare function fromBroadcastChannel<S extends Sub<string, BroadcastSubData>, M>(msgFn: (event: MinimalMessageEvent, sub: S) => M | null): SubscribeHandler<S, M, unknown>;
488
321
 
489
322
  type EventSourceSubData = {
490
- url: string;
323
+ readonly url: string;
491
324
  };
492
325
  interface EventSourceFactoryOpts<S, M> {
493
326
  onMessage: (data: string, sub: S) => M | null;
494
327
  onError?: (event: MinimalEvent, sub: S) => M | null;
495
328
  onOpen?: (sub: S) => M | null;
496
329
  }
497
- declare function fromEventSource<S extends Sub & EventSourceSubData, M>(opts: EventSourceFactoryOpts<S, M>): SubscribeHandler<S, M, unknown>;
330
+ declare function fromEventSource<S extends Sub<string, EventSourceSubData>, M>(opts: EventSourceFactoryOpts<S, M>): SubscribeHandler<S, M, unknown>;
498
331
 
499
332
  declare function fromEventTarget<S extends Sub, M>(getTarget: () => MinimalEventTarget, eventName: string, msgFn: (event: MinimalEvent, sub: S) => M | null): SubscribeHandler<S, M, unknown>;
500
333
 
501
334
  type IntervalSubData = {
502
- intervalMs: number;
335
+ readonly intervalMs: number;
503
336
  };
504
- declare function fromInterval<S extends Sub & IntervalSubData, M>(msgFn: (sub: S) => M): SubscribeHandler<S, M, unknown>;
337
+ declare function fromInterval<S extends Sub<string, IntervalSubData>, M>(msgFn: (sub: S) => M): SubscribeHandler<S, M, unknown>;
505
338
 
506
339
  declare function fromPort<S extends Sub, M, Ctx, T, RS = unknown, RM extends {
507
340
  type: string;
@@ -510,7 +343,7 @@ declare function fromPort<S extends Sub, M, Ctx, T, RS = unknown, RM extends {
510
343
  }>(selectRuntime: (ctx: Ctx) => Runtime<RS, RM>, port: Port<T>, msgFn: (value: T, sub: S) => M | null): SubscribeHandler<S, M, Ctx>;
511
344
 
512
345
  type WebSocketSubData = {
513
- wsUrl: string;
346
+ readonly wsUrl: string;
514
347
  };
515
348
  interface WebSocketFactoryOpts<S, M> {
516
349
  onMessage: (data: unknown, sub: S) => M | null;
@@ -518,7 +351,7 @@ interface WebSocketFactoryOpts<S, M> {
518
351
  onError?: (event: MinimalEvent, sub: S) => M | null;
519
352
  onClose?: (code: number, reason: string, sub: S) => M | null;
520
353
  }
521
- declare function fromWebSocket<S extends Sub & WebSocketSubData, M>(opts: WebSocketFactoryOpts<S, M>): SubscribeHandler<S, M, unknown>;
354
+ declare function fromWebSocket<S extends Sub<string, WebSocketSubData>, M>(opts: WebSocketFactoryOpts<S, M>): SubscribeHandler<S, M, unknown>;
522
355
 
523
356
  /** Cancels a scheduled reconnect timer (the inverse of `schedule`). */
524
357
  type CancelTimer = () => void;
@@ -542,97 +375,37 @@ interface ReconnectingWebSocketFactoryOpts<S, M> {
542
375
  /** Inject the timer (defaults to `setTimeout`/`clearTimeout`); the test seam. */
543
376
  schedule?: (fn: () => void, ms: number) => CancelTimer;
544
377
  }
545
- declare function fromReconnectingWebSocket<S extends Sub & WebSocketSubData, M>(opts: ReconnectingWebSocketFactoryOpts<S, M>): SubscribeHandler<S, M, unknown>;
378
+ declare function fromReconnectingWebSocket<S extends Sub<string, WebSocketSubData>, M>(opts: ReconnectingWebSocketFactoryOpts<S, M>): SubscribeHandler<S, M, unknown>;
546
379
 
547
380
  type TimeoutSubData = {
548
- delayMs: number;
381
+ readonly delayMs: number;
549
382
  };
550
- declare function fromTimeout<S extends Sub & TimeoutSubData, M>(msgFn: (sub: S) => M): SubscribeHandler<S, M, unknown>;
383
+ declare function fromTimeout<S extends Sub<string, TimeoutSubData>, M>(msgFn: (sub: S) => M): SubscribeHandler<S, M, unknown>;
551
384
 
552
385
  /**
553
- * The Sub the battery builds. The consumer's `Sub` union includes
554
- * `ManagedResourceSub<TKey>` directly — the substrate reconciles by `id` and
555
- * `type` like any other Sub.
556
- */
557
- interface ManagedResourceSub<TKey> extends Sub<"managed_resource"> {
558
- /** The lifetime key. Stable while the resource should be held; a change
559
- * retires the old resource (release) and acquires a fresh one. */
560
- readonly key: TKey;
561
- }
562
- /**
563
- * A battery paired with a state-gate. Self-contained: it knows its `name`
564
- * (for the unique-name assert), how to derive its live Sub from a state
565
- * (`subFor`), how to handle its own `subscribe`, and whether it `owns` a given
566
- * Sub id.
567
- *
568
- * Generic in the consumer state `S`, the shared lifetime key `TKey`, and the
569
- * shared `Ctx`. `Handle` is the ONLY thing erased — each battery's handle type
570
- * differs (a checkpoint saver vs. a tool-call bridge) but never crosses this
571
- * interface, so a `combineManagedResources` array holds entries of
572
- * heterogeneous handle types with no cast. The sub type stays
573
- * `ManagedResourceSub<TKey>` end to end, so the substrate's narrowed Sub flows
574
- * straight in.
386
+ * The running Sub of a managed resource named `N`: its `deps` is the lifetime
387
+ * key. The consumer's Sub union (`types.sub`) includes it directly.
575
388
  */
576
- interface GatedManagedResource<S, TKey, Ctx> {
577
- /** The owning battery's `name`. The combinator asserts these are unique. */
578
- readonly name: string;
579
- /**
580
- * Derive this resource's live Sub for a given state: the battery's
581
- * `.sub(key)` when `when(state)` returns a key, or `null` when it returns
582
- * `null` (the resource should be torn down in that state).
583
- */
584
- readonly subFor: (state: S) => ManagedResourceSub<TKey> | null;
389
+ type ManagedResourceSub<N extends string, TKey> = Sub<N, TKey>;
390
+ interface DefineManagedResourceOpts<N extends string, TKey, Handle, Ctx> {
585
391
  /**
586
- * This battery's own `.subscribe`. The combinator routes an incoming
587
- * `managed_resource` Sub to the entry whose `owns(sub)` is true, then calls
588
- * this.
392
+ * A short, stable name — and the resource's Sub `type`. The machine's
393
+ * `subscribe` table holds this battery's runner under it, so it must be
394
+ * unique among the machine's Sub types.
589
395
  */
590
- readonly subscribe: (sub: ManagedResourceSub<TKey>, ctx: Ctx, dispatch: (msg: never) => void) => Dispose;
396
+ readonly name: N;
591
397
  /**
592
- * Does this entry own the given Sub? `sub.id === subIdFor(sub.key)` encodes
593
- * the battery `name`, so a match is unambiguous.
594
- */
595
- readonly owns: (sub: ManagedResourceSub<TKey>) => boolean;
596
- }
597
- /**
598
- * What `combineManagedResources` returns: the single managed-resource
599
- * `subscribe` handler for the machine's `subscribe` record, and a `subs(state)`
600
- * builder for `subscriptions(state)`.
601
- */
602
- interface CombinedManagedResources<S, TKey, Ctx> {
603
- /**
604
- * Map every entry through its `when(state)`, drop the `null`s, and build the
605
- * live Subs. Spread into `subscriptions(state)`. The derived list and the
606
- * `subscribe` routing share one source — the entries — so they never drift.
607
- */
608
- readonly subs: (state: S) => ManagedResourceSub<TKey>[];
609
- /**
610
- * The SINGLE `subscribe.managed_resource` handler. Routes an incoming Sub to
611
- * the owning battery by matching `sub.id === battery.subIdFor(sub.key)` (the
612
- * id encodes the battery `name`). Throws loudly on an unrouted id — a wiring
613
- * bug, never a silent no-op resource.
614
- */
615
- readonly subscribe: (sub: ManagedResourceSub<TKey>, ctx: Ctx, dispatch: (msg: never) => void) => Dispose;
616
- }
617
- interface DefineManagedResourceOpts<TKey, Handle, Ctx> {
618
- /**
619
- * A short, stable name. The Sub id is `managed:${name}:${stringify(key)}`,
620
- * so two managed resources in one machine never collide and neither
621
- * collides with a `fromTransport` Sub keyed on the same identity.
622
- */
623
- readonly name: string;
624
- /**
625
- * Build the resource. Runs when the Sub appears in `subscriptions(state)`.
398
+ * Build the resource. Runs when the resource's `deps` turns non-null.
626
399
  * The returned Handle is the ONLY thing `release` receives — capture
627
- * everything teardown needs into it. If `acquire` throws, the substrate's
628
- * reconcile does NOT register the Sub and `release` is never called (no
629
- * dangling half-built resource).
400
+ * everything teardown needs into it. If `acquire` throws, the engine does
401
+ * NOT register the Sub and `release` is never called (no dangling
402
+ * half-built resource).
630
403
  */
631
404
  readonly acquire: (key: TKey, ctx: Ctx) => Handle;
632
405
  /**
633
- * Tear the resource down. Runs when the Sub leaves the desired set (phase
634
- * exited) or when its key changes. MANDATORY — the guaranteed teardown is
635
- * the reason this battery exists.
406
+ * Tear the resource down. Runs when the key goes null (phase exited) or
407
+ * changes. MANDATORY — the guaranteed teardown is the reason this battery
408
+ * exists.
636
409
  *
637
410
  * May be sync or async, and an async one is really awaited: the promise is
638
411
  * returned to the substrate, which tracks it and drains it inside `stop()`
@@ -646,78 +419,44 @@ interface DefineManagedResourceOpts<TKey, Handle, Ctx> {
646
419
  readonly release: (handle: Handle) => void | Promise<void>;
647
420
  }
648
421
  /**
649
- * What the battery returns: a `.sub(key)` builder for `subscriptions`, the
650
- * `.subscribe` handler for the machine's `subscribe` record, a `.get(key)`
651
- * accessor so Cmd handlers can reach the live Handle while the resource is
652
- * held, and a `.subIdFor(key)` for tests.
422
+ * What the battery returns: a `.depKeyed(when)` entry for the machine's
423
+ * `subs`, the `.subscribe` runner for the `subscribe` table handed to `run`,
424
+ * and a `.get(key)` accessor so Cmd handlers can reach the live Handle while
425
+ * the resource is held.
653
426
  */
654
- interface ManagedResourceBattery<TKey, Handle, Ctx> {
655
- /**
656
- * Build the Sub for `subscriptions(state)`. The consumer calls this from
657
- * inside their `subscriptions` cell for the phases where the resource
658
- * should be live.
659
- */
660
- readonly sub: (key: TKey) => ManagedResourceSub<TKey>;
661
- /**
662
- * Pair this battery with a state-gate, producing a self-contained entry for
663
- * `combineManagedResources`. `when(state)` returns the resource's KEY when
664
- * it should be live in that state, or `null` when it should be torn down.
665
- *
666
- * Folding "is it active?" and "what key?" into ONE declaration at the
667
- * battery's own definition site is the point: a central `subscriptions(state)`
668
- * list can silently forget to gate a new phase, but a per-resource gate
669
- * travels with the resource. The combinator derives BOTH the active-sub list
670
- * and the subscribe routing from these entries, so the two can never disagree.
671
- *
672
- * Generic in the consumer's state `S` (each `.gated` call binds its own `S`).
673
- * The returned entry erases this battery's `Handle` — `combineManagedResources`
674
- * holds entries of heterogeneous handle types with no cast — while keeping
675
- * `TKey` and `Ctx` so the substrate's narrowed `ManagedResourceSub<TKey>`
676
- * flows straight into the entry's `subscribe`.
677
- */
678
- readonly gated: <S>(when: (state: S) => TKey | null) => GatedManagedResource<S, TKey, Ctx>;
427
+ interface ManagedResourceBattery<N extends string, TKey, Handle, Ctx> {
428
+ /** The battery's Sub type — its `name`. */
429
+ readonly type: N;
679
430
  /**
680
- * Dep-keyed entry for the machine's `subs` array. `when(state)` returns the
681
- * resource's KEY when it should be held, or `null` when it should be torn
682
- * down — the exact gate `.gated` takes, now producing a `DepKeyedSub` the
683
- * kernel folds directly instead of an entry the `combineManagedResources`
684
- * router multiplexes.
685
- *
686
- * This is the simplification the dep-keyed kernel unlocks: two managed
687
- * resources used to COLLIDE on the single `subscribe.managed_resource` key
688
- * (both subs share `type: "managed_resource"`), forcing
689
- * `combineManagedResources` to route by SubId. A dep-keyed Sub has its OWN
690
- * reconcile slot (its array position), so there is no shared `type` to route
691
- * — each resource is one `subs` entry, no combinator, no router. `acquire` /
692
- * `release` still ride the kernel's dispose-on-change / dispose-on-null
693
- * reconcile, now via the dep-keyed source's `Dispose` instead of the
694
- * `subscribe` cell's cleanup.
431
+ * The `subs` entry. `when(state)` returns the resource's KEY when it should
432
+ * be held, or `null` when it should be torn down. The key is the Sub's
433
+ * `deps`, so the engine derives the id from it: an unchanged key leaves the
434
+ * resource alone, a changed key releases the old one and acquires anew.
435
+ * Plain JSON-compatible data only — the id hash throws on anything else.
695
436
  */
696
- readonly depKeyed: <S>(when: (state: S) => TKey | null) => DepKeyedSub<S, never, Ctx>;
437
+ readonly depKeyed: <S>(when: (state: S) => TKey | null) => DepKeyedSub<S, ManagedResourceSub<N, TKey>>;
697
438
  /**
698
- * The `subscribe.managed_resource` handler. Drop straight into the
699
- * machine's `subscribe` record. Runs `acquire` on install, holds the
700
- * Handle in the battery's handle table, and returns a cleanup that runs
701
- * `release` (and forgets the Handle) on reconcile-out.
439
+ * The runner for this battery's Sub type — hand it to `run` as
440
+ * `subscribe: { [battery.type]: battery.subscribe }`. Runs `acquire` on
441
+ * start, holds the Handle in the battery's handle table, and returns a
442
+ * cleanup that runs `release` (and forgets the Handle) on stop.
702
443
  */
703
- readonly subscribe: (sub: ManagedResourceSub<TKey>, ctx: Ctx, dispatch: (msg: never) => void) => Dispose;
444
+ readonly subscribe: (sub: ManagedResourceSub<N, TKey>, ctx: Ctx, dispatch: (msg: never) => void) => Dispose;
704
445
  /**
705
- * Accessor for the live Handle, keyed on `key`. Returns `undefined` when
706
- * the resource is not currently held (no active sub for that key) — the
707
- * caller decides whether that is an error. A Cmd handler wires its
708
- * `ctx.getX(key)` through this, so the handler reads the SAME owner the
446
+ * Accessor for the live Handle, keyed on `key` (the author's key, never the
447
+ * derived Sub id). Returns `undefined` when the resource is not currently
448
+ * held — the caller decides whether that is an error. A Cmd handler wires
449
+ * its `ctx.getX(key)` through this, so the handler reads the SAME owner the
709
450
  * reconciler holds, never a hand-built duplicate.
710
451
  */
711
452
  readonly get: (key: TKey) => Handle | undefined;
712
- /** Pre-derived subId for `(key)` — handy for tests. */
713
- readonly subIdFor: (key: TKey) => SubId;
714
453
  }
715
454
  /**
716
455
  * Build the battery. One call per managed resource at the machine's host
717
456
  * file.
718
457
  *
719
458
  * @example
720
- * const checkpointStore = defineManagedResource<RunId, CheckpointSaver, Ctx>({
459
+ * const checkpointStore = defineManagedResource<"checkpoint", RunId, CheckpointSaver, Ctx>({
721
460
  * name: "checkpoint",
722
461
  * acquire: (runId, ctx) =>
723
462
  * new CheckpointSaver({ storage: ctx.storage, bucket: ctx.bucket, runId }),
@@ -725,9 +464,15 @@ interface ManagedResourceBattery<TKey, Handle, Ctx> {
725
464
  * });
726
465
  *
727
466
  * // In the machine:
728
- * subscriptions: (state) =>
729
- * state.type === "running" ? [checkpointStore.sub(state.runId)] : [],
730
- * subscribe: { managed_resource: checkpointStore.subscribe },
467
+ * types: { …, sub: {} as ManagedResourceSub<"checkpoint", RunId> },
468
+ * subs: [
469
+ * checkpointStore.depKeyed((s: State) =>
470
+ * s.type === "running" ? s.runId : null,
471
+ * ),
472
+ * ],
473
+ *
474
+ * // At run:
475
+ * run(machine, { interpret, subscribe: { checkpoint: checkpointStore.subscribe } });
731
476
  *
732
477
  * // In interpret (read the live handle the reconciler holds):
733
478
  * start_graph: async (cmd, ctx) => {
@@ -735,8 +480,7 @@ interface ManagedResourceBattery<TKey, Handle, Ctx> {
735
480
  * ...
736
481
  * }
737
482
  */
738
- declare function defineManagedResource<TKey, Handle, Ctx>(opts: DefineManagedResourceOpts<TKey, Handle, Ctx>): ManagedResourceBattery<TKey, Handle, Ctx>;
739
- declare function combineManagedResources<S, TKey, Ctx>(entries: readonly GatedManagedResource<S, TKey, Ctx>[]): CombinedManagedResources<S, TKey, Ctx>;
483
+ declare function defineManagedResource<N extends string, TKey, Handle, Ctx>(opts: DefineManagedResourceOpts<N, TKey, Handle, Ctx>): ManagedResourceBattery<N, TKey, Handle, Ctx>;
740
484
 
741
485
  /**
742
486
  * Duplex transport. The host (DO, worker thread, in-process bus) constructs
@@ -748,8 +492,8 @@ interface Transport {
748
492
  send(data: string): void;
749
493
  /**
750
494
  * Subscribe to inbound frames. Returns a cleanup that removes the listener.
751
- * The battery wires this in its Sub's subscribe handler; cleanup runs when
752
- * the phase exits (the substrate's reconcileSubs).
495
+ * The battery wires this in its runner; cleanup runs when the seam's key
496
+ * goes null or changes.
753
497
  */
754
498
  onMessage(listener: (data: string) => void): () => void;
755
499
  /**
@@ -762,26 +506,22 @@ interface Transport {
762
506
  }
763
507
  /**
764
508
  * Factory the consumer wires to a platform-specific transport. The battery
765
- * passes the runtime-derived key (`runId`, or whatever identity is on the
766
- * phase) so per-run transports can be addressed.
509
+ * passes the seam's key (`runId`, or whatever identity is on the phase) so
510
+ * per-run transports can be addressed.
767
511
  */
768
512
  type TransportFactory<K> = (key: K) => Transport;
769
513
  /**
770
- * The Sub the battery builds. The consumer's `Sub` union includes
771
- * `TransportSub<TKey>` directly — no wrapping needed, the substrate
772
- * reconciles by `id` and `type` like any other Sub.
514
+ * The running Sub of a seam named `N`: its `deps` is the seam key. The
515
+ * consumer's Sub union (`types.sub`) includes it directly.
773
516
  */
774
- interface TransportSub<TKey> extends Sub<"transport"> {
775
- /** The phase / run identity. Stable for the lifetime of the seam. */
776
- readonly key: TKey;
777
- }
778
- interface FromTransportOpts<TKey, Inbound, Outbound, M, Ctx> {
517
+ type TransportSub<N extends string, TKey> = Sub<N, TKey>;
518
+ interface FromTransportOpts<N extends string, TKey, Inbound, Outbound, M, Ctx> {
779
519
  /**
780
- * A short, stable name for this seam. The Sub id is derived from
781
- * `${name}:${stringify(key)}` so two seams in the same machine (e.g.
782
- * `hands` + `worker`) never collide.
520
+ * A short, stable name for this seam — and its Sub `type`. The machine's
521
+ * `subscribe` table holds the seam's runner under it, so it must be unique
522
+ * among the machine's Sub types.
783
523
  */
784
- readonly name: string;
524
+ readonly name: N;
785
525
  /**
786
526
  * Build the transport. The battery owns the lifetime; the factory is the
787
527
  * platform-specific constructor (`new WebSocket(...)` adapter on a DO,
@@ -813,53 +553,42 @@ interface FromTransportOpts<TKey, Inbound, Outbound, M, Ctx> {
813
553
  readonly serializeOutbound: (outbound: Outbound) => string;
814
554
  }
815
555
  /**
816
- * What the battery returns: a Sub builder (for `subscriptions`), the Sub's
817
- * `subscribe` handler (for `subscribe.transport`), and a `send(key, outbound)`
818
- * helper the consumer's Cmd handler calls. Plus a `subIdFor` factory the
819
- * consumer can use when keying related Subs (e.g. a deadline) to the same seam
820
- * identity.
556
+ * What the battery returns: a `.depKeyed(when)` entry for the machine's
557
+ * `subs`, the `.subscribe` runner for the `subscribe` table handed to `run`,
558
+ * and a `send(key, outbound)` helper the consumer's Cmd handler calls.
821
559
  */
822
- interface TransportBattery<TKey, Outbound, M, Ctx> {
560
+ interface TransportBattery<N extends string, TKey, Outbound, M, Ctx> {
561
+ /** The seam's Sub type — its `name`. */
562
+ readonly type: N;
823
563
  /**
824
- * Build the Sub for `subscriptions(state)`. The consumer calls this from
825
- * inside their `subscriptions` cell when the seam should be open.
564
+ * The `subs` entry. `when(state)` returns the seam's KEY when it should be
565
+ * open, or `null` when it should be torn down. The key is the Sub's `deps`,
566
+ * so the engine derives the id from it: an unchanged key leaves the seam
567
+ * open, a changed key closes it and opens a fresh one. Plain
568
+ * JSON-compatible data only — the id hash throws on anything else.
826
569
  */
827
- readonly sub: (key: TKey) => TransportSub<TKey>;
570
+ readonly depKeyed: <S>(when: (state: S) => TKey | null) => DepKeyedSub<S, TransportSub<N, TKey>>;
828
571
  /**
829
- * The `subscribe.transport` handler. Drop straight into the machine's
830
- * `subscribe` record — the battery wires inbound + close in one shot.
572
+ * The runner for this seam's Sub type — hand it to `run` as
573
+ * `subscribe: { [seam.type]: seam.subscribe }`. Wires inbound + close in
574
+ * one shot and publishes the live transport to the outbound handle table.
831
575
  */
832
- readonly subscribe: (sub: TransportSub<TKey>, ctx: Ctx, dispatch: (msg: M) => void) => () => void;
576
+ readonly subscribe: (sub: TransportSub<N, TKey>, ctx: Ctx, dispatch: (msg: M) => void) => Dispose;
833
577
  /**
834
- * Outbound send. The Cmd handler calls this; the battery's handle table
835
- * (built when subscribe ran) routes to the live transport. If the seam
836
- * is closed (no active sub), the send is dropped honestly (logged, not
837
- * thrown — the reducer already moved past caring, same shape as Rule 2
838
- * fire-and-forget Cmds).
578
+ * Outbound send, addressed by the seam's key (never the derived Sub id,
579
+ * which the caller cannot know). The Cmd handler calls this; the battery's
580
+ * handle table (built when the runner started) routes to the live
581
+ * transport. If the seam is closed (no running Sub), the send is dropped
582
+ * honestly (logged, not thrown — the reducer already moved past caring,
583
+ * same shape as Rule 2 fire-and-forget Cmds).
839
584
  */
840
585
  readonly send: (key: TKey, outbound: Outbound) => void;
841
- /** Pre-derived subId for `(key)` — handy for deadline Subs keyed to the same seam. */
842
- readonly subIdFor: (key: TKey) => SubId;
843
- /**
844
- * Pair this seam with a state-gate, producing a dep-keyed Sub for the
845
- * machine's `subs` array. `when(state)` returns the seam's KEY when it
846
- * should be open, or `null` when it should be torn down. The kernel derives
847
- * the id from `structuralHash(key)` and reconciles the seam's lifetime — so
848
- * the author neither lists this Sub in a central `subscriptions(state)` nor
849
- * writes its id. Replaces `subscriptions: (s) => active(s) ? [hands.sub(...)] : []`
850
- * + `subscribe: { transport: hands.subscribe }` with one `subs` entry.
851
- *
852
- * The `source` closes over the battery's own `subscribe` (open inbound +
853
- * close, wire the handle table) using the same `ctx` the kernel passes every
854
- * Sub source.
855
- */
856
- readonly depKeyed: <S>(when: (state: S) => TKey | null) => DepKeyedSub<S, M, Ctx>;
857
586
  }
858
587
  /**
859
588
  * Build the battery. One call per seam at the machine's host file.
860
589
  *
861
590
  * @example
862
- * const handsSeam = fromTransport<RunId, HandsInbound, HandsOutbound, Msg, Ctx>({
591
+ * const handsSeam = fromTransport<"hands", RunId, HandsInbound, HandsOutbound, Msg, Ctx>({
863
592
  * name: "hands",
864
593
  * openTransport: (runId, ctx) => ctx.openHandsWs(runId),
865
594
  * parseInbound: (raw) => parseHandsInbound(JSON.parse(raw)),
@@ -869,12 +598,19 @@ interface TransportBattery<TKey, Outbound, M, Ctx> {
869
598
  * });
870
599
  *
871
600
  * // In the machine:
872
- * subscriptions: (state) => state.type === "auditing" ? [handsSeam.sub(state.runId)] : [],
873
- * subscribe: { transport: handsSeam.subscribe },
601
+ * types: { …, sub: {} as TransportSub<"hands", RunId> },
602
+ * subs: [
603
+ * handsSeam.depKeyed((s: State) =>
604
+ * s.type === "auditing" ? s.runId : null,
605
+ * ),
606
+ * ],
607
+ *
608
+ * // At run:
609
+ * run(machine, { interpret, subscribe: { hands: handsSeam.subscribe } });
874
610
  *
875
611
  * // In interpret:
876
612
  * send_hands: async (cmd) => { handsSeam.send(cmd.runId, cmd.outbound); },
877
613
  */
878
- declare function fromTransport<TKey, Inbound, Outbound, M, Ctx>(opts: FromTransportOpts<TKey, Inbound, Outbound, M, Ctx>): TransportBattery<TKey, Outbound, M, Ctx>;
614
+ declare function fromTransport<N extends string, TKey, Inbound, Outbound, M, Ctx>(opts: FromTransportOpts<N, TKey, Inbound, Outbound, M, Ctx>): TransportBattery<N, TKey, Outbound, M, Ctx>;
879
615
 
880
- export { type Ack, type AckPartition, BootingRuntime, type CancelTimer, Cmd, type CombinedManagedResources, type DefineManagedResourceOpts, DepKeyedSub, Dispose, type DriveToDoneOptions, type EventSourceFactoryOpts, type FromTransportOpts, type GatedManagedResource, type HistoryTracker, type ListenerTarget, Machine, type ManagedResourceBattery, type ManagedResourceSub, NO_ACK, OnError, Port, type ReadStep, type ReconnectingWebSocketFactoryOpts, RequiredCtx, Runtime, ScopedCtxArg, type Seq, type SeqTagged, Store, Sub, SubId, SubscribeHandler, Supervision, type Transport, type TransportBattery, type TransportFactory, type TransportSub, type WebSocketFactoryOpts, type WebSocketSubData, ack, combineManagedResources, defineListener, defineManagedResource, driveToDone, fromBroadcastChannel, fromEventSource, fromEventTarget, fromInterval, fromPort, fromReconnectingWebSocket, fromTimeout, fromTransport, fromWebSocket, historyTracker, initAck, liftSlice, nextSeq, partitionByAck, readInOrder, reconcile, run, tagSeq };
616
+ export { type Ack, type AckPartition, BootingRuntime, type CancelTimer, Cmd, type DefineManagedResourceOpts, DepKeyedSub, Dispose, type EventSourceFactoryOpts, type FromTransportOpts, type HistoryTracker, type ListenerTarget, Machine, type ManagedResourceBattery, type ManagedResourceSub, NO_ACK, Port, type ReadStep, type ReconnectingWebSocketFactoryOpts, Runtime, type Seq, type SeqTagged, Sub, SubscribeHandler, type Transport, type TransportBattery, type TransportFactory, type TransportSub, type WebSocketFactoryOpts, type WebSocketSubData, ack, defineListener, defineManagedResource, fromBroadcastChannel, fromEventSource, fromEventTarget, fromInterval, fromPort, fromReconnectingWebSocket, fromTimeout, fromTransport, fromWebSocket, historyTracker, initAck, liftSlice, nextSeq, partitionByAck, readInOrder, reconcile, tagSeq };