import type { Hono } from '../hono'; import type { HonoBase } from '../hono-base'; import type { METHODS, METHOD_NAME_ALL_LOWERCASE } from '../router'; import type { Endpoint, ExtractSchema, KnownResponseFormat, ResponseFormat, Schema } from '../types'; import type { StatusCode, SuccessStatusCode } from '../utils/http-status'; import type { HasRequiredKeys } from '../utils/types'; /** * Type representing the '$all' method name */ type MethodNameAll = `$${typeof METHOD_NAME_ALL_LOWERCASE}`; /** * Type representing all standard HTTP methods prefixed with '$' * e.g., '$get' | '$post' | '$put' | '$delete' | '$options' | '$patch' */ type StandardMethods = `$${(typeof METHODS)[number]}`; /** * Expands '$all' into all standard HTTP methods. * If the schema contains '$all', it creates a type where all standard HTTP methods * point to the same endpoint definition as '$all', while removing '$all' itself. */ type ExpandAllMethod = MethodNameAll extends keyof S ? { [M in StandardMethods]: S[MethodNameAll]; } & Omit : S; type HonoRequest = (typeof Hono.prototype)['request']; export type BuildSearchParamsFn = (query: Record) => URLSearchParams; export type ClientRequestOptions = { fetch?: typeof fetch | HonoRequest; webSocket?: (...args: ConstructorParameters) => 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 | (() => Record | Promise>); } : { headers: T | (() => T | Promise); }); export type ClientRequest = { [M in keyof ExpandAllMethod]: ExpandAllMethod[M] extends Endpoint & { input: infer R; } ? R extends object ? HasRequiredKeys extends true ? (args: R, options?: ClientRequestOptions) => Promise[M]>> : (args?: R, options?: ClientRequestOptions) => Promise[M]>> : never : never; } & { $url: (arg?: Arg) => HonoURL; $path: (arg?: Arg) => BuildPath; } & (S['$get'] extends { outputFormat: 'ws'; } ? S['$get'] extends { input: infer I; } ? { $ws: (args?: I) => WebSocket; } : {} : {}); type ClientResponseOfEndpoint = T extends { output: infer O; outputFormat: infer F; status: infer S; } ? ClientResponse : never; export interface ClientResponse { readonly body: ReadableStream | null; readonly bodyUsed: boolean; ok: U extends SuccessStatusCode ? true : U extends Exclude ? false : boolean; redirected: boolean; status: U; statusText: string; type: 'basic' | 'cors' | 'default' | 'error' | 'opaque' | 'opaqueredirect'; headers: Headers; url: string; redirect(url: string, status: number): Response; clone(): Response; bytes(): Promise>; json(): F extends 'text' ? Promise : F extends 'json' ? Promise : Promise; text(): F extends 'text' ? (T extends string ? Promise : Promise) : Promise; blob(): Promise; formData(): Promise; arrayBuffer(): Promise; } type BuildSearch = Arg extends { [K in Key]: infer Query; } ? IsEmptyObject extends true ? '' : `?${string}` : ''; type BuildPathname

= Arg extends { param: infer Param; } ? `${ApplyParam, Param>}` : `/${TrimStartSlash

}`; type BuildPath

= `${BuildPathname}${BuildSearch}`; type BuildTypedURL = TypedURL<`${Protocol}:`, Host, Port, BuildPathname, BuildSearch>; type HonoURL = IsLiteral extends true ? TrimEndSlash extends `${infer Protocol}://${infer Rest}` ? Rest extends `${infer Hostname}/${infer P}` ? ParseHostName extends [infer Host extends string, infer Port extends string] ? BuildTypedURL : never : ParseHostName extends [infer Host extends string, infer Port extends string] ? BuildTypedURL : never : URL : URL; type ParseHostName = T extends `${infer Host}:${infer Port}` ? [Host, Port] : [T, '']; type TrimStartSlash = T extends `/${infer R}` ? TrimStartSlash : T; type TrimEndSlash = T extends `${infer R}/` ? TrimEndSlash : T; type IsLiteral = [T] extends [never] ? false : string extends T ? false : true; type ApplyParam = Path extends `${infer Head}/${infer Rest}` ? Head extends `:${infer Param}` ? P extends Record ? IsLiteral extends true ? ApplyParam : ApplyParam : ApplyParam : ApplyParam : Path extends `:${infer Param}` ? P extends Record ? IsLiteral extends true ? `${Result}/${Value & string}` : `${Result}/${Path}` : `${Result}/${Path}` : `${Result}/${Path}`; type IsEmptyObject = keyof T extends never ? true : false; export interface TypedURL 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 { } export type Fetch = (args?: InferRequestType, opt?: ClientRequestOptions) => Promise>>; type InferEndpointType = T extends (args: infer R, options: any | undefined) => Promise ? U extends ClientResponse ? { input: NonNullable; output: O; outputFormat: F; status: S; } extends Endpoint ? { input: NonNullable; output: O; outputFormat: F; status: S; } : never : never : never; export type InferResponseType = InferResponseTypeFromEndpoint, U>; type InferResponseTypeFromEndpoint = T extends { output: infer O; status: infer S; } ? S extends U ? O : never : never; export type InferRequestType = T extends (args: infer R, options: any | undefined) => Promise> ? NonNullable : never; export type InferRequestOptionsType = T extends (args: any, options: infer R) => Promise> ? NonNullable : never; /** * Filter a ClientResponse type so it only includes responses of specific status codes. */ export type FilterClientResponseByStatusCode, U extends number = StatusCode> = T extends ClientResponse ? RC extends U ? ClientResponse : never : never; type PathToChain = Path extends `/${infer P}` ? PathToChain : Path extends `${infer P}/${infer R}` ? { [K in P]: PathToChain; } : { [K in Path extends '' ? 'index' : Path]: ClientRequest ? E[Original] : never>; }; export type Client = T extends HonoBase ? S extends Record ? K extends string ? PathToChain : never : never : never; export type Callback = (opts: CallbackOptions) => unknown; interface CallbackOptions { path: string[]; args: any[]; } export type ObjectType = { [key: string]: T; }; type GlobalResponseDefinition = { [S in StatusCode]?: { [F in KnownResponseFormat]?: unknown; }; }; type ToEndpoints = { [S in keyof Def & StatusCode]: { [F in keyof Def[S] & KnownResponseFormat]: Omit & { output: Def[S][F]; status: S; outputFormat: F; }; }[keyof Def[S] & KnownResponseFormat]; }[keyof Def & StatusCode]; type ModRoute = R extends Endpoint ? R | ToEndpoints : R; type ModSchema = { [K in keyof D]: { [M in keyof D[K]]: ModRoute; }; }; export type ApplyGlobalResponse = App extends HonoBase ? ModSchema, Def> extends infer S extends Schema ? Hono : never : never; export {};