diff --git a/README.md b/README.md index 86e2738..25d30c3 100644 --- a/README.md +++ b/README.md @@ -1,38 +1,39 @@ # Axios Multi API +Fast, lightweight and reusable data fetching + [npm-url]: https://npmjs.org/package/axios-multi-api [npm-image]: http://img.shields.io/npm/v/axios-multi-api.svg -[![NPM version][npm-image]][npm-url] [![Blazing Fast](https://badgen.now.sh/badge/speed/blazing%20%F0%9F%94%A5/green)](https://github.com/MattCCC/axios-multi-api) [![Code Coverage](https://badgen.now.sh/badge/coverage/94.53/blue)](https://github.com/MattCCC/axios-multi-api) [![npm downloads](https://img.shields.io/npm/dm/axios-multi-api.svg?style=flat-square)](http://npm-stat.com/charts.html?package=axios-multi-api) [![install size](https://packagephobia.now.sh/badge?p=axios-multi-api)](https://packagephobia.now.sh/result?p=axios-multi-api) +[![NPM version][npm-image]][npm-url] [![Blazing Fast](https://badgen.now.sh/badge/speed/blazing%20%F0%9F%94%A5/green)](https://github.com/MattCCC/axios-multi-api) [![Code Coverage](https://badgen.now.sh/badge/coverage/94.53/blue)](https://github.com/MattCCC/axios-multi-api) [![npm downloads](https://img.shields.io/npm/dm/axios-multi-api.svg?style=flat-square)](http://npm-stat.com/charts.html?package=axios-multi-api) [![gzip size](https://img.shields.io/bundlephobia/minzip/axios-multi-api)](https://bundlephobia.com/result?p=axios-multi-api) ## Why? -To handle many API endpoints and calls in a simple, declarative fashion. It aims to provide a possibility to additional fetching features with absolutely minimal code footprint. - -Oftentimes projects require complex APIs setups, middlewares and another stuff to accommodate a lot of API requests. This package simplifies API handling to the extent that developers can focus on operating on the fetched data from their APIs rather than on complex setups. You can set up multiple API fetchers for different sets of APIs from different services. It provides much better scalability for many projects. +Managing multiple API endpoints can be complex and time-consuming. `axios-multi-api` simplifies this process by offering a straightforward, declarative approach to API handling using Repository Pattern. It reduces the need for extensive setup and middlewares, allowing developers to focus on data manipulation and application logic. -> If you’re new to Axios, please check out [this handy Axios readme](https://github.com/axios/axios) +**Key Benefits:** -Package was originally written to accommodate many API requests in an orderly fashion. +**✅ Simplicity:** Minimal code footprint for managing extensive APIs. +**✅ Productivity:** Streamlines API interactions, enhancing developer efficiency. +**✅ Scalability:** Easily scales from a few endpoints to complex API networks. ## Features -- Fast, lightweight and reusable data fetching -- **Pure JavaScript, framework independent** -- **Easily manage large applications with many API endpoints** -- **Native fetch() support by default, so Axios can be skipped** -- Smart error retry with exponential backoff -- Error handling - global and per request -- Automatic cancellation of previous requests using `AbortController` -- Global and per request timeouts -- Multiple fetching strategies when requests fail - promise rejection, silently hang promise, provide default response, -- Dynamic URLs support e.g. `/user/:userId` -- Multiple requests chaining (using promises) -- All Axios options are supported -- 100% performance oriented solution -- **Browsers and Node 18+ compatible** -- **Fully TypeScript compatible** -- **Very lightweight, only a few KBs, gziped** +- **100% Performance-Oriented**: Optimized for speed and efficiency, ensuring fast and reliable API interactions. +- **Fully TypeScript Compatible**: Enjoy full TypeScript support for better development experience and type safety. +- **Smart Error Retry**: Features exponential backoff for intelligent error handling and retry mechanisms. +- **Dynamic URLs Support**: Easily manage routes with dynamic parameters, such as `/user/:userId`. +- **Native `fetch()` Support**: Uses the modern `fetch()` API by default, eliminating the need for Axios. +- **Global and Per Request Error Handling**: Flexible error management at both global and individual request levels. +- **Automatic Request Cancellation**: Utilizes `AbortController` to cancel previous requests automatically. +- **Global and Per Request Timeouts**: Set timeouts globally or per request to prevent hanging operations. +- **Multiple Fetching Strategies**: Handle failed requests with various strategies - promise rejection, silent hang, soft fail, or default response. +- **Multiple Requests Chaining**: Easily chain multiple requests using promises for complex API interactions. +- **Supports All Axios Options**: Fully compatible with all Axios configuration options for seamless integration. +- **Lightweight**: Minimal footprint, only a few KBs when gzipped, ensuring quick load times. +- **Framework Independent**: Pure JavaScript solution, compatible with any framework or library. +- **Browser and Node 18+ Compatible**: Works flawlessly in both modern browsers and Node.js environments. +- **Custom Interceptors**: Includes `onRequest`, `onResponse`, and `onError` interceptors for flexible request and response handling. Please open an issue for future requests. @@ -46,41 +47,57 @@ Using NPM: npm install axios-multi-api ``` -Using yarn: +Using Pnpm: + +```bash +pnpm install axios-multi-api +``` + +Using Yarn: ```bash yarn add axios-multi-api ``` -The native `fetch()` is used by default. If you want to use Axios, install it separately e.g. by running `npm install axios`, and then pass the import to the `createApiFetcher()` function. Check advanced example for more details. +### Standalone usage + +```typescript +import { fetchf } from 'axios-multi-api'; + +const { data } = await fetchf('/api/user-details'); +``` + +### Multiple API Endpoints ```typescript import { createApiFetcher } from 'axios-multi-api'; const api = createApiFetcher({ apiUrl: 'https://example.com/api', + strategy: 'softFail', // no try/catch required endpoints: { getUser: { + method: 'get', url: '/user-details', }, }, }); // Make API GET request to: http://example.com/api/user-details?userId=1&ratings[]=1&ratings[]=2 -const { data } = await api.getUser({ userId: 1, ratings: [1, 2] }); +const { data, error } = await api.getUser({ userId: 1, ratings: [1, 2] }); ``` -Standalone usage: (without endpoints): +Note: -```typescript -import { fetchf } from 'axios-multi-api'; +> The native `fetch()` is used by default. If you want to use Axios, install it separately (e.g. `npm install axios`), and then pass the import to the `createApiFetcher()` function. Check examples for more details. -const { data } = await fetchf('/api/user-details'); -``` +## ✔️ Easy Integration with React and Other Libraries + +`axios-multi-api` is designed to seamlessly integrate with popular libraries like [React Query](https://react-query-v3.tanstack.com/guides/queries) and [SWR](https://swr.vercel.app/). Whether you're using React Query or SWR, you can effortlessly manage API requests with minimal setup. -## ✔️ Easy to use with React and other libraries +### 📦 Using with React Query -You could use [React Query](https://react-query-v3.tanstack.com/guides/queries) hooks with API handler: +Integrate `axios-multi-api` with React Query to streamline your data fetching: ```typescript import { createApiFetcher } from 'axios-multi-api'; @@ -99,77 +116,275 @@ export const useProfile = ({ id }) => { }; ``` -## ✔️ API +### 🌊 Using with SWR -### api.myEndpoint(queryParams, urlPathParams, requestConfig) +Combine `axios-multi-api` with SWR for efficient data fetching and caching: -Where "myEndpoint" is the name of your endpoint from `endpoints` object passed to the `createApiFetcher()`. - -`queryParams` / `payload` (optional) - Query Parameters or Body Payload for POST requests. - -First argument of API functions is an object with query params for `GET` requests, or with a data payload for `POST` alike requests. Other request types are supported as well. For `POST` alike requests you may occasionally want to use both query params and payload. In such case, use this argument as query params and pass the payload as 3rd argument `requestConfig.body` or `requestConfig.data` (for Axios) - -Query params accepts strings, numbers, and even arrays, so you pass { foo: [1, 2] } and it will become: foo[]=1&foo[]=2 automatically. +```typescript +import { createApiFetcher } from 'axios-multi-api'; +import useSWR from 'swr'; -`urlPathParams` (optional) - Dynamic URL Path Parameters +const api = createApiFetcher({ + apiUrl: 'https://example.com/api', + endpoints: { + getProfile: { + url: '/profile/:id', + }, + }, +}); -It gives possibility to modify URLs structure in a declarative way. In our example `/user-details/update/:userId` will become `/user-details/update/1` when API request will be made. +export const useProfile = ({ id }) => { + const fetcher = () => api.getProfile({ id }); -`requestConfig` (optional) - Request Configuration to overwrite global config in case + const { data, error } = useSWR(['profile', id], fetcher); -To have more granular control over specific endpoints you can pass Axios compatible [Request Config](https://github.com/axios/axios#request-config) for particular endpoint. You can also use Global Settings like `cancellable` or `strategy` mentioned below. + return { + profile: data, + isLoading: !error && !data, + isError: error, + }; +}; +``` -### api.getInstance() +Check examples below for more integrations with other libraries. -When API handler is firstly initialized, a new Axios instance is created. You can call `api.getInstance()` if you want to get that instance directly, for example to add some interceptors. +## ✔️ API -### api.config +### `fetchf()` -You can access `api.config` directly, so to modify global headers, and other settings on fly. Please mind it is a property, not a function. +`fetchf()` is a functional wrapper for `fetch()`. It integrates seamlessly with the retry mechanism and error handling improvements. Unlike the traditional class-based approach, `fetchf()` can be used directly as a function, simplifying the usage and making it easier to integrate with functional programming styles. -### api.endpoints +```typescript +import { fetchf } from 'axios-multi-api'; -You can access `api.endpoints` directly, so to modify endpoints list. It can be useful if you want to append or remove global endpoints. Please mind it is a property, not a function. +const { data, error } = await fetchf('/api/user-details', { + timeout: 5000, + cancellable: true, + retry: { retries: 3, delay: 2000 }, + // All other fetch() settings work as well... +}); +``` -## ✔️ fetchf() - improved native fetch() wrapper +> The fetchf() makes requests independently from createApiFetcher() -The `axios-multi-api` wraps the endpoints around and automatically uses `fetchf()` under the hood. However, you can use `fetchf()` directly just like you use `fetch()`. +**Challenges with Native Fetch:** -### Improvements to native fetch +- **Error Status Handling:** Fetch does not throw errors for HTTP error statuses, making it difficult to distinguish between successful and failed requests based on status codes alone. +- **Error Visibility:** Error responses with status codes like 404 or 500 are not automatically propagated as exceptions, which can lead to inconsistent error handling. To address these challenges, the `fetchf()` provides several enhancements: 1. **Consistent Error Handling:** - - The `createApiFetcher()` and `fetchf()` both ensure that HTTP error statuses (e.g., 404, 500) are treated as errors. This is achieved by wrapping `fetch()` in a way that checks the response status and throws an exception if the `ok` property is `false`. + - In JavaScript, the native `fetch()` function does not reject the Promise for HTTP error statuses such as 404 (Not Found) or 500 (Internal Server Error). Instead, `fetch()` resolves the Promise with a `Response` object, where the `ok` property indicates the success of the request. If the request encounters a network error or fails due to other issues (e.g., server downtime), `fetch()` will reject the Promise. - This approach aligns error handling with common practices and makes it easier to manage errors consistently. 2. **Enhanced Retry Mechanism:** - **Retry Configuration:** You can configure the number of retries, delay between retries, and exponential backoff for failed requests. This helps to handle transient errors effectively. - **Custom Retry Logic:** The `shouldRetry` asynchronous function allows for custom retry logic based on the error and attempt count, providing flexibility to handle different types of failures. + - **Retry Conditions:** Errors are only retried based on configurable retry conditions, such as specific HTTP status codes or error types. 3. **Improved Error Visibility:** - **Error Wrapping:** The `createApiFetcher()` and `fetchf()` wrap errors in a custom `RequestError` class, which provides detailed information about the request and response, similarly to what Axios does. This makes debugging easier and improves visibility into what went wrong. - - **Retry Conditions:** Errors are only retried based on configurable retry conditions, such as specific HTTP status codes or error types. -4. **Functional `fetchf()` Wrapper:** - - **Wrapper Function:** `fetchf()` is a functional wrapper for `fetch()`. It integrates seamlessly with the retry mechanism and error handling improvements. - - **No Class Dependency:** Unlike the traditional class-based approach, `fetchf()` can be used directly as a function, simplifying the usage and making it easier to integrate with functional programming styles. +4. **Extended settings:** + - Check Settings table for more information about all settings. -### Improved Fetch Error Handling +### `createApiFetcher()` -In JavaScript, the native `fetch()` function does not reject the Promise for HTTP error statuses such as 404 (Not Found) or 500 (Internal Server Error). Instead, `fetch()` resolves the Promise with a `Response` object, where the `ok` property indicates the success of the request. If the request encounters a network error or fails due to other issues (e.g., server downtime), `fetch()` will reject the Promise. +`createApiFetcher()` is a powerful factory function for creating API fetchers with advanced features. It provides a convenient way to configure and manage multiple API endpoints using a declarative approach. This function offers integration with retry mechanisms, error handling improvements, and other advanced configurations. Unlike traditional methods, `createApiFetcher()` allows you to set up and use API endpoints efficiently with minimal boilerplate code. -**Challenges with Native Fetch:** +#### Usage Example -- **Error Status Handling:** Fetch does not throw errors for HTTP error statuses, making it difficult to distinguish between successful and failed requests based on status codes alone. -- **Error Visibility:** Error responses with status codes like 404 or 500 are not automatically propagated as exceptions, which can lead to inconsistent error handling. +```typescript +import { createApiFetcher } from 'axios-multi-api'; + +const api = createApiFetcher({ + apiUrl: 'https://example.com/api', + endpoints: { + getUserDetails: { + url: '/user-details', + method: 'GET', + retry: { retries: 3, delay: 2000 }, + timeout: 5000, + cancellable: true, + strategy: 'softFail', + }, + updateUser: { + url: '/update-user', + method: 'POST', + retry: { retries: 2, delay: 1000 }, + }, + // Define more endpoints as needed + }, +}); + +// Example usage +const { data, error } = await api.getUserDetails(); +``` + +The `const api` methods and properties are described below: + +#### `api.myEndpoint(queryParams, urlPathParams, requestConfig)` + +Where "myEndpoint" is the name of your endpoint from `endpoints` object passed to the `createApiFetcher()`. + +1. **`queryParams`** / **`payload`** (optional) - Query Parameters or Body Payload for POST requests. + First argument of API functions is an object with query params for `GET` requests, or with a data payload for `POST` alike requests. Other request types are supported as well. For `POST` alike requests you may occasionally want to use both query params and payload. In such case, use this argument as query params and pass the payload as 3rd argument `requestConfig.body` or `requestConfig.data` (for Axios) + Query params accepts strings, numbers, and even arrays, so you pass { foo: [1, 2] } and it will become: foo[]=1&foo[]=2 automatically. + +2. **`urlPathParams`** (optional) - Dynamic URL Path Parameters + It gives possibility to modify URLs structure in a declarative way. In our example `/user-details/update/:userId` will become `/user-details/update/1` when API request will be made. + +3. **`requestConfig`** (optional) - Request Configuration to overwrite global config in case + To have more granular control over specific endpoints you can pass Axios compatible [Request Config](https://github.com/axios/axios#request-config) for particular endpoint. You can also use Global Settings like `cancellable` or `strategy` and others mentioned below. + +Returns: **`response`** or **`data`** object, depending on `flattenResponse` setting: + +##### Response Object without `flattenResponse` (default) + +When `flattenResponse` is disabled, the response object includes a more detailed structure, encapsulating various aspects of the response: + +- **`data`**: + + - Contains the actual data returned from the API request. + +- **`error`**: + + - An object with details about any error that occurred or `null` otherwise. + - **`name`**: The name of the error (e.g., 'ResponseError'). + - **`message`**: A descriptive message about the error. + - **`status`**: The HTTP status code of the response (e.g., 404, 500). + - **`statusText`**: The HTTP status text of the response (e.g., 'Not Found', 'Internal Server Error'). + - **`request`**: Details about the HTTP request that was sent (e.g., URL, method, headers). + - **`config`**: The configuration object used for the request, including URL, method, headers, and query parameters. + - **`response`**: The full response object received from the server, including all headers and body. + +- **`config`**: + + - The configuration object with all settings used for the request, including URL, method, headers, and query parameters. + +- **`request`**: + + - An alias for `config`. + +- **`headers`**: + - The response headers returned by the server, such as content type and caching information returned as simple key-value object. + +##### Response Object with `flattenResponse` + +When the `flattenResponse` option is enabled, the `data` from the API response is directly exposed as the top-level property of the response object. This simplifies access to the actual data, as it is not nested within additional response metadata. + +##### Key Points + +- **With `flattenResponse` Enabled**: + + - **`data`**: Directly contains the API response data. + +- **With `flattenResponse` Disabled**: + - **`data`**: Contains the API response data nested within a broader response structure. + - **`error`**: Provides detailed information about any errors encountered. + - **`config`**: Shows the request configuration. + - **`request`**: Details the actual HTTP request sent. + - **`headers`**: Includes the response headers from the server. + +The `flattenResponse` option provides a more streamlined response object by placing the data directly at the top level, while disabling it gives a more comprehensive response structure with additional metadata. + +#### `api.config` + +You can access `api.config` property directly, so to modify global headers, and other settings on fly. Please mind it is a property, not a function. + +#### `api.endpoints` + +You can access `api.endpoints` property directly, so to modify endpoints list. It can be useful if you want to append or remove global endpoints. Please mind it is a property, not a function. + +#### `api.getInstance()` + +When API handler is firstly initialized, a new custom `fetcher` instance is created. You can call `api.getInstance()` if you want to get that instance directly, for example to add some interceptors. The instance of `fetcher` is created using `fetcher.create()` functions. Your fetcher can include anything. It will be triggered instead of native fetch() that is available by default. + +#### `api.request()` + +The `api.request()` helper function is a versatile method provided for making API requests with customizable configurations. It allows you to perform HTTP requests to any endpoint defined in your API setup and provides a straightforward way to handle queries, path parameters, and request configurations dynamically. + +##### Example + +```typescript +import { createApiFetcher } from 'axios-multi-api'; + +const api = createApiFetcher({ + apiUrl: 'https://example.com/api', + endpoints: { + getUserDetails: { + url: '/user-details/:id', + method: 'GET', + }, + updateUser: { + url: '/update-user', + method: 'POST', + }, + // Define more endpoints as needed + }, +}); + +// Using api.request to make a GET request +const { data, error } = await api.request( + 'getUserDetails', + null, // no Query Params passed + { + id: '123', // URL Path Param :id + }, +); + +// Using api.request to make a POST request +const { data, error } = await api.request('updateUser', { + name: 'John Doe', // Data Payload +}); + +// Using api.request to make a GET request to an external API +const { data, error } = await api.request('https://example.com/api/user', { + name: 'John Smith', // Query Params +}); +``` + +## ✔️ Settings (Request Config) + +Global settings are passed to `createApiFetcher()` function. Settings that are global only are market with star `*` next to setting name. + +Almost all settings can be passed on per-request basis in the third argument of endpoint function, for example `api.getUser({}, {}, { /* Request Config */ })`. + +You can also pass all `fetch()` settings, or if you use Axios, you can pass all [Axios Request Config](https://github.com/axios/axios#request-config) settings. + +| Setting | Type | Default | Description | +| ----------------- | ------------------ | ---------------------------------------- | ----------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------- | +| apiUrl \* | string | | Your API base url. | +| endpoints \* | object | | List of your endpoints. Each endpoint accepts all these settings. They can be set globally or per-endpoint when they are called. | +| fetcher \* | AxiosStatic | fetch | The native `fetch()` is used by default. Axios instance imported from axios package can be used otherwise. Leave as is, if you do not intend to use Axios. | +| strategy | string | reject | Error handling strategies - basically what to return when an error occurs. It can be a default data, promise can be hanged (nothing would be returned) or rejected so to use try/catch.

Available: `reject`, `softFail`, `defaultResponse`, `silent`.

`reject` - Promises are rejected, and global error handling is triggered. Requires try/catch for handling.

`softFail` - returns a response object with additional properties such as `data`, `error`, `config`, `request`, and `headers` when an error occurs. This approach avoids throwing errors, allowing you to handle error information directly within the response object without the need for try/catch blocks.

`defaultResponse` - returns default response specified in case of an error. Promise will not be rejected. It could be used in conjuction with `flattenResponse` and as `defaultResponse: {}` so to provide a sensible defaults.

`silent` - hangs the promise silently on error, useful for fire-and-forget requests without the need for try/catch. In case of an error, the promise will never be resolved or rejected, and any code after will never be executed. The requests could be dispatched within an asynchronous wrapper functions that do not need to be awaited. If used properly, it prevents excessive usage of try/catch or additional response data checks everywhere. You can use it in combination with `onError` to handle errors separately. | +| cancellable | boolean | false | If `true`, any previous requests to same API endpoint will be cancelled, if a subsequent request is made meanwhile. This helps you avoid unnecessary requests to the backend. | +| rejectCancelled | boolean | false | If `true` and request is set to `cancellable`, a cancelled requests' promise will be rejected. By default, instead of rejecting the promise, `defaultResponse` is returned. | +| flattenResponse | boolean | false | Flatten nested response data, so you can avoid writing `response.data.data` and obtain response directly. Response is flattened when there is a "data" within response "data", and no other object properties set. | +| defaultResponse | any | null | Default response when there is no data or when endpoint fails depending on the chosen `strategy` | +| timeout | int | 30000 | You can set a request timeout for all requests or particular in milliseconds. | +| onRequest | function(config) | | You can specify a function that will be triggered before the request is sent. The request configuration object will be sent as the first argument of the function. This is useful for modifying request parameters, headers, etc. | +| onResponse | function(response) | | You can specify a function that will be triggered when the endpoint successfully responds. The full Response Object is sent as the first argument of the function. This is useful for handling the response data, parsing, and error handling based on status codes. | +| onError | function(error) | | You can specify a function or class that will be triggered when endpoint fails. If it's a class it should expose a `process` method. When using native fetch(), the full Response Object is sent as a first argument of the function. In case of Axios, AxiosError object is sent. | +| logger | object | | You can additionally specify logger object with your custom logger to automatically log the errors to the console. It should contain at least `error` and `warn` functions. `console.log` is used by default. | +| method | string | get | Default request method e.g. GET, POST, DELETE, PUT etc. | +| url | string | | URL path e.g. /user-details/get | +| urlPathParams | object | {} | An object representing URL path parameters. These parameters are used to dynamically replace placeholders in the URL path. For example, if your URL contains a placeholder like `/users/:userId`, you can provide an object with the `userId` key to replace that placeholder with an actual value. The keys in the `urlPathParams` object should match the placeholders in the URL. This allows for dynamic URL construction based on runtime values. | +| retry | object | | The object with retry settings available below. | +| retry.retries | number | 0 | The number of times to retry the request in case of failure. If set to `0` (default), no retries will be attempted. | +| retry.delay | number | 1000 | The initial delay (in milliseconds) between retry attempts. | +| retry.backoff | number | 1.5 | The backoff factor to apply to the delay between retries. For example, if the delay is 100ms and the backoff is 1.5, the next delay will be 150ms, then 225ms, and so on. | +| retry.maxDelay | number | 30000 | The maximum delay (in milliseconds) between retry attempts. | +| retry.retryOn | array | [408, 409, 425, 429, 500, 502, 503, 504] | An array of HTTP status codes on which to retry the request. Default values include: 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). | +| retry.shouldRetry | async function | | A custom asynchronous function to determine whether to retry the request. It receives two arguments: `error` (the error object) and `attempts` (the number of attempts made so far). | ## ✔️ Retry Mechanism -The exposed `fetchf()` and `createApiFetcher()` function include a built-in retry mechanism to handle transient errors and improve the reliability of network requests. This mechanism automatically retries requests when certain conditions are met, providing robustness in the face of temporary failures. Below is an overview of how the retry mechanism works and how it can be configured. +The exposed `fetchf()` and `createApiFetcher()` functions include a built-in retry mechanism to handle transient errors and improve the reliability of network requests. This mechanism automatically retries requests when certain conditions are met, providing robustness in the face of temporary failures. Below is an overview of how the retry mechanism works and how it can be configured. ### Configuration @@ -211,39 +426,35 @@ The retry mechanism is configured via the `retry` option when instantiating the Check Examples section below for more information. -## ✔️ Settings (Request Config) - -Global settings are passed to `createApiFetcher()` function. Settings that are global only are market with star `*` next to setting name. - -Almost all settings can be passed on per-request basis in the third argument of endpoint function, for example `api.getUser({}, {}, { /* Request Config */ })`. - -You can also pass all `fetch()` settings, or if you use Axios, you can pass all [Axios Request Config](https://github.com/axios/axios#request-config) settings. - -| Setting | Type | Default | Description | -| ----------------- | ------------------ | ---------------------------------------- | ------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------- | -| apiUrl \* | string | | Your API base url. | -| endpoints \* | object | | List of your endpoints. Each endpoint accepts all these settings. They can be set globally or per-endpoint when they are called. | -| fetcher \* | AxiosStatic | fetch | The native `fetch()` is used by default. Axios instance imported from axios package can be used otherwise. Leave as is, if you do not intend to use Axios. | -| strategy | string | reject | Error handling strategies - basically what to return when an error occurs. It can be a default data, promise can be hanged (nothing would be returned) or rejected so to use try/catch.

Available: `silent`, `reject`, `defaultResponse`.

`reject` - standard way - simply rejects the promise. Global error handling is triggered right before the rejection. You need to set try/catch to catch errors.

`defaultResponse` in case of an error, it returns default response specified in global `defaultResponse` or per endpoint `defaultResponse` setting. Promise will not be rejected! Data from default response will be returned instead. It could be used together with object destructuring by setting `defaultResponse: {}` so to provide a responsible defaults.

`silent` can be used for requests that are dispatched within asynchronous wrapper functions that are not awaited. If a request fails, promise will silently hang and no action will be performed. In case of an error, the promise will never be resolved or rejected, and any code after will never be executed. If used properly it saves developers from try/catch or additional response data checks everywhere. You can use is in combination with `onError` so to handle errors globally. | -| cancellable | boolean | false | If `true`, any previous requests to same API endpoint will be cancelled, if a subsequent request is made meanwhile. This helps you avoid unnecessary requests to the backend. | -| rejectCancelled | boolean | false | If `true` and request is set to `cancellable`, a cancelled requests' promise will be rejected. By default, instead of rejecting the promise, `defaultResponse` is returned. | -| flattenResponse | boolean | false | Flatten nested response data, so you can avoid writing `response.data.data` and obtain response directly. Response is flattened when there is a "data" within response "data", and no other object properties set. | -| defaultResponse | any | null | Default response when there is no data or when endpoint fails depending on the chosen `strategy` | -| timeout | int | 30000 | You can set a request timeout for all requests or particular in milliseconds. | -| onRequest | function(config) | | You can specify a function that will be triggered before the request is sent. The request configuration object will be sent as the first argument of the function. This is useful for modifying request parameters, headers, etc. | -| onResponse | function(response) | | You can specify a function that will be triggered when the endpoint successfully responds. The full Response Object is sent as the first argument of the function. This is useful for handling the response data, parsing, and error handling based on status codes. | -| onError | function(error) | | You can specify a function or class that will be triggered when endpoint fails. If it's a class it should expose a `process` method. When using native fetch(), the full Response Object is sent as a first argument of the function. In case of Axios, AxiosError object is sent. | -| logger | object | | You can additionally specify logger object with your custom logger to automatically log the errors to the console. It should contain at least `error` and `warn` functions. `console.log` is used by default. | -| method | string | get | Default request method e.g. GET, POST, DELETE, PUT etc. | -| url | string | | URL path e.g. /user-details/get | -| urlPathParams | object | {} | An object representing URL path parameters. These parameters are used to dynamically replace placeholders in the URL path. For example, if your URL contains a placeholder like `/users/:userId`, you can provide an object with the `userId` key to replace that placeholder with an actual value. The keys in the `urlPathParams` object should match the placeholders in the URL. This allows for dynamic URL construction based on runtime values. | -| retry | object | | The object with retry settings available below. | -| retry.retries | number | 0 | The number of times to retry the request in case of failure. If set to `0` (default), no retries will be attempted. | -| retry.delay | number | 1000 | The initial delay (in milliseconds) between retry attempts. | -| retry.backoff | number | 1.5 | The backoff factor to apply to the delay between retries. For example, if the delay is 100ms and the backoff is 1.5, the next delay will be 150ms, then 225ms, and so on. | -| retry.maxDelay | number | 30000 | The maximum delay (in milliseconds) between retry attempts. | -| retry.retryOn | array | [408, 409, 425, 429, 500, 502, 503, 504] | An array of HTTP status codes on which to retry the request. Default values include: 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). | -| retry.shouldRetry | async function | | A custom asynchronous function to determine whether to retry the request. It receives two arguments: `error` (the error object) and `attempts` (the number of attempts made so far). | +## Comparison with another libraries + +| Feature | axios-multi-api | ofetch() | Wretch() | Axios | SWR | React Query | Native fetch() | +| --------------------------------------- | --------------- | ------------ | ------------ | ------------ | ------------ | ------------ | -------------- | +| **Unified API Client** | ✅ | -- | -- | -- | -- | -- | -- | +| **Customizable Error Handling** | ✅ | -- | ✅ | ✅ | ✅ | ✅ | -- | +| **Retries with exponential backoff** | ✅ | -- | -- | -- | ✅ | ✅ | -- | +| **Easy Timeouts** | ✅ | ✅ | ✅ | ✅ | -- | -- | -- | +| **Easy Cancellation** | ✅ | -- | -- | -- | ✅ | ✅ | -- | +| **Default Responses** | ✅ | -- | -- | -- | -- | -- | -- | +| **Global Configuration** | ✅ | -- | ✅ | ✅ | ✅ | ✅ | -- | +| **TypeScript Support** | ✅ | ✅ | ✅ | ✅ | ✅ | ✅ | ✅ | +| **Interceptors** | ✅ | ✅ | ✅ | ✅ | -- | -- | -- | +| **Request and Response Transformation** | ✅ | ✅ | ✅ | ✅ | ✅ | ✅ | -- | +| **Integration with Libraries** | ✅ | ✅ | ✅ | ✅ | ✅ | ✅ | -- | +| **Request Queuing** | ✅ | -- | -- | -- | -- | -- | -- | +| **Multiple Fetching Strategies** | ✅ | -- | -- | -- | -- | -- | -- | +| **Dynamic URLs** | ✅ | -- | ✅ | -- | -- | -- | -- | +| **Automatic Retry on Failure** | ✅ | ✅ | -- | ✅ | ✅ | ✅ | -- | +| **Server-Side Rendering (SSR) Support** | ✅ | ✅ | -- | -- | ✅ | ✅ | -- | +| **Pagination Handling** | -- | -- | -- | -- | ✅ | ✅ | -- | +| **Caching** | -- | -- | -- | -- | ✅ | ✅ | -- | +| **Optimistic Updates** | -- | -- | -- | -- | ✅ | ✅ | -- | +| **Data Synchronization** | -- | -- | -- | -- | ✅ | ✅ | -- | +| **Local State Management** | -- | -- | -- | -- | ✅ | ✅ | -- | +| **Minimal Installation Size** | 🟢 (2.83 KB) | 🟡 (6.51 KB) | 🟢 (2.16 KB) | 🔴 (13.9 KB) | 🟡 (4.57 KB) | 🔴 (13.3 KB) | 🟢 (0 KB) | +| **Built-in AbortController Support** | ✅ | -- | -- | -- | ✅ | ✅ | -- | + +Please mind that this table is for informational purposes only. All of these solutions differ. For example `swr` and `react-query` are more focused on React, re-rendering, query caching and keeping data in sync, while fetch wrappers like `axios-multi-api` or `ofetch` aim to extend functionalities of native `fetch` so to reduce complexity of having to maintain various wrappers. ## ✔️ Full TypeScript support @@ -378,6 +589,35 @@ try { } ``` +### Multiple APIs from different API sources + +```typescript +import { createApiFetcher } from 'axios-multi-api'; + +const api = createApiFetcher({ + apiUrl: 'https://example.com/api/v1', + endpoints: { + sendMessage: { + method: 'post', + url: '/send-message/:postId', + }, + getMessage: { + url: '/get-message/', + // Change baseURL to external for this endpoint onyl + baseURL: 'https://externalprovider.com/api/v2', + }, + }, +}); + +async function sendAndGetMessage() { + await api.sendMessage({ message: 'Text' }, { postId: 1 }); + + const { data } = await api.getMessage({ postId: 1 }); +} + +sendAndGetMessage(); +``` + ### Retry Mechanism Here’s an example of configuring and using the `createApiFetcher()` with the retry mechanism: @@ -505,6 +745,38 @@ async function sendMessage() { sendMessage(); ``` +### Per-request Error handling - softFail strategy (recommended) + +```typescript +import { createApiFetcher } from 'axios-multi-api'; + +const api = createApiFetcher({ + apiUrl: 'https://example.com/api', + strategy: 'softFail', + endpoints: { + sendMessage: { + method: 'post', + url: '/send-message/:postId', + }, + }, +}); + +async function sendMessage() { + const { data, error } = await api.sendMessage( + { message: 'Text' }, + { postId: 1 }, + ); + + if (error) { + console.error('Request Error', error); + } else { + console.log('Message sent successfully'); + } +} + +sendMessage(); +``` + ### Per-request Error handling - defaultResponse strategy ```typescript @@ -647,6 +919,75 @@ const { data } = await fetchf('/api/user-details', { }); ``` +### Integration with Vue + +```typescript +// src/api.ts +import { createApiFetcher } from 'axios-multi-api'; + +const api = createApiFetcher({ + apiUrl: 'https://example.com/api', + strategy: 'softFail', + endpoints: { + getProfile: { url: '/profile/:id' }, + }, +}); + +export default api; +``` + +```typescript +// src/composables/useProfile.ts +import { ref, onMounted } from 'vue'; +import api from '../api'; + +export function useProfile(id: number) { + const profile = ref(null); + const isLoading = ref(true); + const isError = ref(null); + + const fetchProfile = async () => { + const { data, error } = await api.getProfile({ id }); + + if (error) isError.value = error; + else if (data) profile.value = data; + + isLoading.value = false; + }; + + onMounted(fetchProfile); + + return { profile, isLoading, isError }; +} +``` + +```html + + + + +``` + ## ✔️ Support and collaboration If you have any idea for an improvement, please file an issue. Feel free to make a PR if you are willing to collaborate on the project. Thank you :) diff --git a/dist/browser/index.global.js b/dist/browser/index.global.js index 58df972..9b1dd86 100644 --- a/dist/browser/index.global.js +++ b/dist/browser/index.global.js @@ -1,2 +1,2 @@ -(()=>{var g=class{logger;requestErrorService;constructor(e,t){this.logger=e,this.requestErrorService=t}process(e){var s;(s=this.logger)!=null&&s.warn&&this.logger.warn("API ERROR",e);let t=e;typeof e=="string"&&(t=new Error(e)),this.requestErrorService&&(typeof this.requestErrorService.process<"u"?this.requestErrorService.process(t):typeof this.requestErrorService=="function"&&this.requestErrorService(t))}};var q=class u extends Error{response;request;constructor(e,t,s){super(e),this.name="RequestError",this.message=e,this.request=t,this.response=s,Error.captureStackTrace(this,u)}};async function C(u,e){if(!e)return u;let t=Array.isArray(e)?e:[e],s={...u};for(let r of t)s=await r(s);return s}async function E(u,e){if(!e)return u;let t=Array.isArray(e)?e:[e],s=u;for(let r of t)s=await r(s);return s}var R=class{requestInstance;baseURL="";timeout=3e4;cancellable=!1;rejectCancelled=!1;strategy="reject";method="get";flattenResponse=!1;defaultResponse=null;fetcher;logger;onError;requestsQueue;retry={retries:0,delay:1e3,maxDelay:3e4,backoff:1.5,retryOn:[408,409,425,429,500,502,503,504],shouldRetry:async()=>!0};config;constructor({fetcher:e=null,timeout:t=null,rejectCancelled:s=!1,strategy:r=null,flattenResponse:o=null,defaultResponse:a={},logger:n=null,onError:i=null,...l}){this.fetcher=e,this.timeout=t??this.timeout,this.strategy=r??this.strategy,this.cancellable=l.cancellable||this.cancellable,this.rejectCancelled=s||this.rejectCancelled,this.flattenResponse=o??this.flattenResponse,this.defaultResponse=a,this.logger=n||(globalThis?globalThis.console:null)||null,this.onError=i,this.requestsQueue=new WeakMap,this.baseURL=l.baseURL||l.apiUrl||"",this.method=l.method||this.method,this.config=l,this.retry={...this.retry,...l.retry||{}},this.requestInstance=this.isCustomFetcher()?e.create({...l,baseURL:this.baseURL,timeout:this.timeout}):null}getInstance(){return this.requestInstance}replaceUrlPathParams(e,t){return t?e.replace(/:[a-zA-Z]+/gi,s=>{let r=s.substring(1);return String(t[r]?t[r]:s)}):e}appendQueryParams(e,t){if(!t)return e;let s=Object.entries(t).flatMap(([r,o])=>Array.isArray(o)?o.map(a=>`${encodeURIComponent(r)}[]=${encodeURIComponent(a)}`):`${encodeURIComponent(r)}=${encodeURIComponent(String(o))}`).join("&");return e.includes("?")?`${e}&${s}`:s?`${e}?${s}`:e}isJSONSerializable(e){if(e==null)return!1;let t=typeof e;if(t==="string"||t==="number"||t==="boolean")return!0;if(t!=="object")return!1;if(Array.isArray(e))return!0;if(Buffer.isBuffer(e)||e instanceof Date)return!1;let s=Object.getPrototypeOf(e);return s===Object.prototype||s===null||typeof e.toJSON=="function"}buildConfig(e,t,s){let r=s.method||this.method,o=r.toLowerCase(),a=o==="get"||o==="head",n=this.replaceUrlPathParams(e,s.urlPathParams||this.config.urlPathParams),i=s.body||s.data||this.config.body||this.config.data;if(this.isCustomFetcher())return{...s,url:n,method:o,...a?{params:t}:{},...!a&&t&&i?{params:t}:{},...!a&&t&&!i?{data:t}:{},...!a&&i?{data:i}:{}};let l=i||t;delete s.data;let d=!a&&t&&!s.body||!t?n:this.appendQueryParams(n,t),m=d.includes("://")?"":typeof s.baseURL<"u"?s.baseURL:this.baseURL;return{...s,url:m+d,method:r.toUpperCase(),headers:{Accept:"application/json, text/plain, */*","Content-Type":"application/json;charset=utf-8",...s.headers||this.config.headers||{}},...a?{}:{body:this.isJSONSerializable(l)?typeof l=="string"?l:JSON.stringify(l):l}}}processError(e,t){if(this.isRequestCancelled(e))return;t.onError&&typeof t.onError=="function"&&t.onError(e),new g(this.logger,this.onError).process(e)}async outputErrorResponse(e,t){let s=this.isRequestCancelled(e),r=t.strategy||this.strategy,o=typeof t.rejectCancelled<"u"?t.rejectCancelled:this.rejectCancelled,a=typeof t.defaultResponse<"u"?t.defaultResponse:this.defaultResponse;return s&&!o?a:r==="silent"?(await new Promise(()=>null),a):r==="reject"?Promise.reject(e):a}isRequestCancelled(e){return e.name==="AbortError"||e.name==="CanceledError"}isCustomFetcher(){return this.fetcher!==null}addCancellationToken(e){if(!this.cancellable&&!e.cancellable)return{};if(typeof e.cancellable<"u"&&!e.cancellable)return{};if(typeof AbortController>"u")return console.error("AbortController is unavailable."),{};let t=this.requestsQueue.get(e);t&&t.abort();let s=new AbortController;if(!this.isCustomFetcher()){let r=setTimeout(()=>{let o=new Error(`[TimeoutError]: The ${e.url} request was aborted due to timeout`);throw o.name="TimeoutError",o.code=23,s.abort(o),clearTimeout(r),o},e.timeout||this.timeout)}return this.requestsQueue.set(e,s),{signal:s.signal}}async request(e,t=null,s=null){var P,w,A;let r=null,o=s||{},a=this.buildConfig(e,t,o),n={...this.addCancellationToken(a),...a},{retries:i,delay:l,backoff:d,retryOn:h,shouldRetry:m,maxDelay:b}={...this.retry,...(n==null?void 0:n.retry)||{}},f=0,y=l;for(;f<=i;)try{if(n=await C(n,n.onRequest),n=await C(n,this.config.onRequest),this.isCustomFetcher())r=await this.requestInstance.request(n);else if(r=await globalThis.fetch(n.url,n),r.config=n,r.ok){let c=String(((P=r==null?void 0:r.headers)==null?void 0:P.get("Content-Type"))||""),p=null;if(!c)try{p=await r.json()}catch{}p||(c&&c.includes("application/json")?p=await r.json():typeof r.text<"u"?p=await r.text():typeof r.blob<"u"?p=await r.blob():p=r.body||r.data||null),r.data=p}else throw r.data=null,new q(`fetchf() Request Failed! Status: ${r.status||null}`,n,r);return r=await E(r,n.onResponse),r=await E(r,this.config.onResponse),this.processResponseData(r,n)}catch(c){if(f===i||!await m(c,f)||!(h!=null&&h.includes((r==null?void 0:r.status)||((w=c==null?void 0:c.response)==null?void 0:w.status)||(c==null?void 0:c.status))))return this.processError(c,n),this.outputErrorResponse(c,n);(A=this.logger)!=null&&A.warn&&this.logger.warn(`Attempt ${f+1} failed. Retrying in ${y}ms...`),await this.delay(y),y*=d,y=Math.min(y,b),f++}return this.processResponseData(r,n)}async delay(e){return new Promise(t=>setTimeout(()=>t(!0),e))}processResponseData(e,t){var o;let s=typeof t.defaultResponse<"u"?t.defaultResponse:this.defaultResponse;return e?(t.flattenResponse||this.flattenResponse)&&typeof e.data<"u"?typeof e.data=="object"&&typeof e.data.data<"u"&&Object.keys(e.data).length===1?e.data.data:e.data:typeof e=="object"&&e.constructor===Object&&Object.keys(e).length===0?s:this.isCustomFetcher()?e:{...e,headers:Array.from(((o=e==null?void 0:e.headers)==null?void 0:o.entries())||{}).reduce((a,[n,i])=>(a[n]=i,a),{}),config:t}:s}};function Q(u){let e=u.endpoints,t=new R(u);function s(){return t.getInstance()}function r(i){return console.error(`${i} endpoint must be added to 'endpoints'.`),Promise.resolve(null)}async function o(i,l={},d={},h={}){let b={...e[i]};return await t.request(b.url,l,{...b,...h,urlPathParams:d})}function a(i){return i in n?n[i]:e[i]?n.request.bind(null,i):r.bind(null,i)}let n={config:u,endpoints:e,requestHandler:t,getInstance:s,request:o};return new Proxy(n,{get:(i,l)=>a(l)})}async function D(u,e={}){return new R(e).request(u,e.body||e.data||e.params,e)}})(); +(()=>{async function C(u,e){if(!e)return u;let s=Array.isArray(e)?e:[e],t={...u};for(let n of s)t=await n(t);return t}async function P(u,e){if(!e)return u;let s=Array.isArray(e)?e:[e],t=u;for(let n of s)t=await n(t);return t}var b=class extends Error{response;request;config;status;statusText;constructor(e,s,t){super(e),this.name="ResponseError",this.message=e,this.status=t.status,this.statusText=t.statusText,this.request=s,this.config=s,this.response=t}};var R=class{requestInstance;baseURL="";timeout=3e4;cancellable=!1;rejectCancelled=!1;strategy="reject";method="get";flattenResponse=!1;defaultResponse=null;fetcher;logger;onError;requestsQueue;retry={retries:0,delay:1e3,maxDelay:3e4,backoff:1.5,retryOn:[408,409,425,429,500,502,503,504],shouldRetry:async()=>!0};config;constructor({fetcher:e=null,timeout:s=null,rejectCancelled:t=!1,strategy:n=null,flattenResponse:o=null,defaultResponse:i={},logger:r=null,onError:a=null,...l}){this.fetcher=e,this.timeout=s??this.timeout,this.strategy=n||this.strategy,this.cancellable=l.cancellable||this.cancellable,this.rejectCancelled=t||this.rejectCancelled,this.flattenResponse=o||this.flattenResponse,this.defaultResponse=i,this.logger=r||(globalThis?globalThis.console:null)||null,this.onError=a,this.requestsQueue=new WeakMap,this.baseURL=l.baseURL||l.apiUrl||"",this.method=l.method||this.method,this.config=l,this.retry={...this.retry,...l.retry||{}},this.requestInstance=this.isCustomFetcher()?e.create({...l,baseURL:this.baseURL,timeout:this.timeout}):null}getInstance(){return this.requestInstance}replaceUrlPathParams(e,s){return s?e.replace(/:[a-zA-Z]+/gi,t=>{let n=t.substring(1);return String(s[n]?s[n]:t)}):e}appendQueryParams(e,s){if(!s)return e;let t=Object.entries(s).flatMap(([n,o])=>Array.isArray(o)?o.map(i=>`${encodeURIComponent(n)}[]=${encodeURIComponent(i)}`):`${encodeURIComponent(n)}=${encodeURIComponent(String(o))}`).join("&");return e.includes("?")?`${e}&${t}`:t?`${e}?${t}`:e}isJSONSerializable(e){if(e==null)return!1;let s=typeof e;if(s==="string"||s==="number"||s==="boolean")return!0;if(s!=="object")return!1;if(Array.isArray(e))return!0;if(Buffer.isBuffer(e)||e instanceof Date)return!1;let t=Object.getPrototypeOf(e);return t===Object.prototype||t===null||typeof e.toJSON=="function"}buildConfig(e,s,t){let n=t.method||this.method,o=n.toLowerCase(),i=o==="get"||o==="head",r=this.replaceUrlPathParams(e,t.urlPathParams||this.config.urlPathParams),a=t.body||t.data||this.config.body||this.config.data;if(this.isCustomFetcher())return{...t,url:r,method:o,...i?{params:s}:{},...!i&&s&&a?{params:s}:{},...!i&&s&&!a?{data:s}:{},...!i&&a?{data:a}:{}};let l=a||s,y=t.withCredentials||this.config.withCredentials?"include":t.credentials;delete t.data,delete t.withCredentials;let p=!i&&s&&!t.body||!s?r:this.appendQueryParams(r,s),h=p.includes("://")?"":typeof t.baseURL<"u"?t.baseURL:this.baseURL;return{...t,credentials:y,url:h+p,method:n.toUpperCase(),headers:{Accept:"application/json, text/plain, */*","Content-Type":"application/json;charset=utf-8",...t.headers||this.config.headers||{}},...i?{}:{body:this.isJSONSerializable(l)?typeof l=="string"?l:JSON.stringify(l):l}}}processError(e,s){var t;this.isRequestCancelled(e)||((t=this.logger)!=null&&t.warn&&this.logger.warn("API ERROR",e),s.onError&&typeof s.onError=="function"&&s.onError(e),this.onError&&typeof this.onError=="function"&&this.onError(e))}async outputErrorResponse(e,s){let t=this.isRequestCancelled(e),n=s.strategy||this.strategy,o=typeof s.rejectCancelled<"u"?s.rejectCancelled:this.rejectCancelled,i=typeof s.defaultResponse<"u"?s.defaultResponse:this.defaultResponse;return n==="softFail"?this.outputResponse(e.response,s,e):t&&!o?i:n==="silent"?(await new Promise(()=>null),i):n==="reject"?Promise.reject(e):i}isRequestCancelled(e){return e.name==="AbortError"||e.name==="CanceledError"}isCustomFetcher(){return this.fetcher!==null}addCancellationToken(e){if(!this.cancellable&&!e.cancellable)return{};if(typeof e.cancellable<"u"&&!e.cancellable)return{};if(typeof AbortController>"u")return console.error("AbortController is unavailable."),{};let s=this.requestsQueue.get(e);s&&s.abort();let t=new AbortController;if(!this.isCustomFetcher()&&this.timeout>0){let n=setTimeout(()=>{let o=new Error(`[TimeoutError]: The ${e.url} request was aborted due to timeout`);throw o.name="TimeoutError",o.code=23,t.abort(o),clearTimeout(n),o},e.timeout||this.timeout)}return this.requestsQueue.set(e,t),{signal:t.signal}}async request(e,s=null,t=null){var q,w,F;let n=null,o=t||{},i=this.buildConfig(e,s,o),r={...this.addCancellationToken(i),...i},{retries:a,delay:l,backoff:y,retryOn:p,shouldRetry:g,maxDelay:h}={...this.retry,...(r==null?void 0:r.retry)||{}},f=0,m=l;for(;f<=a;)try{if(r=await C(r,r.onRequest),r=await C(r,this.config.onRequest),this.isCustomFetcher())n=await this.requestInstance.request(r);else{n=await globalThis.fetch(r.url,r);let c=String(((q=n==null?void 0:n.headers)==null?void 0:q.get("Content-Type"))||""),d;if(!c)try{d=await n.json()}catch{}if(typeof d>"u"&&(c&&c.includes("application/json")?d=await n.json():typeof n.text<"u"?d=await n.text():typeof n.blob<"u"?d=await n.blob():d=n.body||n.data||null),n.config=r,n.data=d,!n.ok)throw new b(`${r.url} failed! Status: ${n.status||null}`,r,n)}return n=await P(n,r.onResponse),n=await P(n,this.config.onResponse),this.outputResponse(n,r)}catch(c){if(f===a||!await g(c,f)||!(p!=null&&p.includes(((w=c==null?void 0:c.response)==null?void 0:w.status)||(c==null?void 0:c.status))))return this.processError(c,r),this.outputErrorResponse(c,r);(F=this.logger)!=null&&F.warn&&this.logger.warn(`Attempt ${f+1} failed. Retrying in ${m}ms...`),await this.delay(m),m*=y,m=Math.min(m,h),f++}return this.outputResponse(n,r)}async delay(e){return new Promise(s=>setTimeout(()=>s(!0),e))}processHeaders(e){if(!e.headers)return{};let s={};if(e.headers instanceof Headers)for(let[t,n]of e.headers.entries())s[t]=n;else s={...e.headers};return s}outputResponse(e,s,t=null){let n=typeof s.defaultResponse<"u"?s.defaultResponse:this.defaultResponse;return e?(s.flattenResponse||this.flattenResponse)&&typeof e.data<"u"?e.data!==null&&typeof e.data=="object"&&typeof e.data.data<"u"&&Object.keys(e.data).length===1?e.data.data:e.data:e!==null&&typeof e=="object"&&e.constructor===Object&&Object.keys(e).length===0?n:this.isCustomFetcher()?e:(t!==null&&(t==null||delete t.response,t==null||delete t.request,t==null||delete t.config),{...e,error:t,headers:this.processHeaders(e),config:s}):n}};function T(u){let e=u.endpoints,s=new R(u);function t(){return s.getInstance()}function n(a){return console.error(`${a} endpoint must be added to 'endpoints'.`),Promise.resolve(null)}async function o(a,l={},y={},p={}){let h={...e[a]};return await s.request(h.url,l,{...h,...p,urlPathParams:y})}function i(a){return a in r?r[a]:e[a]?r.request.bind(null,a):n.bind(null,a)}let r={config:u,endpoints:e,requestHandler:s,getInstance:t,request:o};return new Proxy(r,{get:(a,l)=>i(l)})}async function $(u,e={}){return new R(e).request(u,e.body||e.data||e.params,e)}})(); //# sourceMappingURL=index.global.js.map \ No newline at end of file diff --git a/dist/browser/index.global.js.map b/dist/browser/index.global.js.map index 0d2fc2e..a9a77a6 100644 --- a/dist/browser/index.global.js.map +++ b/dist/browser/index.global.js.map @@ -1 +1 @@ -{"version":3,"sources":["../src/request-error-handler.ts","../src/request-error.ts","../src/interceptor-manager.ts","../src/request-handler.ts","../src/api-handler.ts","../src/index.ts"],"sourcesContent":["/* eslint-disable @typescript-eslint/no-explicit-any */\nexport class RequestErrorHandler {\n /**\n * Logger Class\n *\n * @type {*}\n * @memberof RequestErrorHandler\n */\n protected logger: any;\n\n /**\n * Error Service Class\n *\n * @type {*}\n * @memberof RequestErrorHandler\n */\n public requestErrorService: any;\n\n public constructor(logger: any, requestErrorService: any) {\n this.logger = logger;\n this.requestErrorService = requestErrorService;\n }\n\n /**\n * Process and Error\n *\n * @param {*} error Error instance or message\n * @throws Request error context\n * @returns {void}\n */\n public process(error: string | Error): void {\n if (this.logger?.warn) {\n this.logger.warn('API ERROR', error);\n }\n\n let errorContext = error;\n\n if (typeof error === 'string') {\n errorContext = new Error(error);\n }\n\n if (this.requestErrorService) {\n if (typeof this.requestErrorService.process !== 'undefined') {\n this.requestErrorService.process(errorContext);\n } else if (typeof this.requestErrorService === 'function') {\n this.requestErrorService(errorContext);\n }\n }\n }\n}\n","import type { RequestConfig } from './types';\n\nexport class RequestError extends Error {\n response: Response;\n request: RequestConfig;\n\n constructor(message: string, requestInfo: RequestConfig, response: Response) {\n super(message);\n\n this.name = 'RequestError';\n this.message = message;\n this.request = requestInfo;\n this.response = response;\n\n // Clean stack trace\n Error.captureStackTrace(this, RequestError);\n }\n}\n","import type {\n BaseRequestHandlerConfig,\n FetchResponse,\n RequestResponse,\n} from './types';\nimport type {\n RequestInterceptor,\n ResponseInterceptor,\n} from './types/interceptor-manager';\n\n/**\n * Applies a series of request interceptors to the provided configuration.\n * @param {BaseRequestHandlerConfig} config - The initial request configuration.\n * @param {RequestInterceptor | RequestInterceptor[]} interceptors - The request interceptor function(s) to apply.\n * @returns {Promise} - The modified request configuration.\n */\nexport async function interceptRequest(\n config: BaseRequestHandlerConfig,\n interceptors: RequestInterceptor | RequestInterceptor[],\n): Promise {\n if (!interceptors) {\n return config;\n }\n\n const interceptorList = Array.isArray(interceptors)\n ? interceptors\n : [interceptors];\n\n let interceptedConfig = { ...config };\n\n for (const interceptor of interceptorList) {\n interceptedConfig = await interceptor(interceptedConfig);\n }\n\n return interceptedConfig;\n}\n\n/**\n * Applies a series of response interceptors to the provided response.\n * @param {FetchResponse} response - The initial response object.\n * @param {ResponseInterceptor | ResponseInterceptor[]} interceptors - The response interceptor function(s) to apply.\n * @returns {Promise>} - The modified response object.\n */\nexport async function interceptResponse(\n response: FetchResponse,\n interceptors: ResponseInterceptor | ResponseInterceptor[],\n): Promise> {\n if (!interceptors) {\n return response;\n }\n\n const interceptorList = Array.isArray(interceptors)\n ? interceptors\n : [interceptors];\n\n let interceptedResponse = response;\n\n for (const interceptor of interceptorList) {\n interceptedResponse = await interceptor(interceptedResponse);\n }\n\n return interceptedResponse;\n}\n","/* eslint-disable @typescript-eslint/no-explicit-any */\nimport { RequestErrorHandler } from './request-error-handler';\nimport type {\n ErrorHandlingStrategy,\n RequestHandlerConfig,\n RequestConfig,\n RequestError as RequestErrorResponse,\n FetcherInstance,\n Method,\n RequestConfigHeaders,\n RetryOptions,\n FetchResponse,\n ExtendedResponse,\n} from './types/request-handler';\nimport type {\n APIResponse,\n QueryParams,\n QueryParamsOrBody,\n UrlPathParams,\n} from './types/api-handler';\nimport { RequestError } from './request-error';\nimport { interceptRequest, interceptResponse } from './interceptor-manager';\n\n/**\n * Generic Request Handler\n * It creates an Request Fetcher instance and handles requests within that instance\n * It handles errors depending on a chosen error handling strategy\n */\nexport class RequestHandler {\n /**\n * @var requestInstance Provider's instance\n */\n public requestInstance: FetcherInstance;\n\n /**\n * @var baseURL Base API url\n */\n public baseURL: string = '';\n\n /**\n * @var timeout Request timeout\n */\n public timeout: number = 30000;\n\n /**\n * @var cancellable Response cancellation\n */\n public cancellable: boolean = false;\n\n /**\n * @var rejectCancelled Whether to reject cancelled requests or not\n */\n public rejectCancelled: boolean = false;\n\n /**\n * @var strategy Request timeout\n */\n public strategy: ErrorHandlingStrategy = 'reject';\n\n /**\n * @var method Request method\n */\n public method: Method | string = 'get';\n\n /**\n * @var flattenResponse Response flattening\n */\n public flattenResponse: boolean = false;\n\n /**\n * @var defaultResponse Response flattening\n */\n public defaultResponse: any = null;\n\n /**\n * @var fetcher Request Fetcher instance\n */\n protected fetcher: FetcherInstance;\n\n /**\n * @var logger Logger\n */\n protected logger: any;\n\n /**\n * @var requestErrorService HTTP error service\n */\n protected onError: any;\n\n /**\n * @var requestsQueue Queue of requests\n */\n protected requestsQueue: WeakMap;\n\n /**\n * Request Handler Config\n */\n protected retry: RetryOptions = {\n retries: 0,\n delay: 1000,\n maxDelay: 30000,\n backoff: 1.5,\n\n // https://developer.mozilla.org/en-US/docs/Web/HTTP/Status\n retryOn: [\n 408, // Request Timeout\n 409, // Conflict\n 425, // Too Early\n 429, // Too Many Requests\n 500, // Internal Server Error\n 502, // Bad Gateway\n 503, // Service Unavailable\n 504, // Gateway Timeout\n ],\n\n shouldRetry: async () => true,\n };\n\n /**\n * Request Handler Config\n */\n public config: RequestHandlerConfig;\n\n /**\n * Creates an instance of RequestHandler.\n *\n * @param {Object} config - Configuration object for the request.\n * @param {string} config.baseURL - The base URL for the request.\n * @param {Object} config.endpoints - An object containing endpoint definitions.\n * @param {number} config.timeout - You can set the timeout for particular request in milliseconds.\n * @param {number} config.cancellable - If true, the previous requests will be automatically cancelled.\n * @param {number} config.rejectCancelled - If true and request is set to cancellable, a cancelled request promise will be rejected. By default, instead of rejecting the promise, defaultResponse is returned.\n * @param {number} config.timeout - Request timeout\n * @param {string} config.strategy - Error Handling Strategy\n * @param {string} config.flattenResponse - Whether to flatten response \"data\" object within \"data\" one\n * @param {*} config.defaultResponse - Default response when there is no data or when endpoint fails depending on the chosen strategy. It's \"null\" by default\n * @param {Object} [config.retry] - Options for retrying requests.\n * @param {number} [config.retry.retries=0] - Number of retry attempts. No retries by default.\n * @param {number} [config.retry.delay=1000] - Initial delay between retries in milliseconds.\n * @param {number} [config.retry.backoff=1.5] - Exponential backoff factor.\n * @param {number[]} [config.retry.retryOn=[502, 504, 408]] - HTTP status codes to retry on.\n * @param {RequestInterceptor|RequestInterceptor[]} [config.onRequest] - Optional request interceptor function or an array of functions.\n * These functions will be called with the request configuration object before the request is made. Can be used to modify or log the request configuration.\n * @param {ResponseInterceptor|ResponseInterceptor[]} [config.onResponse] - Optional response interceptor function or an array of functions.\n * These functions will be called with the response object after the response is received. an be used to modify or log the response data.\n * @param {Function} [config.onError] - Optional callback function for handling errors.\n * @param {Object} [config.headers] - Optional default headers to include in every request.\n * @param {Object} config.fetcher - The Axios (or any other) instance to use for making requests.\n * @param {*} config.logger - Instance of custom logger. Either class or an object similar to \"console\". Console is used by default.\n */\n public constructor({\n fetcher = null,\n timeout = null,\n rejectCancelled = false,\n strategy = null,\n flattenResponse = null,\n defaultResponse = {},\n logger = null,\n onError = null,\n ...config\n }: RequestHandlerConfig) {\n this.fetcher = fetcher;\n this.timeout =\n timeout !== null && timeout !== undefined ? timeout : this.timeout;\n this.strategy =\n strategy !== null && strategy !== undefined ? strategy : this.strategy;\n this.cancellable = config.cancellable || this.cancellable;\n this.rejectCancelled = rejectCancelled || this.rejectCancelled;\n this.flattenResponse =\n flattenResponse !== null && flattenResponse !== undefined\n ? flattenResponse\n : this.flattenResponse;\n this.defaultResponse = defaultResponse;\n this.logger = logger || (globalThis ? globalThis.console : null) || null;\n this.onError = onError;\n this.requestsQueue = new WeakMap();\n this.baseURL = config.baseURL || config.apiUrl || '';\n this.method = config.method || this.method;\n this.config = config;\n this.retry = {\n ...this.retry,\n ...(config.retry || {}),\n };\n\n this.requestInstance = this.isCustomFetcher()\n ? (fetcher as any).create({\n ...config,\n baseURL: this.baseURL,\n timeout: this.timeout,\n })\n : null;\n }\n\n /**\n * Get Provider Instance\n *\n * @returns {FetcherInstance} Provider's instance\n */\n public getInstance(): FetcherInstance {\n return this.requestInstance;\n }\n\n /**\n * Replaces dynamic URI parameters in a URL string with values from the provided `urlPathParams` object.\n * Parameters in the URL are denoted by `:`, where `` is a key in `urlPathParams`.\n *\n * @param {string} url - The URL string containing placeholders in the format `:`.\n * @param {Object} urlPathParams - An object containing the parameter values to replace placeholders.\n * @param {string} urlPathParams.paramName - The value to replace the placeholder `:` in the URL.\n * @returns {string} - The URL string with placeholders replaced by corresponding values from `urlPathParams`.\n */\n public replaceUrlPathParams(\n url: string,\n urlPathParams: UrlPathParams,\n ): string {\n if (!urlPathParams) {\n return url;\n }\n\n return url.replace(/:[a-zA-Z]+/gi, (str): string => {\n const word = str.substring(1);\n\n return String(urlPathParams[word] ? urlPathParams[word] : str);\n });\n }\n\n /**\n * Appends query parameters to the given URL\n *\n * @param {string} url - The base URL to which query parameters will be appended.\n * @param {QueryParams} params - An instance of URLSearchParams containing the query parameters to append.\n * @returns {string} - The URL with the appended query parameters.\n */\n public appendQueryParams(url: string, params: QueryParams): string {\n if (!params) {\n return url;\n }\n\n // We don't use URLSearchParams here as we want to ensure that arrays are properly converted similarily to Axios\n // So { foo: [1, 2] } would become: foo[]=1&foo[]=2\n const queryString = Object.entries(params)\n .flatMap(([key, value]) => {\n if (Array.isArray(value)) {\n return value.map(\n (val) => `${encodeURIComponent(key)}[]=${encodeURIComponent(val)}`,\n );\n }\n return `${encodeURIComponent(key)}=${encodeURIComponent(String(value))}`;\n })\n .join('&');\n\n return url.includes('?')\n ? `${url}&${queryString}`\n : queryString\n ? `${url}?${queryString}`\n : url;\n }\n\n /**\n * Checks if a value is JSON serializable.\n *\n * JSON serializable values include:\n * - Primitive types: string, number, boolean, null\n * - Arrays\n * - Plain objects (i.e., objects without special methods)\n * - Values with a `toJSON` method\n *\n * @param {any} value - The value to check for JSON serializability.\n * @returns {boolean} - Returns `true` if the value is JSON serializable, otherwise `false`.\n */\n protected isJSONSerializable(value: any): boolean {\n if (value === undefined || value === null) {\n return false;\n }\n\n const t = typeof value;\n if (t === 'string' || t === 'number' || t === 'boolean') {\n return true;\n }\n\n if (t !== 'object') {\n return false; // bigint, function, symbol, undefined\n }\n\n if (Array.isArray(value)) {\n return true;\n }\n\n if (Buffer.isBuffer(value)) {\n return false;\n }\n\n if (value instanceof Date) {\n return false;\n }\n\n const proto = Object.getPrototypeOf(value);\n\n // Check if the prototype is `Object.prototype` or `null` (plain object)\n if (proto === Object.prototype || proto === null) {\n return true;\n }\n\n // Check if the object has a toJSON method\n if (typeof value.toJSON === 'function') {\n return true;\n }\n\n return false;\n }\n\n /**\n * Build request configuration\n *\n * @param {string} url Request url\n * @param {QueryParamsOrBody} data Request data\n * @param {RequestConfig} config Request config\n * @returns {RequestConfig} Provider's instance\n */\n protected buildConfig(\n url: string,\n data: QueryParamsOrBody,\n config: RequestConfig,\n ): RequestConfig {\n const method = config.method || this.method;\n const methodLowerCase = method.toLowerCase();\n const isGetAlikeMethod =\n methodLowerCase === 'get' || methodLowerCase === 'head';\n\n const dynamicUrl = this.replaceUrlPathParams(\n url,\n config.urlPathParams || this.config.urlPathParams,\n );\n\n // Bonus: Specifying it here brings support for \"body\" in Axios\n const configData =\n config.body || config.data || this.config.body || this.config.data;\n\n // Axios compatibility\n if (this.isCustomFetcher()) {\n return {\n ...config,\n url: dynamicUrl,\n method: methodLowerCase,\n\n ...(isGetAlikeMethod ? { params: data } : {}),\n\n // For POST requests body payload is the first param for convenience (\"data\")\n // In edge cases we want to split so to treat it as query params, and use \"body\" coming from the config instead\n ...(!isGetAlikeMethod && data && configData ? { params: data } : {}),\n\n // Only applicable for request methods 'PUT', 'POST', 'DELETE', and 'PATCH'\n ...(!isGetAlikeMethod && data && !configData ? { data } : {}),\n ...(!isGetAlikeMethod && configData ? { data: configData } : {}),\n };\n }\n\n // Native fetch\n const payload = configData || data;\n\n delete config.data;\n\n const urlPath =\n (!isGetAlikeMethod && data && !config.body) || !data\n ? dynamicUrl\n : this.appendQueryParams(dynamicUrl, data);\n const isFullUrl = urlPath.includes('://');\n const baseURL = isFullUrl\n ? ''\n : typeof config.baseURL !== 'undefined'\n ? config.baseURL\n : this.baseURL;\n\n return {\n ...config,\n\n // Native fetch generally requires query params to be appended in the URL\n // Do not append query params only if it's a POST-alike request with only \"data\" specified as it's treated as body payload\n url: baseURL + urlPath,\n\n // Uppercase method name\n method: method.toUpperCase(),\n\n // For convenience, add the same default headers as Axios does\n headers: {\n Accept: 'application/json, text/plain, */*',\n 'Content-Type': 'application/json;charset=utf-8',\n ...(config.headers || this.config.headers || {}),\n } as RequestConfigHeaders,\n\n // Automatically JSON stringify request bodies, if possible and when not dealing with strings\n ...(!isGetAlikeMethod\n ? {\n body: this.isJSONSerializable(payload)\n ? typeof payload === 'string'\n ? payload\n : JSON.stringify(payload)\n : payload,\n }\n : {}),\n };\n }\n\n /**\n * Process global Request Error\n *\n * @param {RequestErrorResponse} error Error instance\n * @param {RequestConfig} requestConfig Per endpoint request config\n * @returns {void}\n */\n protected processError(\n error: RequestErrorResponse,\n requestConfig: RequestConfig,\n ): void {\n if (this.isRequestCancelled(error)) {\n return;\n }\n\n // Invoke per request \"onError\" call\n if (requestConfig.onError && typeof requestConfig.onError === 'function') {\n requestConfig.onError(error);\n }\n\n const errorHandler = new RequestErrorHandler(this.logger, this.onError);\n\n errorHandler.process(error);\n }\n\n /**\n * Output default response in case of an error, depending on chosen strategy\n *\n * @param {RequestErrorResponse} error Error instance\n * @param {RequestConfig} requestConfig Per endpoint request config\n * @returns {*} Error response\n */\n protected async outputErrorResponse(\n error: RequestErrorResponse,\n requestConfig: RequestConfig,\n ): Promise {\n const isRequestCancelled = this.isRequestCancelled(error);\n const errorHandlingStrategy = requestConfig.strategy || this.strategy;\n const rejectCancelled =\n typeof requestConfig.rejectCancelled !== 'undefined'\n ? requestConfig.rejectCancelled\n : this.rejectCancelled;\n const defaultResponse =\n typeof requestConfig.defaultResponse !== 'undefined'\n ? requestConfig.defaultResponse\n : this.defaultResponse;\n\n // By default cancelled requests aren't rejected\n if (isRequestCancelled && !rejectCancelled) {\n return defaultResponse;\n }\n\n if (errorHandlingStrategy === 'silent') {\n // Hang the promise\n await new Promise(() => null);\n\n return defaultResponse;\n }\n\n // Simply rejects a request promise\n if (errorHandlingStrategy === 'reject') {\n return Promise.reject(error);\n }\n\n return defaultResponse;\n }\n\n /**\n * Output error response depending on chosen strategy\n *\n * @param {RequestErrorResponse} error Error instance\n * @returns {boolean} True if request is aborted\n */\n public isRequestCancelled(error: RequestErrorResponse): boolean {\n return error.name === 'AbortError' || error.name === 'CanceledError';\n }\n\n /**\n * Detects if a custom fetcher is utilized\n *\n * @returns {boolean} True if it's a custom fetcher\n */\n protected isCustomFetcher(): boolean {\n return this.fetcher !== null;\n }\n\n /**\n * Automatically Cancel Previous Requests using AbortController when \"cancellable\" is defined\n *\n * @param {RequestConfig} requestConfig Per endpoint request config\n * @returns {Object} Controller Signal to abort\n */\n protected addCancellationToken(\n requestConfig: RequestConfig,\n ): Partial> {\n // Both disabled\n if (!this.cancellable && !requestConfig.cancellable) {\n return {};\n }\n\n // Explicitly disabled per request\n if (\n typeof requestConfig.cancellable !== 'undefined' &&\n !requestConfig.cancellable\n ) {\n return {};\n }\n\n // Check if AbortController is available\n if (typeof AbortController === 'undefined') {\n console.error('AbortController is unavailable.');\n\n return {};\n }\n\n // Generate unique key as a cancellation token\n const previousRequest = this.requestsQueue.get(requestConfig);\n\n if (previousRequest) {\n previousRequest.abort();\n }\n\n const controller = new AbortController();\n\n // Introduce timeout for native fetch\n if (!this.isCustomFetcher()) {\n const abortTimeout = setTimeout(() => {\n const error = new Error(\n `[TimeoutError]: The ${requestConfig.url} request was aborted due to timeout`,\n );\n\n error.name = 'TimeoutError';\n (error as any).code = 23; // DOMException.TIMEOUT_ERR\n controller.abort(error);\n clearTimeout(abortTimeout);\n throw error;\n }, requestConfig.timeout || this.timeout);\n }\n\n this.requestsQueue.set(requestConfig, controller);\n\n return {\n signal: controller.signal,\n };\n }\n\n /**\n * Handle Request depending on used strategy\n *\n * @param {string} url - Request url\n * @param {QueryParamsOrBody} data - Request data\n * @param {RequestConfig} config - Request config\n * @param {RequestConfig} payload.config Request config\n * @throws {RequestErrorResponse}\n * @returns {Promise>} Response Data\n */\n public async request(\n url: string,\n data: QueryParamsOrBody = null,\n config: RequestConfig = null,\n ): Promise> {\n let response: Response | FetchResponse = null;\n const _config = config || {};\n const _requestConfig = this.buildConfig(url, data, _config);\n\n let requestConfig: RequestConfig = {\n ...this.addCancellationToken(_requestConfig),\n ..._requestConfig,\n };\n\n const { retries, delay, backoff, retryOn, shouldRetry, maxDelay } = {\n ...this.retry,\n ...(requestConfig?.retry || {}),\n };\n\n let attempt = 0;\n let waitTime = delay;\n\n while (attempt <= retries) {\n try {\n // Local interceptors\n requestConfig = await interceptRequest(\n requestConfig,\n requestConfig.onRequest,\n );\n\n // Global interceptors\n requestConfig = await interceptRequest(\n requestConfig,\n this.config.onRequest,\n );\n\n // Axios compatibility\n if (this.isCustomFetcher()) {\n response = (await (this.requestInstance as any).request(\n requestConfig,\n )) as FetchResponse;\n } else {\n response = (await globalThis.fetch(\n requestConfig.url,\n requestConfig,\n )) as ExtendedResponse;\n\n // Add more information to response object\n response.config = requestConfig;\n\n // Check if the response status is not outside the range 200-299\n if (response.ok) {\n const contentType = String(\n response?.headers?.get('Content-Type') || '',\n );\n let data = null;\n\n // Handle edge case of no content type being provided... We assume json here.\n if (!contentType) {\n try {\n data = await response.json();\n // eslint-disable-next-line @typescript-eslint/no-unused-vars\n } catch (_error) {\n //\n }\n }\n\n if (!data) {\n if (contentType && contentType.includes('application/json')) {\n // Parse JSON response\n data = await response.json();\n } else if (typeof response.text !== 'undefined') {\n data = await response.text();\n } else if (typeof response.blob !== 'undefined') {\n data = await response.blob();\n } else {\n // Handle streams\n data = response.body || response.data || null;\n }\n }\n\n response.data = data;\n } else {\n response.data = null;\n\n // Output error in similar format to what Axios does\n throw new RequestError(\n `fetchf() Request Failed! Status: ${response.status || null}`,\n requestConfig,\n response,\n );\n }\n }\n\n // Local interceptors\n response = await interceptResponse(response, requestConfig.onResponse);\n\n // Global interceptors\n response = await interceptResponse(response, this.config.onResponse);\n\n return this.processResponseData(response, requestConfig);\n } catch (error) {\n if (\n attempt === retries ||\n !(await shouldRetry(error, attempt)) ||\n !retryOn?.includes(\n (response as FetchResponse)?.status ||\n error?.response?.status ||\n error?.status,\n )\n ) {\n this.processError(error, requestConfig);\n\n return this.outputErrorResponse(error, requestConfig);\n }\n\n if (this.logger?.warn) {\n this.logger.warn(\n `Attempt ${attempt + 1} failed. Retrying in ${waitTime}ms...`,\n );\n }\n\n await this.delay(waitTime);\n\n waitTime *= backoff;\n waitTime = Math.min(waitTime, maxDelay);\n attempt++;\n }\n }\n\n return this.processResponseData(response, requestConfig);\n }\n\n public async delay(ms: number): Promise {\n return new Promise((resolve) =>\n setTimeout(() => {\n return resolve(true);\n }, ms),\n );\n }\n\n /**\n * Process response\n *\n * @param response - Response payload\n * @param {RequestConfig} requestConfig - Request config\n * @returns {*} Response data\n */\n protected processResponseData(response, requestConfig: RequestConfig) {\n const defaultResponse =\n typeof requestConfig.defaultResponse !== 'undefined'\n ? requestConfig.defaultResponse\n : this.defaultResponse;\n\n if (!response) {\n return defaultResponse;\n }\n\n if (\n (requestConfig.flattenResponse || this.flattenResponse) &&\n typeof response.data !== 'undefined'\n ) {\n // Special case of only data property within response data object (happens in Axios)\n // This is in fact a proper response but we may want to flatten it\n // To ease developers' lives when obtaining the response\n if (\n typeof response.data === 'object' &&\n typeof response.data.data !== 'undefined' &&\n Object.keys(response.data).length === 1\n ) {\n return response.data.data;\n }\n\n return response.data;\n }\n\n // If empty object is returned, ensure that the default response is used instead\n if (\n typeof response === 'object' &&\n response.constructor === Object &&\n Object.keys(response).length === 0\n ) {\n return defaultResponse;\n }\n\n // For fetch()\n const isCustomFetcher = this.isCustomFetcher();\n\n if (!isCustomFetcher) {\n return {\n ...response,\n headers: Array.from(response?.headers?.entries() || {}).reduce(\n (acc, [key, value]) => {\n acc[key] = value;\n return acc;\n },\n {},\n ),\n config: requestConfig,\n };\n }\n\n return response;\n }\n}\n","import { RequestHandler } from './request-handler';\nimport type {\n FetcherInstance,\n RequestConfig,\n FetchResponse,\n} from './types/request-handler';\nimport type {\n ApiHandlerConfig,\n ApiHandlerMethods,\n ApiHandlerReturnType,\n APIResponse,\n QueryParams,\n UrlPathParams,\n} from './types/api-handler';\n\n/**\n * Creates an instance of API Handler.\n * It creates an API fetcher function using native fetch() or Axios if it is passed as \"fetcher\".\n *\n * @param {Object} config - Configuration object for the API fetcher.\n * @param {string} config.apiUrl - The base URL for the API.\n * @param {Object} config.endpoints - An object containing endpoint definitions.\n * @param {number} config.timeout - You can set the timeout for particular request in milliseconds.\n * @param {number} config.cancellable - If true, the previous requests will be automatically cancelled.\n * @param {number} config.rejectCancelled - If true and request is set to cancellable, a cancelled request promise will be rejected. By default, instead of rejecting the promise, defaultResponse is returned.\n * @param {number} config.timeout - Request timeout\n * @param {string} config.strategy - Error Handling Strategy\n * @param {string} config.flattenResponse - Whether to flatten response \"data\" object within \"data\" one\n * @param {*} config.defaultResponse - Default response when there is no data or when endpoint fails depending on the chosen strategy. It's \"null\" by default\n * @param {Object} [config.retry] - Options for retrying requests.\n * @param {number} [config.retry.retries=0] - Number of retry attempts. No retries by default.\n * @param {number} [config.retry.delay=1000] - Initial delay between retries in milliseconds.\n * @param {number} [config.retry.backoff=1.5] - Exponential backoff factor.\n * @param {number[]} [config.retry.retryOn=[502, 504, 408]] - HTTP status codes to retry on.\n * @param {RequestInterceptor|RequestInterceptor[]} [config.onRequest] - Optional request interceptor function or an array of functions.\n * These functions will be called with the request configuration object before the request is made. Can be used to modify or log the request configuration.\n * @param {ResponseInterceptor|ResponseInterceptor[]} [config.onResponse] - Optional response interceptor function or an array of functions.\n * These functions will be called with the response object after the response is received. an be used to modify or log the response data.\n * @param {Function} [config.onError] - Optional callback function for handling errors.\n * @param {Object} [config.headers] - Optional default headers to include in every request.\n * @param {Object} config.fetcher - The Axios (or any other) instance to use for making requests.\n * @param {*} config.logger - Instance of custom logger. Either class or an object similar to \"console\". Console is used by default.\n * @returns API handler functions and endpoints to call\n *\n * @example\n * // Import axios (optional)\n * import axios from 'axios';\n *\n * // Define endpoint paths\n * const endpoints = {\n * getUser: '/user',\n * createPost: '/post',\n * };\n *\n * // Create the API fetcher with configuration\n * const api = createApiFetcher({\n * fetcher: axios, // Axios instance (optional)\n * endpoints,\n * apiUrl: 'https://example.com/api',\n * onError(error) {\n * console.log('Request failed', error);\n * },\n * headers: {\n * 'my-auth-key': 'example-auth-key-32rjjfa',\n * },\n * });\n *\n * // Fetch user data\n * const response = await api.getUser({ userId: 1, ratings: [1, 2] })\n */\nfunction createApiFetcher(\n config: ApiHandlerConfig,\n) {\n const endpoints = config.endpoints;\n const requestHandler = new RequestHandler(config);\n\n /**\n * Get Fetcher Provider Instance\n *\n * @returns {FetcherInstance} Request Handler's Fetcher instance\n */\n function getInstance(): FetcherInstance {\n return requestHandler.getInstance();\n }\n\n /**\n * Triggered when trying to use non-existent endpoints\n *\n * @param endpointName Endpoint Name\n * @returns {Promise}\n */\n function handleNonImplemented(endpointName: string): Promise {\n console.error(`${endpointName} endpoint must be added to 'endpoints'.`);\n\n return Promise.resolve(null);\n }\n\n /**\n * Handle Single API Request\n * It considers settings in following order: per-request settings, global per-endpoint settings, global settings.\n *\n * @param {string} endpointName - The name of the API endpoint to call.\n * @param {QueryParams} [queryParams={}] - Query parameters to include in the request.\n * @param {UrlPathParams} [urlPathParams={}] - URI parameters to include in the request.\n * @param {EndpointConfig} [requestConfig={}] - Additional configuration for the request.\n * @returns {Promise} - A promise that resolves with the response from the API provider.\n */\n async function request(\n endpointName: keyof EndpointsMethods | string,\n queryParams: QueryParams = {},\n urlPathParams: UrlPathParams = {},\n requestConfig: RequestConfig = {},\n ): Promise> {\n // Use global per-endpoint settings\n const endpointConfig = endpoints[endpointName as string];\n const endpointSettings = { ...endpointConfig };\n\n const responseData = await requestHandler.request(\n endpointSettings.url,\n queryParams,\n {\n ...endpointSettings,\n ...requestConfig,\n urlPathParams,\n },\n );\n\n return responseData;\n }\n\n /**\n * Maps all API requests using native Proxy\n *\n * @param {*} prop Caller\n */\n function get(prop: string | symbol) {\n if (prop in apiHandler) {\n return apiHandler[prop];\n }\n\n // Prevent handler from triggering non-existent endpoints\n if (!endpoints[prop as string]) {\n return handleNonImplemented.bind(null, prop);\n }\n\n return apiHandler.request.bind(null, prop);\n }\n\n const apiHandler: ApiHandlerMethods = {\n config,\n endpoints,\n requestHandler,\n getInstance,\n request,\n };\n\n return new Proxy(apiHandler, {\n get: (_target, prop) => get(prop),\n }) as ApiHandlerReturnType;\n}\n\nexport { createApiFetcher };\n","import { RequestHandler } from './request-handler';\nimport type { APIResponse, FetchResponse, RequestHandlerConfig } from './types';\n\n/**\n * Simple wrapper for request fetching.\n * It abstracts the creation of RequestHandler, making it easy to perform API requests.\n *\n * @param {string | URL | globalThis.Request} url - Request URL.\n * @param {Object} config - Configuration object for the request handler.\n * @returns {Promise} Response Data.\n */\nexport async function fetchf(\n url: string,\n config: RequestHandlerConfig = {},\n): Promise> {\n return new RequestHandler(config).request(\n url,\n config.body || config.data || config.params,\n config,\n );\n}\n\nexport * from './types';\nexport * from './api-handler';\n"],"mappings":"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","names":["RequestErrorHandler","logger","requestErrorService","error","_a","errorContext","RequestError","_RequestError","message","requestInfo","response","interceptRequest","config","interceptors","interceptorList","interceptedConfig","interceptor","interceptResponse","response","interceptedResponse","RequestHandler","fetcher","timeout","rejectCancelled","strategy","flattenResponse","defaultResponse","logger","onError","config","url","urlPathParams","str","word","params","queryString","key","value","val","proto","data","method","methodLowerCase","isGetAlikeMethod","dynamicUrl","configData","payload","urlPath","baseURL","error","requestConfig","RequestErrorHandler","isRequestCancelled","errorHandlingStrategy","previousRequest","controller","abortTimeout","_a","_b","_c","response","_config","_requestConfig","retries","delay","backoff","retryOn","shouldRetry","maxDelay","attempt","waitTime","interceptRequest","contentType","RequestError","interceptResponse","ms","resolve","acc","createApiFetcher","config","endpoints","requestHandler","RequestHandler","getInstance","handleNonImplemented","endpointName","request","queryParams","urlPathParams","requestConfig","endpointSettings","get","prop","apiHandler","_target","fetchf","url","config","RequestHandler"]} \ No newline at end of file +{"version":3,"sources":["../src/interceptor-manager.ts","../src/response-error.ts","../src/request-handler.ts","../src/api-handler.ts","../src/index.ts"],"sourcesContent":["import type { RequestHandlerConfig, FetchResponse } from './types';\nimport type {\n RequestInterceptor,\n ResponseInterceptor,\n} from './types/interceptor-manager';\n\n/**\n * Applies a series of request interceptors to the provided configuration.\n * @param {RequestHandlerConfig} config - The initial request configuration.\n * @param {RequestInterceptor | RequestInterceptor[]} interceptors - The request interceptor function(s) to apply.\n * @returns {Promise} - The modified request configuration.\n */\nexport async function interceptRequest(\n config: RequestHandlerConfig,\n interceptors: RequestInterceptor | RequestInterceptor[],\n): Promise {\n if (!interceptors) {\n return config;\n }\n\n const interceptorList = Array.isArray(interceptors)\n ? interceptors\n : [interceptors];\n\n let interceptedConfig = { ...config };\n\n for (const interceptor of interceptorList) {\n interceptedConfig = await interceptor(interceptedConfig);\n }\n\n return interceptedConfig;\n}\n\n/**\n * Applies a series of response interceptors to the provided response.\n * @param {FetchResponse} response - The initial response object.\n * @param {ResponseInterceptor | ResponseInterceptor[]} interceptors - The response interceptor function(s) to apply.\n * @returns {Promise>} - The modified response object.\n */\nexport async function interceptResponse(\n response: FetchResponse,\n interceptors: ResponseInterceptor | ResponseInterceptor[],\n): Promise> {\n if (!interceptors) {\n return response;\n }\n\n const interceptorList = Array.isArray(interceptors)\n ? interceptors\n : [interceptors];\n\n let interceptedResponse = response;\n\n for (const interceptor of interceptorList) {\n interceptedResponse = await interceptor(interceptedResponse);\n }\n\n return interceptedResponse;\n}\n","import type { FetchResponse, RequestConfig } from './types';\n\nexport class ResponseErr extends Error {\n response: FetchResponse;\n request: RequestConfig;\n config: RequestConfig;\n status: number;\n statusText: string;\n\n constructor(\n message: string,\n requestInfo: RequestConfig,\n response: FetchResponse,\n ) {\n super(message);\n\n this.name = 'ResponseError';\n this.message = message;\n this.status = response.status;\n this.statusText = response.statusText;\n this.request = requestInfo;\n this.config = requestInfo;\n this.response = response;\n }\n}\n","/* eslint-disable @typescript-eslint/no-explicit-any */\nimport type {\n ErrorHandlingStrategy,\n RequestHandlerConfig,\n RequestConfig,\n FetcherInstance,\n Method,\n RetryOptions,\n FetchResponse,\n ResponseError,\n HeadersObject,\n} from './types/request-handler';\nimport type {\n APIResponse,\n QueryParams,\n QueryParamsOrBody,\n UrlPathParams,\n} from './types/api-handler';\nimport { interceptRequest, interceptResponse } from './interceptor-manager';\nimport { ResponseErr } from './response-error';\n\n/**\n * Generic Request Handler\n * It creates an Request Fetcher instance and handles requests within that instance\n * It handles errors depending on a chosen error handling strategy\n */\nexport class RequestHandler {\n /**\n * @var requestInstance Provider's instance\n */\n public requestInstance: FetcherInstance;\n\n /**\n * @var baseURL Base API url\n */\n public baseURL: string = '';\n\n /**\n * @var timeout Request timeout\n */\n public timeout: number = 30000;\n\n /**\n * @var cancellable Response cancellation\n */\n public cancellable: boolean = false;\n\n /**\n * @var rejectCancelled Whether to reject cancelled requests or not\n */\n public rejectCancelled: boolean = false;\n\n /**\n * @var strategy Request timeout\n */\n public strategy: ErrorHandlingStrategy = 'reject';\n\n /**\n * @var method Request method\n */\n public method: Method | string = 'get';\n\n /**\n * @var flattenResponse Response flattening\n */\n public flattenResponse: boolean = false;\n\n /**\n * @var defaultResponse Response flattening\n */\n public defaultResponse: any = null;\n\n /**\n * @var fetcher Request Fetcher instance\n */\n protected fetcher: FetcherInstance;\n\n /**\n * @var logger Logger\n */\n protected logger: any;\n\n /**\n * @var onError HTTP error service\n */\n protected onError: any;\n\n /**\n * @var requestsQueue Queue of requests\n */\n protected requestsQueue: WeakMap;\n\n /**\n * Request Handler Config\n */\n protected retry: RetryOptions = {\n retries: 0,\n delay: 1000,\n maxDelay: 30000,\n backoff: 1.5,\n\n // https://developer.mozilla.org/en-US/docs/Web/HTTP/Status\n retryOn: [\n 408, // Request Timeout\n 409, // Conflict\n 425, // Too Early\n 429, // Too Many Requests\n 500, // Internal Server Error\n 502, // Bad Gateway\n 503, // Service Unavailable\n 504, // Gateway Timeout\n ],\n\n shouldRetry: async () => true,\n };\n\n /**\n * Request Handler Config\n */\n public config: RequestHandlerConfig;\n\n /**\n * Creates an instance of RequestHandler.\n *\n * @param {Object} config - Configuration object for the request.\n * @param {string} config.baseURL - The base URL for the request.\n * @param {Object} config.endpoints - An object containing endpoint definitions.\n * @param {number} config.timeout - You can set the timeout for particular request in milliseconds.\n * @param {number} config.cancellable - If true, the previous requests will be automatically cancelled.\n * @param {number} config.rejectCancelled - If true and request is set to cancellable, a cancelled request promise will be rejected. By default, instead of rejecting the promise, defaultResponse is returned.\n * @param {number} config.timeout - Request timeout\n * @param {string} config.strategy - Error Handling Strategy\n * @param {string} config.flattenResponse - Whether to flatten response \"data\" object within \"data\" one\n * @param {*} config.defaultResponse - Default response when there is no data or when endpoint fails depending on the chosen strategy. It's \"null\" by default\n * @param {Object} [config.retry] - Options for retrying requests.\n * @param {number} [config.retry.retries=0] - Number of retry attempts. No retries by default.\n * @param {number} [config.retry.delay=1000] - Initial delay between retries in milliseconds.\n * @param {number} [config.retry.backoff=1.5] - Exponential backoff factor.\n * @param {number[]} [config.retry.retryOn=[502, 504, 408]] - HTTP status codes to retry on.\n * @param {RequestInterceptor|RequestInterceptor[]} [config.onRequest] - Optional request interceptor function or an array of functions.\n * These functions will be called with the request configuration object before the request is made. Can be used to modify or log the request configuration.\n * @param {ResponseInterceptor|ResponseInterceptor[]} [config.onResponse] - Optional response interceptor function or an array of functions.\n * These functions will be called with the response object after the response is received. an be used to modify or log the response data.\n * @param {Function} [config.onError] - Optional callback function for handling errors.\n * @param {Object} [config.headers] - Optional default headers to include in every request.\n * @param {Object} config.fetcher - The Axios (or any other) instance to use for making requests.\n * @param {*} config.logger - Instance of custom logger. Either class or an object similar to \"console\". Console is used by default.\n */\n public constructor({\n fetcher = null,\n timeout = null,\n rejectCancelled = false,\n strategy = null,\n flattenResponse = null,\n defaultResponse = {},\n logger = null,\n onError = null,\n ...config\n }: RequestHandlerConfig) {\n this.fetcher = fetcher;\n this.timeout =\n timeout !== null && timeout !== undefined ? timeout : this.timeout;\n this.strategy = strategy || this.strategy;\n this.cancellable = config.cancellable || this.cancellable;\n this.rejectCancelled = rejectCancelled || this.rejectCancelled;\n this.flattenResponse = flattenResponse || this.flattenResponse;\n this.defaultResponse = defaultResponse;\n this.logger = logger || (globalThis ? globalThis.console : null) || null;\n this.onError = onError;\n this.requestsQueue = new WeakMap();\n this.baseURL = config.baseURL || config.apiUrl || '';\n this.method = config.method || this.method;\n this.config = config;\n this.retry = {\n ...this.retry,\n ...(config.retry || {}),\n };\n\n this.requestInstance = this.isCustomFetcher()\n ? (fetcher as any).create({\n ...config,\n baseURL: this.baseURL,\n timeout: this.timeout,\n })\n : null;\n }\n\n /**\n * Get Provider Instance\n *\n * @returns {FetcherInstance} Provider's instance\n */\n public getInstance(): FetcherInstance {\n return this.requestInstance;\n }\n\n /**\n * Replaces dynamic URI parameters in a URL string with values from the provided `urlPathParams` object.\n * Parameters in the URL are denoted by `:`, where `` is a key in `urlPathParams`.\n *\n * @param {string} url - The URL string containing placeholders in the format `:`.\n * @param {Object} urlPathParams - An object containing the parameter values to replace placeholders.\n * @param {string} urlPathParams.paramName - The value to replace the placeholder `:` in the URL.\n * @returns {string} - The URL string with placeholders replaced by corresponding values from `urlPathParams`.\n */\n public replaceUrlPathParams(\n url: string,\n urlPathParams: UrlPathParams,\n ): string {\n if (!urlPathParams) {\n return url;\n }\n\n return url.replace(/:[a-zA-Z]+/gi, (str): string => {\n const word = str.substring(1);\n\n return String(urlPathParams[word] ? urlPathParams[word] : str);\n });\n }\n\n /**\n * Appends query parameters to the given URL\n *\n * @param {string} url - The base URL to which query parameters will be appended.\n * @param {QueryParams} params - An instance of URLSearchParams containing the query parameters to append.\n * @returns {string} - The URL with the appended query parameters.\n */\n public appendQueryParams(url: string, params: QueryParams): string {\n if (!params) {\n return url;\n }\n\n // We don't use URLSearchParams here as we want to ensure that arrays are properly converted similarily to Axios\n // So { foo: [1, 2] } would become: foo[]=1&foo[]=2\n const queryString = Object.entries(params)\n .flatMap(([key, value]) => {\n if (Array.isArray(value)) {\n return value.map(\n (val) => `${encodeURIComponent(key)}[]=${encodeURIComponent(val)}`,\n );\n }\n return `${encodeURIComponent(key)}=${encodeURIComponent(String(value))}`;\n })\n .join('&');\n\n return url.includes('?')\n ? `${url}&${queryString}`\n : queryString\n ? `${url}?${queryString}`\n : url;\n }\n\n /**\n * Checks if a value is JSON serializable.\n *\n * JSON serializable values include:\n * - Primitive types: string, number, boolean, null\n * - Arrays\n * - Plain objects (i.e., objects without special methods)\n * - Values with a `toJSON` method\n *\n * @param {any} value - The value to check for JSON serializability.\n * @returns {boolean} - Returns `true` if the value is JSON serializable, otherwise `false`.\n */\n protected isJSONSerializable(value: any): boolean {\n if (value === undefined || value === null) {\n return false;\n }\n\n const t = typeof value;\n if (t === 'string' || t === 'number' || t === 'boolean') {\n return true;\n }\n\n if (t !== 'object') {\n return false; // bigint, function, symbol, undefined\n }\n\n if (Array.isArray(value)) {\n return true;\n }\n\n if (Buffer.isBuffer(value)) {\n return false;\n }\n\n if (value instanceof Date) {\n return false;\n }\n\n const proto = Object.getPrototypeOf(value);\n\n // Check if the prototype is `Object.prototype` or `null` (plain object)\n if (proto === Object.prototype || proto === null) {\n return true;\n }\n\n // Check if the object has a toJSON method\n if (typeof value.toJSON === 'function') {\n return true;\n }\n\n return false;\n }\n\n /**\n * Build request configuration\n *\n * @param {string} url Request url\n * @param {QueryParamsOrBody} data Request data\n * @param {RequestConfig} config Request config\n * @returns {RequestConfig} Provider's instance\n */\n protected buildConfig(\n url: string,\n data: QueryParamsOrBody,\n config: RequestConfig,\n ): RequestConfig {\n const method = config.method || this.method;\n const methodLowerCase = method.toLowerCase();\n const isGetAlikeMethod =\n methodLowerCase === 'get' || methodLowerCase === 'head';\n\n const dynamicUrl = this.replaceUrlPathParams(\n url,\n config.urlPathParams || this.config.urlPathParams,\n );\n\n // Bonus: Specifying it here brings support for \"body\" in Axios\n const configData =\n config.body || config.data || this.config.body || this.config.data;\n\n // Axios compatibility\n if (this.isCustomFetcher()) {\n return {\n ...config,\n url: dynamicUrl,\n method: methodLowerCase,\n\n ...(isGetAlikeMethod ? { params: data } : {}),\n\n // For POST requests body payload is the first param for convenience (\"data\")\n // In edge cases we want to split so to treat it as query params, and use \"body\" coming from the config instead\n ...(!isGetAlikeMethod && data && configData ? { params: data } : {}),\n\n // Only applicable for request methods 'PUT', 'POST', 'DELETE', and 'PATCH'\n ...(!isGetAlikeMethod && data && !configData ? { data } : {}),\n ...(!isGetAlikeMethod && configData ? { data: configData } : {}),\n };\n }\n\n // Native fetch\n const payload = configData || data;\n const credentials =\n config.withCredentials || this.config.withCredentials\n ? 'include'\n : config.credentials;\n\n delete config.data;\n delete config.withCredentials;\n\n const urlPath =\n (!isGetAlikeMethod && data && !config.body) || !data\n ? dynamicUrl\n : this.appendQueryParams(dynamicUrl, data);\n const isFullUrl = urlPath.includes('://');\n const baseURL = isFullUrl\n ? ''\n : typeof config.baseURL !== 'undefined'\n ? config.baseURL\n : this.baseURL;\n\n return {\n ...config,\n credentials,\n\n // Native fetch generally requires query params to be appended in the URL\n // Do not append query params only if it's a POST-alike request with only \"data\" specified as it's treated as body payload\n url: baseURL + urlPath,\n\n // Uppercase method name\n method: method.toUpperCase(),\n\n // For convenience, add the same default headers as Axios does\n headers: {\n Accept: 'application/json, text/plain, */*',\n 'Content-Type': 'application/json;charset=utf-8',\n ...(config.headers || this.config.headers || {}),\n },\n\n // Automatically JSON stringify request bodies, if possible and when not dealing with strings\n ...(!isGetAlikeMethod\n ? {\n body: this.isJSONSerializable(payload)\n ? typeof payload === 'string'\n ? payload\n : JSON.stringify(payload)\n : payload,\n }\n : {}),\n };\n }\n\n /**\n * Process global Request Error\n *\n * @param {ResponseError} error Error instance\n * @param {RequestConfig} requestConfig Per endpoint request config\n * @returns {void}\n */\n protected processError(\n error: ResponseError,\n requestConfig: RequestConfig,\n ): void {\n if (this.isRequestCancelled(error)) {\n return;\n }\n\n if (this.logger?.warn) {\n this.logger.warn('API ERROR', error);\n }\n\n // Invoke per request \"onError\" interceptor\n if (requestConfig.onError && typeof requestConfig.onError === 'function') {\n requestConfig.onError(error);\n }\n\n // Invoke global \"onError\" interceptor\n if (this.onError && typeof this.onError === 'function') {\n this.onError(error);\n }\n }\n\n /**\n * Output default response in case of an error, depending on chosen strategy\n *\n * @param {ResponseError} error Error instance\n * @param {RequestConfig} requestConfig Per endpoint request config\n * @returns {*} Error response\n */\n protected async outputErrorResponse(\n error: ResponseError,\n requestConfig: RequestConfig,\n ): Promise {\n const isRequestCancelled = this.isRequestCancelled(error);\n const errorHandlingStrategy = requestConfig.strategy || this.strategy;\n const rejectCancelled =\n typeof requestConfig.rejectCancelled !== 'undefined'\n ? requestConfig.rejectCancelled\n : this.rejectCancelled;\n const defaultResponse =\n typeof requestConfig.defaultResponse !== 'undefined'\n ? requestConfig.defaultResponse\n : this.defaultResponse;\n\n // Output full response with the error object\n if (errorHandlingStrategy === 'softFail') {\n return this.outputResponse(error.response, requestConfig, error);\n }\n\n // By default cancelled requests aren't rejected\n if (isRequestCancelled && !rejectCancelled) {\n return defaultResponse;\n }\n\n // Hang the promise\n if (errorHandlingStrategy === 'silent') {\n await new Promise(() => null);\n\n return defaultResponse;\n }\n\n // Reject the promise\n if (errorHandlingStrategy === 'reject') {\n return Promise.reject(error);\n }\n\n return defaultResponse;\n }\n\n /**\n * Output error response depending on chosen strategy\n *\n * @param {ResponseError} error Error instance\n * @returns {boolean} True if request is aborted\n */\n public isRequestCancelled(error: ResponseError): boolean {\n return error.name === 'AbortError' || error.name === 'CanceledError';\n }\n\n /**\n * Detects if a custom fetcher is utilized\n *\n * @returns {boolean} True if it's a custom fetcher\n */\n protected isCustomFetcher(): boolean {\n return this.fetcher !== null;\n }\n\n /**\n * Automatically Cancel Previous Requests using AbortController when \"cancellable\" is defined\n *\n * @param {RequestConfig} requestConfig Per endpoint request config\n * @returns {Object} Controller Signal to abort\n */\n protected addCancellationToken(\n requestConfig: RequestConfig,\n ): Partial> {\n // Both disabled\n if (!this.cancellable && !requestConfig.cancellable) {\n return {};\n }\n\n // Explicitly disabled per request\n if (\n typeof requestConfig.cancellable !== 'undefined' &&\n !requestConfig.cancellable\n ) {\n return {};\n }\n\n // Check if AbortController is available\n if (typeof AbortController === 'undefined') {\n console.error('AbortController is unavailable.');\n\n return {};\n }\n\n // Generate unique key as a cancellation token\n const previousRequest = this.requestsQueue.get(requestConfig);\n\n if (previousRequest) {\n previousRequest.abort();\n }\n\n const controller = new AbortController();\n\n // Introduce timeout for native fetch\n if (!this.isCustomFetcher() && this.timeout > 0) {\n const abortTimeout = setTimeout(() => {\n const error = new Error(\n `[TimeoutError]: The ${requestConfig.url} request was aborted due to timeout`,\n );\n\n error.name = 'TimeoutError';\n (error as any).code = 23; // DOMException.TIMEOUT_ERR\n controller.abort(error);\n clearTimeout(abortTimeout);\n throw error;\n }, requestConfig.timeout || this.timeout);\n }\n\n this.requestsQueue.set(requestConfig, controller);\n\n return {\n signal: controller.signal,\n };\n }\n\n /**\n * Handle Request depending on used strategy\n *\n * @param {string} url - Request url\n * @param {QueryParamsOrBody} data - Request data\n * @param {RequestConfig} config - Request config\n * @param {RequestConfig} payload.config Request config\n * @throws {ResponseError}\n * @returns {Promise>} Response Data\n */\n public async request(\n url: string,\n data: QueryParamsOrBody = null,\n config: RequestConfig = null,\n ): Promise> {\n let response: FetchResponse = null;\n const _config = config || {};\n const _requestConfig = this.buildConfig(url, data, _config);\n\n let requestConfig: RequestConfig = {\n ...this.addCancellationToken(_requestConfig),\n ..._requestConfig,\n };\n\n const { retries, delay, backoff, retryOn, shouldRetry, maxDelay } = {\n ...this.retry,\n ...(requestConfig?.retry || {}),\n };\n\n let attempt = 0;\n let waitTime = delay;\n\n while (attempt <= retries) {\n try {\n // Local interceptors\n requestConfig = await interceptRequest(\n requestConfig,\n requestConfig.onRequest,\n );\n\n // Global interceptors\n requestConfig = await interceptRequest(\n requestConfig,\n this.config.onRequest,\n );\n\n // Axios compatibility\n if (this.isCustomFetcher()) {\n response = (await (this.requestInstance as any).request(\n requestConfig,\n )) as FetchResponse;\n } else {\n response = (await globalThis.fetch(\n requestConfig.url,\n requestConfig,\n )) as FetchResponse;\n\n // Attempt to collect response data regardless of response status\n const contentType = String(\n (response as Response)?.headers?.get('Content-Type') || '',\n );\n let data;\n\n // Handle edge case of no content type being provided... We assume json here.\n if (!contentType) {\n try {\n data = await response.json();\n // eslint-disable-next-line @typescript-eslint/no-unused-vars\n } catch (_error) {\n //\n }\n }\n\n if (typeof data === 'undefined') {\n if (contentType && contentType.includes('application/json')) {\n // Parse JSON response\n data = await response.json();\n } else if (typeof response.text !== 'undefined') {\n data = await response.text();\n } else if (typeof response.blob !== 'undefined') {\n data = await response.blob();\n } else {\n // Handle streams\n data = response.body || response.data || null;\n }\n }\n\n // Add more information to response object\n response.config = requestConfig;\n response.data = data;\n\n // Check if the response status is not outside the range 200-299 and if so, output error\n if (!response.ok) {\n throw new ResponseErr(\n `${requestConfig.url} failed! Status: ${response.status || null}`,\n requestConfig,\n response,\n );\n }\n }\n\n // Local interceptors\n response = await interceptResponse(response, requestConfig.onResponse);\n\n // Global interceptors\n response = await interceptResponse(response, this.config.onResponse);\n\n return this.outputResponse(response, requestConfig) as ResponseData &\n FetchResponse;\n } catch (error) {\n if (\n attempt === retries ||\n !(await shouldRetry(error, attempt)) ||\n !retryOn?.includes(error?.response?.status || error?.status)\n ) {\n this.processError(error, requestConfig);\n\n return this.outputErrorResponse(error, requestConfig);\n }\n\n if (this.logger?.warn) {\n this.logger.warn(\n `Attempt ${attempt + 1} failed. Retrying in ${waitTime}ms...`,\n );\n }\n\n await this.delay(waitTime);\n\n waitTime *= backoff;\n waitTime = Math.min(waitTime, maxDelay);\n attempt++;\n }\n }\n\n return this.outputResponse(response, requestConfig) as ResponseData &\n FetchResponse;\n }\n\n public async delay(ms: number): Promise {\n return new Promise((resolve) =>\n setTimeout(() => {\n return resolve(true);\n }, ms),\n );\n }\n\n public processHeaders(\n response: FetchResponse,\n ): HeadersObject {\n if (!response.headers) {\n return {};\n }\n\n let headersObject: HeadersObject = {};\n\n // Handle Headers object with entries() method\n if (response.headers instanceof Headers) {\n for (const [key, value] of (response.headers as any).entries()) {\n headersObject[key] = value;\n }\n } else {\n // Handle plain object\n headersObject = { ...(response.headers as HeadersObject) };\n }\n\n return headersObject;\n }\n\n /**\n * Output response\n *\n * @param response - Response payload\n * @param {RequestConfig} requestConfig - Request config\n * @param {*} error - whether the response is erroneous\n * @returns {ResponseData | FetchResponse} Response data\n */\n protected outputResponse(\n response: FetchResponse,\n requestConfig: RequestConfig,\n error = null,\n ): ResponseData | FetchResponse {\n const defaultResponse =\n typeof requestConfig.defaultResponse !== 'undefined'\n ? requestConfig.defaultResponse\n : this.defaultResponse;\n\n if (!response) {\n return defaultResponse;\n }\n\n if (\n (requestConfig.flattenResponse || this.flattenResponse) &&\n typeof response.data !== 'undefined'\n ) {\n // Special case of only data property within response data object (happens in Axios)\n // This is in fact a proper response but we may want to flatten it\n // To ease developers' lives when obtaining the response\n if (\n response.data !== null &&\n typeof response.data === 'object' &&\n typeof (response.data as any).data !== 'undefined' &&\n Object.keys(response.data).length === 1\n ) {\n return (response.data as any).data;\n }\n\n return response.data;\n }\n\n // If empty object is returned, ensure that the default response is used instead\n if (\n response !== null &&\n typeof response === 'object' &&\n response.constructor === Object &&\n Object.keys(response).length === 0\n ) {\n return defaultResponse;\n }\n\n const isCustomFetcher = this.isCustomFetcher();\n\n if (isCustomFetcher) {\n return response;\n }\n\n if (error !== null) {\n delete error?.response;\n delete error?.request;\n delete error?.config;\n }\n\n // Native fetch()\n return {\n ...response,\n error,\n headers: this.processHeaders(response),\n config: requestConfig,\n };\n }\n}\n","import { RequestHandler } from './request-handler';\nimport type {\n FetcherInstance,\n RequestConfig,\n FetchResponse,\n} from './types/request-handler';\nimport type {\n ApiHandlerConfig,\n ApiHandlerMethods,\n ApiHandlerReturnType,\n APIResponse,\n QueryParams,\n UrlPathParams,\n} from './types/api-handler';\n\n/**\n * Creates an instance of API Handler.\n * It creates an API fetcher function using native fetch() or Axios if it is passed as \"fetcher\".\n *\n * @param {Object} config - Configuration object for the API fetcher.\n * @param {string} config.apiUrl - The base URL for the API.\n * @param {Object} config.endpoints - An object containing endpoint definitions.\n * @param {number} config.timeout - You can set the timeout for particular request in milliseconds.\n * @param {number} config.cancellable - If true, the previous requests will be automatically cancelled.\n * @param {number} config.rejectCancelled - If true and request is set to cancellable, a cancelled request promise will be rejected. By default, instead of rejecting the promise, defaultResponse is returned.\n * @param {number} config.timeout - Request timeout\n * @param {string} config.strategy - Error Handling Strategy\n * @param {string} config.flattenResponse - Whether to flatten response \"data\" object within \"data\" one\n * @param {*} config.defaultResponse - Default response when there is no data or when endpoint fails depending on the chosen strategy. It's \"null\" by default\n * @param {Object} [config.retry] - Options for retrying requests.\n * @param {number} [config.retry.retries=0] - Number of retry attempts. No retries by default.\n * @param {number} [config.retry.delay=1000] - Initial delay between retries in milliseconds.\n * @param {number} [config.retry.backoff=1.5] - Exponential backoff factor.\n * @param {number[]} [config.retry.retryOn=[502, 504, 408]] - HTTP status codes to retry on.\n * @param {RequestInterceptor|RequestInterceptor[]} [config.onRequest] - Optional request interceptor function or an array of functions.\n * These functions will be called with the request configuration object before the request is made. Can be used to modify or log the request configuration.\n * @param {ResponseInterceptor|ResponseInterceptor[]} [config.onResponse] - Optional response interceptor function or an array of functions.\n * These functions will be called with the response object after the response is received. an be used to modify or log the response data.\n * @param {Function} [config.onError] - Optional callback function for handling errors.\n * @param {Object} [config.headers] - Optional default headers to include in every request.\n * @param {Object} config.fetcher - The Axios (or any other) instance to use for making requests.\n * @param {*} config.logger - Instance of custom logger. Either class or an object similar to \"console\". Console is used by default.\n * @returns API handler functions and endpoints to call\n *\n * @example\n * // Import axios (optional)\n * import axios from 'axios';\n *\n * // Define endpoint paths\n * const endpoints = {\n * getUser: '/user',\n * createPost: '/post',\n * };\n *\n * // Create the API fetcher with configuration\n * const api = createApiFetcher({\n * fetcher: axios, // Axios instance (optional)\n * endpoints,\n * apiUrl: 'https://example.com/api',\n * onError(error) {\n * console.log('Request failed', error);\n * },\n * headers: {\n * 'my-auth-key': 'example-auth-key-32rjjfa',\n * },\n * });\n *\n * // Fetch user data\n * const response = await api.getUser({ userId: 1, ratings: [1, 2] })\n */\nfunction createApiFetcher<\n EndpointsMethods extends object,\n EndpointsCfg = never,\n>(config: ApiHandlerConfig) {\n const endpoints = config.endpoints;\n const requestHandler = new RequestHandler(config);\n\n /**\n * Get Fetcher Provider Instance\n *\n * @returns {FetcherInstance} Request Handler's Fetcher instance\n */\n function getInstance(): FetcherInstance {\n return requestHandler.getInstance();\n }\n\n /**\n * Triggered when trying to use non-existent endpoints\n *\n * @param endpointName Endpoint Name\n * @returns {Promise}\n */\n function handleNonImplemented(endpointName: string): Promise {\n console.error(`${endpointName} endpoint must be added to 'endpoints'.`);\n\n return Promise.resolve(null);\n }\n\n /**\n * Handle Single API Request\n * It considers settings in following order: per-request settings, global per-endpoint settings, global settings.\n *\n * @param {string} endpointName - The name of the API endpoint to call.\n * @param {QueryParams} [queryParams={}] - Query parameters to include in the request.\n * @param {UrlPathParams} [urlPathParams={}] - URI parameters to include in the request.\n * @param {EndpointConfig} [requestConfig={}] - Additional configuration for the request.\n * @returns {Promise} - A promise that resolves with the response from the API provider.\n */\n async function request(\n endpointName: keyof EndpointsMethods | string,\n queryParams: QueryParams = {},\n urlPathParams: UrlPathParams = {},\n requestConfig: RequestConfig = {},\n ): Promise> {\n // Use global per-endpoint settings\n const endpointConfig = endpoints[endpointName as string];\n const endpointSettings = { ...endpointConfig };\n\n const responseData = await requestHandler.request(\n endpointSettings.url,\n queryParams,\n {\n ...endpointSettings,\n ...requestConfig,\n urlPathParams,\n },\n );\n\n return responseData;\n }\n\n /**\n * Maps all API requests using native Proxy\n *\n * @param {*} prop Caller\n */\n function get(prop: string | symbol) {\n if (prop in apiHandler) {\n return apiHandler[prop];\n }\n\n // Prevent handler from triggering non-existent endpoints\n if (!endpoints[prop as string]) {\n return handleNonImplemented.bind(null, prop);\n }\n\n return apiHandler.request.bind(null, prop);\n }\n\n const apiHandler: ApiHandlerMethods = {\n config,\n endpoints,\n requestHandler,\n getInstance,\n request,\n };\n\n return new Proxy(apiHandler, {\n get: (_target, prop) => get(prop),\n }) as ApiHandlerReturnType;\n}\n\nexport { createApiFetcher };\n","import { RequestHandler } from './request-handler';\nimport type { APIResponse, FetchResponse, RequestHandlerConfig } from './types';\n\n/**\n * Simple wrapper for request fetching.\n * It abstracts the creation of RequestHandler, making it easy to perform API requests.\n *\n * @param {string | URL | globalThis.Request} url - Request URL.\n * @param {RequestHandlerConfig} config - Configuration object for the request handler.\n * @returns {Promise>} Response Data.\n */\nexport async function fetchf(\n url: string,\n config: RequestHandlerConfig = {},\n): Promise> {\n return new RequestHandler(config).request(\n url,\n config.body || config.data || config.params,\n config,\n );\n}\n\nexport * from './types';\nexport * from './api-handler';\n"],"mappings":"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","names":["interceptRequest","config","interceptors","interceptorList","interceptedConfig","interceptor","interceptResponse","response","interceptedResponse","ResponseErr","message","requestInfo","response","RequestHandler","fetcher","timeout","rejectCancelled","strategy","flattenResponse","defaultResponse","logger","onError","config","url","urlPathParams","str","word","params","queryString","key","value","val","t","proto","data","method","methodLowerCase","isGetAlikeMethod","dynamicUrl","configData","payload","credentials","urlPath","baseURL","error","requestConfig","_a","isRequestCancelled","errorHandlingStrategy","previousRequest","controller","abortTimeout","_b","_c","response","_config","_requestConfig","retries","delay","backoff","retryOn","shouldRetry","maxDelay","attempt","waitTime","interceptRequest","contentType","ResponseErr","interceptResponse","ms","resolve","headersObject","createApiFetcher","config","endpoints","requestHandler","RequestHandler","getInstance","handleNonImplemented","endpointName","request","queryParams","urlPathParams","requestConfig","endpointSettings","get","prop","apiHandler","_target","fetchf","url","config","RequestHandler"]} \ No newline at end of file diff --git a/dist/browser/index.mjs b/dist/browser/index.mjs index d3d67ea..c53a520 100644 --- a/dist/browser/index.mjs +++ b/dist/browser/index.mjs @@ -1,2 +1,2 @@ -var g=class{logger;requestErrorService;constructor(e,t){this.logger=e,this.requestErrorService=t}process(e){var s;(s=this.logger)!=null&&s.warn&&this.logger.warn("API ERROR",e);let t=e;typeof e=="string"&&(t=new Error(e)),this.requestErrorService&&(typeof this.requestErrorService.process<"u"?this.requestErrorService.process(t):typeof this.requestErrorService=="function"&&this.requestErrorService(t))}};var q=class u extends Error{response;request;constructor(e,t,s){super(e),this.name="RequestError",this.message=e,this.request=t,this.response=s,Error.captureStackTrace(this,u)}};async function C(u,e){if(!e)return u;let t=Array.isArray(e)?e:[e],s={...u};for(let r of t)s=await r(s);return s}async function E(u,e){if(!e)return u;let t=Array.isArray(e)?e:[e],s=u;for(let r of t)s=await r(s);return s}var R=class{requestInstance;baseURL="";timeout=3e4;cancellable=!1;rejectCancelled=!1;strategy="reject";method="get";flattenResponse=!1;defaultResponse=null;fetcher;logger;onError;requestsQueue;retry={retries:0,delay:1e3,maxDelay:3e4,backoff:1.5,retryOn:[408,409,425,429,500,502,503,504],shouldRetry:async()=>!0};config;constructor({fetcher:e=null,timeout:t=null,rejectCancelled:s=!1,strategy:r=null,flattenResponse:o=null,defaultResponse:a={},logger:n=null,onError:i=null,...l}){this.fetcher=e,this.timeout=t??this.timeout,this.strategy=r??this.strategy,this.cancellable=l.cancellable||this.cancellable,this.rejectCancelled=s||this.rejectCancelled,this.flattenResponse=o??this.flattenResponse,this.defaultResponse=a,this.logger=n||(globalThis?globalThis.console:null)||null,this.onError=i,this.requestsQueue=new WeakMap,this.baseURL=l.baseURL||l.apiUrl||"",this.method=l.method||this.method,this.config=l,this.retry={...this.retry,...l.retry||{}},this.requestInstance=this.isCustomFetcher()?e.create({...l,baseURL:this.baseURL,timeout:this.timeout}):null}getInstance(){return this.requestInstance}replaceUrlPathParams(e,t){return t?e.replace(/:[a-zA-Z]+/gi,s=>{let r=s.substring(1);return String(t[r]?t[r]:s)}):e}appendQueryParams(e,t){if(!t)return e;let s=Object.entries(t).flatMap(([r,o])=>Array.isArray(o)?o.map(a=>`${encodeURIComponent(r)}[]=${encodeURIComponent(a)}`):`${encodeURIComponent(r)}=${encodeURIComponent(String(o))}`).join("&");return e.includes("?")?`${e}&${s}`:s?`${e}?${s}`:e}isJSONSerializable(e){if(e==null)return!1;let t=typeof e;if(t==="string"||t==="number"||t==="boolean")return!0;if(t!=="object")return!1;if(Array.isArray(e))return!0;if(Buffer.isBuffer(e)||e instanceof Date)return!1;let s=Object.getPrototypeOf(e);return s===Object.prototype||s===null||typeof e.toJSON=="function"}buildConfig(e,t,s){let r=s.method||this.method,o=r.toLowerCase(),a=o==="get"||o==="head",n=this.replaceUrlPathParams(e,s.urlPathParams||this.config.urlPathParams),i=s.body||s.data||this.config.body||this.config.data;if(this.isCustomFetcher())return{...s,url:n,method:o,...a?{params:t}:{},...!a&&t&&i?{params:t}:{},...!a&&t&&!i?{data:t}:{},...!a&&i?{data:i}:{}};let l=i||t;delete s.data;let d=!a&&t&&!s.body||!t?n:this.appendQueryParams(n,t),m=d.includes("://")?"":typeof s.baseURL<"u"?s.baseURL:this.baseURL;return{...s,url:m+d,method:r.toUpperCase(),headers:{Accept:"application/json, text/plain, */*","Content-Type":"application/json;charset=utf-8",...s.headers||this.config.headers||{}},...a?{}:{body:this.isJSONSerializable(l)?typeof l=="string"?l:JSON.stringify(l):l}}}processError(e,t){if(this.isRequestCancelled(e))return;t.onError&&typeof t.onError=="function"&&t.onError(e),new g(this.logger,this.onError).process(e)}async outputErrorResponse(e,t){let s=this.isRequestCancelled(e),r=t.strategy||this.strategy,o=typeof t.rejectCancelled<"u"?t.rejectCancelled:this.rejectCancelled,a=typeof t.defaultResponse<"u"?t.defaultResponse:this.defaultResponse;return s&&!o?a:r==="silent"?(await new Promise(()=>null),a):r==="reject"?Promise.reject(e):a}isRequestCancelled(e){return e.name==="AbortError"||e.name==="CanceledError"}isCustomFetcher(){return this.fetcher!==null}addCancellationToken(e){if(!this.cancellable&&!e.cancellable)return{};if(typeof e.cancellable<"u"&&!e.cancellable)return{};if(typeof AbortController>"u")return console.error("AbortController is unavailable."),{};let t=this.requestsQueue.get(e);t&&t.abort();let s=new AbortController;if(!this.isCustomFetcher()){let r=setTimeout(()=>{let o=new Error(`[TimeoutError]: The ${e.url} request was aborted due to timeout`);throw o.name="TimeoutError",o.code=23,s.abort(o),clearTimeout(r),o},e.timeout||this.timeout)}return this.requestsQueue.set(e,s),{signal:s.signal}}async request(e,t=null,s=null){var P,w,A;let r=null,o=s||{},a=this.buildConfig(e,t,o),n={...this.addCancellationToken(a),...a},{retries:i,delay:l,backoff:d,retryOn:h,shouldRetry:m,maxDelay:b}={...this.retry,...(n==null?void 0:n.retry)||{}},f=0,y=l;for(;f<=i;)try{if(n=await C(n,n.onRequest),n=await C(n,this.config.onRequest),this.isCustomFetcher())r=await this.requestInstance.request(n);else if(r=await globalThis.fetch(n.url,n),r.config=n,r.ok){let c=String(((P=r==null?void 0:r.headers)==null?void 0:P.get("Content-Type"))||""),p=null;if(!c)try{p=await r.json()}catch{}p||(c&&c.includes("application/json")?p=await r.json():typeof r.text<"u"?p=await r.text():typeof r.blob<"u"?p=await r.blob():p=r.body||r.data||null),r.data=p}else throw r.data=null,new q(`fetchf() Request Failed! Status: ${r.status||null}`,n,r);return r=await E(r,n.onResponse),r=await E(r,this.config.onResponse),this.processResponseData(r,n)}catch(c){if(f===i||!await m(c,f)||!(h!=null&&h.includes((r==null?void 0:r.status)||((w=c==null?void 0:c.response)==null?void 0:w.status)||(c==null?void 0:c.status))))return this.processError(c,n),this.outputErrorResponse(c,n);(A=this.logger)!=null&&A.warn&&this.logger.warn(`Attempt ${f+1} failed. Retrying in ${y}ms...`),await this.delay(y),y*=d,y=Math.min(y,b),f++}return this.processResponseData(r,n)}async delay(e){return new Promise(t=>setTimeout(()=>t(!0),e))}processResponseData(e,t){var o;let s=typeof t.defaultResponse<"u"?t.defaultResponse:this.defaultResponse;return e?(t.flattenResponse||this.flattenResponse)&&typeof e.data<"u"?typeof e.data=="object"&&typeof e.data.data<"u"&&Object.keys(e.data).length===1?e.data.data:e.data:typeof e=="object"&&e.constructor===Object&&Object.keys(e).length===0?s:this.isCustomFetcher()?e:{...e,headers:Array.from(((o=e==null?void 0:e.headers)==null?void 0:o.entries())||{}).reduce((a,[n,i])=>(a[n]=i,a),{}),config:t}:s}};function Q(u){let e=u.endpoints,t=new R(u);function s(){return t.getInstance()}function r(i){return console.error(`${i} endpoint must be added to 'endpoints'.`),Promise.resolve(null)}async function o(i,l={},d={},h={}){let b={...e[i]};return await t.request(b.url,l,{...b,...h,urlPathParams:d})}function a(i){return i in n?n[i]:e[i]?n.request.bind(null,i):r.bind(null,i)}let n={config:u,endpoints:e,requestHandler:t,getInstance:s,request:o};return new Proxy(n,{get:(i,l)=>a(l)})}async function D(u,e={}){return new R(e).request(u,e.body||e.data||e.params,e)}export{Q as createApiFetcher,D as fetchf}; +async function C(u,e){if(!e)return u;let s=Array.isArray(e)?e:[e],t={...u};for(let n of s)t=await n(t);return t}async function P(u,e){if(!e)return u;let s=Array.isArray(e)?e:[e],t=u;for(let n of s)t=await n(t);return t}var b=class extends Error{response;request;config;status;statusText;constructor(e,s,t){super(e),this.name="ResponseError",this.message=e,this.status=t.status,this.statusText=t.statusText,this.request=s,this.config=s,this.response=t}};var R=class{requestInstance;baseURL="";timeout=3e4;cancellable=!1;rejectCancelled=!1;strategy="reject";method="get";flattenResponse=!1;defaultResponse=null;fetcher;logger;onError;requestsQueue;retry={retries:0,delay:1e3,maxDelay:3e4,backoff:1.5,retryOn:[408,409,425,429,500,502,503,504],shouldRetry:async()=>!0};config;constructor({fetcher:e=null,timeout:s=null,rejectCancelled:t=!1,strategy:n=null,flattenResponse:o=null,defaultResponse:i={},logger:r=null,onError:a=null,...l}){this.fetcher=e,this.timeout=s??this.timeout,this.strategy=n||this.strategy,this.cancellable=l.cancellable||this.cancellable,this.rejectCancelled=t||this.rejectCancelled,this.flattenResponse=o||this.flattenResponse,this.defaultResponse=i,this.logger=r||(globalThis?globalThis.console:null)||null,this.onError=a,this.requestsQueue=new WeakMap,this.baseURL=l.baseURL||l.apiUrl||"",this.method=l.method||this.method,this.config=l,this.retry={...this.retry,...l.retry||{}},this.requestInstance=this.isCustomFetcher()?e.create({...l,baseURL:this.baseURL,timeout:this.timeout}):null}getInstance(){return this.requestInstance}replaceUrlPathParams(e,s){return s?e.replace(/:[a-zA-Z]+/gi,t=>{let n=t.substring(1);return String(s[n]?s[n]:t)}):e}appendQueryParams(e,s){if(!s)return e;let t=Object.entries(s).flatMap(([n,o])=>Array.isArray(o)?o.map(i=>`${encodeURIComponent(n)}[]=${encodeURIComponent(i)}`):`${encodeURIComponent(n)}=${encodeURIComponent(String(o))}`).join("&");return e.includes("?")?`${e}&${t}`:t?`${e}?${t}`:e}isJSONSerializable(e){if(e==null)return!1;let s=typeof e;if(s==="string"||s==="number"||s==="boolean")return!0;if(s!=="object")return!1;if(Array.isArray(e))return!0;if(Buffer.isBuffer(e)||e instanceof Date)return!1;let t=Object.getPrototypeOf(e);return t===Object.prototype||t===null||typeof e.toJSON=="function"}buildConfig(e,s,t){let n=t.method||this.method,o=n.toLowerCase(),i=o==="get"||o==="head",r=this.replaceUrlPathParams(e,t.urlPathParams||this.config.urlPathParams),a=t.body||t.data||this.config.body||this.config.data;if(this.isCustomFetcher())return{...t,url:r,method:o,...i?{params:s}:{},...!i&&s&&a?{params:s}:{},...!i&&s&&!a?{data:s}:{},...!i&&a?{data:a}:{}};let l=a||s,y=t.withCredentials||this.config.withCredentials?"include":t.credentials;delete t.data,delete t.withCredentials;let p=!i&&s&&!t.body||!s?r:this.appendQueryParams(r,s),h=p.includes("://")?"":typeof t.baseURL<"u"?t.baseURL:this.baseURL;return{...t,credentials:y,url:h+p,method:n.toUpperCase(),headers:{Accept:"application/json, text/plain, */*","Content-Type":"application/json;charset=utf-8",...t.headers||this.config.headers||{}},...i?{}:{body:this.isJSONSerializable(l)?typeof l=="string"?l:JSON.stringify(l):l}}}processError(e,s){var t;this.isRequestCancelled(e)||((t=this.logger)!=null&&t.warn&&this.logger.warn("API ERROR",e),s.onError&&typeof s.onError=="function"&&s.onError(e),this.onError&&typeof this.onError=="function"&&this.onError(e))}async outputErrorResponse(e,s){let t=this.isRequestCancelled(e),n=s.strategy||this.strategy,o=typeof s.rejectCancelled<"u"?s.rejectCancelled:this.rejectCancelled,i=typeof s.defaultResponse<"u"?s.defaultResponse:this.defaultResponse;return n==="softFail"?this.outputResponse(e.response,s,e):t&&!o?i:n==="silent"?(await new Promise(()=>null),i):n==="reject"?Promise.reject(e):i}isRequestCancelled(e){return e.name==="AbortError"||e.name==="CanceledError"}isCustomFetcher(){return this.fetcher!==null}addCancellationToken(e){if(!this.cancellable&&!e.cancellable)return{};if(typeof e.cancellable<"u"&&!e.cancellable)return{};if(typeof AbortController>"u")return console.error("AbortController is unavailable."),{};let s=this.requestsQueue.get(e);s&&s.abort();let t=new AbortController;if(!this.isCustomFetcher()&&this.timeout>0){let n=setTimeout(()=>{let o=new Error(`[TimeoutError]: The ${e.url} request was aborted due to timeout`);throw o.name="TimeoutError",o.code=23,t.abort(o),clearTimeout(n),o},e.timeout||this.timeout)}return this.requestsQueue.set(e,t),{signal:t.signal}}async request(e,s=null,t=null){var q,w,F;let n=null,o=t||{},i=this.buildConfig(e,s,o),r={...this.addCancellationToken(i),...i},{retries:a,delay:l,backoff:y,retryOn:p,shouldRetry:g,maxDelay:h}={...this.retry,...(r==null?void 0:r.retry)||{}},f=0,m=l;for(;f<=a;)try{if(r=await C(r,r.onRequest),r=await C(r,this.config.onRequest),this.isCustomFetcher())n=await this.requestInstance.request(r);else{n=await globalThis.fetch(r.url,r);let c=String(((q=n==null?void 0:n.headers)==null?void 0:q.get("Content-Type"))||""),d;if(!c)try{d=await n.json()}catch{}if(typeof d>"u"&&(c&&c.includes("application/json")?d=await n.json():typeof n.text<"u"?d=await n.text():typeof n.blob<"u"?d=await n.blob():d=n.body||n.data||null),n.config=r,n.data=d,!n.ok)throw new b(`${r.url} failed! Status: ${n.status||null}`,r,n)}return n=await P(n,r.onResponse),n=await P(n,this.config.onResponse),this.outputResponse(n,r)}catch(c){if(f===a||!await g(c,f)||!(p!=null&&p.includes(((w=c==null?void 0:c.response)==null?void 0:w.status)||(c==null?void 0:c.status))))return this.processError(c,r),this.outputErrorResponse(c,r);(F=this.logger)!=null&&F.warn&&this.logger.warn(`Attempt ${f+1} failed. Retrying in ${m}ms...`),await this.delay(m),m*=y,m=Math.min(m,h),f++}return this.outputResponse(n,r)}async delay(e){return new Promise(s=>setTimeout(()=>s(!0),e))}processHeaders(e){if(!e.headers)return{};let s={};if(e.headers instanceof Headers)for(let[t,n]of e.headers.entries())s[t]=n;else s={...e.headers};return s}outputResponse(e,s,t=null){let n=typeof s.defaultResponse<"u"?s.defaultResponse:this.defaultResponse;return e?(s.flattenResponse||this.flattenResponse)&&typeof e.data<"u"?e.data!==null&&typeof e.data=="object"&&typeof e.data.data<"u"&&Object.keys(e.data).length===1?e.data.data:e.data:e!==null&&typeof e=="object"&&e.constructor===Object&&Object.keys(e).length===0?n:this.isCustomFetcher()?e:(t!==null&&(t==null||delete t.response,t==null||delete t.request,t==null||delete t.config),{...e,error:t,headers:this.processHeaders(e),config:s}):n}};function T(u){let e=u.endpoints,s=new R(u);function t(){return s.getInstance()}function n(a){return console.error(`${a} endpoint must be added to 'endpoints'.`),Promise.resolve(null)}async function o(a,l={},y={},p={}){let h={...e[a]};return await s.request(h.url,l,{...h,...p,urlPathParams:y})}function i(a){return a in r?r[a]:e[a]?r.request.bind(null,a):n.bind(null,a)}let r={config:u,endpoints:e,requestHandler:s,getInstance:t,request:o};return new Proxy(r,{get:(a,l)=>i(l)})}async function $(u,e={}){return new R(e).request(u,e.body||e.data||e.params,e)}export{T as createApiFetcher,$ as fetchf}; //# sourceMappingURL=index.mjs.map \ No newline at end of file diff --git a/dist/browser/index.mjs.map b/dist/browser/index.mjs.map index 9e2f74b..f8d9be8 100644 --- a/dist/browser/index.mjs.map +++ b/dist/browser/index.mjs.map @@ -1 +1 @@ -{"version":3,"sources":["../src/request-error-handler.ts","../src/request-error.ts","../src/interceptor-manager.ts","../src/request-handler.ts","../src/api-handler.ts","../src/index.ts"],"sourcesContent":["/* eslint-disable @typescript-eslint/no-explicit-any */\nexport class RequestErrorHandler {\n /**\n * Logger Class\n *\n * @type {*}\n * @memberof RequestErrorHandler\n */\n protected logger: any;\n\n /**\n * Error Service Class\n *\n * @type {*}\n * @memberof RequestErrorHandler\n */\n public requestErrorService: any;\n\n public constructor(logger: any, requestErrorService: any) {\n this.logger = logger;\n this.requestErrorService = requestErrorService;\n }\n\n /**\n * Process and Error\n *\n * @param {*} error Error instance or message\n * @throws Request error context\n * @returns {void}\n */\n public process(error: string | Error): void {\n if (this.logger?.warn) {\n this.logger.warn('API ERROR', error);\n }\n\n let errorContext = error;\n\n if (typeof error === 'string') {\n errorContext = new Error(error);\n }\n\n if (this.requestErrorService) {\n if (typeof this.requestErrorService.process !== 'undefined') {\n this.requestErrorService.process(errorContext);\n } else if (typeof this.requestErrorService === 'function') {\n this.requestErrorService(errorContext);\n }\n }\n }\n}\n","import type { RequestConfig } from './types';\n\nexport class RequestError extends Error {\n response: Response;\n request: RequestConfig;\n\n constructor(message: string, requestInfo: RequestConfig, response: Response) {\n super(message);\n\n this.name = 'RequestError';\n this.message = message;\n this.request = requestInfo;\n this.response = response;\n\n // Clean stack trace\n Error.captureStackTrace(this, RequestError);\n }\n}\n","import type {\n BaseRequestHandlerConfig,\n FetchResponse,\n RequestResponse,\n} from './types';\nimport type {\n RequestInterceptor,\n ResponseInterceptor,\n} from './types/interceptor-manager';\n\n/**\n * Applies a series of request interceptors to the provided configuration.\n * @param {BaseRequestHandlerConfig} config - The initial request configuration.\n * @param {RequestInterceptor | RequestInterceptor[]} interceptors - The request interceptor function(s) to apply.\n * @returns {Promise} - The modified request configuration.\n */\nexport async function interceptRequest(\n config: BaseRequestHandlerConfig,\n interceptors: RequestInterceptor | RequestInterceptor[],\n): Promise {\n if (!interceptors) {\n return config;\n }\n\n const interceptorList = Array.isArray(interceptors)\n ? interceptors\n : [interceptors];\n\n let interceptedConfig = { ...config };\n\n for (const interceptor of interceptorList) {\n interceptedConfig = await interceptor(interceptedConfig);\n }\n\n return interceptedConfig;\n}\n\n/**\n * Applies a series of response interceptors to the provided response.\n * @param {FetchResponse} response - The initial response object.\n * @param {ResponseInterceptor | ResponseInterceptor[]} interceptors - The response interceptor function(s) to apply.\n * @returns {Promise>} - The modified response object.\n */\nexport async function interceptResponse(\n response: FetchResponse,\n interceptors: ResponseInterceptor | ResponseInterceptor[],\n): Promise> {\n if (!interceptors) {\n return response;\n }\n\n const interceptorList = Array.isArray(interceptors)\n ? interceptors\n : [interceptors];\n\n let interceptedResponse = response;\n\n for (const interceptor of interceptorList) {\n interceptedResponse = await interceptor(interceptedResponse);\n }\n\n return interceptedResponse;\n}\n","/* eslint-disable @typescript-eslint/no-explicit-any */\nimport { RequestErrorHandler } from './request-error-handler';\nimport type {\n ErrorHandlingStrategy,\n RequestHandlerConfig,\n RequestConfig,\n RequestError as RequestErrorResponse,\n FetcherInstance,\n Method,\n RequestConfigHeaders,\n RetryOptions,\n FetchResponse,\n ExtendedResponse,\n} from './types/request-handler';\nimport type {\n APIResponse,\n QueryParams,\n QueryParamsOrBody,\n UrlPathParams,\n} from './types/api-handler';\nimport { RequestError } from './request-error';\nimport { interceptRequest, interceptResponse } from './interceptor-manager';\n\n/**\n * Generic Request Handler\n * It creates an Request Fetcher instance and handles requests within that instance\n * It handles errors depending on a chosen error handling strategy\n */\nexport class RequestHandler {\n /**\n * @var requestInstance Provider's instance\n */\n public requestInstance: FetcherInstance;\n\n /**\n * @var baseURL Base API url\n */\n public baseURL: string = '';\n\n /**\n * @var timeout Request timeout\n */\n public timeout: number = 30000;\n\n /**\n * @var cancellable Response cancellation\n */\n public cancellable: boolean = false;\n\n /**\n * @var rejectCancelled Whether to reject cancelled requests or not\n */\n public rejectCancelled: boolean = false;\n\n /**\n * @var strategy Request timeout\n */\n public strategy: ErrorHandlingStrategy = 'reject';\n\n /**\n * @var method Request method\n */\n public method: Method | string = 'get';\n\n /**\n * @var flattenResponse Response flattening\n */\n public flattenResponse: boolean = false;\n\n /**\n * @var defaultResponse Response flattening\n */\n public defaultResponse: any = null;\n\n /**\n * @var fetcher Request Fetcher instance\n */\n protected fetcher: FetcherInstance;\n\n /**\n * @var logger Logger\n */\n protected logger: any;\n\n /**\n * @var requestErrorService HTTP error service\n */\n protected onError: any;\n\n /**\n * @var requestsQueue Queue of requests\n */\n protected requestsQueue: WeakMap;\n\n /**\n * Request Handler Config\n */\n protected retry: RetryOptions = {\n retries: 0,\n delay: 1000,\n maxDelay: 30000,\n backoff: 1.5,\n\n // https://developer.mozilla.org/en-US/docs/Web/HTTP/Status\n retryOn: [\n 408, // Request Timeout\n 409, // Conflict\n 425, // Too Early\n 429, // Too Many Requests\n 500, // Internal Server Error\n 502, // Bad Gateway\n 503, // Service Unavailable\n 504, // Gateway Timeout\n ],\n\n shouldRetry: async () => true,\n };\n\n /**\n * Request Handler Config\n */\n public config: RequestHandlerConfig;\n\n /**\n * Creates an instance of RequestHandler.\n *\n * @param {Object} config - Configuration object for the request.\n * @param {string} config.baseURL - The base URL for the request.\n * @param {Object} config.endpoints - An object containing endpoint definitions.\n * @param {number} config.timeout - You can set the timeout for particular request in milliseconds.\n * @param {number} config.cancellable - If true, the previous requests will be automatically cancelled.\n * @param {number} config.rejectCancelled - If true and request is set to cancellable, a cancelled request promise will be rejected. By default, instead of rejecting the promise, defaultResponse is returned.\n * @param {number} config.timeout - Request timeout\n * @param {string} config.strategy - Error Handling Strategy\n * @param {string} config.flattenResponse - Whether to flatten response \"data\" object within \"data\" one\n * @param {*} config.defaultResponse - Default response when there is no data or when endpoint fails depending on the chosen strategy. It's \"null\" by default\n * @param {Object} [config.retry] - Options for retrying requests.\n * @param {number} [config.retry.retries=0] - Number of retry attempts. No retries by default.\n * @param {number} [config.retry.delay=1000] - Initial delay between retries in milliseconds.\n * @param {number} [config.retry.backoff=1.5] - Exponential backoff factor.\n * @param {number[]} [config.retry.retryOn=[502, 504, 408]] - HTTP status codes to retry on.\n * @param {RequestInterceptor|RequestInterceptor[]} [config.onRequest] - Optional request interceptor function or an array of functions.\n * These functions will be called with the request configuration object before the request is made. Can be used to modify or log the request configuration.\n * @param {ResponseInterceptor|ResponseInterceptor[]} [config.onResponse] - Optional response interceptor function or an array of functions.\n * These functions will be called with the response object after the response is received. an be used to modify or log the response data.\n * @param {Function} [config.onError] - Optional callback function for handling errors.\n * @param {Object} [config.headers] - Optional default headers to include in every request.\n * @param {Object} config.fetcher - The Axios (or any other) instance to use for making requests.\n * @param {*} config.logger - Instance of custom logger. Either class or an object similar to \"console\". Console is used by default.\n */\n public constructor({\n fetcher = null,\n timeout = null,\n rejectCancelled = false,\n strategy = null,\n flattenResponse = null,\n defaultResponse = {},\n logger = null,\n onError = null,\n ...config\n }: RequestHandlerConfig) {\n this.fetcher = fetcher;\n this.timeout =\n timeout !== null && timeout !== undefined ? timeout : this.timeout;\n this.strategy =\n strategy !== null && strategy !== undefined ? strategy : this.strategy;\n this.cancellable = config.cancellable || this.cancellable;\n this.rejectCancelled = rejectCancelled || this.rejectCancelled;\n this.flattenResponse =\n flattenResponse !== null && flattenResponse !== undefined\n ? flattenResponse\n : this.flattenResponse;\n this.defaultResponse = defaultResponse;\n this.logger = logger || (globalThis ? globalThis.console : null) || null;\n this.onError = onError;\n this.requestsQueue = new WeakMap();\n this.baseURL = config.baseURL || config.apiUrl || '';\n this.method = config.method || this.method;\n this.config = config;\n this.retry = {\n ...this.retry,\n ...(config.retry || {}),\n };\n\n this.requestInstance = this.isCustomFetcher()\n ? (fetcher as any).create({\n ...config,\n baseURL: this.baseURL,\n timeout: this.timeout,\n })\n : null;\n }\n\n /**\n * Get Provider Instance\n *\n * @returns {FetcherInstance} Provider's instance\n */\n public getInstance(): FetcherInstance {\n return this.requestInstance;\n }\n\n /**\n * Replaces dynamic URI parameters in a URL string with values from the provided `urlPathParams` object.\n * Parameters in the URL are denoted by `:`, where `` is a key in `urlPathParams`.\n *\n * @param {string} url - The URL string containing placeholders in the format `:`.\n * @param {Object} urlPathParams - An object containing the parameter values to replace placeholders.\n * @param {string} urlPathParams.paramName - The value to replace the placeholder `:` in the URL.\n * @returns {string} - The URL string with placeholders replaced by corresponding values from `urlPathParams`.\n */\n public replaceUrlPathParams(\n url: string,\n urlPathParams: UrlPathParams,\n ): string {\n if (!urlPathParams) {\n return url;\n }\n\n return url.replace(/:[a-zA-Z]+/gi, (str): string => {\n const word = str.substring(1);\n\n return String(urlPathParams[word] ? urlPathParams[word] : str);\n });\n }\n\n /**\n * Appends query parameters to the given URL\n *\n * @param {string} url - The base URL to which query parameters will be appended.\n * @param {QueryParams} params - An instance of URLSearchParams containing the query parameters to append.\n * @returns {string} - The URL with the appended query parameters.\n */\n public appendQueryParams(url: string, params: QueryParams): string {\n if (!params) {\n return url;\n }\n\n // We don't use URLSearchParams here as we want to ensure that arrays are properly converted similarily to Axios\n // So { foo: [1, 2] } would become: foo[]=1&foo[]=2\n const queryString = Object.entries(params)\n .flatMap(([key, value]) => {\n if (Array.isArray(value)) {\n return value.map(\n (val) => `${encodeURIComponent(key)}[]=${encodeURIComponent(val)}`,\n );\n }\n return `${encodeURIComponent(key)}=${encodeURIComponent(String(value))}`;\n })\n .join('&');\n\n return url.includes('?')\n ? `${url}&${queryString}`\n : queryString\n ? `${url}?${queryString}`\n : url;\n }\n\n /**\n * Checks if a value is JSON serializable.\n *\n * JSON serializable values include:\n * - Primitive types: string, number, boolean, null\n * - Arrays\n * - Plain objects (i.e., objects without special methods)\n * - Values with a `toJSON` method\n *\n * @param {any} value - The value to check for JSON serializability.\n * @returns {boolean} - Returns `true` if the value is JSON serializable, otherwise `false`.\n */\n protected isJSONSerializable(value: any): boolean {\n if (value === undefined || value === null) {\n return false;\n }\n\n const t = typeof value;\n if (t === 'string' || t === 'number' || t === 'boolean') {\n return true;\n }\n\n if (t !== 'object') {\n return false; // bigint, function, symbol, undefined\n }\n\n if (Array.isArray(value)) {\n return true;\n }\n\n if (Buffer.isBuffer(value)) {\n return false;\n }\n\n if (value instanceof Date) {\n return false;\n }\n\n const proto = Object.getPrototypeOf(value);\n\n // Check if the prototype is `Object.prototype` or `null` (plain object)\n if (proto === Object.prototype || proto === null) {\n return true;\n }\n\n // Check if the object has a toJSON method\n if (typeof value.toJSON === 'function') {\n return true;\n }\n\n return false;\n }\n\n /**\n * Build request configuration\n *\n * @param {string} url Request url\n * @param {QueryParamsOrBody} data Request data\n * @param {RequestConfig} config Request config\n * @returns {RequestConfig} Provider's instance\n */\n protected buildConfig(\n url: string,\n data: QueryParamsOrBody,\n config: RequestConfig,\n ): RequestConfig {\n const method = config.method || this.method;\n const methodLowerCase = method.toLowerCase();\n const isGetAlikeMethod =\n methodLowerCase === 'get' || methodLowerCase === 'head';\n\n const dynamicUrl = this.replaceUrlPathParams(\n url,\n config.urlPathParams || this.config.urlPathParams,\n );\n\n // Bonus: Specifying it here brings support for \"body\" in Axios\n const configData =\n config.body || config.data || this.config.body || this.config.data;\n\n // Axios compatibility\n if (this.isCustomFetcher()) {\n return {\n ...config,\n url: dynamicUrl,\n method: methodLowerCase,\n\n ...(isGetAlikeMethod ? { params: data } : {}),\n\n // For POST requests body payload is the first param for convenience (\"data\")\n // In edge cases we want to split so to treat it as query params, and use \"body\" coming from the config instead\n ...(!isGetAlikeMethod && data && configData ? { params: data } : {}),\n\n // Only applicable for request methods 'PUT', 'POST', 'DELETE', and 'PATCH'\n ...(!isGetAlikeMethod && data && !configData ? { data } : {}),\n ...(!isGetAlikeMethod && configData ? { data: configData } : {}),\n };\n }\n\n // Native fetch\n const payload = configData || data;\n\n delete config.data;\n\n const urlPath =\n (!isGetAlikeMethod && data && !config.body) || !data\n ? dynamicUrl\n : this.appendQueryParams(dynamicUrl, data);\n const isFullUrl = urlPath.includes('://');\n const baseURL = isFullUrl\n ? ''\n : typeof config.baseURL !== 'undefined'\n ? config.baseURL\n : this.baseURL;\n\n return {\n ...config,\n\n // Native fetch generally requires query params to be appended in the URL\n // Do not append query params only if it's a POST-alike request with only \"data\" specified as it's treated as body payload\n url: baseURL + urlPath,\n\n // Uppercase method name\n method: method.toUpperCase(),\n\n // For convenience, add the same default headers as Axios does\n headers: {\n Accept: 'application/json, text/plain, */*',\n 'Content-Type': 'application/json;charset=utf-8',\n ...(config.headers || this.config.headers || {}),\n } as RequestConfigHeaders,\n\n // Automatically JSON stringify request bodies, if possible and when not dealing with strings\n ...(!isGetAlikeMethod\n ? {\n body: this.isJSONSerializable(payload)\n ? typeof payload === 'string'\n ? payload\n : JSON.stringify(payload)\n : payload,\n }\n : {}),\n };\n }\n\n /**\n * Process global Request Error\n *\n * @param {RequestErrorResponse} error Error instance\n * @param {RequestConfig} requestConfig Per endpoint request config\n * @returns {void}\n */\n protected processError(\n error: RequestErrorResponse,\n requestConfig: RequestConfig,\n ): void {\n if (this.isRequestCancelled(error)) {\n return;\n }\n\n // Invoke per request \"onError\" call\n if (requestConfig.onError && typeof requestConfig.onError === 'function') {\n requestConfig.onError(error);\n }\n\n const errorHandler = new RequestErrorHandler(this.logger, this.onError);\n\n errorHandler.process(error);\n }\n\n /**\n * Output default response in case of an error, depending on chosen strategy\n *\n * @param {RequestErrorResponse} error Error instance\n * @param {RequestConfig} requestConfig Per endpoint request config\n * @returns {*} Error response\n */\n protected async outputErrorResponse(\n error: RequestErrorResponse,\n requestConfig: RequestConfig,\n ): Promise {\n const isRequestCancelled = this.isRequestCancelled(error);\n const errorHandlingStrategy = requestConfig.strategy || this.strategy;\n const rejectCancelled =\n typeof requestConfig.rejectCancelled !== 'undefined'\n ? requestConfig.rejectCancelled\n : this.rejectCancelled;\n const defaultResponse =\n typeof requestConfig.defaultResponse !== 'undefined'\n ? requestConfig.defaultResponse\n : this.defaultResponse;\n\n // By default cancelled requests aren't rejected\n if (isRequestCancelled && !rejectCancelled) {\n return defaultResponse;\n }\n\n if (errorHandlingStrategy === 'silent') {\n // Hang the promise\n await new Promise(() => null);\n\n return defaultResponse;\n }\n\n // Simply rejects a request promise\n if (errorHandlingStrategy === 'reject') {\n return Promise.reject(error);\n }\n\n return defaultResponse;\n }\n\n /**\n * Output error response depending on chosen strategy\n *\n * @param {RequestErrorResponse} error Error instance\n * @returns {boolean} True if request is aborted\n */\n public isRequestCancelled(error: RequestErrorResponse): boolean {\n return error.name === 'AbortError' || error.name === 'CanceledError';\n }\n\n /**\n * Detects if a custom fetcher is utilized\n *\n * @returns {boolean} True if it's a custom fetcher\n */\n protected isCustomFetcher(): boolean {\n return this.fetcher !== null;\n }\n\n /**\n * Automatically Cancel Previous Requests using AbortController when \"cancellable\" is defined\n *\n * @param {RequestConfig} requestConfig Per endpoint request config\n * @returns {Object} Controller Signal to abort\n */\n protected addCancellationToken(\n requestConfig: RequestConfig,\n ): Partial> {\n // Both disabled\n if (!this.cancellable && !requestConfig.cancellable) {\n return {};\n }\n\n // Explicitly disabled per request\n if (\n typeof requestConfig.cancellable !== 'undefined' &&\n !requestConfig.cancellable\n ) {\n return {};\n }\n\n // Check if AbortController is available\n if (typeof AbortController === 'undefined') {\n console.error('AbortController is unavailable.');\n\n return {};\n }\n\n // Generate unique key as a cancellation token\n const previousRequest = this.requestsQueue.get(requestConfig);\n\n if (previousRequest) {\n previousRequest.abort();\n }\n\n const controller = new AbortController();\n\n // Introduce timeout for native fetch\n if (!this.isCustomFetcher()) {\n const abortTimeout = setTimeout(() => {\n const error = new Error(\n `[TimeoutError]: The ${requestConfig.url} request was aborted due to timeout`,\n );\n\n error.name = 'TimeoutError';\n (error as any).code = 23; // DOMException.TIMEOUT_ERR\n controller.abort(error);\n clearTimeout(abortTimeout);\n throw error;\n }, requestConfig.timeout || this.timeout);\n }\n\n this.requestsQueue.set(requestConfig, controller);\n\n return {\n signal: controller.signal,\n };\n }\n\n /**\n * Handle Request depending on used strategy\n *\n * @param {string} url - Request url\n * @param {QueryParamsOrBody} data - Request data\n * @param {RequestConfig} config - Request config\n * @param {RequestConfig} payload.config Request config\n * @throws {RequestErrorResponse}\n * @returns {Promise>} Response Data\n */\n public async request(\n url: string,\n data: QueryParamsOrBody = null,\n config: RequestConfig = null,\n ): Promise> {\n let response: Response | FetchResponse = null;\n const _config = config || {};\n const _requestConfig = this.buildConfig(url, data, _config);\n\n let requestConfig: RequestConfig = {\n ...this.addCancellationToken(_requestConfig),\n ..._requestConfig,\n };\n\n const { retries, delay, backoff, retryOn, shouldRetry, maxDelay } = {\n ...this.retry,\n ...(requestConfig?.retry || {}),\n };\n\n let attempt = 0;\n let waitTime = delay;\n\n while (attempt <= retries) {\n try {\n // Local interceptors\n requestConfig = await interceptRequest(\n requestConfig,\n requestConfig.onRequest,\n );\n\n // Global interceptors\n requestConfig = await interceptRequest(\n requestConfig,\n this.config.onRequest,\n );\n\n // Axios compatibility\n if (this.isCustomFetcher()) {\n response = (await (this.requestInstance as any).request(\n requestConfig,\n )) as FetchResponse;\n } else {\n response = (await globalThis.fetch(\n requestConfig.url,\n requestConfig,\n )) as ExtendedResponse;\n\n // Add more information to response object\n response.config = requestConfig;\n\n // Check if the response status is not outside the range 200-299\n if (response.ok) {\n const contentType = String(\n response?.headers?.get('Content-Type') || '',\n );\n let data = null;\n\n // Handle edge case of no content type being provided... We assume json here.\n if (!contentType) {\n try {\n data = await response.json();\n // eslint-disable-next-line @typescript-eslint/no-unused-vars\n } catch (_error) {\n //\n }\n }\n\n if (!data) {\n if (contentType && contentType.includes('application/json')) {\n // Parse JSON response\n data = await response.json();\n } else if (typeof response.text !== 'undefined') {\n data = await response.text();\n } else if (typeof response.blob !== 'undefined') {\n data = await response.blob();\n } else {\n // Handle streams\n data = response.body || response.data || null;\n }\n }\n\n response.data = data;\n } else {\n response.data = null;\n\n // Output error in similar format to what Axios does\n throw new RequestError(\n `fetchf() Request Failed! Status: ${response.status || null}`,\n requestConfig,\n response,\n );\n }\n }\n\n // Local interceptors\n response = await interceptResponse(response, requestConfig.onResponse);\n\n // Global interceptors\n response = await interceptResponse(response, this.config.onResponse);\n\n return this.processResponseData(response, requestConfig);\n } catch (error) {\n if (\n attempt === retries ||\n !(await shouldRetry(error, attempt)) ||\n !retryOn?.includes(\n (response as FetchResponse)?.status ||\n error?.response?.status ||\n error?.status,\n )\n ) {\n this.processError(error, requestConfig);\n\n return this.outputErrorResponse(error, requestConfig);\n }\n\n if (this.logger?.warn) {\n this.logger.warn(\n `Attempt ${attempt + 1} failed. Retrying in ${waitTime}ms...`,\n );\n }\n\n await this.delay(waitTime);\n\n waitTime *= backoff;\n waitTime = Math.min(waitTime, maxDelay);\n attempt++;\n }\n }\n\n return this.processResponseData(response, requestConfig);\n }\n\n public async delay(ms: number): Promise {\n return new Promise((resolve) =>\n setTimeout(() => {\n return resolve(true);\n }, ms),\n );\n }\n\n /**\n * Process response\n *\n * @param response - Response payload\n * @param {RequestConfig} requestConfig - Request config\n * @returns {*} Response data\n */\n protected processResponseData(response, requestConfig: RequestConfig) {\n const defaultResponse =\n typeof requestConfig.defaultResponse !== 'undefined'\n ? requestConfig.defaultResponse\n : this.defaultResponse;\n\n if (!response) {\n return defaultResponse;\n }\n\n if (\n (requestConfig.flattenResponse || this.flattenResponse) &&\n typeof response.data !== 'undefined'\n ) {\n // Special case of only data property within response data object (happens in Axios)\n // This is in fact a proper response but we may want to flatten it\n // To ease developers' lives when obtaining the response\n if (\n typeof response.data === 'object' &&\n typeof response.data.data !== 'undefined' &&\n Object.keys(response.data).length === 1\n ) {\n return response.data.data;\n }\n\n return response.data;\n }\n\n // If empty object is returned, ensure that the default response is used instead\n if (\n typeof response === 'object' &&\n response.constructor === Object &&\n Object.keys(response).length === 0\n ) {\n return defaultResponse;\n }\n\n // For fetch()\n const isCustomFetcher = this.isCustomFetcher();\n\n if (!isCustomFetcher) {\n return {\n ...response,\n headers: Array.from(response?.headers?.entries() || {}).reduce(\n (acc, [key, value]) => {\n acc[key] = value;\n return acc;\n },\n {},\n ),\n config: requestConfig,\n };\n }\n\n return response;\n }\n}\n","import { RequestHandler } from './request-handler';\nimport type {\n FetcherInstance,\n RequestConfig,\n FetchResponse,\n} from './types/request-handler';\nimport type {\n ApiHandlerConfig,\n ApiHandlerMethods,\n ApiHandlerReturnType,\n APIResponse,\n QueryParams,\n UrlPathParams,\n} from './types/api-handler';\n\n/**\n * Creates an instance of API Handler.\n * It creates an API fetcher function using native fetch() or Axios if it is passed as \"fetcher\".\n *\n * @param {Object} config - Configuration object for the API fetcher.\n * @param {string} config.apiUrl - The base URL for the API.\n * @param {Object} config.endpoints - An object containing endpoint definitions.\n * @param {number} config.timeout - You can set the timeout for particular request in milliseconds.\n * @param {number} config.cancellable - If true, the previous requests will be automatically cancelled.\n * @param {number} config.rejectCancelled - If true and request is set to cancellable, a cancelled request promise will be rejected. By default, instead of rejecting the promise, defaultResponse is returned.\n * @param {number} config.timeout - Request timeout\n * @param {string} config.strategy - Error Handling Strategy\n * @param {string} config.flattenResponse - Whether to flatten response \"data\" object within \"data\" one\n * @param {*} config.defaultResponse - Default response when there is no data or when endpoint fails depending on the chosen strategy. It's \"null\" by default\n * @param {Object} [config.retry] - Options for retrying requests.\n * @param {number} [config.retry.retries=0] - Number of retry attempts. No retries by default.\n * @param {number} [config.retry.delay=1000] - Initial delay between retries in milliseconds.\n * @param {number} [config.retry.backoff=1.5] - Exponential backoff factor.\n * @param {number[]} [config.retry.retryOn=[502, 504, 408]] - HTTP status codes to retry on.\n * @param {RequestInterceptor|RequestInterceptor[]} [config.onRequest] - Optional request interceptor function or an array of functions.\n * These functions will be called with the request configuration object before the request is made. Can be used to modify or log the request configuration.\n * @param {ResponseInterceptor|ResponseInterceptor[]} [config.onResponse] - Optional response interceptor function or an array of functions.\n * These functions will be called with the response object after the response is received. an be used to modify or log the response data.\n * @param {Function} [config.onError] - Optional callback function for handling errors.\n * @param {Object} [config.headers] - Optional default headers to include in every request.\n * @param {Object} config.fetcher - The Axios (or any other) instance to use for making requests.\n * @param {*} config.logger - Instance of custom logger. Either class or an object similar to \"console\". Console is used by default.\n * @returns API handler functions and endpoints to call\n *\n * @example\n * // Import axios (optional)\n * import axios from 'axios';\n *\n * // Define endpoint paths\n * const endpoints = {\n * getUser: '/user',\n * createPost: '/post',\n * };\n *\n * // Create the API fetcher with configuration\n * const api = createApiFetcher({\n * fetcher: axios, // Axios instance (optional)\n * endpoints,\n * apiUrl: 'https://example.com/api',\n * onError(error) {\n * console.log('Request failed', error);\n * },\n * headers: {\n * 'my-auth-key': 'example-auth-key-32rjjfa',\n * },\n * });\n *\n * // Fetch user data\n * const response = await api.getUser({ userId: 1, ratings: [1, 2] })\n */\nfunction createApiFetcher(\n config: ApiHandlerConfig,\n) {\n const endpoints = config.endpoints;\n const requestHandler = new RequestHandler(config);\n\n /**\n * Get Fetcher Provider Instance\n *\n * @returns {FetcherInstance} Request Handler's Fetcher instance\n */\n function getInstance(): FetcherInstance {\n return requestHandler.getInstance();\n }\n\n /**\n * Triggered when trying to use non-existent endpoints\n *\n * @param endpointName Endpoint Name\n * @returns {Promise}\n */\n function handleNonImplemented(endpointName: string): Promise {\n console.error(`${endpointName} endpoint must be added to 'endpoints'.`);\n\n return Promise.resolve(null);\n }\n\n /**\n * Handle Single API Request\n * It considers settings in following order: per-request settings, global per-endpoint settings, global settings.\n *\n * @param {string} endpointName - The name of the API endpoint to call.\n * @param {QueryParams} [queryParams={}] - Query parameters to include in the request.\n * @param {UrlPathParams} [urlPathParams={}] - URI parameters to include in the request.\n * @param {EndpointConfig} [requestConfig={}] - Additional configuration for the request.\n * @returns {Promise} - A promise that resolves with the response from the API provider.\n */\n async function request(\n endpointName: keyof EndpointsMethods | string,\n queryParams: QueryParams = {},\n urlPathParams: UrlPathParams = {},\n requestConfig: RequestConfig = {},\n ): Promise> {\n // Use global per-endpoint settings\n const endpointConfig = endpoints[endpointName as string];\n const endpointSettings = { ...endpointConfig };\n\n const responseData = await requestHandler.request(\n endpointSettings.url,\n queryParams,\n {\n ...endpointSettings,\n ...requestConfig,\n urlPathParams,\n },\n );\n\n return responseData;\n }\n\n /**\n * Maps all API requests using native Proxy\n *\n * @param {*} prop Caller\n */\n function get(prop: string | symbol) {\n if (prop in apiHandler) {\n return apiHandler[prop];\n }\n\n // Prevent handler from triggering non-existent endpoints\n if (!endpoints[prop as string]) {\n return handleNonImplemented.bind(null, prop);\n }\n\n return apiHandler.request.bind(null, prop);\n }\n\n const apiHandler: ApiHandlerMethods = {\n config,\n endpoints,\n requestHandler,\n getInstance,\n request,\n };\n\n return new Proxy(apiHandler, {\n get: (_target, prop) => get(prop),\n }) as ApiHandlerReturnType;\n}\n\nexport { createApiFetcher };\n","import { RequestHandler } from './request-handler';\nimport type { APIResponse, FetchResponse, RequestHandlerConfig } from './types';\n\n/**\n * Simple wrapper for request fetching.\n * It abstracts the creation of RequestHandler, making it easy to perform API requests.\n *\n * @param {string | URL | globalThis.Request} url - Request URL.\n * @param {Object} config - Configuration object for the request handler.\n * @returns {Promise} Response Data.\n */\nexport async function fetchf(\n url: string,\n config: RequestHandlerConfig = {},\n): Promise> {\n return new RequestHandler(config).request(\n url,\n config.body || config.data || config.params,\n config,\n );\n}\n\nexport * from './types';\nexport * from './api-handler';\n"],"mappings":"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","names":["RequestErrorHandler","logger","requestErrorService","error","_a","errorContext","RequestError","_RequestError","message","requestInfo","response","interceptRequest","config","interceptors","interceptorList","interceptedConfig","interceptor","interceptResponse","response","interceptedResponse","RequestHandler","fetcher","timeout","rejectCancelled","strategy","flattenResponse","defaultResponse","logger","onError","config","url","urlPathParams","str","word","params","queryString","key","value","val","proto","data","method","methodLowerCase","isGetAlikeMethod","dynamicUrl","configData","payload","urlPath","baseURL","error","requestConfig","RequestErrorHandler","isRequestCancelled","errorHandlingStrategy","previousRequest","controller","abortTimeout","_a","_b","_c","response","_config","_requestConfig","retries","delay","backoff","retryOn","shouldRetry","maxDelay","attempt","waitTime","interceptRequest","contentType","RequestError","interceptResponse","ms","resolve","acc","createApiFetcher","config","endpoints","requestHandler","RequestHandler","getInstance","handleNonImplemented","endpointName","request","queryParams","urlPathParams","requestConfig","endpointSettings","get","prop","apiHandler","_target","fetchf","url","config","RequestHandler"]} \ No newline at end of file +{"version":3,"sources":["../src/interceptor-manager.ts","../src/response-error.ts","../src/request-handler.ts","../src/api-handler.ts","../src/index.ts"],"sourcesContent":["import type { RequestHandlerConfig, FetchResponse } from './types';\nimport type {\n RequestInterceptor,\n ResponseInterceptor,\n} from './types/interceptor-manager';\n\n/**\n * Applies a series of request interceptors to the provided configuration.\n * @param {RequestHandlerConfig} config - The initial request configuration.\n * @param {RequestInterceptor | RequestInterceptor[]} interceptors - The request interceptor function(s) to apply.\n * @returns {Promise} - The modified request configuration.\n */\nexport async function interceptRequest(\n config: RequestHandlerConfig,\n interceptors: RequestInterceptor | RequestInterceptor[],\n): Promise {\n if (!interceptors) {\n return config;\n }\n\n const interceptorList = Array.isArray(interceptors)\n ? interceptors\n : [interceptors];\n\n let interceptedConfig = { ...config };\n\n for (const interceptor of interceptorList) {\n interceptedConfig = await interceptor(interceptedConfig);\n }\n\n return interceptedConfig;\n}\n\n/**\n * Applies a series of response interceptors to the provided response.\n * @param {FetchResponse} response - The initial response object.\n * @param {ResponseInterceptor | ResponseInterceptor[]} interceptors - The response interceptor function(s) to apply.\n * @returns {Promise>} - The modified response object.\n */\nexport async function interceptResponse(\n response: FetchResponse,\n interceptors: ResponseInterceptor | ResponseInterceptor[],\n): Promise> {\n if (!interceptors) {\n return response;\n }\n\n const interceptorList = Array.isArray(interceptors)\n ? interceptors\n : [interceptors];\n\n let interceptedResponse = response;\n\n for (const interceptor of interceptorList) {\n interceptedResponse = await interceptor(interceptedResponse);\n }\n\n return interceptedResponse;\n}\n","import type { FetchResponse, RequestConfig } from './types';\n\nexport class ResponseErr extends Error {\n response: FetchResponse;\n request: RequestConfig;\n config: RequestConfig;\n status: number;\n statusText: string;\n\n constructor(\n message: string,\n requestInfo: RequestConfig,\n response: FetchResponse,\n ) {\n super(message);\n\n this.name = 'ResponseError';\n this.message = message;\n this.status = response.status;\n this.statusText = response.statusText;\n this.request = requestInfo;\n this.config = requestInfo;\n this.response = response;\n }\n}\n","/* eslint-disable @typescript-eslint/no-explicit-any */\nimport type {\n ErrorHandlingStrategy,\n RequestHandlerConfig,\n RequestConfig,\n FetcherInstance,\n Method,\n RetryOptions,\n FetchResponse,\n ResponseError,\n HeadersObject,\n} from './types/request-handler';\nimport type {\n APIResponse,\n QueryParams,\n QueryParamsOrBody,\n UrlPathParams,\n} from './types/api-handler';\nimport { interceptRequest, interceptResponse } from './interceptor-manager';\nimport { ResponseErr } from './response-error';\n\n/**\n * Generic Request Handler\n * It creates an Request Fetcher instance and handles requests within that instance\n * It handles errors depending on a chosen error handling strategy\n */\nexport class RequestHandler {\n /**\n * @var requestInstance Provider's instance\n */\n public requestInstance: FetcherInstance;\n\n /**\n * @var baseURL Base API url\n */\n public baseURL: string = '';\n\n /**\n * @var timeout Request timeout\n */\n public timeout: number = 30000;\n\n /**\n * @var cancellable Response cancellation\n */\n public cancellable: boolean = false;\n\n /**\n * @var rejectCancelled Whether to reject cancelled requests or not\n */\n public rejectCancelled: boolean = false;\n\n /**\n * @var strategy Request timeout\n */\n public strategy: ErrorHandlingStrategy = 'reject';\n\n /**\n * @var method Request method\n */\n public method: Method | string = 'get';\n\n /**\n * @var flattenResponse Response flattening\n */\n public flattenResponse: boolean = false;\n\n /**\n * @var defaultResponse Response flattening\n */\n public defaultResponse: any = null;\n\n /**\n * @var fetcher Request Fetcher instance\n */\n protected fetcher: FetcherInstance;\n\n /**\n * @var logger Logger\n */\n protected logger: any;\n\n /**\n * @var onError HTTP error service\n */\n protected onError: any;\n\n /**\n * @var requestsQueue Queue of requests\n */\n protected requestsQueue: WeakMap;\n\n /**\n * Request Handler Config\n */\n protected retry: RetryOptions = {\n retries: 0,\n delay: 1000,\n maxDelay: 30000,\n backoff: 1.5,\n\n // https://developer.mozilla.org/en-US/docs/Web/HTTP/Status\n retryOn: [\n 408, // Request Timeout\n 409, // Conflict\n 425, // Too Early\n 429, // Too Many Requests\n 500, // Internal Server Error\n 502, // Bad Gateway\n 503, // Service Unavailable\n 504, // Gateway Timeout\n ],\n\n shouldRetry: async () => true,\n };\n\n /**\n * Request Handler Config\n */\n public config: RequestHandlerConfig;\n\n /**\n * Creates an instance of RequestHandler.\n *\n * @param {Object} config - Configuration object for the request.\n * @param {string} config.baseURL - The base URL for the request.\n * @param {Object} config.endpoints - An object containing endpoint definitions.\n * @param {number} config.timeout - You can set the timeout for particular request in milliseconds.\n * @param {number} config.cancellable - If true, the previous requests will be automatically cancelled.\n * @param {number} config.rejectCancelled - If true and request is set to cancellable, a cancelled request promise will be rejected. By default, instead of rejecting the promise, defaultResponse is returned.\n * @param {number} config.timeout - Request timeout\n * @param {string} config.strategy - Error Handling Strategy\n * @param {string} config.flattenResponse - Whether to flatten response \"data\" object within \"data\" one\n * @param {*} config.defaultResponse - Default response when there is no data or when endpoint fails depending on the chosen strategy. It's \"null\" by default\n * @param {Object} [config.retry] - Options for retrying requests.\n * @param {number} [config.retry.retries=0] - Number of retry attempts. No retries by default.\n * @param {number} [config.retry.delay=1000] - Initial delay between retries in milliseconds.\n * @param {number} [config.retry.backoff=1.5] - Exponential backoff factor.\n * @param {number[]} [config.retry.retryOn=[502, 504, 408]] - HTTP status codes to retry on.\n * @param {RequestInterceptor|RequestInterceptor[]} [config.onRequest] - Optional request interceptor function or an array of functions.\n * These functions will be called with the request configuration object before the request is made. Can be used to modify or log the request configuration.\n * @param {ResponseInterceptor|ResponseInterceptor[]} [config.onResponse] - Optional response interceptor function or an array of functions.\n * These functions will be called with the response object after the response is received. an be used to modify or log the response data.\n * @param {Function} [config.onError] - Optional callback function for handling errors.\n * @param {Object} [config.headers] - Optional default headers to include in every request.\n * @param {Object} config.fetcher - The Axios (or any other) instance to use for making requests.\n * @param {*} config.logger - Instance of custom logger. Either class or an object similar to \"console\". Console is used by default.\n */\n public constructor({\n fetcher = null,\n timeout = null,\n rejectCancelled = false,\n strategy = null,\n flattenResponse = null,\n defaultResponse = {},\n logger = null,\n onError = null,\n ...config\n }: RequestHandlerConfig) {\n this.fetcher = fetcher;\n this.timeout =\n timeout !== null && timeout !== undefined ? timeout : this.timeout;\n this.strategy = strategy || this.strategy;\n this.cancellable = config.cancellable || this.cancellable;\n this.rejectCancelled = rejectCancelled || this.rejectCancelled;\n this.flattenResponse = flattenResponse || this.flattenResponse;\n this.defaultResponse = defaultResponse;\n this.logger = logger || (globalThis ? globalThis.console : null) || null;\n this.onError = onError;\n this.requestsQueue = new WeakMap();\n this.baseURL = config.baseURL || config.apiUrl || '';\n this.method = config.method || this.method;\n this.config = config;\n this.retry = {\n ...this.retry,\n ...(config.retry || {}),\n };\n\n this.requestInstance = this.isCustomFetcher()\n ? (fetcher as any).create({\n ...config,\n baseURL: this.baseURL,\n timeout: this.timeout,\n })\n : null;\n }\n\n /**\n * Get Provider Instance\n *\n * @returns {FetcherInstance} Provider's instance\n */\n public getInstance(): FetcherInstance {\n return this.requestInstance;\n }\n\n /**\n * Replaces dynamic URI parameters in a URL string with values from the provided `urlPathParams` object.\n * Parameters in the URL are denoted by `:`, where `` is a key in `urlPathParams`.\n *\n * @param {string} url - The URL string containing placeholders in the format `:`.\n * @param {Object} urlPathParams - An object containing the parameter values to replace placeholders.\n * @param {string} urlPathParams.paramName - The value to replace the placeholder `:` in the URL.\n * @returns {string} - The URL string with placeholders replaced by corresponding values from `urlPathParams`.\n */\n public replaceUrlPathParams(\n url: string,\n urlPathParams: UrlPathParams,\n ): string {\n if (!urlPathParams) {\n return url;\n }\n\n return url.replace(/:[a-zA-Z]+/gi, (str): string => {\n const word = str.substring(1);\n\n return String(urlPathParams[word] ? urlPathParams[word] : str);\n });\n }\n\n /**\n * Appends query parameters to the given URL\n *\n * @param {string} url - The base URL to which query parameters will be appended.\n * @param {QueryParams} params - An instance of URLSearchParams containing the query parameters to append.\n * @returns {string} - The URL with the appended query parameters.\n */\n public appendQueryParams(url: string, params: QueryParams): string {\n if (!params) {\n return url;\n }\n\n // We don't use URLSearchParams here as we want to ensure that arrays are properly converted similarily to Axios\n // So { foo: [1, 2] } would become: foo[]=1&foo[]=2\n const queryString = Object.entries(params)\n .flatMap(([key, value]) => {\n if (Array.isArray(value)) {\n return value.map(\n (val) => `${encodeURIComponent(key)}[]=${encodeURIComponent(val)}`,\n );\n }\n return `${encodeURIComponent(key)}=${encodeURIComponent(String(value))}`;\n })\n .join('&');\n\n return url.includes('?')\n ? `${url}&${queryString}`\n : queryString\n ? `${url}?${queryString}`\n : url;\n }\n\n /**\n * Checks if a value is JSON serializable.\n *\n * JSON serializable values include:\n * - Primitive types: string, number, boolean, null\n * - Arrays\n * - Plain objects (i.e., objects without special methods)\n * - Values with a `toJSON` method\n *\n * @param {any} value - The value to check for JSON serializability.\n * @returns {boolean} - Returns `true` if the value is JSON serializable, otherwise `false`.\n */\n protected isJSONSerializable(value: any): boolean {\n if (value === undefined || value === null) {\n return false;\n }\n\n const t = typeof value;\n if (t === 'string' || t === 'number' || t === 'boolean') {\n return true;\n }\n\n if (t !== 'object') {\n return false; // bigint, function, symbol, undefined\n }\n\n if (Array.isArray(value)) {\n return true;\n }\n\n if (Buffer.isBuffer(value)) {\n return false;\n }\n\n if (value instanceof Date) {\n return false;\n }\n\n const proto = Object.getPrototypeOf(value);\n\n // Check if the prototype is `Object.prototype` or `null` (plain object)\n if (proto === Object.prototype || proto === null) {\n return true;\n }\n\n // Check if the object has a toJSON method\n if (typeof value.toJSON === 'function') {\n return true;\n }\n\n return false;\n }\n\n /**\n * Build request configuration\n *\n * @param {string} url Request url\n * @param {QueryParamsOrBody} data Request data\n * @param {RequestConfig} config Request config\n * @returns {RequestConfig} Provider's instance\n */\n protected buildConfig(\n url: string,\n data: QueryParamsOrBody,\n config: RequestConfig,\n ): RequestConfig {\n const method = config.method || this.method;\n const methodLowerCase = method.toLowerCase();\n const isGetAlikeMethod =\n methodLowerCase === 'get' || methodLowerCase === 'head';\n\n const dynamicUrl = this.replaceUrlPathParams(\n url,\n config.urlPathParams || this.config.urlPathParams,\n );\n\n // Bonus: Specifying it here brings support for \"body\" in Axios\n const configData =\n config.body || config.data || this.config.body || this.config.data;\n\n // Axios compatibility\n if (this.isCustomFetcher()) {\n return {\n ...config,\n url: dynamicUrl,\n method: methodLowerCase,\n\n ...(isGetAlikeMethod ? { params: data } : {}),\n\n // For POST requests body payload is the first param for convenience (\"data\")\n // In edge cases we want to split so to treat it as query params, and use \"body\" coming from the config instead\n ...(!isGetAlikeMethod && data && configData ? { params: data } : {}),\n\n // Only applicable for request methods 'PUT', 'POST', 'DELETE', and 'PATCH'\n ...(!isGetAlikeMethod && data && !configData ? { data } : {}),\n ...(!isGetAlikeMethod && configData ? { data: configData } : {}),\n };\n }\n\n // Native fetch\n const payload = configData || data;\n const credentials =\n config.withCredentials || this.config.withCredentials\n ? 'include'\n : config.credentials;\n\n delete config.data;\n delete config.withCredentials;\n\n const urlPath =\n (!isGetAlikeMethod && data && !config.body) || !data\n ? dynamicUrl\n : this.appendQueryParams(dynamicUrl, data);\n const isFullUrl = urlPath.includes('://');\n const baseURL = isFullUrl\n ? ''\n : typeof config.baseURL !== 'undefined'\n ? config.baseURL\n : this.baseURL;\n\n return {\n ...config,\n credentials,\n\n // Native fetch generally requires query params to be appended in the URL\n // Do not append query params only if it's a POST-alike request with only \"data\" specified as it's treated as body payload\n url: baseURL + urlPath,\n\n // Uppercase method name\n method: method.toUpperCase(),\n\n // For convenience, add the same default headers as Axios does\n headers: {\n Accept: 'application/json, text/plain, */*',\n 'Content-Type': 'application/json;charset=utf-8',\n ...(config.headers || this.config.headers || {}),\n },\n\n // Automatically JSON stringify request bodies, if possible and when not dealing with strings\n ...(!isGetAlikeMethod\n ? {\n body: this.isJSONSerializable(payload)\n ? typeof payload === 'string'\n ? payload\n : JSON.stringify(payload)\n : payload,\n }\n : {}),\n };\n }\n\n /**\n * Process global Request Error\n *\n * @param {ResponseError} error Error instance\n * @param {RequestConfig} requestConfig Per endpoint request config\n * @returns {void}\n */\n protected processError(\n error: ResponseError,\n requestConfig: RequestConfig,\n ): void {\n if (this.isRequestCancelled(error)) {\n return;\n }\n\n if (this.logger?.warn) {\n this.logger.warn('API ERROR', error);\n }\n\n // Invoke per request \"onError\" interceptor\n if (requestConfig.onError && typeof requestConfig.onError === 'function') {\n requestConfig.onError(error);\n }\n\n // Invoke global \"onError\" interceptor\n if (this.onError && typeof this.onError === 'function') {\n this.onError(error);\n }\n }\n\n /**\n * Output default response in case of an error, depending on chosen strategy\n *\n * @param {ResponseError} error Error instance\n * @param {RequestConfig} requestConfig Per endpoint request config\n * @returns {*} Error response\n */\n protected async outputErrorResponse(\n error: ResponseError,\n requestConfig: RequestConfig,\n ): Promise {\n const isRequestCancelled = this.isRequestCancelled(error);\n const errorHandlingStrategy = requestConfig.strategy || this.strategy;\n const rejectCancelled =\n typeof requestConfig.rejectCancelled !== 'undefined'\n ? requestConfig.rejectCancelled\n : this.rejectCancelled;\n const defaultResponse =\n typeof requestConfig.defaultResponse !== 'undefined'\n ? requestConfig.defaultResponse\n : this.defaultResponse;\n\n // Output full response with the error object\n if (errorHandlingStrategy === 'softFail') {\n return this.outputResponse(error.response, requestConfig, error);\n }\n\n // By default cancelled requests aren't rejected\n if (isRequestCancelled && !rejectCancelled) {\n return defaultResponse;\n }\n\n // Hang the promise\n if (errorHandlingStrategy === 'silent') {\n await new Promise(() => null);\n\n return defaultResponse;\n }\n\n // Reject the promise\n if (errorHandlingStrategy === 'reject') {\n return Promise.reject(error);\n }\n\n return defaultResponse;\n }\n\n /**\n * Output error response depending on chosen strategy\n *\n * @param {ResponseError} error Error instance\n * @returns {boolean} True if request is aborted\n */\n public isRequestCancelled(error: ResponseError): boolean {\n return error.name === 'AbortError' || error.name === 'CanceledError';\n }\n\n /**\n * Detects if a custom fetcher is utilized\n *\n * @returns {boolean} True if it's a custom fetcher\n */\n protected isCustomFetcher(): boolean {\n return this.fetcher !== null;\n }\n\n /**\n * Automatically Cancel Previous Requests using AbortController when \"cancellable\" is defined\n *\n * @param {RequestConfig} requestConfig Per endpoint request config\n * @returns {Object} Controller Signal to abort\n */\n protected addCancellationToken(\n requestConfig: RequestConfig,\n ): Partial> {\n // Both disabled\n if (!this.cancellable && !requestConfig.cancellable) {\n return {};\n }\n\n // Explicitly disabled per request\n if (\n typeof requestConfig.cancellable !== 'undefined' &&\n !requestConfig.cancellable\n ) {\n return {};\n }\n\n // Check if AbortController is available\n if (typeof AbortController === 'undefined') {\n console.error('AbortController is unavailable.');\n\n return {};\n }\n\n // Generate unique key as a cancellation token\n const previousRequest = this.requestsQueue.get(requestConfig);\n\n if (previousRequest) {\n previousRequest.abort();\n }\n\n const controller = new AbortController();\n\n // Introduce timeout for native fetch\n if (!this.isCustomFetcher() && this.timeout > 0) {\n const abortTimeout = setTimeout(() => {\n const error = new Error(\n `[TimeoutError]: The ${requestConfig.url} request was aborted due to timeout`,\n );\n\n error.name = 'TimeoutError';\n (error as any).code = 23; // DOMException.TIMEOUT_ERR\n controller.abort(error);\n clearTimeout(abortTimeout);\n throw error;\n }, requestConfig.timeout || this.timeout);\n }\n\n this.requestsQueue.set(requestConfig, controller);\n\n return {\n signal: controller.signal,\n };\n }\n\n /**\n * Handle Request depending on used strategy\n *\n * @param {string} url - Request url\n * @param {QueryParamsOrBody} data - Request data\n * @param {RequestConfig} config - Request config\n * @param {RequestConfig} payload.config Request config\n * @throws {ResponseError}\n * @returns {Promise>} Response Data\n */\n public async request(\n url: string,\n data: QueryParamsOrBody = null,\n config: RequestConfig = null,\n ): Promise> {\n let response: FetchResponse = null;\n const _config = config || {};\n const _requestConfig = this.buildConfig(url, data, _config);\n\n let requestConfig: RequestConfig = {\n ...this.addCancellationToken(_requestConfig),\n ..._requestConfig,\n };\n\n const { retries, delay, backoff, retryOn, shouldRetry, maxDelay } = {\n ...this.retry,\n ...(requestConfig?.retry || {}),\n };\n\n let attempt = 0;\n let waitTime = delay;\n\n while (attempt <= retries) {\n try {\n // Local interceptors\n requestConfig = await interceptRequest(\n requestConfig,\n requestConfig.onRequest,\n );\n\n // Global interceptors\n requestConfig = await interceptRequest(\n requestConfig,\n this.config.onRequest,\n );\n\n // Axios compatibility\n if (this.isCustomFetcher()) {\n response = (await (this.requestInstance as any).request(\n requestConfig,\n )) as FetchResponse;\n } else {\n response = (await globalThis.fetch(\n requestConfig.url,\n requestConfig,\n )) as FetchResponse;\n\n // Attempt to collect response data regardless of response status\n const contentType = String(\n (response as Response)?.headers?.get('Content-Type') || '',\n );\n let data;\n\n // Handle edge case of no content type being provided... We assume json here.\n if (!contentType) {\n try {\n data = await response.json();\n // eslint-disable-next-line @typescript-eslint/no-unused-vars\n } catch (_error) {\n //\n }\n }\n\n if (typeof data === 'undefined') {\n if (contentType && contentType.includes('application/json')) {\n // Parse JSON response\n data = await response.json();\n } else if (typeof response.text !== 'undefined') {\n data = await response.text();\n } else if (typeof response.blob !== 'undefined') {\n data = await response.blob();\n } else {\n // Handle streams\n data = response.body || response.data || null;\n }\n }\n\n // Add more information to response object\n response.config = requestConfig;\n response.data = data;\n\n // Check if the response status is not outside the range 200-299 and if so, output error\n if (!response.ok) {\n throw new ResponseErr(\n `${requestConfig.url} failed! Status: ${response.status || null}`,\n requestConfig,\n response,\n );\n }\n }\n\n // Local interceptors\n response = await interceptResponse(response, requestConfig.onResponse);\n\n // Global interceptors\n response = await interceptResponse(response, this.config.onResponse);\n\n return this.outputResponse(response, requestConfig) as ResponseData &\n FetchResponse;\n } catch (error) {\n if (\n attempt === retries ||\n !(await shouldRetry(error, attempt)) ||\n !retryOn?.includes(error?.response?.status || error?.status)\n ) {\n this.processError(error, requestConfig);\n\n return this.outputErrorResponse(error, requestConfig);\n }\n\n if (this.logger?.warn) {\n this.logger.warn(\n `Attempt ${attempt + 1} failed. Retrying in ${waitTime}ms...`,\n );\n }\n\n await this.delay(waitTime);\n\n waitTime *= backoff;\n waitTime = Math.min(waitTime, maxDelay);\n attempt++;\n }\n }\n\n return this.outputResponse(response, requestConfig) as ResponseData &\n FetchResponse;\n }\n\n public async delay(ms: number): Promise {\n return new Promise((resolve) =>\n setTimeout(() => {\n return resolve(true);\n }, ms),\n );\n }\n\n public processHeaders(\n response: FetchResponse,\n ): HeadersObject {\n if (!response.headers) {\n return {};\n }\n\n let headersObject: HeadersObject = {};\n\n // Handle Headers object with entries() method\n if (response.headers instanceof Headers) {\n for (const [key, value] of (response.headers as any).entries()) {\n headersObject[key] = value;\n }\n } else {\n // Handle plain object\n headersObject = { ...(response.headers as HeadersObject) };\n }\n\n return headersObject;\n }\n\n /**\n * Output response\n *\n * @param response - Response payload\n * @param {RequestConfig} requestConfig - Request config\n * @param {*} error - whether the response is erroneous\n * @returns {ResponseData | FetchResponse} Response data\n */\n protected outputResponse(\n response: FetchResponse,\n requestConfig: RequestConfig,\n error = null,\n ): ResponseData | FetchResponse {\n const defaultResponse =\n typeof requestConfig.defaultResponse !== 'undefined'\n ? requestConfig.defaultResponse\n : this.defaultResponse;\n\n if (!response) {\n return defaultResponse;\n }\n\n if (\n (requestConfig.flattenResponse || this.flattenResponse) &&\n typeof response.data !== 'undefined'\n ) {\n // Special case of only data property within response data object (happens in Axios)\n // This is in fact a proper response but we may want to flatten it\n // To ease developers' lives when obtaining the response\n if (\n response.data !== null &&\n typeof response.data === 'object' &&\n typeof (response.data as any).data !== 'undefined' &&\n Object.keys(response.data).length === 1\n ) {\n return (response.data as any).data;\n }\n\n return response.data;\n }\n\n // If empty object is returned, ensure that the default response is used instead\n if (\n response !== null &&\n typeof response === 'object' &&\n response.constructor === Object &&\n Object.keys(response).length === 0\n ) {\n return defaultResponse;\n }\n\n const isCustomFetcher = this.isCustomFetcher();\n\n if (isCustomFetcher) {\n return response;\n }\n\n if (error !== null) {\n delete error?.response;\n delete error?.request;\n delete error?.config;\n }\n\n // Native fetch()\n return {\n ...response,\n error,\n headers: this.processHeaders(response),\n config: requestConfig,\n };\n }\n}\n","import { RequestHandler } from './request-handler';\nimport type {\n FetcherInstance,\n RequestConfig,\n FetchResponse,\n} from './types/request-handler';\nimport type {\n ApiHandlerConfig,\n ApiHandlerMethods,\n ApiHandlerReturnType,\n APIResponse,\n QueryParams,\n UrlPathParams,\n} from './types/api-handler';\n\n/**\n * Creates an instance of API Handler.\n * It creates an API fetcher function using native fetch() or Axios if it is passed as \"fetcher\".\n *\n * @param {Object} config - Configuration object for the API fetcher.\n * @param {string} config.apiUrl - The base URL for the API.\n * @param {Object} config.endpoints - An object containing endpoint definitions.\n * @param {number} config.timeout - You can set the timeout for particular request in milliseconds.\n * @param {number} config.cancellable - If true, the previous requests will be automatically cancelled.\n * @param {number} config.rejectCancelled - If true and request is set to cancellable, a cancelled request promise will be rejected. By default, instead of rejecting the promise, defaultResponse is returned.\n * @param {number} config.timeout - Request timeout\n * @param {string} config.strategy - Error Handling Strategy\n * @param {string} config.flattenResponse - Whether to flatten response \"data\" object within \"data\" one\n * @param {*} config.defaultResponse - Default response when there is no data or when endpoint fails depending on the chosen strategy. It's \"null\" by default\n * @param {Object} [config.retry] - Options for retrying requests.\n * @param {number} [config.retry.retries=0] - Number of retry attempts. No retries by default.\n * @param {number} [config.retry.delay=1000] - Initial delay between retries in milliseconds.\n * @param {number} [config.retry.backoff=1.5] - Exponential backoff factor.\n * @param {number[]} [config.retry.retryOn=[502, 504, 408]] - HTTP status codes to retry on.\n * @param {RequestInterceptor|RequestInterceptor[]} [config.onRequest] - Optional request interceptor function or an array of functions.\n * These functions will be called with the request configuration object before the request is made. Can be used to modify or log the request configuration.\n * @param {ResponseInterceptor|ResponseInterceptor[]} [config.onResponse] - Optional response interceptor function or an array of functions.\n * These functions will be called with the response object after the response is received. an be used to modify or log the response data.\n * @param {Function} [config.onError] - Optional callback function for handling errors.\n * @param {Object} [config.headers] - Optional default headers to include in every request.\n * @param {Object} config.fetcher - The Axios (or any other) instance to use for making requests.\n * @param {*} config.logger - Instance of custom logger. Either class or an object similar to \"console\". Console is used by default.\n * @returns API handler functions and endpoints to call\n *\n * @example\n * // Import axios (optional)\n * import axios from 'axios';\n *\n * // Define endpoint paths\n * const endpoints = {\n * getUser: '/user',\n * createPost: '/post',\n * };\n *\n * // Create the API fetcher with configuration\n * const api = createApiFetcher({\n * fetcher: axios, // Axios instance (optional)\n * endpoints,\n * apiUrl: 'https://example.com/api',\n * onError(error) {\n * console.log('Request failed', error);\n * },\n * headers: {\n * 'my-auth-key': 'example-auth-key-32rjjfa',\n * },\n * });\n *\n * // Fetch user data\n * const response = await api.getUser({ userId: 1, ratings: [1, 2] })\n */\nfunction createApiFetcher<\n EndpointsMethods extends object,\n EndpointsCfg = never,\n>(config: ApiHandlerConfig) {\n const endpoints = config.endpoints;\n const requestHandler = new RequestHandler(config);\n\n /**\n * Get Fetcher Provider Instance\n *\n * @returns {FetcherInstance} Request Handler's Fetcher instance\n */\n function getInstance(): FetcherInstance {\n return requestHandler.getInstance();\n }\n\n /**\n * Triggered when trying to use non-existent endpoints\n *\n * @param endpointName Endpoint Name\n * @returns {Promise}\n */\n function handleNonImplemented(endpointName: string): Promise {\n console.error(`${endpointName} endpoint must be added to 'endpoints'.`);\n\n return Promise.resolve(null);\n }\n\n /**\n * Handle Single API Request\n * It considers settings in following order: per-request settings, global per-endpoint settings, global settings.\n *\n * @param {string} endpointName - The name of the API endpoint to call.\n * @param {QueryParams} [queryParams={}] - Query parameters to include in the request.\n * @param {UrlPathParams} [urlPathParams={}] - URI parameters to include in the request.\n * @param {EndpointConfig} [requestConfig={}] - Additional configuration for the request.\n * @returns {Promise} - A promise that resolves with the response from the API provider.\n */\n async function request(\n endpointName: keyof EndpointsMethods | string,\n queryParams: QueryParams = {},\n urlPathParams: UrlPathParams = {},\n requestConfig: RequestConfig = {},\n ): Promise> {\n // Use global per-endpoint settings\n const endpointConfig = endpoints[endpointName as string];\n const endpointSettings = { ...endpointConfig };\n\n const responseData = await requestHandler.request(\n endpointSettings.url,\n queryParams,\n {\n ...endpointSettings,\n ...requestConfig,\n urlPathParams,\n },\n );\n\n return responseData;\n }\n\n /**\n * Maps all API requests using native Proxy\n *\n * @param {*} prop Caller\n */\n function get(prop: string | symbol) {\n if (prop in apiHandler) {\n return apiHandler[prop];\n }\n\n // Prevent handler from triggering non-existent endpoints\n if (!endpoints[prop as string]) {\n return handleNonImplemented.bind(null, prop);\n }\n\n return apiHandler.request.bind(null, prop);\n }\n\n const apiHandler: ApiHandlerMethods = {\n config,\n endpoints,\n requestHandler,\n getInstance,\n request,\n };\n\n return new Proxy(apiHandler, {\n get: (_target, prop) => get(prop),\n }) as ApiHandlerReturnType;\n}\n\nexport { createApiFetcher };\n","import { RequestHandler } from './request-handler';\nimport type { APIResponse, FetchResponse, RequestHandlerConfig } from './types';\n\n/**\n * Simple wrapper for request fetching.\n * It abstracts the creation of RequestHandler, making it easy to perform API requests.\n *\n * @param {string | URL | globalThis.Request} url - Request URL.\n * @param {RequestHandlerConfig} config - Configuration object for the request handler.\n * @returns {Promise>} Response Data.\n */\nexport async function fetchf(\n url: string,\n config: RequestHandlerConfig = {},\n): Promise> {\n return new RequestHandler(config).request(\n url,\n config.body || config.data || config.params,\n config,\n );\n}\n\nexport * from './types';\nexport * from './api-handler';\n"],"mappings":"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","names":["interceptRequest","config","interceptors","interceptorList","interceptedConfig","interceptor","interceptResponse","response","interceptedResponse","ResponseErr","message","requestInfo","response","RequestHandler","fetcher","timeout","rejectCancelled","strategy","flattenResponse","defaultResponse","logger","onError","config","url","urlPathParams","str","word","params","queryString","key","value","val","t","proto","data","method","methodLowerCase","isGetAlikeMethod","dynamicUrl","configData","payload","credentials","urlPath","baseURL","error","requestConfig","_a","isRequestCancelled","errorHandlingStrategy","previousRequest","controller","abortTimeout","_b","_c","response","_config","_requestConfig","retries","delay","backoff","retryOn","shouldRetry","maxDelay","attempt","waitTime","interceptRequest","contentType","ResponseErr","interceptResponse","ms","resolve","headersObject","createApiFetcher","config","endpoints","requestHandler","RequestHandler","getInstance","handleNonImplemented","endpointName","request","queryParams","urlPathParams","requestConfig","endpointSettings","get","prop","apiHandler","_target","fetchf","url","config","RequestHandler"]} \ No newline at end of file diff --git a/dist/node/index.js b/dist/node/index.js index eba5355..d559665 100644 --- a/dist/node/index.js +++ b/dist/node/index.js @@ -1,2 +1,2 @@ -var C=Object.defineProperty;var F=Object.getOwnPropertyDescriptor;var j=Object.getOwnPropertyNames;var H=Object.prototype.hasOwnProperty;var S=(i,e)=>{for(var t in e)C(i,t,{get:e[t],enumerable:!0})},U=(i,e,t,s)=>{if(e&&typeof e=="object"||typeof e=="function")for(let r of j(e))!H.call(i,r)&&r!==t&&C(i,r,{get:()=>e[r],enumerable:!(s=F(e,r))||s.enumerable});return i};var x=i=>U(C({},"__esModule",{value:!0}),i);var M={};S(M,{createApiFetcher:()=>T,fetchf:()=>O});module.exports=x(M);var g=class{logger;requestErrorService;constructor(e,t){this.logger=e,this.requestErrorService=t}process(e){var s;(s=this.logger)!=null&&s.warn&&this.logger.warn("API ERROR",e);let t=e;typeof e=="string"&&(t=new Error(e)),this.requestErrorService&&(typeof this.requestErrorService.process<"u"?this.requestErrorService.process(t):typeof this.requestErrorService=="function"&&this.requestErrorService(t))}};var q=class i extends Error{response;request;constructor(e,t,s){super(e),this.name="RequestError",this.message=e,this.request=t,this.response=s,Error.captureStackTrace(this,i)}};async function E(i,e){if(!e)return i;let t=Array.isArray(e)?e:[e],s={...i};for(let r of t)s=await r(s);return s}async function P(i,e){if(!e)return i;let t=Array.isArray(e)?e:[e],s=i;for(let r of t)s=await r(s);return s}var R=class{requestInstance;baseURL="";timeout=3e4;cancellable=!1;rejectCancelled=!1;strategy="reject";method="get";flattenResponse=!1;defaultResponse=null;fetcher;logger;onError;requestsQueue;retry={retries:0,delay:1e3,maxDelay:3e4,backoff:1.5,retryOn:[408,409,425,429,500,502,503,504],shouldRetry:async()=>!0};config;constructor({fetcher:e=null,timeout:t=null,rejectCancelled:s=!1,strategy:r=null,flattenResponse:o=null,defaultResponse:l={},logger:n=null,onError:a=null,...u}){this.fetcher=e,this.timeout=t??this.timeout,this.strategy=r??this.strategy,this.cancellable=u.cancellable||this.cancellable,this.rejectCancelled=s||this.rejectCancelled,this.flattenResponse=o??this.flattenResponse,this.defaultResponse=l,this.logger=n||(globalThis?globalThis.console:null)||null,this.onError=a,this.requestsQueue=new WeakMap,this.baseURL=u.baseURL||u.apiUrl||"",this.method=u.method||this.method,this.config=u,this.retry={...this.retry,...u.retry||{}},this.requestInstance=this.isCustomFetcher()?e.create({...u,baseURL:this.baseURL,timeout:this.timeout}):null}getInstance(){return this.requestInstance}replaceUrlPathParams(e,t){return t?e.replace(/:[a-zA-Z]+/gi,s=>{let r=s.substring(1);return String(t[r]?t[r]:s)}):e}appendQueryParams(e,t){if(!t)return e;let s=Object.entries(t).flatMap(([r,o])=>Array.isArray(o)?o.map(l=>`${encodeURIComponent(r)}[]=${encodeURIComponent(l)}`):`${encodeURIComponent(r)}=${encodeURIComponent(String(o))}`).join("&");return e.includes("?")?`${e}&${s}`:s?`${e}?${s}`:e}isJSONSerializable(e){if(e==null)return!1;let t=typeof e;if(t==="string"||t==="number"||t==="boolean")return!0;if(t!=="object")return!1;if(Array.isArray(e))return!0;if(Buffer.isBuffer(e)||e instanceof Date)return!1;let s=Object.getPrototypeOf(e);return s===Object.prototype||s===null||typeof e.toJSON=="function"}buildConfig(e,t,s){let r=s.method||this.method,o=r.toLowerCase(),l=o==="get"||o==="head",n=this.replaceUrlPathParams(e,s.urlPathParams||this.config.urlPathParams),a=s.body||s.data||this.config.body||this.config.data;if(this.isCustomFetcher())return{...s,url:n,method:o,...l?{params:t}:{},...!l&&t&&a?{params:t}:{},...!l&&t&&!a?{data:t}:{},...!l&&a?{data:a}:{}};let u=a||t;delete s.data;let d=!l&&t&&!s.body||!t?n:this.appendQueryParams(n,t),m=d.includes("://")?"":typeof s.baseURL<"u"?s.baseURL:this.baseURL;return{...s,url:m+d,method:r.toUpperCase(),headers:{Accept:"application/json, text/plain, */*","Content-Type":"application/json;charset=utf-8",...s.headers||this.config.headers||{}},...l?{}:{body:this.isJSONSerializable(u)?typeof u=="string"?u:JSON.stringify(u):u}}}processError(e,t){if(this.isRequestCancelled(e))return;t.onError&&typeof t.onError=="function"&&t.onError(e),new g(this.logger,this.onError).process(e)}async outputErrorResponse(e,t){let s=this.isRequestCancelled(e),r=t.strategy||this.strategy,o=typeof t.rejectCancelled<"u"?t.rejectCancelled:this.rejectCancelled,l=typeof t.defaultResponse<"u"?t.defaultResponse:this.defaultResponse;return s&&!o?l:r==="silent"?(await new Promise(()=>null),l):r==="reject"?Promise.reject(e):l}isRequestCancelled(e){return e.name==="AbortError"||e.name==="CanceledError"}isCustomFetcher(){return this.fetcher!==null}addCancellationToken(e){if(!this.cancellable&&!e.cancellable)return{};if(typeof e.cancellable<"u"&&!e.cancellable)return{};if(typeof AbortController>"u")return console.error("AbortController is unavailable."),{};let t=this.requestsQueue.get(e);t&&t.abort();let s=new AbortController;if(!this.isCustomFetcher()){let r=setTimeout(()=>{let o=new Error(`[TimeoutError]: The ${e.url} request was aborted due to timeout`);throw o.name="TimeoutError",o.code=23,s.abort(o),clearTimeout(r),o},e.timeout||this.timeout)}return this.requestsQueue.set(e,s),{signal:s.signal}}async request(e,t=null,s=null){var w,A,I;let r=null,o=s||{},l=this.buildConfig(e,t,o),n={...this.addCancellationToken(l),...l},{retries:a,delay:u,backoff:d,retryOn:h,shouldRetry:m,maxDelay:b}={...this.retry,...(n==null?void 0:n.retry)||{}},f=0,y=u;for(;f<=a;)try{if(n=await E(n,n.onRequest),n=await E(n,this.config.onRequest),this.isCustomFetcher())r=await this.requestInstance.request(n);else if(r=await globalThis.fetch(n.url,n),r.config=n,r.ok){let c=String(((w=r==null?void 0:r.headers)==null?void 0:w.get("Content-Type"))||""),p=null;if(!c)try{p=await r.json()}catch{}p||(c&&c.includes("application/json")?p=await r.json():typeof r.text<"u"?p=await r.text():typeof r.blob<"u"?p=await r.blob():p=r.body||r.data||null),r.data=p}else throw r.data=null,new q(`fetchf() Request Failed! Status: ${r.status||null}`,n,r);return r=await P(r,n.onResponse),r=await P(r,this.config.onResponse),this.processResponseData(r,n)}catch(c){if(f===a||!await m(c,f)||!(h!=null&&h.includes((r==null?void 0:r.status)||((A=c==null?void 0:c.response)==null?void 0:A.status)||(c==null?void 0:c.status))))return this.processError(c,n),this.outputErrorResponse(c,n);(I=this.logger)!=null&&I.warn&&this.logger.warn(`Attempt ${f+1} failed. Retrying in ${y}ms...`),await this.delay(y),y*=d,y=Math.min(y,b),f++}return this.processResponseData(r,n)}async delay(e){return new Promise(t=>setTimeout(()=>t(!0),e))}processResponseData(e,t){var o;let s=typeof t.defaultResponse<"u"?t.defaultResponse:this.defaultResponse;return e?(t.flattenResponse||this.flattenResponse)&&typeof e.data<"u"?typeof e.data=="object"&&typeof e.data.data<"u"&&Object.keys(e.data).length===1?e.data.data:e.data:typeof e=="object"&&e.constructor===Object&&Object.keys(e).length===0?s:this.isCustomFetcher()?e:{...e,headers:Array.from(((o=e==null?void 0:e.headers)==null?void 0:o.entries())||{}).reduce((l,[n,a])=>(l[n]=a,l),{}),config:t}:s}};function T(i){let e=i.endpoints,t=new R(i);function s(){return t.getInstance()}function r(a){return console.error(`${a} endpoint must be added to 'endpoints'.`),Promise.resolve(null)}async function o(a,u={},d={},h={}){let b={...e[a]};return await t.request(b.url,u,{...b,...h,urlPathParams:d})}function l(a){return a in n?n[a]:e[a]?n.request.bind(null,a):r.bind(null,a)}let n={config:i,endpoints:e,requestHandler:t,getInstance:s,request:o};return new Proxy(n,{get:(a,u)=>l(u)})}async function O(i,e={}){return new R(e).request(i,e.body||e.data||e.params,e)}0&&(module.exports={createApiFetcher,fetchf}); +var C=Object.defineProperty;var E=Object.getOwnPropertyDescriptor;var j=Object.getOwnPropertyNames;var I=Object.prototype.hasOwnProperty;var D=(o,e)=>{for(var t in e)C(o,t,{get:e[t],enumerable:!0})},H=(o,e,t,s)=>{if(e&&typeof e=="object"||typeof e=="function")for(let n of j(e))!I.call(o,n)&&n!==t&&C(o,n,{get:()=>e[n],enumerable:!(s=E(e,n))||s.enumerable});return o};var U=o=>H(C({},"__esModule",{value:!0}),o);var T={};D(T,{createApiFetcher:()=>O,fetchf:()=>x});module.exports=U(T);async function P(o,e){if(!e)return o;let t=Array.isArray(e)?e:[e],s={...o};for(let n of t)s=await n(s);return s}async function q(o,e){if(!e)return o;let t=Array.isArray(e)?e:[e],s=o;for(let n of t)s=await n(s);return s}var b=class extends Error{response;request;config;status;statusText;constructor(e,t,s){super(e),this.name="ResponseError",this.message=e,this.status=s.status,this.statusText=s.statusText,this.request=t,this.config=t,this.response=s}};var R=class{requestInstance;baseURL="";timeout=3e4;cancellable=!1;rejectCancelled=!1;strategy="reject";method="get";flattenResponse=!1;defaultResponse=null;fetcher;logger;onError;requestsQueue;retry={retries:0,delay:1e3,maxDelay:3e4,backoff:1.5,retryOn:[408,409,425,429,500,502,503,504],shouldRetry:async()=>!0};config;constructor({fetcher:e=null,timeout:t=null,rejectCancelled:s=!1,strategy:n=null,flattenResponse:a=null,defaultResponse:l={},logger:r=null,onError:i=null,...u}){this.fetcher=e,this.timeout=t??this.timeout,this.strategy=n||this.strategy,this.cancellable=u.cancellable||this.cancellable,this.rejectCancelled=s||this.rejectCancelled,this.flattenResponse=a||this.flattenResponse,this.defaultResponse=l,this.logger=r||(globalThis?globalThis.console:null)||null,this.onError=i,this.requestsQueue=new WeakMap,this.baseURL=u.baseURL||u.apiUrl||"",this.method=u.method||this.method,this.config=u,this.retry={...this.retry,...u.retry||{}},this.requestInstance=this.isCustomFetcher()?e.create({...u,baseURL:this.baseURL,timeout:this.timeout}):null}getInstance(){return this.requestInstance}replaceUrlPathParams(e,t){return t?e.replace(/:[a-zA-Z]+/gi,s=>{let n=s.substring(1);return String(t[n]?t[n]:s)}):e}appendQueryParams(e,t){if(!t)return e;let s=Object.entries(t).flatMap(([n,a])=>Array.isArray(a)?a.map(l=>`${encodeURIComponent(n)}[]=${encodeURIComponent(l)}`):`${encodeURIComponent(n)}=${encodeURIComponent(String(a))}`).join("&");return e.includes("?")?`${e}&${s}`:s?`${e}?${s}`:e}isJSONSerializable(e){if(e==null)return!1;let t=typeof e;if(t==="string"||t==="number"||t==="boolean")return!0;if(t!=="object")return!1;if(Array.isArray(e))return!0;if(Buffer.isBuffer(e)||e instanceof Date)return!1;let s=Object.getPrototypeOf(e);return s===Object.prototype||s===null||typeof e.toJSON=="function"}buildConfig(e,t,s){let n=s.method||this.method,a=n.toLowerCase(),l=a==="get"||a==="head",r=this.replaceUrlPathParams(e,s.urlPathParams||this.config.urlPathParams),i=s.body||s.data||this.config.body||this.config.data;if(this.isCustomFetcher())return{...s,url:r,method:a,...l?{params:t}:{},...!l&&t&&i?{params:t}:{},...!l&&t&&!i?{data:t}:{},...!l&&i?{data:i}:{}};let u=i||t,y=s.withCredentials||this.config.withCredentials?"include":s.credentials;delete s.data,delete s.withCredentials;let p=!l&&t&&!s.body||!t?r:this.appendQueryParams(r,t),h=p.includes("://")?"":typeof s.baseURL<"u"?s.baseURL:this.baseURL;return{...s,credentials:y,url:h+p,method:n.toUpperCase(),headers:{Accept:"application/json, text/plain, */*","Content-Type":"application/json;charset=utf-8",...s.headers||this.config.headers||{}},...l?{}:{body:this.isJSONSerializable(u)?typeof u=="string"?u:JSON.stringify(u):u}}}processError(e,t){var s;this.isRequestCancelled(e)||((s=this.logger)!=null&&s.warn&&this.logger.warn("API ERROR",e),t.onError&&typeof t.onError=="function"&&t.onError(e),this.onError&&typeof this.onError=="function"&&this.onError(e))}async outputErrorResponse(e,t){let s=this.isRequestCancelled(e),n=t.strategy||this.strategy,a=typeof t.rejectCancelled<"u"?t.rejectCancelled:this.rejectCancelled,l=typeof t.defaultResponse<"u"?t.defaultResponse:this.defaultResponse;return n==="softFail"?this.outputResponse(e.response,t,e):s&&!a?l:n==="silent"?(await new Promise(()=>null),l):n==="reject"?Promise.reject(e):l}isRequestCancelled(e){return e.name==="AbortError"||e.name==="CanceledError"}isCustomFetcher(){return this.fetcher!==null}addCancellationToken(e){if(!this.cancellable&&!e.cancellable)return{};if(typeof e.cancellable<"u"&&!e.cancellable)return{};if(typeof AbortController>"u")return console.error("AbortController is unavailable."),{};let t=this.requestsQueue.get(e);t&&t.abort();let s=new AbortController;if(!this.isCustomFetcher()&&this.timeout>0){let n=setTimeout(()=>{let a=new Error(`[TimeoutError]: The ${e.url} request was aborted due to timeout`);throw a.name="TimeoutError",a.code=23,s.abort(a),clearTimeout(n),a},e.timeout||this.timeout)}return this.requestsQueue.set(e,s),{signal:s.signal}}async request(e,t=null,s=null){var w,F,A;let n=null,a=s||{},l=this.buildConfig(e,t,a),r={...this.addCancellationToken(l),...l},{retries:i,delay:u,backoff:y,retryOn:p,shouldRetry:g,maxDelay:h}={...this.retry,...(r==null?void 0:r.retry)||{}},f=0,m=u;for(;f<=i;)try{if(r=await P(r,r.onRequest),r=await P(r,this.config.onRequest),this.isCustomFetcher())n=await this.requestInstance.request(r);else{n=await globalThis.fetch(r.url,r);let c=String(((w=n==null?void 0:n.headers)==null?void 0:w.get("Content-Type"))||""),d;if(!c)try{d=await n.json()}catch{}if(typeof d>"u"&&(c&&c.includes("application/json")?d=await n.json():typeof n.text<"u"?d=await n.text():typeof n.blob<"u"?d=await n.blob():d=n.body||n.data||null),n.config=r,n.data=d,!n.ok)throw new b(`${r.url} failed! Status: ${n.status||null}`,r,n)}return n=await q(n,r.onResponse),n=await q(n,this.config.onResponse),this.outputResponse(n,r)}catch(c){if(f===i||!await g(c,f)||!(p!=null&&p.includes(((F=c==null?void 0:c.response)==null?void 0:F.status)||(c==null?void 0:c.status))))return this.processError(c,r),this.outputErrorResponse(c,r);(A=this.logger)!=null&&A.warn&&this.logger.warn(`Attempt ${f+1} failed. Retrying in ${m}ms...`),await this.delay(m),m*=y,m=Math.min(m,h),f++}return this.outputResponse(n,r)}async delay(e){return new Promise(t=>setTimeout(()=>t(!0),e))}processHeaders(e){if(!e.headers)return{};let t={};if(e.headers instanceof Headers)for(let[s,n]of e.headers.entries())t[s]=n;else t={...e.headers};return t}outputResponse(e,t,s=null){let n=typeof t.defaultResponse<"u"?t.defaultResponse:this.defaultResponse;return e?(t.flattenResponse||this.flattenResponse)&&typeof e.data<"u"?e.data!==null&&typeof e.data=="object"&&typeof e.data.data<"u"&&Object.keys(e.data).length===1?e.data.data:e.data:e!==null&&typeof e=="object"&&e.constructor===Object&&Object.keys(e).length===0?n:this.isCustomFetcher()?e:(s!==null&&(s==null||delete s.response,s==null||delete s.request,s==null||delete s.config),{...e,error:s,headers:this.processHeaders(e),config:t}):n}};function O(o){let e=o.endpoints,t=new R(o);function s(){return t.getInstance()}function n(i){return console.error(`${i} endpoint must be added to 'endpoints'.`),Promise.resolve(null)}async function a(i,u={},y={},p={}){let h={...e[i]};return await t.request(h.url,u,{...h,...p,urlPathParams:y})}function l(i){return i in r?r[i]:e[i]?r.request.bind(null,i):n.bind(null,i)}let r={config:o,endpoints:e,requestHandler:t,getInstance:s,request:a};return new Proxy(r,{get:(i,u)=>l(u)})}async function x(o,e={}){return new R(e).request(o,e.body||e.data||e.params,e)}0&&(module.exports={createApiFetcher,fetchf}); //# sourceMappingURL=index.js.map \ No newline at end of file diff --git a/dist/node/index.js.map b/dist/node/index.js.map index 25051d0..4c51197 100644 --- a/dist/node/index.js.map +++ b/dist/node/index.js.map @@ -1 +1 @@ -{"version":3,"sources":["../src/index.ts","../src/request-error-handler.ts","../src/request-error.ts","../src/interceptor-manager.ts","../src/request-handler.ts","../src/api-handler.ts"],"sourcesContent":["import { RequestHandler } from './request-handler';\nimport type { APIResponse, FetchResponse, RequestHandlerConfig } from './types';\n\n/**\n * Simple wrapper for request fetching.\n * It abstracts the creation of RequestHandler, making it easy to perform API requests.\n *\n * @param {string | URL | globalThis.Request} url - Request URL.\n * @param {Object} config - Configuration object for the request handler.\n * @returns {Promise} Response Data.\n */\nexport async function fetchf(\n url: string,\n config: RequestHandlerConfig = {},\n): Promise> {\n return new RequestHandler(config).request(\n url,\n config.body || config.data || config.params,\n config,\n );\n}\n\nexport * from './types';\nexport * from './api-handler';\n","/* eslint-disable @typescript-eslint/no-explicit-any */\nexport class RequestErrorHandler {\n /**\n * Logger Class\n *\n * @type {*}\n * @memberof RequestErrorHandler\n */\n protected logger: any;\n\n /**\n * Error Service Class\n *\n * @type {*}\n * @memberof RequestErrorHandler\n */\n public requestErrorService: any;\n\n public constructor(logger: any, requestErrorService: any) {\n this.logger = logger;\n this.requestErrorService = requestErrorService;\n }\n\n /**\n * Process and Error\n *\n * @param {*} error Error instance or message\n * @throws Request error context\n * @returns {void}\n */\n public process(error: string | Error): void {\n if (this.logger?.warn) {\n this.logger.warn('API ERROR', error);\n }\n\n let errorContext = error;\n\n if (typeof error === 'string') {\n errorContext = new Error(error);\n }\n\n if (this.requestErrorService) {\n if (typeof this.requestErrorService.process !== 'undefined') {\n this.requestErrorService.process(errorContext);\n } else if (typeof this.requestErrorService === 'function') {\n this.requestErrorService(errorContext);\n }\n }\n }\n}\n","import type { RequestConfig } from './types';\n\nexport class RequestError extends Error {\n response: Response;\n request: RequestConfig;\n\n constructor(message: string, requestInfo: RequestConfig, response: Response) {\n super(message);\n\n this.name = 'RequestError';\n this.message = message;\n this.request = requestInfo;\n this.response = response;\n\n // Clean stack trace\n Error.captureStackTrace(this, RequestError);\n }\n}\n","import type {\n BaseRequestHandlerConfig,\n FetchResponse,\n RequestResponse,\n} from './types';\nimport type {\n RequestInterceptor,\n ResponseInterceptor,\n} from './types/interceptor-manager';\n\n/**\n * Applies a series of request interceptors to the provided configuration.\n * @param {BaseRequestHandlerConfig} config - The initial request configuration.\n * @param {RequestInterceptor | RequestInterceptor[]} interceptors - The request interceptor function(s) to apply.\n * @returns {Promise} - The modified request configuration.\n */\nexport async function interceptRequest(\n config: BaseRequestHandlerConfig,\n interceptors: RequestInterceptor | RequestInterceptor[],\n): Promise {\n if (!interceptors) {\n return config;\n }\n\n const interceptorList = Array.isArray(interceptors)\n ? interceptors\n : [interceptors];\n\n let interceptedConfig = { ...config };\n\n for (const interceptor of interceptorList) {\n interceptedConfig = await interceptor(interceptedConfig);\n }\n\n return interceptedConfig;\n}\n\n/**\n * Applies a series of response interceptors to the provided response.\n * @param {FetchResponse} response - The initial response object.\n * @param {ResponseInterceptor | ResponseInterceptor[]} interceptors - The response interceptor function(s) to apply.\n * @returns {Promise>} - The modified response object.\n */\nexport async function interceptResponse(\n response: FetchResponse,\n interceptors: ResponseInterceptor | ResponseInterceptor[],\n): Promise> {\n if (!interceptors) {\n return response;\n }\n\n const interceptorList = Array.isArray(interceptors)\n ? interceptors\n : [interceptors];\n\n let interceptedResponse = response;\n\n for (const interceptor of interceptorList) {\n interceptedResponse = await interceptor(interceptedResponse);\n }\n\n return interceptedResponse;\n}\n","/* eslint-disable @typescript-eslint/no-explicit-any */\nimport { RequestErrorHandler } from './request-error-handler';\nimport type {\n ErrorHandlingStrategy,\n RequestHandlerConfig,\n RequestConfig,\n RequestError as RequestErrorResponse,\n FetcherInstance,\n Method,\n RequestConfigHeaders,\n RetryOptions,\n FetchResponse,\n ExtendedResponse,\n} from './types/request-handler';\nimport type {\n APIResponse,\n QueryParams,\n QueryParamsOrBody,\n UrlPathParams,\n} from './types/api-handler';\nimport { RequestError } from './request-error';\nimport { interceptRequest, interceptResponse } from './interceptor-manager';\n\n/**\n * Generic Request Handler\n * It creates an Request Fetcher instance and handles requests within that instance\n * It handles errors depending on a chosen error handling strategy\n */\nexport class RequestHandler {\n /**\n * @var requestInstance Provider's instance\n */\n public requestInstance: FetcherInstance;\n\n /**\n * @var baseURL Base API url\n */\n public baseURL: string = '';\n\n /**\n * @var timeout Request timeout\n */\n public timeout: number = 30000;\n\n /**\n * @var cancellable Response cancellation\n */\n public cancellable: boolean = false;\n\n /**\n * @var rejectCancelled Whether to reject cancelled requests or not\n */\n public rejectCancelled: boolean = false;\n\n /**\n * @var strategy Request timeout\n */\n public strategy: ErrorHandlingStrategy = 'reject';\n\n /**\n * @var method Request method\n */\n public method: Method | string = 'get';\n\n /**\n * @var flattenResponse Response flattening\n */\n public flattenResponse: boolean = false;\n\n /**\n * @var defaultResponse Response flattening\n */\n public defaultResponse: any = null;\n\n /**\n * @var fetcher Request Fetcher instance\n */\n protected fetcher: FetcherInstance;\n\n /**\n * @var logger Logger\n */\n protected logger: any;\n\n /**\n * @var requestErrorService HTTP error service\n */\n protected onError: any;\n\n /**\n * @var requestsQueue Queue of requests\n */\n protected requestsQueue: WeakMap;\n\n /**\n * Request Handler Config\n */\n protected retry: RetryOptions = {\n retries: 0,\n delay: 1000,\n maxDelay: 30000,\n backoff: 1.5,\n\n // https://developer.mozilla.org/en-US/docs/Web/HTTP/Status\n retryOn: [\n 408, // Request Timeout\n 409, // Conflict\n 425, // Too Early\n 429, // Too Many Requests\n 500, // Internal Server Error\n 502, // Bad Gateway\n 503, // Service Unavailable\n 504, // Gateway Timeout\n ],\n\n shouldRetry: async () => true,\n };\n\n /**\n * Request Handler Config\n */\n public config: RequestHandlerConfig;\n\n /**\n * Creates an instance of RequestHandler.\n *\n * @param {Object} config - Configuration object for the request.\n * @param {string} config.baseURL - The base URL for the request.\n * @param {Object} config.endpoints - An object containing endpoint definitions.\n * @param {number} config.timeout - You can set the timeout for particular request in milliseconds.\n * @param {number} config.cancellable - If true, the previous requests will be automatically cancelled.\n * @param {number} config.rejectCancelled - If true and request is set to cancellable, a cancelled request promise will be rejected. By default, instead of rejecting the promise, defaultResponse is returned.\n * @param {number} config.timeout - Request timeout\n * @param {string} config.strategy - Error Handling Strategy\n * @param {string} config.flattenResponse - Whether to flatten response \"data\" object within \"data\" one\n * @param {*} config.defaultResponse - Default response when there is no data or when endpoint fails depending on the chosen strategy. It's \"null\" by default\n * @param {Object} [config.retry] - Options for retrying requests.\n * @param {number} [config.retry.retries=0] - Number of retry attempts. No retries by default.\n * @param {number} [config.retry.delay=1000] - Initial delay between retries in milliseconds.\n * @param {number} [config.retry.backoff=1.5] - Exponential backoff factor.\n * @param {number[]} [config.retry.retryOn=[502, 504, 408]] - HTTP status codes to retry on.\n * @param {RequestInterceptor|RequestInterceptor[]} [config.onRequest] - Optional request interceptor function or an array of functions.\n * These functions will be called with the request configuration object before the request is made. Can be used to modify or log the request configuration.\n * @param {ResponseInterceptor|ResponseInterceptor[]} [config.onResponse] - Optional response interceptor function or an array of functions.\n * These functions will be called with the response object after the response is received. an be used to modify or log the response data.\n * @param {Function} [config.onError] - Optional callback function for handling errors.\n * @param {Object} [config.headers] - Optional default headers to include in every request.\n * @param {Object} config.fetcher - The Axios (or any other) instance to use for making requests.\n * @param {*} config.logger - Instance of custom logger. Either class or an object similar to \"console\". Console is used by default.\n */\n public constructor({\n fetcher = null,\n timeout = null,\n rejectCancelled = false,\n strategy = null,\n flattenResponse = null,\n defaultResponse = {},\n logger = null,\n onError = null,\n ...config\n }: RequestHandlerConfig) {\n this.fetcher = fetcher;\n this.timeout =\n timeout !== null && timeout !== undefined ? timeout : this.timeout;\n this.strategy =\n strategy !== null && strategy !== undefined ? strategy : this.strategy;\n this.cancellable = config.cancellable || this.cancellable;\n this.rejectCancelled = rejectCancelled || this.rejectCancelled;\n this.flattenResponse =\n flattenResponse !== null && flattenResponse !== undefined\n ? flattenResponse\n : this.flattenResponse;\n this.defaultResponse = defaultResponse;\n this.logger = logger || (globalThis ? globalThis.console : null) || null;\n this.onError = onError;\n this.requestsQueue = new WeakMap();\n this.baseURL = config.baseURL || config.apiUrl || '';\n this.method = config.method || this.method;\n this.config = config;\n this.retry = {\n ...this.retry,\n ...(config.retry || {}),\n };\n\n this.requestInstance = this.isCustomFetcher()\n ? (fetcher as any).create({\n ...config,\n baseURL: this.baseURL,\n timeout: this.timeout,\n })\n : null;\n }\n\n /**\n * Get Provider Instance\n *\n * @returns {FetcherInstance} Provider's instance\n */\n public getInstance(): FetcherInstance {\n return this.requestInstance;\n }\n\n /**\n * Replaces dynamic URI parameters in a URL string with values from the provided `urlPathParams` object.\n * Parameters in the URL are denoted by `:`, where `` is a key in `urlPathParams`.\n *\n * @param {string} url - The URL string containing placeholders in the format `:`.\n * @param {Object} urlPathParams - An object containing the parameter values to replace placeholders.\n * @param {string} urlPathParams.paramName - The value to replace the placeholder `:` in the URL.\n * @returns {string} - The URL string with placeholders replaced by corresponding values from `urlPathParams`.\n */\n public replaceUrlPathParams(\n url: string,\n urlPathParams: UrlPathParams,\n ): string {\n if (!urlPathParams) {\n return url;\n }\n\n return url.replace(/:[a-zA-Z]+/gi, (str): string => {\n const word = str.substring(1);\n\n return String(urlPathParams[word] ? urlPathParams[word] : str);\n });\n }\n\n /**\n * Appends query parameters to the given URL\n *\n * @param {string} url - The base URL to which query parameters will be appended.\n * @param {QueryParams} params - An instance of URLSearchParams containing the query parameters to append.\n * @returns {string} - The URL with the appended query parameters.\n */\n public appendQueryParams(url: string, params: QueryParams): string {\n if (!params) {\n return url;\n }\n\n // We don't use URLSearchParams here as we want to ensure that arrays are properly converted similarily to Axios\n // So { foo: [1, 2] } would become: foo[]=1&foo[]=2\n const queryString = Object.entries(params)\n .flatMap(([key, value]) => {\n if (Array.isArray(value)) {\n return value.map(\n (val) => `${encodeURIComponent(key)}[]=${encodeURIComponent(val)}`,\n );\n }\n return `${encodeURIComponent(key)}=${encodeURIComponent(String(value))}`;\n })\n .join('&');\n\n return url.includes('?')\n ? `${url}&${queryString}`\n : queryString\n ? `${url}?${queryString}`\n : url;\n }\n\n /**\n * Checks if a value is JSON serializable.\n *\n * JSON serializable values include:\n * - Primitive types: string, number, boolean, null\n * - Arrays\n * - Plain objects (i.e., objects without special methods)\n * - Values with a `toJSON` method\n *\n * @param {any} value - The value to check for JSON serializability.\n * @returns {boolean} - Returns `true` if the value is JSON serializable, otherwise `false`.\n */\n protected isJSONSerializable(value: any): boolean {\n if (value === undefined || value === null) {\n return false;\n }\n\n const t = typeof value;\n if (t === 'string' || t === 'number' || t === 'boolean') {\n return true;\n }\n\n if (t !== 'object') {\n return false; // bigint, function, symbol, undefined\n }\n\n if (Array.isArray(value)) {\n return true;\n }\n\n if (Buffer.isBuffer(value)) {\n return false;\n }\n\n if (value instanceof Date) {\n return false;\n }\n\n const proto = Object.getPrototypeOf(value);\n\n // Check if the prototype is `Object.prototype` or `null` (plain object)\n if (proto === Object.prototype || proto === null) {\n return true;\n }\n\n // Check if the object has a toJSON method\n if (typeof value.toJSON === 'function') {\n return true;\n }\n\n return false;\n }\n\n /**\n * Build request configuration\n *\n * @param {string} url Request url\n * @param {QueryParamsOrBody} data Request data\n * @param {RequestConfig} config Request config\n * @returns {RequestConfig} Provider's instance\n */\n protected buildConfig(\n url: string,\n data: QueryParamsOrBody,\n config: RequestConfig,\n ): RequestConfig {\n const method = config.method || this.method;\n const methodLowerCase = method.toLowerCase();\n const isGetAlikeMethod =\n methodLowerCase === 'get' || methodLowerCase === 'head';\n\n const dynamicUrl = this.replaceUrlPathParams(\n url,\n config.urlPathParams || this.config.urlPathParams,\n );\n\n // Bonus: Specifying it here brings support for \"body\" in Axios\n const configData =\n config.body || config.data || this.config.body || this.config.data;\n\n // Axios compatibility\n if (this.isCustomFetcher()) {\n return {\n ...config,\n url: dynamicUrl,\n method: methodLowerCase,\n\n ...(isGetAlikeMethod ? { params: data } : {}),\n\n // For POST requests body payload is the first param for convenience (\"data\")\n // In edge cases we want to split so to treat it as query params, and use \"body\" coming from the config instead\n ...(!isGetAlikeMethod && data && configData ? { params: data } : {}),\n\n // Only applicable for request methods 'PUT', 'POST', 'DELETE', and 'PATCH'\n ...(!isGetAlikeMethod && data && !configData ? { data } : {}),\n ...(!isGetAlikeMethod && configData ? { data: configData } : {}),\n };\n }\n\n // Native fetch\n const payload = configData || data;\n\n delete config.data;\n\n const urlPath =\n (!isGetAlikeMethod && data && !config.body) || !data\n ? dynamicUrl\n : this.appendQueryParams(dynamicUrl, data);\n const isFullUrl = urlPath.includes('://');\n const baseURL = isFullUrl\n ? ''\n : typeof config.baseURL !== 'undefined'\n ? config.baseURL\n : this.baseURL;\n\n return {\n ...config,\n\n // Native fetch generally requires query params to be appended in the URL\n // Do not append query params only if it's a POST-alike request with only \"data\" specified as it's treated as body payload\n url: baseURL + urlPath,\n\n // Uppercase method name\n method: method.toUpperCase(),\n\n // For convenience, add the same default headers as Axios does\n headers: {\n Accept: 'application/json, text/plain, */*',\n 'Content-Type': 'application/json;charset=utf-8',\n ...(config.headers || this.config.headers || {}),\n } as RequestConfigHeaders,\n\n // Automatically JSON stringify request bodies, if possible and when not dealing with strings\n ...(!isGetAlikeMethod\n ? {\n body: this.isJSONSerializable(payload)\n ? typeof payload === 'string'\n ? payload\n : JSON.stringify(payload)\n : payload,\n }\n : {}),\n };\n }\n\n /**\n * Process global Request Error\n *\n * @param {RequestErrorResponse} error Error instance\n * @param {RequestConfig} requestConfig Per endpoint request config\n * @returns {void}\n */\n protected processError(\n error: RequestErrorResponse,\n requestConfig: RequestConfig,\n ): void {\n if (this.isRequestCancelled(error)) {\n return;\n }\n\n // Invoke per request \"onError\" call\n if (requestConfig.onError && typeof requestConfig.onError === 'function') {\n requestConfig.onError(error);\n }\n\n const errorHandler = new RequestErrorHandler(this.logger, this.onError);\n\n errorHandler.process(error);\n }\n\n /**\n * Output default response in case of an error, depending on chosen strategy\n *\n * @param {RequestErrorResponse} error Error instance\n * @param {RequestConfig} requestConfig Per endpoint request config\n * @returns {*} Error response\n */\n protected async outputErrorResponse(\n error: RequestErrorResponse,\n requestConfig: RequestConfig,\n ): Promise {\n const isRequestCancelled = this.isRequestCancelled(error);\n const errorHandlingStrategy = requestConfig.strategy || this.strategy;\n const rejectCancelled =\n typeof requestConfig.rejectCancelled !== 'undefined'\n ? requestConfig.rejectCancelled\n : this.rejectCancelled;\n const defaultResponse =\n typeof requestConfig.defaultResponse !== 'undefined'\n ? requestConfig.defaultResponse\n : this.defaultResponse;\n\n // By default cancelled requests aren't rejected\n if (isRequestCancelled && !rejectCancelled) {\n return defaultResponse;\n }\n\n if (errorHandlingStrategy === 'silent') {\n // Hang the promise\n await new Promise(() => null);\n\n return defaultResponse;\n }\n\n // Simply rejects a request promise\n if (errorHandlingStrategy === 'reject') {\n return Promise.reject(error);\n }\n\n return defaultResponse;\n }\n\n /**\n * Output error response depending on chosen strategy\n *\n * @param {RequestErrorResponse} error Error instance\n * @returns {boolean} True if request is aborted\n */\n public isRequestCancelled(error: RequestErrorResponse): boolean {\n return error.name === 'AbortError' || error.name === 'CanceledError';\n }\n\n /**\n * Detects if a custom fetcher is utilized\n *\n * @returns {boolean} True if it's a custom fetcher\n */\n protected isCustomFetcher(): boolean {\n return this.fetcher !== null;\n }\n\n /**\n * Automatically Cancel Previous Requests using AbortController when \"cancellable\" is defined\n *\n * @param {RequestConfig} requestConfig Per endpoint request config\n * @returns {Object} Controller Signal to abort\n */\n protected addCancellationToken(\n requestConfig: RequestConfig,\n ): Partial> {\n // Both disabled\n if (!this.cancellable && !requestConfig.cancellable) {\n return {};\n }\n\n // Explicitly disabled per request\n if (\n typeof requestConfig.cancellable !== 'undefined' &&\n !requestConfig.cancellable\n ) {\n return {};\n }\n\n // Check if AbortController is available\n if (typeof AbortController === 'undefined') {\n console.error('AbortController is unavailable.');\n\n return {};\n }\n\n // Generate unique key as a cancellation token\n const previousRequest = this.requestsQueue.get(requestConfig);\n\n if (previousRequest) {\n previousRequest.abort();\n }\n\n const controller = new AbortController();\n\n // Introduce timeout for native fetch\n if (!this.isCustomFetcher()) {\n const abortTimeout = setTimeout(() => {\n const error = new Error(\n `[TimeoutError]: The ${requestConfig.url} request was aborted due to timeout`,\n );\n\n error.name = 'TimeoutError';\n (error as any).code = 23; // DOMException.TIMEOUT_ERR\n controller.abort(error);\n clearTimeout(abortTimeout);\n throw error;\n }, requestConfig.timeout || this.timeout);\n }\n\n this.requestsQueue.set(requestConfig, controller);\n\n return {\n signal: controller.signal,\n };\n }\n\n /**\n * Handle Request depending on used strategy\n *\n * @param {string} url - Request url\n * @param {QueryParamsOrBody} data - Request data\n * @param {RequestConfig} config - Request config\n * @param {RequestConfig} payload.config Request config\n * @throws {RequestErrorResponse}\n * @returns {Promise>} Response Data\n */\n public async request(\n url: string,\n data: QueryParamsOrBody = null,\n config: RequestConfig = null,\n ): Promise> {\n let response: Response | FetchResponse = null;\n const _config = config || {};\n const _requestConfig = this.buildConfig(url, data, _config);\n\n let requestConfig: RequestConfig = {\n ...this.addCancellationToken(_requestConfig),\n ..._requestConfig,\n };\n\n const { retries, delay, backoff, retryOn, shouldRetry, maxDelay } = {\n ...this.retry,\n ...(requestConfig?.retry || {}),\n };\n\n let attempt = 0;\n let waitTime = delay;\n\n while (attempt <= retries) {\n try {\n // Local interceptors\n requestConfig = await interceptRequest(\n requestConfig,\n requestConfig.onRequest,\n );\n\n // Global interceptors\n requestConfig = await interceptRequest(\n requestConfig,\n this.config.onRequest,\n );\n\n // Axios compatibility\n if (this.isCustomFetcher()) {\n response = (await (this.requestInstance as any).request(\n requestConfig,\n )) as FetchResponse;\n } else {\n response = (await globalThis.fetch(\n requestConfig.url,\n requestConfig,\n )) as ExtendedResponse;\n\n // Add more information to response object\n response.config = requestConfig;\n\n // Check if the response status is not outside the range 200-299\n if (response.ok) {\n const contentType = String(\n response?.headers?.get('Content-Type') || '',\n );\n let data = null;\n\n // Handle edge case of no content type being provided... We assume json here.\n if (!contentType) {\n try {\n data = await response.json();\n // eslint-disable-next-line @typescript-eslint/no-unused-vars\n } catch (_error) {\n //\n }\n }\n\n if (!data) {\n if (contentType && contentType.includes('application/json')) {\n // Parse JSON response\n data = await response.json();\n } else if (typeof response.text !== 'undefined') {\n data = await response.text();\n } else if (typeof response.blob !== 'undefined') {\n data = await response.blob();\n } else {\n // Handle streams\n data = response.body || response.data || null;\n }\n }\n\n response.data = data;\n } else {\n response.data = null;\n\n // Output error in similar format to what Axios does\n throw new RequestError(\n `fetchf() Request Failed! Status: ${response.status || null}`,\n requestConfig,\n response,\n );\n }\n }\n\n // Local interceptors\n response = await interceptResponse(response, requestConfig.onResponse);\n\n // Global interceptors\n response = await interceptResponse(response, this.config.onResponse);\n\n return this.processResponseData(response, requestConfig);\n } catch (error) {\n if (\n attempt === retries ||\n !(await shouldRetry(error, attempt)) ||\n !retryOn?.includes(\n (response as FetchResponse)?.status ||\n error?.response?.status ||\n error?.status,\n )\n ) {\n this.processError(error, requestConfig);\n\n return this.outputErrorResponse(error, requestConfig);\n }\n\n if (this.logger?.warn) {\n this.logger.warn(\n `Attempt ${attempt + 1} failed. Retrying in ${waitTime}ms...`,\n );\n }\n\n await this.delay(waitTime);\n\n waitTime *= backoff;\n waitTime = Math.min(waitTime, maxDelay);\n attempt++;\n }\n }\n\n return this.processResponseData(response, requestConfig);\n }\n\n public async delay(ms: number): Promise {\n return new Promise((resolve) =>\n setTimeout(() => {\n return resolve(true);\n }, ms),\n );\n }\n\n /**\n * Process response\n *\n * @param response - Response payload\n * @param {RequestConfig} requestConfig - Request config\n * @returns {*} Response data\n */\n protected processResponseData(response, requestConfig: RequestConfig) {\n const defaultResponse =\n typeof requestConfig.defaultResponse !== 'undefined'\n ? requestConfig.defaultResponse\n : this.defaultResponse;\n\n if (!response) {\n return defaultResponse;\n }\n\n if (\n (requestConfig.flattenResponse || this.flattenResponse) &&\n typeof response.data !== 'undefined'\n ) {\n // Special case of only data property within response data object (happens in Axios)\n // This is in fact a proper response but we may want to flatten it\n // To ease developers' lives when obtaining the response\n if (\n typeof response.data === 'object' &&\n typeof response.data.data !== 'undefined' &&\n Object.keys(response.data).length === 1\n ) {\n return response.data.data;\n }\n\n return response.data;\n }\n\n // If empty object is returned, ensure that the default response is used instead\n if (\n typeof response === 'object' &&\n response.constructor === Object &&\n Object.keys(response).length === 0\n ) {\n return defaultResponse;\n }\n\n // For fetch()\n const isCustomFetcher = this.isCustomFetcher();\n\n if (!isCustomFetcher) {\n return {\n ...response,\n headers: Array.from(response?.headers?.entries() || {}).reduce(\n (acc, [key, value]) => {\n acc[key] = value;\n return acc;\n },\n {},\n ),\n config: requestConfig,\n };\n }\n\n return response;\n }\n}\n","import { RequestHandler } from './request-handler';\nimport type {\n FetcherInstance,\n RequestConfig,\n FetchResponse,\n} from './types/request-handler';\nimport type {\n ApiHandlerConfig,\n ApiHandlerMethods,\n ApiHandlerReturnType,\n APIResponse,\n QueryParams,\n UrlPathParams,\n} from './types/api-handler';\n\n/**\n * Creates an instance of API Handler.\n * It creates an API fetcher function using native fetch() or Axios if it is passed as \"fetcher\".\n *\n * @param {Object} config - Configuration object for the API fetcher.\n * @param {string} config.apiUrl - The base URL for the API.\n * @param {Object} config.endpoints - An object containing endpoint definitions.\n * @param {number} config.timeout - You can set the timeout for particular request in milliseconds.\n * @param {number} config.cancellable - If true, the previous requests will be automatically cancelled.\n * @param {number} config.rejectCancelled - If true and request is set to cancellable, a cancelled request promise will be rejected. By default, instead of rejecting the promise, defaultResponse is returned.\n * @param {number} config.timeout - Request timeout\n * @param {string} config.strategy - Error Handling Strategy\n * @param {string} config.flattenResponse - Whether to flatten response \"data\" object within \"data\" one\n * @param {*} config.defaultResponse - Default response when there is no data or when endpoint fails depending on the chosen strategy. It's \"null\" by default\n * @param {Object} [config.retry] - Options for retrying requests.\n * @param {number} [config.retry.retries=0] - Number of retry attempts. No retries by default.\n * @param {number} [config.retry.delay=1000] - Initial delay between retries in milliseconds.\n * @param {number} [config.retry.backoff=1.5] - Exponential backoff factor.\n * @param {number[]} [config.retry.retryOn=[502, 504, 408]] - HTTP status codes to retry on.\n * @param {RequestInterceptor|RequestInterceptor[]} [config.onRequest] - Optional request interceptor function or an array of functions.\n * These functions will be called with the request configuration object before the request is made. Can be used to modify or log the request configuration.\n * @param {ResponseInterceptor|ResponseInterceptor[]} [config.onResponse] - Optional response interceptor function or an array of functions.\n * These functions will be called with the response object after the response is received. an be used to modify or log the response data.\n * @param {Function} [config.onError] - Optional callback function for handling errors.\n * @param {Object} [config.headers] - Optional default headers to include in every request.\n * @param {Object} config.fetcher - The Axios (or any other) instance to use for making requests.\n * @param {*} config.logger - Instance of custom logger. Either class or an object similar to \"console\". Console is used by default.\n * @returns API handler functions and endpoints to call\n *\n * @example\n * // Import axios (optional)\n * import axios from 'axios';\n *\n * // Define endpoint paths\n * const endpoints = {\n * getUser: '/user',\n * createPost: '/post',\n * };\n *\n * // Create the API fetcher with configuration\n * const api = createApiFetcher({\n * fetcher: axios, // Axios instance (optional)\n * endpoints,\n * apiUrl: 'https://example.com/api',\n * onError(error) {\n * console.log('Request failed', error);\n * },\n * headers: {\n * 'my-auth-key': 'example-auth-key-32rjjfa',\n * },\n * });\n *\n * // Fetch user data\n * const response = await api.getUser({ userId: 1, ratings: [1, 2] })\n */\nfunction createApiFetcher(\n config: ApiHandlerConfig,\n) {\n const endpoints = config.endpoints;\n const requestHandler = new RequestHandler(config);\n\n /**\n * Get Fetcher Provider Instance\n *\n * @returns {FetcherInstance} Request Handler's Fetcher instance\n */\n function getInstance(): FetcherInstance {\n return requestHandler.getInstance();\n }\n\n /**\n * Triggered when trying to use non-existent endpoints\n *\n * @param endpointName Endpoint Name\n * @returns {Promise}\n */\n function handleNonImplemented(endpointName: string): Promise {\n console.error(`${endpointName} endpoint must be added to 'endpoints'.`);\n\n return Promise.resolve(null);\n }\n\n /**\n * Handle Single API Request\n * It considers settings in following order: per-request settings, global per-endpoint settings, global settings.\n *\n * @param {string} endpointName - The name of the API endpoint to call.\n * @param {QueryParams} [queryParams={}] - Query parameters to include in the request.\n * @param {UrlPathParams} [urlPathParams={}] - URI parameters to include in the request.\n * @param {EndpointConfig} [requestConfig={}] - Additional configuration for the request.\n * @returns {Promise} - A promise that resolves with the response from the API provider.\n */\n async function request(\n endpointName: keyof EndpointsMethods | string,\n queryParams: QueryParams = {},\n urlPathParams: UrlPathParams = {},\n requestConfig: RequestConfig = {},\n ): Promise> {\n // Use global per-endpoint settings\n const endpointConfig = endpoints[endpointName as string];\n const endpointSettings = { ...endpointConfig };\n\n const responseData = await requestHandler.request(\n endpointSettings.url,\n queryParams,\n {\n ...endpointSettings,\n ...requestConfig,\n urlPathParams,\n },\n );\n\n return responseData;\n }\n\n /**\n * Maps all API requests using native Proxy\n *\n * @param {*} prop Caller\n */\n function get(prop: string | symbol) {\n if (prop in apiHandler) {\n return apiHandler[prop];\n }\n\n // Prevent handler from triggering non-existent endpoints\n if (!endpoints[prop as string]) {\n return handleNonImplemented.bind(null, prop);\n }\n\n return apiHandler.request.bind(null, prop);\n }\n\n const apiHandler: ApiHandlerMethods = {\n config,\n endpoints,\n requestHandler,\n getInstance,\n request,\n };\n\n return new Proxy(apiHandler, {\n get: (_target, prop) => get(prop),\n }) as ApiHandlerReturnType;\n}\n\nexport { createApiFetcher };\n"],"mappings":"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","names":["src_exports","__export","createApiFetcher","fetchf","__toCommonJS","RequestErrorHandler","logger","requestErrorService","error","_a","errorContext","RequestError","_RequestError","message","requestInfo","response","interceptRequest","config","interceptors","interceptorList","interceptedConfig","interceptor","interceptResponse","response","interceptedResponse","RequestHandler","fetcher","timeout","rejectCancelled","strategy","flattenResponse","defaultResponse","logger","onError","config","url","urlPathParams","str","word","params","queryString","key","value","val","proto","data","method","methodLowerCase","isGetAlikeMethod","dynamicUrl","configData","payload","urlPath","baseURL","error","requestConfig","RequestErrorHandler","isRequestCancelled","errorHandlingStrategy","previousRequest","controller","abortTimeout","_a","_b","_c","response","_config","_requestConfig","retries","delay","backoff","retryOn","shouldRetry","maxDelay","attempt","waitTime","interceptRequest","contentType","RequestError","interceptResponse","ms","resolve","acc","createApiFetcher","config","endpoints","requestHandler","RequestHandler","getInstance","handleNonImplemented","endpointName","request","queryParams","urlPathParams","requestConfig","endpointSettings","get","prop","apiHandler","_target","fetchf","url","config","RequestHandler"]} \ No newline at end of file +{"version":3,"sources":["../src/index.ts","../src/interceptor-manager.ts","../src/response-error.ts","../src/request-handler.ts","../src/api-handler.ts"],"sourcesContent":["import { RequestHandler } from './request-handler';\nimport type { APIResponse, FetchResponse, RequestHandlerConfig } from './types';\n\n/**\n * Simple wrapper for request fetching.\n * It abstracts the creation of RequestHandler, making it easy to perform API requests.\n *\n * @param {string | URL | globalThis.Request} url - Request URL.\n * @param {RequestHandlerConfig} config - Configuration object for the request handler.\n * @returns {Promise>} Response Data.\n */\nexport async function fetchf(\n url: string,\n config: RequestHandlerConfig = {},\n): Promise> {\n return new RequestHandler(config).request(\n url,\n config.body || config.data || config.params,\n config,\n );\n}\n\nexport * from './types';\nexport * from './api-handler';\n","import type { RequestHandlerConfig, FetchResponse } from './types';\nimport type {\n RequestInterceptor,\n ResponseInterceptor,\n} from './types/interceptor-manager';\n\n/**\n * Applies a series of request interceptors to the provided configuration.\n * @param {RequestHandlerConfig} config - The initial request configuration.\n * @param {RequestInterceptor | RequestInterceptor[]} interceptors - The request interceptor function(s) to apply.\n * @returns {Promise} - The modified request configuration.\n */\nexport async function interceptRequest(\n config: RequestHandlerConfig,\n interceptors: RequestInterceptor | RequestInterceptor[],\n): Promise {\n if (!interceptors) {\n return config;\n }\n\n const interceptorList = Array.isArray(interceptors)\n ? interceptors\n : [interceptors];\n\n let interceptedConfig = { ...config };\n\n for (const interceptor of interceptorList) {\n interceptedConfig = await interceptor(interceptedConfig);\n }\n\n return interceptedConfig;\n}\n\n/**\n * Applies a series of response interceptors to the provided response.\n * @param {FetchResponse} response - The initial response object.\n * @param {ResponseInterceptor | ResponseInterceptor[]} interceptors - The response interceptor function(s) to apply.\n * @returns {Promise>} - The modified response object.\n */\nexport async function interceptResponse(\n response: FetchResponse,\n interceptors: ResponseInterceptor | ResponseInterceptor[],\n): Promise> {\n if (!interceptors) {\n return response;\n }\n\n const interceptorList = Array.isArray(interceptors)\n ? interceptors\n : [interceptors];\n\n let interceptedResponse = response;\n\n for (const interceptor of interceptorList) {\n interceptedResponse = await interceptor(interceptedResponse);\n }\n\n return interceptedResponse;\n}\n","import type { FetchResponse, RequestConfig } from './types';\n\nexport class ResponseErr extends Error {\n response: FetchResponse;\n request: RequestConfig;\n config: RequestConfig;\n status: number;\n statusText: string;\n\n constructor(\n message: string,\n requestInfo: RequestConfig,\n response: FetchResponse,\n ) {\n super(message);\n\n this.name = 'ResponseError';\n this.message = message;\n this.status = response.status;\n this.statusText = response.statusText;\n this.request = requestInfo;\n this.config = requestInfo;\n this.response = response;\n }\n}\n","/* eslint-disable @typescript-eslint/no-explicit-any */\nimport type {\n ErrorHandlingStrategy,\n RequestHandlerConfig,\n RequestConfig,\n FetcherInstance,\n Method,\n RetryOptions,\n FetchResponse,\n ResponseError,\n HeadersObject,\n} from './types/request-handler';\nimport type {\n APIResponse,\n QueryParams,\n QueryParamsOrBody,\n UrlPathParams,\n} from './types/api-handler';\nimport { interceptRequest, interceptResponse } from './interceptor-manager';\nimport { ResponseErr } from './response-error';\n\n/**\n * Generic Request Handler\n * It creates an Request Fetcher instance and handles requests within that instance\n * It handles errors depending on a chosen error handling strategy\n */\nexport class RequestHandler {\n /**\n * @var requestInstance Provider's instance\n */\n public requestInstance: FetcherInstance;\n\n /**\n * @var baseURL Base API url\n */\n public baseURL: string = '';\n\n /**\n * @var timeout Request timeout\n */\n public timeout: number = 30000;\n\n /**\n * @var cancellable Response cancellation\n */\n public cancellable: boolean = false;\n\n /**\n * @var rejectCancelled Whether to reject cancelled requests or not\n */\n public rejectCancelled: boolean = false;\n\n /**\n * @var strategy Request timeout\n */\n public strategy: ErrorHandlingStrategy = 'reject';\n\n /**\n * @var method Request method\n */\n public method: Method | string = 'get';\n\n /**\n * @var flattenResponse Response flattening\n */\n public flattenResponse: boolean = false;\n\n /**\n * @var defaultResponse Response flattening\n */\n public defaultResponse: any = null;\n\n /**\n * @var fetcher Request Fetcher instance\n */\n protected fetcher: FetcherInstance;\n\n /**\n * @var logger Logger\n */\n protected logger: any;\n\n /**\n * @var onError HTTP error service\n */\n protected onError: any;\n\n /**\n * @var requestsQueue Queue of requests\n */\n protected requestsQueue: WeakMap;\n\n /**\n * Request Handler Config\n */\n protected retry: RetryOptions = {\n retries: 0,\n delay: 1000,\n maxDelay: 30000,\n backoff: 1.5,\n\n // https://developer.mozilla.org/en-US/docs/Web/HTTP/Status\n retryOn: [\n 408, // Request Timeout\n 409, // Conflict\n 425, // Too Early\n 429, // Too Many Requests\n 500, // Internal Server Error\n 502, // Bad Gateway\n 503, // Service Unavailable\n 504, // Gateway Timeout\n ],\n\n shouldRetry: async () => true,\n };\n\n /**\n * Request Handler Config\n */\n public config: RequestHandlerConfig;\n\n /**\n * Creates an instance of RequestHandler.\n *\n * @param {Object} config - Configuration object for the request.\n * @param {string} config.baseURL - The base URL for the request.\n * @param {Object} config.endpoints - An object containing endpoint definitions.\n * @param {number} config.timeout - You can set the timeout for particular request in milliseconds.\n * @param {number} config.cancellable - If true, the previous requests will be automatically cancelled.\n * @param {number} config.rejectCancelled - If true and request is set to cancellable, a cancelled request promise will be rejected. By default, instead of rejecting the promise, defaultResponse is returned.\n * @param {number} config.timeout - Request timeout\n * @param {string} config.strategy - Error Handling Strategy\n * @param {string} config.flattenResponse - Whether to flatten response \"data\" object within \"data\" one\n * @param {*} config.defaultResponse - Default response when there is no data or when endpoint fails depending on the chosen strategy. It's \"null\" by default\n * @param {Object} [config.retry] - Options for retrying requests.\n * @param {number} [config.retry.retries=0] - Number of retry attempts. No retries by default.\n * @param {number} [config.retry.delay=1000] - Initial delay between retries in milliseconds.\n * @param {number} [config.retry.backoff=1.5] - Exponential backoff factor.\n * @param {number[]} [config.retry.retryOn=[502, 504, 408]] - HTTP status codes to retry on.\n * @param {RequestInterceptor|RequestInterceptor[]} [config.onRequest] - Optional request interceptor function or an array of functions.\n * These functions will be called with the request configuration object before the request is made. Can be used to modify or log the request configuration.\n * @param {ResponseInterceptor|ResponseInterceptor[]} [config.onResponse] - Optional response interceptor function or an array of functions.\n * These functions will be called with the response object after the response is received. an be used to modify or log the response data.\n * @param {Function} [config.onError] - Optional callback function for handling errors.\n * @param {Object} [config.headers] - Optional default headers to include in every request.\n * @param {Object} config.fetcher - The Axios (or any other) instance to use for making requests.\n * @param {*} config.logger - Instance of custom logger. Either class or an object similar to \"console\". Console is used by default.\n */\n public constructor({\n fetcher = null,\n timeout = null,\n rejectCancelled = false,\n strategy = null,\n flattenResponse = null,\n defaultResponse = {},\n logger = null,\n onError = null,\n ...config\n }: RequestHandlerConfig) {\n this.fetcher = fetcher;\n this.timeout =\n timeout !== null && timeout !== undefined ? timeout : this.timeout;\n this.strategy = strategy || this.strategy;\n this.cancellable = config.cancellable || this.cancellable;\n this.rejectCancelled = rejectCancelled || this.rejectCancelled;\n this.flattenResponse = flattenResponse || this.flattenResponse;\n this.defaultResponse = defaultResponse;\n this.logger = logger || (globalThis ? globalThis.console : null) || null;\n this.onError = onError;\n this.requestsQueue = new WeakMap();\n this.baseURL = config.baseURL || config.apiUrl || '';\n this.method = config.method || this.method;\n this.config = config;\n this.retry = {\n ...this.retry,\n ...(config.retry || {}),\n };\n\n this.requestInstance = this.isCustomFetcher()\n ? (fetcher as any).create({\n ...config,\n baseURL: this.baseURL,\n timeout: this.timeout,\n })\n : null;\n }\n\n /**\n * Get Provider Instance\n *\n * @returns {FetcherInstance} Provider's instance\n */\n public getInstance(): FetcherInstance {\n return this.requestInstance;\n }\n\n /**\n * Replaces dynamic URI parameters in a URL string with values from the provided `urlPathParams` object.\n * Parameters in the URL are denoted by `:`, where `` is a key in `urlPathParams`.\n *\n * @param {string} url - The URL string containing placeholders in the format `:`.\n * @param {Object} urlPathParams - An object containing the parameter values to replace placeholders.\n * @param {string} urlPathParams.paramName - The value to replace the placeholder `:` in the URL.\n * @returns {string} - The URL string with placeholders replaced by corresponding values from `urlPathParams`.\n */\n public replaceUrlPathParams(\n url: string,\n urlPathParams: UrlPathParams,\n ): string {\n if (!urlPathParams) {\n return url;\n }\n\n return url.replace(/:[a-zA-Z]+/gi, (str): string => {\n const word = str.substring(1);\n\n return String(urlPathParams[word] ? urlPathParams[word] : str);\n });\n }\n\n /**\n * Appends query parameters to the given URL\n *\n * @param {string} url - The base URL to which query parameters will be appended.\n * @param {QueryParams} params - An instance of URLSearchParams containing the query parameters to append.\n * @returns {string} - The URL with the appended query parameters.\n */\n public appendQueryParams(url: string, params: QueryParams): string {\n if (!params) {\n return url;\n }\n\n // We don't use URLSearchParams here as we want to ensure that arrays are properly converted similarily to Axios\n // So { foo: [1, 2] } would become: foo[]=1&foo[]=2\n const queryString = Object.entries(params)\n .flatMap(([key, value]) => {\n if (Array.isArray(value)) {\n return value.map(\n (val) => `${encodeURIComponent(key)}[]=${encodeURIComponent(val)}`,\n );\n }\n return `${encodeURIComponent(key)}=${encodeURIComponent(String(value))}`;\n })\n .join('&');\n\n return url.includes('?')\n ? `${url}&${queryString}`\n : queryString\n ? `${url}?${queryString}`\n : url;\n }\n\n /**\n * Checks if a value is JSON serializable.\n *\n * JSON serializable values include:\n * - Primitive types: string, number, boolean, null\n * - Arrays\n * - Plain objects (i.e., objects without special methods)\n * - Values with a `toJSON` method\n *\n * @param {any} value - The value to check for JSON serializability.\n * @returns {boolean} - Returns `true` if the value is JSON serializable, otherwise `false`.\n */\n protected isJSONSerializable(value: any): boolean {\n if (value === undefined || value === null) {\n return false;\n }\n\n const t = typeof value;\n if (t === 'string' || t === 'number' || t === 'boolean') {\n return true;\n }\n\n if (t !== 'object') {\n return false; // bigint, function, symbol, undefined\n }\n\n if (Array.isArray(value)) {\n return true;\n }\n\n if (Buffer.isBuffer(value)) {\n return false;\n }\n\n if (value instanceof Date) {\n return false;\n }\n\n const proto = Object.getPrototypeOf(value);\n\n // Check if the prototype is `Object.prototype` or `null` (plain object)\n if (proto === Object.prototype || proto === null) {\n return true;\n }\n\n // Check if the object has a toJSON method\n if (typeof value.toJSON === 'function') {\n return true;\n }\n\n return false;\n }\n\n /**\n * Build request configuration\n *\n * @param {string} url Request url\n * @param {QueryParamsOrBody} data Request data\n * @param {RequestConfig} config Request config\n * @returns {RequestConfig} Provider's instance\n */\n protected buildConfig(\n url: string,\n data: QueryParamsOrBody,\n config: RequestConfig,\n ): RequestConfig {\n const method = config.method || this.method;\n const methodLowerCase = method.toLowerCase();\n const isGetAlikeMethod =\n methodLowerCase === 'get' || methodLowerCase === 'head';\n\n const dynamicUrl = this.replaceUrlPathParams(\n url,\n config.urlPathParams || this.config.urlPathParams,\n );\n\n // Bonus: Specifying it here brings support for \"body\" in Axios\n const configData =\n config.body || config.data || this.config.body || this.config.data;\n\n // Axios compatibility\n if (this.isCustomFetcher()) {\n return {\n ...config,\n url: dynamicUrl,\n method: methodLowerCase,\n\n ...(isGetAlikeMethod ? { params: data } : {}),\n\n // For POST requests body payload is the first param for convenience (\"data\")\n // In edge cases we want to split so to treat it as query params, and use \"body\" coming from the config instead\n ...(!isGetAlikeMethod && data && configData ? { params: data } : {}),\n\n // Only applicable for request methods 'PUT', 'POST', 'DELETE', and 'PATCH'\n ...(!isGetAlikeMethod && data && !configData ? { data } : {}),\n ...(!isGetAlikeMethod && configData ? { data: configData } : {}),\n };\n }\n\n // Native fetch\n const payload = configData || data;\n const credentials =\n config.withCredentials || this.config.withCredentials\n ? 'include'\n : config.credentials;\n\n delete config.data;\n delete config.withCredentials;\n\n const urlPath =\n (!isGetAlikeMethod && data && !config.body) || !data\n ? dynamicUrl\n : this.appendQueryParams(dynamicUrl, data);\n const isFullUrl = urlPath.includes('://');\n const baseURL = isFullUrl\n ? ''\n : typeof config.baseURL !== 'undefined'\n ? config.baseURL\n : this.baseURL;\n\n return {\n ...config,\n credentials,\n\n // Native fetch generally requires query params to be appended in the URL\n // Do not append query params only if it's a POST-alike request with only \"data\" specified as it's treated as body payload\n url: baseURL + urlPath,\n\n // Uppercase method name\n method: method.toUpperCase(),\n\n // For convenience, add the same default headers as Axios does\n headers: {\n Accept: 'application/json, text/plain, */*',\n 'Content-Type': 'application/json;charset=utf-8',\n ...(config.headers || this.config.headers || {}),\n },\n\n // Automatically JSON stringify request bodies, if possible and when not dealing with strings\n ...(!isGetAlikeMethod\n ? {\n body: this.isJSONSerializable(payload)\n ? typeof payload === 'string'\n ? payload\n : JSON.stringify(payload)\n : payload,\n }\n : {}),\n };\n }\n\n /**\n * Process global Request Error\n *\n * @param {ResponseError} error Error instance\n * @param {RequestConfig} requestConfig Per endpoint request config\n * @returns {void}\n */\n protected processError(\n error: ResponseError,\n requestConfig: RequestConfig,\n ): void {\n if (this.isRequestCancelled(error)) {\n return;\n }\n\n if (this.logger?.warn) {\n this.logger.warn('API ERROR', error);\n }\n\n // Invoke per request \"onError\" interceptor\n if (requestConfig.onError && typeof requestConfig.onError === 'function') {\n requestConfig.onError(error);\n }\n\n // Invoke global \"onError\" interceptor\n if (this.onError && typeof this.onError === 'function') {\n this.onError(error);\n }\n }\n\n /**\n * Output default response in case of an error, depending on chosen strategy\n *\n * @param {ResponseError} error Error instance\n * @param {RequestConfig} requestConfig Per endpoint request config\n * @returns {*} Error response\n */\n protected async outputErrorResponse(\n error: ResponseError,\n requestConfig: RequestConfig,\n ): Promise {\n const isRequestCancelled = this.isRequestCancelled(error);\n const errorHandlingStrategy = requestConfig.strategy || this.strategy;\n const rejectCancelled =\n typeof requestConfig.rejectCancelled !== 'undefined'\n ? requestConfig.rejectCancelled\n : this.rejectCancelled;\n const defaultResponse =\n typeof requestConfig.defaultResponse !== 'undefined'\n ? requestConfig.defaultResponse\n : this.defaultResponse;\n\n // Output full response with the error object\n if (errorHandlingStrategy === 'softFail') {\n return this.outputResponse(error.response, requestConfig, error);\n }\n\n // By default cancelled requests aren't rejected\n if (isRequestCancelled && !rejectCancelled) {\n return defaultResponse;\n }\n\n // Hang the promise\n if (errorHandlingStrategy === 'silent') {\n await new Promise(() => null);\n\n return defaultResponse;\n }\n\n // Reject the promise\n if (errorHandlingStrategy === 'reject') {\n return Promise.reject(error);\n }\n\n return defaultResponse;\n }\n\n /**\n * Output error response depending on chosen strategy\n *\n * @param {ResponseError} error Error instance\n * @returns {boolean} True if request is aborted\n */\n public isRequestCancelled(error: ResponseError): boolean {\n return error.name === 'AbortError' || error.name === 'CanceledError';\n }\n\n /**\n * Detects if a custom fetcher is utilized\n *\n * @returns {boolean} True if it's a custom fetcher\n */\n protected isCustomFetcher(): boolean {\n return this.fetcher !== null;\n }\n\n /**\n * Automatically Cancel Previous Requests using AbortController when \"cancellable\" is defined\n *\n * @param {RequestConfig} requestConfig Per endpoint request config\n * @returns {Object} Controller Signal to abort\n */\n protected addCancellationToken(\n requestConfig: RequestConfig,\n ): Partial> {\n // Both disabled\n if (!this.cancellable && !requestConfig.cancellable) {\n return {};\n }\n\n // Explicitly disabled per request\n if (\n typeof requestConfig.cancellable !== 'undefined' &&\n !requestConfig.cancellable\n ) {\n return {};\n }\n\n // Check if AbortController is available\n if (typeof AbortController === 'undefined') {\n console.error('AbortController is unavailable.');\n\n return {};\n }\n\n // Generate unique key as a cancellation token\n const previousRequest = this.requestsQueue.get(requestConfig);\n\n if (previousRequest) {\n previousRequest.abort();\n }\n\n const controller = new AbortController();\n\n // Introduce timeout for native fetch\n if (!this.isCustomFetcher() && this.timeout > 0) {\n const abortTimeout = setTimeout(() => {\n const error = new Error(\n `[TimeoutError]: The ${requestConfig.url} request was aborted due to timeout`,\n );\n\n error.name = 'TimeoutError';\n (error as any).code = 23; // DOMException.TIMEOUT_ERR\n controller.abort(error);\n clearTimeout(abortTimeout);\n throw error;\n }, requestConfig.timeout || this.timeout);\n }\n\n this.requestsQueue.set(requestConfig, controller);\n\n return {\n signal: controller.signal,\n };\n }\n\n /**\n * Handle Request depending on used strategy\n *\n * @param {string} url - Request url\n * @param {QueryParamsOrBody} data - Request data\n * @param {RequestConfig} config - Request config\n * @param {RequestConfig} payload.config Request config\n * @throws {ResponseError}\n * @returns {Promise>} Response Data\n */\n public async request(\n url: string,\n data: QueryParamsOrBody = null,\n config: RequestConfig = null,\n ): Promise> {\n let response: FetchResponse = null;\n const _config = config || {};\n const _requestConfig = this.buildConfig(url, data, _config);\n\n let requestConfig: RequestConfig = {\n ...this.addCancellationToken(_requestConfig),\n ..._requestConfig,\n };\n\n const { retries, delay, backoff, retryOn, shouldRetry, maxDelay } = {\n ...this.retry,\n ...(requestConfig?.retry || {}),\n };\n\n let attempt = 0;\n let waitTime = delay;\n\n while (attempt <= retries) {\n try {\n // Local interceptors\n requestConfig = await interceptRequest(\n requestConfig,\n requestConfig.onRequest,\n );\n\n // Global interceptors\n requestConfig = await interceptRequest(\n requestConfig,\n this.config.onRequest,\n );\n\n // Axios compatibility\n if (this.isCustomFetcher()) {\n response = (await (this.requestInstance as any).request(\n requestConfig,\n )) as FetchResponse;\n } else {\n response = (await globalThis.fetch(\n requestConfig.url,\n requestConfig,\n )) as FetchResponse;\n\n // Attempt to collect response data regardless of response status\n const contentType = String(\n (response as Response)?.headers?.get('Content-Type') || '',\n );\n let data;\n\n // Handle edge case of no content type being provided... We assume json here.\n if (!contentType) {\n try {\n data = await response.json();\n // eslint-disable-next-line @typescript-eslint/no-unused-vars\n } catch (_error) {\n //\n }\n }\n\n if (typeof data === 'undefined') {\n if (contentType && contentType.includes('application/json')) {\n // Parse JSON response\n data = await response.json();\n } else if (typeof response.text !== 'undefined') {\n data = await response.text();\n } else if (typeof response.blob !== 'undefined') {\n data = await response.blob();\n } else {\n // Handle streams\n data = response.body || response.data || null;\n }\n }\n\n // Add more information to response object\n response.config = requestConfig;\n response.data = data;\n\n // Check if the response status is not outside the range 200-299 and if so, output error\n if (!response.ok) {\n throw new ResponseErr(\n `${requestConfig.url} failed! Status: ${response.status || null}`,\n requestConfig,\n response,\n );\n }\n }\n\n // Local interceptors\n response = await interceptResponse(response, requestConfig.onResponse);\n\n // Global interceptors\n response = await interceptResponse(response, this.config.onResponse);\n\n return this.outputResponse(response, requestConfig) as ResponseData &\n FetchResponse;\n } catch (error) {\n if (\n attempt === retries ||\n !(await shouldRetry(error, attempt)) ||\n !retryOn?.includes(error?.response?.status || error?.status)\n ) {\n this.processError(error, requestConfig);\n\n return this.outputErrorResponse(error, requestConfig);\n }\n\n if (this.logger?.warn) {\n this.logger.warn(\n `Attempt ${attempt + 1} failed. Retrying in ${waitTime}ms...`,\n );\n }\n\n await this.delay(waitTime);\n\n waitTime *= backoff;\n waitTime = Math.min(waitTime, maxDelay);\n attempt++;\n }\n }\n\n return this.outputResponse(response, requestConfig) as ResponseData &\n FetchResponse;\n }\n\n public async delay(ms: number): Promise {\n return new Promise((resolve) =>\n setTimeout(() => {\n return resolve(true);\n }, ms),\n );\n }\n\n public processHeaders(\n response: FetchResponse,\n ): HeadersObject {\n if (!response.headers) {\n return {};\n }\n\n let headersObject: HeadersObject = {};\n\n // Handle Headers object with entries() method\n if (response.headers instanceof Headers) {\n for (const [key, value] of (response.headers as any).entries()) {\n headersObject[key] = value;\n }\n } else {\n // Handle plain object\n headersObject = { ...(response.headers as HeadersObject) };\n }\n\n return headersObject;\n }\n\n /**\n * Output response\n *\n * @param response - Response payload\n * @param {RequestConfig} requestConfig - Request config\n * @param {*} error - whether the response is erroneous\n * @returns {ResponseData | FetchResponse} Response data\n */\n protected outputResponse(\n response: FetchResponse,\n requestConfig: RequestConfig,\n error = null,\n ): ResponseData | FetchResponse {\n const defaultResponse =\n typeof requestConfig.defaultResponse !== 'undefined'\n ? requestConfig.defaultResponse\n : this.defaultResponse;\n\n if (!response) {\n return defaultResponse;\n }\n\n if (\n (requestConfig.flattenResponse || this.flattenResponse) &&\n typeof response.data !== 'undefined'\n ) {\n // Special case of only data property within response data object (happens in Axios)\n // This is in fact a proper response but we may want to flatten it\n // To ease developers' lives when obtaining the response\n if (\n response.data !== null &&\n typeof response.data === 'object' &&\n typeof (response.data as any).data !== 'undefined' &&\n Object.keys(response.data).length === 1\n ) {\n return (response.data as any).data;\n }\n\n return response.data;\n }\n\n // If empty object is returned, ensure that the default response is used instead\n if (\n response !== null &&\n typeof response === 'object' &&\n response.constructor === Object &&\n Object.keys(response).length === 0\n ) {\n return defaultResponse;\n }\n\n const isCustomFetcher = this.isCustomFetcher();\n\n if (isCustomFetcher) {\n return response;\n }\n\n if (error !== null) {\n delete error?.response;\n delete error?.request;\n delete error?.config;\n }\n\n // Native fetch()\n return {\n ...response,\n error,\n headers: this.processHeaders(response),\n config: requestConfig,\n };\n }\n}\n","import { RequestHandler } from './request-handler';\nimport type {\n FetcherInstance,\n RequestConfig,\n FetchResponse,\n} from './types/request-handler';\nimport type {\n ApiHandlerConfig,\n ApiHandlerMethods,\n ApiHandlerReturnType,\n APIResponse,\n QueryParams,\n UrlPathParams,\n} from './types/api-handler';\n\n/**\n * Creates an instance of API Handler.\n * It creates an API fetcher function using native fetch() or Axios if it is passed as \"fetcher\".\n *\n * @param {Object} config - Configuration object for the API fetcher.\n * @param {string} config.apiUrl - The base URL for the API.\n * @param {Object} config.endpoints - An object containing endpoint definitions.\n * @param {number} config.timeout - You can set the timeout for particular request in milliseconds.\n * @param {number} config.cancellable - If true, the previous requests will be automatically cancelled.\n * @param {number} config.rejectCancelled - If true and request is set to cancellable, a cancelled request promise will be rejected. By default, instead of rejecting the promise, defaultResponse is returned.\n * @param {number} config.timeout - Request timeout\n * @param {string} config.strategy - Error Handling Strategy\n * @param {string} config.flattenResponse - Whether to flatten response \"data\" object within \"data\" one\n * @param {*} config.defaultResponse - Default response when there is no data or when endpoint fails depending on the chosen strategy. It's \"null\" by default\n * @param {Object} [config.retry] - Options for retrying requests.\n * @param {number} [config.retry.retries=0] - Number of retry attempts. No retries by default.\n * @param {number} [config.retry.delay=1000] - Initial delay between retries in milliseconds.\n * @param {number} [config.retry.backoff=1.5] - Exponential backoff factor.\n * @param {number[]} [config.retry.retryOn=[502, 504, 408]] - HTTP status codes to retry on.\n * @param {RequestInterceptor|RequestInterceptor[]} [config.onRequest] - Optional request interceptor function or an array of functions.\n * These functions will be called with the request configuration object before the request is made. Can be used to modify or log the request configuration.\n * @param {ResponseInterceptor|ResponseInterceptor[]} [config.onResponse] - Optional response interceptor function or an array of functions.\n * These functions will be called with the response object after the response is received. an be used to modify or log the response data.\n * @param {Function} [config.onError] - Optional callback function for handling errors.\n * @param {Object} [config.headers] - Optional default headers to include in every request.\n * @param {Object} config.fetcher - The Axios (or any other) instance to use for making requests.\n * @param {*} config.logger - Instance of custom logger. Either class or an object similar to \"console\". Console is used by default.\n * @returns API handler functions and endpoints to call\n *\n * @example\n * // Import axios (optional)\n * import axios from 'axios';\n *\n * // Define endpoint paths\n * const endpoints = {\n * getUser: '/user',\n * createPost: '/post',\n * };\n *\n * // Create the API fetcher with configuration\n * const api = createApiFetcher({\n * fetcher: axios, // Axios instance (optional)\n * endpoints,\n * apiUrl: 'https://example.com/api',\n * onError(error) {\n * console.log('Request failed', error);\n * },\n * headers: {\n * 'my-auth-key': 'example-auth-key-32rjjfa',\n * },\n * });\n *\n * // Fetch user data\n * const response = await api.getUser({ userId: 1, ratings: [1, 2] })\n */\nfunction createApiFetcher<\n EndpointsMethods extends object,\n EndpointsCfg = never,\n>(config: ApiHandlerConfig) {\n const endpoints = config.endpoints;\n const requestHandler = new RequestHandler(config);\n\n /**\n * Get Fetcher Provider Instance\n *\n * @returns {FetcherInstance} Request Handler's Fetcher instance\n */\n function getInstance(): FetcherInstance {\n return requestHandler.getInstance();\n }\n\n /**\n * Triggered when trying to use non-existent endpoints\n *\n * @param endpointName Endpoint Name\n * @returns {Promise}\n */\n function handleNonImplemented(endpointName: string): Promise {\n console.error(`${endpointName} endpoint must be added to 'endpoints'.`);\n\n return Promise.resolve(null);\n }\n\n /**\n * Handle Single API Request\n * It considers settings in following order: per-request settings, global per-endpoint settings, global settings.\n *\n * @param {string} endpointName - The name of the API endpoint to call.\n * @param {QueryParams} [queryParams={}] - Query parameters to include in the request.\n * @param {UrlPathParams} [urlPathParams={}] - URI parameters to include in the request.\n * @param {EndpointConfig} [requestConfig={}] - Additional configuration for the request.\n * @returns {Promise} - A promise that resolves with the response from the API provider.\n */\n async function request(\n endpointName: keyof EndpointsMethods | string,\n queryParams: QueryParams = {},\n urlPathParams: UrlPathParams = {},\n requestConfig: RequestConfig = {},\n ): Promise> {\n // Use global per-endpoint settings\n const endpointConfig = endpoints[endpointName as string];\n const endpointSettings = { ...endpointConfig };\n\n const responseData = await requestHandler.request(\n endpointSettings.url,\n queryParams,\n {\n ...endpointSettings,\n ...requestConfig,\n urlPathParams,\n },\n );\n\n return responseData;\n }\n\n /**\n * Maps all API requests using native Proxy\n *\n * @param {*} prop Caller\n */\n function get(prop: string | symbol) {\n if (prop in apiHandler) {\n return apiHandler[prop];\n }\n\n // Prevent handler from triggering non-existent endpoints\n if (!endpoints[prop as string]) {\n return handleNonImplemented.bind(null, prop);\n }\n\n return apiHandler.request.bind(null, prop);\n }\n\n const apiHandler: ApiHandlerMethods = {\n config,\n endpoints,\n requestHandler,\n getInstance,\n request,\n };\n\n return new Proxy(apiHandler, {\n get: (_target, prop) => get(prop),\n }) as ApiHandlerReturnType;\n}\n\nexport { createApiFetcher };\n"],"mappings":"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","names":["src_exports","__export","createApiFetcher","fetchf","__toCommonJS","interceptRequest","config","interceptors","interceptorList","interceptedConfig","interceptor","interceptResponse","response","interceptedResponse","ResponseErr","message","requestInfo","response","RequestHandler","fetcher","timeout","rejectCancelled","strategy","flattenResponse","defaultResponse","logger","onError","config","url","urlPathParams","str","word","params","queryString","key","value","val","proto","data","method","methodLowerCase","isGetAlikeMethod","dynamicUrl","configData","payload","credentials","urlPath","baseURL","error","requestConfig","_a","isRequestCancelled","errorHandlingStrategy","previousRequest","controller","abortTimeout","_b","_c","response","_config","_requestConfig","retries","delay","backoff","retryOn","shouldRetry","maxDelay","attempt","waitTime","interceptRequest","contentType","ResponseErr","interceptResponse","ms","resolve","headersObject","createApiFetcher","config","endpoints","requestHandler","RequestHandler","getInstance","handleNonImplemented","endpointName","request","queryParams","urlPathParams","requestConfig","endpointSettings","get","prop","apiHandler","_target","fetchf","url","config","RequestHandler"]} \ No newline at end of file diff --git a/docs/examples/examples.ts b/docs/examples/examples.ts index 7130a83..220512f 100644 --- a/docs/examples/examples.ts +++ b/docs/examples/examples.ts @@ -161,8 +161,12 @@ async function example4() { 'https://example.com/api/custom-endpoint', ); + const data3 = await fetchf<{ myData: true }>( + 'https://example.com/api/custom-endpoint', + ); + console.log('Example 4', books); - console.log('Example 4', data1, data2); + console.log('Example 4', data1, data2, data3); } // createApiFetcher() - direct API request() call to a custom endpoint with flattenResponse == false diff --git a/package-lock.json b/package-lock.json index c0bf4cf..accc9b9 100644 --- a/package-lock.json +++ b/package-lock.json @@ -11,20 +11,20 @@ "devDependencies": { "@size-limit/preset-small-lib": "11.1.4", "@types/jest": "29.5.12", - "eslint": "9.6.0", + "eslint": "9.8.0", "eslint-config-prettier": "9.1.0", - "eslint-plugin-prettier": "5.1.3", - "fetch-mock": "10.1.1", + "eslint-plugin-prettier": "5.2.1", + "fetch-mock": "11.0.0", "jest": "29.7.0", - "prettier": "3.3.2", + "prettier": "3.3.3", "promise-any": "0.2.0", "rollup-plugin-bundle-imports": "1.5.1", "size-limit": "11.1.4", - "ts-jest": "29.1.5", + "ts-jest": "29.2.4", "tslib": "2.6.3", - "tsup": "8.1.0", + "tsup": "8.2.4", "typescript": "5.5.4", - "typescript-eslint": "8.0.0-alpha.54" + "typescript-eslint": "8.0.1" }, "engines": { "node": ">=18" @@ -991,6 +991,23 @@ "node": ">=12" } }, + "node_modules/@esbuild/openbsd-arm64": { + "version": "0.23.0", + "resolved": "https://registry.npmjs.org/@esbuild/openbsd-arm64/-/openbsd-arm64-0.23.0.tgz", + "integrity": "sha512-suXjq53gERueVWu0OKxzWqk7NxiUWSUlrxoZK7usiF50C6ipColGR5qie2496iKGYNLhDZkPxBI3erbnYkU0rQ==", + "cpu": [ + "arm64" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "openbsd" + ], + "engines": { + "node": ">=18" + } + }, "node_modules/@esbuild/openbsd-x64": { "version": "0.21.5", "resolved": "https://registry.npmjs.org/@esbuild/openbsd-x64/-/openbsd-x64-0.21.5.tgz", @@ -1103,9 +1120,9 @@ } }, "node_modules/@eslint/config-array": { - "version": "0.17.0", - "resolved": "https://registry.npmjs.org/@eslint/config-array/-/config-array-0.17.0.tgz", - "integrity": "sha512-A68TBu6/1mHHuc5YJL0U0VVeGNiklLAL6rRmhTCP2B5XjWLMnrX+HkO+IAXyHvks5cyyY1jjK5ITPQ1HGS2EVA==", + "version": "0.17.1", + "resolved": "https://registry.npmjs.org/@eslint/config-array/-/config-array-0.17.1.tgz", + "integrity": "sha512-BlYOpej8AQ8Ev9xVqroV7a02JK3SkBAaN9GfMMH9W6Ch8FlQlkjGw4Ir7+FgYwfirivAf4t+GtzuAxqfukmISA==", "dev": true, "license": "Apache-2.0", "dependencies": { @@ -1142,9 +1159,9 @@ } }, "node_modules/@eslint/js": { - "version": "9.6.0", - "resolved": "https://registry.npmjs.org/@eslint/js/-/js-9.6.0.tgz", - "integrity": "sha512-D9B0/3vNg44ZeWbYMpBoXqNP4j6eQD5vNwIlGAuFRRzK/WtT/jvDQW3Bi9kkf3PMDMlM7Yi+73VLUsn5bJcl8A==", + "version": "9.8.0", + "resolved": "https://registry.npmjs.org/@eslint/js/-/js-9.8.0.tgz", + "integrity": "sha512-MfluB7EUfxXtv3i/++oh89uzAr4PDI4nn201hsp+qaXqsjAWzinlZEHEfPgAX4doIlKvPG/i0A9dpKxOLII8yA==", "dev": true, "license": "MIT", "engines": { @@ -1889,9 +1906,9 @@ "peer": true }, "node_modules/@rollup/rollup-android-arm-eabi": { - "version": "4.18.0", - "resolved": "https://registry.npmjs.org/@rollup/rollup-android-arm-eabi/-/rollup-android-arm-eabi-4.18.0.tgz", - "integrity": "sha512-Tya6xypR10giZV1XzxmH5wr25VcZSncG0pZIjfePT0OVBvqNEurzValetGNarVrGiq66EBVAFn15iYX4w6FKgQ==", + "version": "4.20.0", + "resolved": "https://registry.npmjs.org/@rollup/rollup-android-arm-eabi/-/rollup-android-arm-eabi-4.20.0.tgz", + "integrity": "sha512-TSpWzflCc4VGAUJZlPpgAJE1+V60MePDQnBd7PPkpuEmOy8i87aL6tinFGKBFKuEDikYpig72QzdT3QPYIi+oA==", "cpu": [ "arm" ], @@ -1903,9 +1920,9 @@ ] }, "node_modules/@rollup/rollup-android-arm64": { - "version": "4.18.0", - "resolved": "https://registry.npmjs.org/@rollup/rollup-android-arm64/-/rollup-android-arm64-4.18.0.tgz", - "integrity": "sha512-avCea0RAP03lTsDhEyfy+hpfr85KfyTctMADqHVhLAF3MlIkq83CP8UfAHUssgXTYd+6er6PaAhx/QGv4L1EiA==", + "version": "4.20.0", + "resolved": "https://registry.npmjs.org/@rollup/rollup-android-arm64/-/rollup-android-arm64-4.20.0.tgz", + "integrity": "sha512-u00Ro/nok7oGzVuh/FMYfNoGqxU5CPWz1mxV85S2w9LxHR8OoMQBuSk+3BKVIDYgkpeOET5yXkx90OYFc+ytpQ==", "cpu": [ "arm64" ], @@ -1917,9 +1934,9 @@ ] }, "node_modules/@rollup/rollup-darwin-arm64": { - "version": "4.18.0", - "resolved": "https://registry.npmjs.org/@rollup/rollup-darwin-arm64/-/rollup-darwin-arm64-4.18.0.tgz", - "integrity": "sha512-IWfdwU7KDSm07Ty0PuA/W2JYoZ4iTj3TUQjkVsO/6U+4I1jN5lcR71ZEvRh52sDOERdnNhhHU57UITXz5jC1/w==", + "version": "4.20.0", + "resolved": "https://registry.npmjs.org/@rollup/rollup-darwin-arm64/-/rollup-darwin-arm64-4.20.0.tgz", + "integrity": "sha512-uFVfvzvsdGtlSLuL0ZlvPJvl6ZmrH4CBwLGEFPe7hUmf7htGAN+aXo43R/V6LATyxlKVC/m6UsLb7jbG+LG39Q==", "cpu": [ "arm64" ], @@ -1931,9 +1948,9 @@ ] }, "node_modules/@rollup/rollup-darwin-x64": { - "version": "4.18.0", - "resolved": "https://registry.npmjs.org/@rollup/rollup-darwin-x64/-/rollup-darwin-x64-4.18.0.tgz", - "integrity": "sha512-n2LMsUz7Ynu7DoQrSQkBf8iNrjOGyPLrdSg802vk6XT3FtsgX6JbE8IHRvposskFm9SNxzkLYGSq9QdpLYpRNA==", + "version": "4.20.0", + "resolved": "https://registry.npmjs.org/@rollup/rollup-darwin-x64/-/rollup-darwin-x64-4.20.0.tgz", + "integrity": "sha512-xbrMDdlev53vNXexEa6l0LffojxhqDTBeL+VUxuuIXys4x6xyvbKq5XqTXBCEUA8ty8iEJblHvFaWRJTk/icAQ==", "cpu": [ "x64" ], @@ -1945,9 +1962,9 @@ ] }, "node_modules/@rollup/rollup-linux-arm-gnueabihf": { - "version": "4.18.0", - "resolved": "https://registry.npmjs.org/@rollup/rollup-linux-arm-gnueabihf/-/rollup-linux-arm-gnueabihf-4.18.0.tgz", - "integrity": "sha512-C/zbRYRXFjWvz9Z4haRxcTdnkPt1BtCkz+7RtBSuNmKzMzp3ZxdM28Mpccn6pt28/UWUCTXa+b0Mx1k3g6NOMA==", + "version": "4.20.0", + "resolved": "https://registry.npmjs.org/@rollup/rollup-linux-arm-gnueabihf/-/rollup-linux-arm-gnueabihf-4.20.0.tgz", + "integrity": "sha512-jMYvxZwGmoHFBTbr12Xc6wOdc2xA5tF5F2q6t7Rcfab68TT0n+r7dgawD4qhPEvasDsVpQi+MgDzj2faOLsZjA==", "cpu": [ "arm" ], @@ -1959,9 +1976,9 @@ ] }, "node_modules/@rollup/rollup-linux-arm-musleabihf": { - "version": "4.18.0", - "resolved": "https://registry.npmjs.org/@rollup/rollup-linux-arm-musleabihf/-/rollup-linux-arm-musleabihf-4.18.0.tgz", - "integrity": "sha512-l3m9ewPgjQSXrUMHg93vt0hYCGnrMOcUpTz6FLtbwljo2HluS4zTXFy2571YQbisTnfTKPZ01u/ukJdQTLGh9A==", + "version": "4.20.0", + "resolved": "https://registry.npmjs.org/@rollup/rollup-linux-arm-musleabihf/-/rollup-linux-arm-musleabihf-4.20.0.tgz", + "integrity": "sha512-1asSTl4HKuIHIB1GcdFHNNZhxAYEdqML/MW4QmPS4G0ivbEcBr1JKlFLKsIRqjSwOBkdItn3/ZDlyvZ/N6KPlw==", "cpu": [ "arm" ], @@ -1973,9 +1990,9 @@ ] }, "node_modules/@rollup/rollup-linux-arm64-gnu": { - "version": "4.18.0", - "resolved": "https://registry.npmjs.org/@rollup/rollup-linux-arm64-gnu/-/rollup-linux-arm64-gnu-4.18.0.tgz", - "integrity": "sha512-rJ5D47d8WD7J+7STKdCUAgmQk49xuFrRi9pZkWoRD1UeSMakbcepWXPF8ycChBoAqs1pb2wzvbY6Q33WmN2ftw==", + "version": "4.20.0", + "resolved": "https://registry.npmjs.org/@rollup/rollup-linux-arm64-gnu/-/rollup-linux-arm64-gnu-4.20.0.tgz", + "integrity": "sha512-COBb8Bkx56KldOYJfMf6wKeYJrtJ9vEgBRAOkfw6Ens0tnmzPqvlpjZiLgkhg6cA3DGzCmLmmd319pmHvKWWlQ==", "cpu": [ "arm64" ], @@ -1987,9 +2004,9 @@ ] }, "node_modules/@rollup/rollup-linux-arm64-musl": { - "version": "4.18.0", - "resolved": "https://registry.npmjs.org/@rollup/rollup-linux-arm64-musl/-/rollup-linux-arm64-musl-4.18.0.tgz", - "integrity": "sha512-be6Yx37b24ZwxQ+wOQXXLZqpq4jTckJhtGlWGZs68TgdKXJgw54lUUoFYrg6Zs/kjzAQwEwYbp8JxZVzZLRepQ==", + "version": "4.20.0", + "resolved": "https://registry.npmjs.org/@rollup/rollup-linux-arm64-musl/-/rollup-linux-arm64-musl-4.20.0.tgz", + "integrity": "sha512-+it+mBSyMslVQa8wSPvBx53fYuZK/oLTu5RJoXogjk6x7Q7sz1GNRsXWjn6SwyJm8E/oMjNVwPhmNdIjwP135Q==", "cpu": [ "arm64" ], @@ -2001,9 +2018,9 @@ ] }, "node_modules/@rollup/rollup-linux-powerpc64le-gnu": { - "version": "4.18.0", - "resolved": "https://registry.npmjs.org/@rollup/rollup-linux-powerpc64le-gnu/-/rollup-linux-powerpc64le-gnu-4.18.0.tgz", - "integrity": "sha512-hNVMQK+qrA9Todu9+wqrXOHxFiD5YmdEi3paj6vP02Kx1hjd2LLYR2eaN7DsEshg09+9uzWi2W18MJDlG0cxJA==", + "version": "4.20.0", + "resolved": "https://registry.npmjs.org/@rollup/rollup-linux-powerpc64le-gnu/-/rollup-linux-powerpc64le-gnu-4.20.0.tgz", + "integrity": "sha512-yAMvqhPfGKsAxHN8I4+jE0CpLWD8cv4z7CK7BMmhjDuz606Q2tFKkWRY8bHR9JQXYcoLfopo5TTqzxgPUjUMfw==", "cpu": [ "ppc64" ], @@ -2015,9 +2032,9 @@ ] }, "node_modules/@rollup/rollup-linux-riscv64-gnu": { - "version": "4.18.0", - "resolved": "https://registry.npmjs.org/@rollup/rollup-linux-riscv64-gnu/-/rollup-linux-riscv64-gnu-4.18.0.tgz", - "integrity": "sha512-ROCM7i+m1NfdrsmvwSzoxp9HFtmKGHEqu5NNDiZWQtXLA8S5HBCkVvKAxJ8U+CVctHwV2Gb5VUaK7UAkzhDjlg==", + "version": "4.20.0", + "resolved": "https://registry.npmjs.org/@rollup/rollup-linux-riscv64-gnu/-/rollup-linux-riscv64-gnu-4.20.0.tgz", + "integrity": "sha512-qmuxFpfmi/2SUkAw95TtNq/w/I7Gpjurx609OOOV7U4vhvUhBcftcmXwl3rqAek+ADBwSjIC4IVNLiszoj3dPA==", "cpu": [ "riscv64" ], @@ -2029,9 +2046,9 @@ ] }, "node_modules/@rollup/rollup-linux-s390x-gnu": { - "version": "4.18.0", - "resolved": "https://registry.npmjs.org/@rollup/rollup-linux-s390x-gnu/-/rollup-linux-s390x-gnu-4.18.0.tgz", - "integrity": "sha512-0UyyRHyDN42QL+NbqevXIIUnKA47A+45WyasO+y2bGJ1mhQrfrtXUpTxCOrfxCR4esV3/RLYyucGVPiUsO8xjg==", + "version": "4.20.0", + "resolved": "https://registry.npmjs.org/@rollup/rollup-linux-s390x-gnu/-/rollup-linux-s390x-gnu-4.20.0.tgz", + "integrity": "sha512-I0BtGXddHSHjV1mqTNkgUZLnS3WtsqebAXv11D5BZE/gfw5KoyXSAXVqyJximQXNvNzUo4GKlCK/dIwXlz+jlg==", "cpu": [ "s390x" ], @@ -2043,9 +2060,9 @@ ] }, "node_modules/@rollup/rollup-linux-x64-gnu": { - "version": "4.18.0", - "resolved": "https://registry.npmjs.org/@rollup/rollup-linux-x64-gnu/-/rollup-linux-x64-gnu-4.18.0.tgz", - "integrity": "sha512-xuglR2rBVHA5UsI8h8UbX4VJ470PtGCf5Vpswh7p2ukaqBGFTnsfzxUBetoWBWymHMxbIG0Cmx7Y9qDZzr648w==", + "version": "4.20.0", + "resolved": "https://registry.npmjs.org/@rollup/rollup-linux-x64-gnu/-/rollup-linux-x64-gnu-4.20.0.tgz", + "integrity": "sha512-y+eoL2I3iphUg9tN9GB6ku1FA8kOfmF4oUEWhztDJ4KXJy1agk/9+pejOuZkNFhRwHAOxMsBPLbXPd6mJiCwew==", "cpu": [ "x64" ], @@ -2057,9 +2074,9 @@ ] }, "node_modules/@rollup/rollup-linux-x64-musl": { - "version": "4.18.0", - "resolved": "https://registry.npmjs.org/@rollup/rollup-linux-x64-musl/-/rollup-linux-x64-musl-4.18.0.tgz", - "integrity": "sha512-LKaqQL9osY/ir2geuLVvRRs+utWUNilzdE90TpyoX0eNqPzWjRm14oMEE+YLve4k/NAqCdPkGYDaDF5Sw+xBfg==", + "version": "4.20.0", + "resolved": "https://registry.npmjs.org/@rollup/rollup-linux-x64-musl/-/rollup-linux-x64-musl-4.20.0.tgz", + "integrity": "sha512-hM3nhW40kBNYUkZb/r9k2FKK+/MnKglX7UYd4ZUy5DJs8/sMsIbqWK2piZtVGE3kcXVNj3B2IrUYROJMMCikNg==", "cpu": [ "x64" ], @@ -2071,9 +2088,9 @@ ] }, "node_modules/@rollup/rollup-win32-arm64-msvc": { - "version": "4.18.0", - "resolved": "https://registry.npmjs.org/@rollup/rollup-win32-arm64-msvc/-/rollup-win32-arm64-msvc-4.18.0.tgz", - "integrity": "sha512-7J6TkZQFGo9qBKH0pk2cEVSRhJbL6MtfWxth7Y5YmZs57Pi+4x6c2dStAUvaQkHQLnEQv1jzBUW43GvZW8OFqA==", + "version": "4.20.0", + "resolved": "https://registry.npmjs.org/@rollup/rollup-win32-arm64-msvc/-/rollup-win32-arm64-msvc-4.20.0.tgz", + "integrity": "sha512-psegMvP+Ik/Bg7QRJbv8w8PAytPA7Uo8fpFjXyCRHWm6Nt42L+JtoqH8eDQ5hRP7/XW2UiIriy1Z46jf0Oa1kA==", "cpu": [ "arm64" ], @@ -2085,9 +2102,9 @@ ] }, "node_modules/@rollup/rollup-win32-ia32-msvc": { - "version": "4.18.0", - "resolved": "https://registry.npmjs.org/@rollup/rollup-win32-ia32-msvc/-/rollup-win32-ia32-msvc-4.18.0.tgz", - "integrity": "sha512-Txjh+IxBPbkUB9+SXZMpv+b/vnTEtFyfWZgJ6iyCmt2tdx0OF5WhFowLmnh8ENGNpfUlUZkdI//4IEmhwPieNg==", + "version": "4.20.0", + "resolved": "https://registry.npmjs.org/@rollup/rollup-win32-ia32-msvc/-/rollup-win32-ia32-msvc-4.20.0.tgz", + "integrity": "sha512-GabekH3w4lgAJpVxkk7hUzUf2hICSQO0a/BLFA11/RMxQT92MabKAqyubzDZmMOC/hcJNlc+rrypzNzYl4Dx7A==", "cpu": [ "ia32" ], @@ -2099,9 +2116,9 @@ ] }, "node_modules/@rollup/rollup-win32-x64-msvc": { - "version": "4.18.0", - "resolved": "https://registry.npmjs.org/@rollup/rollup-win32-x64-msvc/-/rollup-win32-x64-msvc-4.18.0.tgz", - "integrity": "sha512-UOo5FdvOL0+eIVTgS4tIdbW+TtnBLWg1YBCcU2KWM7nuNwRz9bksDX1bekJJCpu25N1DVWaCwnT39dVQxzqS8g==", + "version": "4.20.0", + "resolved": "https://registry.npmjs.org/@rollup/rollup-win32-x64-msvc/-/rollup-win32-x64-msvc-4.20.0.tgz", + "integrity": "sha512-aJ1EJSuTdGnM6qbVC4B5DSmozPTqIag9fSzXRNNo+humQLG89XpPgdt16Ia56ORD7s+H8Pmyx44uczDQ0yDzpg==", "cpu": [ "x64" ], @@ -2380,17 +2397,17 @@ "license": "MIT" }, "node_modules/@typescript-eslint/eslint-plugin": { - "version": "8.0.0-alpha.54", - "resolved": "https://registry.npmjs.org/@typescript-eslint/eslint-plugin/-/eslint-plugin-8.0.0-alpha.54.tgz", - "integrity": "sha512-JBuk5rdo9XfoAc797uPh2QdzfnbQmYTnOZ//IKiXm96a2AzS05VmXSVka4GQyrp7giGWSNjW6y2wPpsWheqd9Q==", + "version": "8.0.1", + "resolved": "https://registry.npmjs.org/@typescript-eslint/eslint-plugin/-/eslint-plugin-8.0.1.tgz", + "integrity": "sha512-5g3Y7GDFsJAnY4Yhvk8sZtFfV6YNF2caLzjrRPUBzewjPCaj0yokePB4LJSobyCzGMzjZZYFbwuzbfDHlimXbQ==", "dev": true, "license": "MIT", "dependencies": { "@eslint-community/regexpp": "^4.10.0", - "@typescript-eslint/scope-manager": "8.0.0-alpha.54", - "@typescript-eslint/type-utils": "8.0.0-alpha.54", - "@typescript-eslint/utils": "8.0.0-alpha.54", - "@typescript-eslint/visitor-keys": "8.0.0-alpha.54", + "@typescript-eslint/scope-manager": "8.0.1", + "@typescript-eslint/type-utils": "8.0.1", + "@typescript-eslint/utils": "8.0.1", + "@typescript-eslint/visitor-keys": "8.0.1", "graphemer": "^1.4.0", "ignore": "^5.3.1", "natural-compare": "^1.4.0", @@ -2414,16 +2431,16 @@ } }, "node_modules/@typescript-eslint/parser": { - "version": "8.0.0-alpha.54", - "resolved": "https://registry.npmjs.org/@typescript-eslint/parser/-/parser-8.0.0-alpha.54.tgz", - "integrity": "sha512-473V2mTNH+KPNVPj8MIGizDXmmJ56gpYsh+ILa8uEWUYMhvE0DNnozEt59TonS1Y9D15AJZGas6+1hcpQ77Dbg==", + "version": "8.0.1", + "resolved": "https://registry.npmjs.org/@typescript-eslint/parser/-/parser-8.0.1.tgz", + "integrity": "sha512-5IgYJ9EO/12pOUwiBKFkpU7rS3IU21mtXzB81TNwq2xEybcmAZrE9qwDtsb5uQd9aVO9o0fdabFyAmKveXyujg==", "dev": true, "license": "BSD-2-Clause", "dependencies": { - "@typescript-eslint/scope-manager": "8.0.0-alpha.54", - "@typescript-eslint/types": "8.0.0-alpha.54", - "@typescript-eslint/typescript-estree": "8.0.0-alpha.54", - "@typescript-eslint/visitor-keys": "8.0.0-alpha.54", + "@typescript-eslint/scope-manager": "8.0.1", + "@typescript-eslint/types": "8.0.1", + "@typescript-eslint/typescript-estree": "8.0.1", + "@typescript-eslint/visitor-keys": "8.0.1", "debug": "^4.3.4" }, "engines": { @@ -2443,14 +2460,14 @@ } }, "node_modules/@typescript-eslint/scope-manager": { - "version": "8.0.0-alpha.54", - "resolved": "https://registry.npmjs.org/@typescript-eslint/scope-manager/-/scope-manager-8.0.0-alpha.54.tgz", - "integrity": "sha512-z+5GlCAskUTTWOFF2G7olTyKZyn+AVdDkiNCP2fhDtOCV1ePX1EaXy1uwqRRROf8p8uryj7vR7OtIErZE5yAng==", + "version": "8.0.1", + "resolved": "https://registry.npmjs.org/@typescript-eslint/scope-manager/-/scope-manager-8.0.1.tgz", + "integrity": "sha512-NpixInP5dm7uukMiRyiHjRKkom5RIFA4dfiHvalanD2cF0CLUuQqxfg8PtEUo9yqJI2bBhF+pcSafqnG3UBnRQ==", "dev": true, "license": "MIT", "dependencies": { - "@typescript-eslint/types": "8.0.0-alpha.54", - "@typescript-eslint/visitor-keys": "8.0.0-alpha.54" + "@typescript-eslint/types": "8.0.1", + "@typescript-eslint/visitor-keys": "8.0.1" }, "engines": { "node": "^18.18.0 || ^20.9.0 || >=21.1.0" @@ -2461,14 +2478,14 @@ } }, "node_modules/@typescript-eslint/type-utils": { - "version": "8.0.0-alpha.54", - "resolved": "https://registry.npmjs.org/@typescript-eslint/type-utils/-/type-utils-8.0.0-alpha.54.tgz", - "integrity": "sha512-aGqNg1vP3a1tAE7lN8VDw+JhAefhqotMEcxw+2NKQm3vG4BqzIQNeF87xle9+94t8MPPmUPzRjRmO7GySu8LRg==", + "version": "8.0.1", + "resolved": "https://registry.npmjs.org/@typescript-eslint/type-utils/-/type-utils-8.0.1.tgz", + "integrity": "sha512-+/UT25MWvXeDX9YaHv1IS6KI1fiuTto43WprE7pgSMswHbn1Jm9GEM4Txp+X74ifOWV8emu2AWcbLhpJAvD5Ng==", "dev": true, "license": "MIT", "dependencies": { - "@typescript-eslint/typescript-estree": "8.0.0-alpha.54", - "@typescript-eslint/utils": "8.0.0-alpha.54", + "@typescript-eslint/typescript-estree": "8.0.1", + "@typescript-eslint/utils": "8.0.1", "debug": "^4.3.4", "ts-api-utils": "^1.3.0" }, @@ -2486,9 +2503,9 @@ } }, "node_modules/@typescript-eslint/types": { - "version": "8.0.0-alpha.54", - "resolved": "https://registry.npmjs.org/@typescript-eslint/types/-/types-8.0.0-alpha.54.tgz", - "integrity": "sha512-p4CGzb2UW2tJgk7zRL1Iwyd4qMuPnF2TL5/VdEcz2KANHkTReagQ6B3MzJGcuNIK7t+ysolZZILJpj+8cHBzsQ==", + "version": "8.0.1", + "resolved": "https://registry.npmjs.org/@typescript-eslint/types/-/types-8.0.1.tgz", + "integrity": "sha512-PpqTVT3yCA/bIgJ12czBuE3iBlM3g4inRSC5J0QOdQFAn07TYrYEQBBKgXH1lQpglup+Zy6c1fxuwTk4MTNKIw==", "dev": true, "license": "MIT", "engines": { @@ -2500,14 +2517,14 @@ } }, "node_modules/@typescript-eslint/typescript-estree": { - "version": "8.0.0-alpha.54", - "resolved": "https://registry.npmjs.org/@typescript-eslint/typescript-estree/-/typescript-estree-8.0.0-alpha.54.tgz", - "integrity": "sha512-oCgHCQm88pBx9QwfGVE42LXVRG3M5PUIP4w521yGMijHn5FEt+E/NGMPU3NXWKUvp0LpEkxABSinYdz69aZITA==", + "version": "8.0.1", + "resolved": "https://registry.npmjs.org/@typescript-eslint/typescript-estree/-/typescript-estree-8.0.1.tgz", + "integrity": "sha512-8V9hriRvZQXPWU3bbiUV4Epo7EvgM6RTs+sUmxp5G//dBGy402S7Fx0W0QkB2fb4obCF8SInoUzvTYtc3bkb5w==", "dev": true, "license": "BSD-2-Clause", "dependencies": { - "@typescript-eslint/types": "8.0.0-alpha.54", - "@typescript-eslint/visitor-keys": "8.0.0-alpha.54", + "@typescript-eslint/types": "8.0.1", + "@typescript-eslint/visitor-keys": "8.0.1", "debug": "^4.3.4", "globby": "^11.1.0", "is-glob": "^4.0.3", @@ -2589,16 +2606,16 @@ } }, "node_modules/@typescript-eslint/utils": { - "version": "8.0.0-alpha.54", - "resolved": "https://registry.npmjs.org/@typescript-eslint/utils/-/utils-8.0.0-alpha.54.tgz", - "integrity": "sha512-Xu+dl3SJ4NOuzSHpRj1nIJPsoNTcPuG6EFVolrEVl+GZReaiBdexwpTo4/gV64khZEVewEIdYV3FBs5elIjI0g==", + "version": "8.0.1", + "resolved": "https://registry.npmjs.org/@typescript-eslint/utils/-/utils-8.0.1.tgz", + "integrity": "sha512-CBFR0G0sCt0+fzfnKaciu9IBsKvEKYwN9UZ+eeogK1fYHg4Qxk1yf/wLQkLXlq8wbU2dFlgAesxt8Gi76E8RTA==", "dev": true, "license": "MIT", "dependencies": { "@eslint-community/eslint-utils": "^4.4.0", - "@typescript-eslint/scope-manager": "8.0.0-alpha.54", - "@typescript-eslint/types": "8.0.0-alpha.54", - "@typescript-eslint/typescript-estree": "8.0.0-alpha.54" + "@typescript-eslint/scope-manager": "8.0.1", + "@typescript-eslint/types": "8.0.1", + "@typescript-eslint/typescript-estree": "8.0.1" }, "engines": { "node": "^18.18.0 || ^20.9.0 || >=21.1.0" @@ -2612,13 +2629,13 @@ } }, "node_modules/@typescript-eslint/visitor-keys": { - "version": "8.0.0-alpha.54", - "resolved": "https://registry.npmjs.org/@typescript-eslint/visitor-keys/-/visitor-keys-8.0.0-alpha.54.tgz", - "integrity": "sha512-lS8wrI6Vxw6ebIi+ehocAjXLG43bN5JCC8+wtGDD3Xw9O/EVpanAVdftefJs/mlK87eyccvVIiiHgD294TpIEQ==", + "version": "8.0.1", + "resolved": "https://registry.npmjs.org/@typescript-eslint/visitor-keys/-/visitor-keys-8.0.1.tgz", + "integrity": "sha512-W5E+o0UfUcK5EgchLZsyVWqARmsM7v54/qEq6PY3YI5arkgmCzHiuk0zKSJJbm71V0xdRna4BGomkCTXz2/LkQ==", "dev": true, "license": "MIT", "dependencies": { - "@typescript-eslint/types": "8.0.0-alpha.54", + "@typescript-eslint/types": "8.0.1", "eslint-visitor-keys": "^3.4.3" }, "engines": { @@ -2786,6 +2803,13 @@ "node": ">=8" } }, + "node_modules/async": { + "version": "3.2.5", + "resolved": "https://registry.npmjs.org/async/-/async-3.2.5.tgz", + "integrity": "sha512-baNZyqaaLhyLVKm/DlvdW051MSgO6b8eVfIezl9E5PqWxFgzLm/wQntEW4zOytVburDEr0JlALEpdOFwvErLsg==", + "dev": true, + "license": "MIT" + }, "node_modules/asynckit": { "version": "0.4.0", "resolved": "https://registry.npmjs.org/asynckit/-/asynckit-0.4.0.tgz", @@ -3040,9 +3064,9 @@ } }, "node_modules/bundle-require": { - "version": "4.2.1", - "resolved": "https://registry.npmjs.org/bundle-require/-/bundle-require-4.2.1.tgz", - "integrity": "sha512-7Q/6vkyYAwOmQNRw75x+4yRtZCZJXUDmHHlFdkiV0wgv/reNjtJwpu1jPJ0w2kbEpIM0uoKI3S4/f39dU7AjSA==", + "version": "5.0.0", + "resolved": "https://registry.npmjs.org/bundle-require/-/bundle-require-5.0.0.tgz", + "integrity": "sha512-GuziW3fSSmopcx4KRymQEJVbZUfqlCqcq7dvs6TYwKRZiegK/2buMxQTPs6MGlNv50wms1699qYO54R8XfRX4w==", "dev": true, "license": "MIT", "dependencies": { @@ -3052,7 +3076,7 @@ "node": "^12.20.0 || ^14.13.1 || >=16.0.0" }, "peerDependencies": { - "esbuild": ">=0.17" + "esbuild": ">=0.18" } }, "node_modules/bytes-iec": { @@ -3295,6 +3319,16 @@ "dev": true, "license": "MIT" }, + "node_modules/consola": { + "version": "3.2.3", + "resolved": "https://registry.npmjs.org/consola/-/consola-3.2.3.tgz", + "integrity": "sha512-I5qxpzLv+sJhTVEoLYNcTW+bThDCPsit0vLNKShZx6rLtpilNpmmeTPaeqJb9ZE9dV3DGaeby6Vuhrw38WjeyQ==", + "dev": true, + "license": "MIT", + "engines": { + "node": "^14.18.0 || >=16.10.0" + } + }, "node_modules/convert-source-map": { "version": "2.0.0", "resolved": "https://registry.npmjs.org/convert-source-map/-/convert-source-map-2.0.0.tgz", @@ -3480,6 +3514,22 @@ "dev": true, "license": "MIT" }, + "node_modules/ejs": { + "version": "3.1.10", + "resolved": "https://registry.npmjs.org/ejs/-/ejs-3.1.10.tgz", + "integrity": "sha512-UeJmFfOrAQS8OJWPZ4qtgHyWExa088/MtK5UEyoJGFH67cDEXkZSviOiKRCZ4Xij0zxI3JECgYs3oKx+AizQBA==", + "dev": true, + "license": "Apache-2.0", + "dependencies": { + "jake": "^10.8.5" + }, + "bin": { + "ejs": "bin/cli.js" + }, + "engines": { + "node": ">=0.10.0" + } + }, "node_modules/electron-to-chromium": { "version": "1.4.816", "resolved": "https://registry.npmjs.org/electron-to-chromium/-/electron-to-chromium-1.4.816.tgz", @@ -3580,17 +3630,17 @@ } }, "node_modules/eslint": { - "version": "9.6.0", - "resolved": "https://registry.npmjs.org/eslint/-/eslint-9.6.0.tgz", - "integrity": "sha512-ElQkdLMEEqQNM9Njff+2Y4q2afHk7JpkPvrd7Xh7xefwgQynqPxwf55J7di9+MEibWUGdNjFF9ITG9Pck5M84w==", + "version": "9.8.0", + "resolved": "https://registry.npmjs.org/eslint/-/eslint-9.8.0.tgz", + "integrity": "sha512-K8qnZ/QJzT2dLKdZJVX6W4XOwBzutMYmt0lqUS+JdXgd+HTYFlonFgkJ8s44d/zMPPCnOOk0kMWCApCPhiOy9A==", "dev": true, "license": "MIT", "dependencies": { "@eslint-community/eslint-utils": "^4.2.0", - "@eslint-community/regexpp": "^4.6.1", - "@eslint/config-array": "^0.17.0", + "@eslint-community/regexpp": "^4.11.0", + "@eslint/config-array": "^0.17.1", "@eslint/eslintrc": "^3.1.0", - "@eslint/js": "9.6.0", + "@eslint/js": "9.8.0", "@humanwhocodes/module-importer": "^1.0.1", "@humanwhocodes/retry": "^0.3.0", "@nodelib/fs.walk": "^1.2.8", @@ -3599,7 +3649,7 @@ "cross-spawn": "^7.0.2", "debug": "^4.3.2", "escape-string-regexp": "^4.0.0", - "eslint-scope": "^8.0.1", + "eslint-scope": "^8.0.2", "eslint-visitor-keys": "^4.0.0", "espree": "^10.1.0", "esquery": "^1.5.0", @@ -3645,14 +3695,14 @@ } }, "node_modules/eslint-plugin-prettier": { - "version": "5.1.3", - "resolved": "https://registry.npmjs.org/eslint-plugin-prettier/-/eslint-plugin-prettier-5.1.3.tgz", - "integrity": "sha512-C9GCVAs4Eq7ZC/XFQHITLiHJxQngdtraXaM+LoUFoFp/lHNl2Zn8f3WQbe9HvTBBQ9YnKFB0/2Ajdqwo5D1EAw==", + "version": "5.2.1", + "resolved": "https://registry.npmjs.org/eslint-plugin-prettier/-/eslint-plugin-prettier-5.2.1.tgz", + "integrity": "sha512-gH3iR3g4JfF+yYPaJYkN7jEl9QbweL/YfkoRlNnuIEHEz1vHVlCmWOS+eGGiRuzHQXdJFCOTxRgvju9b8VUmrw==", "dev": true, "license": "MIT", "dependencies": { "prettier-linter-helpers": "^1.0.0", - "synckit": "^0.8.6" + "synckit": "^0.9.1" }, "engines": { "node": "^14.18.0 || >=16.0.0" @@ -3676,9 +3726,9 @@ } }, "node_modules/eslint-scope": { - "version": "8.0.1", - "resolved": "https://registry.npmjs.org/eslint-scope/-/eslint-scope-8.0.1.tgz", - "integrity": "sha512-pL8XjgP4ZOmmwfFE8mEhSxA7ZY4C+LWyqjQ3o4yWkkmD0qcMT9kkW3zWHOczhWcjTSgqycYAgwSlXvZltv65og==", + "version": "8.0.2", + "resolved": "https://registry.npmjs.org/eslint-scope/-/eslint-scope-8.0.2.tgz", + "integrity": "sha512-6E4xmrTw5wtxnLA5wYL3WDfhZ/1bUBGOXV0zQvVRDOtrR8D0p6W7fs3JweNYhwRYeGvd/1CKX2se0/2s7Q/nJA==", "dev": true, "license": "BSD-2-Clause", "dependencies": { @@ -3946,9 +3996,9 @@ } }, "node_modules/fetch-mock": { - "version": "10.1.1", - "resolved": "https://registry.npmjs.org/fetch-mock/-/fetch-mock-10.1.1.tgz", - "integrity": "sha512-R6MwxuGwlUe0K6GzdLY1Wa9voX/GbUBDZjNHBsvlBhrpXurCYpQN4EW0iFCmtWddDTuS0ubR93OtFSgy9E/L2A==", + "version": "11.0.0", + "resolved": "https://registry.npmjs.org/fetch-mock/-/fetch-mock-11.0.0.tgz", + "integrity": "sha512-AD2Gh1xDQBLBs4iJpSxar19cTOH/Gu9hf1ko2J4hHW1UbR+ZHOfmIAqfT+Wlku4U8cYbffjaTbGv7mqe5kPi3w==", "dev": true, "license": "MIT", "dependencies": { @@ -3980,6 +4030,39 @@ "node": ">=16.0.0" } }, + "node_modules/filelist": { + "version": "1.0.4", + "resolved": "https://registry.npmjs.org/filelist/-/filelist-1.0.4.tgz", + "integrity": "sha512-w1cEuf3S+DrLCQL7ET6kz+gmlJdbq9J7yXCSjK/OZCPA+qEN1WyF4ZAf0YYJa4/shHJra2t/d/r8SV4Ji+x+8Q==", + "dev": true, + "license": "Apache-2.0", + "dependencies": { + "minimatch": "^5.0.1" + } + }, + "node_modules/filelist/node_modules/brace-expansion": { + "version": "2.0.1", + "resolved": "https://registry.npmjs.org/brace-expansion/-/brace-expansion-2.0.1.tgz", + "integrity": "sha512-XnAIvQ8eM+kC6aULx6wuQiwVsnzsi9d3WxzV3FpWTGA19F621kwdbsAcFKXgKUHZWsy+mY6iL1sHTxWEFCytDA==", + "dev": true, + "license": "MIT", + "dependencies": { + "balanced-match": "^1.0.0" + } + }, + "node_modules/filelist/node_modules/minimatch": { + "version": "5.1.6", + "resolved": "https://registry.npmjs.org/minimatch/-/minimatch-5.1.6.tgz", + "integrity": "sha512-lKwV/1brpG6mBUFHtb7NUmtABCb2WZZmm2wNiOA5hAb8VdCS4B3dtMWyvcoViccwAW/COERjXLt0zP1zXUN26g==", + "dev": true, + "license": "ISC", + "dependencies": { + "brace-expansion": "^2.0.1" + }, + "engines": { + "node": ">=10" + } + }, "node_modules/fill-range": { "version": "7.1.1", "resolved": "https://registry.npmjs.org/fill-range/-/fill-range-7.1.1.tgz", @@ -4656,6 +4739,25 @@ "@pkgjs/parseargs": "^0.11.0" } }, + "node_modules/jake": { + "version": "10.9.2", + "resolved": "https://registry.npmjs.org/jake/-/jake-10.9.2.tgz", + "integrity": "sha512-2P4SQ0HrLQ+fw6llpLnOaGAvN2Zu6778SJMrCUwns4fOoG9ayrTiZk3VV8sCPkVZF8ab0zksVpS8FDY5pRCNBA==", + "dev": true, + "license": "Apache-2.0", + "dependencies": { + "async": "^3.2.3", + "chalk": "^4.0.2", + "filelist": "^1.0.4", + "minimatch": "^3.1.2" + }, + "bin": { + "jake": "bin/cli.js" + }, + "engines": { + "node": ">=10" + } + }, "node_modules/jest": { "version": "29.7.0", "resolved": "https://registry.npmjs.org/jest/-/jest-29.7.0.tgz", @@ -6015,9 +6117,9 @@ } }, "node_modules/postcss-load-config": { - "version": "4.0.2", - "resolved": "https://registry.npmjs.org/postcss-load-config/-/postcss-load-config-4.0.2.tgz", - "integrity": "sha512-bSVhyJGL00wMVoPUzAVAnbEoWyqRxkjv64tUl427SKnPrENtq6hJwUojroMz2VB+Q1edmi4IfrAPpami5VVgMQ==", + "version": "6.0.1", + "resolved": "https://registry.npmjs.org/postcss-load-config/-/postcss-load-config-6.0.1.tgz", + "integrity": "sha512-oPtTM4oerL+UXmx+93ytZVN82RrlY/wPUV8IeDxFrzIjXOLF1pN+EmKPLbubvKHT2HC20xXsCAH2Z+CKV6Oz/g==", "dev": true, "funding": [ { @@ -6031,21 +6133,28 @@ ], "license": "MIT", "dependencies": { - "lilconfig": "^3.0.0", - "yaml": "^2.3.4" + "lilconfig": "^3.1.1" }, "engines": { - "node": ">= 14" + "node": ">= 18" }, "peerDependencies": { + "jiti": ">=1.21.0", "postcss": ">=8.0.9", - "ts-node": ">=9.0.0" + "tsx": "^4.8.1", + "yaml": "^2.4.2" }, "peerDependenciesMeta": { + "jiti": { + "optional": true + }, "postcss": { "optional": true }, - "ts-node": { + "tsx": { + "optional": true + }, + "yaml": { "optional": true } } @@ -6061,9 +6170,9 @@ } }, "node_modules/prettier": { - "version": "3.3.2", - "resolved": "https://registry.npmjs.org/prettier/-/prettier-3.3.2.tgz", - "integrity": "sha512-rAVeHYMcv8ATV5d508CFdn+8/pHPpXeIid1DdrPwXnaAdH7cqjVbpJaT5eq4yRAFU/lsbwYwSF/n5iNrdJHPQA==", + "version": "3.3.3", + "resolved": "https://registry.npmjs.org/prettier/-/prettier-3.3.3.tgz", + "integrity": "sha512-i2tDNA0O5IrMO757lfrdQZCc2jPNDVntV0m/+4whiDfWaTKfMNgR7Qz0NAeGz/nRqF4m5/6CLzbP4/liHt12Ew==", "dev": true, "license": "MIT", "bin": { @@ -6725,9 +6834,9 @@ } }, "node_modules/synckit": { - "version": "0.8.8", - "resolved": "https://registry.npmjs.org/synckit/-/synckit-0.8.8.tgz", - "integrity": "sha512-HwOKAP7Wc5aRGYdKH+dw0PRRpbO841v2DENBtjnR5HFWoiNByAl7vrx3p0G/rCyYXQsrxqtX48TImFtPcIHSpQ==", + "version": "0.9.1", + "resolved": "https://registry.npmjs.org/synckit/-/synckit-0.9.1.tgz", + "integrity": "sha512-7gr8p9TQP6RAHusBOSLs46F4564ZrjV8xFmw5zCmgmhGUcw2hxsShhJ6CEiHQMgPDwAQ1fWHPM0ypc4RMAig4A==", "dev": true, "license": "MIT", "dependencies": { @@ -6857,13 +6966,14 @@ "license": "Apache-2.0" }, "node_modules/ts-jest": { - "version": "29.1.5", - "resolved": "https://registry.npmjs.org/ts-jest/-/ts-jest-29.1.5.tgz", - "integrity": "sha512-UuClSYxM7byvvYfyWdFI+/2UxMmwNyJb0NPkZPQE2hew3RurV7l7zURgOHAd/1I1ZdPpe3GUsXNXAcN8TFKSIg==", + "version": "29.2.4", + "resolved": "https://registry.npmjs.org/ts-jest/-/ts-jest-29.2.4.tgz", + "integrity": "sha512-3d6tgDyhCI29HlpwIq87sNuI+3Q6GLTTCeYRHCs7vDz+/3GCMwEtV9jezLyl4ZtnBgx00I7hm8PCP8cTksMGrw==", "dev": true, "license": "MIT", "dependencies": { "bs-logger": "0.x", + "ejs": "^3.1.10", "fast-json-stable-stringify": "2.x", "jest-util": "^29.0.0", "json5": "^2.2.3", @@ -6971,25 +7081,27 @@ "license": "0BSD" }, "node_modules/tsup": { - "version": "8.1.0", - "resolved": "https://registry.npmjs.org/tsup/-/tsup-8.1.0.tgz", - "integrity": "sha512-UFdfCAXukax+U6KzeTNO2kAARHcWxmKsnvSPXUcfA1D+kU05XDccCrkffCQpFaWDsZfV0jMyTsxU39VfCp6EOg==", + "version": "8.2.4", + "resolved": "https://registry.npmjs.org/tsup/-/tsup-8.2.4.tgz", + "integrity": "sha512-akpCPePnBnC/CXgRrcy72ZSntgIEUa1jN0oJbbvpALWKNOz1B7aM+UVDWGRGIO/T/PZugAESWDJUAb5FD48o8Q==", "dev": true, "license": "MIT", "dependencies": { - "bundle-require": "^4.0.0", - "cac": "^6.7.12", - "chokidar": "^3.5.1", - "debug": "^4.3.1", - "esbuild": "^0.21.4", - "execa": "^5.0.0", - "globby": "^11.0.3", - "joycon": "^3.0.1", - "postcss-load-config": "^4.0.1", + "bundle-require": "^5.0.0", + "cac": "^6.7.14", + "chokidar": "^3.6.0", + "consola": "^3.2.3", + "debug": "^4.3.5", + "esbuild": "^0.23.0", + "execa": "^5.1.1", + "globby": "^11.1.0", + "joycon": "^3.1.1", + "picocolors": "^1.0.1", + "postcss-load-config": "^6.0.1", "resolve-from": "^5.0.0", - "rollup": "^4.0.2", + "rollup": "^4.19.0", "source-map": "0.8.0-beta.0", - "sucrase": "^3.20.3", + "sucrase": "^3.35.0", "tree-kill": "^1.2.2" }, "bin": { @@ -7020,6 +7132,397 @@ } } }, + "node_modules/tsup/node_modules/@esbuild/aix-ppc64": { + "version": "0.23.0", + "resolved": "https://registry.npmjs.org/@esbuild/aix-ppc64/-/aix-ppc64-0.23.0.tgz", + "integrity": "sha512-3sG8Zwa5fMcA9bgqB8AfWPQ+HFke6uD3h1s3RIwUNK8EG7a4buxvuFTs3j1IMs2NXAk9F30C/FF4vxRgQCcmoQ==", + "cpu": [ + "ppc64" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "aix" + ], + "engines": { + "node": ">=18" + } + }, + "node_modules/tsup/node_modules/@esbuild/android-arm": { + "version": "0.23.0", + "resolved": "https://registry.npmjs.org/@esbuild/android-arm/-/android-arm-0.23.0.tgz", + "integrity": "sha512-+KuOHTKKyIKgEEqKbGTK8W7mPp+hKinbMBeEnNzjJGyFcWsfrXjSTNluJHCY1RqhxFurdD8uNXQDei7qDlR6+g==", + "cpu": [ + "arm" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "android" + ], + "engines": { + "node": ">=18" + } + }, + "node_modules/tsup/node_modules/@esbuild/android-arm64": { + "version": "0.23.0", + "resolved": "https://registry.npmjs.org/@esbuild/android-arm64/-/android-arm64-0.23.0.tgz", + "integrity": "sha512-EuHFUYkAVfU4qBdyivULuu03FhJO4IJN9PGuABGrFy4vUuzk91P2d+npxHcFdpUnfYKy0PuV+n6bKIpHOB3prQ==", + "cpu": [ + "arm64" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "android" + ], + "engines": { + "node": ">=18" + } + }, + "node_modules/tsup/node_modules/@esbuild/android-x64": { + "version": "0.23.0", + "resolved": "https://registry.npmjs.org/@esbuild/android-x64/-/android-x64-0.23.0.tgz", + "integrity": "sha512-WRrmKidLoKDl56LsbBMhzTTBxrsVwTKdNbKDalbEZr0tcsBgCLbEtoNthOW6PX942YiYq8HzEnb4yWQMLQuipQ==", + "cpu": [ + "x64" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "android" + ], + "engines": { + "node": ">=18" + } + }, + "node_modules/tsup/node_modules/@esbuild/darwin-arm64": { + "version": "0.23.0", + "resolved": "https://registry.npmjs.org/@esbuild/darwin-arm64/-/darwin-arm64-0.23.0.tgz", + "integrity": "sha512-YLntie/IdS31H54Ogdn+v50NuoWF5BDkEUFpiOChVa9UnKpftgwzZRrI4J132ETIi+D8n6xh9IviFV3eXdxfow==", + "cpu": [ + "arm64" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "darwin" + ], + "engines": { + "node": ">=18" + } + }, + "node_modules/tsup/node_modules/@esbuild/darwin-x64": { + "version": "0.23.0", + "resolved": "https://registry.npmjs.org/@esbuild/darwin-x64/-/darwin-x64-0.23.0.tgz", + "integrity": "sha512-IMQ6eme4AfznElesHUPDZ+teuGwoRmVuuixu7sv92ZkdQcPbsNHzutd+rAfaBKo8YK3IrBEi9SLLKWJdEvJniQ==", + "cpu": [ + "x64" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "darwin" + ], + "engines": { + "node": ">=18" + } + }, + "node_modules/tsup/node_modules/@esbuild/freebsd-arm64": { + "version": "0.23.0", + "resolved": "https://registry.npmjs.org/@esbuild/freebsd-arm64/-/freebsd-arm64-0.23.0.tgz", + "integrity": "sha512-0muYWCng5vqaxobq6LB3YNtevDFSAZGlgtLoAc81PjUfiFz36n4KMpwhtAd4he8ToSI3TGyuhyx5xmiWNYZFyw==", + "cpu": [ + "arm64" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "freebsd" + ], + "engines": { + "node": ">=18" + } + }, + "node_modules/tsup/node_modules/@esbuild/freebsd-x64": { + "version": "0.23.0", + "resolved": "https://registry.npmjs.org/@esbuild/freebsd-x64/-/freebsd-x64-0.23.0.tgz", + "integrity": "sha512-XKDVu8IsD0/q3foBzsXGt/KjD/yTKBCIwOHE1XwiXmrRwrX6Hbnd5Eqn/WvDekddK21tfszBSrE/WMaZh+1buQ==", + "cpu": [ + "x64" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "freebsd" + ], + "engines": { + "node": ">=18" + } + }, + "node_modules/tsup/node_modules/@esbuild/linux-arm": { + "version": "0.23.0", + "resolved": "https://registry.npmjs.org/@esbuild/linux-arm/-/linux-arm-0.23.0.tgz", + "integrity": "sha512-SEELSTEtOFu5LPykzA395Mc+54RMg1EUgXP+iw2SJ72+ooMwVsgfuwXo5Fn0wXNgWZsTVHwY2cg4Vi/bOD88qw==", + "cpu": [ + "arm" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "linux" + ], + "engines": { + "node": ">=18" + } + }, + "node_modules/tsup/node_modules/@esbuild/linux-arm64": { + "version": "0.23.0", + "resolved": "https://registry.npmjs.org/@esbuild/linux-arm64/-/linux-arm64-0.23.0.tgz", + "integrity": "sha512-j1t5iG8jE7BhonbsEg5d9qOYcVZv/Rv6tghaXM/Ug9xahM0nX/H2gfu6X6z11QRTMT6+aywOMA8TDkhPo8aCGw==", + "cpu": [ + "arm64" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "linux" + ], + "engines": { + "node": ">=18" + } + }, + "node_modules/tsup/node_modules/@esbuild/linux-ia32": { + "version": "0.23.0", + "resolved": "https://registry.npmjs.org/@esbuild/linux-ia32/-/linux-ia32-0.23.0.tgz", + "integrity": "sha512-P7O5Tkh2NbgIm2R6x1zGJJsnacDzTFcRWZyTTMgFdVit6E98LTxO+v8LCCLWRvPrjdzXHx9FEOA8oAZPyApWUA==", + "cpu": [ + "ia32" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "linux" + ], + "engines": { + "node": ">=18" + } + }, + "node_modules/tsup/node_modules/@esbuild/linux-loong64": { + "version": "0.23.0", + "resolved": "https://registry.npmjs.org/@esbuild/linux-loong64/-/linux-loong64-0.23.0.tgz", + "integrity": "sha512-InQwepswq6urikQiIC/kkx412fqUZudBO4SYKu0N+tGhXRWUqAx+Q+341tFV6QdBifpjYgUndV1hhMq3WeJi7A==", + "cpu": [ + "loong64" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "linux" + ], + "engines": { + "node": ">=18" + } + }, + "node_modules/tsup/node_modules/@esbuild/linux-mips64el": { + "version": "0.23.0", + "resolved": "https://registry.npmjs.org/@esbuild/linux-mips64el/-/linux-mips64el-0.23.0.tgz", + "integrity": "sha512-J9rflLtqdYrxHv2FqXE2i1ELgNjT+JFURt/uDMoPQLcjWQA5wDKgQA4t/dTqGa88ZVECKaD0TctwsUfHbVoi4w==", + "cpu": [ + "mips64el" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "linux" + ], + "engines": { + "node": ">=18" + } + }, + "node_modules/tsup/node_modules/@esbuild/linux-ppc64": { + "version": "0.23.0", + "resolved": "https://registry.npmjs.org/@esbuild/linux-ppc64/-/linux-ppc64-0.23.0.tgz", + "integrity": "sha512-cShCXtEOVc5GxU0fM+dsFD10qZ5UpcQ8AM22bYj0u/yaAykWnqXJDpd77ublcX6vdDsWLuweeuSNZk4yUxZwtw==", + "cpu": [ + "ppc64" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "linux" + ], + "engines": { + "node": ">=18" + } + }, + "node_modules/tsup/node_modules/@esbuild/linux-riscv64": { + "version": "0.23.0", + "resolved": "https://registry.npmjs.org/@esbuild/linux-riscv64/-/linux-riscv64-0.23.0.tgz", + "integrity": "sha512-HEtaN7Y5UB4tZPeQmgz/UhzoEyYftbMXrBCUjINGjh3uil+rB/QzzpMshz3cNUxqXN7Vr93zzVtpIDL99t9aRw==", + "cpu": [ + "riscv64" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "linux" + ], + "engines": { + "node": ">=18" + } + }, + "node_modules/tsup/node_modules/@esbuild/linux-s390x": { + "version": "0.23.0", + "resolved": "https://registry.npmjs.org/@esbuild/linux-s390x/-/linux-s390x-0.23.0.tgz", + "integrity": "sha512-WDi3+NVAuyjg/Wxi+o5KPqRbZY0QhI9TjrEEm+8dmpY9Xir8+HE/HNx2JoLckhKbFopW0RdO2D72w8trZOV+Wg==", + "cpu": [ + "s390x" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "linux" + ], + "engines": { + "node": ">=18" + } + }, + "node_modules/tsup/node_modules/@esbuild/linux-x64": { + "version": "0.23.0", + "resolved": "https://registry.npmjs.org/@esbuild/linux-x64/-/linux-x64-0.23.0.tgz", + "integrity": "sha512-a3pMQhUEJkITgAw6e0bWA+F+vFtCciMjW/LPtoj99MhVt+Mfb6bbL9hu2wmTZgNd994qTAEw+U/r6k3qHWWaOQ==", + "cpu": [ + "x64" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "linux" + ], + "engines": { + "node": ">=18" + } + }, + "node_modules/tsup/node_modules/@esbuild/netbsd-x64": { + "version": "0.23.0", + "resolved": "https://registry.npmjs.org/@esbuild/netbsd-x64/-/netbsd-x64-0.23.0.tgz", + "integrity": "sha512-cRK+YDem7lFTs2Q5nEv/HHc4LnrfBCbH5+JHu6wm2eP+d8OZNoSMYgPZJq78vqQ9g+9+nMuIsAO7skzphRXHyw==", + "cpu": [ + "x64" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "netbsd" + ], + "engines": { + "node": ">=18" + } + }, + "node_modules/tsup/node_modules/@esbuild/openbsd-x64": { + "version": "0.23.0", + "resolved": "https://registry.npmjs.org/@esbuild/openbsd-x64/-/openbsd-x64-0.23.0.tgz", + "integrity": "sha512-6p3nHpby0DM/v15IFKMjAaayFhqnXV52aEmv1whZHX56pdkK+MEaLoQWj+H42ssFarP1PcomVhbsR4pkz09qBg==", + "cpu": [ + "x64" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "openbsd" + ], + "engines": { + "node": ">=18" + } + }, + "node_modules/tsup/node_modules/@esbuild/sunos-x64": { + "version": "0.23.0", + "resolved": "https://registry.npmjs.org/@esbuild/sunos-x64/-/sunos-x64-0.23.0.tgz", + "integrity": "sha512-BFelBGfrBwk6LVrmFzCq1u1dZbG4zy/Kp93w2+y83Q5UGYF1d8sCzeLI9NXjKyujjBBniQa8R8PzLFAUrSM9OA==", + "cpu": [ + "x64" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "sunos" + ], + "engines": { + "node": ">=18" + } + }, + "node_modules/tsup/node_modules/@esbuild/win32-arm64": { + "version": "0.23.0", + "resolved": "https://registry.npmjs.org/@esbuild/win32-arm64/-/win32-arm64-0.23.0.tgz", + "integrity": "sha512-lY6AC8p4Cnb7xYHuIxQ6iYPe6MfO2CC43XXKo9nBXDb35krYt7KGhQnOkRGar5psxYkircpCqfbNDB4uJbS2jQ==", + "cpu": [ + "arm64" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "win32" + ], + "engines": { + "node": ">=18" + } + }, + "node_modules/tsup/node_modules/@esbuild/win32-ia32": { + "version": "0.23.0", + "resolved": "https://registry.npmjs.org/@esbuild/win32-ia32/-/win32-ia32-0.23.0.tgz", + "integrity": "sha512-7L1bHlOTcO4ByvI7OXVI5pNN6HSu6pUQq9yodga8izeuB1KcT2UkHaH6118QJwopExPn0rMHIseCTx1CRo/uNA==", + "cpu": [ + "ia32" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "win32" + ], + "engines": { + "node": ">=18" + } + }, + "node_modules/tsup/node_modules/@esbuild/win32-x64": { + "version": "0.23.0", + "resolved": "https://registry.npmjs.org/@esbuild/win32-x64/-/win32-x64-0.23.0.tgz", + "integrity": "sha512-Arm+WgUFLUATuoxCJcahGuk6Yj9Pzxd6l11Zb/2aAuv5kWWvvfhLFo2fni4uSK5vzlUdCGZ/BdV5tH8klj8p8g==", + "cpu": [ + "x64" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "win32" + ], + "engines": { + "node": ">=18" + } + }, "node_modules/tsup/node_modules/@types/estree": { "version": "1.0.5", "resolved": "https://registry.npmjs.org/@types/estree/-/estree-1.0.5.tgz", @@ -7027,6 +7530,46 @@ "dev": true, "license": "MIT" }, + "node_modules/tsup/node_modules/esbuild": { + "version": "0.23.0", + "resolved": "https://registry.npmjs.org/esbuild/-/esbuild-0.23.0.tgz", + "integrity": "sha512-1lvV17H2bMYda/WaFb2jLPeHU3zml2k4/yagNMG8Q/YtfMjCwEUZa2eXXMgZTVSL5q1n4H7sQ0X6CdJDqqeCFA==", + "dev": true, + "hasInstallScript": true, + "license": "MIT", + "bin": { + "esbuild": "bin/esbuild" + }, + "engines": { + "node": ">=18" + }, + "optionalDependencies": { + "@esbuild/aix-ppc64": "0.23.0", + "@esbuild/android-arm": "0.23.0", + "@esbuild/android-arm64": "0.23.0", + "@esbuild/android-x64": "0.23.0", + "@esbuild/darwin-arm64": "0.23.0", + "@esbuild/darwin-x64": "0.23.0", + "@esbuild/freebsd-arm64": "0.23.0", + "@esbuild/freebsd-x64": "0.23.0", + "@esbuild/linux-arm": "0.23.0", + "@esbuild/linux-arm64": "0.23.0", + "@esbuild/linux-ia32": "0.23.0", + "@esbuild/linux-loong64": "0.23.0", + "@esbuild/linux-mips64el": "0.23.0", + "@esbuild/linux-ppc64": "0.23.0", + "@esbuild/linux-riscv64": "0.23.0", + "@esbuild/linux-s390x": "0.23.0", + "@esbuild/linux-x64": "0.23.0", + "@esbuild/netbsd-x64": "0.23.0", + "@esbuild/openbsd-arm64": "0.23.0", + "@esbuild/openbsd-x64": "0.23.0", + "@esbuild/sunos-x64": "0.23.0", + "@esbuild/win32-arm64": "0.23.0", + "@esbuild/win32-ia32": "0.23.0", + "@esbuild/win32-x64": "0.23.0" + } + }, "node_modules/tsup/node_modules/globby": { "version": "11.1.0", "resolved": "https://registry.npmjs.org/globby/-/globby-11.1.0.tgz", @@ -7059,9 +7602,9 @@ } }, "node_modules/tsup/node_modules/rollup": { - "version": "4.18.0", - "resolved": "https://registry.npmjs.org/rollup/-/rollup-4.18.0.tgz", - "integrity": "sha512-QmJz14PX3rzbJCN1SG4Xe/bAAX2a6NpCP8ab2vfu2GiUr8AQcr2nCV/oEO3yneFarB67zk8ShlIyWb2LGTb3Sg==", + "version": "4.20.0", + "resolved": "https://registry.npmjs.org/rollup/-/rollup-4.20.0.tgz", + "integrity": "sha512-6rbWBChcnSGzIlXeIdNIZTopKYad8ZG8ajhl78lGRLsI2rX8IkaotQhVas2Ma+GPxJav19wrSzvRvuiv0YKzWw==", "dev": true, "license": "MIT", "dependencies": { @@ -7075,22 +7618,22 @@ "npm": ">=8.0.0" }, "optionalDependencies": { - "@rollup/rollup-android-arm-eabi": "4.18.0", - "@rollup/rollup-android-arm64": "4.18.0", - "@rollup/rollup-darwin-arm64": "4.18.0", - "@rollup/rollup-darwin-x64": "4.18.0", - "@rollup/rollup-linux-arm-gnueabihf": "4.18.0", - "@rollup/rollup-linux-arm-musleabihf": "4.18.0", - "@rollup/rollup-linux-arm64-gnu": "4.18.0", - "@rollup/rollup-linux-arm64-musl": "4.18.0", - "@rollup/rollup-linux-powerpc64le-gnu": "4.18.0", - "@rollup/rollup-linux-riscv64-gnu": "4.18.0", - "@rollup/rollup-linux-s390x-gnu": "4.18.0", - "@rollup/rollup-linux-x64-gnu": "4.18.0", - "@rollup/rollup-linux-x64-musl": "4.18.0", - "@rollup/rollup-win32-arm64-msvc": "4.18.0", - "@rollup/rollup-win32-ia32-msvc": "4.18.0", - "@rollup/rollup-win32-x64-msvc": "4.18.0", + "@rollup/rollup-android-arm-eabi": "4.20.0", + "@rollup/rollup-android-arm64": "4.20.0", + "@rollup/rollup-darwin-arm64": "4.20.0", + "@rollup/rollup-darwin-x64": "4.20.0", + "@rollup/rollup-linux-arm-gnueabihf": "4.20.0", + "@rollup/rollup-linux-arm-musleabihf": "4.20.0", + "@rollup/rollup-linux-arm64-gnu": "4.20.0", + "@rollup/rollup-linux-arm64-musl": "4.20.0", + "@rollup/rollup-linux-powerpc64le-gnu": "4.20.0", + "@rollup/rollup-linux-riscv64-gnu": "4.20.0", + "@rollup/rollup-linux-s390x-gnu": "4.20.0", + "@rollup/rollup-linux-x64-gnu": "4.20.0", + "@rollup/rollup-linux-x64-musl": "4.20.0", + "@rollup/rollup-win32-arm64-msvc": "4.20.0", + "@rollup/rollup-win32-ia32-msvc": "4.20.0", + "@rollup/rollup-win32-x64-msvc": "4.20.0", "fsevents": "~2.3.2" } }, @@ -7145,15 +7688,15 @@ } }, "node_modules/typescript-eslint": { - "version": "8.0.0-alpha.54", - "resolved": "https://registry.npmjs.org/typescript-eslint/-/typescript-eslint-8.0.0-alpha.54.tgz", - "integrity": "sha512-5rTqLSAgzum8W0dIh8jgDosXNQufhfc9pyDXaBpBTgwtTypBzrcRgCz8Xp0XKrpSBCuLdfFt+X/ueEnoqrOcJA==", + "version": "8.0.1", + "resolved": "https://registry.npmjs.org/typescript-eslint/-/typescript-eslint-8.0.1.tgz", + "integrity": "sha512-V3Y+MdfhawxEjE16dWpb7/IOgeXnLwAEEkS7v8oDqNcR1oYlqWhGH/iHqHdKVdpWme1VPZ0SoywXAkCqawj2eQ==", "dev": true, "license": "MIT", "dependencies": { - "@typescript-eslint/eslint-plugin": "8.0.0-alpha.54", - "@typescript-eslint/parser": "8.0.0-alpha.54", - "@typescript-eslint/utils": "8.0.0-alpha.54" + "@typescript-eslint/eslint-plugin": "8.0.1", + "@typescript-eslint/parser": "8.0.1", + "@typescript-eslint/utils": "8.0.1" }, "engines": { "node": "^18.18.0 || ^20.9.0 || >=21.1.0" @@ -7383,19 +7926,6 @@ "dev": true, "license": "ISC" }, - "node_modules/yaml": { - "version": "2.4.5", - "resolved": "https://registry.npmjs.org/yaml/-/yaml-2.4.5.tgz", - "integrity": "sha512-aBx2bnqDzVOyNKfsysjA2ms5ZlnjSAW2eG3/L5G/CSujfjLJTJsEw1bGw8kCf04KodQWk1pxlGnZ56CRxiawmg==", - "dev": true, - "license": "ISC", - "bin": { - "yaml": "bin.mjs" - }, - "engines": { - "node": ">= 14" - } - }, "node_modules/yargs": { "version": "17.7.2", "resolved": "https://registry.npmjs.org/yargs/-/yargs-17.7.2.tgz", diff --git a/package.json b/package.json index d835c3e..5cf2cfa 100644 --- a/package.json +++ b/package.json @@ -54,20 +54,20 @@ "devDependencies": { "@size-limit/preset-small-lib": "11.1.4", "@types/jest": "29.5.12", - "eslint": "9.6.0", + "eslint": "9.8.0", "eslint-config-prettier": "9.1.0", - "eslint-plugin-prettier": "5.1.3", - "fetch-mock": "10.1.1", + "eslint-plugin-prettier": "5.2.1", + "fetch-mock": "11.0.0", "jest": "29.7.0", - "prettier": "3.3.2", + "prettier": "3.3.3", "promise-any": "0.2.0", "rollup-plugin-bundle-imports": "1.5.1", "size-limit": "11.1.4", - "ts-jest": "29.1.5", + "ts-jest": "29.2.4", "tslib": "2.6.3", - "tsup": "8.1.0", + "tsup": "8.2.4", "typescript": "5.5.4", - "typescript-eslint": "8.0.0-alpha.54" + "typescript-eslint": "8.0.1" }, "peerDependencies": { "axios": "^1" diff --git a/src/api-handler.ts b/src/api-handler.ts index 80c504d..d9bf576 100644 --- a/src/api-handler.ts +++ b/src/api-handler.ts @@ -68,9 +68,10 @@ import type { * // Fetch user data * const response = await api.getUser({ userId: 1, ratings: [1, 2] }) */ -function createApiFetcher( - config: ApiHandlerConfig, -) { +function createApiFetcher< + EndpointsMethods extends object, + EndpointsCfg = never, +>(config: ApiHandlerConfig) { const endpoints = config.endpoints; const requestHandler = new RequestHandler(config); diff --git a/src/index.ts b/src/index.ts index 90ff2e3..26a8572 100644 --- a/src/index.ts +++ b/src/index.ts @@ -6,14 +6,14 @@ import type { APIResponse, FetchResponse, RequestHandlerConfig } from './types'; * It abstracts the creation of RequestHandler, making it easy to perform API requests. * * @param {string | URL | globalThis.Request} url - Request URL. - * @param {Object} config - Configuration object for the request handler. - * @returns {Promise} Response Data. + * @param {RequestHandlerConfig} config - Configuration object for the request handler. + * @returns {Promise>} Response Data. */ -export async function fetchf( +export async function fetchf( url: string, config: RequestHandlerConfig = {}, -): Promise> { - return new RequestHandler(config).request( +): Promise> { + return new RequestHandler(config).request( url, config.body || config.data || config.params, config, diff --git a/src/interceptor-manager.ts b/src/interceptor-manager.ts index 571f221..c645935 100644 --- a/src/interceptor-manager.ts +++ b/src/interceptor-manager.ts @@ -1,8 +1,4 @@ -import type { - BaseRequestHandlerConfig, - FetchResponse, - RequestResponse, -} from './types'; +import type { RequestHandlerConfig, FetchResponse } from './types'; import type { RequestInterceptor, ResponseInterceptor, @@ -10,14 +6,14 @@ import type { /** * Applies a series of request interceptors to the provided configuration. - * @param {BaseRequestHandlerConfig} config - The initial request configuration. + * @param {RequestHandlerConfig} config - The initial request configuration. * @param {RequestInterceptor | RequestInterceptor[]} interceptors - The request interceptor function(s) to apply. - * @returns {Promise} - The modified request configuration. + * @returns {Promise} - The modified request configuration. */ export async function interceptRequest( - config: BaseRequestHandlerConfig, + config: RequestHandlerConfig, interceptors: RequestInterceptor | RequestInterceptor[], -): Promise { +): Promise { if (!interceptors) { return config; } @@ -39,12 +35,12 @@ export async function interceptRequest( * Applies a series of response interceptors to the provided response. * @param {FetchResponse} response - The initial response object. * @param {ResponseInterceptor | ResponseInterceptor[]} interceptors - The response interceptor function(s) to apply. - * @returns {Promise>} - The modified response object. + * @returns {Promise>} - The modified response object. */ export async function interceptResponse( response: FetchResponse, interceptors: ResponseInterceptor | ResponseInterceptor[], -): Promise> { +): Promise> { if (!interceptors) { return response; } diff --git a/src/request-error-handler.ts b/src/request-error-handler.ts deleted file mode 100644 index 098d001..0000000 --- a/src/request-error-handler.ts +++ /dev/null @@ -1,50 +0,0 @@ -/* eslint-disable @typescript-eslint/no-explicit-any */ -export class RequestErrorHandler { - /** - * Logger Class - * - * @type {*} - * @memberof RequestErrorHandler - */ - protected logger: any; - - /** - * Error Service Class - * - * @type {*} - * @memberof RequestErrorHandler - */ - public requestErrorService: any; - - public constructor(logger: any, requestErrorService: any) { - this.logger = logger; - this.requestErrorService = requestErrorService; - } - - /** - * Process and Error - * - * @param {*} error Error instance or message - * @throws Request error context - * @returns {void} - */ - public process(error: string | Error): void { - if (this.logger?.warn) { - this.logger.warn('API ERROR', error); - } - - let errorContext = error; - - if (typeof error === 'string') { - errorContext = new Error(error); - } - - if (this.requestErrorService) { - if (typeof this.requestErrorService.process !== 'undefined') { - this.requestErrorService.process(errorContext); - } else if (typeof this.requestErrorService === 'function') { - this.requestErrorService(errorContext); - } - } - } -} diff --git a/src/request-handler.ts b/src/request-handler.ts index 1086e7e..800280a 100644 --- a/src/request-handler.ts +++ b/src/request-handler.ts @@ -1,16 +1,14 @@ /* eslint-disable @typescript-eslint/no-explicit-any */ -import { RequestErrorHandler } from './request-error-handler'; import type { ErrorHandlingStrategy, RequestHandlerConfig, RequestConfig, - RequestError as RequestErrorResponse, FetcherInstance, Method, - RequestConfigHeaders, RetryOptions, FetchResponse, - ExtendedResponse, + ResponseError, + HeadersObject, } from './types/request-handler'; import type { APIResponse, @@ -18,8 +16,8 @@ import type { QueryParamsOrBody, UrlPathParams, } from './types/api-handler'; -import { RequestError } from './request-error'; import { interceptRequest, interceptResponse } from './interceptor-manager'; +import { ResponseErr } from './response-error'; /** * Generic Request Handler @@ -83,7 +81,7 @@ export class RequestHandler { protected logger: any; /** - * @var requestErrorService HTTP error service + * @var onError HTTP error service */ protected onError: any; @@ -162,14 +160,10 @@ export class RequestHandler { this.fetcher = fetcher; this.timeout = timeout !== null && timeout !== undefined ? timeout : this.timeout; - this.strategy = - strategy !== null && strategy !== undefined ? strategy : this.strategy; + this.strategy = strategy || this.strategy; this.cancellable = config.cancellable || this.cancellable; this.rejectCancelled = rejectCancelled || this.rejectCancelled; - this.flattenResponse = - flattenResponse !== null && flattenResponse !== undefined - ? flattenResponse - : this.flattenResponse; + this.flattenResponse = flattenResponse || this.flattenResponse; this.defaultResponse = defaultResponse; this.logger = logger || (globalThis ? globalThis.console : null) || null; this.onError = onError; @@ -357,8 +351,13 @@ export class RequestHandler { // Native fetch const payload = configData || data; + const credentials = + config.withCredentials || this.config.withCredentials + ? 'include' + : config.credentials; delete config.data; + delete config.withCredentials; const urlPath = (!isGetAlikeMethod && data && !config.body) || !data @@ -373,6 +372,7 @@ export class RequestHandler { return { ...config, + credentials, // Native fetch generally requires query params to be appended in the URL // Do not append query params only if it's a POST-alike request with only "data" specified as it's treated as body payload @@ -386,7 +386,7 @@ export class RequestHandler { Accept: 'application/json, text/plain, */*', 'Content-Type': 'application/json;charset=utf-8', ...(config.headers || this.config.headers || {}), - } as RequestConfigHeaders, + }, // Automatically JSON stringify request bodies, if possible and when not dealing with strings ...(!isGetAlikeMethod @@ -404,37 +404,42 @@ export class RequestHandler { /** * Process global Request Error * - * @param {RequestErrorResponse} error Error instance + * @param {ResponseError} error Error instance * @param {RequestConfig} requestConfig Per endpoint request config * @returns {void} */ protected processError( - error: RequestErrorResponse, + error: ResponseError, requestConfig: RequestConfig, ): void { if (this.isRequestCancelled(error)) { return; } - // Invoke per request "onError" call + if (this.logger?.warn) { + this.logger.warn('API ERROR', error); + } + + // Invoke per request "onError" interceptor if (requestConfig.onError && typeof requestConfig.onError === 'function') { requestConfig.onError(error); } - const errorHandler = new RequestErrorHandler(this.logger, this.onError); - - errorHandler.process(error); + // Invoke global "onError" interceptor + if (this.onError && typeof this.onError === 'function') { + this.onError(error); + } } /** * Output default response in case of an error, depending on chosen strategy * - * @param {RequestErrorResponse} error Error instance + * @param {ResponseError} error Error instance * @param {RequestConfig} requestConfig Per endpoint request config * @returns {*} Error response */ protected async outputErrorResponse( - error: RequestErrorResponse, + error: ResponseError, requestConfig: RequestConfig, ): Promise { const isRequestCancelled = this.isRequestCancelled(error); @@ -448,19 +453,24 @@ export class RequestHandler { ? requestConfig.defaultResponse : this.defaultResponse; + // Output full response with the error object + if (errorHandlingStrategy === 'softFail') { + return this.outputResponse(error.response, requestConfig, error); + } + // By default cancelled requests aren't rejected if (isRequestCancelled && !rejectCancelled) { return defaultResponse; } + // Hang the promise if (errorHandlingStrategy === 'silent') { - // Hang the promise await new Promise(() => null); return defaultResponse; } - // Simply rejects a request promise + // Reject the promise if (errorHandlingStrategy === 'reject') { return Promise.reject(error); } @@ -471,10 +481,10 @@ export class RequestHandler { /** * Output error response depending on chosen strategy * - * @param {RequestErrorResponse} error Error instance + * @param {ResponseError} error Error instance * @returns {boolean} True if request is aborted */ - public isRequestCancelled(error: RequestErrorResponse): boolean { + public isRequestCancelled(error: ResponseError): boolean { return error.name === 'AbortError' || error.name === 'CanceledError'; } @@ -526,7 +536,7 @@ export class RequestHandler { const controller = new AbortController(); // Introduce timeout for native fetch - if (!this.isCustomFetcher()) { + if (!this.isCustomFetcher() && this.timeout > 0) { const abortTimeout = setTimeout(() => { const error = new Error( `[TimeoutError]: The ${requestConfig.url} request was aborted due to timeout`, @@ -554,15 +564,15 @@ export class RequestHandler { * @param {QueryParamsOrBody} data - Request data * @param {RequestConfig} config - Request config * @param {RequestConfig} payload.config Request config - * @throws {RequestErrorResponse} - * @returns {Promise>} Response Data + * @throws {ResponseError} + * @returns {Promise>} Response Data */ - public async request( + public async request( url: string, data: QueryParamsOrBody = null, config: RequestConfig = null, - ): Promise> { - let response: Response | FetchResponse = null; + ): Promise> { + let response: FetchResponse = null; const _config = config || {}; const _requestConfig = this.buildConfig(url, data, _config); @@ -597,54 +607,51 @@ export class RequestHandler { if (this.isCustomFetcher()) { response = (await (this.requestInstance as any).request( requestConfig, - )) as FetchResponse; + )) as FetchResponse; } else { response = (await globalThis.fetch( requestConfig.url, requestConfig, - )) as ExtendedResponse; - - // Add more information to response object - response.config = requestConfig; + )) as FetchResponse; - // Check if the response status is not outside the range 200-299 - if (response.ok) { - const contentType = String( - response?.headers?.get('Content-Type') || '', - ); - let data = null; - - // Handle edge case of no content type being provided... We assume json here. - if (!contentType) { - try { - data = await response.json(); - // eslint-disable-next-line @typescript-eslint/no-unused-vars - } catch (_error) { - // - } + // Attempt to collect response data regardless of response status + const contentType = String( + (response as Response)?.headers?.get('Content-Type') || '', + ); + let data; + + // Handle edge case of no content type being provided... We assume json here. + if (!contentType) { + try { + data = await response.json(); + // eslint-disable-next-line @typescript-eslint/no-unused-vars + } catch (_error) { + // } + } - if (!data) { - if (contentType && contentType.includes('application/json')) { - // Parse JSON response - data = await response.json(); - } else if (typeof response.text !== 'undefined') { - data = await response.text(); - } else if (typeof response.blob !== 'undefined') { - data = await response.blob(); - } else { - // Handle streams - data = response.body || response.data || null; - } + if (typeof data === 'undefined') { + if (contentType && contentType.includes('application/json')) { + // Parse JSON response + data = await response.json(); + } else if (typeof response.text !== 'undefined') { + data = await response.text(); + } else if (typeof response.blob !== 'undefined') { + data = await response.blob(); + } else { + // Handle streams + data = response.body || response.data || null; } + } - response.data = data; - } else { - response.data = null; + // Add more information to response object + response.config = requestConfig; + response.data = data; - // Output error in similar format to what Axios does - throw new RequestError( - `fetchf() Request Failed! Status: ${response.status || null}`, + // Check if the response status is not outside the range 200-299 and if so, output error + if (!response.ok) { + throw new ResponseErr( + `${requestConfig.url} failed! Status: ${response.status || null}`, requestConfig, response, ); @@ -657,16 +664,13 @@ export class RequestHandler { // Global interceptors response = await interceptResponse(response, this.config.onResponse); - return this.processResponseData(response, requestConfig); + return this.outputResponse(response, requestConfig) as ResponseData & + FetchResponse; } catch (error) { if ( attempt === retries || !(await shouldRetry(error, attempt)) || - !retryOn?.includes( - (response as FetchResponse)?.status || - error?.response?.status || - error?.status, - ) + !retryOn?.includes(error?.response?.status || error?.status) ) { this.processError(error, requestConfig); @@ -687,7 +691,8 @@ export class RequestHandler { } } - return this.processResponseData(response, requestConfig); + return this.outputResponse(response, requestConfig) as ResponseData & + FetchResponse; } public async delay(ms: number): Promise { @@ -698,14 +703,41 @@ export class RequestHandler { ); } + public processHeaders( + response: FetchResponse, + ): HeadersObject { + if (!response.headers) { + return {}; + } + + let headersObject: HeadersObject = {}; + + // Handle Headers object with entries() method + if (response.headers instanceof Headers) { + for (const [key, value] of (response.headers as any).entries()) { + headersObject[key] = value; + } + } else { + // Handle plain object + headersObject = { ...(response.headers as HeadersObject) }; + } + + return headersObject; + } + /** - * Process response + * Output response * * @param response - Response payload * @param {RequestConfig} requestConfig - Request config - * @returns {*} Response data + * @param {*} error - whether the response is erroneous + * @returns {ResponseData | FetchResponse} Response data */ - protected processResponseData(response, requestConfig: RequestConfig) { + protected outputResponse( + response: FetchResponse, + requestConfig: RequestConfig, + error = null, + ): ResponseData | FetchResponse { const defaultResponse = typeof requestConfig.defaultResponse !== 'undefined' ? requestConfig.defaultResponse @@ -723,11 +755,12 @@ export class RequestHandler { // This is in fact a proper response but we may want to flatten it // To ease developers' lives when obtaining the response if ( + response.data !== null && typeof response.data === 'object' && - typeof response.data.data !== 'undefined' && + typeof (response.data as any).data !== 'undefined' && Object.keys(response.data).length === 1 ) { - return response.data.data; + return (response.data as any).data; } return response.data; @@ -735,6 +768,7 @@ export class RequestHandler { // If empty object is returned, ensure that the default response is used instead if ( + response !== null && typeof response === 'object' && response.constructor === Object && Object.keys(response).length === 0 @@ -742,23 +776,24 @@ export class RequestHandler { return defaultResponse; } - // For fetch() const isCustomFetcher = this.isCustomFetcher(); - if (!isCustomFetcher) { - return { - ...response, - headers: Array.from(response?.headers?.entries() || {}).reduce( - (acc, [key, value]) => { - acc[key] = value; - return acc; - }, - {}, - ), - config: requestConfig, - }; + if (isCustomFetcher) { + return response; } - return response; + if (error !== null) { + delete error?.response; + delete error?.request; + delete error?.config; + } + + // Native fetch() + return { + ...response, + error, + headers: this.processHeaders(response), + config: requestConfig, + }; } } diff --git a/src/response-error.ts b/src/response-error.ts new file mode 100644 index 0000000..f54ee2f --- /dev/null +++ b/src/response-error.ts @@ -0,0 +1,25 @@ +import type { FetchResponse, RequestConfig } from './types'; + +export class ResponseErr extends Error { + response: FetchResponse; + request: RequestConfig; + config: RequestConfig; + status: number; + statusText: string; + + constructor( + message: string, + requestInfo: RequestConfig, + response: FetchResponse, + ) { + super(message); + + this.name = 'ResponseError'; + this.message = message; + this.status = response.status; + this.statusText = response.statusText; + this.request = requestInfo; + this.config = requestInfo; + this.response = response; + } +} diff --git a/src/types/api-handler.ts b/src/types/api-handler.ts index 4579440..37e7580 100644 --- a/src/types/api-handler.ts +++ b/src/types/api-handler.ts @@ -6,15 +6,6 @@ import type { FetchResponse, } from './request-handler'; -// Utility type to check if a type is never -type IsNever = [T] extends [never] ? true : false; - -// Utility type to check if a type has keys -type HasKeys = keyof T extends never ? false : true; - -// Conditional Omit utility type -type ConditionalOmit = HasKeys extends true ? Omit : T; - // Common type definitions export declare type QueryParams = Record | null; export declare type BodyPayload = Record | null; @@ -26,7 +17,7 @@ export declare type APIResponse = unknown; // Endpoint function type export declare type Endpoint< - Response = APIResponse, + ResponseData = APIResponse, QueryParams = QueryParamsOrBody, PathParams = UrlPathParams, > = @@ -35,53 +26,59 @@ export declare type Endpoint< queryParams?: QueryParams, urlPathParams?: PathParams, requestConfig?: RequestConfig, - ): Promise>; + ): Promise>; } | { - ( - queryParams?: T | null, + ( + queryParams?: T, urlPathParams?: T2, requestConfig?: RequestConfig, - ): Promise>; + ): Promise>; }; -export type EndpointsRecord = { - [K in keyof EndpointsMethods]: EndpointsMethods[K] extends Endpoint< - infer Response, - infer QueryParams, - infer UrlPathParams - > - ? Endpoint - : Endpoint; +type EndpointDefaults = Endpoint; + +type Fn = (...args: unknown[]) => unknown; + +type EndpointsRecord = { + [K in keyof EndpointsMethods]: EndpointsMethods[K] extends Fn + ? EndpointsMethods[K] + : EndpointDefaults; }; -export type DefaultEndpoints = { - [K in keyof EndpointsMethods]: Endpoint; +type DefaultEndpoints = { + [K in keyof EndpointsCfg]: EndpointDefaults; }; -export type EndpointsConfig = - IsNever extends true - ? Record - : Record; +export type EndpointsConfig = Record< + keyof EndpointsMethods | string, + RequestConfig +>; + +type EndpointsConfigPart = [ + EndpointsCfg, +] extends [never] + ? unknown + : DefaultEndpoints>; -export type ApiHandlerReturnType = - EndpointsRecord & - (IsNever extends true - ? unknown - : DefaultEndpoints>) & - ApiHandlerMethods; +export type ApiHandlerReturnType< + EndpointsMethods extends object, + EndpointsCfg, +> = EndpointsRecord & + EndpointsConfigPart & + ApiHandlerMethods; export type ApiHandlerMethods = { config: ApiHandlerConfig; endpoints: EndpointsConfig; requestHandler: RequestHandler; getInstance: () => FetcherInstance; - request: ( + request: ( endpointName: keyof EndpointsMethods | string, queryParams?: QueryParams, urlPathParams?: UrlPathParams, requestConfig?: RequestConfig, - ) => Promise>; + ) => Promise>; }; export interface ApiHandlerConfig diff --git a/src/types/interceptor-manager.ts b/src/types/interceptor-manager.ts index f7d5ac2..3485d19 100644 --- a/src/types/interceptor-manager.ts +++ b/src/types/interceptor-manager.ts @@ -1,13 +1,9 @@ -import type { - BaseRequestHandlerConfig, - FetchResponse, - RequestResponse, -} from './request-handler'; +import type { FetchResponse, RequestHandlerConfig } from './request-handler'; export type RequestInterceptor = ( - config: BaseRequestHandlerConfig, -) => BaseRequestHandlerConfig | Promise; + config: RequestHandlerConfig, +) => RequestHandlerConfig | Promise; export type ResponseInterceptor = ( response: FetchResponse, -) => RequestResponse; +) => Promise>; diff --git a/src/types/request-handler.ts b/src/types/request-handler.ts index 0cbab29..e96c25a 100644 --- a/src/types/request-handler.ts +++ b/src/types/request-handler.ts @@ -31,17 +31,13 @@ export type NativeFetch = typeof fetch; export type FetcherInstance = unknown | null; -export type RequestResponse = Promise>; +export type ErrorHandlingStrategy = + | 'reject' + | 'silent' + | 'defaultResponse' + | 'softFail'; -export type ErrorHandlingStrategy = 'reject' | 'silent' | 'defaultResponse'; - -export type RequestError = ResponseError; - -interface ErrorHandlerClass { - process(error?: RequestError): unknown; -} - -type ErrorHandlerFunction = (error: RequestError) => unknown; +type ErrorHandlerInterceptor = (error: ResponseError) => unknown; export interface BaseRequestConfig { url?: string; @@ -49,7 +45,7 @@ export interface BaseRequestConfig { baseURL?: string; transformRequest?: Transformer | Transformer[]; transformResponse?: Transformer | Transformer[]; - headers?: any; + headers?: HeadersInit; params?: any; paramsSerializer?: (params: any) => string; data?: D; @@ -78,34 +74,31 @@ export interface BaseRequestConfig { insecureHTTPParser?: boolean; } -export interface ExtendedResponse extends Response { +export interface ExtendedResponse extends Omit { data: T; - config: BaseRequestConfig; + error: ResponseError; + headers: HeadersObject | HeadersInit; + config: ExtendedRequestConfig; + request?: ExtendedRequestConfig; } -export type FetchResponse = BaseFetchResponse & - ExtendedResponse; +export type FetchResponse = ExtendedResponse; -export interface BaseFetchResponse { - data: T; - status: number; - statusText: string; - headers: any; - config: BaseRequestConfig; - request?: any; +export interface HeadersObject { + [key: string]: string; } -export interface ResponseError extends Error { - config: BaseRequestConfig; +export interface ResponseError extends Error { code?: string; - request?: any; - response?: FetchResponse; isAxiosError: boolean; - toJSON: () => object; + config: ExtendedRequestConfig; + request?: ExtendedRequestConfig; + response?: FetchResponse; + toJSON?: () => object; } export interface Transformer { - (data: any, headers?: any): any; + (data: any, headers?: HeadersInit): any; } export interface Adapter { @@ -154,9 +147,6 @@ export interface TransitionalOptions { // eslint-disable-next-line @typescript-eslint/no-empty-object-type export interface ReturnedPromise extends Promise> {} -export type RequestConfigHeaders = Record & - HeadersInit; - /** * Interface for configuring retry options. */ @@ -203,7 +193,10 @@ export interface RetryOptions { /** * A function to determine whether to retry based on the error and attempt number. */ - shouldRetry?: (error: RequestError, attempt: number) => Promise; + shouldRetry?: ( + error: ResponseError, + attempt: number, + ) => Promise; } interface ExtendedRequestConfig extends BaseRequestConfig, RequestInit { @@ -215,13 +208,13 @@ interface ExtendedRequestConfig extends BaseRequestConfig, RequestInit { strategy?: ErrorHandlingStrategy; onRequest?: RequestInterceptor | RequestInterceptor[]; onResponse?: ResponseInterceptor | ResponseInterceptor[]; - onError?: ErrorHandlerFunction | ErrorHandlerClass; - headers?: RequestConfigHeaders; + onError?: ErrorHandlerInterceptor; + headers?: HeadersInit; signal?: AbortSignal; urlPathParams?: UrlPathParams; } -export interface BaseRequestHandlerConfig extends RequestConfig { +interface BaseRequestHandlerConfig extends RequestConfig { fetcher?: FetcherInstance; apiUrl?: string; logger?: unknown; diff --git a/test/api-handler.spec.ts b/test/api-handler.spec.ts index dea8465..b0e9c16 100644 --- a/test/api-handler.spec.ts +++ b/test/api-handler.spec.ts @@ -1,6 +1,5 @@ /* eslint-disable @typescript-eslint/no-explicit-any */ import axios from 'axios'; -import { mockErrorCallbackClass } from './request-error-handler.spec'; import { endpoints } from './mocks/endpoints'; import { createApiFetcher } from '../src'; @@ -10,7 +9,7 @@ describe('API Handler', () => { fetcher: axios, apiUrl, endpoints, - onError: new mockErrorCallbackClass(), + onError: jest.fn(), }; const userDataMock = { name: 'Mark', age: 20 }; diff --git a/test/request-error-handler.spec.ts b/test/request-error-handler.spec.ts deleted file mode 100644 index b759920..0000000 --- a/test/request-error-handler.spec.ts +++ /dev/null @@ -1,44 +0,0 @@ -import { RequestErrorHandler } from '../src/request-error-handler'; - -export const mockErrorCallbackClass = class CustomErrorHandler { - public process() { - return 'function called'; - } -}; - -describe('Request Error Handler', () => { - const mockErrorCallback = () => 'function called'; - - it('should call provided error callback', () => { - const httpRequestHandler = new RequestErrorHandler(null, mockErrorCallback); - httpRequestHandler.requestErrorService = jest - .fn() - .mockResolvedValue(mockErrorCallback); - - httpRequestHandler.process('My error text'); - - expect(httpRequestHandler.requestErrorService).toHaveBeenCalledTimes(1); - expect(httpRequestHandler.requestErrorService).toHaveBeenCalledWith( - new Error('My error text'), - ); - }); - - it('should call provided error class', () => { - const httpRequestHandler = new RequestErrorHandler( - null, - mockErrorCallbackClass, - ); - httpRequestHandler.requestErrorService.process = jest - .fn() - .mockResolvedValue(mockErrorCallbackClass); - - httpRequestHandler.process('My error text'); - - expect( - httpRequestHandler.requestErrorService.process, - ).toHaveBeenCalledTimes(1); - expect(httpRequestHandler.requestErrorService.process).toHaveBeenCalledWith( - new Error('My error text'), - ); - }); -}); diff --git a/test/request-handler.spec.ts b/test/request-handler.spec.ts index 4b311c3..2955510 100644 --- a/test/request-handler.spec.ts +++ b/test/request-handler.spec.ts @@ -864,7 +864,7 @@ describe('Request Handler', () => { const config = {}; await expect(requestHandler.request(url, data, config)).rejects.toThrow( - 'fetchf() Request Failed! Status: 500', + 'https://api.example.com/test-endpoint?key=value failed! Status: 500', ); expect(interceptRequest).toHaveBeenCalled(); @@ -889,7 +889,7 @@ describe('Request Handler', () => { const config = {}; await expect(requestHandler.request(url, data, config)).rejects.toThrow( - 'fetchf() Request Failed! Status: 404', + 'https://api.example.com/test-endpoint?key=value failed! Status: 404', ); expect(interceptRequest).toHaveBeenCalled(); @@ -1160,7 +1160,7 @@ describe('Request Handler', () => { }); }); - describe('processResponseData()', () => { + describe('outputResponse()', () => { it('should show nested data object if flattening is off', async () => { const requestHandler = new RequestHandler({ fetcher: axios,