@zap-studio/retry 0.3.1 → 1.0.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 +55 -14
- package/README.md +87 -141
- package/dist/base-policy.d.ts +67 -0
- package/dist/base-policy.d.ts.map +1 -0
- package/dist/base-policy.js +306 -0
- package/dist/base-policy.js.map +1 -0
- package/dist/{errors-BVZjP1Q5.d.mts → errors-CS5UPJWs.d.ts} +25 -1
- package/dist/errors-CS5UPJWs.d.ts.map +1 -0
- package/dist/{errors.d.mts → errors.d.ts} +1 -1
- package/dist/{errors.mjs → errors.js} +19 -1
- package/dist/errors.js.map +1 -0
- package/dist/exponential-backoff.d.ts +40 -0
- package/dist/exponential-backoff.d.ts.map +1 -0
- package/dist/exponential-backoff.js +35 -0
- package/dist/exponential-backoff.js.map +1 -0
- package/dist/fixed-delay.d.ts +31 -0
- package/dist/fixed-delay.d.ts.map +1 -0
- package/dist/fixed-delay.js +33 -0
- package/dist/fixed-delay.js.map +1 -0
- package/dist/index.d.ts +7 -0
- package/dist/index.js +6 -0
- package/dist/linear-backoff.d.ts +46 -0
- package/dist/linear-backoff.d.ts.map +1 -0
- package/dist/linear-backoff.js +35 -0
- package/dist/linear-backoff.js.map +1 -0
- package/dist/{types.d.mts → types.d.ts} +52 -8
- package/dist/types.d.ts.map +1 -0
- package/dist/types.js +0 -0
- package/package.json +12 -22
- package/dist/abort.d.mts +0 -29
- package/dist/abort.d.mts.map +0 -1
- package/dist/abort.mjs +0 -61
- package/dist/abort.mjs.map +0 -1
- package/dist/errors-BVZjP1Q5.d.mts.map +0 -1
- package/dist/errors.mjs.map +0 -1
- package/dist/exponential-backoff.d.mts +0 -55
- package/dist/exponential-backoff.d.mts.map +0 -1
- package/dist/exponential-backoff.mjs +0 -60
- package/dist/exponential-backoff.mjs.map +0 -1
- package/dist/fixed-delay.d.mts +0 -46
- package/dist/fixed-delay.d.mts.map +0 -1
- package/dist/fixed-delay.mjs +0 -53
- package/dist/fixed-delay.mjs.map +0 -1
- package/dist/index.d.mts +0 -59
- package/dist/index.d.mts.map +0 -1
- package/dist/index.mjs +0 -63
- package/dist/index.mjs.map +0 -1
- package/dist/result-mode.d.mts +0 -19
- package/dist/result-mode.d.mts.map +0 -1
- package/dist/result-mode.mjs +0 -145
- package/dist/result-mode.mjs.map +0 -1
- package/dist/sleep.d.mts +0 -17
- package/dist/sleep.d.mts.map +0 -1
- package/dist/sleep.mjs +0 -23
- package/dist/sleep.mjs.map +0 -1
- package/dist/throw-mode.d.mts +0 -21
- package/dist/throw-mode.d.mts.map +0 -1
- package/dist/throw-mode.mjs +0 -49
- package/dist/throw-mode.mjs.map +0 -1
- package/dist/types.d.mts.map +0 -1
- package/dist/types.mjs +0 -1
package/CHANGELOG.md
CHANGED
|
@@ -1,19 +1,63 @@
|
|
|
1
|
-
|
|
1
|
+
# Changelog
|
|
2
2
|
|
|
3
|
-
|
|
3
|
+
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
|
+
|
|
7
|
+
## [1.0.0]
|
|
8
|
+
|
|
9
|
+
### Added
|
|
6
10
|
|
|
7
|
-
|
|
11
|
+
`RetryPolicy` gains an optional `isKnownError?: (error: unknown) => error is TError` hook that `BaseRetryPolicy.run(...)` now calls before handing a caught value to `next(...)`/`onExhausted(...)`. `BaseRetryPolicy.isKnownError` default checks `error instanceof Error`.
|
|
8
12
|
|
|
9
|
-
|
|
13
|
+
- Override `isKnownError` when `TError` is a narrower subclass (an HTTP error, a domain-specific error) to get real narrowing instead of an `instanceof Error` assumption, and to stop unrelated `Error` types from being retried as if they belonged to your domain.
|
|
10
14
|
|
|
11
|
-
|
|
15
|
+
See [Narrow the Error Domain](https://www.zapstudio.dev/retry/custom-policies#narrow-the-error-domain) for the override pattern.
|
|
12
16
|
|
|
13
|
-
-
|
|
17
|
+
New built-in policy: `linearBackoff(options)` adds a fixed `incrementMs` to the delay after each failed attempt, capped at `maxDelayMs` — steadier growth than `exponentialBackoff`, more spacing than `fixedDelay`. See [linearBackoff](https://www.zapstudio.dev/retry/linear-backoff).
|
|
14
18
|
|
|
15
19
|
### Changed
|
|
16
20
|
|
|
21
|
+
- **Breaking:** `TError` is now constrained to `TError extends Error` and defaults to `Error` (was `TError = unknown`) on `RetryPolicy`, `RetryDecisionInput`, `RetryExhaustedInput`, and `BaseRetryPolicy`.
|
|
22
|
+
- **Breaking behavior change:** a rejection with a non-`Error` value (a thrown string, plain object, `undefined`, ...) now bypasses retry entirely on the attempt that produced it — it no longer reaches `next(...)`, and no delay/backoff is applied. Previously any thrown value was passed through to the policy unchanged. In throw mode, `run(...)` rethrows the value as-is; with `throwOnExhausted: false`, it's wrapped in a `RetryError` and returned on `result.error` — `run(...)` never throws in that mode.
|
|
23
|
+
- **Breaking:** Policies are plain objects instead of classes, for tree-shaking — bundlers can drop an unused policy factory and its defaults entirely, which isn't possible across a shared class hierarchy. `ExponentialBackoff`/`FixedDelay` classes are replaced by `exponentialBackoff(options)`/`fixedDelay(options)` factory functions that return a `RetryPolicy`. Migrate `new ExponentialBackoff(opts)` to `exponentialBackoff(opts)`, and `new FixedDelay(opts)` to `fixedDelay(opts)`.
|
|
24
|
+
- **Breaking:** `RetryPolicy.onExhausted` is now optional (previously required); omit it to use the same default `RetryError` that `BaseRetryPolicy.onExhausted` used to build.
|
|
25
|
+
|
|
26
|
+
### Removed
|
|
27
|
+
|
|
28
|
+
- **Breaking:** `BaseRetryPolicy` is removed. Retry orchestration is now the standalone function `runRetryPolicy(policy, execute, options?)`, which accepts any object satisfying `RetryPolicy` — no subclassing required. Migrate `policy.run(execute, options)` to `runRetryPolicy(policy, execute, options)`. A custom policy that previously extended `BaseRetryPolicy` and overrode `next`/`onExhausted`/`isKnownError` becomes an object literal implementing the same members; see [Custom Policies](https://www.zapstudio.dev/retry/custom-policies).
|
|
29
|
+
|
|
30
|
+
### Removed
|
|
31
|
+
|
|
32
|
+
Collapsed abort/sleep orchestration internals out of the public API.
|
|
33
|
+
|
|
34
|
+
- Removed the `./abort` and `./sleep` subpath exports.
|
|
35
|
+
- Removed the public `sleepWithAbortSignal`, `throwIfAborted`, and `toAbortError` exports — they were orchestration internals with no consumer outside the retry loop, not standalone utilities.
|
|
36
|
+
- `defaultSleep` is unaffected and still exported from `@zap-studio/retry` (no dedicated subpath).
|
|
37
|
+
|
|
38
|
+
## [0.3.2]
|
|
39
|
+
|
|
40
|
+
### Added
|
|
41
|
+
|
|
42
|
+
The package root now re-exports the full public API, so everything can be imported from `@zap-studio/retry` directly (`BaseRetryPolicy`, `ExponentialBackoff`, `FixedDelay`, `RetryError`, `AbortError`, abort helpers, `defaultSleep`, and all public types). All exports are side-effect free and tree-shakeable; granular subpath imports keep working.
|
|
43
|
+
|
|
44
|
+
- `BaseRetryPolicy` moved from the entrypoint into its own module, available as the new `./base-policy` subpath.
|
|
45
|
+
|
|
46
|
+
### Removed
|
|
47
|
+
|
|
48
|
+
- Removed the `./result-mode` and `./throw-mode` subpath exports. Both were orchestration internals (`runResultMode`, `runThrowMode`) and are no longer part of the public API.
|
|
49
|
+
|
|
50
|
+
## [0.3.1]
|
|
51
|
+
|
|
52
|
+
### Changed
|
|
53
|
+
|
|
54
|
+
Internal formatting and lint cleanup only. No public API or behavior change.
|
|
55
|
+
|
|
56
|
+
## [0.3.0]
|
|
57
|
+
|
|
58
|
+
### Changed
|
|
59
|
+
|
|
60
|
+
- **Breaking:** Subpath for error types: use `@zap-studio/retry/errors` (plural) for `RetryError`, `AbortError`, and related types. A prior JSR `error` subpath that pointed at a non-existent `error.ts` entry is removed; update deep imports from `@zap-studio/retry/error` to `@zap-studio/retry/errors`.
|
|
17
61
|
- Add dedicated `AbortError` and normalize cancellation paths so retry internals throw/return `RetryError` or `AbortError` instead of plain `Error`.
|
|
18
62
|
- Expose `defaultSleep` from the `@zap-studio/retry/sleep` subpath only (the main entry does not re-export it; `run` still uses it internally when `sleep` is omitted).
|
|
19
63
|
- Align non-throw exhaustion metadata so `result.attempts` and `result.error.attempts` stay consistent for `RetryError` outcomes.
|
|
@@ -24,7 +68,7 @@ Internal formatting and lint cleanup only. No public API or behavior change.
|
|
|
24
68
|
- Add exhaustive TSDoc for `result-mode` and other `src` modules, including private helpers, policy option and state fields, and `RetryRunResult` union members.
|
|
25
69
|
- Rework test layout into `sleep`, `throw-mode`, `result-mode`, and `index` test files with a shared `sequence-policy` fixture, replacing the prior combined `index` and `abort` test files.
|
|
26
70
|
|
|
27
|
-
## 0.2.0
|
|
71
|
+
## [0.2.0]
|
|
28
72
|
|
|
29
73
|
### Changed
|
|
30
74
|
|
|
@@ -34,13 +78,13 @@ Internal formatting and lint cleanup only. No public API or behavior change.
|
|
|
34
78
|
- Add abort-focused ecosystem benchmarks comparing signal overhead and immediate cancellation behavior.
|
|
35
79
|
- Expand TSDoc coverage for new runner internals added in this release.
|
|
36
80
|
|
|
37
|
-
## 0.1.2
|
|
81
|
+
## [0.1.2]
|
|
38
82
|
|
|
39
83
|
### Changed
|
|
40
84
|
|
|
41
85
|
- Expand TSDoc coverage across retry modules and exported contracts for stronger JSR documentation completeness.
|
|
42
86
|
|
|
43
|
-
## 0.1.1
|
|
87
|
+
## [0.1.1]
|
|
44
88
|
|
|
45
89
|
### Fixed
|
|
46
90
|
|
|
@@ -50,7 +94,7 @@ Internal formatting and lint cleanup only. No public API or behavior change.
|
|
|
50
94
|
|
|
51
95
|
- e9903c5: Removed redundant `| undefined` unions from public retry option and decision types.
|
|
52
96
|
|
|
53
|
-
## 0.1.0
|
|
97
|
+
## [0.1.0]
|
|
54
98
|
|
|
55
99
|
### Added
|
|
56
100
|
|
|
@@ -62,7 +106,4 @@ Internal formatting and lint cleanup only. No public API or behavior change.
|
|
|
62
106
|
- Added `ExponentialBackoff` policy with bounded exponential delay via `baseDelayMs`, `maxDelayMs`, and `maxAttempts`.
|
|
63
107
|
- Added `FixedDelay` policy with constant delay and bounded attempts.
|
|
64
108
|
- Added `RetryError` for exhausted-retry failures with structured attempt/error/data context.
|
|
65
|
-
|
|
66
|
-
### Documentation
|
|
67
|
-
|
|
68
109
|
- Documented throwable behavior on `RetryPolicy`, `BaseRetryPolicy.run`, and related contracts with explicit `@throws` tags for policy, exhaustion, and custom `sleep` failures.
|
package/README.md
CHANGED
|
@@ -2,26 +2,37 @@
|
|
|
2
2
|
|
|
3
3
|
Composable retry policy primitives for HTTP clients and async workflows.
|
|
4
4
|
|
|
5
|
+
Full documentation: [zapstudio.dev/retry](https://www.zapstudio.dev/retry)
|
|
6
|
+
|
|
5
7
|
## Installation
|
|
6
8
|
|
|
7
9
|
```bash
|
|
8
10
|
npm install @zap-studio/retry
|
|
9
11
|
```
|
|
10
12
|
|
|
11
|
-
##
|
|
13
|
+
## Features
|
|
14
|
+
|
|
15
|
+
- **Built-in policies**: `fixedDelay(...)`, `linearBackoff(...)`, and `exponentialBackoff(...)`.
|
|
16
|
+
- **A shared runner** via `runRetryPolicy(policy, execute, options?)` with attempt-aware callbacks and custom sleep injection.
|
|
17
|
+
- **Structured terminal errors**: `RetryError` on exhaustion, `AbortError` on cancellation.
|
|
18
|
+
- **Non-throw mode** (`throwOnExhausted: false`) returns a `RetryRunResult` instead of throwing.
|
|
19
|
+
- **Cancellation** through `AbortSignal`, checked before, between, and during retries.
|
|
20
|
+
- **Custom policies** as plain objects implementing `RetryPolicy` — just a `next(...)` function, no subclassing.
|
|
21
|
+
- **Tree-shakeable** — policies are functions returning plain objects, not classes; unused policies are dropped by any modern bundler.
|
|
22
|
+
|
|
23
|
+
## Quick Start
|
|
12
24
|
|
|
13
25
|
```ts
|
|
14
|
-
import {
|
|
15
|
-
import { FixedDelay } from "@zap-studio/retry/fixed-delay";
|
|
26
|
+
import { exponentialBackoff, runRetryPolicy } from "@zap-studio/retry";
|
|
16
27
|
import { $fetch } from "@zap-studio/fetch";
|
|
17
28
|
|
|
18
|
-
const
|
|
29
|
+
const policy = exponentialBackoff({
|
|
19
30
|
maxAttempts: 5,
|
|
20
31
|
baseDelayMs: 100,
|
|
21
32
|
maxDelayMs: 2_000,
|
|
22
33
|
});
|
|
23
34
|
|
|
24
|
-
const data = await
|
|
35
|
+
const data = await runRetryPolicy(policy, async () => {
|
|
25
36
|
const response = await $fetch("https://api.example.com/users", {
|
|
26
37
|
throwOnFetchError: true,
|
|
27
38
|
});
|
|
@@ -29,189 +40,124 @@ const data = await exponential.run(async () => {
|
|
|
29
40
|
});
|
|
30
41
|
```
|
|
31
42
|
|
|
32
|
-
##
|
|
33
|
-
|
|
34
|
-
`run(...)` throws when retries are exhausted.
|
|
43
|
+
## Built-in Policies
|
|
35
44
|
|
|
36
|
-
|
|
45
|
+
`fixedDelay(...)`, `linearBackoff(...)`, and `exponentialBackoff(...)`.
|
|
37
46
|
|
|
38
47
|
```ts
|
|
39
|
-
import {
|
|
48
|
+
import {
|
|
49
|
+
exponentialBackoff,
|
|
50
|
+
fixedDelay,
|
|
51
|
+
linearBackoff,
|
|
52
|
+
} from "@zap-studio/retry";
|
|
40
53
|
|
|
41
|
-
|
|
42
|
-
|
|
43
|
-
|
|
44
|
-
|
|
45
|
-
|
|
46
|
-
|
|
47
|
-
|
|
48
|
-
|
|
49
|
-
|
|
50
|
-
|
|
51
|
-
|
|
52
|
-
|
|
53
|
-
} else if (error instanceof AbortError) {
|
|
54
|
-
console.error("Retry aborted:", error.message);
|
|
55
|
-
} else {
|
|
56
|
-
throw error;
|
|
57
|
-
}
|
|
58
|
-
}
|
|
54
|
+
const exponential = exponentialBackoff({
|
|
55
|
+
maxAttempts: 5,
|
|
56
|
+
baseDelayMs: 100,
|
|
57
|
+
maxDelayMs: 2_000,
|
|
58
|
+
});
|
|
59
|
+
const linear = linearBackoff({
|
|
60
|
+
maxAttempts: 5,
|
|
61
|
+
baseDelayMs: 100,
|
|
62
|
+
incrementMs: 100,
|
|
63
|
+
maxDelayMs: 2_000,
|
|
64
|
+
});
|
|
65
|
+
const fixed = fixedDelay({ maxAttempts: 4, delayMs: 300 });
|
|
59
66
|
```
|
|
60
67
|
|
|
61
|
-
|
|
68
|
+
## Shared Runner
|
|
62
69
|
|
|
63
|
-
|
|
64
|
-
const result = await exponential.run(
|
|
65
|
-
async () => {
|
|
66
|
-
const response = await $fetch("https://api.example.com/users", {
|
|
67
|
-
throwOnFetchError: true,
|
|
68
|
-
});
|
|
69
|
-
return await response.json();
|
|
70
|
-
},
|
|
71
|
-
{ throwOnExhausted: false }
|
|
72
|
-
);
|
|
70
|
+
Via `runRetryPolicy(policy, execute, options?)` with attempt-aware callbacks and custom sleep injection.
|
|
73
71
|
|
|
74
|
-
|
|
75
|
-
|
|
76
|
-
|
|
77
|
-
}
|
|
78
|
-
console.log(result.value);
|
|
79
|
-
}
|
|
72
|
+
```ts
|
|
73
|
+
await runRetryPolicy(policy, execute, {
|
|
74
|
+
sleep: (delayMs) => customSleep(delayMs),
|
|
75
|
+
});
|
|
80
76
|
```
|
|
81
77
|
|
|
82
|
-
##
|
|
78
|
+
## Structured Terminal Errors
|
|
83
79
|
|
|
84
|
-
`
|
|
85
|
-
|
|
86
|
-
That default is the `defaultSleep` helper, exported from `@zap-studio/retry/sleep`.
|
|
87
|
-
|
|
88
|
-
By default, this delay mechanism relies on the native JavaScript `setTimeout`, meaning retries are scheduled using the standard event loop timing rather than any custom or blocking implementation.
|
|
89
|
-
|
|
90
|
-
## Cancellation With AbortSignal
|
|
91
|
-
|
|
92
|
-
Use `signal` in `run(...)` options to stop retrying early.
|
|
80
|
+
`RetryError` on exhaustion, `AbortError` on cancellation.
|
|
93
81
|
|
|
94
82
|
```ts
|
|
95
|
-
|
|
96
|
-
|
|
97
|
-
const promise = exponential.run(
|
|
98
|
-
async () => {
|
|
99
|
-
const response = await $fetch("https://api.example.com/users", {
|
|
100
|
-
throwOnFetchError: true,
|
|
101
|
-
});
|
|
102
|
-
return await response.json();
|
|
103
|
-
},
|
|
104
|
-
{ signal: controller.signal }
|
|
105
|
-
);
|
|
83
|
+
import { AbortError, RetryError, runRetryPolicy } from "@zap-studio/retry";
|
|
106
84
|
|
|
107
|
-
|
|
108
|
-
|
|
109
|
-
|
|
85
|
+
try {
|
|
86
|
+
await runRetryPolicy(policy, execute);
|
|
87
|
+
} catch (error) {
|
|
88
|
+
if (error instanceof RetryError)
|
|
89
|
+
console.error(error.attempts, error.lastError);
|
|
90
|
+
if (error instanceof AbortError) console.error(error.message);
|
|
91
|
+
}
|
|
110
92
|
```
|
|
111
93
|
|
|
112
|
-
|
|
94
|
+
## Non-throw Mode
|
|
113
95
|
|
|
114
|
-
|
|
115
|
-
const controller = new AbortController();
|
|
96
|
+
`throwOnExhausted: false` returns a `RetryRunResult` instead of throwing.
|
|
116
97
|
|
|
117
|
-
|
|
118
|
-
|
|
119
|
-
|
|
120
|
-
|
|
121
|
-
});
|
|
122
|
-
return await response.json();
|
|
123
|
-
},
|
|
124
|
-
{
|
|
125
|
-
signal: controller.signal,
|
|
126
|
-
throwOnExhausted: false,
|
|
127
|
-
}
|
|
128
|
-
);
|
|
98
|
+
```ts
|
|
99
|
+
const result = await runRetryPolicy(policy, execute, {
|
|
100
|
+
throwOnExhausted: false,
|
|
101
|
+
});
|
|
129
102
|
|
|
130
103
|
if (!result.ok) {
|
|
131
|
-
console.error(
|
|
104
|
+
console.error(result.error);
|
|
105
|
+
} else {
|
|
106
|
+
console.log(result.value);
|
|
132
107
|
}
|
|
133
108
|
```
|
|
134
109
|
|
|
135
|
-
##
|
|
110
|
+
## Cancellation
|
|
136
111
|
|
|
137
|
-
|
|
112
|
+
Through `AbortSignal`, checked before, between, and during retries.
|
|
138
113
|
|
|
139
114
|
```ts
|
|
140
|
-
const
|
|
141
|
-
maxAttempts: 6,
|
|
142
|
-
baseDelayMs: 100,
|
|
143
|
-
maxDelayMs: 2_000,
|
|
144
|
-
});
|
|
145
|
-
```
|
|
115
|
+
const controller = new AbortController();
|
|
146
116
|
|
|
147
|
-
|
|
117
|
+
const promise = runRetryPolicy(policy, execute, { signal: controller.signal });
|
|
148
118
|
|
|
149
|
-
|
|
150
|
-
const predictableIntervalPolicy = new FixedDelay({
|
|
151
|
-
maxAttempts: 4,
|
|
152
|
-
delayMs: 300,
|
|
153
|
-
});
|
|
119
|
+
controller.abort(new Error("Request canceled"));
|
|
154
120
|
```
|
|
155
121
|
|
|
156
122
|
## Custom Policies
|
|
157
123
|
|
|
158
|
-
|
|
159
|
-
|
|
160
|
-
You implement `next(...)` only; the base class supplies `onExhausted` with a default `RetryError` and keeps the shared `run(...)` orchestration (override `onExhausted` when you need a different terminal error).
|
|
124
|
+
As plain objects implementing `RetryPolicy` — just a `next(...)` function, no subclassing.
|
|
161
125
|
|
|
162
126
|
```ts
|
|
163
|
-
import {
|
|
127
|
+
import { runRetryPolicy } from "@zap-studio/retry";
|
|
164
128
|
import type {
|
|
165
129
|
RetryDecision,
|
|
166
130
|
RetryDecisionInput,
|
|
167
|
-
|
|
168
|
-
|
|
169
|
-
class LinearBackoff extends BaseRetryPolicy {
|
|
170
|
-
constructor(
|
|
171
|
-
private readonly maxAttempts: number,
|
|
172
|
-
private readonly stepMs: number
|
|
173
|
-
) {
|
|
174
|
-
super();
|
|
175
|
-
}
|
|
176
|
-
|
|
177
|
-
public next(input: RetryDecisionInput): RetryDecision {
|
|
178
|
-
if (input.attempt >= this.maxAttempts) {
|
|
179
|
-
return {
|
|
180
|
-
shouldRetry: false,
|
|
181
|
-
delayMs: 0,
|
|
182
|
-
reason: "max-attempts-reached",
|
|
183
|
-
};
|
|
184
|
-
}
|
|
131
|
+
RetryPolicy,
|
|
132
|
+
} from "@zap-studio/retry";
|
|
185
133
|
|
|
134
|
+
const stepDelay = (maxAttempts: number, stepMs: number): RetryPolicy => ({
|
|
135
|
+
next(input: RetryDecisionInput): RetryDecision {
|
|
136
|
+
if (input.attempt >= maxAttempts) {
|
|
137
|
+
return { shouldRetry: false, delayMs: 0, reason: "max-attempts-reached" };
|
|
138
|
+
}
|
|
186
139
|
return {
|
|
187
140
|
shouldRetry: true,
|
|
188
|
-
delayMs: input.attempt *
|
|
141
|
+
delayMs: input.attempt * stepMs,
|
|
189
142
|
reason: "retry",
|
|
190
143
|
};
|
|
191
|
-
}
|
|
192
|
-
}
|
|
144
|
+
},
|
|
145
|
+
});
|
|
193
146
|
|
|
194
|
-
const
|
|
195
|
-
const value = await policy.run(doWork);
|
|
147
|
+
const data = await runRetryPolicy(stepDelay(5, 100), execute);
|
|
196
148
|
```
|
|
197
149
|
|
|
198
|
-
##
|
|
199
|
-
|
|
200
|
-
Use `RetryError` when an orchestrator exhausts retries and needs to surface final context.
|
|
201
|
-
|
|
202
|
-
```ts
|
|
203
|
-
import { RetryError } from "@zap-studio/retry/errors";
|
|
204
|
-
|
|
205
|
-
throw new RetryError("Retry policy exhausted all attempts.", {
|
|
206
|
-
attempts: attempt,
|
|
207
|
-
lastError: error,
|
|
208
|
-
lastData: data,
|
|
209
|
-
});
|
|
210
|
-
```
|
|
150
|
+
## Runtime Support
|
|
211
151
|
|
|
212
|
-
|
|
152
|
+
| Runtime | Minimum version |
|
|
153
|
+
| ------------------ | --------------------------------------- |
|
|
154
|
+
| Node.js | 18.0.0 |
|
|
155
|
+
| Bun | 1.0.0 |
|
|
156
|
+
| Deno | 1.42 |
|
|
157
|
+
| Cloudflare Workers | Any current release |
|
|
158
|
+
| Browsers | Chrome/Edge 98, Firefox 97, Safari 15.4 |
|
|
213
159
|
|
|
214
|
-
|
|
160
|
+
Cancellation relies on `AbortSignal.reason`, which sets the browser minimums above. Deno 1.42 is the first release that can install packages from JSR (`deno add jsr:@zap-studio/retry`).
|
|
215
161
|
|
|
216
162
|
## License
|
|
217
163
|
|
|
@@ -0,0 +1,67 @@
|
|
|
1
|
+
import { RetryPolicy, RetryRunOptions, RetryRunResult } from "./types.js";
|
|
2
|
+
//#region src/base-policy.d.ts
|
|
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
|
+
declare const defaultSleep: (delayMs: number) => Promise<void>;
|
|
17
|
+
/**
|
|
18
|
+
* Runs retry orchestration in non-throw mode.
|
|
19
|
+
*
|
|
20
|
+
* @param policy - Retry policy: `next` is required, `onExhausted` and
|
|
21
|
+
* `isKnownError` fall back to their defaults when omitted.
|
|
22
|
+
* @param execute - Async function to execute per attempt.
|
|
23
|
+
* @param options - Runner settings with `throwOnExhausted: false`.
|
|
24
|
+
* @returns A discriminated result union containing success value or terminal error.
|
|
25
|
+
* When `policy.isKnownError` rejects a caught value, it is wrapped in a
|
|
26
|
+
* `RetryError` and returned as the terminal failure instead of thrown.
|
|
27
|
+
* @throws {Error} Any error thrown by `next`, `onExhausted`, or a custom `sleep`.
|
|
28
|
+
*/
|
|
29
|
+
declare function runRetryPolicy<T, TError extends Error = Error, TData = unknown>(policy: RetryPolicy<TError, TData>, execute: (attempt: number) => Promise<T>, options: RetryRunOptions & {
|
|
30
|
+
throwOnExhausted: false;
|
|
31
|
+
}): Promise<RetryRunResult<T>>;
|
|
32
|
+
/**
|
|
33
|
+
* Runs retry orchestration and throws terminal error on exhaustion.
|
|
34
|
+
*
|
|
35
|
+
* @param policy - Retry policy: `next` is required, `onExhausted` and
|
|
36
|
+
* `isKnownError` fall back to their defaults when omitted.
|
|
37
|
+
* @param execute - Async function to execute per attempt.
|
|
38
|
+
* @param options - Optional runner settings.
|
|
39
|
+
* @returns The successful execution value.
|
|
40
|
+
* @throws {RetryError} When retries are exhausted and `onExhausted` returns the
|
|
41
|
+
* terminal retry error. The default implementation returns `RetryError` with the last
|
|
42
|
+
* execution failure available on `RetryError.lastError`.
|
|
43
|
+
* @throws {AbortError} When `options.signal` is already aborted or aborts while retrying.
|
|
44
|
+
* @throws {Error} Any error thrown by `next`, by `onExhausted`, or by a custom `sleep`
|
|
45
|
+
* function.
|
|
46
|
+
*
|
|
47
|
+
* @example
|
|
48
|
+
* ```ts
|
|
49
|
+
* import { runRetryPolicy } from "@zap-studio/retry";
|
|
50
|
+
* import type { RetryPolicy } from "@zap-studio/retry";
|
|
51
|
+
*
|
|
52
|
+
* const linearBackoff: RetryPolicy = {
|
|
53
|
+
* next: ({ attempt }) =>
|
|
54
|
+
* attempt < 3
|
|
55
|
+
* ? { shouldRetry: true, delayMs: attempt * 100, reason: "retry" }
|
|
56
|
+
* : { shouldRetry: false, delayMs: 0, reason: "max-attempts-reached" },
|
|
57
|
+
* };
|
|
58
|
+
*
|
|
59
|
+
* const data = await runRetryPolicy(linearBackoff, async () => fetchFlakyResource());
|
|
60
|
+
* ```
|
|
61
|
+
*/
|
|
62
|
+
declare function runRetryPolicy<T, TError extends Error = Error, TData = unknown>(policy: RetryPolicy<TError, TData>, execute: (attempt: number) => Promise<T>, options?: RetryRunOptions & {
|
|
63
|
+
throwOnExhausted?: true;
|
|
64
|
+
}): Promise<T>;
|
|
65
|
+
//#endregion
|
|
66
|
+
export { defaultSleep, runRetryPolicy };
|
|
67
|
+
//# sourceMappingURL=base-policy.d.ts.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"base-policy.d.ts","names":[],"sources":["../src/base-policy.ts"],"mappings":";;;;;;;;;;;;;;;cA4Ba,eAAsB,oBAAkB;;;;;;;;;;;;;iBA8YrC,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"}
|