UNPKG

@zimic/fetch

Version:

Next-gen TypeScript-first Fetch client

350 lines (342 loc) 21.5 kB
import { HttpSchema, HttpSchemaMethod, HttpSchemaPath, HttpMethodSchema, HttpHeadersSchema, HttpHeadersInit, HttpRequestHeadersSchema, HttpSearchParamsSchema, HttpSearchParamsInit, HttpRequestSearchParamsSchema, HttpBody, HttpRequestSchema, HttpSearchParams, HttpFormData, HttpRequest, HttpRequestBodySchema, AllowAnyStringInPathParams, HttpMethod, HttpStatusCode, HttpResponseSchemaStatusCode, HttpResponse, HttpResponseBodySchema, HttpResponseHeadersSchema, HttpHeadersSerialized, LiteralHttpSchemaPathFromNonLiteral } from '@zimic/http'; type JSON = { [key: string]: JSON; } | JSON[] | string | number | boolean | null | undefined; declare namespace JSON { type Loose = Record<string, any> | Loose[] | string | number | boolean | null | undefined; } /** * Represents or validates a type that is compatible with JSON. * * **IMPORTANT**: the input of `JSONValue` and all of its internal types must be declared inline or as a type aliases * (`type`). They cannot be interfaces. * * @example * import { type JSONValue } from '@zimic/http'; * * // Can be used as a standalone type: * const value: JSONValue = { * name: 'example', * tags: ['one', 'two'], * }; * * @example * import { type JSONValue } from '@zimic/http'; * * // Can be used with a type argument to validate a JSON value: * type ValidJSON = JSONValue<{ * id: string; * email: string; * createdAt: string; * }>; * * // This results in a type error: * type InvalidJSON = JSONValue<{ * id: string; * email: string; * createdAt: Date; // `Date` is not a valid JSON value. * save: () => Promise<void>; // Functions are not valid JSON values. * }>; */ type JSONValue<Type extends JSON = JSON> = Type; declare namespace JSONValue { /** A loose version of the JSON value type. JSON objects are not strictly typed. */ type Loose<Type extends JSON.Loose = JSON.Loose> = Type; } /** * Recursively converts a type to its JSON-serialized version. Dates are converted to strings and keys with non-JSON * values are excluded. * * @example * import { type JSONSerialized } from '@zimic/http'; * * type SerializedUser = JSONSerialized<{ * id: string; * email: string; * createdAt: Date; * save: () => Promise<void>; * }>; * // { * // id: string; * // email: string; * // createdAt: string; * // } */ type JSONSerialized<Type> = Type extends JSONValue ? Type : Type extends Date ? string : Type extends (...parameters: never[]) => unknown ? never : Type extends symbol ? never : Type extends Map<infer _Key, infer _Value> ? Record<string, never> : Type extends Set<infer _Value> ? Record<string, never> : Type extends (infer ArrayItem)[] ? JSONSerialized<ArrayItem>[] : Type extends object ? { [Key in keyof Type as [JSONSerialized<Type[Key]>] extends [never] ? never : Key]: JSONSerialized<Type[Key]>; } : never; declare global { interface JSON { readonly value: unique symbol; stringify<Value>(value: Value, replacer?: ((this: any, key: string, value: Value) => any) | (number | string)[] | null, space?: string | number): JSONStringified<Value>; parse<Value>(text: JSONStringified<Value>, reviver?: (this: any, key: string, value: any) => any): JSONSerialized<Value>; } } type JSONStringified<Value> = string & { [JSON.value]: JSONSerialized<Value>; }; type Default<Type, IfEmpty = never> = [undefined | void] extends [Type] ? IfEmpty : Exclude<Type, undefined | void>; type PossiblePromise<Type> = Type | PromiseLike<Type>; type RequiredByKey<Type, Key extends keyof Type> = Omit<Type, Key> & Required<Pick<Type, Key>>; type FetchRequestInitWithHeaders<HeadersSchema extends HttpHeadersSchema | undefined> = [HeadersSchema] extends [never] ? { headers?: undefined; } : undefined extends HeadersSchema ? { headers?: HttpHeadersInit<Default<HeadersSchema>>; } : { headers: HttpHeadersInit<Default<HeadersSchema>>; }; type FetchRequestInitWithSearchParams<SearchParamsSchema extends HttpSearchParamsSchema | undefined> = [ SearchParamsSchema ] extends [never] ? { searchParams?: undefined; } : undefined extends SearchParamsSchema ? { searchParams?: HttpSearchParamsInit<Default<SearchParamsSchema>>; } : { searchParams: HttpSearchParamsInit<Default<SearchParamsSchema>>; }; type FetchRequestBodySchema<RequestSchema extends HttpRequestSchema> = 'body' extends keyof RequestSchema ? [RequestSchema['body']] extends [never] ? null | undefined : [Extract<RequestSchema['body'], BodyInit | HttpSearchParams | HttpFormData>] extends [never] ? undefined extends RequestSchema['body'] ? JSONStringified<Exclude<RequestSchema['body'], null | undefined>> | null | undefined : JSONStringified<Exclude<RequestSchema['body'], null>> | Extract<RequestSchema['body'], null> : undefined extends RequestSchema['body'] ? RequestSchema['body'] : RequestSchema['body'] : null | undefined; type FetchRequestInitWithBody<BodySchema extends HttpBody> = [BodySchema] extends [never] ? { body?: BodySchema; } : undefined extends BodySchema ? { body?: BodySchema; } : { body: BodySchema; }; type FetchRequestInitPerPath<MethodSchema extends HttpMethodSchema> = FetchRequestInitWithHeaders<HttpRequestHeadersSchema<MethodSchema>> & FetchRequestInitWithSearchParams<HttpRequestSearchParamsSchema<MethodSchema>> & FetchRequestInitWithBody<FetchRequestBodySchema<Default<MethodSchema['request']>>>; /** @see {@link https://zimic.dev/docs/fetch/api/fetch `fetch` API reference} */ 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; /** The redirect mode of the request. */ redirect?: Redirect; /** The duplex mode of the request. */ duplex?: 'half'; } & (Path extends Path ? FetchRequestInitPerPath<Default<Schema[Path][Method]>> : never); declare namespace FetchRequestInit { /** The default options for each request sent by a fetch instance. */ interface Defaults extends Omit<RequestInit, 'headers'> { baseURL: string; /** The headers of the request. */ headers?: HttpHeadersSchema.Loose; /** The search parameters of the request. */ searchParams?: HttpSearchParamsSchema.Loose; /** The duplex mode of the request. */ duplex?: 'half'; } /** 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>; /** @see {@link https://zimic.dev/docs/fetch/api/fetch-request `FetchRequest` API reference} */ interface FetchRequest<Schema extends HttpSchema, Method extends HttpSchemaMethod<Schema>, Path extends HttpSchemaPath.Literal<Schema, Method>> extends HttpRequest<HttpRequestBodySchema<Default<Schema[Path][Method]>>, Default<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 automatically parsed based on the `content-type` header of the request. * * @see {@link https://zimic.dev/docs/fetch/api/fetch-response-error#errortoobject `error.toObject()`} */ 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: HttpHeadersSerialized<HttpHeadersSchema>; /** * The body of the request. It is automatically parsed based on the `content-type` header of the request. * * @see {@link https://zimic.dev/docs/fetch/api/fetch-response-error#errortoobject `error.toObject()`} */ body?: HttpBody | null; }; /** @see {@link https://zimic.dev/docs/fetch/api/fetch-response `FetchResponse` API reference} */ 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>, Default<HttpResponseHeadersSchema<Default<Schema[Path][Method]>, StatusCode>>, 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; } /** @see {@link https://zimic.dev/docs/fetch/api/fetch-response `FetchResponse` API reference} */ 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 automatically parsed based on the `content-type` header of the response. * * @see {@link https://zimic.dev/docs/fetch/api/fetch-response-error#errortoobject `error.toObject()`} */ type FetchResponseObject = Pick<FetchResponse.Loose, 'url' | 'type' | 'status' | 'statusText' | 'ok' | 'redirected'> & { /** [MDN Reference](https://developer.mozilla.org/docs/Web/API/Response/headers) */ headers: HttpHeadersSerialized<HttpHeadersSchema>; /** * The body of the response. It is automatically parsed based on the `content-type` header of the response. * * @see {@link https://zimic.dev/docs/fetch/api/fetch-response-error#errortoobject `error.toObject()`} */ body?: HttpBody | null; }; /** @see {@link https://zimic.dev/docs/fetch/api/fetch#fetchrequest `fetch.Request` API reference} */ 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>>; /** @see {@link https://zimic.dev/docs/fetch/api/fetch-response-error#errortoobject `fetchResponseError.toObject()` API reference} */ interface FetchResponseErrorObjectOptions { /** @see {@link https://zimic.dev/docs/fetch/api/fetch-response-error#errortoobject `fetchResponseError.toObject()` API reference} */ includeRequestBody?: boolean; /** @see {@link https://zimic.dev/docs/fetch/api/fetch-response-error#errortoobject `fetchResponseError.toObject()` API reference} */ 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://zimic.dev/docs/fetch/api/fetch-response-error#errortoobject `fetchResponseError.toObject()` API reference} */ interface FetchResponseErrorObject { name: string; message: string; request: FetchRequestObject; response: FetchResponseObject; } /** @see {@link https://zimic.dev/docs/fetch/api/fetch-response-error `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'>); /** @see {@link https://zimic.dev/docs/fetch/api/fetch-response-error#errortoobject `fetchResponseError.toObject()` API reference} */ toObject(options: FetchResponseErrorObjectOptions.WithBody): Promise<FetchResponseErrorObject>; toObject(options?: FetchResponseErrorObjectOptions.WithoutBody): FetchResponseErrorObject; toObject(options?: FetchResponseErrorObjectOptions): PossiblePromise<FetchResponseErrorObject>; private requestToObject; private responseToObject; private convertHeadersToObject; private withIncludedBodyIfAvailable; } type AnyFetchRequestError = FetchResponseError<any, any, any>; /** @see {@link https://zimic.dev/docs/fetch/api/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>>; /** @see {@link https://zimic.dev/docs/fetch/api/fetch `fetch` API reference} */ interface Fetch<Schema extends HttpSchema> extends Pick<FetchOptions<Schema>, 'onRequest' | 'onResponse'>, FetchDefaults { <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?: Omit<FetchRequestInit<Schema, Method, LiteralHttpSchemaPathFromNonLiteral<Schema, Method, Path>, Redirect>, 'baseURL' | 'searchParams'>): Promise<FetchResponse<Schema, Method, LiteralHttpSchemaPathFromNonLiteral<Schema, Method, Path>, false, Redirect>>; /** * @deprecated Consider accessing the default options directly from the fetch instance. * @see {@link https://zimic.dev/docs/fetch/api/fetch#fetch-defaults `fetch` defaults} */ defaults: FetchDefaults; /** @see {@link https://zimic.dev/docs/fetch/api/fetch#fetchloose `fetch.loose`} */ loose: Fetch.Loose; /** @see {@link https://zimic.dev/docs/fetch/api/fetch#fetchrequest `fetch.Request`} */ Request: FetchRequestConstructor<Schema>; /** @see {@link https://zimic.dev/docs/fetch/api/fetch#isrequest `fetch.isRequest`} */ isRequest: <Method extends HttpSchemaMethod<Schema>, Path extends HttpSchemaPath.Literal<Schema, Method>>(request: unknown, method: Method, path: Path) => request is FetchRequest<Schema, Method, Path>; /** @see {@link https://zimic.dev/docs/fetch/api/fetch#isresponseerror `fetch.isResponse`} */ isResponse: <Method extends HttpSchemaMethod<Schema>, Path extends HttpSchemaPath.Literal<Schema, Method>>(response: unknown, method: Method, path: Path) => response is FetchResponse<Schema, Method, Path>; /** @see {@link https://zimic.dev/docs/fetch/api/fetch#isresponseerror `fetch.isResponseError`} */ 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>; } /** @see {@link https://zimic.dev/docs/fetch/api/create-fetch `createFetch` API reference} */ interface FetchOptions<Schema extends HttpSchema> extends Omit<FetchRequestInit.Defaults, 'method'> { /** @see {@link https://zimic.dev/docs/fetch/api/create-fetch#onrequest `createFetch.onRequest`} API reference */ onRequest?: (this: Fetch<Schema>, request: FetchRequest.Loose) => PossiblePromise<Request>; /** @see {@link https://zimic.dev/docs/fetch/api/create-fetch#onresponse `createFetch.onResponse`} API reference */ onResponse?: (this: Fetch<Schema>, response: FetchResponse.Loose) => PossiblePromise<Response>; } /** * The default options to send with each request. * * @see {@link https://zimic.dev/docs/fetch/api/fetch `fetch` API reference} */ type FetchDefaults = RequiredByKey<FetchRequestInit.Defaults, 'headers' | 'searchParams'>; /** * Infers the schema of a {@link https://zimic.dev/docs/fetch/api/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://zimic.dev/docs/fetch/api/fetch `fetch` API reference} */ type InferFetchSchema<FetchInstance> = FetchInstance extends Fetch<infer Schema> ? Schema : never; /** @see {@link https://zimic.dev/docs/fetch/api/create-fetch `createFetch` 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 };