@zap-studio/retry 1.2.1 → 2.1.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/CHANGELOG.md +16 -0
- package/README.md +46 -13
- package/dist/base-policy-DkxBg9dh.js +454 -0
- package/dist/base-policy-DkxBg9dh.js.map +1 -0
- package/dist/base-policy.d.ts +41 -2
- package/dist/base-policy.d.ts.map +1 -1
- package/dist/base-policy.js +3 -334
- package/dist/errors-CS5UPJWs.d.ts.map +1 -1
- package/dist/errors.js.map +1 -1
- package/dist/exponential-backoff.d.ts.map +1 -1
- package/dist/exponential-backoff.js +0 -5
- package/dist/exponential-backoff.js.map +1 -1
- package/dist/fixed-delay.d.ts.map +1 -1
- package/dist/fixed-delay.js.map +1 -1
- package/dist/index.d.ts +3 -3
- package/dist/index.js +2 -2
- package/dist/jitter.d.ts.map +1 -1
- package/dist/jitter.js.map +1 -1
- package/dist/linear-backoff.d.ts.map +1 -1
- package/dist/linear-backoff.js +0 -5
- package/dist/linear-backoff.js.map +1 -1
- package/dist/types.d.ts +12 -1
- package/dist/types.d.ts.map +1 -1
- package/package.json +12 -4
- package/dist/base-policy.js.map +0 -1
package/dist/base-policy.d.ts
CHANGED
|
@@ -1,4 +1,6 @@
|
|
|
1
|
-
import {
|
|
1
|
+
import { r as RetryError, t as AbortError } from "./errors-CS5UPJWs.js";
|
|
2
|
+
import { RetryPolicy, RetryRunOptions, RetryRunResult, RetryRunResultOptions } from "./types.js";
|
|
3
|
+
import { ResultAsync } from "@zap-studio/monads";
|
|
2
4
|
//#region src/base-policy.d.ts
|
|
3
5
|
/**
|
|
4
6
|
* Awaits a timer-based delay, unless `delayMs` is non-positive.
|
|
@@ -62,6 +64,43 @@ declare function runRetryPolicy<T, TError extends Error = Error, TData = unknown
|
|
|
62
64
|
declare function runRetryPolicy<T, TError extends Error = Error, TData = unknown>(policy: RetryPolicy<TError, TData>, execute: (attempt: number) => Promise<T>, options?: RetryRunOptions & {
|
|
63
65
|
throwOnExhausted?: true;
|
|
64
66
|
}): Promise<T>;
|
|
67
|
+
/**
|
|
68
|
+
* Runs retry orchestration, returning a `ResultAsync` instead of throwing or
|
|
69
|
+
* returning the hand-rolled {@link RetryRunResult} union.
|
|
70
|
+
*
|
|
71
|
+
* Additive alternative to `runRetryPolicy` for consumers who prefer explicit
|
|
72
|
+
* `Result`/`ResultAsync` values (from `@zap-studio/monads`) over throw/catch.
|
|
73
|
+
* There's no `throwOnExhausted` option — this function always returns a
|
|
74
|
+
* `Result`, so the flag doesn't apply.
|
|
75
|
+
*
|
|
76
|
+
* @param policy - Retry policy: `next` is required, `onExhausted` and
|
|
77
|
+
* `isKnownError` fall back to their defaults when omitted.
|
|
78
|
+
* @param execute - Async function to execute per attempt.
|
|
79
|
+
* @param options - Runner settings, same as {@link RetryRunOptions} minus
|
|
80
|
+
* `throwOnExhausted`.
|
|
81
|
+
* @returns A `ResultAsync` resolving to `Ok` with the successful value, or `Err`
|
|
82
|
+
* with a `RetryError` (exhaustion) or `AbortError` (cancellation) — the same
|
|
83
|
+
* error object `runRetryPolicy`'s throw mode would throw. When
|
|
84
|
+
* `policy.isKnownError` rejects a caught value, it is wrapped in a new
|
|
85
|
+
* `RetryError` and returned on `Err` instead — in throw mode that same value
|
|
86
|
+
* is rethrown unchanged, not wrapped.
|
|
87
|
+
* @throws {Error} Any error thrown by `next`, `onExhausted`, or a custom `sleep`
|
|
88
|
+
* function.
|
|
89
|
+
*
|
|
90
|
+
* @example
|
|
91
|
+
* ```ts
|
|
92
|
+
* import { runRetryPolicyResult } from "@zap-studio/retry";
|
|
93
|
+
*
|
|
94
|
+
* const result = await runRetryPolicyResult(policy, async () => fetchFlakyResource());
|
|
95
|
+
*
|
|
96
|
+
* if (isOk(result)) {
|
|
97
|
+
* console.log(result.value);
|
|
98
|
+
* } else {
|
|
99
|
+
* console.error(result.error);
|
|
100
|
+
* }
|
|
101
|
+
* ```
|
|
102
|
+
*/
|
|
103
|
+
declare const runRetryPolicyResult: <T, TError extends Error = Error, TData = unknown>(policy: RetryPolicy<TError, TData>, execute: (attempt: number) => Promise<T>, options?: RetryRunResultOptions) => ResultAsync<T, RetryError | AbortError>;
|
|
65
104
|
//#endregion
|
|
66
|
-
export { defaultSleep, runRetryPolicy };
|
|
105
|
+
export { defaultSleep, runRetryPolicy, runRetryPolicyResult };
|
|
67
106
|
//# sourceMappingURL=base-policy.d.ts.map
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"base-policy.d.ts","names":[],"sources":["../src/base-policy.ts"],"mappings":"
|
|
1
|
+
{"version":3,"file":"base-policy.d.ts","names":[],"sources":["../src/base-policy.ts"],"mappings":";;;;;;;;;;;;;;;;;cAsCa,eAAsB,oBAAkB;;;;;;;;;;;;;iBAwerC,eAAe,GAAG,eAAe,QAAQ,OAAO,iBAC9D,QAAQ,YAAY,QAAQ,QAC5B,UAAU,oBAAoB,QAAQ,IACtC,SAAS;EAAoB;IAC5B,QAAQ,eAAe;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;iBAgCV,eAAe,GAAG,eAAe,QAAQ,OAAO,iBAC9D,QAAQ,YAAY,QAAQ,QAC5B,UAAU,oBAAoB,QAAQ,IACtC,UAAU;EAAoB;IAC7B,QAAQ;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;cA4EE,uBAAwB,GAAG,eAAe,QAAQ,OAAO,iBACpE,QAAQ,YAAY,QAAQ,QAC5B,UAAU,oBAAoB,QAAQ,IACtC,UAAS,0BACR,YAAY,GAAG,aAAa"}
|
package/dist/base-policy.js
CHANGED
|
@@ -1,334 +1,3 @@
|
|
|
1
|
-
import {
|
|
2
|
-
|
|
3
|
-
|
|
4
|
-
* Awaits a timer-based delay, unless `delayMs` is non-positive.
|
|
5
|
-
*
|
|
6
|
-
* @param delayMs - Milliseconds to wait before resolving.
|
|
7
|
-
* @returns Promise that resolves when the delay completes.
|
|
8
|
-
*
|
|
9
|
-
* @example
|
|
10
|
-
* ```ts
|
|
11
|
-
* import { defaultSleep } from "@zap-studio/retry";
|
|
12
|
-
*
|
|
13
|
-
* await defaultSleep(250); // waits 250ms
|
|
14
|
-
* ```
|
|
15
|
-
*/
|
|
16
|
-
const defaultSleep = async (delayMs) => {
|
|
17
|
-
if (delayMs <= 0) return;
|
|
18
|
-
await new Promise((resolve) => {
|
|
19
|
-
setTimeout(resolve, delayMs);
|
|
20
|
-
});
|
|
21
|
-
};
|
|
22
|
-
/**
|
|
23
|
-
* Normalizes an abort `reason` into an `AbortError`.
|
|
24
|
-
*/
|
|
25
|
-
const toAbortError = (reason) => {
|
|
26
|
-
if (reason instanceof AbortError) return reason;
|
|
27
|
-
if (reason instanceof Error) return new AbortError(reason.message, { cause: reason });
|
|
28
|
-
if (typeof reason === "string" && reason.length > 0) return new AbortError(reason);
|
|
29
|
-
if (reason === void 0) return new AbortError("Retry aborted.");
|
|
30
|
-
try {
|
|
31
|
-
return new AbortError(`Retry aborted: ${JSON.stringify(reason)}`);
|
|
32
|
-
} catch {
|
|
33
|
-
return new AbortError("Retry aborted.");
|
|
34
|
-
}
|
|
35
|
-
};
|
|
36
|
-
/**
|
|
37
|
-
* Throws when the provided abort signal is already aborted.
|
|
38
|
-
*
|
|
39
|
-
* @param signal - Optional abort signal to inspect.
|
|
40
|
-
* @param logger - Optional logger; logs the abort at `debug` before throwing.
|
|
41
|
-
* @throws {AbortError} When the signal is aborted.
|
|
42
|
-
*/
|
|
43
|
-
const throwIfAborted = (signal, logger) => {
|
|
44
|
-
if (signal?.aborted !== true) return;
|
|
45
|
-
logger?.debug("retry aborted", { reason: signal.reason });
|
|
46
|
-
throw toAbortError(signal.reason);
|
|
47
|
-
};
|
|
48
|
-
/**
|
|
49
|
-
* Waits for delay sleep while observing cancellation through an abort signal.
|
|
50
|
-
*
|
|
51
|
-
* @param sleep - Sleep function used to await `delayMs`.
|
|
52
|
-
* @param delayMs - Delay duration in milliseconds.
|
|
53
|
-
* @param signal - Abort signal to observe while waiting.
|
|
54
|
-
* @returns Promise that resolves when delay finishes.
|
|
55
|
-
* @throws {AbortError} When the signal aborts before or during wait.
|
|
56
|
-
*/
|
|
57
|
-
const sleepWithAbortSignal = async (sleep, delayMs, signal) => {
|
|
58
|
-
if (signal.aborted) throw toAbortError(signal.reason);
|
|
59
|
-
let onAbort;
|
|
60
|
-
try {
|
|
61
|
-
await Promise.race([sleep(delayMs), new Promise((_resolve, reject) => {
|
|
62
|
-
onAbort = () => {
|
|
63
|
-
reject(toAbortError(signal.reason));
|
|
64
|
-
};
|
|
65
|
-
signal.addEventListener("abort", onAbort, { once: true });
|
|
66
|
-
})]);
|
|
67
|
-
} finally {
|
|
68
|
-
if (onAbort) signal.removeEventListener("abort", onAbort);
|
|
69
|
-
}
|
|
70
|
-
};
|
|
71
|
-
/**
|
|
72
|
-
* Logs a `next(...)` decision: `debug` when retrying, `warn` when exhausted.
|
|
73
|
-
* Shared by both the throw-mode and non-throw retry loops.
|
|
74
|
-
*/
|
|
75
|
-
const logRetryDecision = (logger, attempt, decision, error) => {
|
|
76
|
-
if (decision.shouldRetry) {
|
|
77
|
-
logger?.debug("retry scheduled", {
|
|
78
|
-
attempt,
|
|
79
|
-
delayMs: decision.delayMs,
|
|
80
|
-
reason: decision.reason
|
|
81
|
-
});
|
|
82
|
-
return;
|
|
83
|
-
}
|
|
84
|
-
logger?.warn("retry policy exhausted", {
|
|
85
|
-
attempts: attempt,
|
|
86
|
-
error,
|
|
87
|
-
reason: decision.reason
|
|
88
|
-
});
|
|
89
|
-
};
|
|
90
|
-
/**
|
|
91
|
-
* Runs the throw-mode retry loop: throws `RetryError` on exhaustion and
|
|
92
|
-
* `AbortError` when `signal` aborts.
|
|
93
|
-
*
|
|
94
|
-
* @param policy - Resolved retry policy providing `next` and `onExhausted`.
|
|
95
|
-
* @param execute - Async work callback per attempt.
|
|
96
|
-
* @param sleep - Delay function between retries.
|
|
97
|
-
* @param signal - Optional cancel signal.
|
|
98
|
-
* @param logger - Optional logger; logs each retry decision at `debug` and
|
|
99
|
-
* exhaustion at `warn`.
|
|
100
|
-
* @returns Resolves to the first successful return value.
|
|
101
|
-
* @throws {RetryError} When retries are exhausted and `onExhausted` returns
|
|
102
|
-
* the terminal error.
|
|
103
|
-
* @throws {AbortError} When `signal` is already aborted or aborts while waiting.
|
|
104
|
-
* @throws {Error} Any error thrown by `next`, `onExhausted`, or `sleep`. Also
|
|
105
|
-
* rethrows the original caught value immediately, bypassing retry, when
|
|
106
|
-
* `policy.isKnownError` rejects it as outside this policy's error domain.
|
|
107
|
-
*/
|
|
108
|
-
const runThrowMode = async (policy, execute, sleep, signal, logger) => {
|
|
109
|
-
let attempt = 1;
|
|
110
|
-
while (true) {
|
|
111
|
-
throwIfAborted(signal, logger);
|
|
112
|
-
try {
|
|
113
|
-
return await execute(attempt);
|
|
114
|
-
} catch (error) {
|
|
115
|
-
throwIfAborted(signal, logger);
|
|
116
|
-
if (!policy.isKnownError(error)) throw error;
|
|
117
|
-
const decision = policy.next({
|
|
118
|
-
attempt,
|
|
119
|
-
error
|
|
120
|
-
});
|
|
121
|
-
logRetryDecision(logger, attempt, decision, error);
|
|
122
|
-
if (!decision.shouldRetry) throw policy.onExhausted({
|
|
123
|
-
attempts: attempt,
|
|
124
|
-
error
|
|
125
|
-
});
|
|
126
|
-
if (decision.delayMs > 0) await (signal === void 0 ? sleep(decision.delayMs) : sleepWithAbortSignal(sleep, decision.delayMs, signal));
|
|
127
|
-
attempt += 1;
|
|
128
|
-
}
|
|
129
|
-
}
|
|
130
|
-
};
|
|
131
|
-
/**
|
|
132
|
-
* When `signal` is already aborted, builds the terminal `{ ok: false }` object
|
|
133
|
-
* with a normalized `AbortError` on `error`.
|
|
134
|
-
*
|
|
135
|
-
* @param signal - Optional abort signal; only acts when `aborted` is set.
|
|
136
|
-
* @param attempts - Number of finished attempts to report in the result.
|
|
137
|
-
* @param logger - Optional logger; logs the abort at `debug`.
|
|
138
|
-
* @returns Failure result or `undefined` if not aborted.
|
|
139
|
-
*/
|
|
140
|
-
const buildAbortResult = (signal, attempts, logger) => {
|
|
141
|
-
if (signal?.aborted !== true) return;
|
|
142
|
-
logger?.debug("retry aborted", { reason: signal.reason });
|
|
143
|
-
return {
|
|
144
|
-
attempts,
|
|
145
|
-
error: toAbortError(signal.reason),
|
|
146
|
-
ok: false
|
|
147
|
-
};
|
|
148
|
-
};
|
|
149
|
-
/**
|
|
150
|
-
* Runs one `execute(attempt)` call and returns either a success value or a
|
|
151
|
-
* captured error without rethrowing.
|
|
152
|
-
*
|
|
153
|
-
* @param execute - User work callback.
|
|
154
|
-
* @param attempt - One-based attempt number passed to `execute`.
|
|
155
|
-
* @returns A tagged success with `value` or a tagged failure with `error`.
|
|
156
|
-
*/
|
|
157
|
-
const runAttempt = async (execute, attempt) => {
|
|
158
|
-
try {
|
|
159
|
-
return {
|
|
160
|
-
ok: true,
|
|
161
|
-
value: await execute(attempt)
|
|
162
|
-
};
|
|
163
|
-
} catch (error) {
|
|
164
|
-
return {
|
|
165
|
-
error,
|
|
166
|
-
ok: false
|
|
167
|
-
};
|
|
168
|
-
}
|
|
169
|
-
};
|
|
170
|
-
/**
|
|
171
|
-
* Awaits inter-attempt delay in result mode, mapping an abort during wait to
|
|
172
|
-
* a terminal result instead of throwing when `throwOnExhausted` is false.
|
|
173
|
-
*
|
|
174
|
-
* @param sleep - Custom or default sleep implementation.
|
|
175
|
-
* @param delayMs - Milliseconds to wait.
|
|
176
|
-
* @param signal - If set, `sleep` is raced with the abort signal.
|
|
177
|
-
* @param attempts - Attempt count to attach if the wait ends in abort.
|
|
178
|
-
* @param logger - Optional logger; logs an abort ending the wait at `debug`.
|
|
179
|
-
* @returns A terminal result when canceled during the wait, otherwise
|
|
180
|
-
* `undefined`.
|
|
181
|
-
* @throws {Error} The underlying `sleep` rejection when it is not an abort.
|
|
182
|
-
*/
|
|
183
|
-
const waitForDelay = async (sleep, delayMs, signal, attempts, logger) => {
|
|
184
|
-
if (signal === void 0) {
|
|
185
|
-
await sleep(delayMs);
|
|
186
|
-
return;
|
|
187
|
-
}
|
|
188
|
-
try {
|
|
189
|
-
await sleepWithAbortSignal(sleep, delayMs, signal);
|
|
190
|
-
return;
|
|
191
|
-
} catch (error) {
|
|
192
|
-
const aborted = buildAbortResult(signal, attempts, logger);
|
|
193
|
-
if (aborted !== void 0) return aborted;
|
|
194
|
-
throw error;
|
|
195
|
-
}
|
|
196
|
-
};
|
|
197
|
-
/**
|
|
198
|
-
* After a failed attempt, applies abort rules, `next`, optional delay, and
|
|
199
|
-
* either returns a terminal `RetryRunResult` or `undefined` to continue.
|
|
200
|
-
*
|
|
201
|
-
* @param policy - Resolved retry policy hooks (`next`, `onExhausted`).
|
|
202
|
-
* @param params - Failure context for the current attempt.
|
|
203
|
-
* @param params.attempt - Current attempt number.
|
|
204
|
-
* @param params.error - Error thrown by the attempt.
|
|
205
|
-
* @param params.sleep - Delay function between retries.
|
|
206
|
-
* @param params.signal - Optional abort signal.
|
|
207
|
-
* @param params.logger - Optional logger; logs each retry decision at
|
|
208
|
-
* `debug`, exhaustion at `warn`, and cancellation at `debug`.
|
|
209
|
-
* @returns Terminal non-throw result if the loop should stop, otherwise
|
|
210
|
-
* `undefined` to schedule another attempt.
|
|
211
|
-
* @throws {Error} Any error thrown by `next`, `onExhausted`, or a custom `sleep` when
|
|
212
|
-
* the error is not an abort.
|
|
213
|
-
*/
|
|
214
|
-
const handleFailure = async (policy, params) => {
|
|
215
|
-
const { attempt, error, sleep, signal, logger } = params;
|
|
216
|
-
const abortResult = buildAbortResult(signal, attempt, logger);
|
|
217
|
-
if (abortResult !== void 0) return abortResult;
|
|
218
|
-
const decision = policy.next({
|
|
219
|
-
attempt,
|
|
220
|
-
error
|
|
221
|
-
});
|
|
222
|
-
logRetryDecision(logger, attempt, decision, error);
|
|
223
|
-
if (!decision.shouldRetry) return {
|
|
224
|
-
attempts: attempt,
|
|
225
|
-
error: policy.onExhausted({
|
|
226
|
-
attempts: attempt,
|
|
227
|
-
error
|
|
228
|
-
}),
|
|
229
|
-
ok: false
|
|
230
|
-
};
|
|
231
|
-
if (decision.delayMs > 0) {
|
|
232
|
-
const delayAbortResult = await waitForDelay(sleep, decision.delayMs, signal, attempt, logger);
|
|
233
|
-
if (delayAbortResult !== void 0) return delayAbortResult;
|
|
234
|
-
}
|
|
235
|
-
};
|
|
236
|
-
/**
|
|
237
|
-
* Runs the non-throw retry loop, returning
|
|
238
|
-
* `RetryRunResult`.
|
|
239
|
-
*
|
|
240
|
-
* @param policy - Resolved retry policy providing `next` and `onExhausted`.
|
|
241
|
-
* @param execute - Async work callback per attempt.
|
|
242
|
-
* @param sleep - Delay function between retries.
|
|
243
|
-
* @param signal - Optional cancel signal.
|
|
244
|
-
* @param logger - Optional logger; logs each retry decision at `debug`,
|
|
245
|
-
* exhaustion at `warn`, and cancellation at `debug`.
|
|
246
|
-
* @returns Terminal success or failure object. When `policy.isKnownError`
|
|
247
|
-
* rejects a caught value as outside this policy's error domain, the
|
|
248
|
-
* original value is wrapped in a `RetryError` and returned on
|
|
249
|
-
* `result.error` immediately, bypassing retry — never thrown.
|
|
250
|
-
* @throws {Error} Any error thrown by `next`, `onExhausted`, or a non-abort `sleep`
|
|
251
|
-
* failure.
|
|
252
|
-
*/
|
|
253
|
-
const runResultMode = async (policy, execute, sleep, signal, logger) => {
|
|
254
|
-
let attempt = 1;
|
|
255
|
-
while (true) {
|
|
256
|
-
const abortResult = buildAbortResult(signal, Math.max(0, attempt - 1), logger);
|
|
257
|
-
if (abortResult !== void 0) return abortResult;
|
|
258
|
-
const execution = await runAttempt(execute, attempt);
|
|
259
|
-
if (execution.ok) return {
|
|
260
|
-
ok: true,
|
|
261
|
-
value: execution.value
|
|
262
|
-
};
|
|
263
|
-
const attemptAbortResult = buildAbortResult(signal, attempt, logger);
|
|
264
|
-
if (attemptAbortResult !== void 0) return attemptAbortResult;
|
|
265
|
-
if (!policy.isKnownError(execution.error)) return {
|
|
266
|
-
attempts: attempt,
|
|
267
|
-
error: new RetryError("Retry policy encountered an unknown error.", {
|
|
268
|
-
attempts: attempt,
|
|
269
|
-
lastError: execution.error
|
|
270
|
-
}),
|
|
271
|
-
ok: false
|
|
272
|
-
};
|
|
273
|
-
const failure = await handleFailure(policy, {
|
|
274
|
-
attempt,
|
|
275
|
-
error: execution.error,
|
|
276
|
-
logger,
|
|
277
|
-
signal,
|
|
278
|
-
sleep
|
|
279
|
-
});
|
|
280
|
-
if (failure !== void 0) return failure;
|
|
281
|
-
attempt += 1;
|
|
282
|
-
}
|
|
283
|
-
};
|
|
284
|
-
/**
|
|
285
|
-
* Default `onExhausted` used when a policy omits it: wraps the exhaustion
|
|
286
|
-
* context in a generic `RetryError`.
|
|
287
|
-
*/
|
|
288
|
-
const defaultOnExhausted = (input) => new RetryError("Retry policy exhausted all attempts.", {
|
|
289
|
-
attempts: input.attempts,
|
|
290
|
-
lastData: input.data,
|
|
291
|
-
lastError: input.error
|
|
292
|
-
});
|
|
293
|
-
/**
|
|
294
|
-
* Default `isKnownError` used when a policy omits it: accepts any `Error`
|
|
295
|
-
* instance and rejects everything else.
|
|
296
|
-
*/
|
|
297
|
-
const defaultIsKnownError = (error) => error instanceof Error;
|
|
298
|
-
/**
|
|
299
|
-
* Runs retry orchestration in non-throw mode.
|
|
300
|
-
*
|
|
301
|
-
* When `throwOnExhausted` is `false`, returns a discriminated result union.
|
|
302
|
-
*
|
|
303
|
-
* @param policy - Retry policy: `next` is required, `onExhausted` and
|
|
304
|
-
* `isKnownError` fall back to their defaults when omitted.
|
|
305
|
-
* @param execute - Async function to execute per attempt.
|
|
306
|
-
* @param options - Runner settings.
|
|
307
|
-
* @returns Success value or terminal result object based on option mode.
|
|
308
|
-
* @throws {Error} Any error thrown by `next`, by `onExhausted`, or by a custom `sleep`
|
|
309
|
-
* function. When `throwOnExhausted` is `false`, exhaustion itself is returned
|
|
310
|
-
* as `{ ok: false }` instead of thrown.
|
|
311
|
-
* Cancellation is returned as `{ ok: false, error: AbortError }` in non-throw
|
|
312
|
-
* mode. A value rejected by `policy.isKnownError` is wrapped in a
|
|
313
|
-
* `RetryError` and returned the same way in non-throw mode; in throw mode
|
|
314
|
-
* it is rethrown as-is.
|
|
315
|
-
*
|
|
316
|
-
* @example
|
|
317
|
-
* const result = await runRetryPolicy(policy, doWork, { throwOnExhausted: false });
|
|
318
|
-
* if (!result.ok) console.error(result.error);
|
|
319
|
-
*/
|
|
320
|
-
async function runRetryPolicy(policy, execute, options = {}) {
|
|
321
|
-
const sleep = options.sleep ?? defaultSleep;
|
|
322
|
-
const { signal, logger } = options;
|
|
323
|
-
const resolvedPolicy = {
|
|
324
|
-
isKnownError: (error) => policy.isKnownError ? policy.isKnownError(error) : defaultIsKnownError(error),
|
|
325
|
-
next: (input) => policy.next(input),
|
|
326
|
-
onExhausted: (input) => policy.onExhausted ? policy.onExhausted(input) : defaultOnExhausted(input)
|
|
327
|
-
};
|
|
328
|
-
if (options.throwOnExhausted === false) return await runResultMode(resolvedPolicy, execute, sleep, signal, logger);
|
|
329
|
-
return await runThrowMode(resolvedPolicy, execute, sleep, signal, logger);
|
|
330
|
-
}
|
|
331
|
-
//#endregion
|
|
332
|
-
export { defaultSleep, runRetryPolicy };
|
|
333
|
-
|
|
334
|
-
//# sourceMappingURL=base-policy.js.map
|
|
1
|
+
import { n as runRetryPolicy, r as runRetryPolicyResult, t as defaultSleep } from "./base-policy-DkxBg9dh.js";
|
|
2
|
+
import "./errors.js";
|
|
3
|
+
export { defaultSleep, runRetryPolicy, runRetryPolicyResult };
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"errors-CS5UPJWs.d.ts","names":[],"sources":["../src/errors.ts"],"mappings":";;;;;;;;;;;;
|
|
1
|
+
{"version":3,"file":"errors-CS5UPJWs.d.ts","names":[],"sources":["../src/errors.ts"],"mappings":";;;;;;;;;;;;UAYiB;;;;WAIN;;;;WAIA;;;;WAIA;;;;;;;;UASM;;;;WAIN;;;;;;;;;;;cAYE,mBAAmB;;;;WAId;;;;WAIA;;;;WAIA;;;;EAKhB,YAAY,iBAAiB,SAAS;;;;;;;;;;;;;;;;;;;;;cA4B3B,mBAAmB;;;;oBAIL;;;;;;;EAQzB,YAAY,iBAAiB,UAAS"}
|
package/dist/errors.js.map
CHANGED
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"errors.js","names":[],"sources":["../src/errors.ts"],"sourcesContent":["
|
|
1
|
+
{"version":3,"file":"errors.js","names":[],"sources":["../src/errors.ts"],"sourcesContent":["/**\n * Terminal error types used by retry policies and runners.\n *\n * @module @zap-studio/retry/errors\n */\n\n/**\n * Context payload attached to `RetryError`.\n *\n * @example\n * const context: RetryErrorContext = { attempts: 3, lastError: new Error(\"network\") };\n */\nexport interface RetryErrorContext {\n /**\n * Count of completed attempts at exhaustion.\n */\n readonly attempts: number;\n /**\n * The last error object raised by a failed `execute` attempt.\n */\n readonly lastError?: unknown;\n /**\n * Optional data captured from the last attempt when provided by a policy.\n */\n readonly lastData?: unknown;\n}\n\n/**\n * Context payload attached to `AbortError`.\n *\n * @example\n * const context: AbortErrorContext = { cause: new Error(\"shutting down\") };\n */\nexport interface AbortErrorContext {\n /**\n * When the abort `reason` was an `Error`, the optional wrapped cause.\n */\n readonly cause?: unknown;\n}\n\n/**\n * Error thrown when retries are exhausted.\n *\n * @example\n * throw new RetryError(\"Retry exhausted\", {\n * attempts: 3,\n * lastError: new Error(\"network\"),\n * });\n */\nexport class RetryError extends Error {\n /**\n * Total attempts performed before exhaustion.\n */\n public readonly attempts: number;\n /**\n * Last captured error from execution.\n */\n public readonly lastError?: unknown;\n /**\n * Last captured data value, when available.\n */\n public readonly lastData?: unknown;\n\n /**\n * Creates a RetryError with structured terminal context.\n */\n constructor(message: string, context: RetryErrorContext) {\n super(message);\n this.name = \"RetryError\";\n this.attempts = context.attempts;\n this.lastError = context.lastError;\n this.lastData = context.lastData;\n }\n}\n\n/**\n * Error thrown when retry orchestration is canceled through `AbortSignal`.\n *\n * @example\n * ```ts\n * import { AbortError, runRetryPolicy } from \"@zap-studio/retry\";\n *\n * const controller = new AbortController();\n * controller.abort(\"shutting down\");\n *\n * try {\n * await runRetryPolicy(policy, doWork, { signal: controller.signal });\n * } catch (error) {\n * if (error instanceof AbortError) {\n * console.error(\"Retry canceled:\", error.message);\n * }\n * }\n * ```\n */\nexport class AbortError extends Error {\n /**\n * Optional wrapped cause when the native abort `reason` was an `Error`.\n */\n public override readonly cause?: unknown;\n\n /**\n * Creates an AbortError with an optional diagnostic cause.\n *\n * @param message - Human-readable abort description.\n * @param context - Optional `cause` link for diagnostic chaining.\n */\n constructor(message: string, context: AbortErrorContext = {}) {\n super(message);\n this.name = \"AbortError\";\n this.cause = context.cause;\n }\n}\n"],"mappings":";;;;;;;;;;AAiDA,IAAa,aAAb,cAAgC,MAAM;;;;CAIpC;;;;CAIA;;;;CAIA;;;;CAKA,YAAY,SAAiB,SAA4B;EACvD,MAAM,OAAO;EACb,KAAK,OAAO;EACZ,KAAK,WAAW,QAAQ;EACxB,KAAK,YAAY,QAAQ;EACzB,KAAK,WAAW,QAAQ;CAC1B;AACF;;;;;;;;;;;;;;;;;;;;AAqBA,IAAa,aAAb,cAAgC,MAAM;;;;CAIpC;;;;;;;CAQA,YAAY,SAAiB,UAA6B,CAAC,GAAG;EAC5D,MAAM,OAAO;EACb,KAAK,OAAO;EACZ,KAAK,QAAQ,QAAQ;CACvB;AACF"}
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"exponential-backoff.d.ts","names":[],"sources":["../src/exponential-backoff.ts"],"mappings":";;;;;;;;;;;;;;
|
|
1
|
+
{"version":3,"file":"exponential-backoff.d.ts","names":[],"sources":["../src/exponential-backoff.ts"],"mappings":";;;;;;;;;;;;;;UAsBiB;;;;EAIf;;;;EAIA;;;;EAIA;;;;;EAKA,SAAS,aAAa;;;;;;;;;;;;cAaX,qBAAsB,SAAS,8BAA4B"}
|
|
@@ -1,11 +1,6 @@
|
|
|
1
1
|
import { applyJitter } from "./jitter.js";
|
|
2
2
|
//#region src/exponential-backoff.ts
|
|
3
3
|
/**
|
|
4
|
-
* Exponential backoff retry strategy.
|
|
5
|
-
*
|
|
6
|
-
* @module @zap-studio/retry/exponential-backoff
|
|
7
|
-
*/
|
|
8
|
-
/**
|
|
9
4
|
* Creates a retry policy with exponential delay growth up to a max cap.
|
|
10
5
|
*
|
|
11
6
|
* @example
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"exponential-backoff.js","names":[],"sources":["../src/exponential-backoff.ts"],"sourcesContent":["/**\n * Exponential backoff retry strategy.\n *\n * @module @zap-studio/retry/exponential-backoff\n */\n\nimport {
|
|
1
|
+
{"version":3,"file":"exponential-backoff.js","names":[],"sources":["../src/exponential-backoff.ts"],"sourcesContent":["/**\n * Exponential backoff retry strategy.\n *\n * @module @zap-studio/retry/exponential-backoff\n */\n\nimport type { JitterMode, JitterOptions } from \"./jitter.ts\";\nimport type { RetryDecision, RetryDecisionInput, RetryPolicy } from \"./types.ts\";\n\nimport { applyJitter } from \"./jitter.ts\";\n\n/**\n * Configuration for `exponentialBackoff(...)`.\n *\n * @example\n * const options: ExponentialBackoffOptions = {\n * maxAttempts: 5,\n * baseDelayMs: 100,\n * maxDelayMs: 2_000,\n * jitter: \"full\",\n * };\n */\nexport interface ExponentialBackoffOptions {\n /**\n * Maximum number of attempts (including the first) before giving up.\n */\n maxAttempts: number;\n /**\n * Initial delay in milliseconds, doubled each retry until capped.\n */\n baseDelayMs: number;\n /**\n * Hard upper bound in milliseconds for computed exponential delay.\n */\n maxDelayMs: number;\n /**\n * Optional jitter applied to the computed delay, after capping at\n * `maxDelayMs`.\n */\n jitter?: JitterMode | JitterOptions;\n}\n\n/**\n * Creates a retry policy with exponential delay growth up to a max cap.\n *\n * @example\n * const policy = exponentialBackoff({\n * maxAttempts: 5,\n * baseDelayMs: 100,\n * maxDelayMs: 2_000,\n * });\n */\nexport const exponentialBackoff = (options: ExponentialBackoffOptions): RetryPolicy => {\n const { maxAttempts, baseDelayMs, maxDelayMs, jitter } = options;\n\n return {\n /**\n * Computes retry decision for the current attempt.\n */\n next(input: RetryDecisionInput): RetryDecision {\n if (input.attempt >= maxAttempts) {\n return {\n delayMs: 0,\n reason: \"max-attempts-reached\",\n shouldRetry: false,\n };\n }\n\n const exponent = Math.max(0, input.attempt - 1);\n const cappedDelayMs = Math.min(maxDelayMs, baseDelayMs * 2 ** exponent);\n const delayMs = applyJitter(cappedDelayMs, jitter);\n\n return { delayMs, reason: \"retry\", shouldRetry: true };\n },\n };\n};\n"],"mappings":";;;;;;;;;;;;AAoDA,MAAa,sBAAsB,YAAoD;CACrF,MAAM,EAAE,aAAa,aAAa,YAAY,WAAW;CAEzD,OAAO;;;;AAIL,KAAK,OAA0C;EAC7C,IAAI,MAAM,WAAW,aACnB,OAAO;GACL,SAAS;GACT,QAAQ;GACR,aAAa;EACf;EAGF,MAAM,WAAW,KAAK,IAAI,GAAG,MAAM,UAAU,CAAC;EAC9C,MAAM,gBAAgB,KAAK,IAAI,YAAY,cAAc,KAAK,QAAQ;EAGtE,OAAO;GAAE,SAFO,YAAY,eAAe,MAE5B;GAAG,QAAQ;GAAS,aAAa;EAAK;CACvD,EACF;AACF"}
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"fixed-delay.d.ts","names":[],"sources":["../src/fixed-delay.ts"],"mappings":";;;;;;;;
|
|
1
|
+
{"version":3,"file":"fixed-delay.d.ts","names":[],"sources":["../src/fixed-delay.ts"],"mappings":";;;;;;;;UAciB;;;;EAIf;;;;EAIA;;;;;;;;;;;cAYW,aAAc,SAAS,sBAAoB"}
|
package/dist/fixed-delay.js.map
CHANGED
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"fixed-delay.js","names":[],"sources":["../src/fixed-delay.ts"],"sourcesContent":["/**\n * Fixed-delay retry strategy.\n *\n * @module @zap-studio/retry/fixed-delay\n */\n\nimport type {
|
|
1
|
+
{"version":3,"file":"fixed-delay.js","names":[],"sources":["../src/fixed-delay.ts"],"sourcesContent":["/**\n * Fixed-delay retry strategy.\n *\n * @module @zap-studio/retry/fixed-delay\n */\n\nimport type { RetryDecision, RetryDecisionInput, RetryPolicy } from \"./types.ts\";\n\n/**\n * Configuration for `fixedDelay(...)`.\n *\n * @example\n * const options: FixedDelayOptions = { maxAttempts: 3, delayMs: 250 };\n */\nexport interface FixedDelayOptions {\n /**\n * Maximum number of attempts (including the first) before giving up.\n */\n maxAttempts: number;\n /**\n * Constant delay in milliseconds before each retry after a failure.\n */\n delayMs: number;\n}\n\n/**\n * Creates a retry policy with a constant delay between attempts.\n *\n * @example\n * const policy = fixedDelay({\n * maxAttempts: 3,\n * delayMs: 250,\n * });\n */\nexport const fixedDelay = (options: FixedDelayOptions): RetryPolicy => {\n const { maxAttempts, delayMs } = options;\n\n return {\n /**\n * Computes retry decision for the current attempt.\n */\n next(input: RetryDecisionInput): RetryDecision {\n if (input.attempt >= maxAttempts) {\n return {\n delayMs: 0,\n reason: \"max-attempts-reached\",\n shouldRetry: false,\n };\n }\n\n return { delayMs, reason: \"retry\", shouldRetry: true };\n },\n };\n};\n"],"mappings":";;;;;;;;;;AAkCA,MAAa,cAAc,YAA4C;CACrE,MAAM,EAAE,aAAa,YAAY;CAEjC,OAAO;;;;AAIL,KAAK,OAA0C;EAC7C,IAAI,MAAM,WAAW,aACnB,OAAO;GACL,SAAS;GACT,QAAQ;GACR,aAAa;EACf;EAGF,OAAO;GAAE;GAAS,QAAQ;GAAS,aAAa;EAAK;CACvD,EACF;AACF"}
|
package/dist/index.d.ts
CHANGED
|
@@ -1,8 +1,8 @@
|
|
|
1
1
|
import { i as RetryErrorContext, n as AbortErrorContext, r as RetryError, t as AbortError } from "./errors-CS5UPJWs.js";
|
|
2
|
-
import { RetryDecision, RetryDecisionInput, RetryExhaustedInput, RetryPolicy, RetryRunOptions, RetryRunResult } from "./types.js";
|
|
3
|
-
import { defaultSleep, runRetryPolicy } from "./base-policy.js";
|
|
2
|
+
import { RetryDecision, RetryDecisionInput, RetryExhaustedInput, RetryPolicy, RetryRunOptions, RetryRunResult, RetryRunResultOptions } from "./types.js";
|
|
3
|
+
import { defaultSleep, runRetryPolicy, runRetryPolicyResult } from "./base-policy.js";
|
|
4
4
|
import { JitterMode, JitterOptions, applyJitter } from "./jitter.js";
|
|
5
5
|
import { ExponentialBackoffOptions, exponentialBackoff } from "./exponential-backoff.js";
|
|
6
6
|
import { FixedDelayOptions, fixedDelay } from "./fixed-delay.js";
|
|
7
7
|
import { LinearBackoffOptions, linearBackoff } from "./linear-backoff.js";
|
|
8
|
-
export { AbortError, type AbortErrorContext, type ExponentialBackoffOptions, type FixedDelayOptions, type JitterMode, type JitterOptions, type LinearBackoffOptions, type RetryDecision, type RetryDecisionInput, RetryError, type RetryErrorContext, type RetryExhaustedInput, type RetryPolicy, type RetryRunOptions, type RetryRunResult, applyJitter, defaultSleep, exponentialBackoff, fixedDelay, linearBackoff, runRetryPolicy };
|
|
8
|
+
export { AbortError, type AbortErrorContext, type ExponentialBackoffOptions, type FixedDelayOptions, type JitterMode, type JitterOptions, type LinearBackoffOptions, type RetryDecision, type RetryDecisionInput, RetryError, type RetryErrorContext, type RetryExhaustedInput, type RetryPolicy, type RetryRunOptions, type RetryRunResult, type RetryRunResultOptions, applyJitter, defaultSleep, exponentialBackoff, fixedDelay, linearBackoff, runRetryPolicy, runRetryPolicyResult };
|
package/dist/index.js
CHANGED
|
@@ -1,7 +1,7 @@
|
|
|
1
|
+
import { n as runRetryPolicy, r as runRetryPolicyResult, t as defaultSleep } from "./base-policy-DkxBg9dh.js";
|
|
1
2
|
import { AbortError, RetryError } from "./errors.js";
|
|
2
|
-
import { defaultSleep, runRetryPolicy } from "./base-policy.js";
|
|
3
3
|
import { applyJitter } from "./jitter.js";
|
|
4
4
|
import { exponentialBackoff } from "./exponential-backoff.js";
|
|
5
5
|
import { fixedDelay } from "./fixed-delay.js";
|
|
6
6
|
import { linearBackoff } from "./linear-backoff.js";
|
|
7
|
-
export { AbortError, RetryError, applyJitter, defaultSleep, exponentialBackoff, fixedDelay, linearBackoff, runRetryPolicy };
|
|
7
|
+
export { AbortError, RetryError, applyJitter, defaultSleep, exponentialBackoff, fixedDelay, linearBackoff, runRetryPolicy, runRetryPolicyResult };
|
package/dist/jitter.d.ts.map
CHANGED
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"jitter.d.ts","names":[],"sources":["../src/jitter.ts"],"mappings":";;;;;;;;;;;;;;KAcY;;;;;;;UAQK;;;;EAIf,MAAM;;;;;;EAMN;;;;;;;;;;;;;cAcW,
|
|
1
|
+
{"version":3,"file":"jitter.d.ts","names":[],"sources":["../src/jitter.ts"],"mappings":";;;;;;;;;;;;;;KAcY;;;;;;;UAQK;;;;EAIf,MAAM;;;;;;EAMN;;;;;;;;;;;;;cAcW,cAAe,iBAAiB,SAAS,aAAa"}
|
package/dist/jitter.js.map
CHANGED
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"jitter.js","names":[],"sources":["../src/jitter.ts"],"sourcesContent":["/**\n * Jitter strategies applied to a computed backoff delay.\n *\n * @module @zap-studio/retry/jitter\n */\n\n/**\n * Supported jitter strategies.\n *\n * - `\"full\"`: `random(0, delayMs)` — max spread, best thundering-herd\n * protection.\n * - `\"equal\"`: `delayMs/2 + random(0, delayMs/2)` — keeps a floor at half\n * the computed delay, less spread than full jitter.\n */\nexport type JitterMode = \"equal\" | \"full\";\n\n/**\n * Configuration for jitter application.\n *\n * @example\n * const jitter: JitterOptions = { mode: \"full\" };\n */\nexport interface JitterOptions {\n /**\n * Jitter strategy to apply.\n */\n mode: JitterMode;\n /**\n * Random source in `[0, 1)`, overridable for deterministic tests.\n *\n * @default Math.random\n */\n random?: () => number;\n}\n\n/**\n * Applies a jitter strategy to a computed delay.\n *\n * @param delayMs - Delay in milliseconds before jitter.\n * @param jitter - Jitter mode shorthand, full `JitterOptions`, or `undefined`\n * to leave `delayMs` untouched.\n * @returns Jittered delay in milliseconds, rounded to the nearest integer.\n *\n * @example\n * const delayMs = applyJitter(1000, \"full\"); // 0-1000\n */\nexport const applyJitter = (
|
|
1
|
+
{"version":3,"file":"jitter.js","names":[],"sources":["../src/jitter.ts"],"sourcesContent":["/**\n * Jitter strategies applied to a computed backoff delay.\n *\n * @module @zap-studio/retry/jitter\n */\n\n/**\n * Supported jitter strategies.\n *\n * - `\"full\"`: `random(0, delayMs)` — max spread, best thundering-herd\n * protection.\n * - `\"equal\"`: `delayMs/2 + random(0, delayMs/2)` — keeps a floor at half\n * the computed delay, less spread than full jitter.\n */\nexport type JitterMode = \"equal\" | \"full\";\n\n/**\n * Configuration for jitter application.\n *\n * @example\n * const jitter: JitterOptions = { mode: \"full\" };\n */\nexport interface JitterOptions {\n /**\n * Jitter strategy to apply.\n */\n mode: JitterMode;\n /**\n * Random source in `[0, 1)`, overridable for deterministic tests.\n *\n * @default Math.random\n */\n random?: () => number;\n}\n\n/**\n * Applies a jitter strategy to a computed delay.\n *\n * @param delayMs - Delay in milliseconds before jitter.\n * @param jitter - Jitter mode shorthand, full `JitterOptions`, or `undefined`\n * to leave `delayMs` untouched.\n * @returns Jittered delay in milliseconds, rounded to the nearest integer.\n *\n * @example\n * const delayMs = applyJitter(1000, \"full\"); // 0-1000\n */\nexport const applyJitter = (delayMs: number, jitter?: JitterMode | JitterOptions): number => {\n if (jitter === undefined) {\n return delayMs;\n }\n\n const mode = typeof jitter === \"string\" ? jitter : jitter.mode;\n const random = (typeof jitter === \"string\" ? undefined : jitter.random) ?? Math.random;\n\n if (mode === \"full\") {\n return Math.round(random() * delayMs);\n }\n\n const half = delayMs / 2;\n return Math.round(half + random() * half);\n};\n"],"mappings":";;;;;;;;;;;;AA8CA,MAAa,eAAe,SAAiB,WAAgD;CAC3F,IAAI,WAAW,KAAA,GACb,OAAO;CAGT,MAAM,OAAO,OAAO,WAAW,WAAW,SAAS,OAAO;CAC1D,MAAM,UAAU,OAAO,WAAW,WAAW,KAAA,IAAY,OAAO,WAAW,KAAK;CAEhF,IAAI,SAAS,QACX,OAAO,KAAK,MAAM,OAAO,IAAI,OAAO;CAGtC,MAAM,OAAO,UAAU;CACvB,OAAO,KAAK,MAAM,OAAO,OAAO,IAAI,IAAI;AAC1C"}
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"linear-backoff.d.ts","names":[],"sources":["../src/linear-backoff.ts"],"mappings":";;;;;;;;;;;;;;;
|
|
1
|
+
{"version":3,"file":"linear-backoff.d.ts","names":[],"sources":["../src/linear-backoff.ts"],"mappings":";;;;;;;;;;;;;;;UAuBiB;;;;EAIf;;;;EAIA;;;;EAIA;;;;EAIA;;;;;EAKA,SAAS,aAAa;;;;;;;;;;;;;cAcX,gBAAiB,SAAS,yBAAuB"}
|
package/dist/linear-backoff.js
CHANGED
|
@@ -1,11 +1,6 @@
|
|
|
1
1
|
import { applyJitter } from "./jitter.js";
|
|
2
2
|
//#region src/linear-backoff.ts
|
|
3
3
|
/**
|
|
4
|
-
* Linear backoff retry strategy.
|
|
5
|
-
*
|
|
6
|
-
* @module @zap-studio/retry/linear-backoff
|
|
7
|
-
*/
|
|
8
|
-
/**
|
|
9
4
|
* Creates a retry policy with linear delay growth up to a max cap.
|
|
10
5
|
*
|
|
11
6
|
* @example
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"linear-backoff.js","names":[],"sources":["../src/linear-backoff.ts"],"sourcesContent":["/**\n * Linear backoff retry strategy.\n *\n * @module @zap-studio/retry/linear-backoff\n */\n\nimport {
|
|
1
|
+
{"version":3,"file":"linear-backoff.js","names":[],"sources":["../src/linear-backoff.ts"],"sourcesContent":["/**\n * Linear backoff retry strategy.\n *\n * @module @zap-studio/retry/linear-backoff\n */\n\nimport type { JitterMode, JitterOptions } from \"./jitter.ts\";\nimport type { RetryDecision, RetryDecisionInput, RetryPolicy } from \"./types.ts\";\n\nimport { applyJitter } from \"./jitter.ts\";\n\n/**\n * Configuration for `linearBackoff(...)`.\n *\n * @example\n * const options: LinearBackoffOptions = {\n * maxAttempts: 5,\n * baseDelayMs: 100,\n * incrementMs: 100,\n * maxDelayMs: 2_000,\n * jitter: \"equal\",\n * };\n */\nexport interface LinearBackoffOptions {\n /**\n * Maximum number of attempts (including the first) before giving up.\n */\n maxAttempts: number;\n /**\n * Delay in milliseconds after the first failed attempt.\n */\n baseDelayMs: number;\n /**\n * Amount added to the delay for each subsequent retry.\n */\n incrementMs: number;\n /**\n * Hard upper bound in milliseconds for computed linear delay.\n */\n maxDelayMs: number;\n /**\n * Optional jitter applied to the computed delay, after capping at\n * `maxDelayMs`.\n */\n jitter?: JitterMode | JitterOptions;\n}\n\n/**\n * Creates a retry policy with linear delay growth up to a max cap.\n *\n * @example\n * const policy = linearBackoff({\n * maxAttempts: 5,\n * baseDelayMs: 100,\n * incrementMs: 100,\n * maxDelayMs: 2_000,\n * });\n */\nexport const linearBackoff = (options: LinearBackoffOptions): RetryPolicy => {\n const { maxAttempts, baseDelayMs, incrementMs, maxDelayMs, jitter } = options;\n\n return {\n /**\n * Computes retry decision for the current attempt.\n */\n next(input: RetryDecisionInput): RetryDecision {\n if (input.attempt >= maxAttempts) {\n return {\n delayMs: 0,\n reason: \"max-attempts-reached\",\n shouldRetry: false,\n };\n }\n\n const cappedDelayMs = Math.min(maxDelayMs, baseDelayMs + incrementMs * (input.attempt - 1));\n const delayMs = applyJitter(cappedDelayMs, jitter);\n\n return { delayMs, reason: \"retry\", shouldRetry: true };\n },\n };\n};\n"],"mappings":";;;;;;;;;;;;;AA0DA,MAAa,iBAAiB,YAA+C;CAC3E,MAAM,EAAE,aAAa,aAAa,aAAa,YAAY,WAAW;CAEtE,OAAO;;;;AAIL,KAAK,OAA0C;EAC7C,IAAI,MAAM,WAAW,aACnB,OAAO;GACL,SAAS;GACT,QAAQ;GACR,aAAa;EACf;EAGF,MAAM,gBAAgB,KAAK,IAAI,YAAY,cAAc,eAAe,MAAM,UAAU,EAAE;EAG1F,OAAO;GAAE,SAFO,YAAY,eAAe,MAE5B;GAAG,QAAQ;GAAS,aAAa;EAAK;CACvD,EACF;AACF"}
|
package/dist/types.d.ts
CHANGED
|
@@ -166,6 +166,17 @@ interface RetryRunOptions {
|
|
|
166
166
|
*/
|
|
167
167
|
readonly logger?: Logger;
|
|
168
168
|
}
|
|
169
|
+
/**
|
|
170
|
+
* Options for `runRetryPolicyResult(...)`.
|
|
171
|
+
*
|
|
172
|
+
* Same as {@link RetryRunOptions} minus `throwOnExhausted`, which doesn't
|
|
173
|
+
* apply — `runRetryPolicyResult` always returns a `Result`, never throws for
|
|
174
|
+
* exhaustion or abort.
|
|
175
|
+
*
|
|
176
|
+
* @example
|
|
177
|
+
* const options: RetryRunResultOptions = { signal: controller.signal };
|
|
178
|
+
*/
|
|
179
|
+
type RetryRunResultOptions = Omit<RetryRunOptions, "throwOnExhausted">;
|
|
169
180
|
/**
|
|
170
181
|
* Result union returned by non-throw runner mode.
|
|
171
182
|
*
|
|
@@ -202,5 +213,5 @@ type RetryRunResult<T> = {
|
|
|
202
213
|
attempts: number;
|
|
203
214
|
};
|
|
204
215
|
//#endregion
|
|
205
|
-
export { ResolvedRetryPolicy, RetryDecision, RetryDecisionInput, RetryExhaustedInput, RetryPolicy, RetryRunOptions, RetryRunResult };
|
|
216
|
+
export { ResolvedRetryPolicy, RetryDecision, RetryDecisionInput, RetryExhaustedInput, RetryPolicy, RetryRunOptions, RetryRunResult, RetryRunResultOptions };
|
|
206
217
|
//# sourceMappingURL=types.d.ts.map
|
package/dist/types.d.ts.map
CHANGED
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"types.d.ts","names":[],"sources":["../src/types.ts"],"mappings":";;;;;;;;;;;;;;;;UAuBiB,YAAY,eAAe,QAAQ,OAAO;;;;;;EAMzD,OAAO,OAAO,mBAAmB,QAAQ,WAAW;;;;;;;;EAQpD,eAAe,OAAO,oBAAoB,QAAQ,WAAW;;;;;;;;;;;;;EAa7D,gBAAgB,mBAAmB,SAAS;;;;;;UAO7B,oBAAoB,eAAe,OAAO;;;;EAIzD,MAAM,YAAY,QAAQ;;;;;EAK1B,cAAc,OAAO,oBAAoB,QAAQ,WAAW;;;;;EAK5D,eAAe,mBAAmB,SAAS;;;;;;;;UAS5B;;;;;WAKN;;;;WAIA;;;;WAIA;;;;;;;;UASM,
|
|
1
|
+
{"version":3,"file":"types.d.ts","names":[],"sources":["../src/types.ts"],"mappings":";;;;;;;;;;;;;;;;UAuBiB,YAAY,eAAe,QAAQ,OAAO;;;;;;EAMzD,OAAO,OAAO,mBAAmB,QAAQ,WAAW;;;;;;;;EAQpD,eAAe,OAAO,oBAAoB,QAAQ,WAAW;;;;;;;;;;;;;EAa7D,gBAAgB,mBAAmB,SAAS;;;;;;UAO7B,oBAAoB,eAAe,OAAO;;;;EAIzD,MAAM,YAAY,QAAQ;;;;;EAK1B,cAAc,OAAO,oBAAoB,QAAQ,WAAW;;;;;EAK5D,eAAe,mBAAmB,SAAS;;;;;;;;UAS5B;;;;;WAKN;;;;WAIA;;;;WAIA;;;;;;;;UASM,mBAAmB,eAAe,QAAQ,OAAO;;;;WAIvD;;;;;WAKA;;;;;WAKA,QAAQ;;;;;WAKR,OAAO;;;;;;;;UASD,oBAAoB,eAAe,QAAQ,OAAO;;;;WAIxD;;;;WAIA,QAAQ;;;;WAIR,OAAO;;;;;;;;UASD;;;;;;WAMN,SAAS,oBAAoB;;;;;;WAM7B,SAAS;;;;;;;;WAQT;;;;;;;WAOA,SAAS;;;;;;;;;;;;KAaR,wBAAwB,KAAK;;;;;;;;;;;;KAa7B,eAAe;;;;EAKrB;;;;EAIA,OAAO;;;;;EAMP;;;;;EAKA,OAAO,aAAa;;;;EAIpB"}
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@zap-studio/retry",
|
|
3
|
-
"version": "
|
|
3
|
+
"version": "2.1.0",
|
|
4
4
|
"private": false,
|
|
5
5
|
"description": "Composable, tree-shakeable retry policies for resilient async operations.",
|
|
6
6
|
"keywords": [
|
|
@@ -43,15 +43,23 @@
|
|
|
43
43
|
"publishConfig": {
|
|
44
44
|
"access": "public"
|
|
45
45
|
},
|
|
46
|
+
"dependencies": {
|
|
47
|
+
"@zap-studio/monads": "1.0.0"
|
|
48
|
+
},
|
|
46
49
|
"devDependencies": {
|
|
50
|
+
"@opentelemetry/api": "^1.9.0",
|
|
51
|
+
"@opentelemetry/context-async-hooks": "^2.10.0",
|
|
52
|
+
"@opentelemetry/sdk-metrics": "^2.10.0",
|
|
53
|
+
"@opentelemetry/sdk-trace-base": "^2.10.0",
|
|
47
54
|
"tsdown": "^0.22.14",
|
|
48
55
|
"typescript": "^7.0.2",
|
|
49
56
|
"vitest": "^4.1.10",
|
|
50
|
-
"@zap-studio/
|
|
51
|
-
"@zap-studio/
|
|
57
|
+
"@zap-studio/typescript": "0.0.0",
|
|
58
|
+
"@zap-studio/logger": "2.0.0"
|
|
52
59
|
},
|
|
53
60
|
"peerDependencies": {
|
|
54
|
-
"@
|
|
61
|
+
"@opentelemetry/api": "^1.9.0",
|
|
62
|
+
"@zap-studio/logger": "2.0.0"
|
|
55
63
|
},
|
|
56
64
|
"peerDependenciesMeta": {
|
|
57
65
|
"@zap-studio/logger": {
|