fetch-retrier 0.5.5 → 0.6.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/README.md CHANGED
@@ -1,9 +1,9 @@
1
1
  # Fetch Retrier
2
2
 
3
- [![npm version](https://img.shields.io/npm/v/fetch-retrier.svg)](https://www.npmjs.com/package/fetch-retrier)
4
- [![npm downloads](https://img.shields.io/npm/dm/fetch-retrier.svg)](https://www.npmjs.com/package/fetch-retrier)
5
- [![build](https://github.com/gammarers-labs/fetch-retrier/actions/workflows/build.yml/badge.svg)](https://github.com/gammarers-labs/fetch-retrier/actions/workflows/build.yml)
6
- [![release](https://github.com/gammarers-labs/fetch-retrier/actions/workflows/release.yml/badge.svg)](https://github.com/gammarers-labs/fetch-retrier/actions/workflows/release.yml)
3
+ [![npm version](https://img.shields.io/npm/v/fetch-retrier?style=flat-square)](https://www.npmjs.com/package/fetch-retrier)
4
+ [![license](https://img.shields.io/npm/l/fetch-retrier?style=flat-square)](https://www.npmjs.com/package/fetch-retrier)
5
+ [![Node.js](https://img.shields.io/node/v/fetch-retrier?style=flat-square)](https://www.npmjs.com/package/fetch-retrier)
6
+ [![build](https://img.shields.io/github/actions/workflow/status/gammarers-labs/fetch-retrier/build.yml?branch=main&label=build&style=flat-square)](https://github.com/gammarers-labs/fetch-retrier/actions/workflows/build.yml)
7
7
 
8
8
  A lightweight wrapper around `fetch` that adds **retries**, **per-attempt timeout**, **Retry-After** support, **full jitter** backoff, and **option validation**. Pass standard `RequestInit` options (`method`, `body`, `credentials`, and more) for POST/PUT APIs and other HTTP calls that may be rate-limited or temporarily unavailable.
9
9
 
@@ -12,30 +12,50 @@ A lightweight wrapper around `fetch` that adds **retries**, **per-attempt timeou
12
12
  - **Configurable retries** – Set the maximum number of attempts per request (`retries >= 1`).
13
13
  - **Per-attempt timeout** – Abort each attempt when it exceeds a given duration (`timeoutMs > 0`).
14
14
  - **Retry-After support** – On HTTP retries, prefers a valid `Retry-After` header (delta-seconds or HTTP-date); falls back to full jitter when absent or invalid.
15
- - **Full jitter backoff** – Exponential backoff with random jitter (AWS-style) for abort/network retries and as the HTTP fallback (`baseBackoffMs >= 0`).
15
+ - **Full jitter backoff** – Exponential backoff with random jitter (AWS-style) for abort/network retries and as the HTTP fallback (`baseBackoffMs >= 0`). Optional `maxBackoffMs` clips the jitter result (`>= 0`); it does not clip a valid `Retry-After`.
16
16
  - **Option validation** – Invalid numeric options throw `FetchRetrierInvalidOptionsError` at call time.
17
17
  - **RequestInit forwarding** – Pass `method`, `body`, `credentials`, `redirect`, and other `fetch` options via `init` on every attempt.
18
18
  - **Header shorthand** – Optional top-level `headers` override `init.headers` when both are set.
19
19
  - **Default retry policy** – Retries transient HTTP statuses (408, 425, 429, 500, 502, 503, 504) via `defaultShouldRetry` and `DEFAULT_RETRYABLE_HTTP_STATUSES`.
20
20
  - **Extensible retry predicate** – Compose `defaultShouldRetry` with custom `shouldRetry` logic (receives response body text).
21
21
  - **External cancellation** – Pass an `AbortSignal` to cancel in-flight requests.
22
- - **Typed errors** – `FetchRetrierHttpError` (with `status` and `body`), `FetchRetrierNetworkError`, `FetchRetrierAbortError`, `FetchRetrierInvalidOptionsError`, and related classes.
22
+ - **Typed errors** – All failures extend `FetchRetrierError`. Subclasses include `FetchRetrierHttpError` (with `status` and `body`), `FetchRetrierNetworkError`, `FetchRetrierAbortError`, and `FetchRetrierInvalidOptionsError`.
23
23
  - **TypeScript** – Exported types including `RequestOptions` and `FetchInitOptions`.
24
24
 
25
+ ## How it works
26
+
27
+ Options are validated, then each attempt sends the same URL and `init`/`headers` with an internal timeout. If `response.ok` is true, that response is returned. Otherwise `shouldRetry` decides whether to wait and try again. HTTP retries prefer a valid `Retry-After` header; abort and network retries use full jitter. After the last attempt, a subclass of `FetchRetrierError` is thrown.
28
+
29
+ - **Success** – If `response.ok` is true, the response is returned immediately.
30
+ - **Package errors** – Failures from this package extend `FetchRetrierError`. Catch the base, or a subclass for a specific case.
31
+ - **Invalid options** – If `retries < 1`, `timeoutMs <= 0`, `baseBackoffMs < 0`, or `maxBackoffMs` is set and `< 0`, `FetchRetrierInvalidOptionsError` is thrown before any request is made. This is not a `TypeError`.
32
+ - **Retriable failure** – If the response is not OK and `shouldRetry(response, body)` returns true, the client waits and retries until `retries` is exhausted. Wait prefers a valid `Retry-After` header (delta-seconds or HTTP-date); otherwise uses full jitter. On the last attempt, `FetchRetrierHttpError` is thrown (includes `status` and `body`).
33
+ - **Non-retriable failure** – If `shouldRetry` returns false, `FetchRetrierHttpError` is thrown immediately with `status` and `body` (e.g. `Non-retriable HTTP error: 404`).
34
+ - **Timeout** – If a request exceeds `timeoutMs`, that attempt is aborted and retried with full jitter until `retries` is exhausted. Timeout is per-attempt and does not cancel later attempts. The final failure is `FetchRetrierAbortError`.
35
+ - **External abort (in-flight)** – If `signal` is aborted during an attempt, the in-flight request is aborted. On the last attempt, the failure is `FetchRetrierAbortError`. If retries remain, the next attempt sees the still-aborted signal and throws `FetchRetrierAlreadyAbortedError` (no further request is made).
36
+ - **Network / TypeError** – Network errors are retried with full jitter; after the last attempt, `FetchRetrierNetworkError` is thrown with the original error as `cause`.
37
+ - **Already aborted signal** – If `signal` is already aborted before an attempt starts, `FetchRetrierAlreadyAbortedError` is thrown (no attempt is made).
38
+
25
39
  ## Installation
26
40
 
27
- **npm**
41
+ ### npm
28
42
 
29
43
  ```bash
30
44
  npm install fetch-retrier
31
45
  ```
32
46
 
33
- **yarn**
47
+ ### yarn
34
48
 
35
49
  ```bash
36
50
  yarn add fetch-retrier
37
51
  ```
38
52
 
53
+ ### pnpm
54
+
55
+ ```bash
56
+ pnpm add fetch-retrier
57
+ ```
58
+
39
59
  ## Usage
40
60
 
41
61
  ### GET request
@@ -104,12 +124,21 @@ const response = await fetchRetrier('https://api.example.com/data', {
104
124
  // DEFAULT_RETRYABLE_HTTP_STATUSES is [408, 425, 429, 500, 502, 503, 504]
105
125
  ```
106
126
 
107
- ### Handling HTTP errors
127
+ ### Handling errors
128
+
129
+ All failures thrown by `fetchRetrier` extend `FetchRetrierError`. Catch the base for any library
130
+ failure, or a subclass for a specific case. `FetchRetrierInvalidOptionsError` does not extend
131
+ `TypeError`.
108
132
 
109
- On a non-OK response that is not retried (or after retries are exhausted), `FetchRetrierHttpError` includes both `status` and the already-read `body`:
133
+ On a non-OK response that is not retried (or after retries are exhausted), `FetchRetrierHttpError`
134
+ includes both `status` and the already-read `body`:
110
135
 
111
136
  ```typescript
112
- import { fetchRetrier, FetchRetrierHttpError } from 'fetch-retrier';
137
+ import {
138
+ fetchRetrier,
139
+ FetchRetrierError,
140
+ FetchRetrierHttpError,
141
+ } from 'fetch-retrier';
113
142
 
114
143
  try {
115
144
  await fetchRetrier('https://api.example.com/data', {
@@ -121,45 +150,63 @@ try {
121
150
  if (err instanceof FetchRetrierHttpError) {
122
151
  console.error(err.status, err.body);
123
152
  }
153
+ if (err instanceof FetchRetrierError) {
154
+ // Any failure from this package.
155
+ }
124
156
  throw err;
125
157
  }
126
158
  ```
127
159
 
128
160
  ### Cancellation with `AbortController`
129
161
 
162
+ Timeout abort and external abort are different. Per-attempt timeout retries remaining attempts and
163
+ then throws `FetchRetrierAbortError`. An in-flight external abort cancels the current request;
164
+ the last attempt throws `FetchRetrierAbortError`, while remaining attempts throw
165
+ `FetchRetrierAlreadyAbortedError` because the signal stays aborted.
166
+
167
+ `FetchRetrierAlreadyAbortedError` extends `FetchRetrierAbortError`, so check the subclass first.
168
+
130
169
  ```typescript
170
+ import {
171
+ fetchRetrier,
172
+ FetchRetrierAbortError,
173
+ FetchRetrierAlreadyAbortedError,
174
+ } from 'fetch-retrier';
175
+
131
176
  const controller = new AbortController();
132
177
 
133
178
  setTimeout(() => controller.abort(), 250);
134
179
 
135
- await fetchRetrier('https://api.example.com/data', {
136
- retries: 3,
137
- timeoutMs: 5000,
138
- baseBackoffMs: 250,
139
- signal: controller.signal,
140
- });
180
+ try {
181
+ await fetchRetrier('https://api.example.com/data', {
182
+ retries: 3,
183
+ timeoutMs: 5000,
184
+ baseBackoffMs: 250,
185
+ signal: controller.signal,
186
+ });
187
+ } catch (err) {
188
+ if (err instanceof FetchRetrierAlreadyAbortedError) {
189
+ // Signal stayed aborted, so a later attempt was not started.
190
+ throw err;
191
+ }
192
+ if (err instanceof FetchRetrierAbortError) {
193
+ // Last attempt was cancelled by timeout or an in-flight external abort.
194
+ }
195
+ throw err;
196
+ }
141
197
  ```
142
198
 
143
- ### Retry and error behavior
144
-
145
- - **Success** – If `response.ok` is true, the response is returned immediately.
146
- - **Invalid options** – If `retries < 1`, `timeoutMs <= 0`, or `baseBackoffMs < 0`, `FetchRetrierInvalidOptionsError` is thrown before any request is made.
147
- - **Retriable failure** – If the response is not OK and `shouldRetry(response, body)` returns true, the client waits and retries until `retries` is exhausted. Wait prefers a valid `Retry-After` header (delta-seconds or HTTP-date); otherwise uses full jitter. On the last attempt, `FetchRetrierHttpError` is thrown (includes `status` and `body`).
148
- - **Non-retriable failure** – If `shouldRetry` returns false, `FetchRetrierHttpError` is thrown immediately with `status` and `body` (e.g. `Non-retriable HTTP error: 404`).
149
- - **Timeout** – If a request exceeds `timeoutMs`, it is aborted and retried with full jitter until `retries` is exhausted; the final failure is `FetchRetrierAbortError`.
150
- - **Network / TypeError** – Network errors are retried with full jitter; after the last attempt, `FetchRetrierNetworkError` is thrown with the original error as `cause`.
151
- - **Already aborted signal** – If `signal` is already aborted before an attempt starts, `FetchRetrierAlreadyAbortedError` is thrown (no attempt is made).
152
-
153
199
  ## Options
154
200
 
155
201
  | Option | Type | Required | Description |
156
202
  |--------|------|----------|-------------|
157
203
  | `retries` | `number` | Yes | Maximum number of attempts (including the first request). Must be `>= 1`. |
158
204
  | `timeoutMs` | `number` | Yes | Timeout in milliseconds for each attempt. Exceeded attempts are aborted and retried. Must be `> 0`. |
159
- | `baseBackoffMs` | `number` | Yes | Base delay in milliseconds for full jitter when `Retry-After` is absent or invalid (also used for abort/network retries). Cap is `baseBackoffMs * 2^attempt`, randomized. Must be `>= 0` (`0` skips backoff delay when falling back). |
205
+ | `baseBackoffMs` | `number` | Yes | Base delay in milliseconds for full jitter when `Retry-After` is absent or invalid (also used for abort/network retries). Exponential span is `baseBackoffMs * 2^attempt`, randomized. Must be `>= 0` (`0` skips backoff delay when falling back). |
206
+ | `maxBackoffMs` | `number` | No | Optional ceiling in milliseconds applied to the full-jitter result (`Math.min(jitter, maxBackoffMs)`). Does not clip a valid `Retry-After`. Must be `>= 0` when set (`0` skips jitter wait). Omitted means no extra clip. |
160
207
  | `init` | `FetchInitOptions` | No | `fetch` options forwarded to every attempt: `method`, `body`, `credentials`, `redirect`, `mode`, `cache`, etc. `signal` is reserved for internal timeout and cancellation. |
161
208
  | `headers` | `Record<string, string>` | No | Headers sent on every attempt. Overrides `init.headers` when both are set. |
162
- | `signal` | `AbortSignal` | No | External abort signal. If already aborted, `FetchRetrierAlreadyAbortedError` is thrown. If aborted during an attempt, the request is aborted and retried until `retries` is exhausted. |
209
+ | `signal` | `AbortSignal` | No | External abort signal. If already aborted before an attempt, `FetchRetrierAlreadyAbortedError` is thrown. If aborted during an attempt, the in-flight request is aborted: the last attempt fails with `FetchRetrierAbortError`; remaining attempts fail with `FetchRetrierAlreadyAbortedError`. Distinct from `timeoutMs`, which retries remaining attempts and then throws `FetchRetrierAbortError`. |
163
210
  | `shouldRetry` | `(response: Response, body: string) => boolean` | No | Called after `response.text()` when `response.ok` is false. Return `true` to retry. Default: `defaultShouldRetry` (statuses in `DEFAULT_RETRYABLE_HTTP_STATUSES`: 408, 425, 429, 500, 502, 503, 504). |
164
211
 
165
212
  ### Exported helpers
@@ -0,0 +1,13 @@
1
+ /**
2
+ * Full jitter backoff: random delay in `[0, base * 2^attempt)` ms (AWS-recommended pattern).
3
+ *
4
+ * Used between abort/network retries, and as the fallback when HTTP retries lack a usable
5
+ * `Retry-After` header. When `maxBackoffMs` is set, the computed delay is clipped with
6
+ * `Math.min(delay, maxBackoffMs)`.
7
+ *
8
+ * @param base - Base backoff in milliseconds
9
+ * @param attempt - 1-based attempt index (first retry uses `attempt === 1`)
10
+ * @param maxBackoffMs - Optional ceiling applied to the jitter result
11
+ * @returns Wait duration in milliseconds before the next attempt
12
+ */
13
+ export declare const fullJitter: (base: number, attempt: number, maxBackoffMs?: number) => number;
@@ -0,0 +1,25 @@
1
+ "use strict";
2
+ Object.defineProperty(exports, "__esModule", { value: true });
3
+ exports.fullJitter = void 0;
4
+ /**
5
+ * Full jitter backoff: random delay in `[0, base * 2^attempt)` ms (AWS-recommended pattern).
6
+ *
7
+ * Used between abort/network retries, and as the fallback when HTTP retries lack a usable
8
+ * `Retry-After` header. When `maxBackoffMs` is set, the computed delay is clipped with
9
+ * `Math.min(delay, maxBackoffMs)`.
10
+ *
11
+ * @param base - Base backoff in milliseconds
12
+ * @param attempt - 1-based attempt index (first retry uses `attempt === 1`)
13
+ * @param maxBackoffMs - Optional ceiling applied to the jitter result
14
+ * @returns Wait duration in milliseconds before the next attempt
15
+ */
16
+ const fullJitter = (base, attempt, maxBackoffMs) => {
17
+ const cap = base * Math.pow(2, attempt);
18
+ const delay = Math.floor(Math.random() * cap);
19
+ if (maxBackoffMs === undefined) {
20
+ return delay;
21
+ }
22
+ return Math.min(delay, maxBackoffMs);
23
+ };
24
+ exports.fullJitter = fullJitter;
25
+ //# sourceMappingURL=data:application/json;base64,eyJ2ZXJzaW9uIjozLCJmaWxlIjoiaml0dGVyLmpzIiwic291cmNlUm9vdCI6IiIsInNvdXJjZXMiOlsiLi4vLi4vLi4vc3JjL2NvcmUvYmFja29mZi9qaXR0ZXIudHMiXSwibmFtZXMiOltdLCJtYXBwaW5ncyI6Ijs7O0FBQUE7Ozs7Ozs7Ozs7O0dBV0c7QUFDSSxNQUFNLFVBQVUsR0FBRyxDQUFDLElBQVksRUFBRSxPQUFlLEVBQUUsWUFBcUIsRUFBVSxFQUFFO0lBQ3pGLE1BQU0sR0FBRyxHQUFHLElBQUksR0FBRyxJQUFJLENBQUMsR0FBRyxDQUFDLENBQUMsRUFBRSxPQUFPLENBQUMsQ0FBQztJQUN4QyxNQUFNLEtBQUssR0FBRyxJQUFJLENBQUMsS0FBSyxDQUFDLElBQUksQ0FBQyxNQUFNLEVBQUUsR0FBRyxHQUFHLENBQUMsQ0FBQztJQUM5QyxJQUFJLFlBQVksS0FBSyxTQUFTLEVBQUUsQ0FBQztRQUMvQixPQUFPLEtBQUssQ0FBQztJQUNmLENBQUM7SUFDRCxPQUFPLElBQUksQ0FBQyxHQUFHLENBQUMsS0FBSyxFQUFFLFlBQVksQ0FBQyxDQUFDO0FBQ3ZDLENBQUMsQ0FBQztBQVBXLFFBQUEsVUFBVSxjQU9yQiIsInNvdXJjZXNDb250ZW50IjpbIi8qKlxuICogRnVsbCBqaXR0ZXIgYmFja29mZjogcmFuZG9tIGRlbGF5IGluIGBbMCwgYmFzZSAqIDJeYXR0ZW1wdClgIG1zIChBV1MtcmVjb21tZW5kZWQgcGF0dGVybikuXG4gKlxuICogVXNlZCBiZXR3ZWVuIGFib3J0L25ldHdvcmsgcmV0cmllcywgYW5kIGFzIHRoZSBmYWxsYmFjayB3aGVuIEhUVFAgcmV0cmllcyBsYWNrIGEgdXNhYmxlXG4gKiBgUmV0cnktQWZ0ZXJgIGhlYWRlci4gV2hlbiBgbWF4QmFja29mZk1zYCBpcyBzZXQsIHRoZSBjb21wdXRlZCBkZWxheSBpcyBjbGlwcGVkIHdpdGhcbiAqIGBNYXRoLm1pbihkZWxheSwgbWF4QmFja29mZk1zKWAuXG4gKlxuICogQHBhcmFtIGJhc2UgLSBCYXNlIGJhY2tvZmYgaW4gbWlsbGlzZWNvbmRzXG4gKiBAcGFyYW0gYXR0ZW1wdCAtIDEtYmFzZWQgYXR0ZW1wdCBpbmRleCAoZmlyc3QgcmV0cnkgdXNlcyBgYXR0ZW1wdCA9PT0gMWApXG4gKiBAcGFyYW0gbWF4QmFja29mZk1zIC0gT3B0aW9uYWwgY2VpbGluZyBhcHBsaWVkIHRvIHRoZSBqaXR0ZXIgcmVzdWx0XG4gKiBAcmV0dXJucyBXYWl0IGR1cmF0aW9uIGluIG1pbGxpc2Vjb25kcyBiZWZvcmUgdGhlIG5leHQgYXR0ZW1wdFxuICovXG5leHBvcnQgY29uc3QgZnVsbEppdHRlciA9IChiYXNlOiBudW1iZXIsIGF0dGVtcHQ6IG51bWJlciwgbWF4QmFja29mZk1zPzogbnVtYmVyKTogbnVtYmVyID0+IHtcbiAgY29uc3QgY2FwID0gYmFzZSAqIE1hdGgucG93KDIsIGF0dGVtcHQpO1xuICBjb25zdCBkZWxheSA9IE1hdGguZmxvb3IoTWF0aC5yYW5kb20oKSAqIGNhcCk7XG4gIGlmIChtYXhCYWNrb2ZmTXMgPT09IHVuZGVmaW5lZCkge1xuICAgIHJldHVybiBkZWxheTtcbiAgfVxuICByZXR1cm4gTWF0aC5taW4oZGVsYXksIG1heEJhY2tvZmZNcyk7XG59O1xuIl19
@@ -0,0 +1,27 @@
1
+ /**
2
+ * Parses a `Retry-After` header value into a delay in milliseconds.
3
+ *
4
+ * Supports RFC 7231 forms: non-negative integer delta-seconds, or an HTTP-date. Empty values,
5
+ * non-integer numerics (e.g. floats or negatives), and unparsable dates yield `undefined` so
6
+ * callers can fall back to full jitter. An HTTP-date in the past yields `0`.
7
+ *
8
+ * @param value - Raw `Retry-After` header value
9
+ * @param nowMs - Current time in milliseconds (injectable for tests)
10
+ * @returns Delay in milliseconds, or `undefined` when the value cannot be parsed
11
+ */
12
+ export declare const parseRetryAfterMs: (value: string, nowMs?: number) => number | undefined;
13
+ /**
14
+ * Chooses the delay before the next HTTP retry: prefer a valid `Retry-After`, else full jitter.
15
+ *
16
+ * Reads `response.headers.get('Retry-After')` and parses it with {@link parseRetryAfterMs}.
17
+ * Missing headers, or values that parse to `undefined`, fall back to {@link fullJitter}.
18
+ * Abort and network retries do not use this helper.
19
+ *
20
+ * @param response - Non-OK response from the current attempt
21
+ * @param baseBackoffMs - Base backoff passed to {@link fullJitter} when falling back
22
+ * @param attempt - 1-based attempt index
23
+ * @param maxBackoffMs - Optional ceiling applied only to the jitter fallback
24
+ * @param nowMs - Current time in milliseconds (injectable for tests)
25
+ * @returns Wait duration in milliseconds before the next attempt
26
+ */
27
+ export declare const resolveRetryDelayMs: (response: Response, baseBackoffMs: number, attempt: number, maxBackoffMs?: number, nowMs?: number) => number;
@@ -0,0 +1,61 @@
1
+ "use strict";
2
+ Object.defineProperty(exports, "__esModule", { value: true });
3
+ exports.resolveRetryDelayMs = exports.parseRetryAfterMs = void 0;
4
+ const jitter_1 = require("./jitter");
5
+ /**
6
+ * Parses a `Retry-After` header value into a delay in milliseconds.
7
+ *
8
+ * Supports RFC 7231 forms: non-negative integer delta-seconds, or an HTTP-date. Empty values,
9
+ * non-integer numerics (e.g. floats or negatives), and unparsable dates yield `undefined` so
10
+ * callers can fall back to full jitter. An HTTP-date in the past yields `0`.
11
+ *
12
+ * @param value - Raw `Retry-After` header value
13
+ * @param nowMs - Current time in milliseconds (injectable for tests)
14
+ * @returns Delay in milliseconds, or `undefined` when the value cannot be parsed
15
+ */
16
+ const parseRetryAfterMs = (value, nowMs = Date.now()) => {
17
+ const trimmed = value.trim();
18
+ if (trimmed === '') {
19
+ return undefined;
20
+ }
21
+ if (/^\d+$/.test(trimmed)) {
22
+ return Number.parseInt(trimmed, 10) * 1000;
23
+ }
24
+ // Reject other numeric forms (floats, negatives); not valid delta-seconds or HTTP-date.
25
+ if (/^-?\d+(\.\d+)?$/.test(trimmed)) {
26
+ return undefined;
27
+ }
28
+ const dateMs = Date.parse(trimmed);
29
+ if (Number.isNaN(dateMs)) {
30
+ return undefined;
31
+ }
32
+ return Math.max(0, dateMs - nowMs);
33
+ };
34
+ exports.parseRetryAfterMs = parseRetryAfterMs;
35
+ /**
36
+ * Chooses the delay before the next HTTP retry: prefer a valid `Retry-After`, else full jitter.
37
+ *
38
+ * Reads `response.headers.get('Retry-After')` and parses it with {@link parseRetryAfterMs}.
39
+ * Missing headers, or values that parse to `undefined`, fall back to {@link fullJitter}.
40
+ * Abort and network retries do not use this helper.
41
+ *
42
+ * @param response - Non-OK response from the current attempt
43
+ * @param baseBackoffMs - Base backoff passed to {@link fullJitter} when falling back
44
+ * @param attempt - 1-based attempt index
45
+ * @param maxBackoffMs - Optional ceiling applied only to the jitter fallback
46
+ * @param nowMs - Current time in milliseconds (injectable for tests)
47
+ * @returns Wait duration in milliseconds before the next attempt
48
+ */
49
+ const resolveRetryDelayMs = (response, baseBackoffMs, attempt, maxBackoffMs, nowMs = Date.now()) => {
50
+ const header = response.headers?.get('Retry-After');
51
+ if (header == null) {
52
+ return (0, jitter_1.fullJitter)(baseBackoffMs, attempt, maxBackoffMs);
53
+ }
54
+ const fromHeader = (0, exports.parseRetryAfterMs)(header, nowMs);
55
+ if (fromHeader === undefined) {
56
+ return (0, jitter_1.fullJitter)(baseBackoffMs, attempt, maxBackoffMs);
57
+ }
58
+ return fromHeader;
59
+ };
60
+ exports.resolveRetryDelayMs = resolveRetryDelayMs;
61
+ //# sourceMappingURL=data:application/json;base64,eyJ2ZXJzaW9uIjozLCJmaWxlIjoicmV0cnktYWZ0ZXIuanMiLCJzb3VyY2VSb290IjoiIiwic291cmNlcyI6WyIuLi8uLi8uLi9zcmMvY29yZS9iYWNrb2ZmL3JldHJ5LWFmdGVyLnRzIl0sIm5hbWVzIjpbXSwibWFwcGluZ3MiOiI7OztBQUFBLHFDQUFzQztBQUV0Qzs7Ozs7Ozs7OztHQVVHO0FBQ0ksTUFBTSxpQkFBaUIsR0FBRyxDQUFDLEtBQWEsRUFBRSxRQUFnQixJQUFJLENBQUMsR0FBRyxFQUFFLEVBQXNCLEVBQUU7SUFDakcsTUFBTSxPQUFPLEdBQUcsS0FBSyxDQUFDLElBQUksRUFBRSxDQUFDO0lBQzdCLElBQUksT0FBTyxLQUFLLEVBQUUsRUFBRSxDQUFDO1FBQ25CLE9BQU8sU0FBUyxDQUFDO0lBQ25CLENBQUM7SUFFRCxJQUFJLE9BQU8sQ0FBQyxJQUFJLENBQUMsT0FBTyxDQUFDLEVBQUUsQ0FBQztRQUMxQixPQUFPLE1BQU0sQ0FBQyxRQUFRLENBQUMsT0FBTyxFQUFFLEVBQUUsQ0FBQyxHQUFHLElBQUksQ0FBQztJQUM3QyxDQUFDO0lBRUQsd0ZBQXdGO0lBQ3hGLElBQUksaUJBQWlCLENBQUMsSUFBSSxDQUFDLE9BQU8sQ0FBQyxFQUFFLENBQUM7UUFDcEMsT0FBTyxTQUFTLENBQUM7SUFDbkIsQ0FBQztJQUVELE1BQU0sTUFBTSxHQUFHLElBQUksQ0FBQyxLQUFLLENBQUMsT0FBTyxDQUFDLENBQUM7SUFDbkMsSUFBSSxNQUFNLENBQUMsS0FBSyxDQUFDLE1BQU0sQ0FBQyxFQUFFLENBQUM7UUFDekIsT0FBTyxTQUFTLENBQUM7SUFDbkIsQ0FBQztJQUVELE9BQU8sSUFBSSxDQUFDLEdBQUcsQ0FBQyxDQUFDLEVBQUUsTUFBTSxHQUFHLEtBQUssQ0FBQyxDQUFDO0FBQ3JDLENBQUMsQ0FBQztBQXJCVyxRQUFBLGlCQUFpQixxQkFxQjVCO0FBRUY7Ozs7Ozs7Ozs7Ozs7R0FhRztBQUNJLE1BQU0sbUJBQW1CLEdBQUcsQ0FDakMsUUFBa0IsRUFDbEIsYUFBcUIsRUFDckIsT0FBZSxFQUNmLFlBQXFCLEVBQ3JCLFFBQWdCLElBQUksQ0FBQyxHQUFHLEVBQUUsRUFDbEIsRUFBRTtJQUNWLE1BQU0sTUFBTSxHQUFHLFFBQVEsQ0FBQyxPQUFPLEVBQUUsR0FBRyxDQUFDLGFBQWEsQ0FBQyxDQUFDO0lBQ3BELElBQUksTUFBTSxJQUFJLElBQUksRUFBRSxDQUFDO1FBQ25CLE9BQU8sSUFBQSxtQkFBVSxFQUFDLGFBQWEsRUFBRSxPQUFPLEVBQUUsWUFBWSxDQUFDLENBQUM7SUFDMUQsQ0FBQztJQUVELE1BQU0sVUFBVSxHQUFHLElBQUEseUJBQWlCLEVBQUMsTUFBTSxFQUFFLEtBQUssQ0FBQyxDQUFDO0lBQ3BELElBQUksVUFBVSxLQUFLLFNBQVMsRUFBRSxDQUFDO1FBQzdCLE9BQU8sSUFBQSxtQkFBVSxFQUFDLGFBQWEsRUFBRSxPQUFPLEVBQUUsWUFBWSxDQUFDLENBQUM7SUFDMUQsQ0FBQztJQUVELE9BQU8sVUFBVSxDQUFDO0FBQ3BCLENBQUMsQ0FBQztBQWxCVyxRQUFBLG1CQUFtQix1QkFrQjlCIiwic291cmNlc0NvbnRlbnQiOlsiaW1wb3J0IHsgZnVsbEppdHRlciB9IGZyb20gJy4vaml0dGVyJztcblxuLyoqXG4gKiBQYXJzZXMgYSBgUmV0cnktQWZ0ZXJgIGhlYWRlciB2YWx1ZSBpbnRvIGEgZGVsYXkgaW4gbWlsbGlzZWNvbmRzLlxuICpcbiAqIFN1cHBvcnRzIFJGQyA3MjMxIGZvcm1zOiBub24tbmVnYXRpdmUgaW50ZWdlciBkZWx0YS1zZWNvbmRzLCBvciBhbiBIVFRQLWRhdGUuIEVtcHR5IHZhbHVlcyxcbiAqIG5vbi1pbnRlZ2VyIG51bWVyaWNzIChlLmcuIGZsb2F0cyBvciBuZWdhdGl2ZXMpLCBhbmQgdW5wYXJzYWJsZSBkYXRlcyB5aWVsZCBgdW5kZWZpbmVkYCBzb1xuICogY2FsbGVycyBjYW4gZmFsbCBiYWNrIHRvIGZ1bGwgaml0dGVyLiBBbiBIVFRQLWRhdGUgaW4gdGhlIHBhc3QgeWllbGRzIGAwYC5cbiAqXG4gKiBAcGFyYW0gdmFsdWUgLSBSYXcgYFJldHJ5LUFmdGVyYCBoZWFkZXIgdmFsdWVcbiAqIEBwYXJhbSBub3dNcyAtIEN1cnJlbnQgdGltZSBpbiBtaWxsaXNlY29uZHMgKGluamVjdGFibGUgZm9yIHRlc3RzKVxuICogQHJldHVybnMgRGVsYXkgaW4gbWlsbGlzZWNvbmRzLCBvciBgdW5kZWZpbmVkYCB3aGVuIHRoZSB2YWx1ZSBjYW5ub3QgYmUgcGFyc2VkXG4gKi9cbmV4cG9ydCBjb25zdCBwYXJzZVJldHJ5QWZ0ZXJNcyA9ICh2YWx1ZTogc3RyaW5nLCBub3dNczogbnVtYmVyID0gRGF0ZS5ub3coKSk6IG51bWJlciB8IHVuZGVmaW5lZCA9PiB7XG4gIGNvbnN0IHRyaW1tZWQgPSB2YWx1ZS50cmltKCk7XG4gIGlmICh0cmltbWVkID09PSAnJykge1xuICAgIHJldHVybiB1bmRlZmluZWQ7XG4gIH1cblxuICBpZiAoL15cXGQrJC8udGVzdCh0cmltbWVkKSkge1xuICAgIHJldHVybiBOdW1iZXIucGFyc2VJbnQodHJpbW1lZCwgMTApICogMTAwMDtcbiAgfVxuXG4gIC8vIFJlamVjdCBvdGhlciBudW1lcmljIGZvcm1zIChmbG9hdHMsIG5lZ2F0aXZlcyk7IG5vdCB2YWxpZCBkZWx0YS1zZWNvbmRzIG9yIEhUVFAtZGF0ZS5cbiAgaWYgKC9eLT9cXGQrKFxcLlxcZCspPyQvLnRlc3QodHJpbW1lZCkpIHtcbiAgICByZXR1cm4gdW5kZWZpbmVkO1xuICB9XG5cbiAgY29uc3QgZGF0ZU1zID0gRGF0ZS5wYXJzZSh0cmltbWVkKTtcbiAgaWYgKE51bWJlci5pc05hTihkYXRlTXMpKSB7XG4gICAgcmV0dXJuIHVuZGVmaW5lZDtcbiAgfVxuXG4gIHJldHVybiBNYXRoLm1heCgwLCBkYXRlTXMgLSBub3dNcyk7XG59O1xuXG4vKipcbiAqIENob29zZXMgdGhlIGRlbGF5IGJlZm9yZSB0aGUgbmV4dCBIVFRQIHJldHJ5OiBwcmVmZXIgYSB2YWxpZCBgUmV0cnktQWZ0ZXJgLCBlbHNlIGZ1bGwgaml0dGVyLlxuICpcbiAqIFJlYWRzIGByZXNwb25zZS5oZWFkZXJzLmdldCgnUmV0cnktQWZ0ZXInKWAgYW5kIHBhcnNlcyBpdCB3aXRoIHtAbGluayBwYXJzZVJldHJ5QWZ0ZXJNc30uXG4gKiBNaXNzaW5nIGhlYWRlcnMsIG9yIHZhbHVlcyB0aGF0IHBhcnNlIHRvIGB1bmRlZmluZWRgLCBmYWxsIGJhY2sgdG8ge0BsaW5rIGZ1bGxKaXR0ZXJ9LlxuICogQWJvcnQgYW5kIG5ldHdvcmsgcmV0cmllcyBkbyBub3QgdXNlIHRoaXMgaGVscGVyLlxuICpcbiAqIEBwYXJhbSByZXNwb25zZSAtIE5vbi1PSyByZXNwb25zZSBmcm9tIHRoZSBjdXJyZW50IGF0dGVtcHRcbiAqIEBwYXJhbSBiYXNlQmFja29mZk1zIC0gQmFzZSBiYWNrb2ZmIHBhc3NlZCB0byB7QGxpbmsgZnVsbEppdHRlcn0gd2hlbiBmYWxsaW5nIGJhY2tcbiAqIEBwYXJhbSBhdHRlbXB0IC0gMS1iYXNlZCBhdHRlbXB0IGluZGV4XG4gKiBAcGFyYW0gbWF4QmFja29mZk1zIC0gT3B0aW9uYWwgY2VpbGluZyBhcHBsaWVkIG9ubHkgdG8gdGhlIGppdHRlciBmYWxsYmFja1xuICogQHBhcmFtIG5vd01zIC0gQ3VycmVudCB0aW1lIGluIG1pbGxpc2Vjb25kcyAoaW5qZWN0YWJsZSBmb3IgdGVzdHMpXG4gKiBAcmV0dXJucyBXYWl0IGR1cmF0aW9uIGluIG1pbGxpc2Vjb25kcyBiZWZvcmUgdGhlIG5leHQgYXR0ZW1wdFxuICovXG5leHBvcnQgY29uc3QgcmVzb2x2ZVJldHJ5RGVsYXlNcyA9IChcbiAgcmVzcG9uc2U6IFJlc3BvbnNlLFxuICBiYXNlQmFja29mZk1zOiBudW1iZXIsXG4gIGF0dGVtcHQ6IG51bWJlcixcbiAgbWF4QmFja29mZk1zPzogbnVtYmVyLFxuICBub3dNczogbnVtYmVyID0gRGF0ZS5ub3coKSxcbik6IG51bWJlciA9PiB7XG4gIGNvbnN0IGhlYWRlciA9IHJlc3BvbnNlLmhlYWRlcnM/LmdldCgnUmV0cnktQWZ0ZXInKTtcbiAgaWYgKGhlYWRlciA9PSBudWxsKSB7XG4gICAgcmV0dXJuIGZ1bGxKaXR0ZXIoYmFzZUJhY2tvZmZNcywgYXR0ZW1wdCwgbWF4QmFja29mZk1zKTtcbiAgfVxuXG4gIGNvbnN0IGZyb21IZWFkZXIgPSBwYXJzZVJldHJ5QWZ0ZXJNcyhoZWFkZXIsIG5vd01zKTtcbiAgaWYgKGZyb21IZWFkZXIgPT09IHVuZGVmaW5lZCkge1xuICAgIHJldHVybiBmdWxsSml0dGVyKGJhc2VCYWNrb2ZmTXMsIGF0dGVtcHQsIG1heEJhY2tvZmZNcyk7XG4gIH1cblxuICByZXR1cm4gZnJvbUhlYWRlcjtcbn07XG4iXX0=
@@ -0,0 +1,100 @@
1
+ /**
2
+ * Typed errors thrown by {@link fetchRetrier}.
3
+ *
4
+ * All public error classes extend {@link FetchRetrierError} so callers can catch any library
5
+ * failure with one `instanceof` check.
6
+ */
7
+ /**
8
+ * Base class for every error thrown by this package.
9
+ *
10
+ * Catch {@link FetchRetrierError} to handle any library failure, or a subclass for a specific
11
+ * case. Distinct from native `fetch` `TypeError` and `AbortError` values, which are wrapped
12
+ * before they leave {@link fetchRetrier}.
13
+ */
14
+ export declare class FetchRetrierError extends Error {
15
+ readonly name: string;
16
+ /**
17
+ * @param message - Human-readable reason
18
+ */
19
+ constructor(message: string);
20
+ }
21
+ /**
22
+ * Error thrown when the last attempt is cancelled by per-attempt timeout or an in-flight
23
+ * external {@link AbortSignal}. Remaining retries after an external abort throw
24
+ * {@link FetchRetrierAlreadyAbortedError} instead, because the signal stays aborted.
25
+ */
26
+ export declare class FetchRetrierAbortError extends FetchRetrierError {
27
+ readonly name: string;
28
+ /**
29
+ * @param message - Human-readable reason (default: `'Aborted'`)
30
+ */
31
+ constructor(message?: string);
32
+ }
33
+ /**
34
+ * Error thrown when {@link RequestOptions.signal} is already aborted before an attempt starts.
35
+ */
36
+ export declare class FetchRetrierAlreadyAbortedError extends FetchRetrierAbortError {
37
+ readonly name: string;
38
+ /**
39
+ * @param message - Human-readable reason (default: `'Signal was already aborted'`)
40
+ */
41
+ constructor(message?: string);
42
+ }
43
+ /**
44
+ * Error thrown when the server returns a non-OK HTTP status and no further retry is performed.
45
+ *
46
+ * Carries the last response `status` and the body text already consumed via `response.text()`
47
+ * (the same text passed to {@link RequestOptions.shouldRetry}).
48
+ *
49
+ * @property status - HTTP status code from the last non-OK response
50
+ * @property body - Response body text already read via `response.text()` for that attempt
51
+ */
52
+ export declare class FetchRetrierHttpError extends FetchRetrierError {
53
+ readonly status: number;
54
+ readonly body: string;
55
+ readonly name: string;
56
+ /**
57
+ * @param message - Error description
58
+ * @param status - HTTP status code from the last non-OK response
59
+ * @param body - Response body text already read via `response.text()` for that attempt
60
+ */
61
+ constructor(message: string, status: number, body: string);
62
+ }
63
+ /**
64
+ * Error thrown when a fetch fails with a network-level error (e.g. DNS failure, connection refused).
65
+ *
66
+ * @property cause - Original error from the underlying `fetch`, when available
67
+ */
68
+ export declare class FetchRetrierNetworkError extends FetchRetrierError {
69
+ readonly cause?: unknown | undefined;
70
+ readonly name: string;
71
+ /**
72
+ * @param message - Human-readable reason (default: `'Network error'`)
73
+ * @param cause - Original error from the underlying `fetch`, when available
74
+ */
75
+ constructor(message?: string, cause?: unknown | undefined);
76
+ }
77
+ /**
78
+ * Error thrown when {@link RequestOptions} contains invalid numeric values.
79
+ *
80
+ * Extends {@link FetchRetrierError}, not {@link TypeError}, so it is not treated as a network
81
+ * failure. Native `fetch` `TypeError` values are retried and surfaced as
82
+ * {@link FetchRetrierNetworkError} after the last attempt.
83
+ */
84
+ export declare class FetchRetrierInvalidOptionsError extends FetchRetrierError {
85
+ readonly name: string;
86
+ /**
87
+ * @param message - Human-readable reason describing the invalid option
88
+ */
89
+ constructor(message: string);
90
+ }
91
+ /**
92
+ * Error thrown when an internal invariant fails (should not happen in normal use).
93
+ */
94
+ export declare class FetchRetrierUnreachableError extends FetchRetrierError {
95
+ readonly name: string;
96
+ /**
97
+ * @param message - Human-readable reason (default: `'Unreachable'`)
98
+ */
99
+ constructor(message?: string);
100
+ }
@@ -0,0 +1,132 @@
1
+ "use strict";
2
+ /**
3
+ * Typed errors thrown by {@link fetchRetrier}.
4
+ *
5
+ * All public error classes extend {@link FetchRetrierError} so callers can catch any library
6
+ * failure with one `instanceof` check.
7
+ */
8
+ Object.defineProperty(exports, "__esModule", { value: true });
9
+ exports.FetchRetrierUnreachableError = exports.FetchRetrierInvalidOptionsError = exports.FetchRetrierNetworkError = exports.FetchRetrierHttpError = exports.FetchRetrierAlreadyAbortedError = exports.FetchRetrierAbortError = exports.FetchRetrierError = void 0;
10
+ /**
11
+ * Base class for every error thrown by this package.
12
+ *
13
+ * Catch {@link FetchRetrierError} to handle any library failure, or a subclass for a specific
14
+ * case. Distinct from native `fetch` `TypeError` and `AbortError` values, which are wrapped
15
+ * before they leave {@link fetchRetrier}.
16
+ */
17
+ class FetchRetrierError extends Error {
18
+ /**
19
+ * @param message - Human-readable reason
20
+ */
21
+ constructor(message) {
22
+ super(message);
23
+ this.name = 'FetchRetrierError';
24
+ Object.setPrototypeOf(this, FetchRetrierError.prototype);
25
+ }
26
+ }
27
+ exports.FetchRetrierError = FetchRetrierError;
28
+ /**
29
+ * Error thrown when the last attempt is cancelled by per-attempt timeout or an in-flight
30
+ * external {@link AbortSignal}. Remaining retries after an external abort throw
31
+ * {@link FetchRetrierAlreadyAbortedError} instead, because the signal stays aborted.
32
+ */
33
+ class FetchRetrierAbortError extends FetchRetrierError {
34
+ /**
35
+ * @param message - Human-readable reason (default: `'Aborted'`)
36
+ */
37
+ constructor(message = 'Aborted') {
38
+ super(message);
39
+ this.name = 'FetchRetrierAbortError';
40
+ Object.setPrototypeOf(this, FetchRetrierAbortError.prototype);
41
+ }
42
+ }
43
+ exports.FetchRetrierAbortError = FetchRetrierAbortError;
44
+ /**
45
+ * Error thrown when {@link RequestOptions.signal} is already aborted before an attempt starts.
46
+ */
47
+ class FetchRetrierAlreadyAbortedError extends FetchRetrierAbortError {
48
+ /**
49
+ * @param message - Human-readable reason (default: `'Signal was already aborted'`)
50
+ */
51
+ constructor(message = 'Signal was already aborted') {
52
+ super(message);
53
+ this.name = 'FetchRetrierAlreadyAbortedError';
54
+ Object.setPrototypeOf(this, FetchRetrierAlreadyAbortedError.prototype);
55
+ }
56
+ }
57
+ exports.FetchRetrierAlreadyAbortedError = FetchRetrierAlreadyAbortedError;
58
+ /**
59
+ * Error thrown when the server returns a non-OK HTTP status and no further retry is performed.
60
+ *
61
+ * Carries the last response `status` and the body text already consumed via `response.text()`
62
+ * (the same text passed to {@link RequestOptions.shouldRetry}).
63
+ *
64
+ * @property status - HTTP status code from the last non-OK response
65
+ * @property body - Response body text already read via `response.text()` for that attempt
66
+ */
67
+ class FetchRetrierHttpError extends FetchRetrierError {
68
+ /**
69
+ * @param message - Error description
70
+ * @param status - HTTP status code from the last non-OK response
71
+ * @param body - Response body text already read via `response.text()` for that attempt
72
+ */
73
+ constructor(message, status, body) {
74
+ super(message);
75
+ this.status = status;
76
+ this.body = body;
77
+ this.name = 'FetchRetrierHttpError';
78
+ Object.setPrototypeOf(this, FetchRetrierHttpError.prototype);
79
+ }
80
+ }
81
+ exports.FetchRetrierHttpError = FetchRetrierHttpError;
82
+ /**
83
+ * Error thrown when a fetch fails with a network-level error (e.g. DNS failure, connection refused).
84
+ *
85
+ * @property cause - Original error from the underlying `fetch`, when available
86
+ */
87
+ class FetchRetrierNetworkError extends FetchRetrierError {
88
+ /**
89
+ * @param message - Human-readable reason (default: `'Network error'`)
90
+ * @param cause - Original error from the underlying `fetch`, when available
91
+ */
92
+ constructor(message = 'Network error', cause) {
93
+ super(message);
94
+ this.cause = cause;
95
+ this.name = 'FetchRetrierNetworkError';
96
+ Object.setPrototypeOf(this, FetchRetrierNetworkError.prototype);
97
+ }
98
+ }
99
+ exports.FetchRetrierNetworkError = FetchRetrierNetworkError;
100
+ /**
101
+ * Error thrown when {@link RequestOptions} contains invalid numeric values.
102
+ *
103
+ * Extends {@link FetchRetrierError}, not {@link TypeError}, so it is not treated as a network
104
+ * failure. Native `fetch` `TypeError` values are retried and surfaced as
105
+ * {@link FetchRetrierNetworkError} after the last attempt.
106
+ */
107
+ class FetchRetrierInvalidOptionsError extends FetchRetrierError {
108
+ /**
109
+ * @param message - Human-readable reason describing the invalid option
110
+ */
111
+ constructor(message) {
112
+ super(message);
113
+ this.name = 'FetchRetrierInvalidOptionsError';
114
+ Object.setPrototypeOf(this, FetchRetrierInvalidOptionsError.prototype);
115
+ }
116
+ }
117
+ exports.FetchRetrierInvalidOptionsError = FetchRetrierInvalidOptionsError;
118
+ /**
119
+ * Error thrown when an internal invariant fails (should not happen in normal use).
120
+ */
121
+ class FetchRetrierUnreachableError extends FetchRetrierError {
122
+ /**
123
+ * @param message - Human-readable reason (default: `'Unreachable'`)
124
+ */
125
+ constructor(message = 'Unreachable') {
126
+ super(message);
127
+ this.name = 'FetchRetrierUnreachableError';
128
+ Object.setPrototypeOf(this, FetchRetrierUnreachableError.prototype);
129
+ }
130
+ }
131
+ exports.FetchRetrierUnreachableError = FetchRetrierUnreachableError;
132
+ //# sourceMappingURL=data:application/json;base64,eyJ2ZXJzaW9uIjozLCJmaWxlIjoiZXJyb3JzLmpzIiwic291cmNlUm9vdCI6IiIsInNvdXJjZXMiOlsiLi4vLi4vc3JjL2NvcmUvZXJyb3JzLnRzIl0sIm5hbWVzIjpbXSwibWFwcGluZ3MiOiI7QUFBQTs7Ozs7R0FLRzs7O0FBRUg7Ozs7OztHQU1HO0FBQ0gsTUFBYSxpQkFBa0IsU0FBUSxLQUFLO0lBRTFDOztPQUVHO0lBQ0gsWUFBWSxPQUFlO1FBQ3pCLEtBQUssQ0FBQyxPQUFPLENBQUMsQ0FBQztRQUxDLFNBQUksR0FBVyxtQkFBbUIsQ0FBQztRQU1uRCxNQUFNLENBQUMsY0FBYyxDQUFDLElBQUksRUFBRSxpQkFBaUIsQ0FBQyxTQUFTLENBQUMsQ0FBQztJQUMzRCxDQUFDO0NBQ0Y7QUFURCw4Q0FTQztBQUVEOzs7O0dBSUc7QUFDSCxNQUFhLHNCQUF1QixTQUFRLGlCQUFpQjtJQUUzRDs7T0FFRztJQUNILFlBQVksT0FBTyxHQUFHLFNBQVM7UUFDN0IsS0FBSyxDQUFDLE9BQU8sQ0FBQyxDQUFDO1FBTEMsU0FBSSxHQUFXLHdCQUF3QixDQUFDO1FBTXhELE1BQU0sQ0FBQyxjQUFjLENBQUMsSUFBSSxFQUFFLHNCQUFzQixDQUFDLFNBQVMsQ0FBQyxDQUFDO0lBQ2hFLENBQUM7Q0FDRjtBQVRELHdEQVNDO0FBRUQ7O0dBRUc7QUFDSCxNQUFhLCtCQUFnQyxTQUFRLHNCQUFzQjtJQUV6RTs7T0FFRztJQUNILFlBQVksT0FBTyxHQUFHLDRCQUE0QjtRQUNoRCxLQUFLLENBQUMsT0FBTyxDQUFDLENBQUM7UUFMQyxTQUFJLEdBQVcsaUNBQWlDLENBQUM7UUFNakUsTUFBTSxDQUFDLGNBQWMsQ0FBQyxJQUFJLEVBQUUsK0JBQStCLENBQUMsU0FBUyxDQUFDLENBQUM7SUFDekUsQ0FBQztDQUNGO0FBVEQsMEVBU0M7QUFFRDs7Ozs7Ozs7R0FRRztBQUNILE1BQWEscUJBQXNCLFNBQVEsaUJBQWlCO0lBRTFEOzs7O09BSUc7SUFDSCxZQUNFLE9BQWUsRUFDQyxNQUFjLEVBQ2QsSUFBWTtRQUU1QixLQUFLLENBQUMsT0FBTyxDQUFDLENBQUM7UUFIQyxXQUFNLEdBQU4sTUFBTSxDQUFRO1FBQ2QsU0FBSSxHQUFKLElBQUksQ0FBUTtRQVRaLFNBQUksR0FBVyx1QkFBdUIsQ0FBQztRQVl2RCxNQUFNLENBQUMsY0FBYyxDQUFDLElBQUksRUFBRSxxQkFBcUIsQ0FBQyxTQUFTLENBQUMsQ0FBQztJQUMvRCxDQUFDO0NBQ0Y7QUFmRCxzREFlQztBQUVEOzs7O0dBSUc7QUFDSCxNQUFhLHdCQUF5QixTQUFRLGlCQUFpQjtJQUU3RDs7O09BR0c7SUFDSCxZQUFZLE9BQU8sR0FBRyxlQUFlLEVBQWtCLEtBQWU7UUFDcEUsS0FBSyxDQUFDLE9BQU8sQ0FBQyxDQUFDO1FBRHNDLFVBQUssR0FBTCxLQUFLLENBQVU7UUFMcEQsU0FBSSxHQUFXLDBCQUEwQixDQUFDO1FBTzFELE1BQU0sQ0FBQyxjQUFjLENBQUMsSUFBSSxFQUFFLHdCQUF3QixDQUFDLFNBQVMsQ0FBQyxDQUFDO0lBQ2xFLENBQUM7Q0FDRjtBQVZELDREQVVDO0FBRUQ7Ozs7OztHQU1HO0FBQ0gsTUFBYSwrQkFBZ0MsU0FBUSxpQkFBaUI7SUFFcEU7O09BRUc7SUFDSCxZQUFZLE9BQWU7UUFDekIsS0FBSyxDQUFDLE9BQU8sQ0FBQyxDQUFDO1FBTEMsU0FBSSxHQUFXLGlDQUFpQyxDQUFDO1FBTWpFLE1BQU0sQ0FBQyxjQUFjLENBQUMsSUFBSSxFQUFFLCtCQUErQixDQUFDLFNBQVMsQ0FBQyxDQUFDO0lBQ3pFLENBQUM7Q0FDRjtBQVRELDBFQVNDO0FBRUQ7O0dBRUc7QUFDSCxNQUFhLDRCQUE2QixTQUFRLGlCQUFpQjtJQUVqRTs7T0FFRztJQUNILFlBQVksT0FBTyxHQUFHLGFBQWE7UUFDakMsS0FBSyxDQUFDLE9BQU8sQ0FBQyxDQUFDO1FBTEMsU0FBSSxHQUFXLDhCQUE4QixDQUFDO1FBTTlELE1BQU0sQ0FBQyxjQUFjLENBQUMsSUFBSSxFQUFFLDRCQUE0QixDQUFDLFNBQVMsQ0FBQyxDQUFDO0lBQ3RFLENBQUM7Q0FDRjtBQVRELG9FQVNDIiwic291cmNlc0NvbnRlbnQiOlsiLyoqXG4gKiBUeXBlZCBlcnJvcnMgdGhyb3duIGJ5IHtAbGluayBmZXRjaFJldHJpZXJ9LlxuICpcbiAqIEFsbCBwdWJsaWMgZXJyb3IgY2xhc3NlcyBleHRlbmQge0BsaW5rIEZldGNoUmV0cmllckVycm9yfSBzbyBjYWxsZXJzIGNhbiBjYXRjaCBhbnkgbGlicmFyeVxuICogZmFpbHVyZSB3aXRoIG9uZSBgaW5zdGFuY2VvZmAgY2hlY2suXG4gKi9cblxuLyoqXG4gKiBCYXNlIGNsYXNzIGZvciBldmVyeSBlcnJvciB0aHJvd24gYnkgdGhpcyBwYWNrYWdlLlxuICpcbiAqIENhdGNoIHtAbGluayBGZXRjaFJldHJpZXJFcnJvcn0gdG8gaGFuZGxlIGFueSBsaWJyYXJ5IGZhaWx1cmUsIG9yIGEgc3ViY2xhc3MgZm9yIGEgc3BlY2lmaWNcbiAqIGNhc2UuIERpc3RpbmN0IGZyb20gbmF0aXZlIGBmZXRjaGAgYFR5cGVFcnJvcmAgYW5kIGBBYm9ydEVycm9yYCB2YWx1ZXMsIHdoaWNoIGFyZSB3cmFwcGVkXG4gKiBiZWZvcmUgdGhleSBsZWF2ZSB7QGxpbmsgZmV0Y2hSZXRyaWVyfS5cbiAqL1xuZXhwb3J0IGNsYXNzIEZldGNoUmV0cmllckVycm9yIGV4dGVuZHMgRXJyb3Ige1xuICBvdmVycmlkZSByZWFkb25seSBuYW1lOiBzdHJpbmcgPSAnRmV0Y2hSZXRyaWVyRXJyb3InO1xuICAvKipcbiAgICogQHBhcmFtIG1lc3NhZ2UgLSBIdW1hbi1yZWFkYWJsZSByZWFzb25cbiAgICovXG4gIGNvbnN0cnVjdG9yKG1lc3NhZ2U6IHN0cmluZykge1xuICAgIHN1cGVyKG1lc3NhZ2UpO1xuICAgIE9iamVjdC5zZXRQcm90b3R5cGVPZih0aGlzLCBGZXRjaFJldHJpZXJFcnJvci5wcm90b3R5cGUpO1xuICB9XG59XG5cbi8qKlxuICogRXJyb3IgdGhyb3duIHdoZW4gdGhlIGxhc3QgYXR0ZW1wdCBpcyBjYW5jZWxsZWQgYnkgcGVyLWF0dGVtcHQgdGltZW91dCBvciBhbiBpbi1mbGlnaHRcbiAqIGV4dGVybmFsIHtAbGluayBBYm9ydFNpZ25hbH0uIFJlbWFpbmluZyByZXRyaWVzIGFmdGVyIGFuIGV4dGVybmFsIGFib3J0IHRocm93XG4gKiB7QGxpbmsgRmV0Y2hSZXRyaWVyQWxyZWFkeUFib3J0ZWRFcnJvcn0gaW5zdGVhZCwgYmVjYXVzZSB0aGUgc2lnbmFsIHN0YXlzIGFib3J0ZWQuXG4gKi9cbmV4cG9ydCBjbGFzcyBGZXRjaFJldHJpZXJBYm9ydEVycm9yIGV4dGVuZHMgRmV0Y2hSZXRyaWVyRXJyb3Ige1xuICBvdmVycmlkZSByZWFkb25seSBuYW1lOiBzdHJpbmcgPSAnRmV0Y2hSZXRyaWVyQWJvcnRFcnJvcic7XG4gIC8qKlxuICAgKiBAcGFyYW0gbWVzc2FnZSAtIEh1bWFuLXJlYWRhYmxlIHJlYXNvbiAoZGVmYXVsdDogYCdBYm9ydGVkJ2ApXG4gICAqL1xuICBjb25zdHJ1Y3RvcihtZXNzYWdlID0gJ0Fib3J0ZWQnKSB7XG4gICAgc3VwZXIobWVzc2FnZSk7XG4gICAgT2JqZWN0LnNldFByb3RvdHlwZU9mKHRoaXMsIEZldGNoUmV0cmllckFib3J0RXJyb3IucHJvdG90eXBlKTtcbiAgfVxufVxuXG4vKipcbiAqIEVycm9yIHRocm93biB3aGVuIHtAbGluayBSZXF1ZXN0T3B0aW9ucy5zaWduYWx9IGlzIGFscmVhZHkgYWJvcnRlZCBiZWZvcmUgYW4gYXR0ZW1wdCBzdGFydHMuXG4gKi9cbmV4cG9ydCBjbGFzcyBGZXRjaFJldHJpZXJBbHJlYWR5QWJvcnRlZEVycm9yIGV4dGVuZHMgRmV0Y2hSZXRyaWVyQWJvcnRFcnJvciB7XG4gIG92ZXJyaWRlIHJlYWRvbmx5IG5hbWU6IHN0cmluZyA9ICdGZXRjaFJldHJpZXJBbHJlYWR5QWJvcnRlZEVycm9yJztcbiAgLyoqXG4gICAqIEBwYXJhbSBtZXNzYWdlIC0gSHVtYW4tcmVhZGFibGUgcmVhc29uIChkZWZhdWx0OiBgJ1NpZ25hbCB3YXMgYWxyZWFkeSBhYm9ydGVkJ2ApXG4gICAqL1xuICBjb25zdHJ1Y3RvcihtZXNzYWdlID0gJ1NpZ25hbCB3YXMgYWxyZWFkeSBhYm9ydGVkJykge1xuICAgIHN1cGVyKG1lc3NhZ2UpO1xuICAgIE9iamVjdC5zZXRQcm90b3R5cGVPZih0aGlzLCBGZXRjaFJldHJpZXJBbHJlYWR5QWJvcnRlZEVycm9yLnByb3RvdHlwZSk7XG4gIH1cbn1cblxuLyoqXG4gKiBFcnJvciB0aHJvd24gd2hlbiB0aGUgc2VydmVyIHJldHVybnMgYSBub24tT0sgSFRUUCBzdGF0dXMgYW5kIG5vIGZ1cnRoZXIgcmV0cnkgaXMgcGVyZm9ybWVkLlxuICpcbiAqIENhcnJpZXMgdGhlIGxhc3QgcmVzcG9uc2UgYHN0YXR1c2AgYW5kIHRoZSBib2R5IHRleHQgYWxyZWFkeSBjb25zdW1lZCB2aWEgYHJlc3BvbnNlLnRleHQoKWBcbiAqICh0aGUgc2FtZSB0ZXh0IHBhc3NlZCB0byB7QGxpbmsgUmVxdWVzdE9wdGlvbnMuc2hvdWxkUmV0cnl9KS5cbiAqXG4gKiBAcHJvcGVydHkgc3RhdHVzIC0gSFRUUCBzdGF0dXMgY29kZSBmcm9tIHRoZSBsYXN0IG5vbi1PSyByZXNwb25zZVxuICogQHByb3BlcnR5IGJvZHkgLSBSZXNwb25zZSBib2R5IHRleHQgYWxyZWFkeSByZWFkIHZpYSBgcmVzcG9uc2UudGV4dCgpYCBmb3IgdGhhdCBhdHRlbXB0XG4gKi9cbmV4cG9ydCBjbGFzcyBGZXRjaFJldHJpZXJIdHRwRXJyb3IgZXh0ZW5kcyBGZXRjaFJldHJpZXJFcnJvciB7XG4gIG92ZXJyaWRlIHJlYWRvbmx5IG5hbWU6IHN0cmluZyA9ICdGZXRjaFJldHJpZXJIdHRwRXJyb3InO1xuICAvKipcbiAgICogQHBhcmFtIG1lc3NhZ2UgLSBFcnJvciBkZXNjcmlwdGlvblxuICAgKiBAcGFyYW0gc3RhdHVzIC0gSFRUUCBzdGF0dXMgY29kZSBmcm9tIHRoZSBsYXN0IG5vbi1PSyByZXNwb25zZVxuICAgKiBAcGFyYW0gYm9keSAtIFJlc3BvbnNlIGJvZHkgdGV4dCBhbHJlYWR5IHJlYWQgdmlhIGByZXNwb25zZS50ZXh0KClgIGZvciB0aGF0IGF0dGVtcHRcbiAgICovXG4gIGNvbnN0cnVjdG9yKFxuICAgIG1lc3NhZ2U6IHN0cmluZyxcbiAgICBwdWJsaWMgcmVhZG9ubHkgc3RhdHVzOiBudW1iZXIsXG4gICAgcHVibGljIHJlYWRvbmx5IGJvZHk6IHN0cmluZyxcbiAgKSB7XG4gICAgc3VwZXIobWVzc2FnZSk7XG4gICAgT2JqZWN0LnNldFByb3RvdHlwZU9mKHRoaXMsIEZldGNoUmV0cmllckh0dHBFcnJvci5wcm90b3R5cGUpO1xuICB9XG59XG5cbi8qKlxuICogRXJyb3IgdGhyb3duIHdoZW4gYSBmZXRjaCBmYWlscyB3aXRoIGEgbmV0d29yay1sZXZlbCBlcnJvciAoZS5nLiBETlMgZmFpbHVyZSwgY29ubmVjdGlvbiByZWZ1c2VkKS5cbiAqXG4gKiBAcHJvcGVydHkgY2F1c2UgLSBPcmlnaW5hbCBlcnJvciBmcm9tIHRoZSB1bmRlcmx5aW5nIGBmZXRjaGAsIHdoZW4gYXZhaWxhYmxlXG4gKi9cbmV4cG9ydCBjbGFzcyBGZXRjaFJldHJpZXJOZXR3b3JrRXJyb3IgZXh0ZW5kcyBGZXRjaFJldHJpZXJFcnJvciB7XG4gIG92ZXJyaWRlIHJlYWRvbmx5IG5hbWU6IHN0cmluZyA9ICdGZXRjaFJldHJpZXJOZXR3b3JrRXJyb3InO1xuICAvKipcbiAgICogQHBhcmFtIG1lc3NhZ2UgLSBIdW1hbi1yZWFkYWJsZSByZWFzb24gKGRlZmF1bHQ6IGAnTmV0d29yayBlcnJvcidgKVxuICAgKiBAcGFyYW0gY2F1c2UgLSBPcmlnaW5hbCBlcnJvciBmcm9tIHRoZSB1bmRlcmx5aW5nIGBmZXRjaGAsIHdoZW4gYXZhaWxhYmxlXG4gICAqL1xuICBjb25zdHJ1Y3RvcihtZXNzYWdlID0gJ05ldHdvcmsgZXJyb3InLCBwdWJsaWMgcmVhZG9ubHkgY2F1c2U/OiB1bmtub3duKSB7XG4gICAgc3VwZXIobWVzc2FnZSk7XG4gICAgT2JqZWN0LnNldFByb3RvdHlwZU9mKHRoaXMsIEZldGNoUmV0cmllck5ldHdvcmtFcnJvci5wcm90b3R5cGUpO1xuICB9XG59XG5cbi8qKlxuICogRXJyb3IgdGhyb3duIHdoZW4ge0BsaW5rIFJlcXVlc3RPcHRpb25zfSBjb250YWlucyBpbnZhbGlkIG51bWVyaWMgdmFsdWVzLlxuICpcbiAqIEV4dGVuZHMge0BsaW5rIEZldGNoUmV0cmllckVycm9yfSwgbm90IHtAbGluayBUeXBlRXJyb3J9LCBzbyBpdCBpcyBub3QgdHJlYXRlZCBhcyBhIG5ldHdvcmtcbiAqIGZhaWx1cmUuIE5hdGl2ZSBgZmV0Y2hgIGBUeXBlRXJyb3JgIHZhbHVlcyBhcmUgcmV0cmllZCBhbmQgc3VyZmFjZWQgYXNcbiAqIHtAbGluayBGZXRjaFJldHJpZXJOZXR3b3JrRXJyb3J9IGFmdGVyIHRoZSBsYXN0IGF0dGVtcHQuXG4gKi9cbmV4cG9ydCBjbGFzcyBGZXRjaFJldHJpZXJJbnZhbGlkT3B0aW9uc0Vycm9yIGV4dGVuZHMgRmV0Y2hSZXRyaWVyRXJyb3Ige1xuICBvdmVycmlkZSByZWFkb25seSBuYW1lOiBzdHJpbmcgPSAnRmV0Y2hSZXRyaWVySW52YWxpZE9wdGlvbnNFcnJvcic7XG4gIC8qKlxuICAgKiBAcGFyYW0gbWVzc2FnZSAtIEh1bWFuLXJlYWRhYmxlIHJlYXNvbiBkZXNjcmliaW5nIHRoZSBpbnZhbGlkIG9wdGlvblxuICAgKi9cbiAgY29uc3RydWN0b3IobWVzc2FnZTogc3RyaW5nKSB7XG4gICAgc3VwZXIobWVzc2FnZSk7XG4gICAgT2JqZWN0LnNldFByb3RvdHlwZU9mKHRoaXMsIEZldGNoUmV0cmllckludmFsaWRPcHRpb25zRXJyb3IucHJvdG90eXBlKTtcbiAgfVxufVxuXG4vKipcbiAqIEVycm9yIHRocm93biB3aGVuIGFuIGludGVybmFsIGludmFyaWFudCBmYWlscyAoc2hvdWxkIG5vdCBoYXBwZW4gaW4gbm9ybWFsIHVzZSkuXG4gKi9cbmV4cG9ydCBjbGFzcyBGZXRjaFJldHJpZXJVbnJlYWNoYWJsZUVycm9yIGV4dGVuZHMgRmV0Y2hSZXRyaWVyRXJyb3Ige1xuICBvdmVycmlkZSByZWFkb25seSBuYW1lOiBzdHJpbmcgPSAnRmV0Y2hSZXRyaWVyVW5yZWFjaGFibGVFcnJvcic7XG4gIC8qKlxuICAgKiBAcGFyYW0gbWVzc2FnZSAtIEh1bWFuLXJlYWRhYmxlIHJlYXNvbiAoZGVmYXVsdDogYCdVbnJlYWNoYWJsZSdgKVxuICAgKi9cbiAgY29uc3RydWN0b3IobWVzc2FnZSA9ICdVbnJlYWNoYWJsZScpIHtcbiAgICBzdXBlcihtZXNzYWdlKTtcbiAgICBPYmplY3Quc2V0UHJvdG90eXBlT2YodGhpcywgRmV0Y2hSZXRyaWVyVW5yZWFjaGFibGVFcnJvci5wcm90b3R5cGUpO1xuICB9XG59XG4iXX0=
@@ -0,0 +1,28 @@
1
+ import { RequestOptions } from './options';
2
+ /**
3
+ * Wraps `fetch` with retries, per-attempt timeout, Retry-After support, full-jitter backoff, and
4
+ * optional cancellation.
5
+ *
6
+ * Each attempt calls {@link timedFetch} with `timeoutMs`. Non-OK responses are retried when
7
+ * `shouldRetry` returns `true` (default: {@link defaultShouldRetry}). Between HTTP retries, a
8
+ * valid `Retry-After` header (delta-seconds or HTTP-date) is preferred over full jitter; abort
9
+ * and network retries always use full jitter. Optional {@link RequestOptions.maxBackoffMs} clips
10
+ * the jitter result, not a valid `Retry-After`. The same {@link FetchInitOptions} (including
11
+ * `body`) is reused on every attempt.
12
+ *
13
+ * @param url - Request URL passed to `fetch`
14
+ * @param options - {@link RequestOptions} controlling retries, timeout, request init, and cancellation
15
+ * @returns The first {@link Response} for which `ok` is `true`
16
+ * @throws {FetchRetrierError} All failures from this function are subclasses of this class
17
+ * @throws {FetchRetrierInvalidOptionsError} If `retries < 1`, `timeoutMs <= 0`, `baseBackoffMs < 0`,
18
+ * or `maxBackoffMs` is set and `< 0`
19
+ * @throws {FetchRetrierAlreadyAbortedError} If `options.signal` is already aborted before an attempt,
20
+ * including the next attempt after an in-flight external abort while retries remain
21
+ * @throws {FetchRetrierHttpError} On a non-OK response that is not retried or after the last attempt
22
+ * (includes `status` and `body`)
23
+ * @throws {FetchRetrierNetworkError} On a network `TypeError` after the last attempt
24
+ * @throws {FetchRetrierAbortError} On per-attempt timeout after the last attempt, or external abort
25
+ * on the last attempt
26
+ * @throws {FetchRetrierUnreachableError} If the retry loop exits without returning (internal bug)
27
+ */
28
+ export declare const fetchRetrier: (url: string, options: RequestOptions) => Promise<Response>;