import type { z } from "zod"; // ============================================================================ // HTTP Executor Interface // ============================================================================ /** * Request to be executed by the HTTP executor */ export interface ExecuteRequest { method: string; url: string; headers: Record; body?: string; timeout?: number; } /** * Response from the HTTP executor */ export interface ExecuteResponse { status: number; statusText: string; data: unknown; raw: TRawResponse; } /** * Interface for HTTP executors (fetch, ky, etc.) */ export interface HttpExecutor { execute(request: ExecuteRequest): Promise>; } export type ApiRequestSchema = Record>; export type ApiResponseSchema = Record>>; // ============================================================================ // Logging Types // ============================================================================ export type LogLevel = "error" | "warn" | "info" | "debug"; export type LogMeta = Record; export type LogHandler = (message: string, meta?: LogMeta) => void; /** * Optional structured logger for internal client logging. */ export type Logger = Partial>; export interface ApiClientLoggingOptions { logger?: Logger; } // ============================================================================ // Path Parameter Extraction // ============================================================================ /** * Extracts path parameters from an endpoint string * @example ExtractPathParams<'/users/{id}/posts/{postId}'> → 'id' | 'postId' */ export type ExtractPathParams = T extends `${string}{${infer Param}}${infer Rest}` ? Param extends `${infer Key}` ? Key | ExtractPathParams : ExtractPathParams : never; // ============================================================================ // Request Type Extraction // ============================================================================ /** * Extracts params type from Request object */ export type ExtractRequestParams< TRequest extends ApiRequestSchema, TEndpoint extends keyof TRequest, TMethod extends keyof TRequest[TEndpoint], > = TRequest[TEndpoint][TMethod] extends { params: infer P } ? P extends z.ZodTypeAny ? z.input

