@demlik/tea 0.1.2 → 0.3.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 +49 -0
- package/dist/agent/index.d.ts +16 -770
- package/dist/agent/index.js +18 -16
- package/dist/authed-call/index.d.ts +3 -3
- package/dist/authed-call/index.js +22 -36
- package/dist/authed-call/index.js.map +1 -1
- package/dist/await-terminal/index.d.ts +2 -2
- package/dist/await-terminal/index.js +2 -2
- package/dist/batch-window/index.d.ts +3 -3
- package/dist/batch-window/index.js +3 -3
- package/dist/cache/index.d.ts +2 -2
- package/dist/cache/index.js +5 -2
- package/dist/{chunk-3545BOYL.js → chunk-25LBDCBC.js} +6 -5
- package/dist/chunk-25LBDCBC.js.map +1 -0
- package/dist/{chunk-IN33Q4VJ.js → chunk-2KNLKQVN.js} +3 -3
- package/dist/{chunk-IN33Q4VJ.js.map → chunk-2KNLKQVN.js.map} +1 -1
- package/dist/{chunk-MMH7CW5K.js → chunk-BP2R7TPP.js} +216 -157
- package/dist/chunk-BP2R7TPP.js.map +1 -0
- package/dist/chunk-C6QC273X.js +71 -0
- package/dist/chunk-C6QC273X.js.map +1 -0
- package/dist/{chunk-7W4LMPUW.js → chunk-CAXMQJJ3.js} +17 -24
- package/dist/chunk-CAXMQJJ3.js.map +1 -0
- package/dist/{chunk-UV6UWGEO.js → chunk-D736QUGF.js} +6 -5
- package/dist/chunk-D736QUGF.js.map +1 -0
- package/dist/chunk-EWJP3YYP.js +21 -0
- package/dist/chunk-EWJP3YYP.js.map +1 -0
- package/dist/{chunk-TGG26XMK.js → chunk-FV3Z5MBE.js} +10 -5
- package/dist/chunk-FV3Z5MBE.js.map +1 -0
- package/dist/chunk-G5E74IJB.js +8 -0
- package/dist/chunk-G5E74IJB.js.map +1 -0
- package/dist/{chunk-27N2SPTN.js → chunk-IXBKFQH5.js} +12 -11
- package/dist/chunk-IXBKFQH5.js.map +1 -0
- package/dist/{chunk-SQTCS44W.js → chunk-JFFVBEDI.js} +3 -3
- package/dist/{chunk-SQTCS44W.js.map → chunk-JFFVBEDI.js.map} +1 -1
- package/dist/chunk-LYLDCIPH.js +365 -0
- package/dist/chunk-LYLDCIPH.js.map +1 -0
- package/dist/{chunk-HJO6VNQI.js → chunk-MU6MCIAC.js} +3 -3
- package/dist/{chunk-HJO6VNQI.js.map → chunk-MU6MCIAC.js.map} +1 -1
- package/dist/{chunk-UYWUVEP6.js → chunk-MUUROGIH.js} +2 -2
- package/dist/chunk-MUUROGIH.js.map +1 -0
- package/dist/{chunk-EHIX7NYZ.js → chunk-MVY5DAMF.js} +4 -4
- package/dist/{chunk-EHIX7NYZ.js.map → chunk-MVY5DAMF.js.map} +1 -1
- package/dist/{chunk-LP7VQBRG.js → chunk-NPERUUUE.js} +76 -95
- package/dist/chunk-NPERUUUE.js.map +1 -0
- package/dist/{chunk-QLA76GIT.js → chunk-PBDYW2TU.js} +68 -9
- package/dist/chunk-PBDYW2TU.js.map +1 -0
- package/dist/{chunk-5VNWZADP.js → chunk-Q2UM7BXU.js} +18 -7
- package/dist/chunk-Q2UM7BXU.js.map +1 -0
- package/dist/chunk-RMX4EOQK.js +26 -0
- package/dist/chunk-RMX4EOQK.js.map +1 -0
- package/dist/{chunk-2YOE5PIS.js → chunk-S2EGRPCM.js} +8 -6
- package/dist/chunk-S2EGRPCM.js.map +1 -0
- package/dist/{chunk-ON24RKSE.js → chunk-S4O26D7B.js} +7 -14
- package/dist/chunk-S4O26D7B.js.map +1 -0
- package/dist/{chunk-3XJWC7VW.js → chunk-SK5JGUY7.js} +3 -3
- package/dist/{chunk-3XJWC7VW.js.map → chunk-SK5JGUY7.js.map} +1 -1
- package/dist/{chunk-5DEQ5I3U.js → chunk-TPGASTFF.js} +8 -10
- package/dist/chunk-TPGASTFF.js.map +1 -0
- package/dist/{chunk-SC3FAFZN.js → chunk-UF4MZSLT.js} +2 -2
- package/dist/chunk-UF4MZSLT.js.map +1 -0
- package/dist/{chunk-PS23H6KQ.js → chunk-VBUCHVCV.js} +6 -5
- package/dist/chunk-VBUCHVCV.js.map +1 -0
- package/dist/{chunk-S3FQN4SL.js → chunk-WGGAMW7Y.js} +191 -178
- package/dist/chunk-WGGAMW7Y.js.map +1 -0
- package/dist/chunk-ZJ5AOYBH.js +8 -0
- package/dist/chunk-ZJ5AOYBH.js.map +1 -0
- package/dist/{chunk-LEC4U6CX.js → chunk-ZVJZ72HY.js} +3 -3
- package/dist/{chunk-LEC4U6CX.js.map → chunk-ZVJZ72HY.js.map} +1 -1
- package/dist/circuit-breaker/index.d.ts +1 -0
- package/dist/circuit-breaker/index.js +1 -1
- package/dist/{core-BoB6t7dB.d.ts → core-42kapW0n.d.ts} +105 -6
- package/dist/deadline/index.d.ts +77 -11
- package/dist/deadline/index.js +3 -3
- package/dist/devtools/index.js +8 -28
- package/dist/devtools/index.js.map +1 -1
- package/dist/do/index.d.ts +483 -339
- package/dist/do/index.js +244 -214
- package/dist/do/index.js.map +1 -1
- package/dist/{durable-effects-CO9Uh971.d.ts → durable-effects-CFRngTNA.d.ts} +3 -3
- package/dist/extension/index.d.ts +25 -7
- package/dist/extension/index.js +4 -3
- package/dist/extension/index.js.map +1 -1
- package/dist/extension/react.js +4 -3
- package/dist/extension/react.js.map +1 -1
- package/dist/extension/subs/index.d.ts +2 -2
- package/dist/extension/subs/index.js +7 -10
- package/dist/extension/subs/index.js.map +1 -1
- package/dist/fan-out/index.d.ts +10 -10
- package/dist/fan-out/index.js +3 -3
- package/dist/idempotent-intake/index.d.ts +2 -2
- package/dist/idempotent-intake/index.js +3 -3
- package/dist/index.d.ts +50 -472
- package/dist/index.js +2 -2
- package/dist/llm-call/index.d.ts +3 -2
- package/dist/llm-call/index.js +12 -10
- package/dist/machine-B_BF4jij.d.ts +804 -0
- package/dist/machine-viz/index.d.ts +1 -1
- package/dist/machine-viz/index.js +2 -2
- package/dist/mem/index.d.ts +3 -2
- package/dist/mem/index.js.map +1 -1
- package/dist/monitored-run/index.d.ts +54 -30
- package/dist/monitored-run/index.js +7 -7
- package/dist/node/index.d.ts +3 -2
- package/dist/node/index.js +2 -3
- package/dist/node/index.js.map +1 -1
- package/dist/paginated-walk/index.d.ts +2 -2
- package/dist/paginated-walk/index.js +11 -10
- package/dist/parity/index.d.ts +6 -5
- package/dist/parity/index.js +5 -4
- package/dist/parity/index.js.map +1 -1
- package/dist/pbt/arbitraries/index.d.ts +1 -1
- package/dist/pbt/index.d.ts +1 -1
- package/dist/pbt/index.js +3 -3
- package/dist/pbt/index.js.map +1 -1
- package/dist/pbt/runners/index.d.ts +1 -1
- package/dist/pbt/runners/index.js +3 -3
- package/dist/poller/index.d.ts +59 -17
- package/dist/poller/index.js +56 -20
- package/dist/poller/index.js.map +1 -1
- package/dist/prediction/index.d.ts +1 -1
- package/dist/prediction/index.js +2 -2
- package/dist/pure/index.d.ts +1 -1
- package/dist/pure/index.js +2 -2
- package/dist/rate-limit/index.d.ts +2 -2
- package/dist/rate-limit/index.js +1 -1
- package/dist/react/index.d.ts +3 -2
- package/dist/react/index.js +2 -2
- package/dist/react/index.js.map +1 -1
- package/dist/reconciler/index.d.ts +2 -2
- package/dist/reconciler/index.js +13 -15
- package/dist/reconciler/index.js.map +1 -1
- package/dist/recorder/index.d.ts +20 -20
- package/dist/recorder/index.js +1 -1
- package/dist/resilient-call/index.d.ts +31 -6
- package/dist/resilient-call/index.js +10 -9
- package/dist/retry-backoff/index.d.ts +191 -14
- package/dist/retry-backoff/index.js +1 -1
- package/dist/retry-to-success/index.js +1 -1
- package/dist/runtime-types-gsog27b6.d.ts +337 -0
- package/dist/saga/index.d.ts +3 -2
- package/dist/saga/index.js +2 -2
- package/dist/saga/index.js.map +1 -1
- package/dist/snapshot/index.d.ts +1 -1
- package/dist/snapshot/index.js +3 -3
- package/dist/subs/index.d.ts +372 -4
- package/dist/subs/index.js +4 -1
- package/dist/testing/index.d.ts +2 -2
- package/dist/testing/index.js +2 -2
- package/dist/throttled-input/index.d.ts +2 -2
- package/dist/throttled-input/index.js +14 -13
- package/dist/throttled-input/index.js.map +1 -1
- package/dist/token-refresh/index.d.ts +1 -1
- package/dist/token-refresh/index.js +3 -3
- package/dist/trace-replay/index.d.ts +12 -7
- package/dist/trace-replay/index.js +4 -3
- package/dist/{types-B-8bcAp8.d.ts → types-KbL3W1mj.d.ts} +1 -1
- package/dist/with-deadline/index.d.ts +1 -1
- package/dist/with-deadline/index.js +2 -2
- package/dist/with-resilience/index.d.ts +21 -2
- package/dist/with-resilience/index.js +12 -23
- package/dist/with-resilience/index.js.map +1 -1
- package/dist/with-telemetry/index.d.ts +1 -1
- package/dist/with-telemetry/index.js +2 -2
- package/dist/work-queue/adapter.d.ts +2 -2
- package/dist/work-queue/adapter.js +2 -2
- package/dist/work-queue/index.d.ts +3 -2
- package/dist/work-queue/index.js +1 -1
- package/dist/work-queue/index.js.map +1 -1
- package/dist/work-queue/ops.d.ts +2 -2
- package/dist/work-queue/ops.js +1 -1
- package/dist/workflow/index.d.ts +51 -19
- package/dist/workflow/index.js +71 -52
- package/dist/workflow/index.js.map +1 -1
- package/package.json +4 -19
- package/dist/chunk-27N2SPTN.js.map +0 -1
- package/dist/chunk-2YOE5PIS.js.map +0 -1
- package/dist/chunk-3545BOYL.js.map +0 -1
- package/dist/chunk-5DEQ5I3U.js.map +0 -1
- package/dist/chunk-5VNWZADP.js.map +0 -1
- package/dist/chunk-6RGBKODV.js +0 -268
- package/dist/chunk-6RGBKODV.js.map +0 -1
- package/dist/chunk-7W4LMPUW.js.map +0 -1
- package/dist/chunk-DILMKOZ7.js +0 -91
- package/dist/chunk-DILMKOZ7.js.map +0 -1
- package/dist/chunk-K7Q23EQ4.js +0 -20
- package/dist/chunk-K7Q23EQ4.js.map +0 -1
- package/dist/chunk-LP7VQBRG.js.map +0 -1
- package/dist/chunk-MMH7CW5K.js.map +0 -1
- package/dist/chunk-NSDFA6M6.js +0 -198
- package/dist/chunk-NSDFA6M6.js.map +0 -1
- package/dist/chunk-ON24RKSE.js.map +0 -1
- package/dist/chunk-PS23H6KQ.js.map +0 -1
- package/dist/chunk-QLA76GIT.js.map +0 -1
- package/dist/chunk-S3FQN4SL.js.map +0 -1
- package/dist/chunk-SC3FAFZN.js.map +0 -1
- package/dist/chunk-TGG26XMK.js.map +0 -1
- package/dist/chunk-UV6UWGEO.js.map +0 -1
- package/dist/chunk-UYWUVEP6.js.map +0 -1
- package/dist/chunk-VLGARN6H.js +0 -202
- package/dist/chunk-VLGARN6H.js.map +0 -1
- package/dist/raft/demo.d.ts +0 -105
- package/dist/raft/demo.js +0 -172
- package/dist/raft/demo.js.map +0 -1
- package/dist/raft/index.d.ts +0 -548
- package/dist/raft/index.js +0 -7
- package/dist/raft/index.js.map +0 -1
- package/dist/raft/sim.d.ts +0 -222
- package/dist/raft/sim.js +0 -8
- package/dist/raft/sim.js.map +0 -1
- package/dist/raft/timeline.d.ts +0 -46
- package/dist/raft/timeline.js +0 -62
- package/dist/raft/timeline.js.map +0 -1
|
@@ -1,4 +1,5 @@
|
|
|
1
1
|
/**
|
|
2
|
+
* @packageDocumentation
|
|
2
3
|
* @demlik/tea/retry-backoff — exponential backoff with jitter + cap, and the
|
|
3
4
|
* retry-attempt state every fallible `interpret` handler folds over.
|
|
4
5
|
*
|
|
@@ -20,6 +21,13 @@
|
|
|
20
21
|
* const delayMs = nextDelayMs(retry, policy); // pure: rng injected at call site
|
|
21
22
|
* return [{ ...state, retry }, [{ type: "ScheduleRetry", delayMs, into: TimerFiredRetry }]];
|
|
22
23
|
*
|
|
24
|
+
* and the same wiring under a wall-clock outage budget, where the failure Msg
|
|
25
|
+
* carries the instant it was observed:
|
|
26
|
+
*
|
|
27
|
+
* // policy: { baseMs: 250, factor: 2, capMs: 4_000, maxElapsedMs: 1_050_000, jitter: "full" }
|
|
28
|
+
* const retry = recordFailure(state.retry, msg.error, msg.at);
|
|
29
|
+
* if (!shouldRetry(retry, policy, msg.at)) return [toGaveUp(state, retryElapsedMs(retry, msg.at)), []];
|
|
30
|
+
*
|
|
23
31
|
* The reducer stays pure: `nextDelayMs` is called with the runtime's RNG only
|
|
24
32
|
* at the emit boundary (or the caller defers the whole delay computation to the
|
|
25
33
|
* `interpret` handler and passes `rng` there). Either way the substrate never
|
|
@@ -27,6 +35,42 @@
|
|
|
27
35
|
*
|
|
28
36
|
* NOT a substrate primitive: it depends on nothing from `../index`. It is a
|
|
29
37
|
* leaf utility consumers reach via the `@demlik/tea/retry-backoff` subpath.
|
|
38
|
+
*
|
|
39
|
+
* ── THE BOUND MAY BE A DURATION, NOT ONLY A COUNT ──────────────────────────
|
|
40
|
+
* `maxAttempts` was the module's only terminal bound, and a count is the wrong
|
|
41
|
+
* bound whenever this ladder nests inside somebody else's ladder. A downstream
|
|
42
|
+
* consumer proved it in production: 4 restarts over a 250ms→4s ladder is ~3.75s
|
|
43
|
+
* of backoff, which nested under a client's own ~2.5s ladder came to roughly
|
|
44
|
+
* SIXTEEN SECONDS of total patience — against a peer that waits ~17.5 MINUTES
|
|
45
|
+
* before it gives up on the other side. A routine deploy outran the budget and
|
|
46
|
+
* killed 170 runs in 7 days that the peer would have resumed from a checkpoint.
|
|
47
|
+
*
|
|
48
|
+
* The failure a count cannot express is that the QUESTION is temporal. "How
|
|
49
|
+
* long do we tolerate an unreachable peer" has an answer in seconds, usually
|
|
50
|
+
* derived from the peer's own give-up window; what a count of attempts costs in
|
|
51
|
+
* seconds depends on how long each attempt takes, which is the carrier's
|
|
52
|
+
* business and not the policy's. So a policy may now declare `maxElapsedMs` —
|
|
53
|
+
* a wall-clock outage budget measured from the FIRST failure of the streak —
|
|
54
|
+
* instead of, or in addition to, `maxAttempts`. When both are declared, retry
|
|
55
|
+
* continues only while EVERY declared bound still permits it.
|
|
56
|
+
*
|
|
57
|
+
* TIME IS AN INPUT, NOT AN AMBIENT READ (invariant 2). The streak's origin is
|
|
58
|
+
* supplied by the caller to `recordFailure`, and `shouldRetry` is told `nowMs`;
|
|
59
|
+
* this module still never reads a clock, exactly as it never reads the RNG.
|
|
60
|
+
* The types make the wiring the only wiring that compiles: a duration-bounded
|
|
61
|
+
* policy only satisfies the `shouldRetry` overload that demands a
|
|
62
|
+
* {@link TimedRetryState} *and* a `nowMs`, so "declared an outage budget, never
|
|
63
|
+
* fed it a clock" is a type error rather than a bound that quietly never fires.
|
|
64
|
+
*
|
|
65
|
+
* A policy with NO bound at all is likewise unrepresentable by accident — the
|
|
66
|
+
* {@link RetryBudget} union has no "neither" arm; forever-retry must be spelled
|
|
67
|
+
* `unbounded: true` ({@link UnboundedRetryPolicy}).
|
|
68
|
+
*
|
|
69
|
+
* Strengthens invariant 2 (transitions are pure — the clock enters as an
|
|
70
|
+
* argument, never as a `Date.now()` inside the fold) and invariant 5
|
|
71
|
+
* (composition is by reduction — the new bound is an additive arm on the policy
|
|
72
|
+
* union and an optional argument on the verbs, so every existing count-bounded
|
|
73
|
+
* call site keeps compiling untouched).
|
|
30
74
|
*/
|
|
31
75
|
/**
|
|
32
76
|
* Jitter strategy applied to the computed backoff delay.
|
|
@@ -82,25 +126,107 @@ declare function asRng(fn: () => number, probe?: boolean): Rng;
|
|
|
82
126
|
*/
|
|
83
127
|
declare const defaultRng: Rng;
|
|
84
128
|
/**
|
|
85
|
-
*
|
|
86
|
-
*
|
|
87
|
-
* the
|
|
129
|
+
* The shape of the delay ladder, with no terminal bound attached — `baseMs`,
|
|
130
|
+
* `factor`, `capMs`, `jitter`. Split out from the policy because the curve and
|
|
131
|
+
* the bound answer different questions: the curve is "how fast do we come
|
|
132
|
+
* back", the bound is "when do we stop". `backoffDelay` needs only the curve.
|
|
88
133
|
*/
|
|
89
|
-
interface
|
|
134
|
+
interface BackoffCurve {
|
|
90
135
|
/** Delay for attempt 0, in milliseconds, before any jitter or cap. */
|
|
91
136
|
readonly baseMs: number;
|
|
92
137
|
/** Geometric growth factor. `2` doubles the delay each attempt. */
|
|
93
138
|
readonly factor: number;
|
|
94
139
|
/** Hard ceiling, in milliseconds. The capped exponential never exceeds this. */
|
|
95
140
|
readonly capMs: number;
|
|
141
|
+
/** Jitter strategy applied to the capped exponential delay. */
|
|
142
|
+
readonly jitter: Jitter;
|
|
143
|
+
}
|
|
144
|
+
/**
|
|
145
|
+
* Bound by ATTEMPT COUNT — the original bound, and the right one when each
|
|
146
|
+
* attempt costs about the same and the caller is the outermost ladder.
|
|
147
|
+
*
|
|
148
|
+
* `maxElapsedMs: never` is what keeps this arm honest: a count-bounded policy
|
|
149
|
+
* is *declared* to have no outage budget, so it satisfies the clock-free
|
|
150
|
+
* `shouldRetry` overload. Add `maxElapsedMs` and it becomes a
|
|
151
|
+
* {@link DurationRetryPolicy}, which no longer does.
|
|
152
|
+
*/
|
|
153
|
+
interface CountBound {
|
|
96
154
|
/**
|
|
97
155
|
* Maximum number of attempts. `shouldRetry` allows attempts `0 .. maxAttempts-1`,
|
|
98
156
|
* so `maxAttempts: 3` means "try once, then retry at most twice" (attempts 0, 1, 2).
|
|
99
157
|
*/
|
|
100
158
|
readonly maxAttempts: number;
|
|
101
|
-
|
|
102
|
-
readonly
|
|
159
|
+
readonly maxElapsedMs?: never;
|
|
160
|
+
readonly unbounded?: never;
|
|
161
|
+
}
|
|
162
|
+
/**
|
|
163
|
+
* Bound by WALL-CLOCK OUTAGE DURATION, optionally also by count.
|
|
164
|
+
*
|
|
165
|
+
* `maxElapsedMs` is measured from the FIRST failure of the current streak
|
|
166
|
+
* ({@link TimedRetryState.firstFailureAtMs}), not from the last attempt: the
|
|
167
|
+
* question it answers is "how long has the far side been unreachable", which is
|
|
168
|
+
* the question a nesting caller's own patience is denominated in. Derive it
|
|
169
|
+
* from the peer's give-up window rather than restating a guess — two sides
|
|
170
|
+
* holding different answers to one ordered fact is the defect this bound
|
|
171
|
+
* exists to prevent.
|
|
172
|
+
*
|
|
173
|
+
* `maxAttempts` stays available here, and when both are present retry continues
|
|
174
|
+
* only while BOTH still permit it (see {@link shouldRetry}). Adding a count to a
|
|
175
|
+
* duration budget is a belt-and-braces guard against a pathologically fast
|
|
176
|
+
* failure loop, not a substitute for the duration.
|
|
177
|
+
*/
|
|
178
|
+
interface DurationBound {
|
|
179
|
+
/**
|
|
180
|
+
* Outage budget in wall-clock milliseconds. `shouldRetry` permits another
|
|
181
|
+
* attempt while `nowMs - firstFailureAtMs < maxElapsedMs`, however many
|
|
182
|
+
* attempts that takes.
|
|
183
|
+
*/
|
|
184
|
+
readonly maxElapsedMs: number;
|
|
185
|
+
/** Optional secondary count bound; omitted means "no count bound". */
|
|
186
|
+
readonly maxAttempts?: number;
|
|
187
|
+
readonly unbounded?: never;
|
|
188
|
+
}
|
|
189
|
+
/**
|
|
190
|
+
* No bound at all — retry forever. Deliberately verbose and deliberately the
|
|
191
|
+
* only way to say it: {@link RetryBudget} has no "neither bound declared" arm,
|
|
192
|
+
* so an infinite retry loop is something a policy author must write down, never
|
|
193
|
+
* something a forgotten field produces.
|
|
194
|
+
*/
|
|
195
|
+
interface Unbounded {
|
|
196
|
+
readonly unbounded: true;
|
|
197
|
+
readonly maxAttempts?: never;
|
|
198
|
+
readonly maxElapsedMs?: never;
|
|
103
199
|
}
|
|
200
|
+
/**
|
|
201
|
+
* The terminal bound a policy declares: a count, a duration (optionally with a
|
|
202
|
+
* count), or an explicit opt-in to neither. Exactly one arm applies, and there
|
|
203
|
+
* is no arm for "I forgot".
|
|
204
|
+
*/
|
|
205
|
+
type RetryBudget = CountBound | DurationBound | Unbounded;
|
|
206
|
+
/**
|
|
207
|
+
* A retry policy is pure configuration — the knobs of the backoff curve plus
|
|
208
|
+
* the bound that ends the retrying. It carries no mutable state; `RetryState`
|
|
209
|
+
* does. One policy is shared across all the attempts of one logical operation
|
|
210
|
+
* (often a module-scope const).
|
|
211
|
+
*
|
|
212
|
+
* `RetryPolicy` is the COUNT-bounded shape, unchanged since the module's first
|
|
213
|
+
* release: every existing `{ baseMs, factor, capMs, maxAttempts, jitter }`
|
|
214
|
+
* literal still satisfies it, `policy.maxAttempts` is still a `number` (not
|
|
215
|
+
* `number | undefined`), and `shouldRetry(state, policy)` still takes no clock.
|
|
216
|
+
* Reach for {@link DurationRetryPolicy} when the bound should be temporal, or
|
|
217
|
+
* {@link AnyRetryPolicy} when writing code generic over the bound.
|
|
218
|
+
*/
|
|
219
|
+
type RetryPolicy = BackoffCurve & CountBound;
|
|
220
|
+
/** A curve bounded by wall-clock outage duration (and optionally by count). */
|
|
221
|
+
type DurationRetryPolicy = BackoffCurve & DurationBound;
|
|
222
|
+
/** A curve with the explicit opt-in to unbounded retry. */
|
|
223
|
+
type UnboundedRetryPolicy = BackoffCurve & Unbounded;
|
|
224
|
+
/**
|
|
225
|
+
* Any well-formed policy. The parameter type for code that must work whatever
|
|
226
|
+
* bound the caller declared — `shouldRetry`'s implementation, wrappers, and
|
|
227
|
+
* consumers that take a policy from configuration.
|
|
228
|
+
*/
|
|
229
|
+
type AnyRetryPolicy = BackoffCurve & RetryBudget;
|
|
104
230
|
/**
|
|
105
231
|
* Sensible defaults: 100ms base, doubling, capped at 30s, up to 5 attempts,
|
|
106
232
|
* full jitter (the herd-avoidance default). Tuned for network calls; override
|
|
@@ -128,7 +254,7 @@ declare const defaultRetryPolicy: RetryPolicy;
|
|
|
128
254
|
* `baseMs`. `capMs` is also the floor of the clamp, so a misconfigured
|
|
129
255
|
* `capMs < baseMs` still yields a delay within `[0, capMs]`.
|
|
130
256
|
*/
|
|
131
|
-
declare function backoffDelay(attempt: number, policy:
|
|
257
|
+
declare function backoffDelay(attempt: number, policy: BackoffCurve, rng?: Rng): number;
|
|
132
258
|
/**
|
|
133
259
|
* Per-operation retry state: how many attempts have failed so far, and the most
|
|
134
260
|
* recent error. `lastError` is `unknown` because the failure can be anything a
|
|
@@ -141,21 +267,72 @@ interface RetryState {
|
|
|
141
267
|
/** The error from the most recent failure, if any. Carried, never interpreted. */
|
|
142
268
|
readonly lastError?: unknown;
|
|
143
269
|
}
|
|
144
|
-
/**
|
|
270
|
+
/**
|
|
271
|
+
* A retry state that also knows WHEN its failure streak began — the origin a
|
|
272
|
+
* duration bound is measured from. Minted by passing the observation instant to
|
|
273
|
+
* {@link recordFailure}; there is no other way to obtain one, so a streak with
|
|
274
|
+
* an outage budget but no origin is not a value that can be written down.
|
|
275
|
+
*
|
|
276
|
+
* The origin is the FIRST failure of the streak and is preserved across
|
|
277
|
+
* subsequent failures. A success resets the streak the way it always did — by
|
|
278
|
+
* returning to `initRetry()` — which drops the origin with it, so an operation
|
|
279
|
+
* that fails occasionally never accumulates outage and pays nothing for a wide
|
|
280
|
+
* `maxElapsedMs`; only an unbroken run of failures grows toward the bound.
|
|
281
|
+
*/
|
|
282
|
+
interface TimedRetryState extends RetryState {
|
|
283
|
+
/** When the streak's first failure was observed (caller's clock, epoch ms). */
|
|
284
|
+
readonly firstFailureAtMs: number;
|
|
285
|
+
}
|
|
286
|
+
/** The starting state: zero attempts, no error yet, no streak. */
|
|
145
287
|
declare function initRetry(): RetryState;
|
|
146
288
|
/**
|
|
147
289
|
* Record a failed attempt. PURE — returns a *new* state with `attempt`
|
|
148
290
|
* incremented and `lastError` set; the input `state` is never mutated.
|
|
291
|
+
*
|
|
292
|
+
* `atMs` is the instant the failure was OBSERVED, supplied by the caller (the
|
|
293
|
+
* `interpret` handler owns the clock; this module never reads one — invariant
|
|
294
|
+
* 2). Passing it starts the streak clock on the first failure and preserves the
|
|
295
|
+
* original origin on every failure after it, yielding a {@link TimedRetryState}
|
|
296
|
+
* that a duration-bounded policy can be evaluated against. Omit it and the
|
|
297
|
+
* count-bounded behaviour is bit-for-bit what it always was.
|
|
149
298
|
*/
|
|
299
|
+
declare function recordFailure(state: TimedRetryState, error: unknown, atMs?: number): TimedRetryState;
|
|
300
|
+
declare function recordFailure(state: RetryState, error: unknown, atMs: number): TimedRetryState;
|
|
150
301
|
declare function recordFailure(state: RetryState, error: unknown): RetryState;
|
|
302
|
+
/**
|
|
303
|
+
* How long the current failure streak has lasted, in milliseconds. PURE.
|
|
304
|
+
*
|
|
305
|
+
* The honest number for a give-up log — a count of attempts says nothing about
|
|
306
|
+
* how much patience it actually spent. Clamped at 0 so a caller's clock going
|
|
307
|
+
* backwards (NTP step, a rehydrated state whose origin is from a different
|
|
308
|
+
* host) reads as "no outage yet" rather than a negative duration.
|
|
309
|
+
*/
|
|
310
|
+
declare function retryElapsedMs(state: TimedRetryState, nowMs: number): number;
|
|
151
311
|
/**
|
|
152
312
|
* Whether another attempt is permitted under `policy`. PURE.
|
|
153
313
|
*
|
|
154
|
-
*
|
|
155
|
-
*
|
|
156
|
-
*
|
|
314
|
+
* Every bound the policy DECLARES must still permit the attempt; a bound it
|
|
315
|
+
* does not declare is not consulted. Concretely:
|
|
316
|
+
*
|
|
317
|
+
* - `maxAttempts` — `attempt` is the number of failures recorded so far (= the
|
|
318
|
+
* index of the next attempt); we may retry while that index is below
|
|
319
|
+
* `maxAttempts`, i.e. attempts `0 .. maxAttempts-1` are allowed and the
|
|
320
|
+
* `maxAttempts`-th is refused. Unchanged.
|
|
321
|
+
* - `maxElapsedMs` — we may retry while `nowMs - firstFailureAtMs` is below
|
|
322
|
+
* the budget, *however many attempts that takes*. A streak's first failure
|
|
323
|
+
* measures a zero-length outage and is therefore always retried, unless the
|
|
324
|
+
* budget is itself `0` (the "no patience" setting).
|
|
325
|
+
* - `unbounded: true` — always permitted.
|
|
326
|
+
*
|
|
327
|
+
* The overloads are the wiring contract. A count-bounded or explicitly
|
|
328
|
+
* unbounded policy needs no clock, so the two-argument call every existing
|
|
329
|
+
* caller already writes keeps type-checking. A policy carrying `maxElapsedMs`
|
|
330
|
+
* matches only the second overload, which demands a {@link TimedRetryState} and
|
|
331
|
+
* a `nowMs` — so declaring an outage budget and then never feeding it a clock
|
|
332
|
+
* fails to compile instead of silently producing a bound that never fires.
|
|
157
333
|
*/
|
|
158
|
-
declare function shouldRetry(state: RetryState, policy:
|
|
334
|
+
declare function shouldRetry(state: RetryState, policy: BackoffCurve & (CountBound | Unbounded), nowMs?: number): boolean;
|
|
335
|
+
declare function shouldRetry(state: TimedRetryState, policy: AnyRetryPolicy, nowMs: number): boolean;
|
|
159
336
|
/**
|
|
160
337
|
* The delay to wait before the next attempt, given the current state. PURE.
|
|
161
338
|
*
|
|
@@ -163,6 +340,6 @@ declare function shouldRetry(state: RetryState, policy: RetryPolicy): boolean;
|
|
|
163
340
|
* attempt index, so the delay grows with each recorded failure. `rng` is
|
|
164
341
|
* injected for determinism, same contract as `backoffDelay`.
|
|
165
342
|
*/
|
|
166
|
-
declare function nextDelayMs(state: RetryState, policy:
|
|
343
|
+
declare function nextDelayMs(state: RetryState, policy: BackoffCurve, rng?: Rng): number;
|
|
167
344
|
|
|
168
|
-
export { type Jitter, type RetryPolicy, type RetryState, type Rng, asRng, backoffDelay, defaultRetryPolicy, defaultRng, initRetry, nextDelayMs, recordFailure, shouldRetry };
|
|
345
|
+
export { type AnyRetryPolicy, type BackoffCurve, type CountBound, type DurationBound, type DurationRetryPolicy, type Jitter, type RetryBudget, type RetryPolicy, type RetryState, type Rng, type TimedRetryState, type Unbounded, type UnboundedRetryPolicy, asRng, backoffDelay, defaultRetryPolicy, defaultRng, initRetry, nextDelayMs, recordFailure, retryElapsedMs, shouldRetry };
|
|
@@ -1,3 +1,3 @@
|
|
|
1
|
-
export { asRng, backoffDelay, defaultRetryPolicy, defaultRng, initRetry, nextDelayMs, recordFailure, shouldRetry } from '../chunk-
|
|
1
|
+
export { asRng, backoffDelay, defaultRetryPolicy, defaultRng, initRetry, nextDelayMs, recordFailure, retryElapsedMs, shouldRetry } from '../chunk-Q2UM7BXU.js';
|
|
2
2
|
//# sourceMappingURL=index.js.map
|
|
3
3
|
//# sourceMappingURL=index.js.map
|
|
@@ -1,4 +1,4 @@
|
|
|
1
|
-
import { defaultRng, initRetry, recordFailure, shouldRetry, nextDelayMs } from '../chunk-
|
|
1
|
+
import { defaultRng, initRetry, recordFailure, shouldRetry, nextDelayMs } from '../chunk-Q2UM7BXU.js';
|
|
2
2
|
|
|
3
3
|
// src/retry-to-success/index.ts
|
|
4
4
|
var RetryExhaustedError = class extends Error {
|
|
@@ -0,0 +1,337 @@
|
|
|
1
|
+
import { P as Port, C as Cmd, S as Sub, M as Machine, T as Transitions, R as Reducer, I as InterpretDetached, b as PortEmitter } from './core-42kapW0n.js';
|
|
2
|
+
|
|
3
|
+
/**
|
|
4
|
+
* @demlik/tea runtime interface surface + pure helpers — the public types,
|
|
5
|
+
* interfaces, and construction/composition helpers the runtime (`./run`)
|
|
6
|
+
* implements against: `Store`, the error-sink and supervision contracts, the
|
|
7
|
+
* `RuntimeRef` / `BootingRuntime` / `Runtime` handle hierarchy, `definePort`, the
|
|
8
|
+
* identity-typed constructors `defineMachine` / `asReducer`, and the pure tools
|
|
9
|
+
* `replay` (compose without running) and `tryInterpret` (Railway).
|
|
10
|
+
*/
|
|
11
|
+
|
|
12
|
+
declare const ReducerBrand: unique symbol;
|
|
13
|
+
type Branded<T> = T & {
|
|
14
|
+
readonly [ReducerBrand]: true;
|
|
15
|
+
};
|
|
16
|
+
/**
|
|
17
|
+
* Compile-time exhaustiveness assertion for default branches of switches over
|
|
18
|
+
* discriminated unions narrowed by hand (Sub handlers, message-bridge
|
|
19
|
+
* dispatchers, port fanouts) — TS narrows the operand to `never` only if every
|
|
20
|
+
* variant is covered, so adding one produces a compile error at the `absurd(x)`
|
|
21
|
+
* site. Strengthens invariant 7 (identity is explicit).
|
|
22
|
+
*
|
|
23
|
+
* @example
|
|
24
|
+
* switch (msg.type) {
|
|
25
|
+
* case "a": return ...;
|
|
26
|
+
* case "b": return ...;
|
|
27
|
+
* default: return absurd(msg); // compile error if a "c" is added
|
|
28
|
+
* }
|
|
29
|
+
*/
|
|
30
|
+
declare function absurd(x: never): never;
|
|
31
|
+
declare const noop: <S, M, C extends Cmd>(state: S, _msg: M) => readonly [S, readonly C[]];
|
|
32
|
+
/**
|
|
33
|
+
* Thrown by `definePort` when a name has already been registered in the current
|
|
34
|
+
* process. Symmetric with `SubIdCollisionError` (thrown by `reconcileSubs`) —
|
|
35
|
+
* both mechanize invariant 7 (identity is explicit) at the runtime layer the
|
|
36
|
+
* type system cannot reach (string names compared at runtime).
|
|
37
|
+
*/
|
|
38
|
+
declare class PortNameCollisionError extends Error {
|
|
39
|
+
readonly name = "PortNameCollisionError";
|
|
40
|
+
readonly _tag: "PortNameCollisionError";
|
|
41
|
+
constructor(portName: string);
|
|
42
|
+
}
|
|
43
|
+
/**
|
|
44
|
+
* Thrown by `reconcileSubs` when, within ONE desired subscription set, two subs
|
|
45
|
+
* share an `id` but declare different `type`s — a silent bug class the type
|
|
46
|
+
* system cannot reach (ids are strings compared at runtime). The symmetric twin
|
|
47
|
+
* of `PortNameCollisionError`: both mechanize invariant 7 (identity is explicit)
|
|
48
|
+
* at the runtime layer. Same id across transitions is the no-churn case and does
|
|
49
|
+
* NOT throw; only a within-set type conflict does.
|
|
50
|
+
*/
|
|
51
|
+
declare class SubIdCollisionError extends Error {
|
|
52
|
+
readonly name = "SubIdCollisionError";
|
|
53
|
+
readonly _tag: "SubIdCollisionError";
|
|
54
|
+
constructor(id: string, declaredType: string, conflictingType: string);
|
|
55
|
+
}
|
|
56
|
+
/**
|
|
57
|
+
* Test-only escape hatch — clears the module-level port-name registry so the
|
|
58
|
+
* collision assert in `definePort` doesn't false-positive across vitest's
|
|
59
|
+
* non-isolated runs. Production code MUST NOT call this.
|
|
60
|
+
*
|
|
61
|
+
* @internal test-only
|
|
62
|
+
*/
|
|
63
|
+
declare function __resetPortRegistry(): void;
|
|
64
|
+
/**
|
|
65
|
+
* Which otherwise-unattributable runtime path produced an error. Every fanout is
|
|
66
|
+
* throw-isolated so one bad consumer never strands its siblings, and the
|
|
67
|
+
* isolated throw routes to the `OnError` sink rather than a bare `console.error`.
|
|
68
|
+
*
|
|
69
|
+
* - `"follow-up"` — a follow-up Msg an interpret handler returned rejected when
|
|
70
|
+
* re-dispatched (the original dispatcher already resolved, so no caller).
|
|
71
|
+
* - `"stop-save"` — the final `store.save(state)` inside `stop()` threw
|
|
72
|
+
* (`stop()` resolves regardless, so without the sink this was silent loss).
|
|
73
|
+
* - `"reduce"` — the pure `update` (reducer) threw synchronously; the configured
|
|
74
|
+
* `Supervision` strategy decides what happens next, but the throw is surfaced
|
|
75
|
+
* here as data first.
|
|
76
|
+
* - `"listener"` — a `runtime.subscribe(...)` listener threw during fanout.
|
|
77
|
+
* - `"observer"` — a `runtime.observe(...)` observer threw during fanout.
|
|
78
|
+
* - `"event"` — the `events` projector or an `on(type, ...)` handler threw.
|
|
79
|
+
* - `"boot"` — an `onBoot` handler threw (boot fanout, or a late registration).
|
|
80
|
+
* - `"port-emit"` — a `subscribePort` listener threw during a port emission.
|
|
81
|
+
* - `"sub-cleanup"` — a subscription's cleanup threw (reconcile-removal or
|
|
82
|
+
* `stop()` teardown).
|
|
83
|
+
*/
|
|
84
|
+
type RuntimeErrorPhase = "follow-up" | "stop-save" | "reduce" | "listener" | "observer" | "event" | "boot" | "port-emit" | "sub-cleanup";
|
|
85
|
+
/** Context handed to an `OnError` sink alongside the error itself. */
|
|
86
|
+
interface RuntimeErrorContext {
|
|
87
|
+
readonly phase: RuntimeErrorPhase;
|
|
88
|
+
}
|
|
89
|
+
/**
|
|
90
|
+
* Sink for runtime failures that have no caller to reject at. Configured via
|
|
91
|
+
* `run(machine, { onError })`. Should be total — a throw inside the sink is
|
|
92
|
+
* itself surfaced via the default sink, so it can never re-introduce a silent
|
|
93
|
+
* failure.
|
|
94
|
+
*/
|
|
95
|
+
type OnError = (error: unknown, context: RuntimeErrorContext) => void;
|
|
96
|
+
/** The three declared reducer-throw supervision strategies. */
|
|
97
|
+
type SupervisionStrategy = "stop" | "escalate" | "restart";
|
|
98
|
+
/**
|
|
99
|
+
* Declared supervision policy for a reducer (`update`) throw, at
|
|
100
|
+
* `run(machine, { supervision })`. The bare-string shorthands (`"stop"` /
|
|
101
|
+
* `"escalate"`) are accepted for the two strategies that need no host data;
|
|
102
|
+
* `restart` MUST be the object form because it carries the `rehydrate` callback,
|
|
103
|
+
* which MUST return a valid `S` synchronously (the reducer is synchronous, so
|
|
104
|
+
* recovery is too — no suspension across the single-writer slot).
|
|
105
|
+
*/
|
|
106
|
+
type Supervision<S, M extends {
|
|
107
|
+
type: string;
|
|
108
|
+
}> = "stop" | "escalate" | {
|
|
109
|
+
readonly strategy: "stop";
|
|
110
|
+
} | {
|
|
111
|
+
readonly strategy: "escalate";
|
|
112
|
+
} | {
|
|
113
|
+
readonly strategy: "restart";
|
|
114
|
+
/**
|
|
115
|
+
* Host-provided rehydration to last-known-good state. Invoked when the
|
|
116
|
+
* reducer throws; its return value becomes the new state and the transition
|
|
117
|
+
* continues from there. Receives the pre-throw `state`, the `msg` that
|
|
118
|
+
* triggered the throw, and the thrown `error` so the host can route by
|
|
119
|
+
* cause.
|
|
120
|
+
*/
|
|
121
|
+
readonly rehydrate: (state: S, msg: M, error: unknown) => S;
|
|
122
|
+
};
|
|
123
|
+
/**
|
|
124
|
+
* Raised by `idle()` when the quiescence wait hits its iteration cap without the
|
|
125
|
+
* dispatch tail stabilizing — `idle()` REJECTS rather than silently resolving,
|
|
126
|
+
* so a livelocking machine surfaces instead of masquerading as quiescent.
|
|
127
|
+
* Mechanizes invariant 6 at the runtime layer, the role `PortNameCollisionError`
|
|
128
|
+
* plays for invariant 7.
|
|
129
|
+
*/
|
|
130
|
+
declare class QuiescenceTimeoutError extends Error {
|
|
131
|
+
readonly iterations: number;
|
|
132
|
+
readonly name = "QuiescenceTimeoutError";
|
|
133
|
+
readonly _tag: "QuiescenceTimeoutError";
|
|
134
|
+
constructor(iterations: number);
|
|
135
|
+
}
|
|
136
|
+
/**
|
|
137
|
+
* Define a typed port. `name` is metadata (devtools / debug logs); port identity
|
|
138
|
+
* is by reference. Each definePort call must use a unique `name` — a second call
|
|
139
|
+
* with the same name throws `PortNameCollisionError`. Use ports for "data leaving
|
|
140
|
+
* the runtime selectively" (cursor announcements, DOM-mutation broadcasts,
|
|
141
|
+
* telemetry) that should NOT be folded into State. Strengthens invariant 7.
|
|
142
|
+
*/
|
|
143
|
+
declare function definePort<T>(name: string): Port<T>;
|
|
144
|
+
interface Store<S> {
|
|
145
|
+
load(): Promise<unknown>;
|
|
146
|
+
save(state: S): Promise<void>;
|
|
147
|
+
migrate(raw: unknown): S | null;
|
|
148
|
+
}
|
|
149
|
+
interface Schema<S> {
|
|
150
|
+
safeParse(raw: unknown): {
|
|
151
|
+
success: true;
|
|
152
|
+
data: S;
|
|
153
|
+
} | {
|
|
154
|
+
success: false;
|
|
155
|
+
};
|
|
156
|
+
}
|
|
157
|
+
/**
|
|
158
|
+
* Build a `Store.migrate` from a schema (job 1) and an optional thin `upcast`
|
|
159
|
+
* (job 2). `upcast` maps a recognized-but-OLD raw shape forward into the shape
|
|
160
|
+
* the schema validates; it defaults to identity (no version migration yet).
|
|
161
|
+
*
|
|
162
|
+
* Never throws — a shape the schema rejects returns `null`, the substrate's
|
|
163
|
+
* fresh-boot path, per the `Store.migrate` contract. An `upcast` that itself
|
|
164
|
+
* throws on a corrupt blob is caught and collapses to `null` (same posture).
|
|
165
|
+
*/
|
|
166
|
+
declare function schemaMigrate<S>(schema: Schema<S>, upcast?: (raw: unknown) => unknown): (raw: unknown) => S | null;
|
|
167
|
+
type DispatchSettle = "quiescent" | "once";
|
|
168
|
+
interface RuntimeRef<M extends {
|
|
169
|
+
type: string;
|
|
170
|
+
}> {
|
|
171
|
+
/**
|
|
172
|
+
* Put a Msg in the runtime's inbox and resolve once processed. Runs to
|
|
173
|
+
* QUIESCENCE by default: settles only after the dispatched Msg AND every
|
|
174
|
+
* transitive interpret follow-up has drained. Pass `{ settle: "once" }` (or
|
|
175
|
+
* call `dispatchOnce`) for the single-step case.
|
|
176
|
+
*
|
|
177
|
+
* Rejection ordering: the ONE dispatched transition's own failure surfaces
|
|
178
|
+
* first, here. If it succeeds but the follow-up chain never stabilizes, the
|
|
179
|
+
* quiescent drain rejects with `QuiescenceTimeoutError`. Follow-up Msg
|
|
180
|
+
* rejections themselves route to the `onError` sink, never here.
|
|
181
|
+
*/
|
|
182
|
+
dispatch(msg: M, opts?: {
|
|
183
|
+
readonly settle?: DispatchSettle;
|
|
184
|
+
}): Promise<void>;
|
|
185
|
+
/**
|
|
186
|
+
* Dispatch `msg` and resolve after exactly ONE transition's effects settle,
|
|
187
|
+
* WITHOUT draining the follow-up chain — equivalent to
|
|
188
|
+
* `dispatch(msg, { settle: "once" })`. Prefer plain `dispatch` unless you will
|
|
189
|
+
* `await runtime.idle()` yourself; the un-awaited follow-up chain is the
|
|
190
|
+
* footgun the quiescent default removed.
|
|
191
|
+
*/
|
|
192
|
+
dispatchOnce(msg: M): Promise<void>;
|
|
193
|
+
}
|
|
194
|
+
interface BootingRuntime<S, M extends {
|
|
195
|
+
type: string;
|
|
196
|
+
}, E extends {
|
|
197
|
+
type: string;
|
|
198
|
+
} = never> extends RuntimeRef<M> {
|
|
199
|
+
dispatch(msg: M, opts?: {
|
|
200
|
+
readonly settle?: DispatchSettle;
|
|
201
|
+
}): Promise<void>;
|
|
202
|
+
dispatchOnce(msg: M): Promise<void>;
|
|
203
|
+
subscribe(listener: () => void): () => void;
|
|
204
|
+
observe(observer: (msg: M, state: S) => void): () => void;
|
|
205
|
+
/**
|
|
206
|
+
* Subscribe to the INITIAL State — the boot transition. Fires exactly once:
|
|
207
|
+
* immediately if boot already completed (so a late subscriber never misses
|
|
208
|
+
* it), otherwise on the boot fanout. Returns a cleanup (a no-op once fired).
|
|
209
|
+
*/
|
|
210
|
+
onBoot(handler: (state: S) => void): () => void;
|
|
211
|
+
/**
|
|
212
|
+
* Subscribe to a SEMANTIC event of `type`. The handler receives exactly the
|
|
213
|
+
* `E` member whose `type` matches `K`, so a consumer never touches another
|
|
214
|
+
* event's shape and never references the machine's PRIVATE Msg names. Multiple
|
|
215
|
+
* handlers per type; fanout is synchronous and throw-isolated. Fires only when
|
|
216
|
+
* the machine's `events` projector (wired on `run`) emits that type.
|
|
217
|
+
*/
|
|
218
|
+
on<K extends E["type"]>(type: K, handler: (event: Extract<E, {
|
|
219
|
+
type: K;
|
|
220
|
+
}>) => void): () => void;
|
|
221
|
+
/**
|
|
222
|
+
* Subscribe to a typed Port. Multiple listeners per port; fanout is synchronous
|
|
223
|
+
* and isolated. Ports are for "data leaving the runtime selectively" (see
|
|
224
|
+
* `definePort`) — distinct from `observe` (every transition) and State.
|
|
225
|
+
*/
|
|
226
|
+
subscribePort<T>(port: Port<T>, listener: (value: T) => void): () => void;
|
|
227
|
+
/**
|
|
228
|
+
* Emit a value on a Port from OUTSIDE an interpret handler — same synchronous
|
|
229
|
+
* fanout as `ctx.emit`. The canonical use is `observe`-driven emission (one
|
|
230
|
+
* runtime publishing a state-derived signal another subscribes to via
|
|
231
|
+
* `subscribePort`) without spawning a "tell the outside world" Cmd per
|
|
232
|
+
* transition.
|
|
233
|
+
*/
|
|
234
|
+
emitPort<T>(port: Port<T>, value: T): void;
|
|
235
|
+
/**
|
|
236
|
+
* Resolves to the booted `Runtime<S, M>` after boot completes (or rejects with
|
|
237
|
+
* the boot error). This is the ONLY way to obtain a `Runtime` — and a
|
|
238
|
+
* `Runtime` is the only handle whose `getState()` is total. Awaiting `ready` is
|
|
239
|
+
* the single gate between "boot in flight" and "State exists":
|
|
240
|
+
*
|
|
241
|
+
* ```ts
|
|
242
|
+
* const runtime = await run(machine, opts).ready;
|
|
243
|
+
* runtime.getState(); // total — never throws-before-boot
|
|
244
|
+
* ```
|
|
245
|
+
*
|
|
246
|
+
* Idempotent — the same settled promise, resolving to the same `Runtime`, comes
|
|
247
|
+
* back on every read. Expresses canon §2.3 (boot is a named, awaitable moment).
|
|
248
|
+
*/
|
|
249
|
+
ready: Promise<Runtime<S, M, E>>;
|
|
250
|
+
stop(): Promise<void>;
|
|
251
|
+
}
|
|
252
|
+
interface Runtime<S, M extends {
|
|
253
|
+
type: string;
|
|
254
|
+
}, E extends {
|
|
255
|
+
type: string;
|
|
256
|
+
} = never> extends BootingRuntime<S, M, E> {
|
|
257
|
+
/**
|
|
258
|
+
* The current State. TOTAL — never throws. Obtaining a `Runtime` requires
|
|
259
|
+
* awaiting `ready`, which only resolves AFTER boot has run `init` and set the
|
|
260
|
+
* initial State.
|
|
261
|
+
*/
|
|
262
|
+
getState(): S;
|
|
263
|
+
/** Resolves to this same `Runtime` once boot completes. Idempotent. */
|
|
264
|
+
ready: Promise<Runtime<S, M, E>>;
|
|
265
|
+
/**
|
|
266
|
+
* Resolves once the runtime has reached QUIESCENCE — every dispatched Msg AND
|
|
267
|
+
* every transitive interpret follow-up has been processed, with no further step
|
|
268
|
+
* pending on the tail. Since plain `dispatch` already runs to quiescence, this
|
|
269
|
+
* is mainly for follow-ups left by a `dispatchOnce` single step, or a chain
|
|
270
|
+
* kicked off by a Sub / boot `init` cmds with no `dispatch` to await.
|
|
271
|
+
*
|
|
272
|
+
* Idempotent and re-entrant-safe. Tail rejections are NOT surfaced here (a
|
|
273
|
+
* failing dispatch surfaces on its OWN promise; follow-up rejections route to
|
|
274
|
+
* `onError`). The one rejection `idle()` produces is `QuiescenceTimeoutError`
|
|
275
|
+
* on hitting the iteration cap — so a livelock stays distinguishable from a
|
|
276
|
+
* genuine quiesce (invariant 6). A poll is still correct when waiting on an
|
|
277
|
+
* EXTERNAL event the runtime cannot enqueue itself.
|
|
278
|
+
*/
|
|
279
|
+
idle(): Promise<void>;
|
|
280
|
+
/**
|
|
281
|
+
* The terminal State of the run, or `undefined` while in flight — "terminal"
|
|
282
|
+
* per the `terminal` predicate passed to `run()` (no predicate → never
|
|
283
|
+
* terminal → always `undefined`). The first-class result read: the run's
|
|
284
|
+
* product off the State the machine already owns, NOT scraped off the `observe`
|
|
285
|
+
* firehose by matching an internal Msg name. Total — never throws.
|
|
286
|
+
*/
|
|
287
|
+
result(): S | undefined;
|
|
288
|
+
/**
|
|
289
|
+
* Resolves with the terminal State the first time the run reaches one (per the
|
|
290
|
+
* `terminal` predicate). If ALREADY terminal, resolves immediately; otherwise
|
|
291
|
+
* on the transition that first makes `terminal` hold. The awaitable companion
|
|
292
|
+
* to `result()`. With no predicate this never resolves. Idempotent and
|
|
293
|
+
* multi-caller safe.
|
|
294
|
+
*/
|
|
295
|
+
done(): Promise<S>;
|
|
296
|
+
}
|
|
297
|
+
declare function defineMachine<S, M extends {
|
|
298
|
+
type: string;
|
|
299
|
+
}, C extends Cmd, U extends Sub, Ctx>(m: Omit<Machine<S, M, C, U, Ctx>, "update"> & {
|
|
300
|
+
update: [S] extends [{
|
|
301
|
+
type: string;
|
|
302
|
+
}] ? Transitions<S, M, C> : never;
|
|
303
|
+
}): Machine<S, M, C, U, Ctx>;
|
|
304
|
+
declare function defineMachine<S, M extends {
|
|
305
|
+
type: string;
|
|
306
|
+
}, C extends Cmd, U extends Sub, Ctx>(m: Omit<Machine<S, M, C, U, Ctx>, "update"> & {
|
|
307
|
+
update: Reducer<S, M, C>;
|
|
308
|
+
}): Machine<S, M, C, U, Ctx>;
|
|
309
|
+
declare function asReducer<S, M extends {
|
|
310
|
+
type: string;
|
|
311
|
+
}, C extends Cmd>(reducer: Reducer<S, M, C>): Branded<Reducer<S, M, C>>;
|
|
312
|
+
type CtxArg<Ctx> = [Record<never, never>] extends [Ctx] ? {
|
|
313
|
+
ctx?: Ctx;
|
|
314
|
+
} : {
|
|
315
|
+
ctx: Ctx;
|
|
316
|
+
};
|
|
317
|
+
declare function replay<S, M extends {
|
|
318
|
+
type: string;
|
|
319
|
+
}, C extends Cmd, U extends Sub, Ctx>(machine: Machine<S, M, C, U, Ctx>, opts: {
|
|
320
|
+
msgs: readonly M[];
|
|
321
|
+
ctx: Ctx;
|
|
322
|
+
loaded?: S | null;
|
|
323
|
+
}): {
|
|
324
|
+
state: S;
|
|
325
|
+
cmds: C[];
|
|
326
|
+
subs: U[];
|
|
327
|
+
depSubs: {
|
|
328
|
+
index: number;
|
|
329
|
+
id: string;
|
|
330
|
+
}[];
|
|
331
|
+
};
|
|
332
|
+
declare function wrapDetached<C extends Cmd, M extends {
|
|
333
|
+
type: string;
|
|
334
|
+
}, Allowed extends M, Ctx>(handler: InterpretDetached<C, Allowed, Ctx>): (cmd: C, ctx: Ctx & PortEmitter, dispatch?: (msg: M) => void) => Promise<void>;
|
|
335
|
+
declare function tryInterpret<C extends Cmd, Ok, M, Ctx>(work: (cmd: C, ctx: Ctx) => Promise<Ok>, onOk: (value: Ok, cmd: C) => M, onErr: (error: unknown, cmd: C) => M): (cmd: C, ctx: Ctx) => Promise<M>;
|
|
336
|
+
|
|
337
|
+
export { type BootingRuntime as B, type CtxArg as C, type DispatchSettle as D, type OnError as O, PortNameCollisionError as P, QuiescenceTimeoutError as Q, type Runtime as R, type Store as S, __resetPortRegistry as _, type Supervision as a, type Branded as b, absurd as c, SubIdCollisionError as d, type RuntimeErrorPhase as e, type RuntimeErrorContext as f, type SupervisionStrategy as g, definePort as h, type Schema as i, type RuntimeRef as j, defineMachine as k, asReducer as l, noop as n, replay as r, schemaMigrate as s, tryInterpret as t, wrapDetached as w };
|
package/dist/saga/index.d.ts
CHANGED
|
@@ -1,8 +1,9 @@
|
|
|
1
|
-
import { C as Cmd } from '../core-
|
|
1
|
+
import { C as Cmd } from '../core-42kapW0n.js';
|
|
2
2
|
import { QueueItem } from '../work-queue/index.js';
|
|
3
|
-
import '../
|
|
3
|
+
import '../runtime-types-gsog27b6.js';
|
|
4
4
|
|
|
5
5
|
/**
|
|
6
|
+
* @packageDocumentation
|
|
6
7
|
* @demlik/tea/saga — a forward-then-compensate transaction over an ordered list
|
|
7
8
|
* of reversible steps.
|
|
8
9
|
*
|
package/dist/saga/index.js
CHANGED