@fetchkit/ffetch 5.0.0 → 5.0.1

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,230 +1,231 @@
1
- ![npm](https://img.shields.io/npm/v/@fetchkit/ffetch)
2
- ![Downloads](https://img.shields.io/npm/dm/@fetchkit/ffetch)
3
- ![GitHub stars](https://img.shields.io/github/stars/fetch-kit/ffetch?style=social)
4
-
5
- ![Build](https://github.com/fetch-kit/ffetch/actions/workflows/ci.yml/badge.svg)
6
- ![codecov](https://codecov.io/gh/fetch-kit/ffetch/branch/main/graph/badge.svg)
7
-
8
- ![MIT](https://img.shields.io/npm/l/@fetchkit/ffetch)
9
- ![bundlephobia](https://badgen.net/bundlephobia/minzip/@fetchkit/ffetch)
10
- ![Types](https://img.shields.io/npm/types/@fetchkit/ffetch)
11
-
12
- # @fetchkit/ffetch
13
-
14
- **A production-ready TypeScript-first drop-in replacement for native fetch, or any fetch-compatible implementation.**
15
-
16
- ffetch can wrap any fetch-compatible implementation (native fetch, node-fetch, undici, or framework-provided fetch), making it flexible for SSR, edge, and custom environments.
17
-
18
- ffetch uses a plugin architecture for optional features, so you only include what you need.
19
-
20
- **Key Features:**
21
-
22
- - **Timeouts** – per-request or global
23
- - **Retries** – exponential backoff + jitter
24
- - **Plugin architecture** – extensible lifecycle-based plugins for optional behavior
25
- - **Hooks** – logging, auth, metrics, request/response transformation
26
- - **Pending requests** – real-time monitoring of active requests
27
- - **Per-request overrides** – customize behavior on a per-request basis
28
- - **Universal** – Node.js, Browser, Cloudflare Workers, React Native
29
- - **Zero runtime deps** – ships as dual ESM/CJS
30
- - **Configurable error handling** – custom error types and `throwOnHttpError` flag to throw on HTTP errors
31
- - **Circuit breaker plugin (optional, prebuilt)** – automatic failure protection
32
- - **Deduplication plugin (optional, prebuilt)** – automatic deduping of in-flight identical requests
33
-
34
- ## Quick Start
35
-
36
- ### Install
37
-
38
- ```bash
39
- npm install @fetchkit/ffetch
40
- ```
41
-
42
- ### Basic Usage
43
-
44
- ```typescript
45
- import { createClient } from '@fetchkit/ffetch'
46
- import { dedupePlugin } from '@fetchkit/ffetch/plugins/dedupe'
47
-
48
- // Create a client with timeout, retries, and deduplication plugin
49
- const api = createClient({
50
- timeout: 5000,
51
- retries: 3,
52
- plugins: [dedupePlugin()],
53
- retryDelay: ({ attempt }) => 2 ** attempt * 100 + Math.random() * 100,
54
- })
55
-
56
- // Make requests
57
- const response = await api('https://api.example.com/users')
58
- const data = await response.json()
59
-
60
- // Deduplication example: these two requests will be deduped
61
- const p1 = api('https://api.example.com/data')
62
- const p2 = api('https://api.example.com/data')
63
- const [r1, r2] = await Promise.all([p1, p2])
64
- // Only one fetch will occur; both promises resolve to the same response
65
- ```
66
-
67
- ### Using a Custom fetchHandler (SSR, metaframeworks, or polyfills)
68
-
69
- ```typescript
70
- // Example: SvelteKit, Next.js, Nuxt, or node-fetch
71
- import { createClient } from '@fetchkit/ffetch'
72
-
73
- // Pass your framework's fetch implementation
74
- const api = createClient({
75
- fetchHandler: fetch, // SvelteKit/Next.js/Nuxt provide their own fetch
76
- timeout: 5000,
77
- })
78
-
79
- // Or use node-fetch/undici in Node.js
80
- import nodeFetch from 'node-fetch'
81
- const apiNode = createClient({ fetchHandler: nodeFetch })
82
-
83
- // All ffetch features work identically
84
- const response = await api('/api/data')
85
- ```
86
-
87
- ### Advanced Example
88
-
89
- ```typescript
90
- // Production-ready client with error handling and monitoring
91
- import { createClient } from '@fetchkit/ffetch'
92
- import { dedupePlugin } from '@fetchkit/ffetch/plugins/dedupe'
93
- import { circuitPlugin } from '@fetchkit/ffetch/plugins/circuit'
94
-
95
- const client = createClient({
96
- timeout: 10000,
97
- retries: 2,
98
- fetchHandler: fetch, // Use custom fetch if needed
99
- plugins: [
100
- dedupePlugin({
101
- hashFn: (params) => `${params.method}|${params.url}|${params.body}`,
102
- ttl: 30_000,
103
- sweepInterval: 5_000,
104
- }),
105
- circuitPlugin({
106
- threshold: 5,
107
- reset: 30_000,
108
- onCircuitOpen: (req) => console.warn('Circuit opened due to:', req.url),
109
- onCircuitClose: (req) => console.info('Circuit closed after:', req.url),
110
- }),
111
- ],
112
- hooks: {
113
- before: async (req) => console.log('→', req.url),
114
- after: async (req, res) => console.log('', res.status),
115
- onError: async (req, err) => console.error('Error:', err.message),
116
- },
117
- })
118
-
119
- try {
120
- const response = await client('/api/data')
121
-
122
- // Check HTTP status manually (like native fetch)
123
- if (!response.ok) {
124
- console.log('HTTP error:', response.status)
125
- return
126
- }
127
-
128
- const data = await response.json()
129
- console.log('Active requests:', client.pendingRequests.length)
130
- } catch (err) {
131
- if (err instanceof TimeoutError) {
132
- console.log('Request timed out')
133
- } else if (err instanceof RetryLimitError) {
134
- console.log('Request failed after retries')
135
- }
136
- }
137
- ```
138
-
139
- ### Custom Error Handling with `throwOnHttpError`
140
-
141
- Native `fetch`'s controversial behavior of not throwing errors for HTTP error status codes (4xx, 5xx) can lead to overlooked errors in applications. By default, `ffetch` follows this same pattern, returning a `Response` object regardless of the HTTP status code. However, with the `throwOnHttpError` flag, developers can configure `ffetch` to throw an `HttpError` for HTTP error responses, making error handling more explicit and robust. Note that this behavior is affected by retries and the circuit breaker - full details are explained in the [Error Handling documentation](./docs/errorhandling.md).
142
-
143
- ## Documentation
144
-
145
- | Topic | Description |
146
- | --------------------------------------------- | ------------------------------------------------------------------------- |
147
- | **[Complete Documentation](./docs/index.md)** | **Start here** - Documentation index and overview |
148
- | **[API Reference](./docs/api.md)** | Complete API documentation and configuration options |
149
- | **[Plugin Architecture](./docs/plugins.md)** | Plugin lifecycle, custom plugin authoring, and integration patterns |
150
- | **[Deduplication](./docs/deduplication.md)** | How deduplication works, hash config, optional TTL cleanup, limitations |
151
- | **[Error Handling](./docs/errorhandling.md)** | Strategies for managing errors, including `throwOnHttpError` |
152
- | **[Advanced Features](./docs/advanced.md)** | Per-request overrides, pending requests, circuit breakers, custom errors |
153
- | **[Hooks & Transformation](./docs/hooks.md)** | Lifecycle hooks, authentication, logging, request/response transformation |
154
- | **[Usage Examples](./docs/examples.md)** | Real-world patterns: REST clients, GraphQL, file uploads, microservices |
155
- | **[Compatibility](./docs/compatibility.md)** | Browser/Node.js support, polyfills, framework integration |
156
-
157
- ## Environment Requirements
158
-
159
- `ffetch` works best with native `AbortSignal.any` support:
160
-
161
- - **Node.js 20.6+** (native `AbortSignal.any`)
162
- - **Modern browsers with `AbortSignal.any`** (for example: Chrome 117+, Firefox 117+, Safari 17+, Edge 117+)
163
-
164
- If your environment does not support `AbortSignal.any` (Node.js < 20.6, older browsers), you can still use ffetch by installing an `AbortSignal.any` polyfill. `AbortSignal.timeout` is optional because ffetch includes an internal timeout fallback. See the [compatibility guide](./docs/compatibility.md) for instructions.
165
-
166
- **Custom fetch support:**
167
- You can pass any fetch-compatible implementation (native fetch, node-fetch, undici, SvelteKit, Next.js, Nuxt, or a polyfill) via the `fetchHandler` option. This makes ffetch fully compatible with SSR, edge, metaframework environments, custom backends, and test runners.
168
-
169
- #### "AbortSignal.any is not a function"
170
-
171
- Solution: Install a polyfill for `AbortSignal.any`
172
-
173
- ```bash
174
- npm install abort-controller-x
175
- ```
176
-
177
- ## CDN Usage
178
-
179
- ```html
180
- <script type="module">
181
- import { createClient } from 'https://unpkg.com/@fetchkit/ffetch/dist/index.min.js'
182
-
183
- const api = createClient({ timeout: 5000 })
184
- const data = await api('/api/data').then((r) => r.json())
185
- </script>
186
- ```
187
-
188
- ## Deduplication Limitations
189
-
190
- - Deduplication is **off** by default. Enable it via `plugins: [dedupePlugin()]`.
191
- - The default hash function is `dedupeRequestHash`, which handles common body types and skips deduplication for streams and FormData.
192
- - Optional stale-entry cleanup: `dedupePlugin({ ttl, sweepInterval })` enables map-entry eviction. TTL eviction only removes dedupe keys; it does not reject already in-flight promises.
193
- - **Stream bodies** (`ReadableStream`, `FormData`): Deduplication is skipped for requests with these body types, as they cannot be reliably hashed or replayed.
194
- - **Non-idempotent requests**: Use deduplication with caution for non-idempotent methods (e.g., POST), as it may suppress multiple intended requests.
195
- - **Custom hash function**: Ensure your hash function uniquely identifies requests to avoid accidental deduplication.
196
-
197
- See [deduplication.md](./docs/deduplication.md) for full details.
198
-
199
- ## Fetch vs. Axios vs. `ffetch`
200
-
201
- | Feature | Native Fetch | Axios | ffetch |
202
- | -------------------- | ------------------------- | -------------------- | -------------------------------------------------------------------------------------- |
203
- | Timeouts | Manual AbortController | Built-in | Built-in with fallbacks |
204
- | Retries | ❌ Manual implementation | Manual or plugins | ✅ Smart exponential backoff |
205
- | Plugin Architecture | ❌ Not available | ⚠️ Interceptors only | ✅ First-class plugin pipeline (optional built-in + custom plugins) |
206
- | Circuit Breaker | ❌ Not available | Manual or plugins | ✅ Automatic failure protection |
207
- | Deduplication | ❌ Not available | ❌ Not available | ✅ Automatic deduplication of in-flight identical requests |
208
- | Request Monitoring | ❌ Manual tracking | ❌ Manual tracking | ✅ Built-in pending requests |
209
- | Error Types | ❌ Generic errors | ⚠️ HTTP errors only | ✅ Specific error classes |
210
- | TypeScript | ⚠️ Basic types | ⚠️ Basic types | ✅ Full type safety |
211
- | Hooks/Middleware | Not available | Interceptors | ✅ Comprehensive lifecycle hooks |
212
- | Bundle Size | Native (0kb) | ~13kb minified | ✅ ~3kb minified |
213
- | Modern APIs | ✅ Web standards | ❌ XMLHttpRequest | ✅ Fetch + modern features |
214
- | Custom Fetch Support | No (global only) | ❌ No | ✅ Yes (wrap any fetch-compatible implementation, including framework or custom fetch) |
215
-
216
- ## Join the Community
217
-
218
- Got questions, want to discuss features, or share examples? Join the **Fetch-Kit Discord server**:
219
-
220
- [![Discord](https://img.shields.io/badge/Discord-Join_Fetch--Kit-7289DA?logo=discord&logoColor=white)](https://discord.gg/sdyPBPCDUg)
221
-
222
- ## Contributing
223
-
224
- - **Issues**: [GitHub Issues](https://github.com/fetch-kit/ffetch/issues)
225
- - **Pull Requests**: [GitHub PRs](https://github.com/fetch-kit/ffetch/pulls)
226
- - **Documentation**: Found in `./docs/` - PRs welcome!
227
-
228
- ## License
229
-
230
- MIT © 2025 gkoos
1
+ ![npm](https://img.shields.io/npm/v/@fetchkit/ffetch)
2
+ ![Downloads](https://img.shields.io/npm/dm/@fetchkit/ffetch)
3
+ ![GitHub stars](https://img.shields.io/github/stars/fetch-kit/ffetch?style=social)
4
+
5
+ ![Build](https://github.com/fetch-kit/ffetch/actions/workflows/ci.yml/badge.svg)
6
+ ![codecov](https://codecov.io/gh/fetch-kit/ffetch/branch/main/graph/badge.svg)
7
+
8
+ ![MIT](https://img.shields.io/npm/l/@fetchkit/ffetch)
9
+ ![bundlephobia](https://badgen.net/bundlephobia/minzip/@fetchkit/ffetch)
10
+ ![Types](https://img.shields.io/npm/types/@fetchkit/ffetch)
11
+
12
+ # @fetchkit/ffetch
13
+
14
+ **A production-ready TypeScript-first drop-in replacement for native fetch, or any fetch-compatible implementation.**
15
+
16
+ ffetch can wrap any fetch-compatible implementation (native fetch, node-fetch, undici, or framework-provided fetch), making it flexible for SSR, edge, and custom environments.
17
+
18
+ ffetch uses a plugin architecture for optional features, so you only include what you need.
19
+
20
+ **Key Features:**
21
+
22
+ - **Timeouts** – per-request or global
23
+ - **Retries** – exponential backoff + jitter
24
+ - **Abort-aware retries** – aborting during backoff cancels immediately
25
+ - **Plugin architecture** – extensible lifecycle-based plugins for optional behavior
26
+ - **Hooks** – logging, auth, metrics, request/response transformation
27
+ - **Pending requests** – real-time monitoring of active requests
28
+ - **Per-request overrides** – customize behavior on a per-request basis
29
+ - **Universal** – Node.js, Browser, Cloudflare Workers, React Native
30
+ - **Zero runtime deps** – ships as dual ESM/CJS
31
+ - **Configurable error handling** – custom error types and `throwOnHttpError` flag to throw on HTTP errors
32
+ - **Circuit breaker plugin (optional, prebuilt)** – automatic failure protection
33
+ - **Deduplication plugin (optional, prebuilt)** – automatic deduping of in-flight identical requests
34
+
35
+ ## Quick Start
36
+
37
+ ### Install
38
+
39
+ ```bash
40
+ npm install @fetchkit/ffetch
41
+ ```
42
+
43
+ ### Basic Usage
44
+
45
+ ```typescript
46
+ import { createClient } from '@fetchkit/ffetch'
47
+ import { dedupePlugin } from '@fetchkit/ffetch/plugins/dedupe'
48
+
49
+ // Create a client with timeout, retries, and deduplication plugin
50
+ const api = createClient({
51
+ timeout: 5000,
52
+ retries: 3,
53
+ plugins: [dedupePlugin()],
54
+ retryDelay: ({ attempt }) => 2 ** attempt * 100 + Math.random() * 100,
55
+ })
56
+
57
+ // Make requests
58
+ const response = await api('https://api.example.com/users')
59
+ const data = await response.json()
60
+
61
+ // Deduplication example: these two requests will be deduped
62
+ const p1 = api('https://api.example.com/data')
63
+ const p2 = api('https://api.example.com/data')
64
+ const [r1, r2] = await Promise.all([p1, p2])
65
+ // Only one fetch will occur; both promises resolve to the same response
66
+ ```
67
+
68
+ ### Using a Custom fetchHandler (SSR, metaframeworks, or polyfills)
69
+
70
+ ```typescript
71
+ // Example: SvelteKit, Next.js, Nuxt, or node-fetch
72
+ import { createClient } from '@fetchkit/ffetch'
73
+
74
+ // Pass your framework's fetch implementation
75
+ const api = createClient({
76
+ fetchHandler: fetch, // SvelteKit/Next.js/Nuxt provide their own fetch
77
+ timeout: 5000,
78
+ })
79
+
80
+ // Or use node-fetch/undici in Node.js
81
+ import nodeFetch from 'node-fetch'
82
+ const apiNode = createClient({ fetchHandler: nodeFetch })
83
+
84
+ // All ffetch features work identically
85
+ const response = await api('/api/data')
86
+ ```
87
+
88
+ ### Advanced Example
89
+
90
+ ```typescript
91
+ // Production-ready client with error handling and monitoring
92
+ import { createClient } from '@fetchkit/ffetch'
93
+ import { dedupePlugin } from '@fetchkit/ffetch/plugins/dedupe'
94
+ import { circuitPlugin } from '@fetchkit/ffetch/plugins/circuit'
95
+
96
+ const client = createClient({
97
+ timeout: 10000,
98
+ retries: 2,
99
+ fetchHandler: fetch, // Use custom fetch if needed
100
+ plugins: [
101
+ dedupePlugin({
102
+ hashFn: (params) => `${params.method}|${params.url}|${params.body}`,
103
+ ttl: 30_000,
104
+ sweepInterval: 5_000,
105
+ }),
106
+ circuitPlugin({
107
+ threshold: 5,
108
+ reset: 30_000,
109
+ onCircuitOpen: (req) => console.warn('Circuit opened due to:', req.url),
110
+ onCircuitClose: (req) => console.info('Circuit closed after:', req.url),
111
+ }),
112
+ ],
113
+ hooks: {
114
+ before: async (req) => console.log('', req.url),
115
+ after: async (req, res) => console.log('', res.status),
116
+ onError: async (req, err) => console.error('Error:', err.message),
117
+ },
118
+ })
119
+
120
+ try {
121
+ const response = await client('/api/data')
122
+
123
+ // Check HTTP status manually (like native fetch)
124
+ if (!response.ok) {
125
+ console.log('HTTP error:', response.status)
126
+ return
127
+ }
128
+
129
+ const data = await response.json()
130
+ console.log('Active requests:', client.pendingRequests.length)
131
+ } catch (err) {
132
+ if (err instanceof TimeoutError) {
133
+ console.log('Request timed out')
134
+ } else if (err instanceof RetryLimitError) {
135
+ console.log('Request failed after retries')
136
+ }
137
+ }
138
+ ```
139
+
140
+ ### Custom Error Handling with `throwOnHttpError`
141
+
142
+ Native `fetch`'s controversial behavior of not throwing errors for HTTP error status codes (4xx, 5xx) can lead to overlooked errors in applications. By default, `ffetch` follows this same pattern, returning a `Response` object regardless of the HTTP status code. However, with the `throwOnHttpError` flag, developers can configure `ffetch` to throw an `HttpError` for HTTP error responses, making error handling more explicit and robust. Note that this behavior is affected by retries and the circuit breaker - full details are explained in the [Error Handling documentation](./docs/errorhandling.md).
143
+
144
+ ## Documentation
145
+
146
+ | Topic | Description |
147
+ | --------------------------------------------- | ------------------------------------------------------------------------- |
148
+ | **[Complete Documentation](./docs/index.md)** | **Start here** - Documentation index and overview |
149
+ | **[API Reference](./docs/api.md)** | Complete API documentation and configuration options |
150
+ | **[Plugin Architecture](./docs/plugins.md)** | Plugin lifecycle, custom plugin authoring, and integration patterns |
151
+ | **[Deduplication](./docs/deduplication.md)** | How deduplication works, hash config, optional TTL cleanup, limitations |
152
+ | **[Error Handling](./docs/errorhandling.md)** | Strategies for managing errors, including `throwOnHttpError` |
153
+ | **[Advanced Features](./docs/advanced.md)** | Per-request overrides, pending requests, circuit breakers, custom errors |
154
+ | **[Hooks & Transformation](./docs/hooks.md)** | Lifecycle hooks, authentication, logging, request/response transformation |
155
+ | **[Usage Examples](./docs/examples.md)** | Real-world patterns: REST clients, GraphQL, file uploads, microservices |
156
+ | **[Compatibility](./docs/compatibility.md)** | Browser/Node.js support, polyfills, framework integration |
157
+
158
+ ## Environment Requirements
159
+
160
+ `ffetch` works best with native `AbortSignal.any` support:
161
+
162
+ - **Node.js 20.6+** (native `AbortSignal.any`)
163
+ - **Modern browsers with `AbortSignal.any`** (for example: Chrome 117+, Firefox 117+, Safari 17+, Edge 117+)
164
+
165
+ If your environment does not support `AbortSignal.any` (Node.js < 20.6, older browsers), you can still use ffetch by installing an `AbortSignal.any` polyfill. `AbortSignal.timeout` is optional because ffetch includes an internal timeout fallback. See the [compatibility guide](./docs/compatibility.md) for instructions.
166
+
167
+ **Custom fetch support:**
168
+ You can pass any fetch-compatible implementation (native fetch, node-fetch, undici, SvelteKit, Next.js, Nuxt, or a polyfill) via the `fetchHandler` option. This makes ffetch fully compatible with SSR, edge, metaframework environments, custom backends, and test runners.
169
+
170
+ #### "AbortSignal.any is not a function"
171
+
172
+ Solution: Install a polyfill for `AbortSignal.any`
173
+
174
+ ```bash
175
+ npm install abort-controller-x
176
+ ```
177
+
178
+ ## CDN Usage
179
+
180
+ ```html
181
+ <script type="module">
182
+ import { createClient } from 'https://unpkg.com/@fetchkit/ffetch/dist/index.min.js'
183
+
184
+ const api = createClient({ timeout: 5000 })
185
+ const data = await api('/api/data').then((r) => r.json())
186
+ </script>
187
+ ```
188
+
189
+ ## Deduplication Limitations
190
+
191
+ - Deduplication is **off** by default. Enable it via `plugins: [dedupePlugin()]`.
192
+ - The default hash function is `dedupeRequestHash`, which handles common body types and skips deduplication for streams and FormData.
193
+ - Optional stale-entry cleanup: `dedupePlugin({ ttl, sweepInterval })` enables map-entry eviction. TTL eviction only removes dedupe keys; it does not reject already in-flight promises.
194
+ - **Stream bodies** (`ReadableStream`, `FormData`): Deduplication is skipped for requests with these body types, as they cannot be reliably hashed or replayed.
195
+ - **Non-idempotent requests**: Use deduplication with caution for non-idempotent methods (e.g., POST), as it may suppress multiple intended requests.
196
+ - **Custom hash function**: Ensure your hash function uniquely identifies requests to avoid accidental deduplication.
197
+
198
+ See [deduplication.md](./docs/deduplication.md) for full details.
199
+
200
+ ## Fetch vs. Axios vs. `ffetch`
201
+
202
+ | Feature | Native Fetch | Axios | ffetch |
203
+ | -------------------- | ------------------------- | -------------------- | -------------------------------------------------------------------------------------- |
204
+ | Timeouts | ❌ Manual AbortController | Built-in | ✅ Built-in with fallbacks |
205
+ | Retries | ❌ Manual implementation | Manual or plugins | ✅ Smart exponential backoff |
206
+ | Plugin Architecture | ❌ Not available | ⚠️ Interceptors only | ✅ First-class plugin pipeline (optional built-in + custom plugins) |
207
+ | Circuit Breaker | ❌ Not available | ❌ Manual or plugins | ✅ Automatic failure protection |
208
+ | Deduplication | ❌ Not available | ❌ Not available | ✅ Automatic deduplication of in-flight identical requests |
209
+ | Request Monitoring | ❌ Manual tracking | Manual tracking | ✅ Built-in pending requests |
210
+ | Error Types | Generic errors | ⚠️ HTTP errors only | ✅ Specific error classes |
211
+ | TypeScript | ⚠️ Basic types | ⚠️ Basic types | ✅ Full type safety |
212
+ | Hooks/Middleware | Not available | Interceptors | ✅ Comprehensive lifecycle hooks |
213
+ | Bundle Size | ✅ Native (0kb) | ❌ ~13kb minified | ✅ ~3kb minified |
214
+ | Modern APIs | Web standards | ❌ XMLHttpRequest | ✅ Fetch + modern features |
215
+ | Custom Fetch Support | ❌ No (global only) | ❌ No | ✅ Yes (wrap any fetch-compatible implementation, including framework or custom fetch) |
216
+
217
+ ## Join the Community
218
+
219
+ Got questions, want to discuss features, or share examples? Join the **Fetch-Kit Discord server**:
220
+
221
+ [![Discord](https://img.shields.io/badge/Discord-Join_Fetch--Kit-7289DA?logo=discord&logoColor=white)](https://discord.gg/sdyPBPCDUg)
222
+
223
+ ## Contributing
224
+
225
+ - **Issues**: [GitHub Issues](https://github.com/fetch-kit/ffetch/issues)
226
+ - **Pull Requests**: [GitHub PRs](https://github.com/fetch-kit/ffetch/pulls)
227
+ - **Documentation**: Found in `./docs/` - PRs welcome!
228
+
229
+ ## License
230
+
231
+ MIT © 2025 gkoos
package/dist/index.cjs CHANGED
@@ -97,7 +97,29 @@ var defaultDelay = (ctx) => {
97
97
  }
98
98
  return 2 ** ctx.attempt * 200 + Math.random() * 100;
99
99
  };
100
- async function retry(fn, retries, delay, shouldRetry2 = () => true, request) {
100
+ function waitForRetryDelay(ms, signal) {
101
+ if (ms <= 0) return Promise.resolve();
102
+ return new Promise((resolve) => {
103
+ if (!signal) {
104
+ setTimeout(resolve, ms);
105
+ return;
106
+ }
107
+ if (signal.aborted) {
108
+ resolve();
109
+ return;
110
+ }
111
+ const onAbort = () => {
112
+ clearTimeout(timer);
113
+ resolve();
114
+ };
115
+ const timer = setTimeout(() => {
116
+ signal.removeEventListener("abort", onAbort);
117
+ resolve();
118
+ }, ms);
119
+ signal.addEventListener("abort", onAbort, { once: true });
120
+ });
121
+ }
122
+ async function retry(fn, retries, delay, shouldRetry2 = () => true, request, signal) {
101
123
  let lastErr;
102
124
  let lastRes;
103
125
  for (let i = 0; i <= retries; i++) {
@@ -113,7 +135,7 @@ async function retry(fn, retries, delay, shouldRetry2 = () => true, request) {
113
135
  ctx.error = void 0;
114
136
  if (i < retries && shouldRetry2(ctx)) {
115
137
  const wait = typeof delay === "function" ? delay(ctx) : delay;
116
- await new Promise((r) => setTimeout(r, wait));
138
+ await waitForRetryDelay(wait, signal);
117
139
  continue;
118
140
  }
119
141
  return lastRes;
@@ -122,7 +144,7 @@ async function retry(fn, retries, delay, shouldRetry2 = () => true, request) {
122
144
  ctx.error = err;
123
145
  if (i === retries || !shouldRetry2(ctx)) throw err;
124
146
  const wait = typeof delay === "function" ? delay(ctx) : delay;
125
- await new Promise((r) => setTimeout(r, wait));
147
+ await waitForRetryDelay(wait, signal);
126
148
  }
127
149
  }
128
150
  throw lastErr;
@@ -345,7 +367,8 @@ function createClient(opts = {}) {
345
367
  effectiveRetries,
346
368
  effectiveRetryDelay,
347
369
  shouldRetryWithHook,
348
- request
370
+ request,
371
+ combinedSignal
349
372
  );
350
373
  if (effectiveHooks.transformResponse) {
351
374
  res = await effectiveHooks.transformResponse(res, request);
@@ -1 +1 @@
1
- {"version":3,"sources":["../src/error.ts","../src/index.ts","../src/retry.ts","../src/should-retry.ts","../src/client.ts"],"sourcesContent":["// Base error class to reduce duplication\nclass BaseError extends Error {\n public cause?: unknown\n constructor(name: string, message: string, cause?: unknown) {\n super(message)\n this.name = name\n if (cause !== undefined) this.cause = cause\n }\n}\n\nexport class TimeoutError extends BaseError {\n constructor(message = 'Request timed out', cause?: unknown) {\n super('TimeoutError', message, cause)\n }\n}\n\nexport class CircuitOpenError extends BaseError {\n constructor(message = 'Circuit is open', cause?: unknown) {\n super('CircuitOpenError', message, cause)\n }\n}\n\nexport class AbortError extends BaseError {\n constructor(message = 'Request was aborted', cause?: unknown) {\n super('AbortError', message, cause)\n }\n}\n\nexport class RetryLimitError extends BaseError {\n constructor(message = 'Retry limit reached', cause?: unknown) {\n super('RetryLimitError', message, cause)\n }\n}\n\nexport class NetworkError extends BaseError {\n constructor(message = 'Network error occurred', cause?: unknown) {\n super('NetworkError', message, cause)\n }\n}\n\nexport class HttpError extends BaseError {\n constructor(message = 'HTTP error occurred', cause?: unknown) {\n super('HttpError', message, cause)\n }\n}\n","export type { FFetch, FFetchOptions } from './types'\r\nexport type { Hooks } from './hooks'\r\nexport type {\r\n ClientPlugin,\r\n PluginRequestContext,\r\n PluginDispatch,\r\n PluginSetupContext,\r\n} from './plugins'\r\n\r\nexport { createClient } from './client'\r\n\r\nexport {\r\n TimeoutError,\r\n CircuitOpenError,\r\n AbortError,\r\n RetryLimitError,\r\n NetworkError,\r\n} from './error'\r\n","import type { RetryContext } from './types.js'\n\nexport type RetryDelay = number | ((ctx: RetryContext) => number)\n\nexport const defaultDelay: RetryDelay = (ctx) => {\n const retryAfter = ctx.response?.headers.get('Retry-After')\n if (retryAfter) {\n const seconds = parseInt(retryAfter, 10)\n if (!isNaN(seconds)) return seconds * 1000\n const date = Date.parse(retryAfter)\n if (!isNaN(date)) return Math.max(0, date - Date.now())\n }\n return 2 ** ctx.attempt * 200 + Math.random() * 100\n}\n\nexport async function retry(\n fn: () => Promise<Response>,\n retries: number,\n delay: RetryDelay,\n shouldRetry: (ctx: RetryContext) => boolean = () => true,\n request: Request\n): Promise<Response> {\n let lastErr: unknown\n let lastRes: Response | undefined\n\n for (let i = 0; i <= retries; i++) {\n const ctx: RetryContext = {\n attempt: i + 1,\n request,\n response: lastRes,\n error: lastErr,\n }\n try {\n lastRes = await fn()\n ctx.response = lastRes\n ctx.error = undefined\n if (i < retries && shouldRetry(ctx)) {\n const wait = typeof delay === 'function' ? delay(ctx) : delay\n await new Promise((r) => setTimeout(r, wait))\n continue\n }\n return lastRes\n } catch (err) {\n lastErr = err\n ctx.error = err\n if (i === retries || !shouldRetry(ctx)) throw err\n const wait = typeof delay === 'function' ? delay(ctx) : delay\n await new Promise((r) => setTimeout(r, wait))\n }\n }\n throw lastErr\n}\n","import { AbortError, CircuitOpenError, TimeoutError } from './error.js'\nimport type { RetryContext } from './types.js'\n\nexport function shouldRetry(ctx: RetryContext): boolean {\n const { error, response } = ctx\n if (\n error instanceof AbortError ||\n error instanceof CircuitOpenError ||\n error instanceof TimeoutError\n )\n return false\n if (!response) return true // network error\n return response.status >= 500 || response.status === 429\n}\n","import type {\r\n FFetchOptions,\r\n FFetch,\r\n FFetchRequestInit,\r\n PendingRequest,\r\n} from './types.js'\r\nimport { retry, defaultDelay } from './retry.js'\r\nimport { shouldRetry as defaultShouldRetry } from './should-retry.js'\r\nimport {\r\n type PluginDispatch,\r\n type PluginRequestContext,\r\n type PluginExtensions,\r\n type ClientPlugin,\r\n type PluginExtensionBase,\r\n} from './plugins.js'\r\nimport {\r\n TimeoutError,\r\n AbortError,\r\n RetryLimitError,\r\n NetworkError,\r\n} from './error.js'\r\n\r\nexport function createClient<\r\n TPlugins extends\r\n readonly ClientPlugin<PluginExtensionBase>[] = readonly ClientPlugin<PluginExtensionBase>[],\r\n>(\r\n opts: FFetchOptions<TPlugins> = {} as FFetchOptions<TPlugins>\r\n): FFetch<PluginExtensions<TPlugins>> {\r\n const {\r\n timeout: clientDefaultTimeout = 5_000,\r\n retries: clientDefaultRetries = 0,\r\n retryDelay: clientDefaultRetryDelay = defaultDelay,\r\n shouldRetry: clientDefaultShouldRetry = defaultShouldRetry,\r\n hooks: clientDefaultHooks = {},\r\n fetchHandler,\r\n plugins: inputPlugins = [] as unknown as TPlugins,\r\n } = opts\r\n\r\n const extensionDescriptors: PropertyDescriptorMap = Object.create(null)\r\n\r\n const plugins = inputPlugins\r\n .map((plugin, index) => ({ plugin, index }))\r\n .sort((a, b) => {\r\n const aOrder = a.plugin.order ?? 0\r\n const bOrder = b.plugin.order ?? 0\r\n if (aOrder !== bOrder) return aOrder - bOrder\r\n return a.index - b.index\r\n })\r\n .map((entry) => entry.plugin)\r\n\r\n for (const plugin of plugins) {\r\n plugin.setup?.({\r\n defineExtension: (key, descriptor) => {\r\n const propertyKey = key as PropertyKey\r\n if (propertyKey in extensionDescriptors) {\r\n throw new Error(\r\n `Plugin extension collision for property \"${String(propertyKey)}\"`\r\n )\r\n }\r\n if ('get' in descriptor) {\r\n extensionDescriptors[propertyKey] = {\r\n get: descriptor.get,\r\n enumerable: descriptor.enumerable ?? true,\r\n configurable: false,\r\n }\r\n return\r\n }\r\n extensionDescriptors[propertyKey] = {\r\n value: descriptor.value,\r\n writable: false,\r\n enumerable: descriptor.enumerable ?? true,\r\n configurable: false,\r\n }\r\n },\r\n })\r\n }\r\n\r\n const pendingRequests: PendingRequest[] = []\r\n\r\n // Helper to abort all pending requests\r\n function abortAll() {\r\n for (const entry of pendingRequests) {\r\n entry.controller?.abort()\r\n }\r\n }\r\n\r\n const client = async (\r\n input: RequestInfo | URL,\r\n init: FFetchRequestInit = {}\r\n ) => {\r\n let request = new Request(input, init)\r\n\r\n // Merge hooks: per-request hooks override client hooks, but fallback to client hooks\r\n const effectiveHooks = { ...clientDefaultHooks, ...(init.hooks || {}) }\r\n if (effectiveHooks.transformRequest) {\r\n request = await effectiveHooks.transformRequest(request)\r\n }\r\n await effectiveHooks.before?.(request)\r\n\r\n // Determine retry config (per-request overrides client default)\r\n const effectiveRetries = init.retries ?? clientDefaultRetries\r\n const effectiveRetryDelay =\r\n typeof init.retryDelay !== 'undefined'\r\n ? init.retryDelay\r\n : clientDefaultRetryDelay\r\n const effectiveShouldRetry = init.shouldRetry ?? clientDefaultShouldRetry\r\n\r\n // AbortSignal.timeout/any logic\r\n const effectiveTimeout = init.timeout ?? clientDefaultTimeout\r\n const userSignal = init.signal\r\n const transformedSignal = request.signal\r\n\r\n const pluginContext: PluginRequestContext = {\r\n request,\r\n init,\r\n state: Object.create(null),\r\n metadata: {\r\n startedAt: Date.now(),\r\n timeoutMs: effectiveTimeout,\r\n signals: {\r\n user:\r\n userSignal === undefined || userSignal === null\r\n ? undefined\r\n : userSignal,\r\n transformed: transformedSignal,\r\n },\r\n retry: {\r\n configuredRetries: effectiveRetries,\r\n configuredDelay: effectiveRetryDelay,\r\n attempt: 0,\r\n },\r\n },\r\n }\r\n\r\n for (const plugin of plugins) {\r\n await plugin.preRequest?.(pluginContext)\r\n }\r\n\r\n // Determine throwOnHttpError (per-request overrides client default)\r\n const effectiveThrowOnHttpError =\r\n typeof init.throwOnHttpError !== 'undefined'\r\n ? init.throwOnHttpError\r\n : (opts.throwOnHttpError ?? false)\r\n\r\n // Create timeout signal (manual implementation if AbortSignal.timeout not available)\r\n function createTimeoutSignal(timeout: number): AbortSignal {\r\n if (typeof AbortSignal?.timeout === 'function') {\r\n return AbortSignal.timeout(timeout)\r\n }\r\n const controller = new AbortController()\r\n const timeoutId = setTimeout(() => controller.abort(), timeout)\r\n controller.signal.addEventListener(\r\n 'abort',\r\n () => clearTimeout(timeoutId),\r\n { once: true }\r\n )\r\n return controller.signal\r\n }\r\n\r\n let timeoutSignal: AbortSignal | undefined = undefined\r\n let combinedSignal: AbortSignal | undefined = undefined\r\n let controller: AbortController | undefined = undefined\r\n\r\n if (effectiveTimeout > 0) {\r\n timeoutSignal = createTimeoutSignal(effectiveTimeout)\r\n pluginContext.metadata.signals.timeout = timeoutSignal\r\n }\r\n\r\n const signals: AbortSignal[] = []\r\n if (userSignal) signals.push(userSignal)\r\n if (transformedSignal && transformedSignal !== userSignal) {\r\n signals.push(transformedSignal)\r\n }\r\n if (timeoutSignal) signals.push(timeoutSignal)\r\n\r\n if (signals.length === 1) {\r\n combinedSignal = signals[0]\r\n controller = new AbortController()\r\n } else {\r\n if (typeof AbortSignal.any !== 'function') {\r\n throw new Error(\r\n 'AbortSignal.any is required for combining multiple signals. Please install a polyfill for environments that do not support it.'\r\n )\r\n }\r\n combinedSignal = AbortSignal.any(signals)\r\n controller = new AbortController()\r\n }\r\n pluginContext.metadata.signals.combined = combinedSignal\r\n\r\n const retryWithHooks = async () => {\r\n let attempt = 0\r\n const shouldRetryWithHook = (ctx: import('./types').RetryContext) => {\r\n attempt = ctx.attempt\r\n pluginContext.metadata.retry.attempt = attempt\r\n pluginContext.metadata.retry.lastError = ctx.error\r\n pluginContext.metadata.retry.lastResponse = ctx.response\r\n const retrying = effectiveShouldRetry(ctx)\r\n pluginContext.metadata.retry.shouldRetryResult = retrying\r\n if (retrying && attempt <= effectiveRetries) {\r\n effectiveHooks.onRetry?.(\r\n request,\r\n attempt - 1,\r\n ctx.error,\r\n ctx.response\r\n )\r\n }\r\n return retrying\r\n }\r\n\r\n let lastResponse: Response | undefined = undefined\r\n try {\r\n let res = await retry(\r\n async () => {\r\n if (userSignal?.aborted) {\r\n effectiveHooks.onAbort?.(request)\r\n throw new AbortError('Request was aborted by user')\r\n }\r\n if (timeoutSignal?.aborted) {\r\n effectiveHooks.onTimeout?.(request)\r\n throw new TimeoutError('signal timed out')\r\n }\r\n if (typeof combinedSignal?.throwIfAborted === 'function') {\r\n combinedSignal.throwIfAborted()\r\n } else if (combinedSignal?.aborted) {\r\n if (userSignal?.aborted) {\r\n effectiveHooks.onAbort?.(request)\r\n throw new AbortError('Request was aborted by user')\r\n } else if (timeoutSignal?.aborted) {\r\n effectiveHooks.onTimeout?.(request)\r\n throw new TimeoutError('signal timed out')\r\n } else {\r\n throw new AbortError(\r\n 'Request was aborted',\r\n new DOMException('Aborted', 'AbortError')\r\n )\r\n }\r\n }\r\n const reqWithSignal = new Request(request, {\r\n signal: combinedSignal,\r\n })\r\n try {\r\n const handler = init.fetchHandler ?? fetchHandler ?? fetch\r\n const response = await handler(reqWithSignal)\r\n lastResponse = response\r\n pluginContext.metadata.retry.lastResponse = response\r\n return response\r\n } catch (err) {\r\n pluginContext.metadata.retry.lastError = err\r\n if (err instanceof DOMException && err.name === 'AbortError') {\r\n if (\r\n timeoutSignal?.aborted &&\r\n (!userSignal || !userSignal.aborted)\r\n ) {\r\n effectiveHooks.onTimeout?.(request)\r\n throw new TimeoutError('signal timed out', err)\r\n } else if (userSignal?.aborted) {\r\n effectiveHooks.onAbort?.(request)\r\n throw new AbortError('Request was aborted by user')\r\n } else {\r\n throw new AbortError(\r\n 'Request was aborted',\r\n new DOMException('Aborted', 'AbortError')\r\n )\r\n }\r\n } else if (\r\n err instanceof TypeError &&\r\n /NetworkError|network error|failed to fetch|lost connection|NetworkError when attempting to fetch resource/i.test(\r\n err.message\r\n )\r\n ) {\r\n throw new NetworkError(err.message, err)\r\n }\r\n throw err\r\n }\r\n },\r\n effectiveRetries,\r\n effectiveRetryDelay,\r\n shouldRetryWithHook,\r\n request\r\n )\r\n if (effectiveHooks.transformResponse) {\r\n res = await effectiveHooks.transformResponse(res, request)\r\n }\r\n await effectiveHooks.after?.(request, res)\r\n await effectiveHooks.onComplete?.(request, res, undefined)\r\n if (\r\n effectiveThrowOnHttpError &&\r\n ((res.status >= 400 && res.status < 500 && res.status !== 429) ||\r\n res.status >= 500 ||\r\n res.status === 429)\r\n ) {\r\n const { HttpError } = await import('./error.js')\r\n throw new HttpError(\r\n `HTTP error: ${res.status} ${res.statusText}`,\r\n res\r\n )\r\n }\r\n return res\r\n } catch (err: unknown) {\r\n pluginContext.metadata.retry.lastError = err\r\n if (lastResponse) {\r\n const resp = lastResponse as Response\r\n if (\r\n effectiveThrowOnHttpError &&\r\n ((resp.status >= 400 && resp.status < 500 && resp.status !== 429) ||\r\n resp.status >= 500 ||\r\n resp.status === 429)\r\n ) {\r\n const { HttpError } = await import('./error.js')\r\n throw new HttpError(\r\n `HTTP error: ${resp.status} ${resp.statusText}`,\r\n resp\r\n )\r\n }\r\n return resp\r\n }\r\n if (err instanceof TimeoutError) {\r\n await effectiveHooks.onTimeout?.(request)\r\n await effectiveHooks.onError?.(request, err)\r\n await effectiveHooks.onComplete?.(request, undefined, err)\r\n throw err\r\n }\r\n if (err instanceof AbortError) {\r\n await effectiveHooks.onAbort?.(request)\r\n await effectiveHooks.onError?.(request, err)\r\n await effectiveHooks.onComplete?.(request, undefined, err)\r\n throw err\r\n }\r\n if (err instanceof NetworkError) {\r\n await effectiveHooks.onError?.(request, err)\r\n await effectiveHooks.onComplete?.(request, undefined, err)\r\n throw err\r\n }\r\n const retryErr = new RetryLimitError(\r\n typeof err === 'object' &&\r\n err &&\r\n 'message' in err &&\r\n typeof (err as { message?: unknown }).message === 'string'\r\n ? (err as { message: string }).message\r\n : 'Retry limit reached',\r\n err\r\n )\r\n await effectiveHooks.onError?.(request, retryErr)\r\n await effectiveHooks.onComplete?.(request, undefined, retryErr)\r\n throw retryErr\r\n }\r\n }\r\n\r\n const baseDispatch: PluginDispatch = async () => retryWithHooks()\r\n\r\n let dispatch = baseDispatch\r\n for (let i = plugins.length - 1; i >= 0; i--) {\r\n const plugin = plugins[i]\r\n if (plugin.wrapDispatch) {\r\n dispatch = plugin.wrapDispatch(dispatch)\r\n }\r\n }\r\n\r\n const actualPromise = dispatch(pluginContext)\r\n .then(async (response) => {\r\n for (const plugin of plugins) {\r\n await plugin.onSuccess?.(pluginContext, response)\r\n }\r\n return response\r\n })\r\n .catch(async (err: unknown) => {\r\n for (const plugin of plugins) {\r\n await plugin.onError?.(pluginContext, err)\r\n }\r\n throw err\r\n })\r\n\r\n const pendingEntry: PendingRequest = {\r\n promise: actualPromise,\r\n request,\r\n controller,\r\n }\r\n pendingRequests.push(pendingEntry)\r\n\r\n return actualPromise.finally(async () => {\r\n for (const plugin of plugins) {\r\n await plugin.onFinally?.(pluginContext)\r\n }\r\n\r\n const index = pendingRequests.indexOf(pendingEntry)\r\n if (index > -1) {\r\n pendingRequests.splice(index, 1)\r\n }\r\n })\r\n }\r\n\r\n Object.defineProperty(client, 'pendingRequests', {\r\n get() {\r\n return pendingRequests\r\n },\r\n enumerable: false,\r\n configurable: false,\r\n })\r\n\r\n Object.defineProperty(client, 'abortAll', {\r\n value: abortAll,\r\n writable: false,\r\n enumerable: false,\r\n configurable: false,\r\n })\r\n\r\n Object.defineProperties(client, extensionDescriptors)\r\n\r\n return client as FFetch<PluginExtensions<TPlugins>>\r\n}\r\n"],"mappings":"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","names":["shouldRetry","controller","HttpError"]}
1
+ {"version":3,"sources":["../src/error.ts","../src/index.ts","../src/retry.ts","../src/should-retry.ts","../src/client.ts"],"sourcesContent":["// Base error class to reduce duplication\nclass BaseError extends Error {\n public cause?: unknown\n constructor(name: string, message: string, cause?: unknown) {\n super(message)\n this.name = name\n if (cause !== undefined) this.cause = cause\n }\n}\n\nexport class TimeoutError extends BaseError {\n constructor(message = 'Request timed out', cause?: unknown) {\n super('TimeoutError', message, cause)\n }\n}\n\nexport class CircuitOpenError extends BaseError {\n constructor(message = 'Circuit is open', cause?: unknown) {\n super('CircuitOpenError', message, cause)\n }\n}\n\nexport class AbortError extends BaseError {\n constructor(message = 'Request was aborted', cause?: unknown) {\n super('AbortError', message, cause)\n }\n}\n\nexport class RetryLimitError extends BaseError {\n constructor(message = 'Retry limit reached', cause?: unknown) {\n super('RetryLimitError', message, cause)\n }\n}\n\nexport class NetworkError extends BaseError {\n constructor(message = 'Network error occurred', cause?: unknown) {\n super('NetworkError', message, cause)\n }\n}\n\nexport class HttpError extends BaseError {\n constructor(message = 'HTTP error occurred', cause?: unknown) {\n super('HttpError', message, cause)\n }\n}\n","export type { FFetch, FFetchOptions } from './types'\r\nexport type { Hooks } from './hooks'\r\nexport type {\r\n ClientPlugin,\r\n PluginRequestContext,\r\n PluginDispatch,\r\n PluginSetupContext,\r\n} from './plugins'\r\n\r\nexport { createClient } from './client'\r\n\r\nexport {\r\n TimeoutError,\r\n CircuitOpenError,\r\n AbortError,\r\n RetryLimitError,\r\n NetworkError,\r\n} from './error'\r\n","import type { RetryContext } from './types.js'\n\nexport type RetryDelay = number | ((ctx: RetryContext) => number)\n\nexport const defaultDelay: RetryDelay = (ctx) => {\n const retryAfter = ctx.response?.headers.get('Retry-After')\n if (retryAfter) {\n const seconds = parseInt(retryAfter, 10)\n if (!isNaN(seconds)) return seconds * 1000\n const date = Date.parse(retryAfter)\n if (!isNaN(date)) return Math.max(0, date - Date.now())\n }\n return 2 ** ctx.attempt * 200 + Math.random() * 100\n}\n\nfunction waitForRetryDelay(ms: number, signal?: AbortSignal): Promise<void> {\n if (ms <= 0) return Promise.resolve()\n return new Promise((resolve) => {\n if (!signal) {\n setTimeout(resolve, ms)\n return\n }\n\n if (signal.aborted) {\n resolve()\n return\n }\n\n const onAbort = () => {\n clearTimeout(timer)\n resolve()\n }\n\n const timer = setTimeout(() => {\n signal.removeEventListener('abort', onAbort)\n resolve()\n }, ms)\n\n signal.addEventListener('abort', onAbort, { once: true })\n })\n}\n\nexport async function retry(\n fn: () => Promise<Response>,\n retries: number,\n delay: RetryDelay,\n shouldRetry: (ctx: RetryContext) => boolean = () => true,\n request: Request,\n signal?: AbortSignal\n): Promise<Response> {\n let lastErr: unknown\n let lastRes: Response | undefined\n\n for (let i = 0; i <= retries; i++) {\n const ctx: RetryContext = {\n attempt: i + 1,\n request,\n response: lastRes,\n error: lastErr,\n }\n try {\n lastRes = await fn()\n ctx.response = lastRes\n ctx.error = undefined\n if (i < retries && shouldRetry(ctx)) {\n const wait = typeof delay === 'function' ? delay(ctx) : delay\n await waitForRetryDelay(wait, signal)\n continue\n }\n return lastRes\n } catch (err) {\n lastErr = err\n ctx.error = err\n if (i === retries || !shouldRetry(ctx)) throw err\n const wait = typeof delay === 'function' ? delay(ctx) : delay\n await waitForRetryDelay(wait, signal)\n }\n }\n throw lastErr\n}\n","import { AbortError, CircuitOpenError, TimeoutError } from './error.js'\nimport type { RetryContext } from './types.js'\n\nexport function shouldRetry(ctx: RetryContext): boolean {\n const { error, response } = ctx\n if (\n error instanceof AbortError ||\n error instanceof CircuitOpenError ||\n error instanceof TimeoutError\n )\n return false\n if (!response) return true // network error\n return response.status >= 500 || response.status === 429\n}\n","import type {\n FFetchOptions,\n FFetch,\n FFetchRequestInit,\n PendingRequest,\n} from './types.js'\nimport { retry, defaultDelay } from './retry.js'\nimport { shouldRetry as defaultShouldRetry } from './should-retry.js'\nimport {\n type PluginDispatch,\n type PluginRequestContext,\n type PluginExtensions,\n type ClientPlugin,\n type PluginExtensionBase,\n} from './plugins.js'\nimport {\n TimeoutError,\n AbortError,\n RetryLimitError,\n NetworkError,\n} from './error.js'\n\nexport function createClient<\n TPlugins extends\n readonly ClientPlugin<PluginExtensionBase>[] = readonly ClientPlugin<PluginExtensionBase>[],\n>(\n opts: FFetchOptions<TPlugins> = {} as FFetchOptions<TPlugins>\n): FFetch<PluginExtensions<TPlugins>> {\n const {\n timeout: clientDefaultTimeout = 5_000,\n retries: clientDefaultRetries = 0,\n retryDelay: clientDefaultRetryDelay = defaultDelay,\n shouldRetry: clientDefaultShouldRetry = defaultShouldRetry,\n hooks: clientDefaultHooks = {},\n fetchHandler,\n plugins: inputPlugins = [] as unknown as TPlugins,\n } = opts\n\n const extensionDescriptors: PropertyDescriptorMap = Object.create(null)\n\n const plugins = inputPlugins\n .map((plugin, index) => ({ plugin, index }))\n .sort((a, b) => {\n const aOrder = a.plugin.order ?? 0\n const bOrder = b.plugin.order ?? 0\n if (aOrder !== bOrder) return aOrder - bOrder\n return a.index - b.index\n })\n .map((entry) => entry.plugin)\n\n for (const plugin of plugins) {\n plugin.setup?.({\n defineExtension: (key, descriptor) => {\n const propertyKey = key as PropertyKey\n if (propertyKey in extensionDescriptors) {\n throw new Error(\n `Plugin extension collision for property \"${String(propertyKey)}\"`\n )\n }\n if ('get' in descriptor) {\n extensionDescriptors[propertyKey] = {\n get: descriptor.get,\n enumerable: descriptor.enumerable ?? true,\n configurable: false,\n }\n return\n }\n extensionDescriptors[propertyKey] = {\n value: descriptor.value,\n writable: false,\n enumerable: descriptor.enumerable ?? true,\n configurable: false,\n }\n },\n })\n }\n\n const pendingRequests: PendingRequest[] = []\n\n // Helper to abort all pending requests\n function abortAll() {\n for (const entry of pendingRequests) {\n entry.controller?.abort()\n }\n }\n\n const client = async (\n input: RequestInfo | URL,\n init: FFetchRequestInit = {}\n ) => {\n let request = new Request(input, init)\n\n // Merge hooks: per-request hooks override client hooks, but fallback to client hooks\n const effectiveHooks = { ...clientDefaultHooks, ...(init.hooks || {}) }\n if (effectiveHooks.transformRequest) {\n request = await effectiveHooks.transformRequest(request)\n }\n await effectiveHooks.before?.(request)\n\n // Determine retry config (per-request overrides client default)\n const effectiveRetries = init.retries ?? clientDefaultRetries\n const effectiveRetryDelay =\n typeof init.retryDelay !== 'undefined'\n ? init.retryDelay\n : clientDefaultRetryDelay\n const effectiveShouldRetry = init.shouldRetry ?? clientDefaultShouldRetry\n\n // AbortSignal.timeout/any logic\n const effectiveTimeout = init.timeout ?? clientDefaultTimeout\n const userSignal = init.signal\n const transformedSignal = request.signal\n\n const pluginContext: PluginRequestContext = {\n request,\n init,\n state: Object.create(null),\n metadata: {\n startedAt: Date.now(),\n timeoutMs: effectiveTimeout,\n signals: {\n user:\n userSignal === undefined || userSignal === null\n ? undefined\n : userSignal,\n transformed: transformedSignal,\n },\n retry: {\n configuredRetries: effectiveRetries,\n configuredDelay: effectiveRetryDelay,\n attempt: 0,\n },\n },\n }\n\n for (const plugin of plugins) {\n await plugin.preRequest?.(pluginContext)\n }\n\n // Determine throwOnHttpError (per-request overrides client default)\n const effectiveThrowOnHttpError =\n typeof init.throwOnHttpError !== 'undefined'\n ? init.throwOnHttpError\n : (opts.throwOnHttpError ?? false)\n\n // Create timeout signal (manual implementation if AbortSignal.timeout not available)\n function createTimeoutSignal(timeout: number): AbortSignal {\n if (typeof AbortSignal?.timeout === 'function') {\n return AbortSignal.timeout(timeout)\n }\n const controller = new AbortController()\n const timeoutId = setTimeout(() => controller.abort(), timeout)\n controller.signal.addEventListener(\n 'abort',\n () => clearTimeout(timeoutId),\n { once: true }\n )\n return controller.signal\n }\n\n let timeoutSignal: AbortSignal | undefined = undefined\n let combinedSignal: AbortSignal | undefined = undefined\n let controller: AbortController | undefined = undefined\n\n if (effectiveTimeout > 0) {\n timeoutSignal = createTimeoutSignal(effectiveTimeout)\n pluginContext.metadata.signals.timeout = timeoutSignal\n }\n\n const signals: AbortSignal[] = []\n if (userSignal) signals.push(userSignal)\n if (transformedSignal && transformedSignal !== userSignal) {\n signals.push(transformedSignal)\n }\n if (timeoutSignal) signals.push(timeoutSignal)\n\n if (signals.length === 1) {\n combinedSignal = signals[0]\n controller = new AbortController()\n } else {\n if (typeof AbortSignal.any !== 'function') {\n throw new Error(\n 'AbortSignal.any is required for combining multiple signals. Please install a polyfill for environments that do not support it.'\n )\n }\n combinedSignal = AbortSignal.any(signals)\n controller = new AbortController()\n }\n pluginContext.metadata.signals.combined = combinedSignal\n\n const retryWithHooks = async () => {\n let attempt = 0\n const shouldRetryWithHook = (ctx: import('./types').RetryContext) => {\n attempt = ctx.attempt\n pluginContext.metadata.retry.attempt = attempt\n pluginContext.metadata.retry.lastError = ctx.error\n pluginContext.metadata.retry.lastResponse = ctx.response\n const retrying = effectiveShouldRetry(ctx)\n pluginContext.metadata.retry.shouldRetryResult = retrying\n if (retrying && attempt <= effectiveRetries) {\n effectiveHooks.onRetry?.(\n request,\n attempt - 1,\n ctx.error,\n ctx.response\n )\n }\n return retrying\n }\n\n let lastResponse: Response | undefined = undefined\n try {\n let res = await retry(\n async () => {\n if (userSignal?.aborted) {\n effectiveHooks.onAbort?.(request)\n throw new AbortError('Request was aborted by user')\n }\n if (timeoutSignal?.aborted) {\n effectiveHooks.onTimeout?.(request)\n throw new TimeoutError('signal timed out')\n }\n if (typeof combinedSignal?.throwIfAborted === 'function') {\n combinedSignal.throwIfAborted()\n } else if (combinedSignal?.aborted) {\n if (userSignal?.aborted) {\n effectiveHooks.onAbort?.(request)\n throw new AbortError('Request was aborted by user')\n } else if (timeoutSignal?.aborted) {\n effectiveHooks.onTimeout?.(request)\n throw new TimeoutError('signal timed out')\n } else {\n throw new AbortError(\n 'Request was aborted',\n new DOMException('Aborted', 'AbortError')\n )\n }\n }\n const reqWithSignal = new Request(request, {\n signal: combinedSignal,\n })\n try {\n const handler = init.fetchHandler ?? fetchHandler ?? fetch\n const response = await handler(reqWithSignal)\n lastResponse = response\n pluginContext.metadata.retry.lastResponse = response\n return response\n } catch (err) {\n pluginContext.metadata.retry.lastError = err\n if (err instanceof DOMException && err.name === 'AbortError') {\n if (\n timeoutSignal?.aborted &&\n (!userSignal || !userSignal.aborted)\n ) {\n effectiveHooks.onTimeout?.(request)\n throw new TimeoutError('signal timed out', err)\n } else if (userSignal?.aborted) {\n effectiveHooks.onAbort?.(request)\n throw new AbortError('Request was aborted by user')\n } else {\n throw new AbortError(\n 'Request was aborted',\n new DOMException('Aborted', 'AbortError')\n )\n }\n } else if (\n err instanceof TypeError &&\n /NetworkError|network error|failed to fetch|lost connection|NetworkError when attempting to fetch resource/i.test(\n err.message\n )\n ) {\n throw new NetworkError(err.message, err)\n }\n throw err\n }\n },\n effectiveRetries,\n effectiveRetryDelay,\n shouldRetryWithHook,\n request,\n combinedSignal\n )\n if (effectiveHooks.transformResponse) {\n res = await effectiveHooks.transformResponse(res, request)\n }\n await effectiveHooks.after?.(request, res)\n await effectiveHooks.onComplete?.(request, res, undefined)\n if (\n effectiveThrowOnHttpError &&\n ((res.status >= 400 && res.status < 500 && res.status !== 429) ||\n res.status >= 500 ||\n res.status === 429)\n ) {\n const { HttpError } = await import('./error.js')\n throw new HttpError(\n `HTTP error: ${res.status} ${res.statusText}`,\n res\n )\n }\n return res\n } catch (err: unknown) {\n pluginContext.metadata.retry.lastError = err\n if (lastResponse) {\n const resp = lastResponse as Response\n if (\n effectiveThrowOnHttpError &&\n ((resp.status >= 400 && resp.status < 500 && resp.status !== 429) ||\n resp.status >= 500 ||\n resp.status === 429)\n ) {\n const { HttpError } = await import('./error.js')\n throw new HttpError(\n `HTTP error: ${resp.status} ${resp.statusText}`,\n resp\n )\n }\n return resp\n }\n if (err instanceof TimeoutError) {\n await effectiveHooks.onTimeout?.(request)\n await effectiveHooks.onError?.(request, err)\n await effectiveHooks.onComplete?.(request, undefined, err)\n throw err\n }\n if (err instanceof AbortError) {\n await effectiveHooks.onAbort?.(request)\n await effectiveHooks.onError?.(request, err)\n await effectiveHooks.onComplete?.(request, undefined, err)\n throw err\n }\n if (err instanceof NetworkError) {\n await effectiveHooks.onError?.(request, err)\n await effectiveHooks.onComplete?.(request, undefined, err)\n throw err\n }\n const retryErr = new RetryLimitError(\n typeof err === 'object' &&\n err &&\n 'message' in err &&\n typeof (err as { message?: unknown }).message === 'string'\n ? (err as { message: string }).message\n : 'Retry limit reached',\n err\n )\n await effectiveHooks.onError?.(request, retryErr)\n await effectiveHooks.onComplete?.(request, undefined, retryErr)\n throw retryErr\n }\n }\n\n const baseDispatch: PluginDispatch = async () => retryWithHooks()\n\n let dispatch = baseDispatch\n for (let i = plugins.length - 1; i >= 0; i--) {\n const plugin = plugins[i]\n if (plugin.wrapDispatch) {\n dispatch = plugin.wrapDispatch(dispatch)\n }\n }\n\n const actualPromise = dispatch(pluginContext)\n .then(async (response) => {\n for (const plugin of plugins) {\n await plugin.onSuccess?.(pluginContext, response)\n }\n return response\n })\n .catch(async (err: unknown) => {\n for (const plugin of plugins) {\n await plugin.onError?.(pluginContext, err)\n }\n throw err\n })\n\n const pendingEntry: PendingRequest = {\n promise: actualPromise,\n request,\n controller,\n }\n pendingRequests.push(pendingEntry)\n\n return actualPromise.finally(async () => {\n for (const plugin of plugins) {\n await plugin.onFinally?.(pluginContext)\n }\n\n const index = pendingRequests.indexOf(pendingEntry)\n if (index > -1) {\n pendingRequests.splice(index, 1)\n }\n })\n }\n\n Object.defineProperty(client, 'pendingRequests', {\n get() {\n return pendingRequests\n },\n enumerable: false,\n configurable: false,\n })\n\n Object.defineProperty(client, 'abortAll', {\n value: abortAll,\n writable: false,\n enumerable: false,\n configurable: false,\n })\n\n Object.defineProperties(client, extensionDescriptors)\n\n return client as FFetch<PluginExtensions<TPlugins>>\n}\n"],"mappings":"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","names":["shouldRetry","controller","HttpError"]}
package/dist/index.js CHANGED
@@ -17,7 +17,29 @@ var defaultDelay = (ctx) => {
17
17
  }
18
18
  return 2 ** ctx.attempt * 200 + Math.random() * 100;
19
19
  };
20
- async function retry(fn, retries, delay, shouldRetry2 = () => true, request) {
20
+ function waitForRetryDelay(ms, signal) {
21
+ if (ms <= 0) return Promise.resolve();
22
+ return new Promise((resolve) => {
23
+ if (!signal) {
24
+ setTimeout(resolve, ms);
25
+ return;
26
+ }
27
+ if (signal.aborted) {
28
+ resolve();
29
+ return;
30
+ }
31
+ const onAbort = () => {
32
+ clearTimeout(timer);
33
+ resolve();
34
+ };
35
+ const timer = setTimeout(() => {
36
+ signal.removeEventListener("abort", onAbort);
37
+ resolve();
38
+ }, ms);
39
+ signal.addEventListener("abort", onAbort, { once: true });
40
+ });
41
+ }
42
+ async function retry(fn, retries, delay, shouldRetry2 = () => true, request, signal) {
21
43
  let lastErr;
22
44
  let lastRes;
23
45
  for (let i = 0; i <= retries; i++) {
@@ -33,7 +55,7 @@ async function retry(fn, retries, delay, shouldRetry2 = () => true, request) {
33
55
  ctx.error = void 0;
34
56
  if (i < retries && shouldRetry2(ctx)) {
35
57
  const wait = typeof delay === "function" ? delay(ctx) : delay;
36
- await new Promise((r) => setTimeout(r, wait));
58
+ await waitForRetryDelay(wait, signal);
37
59
  continue;
38
60
  }
39
61
  return lastRes;
@@ -42,7 +64,7 @@ async function retry(fn, retries, delay, shouldRetry2 = () => true, request) {
42
64
  ctx.error = err;
43
65
  if (i === retries || !shouldRetry2(ctx)) throw err;
44
66
  const wait = typeof delay === "function" ? delay(ctx) : delay;
45
- await new Promise((r) => setTimeout(r, wait));
67
+ await waitForRetryDelay(wait, signal);
46
68
  }
47
69
  }
48
70
  throw lastErr;
@@ -263,7 +285,8 @@ function createClient(opts = {}) {
263
285
  effectiveRetries,
264
286
  effectiveRetryDelay,
265
287
  shouldRetryWithHook,
266
- request
288
+ request,
289
+ combinedSignal
267
290
  );
268
291
  if (effectiveHooks.transformResponse) {
269
292
  res = await effectiveHooks.transformResponse(res, request);
package/dist/index.js.map CHANGED
@@ -1 +1 @@
1
- {"version":3,"sources":["../src/retry.ts","../src/should-retry.ts","../src/client.ts"],"sourcesContent":["import type { RetryContext } from './types.js'\n\nexport type RetryDelay = number | ((ctx: RetryContext) => number)\n\nexport const defaultDelay: RetryDelay = (ctx) => {\n const retryAfter = ctx.response?.headers.get('Retry-After')\n if (retryAfter) {\n const seconds = parseInt(retryAfter, 10)\n if (!isNaN(seconds)) return seconds * 1000\n const date = Date.parse(retryAfter)\n if (!isNaN(date)) return Math.max(0, date - Date.now())\n }\n return 2 ** ctx.attempt * 200 + Math.random() * 100\n}\n\nexport async function retry(\n fn: () => Promise<Response>,\n retries: number,\n delay: RetryDelay,\n shouldRetry: (ctx: RetryContext) => boolean = () => true,\n request: Request\n): Promise<Response> {\n let lastErr: unknown\n let lastRes: Response | undefined\n\n for (let i = 0; i <= retries; i++) {\n const ctx: RetryContext = {\n attempt: i + 1,\n request,\n response: lastRes,\n error: lastErr,\n }\n try {\n lastRes = await fn()\n ctx.response = lastRes\n ctx.error = undefined\n if (i < retries && shouldRetry(ctx)) {\n const wait = typeof delay === 'function' ? delay(ctx) : delay\n await new Promise((r) => setTimeout(r, wait))\n continue\n }\n return lastRes\n } catch (err) {\n lastErr = err\n ctx.error = err\n if (i === retries || !shouldRetry(ctx)) throw err\n const wait = typeof delay === 'function' ? delay(ctx) : delay\n await new Promise((r) => setTimeout(r, wait))\n }\n }\n throw lastErr\n}\n","import { AbortError, CircuitOpenError, TimeoutError } from './error.js'\nimport type { RetryContext } from './types.js'\n\nexport function shouldRetry(ctx: RetryContext): boolean {\n const { error, response } = ctx\n if (\n error instanceof AbortError ||\n error instanceof CircuitOpenError ||\n error instanceof TimeoutError\n )\n return false\n if (!response) return true // network error\n return response.status >= 500 || response.status === 429\n}\n","import type {\r\n FFetchOptions,\r\n FFetch,\r\n FFetchRequestInit,\r\n PendingRequest,\r\n} from './types.js'\r\nimport { retry, defaultDelay } from './retry.js'\r\nimport { shouldRetry as defaultShouldRetry } from './should-retry.js'\r\nimport {\r\n type PluginDispatch,\r\n type PluginRequestContext,\r\n type PluginExtensions,\r\n type ClientPlugin,\r\n type PluginExtensionBase,\r\n} from './plugins.js'\r\nimport {\r\n TimeoutError,\r\n AbortError,\r\n RetryLimitError,\r\n NetworkError,\r\n} from './error.js'\r\n\r\nexport function createClient<\r\n TPlugins extends\r\n readonly ClientPlugin<PluginExtensionBase>[] = readonly ClientPlugin<PluginExtensionBase>[],\r\n>(\r\n opts: FFetchOptions<TPlugins> = {} as FFetchOptions<TPlugins>\r\n): FFetch<PluginExtensions<TPlugins>> {\r\n const {\r\n timeout: clientDefaultTimeout = 5_000,\r\n retries: clientDefaultRetries = 0,\r\n retryDelay: clientDefaultRetryDelay = defaultDelay,\r\n shouldRetry: clientDefaultShouldRetry = defaultShouldRetry,\r\n hooks: clientDefaultHooks = {},\r\n fetchHandler,\r\n plugins: inputPlugins = [] as unknown as TPlugins,\r\n } = opts\r\n\r\n const extensionDescriptors: PropertyDescriptorMap = Object.create(null)\r\n\r\n const plugins = inputPlugins\r\n .map((plugin, index) => ({ plugin, index }))\r\n .sort((a, b) => {\r\n const aOrder = a.plugin.order ?? 0\r\n const bOrder = b.plugin.order ?? 0\r\n if (aOrder !== bOrder) return aOrder - bOrder\r\n return a.index - b.index\r\n })\r\n .map((entry) => entry.plugin)\r\n\r\n for (const plugin of plugins) {\r\n plugin.setup?.({\r\n defineExtension: (key, descriptor) => {\r\n const propertyKey = key as PropertyKey\r\n if (propertyKey in extensionDescriptors) {\r\n throw new Error(\r\n `Plugin extension collision for property \"${String(propertyKey)}\"`\r\n )\r\n }\r\n if ('get' in descriptor) {\r\n extensionDescriptors[propertyKey] = {\r\n get: descriptor.get,\r\n enumerable: descriptor.enumerable ?? true,\r\n configurable: false,\r\n }\r\n return\r\n }\r\n extensionDescriptors[propertyKey] = {\r\n value: descriptor.value,\r\n writable: false,\r\n enumerable: descriptor.enumerable ?? true,\r\n configurable: false,\r\n }\r\n },\r\n })\r\n }\r\n\r\n const pendingRequests: PendingRequest[] = []\r\n\r\n // Helper to abort all pending requests\r\n function abortAll() {\r\n for (const entry of pendingRequests) {\r\n entry.controller?.abort()\r\n }\r\n }\r\n\r\n const client = async (\r\n input: RequestInfo | URL,\r\n init: FFetchRequestInit = {}\r\n ) => {\r\n let request = new Request(input, init)\r\n\r\n // Merge hooks: per-request hooks override client hooks, but fallback to client hooks\r\n const effectiveHooks = { ...clientDefaultHooks, ...(init.hooks || {}) }\r\n if (effectiveHooks.transformRequest) {\r\n request = await effectiveHooks.transformRequest(request)\r\n }\r\n await effectiveHooks.before?.(request)\r\n\r\n // Determine retry config (per-request overrides client default)\r\n const effectiveRetries = init.retries ?? clientDefaultRetries\r\n const effectiveRetryDelay =\r\n typeof init.retryDelay !== 'undefined'\r\n ? init.retryDelay\r\n : clientDefaultRetryDelay\r\n const effectiveShouldRetry = init.shouldRetry ?? clientDefaultShouldRetry\r\n\r\n // AbortSignal.timeout/any logic\r\n const effectiveTimeout = init.timeout ?? clientDefaultTimeout\r\n const userSignal = init.signal\r\n const transformedSignal = request.signal\r\n\r\n const pluginContext: PluginRequestContext = {\r\n request,\r\n init,\r\n state: Object.create(null),\r\n metadata: {\r\n startedAt: Date.now(),\r\n timeoutMs: effectiveTimeout,\r\n signals: {\r\n user:\r\n userSignal === undefined || userSignal === null\r\n ? undefined\r\n : userSignal,\r\n transformed: transformedSignal,\r\n },\r\n retry: {\r\n configuredRetries: effectiveRetries,\r\n configuredDelay: effectiveRetryDelay,\r\n attempt: 0,\r\n },\r\n },\r\n }\r\n\r\n for (const plugin of plugins) {\r\n await plugin.preRequest?.(pluginContext)\r\n }\r\n\r\n // Determine throwOnHttpError (per-request overrides client default)\r\n const effectiveThrowOnHttpError =\r\n typeof init.throwOnHttpError !== 'undefined'\r\n ? init.throwOnHttpError\r\n : (opts.throwOnHttpError ?? false)\r\n\r\n // Create timeout signal (manual implementation if AbortSignal.timeout not available)\r\n function createTimeoutSignal(timeout: number): AbortSignal {\r\n if (typeof AbortSignal?.timeout === 'function') {\r\n return AbortSignal.timeout(timeout)\r\n }\r\n const controller = new AbortController()\r\n const timeoutId = setTimeout(() => controller.abort(), timeout)\r\n controller.signal.addEventListener(\r\n 'abort',\r\n () => clearTimeout(timeoutId),\r\n { once: true }\r\n )\r\n return controller.signal\r\n }\r\n\r\n let timeoutSignal: AbortSignal | undefined = undefined\r\n let combinedSignal: AbortSignal | undefined = undefined\r\n let controller: AbortController | undefined = undefined\r\n\r\n if (effectiveTimeout > 0) {\r\n timeoutSignal = createTimeoutSignal(effectiveTimeout)\r\n pluginContext.metadata.signals.timeout = timeoutSignal\r\n }\r\n\r\n const signals: AbortSignal[] = []\r\n if (userSignal) signals.push(userSignal)\r\n if (transformedSignal && transformedSignal !== userSignal) {\r\n signals.push(transformedSignal)\r\n }\r\n if (timeoutSignal) signals.push(timeoutSignal)\r\n\r\n if (signals.length === 1) {\r\n combinedSignal = signals[0]\r\n controller = new AbortController()\r\n } else {\r\n if (typeof AbortSignal.any !== 'function') {\r\n throw new Error(\r\n 'AbortSignal.any is required for combining multiple signals. Please install a polyfill for environments that do not support it.'\r\n )\r\n }\r\n combinedSignal = AbortSignal.any(signals)\r\n controller = new AbortController()\r\n }\r\n pluginContext.metadata.signals.combined = combinedSignal\r\n\r\n const retryWithHooks = async () => {\r\n let attempt = 0\r\n const shouldRetryWithHook = (ctx: import('./types').RetryContext) => {\r\n attempt = ctx.attempt\r\n pluginContext.metadata.retry.attempt = attempt\r\n pluginContext.metadata.retry.lastError = ctx.error\r\n pluginContext.metadata.retry.lastResponse = ctx.response\r\n const retrying = effectiveShouldRetry(ctx)\r\n pluginContext.metadata.retry.shouldRetryResult = retrying\r\n if (retrying && attempt <= effectiveRetries) {\r\n effectiveHooks.onRetry?.(\r\n request,\r\n attempt - 1,\r\n ctx.error,\r\n ctx.response\r\n )\r\n }\r\n return retrying\r\n }\r\n\r\n let lastResponse: Response | undefined = undefined\r\n try {\r\n let res = await retry(\r\n async () => {\r\n if (userSignal?.aborted) {\r\n effectiveHooks.onAbort?.(request)\r\n throw new AbortError('Request was aborted by user')\r\n }\r\n if (timeoutSignal?.aborted) {\r\n effectiveHooks.onTimeout?.(request)\r\n throw new TimeoutError('signal timed out')\r\n }\r\n if (typeof combinedSignal?.throwIfAborted === 'function') {\r\n combinedSignal.throwIfAborted()\r\n } else if (combinedSignal?.aborted) {\r\n if (userSignal?.aborted) {\r\n effectiveHooks.onAbort?.(request)\r\n throw new AbortError('Request was aborted by user')\r\n } else if (timeoutSignal?.aborted) {\r\n effectiveHooks.onTimeout?.(request)\r\n throw new TimeoutError('signal timed out')\r\n } else {\r\n throw new AbortError(\r\n 'Request was aborted',\r\n new DOMException('Aborted', 'AbortError')\r\n )\r\n }\r\n }\r\n const reqWithSignal = new Request(request, {\r\n signal: combinedSignal,\r\n })\r\n try {\r\n const handler = init.fetchHandler ?? fetchHandler ?? fetch\r\n const response = await handler(reqWithSignal)\r\n lastResponse = response\r\n pluginContext.metadata.retry.lastResponse = response\r\n return response\r\n } catch (err) {\r\n pluginContext.metadata.retry.lastError = err\r\n if (err instanceof DOMException && err.name === 'AbortError') {\r\n if (\r\n timeoutSignal?.aborted &&\r\n (!userSignal || !userSignal.aborted)\r\n ) {\r\n effectiveHooks.onTimeout?.(request)\r\n throw new TimeoutError('signal timed out', err)\r\n } else if (userSignal?.aborted) {\r\n effectiveHooks.onAbort?.(request)\r\n throw new AbortError('Request was aborted by user')\r\n } else {\r\n throw new AbortError(\r\n 'Request was aborted',\r\n new DOMException('Aborted', 'AbortError')\r\n )\r\n }\r\n } else if (\r\n err instanceof TypeError &&\r\n /NetworkError|network error|failed to fetch|lost connection|NetworkError when attempting to fetch resource/i.test(\r\n err.message\r\n )\r\n ) {\r\n throw new NetworkError(err.message, err)\r\n }\r\n throw err\r\n }\r\n },\r\n effectiveRetries,\r\n effectiveRetryDelay,\r\n shouldRetryWithHook,\r\n request\r\n )\r\n if (effectiveHooks.transformResponse) {\r\n res = await effectiveHooks.transformResponse(res, request)\r\n }\r\n await effectiveHooks.after?.(request, res)\r\n await effectiveHooks.onComplete?.(request, res, undefined)\r\n if (\r\n effectiveThrowOnHttpError &&\r\n ((res.status >= 400 && res.status < 500 && res.status !== 429) ||\r\n res.status >= 500 ||\r\n res.status === 429)\r\n ) {\r\n const { HttpError } = await import('./error.js')\r\n throw new HttpError(\r\n `HTTP error: ${res.status} ${res.statusText}`,\r\n res\r\n )\r\n }\r\n return res\r\n } catch (err: unknown) {\r\n pluginContext.metadata.retry.lastError = err\r\n if (lastResponse) {\r\n const resp = lastResponse as Response\r\n if (\r\n effectiveThrowOnHttpError &&\r\n ((resp.status >= 400 && resp.status < 500 && resp.status !== 429) ||\r\n resp.status >= 500 ||\r\n resp.status === 429)\r\n ) {\r\n const { HttpError } = await import('./error.js')\r\n throw new HttpError(\r\n `HTTP error: ${resp.status} ${resp.statusText}`,\r\n resp\r\n )\r\n }\r\n return resp\r\n }\r\n if (err instanceof TimeoutError) {\r\n await effectiveHooks.onTimeout?.(request)\r\n await effectiveHooks.onError?.(request, err)\r\n await effectiveHooks.onComplete?.(request, undefined, err)\r\n throw err\r\n }\r\n if (err instanceof AbortError) {\r\n await effectiveHooks.onAbort?.(request)\r\n await effectiveHooks.onError?.(request, err)\r\n await effectiveHooks.onComplete?.(request, undefined, err)\r\n throw err\r\n }\r\n if (err instanceof NetworkError) {\r\n await effectiveHooks.onError?.(request, err)\r\n await effectiveHooks.onComplete?.(request, undefined, err)\r\n throw err\r\n }\r\n const retryErr = new RetryLimitError(\r\n typeof err === 'object' &&\r\n err &&\r\n 'message' in err &&\r\n typeof (err as { message?: unknown }).message === 'string'\r\n ? (err as { message: string }).message\r\n : 'Retry limit reached',\r\n err\r\n )\r\n await effectiveHooks.onError?.(request, retryErr)\r\n await effectiveHooks.onComplete?.(request, undefined, retryErr)\r\n throw retryErr\r\n }\r\n }\r\n\r\n const baseDispatch: PluginDispatch = async () => retryWithHooks()\r\n\r\n let dispatch = baseDispatch\r\n for (let i = plugins.length - 1; i >= 0; i--) {\r\n const plugin = plugins[i]\r\n if (plugin.wrapDispatch) {\r\n dispatch = plugin.wrapDispatch(dispatch)\r\n }\r\n }\r\n\r\n const actualPromise = dispatch(pluginContext)\r\n .then(async (response) => {\r\n for (const plugin of plugins) {\r\n await plugin.onSuccess?.(pluginContext, response)\r\n }\r\n return response\r\n })\r\n .catch(async (err: unknown) => {\r\n for (const plugin of plugins) {\r\n await plugin.onError?.(pluginContext, err)\r\n }\r\n throw err\r\n })\r\n\r\n const pendingEntry: PendingRequest = {\r\n promise: actualPromise,\r\n request,\r\n controller,\r\n }\r\n pendingRequests.push(pendingEntry)\r\n\r\n return actualPromise.finally(async () => {\r\n for (const plugin of plugins) {\r\n await plugin.onFinally?.(pluginContext)\r\n }\r\n\r\n const index = pendingRequests.indexOf(pendingEntry)\r\n if (index > -1) {\r\n pendingRequests.splice(index, 1)\r\n }\r\n })\r\n }\r\n\r\n Object.defineProperty(client, 'pendingRequests', {\r\n get() {\r\n return pendingRequests\r\n },\r\n enumerable: false,\r\n configurable: false,\r\n })\r\n\r\n Object.defineProperty(client, 'abortAll', {\r\n value: abortAll,\r\n writable: false,\r\n enumerable: false,\r\n configurable: false,\r\n })\r\n\r\n Object.defineProperties(client, extensionDescriptors)\r\n\r\n return client as FFetch<PluginExtensions<TPlugins>>\r\n}\r\n"],"mappings":"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","names":["shouldRetry","controller"]}
1
+ {"version":3,"sources":["../src/retry.ts","../src/should-retry.ts","../src/client.ts"],"sourcesContent":["import type { RetryContext } from './types.js'\n\nexport type RetryDelay = number | ((ctx: RetryContext) => number)\n\nexport const defaultDelay: RetryDelay = (ctx) => {\n const retryAfter = ctx.response?.headers.get('Retry-After')\n if (retryAfter) {\n const seconds = parseInt(retryAfter, 10)\n if (!isNaN(seconds)) return seconds * 1000\n const date = Date.parse(retryAfter)\n if (!isNaN(date)) return Math.max(0, date - Date.now())\n }\n return 2 ** ctx.attempt * 200 + Math.random() * 100\n}\n\nfunction waitForRetryDelay(ms: number, signal?: AbortSignal): Promise<void> {\n if (ms <= 0) return Promise.resolve()\n return new Promise((resolve) => {\n if (!signal) {\n setTimeout(resolve, ms)\n return\n }\n\n if (signal.aborted) {\n resolve()\n return\n }\n\n const onAbort = () => {\n clearTimeout(timer)\n resolve()\n }\n\n const timer = setTimeout(() => {\n signal.removeEventListener('abort', onAbort)\n resolve()\n }, ms)\n\n signal.addEventListener('abort', onAbort, { once: true })\n })\n}\n\nexport async function retry(\n fn: () => Promise<Response>,\n retries: number,\n delay: RetryDelay,\n shouldRetry: (ctx: RetryContext) => boolean = () => true,\n request: Request,\n signal?: AbortSignal\n): Promise<Response> {\n let lastErr: unknown\n let lastRes: Response | undefined\n\n for (let i = 0; i <= retries; i++) {\n const ctx: RetryContext = {\n attempt: i + 1,\n request,\n response: lastRes,\n error: lastErr,\n }\n try {\n lastRes = await fn()\n ctx.response = lastRes\n ctx.error = undefined\n if (i < retries && shouldRetry(ctx)) {\n const wait = typeof delay === 'function' ? delay(ctx) : delay\n await waitForRetryDelay(wait, signal)\n continue\n }\n return lastRes\n } catch (err) {\n lastErr = err\n ctx.error = err\n if (i === retries || !shouldRetry(ctx)) throw err\n const wait = typeof delay === 'function' ? delay(ctx) : delay\n await waitForRetryDelay(wait, signal)\n }\n }\n throw lastErr\n}\n","import { AbortError, CircuitOpenError, TimeoutError } from './error.js'\nimport type { RetryContext } from './types.js'\n\nexport function shouldRetry(ctx: RetryContext): boolean {\n const { error, response } = ctx\n if (\n error instanceof AbortError ||\n error instanceof CircuitOpenError ||\n error instanceof TimeoutError\n )\n return false\n if (!response) return true // network error\n return response.status >= 500 || response.status === 429\n}\n","import type {\n FFetchOptions,\n FFetch,\n FFetchRequestInit,\n PendingRequest,\n} from './types.js'\nimport { retry, defaultDelay } from './retry.js'\nimport { shouldRetry as defaultShouldRetry } from './should-retry.js'\nimport {\n type PluginDispatch,\n type PluginRequestContext,\n type PluginExtensions,\n type ClientPlugin,\n type PluginExtensionBase,\n} from './plugins.js'\nimport {\n TimeoutError,\n AbortError,\n RetryLimitError,\n NetworkError,\n} from './error.js'\n\nexport function createClient<\n TPlugins extends\n readonly ClientPlugin<PluginExtensionBase>[] = readonly ClientPlugin<PluginExtensionBase>[],\n>(\n opts: FFetchOptions<TPlugins> = {} as FFetchOptions<TPlugins>\n): FFetch<PluginExtensions<TPlugins>> {\n const {\n timeout: clientDefaultTimeout = 5_000,\n retries: clientDefaultRetries = 0,\n retryDelay: clientDefaultRetryDelay = defaultDelay,\n shouldRetry: clientDefaultShouldRetry = defaultShouldRetry,\n hooks: clientDefaultHooks = {},\n fetchHandler,\n plugins: inputPlugins = [] as unknown as TPlugins,\n } = opts\n\n const extensionDescriptors: PropertyDescriptorMap = Object.create(null)\n\n const plugins = inputPlugins\n .map((plugin, index) => ({ plugin, index }))\n .sort((a, b) => {\n const aOrder = a.plugin.order ?? 0\n const bOrder = b.plugin.order ?? 0\n if (aOrder !== bOrder) return aOrder - bOrder\n return a.index - b.index\n })\n .map((entry) => entry.plugin)\n\n for (const plugin of plugins) {\n plugin.setup?.({\n defineExtension: (key, descriptor) => {\n const propertyKey = key as PropertyKey\n if (propertyKey in extensionDescriptors) {\n throw new Error(\n `Plugin extension collision for property \"${String(propertyKey)}\"`\n )\n }\n if ('get' in descriptor) {\n extensionDescriptors[propertyKey] = {\n get: descriptor.get,\n enumerable: descriptor.enumerable ?? true,\n configurable: false,\n }\n return\n }\n extensionDescriptors[propertyKey] = {\n value: descriptor.value,\n writable: false,\n enumerable: descriptor.enumerable ?? true,\n configurable: false,\n }\n },\n })\n }\n\n const pendingRequests: PendingRequest[] = []\n\n // Helper to abort all pending requests\n function abortAll() {\n for (const entry of pendingRequests) {\n entry.controller?.abort()\n }\n }\n\n const client = async (\n input: RequestInfo | URL,\n init: FFetchRequestInit = {}\n ) => {\n let request = new Request(input, init)\n\n // Merge hooks: per-request hooks override client hooks, but fallback to client hooks\n const effectiveHooks = { ...clientDefaultHooks, ...(init.hooks || {}) }\n if (effectiveHooks.transformRequest) {\n request = await effectiveHooks.transformRequest(request)\n }\n await effectiveHooks.before?.(request)\n\n // Determine retry config (per-request overrides client default)\n const effectiveRetries = init.retries ?? clientDefaultRetries\n const effectiveRetryDelay =\n typeof init.retryDelay !== 'undefined'\n ? init.retryDelay\n : clientDefaultRetryDelay\n const effectiveShouldRetry = init.shouldRetry ?? clientDefaultShouldRetry\n\n // AbortSignal.timeout/any logic\n const effectiveTimeout = init.timeout ?? clientDefaultTimeout\n const userSignal = init.signal\n const transformedSignal = request.signal\n\n const pluginContext: PluginRequestContext = {\n request,\n init,\n state: Object.create(null),\n metadata: {\n startedAt: Date.now(),\n timeoutMs: effectiveTimeout,\n signals: {\n user:\n userSignal === undefined || userSignal === null\n ? undefined\n : userSignal,\n transformed: transformedSignal,\n },\n retry: {\n configuredRetries: effectiveRetries,\n configuredDelay: effectiveRetryDelay,\n attempt: 0,\n },\n },\n }\n\n for (const plugin of plugins) {\n await plugin.preRequest?.(pluginContext)\n }\n\n // Determine throwOnHttpError (per-request overrides client default)\n const effectiveThrowOnHttpError =\n typeof init.throwOnHttpError !== 'undefined'\n ? init.throwOnHttpError\n : (opts.throwOnHttpError ?? false)\n\n // Create timeout signal (manual implementation if AbortSignal.timeout not available)\n function createTimeoutSignal(timeout: number): AbortSignal {\n if (typeof AbortSignal?.timeout === 'function') {\n return AbortSignal.timeout(timeout)\n }\n const controller = new AbortController()\n const timeoutId = setTimeout(() => controller.abort(), timeout)\n controller.signal.addEventListener(\n 'abort',\n () => clearTimeout(timeoutId),\n { once: true }\n )\n return controller.signal\n }\n\n let timeoutSignal: AbortSignal | undefined = undefined\n let combinedSignal: AbortSignal | undefined = undefined\n let controller: AbortController | undefined = undefined\n\n if (effectiveTimeout > 0) {\n timeoutSignal = createTimeoutSignal(effectiveTimeout)\n pluginContext.metadata.signals.timeout = timeoutSignal\n }\n\n const signals: AbortSignal[] = []\n if (userSignal) signals.push(userSignal)\n if (transformedSignal && transformedSignal !== userSignal) {\n signals.push(transformedSignal)\n }\n if (timeoutSignal) signals.push(timeoutSignal)\n\n if (signals.length === 1) {\n combinedSignal = signals[0]\n controller = new AbortController()\n } else {\n if (typeof AbortSignal.any !== 'function') {\n throw new Error(\n 'AbortSignal.any is required for combining multiple signals. Please install a polyfill for environments that do not support it.'\n )\n }\n combinedSignal = AbortSignal.any(signals)\n controller = new AbortController()\n }\n pluginContext.metadata.signals.combined = combinedSignal\n\n const retryWithHooks = async () => {\n let attempt = 0\n const shouldRetryWithHook = (ctx: import('./types').RetryContext) => {\n attempt = ctx.attempt\n pluginContext.metadata.retry.attempt = attempt\n pluginContext.metadata.retry.lastError = ctx.error\n pluginContext.metadata.retry.lastResponse = ctx.response\n const retrying = effectiveShouldRetry(ctx)\n pluginContext.metadata.retry.shouldRetryResult = retrying\n if (retrying && attempt <= effectiveRetries) {\n effectiveHooks.onRetry?.(\n request,\n attempt - 1,\n ctx.error,\n ctx.response\n )\n }\n return retrying\n }\n\n let lastResponse: Response | undefined = undefined\n try {\n let res = await retry(\n async () => {\n if (userSignal?.aborted) {\n effectiveHooks.onAbort?.(request)\n throw new AbortError('Request was aborted by user')\n }\n if (timeoutSignal?.aborted) {\n effectiveHooks.onTimeout?.(request)\n throw new TimeoutError('signal timed out')\n }\n if (typeof combinedSignal?.throwIfAborted === 'function') {\n combinedSignal.throwIfAborted()\n } else if (combinedSignal?.aborted) {\n if (userSignal?.aborted) {\n effectiveHooks.onAbort?.(request)\n throw new AbortError('Request was aborted by user')\n } else if (timeoutSignal?.aborted) {\n effectiveHooks.onTimeout?.(request)\n throw new TimeoutError('signal timed out')\n } else {\n throw new AbortError(\n 'Request was aborted',\n new DOMException('Aborted', 'AbortError')\n )\n }\n }\n const reqWithSignal = new Request(request, {\n signal: combinedSignal,\n })\n try {\n const handler = init.fetchHandler ?? fetchHandler ?? fetch\n const response = await handler(reqWithSignal)\n lastResponse = response\n pluginContext.metadata.retry.lastResponse = response\n return response\n } catch (err) {\n pluginContext.metadata.retry.lastError = err\n if (err instanceof DOMException && err.name === 'AbortError') {\n if (\n timeoutSignal?.aborted &&\n (!userSignal || !userSignal.aborted)\n ) {\n effectiveHooks.onTimeout?.(request)\n throw new TimeoutError('signal timed out', err)\n } else if (userSignal?.aborted) {\n effectiveHooks.onAbort?.(request)\n throw new AbortError('Request was aborted by user')\n } else {\n throw new AbortError(\n 'Request was aborted',\n new DOMException('Aborted', 'AbortError')\n )\n }\n } else if (\n err instanceof TypeError &&\n /NetworkError|network error|failed to fetch|lost connection|NetworkError when attempting to fetch resource/i.test(\n err.message\n )\n ) {\n throw new NetworkError(err.message, err)\n }\n throw err\n }\n },\n effectiveRetries,\n effectiveRetryDelay,\n shouldRetryWithHook,\n request,\n combinedSignal\n )\n if (effectiveHooks.transformResponse) {\n res = await effectiveHooks.transformResponse(res, request)\n }\n await effectiveHooks.after?.(request, res)\n await effectiveHooks.onComplete?.(request, res, undefined)\n if (\n effectiveThrowOnHttpError &&\n ((res.status >= 400 && res.status < 500 && res.status !== 429) ||\n res.status >= 500 ||\n res.status === 429)\n ) {\n const { HttpError } = await import('./error.js')\n throw new HttpError(\n `HTTP error: ${res.status} ${res.statusText}`,\n res\n )\n }\n return res\n } catch (err: unknown) {\n pluginContext.metadata.retry.lastError = err\n if (lastResponse) {\n const resp = lastResponse as Response\n if (\n effectiveThrowOnHttpError &&\n ((resp.status >= 400 && resp.status < 500 && resp.status !== 429) ||\n resp.status >= 500 ||\n resp.status === 429)\n ) {\n const { HttpError } = await import('./error.js')\n throw new HttpError(\n `HTTP error: ${resp.status} ${resp.statusText}`,\n resp\n )\n }\n return resp\n }\n if (err instanceof TimeoutError) {\n await effectiveHooks.onTimeout?.(request)\n await effectiveHooks.onError?.(request, err)\n await effectiveHooks.onComplete?.(request, undefined, err)\n throw err\n }\n if (err instanceof AbortError) {\n await effectiveHooks.onAbort?.(request)\n await effectiveHooks.onError?.(request, err)\n await effectiveHooks.onComplete?.(request, undefined, err)\n throw err\n }\n if (err instanceof NetworkError) {\n await effectiveHooks.onError?.(request, err)\n await effectiveHooks.onComplete?.(request, undefined, err)\n throw err\n }\n const retryErr = new RetryLimitError(\n typeof err === 'object' &&\n err &&\n 'message' in err &&\n typeof (err as { message?: unknown }).message === 'string'\n ? (err as { message: string }).message\n : 'Retry limit reached',\n err\n )\n await effectiveHooks.onError?.(request, retryErr)\n await effectiveHooks.onComplete?.(request, undefined, retryErr)\n throw retryErr\n }\n }\n\n const baseDispatch: PluginDispatch = async () => retryWithHooks()\n\n let dispatch = baseDispatch\n for (let i = plugins.length - 1; i >= 0; i--) {\n const plugin = plugins[i]\n if (plugin.wrapDispatch) {\n dispatch = plugin.wrapDispatch(dispatch)\n }\n }\n\n const actualPromise = dispatch(pluginContext)\n .then(async (response) => {\n for (const plugin of plugins) {\n await plugin.onSuccess?.(pluginContext, response)\n }\n return response\n })\n .catch(async (err: unknown) => {\n for (const plugin of plugins) {\n await plugin.onError?.(pluginContext, err)\n }\n throw err\n })\n\n const pendingEntry: PendingRequest = {\n promise: actualPromise,\n request,\n controller,\n }\n pendingRequests.push(pendingEntry)\n\n return actualPromise.finally(async () => {\n for (const plugin of plugins) {\n await plugin.onFinally?.(pluginContext)\n }\n\n const index = pendingRequests.indexOf(pendingEntry)\n if (index > -1) {\n pendingRequests.splice(index, 1)\n }\n })\n }\n\n Object.defineProperty(client, 'pendingRequests', {\n get() {\n return pendingRequests\n },\n enumerable: false,\n configurable: false,\n })\n\n Object.defineProperty(client, 'abortAll', {\n value: abortAll,\n writable: false,\n enumerable: false,\n configurable: false,\n })\n\n Object.defineProperties(client, extensionDescriptors)\n\n return client as FFetch<PluginExtensions<TPlugins>>\n}\n"],"mappings":"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","names":["shouldRetry","controller"]}
package/dist/index.min.js CHANGED
@@ -1,2 +1,2 @@
1
- import{a as h,b as I,c as y,d as M,e as O}from"./chunk-UP35S5ZH.min.js";var K=w=>{let l=w.response?.headers.get("Retry-After");if(l){let a=parseInt(l,10);if(!isNaN(a))return a*1e3;let b=Date.parse(l);if(!isNaN(b))return Math.max(0,b-Date.now())}return 2**w.attempt*200+Math.random()*100};async function U(w,l,a,b=()=>!0,S){let D,x;for(let R=0;R<=l;R++){let c={attempt:R+1,request:S,response:x,error:D};try{if(x=await w(),c.response=x,c.error=void 0,R<l&&b(c)){let f=typeof a=="function"?a(c):a;await new Promise(g=>setTimeout(g,f));continue}return x}catch(f){if(D=f,c.error=f,R===l||!b(c))throw f;let g=typeof a=="function"?a(c):a;await new Promise(k=>setTimeout(k,g))}}throw D}function _(w){let{error:l,response:a}=w;return l instanceof y||l instanceof I||l instanceof h?!1:a?a.status>=500||a.status===429:!0}function Q(w={}){let{timeout:l=5e3,retries:a=0,retryDelay:b=K,shouldRetry:S=_,hooks:D={},fetchHandler:x,plugins:R=[]}=w,c=Object.create(null),f=R.map((p,s)=>({plugin:p,index:s})).sort((p,s)=>{let t=p.plugin.order??0,r=s.plugin.order??0;return t!==r?t-r:p.index-s.index}).map(p=>p.plugin);for(let p of f)p.setup?.({defineExtension:(s,t)=>{let r=s;if(r in c)throw new Error(`Plugin extension collision for property "${String(r)}"`);if("get"in t){c[r]={get:t.get,enumerable:t.enumerable??!0,configurable:!1};return}c[r]={value:t.value,writable:!1,enumerable:t.enumerable??!0,configurable:!1}}});let g=[];function k(){for(let p of g)p.controller?.abort()}let q=async(p,s={})=>{let t=new Request(p,s),r={...D,...s.hooks||{}};r.transformRequest&&(t=await r.transformRequest(t)),await r.before?.(t);let H=s.retries??a,$=typeof s.retryDelay<"u"?s.retryDelay:b,z=s.shouldRetry??S,v=s.timeout??l,d=s.signal,F=t.signal,i={request:t,init:s,state:Object.create(null),metadata:{startedAt:Date.now(),timeoutMs:v,signals:{user:d??void 0,transformed:F},retry:{configuredRetries:H,configuredDelay:$,attempt:0}}};for(let n of f)await n.preRequest?.(i);let L=typeof s.throwOnHttpError<"u"?s.throwOnHttpError:w.throwOnHttpError??!1;function G(n){if(typeof AbortSignal?.timeout=="function")return AbortSignal.timeout(n);let u=new AbortController,C=setTimeout(()=>u.abort(),n);return u.signal.addEventListener("abort",()=>clearTimeout(C),{once:!0}),u.signal}let E,P,j;v>0&&(E=G(v),i.metadata.signals.timeout=E);let A=[];if(d&&A.push(d),F&&F!==d&&A.push(F),E&&A.push(E),A.length===1)P=A[0],j=new AbortController;else{if(typeof AbortSignal.any!="function")throw new Error("AbortSignal.any is required for combining multiple signals. Please install a polyfill for environments that do not support it.");P=AbortSignal.any(A),j=new AbortController}i.metadata.signals.combined=P;let J=async()=>{let n=0,u=e=>{n=e.attempt,i.metadata.retry.attempt=n,i.metadata.retry.lastError=e.error,i.metadata.retry.lastResponse=e.response;let m=z(e);return i.metadata.retry.shouldRetryResult=m,m&&n<=H&&r.onRetry?.(t,n-1,e.error,e.response),m},C;try{let e=await U(async()=>{if(d?.aborted)throw r.onAbort?.(t),new y("Request was aborted by user");if(E?.aborted)throw r.onTimeout?.(t),new h("signal timed out");if(typeof P?.throwIfAborted=="function")P.throwIfAborted();else if(P?.aborted)throw d?.aborted?(r.onAbort?.(t),new y("Request was aborted by user")):E?.aborted?(r.onTimeout?.(t),new h("signal timed out")):new y("Request was aborted",new DOMException("Aborted","AbortError"));let m=new Request(t,{signal:P});try{let T=await(s.fetchHandler??x??fetch)(m);return C=T,i.metadata.retry.lastResponse=T,T}catch(o){throw i.metadata.retry.lastError=o,o instanceof DOMException&&o.name==="AbortError"?E?.aborted&&(!d||!d.aborted)?(r.onTimeout?.(t),new h("signal timed out",o)):d?.aborted?(r.onAbort?.(t),new y("Request was aborted by user")):new y("Request was aborted",new DOMException("Aborted","AbortError")):o instanceof TypeError&&/NetworkError|network error|failed to fetch|lost connection|NetworkError when attempting to fetch resource/i.test(o.message)?new O(o.message,o):o}},H,$,u,t);if(r.transformResponse&&(e=await r.transformResponse(e,t)),await r.after?.(t,e),await r.onComplete?.(t,e,void 0),L&&(e.status>=400&&e.status<500&&e.status!==429||e.status>=500||e.status===429)){let{HttpError:m}=await import("./error-7EEQP46E.min.js");throw new m(`HTTP error: ${e.status} ${e.statusText}`,e)}return e}catch(e){if(i.metadata.retry.lastError=e,C){let o=C;if(L&&(o.status>=400&&o.status<500&&o.status!==429||o.status>=500||o.status===429)){let{HttpError:T}=await import("./error-7EEQP46E.min.js");throw new T(`HTTP error: ${o.status} ${o.statusText}`,o)}return o}if(e instanceof h)throw await r.onTimeout?.(t),await r.onError?.(t,e),await r.onComplete?.(t,void 0,e),e;if(e instanceof y)throw await r.onAbort?.(t),await r.onError?.(t,e),await r.onComplete?.(t,void 0,e),e;if(e instanceof O)throw await r.onError?.(t,e),await r.onComplete?.(t,void 0,e),e;let m=new M(typeof e=="object"&&e&&"message"in e&&typeof e.message=="string"?e.message:"Retry limit reached",e);throw await r.onError?.(t,m),await r.onComplete?.(t,void 0,m),m}},N=async()=>J();for(let n=f.length-1;n>=0;n--){let u=f[n];u.wrapDispatch&&(N=u.wrapDispatch(N))}let B=N(i).then(async n=>{for(let u of f)await u.onSuccess?.(i,n);return n}).catch(async n=>{for(let u of f)await u.onError?.(i,n);throw n}),W={promise:B,request:t,controller:j};return g.push(W),B.finally(async()=>{for(let u of f)await u.onFinally?.(i);let n=g.indexOf(W);n>-1&&g.splice(n,1)})};return Object.defineProperty(q,"pendingRequests",{get(){return g},enumerable:!1,configurable:!1}),Object.defineProperty(q,"abortAll",{value:k,writable:!1,enumerable:!1,configurable:!1}),Object.defineProperties(q,c),q}export{y as AbortError,I as CircuitOpenError,O as NetworkError,M as RetryLimitError,h as TimeoutError,Q as createClient};
1
+ import{a as R,b as I,c as b,d as M,e as O}from"./chunk-UP35S5ZH.min.js";var U=c=>{let i=c.response?.headers.get("Retry-After");if(i){let o=parseInt(i,10);if(!isNaN(o))return o*1e3;let w=Date.parse(i);if(!isNaN(w))return Math.max(0,w-Date.now())}return 2**c.attempt*200+Math.random()*100};function K(c,i){return c<=0?Promise.resolve():new Promise(o=>{if(!i){setTimeout(o,c);return}if(i.aborted){o();return}let w=()=>{clearTimeout(A),o()},A=setTimeout(()=>{i.removeEventListener("abort",w),o()},c);i.addEventListener("abort",w,{once:!0})})}async function _(c,i,o,w=()=>!0,A,q){let D,P;for(let g=0;g<=i;g++){let u={attempt:g+1,request:A,response:P,error:D};try{if(P=await c(),u.response=P,u.error=void 0,g<i&&w(u)){let d=typeof o=="function"?o(u):o;await K(d,q);continue}return P}catch(d){if(D=d,u.error=d,g===i||!w(u))throw d;let k=typeof o=="function"?o(u):o;await K(k,q)}}throw D}function z(c){let{error:i,response:o}=c;return i instanceof b||i instanceof I||i instanceof R?!1:o?o.status>=500||o.status===429:!0}function V(c={}){let{timeout:i=5e3,retries:o=0,retryDelay:w=U,shouldRetry:A=z,hooks:q={},fetchHandler:D,plugins:P=[]}=c,g=Object.create(null),u=P.map((p,a)=>({plugin:p,index:a})).sort((p,a)=>{let t=p.plugin.order??0,r=a.plugin.order??0;return t!==r?t-r:p.index-a.index}).map(p=>p.plugin);for(let p of u)p.setup?.({defineExtension:(a,t)=>{let r=a;if(r in g)throw new Error(`Plugin extension collision for property "${String(r)}"`);if("get"in t){g[r]={get:t.get,enumerable:t.enumerable??!0,configurable:!1};return}g[r]={value:t.value,writable:!1,enumerable:t.enumerable??!0,configurable:!1}}});let d=[];function k(){for(let p of d)p.controller?.abort()}let S=async(p,a={})=>{let t=new Request(p,a),r={...q,...a.hooks||{}};r.transformRequest&&(t=await r.transformRequest(t)),await r.before?.(t);let H=a.retries??o,L=typeof a.retryDelay<"u"?a.retryDelay:w,G=a.shouldRetry??A,v=a.timeout??i,m=a.signal,F=t.signal,l={request:t,init:a,state:Object.create(null),metadata:{startedAt:Date.now(),timeoutMs:v,signals:{user:m??void 0,transformed:F},retry:{configuredRetries:H,configuredDelay:L,attempt:0}}};for(let n of u)await n.preRequest?.(l);let $=typeof a.throwOnHttpError<"u"?a.throwOnHttpError:c.throwOnHttpError??!1;function J(n){if(typeof AbortSignal?.timeout=="function")return AbortSignal.timeout(n);let f=new AbortController,C=setTimeout(()=>f.abort(),n);return f.signal.addEventListener("abort",()=>clearTimeout(C),{once:!0}),f.signal}let E,h,j;v>0&&(E=J(v),l.metadata.signals.timeout=E);let x=[];if(m&&x.push(m),F&&F!==m&&x.push(F),E&&x.push(E),x.length===1)h=x[0],j=new AbortController;else{if(typeof AbortSignal.any!="function")throw new Error("AbortSignal.any is required for combining multiple signals. Please install a polyfill for environments that do not support it.");h=AbortSignal.any(x),j=new AbortController}l.metadata.signals.combined=h;let Q=async()=>{let n=0,f=e=>{n=e.attempt,l.metadata.retry.attempt=n,l.metadata.retry.lastError=e.error,l.metadata.retry.lastResponse=e.response;let y=G(e);return l.metadata.retry.shouldRetryResult=y,y&&n<=H&&r.onRetry?.(t,n-1,e.error,e.response),y},C;try{let e=await _(async()=>{if(m?.aborted)throw r.onAbort?.(t),new b("Request was aborted by user");if(E?.aborted)throw r.onTimeout?.(t),new R("signal timed out");if(typeof h?.throwIfAborted=="function")h.throwIfAborted();else if(h?.aborted)throw m?.aborted?(r.onAbort?.(t),new b("Request was aborted by user")):E?.aborted?(r.onTimeout?.(t),new R("signal timed out")):new b("Request was aborted",new DOMException("Aborted","AbortError"));let y=new Request(t,{signal:h});try{let T=await(a.fetchHandler??D??fetch)(y);return C=T,l.metadata.retry.lastResponse=T,T}catch(s){throw l.metadata.retry.lastError=s,s instanceof DOMException&&s.name==="AbortError"?E?.aborted&&(!m||!m.aborted)?(r.onTimeout?.(t),new R("signal timed out",s)):m?.aborted?(r.onAbort?.(t),new b("Request was aborted by user")):new b("Request was aborted",new DOMException("Aborted","AbortError")):s instanceof TypeError&&/NetworkError|network error|failed to fetch|lost connection|NetworkError when attempting to fetch resource/i.test(s.message)?new O(s.message,s):s}},H,L,f,t,h);if(r.transformResponse&&(e=await r.transformResponse(e,t)),await r.after?.(t,e),await r.onComplete?.(t,e,void 0),$&&(e.status>=400&&e.status<500&&e.status!==429||e.status>=500||e.status===429)){let{HttpError:y}=await import("./error-7EEQP46E.min.js");throw new y(`HTTP error: ${e.status} ${e.statusText}`,e)}return e}catch(e){if(l.metadata.retry.lastError=e,C){let s=C;if($&&(s.status>=400&&s.status<500&&s.status!==429||s.status>=500||s.status===429)){let{HttpError:T}=await import("./error-7EEQP46E.min.js");throw new T(`HTTP error: ${s.status} ${s.statusText}`,s)}return s}if(e instanceof R)throw await r.onTimeout?.(t),await r.onError?.(t,e),await r.onComplete?.(t,void 0,e),e;if(e instanceof b)throw await r.onAbort?.(t),await r.onError?.(t,e),await r.onComplete?.(t,void 0,e),e;if(e instanceof O)throw await r.onError?.(t,e),await r.onComplete?.(t,void 0,e),e;let y=new M(typeof e=="object"&&e&&"message"in e&&typeof e.message=="string"?e.message:"Retry limit reached",e);throw await r.onError?.(t,y),await r.onComplete?.(t,void 0,y),y}},N=async()=>Q();for(let n=u.length-1;n>=0;n--){let f=u[n];f.wrapDispatch&&(N=f.wrapDispatch(N))}let B=N(l).then(async n=>{for(let f of u)await f.onSuccess?.(l,n);return n}).catch(async n=>{for(let f of u)await f.onError?.(l,n);throw n}),W={promise:B,request:t,controller:j};return d.push(W),B.finally(async()=>{for(let f of u)await f.onFinally?.(l);let n=d.indexOf(W);n>-1&&d.splice(n,1)})};return Object.defineProperty(S,"pendingRequests",{get(){return d},enumerable:!1,configurable:!1}),Object.defineProperty(S,"abortAll",{value:k,writable:!1,enumerable:!1,configurable:!1}),Object.defineProperties(S,g),S}export{b as AbortError,I as CircuitOpenError,O as NetworkError,M as RetryLimitError,R as TimeoutError,V as createClient};
2
2
  //# sourceMappingURL=index.min.js.map
@@ -1 +1 @@
1
- {"version":3,"sources":["../src/retry.ts","../src/should-retry.ts","../src/client.ts"],"sourcesContent":["import type { RetryContext } from './types.js'\n\nexport type RetryDelay = number | ((ctx: RetryContext) => number)\n\nexport const defaultDelay: RetryDelay = (ctx) => {\n const retryAfter = ctx.response?.headers.get('Retry-After')\n if (retryAfter) {\n const seconds = parseInt(retryAfter, 10)\n if (!isNaN(seconds)) return seconds * 1000\n const date = Date.parse(retryAfter)\n if (!isNaN(date)) return Math.max(0, date - Date.now())\n }\n return 2 ** ctx.attempt * 200 + Math.random() * 100\n}\n\nexport async function retry(\n fn: () => Promise<Response>,\n retries: number,\n delay: RetryDelay,\n shouldRetry: (ctx: RetryContext) => boolean = () => true,\n request: Request\n): Promise<Response> {\n let lastErr: unknown\n let lastRes: Response | undefined\n\n for (let i = 0; i <= retries; i++) {\n const ctx: RetryContext = {\n attempt: i + 1,\n request,\n response: lastRes,\n error: lastErr,\n }\n try {\n lastRes = await fn()\n ctx.response = lastRes\n ctx.error = undefined\n if (i < retries && shouldRetry(ctx)) {\n const wait = typeof delay === 'function' ? delay(ctx) : delay\n await new Promise((r) => setTimeout(r, wait))\n continue\n }\n return lastRes\n } catch (err) {\n lastErr = err\n ctx.error = err\n if (i === retries || !shouldRetry(ctx)) throw err\n const wait = typeof delay === 'function' ? delay(ctx) : delay\n await new Promise((r) => setTimeout(r, wait))\n }\n }\n throw lastErr\n}\n","import { AbortError, CircuitOpenError, TimeoutError } from './error.js'\nimport type { RetryContext } from './types.js'\n\nexport function shouldRetry(ctx: RetryContext): boolean {\n const { error, response } = ctx\n if (\n error instanceof AbortError ||\n error instanceof CircuitOpenError ||\n error instanceof TimeoutError\n )\n return false\n if (!response) return true // network error\n return response.status >= 500 || response.status === 429\n}\n","import type {\r\n FFetchOptions,\r\n FFetch,\r\n FFetchRequestInit,\r\n PendingRequest,\r\n} from './types.js'\r\nimport { retry, defaultDelay } from './retry.js'\r\nimport { shouldRetry as defaultShouldRetry } from './should-retry.js'\r\nimport {\r\n type PluginDispatch,\r\n type PluginRequestContext,\r\n type PluginExtensions,\r\n type ClientPlugin,\r\n type PluginExtensionBase,\r\n} from './plugins.js'\r\nimport {\r\n TimeoutError,\r\n AbortError,\r\n RetryLimitError,\r\n NetworkError,\r\n} from './error.js'\r\n\r\nexport function createClient<\r\n TPlugins extends\r\n readonly ClientPlugin<PluginExtensionBase>[] = readonly ClientPlugin<PluginExtensionBase>[],\r\n>(\r\n opts: FFetchOptions<TPlugins> = {} as FFetchOptions<TPlugins>\r\n): FFetch<PluginExtensions<TPlugins>> {\r\n const {\r\n timeout: clientDefaultTimeout = 5_000,\r\n retries: clientDefaultRetries = 0,\r\n retryDelay: clientDefaultRetryDelay = defaultDelay,\r\n shouldRetry: clientDefaultShouldRetry = defaultShouldRetry,\r\n hooks: clientDefaultHooks = {},\r\n fetchHandler,\r\n plugins: inputPlugins = [] as unknown as TPlugins,\r\n } = opts\r\n\r\n const extensionDescriptors: PropertyDescriptorMap = Object.create(null)\r\n\r\n const plugins = inputPlugins\r\n .map((plugin, index) => ({ plugin, index }))\r\n .sort((a, b) => {\r\n const aOrder = a.plugin.order ?? 0\r\n const bOrder = b.plugin.order ?? 0\r\n if (aOrder !== bOrder) return aOrder - bOrder\r\n return a.index - b.index\r\n })\r\n .map((entry) => entry.plugin)\r\n\r\n for (const plugin of plugins) {\r\n plugin.setup?.({\r\n defineExtension: (key, descriptor) => {\r\n const propertyKey = key as PropertyKey\r\n if (propertyKey in extensionDescriptors) {\r\n throw new Error(\r\n `Plugin extension collision for property \"${String(propertyKey)}\"`\r\n )\r\n }\r\n if ('get' in descriptor) {\r\n extensionDescriptors[propertyKey] = {\r\n get: descriptor.get,\r\n enumerable: descriptor.enumerable ?? true,\r\n configurable: false,\r\n }\r\n return\r\n }\r\n extensionDescriptors[propertyKey] = {\r\n value: descriptor.value,\r\n writable: false,\r\n enumerable: descriptor.enumerable ?? true,\r\n configurable: false,\r\n }\r\n },\r\n })\r\n }\r\n\r\n const pendingRequests: PendingRequest[] = []\r\n\r\n // Helper to abort all pending requests\r\n function abortAll() {\r\n for (const entry of pendingRequests) {\r\n entry.controller?.abort()\r\n }\r\n }\r\n\r\n const client = async (\r\n input: RequestInfo | URL,\r\n init: FFetchRequestInit = {}\r\n ) => {\r\n let request = new Request(input, init)\r\n\r\n // Merge hooks: per-request hooks override client hooks, but fallback to client hooks\r\n const effectiveHooks = { ...clientDefaultHooks, ...(init.hooks || {}) }\r\n if (effectiveHooks.transformRequest) {\r\n request = await effectiveHooks.transformRequest(request)\r\n }\r\n await effectiveHooks.before?.(request)\r\n\r\n // Determine retry config (per-request overrides client default)\r\n const effectiveRetries = init.retries ?? clientDefaultRetries\r\n const effectiveRetryDelay =\r\n typeof init.retryDelay !== 'undefined'\r\n ? init.retryDelay\r\n : clientDefaultRetryDelay\r\n const effectiveShouldRetry = init.shouldRetry ?? clientDefaultShouldRetry\r\n\r\n // AbortSignal.timeout/any logic\r\n const effectiveTimeout = init.timeout ?? clientDefaultTimeout\r\n const userSignal = init.signal\r\n const transformedSignal = request.signal\r\n\r\n const pluginContext: PluginRequestContext = {\r\n request,\r\n init,\r\n state: Object.create(null),\r\n metadata: {\r\n startedAt: Date.now(),\r\n timeoutMs: effectiveTimeout,\r\n signals: {\r\n user:\r\n userSignal === undefined || userSignal === null\r\n ? undefined\r\n : userSignal,\r\n transformed: transformedSignal,\r\n },\r\n retry: {\r\n configuredRetries: effectiveRetries,\r\n configuredDelay: effectiveRetryDelay,\r\n attempt: 0,\r\n },\r\n },\r\n }\r\n\r\n for (const plugin of plugins) {\r\n await plugin.preRequest?.(pluginContext)\r\n }\r\n\r\n // Determine throwOnHttpError (per-request overrides client default)\r\n const effectiveThrowOnHttpError =\r\n typeof init.throwOnHttpError !== 'undefined'\r\n ? init.throwOnHttpError\r\n : (opts.throwOnHttpError ?? false)\r\n\r\n // Create timeout signal (manual implementation if AbortSignal.timeout not available)\r\n function createTimeoutSignal(timeout: number): AbortSignal {\r\n if (typeof AbortSignal?.timeout === 'function') {\r\n return AbortSignal.timeout(timeout)\r\n }\r\n const controller = new AbortController()\r\n const timeoutId = setTimeout(() => controller.abort(), timeout)\r\n controller.signal.addEventListener(\r\n 'abort',\r\n () => clearTimeout(timeoutId),\r\n { once: true }\r\n )\r\n return controller.signal\r\n }\r\n\r\n let timeoutSignal: AbortSignal | undefined = undefined\r\n let combinedSignal: AbortSignal | undefined = undefined\r\n let controller: AbortController | undefined = undefined\r\n\r\n if (effectiveTimeout > 0) {\r\n timeoutSignal = createTimeoutSignal(effectiveTimeout)\r\n pluginContext.metadata.signals.timeout = timeoutSignal\r\n }\r\n\r\n const signals: AbortSignal[] = []\r\n if (userSignal) signals.push(userSignal)\r\n if (transformedSignal && transformedSignal !== userSignal) {\r\n signals.push(transformedSignal)\r\n }\r\n if (timeoutSignal) signals.push(timeoutSignal)\r\n\r\n if (signals.length === 1) {\r\n combinedSignal = signals[0]\r\n controller = new AbortController()\r\n } else {\r\n if (typeof AbortSignal.any !== 'function') {\r\n throw new Error(\r\n 'AbortSignal.any is required for combining multiple signals. Please install a polyfill for environments that do not support it.'\r\n )\r\n }\r\n combinedSignal = AbortSignal.any(signals)\r\n controller = new AbortController()\r\n }\r\n pluginContext.metadata.signals.combined = combinedSignal\r\n\r\n const retryWithHooks = async () => {\r\n let attempt = 0\r\n const shouldRetryWithHook = (ctx: import('./types').RetryContext) => {\r\n attempt = ctx.attempt\r\n pluginContext.metadata.retry.attempt = attempt\r\n pluginContext.metadata.retry.lastError = ctx.error\r\n pluginContext.metadata.retry.lastResponse = ctx.response\r\n const retrying = effectiveShouldRetry(ctx)\r\n pluginContext.metadata.retry.shouldRetryResult = retrying\r\n if (retrying && attempt <= effectiveRetries) {\r\n effectiveHooks.onRetry?.(\r\n request,\r\n attempt - 1,\r\n ctx.error,\r\n ctx.response\r\n )\r\n }\r\n return retrying\r\n }\r\n\r\n let lastResponse: Response | undefined = undefined\r\n try {\r\n let res = await retry(\r\n async () => {\r\n if (userSignal?.aborted) {\r\n effectiveHooks.onAbort?.(request)\r\n throw new AbortError('Request was aborted by user')\r\n }\r\n if (timeoutSignal?.aborted) {\r\n effectiveHooks.onTimeout?.(request)\r\n throw new TimeoutError('signal timed out')\r\n }\r\n if (typeof combinedSignal?.throwIfAborted === 'function') {\r\n combinedSignal.throwIfAborted()\r\n } else if (combinedSignal?.aborted) {\r\n if (userSignal?.aborted) {\r\n effectiveHooks.onAbort?.(request)\r\n throw new AbortError('Request was aborted by user')\r\n } else if (timeoutSignal?.aborted) {\r\n effectiveHooks.onTimeout?.(request)\r\n throw new TimeoutError('signal timed out')\r\n } else {\r\n throw new AbortError(\r\n 'Request was aborted',\r\n new DOMException('Aborted', 'AbortError')\r\n )\r\n }\r\n }\r\n const reqWithSignal = new Request(request, {\r\n signal: combinedSignal,\r\n })\r\n try {\r\n const handler = init.fetchHandler ?? fetchHandler ?? fetch\r\n const response = await handler(reqWithSignal)\r\n lastResponse = response\r\n pluginContext.metadata.retry.lastResponse = response\r\n return response\r\n } catch (err) {\r\n pluginContext.metadata.retry.lastError = err\r\n if (err instanceof DOMException && err.name === 'AbortError') {\r\n if (\r\n timeoutSignal?.aborted &&\r\n (!userSignal || !userSignal.aborted)\r\n ) {\r\n effectiveHooks.onTimeout?.(request)\r\n throw new TimeoutError('signal timed out', err)\r\n } else if (userSignal?.aborted) {\r\n effectiveHooks.onAbort?.(request)\r\n throw new AbortError('Request was aborted by user')\r\n } else {\r\n throw new AbortError(\r\n 'Request was aborted',\r\n new DOMException('Aborted', 'AbortError')\r\n )\r\n }\r\n } else if (\r\n err instanceof TypeError &&\r\n /NetworkError|network error|failed to fetch|lost connection|NetworkError when attempting to fetch resource/i.test(\r\n err.message\r\n )\r\n ) {\r\n throw new NetworkError(err.message, err)\r\n }\r\n throw err\r\n }\r\n },\r\n effectiveRetries,\r\n effectiveRetryDelay,\r\n shouldRetryWithHook,\r\n request\r\n )\r\n if (effectiveHooks.transformResponse) {\r\n res = await effectiveHooks.transformResponse(res, request)\r\n }\r\n await effectiveHooks.after?.(request, res)\r\n await effectiveHooks.onComplete?.(request, res, undefined)\r\n if (\r\n effectiveThrowOnHttpError &&\r\n ((res.status >= 400 && res.status < 500 && res.status !== 429) ||\r\n res.status >= 500 ||\r\n res.status === 429)\r\n ) {\r\n const { HttpError } = await import('./error.js')\r\n throw new HttpError(\r\n `HTTP error: ${res.status} ${res.statusText}`,\r\n res\r\n )\r\n }\r\n return res\r\n } catch (err: unknown) {\r\n pluginContext.metadata.retry.lastError = err\r\n if (lastResponse) {\r\n const resp = lastResponse as Response\r\n if (\r\n effectiveThrowOnHttpError &&\r\n ((resp.status >= 400 && resp.status < 500 && resp.status !== 429) ||\r\n resp.status >= 500 ||\r\n resp.status === 429)\r\n ) {\r\n const { HttpError } = await import('./error.js')\r\n throw new HttpError(\r\n `HTTP error: ${resp.status} ${resp.statusText}`,\r\n resp\r\n )\r\n }\r\n return resp\r\n }\r\n if (err instanceof TimeoutError) {\r\n await effectiveHooks.onTimeout?.(request)\r\n await effectiveHooks.onError?.(request, err)\r\n await effectiveHooks.onComplete?.(request, undefined, err)\r\n throw err\r\n }\r\n if (err instanceof AbortError) {\r\n await effectiveHooks.onAbort?.(request)\r\n await effectiveHooks.onError?.(request, err)\r\n await effectiveHooks.onComplete?.(request, undefined, err)\r\n throw err\r\n }\r\n if (err instanceof NetworkError) {\r\n await effectiveHooks.onError?.(request, err)\r\n await effectiveHooks.onComplete?.(request, undefined, err)\r\n throw err\r\n }\r\n const retryErr = new RetryLimitError(\r\n typeof err === 'object' &&\r\n err &&\r\n 'message' in err &&\r\n typeof (err as { message?: unknown }).message === 'string'\r\n ? (err as { message: string }).message\r\n : 'Retry limit reached',\r\n err\r\n )\r\n await effectiveHooks.onError?.(request, retryErr)\r\n await effectiveHooks.onComplete?.(request, undefined, retryErr)\r\n throw retryErr\r\n }\r\n }\r\n\r\n const baseDispatch: PluginDispatch = async () => retryWithHooks()\r\n\r\n let dispatch = baseDispatch\r\n for (let i = plugins.length - 1; i >= 0; i--) {\r\n const plugin = plugins[i]\r\n if (plugin.wrapDispatch) {\r\n dispatch = plugin.wrapDispatch(dispatch)\r\n }\r\n }\r\n\r\n const actualPromise = dispatch(pluginContext)\r\n .then(async (response) => {\r\n for (const plugin of plugins) {\r\n await plugin.onSuccess?.(pluginContext, response)\r\n }\r\n return response\r\n })\r\n .catch(async (err: unknown) => {\r\n for (const plugin of plugins) {\r\n await plugin.onError?.(pluginContext, err)\r\n }\r\n throw err\r\n })\r\n\r\n const pendingEntry: PendingRequest = {\r\n promise: actualPromise,\r\n request,\r\n controller,\r\n }\r\n pendingRequests.push(pendingEntry)\r\n\r\n return actualPromise.finally(async () => {\r\n for (const plugin of plugins) {\r\n await plugin.onFinally?.(pluginContext)\r\n }\r\n\r\n const index = pendingRequests.indexOf(pendingEntry)\r\n if (index > -1) {\r\n pendingRequests.splice(index, 1)\r\n }\r\n })\r\n }\r\n\r\n Object.defineProperty(client, 'pendingRequests', {\r\n get() {\r\n return pendingRequests\r\n },\r\n enumerable: false,\r\n configurable: false,\r\n })\r\n\r\n Object.defineProperty(client, 'abortAll', {\r\n value: abortAll,\r\n writable: false,\r\n enumerable: false,\r\n configurable: false,\r\n })\r\n\r\n Object.defineProperties(client, extensionDescriptors)\r\n\r\n return client as FFetch<PluginExtensions<TPlugins>>\r\n}\r\n"],"mappings":"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","names":["defaultDelay","ctx","retryAfter","seconds","date","retry","fn","retries","delay","shouldRetry","request","lastErr","lastRes","i","wait","r","err","shouldRetry","ctx","error","response","AbortError","CircuitOpenError","TimeoutError","createClient","opts","clientDefaultTimeout","clientDefaultRetries","clientDefaultRetryDelay","defaultDelay","clientDefaultShouldRetry","shouldRetry","clientDefaultHooks","fetchHandler","inputPlugins","extensionDescriptors","plugins","plugin","index","a","b","aOrder","bOrder","entry","key","descriptor","propertyKey","pendingRequests","abortAll","client","input","init","request","effectiveHooks","effectiveRetries","effectiveRetryDelay","effectiveShouldRetry","effectiveTimeout","userSignal","transformedSignal","pluginContext","effectiveThrowOnHttpError","createTimeoutSignal","timeout","controller","timeoutId","timeoutSignal","combinedSignal","signals","retryWithHooks","attempt","shouldRetryWithHook","ctx","retrying","lastResponse","res","retry","AbortError","TimeoutError","reqWithSignal","response","err","NetworkError","HttpError","resp","retryErr","RetryLimitError","dispatch","i","actualPromise","pendingEntry"]}
1
+ {"version":3,"sources":["../src/retry.ts","../src/should-retry.ts","../src/client.ts"],"sourcesContent":["import type { RetryContext } from './types.js'\n\nexport type RetryDelay = number | ((ctx: RetryContext) => number)\n\nexport const defaultDelay: RetryDelay = (ctx) => {\n const retryAfter = ctx.response?.headers.get('Retry-After')\n if (retryAfter) {\n const seconds = parseInt(retryAfter, 10)\n if (!isNaN(seconds)) return seconds * 1000\n const date = Date.parse(retryAfter)\n if (!isNaN(date)) return Math.max(0, date - Date.now())\n }\n return 2 ** ctx.attempt * 200 + Math.random() * 100\n}\n\nfunction waitForRetryDelay(ms: number, signal?: AbortSignal): Promise<void> {\n if (ms <= 0) return Promise.resolve()\n return new Promise((resolve) => {\n if (!signal) {\n setTimeout(resolve, ms)\n return\n }\n\n if (signal.aborted) {\n resolve()\n return\n }\n\n const onAbort = () => {\n clearTimeout(timer)\n resolve()\n }\n\n const timer = setTimeout(() => {\n signal.removeEventListener('abort', onAbort)\n resolve()\n }, ms)\n\n signal.addEventListener('abort', onAbort, { once: true })\n })\n}\n\nexport async function retry(\n fn: () => Promise<Response>,\n retries: number,\n delay: RetryDelay,\n shouldRetry: (ctx: RetryContext) => boolean = () => true,\n request: Request,\n signal?: AbortSignal\n): Promise<Response> {\n let lastErr: unknown\n let lastRes: Response | undefined\n\n for (let i = 0; i <= retries; i++) {\n const ctx: RetryContext = {\n attempt: i + 1,\n request,\n response: lastRes,\n error: lastErr,\n }\n try {\n lastRes = await fn()\n ctx.response = lastRes\n ctx.error = undefined\n if (i < retries && shouldRetry(ctx)) {\n const wait = typeof delay === 'function' ? delay(ctx) : delay\n await waitForRetryDelay(wait, signal)\n continue\n }\n return lastRes\n } catch (err) {\n lastErr = err\n ctx.error = err\n if (i === retries || !shouldRetry(ctx)) throw err\n const wait = typeof delay === 'function' ? delay(ctx) : delay\n await waitForRetryDelay(wait, signal)\n }\n }\n throw lastErr\n}\n","import { AbortError, CircuitOpenError, TimeoutError } from './error.js'\nimport type { RetryContext } from './types.js'\n\nexport function shouldRetry(ctx: RetryContext): boolean {\n const { error, response } = ctx\n if (\n error instanceof AbortError ||\n error instanceof CircuitOpenError ||\n error instanceof TimeoutError\n )\n return false\n if (!response) return true // network error\n return response.status >= 500 || response.status === 429\n}\n","import type {\n FFetchOptions,\n FFetch,\n FFetchRequestInit,\n PendingRequest,\n} from './types.js'\nimport { retry, defaultDelay } from './retry.js'\nimport { shouldRetry as defaultShouldRetry } from './should-retry.js'\nimport {\n type PluginDispatch,\n type PluginRequestContext,\n type PluginExtensions,\n type ClientPlugin,\n type PluginExtensionBase,\n} from './plugins.js'\nimport {\n TimeoutError,\n AbortError,\n RetryLimitError,\n NetworkError,\n} from './error.js'\n\nexport function createClient<\n TPlugins extends\n readonly ClientPlugin<PluginExtensionBase>[] = readonly ClientPlugin<PluginExtensionBase>[],\n>(\n opts: FFetchOptions<TPlugins> = {} as FFetchOptions<TPlugins>\n): FFetch<PluginExtensions<TPlugins>> {\n const {\n timeout: clientDefaultTimeout = 5_000,\n retries: clientDefaultRetries = 0,\n retryDelay: clientDefaultRetryDelay = defaultDelay,\n shouldRetry: clientDefaultShouldRetry = defaultShouldRetry,\n hooks: clientDefaultHooks = {},\n fetchHandler,\n plugins: inputPlugins = [] as unknown as TPlugins,\n } = opts\n\n const extensionDescriptors: PropertyDescriptorMap = Object.create(null)\n\n const plugins = inputPlugins\n .map((plugin, index) => ({ plugin, index }))\n .sort((a, b) => {\n const aOrder = a.plugin.order ?? 0\n const bOrder = b.plugin.order ?? 0\n if (aOrder !== bOrder) return aOrder - bOrder\n return a.index - b.index\n })\n .map((entry) => entry.plugin)\n\n for (const plugin of plugins) {\n plugin.setup?.({\n defineExtension: (key, descriptor) => {\n const propertyKey = key as PropertyKey\n if (propertyKey in extensionDescriptors) {\n throw new Error(\n `Plugin extension collision for property \"${String(propertyKey)}\"`\n )\n }\n if ('get' in descriptor) {\n extensionDescriptors[propertyKey] = {\n get: descriptor.get,\n enumerable: descriptor.enumerable ?? true,\n configurable: false,\n }\n return\n }\n extensionDescriptors[propertyKey] = {\n value: descriptor.value,\n writable: false,\n enumerable: descriptor.enumerable ?? true,\n configurable: false,\n }\n },\n })\n }\n\n const pendingRequests: PendingRequest[] = []\n\n // Helper to abort all pending requests\n function abortAll() {\n for (const entry of pendingRequests) {\n entry.controller?.abort()\n }\n }\n\n const client = async (\n input: RequestInfo | URL,\n init: FFetchRequestInit = {}\n ) => {\n let request = new Request(input, init)\n\n // Merge hooks: per-request hooks override client hooks, but fallback to client hooks\n const effectiveHooks = { ...clientDefaultHooks, ...(init.hooks || {}) }\n if (effectiveHooks.transformRequest) {\n request = await effectiveHooks.transformRequest(request)\n }\n await effectiveHooks.before?.(request)\n\n // Determine retry config (per-request overrides client default)\n const effectiveRetries = init.retries ?? clientDefaultRetries\n const effectiveRetryDelay =\n typeof init.retryDelay !== 'undefined'\n ? init.retryDelay\n : clientDefaultRetryDelay\n const effectiveShouldRetry = init.shouldRetry ?? clientDefaultShouldRetry\n\n // AbortSignal.timeout/any logic\n const effectiveTimeout = init.timeout ?? clientDefaultTimeout\n const userSignal = init.signal\n const transformedSignal = request.signal\n\n const pluginContext: PluginRequestContext = {\n request,\n init,\n state: Object.create(null),\n metadata: {\n startedAt: Date.now(),\n timeoutMs: effectiveTimeout,\n signals: {\n user:\n userSignal === undefined || userSignal === null\n ? undefined\n : userSignal,\n transformed: transformedSignal,\n },\n retry: {\n configuredRetries: effectiveRetries,\n configuredDelay: effectiveRetryDelay,\n attempt: 0,\n },\n },\n }\n\n for (const plugin of plugins) {\n await plugin.preRequest?.(pluginContext)\n }\n\n // Determine throwOnHttpError (per-request overrides client default)\n const effectiveThrowOnHttpError =\n typeof init.throwOnHttpError !== 'undefined'\n ? init.throwOnHttpError\n : (opts.throwOnHttpError ?? false)\n\n // Create timeout signal (manual implementation if AbortSignal.timeout not available)\n function createTimeoutSignal(timeout: number): AbortSignal {\n if (typeof AbortSignal?.timeout === 'function') {\n return AbortSignal.timeout(timeout)\n }\n const controller = new AbortController()\n const timeoutId = setTimeout(() => controller.abort(), timeout)\n controller.signal.addEventListener(\n 'abort',\n () => clearTimeout(timeoutId),\n { once: true }\n )\n return controller.signal\n }\n\n let timeoutSignal: AbortSignal | undefined = undefined\n let combinedSignal: AbortSignal | undefined = undefined\n let controller: AbortController | undefined = undefined\n\n if (effectiveTimeout > 0) {\n timeoutSignal = createTimeoutSignal(effectiveTimeout)\n pluginContext.metadata.signals.timeout = timeoutSignal\n }\n\n const signals: AbortSignal[] = []\n if (userSignal) signals.push(userSignal)\n if (transformedSignal && transformedSignal !== userSignal) {\n signals.push(transformedSignal)\n }\n if (timeoutSignal) signals.push(timeoutSignal)\n\n if (signals.length === 1) {\n combinedSignal = signals[0]\n controller = new AbortController()\n } else {\n if (typeof AbortSignal.any !== 'function') {\n throw new Error(\n 'AbortSignal.any is required for combining multiple signals. Please install a polyfill for environments that do not support it.'\n )\n }\n combinedSignal = AbortSignal.any(signals)\n controller = new AbortController()\n }\n pluginContext.metadata.signals.combined = combinedSignal\n\n const retryWithHooks = async () => {\n let attempt = 0\n const shouldRetryWithHook = (ctx: import('./types').RetryContext) => {\n attempt = ctx.attempt\n pluginContext.metadata.retry.attempt = attempt\n pluginContext.metadata.retry.lastError = ctx.error\n pluginContext.metadata.retry.lastResponse = ctx.response\n const retrying = effectiveShouldRetry(ctx)\n pluginContext.metadata.retry.shouldRetryResult = retrying\n if (retrying && attempt <= effectiveRetries) {\n effectiveHooks.onRetry?.(\n request,\n attempt - 1,\n ctx.error,\n ctx.response\n )\n }\n return retrying\n }\n\n let lastResponse: Response | undefined = undefined\n try {\n let res = await retry(\n async () => {\n if (userSignal?.aborted) {\n effectiveHooks.onAbort?.(request)\n throw new AbortError('Request was aborted by user')\n }\n if (timeoutSignal?.aborted) {\n effectiveHooks.onTimeout?.(request)\n throw new TimeoutError('signal timed out')\n }\n if (typeof combinedSignal?.throwIfAborted === 'function') {\n combinedSignal.throwIfAborted()\n } else if (combinedSignal?.aborted) {\n if (userSignal?.aborted) {\n effectiveHooks.onAbort?.(request)\n throw new AbortError('Request was aborted by user')\n } else if (timeoutSignal?.aborted) {\n effectiveHooks.onTimeout?.(request)\n throw new TimeoutError('signal timed out')\n } else {\n throw new AbortError(\n 'Request was aborted',\n new DOMException('Aborted', 'AbortError')\n )\n }\n }\n const reqWithSignal = new Request(request, {\n signal: combinedSignal,\n })\n try {\n const handler = init.fetchHandler ?? fetchHandler ?? fetch\n const response = await handler(reqWithSignal)\n lastResponse = response\n pluginContext.metadata.retry.lastResponse = response\n return response\n } catch (err) {\n pluginContext.metadata.retry.lastError = err\n if (err instanceof DOMException && err.name === 'AbortError') {\n if (\n timeoutSignal?.aborted &&\n (!userSignal || !userSignal.aborted)\n ) {\n effectiveHooks.onTimeout?.(request)\n throw new TimeoutError('signal timed out', err)\n } else if (userSignal?.aborted) {\n effectiveHooks.onAbort?.(request)\n throw new AbortError('Request was aborted by user')\n } else {\n throw new AbortError(\n 'Request was aborted',\n new DOMException('Aborted', 'AbortError')\n )\n }\n } else if (\n err instanceof TypeError &&\n /NetworkError|network error|failed to fetch|lost connection|NetworkError when attempting to fetch resource/i.test(\n err.message\n )\n ) {\n throw new NetworkError(err.message, err)\n }\n throw err\n }\n },\n effectiveRetries,\n effectiveRetryDelay,\n shouldRetryWithHook,\n request,\n combinedSignal\n )\n if (effectiveHooks.transformResponse) {\n res = await effectiveHooks.transformResponse(res, request)\n }\n await effectiveHooks.after?.(request, res)\n await effectiveHooks.onComplete?.(request, res, undefined)\n if (\n effectiveThrowOnHttpError &&\n ((res.status >= 400 && res.status < 500 && res.status !== 429) ||\n res.status >= 500 ||\n res.status === 429)\n ) {\n const { HttpError } = await import('./error.js')\n throw new HttpError(\n `HTTP error: ${res.status} ${res.statusText}`,\n res\n )\n }\n return res\n } catch (err: unknown) {\n pluginContext.metadata.retry.lastError = err\n if (lastResponse) {\n const resp = lastResponse as Response\n if (\n effectiveThrowOnHttpError &&\n ((resp.status >= 400 && resp.status < 500 && resp.status !== 429) ||\n resp.status >= 500 ||\n resp.status === 429)\n ) {\n const { HttpError } = await import('./error.js')\n throw new HttpError(\n `HTTP error: ${resp.status} ${resp.statusText}`,\n resp\n )\n }\n return resp\n }\n if (err instanceof TimeoutError) {\n await effectiveHooks.onTimeout?.(request)\n await effectiveHooks.onError?.(request, err)\n await effectiveHooks.onComplete?.(request, undefined, err)\n throw err\n }\n if (err instanceof AbortError) {\n await effectiveHooks.onAbort?.(request)\n await effectiveHooks.onError?.(request, err)\n await effectiveHooks.onComplete?.(request, undefined, err)\n throw err\n }\n if (err instanceof NetworkError) {\n await effectiveHooks.onError?.(request, err)\n await effectiveHooks.onComplete?.(request, undefined, err)\n throw err\n }\n const retryErr = new RetryLimitError(\n typeof err === 'object' &&\n err &&\n 'message' in err &&\n typeof (err as { message?: unknown }).message === 'string'\n ? (err as { message: string }).message\n : 'Retry limit reached',\n err\n )\n await effectiveHooks.onError?.(request, retryErr)\n await effectiveHooks.onComplete?.(request, undefined, retryErr)\n throw retryErr\n }\n }\n\n const baseDispatch: PluginDispatch = async () => retryWithHooks()\n\n let dispatch = baseDispatch\n for (let i = plugins.length - 1; i >= 0; i--) {\n const plugin = plugins[i]\n if (plugin.wrapDispatch) {\n dispatch = plugin.wrapDispatch(dispatch)\n }\n }\n\n const actualPromise = dispatch(pluginContext)\n .then(async (response) => {\n for (const plugin of plugins) {\n await plugin.onSuccess?.(pluginContext, response)\n }\n return response\n })\n .catch(async (err: unknown) => {\n for (const plugin of plugins) {\n await plugin.onError?.(pluginContext, err)\n }\n throw err\n })\n\n const pendingEntry: PendingRequest = {\n promise: actualPromise,\n request,\n controller,\n }\n pendingRequests.push(pendingEntry)\n\n return actualPromise.finally(async () => {\n for (const plugin of plugins) {\n await plugin.onFinally?.(pluginContext)\n }\n\n const index = pendingRequests.indexOf(pendingEntry)\n if (index > -1) {\n pendingRequests.splice(index, 1)\n }\n })\n }\n\n Object.defineProperty(client, 'pendingRequests', {\n get() {\n return pendingRequests\n },\n enumerable: false,\n configurable: false,\n })\n\n Object.defineProperty(client, 'abortAll', {\n value: abortAll,\n writable: false,\n enumerable: false,\n configurable: false,\n })\n\n Object.defineProperties(client, extensionDescriptors)\n\n return client as FFetch<PluginExtensions<TPlugins>>\n}\n"],"mappings":"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","names":["defaultDelay","ctx","retryAfter","seconds","date","waitForRetryDelay","ms","signal","resolve","onAbort","timer","retry","fn","retries","delay","shouldRetry","request","lastErr","lastRes","i","wait","err","shouldRetry","ctx","error","response","AbortError","CircuitOpenError","TimeoutError","createClient","opts","clientDefaultTimeout","clientDefaultRetries","clientDefaultRetryDelay","defaultDelay","clientDefaultShouldRetry","shouldRetry","clientDefaultHooks","fetchHandler","inputPlugins","extensionDescriptors","plugins","plugin","index","a","b","aOrder","bOrder","entry","key","descriptor","propertyKey","pendingRequests","abortAll","client","input","init","request","effectiveHooks","effectiveRetries","effectiveRetryDelay","effectiveShouldRetry","effectiveTimeout","userSignal","transformedSignal","pluginContext","effectiveThrowOnHttpError","createTimeoutSignal","timeout","controller","timeoutId","timeoutSignal","combinedSignal","signals","retryWithHooks","attempt","shouldRetryWithHook","ctx","retrying","lastResponse","res","retry","AbortError","TimeoutError","reqWithSignal","response","err","NetworkError","HttpError","resp","retryErr","RetryLimitError","dispatch","i","actualPromise","pendingEntry"]}
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@fetchkit/ffetch",
3
- "version": "5.0.0",
3
+ "version": "5.0.1",
4
4
  "description": "Fetch wrapper with configurable timeouts, retries, and TypeScript-first DX",
5
5
  "keywords": [
6
6
  "fetch",