@kubb/plugin-client 6.0.3 → 6.0.4

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
package/dist/index.cjs CHANGED
@@ -33,11 +33,11 @@ let _kubb_renderer_jsx_jsx_runtime = require("@kubb/renderer-jsx/jsx-runtime");
33
33
  let node_url = require("node:url");
34
34
  // kubb-extension.json
35
35
  var compatibility = {
36
- "kubb": ">=5.18.4",
36
+ "kubb": ">=5.19.0",
37
37
  "node": ">=22.22"
38
38
  };
39
39
  // package.json
40
- var version = "6.0.3";
40
+ var version = "6.0.4";
41
41
  // ../../internals/utils/src/reserved.ts
42
42
  /**
43
43
  * JavaScript and Java reserved words.
package/dist/index.js CHANGED
@@ -7,11 +7,11 @@ import { Fragment, jsx, jsxs } from "@kubb/renderer-jsx/jsx-runtime";
7
7
  import { fileURLToPath } from "node:url";
8
8
  // kubb-extension.json
9
9
  var compatibility = {
10
- "kubb": ">=5.18.4",
10
+ "kubb": ">=5.19.0",
11
11
  "node": ">=22.22"
12
12
  };
13
13
  // package.json
14
- var version = "6.0.3";
14
+ var version = "6.0.4";
15
15
  // ../../internals/utils/src/reserved.ts
16
16
  /**
17
17
  * JavaScript and Java reserved words.
@@ -1,6 +1,6 @@
1
1
  # Call operations
2
2
 
3
- Each generated function takes one grouped options object and returns your client's promise.
3
+ Call a generated function with one grouped options object. It returns your client's promise, shaped by your `RequestResult` type.
4
4
 
5
5
  ```typescript
6
6
  import { addPet, getPetById } from './gen/clients'
@@ -11,31 +11,34 @@ console.log(created.data, created.response.status)
11
11
  const pet = await getPetById({ path: { petId: 1 } })
12
12
  ```
13
13
 
14
- The examples set [`integerType: 'number'`](https://kubb.dev/extensions/kubb-labs/adapter-oas/options#integertype) on the adapter, so an `int64` id such as `petId` is a `number`. With the default it is a `bigint` and the call takes `1n`.
14
+ The examples set [`integerType: 'number'`](https://kubb.dev/extensions/kubb-labs/adapter-oas/options#integertype) on the adapter. Without it, `petId` is a `bigint` and takes `1n`.
15
15
 
16
- Path keys keep the names from the spec, so a `{pet-id}` placeholder is passed as `path: { 'pet-id': 1 }`. Set [`pathParamsCasing: 'camelcase'`](https://kubb.dev/extensions/kubb-labs/adapter-oas/options#pathparamscasing) on the adapter (`@kubb/adapter-oas` 5.20.0 or later) to pass `path: { petId: 1 }` instead, the name MSW handlers and query keys already use. The HTTP request stays the same.
16
+ | Key | Holds |
17
+ | --------------------------------------------- | --------------------------------------------------------------------------------------------------------------------------------- |
18
+ | `path`, `query`, `headers`, `cookies`, `body` | The request values, typed from the spec. |
19
+ | `throwOnError` | Overrides [`throwOnErrorDefault`](https://kubb.dev/extensions/kubb-labs/plugin-client/options#throwonerrordefault) for this call. |
20
+ | `client` | Another client function for this call, such as a signed or per-tenant client. |
21
+ | Any other key | Passed to your client as is, such as `signal`. |
17
22
 
18
- The keys are `path`, `query`, `headers`, `cookies`, and `body`. Kubb types each from the spec, so a wrong key or value fails at compile time.
23
+ Path keys keep their spec names unless you set [`pathParamsCasing`](https://kubb.dev/extensions/kubb-labs/adapter-oas/options#pathparamscasing). See [Path parameter names](#path-parameter-names).
19
24
 
20
- ## Errors
25
+ ## Handle errors
21
26
 
22
- `throwOnError` defaults to `true` (see [`throwOnErrorDefault`](https://kubb.dev/extensions/kubb-labs/plugin-client/options#throwonerrordefault)). What that means is up to your client. With the [minimal client](https://kubb.dev/extensions/kubb-labs/plugin-client/guides/write-your-client#a-minimal-client), a non-2xx status throws, and `throwOnError: false` returns the error as a value:
27
+ Your client decides what `throwOnError` does. With the [client from scratch](https://kubb.dev/extensions/kubb-labs/plugin-client/guides/write-your-client#write-the-client), a non-2xx status throws, and `throwOnError: false` returns the error as a value:
23
28
 
24
29
  ```typescript
25
30
  const result = await getPetById({ path: { petId: 1 }, throwOnError: false })
26
31
 
27
- if (result.error === undefined) {
28
- console.log('pet', result.data)
29
- }
30
- if (result.error !== undefined) {
31
- console.log('failed', result.response.status)
32
- }
32
+ if (result.error === undefined) console.log('pet', result.data)
33
+ else console.log('failed', result.response.status)
33
34
  ```
34
35
 
35
- ## Use a different client for one call
36
+ Next, [validate requests and responses](https://kubb.dev/extensions/kubb-labs/plugin-client/guides/validate-requests-and-responses).
36
37
 
37
- Pass `client` to send one call through another function:
38
+ ## Less common cases
38
39
 
39
- ```typescript
40
- await getPetById({ path: { petId: 2 }, client: tenantClient })
41
- ```
40
+ ### Path parameter names
41
+
42
+ When the spec names a path parameter in kebab or snake case, such as `{pet-id}`, the path key keeps that name: pass `path: { 'pet-id': 1 }`.
43
+
44
+ Set [`pathParamsCasing: 'camelcase'`](https://kubb.dev/extensions/kubb-labs/adapter-oas/options#pathparamscasing) on the adapter (`@kubb/adapter-oas` 5.20.0 or later) to pass `path: { petId: 1 }` instead. The HTTP request stays the same.
@@ -1,45 +1,35 @@
1
1
  # Migrate to v6
2
2
 
3
- `@kubb/plugin-client` 6 calls your client with type arguments and streams through `client.stream`. Update the `client` export of your module, then regenerate. Find your code in the table, then follow its section.
3
+ Update your client module for `@kubb/plugin-client` 6, which calls your client with type arguments and streams through `client.stream`.
4
4
 
5
5
  ## What to change
6
6
 
7
- | Your code | What to do |
8
- | ------------------------------------------------------------------------------- | ---------------------------------------------------------------------------------------------------------------------------------------------------------------------------- |
9
- | Exports a `client` that takes no type arguments | Make it generic. See [A generic client call](https://kubb.dev/extensions/kubb-labs/plugin-client/guides/migrate-to-v6#a-generic-client-call). |
10
- | Exports `toEventStream` or `EventStreamResult` | Add a `stream` method to `client`. See [A stream method](https://kubb.dev/extensions/kubb-labs/plugin-client/guides/migrate-to-v6#a-stream-method-instead-of-toeventstream). |
11
- | Declares `responseType` on `RequestConfig`, or lacks `contentType` or `cookies` | Add the new fields. See [New fields on the request config](https://kubb.dev/extensions/kubb-labs/plugin-client/guides/migrate-to-v6#new-fields-on-the-request-config). |
12
- | Reads `security` entries as single schemes | Handle arrays and the new fields. See [Security requirements](https://kubb.dev/extensions/kubb-labs/plugin-client/guides/migrate-to-v6#security-requirements). |
13
- | Imports a `…Status2XX` type, or writes a `'2XX'` key | Write the range in lower case. See [Status ranges](https://kubb.dev/extensions/kubb-labs/plugin-client/guides/migrate-to-v6#status-ranges). |
14
- | Groups by tag in an OpenAPI 3.2 document whose tags set `parent` | Update the import paths. See [Tag folders](https://kubb.dev/extensions/kubb-labs/plugin-client/guides/migrate-to-v6#tag-folders). |
15
- | Has `servers` on a path or operation, or reads XML, YAML, CSV, or binary bodies | Check [Behavior changes](https://kubb.dev/extensions/kubb-labs/plugin-client/guides/migrate-to-v6#behavior-changes). It compiles. |
16
- | Wants `.unwrap()` or a call that resolves to the body | Set `returnType`. See [Return type](https://kubb.dev/extensions/kubb-labs/plugin-client/guides/migrate-to-v6#return-type). This is optional. |
7
+ | Your code | What to do |
8
+ | ------------------------------------------------------------------------------- | ----------------------------------------------------------------------------------------------------------------------------------------- |
9
+ | Exports a `client` that takes no type arguments | Make it [generic](https://kubb.dev/extensions/kubb-labs/plugin-client/guides/migrate-to-v6#a-generic-client-call). |
10
+ | Exports `toEventStream` or `EventStreamResult` | Add a [stream method](https://kubb.dev/extensions/kubb-labs/plugin-client/guides/migrate-to-v6#a-stream-method-instead-of-toeventstream). |
11
+ | Declares `responseType` on `RequestConfig`, or lacks `contentType` or `cookies` | Add the [new fields](https://kubb.dev/extensions/kubb-labs/plugin-client/guides/migrate-to-v6#new-fields-on-the-request-config). |
12
+ | Reads `security` entries as single schemes | Handle [arrays and the new fields](https://kubb.dev/extensions/kubb-labs/plugin-client/guides/migrate-to-v6#security-requirements). |
13
+ | Imports a `…Status2XX` type, or writes a `'2XX'` key | Write [ranges in lower case](https://kubb.dev/extensions/kubb-labs/plugin-client/guides/migrate-to-v6#status-ranges). |
14
+ | Groups by tag in an OpenAPI 3.2 document whose tags set `parent` | Update the [import paths](https://kubb.dev/extensions/kubb-labs/plugin-client/guides/migrate-to-v6#tag-folders). |
15
+ | Has `servers` on a path or operation, or reads XML, YAML, CSV, or binary bodies | It compiles. Check the [behavior changes](https://kubb.dev/extensions/kubb-labs/plugin-client/guides/migrate-to-v6#behavior-changes). |
16
+ | Wants `.unwrap()` or a call that resolves to the body | Optional. Set [`returnType`](https://kubb.dev/extensions/kubb-labs/plugin-client/guides/migrate-to-v6#return-type). |
17
17
 
18
18
  ## Upgrade steps
19
19
 
20
- 1. Upgrade `kubb` and `@kubb/plugin-client` together. v6 needs Kubb 5.18.4 or later:
20
+ 1. Upgrade `kubb` and `@kubb/plugin-client` together. v6 needs Kubb 5.19.0 or later:
21
21
 
22
22
  ```bash
23
23
  npm install --save-dev kubb@latest @kubb/plugin-client@6
24
24
  ```
25
25
 
26
26
  2. Update your client module with the sections below.
27
-
28
- 3. Regenerate the operation files:
29
-
30
- ```bash
31
- npx kubb generate
32
- ```
33
-
34
- 4. Run the type checker. A missing export or field in your client module shows up as a compile error in the generated files:
35
-
36
- ```bash
37
- npx tsc --noEmit
38
- ```
27
+ 3. Regenerate with `npx kubb generate`.
28
+ 4. Run `npx tsc --noEmit`. A missing export or field shows up as a compile error in the generated files.
39
29
 
40
30
  ## A generic client call
41
31
 
42
- A generated function now calls `client<TResponses, ThrowOnError>(config)`. Make `client` generic:
32
+ A generated function calls `client<TResponses, ThrowOnError>(config)`:
43
33
 
44
34
  ```typescript
45
35
  export const client = <TResponses, ThrowOnError extends boolean = true>(config: RequestConfig) =>
@@ -67,7 +57,7 @@ client.stream = async <TResponses, ThrowOnError extends boolean = true, TEvent =
67
57
  }
68
58
  ```
69
59
 
70
- A Server-Sent Events call also passes `eventShape: 'event'` or `'data'`. A JSON Lines call, such as `application/jsonl` or `application/x-ndjson`, passes the media type in `contentType.response` and expects one parsed item per line. See [Write your client](https://kubb.dev/extensions/kubb-labs/plugin-client/guides/write-your-client).
60
+ See [Stream Server-Sent Events and JSON Lines](https://kubb.dev/extensions/kubb-labs/plugin-client/guides/streaming) for every stream field and for JSON Lines.
71
61
 
72
62
  ## New fields on the request config
73
63
 
@@ -79,21 +69,22 @@ export type RequestConfig = {
79
69
  contentType?: string | { request?: string; response?: string }
80
70
  // Replaces `responseType`. Binary downloads pass 'blob'.
81
71
  parseAs?: 'json' | 'text' | 'blob' | 'arrayBuffer' | 'stream'
72
+ // Server-Sent Events only.
82
73
  eventShape?: 'event' | 'data'
83
- // Name and value pairs for `in: cookie` parameters. Join them into a `Cookie` header on the server. Browsers ignore a `Cookie` header set by script and send their own cookies.
74
+ rawData?: boolean | Array<string>
75
+ // Name and value pairs for `in: cookie` parameters. Join them into a `Cookie` header on the server.
84
76
  cookies?: object
85
77
  // `style`, `explode`, `allowReserved`, and `contentType` per parameter, grouped by location.
86
78
  styles?: object
87
79
  }
88
80
  ```
89
81
 
90
- A cookie parameter with the OpenAPI 3.2 `cookie` style passes `style: 'cookie'` in `styles`. Send its values without percent-encoding. See [Serialize parameters](https://kubb.dev/extensions/kubb-labs/plugin-client/guides/write-your-client#serialize-parameters).
82
+ > [!NOTE]
83
+ > Browsers ignore a `Cookie` header set by script and send their own cookies. See [Cookie parameters with the cookie style](https://kubb.dev/extensions/kubb-labs/plugin-client/guides/write-your-client#cookie-parameters-with-the-cookie-style) for the OpenAPI 3.2 `cookie` style.
91
84
 
92
85
  ## Security requirements
93
86
 
94
- A `security` 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.
95
-
96
- An `oauth2` scheme now also passes `flows` and `metadataUrl`, and an `openIdConnect` scheme passes `url`. Accept them on your auth type:
87
+ 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`:
97
88
 
98
89
  ```typescript
99
90
  export type Auth = {
@@ -110,7 +101,7 @@ export type Auth = {
110
101
 
111
102
  ## Status ranges
112
103
 
113
- A status range is written in lower case in generated names and keys. Rename the imports, and the range keys in a responses record or `validator.error` map you wrote by hand. An upper-case key such as `'2XX'` is no longer read:
104
+ 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:
114
105
 
115
106
  ```diff
116
107
  - import type { ListPetsStatus4XX } from './gen/types/ListPets'
@@ -119,7 +110,7 @@ A status range is written in lower case in generated names and keys. Rename the
119
110
 
120
111
  ## Tag folders
121
112
 
122
- With `group: { type: 'tag' }`, a tag whose OpenAPI 3.2 `parent` is `store` now writes to `store/payment/` instead of `payment/`. `importPath` is written into every file as given, so one relative path cannot serve files at two depths, such as `store/` and `store/payment/`. Pass `group.name` to keep one folder per tag, or set `importPath` to a package name or alias that resolves from every folder:
113
+ 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:
123
114
 
124
115
  ```typescript
125
116
  pluginClient({ importPath: '../../../client', group: { type: 'tag', name: ({ group }) => group } })
@@ -129,11 +120,13 @@ pluginClient({ importPath: '../../../client', group: { type: 'tag', name: ({ gro
129
120
 
130
121
  These compile without errors, so check them by hand:
131
122
 
132
- - A path or operation with its own `servers` passes that server as `baseURL` on the call. Your client must use `config.baseURL` when it is set.
133
- - A `2xx` response key is a success, and an exact code such as `200` wins over a range listed before it.
134
- - An XML, YAML, or CSV response is typed `string`. An image, audio, video, PDF, or `application/octet-stream` response is typed `Blob`. When every success content type is binary, the call passes `parseAs: 'blob'`.
135
- - An operation whose only success content type is JSON Lines returns a `StreamResult`, not the whole body as one string.
123
+ | Change | What your client must do |
124
+ | ------------------------------------------------------------------------------------------------------------------------------- | ------------------------------------------------------------------------------------------------------ |
125
+ | A path or operation with its own `servers` passes that server as `baseURL`. | Use `config.baseURL` when set. |
126
+ | 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. |
127
+ | 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. |
128
+ | An operation whose only success type is JSON Lines returns a `StreamResult`. | Parse one item per line in `client.stream`. |
136
129
 
137
130
  ## Return type
138
131
 
139
- `returnType` is new and defaults to `'plain'`, so existing code keeps working. With `'full'`, generated calls wrap the result in `withUnwrap`, so your module must also export `withUnwrap`, `resolveThrowOnError`, and the `Unwrappable` type. With `'data'`, calls resolve to the body when `throwOnError` is `true`, and to the full result when it is `false`. Your module must then export `unwrapResult`, `resolveThrowOnError`, and the `UnwrappedResult` type. A stream operation also imports `StreamOptions` with `StreamResult` for `'full'`, or with `UnwrappedStreamResult` for `'data'`. Both modes need a `StreamResult` with `data` and `error`, as the one in `@kubb/plugin-client/templates/core.ts` has, so the minimal type above is not enough. `@kubb/plugin-client/templates/core.ts` exports all of these. See [returnType](https://kubb.dev/extensions/kubb-labs/plugin-client/options#returntype).
132
+ `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](https://kubb.dev/extensions/kubb-labs/plugin-client/options#returntype).
@@ -0,0 +1,106 @@
1
+ # Stream Server-Sent Events and JSON Lines
2
+
3
+ Add `client.stream` and its types to your client module, so streamed operations compile and yield typed events or lines.
4
+
5
+ ## Add a stream method
6
+
7
+ When the spec has a Server-Sent Events or JSON Lines operation, its generated function calls `client.stream` instead of `client`:
8
+
9
+ ::code-group
10
+
11
+ ```typescript [src/gen/clients/chat/streamChat.ts]
12
+ import type { Options, StreamResult, SuccessOf, TypedEvent } from '../../../client'
13
+ import type { StreamChatOptions, StreamChatResponses } from '../../types/StreamChat'
14
+ import { client } from '../../../client'
15
+
16
+ export function streamChat<ThrowOnError extends boolean = true>(
17
+ options: Options<StreamChatOptions, ThrowOnError> = {},
18
+ ): Promise<StreamResult<StreamChatResponses, ThrowOnError, TypedEvent<SuccessOf<StreamChatResponses>>>> {
19
+ const { client: apiClient = client, ...config } = options
20
+
21
+ return apiClient.stream<StreamChatResponses, ThrowOnError, TypedEvent<SuccessOf<StreamChatResponses>>>({
22
+ method: 'POST',
23
+ url: '/chat',
24
+ contentType: { response: 'text/event-stream' },
25
+ eventShape: 'event',
26
+ rawData: true,
27
+ ...config,
28
+ throwOnError: config.throwOnError ?? true,
29
+ })
30
+ }
31
+ ```
32
+
33
+ ```typescript [src/client.ts]
34
+ export type SuccessOf<TResponses> = TResponses[Extract<keyof TResponses, '200' | '201' | '202' | '203' | '204' | '205' | '206' | '207' | '208' | '226' | '2xx'>]
35
+
36
+ export type ServerSentEvent<TData = unknown> = { data: TData; event: string; id?: string; retry?: number }
37
+
38
+ export type TypedEvent<TEvent> = TEvent & Pick<ServerSentEvent, 'id' | 'retry'>
39
+
40
+ export type StreamResult<TResponses, ThrowOnError extends boolean = true, TEvent = ServerSentEvent<SuccessOf<TResponses>>> = {
41
+ stream: AsyncGenerator<TEvent>
42
+ response: Response
43
+ }
44
+
45
+ client.stream = async <TResponses, ThrowOnError extends boolean = true, TEvent = ServerSentEvent<SuccessOf<TResponses>>>(
46
+ config: RequestConfig,
47
+ ): Promise<StreamResult<TResponses, ThrowOnError, TEvent>> => {
48
+ // send the request, then parse the body into TEvent values
49
+ }
50
+ ```
51
+
52
+ ::
53
+
54
+ | Call | Must return |
55
+ | -------------------------------------------------------- | -------------------------------------------------------- |
56
+ | `client<TResponses, ThrowOnError>(config)` | `Promise<RequestResult<TResponses, ThrowOnError>>` |
57
+ | `client.stream<TResponses, ThrowOnError, TItem>(config)` | `Promise<StreamResult<TResponses, ThrowOnError, TItem>>` |
58
+
59
+ ## Export the stream types
60
+
61
+ Your module needs these on top of the [contract](https://kubb.dev/extensions/kubb-labs/plugin-client/guides/write-your-client#check-the-contract):
62
+
63
+ | Export | Kind | Needed when |
64
+ | ------------------------------- | ------------ | ---------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------- |
65
+ | `client.stream`, `StreamResult` | method, type | The spec has Server-Sent Events or JSON Lines operations. |
66
+ | `StreamResultWithHeaders` | type | A stream declares response headers. It replaces `StreamResult`. With `returnType: 'data'`, `UnwrappedStreamResultWithHeaders` replaces `UnwrappedStreamResult`. A stream that types only the payload also imports `SuccessOf` and `ServerSentEvent`. |
67
+ | `SuccessOf`, `TypedEvent` | type | A stream names each event (both) or streams JSON Lines (`SuccessOf`). |
68
+
69
+ ## Type each item
70
+
71
+ The third type argument, `TItem`, follows the document:
72
+
73
+ | The document | `TItem` |
74
+ | ---------------------- | ----------------------------------------------------------------------------------- |
75
+ | Names each event | `TypedEvent<SuccessOf<TResponses>>` |
76
+ | Streams JSON Lines | `SuccessOf<TResponses>` |
77
+ | Types only the payload | Left out. Give `TItem` a default, such as `ServerSentEvent<SuccessOf<TResponses>>`. |
78
+
79
+ ## Read the stream fields
80
+
81
+ Declare these fields on `RequestConfig`. The call passes them next to the [usual fields](https://kubb.dev/extensions/kubb-labs/plugin-client/guides/write-your-client#check-the-contract):
82
+
83
+ | Field | Passed when | Value |
84
+ | ------------- | ---------------------------------------------------------------- | ------------------------------------------------------------------------------ |
85
+ | `contentType` | The operation has a body, streams, or offers `text/event-stream` | `request` is the body's media type, `response` the one to ask for in `Accept`. |
86
+ | `eventShape` | A Server-Sent Events operation | `'event'` or `'data'`: what `validator.response` checks on each event. |
87
+ | `rawData` | A Server-Sent Events operation types `data` as a string | `true`, or the names of those events. Keep their `data` as text. |
88
+
89
+ ## Parse JSON Lines
90
+
91
+ A JSON Lines call, such as `application/jsonl` or `application/x-ndjson`, passes the media type in `contentType.response` and expects one parsed item per line.
92
+
93
+ The shared runtime's `sse.ts` exports both parsers: `parseEventStream` reads events and applies `rawData`, and `parseJsonLines` reads one item per line.
94
+
95
+ ## Streams with full or data return types
96
+
97
+ With [`returnType`](https://kubb.dev/extensions/kubb-labs/plugin-client/options#returntype) `'full'` or `'data'`, every operation imports helpers from your module, and streams import more types:
98
+
99
+ | `returnType` | Extra exports | Stream operations also import |
100
+ | ------------ | -------------------------------------------------------- | ---------------------------------------- |
101
+ | `'full'` | `withUnwrap`, `resolveThrowOnError`, `Unwrappable` | `StreamOptions`, `StreamResult` |
102
+ | `'data'` | `unwrapResult`, `resolveThrowOnError`, `UnwrappedResult` | `StreamOptions`, `UnwrappedStreamResult` |
103
+
104
+ `'full'` and `'data'` need a `StreamResult` with `data` and `error`, so the minimal type in [Add a stream method](#add-a-stream-method) is not enough. `@kubb/plugin-client/templates/core.ts` exports all of these.
105
+
106
+ Next, build the client [on the shared runtime](https://kubb.dev/extensions/kubb-labs/plugin-client/guides/write-your-client#write-the-client), which ships `client.stream`.
@@ -1,8 +1,12 @@
1
1
  # Validate requests and responses
2
2
 
3
- `@kubb/plugin-client` does not validate on its own. With `validator` set, it passes the schemas to your client, which runs them.
3
+ Pass Zod schemas from `@kubb/plugin-zod` to your client and run them there.
4
4
 
5
- ::code-group
5
+ ::steps{level="2"}
6
+
7
+ ## Pass the schemas
8
+
9
+ Add `pluginZod()` and set [`validator`](https://kubb.dev/extensions/kubb-labs/plugin-client/options#validator). Without `pluginZod()`, generation stops with an error.
6
10
 
7
11
  ```typescript [kubb.config.ts]
8
12
  import { defineConfig } from 'kubb/config'
@@ -21,9 +25,14 @@ export default defineConfig({
21
25
  })
22
26
  ```
23
27
 
24
- ::
28
+ | `validator` | Keys on `config.validator` |
29
+ | --------------------- | ------------------------------------- |
30
+ | `'zod'` | `response`, `error` |
31
+ | `{ response: 'zod' }` | `response`, `error` |
32
+ | `{ request: 'zod' }` | `request` |
33
+ | `{ params: 'zod' }` | `path`, `query`, `headers`, `cookies` |
25
34
 
26
- Each generated call now includes the schemas:
35
+ Each generated call now carries the schemas:
27
36
 
28
37
  ```typescript
29
38
  apiClient<AddPetResponses, ThrowOnError>({
@@ -34,9 +43,11 @@ apiClient<AddPetResponses, ThrowOnError>({
34
43
  })
35
44
  ```
36
45
 
37
- A `validator` passed on one call is merged per key, so `validator: { request }` adds a request schema and keeps the generated response schema. A per-call `error` replaces the generated error map as a whole.
46
+ A `validator` passed on one call merges per key: `{ request }` adds a request schema and keeps the generated response schema. A per-call `error` replaces the whole error map.
38
47
 
39
- Add a `validator` field to your `RequestConfig`, then run the schemas in `client`. Zod schemas follow [Standard Schema](https://standardschema.dev), so `~standard.validate` works with any compatible library:
48
+ ## Run them in your client
49
+
50
+ Add a `validator` field to your `RequestConfig` and run each schema with `~standard.validate`, the [Standard Schema](https://standardschema.dev) method that Zod implements:
40
51
 
41
52
  ```typescript [src/client.ts]
42
53
  type Result = { value: unknown; issues?: undefined } | { issues: ReadonlyArray<unknown> }
@@ -53,32 +64,29 @@ if (result && 'issues' in result && result.issues) throw new Error('Response val
53
64
  const validatedBody = result && 'value' in result ? result.value : body
54
65
  ```
55
66
 
56
- Return `validatedBody` as `data`, so a schema that transforms the value takes effect. Without a response schema, `validatedBody` is the original `body`.
57
-
58
- `error` is keyed by status, such as `{ 404: schema, '5xx': schema, default: schema }`. Pick the exact status, then its range, then `default`, and skip validation when none matches.
67
+ Return `validatedBody` as `data`, so a schema that transforms the value takes effect.
59
68
 
60
- Set `validator: { request: 'zod' }` or `{ response: 'zod' }` to pass one direction only. Without `pluginZod()` in the plugins list, generation stops with an error.
69
+ `error` is keyed by status, such as `{ 404: schema, '5xx': schema, default: schema }`. Try the exact status, then its range, then `default`. If none matches, skip validation.
61
70
 
62
71
  ## Validate params
63
72
 
64
- With `validator: { params: 'zod' }`, each call also passes the schemas for its path, query, header, and cookie params:
73
+ With `validator: { params: 'zod' }`, each call also passes its path, query, header, and cookie schemas:
65
74
 
66
75
  ```typescript
67
- apiClient<PutEventResponses, ThrowOnError>({
68
- method: 'PUT',
69
- url: '/events/{day}',
70
- ...config,
71
- validator: {
72
- path: putEventPathSchema,
73
- query: putEventQuerySchema,
74
- headers: toHeadersStandardSchema(putEventHeadersSchema, putEventHeadersSchema.shape),
75
- ...config.validator,
76
- },
77
- })
76
+ validator: {
77
+ path: putEventPathSchema,
78
+ query: putEventQuerySchema,
79
+ headers: toHeadersStandardSchema(putEventHeadersSchema, putEventHeadersSchema.shape),
80
+ ...config.validator,
81
+ }
78
82
  ```
79
83
 
80
- Add `path`, `query`, `headers`, and `cookies` to the `validator` field of your `RequestConfig`. Run them on `config.path`, `config.query`, `config.headers`, and `config.cookies` before you build the URL and headers, and send the values they return.
84
+ 1. Add `path`, `query`, `headers`, and `cookies` to the `validator` field of your `RequestConfig`.
85
+ 2. Run each on `config.path`, `config.query`, `config.headers`, or `config.cookies` before you build the URL and headers.
86
+ 3. Send the values they return. The path and query schemas strip keys the spec does not declare.
87
+ 4. Export `toHeadersStandardSchema` from your `importPath` module. Copy it from `@kubb/plugin-client/templates/standardSchema.ts`.
81
88
 
82
- The path and query schemas strip keys the spec does not declare, so only declared params are sent. Headers your client adds itself, such as `Authorization`, are not declared, so the headers schema passes undeclared keys through.
89
+ > [!WARNING]
90
+ > If your `toHeadersStandardSchema` returns the schema unchanged, it strips undeclared headers, such as an `Authorization` header your client adds. The template keeps them and matches declared headers in any casing.
83
91
 
84
- The generated call imports `toHeadersStandardSchema` from your `importPath` module, so that module must export it, or the generated code does not compile. `toHeadersStandardSchema` takes a Standard Schema and the object's declared keys, and returns a Standard Schema that matches the declared headers in any casing and passes undeclared headers through. Copy it from the `.kubb/standardSchema.ts` file that `@kubb/plugin-fetch` generates. Returning the schema unchanged is not enough: the plain object schema strips undeclared headers such as `Authorization`.
92
+ ::
@@ -1,48 +1,45 @@
1
1
  # Write your client
2
2
 
3
- The generated functions import your module through [`importPath`](https://kubb.dev/extensions/kubb-labs/plugin-client/options#importpath). That module is the only runtime code the generated functions call, and you own it.
3
+ Build the client module that every generated function imports through `importPath`. You own its transport, auth, and error handling.
4
4
 
5
- ## Required exports
5
+ ::steps{level="2"}
6
6
 
7
- | Export | Kind | Purpose |
8
- | --------------- | -------- | -------------------------------------------------------------------------------------------- |
9
- | `client` | function | Sends the request. Every generated function calls it, unless a call passes its own `client`. |
10
- | `Options` | type | The grouped options object a generated function accepts. |
11
- | `RequestResult` | type | What a generated function resolves to. |
7
+ ## Check the contract
12
8
 
13
- A generated function calls `client<TResponses, ThrowOnError>(config)`, so type `client` to return `Promise<RequestResult<TResponses, ThrowOnError>>`.
9
+ Export the names the generated files import:
14
10
 
15
- If your spec has server-sent event or JSON Lines operations, `client` also needs a `stream` method, and the module exports the `StreamResult`, `SuccessOf`, `TypedEvent`, and `ServerSentEvent` types. A generated function calls `client.stream<TResponses, ThrowOnError, TItem>(config)` and returns `Promise<StreamResult<TResponses, ThrowOnError, TItem>>`. `TItem` is `TypedEvent<…>` when the document names each event and the success type for JSON Lines. When the document types only the payload, `TItem` is left out and defaults to `ServerSentEvent<SuccessOf<TResponses>>`. A Server-Sent Events call passes `eventShape: 'event'` or `'data'` to say whether `config.validator.response` checks the whole event or its `data`.
11
+ | Export | Needed when |
12
+ | -------------------------------------------------------- | ------------------------------------------------ |
13
+ | `client` function, `Options` type | Always. |
14
+ | `RequestResult` type | `returnType` is `'plain'` (default) or `'full'`. |
15
+ | `withUnwrap`, `resolveThrowOnError`, `Unwrappable` | `returnType: 'full'`. |
16
+ | `unwrapResult`, `resolveThrowOnError`, `UnwrappedResult` | `returnType: 'data'`. |
17
+ | `toHeadersStandardSchema` function | `validator: { params: 'zod' }`. |
18
+ | `RequestConfig`, `ResponseErrorConfig` types | A query plugin calls the generated functions. |
16
19
 
17
- The query plugins (`@kubb/plugin-react-query`, `@kubb/plugin-vue-query`, `@kubb/plugin-swr`) and `@kubb/plugin-mcp` call the generated functions with `throwOnError: true` and read `data` off the result. The query plugins also import two more types from your module:
20
+ A generated function calls `client<TResponses, ThrowOnError>(config)`, which must return `Promise<RequestResult<TResponses, ThrowOnError>>`.
18
21
 
19
- | Export | Kind | Purpose |
20
- | --------------------- | ---- | ----------------------------------------------------------------------- |
21
- | `RequestConfig` | type | The per-call `client` options a hook accepts, minus the request fields. |
22
- | `ResponseErrorConfig` | type | The error type of a failed hook, given the spec's error type. |
22
+ > [!NOTE]
23
+ > The query plugins and `@kubb/plugin-mcp` call with `throwOnError: true` and read `data` off the result, so `RequestResult` needs a `data` field.
23
24
 
24
- With a relative `importPath`, the hooks resolve it from the generated operation file, so one path serves both.
25
+ Declare each `config` field your spec produces on `RequestConfig`, or the generated files fail to compile:
25
26
 
26
- ## Build on the shared runtime
27
+ | Field | Passed when | Value |
28
+ | --------------------------------------------- | ---------------------------------------------------------------------- | ---------------------------------------------------------------------------------------------------------------------------------- |
29
+ | `method`, `url`, `throwOnError` | Always | The upper-case method, the path template such as `'/pet/{petId}'`, and the caller's `throwOnError` or the default. |
30
+ | `path`, `query`, `headers`, `cookies`, `body` | The caller passes them | Typed from the spec. `cookies` holds name and value pairs. |
31
+ | `baseURL` | The path or operation declares its own `servers` | That server's URL. Use it when set. |
32
+ | `security` | The operation has security requirements | See [Authenticate](#authenticate). |
33
+ | `styles` | A parameter sets `style`, `explode`, `allowReserved`, or `contentType` | See [Serialize parameters](#serialize-parameters). |
34
+ | `contentType` | The operation has a body or streams | `request` is the body's media type, `response` the `Accept` value. |
35
+ | `parseAs` | Every success content type is binary | `'blob'` |
36
+ | `validator` | The `validator` option is set | See [Validate requests and responses](https://kubb.dev/extensions/kubb-labs/plugin-client/guides/validate-requests-and-responses). |
27
37
 
28
- To reuse the runtime of the fetch and axios clients instead of writing your own request pipeline:
38
+ A spec with Server-Sent Events or JSON Lines operations needs `client.stream` and more exports and fields. See [Stream Server-Sent Events and JSON Lines](https://kubb.dev/extensions/kubb-labs/plugin-client/guides/streaming).
29
39
 
30
- 1. Copy `core.ts`, `mediaTypes.ts`, `serializers.ts`, `standardSchema.ts`, `sse.ts`, and the `mediaTypes/` and `serializers/` files your spec needs from `@kubb/plugin-client/templates` next to your `client.ts`.
31
- 2. In `client.ts`, export the three types the core imports, listed below.
32
- 3. Write a `Transport` function that sends a `ResolvedRequest` and returns a web `Response`. If your library does not return one, wrap its bytes in `new Response(bytes, { status, headers })`.
33
- 4. Export `createLazyClient((config) => createClientCore({ defaultTransport, ...config }))` as `client`. Register the form and stream media types your spec needs with `client.setConfig({ mediaTypes: { 'multipart/form-data': multipartFormData, ...streamMediaTypes } })`, and add `export * from './core.ts'` and `export * from './sse.ts'`. The core has no serializers of its own, so also set `serializer: { query: serializeQuery, path: serializePath }` from `serializers/query.ts` and `serializers/path.ts`, or their styled versions, and `header` and `cookie` for a spec with such parameters.
34
-
35
- | Export | Purpose | On fetch | On axios |
36
- | ---------------- | ------------------------------------------------------- | ------------- | -------------------- |
37
- | `NativeRequest` | The request object on a result and a `ResponseError`. | `Request` | `AxiosRequestConfig` |
38
- | `NativeResponse` | The response object on a result and a `ResponseError`. | `Response` | `AxiosResponse` |
39
- | `NativeOptions` | The `options` a call passes to your HTTP library as is. | `RequestInit` | `AxiosRequestConfig` |
40
-
41
- [`@kubb/plugin-fetch/templates/client.ts`](https://github.com/kubb-labs/plugins/blob/main/packages/plugin-fetch/templates/client.ts) is a complete example.
42
-
43
- ## What a generated function looks like
40
+ Other keys the caller passes, such as `signal`, arrive through `...config`. A generated function looks like this:
44
41
 
45
- ```typescript
42
+ ```typescript [src/gen/clients/pet/getPetById.ts]
46
43
  import type { Options, RequestResult } from '../../../client'
47
44
  import type { GetPetByIdOptions, GetPetByIdResponses } from '../../types/GetPetById'
48
45
  import { client } from '../../../client'
@@ -62,75 +59,15 @@ export function getPetById<ThrowOnError extends boolean = true>(
62
59
  }
63
60
  ```
64
61
 
65
- Your `client` receives `method`, the `url` template with `{param}` placeholders, `security`, the grouped `path`, `query`, `headers`, `cookies`, and `body`, and `throwOnError`. It returns a promise. `cookies` holds name and value pairs, which your client joins into a `Cookie` header, as the example below does. A `security` entry is one scheme, or an array of schemes to send together.
66
-
67
- Read the body by the response `Content-Type`. An operation with only binary success content also passes `parseAs: 'blob'`.
68
-
69
- A call can also pass `contentType`: `request` is the body's media type, and `response` is the media type to ask for in `Accept`. A streamed operation always passes its media type, such as `contentType: { response: 'text/event-stream' }` or `contentType: { response: 'application/jsonl' }`. A call whose response offers `text/event-stream` next to another content type passes `text/event-stream` too, unless the caller picks another one. `RequestConfig` needs the field.
70
-
71
- ## Serialize parameters
72
-
73
- When the spec sets `style`, `explode`, `allowReserved`, or `contentType` on a parameter, the generated call also passes `styles`, grouped by location and keyed by the parameter name:
74
-
75
- ```typescript
76
- styles: {
77
- path: { petId: { style: 'matrix', explode: true } },
78
- query: { tags: { style: 'pipeDelimited', explode: false } },
79
- }
80
- ```
81
-
82
- A parameter without an entry uses the default for its location. The shared runtime's `serializers.ts` applies these styles. A client you write from scratch declares `styles` on `RequestConfig` and applies the styles itself, or ignores them when the spec only uses the defaults.
83
-
84
- ### Query
85
-
86
- Query parameters default to the `form` style. Arrays explode into repeated keys unless the spec
87
- says otherwise, and `spaceDelimited`, `pipeDelimited`, and `deepObject` change how arrays and
88
- objects collapse.
62
+ ## Write the client
89
63
 
90
- | Style | `explode` | Input | Result |
91
- | ---------------- | --------- | ------------------- | ---------------- |
92
- | `form` (default) | `true` | `{ id: [3, 4, 5] }` | `id=3&id=4&id=5` |
93
- | `form` | `false` | `{ id: [3, 4, 5] }` | `id=3,4,5` |
94
- | `spaceDelimited` | `false` | `{ id: [3, 4, 5] }` | `id=3%204%205` |
95
- | `pipeDelimited` | `false` | `{ id: [3, 4, 5] }` | `id=3\|4\|5` |
96
- | `deepObject` | `n/a` | `{ a: { b: 1 } }` | `a%5Bb%5D=1` |
64
+ Write the module from scratch, or build it on the runtime that `@kubb/plugin-fetch` and `@kubb/plugin-axios` use.
97
65
 
98
- With `explode: true`, `spaceDelimited` and `pipeDelimited` fall back to repeated keys like `form`,
99
- so the delimiter only shows with `explode: false`.
66
+ :::tabs
100
67
 
101
- ### Path
68
+ ::::tabs-item{label="From scratch"}
102
69
 
103
- Path parameters default to the `simple` style, which emits the bare value. `label` prefixes a
104
- `.` and `matrix` prefixes a `;name=` segment. The results below are the serialized segment for a
105
- parameter named `id`.
106
-
107
- | Style | `explode` | Input | Result |
108
- | ------------------ | --------- | ---------------- | ----------------- |
109
- | `simple` (default) | `false` | `[3, 4, 5]` | `3,4,5` |
110
- | `label` | `true` | `[3, 4, 5]` | `.3.4.5` |
111
- | `matrix` | `true` | `[3, 4, 5]` | `;id=3;id=4;id=5` |
112
- | `simple` | `false` | `{ x: 1, y: 2 }` | `x,1,y,2` |
113
-
114
- ### Header and cookie
115
-
116
- Header parameters use the `simple` style and cookie parameters use the `form` style, so the metadata
117
- for these locations carries only `explode`, plus `style: 'cookie'` for a cookie parameter with the
118
- OpenAPI 3.2 `cookie` style. Header values are sent as-is. Cookie values are URL-encoded into a single
119
- `Cookie` header. The `cookie` style sends them unencoded, as RFC 6265 describes, defaults `explode` to
120
- `true`, and throws on a value with a character such as `;` or a space, or on an exploded object key that is not a valid
121
- cookie name, such as `a=b`.
122
-
123
- | Location | `explode` | Input | Result |
124
- | ---------------------- | --------- | --------------------------------- | ------------------------- |
125
- | header | `false` | `[3, 4]` | `X-Ids: 3,4` |
126
- | header | `true` | `{ role: 'admin' }` | `X-Filter: role=admin` |
127
- | cookie | `false` | `{ session: 'abc', ids: [1, 2] }` | `session=abc; ids=1,2` |
128
- | cookie | `true` | `{ ids: [1, 2] }` | `ids=1; ids=2` |
129
- | cookie, `cookie` style | default | `{ sid: 'a%20b', ids: [1, 2] }` | `sid=a%20b; ids=1; ids=2` |
130
-
131
- ## A minimal client
132
-
133
- The client below uses `fetch`. Replace the transport with the HTTP library you use.
70
+ This client uses `fetch` and reads JSON. Replace `send` with your HTTP library.
134
71
 
135
72
  ```typescript [src/client.ts]
136
73
  export type Auth = {
@@ -180,17 +117,28 @@ export const settings = { baseURL: 'https://petstore3.swagger.io/api/v3' }
180
117
  async function send(config: RequestConfig) {
181
118
  const path: Record<string, unknown> = { ...config.path }
182
119
  const url = new URL((config.baseURL ?? settings.baseURL) + config.url.replace(/\{(\w+)\}/g, (_, key) => encodeURIComponent(String(path[key]))))
120
+ // The default `form` style repeats the key for each array item and sends each object property as its own parameter. Other styles: see Serialize parameters.
121
+ for (const [key, value] of Object.entries(config.query ?? {})) {
122
+ const isObject = typeof value === 'object' && value !== null && !Array.isArray(value) && !(value instanceof Date)
123
+ for (const [name, entry] of isObject ? Object.entries(value) : [[key, value]]) {
124
+ for (const item of Array.isArray(entry) ? entry : [entry]) {
125
+ if (item !== undefined) url.searchParams.append(name, String(item))
126
+ }
127
+ }
128
+ }
183
129
  const cookie = Object.entries(config.cookies ?? {})
184
130
  .filter(([, value]) => value != null)
185
131
  .map(([name, value]) => `${name}=${encodeURIComponent(String(value))}`)
186
132
  .join('; ')
133
+ // `Headers` matches names in any case, so a caller's `content-type` or `authorization` still wins.
134
+ const headers = new Headers(config.headers as Record<string, string>)
135
+ if (config.body !== undefined && !headers.has('Content-Type')) headers.set('Content-Type', 'application/json')
136
+ // On the server this adds to the caller's cookies. Browsers drop a script-set `Cookie` header and send their own.
137
+ if (cookie) headers.set('Cookie', [headers.get('Cookie'), cookie].filter(Boolean).join('; '))
187
138
 
188
139
  const response = await fetch(url, {
189
140
  method: config.method,
190
- headers: {
191
- ...(config.headers as Record<string, string>),
192
- ...(cookie ? { Cookie: cookie } : {}),
193
- },
141
+ headers,
194
142
  body: config.body === undefined ? undefined : JSON.stringify(config.body),
195
143
  signal: config.signal,
196
144
  })
@@ -206,7 +154,127 @@ export const client = <TResponses, ThrowOnError extends boolean = true>(config:
206
154
  send(config) as Promise<RequestResult<TResponses, ThrowOnError>>
207
155
  ```
208
156
 
209
- The [example project](https://github.com/kubb-labs/plugins/tree/main/examples/client) has a fuller version with a base URL setter, query handling, and an async auth hook. The types in this sketch are minimal. Add status-aware response types when you need them.
157
+ The [example project](https://github.com/kubb-labs/plugins/tree/main/examples/client) adds a base URL setter and an async auth hook.
158
+
159
+ ::::
160
+
161
+ ::::tabs-item{label="On the shared runtime"}
162
+
163
+ Reuse the request pipeline of the fetch and axios clients and write only the transport:
164
+
165
+ 1. Copy `core.ts`, `mediaTypes.ts`, `serializers.ts`, `standardSchema.ts`, `sse.ts`, and the `mediaTypes/` and `serializers/` files your spec needs from `@kubb/plugin-client/templates` next to your `client.ts`.
166
+ 2. In `client.ts`, export the `NativeRequest`, `NativeResponse`, and `NativeOptions` types the core imports. On fetch they are `Request`, `Response`, and `RequestInit`.
167
+ 3. Write a `Transport` function that sends a `ResolvedRequest` and returns a web `Response`. Wrap other results in `new Response(bytes, { status, headers })`. For a 204, 205, or 304 status, pass `null` as the body, or the constructor throws.
168
+ 4. Export `createLazyClient((config) => createClientCore({ defaultTransport, ...config }))` as `client`, and add `export * from './core.ts'` and `export * from './sse.ts'`.
169
+ 5. Register what the core does not ship: `client.setConfig({ mediaTypes: { 'multipart/form-data': multipartFormData, ...streamMediaTypes }, serializer: { query: serializeQuery, path: serializePath } })`. Add `header` and `cookie` serializers when the spec has such parameters.
170
+
171
+ [`@kubb/plugin-fetch/templates/client.ts`](https://github.com/kubb-labs/plugins/blob/main/packages/plugin-fetch/templates/client.ts) is a complete example.
172
+
173
+ ::::
174
+
175
+ :::
176
+
177
+ ## Authenticate
178
+
179
+ The plugin sends no credentials. Each call passes the operation's security requirements as `security`, and your client decides what to send:
180
+
181
+ ```typescript
182
+ security: [{ type: 'apiKey', name: 'api_key', in: 'header' }, { type: 'oauth2' }]
183
+ ```
184
+
185
+ | Shape | Meaning |
186
+ | -------------------------------------------- | -------------------------------------------------------------------------------------------------------- |
187
+ | No `security` field | The operation is public. Skip auth. |
188
+ | One entry per requirement | Meeting one requirement is enough. |
189
+ | An array inside the list | One requirement needs these schemes together, such as an API key and a session cookie. Send all of them. |
190
+ | `type: 'http'` with `scheme` | `'bearer'` or `'basic'`. Other HTTP schemes arrive as `'bearer'`. |
191
+ | `type: 'oauth2'` with `flows`, `metadataUrl` | The flow URLs to get a token. |
192
+ | `type: 'openIdConnect'` with `url` | The discovery URL. |
193
+
194
+ Await a token before the request, so it can come from a secrets store or a refresh call:
195
+
196
+ :::code-group
197
+
198
+ ```typescript [src/client.ts]
199
+ type GetToken = () => Promise<string | undefined>
200
+
201
+ let getToken: GetToken | undefined
202
+
203
+ export function setAuth(resolve: GetToken) {
204
+ getToken = resolve
205
+ }
206
+
207
+ // inside send(), after `headers` and before fetch(). This sends bearer tokens only, not API keys or basic credentials.
208
+ const bearer = config.security?.flat().some((auth) => auth.type !== 'apiKey' && auth.scheme !== 'basic')
209
+ const token = bearer ? await getToken?.() : undefined
210
+ if (token && !headers.has('Authorization')) headers.set('Authorization', `Bearer ${token}`)
211
+ ```
212
+
213
+ ```typescript [src/index.ts]
214
+ import { setAuth } from './client'
215
+
216
+ setAuth(async () => readTokenFromSecretsStore())
217
+ ```
218
+
219
+ :::
220
+
221
+ For request signing, such as AWS SigV4, pass a different `client` on the call:
222
+
223
+ ```typescript
224
+ await getPetById({ path: { petId: 1 }, client: createSignedClient() })
225
+ ```
226
+
227
+ ## Serialize parameters
228
+
229
+ When the spec sets `style`, `explode`, `allowReserved`, or `contentType` on a parameter, the call passes `styles`, grouped by location and keyed by parameter name:
230
+
231
+ ```typescript
232
+ styles: {
233
+ path: { petId: { style: 'matrix', explode: true } },
234
+ query: { tags: { style: 'pipeDelimited', explode: false } },
235
+ }
236
+ ```
237
+
238
+ - A parameter without an entry uses its location's default: `form` for query and cookie, `simple` for path and header.
239
+ - The shared runtime's `serializers.ts` applies every style.
240
+ - A client written from scratch applies them itself, or ignores `styles` when the spec only uses the defaults.
241
+ - See [Cookie parameters with the cookie style](#cookie-parameters-with-the-cookie-style) for the OpenAPI 3.2 `cookie` style.
242
+
243
+ The [plugin-fetch serialization guide](https://kubb.dev/extensions/kubb-labs/plugin-fetch/guides/serialization) shows the output of each style.
244
+
245
+ ## Register it
246
+
247
+ Point `importPath` at the module, resolved from the generated files:
248
+
249
+ ```typescript [kubb.config.ts]
250
+ import { defineConfig } from 'kubb/config'
251
+ import { pluginTs } from '@kubb/plugin-ts'
252
+ import { pluginClient } from '@kubb/plugin-client'
253
+
254
+ export default defineConfig({
255
+ input: './petStore.yaml',
256
+ output: { path: './src/gen' },
257
+ plugins: [
258
+ pluginTs(),
259
+ pluginClient({ group: { type: 'tag' }, importPath: '../../../client' }),
260
+ ],
261
+ })
262
+ ```
263
+
264
+ Run `npx kubb generate`, then `npx tsc --noEmit`. A missing export or `RequestConfig` field shows up as a compile error in the generated files. With a relative `importPath`, the query plugins resolve it from the same generated file, so one path serves both.
265
+
266
+ ::
267
+
268
+ ## Less common cases
269
+
270
+ ### Cookie parameters with the cookie style
271
+
272
+ When a cookie parameter sets the OpenAPI 3.2 `cookie` style, the call passes `style: 'cookie'` in `styles`:
273
+
274
+ ```typescript
275
+ styles: { cookie: { session: { style: 'cookie' } } }
276
+ ```
210
277
 
211
- > [!TIP]
212
- > Kubb does not depend on retries, interceptors, or request signing. Add them to `client` when you need them.
278
+ - Send its values without percent-encoding.
279
+ - OpenAPI 3.2 defaults `explode` to `true` for this style.
280
+ - The shared runtime throws a `TypeError` when a value has characters a cookie cannot carry.
package/docs/options.md CHANGED
@@ -4,25 +4,25 @@ Pass these options to `pluginClient()`. Shared options link to [Shared plugin op
4
4
 
5
5
  ## Options overview
6
6
 
7
- | Option | Purpose | Default |
8
- | ------------------------------------------------------------------------- | ---------------------------------------------------------------------- | ------------------------------------------------ |
9
- | [`importPath`](#importpath) | Import specifier of your client module. | Required |
10
- | [`output`](https://kubb.dev/docs/5.x/reference/plugin-options#output) | Where files are written: `path`, `mode`, `barrel`, `banner`, `footer`. | `{ path: 'clients', barrel: { type: 'named' } }` |
11
- | [`group`](https://kubb.dev/docs/5.x/reference/plugin-options#group) | Split output by tag or path: `type`, `name`. | None |
12
- | [`throwOnErrorDefault`](#throwonerrordefault) | Default `throwOnError` value passed to your client. | `true` |
13
- | [`validator`](#validator) | Pass Zod schemas to your client. | `false` |
14
- | [`returnType`](#returntype) | Shape of the value a generated operation resolves to. | `'plain'` |
15
- | [`include`](https://kubb.dev/docs/5.x/reference/plugin-options#include) | Keep only operations that match. | None |
16
- | [`exclude`](https://kubb.dev/docs/5.x/reference/plugin-options#exclude) | Skip operations that match. | `[]` |
17
- | [`override`](https://kubb.dev/docs/5.x/reference/plugin-options#override) | Apply different options per pattern. | `[]` |
18
- | [`resolver`](https://kubb.dev/docs/5.x/reference/plugin-options#resolver) | Customize generated names and file paths. | `resolverClient` |
19
- | [`macros`](https://kubb.dev/docs/5.x/reference/plugin-options#macros) | Rewrite AST nodes before printing. | `[]`, run after the built-in client macros |
7
+ | Option | Purpose | Default |
8
+ | ------------------------------------------------------------------------- | ----------------------------------------------------------------------------------- | ------------------------------------------------ |
9
+ | [`importPath`](#importpath) | Import specifier of your client module. | Required |
10
+ | [`returnType`](#returntype) | What a generated function resolves to. | `'plain'` |
11
+ | [`throwOnErrorDefault`](#throwonerrordefault) | `throwOnError` value each call passes to your client. | `true` |
12
+ | [`validator`](#validator) | Zod schemas to pass to your client. | `false` |
13
+ | [`output`](https://kubb.dev/docs/5.x/reference/plugin-options#output) | Where files are written. | `{ path: 'clients', barrel: { type: 'named' } }` |
14
+ | [`group`](https://kubb.dev/docs/5.x/reference/plugin-options#group) | Split output by tag or path. | None |
15
+ | [`include`](https://kubb.dev/docs/5.x/reference/plugin-options#include) | Keep only operations that match. | None |
16
+ | [`exclude`](https://kubb.dev/docs/5.x/reference/plugin-options#exclude) | Skip operations that match. | `[]` |
17
+ | [`override`](https://kubb.dev/docs/5.x/reference/plugin-options#override) | Apply different options per pattern, except `returnType` and `throwOnErrorDefault`. | `[]` |
18
+ | [`resolver`](https://kubb.dev/docs/5.x/reference/plugin-options#resolver) | Customize generated names and file paths. | `resolverClient` |
19
+ | [`macros`](https://kubb.dev/docs/5.x/reference/plugin-options#macros) | Rewrite AST nodes before printing. | `[]`, run after the built-in client macros |
20
20
 
21
21
  ## Option details
22
22
 
23
23
  ### importPath
24
24
 
25
- Import specifier of your client module. The plugin writes it into every generated import exactly as given, so it must resolve from the generated file. Use a relative path, a package name, or an alias.
25
+ Import specifier of your client module, written into every generated import as given. It must resolve from the generated file: use a relative path, a package name, or an alias.
26
26
 
27
27
  | | |
28
28
  | -------- | -------- |
@@ -30,35 +30,15 @@ Import specifier of your client module. The plugin writes it into every generate
30
30
  | Required | `true` |
31
31
 
32
32
  ```typescript
33
- pluginClient({ importPath: '../../../client' }) // relative to src/gen/clients/<tag>/
33
+ pluginClient({ importPath: '../../../client' }) // from src/gen/clients/<tag>/ to src/client.ts
34
34
  pluginClient({ importPath: '@my-org/api-client' }) // a package
35
35
  ```
36
36
 
37
- With `group: { type: 'tag' }` and `output.path: 'clients'`, a generated file sits in `src/gen/clients/<tag>/`, so `../../../client` points at `src/client.ts`. Without `group` it is `../../client`.
38
-
39
- The module must export `client`, plus the `Options` and `RequestResult` types. See [write your client](https://kubb.dev/extensions/kubb-labs/plugin-client/guides/write-your-client).
40
-
41
- ### throwOnErrorDefault
42
-
43
- Default for the `throwOnError` field that every generated function passes to your client. A call that sets `throwOnError` itself wins. Your client decides what the flag does: the [example client](https://kubb.dev/extensions/kubb-labs/plugin-client/guides/write-your-client) throws on a non-2xx status when it is `true` and returns the error as a value when it is `false`.
44
-
45
- | | |
46
- | -------- | --------- |
47
- | Type | `boolean` |
48
- | Required | `false` |
49
- | Default | `true` |
50
-
51
- The setting applies to the whole plugin, so you cannot set it in `override`.
37
+ Without `group`, files sit one folder higher, so use `../../client`. [Write your client](https://kubb.dev/extensions/kubb-labs/plugin-client/guides/write-your-client) lists the exports the module needs.
52
38
 
53
39
  ### returnType
54
40
 
55
- Shape of the value a generated operation resolves to.
56
-
57
- - `'plain'`: returns what your `client` returns.
58
- - `'full'`: wraps the call in `withUnwrap`, so the caller can `await` it or call `.unwrap()`. Your module must export `withUnwrap`, `resolveThrowOnError`, and the `Unwrappable` type.
59
- - `'data'`: passes the call through `unwrapResult` and resolves to the body. Your module must export `unwrapResult`, `resolveThrowOnError`, and the `UnwrappedResult` type.
60
-
61
- `@kubb/plugin-client/templates/core.ts` exports all of these, so your module can re-export them from there.
41
+ What a generated function resolves to.
62
42
 
63
43
  | | |
64
44
  | -------- | ----------------------------- |
@@ -66,18 +46,43 @@ Shape of the value a generated operation resolves to.
66
46
  | Required | `false` |
67
47
  | Default | `'plain'` |
68
48
 
49
+ ::field-group
50
+
51
+ :::field{name="'plain'"}
52
+ Default. Returns what your `client` returns.
53
+ :::
54
+
55
+ :::field{name="'full'"}
56
+ The caller can `await` the call or call `.unwrap()`. Your module must export `withUnwrap`, `resolveThrowOnError`, and the `Unwrappable` type.
57
+ :::
58
+
59
+ :::field{name="'data'"}
60
+ Resolves to the body when `throwOnError` is `true`, and to the full result when it is `false`. Your module must export `unwrapResult`, `resolveThrowOnError`, and the `UnwrappedResult` type.
61
+ :::
62
+
63
+ ::
64
+
65
+ You can re-export these helpers from `@kubb/plugin-client/templates/core.ts`.
66
+
69
67
  ```typescript
70
68
  pluginClient({ importPath: '../../client', returnType: 'full' })
69
+ // with integerType: 'number' on the adapter
71
70
  // const pet = await getPetById({ path: { petId: 1 } }).unwrap()
72
71
  ```
73
72
 
74
- The examples set [`integerType: 'number'`](https://kubb.dev/extensions/kubb-labs/adapter-oas/options#integertype) on the adapter, so an `int64` id such as `petId` is a `number`. With the default it is a `bigint` and the call takes `1n`.
73
+ ### throwOnErrorDefault
74
+
75
+ The `throwOnError` value every generated function passes to your client, unless the call sets its own. Your client decides what the flag does.
75
76
 
76
- The setting applies to the whole plugin, so you cannot set it in `override`.
77
+ | | |
78
+ | -------- | --------- |
79
+ | Type | `boolean` |
80
+ | Required | `false` |
81
+ | Default | `true` |
77
82
 
78
83
  ### validator
79
84
 
80
- Passes Zod schemas from `@kubb/plugin-zod` to your client on `config.validator`. The plugin does not run the schemas. Your client reads `config.validator` and runs them. See [validate requests and responses](https://kubb.dev/extensions/kubb-labs/plugin-client/guides/validate-requests-and-responses).
85
+ Passes Zod schemas from `@kubb/plugin-zod` to your client on `config.validator`. Your client runs them. See [Validate requests and responses](https://kubb.dev/extensions/kubb-labs/plugin-client/guides/validate-requests-and-responses).
81
86
 
82
87
  | | |
83
88
  | -------- | ------------------------------------------------------------------------- |
@@ -88,7 +93,7 @@ Passes Zod schemas from `@kubb/plugin-zod` to your client on `config.validator`.
88
93
  ::field-group
89
94
 
90
95
  :::field{name="false"}
91
- Default value. Passes nothing.
96
+ Default. Passes nothing.
92
97
  :::
93
98
 
94
99
  :::field{name="'zod'"}
@@ -96,12 +101,10 @@ Passes the response and error schemas.
96
101
  :::
97
102
 
98
103
  :::field{name="{ request?: 'zod', params?: 'zod', response?: 'zod' }"}
99
- Opts in per direction. `request` passes the request body schema, `params` the path, query, header, and cookie params schemas, and `response` the response and error schemas.
104
+ Opts in per slot: `request` for the body, `params` for the path, query, header, and cookie schemas, and `response` for the response and error schemas. `params` also imports `toHeadersStandardSchema` from your module.
100
105
  :::
101
106
 
102
107
  ::
103
108
 
104
109
  > [!IMPORTANT]
105
- > Add `pluginZod()` to the plugins list when `validator` is set. Generation stops with an error if it is missing.
106
-
107
- With `params: 'zod'`, generated calls also import `toHeadersStandardSchema` from your [`importPath`](#importpath) module, so that module must export it. See [validate requests and responses](https://kubb.dev/extensions/kubb-labs/plugin-client/guides/validate-requests-and-responses).
110
+ > Add `pluginZod()` to `plugins` when `validator` is set, or generation stops with an error.
package/docs/overview.md CHANGED
@@ -1,26 +1,32 @@
1
1
  # Client
2
2
 
3
- The Client plugin turns each operation in your OpenAPI spec into a typed function that calls a client module you write. The generated code has no Fetch, axios, or Kubb runtime dependency.
3
+ The Client plugin turns each OpenAPI operation into a typed function that calls your own client module, with no Fetch, axios, or Kubb runtime dependency.
4
4
 
5
- Each function takes grouped `path`, `query`, `headers`, and `body` parameters and passes the operation's auth requirements to your client in a `security` list. A single call can pass its own `client` to replace the default. For a ready-made client, use `@kubb/plugin-fetch` or `@kubb/plugin-axios` instead. The query plugins and `@kubb/plugin-mcp` work with this plugin too, and their hooks and handlers then call your client module.
5
+ - Each function takes grouped `path`, `query`, `headers`, `cookies`, and `body` parameters, and passes your client the operation's auth requirements as `security`.
6
+ - A call can pass its own `client` to replace the default.
7
+ - The query plugins and `@kubb/plugin-mcp` can call the generated functions.
8
+ - For a ready-made client, use [`@kubb/plugin-fetch`](https://kubb.dev/extensions/kubb-labs/plugin-fetch) or [`@kubb/plugin-axios`](https://kubb.dev/extensions/kubb-labs/plugin-axios) instead.
6
9
 
7
10
  ## Setup
8
11
 
9
- - Set the required `importPath` option to your client module. The module must export a generic `client` and the `Options` and `RequestResult` types. When the spec has server-sent event or JSON Lines operations, `client` also needs a `stream` method, and the module exports the `StreamResult`, `SuccessOf`, `TypedEvent`, and `ServerSentEvent` types.
12
+ - Set the required `importPath` to your client module.
10
13
  - Add `@kubb/plugin-ts` for the operation types, or `@kubb/plugin-zod` with `inferred: true`.
11
- - Add `@kubb/plugin-zod` when you set `validator`, and install `zod` v4. Generation fails without the plugin.
14
+ - When you set `validator`, add `@kubb/plugin-zod` and install `zod` v4. Generation fails without the plugin.
12
15
 
13
16
  ## Options worth knowing
14
17
 
15
- - `importPath` is written into every generated import as given, so it must resolve from the generated file. A relative path, a package name, or an alias works.
16
- - `validator: 'zod'` passes response schemas from `@kubb/plugin-zod` to your client. Use `{ request: 'zod', response: 'zod' }` to also pass request body schemas.
17
- - `throwOnErrorDefault` is the `throwOnError` value each call passes to your client. The default is `true`.
18
+ | Option | What it does |
19
+ | --------------------- | ------------------------------------------------------------------------------------------------------- |
20
+ | `importPath` | Your client module, resolved from each generated file. |
21
+ | `returnType` | `'plain'` returns what your `client` returns. `'full'` adds `.unwrap()`. `'data'` resolves to the body. |
22
+ | `validator` | Passes Zod schemas to your client. |
23
+ | `throwOnErrorDefault` | The `throwOnError` value each call passes. Defaults to `true`. |
18
24
 
19
- See [Options](https://kubb.dev/extensions/kubb-labs/plugin-client/options) for every option and its default.
25
+ See [Options](https://kubb.dev/extensions/kubb-labs/plugin-client/options) for every option. Next, [write your client](https://kubb.dev/extensions/kubb-labs/plugin-client/guides/write-your-client).
20
26
 
21
27
  ## Example
22
28
 
23
- The config below groups the generated functions by tag and points them at the client module in `src/client.ts`.
29
+ This config groups the functions by tag and points them at `src/client.ts`.
24
30
 
25
31
  ```typescript [kubb.config.ts]
26
32
  import { defineConfig } from 'kubb/config'
@@ -19,7 +19,7 @@
19
19
  ],
20
20
  "optionalDependencies": ["plugin-ts", "plugin-zod"],
21
21
  "compatibility": {
22
- "kubb": ">=5.18.4",
22
+ "kubb": ">=5.19.0",
23
23
  "node": ">=22.22"
24
24
  },
25
25
  "options": [
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@kubb/plugin-client",
3
- "version": "6.0.3",
3
+ "version": "6.0.4",
4
4
  "description": "Generate typed API operations against your own client module.",
5
5
  "keywords": [
6
6
  "api-client",
@@ -62,8 +62,8 @@
62
62
  },
63
63
  "dependencies": {
64
64
  "@kubb/kit": "^5.20.1",
65
- "@kubb/plugin-ts": "^5.5.3",
66
- "@kubb/plugin-zod": "^5.9.3",
65
+ "@kubb/plugin-ts": "^5.5.4",
66
+ "@kubb/plugin-zod": "^5.9.4",
67
67
  "@kubb/renderer-jsx": "^5.20.1"
68
68
  },
69
69
  "devDependencies": {
@@ -1,75 +0,0 @@
1
- # Authenticate
2
-
3
- Auth lives in your `client`. The plugin does not send credentials itself. It tells your client which schemes each operation needs, and your client decides what to do.
4
-
5
- ## The `security` list
6
-
7
- Each generated function passes the operation's security schemes from the spec as `security`:
8
-
9
- ```typescript
10
- apiClient<GetPetByIdResponses, ThrowOnError>({
11
- method: 'GET',
12
- url: '/pet/{petId}',
13
- security: [{ type: 'apiKey', name: 'api_key', in: 'header' }, { type: 'oauth2' }],
14
- ...config,
15
- })
16
- ```
17
-
18
- Each entry is one security requirement, and meeting one is enough. A requirement that needs several schemes together, such as an API key and a session cookie, is an array, and your client sends all of them: `security: [[{ type: 'apiKey', name: 'api_key', in: 'header' }, { type: 'apiKey', name: 'sid', in: 'cookie' }]]`.
19
-
20
- An operation without security requirements passes no `security` field. Use that to skip auth on public endpoints.
21
-
22
- An `oauth2` entry carries its `flows` with their URLs, and its `metadataUrl` when the spec sets `oauth2MetadataUrl`. An `openIdConnect` entry carries its discovery `url`. Your client can use them to run the flow, such as the OpenAPI 3.2 `deviceAuthorization` flow, and then send the token as a bearer token:
23
-
24
- ```typescript
25
- security: [{ type: 'oauth2', flows: { deviceAuthorization: { deviceAuthorizationUrl: 'https://auth.example.com/device', tokenUrl: 'https://auth.example.com/token' } }, scopes: ['read'] }]
26
- ```
27
-
28
- ## Add a bearer token from an async source
29
-
30
- The client can `await` anything before it sends the request. The client below awaits a token function you register with `setAuth`, so the token can come from a secrets store or a refresh call:
31
-
32
- ::code-group
33
-
34
- ```typescript [src/client.ts]
35
- type GetToken = () => Promise<string | undefined>
36
-
37
- let getToken: GetToken | undefined
38
-
39
- export function setAuth(resolve: GetToken) {
40
- getToken = resolve
41
- }
42
-
43
- export async function client(config: RequestConfig) {
44
- const token = config.security?.length ? await getToken?.() : undefined
45
-
46
- const response = await fetch(url, {
47
- method: config.method,
48
- headers: { ...(token ? { Authorization: `Bearer ${token}` } : {}), ...(config.headers as Record<string, string>) },
49
- // ...
50
- })
51
- // ...
52
- }
53
- ```
54
-
55
- ```typescript [src/index.ts]
56
- import { setAuth } from './client'
57
-
58
- setAuth(async () => readTokenFromSecretsStore())
59
- ```
60
-
61
- ::
62
-
63
- ## Sign requests or use several auth methods
64
-
65
- Read `config.security` and pick per operation, or pass a different `client` on a call. Both ways work for request signing, such as AWS SigV4, and for services that use different credentials:
66
-
67
- ```typescript
68
- import { createSignedClient } from './clients/iam'
69
-
70
- await getPetById({ path: { petId: 1 }, client: createSignedClient() })
71
- ```
72
-
73
- The examples set [`integerType: 'number'`](https://kubb.dev/extensions/kubb-labs/adapter-oas/options#integertype) on the adapter, so an `int64` id such as `petId` is a `number`. With the default it is a `bigint` and the call takes `1n`.
74
-
75
- A client passed on a call replaces the default for that call only.