Skip to content
Client logo

Client

Officialv6.0.4

@kubb/plugin-client

kubb add plugin-client

Migrate to v6

Update your client module for @kubb/plugin-client 6, which calls your client with type arguments and streams through client.stream.

What to change

Your codeWhat to do
Exports a client that takes no type argumentsMake it generic.
Exports toEventStream or EventStreamResultAdd a stream method.
Declares responseType on RequestConfig, or lacks contentType or cookiesAdd the new fields.
Reads security entries as single schemesHandle arrays and the new fields.
Imports a …Status2XX type, or writes a '2XX' keyWrite ranges in lower case.
Groups by tag in an OpenAPI 3.2 document whose tags set parentUpdate the import paths.
Has servers on a path or operation, or reads XML, YAML, CSV, or binary bodiesIt compiles. Check the behavior changes.
Wants .unwrap() or a call that resolves to the bodyOptional. Set returnType.

Upgrade steps

  1. Upgrade kubb and @kubb/plugin-client together. v6 needs Kubb 5.19.0 or later:

    npm install --save-dev kubb@latest @kubb/plugin-client@6
  2. Update your client module with the sections below.
  3. Regenerate with npx kubb generate.
  4. Run npx tsc --noEmit. A missing export or field shows up as a compile error in the generated files.

A generic client call

A generated function calls client<TResponses, ThrowOnError>(config):

export const client = <TResponses, ThrowOnError extends boolean = true>(config: RequestConfig) =>
  send(config) as Promise<RequestResult<TResponses, ThrowOnError>>

A stream method instead of toEventStream

A Server-Sent Events or JSON Lines operation calls client.stream<TResponses, ThrowOnError, TItem>(config). Remove toEventStream and EventStreamResult, add stream to client, and export StreamResult, SuccessOf, TypedEvent, and ServerSentEvent:

export type StreamResult<TResponses, ThrowOnError extends boolean = true, TEvent = ServerSentEvent<SuccessOf<TResponses>>> = {
  stream: AsyncGenerator<TEvent>
  response: Response
}

export function client<TResponses, ThrowOnError extends boolean = true>(config: RequestConfig) {
  return send<TResponses, ThrowOnError>(config)
}

client.stream = async <TResponses, ThrowOnError extends boolean = true, TEvent = ServerSentEvent<SuccessOf<TResponses>>>(
  config: RequestConfig,
): Promise<StreamResult<TResponses, ThrowOnError, TEvent>> => {
  // send the request and parse the body into TEvent values
}

See Stream Server-Sent Events and JSON Lines for every stream field and for JSON Lines.

New fields on the request config

Add these fields to your RequestConfig, or the generated functions fail to compile:

export type RequestConfig = {
  // ...
  contentType?: string | { request?: string; response?: string }
  // Replaces `responseType`. Binary downloads pass 'blob'.
  parseAs?: 'json' | 'text' | 'blob' | 'arrayBuffer' | 'stream'
  // Server-Sent Events only.
  eventShape?: 'event' | 'data'
  rawData?: boolean | Array<string>
  // Name and value pairs for `in: cookie` parameters. Join them into a `Cookie` header on the server.
  cookies?: object
  // `style`, `explode`, `allowReserved`, and `contentType` per parameter, grouped by location.
  styles?: object
}
Browsers ignore a Cookie header set by script and send their own cookies. See Cookie parameters with the cookie style for the OpenAPI 3.2 cookie style.

Security requirements

An entry is an array when one requirement needs several schemes together, such as an API key and a session cookie. Send all of them, or skip the entry. oauth2 now also passes flows and metadataUrl, and openIdConnect passes url:

export type Auth = {
  type: string
  scheme?: string
  name?: string
  in?: string
  scopes?: Array<string>
  flows?: Record<string, Record<string, string>>
  metadataUrl?: string
  url?: string
}

Status ranges

Generated names and keys write status ranges in lower case. Rename the imports and any range keys you wrote by hand in a responses record or validator.error map. An upper-case key such as '2XX' is no longer read:

 import type { ListPetsStatus4XX } from './gen/types/ListPets'
 import type { ListPetsStatus4xx } from './gen/types/ListPets'

Tag folders

With group: { type: 'tag' }, a tag whose OpenAPI 3.2 parent is store now writes to store/payment/, not payment/. One relative importPath cannot serve both folder depths. Pass group.name to keep one folder per tag, or set importPath to a package name or alias:

pluginClient({ importPath: '../../../client', group: { type: 'tag', name: ({ group }) => group } })

Behavior changes

These compile without errors, so check them by hand:

ChangeWhat your client must do
A path or operation with its own servers passes that server as baseURL.Use config.baseURL when set.
A 2xx key is a success, and an exact code such as 200 wins over a range before it.Nothing, unless you read the responses record by hand.
XML, YAML, and CSV responses are typed string. Image, audio, video, PDF, and application/octet-stream responses are Blob.Read a Blob when the call passes parseAs: 'blob', which it does when every success type is binary.
An operation whose only success type is JSON Lines returns a StreamResult.Parse one item per line in client.stream.

Return type

returnType is new and defaults to 'plain', so existing code keeps working. 'full' adds .unwrap(), and 'data' resolves to the body when throwOnError is true. Both 'full' and 'data' import extra helpers from your module. See returnType.