@demlik/tea 0.2.0 → 0.4.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-IK4S5DJN.js → chunk-2AHAR7CX.js} +7 -14
- package/dist/chunk-2AHAR7CX.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-VFGKTGGC.js → chunk-5W7CNAHB.js} +191 -178
- package/dist/chunk-5W7CNAHB.js.map +1 -0
- package/dist/{chunk-6GIGQT6C.js → chunk-B5RAZVXW.js} +12 -11
- package/dist/chunk-B5RAZVXW.js.map +1 -0
- package/dist/{chunk-6VDCTR7N.js → chunk-D4NJBQAH.js} +258 -151
- package/dist/chunk-D4NJBQAH.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-EGWTT5XJ.js → chunk-ET6QQNSQ.js} +76 -95
- package/dist/chunk-ET6QQNSQ.js.map +1 -0
- package/dist/chunk-EWJP3YYP.js +21 -0
- package/dist/chunk-EWJP3YYP.js.map +1 -0
- package/dist/{chunk-M6UZT7HS.js → chunk-FCKP6N7Y.js} +6 -5
- package/dist/chunk-FCKP6N7Y.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-G2U7DJHL.js → chunk-JFFVBEDI.js} +3 -3
- package/dist/{chunk-G2U7DJHL.js.map → chunk-JFFVBEDI.js.map} +1 -1
- package/dist/{chunk-FOGL3TC7.js → chunk-JXYYRKD6.js} +8 -10
- package/dist/chunk-JXYYRKD6.js.map +1 -0
- package/dist/{chunk-ZDRN3YCE.js → chunk-LJJV6ZD5.js} +17 -24
- package/dist/chunk-LJJV6ZD5.js.map +1 -0
- 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-DYRKDCJI.js → chunk-PBDYW2TU.js} +41 -7
- 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-5KVHID6A.js → chunk-SPWVPIFQ.js} +4 -4
- package/dist/{chunk-5KVHID6A.js.map → chunk-SPWVPIFQ.js.map} +1 -1
- package/dist/chunk-SSHDBOXF.js +71 -0
- package/dist/chunk-SSHDBOXF.js.map +1 -0
- package/dist/{chunk-6OUDPSSG.js → chunk-TWDHNIY6.js} +3 -3
- package/dist/{chunk-6OUDPSSG.js.map → chunk-TWDHNIY6.js.map} +1 -1
- package/dist/{chunk-SC3FAFZN.js → chunk-UF4MZSLT.js} +2 -2
- package/dist/chunk-UF4MZSLT.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-BmlJOvxM.d.ts → core-42kapW0n.d.ts} +96 -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 +52 -24
- package/dist/do/index.js +94 -92
- package/dist/do/index.js.map +1 -1
- package/dist/{durable-effects-9LxFpmh3.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 -489
- 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 +10 -3
- 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-CukV8kMX.d.ts +406 -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-BoUWgwSm.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 -1
- package/dist/chunk-2YOE5PIS.js.map +0 -1
- package/dist/chunk-3545BOYL.js.map +0 -1
- package/dist/chunk-5VNWZADP.js.map +0 -1
- package/dist/chunk-6GIGQT6C.js.map +0 -1
- package/dist/chunk-6VDCTR7N.js.map +0 -1
- package/dist/chunk-DYRKDCJI.js.map +0 -1
- package/dist/chunk-EGWTT5XJ.js.map +0 -1
- package/dist/chunk-EMJTPHI7.js +0 -91
- package/dist/chunk-EMJTPHI7.js.map +0 -1
- package/dist/chunk-FOGL3TC7.js.map +0 -1
- package/dist/chunk-IK4S5DJN.js.map +0 -1
- package/dist/chunk-M6UZT7HS.js.map +0 -1
- package/dist/chunk-NSDFA6M6.js +0 -198
- package/dist/chunk-NSDFA6M6.js.map +0 -1
- package/dist/chunk-SC3FAFZN.js.map +0 -1
- package/dist/chunk-SKAMZKTE.js +0 -20
- package/dist/chunk-SKAMZKTE.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-VFGKTGGC.js.map +0 -1
- package/dist/chunk-ZDRN3YCE.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 {
|