import type { B, Call, Fn, Objects, Pipe, Strings, Tuples } from 'hotscript'; import { type O } from 'ts-toolbelt'; import { HTTPMethod, NotOkStatusCode, OkStatusCode, StatusCode, TypedResponse } from '../typed-fetch.js'; import type { ExtractPathParamsWithBrackets, ExtractPathParamsWithPattern, FromSchema, JSONSchema, OpenAPIDocument, Simplify } from '../types.js'; import type { OASOAuthPath, OAuth2AuthParams } from './auth/oauth.js'; import { ClientTypedResponsePromise } from './clientResponse.js'; type JSONSchema7TypeName = 'string' | 'number' | 'integer' | 'boolean' | 'object' | 'array' | 'null'; type Mutable = FixJSONSchema<{ -readonly [Key in keyof Type]: Mutable; }>; type RefToPath = T extends `#/${infer Ref}` ? Call, Ref> : never; type ResolveRef = { $id: TRef; } & O.Path>; type ResolveRefInObj = T extends { $ref: infer Ref; } ? Ref extends string ? ResolveRef : T : T; type ResolveRefsInObj = { [K in keyof T]: ResolveRefsInObj, TBase>; }; /** * Resolve all $refs in the OpenAPI document and normalizes the types for the client generic */ export type NormalizeOAS = Mutable>; export type OASPathMap = TOAS['paths']; export type OASMethodMap> = OASPathMap[TPath]; export type OASStatusMap, TMethod extends keyof OASMethodMap> = OASMethodMap[TMethod] extends { responses: any; } ? OASMethodMap[TMethod]['responses'] : never; export type OASResponseSchemas, TMethod extends keyof OASMethodMap, TStatus extends keyof OASStatusMap> = OASStatusMap[TStatus]['content']; export type OASJSONResponseSchema, TMethod extends keyof OASMethodMap, TStatus extends keyof OASStatusMap> = OASStatusMap[TStatus] extends { content: any; } ? OASResponseSchemas[keyof OASResponseSchemas]['schema'] : OASStatusMap[TStatus]['schema']; type ToNumber = T extends `${infer N extends number}` ? N : never; export type OASResponse = { [TStatus in keyof OASStatusMap]: TypedResponse>, Record, TStatus extends StatusCode ? TStatus : TStatus extends 'default' ? OASStatusMap extends Record<'200' | 200, any> ? Exclude, symbol>>> : Exclude, symbol>>> : TStatus extends `${StatusCode}` ? ToNumber : 200>; }[keyof OASStatusMap]; interface OASParamPropMap { query: 'query'; path: 'params'; header: 'headers'; } export type OASParamObj = TParameter extends { required: true; } ? { [TName in TParameter['name']]: TParameter extends { schema: JSONSchema; } ? FromSchema : TParameter extends { type: JSONSchema7TypeName; enum?: any[]; } ? FromSchema<{ type: TParameter['type']; enum: TParameter['enum']; }> : unknown; } : { [TName in TParameter['name']]?: TParameter extends { schema: JSONSchema; } ? FromSchema : TParameter extends { type: JSONSchema7TypeName; enum?: any[]; } ? FromSchema<{ type: TParameter['type']; enum: TParameter['enum']; }> : unknown; }; interface OASParamToRequestParam extends Fn { return: this['arg0'] extends { name: string; in: infer TParamType; } ? Extract extends never ? { [TKey in TParamType extends keyof OASParamPropMap ? OASParamPropMap[TParamType] : never]?: OASParamObj; } : { [TKey in TParamType extends keyof OASParamPropMap ? OASParamPropMap[TParamType] : never]: OASParamObj; } : {}; } export type OASParamMap = Pipe>, Tuples.ToIntersection ]>; export type OASClient = { [TPath in keyof OASPathMap]: { [TMethod in keyof OASMethodMap]: OASRequestParams extends { json: {}; } | { params: {}; } | { headers: {}; } | { query: {}; } ? (requestParams: Simplify> & ClientRequestInit) => ClientTypedResponsePromise> : (requestParams?: Simplify> & ClientRequestInit) => ClientTypedResponsePromise>; }; } & OASOAuthPath; export type OASModel; }; } ? keyof TOAS['components']['schemas'] : TOAS extends { definitions: Record; } ? keyof TOAS['definitions'] : never)> = TOAS extends { components: { schemas: { [TModelName in TName]: JSONSchema; }; }; } ? FromSchema : TOAS extends { definitions: { [TModelName in TName]: JSONSchema; }; } ? FromSchema : never; export type FixJSONSchema = RemoveExclusiveMinimumAndMaximum>>>>; type FixAdditionalPropertiesForAllOf = T extends { allOf: any[]; } ? Omit & { allOf: Call>, T['allOf']>; } : T; type LooksLikeSchemaObject = T extends { type: JSONSchema7TypeName; } | { $ref: string; } | { enum: any; } | { const: any; } | { oneOf: any; } | { anyOf: any; } | { allOf: any; } | { not: any; } | { additionalProperties: any; } | { items: any; } ? true : false; type FixMissingTypeObject = T extends { type: any; properties: any; } ? T : T extends { properties: infer TProps; } ? LooksLikeSchemaObject extends true ? T : T & { type: 'object'; } : T; type FixMissingAdditionalProperties = T extends { type: 'object'; properties: any; } ? Omit & { additionalProperties: false; } : T extends { type: readonly (infer TType)[]; properties: any; } ? 'object' extends TType ? Omit & { additionalProperties: false; } : T : T; type FixExtraRequiredFields = T extends { properties: Record; required: string[]; } ? Omit & { required: Call>, T['required']>; } : T; type RemoveExclusiveMinimumAndMaximum = T extends { exclusiveMinimum?: boolean; exclusiveMaximum?: boolean; } ? Omit : T; export type OASRequestParams, TMethod extends keyof OASMethodMap, TAuthParamsRequired extends boolean = true> = (OASMethodMap[TMethod] extends { requestBody: { content: { 'application/json': { schema: JSONSchema; }; }; }; } ? OASMethodMap[TMethod]['requestBody'] extends { required: true; } ? { /** * The request body in JSON is required for this request. * * The value of `json` will be stringified and sent as the request body with `Content-Type: application/json`. */ json: FromSchema[TMethod]['requestBody']['content']['application/json']['schema']>; } : { /** * The request body in JSON is optional for this request. * * The value of `json` will be stringified and sent as the request body with `Content-Type: application/json`. */ json?: FromSchema[TMethod]['requestBody']['content']['application/json']['schema']>; } : OASMethodMap[TMethod] extends { requestBody: { content: { 'multipart/form-data': { schema: JSONSchema; }; }; }; } ? OASMethodMap[TMethod]['requestBody'] extends { required: true; } ? { /** * The request body in multipart/form-data is required for this request. * * The value of `formData` will be sent as the request body with `Content-Type: multipart/form-data`. */ formData: FromSchema[TMethod]['requestBody']['content']['multipart/form-data']['schema']>; } : { /** * The request body in multipart/form-data is optional for this request. * * The value of `formData` will be sent as the request body with `Content-Type: multipart/form-data`. */ formData?: FromSchema[TMethod]['requestBody']['content']['multipart/form-data']['schema']>; } : OASMethodMap[TMethod] extends { requestBody: { content: { 'application/x-www-form-urlencoded': { schema: JSONSchema; }; }; }; } ? OASMethodMap[TMethod]['requestBody'] extends { required: true; } ? { /** * The request body in application/x-www-form-urlencoded is required for this request. * * The value of `formUrlEncoded` will be sent as the request body with `Content-Type: application/x-www-form-urlencoded`. */ formUrlEncoded: FromSchema[TMethod]['requestBody']['content']['application/x-www-form-urlencoded']['schema']>; } : { /** * The request body in application/x-www-form-urlencoded is optional for this request. * * The value of `formUrlEncoded` will be sent as the request body with `Content-Type: application/x-www-form-urlencoded`. */ formUrlEncoded?: FromSchema[TMethod]['requestBody']['content']['application/x-www-form-urlencoded']['schema']>; } : {}) & (OASMethodMap[TMethod] extends { parameters: { name: string; in: string; }[]; } ? OASParamMap[TMethod]['parameters']> : {}) & (TPath extends `${string}{${string}}${string}` ? { /** * Parameters defined in the path are required for this request. * * The value of `params` will be used to replace the path parameters. * * For example if path is `/todos/{id}` and `params` is `{ id: '1' }`, the path will be `/todos/1` */ params: Record, string | number | bigint | boolean>; } : {}) & (TPath extends `${string}:${string}${string}` ? { /** * Parameters defined in the path are required for this request. * * The value of `params` will be used to replace the path parameters. * * For example if path is `/todos/:id` and `params` is `{ id: '1' }`, the path will be `/todos/1`. */ params: Record, string | number | bigint | boolean>; } : {}) & (TAuthParamsRequired extends true ? OASSecurityParamsBySecurityRef[TMethod]> : DeepPartial[TMethod]>>) & (TAuthParamsRequired extends true ? OASSecurityParamsBySecurityRef : DeepPartial>); type DeepPartial = T extends Record ? { [K in keyof T]?: DeepPartial; } : T; export type OASInput, TMethod extends keyof OASMethodMap, TRequestType extends keyof OASRequestParams> = OASRequestParams[TRequestType]; export type OASOutput, TMethod extends keyof OASMethodMap, TStatusCode extends keyof OASStatusMap = 200> = FromSchema>; export type OASComponentSchema = TOAS extends { components: { schemas: { [TModelName in TName]: JSONSchema; }; }; } ? FromSchema : never; export interface ClientOptions { /** * The base URL of the API */ endpoint?: string; /** * WHATWG compatible fetch implementation * * @see https://fets.dev/client/client-configuration#customizing-the-fetch-function */ fetchFn?: ClientFetchFn; /** * Plugins to extend the client functionality * * @see https://fets.dev/client/plugins */ plugins?: ClientPlugin[]; /** * Global parameters */ globalParams?: ClientRequestParams; } type ServerVariableType = TVariables extends Record ? TVarName extends keyof TVariables ? TVariables[TVarName] extends { enum: readonly (infer TEnum extends string)[]; } ? TEnum : string : string : string; type ResolveServerUrl = TUrl extends `${infer Before}{${infer VarName}}${infer After}` ? `${Before}${ServerVariableType}${ResolveServerUrl}` : TUrl; export type ClientOptionsWithStrictEndpoint = Omit & (TOAS extends { servers: (infer TEndpoint extends string)[]; } ? { /** * The base URL of the API defined in the OAS document. * * @see https://swagger.io/docs/specification/api-host-and-base-path/ */ endpoint: TEndpoint; } : TOAS extends { servers: { url: infer TEndpoint extends string; variables?: infer TVariables; }[]; } ? { /** * The base URL of the API defined in the OAS document. * * @see https://swagger.io/docs/specification/api-host-and-base-path/ */ endpoint: ResolveServerUrl; } : TOAS extends { host: infer THost extends string; basePath: infer TBasePath extends string; schemes: (infer TProtocol extends string)[]; } ? { /** * REST APIs have a base URL to which the endpoint paths are appended. The base URL is defined by `schemes`, `host` and `basePath` on the root level of the API specification. * * @see https://swagger.io/docs/specification/2-0/api-host-and-base-path/ */ endpoint: `${TProtocol}://${THost}${TBasePath}`; } : { endpoint?: string; }); export interface ClientRequestParams extends ClientRequestInit { json?: any; formData?: Record; formUrlEncoded?: Record; params?: Record; query?: any; headers?: Record; } export type ClientRequestInit = Omit; export type ClientMethod = (requestParams?: ClientRequestParams) => ClientTypedResponsePromise; export interface ClientPlugin { onRequestInit?: OnRequestInitHook; onFetch?: OnFetchHook; onResponse?: OnResponseHook; } export type OnRequestInitHook = (payload: ClientOnRequestInitPayload) => Promise | void; export type OnFetchHook = (payload: ClientOnFetchHookPayload) => Promise | void; export type OnResponseHook = (payload: ClientOnResponseHookPayload) => Promise | void; export interface ClientOnRequestInitPayload { path: string; method: HTTPMethod; requestParams: ClientRequestParams; requestInit: RequestInit; endResponse(response: Response): void; } export type ClientFetchFn = (input: string, init?: RequestInit) => Promise | Response; export interface ClientOnFetchHookPayload { url: string; init: RequestInit; fetchFn: ClientFetchFn; setFetchFn(fetchFn: ClientFetchFn): void; } export interface ClientOnResponseHookPayload { path: string; method: HTTPMethod; requestParams: ClientRequestParams; requestInit: RequestInit; response: Response; } export type BasicAuthParams = TSecurityScheme extends { type: 'http'; scheme: 'basic'; } | { type: 'basic'; } ? { headers: { /** * `Authorization` header is required for basic authentication * @see https://en.wikipedia.org/wiki/Basic_access_authentication * * It contains the word `Basic` followed by a space and a base64-encoded string `username:password` * * @example * ``` * Authorization: Basic QWxhZGRpbjpvcGVuIHNlc2FtZQ== * ``` */ Authorization: `Basic ${string}`; }; } : {}; export type BearerAuthParams = TSecurityScheme extends { type: 'http'; scheme: 'bearer'; } | { type: 'bearer'; } ? { /** * `Authorization` header is required for bearer authentication * @see https://swagger.io/docs/specification/authentication/bearer-authentication/ */ headers: { /** * It contains the word `Bearer` followed by a space and the token * * @example * ``` * Authorization: Bearer {token} * ``` */ Authorization: `Bearer ${string}`; }; } : {}; export type ApiKeyAuthParams = TSecurityScheme extends { type: 'apiKey'; in: 'header'; name: infer TApiKeyHeaderName; } ? { headers: { [THeaderName in TApiKeyHeaderName extends string ? TApiKeyHeaderName : never]: string; }; } : TSecurityScheme extends { type: 'apiKey'; in: 'query'; name: infer TApiKeyQueryName; } ? { query: { [TQueryName in TApiKeyQueryName extends string ? TApiKeyQueryName : never]: string; }; } : {}; export type SecuritySchemeName = Call, T['security']>[number]; type HasAnonymousSecurityAlternative = true extends (T['security'][number] extends infer TRequirement ? TRequirement extends unknown ? keyof TRequirement extends never ? true : false : false : false) ? true : false; export type OASSecurityParams = BasicAuthParams & BearerAuthParams & ApiKeyAuthParams & OAuth2AuthParams; type OASSecurityParamsBySecurityRefBase = TSecurityObj extends { security: { [key: string]: any; }[]; } ? TOAS extends { components: { securitySchemes: { [TSecuritySchemeNameKey in SecuritySchemeName extends string ? SecuritySchemeName : never]: infer TSecurityScheme; }; }; } | { securityDefinitions: { [TSecuritySchemeNameKey in SecuritySchemeName extends string ? SecuritySchemeName : never]: infer TSecurityScheme; }; } ? OASSecurityParams : SecuritySchemeName extends `basic${string}` ? BasicAuthParams<{ type: 'http'; scheme: 'basic'; }> : SecuritySchemeName extends `bearer${string}` ? BearerAuthParams<{ type: 'http'; scheme: 'bearer'; }> : SecuritySchemeName extends `oauth${string}` ? OAuth2AuthParams<{ type: 'oauth2'; }> : {} : {}; export type OASSecurityParamsBySecurityRef = TSecurityObj extends { security: { [key: string]: any; }[]; } ? HasAnonymousSecurityAlternative extends true ? DeepPartial> : OASSecurityParamsBySecurityRefBase : {}; export {};