@zimic/fetch
Version:
Next-gen TypeScript-first Fetch client
275 lines (250 loc) • 10.6 kB
text/typescript
import {
HttpRequestSchema,
HttpMethod,
HttpSchema,
HttpSchemaPath,
HttpSchemaMethod,
HttpMethodSchema,
HttpResponseSchemaStatusCode,
HttpStatusCode,
HttpResponse,
HttpRequest,
AllowAnyStringInPathParams,
LiteralHttpSchemaPathFromNonLiteral,
HttpResponseBodySchema,
HttpResponseHeadersSchema,
HttpRequestHeadersSchema,
HttpHeadersSchema,
HttpSearchParamsSchema,
HttpHeadersInit,
HttpHeadersSerialized,
HttpSearchParamsInit,
HttpBody,
HttpRequestBodySchema,
HttpRequestSearchParamsSchema,
HttpSearchParams,
HttpFormData,
} from '@zimic/http';
import { Default } from '@zimic/utils/types';
import { JSONStringified } from '@zimic/utils/types/json';
import FetchResponseError, { AnyFetchRequestError } from '../errors/FetchResponseError';
import { FetchInput } from './public';
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} */
export 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);
export namespace FetchRequestInit {
/** The default options for each request sent by a fetch instance. */
export 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`}. */
export 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} */
export 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;
}
export namespace FetchRequest {
/** A loosely typed version of a {@link FetchRequest `FetchRequest`}. */
export 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()`}
*/
export 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} */
export 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} */
export 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;
export namespace FetchResponse {
/** A loosely typed version of a {@link FetchResponse}. */
export 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()`}
*/
export 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} */
export 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>>;