@zap-studio/retry 1.1.0 → 1.2.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 +6 -0
- package/README.md +30 -6
- package/dist/base-policy.d.ts.map +1 -1
- package/dist/base-policy.js +49 -21
- package/dist/base-policy.js.map +1 -1
- package/dist/types.d.ts +19 -0
- package/dist/types.d.ts.map +1 -1
- package/package.json +4 -1
package/CHANGELOG.md
CHANGED
|
@@ -4,6 +4,12 @@ All notable changes to this project will be documented in this file.
|
|
|
4
4
|
|
|
5
5
|
The format is based on [Keep a Changelog](https://keepachangelog.com/en/1.0.0/), and this project adheres to [Semantic Versioning](https://semver.org/spec/v2.0.0.html).
|
|
6
6
|
|
|
7
|
+
## [1.2.0]
|
|
8
|
+
|
|
9
|
+
### Added
|
|
10
|
+
|
|
11
|
+
`runRetryPolicy(...)` gains an optional `logger?: Logger` option (from `@zap-studio/logger`). When provided, it logs each retry decision at `debug` (attempt, delay, reason), exhaustion at `warn`, and cancellation at `debug`. Omitting it keeps zero logging overhead. See [Logging](https://www.zapstudio.dev/retry/logging).
|
|
12
|
+
|
|
7
13
|
## [1.1.0]
|
|
8
14
|
|
|
9
15
|
### Added
|
package/README.md
CHANGED
|
@@ -19,26 +19,34 @@ npm install @zap-studio/retry
|
|
|
19
19
|
- **Non-throw mode** (`throwOnExhausted: false`) returns a `RetryRunResult` instead of throwing.
|
|
20
20
|
- **Cancellation** through `AbortSignal`, checked before, between, and during retries.
|
|
21
21
|
- **Custom policies** as plain objects implementing `RetryPolicy` — just a `next(...)` function, no subclassing.
|
|
22
|
+
- **Optional logging** via a `logger?: Logger` option ([`@zap-studio/logger`](https://www.npmjs.com/package/@zap-studio/logger)) — omit it and there's zero logging overhead.
|
|
22
23
|
- **Tree-shakeable** — policies are functions returning plain objects, not classes; unused policies are dropped by any modern bundler.
|
|
23
24
|
|
|
24
25
|
## Quick Start
|
|
25
26
|
|
|
26
27
|
```ts
|
|
28
|
+
import { ConsoleLogger } from "@zap-studio/logger";
|
|
27
29
|
import { exponentialBackoff, runRetryPolicy } from "@zap-studio/retry";
|
|
28
30
|
import { $fetch } from "@zap-studio/fetch";
|
|
29
31
|
|
|
32
|
+
const logger = new ConsoleLogger({ minLevel: "debug" });
|
|
33
|
+
|
|
30
34
|
const policy = exponentialBackoff({
|
|
31
35
|
maxAttempts: 5,
|
|
32
36
|
baseDelayMs: 100,
|
|
33
37
|
maxDelayMs: 2_000,
|
|
34
38
|
});
|
|
35
39
|
|
|
36
|
-
const data = await runRetryPolicy(
|
|
37
|
-
|
|
38
|
-
|
|
39
|
-
|
|
40
|
-
|
|
41
|
-
});
|
|
40
|
+
const data = await runRetryPolicy(
|
|
41
|
+
policy,
|
|
42
|
+
async () => {
|
|
43
|
+
const response = await $fetch("https://api.example.com/users", {
|
|
44
|
+
throwOnFetchError: true,
|
|
45
|
+
});
|
|
46
|
+
return await response.json();
|
|
47
|
+
},
|
|
48
|
+
{ logger }
|
|
49
|
+
);
|
|
42
50
|
```
|
|
43
51
|
|
|
44
52
|
## Built-in Policies
|
|
@@ -161,6 +169,22 @@ const stepDelay = (maxAttempts: number, stepMs: number): RetryPolicy => ({
|
|
|
161
169
|
const data = await runRetryPolicy(stepDelay(5, 100), execute);
|
|
162
170
|
```
|
|
163
171
|
|
|
172
|
+
## Logging
|
|
173
|
+
|
|
174
|
+
Pass a `logger?: Logger` from [`@zap-studio/logger`](https://www.npmjs.com/package/@zap-studio/logger) to `runRetryPolicy(...)` to observe retry decisions, exhaustion, and cancellation. Omit it and nothing is logged.
|
|
175
|
+
|
|
176
|
+
```ts
|
|
177
|
+
import { ConsoleLogger } from "@zap-studio/logger";
|
|
178
|
+
import { exponentialBackoff, runRetryPolicy } from "@zap-studio/retry";
|
|
179
|
+
|
|
180
|
+
const logger = new ConsoleLogger({ minLevel: "debug" });
|
|
181
|
+
const policy = exponentialBackoff({ maxAttempts: 5, baseDelayMs: 100 });
|
|
182
|
+
|
|
183
|
+
await runRetryPolicy(policy, execute, { logger });
|
|
184
|
+
```
|
|
185
|
+
|
|
186
|
+
Each retry decision logs at `debug` (attempt, delay, reason), exhaustion logs at `warn`, and cancellation logs at `debug`.
|
|
187
|
+
|
|
164
188
|
## Runtime Support
|
|
165
189
|
|
|
166
190
|
| Runtime | Minimum version |
|
|
@@ -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":";;;;;;;;;;;;;;;cA+Ba,eAAsB,oBAAkB;;;;;;;;;;;;;iBAicrC,eACd,GACA,eAAe,QAAQ,OACvB,iBAEA,QAAQ,YAAY,QAAQ,QAC5B,UAAU,oBAAoB,QAAQ,IACtC,SAAS;EAAoB;IAC5B,QAAQ,eAAe;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;iBAgCV,eACd,GACA,eAAe,QAAQ,OACvB,iBAEA,QAAQ,YAAY,QAAQ,QAC5B,UAAU,oBAAoB,QAAQ,IACtC,UAAU;EAAoB;IAC7B,QAAQ"}
|
package/dist/base-policy.js
CHANGED
|
@@ -1,11 +1,6 @@
|
|
|
1
1
|
import { AbortError, RetryError } from "./errors.js";
|
|
2
2
|
//#region src/base-policy.ts
|
|
3
3
|
/**
|
|
4
|
-
* Retry runner base class and shared orchestration implementation.
|
|
5
|
-
*
|
|
6
|
-
* @module @zap-studio/retry/base-policy
|
|
7
|
-
*/
|
|
8
|
-
/**
|
|
9
4
|
* Awaits a timer-based delay, unless `delayMs` is non-positive.
|
|
10
5
|
*
|
|
11
6
|
* @param delayMs - Milliseconds to wait before resolving.
|
|
@@ -42,10 +37,12 @@ const toAbortError = (reason) => {
|
|
|
42
37
|
* Throws when the provided abort signal is already aborted.
|
|
43
38
|
*
|
|
44
39
|
* @param signal - Optional abort signal to inspect.
|
|
40
|
+
* @param logger - Optional logger; logs the abort at `debug` before throwing.
|
|
45
41
|
* @throws {AbortError} When the signal is aborted.
|
|
46
42
|
*/
|
|
47
|
-
const throwIfAborted = (signal) => {
|
|
43
|
+
const throwIfAborted = (signal, logger) => {
|
|
48
44
|
if (signal?.aborted !== true) return;
|
|
45
|
+
logger?.debug("retry aborted", { reason: signal.reason });
|
|
49
46
|
throw toAbortError(signal.reason);
|
|
50
47
|
};
|
|
51
48
|
/**
|
|
@@ -72,6 +69,25 @@ const sleepWithAbortSignal = async (sleep, delayMs, signal) => {
|
|
|
72
69
|
}
|
|
73
70
|
};
|
|
74
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
|
+
/**
|
|
75
91
|
* Runs the throw-mode retry loop: throws `RetryError` on exhaustion and
|
|
76
92
|
* `AbortError` when `signal` aborts.
|
|
77
93
|
*
|
|
@@ -79,6 +95,8 @@ const sleepWithAbortSignal = async (sleep, delayMs, signal) => {
|
|
|
79
95
|
* @param execute - Async work callback per attempt.
|
|
80
96
|
* @param sleep - Delay function between retries.
|
|
81
97
|
* @param signal - Optional cancel signal.
|
|
98
|
+
* @param logger - Optional logger; logs each retry decision at `debug` and
|
|
99
|
+
* exhaustion at `warn`.
|
|
82
100
|
* @returns Resolves to the first successful return value.
|
|
83
101
|
* @throws {RetryError} When retries are exhausted and `onExhausted` returns
|
|
84
102
|
* the terminal error.
|
|
@@ -87,19 +105,20 @@ const sleepWithAbortSignal = async (sleep, delayMs, signal) => {
|
|
|
87
105
|
* rethrows the original caught value immediately, bypassing retry, when
|
|
88
106
|
* `policy.isKnownError` rejects it as outside this policy's error domain.
|
|
89
107
|
*/
|
|
90
|
-
const runThrowMode = async (policy, execute, sleep, signal) => {
|
|
108
|
+
const runThrowMode = async (policy, execute, sleep, signal, logger) => {
|
|
91
109
|
let attempt = 1;
|
|
92
110
|
while (true) {
|
|
93
|
-
throwIfAborted(signal);
|
|
111
|
+
throwIfAborted(signal, logger);
|
|
94
112
|
try {
|
|
95
113
|
return await execute(attempt);
|
|
96
114
|
} catch (error) {
|
|
97
|
-
throwIfAborted(signal);
|
|
115
|
+
throwIfAborted(signal, logger);
|
|
98
116
|
if (!policy.isKnownError(error)) throw error;
|
|
99
117
|
const decision = policy.next({
|
|
100
118
|
attempt,
|
|
101
119
|
error
|
|
102
120
|
});
|
|
121
|
+
logRetryDecision(logger, attempt, decision, error);
|
|
103
122
|
if (!decision.shouldRetry) throw policy.onExhausted({
|
|
104
123
|
attempts: attempt,
|
|
105
124
|
error
|
|
@@ -115,10 +134,12 @@ const runThrowMode = async (policy, execute, sleep, signal) => {
|
|
|
115
134
|
*
|
|
116
135
|
* @param signal - Optional abort signal; only acts when `aborted` is set.
|
|
117
136
|
* @param attempts - Number of finished attempts to report in the result.
|
|
137
|
+
* @param logger - Optional logger; logs the abort at `debug`.
|
|
118
138
|
* @returns Failure result or `undefined` if not aborted.
|
|
119
139
|
*/
|
|
120
|
-
const buildAbortResult = (signal, attempts) => {
|
|
140
|
+
const buildAbortResult = (signal, attempts, logger) => {
|
|
121
141
|
if (signal?.aborted !== true) return;
|
|
142
|
+
logger?.debug("retry aborted", { reason: signal.reason });
|
|
122
143
|
return {
|
|
123
144
|
attempts,
|
|
124
145
|
error: toAbortError(signal.reason),
|
|
@@ -154,11 +175,12 @@ const runAttempt = async (execute, attempt) => {
|
|
|
154
175
|
* @param delayMs - Milliseconds to wait.
|
|
155
176
|
* @param signal - If set, `sleep` is raced with the abort signal.
|
|
156
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`.
|
|
157
179
|
* @returns A terminal result when canceled during the wait, otherwise
|
|
158
180
|
* `undefined`.
|
|
159
181
|
* @throws {Error} The underlying `sleep` rejection when it is not an abort.
|
|
160
182
|
*/
|
|
161
|
-
const waitForDelay = async (sleep, delayMs, signal, attempts) => {
|
|
183
|
+
const waitForDelay = async (sleep, delayMs, signal, attempts, logger) => {
|
|
162
184
|
if (signal === void 0) {
|
|
163
185
|
await sleep(delayMs);
|
|
164
186
|
return;
|
|
@@ -167,7 +189,7 @@ const waitForDelay = async (sleep, delayMs, signal, attempts) => {
|
|
|
167
189
|
await sleepWithAbortSignal(sleep, delayMs, signal);
|
|
168
190
|
return;
|
|
169
191
|
} catch (error) {
|
|
170
|
-
const aborted = buildAbortResult(signal, attempts);
|
|
192
|
+
const aborted = buildAbortResult(signal, attempts, logger);
|
|
171
193
|
if (aborted !== void 0) return aborted;
|
|
172
194
|
throw error;
|
|
173
195
|
}
|
|
@@ -182,19 +204,22 @@ const waitForDelay = async (sleep, delayMs, signal, attempts) => {
|
|
|
182
204
|
* @param params.error - Error thrown by the attempt.
|
|
183
205
|
* @param params.sleep - Delay function between retries.
|
|
184
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`.
|
|
185
209
|
* @returns Terminal non-throw result if the loop should stop, otherwise
|
|
186
210
|
* `undefined` to schedule another attempt.
|
|
187
211
|
* @throws {Error} Any error thrown by `next`, `onExhausted`, or a custom `sleep` when
|
|
188
212
|
* the error is not an abort.
|
|
189
213
|
*/
|
|
190
214
|
const handleFailure = async (policy, params) => {
|
|
191
|
-
const { attempt, error, sleep, signal } = params;
|
|
192
|
-
const abortResult = buildAbortResult(signal, attempt);
|
|
215
|
+
const { attempt, error, sleep, signal, logger } = params;
|
|
216
|
+
const abortResult = buildAbortResult(signal, attempt, logger);
|
|
193
217
|
if (abortResult !== void 0) return abortResult;
|
|
194
218
|
const decision = policy.next({
|
|
195
219
|
attempt,
|
|
196
220
|
error
|
|
197
221
|
});
|
|
222
|
+
logRetryDecision(logger, attempt, decision, error);
|
|
198
223
|
if (!decision.shouldRetry) return {
|
|
199
224
|
attempts: attempt,
|
|
200
225
|
error: policy.onExhausted({
|
|
@@ -204,7 +229,7 @@ const handleFailure = async (policy, params) => {
|
|
|
204
229
|
ok: false
|
|
205
230
|
};
|
|
206
231
|
if (decision.delayMs > 0) {
|
|
207
|
-
const delayAbortResult = await waitForDelay(sleep, decision.delayMs, signal, attempt);
|
|
232
|
+
const delayAbortResult = await waitForDelay(sleep, decision.delayMs, signal, attempt, logger);
|
|
208
233
|
if (delayAbortResult !== void 0) return delayAbortResult;
|
|
209
234
|
}
|
|
210
235
|
};
|
|
@@ -216,6 +241,8 @@ const handleFailure = async (policy, params) => {
|
|
|
216
241
|
* @param execute - Async work callback per attempt.
|
|
217
242
|
* @param sleep - Delay function between retries.
|
|
218
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`.
|
|
219
246
|
* @returns Terminal success or failure object. When `policy.isKnownError`
|
|
220
247
|
* rejects a caught value as outside this policy's error domain, the
|
|
221
248
|
* original value is wrapped in a `RetryError` and returned on
|
|
@@ -223,17 +250,17 @@ const handleFailure = async (policy, params) => {
|
|
|
223
250
|
* @throws {Error} Any error thrown by `next`, `onExhausted`, or a non-abort `sleep`
|
|
224
251
|
* failure.
|
|
225
252
|
*/
|
|
226
|
-
const runResultMode = async (policy, execute, sleep, signal) => {
|
|
253
|
+
const runResultMode = async (policy, execute, sleep, signal, logger) => {
|
|
227
254
|
let attempt = 1;
|
|
228
255
|
while (true) {
|
|
229
|
-
const abortResult = buildAbortResult(signal, Math.max(0, attempt - 1));
|
|
256
|
+
const abortResult = buildAbortResult(signal, Math.max(0, attempt - 1), logger);
|
|
230
257
|
if (abortResult !== void 0) return abortResult;
|
|
231
258
|
const execution = await runAttempt(execute, attempt);
|
|
232
259
|
if (execution.ok) return {
|
|
233
260
|
ok: true,
|
|
234
261
|
value: execution.value
|
|
235
262
|
};
|
|
236
|
-
const attemptAbortResult = buildAbortResult(signal, attempt);
|
|
263
|
+
const attemptAbortResult = buildAbortResult(signal, attempt, logger);
|
|
237
264
|
if (attemptAbortResult !== void 0) return attemptAbortResult;
|
|
238
265
|
if (!policy.isKnownError(execution.error)) return {
|
|
239
266
|
attempts: attempt,
|
|
@@ -246,6 +273,7 @@ const runResultMode = async (policy, execute, sleep, signal) => {
|
|
|
246
273
|
const failure = await handleFailure(policy, {
|
|
247
274
|
attempt,
|
|
248
275
|
error: execution.error,
|
|
276
|
+
logger,
|
|
249
277
|
signal,
|
|
250
278
|
sleep
|
|
251
279
|
});
|
|
@@ -291,14 +319,14 @@ const defaultIsKnownError = (error) => error instanceof Error;
|
|
|
291
319
|
*/
|
|
292
320
|
async function runRetryPolicy(policy, execute, options = {}) {
|
|
293
321
|
const sleep = options.sleep ?? defaultSleep;
|
|
294
|
-
const { signal } = options;
|
|
322
|
+
const { signal, logger } = options;
|
|
295
323
|
const resolvedPolicy = {
|
|
296
324
|
isKnownError: (error) => policy.isKnownError ? policy.isKnownError(error) : defaultIsKnownError(error),
|
|
297
325
|
next: (input) => policy.next(input),
|
|
298
326
|
onExhausted: (input) => policy.onExhausted ? policy.onExhausted(input) : defaultOnExhausted(input)
|
|
299
327
|
};
|
|
300
|
-
if (options.throwOnExhausted === false) return await runResultMode(resolvedPolicy, execute, sleep, signal);
|
|
301
|
-
return await runThrowMode(resolvedPolicy, execute, sleep, signal);
|
|
328
|
+
if (options.throwOnExhausted === false) return await runResultMode(resolvedPolicy, execute, sleep, signal, logger);
|
|
329
|
+
return await runThrowMode(resolvedPolicy, execute, sleep, signal, logger);
|
|
302
330
|
}
|
|
303
331
|
//#endregion
|
|
304
332
|
export { defaultSleep, runRetryPolicy };
|
package/dist/base-policy.js.map
CHANGED
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"base-policy.js","names":[],"sources":["../src/base-policy.ts"],"sourcesContent":["/**\n * Retry runner base class and shared orchestration implementation.\n *\n * @module @zap-studio/retry/base-policy\n */\n\nimport { AbortError, RetryError } from \"./errors.js\";\nimport type {\n ResolvedRetryPolicy,\n RetryExhaustedInput,\n RetryPolicy,\n RetryRunOptions,\n RetryRunResult,\n} from \"./types.js\";\n\n/**\n * Awaits a timer-based delay, unless `delayMs` is non-positive.\n *\n * @param delayMs - Milliseconds to wait before resolving.\n * @returns Promise that resolves when the delay completes.\n *\n * @example\n * ```ts\n * import { defaultSleep } from \"@zap-studio/retry\";\n *\n * await defaultSleep(250); // waits 250ms\n * ```\n */\nexport const defaultSleep = async (delayMs: number): Promise<void> => {\n if (delayMs <= 0) {\n return;\n }\n\n // oxlint-disable-next-line promise/avoid-new -- Timer sleep requires adapting callback API to a promise.\n await new Promise<void>((resolve) => {\n setTimeout(resolve, delayMs);\n });\n};\n\n/**\n * Normalizes an abort `reason` into an `AbortError`.\n */\nconst toAbortError = (reason: unknown): AbortError => {\n if (reason instanceof AbortError) {\n return reason;\n }\n\n if (reason instanceof Error) {\n return new AbortError(reason.message, { cause: reason });\n }\n\n if (typeof reason === \"string\" && reason.length > 0) {\n return new AbortError(reason);\n }\n\n if (reason === undefined) {\n return new AbortError(\"Retry aborted.\");\n }\n\n try {\n return new AbortError(`Retry aborted: ${JSON.stringify(reason)}`);\n } catch {\n return new AbortError(\"Retry aborted.\");\n }\n};\n\n/**\n * Throws when the provided abort signal is already aborted.\n *\n * @param signal - Optional abort signal to inspect.\n * @throws {AbortError} When the signal is aborted.\n */\nconst throwIfAborted = (signal?: AbortSignal): void => {\n if (signal?.aborted !== true) {\n return;\n }\n\n throw toAbortError(signal.reason);\n};\n\n/**\n * Waits for delay sleep while observing cancellation through an abort signal.\n *\n * @param sleep - Sleep function used to await `delayMs`.\n * @param delayMs - Delay duration in milliseconds.\n * @param signal - Abort signal to observe while waiting.\n * @returns Promise that resolves when delay finishes.\n * @throws {AbortError} When the signal aborts before or during wait.\n */\nconst sleepWithAbortSignal = async (\n sleep: (delayMs: number) => Promise<void>,\n delayMs: number,\n signal: AbortSignal\n): Promise<void> => {\n if (signal.aborted) {\n throw toAbortError(signal.reason);\n }\n\n let onAbort: (() => void) | undefined;\n\n try {\n await Promise.race([\n sleep(delayMs),\n // oxlint-disable-next-line promise/avoid-new -- AbortSignal callback is adapted into the race promise.\n new Promise<never>((_resolve, reject) => {\n onAbort = (): void => {\n reject(toAbortError(signal.reason));\n };\n\n signal.addEventListener(\"abort\", onAbort, { once: true });\n }),\n ]);\n } finally {\n if (onAbort) {\n signal.removeEventListener(\"abort\", onAbort);\n }\n }\n};\n\n/**\n * Runs the throw-mode retry loop: throws `RetryError` on exhaustion and\n * `AbortError` when `signal` aborts.\n *\n * @param policy - Resolved retry policy providing `next` and `onExhausted`.\n * @param execute - Async work callback per attempt.\n * @param sleep - Delay function between retries.\n * @param signal - Optional cancel signal.\n * @returns Resolves to the first successful return value.\n * @throws {RetryError} When retries are exhausted and `onExhausted` returns\n * the terminal error.\n * @throws {AbortError} When `signal` is already aborted or aborts while waiting.\n * @throws {Error} Any error thrown by `next`, `onExhausted`, or `sleep`. Also\n * rethrows the original caught value immediately, bypassing retry, when\n * `policy.isKnownError` rejects it as outside this policy's error domain.\n */\nconst runThrowMode = async <T, TError extends Error, TData>(\n policy: ResolvedRetryPolicy<TError, TData>,\n execute: (attempt: number) => Promise<T>,\n sleep: (delayMs: number) => Promise<void>,\n signal?: AbortSignal\n): Promise<T> => {\n let attempt = 1;\n\n while (true) {\n throwIfAborted(signal);\n\n try {\n // oxlint-disable-next-line no-await-in-loop -- Retry attempts must run sequentially.\n return await execute(attempt);\n } catch (error) {\n throwIfAborted(signal);\n\n if (!policy.isKnownError(error)) {\n throw error;\n }\n\n const decision = policy.next({\n attempt,\n error,\n });\n\n if (!decision.shouldRetry) {\n throw policy.onExhausted({\n attempts: attempt,\n error,\n });\n }\n\n if (decision.delayMs > 0) {\n // oxlint-disable-next-line no-await-in-loop -- Delay belongs between sequential retry attempts.\n await (signal === undefined\n ? sleep(decision.delayMs)\n : sleepWithAbortSignal(sleep, decision.delayMs, signal));\n }\n\n attempt += 1;\n }\n }\n};\n\n/**\n * When `signal` is already aborted, builds the terminal `{ ok: false }` object\n * with a normalized `AbortError` on `error`.\n *\n * @param signal - Optional abort signal; only acts when `aborted` is set.\n * @param attempts - Number of finished attempts to report in the result.\n * @returns Failure result or `undefined` if not aborted.\n */\nconst buildAbortResult = (\n signal: AbortSignal | undefined,\n attempts: number\n): RetryRunResult<never> | undefined => {\n if (signal?.aborted !== true) {\n return undefined;\n }\n\n return {\n attempts,\n error: toAbortError(signal.reason),\n ok: false,\n };\n};\n\n/**\n * Runs one `execute(attempt)` call and returns either a success value or a\n * captured error without rethrowing.\n *\n * @param execute - User work callback.\n * @param attempt - One-based attempt number passed to `execute`.\n * @returns A tagged success with `value` or a tagged failure with `error`.\n */\nconst runAttempt = async <T>(\n execute: (attempt: number) => Promise<T>,\n attempt: number\n): Promise<{ ok: true; value: T } | { ok: false; error: unknown }> => {\n try {\n return {\n ok: true,\n value: await execute(attempt),\n };\n } catch (error) {\n return {\n error,\n ok: false,\n };\n }\n};\n\n/**\n * Awaits inter-attempt delay in result mode, mapping an abort during wait to\n * a terminal result instead of throwing when `throwOnExhausted` is false.\n *\n * @param sleep - Custom or default sleep implementation.\n * @param delayMs - Milliseconds to wait.\n * @param signal - If set, `sleep` is raced with the abort signal.\n * @param attempts - Attempt count to attach if the wait ends in abort.\n * @returns A terminal result when canceled during the wait, otherwise\n * `undefined`.\n * @throws {Error} The underlying `sleep` rejection when it is not an abort.\n */\nconst waitForDelay = async (\n sleep: (delayMs: number) => Promise<void>,\n delayMs: number,\n signal: AbortSignal | undefined,\n attempts: number\n): Promise<RetryRunResult<never> | undefined> => {\n if (signal === undefined) {\n await sleep(delayMs);\n return undefined;\n }\n\n try {\n await sleepWithAbortSignal(sleep, delayMs, signal);\n return undefined;\n } catch (error) {\n const aborted = buildAbortResult(signal, attempts);\n if (aborted !== undefined) {\n return aborted;\n }\n throw error;\n }\n};\n\n/**\n * After a failed attempt, applies abort rules, `next`, optional delay, and\n * either returns a terminal `RetryRunResult` or `undefined` to continue.\n *\n * @param policy - Resolved retry policy hooks (`next`, `onExhausted`).\n * @param params - Failure context for the current attempt.\n * @param params.attempt - Current attempt number.\n * @param params.error - Error thrown by the attempt.\n * @param params.sleep - Delay function between retries.\n * @param params.signal - Optional abort signal.\n * @returns Terminal non-throw result if the loop should stop, otherwise\n * `undefined` to schedule another attempt.\n * @throws {Error} Any error thrown by `next`, `onExhausted`, or a custom `sleep` when\n * the error is not an abort.\n */\nconst handleFailure = async <TError extends Error, TData>(\n policy: ResolvedRetryPolicy<TError, TData>,\n params: {\n attempt: number;\n error: TError;\n sleep: (delayMs: number) => Promise<void>;\n signal: AbortSignal | undefined;\n }\n): Promise<RetryRunResult<never> | undefined> => {\n const { attempt, error, sleep, signal } = params;\n const abortResult = buildAbortResult(signal, attempt);\n if (abortResult !== undefined) {\n return abortResult;\n }\n\n const decision = policy.next({\n attempt,\n error,\n });\n\n if (!decision.shouldRetry) {\n const terminalError = policy.onExhausted({\n attempts: attempt,\n error,\n });\n\n return {\n attempts: attempt,\n error: terminalError,\n ok: false,\n };\n }\n\n if (decision.delayMs > 0) {\n const delayAbortResult = await waitForDelay(\n sleep,\n decision.delayMs,\n signal,\n attempt\n );\n if (delayAbortResult !== undefined) {\n return delayAbortResult;\n }\n }\n\n return undefined;\n};\n\n/**\n * Runs the non-throw retry loop, returning\n * `RetryRunResult`.\n *\n * @param policy - Resolved retry policy providing `next` and `onExhausted`.\n * @param execute - Async work callback per attempt.\n * @param sleep - Delay function between retries.\n * @param signal - Optional cancel signal.\n * @returns Terminal success or failure object. When `policy.isKnownError`\n * rejects a caught value as outside this policy's error domain, the\n * original value is wrapped in a `RetryError` and returned on\n * `result.error` immediately, bypassing retry — never thrown.\n * @throws {Error} Any error thrown by `next`, `onExhausted`, or a non-abort `sleep`\n * failure.\n */\nconst runResultMode = async <T, TError extends Error, TData>(\n policy: ResolvedRetryPolicy<TError, TData>,\n execute: (attempt: number) => Promise<T>,\n sleep: (delayMs: number) => Promise<void>,\n signal?: AbortSignal\n): Promise<RetryRunResult<T>> => {\n let attempt = 1;\n\n while (true) {\n const abortResult = buildAbortResult(signal, Math.max(0, attempt - 1));\n if (abortResult !== undefined) {\n return abortResult;\n }\n\n // oxlint-disable-next-line no-await-in-loop -- Retry attempts must run sequentially.\n const execution = await runAttempt(execute, attempt);\n if (execution.ok) {\n return { ok: true, value: execution.value };\n }\n\n const attemptAbortResult = buildAbortResult(signal, attempt);\n if (attemptAbortResult !== undefined) {\n return attemptAbortResult;\n }\n\n if (!policy.isKnownError(execution.error)) {\n return {\n attempts: attempt,\n error: new RetryError(\"Retry policy encountered an unknown error.\", {\n attempts: attempt,\n lastError: execution.error,\n }),\n ok: false,\n };\n }\n\n // oxlint-disable-next-line no-await-in-loop -- Failure handling belongs to the current sequential attempt.\n const failure = await handleFailure(policy, {\n attempt,\n error: execution.error,\n signal,\n sleep,\n });\n if (failure !== undefined) {\n return failure;\n }\n\n attempt += 1;\n }\n};\n\n/**\n * Default `onExhausted` used when a policy omits it: wraps the exhaustion\n * context in a generic `RetryError`.\n */\nconst defaultOnExhausted = <TError extends Error, TData>(\n input: RetryExhaustedInput<TError, TData>\n): RetryError =>\n new RetryError(\"Retry policy exhausted all attempts.\", {\n attempts: input.attempts,\n lastData: input.data,\n lastError: input.error,\n });\n\n/**\n * Default `isKnownError` used when a policy omits it: accepts any `Error`\n * instance and rejects everything else.\n */\n// oxlint-disable-next-line typescript/no-unnecessary-type-parameters -- TError only appears in the return predicate; needed so callers infer the right narrowed type.\nconst defaultIsKnownError = <TError extends Error>(\n error: unknown\n): error is TError => error instanceof Error;\n\n/**\n * Runs retry orchestration in non-throw mode.\n *\n * @param policy - Retry policy: `next` is required, `onExhausted` and\n * `isKnownError` fall back to their defaults when omitted.\n * @param execute - Async function to execute per attempt.\n * @param options - Runner settings with `throwOnExhausted: false`.\n * @returns A discriminated result union containing success value or terminal error.\n * When `policy.isKnownError` rejects a caught value, it is wrapped in a\n * `RetryError` and returned as the terminal failure instead of thrown.\n * @throws {Error} Any error thrown by `next`, `onExhausted`, or a custom `sleep`.\n */\nexport function runRetryPolicy<\n T,\n TError extends Error = Error,\n TData = unknown,\n>(\n policy: RetryPolicy<TError, TData>,\n execute: (attempt: number) => Promise<T>,\n options: RetryRunOptions & { throwOnExhausted: false }\n): Promise<RetryRunResult<T>>;\n\n/**\n * Runs retry orchestration and throws terminal error on exhaustion.\n *\n * @param policy - Retry policy: `next` is required, `onExhausted` and\n * `isKnownError` fall back to their defaults when omitted.\n * @param execute - Async function to execute per attempt.\n * @param options - Optional runner settings.\n * @returns The successful execution value.\n * @throws {RetryError} When retries are exhausted and `onExhausted` returns the\n * terminal retry error. The default implementation returns `RetryError` with the last\n * execution failure available on `RetryError.lastError`.\n * @throws {AbortError} When `options.signal` is already aborted or aborts while retrying.\n * @throws {Error} Any error thrown by `next`, by `onExhausted`, or by a custom `sleep`\n * function.\n *\n * @example\n * ```ts\n * import { runRetryPolicy } from \"@zap-studio/retry\";\n * import type { RetryPolicy } from \"@zap-studio/retry\";\n *\n * const linearBackoff: RetryPolicy = {\n * next: ({ attempt }) =>\n * attempt < 3\n * ? { shouldRetry: true, delayMs: attempt * 100, reason: \"retry\" }\n * : { shouldRetry: false, delayMs: 0, reason: \"max-attempts-reached\" },\n * };\n *\n * const data = await runRetryPolicy(linearBackoff, async () => fetchFlakyResource());\n * ```\n */\nexport function runRetryPolicy<\n T,\n TError extends Error = Error,\n TData = unknown,\n>(\n policy: RetryPolicy<TError, TData>,\n execute: (attempt: number) => Promise<T>,\n options?: RetryRunOptions & { throwOnExhausted?: true }\n): Promise<T>;\n\n/**\n * Runs retry orchestration in non-throw mode.\n *\n * When `throwOnExhausted` is `false`, returns a discriminated result union.\n *\n * @param policy - Retry policy: `next` is required, `onExhausted` and\n * `isKnownError` fall back to their defaults when omitted.\n * @param execute - Async function to execute per attempt.\n * @param options - Runner settings.\n * @returns Success value or terminal result object based on option mode.\n * @throws {Error} Any error thrown by `next`, by `onExhausted`, or by a custom `sleep`\n * function. When `throwOnExhausted` is `false`, exhaustion itself is returned\n * as `{ ok: false }` instead of thrown.\n * Cancellation is returned as `{ ok: false, error: AbortError }` in non-throw\n * mode. A value rejected by `policy.isKnownError` is wrapped in a\n * `RetryError` and returned the same way in non-throw mode; in throw mode\n * it is rethrown as-is.\n *\n * @example\n * const result = await runRetryPolicy(policy, doWork, { throwOnExhausted: false });\n * if (!result.ok) console.error(result.error);\n */\nexport async function runRetryPolicy<\n T,\n TError extends Error = Error,\n TData = unknown,\n>(\n policy: RetryPolicy<TError, TData>,\n execute: (attempt: number) => Promise<T>,\n options: RetryRunOptions = {}\n): Promise<T | RetryRunResult<T>> {\n const sleep = options.sleep ?? defaultSleep;\n const { signal } = options;\n const resolvedPolicy: ResolvedRetryPolicy<TError, TData> = {\n isKnownError: (error): error is TError =>\n policy.isKnownError\n ? policy.isKnownError(error)\n : defaultIsKnownError(error),\n next: (input) => policy.next(input),\n onExhausted: (input) =>\n policy.onExhausted\n ? policy.onExhausted(input)\n : defaultOnExhausted(input),\n };\n\n if (options.throwOnExhausted === false) {\n return await runResultMode(resolvedPolicy, execute, sleep, signal);\n }\n\n return await runThrowMode(resolvedPolicy, execute, sleep, signal);\n}\n"],"mappings":"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"}
|
|
1
|
+
{"version":3,"file":"base-policy.js","names":[],"sources":["../src/base-policy.ts"],"sourcesContent":["/**\n * Retry runner base class and shared orchestration implementation.\n *\n * @module @zap-studio/retry/base-policy\n */\n\nimport type { Logger } from \"@zap-studio/logger\";\n\nimport { AbortError, RetryError } from \"./errors.js\";\nimport type {\n ResolvedRetryPolicy,\n RetryDecision,\n RetryExhaustedInput,\n RetryPolicy,\n RetryRunOptions,\n RetryRunResult,\n} from \"./types.js\";\n\n/**\n * Awaits a timer-based delay, unless `delayMs` is non-positive.\n *\n * @param delayMs - Milliseconds to wait before resolving.\n * @returns Promise that resolves when the delay completes.\n *\n * @example\n * ```ts\n * import { defaultSleep } from \"@zap-studio/retry\";\n *\n * await defaultSleep(250); // waits 250ms\n * ```\n */\nexport const defaultSleep = async (delayMs: number): Promise<void> => {\n if (delayMs <= 0) {\n return;\n }\n\n // oxlint-disable-next-line promise/avoid-new -- Timer sleep requires adapting callback API to a promise.\n await new Promise<void>((resolve) => {\n setTimeout(resolve, delayMs);\n });\n};\n\n/**\n * Normalizes an abort `reason` into an `AbortError`.\n */\nconst toAbortError = (reason: unknown): AbortError => {\n if (reason instanceof AbortError) {\n return reason;\n }\n\n if (reason instanceof Error) {\n return new AbortError(reason.message, { cause: reason });\n }\n\n if (typeof reason === \"string\" && reason.length > 0) {\n return new AbortError(reason);\n }\n\n if (reason === undefined) {\n return new AbortError(\"Retry aborted.\");\n }\n\n try {\n return new AbortError(`Retry aborted: ${JSON.stringify(reason)}`);\n } catch {\n return new AbortError(\"Retry aborted.\");\n }\n};\n\n/**\n * Throws when the provided abort signal is already aborted.\n *\n * @param signal - Optional abort signal to inspect.\n * @param logger - Optional logger; logs the abort at `debug` before throwing.\n * @throws {AbortError} When the signal is aborted.\n */\nconst throwIfAborted = (signal?: AbortSignal, logger?: Logger): void => {\n if (signal?.aborted !== true) {\n return;\n }\n\n logger?.debug(\"retry aborted\", { reason: signal.reason });\n throw toAbortError(signal.reason);\n};\n\n/**\n * Waits for delay sleep while observing cancellation through an abort signal.\n *\n * @param sleep - Sleep function used to await `delayMs`.\n * @param delayMs - Delay duration in milliseconds.\n * @param signal - Abort signal to observe while waiting.\n * @returns Promise that resolves when delay finishes.\n * @throws {AbortError} When the signal aborts before or during wait.\n */\nconst sleepWithAbortSignal = async (\n sleep: (delayMs: number) => Promise<void>,\n delayMs: number,\n signal: AbortSignal\n): Promise<void> => {\n if (signal.aborted) {\n throw toAbortError(signal.reason);\n }\n\n let onAbort: (() => void) | undefined;\n\n try {\n await Promise.race([\n sleep(delayMs),\n // oxlint-disable-next-line promise/avoid-new -- AbortSignal callback is adapted into the race promise.\n new Promise<never>((_resolve, reject) => {\n onAbort = (): void => {\n reject(toAbortError(signal.reason));\n };\n\n signal.addEventListener(\"abort\", onAbort, { once: true });\n }),\n ]);\n } finally {\n if (onAbort) {\n signal.removeEventListener(\"abort\", onAbort);\n }\n }\n};\n\n/**\n * Logs a `next(...)` decision: `debug` when retrying, `warn` when exhausted.\n * Shared by both the throw-mode and non-throw retry loops.\n */\nconst logRetryDecision = (\n logger: Logger | undefined,\n attempt: number,\n decision: RetryDecision,\n error: unknown\n): void => {\n if (decision.shouldRetry) {\n logger?.debug(\"retry scheduled\", {\n attempt,\n delayMs: decision.delayMs,\n reason: decision.reason,\n });\n return;\n }\n\n logger?.warn(\"retry policy exhausted\", {\n attempts: attempt,\n error,\n reason: decision.reason,\n });\n};\n\n/**\n * Runs the throw-mode retry loop: throws `RetryError` on exhaustion and\n * `AbortError` when `signal` aborts.\n *\n * @param policy - Resolved retry policy providing `next` and `onExhausted`.\n * @param execute - Async work callback per attempt.\n * @param sleep - Delay function between retries.\n * @param signal - Optional cancel signal.\n * @param logger - Optional logger; logs each retry decision at `debug` and\n * exhaustion at `warn`.\n * @returns Resolves to the first successful return value.\n * @throws {RetryError} When retries are exhausted and `onExhausted` returns\n * the terminal error.\n * @throws {AbortError} When `signal` is already aborted or aborts while waiting.\n * @throws {Error} Any error thrown by `next`, `onExhausted`, or `sleep`. Also\n * rethrows the original caught value immediately, bypassing retry, when\n * `policy.isKnownError` rejects it as outside this policy's error domain.\n */\nconst runThrowMode = async <T, TError extends Error, TData>(\n policy: ResolvedRetryPolicy<TError, TData>,\n execute: (attempt: number) => Promise<T>,\n sleep: (delayMs: number) => Promise<void>,\n signal?: AbortSignal,\n logger?: Logger\n): Promise<T> => {\n let attempt = 1;\n\n while (true) {\n throwIfAborted(signal, logger);\n\n try {\n // oxlint-disable-next-line no-await-in-loop -- Retry attempts must run sequentially.\n return await execute(attempt);\n } catch (error) {\n throwIfAborted(signal, logger);\n\n if (!policy.isKnownError(error)) {\n throw error;\n }\n\n const decision = policy.next({\n attempt,\n error,\n });\n logRetryDecision(logger, attempt, decision, error);\n\n if (!decision.shouldRetry) {\n throw policy.onExhausted({\n attempts: attempt,\n error,\n });\n }\n\n if (decision.delayMs > 0) {\n // oxlint-disable-next-line no-await-in-loop -- Delay belongs between sequential retry attempts.\n await (signal === undefined\n ? sleep(decision.delayMs)\n : sleepWithAbortSignal(sleep, decision.delayMs, signal));\n }\n\n attempt += 1;\n }\n }\n};\n\n/**\n * When `signal` is already aborted, builds the terminal `{ ok: false }` object\n * with a normalized `AbortError` on `error`.\n *\n * @param signal - Optional abort signal; only acts when `aborted` is set.\n * @param attempts - Number of finished attempts to report in the result.\n * @param logger - Optional logger; logs the abort at `debug`.\n * @returns Failure result or `undefined` if not aborted.\n */\nconst buildAbortResult = (\n signal: AbortSignal | undefined,\n attempts: number,\n logger?: Logger\n): RetryRunResult<never> | undefined => {\n if (signal?.aborted !== true) {\n return undefined;\n }\n\n logger?.debug(\"retry aborted\", { reason: signal.reason });\n\n return {\n attempts,\n error: toAbortError(signal.reason),\n ok: false,\n };\n};\n\n/**\n * Runs one `execute(attempt)` call and returns either a success value or a\n * captured error without rethrowing.\n *\n * @param execute - User work callback.\n * @param attempt - One-based attempt number passed to `execute`.\n * @returns A tagged success with `value` or a tagged failure with `error`.\n */\nconst runAttempt = async <T>(\n execute: (attempt: number) => Promise<T>,\n attempt: number\n): Promise<{ ok: true; value: T } | { ok: false; error: unknown }> => {\n try {\n return {\n ok: true,\n value: await execute(attempt),\n };\n } catch (error) {\n return {\n error,\n ok: false,\n };\n }\n};\n\n/**\n * Awaits inter-attempt delay in result mode, mapping an abort during wait to\n * a terminal result instead of throwing when `throwOnExhausted` is false.\n *\n * @param sleep - Custom or default sleep implementation.\n * @param delayMs - Milliseconds to wait.\n * @param signal - If set, `sleep` is raced with the abort signal.\n * @param attempts - Attempt count to attach if the wait ends in abort.\n * @param logger - Optional logger; logs an abort ending the wait at `debug`.\n * @returns A terminal result when canceled during the wait, otherwise\n * `undefined`.\n * @throws {Error} The underlying `sleep` rejection when it is not an abort.\n */\nconst waitForDelay = async (\n sleep: (delayMs: number) => Promise<void>,\n delayMs: number,\n signal: AbortSignal | undefined,\n attempts: number,\n logger?: Logger\n): Promise<RetryRunResult<never> | undefined> => {\n if (signal === undefined) {\n await sleep(delayMs);\n return undefined;\n }\n\n try {\n await sleepWithAbortSignal(sleep, delayMs, signal);\n return undefined;\n } catch (error) {\n const aborted = buildAbortResult(signal, attempts, logger);\n if (aborted !== undefined) {\n return aborted;\n }\n throw error;\n }\n};\n\n/**\n * After a failed attempt, applies abort rules, `next`, optional delay, and\n * either returns a terminal `RetryRunResult` or `undefined` to continue.\n *\n * @param policy - Resolved retry policy hooks (`next`, `onExhausted`).\n * @param params - Failure context for the current attempt.\n * @param params.attempt - Current attempt number.\n * @param params.error - Error thrown by the attempt.\n * @param params.sleep - Delay function between retries.\n * @param params.signal - Optional abort signal.\n * @param params.logger - Optional logger; logs each retry decision at\n * `debug`, exhaustion at `warn`, and cancellation at `debug`.\n * @returns Terminal non-throw result if the loop should stop, otherwise\n * `undefined` to schedule another attempt.\n * @throws {Error} Any error thrown by `next`, `onExhausted`, or a custom `sleep` when\n * the error is not an abort.\n */\nconst handleFailure = async <TError extends Error, TData>(\n policy: ResolvedRetryPolicy<TError, TData>,\n params: {\n attempt: number;\n error: TError;\n sleep: (delayMs: number) => Promise<void>;\n signal: AbortSignal | undefined;\n logger: Logger | undefined;\n }\n): Promise<RetryRunResult<never> | undefined> => {\n const { attempt, error, sleep, signal, logger } = params;\n const abortResult = buildAbortResult(signal, attempt, logger);\n if (abortResult !== undefined) {\n return abortResult;\n }\n\n const decision = policy.next({\n attempt,\n error,\n });\n logRetryDecision(logger, attempt, decision, error);\n\n if (!decision.shouldRetry) {\n const terminalError = policy.onExhausted({\n attempts: attempt,\n error,\n });\n\n return {\n attempts: attempt,\n error: terminalError,\n ok: false,\n };\n }\n\n if (decision.delayMs > 0) {\n const delayAbortResult = await waitForDelay(\n sleep,\n decision.delayMs,\n signal,\n attempt,\n logger\n );\n if (delayAbortResult !== undefined) {\n return delayAbortResult;\n }\n }\n\n return undefined;\n};\n\n/**\n * Runs the non-throw retry loop, returning\n * `RetryRunResult`.\n *\n * @param policy - Resolved retry policy providing `next` and `onExhausted`.\n * @param execute - Async work callback per attempt.\n * @param sleep - Delay function between retries.\n * @param signal - Optional cancel signal.\n * @param logger - Optional logger; logs each retry decision at `debug`,\n * exhaustion at `warn`, and cancellation at `debug`.\n * @returns Terminal success or failure object. When `policy.isKnownError`\n * rejects a caught value as outside this policy's error domain, the\n * original value is wrapped in a `RetryError` and returned on\n * `result.error` immediately, bypassing retry — never thrown.\n * @throws {Error} Any error thrown by `next`, `onExhausted`, or a non-abort `sleep`\n * failure.\n */\nconst runResultMode = async <T, TError extends Error, TData>(\n policy: ResolvedRetryPolicy<TError, TData>,\n execute: (attempt: number) => Promise<T>,\n sleep: (delayMs: number) => Promise<void>,\n signal?: AbortSignal,\n logger?: Logger\n): Promise<RetryRunResult<T>> => {\n let attempt = 1;\n\n while (true) {\n const abortResult = buildAbortResult(\n signal,\n Math.max(0, attempt - 1),\n logger\n );\n if (abortResult !== undefined) {\n return abortResult;\n }\n\n // oxlint-disable-next-line no-await-in-loop -- Retry attempts must run sequentially.\n const execution = await runAttempt(execute, attempt);\n if (execution.ok) {\n return { ok: true, value: execution.value };\n }\n\n const attemptAbortResult = buildAbortResult(signal, attempt, logger);\n if (attemptAbortResult !== undefined) {\n return attemptAbortResult;\n }\n\n if (!policy.isKnownError(execution.error)) {\n return {\n attempts: attempt,\n error: new RetryError(\"Retry policy encountered an unknown error.\", {\n attempts: attempt,\n lastError: execution.error,\n }),\n ok: false,\n };\n }\n\n // oxlint-disable-next-line no-await-in-loop -- Failure handling belongs to the current sequential attempt.\n const failure = await handleFailure(policy, {\n attempt,\n error: execution.error,\n logger,\n signal,\n sleep,\n });\n if (failure !== undefined) {\n return failure;\n }\n\n attempt += 1;\n }\n};\n\n/**\n * Default `onExhausted` used when a policy omits it: wraps the exhaustion\n * context in a generic `RetryError`.\n */\nconst defaultOnExhausted = <TError extends Error, TData>(\n input: RetryExhaustedInput<TError, TData>\n): RetryError =>\n new RetryError(\"Retry policy exhausted all attempts.\", {\n attempts: input.attempts,\n lastData: input.data,\n lastError: input.error,\n });\n\n/**\n * Default `isKnownError` used when a policy omits it: accepts any `Error`\n * instance and rejects everything else.\n */\n// oxlint-disable-next-line typescript/no-unnecessary-type-parameters -- TError only appears in the return predicate; needed so callers infer the right narrowed type.\nconst defaultIsKnownError = <TError extends Error>(\n error: unknown\n): error is TError => error instanceof Error;\n\n/**\n * Runs retry orchestration in non-throw mode.\n *\n * @param policy - Retry policy: `next` is required, `onExhausted` and\n * `isKnownError` fall back to their defaults when omitted.\n * @param execute - Async function to execute per attempt.\n * @param options - Runner settings with `throwOnExhausted: false`.\n * @returns A discriminated result union containing success value or terminal error.\n * When `policy.isKnownError` rejects a caught value, it is wrapped in a\n * `RetryError` and returned as the terminal failure instead of thrown.\n * @throws {Error} Any error thrown by `next`, `onExhausted`, or a custom `sleep`.\n */\nexport function runRetryPolicy<\n T,\n TError extends Error = Error,\n TData = unknown,\n>(\n policy: RetryPolicy<TError, TData>,\n execute: (attempt: number) => Promise<T>,\n options: RetryRunOptions & { throwOnExhausted: false }\n): Promise<RetryRunResult<T>>;\n\n/**\n * Runs retry orchestration and throws terminal error on exhaustion.\n *\n * @param policy - Retry policy: `next` is required, `onExhausted` and\n * `isKnownError` fall back to their defaults when omitted.\n * @param execute - Async function to execute per attempt.\n * @param options - Optional runner settings.\n * @returns The successful execution value.\n * @throws {RetryError} When retries are exhausted and `onExhausted` returns the\n * terminal retry error. The default implementation returns `RetryError` with the last\n * execution failure available on `RetryError.lastError`.\n * @throws {AbortError} When `options.signal` is already aborted or aborts while retrying.\n * @throws {Error} Any error thrown by `next`, by `onExhausted`, or by a custom `sleep`\n * function.\n *\n * @example\n * ```ts\n * import { runRetryPolicy } from \"@zap-studio/retry\";\n * import type { RetryPolicy } from \"@zap-studio/retry\";\n *\n * const linearBackoff: RetryPolicy = {\n * next: ({ attempt }) =>\n * attempt < 3\n * ? { shouldRetry: true, delayMs: attempt * 100, reason: \"retry\" }\n * : { shouldRetry: false, delayMs: 0, reason: \"max-attempts-reached\" },\n * };\n *\n * const data = await runRetryPolicy(linearBackoff, async () => fetchFlakyResource());\n * ```\n */\nexport function runRetryPolicy<\n T,\n TError extends Error = Error,\n TData = unknown,\n>(\n policy: RetryPolicy<TError, TData>,\n execute: (attempt: number) => Promise<T>,\n options?: RetryRunOptions & { throwOnExhausted?: true }\n): Promise<T>;\n\n/**\n * Runs retry orchestration in non-throw mode.\n *\n * When `throwOnExhausted` is `false`, returns a discriminated result union.\n *\n * @param policy - Retry policy: `next` is required, `onExhausted` and\n * `isKnownError` fall back to their defaults when omitted.\n * @param execute - Async function to execute per attempt.\n * @param options - Runner settings.\n * @returns Success value or terminal result object based on option mode.\n * @throws {Error} Any error thrown by `next`, by `onExhausted`, or by a custom `sleep`\n * function. When `throwOnExhausted` is `false`, exhaustion itself is returned\n * as `{ ok: false }` instead of thrown.\n * Cancellation is returned as `{ ok: false, error: AbortError }` in non-throw\n * mode. A value rejected by `policy.isKnownError` is wrapped in a\n * `RetryError` and returned the same way in non-throw mode; in throw mode\n * it is rethrown as-is.\n *\n * @example\n * const result = await runRetryPolicy(policy, doWork, { throwOnExhausted: false });\n * if (!result.ok) console.error(result.error);\n */\nexport async function runRetryPolicy<\n T,\n TError extends Error = Error,\n TData = unknown,\n>(\n policy: RetryPolicy<TError, TData>,\n execute: (attempt: number) => Promise<T>,\n options: RetryRunOptions = {}\n): Promise<T | RetryRunResult<T>> {\n const sleep = options.sleep ?? defaultSleep;\n const { signal, logger } = options;\n const resolvedPolicy: ResolvedRetryPolicy<TError, TData> = {\n isKnownError: (error): error is TError =>\n policy.isKnownError\n ? policy.isKnownError(error)\n : defaultIsKnownError(error),\n next: (input) => policy.next(input),\n onExhausted: (input) =>\n policy.onExhausted\n ? policy.onExhausted(input)\n : defaultOnExhausted(input),\n };\n\n if (options.throwOnExhausted === false) {\n return await runResultMode(resolvedPolicy, execute, sleep, signal, logger);\n }\n\n return await runThrowMode(resolvedPolicy, execute, sleep, signal, logger);\n}\n"],"mappings":";;;;;;;;;;;;;;;AA+BA,MAAa,eAAe,OAAO,YAAmC;CACpE,IAAI,WAAW,GACb;CAIF,MAAM,IAAI,SAAe,YAAY;EACnC,WAAW,SAAS,OAAO;CAC7B,CAAC;AACH;;;;AAKA,MAAM,gBAAgB,WAAgC;CACpD,IAAI,kBAAkB,YACpB,OAAO;CAGT,IAAI,kBAAkB,OACpB,OAAO,IAAI,WAAW,OAAO,SAAS,EAAE,OAAO,OAAO,CAAC;CAGzD,IAAI,OAAO,WAAW,YAAY,OAAO,SAAS,GAChD,OAAO,IAAI,WAAW,MAAM;CAG9B,IAAI,WAAW,KAAA,GACb,OAAO,IAAI,WAAW,gBAAgB;CAGxC,IAAI;EACF,OAAO,IAAI,WAAW,kBAAkB,KAAK,UAAU,MAAM,GAAG;CAClE,QAAQ;EACN,OAAO,IAAI,WAAW,gBAAgB;CACxC;AACF;;;;;;;;AASA,MAAM,kBAAkB,QAAsB,WAA0B;CACtE,IAAI,QAAQ,YAAY,MACtB;CAGF,QAAQ,MAAM,iBAAiB,EAAE,QAAQ,OAAO,OAAO,CAAC;CACxD,MAAM,aAAa,OAAO,MAAM;AAClC;;;;;;;;;;AAWA,MAAM,uBAAuB,OAC3B,OACA,SACA,WACkB;CAClB,IAAI,OAAO,SACT,MAAM,aAAa,OAAO,MAAM;CAGlC,IAAI;CAEJ,IAAI;EACF,MAAM,QAAQ,KAAK,CACjB,MAAM,OAAO,GAEb,IAAI,SAAgB,UAAU,WAAW;GACvC,gBAAsB;IACpB,OAAO,aAAa,OAAO,MAAM,CAAC;GACpC;GAEA,OAAO,iBAAiB,SAAS,SAAS,EAAE,MAAM,KAAK,CAAC;EAC1D,CAAC,CACH,CAAC;CACH,UAAU;EACR,IAAI,SACF,OAAO,oBAAoB,SAAS,OAAO;CAE/C;AACF;;;;;AAMA,MAAM,oBACJ,QACA,SACA,UACA,UACS;CACT,IAAI,SAAS,aAAa;EACxB,QAAQ,MAAM,mBAAmB;GAC/B;GACA,SAAS,SAAS;GAClB,QAAQ,SAAS;EACnB,CAAC;EACD;CACF;CAEA,QAAQ,KAAK,0BAA0B;EACrC,UAAU;EACV;EACA,QAAQ,SAAS;CACnB,CAAC;AACH;;;;;;;;;;;;;;;;;;;AAoBA,MAAM,eAAe,OACnB,QACA,SACA,OACA,QACA,WACe;CACf,IAAI,UAAU;CAEd,OAAO,MAAM;EACX,eAAe,QAAQ,MAAM;EAE7B,IAAI;GAEF,OAAO,MAAM,QAAQ,OAAO;EAC9B,SAAS,OAAO;GACd,eAAe,QAAQ,MAAM;GAE7B,IAAI,CAAC,OAAO,aAAa,KAAK,GAC5B,MAAM;GAGR,MAAM,WAAW,OAAO,KAAK;IAC3B;IACA;GACF,CAAC;GACD,iBAAiB,QAAQ,SAAS,UAAU,KAAK;GAEjD,IAAI,CAAC,SAAS,aACZ,MAAM,OAAO,YAAY;IACvB,UAAU;IACV;GACF,CAAC;GAGH,IAAI,SAAS,UAAU,GAErB,OAAO,WAAW,KAAA,IACd,MAAM,SAAS,OAAO,IACtB,qBAAqB,OAAO,SAAS,SAAS,MAAM;GAG1D,WAAW;EACb;CACF;AACF;;;;;;;;;;AAWA,MAAM,oBACJ,QACA,UACA,WACsC;CACtC,IAAI,QAAQ,YAAY,MACtB;CAGF,QAAQ,MAAM,iBAAiB,EAAE,QAAQ,OAAO,OAAO,CAAC;CAExD,OAAO;EACL;EACA,OAAO,aAAa,OAAO,MAAM;EACjC,IAAI;CACN;AACF;;;;;;;;;AAUA,MAAM,aAAa,OACjB,SACA,YACoE;CACpE,IAAI;EACF,OAAO;GACL,IAAI;GACJ,OAAO,MAAM,QAAQ,OAAO;EAC9B;CACF,SAAS,OAAO;EACd,OAAO;GACL;GACA,IAAI;EACN;CACF;AACF;;;;;;;;;;;;;;AAeA,MAAM,eAAe,OACnB,OACA,SACA,QACA,UACA,WAC+C;CAC/C,IAAI,WAAW,KAAA,GAAW;EACxB,MAAM,MAAM,OAAO;EACnB;CACF;CAEA,IAAI;EACF,MAAM,qBAAqB,OAAO,SAAS,MAAM;EACjD;CACF,SAAS,OAAO;EACd,MAAM,UAAU,iBAAiB,QAAQ,UAAU,MAAM;EACzD,IAAI,YAAY,KAAA,GACd,OAAO;EAET,MAAM;CACR;AACF;;;;;;;;;;;;;;;;;;AAmBA,MAAM,gBAAgB,OACpB,QACA,WAO+C;CAC/C,MAAM,EAAE,SAAS,OAAO,OAAO,QAAQ,WAAW;CAClD,MAAM,cAAc,iBAAiB,QAAQ,SAAS,MAAM;CAC5D,IAAI,gBAAgB,KAAA,GAClB,OAAO;CAGT,MAAM,WAAW,OAAO,KAAK;EAC3B;EACA;CACF,CAAC;CACD,iBAAiB,QAAQ,SAAS,UAAU,KAAK;CAEjD,IAAI,CAAC,SAAS,aAMZ,OAAO;EACL,UAAU;EACV,OAPoB,OAAO,YAAY;GACvC,UAAU;GACV;EACF,CAIqB;EACnB,IAAI;CACN;CAGF,IAAI,SAAS,UAAU,GAAG;EACxB,MAAM,mBAAmB,MAAM,aAC7B,OACA,SAAS,SACT,QACA,SACA,MACF;EACA,IAAI,qBAAqB,KAAA,GACvB,OAAO;CAEX;AAGF;;;;;;;;;;;;;;;;;;AAmBA,MAAM,gBAAgB,OACpB,QACA,SACA,OACA,QACA,WAC+B;CAC/B,IAAI,UAAU;CAEd,OAAO,MAAM;EACX,MAAM,cAAc,iBAClB,QACA,KAAK,IAAI,GAAG,UAAU,CAAC,GACvB,MACF;EACA,IAAI,gBAAgB,KAAA,GAClB,OAAO;EAIT,MAAM,YAAY,MAAM,WAAW,SAAS,OAAO;EACnD,IAAI,UAAU,IACZ,OAAO;GAAE,IAAI;GAAM,OAAO,UAAU;EAAM;EAG5C,MAAM,qBAAqB,iBAAiB,QAAQ,SAAS,MAAM;EACnE,IAAI,uBAAuB,KAAA,GACzB,OAAO;EAGT,IAAI,CAAC,OAAO,aAAa,UAAU,KAAK,GACtC,OAAO;GACL,UAAU;GACV,OAAO,IAAI,WAAW,8CAA8C;IAClE,UAAU;IACV,WAAW,UAAU;GACvB,CAAC;GACD,IAAI;EACN;EAIF,MAAM,UAAU,MAAM,cAAc,QAAQ;GAC1C;GACA,OAAO,UAAU;GACjB;GACA;GACA;EACF,CAAC;EACD,IAAI,YAAY,KAAA,GACd,OAAO;EAGT,WAAW;CACb;AACF;;;;;AAMA,MAAM,sBACJ,UAEA,IAAI,WAAW,wCAAwC;CACrD,UAAU,MAAM;CAChB,UAAU,MAAM;CAChB,WAAW,MAAM;AACnB,CAAC;;;;;AAOH,MAAM,uBACJ,UACoB,iBAAiB;;;;;;;;;;;;;;;;;;;;;;;AAsFvC,eAAsB,eAKpB,QACA,SACA,UAA2B,CAAC,GACI;CAChC,MAAM,QAAQ,QAAQ,SAAS;CAC/B,MAAM,EAAE,QAAQ,WAAW;CAC3B,MAAM,iBAAqD;EACzD,eAAe,UACb,OAAO,eACH,OAAO,aAAa,KAAK,IACzB,oBAAoB,KAAK;EAC/B,OAAO,UAAU,OAAO,KAAK,KAAK;EAClC,cAAc,UACZ,OAAO,cACH,OAAO,YAAY,KAAK,IACxB,mBAAmB,KAAK;CAChC;CAEA,IAAI,QAAQ,qBAAqB,OAC/B,OAAO,MAAM,cAAc,gBAAgB,SAAS,OAAO,QAAQ,MAAM;CAG3E,OAAO,MAAM,aAAa,gBAAgB,SAAS,OAAO,QAAQ,MAAM;AAC1E"}
|
package/dist/types.d.ts
CHANGED
|
@@ -1,4 +1,5 @@
|
|
|
1
1
|
import { r as RetryError, t as AbortError } from "./errors-CS5UPJWs.js";
|
|
2
|
+
import { Logger } from "@zap-studio/logger";
|
|
2
3
|
//#region src/types.d.ts
|
|
3
4
|
/**
|
|
4
5
|
* Retry policy contract consumed by `runRetryPolicy(...)`.
|
|
@@ -47,8 +48,19 @@ interface RetryPolicy<TError extends Error = Error, TData = unknown> {
|
|
|
47
48
|
* functions, used internally once `runRetryPolicy` has applied defaults.
|
|
48
49
|
*/
|
|
49
50
|
interface ResolvedRetryPolicy<TError extends Error, TData> {
|
|
51
|
+
/**
|
|
52
|
+
* Returns the retry decision for a failed attempt.
|
|
53
|
+
*/
|
|
50
54
|
next: RetryPolicy<TError, TData>["next"];
|
|
55
|
+
/**
|
|
56
|
+
* Builds the terminal error used when retries are exhausted. Always
|
|
57
|
+
* present here, unlike on `RetryPolicy` where it's optional.
|
|
58
|
+
*/
|
|
51
59
|
onExhausted: (input: RetryExhaustedInput<TError, TData>) => RetryError;
|
|
60
|
+
/**
|
|
61
|
+
* Narrows a caught `unknown` value into `TError`. Always present here,
|
|
62
|
+
* unlike on `RetryPolicy` where it's optional.
|
|
63
|
+
*/
|
|
52
64
|
isKnownError: (error: unknown) => error is TError;
|
|
53
65
|
}
|
|
54
66
|
/**
|
|
@@ -146,6 +158,13 @@ interface RetryRunOptions {
|
|
|
146
158
|
* @default true
|
|
147
159
|
*/
|
|
148
160
|
readonly throwOnExhausted?: boolean;
|
|
161
|
+
/**
|
|
162
|
+
* Optional logger for retry internals. When omitted, nothing is logged.
|
|
163
|
+
*
|
|
164
|
+
* Logs each retry decision at `debug`, exhaustion at `warn`, and
|
|
165
|
+
* cancellation at `debug`.
|
|
166
|
+
*/
|
|
167
|
+
readonly logger?: Logger;
|
|
149
168
|
}
|
|
150
169
|
/**
|
|
151
170
|
* Result union returned by non-throw runner mode.
|
package/dist/types.d.ts.map
CHANGED
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"types.d.ts","names":[],"sources":["../src/types.ts"],"mappings":"
|
|
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,mBACf,eAAe,QAAQ,OACvB;;;;WAKS;;;;;WAKA;;;;;WAKA,QAAQ;;;;;WAKR,OAAO;;;;;;;;UASD,oBACf,eAAe,QAAQ,OACvB;;;;WAKS;;;;WAIA,QAAQ;;;;WAIR,OAAO;;;;;;;;UASD;;;;;;WAMN,SAAS,oBAAoB;;;;;;WAM7B,SAAS;;;;;;;;WAQT;;;;;;;WAOA,SAAS;;;;;;;;;;;;;KAcR,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": "1.
|
|
3
|
+
"version": "1.2.0",
|
|
4
4
|
"private": false,
|
|
5
5
|
"description": "Composable, tree-shakeable retry policies for resilient async operations.",
|
|
6
6
|
"keywords": [
|
|
@@ -43,6 +43,9 @@
|
|
|
43
43
|
"publishConfig": {
|
|
44
44
|
"access": "public"
|
|
45
45
|
},
|
|
46
|
+
"dependencies": {
|
|
47
|
+
"@zap-studio/logger": "1.0.0"
|
|
48
|
+
},
|
|
46
49
|
"devDependencies": {
|
|
47
50
|
"tsdown": "^0.22.14",
|
|
48
51
|
"typescript": "^7.0.2",
|