@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.
Files changed (44) hide show
  1. package/CHANGELOG.md +58 -18
  2. package/LICENSE +1 -1
  3. package/README.md +99 -151
  4. package/dist/base-policy.d.ts +61 -53
  5. package/dist/base-policy.d.ts.map +1 -1
  6. package/dist/base-policy.js +306 -2
  7. package/dist/base-policy.js.map +1 -0
  8. package/dist/{errors-BVZjP1Q5.d.ts → errors-CS5UPJWs.d.ts} +25 -1
  9. package/dist/errors-CS5UPJWs.d.ts.map +1 -0
  10. package/dist/errors.d.ts +1 -1
  11. package/dist/errors.js +18 -0
  12. package/dist/errors.js.map +1 -1
  13. package/dist/exponential-backoff.d.ts +20 -28
  14. package/dist/exponential-backoff.d.ts.map +1 -1
  15. package/dist/exponential-backoff.js +12 -30
  16. package/dist/exponential-backoff.js.map +1 -1
  17. package/dist/fixed-delay.d.ts +9 -24
  18. package/dist/fixed-delay.d.ts.map +1 -1
  19. package/dist/fixed-delay.js +10 -30
  20. package/dist/fixed-delay.js.map +1 -1
  21. package/dist/index.d.ts +7 -7
  22. package/dist/index.js +6 -6
  23. package/dist/jitter.d.ts +48 -0
  24. package/dist/jitter.d.ts.map +1 -0
  25. package/dist/jitter.js +24 -0
  26. package/dist/jitter.js.map +1 -0
  27. package/dist/linear-backoff.d.ts +53 -0
  28. package/dist/linear-backoff.d.ts.map +1 -0
  29. package/dist/linear-backoff.js +42 -0
  30. package/dist/linear-backoff.js.map +1 -0
  31. package/dist/types.d.ts +51 -7
  32. package/dist/types.d.ts.map +1 -1
  33. package/package.json +10 -9
  34. package/dist/abort.d.ts +0 -29
  35. package/dist/abort.d.ts.map +0 -1
  36. package/dist/abort.js +0 -61
  37. package/dist/abort.js.map +0 -1
  38. package/dist/base-policy-Dn3TOJd3.js +0 -248
  39. package/dist/base-policy-Dn3TOJd3.js.map +0 -1
  40. package/dist/errors-BVZjP1Q5.d.ts.map +0 -1
  41. package/dist/sleep.d.ts +0 -17
  42. package/dist/sleep.d.ts.map +0 -1
  43. package/dist/sleep.js +0 -23
  44. package/dist/sleep.js.map +0 -1
package/CHANGELOG.md CHANGED
@@ -1,28 +1,71 @@
1
- ## @zap-studio/retry@0.3.2
1
+ # Changelog
2
2
 
3
- ### Tree-shakeable root re-exports
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
- ### Migrate to ultracite lint/format
54
+ ### Removed
13
55
 
14
- Internal formatting and lint cleanup only. No public API or behavior change.
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
- # @zap-studio/retry
58
+ ## [0.3.1]
17
59
 
18
- ## 0.3.0
60
+ ### Changed
19
61
 
20
- ### Breaking
62
+ Internal formatting and lint cleanup only. No public API or behavior change.
21
63
 
22
- - **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`.
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
@@ -1,6 +1,6 @@
1
1
  MIT License
2
2
 
3
- Copyright (c) 2026 alexandretrotel
3
+ Copyright (c) 2026 Alexandre Trotel
4
4
 
5
5
  Permission is hereby granted, free of charge, to any person obtaining a copy
6
6
  of this software and associated documentation files (the "Software"), to deal
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
- ## Runtime Support
13
+ ## Features
16
14
 
17
- | Runtime | Minimum version |
18
- | ------------------ | --------------------------------------- |
19
- | Node.js | 18.0.0 |
20
- | Bun | 1.0.0 |
21
- | Deno | 1.42 |
22
- | Cloudflare Workers | Any current release |
23
- | Browsers | Chrome/Edge 98, Firefox 97, Safari 15.4 |
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
- ## Usage
24
+ ## Quick Start
28
25
 
29
26
  ```ts
30
- import { ExponentialBackoff, FixedDelay } from "@zap-studio/retry";
27
+ import { exponentialBackoff, runRetryPolicy } from "@zap-studio/retry";
31
28
  import { $fetch } from "@zap-studio/fetch";
32
29
 
