# @zerotal/client — public API surface

<!-- AUTO-GENERATED by scripts/api-surface.ts. Do not edit by hand.
     Run `bun run api:surface` to regenerate after an intentional API change. -->

> Not a guide — this is a flat, alphabetical snapshot used to detect unintended
> API changes. For how to use these, read the [documentation](https://zerotal.dev/docs).

## .  `(./src/index.ts)`

class ApiClient = {
  new <Routes extends ApiRouteMap>(config?: ApiClientConfig): ApiClient<Routes>
  delete: <Path extends PathsFor<Routes, 'DELETE'>>(path: Path, params?: ParamRecord<Path & string>, options?: RequestOptions) => Promise<ResponseOf<Routes, 'DELETE', Path & string>>
  get: <Path extends PathsFor<Routes, 'GET'>>(path: Path, params?: ParamRecord<Path & string>, options?: GetOptions<QueryOf<Routes, 'GET', Path & string>>) => Promise<ResponseOf<Routes, 'GET', Path & string>>
  patch: <Path extends PathsFor<Routes, 'PATCH'>>(path: Path, body?: BodyOf<Routes, 'PATCH', Path & string>, options?: MutationOptions<QueryOf<Routes, 'PATCH', Path & string>>) => Promise<ResponseOf<Routes, 'PATCH', Path & string>>
  post: <Path extends PathsFor<Routes, 'POST'>>(path: Path, body?: BodyOf<Routes, 'POST', Path & string>, options?: MutationOptions<QueryOf<Routes, 'POST', Path & string>>) => Promise<ResponseOf<Routes, 'POST', Path & string>>
  put: <Path extends PathsFor<Routes, 'PUT'>>(path: Path, body?: BodyOf<Routes, 'PUT', Path & string>, options?: MutationOptions<QueryOf<Routes, 'PUT', Path & string>>) => Promise<ResponseOf<Routes, 'PUT', Path & string>>
  setToken: (token: TokenSource | null) => void
}

class ApiClientError = {
  new (status: number, statusText: string, body: string, headers?: Headers): ApiClientError
  readonly body: string
  readonly code: string
  readonly context?: Record<string, unknown> | undefined
  readonly headers: Headers | undefined
  readonly status: number
  readonly statusText: string
  retryAfterMs: number | null
}

class Channel = {
  new (name: string, _socket: Socket): Channel
  _dispatch: (frame: ServerFrame) => void
  error: (cb: Listener) => Channel
  listen: (event: string, cb: Listener) => Channel
  readonly name: string
  stopListening: (event: string, cb?: Listener) => Channel
  subscribed: (cb: Listener) => Channel
  unsubscribe: () => void
}

class CircuitBreaker = {
  new (options?: CircuitBreakerOptions): CircuitBreaker
  call: <T>(fn: () => Promise<T>) => Promise<T>
  failures: number
  reset: () => void
  state: CircuitState
}

class CircuitBreakerOpenError = {
  new (): CircuitBreakerOpenError
  readonly code: string
  readonly context?: Record<string, unknown> | undefined
  readonly status: number
}

class ClientProvider = {
  new (app: Application): ClientProvider
  static dependsOn?: (new (app: Application) => ServiceProvider)[]
  static environments: AppEnvironment[]
  static priority?: number
  static provides: readonly ['client']
  devProcesses: () => DevProcessDefinition[]
  doctorChecks: () => DoctorCheck[]
  onBooted: () => Promise<void>
  onBooting: () => Promise<void>
  onRegister: () => void
  onRequestProcessed: (_ctx: HttpContext) => Promise<void>
  onRequestReceived: (_ctx: HttpContext) => Promise<void>
  onResponseSent: (_ctx: HttpContext) => Promise<void>
  onStarted: () => Promise<void>
  onStarting: () => Promise<void>
  onStopped: () => Promise<void>
  onStopping: () => Promise<void>
  replContext: () => Record<string, unknown>
}

class PresenceChannel = {
  new (name: string, _socket: Socket): PresenceChannel
  _dispatch: (frame: ServerFrame) => void
  error: (cb: Listener) => PresenceChannel
  here: (cb: (members: PresenceMember[]) => void) => PresenceChannel
  joining: (cb: (member: PresenceMember) => void) => PresenceChannel
  leaving: (cb: (member: PresenceMember) => void) => PresenceChannel
  listen: (event: string, cb: Listener) => PresenceChannel
  readonly name: string
  stopListening: (event: string, cb?: Listener) => PresenceChannel
  subscribed: (cb: Listener) => PresenceChannel
  unsubscribe: () => void
}

class Socket = {
  new (options?: SocketOptions): Socket
  channel: (name: string) => Channel
  channels: () => Channel[]
  connect: () => void
  disconnect: () => void
  join: (name: string) => PresenceChannel
  leave: (name: string) => void
  leaveChannel: (wireName: string) => void
  on: (state: SocketState, cb: (detail?: unknown) => void) => () => void
  presence: (name: string) => PresenceChannel
  private: (name: string) => Channel
  socketId: () => string | null
  state: SocketState
}

class ValidationError = {
  new (statusText: string, body: string, errors: Record<string, string[]>, validationMessage: string, headers?: Headers): ValidationError
  all: () => Record<string, string[]>
  fields: () => string[]
  first: (field: string) => string | undefined
  has: (field: string) => boolean
  readonly body: string
  readonly code: string
  readonly context?: Record<string, unknown> | undefined
  readonly errors: Record<string, string[]>
  readonly headers: Headers | undefined
  readonly status: number
  readonly statusText: string
  readonly validationMessage: string
  retryAfterMs: number | null
}

const Client = ApiClient<Partial<Record<`GET ${string}` | `POST ${string}` | `PUT ${string}` | `PATCH ${string}` | `DELETE ${string}`, RouteShape>>>

function ClientConfig = (options?: Partial<ClientConfigShape>) => Partial<ClientConfigShape>

function createApiClient = <Routes extends ApiRouteMap>(config?: ApiClientConfig) => ApiClient<Routes>

interface ApiClientConfig = {
  baseUrl?: string | undefined
  circuitBreaker?: CircuitBreaker | CircuitBreakerOptions | undefined
  csrf?: boolean | {    cookie?: string;    header?: string;} | undefined
  headers?: Record<string, string> | undefined
  onError?: ((error: ApiClientError) => void | Promise<void>) | undefined
  onForbidden?: ((error: ApiClientError) => void | Promise<void>) | undefined
  onRequest?: RequestInterceptor | RequestInterceptor[] | undefined
  onResponse?: ResponseInterceptor | ResponseInterceptor[] | undefined
  onUnauthorized?: ((error: ApiClientError, retry: (headerOverrides?: Record<string, string>) => Promise<unknown>) => Promise<unknown> | void) | undefined
  retry?: number | RetryOptions | undefined
  timeout?: number | undefined
  token?: TokenSource | undefined
  withCredentials?: boolean | undefined
}

interface CircuitBreakerOptions = {
  isFailure?: ((error: unknown) => boolean) | undefined
  resetTimeout?: number | undefined
  threshold?: number | undefined
}

interface ClientConfigShape = {
  baseUrl?: string | undefined
  circuitBreaker?: CircuitBreaker | CircuitBreakerOptions | undefined
  csrf?: boolean | {    cookie?: string;    header?: string;} | undefined
  headers?: Record<string, string> | undefined
  onError?: ((error: ApiClientError) => void | Promise<void>) | undefined
  onForbidden?: ((error: ApiClientError) => void | Promise<void>) | undefined
  onRequest?: RequestInterceptor | RequestInterceptor[] | undefined
  onResponse?: ResponseInterceptor | ResponseInterceptor[] | undefined
  onUnauthorized?: ((error: ApiClientError, retry: (headerOverrides?: Record<string, string>) => Promise<unknown>) => Promise<unknown> | void) | undefined
  retry?: number | RetryOptions | undefined
  timeout?: number | undefined
  token?: TokenSource | undefined
  withCredentials?: boolean | undefined
}

interface GetOptions = {
  headers?: Record<string, string> | undefined
  init?: Omit<RequestInit, 'method' | 'headers' | 'body'> | undefined
  meta?: ((meta: ResponseMeta) => void) | undefined
  query?: Q | undefined
  responseType?: 'text' | 'json' | 'auto' | 'blob' | 'arrayBuffer' | undefined
  retry?: number | false | RetryOptions | undefined
  signal?: AbortSignal | undefined
  timeout?: number | undefined
}

interface MutationOptions = {
  headers?: Record<string, string> | undefined
  init?: Omit<RequestInit, 'method' | 'headers' | 'body'> | undefined
  meta?: ((meta: ResponseMeta) => void) | undefined
  params?: Record<string, string | number> | undefined
  query?: Q | undefined
  responseType?: 'text' | 'json' | 'auto' | 'blob' | 'arrayBuffer' | undefined
  retry?: number | false | RetryOptions | undefined
  signal?: AbortSignal | undefined
  timeout?: number | undefined
}

interface PresenceMember = {
  id: string | number
  info: Record<string, unknown>
}

interface RequestConfig = {
  body: BodyInit | undefined
  headers: Record<string, string>
  method: string
  url: string
}

interface RequestOptions = {
  headers?: Record<string, string> | undefined
  init?: Omit<RequestInit, 'method' | 'headers' | 'body'> | undefined
  meta?: ((meta: ResponseMeta) => void) | undefined
  responseType?: 'text' | 'json' | 'auto' | 'blob' | 'arrayBuffer' | undefined
  retry?: number | false | RetryOptions | undefined
  signal?: AbortSignal | undefined
  timeout?: number | undefined
}

interface ResponseContext = {
  data: unknown
  headers: Headers
  request: RequestConfig
  status: number
}

interface ResponseMeta = {
  headers: Headers
  status: number
}

interface RetryOptions = {
  attempts?: number | undefined
  backoff?: ((attempt: number, retryAfterMs: number | null) => number) | undefined
  methods?: string[] | undefined
  respectRetryAfter?: boolean | undefined
  statuses?: number[] | undefined
}

interface RouteShape = {
  body?: Record<string, unknown>
  params?: Record<string, string | number>
  query?: Record<string, string | number | boolean | undefined>
  response?: unknown
}

interface SocketLike = {
  close: (code?: number, reason?: string) => void
  onclose: ((ev: {    code?: number;    reason?: string;}) => void) | null
  onerror: ((ev: unknown) => void) | null
  onmessage: ((ev: {    data: unknown;}) => void) | null
  onopen: ((ev: unknown) => void) | null
  readyState: number
  send: (data: string) => void
}

interface SocketOptions = {
  WebSocket?: (new (url: string) => SocketLike) | undefined
  auth?: {    params?: Record<string, string>;    headers?: Record<string, string>;} | undefined
  authEndpoint?: string | false | undefined
  autoConnect?: boolean | undefined
  fetch?: typeof fetch | undefined
  host?: string | undefined
  maxReconnectDelay?: number | undefined
  path?: string | undefined
  pingInterval?: number | undefined
  port?: string | number | undefined
  reconnect?: boolean | undefined
  reconnectDelay?: number | undefined
  scheme?: 'ws' | 'wss' | undefined
  url?: string | undefined
}

type ApiRouteMap = {    [x: `GET ${string}`]: RouteShape | undefined;    [x: `POST ${string}`]: RouteShape | undefined;    [x: `PUT ${string}`]: RouteShape | undefined;    [x: `PATCH ${string}`]: RouteShape | undefined;    [x: `DELETE ${string}`]: RouteShape | undefined;}

type BodyOf = `${Method} ${Path}` extends keyof Routes ? Routes[`${Method} ${Path}`] extends {    body: infer B;} ? B : Record<string, unknown> : Record<string, unknown>

type CircuitState = 'closed' | 'open' | 'half-open'

type HttpMethod = 'GET' | 'POST' | 'PUT' | 'PATCH' | 'DELETE'

type ParamRecord = [PathParams<Path>] extends [never] ? undefined : { [K in PathParams<Path>]: string | number; }

type PathParams = Path extends `${string}{${infer Param}}${infer Tail}` ? Param | PathParams<Tail> : never

type PathsFor = { [K in keyof Routes & string]: K extends `${Method} ${infer Path}` ? Path : never; }[keyof Routes & string]

type QueryOf = `${Method} ${Path}` extends keyof Routes ? Routes[`${Method} ${Path}`] extends {    query: infer Q;} ? Q : Record<string, string | number | boolean | undefined> : Record<string, string | number | boolean | undefined>

type RequestInterceptor = (config: RequestConfig) => RequestConfig | Promise<RequestConfig>

type ResponseInterceptor = (ctx: ResponseContext) => ResponseContext | Promise<ResponseContext>

type ResponseOf = `${Method} ${Path}` extends keyof Routes ? Routes[`${Method} ${Path}`] extends {    response: infer R;} ? R : unknown : unknown

type SocketState = 'connecting' | 'connected' | 'disconnected' | 'reconnecting' | 'error'

type TokenSource = string | (() => string | null | undefined | Promise<string | null | undefined>)

## ./provider  `(./src/provider/ClientProvider.ts)`

class ClientProvider = {
  new (app: Application): ClientProvider
  static dependsOn?: (new (app: Application) => ServiceProvider)[]
  static environments: AppEnvironment[]
  static priority?: number
  static provides: readonly ['client']
  devProcesses: () => DevProcessDefinition[]
  doctorChecks: () => DoctorCheck[]
  onBooted: () => Promise<void>
  onBooting: () => Promise<void>
  onRegister: () => void
  onRequestProcessed: (_ctx: HttpContext) => Promise<void>
  onRequestReceived: (_ctx: HttpContext) => Promise<void>
  onResponseSent: (_ctx: HttpContext) => Promise<void>
  onStarted: () => Promise<void>
  onStarting: () => Promise<void>
  onStopped: () => Promise<void>
  onStopping: () => Promise<void>
  replContext: () => Record<string, unknown>
}

const Client = ApiClient<Partial<Record<`GET ${string}` | `POST ${string}` | `PUT ${string}` | `PATCH ${string}` | `DELETE ${string}`, RouteShape>>>

## ./Socket  `(./src/Socket.ts)`

class Channel = {
  new (name: string, _socket: Socket): Channel
  _dispatch: (frame: ServerFrame) => void
  error: (cb: Listener) => Channel
  listen: (event: string, cb: Listener) => Channel
  readonly name: string
  stopListening: (event: string, cb?: Listener) => Channel
  subscribed: (cb: Listener) => Channel
  unsubscribe: () => void
}

class PresenceChannel = {
  new (name: string, _socket: Socket): PresenceChannel
  _dispatch: (frame: ServerFrame) => void
  error: (cb: Listener) => PresenceChannel
  here: (cb: (members: PresenceMember[]) => void) => PresenceChannel
  joining: (cb: (member: PresenceMember) => void) => PresenceChannel
  leaving: (cb: (member: PresenceMember) => void) => PresenceChannel
  listen: (event: string, cb: Listener) => PresenceChannel
  readonly name: string
  stopListening: (event: string, cb?: Listener) => PresenceChannel
  subscribed: (cb: Listener) => PresenceChannel
  unsubscribe: () => void
}

class Socket = {
  new (options?: SocketOptions): Socket
  channel: (name: string) => Channel
  channels: () => Channel[]
  connect: () => void
  disconnect: () => void
  join: (name: string) => PresenceChannel
  leave: (name: string) => void
  leaveChannel: (wireName: string) => void
  on: (state: SocketState, cb: (detail?: unknown) => void) => () => void
  presence: (name: string) => PresenceChannel
  private: (name: string) => Channel
  socketId: () => string | null
  state: SocketState
}

interface PresenceMember = {
  id: string | number
  info: Record<string, unknown>
}

interface SocketLike = {
  close: (code?: number, reason?: string) => void
  onclose: ((ev: {    code?: number;    reason?: string;}) => void) | null
  onerror: ((ev: unknown) => void) | null
  onmessage: ((ev: {    data: unknown;}) => void) | null
  onopen: ((ev: unknown) => void) | null
  readyState: number
  send: (data: string) => void
}

interface SocketOptions = {
  WebSocket?: (new (url: string) => SocketLike) | undefined
  auth?: {    params?: Record<string, string>;    headers?: Record<string, string>;} | undefined
  authEndpoint?: string | false | undefined
  autoConnect?: boolean | undefined
  fetch?: typeof fetch | undefined
  host?: string | undefined
  maxReconnectDelay?: number | undefined
  path?: string | undefined
  pingInterval?: number | undefined
  port?: string | number | undefined
  reconnect?: boolean | undefined
  reconnectDelay?: number | undefined
  scheme?: 'ws' | 'wss' | undefined
  url?: string | undefined
}

type SocketState = 'connecting' | 'connected' | 'disconnected' | 'reconnecting' | 'error'
