@zimic/fetch
Version:
Next-gen TypeScript-first fetch API client
283 lines (275 loc) • 18.8 kB
TypeScript
import { HttpSchema, HttpSchemaMethod, HttpSchemaPath, HttpMethodSchema, HttpHeadersSchema, HttpHeadersInit, HttpRequestHeadersSchema, HttpSearchParamsSchema, HttpSearchParamsInit, HttpRequestSearchParamsSchema, HttpBody, HttpRequestSchema, HttpSearchParams, HttpFormData, HttpMethod, HttpRequest, HttpRequestBodySchema, AllowAnyStringInPathParams, HttpStatusCode, HttpResponseSchemaStatusCode, HttpResponse, HttpResponseBodySchema, HttpResponseHeadersSchema, HttpHeadersSerialized, 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://zimic.dev/docs/fetch/guides/bodies#json-request-body Using a JSON request 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 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'] | null : 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 HTTP method of the request. */
method?: HttpMethod;
/** 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 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: HttpHeadersSerialized<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;
};
/** @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 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: HttpHeadersSerialized<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;
};
/** @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): Promise<FetchResponseErrorObject> | FetchResponseErrorObject;
private convertRequestToObject;
private convertResponseToObject;
}
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'> {
<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>>;
/** @see {@link https://zimic.dev/docs/fetch/api/fetch#fetchdefaults `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 for each request sent by the fetch instance.
*
* @see {@link https://zimic.dev/docs/fetch/api/fetch#fetchdefaults `fetch.defaults` 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 };