@zayne-labs/callapi 0.0.6 → 0.1.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 CHANGED
@@ -1 +1,537 @@
1
1
  # CallApi
2
+
3
+ [![Build Size](https://img.shields.io/bundlephobia/minzip/@zayne-labs/callapi?label=bundle%20size&style=flat&colorA=000000&colorB=000000)](https://bundlephobia.com/result?p=@zayne-labs/callapi)[![Version](https://img.shields.io/npm/v/@zayne-labs/callapi?style=flat&colorA=000000&colorB=000000)](https://www.npmjs.com/package/@zayne-labs/callapi)
4
+
5
+ CallApi Fetch is an extra-lightweight wrapper over fetch that provides convenient options for making HTTP requests, while keeping the API familiar to the fetch api.
6
+
7
+ It takes in a url and an request options object, just like fetch, but with some additional options to make your life easier. Check out the [API Reference](#api-reference) for more details.
8
+
9
+ ## Installing CallApi
10
+
11
+ ### Through npm (recommended)
12
+
13
+ ```bash
14
+ # npm
15
+ npm install @zayne-labs/callapi
16
+
17
+ # pnpm
18
+ pnpm add @zayne-labs/callapi
19
+ ```
20
+
21
+ Then you can use it by importing it in your JavaScript file.
22
+
23
+ ```js
24
+ import { callApi } from "@zayne-labs/callapi";
25
+ ```
26
+
27
+ ### Using `callApi` without `npm`
28
+
29
+ You can import callApi directly into JavaScript through a CDN.
30
+
31
+ To do this, you first need to set your `script`'s type to `module`, then import `callApi`.
32
+
33
+ ```html
34
+ <script type="module">
35
+ import { callApi } from "https://cdn.jsdelivr.net/npm/@zayne-labs/callapi/dist/index.js";
36
+ </script>
37
+ ```
38
+
39
+ ## Quick Start
40
+
41
+ You can use callApi just like a normal `fetch` function. The only difference is you don't have to write a `response.json` or `response.text`, you could just destructure the data and data directly.
42
+
43
+ This also means that all options for the native fetch function are supported, and you can use the same syntax to send requests.
44
+
45
+ ```js
46
+ const { data, error } = await callApi("url", fetchOptions);
47
+ ```
48
+
49
+ You also have access to the response object itself via destructuring:
50
+
51
+ ```js
52
+ const { data, error, response } = await callApi("url", fetchOptions);
53
+ ```
54
+
55
+ To see how to use callApi with typescript for extra autocomplete convenience, visit the [Typescript section](#usage-with-typescript)
56
+
57
+ ## Supported response types
58
+
59
+ CallApi supports all response types offered by the fetch api like `json`, `text`,`blob` etc, so you don't have to write `response.json()`, `response.text()` or `response.blob()`.
60
+
61
+ It can configure the response type by passing in the `responseType` option and setting it to the appropriate type. By default it's set to `json`.
62
+
63
+ ```js
64
+ const { data, error } = await callApi("url", { responseType: "json" });
65
+ ```
66
+
67
+ ## Easy error handling when using `async`/`await`
68
+
69
+ CallApi lets you access all errors, both http errors and javascript errors, in an `error` object. This object contains the `errorName` (eg: TypeError, SyntaxError etx) and the error message as well.
70
+
71
+ If the error is an http error, the `errorName` property will be set to "HTTPError" and the `error` object will also contain a property `errorData`.
72
+
73
+ This property contains the error response data coming from the api. If the error is not an http error but some other error, the `errorData` property will be set to `null`.
74
+
75
+ ```js
76
+ const { data, error } = await callApi("some-url");
77
+
78
+ console.log(error.errorName);
79
+ console.log(error.message);
80
+ console.log(error.errorData);
81
+ ```
82
+
83
+ For extra convenience with typescript, visit the [Typescript section](#usage-with-typescript)
84
+
85
+ ## Helpful Features
86
+
87
+ ## Auto cancellation of redundant requests
88
+
89
+ `CallApi` automatically cancels the previous requests if the same url is called again before the previous request is resolved. This essentially only lets the last request through, hence preventing dreaded race conditions.
90
+
91
+ What this implies is that you can use `callApi` in a `useEffect` hook for instance and it will automatically cancel the previous request if the url is called again before the previous request is resolved 🤩.
92
+
93
+ This behavior can be disabled if you don't like it, by passing in `{ cancelPreviousRequest: false }` to the fetch options.
94
+
95
+ You can also cancel a request to a particular url by passing the url as a parameter to the cancel property attached to callApi.
96
+
97
+ ```js
98
+ callApi("some-url");
99
+
100
+ callApi.cancel("some-url");
101
+ ```
102
+
103
+ You can also pass a signal to callApi as an option and cancel it when you want to.
104
+
105
+ ```js
106
+ const controller = new AbortController();
107
+
108
+ callApi("some-url", { signal: controller.signal });
109
+
110
+ controller.abort();
111
+ ```
112
+
113
+ ## ✔️ Query search params
114
+
115
+ You can add `query` object as an option and callApi will create a query string for you automatically.
116
+
117
+ ```js
118
+ callApi("some-url", {
119
+ query: {
120
+ param1: "value1",
121
+ param2: "to encode",
122
+ },
123
+ });
124
+
125
+ // The above request can be written in Fetch like this:
126
+ fetch("url?param1=value1&param2=to%20encode");
127
+ ```
128
+
129
+ ## ✔️ `Content-Type` generation based on `body` content
130
+
131
+ `CallApi` sets `Content-Type` automatically depending on your `body` data. Supported data types for this automatic setting include:
132
+
133
+ - Object
134
+ - Query Strings
135
+ - FormData
136
+
137
+ If you pass in an `object`, callApi will set `Content-Type` to `application/json`. It will also `JSON.stringify` your body so you don't have to do it yourself.
138
+
139
+ ```js
140
+ callApi.post("some-url", {
141
+ body: { message: "Good game" },
142
+ });
143
+
144
+ // The above request is equivalent to this
145
+ fetch("some-url", {
146
+ method: "post",
147
+ headers: { "Content-Type": "application/json" },
148
+ body: JSON.stringify({ message: "Good game" }),
149
+ });
150
+ ```
151
+
152
+ If you pass in a string, callApi will set `Content-Type` to `application/x-www-form-urlencoded`.
153
+
154
+ `CallApi` also contains a `toQueryString` method that can help you convert objects to query strings so you can use this option easily.
155
+
156
+ ```js
157
+ import { toQueryString } from "@zayne-labs/callapi";
158
+
159
+ callApi("some-url", {
160
+ method: "POST",
161
+ body: toQueryString({ message: "Good game" }),
162
+ });
163
+
164
+ // The above request is equivalent to this
165
+ fetch("some-url", {
166
+ method: "post",
167
+ headers: { "Content-Type": "application/x-www-form-urlencoded" },
168
+ body: "message=Good%20game",
169
+ });
170
+ ```
171
+
172
+ If you pass in a FormData, callApi will let the native `fetch` function handle the `Content-Type`. Generally, this will use `multipart/form-data` with the default options.
173
+
174
+ ```js
175
+ const data = new FormData(form.elements);
176
+
177
+ callApi("some-url", { body: data });
178
+ ```
179
+
180
+ ## ✔️ Authorization header helpers
181
+
182
+ If you provide callApi with an `auth` property, it will generate an Authorization Header for you.
183
+
184
+ If you pass in a `string` (commonly for tokens) , it will generate a Bearer Auth.
185
+
186
+ ```js
187
+ callApi("some-url", { auth: "token12345" });
188
+
189
+ // The above request can be written in Fetch like this:
190
+ fetch("some-url", {
191
+ headers: { Authorization: `Bearer token12345` },
192
+ });
193
+ ```
194
+
195
+ ## ✔️ Creating a callApi Instance
196
+
197
+ You can create an instance of `callApi` with predefined options. This is super helpful if you need to send requests with similar `options`.
198
+
199
+ **Things to note:**
200
+
201
+ - All `options` that can be passed to `callApi` can also be passed to `callApi.create`.
202
+ - Any options passed to `callApi.create` will be applied to all requests made with the instance.
203
+ - If you pass a similar `options` property to the instance, the instance's options will take precedence.
204
+
205
+ ```js
206
+ import { callApi } from "@zayne-labs/callapi";
207
+
208
+ // Creating the instance, with some base options
209
+ const callAnotherApi = callApi.create({ timeout: 5000, baseURL: "https://api.example.com" });
210
+
211
+ // Using the instance
212
+ const { data, error } = await callAnotherApi("some-url");
213
+
214
+ // Overriding the timeout option (all base options can be overridden via the instance)
215
+ const { data, error } = await callAnotherApi("some-url", {
216
+ timeout: 10000,
217
+ });
218
+ ```
219
+
220
+ ## ✔️ Custom response handler and custom body serializer
221
+
222
+ By default callApi supports all response types offered by the fetch api like `json`, `text`,`blob` etc, so you don't have to write `response.json()`, `response.text()` or `response.blob()`.
223
+
224
+ But if you want to handle a response not supported by fetch, you can pass a custom handler function to the `responseParser` option.
225
+
226
+ ```js
227
+ const { data, error } = await callApi("url", {
228
+ responseParser: customResponseParser,
229
+ });
230
+ ```
231
+
232
+ Or even better, provide it as a callApi base option.
233
+
234
+ ```js
235
+ const callAnotherApi = callApi.create({
236
+ responseParser: customResponseParser,
237
+ });
238
+ ```
239
+
240
+ You could also provide a custom serializer/stringifier for objects passed to the body of the request via the `bodySerializer` option.
241
+
242
+ ```js
243
+ const callAnotherApi = callApi.create({
244
+ bodySerializer: customBodySerializer,
245
+ });
246
+ ```
247
+
248
+ ## ✔️ Interceptors (just like axios)
249
+
250
+ Providing interceptors to hook into lifecycle events of a `callApi` call is possible.
251
+
252
+ These interceptors can be either asynchronous or synchronous.
253
+
254
+ You might want to use `callApi.create` to set shared interceptors.
255
+
256
+ ### `onRequest({ request, options })`
257
+
258
+ `onRequest` is called function that is called just before the request is made, allowing you to modify the request or perform additional operations.
259
+
260
+ ```js
261
+ await callApi("/api", {
262
+ onRequest: ({ request, options }) => {
263
+ // Log request
264
+ console.log(request, options);
265
+
266
+ // Do other stuff
267
+ },
268
+ });
269
+ ```
270
+
271
+ ### `onRequestError({ error, request, options,})`
272
+
273
+ `onRequestError` when an error occurs during the fetch request and it fails, providing access to the error object, request details and fetch options.
274
+
275
+ ```js
276
+ await callApi("/api", {
277
+ onRequestError: ({ request, options, error }) => {
278
+ // Log error
279
+ console.log("[fetch request error]", request, error);
280
+ },
281
+ });
282
+ ```
283
+
284
+ ### `onResponse({ response, request, options })`
285
+
286
+ `onResponse` will be called when a successful response is received, providing access to the response, request details and fetch options.
287
+
288
+ The response object here contains all regular fetch response properties, plus a `data` property, which contains the parsed response body.
289
+
290
+ ```js
291
+ await callApi("/api", {
292
+ onResponse: ({ request, response, options }) => {
293
+ // Log response
294
+ console.log(request, response.status, response.data);
295
+
296
+ // Do other stuff
297
+ },
298
+ });
299
+ ```
300
+
301
+ ### `onResponseError({ request, options, response })`
302
+
303
+ `onResponseError` is called when an error response (status code >= 400) is received from the api, providing access to the response object, request details, and fetch options used.
304
+
305
+ The response object here contains all regular fetch response properties, plus an `errorData` property, which contains the parsed response error json response, if the server returns one.
306
+
307
+ **This to note for this interceptor to be triggered:**
308
+
309
+ - The `response.ok` property will be `false`.
310
+ - The `response.status` property will be >= 400.
311
+ - Essentially only error http responses return by the api will trigger this interceptor.
312
+ - It won't trigger for error responses not from the api, like network errors, syntax errors etc. Handle those in `onRequestError` interceptor.
313
+
314
+ The response object here contains all regular fetch response properties, plus an `errorData` property, which contains the parsed response error json response, if the server returns one.
315
+
316
+ This example uses a shared interceptor for all requests made with the instance.
317
+
318
+ ```js
319
+ const callAnotherApi = callApi.create({
320
+ onResponseError: ({ response, request, options }) => {
321
+ // Log error response
322
+ console.log(request, response.status, response.errorData);
323
+
324
+ // Perform action on various error conditions
325
+ if (response.status === 401) {
326
+ actions.clearSession();
327
+ }
328
+
329
+ if (response.status === 429) {
330
+ toast.error("Too may requests!");
331
+ }
332
+
333
+ if (response.status === 403 && response.errorData?.message === "2FA is required") {
334
+ toast.error(response.errorData?.message, {
335
+ description: "Please authenticate to continue",
336
+ });
337
+ }
338
+
339
+ if (response.status === 500) {
340
+ toast.error("Internal server Error!");
341
+ }
342
+ },
343
+ });
344
+ ```
345
+
346
+ ## ✔️ Retries
347
+
348
+ `CallApi` support retries for requests if an error happens and if the response status code is included in `retryStatusCodes` list:
349
+
350
+ **Default Retry status codes:**
351
+
352
+ - `408` - Request Timeout
353
+ - `409` - Conflict
354
+ - `425` - Too Early
355
+ - `429` - Too Many Requests
356
+ - `500` - Internal Server Error
357
+ - `502` - Bad Gateway
358
+ - `503` - Service Unavailable
359
+ - `504` - Gateway Timeout
360
+
361
+ You can specify the amount of retries and delay between them using `retry` and `retryDelay` options and also pass a custom array of codes using `retryStatusCodes` option.
362
+
363
+ You can also specify which methods should be retried by passing in a custom `retryMethods` array.
364
+
365
+ The default for `retry` is `0` retries.
366
+ The default for `retryDelay` is `0` ms.
367
+ The default for `retryMethods` is `["GET", "POST"]`.
368
+
369
+ ```js
370
+ await callApi("http://google.com/404", {
371
+ retry: 3,
372
+ retryDelay: 500, // ms
373
+ retryStatusCodes: [404, 502, 503, 504], // custom status codes for retries
374
+ retryMethods: ["POST", "PUT", "PATCH", "DELETE"], // custom methods for retries
375
+ });
376
+ ```
377
+
378
+ ## ✔️ Timeout
379
+
380
+ You can specify `timeout` in milliseconds to automatically abort a request after a timeout (default is disabled).
381
+
382
+ ```js
383
+ await callApi("http://google.com/404", {
384
+ timeout: 3000, // Timeout after 3 seconds
385
+ });
386
+ ```
387
+
388
+ ## ✔️ Throw on all errors
389
+
390
+ You can throw an error on all errors (including http errors) by passing `throwOnError` option. This makes callApi play nice with other libraries that expect a promise to resolve to a value, for example `React Query`.
391
+
392
+ ```js
393
+ const callMainApi = callApi.create({
394
+ throwOnError: true,
395
+ });
396
+
397
+ const { data, error } = useQuery({
398
+ queryKey: ["todos"],
399
+ queryFn: async () => {
400
+ // CallApi will throw an error if the request fails or there is an error response, which react query would handle
401
+ const { data } = await callMainApi("todos");
402
+
403
+ return data;
404
+ },
405
+ });
406
+ ```
407
+
408
+ Doing this with regular fetch would imply the following extra steps:
409
+
410
+ ```js
411
+ const { data, error } = useQuery({
412
+ queryKey: ["todos"],
413
+ queryFn: async () => {
414
+ const response = await fetch("todos");
415
+
416
+ if (!response.ok) {
417
+ throw new Error("Failed to fetch");
418
+ }
419
+
420
+ return response.json();
421
+ },
422
+ });
423
+ ```
424
+
425
+ For even more convenience, you can specify a resultMode for callApi in addition with the throwOnError option. Use this if you feel to lazy to make a tiny wrapper over callApi for something like react query:
426
+
427
+ ```js
428
+ const callMainApi = callApi.create({
429
+ throwOnError: true,
430
+ resultMode: "onlySuccess",
431
+ });
432
+
433
+ const { data, error } = useQuery({
434
+ queryKey: ["todos"],
435
+ // CallApi will throw on errors here, and also return only data, which react query is interested in
436
+ queryFn: () => callMainApi("todos"),
437
+ });
438
+ ```
439
+
440
+ ## Usage with Typescript
441
+
442
+ - You can provide types for the success and error data via generics, to enable autocomplete and type checking in your codebase.
443
+
444
+ ```ts
445
+ const callMainApi = callApi.create<FormResponseDataType, FormErrorResponseType>({
446
+ baseURL: BASE_AUTH_URL,
447
+
448
+ method: "POST",
449
+
450
+ retries: 3,
451
+
452
+ credentials: "same-origin",
453
+ });
454
+ ```
455
+
456
+ - Since the data and error properties destructured from callApi are a discriminated union, simply checking for and handling the `error` property will narrow down the type of the data.
457
+
458
+ This simply means that if data is available error will be null, and if error is available data will be null. Both cannot exist at the same time.
459
+
460
+ ```ts
461
+ // As is, both data and error could be null
462
+ const { data, error } = await callMainApi("some-url", {
463
+ body: { message: "Good game" },
464
+ });
465
+
466
+ if (error) {
467
+ console.error(error);
468
+ return;
469
+ }
470
+
471
+ // Now, data is no longer null
472
+ console.log(data);
473
+ ```
474
+
475
+ - The types for the object passed to onResponse and onResponseError could be augmented with type helpers provided by `@zayne-labs/callapi`.
476
+
477
+ ```ts
478
+ const callAnotherApi = callApi.create({
479
+ onResponseError: ({ response, request, options }: ResponseErrorContext<{ message?: string }>) => {
480
+ // Log error response
481
+ console.log(
482
+ request,
483
+ response.status,
484
+ // error data, coming back from api
485
+ response.errorData,
486
+ // Typescript will then understand the errorData might contains a message property
487
+ response.errorData?.message
488
+ );
489
+ },
490
+ });
491
+ ```
492
+
493
+ ## Api Reference
494
+
495
+ ### Fetch Options
496
+
497
+ - All Regular fetch options are supported as is, with only body extended to support more types.
498
+ - `body`: Optional body of the request, can be an object or any other supported body type.
499
+ - `query`: Query parameters to append to the URL.
500
+ - `auth`: Authorization header value.
501
+ - `bodySerializer`: Custom function to serialize the body object into a string.
502
+ - `responseParser`: Custom function to parse the response string into an object.
503
+ - `resultMode`: Mode of the result, can influence how results are handled or returned. (default: "all")
504
+ - `cancelRedundantRequests`: If true, cancels previous unfinished requests to the same URL. (default: true)
505
+ - `baseURL`: Base URL to be prepended to all request URLs.
506
+ - `timeout`: Request timeout in milliseconds.
507
+ - `defaultErrorMessage`: Default error message to use if none is provided from a response. (default: "Failed to fetch data from server!")
508
+ - `throwOnError`: If true or the function returns true, throws errors instead of returning them.
509
+
510
+ - `responseType`: Expected response type, affects how response is parsed. (default: "json")
511
+ - `retries`: Number of retry attempts for failed requests. (default: 0)
512
+ - `retryDelay`: Delay between retries in milliseconds. (default: 500)
513
+ - `retryCodes`: HTTP status codes that trigger a retry. (default: [409, 425, 429, 500, 502, 503, 504])
514
+ - `retryMethods`: HTTP methods that are allowed to retry. (default: ["GET", "POST"])
515
+ - `meta`: An optional field for additional information, typically used for logging or tracing.
516
+ - `onRequest`: Interceptor called just before the request is made, allowing for modifications or additional operations.
517
+ - `onRequestError`: Interceptor called when an error occurs during the fetch request.
518
+ - `onResponse`: Interceptor called when a successful response is received from the API.
519
+ - `onResponseError`: Interceptor called when an error response is received from the API.
520
+
521
+ ### Methods
522
+
523
+ - `callApi.create(options)`: Creates an instance of `callApi` with shared base configurations.
524
+ - `callApi.cancel(url: string)`: Cancels an ongoing request to the specified URL.
525
+
526
+ ### Utility Functions
527
+
528
+ - `isHTTPError`: Type guard for if an error is an HTTPError
529
+
530
+ - `isHTTPErrorInstance`: Type guard for if an error is an instance of HTTPError. Useful for when `throwAllErrors` option is set to `true`
531
+
532
+ - `toQueryString`: Converts an object to a URL query string
533
+
534
+ ## Acknowledgements
535
+
536
+ - Credits to `ofetch` by unjs for some of the ideas for the features in the library like the function-based interceptors, retries etc
537
+ - Credits to `zl-fetch` fetch wrapper as well for the inspiration behind a few features in this library as well
@@ -1,9 +1,10 @@
1
1
  'use strict';
2
2
 
3
+ var _typeof = require('./typeof');
3
4
  var utils = require('./utils');
4
5
 
5
- const m=C=>{const l=new Map,[D,A]=utils.splitConfig(C??{}),{headers:p,signal:w,...M}=D,o=async(s,f)=>{const[q,S]=utils.splitConfig(f??{}),e={bodySerializer:JSON.stringify,responseType:"json",baseURL:"",retries:0,retryDelay:0,retryCodes:utils.defaultRetryCodes,retryMethods:utils.defaultRetryMethods,defaultErrorMessage:"Failed to fetch data from server!",cancelRedundantRequests:!0,...A,...S},{signal:O=w,body:a,headers:T,...x}=q,g=l.get(s);if(g&&e.cancelRedundantRequests){const t=new DOMException("Cancelled the previous unfinished request","AbortError");g.abort(t);}const c=new AbortController;l.set(s,c);const B=e.timeout?AbortSignal.timeout(e.timeout):null,E=AbortSignal.any([c.signal,B??c.signal,O??c.signal]),n={signal:E,method:"GET",body:utils.isObject(a)?e.bodySerializer(a):a,headers:p||T||utils.isObject(a)?{...utils.isObject(a)&&{"Content-Type":"application/json",Accept:"application/json"},...utils.isFormData(a)&&{"Content-Type":"multipart/form-data"},...utils.objectifyHeaders(p),...utils.objectifyHeaders(T)}:void 0,...M,...x};try{await e.onRequest?.({request:n,options:e});const t=await fetch(`${e.baseURL}${utils.mergeUrlWithParams(s,e.query)}`,n);if(!E.aborted&&e.retries!==0&&!t.ok&&e.retryCodes.includes(t.status)&&e.retryMethods.includes(n.method))return await utils.wait(e.retryDelay),await o(s,{...f,retries:e.retries-1});if(!t.ok){const u=await utils.getResponseData(t,e.responseType,e.responseParser);throw new utils.HTTPError({response:{...t,errorData:u},defaultErrorMessage:e.defaultErrorMessage})}const r=await utils.getResponseData(t,e.responseType,e.responseParser);return await e.onResponse?.({response:{...t,data:r},request:n,options:e}),utils.resolveSuccessResult({successData:r,response:t,options:e})}catch(t){const i=utils.$resolveErrorResult({error:t,options:e});if(t instanceof DOMException&&t.name==="TimeoutError"){const r=`Request timed out after ${e.timeout}ms`;return console.info(`%cTimeoutError: ${r}`,"color: red; font-weight: 500; font-size: 14px;"),console.trace("TimeoutError"),i({message:r})}if(t instanceof DOMException&&t.name==="AbortError"){const r="Request was cancelled";return console.info(`%AbortError: ${r}`,"color: red; font-weight: 500; font-size: 14px;"),console.trace("AbortError"),i({message:r})}if(utils.isHTTPErrorInstance(t)){const{errorData:r,...u}=t.response;return await e.onResponseError?.({response:{...u,errorData:r},request:n,options:e}),i({errorData:r,response:u,message:r?.message})}return await e.onRequestError?.({request:n,error:t,options:e}),i()}finally{l.delete(s);}};return o.create=m,o.isHTTPErrorInfo=utils.isHTTPErrorInfo,o.isHTTPErrorInstance=utils.isHTTPErrorInstance,o.cancel=s=>l.get(s)?.abort(),o},G=m();var k=G;
6
+ const b=m=>{const i=new Map,[C,D]=utils.splitConfig(m??{}),{headers:p,body:w,signal:A,...M}=C,l=async(o,f)=>{const[q,B]=utils.splitConfig(f??{}),e={bodySerializer:JSON.stringify,responseType:"json",baseURL:"",retries:0,retryDelay:0,retryCodes:utils.defaultRetryCodes,retryMethods:utils.defaultRetryMethods,defaultErrorMessage:"Failed to fetch data from server!",cancelRedundantRequests:!0,...D,...B},{signal:O=A,body:s=w,headers:g,...x}=q,R=i.get(o);if(R&&e.cancelRedundantRequests){const t=new DOMException("Cancelled the previous unfinished request","AbortError");R.abort(t);}const c=new AbortController;i.set(o,c);const S=e.timeout?AbortSignal.timeout(e.timeout):null,T=AbortSignal.any([c.signal,S??c.signal,O??c.signal]),a={signal:T,method:"GET",body:_typeof.isObject(s)?e.bodySerializer(s):s,headers:p||g||e.auth||_typeof.isObject(s)?{..._typeof.isObject(s)&&{"Content-Type":"application/json",Accept:"application/json"},..._typeof.isFormData(s)&&{"Content-Type":"multipart/form-data"},..._typeof.isString(s)&&{"Content-Type":"application/x-www-form-urlencoded"},...!!e.auth&&{Authorization:`Bearer ${e.auth}`},...utils.objectifyHeaders(p),...utils.objectifyHeaders(g)}:void 0,...M,...x};try{await e.onRequest?.({request:a,options:e});const t=await fetch(`${e.baseURL}${utils.mergeUrlWithParams(o,e.query)}`,a);if(!t.ok&&!T.aborted&&e.retries>0&&e.retryCodes.includes(t.status)&&e.retryMethods.includes(a.method))return await utils.waitUntil(e.retryDelay),await l(o,{...f,retries:e.retries-1});if(!t.ok){const u=await utils.getResponseData(t,e.responseType,e.responseParser);throw new utils.HTTPError({response:{...t,errorData:u},defaultErrorMessage:e.defaultErrorMessage})}const r=await utils.getResponseData(t,e.responseType,e.responseParser);return await e.onResponse?.({response:{...t,data:r},request:a,options:e}),utils.resolveSuccessResult({successData:r,response:t,options:e})}catch(t){const n=utils.$resolveErrorResult({error:t,options:e});if(t instanceof DOMException&&t.name==="TimeoutError"){const r=`Request timed out after ${e.timeout}ms`;return console.info(`%cTimeoutError: ${r}`,"color: red; font-weight: 500; font-size: 14px;"),console.trace("TimeoutError"),n({message:r})}if(t instanceof DOMException&&t.name==="AbortError"){const r="Request was cancelled";return console.info(`%AbortError: ${r}`,"color: red; font-weight: 500; font-size: 14px;"),console.trace("AbortError"),n({message:r})}if(utils.isHTTPErrorInstance(t)){const{errorData:r,...u}=t.response;return await e.onResponseError?.({response:{...u,errorData:r},request:a,options:e}),n({errorData:r,response:u,message:r?.message})}return await e.onRequestError?.({request:a,error:t,options:e}),n()}finally{i.delete(o);}};return l.create=b,l.cancel=o=>i.get(o)?.abort(),l};
6
7
 
7
- module.exports = k;
8
+ exports.createFetchClient = b;
8
9
  //# sourceMappingURL=out.js.map
9
10
  //# sourceMappingURL=createFetchClient.cjs.map
@@ -1 +1 @@
1
- {"version":3,"sources":["../src/createFetchClient.ts"],"names":["$resolveErrorResult","HTTPError","defaultRetryCodes","defaultRetryMethods","getResponseData","isFormData","isHTTPErrorInfo","isHTTPErrorInstance","isObject","mergeUrlWithParams","objectifyHeaders","resolveSuccessResult","splitConfig","wait","createFetchClient","baseConfig","abortControllerStore","baseFetchConfig","baseExtraOptions","baseHeaders","baseSignal","restOfBaseFetchConfig","callApi","url","config","fetchConfig","extraOptions","options","signal","body","headers","restOfFetchConfig","prevFetchController","reason","newFetchController","timeoutSignal","combinedSignal","requestInit","response","errorData","successData","error","resolveErrorResult","message","createFetchClient_default"],"mappings":"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","sourcesContent":["import type {\n\t$RequestConfig,\n\tAbortSignalWithAny,\n\tBaseConfig,\n\tExtraOptions,\n\tFetchConfig,\n\tGetCallApiResult,\n\tPossibleErrorObject,\n\tResultStyleUnion,\n} from \"./types\";\nimport {\n\t$resolveErrorResult,\n\tHTTPError,\n\tdefaultRetryCodes,\n\tdefaultRetryMethods,\n\tgetResponseData,\n\tisFormData,\n\tisHTTPErrorInfo,\n\tisHTTPErrorInstance,\n\tisObject,\n\tmergeUrlWithParams,\n\tobjectifyHeaders,\n\tresolveSuccessResult,\n\tsplitConfig,\n\twait,\n} from \"./utils\";\n\nconst createFetchClient = <\n\tTBaseData,\n\tTBaseErrorData,\n\tTBaseResultMode extends ResultStyleUnion = undefined,\n>(\n\tbaseConfig?: BaseConfig<TBaseData, TBaseErrorData, TBaseResultMode>\n) => {\n\tconst abortControllerStore = new Map<string, AbortController>();\n\n\tconst [baseFetchConfig, baseExtraOptions] = splitConfig(baseConfig ?? {});\n\n\tconst { headers: baseHeaders, signal: baseSignal, ...restOfBaseFetchConfig } = baseFetchConfig;\n\n\tconst callApi = async <\n\t\tTData = TBaseData,\n\t\tTErrorData = TBaseErrorData,\n\t\tTResultMode extends ResultStyleUnion = TBaseResultMode,\n\t>(\n\t\turl: string,\n\t\tconfig?: FetchConfig<TData, TErrorData, TResultMode>\n\t): Promise<GetCallApiResult<TData, TErrorData, TResultMode>> => {\n\t\ttype CallApiResult = GetCallApiResult<TData, TErrorData, TResultMode>;\n\n\t\tconst [fetchConfig, extraOptions] = splitConfig(config ?? {});\n\n\t\tconst options = {\n\t\t\tbodySerializer: JSON.stringify,\n\t\t\tresponseType: \"json\",\n\t\t\tbaseURL: \"\",\n\t\t\tretries: 0,\n\t\t\tretryDelay: 0,\n\t\t\tretryCodes: defaultRetryCodes,\n\t\t\tretryMethods: defaultRetryMethods,\n\t\t\tdefaultErrorMessage: \"Failed to fetch data from server!\",\n\t\t\tcancelRedundantRequests: true,\n\t\t\t...baseExtraOptions,\n\t\t\t...extraOptions,\n\t\t} satisfies ExtraOptions;\n\n\t\tconst { signal = baseSignal, body, headers, ...restOfFetchConfig } = fetchConfig;\n\n\t\tconst prevFetchController = abortControllerStore.get(url);\n\n\t\tif (prevFetchController && options.cancelRedundantRequests) {\n\t\t\tconst reason = new DOMException(\"Cancelled the previous unfinished request\", \"AbortError\");\n\t\t\tprevFetchController.abort(reason);\n\t\t}\n\n\t\tconst newFetchController = new AbortController();\n\n\t\tabortControllerStore.set(url, newFetchController);\n\n\t\tconst timeoutSignal = options.timeout ? AbortSignal.timeout(options.timeout) : null;\n\n\t\t// FIXME - Remove this type cast once TS updates its lib-dom types for AbortSignal to include the any() method\n\t\tconst combinedSignal = (AbortSignal as AbortSignalWithAny).any([\n\t\t\tnewFetchController.signal,\n\t\t\ttimeoutSignal ?? newFetchController.signal,\n\t\t\tsignal ?? newFetchController.signal,\n\t\t]);\n\n\t\tconst requestInit = {\n\t\t\tsignal: combinedSignal,\n\n\t\t\tmethod: \"GET\",\n\n\t\t\tbody: isObject(body) ? options.bodySerializer(body) : body,\n\n\t\t\t// == Return undefined if there are no headers provided or if the body is not an object\n\t\t\theaders:\n\t\t\t\tbaseHeaders || headers || isObject(body)\n\t\t\t\t\t? {\n\t\t\t\t\t\t\t...(isObject(body) && {\n\t\t\t\t\t\t\t\t\"Content-Type\": \"application/json\",\n\t\t\t\t\t\t\t\tAccept: \"application/json\",\n\t\t\t\t\t\t\t}),\n\t\t\t\t\t\t\t...(isFormData(body) && {\n\t\t\t\t\t\t\t\t\"Content-Type\": \"multipart/form-data\",\n\t\t\t\t\t\t\t}),\n\t\t\t\t\t\t\t...objectifyHeaders(baseHeaders),\n\t\t\t\t\t\t\t...objectifyHeaders(headers),\n\t\t\t\t\t\t}\n\t\t\t\t\t: undefined,\n\n\t\t\t...restOfBaseFetchConfig,\n\t\t\t...restOfFetchConfig,\n\t\t} satisfies $RequestConfig;\n\n\t\ttry {\n\t\t\tawait options.onRequest?.({ request: requestInit, options });\n\n\t\t\tconst response = await fetch(\n\t\t\t\t`${options.baseURL}${mergeUrlWithParams(url, options.query)}`,\n\t\t\t\trequestInit\n\t\t\t);\n\n\t\t\tconst shouldRetry =\n\t\t\t\t!combinedSignal.aborted &&\n\t\t\t\toptions.retries !== 0 &&\n\t\t\t\t!response.ok &&\n\t\t\t\toptions.retryCodes.includes(response.status) &&\n\t\t\t\toptions.retryMethods.includes(requestInit.method);\n\n\t\t\tif (shouldRetry) {\n\t\t\t\tawait wait(options.retryDelay);\n\n\t\t\t\treturn await callApi(url, { ...config, retries: options.retries - 1 });\n\t\t\t}\n\n\t\t\tif (!response.ok) {\n\t\t\t\tconst errorData = await getResponseData<TErrorData>(\n\t\t\t\t\tresponse,\n\t\t\t\t\toptions.responseType,\n\t\t\t\t\toptions.responseParser\n\t\t\t\t);\n\n\t\t\t\t// == Pushing all error handling responsibilities to the catch block\n\t\t\t\tthrow new HTTPError({\n\t\t\t\t\tresponse: { ...response, errorData },\n\t\t\t\t\tdefaultErrorMessage: options.defaultErrorMessage,\n\t\t\t\t});\n\t\t\t}\n\n\t\t\tconst successData = await getResponseData<TData>(\n\t\t\t\tresponse,\n\t\t\t\toptions.responseType,\n\t\t\t\toptions.responseParser\n\t\t\t);\n\n\t\t\tawait options.onResponse?.({\n\t\t\t\tresponse: { ...response, data: successData },\n\t\t\t\trequest: requestInit,\n\t\t\t\toptions,\n\t\t\t});\n\n\t\t\treturn resolveSuccessResult<CallApiResult>({ successData, response, options });\n\n\t\t\t// == Exhaustive Error handling\n\t\t} catch (error) {\n\t\t\tconst resolveErrorResult = $resolveErrorResult<CallApiResult>({ error, options });\n\n\t\t\tif (error instanceof DOMException && error.name === \"TimeoutError\") {\n\t\t\t\tconst message = `Request timed out after ${options.timeout}ms`;\n\n\t\t\t\tconsole.info(`%cTimeoutError: ${message}`, \"color: red; font-weight: 500; font-size: 14px;\");\n\t\t\t\tconsole.trace(\"TimeoutError\");\n\n\t\t\t\treturn resolveErrorResult({ message });\n\t\t\t}\n\n\t\t\tif (error instanceof DOMException && error.name === \"AbortError\") {\n\t\t\t\tconst message = `Request was cancelled`;\n\n\t\t\t\tconsole.info(`%AbortError: ${message}`, \"color: red; font-weight: 500; font-size: 14px;\");\n\t\t\t\tconsole.trace(\"AbortError\");\n\n\t\t\t\treturn resolveErrorResult({ message });\n\t\t\t}\n\n\t\t\tif (isHTTPErrorInstance<TErrorData>(error)) {\n\t\t\t\tconst { errorData, ...response } = error.response;\n\n\t\t\t\tawait options.onResponseError?.({\n\t\t\t\t\tresponse: { ...response, errorData },\n\t\t\t\t\trequest: requestInit,\n\t\t\t\t\toptions,\n\t\t\t\t});\n\n\t\t\t\treturn resolveErrorResult({\n\t\t\t\t\terrorData,\n\t\t\t\t\tresponse,\n\t\t\t\t\tmessage: (errorData as PossibleErrorObject)?.message,\n\t\t\t\t});\n\t\t\t}\n\n\t\t\t// == At this point only the request errors exist, so the request error interceptor is called\n\t\t\tawait options.onRequestError?.({ request: requestInit, error: error as Error, options });\n\n\t\t\treturn resolveErrorResult();\n\n\t\t\t// == Removing the now unneeded AbortController from store\n\t\t} finally {\n\t\t\tabortControllerStore.delete(url);\n\t\t}\n\t};\n\n\tcallApi.create = createFetchClient;\n\tcallApi.isHTTPErrorInfo = isHTTPErrorInfo;\n\tcallApi.isHTTPErrorInstance = isHTTPErrorInstance;\n\tcallApi.cancel = (url: string) => abortControllerStore.get(url)?.abort();\n\n\treturn callApi;\n};\n\nconst callApi = createFetchClient();\n\nexport default callApi;\n"]}
1
+ {"version":3,"sources":["../src/createFetchClient.ts"],"names":["isFormData","isObject","isString","$resolveErrorResult","HTTPError","defaultRetryCodes","defaultRetryMethods","getResponseData","isHTTPErrorInstance","mergeUrlWithParams","objectifyHeaders","resolveSuccessResult","splitConfig","waitUntil","createFetchClient","baseConfig","abortControllerStore","baseFetchConfig","baseExtraOptions","baseHeaders","baseBody","baseSignal","restOfBaseFetchConfig","callApi","url","config","fetchConfig","extraOptions","options","signal","body","headers","restOfFetchConfig","prevFetchController","reason","newFetchController","timeoutSignal","combinedSignal","requestInit","response","errorData","successData","error","resolveErrorResult","message"],"mappings":"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","sourcesContent":["import { isFormData, isObject, isString } from \"./typeof\";\nimport type {\n\t$RequestOptions,\n\tAbortSignalWithAny,\n\tBaseConfig,\n\tExtraOptions,\n\tFetchConfig,\n\tGetCallApiResult,\n\tPossibleErrorObject,\n\tResultModeUnion,\n} from \"./types\";\nimport {\n\t$resolveErrorResult,\n\tHTTPError,\n\tdefaultRetryCodes,\n\tdefaultRetryMethods,\n\tgetResponseData,\n\tisHTTPErrorInstance,\n\tmergeUrlWithParams,\n\tobjectifyHeaders,\n\tresolveSuccessResult,\n\tsplitConfig,\n\twaitUntil,\n} from \"./utils\";\n\nconst createFetchClient = <TBaseData, TBaseErrorData, TBaseResultMode extends ResultModeUnion = undefined>(\n\tbaseConfig?: BaseConfig<TBaseData, TBaseErrorData, TBaseResultMode>\n) => {\n\tconst abortControllerStore = new Map<string, AbortController>();\n\n\tconst [baseFetchConfig, baseExtraOptions] = splitConfig(baseConfig ?? {});\n\n\tconst {\n\t\theaders: baseHeaders,\n\t\tbody: baseBody,\n\t\tsignal: baseSignal,\n\t\t...restOfBaseFetchConfig\n\t} = baseFetchConfig;\n\n\tconst callApi = async <\n\t\tTData = TBaseData,\n\t\tTErrorData = TBaseErrorData,\n\t\tTResultMode extends ResultModeUnion = TBaseResultMode,\n\t>(\n\t\turl: string,\n\t\tconfig?: FetchConfig<TData, TErrorData, TResultMode>\n\t): Promise<GetCallApiResult<TData, TErrorData, TResultMode>> => {\n\t\ttype CallApiResult = GetCallApiResult<TData, TErrorData, TResultMode>;\n\n\t\tconst [fetchConfig, extraOptions] = splitConfig(config ?? {});\n\n\t\tconst options = {\n\t\t\tbodySerializer: JSON.stringify,\n\t\t\tresponseType: \"json\",\n\t\t\tbaseURL: \"\",\n\t\t\tretries: 0,\n\t\t\tretryDelay: 0,\n\t\t\tretryCodes: defaultRetryCodes,\n\t\t\tretryMethods: defaultRetryMethods,\n\t\t\tdefaultErrorMessage: \"Failed to fetch data from server!\",\n\t\t\tcancelRedundantRequests: true,\n\t\t\t...baseExtraOptions,\n\t\t\t...extraOptions,\n\t\t} satisfies ExtraOptions;\n\n\t\tconst { signal = baseSignal, body = baseBody, headers, ...restOfFetchConfig } = fetchConfig;\n\n\t\tconst prevFetchController = abortControllerStore.get(url);\n\n\t\tif (prevFetchController && options.cancelRedundantRequests) {\n\t\t\tconst reason = new DOMException(\"Cancelled the previous unfinished request\", \"AbortError\");\n\t\t\tprevFetchController.abort(reason);\n\t\t}\n\n\t\tconst newFetchController = new AbortController();\n\n\t\tabortControllerStore.set(url, newFetchController);\n\n\t\tconst timeoutSignal = options.timeout ? AbortSignal.timeout(options.timeout) : null;\n\n\t\t// FIXME - Remove this type cast once TS updates its lib-dom types for AbortSignal to include the any() method\n\t\tconst combinedSignal = (AbortSignal as AbortSignalWithAny).any([\n\t\t\tnewFetchController.signal,\n\t\t\ttimeoutSignal ?? newFetchController.signal,\n\t\t\tsignal ?? newFetchController.signal,\n\t\t]);\n\n\t\tconst requestInit = {\n\t\t\tsignal: combinedSignal,\n\n\t\t\tmethod: \"GET\",\n\n\t\t\tbody: isObject(body) ? options.bodySerializer(body) : body,\n\n\t\t\t// == Return undefined if the following conditions are not met (so that native fetch would auto set the correct headers):\n\t\t\t// - headers are provided\n\t\t\t// - The body is an object\n\t\t\t// - The auth option is provided\n\t\t\theaders:\n\t\t\t\tbaseHeaders || headers || options.auth || isObject(body)\n\t\t\t\t\t? {\n\t\t\t\t\t\t\t...(isObject(body) && {\n\t\t\t\t\t\t\t\t\"Content-Type\": \"application/json\",\n\t\t\t\t\t\t\t\tAccept: \"application/json\",\n\t\t\t\t\t\t\t}),\n\t\t\t\t\t\t\t...(isFormData(body) && {\n\t\t\t\t\t\t\t\t\"Content-Type\": \"multipart/form-data\",\n\t\t\t\t\t\t\t}),\n\t\t\t\t\t\t\t...(isString(body) && {\n\t\t\t\t\t\t\t\t\"Content-Type\": \"application/x-www-form-urlencoded\",\n\t\t\t\t\t\t\t}),\n\t\t\t\t\t\t\t...(Boolean(options.auth) && {\n\t\t\t\t\t\t\t\tAuthorization: `Bearer ${options.auth}`,\n\t\t\t\t\t\t\t}),\n\t\t\t\t\t\t\t...objectifyHeaders(baseHeaders),\n\t\t\t\t\t\t\t...objectifyHeaders(headers),\n\t\t\t\t\t\t}\n\t\t\t\t\t: undefined,\n\n\t\t\t...restOfBaseFetchConfig,\n\t\t\t...restOfFetchConfig,\n\t\t} satisfies $RequestOptions;\n\n\t\ttry {\n\t\t\tawait options.onRequest?.({ request: requestInit, options });\n\n\t\t\tconst response = await fetch(\n\t\t\t\t`${options.baseURL}${mergeUrlWithParams(url, options.query)}`,\n\t\t\t\trequestInit\n\t\t\t);\n\n\t\t\tconst shouldRetry =\n\t\t\t\t!response.ok &&\n\t\t\t\t!combinedSignal.aborted &&\n\t\t\t\toptions.retries > 0 &&\n\t\t\t\toptions.retryCodes.includes(response.status) &&\n\t\t\t\toptions.retryMethods.includes(requestInit.method);\n\n\t\t\tif (shouldRetry) {\n\t\t\t\tawait waitUntil(options.retryDelay);\n\n\t\t\t\treturn await callApi(url, { ...config, retries: options.retries - 1 });\n\t\t\t}\n\n\t\t\tif (!response.ok) {\n\t\t\t\tconst errorData = await getResponseData<TErrorData>(\n\t\t\t\t\tresponse,\n\t\t\t\t\toptions.responseType,\n\t\t\t\t\toptions.responseParser\n\t\t\t\t);\n\n\t\t\t\t// == Pushing all error handling responsibilities to the catch block\n\t\t\t\tthrow new HTTPError({\n\t\t\t\t\tresponse: { ...response, errorData },\n\t\t\t\t\tdefaultErrorMessage: options.defaultErrorMessage,\n\t\t\t\t});\n\t\t\t}\n\n\t\t\tconst successData = await getResponseData<TData>(\n\t\t\t\tresponse,\n\t\t\t\toptions.responseType,\n\t\t\t\toptions.responseParser\n\t\t\t);\n\n\t\t\tawait options.onResponse?.({\n\t\t\t\tresponse: { ...response, data: successData },\n\t\t\t\trequest: requestInit,\n\t\t\t\toptions,\n\t\t\t});\n\n\t\t\treturn resolveSuccessResult<CallApiResult>({ successData, response, options });\n\n\t\t\t// == Exhaustive Error handling\n\t\t} catch (error) {\n\t\t\tconst resolveErrorResult = $resolveErrorResult<CallApiResult>({ error, options });\n\n\t\t\tif (error instanceof DOMException && error.name === \"TimeoutError\") {\n\t\t\t\tconst message = `Request timed out after ${options.timeout}ms`;\n\n\t\t\t\tconsole.info(`%cTimeoutError: ${message}`, \"color: red; font-weight: 500; font-size: 14px;\");\n\t\t\t\tconsole.trace(\"TimeoutError\");\n\n\t\t\t\treturn resolveErrorResult({ message });\n\t\t\t}\n\n\t\t\tif (error instanceof DOMException && error.name === \"AbortError\") {\n\t\t\t\tconst message = `Request was cancelled`;\n\n\t\t\t\tconsole.info(`%AbortError: ${message}`, \"color: red; font-weight: 500; font-size: 14px;\");\n\t\t\t\tconsole.trace(\"AbortError\");\n\n\t\t\t\treturn resolveErrorResult({ message });\n\t\t\t}\n\n\t\t\tif (isHTTPErrorInstance<TErrorData>(error)) {\n\t\t\t\tconst { errorData, ...response } = error.response;\n\n\t\t\t\tawait options.onResponseError?.({\n\t\t\t\t\tresponse: { ...response, errorData },\n\t\t\t\t\trequest: requestInit,\n\t\t\t\t\toptions,\n\t\t\t\t});\n\n\t\t\t\treturn resolveErrorResult({\n\t\t\t\t\terrorData,\n\t\t\t\t\tresponse,\n\t\t\t\t\tmessage: (errorData as PossibleErrorObject)?.message,\n\t\t\t\t});\n\t\t\t}\n\n\t\t\t// == At this point only the request errors exist, so the request error interceptor is called\n\t\t\tawait options.onRequestError?.({ request: requestInit, error: error as Error, options });\n\n\t\t\treturn resolveErrorResult();\n\n\t\t\t// == Removing the now unneeded AbortController from store\n\t\t} finally {\n\t\t\tabortControllerStore.delete(url);\n\t\t}\n\t};\n\n\tcallApi.create = createFetchClient;\n\n\tcallApi.cancel = (url: string) => abortControllerStore.get(url)?.abort();\n\n\treturn callApi;\n};\n\nexport { createFetchClient };\n"]}
@@ -1,21 +1,9 @@
1
- import { R as ResultStyleUnion, F as FetchConfig, G as GetCallApiResult, B as BaseConfig, H as HTTPError } from './types-B8arQl4O.cjs';
1
+ import { R as ResultModeUnion, B as BaseConfig, F as FetchConfig, G as GetCallApiResult } from './types-CGXvcfe9.cjs';
2
2
 
3
- declare const callApi: {
4
- <TData = unknown, TErrorData = unknown, TResultMode extends ResultStyleUnion = undefined>(url: string, config?: FetchConfig<TData, TErrorData, TResultMode> | undefined): Promise<GetCallApiResult<TData, TErrorData, TResultMode>>;
5
- create: <TBaseData, TBaseErrorData, TBaseResultMode extends ResultStyleUnion = undefined>(baseConfig?: BaseConfig<TBaseData, TBaseErrorData, TBaseResultMode>) => {
6
- <TData = TBaseData, TErrorData = TBaseErrorData, TResultMode extends ResultStyleUnion = TBaseResultMode>(url: string, config?: FetchConfig<TData, TErrorData, TResultMode>): Promise<GetCallApiResult<TData, TErrorData, TResultMode>>;
7
- create: any;
8
- isHTTPErrorInfo: (errorInfo: Record<string, unknown> | null) => errorInfo is {
9
- errorName: "HTTPError";
10
- };
11
- isHTTPErrorInstance: <TErrorResponse>(error: unknown) => error is HTTPError<TErrorResponse>;
12
- cancel(url: string): void | undefined;
13
- };
14
- isHTTPErrorInfo: (errorInfo: Record<string, unknown> | null) => errorInfo is {
15
- errorName: "HTTPError";
16
- };
17
- isHTTPErrorInstance: <TErrorResponse>(error: unknown) => error is HTTPError<TErrorResponse>;
3
+ declare const createFetchClient: <TBaseData, TBaseErrorData, TBaseResultMode extends ResultModeUnion = undefined>(baseConfig?: BaseConfig<TBaseData, TBaseErrorData, TBaseResultMode>) => {
4
+ <TData = TBaseData, TErrorData = TBaseErrorData, TResultMode extends ResultModeUnion = TBaseResultMode>(url: string, config?: FetchConfig<TData, TErrorData, TResultMode>): Promise<GetCallApiResult<TData, TErrorData, TResultMode>>;
5
+ create: any;
18
6
  cancel(url: string): void | undefined;
19
7
  };
20
8
 
21
- export { callApi as default };
9
+ export { createFetchClient };
@@ -1,21 +1,9 @@
1
- import { R as ResultStyleUnion, F as FetchConfig, G as GetCallApiResult, B as BaseConfig, H as HTTPError } from './types-B8arQl4O.js';
1
+ import { R as ResultModeUnion, B as BaseConfig, F as FetchConfig, G as GetCallApiResult } from './types-CGXvcfe9.js';
2
2
 
3
- declare const callApi: {
4
- <TData = unknown, TErrorData = unknown, TResultMode extends ResultStyleUnion = undefined>(url: string, config?: FetchConfig<TData, TErrorData, TResultMode> | undefined): Promise<GetCallApiResult<TData, TErrorData, TResultMode>>;
5
- create: <TBaseData, TBaseErrorData, TBaseResultMode extends ResultStyleUnion = undefined>(baseConfig?: BaseConfig<TBaseData, TBaseErrorData, TBaseResultMode>) => {
6
- <TData = TBaseData, TErrorData = TBaseErrorData, TResultMode extends ResultStyleUnion = TBaseResultMode>(url: string, config?: FetchConfig<TData, TErrorData, TResultMode>): Promise<GetCallApiResult<TData, TErrorData, TResultMode>>;
7
- create: any;
8
- isHTTPErrorInfo: (errorInfo: Record<string, unknown> | null) => errorInfo is {
9
- errorName: "HTTPError";
10
- };
11
- isHTTPErrorInstance: <TErrorResponse>(error: unknown) => error is HTTPError<TErrorResponse>;
12
- cancel(url: string): void | undefined;
13
- };
14
- isHTTPErrorInfo: (errorInfo: Record<string, unknown> | null) => errorInfo is {
15
- errorName: "HTTPError";
16
- };
17
- isHTTPErrorInstance: <TErrorResponse>(error: unknown) => error is HTTPError<TErrorResponse>;
3
+ declare const createFetchClient: <TBaseData, TBaseErrorData, TBaseResultMode extends ResultModeUnion = undefined>(baseConfig?: BaseConfig<TBaseData, TBaseErrorData, TBaseResultMode>) => {
4
+ <TData = TBaseData, TErrorData = TBaseErrorData, TResultMode extends ResultModeUnion = TBaseResultMode>(url: string, config?: FetchConfig<TData, TErrorData, TResultMode>): Promise<GetCallApiResult<TData, TErrorData, TResultMode>>;
5
+ create: any;
18
6
  cancel(url: string): void | undefined;
19
7
  };
20
8
 
21
- export { callApi as default };
9
+ export { createFetchClient };