Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
159 changes: 119 additions & 40 deletions README.md
Original file line number Diff line number Diff line change
Expand Up @@ -121,7 +121,9 @@ yarn add fetchff

#### `fetchf(url, config)`

It is a functional wrapper for `fetch()`. It seamlessly enhances it with additional settings like the retry mechanism and error handling improvements. The `fetchf()` can be used directly as a function, simplifying the usage and making it easier to integrate with functional programming styles. The `fetchf()` makes requests independently of `createApiFetcher()` settings.
_Alias: `fetchff(url, config)`_

A simple function that wraps the native `fetch()` and adds extra features like retries and better error handling. Use `fetchf()` directly for quick, enhanced requests - no need to set up `createApiFetcher()`. It works independently and is easy to use in any codebase.

#### Example

Expand All @@ -138,6 +140,24 @@ const { data, error } = await fetchf('/api/user-details', {

### Global Configuration

#### `getDefaultConfig()`

<details>
<summary><span style="cursor:pointer">Click to expand</span></summary>
<br>

Returns the current global default configuration used for all requests. This is useful for inspecting or debugging the effective global settings.

```typescript
import { getDefaultConfig } from 'fetchff';

// Retrieve the current global default config
const config = getDefaultConfig();
console.log('Current global fetchff config:', config);
```

</details>

#### `setDefaultConfig(customConfig)`

<details>
Expand Down Expand Up @@ -212,7 +232,7 @@ const { data, error } = await api.getUser({
All the Request Settings can be directly used in the function as global settings for all endpoints. They can be also used within the `endpoints` property (on per-endpoint basis). The exposed `endpoints` property is as follows:

- **`endpoints`**:
Type: `EndpointsConfig<EndpointsMethods>`
Type: `EndpointsConfig<EndpointTypes>`
List of your endpoints. Each endpoint is an object that accepts all the Request Settings (see the Basic Settings below). The endpoints are required to be specified.

#### How It Works
Expand Down Expand Up @@ -1761,9 +1781,9 @@ You can use the `onResponse` interceptor to customize how the response is handle
```typescript
interface FetchResponse<
ResponseData = any,
RequestBody = any,
QueryParams = any,
PathParams = any,
RequestBody = any,
> extends Response {
data: ResponseData | null; // The parsed response data, or null/defaultResponse if unavailable
error: ResponseError<
Expand Down Expand Up @@ -2223,42 +2243,93 @@ if (error) {

The `fetchff` package provides comprehensive TypeScript typings to enhance development experience and ensure type safety. Below are details on the available, exportable types for both `createApiFetcher()` and `fetchf()`.

### Generic Typings
### Typings for `fetchf<RequestType>()`

The `fetchff` package includes several generic types to handle various aspects of API requests and responses:
The `fetchf()` function includes types that help configure and manage network requests effectively:

- **`QueryParams<ParamsType>`**: Represents query parameters for requests. Can be an object, `URLSearchParams`, an array of name-value pairs, or `null`.
- **`BodyPayload<PayloadType>`**: Represents the request body. Can be `BodyInit`, an object, an array, a string, or `null`.
- **`UrlPathParams<UrlParamsType>`**: Represents URL path parameters. Can be an object or `null`.
- **`DefaultResponse`**: Default response for all requests. Default is: `any`.
```typescript
interface AddBookRequest {
response: AddBookResponseData;
params: AddBookQueryParams;
urlPathParams: AddBookPathParams;
body: AddBookRequestBody;
}

// You could also use: fetchf<Req<AddBookResponseData>> as a shorthand so not to create additional request interface
const { data: book } = await fetchf<AddBookRequest>('/api/add-book', {
method: 'POST',
});
// Your book is of type AddBookResponseData
```

- **`Req<ResponseData, RequestBody, QueryParams, UrlPathParams>`**: Represents a shorter 4-generics version of request object type for endpoints, allowing you to compose the shape of the request payload, query parameters, and path parameters for each request using a couple inline generics e.g. `fetchf<ResponseData, RequestBody, QueryParams, UrlPathParams>()`. While there is no plan for deprecation, this is for compatibility with older versions only. Aim to use the new method with single generic presented above instead. We don't use overload here to keep it all fast and snappy.
- **`RequestConfig`**: Main configuration options for the `fetchf()` function, including request settings, interceptors, and retry configurations.
- **`RetryConfig`**: Configuration options for retry mechanisms, including the number of retries, delay between retries, and backoff strategies.
- **`CacheConfig`**: Configuration options for caching, including cache time, custom cache keys, and cache invalidation rules.
- **`PollingConfig`**: Configuration options for polling, including polling intervals and conditions to stop polling.
- **`ErrorStrategy`**: Defines strategies for handling errors, such as rejection, soft fail, default response, and silent modes.

For a complete list of types and their definitions, refer to the [request-handler.ts](https://github.com/MattCCC/fetchff/blob/master/src/types/request-handler.ts) file.

### Typings for `createApiFetcher()`

The `createApiFetcher<EndpointsMethods, EndpointsConfiguration>()` function provides a robust set of types to define and manage API interactions.
The `createApiFetcher<EndpointTypes>()` function provides a robust set of types to define and manage API interactions.

- **`EndpointTypes`**: Represents the list of API endpoints with their respective settings. It is your own interface that you can pass to this generic. It will be cross-checked against the `endpoints` object in your `createApiFetcher()` configuration. Each endpoint can be configured with its own specific types such as Response Data Structure, Query Parameters, URL Path Parameters or Request Body. Example:

```typescript
interface EndpointTypes {
fetchBook: Endpoint<{
response: Book;
params: BookQueryParams;
urlPathParams: BookPathParams;
}>;
// or shorter version: fetchBook: EndpointReq<Book, undefined, BookQueryParams, BookPathParams>;
addBook: Endpoint<{
response: Book;
body: BookBody;
params: BookQueryParams;
urlPathParams: BookPathParams;
}>;
// or shorter version: fetchBook: EndpointReq<Book, BookBody, BookQueryParams, BookPathParams>;
someOtherEndpoint: Endpoint; // The generic is fully optional but it must be defined for endpoint not to output error
}

The key types are:
const api = createApiFetcher<EndpointTypes>({
baseURL: 'https://example.com/api',
endpoints: {
fetchBook: {
url: '/get-book',
},
addBook: {
url: '/add-book',
method: 'POST',
},
},
});

const { data: book } = await api.addBook();
// book will be of type Book
```

<br>

- **`EndpointsMethods`**: Represents the list of API endpoints with their respective settings. It is your own interface that you can pass to this generic. It will be cross-checked against the `endpoints` object in your `createApiFetcher()` configuration.<br><br>Each endpoint can be configured with its own specific settings such as Response Payload, Query Parameters and URL Path Parameters.
- **`Endpoint<ResponseData = DefaultResponse, QueryParams = DefaultParams, PathParams = DefaultUrlParams, RequestBody = DefaultPayload>`**: Represents an API endpoint function, allowing to be defined with optional query parameters, URL path parameters, request configuration (settings), and request body (data).
- **`EndpointsSettings`**: Configuration for API endpoints, including query parameters, URL path parameters, and additional request configurations. Default is `typeof endpoints`.
- **`Endpoint<{response: ResponseData, params: QueryParams, urlPathParams: PathParams, body: RequestBody}>`**: Represents an API endpoint function, allowing to be defined with optional response data (default `DefaultResponse`), query parameters (default `QueryParams`), URL path parameters (default `DefaultUrlParams`), and request body (default `DefaultPayload`).
- **`RequestInterceptor`**: Function to modify request configurations before they are sent.
- **`ResponseInterceptor`**: Function to process responses before they are handled by the application.
- **`ErrorInterceptor`**: Function to handle errors when a request fails.
- **`CustomFetcher`**: Represents the custom `fetcher` function.

For a full list of types and detailed definitions, refer to the [api-handler.ts](https://github.com/MattCCC/fetchff/blob/docs-update/src/types/api-handler.ts) file.
For a full list of types and detailed definitions, refer to the [api-handler.ts](https://github.com/MattCCC/fetchff/blob/master/src/types/api-handler.ts) file.

### Typings for `fetchf()`

The `fetchf()` function includes types that help configure and manage network requests effectively:
### Generic Typings

- **`RequestConfig`**: Main configuration options for the `fetchf()` function, including request settings, interceptors, and retry configurations.
- **`RetryConfig`**: Configuration options for retry mechanisms, including the number of retries, delay between retries, and backoff strategies.
- **`CacheConfig`**: Configuration options for caching, including cache time, custom cache keys, and cache invalidation rules.
- **`PollingConfig`**: Configuration options for polling, including polling intervals and conditions to stop polling.
- **`ErrorStrategy`**: Defines strategies for handling errors, such as rejection, soft fail, default response, and silent modes.
The `fetchff` package includes several generic types to handle various aspects of API requests and responses:

For a complete list of types and their definitions, refer to the [request-handler.ts](https://github.com/MattCCC/fetchff/blob/docs-update/src/types/request-handler.ts) file.
- **`QueryParams<ParamsType>`**: Represents query parameters for requests. Can be an object, `URLSearchParams`, an array of name-value pairs, or `null`.
- **`BodyPayload<PayloadType>`**: Represents the request body. Can be `BodyInit`, an object, an array, a string, or `null`.
- **`UrlPathParams<UrlParamsType>`**: Represents URL path parameters. Can be an object or `null`.
- **`DefaultResponse`**: Default response for all requests. Default is: `any`.

### Benefits of Using Typings

Expand Down Expand Up @@ -2713,7 +2784,7 @@ _fetchff uniquely combines advanced input sanitization, prototype pollution prot

## ✏️ Examples

Click to expand particular examples below. You can also check [examples.ts](./docs/examples/examples.ts) for more examples of usage.
Click to expand particular examples below. You can also check [docs/examples/](./docs/examples/) for more examples of usage.

### All Settings

Expand Down Expand Up @@ -2901,17 +2972,19 @@ const endpoints = {
} as const;

// Define endpoints with proper typing
interface EndpointsList {
fetchBook: Endpoint<Book, BookQueryParams, BookPathParams>;
fetchBooks: Endpoint<Books, BookQueryParams>;
interface EndpointTypes {
fetchBook: Endpoint<{
response: Book;
params: BookQueryParams;
urlPathParams: BookPathParams;
}>;
fetchBooks: Endpoint<{ response: Books; params: BookQueryParams }>;
}

type EndpointsConfiguration = typeof endpoints;

const api = createApiFetcher<EndpointsList, EndpointsConfiguration>({
const api = createApiFetcher<EndpointTypes>({
baseURL: 'https://example.com/api',
endpoints,
strategy: 'softFail',
endpoints,
});

export { api };
Expand All @@ -2934,7 +3007,7 @@ if (book.error) {
console.log('Book title:', book.data?.title);
}

// For example, this will cause a TypeScript error - 'rating' doesn't exist in BookQueryParams
// For example, this will cause a TypeScript error as 'rating' doesn't exist in BookQueryParams
// const invalidBook = await api.fetchBook({
// params: { rating: 5 }
// });
Expand Down Expand Up @@ -2992,13 +3065,15 @@ interface PostsPathParams {
subject: string;
}

interface EndpointsList {
getPosts: Endpoint<PostsResponse, PostsQueryParams, PostsPathParams>;
interface EndpointTypes {
getPosts: Endpoint<{
response: PostsResponse;
params: PostsQueryParams;
urlPathParams: PostsPathParams;
}>;
}

type EndpointsConfiguration = typeof endpoints;

const api = createApiFetcher<EndpointsList, EndpointsConfiguration>({
const api = createApiFetcher<EndpointTypes>({
baseURL: 'https://example.com/api',
endpoints,
onError(error) {
Expand Down Expand Up @@ -3046,14 +3121,14 @@ In the example above we fetch data from an API for user with an ID of 1. We also
<br>

```typescript
import { createApiFetcher, RequestConfig, FetchResponse } from 'fetchff';
import { createApiFetcher } from 'fetchff';

// Create the API fetcher with the custom fetcher
const api = createApiFetcher({
baseURL: 'https://api.example.com/',
retry: retryConfig,
// This function will be called whenever a request is being fired.
fetcher: async (config: RequestConfig): Promise<FetchResponse> => {
async fetcher(config) {
// Implement your custom fetch logic here
const response = await fetch(config.url, config);
// Optionally, process or transform the response
Expand Down Expand Up @@ -3499,6 +3574,8 @@ function notifyAdmins(healthData: any) {

Integrate `fetchff` with React Query to streamline your data fetching:

> **Note:** Official support for `useFetcher(url, config)` is here. Check React Integration section above to get an idea how to use it instead of SWR.

```tsx
import { createApiFetcher } from 'fetchff';
import { useQuery } from '@tanstack/react-query';
Expand Down Expand Up @@ -3531,6 +3608,8 @@ export const useProfile = (id: string) => {

Combine `fetchff` with SWR for efficient data fetching and caching.

> **Note:** Official support for `useFetcher(url, config)` is here. Check React Integration section above to get an idea how to use it instead of SWR.

Single calls:

```typescript
Expand Down
Binary file added docs/benchmarks/concurrency-react.png
Loading
Sorry, something went wrong. Reload?
Sorry, we cannot display this file.
Sorry, this file is invalid so it cannot be displayed.
Binary file added docs/benchmarks/react-benchmark.png
Loading
Sorry, something went wrong. Reload?
Sorry, we cannot display this file.
Sorry, this file is invalid so it cannot be displayed.
27 changes: 27 additions & 0 deletions docs/examples/example-basic.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,27 @@
import { createApiFetcher } from 'fetchff';

const api = createApiFetcher({
apiUrl: 'https://example.com/api',
endpoints: {
getUser: {
url: '/user-details/:id',
method: 'GET',
},
getBooks: {
url: '/books/all',
method: 'GET',
},
},
});

async function main() {
// Basic GET request with path param
const { data: user } = await api.getUser({ urlPathParams: { id: 2 } });
console.log('User:', user);

// Basic GET request to fetch all books
const { data: books } = await api.getBooks();
console.log('Books:', books);
}

main();
24 changes: 24 additions & 0 deletions docs/examples/example-custom-headers.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,24 @@
import { createApiFetcher } from 'fetchff';

const api = createApiFetcher({
apiUrl: 'https://api.example.com/',
headers: {
'Content-Type': 'application/json',
Authorization: 'Bearer YOUR_TOKEN',
},
endpoints: {
getProfile: {
url: '/profile/:id',
},
},
});

async function main() {
// GET request with custom headers and path param
const { data: profile } = await api.getProfile({
urlPathParams: { id: 123 },
});
console.log('Profile:', profile);
}

main();
34 changes: 34 additions & 0 deletions docs/examples/example-error-strategy-defaultResponse.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,34 @@
import { createApiFetcher } from 'fetchff';

const api = createApiFetcher({
apiUrl: 'https://example.com/api',
// strategy: 'defaultResponse',
endpoints: {
sendMessage: {
method: 'post',
url: '/send-message/:postId',
// strategy: 'defaultResponse',
},
},
});

async function sendMessage() {
const { data, error } = await api.sendMessage({
body: { message: 'Text' },
urlPathParams: { postId: 1 },
strategy: 'defaultResponse',
defaultResponse: { status: 'failed', message: 'Default response' },
onError(error) {
console.error('API error:', error.message);
},
});

if (error) {
console.warn('Message failed to send, using default response:', data);
return;
}

console.log('Message sent successfully:', data);
}

sendMessage();
27 changes: 27 additions & 0 deletions docs/examples/example-error-strategy-reject.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,27 @@
import { createApiFetcher } from 'fetchff';
import type { ResponseError } from 'fetchff';

const api = createApiFetcher({
apiUrl: 'https://example.com/api',
endpoints: {
sendMessage: {
method: 'post',
url: '/send-message/:postId',
strategy: 'reject',
},
},
});

async function sendMessage() {
try {
await api.sendMessage({
body: { message: 'Text' },
urlPathParams: { postId: 1 },
});
console.log('Message sent successfully');
} catch (error) {
console.error('Message failed to send:', (error as ResponseError).message);
}
}

sendMessage();
Loading
Loading