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.
@@ -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