@zayne-labs/callapi 0.5.0 → 0.5.2
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/README.md +1 -649
- package/dist/cjs/index.cjs +1 -1
- package/dist/cjs/index.cjs.map +1 -1
- package/dist/cjs/index.d.cts +3 -3
- package/dist/esm/chunk-AYODUGGR.js +8 -0
- package/dist/esm/chunk-AYODUGGR.js.map +1 -0
- package/dist/esm/{chunk-YRSDB5GD.js → chunk-GSYOXK46.js} +2 -2
- package/dist/esm/{chunk-YRSDB5GD.js.map → chunk-GSYOXK46.js.map} +1 -1
- package/dist/esm/createFetchClient.d.ts +3 -3
- package/dist/esm/createFetchClient.js +2 -2
- package/dist/esm/index.d.ts +1 -1
- package/dist/esm/index.js +2 -2
- package/dist/esm/{types-6I2zlFag.d.ts → types-DIDORyP8.d.ts} +1 -1
- package/dist/esm/utils.d.ts +1 -1
- package/dist/esm/utils.js +1 -1
- package/package.json +1 -1
- package/dist/esm/chunk-E7YEJJYJ.js +0 -8
- package/dist/esm/chunk-E7YEJJYJ.js.map +0 -1
package/README.md
CHANGED
|
@@ -6,7 +6,7 @@ CallApi Fetch is an extra-lightweight wrapper over fetch that provides quality o
|
|
|
6
6
|
|
|
7
7
|
It takes in a url and a request options object, just like fetch, but with some additional options to make your life easier. Check out the [API Reference](#api-reference) for a quick look at each option.
|
|
8
8
|
|
|
9
|
-
|
|
9
|
+
# Docs
|
|
10
10
|
|
|
11
11
|
[View Documentation website](https://zayne-callapi.netlify.app/)
|
|
12
12
|
|
|
@@ -45,651 +45,3 @@ To do this, you first need to set your `script`'s type to `module`, then import
|
|
|
45
45
|
</script>
|
|
46
46
|
```
|
|
47
47
|
|
|
48
|
-
## Quick Start
|
|
49
|
-
|
|
50
|
-
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 error directly.
|
|
51
|
-
|
|
52
|
-
This also means that all options for the native fetch options are supported, and you can use the same syntax to send requests.
|
|
53
|
-
|
|
54
|
-
```js
|
|
55
|
-
const { data, error } = await callApi("url", fetchOptions);
|
|
56
|
-
```
|
|
57
|
-
|
|
58
|
-
You also have access to the response object itself via destructuring:
|
|
59
|
-
|
|
60
|
-
```js
|
|
61
|
-
const { data, error, response } = await callApi("url", fetchOptions);
|
|
62
|
-
```
|
|
63
|
-
|
|
64
|
-
To see how to use callApi with typescript for extra autocomplete convenience, visit the [Typescript section](#usage-with-typescript)
|
|
65
|
-
|
|
66
|
-
<br>
|
|
67
|
-
|
|
68
|
-
# Helpful Features
|
|
69
|
-
|
|
70
|
-
## ✔️ Supported response types
|
|
71
|
-
|
|
72
|
-
CallApi supports all response types offered by the fetch api like `json`, `text`,`blob`,`formData` etc, so you don't have to write `response.json()`, `response.text()`, `response.formData()` etc.
|
|
73
|
-
|
|
74
|
-
You can configure the response type you prefer by passing in the `responseType` option and setting it to the form you want the data from the response to be in. By default it's set to `json`.
|
|
75
|
-
|
|
76
|
-
```js
|
|
77
|
-
// Json (default)
|
|
78
|
-
const { data } = await callApi("url", { responseType: "json" });
|
|
79
|
-
// Text
|
|
80
|
-
const { data } = await callApi("url", { responseType: "text" });
|
|
81
|
-
// Blob, etc
|
|
82
|
-
const { data } = await callApi("url", { responseType: "blob" });
|
|
83
|
-
|
|
84
|
-
|
|
85
|
-
// Doing this in fetch would imply:
|
|
86
|
-
const response = await fetch("some-url");
|
|
87
|
-
|
|
88
|
-
const data = await response.json(); // Or response.text() or response.blob() etc
|
|
89
|
-
```
|
|
90
|
-
|
|
91
|
-
## ✔️ Easy error handling via `async`/`await`
|
|
92
|
-
|
|
93
|
-
CallApi provides a unified error handling through an `error` object, which captures both HTTP errors (errors coming as a response from the api) and standard JavaScript errors.
|
|
94
|
-
|
|
95
|
-
The `error` object contains the following properties:
|
|
96
|
-
|
|
97
|
-
1. `name`: A string indicating the type of error (e.g., 'TypeError', 'SyntaxError', 'HTTPError').
|
|
98
|
-
2. `message`: The error message describing what went wrong.
|
|
99
|
-
3. `errorData`: The error data, which can be an error response from the API or a standard JavaScript error object.
|
|
100
|
-
|
|
101
|
-
For HTTP errors:
|
|
102
|
-
|
|
103
|
-
- `name` is set to "HTTPError"
|
|
104
|
-
- An additional `errorData` property contains the error response data from the API.
|
|
105
|
-
|
|
106
|
-
For non-HTTP errors (e.g., TypeError, SyntaxError):
|
|
107
|
-
|
|
108
|
-
- `name` reflects the specific JavaScript error type (i.e., 'TypeError', 'SyntaxError')
|
|
109
|
-
- The `errorData` property contains the original JavaScript Error object.
|
|
110
|
-
|
|
111
|
-
This structure allows you to easily identify and handle different types of errors that may occur during API calls.
|
|
112
|
-
|
|
113
|
-
```js
|
|
114
|
-
const { data, error } = await callApi("some-url");
|
|
115
|
-
|
|
116
|
-
console.log(error.name);
|
|
117
|
-
console.log(error.message);
|
|
118
|
-
// Will reference the would contain the parsed error response data if it is an HTTPError, else it would just reference the corresponding js Error object
|
|
119
|
-
console.log(error.errorData);
|
|
120
|
-
```
|
|
121
|
-
|
|
122
|
-
For extra convenience with typescript, visit the [Typescript section](#usage-with-typescript)
|
|
123
|
-
|
|
124
|
-
## ✔️ Automatic Cancellation of Redundant Requests (No more race conditions🤩)
|
|
125
|
-
|
|
126
|
-
`CallApi` uses an internal request management system to ensure that only the most recent request to a given URL is processed, hence preventing the dreaded race conditions.
|
|
127
|
-
|
|
128
|
-
**How this feature Works in detail**:
|
|
129
|
-
|
|
130
|
-
- When a request is made, `callApi` internals check if there's an ongoing request to the same URL.
|
|
131
|
-
- If a pending request exists, it's automatically cancelled.
|
|
132
|
-
- Then the new request is sent.
|
|
133
|
-
- This process is repeated until it is certain that only the latest request to that same URL is allowed to proceed.
|
|
134
|
-
|
|
135
|
-

|
|
136
|
-
|
|
137
|
-
**Key takeaways**:
|
|
138
|
-
|
|
139
|
-
- *Automatic Cancellation*: When multiple requests are made to the same URL in quick succession, `callApi` automatically cancels any pending previous requests, allowing only the latest request to proceed.
|
|
140
|
-
- *Race Condition Prevention*: This mechanism eliminates race conditions that can occur when rapid, successive API calls are made, such as during fast typing in a search input, button clicks, etc.
|
|
141
|
-
- *Ideal for React Hooks*: This feature is particularly useful when `callApi` is used within React's useEffect hook or similar scenarios where component updates might trigger multiple API calls.
|
|
142
|
-
- *Configurable*: If you prefer to handle request management differently, you can disable this feature by setting `{ cancelRedundantRequests: false }` in the fetch options. No pressure 👌.
|
|
143
|
-
- *Manual Cancellation*: You can manually cancel ongoing requests to a specific URL using the cancel method attached to `callApi`. You can also pass an abort controller signal to `callApi` (just like with fetch) as an option and abort the request when you want to.
|
|
144
|
-
|
|
145
|
-
Using `callApi.cancel`:
|
|
146
|
-
|
|
147
|
-
```js
|
|
148
|
-
callApi("some-url");
|
|
149
|
-
|
|
150
|
-
callApi.cancel("some-url");
|
|
151
|
-
|
|
152
|
-
// With reason
|
|
153
|
-
callApi.cancel("some-url", "Cancelled due to some reason")
|
|
154
|
-
```
|
|
155
|
-
|
|
156
|
-
Using `AbortController`:
|
|
157
|
-
|
|
158
|
-
```js
|
|
159
|
-
const controller = new AbortController();
|
|
160
|
-
|
|
161
|
-
callApi("some-url", { signal: controller.signal });
|
|
162
|
-
|
|
163
|
-
controller.abort();
|
|
164
|
-
```
|
|
165
|
-
|
|
166
|
-
## ✔️ Query search params
|
|
167
|
-
|
|
168
|
-
You can add `query` object as an option and callApi will create a query string for you automatically.
|
|
169
|
-
|
|
170
|
-
```js
|
|
171
|
-
callApi("some-url", {
|
|
172
|
-
query: {
|
|
173
|
-
param1: "value1",
|
|
174
|
-
param2: "to encode",
|
|
175
|
-
},
|
|
176
|
-
});
|
|
177
|
-
|
|
178
|
-
// The above request can be written in Fetch like this:
|
|
179
|
-
fetch("url?param1=value1¶m2=to%20encode");
|
|
180
|
-
```
|
|
181
|
-
|
|
182
|
-
## ✔️ Content-Type Generation
|
|
183
|
-
|
|
184
|
-
`CallApi` takes care of setting the `Content-Type` for you based on the body content. Here's how it works:
|
|
185
|
-
|
|
186
|
-
**Automatic Content-Type Setting**: `CallApi` sets the `Content-Type` automatically depending on your body data.
|
|
187
|
-
Data types eligible for this automatic setting include:
|
|
188
|
-
|
|
189
|
-
- Object
|
|
190
|
-
- Query Strings
|
|
191
|
-
- FormData
|
|
192
|
-
|
|
193
|
-
**Object/JSON Handling**: If you pass in an object, `CallApi` will set `Content-Type` to `application/json` for you. It will also handle `JSON.stringify` process for you (although you can always pass in a custom stringifier/serializer if you want), so you don't have to do it yourself.
|
|
194
|
-
|
|
195
|
-
```js
|
|
196
|
-
callApi.post("some-url", {
|
|
197
|
-
body: { message: "Good game" },
|
|
198
|
-
});
|
|
199
|
-
|
|
200
|
-
// The above request is equivalent to this
|
|
201
|
-
fetch("some-url", {
|
|
202
|
-
method: "post",
|
|
203
|
-
headers: { "Content-Type": "application/json" },
|
|
204
|
-
body: JSON.stringify({ message: "Good game" }),
|
|
205
|
-
});
|
|
206
|
-
```
|
|
207
|
-
|
|
208
|
-
**Query String Handling**: If you pass in a string, `CallApi` will ensure the `Content-Type` is set to `application/x-www-form-urlencoded` for you.
|
|
209
|
-
|
|
210
|
-
`CallApi` also includes a `toQueryString` method that helps you convert objects to query strings, for extra convenience.
|
|
211
|
-
|
|
212
|
-
```js
|
|
213
|
-
import { toQueryString } from "@zayne-labs/callapi";
|
|
214
|
-
|
|
215
|
-
callApi("some-url", {
|
|
216
|
-
method: "POST",
|
|
217
|
-
body: toQueryString({ message: "Good game" }),
|
|
218
|
-
});
|
|
219
|
-
|
|
220
|
-
// The above request is equivalent to this
|
|
221
|
-
fetch("some-url", {
|
|
222
|
-
method: "post",
|
|
223
|
-
headers: { "Content-Type": "application/x-www-form-urlencoded" },
|
|
224
|
-
body: "message=Good%20game",
|
|
225
|
-
});
|
|
226
|
-
```
|
|
227
|
-
|
|
228
|
-
**FormData Handling**: 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.
|
|
229
|
-
|
|
230
|
-
```js
|
|
231
|
-
const data = new FormData(form.elements);
|
|
232
|
-
|
|
233
|
-
callApi("some-url", { body: data });
|
|
234
|
-
```
|
|
235
|
-
|
|
236
|
-
## ✔️ Authorization header helpers
|
|
237
|
-
|
|
238
|
-
If you provide callApi with an `auth` property, it will conveniently generate an Authorization Header for you.
|
|
239
|
-
|
|
240
|
-
If you pass in a `string` (commonly for tokens) , it will generate a Bearer Auth.
|
|
241
|
-
|
|
242
|
-
But if you pass in an `object`, you would have two options to chose from:
|
|
243
|
-
|
|
244
|
-
- Use `bearer` option if you want to generate a Bearer Auth Header.
|
|
245
|
-
- Use `token` if you want to generate a Token Auth Header.
|
|
246
|
-
|
|
247
|
-
**Passing a string:**
|
|
248
|
-
|
|
249
|
-
```js
|
|
250
|
-
callApi("some-url", { auth: "token12345" });
|
|
251
|
-
```
|
|
252
|
-
|
|
253
|
-
The above request can be written in Fetch like this:
|
|
254
|
-
|
|
255
|
-
```js
|
|
256
|
-
fetch("some-url", {
|
|
257
|
-
headers: { Authorization: `Bearer token12345` },
|
|
258
|
-
});
|
|
259
|
-
```
|
|
260
|
-
|
|
261
|
-
**Passing an object:**
|
|
262
|
-
|
|
263
|
-
```js
|
|
264
|
-
// For Bearer Auth
|
|
265
|
-
callApi("some-url", { auth: { bearer: "token12345" } });
|
|
266
|
-
|
|
267
|
-
// Or
|
|
268
|
-
|
|
269
|
-
// For Token Auth
|
|
270
|
-
callApi("some-url", { auth: { token: "token12345" } });
|
|
271
|
-
```
|
|
272
|
-
|
|
273
|
-
The above requests can be written in Fetch like this:
|
|
274
|
-
|
|
275
|
-
```js
|
|
276
|
-
// For Bearer Auth
|
|
277
|
-
fetch("some-url", {
|
|
278
|
-
headers: { Authorization: `Bearer token12345` },
|
|
279
|
-
});
|
|
280
|
-
|
|
281
|
-
// Or
|
|
282
|
-
|
|
283
|
-
// For Token Auth
|
|
284
|
-
fetch("some-url", {
|
|
285
|
-
headers: { Authorization: `Token token12345` },
|
|
286
|
-
});
|
|
287
|
-
```
|
|
288
|
-
|
|
289
|
-
## ✔️ Creating a callApi Instance
|
|
290
|
-
|
|
291
|
-
You can create an instance of `callApi` with predefined options. This is super helpful if you need to send requests with similar `options`.
|
|
292
|
-
|
|
293
|
-
**Things to note:**
|
|
294
|
-
|
|
295
|
-
- All `options` that can be passed to `callApi` can also be passed to `callApi.create`.
|
|
296
|
-
- Any options passed to `callApi.create` will be applied to all requests made with the instance.
|
|
297
|
-
- If you pass a similar `options` property to the instance, the instance's options will take precedence.
|
|
298
|
-
|
|
299
|
-
```js
|
|
300
|
-
import { callApi } from "@zayne-labs/callapi";
|
|
301
|
-
|
|
302
|
-
// Creating the instance, with some base options
|
|
303
|
-
const callAnotherApi = callApi.create({
|
|
304
|
-
timeout: 5000,
|
|
305
|
-
baseURL: "https://api.example.com"
|
|
306
|
-
});
|
|
307
|
-
|
|
308
|
-
// Using the instance
|
|
309
|
-
const { data, error } = await callAnotherApi("some-url");
|
|
310
|
-
|
|
311
|
-
// Overriding the timeout option (all base options can be overridden via the instance)
|
|
312
|
-
const { data, error } = await callAnotherApi("some-url", {
|
|
313
|
-
timeout: 10000,
|
|
314
|
-
});
|
|
315
|
-
```
|
|
316
|
-
|
|
317
|
-
You could also use the `createFetchClient` function to create an instance, if you don't want to use `callApi.create`.
|
|
318
|
-
|
|
319
|
-
```js
|
|
320
|
-
import { createFetchClient } from "@zayne-labs/callapi";
|
|
321
|
-
|
|
322
|
-
const callApi = createFetchClient({
|
|
323
|
-
timeout: 5000,
|
|
324
|
-
baseURL: "https://api.example.com"
|
|
325
|
-
});
|
|
326
|
-
```
|
|
327
|
-
|
|
328
|
-
## ✔️ Custom response parser and custom body serializer
|
|
329
|
-
|
|
330
|
-
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()`.
|
|
331
|
-
|
|
332
|
-
But if you want to parse a response with a custom function other than the default `JSON.parse`, you can pass a custom parser function to the `responseParser` option.
|
|
333
|
-
|
|
334
|
-
```js
|
|
335
|
-
const { data, error } = await callApi("url", {
|
|
336
|
-
responseParser: customResponseParser,
|
|
337
|
-
});
|
|
338
|
-
```
|
|
339
|
-
|
|
340
|
-
Or even better, provide it as a callApi base option.
|
|
341
|
-
|
|
342
|
-
```js
|
|
343
|
-
const callAnotherApi = callApi.create({
|
|
344
|
-
responseParser: customResponseParser,
|
|
345
|
-
});
|
|
346
|
-
```
|
|
347
|
-
|
|
348
|
-
You could also provide a custom serializer/stringifier, other the default `JSON.stringify`, for objects passed to the reuqest body via the `bodySerializer` option.
|
|
349
|
-
|
|
350
|
-
```js
|
|
351
|
-
const callAnotherApi = callApi.create({
|
|
352
|
-
bodySerializer: customBodySerializer,
|
|
353
|
-
});
|
|
354
|
-
```
|
|
355
|
-
|
|
356
|
-
## ✔️Validator function
|
|
357
|
-
|
|
358
|
-
CallApi also provides a `responseValidator` option, which could pass in a function that would validate the data returned from the server.
|
|
359
|
-
|
|
360
|
-
A good use case for this would to pass in, for instance, a zod schema parse/safeParse function to validate the data.
|
|
361
|
-
|
|
362
|
-
If your parser function throws an error, and have the `throwOnError` option set to `true`, you will expected to check and handle the errors in a catch block.
|
|
363
|
-
But, if `throwOnError` is set to `false` (default), it will just return the error object as usual.
|
|
364
|
-
|
|
365
|
-
```js
|
|
366
|
-
const callMainApi = await callApi.create({
|
|
367
|
-
responseValidator: zodSchema.parse, // or zodSchema.safeParse or any other validator you wish to use
|
|
368
|
-
});
|
|
369
|
-
```
|
|
370
|
-
|
|
371
|
-
## ✔️ Interceptors (just like axios)
|
|
372
|
-
|
|
373
|
-
Providing interceptors to hook into lifecycle events of a `callApi` call is possible.
|
|
374
|
-
|
|
375
|
-
These interceptors can be either asynchronous or synchronous.
|
|
376
|
-
|
|
377
|
-
**Note: You might want to use `callApi.create` to set shared interceptors**
|
|
378
|
-
|
|
379
|
-
### `onRequest({ request, options })`
|
|
380
|
-
|
|
381
|
-
`onRequest` is called function that is called just before the request is made, allowing you to modify the request or perform additional operations.
|
|
382
|
-
|
|
383
|
-
```js
|
|
384
|
-
await callApi("/api", {
|
|
385
|
-
onRequest: ({ request, options }) => {
|
|
386
|
-
// Log request
|
|
387
|
-
console.log(request, options);
|
|
388
|
-
|
|
389
|
-
// Do other stuff
|
|
390
|
-
},
|
|
391
|
-
});
|
|
392
|
-
```
|
|
393
|
-
|
|
394
|
-
### `onRequestError({ error, request, options,})`
|
|
395
|
-
|
|
396
|
-
`onRequestError` when an error occurs during the fetch request and it fails, providing access to the error object, request details and fetch options.
|
|
397
|
-
|
|
398
|
-
```js
|
|
399
|
-
await callApi("/api", {
|
|
400
|
-
onRequestError: ({ request, options, error }) => {
|
|
401
|
-
// Log error
|
|
402
|
-
console.log("[fetch request error]", request, error);
|
|
403
|
-
},
|
|
404
|
-
});
|
|
405
|
-
```
|
|
406
|
-
|
|
407
|
-
### `onResponse({ response, request, options })`
|
|
408
|
-
|
|
409
|
-
`onResponse` will be called when a successful response is received, providing access to the response, request details and fetch options.
|
|
410
|
-
|
|
411
|
-
The response object here contains all regular fetch response properties, plus a `data` property, which contains the parsed response body.
|
|
412
|
-
|
|
413
|
-
```js
|
|
414
|
-
await callApi("/api", {
|
|
415
|
-
onResponse: ({ request, response, options }) => {
|
|
416
|
-
// Log response
|
|
417
|
-
console.log(request, response.status, response.data);
|
|
418
|
-
|
|
419
|
-
// Do other stuff
|
|
420
|
-
},
|
|
421
|
-
});
|
|
422
|
-
```
|
|
423
|
-
|
|
424
|
-
### `onResponseError({ request, options, response })`
|
|
425
|
-
|
|
426
|
-
`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.
|
|
427
|
-
|
|
428
|
-
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.
|
|
429
|
-
|
|
430
|
-
**This to note for this interceptor to be triggered:**
|
|
431
|
-
|
|
432
|
-
- The `response.ok` property will be `false`.
|
|
433
|
-
- The `response.status` property will be >= 400.
|
|
434
|
-
- Essentially only error http responses return by the api will trigger this interceptor.
|
|
435
|
-
- It won't trigger for error responses not from the api, like network errors, syntax errors etc. Handle those in `onRequestError` interceptor.
|
|
436
|
-
|
|
437
|
-
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.
|
|
438
|
-
|
|
439
|
-
This example uses a shared interceptor for all requests made with the instance.
|
|
440
|
-
|
|
441
|
-
```js
|
|
442
|
-
const callAnotherApi = callApi.create({
|
|
443
|
-
onResponseError: ({ response, request, options }) => {
|
|
444
|
-
// Log error response
|
|
445
|
-
console.log(request, response.status, response.errorData);
|
|
446
|
-
|
|
447
|
-
// Perform action on various error conditions
|
|
448
|
-
if (response.status === 401) {
|
|
449
|
-
actions.clearSession();
|
|
450
|
-
}
|
|
451
|
-
|
|
452
|
-
if (response.status === 429) {
|
|
453
|
-
toast.error("Too may requests!");
|
|
454
|
-
}
|
|
455
|
-
|
|
456
|
-
if (response.status === 403 && response.errorData?.message === "2FA is required") {
|
|
457
|
-
toast.error(response.errorData?.message, {
|
|
458
|
-
description: "Please authenticate to continue",
|
|
459
|
-
});
|
|
460
|
-
}
|
|
461
|
-
|
|
462
|
-
if (response.status === 500) {
|
|
463
|
-
toast.error("Internal server Error!");
|
|
464
|
-
}
|
|
465
|
-
},
|
|
466
|
-
});
|
|
467
|
-
```
|
|
468
|
-
|
|
469
|
-
## ✔️ Retries
|
|
470
|
-
|
|
471
|
-
`CallApi` support retries for requests if an error happens and if the response status code is included in `retryStatusCodes` list:
|
|
472
|
-
|
|
473
|
-
**Default Retry status codes:**
|
|
474
|
-
|
|
475
|
-
- `408` - Request Timeout
|
|
476
|
-
- `409` - Conflict
|
|
477
|
-
- `425` - Too Early
|
|
478
|
-
- `429` - Too Many Requests
|
|
479
|
-
- `500` - Internal Server Error
|
|
480
|
-
- `502` - Bad Gateway
|
|
481
|
-
- `503` - Service Unavailable
|
|
482
|
-
- `504` - Gateway Timeout
|
|
483
|
-
|
|
484
|
-
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.
|
|
485
|
-
|
|
486
|
-
You can also specify which methods should be retried by passing in a custom `retryMethods` array.
|
|
487
|
-
|
|
488
|
-
The default for `retry` is `0` retries.
|
|
489
|
-
The default for `retryDelay` is `0` ms.
|
|
490
|
-
The default for `retryMethods` is `["GET", "POST"]`.
|
|
491
|
-
|
|
492
|
-
```js
|
|
493
|
-
await callApi("http://google.com/404", {
|
|
494
|
-
retry: 3,
|
|
495
|
-
retryDelay: 500, // ms
|
|
496
|
-
retryStatusCodes: [404, 502, 503, 504], // custom status codes for retries
|
|
497
|
-
retryMethods: ["POST", "PUT", "PATCH", "DELETE"], // custom methods for retries
|
|
498
|
-
});
|
|
499
|
-
```
|
|
500
|
-
|
|
501
|
-
## ✔️ Timeout
|
|
502
|
-
|
|
503
|
-
You can specify `timeout` in milliseconds to automatically abort a request after a timeout (default is disabled).
|
|
504
|
-
|
|
505
|
-
```js
|
|
506
|
-
await callApi("http://google.com/404", {
|
|
507
|
-
timeout: 3000, // Timeout after 3 seconds
|
|
508
|
-
});
|
|
509
|
-
```
|
|
510
|
-
|
|
511
|
-
## ✔️ Throw on all errors
|
|
512
|
-
|
|
513
|
-
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`.
|
|
514
|
-
|
|
515
|
-
```js
|
|
516
|
-
const callMainApi = callApi.create({
|
|
517
|
-
throwOnError: true,
|
|
518
|
-
});
|
|
519
|
-
|
|
520
|
-
const { data, error } = useQuery({
|
|
521
|
-
queryKey: ["todos"],
|
|
522
|
-
queryFn: async () => {
|
|
523
|
-
// CallApi will throw an error if the request fails or there is an error response, which react query would handle
|
|
524
|
-
const { data } = await callMainApi("todos");
|
|
525
|
-
|
|
526
|
-
return data;
|
|
527
|
-
},
|
|
528
|
-
});
|
|
529
|
-
```
|
|
530
|
-
|
|
531
|
-
Doing this with regular fetch would imply the following extra steps:
|
|
532
|
-
|
|
533
|
-
```js
|
|
534
|
-
const { data, error } = useQuery({
|
|
535
|
-
queryKey: ["todos"],
|
|
536
|
-
queryFn: async () => {
|
|
537
|
-
const response = await fetch("todos");
|
|
538
|
-
|
|
539
|
-
if (!response.ok) {
|
|
540
|
-
throw new Error("Failed to fetch");
|
|
541
|
-
}
|
|
542
|
-
|
|
543
|
-
return response.json();
|
|
544
|
-
},
|
|
545
|
-
});
|
|
546
|
-
```
|
|
547
|
-
|
|
548
|
-
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:
|
|
549
|
-
|
|
550
|
-
```js
|
|
551
|
-
const callMainApi = callApi.create({
|
|
552
|
-
throwOnError: true,
|
|
553
|
-
resultMode: "onlySuccess",
|
|
554
|
-
});
|
|
555
|
-
|
|
556
|
-
const { data, error } = useQuery({
|
|
557
|
-
queryKey: ["todos"],
|
|
558
|
-
// CallApi will throw on errors here, and also return only data, which react query is interested in
|
|
559
|
-
queryFn: () => callMainApi("todos"),
|
|
560
|
-
});
|
|
561
|
-
```
|
|
562
|
-
|
|
563
|
-
## Usage with Typescript
|
|
564
|
-
|
|
565
|
-
- You can provide types for the success and error data via generics, to enable autocomplete and type checking in your codebase.
|
|
566
|
-
|
|
567
|
-
```ts
|
|
568
|
-
const callMainApi = callApi.create<FormResponseDataType, FormErrorResponseType>({
|
|
569
|
-
baseURL: BASE_AUTH_URL,
|
|
570
|
-
|
|
571
|
-
method: "POST",
|
|
572
|
-
|
|
573
|
-
retries: 3,
|
|
574
|
-
|
|
575
|
-
credentials: "same-origin",
|
|
576
|
-
});
|
|
577
|
-
```
|
|
578
|
-
|
|
579
|
-
- Just like the fetch options, all type parameters (generics) can also be overriden per instance level
|
|
580
|
-
|
|
581
|
-
```ts
|
|
582
|
-
const { data } = callMainApi<NewResponseDataType>({
|
|
583
|
-
method: "GET",
|
|
584
|
-
|
|
585
|
-
retries: 5,
|
|
586
|
-
});
|
|
587
|
-
```
|
|
588
|
-
|
|
589
|
-
- Since the `data` and `error` properties destructured from callApi are in a discriminated union, simply checking for and handling the `error` property will narrow down the type of the data. The reverse case also holds (checking for data to narrow error type).
|
|
590
|
-
|
|
591
|
-
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.
|
|
592
|
-
|
|
593
|
-
```ts
|
|
594
|
-
// As is, both data and error could be null
|
|
595
|
-
const { data, error } = await callMainApi("some-url", {
|
|
596
|
-
body: { message: "Good game" },
|
|
597
|
-
});
|
|
598
|
-
|
|
599
|
-
if (error) {
|
|
600
|
-
console.error(error);
|
|
601
|
-
return;
|
|
602
|
-
}
|
|
603
|
-
|
|
604
|
-
// Now, data is no longer null
|
|
605
|
-
console.log(data);
|
|
606
|
-
```
|
|
607
|
-
|
|
608
|
-
- CallApi provides a type guard that allows differentiate between an HTTPError and standard js errors. It also helps narrow down the discriminated union type of the error object.
|
|
609
|
-
|
|
610
|
-
```ts
|
|
611
|
-
import { isHTTPError } from "@zayne-labs/callapi";
|
|
612
|
-
|
|
613
|
-
const { data, error } = await callMainApi("some-url", {
|
|
614
|
-
body: { message: "Good game" },
|
|
615
|
-
});
|
|
616
|
-
|
|
617
|
-
if (isHTTPError(error)) {
|
|
618
|
-
console.error(error.name); // `HTTPError`
|
|
619
|
-
console.error(error.message); // contains the parsed error message, if the response from the server contains such a property
|
|
620
|
-
console.error(error.errorData); // contains the parsed error response
|
|
621
|
-
|
|
622
|
-
return;
|
|
623
|
-
}
|
|
624
|
-
|
|
625
|
-
if (error) {
|
|
626
|
-
console.error(error.name); // contains the name of the error
|
|
627
|
-
console.error(error.message); // contains the error message
|
|
628
|
-
console.error(error.errorData); // contains the original error object
|
|
629
|
-
}
|
|
630
|
-
|
|
631
|
-
```
|
|
632
|
-
|
|
633
|
-
- The types for the object passed to onResponse and onResponseError could be augmented with type helpers provided by `@zayne-labs/callapi`.
|
|
634
|
-
|
|
635
|
-
```ts
|
|
636
|
-
const callAnotherApi = callApi.create({
|
|
637
|
-
onResponseError: ({ response, request, options }: ResponseErrorContext<{ message?: string }>) => {
|
|
638
|
-
// Log error response
|
|
639
|
-
console.log(
|
|
640
|
-
request,
|
|
641
|
-
response.status,
|
|
642
|
-
// error data, coming back from api
|
|
643
|
-
response.errorData,
|
|
644
|
-
// Typescript will then understand the errorData might contains a message property
|
|
645
|
-
response.errorData?.message
|
|
646
|
-
);
|
|
647
|
-
},
|
|
648
|
-
});
|
|
649
|
-
```
|
|
650
|
-
|
|
651
|
-
## Api Reference
|
|
652
|
-
|
|
653
|
-
### Fetch Options
|
|
654
|
-
|
|
655
|
-
- All Regular fetch options are supported as is, with only body extended to support more types.
|
|
656
|
-
- `body`: Optional body of the request, can be an object or any other supported body type.
|
|
657
|
-
- `query`: Query parameters to append to the URL.
|
|
658
|
-
- `auth`: Authorization header value.
|
|
659
|
-
- `bodySerializer`: Custom function to serialize the body object into a string.
|
|
660
|
-
- `responseParser`: Custom function to parse the response string into an object.
|
|
661
|
-
- `resultMode`: Mode of the result, can influence how results are handled or returned. (default: "all")
|
|
662
|
-
- `cancelRedundantRequests`: If true, cancels previous unfinished requests to the same URL. (default: true)
|
|
663
|
-
- `baseURL`: Base URL to be prepended to all request URLs.
|
|
664
|
-
- `timeout`: Request timeout in milliseconds.
|
|
665
|
-
- `defaultErrorMessage`: Default error message to use if none is provided from a response. (default: "Failed to fetch data from server!")
|
|
666
|
-
- `throwOnError`: If true or the function returns true, throws errors instead of returning them.
|
|
667
|
-
|
|
668
|
-
- `responseType`: Expected response type, affects how response is parsed. (default: "json")
|
|
669
|
-
- `retries`: Number of retry attempts for failed requests. (default: 0)
|
|
670
|
-
- `retryDelay`: Delay between retries in milliseconds. (default: 500)
|
|
671
|
-
- `retryCodes`: HTTP status codes that trigger a retry. (default: [409, 425, 429, 500, 502, 503, 504])
|
|
672
|
-
- `retryMethods`: HTTP methods that are allowed to retry. (default: ["GET", "POST"])
|
|
673
|
-
- `meta`: An optional field that can contain additional information about a request, which could be helpful in differentiating between different requests in a shared interceptor.
|
|
674
|
-
- `onRequest`: Interceptor called just before the request is made, allowing for modifications or additional operations.
|
|
675
|
-
- `onRequestError`: Interceptor called when an error occurs during the fetch request.
|
|
676
|
-
- `onResponse`: Interceptor called when a successful response is received from the API.
|
|
677
|
-
- `onResponseError`: Interceptor called when an error response is received from the API.
|
|
678
|
-
|
|
679
|
-
### Methods
|
|
680
|
-
|
|
681
|
-
- `callApi.create(options)`: Creates an instance of `callApi` with shared base configurations.
|
|
682
|
-
- `callApi.cancel(url: string)`: Cancels an ongoing request to the specified URL.
|
|
683
|
-
|
|
684
|
-
### Utility Functions
|
|
685
|
-
|
|
686
|
-
- `isHTTPError`: Type guard for if an error is an HTTPError
|
|
687
|
-
|
|
688
|
-
- `isHTTPErrorInstance`: Type guard for if an error is an instance of HTTPError. Useful for when `throwAllErrors` option is set to `true`
|
|
689
|
-
|
|
690
|
-
- `toQueryString`: Converts an object to a URL query string
|
|
691
|
-
|
|
692
|
-
## Acknowledgements
|
|
693
|
-
|
|
694
|
-
- Credits to `ofetch` by unjs for some of the ideas for the features in the library like the function-based interceptors, retries etc
|
|
695
|
-
- Credits to `zl-fetch` fetch wrapper as well for the inspiration behind a few features in this library as well
|
package/dist/cjs/index.cjs
CHANGED
|
@@ -1,2 +1,2 @@
|
|
|
1
|
-
"use strict";var h=Object.defineProperty;var J=Object.getOwnPropertyDescriptor;var Y=Object.getOwnPropertyNames;var X=Object.prototype.hasOwnProperty;var Z=(e,r)=>{for(var o in r)h(e,o,{get:r[o],enumerable:!0})},ee=(e,r,o,a)=>{if(r&&typeof r=="object"||typeof r=="function")for(let n of Y(r))!X.call(e,n)&&n!==o&&h(e,n,{get:()=>r[n],enumerable:!(a=J(r,n))||a.enumerable});return e};var re=e=>ee(h({},"__esModule",{value:!0}),e);var ae={};Z(ae,{HTTPError:()=>d,callApi:()=>
|
|
1
|
+
"use strict";var h=Object.defineProperty;var J=Object.getOwnPropertyDescriptor;var Y=Object.getOwnPropertyNames;var X=Object.prototype.hasOwnProperty;var Z=(e,r)=>{for(var o in r)h(e,o,{get:r[o],enumerable:!0})},ee=(e,r,o,a)=>{if(r&&typeof r=="object"||typeof r=="function")for(let n of Y(r))!X.call(e,n)&&n!==o&&h(e,n,{get:()=>r[n],enumerable:!(a=J(r,n))||a.enumerable});return e};var re=e=>ee(h({},"__esModule",{value:!0}),e);var ae={};Z(ae,{HTTPError:()=>d,callApi:()=>N,createFetchClient:()=>g,isHTTPError:()=>z,isHTTPErrorInstance:()=>b,toQueryString:()=>w});module.exports=re(ae);var S=e=>Array.isArray(e),$=e=>e instanceof FormData,u=e=>typeof e=="object"&&e!==null&&!(e instanceof FormData)&&!Array.isArray(e),v=e=>typeof e=="function",x=e=>typeof e=="string";var w=e=>e?new URLSearchParams(e).toString():(console.error("No query params provided"),null),H=(e,r)=>{if(!r)return e;let o=w(r);return e.endsWith("?")?`${e}${o}`:e.includes("?")?`${e}&${o}`:`${e}?${o}`},D=e=>!e||u(e)?e:Object.fromEntries(S(e)?e:e.entries()),te={408:"Request Timeout",409:"Conflict",425:"Too Early",429:"Too Many Requests",500:"Internal Server Error",502:"Bad Gateway",503:"Service Unavailable",504:"Gateway Timeout"},U=Object.keys(te).map(Number),I=["GET"],B=["body","integrity","method","headers","signal","cache","redirect","window","credentials","keepalive","referrer","priority","mode","referrerPolicy"],oe=(e,r)=>{let o=Object.entries(e).filter(([n])=>!r.includes(n));return Object.fromEntries(o)},se=(e,r)=>{let o=new Set(r),n=Object.entries(e).filter(([f])=>o.has(f));return Object.fromEntries(n)},A=e=>[se(e,B),oe(e,B)],ne=(e,r)=>({json:async()=>r?r(await e.text()):e.json(),arrayBuffer:()=>e.arrayBuffer(),blob:()=>e.blob(),formData:()=>e.formData(),text:()=>e.text()}),C=(e,r,o)=>{let a=ne(e,o);if(!Object.hasOwn(a,r))throw new Error(`Invalid response type: ${r}`);return a[r]()},G=e=>{let{options:r,response:o,successData:a}=e,n={data:a,error:null,response:o};return r.resultMode===void 0||r.resultMode==="all"?n:{onlySuccess:n.data,onlyError:n.error,onlyResponse:n.response}[r.resultMode]},L=e=>{let{error:r,options:o}=e;return(n={})=>{let{errorData:l,message:f,response:O}=n;if(v(o.throwOnError)?o.throwOnError(r):o.throwOnError)throw r;return{data:null,error:{name:r?.name??"UnknownError",errorData:l??r,message:f??r?.message??o.defaultErrorMessage},response:O??null}}},z=e=>u(e)&&e.name==="HTTPError",d=class extends Error{response;errorData;name="HTTPError";isHTTPError=!0;constructor(r,o){let{defaultErrorMessage:a,response:n,errorData:l}=r;super(l.message??a,o),this.errorData=l,this.response=n}},b=e=>e instanceof d||u(e)&&e.name==="HTTPError"&&e.isHTTPError===!0,K=e=>{if(e===0)return;let{promise:r,resolve:o}=Promise.withResolvers();return setTimeout(o,e),r};var g=e=>{let r=new Map,[o,a]=A(e??{}),{headers:n,body:l,signal:f,...O}=o,R=async(c,E)=>{let[Q,V]=A(E??{}),{signal:j=f,body:p=l,headers:P,...W}=Q,t={bodySerializer:JSON.stringify,responseType:"json",baseURL:"",retries:0,retryDelay:0,retryCodes:U,retryMethods:I,defaultErrorMessage:"Failed to fetch data from server!",cancelRedundantRequests:!0,...a,...V},k=r.get(c);if(k&&t.cancelRedundantRequests){let s=new DOMException(`Automatic cancelation of the previous unfinished request to this same url: ${c}`,"AbortError");k.abort(s)}let q=new AbortController;r.set(c,q);let F=t.timeout?AbortSignal.timeout(t.timeout):null,M=AbortSignal.any([q.signal,...F?[F]:[],...j?[j]:[]]),y={signal:M,method:"GET",body:u(p)?t.bodySerializer(p):p,headers:n||P||t.auth||u(p)?{...u(p)&&{"Content-Type":"application/json",Accept:"application/json"},...$(p)&&{"Content-Type":"multipart/form-data"},...x(p)&&{"Content-Type":"application/x-www-form-urlencoded"},...x(t.auth)&&{Authorization:`Bearer ${t.auth}`},...u(t.auth)&&{Authorization:"bearer"in t.auth?`Bearer ${t.auth.bearer}`:`Token ${t.auth.token}`},...D(n),...D(P)}:void 0,...O,...W};try{await t.onRequest?.({request:y,options:t});let s=await fetch(`${t.baseURL}${H(c,t.query)}`,y);if(!s.ok&&!M.aborted&&t.retries>0&&t.retryCodes.includes(s.status)&&t.retryMethods.includes(y.method))return await K(t.retryDelay),await R(c,{...E,retries:t.retries-1});if(!s.ok){let _=await C(t.cloneResponse?s.clone():s,t.responseType,t.responseParser);throw new d({errorData:_,response:s,defaultErrorMessage:t.defaultErrorMessage})}let i=await C(t.cloneResponse?s.clone():s,t.responseType,t.responseParser),T=t.responseValidator?t.responseValidator(i):i;return await t.onResponse?.({data:T,response:t.cloneResponse?s.clone():s,request:y,options:t}),G({successData:T,response:s,options:t})}catch(s){let m=L({error:s,options:t});if(s instanceof DOMException&&s.name==="TimeoutError"){let i=`Request timed out after ${t.timeout}ms`;return console.error(`${s.name}:`,i),m({message:i})}if(s instanceof DOMException&&s.name==="AbortError"){let i=`Request aborted due to ${s.message}`;return console.error(`${s.name}:`,i),m({message:i})}if(b(s)){let{errorData:i,response:T}=s;return await t.onResponseError?.({errorData:i,response:t.cloneResponse?T.clone():T,request:y,options:t}),m({errorData:i,message:i?.message,response:T})}return await t.onRequestError?.({request:y,error:s,options:t}),m()}finally{r.delete(c)}};return R.create=g,R.cancel=(c,E)=>{E?r.get(c)?.abort(E):r.get(c)?.abort()},R},N=g();0&&(module.exports={HTTPError,callApi,createFetchClient,isHTTPError,isHTTPErrorInstance,toQueryString});
|
|
2
2
|
//# sourceMappingURL=index.cjs.map
|
package/dist/cjs/index.cjs.map
CHANGED
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"sources":["../../src/index.ts","../../src/typeof.ts","../../src/utils.ts","../../src/createFetchClient.ts"],"sourcesContent":["export { callApi, createFetchClient } from \"./createFetchClient\";\n\nexport type {\n\t$RequestOptions,\n\tExtraOptions,\n\tFetchConfig,\n\tResponseContext,\n\tResponseErrorContext,\n} from \"./types\";\n\nexport { HTTPError, isHTTPError, isHTTPErrorInstance, toQueryString } from \"./utils\";\n","import type { AnyFunction } from \"./type-helpers\";\n\nexport const isArray = <TArray>(value: unknown): value is TArray[] => Array.isArray(value);\n\nexport const isFormData = (value: unknown) => value instanceof FormData;\n\nexport const isObject = <TObject extends Record<string, unknown>>(value: unknown): value is TObject => {\n\treturn (\n\t\ttypeof value === \"object\" && value !== null && !(value instanceof FormData) && !Array.isArray(value)\n\t);\n};\n\nexport const isFunction = <TFunction extends AnyFunction>(value: unknown): value is TFunction =>\n\ttypeof value === \"function\";\n\nexport const isString = (value: unknown) => typeof value === \"string\";\n","import { isArray, isFunction, isObject } from \"./typeof\";\nimport type {\n\t$BaseRequestOptions,\n\t$RequestOptions,\n\tApiErrorVariant,\n\tBaseConfig,\n\tExtraOptions,\n\tFetchConfig,\n\tPossibleErrorObject,\n} from \"./types\";\n\ntype ToQueryStringFn = {\n\t(params: ExtraOptions[\"query\"]): string | null;\n\t(params: Required<ExtraOptions>[\"query\"]): string;\n};\n\nexport const toQueryString: ToQueryStringFn = (params) => {\n\tif (!params) {\n\t\tconsole.error(\"No query params provided\");\n\t\treturn null as never;\n\t}\n\n\treturn new URLSearchParams(params as Record<string, string>).toString();\n};\n\nexport const mergeUrlWithParams = (url: string, params: ExtraOptions[\"query\"]): string => {\n\tif (!params) {\n\t\treturn url;\n\t}\n\n\tconst paramsString = toQueryString(params);\n\n\tif (url.endsWith(\"?\")) {\n\t\treturn `${url}${paramsString}`;\n\t}\n\n\tif (url.includes(\"?\")) {\n\t\treturn `${url}&${paramsString}`;\n\t}\n\n\treturn `${url}?${paramsString}`;\n};\n\nexport const objectifyHeaders = (headers: RequestInit[\"headers\"]): Record<string, string> | undefined => {\n\tif (!headers || isObject(headers)) {\n\t\treturn headers;\n\t}\n\n\treturn Object.fromEntries(isArray(headers) ? headers : headers.entries());\n};\n\nconst retryCodesLookup = {\n\t408: \"Request Timeout\",\n\t409: \"Conflict\",\n\t425: \"Too Early\",\n\t429: \"Too Many Requests\",\n\t500: \"Internal Server Error\",\n\t502: \"Bad Gateway\",\n\t503: \"Service Unavailable\",\n\t504: \"Gateway Timeout\",\n};\n\nexport const defaultRetryCodes: Required<BaseConfig>[\"retryCodes\"] =\n\tObject.keys(retryCodesLookup).map(Number);\n\nexport const defaultRetryMethods: Required<BaseConfig>[\"retryMethods\"] = [\"GET\"];\n\nexport const fetchSpecificKeys = [\n\t\"body\",\n\t\"integrity\",\n\t\"method\",\n\t\"headers\",\n\t\"signal\",\n\t\"cache\",\n\t\"redirect\",\n\t\"window\",\n\t\"credentials\",\n\t\"keepalive\",\n\t\"referrer\",\n\t\"priority\",\n\t\"mode\",\n\t\"referrerPolicy\",\n] satisfies Array<keyof FetchConfig>;\n\nconst omitKeys = <TObject extends Record<string, unknown>, const TOmitArray extends Array<keyof TObject>>(\n\tinitialObject: TObject,\n\tkeysToOmit: TOmitArray\n) => {\n\tconst arrayFromFilteredObject = Object.entries(initialObject).filter(\n\t\t([key]) => !keysToOmit.includes(key)\n\t);\n\n\tconst updatedObject = Object.fromEntries(arrayFromFilteredObject);\n\n\treturn updatedObject as Omit<TObject, keyof TOmitArray>;\n};\n\nconst pickKeys = <TObject extends Record<string, unknown>, const TPickArray extends Array<keyof TObject>>(\n\tinitialObject: TObject,\n\tkeysToPick: TPickArray\n) => {\n\tconst keysToPickSet = new Set(keysToPick);\n\n\tconst arrayFromInitObject = Object.entries(initialObject);\n\n\tconst filteredArray = arrayFromInitObject.filter(([objectKey]) => keysToPickSet.has(objectKey));\n\n\tconst updatedObject = Object.fromEntries(filteredArray);\n\n\treturn updatedObject as Pick<TObject, TPickArray[number]>;\n};\n\nexport const splitConfig = <TObject extends object>(\n\tconfig: TObject\n): [\"body\" extends keyof TObject ? $RequestOptions : $BaseRequestOptions, ExtraOptions] => [\n\tpickKeys(config as Record<string, unknown>, fetchSpecificKeys) as never,\n\tomitKeys(config as Record<string, unknown>, fetchSpecificKeys) as never,\n];\n\nexport const handleResponseType = <TResponse>(\n\tresponse: Response,\n\tparser?: Required<ExtraOptions>[\"responseParser\"]\n) => ({\n\tjson: async () => {\n\t\tif (parser) {\n\t\t\treturn parser(await response.text());\n\t\t}\n\n\t\treturn response.json() as Promise<TResponse>;\n\t},\n\tarrayBuffer: () => response.arrayBuffer() as Promise<TResponse>,\n\tblob: () => response.blob() as Promise<TResponse>,\n\tformData: () => response.formData() as Promise<TResponse>,\n\ttext: () => response.text() as Promise<TResponse>,\n});\n\nexport const getResponseData = <TResponse>(\n\tresponse: Response,\n\tresponseType: keyof ReturnType<typeof handleResponseType>,\n\tparser: ExtraOptions[\"responseParser\"]\n) => {\n\tconst RESPONSE_TYPE_LOOKUP = handleResponseType<TResponse>(response, parser);\n\n\tif (!Object.hasOwn(RESPONSE_TYPE_LOOKUP, responseType)) {\n\t\tthrow new Error(`Invalid response type: ${responseType}`);\n\t}\n\n\treturn RESPONSE_TYPE_LOOKUP[responseType]();\n};\n\ntype data = {\n\tsuccessData: unknown;\n\toptions: ExtraOptions;\n\tresponse: Response;\n};\n\n// == The CallApiResult type is used to cast all return statements due to a design limitation in ts.\n// LINK - See https://www.zhenghao.io/posts/type-functions for more info\nexport const resolveSuccessResult = <CallApiResult>(info: data): CallApiResult => {\n\tconst { options, response, successData } = info;\n\n\tconst apiDetails = {\n\t\tdata: successData,\n\t\terrorInfo: null,\n\t\tresponse,\n\t};\n\n\tif (options.resultMode === undefined || options.resultMode === \"all\") {\n\t\treturn apiDetails as CallApiResult;\n\t}\n\n\treturn {\n\t\tonlySuccess: apiDetails.data,\n\t\tonlyError: apiDetails.errorInfo,\n\t\tonlyResponse: apiDetails.response,\n\t}[options.resultMode] as CallApiResult;\n};\n\n// == Using curring here so error and options are not required to be passed every time, instead to be captured once by way of closure\nexport const $resolveErrorResult = <CallApiResult>($info: { error?: unknown; options: ExtraOptions }) => {\n\tconst { error, options } = $info;\n\n\ttype ErrorInfo = {\n\t\tresponse?: Response;\n\t\terrorData?: unknown;\n\t\tmessage?: string;\n\t};\n\n\tconst resolveErrorResult = (info: ErrorInfo = {}): CallApiResult => {\n\t\tconst { errorData, message, response } = info;\n\n\t\tconst shouldThrowOnError = isFunction(options.throwOnError)\n\t\t\t? options.throwOnError(error as Error)\n\t\t\t: options.throwOnError;\n\n\t\tif (shouldThrowOnError) {\n\t\t\tthrow error;\n\t\t}\n\n\t\treturn {\n\t\t\tdata: null,\n\t\t\terror: {\n\t\t\t\tname: (error as PossibleErrorObject)?.name ?? \"UnknownError\",\n\t\t\t\terrorData: errorData ?? error,\n\t\t\t\tmessage: message ?? (error as PossibleErrorObject)?.message ?? options.defaultErrorMessage,\n\t\t\t},\n\t\t\tresponse: response ?? null,\n\t\t} as CallApiResult;\n\t};\n\n\treturn resolveErrorResult;\n};\n\nexport const isHTTPError = <TErrorData>(error: ApiErrorVariant<TErrorData>[\"error\"] | null) => {\n\treturn isObject(error) && error.name === \"HTTPError\";\n};\n\ntype ErrorDetails<TErrorResponse> = {\n\terrorData: TErrorResponse;\n\tresponse: Response;\n\tdefaultErrorMessage: string;\n};\n\ntype ErrorOptions = {\n\tcause?: unknown;\n};\n\nexport class HTTPError<TErrorResponse = Record<string, unknown>> extends Error {\n\tresponse: ErrorDetails<TErrorResponse>[\"response\"];\n\terrorData: ErrorDetails<TErrorResponse>[\"errorData\"];\n\n\toverride name = \"HTTPError\" as const;\n\n\tisHTTPError = true;\n\n\tconstructor(errorDetails: ErrorDetails<TErrorResponse>, errorOptions?: ErrorOptions) {\n\t\tconst { defaultErrorMessage, response, errorData } = errorDetails;\n\n\t\tsuper((errorData as { message?: string }).message ?? defaultErrorMessage, errorOptions);\n\n\t\tthis.errorData = errorData;\n\t\tthis.response = response;\n\t}\n}\n\n// prettier-ignore\nexport const isHTTPErrorInstance = <TErrorResponse>(\n\terror: unknown\n): error is HTTPError<TErrorResponse> => {\n\treturn (\n\t\terror instanceof HTTPError || (isObject(error) && error.name === \"HTTPError\" && error.isHTTPError === true)\n\t);\n};\n\nexport const waitUntil = (delay: number) => {\n\tif (delay === 0) return;\n\n\tconst { promise, resolve } = Promise.withResolvers();\n\n\tsetTimeout(resolve, delay);\n\n\treturn promise;\n};\n","import { isFormData, isObject, isString } from \"./typeof\";\nimport type {\n\t$RequestOptions,\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\nexport const createFetchClient = <\n\tTBaseData,\n\tTBaseErrorData,\n\tTBaseResultMode extends ResultModeUnion = 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 {\n\t\theaders: baseHeaders,\n\t\tbody: baseBody,\n\t\tsignal: baseSignal,\n\t\t...restOfBaseFetchConfig\n\t} = baseFetchConfig;\n\n\t/* eslint-disable complexity */\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 { signal = baseSignal, body = baseBody, headers, ...restOfFetchConfig } = fetchConfig;\n\n\t\t// == Default Options\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\t...baseExtraOptions,\n\t\t\t...extraOptions,\n\t\t} satisfies ExtraOptions;\n\n\t\tconst prevFetchController = abortControllerStore.get(url);\n\n\t\tif (prevFetchController && options.cancelRedundantRequests) {\n\t\t\tconst reason = new DOMException(\n\t\t\t\t`Automatic cancelation of the previous unfinished request to this same url: ${url}`,\n\t\t\t\t\"AbortError\"\n\t\t\t);\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\tconst combinedSignal = AbortSignal.any([\n\t\t\tnewFetchController.signal,\n\t\t\t...(timeoutSignal ? [timeoutSignal] : []),\n\t\t\t...(signal ? [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...(isString(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...(isObject(options.auth) && {\n\t\t\t\t\t\t\t\tAuthorization:\n\t\t\t\t\t\t\t\t\t\"bearer\" in options.auth\n\t\t\t\t\t\t\t\t\t\t? `Bearer ${options.auth.bearer}`\n\t\t\t\t\t\t\t\t\t\t: `Token ${options.auth.token}`,\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\toptions.cloneResponse ? response.clone() : response,\n\t\t\t\t\toptions.responseType,\n\t\t\t\t\toptions.responseParser\n\t\t\t\t);\n\n\t\t\t\tawait options.onResponseError?.({\n\t\t\t\t\tresponse: options.cloneResponse ? response.clone() : response,\n\t\t\t\t\terrorData,\n\t\t\t\t\trequest: requestInit,\n\t\t\t\t\toptions,\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\terrorData,\n\t\t\t\t\tresponse,\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\toptions.cloneResponse ? response.clone() : response,\n\t\t\t\toptions.responseType,\n\t\t\t\toptions.responseParser\n\t\t\t);\n\n\t\t\tconst validSuccessData = options.responseValidator\n\t\t\t\t? options.responseValidator(successData)\n\t\t\t\t: successData;\n\n\t\t\tawait options.onResponse?.({\n\t\t\t\tresponse: options.cloneResponse ? response.clone() : response,\n\t\t\t\tdata: validSuccessData,\n\t\t\t\trequest: requestInit,\n\t\t\t\toptions,\n\t\t\t});\n\n\t\t\treturn resolveSuccessResult<CallApiResult>({ successData: validSuccessData, 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.error(`${error.name}:`, message);\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 aborted due to ${error.message}`;\n\n\t\t\t\tconsole.error(`${error.name}:`, message);\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;\n\n\t\t\t\treturn resolveErrorResult({\n\t\t\t\t\terrorData,\n\t\t\t\t\tmessage: (errorData as PossibleErrorObject)?.message,\n\t\t\t\t\tresponse,\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, reason?: unknown) => {\n\t\treason ? abortControllerStore.get(url)?.abort(reason) : abortControllerStore.get(url)?.abort();\n\t};\n\n\treturn callApi;\n};\n\nexport const callApi = createFetchClient();\n"],"mappings":"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","names":["src_exports","__export","HTTPError","callApi","createFetchClient","isHTTPError","isHTTPErrorInstance","toQueryString","__toCommonJS","isArray","value","isFormData","isObject","isFunction","isString","toQueryString","params","mergeUrlWithParams","url","paramsString","objectifyHeaders","headers","isObject","isArray","retryCodesLookup","defaultRetryCodes","defaultRetryMethods","fetchSpecificKeys","omitKeys","initialObject","keysToOmit","arrayFromFilteredObject","key","pickKeys","keysToPick","keysToPickSet","filteredArray","objectKey","splitConfig","config","handleResponseType","response","parser","getResponseData","responseType","RESPONSE_TYPE_LOOKUP","resolveSuccessResult","info","options","successData","apiDetails","$resolveErrorResult","$info","error","errorData","message","isFunction","isHTTPError","HTTPError","errorDetails","errorOptions","defaultErrorMessage","isHTTPErrorInstance","waitUntil","delay","promise","resolve","createFetchClient","baseConfig","abortControllerStore","baseFetchConfig","baseExtraOptions","splitConfig","baseHeaders","baseBody","baseSignal","restOfBaseFetchConfig","callApi","url","config","fetchConfig","extraOptions","signal","body","headers","restOfFetchConfig","options","defaultRetryCodes","defaultRetryMethods","prevFetchController","reason","newFetchController","timeoutSignal","combinedSignal","requestInit","isObject","isFormData","isString","objectifyHeaders","response","mergeUrlWithParams","waitUntil","errorData","getResponseData","HTTPError","successData","validSuccessData","resolveSuccessResult","error","resolveErrorResult","$resolveErrorResult","message","isHTTPErrorInstance"]}
|
|
1
|
+
{"version":3,"sources":["../../src/index.ts","../../src/typeof.ts","../../src/utils.ts","../../src/createFetchClient.ts"],"sourcesContent":["export { callApi, createFetchClient } from \"./createFetchClient\";\n\nexport type {\n\t$RequestOptions,\n\tExtraOptions,\n\tFetchConfig,\n\tResponseContext,\n\tResponseErrorContext,\n} from \"./types\";\n\nexport { HTTPError, isHTTPError, isHTTPErrorInstance, toQueryString } from \"./utils\";\n","import type { AnyFunction } from \"./type-helpers\";\n\nexport const isArray = <TArray>(value: unknown): value is TArray[] => Array.isArray(value);\n\nexport const isFormData = (value: unknown) => value instanceof FormData;\n\nexport const isObject = <TObject extends Record<string, unknown>>(value: unknown): value is TObject => {\n\treturn (\n\t\ttypeof value === \"object\" && value !== null && !(value instanceof FormData) && !Array.isArray(value)\n\t);\n};\n\nexport const isFunction = <TFunction extends AnyFunction>(value: unknown): value is TFunction =>\n\ttypeof value === \"function\";\n\nexport const isString = (value: unknown) => typeof value === \"string\";\n","import { isArray, isFunction, isObject } from \"./typeof\";\nimport type {\n\t$BaseRequestOptions,\n\t$RequestOptions,\n\tApiErrorVariant,\n\tBaseConfig,\n\tExtraOptions,\n\tFetchConfig,\n\tPossibleErrorObject,\n} from \"./types\";\n\ntype ToQueryStringFn = {\n\t(params: ExtraOptions[\"query\"]): string | null;\n\t(params: Required<ExtraOptions>[\"query\"]): string;\n};\n\nexport const toQueryString: ToQueryStringFn = (params) => {\n\tif (!params) {\n\t\tconsole.error(\"No query params provided\");\n\t\treturn null as never;\n\t}\n\n\treturn new URLSearchParams(params as Record<string, string>).toString();\n};\n\nexport const mergeUrlWithParams = (url: string, params: ExtraOptions[\"query\"]): string => {\n\tif (!params) {\n\t\treturn url;\n\t}\n\n\tconst paramsString = toQueryString(params);\n\n\tif (url.endsWith(\"?\")) {\n\t\treturn `${url}${paramsString}`;\n\t}\n\n\tif (url.includes(\"?\")) {\n\t\treturn `${url}&${paramsString}`;\n\t}\n\n\treturn `${url}?${paramsString}`;\n};\n\nexport const objectifyHeaders = (headers: RequestInit[\"headers\"]): Record<string, string> | undefined => {\n\tif (!headers || isObject(headers)) {\n\t\treturn headers;\n\t}\n\n\treturn Object.fromEntries(isArray(headers) ? headers : headers.entries());\n};\n\nconst retryCodesLookup = {\n\t408: \"Request Timeout\",\n\t409: \"Conflict\",\n\t425: \"Too Early\",\n\t429: \"Too Many Requests\",\n\t500: \"Internal Server Error\",\n\t502: \"Bad Gateway\",\n\t503: \"Service Unavailable\",\n\t504: \"Gateway Timeout\",\n};\n\nexport const defaultRetryCodes: Required<BaseConfig>[\"retryCodes\"] =\n\tObject.keys(retryCodesLookup).map(Number);\n\nexport const defaultRetryMethods: Required<BaseConfig>[\"retryMethods\"] = [\"GET\"];\n\nexport const fetchSpecificKeys = [\n\t\"body\",\n\t\"integrity\",\n\t\"method\",\n\t\"headers\",\n\t\"signal\",\n\t\"cache\",\n\t\"redirect\",\n\t\"window\",\n\t\"credentials\",\n\t\"keepalive\",\n\t\"referrer\",\n\t\"priority\",\n\t\"mode\",\n\t\"referrerPolicy\",\n] satisfies Array<keyof FetchConfig>;\n\nconst omitKeys = <TObject extends Record<string, unknown>, const TOmitArray extends Array<keyof TObject>>(\n\tinitialObject: TObject,\n\tkeysToOmit: TOmitArray\n) => {\n\tconst arrayFromFilteredObject = Object.entries(initialObject).filter(\n\t\t([key]) => !keysToOmit.includes(key)\n\t);\n\n\tconst updatedObject = Object.fromEntries(arrayFromFilteredObject);\n\n\treturn updatedObject as Omit<TObject, keyof TOmitArray>;\n};\n\nconst pickKeys = <TObject extends Record<string, unknown>, const TPickArray extends Array<keyof TObject>>(\n\tinitialObject: TObject,\n\tkeysToPick: TPickArray\n) => {\n\tconst keysToPickSet = new Set(keysToPick);\n\n\tconst arrayFromInitObject = Object.entries(initialObject);\n\n\tconst filteredArray = arrayFromInitObject.filter(([objectKey]) => keysToPickSet.has(objectKey));\n\n\tconst updatedObject = Object.fromEntries(filteredArray);\n\n\treturn updatedObject as Pick<TObject, TPickArray[number]>;\n};\n\nexport const splitConfig = <TObject extends object>(\n\tconfig: TObject\n): [\"body\" extends keyof TObject ? $RequestOptions : $BaseRequestOptions, ExtraOptions] => [\n\tpickKeys(config as Record<string, unknown>, fetchSpecificKeys) as never,\n\tomitKeys(config as Record<string, unknown>, fetchSpecificKeys) as never,\n];\n\nexport const handleResponseType = <TResponse>(\n\tresponse: Response,\n\tparser?: Required<ExtraOptions>[\"responseParser\"]\n) => ({\n\tjson: async () => {\n\t\tif (parser) {\n\t\t\treturn parser(await response.text());\n\t\t}\n\n\t\treturn response.json() as Promise<TResponse>;\n\t},\n\tarrayBuffer: () => response.arrayBuffer() as Promise<TResponse>,\n\tblob: () => response.blob() as Promise<TResponse>,\n\tformData: () => response.formData() as Promise<TResponse>,\n\ttext: () => response.text() as Promise<TResponse>,\n});\n\nexport const getResponseData = <TResponse>(\n\tresponse: Response,\n\tresponseType: keyof ReturnType<typeof handleResponseType>,\n\tparser: ExtraOptions[\"responseParser\"]\n) => {\n\tconst RESPONSE_TYPE_LOOKUP = handleResponseType<TResponse>(response, parser);\n\n\tif (!Object.hasOwn(RESPONSE_TYPE_LOOKUP, responseType)) {\n\t\tthrow new Error(`Invalid response type: ${responseType}`);\n\t}\n\n\treturn RESPONSE_TYPE_LOOKUP[responseType]();\n};\n\ntype data = {\n\tsuccessData: unknown;\n\toptions: ExtraOptions;\n\tresponse: Response;\n};\n\n// == The CallApiResult type is used to cast all return statements due to a design limitation in ts.\n// LINK - See https://www.zhenghao.io/posts/type-functions for more info\nexport const resolveSuccessResult = <CallApiResult>(info: data): CallApiResult => {\n\tconst { options, response, successData } = info;\n\n\tconst apiDetails = {\n\t\tdata: successData,\n\t\terror: null,\n\t\tresponse,\n\t};\n\n\tif (options.resultMode === undefined || options.resultMode === \"all\") {\n\t\treturn apiDetails as CallApiResult;\n\t}\n\n\treturn {\n\t\tonlySuccess: apiDetails.data,\n\t\tonlyError: apiDetails.error,\n\t\tonlyResponse: apiDetails.response,\n\t}[options.resultMode] as CallApiResult;\n};\n\n// == Using curring here so error and options are not required to be passed every time, instead to be captured once by way of closure\nexport const $resolveErrorResult = <CallApiResult>($info: { error?: unknown; options: ExtraOptions }) => {\n\tconst { error, options } = $info;\n\n\ttype ErrorInfo = {\n\t\tresponse?: Response;\n\t\terrorData?: unknown;\n\t\tmessage?: string;\n\t};\n\n\tconst resolveErrorResult = (info: ErrorInfo = {}): CallApiResult => {\n\t\tconst { errorData, message, response } = info;\n\n\t\tconst shouldThrowOnError = isFunction(options.throwOnError)\n\t\t\t? options.throwOnError(error as Error)\n\t\t\t: options.throwOnError;\n\n\t\tif (shouldThrowOnError) {\n\t\t\tthrow error;\n\t\t}\n\n\t\treturn {\n\t\t\tdata: null,\n\t\t\terror: {\n\t\t\t\tname: (error as PossibleErrorObject)?.name ?? \"UnknownError\",\n\t\t\t\terrorData: errorData ?? error,\n\t\t\t\tmessage: message ?? (error as PossibleErrorObject)?.message ?? options.defaultErrorMessage,\n\t\t\t},\n\t\t\tresponse: response ?? null,\n\t\t} as CallApiResult;\n\t};\n\n\treturn resolveErrorResult;\n};\n\nexport const isHTTPError = <TErrorData>(error: ApiErrorVariant<TErrorData>[\"error\"] | null) => {\n\treturn isObject(error) && error.name === \"HTTPError\";\n};\n\ntype ErrorDetails<TErrorResponse> = {\n\terrorData: TErrorResponse;\n\tresponse: Response;\n\tdefaultErrorMessage: string;\n};\n\ntype ErrorOptions = {\n\tcause?: unknown;\n};\n\nexport class HTTPError<TErrorResponse = Record<string, unknown>> extends Error {\n\tresponse: ErrorDetails<TErrorResponse>[\"response\"];\n\terrorData: ErrorDetails<TErrorResponse>[\"errorData\"];\n\n\toverride name = \"HTTPError\" as const;\n\n\tisHTTPError = true;\n\n\tconstructor(errorDetails: ErrorDetails<TErrorResponse>, errorOptions?: ErrorOptions) {\n\t\tconst { defaultErrorMessage, response, errorData } = errorDetails;\n\n\t\tsuper((errorData as { message?: string }).message ?? defaultErrorMessage, errorOptions);\n\n\t\tthis.errorData = errorData;\n\t\tthis.response = response;\n\t}\n}\n\n// prettier-ignore\nexport const isHTTPErrorInstance = <TErrorResponse>(\n\terror: unknown\n): error is HTTPError<TErrorResponse> => {\n\treturn (\n\t\terror instanceof HTTPError || (isObject(error) && error.name === \"HTTPError\" && error.isHTTPError === true)\n\t);\n};\n\nexport const waitUntil = (delay: number) => {\n\tif (delay === 0) return;\n\n\tconst { promise, resolve } = Promise.withResolvers();\n\n\tsetTimeout(resolve, delay);\n\n\treturn promise;\n};\n","import { isFormData, isObject, isString } from \"./typeof\";\nimport type {\n\t$RequestOptions,\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\nexport const createFetchClient = <\n\tTBaseData,\n\tTBaseErrorData = unknown,\n\tTBaseResultMode extends ResultModeUnion = 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 {\n\t\theaders: baseHeaders,\n\t\tbody: baseBody,\n\t\tsignal: baseSignal,\n\t\t...restOfBaseFetchConfig\n\t} = baseFetchConfig;\n\n\t/* eslint-disable complexity */\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 { signal = baseSignal, body = baseBody, headers, ...restOfFetchConfig } = fetchConfig;\n\n\t\t// == Default Options\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 prevFetchController = abortControllerStore.get(url);\n\n\t\tif (prevFetchController && options.cancelRedundantRequests) {\n\t\t\tconst reason = new DOMException(\n\t\t\t\t`Automatic cancelation of the previous unfinished request to this same url: ${url}`,\n\t\t\t\t\"AbortError\"\n\t\t\t);\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\tconst combinedSignal = AbortSignal.any([\n\t\t\tnewFetchController.signal,\n\t\t\t...(timeoutSignal ? [timeoutSignal] : []),\n\t\t\t...(signal ? [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...(isString(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...(isObject(options.auth) && {\n\t\t\t\t\t\t\t\tAuthorization:\n\t\t\t\t\t\t\t\t\t\"bearer\" in options.auth\n\t\t\t\t\t\t\t\t\t\t? `Bearer ${options.auth.bearer}`\n\t\t\t\t\t\t\t\t\t\t: `Token ${options.auth.token}`,\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\toptions.cloneResponse ? response.clone() : response,\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\terrorData,\n\t\t\t\t\tresponse,\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\toptions.cloneResponse ? response.clone() : response,\n\t\t\t\toptions.responseType,\n\t\t\t\toptions.responseParser\n\t\t\t);\n\n\t\t\tconst validSuccessData = options.responseValidator\n\t\t\t\t? options.responseValidator(successData)\n\t\t\t\t: successData;\n\n\t\t\tawait options.onResponse?.({\n\t\t\t\tdata: validSuccessData,\n\t\t\t\tresponse: options.cloneResponse ? response.clone() : response,\n\t\t\t\trequest: requestInit,\n\t\t\t\toptions,\n\t\t\t});\n\n\t\t\treturn resolveSuccessResult<CallApiResult>({ successData: validSuccessData, 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.error(`${error.name}:`, message);\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 aborted due to ${error.message}`;\n\n\t\t\t\tconsole.error(`${error.name}:`, message);\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;\n\n\t\t\t\tawait options.onResponseError?.({\n\t\t\t\t\terrorData,\n\t\t\t\t\tresponse: options.cloneResponse ? response.clone() : response,\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\tmessage: (errorData as PossibleErrorObject)?.message,\n\t\t\t\t\tresponse,\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, reason?: unknown) => {\n\t\treason ? abortControllerStore.get(url)?.abort(reason) : abortControllerStore.get(url)?.abort();\n\t};\n\n\treturn callApi;\n};\n\nexport const callApi = createFetchClient();\n"],"mappings":"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","names":["src_exports","__export","HTTPError","callApi","createFetchClient","isHTTPError","isHTTPErrorInstance","toQueryString","__toCommonJS","isArray","value","isFormData","isObject","isFunction","isString","toQueryString","params","mergeUrlWithParams","url","paramsString","objectifyHeaders","headers","isObject","isArray","retryCodesLookup","defaultRetryCodes","defaultRetryMethods","fetchSpecificKeys","omitKeys","initialObject","keysToOmit","arrayFromFilteredObject","key","pickKeys","keysToPick","keysToPickSet","filteredArray","objectKey","splitConfig","config","handleResponseType","response","parser","getResponseData","responseType","RESPONSE_TYPE_LOOKUP","resolveSuccessResult","info","options","successData","apiDetails","$resolveErrorResult","$info","error","errorData","message","isFunction","isHTTPError","HTTPError","errorDetails","errorOptions","defaultErrorMessage","isHTTPErrorInstance","waitUntil","delay","promise","resolve","createFetchClient","baseConfig","abortControllerStore","baseFetchConfig","baseExtraOptions","splitConfig","baseHeaders","baseBody","baseSignal","restOfBaseFetchConfig","callApi","url","config","fetchConfig","extraOptions","signal","body","headers","restOfFetchConfig","options","defaultRetryCodes","defaultRetryMethods","prevFetchController","reason","newFetchController","timeoutSignal","combinedSignal","requestInit","isObject","isFormData","isString","objectifyHeaders","response","mergeUrlWithParams","waitUntil","errorData","getResponseData","HTTPError","successData","validSuccessData","resolveSuccessResult","error","resolveErrorResult","$resolveErrorResult","message","isHTTPErrorInstance"]}
|
package/dist/cjs/index.d.cts
CHANGED
|
@@ -67,7 +67,7 @@ interface ExtraOptions<TData = unknown, TErrorData = unknown, TResultMode extend
|
|
|
67
67
|
/**
|
|
68
68
|
* @description Custom function to validate the response data.
|
|
69
69
|
*/
|
|
70
|
-
responseValidator?: (data:
|
|
70
|
+
responseValidator?: (data: unknown) => TData;
|
|
71
71
|
/**
|
|
72
72
|
* @description Custom function to serialize the body object into a string.
|
|
73
73
|
*/
|
|
@@ -232,14 +232,14 @@ type ResultModeUnion = {
|
|
|
232
232
|
}["_"];
|
|
233
233
|
type GetCallApiResult<TData, TErrorData, TResultMode> = TResultMode extends NonNullable<ResultModeUnion> ? ResultModeMap<TData, TErrorData>[TResultMode] : ResultModeMap<TData, TErrorData>["all"];
|
|
234
234
|
|
|
235
|
-
declare const createFetchClient: <TBaseData, TBaseErrorData, TBaseResultMode extends ResultModeUnion = undefined>(baseConfig?: BaseConfig<TBaseData, TBaseErrorData, TBaseResultMode>) => {
|
|
235
|
+
declare const createFetchClient: <TBaseData, TBaseErrorData = unknown, TBaseResultMode extends ResultModeUnion = undefined>(baseConfig?: BaseConfig<TBaseData, TBaseErrorData, TBaseResultMode>) => {
|
|
236
236
|
<TData = TBaseData, TErrorData = TBaseErrorData, TResultMode extends ResultModeUnion = TBaseResultMode>(url: string, config?: FetchConfig<TData, TErrorData, TResultMode>): Promise<GetCallApiResult<TData, TErrorData, TResultMode>>;
|
|
237
237
|
create: any;
|
|
238
238
|
cancel(url: string, reason?: unknown): void;
|
|
239
239
|
};
|
|
240
240
|
declare const callApi: {
|
|
241
241
|
<TData = unknown, TErrorData = unknown, TResultMode extends ResultModeUnion = undefined>(url: string, config?: FetchConfig<TData, TErrorData, TResultMode> | undefined): Promise<GetCallApiResult<TData, TErrorData, TResultMode>>;
|
|
242
|
-
create: <TBaseData, TBaseErrorData, TBaseResultMode extends ResultModeUnion = undefined>(baseConfig?: BaseConfig<TBaseData, TBaseErrorData, TBaseResultMode>) => {
|
|
242
|
+
create: <TBaseData, TBaseErrorData = unknown, TBaseResultMode extends ResultModeUnion = undefined>(baseConfig?: BaseConfig<TBaseData, TBaseErrorData, TBaseResultMode>) => {
|
|
243
243
|
<TData = TBaseData, TErrorData = TBaseErrorData, TResultMode extends ResultModeUnion = TBaseResultMode>(url: string, config?: FetchConfig<TData, TErrorData, TResultMode>): Promise<GetCallApiResult<TData, TErrorData, TResultMode>>;
|
|
244
244
|
create: any;
|
|
245
245
|
cancel(url: string, reason?: unknown): void;
|
|
@@ -0,0 +1,8 @@
|
|
|
1
|
+
import { g, d, e, c as c$1, b as b$1, o, i, m, j, k, n } from './chunk-GSYOXK46.js';
|
|
2
|
+
import { c, b, e as e$1 } from './chunk-3E33QJL5.js';
|
|
3
|
+
|
|
4
|
+
var S=F=>{let i$1=new Map,[P,j$1]=g(F??{}),{headers:h,body:v,signal:U,...k$1}=P,d$1=async(s,l)=>{let[z,G]=g(l??{}),{signal:m$1=U,body:o$1=v,headers:g$1,...H}=z,e$2={bodySerializer:JSON.stringify,responseType:"json",baseURL:"",retries:0,retryDelay:0,retryCodes:d,retryMethods:e,defaultErrorMessage:"Failed to fetch data from server!",cancelRedundantRequests:!0,...j$1,...G},y=i$1.get(s);if(y&&e$2.cancelRedundantRequests){let t=new DOMException(`Automatic cancelation of the previous unfinished request to this same url: ${s}`,"AbortError");y.abort(t);}let b$2=new AbortController;i$1.set(s,b$2);let E=e$2.timeout?AbortSignal.timeout(e$2.timeout):null,D=AbortSignal.any([b$2.signal,...E?[E]:[],...m$1?[m$1]:[]]),a={signal:D,method:"GET",body:c(o$1)?e$2.bodySerializer(o$1):o$1,headers:h||g$1||e$2.auth||c(o$1)?{...c(o$1)&&{"Content-Type":"application/json",Accept:"application/json"},...b(o$1)&&{"Content-Type":"multipart/form-data"},...e$1(o$1)&&{"Content-Type":"application/x-www-form-urlencoded"},...e$1(e$2.auth)&&{Authorization:`Bearer ${e$2.auth}`},...c(e$2.auth)&&{Authorization:"bearer"in e$2.auth?`Bearer ${e$2.auth.bearer}`:`Token ${e$2.auth.token}`},...c$1(h),...c$1(g$1)}:void 0,...k$1,...H};try{await e$2.onRequest?.({request:a,options:e$2});let t=await fetch(`${e$2.baseURL}${b$1(s,e$2.query)}`,a);if(!t.ok&&!D.aborted&&e$2.retries>0&&e$2.retryCodes.includes(t.status)&&e$2.retryMethods.includes(a.method))return await o(e$2.retryDelay),await d$1(s,{...l,retries:e$2.retries-1});if(!t.ok){let I=await i(e$2.cloneResponse?t.clone():t,e$2.responseType,e$2.responseParser);throw new m({errorData:I,response:t,defaultErrorMessage:e$2.defaultErrorMessage})}let r=await i(e$2.cloneResponse?t.clone():t,e$2.responseType,e$2.responseParser),n=e$2.responseValidator?e$2.responseValidator(r):r;return await e$2.onResponse?.({data:n,response:e$2.cloneResponse?t.clone():t,request:a,options:e$2}),j({successData:n,response:t,options:e$2})}catch(t){let c=k({error:t,options:e$2});if(t instanceof DOMException&&t.name==="TimeoutError"){let r=`Request timed out after ${e$2.timeout}ms`;return console.error(`${t.name}:`,r),c({message:r})}if(t instanceof DOMException&&t.name==="AbortError"){let r=`Request aborted due to ${t.message}`;return console.error(`${t.name}:`,r),c({message:r})}if(n(t)){let{errorData:r,response:n}=t;return await e$2.onResponseError?.({errorData:r,response:e$2.cloneResponse?n.clone():n,request:a,options:e$2}),c({errorData:r,message:r?.message,response:n})}return await e$2.onRequestError?.({request:a,error:t,options:e$2}),c()}finally{i$1.delete(s);}};return d$1.create=S,d$1.cancel=(s,l)=>{l?i$1.get(s)?.abort(l):i$1.get(s)?.abort();},d$1},J=S();
|
|
5
|
+
|
|
6
|
+
export { S as a, J as b };
|
|
7
|
+
//# sourceMappingURL=out.js.map
|
|
8
|
+
//# sourceMappingURL=chunk-AYODUGGR.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"sources":["../../src/createFetchClient.ts"],"names":["createFetchClient","baseConfig","abortControllerStore","baseFetchConfig","baseExtraOptions","splitConfig","baseHeaders","baseBody","baseSignal","restOfBaseFetchConfig","callApi","url","config","fetchConfig","extraOptions","signal","body","headers","restOfFetchConfig","options","defaultRetryCodes","defaultRetryMethods","prevFetchController","reason","newFetchController","timeoutSignal","combinedSignal","requestInit","isObject","isFormData","isString","objectifyHeaders","response","mergeUrlWithParams","waitUntil","errorData","getResponseData","HTTPError","successData","validSuccessData","resolveSuccessResult","error","resolveErrorResult","$resolveErrorResult","message","isHTTPErrorInstance"],"mappings":"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","sourcesContent":["import { isFormData, isObject, isString } from \"./typeof\";\nimport type {\n\t$RequestOptions,\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\nexport const createFetchClient = <\n\tTBaseData,\n\tTBaseErrorData = unknown,\n\tTBaseResultMode extends ResultModeUnion = 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 {\n\t\theaders: baseHeaders,\n\t\tbody: baseBody,\n\t\tsignal: baseSignal,\n\t\t...restOfBaseFetchConfig\n\t} = baseFetchConfig;\n\n\t/* eslint-disable complexity */\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 { signal = baseSignal, body = baseBody, headers, ...restOfFetchConfig } = fetchConfig;\n\n\t\t// == Default Options\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 prevFetchController = abortControllerStore.get(url);\n\n\t\tif (prevFetchController && options.cancelRedundantRequests) {\n\t\t\tconst reason = new DOMException(\n\t\t\t\t`Automatic cancelation of the previous unfinished request to this same url: ${url}`,\n\t\t\t\t\"AbortError\"\n\t\t\t);\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\tconst combinedSignal = AbortSignal.any([\n\t\t\tnewFetchController.signal,\n\t\t\t...(timeoutSignal ? [timeoutSignal] : []),\n\t\t\t...(signal ? [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...(isString(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...(isObject(options.auth) && {\n\t\t\t\t\t\t\t\tAuthorization:\n\t\t\t\t\t\t\t\t\t\"bearer\" in options.auth\n\t\t\t\t\t\t\t\t\t\t? `Bearer ${options.auth.bearer}`\n\t\t\t\t\t\t\t\t\t\t: `Token ${options.auth.token}`,\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\toptions.cloneResponse ? response.clone() : response,\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\terrorData,\n\t\t\t\t\tresponse,\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\toptions.cloneResponse ? response.clone() : response,\n\t\t\t\toptions.responseType,\n\t\t\t\toptions.responseParser\n\t\t\t);\n\n\t\t\tconst validSuccessData = options.responseValidator\n\t\t\t\t? options.responseValidator(successData)\n\t\t\t\t: successData;\n\n\t\t\tawait options.onResponse?.({\n\t\t\t\tdata: validSuccessData,\n\t\t\t\tresponse: options.cloneResponse ? response.clone() : response,\n\t\t\t\trequest: requestInit,\n\t\t\t\toptions,\n\t\t\t});\n\n\t\t\treturn resolveSuccessResult<CallApiResult>({ successData: validSuccessData, 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.error(`${error.name}:`, message);\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 aborted due to ${error.message}`;\n\n\t\t\t\tconsole.error(`${error.name}:`, message);\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;\n\n\t\t\t\tawait options.onResponseError?.({\n\t\t\t\t\terrorData,\n\t\t\t\t\tresponse: options.cloneResponse ? response.clone() : response,\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\tmessage: (errorData as PossibleErrorObject)?.message,\n\t\t\t\t\tresponse,\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, reason?: unknown) => {\n\t\treason ? abortControllerStore.get(url)?.abort(reason) : abortControllerStore.get(url)?.abort();\n\t};\n\n\treturn callApi;\n};\n\nexport const callApi = createFetchClient();\n"]}
|
|
@@ -1,7 +1,7 @@
|
|
|
1
1
|
import { c as c$1, a, d } from './chunk-3E33QJL5.js';
|
|
2
2
|
|
|
3
|
-
var E=r=>r?new URLSearchParams(r).toString():(console.error("No query params provided"),null),b=(r,e)=>{if(!e)return r;let t=E(e);return r.endsWith("?")?`${r}${t}`:r.includes("?")?`${r}&${t}`:`${r}?${t}`},g=r=>!r||c$1(r)?r:Object.fromEntries(a(r)?r:r.entries()),y={408:"Request Timeout",409:"Conflict",425:"Too Early",429:"Too Many Requests",500:"Internal Server Error",502:"Bad Gateway",503:"Service Unavailable",504:"Gateway Timeout"},x=Object.keys(y).map(Number),j=["GET"],l=["body","integrity","method","headers","signal","cache","redirect","window","credentials","keepalive","referrer","priority","mode","referrerPolicy"],T=(r,e)=>{let t=Object.entries(r).filter(([s])=>!e.includes(s));return Object.fromEntries(t)},R=(r,e)=>{let t=new Set(e),s=Object.entries(r).filter(([i])=>t.has(i));return Object.fromEntries(s)},w=r=>[R(r,l),T(r,l)],O=(r,e)=>({json:async()=>e?e(await r.text()):r.json(),arrayBuffer:()=>r.arrayBuffer(),blob:()=>r.blob(),formData:()=>r.formData(),text:()=>r.text()}),P=(r,e,t)=>{let o=O(r,t);if(!Object.hasOwn(o,e))throw new Error(`Invalid response type: ${e}`);return o[e]()},k=r=>{let{options:e,response:t,successData:o}=r,s={data:o,
|
|
3
|
+
var E=r=>r?new URLSearchParams(r).toString():(console.error("No query params provided"),null),b=(r,e)=>{if(!e)return r;let t=E(e);return r.endsWith("?")?`${r}${t}`:r.includes("?")?`${r}&${t}`:`${r}?${t}`},g=r=>!r||c$1(r)?r:Object.fromEntries(a(r)?r:r.entries()),y={408:"Request Timeout",409:"Conflict",425:"Too Early",429:"Too Many Requests",500:"Internal Server Error",502:"Bad Gateway",503:"Service Unavailable",504:"Gateway Timeout"},x=Object.keys(y).map(Number),j=["GET"],l=["body","integrity","method","headers","signal","cache","redirect","window","credentials","keepalive","referrer","priority","mode","referrerPolicy"],T=(r,e)=>{let t=Object.entries(r).filter(([s])=>!e.includes(s));return Object.fromEntries(t)},R=(r,e)=>{let t=new Set(e),s=Object.entries(r).filter(([i])=>t.has(i));return Object.fromEntries(s)},w=r=>[R(r,l),T(r,l)],O=(r,e)=>({json:async()=>e?e(await r.text()):r.json(),arrayBuffer:()=>r.arrayBuffer(),blob:()=>r.blob(),formData:()=>r.formData(),text:()=>r.text()}),P=(r,e,t)=>{let o=O(r,t);if(!Object.hasOwn(o,e))throw new Error(`Invalid response type: ${e}`);return o[e]()},k=r=>{let{options:e,response:t,successData:o}=r,s={data:o,error:null,response:t};return e.resultMode===void 0||e.resultMode==="all"?s:{onlySuccess:s.data,onlyError:s.error,onlyResponse:s.response}[e.resultMode]},h=r=>{let{error:e,options:t}=r;return (s={})=>{let{errorData:n,message:i,response:d$1}=s;if(d(t.throwOnError)?t.throwOnError(e):t.throwOnError)throw e;return {data:null,error:{name:e?.name??"UnknownError",errorData:n??e,message:i??e?.message??t.defaultErrorMessage},response:d$1??null}}},A=r=>c$1(r)&&r.name==="HTTPError",c=class extends Error{response;errorData;name="HTTPError";isHTTPError=!0;constructor(e,t){let{defaultErrorMessage:o,response:s,errorData:n}=e;super(n.message??o,t),this.errorData=n,this.response=s;}},D=r=>r instanceof c||c$1(r)&&r.name==="HTTPError"&&r.isHTTPError===!0,C=r=>{if(r===0)return;let{promise:e,resolve:t}=Promise.withResolvers();return setTimeout(t,r),e};
|
|
4
4
|
|
|
5
5
|
export { E as a, b, g as c, x as d, j as e, l as f, w as g, O as h, P as i, k as j, h as k, A as l, c as m, D as n, C as o };
|
|
6
6
|
//# sourceMappingURL=out.js.map
|
|
7
|
-
//# sourceMappingURL=chunk-
|
|
7
|
+
//# sourceMappingURL=chunk-GSYOXK46.js.map
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"sources":["../../src/utils.ts"],"names":["toQueryString","params","mergeUrlWithParams","url","paramsString","objectifyHeaders","headers","isObject","isArray","retryCodesLookup","defaultRetryCodes","defaultRetryMethods","fetchSpecificKeys","omitKeys","initialObject","keysToOmit","arrayFromFilteredObject","key","pickKeys","keysToPick","keysToPickSet","filteredArray","objectKey","splitConfig","config","handleResponseType","response","parser","getResponseData","responseType","RESPONSE_TYPE_LOOKUP","resolveSuccessResult","info","options","successData","apiDetails","$resolveErrorResult","$info","error","errorData","message","isFunction","isHTTPError","HTTPError","errorDetails","errorOptions","defaultErrorMessage","isHTTPErrorInstance","waitUntil","delay","promise","resolve"],"mappings":"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","sourcesContent":["import { isArray, isFunction, isObject } from \"./typeof\";\nimport type {\n\t$BaseRequestOptions,\n\t$RequestOptions,\n\tApiErrorVariant,\n\tBaseConfig,\n\tExtraOptions,\n\tFetchConfig,\n\tPossibleErrorObject,\n} from \"./types\";\n\ntype ToQueryStringFn = {\n\t(params: ExtraOptions[\"query\"]): string | null;\n\t(params: Required<ExtraOptions>[\"query\"]): string;\n};\n\nexport const toQueryString: ToQueryStringFn = (params) => {\n\tif (!params) {\n\t\tconsole.error(\"No query params provided\");\n\t\treturn null as never;\n\t}\n\n\treturn new URLSearchParams(params as Record<string, string>).toString();\n};\n\nexport const mergeUrlWithParams = (url: string, params: ExtraOptions[\"query\"]): string => {\n\tif (!params) {\n\t\treturn url;\n\t}\n\n\tconst paramsString = toQueryString(params);\n\n\tif (url.endsWith(\"?\")) {\n\t\treturn `${url}${paramsString}`;\n\t}\n\n\tif (url.includes(\"?\")) {\n\t\treturn `${url}&${paramsString}`;\n\t}\n\n\treturn `${url}?${paramsString}`;\n};\n\nexport const objectifyHeaders = (headers: RequestInit[\"headers\"]): Record<string, string> | undefined => {\n\tif (!headers || isObject(headers)) {\n\t\treturn headers;\n\t}\n\n\treturn Object.fromEntries(isArray(headers) ? headers : headers.entries());\n};\n\nconst retryCodesLookup = {\n\t408: \"Request Timeout\",\n\t409: \"Conflict\",\n\t425: \"Too Early\",\n\t429: \"Too Many Requests\",\n\t500: \"Internal Server Error\",\n\t502: \"Bad Gateway\",\n\t503: \"Service Unavailable\",\n\t504: \"Gateway Timeout\",\n};\n\nexport const defaultRetryCodes: Required<BaseConfig>[\"retryCodes\"] =\n\tObject.keys(retryCodesLookup).map(Number);\n\nexport const defaultRetryMethods: Required<BaseConfig>[\"retryMethods\"] = [\"GET\"];\n\nexport const fetchSpecificKeys = [\n\t\"body\",\n\t\"integrity\",\n\t\"method\",\n\t\"headers\",\n\t\"signal\",\n\t\"cache\",\n\t\"redirect\",\n\t\"window\",\n\t\"credentials\",\n\t\"keepalive\",\n\t\"referrer\",\n\t\"priority\",\n\t\"mode\",\n\t\"referrerPolicy\",\n] satisfies Array<keyof FetchConfig>;\n\nconst omitKeys = <TObject extends Record<string, unknown>, const TOmitArray extends Array<keyof TObject>>(\n\tinitialObject: TObject,\n\tkeysToOmit: TOmitArray\n) => {\n\tconst arrayFromFilteredObject = Object.entries(initialObject).filter(\n\t\t([key]) => !keysToOmit.includes(key)\n\t);\n\n\tconst updatedObject = Object.fromEntries(arrayFromFilteredObject);\n\n\treturn updatedObject as Omit<TObject, keyof TOmitArray>;\n};\n\nconst pickKeys = <TObject extends Record<string, unknown>, const TPickArray extends Array<keyof TObject>>(\n\tinitialObject: TObject,\n\tkeysToPick: TPickArray\n) => {\n\tconst keysToPickSet = new Set(keysToPick);\n\n\tconst arrayFromInitObject = Object.entries(initialObject);\n\n\tconst filteredArray = arrayFromInitObject.filter(([objectKey]) => keysToPickSet.has(objectKey));\n\n\tconst updatedObject = Object.fromEntries(filteredArray);\n\n\treturn updatedObject as Pick<TObject, TPickArray[number]>;\n};\n\nexport const splitConfig = <TObject extends object>(\n\tconfig: TObject\n): [\"body\" extends keyof TObject ? $RequestOptions : $BaseRequestOptions, ExtraOptions] => [\n\tpickKeys(config as Record<string, unknown>, fetchSpecificKeys) as never,\n\tomitKeys(config as Record<string, unknown>, fetchSpecificKeys) as never,\n];\n\nexport const handleResponseType = <TResponse>(\n\tresponse: Response,\n\tparser?: Required<ExtraOptions>[\"responseParser\"]\n) => ({\n\tjson: async () => {\n\t\tif (parser) {\n\t\t\treturn parser(await response.text());\n\t\t}\n\n\t\treturn response.json() as Promise<TResponse>;\n\t},\n\tarrayBuffer: () => response.arrayBuffer() as Promise<TResponse>,\n\tblob: () => response.blob() as Promise<TResponse>,\n\tformData: () => response.formData() as Promise<TResponse>,\n\ttext: () => response.text() as Promise<TResponse>,\n});\n\nexport const getResponseData = <TResponse>(\n\tresponse: Response,\n\tresponseType: keyof ReturnType<typeof handleResponseType>,\n\tparser: ExtraOptions[\"responseParser\"]\n) => {\n\tconst RESPONSE_TYPE_LOOKUP = handleResponseType<TResponse>(response, parser);\n\n\tif (!Object.hasOwn(RESPONSE_TYPE_LOOKUP, responseType)) {\n\t\tthrow new Error(`Invalid response type: ${responseType}`);\n\t}\n\n\treturn RESPONSE_TYPE_LOOKUP[responseType]();\n};\n\ntype data = {\n\tsuccessData: unknown;\n\toptions: ExtraOptions;\n\tresponse: Response;\n};\n\n// == The CallApiResult type is used to cast all return statements due to a design limitation in ts.\n// LINK - See https://www.zhenghao.io/posts/type-functions for more info\nexport const resolveSuccessResult = <CallApiResult>(info: data): CallApiResult => {\n\tconst { options, response, successData } = info;\n\n\tconst apiDetails = {\n\t\tdata: successData,\n\t\terrorInfo: null,\n\t\tresponse,\n\t};\n\n\tif (options.resultMode === undefined || options.resultMode === \"all\") {\n\t\treturn apiDetails as CallApiResult;\n\t}\n\n\treturn {\n\t\tonlySuccess: apiDetails.data,\n\t\tonlyError: apiDetails.errorInfo,\n\t\tonlyResponse: apiDetails.response,\n\t}[options.resultMode] as CallApiResult;\n};\n\n// == Using curring here so error and options are not required to be passed every time, instead to be captured once by way of closure\nexport const $resolveErrorResult = <CallApiResult>($info: { error?: unknown; options: ExtraOptions }) => {\n\tconst { error, options } = $info;\n\n\ttype ErrorInfo = {\n\t\tresponse?: Response;\n\t\terrorData?: unknown;\n\t\tmessage?: string;\n\t};\n\n\tconst resolveErrorResult = (info: ErrorInfo = {}): CallApiResult => {\n\t\tconst { errorData, message, response } = info;\n\n\t\tconst shouldThrowOnError = isFunction(options.throwOnError)\n\t\t\t? options.throwOnError(error as Error)\n\t\t\t: options.throwOnError;\n\n\t\tif (shouldThrowOnError) {\n\t\t\tthrow error;\n\t\t}\n\n\t\treturn {\n\t\t\tdata: null,\n\t\t\terror: {\n\t\t\t\tname: (error as PossibleErrorObject)?.name ?? \"UnknownError\",\n\t\t\t\terrorData: errorData ?? error,\n\t\t\t\tmessage: message ?? (error as PossibleErrorObject)?.message ?? options.defaultErrorMessage,\n\t\t\t},\n\t\t\tresponse: response ?? null,\n\t\t} as CallApiResult;\n\t};\n\n\treturn resolveErrorResult;\n};\n\nexport const isHTTPError = <TErrorData>(error: ApiErrorVariant<TErrorData>[\"error\"] | null) => {\n\treturn isObject(error) && error.name === \"HTTPError\";\n};\n\ntype ErrorDetails<TErrorResponse> = {\n\terrorData: TErrorResponse;\n\tresponse: Response;\n\tdefaultErrorMessage: string;\n};\n\ntype ErrorOptions = {\n\tcause?: unknown;\n};\n\nexport class HTTPError<TErrorResponse = Record<string, unknown>> extends Error {\n\tresponse: ErrorDetails<TErrorResponse>[\"response\"];\n\terrorData: ErrorDetails<TErrorResponse>[\"errorData\"];\n\n\toverride name = \"HTTPError\" as const;\n\n\tisHTTPError = true;\n\n\tconstructor(errorDetails: ErrorDetails<TErrorResponse>, errorOptions?: ErrorOptions) {\n\t\tconst { defaultErrorMessage, response, errorData } = errorDetails;\n\n\t\tsuper((errorData as { message?: string }).message ?? defaultErrorMessage, errorOptions);\n\n\t\tthis.errorData = errorData;\n\t\tthis.response = response;\n\t}\n}\n\n// prettier-ignore\nexport const isHTTPErrorInstance = <TErrorResponse>(\n\terror: unknown\n): error is HTTPError<TErrorResponse> => {\n\treturn (\n\t\terror instanceof HTTPError || (isObject(error) && error.name === \"HTTPError\" && error.isHTTPError === true)\n\t);\n};\n\nexport const waitUntil = (delay: number) => {\n\tif (delay === 0) return;\n\n\tconst { promise, resolve } = Promise.withResolvers();\n\n\tsetTimeout(resolve, delay);\n\n\treturn promise;\n};\n"]}
|
|
1
|
+
{"version":3,"sources":["../../src/utils.ts"],"names":["toQueryString","params","mergeUrlWithParams","url","paramsString","objectifyHeaders","headers","isObject","isArray","retryCodesLookup","defaultRetryCodes","defaultRetryMethods","fetchSpecificKeys","omitKeys","initialObject","keysToOmit","arrayFromFilteredObject","key","pickKeys","keysToPick","keysToPickSet","filteredArray","objectKey","splitConfig","config","handleResponseType","response","parser","getResponseData","responseType","RESPONSE_TYPE_LOOKUP","resolveSuccessResult","info","options","successData","apiDetails","$resolveErrorResult","$info","error","errorData","message","isFunction","isHTTPError","HTTPError","errorDetails","errorOptions","defaultErrorMessage","isHTTPErrorInstance","waitUntil","delay","promise","resolve"],"mappings":"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","sourcesContent":["import { isArray, isFunction, isObject } from \"./typeof\";\nimport type {\n\t$BaseRequestOptions,\n\t$RequestOptions,\n\tApiErrorVariant,\n\tBaseConfig,\n\tExtraOptions,\n\tFetchConfig,\n\tPossibleErrorObject,\n} from \"./types\";\n\ntype ToQueryStringFn = {\n\t(params: ExtraOptions[\"query\"]): string | null;\n\t(params: Required<ExtraOptions>[\"query\"]): string;\n};\n\nexport const toQueryString: ToQueryStringFn = (params) => {\n\tif (!params) {\n\t\tconsole.error(\"No query params provided\");\n\t\treturn null as never;\n\t}\n\n\treturn new URLSearchParams(params as Record<string, string>).toString();\n};\n\nexport const mergeUrlWithParams = (url: string, params: ExtraOptions[\"query\"]): string => {\n\tif (!params) {\n\t\treturn url;\n\t}\n\n\tconst paramsString = toQueryString(params);\n\n\tif (url.endsWith(\"?\")) {\n\t\treturn `${url}${paramsString}`;\n\t}\n\n\tif (url.includes(\"?\")) {\n\t\treturn `${url}&${paramsString}`;\n\t}\n\n\treturn `${url}?${paramsString}`;\n};\n\nexport const objectifyHeaders = (headers: RequestInit[\"headers\"]): Record<string, string> | undefined => {\n\tif (!headers || isObject(headers)) {\n\t\treturn headers;\n\t}\n\n\treturn Object.fromEntries(isArray(headers) ? headers : headers.entries());\n};\n\nconst retryCodesLookup = {\n\t408: \"Request Timeout\",\n\t409: \"Conflict\",\n\t425: \"Too Early\",\n\t429: \"Too Many Requests\",\n\t500: \"Internal Server Error\",\n\t502: \"Bad Gateway\",\n\t503: \"Service Unavailable\",\n\t504: \"Gateway Timeout\",\n};\n\nexport const defaultRetryCodes: Required<BaseConfig>[\"retryCodes\"] =\n\tObject.keys(retryCodesLookup).map(Number);\n\nexport const defaultRetryMethods: Required<BaseConfig>[\"retryMethods\"] = [\"GET\"];\n\nexport const fetchSpecificKeys = [\n\t\"body\",\n\t\"integrity\",\n\t\"method\",\n\t\"headers\",\n\t\"signal\",\n\t\"cache\",\n\t\"redirect\",\n\t\"window\",\n\t\"credentials\",\n\t\"keepalive\",\n\t\"referrer\",\n\t\"priority\",\n\t\"mode\",\n\t\"referrerPolicy\",\n] satisfies Array<keyof FetchConfig>;\n\nconst omitKeys = <TObject extends Record<string, unknown>, const TOmitArray extends Array<keyof TObject>>(\n\tinitialObject: TObject,\n\tkeysToOmit: TOmitArray\n) => {\n\tconst arrayFromFilteredObject = Object.entries(initialObject).filter(\n\t\t([key]) => !keysToOmit.includes(key)\n\t);\n\n\tconst updatedObject = Object.fromEntries(arrayFromFilteredObject);\n\n\treturn updatedObject as Omit<TObject, keyof TOmitArray>;\n};\n\nconst pickKeys = <TObject extends Record<string, unknown>, const TPickArray extends Array<keyof TObject>>(\n\tinitialObject: TObject,\n\tkeysToPick: TPickArray\n) => {\n\tconst keysToPickSet = new Set(keysToPick);\n\n\tconst arrayFromInitObject = Object.entries(initialObject);\n\n\tconst filteredArray = arrayFromInitObject.filter(([objectKey]) => keysToPickSet.has(objectKey));\n\n\tconst updatedObject = Object.fromEntries(filteredArray);\n\n\treturn updatedObject as Pick<TObject, TPickArray[number]>;\n};\n\nexport const splitConfig = <TObject extends object>(\n\tconfig: TObject\n): [\"body\" extends keyof TObject ? $RequestOptions : $BaseRequestOptions, ExtraOptions] => [\n\tpickKeys(config as Record<string, unknown>, fetchSpecificKeys) as never,\n\tomitKeys(config as Record<string, unknown>, fetchSpecificKeys) as never,\n];\n\nexport const handleResponseType = <TResponse>(\n\tresponse: Response,\n\tparser?: Required<ExtraOptions>[\"responseParser\"]\n) => ({\n\tjson: async () => {\n\t\tif (parser) {\n\t\t\treturn parser(await response.text());\n\t\t}\n\n\t\treturn response.json() as Promise<TResponse>;\n\t},\n\tarrayBuffer: () => response.arrayBuffer() as Promise<TResponse>,\n\tblob: () => response.blob() as Promise<TResponse>,\n\tformData: () => response.formData() as Promise<TResponse>,\n\ttext: () => response.text() as Promise<TResponse>,\n});\n\nexport const getResponseData = <TResponse>(\n\tresponse: Response,\n\tresponseType: keyof ReturnType<typeof handleResponseType>,\n\tparser: ExtraOptions[\"responseParser\"]\n) => {\n\tconst RESPONSE_TYPE_LOOKUP = handleResponseType<TResponse>(response, parser);\n\n\tif (!Object.hasOwn(RESPONSE_TYPE_LOOKUP, responseType)) {\n\t\tthrow new Error(`Invalid response type: ${responseType}`);\n\t}\n\n\treturn RESPONSE_TYPE_LOOKUP[responseType]();\n};\n\ntype data = {\n\tsuccessData: unknown;\n\toptions: ExtraOptions;\n\tresponse: Response;\n};\n\n// == The CallApiResult type is used to cast all return statements due to a design limitation in ts.\n// LINK - See https://www.zhenghao.io/posts/type-functions for more info\nexport const resolveSuccessResult = <CallApiResult>(info: data): CallApiResult => {\n\tconst { options, response, successData } = info;\n\n\tconst apiDetails = {\n\t\tdata: successData,\n\t\terror: null,\n\t\tresponse,\n\t};\n\n\tif (options.resultMode === undefined || options.resultMode === \"all\") {\n\t\treturn apiDetails as CallApiResult;\n\t}\n\n\treturn {\n\t\tonlySuccess: apiDetails.data,\n\t\tonlyError: apiDetails.error,\n\t\tonlyResponse: apiDetails.response,\n\t}[options.resultMode] as CallApiResult;\n};\n\n// == Using curring here so error and options are not required to be passed every time, instead to be captured once by way of closure\nexport const $resolveErrorResult = <CallApiResult>($info: { error?: unknown; options: ExtraOptions }) => {\n\tconst { error, options } = $info;\n\n\ttype ErrorInfo = {\n\t\tresponse?: Response;\n\t\terrorData?: unknown;\n\t\tmessage?: string;\n\t};\n\n\tconst resolveErrorResult = (info: ErrorInfo = {}): CallApiResult => {\n\t\tconst { errorData, message, response } = info;\n\n\t\tconst shouldThrowOnError = isFunction(options.throwOnError)\n\t\t\t? options.throwOnError(error as Error)\n\t\t\t: options.throwOnError;\n\n\t\tif (shouldThrowOnError) {\n\t\t\tthrow error;\n\t\t}\n\n\t\treturn {\n\t\t\tdata: null,\n\t\t\terror: {\n\t\t\t\tname: (error as PossibleErrorObject)?.name ?? \"UnknownError\",\n\t\t\t\terrorData: errorData ?? error,\n\t\t\t\tmessage: message ?? (error as PossibleErrorObject)?.message ?? options.defaultErrorMessage,\n\t\t\t},\n\t\t\tresponse: response ?? null,\n\t\t} as CallApiResult;\n\t};\n\n\treturn resolveErrorResult;\n};\n\nexport const isHTTPError = <TErrorData>(error: ApiErrorVariant<TErrorData>[\"error\"] | null) => {\n\treturn isObject(error) && error.name === \"HTTPError\";\n};\n\ntype ErrorDetails<TErrorResponse> = {\n\terrorData: TErrorResponse;\n\tresponse: Response;\n\tdefaultErrorMessage: string;\n};\n\ntype ErrorOptions = {\n\tcause?: unknown;\n};\n\nexport class HTTPError<TErrorResponse = Record<string, unknown>> extends Error {\n\tresponse: ErrorDetails<TErrorResponse>[\"response\"];\n\terrorData: ErrorDetails<TErrorResponse>[\"errorData\"];\n\n\toverride name = \"HTTPError\" as const;\n\n\tisHTTPError = true;\n\n\tconstructor(errorDetails: ErrorDetails<TErrorResponse>, errorOptions?: ErrorOptions) {\n\t\tconst { defaultErrorMessage, response, errorData } = errorDetails;\n\n\t\tsuper((errorData as { message?: string }).message ?? defaultErrorMessage, errorOptions);\n\n\t\tthis.errorData = errorData;\n\t\tthis.response = response;\n\t}\n}\n\n// prettier-ignore\nexport const isHTTPErrorInstance = <TErrorResponse>(\n\terror: unknown\n): error is HTTPError<TErrorResponse> => {\n\treturn (\n\t\terror instanceof HTTPError || (isObject(error) && error.name === \"HTTPError\" && error.isHTTPError === true)\n\t);\n};\n\nexport const waitUntil = (delay: number) => {\n\tif (delay === 0) return;\n\n\tconst { promise, resolve } = Promise.withResolvers();\n\n\tsetTimeout(resolve, delay);\n\n\treturn promise;\n};\n"]}
|
|
@@ -1,14 +1,14 @@
|
|
|
1
|
-
import { R as ResultModeUnion, B as BaseConfig, F as FetchConfig, G as GetCallApiResult } from './types-
|
|
1
|
+
import { R as ResultModeUnion, B as BaseConfig, F as FetchConfig, G as GetCallApiResult } from './types-DIDORyP8.js';
|
|
2
2
|
import './type-helpers-Dibitydy.js';
|
|
3
3
|
|
|
4
|
-
declare const createFetchClient: <TBaseData, TBaseErrorData, TBaseResultMode extends ResultModeUnion = undefined>(baseConfig?: BaseConfig<TBaseData, TBaseErrorData, TBaseResultMode>) => {
|
|
4
|
+
declare const createFetchClient: <TBaseData, TBaseErrorData = unknown, TBaseResultMode extends ResultModeUnion = undefined>(baseConfig?: BaseConfig<TBaseData, TBaseErrorData, TBaseResultMode>) => {
|
|
5
5
|
<TData = TBaseData, TErrorData = TBaseErrorData, TResultMode extends ResultModeUnion = TBaseResultMode>(url: string, config?: FetchConfig<TData, TErrorData, TResultMode>): Promise<GetCallApiResult<TData, TErrorData, TResultMode>>;
|
|
6
6
|
create: any;
|
|
7
7
|
cancel(url: string, reason?: unknown): void;
|
|
8
8
|
};
|
|
9
9
|
declare const callApi: {
|
|
10
10
|
<TData = unknown, TErrorData = unknown, TResultMode extends ResultModeUnion = undefined>(url: string, config?: FetchConfig<TData, TErrorData, TResultMode> | undefined): Promise<GetCallApiResult<TData, TErrorData, TResultMode>>;
|
|
11
|
-
create: <TBaseData, TBaseErrorData, TBaseResultMode extends ResultModeUnion = undefined>(baseConfig?: BaseConfig<TBaseData, TBaseErrorData, TBaseResultMode>) => {
|
|
11
|
+
create: <TBaseData, TBaseErrorData = unknown, TBaseResultMode extends ResultModeUnion = undefined>(baseConfig?: BaseConfig<TBaseData, TBaseErrorData, TBaseResultMode>) => {
|
|
12
12
|
<TData = TBaseData, TErrorData = TBaseErrorData, TResultMode extends ResultModeUnion = TBaseResultMode>(url: string, config?: FetchConfig<TData, TErrorData, TResultMode>): Promise<GetCallApiResult<TData, TErrorData, TResultMode>>;
|
|
13
13
|
create: any;
|
|
14
14
|
cancel(url: string, reason?: unknown): void;
|
|
@@ -1,5 +1,5 @@
|
|
|
1
|
-
export { b as callApi, a as createFetchClient } from './chunk-
|
|
2
|
-
import './chunk-
|
|
1
|
+
export { b as callApi, a as createFetchClient } from './chunk-AYODUGGR.js';
|
|
2
|
+
import './chunk-GSYOXK46.js';
|
|
3
3
|
import './chunk-3E33QJL5.js';
|
|
4
4
|
//# sourceMappingURL=out.js.map
|
|
5
5
|
//# sourceMappingURL=createFetchClient.js.map
|
package/dist/esm/index.d.ts
CHANGED
|
@@ -1,3 +1,3 @@
|
|
|
1
1
|
export { callApi, createFetchClient } from './createFetchClient.js';
|
|
2
|
-
export { $ as $RequestOptions, E as ExtraOptions, F as FetchConfig, H as HTTPError, a as ResponseContext, b as ResponseErrorContext, i as isHTTPError, c as isHTTPErrorInstance, t as toQueryString } from './types-
|
|
2
|
+
export { $ as $RequestOptions, E as ExtraOptions, F as FetchConfig, H as HTTPError, a as ResponseContext, b as ResponseErrorContext, i as isHTTPError, c as isHTTPErrorInstance, t as toQueryString } from './types-DIDORyP8.js';
|
|
3
3
|
import './type-helpers-Dibitydy.js';
|
package/dist/esm/index.js
CHANGED
|
@@ -1,5 +1,5 @@
|
|
|
1
|
-
export { b as callApi, a as createFetchClient } from './chunk-
|
|
2
|
-
export { m as HTTPError, l as isHTTPError, n as isHTTPErrorInstance, a as toQueryString } from './chunk-
|
|
1
|
+
export { b as callApi, a as createFetchClient } from './chunk-AYODUGGR.js';
|
|
2
|
+
export { m as HTTPError, l as isHTTPError, n as isHTTPErrorInstance, a as toQueryString } from './chunk-GSYOXK46.js';
|
|
3
3
|
import './chunk-3E33QJL5.js';
|
|
4
4
|
//# sourceMappingURL=out.js.map
|
|
5
5
|
//# sourceMappingURL=index.js.map
|
|
@@ -85,7 +85,7 @@ interface ExtraOptions<TData = unknown, TErrorData = unknown, TResultMode extend
|
|
|
85
85
|
/**
|
|
86
86
|
* @description Custom function to validate the response data.
|
|
87
87
|
*/
|
|
88
|
-
responseValidator?: (data:
|
|
88
|
+
responseValidator?: (data: unknown) => TData;
|
|
89
89
|
/**
|
|
90
90
|
* @description Custom function to serialize the body object into a string.
|
|
91
91
|
*/
|
package/dist/esm/utils.d.ts
CHANGED
|
@@ -1,2 +1,2 @@
|
|
|
1
|
-
export { j as $resolveErrorResult, H as HTTPError, d as defaultRetryCodes, e as defaultRetryMethods, f as fetchSpecificKeys, g as getResponseData, h as handleResponseType, i as isHTTPError, c as isHTTPErrorInstance, m as mergeUrlWithParams, o as objectifyHeaders, r as resolveSuccessResult, s as splitConfig, t as toQueryString, w as waitUntil } from './types-
|
|
1
|
+
export { j as $resolveErrorResult, H as HTTPError, d as defaultRetryCodes, e as defaultRetryMethods, f as fetchSpecificKeys, g as getResponseData, h as handleResponseType, i as isHTTPError, c as isHTTPErrorInstance, m as mergeUrlWithParams, o as objectifyHeaders, r as resolveSuccessResult, s as splitConfig, t as toQueryString, w as waitUntil } from './types-DIDORyP8.js';
|
|
2
2
|
import './type-helpers-Dibitydy.js';
|
package/dist/esm/utils.js
CHANGED
|
@@ -1,4 +1,4 @@
|
|
|
1
|
-
export { k as $resolveErrorResult, m as HTTPError, d as defaultRetryCodes, e as defaultRetryMethods, f as fetchSpecificKeys, i as getResponseData, h as handleResponseType, l as isHTTPError, n as isHTTPErrorInstance, b as mergeUrlWithParams, c as objectifyHeaders, j as resolveSuccessResult, g as splitConfig, a as toQueryString, o as waitUntil } from './chunk-
|
|
1
|
+
export { k as $resolveErrorResult, m as HTTPError, d as defaultRetryCodes, e as defaultRetryMethods, f as fetchSpecificKeys, i as getResponseData, h as handleResponseType, l as isHTTPError, n as isHTTPErrorInstance, b as mergeUrlWithParams, c as objectifyHeaders, j as resolveSuccessResult, g as splitConfig, a as toQueryString, o as waitUntil } from './chunk-GSYOXK46.js';
|
|
2
2
|
import './chunk-3E33QJL5.js';
|
|
3
3
|
//# sourceMappingURL=out.js.map
|
|
4
4
|
//# sourceMappingURL=utils.js.map
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@zayne-labs/callapi",
|
|
3
|
-
"version": "0.5.
|
|
3
|
+
"version": "0.5.2",
|
|
4
4
|
"type": "module",
|
|
5
5
|
"description": "A lightweight wrapper over fetch with quality of life improvements like built-in request cancellation, retries, interceptors and more",
|
|
6
6
|
"main": "./dist/esm/index.js",
|
|
@@ -1,8 +0,0 @@
|
|
|
1
|
-
import { g, d, e, c as c$1, b as b$1, o, i, m, j, k, n } from './chunk-YRSDB5GD.js';
|
|
2
|
-
import { c, b, e as e$1 } from './chunk-3E33QJL5.js';
|
|
3
|
-
|
|
4
|
-
var F=P=>{let n$1=new Map,[j$1,v]=g(P??{}),{headers:h,body:U,signal:k$1,...z}=j$1,u=async(s,i$1)=>{let[G,H]=g(i$1??{}),{signal:m$1=k$1,body:o$1=U,headers:g$1,...I}=G,e$2={bodySerializer:JSON.stringify,responseType:"json",baseURL:"",retries:0,retryDelay:0,retryCodes:d,retryMethods:e,defaultErrorMessage:"Failed to fetch data from server!",...v,...H},y=n$1.get(s);if(y&&e$2.cancelRedundantRequests){let t=new DOMException(`Automatic cancelation of the previous unfinished request to this same url: ${s}`,"AbortError");y.abort(t);}let b$2=new AbortController;n$1.set(s,b$2);let E=e$2.timeout?AbortSignal.timeout(e$2.timeout):null,D=AbortSignal.any([b$2.signal,...E?[E]:[],...m$1?[m$1]:[]]),a={signal:D,method:"GET",body:c(o$1)?e$2.bodySerializer(o$1):o$1,headers:h||g$1||e$2.auth||c(o$1)?{...c(o$1)&&{"Content-Type":"application/json",Accept:"application/json"},...b(o$1)&&{"Content-Type":"multipart/form-data"},...e$1(o$1)&&{"Content-Type":"application/x-www-form-urlencoded"},...e$1(e$2.auth)&&{Authorization:`Bearer ${e$2.auth}`},...c(e$2.auth)&&{Authorization:"bearer"in e$2.auth?`Bearer ${e$2.auth.bearer}`:`Token ${e$2.auth.token}`},...c$1(h),...c$1(g$1)}:void 0,...z,...I};try{await e$2.onRequest?.({request:a,options:e$2});let t=await fetch(`${e$2.baseURL}${b$1(s,e$2.query)}`,a);if(!t.ok&&!D.aborted&&e$2.retries>0&&e$2.retryCodes.includes(t.status)&&e$2.retryMethods.includes(a.method))return await o(e$2.retryDelay),await u(s,{...i$1,retries:e$2.retries-1});if(!t.ok){let C=await i(e$2.cloneResponse?t.clone():t,e$2.responseType,e$2.responseParser);throw await e$2.onResponseError?.({response:e$2.cloneResponse?t.clone():t,errorData:C,request:a,options:e$2}),new m({errorData:C,response:t,defaultErrorMessage:e$2.defaultErrorMessage})}let r=await i(e$2.cloneResponse?t.clone():t,e$2.responseType,e$2.responseParser),d=e$2.responseValidator?e$2.responseValidator(r):r;return await e$2.onResponse?.({response:e$2.cloneResponse?t.clone():t,data:d,request:a,options:e$2}),j({successData:d,response:t,options:e$2})}catch(t){let l=k({error:t,options:e$2});if(t instanceof DOMException&&t.name==="TimeoutError"){let r=`Request timed out after ${e$2.timeout}ms`;return console.error(`${t.name}:`,r),l({message:r})}if(t instanceof DOMException&&t.name==="AbortError"){let r=`Request aborted due to ${t.message}`;return console.error(`${t.name}:`,r),l({message:r})}if(n(t)){let{errorData:r,response:d}=t;return l({errorData:r,message:r?.message,response:d})}return await e$2.onRequestError?.({request:a,error:t,options:e$2}),l()}finally{n$1.delete(s);}};return u.create=F,u.cancel=(s,i)=>{i?n$1.get(s)?.abort(i):n$1.get(s)?.abort();},u},J=F();
|
|
5
|
-
|
|
6
|
-
export { F as a, J as b };
|
|
7
|
-
//# sourceMappingURL=out.js.map
|
|
8
|
-
//# sourceMappingURL=chunk-E7YEJJYJ.js.map
|
|
@@ -1 +0,0 @@
|
|
|
1
|
-
{"version":3,"sources":["../../src/createFetchClient.ts"],"names":["createFetchClient","baseConfig","abortControllerStore","baseFetchConfig","baseExtraOptions","splitConfig","baseHeaders","baseBody","baseSignal","restOfBaseFetchConfig","callApi","url","config","fetchConfig","extraOptions","signal","body","headers","restOfFetchConfig","options","defaultRetryCodes","defaultRetryMethods","prevFetchController","reason","newFetchController","timeoutSignal","combinedSignal","requestInit","isObject","isFormData","isString","objectifyHeaders","response","mergeUrlWithParams","waitUntil","errorData","getResponseData","HTTPError","successData","validSuccessData","resolveSuccessResult","error","resolveErrorResult","$resolveErrorResult","message","isHTTPErrorInstance"],"mappings":"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","sourcesContent":["import { isFormData, isObject, isString } from \"./typeof\";\nimport type {\n\t$RequestOptions,\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\nexport const createFetchClient = <\n\tTBaseData,\n\tTBaseErrorData,\n\tTBaseResultMode extends ResultModeUnion = 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 {\n\t\theaders: baseHeaders,\n\t\tbody: baseBody,\n\t\tsignal: baseSignal,\n\t\t...restOfBaseFetchConfig\n\t} = baseFetchConfig;\n\n\t/* eslint-disable complexity */\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 { signal = baseSignal, body = baseBody, headers, ...restOfFetchConfig } = fetchConfig;\n\n\t\t// == Default Options\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\t...baseExtraOptions,\n\t\t\t...extraOptions,\n\t\t} satisfies ExtraOptions;\n\n\t\tconst prevFetchController = abortControllerStore.get(url);\n\n\t\tif (prevFetchController && options.cancelRedundantRequests) {\n\t\t\tconst reason = new DOMException(\n\t\t\t\t`Automatic cancelation of the previous unfinished request to this same url: ${url}`,\n\t\t\t\t\"AbortError\"\n\t\t\t);\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\tconst combinedSignal = AbortSignal.any([\n\t\t\tnewFetchController.signal,\n\t\t\t...(timeoutSignal ? [timeoutSignal] : []),\n\t\t\t...(signal ? [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...(isString(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...(isObject(options.auth) && {\n\t\t\t\t\t\t\t\tAuthorization:\n\t\t\t\t\t\t\t\t\t\"bearer\" in options.auth\n\t\t\t\t\t\t\t\t\t\t? `Bearer ${options.auth.bearer}`\n\t\t\t\t\t\t\t\t\t\t: `Token ${options.auth.token}`,\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\toptions.cloneResponse ? response.clone() : response,\n\t\t\t\t\toptions.responseType,\n\t\t\t\t\toptions.responseParser\n\t\t\t\t);\n\n\t\t\t\tawait options.onResponseError?.({\n\t\t\t\t\tresponse: options.cloneResponse ? response.clone() : response,\n\t\t\t\t\terrorData,\n\t\t\t\t\trequest: requestInit,\n\t\t\t\t\toptions,\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\terrorData,\n\t\t\t\t\tresponse,\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\toptions.cloneResponse ? response.clone() : response,\n\t\t\t\toptions.responseType,\n\t\t\t\toptions.responseParser\n\t\t\t);\n\n\t\t\tconst validSuccessData = options.responseValidator\n\t\t\t\t? options.responseValidator(successData)\n\t\t\t\t: successData;\n\n\t\t\tawait options.onResponse?.({\n\t\t\t\tresponse: options.cloneResponse ? response.clone() : response,\n\t\t\t\tdata: validSuccessData,\n\t\t\t\trequest: requestInit,\n\t\t\t\toptions,\n\t\t\t});\n\n\t\t\treturn resolveSuccessResult<CallApiResult>({ successData: validSuccessData, 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.error(`${error.name}:`, message);\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 aborted due to ${error.message}`;\n\n\t\t\t\tconsole.error(`${error.name}:`, message);\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;\n\n\t\t\t\treturn resolveErrorResult({\n\t\t\t\t\terrorData,\n\t\t\t\t\tmessage: (errorData as PossibleErrorObject)?.message,\n\t\t\t\t\tresponse,\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, reason?: unknown) => {\n\t\treason ? abortControllerStore.get(url)?.abort(reason) : abortControllerStore.get(url)?.abort();\n\t};\n\n\treturn callApi;\n};\n\nexport const callApi = createFetchClient();\n"]}
|