: never : ExtractPathParams extends never ? never : Record & string, string>; /** * Extracts query type from Request object */ export type ExtractRequestQuery< TRequest extends ApiRequestSchema, TEndpoint extends keyof TRequest, TMethod extends keyof TRequest[TEndpoint], > = TRequest[TEndpoint][TMethod] extends { query: infer Q } ? Q extends z.ZodTypeAny ? z.input : never : never; /** * Extracts body type from Request object */ export type ExtractRequestBody< TRequest extends ApiRequestSchema, TEndpoint extends keyof TRequest, TMethod extends keyof TRequest[TEndpoint], > = TRequest[TEndpoint][TMethod] extends { body: infer B } ? B extends z.ZodTypeAny ? z.input : never : never; /** * Helper to determine if params are required */ export type ParamsRequired< TRequest extends ApiRequestSchema, TEndpoint extends keyof TRequest, TMethod extends keyof TRequest[TEndpoint], > = TRequest[TEndpoint][TMethod] extends { params: z.ZodTypeAny } ? true : ExtractPathParams extends never ? false : true; /** * Helper to determine if body is required */ export type BodyRequired< TRequest extends ApiRequestSchema, TEndpoint extends keyof TRequest, TMethod extends keyof TRequest[TEndpoint], > = TRequest[TEndpoint][TMethod] extends { body: z.ZodTypeAny } ? true : false; /** * Extracts endpoints that have a specific HTTP method */ export type EndpointsWithMethod< TRequest extends ApiRequestSchema, TMethod extends string, > = { [K in keyof TRequest]: TMethod extends keyof TRequest[K] ? K : never; }[keyof TRequest] & string; // ============================================================================ // Response Type Extraction // ============================================================================ /** * Extracts all status codes from Response object for an endpoint/method */ export type ExtractStatusCodes< TResponse extends ApiResponseSchema, TEndpoint extends keyof TResponse, TMethod extends keyof TResponse[TEndpoint], > = TResponse[TEndpoint][TMethod] extends Record ? keyof TResponse[TEndpoint][TMethod] & string : never; /** * Extracts success status codes (2xx) from Response object */ export type ExtractSuccessCodes< TResponse extends ApiResponseSchema, TEndpoint extends keyof TResponse, TMethod extends keyof TResponse[TEndpoint], > = ExtractStatusCodes extends infer Codes ? Codes extends string ? Codes extends `2${string}` ? Codes : never : never : never; /** * Extracts error status codes (non-2xx) from Response object */ export type ExtractErrorCodes< TResponse extends ApiResponseSchema, TEndpoint extends keyof TResponse, TMethod extends keyof TResponse[TEndpoint], > = ExtractStatusCodes extends infer Codes ? Codes extends string ? Codes extends `2${string}` ? never : Codes : never : never; /** * Extracts response schema for a specific status code */ export type ExtractResponseSchema< TResponse extends ApiResponseSchema, TEndpoint extends keyof TResponse, TMethod extends keyof TResponse[TEndpoint], TCode extends string, > = TResponse[TEndpoint][TMethod] extends Record ? TCode extends keyof TResponse[TEndpoint][TMethod] ? TResponse[TEndpoint][TMethod][TCode] : never : never; /** * Infers the success response body type (uses first success code, typically 200) */ export type ExtractSuccessBody< TResponse extends ApiResponseSchema, TEndpoint extends keyof TResponse, TMethod extends keyof TResponse[TEndpoint], > = ExtractSuccessCodes extends infer SuccessCode ? SuccessCode extends string ? ExtractResponseSchema extends z.ZodTypeAny ? z.infer> : never : never : never; // ============================================================================ // Discriminated Union Response Type // ============================================================================ /** * Creates a discriminated union member for a success response */ export type SuccessResponse = { success: true; body: TSuccessBody; code: TCode; raw: TRawResponse; }; /** * Creates a discriminated union member for an error response with specific code */ export type ErrorResponse = { success: false; error: TError; code: TCode; raw: TRawResponse; }; /** * Unexpected error response (for errors not in the spec) */ export type UnexpectedErrorResponse = { success: false; error: unknown; code: number; raw?: TRawResponse; }; /** * Builds the complete discriminated union response type */ export type ApiResponse< TResponse extends ApiResponseSchema, TEndpoint extends string, TMethod extends string, TRawResponse = unknown, > = TEndpoint extends keyof TResponse ? TMethod extends keyof TResponse[TEndpoint] ? // Success responses (one per success code) | (ExtractSuccessCodes extends infer SuccessCode ? SuccessCode extends string ? ExtractResponseSchema< TResponse, TEndpoint, TMethod, SuccessCode > extends z.ZodTypeAny ? SuccessResponse< z.infer>, SuccessCode, TRawResponse > : never : never : never) // Error responses (one per error code) | (ExtractErrorCodes extends infer ErrorCodes ? ErrorCodes extends string ? ExtractResponseSchema< TResponse, TEndpoint, TMethod, ErrorCodes > extends z.ZodTypeAny ? ErrorResponse< z.infer>, ErrorCodes, TRawResponse > : never : never : never) // Unexpected error (catch-all) | UnexpectedErrorResponse : UnexpectedErrorResponse : UnexpectedErrorResponse; // ============================================================================ // Request Options Type // ============================================================================ /** * Context passed to shouldRetry callback */ export type RetryContext = { /** Current attempt number (0-indexed) */ attempt: number; /** Error thrown during request (network errors, timeouts, etc.) */ error?: unknown; /** HTTP response received (for retrying based on status codes) */ response?: { status: number; statusText: string; data: unknown }; }; /** * Request options with conditional required fields */ export type RequestOptions< TRequest extends ApiRequestSchema, TEndpoint extends keyof TRequest, TMethod extends keyof TRequest[TEndpoint], > = { timeout?: number; retries?: number; headers?: Record; /** Custom retry logic - return true to retry, false to stop */ shouldRetry?: (context: RetryContext) => boolean; } & (ExtractRequestQuery extends never ? { query?: never } : { query?: ExtractRequestQuery }) & (ParamsRequired extends true ? { params: ExtractRequestParams } : ExtractRequestParams extends never ? { params?: never } : { params?: ExtractRequestParams }) & (BodyRequired extends true ? { body: ExtractRequestBody } : { body?: ExtractRequestBody });