@sizium/api-client
Version:
REST API client for Sizium. Get the actual size of any local or remote package
418 lines (374 loc) • 15 kB
text/typescript
// HTTP types
type HttpMethod = "get" | "put" | "post" | "delete" | "options" | "head" | "patch" | "trace";
/** 2XX statuses */
type OkStatus = 200 | 201 | 202 | 203 | 204 | 206 | 207 | "2XX";
/** 4XX and 5XX statuses */
// biome-ignore format: keep on one line
type ErrorStatus = 500 | 501 | 502 | 503 | 504 | 505 | 506 | 507 | 508 | 510 | 511 | '5XX' | 400 | 401 | 402 | 403 | 404 | 405 | 406 | 407 | 408 | 409 | 410 | 411 | 412 | 413 | 414 | 415 | 416 | 417 | 418 | 420 | 421 | 422 | 423 | 424 | 425 | 426 | 427 | 428 | 429 | 430 | 431 | 444 | 450 | 451 | 497 | 498 | 499 | '4XX' | "default";
// OpenAPI type helpers
/** Given an OpenAPI **Paths Object**, find all paths that have the given method */
type PathsWithMethod<Paths extends {}, PathnameMethod extends HttpMethod> = {
[Pathname in keyof Paths]: Paths[Pathname] extends {
[K in PathnameMethod]: any;
}
? Pathname
: never;
}[keyof Paths];
/** Return `responses` for an Operation Object */
type ResponseObjectMap<T> = T extends { responses: any } ? T["responses"] : unknown;
/** Return `content` for a Response Object */
type ResponseContent<T> = T extends { content: any } ? T["content"] : unknown;
/** Return type of `requestBody` for an Operation Object */
type OperationRequestBody<T> = "requestBody" extends keyof T ? T["requestBody"] : never;
/** Internal helper to get object type with only the `requestBody` property */
type PickRequestBody<T> = "requestBody" extends keyof T ? Pick<T, "requestBody"> : never;
/** Resolve to `true` if request body is optional, else `false` */
type IsOperationRequestBodyOptional<T> = RequiredKeysOf<PickRequestBody<T>> extends never ? true : false;
/** Internal helper used in OperationRequestBodyContent */
type OperationRequestBodyMediaContent<T> = IsOperationRequestBodyOptional<T> extends true
? ResponseContent<NonNullable<OperationRequestBody<T>>> | undefined
: ResponseContent<OperationRequestBody<T>>;
/** Return first `content` from a Request Object Mapping, allowing any media type */
type OperationRequestBodyContent<T> = FilterKeys<OperationRequestBodyMediaContent<T>, MediaType> extends never
? FilterKeys<NonNullable<OperationRequestBodyMediaContent<T>>, MediaType> | undefined
: FilterKeys<OperationRequestBodyMediaContent<T>, MediaType>;
/** Return all 2XX responses from a Response Object Map */
type SuccessResponse<
T extends Record<string | number, any>,
Media extends MediaType = MediaType,
> = GetResponseContent<T, Media, OkStatus>;
type GetResponseContent<
T extends Record<string | number, any>,
Media extends MediaType = MediaType,
ResponseCode extends keyof T = keyof T,
> = ResponseCode extends keyof T
? {
[K in ResponseCode]: T[K]["content"] extends Record<string, any>
? FilterKeys<T[K]["content"], Media> extends never
? T[K]["content"]
: FilterKeys<T[K]["content"], Media>
: K extends keyof T
? T[K]["content"]
: never;
}[ResponseCode]
: never;
/**
* Return all 5XX and 4XX responses (in that order) from a Response Object Map
*/
type ErrorResponse<
T extends Record<string | number, any>,
Media extends MediaType = MediaType,
> = GetResponseContent<T, Media, ErrorStatus>;
// Generic TS utils
/** Find first match of multiple keys */
type FilterKeys<Obj, Matchers> = Obj[keyof Obj & Matchers];
/** Return any `[string]/[string]` media type (important because openapi-fetch allows any content response, not just JSON-like) */
type MediaType = `${string}/${string}`;
/** Helper to get the required keys of an object. If no keys are required, will be `undefined` with strictNullChecks enabled, else `never` */
type RequiredKeysOfHelper<T> = {
// biome-ignore lint/complexity/noBannedTypes: `{}` is necessary here
[K in keyof T]: {} extends Pick<T, K> ? never : K;
}[keyof T];
/** Get the required keys of an object, or `never` if no keys are required */
type RequiredKeysOf<T> = RequiredKeysOfHelper<T> extends undefined ? never : RequiredKeysOfHelper<T>;
/** Options for each client instance */
interface ClientOptions extends Omit<RequestInit, "headers"> {
/** set the common root URL for all API requests */
baseUrl?: string;
/** custom fetch (defaults to globalThis.fetch) */
fetch?: (input: Request) => Promise<Response>;
/** custom Request (defaults to globalThis.Request) */
Request?: typeof Request;
/** global querySerializer */
querySerializer?: QuerySerializer<unknown> | QuerySerializerOptions;
/** global bodySerializer */
bodySerializer?: BodySerializer<unknown>;
headers?: HeadersOptions;
/** RequestInit extension object to pass as 2nd argument to fetch when supported (defaults to undefined) */
requestInitExt?: Record<string, unknown>;
}
type HeadersOptions =
| Required<RequestInit>["headers"]
| Record<string, string | number | boolean | (string | number | boolean)[] | null | undefined>;
type QuerySerializer<T> = (
query: T extends { parameters: any } ? NonNullable<T["parameters"]["query"]> : Record<string, unknown>,
) => string;
/** @see https://swagger.io/docs/specification/serialization/#query */
type QuerySerializerOptions = {
/** Set serialization for arrays. @see https://swagger.io/docs/specification/serialization/#query */
array?: {
/** default: "form" */
style: "form" | "spaceDelimited" | "pipeDelimited";
/** default: true */
explode: boolean;
};
/** Set serialization for objects. @see https://swagger.io/docs/specification/serialization/#query */
object?: {
/** default: "deepObject" */
style: "form" | "deepObject";
/** default: true */
explode: boolean;
};
/**
* The `allowReserved` keyword specifies whether the reserved characters
* `:/?#[]@!$&'()*+,;=` in parameter values are allowed to be sent as they
* are, or should be percent-encoded. By default, allowReserved is `false`,
* and reserved characters are percent-encoded.
* @see https://swagger.io/docs/specification/serialization/#query
*/
allowReserved?: boolean;
};
type BodySerializer<T> = (body: OperationRequestBodyContent<T>) => any;
type BodyType<T = unknown> = {
json: T;
text: Awaited<ReturnType<Response["text"]>>;
blob: Awaited<ReturnType<Response["blob"]>>;
arrayBuffer: Awaited<ReturnType<Response["arrayBuffer"]>>;
stream: Response["body"];
};
type ParseAs = keyof BodyType;
type ParseAsResponse<T, Options> = Options extends {
parseAs: ParseAs;
}
? BodyType<T>[Options["parseAs"]]
: T;
interface DefaultParamsOption {
params?: {
query?: Record<string, unknown>;
};
}
type ParamsOption<T> = T extends {
parameters: any;
}
? RequiredKeysOf<T["parameters"]> extends never
? { params?: T["parameters"] }
: { params: T["parameters"] }
: DefaultParamsOption;
type RequestBodyOption<T> = OperationRequestBodyContent<T> extends never
? { body?: never }
: IsOperationRequestBodyOptional<T> extends true
? { body?: OperationRequestBodyContent<T> }
: { body: OperationRequestBodyContent<T> };
type FetchOptions<T> = RequestOptions<T> & Omit<RequestInit, "body" | "headers">;
type FetchResponse<T extends Record<string | number, any>, Options, Media extends MediaType> =
| {
data: ParseAsResponse<SuccessResponse<ResponseObjectMap<T>, Media>, Options>;
error?: never;
response: Response;
}
| {
data?: never;
error: ErrorResponse<ResponseObjectMap<T>, Media>;
response: Response;
};
type RequestOptions<T> = ParamsOption<T> &
RequestBodyOption<T> & {
baseUrl?: string;
querySerializer?: QuerySerializer<T> | QuerySerializerOptions;
bodySerializer?: BodySerializer<T>;
parseAs?: ParseAs;
fetch?: ClientOptions["fetch"];
headers?: HeadersOptions;
};
type MergedOptions<T = unknown> = {
baseUrl: string;
parseAs: ParseAs;
querySerializer: QuerySerializer<T>;
bodySerializer: BodySerializer<T>;
fetch: typeof globalThis.fetch;
};
interface MiddlewareCallbackParams {
/** Current Request object */
request: Request;
/** The original OpenAPI schema path (including curly braces) */
readonly schemaPath: string;
/** OpenAPI parameters as provided from openapi-fetch */
readonly params: {
query?: Record<string, unknown>;
header?: Record<string, unknown>;
path?: Record<string, unknown>;
cookie?: Record<string, unknown>;
};
/** Unique ID for this request */
readonly id: string;
/** createClient options (read-only) */
readonly options: MergedOptions;
}
type MiddlewareOnRequest = (
options: MiddlewareCallbackParams,
) => void | Request | Response | undefined | Promise<Request | Response | undefined | void>;
type MiddlewareOnResponse = (
options: MiddlewareCallbackParams & { response: Response },
) => void | Response | undefined | Promise<Response | undefined | void>;
type MiddlewareOnError = (
options: MiddlewareCallbackParams & { error: unknown },
) => void | Response | Error | Promise<void | Response | Error>;
type Middleware =
| {
onRequest: MiddlewareOnRequest;
onResponse?: MiddlewareOnResponse;
onError?: MiddlewareOnError;
}
| {
onRequest?: MiddlewareOnRequest;
onResponse: MiddlewareOnResponse;
onError?: MiddlewareOnError;
}
| {
onRequest?: MiddlewareOnRequest;
onResponse?: MiddlewareOnResponse;
onError: MiddlewareOnError;
};
/** This type helper makes the 2nd function param required if params/requestBody are required; otherwise, optional */
type MaybeOptionalInit<Params, Location extends keyof Params> = RequiredKeysOf<
FetchOptions<FilterKeys<Params, Location>>
> extends never
? FetchOptions<FilterKeys<Params, Location>> | undefined
: FetchOptions<FilterKeys<Params, Location>>;
// The final init param to accept.
// - Determines if the param is optional or not.
// - Performs arbitrary [key: string] addition.
// Note: the addition MUST happen after all the inference happens (otherwise TS can’t infer if init is required or not).
type InitParam<Init> = RequiredKeysOf<Init> extends never
? [(Init & { [key: string]: unknown })?]
: [Init & { [key: string]: unknown }];
type ClientMethod<
Paths extends Record<string, Record<HttpMethod, {}>>,
Method extends HttpMethod,
Media extends MediaType,
> = <Path extends PathsWithMethod<Paths, Method>, Init extends MaybeOptionalInit<Paths[Path], Method>>(
url: Path,
...init: InitParam<Init>
) => Promise<FetchResponse<Paths[Path][Method], Init, Media>>;
type ClientRequestMethod<Paths extends Record<string, Record<HttpMethod, {}>>, Media extends MediaType> = <
Method extends HttpMethod,
Path extends PathsWithMethod<Paths, Method>,
Init extends MaybeOptionalInit<Paths[Path], Method>,
>(
method: Method,
url: Path,
...init: InitParam<Init>
) => Promise<FetchResponse<Paths[Path][Method], Init, Media>>;
interface Client<Paths extends {}, Media extends MediaType = MediaType> {
request: ClientRequestMethod<Paths, Media>;
/** Call a GET endpoint */
GET: ClientMethod<Paths, "get", Media>;
/** Call a PUT endpoint */
PUT: ClientMethod<Paths, "put", Media>;
/** Call a POST endpoint */
POST: ClientMethod<Paths, "post", Media>;
/** Call a DELETE endpoint */
DELETE: ClientMethod<Paths, "delete", Media>;
/** Call a OPTIONS endpoint */
OPTIONS: ClientMethod<Paths, "options", Media>;
/** Call a HEAD endpoint */
HEAD: ClientMethod<Paths, "head", Media>;
/** Call a PATCH endpoint */
PATCH: ClientMethod<Paths, "patch", Media>;
/** Call a TRACE endpoint */
TRACE: ClientMethod<Paths, "trace", Media>;
/** Register middleware */
use(...middleware: Middleware[]): void;
/** Unregister middleware */
eject(...middleware: Middleware[]): void;
}
declare function createClient$2<Paths extends {}, Media extends MediaType = MediaType>(
clientOptions?: ClientOptions,
): Client<Paths, Media>;
/**
* Creates an instance of the OpenAPI client.
* @returns {ReturnType<typeof OpenApiCreateClient>} - The configured OpenAPI client.
* @example
* import type { paths } from "./openapi.d.ts" // Types generated by `buildSchema` function from `@backan/builder`
*
* // create client
* const client = createClient<paths>( {
* baseUrl : 'http://localhost:1312/',
* } )
*
* // example of call
* const response = await client.GET( '/random/child', {
* params : { query : { value : 'myValue', }, },
* } )
*
* console.log( response )
*/
declare const createClient$1: typeof createClient$2;
interface paths {
"/size": {
parameters: {
query?: never;
header?: never;
path?: never;
cookie?: never;
};
/** Get Package size data */
get: {
parameters: {
query: {
/** @description Set the input */
input: string;
};
header?: never;
path?: never;
cookie?: never;
};
requestBody?: never;
responses: {
/** @description Successfully fetched data */
200: {
headers: {
[name: string]: unknown;
};
content: {
"application/json": Record<string, never>;
};
};
/** @description Bad request */
400: {
headers: {
[name: string]: unknown;
};
content: {
"application/json": {
/** @enum {number} */
status: 400;
id: string;
message: string;
error: Record<string, never>;
help: string;
};
};
};
/** @description Internal Server error */
500: {
headers: {
[name: string]: unknown;
};
content: {
"application/json": {
/** @enum {number} */
status: 500;
id: string;
message: string;
error: Record<string, never>;
help: string;
};
};
};
};
};
put?: never;
post?: never;
delete?: never;
options?: never;
head?: never;
patch?: never;
trace?: never;
};
}
type ClientOpts = NonNullable<Parameters<typeof createClient$1>[0]>;
type ClientRes = ReturnType<typeof createClient$1<paths>>;
declare const createClient: (opts?: ClientOpts) => ClientRes;
export { createClient };
export type { ClientOpts, ClientRes, paths };