@superutils/fetch 1.5.15 → 1.5.17

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
@@ -14,26 +14,25 @@ For full API reference check out the [docs page](https://alien45.github.io/super
14
14
 
15
15
  - [Features](#features)
16
16
  - [Installation](#installation)
17
- - [NPM](#npm)
18
- - [CDN / Browser](#cdn--browser)
19
- - [Defaults](#defaults)
20
- - [Timeout](#timeout)
21
- - [Conent Type](#content-type)
22
- - [Response Parsing](#fetch-as)
17
+ - [NPM](#npm)
18
+ - [CDN / Browser](#cdn--browser)
19
+ - [Defaults](#defaults)
20
+ - [Timeout](#timeout)
21
+ - [Conent Type](#content-type)
22
+ - [Response Parsing](#fetch-as)
23
23
  - [Usage](#usage)
24
- - [`fetch()`](#fetch): drop-in replacement for built-in `fetch()`
25
- - [`TimeoutPromise` Instance](#promise-features): finer control over the request
26
- - [`Method Specific Functions`](#methods)
27
- - [`fetch.get()`](#fetch-get)
28
- - [`fetch.get.deferred()`](#fetch-deferred): cancellable and debounced or throttled `fetch()`
29
- - [`fetch.post()`](#post): make post requests
30
- - [`fetch.post.deferred()`](#post-deferred): cancellable and debounced or throttled `post()`
31
- - [`Interceptors/Transformers`](#interceptors)
32
- - [`Retry`](#retry) Retry on request failure
33
- - [`Timeout`](#timeout) Abort request on timeout
34
- - [`createClient()`](#create-client):
35
- - [`createPostClient()`](#create-post-client):
36
- - [`fetchFunc`](#fetch-func): Using with third-party libraries (e.g., Axios)
24
+ - [`fetch()`](#fetch): drop-in replacement for built-in `fetch()`
25
+ - [`TimeoutPromise` Instance](#promise-features): finer control over the request
26
+ - [`Method Specific Functions`](#methods)
27
+ - [`fetch.get()`](#fetch-get)
28
+ - [`fetch.get.deferred()`](#fetch-deferred): cancellable and debounced or throttled `fetch()`
29
+ - [`fetch.post()`](#post): make post requests
30
+ - [`fetch.post.deferred()`](#post-deferred): cancellable and debounced or throttled `post()`
31
+ - [`Interceptors/Transformers`](#interceptors)
32
+ - [`Retry`](#retry) Retry on request failure
33
+ - [`Timeout`](#timeout) Abort request on timeout
34
+ - [`ApiClient`](#api-client): Isolated API client factory
35
+ - [`fetchFunc`](#fetch-func): Using with third-party libraries (e.g., Axios)
37
36
 
38
37
  ## Features
39
38
 
@@ -142,8 +141,8 @@ To retrieve the response in a different format (e.g., as text, a blob, or the ra
142
141
  import fetch, { FetchAs } from '@superutils/fetch'
143
142
 
144
143
  fetch
145
- .get('[DUMMYJSON-DOT-COM]/products/1', { as: FetchAs.text })
146
- .then(console.log)
144
+ .get('[DUMMYJSON-DOT-COM]/products/1', { as: FetchAs.text })
145
+ .then(console.log)
147
146
  ```
148
147
 
149
148
  > **Note:** To ensure type safety, the `as` property is excluded from `fetch.defaults` in TypeScript. Since this option determines the function's return type, setting it globally would prevent accurate type inference for individual requests.
@@ -160,8 +159,8 @@ Use as a drop-in replacement to built-in `fetch()`.
160
159
  import fetch from '@superutils/fetch'
161
160
 
162
161
  fetch('[DUMMYJSON-DOT-COM]/products/1')
163
- .then(response => response.json())
164
- .then(console.log)
162
+ .then(response => response.json())
163
+ .then(console.log)
165
164
  ```
166
165
 
167
166
  <div id="promise-features"></div>
@@ -180,11 +179,11 @@ const request = fetch('[DUMMYJSON-DOT-COM]/products/1')
180
179
  console.log(request.pending) // true
181
180
 
182
181
  request.then(() => {
183
- console.log(request.resolved) // true
184
- console.log(request.pending) // false
185
- console.log(request.rejected) // false
186
- console.log(request.aborted) // false
187
- console.log(request.timedout) // false
182
+ console.log(request.resolved) // true
183
+ console.log(request.pending) // false
184
+ console.log(request.rejected) // false
185
+ console.log(request.aborted) // false
186
+ console.log(request.timedout) // false
188
187
  })
189
188
  ```
190
189
 
@@ -201,7 +200,7 @@ request.then(result => console.log(result), console.warn)
201
200
  // Add a callback to do stuff whenever request is aborted externally.
202
201
  // This will not be invoked if fetch fails or resolves (promise finalized naturally) using the Promise executor.
203
202
  request.onEarlyFinalize.push((resolved, valueOrReason) =>
204
- console.log('Aborted externally:', { resolved, valueOrReason }),
203
+ console.log('Aborted externally:', { resolved, valueOrReason }),
205
204
  )
206
205
 
207
206
  // resolve/reject before the promise is finalized
@@ -261,8 +260,8 @@ Equivalent to `fetch(url, { method: 'get', as: 'json' })`.
261
260
  import fetch from '@superutils/fetch'
262
261
 
263
262
  fetch
264
- .get('[DUMMYJSON-DOT-COM]/products/1')
265
- .then(product => console.log({ product }))
263
+ .get('[DUMMYJSON-DOT-COM]/products/1')
264
+ .then(product => console.log({ product }))
266
265
  ```
267
266
 
268
267
  <div id="fetch-deferred"></div>
@@ -276,22 +275,22 @@ import fetch from '@superutils/fetch'
276
275
 
277
276
  // Create a debounced search function with a 300ms delay.
278
277
  const searchProducts = fetch.get.deferred({
279
- delay: 300, // Debounce delay
280
- resolveIgnored: 'WITH_UNDEFINED', // Ignored (aborted) promises will resolve with `undefined`
278
+ delay: 300, // Debounce delay
279
+ resolveIgnored: 'WITH_UNDEFINED', // Ignored (aborted) promises will resolve with `undefined`
281
280
  })
282
281
 
283
282
  // User types 'iphone'
284
283
  searchProducts('[DUMMYJSON-DOT-COM]/products/search?q=iphone').then(result => {
285
- console.log('Result for "iphone":', result)
284
+ console.log('Result for "iphone":', result)
286
285
  })
287
286
 
288
287
  // Before 300ms has passed, the user continues typing 'iphone 12'
289
288
  setTimeout(() => {
290
- searchProducts('[DUMMYJSON-DOT-COM]/products/search?q=iphone 12').then(
291
- result => {
292
- console.log('Result for "iphone 12":', result)
293
- },
294
- )
289
+ searchProducts('[DUMMYJSON-DOT-COM]/products/search?q=iphone 12').then(
290
+ result => {
291
+ console.log('Result for "iphone 12":', result)
292
+ },
293
+ )
295
294
  }, 200)
296
295
  // Outcome:
297
296
  // The first request for "iphone" is aborted.
@@ -306,18 +305,18 @@ setTimeout(() => {
306
305
  - **`delay: 0`**: Disables debouncing and throttling, enabling sequential/queue mode. Both requests ("iphone"
307
306
  and "iphone 12") would execute, but one after the other, never simultaneously.
308
307
  - **`resolveIgnored` (enum)**: Controls how the promise for an aborted request (like the first "iphone" call) resolves.
309
- 1. `ResolveIgnored.WITH_UNDEFINED` (used in the example): The promise for the aborted "iphone"
310
- request resolves with `undefined`.
311
- 2. `ResolveIgnored.WITH_LAST`: The promise for the aborted "iphone" request waits and resolves with the result
312
- of the final "iphone 12" request. Both promises resolve to the same value.
313
- 3. `ResolveIgnored.NEVER`: The promise for the aborted "iphone" request is neither resolved nor rejected.
314
- It will remain pending indefinitely.
308
+ 1. `ResolveIgnored.WITH_UNDEFINED` (used in the example): The promise for the aborted "iphone"
309
+ request resolves with `undefined`.
310
+ 2. `ResolveIgnored.WITH_LAST`: The promise for the aborted "iphone" request waits and resolves with the result
311
+ of the final "iphone 12" request. Both promises resolve to the same value.
312
+ 3. `ResolveIgnored.NEVER`: The promise for the aborted "iphone" request is neither resolved nor rejected.
313
+ It will remain pending indefinitely.
315
314
  - **`resolveError` (enum)**: Controls how failed requests are handled.
316
- 1. `ResolveError.NEVER`: The promise for a failed request will neither resolve nor reject, causing it to remain pending indefinitely.
317
- > **Warning:** Use with caution, as this may lead to memory leaks if not handled properly.
318
- 2. `ResolveError.WITH_ERROR`: The promise resolves with the `FetchError` object instead of being rejected.
319
- 3. `ResolveError.WITH_UNDEFINED`: The promise resolves with an `undefined` value upon failure.
320
- 4. `ResolveError.REJECT`: (Default) The promise is rejected with a `FetchError`, adhering to standard promise behavior.
315
+ 1. `ResolveError.NEVER`: The promise for a failed request will neither resolve nor reject, causing it to remain pending indefinitely.
316
+ > **Warning:** Use with caution, as this may lead to memory leaks if not handled properly.
317
+ 2. `ResolveError.WITH_ERROR`: The promise resolves with the `FetchError` object instead of being rejected.
318
+ 3. `ResolveError.WITH_UNDEFINED`: The promise resolves with an `undefined` value upon failure.
319
+ 4. `ResolveError.REJECT`: (Default) The promise is rejected with a `FetchError`, adhering to standard promise behavior.
321
320
 
322
321
  <!-- #### Using defaults to reduce redundancy
323
322
 
@@ -361,8 +360,8 @@ import fetch from '@superutils/fetch'
361
360
  const newProduct = { title: 'Perfume Oil' }
362
361
 
363
362
  fetch.post('[DUMMYJSON-DOT-COM]/products/add', newProduct).then(
364
- createdProduct => console.log('Product created:', createdProduct),
365
- error => console.error('Failed to create product:', error),
363
+ createdProduct => console.log('Product created:', createdProduct),
364
+ error => console.error('Failed to create product:', error),
366
365
  )
367
366
  ```
368
367
 
@@ -380,13 +379,13 @@ import PromisE from '@superutils/promise'
380
379
 
381
380
  // Create a throttled function to auto-save product updates.
382
381
  const saveProductThrottled = fetch.post.deferred(
383
- {
384
- delay: 1000, // Throttle window of 1 second
385
- throttle: true,
386
- trailing: true, // Ensures the very last update is always saved
387
- onResult: product => console.log(`[Saved] Product: ${product.title}`),
388
- },
389
- '[DUMMYJSON-DOT-COM]/products/add', // Default URL
382
+ {
383
+ delay: 1000, // Throttle window of 1 second
384
+ throttle: true,
385
+ trailing: true, // Ensures the very last update is always saved
386
+ onResult: product => console.log(`[Saved] Product: ${product.title}`),
387
+ },
388
+ '[DUMMYJSON-DOT-COM]/products/add', // Default URL
390
389
  )
391
390
  // Simulate a user typing quickly, triggering multiple saves.
392
391
  console.log('User starts typing...')
@@ -418,40 +417,40 @@ let currentRefreshToken = ''
418
417
  // Create a debounced function to refresh the auth token.
419
418
  // It waits 300ms after the last call before executing.
420
419
  const requestNewToken = fetch.post.deferred(
421
- {
422
- delay: 300, // debounce delay
423
- onResult: ({ refreshToken = '' }) => {
424
- console.log(
425
- `Auth token successfully refreshed at ${new Date().toISOString()}`,
426
- )
427
- currentRefreshToken = refreshToken
428
- },
429
- },
430
- '[DUMMYJSON-DOT-COM]/auth/refresh', // Default URL
431
- () => ({
432
- refreshToken: currentRefreshToken,
433
- expiresInMins: 30,
434
- }),
420
+ {
421
+ delay: 300, // debounce delay
422
+ onResult: ({ refreshToken = '' }) => {
423
+ console.log(
424
+ `Auth token successfully refreshed at ${new Date().toISOString()}`,
425
+ )
426
+ currentRefreshToken = refreshToken
427
+ },
428
+ },
429
+ '[DUMMYJSON-DOT-COM]/auth/refresh', // Default URL
430
+ () => ({
431
+ refreshToken: currentRefreshToken,
432
+ expiresInMins: 30,
433
+ }),
435
434
  )
436
435
 
437
436
  // First authenticate user to get the initial refresh token and then request new refresh tokens
438
437
  fetch
439
- .post<{ refreshToken: string }>(
440
- '[DUMMYJSON-DOT-COM]/auth/login',
441
- {
442
- username: 'emilys',
443
- password: 'emilyspass',
444
- expiresInMins: 30,
445
- },
446
- { credentials: 'include' },
447
- )
448
- .then(result => {
449
- currentRefreshToken = result?.refreshToken
450
-
451
- requestNewToken() // Called at 0ms
452
- PromisE.delay(50, requestNewToken) // Called at 50ms
453
- PromisE.delay(100, requestNewToken) // Called at 100ms
454
- }, console.error)
438
+ .post<{ refreshToken: string }>(
439
+ '[DUMMYJSON-DOT-COM]/auth/login',
440
+ {
441
+ username: 'emilys',
442
+ password: 'emilyspass',
443
+ expiresInMins: 30,
444
+ },
445
+ { credentials: 'include' },
446
+ )
447
+ .then(result => {
448
+ currentRefreshToken = result?.refreshToken
449
+
450
+ requestNewToken() // Called at 0ms
451
+ PromisE.delay(50, requestNewToken) // Called at 50ms
452
+ PromisE.delay(100, requestNewToken) // Called at 100ms
453
+ }, console.error)
455
454
  // Outcome:
456
455
  // The first two calls are aborted by the debounce mechanism.
457
456
  // Only the final call executes, 300ms after it was made (at the 400ms mark).
@@ -467,8 +466,8 @@ The following interceptor callbacks allow intercepting and/or transforming at di
467
466
  #### Interceptor types (executed in sequence):
468
467
 
469
468
  1. `request`: Request interceptors are executed before a HTTP request is made.
470
- - To transform the URL simply return a new or modified URL.
471
- - To transform `fetch` options simply modify the options parameter
469
+ - To transform the URL simply return a new or modified URL.
470
+ - To transform `fetch` options simply modify the options parameter
472
471
  2. `response`: Response interceptors are executed after receiving a `fetch` Response regardless of the HTTP status code.
473
472
  3. `result`: Result interceptors are executed before returning the result. To transform the result simply return a new value.
474
473
  PS: if the value of `options.as` is `FetchAs.response` (`"response"`), the value received in result will be a `Response` object.
@@ -480,8 +479,8 @@ The following interceptor callbacks allow intercepting and/or transforming at di
480
479
  - If an exception is raised while executing the interceptors, it will be gracefully ignored.
481
480
  - Value returned (transformed) by an interceptor will be carried over to the subsequent interceptor of the same type.
482
481
  - There are 2 category of interceptors:
483
- - Local: interceptors provided when making a request.
484
- - Global: interceptors that are executed application-wide on every request. Global interceptors can be added/accessed at `fetch.defaults.interceptors`. Global interceptors are always executed before local interceptors.
482
+ - Local: interceptors provided when making a request.
483
+ - Global: interceptors that are executed application-wide on every request. Global interceptors can be added/accessed at `fetch.defaults.interceptors`. Global interceptors are always executed before local interceptors.
485
484
 
486
485
  **Example: Interceptor usage**
487
486
 
@@ -489,50 +488,48 @@ The following interceptor callbacks allow intercepting and/or transforming at di
489
488
  import fetch, { FetchError } from '@superutils/fetch'
490
489
 
491
490
  const interceptors = {
492
- error: [
493
- (err, url, options) => {
494
- console.log('Request failed', err, url, options)
495
- // return nothing/undefined to keep the error unchanged
496
- // or return modified/new error
497
- err.message = 'My custom error message!'
498
- // or create a new FetchError by cloning it (make sure all the required properties are set correctly)
499
- return err.clone('My custom error message!')
500
- },
501
- ],
502
- request: [
503
- (url, options) => {
504
- // add extra headers or modify request options here
505
- options.headers.append('x-custom-header', 'some value')
506
-
507
- // transform the URL by returning a modified URL
508
- return url + '?param=value'
509
- },
510
- ],
511
- response: [
512
- (response, url, options) => {
513
- if (response.ok) return
514
- console.log('request was not successful', { url, options })
515
-
516
- // You can transform the response by returning different `Response` object or even make a completely new HTTP request.
517
- // The subsequent response interceptors will receive the returned response
518
- return fetch('[DUMMYJSON-DOT-COM]/products/1') // promise will be resolved automatically
519
- },
520
- ],
521
- result: [
522
- (result, url, options) => {
523
- const productId = Number(
524
- new URL(url).pathname.split('/products/')[1],
525
- )
526
- if (options.method === 'get' && !Number.isNaN(productId)) {
527
- result.title ??= 'Unknown title'
528
- }
529
- return result
530
- },
531
- ],
491
+ error: [
492
+ (err, url, options) => {
493
+ console.log('Request failed', err, url, options)
494
+ // return nothing/undefined to keep the error unchanged
495
+ // or return modified/new error
496
+ err.message = 'My custom error message!'
497
+ // or create a new FetchError by cloning it (make sure all the required properties are set correctly)
498
+ return err.clone('My custom error message!')
499
+ },
500
+ ],
501
+ request: [
502
+ (url, options) => {
503
+ // add extra headers or modify request options here
504
+ options.headers.append('x-custom-header', 'some value')
505
+
506
+ // transform the URL by returning a modified URL
507
+ return url + '?param=value'
508
+ },
509
+ ],
510
+ response: [
511
+ (response, url, options) => {
512
+ if (response.ok) return
513
+ console.log('request was not successful', { url, options })
514
+
515
+ // You can transform the response by returning different `Response` object or even make a completely new HTTP request.
516
+ // The subsequent response interceptors will receive the returned response
517
+ return fetch('[DUMMYJSON-DOT-COM]/products/1') // promise will be resolved automatically
518
+ },
519
+ ],
520
+ result: [
521
+ (result, url, options) => {
522
+ const productId = Number(new URL(url).pathname.split('/products/')[1])
523
+ if (options.method === 'get' && !Number.isNaN(productId)) {
524
+ result.title ??= 'Unknown title'
525
+ }
526
+ return result
527
+ },
528
+ ],
532
529
  }
533
530
  fetch
534
- .get('[DUMMYJSON-DOT-COM]/products/1', { interceptors })
535
- .then(product => console.log({ product }))
531
+ .get('[DUMMYJSON-DOT-COM]/products/1', { interceptors })
532
+ .then(product => console.log({ product }))
536
533
  ```
537
534
 
538
535
  **Example: Add global request and error interceptors**
@@ -543,135 +540,131 @@ import fetch from '@superutils/fetch'
543
540
  const { interceptors } = fetch.defaults
544
541
 
545
542
  interceptors.request.push((url, options) => {
546
- // a headers to all requests make by the application
547
- // add headers to all requests made by the application
548
- options.headers.append('x-auth', 'token')
543
+ // a headers to all requests make by the application
544
+ // add headers to all requests made by the application
545
+ options.headers.append('x-auth', 'token')
549
546
  })
550
547
 
551
548
  interceptors.error.push((err, url, options) => {
552
- // log whenever a request fails
553
- console.log('Error interceptor', err)
549
+ // log whenever a request fails
550
+ console.log('Error interceptor', err)
554
551
  })
555
552
 
556
553
  // Each time a requst is made using @superutils/fetch, the above interceptors will be executed when appropriate
557
554
  fetch('[DUMMYJSON-DOT-COM]/products/1').then(console.log, console.warn)
558
555
  ```
559
556
 
560
- <div id="retry"></div>
561
-
562
557
  ### Retry
563
558
 
559
+ <div id="retry"></div>
560
+
564
561
  The `retry` option provides a robust mechanism to automatically re-attempt failed requests, with support for both linear and exponential backoff strategies to gracefully handle transient network issues.
565
562
 
566
563
  ```javascript
567
564
  import fetch from '@superutils/fetch'
568
565
 
569
566
  fetch
570
- .get('[DUMMYJSON-DOT-COM]/products/1', {
571
- retry: 3, // If request fails, retry up to three more times
572
- // Retry on rate limits (429) or transient server errors (5xx).
573
- retryIf: r => r.status === 429 || r.status >= 500,
574
- })
575
- .then(console.log)
567
+ .get('[DUMMYJSON-DOT-COM]/products/1', {
568
+ retry: 3, // If request fails, retry up to three more times
569
+ // Retry on rate limits (429) or transient server errors (5xx).
570
+ retryIf: r => r.status === 429 || r.status >= 500,
571
+ })
572
+ .then(console.log)
576
573
  ```
577
574
 
578
- <div id="create-client"></div>
575
+ ### `ApiClient`: Isolated API client factory
579
576
 
580
- ### `createClient(fixedOptions, commonOptions, commonDeferOptions)`
577
+ <div id="api-client"></div>
581
578
 
582
- The `createClient` utility streamlines the creation of dedicated API clients by generating pre-configured fetch functions. These functions can be equipped with default options like headers, timeouts, or a specific HTTP method, which minimizes code repetition across your application. If a method is not specified during creation, the client will default to `GET`.
579
+ A fully encapsulated and isolated API client factory designed to simplify creation of dedicated API clients with integrated request execution controls.
583
580
 
584
- The returned client also includes a `.deferred()` method, providing the same debounce, throttle, and sequential execution capabilities found in functions like `fetch.get.deferred()`.
581
+ `ApiClient` creates a sandboxed environment for a specific API service. It provides complete isolation by ignoring global `fetch.defaults` by default, ensuring that instance-specific configurations remain clean and predictable. It bundles RESTful methods (`delete`, `get`, `head`, `options`, `patch`, `post`, `put`) and execution controls (debounce/throttle) into a single, cohesive unit.
585
582
 
586
- ```javascript
587
- import { createClient } from '@superutils/fetch'
583
+ #### Options Precedence & Merging
588
584
 
589
- // Create a "GET" client with default headers and a 5-second timeout
590
- const apiClient = createClient(
591
- {
592
- // fixed options (cannot be overridden)
593
- method: 'get',
594
- },
595
- {
596
- // common options (can be overridden)
597
- headers: {
598
- Authorization: 'Bearer my-secret-token',
599
- 'Content-Type': 'application/json',
600
- },
601
- timeout: 5000,
602
- },
603
- {
604
- // defer options (can be overridden)
605
- delay: 300,
606
- retry: 2, // If request fails, retry up to two more times
607
- },
608
- )
585
+ - **Options follow a strict hierarchy**: `fixedOptions` > `call options` > `commonOptions`.
586
+ - Global `fetch.defaults` are ignored by default.
587
+ - **Headers**: Merged by key. Call-level headers override common headers with the same name.
588
+ - **Interceptors**: Cumulative. Interceptors execute sequentially (Common Call → Fixed).
589
+ - **Error Messages**: Merged by key, allowing per-service customization without losing global messages.
609
590
 
610
- // Use it just like the standard fetch
611
- apiClient('[DUMMYJSON-DOT-COM]/products/1', {
612
- // The 'method' property cannot be overridden as it is used in the fixed options when creating the client.
613
- // In TypeScript, the compiler will not allow this property.
614
- // In Javascript, it will simply be ignored.
615
- // method: 'post',
616
- timeout: 3000, // The 'timeout' property can be overridden
617
- }).then(console.log, console.warn)
618
-
619
- // create a deferred client using "apiClient"
620
- const deferredClient = apiClient.deferred(
621
- { retry: 0 }, // disable retrying by overriding the `retry` defer option
622
- '[DUMMYJSON-DOT-COM]/products/1',
623
- { timeout: 3000 },
624
- )
625
- deferredClient({ timeout: 10000 }) // timeout is overridden by individual request
626
- .then(console.log, console.warn)
627
- ```
628
-
629
- <div id="create-post-client"></div>
630
-
631
- ### `createPostClient(fixedOptions, commonOptions, commonDeferOptions)`
591
+ #### Key Features
632
592
 
633
- While `createClient()` is versatile enough for any HTTP method, `createPostClient()` is specifically designed for methods that require a request body, such as `DELETE`, `PATCH`, `POST`, and `PUT`. If a method is not provided, it defaults to `POST`. The generated client accepts an additional second parameter (`data`) for the request payload.
593
+ - **Isolation**: Instance-specific options scoped to the client and isolated from other instances.
594
+ - **Base Resolution**: Automatic path joining when `apiBaseUrl` is provided.
595
+ - **Unified Error Handling**: Optional `errorPrefix` to namespace errors for easier debugging.
596
+ - **Method Suite**: Integrated `delete`, `get`, `head`, `options`, `patch`, `post`, and `put` methods.
597
+ - **Deferred Variants**: All methods support `.deferred()` for debouncing, throttling, and sequential execution.
634
598
 
635
- Similar to `createClient`, the returned function comes equipped with a `.deferred()` method, enabling debounced, throttled, or sequential execution.
599
+ #### Example: Creating an API client
636
600
 
637
601
  ```javascript
638
- import { createPostClient } from '@superutils/fetch'
602
+ import { ApiClient } from '@superutils/fetch'
603
+
604
+ // Create a client for a specific API service
605
+ const productsClient = new ApiClient(
606
+ 'https://dummyjson.com/api', // base URL
607
+ {
608
+ fixedOptions: {
609
+ // Options that cannot be overridden
610
+ headers: { Authorization: 'Bearer secret-token' },
611
+ },
612
+ commonOptions: {
613
+ // Default options (can be overridden per request)
614
+ timeout: 5000,
615
+ },
616
+ commonDeferOptions: {
617
+ // Defer options for deferred methods
618
+ delay: 300,
619
+ retry: 2,
620
+ },
621
+ errorPrefix: '[Products API] ', // Prefix for error messages
622
+ },
623
+ )
639
624
 
640
- // Create a POST client with 10-second as the default timeout
641
- const postClient = createPostClient(
642
- {
643
- headers: { 'content-type': 'application/json' },
644
- },
645
- {
646
- method: 'post',
647
- timeout: 10000,
648
- },
625
+ // Use the integrated methods
626
+ productsClient.get('/products/1').then(console.log, console.warn)
627
+
628
+ // Override per request
629
+ productsClient
630
+ .post(
631
+ '/products/add',
632
+ { title: 'New Product' },
633
+ {
634
+ timeout: 10000, // Override timeout
635
+ },
636
+ )
637
+ .then(console.log, console.warn)
638
+
639
+ // Use deferred (debounced) methods
640
+ const deferredSearch = productsClient.get.deferred(
641
+ { delay: 300 },
642
+ '/products/search',
649
643
  )
644
+ deferredSearch({ q: 'iphone' }).then(console.log)
645
+ ```
650
646
 
651
- // Invoking `postClient()` automatically applies the pre-configured options
652
- postClient(
653
- '[DUMMYJSON-DOT-COM]/products/add',
654
- { title: 'New Product' }, // data/body
655
- {}, // other options
656
- ).then(result => console.log('Product created:', result))
647
+ #### Example: Multiple clients with different configurations
657
648
 
658
- // create a deferred client using "postClient"
659
- const deferredPatchClient = postClient.deferred(
660
- {
661
- delay: 300,
662
- // prints only successful results
663
- onResult: result =>
664
- console.log('Product updated using deferred function:', result),
665
- },
666
- '[DUMMYJSON-DOT-COM]/products/add',
667
- undefined, // data to be provided later
668
- {
669
- method: 'patch', // default method for deferredPatchClient
670
- timeout: 3000,
671
- },
672
- )
673
- deferredPatchClient({ title: 'New title 1' }) // ignored by debounce
674
- deferredPatchClient({ title: 'New title 2' }) // executed
649
+ ```javascript
650
+ import { ApiClient } from '@superutils/fetch'
651
+
652
+ // Client for public API (no auth required)
653
+ const publicApi = new ApiClient('https://api.example.com/public')
654
+
655
+ // Client for authenticated endpoints
656
+ const privateApi = new ApiClient('https://api.example.com/private', {
657
+ fixedOptions: {
658
+ headers: { Authorization: 'Bearer token' },
659
+ },
660
+ commonOptions: {
661
+ timeout: 10000,
662
+ },
663
+ })
664
+
665
+ // Use them independently
666
+ publicApi.get('/posts').then(console.log)
667
+ privateApi.post('/user/profile', { name: 'John' }).then(console.log)
675
668
  ```
676
669
 
677
670
  <div id="fetch-func"></div>
@@ -682,29 +675,29 @@ The `fetchFunc` option allows you to replace the default request engine. This en
682
675
 
683
676
  ```typescript
684
677
  import fetch, {
685
- FetchCustomOptions,
686
- FetchFunc,
687
- FetchOptions,
678
+ FetchCustomOptions,
679
+ FetchFunc,
680
+ FetchOptions,
688
681
  } from '@superutils/fetch'
689
682
  import axios from 'axios'
690
683
 
691
684
  type Product = {
692
- id: number
693
- title: string
685
+ id: number
686
+ title: string
694
687
  }
695
688
  fetch
696
- .get<{ data: Product }>('[DUMMYJSON-DOT-COM]/products/1', {
697
- /**
698
- * Note: Ensure request options are compatible with the third-party
699
- * engine's configuration schema.
700
- *
701
- * Check {@link FetchOptions} (includes `FetchCustomOptions`).
702
- */
703
- fetchFunc: axios as FetchFunc,
704
-
705
- // if request fails retry maximus 3 more times
706
- retry: 3,
707
- // ...additional options
708
- })
709
- .then(({ data }) => console.log({ product: data }))
689
+ .get<{ data: Product }>('[DUMMYJSON-DOT-COM]/products/1', {
690
+ /**
691
+ * Note: Ensure request options are compatible with the third-party
692
+ * engine's configuration schema.
693
+ *
694
+ * Check {@link FetchOptions} (includes `FetchCustomOptions`).
695
+ */
696
+ fetchFunc: axios as FetchFunc,
697
+
698
+ // if request fails retry maximus 3 more times
699
+ retry: 3,
700
+ // ...additional options
701
+ })
702
+ .then(({ data }) => console.log({ product: data }))
710
703
  ```