UNPKG

@zimic/fetch

Version:

Next-gen, TypeScript-first fetch-like API client

1,066 lines (1,058 loc) 45.1 kB
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 };