openapi-hooks
Version:
Magical fetch inference for OpenAPI with React Query support
248 lines (245 loc) • 8.12 kB
TypeScript
type HTTPStatusCode =
/** CONTINUE */
100
/** SWITCHING_PROTOCOLS */
| 101
/** PROCESSING */
| 102
/** EARLY_HINTS */
| 103
/** OK */
| 200
/** CREATED */
| 201
/** ACCEPTED */
| 202
/** NON_AUTHORITATIVE_INFORMATION */
| 203
/** NO_CONTENT */
| 204
/** RESET_CONTENT */
| 205
/** PARTIAL_CONTENT */
| 206
/** MULTI_STATUS */
| 207
/** MULTIPLE_CHOICES */
| 300
/** MOVED_PERMANENTLY */
| 301
/** MOVED_TEMPORARILY */
| 302
/** SEE_OTHER */
| 303
/** NOT_MODIFIED */
| 304
/** USE_PROXY */
| 305
/** TEMPORARY_REDIRECT */
| 307
/** PERMANENT_REDIRECT */
| 308
/** BAD_REQUEST */
| 400
/** UNAUTHORIZED */
| 401
/** PAYMENT_REQUIRED */
| 402
/** FORBIDDEN */
| 403
/** NOT_FOUND */
| 404
/** METHOD_NOT_ALLOWED */
| 405
/** NOT_ACCEPTABLE */
| 406
/** PROXY_AUTHENTICATION_REQUIRED */
| 407
/** REQUEST_TIMEOUT */
| 408
/** CONFLICT */
| 409
/** GONE */
| 410
/** LENGTH_REQUIRED */
| 411
/** PRECONDITION_FAILED */
| 412
/** REQUEST_TOO_LONG */
| 413
/** REQUEST_URI_TOO_LONG */
| 414
/** UNSUPPORTED_MEDIA_TYPE */
| 415
/** REQUESTED_RANGE_NOT_SATISFIABLE */
| 416
/** EXPECTATION_FAILED */
| 417
/** IM_A_TEAPOT */
| 418
/** INSUFFICIENT_SPACE_ON_RESOURCE */
| 419
/** METHOD_FAILURE */
| 420
/** MISDIRECTED_REQUEST */
| 421
/** UNPROCESSABLE_ENTITY */
| 422
/** LOCKED */
| 423
/** FAILED_DEPENDENCY */
| 424
/** UPGRADE_REQUIRED */
| 426
/** PRECONDITION_REQUIRED */
| 428
/** TOO_MANY_REQUESTS */
| 429
/** REQUEST_HEADER_FIELDS_TOO_LARGE */
| 431
/** UNAVAILABLE_FOR_LEGAL_REASONS */
| 451
/** INTERNAL_SERVER_ERROR */
| 500
/** NOT_IMPLEMENTED */
| 501
/** BAD_GATEWAY */
| 502
/** SERVICE_UNAVAILABLE */
| 503
/** GATEWAY_TIMEOUT */
| 504
/** HTTP_VERSION_NOT_SUPPORTED */
| 505
/** INSUFFICIENT_STORAGE */
| 507
/** NETWORK_AUTHENTICATION_REQUIRED */
| 511;
type HTTPMethod = "get" | "put" | "post" | "delete" | "options" | "head" | "patch" | "trace";
type RouteFor<TPaths, TPath extends keyof TPaths, TMethod extends HTTPMethod> = TMethod extends keyof TPaths[TPath] ? Extract<NonNullable<TPaths[TPath][TMethod]>, AnyRoute> : never;
type EmptyParameters = {
query?: never;
header?: never;
path?: never;
cookie?: never;
};
type RouteParameters<TParameters> = [Extract<TParameters, AnyParameters>] extends [
never
] ? EmptyParameters : Extract<TParameters, AnyParameters>;
type PathMethods<TPaths, TPath extends keyof TPaths> = {
[TMethod in HTTPMethod]: [RouteFor<TPaths, TPath, TMethod>] extends [never] ? never : TMethod;
}[HTTPMethod];
type AnyRequestBody = {
content: Record<string, any>;
};
type AnyResponses = Record<number, {
content?: Record<string, any>;
headers?: Record<string, any>;
}>;
type AnyParameters = {
query?: Record<string, any>;
header?: Record<string, any>;
path?: Record<string, any>;
cookie?: Record<string, any>;
};
type AnyRoute = {
responses: AnyResponses;
requestBody?: AnyRequestBody;
parameters?: AnyParameters;
};
type Prettify<T> = {
[K in keyof T]: T[K];
} & {};
/**
* ApiResponse is a utility type that infers the possible response shapes for an OpenAPI route.
*
* Given a set of possible HTTP responses (TResponses), it produces a union type representing
* all possible response objects, including status, content type, data, and headers.
*
* For each status code (TStatus) in TResponses:
* - If TResponses[TStatus]['content'] is undefined (i.e., no content for this status):
* - The response object has:
* - status: the status code
* - contentType: never
* - data: never
* - headers: either a Map of header keys/values (if headers are a record), or the headers as-is
* - If TResponses[TStatus]['content'] is defined:
* - For each content type K in the content object:
* - The response object has:
* - status: the status code
* - contentType: K (the MIME type)
* - data: the data for that content type
* - headers: either a Map of header keys/values (if headers are a record), or the headers as-is
*
* The final type is a union of all possible response objects for all status codes and content types.
*/
type ApiResponse<TResponses extends AnyResponses> = {
[TStatus in keyof TResponses]: TStatus extends number ? TResponses[TStatus]["content"] extends undefined ? {
status: TStatus;
contentType: never;
data: never;
headers: TResponses[TStatus]["headers"] extends Record<string, unknown> ? Map<keyof TResponses[TStatus]["headers"], TResponses[TStatus]["headers"][keyof TResponses[TStatus]["headers"]]> : TResponses[TStatus]["headers"];
} : {
[K in keyof TResponses[TStatus]["content"]]: {
status: TStatus;
contentType: K;
data: TResponses[TStatus]["content"][K];
headers: TResponses[TStatus]["headers"] extends Record<string, unknown> ? Map<keyof TResponses[TStatus]["headers"], TResponses[TStatus]["headers"][keyof TResponses[TStatus]["headers"]]> : TResponses[TStatus]["headers"];
};
}[keyof TResponses[TStatus]["content"]] : never;
}[keyof TResponses] | UnknownApiResponse<keyof TResponses>;
type ExcludedStatusCodes<TStatus> = Exclude<HTTPStatusCode, TStatus> & {};
type UnknownApiResponse<TStatus> = {
status: ExcludedStatusCodes<TStatus>;
contentType?: string;
data?: unknown;
headers?: Headers;
};
type AnyApiResponse = {
status: number;
contentType?: string;
data?: unknown;
headers?: Headers;
};
type NoRequestBody = {
contentType?: undefined;
data?: undefined;
};
type RequestBodyContentType<TBody extends AnyRequestBody | undefined> = Extract<keyof Extract<TBody, AnyRequestBody>["content"], string>;
type ApiRequestBody<TBody extends AnyRequestBody | undefined> = [
Extract<TBody, AnyRequestBody>
] extends [never] ? NoRequestBody : {
[K in RequestBodyContentType<TBody>]: {
contentType: K;
data: Extract<TBody, AnyRequestBody>["content"][K];
};
}[RequestBodyContentType<TBody>];
type HeaderObject = Record<string, string>;
type HeaderPredicate = () => PromiseLike<HeaderObject>;
type OpenApiHookOptions = {
baseUrl: URL | string;
headers?: HeaderObject | HeaderPredicate;
onError?: (error: ApiError) => void;
fetch?: typeof fetch;
decodeResponse?: (response: Response, responseContentType: string | null) => Promise<AnyApiResponse>;
encodeBody?: (data: any, contentType: string | undefined) => BodyInit | undefined;
};
/**
* FetchOptions is a type that represents the options for the fetch function.
* It can be a RequestInit object or a function that takes a base RequestInit object and returns a RequestInit object.
* The function is useful for adding additional headers or other properties to the request or modifying properties.
*/
type FetchOptions = RequestInit | ((base: Pick<RequestInit, "body" | "headers" | "method" | "mode">) => RequestInit);
declare class ApiError extends Error {
status: number;
response?: Response | undefined;
data?: unknown | undefined;
constructor(message: string, status: number, response?: Response | undefined, data?: unknown | undefined);
static fromResponse(response: Response, data?: unknown): ApiError;
static fromNetworkError(error: Error): ApiError;
}
type OptionsFor<TRoute extends AnyRoute> = RouteParameters<TRoute["parameters"]> & ApiRequestBody<TRoute["requestBody"]> & {
fetchOptions?: FetchOptions;
};
declare const createFetch: <paths extends object>(options?: OpenApiHookOptions) => <TPath extends keyof paths & string, TMethod extends PathMethods<paths, TPath>, TRoute extends AnyRoute = RouteFor<paths, TPath, TMethod>, TOptions extends OptionsFor<TRoute> = OptionsFor<TRoute>>(path: TPath, method: TMethod, options: TOptions) => Promise<Prettify<ApiResponse<TRoute["responses"]>>>;
export { type AnyApiResponse, ApiError, type ApiRequestBody, type ApiResponse, type FetchOptions, type HeaderObject, type HeaderPredicate, type OpenApiHookOptions, type OptionsFor, type PathMethods, createFetch };