@demlik/tea 0.15.0 → 0.17.0
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/README.md +30 -7
- package/dist/agent/index.d.ts +49 -37
- package/dist/agent/index.js +12 -8
- package/dist/chunk-4LPY67E4.js +18 -0
- package/dist/chunk-4LPY67E4.js.map +1 -0
- package/dist/chunk-4NTCPTNQ.js +64 -0
- package/dist/chunk-4NTCPTNQ.js.map +1 -0
- package/dist/{chunk-3B5X7SBB.js → chunk-4PXNZ5UY.js} +11 -9
- package/dist/chunk-4PXNZ5UY.js.map +1 -0
- package/dist/{chunk-AYS5N6WY.js → chunk-6XBEYZME.js} +62 -172
- package/dist/chunk-6XBEYZME.js.map +1 -0
- package/dist/chunk-C4U5D5HP.js +380 -0
- package/dist/chunk-C4U5D5HP.js.map +1 -0
- package/dist/{chunk-QPEVCJ5Q.js → chunk-DFUEAA6L.js} +13 -6
- package/dist/chunk-DFUEAA6L.js.map +1 -0
- package/dist/{chunk-22POWGFA.js → chunk-GWWCCSDH.js} +46 -20
- package/dist/chunk-GWWCCSDH.js.map +1 -0
- package/dist/chunk-K4ETBNKS.js +733 -0
- package/dist/chunk-K4ETBNKS.js.map +1 -0
- package/dist/chunk-KWNCM6XM.js +539 -0
- package/dist/chunk-KWNCM6XM.js.map +1 -0
- package/dist/{chunk-V46HSVCK.js → chunk-LYVOGOBC.js} +3 -3
- package/dist/{chunk-V46HSVCK.js.map → chunk-LYVOGOBC.js.map} +1 -1
- package/dist/{chunk-VTQI2NHP.js → chunk-OPH6Y4UT.js} +9 -8
- package/dist/chunk-OPH6Y4UT.js.map +1 -0
- package/dist/{chunk-Q5CQTBFA.js → chunk-OYR7ENKU.js} +20 -30
- package/dist/chunk-OYR7ENKU.js.map +1 -0
- package/dist/{chunk-MYXFNWAJ.js → chunk-QNGYQMZB.js} +4 -4
- package/dist/chunk-QNGYQMZB.js.map +1 -0
- package/dist/chunk-QWIPH4YH.js +98 -0
- package/dist/chunk-QWIPH4YH.js.map +1 -0
- package/dist/{chunk-TCLBCERH.js → chunk-XBZQT2TD.js} +238 -212
- package/dist/chunk-XBZQT2TD.js.map +1 -0
- package/dist/{core-DnzqWy6u.d.ts → core-rDbOaJTN.d.ts} +236 -140
- package/dist/do/index.d.ts +37 -33
- package/dist/do/index.js +21 -14
- package/dist/do/index.js.map +1 -1
- package/dist/{durable-effects-CWzA-o2B.d.ts → durable-effects-CNaw97Be.d.ts} +1 -1
- package/dist/effect/index.d.ts +151 -0
- package/dist/effect/index.js +98 -0
- package/dist/effect/index.js.map +1 -0
- package/dist/extension/index.d.ts +6 -7
- package/dist/extension/index.js +3 -2
- package/dist/extension/index.js.map +1 -1
- package/dist/flow/index.d.ts +68 -59
- package/dist/flow/index.js +32 -27
- package/dist/flow/index.js.map +1 -1
- package/dist/idempotency/index.d.ts +6 -7
- package/dist/idempotency/index.js +4 -2
- package/dist/{index-KXqTRJyb.d.ts → index-BOrsbdAf.d.ts} +19 -96
- package/dist/{index-DrIRb_Ch.d.ts → index-B_BxplWi.d.ts} +1 -1
- package/dist/index-CWmsXNdX.d.ts +207 -0
- package/dist/{index-C2DwkjlP.d.ts → index-Cberb5Rr.d.ts} +57 -45
- package/dist/{index-CSyMYDAB.d.ts → index-CcrKLtse.d.ts} +1 -1
- package/dist/{index-CuNO9m8i.d.ts → index-Cx0F2M34.d.ts} +14 -16
- package/dist/{index-HSYgG2Yt.d.ts → index-DaX_P35Q.d.ts} +247 -211
- package/dist/{index-CA_7I5g9.d.ts → index-q1PoukOZ.d.ts} +137 -246
- package/dist/index.d.ts +121 -385
- package/dist/index.js +2 -1
- package/dist/jev/index.d.ts +133 -97
- package/dist/jev/index.js +205 -161
- package/dist/jev/index.js.map +1 -1
- package/dist/{machine-DGlPWCWI.d.ts → machine-BcfCFMYF.d.ts} +171 -288
- package/dist/machine-viz/index.d.ts +5 -5
- package/dist/machine-viz/index.js +8 -4
- package/dist/machine-viz/index.js.map +1 -1
- package/dist/mem/index.d.ts +3 -4
- package/dist/mem/index.js +2 -1
- package/dist/mem/index.js.map +1 -1
- package/dist/node/index.d.ts +119 -78
- package/dist/node/index.js +52 -38
- package/dist/node/index.js.map +1 -1
- package/dist/paginate/index.d.ts +27 -22
- package/dist/paginate/index.js +6 -4
- package/dist/parity/index.d.ts +4 -5
- package/dist/parity/index.js +3 -2
- package/dist/parity/index.js.map +1 -1
- package/dist/pbt/index.d.ts +2 -2
- package/dist/pbt/index.js +2 -1
- package/dist/pbt/index.js.map +1 -1
- package/dist/persistence/index.d.ts +10 -12
- package/dist/persistence/index.js +5 -3
- package/dist/promise/index.d.ts +186 -0
- package/dist/promise/index.js +5 -0
- package/dist/promise/index.js.map +1 -0
- package/dist/{protocol-D8VbH-VH.d.ts → protocol-ClRZBhHQ.d.ts} +7 -8
- package/dist/react/index.d.ts +36 -17
- package/dist/react/index.js +27 -4
- package/dist/react/index.js.map +1 -1
- package/dist/resilience/index.d.ts +63 -456
- package/dist/resilience/index.js +37 -483
- package/dist/resilience/index.js.map +1 -1
- package/dist/{runtime-types-CMF13BBh.d.ts → runtime-types-B62Ucnxr.d.ts} +132 -389
- package/dist/testing/index.d.ts +33 -113
- package/dist/testing/index.js +39 -181
- package/dist/testing/index.js.map +1 -1
- package/dist/timing/index.d.ts +25 -22
- package/dist/timing/index.js +4 -3
- package/dist/timing/index.js.map +1 -1
- package/dist/{types-BseNPIhr.d.ts → types-DPm5YCDQ.d.ts} +2 -2
- package/dist/work-queue/index.d.ts +5 -6
- package/package.json +20 -7
- package/dist/chunk-22POWGFA.js.map +0 -1
- package/dist/chunk-3B5X7SBB.js.map +0 -1
- package/dist/chunk-66PY5WYF.js +0 -1816
- package/dist/chunk-66PY5WYF.js.map +0 -1
- package/dist/chunk-AYS5N6WY.js.map +0 -1
- package/dist/chunk-L3TND7LA.js +0 -97
- package/dist/chunk-L3TND7LA.js.map +0 -1
- package/dist/chunk-MYXFNWAJ.js.map +0 -1
- package/dist/chunk-Q5CQTBFA.js.map +0 -1
- package/dist/chunk-QPEVCJ5Q.js.map +0 -1
- package/dist/chunk-TCLBCERH.js.map +0 -1
- package/dist/chunk-VTQI2NHP.js.map +0 -1
- package/dist/index-ZCMhnVKN.d.ts +0 -272
package/dist/index.d.ts
CHANGED
|
@@ -1,10 +1,9 @@
|
|
|
1
|
-
import { B as BootingRuntime,
|
|
2
|
-
export {
|
|
3
|
-
import { C as Cmd, S as Sub, M as Machine,
|
|
4
|
-
export { A as AnyCmdDef, b as CmdDef,
|
|
5
|
-
import { S as SubscribeHandler } from './types-
|
|
6
|
-
import '
|
|
7
|
-
import 'zod';
|
|
1
|
+
import { B as BootingRuntime, R as Runtime } from './runtime-types-B62Ucnxr.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-B62Ucnxr.js';
|
|
3
|
+
import { C as Cmd, S as Sub, M as Machine, P as Port, n as DepKeyedSub, h as Dispose } from './core-rDbOaJTN.js';
|
|
4
|
+
export { A as AnyCmdDef, v as AsyncSchemaError, a as BuiltinSub, B as BuiltinSubType, b as CmdDef, w as CmdInput, c as CmdOf, u as CmdValue, D as DeclaredErrorsOf, t as ErrOf, E as ErrorsOf, x as ExhaustiveTransitions, y as Identity, o as Interpret, z as InterpretArg, F as InterpretCell, I as InterpretDetached, G as MachineShape, q as MalformedResult, N as NoCellError, J as NoCtx, s as OkOf, O as OkOfCmd, l as Outcome, K as OutcomeContractError, L as OutcomeHelpers, Q as PortEmitter, i as Reducer, R as RunHandlers, j as Settled, f as SettledErr, e as SettledOk, m as SubId, d as Subscribe, V as SubscribeArg, X as SyncReturn, r as Tagged, g as TaggedError, T as TimerDeps, p as TimerSub, k as Transitions, Y as UndeclaredFailureError, U as UpdateForm, W as Wired, Z as acceptedTypes, _ as acceptsOf, $ as applyCell, a0 as applyCellChecked, a1 as describeMachine, a2 as detectUpdateForm, a3 as foldMsgs, a4 as foldUpdates, a5 as formOf, a6 as msgKeysOf, a7 as structuralHash, a8 as subId, a9 as subIdOf } from './core-rDbOaJTN.js';
|
|
5
|
+
import { S as SubscribeHandler } from './types-DPm5YCDQ.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
|
-
*
|
|
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
|
|
451
|
-
* `sub.channel` — with NO cast at the substrate edge: the narrowing
|
|
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`
|
|
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
|
|
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
|
|
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
|
|
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
|
|
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
|
|
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
|
|
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
|
|
554
|
-
*
|
|
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
|
-
|
|
577
|
-
|
|
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
|
-
*
|
|
587
|
-
* `
|
|
588
|
-
*
|
|
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
|
|
396
|
+
readonly name: N;
|
|
591
397
|
/**
|
|
592
|
-
*
|
|
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
|
|
628
|
-
*
|
|
629
|
-
*
|
|
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
|
|
634
|
-
*
|
|
635
|
-
*
|
|
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 `.
|
|
650
|
-
* `.subscribe`
|
|
651
|
-
* accessor so Cmd handlers can reach the live Handle while
|
|
652
|
-
*
|
|
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
|
-
|
|
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
|
-
*
|
|
681
|
-
*
|
|
682
|
-
*
|
|
683
|
-
*
|
|
684
|
-
*
|
|
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,
|
|
437
|
+
readonly depKeyed: <S>(when: (state: S) => TKey | null) => DepKeyedSub<S, ManagedResourceSub<N, TKey>>;
|
|
697
438
|
/**
|
|
698
|
-
* The
|
|
699
|
-
*
|
|
700
|
-
* Handle in the battery's handle table, and returns a
|
|
701
|
-
* `release` (and forgets the Handle) on
|
|
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
|
|
706
|
-
*
|
|
707
|
-
* caller decides whether that is an error. A Cmd handler wires
|
|
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
|
-
*
|
|
729
|
-
*
|
|
730
|
-
*
|
|
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
|
|
752
|
-
*
|
|
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
|
|
766
|
-
*
|
|
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
|
|
771
|
-
*
|
|
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
|
-
|
|
775
|
-
|
|
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
|
|
781
|
-
*
|
|
782
|
-
*
|
|
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:
|
|
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
|
|
817
|
-
* `subscribe`
|
|
818
|
-
* helper the consumer's Cmd handler calls.
|
|
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
|
-
*
|
|
825
|
-
*
|
|
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
|
|
570
|
+
readonly depKeyed: <S>(when: (state: S) => TKey | null) => DepKeyedSub<S, TransportSub<N, TKey>>;
|
|
828
571
|
/**
|
|
829
|
-
* The
|
|
830
|
-
* `subscribe
|
|
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) =>
|
|
576
|
+
readonly subscribe: (sub: TransportSub<N, TKey>, ctx: Ctx, dispatch: (msg: M) => void) => Dispose;
|
|
833
577
|
/**
|
|
834
|
-
* Outbound send
|
|
835
|
-
*
|
|
836
|
-
*
|
|
837
|
-
*
|
|
838
|
-
*
|
|
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
|
-
*
|
|
873
|
-
*
|
|
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
|
|
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 };
|