hono
Version:
Web framework built on Web Standards
151 lines (150 loc) • 8.53 kB
TypeScript
import type { Hono } from '../hono';
import type { HonoBase } from '../hono-base';
import type { Endpoint, ResponseFormat, Schema } from '../types';
import type { StatusCode, SuccessStatusCode } from '../utils/http-status';
import type { HasRequiredKeys } from '../utils/types';
type HonoRequest = (typeof Hono.prototype)['request'];
export type BuildSearchParamsFn = (query: Record<string, string | string[]>) => URLSearchParams;
export type ClientRequestOptions<T = unknown> = {
fetch?: typeof fetch | HonoRequest;
webSocket?: (...args: ConstructorParameters<typeof WebSocket>) => WebSocket;
/**
* Standard `RequestInit`, caution that this take highest priority
* and could be used to overwrite things that Hono sets for you, like `body | method | headers`.
*
* If you want to add some headers, use in `headers` instead of `init`
*/
init?: RequestInit;
/**
* Custom function to serialize query parameters into URLSearchParams.
* By default, arrays are serialized as multiple parameters with the same key (e.g., `key=a&key=b`).
* You can provide a custom function to change this behavior, for example to use bracket notation (e.g., `key[]=a&key[]=b`).
*
* @example
* ```ts
* const client = hc('http://localhost', {
* buildSearchParams: (query) => {
* return new URLSearchParams(qs.stringify(query))
* }
* })
* ```
*/
buildSearchParams?: BuildSearchParamsFn;
} & (keyof T extends never ? {
headers?: Record<string, string> | (() => Record<string, string> | Promise<Record<string, string>>);
} : {
headers: T | (() => T | Promise<T>);
});
export type ClientRequest<Prefix extends string, Path extends string, S extends Schema> = {
[M in keyof S]: S[M] extends Endpoint & {
input: infer R;
} ? R extends object ? HasRequiredKeys<R> extends true ? (args: R, options?: ClientRequestOptions) => Promise<ClientResponseOfEndpoint<S[M]>> : (args?: R, options?: ClientRequestOptions) => Promise<ClientResponseOfEndpoint<S[M]>> : never : never;
} & {
$url: <const Arg extends (S[keyof S] extends {
input: infer R;
} ? R extends {
param: infer P;
} ? R extends {
query: infer Q;
} ? {
param: P;
query: Q;
} : {
param: P;
} : R extends {
query: infer Q;
} ? {
query: Q;
} : {} : {}) | undefined = undefined>(arg?: Arg) => HonoURL<Prefix, Path, Arg>;
} & (S['$get'] extends {
outputFormat: 'ws';
} ? S['$get'] extends {
input: infer I;
} ? {
$ws: (args?: I) => WebSocket;
} : {} : {});
type ClientResponseOfEndpoint<T extends Endpoint = Endpoint> = T extends {
output: infer O;
outputFormat: infer F;
status: infer S;
} ? ClientResponse<O, S extends number ? S : never, F extends ResponseFormat ? F : never> : never;
export interface ClientResponse<T, U extends number = StatusCode, F extends ResponseFormat = ResponseFormat> extends globalThis.Response {
readonly body: ReadableStream | null;
readonly bodyUsed: boolean;
ok: U extends SuccessStatusCode ? true : U extends Exclude<StatusCode, SuccessStatusCode> ? false : boolean;
status: U;
statusText: string;
headers: Headers;
url: string;
redirect(url: string, status: number): Response;
clone(): Response;
json(): F extends 'text' ? Promise<never> : F extends 'json' ? Promise<T> : Promise<unknown>;
text(): F extends 'text' ? (T extends string ? Promise<T> : Promise<never>) : Promise<string>;
blob(): Promise<Blob>;
formData(): Promise<FormData>;
arrayBuffer(): Promise<ArrayBuffer>;
}
type BuildSearch<Arg, Key extends 'query'> = Arg extends {
[K in Key]: infer Query;
} ? IsEmptyObject<Query> extends true ? '' : `?${string}` : '';
type BuildPathname<P extends string, Arg> = Arg extends {
param: infer Param;
} ? `${ApplyParam<TrimStartSlash<P>, Param>}` : `/${TrimStartSlash<P>}`;
type BuildTypedURL<Protocol extends string, Host extends string, Port extends string, P extends string, Arg> = TypedURL<`${Protocol}:`, Host, Port, BuildPathname<P, Arg>, BuildSearch<Arg, 'query'>>;
type HonoURL<Prefix extends string, Path extends string, Arg> = IsLiteral<Prefix> extends true ? TrimEndSlash<Prefix> extends `${infer Protocol}://${infer Rest}` ? Rest extends `${infer Hostname}/${infer P}` ? ParseHostName<Hostname> extends [infer Host extends string, infer Port extends string] ? BuildTypedURL<Protocol, Host, Port, P, Arg> : never : ParseHostName<Rest> extends [infer Host extends string, infer Port extends string] ? BuildTypedURL<Protocol, Host, Port, Path, Arg> : never : URL : URL;
type ParseHostName<T extends string> = T extends `${infer Host}:${infer Port}` ? [Host, Port] : [T, ''];
type TrimStartSlash<T extends string> = T extends `/${infer R}` ? TrimStartSlash<R> : T;
type TrimEndSlash<T extends string> = T extends `${infer R}/` ? TrimEndSlash<R> : T;
type IsLiteral<T extends string> = [T] extends [never] ? false : string extends T ? false : true;
type ApplyParam<Path extends string, P, Result extends string = ''> = Path extends `${infer Head}/${infer Rest}` ? Head extends `:${infer Param}` ? P extends Record<Param, infer Value extends string> ? IsLiteral<Value> extends true ? ApplyParam<Rest, P, `${Result}/${Value & string}`> : ApplyParam<Rest, P, `${Result}/${Head}`> : ApplyParam<Rest, P, `${Result}/${Head}`> : ApplyParam<Rest, P, `${Result}/${Head}`> : Path extends `:${infer Param}` ? P extends Record<Param, infer Value extends string> ? IsLiteral<Value> extends true ? `${Result}/${Value & string}` : `${Result}/${Path}` : `${Result}/${Path}` : `${Result}/${Path}`;
type IsEmptyObject<T> = keyof T extends never ? true : false;
export interface TypedURL<Protocol extends string, Hostname extends string, Port extends string, Pathname extends string, Search extends string> extends URL {
protocol: Protocol;
hostname: Hostname;
port: Port;
host: Port extends '' ? Hostname : `${Hostname}:${Port}`;
origin: `${Protocol}//${Hostname}${Port extends '' ? '' : `:${Port}`}`;
pathname: Pathname;
search: Search;
href: `${Protocol}//${Hostname}${Port extends '' ? '' : `:${Port}`}${Pathname}${Search}`;
}
export interface Response extends ClientResponse<unknown> {
}
export type Fetch<T> = (args?: InferRequestType<T>, opt?: ClientRequestOptions) => Promise<ClientResponseOfEndpoint<InferEndpointType<T>>>;
type InferEndpointType<T> = T extends (args: infer R, options: any | undefined) => Promise<infer U> ? U extends ClientResponse<infer O, infer S, infer F> ? {
input: NonNullable<R>;
output: O;
outputFormat: F;
status: S;
} extends Endpoint ? {
input: NonNullable<R>;
output: O;
outputFormat: F;
status: S;
} : never : never : never;
export type InferResponseType<T, U extends StatusCode = StatusCode> = InferResponseTypeFromEndpoint<InferEndpointType<T>, U>;
type InferResponseTypeFromEndpoint<T extends Endpoint, U extends StatusCode> = T extends {
output: infer O;
status: infer S;
} ? S extends U ? O : never : never;
export type InferRequestType<T> = T extends (args: infer R, options: any | undefined) => Promise<ClientResponse<unknown>> ? NonNullable<R> : never;
export type InferRequestOptionsType<T> = T extends (args: any, options: infer R) => Promise<ClientResponse<unknown>> ? NonNullable<R> : never;
/**
* Filter a ClientResponse type so it only includes responses of specific status codes.
*/
export type FilterClientResponseByStatusCode<T extends ClientResponse<any, any, any>, U extends number = StatusCode> = T extends ClientResponse<infer RT, infer RC, infer RF> ? RC extends U ? ClientResponse<RT, RC, RF> : never : never;
type PathToChain<Prefix extends string, Path extends string, E extends Schema, Original extends string = Path> = Path extends `/${infer P}` ? PathToChain<Prefix, P, E, Path> : Path extends `${infer P}/${infer R}` ? {
[K in P]: PathToChain<Prefix, R, E, Original>;
} : {
[K in Path extends '' ? 'index' : Path]: ClientRequest<Prefix, Original, E extends Record<string, unknown> ? E[Original] : never>;
};
export type Client<T, Prefix extends string> = T extends HonoBase<any, infer S, any> ? S extends Record<infer K, Schema> ? K extends string ? PathToChain<Prefix, K, S> : never : never : never;
export type Callback = (opts: CallbackOptions) => unknown;
interface CallbackOptions {
path: string[];
args: any[];
}
export type ObjectType<T = unknown> = {
[key: string]: T;
};
export {};