@zap-studio/retry 0.3.2 → 1.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 +58 -18
- package/LICENSE +1 -1
- package/README.md +99 -151
- package/dist/base-policy.d.ts +61 -53
- package/dist/base-policy.d.ts.map +1 -1
- package/dist/base-policy.js +306 -2
- package/dist/base-policy.js.map +1 -0
- package/dist/{errors-BVZjP1Q5.d.ts → errors-CS5UPJWs.d.ts} +25 -1
- package/dist/errors-CS5UPJWs.d.ts.map +1 -0
- package/dist/errors.d.ts +1 -1
- package/dist/errors.js +18 -0
- package/dist/errors.js.map +1 -1
- package/dist/exponential-backoff.d.ts +20 -28
- package/dist/exponential-backoff.d.ts.map +1 -1
- package/dist/exponential-backoff.js +12 -30
- package/dist/exponential-backoff.js.map +1 -1
- package/dist/fixed-delay.d.ts +9 -24
- package/dist/fixed-delay.d.ts.map +1 -1
- package/dist/fixed-delay.js +10 -30
- package/dist/fixed-delay.js.map +1 -1
- package/dist/index.d.ts +7 -7
- package/dist/index.js +6 -6
- package/dist/jitter.d.ts +48 -0
- package/dist/jitter.d.ts.map +1 -0
- package/dist/jitter.js +24 -0
- package/dist/jitter.js.map +1 -0
- package/dist/linear-backoff.d.ts +53 -0
- package/dist/linear-backoff.d.ts.map +1 -0
- package/dist/linear-backoff.js +42 -0
- package/dist/linear-backoff.js.map +1 -0
- package/dist/types.d.ts +51 -7
- package/dist/types.d.ts.map +1 -1
- package/package.json +10 -9
- package/dist/abort.d.ts +0 -29
- package/dist/abort.d.ts.map +0 -1
- package/dist/abort.js +0 -61
- package/dist/abort.js.map +0 -1
- package/dist/base-policy-Dn3TOJd3.js +0 -248
- package/dist/base-policy-Dn3TOJd3.js.map +0 -1
- package/dist/errors-BVZjP1Q5.d.ts.map +0 -1
- package/dist/sleep.d.ts +0 -17
- package/dist/sleep.d.ts.map +0 -1
- package/dist/sleep.js +0 -23
- package/dist/sleep.js.map +0 -1
package/CHANGELOG.md
CHANGED
|
@@ -1,28 +1,71 @@
|
|
|
1
|
-
|
|
1
|
+
# Changelog
|
|
2
2
|
|
|
3
|
-
|
|
3
|
+
All notable changes to this project will be documented in this file.
|
|
4
|
+
|
|
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.1.0]
|
|
8
|
+
|
|
9
|
+
### Added
|
|
10
|
+
|
|
11
|
+
`exponentialBackoff(...)` and `linearBackoff(...)` gain an optional `jitter?: "full" | "equal" | JitterOptions` option, applied to the computed delay after it's capped at `maxDelayMs`. `"full"` randomizes across `[0, delayMs]`; `"equal"` keeps a floor at half the delay, randomizing across `[delayMs/2, delayMs]`. Pass `{ mode, random }` to override the random source, useful for deterministic tests.
|
|
12
|
+
|
|
13
|
+
New export: `applyJitter(delayMs, jitter?)`, also available from the `./jitter` subpath. See [Jitter](https://www.zapstudio.dev/retry/jitter).
|
|
14
|
+
|
|
15
|
+
## [1.0.0]
|
|
16
|
+
|
|
17
|
+
### Added
|
|
18
|
+
|
|
19
|
+
`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`.
|
|
20
|
+
|
|
21
|
+
- 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.
|
|
22
|
+
|
|
23
|
+
See [Narrow the Error Domain](https://www.zapstudio.dev/retry/custom-policies#narrow-the-error-domain) for the override pattern.
|
|
24
|
+
|
|
25
|
+
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).
|
|
26
|
+
|
|
27
|
+
### Changed
|
|
28
|
+
|
|
29
|
+
- **Breaking:** `TError` is now constrained to `TError extends Error` and defaults to `Error` (was `TError = unknown`) on `RetryPolicy`, `RetryDecisionInput`, `RetryExhaustedInput`, and `BaseRetryPolicy`.
|
|
30
|
+
- **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.
|
|
31
|
+
- **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)`.
|
|
32
|
+
- **Breaking:** `RetryPolicy.onExhausted` is now optional (previously required); omit it to use the same default `RetryError` that `BaseRetryPolicy.onExhausted` used to build.
|
|
33
|
+
|
|
34
|
+
### Removed
|
|
35
|
+
|
|
36
|
+
- **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).
|
|
37
|
+
|
|
38
|
+
### Removed
|
|
39
|
+
|
|
40
|
+
Collapsed abort/sleep orchestration internals out of the public API.
|
|
41
|
+
|
|
42
|
+
- Removed the `./abort` and `./sleep` subpath exports.
|
|
43
|
+
- Removed the public `sleepWithAbortSignal`, `throwIfAborted`, and `toAbortError` exports — they were orchestration internals with no consumer outside the retry loop, not standalone utilities.
|
|
44
|
+
- `defaultSleep` is unaffected and still exported from `@zap-studio/retry` (no dedicated subpath).
|
|
45
|
+
|
|
46
|
+
## [0.3.2]
|
|
47
|
+
|
|
48
|
+
### Added
|
|
4
49
|
|
|
5
50
|
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.
|
|
6
51
|
|
|
7
52
|
- `BaseRetryPolicy` moved from the entrypoint into its own module, available as the new `./base-policy` subpath.
|
|
8
|
-
- Removed the `./result-mode` and `./throw-mode` subpath exports. Both were orchestration internals (`runResultMode`, `runThrowMode`) and are no longer part of the public API.
|
|
9
|
-
|
|
10
|
-
## @zap-studio/retry@0.3.1
|
|
11
53
|
|
|
12
|
-
###
|
|
54
|
+
### Removed
|
|
13
55
|
|
|
14
|
-
|
|
56
|
+
- Removed the `./result-mode` and `./throw-mode` subpath exports. Both were orchestration internals (`runResultMode`, `runThrowMode`) and are no longer part of the public API.
|
|
15
57
|
|
|
16
|
-
|
|
58
|
+
## [0.3.1]
|
|
17
59
|
|
|
18
|
-
|
|
60
|
+
### Changed
|
|
19
61
|
|
|
20
|
-
|
|
62
|
+
Internal formatting and lint cleanup only. No public API or behavior change.
|
|
21
63
|
|
|
22
|
-
|
|
64
|
+
## [0.3.0]
|
|
23
65
|
|
|
24
66
|
### Changed
|
|
25
67
|
|
|
68
|
+
- **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`.
|
|
26
69
|
- Add dedicated `AbortError` and normalize cancellation paths so retry internals throw/return `RetryError` or `AbortError` instead of plain `Error`.
|
|
27
70
|
- 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).
|
|
28
71
|
- Align non-throw exhaustion metadata so `result.attempts` and `result.error.attempts` stay consistent for `RetryError` outcomes.
|
|
@@ -33,7 +76,7 @@ Internal formatting and lint cleanup only. No public API or behavior change.
|
|
|
33
76
|
- Add exhaustive TSDoc for `result-mode` and other `src` modules, including private helpers, policy option and state fields, and `RetryRunResult` union members.
|
|
34
77
|
- 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.
|
|
35
78
|
|
|
36
|
-
## 0.2.0
|
|
79
|
+
## [0.2.0]
|
|
37
80
|
|
|
38
81
|
### Changed
|
|
39
82
|
|
|
@@ -43,13 +86,13 @@ Internal formatting and lint cleanup only. No public API or behavior change.
|
|
|
43
86
|
- Add abort-focused ecosystem benchmarks comparing signal overhead and immediate cancellation behavior.
|
|
44
87
|
- Expand TSDoc coverage for new runner internals added in this release.
|
|
45
88
|
|
|
46
|
-
## 0.1.2
|
|
89
|
+
## [0.1.2]
|
|
47
90
|
|
|
48
91
|
### Changed
|
|
49
92
|
|
|
50
93
|
- Expand TSDoc coverage across retry modules and exported contracts for stronger JSR documentation completeness.
|
|
51
94
|
|
|
52
|
-
## 0.1.1
|
|
95
|
+
## [0.1.1]
|
|
53
96
|
|
|
54
97
|
### Fixed
|
|
55
98
|
|
|
@@ -59,7 +102,7 @@ Internal formatting and lint cleanup only. No public API or behavior change.
|
|
|
59
102
|
|
|
60
103
|
- e9903c5: Removed redundant `| undefined` unions from public retry option and decision types.
|
|
61
104
|
|
|
62
|
-
## 0.1.0
|
|
105
|
+
## [0.1.0]
|
|
63
106
|
|
|
64
107
|
### Added
|
|
65
108
|
|
|
@@ -71,7 +114,4 @@ Internal formatting and lint cleanup only. No public API or behavior change.
|
|
|
71
114
|
- Added `ExponentialBackoff` policy with bounded exponential delay via `baseDelayMs`, `maxDelayMs`, and `maxAttempts`.
|
|
72
115
|
- Added `FixedDelay` policy with constant delay and bounded attempts.
|
|
73
116
|
- Added `RetryError` for exhausted-retry failures with structured attempt/error/data context.
|
|
74
|
-
|
|
75
|
-
### Documentation
|
|
76
|
-
|
|
77
117
|
- Documented throwable behavior on `RetryPolicy`, `BaseRetryPolicy.run`, and related contracts with explicit `@throws` tags for policy, exhaustion, and custom `sleep` failures.
|
package/LICENSE
CHANGED
package/README.md
CHANGED
|
@@ -2,41 +2,38 @@
|
|
|
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
|
-
nub add @zap-studio/retry
|
|
9
|
-
# or
|
|
10
10
|
npm install @zap-studio/retry
|
|
11
|
-
# or
|
|
12
|
-
pnpm add @zap-studio/retry
|
|
13
11
|
```
|
|
14
12
|
|
|
15
|
-
##
|
|
13
|
+
## Features
|
|
16
14
|
|
|
17
|
-
|
|
18
|
-
|
|
19
|
-
|
|
20
|
-
|
|
21
|
-
|
|
22
|
-
|
|
23
|
-
|
|
24
|
-
|
|
25
|
-
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`).
|
|
15
|
+
- **Built-in policies**: `fixedDelay(...)`, `linearBackoff(...)`, and `exponentialBackoff(...)`.
|
|
16
|
+
- **Jitter**: `"full"` or `"equal"` jitter on `exponentialBackoff`/`linearBackoff`, to avoid synchronized retries against a shared upstream.
|
|
17
|
+
- **A shared runner** via `runRetryPolicy(policy, execute, options?)` with attempt-aware callbacks and custom sleep injection.
|
|
18
|
+
- **Structured terminal errors**: `RetryError` on exhaustion, `AbortError` on cancellation.
|
|
19
|
+
- **Non-throw mode** (`throwOnExhausted: false`) returns a `RetryRunResult` instead of throwing.
|
|
20
|
+
- **Cancellation** through `AbortSignal`, checked before, between, and during retries.
|
|
21
|
+
- **Custom policies** as plain objects implementing `RetryPolicy` — just a `next(...)` function, no subclassing.
|
|
22
|
+
- **Tree-shakeable** — policies are functions returning plain objects, not classes; unused policies are dropped by any modern bundler.
|
|
26
23
|
|
|
27
|
-
##
|
|
24
|
+
## Quick Start
|
|
28
25
|
|
|
29
26
|
```ts
|
|
30
|
-
import {
|
|
27
|
+
import { exponentialBackoff, runRetryPolicy } from "@zap-studio/retry";
|
|
31
28
|
import { $fetch } from "@zap-studio/fetch";
|
|
32
29
|
|
|
33
|
-
const
|
|
30
|
+
const policy = exponentialBackoff({
|
|
34
31
|
maxAttempts: 5,
|
|
35
32
|
baseDelayMs: 100,
|
|
36
33
|
maxDelayMs: 2_000,
|
|
37
34
|
});
|
|
38
35
|
|
|
39
|
-
const data = await
|
|
36
|
+
const data = await runRetryPolicy(policy, async () => {
|
|
40
37
|
const response = await $fetch("https://api.example.com/users", {
|
|
41
38
|
throwOnFetchError: true,
|
|
42
39
|
});
|
|
@@ -44,186 +41,137 @@ const data = await exponential.run(async () => {
|
|
|
44
41
|
});
|
|
45
42
|
```
|
|
46
43
|
|
|
47
|
-
##
|
|
44
|
+
## Built-in Policies
|
|
48
45
|
|
|
49
|
-
`
|
|
50
|
-
|
|
51
|
-
By default, policies extending `BaseRetryPolicy` throw `RetryError` on exhaustion and `AbortError` on cancellation.
|
|
46
|
+
`fixedDelay(...)`, `linearBackoff(...)`, and `exponentialBackoff(...)`.
|
|
52
47
|
|
|
53
48
|
```ts
|
|
54
|
-
import {
|
|
49
|
+
import {
|
|
50
|
+
exponentialBackoff,
|
|
51
|
+
fixedDelay,
|
|
52
|
+
linearBackoff,
|
|
53
|
+
} from "@zap-studio/retry";
|
|
55
54
|
|
|
56
|
-
|
|
57
|
-
|
|
58
|
-
|
|
59
|
-
|
|
60
|
-
|
|
61
|
-
|
|
62
|
-
|
|
63
|
-
|
|
64
|
-
|
|
65
|
-
|
|
66
|
-
|
|
67
|
-
|
|
68
|
-
} else if (error instanceof AbortError) {
|
|
69
|
-
console.error("Retry aborted:", error.message);
|
|
70
|
-
} else {
|
|
71
|
-
throw error;
|
|
72
|
-
}
|
|
73
|
-
}
|
|
55
|
+
const exponential = exponentialBackoff({
|
|
56
|
+
maxAttempts: 5,
|
|
57
|
+
baseDelayMs: 100,
|
|
58
|
+
maxDelayMs: 2_000,
|
|
59
|
+
});
|
|
60
|
+
const linear = linearBackoff({
|
|
61
|
+
maxAttempts: 5,
|
|
62
|
+
baseDelayMs: 100,
|
|
63
|
+
incrementMs: 100,
|
|
64
|
+
maxDelayMs: 2_000,
|
|
65
|
+
});
|
|
66
|
+
const fixed = fixedDelay({ maxAttempts: 4, delayMs: 300 });
|
|
74
67
|
```
|
|
75
68
|
|
|
76
|
-
|
|
69
|
+
## Jitter
|
|
77
70
|
|
|
78
|
-
|
|
79
|
-
const result = await exponential.run(
|
|
80
|
-
async () => {
|
|
81
|
-
const response = await $fetch("https://api.example.com/users", {
|
|
82
|
-
throwOnFetchError: true,
|
|
83
|
-
});
|
|
84
|
-
return await response.json();
|
|
85
|
-
},
|
|
86
|
-
{ throwOnExhausted: false }
|
|
87
|
-
);
|
|
71
|
+
`"full"` or `"equal"` jitter on `exponentialBackoff`/`linearBackoff`, applied to the delay after it's capped, to avoid synchronized retries against a shared upstream.
|
|
88
72
|
|
|
89
|
-
|
|
90
|
-
|
|
91
|
-
|
|
92
|
-
|
|
93
|
-
|
|
94
|
-
|
|
73
|
+
```ts
|
|
74
|
+
const policy = exponentialBackoff({
|
|
75
|
+
maxAttempts: 5,
|
|
76
|
+
baseDelayMs: 100,
|
|
77
|
+
maxDelayMs: 2_000,
|
|
78
|
+
jitter: "full",
|
|
79
|
+
});
|
|
95
80
|
```
|
|
96
81
|
|
|
97
|
-
##
|
|
98
|
-
|
|
99
|
-
`BaseRetryPolicy.run` automatically applies a delay between retry attempts when no custom `sleep` function is provided in the options.
|
|
82
|
+
## Shared Runner
|
|
100
83
|
|
|
101
|
-
|
|
84
|
+
Via `runRetryPolicy(policy, execute, options?)` with attempt-aware callbacks and custom sleep injection.
|
|
102
85
|
|
|
103
|
-
|
|
86
|
+
```ts
|
|
87
|
+
await runRetryPolicy(policy, execute, {
|
|
88
|
+
sleep: (delayMs) => customSleep(delayMs),
|
|
89
|
+
});
|
|
90
|
+
```
|
|
104
91
|
|
|
105
|
-
##
|
|
92
|
+
## Structured Terminal Errors
|
|
106
93
|
|
|
107
|
-
|
|
94
|
+
`RetryError` on exhaustion, `AbortError` on cancellation.
|
|
108
95
|
|
|
109
96
|
```ts
|
|
110
|
-
|
|
111
|
-
|
|
112
|
-
const promise = exponential.run(
|
|
113
|
-
async () => {
|
|
114
|
-
const response = await $fetch("https://api.example.com/users", {
|
|
115
|
-
throwOnFetchError: true,
|
|
116
|
-
});
|
|
117
|
-
return await response.json();
|
|
118
|
-
},
|
|
119
|
-
{ signal: controller.signal }
|
|
120
|
-
);
|
|
121
|
-
|
|
122
|
-
controller.abort(new Error("Request canceled"));
|
|
97
|
+
import { AbortError, RetryError, runRetryPolicy } from "@zap-studio/retry";
|
|
123
98
|
|
|
124
|
-
|
|
99
|
+
try {
|
|
100
|
+
await runRetryPolicy(policy, execute);
|
|
101
|
+
} catch (error) {
|
|
102
|
+
if (error instanceof RetryError)
|
|
103
|
+
console.error(error.attempts, error.lastError);
|
|
104
|
+
if (error instanceof AbortError) console.error(error.message);
|
|
105
|
+
}
|
|
125
106
|
```
|
|
126
107
|
|
|
127
|
-
|
|
108
|
+
## Non-throw Mode
|
|
128
109
|
|
|
129
|
-
|
|
130
|
-
const controller = new AbortController();
|
|
110
|
+
`throwOnExhausted: false` returns a `RetryRunResult` instead of throwing.
|
|
131
111
|
|
|
132
|
-
|
|
133
|
-
|
|
134
|
-
|
|
135
|
-
|
|
136
|
-
});
|
|
137
|
-
return await response.json();
|
|
138
|
-
},
|
|
139
|
-
{
|
|
140
|
-
signal: controller.signal,
|
|
141
|
-
throwOnExhausted: false,
|
|
142
|
-
}
|
|
143
|
-
);
|
|
112
|
+
```ts
|
|
113
|
+
const result = await runRetryPolicy(policy, execute, {
|
|
114
|
+
throwOnExhausted: false,
|
|
115
|
+
});
|
|
144
116
|
|
|
145
117
|
if (!result.ok) {
|
|
146
|
-
console.error(
|
|
118
|
+
console.error(result.error);
|
|
119
|
+
} else {
|
|
120
|
+
console.log(result.value);
|
|
147
121
|
}
|
|
148
122
|
```
|
|
149
123
|
|
|
150
|
-
##
|
|
124
|
+
## Cancellation
|
|
151
125
|
|
|
152
|
-
|
|
126
|
+
Through `AbortSignal`, checked before, between, and during retries.
|
|
153
127
|
|
|
154
128
|
```ts
|
|
155
|
-
const
|
|
156
|
-
maxAttempts: 6,
|
|
157
|
-
baseDelayMs: 100,
|
|
158
|
-
maxDelayMs: 2_000,
|
|
159
|
-
});
|
|
160
|
-
```
|
|
129
|
+
const controller = new AbortController();
|
|
161
130
|
|
|
162
|
-
|
|
131
|
+
const promise = runRetryPolicy(policy, execute, { signal: controller.signal });
|
|
163
132
|
|
|
164
|
-
|
|
165
|
-
const predictableIntervalPolicy = new FixedDelay({
|
|
166
|
-
maxAttempts: 4,
|
|
167
|
-
delayMs: 300,
|
|
168
|
-
});
|
|
133
|
+
controller.abort(new Error("Request canceled"));
|
|
169
134
|
```
|
|
170
135
|
|
|
171
136
|
## Custom Policies
|
|
172
137
|
|
|
173
|
-
|
|
174
|
-
|
|
175
|
-
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).
|
|
138
|
+
As plain objects implementing `RetryPolicy` — just a `next(...)` function, no subclassing.
|
|
176
139
|
|
|
177
140
|
```ts
|
|
178
|
-
import {
|
|
179
|
-
import type {
|
|
180
|
-
|
|
181
|
-
|
|
182
|
-
|
|
183
|
-
|
|
184
|
-
|
|
185
|
-
|
|
186
|
-
|
|
187
|
-
|
|
188
|
-
|
|
189
|
-
public next(input: RetryDecisionInput): RetryDecision {
|
|
190
|
-
if (input.attempt >= this.maxAttempts) {
|
|
191
|
-
return {
|
|
192
|
-
shouldRetry: false,
|
|
193
|
-
delayMs: 0,
|
|
194
|
-
reason: "max-attempts-reached",
|
|
195
|
-
};
|
|
141
|
+
import { runRetryPolicy } from "@zap-studio/retry";
|
|
142
|
+
import type {
|
|
143
|
+
RetryDecision,
|
|
144
|
+
RetryDecisionInput,
|
|
145
|
+
RetryPolicy,
|
|
146
|
+
} from "@zap-studio/retry";
|
|
147
|
+
|
|
148
|
+
const stepDelay = (maxAttempts: number, stepMs: number): RetryPolicy => ({
|
|
149
|
+
next(input: RetryDecisionInput): RetryDecision {
|
|
150
|
+
if (input.attempt >= maxAttempts) {
|
|
151
|
+
return { shouldRetry: false, delayMs: 0, reason: "max-attempts-reached" };
|
|
196
152
|
}
|
|
197
|
-
|
|
198
153
|
return {
|
|
199
154
|
shouldRetry: true,
|
|
200
|
-
delayMs: input.attempt *
|
|
155
|
+
delayMs: input.attempt * stepMs,
|
|
201
156
|
reason: "retry",
|
|
202
157
|
};
|
|
203
|
-
}
|
|
204
|
-
}
|
|
158
|
+
},
|
|
159
|
+
});
|
|
205
160
|
|
|
206
|
-
const
|
|
207
|
-
const value = await policy.run(doWork);
|
|
161
|
+
const data = await runRetryPolicy(stepDelay(5, 100), execute);
|
|
208
162
|
```
|
|
209
163
|
|
|
210
|
-
##
|
|
211
|
-
|
|
212
|
-
Use `RetryError` when an orchestrator exhausts retries and needs to surface final context.
|
|
213
|
-
|
|
214
|
-
```ts
|
|
215
|
-
import { RetryError } from "@zap-studio/retry";
|
|
216
|
-
|
|
217
|
-
throw new RetryError("Retry policy exhausted all attempts.", {
|
|
218
|
-
attempts: attempt,
|
|
219
|
-
lastError: error,
|
|
220
|
-
lastData: data,
|
|
221
|
-
});
|
|
222
|
-
```
|
|
164
|
+
## Runtime Support
|
|
223
165
|
|
|
224
|
-
|
|
166
|
+
| Runtime | Minimum version |
|
|
167
|
+
| ------------------ | --------------------------------------- |
|
|
168
|
+
| Node.js | 18.0.0 |
|
|
169
|
+
| Bun | 1.0.0 |
|
|
170
|
+
| Deno | 1.42 |
|
|
171
|
+
| Cloudflare Workers | Any current release |
|
|
172
|
+
| Browsers | Chrome/Edge 98, Firefox 97, Safari 15.4 |
|
|
225
173
|
|
|
226
|
-
|
|
174
|
+
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`).
|
|
227
175
|
|
|
228
176
|
## License
|
|
229
177
|
|
package/dist/base-policy.d.ts
CHANGED
|
@@ -1,59 +1,67 @@
|
|
|
1
|
-
import {
|
|
2
|
-
import { RetryDecision, RetryDecisionInput, RetryExhaustedInput, RetryPolicy, RetryRunOptions, RetryRunResult } from "./types.js";
|
|
1
|
+
import { RetryPolicy, RetryRunOptions, RetryRunResult } from "./types.js";
|
|
3
2
|
//#region src/base-policy.d.ts
|
|
4
3
|
/**
|
|
5
|
-
*
|
|
4
|
+
* Awaits a timer-based delay, unless `delayMs` is non-positive.
|
|
6
5
|
*
|
|
7
|
-
*
|
|
8
|
-
*
|
|
9
|
-
*
|
|
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
|
+
* ```
|
|
10
61
|
*/
|
|
11
|
-
declare
|
|
12
|
-
|
|
13
|
-
|
|
14
|
-
*
|
|
15
|
-
* @param input - Attempt context used to compute retry behavior.
|
|
16
|
-
* @throws {Error} Any error thrown by a concrete retry policy implementation.
|
|
17
|
-
*/
|
|
18
|
-
abstract next(input: RetryDecisionInput<TError, TData>): RetryDecision;
|
|
19
|
-
/**
|
|
20
|
-
* Builds the terminal error thrown or returned when retries are exhausted.
|
|
21
|
-
*
|
|
22
|
-
* Override this when you need custom terminal error types.
|
|
23
|
-
*
|
|
24
|
-
* @param input - Exhaustion context.
|
|
25
|
-
* @returns `RetryError` by default.
|
|
26
|
-
* @throws {Error} Any error thrown by an overriding policy implementation.
|
|
27
|
-
*/
|
|
28
|
-
onExhausted(input: RetryExhaustedInput<TError, TData>): RetryError;
|
|
29
|
-
/**
|
|
30
|
-
* Runs retry orchestration in non-throw mode.
|
|
31
|
-
*
|
|
32
|
-
* @param execute - Async function to execute per attempt.
|
|
33
|
-
* @param options - Runner settings with `throwOnExhausted: false`.
|
|
34
|
-
* @returns A discriminated result union containing success value or terminal error.
|
|
35
|
-
* @throws {Error} Any error thrown by `next`, `onExhausted`, or a custom `sleep`.
|
|
36
|
-
*/
|
|
37
|
-
run<T>(execute: (attempt: number) => Promise<T>, options: RetryRunOptions & {
|
|
38
|
-
throwOnExhausted: false;
|
|
39
|
-
}): Promise<RetryRunResult<T>>;
|
|
40
|
-
/**
|
|
41
|
-
* Runs retry orchestration and throws terminal error on exhaustion.
|
|
42
|
-
*
|
|
43
|
-
* @param execute - Async function to execute per attempt.
|
|
44
|
-
* @param options - Optional runner settings.
|
|
45
|
-
* @returns The successful execution value.
|
|
46
|
-
* @throws {RetryError} When retries are exhausted and `onExhausted` returns the
|
|
47
|
-
* terminal retry error. The default implementation returns `RetryError` with the last
|
|
48
|
-
* execution failure available on `RetryError.lastError`.
|
|
49
|
-
* @throws {AbortError} When `options.signal` is already aborted or aborts while retrying.
|
|
50
|
-
* @throws {Error} Any error thrown by `next`, by `onExhausted`, or by a custom `sleep`
|
|
51
|
-
* function.
|
|
52
|
-
*/
|
|
53
|
-
run<T>(execute: (attempt: number) => Promise<T>, options?: RetryRunOptions & {
|
|
54
|
-
throwOnExhausted?: true;
|
|
55
|
-
}): Promise<T>;
|
|
56
|
-
}
|
|
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>;
|
|
57
65
|
//#endregion
|
|
58
|
-
export {
|
|
66
|
+
export { defaultSleep, runRetryPolicy };
|
|
59
67
|
//# 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":";;;;;;;;;;;;;;;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"}
|