@demlik/tea 0.14.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.
- package/README.md +30 -7
- package/dist/agent/index.d.ts +49 -36
- 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-5XU6ZJO2.js +533 -0
- package/dist/chunk-5XU6ZJO2.js.map +1 -0
- package/dist/{chunk-AQ6ZIMEA.js → chunk-6NG4V5SB.js} +89 -115
- package/dist/chunk-6NG4V5SB.js.map +1 -0
- package/dist/{chunk-HYDWG35Q.js → chunk-7MGXQZPW.js} +11 -9
- package/dist/chunk-7MGXQZPW.js.map +1 -0
- package/dist/{chunk-BLJ2JQSH.js → chunk-BCTEOVOU.js} +3 -3
- package/dist/{chunk-BLJ2JQSH.js.map → chunk-BCTEOVOU.js.map} +1 -1
- package/dist/chunk-CH5KTOEZ.js +710 -0
- package/dist/chunk-CH5KTOEZ.js.map +1 -0
- package/dist/chunk-CIHSHLQX.js +64 -0
- package/dist/chunk-CIHSHLQX.js.map +1 -0
- package/dist/{chunk-Z2L3LSNV.js → chunk-EHUDXAKC.js} +246 -210
- package/dist/chunk-EHUDXAKC.js.map +1 -0
- package/dist/chunk-GOX2CYIM.js +380 -0
- package/dist/chunk-GOX2CYIM.js.map +1 -0
- package/dist/chunk-IVJP3GKJ.js +104 -0
- package/dist/chunk-IVJP3GKJ.js.map +1 -0
- package/dist/{chunk-F7FMAWLA.js → chunk-MVZS5FDM.js} +13 -6
- package/dist/chunk-MVZS5FDM.js.map +1 -0
- package/dist/{chunk-LYLTKJWM.js → chunk-MYNLY3HP.js} +9 -8
- package/dist/chunk-MYNLY3HP.js.map +1 -0
- package/dist/{chunk-I5MQMJDW.js → chunk-NSIAORRY.js} +20 -30
- package/dist/chunk-NSIAORRY.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-WT5PBPLB.js +98 -0
- package/dist/chunk-WT5PBPLB.js.map +1 -0
- package/dist/{core-CTzMJ82M.d.ts → core-BbgtiXdg.d.ts} +231 -139
- package/dist/do/index.d.ts +37 -32
- package/dist/do/index.js +21 -14
- package/dist/do/index.js.map +1 -1
- package/dist/{durable-effects-B0-qGQDy.d.ts → durable-effects-7MTK_jnj.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 -70
- 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-DJWIER_3.d.ts → index-7vV8xmsD.d.ts} +1 -1
- package/dist/{index-NjrT3fL2.d.ts → index-Bz5Rh9Ga.d.ts} +19 -96
- package/dist/index-CjFDUBeq.d.ts +207 -0
- package/dist/{index-6A7dB8f6.d.ts → index-CkFYA9Y6.d.ts} +14 -16
- package/dist/{index-B5BZ-_cT.d.ts → index-Cou86-GX.d.ts} +189 -108
- package/dist/{index-TvRb1KVf.d.ts → index-DU6JpQ8G.d.ts} +1 -1
- package/dist/{index-q0hXFeZ0.d.ts → index-Xufl55W8.d.ts} +286 -215
- package/dist/{index-4vZzljCD.d.ts → index-_dOR3xqA.d.ts} +57 -45
- package/dist/index.d.ts +181 -385
- package/dist/index.js +2 -1
- package/dist/jev/index.d.ts +147 -99
- package/dist/jev/index.js +329 -233
- package/dist/jev/index.js.map +1 -1
- package/dist/{machine-CdIcN2Xm.d.ts → machine-BQzTV8On.d.ts} +171 -280
- 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 -33
- 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 +6 -7
- 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-ClRZBhHQ.d.ts +53 -0
- 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 -455
- package/dist/resilience/index.js +37 -483
- package/dist/resilience/index.js.map +1 -1
- package/dist/{runtime-types-LWDoi943.d.ts → runtime-types-CiVpC0HN.d.ts} +131 -388
- package/dist/testing/index.d.ts +165 -15
- package/dist/testing/index.js +104 -3
- 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-DP3f5NIl.d.ts → types-LxZxGYda.d.ts} +2 -2
- package/dist/work-queue/index.d.ts +5 -6
- package/package.json +18 -5
- package/dist/chunk-AQ6ZIMEA.js.map +0 -1
- package/dist/chunk-F2US5BW6.js +0 -72
- package/dist/chunk-F2US5BW6.js.map +0 -1
- package/dist/chunk-F7FMAWLA.js.map +0 -1
- package/dist/chunk-HUKZQX6U.js +0 -97
- package/dist/chunk-HUKZQX6U.js.map +0 -1
- package/dist/chunk-HYDWG35Q.js.map +0 -1
- package/dist/chunk-I5MQMJDW.js.map +0 -1
- package/dist/chunk-LYLTKJWM.js.map +0 -1
- package/dist/chunk-MYXFNWAJ.js.map +0 -1
- package/dist/chunk-RQULHGYH.js +0 -1804
- package/dist/chunk-RQULHGYH.js.map +0 -1
- package/dist/chunk-Z2L3LSNV.js.map +0 -1
- package/dist/index-DMCeMPMJ.d.ts +0 -272
package/dist/index.d.ts
CHANGED
|
@@ -1,10 +1,73 @@
|
|
|
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
|
-
|
|
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';
|
|
7
|
+
|
|
8
|
+
/**
|
|
9
|
+
* @packageDocumentation
|
|
10
|
+
* The composition seam — the layer between a battery and a door.
|
|
11
|
+
*
|
|
12
|
+
* A battery is pure state plus verbs over that state. A door holds several
|
|
13
|
+
* batteries side by side in one Model. Everything between those two facts was,
|
|
14
|
+
* until now, hand-written at every consumer: the one-key record rebuild that
|
|
15
|
+
* threads a verb's `[slice, cmds]` result back into the host state, and the
|
|
16
|
+
* statement order that decides which slice wins a derived read.
|
|
17
|
+
*
|
|
18
|
+
* Two helpers cover it, and neither is an optics kit. {@link liftSlice} is the
|
|
19
|
+
* record rebuild with the key checked by the type system; {@link readInOrder}
|
|
20
|
+
* turns a read's precedence from statement order into a value you can name,
|
|
21
|
+
* export and assert on. ADR 0001 (no off-the-shelf resilience) and ADR 0014
|
|
22
|
+
* (Effect's types, never its runtime values) between them settle why this is
|
|
23
|
+
* forty lines here rather than a dependency.
|
|
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
|
+
*
|
|
29
|
+
* Both are pure: no clock, no RNG, no storage, nothing captured. A `liftSlice`
|
|
30
|
+
* result is a fresh record and a Cmd array, so ADR 0014's journal/hash promise
|
|
31
|
+
* over a reducer's return value is untouched.
|
|
32
|
+
*/
|
|
33
|
+
/**
|
|
34
|
+
* Lift a battery verb's result into the host state that carries its slice.
|
|
35
|
+
*
|
|
36
|
+
* `liftSlice("resilience", state, knob.attempt(state.resilience, …))` is the
|
|
37
|
+
* `{ ...state, resilience: slice }` every consumer writes by hand, with the key
|
|
38
|
+
* checked: `key` must be a key of `S`, and the slice the verb returned must be
|
|
39
|
+
* assignable to the type that key carries — so naming the wrong field, or
|
|
40
|
+
* threading one battery's slice into another's key, is a compile error rather
|
|
41
|
+
* than a state shape nothing reads correctly.
|
|
42
|
+
*
|
|
43
|
+
* PURE — a fresh record and the verb's own Cmds, unchanged.
|
|
44
|
+
*/
|
|
45
|
+
declare function liftSlice<S, K extends keyof S, C>(key: K, state: S, [slice, cmds]: readonly [S[K], readonly C[]]): readonly [S, readonly C[]];
|
|
46
|
+
/**
|
|
47
|
+
* One step of a composed read: the slice it consults, under the name that slice
|
|
48
|
+
* goes by. `read` returns `undefined` for "this slice has nothing to say",
|
|
49
|
+
* which is what hands the read on to the next step.
|
|
50
|
+
*/
|
|
51
|
+
interface ReadStep<In, Out> {
|
|
52
|
+
/** The slice this step consults — `"cache"`, `"failed"`, `"inFlight"`. */
|
|
53
|
+
readonly name: string;
|
|
54
|
+
/** What this slice says about `input`, or `undefined` to defer. */
|
|
55
|
+
readonly read: (input: In) => Out | undefined;
|
|
56
|
+
}
|
|
57
|
+
/**
|
|
58
|
+
* Run a composed read through `order` and return the first answer any step
|
|
59
|
+
* gives, or `absent` when every one defers.
|
|
60
|
+
*
|
|
61
|
+
* The point is that `order` is a VALUE. A derived read over three battery
|
|
62
|
+
* slices otherwise states its precedence as the order of three `if` statements,
|
|
63
|
+
* where it is invisible to the type system, unnameable in a test and one
|
|
64
|
+
* innocent-looking reshuffle away from being wrong — which is exactly the
|
|
65
|
+
* defect that escaped `classify-batch`'s per-battery test suites. Written as
|
|
66
|
+
* data, the precedence can be exported, printed and asserted on directly.
|
|
67
|
+
*
|
|
68
|
+
* PURE — it evaluates `read` in order and stores nothing.
|
|
69
|
+
*/
|
|
70
|
+
declare function readInOrder<In, Out>(input: In, order: readonly ReadStep<In, Out>[], absent: Out): Out;
|
|
8
71
|
|
|
9
72
|
interface HistoryTracker<S, M extends {
|
|
10
73
|
type: string;
|
|
@@ -181,177 +244,6 @@ declare function reconcile<S, M extends {
|
|
|
181
244
|
type: string;
|
|
182
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;
|
|
183
246
|
|
|
184
|
-
/**
|
|
185
|
-
* @demlik/tea runtime — `run`: boot + the serial dispatch loop. The pure helpers
|
|
186
|
-
* that compose against a machine without running it (`replay`, `tryInterpret`)
|
|
187
|
-
* live in `./runtime-types`.
|
|
188
|
-
*/
|
|
189
|
-
|
|
190
|
-
declare function run<S, M extends {
|
|
191
|
-
type: string;
|
|
192
|
-
}, C extends Cmd, U extends Sub, Ctx, E extends {
|
|
193
|
-
type: string;
|
|
194
|
-
} = never>(machine: Machine<S, M, C, U, Ctx>, opts: ScopedCtxArg<Ctx & RequiredCtx<C>> & {
|
|
195
|
-
store?: Store<S>;
|
|
196
|
-
onError?: OnError;
|
|
197
|
-
/**
|
|
198
|
-
* The clock that stamps `at` on a `Cmd.define`d effect's settled Msg at
|
|
199
|
-
* the interpret edge. Defaults to `Date.now`. Inject a fixed one for a
|
|
200
|
-
* deterministic run; a `replay` log carries its own `at`s and never reads
|
|
201
|
-
* this.
|
|
202
|
-
*/
|
|
203
|
-
clock?: () => number;
|
|
204
|
-
/**
|
|
205
|
-
* The SEMANTIC event projector. Maps one APPLIED transition `(msg, state)`
|
|
206
|
-
* to zero-or-more public events of `E`; `[]` skips the transition. Maps the
|
|
207
|
-
* machine's PRIVATE Msg vocabulary to NAMED events — the private names never
|
|
208
|
-
* reach `on`'s `E` surface. Omit → `E = never` and `on` is uncallable. PURE.
|
|
209
|
-
*/
|
|
210
|
-
events?: (msg: M, state: S) => readonly E[];
|
|
211
|
-
/**
|
|
212
|
-
* Declared policy for a reducer (`update`) throw. Always surfaced via
|
|
213
|
-
* `onError` (`phase: "reduce"`); the strategy decides what the runtime does
|
|
214
|
-
* next. Defaults to `"stop"`. See `Supervision`.
|
|
215
|
-
*/
|
|
216
|
-
supervision?: Supervision<S, M>;
|
|
217
|
-
/**
|
|
218
|
-
* The run-terminality predicate — makes the run's outcome first-class. Fed to
|
|
219
|
-
* `Runtime.result()` and `Runtime.done()`. PURE. Omit → never terminal.
|
|
220
|
-
*/
|
|
221
|
-
terminal?: (state: S) => boolean;
|
|
222
|
-
/**
|
|
223
|
-
* How long `stop()` waits for teardown work that returned a Promise (an
|
|
224
|
-
* async `release` in `defineManagedResource`, an async Sub cleanup) before
|
|
225
|
-
* giving up on it. Defaults to 5_000ms.
|
|
226
|
-
*
|
|
227
|
-
* `stop()` awaits those disposals so a host doing
|
|
228
|
-
* `await runtime.stop(); env.evict()` cannot drop the isolate mid-release —
|
|
229
|
-
* the leak the managed-resource battery exists to prevent, relocated to
|
|
230
|
-
* shutdown. The bound is what keeps a release that never settles from
|
|
231
|
-
* hanging the host: on expiry `stop()` reports a `DisposeTimeoutNotice`
|
|
232
|
-
* (warn-only, like every `RuntimeDiscardNotice`) and resolves anyway,
|
|
233
|
-
* because `stop()` resolving is a contract.
|
|
234
|
-
*/
|
|
235
|
-
disposeTimeoutMs?: number;
|
|
236
|
-
/**
|
|
237
|
-
* Iteration cap for `idle()`'s quiescence wait. Defaults to 100_000. Test
|
|
238
|
-
* seam only. Production code must not set it.
|
|
239
|
-
*
|
|
240
|
-
* @internal test-only
|
|
241
|
-
*/
|
|
242
|
-
__idleCap?: number;
|
|
243
|
-
}): BootingRuntime<S, M, E>;
|
|
244
|
-
/**
|
|
245
|
-
* The outside-in stop, as a PAIR. A signal is only cancellation if something can
|
|
246
|
-
* turn "aborted" into a transition this machine understands, and the kernel has
|
|
247
|
-
* no built-in cancel Msg any more than it has a built-in terminal set — so the
|
|
248
|
-
* `signal` and the `cancel` that reads it are one option, present together or
|
|
249
|
-
* absent together. A discriminated union rather than two optional fields, on the
|
|
250
|
-
* same reasoning as `AgentCompactionConfig`: a signal with no `cancel` is a stop
|
|
251
|
-
* button wired to nothing, and that is a state worth making unrepresentable
|
|
252
|
-
* rather than checking for.
|
|
253
|
-
*/
|
|
254
|
-
type DriveCancellation<S, M> = {
|
|
255
|
-
readonly signal?: undefined;
|
|
256
|
-
readonly cancel?: undefined;
|
|
257
|
-
} | {
|
|
258
|
-
/**
|
|
259
|
-
* Abort the run through this signal. On abort the drive dispatches
|
|
260
|
-
* `cancel`'s Msg and resolves on the State that lands — it does NOT reject,
|
|
261
|
-
* and a cancellation is never a `DriveFailedError`. Already aborted when
|
|
262
|
-
* the drive is called → the cancel Msg goes in place of `start`, so the
|
|
263
|
-
* run ends without the start's effects ever firing.
|
|
264
|
-
*/
|
|
265
|
-
readonly signal: AbortSignal;
|
|
266
|
-
/**
|
|
267
|
-
* The Msg that settles this machine as cancelled, chosen off the current
|
|
268
|
-
* State exactly as `start` is. PURE. It must land a State `isTerminal`
|
|
269
|
-
* holds for — the drive resolves on the terminal predicate, not on the
|
|
270
|
-
* abort — and that State should be DURABLE, or a reload resumes the run
|
|
271
|
-
* its caller stopped.
|
|
272
|
-
*/
|
|
273
|
-
readonly cancel: (state: S) => M;
|
|
274
|
-
};
|
|
275
|
-
/** Options for `driveToDone`. */
|
|
276
|
-
type DriveToDoneOptions<S, M extends {
|
|
277
|
-
type: string;
|
|
278
|
-
} = never> = DriveCancellation<S, M> & {
|
|
279
|
-
/**
|
|
280
|
-
* Marks a terminal State as a FAILURE. A State this holds for ends the drive
|
|
281
|
-
* like any terminal one — the runtime is stopped — but the drive REJECTS with
|
|
282
|
-
* `DriveFailedError` carrying it instead of resolving. A failed State is
|
|
283
|
-
* terminal by definition; it need not also satisfy `isTerminal`. Omit → the
|
|
284
|
-
* drive never rejects on State, only on a runtime error.
|
|
285
|
-
*/
|
|
286
|
-
readonly failed?: (state: S) => boolean;
|
|
287
|
-
};
|
|
288
|
-
/**
|
|
289
|
-
* Drive a machine from `start` to its terminal State in one call, then tear the
|
|
290
|
-
* runtime down. The one-shot shape every "run this machine to done" test and
|
|
291
|
-
* caller boundary otherwise hand-wires as six steps — `await ready`, `observe`,
|
|
292
|
-
* park a promise, `dispatch(start)`, `getState()`, `stop()` — with the observer
|
|
293
|
-
* leak and the forgotten `stop` those six steps invite.
|
|
294
|
-
*
|
|
295
|
-
* Takes the handle `run()` returns (a `Runtime` extends it, so a booted one is
|
|
296
|
-
* accepted too), awaits `ready`, dispatches `start`, and resolves with the first
|
|
297
|
-
* State for which `isTerminal` holds. A machine that boots already terminal (a
|
|
298
|
-
* rehydrated finished run) resolves on its boot State and `start` is never
|
|
299
|
-
* dispatched — a finished run has nothing to set in motion. `start` may be a
|
|
300
|
-
* function of the boot State instead of a Msg: a machine rehydrated MID-run
|
|
301
|
-
* needs its resume Msg, not its start Msg, and only the boot State says which
|
|
302
|
-
* (the agent's `agent_boot` vs `agent_start` is the case in point). The
|
|
303
|
-
* observer is detached and `stop()` awaited on EVERY exit: resolve, `failed`,
|
|
304
|
-
* a stall, a boot or dispatch throw, the quiescence cap.
|
|
305
|
-
*
|
|
306
|
-
* Rejections are typed, never silent:
|
|
307
|
-
* - `DriveFailedError<S>` when `opts.failed` marks the final State — the State
|
|
308
|
-
* rides on `error.state`.
|
|
309
|
-
* - `DriveStalledError<S>` when `start`'s follow-up chain quiesces on a State
|
|
310
|
-
* that is neither terminal nor `failed` and the runtime has no live Sub and
|
|
311
|
-
* no Cmd in flight — nothing inside it can deliver another transition, so
|
|
312
|
-
* waiting would be a hang with the runtime leaked. The stalled State rides
|
|
313
|
-
* on `error.state`. A machine that CAN still move is not stalled: a live Sub
|
|
314
|
-
* (manual or dep-keyed) that delivers the terminal Msg after the dispatch
|
|
315
|
-
* quiesces keeps the drive waiting, and it resolves on that State. What the
|
|
316
|
-
* runtime cannot see — a dispatch from outside it after quiescence — does
|
|
317
|
-
* not count as live; a machine that depends on one declares it as a Sub.
|
|
318
|
-
* - `QuiescenceTimeoutError` when `start`'s follow-up chain never settles —
|
|
319
|
-
* the SAME cap `dispatch` and `idle()` already reject on (invariant 6), not a
|
|
320
|
-
* second clock. A livelocking machine surfaces as its own failure class.
|
|
321
|
-
* - the boot error, or whatever `dispatch(start)` rejects with, otherwise.
|
|
322
|
-
*
|
|
323
|
-
* `isTerminal` is caller-supplied, exactly as `run()`'s `terminal` option is —
|
|
324
|
-
* the kernel has no built-in terminal-set concept and this does not add one.
|
|
325
|
-
*
|
|
326
|
-
* Cancellation is the same story: `{ signal, cancel }` stops the drive from
|
|
327
|
-
* outside, and it settles the run rather than escaping it. On abort the drive
|
|
328
|
-
* dispatches `cancel`'s Msg and RESOLVES on the terminal State that transition
|
|
329
|
-
* lands — no rejection, no `DriveFailedError`, no `AbortError`. That is the whole
|
|
330
|
-
* point of routing a stop through the Model: the outcome is durable, so a killed
|
|
331
|
-
* process resumes reading a run that ended instead of restarting one someone
|
|
332
|
-
* stopped. A cancel that THROWS is the one abort that rejects: it lands
|
|
333
|
-
* no State, so its error is what the drive has to report — and that holds
|
|
334
|
-
* whether the `cancel` function throws synchronously or the fold it produced
|
|
335
|
-
* throws later, since both leave the run with no State to settle on. A State the
|
|
336
|
-
* caller's `failed` predicate marks rejects on an abort exit exactly as it does
|
|
337
|
-
* anywhere else. In-flight effects are not recalled — a promise cannot be cancelled —
|
|
338
|
-
* so they settle to their own end and `stop()` drains them as it always did;
|
|
339
|
-
* keeping their results off the public channels is the machine's own business.
|
|
340
|
-
* Omit the pair and every path here behaves exactly as it did before.
|
|
341
|
-
*
|
|
342
|
-
* @param handle the handle `run(machine, opts)` returned.
|
|
343
|
-
* @param start the Msg that sets the run in motion, or a function choosing
|
|
344
|
-
* it off the boot State. PURE.
|
|
345
|
-
* @param isTerminal the terminal predicate over the machine's State. PURE.
|
|
346
|
-
* @param opts an optional `failed` predicate and an optional
|
|
347
|
-
* `{ signal, cancel }` pair (see {@link DriveToDoneOptions}).
|
|
348
|
-
*/
|
|
349
|
-
declare function driveToDone<S, M extends {
|
|
350
|
-
type: string;
|
|
351
|
-
}, E extends {
|
|
352
|
-
type: string;
|
|
353
|
-
} = never>(handle: BootingRuntime<S, M, E>, start: M | ((booted: S) => M), isTerminal: (state: S) => boolean, opts?: DriveToDoneOptions<S, M>): Promise<S>;
|
|
354
|
-
|
|
355
247
|
/**
|
|
356
248
|
* The imperative listener target, expressed as the `add`/`remove` pair the
|
|
357
249
|
* substrate pairs into a reconciled resource. Both halves receive the SAME
|
|
@@ -385,15 +277,16 @@ interface ListenerTarget<Args extends readonly unknown[], S extends Sub, Ctx> {
|
|
|
385
277
|
* `Args`, `S`, `Ctx` are fixed when the target is defined — they describe the
|
|
386
278
|
* one listener topology this factory owns (the platform arg tuple, which Sub
|
|
387
279
|
* shape it filters on, what context resolves it). `M` is supplied at the
|
|
388
|
-
*
|
|
280
|
+
* runner because the same listener feeds different Msgs in different
|
|
389
281
|
* machines. Pinning `S` here (rather than at the `msgFn` step) lets
|
|
390
|
-
* `add`/`remove` read the concrete Sub's
|
|
391
|
-
* `sub.channel` — with NO cast at the substrate edge: the narrowing
|
|
392
|
-
* 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.
|
|
393
285
|
*
|
|
394
286
|
* The returned function is a `SubscribeHandler` factory in the exact shape of
|
|
395
287
|
* every other Sub factory in `src/subs/`: supply the `msgFn` projection, get a
|
|
396
|
-
* `(sub, ctx, dispatch) => cleanup`
|
|
288
|
+
* `(sub, ctx, dispatch) => cleanup` runner for the `subscribe` table `run`
|
|
289
|
+
* takes.
|
|
397
290
|
* The cleanup the author NEVER writes is `() => remove(listener, sub, ctx)`,
|
|
398
291
|
* with `listener` the identical reference passed to `add`.
|
|
399
292
|
*/
|
|
@@ -422,26 +315,26 @@ interface MinimalWebSocket {
|
|
|
422
315
|
}
|
|
423
316
|
|
|
424
317
|
type BroadcastSubData = {
|
|
425
|
-
channelName: string;
|
|
318
|
+
readonly channelName: string;
|
|
426
319
|
};
|
|
427
|
-
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>;
|
|
428
321
|
|
|
429
322
|
type EventSourceSubData = {
|
|
430
|
-
url: string;
|
|
323
|
+
readonly url: string;
|
|
431
324
|
};
|
|
432
325
|
interface EventSourceFactoryOpts<S, M> {
|
|
433
326
|
onMessage: (data: string, sub: S) => M | null;
|
|
434
327
|
onError?: (event: MinimalEvent, sub: S) => M | null;
|
|
435
328
|
onOpen?: (sub: S) => M | null;
|
|
436
329
|
}
|
|
437
|
-
declare function fromEventSource<S extends Sub
|
|
330
|
+
declare function fromEventSource<S extends Sub<string, EventSourceSubData>, M>(opts: EventSourceFactoryOpts<S, M>): SubscribeHandler<S, M, unknown>;
|
|
438
331
|
|
|
439
332
|
declare function fromEventTarget<S extends Sub, M>(getTarget: () => MinimalEventTarget, eventName: string, msgFn: (event: MinimalEvent, sub: S) => M | null): SubscribeHandler<S, M, unknown>;
|
|
440
333
|
|
|
441
334
|
type IntervalSubData = {
|
|
442
|
-
intervalMs: number;
|
|
335
|
+
readonly intervalMs: number;
|
|
443
336
|
};
|
|
444
|
-
declare function fromInterval<S extends Sub
|
|
337
|
+
declare function fromInterval<S extends Sub<string, IntervalSubData>, M>(msgFn: (sub: S) => M): SubscribeHandler<S, M, unknown>;
|
|
445
338
|
|
|
446
339
|
declare function fromPort<S extends Sub, M, Ctx, T, RS = unknown, RM extends {
|
|
447
340
|
type: string;
|
|
@@ -450,7 +343,7 @@ declare function fromPort<S extends Sub, M, Ctx, T, RS = unknown, RM extends {
|
|
|
450
343
|
}>(selectRuntime: (ctx: Ctx) => Runtime<RS, RM>, port: Port<T>, msgFn: (value: T, sub: S) => M | null): SubscribeHandler<S, M, Ctx>;
|
|
451
344
|
|
|
452
345
|
type WebSocketSubData = {
|
|
453
|
-
wsUrl: string;
|
|
346
|
+
readonly wsUrl: string;
|
|
454
347
|
};
|
|
455
348
|
interface WebSocketFactoryOpts<S, M> {
|
|
456
349
|
onMessage: (data: unknown, sub: S) => M | null;
|
|
@@ -458,7 +351,7 @@ interface WebSocketFactoryOpts<S, M> {
|
|
|
458
351
|
onError?: (event: MinimalEvent, sub: S) => M | null;
|
|
459
352
|
onClose?: (code: number, reason: string, sub: S) => M | null;
|
|
460
353
|
}
|
|
461
|
-
declare function fromWebSocket<S extends Sub
|
|
354
|
+
declare function fromWebSocket<S extends Sub<string, WebSocketSubData>, M>(opts: WebSocketFactoryOpts<S, M>): SubscribeHandler<S, M, unknown>;
|
|
462
355
|
|
|
463
356
|
/** Cancels a scheduled reconnect timer (the inverse of `schedule`). */
|
|
464
357
|
type CancelTimer = () => void;
|
|
@@ -482,97 +375,37 @@ interface ReconnectingWebSocketFactoryOpts<S, M> {
|
|
|
482
375
|
/** Inject the timer (defaults to `setTimeout`/`clearTimeout`); the test seam. */
|
|
483
376
|
schedule?: (fn: () => void, ms: number) => CancelTimer;
|
|
484
377
|
}
|
|
485
|
-
declare function fromReconnectingWebSocket<S extends Sub
|
|
378
|
+
declare function fromReconnectingWebSocket<S extends Sub<string, WebSocketSubData>, M>(opts: ReconnectingWebSocketFactoryOpts<S, M>): SubscribeHandler<S, M, unknown>;
|
|
486
379
|
|
|
487
380
|
type TimeoutSubData = {
|
|
488
|
-
delayMs: number;
|
|
381
|
+
readonly delayMs: number;
|
|
489
382
|
};
|
|
490
|
-
declare function fromTimeout<S extends Sub
|
|
383
|
+
declare function fromTimeout<S extends Sub<string, TimeoutSubData>, M>(msgFn: (sub: S) => M): SubscribeHandler<S, M, unknown>;
|
|
491
384
|
|
|
492
385
|
/**
|
|
493
|
-
* The Sub
|
|
494
|
-
*
|
|
495
|
-
* `type` like any other Sub.
|
|
496
|
-
*/
|
|
497
|
-
interface ManagedResourceSub<TKey> extends Sub<"managed_resource"> {
|
|
498
|
-
/** The lifetime key. Stable while the resource should be held; a change
|
|
499
|
-
* retires the old resource (release) and acquires a fresh one. */
|
|
500
|
-
readonly key: TKey;
|
|
501
|
-
}
|
|
502
|
-
/**
|
|
503
|
-
* A battery paired with a state-gate. Self-contained: it knows its `name`
|
|
504
|
-
* (for the unique-name assert), how to derive its live Sub from a state
|
|
505
|
-
* (`subFor`), how to handle its own `subscribe`, and whether it `owns` a given
|
|
506
|
-
* Sub id.
|
|
507
|
-
*
|
|
508
|
-
* Generic in the consumer state `S`, the shared lifetime key `TKey`, and the
|
|
509
|
-
* shared `Ctx`. `Handle` is the ONLY thing erased — each battery's handle type
|
|
510
|
-
* differs (a checkpoint saver vs. a tool-call bridge) but never crosses this
|
|
511
|
-
* interface, so a `combineManagedResources` array holds entries of
|
|
512
|
-
* heterogeneous handle types with no cast. The sub type stays
|
|
513
|
-
* `ManagedResourceSub<TKey>` end to end, so the substrate's narrowed Sub flows
|
|
514
|
-
* straight in.
|
|
515
|
-
*/
|
|
516
|
-
interface GatedManagedResource<S, TKey, Ctx> {
|
|
517
|
-
/** The owning battery's `name`. The combinator asserts these are unique. */
|
|
518
|
-
readonly name: string;
|
|
519
|
-
/**
|
|
520
|
-
* Derive this resource's live Sub for a given state: the battery's
|
|
521
|
-
* `.sub(key)` when `when(state)` returns a key, or `null` when it returns
|
|
522
|
-
* `null` (the resource should be torn down in that state).
|
|
523
|
-
*/
|
|
524
|
-
readonly subFor: (state: S) => ManagedResourceSub<TKey> | null;
|
|
525
|
-
/**
|
|
526
|
-
* This battery's own `.subscribe`. The combinator routes an incoming
|
|
527
|
-
* `managed_resource` Sub to the entry whose `owns(sub)` is true, then calls
|
|
528
|
-
* this.
|
|
529
|
-
*/
|
|
530
|
-
readonly subscribe: (sub: ManagedResourceSub<TKey>, ctx: Ctx, dispatch: (msg: never) => void) => Dispose;
|
|
531
|
-
/**
|
|
532
|
-
* Does this entry own the given Sub? `sub.id === subIdFor(sub.key)` encodes
|
|
533
|
-
* the battery `name`, so a match is unambiguous.
|
|
534
|
-
*/
|
|
535
|
-
readonly owns: (sub: ManagedResourceSub<TKey>) => boolean;
|
|
536
|
-
}
|
|
537
|
-
/**
|
|
538
|
-
* What `combineManagedResources` returns: the single managed-resource
|
|
539
|
-
* `subscribe` handler for the machine's `subscribe` record, and a `subs(state)`
|
|
540
|
-
* builder for `subscriptions(state)`.
|
|
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.
|
|
541
388
|
*/
|
|
542
|
-
|
|
543
|
-
|
|
544
|
-
* Map every entry through its `when(state)`, drop the `null`s, and build the
|
|
545
|
-
* live Subs. Spread into `subscriptions(state)`. The derived list and the
|
|
546
|
-
* `subscribe` routing share one source — the entries — so they never drift.
|
|
547
|
-
*/
|
|
548
|
-
readonly subs: (state: S) => ManagedResourceSub<TKey>[];
|
|
549
|
-
/**
|
|
550
|
-
* The SINGLE `subscribe.managed_resource` handler. Routes an incoming Sub to
|
|
551
|
-
* the owning battery by matching `sub.id === battery.subIdFor(sub.key)` (the
|
|
552
|
-
* id encodes the battery `name`). Throws loudly on an unrouted id — a wiring
|
|
553
|
-
* bug, never a silent no-op resource.
|
|
554
|
-
*/
|
|
555
|
-
readonly subscribe: (sub: ManagedResourceSub<TKey>, ctx: Ctx, dispatch: (msg: never) => void) => Dispose;
|
|
556
|
-
}
|
|
557
|
-
interface DefineManagedResourceOpts<TKey, Handle, Ctx> {
|
|
389
|
+
type ManagedResourceSub<N extends string, TKey> = Sub<N, TKey>;
|
|
390
|
+
interface DefineManagedResourceOpts<N extends string, TKey, Handle, Ctx> {
|
|
558
391
|
/**
|
|
559
|
-
* A short, stable name
|
|
560
|
-
*
|
|
561
|
-
*
|
|
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.
|
|
562
395
|
*/
|
|
563
|
-
readonly name:
|
|
396
|
+
readonly name: N;
|
|
564
397
|
/**
|
|
565
|
-
* Build the resource. Runs when the
|
|
398
|
+
* Build the resource. Runs when the resource's `deps` turns non-null.
|
|
566
399
|
* The returned Handle is the ONLY thing `release` receives — capture
|
|
567
|
-
* everything teardown needs into it. If `acquire` throws, the
|
|
568
|
-
*
|
|
569
|
-
*
|
|
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).
|
|
570
403
|
*/
|
|
571
404
|
readonly acquire: (key: TKey, ctx: Ctx) => Handle;
|
|
572
405
|
/**
|
|
573
|
-
* Tear the resource down. Runs when the
|
|
574
|
-
*
|
|
575
|
-
*
|
|
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.
|
|
576
409
|
*
|
|
577
410
|
* May be sync or async, and an async one is really awaited: the promise is
|
|
578
411
|
* returned to the substrate, which tracks it and drains it inside `stop()`
|
|
@@ -586,78 +419,44 @@ interface DefineManagedResourceOpts<TKey, Handle, Ctx> {
|
|
|
586
419
|
readonly release: (handle: Handle) => void | Promise<void>;
|
|
587
420
|
}
|
|
588
421
|
/**
|
|
589
|
-
* What the battery returns: a `.
|
|
590
|
-
* `.subscribe`
|
|
591
|
-
* accessor so Cmd handlers can reach the live Handle while
|
|
592
|
-
*
|
|
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.
|
|
593
426
|
*/
|
|
594
|
-
interface ManagedResourceBattery<TKey, Handle, Ctx> {
|
|
427
|
+
interface ManagedResourceBattery<N extends string, TKey, Handle, Ctx> {
|
|
428
|
+
/** The battery's Sub type — its `name`. */
|
|
429
|
+
readonly type: N;
|
|
595
430
|
/**
|
|
596
|
-
*
|
|
597
|
-
*
|
|
598
|
-
*
|
|
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.
|
|
599
436
|
*/
|
|
600
|
-
readonly
|
|
437
|
+
readonly depKeyed: <S>(when: (state: S) => TKey | null) => DepKeyedSub<S, ManagedResourceSub<N, TKey>>;
|
|
601
438
|
/**
|
|
602
|
-
*
|
|
603
|
-
* `
|
|
604
|
-
*
|
|
605
|
-
*
|
|
606
|
-
* Folding "is it active?" and "what key?" into ONE declaration at the
|
|
607
|
-
* battery's own definition site is the point: a central `subscriptions(state)`
|
|
608
|
-
* list can silently forget to gate a new phase, but a per-resource gate
|
|
609
|
-
* travels with the resource. The combinator derives BOTH the active-sub list
|
|
610
|
-
* and the subscribe routing from these entries, so the two can never disagree.
|
|
611
|
-
*
|
|
612
|
-
* Generic in the consumer's state `S` (each `.gated` call binds its own `S`).
|
|
613
|
-
* The returned entry erases this battery's `Handle` — `combineManagedResources`
|
|
614
|
-
* holds entries of heterogeneous handle types with no cast — while keeping
|
|
615
|
-
* `TKey` and `Ctx` so the substrate's narrowed `ManagedResourceSub<TKey>`
|
|
616
|
-
* flows straight into the entry's `subscribe`.
|
|
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.
|
|
617
443
|
*/
|
|
618
|
-
readonly
|
|
444
|
+
readonly subscribe: (sub: ManagedResourceSub<N, TKey>, ctx: Ctx, dispatch: (msg: never) => void) => Dispose;
|
|
619
445
|
/**
|
|
620
|
-
*
|
|
621
|
-
*
|
|
622
|
-
*
|
|
623
|
-
*
|
|
624
|
-
* router multiplexes.
|
|
625
|
-
*
|
|
626
|
-
* This is the simplification the dep-keyed kernel unlocks: two managed
|
|
627
|
-
* resources used to COLLIDE on the single `subscribe.managed_resource` key
|
|
628
|
-
* (both subs share `type: "managed_resource"`), forcing
|
|
629
|
-
* `combineManagedResources` to route by SubId. A dep-keyed Sub has its OWN
|
|
630
|
-
* reconcile slot (its array position), so there is no shared `type` to route
|
|
631
|
-
* — each resource is one `subs` entry, no combinator, no router. `acquire` /
|
|
632
|
-
* `release` still ride the kernel's dispose-on-change / dispose-on-null
|
|
633
|
-
* reconcile, now via the dep-keyed source's `Dispose` instead of the
|
|
634
|
-
* `subscribe` cell's cleanup.
|
|
635
|
-
*/
|
|
636
|
-
readonly depKeyed: <S>(when: (state: S) => TKey | null) => DepKeyedSub<S, never, Ctx>;
|
|
637
|
-
/**
|
|
638
|
-
* The `subscribe.managed_resource` handler. Drop straight into the
|
|
639
|
-
* machine's `subscribe` record. Runs `acquire` on install, holds the
|
|
640
|
-
* Handle in the battery's handle table, and returns a cleanup that runs
|
|
641
|
-
* `release` (and forgets the Handle) on reconcile-out.
|
|
642
|
-
*/
|
|
643
|
-
readonly subscribe: (sub: ManagedResourceSub<TKey>, ctx: Ctx, dispatch: (msg: never) => void) => Dispose;
|
|
644
|
-
/**
|
|
645
|
-
* Accessor for the live Handle, keyed on `key`. Returns `undefined` when
|
|
646
|
-
* the resource is not currently held (no active sub for that key) — the
|
|
647
|
-
* caller decides whether that is an error. A Cmd handler wires its
|
|
648
|
-
* `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
|
|
649
450
|
* reconciler holds, never a hand-built duplicate.
|
|
650
451
|
*/
|
|
651
452
|
readonly get: (key: TKey) => Handle | undefined;
|
|
652
|
-
/** Pre-derived subId for `(key)` — handy for tests. */
|
|
653
|
-
readonly subIdFor: (key: TKey) => SubId;
|
|
654
453
|
}
|
|
655
454
|
/**
|
|
656
455
|
* Build the battery. One call per managed resource at the machine's host
|
|
657
456
|
* file.
|
|
658
457
|
*
|
|
659
458
|
* @example
|
|
660
|
-
* const checkpointStore = defineManagedResource<RunId, CheckpointSaver, Ctx>({
|
|
459
|
+
* const checkpointStore = defineManagedResource<"checkpoint", RunId, CheckpointSaver, Ctx>({
|
|
661
460
|
* name: "checkpoint",
|
|
662
461
|
* acquire: (runId, ctx) =>
|
|
663
462
|
* new CheckpointSaver({ storage: ctx.storage, bucket: ctx.bucket, runId }),
|
|
@@ -665,9 +464,15 @@ interface ManagedResourceBattery<TKey, Handle, Ctx> {
|
|
|
665
464
|
* });
|
|
666
465
|
*
|
|
667
466
|
* // In the machine:
|
|
668
|
-
*
|
|
669
|
-
*
|
|
670
|
-
*
|
|
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 } });
|
|
671
476
|
*
|
|
672
477
|
* // In interpret (read the live handle the reconciler holds):
|
|
673
478
|
* start_graph: async (cmd, ctx) => {
|
|
@@ -675,8 +480,7 @@ interface ManagedResourceBattery<TKey, Handle, Ctx> {
|
|
|
675
480
|
* ...
|
|
676
481
|
* }
|
|
677
482
|
*/
|
|
678
|
-
declare function defineManagedResource<TKey, Handle, Ctx>(opts: DefineManagedResourceOpts<TKey, Handle, Ctx>): ManagedResourceBattery<TKey, Handle, Ctx>;
|
|
679
|
-
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>;
|
|
680
484
|
|
|
681
485
|
/**
|
|
682
486
|
* Duplex transport. The host (DO, worker thread, in-process bus) constructs
|
|
@@ -688,8 +492,8 @@ interface Transport {
|
|
|
688
492
|
send(data: string): void;
|
|
689
493
|
/**
|
|
690
494
|
* Subscribe to inbound frames. Returns a cleanup that removes the listener.
|
|
691
|
-
* The battery wires this in its
|
|
692
|
-
*
|
|
495
|
+
* The battery wires this in its runner; cleanup runs when the seam's key
|
|
496
|
+
* goes null or changes.
|
|
693
497
|
*/
|
|
694
498
|
onMessage(listener: (data: string) => void): () => void;
|
|
695
499
|
/**
|
|
@@ -702,26 +506,22 @@ interface Transport {
|
|
|
702
506
|
}
|
|
703
507
|
/**
|
|
704
508
|
* Factory the consumer wires to a platform-specific transport. The battery
|
|
705
|
-
* passes the
|
|
706
|
-
*
|
|
509
|
+
* passes the seam's key (`runId`, or whatever identity is on the phase) so
|
|
510
|
+
* per-run transports can be addressed.
|
|
707
511
|
*/
|
|
708
512
|
type TransportFactory<K> = (key: K) => Transport;
|
|
709
513
|
/**
|
|
710
|
-
* The Sub
|
|
711
|
-
*
|
|
712
|
-
* 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.
|
|
713
516
|
*/
|
|
714
|
-
|
|
715
|
-
|
|
716
|
-
readonly key: TKey;
|
|
717
|
-
}
|
|
718
|
-
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> {
|
|
719
519
|
/**
|
|
720
|
-
* A short, stable name for this seam
|
|
721
|
-
*
|
|
722
|
-
*
|
|
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.
|
|
723
523
|
*/
|
|
724
|
-
readonly name:
|
|
524
|
+
readonly name: N;
|
|
725
525
|
/**
|
|
726
526
|
* Build the transport. The battery owns the lifetime; the factory is the
|
|
727
527
|
* platform-specific constructor (`new WebSocket(...)` adapter on a DO,
|
|
@@ -753,53 +553,42 @@ interface FromTransportOpts<TKey, Inbound, Outbound, M, Ctx> {
|
|
|
753
553
|
readonly serializeOutbound: (outbound: Outbound) => string;
|
|
754
554
|
}
|
|
755
555
|
/**
|
|
756
|
-
* What the battery returns: a
|
|
757
|
-
* `subscribe`
|
|
758
|
-
* helper the consumer's Cmd handler calls.
|
|
759
|
-
* consumer can use when keying related Subs (e.g. a deadline) to the same seam
|
|
760
|
-
* 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.
|
|
761
559
|
*/
|
|
762
|
-
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;
|
|
763
563
|
/**
|
|
764
|
-
*
|
|
765
|
-
*
|
|
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.
|
|
766
569
|
*/
|
|
767
|
-
readonly
|
|
570
|
+
readonly depKeyed: <S>(when: (state: S) => TKey | null) => DepKeyedSub<S, TransportSub<N, TKey>>;
|
|
768
571
|
/**
|
|
769
|
-
* The
|
|
770
|
-
* `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.
|
|
771
575
|
*/
|
|
772
|
-
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;
|
|
773
577
|
/**
|
|
774
|
-
* Outbound send
|
|
775
|
-
*
|
|
776
|
-
*
|
|
777
|
-
*
|
|
778
|
-
*
|
|
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).
|
|
779
584
|
*/
|
|
780
585
|
readonly send: (key: TKey, outbound: Outbound) => void;
|
|
781
|
-
/** Pre-derived subId for `(key)` — handy for deadline Subs keyed to the same seam. */
|
|
782
|
-
readonly subIdFor: (key: TKey) => SubId;
|
|
783
|
-
/**
|
|
784
|
-
* Pair this seam with a state-gate, producing a dep-keyed Sub for the
|
|
785
|
-
* machine's `subs` array. `when(state)` returns the seam's KEY when it
|
|
786
|
-
* should be open, or `null` when it should be torn down. The kernel derives
|
|
787
|
-
* the id from `structuralHash(key)` and reconciles the seam's lifetime — so
|
|
788
|
-
* the author neither lists this Sub in a central `subscriptions(state)` nor
|
|
789
|
-
* writes its id. Replaces `subscriptions: (s) => active(s) ? [hands.sub(...)] : []`
|
|
790
|
-
* + `subscribe: { transport: hands.subscribe }` with one `subs` entry.
|
|
791
|
-
*
|
|
792
|
-
* The `source` closes over the battery's own `subscribe` (open inbound +
|
|
793
|
-
* close, wire the handle table) using the same `ctx` the kernel passes every
|
|
794
|
-
* Sub source.
|
|
795
|
-
*/
|
|
796
|
-
readonly depKeyed: <S>(when: (state: S) => TKey | null) => DepKeyedSub<S, M, Ctx>;
|
|
797
586
|
}
|
|
798
587
|
/**
|
|
799
588
|
* Build the battery. One call per seam at the machine's host file.
|
|
800
589
|
*
|
|
801
590
|
* @example
|
|
802
|
-
* const handsSeam = fromTransport<RunId, HandsInbound, HandsOutbound, Msg, Ctx>({
|
|
591
|
+
* const handsSeam = fromTransport<"hands", RunId, HandsInbound, HandsOutbound, Msg, Ctx>({
|
|
803
592
|
* name: "hands",
|
|
804
593
|
* openTransport: (runId, ctx) => ctx.openHandsWs(runId),
|
|
805
594
|
* parseInbound: (raw) => parseHandsInbound(JSON.parse(raw)),
|
|
@@ -809,12 +598,19 @@ interface TransportBattery<TKey, Outbound, M, Ctx> {
|
|
|
809
598
|
* });
|
|
810
599
|
*
|
|
811
600
|
* // In the machine:
|
|
812
|
-
*
|
|
813
|
-
*
|
|
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 } });
|
|
814
610
|
*
|
|
815
611
|
* // In interpret:
|
|
816
612
|
* send_hands: async (cmd) => { handsSeam.send(cmd.runId, cmd.outbound); },
|
|
817
613
|
*/
|
|
818
|
-
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>;
|
|
819
615
|
|
|
820
|
-
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 };
|