@fetchkit/ffetch 5.0.1 → 5.1.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 +182 -39
- package/dist/index.cjs +223 -212
- package/dist/index.cjs.map +1 -1
- package/dist/index.d.cts +9 -48
- package/dist/index.d.ts +9 -48
- package/dist/index.js +225 -214
- 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.d.cts +1 -1
- package/dist/plugins/circuit.d.ts +1 -1
- package/dist/plugins/dedupe.d.cts +1 -1
- package/dist/plugins/dedupe.d.ts +1 -1
- package/dist/plugins/request-shortcuts.cjs +76 -0
- package/dist/plugins/request-shortcuts.cjs.map +1 -0
- package/dist/plugins/request-shortcuts.d.cts +16 -0
- package/dist/plugins/request-shortcuts.d.ts +16 -0
- package/dist/plugins/request-shortcuts.js +51 -0
- package/dist/plugins/request-shortcuts.js.map +1 -0
- package/dist/plugins/response-shortcuts.cjs +92 -0
- package/dist/plugins/response-shortcuts.cjs.map +1 -0
- package/dist/plugins/response-shortcuts.d.cts +12 -0
- package/dist/plugins/response-shortcuts.d.ts +12 -0
- package/dist/plugins/response-shortcuts.js +67 -0
- package/dist/plugins/response-shortcuts.js.map +1 -0
- package/dist/{plugins-9qcU31nU.d.cts → plugins-DqBQVM4I.d.cts} +6 -2
- package/dist/{plugins-9qcU31nU.d.ts → plugins-DqBQVM4I.d.ts} +6 -2
- package/dist/types-DbmivXba.d.cts +48 -0
- package/dist/types-DpHoyvhy.d.ts +48 -0
- package/package.json +11 -1
package/README.md
CHANGED
|
@@ -17,7 +17,41 @@ ffetch can wrap any fetch-compatible implementation (native fetch, node-fetch, u
|
|
|
17
17
|
|
|
18
18
|
ffetch uses a plugin architecture for optional features, so you only include what you need.
|
|
19
19
|
|
|
20
|
-
|
|
20
|
+
## Why ffetch
|
|
21
|
+
|
|
22
|
+
- Keep native fetch ergonomics, add production safety (timeouts, retries, error strategy).
|
|
23
|
+
- Keep your runtime flexibility (use global fetch or any fetch-compatible handler).
|
|
24
|
+
- Keep your bundle lean – **~3kb minified** (optional plugins, zero runtime dependencies).
|
|
25
|
+
|
|
26
|
+
## Table of Contents
|
|
27
|
+
|
|
28
|
+
- [@fetchkit/ffetch](#fetchkitffetch)
|
|
29
|
+
- [Why ffetch](#why-ffetch)
|
|
30
|
+
- [Table of Contents](#table-of-contents)
|
|
31
|
+
- [Key Features](#key-features)
|
|
32
|
+
- [Built-in Plugins at a Glance](#built-in-plugins-at-a-glance)
|
|
33
|
+
- [What Problems Does ffetch Solve?](#what-problems-does-ffetch-solve)
|
|
34
|
+
- [Quick Start](#quick-start)
|
|
35
|
+
- [Install](#install)
|
|
36
|
+
- [Basic Setup](#basic-setup)
|
|
37
|
+
- [Production Setup with Plugins](#production-setup-with-plugins)
|
|
38
|
+
- [Why not only native fetch?](#why-not-only-native-fetch)
|
|
39
|
+
- [Common Recipes](#common-recipes)
|
|
40
|
+
- [Using a Custom fetchHandler (SSR, metaframeworks, or polyfills)](#using-a-custom-fetchhandler-ssr-metaframeworks-or-polyfills)
|
|
41
|
+
- [Advanced Example](#advanced-example)
|
|
42
|
+
- [Custom Error Handling with `throwOnHttpError`](#custom-error-handling-with-throwonhttperror)
|
|
43
|
+
- [Documentation](#documentation)
|
|
44
|
+
- [Environment Requirements](#environment-requirements)
|
|
45
|
+
- ["AbortSignal.any is not a function"](#abortsignalany-is-not-a-function)
|
|
46
|
+
- [CDN Usage](#cdn-usage)
|
|
47
|
+
- [Deduplication Limitations](#deduplication-limitations)
|
|
48
|
+
- [Fetch vs. Axios vs. ky vs. `ffetch`](#fetch-vs-axios-vs-ky-vs-ffetch)
|
|
49
|
+
- [Try ffetch in Action](#try-ffetch-in-action)
|
|
50
|
+
- [Join the Community](#join-the-community)
|
|
51
|
+
- [Contributing](#contributing)
|
|
52
|
+
- [License](#license)
|
|
53
|
+
|
|
54
|
+
## Key Features
|
|
21
55
|
|
|
22
56
|
- **Timeouts** – per-request or global
|
|
23
57
|
- **Retries** – exponential backoff + jitter
|
|
@@ -31,59 +65,161 @@ ffetch uses a plugin architecture for optional features, so you only include wha
|
|
|
31
65
|
- **Configurable error handling** – custom error types and `throwOnHttpError` flag to throw on HTTP errors
|
|
32
66
|
- **Circuit breaker plugin (optional, prebuilt)** – automatic failure protection
|
|
33
67
|
- **Deduplication plugin (optional, prebuilt)** – automatic deduping of in-flight identical requests
|
|
68
|
+
- **Request shortcuts plugin (optional, prebuilt)** – call `client.get(url)` / `.post()` / `.put()` / `.patch()` / `.delete()` directly on the client
|
|
69
|
+
- **Response shortcuts plugin (optional, prebuilt)** – call `client(url).json()` / `.text()` / `.blob()` directly on the request promise
|
|
70
|
+
|
|
71
|
+
**Built-in error classes:** `TimeoutError`, `RetryLimitError`, `CircuitOpenError`, `HttpError`, `NetworkError`, `AbortError`
|
|
72
|
+
|
|
73
|
+
### Built-in Plugins at a Glance
|
|
74
|
+
|
|
75
|
+
All plugins are tree-shakeable — import only what you use.
|
|
76
|
+
|
|
77
|
+
- **dedupePlugin (optional)**: dedupe in-flight identical requests.
|
|
78
|
+
- **circuitPlugin (optional)**: fail fast after repeated failures.
|
|
79
|
+
- **requestShortcutsPlugin (optional)**: HTTP method shortcuts on the client (`.get()` / `.post()` / `.put()` / `.patch()` / `.delete()` / `.head()` / `.options()`).
|
|
80
|
+
- **responseShortcutsPlugin (optional)**: use `client(url).json()` / `.text()` / `.blob()` style parsing.
|
|
81
|
+
|
|
82
|
+
## What Problems Does ffetch Solve?
|
|
83
|
+
|
|
84
|
+
ffetch is ideal for:
|
|
85
|
+
|
|
86
|
+
- **Microservices and REST APIs** with retry requirements and timeout control
|
|
87
|
+
- **High-traffic client applications** that need in-flight deduplication and circuit breaker protection
|
|
88
|
+
- **SSR and metaframework apps** that require runtime flexibility (custom fetch handlers for different environments)
|
|
89
|
+
- **Type-safe request handling** with strong TypeScript support and zero runtime dependencies
|
|
34
90
|
|
|
35
91
|
## Quick Start
|
|
36
92
|
|
|
37
93
|
### Install
|
|
38
94
|
|
|
39
95
|
```bash
|
|
96
|
+
# npm
|
|
40
97
|
npm install @fetchkit/ffetch
|
|
98
|
+
|
|
99
|
+
# yarn
|
|
100
|
+
yarn add @fetchkit/ffetch
|
|
101
|
+
|
|
102
|
+
# pnpm
|
|
103
|
+
pnpm add @fetchkit/ffetch
|
|
104
|
+
|
|
105
|
+
# bun
|
|
106
|
+
bun add @fetchkit/ffetch
|
|
41
107
|
```
|
|
42
108
|
|
|
43
|
-
### Basic
|
|
109
|
+
### Basic Setup
|
|
110
|
+
|
|
111
|
+
```typescript
|
|
112
|
+
import { createClient } from '@fetchkit/ffetch'
|
|
113
|
+
|
|
114
|
+
type User = { id: number; name: string }
|
|
115
|
+
|
|
116
|
+
const api = createClient({ timeout: 5000, retries: 2 })
|
|
117
|
+
const response = await api('https://api.example.com/users')
|
|
118
|
+
|
|
119
|
+
if (!response.ok) {
|
|
120
|
+
throw new Error(`Request failed: ${response.status}`)
|
|
121
|
+
}
|
|
122
|
+
|
|
123
|
+
const users = (await response.json()) as User[]
|
|
124
|
+
```
|
|
125
|
+
|
|
126
|
+
### Production Setup with Plugins
|
|
44
127
|
|
|
45
128
|
```typescript
|
|
46
129
|
import { createClient } from '@fetchkit/ffetch'
|
|
47
130
|
import { dedupePlugin } from '@fetchkit/ffetch/plugins/dedupe'
|
|
131
|
+
import { circuitPlugin } from '@fetchkit/ffetch/plugins/circuit'
|
|
132
|
+
import { requestShortcutsPlugin } from '@fetchkit/ffetch/plugins/request-shortcuts'
|
|
133
|
+
import { responseShortcutsPlugin } from '@fetchkit/ffetch/plugins/response-shortcuts'
|
|
48
134
|
|
|
49
|
-
// Create a client with timeout, retries, and deduplication plugin
|
|
50
135
|
const api = createClient({
|
|
51
|
-
timeout:
|
|
52
|
-
retries:
|
|
53
|
-
plugins: [
|
|
54
|
-
|
|
136
|
+
timeout: 10_000,
|
|
137
|
+
retries: 2,
|
|
138
|
+
plugins: [
|
|
139
|
+
// 1) Optional: dedupe identical in-flight requests
|
|
140
|
+
dedupePlugin({ ttl: 30_000, sweepInterval: 5_000 }),
|
|
141
|
+
// 2) Optional: open the circuit after repeated failures
|
|
142
|
+
circuitPlugin({ threshold: 5, reset: 30_000 }),
|
|
143
|
+
// 3) Optional: enable request-promise parsing shortcuts
|
|
144
|
+
responseShortcutsPlugin(),
|
|
145
|
+
// 4) Optional: enable client HTTP method shortcuts
|
|
146
|
+
requestShortcutsPlugin(),
|
|
147
|
+
],
|
|
55
148
|
})
|
|
56
149
|
|
|
57
|
-
|
|
58
|
-
|
|
59
|
-
|
|
150
|
+
const users = await api
|
|
151
|
+
.get('https://api.example.com/users')
|
|
152
|
+
.json<Array<{ id: number; name: string }>>()
|
|
60
153
|
|
|
61
|
-
// Deduplication example: these two requests will be deduped
|
|
62
154
|
const p1 = api('https://api.example.com/data')
|
|
63
155
|
const p2 = api('https://api.example.com/data')
|
|
64
|
-
const [
|
|
65
|
-
|
|
156
|
+
const [res1, res2] = await Promise.all([p1, p2])
|
|
157
|
+
```
|
|
158
|
+
|
|
159
|
+
What this setup gives you:
|
|
160
|
+
|
|
161
|
+
- **Operational safety**: retries with timeout defaults.
|
|
162
|
+
- **Lower duplicate traffic (optional)**: concurrent identical requests share one in-flight call.
|
|
163
|
+
- **Faster failure recovery (optional)**: circuit breaker blocks repeated failing calls.
|
|
164
|
+
- **Cleaner request ergonomics (optional)**: `client.get(url)` / `.post(url, init)` style shortcuts.
|
|
165
|
+
- **Cleaner parsing (optional)**: `client(url).json()` style shortcuts.
|
|
166
|
+
|
|
167
|
+
### Why not only native fetch?
|
|
168
|
+
|
|
169
|
+
- Native fetch is a great baseline, but production apps usually need retries and timeout control.
|
|
170
|
+
- ffetch keeps the fetch model and adds optional resilience features.
|
|
171
|
+
- You can keep strict native behavior and only opt into plugins you need.
|
|
172
|
+
|
|
173
|
+
### Common Recipes
|
|
174
|
+
|
|
175
|
+
```typescript
|
|
176
|
+
// Throw on non-2xx/429 once retries are exhausted
|
|
177
|
+
const strict = createClient({ throwOnHttpError: true })
|
|
178
|
+
|
|
179
|
+
// Use a custom fetch implementation (SSR/framework/runtime)
|
|
180
|
+
import nodeFetch from 'node-fetch'
|
|
181
|
+
const apiWithCustomHandler = createClient({ fetchHandler: nodeFetch })
|
|
182
|
+
|
|
183
|
+
// Keep native Response flow (works with or without plugins)
|
|
184
|
+
const plainApi = createClient({ timeout: 5000 })
|
|
185
|
+
const response = await plainApi('https://api.example.com/health')
|
|
186
|
+
const text = await response.text()
|
|
66
187
|
```
|
|
67
188
|
|
|
68
189
|
### Using a Custom fetchHandler (SSR, metaframeworks, or polyfills)
|
|
69
190
|
|
|
70
191
|
```typescript
|
|
71
|
-
//
|
|
192
|
+
// Why this exists:
|
|
193
|
+
// ffetch wraps whatever fetch-compatible function you provide.
|
|
194
|
+
// This is useful when your runtime has a scoped/framework fetch,
|
|
195
|
+
// or when Node needs an explicit fetch implementation.
|
|
196
|
+
|
|
72
197
|
import { createClient } from '@fetchkit/ffetch'
|
|
198
|
+
import nodeFetch from 'node-fetch'
|
|
73
199
|
|
|
74
|
-
//
|
|
75
|
-
const
|
|
76
|
-
fetchHandler:
|
|
200
|
+
// Node.js example: provide node-fetch explicitly
|
|
201
|
+
const apiNode = createClient({
|
|
202
|
+
fetchHandler: nodeFetch,
|
|
77
203
|
timeout: 5000,
|
|
78
204
|
})
|
|
205
|
+
const nodeResponse = await apiNode('https://api.example.com/data')
|
|
206
|
+
|
|
207
|
+
// Framework example: pass the framework-scoped fetch
|
|
208
|
+
// (e.g. the fetch passed into a request handler)
|
|
209
|
+
async function loadData(frameworkFetch: typeof fetch) {
|
|
210
|
+
const api = createClient({
|
|
211
|
+
fetchHandler: frameworkFetch,
|
|
212
|
+
timeout: 5000,
|
|
213
|
+
})
|
|
214
|
+
|
|
215
|
+
const response = await api('/internal/data')
|
|
216
|
+
return response.json()
|
|
217
|
+
}
|
|
218
|
+
```
|
|
79
219
|
|
|
80
|
-
|
|
81
|
-
import nodeFetch from 'node-fetch'
|
|
82
|
-
const apiNode = createClient({ fetchHandler: nodeFetch })
|
|
220
|
+
All ffetch features (timeouts, retries, plugins, hooks) behave the same with a custom `fetchHandler`.
|
|
83
221
|
|
|
84
|
-
|
|
85
|
-
const response = await api('/api/data')
|
|
86
|
-
```
|
|
222
|
+
With `responseShortcutsPlugin()` enabled, request-promise shortcuts like `api(url).json()` also work the same.
|
|
87
223
|
|
|
88
224
|
### Advanced Example
|
|
89
225
|
|
|
@@ -197,22 +333,29 @@ npm install abort-controller-x
|
|
|
197
333
|
|
|
198
334
|
See [deduplication.md](./docs/deduplication.md) for full details.
|
|
199
335
|
|
|
200
|
-
## Fetch vs. Axios vs. `ffetch`
|
|
201
|
-
|
|
202
|
-
| Feature | Native Fetch
|
|
203
|
-
| -------------------- |
|
|
204
|
-
| Timeouts | ❌ Manual AbortController | ✅ Built-in
|
|
205
|
-
| Retries | ❌ Manual implementation
|
|
206
|
-
|
|
|
207
|
-
|
|
|
208
|
-
|
|
|
209
|
-
|
|
|
210
|
-
|
|
|
211
|
-
|
|
|
212
|
-
|
|
|
213
|
-
|
|
|
214
|
-
|
|
|
215
|
-
|
|
|
336
|
+
## Fetch vs. Axios vs. ky vs. `ffetch`
|
|
337
|
+
|
|
338
|
+
| Feature | Native Fetch | Axios | ky | ffetch |
|
|
339
|
+
| -------------------- | ------------------------------------------------------- | ------------------------------ | --------------------------------------------- | -------------------------------------------------------------------------------------- |
|
|
340
|
+
| Timeouts | ❌ Manual AbortController | ✅ Built-in | ✅ Built-in | ✅ Built-in with fallbacks |
|
|
341
|
+
| Retries | ❌ Manual implementation | ❌ Manual or plugins | ✅ Built-in | ✅ Smart exponential backoff |
|
|
342
|
+
| Response Parsing DX | ⚠️ Response methods only (`await fetch(...).then(...)`) | ✅ `response.data` convenience | ✅ `.json()/.text()/.blob()` on request chain | ✅ Optional `responseShortcutsPlugin()` (`.json()/.text()/.blob()` on request chain) |
|
|
343
|
+
| Plugin Architecture | ❌ Not available | ⚠️ Interceptors only | ⚠️ Hook-based extensions | ✅ First-class plugin pipeline (optional built-in + custom plugins) |
|
|
344
|
+
| Circuit Breaker | ❌ Not available | ❌ Manual or plugins | ❌ Manual | ✅ Automatic failure protection |
|
|
345
|
+
| Deduplication | ❌ Not available | ❌ Not available | ❌ Not available | ✅ Optional via `dedupePlugin()` |
|
|
346
|
+
| Request Monitoring | ❌ Manual tracking | ❌ Manual tracking | ❌ Manual tracking | ✅ Built-in pending requests |
|
|
347
|
+
| Error Types | ❌ Generic errors | ⚠️ HTTP errors only | ✅ Specific error classes | ✅ Specific error classes |
|
|
348
|
+
| TypeScript | ⚠️ Basic types | ⚠️ Basic types | ✅ Strong types | ✅ Full type safety |
|
|
349
|
+
| Hooks/Middleware | ❌ Not available | ✅ Interceptors | ✅ Hooks | ✅ Comprehensive lifecycle hooks |
|
|
350
|
+
| Bundle Size | ✅ Native (0kb) | ❌ ~13kb minified | ✅ Lightweight (fetch-based) | ✅ ~3kb minified |
|
|
351
|
+
| Modern APIs | ✅ Web standards | ❌ XMLHttpRequest | ✅ Fetch + modern APIs | ✅ Fetch + modern features |
|
|
352
|
+
| Custom Fetch Support | ❌ No (global only) | ❌ No | ❌ No | ✅ Yes (wrap any fetch-compatible implementation, including framework or custom fetch) |
|
|
353
|
+
|
|
354
|
+
Note: built-in plugins in ffetch are opt-in. Use `dedupePlugin()` for deduplication, `circuitPlugin()` for circuit breaking, `requestShortcutsPlugin()` for client HTTP method shortcuts, and `responseShortcutsPlugin()` for request-promise parsing shortcuts. Bundle size: ~3kb core, additional optional plugin imports are tree-shakeable.
|
|
355
|
+
|
|
356
|
+
### Try ffetch in Action
|
|
357
|
+
|
|
358
|
+
Want to see these clients in practice? Check out [ffetch-demo](https://github.com/fetch-kit/ffetch-demo) for working examples and side-by-side comparisons of how ffetch simplifies common fetch patterns.
|
|
216
359
|
|
|
217
360
|
## Join the Community
|
|
218
361
|
|