@zimic/fetch
Version:
Next-gen, TypeScript-first fetch-like API client
1,066 lines (1,058 loc) • 45.1 kB
TypeScript
import { HttpSchema, HttpSchemaMethod, HttpSchemaPath, HttpRequestSchema, HttpHeaders, HttpSearchParams, JSONValue, HttpMethod, HttpHeadersSchema, HttpSearchParamsSchema, HttpRequest, HttpMethodSchema, HttpRequestHeadersSchema, AllowAnyStringInPathParams, HttpStatusCode, HttpResponseSchemaStatusCode, HttpResponse, HttpResponseBodySchema, HttpResponseHeadersSchema, LiteralHttpSchemaPathFromNonLiteral } from '@zimic/http';
declare const value: unique symbol;
/**
* Represents a value stringified by `JSON.stringify`, maintaining a reference to the original type.
*
* This type is used to validate that the expected stringified body is passed to `fetch`.
*
* @see {@link https://github.com/zimicjs/zimic/wiki/api‐zimic‐fetch#using-a-json-body}
*/
type JSONStringified<Value> = string & {
[value]: Value;
};
declare global {
interface JSON {
stringify<Value>(value: Value, replacer?: ((this: any, key: string, value: Value) => any) | (number | string)[] | null, space?: string | number): JSONStringified<Value>;
}
}
type Default<Type, IfEmpty = never> = [undefined | void] extends [Type] ? IfEmpty : Exclude<Type, undefined | void>;
type DefaultNoExclude<Type, IfEmpty = never> = [undefined | void] extends Type ? IfEmpty : Type;
type IfNever<Type, Yes, No = Type> = [Type] extends [never] ? Yes : No;
type PossiblePromise<Type> = Type | PromiseLike<Type>;
type ReplaceBy<Type, Source, Target> = Type extends Source ? Target : Type;
type RequiredByKey<Type, Key extends keyof Type> = Omit<Type, Key> & Required<Pick<Type, Key>>;
type FetchRequestInitHeaders<RequestSchema extends HttpRequestSchema> = RequestSchema['headers'] | HttpHeaders<Default<RequestSchema['headers']>>;
type FetchRequestInitWithHeaders<RequestSchema extends HttpRequestSchema> = 'headers' extends keyof RequestSchema ? [RequestSchema['headers']] extends [never] ? {
headers?: undefined;
} : undefined extends RequestSchema['headers'] ? {
headers?: FetchRequestInitHeaders<RequestSchema>;
} : {
headers: FetchRequestInitHeaders<RequestSchema>;
} : {
headers?: undefined;
};
type FetchRequestInitSearchParams<RequestSchema extends HttpRequestSchema> = RequestSchema['searchParams'] | HttpSearchParams<Default<RequestSchema['searchParams']>>;
type FetchRequestInitWithSearchParams<RequestSchema extends HttpRequestSchema> = 'searchParams' extends keyof RequestSchema ? [RequestSchema['searchParams']] extends [never] ? {
searchParams?: undefined;
} : undefined extends RequestSchema['searchParams'] ? {
searchParams?: FetchRequestInitSearchParams<RequestSchema>;
} : {
searchParams: FetchRequestInitSearchParams<RequestSchema>;
} : {
searchParams?: undefined;
};
type FetchRequestInitWithBody<RequestSchema extends HttpRequestSchema> = 'body' extends keyof RequestSchema ? [RequestSchema['body']] extends [never] ? {
body?: null;
} : RequestSchema['body'] extends string ? undefined extends RequestSchema['body'] ? {
body?: ReplaceBy<RequestSchema['body'], undefined, null>;
} : {
body: RequestSchema['body'];
} : RequestSchema['body'] extends JSONValue ? undefined extends RequestSchema['body'] ? {
body?: JSONStringified<ReplaceBy<RequestSchema['body'], undefined, null>>;
} : {
body: JSONStringified<RequestSchema['body']>;
} : undefined extends RequestSchema['body'] ? {
body?: ReplaceBy<RequestSchema['body'], undefined, null>;
} : {
body: RequestSchema['body'];
} : {
body?: null;
};
type FetchRequestInitPerPath<RequestSchema extends HttpRequestSchema> = FetchRequestInitWithHeaders<RequestSchema> & FetchRequestInitWithSearchParams<RequestSchema> & FetchRequestInitWithBody<RequestSchema>;
/**
* The options to create a {@link FetchRequest} instance, compatible with
* {@link https://developer.mozilla.org/docs/Web/API/RequestInit `RequestInit`}.
*
* @see {@link https://github.com/zimicjs/zimic/wiki/api‐zimic‐fetch#fetch `fetch` API reference}
* @see {@link https://developer.mozilla.org/docs/Web/API/RequestInit `RequestInit`}
*/
type FetchRequestInit<Schema extends HttpSchema, Method extends HttpSchemaMethod<Schema>, Path extends HttpSchemaPath<Schema, Method>, Redirect extends RequestRedirect = 'follow'> = Omit<RequestInit, 'method' | 'headers' | 'body'> & {
/** The HTTP method of the request. */
method: Method;
/** The base URL to prefix the path of the request. */
baseURL?: string;
redirect?: Redirect;
} & (Path extends Path ? FetchRequestInitPerPath<Default<Default<Schema[Path][Method]>['request']>> : never);
declare namespace FetchRequestInit {
/** The default options for each request sent by a fetch instance. */
interface Defaults extends Omit<RequestInit, 'headers'> {
baseURL: string;
/** The HTTP method of the request. */
method?: HttpMethod;
/** The headers of the request. */
headers?: HttpHeadersSchema;
/** The search parameters of the request. */
searchParams?: HttpSearchParamsSchema;
}
/** A loosely typed version of {@link FetchRequestInit `FetchRequestInit`}. */
type Loose = Partial<Defaults>;
}
type AllFetchResponseStatusCode<MethodSchema extends HttpMethodSchema> = HttpResponseSchemaStatusCode<Default<MethodSchema['response']>>;
type FilterFetchResponseStatusCodeByError<StatusCode extends HttpStatusCode, ErrorOnly extends boolean> = ErrorOnly extends true ? Extract<StatusCode, HttpStatusCode.ClientError | HttpStatusCode.ServerError> : StatusCode;
type FilterFetchResponseStatusCodeByRedirect<StatusCode extends HttpStatusCode, Redirect extends RequestRedirect> = Redirect extends 'error' ? FilterFetchResponseStatusCodeByRedirect<StatusCode, 'follow'> : Redirect extends 'follow' ? Exclude<StatusCode, Exclude<HttpStatusCode.Redirection, 304>> : StatusCode;
type FetchResponseStatusCode<MethodSchema extends HttpMethodSchema, ErrorOnly extends boolean, Redirect extends RequestRedirect> = FilterFetchResponseStatusCodeByRedirect<FilterFetchResponseStatusCodeByError<AllFetchResponseStatusCode<MethodSchema>, ErrorOnly>, Redirect>;
type HttpRequestBodySchema<MethodSchema extends HttpMethodSchema> = ReplaceBy<ReplaceBy<IfNever<DefaultNoExclude<Default<MethodSchema['request']>['body']>, null>, undefined, null>, ArrayBuffer, Blob>;
/**
* A request instance typed with an HTTP schema, closely compatible with the
* {@link https://developer.mozilla.org/docs/Web/API/Request native Request class}.
*
* On top of the properties available in native {@link https://developer.mozilla.org/docs/Web/API/Request `Request`}
* instances, fetch requests have their URL automatically prefixed with the base URL of their fetch instance. Default
* options are also applied, if present in the fetch instance.
*
* The path of the request is extracted from the URL, excluding the base URL, and is available in the `path` property.
*
* @example
* import { type HttpSchema } from '@zimic/http';
* import { createFetch } from '@zimic/fetch';
*
* interface User {
* id: string;
* username: string;
* }
*
* type Schema = HttpSchema<{
* '/users': {
* POST: {
* request: {
* headers: { 'content-type': 'application/json' };
* body: { username: string };
* };
* response: {
* 201: { body: User };
* };
* };
* };
* }>;
*
* const fetch = createFetch<Schema>({
* baseURL: 'http://localhost:3000',
* });
*
* const request = new fetch.Request('/users', {
* method: 'POST',
* headers: { 'content-type': 'application/json' },
* body: JSON.stringify({ username: 'me' }),
* });
*
* console.log(request); // FetchRequest<Schema, 'POST', '/users'>
* console.log(request.path); // '/users'
*
* @see {@link https://github.com/zimicjs/zimic/wiki/api‐zimic‐fetch#fetchrequest `FetchRequest` API reference}
* @see {@link https://developer.mozilla.org/docs/Web/API/Request}
*/
interface FetchRequest<Schema extends HttpSchema, Method extends HttpSchemaMethod<Schema>, Path extends HttpSchemaPath.Literal<Schema, Method>> extends HttpRequest<HttpRequestBodySchema<Default<Schema[Path][Method]>>, HttpRequestHeadersSchema<Default<Schema[Path][Method]>>> {
/** The path of the request, excluding the base URL. */
path: AllowAnyStringInPathParams<Path>;
/** The HTTP method of the request. */
method: Method;
}
declare namespace FetchRequest {
/** A loosely typed version of a {@link FetchRequest `FetchRequest`}. */
interface Loose extends Request {
/** The path of the request, excluding the base URL. */
path: string;
/** The HTTP method of the request. */
method: HttpMethod;
/** Clones the request instance, returning a new instance with the same properties. */
clone: () => Loose;
}
}
/**
* A plain object representation of a {@link FetchRequest `FetchRequest`}, compatible with JSON.
*
* If the body is included in the object, it is represented as a string or null if empty.
*/
type FetchRequestObject = Pick<FetchRequest.Loose, 'url' | 'path' | 'method' | 'cache' | 'destination' | 'credentials' | 'integrity' | 'keepalive' | 'mode' | 'redirect' | 'referrer' | 'referrerPolicy'> & {
/** [MDN Reference](https://developer.mozilla.org/docs/Web/API/Response/headers) */
headers: HttpHeadersSchema;
/**
* The body of the response, represented as a string or null if empty.
*
* [MDN Reference](https://developer.mozilla.org/docs/Web/API/Request/body)
*/
body?: string | null;
};
/**
* A {@link FetchResponse `FetchResponse`} instance with a specific status code.
*
* @see {@link https://github.com/zimicjs/zimic/wiki/api‐zimic‐fetch#fetchresponse `FetchResponse` API reference}
* @see {@link https://developer.mozilla.org/docs/Web/API/Response}
*/
interface FetchResponsePerStatusCode<Schema extends HttpSchema, Method extends HttpSchemaMethod<Schema>, Path extends HttpSchemaPath.Literal<Schema, Method>, StatusCode extends HttpStatusCode = HttpStatusCode> extends HttpResponse<HttpResponseBodySchema<Default<Schema[Path][Method]>, StatusCode>, StatusCode, HttpResponseHeadersSchema<Default<Schema[Path][Method]>, StatusCode>> {
/** The request that originated the response. */
request: FetchRequest<Schema, Method, Path>;
/**
* An error representing a response with a failure status code (4XX or 5XX). It can be thrown to handle the error
* upper in the call stack.
*
* If the response has a success status code (1XX, 2XX or 3XX), this property will be null.
*/
error: StatusCode extends HttpStatusCode.ClientError | HttpStatusCode.ServerError ? FetchResponseError<Schema, Method, Path> : null;
}
/**
* A response instance typed with an HTTP schema, closely compatible with the
* {@link https://developer.mozilla.org/docs/Web/API/Response native Response class}.
*
* On top of the properties available in native Response instances, fetch responses have a reference to the request that
* originated them, available in the `request` property.
*
* If the response has a failure status code (4XX or 5XX), an error is available in the `error` property.
*
* @example
* import { type HttpSchema } from '@zimic/http';
* import { createFetch } from '@zimic/fetch';
*
* interface User {
* id: string;
* username: string;
* }
*
* type Schema = HttpSchema<{
* '/users/:userId': {
* GET: {
* response: {
* 200: { body: User };
* 404: { body: { message: string } };
* };
* };
* };
* }>;
*
* const fetch = createFetch<Schema>({
* baseURL: 'http://localhost:3000',
* });
*
* const response = await fetch(`/users/${userId}`, {
* method: 'GET',
* });
*
* console.log(response); // FetchResponse<Schema, 'GET', '/users'>
*
* if (response.status === 404) {
* const errorBody = await response.json(); // { message: string }
* console.error(errorBody.message);
* return null;
* } else {
* const user = await response.json(); // User
* return user;
* }
*
* @see {@link https://github.com/zimicjs/zimic/wiki/api‐zimic‐fetch#fetchresponse `FetchResponse` API reference}
* @see {@link https://developer.mozilla.org/docs/Web/API/Response}
*/
type FetchResponse<Schema extends HttpSchema, Method extends HttpSchemaMethod<Schema>, Path extends HttpSchemaPath.Literal<Schema, Method>, ErrorOnly extends boolean = false, Redirect extends RequestRedirect = 'follow', StatusCode extends FetchResponseStatusCode<Default<Schema[Path][Method]>, ErrorOnly, Redirect> = FetchResponseStatusCode<Default<Schema[Path][Method]>, ErrorOnly, Redirect>> = StatusCode extends StatusCode ? FetchResponsePerStatusCode<Schema, Method, Path, StatusCode> : never;
declare namespace FetchResponse {
/** A loosely typed version of a {@link FetchResponse}. */
interface Loose extends Response {
/** The request that originated the response. */
request: FetchRequest.Loose;
/**
* An error representing a response with a failure status code (4XX or 5XX). It can be thrown to handle the error
* upper in the call stack.
*
* If the response has a success status code (1XX, 2XX or 3XX), this property will be null.
*/
error: AnyFetchRequestError | null;
/** Clones the request instance, returning a new instance with the same properties. */
clone: () => Loose;
}
}
/**
* A plain object representation of a {@link FetchResponse `FetchResponse`}, compatible with JSON.
*
* If the body is included in the object, it is represented as a string or null if empty.
*/
type FetchResponseObject = Pick<FetchResponse.Loose, 'url' | 'type' | 'status' | 'statusText' | 'ok' | 'redirected'> & {
/** [MDN Reference](https://developer.mozilla.org/docs/Web/API/Response/headers) */
headers: HttpHeadersSchema;
/**
* The body of the response, represented as a string or null if empty.
*
* [MDN Reference](https://developer.mozilla.org/docs/Web/API/Request/body)
*/
body?: string | null;
};
/**
* A constructor for {@link FetchRequest} instances, typed with an HTTP schema and compatible with the
* {@link https://developer.mozilla.org/docs/Web/API/Request Request class constructor}.
*
* @example
* import { type HttpSchema } from '@zimic/http';
* import { createFetch } from '@zimic/fetch';
*
* type Schema = HttpSchema<{
* // ...
* }>;
*
* const fetch = createFetch<Schema>({
* baseURL: 'http://localhost:3000',
* });
*
* const request = new fetch.Request('POST', '/users', {
* body: JSON.stringify({ username: 'me' }),
* });
* console.log(request); // FetchRequest<Schema, 'POST', '/users'>
*
* @param input The resource to fetch, either a path, a URL, or a {@link FetchRequest request}. If a path is provided, it
* is automatically prefixed with the base URL of the fetch instance when the request is sent. If a URL or a request
* is provided, it is used as is.
* @param init The request options. If a path or a URL is provided as the first argument, this argument is required and
* should contain at least the method of the request. If the first argument is a {@link FetchRequest request}, this
* argument is optional.
* @returns A promise that resolves to the response to the request.
* @see {@link https://github.com/zimicjs/zimic/wiki/api‐zimic‐fetch#fetchresponse `FetchResponse` API reference}
* @see {@link https://developer.mozilla.org/docs/Web/API/Request}
*/
type FetchRequestConstructor<Schema extends HttpSchema> = new <Method extends HttpSchemaMethod<Schema>, Path extends HttpSchemaPath.NonLiteral<Schema, Method>>(input: FetchInput<Schema, Method, Path>, init: FetchRequestInit<Schema, Method, LiteralHttpSchemaPathFromNonLiteral<Schema, Method, Path>>) => FetchRequest<Schema, Method, LiteralHttpSchemaPathFromNonLiteral<Schema, Method, Path>>;
/**
* Options for converting a {@link FetchResponseError `FetchResponseError`} into a plain object.
*
* @see {@link https://github.com/zimicjs/zimic/wiki/api‐zimic‐fetch#fetchresponseerrortoobject `FetchResponseError#toObject` API reference}
*/
interface FetchResponseErrorObjectOptions {
/**
* Whether to include the body of the request in the plain object.
*
* @default false
*/
includeRequestBody?: boolean;
/**
* Whether to include the body of the response in the plain object.
*
* @default false
*/
includeResponseBody?: boolean;
}
declare namespace FetchResponseErrorObjectOptions {
/**
* Options for converting a {@link FetchResponseError `FetchResponseError`} into a plain object, including the body of
* the request and/or response.
*/
type WithBody = FetchResponseErrorObjectOptions & ({
includeRequestBody: true;
} | {
includeResponseBody: true;
});
/**
* Options for converting a {@link FetchResponseError `FetchResponseError`} into a plain object, excluding the body of
* the request and/or response.
*/
type WithoutBody = FetchResponseErrorObjectOptions & ({
includeRequestBody?: false;
} | {
includeResponseBody?: false;
});
}
/**
* A plain object representation of a {@link FetchResponseError `FetchResponseError`}, compatible with JSON. It is useful
* for serialization, debugging, and logging purposes.
*
* @see {@link https://github.com/zimicjs/zimic/wiki/api‐zimic‐fetch#fetchresponseerrortoobject `FetchResponseError#toObject` API reference}
*/
interface FetchResponseErrorObject {
name: string;
message: string;
request: FetchRequestObject;
response: FetchResponseObject;
}
/**
* An error representing a response with a failure status code (4XX or 5XX).
*
* @example
* import { type HttpSchema } from '@zimic/http';
* import { createFetch } from '@zimic/fetch';
*
* interface User {
* id: string;
* username: string;
* }
*
* type Schema = HttpSchema<{
* '/users/:userId': {
* GET: {
* response: {
* 200: { body: User };
* 404: { body: { message: string } };
* };
* };
* };
* }>;
*
* const fetch = createFetch<Schema>({
* baseURL: 'http://localhost:3000',
* });
*
* const response = await fetch(`/users/${userId}`, {
* method: 'GET',
* });
*
* if (!response.ok) {
* console.log(response.status); // 404
*
* console.log(response.error); // FetchResponseError<Schema, 'GET', '/users'>
* console.log(response.error.request); // FetchRequest<Schema, 'GET', '/users'>
* console.log(response.error.response); // FetchResponse<Schema, 'GET', '/users'>
*
* const plainError = response.error.toObject();
* console.log(JSON.stringify(plainError));
* // {"name":"FetchResponseError","message":"...","request":{...},"response":{...}}
* }
*
* @see {@link https://github.com/zimicjs/zimic/wiki/api‐zimic‐fetch#fetchresponseerror `FetchResponseError` API reference}
*/
declare class FetchResponseError<Schema extends HttpSchema, Method extends HttpSchemaMethod<Schema>, Path extends HttpSchemaPath.Literal<Schema, Method>> extends Error {
request: FetchRequest<Schema, Method, Path>;
response: FetchResponse<Schema, Method, Path, true, 'manual'>;
constructor(request: FetchRequest<Schema, Method, Path>, response: FetchResponse<Schema, Method, Path, true, 'manual'>);
/**
* Converts this error into a plain object. This method is useful for serialization, debugging, and logging purposes.
*
* @example
* const fetch = createFetch<Schema>({
* baseURL: 'http://localhost:3000',
* });
*
* const response = await fetch(`/users/${userId}`, {
* method: 'GET',
* });
*
* if (!response.ok) {
* const plainError = response.error.toObject();
* console.log(JSON.stringify(plainError));
* // {"name":"FetchResponseError","message":"...","request":{...},"response":{...}}
* }
*
* @param options Options for converting this error into a plain object. By default, the body of the request and
* response will not be included.
* @returns A plain object representing this error. If `options.includeRequestBody` or `options.includeResponseBody`
* is `true`, the body of the request and response will be included, respectively, and the return is a `Promise`.
* Otherwise, the return is the plain object itself without the bodies.
* @see {@link https://github.com/zimicjs/zimic/wiki/api‐zimic‐fetch#fetchresponseerrortoobject `FetchResponseError#toObject` API reference}
*/
toObject(options: FetchResponseErrorObjectOptions.WithBody): Promise<FetchResponseErrorObject>;
toObject(options: FetchResponseErrorObjectOptions.WithoutBody): FetchResponseErrorObject;
toObject(options?: FetchResponseErrorObjectOptions): Promise<FetchResponseErrorObject> | FetchResponseErrorObject;
private convertRequestToObject;
private convertResponseToObject;
}
type AnyFetchRequestError = FetchResponseError<any, any, any>;
/**
* The input to fetch a resource, either a path, a URL, or a {@link FetchRequest request}.
*
* @see {@link https://github.com/zimicjs/zimic/wiki/api‐zimic‐fetch#fetch `fetch` API reference}
*/
type FetchInput<Schema extends HttpSchema, Method extends HttpSchemaMethod<Schema>, Path extends HttpSchemaPath.NonLiteral<Schema, Method>> = Path | URL | FetchRequest<Schema, Method, LiteralHttpSchemaPathFromNonLiteral<Schema, Method, Path>>;
/**
* A fetch instance typed with an HTTP schema, closely compatible with the
* {@link https://developer.mozilla.org/docs/Web/API/Fetch_API native Fetch API}. All requests and responses are typed by
* default with the schema, including methods, paths, status codes, parameters, and bodies.
*
* Requests sent by the fetch instance have their URL automatically prefixed with the base URL of the instance. Default
* options are also applied to the requests, if present in the instance.
*
* @example
* import { type HttpSchema } from '@zimic/http';
* import { createFetch } from '@zimic/fetch';
*
* interface User {
* id: string;
* username: string;
* }
*
* type Schema = HttpSchema<{
* '/users': {
* GET: {
* request: {
* searchParams: { query?: string };
* };
* response: {
* 200: { body: User[] };
* 404: { body: { message: string } };
* 500: { body: { message: string } };
* };
* };
* };
* }>;
*
* const fetch = createFetch<Schema>({
* baseURL: 'http://localhost:3000',
* headers: { 'accept-language': 'en' },
* });
*
* const response = await fetch('/users', {
* method: 'GET',
* searchParams: { query: 'u' },
* });
*
* if (response.status === 404) {
* return null; // User not found
* }
*
* if (!response.ok) {
* throw response.error;
* }
*
* const users = await response.json();
* return users; // User[]
*
* @param input The resource to fetch, either a path, a URL, or a {@link FetchRequest request}. If a path is provided, it
* is automatically prefixed with the base URL of the fetch instance when the request is sent. If a URL or a request
* is provided, it is used as is.
* @param init The request options. If a path or a URL is provided as the first argument, this argument is required and
* should contain at least the method of the request. If the first argument is a {@link FetchRequest request}, this
* argument is optional.
* @returns A promise that resolves to the response to the request.
* @see {@link https://github.com/zimicjs/zimic/wiki/api‐zimic‐fetch#fetch `fetch` API reference}
* @see {@link https://developer.mozilla.org/docs/Web/API/Fetch_API}
* @see {@link https://developer.mozilla.org/docs/Web/API/Request}
* @see {@link https://developer.mozilla.org/docs/Web/API/RequestInit}
* @see {@link https://developer.mozilla.org/docs/Web/API/Response}
*/
interface Fetch<Schema extends HttpSchema> extends Pick<FetchOptions<Schema>, 'onRequest' | 'onResponse'> {
<Method extends HttpSchemaMethod<Schema>, Path extends HttpSchemaPath.NonLiteral<Schema, Method>, Redirect extends RequestRedirect = 'follow'>(input: Path | URL, init: FetchRequestInit<Schema, Method, LiteralHttpSchemaPathFromNonLiteral<Schema, Method, Path>, Redirect>): Promise<FetchResponse<Schema, Method, LiteralHttpSchemaPathFromNonLiteral<Schema, Method, Path>, false, Redirect>>;
<Method extends HttpSchemaMethod<Schema>, Path extends HttpSchemaPath.NonLiteral<Schema, Method>, Redirect extends RequestRedirect = 'follow'>(input: FetchRequest<Schema, Method, LiteralHttpSchemaPathFromNonLiteral<Schema, Method, Path>>, init?: FetchRequestInit<Schema, Method, LiteralHttpSchemaPathFromNonLiteral<Schema, Method, Path>, Redirect>): Promise<FetchResponse<Schema, Method, LiteralHttpSchemaPathFromNonLiteral<Schema, Method, Path>, false, Redirect>>;
/**
* The default options for each request sent by the fetch instance. The available options are the same as the
* {@link https://developer.mozilla.org/docs/Web/API/RequestInit `RequestInit`} options, plus `baseURL`.
*
* @example
* import { type HttpSchema } from '@zimic/http';
* import { createFetch } from '@zimic/fetch';
*
* interface Post {
* id: string;
* title: string;
* }
*
* type Schema = HttpSchema<{
* '/posts': {
* POST: {
* request: {
* headers: { 'content-type': 'application/json' };
* body: { title: string };
* };
* response: {
* 201: { body: Post };
* };
* };
* };
* }>;
*
* const fetch = createFetch<Schema>({
* baseURL: 'http://localhost:3000',
* headers: { 'accept-language': 'en' },
* });
*
* // Set the authorization header for all requests
* const { accessToken } = await authenticate();
*
* fetch.defaults.headers.authorization = `Bearer ${accessToken}`;
* console.log(fetch.defaults.headers);
*
* const response = await fetch('/posts', {
* method: 'POST',
* headers: { 'content-type': 'application/json' },
* body: JSON.stringify({ title: 'My post' }),
* });
*
* const post = await response.json(); // Post
*/
defaults: FetchDefaults;
/**
* A loosely-typed version of {@link Fetch `fetch`}. This can be useful to make requests with fewer type constraints,
* such as in {@link onRequest `onRequest`} and {@link onResponse `onResponse`} listeners.
*
* @example
* import { type HttpSchema } from '@zimic/http';
* import { createFetch } from '@zimic/fetch';
*
* interface User {
* id: string;
* username: string;
* }
*
* type Schema = HttpSchema<{
* '/auth/login': {
* POST: {
* request: {
* headers: { 'content-type': 'application/json' };
* body: { username: string; password: string };
* };
* response: {
* 201: { body: { accessToken: string } };
* };
* };
* };
*
* '/auth/refresh': {
* POST: {
* response: {
* 201: { body: { accessToken: string } };
* };
* };
* };
*
* '/users': {
* GET: {
* request: {
* headers: { authorization: string };
* };
* response: {
* 200: { body: User[] };
* 401: { body: { message: string } };
* 403: { body: { message: string } };
* };
* };
* };
* }>;
*
* const fetch = createFetch<Schema>({
* baseURL,
*
* async onResponse(response) {
* if (response.status === 401) {
* const body = await response.clone().json();
*
* if (body.message === 'Access token expired') {
* // Refresh the access token
* const refreshResponse = await this('/auth/refresh', { method: 'POST' });
* const { accessToken } = await refreshResponse.json();
*
* // Clone the original request and update its headers
* const updatedRequest = response.request.clone();
* updatedRequest.headers.set('authorization', `Bearer ${accessToken}`);
*
* // Retry the original request with the updated headers
* return this.loose(updatedRequest);
* }
* }
*
* return response;
* },
* });
*
* // Authenticate to your service before requests
* const loginRequest = await fetch('/auth/login', {
* method: 'POST',
* headers: { 'content-type': 'application/json' },
* body: JSON.stringify({ username: 'me', password: 'password' }),
* });
* const { accessToken } = await loginRequest.json();
*
* // Set the authorization header for all requests
* fetch.defaults.headers.authorization = `Bearer ${accessToken}`;
*
* const request = await fetch('/users', {
* method: 'GET',
* searchParams: { query: 'u' },
* });
*
* const users = await request.json(); // User[]
*
* @param input The resource to fetch, either a path, a URL, or a {@link FetchRequest request}. If a path is provided,
* it is automatically prefixed with the base URL of the fetch instance when the request is sent. If a URL or a
* request is provided, it is used as is.
* @param init The request options. If a path or a URL is provided as the first argument, this argument is required
* and should contain at least the method of the request. If the first argument is a {@link FetchRequest request},
* this argument is optional.
* @returns A promise that resolves to the response to the request.
* @see {@link https://github.com/zimicjs/zimic/wiki/api‐zimic‐fetch#fetchloose `fetch.loose` API reference}
* @see {@link https://developer.mozilla.org/docs/Web/API/Fetch_API}
* @see {@link https://developer.mozilla.org/docs/Web/API/Request}
* @see {@link https://developer.mozilla.org/docs/Web/API/RequestInit}
* @see {@link https://developer.mozilla.org/docs/Web/API/Response}
*/
loose: Fetch.Loose;
/**
* A constructor for creating
* {@link https://github.com/zimicjs/zimic/wiki/api‐zimic‐fetch#fetchrequest-1 `FetchRequest`}, closely compatible with
* the native {@link https://developer.mozilla.org/docs/Web/API/Request Request} constructor.
*
* @example
* import { type HttpSchema } from '@zimic/http';
* import { createFetch } from '@zimic/fetch';
*
* interface User {
* id: string;
* username: string;
* }
*
* type Schema = HttpSchema<{
* '/users': {
* POST: {
* request: {
* headers: { 'content-type': 'application/json' };
* body: { username: string };
* };
* response: {
* 201: { body: User };
* };
* };
* };
* }>;
*
* const fetch = createFetch<Schema>({
* baseURL: 'http://localhost:3000',
* });
*
* const request = new fetch.Request('/users', {
* method: 'POST',
* headers: { 'content-type': 'application/json' },
* body: JSON.stringify({ username: 'me' }),
* });
*
* console.log(request); // FetchRequest<Schema, 'POST', '/users'>
* console.log(request.path); // '/users'
*
* @see {@link https://github.com/zimicjs/zimic/wiki/api‐zimic‐fetch#fetchrequest-1 `FetchRequest`}
*/
Request: FetchRequestConstructor<Schema>;
/**
* A type guard that checks if a request is a {@link FetchRequest}, was created by the fetch instance, and has a
* specific method and path. This is useful to narrow down the type of a request before using it.
*
* @example
* import { type HttpSchema } from '@zimic/http';
* import { createFetch } from '@zimic/fetch';
*
* interface User {
* id: string;
* username: string;
* }
*
* type Schema = HttpSchema<{
* '/users': {
* POST: {
* request: {
* headers: { 'content-type': 'application/json' };
* body: { username: string };
* };
* response: {
* 201: { body: User };
* };
* };
* };
* }>;
*
* const fetch = createFetch<Schema>({
* baseURL: 'http://localhost:3000',
* });
*
* const request = new fetch.Request('/users', {
* method: 'POST',
* headers: { 'content-type': 'application/json' },
* body: JSON.stringify({ username: 'me' }),
* });
*
* if (fetch.isRequest(request, 'POST', '/users')) {
* // request is a FetchRequest<Schema, 'POST', '/users'>
*
* const contentType = request.headers.get('content-type'); // 'application/json'
* const body = await request.json(); // { username: string }
* }
*
* @param request The request to check.
* @param method The method to check.
* @param path The path to check.
* @returns `true` if the request was created by the fetch instance and has the specified method and path; `false`
* otherwise.
*/
isRequest: <Method extends HttpSchemaMethod<Schema>, Path extends HttpSchemaPath.Literal<Schema, Method>>(request: unknown, method: Method, path: Path) => request is FetchRequest<Schema, Method, Path>;
/**
* A type guard that checks if a response is a {@link FetchResponse}, was received by the fetch instance, and has a
* specific method and path. This is useful to narrow down the type of a response before using it.
*
* @example
* import { type HttpSchema } from '@zimic/http';
* import { createFetch } from '@zimic/fetch';
*
* interface User {
* id: string;
* username: string;
* }
*
* type Schema = HttpSchema<{
* '/users': {
* GET: {
* request: {
* searchParams: { query?: string };
* };
* response: {
* 200: { body: User[] };
* };
* };
* };
* }>;
*
* const fetch = createFetch<Schema>({
* baseURL: 'http://localhost:3000',
* });
*
* const response = await fetch('/users', {
* method: 'GET',
* searchParams: { query: 'u' },
* });
*
* if (fetch.isResponse(response, 'GET', '/users')) {
* // response is a FetchResponse<Schema, 'GET', '/users'>
*
* const users = await response.json(); // User[]
* }
*
* @param response The response to check.
* @param method The method to check.
* @param path The path to check.
* @returns `true` if the response was received by the fetch instance and has the specified method and path; `false`
* otherwise.
*/
isResponse: <Method extends HttpSchemaMethod<Schema>, Path extends HttpSchemaPath.Literal<Schema, Method>>(response: unknown, method: Method, path: Path) => response is FetchResponse<Schema, Method, Path>;
/**
* A type guard that checks if an error is a {@link FetchResponseError} related to a {@link FetchResponse response}
* received by the fetch instance with a specific method and path. This is useful to narrow down the type of an error
* before handling it.
*
* @example
* import { type HttpSchema } from '@zimic/http';
* import { createFetch } from '@zimic/fetch';
*
* interface User {
* id: string;
* username: string;
* }
*
* type Schema = HttpSchema<{
* '/users': {
* GET: {
* request: {
* searchParams: { query?: string };
* };
* response: {
* 200: { body: User[] };
* 400: { body: { message: string } };
* 500: { body: { message: string } };
* };
* };
* };
* }>;
*
* const fetch = createFetch<Schema>({
* baseURL: 'http://localhost:3000',
* });
*
* try {
* const response = await fetch('/users', {
* method: 'GET',
* searchParams: { query: 'u' },
* });
*
* if (!response.ok) {
* throw response.error; // FetchResponseError<Schema, 'GET', '/users'>
* }
* } catch (error) {
* if (fetch.isResponseError(error, 'GET', '/users')) {
* // error is a FetchResponseError<Schema, 'GET', '/users'>
*
* const status = error.response.status; // 400 | 500
* const { message } = await error.response.json(); // { message: string }
*
* console.error('Could not fetch users:', { status, message });
* }
* }
*
* @param error The error to check.
* @param method The method to check.
* @param path The path to check.
* @returns `true` if the error is a response error received by the fetch instance and has the specified method and
* path; `false` otherwise.
*/
isResponseError: <Method extends HttpSchemaMethod<Schema>, Path extends HttpSchemaPath.Literal<Schema, Method>>(error: unknown, method: Method, path: Path) => error is FetchResponseError<Schema, Method, Path>;
}
declare namespace Fetch {
/** A loosely-typed version of {@link Fetch `fetch`}. This can be useful to make requests with fewer type constraints, */
type Loose = (input: string | URL | FetchRequest.Loose, init?: FetchRequestInit.Loose) => Promise<FetchResponse.Loose>;
}
/**
* The options to create a {@link https://github.com/zimicjs/zimic/wiki/api‐zimic‐fetch#fetch fetch instance}.
*
* @see {@link https://github.com/zimicjs/zimic/wiki/api‐zimic‐fetch#createfetch `createFetch(options)` API reference}
*/
interface FetchOptions<Schema extends HttpSchema> extends Omit<FetchRequestInit.Defaults, 'method'> {
/**
* A listener function that is called for each request. It can modify the requests before they are sent.
*
* @example
* import { createFetch } from '@zimic/fetch';
* import { type HttpSchema } from '@zimic/http';
*
* interface User {
* id: string;
* username: string;
* }
*
* type Schema = HttpSchema<{
* '/users': {
* GET: {
* request: {
* searchParams: { page?: number; limit?: number };
* };
* response: {
* 200: { body: User[] };
* };
* };
* };
* }>;
*
* const fetch = createFetch<Schema>({
* baseURL: 'http://localhost:80',
*
* onRequest(request) {
* if (this.isRequest(request, 'GET', '/users')) {
* const url = new URL(request.url);
* url.searchParams.append('limit', '10');
*
* const updatedRequest = new Request(url, request);
* return updatedRequest;
* }
*
* return request;
* },
* });
*
* @param request The original request.
* @returns The request to be sent. It can be the original request or a modified version of it.
* @this {Fetch<Schema>} The fetch instance that is sending the request.
*/
onRequest?: (this: Fetch<Schema>, request: FetchRequest.Loose) => PossiblePromise<Request>;
/**
* A listener function that is called after each response is received. It can modify the responses before they are
* returned to the fetch caller.
*
* @example
* import { type HttpSchema } from '@zimic/http';
* import { createFetch } from '@zimic/fetch';
*
* interface User {
* id: string;
* username: string;
* }
*
* type Schema = HttpSchema<{
* '/users': {
* GET: {
* response: {
* 200: {
* headers: { 'content-encoding'?: string };
* body: User[];
* };
* };
* };
* };
* }>;
*
* const fetch = createFetch<Schema>({
* baseURL: 'http://localhost:80',
*
* onResponse(response) {
* if (this.isResponse(response, 'GET', '/users')) {
* console.log(response.headers.get('content-encoding'));
* }
* return response;
* },
* });
*
* @param response The original response.
* @returns The response to be returned.
* @this {Fetch<Schema>} The fetch instance that received the response.
*/
onResponse?: (this: Fetch<Schema>, response: FetchResponse.Loose) => PossiblePromise<Response>;
}
/**
* The default options for each request sent by the fetch instance.
*
* @see {@link https://github.com/zimicjs/zimic/wiki/api‐zimic‐fetch#fetchdefaults `fetch.defaults` API reference}
*/
type FetchDefaults = RequiredByKey<FetchRequestInit.Defaults, 'headers' | 'searchParams'>;
/**
* Infers the schema of a {@link https://github.com/zimicjs/zimic/wiki/api‐zimic‐fetch#fetch fetch instance}.
*
* @example
* import { type HttpSchema } from '@zimic/http';
* import { createFetch, InferFetchSchema } from '@zimic/fetch';
*
* const fetch = createFetch<{
* '/users': {
* GET: {
* response: { 200: { body: User[] } };
* };
* };
* }>({
* baseURL: 'http://localhost:3000',
* });
*
* type Schema = InferFetchSchema<typeof fetch>;
* // {
* // '/users': {
* // GET: {
* // response: { 200: { body: User[] } };
* // };
* // };
*
* @see {@link https://github.com/zimicjs/zimic/wiki/api‐zimic‐fetch#fetch `fetch` API reference}
*/
type InferFetchSchema<FetchInstance> = FetchInstance extends Fetch<infer Schema> ? Schema : never;
/**
* Creates a {@link https://github.com/zimicjs/zimic/wiki/api‐zimic‐fetch#fetch fetch instance} typed with an HTTP
* schema, closely compatible with the {@link https://developer.mozilla.org/docs/Web/API/Fetch_API native Fetch API}. All
* requests and responses are typed by default with the schema, including methods, paths, status codes, parameters, and
* bodies.
*
* Requests sent by the fetch instance have their URL automatically prefixed with the base URL of the instance.
* {@link https://github.com/zimicjs/zimic/wiki/api‐zimic‐fetch#fetch.defaults Default options} are also applied to the
* requests, if provided.
*
* @example
* import { type HttpSchema } from '@zimic/http';
* import { createFetch } from '@zimic/fetch';
*
* interface User {
* id: string;
* username: string;
* }
*
* type Schema = HttpSchema<{
* '/users': {
* POST: {
* request: {
* headers: { 'content-type': 'application/json' };
* body: { username: string };
* };
* response: {
* 201: { body: User };
* };
* };
*
* GET: {
* request: {
* searchParams: {
* query?: string;
* page?: number;
* limit?: number;
* };
* };
* response: {
* 200: { body: User[] };
* };
* };
* };
* }>;
*
* const fetch = createFetch<Schema>({
* baseURL: 'http://localhost:3000',
* });
*
* @see {@link https://github.com/zimicjs/zimic/wiki/api‐zimic‐fetch#createfetch `createFetch(options)` API reference}
* @see {@link https://github.com/zimicjs/zimic/wiki/api‐zimic‐fetch#fetch `fetch` API reference}
*/
declare function createFetch<Schema extends HttpSchema>(options: FetchOptions<Schema>): Fetch<Schema>;
export { Fetch, type FetchDefaults, type FetchInput, type FetchOptions, FetchRequest, type FetchRequestConstructor, FetchRequestInit, type FetchRequestObject, FetchResponse, FetchResponseError, type FetchResponseErrorObject, FetchResponseErrorObjectOptions, type FetchResponseObject, type FetchResponsePerStatusCode, type InferFetchSchema, type JSONStringified, createFetch };