Skip to content
This repository has been archived by the owner on May 22, 2023. It is now read-only.

Add custom fetch option #51

Merged
merged 2 commits into from
May 22, 2023
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
1 change: 1 addition & 0 deletions README.md
Original file line number Diff line number Diff line change
Expand Up @@ -206,6 +206,7 @@ createClient<paths>(options);
| Name | Type | Description |
| :-------------- | :------: | :--------------------------------------------------------------------------------------------------------------------------------------- |
| `baseUrl` | `string` | Prefix all fetch URLs with this option (e.g. `"https://myapi.dev/v1/"`). |
| `fetch` | `fetch` | Fetch function used for requests (defaults to `globalThis.fetch`) |
| (Fetch options) | | Any valid fetch option (`headers`, `mode`, `cache`, `signal` …) ([docs](https://developer.mozilla.org/en-US/docs/Web/API/fetch#options)) |

## 🎯 Project Goals
Expand Down
14 changes: 14 additions & 0 deletions src/index.test.ts
Original file line number Diff line number Diff line change
Expand Up @@ -180,6 +180,20 @@ describe('client', () => {
})
);
});

it('accepts a custom fetch function', async () => {
const data = { works: true };
const client = createClient<paths>({
fetch: async () =>
Promise.resolve({
headers: new Headers(),
json: async () => data,
status: 200,
ok: true,
} as Response),
});
expect((await client.get('/self', {})).data).toBe(data);
});
});

describe('get()', () => {
Expand Down
10 changes: 7 additions & 3 deletions src/index.ts
Original file line number Diff line number Diff line change
Expand Up @@ -7,6 +7,8 @@ const DEFAULT_HEADERS = {
interface ClientOptions extends RequestInit {
/** set the common root URL for all API requests */
baseUrl?: string;
/** custom fetch (defaults to globalThis.fetch) */
fetch?: typeof fetch;
}
export interface BaseParams {
params?: { query?: Record<string, unknown> };
Expand Down Expand Up @@ -51,17 +53,19 @@ export type FetchResponse<T> =
| { data: T extends { responses: any } ? NonNullable<FilterKeys<Success<T['responses']>, JSONLike>> : unknown; error?: never; response: Response }
| { data?: never; error: T extends { responses: any } ? NonNullable<FilterKeys<Error<T['responses']>, JSONLike>> : unknown; response: Response };

export default function createClient<Paths extends {}>(options?: ClientOptions) {
export default function createClient<Paths extends {}>(clientOptions: ClientOptions = {}) {
const { fetch = globalThis.fetch, ...options } = clientOptions;

const defaultHeaders = new Headers({
...DEFAULT_HEADERS,
...(options?.headers ?? {}),
...(options.headers ?? {}),
});

async function coreFetch<P extends keyof Paths, M extends HttpMethod>(url: P, fetchOptions: FetchOptions<M extends keyof Paths[P] ? Paths[P][M] : never>): Promise<FetchResponse<M extends keyof Paths[P] ? Paths[P][M] : unknown>> {
const { headers, body: requestBody, params = {}, querySerializer = (q: QuerySerializer<M extends keyof Paths[P] ? Paths[P][M] : never>) => new URLSearchParams(q as any).toString(), ...init } = fetchOptions || {};

// URL
let finalURL = `${options?.baseUrl ?? ''}${url as string}`;
let finalURL = `${options.baseUrl ?? ''}${url as string}`;
if ((params as any).path) {
for (const [k, v] of Object.entries((params as any).path)) finalURL = finalURL.replace(`{${k}}`, encodeURIComponent(String(v)));
}
Expand Down