@fetchkit/ffetch 4.2.0 → 5.0.0
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/README.md +230 -211
- package/dist/index.cjs +91 -206
- package/dist/index.cjs.map +1 -1
- package/dist/index.d.cts +13 -25
- package/dist/index.d.ts +13 -25
- package/dist/index.js +91 -207
- package/dist/index.js.map +1 -1
- package/dist/index.min.js +1 -1
- package/dist/index.min.js.map +1 -1
- package/dist/plugins/circuit.cjs +122 -0
- package/dist/plugins/circuit.cjs.map +1 -0
- package/dist/plugins/circuit.d.cts +15 -0
- package/dist/plugins/circuit.d.ts +15 -0
- package/dist/plugins/circuit.js +85 -0
- package/dist/plugins/circuit.js.map +1 -0
- package/dist/plugins/dedupe.cjs +167 -0
- package/dist/plugins/dedupe.cjs.map +1 -0
- package/dist/plugins/dedupe.d.cts +22 -0
- package/dist/plugins/dedupe.d.ts +22 -0
- package/dist/plugins/dedupe.js +139 -0
- package/dist/plugins/dedupe.js.map +1 -0
- package/dist/plugins-9qcU31nU.d.cts +58 -0
- package/dist/plugins-9qcU31nU.d.ts +58 -0
- package/package.json +11 -1
package/README.md
CHANGED
|
@@ -1,211 +1,230 @@
|
|
|
1
|
-

|
|
2
|
-

|
|
3
|
-

|
|
4
|
-
|
|
5
|
-

|
|
6
|
-

|
|
7
|
-
|
|
8
|
-

|
|
9
|
-

|
|
10
|
-

|
|
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
|
-
|
|
19
|
-
|
|
20
|
-
|
|
21
|
-
|
|
22
|
-
- **
|
|
23
|
-
- **
|
|
24
|
-
- **
|
|
25
|
-
- **
|
|
26
|
-
- **
|
|
27
|
-
- **
|
|
28
|
-
- **
|
|
29
|
-
- **
|
|
30
|
-
|
|
31
|
-
|
|
32
|
-
|
|
33
|
-
|
|
34
|
-
|
|
35
|
-
|
|
36
|
-
|
|
37
|
-
|
|
38
|
-
|
|
39
|
-
|
|
40
|
-
|
|
41
|
-
|
|
42
|
-
|
|
43
|
-
|
|
44
|
-
|
|
45
|
-
|
|
46
|
-
|
|
47
|
-
|
|
48
|
-
|
|
49
|
-
|
|
50
|
-
|
|
51
|
-
|
|
52
|
-
|
|
53
|
-
|
|
54
|
-
|
|
55
|
-
|
|
56
|
-
//
|
|
57
|
-
const
|
|
58
|
-
const
|
|
59
|
-
|
|
60
|
-
//
|
|
61
|
-
|
|
62
|
-
|
|
63
|
-
|
|
64
|
-
|
|
65
|
-
```
|
|
66
|
-
|
|
67
|
-
|
|
68
|
-
|
|
69
|
-
|
|
70
|
-
|
|
71
|
-
|
|
72
|
-
|
|
73
|
-
|
|
74
|
-
|
|
75
|
-
|
|
76
|
-
|
|
77
|
-
|
|
78
|
-
|
|
79
|
-
//
|
|
80
|
-
|
|
81
|
-
|
|
82
|
-
|
|
83
|
-
|
|
84
|
-
|
|
85
|
-
```
|
|
86
|
-
|
|
87
|
-
|
|
88
|
-
|
|
89
|
-
|
|
90
|
-
|
|
91
|
-
|
|
92
|
-
|
|
93
|
-
|
|
94
|
-
|
|
95
|
-
|
|
96
|
-
|
|
97
|
-
|
|
98
|
-
|
|
99
|
-
|
|
100
|
-
|
|
101
|
-
}
|
|
102
|
-
|
|
103
|
-
|
|
104
|
-
|
|
105
|
-
|
|
106
|
-
|
|
107
|
-
|
|
108
|
-
|
|
109
|
-
|
|
110
|
-
|
|
111
|
-
|
|
112
|
-
|
|
113
|
-
|
|
114
|
-
|
|
115
|
-
|
|
116
|
-
|
|
117
|
-
|
|
118
|
-
|
|
119
|
-
|
|
120
|
-
|
|
121
|
-
|
|
122
|
-
|
|
123
|
-
|
|
124
|
-
|
|
125
|
-
|
|
126
|
-
|
|
127
|
-
|
|
128
|
-
|
|
129
|
-
|
|
130
|
-
|
|
131
|
-
|
|
132
|
-
|
|
133
|
-
|
|
134
|
-
|
|
135
|
-
|
|
136
|
-
|
|
137
|
-
|
|
138
|
-
|
|
139
|
-
|
|
140
|
-
|
|
141
|
-
|
|
142
|
-
|
|
143
|
-
|
|
144
|
-
|
|
145
|
-
|
|
146
|
-
|
|
147
|
-
|
|
148
|
-
|
|
149
|
-
**
|
|
150
|
-
|
|
151
|
-
|
|
152
|
-
|
|
153
|
-
|
|
154
|
-
|
|
155
|
-
|
|
156
|
-
|
|
157
|
-
|
|
158
|
-
|
|
159
|
-
|
|
160
|
-
|
|
161
|
-
|
|
162
|
-
|
|
163
|
-
|
|
164
|
-
|
|
165
|
-
|
|
166
|
-
|
|
167
|
-
|
|
168
|
-
|
|
169
|
-
|
|
170
|
-
|
|
171
|
-
|
|
172
|
-
|
|
173
|
-
|
|
174
|
-
|
|
175
|
-
|
|
176
|
-
|
|
177
|
-
|
|
178
|
-
|
|
179
|
-
|
|
180
|
-
|
|
181
|
-
|
|
182
|
-
|
|
183
|
-
|
|
184
|
-
|
|
185
|
-
|
|
186
|
-
|
|
187
|
-
|
|
188
|
-
|
|
189
|
-
|
|
190
|
-
|
|
191
|
-
|
|
192
|
-
|
|
193
|
-
|
|
194
|
-
|
|
195
|
-
|
|
196
|
-
|
|
197
|
-
|
|
198
|
-
|
|
199
|
-
|
|
200
|
-
|
|
201
|
-
|
|
202
|
-
|
|
203
|
-
|
|
204
|
-
|
|
205
|
-
-
|
|
206
|
-
|
|
207
|
-
|
|
208
|
-
|
|
209
|
-
|
|
210
|
-
|
|
211
|
-
|
|
1
|
+

|
|
2
|
+

|
|
3
|
+

|
|
4
|
+
|
|
5
|
+

|
|
6
|
+

|
|
7
|
+
|
|
8
|
+

|
|
9
|
+

|
|
10
|
+

|
|
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
|
+
[](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
|