1000fetches 0.2.0 → 0.2.2
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/LICENSE +1 -1
- package/README.md +94 -68
- package/dist/client-C7dvgNnD.js +2 -0
- package/dist/client-C7dvgNnD.js.map +1 -0
- package/dist/client-DOv6m2TJ.mjs +2 -0
- package/dist/client-DOv6m2TJ.mjs.map +1 -0
- package/dist/client.d.ts +22 -0
- package/dist/core.d.ts +57 -0
- package/dist/errors.d.ts +76 -0
- package/dist/http.cjs +2 -0
- package/dist/http.cjs.map +1 -0
- package/dist/http.d.ts +9 -0
- package/dist/http.mjs +2 -0
- package/dist/http.mjs.map +1 -0
- package/dist/index.cjs +2 -0
- package/dist/index.cjs.map +1 -0
- package/dist/index.d.ts +15 -346
- package/dist/index.mjs +2 -0
- package/dist/index.mjs.map +1 -0
- package/dist/schema.d.ts +35 -0
- package/dist/types.d.ts +145 -0
- package/dist/utils/index.d.ts +2 -0
- package/dist/utils/path.d.ts +73 -0
- package/dist/utils/streaming.d.ts +9 -0
- package/docs/API.md +754 -0
- package/docs/BEST_PRACTICES.md +606 -0
- package/docs/MIGRATION.md +719 -0
- package/package.json +40 -53
- package/dist/index.cjs.js +0 -2
- package/dist/index.cjs.js.map +0 -1
- package/dist/index.es.js +0 -787
- package/dist/index.es.js.map +0 -1
|
@@ -0,0 +1,719 @@
|
|
|
1
|
+
# Migration Guide
|
|
2
|
+
|
|
3
|
+
This guide helps you migrate from other HTTP clients to 1000fetches.
|
|
4
|
+
|
|
5
|
+
## Table of Contents
|
|
6
|
+
|
|
7
|
+
- [From Axios](#from-axios)
|
|
8
|
+
- [From Fetch API](#from-fetch-api)
|
|
9
|
+
- [From Node-Fetch](#from-node-fetch)
|
|
10
|
+
- [From Ky](#from-ky)
|
|
11
|
+
- [From Got](#from-got)
|
|
12
|
+
- [Common Migration Patterns](#common-migration-patterns)
|
|
13
|
+
|
|
14
|
+
## From Axios
|
|
15
|
+
|
|
16
|
+
### Basic Setup
|
|
17
|
+
|
|
18
|
+
**Axios:**
|
|
19
|
+
|
|
20
|
+
```typescript
|
|
21
|
+
import axios from 'axios'
|
|
22
|
+
|
|
23
|
+
const api = axios.create({
|
|
24
|
+
baseURL: 'https://api.example.com',
|
|
25
|
+
timeout: 10_000,
|
|
26
|
+
headers: {
|
|
27
|
+
Authorization: 'Bearer token',
|
|
28
|
+
},
|
|
29
|
+
})
|
|
30
|
+
```
|
|
31
|
+
|
|
32
|
+
**1000fetches:**
|
|
33
|
+
|
|
34
|
+
```typescript
|
|
35
|
+
import { createHttpClient } from '1000fetches'
|
|
36
|
+
|
|
37
|
+
const api = createHttpClient({
|
|
38
|
+
baseUrl: 'https://api.example.com',
|
|
39
|
+
timeout: 10_000,
|
|
40
|
+
headers: {
|
|
41
|
+
Authorization: 'Bearer token',
|
|
42
|
+
},
|
|
43
|
+
})
|
|
44
|
+
```
|
|
45
|
+
|
|
46
|
+
### HTTP Methods
|
|
47
|
+
|
|
48
|
+
**Axios:**
|
|
49
|
+
|
|
50
|
+
```typescript
|
|
51
|
+
// GET request
|
|
52
|
+
const usersResponse = await api.get('/users')
|
|
53
|
+
const users = usersResponse.data
|
|
54
|
+
|
|
55
|
+
// POST request
|
|
56
|
+
const createdUserResponse = await api.post('/users', {
|
|
57
|
+
name: 'John',
|
|
58
|
+
email: 'john@example.com',
|
|
59
|
+
})
|
|
60
|
+
|
|
61
|
+
// With query parameters
|
|
62
|
+
const filteredUsersResponse = await api.get('/users', {
|
|
63
|
+
params: { page: 1, limit: 10 },
|
|
64
|
+
})
|
|
65
|
+
```
|
|
66
|
+
|
|
67
|
+
**1000fetches:**
|
|
68
|
+
|
|
69
|
+
```typescript
|
|
70
|
+
const usersResponse = await api.get('/users')
|
|
71
|
+
const users = usersResponse.data
|
|
72
|
+
|
|
73
|
+
const createdUserResponse = await api.post('/users', {
|
|
74
|
+
name: 'John',
|
|
75
|
+
email: 'john@example.com',
|
|
76
|
+
})
|
|
77
|
+
|
|
78
|
+
const filteredUsersResponse = await api.get('/users', {
|
|
79
|
+
params: { page: 1, limit: 10 },
|
|
80
|
+
})
|
|
81
|
+
```
|
|
82
|
+
|
|
83
|
+
### Middleware
|
|
84
|
+
|
|
85
|
+
**Axios:**
|
|
86
|
+
|
|
87
|
+
```typescript
|
|
88
|
+
api.interceptors.request.use(
|
|
89
|
+
config => {
|
|
90
|
+
config.headers.Authorization = `Bearer ${getToken()}`
|
|
91
|
+
return config
|
|
92
|
+
},
|
|
93
|
+
error => Promise.reject(error)
|
|
94
|
+
)
|
|
95
|
+
|
|
96
|
+
api.interceptors.response.use(
|
|
97
|
+
response => response,
|
|
98
|
+
error => {
|
|
99
|
+
if (error.response?.status === 401) {
|
|
100
|
+
// Handle unauthorized
|
|
101
|
+
}
|
|
102
|
+
return Promise.reject(error)
|
|
103
|
+
}
|
|
104
|
+
)
|
|
105
|
+
```
|
|
106
|
+
|
|
107
|
+
**1000fetches:**
|
|
108
|
+
|
|
109
|
+
```typescript
|
|
110
|
+
const api = createHttpClient({
|
|
111
|
+
onRequestMiddleware: async context => {
|
|
112
|
+
context.headers.set('Authorization', `Bearer ${getToken()}`)
|
|
113
|
+
return context
|
|
114
|
+
},
|
|
115
|
+
onResponseMiddleware: async response => {
|
|
116
|
+
if (response.status === 401) {
|
|
117
|
+
// Handle unauthorized
|
|
118
|
+
}
|
|
119
|
+
return response
|
|
120
|
+
},
|
|
121
|
+
})
|
|
122
|
+
```
|
|
123
|
+
|
|
124
|
+
### Error Handling
|
|
125
|
+
|
|
126
|
+
**Axios:**
|
|
127
|
+
|
|
128
|
+
```typescript
|
|
129
|
+
try {
|
|
130
|
+
const response = await api.get('/users/1')
|
|
131
|
+
} catch (error) {
|
|
132
|
+
if (axios.isAxiosError(error)) {
|
|
133
|
+
if (error.response) {
|
|
134
|
+
console.log(error.response.status)
|
|
135
|
+
console.log(error.response.data)
|
|
136
|
+
} else if (error.request) {
|
|
137
|
+
console.log('Network error')
|
|
138
|
+
}
|
|
139
|
+
}
|
|
140
|
+
}
|
|
141
|
+
```
|
|
142
|
+
|
|
143
|
+
**1000fetches:**
|
|
144
|
+
|
|
145
|
+
```typescript
|
|
146
|
+
import { HttpError, NetworkError } from '1000fetches'
|
|
147
|
+
|
|
148
|
+
try {
|
|
149
|
+
const response = await api.get('/users/1')
|
|
150
|
+
} catch (error) {
|
|
151
|
+
if (error instanceof HttpError) {
|
|
152
|
+
console.log(error.status)
|
|
153
|
+
console.log(error.data)
|
|
154
|
+
console.log(`Request: ${error.method} ${error.url}`)
|
|
155
|
+
} else if (error instanceof NetworkError) {
|
|
156
|
+
console.log('Network error')
|
|
157
|
+
}
|
|
158
|
+
}
|
|
159
|
+
```
|
|
160
|
+
|
|
161
|
+
### Request Cancellation
|
|
162
|
+
|
|
163
|
+
**Axios:**
|
|
164
|
+
|
|
165
|
+
```typescript
|
|
166
|
+
const source = axios.CancelToken.source()
|
|
167
|
+
|
|
168
|
+
const response = await api.get('/users', {
|
|
169
|
+
cancelToken: source.token,
|
|
170
|
+
})
|
|
171
|
+
|
|
172
|
+
source.cancel('Request canceled')
|
|
173
|
+
```
|
|
174
|
+
|
|
175
|
+
**1000fetches:**
|
|
176
|
+
|
|
177
|
+
```typescript
|
|
178
|
+
const controller = new AbortController()
|
|
179
|
+
|
|
180
|
+
const response = await api.get('/users', {
|
|
181
|
+
signal: controller.signal,
|
|
182
|
+
})
|
|
183
|
+
|
|
184
|
+
controller.abort()
|
|
185
|
+
```
|
|
186
|
+
|
|
187
|
+
### Retry Logic
|
|
188
|
+
|
|
189
|
+
**Axios (with axios-retry):**
|
|
190
|
+
|
|
191
|
+
```typescript
|
|
192
|
+
import axiosRetry from 'axios-retry'
|
|
193
|
+
|
|
194
|
+
axiosRetry(api, {
|
|
195
|
+
retries: 3,
|
|
196
|
+
retryDelay: axiosRetry.exponentialDelay,
|
|
197
|
+
retryCondition: axiosRetry.isNetworkOrIdempotentRequestError,
|
|
198
|
+
})
|
|
199
|
+
```
|
|
200
|
+
|
|
201
|
+
**1000fetches:**
|
|
202
|
+
|
|
203
|
+
```typescript
|
|
204
|
+
const api = createHttpClient({
|
|
205
|
+
baseUrl: 'https://api.example.com',
|
|
206
|
+
retry: {
|
|
207
|
+
maxRetries: 3,
|
|
208
|
+
retryDelay: 300,
|
|
209
|
+
backoffFactor: 2,
|
|
210
|
+
retryStatusCodes: [408, 429, 500, 502, 503, 504],
|
|
211
|
+
retryNetworkErrors: true,
|
|
212
|
+
},
|
|
213
|
+
})
|
|
214
|
+
```
|
|
215
|
+
|
|
216
|
+
## From Fetch API
|
|
217
|
+
|
|
218
|
+
### Basic Usage
|
|
219
|
+
|
|
220
|
+
**Fetch:**
|
|
221
|
+
|
|
222
|
+
```typescript
|
|
223
|
+
const response = await fetch('https://api.example.com/users', {
|
|
224
|
+
method: 'GET',
|
|
225
|
+
headers: {
|
|
226
|
+
'Content-Type': 'application/json',
|
|
227
|
+
Authorization: 'Bearer token',
|
|
228
|
+
},
|
|
229
|
+
})
|
|
230
|
+
|
|
231
|
+
if (!response.ok) {
|
|
232
|
+
throw new Error(`HTTP error! status: ${response.status}`)
|
|
233
|
+
}
|
|
234
|
+
|
|
235
|
+
const users = await response.json()
|
|
236
|
+
```
|
|
237
|
+
|
|
238
|
+
**1000fetches:**
|
|
239
|
+
|
|
240
|
+
```typescript
|
|
241
|
+
import { createHttpClient } from '1000fetches'
|
|
242
|
+
|
|
243
|
+
const api = createHttpClient({
|
|
244
|
+
baseUrl: 'https://api.example.com',
|
|
245
|
+
headers: {
|
|
246
|
+
Authorization: 'Bearer token',
|
|
247
|
+
},
|
|
248
|
+
})
|
|
249
|
+
|
|
250
|
+
const response = await api.get('/users')
|
|
251
|
+
const users = response.data
|
|
252
|
+
```
|
|
253
|
+
|
|
254
|
+
### POST Requests
|
|
255
|
+
|
|
256
|
+
**Fetch:**
|
|
257
|
+
|
|
258
|
+
```typescript
|
|
259
|
+
const response = await fetch('https://api.example.com/users', {
|
|
260
|
+
method: 'POST',
|
|
261
|
+
headers: {
|
|
262
|
+
'Content-Type': 'application/json',
|
|
263
|
+
},
|
|
264
|
+
body: JSON.stringify({
|
|
265
|
+
name: 'John',
|
|
266
|
+
email: 'john@example.com',
|
|
267
|
+
}),
|
|
268
|
+
})
|
|
269
|
+
|
|
270
|
+
const user = await response.json()
|
|
271
|
+
```
|
|
272
|
+
|
|
273
|
+
**1000fetches:**
|
|
274
|
+
|
|
275
|
+
```typescript
|
|
276
|
+
const response = await api.post('/users', {
|
|
277
|
+
name: 'John',
|
|
278
|
+
email: 'john@example.com',
|
|
279
|
+
})
|
|
280
|
+
const user = response.data
|
|
281
|
+
```
|
|
282
|
+
|
|
283
|
+
### Error Handling
|
|
284
|
+
|
|
285
|
+
**Fetch:**
|
|
286
|
+
|
|
287
|
+
```typescript
|
|
288
|
+
try {
|
|
289
|
+
const response = await fetch('/api/users/1')
|
|
290
|
+
|
|
291
|
+
if (!response.ok) {
|
|
292
|
+
const errorData = await response.json()
|
|
293
|
+
throw new Error(`${response.status}: ${errorData.message}`)
|
|
294
|
+
}
|
|
295
|
+
|
|
296
|
+
const user = await response.json()
|
|
297
|
+
} catch (error) {
|
|
298
|
+
if (error instanceof TypeError) {
|
|
299
|
+
console.log('Network error')
|
|
300
|
+
} else {
|
|
301
|
+
console.log(error.message)
|
|
302
|
+
}
|
|
303
|
+
}
|
|
304
|
+
```
|
|
305
|
+
|
|
306
|
+
**1000fetches:**
|
|
307
|
+
|
|
308
|
+
```typescript
|
|
309
|
+
import { HttpError, NetworkError } from '1000fetches'
|
|
310
|
+
|
|
311
|
+
try {
|
|
312
|
+
const response = await api.get('/users/1')
|
|
313
|
+
const user = response.data
|
|
314
|
+
} catch (error) {
|
|
315
|
+
if (error instanceof HttpError) {
|
|
316
|
+
console.log(`${error.status}: ${error.message}`)
|
|
317
|
+
} else if (error instanceof NetworkError) {
|
|
318
|
+
console.log('Network error')
|
|
319
|
+
}
|
|
320
|
+
}
|
|
321
|
+
```
|
|
322
|
+
|
|
323
|
+
## From Node-Fetch
|
|
324
|
+
|
|
325
|
+
### Basic Migration
|
|
326
|
+
|
|
327
|
+
**Node-Fetch:**
|
|
328
|
+
|
|
329
|
+
```typescript
|
|
330
|
+
import fetch from 'node-fetch'
|
|
331
|
+
|
|
332
|
+
const response = await fetch('https://api.example.com/users', {
|
|
333
|
+
method: 'POST',
|
|
334
|
+
headers: {
|
|
335
|
+
'Content-Type': 'application/json',
|
|
336
|
+
},
|
|
337
|
+
body: JSON.stringify({ name: 'John' }),
|
|
338
|
+
})
|
|
339
|
+
|
|
340
|
+
const data = await response.json()
|
|
341
|
+
```
|
|
342
|
+
|
|
343
|
+
**1000fetches:**
|
|
344
|
+
|
|
345
|
+
```typescript
|
|
346
|
+
import { createHttpClient } from '1000fetches'
|
|
347
|
+
|
|
348
|
+
const api = createHttpClient({
|
|
349
|
+
baseUrl: 'https://api.example.com',
|
|
350
|
+
})
|
|
351
|
+
|
|
352
|
+
const response = await api.post('/users', { name: 'John' })
|
|
353
|
+
const data = response.data
|
|
354
|
+
```
|
|
355
|
+
|
|
356
|
+
### Timeout Handling
|
|
357
|
+
|
|
358
|
+
**Node-Fetch:**
|
|
359
|
+
|
|
360
|
+
```typescript
|
|
361
|
+
import fetch from 'node-fetch'
|
|
362
|
+
import AbortController from 'abort-controller'
|
|
363
|
+
|
|
364
|
+
const controller = new AbortController()
|
|
365
|
+
const timeout = setTimeout(() => controller.abort(), 5_000)
|
|
366
|
+
|
|
367
|
+
try {
|
|
368
|
+
const response = await fetch(url, {
|
|
369
|
+
signal: controller.signal,
|
|
370
|
+
})
|
|
371
|
+
} finally {
|
|
372
|
+
clearTimeout(timeout)
|
|
373
|
+
}
|
|
374
|
+
```
|
|
375
|
+
|
|
376
|
+
**1000fetches:**
|
|
377
|
+
|
|
378
|
+
```typescript
|
|
379
|
+
const api = createHttpClient({
|
|
380
|
+
timeout: 5_000,
|
|
381
|
+
})
|
|
382
|
+
|
|
383
|
+
const response = await api.get('/users')
|
|
384
|
+
```
|
|
385
|
+
|
|
386
|
+
## From Ky
|
|
387
|
+
|
|
388
|
+
### Basic Setup
|
|
389
|
+
|
|
390
|
+
**Ky:**
|
|
391
|
+
|
|
392
|
+
```typescript
|
|
393
|
+
import ky from 'ky'
|
|
394
|
+
|
|
395
|
+
const api = ky.create({
|
|
396
|
+
prefixUrl: 'https://api.example.com',
|
|
397
|
+
timeout: 10_000,
|
|
398
|
+
headers: {
|
|
399
|
+
Authorization: 'Bearer token',
|
|
400
|
+
},
|
|
401
|
+
})
|
|
402
|
+
```
|
|
403
|
+
|
|
404
|
+
**1000fetches:**
|
|
405
|
+
|
|
406
|
+
```typescript
|
|
407
|
+
import { createHttpClient } from '1000fetches'
|
|
408
|
+
|
|
409
|
+
const api = createHttpClient({
|
|
410
|
+
baseUrl: 'https://api.example.com',
|
|
411
|
+
timeout: 10_000,
|
|
412
|
+
headers: {
|
|
413
|
+
Authorization: 'Bearer token',
|
|
414
|
+
},
|
|
415
|
+
})
|
|
416
|
+
```
|
|
417
|
+
|
|
418
|
+
### Hooks (Middleware)
|
|
419
|
+
|
|
420
|
+
**Ky:**
|
|
421
|
+
|
|
422
|
+
```typescript
|
|
423
|
+
const api = ky.create({
|
|
424
|
+
hooks: {
|
|
425
|
+
beforeRequest: [
|
|
426
|
+
request => {
|
|
427
|
+
request.headers.set('Authorization', `Bearer ${getToken()}`)
|
|
428
|
+
},
|
|
429
|
+
],
|
|
430
|
+
afterResponse: [
|
|
431
|
+
(request, options, response) => {
|
|
432
|
+
if (response.status === 401) {
|
|
433
|
+
// Handle unauthorized
|
|
434
|
+
}
|
|
435
|
+
},
|
|
436
|
+
],
|
|
437
|
+
},
|
|
438
|
+
})
|
|
439
|
+
```
|
|
440
|
+
|
|
441
|
+
**1000fetches:**
|
|
442
|
+
|
|
443
|
+
```typescript
|
|
444
|
+
const api = createHttpClient({
|
|
445
|
+
onRequestMiddleware: async context => {
|
|
446
|
+
context.headers.set('Authorization', `Bearer ${getToken()}`)
|
|
447
|
+
return context
|
|
448
|
+
},
|
|
449
|
+
onResponseMiddleware: async response => {
|
|
450
|
+
if (response.status === 401) {
|
|
451
|
+
// Handle unauthorized
|
|
452
|
+
}
|
|
453
|
+
return response
|
|
454
|
+
},
|
|
455
|
+
})
|
|
456
|
+
```
|
|
457
|
+
|
|
458
|
+
### Retry Logic
|
|
459
|
+
|
|
460
|
+
**Ky:**
|
|
461
|
+
|
|
462
|
+
```typescript
|
|
463
|
+
const api = ky.create({
|
|
464
|
+
retry: {
|
|
465
|
+
limit: 3,
|
|
466
|
+
methods: ['get'],
|
|
467
|
+
statusCodes: [408, 413, 429, 500, 502, 503, 504],
|
|
468
|
+
},
|
|
469
|
+
})
|
|
470
|
+
```
|
|
471
|
+
|
|
472
|
+
**1000fetches:**
|
|
473
|
+
|
|
474
|
+
```typescript
|
|
475
|
+
const api = createHttpClient({
|
|
476
|
+
retry: {
|
|
477
|
+
maxRetries: 3,
|
|
478
|
+
retryStatusCodes: [408, 413, 429, 500, 502, 503, 504],
|
|
479
|
+
},
|
|
480
|
+
})
|
|
481
|
+
```
|
|
482
|
+
|
|
483
|
+
## From Got
|
|
484
|
+
|
|
485
|
+
### Basic Usage
|
|
486
|
+
|
|
487
|
+
**Got:**
|
|
488
|
+
|
|
489
|
+
```typescript
|
|
490
|
+
import got from 'got'
|
|
491
|
+
|
|
492
|
+
const api = got.extend({
|
|
493
|
+
prefixUrl: 'https://api.example.com',
|
|
494
|
+
timeout: { request: 10_000 },
|
|
495
|
+
headers: {
|
|
496
|
+
Authorization: 'Bearer token',
|
|
497
|
+
},
|
|
498
|
+
})
|
|
499
|
+
|
|
500
|
+
const users = await api.get('users').json()
|
|
501
|
+
```
|
|
502
|
+
|
|
503
|
+
**1000fetches:**
|
|
504
|
+
|
|
505
|
+
```typescript
|
|
506
|
+
import { createHttpClient } from '1000fetches'
|
|
507
|
+
|
|
508
|
+
const api = createHttpClient({
|
|
509
|
+
baseUrl: 'https://api.example.com',
|
|
510
|
+
timeout: 10_000,
|
|
511
|
+
headers: {
|
|
512
|
+
Authorization: 'Bearer token',
|
|
513
|
+
},
|
|
514
|
+
})
|
|
515
|
+
|
|
516
|
+
const response = await api.get('/users')
|
|
517
|
+
const users = response.data
|
|
518
|
+
```
|
|
519
|
+
|
|
520
|
+
### Hooks
|
|
521
|
+
|
|
522
|
+
**Got:**
|
|
523
|
+
|
|
524
|
+
```typescript
|
|
525
|
+
const api = got.extend({
|
|
526
|
+
hooks: {
|
|
527
|
+
beforeRequest: [
|
|
528
|
+
options => {
|
|
529
|
+
options.headers.authorization = `Bearer ${getToken()}`
|
|
530
|
+
},
|
|
531
|
+
],
|
|
532
|
+
afterResponse: [
|
|
533
|
+
response => {
|
|
534
|
+
if (response.statusCode === 401) {
|
|
535
|
+
// Handle unauthorized
|
|
536
|
+
}
|
|
537
|
+
return response
|
|
538
|
+
},
|
|
539
|
+
],
|
|
540
|
+
},
|
|
541
|
+
})
|
|
542
|
+
```
|
|
543
|
+
|
|
544
|
+
**1000fetches:**
|
|
545
|
+
|
|
546
|
+
```typescript
|
|
547
|
+
const api = createHttpClient({
|
|
548
|
+
onRequestMiddleware: async context => {
|
|
549
|
+
context.headers.set('Authorization', `Bearer ${getToken()}`)
|
|
550
|
+
return context
|
|
551
|
+
},
|
|
552
|
+
onResponseMiddleware: async response => {
|
|
553
|
+
if (response.status === 401) {
|
|
554
|
+
// Handle unauthorized
|
|
555
|
+
}
|
|
556
|
+
return response
|
|
557
|
+
},
|
|
558
|
+
})
|
|
559
|
+
```
|
|
560
|
+
|
|
561
|
+
## Common Migration Patterns
|
|
562
|
+
|
|
563
|
+
### Authentication
|
|
564
|
+
|
|
565
|
+
**Before (various libraries):**
|
|
566
|
+
|
|
567
|
+
```typescript
|
|
568
|
+
// Axios
|
|
569
|
+
api.defaults.headers.common['Authorization'] = `Bearer ${token}`
|
|
570
|
+
|
|
571
|
+
// Fetch
|
|
572
|
+
headers: { 'Authorization': `Bearer ${token}` }
|
|
573
|
+
|
|
574
|
+
// Got
|
|
575
|
+
headers: { 'authorization': `Bearer ${token}` }
|
|
576
|
+
```
|
|
577
|
+
|
|
578
|
+
**After (1000fetches):**
|
|
579
|
+
|
|
580
|
+
```typescript
|
|
581
|
+
const api = createHttpClient({
|
|
582
|
+
headers: {
|
|
583
|
+
Authorization: `Bearer ${token}`,
|
|
584
|
+
},
|
|
585
|
+
})
|
|
586
|
+
|
|
587
|
+
// Or with middleware for dynamic tokens
|
|
588
|
+
const apiWithDynamicAuth = createHttpClient({
|
|
589
|
+
onRequestMiddleware: async context => {
|
|
590
|
+
context.headers.set('Authorization', `Bearer ${await getToken()}`)
|
|
591
|
+
return context
|
|
592
|
+
},
|
|
593
|
+
})
|
|
594
|
+
```
|
|
595
|
+
|
|
596
|
+
### Response Transformation
|
|
597
|
+
|
|
598
|
+
**Before:**
|
|
599
|
+
|
|
600
|
+
```typescript
|
|
601
|
+
// Axios
|
|
602
|
+
api.interceptors.response.use(response => {
|
|
603
|
+
return response.data.result
|
|
604
|
+
})
|
|
605
|
+
|
|
606
|
+
// Manual with fetch
|
|
607
|
+
const response = await fetch('/api/users')
|
|
608
|
+
const data = await response.json()
|
|
609
|
+
return data.result
|
|
610
|
+
```
|
|
611
|
+
|
|
612
|
+
**After:**
|
|
613
|
+
|
|
614
|
+
```typescript
|
|
615
|
+
const api = createHttpClient({
|
|
616
|
+
onResponseMiddleware: async response => {
|
|
617
|
+
const data = response.data
|
|
618
|
+
|
|
619
|
+
return {
|
|
620
|
+
...response,
|
|
621
|
+
data:
|
|
622
|
+
data && typeof data === 'object' && 'result' in data
|
|
623
|
+
? data.result
|
|
624
|
+
: data,
|
|
625
|
+
}
|
|
626
|
+
},
|
|
627
|
+
})
|
|
628
|
+
```
|
|
629
|
+
|
|
630
|
+
Response middleware is authoritative, like a full response interceptor. If it changes `status`, that changed status controls whether 1000fetches resolves or throws `HttpError`. Preserve `status` when you only want to transform response data.
|
|
631
|
+
|
|
632
|
+
### Error Standardization
|
|
633
|
+
|
|
634
|
+
**Before:**
|
|
635
|
+
|
|
636
|
+
```typescript
|
|
637
|
+
// Different error handling for each library
|
|
638
|
+
try {
|
|
639
|
+
// ... request
|
|
640
|
+
} catch (error) {
|
|
641
|
+
if (axios.isAxiosError(error)) {
|
|
642
|
+
// Axios specific
|
|
643
|
+
} else if (error instanceof TypeError) {
|
|
644
|
+
// Fetch network error
|
|
645
|
+
} else if (error instanceof got.HTTPError) {
|
|
646
|
+
// Got specific
|
|
647
|
+
}
|
|
648
|
+
}
|
|
649
|
+
```
|
|
650
|
+
|
|
651
|
+
**After:**
|
|
652
|
+
|
|
653
|
+
```typescript
|
|
654
|
+
import { HttpError, NetworkError, TimeoutError } from '1000fetches'
|
|
655
|
+
|
|
656
|
+
try {
|
|
657
|
+
// ... request
|
|
658
|
+
} catch (error) {
|
|
659
|
+
if (error instanceof HttpError) {
|
|
660
|
+
// HTTP error with status, data, url, method
|
|
661
|
+
} else if (error instanceof NetworkError) {
|
|
662
|
+
// Network error
|
|
663
|
+
} else if (error instanceof TimeoutError) {
|
|
664
|
+
// Timeout error
|
|
665
|
+
}
|
|
666
|
+
}
|
|
667
|
+
```
|
|
668
|
+
|
|
669
|
+
### Type Safety
|
|
670
|
+
|
|
671
|
+
**Before:**
|
|
672
|
+
|
|
673
|
+
```typescript
|
|
674
|
+
// Manual typing
|
|
675
|
+
interface User {
|
|
676
|
+
id: number
|
|
677
|
+
name: string
|
|
678
|
+
}
|
|
679
|
+
|
|
680
|
+
const response = await api.get<User>('/users/1')
|
|
681
|
+
const user: User = response.data
|
|
682
|
+
```
|
|
683
|
+
|
|
684
|
+
**After:**
|
|
685
|
+
|
|
686
|
+
```typescript
|
|
687
|
+
import { z } from 'zod'
|
|
688
|
+
|
|
689
|
+
const UserSchema = z.object({
|
|
690
|
+
id: z.number(),
|
|
691
|
+
name: z.string(),
|
|
692
|
+
})
|
|
693
|
+
|
|
694
|
+
const response = await api.get('/users/1').schema(UserSchema)
|
|
695
|
+
// response.data is automatically typed and validated
|
|
696
|
+
```
|
|
697
|
+
|
|
698
|
+
## Migration Checklist
|
|
699
|
+
|
|
700
|
+
- [ ] **Replace HTTP client import** with 1000fetches
|
|
701
|
+
- [ ] **Update client configuration** (baseUrl, timeout, headers)
|
|
702
|
+
- [ ] **Migrate interceptors** to request/response middleware
|
|
703
|
+
- [ ] **Update error handling** to use structured error types
|
|
704
|
+
- [ ] **Add schema validation** for better type safety
|
|
705
|
+
- [ ] **Configure retry logic** if needed
|
|
706
|
+
- [ ] **Update request cancellation** to use AbortController
|
|
707
|
+
- [ ] **Test all API calls** to ensure compatibility
|
|
708
|
+
- [ ] **Update TypeScript types** if using custom interfaces
|
|
709
|
+
- [ ] **Review and update tests** to work with new client
|
|
710
|
+
|
|
711
|
+
## Benefits After Migration
|
|
712
|
+
|
|
713
|
+
✅ **Better Type Safety**: Automatic type inference and runtime validation
|
|
714
|
+
✅ **Structured Errors**: Clear error types with context information
|
|
715
|
+
✅ **Built-in Retry Logic**: No need for additional retry libraries
|
|
716
|
+
✅ **Schema Validation**: Runtime validation with popular validation libraries
|
|
717
|
+
✅ **Modern API**: Promise-based with async/await support
|
|
718
|
+
✅ **Better Developer Experience**: Enhanced error messages with request context
|
|
719
|
+
✅ **Consistent Interface**: Same API across different environments
|