@demlik/tea 0.1.0 → 0.1.1
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/LICENSE +21 -0
- package/dist/agent/index.d.ts +907 -0
- package/dist/agent/index.js +20 -0
- package/dist/agent/index.js.map +1 -0
- package/dist/authed-call/index.d.ts +119 -0
- package/dist/authed-call/index.js +148 -0
- package/dist/authed-call/index.js.map +1 -0
- package/dist/await-terminal/index.d.ts +99 -0
- package/dist/await-terminal/index.js +75 -0
- package/dist/await-terminal/index.js.map +1 -0
- package/dist/batch-window/index.d.ts +259 -0
- package/dist/batch-window/index.js +43 -0
- package/dist/batch-window/index.js.map +1 -0
- package/dist/cache/index.d.ts +2 -2
- package/dist/cache/index.js +2 -50
- package/dist/cache/index.js.map +1 -1
- package/dist/{chunk-CMG2KMWX.js → chunk-2WZHS4P2.js} +220 -177
- package/dist/chunk-2WZHS4P2.js.map +1 -0
- package/dist/chunk-2YOE5PIS.js +45 -0
- package/dist/chunk-2YOE5PIS.js.map +1 -0
- package/dist/chunk-3545BOYL.js +65 -0
- package/dist/chunk-3545BOYL.js.map +1 -0
- package/dist/chunk-5VNWZADP.js +49 -0
- package/dist/chunk-5VNWZADP.js.map +1 -0
- package/dist/{chunk-ZRUST6BK.js → chunk-7FYQGDMF.js} +2 -2
- package/dist/chunk-7FYQGDMF.js.map +1 -0
- package/dist/chunk-7VEG4KHB.js +202 -0
- package/dist/chunk-7VEG4KHB.js.map +1 -0
- package/dist/{chunk-PYBQREFU.js → chunk-A56GNPI7.js} +20 -6
- package/dist/chunk-A56GNPI7.js.map +1 -0
- package/dist/chunk-BNJYE4AU.js +157 -0
- package/dist/chunk-BNJYE4AU.js.map +1 -0
- package/dist/chunk-BYQUGDD3.js +16 -0
- package/dist/chunk-BYQUGDD3.js.map +1 -0
- package/dist/chunk-D5EQDLDV.js +186 -0
- package/dist/chunk-D5EQDLDV.js.map +1 -0
- package/dist/chunk-EDGDR5XX.js +60 -0
- package/dist/chunk-EDGDR5XX.js.map +1 -0
- package/dist/chunk-EIZJTYKR.js +93 -0
- package/dist/chunk-EIZJTYKR.js.map +1 -0
- package/dist/chunk-EQRUAP2A.js +56 -0
- package/dist/chunk-EQRUAP2A.js.map +1 -0
- package/dist/chunk-FH5RUIIL.js +61 -0
- package/dist/chunk-FH5RUIIL.js.map +1 -0
- package/dist/chunk-HJO6VNQI.js +15 -0
- package/dist/chunk-HJO6VNQI.js.map +1 -0
- package/dist/chunk-IFBQJYGI.js +132 -0
- package/dist/chunk-IFBQJYGI.js.map +1 -0
- package/dist/chunk-IN33Q4VJ.js +52 -0
- package/dist/chunk-IN33Q4VJ.js.map +1 -0
- package/dist/chunk-JELSZN25.js +47 -0
- package/dist/chunk-JELSZN25.js.map +1 -0
- package/dist/chunk-K575PT4S.js +58 -0
- package/dist/chunk-K575PT4S.js.map +1 -0
- package/dist/chunk-LEC4U6CX.js +91 -0
- package/dist/chunk-LEC4U6CX.js.map +1 -0
- package/dist/{chunk-Y5JM3UUH.js → chunk-NSDFA6M6.js} +75 -3
- package/dist/chunk-NSDFA6M6.js.map +1 -0
- package/dist/chunk-QGZW4W3E.js +20 -0
- package/dist/chunk-QGZW4W3E.js.map +1 -0
- package/dist/{chunk-PYOCUBQJ.js → chunk-RGTPPF4U.js} +22 -15
- package/dist/chunk-RGTPPF4U.js.map +1 -0
- package/dist/chunk-RVJFGRGK.js +222 -0
- package/dist/chunk-RVJFGRGK.js.map +1 -0
- package/dist/chunk-RVU3RBE6.js +31 -0
- package/dist/chunk-RVU3RBE6.js.map +1 -0
- package/dist/chunk-SC3FAFZN.js +53 -0
- package/dist/chunk-SC3FAFZN.js.map +1 -0
- package/dist/chunk-SINXE47Y.js +55 -0
- package/dist/chunk-SINXE47Y.js.map +1 -0
- package/dist/chunk-TAO4YOLO.js +87 -0
- package/dist/chunk-TAO4YOLO.js.map +1 -0
- package/dist/chunk-TC4HWABA.js +506 -0
- package/dist/chunk-TC4HWABA.js.map +1 -0
- package/dist/chunk-TGG26XMK.js +52 -0
- package/dist/chunk-TGG26XMK.js.map +1 -0
- package/dist/chunk-UV6UWGEO.js +147 -0
- package/dist/chunk-UV6UWGEO.js.map +1 -0
- package/dist/chunk-UYWUVEP6.js +158 -0
- package/dist/chunk-UYWUVEP6.js.map +1 -0
- package/dist/chunk-WUV3PSCZ.js +54 -0
- package/dist/chunk-WUV3PSCZ.js.map +1 -0
- package/dist/chunk-XRXKH7KA.js +268 -0
- package/dist/chunk-XRXKH7KA.js.map +1 -0
- package/dist/circuit-breaker/index.js +1 -51
- package/dist/circuit-breaker/index.js.map +1 -1
- package/dist/core-CymXZCeY.d.ts +178 -0
- package/dist/deadline/index.d.ts +24 -5
- package/dist/deadline/index.js +4 -18
- package/dist/deadline/index.js.map +1 -1
- package/dist/debounce/index.js +1 -54
- package/dist/debounce/index.js.map +1 -1
- package/dist/devtools/index.d.ts +1 -1
- package/dist/devtools/index.js +18 -6
- package/dist/devtools/index.js.map +1 -1
- package/dist/do/index.d.ts +1732 -92
- package/dist/do/index.js +677 -31
- package/dist/do/index.js.map +1 -1
- package/dist/durable-effects-2MDxDs45.d.ts +180 -0
- package/dist/extension/index.d.ts +31 -13
- package/dist/extension/index.js +5 -3
- package/dist/extension/index.js.map +1 -1
- package/dist/extension/react.js +8 -3
- package/dist/extension/react.js.map +1 -1
- package/dist/extension/subs/index.d.ts +2 -2
- package/dist/extension/subs/index.js.map +1 -1
- package/dist/extension/test-utils.js +11 -3
- package/dist/extension/test-utils.js.map +1 -1
- package/dist/fan-out/index.d.ts +239 -0
- package/dist/fan-out/index.js +5 -0
- package/dist/fan-out/index.js.map +1 -0
- package/dist/idempotency/adapter.d.ts +62 -0
- package/dist/idempotency/adapter.js +4 -0
- package/dist/idempotency/adapter.js.map +1 -0
- package/dist/idempotency/index.js +1 -56
- package/dist/idempotency/index.js.map +1 -1
- package/dist/idempotent-intake/index.d.ts +186 -0
- package/dist/idempotent-intake/index.js +7 -0
- package/dist/idempotent-intake/index.js.map +1 -0
- package/dist/index.d.ts +341 -191
- package/dist/index.js +2 -1
- package/dist/llm-call/index.d.ts +309 -0
- package/dist/llm-call/index.js +14 -0
- package/dist/llm-call/index.js.map +1 -0
- package/dist/machine-viz/index.d.ts +1 -1
- package/dist/machine-viz/index.js +1 -1
- package/dist/machine-viz/index.js.map +1 -1
- package/dist/mem/index.d.ts +1 -0
- package/dist/monitored-run/index.d.ts +253 -0
- package/dist/monitored-run/index.js +10 -0
- package/dist/monitored-run/index.js.map +1 -0
- package/dist/node/index.d.ts +5 -5
- package/dist/node/index.js.map +1 -1
- package/dist/paginated-walk/index.d.ts +234 -0
- package/dist/paginated-walk/index.js +15 -0
- package/dist/paginated-walk/index.js.map +1 -0
- package/dist/paginator/index.d.ts +271 -0
- package/dist/paginator/index.js +3 -0
- package/dist/paginator/index.js.map +1 -0
- package/dist/parity/index.d.ts +86 -0
- package/dist/parity/index.js +77 -0
- package/dist/parity/index.js.map +1 -0
- package/dist/pbt/arbitraries/index.d.ts +1 -1
- package/dist/pbt/arbitraries/index.js +1 -1
- package/dist/pbt/index.d.ts +1 -1
- package/dist/pbt/index.js +5 -4
- package/dist/pbt/index.js.map +1 -1
- package/dist/pbt/runners/index.d.ts +1 -1
- package/dist/pbt/runners/index.js +3 -2
- package/dist/poller/index.d.ts +288 -0
- package/dist/poller/index.js +79 -0
- package/dist/poller/index.js.map +1 -0
- package/dist/prediction/index.d.ts +109 -0
- package/dist/prediction/index.js +4 -0
- package/dist/prediction/index.js.map +1 -0
- package/dist/protocol-B5YmJr0d.d.ts +45 -0
- package/dist/pure/index.d.ts +2 -0
- package/dist/pure/index.js +4 -0
- package/dist/pure/index.js.map +1 -0
- package/dist/raft/demo.d.ts +105 -0
- package/dist/raft/demo.js +172 -0
- package/dist/raft/demo.js.map +1 -0
- package/dist/raft/index.d.ts +548 -0
- package/dist/raft/index.js +7 -0
- package/dist/raft/index.js.map +1 -0
- package/dist/raft/sim.d.ts +222 -0
- package/dist/raft/sim.js +8 -0
- package/dist/raft/sim.js.map +1 -0
- package/dist/raft/timeline.d.ts +46 -0
- package/dist/raft/timeline.js +62 -0
- package/dist/raft/timeline.js.map +1 -0
- package/dist/rate-limit/index.js +1 -43
- package/dist/rate-limit/index.js.map +1 -1
- package/dist/react/index.d.ts +2 -1
- package/dist/react/index.js +39 -20
- package/dist/react/index.js.map +1 -1
- package/dist/reconciler/index.d.ts +192 -0
- package/dist/reconciler/index.js +166 -0
- package/dist/reconciler/index.js.map +1 -0
- package/dist/recorder/index.d.ts +7 -3
- package/dist/recorder/index.js +1 -155
- package/dist/recorder/index.js.map +1 -1
- package/dist/resilient-call/index.d.ts +251 -0
- package/dist/resilient-call/index.js +13 -0
- package/dist/resilient-call/index.js.map +1 -0
- package/dist/retry-backoff/index.d.ts +42 -5
- package/dist/retry-backoff/index.js +1 -35
- package/dist/retry-backoff/index.js.map +1 -1
- package/dist/saga/index.d.ts +225 -0
- package/dist/saga/index.js +150 -0
- package/dist/saga/index.js.map +1 -0
- package/dist/snapshot/index.d.ts +334 -0
- package/dist/snapshot/index.js +5 -0
- package/dist/snapshot/index.js.map +1 -0
- package/dist/subs/index.d.ts +48 -10
- package/dist/subs/index.js +1 -1
- package/dist/testing/index.d.ts +107 -2
- package/dist/testing/index.js +204 -6
- package/dist/testing/index.js.map +1 -1
- package/dist/throttle/index.js +1 -52
- package/dist/throttle/index.js.map +1 -1
- package/dist/throttled-input/index.d.ts +372 -0
- package/dist/throttled-input/index.js +88 -0
- package/dist/throttled-input/index.js.map +1 -0
- package/dist/token-refresh/index.d.ts +215 -0
- package/dist/token-refresh/index.js +6 -0
- package/dist/token-refresh/index.js.map +1 -0
- package/dist/trace-replay/index.d.ts +11 -2
- package/dist/trace-replay/index.js +3 -55
- package/dist/trace-replay/index.js.map +1 -1
- package/dist/{types-CwBC5wMn.d.ts → types-N9LZbdnr.d.ts} +1 -1
- package/dist/with-deadline/index.d.ts +196 -0
- package/dist/with-deadline/index.js +130 -0
- package/dist/with-deadline/index.js.map +1 -0
- package/dist/with-resilience/index.d.ts +205 -0
- package/dist/with-resilience/index.js +274 -0
- package/dist/with-resilience/index.js.map +1 -0
- package/dist/with-telemetry/index.d.ts +149 -0
- package/dist/with-telemetry/index.js +95 -0
- package/dist/with-telemetry/index.js.map +1 -0
- package/dist/work-queue/adapter.d.ts +82 -0
- package/dist/work-queue/adapter.js +4 -0
- package/dist/work-queue/adapter.js.map +1 -0
- package/dist/work-queue/index.d.ts +25 -11
- package/dist/work-queue/index.js +1 -46
- package/dist/work-queue/index.js.map +1 -1
- package/dist/work-queue/ops.d.ts +69 -0
- package/dist/work-queue/ops.js +3 -0
- package/dist/work-queue/ops.js.map +1 -0
- package/dist/workflow/index.d.ts +444 -0
- package/dist/workflow/index.js +225 -0
- package/dist/workflow/index.js.map +1 -0
- package/package.json +162 -25
- package/dist/chunk-CMG2KMWX.js.map +0 -1
- package/dist/chunk-PYBQREFU.js.map +0 -1
- package/dist/chunk-PYOCUBQJ.js.map +0 -1
- package/dist/chunk-Y5JM3UUH.js.map +0 -1
- package/dist/chunk-ZRUST6BK.js.map +0 -1
|
@@ -0,0 +1,334 @@
|
|
|
1
|
+
import { C as Cmd, I as Interpret } from '../core-CymXZCeY.js';
|
|
2
|
+
|
|
3
|
+
/**
|
|
4
|
+
* @demlik/tea/snapshot — periodic state checkpoint to a host store (R2/KV-shaped).
|
|
5
|
+
*
|
|
6
|
+
* The problem it solves: a long-running machine (a multi-stage run, a paginated
|
|
7
|
+
* crawl, a saga) lives in TEA's in-memory `State` and is persisted as ONE blob
|
|
8
|
+
* by the substrate's `Store<S>` on every transition. That blob is fine for
|
|
9
|
+
* crash recovery of the *runtime* — but it is NOT a coarse, named checkpoint a
|
|
10
|
+
* human (or a separate recovery job) can point a restart at. `snapshot` adds
|
|
11
|
+
* the "every N units of progress, write a durable checkpoint to R2/KV"
|
|
12
|
+
* discipline on top, so a crashed long run restarts from the last checkpoint
|
|
13
|
+
* instead of from zero (the field-guide pain row).
|
|
14
|
+
*
|
|
15
|
+
* Shape: this is the same knob contract every L2 composition follows —
|
|
16
|
+
* `createSnapshot(config)` hands back a `{ init, <verbs>, handlers }` bundle you
|
|
17
|
+
* splice into your machine. The state it owns is a Model slice (durable +
|
|
18
|
+
* replayable, the two non-negotiables); the verbs are PURE decisions; the I/O
|
|
19
|
+
* (the actual `put` to the store) lives only in `handlers`.
|
|
20
|
+
*
|
|
21
|
+
* It is a LEAF module: it imports only the core substrate (`../index`, for
|
|
22
|
+
* `Cmd` / `Interpret`) and no sibling subpath. (It does NOT reuse
|
|
23
|
+
* `@demlik/tea/retry-backoff` — that is an even-leafier utility that depends on
|
|
24
|
+
* nothing at all, not even the core; the kinship here is "leaf with injected
|
|
25
|
+
* time", not a shared dependency.)
|
|
26
|
+
* Nothing here reads the clock or the RNG on its own behalf — time enters as an
|
|
27
|
+
* `at`/`now` parameter at the verb boundary and the store I/O is realized only
|
|
28
|
+
* when the runtime performs the emitted `Cmd`. That keeps every verb
|
|
29
|
+
* deterministic (invariant 2) and the checkpoint write a `Cmd` the runtime
|
|
30
|
+
* interprets (invariant 3).
|
|
31
|
+
*
|
|
32
|
+
* The Store port is deliberately ABSTRACT — `{ get, put }`, the lowest common
|
|
33
|
+
* shape of R2 and KV — so this module never imports `@cloudflare/workers-types`
|
|
34
|
+
* and can be checkpointed to anything (R2, KV, a DO sub-key, an in-memory map in
|
|
35
|
+
* tests). The host binds the concrete adapter at `handlers({ store })`.
|
|
36
|
+
*
|
|
37
|
+
* Typical wiring:
|
|
38
|
+
*
|
|
39
|
+
* const snap = createSnapshot<RunState>({ every: 25, key: "run/checkpoint" });
|
|
40
|
+
*
|
|
41
|
+
* // machine state:
|
|
42
|
+
* type State = { run: RunState; snapshot: SnapshotState };
|
|
43
|
+
* init: (loaded) => loaded ? [loaded, []] : [{ run: initRun(), snapshot: snap.init() }, []],
|
|
44
|
+
*
|
|
45
|
+
* // in `update`, on each progress Msg, fold the snapshot slice:
|
|
46
|
+
* progress: (s, msg) => {
|
|
47
|
+
* const run = advance(s.run, msg);
|
|
48
|
+
* const [snapshot, cmds] = snap.record(s.snapshot, run, msg.at);
|
|
49
|
+
* return [{ ...s, run, snapshot }, cmds];
|
|
50
|
+
* },
|
|
51
|
+
* // the write completed → durable ack folds back through update:
|
|
52
|
+
* snapshot_saved: (s, msg) => {
|
|
53
|
+
* const [snapshot, cmds] = snap.confirm(s.snapshot, msg);
|
|
54
|
+
* return [{ ...s, snapshot }, cmds];
|
|
55
|
+
* },
|
|
56
|
+
*
|
|
57
|
+
* // RECOVERY — the half that makes a checkpoint a restart point, not just a
|
|
58
|
+
* // backup. A `boot` Msg the host dispatches once after `run(...)` asks the
|
|
59
|
+
* // store for the durable checkpoint; the answer folds the recovered payload
|
|
60
|
+
* // back into the run slice:
|
|
61
|
+
* boot: (s) => snap.requestLoad(s), // emits snapshot_load Cmd
|
|
62
|
+
* snapshot_loaded: (s, msg) => {
|
|
63
|
+
* if (msg.payload === null) return [s, []]; // no checkpoint → keep fresh init
|
|
64
|
+
* const [snapshot, cmds] = snap.boot(s.snapshot);
|
|
65
|
+
* return [{ ...s, run: msg.payload, snapshot }, cmds];
|
|
66
|
+
* },
|
|
67
|
+
* snapshot_load_failed: (s) => [s, []], // errors are data — decide here
|
|
68
|
+
*
|
|
69
|
+
* // splice the I/O — both effect cells (write + load):
|
|
70
|
+
* interpret: { ...snap.handlers({ store: r2Adapter }) },
|
|
71
|
+
*/
|
|
72
|
+
|
|
73
|
+
/**
|
|
74
|
+
* The snapshot knob.
|
|
75
|
+
*
|
|
76
|
+
* `every` is the cadence: a checkpoint is written once `every` units of
|
|
77
|
+
* progress have accumulated since the last one (see `record`). It is the
|
|
78
|
+
* `snapshotEvery` field `monitored-run` threads through to this module.
|
|
79
|
+
*
|
|
80
|
+
* `key` is the store key the checkpoint is written under. A single machine
|
|
81
|
+
* keeps a single rolling checkpoint, so the key is fixed at config time rather
|
|
82
|
+
* than passed per write — the latest checkpoint overwrites the previous one
|
|
83
|
+
* (R2/KV are last-write-wins on a key). Defaults to `"@@snapshot"`, mirroring
|
|
84
|
+
* `doStore`'s `@@state` default-key convention.
|
|
85
|
+
*/
|
|
86
|
+
interface SnapshotConfig {
|
|
87
|
+
/**
|
|
88
|
+
* Write a checkpoint once this many progress units have accumulated since the
|
|
89
|
+
* last write. `every: 1` checkpoints on every `record`; a value `<= 0` is
|
|
90
|
+
* treated as `1` (every progress unit triggers a write) — a non-positive
|
|
91
|
+
* cadence has no other sensible meaning, and clamping keeps `record` from
|
|
92
|
+
* never checkpointing on a misconfigured `0`.
|
|
93
|
+
*/
|
|
94
|
+
readonly every: number;
|
|
95
|
+
/** Store key the rolling checkpoint is written under. Defaults to `"@@snapshot"`. */
|
|
96
|
+
readonly key?: string;
|
|
97
|
+
}
|
|
98
|
+
/**
|
|
99
|
+
* The snapshot bookkeeping slice. Plain data — JSON-serializable so it rides
|
|
100
|
+
* inside the consumer's `Store<S>` blob and survives DO eviction / reload (the
|
|
101
|
+
* "durable" non-negotiable). It carries NO payload: the thing being
|
|
102
|
+
* checkpointed lives elsewhere in the machine's state; this slice only tracks
|
|
103
|
+
* *when* to checkpoint and *what has been acknowledged durable*.
|
|
104
|
+
*
|
|
105
|
+
* - `sinceLast` — progress units accumulated since the last checkpoint
|
|
106
|
+
* DECISION (reset to 0 the moment `record` decides to write, not when the
|
|
107
|
+
* write lands). The cadence counter.
|
|
108
|
+
* - `seq` — monotonic checkpoint sequence. Bumped each time a write is
|
|
109
|
+
* emitted, so the write Cmd and its later acknowledgement can be matched
|
|
110
|
+
* even if several are in flight. The "version" of the latest emitted
|
|
111
|
+
* checkpoint.
|
|
112
|
+
* - `lastSavedSeq` — the highest `seq` whose write the host has confirmed
|
|
113
|
+
* durable (via `confirm`). `null` until the first confirmed write. The gap
|
|
114
|
+
* `seq - lastSavedSeq` is the "writes emitted but not yet acknowledged"
|
|
115
|
+
* depth — observable for backpressure / health.
|
|
116
|
+
* - `lastSavedAt` — the `at` of the most recent confirmed write (epoch ms),
|
|
117
|
+
* `null` until the first. Carried for observability, never compared inside
|
|
118
|
+
* a verb (time is injected, never read here).
|
|
119
|
+
*/
|
|
120
|
+
interface SnapshotState {
|
|
121
|
+
readonly sinceLast: number;
|
|
122
|
+
readonly seq: number;
|
|
123
|
+
readonly lastSavedSeq: number | null;
|
|
124
|
+
readonly lastSavedAt: number | null;
|
|
125
|
+
}
|
|
126
|
+
/**
|
|
127
|
+
* The abstract checkpoint sink — the intersection of R2's and KV's surface,
|
|
128
|
+
* narrowed to exactly the two methods a rolling checkpoint needs. Deliberately
|
|
129
|
+
* NOT the substrate's `Store<S>` (`{ load, save, migrate }`): that is the
|
|
130
|
+
* runtime's whole-state persistence boundary keyed at a fixed cell, whereas a
|
|
131
|
+
* checkpoint store is keyed, value-typed, and host-supplied.
|
|
132
|
+
*
|
|
133
|
+
* - `put(key, value)` — write the checkpoint. Last-write-wins on `key`.
|
|
134
|
+
* - `get(key)` — read the latest checkpoint back, or `null` if none. Used by
|
|
135
|
+
* a recovery path to seed `init`'s `loaded` from the durable checkpoint
|
|
136
|
+
* when the runtime's own `Store<S>` blob is gone.
|
|
137
|
+
*
|
|
138
|
+
* No `@cloudflare/workers-types` import — an R2 bucket, a KV namespace, a DO
|
|
139
|
+
* sub-key, or an in-memory `Map` all satisfy this shape with a 3-line adapter
|
|
140
|
+
* at the host.
|
|
141
|
+
*/
|
|
142
|
+
interface SnapshotStore<V> {
|
|
143
|
+
get(key: string): Promise<V | null>;
|
|
144
|
+
put(key: string, value: V): Promise<void>;
|
|
145
|
+
}
|
|
146
|
+
/**
|
|
147
|
+
* The ports `handlers` binds. Just the store today — named so a future port
|
|
148
|
+
* (a metrics sink, a clock) can grow without breaking the `handlers(ports)`
|
|
149
|
+
* call shape.
|
|
150
|
+
*/
|
|
151
|
+
interface SnapshotPorts<V> {
|
|
152
|
+
readonly store: SnapshotStore<V>;
|
|
153
|
+
}
|
|
154
|
+
/**
|
|
155
|
+
* The checkpoint-write Cmd a `record` / `force` decision emits. Plain data
|
|
156
|
+
* (invariant 1 — Cmds are data, not closures): it carries the `payload` to
|
|
157
|
+
* write, the `key` to write it under, the `seq` it represents, and the `at` it
|
|
158
|
+
* was decided. The handler performs the `put` and routes the result back to a
|
|
159
|
+
* `confirm` / fail Msg.
|
|
160
|
+
*
|
|
161
|
+
* `payload` is `V` — the consumer's serializable checkpoint value (the run
|
|
162
|
+
* state, the cursor, whatever the machine wants restartable). The verb takes it
|
|
163
|
+
* as an argument so the snapshot slice never has to hold a copy of the
|
|
164
|
+
* machine's domain state.
|
|
165
|
+
*/
|
|
166
|
+
type SnapshotWriteCmd<V> = Cmd<"snapshot_write"> & {
|
|
167
|
+
readonly key: string;
|
|
168
|
+
readonly seq: number;
|
|
169
|
+
readonly at: number;
|
|
170
|
+
readonly payload: V;
|
|
171
|
+
};
|
|
172
|
+
/**
|
|
173
|
+
* The Msg the write handler dispatches on a SUCCESSFUL `put`. Fold it back
|
|
174
|
+
* through `confirm(state, msg)` to advance `lastSavedSeq` / `lastSavedAt`. The
|
|
175
|
+
* `seq` matches the emitting Cmd so a late acknowledgement of an older write
|
|
176
|
+
* can never regress a newer confirmed one.
|
|
177
|
+
*/
|
|
178
|
+
type SnapshotSavedMsg = {
|
|
179
|
+
readonly type: "snapshot_saved";
|
|
180
|
+
readonly seq: number;
|
|
181
|
+
readonly at: number;
|
|
182
|
+
};
|
|
183
|
+
/**
|
|
184
|
+
* The Msg the write handler dispatches on a FAILED `put`. Errors are data
|
|
185
|
+
* (never swallowed): the consumer's reducer decides whether a failed checkpoint
|
|
186
|
+
* is fatal, retryable (compose `@demlik/tea/retry-backoff`), or merely logged.
|
|
187
|
+
* `seq` identifies which checkpoint failed; `error` is carried, never
|
|
188
|
+
* interpreted here.
|
|
189
|
+
*/
|
|
190
|
+
type SnapshotFailedMsg = {
|
|
191
|
+
readonly type: "snapshot_failed";
|
|
192
|
+
readonly seq: number;
|
|
193
|
+
readonly error: unknown;
|
|
194
|
+
};
|
|
195
|
+
/**
|
|
196
|
+
* The checkpoint-READ Cmd a `requestLoad` decision emits — the recovery half of
|
|
197
|
+
* the module. Plain data (invariant 1): it carries only the `key` to read. The
|
|
198
|
+
* handler performs `store.get(key)` and routes the result back to a
|
|
199
|
+
* `snapshot_loaded` (Ok, payload-or-null) / `snapshot_load_failed` (Err) Msg.
|
|
200
|
+
*
|
|
201
|
+
* Distinct from `snapshot_write`: a write carries a payload OUT to the store; a
|
|
202
|
+
* load asks the store to hand a payload BACK. The consumer dispatches the
|
|
203
|
+
* triggering Msg once on resume (a `boot` Msg, never `init`'s rehydrate branch —
|
|
204
|
+
* invariant 2), folds the recovered payload into its run slice, then continues.
|
|
205
|
+
*/
|
|
206
|
+
type SnapshotLoadCmd = Cmd<"snapshot_load"> & {
|
|
207
|
+
readonly key: string;
|
|
208
|
+
};
|
|
209
|
+
/**
|
|
210
|
+
* The Msg the load handler dispatches when `store.get` RESOLVES. `payload` is
|
|
211
|
+
* the recovered checkpoint value, or `null` when the store has no checkpoint
|
|
212
|
+
* under `key` (fresh boot — never checkpointed, or a wiped store). The consumer
|
|
213
|
+
* folds this into `init`'s loaded branch: `payload === null` → keep the fresh
|
|
214
|
+
* `init` state; non-null → seed the run slice from the durable checkpoint and
|
|
215
|
+
* `boot` the snapshot slice's cadence counter.
|
|
216
|
+
*
|
|
217
|
+
* A `null` payload is NOT an error — "no checkpoint yet" is a normal first-run
|
|
218
|
+
* state, distinct from "the read failed" (`snapshot_load_failed`).
|
|
219
|
+
*/
|
|
220
|
+
type SnapshotLoadedMsg<V> = {
|
|
221
|
+
readonly type: "snapshot_loaded";
|
|
222
|
+
readonly payload: V | null;
|
|
223
|
+
};
|
|
224
|
+
/**
|
|
225
|
+
* The Msg the load handler dispatches when `store.get` REJECTS. Errors are data
|
|
226
|
+
* (never swallowed — the field-guide rule): a failed recovery read is a visible
|
|
227
|
+
* signal the consumer's reducer decides on (fatal-abort, retry via
|
|
228
|
+
* `@demlik/tea/retry-backoff`, or boot-fresh-and-log). `error` is carried, never
|
|
229
|
+
* interpreted here. Kept separate from `snapshot_loaded`'s `null` so "the store
|
|
230
|
+
* has no checkpoint" can never masquerade as "the read failed".
|
|
231
|
+
*/
|
|
232
|
+
type SnapshotLoadFailedMsg = {
|
|
233
|
+
readonly type: "snapshot_load_failed";
|
|
234
|
+
readonly error: unknown;
|
|
235
|
+
};
|
|
236
|
+
/** Construct a `snapshot_saved` Msg. Shared by the handler and tests so the wire shape can't drift. */
|
|
237
|
+
declare function snapshotSaved(seq: number, at: number): SnapshotSavedMsg;
|
|
238
|
+
/** Construct a `snapshot_failed` Msg. Shared by the handler and tests so the wire shape can't drift. */
|
|
239
|
+
declare function snapshotFailed(seq: number, error: unknown): SnapshotFailedMsg;
|
|
240
|
+
/** Construct a `snapshot_loaded` Msg. Shared by the handler and tests so the wire shape can't drift. */
|
|
241
|
+
declare function snapshotLoaded<V>(payload: V | null): SnapshotLoadedMsg<V>;
|
|
242
|
+
/** Construct a `snapshot_load_failed` Msg. Shared by the handler and tests so the wire shape can't drift. */
|
|
243
|
+
declare function snapshotLoadFailed(error: unknown): SnapshotLoadFailedMsg;
|
|
244
|
+
/**
|
|
245
|
+
* The bundle `createSnapshot` returns — the uniform knob contract. `subs` is
|
|
246
|
+
* omitted: a checkpoint is driven by progress (a `record` call from the
|
|
247
|
+
* consumer's reducer), not by a timer, so this module has no subscriptions.
|
|
248
|
+
*/
|
|
249
|
+
interface SnapshotKnob<V> {
|
|
250
|
+
/** The initial slice — zero progress, no checkpoint emitted or confirmed yet. */
|
|
251
|
+
init(): SnapshotState;
|
|
252
|
+
/**
|
|
253
|
+
* Account one unit of progress. PURE. Increments `sinceLast`; once the
|
|
254
|
+
* accumulated count reaches the configured cadence, DECIDES to checkpoint:
|
|
255
|
+
* resets `sinceLast` to 0, bumps `seq`, and emits a single `snapshot_write`
|
|
256
|
+
* Cmd carrying `payload` + `key` + the new `seq` + `at`. Below the cadence it
|
|
257
|
+
* returns the bumped slice and NO Cmd.
|
|
258
|
+
*
|
|
259
|
+
* `payload` is the checkpoint value to persist (the consumer's domain state);
|
|
260
|
+
* `at` is the caller-stamped time the progress occurred (epoch ms) — injected,
|
|
261
|
+
* never read from the clock here.
|
|
262
|
+
*/
|
|
263
|
+
record(state: SnapshotState, payload: V, at: number): readonly [SnapshotState, readonly Cmd[]];
|
|
264
|
+
/**
|
|
265
|
+
* Force a checkpoint regardless of the cadence counter. PURE. Always bumps
|
|
266
|
+
* `seq`, resets `sinceLast`, and emits the write Cmd. Use on a phase boundary
|
|
267
|
+
* the cadence would otherwise miss — e.g. "the run just finished, checkpoint
|
|
268
|
+
* the terminal state now" — so the last durable checkpoint is the final state,
|
|
269
|
+
* not whatever the modulo happened to land on.
|
|
270
|
+
*/
|
|
271
|
+
force(state: SnapshotState, payload: V, at: number): readonly [SnapshotState, readonly Cmd[]];
|
|
272
|
+
/**
|
|
273
|
+
* Record a confirmed durable write. PURE. Advances `lastSavedSeq` /
|
|
274
|
+
* `lastSavedAt` to the acknowledged `seq` / `at`, but only forward: an
|
|
275
|
+
* out-of-order acknowledgement of an OLDER `seq` (a slow write landing after a
|
|
276
|
+
* newer one) is ignored, so the confirmed watermark is monotonic. Fold the
|
|
277
|
+
* `snapshot_saved` Msg here.
|
|
278
|
+
*
|
|
279
|
+
* Returns the reducer-cell shape `readonly [SnapshotState, readonly Cmd[]]`
|
|
280
|
+
* (with `Cmd.none` — an ack emits nothing) for uniformity with every sibling
|
|
281
|
+
* knob, so it drops straight into a `snapshot_saved` cell without the caller
|
|
282
|
+
* re-wrapping a bare State.
|
|
283
|
+
*/
|
|
284
|
+
confirm(state: SnapshotState, msg: SnapshotSavedMsg): readonly [SnapshotState, readonly Cmd[]];
|
|
285
|
+
/**
|
|
286
|
+
* Resume after a reload. PURE. Resets `sinceLast` to 0 because any
|
|
287
|
+
* un-checkpointed progress accumulated before the crash is moot — recovery
|
|
288
|
+
* resumes from the last DURABLE checkpoint (`get` from the store), so the
|
|
289
|
+
* cadence counter starts fresh from there. `seq` / `lastSavedSeq` /
|
|
290
|
+
* `lastSavedAt` are preserved (they describe what is durable, which a reload
|
|
291
|
+
* does not change). Call from a `boot` Msg the host dispatches once after
|
|
292
|
+
* `run(...)`, never from `init`'s rehydrate branch (invariant 2).
|
|
293
|
+
*
|
|
294
|
+
* Returns the reducer-cell shape `readonly [SnapshotState, readonly Cmd[]]`
|
|
295
|
+
* (with `Cmd.none` — a resume emits nothing) for uniformity with every sibling
|
|
296
|
+
* knob, so it drops straight into a `snapshot_loaded` cell without the caller
|
|
297
|
+
* re-wrapping a bare State.
|
|
298
|
+
*/
|
|
299
|
+
boot(state: SnapshotState): readonly [SnapshotState, readonly Cmd[]];
|
|
300
|
+
/**
|
|
301
|
+
* Ask the store for the durable checkpoint. PURE. Emits a single
|
|
302
|
+
* `snapshot_load` Cmd carrying the configured `key`; returns the slice
|
|
303
|
+
* UNCHANGED (a read decides nothing about the cadence bookkeeping — only the
|
|
304
|
+
* later `snapshot_loaded` fold, via `boot`, touches the slice). This is the
|
|
305
|
+
* entry point of the recovery loop: the consumer dispatches a `boot` Msg once
|
|
306
|
+
* after `run(...)`, folds `requestLoad` here, and the emitted Cmd's handler
|
|
307
|
+
* reads `store.get(key)` and dispatches `snapshot_loaded` / `snapshot_load_failed`.
|
|
308
|
+
*
|
|
309
|
+
* Returns `readonly [SnapshotState, readonly Cmd[]]` (the reducer-cell shape)
|
|
310
|
+
* so it drops straight into a `boot: (s) => snap.requestLoad(s)` cell.
|
|
311
|
+
*/
|
|
312
|
+
requestLoad(state: SnapshotState): readonly [SnapshotState, readonly Cmd[]];
|
|
313
|
+
/**
|
|
314
|
+
* The `interpret` map for this knob's two effect Cmds:
|
|
315
|
+
*
|
|
316
|
+
* - `snapshot_write` — binds the store and performs the `put`, routing
|
|
317
|
+
* Ok → `snapshot_saved` / Err → `snapshot_failed`.
|
|
318
|
+
* - `snapshot_load` — binds the store and performs the `get`, routing
|
|
319
|
+
* Ok → `snapshot_loaded` (payload-or-null) / Err → `snapshot_load_failed`.
|
|
320
|
+
*
|
|
321
|
+
* Railway throughout — neither effect throws into the runtime; the outcome is
|
|
322
|
+
* always a Msg. Spread into your machine's `interpret`.
|
|
323
|
+
*/
|
|
324
|
+
handlers(ports: SnapshotPorts<V>): Interpret<SnapshotSavedMsg | SnapshotFailedMsg | SnapshotLoadedMsg<V> | SnapshotLoadFailedMsg, SnapshotWriteCmd<V> | SnapshotLoadCmd, unknown>;
|
|
325
|
+
}
|
|
326
|
+
/**
|
|
327
|
+
* Create a snapshot knob from a `SnapshotConfig`. The returned bundle is spliced
|
|
328
|
+
* into a consumer machine: `init()` seeds the slice, `record` / `force` are
|
|
329
|
+
* folded in reducer cells, `confirm` / `boot` handle the acknowledgement +
|
|
330
|
+
* resume Msgs, and `handlers({ store })` supplies the write `interpret` cell.
|
|
331
|
+
*/
|
|
332
|
+
declare function createSnapshot<V>(config: SnapshotConfig): SnapshotKnob<V>;
|
|
333
|
+
|
|
334
|
+
export { type SnapshotConfig, type SnapshotFailedMsg, type SnapshotKnob, type SnapshotLoadCmd, type SnapshotLoadFailedMsg, type SnapshotLoadedMsg, type SnapshotPorts, type SnapshotSavedMsg, type SnapshotState, type SnapshotStore, type SnapshotWriteCmd, createSnapshot, snapshotFailed, snapshotLoadFailed, snapshotLoaded, snapshotSaved };
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"sources":[],"names":[],"mappings":"","file":"index.js"}
|
package/dist/subs/index.d.ts
CHANGED
|
@@ -1,5 +1,6 @@
|
|
|
1
|
-
import { Sub,
|
|
2
|
-
import { S as SubscribeHandler } from '../types-
|
|
1
|
+
import { S as Sub, P as Port } from '../core-CymXZCeY.js';
|
|
2
|
+
import { S as SubscribeHandler } from '../types-N9LZbdnr.js';
|
|
3
|
+
import { Runtime } from '../index.js';
|
|
3
4
|
|
|
4
5
|
interface MinimalEvent {
|
|
5
6
|
readonly type: string;
|
|
@@ -11,6 +12,17 @@ interface MinimalEventTarget {
|
|
|
11
12
|
addEventListener(type: string, listener: (event: MinimalEvent) => void): void;
|
|
12
13
|
removeEventListener(type: string, listener: (event: MinimalEvent) => void): void;
|
|
13
14
|
}
|
|
15
|
+
interface MinimalCloseEvent extends MinimalEvent {
|
|
16
|
+
readonly code: number;
|
|
17
|
+
readonly reason: string;
|
|
18
|
+
}
|
|
19
|
+
interface MinimalWebSocket {
|
|
20
|
+
close(code?: number, reason?: string): void;
|
|
21
|
+
onopen: ((event: MinimalEvent) => void) | null;
|
|
22
|
+
onmessage: ((event: MinimalMessageEvent) => void) | null;
|
|
23
|
+
onerror: ((event: MinimalEvent) => void) | null;
|
|
24
|
+
onclose: ((event: MinimalCloseEvent) => void) | null;
|
|
25
|
+
}
|
|
14
26
|
|
|
15
27
|
type BroadcastSubData = {
|
|
16
28
|
channelName: string;
|
|
@@ -34,14 +46,11 @@ type IntervalSubData = {
|
|
|
34
46
|
};
|
|
35
47
|
declare function fromInterval<S extends Sub & IntervalSubData, M>(msgFn: (sub: S) => M): SubscribeHandler<S, M, unknown>;
|
|
36
48
|
|
|
37
|
-
declare function fromPort<S extends Sub, M, Ctx, T
|
|
49
|
+
declare function fromPort<S extends Sub, M, Ctx, T, RS = unknown, RM extends {
|
|
38
50
|
type: string;
|
|
39
|
-
}
|
|
40
|
-
|
|
41
|
-
|
|
42
|
-
delayMs: number;
|
|
43
|
-
};
|
|
44
|
-
declare function fromTimeout<S extends Sub & TimeoutSubData, M>(msgFn: (sub: S) => M): SubscribeHandler<S, M, unknown>;
|
|
51
|
+
} = {
|
|
52
|
+
type: string;
|
|
53
|
+
}>(selectRuntime: (ctx: Ctx) => Runtime<RS, RM>, port: Port<T>, msgFn: (value: T, sub: S) => M | null): SubscribeHandler<S, M, Ctx>;
|
|
45
54
|
|
|
46
55
|
type WebSocketSubData = {
|
|
47
56
|
wsUrl: string;
|
|
@@ -54,4 +63,33 @@ interface WebSocketFactoryOpts<S, M> {
|
|
|
54
63
|
}
|
|
55
64
|
declare function fromWebSocket<S extends Sub & WebSocketSubData, M>(opts: WebSocketFactoryOpts<S, M>): SubscribeHandler<S, M, unknown>;
|
|
56
65
|
|
|
57
|
-
|
|
66
|
+
/** Cancels a scheduled reconnect timer (the inverse of `schedule`). */
|
|
67
|
+
type CancelTimer = () => void;
|
|
68
|
+
interface ReconnectingWebSocketFactoryOpts<S, M> {
|
|
69
|
+
onMessage: (data: unknown, sub: S) => M | null;
|
|
70
|
+
onOpen?: (sub: S) => M | null;
|
|
71
|
+
onError?: (event: MinimalEvent, sub: S) => M | null;
|
|
72
|
+
onClose?: (code: number, reason: string, sub: S) => M | null;
|
|
73
|
+
/**
|
|
74
|
+
* Fires when a reconnection (any open after the first) succeeds, carrying the
|
|
75
|
+
* 1-based reconnect count — the resync hook. Omitting drops reconnect events
|
|
76
|
+
* silently.
|
|
77
|
+
*/
|
|
78
|
+
onReconnect?: (attempt: number, sub: S) => M | null;
|
|
79
|
+
/** First backoff delay in ms (doubles each failed attempt). Default 250. */
|
|
80
|
+
backoffBaseMs?: number;
|
|
81
|
+
/** Backoff ceiling in ms. Default 5000. */
|
|
82
|
+
backoffMaxMs?: number;
|
|
83
|
+
/** Inject the socket constructor (defaults to the global `WebSocket`); the test seam. */
|
|
84
|
+
connect?: (url: string) => MinimalWebSocket;
|
|
85
|
+
/** Inject the timer (defaults to `setTimeout`/`clearTimeout`); the test seam. */
|
|
86
|
+
schedule?: (fn: () => void, ms: number) => CancelTimer;
|
|
87
|
+
}
|
|
88
|
+
declare function fromReconnectingWebSocket<S extends Sub & WebSocketSubData, M>(opts: ReconnectingWebSocketFactoryOpts<S, M>): SubscribeHandler<S, M, unknown>;
|
|
89
|
+
|
|
90
|
+
type TimeoutSubData = {
|
|
91
|
+
delayMs: number;
|
|
92
|
+
};
|
|
93
|
+
declare function fromTimeout<S extends Sub & TimeoutSubData, M>(msgFn: (sub: S) => M): SubscribeHandler<S, M, unknown>;
|
|
94
|
+
|
|
95
|
+
export { type CancelTimer, type EventSourceFactoryOpts, type ReconnectingWebSocketFactoryOpts, SubscribeHandler, type WebSocketFactoryOpts, type WebSocketSubData, fromBroadcastChannel, fromEventSource, fromEventTarget, fromInterval, fromPort, fromReconnectingWebSocket, fromTimeout, fromWebSocket };
|
package/dist/subs/index.js
CHANGED
|
@@ -1,4 +1,4 @@
|
|
|
1
|
-
export { fromBroadcastChannel, fromEventSource, fromEventTarget, fromInterval, fromPort, fromWebSocket } from '../chunk-
|
|
1
|
+
export { fromBroadcastChannel, fromEventSource, fromEventTarget, fromInterval, fromPort, fromReconnectingWebSocket, fromWebSocket } from '../chunk-NSDFA6M6.js';
|
|
2
2
|
export { fromTimeout } from '../chunk-2YFLOCWX.js';
|
|
3
3
|
//# sourceMappingURL=index.js.map
|
|
4
4
|
//# sourceMappingURL=index.js.map
|
package/dist/testing/index.d.ts
CHANGED
|
@@ -1,4 +1,109 @@
|
|
|
1
|
-
import { Cmd, Sub, Machine
|
|
1
|
+
import { C as Cmd, S as Sub, M as Machine } from '../core-CymXZCeY.js';
|
|
2
|
+
import { Runtime } from '../index.js';
|
|
3
|
+
|
|
4
|
+
/**
|
|
5
|
+
* The composed Model shape every `withX` wrapper produces: the base machine's
|
|
6
|
+
* state nested under `base`, and the wrapper's own NAMED, serializable slice
|
|
7
|
+
* under a single `$`-prefixed key. This helper does not care WHICH `$`-key the
|
|
8
|
+
* wrapper uses — it discovers it structurally (the one non-`base` field).
|
|
9
|
+
*/
|
|
10
|
+
type WrapperModel<S> = {
|
|
11
|
+
readonly base: S;
|
|
12
|
+
} & {
|
|
13
|
+
readonly [K: `$${string}`]: unknown;
|
|
14
|
+
};
|
|
15
|
+
/** Options for the conformance replay. Mirrors `replay`'s second argument. */
|
|
16
|
+
interface AssertWrapperFaithfulOpts<S, WM, BaseM, BaseCtx, WrapperCtx> {
|
|
17
|
+
/** The Msg sequence to fold through BOTH machines. Use the base's Msgs. */
|
|
18
|
+
readonly msgs: readonly BaseM[];
|
|
19
|
+
/** Ctx for the WRAPPED machine (base ctx + wrapper ports). */
|
|
20
|
+
readonly ctx: WrapperCtx;
|
|
21
|
+
/** Ctx for the BARE base machine. Defaults to `ctx` when omitted. */
|
|
22
|
+
readonly baseCtx?: BaseCtx;
|
|
23
|
+
/** Optional pre-`init` snapshot of the COMPOSED Model (wrapped rehydrate). */
|
|
24
|
+
readonly loaded?: WM | null;
|
|
25
|
+
/**
|
|
26
|
+
* Optional pre-`init` snapshot of the BARE base state. Supply when the
|
|
27
|
+
* wrapped `loaded` is non-null so the base-equivalence check rehydrates the
|
|
28
|
+
* base machine from the matching nested slice. Defaults to `loaded?.base`.
|
|
29
|
+
*/
|
|
30
|
+
readonly baseLoaded?: S | null;
|
|
31
|
+
/**
|
|
32
|
+
* Opt-in relaxation for an INTERCEPTING wrapper (e.g. `withResilience`).
|
|
33
|
+
*
|
|
34
|
+
* DEFAULT (`undefined` / `false`) — the OBSERVE-ONLY contract that
|
|
35
|
+
* `withTelemetry` and `withDeadline` satisfy: every bare base Cmd must appear
|
|
36
|
+
* UNCHANGED, in order, as a subsequence of the wrapped Cmd stream (properties
|
|
37
|
+
* 1 and 3b). The wrapper may APPEND its own Cmds but may NOT alter, drop, or
|
|
38
|
+
* retime a base Cmd. This is the strict default and it is NOT weakened.
|
|
39
|
+
*
|
|
40
|
+
* WHEN SET — for a wrapper whose whole job is to INTERCEPT a chosen base Cmd
|
|
41
|
+
* (retag it into a wrapper-owned carrier so the concern — cache / breaker /
|
|
42
|
+
* rate-limit / retry — can gate it), a target base Cmd LEGITIMATELY does not
|
|
43
|
+
* appear raw at the top level of the wrapped stream. The contract is still
|
|
44
|
+
* **retag-and-re-emit, never swallow** (rule 2): the Cmd must be RECORDED, not
|
|
45
|
+
* vanished. So when a bare base Cmd is ABSENT from the top-level wrapped
|
|
46
|
+
* subsequence, this option accepts it IFF, in that SAME transition window, it
|
|
47
|
+
* is recorded by the wrapper as one of:
|
|
48
|
+
*
|
|
49
|
+
* - the inner payload of a `$x:*` "run" carrier Cmd (`carrierCmdType` +
|
|
50
|
+
* `innerField`) — the retag path; OR
|
|
51
|
+
* - any other `$x:*` divergence Cmd whose `recordsField` deep-equals the
|
|
52
|
+
* base Cmd — the gate/defer path (e.g. a "fast-failed" or "deferred"
|
|
53
|
+
* record Cmd that carries the base Cmd it diverged on).
|
|
54
|
+
*
|
|
55
|
+
* A target base Cmd that is NEITHER present raw NOR recorded by any `$x:*`
|
|
56
|
+
* Cmd in its window is a SWALLOW betrayal and still fails the gate.
|
|
57
|
+
*
|
|
58
|
+
* Provide the field names the carrier uses so the gate can unwrap it
|
|
59
|
+
* structurally without hard-coding `withResilience`'s vocabulary.
|
|
60
|
+
*/
|
|
61
|
+
readonly intercepting?: InterceptingOpt;
|
|
62
|
+
}
|
|
63
|
+
/**
|
|
64
|
+
* Configures the intercepting relaxation (see
|
|
65
|
+
* `AssertWrapperFaithfulOpts.intercepting`). All fields name the carrier /
|
|
66
|
+
* divergence shape so the gate can structurally find a retagged base Cmd
|
|
67
|
+
* inside a `$x:*` Cmd. The gate only EVER inspects `$x:*`-prefixed wrapped
|
|
68
|
+
* Cmds for a recorded base Cmd — it never relaxes the check for a base Cmd
|
|
69
|
+
* that appears under any non-wrapper Cmd type.
|
|
70
|
+
*/
|
|
71
|
+
interface InterceptingOpt {
|
|
72
|
+
/**
|
|
73
|
+
* The `Cmd.type` of the wrapper's run-carrier (e.g. `"$resilience:run"`).
|
|
74
|
+
* A bare base Cmd that is retagged appears as `carrier[innerField]`.
|
|
75
|
+
*/
|
|
76
|
+
readonly carrierCmdType: string;
|
|
77
|
+
/** The field on the carrier Cmd that holds the original base Cmd. */
|
|
78
|
+
readonly innerField: string;
|
|
79
|
+
/**
|
|
80
|
+
* OPTIONAL field on ANY OTHER `$x:*` Cmd that records a base Cmd the wrapper
|
|
81
|
+
* gated/deferred without running it (e.g. a fast-fail record). When present
|
|
82
|
+
* on a `$x:*` Cmd and deep-equal to the missing base Cmd, it counts as a
|
|
83
|
+
* recorded divergence. Omit when the wrapper only ever retags via the
|
|
84
|
+
* carrier.
|
|
85
|
+
*/
|
|
86
|
+
readonly recordsField?: string;
|
|
87
|
+
}
|
|
88
|
+
/**
|
|
89
|
+
* Assert a `withX` wrapper is TEA-faithful. Replays the wrapped machine and the
|
|
90
|
+
* bare base machine over the same Msgs and checks the four conformance
|
|
91
|
+
* properties (see file header).
|
|
92
|
+
*
|
|
93
|
+
* @param base the bare base `Machine`.
|
|
94
|
+
* @param makeWrapped a thunk that wraps `base` and returns the composed machine
|
|
95
|
+
* (e.g. `() => withTelemetry(base, cfg)`). A thunk — not the wrapped value —
|
|
96
|
+
* so a wrapper that registers process-scoped ports (`definePort`) is
|
|
97
|
+
* constructed inside the assertion's control, and so the same betrayal-
|
|
98
|
+
* detector reads as `assertWrapperFaithful(base, () => withX(base, cfg), ...)`
|
|
99
|
+
* at every wrapper's test site.
|
|
100
|
+
* @param opts the conformance replay options.
|
|
101
|
+
*/
|
|
102
|
+
declare function assertWrapperFaithful<S, BaseM extends {
|
|
103
|
+
type: string;
|
|
104
|
+
}, BaseC extends Cmd, BaseU extends Sub, BaseCtx, WM extends WrapperModel<S>, WrapperM extends {
|
|
105
|
+
type: string;
|
|
106
|
+
}, WrapperC extends Cmd, WrapperU extends Sub, WrapperCtx>(base: Machine<S, BaseM, BaseC, BaseU, BaseCtx>, makeWrapped: () => Machine<WM, WrapperM, WrapperC, WrapperU, WrapperCtx>, opts: AssertWrapperFaithfulOpts<S, WM, BaseM, BaseCtx, WrapperCtx>): void;
|
|
2
107
|
|
|
3
108
|
/**
|
|
4
109
|
* Shared options shape for every test assertion below. Mirrors the
|
|
@@ -217,4 +322,4 @@ declare function stateFactory<S extends {
|
|
|
217
322
|
type: string;
|
|
218
323
|
}>(defaults: StateFactoryDefaults<S>): StateFactoryAPI<S>;
|
|
219
324
|
|
|
220
|
-
export { type BoundMachine, type ReplayOpts, type StateFactoryAPI, type StateFactoryDefaults, bindMachine, expectActiveSubs, expectCmdEmitted, expectCmdSequence, expectFinalState, noopRuntime, stateFactory, step };
|
|
325
|
+
export { type AssertWrapperFaithfulOpts, type BoundMachine, type InterceptingOpt, type ReplayOpts, type StateFactoryAPI, type StateFactoryDefaults, type WrapperModel, assertWrapperFaithful, bindMachine, expectActiveSubs, expectCmdEmitted, expectCmdSequence, expectFinalState, noopRuntime, stateFactory, step };
|