33
- const exponential = new ExponentialBackoff({
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 exponential.run(async () => {
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
- ## Handling Errors
44
+ ## Built-in Policies
48
45
 
49
- `run(...)` throws when retries are exhausted.
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 { AbortError, RetryError } from "@zap-studio/retry";
49
+ import {
50
+ exponentialBackoff,
51
+ fixedDelay,
52
+ linearBackoff,
53
+ } from "@zap-studio/retry";
55
54
 
56
- try {
57
- const data = await exponential.run(async () => {
58
- const response = await $fetch("https://api.example.com/users", {
59
- throwOnFetchError: true,
60
- });
61
- return await response.json();
62
- });
63
- console.log(data);
64
- } catch (error) {
65
- if (error instanceof RetryError) {
66
- console.error("Retries exhausted:", error.attempts);
67
- console.error("Last error:", error.lastError);
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
- To handle exhaustion without throwing, pass `throwOnExhausted: false`:
69
+ ## Jitter
77
70
 
78
- ```ts
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
- if (!result.ok) {
90
- console.error("Retries exhausted:", result.attempts);
91
- console.error("Last error:", result.error.lastError);
92
- } else {
93
- console.log(result.value);
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
- ## Default sleep
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
- That default is the `defaultSleep` helper, exported from `@zap-studio/retry`.
84
+ Via `runRetryPolicy(policy, execute, options?)` with attempt-aware callbacks and custom sleep injection.
102
85
 
103
- 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.
86
+ ```ts
87
+ await runRetryPolicy(policy, execute, {
88
+ sleep: (delayMs) => customSleep(delayMs),
89
+ });
90
+ ```
104
91
 
105
- ## Cancellation With AbortSignal
92
+ ## Structured Terminal Errors
106
93
 
107
- Use `signal` in `run(...)` options to stop retrying early.
94
+ `RetryError` on exhaustion, `AbortError` on cancellation.
108
95
 
109
96
  ```ts
110
- const controller = new AbortController();
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
- await promise;
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
- In non-throw mode, abort is returned as `{ ok: false }` with `AbortError` on `result.error`:
108
+ ## Non-throw Mode
128
109
 
129
- ```ts
130
- const controller = new AbortController();
110
+ `throwOnExhausted: false` returns a `RetryRunResult` instead of throwing.
131
111
 
132
- const result = await exponential.run(
133
- async () => {
134
- const response = await $fetch("https://api.example.com/users", {
135
- throwOnFetchError: true,
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("Retry stopped:", result.error);
118
+ console.error(result.error);
119
+ } else {
120
+ console.log(result.value);
147
121
  }
148
122
  ```
149
123
 
150
- ## Choosing The Right Policy
124
+ ## Cancellation
151
125
 
152
- Use `ExponentialBackoff` for transient network instability and shared upstream services.
126
+ Through `AbortSignal`, checked before, between, and during retries.
153
127
 
154
128
  ```ts
155
- const unstableNetworkPolicy = new ExponentialBackoff({
156
- maxAttempts: 6,
157
- baseDelayMs: 100,
158
- maxDelayMs: 2_000,
159
- });
160
- ```
129
+ const controller = new AbortController();
161
130
 
162
- Use `FixedDelay` for stable, predictable retry intervals in controlled environments.
131
+ const promise = runRetryPolicy(policy, execute, { signal: controller.signal });
163
132
 
164
- ```ts
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
- Extend `BaseRetryPolicy` when the built-in policies do not match your retry rules.
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 { BaseRetryPolicy } from "@zap-studio/retry";
179
- import type { RetryDecision, RetryDecisionInput } from "@zap-studio/retry";
180
-
181
- class LinearBackoff extends BaseRetryPolicy {
182
- constructor(
183
- private readonly maxAttempts: number,
184
- private readonly stepMs: number
185
- ) {
186
- super();
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 * this.stepMs,
155
+ delayMs: input.attempt * stepMs,
201
156
  reason: "retry",
202
157
  };
203
- }
204
- }
158
+ },
159
+ });
205
160
 
206
- const policy = new LinearBackoff(5, 250);
207
- const value = await policy.run(doWork);
161
+ const data = await runRetryPolicy(stepDelay(5, 100), execute);
208
162
  ```
209
163
 
210
- ## RetryError
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
- Policies implement `onExhausted(input)` to return the terminal error used by the built-in runner.
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
- `ExponentialBackoff` and `FixedDelay` inherit the default implementation from `BaseRetryPolicy`.
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
 
@@ -1,59 +1,67 @@
1
- import { r as RetryError } from "./errors-BVZjP1Q5.js";
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
- * Base class for implementing retry policies and running retry orchestration.
4
+ * Awaits a timer-based delay, unless `delayMs` is non-positive.
6
5
  *
7
- * Extend this class and implement {@link BaseRetryPolicy.next} to define retry
8
- * behavior, then call {@link BaseRetryPolicy.run} to execute operations with that
9
- * policy.
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 abstract class BaseRetryPolicy<TError = unknown, TData = unknown> implements RetryPolicy<TError, TData> {
12
- /**
13
- * Returns the retry decision for a failed attempt.
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 { BaseRetryPolicy };
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":";;;;;;;;;;uBA0BsB,gBACpB,kBACA,4BACW,YAAY,QAAQ;;;;;;;WAOf,KAAK,OAAO,mBAAmB,QAAQ,SAAS;;;;;;;;;;EAYhE,YAAmB,OAAO,oBAAoB,QAAQ,SAAS;;;;;;;;;EAgB/D,IAAiB,GACf,UAAU,oBAAoB,QAAQ,IACtC,SAAS;IAAoB;MAC5B,QAAQ,eAAe;;;;;;;;;;;;;;EAe1B,IAAiB,GACf,UAAU,oBAAoB,QAAQ,IACtC,UAAU;IAAoB;MAC7B,QAAQ"}
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"}