@kubb/plugin-fetch 5.14.0-canary.20261109T134631 → 6.0.0

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.
@@ -1,6 +1,6 @@
1
1
  # Add interceptors
2
2
 
3
- Register handlers on `client.interceptors.request`, `.response`, or `.error` to apply behavior across calls. Handlers receive the resolved request and result objects.
3
+ Register handlers on `client.interceptors.request`, `.response`, or `.error` to apply behavior across calls. Handlers receive the resolved request, the decoded result, or the error a call ends with.
4
4
 
5
5
  ## Run before the send
6
6
 
@@ -16,37 +16,47 @@ client.interceptors.request.use((request) => {
16
16
  ```
17
17
 
18
18
  The handler receives a `ResolvedRequest` (`url`, `method`, `headers`, `body`, `signal`,
19
- `credentials`, `options`, `responseType`).
19
+ `credentials`, `options`, `stream`).
20
20
 
21
21
  ## Run after the send
22
22
 
23
- Response handlers run before calls resolve, and before the status split and deserialization, so
24
- `data` is the raw body. The handler receives a `TransportResult` (`data`, `status`, `statusText`,
25
- `headers`, `contentType`, `request`, `response`):
23
+ Response handlers run after the body is decoded and before the status check and the validator, so a JSON body is
24
+ already parsed. On a `client.stream` call, `data` is the raw body stream. The handler receives a `DecodedResponse`
25
+ (`data`, `status`, `statusText`, `headers`, `contentType`, `request`, `response`):
26
26
 
27
27
  ```typescript
28
28
  client.interceptors.response.use((result) => {
29
- console.info(`${result.status} ${result.request.url}`)
29
+ console.info(`${result.status} ${result.contentType}`)
30
30
  return result
31
31
  })
32
32
  ```
33
33
 
34
34
  ## React to an error
35
35
 
36
- Use an error handler to react to HTTP errors on the throwing path. The handler receives the
37
- `ResponseError`:
36
+ Error handlers get every failure: a `ResponseError` for a non-2xx status, a network error, an abort, or a
37
+ `ParseError`. Narrow with `ResponseError.is`:
38
38
 
39
39
  ```typescript
40
+ import { client, ResponseError } from './gen/.kubb/client'
41
+
40
42
  client.interceptors.error.use((error) => {
41
- if (error.status === 401) scheduleTokenRefresh()
43
+ if (ResponseError.is(error) && error.status === 401) scheduleTokenRefresh()
44
+ if (error.name === 'AbortError') console.info('canceled')
42
45
  return error
43
46
  })
44
47
  ```
45
48
 
46
- This channel only fires on the throw path. When you read with `throwOnError: false`, a documented
47
- non-2xx response resolves and no error interceptor runs, so inspect the returned `error` on the
48
- result instead. A transport failure (no response) still throws. The error handler does not run,
49
- because there is no result to pass it.
49
+ Return another error, such as a domain error, and the call throws it instead. Each handler receives the error the one
50
+ before it returned. A handler that returns nothing keeps the error:
51
+
52
+ ```typescript
53
+ class NotFoundError extends Error {}
54
+
55
+ client.interceptors.error.use((error) => (ResponseError.is(error) && error.status === 404 ? new NotFoundError(error.message) : error))
56
+ ```
57
+
58
+ With `throwOnError: false`, a documented non-2xx response resolves and no error handler runs for it, so inspect the
59
+ returned `error` on the result. An error inside a stream loop throws from the loop and skips the error handlers.
50
60
 
51
61
  ## Add, replace, and remove handlers
52
62
 
@@ -69,4 +79,14 @@ client.interceptors.request.eject(id)
69
79
 
70
80
  ## Where to set them
71
81
 
72
- Register handlers on the shared `client` for all operations, or on a separate `createClient` instance for isolated calls. Interceptors are instance-level settings.
82
+ Register handlers on the shared `client` for all operations. `client.createClient(config)` returns an instance with the
83
+ same config and no handlers. `client.extend(config)` returns one that runs the parent's handlers, even those added
84
+ later, before its own:
85
+
86
+ ```typescript
87
+ import { client } from './gen/.kubb/client'
88
+
89
+ const admin = client.extend({ headers: { 'X-Role': 'admin' } })
90
+ admin.interceptors.request.use((request) => request) // runs after the handlers on client
91
+ await getPetById({ path: { petId: 1 }, client: admin })
92
+ ```
@@ -0,0 +1,235 @@
1
+ # Migrate to v6
2
+
3
+ `@kubb/plugin-fetch` 6 runs on the shared client core from `@kubb/plugin-client`, the same core as `@kubb/plugin-axios`. Code that only calls the generated functions, SDK classes, or hooks needs a regenerate and nothing else. Code that configures `client`, adds media types, streams, or writes a transport needs the edits below.
4
+
5
+ ## Upgrade steps
6
+
7
+ 1. Upgrade `kubb` and `@kubb/plugin-fetch` together. v6 needs Kubb 5.18.4 or later:
8
+
9
+ ```bash
10
+ npm install --save-dev kubb@latest @kubb/plugin-fetch@6
11
+ ```
12
+
13
+ 2. Regenerate. This rewrites `.kubb/` and the operation files:
14
+
15
+ ```bash
16
+ npx kubb generate
17
+ ```
18
+
19
+ 3. Run the type checker. Most breaking changes show up as compile errors in your own code:
20
+
21
+ ```bash
22
+ npx tsc --noEmit
23
+ ```
24
+
25
+ 4. Fix each error with its row in [What to change](https://kubb.dev/extensions/kubb-labs/plugin-fetch/guides/migrate-to-v6#what-to-change). Then read [Behavior changes](https://kubb.dev/extensions/kubb-labs/plugin-fetch/guides/migrate-to-v6#behavior-changes), which the type checker does not catch.
26
+
27
+ ## What to change
28
+
29
+ | Your code | What to do |
30
+ | ----------------------------------------------------------------------------------------------- | ------------------------------------------------------------------------------------------------------------------------------------------------------------------- |
31
+ | Calls generated functions, SDK methods, or hooks | Regenerate. Then check [Behavior changes](https://kubb.dev/extensions/kubb-labs/plugin-fetch/guides/migrate-to-v6#behavior-changes). |
32
+ | Imports from `@kubb/plugin-fetch/templates/fetch.ts` or `templates/core.ts` | Change the import path. See [Template imports](https://kubb.dev/extensions/kubb-labs/plugin-fetch/guides/migrate-to-v6#template-imports). |
33
+ | Uses `FetchOptions`, `defaultQuerySerializer`, `toEventStream`, or another removed export | Use the replacement. See [Renamed and removed exports](https://kubb.dev/extensions/kubb-labs/plugin-fetch/guides/migrate-to-v6#renamed-and-removed-exports). |
34
+ | Sets `codecs`, `serializer.body`, or `responseType` | Use `mediaTypes` and `parseAs`. See [Media types](https://kubb.dev/extensions/kubb-labs/plugin-fetch/guides/migrate-to-v6#media-types). |
35
+ | Streams Server-Sent Events, or names `EventStreamResult` | Use `client.stream` and `StreamResult`. See [Streams](https://kubb.dev/extensions/kubb-labs/plugin-fetch/guides/migrate-to-v6#streams). |
36
+ | Writes a `transport` | Return a web `Response`. See [Transports](https://kubb.dev/extensions/kubb-labs/plugin-fetch/guides/migrate-to-v6#transports). |
37
+ | Registers `client.interceptors.error` handlers | Narrow the error first. See [Error interceptors](https://kubb.dev/extensions/kubb-labs/plugin-fetch/guides/migrate-to-v6#error-interceptors). |
38
+ | 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-fetch/guides/migrate-to-v6#status-ranges). |
39
+ | Imports generated files by path, and groups by tag or has a schema and an operation of one name | Update the import paths. See [Generated file paths](https://kubb.dev/extensions/kubb-labs/plugin-fetch/guides/migrate-to-v6#generated-file-paths). |
40
+ | Uses SDK classes with `group: { type: 'tag' }`, such as `new pet(config)` | Use the new names, such as `new PetClient(config)`. See [SDK class names](https://kubb.dev/extensions/kubb-labs/plugin-fetch/guides/migrate-to-v6#sdk-class-names). |
41
+ | Comes from 5.12 or earlier and uses streams | Check [From 5.12 or earlier](https://kubb.dev/extensions/kubb-labs/plugin-fetch/guides/migrate-to-v6#from-5-12-or-earlier). |
42
+
43
+ ## Template imports
44
+
45
+ `templates/fetch.ts` is gone, and the package no longer publishes its own `templates/core.ts`, `templates/serializers.ts`, or `templates/standardSchema.ts`. Import the client types from `@kubb/plugin-fetch/templates/client.ts`, and the shared runtime from `@kubb/plugin-client/templates/*`:
46
+
47
+ ```typescript
48
+ // before
49
+ import type { RequestConfig } from '@kubb/plugin-fetch/templates/fetch.ts'
50
+ import type { ResolvedRequest } from '@kubb/plugin-fetch/templates/core.ts'
51
+
52
+ // after
53
+ import type { RequestConfig } from '@kubb/plugin-fetch/templates/client.ts'
54
+ import type { ResolvedRequest } from '@kubb/plugin-client/templates/core.ts'
55
+ ```
56
+
57
+ The package exports `templates/client.ts` for its types only. Runtime values such as `client` still come from the generated `.kubb/client.ts`.
58
+
59
+ ## Renamed and removed exports
60
+
61
+ | Before | After |
62
+ | ------------------------------------------------------------------ | ----------------------------------------------------------------------------------------------------------------------------------- |
63
+ | `FetchOptions` | `NativeOptions`, the same type: `RequestInit` plus Next.js's `next`. |
64
+ | `defaultQuerySerializer` | `serializeQuery`, or `serializeStyledQuery` for a styled query parameter or a urlencoded body |
65
+ | `defaultPathSerializer` | `serializePath`, plus `serializeStyledPath` for a `label` or `matrix` parameter |
66
+ | `applyHeaderStyles` | `serializeHeaders` |
67
+ | `toEventStream`, `EventStreamCall`, `EventStreamResult` | `client.stream` and `StreamResult`. See [Streams](https://kubb.dev/extensions/kubb-labs/plugin-fetch/guides/migrate-to-v6#streams). |
68
+ | `Codec`, `Deserializer`, `ContentBodySerializer`, `BodySerializer` | `MediaType`. See [Media types](https://kubb.dev/extensions/kubb-labs/plugin-fetch/guides/migrate-to-v6#media-types). |
69
+ | `defaultBodySerializer`, `isDefaultJsonBody` | A `mediaTypes` entry, or `defaultMediaTypes`. |
70
+ | `ResponseType`, `ResponseTypeMap`, `resolveResponseType` | `ParseAs` and `parseAs`. |
71
+ | `CallResult`, `isStreamed`, `getResponseContentType` | Removed. Read `result.contentType`. |
72
+ | `clientGenerator` from `@kubb/plugin-fetch` | Removed. Build on `setupClient` and `clientDependencies` from `@kubb/plugin-client`. |
73
+
74
+ The serializers now live in one file each under `.kubb/serializers/`, and Kubb writes a file only when the document needs it. `.kubb/config.ts` registers them, and `setConfig({ serializer })` merges per key, so setting `serializer.query` keeps the others. A client you build with `createClientCore` gets none, so pass them in its `serializer` config. `serializer.header` and `serializer.cookie` are new keys.
75
+
76
+ `ServerSentEvent`, `TypedEvent`, and `StreamResult` moved from `.kubb/sse.ts` to `.kubb/client.ts`.
77
+
78
+ ## Media types
79
+
80
+ `codecs` is now `mediaTypes`, and its methods are renamed:
81
+
82
+ | Before | After | Migration |
83
+ | --------------------------------------------------------------- | -------------------------------------------- | ------------------------------------------------------------------------------ |
84
+ | `codecs` | `mediaTypes` | Rename the key. |
85
+ | `codecs[type].serialize(body, contentType)` | `mediaTypes[type].encode(body, context)` | Read `context.contentType`. |
86
+ | `codecs[type].deserialize(raw, contentType)` with a parsed body | `mediaTypes[type].decode(response, context)` | Read the body yourself: `await response.text()`. |
87
+ | `serializer.body` | removed | Move a custom body serializer into a `mediaTypes` entry. |
88
+ | `responseType` on a call, a string or a map | removed | Use `parseAs`, such as `parseAs: 'blob'`, or a media type entry for that call. |
89
+
90
+ ```typescript
91
+ // before
92
+ client.setConfig({ codecs: { 'application/xml': { deserialize: (raw) => parseXml(raw as string) } } })
93
+
94
+ // after
95
+ client.setConfig({ mediaTypes: { 'application/xml': { decode: async (response) => parseXml(await response.text()) } } })
96
+ ```
97
+
98
+ A key can be a pattern, such as `application/*+json` or `text/*`. The form media types are written to `.kubb/mediaTypes/` as `multipartFormData` and `urlencoded`, only when the document has a form body. See [Configure serialization](https://kubb.dev/extensions/kubb-labs/plugin-fetch/guides/serialization).
99
+
100
+ ## Streams
101
+
102
+ Send a stream request with `client.stream`. Its result is already the stream:
103
+
104
+ ```typescript
105
+ // before
106
+ const { stream } = await toEventStream(client({ method: 'GET', url: '/events', responseType: 'stream' }), { shape: 'data' })
107
+
108
+ // after
109
+ const { stream } = await client.stream({ method: 'GET', url: '/events', eventShape: 'data' })
110
+ ```
111
+
112
+ `StreamResult` and `UnwrappedStreamResult` take the responses record and the full event type. Wrap a payload in `ServerSentEvent`. A stream typed with `TypedEvent<…>` stays the same:
113
+
114
+ ```typescript
115
+ // before
116
+ let result: EventStreamResult<PetEvent>
117
+
118
+ // after
119
+ let result: StreamResult<StreamPetsResponses, true, ServerSentEvent<PetEvent>>
120
+ ```
121
+
122
+ An operation whose only success content type is JSON Lines, such as `application/jsonl` or `application/x-ndjson`, now returns a `StreamResult` with one parsed item per line. It used to return the whole body as one string:
123
+
124
+ ```typescript
125
+ for await (const entry of await streamLogs()) {
126
+ console.info(entry.message) // LogEntry
127
+ }
128
+ ```
129
+
130
+ A stream response that declares headers returns `StreamResultWithHeaders`, or `UnwrappedStreamResultWithHeaders` with `returnType: 'data'`. See [Stream Server-Sent Events](https://kubb.dev/extensions/kubb-labs/plugin-fetch/guides/server-sent-events).
131
+
132
+ ## Transports
133
+
134
+ A transport returns a web `Response`, or `{ webResponse, request, response }` to also set the native objects. Build the `RequestInit` with `toRequestInit(request)`. The core decodes the body, and response interceptors receive a `DecodedResponse`, the shape `TransportResult` had:
135
+
136
+ ```typescript
137
+ // before
138
+ transport: async () => ({ data: { id: 1 }, status: 200, statusText: 'OK', headers: new Headers(), request, response })
139
+
140
+ // after
141
+ transport: async () => Response.json({ id: 1 })
142
+ ```
143
+
144
+ See [Use a custom transport](https://kubb.dev/extensions/kubb-labs/plugin-fetch/guides/transport).
145
+
146
+ ## Error interceptors
147
+
148
+ `client.interceptors.error` handlers now also get network errors, aborts, and validation errors. Narrow with `ResponseError.is` before you read `status`. Each handler gets the error the previous one returned:
149
+
150
+ ```typescript
151
+ client.interceptors.error.use((error) => {
152
+ if (ResponseError.is(error) && error.status === 401) refreshToken()
153
+ return error
154
+ })
155
+ ```
156
+
157
+ See [Handle errors](https://kubb.dev/extensions/kubb-labs/plugin-fetch/guides/error-handling).
158
+
159
+ ## Status ranges
160
+
161
+ 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:
162
+
163
+ ```diff
164
+ - import type { ListPetsStatus4XX } from './gen/types/ListPets'
165
+ + import type { ListPetsStatus4xx } from './gen/types/ListPets'
166
+ ```
167
+
168
+ ## Generated file paths
169
+
170
+ Two changes move generated files. The exported names stay the same:
171
+
172
+ - With `group: { type: 'tag' }`, a tag whose OpenAPI 3.2 `parent` is `store` writes to `store/payment/` instead of `payment/`. Pass `group.name` to keep one folder per tag: `pluginFetch({ group: { type: 'tag', name: ({ group }) => group } })`.
173
+ - An operation file gets an `Operation` suffix when a schema file has the same name, ignoring case. Operation `point` goes to `types/PointOperation.ts`, next to schema `Point`.
174
+
175
+ ```diff
176
+ - import { listPayments } from './gen/clients/payment/listPayments'
177
+ + import { listPayments } from './gen/clients/store/payment/listPayments'
178
+ ```
179
+
180
+ ## SDK class names
181
+
182
+ With `sdk` and `group: { type: 'tag' }`, tag classes are named like `PetClient` instead of `pet`. The files and the root class's `pet` and `store` fields stay the same:
183
+
184
+ ```diff
185
+ - import { pet } from './gen/sdk/pet/pet'
186
+ - const api = new pet({ baseURL })
187
+ + import { PetClient } from './gen/sdk/pet/pet'
188
+ + const api = new PetClient({ baseURL })
189
+ ```
190
+
191
+ The root class shares one client with every tag. Add interceptors on `api.client`.
192
+
193
+ ## Behavior changes
194
+
195
+ These compile without errors, so check them by hand.
196
+
197
+ Requests:
198
+
199
+ - A path or operation with its own `servers` sends its calls there instead of to the client's `baseURL`. Pass `baseURL` on the call to override it. See [Set the base URL](https://kubb.dev/extensions/kubb-labs/plugin-fetch/guides/base-url).
200
+ - An object query parameter without a `style` goes out as `name=odie&age=3`, not `filter[name]=odie`. Set `style: deepObject` in the document to keep the brackets.
201
+ - An array or object header without a `style` goes out as `X-Ids: 3,4`, not as JSON.
202
+ - A space in a query value goes out as `%20`, not `+`.
203
+ - `spaceDelimited` and `pipeDelimited` default `explode` to `false`, so an array goes out as `ids=1%7C2`.
204
+ - A `style: cookie` parameter goes out without percent-encoding. A value it cannot send, such as one with `;`, throws a `TypeError`.
205
+ - `body: null` goes out as the JSON document `null`.
206
+ - A stream call sends `Accept: text/event-stream`, or the JSON Lines media type.
207
+ - An auth requirement that needs several schemes sends all of them.
208
+
209
+ Responses:
210
+
211
+ - Invalid JSON under a JSON media type throws a `ParseError` instead of becoming a string. Catch it, or register a lenient `application/json` entry.
212
+ - An empty body is `undefined` for every media type, text included.
213
+ - A failed call without a body reports `{ status, statusText }` as `error`.
214
+ - `ResponseError.data` holds the validated body. Set `validator.error` to a map keyed by status, such as `{ 404: getPetStatus404Schema }`, to check an error body against the schema of its status.
215
+ - An aborted call or stream rejects with the signal's reason.
216
+ - Every result, and a thrown `ResponseError`, has the response's `Headers` as `headers`.
217
+
218
+ Types:
219
+
220
+ - A `2xx` response key is a success, and range keys such as `4xx` type `status` as the numbers they cover. An exact code such as `200` wins over a range listed before it.
221
+ - An XML, YAML, or CSV response is typed `string`. An operation that returns JSON and XML has `data: Pet | string`, so narrow on `result.contentType`.
222
+ - An image, audio, video, PDF, or `application/octet-stream` response is typed `Blob`, even when its schema says `type: string`.
223
+ - `result.contentType` is the bare media type, such as `'text/html'`.
224
+ - `server.index` no longer counts a server without a `url`. Servers with a `name` are exported as `servers` from `.kubb/client.ts`, for `client.setConfig({ baseURL: servers.staging })`.
225
+
226
+ ## From 5.12 or earlier
227
+
228
+ These changed in 5.13 and compile without errors:
229
+
230
+ - A successful stream call with a body that is not a stream, such as a JSON body, throws a `TypeError` with the body as its `cause`. A successful answer with no body still ends as an empty stream.
231
+ - An event the server ends without a blank line is dropped, as browser `EventSource` does, so end every event with one.
232
+
233
+ ## Query plugins
234
+
235
+ `@kubb/plugin-react-query`, `@kubb/plugin-vue-query`, `@kubb/plugin-swr`, and `@kubb/plugin-mcp` work with v5 and v6, so you can upgrade them separately. They skip hooks for JSON Lines operations, as they do for event streams.
@@ -1,12 +1,57 @@
1
1
  # Configure serialization
2
2
 
3
- The generated client encodes parameters using OpenAPI `style` and `explode`, serializes request bodies by content type, and decodes responses by media type. Each generated operation carries its parameter styles and content types, so most requests need no extra setup.
3
+ The generated client encodes parameters by their OpenAPI `style` and `explode`, and encodes and decodes bodies by their media type. Each generated operation carries its parameter styles and content types, so most requests need no extra setup.
4
4
 
5
- ## Override parameter serialization
5
+ ## Parameter styles
6
+
7
+ A parameter without `style` or `explode` in the spec uses the default for its location.
8
+
9
+ ### Query
10
+
11
+ Query parameters default to the `form` style. Arrays explode into repeated keys unless the spec
12
+ says otherwise, and `spaceDelimited`, `pipeDelimited`, and `deepObject` change how arrays and
13
+ objects collapse.
14
+
15
+ | Style | `explode` | Input | Result |
16
+ | ---------------- | --------- | ------------------- | ---------------- |
17
+ | `form` (default) | `true` | `{ id: [3, 4, 5] }` | `id=3&id=4&id=5` |
18
+ | `form` | `false` | `{ id: [3, 4, 5] }` | `id=3,4,5` |
19
+ | `spaceDelimited` | `false` | `{ id: [3, 4, 5] }` | `id=3%204%205` |
20
+ | `pipeDelimited` | `false` | `{ id: [3, 4, 5] }` | `id=3\|4\|5` |
21
+ | `deepObject` | `n/a` | `{ a: { b: 1 } }` | `a%5Bb%5D=1` |
22
+
23
+ With `explode: true`, `spaceDelimited` and `pipeDelimited` fall back to repeated keys like `form`,
24
+ so the delimiter only shows with `explode: false`.
25
+
26
+ ### Path
27
+
28
+ Path parameters default to the `simple` style, which emits the bare value. `label` prefixes a
29
+ `.` and `matrix` prefixes a `;name=` segment. The results below are the serialized segment for a
30
+ parameter named `id`.
31
+
32
+ | Style | `explode` | Input | Result |
33
+ | ------------------ | --------- | ---------------- | ----------------- |
34
+ | `simple` (default) | `false` | `[3, 4, 5]` | `3,4,5` |
35
+ | `label` | `true` | `[3, 4, 5]` | `.3.4.5` |
36
+ | `matrix` | `true` | `[3, 4, 5]` | `;id=3;id=4;id=5` |
37
+ | `simple` | `false` | `{ x: 1, y: 2 }` | `x,1,y,2` |
6
38
 
7
- Check the [default parameter styles and encoding](https://kubb.dev/docs/5.x/reference/serialization#parameter-styles) before replacing a serializer.
39
+ ### Header and cookie
8
40
 
9
- Override `serializer.query`, `.body`, or `.path` on the client. Omitted functions retain their defaults:
41
+ Header parameters use the `simple` style and cookie parameters use the `form` style. Both fix the
42
+ style and only let `explode` vary, so the metadata for these locations carries `explode` alone.
43
+ Header values are sent as-is, and cookie values are URL-encoded into a single `Cookie` header.
44
+
45
+ | Location | `explode` | Input | Result |
46
+ | -------- | --------- | --------------------------------- | ---------------------- |
47
+ | header | `false` | `[3, 4]` | `X-Ids: 3,4` |
48
+ | header | `true` | `{ role: 'admin' }` | `X-Filter: role=admin` |
49
+ | cookie | `false` | `{ session: 'abc', ids: [1, 2] }` | `session=abc; ids=1,2` |
50
+ | cookie | `true` | `{ ids: [1, 2] }` | `ids=1; ids=2` |
51
+
52
+ ## Override parameter serialization
53
+
54
+ To replace these defaults, override `serializer.query` or `serializer.path` on the client. Omitted functions keep their defaults:
10
55
 
11
56
  ```typescript
12
57
  import { client } from './gen/.kubb/client'
@@ -19,83 +64,102 @@ client.setConfig({
19
64
  })
20
65
  ```
21
66
 
22
- A serializer set this way runs for every call, but you can pass `serializer` on a single call to
23
- override only that request.
67
+ A serializer set this way runs for every call. Pass `serializer` on a single call to override only that request.
68
+
69
+ Kubb writes each serializer a spec needs into `.kubb/serializers/` and lists it in `.kubb/config.ts`, which `.kubb/client.ts` passes to `setConfig`. The core has no serializer of its own.
70
+
71
+ | Key in `serializer` | File | Written for |
72
+ | ------------------- | ------------------------------------------------------------------------------------ | ------------------------------ |
73
+ | `query` | `query.ts`, or `queryStyles.ts` for a spec with a styled query parameter | every spec |
74
+ | `path` | `path.ts`, plus `pathStyles.ts` for a spec with a `label` or `matrix` path parameter | every spec |
75
+ | `header` | `headers.ts` | a spec with a header parameter |
76
+ | `cookie` | `cookies.ts` | a spec with a cookie parameter |
77
+
78
+ A call that needs a serializer the spec did not register throws a `TypeError` that names the key, such as `serializer.cookie`. Set it on the client to send such a call. Without `serializer.header`, an array or object header value is sent as JSON.
79
+
80
+ ## Built-in media types
81
+
82
+ | Keys | Encodes | Decodes |
83
+ | ---------------------------------------------------------------------------------------- | ------------------------------------------------- | ------------------------------------------ |
84
+ | `application/json`, `application/*+json`, `text/json` | JSON | JSON |
85
+ | `text/*` | a string | text |
86
+ | `multipart/form-data`, `application/x-www-form-urlencoded` | `FormData` or `URLSearchParams` from an object | `FormData` |
87
+ | `application/octet-stream`, `application/pdf`, `image/*`, `audio/*`, `video/*` | a `Blob` as is | `Blob` |
88
+ | `text/event-stream`, `application/jsonl`, `application/x-ndjson`, `application/json-seq` | | text, or item by item with `client.stream` |
89
+ | `*/*` | JSON for objects, strings and binary bodies as is | JSON, or text when the body is not JSON |
90
+
91
+ `.kubb/client.ts` registers a form media type only when the document has a body of that type, and the stream media types only when it streams. `multipartFormData` is in `.kubb/mediaTypes/formData.ts` and `urlencoded` in `.kubb/mediaTypes/urlencoded.ts`. The stream media types are `streamMediaTypes` in `.kubb/sse.ts`.
92
+
93
+ A key can be a pattern. The runtime lower-cases the type, drops its parameters, and tries the exact type, then a suffix such as `application/*+json`, then a wildcard such as `text/*`, then `*/*`. Write your own keys in lower case.
24
94
 
25
95
  ## Encode request bodies
26
96
 
27
- The request content type decides how the body is encoded. The default serializer handles the
28
- common types: a plain object becomes JSON, `multipart/form-data` becomes `FormData`, and
29
- `application/x-www-form-urlencoded` becomes `URLSearchParams`. Binary and already-encoded bodies
30
- (`FormData`, `URLSearchParams`, `Blob`, `ArrayBuffer`, typed arrays, and strings) pass through
31
- untouched.
97
+ When an operation declares one request content type, Kubb sets it on the generated function, so you pass only the body. For an operation that accepts more than one, set `contentType` on the call. A body without a content type is sent as `application/json`, unless it is already encoded.
32
98
 
33
- When an operation declares a single request content type, Kubb sets it on the generated function,
34
- so you pass only the body. For an operation that accepts more than one, set `contentType` on the
35
- call.
99
+ Bodies that are already encoded (`FormData`, `URLSearchParams`, `Blob`, `ArrayBuffer`, typed arrays, streams, and strings) pass through every built-in media type as they are. A string under a JSON media type is JSON-encoded.
36
100
 
37
101
  > [!NOTE]
38
- > When the body is `FormData`, the runtime removes any `Content-Type` header so the transport sets
39
- > it with the multipart boundary. You do not need to set `multipart/form-data` yourself, and a
40
- > value you set is dropped for that request.
102
+ > When the body is `FormData`, the runtime removes any `Content-Type` header so the transport sets it with the multipart boundary.
103
+
104
+ An XML, YAML, or CSV media type has no built-in handler, so an object sent under one throws a `TypeError`. Pass the encoded body as a string, or register a media type. The generated body type is the schema, such as `Pet`, so a string body needs a cast: `body: xml as unknown as AddPetBodyXml`.
41
105
 
42
- To encode a content type the default serializer does not handle, register a codec for that media
43
- type. `codecs` is keyed by content type. Each entry takes a `serialize` for the request body, a
44
- `deserialize` for the response, or both:
106
+ ## Add a media type
107
+
108
+ Register a handler under its media type with `mediaTypes`. `encode` gets the body and a context with the full `contentType`, and returns what `fetch` can send. `decode` gets the `Response`, so a binary format can read bytes:
45
109
 
46
110
  ```typescript
47
111
  import { client } from './gen/.kubb/client'
48
- import { stringify } from 'yaml'
112
+ import { XMLBuilder, XMLParser } from 'fast-xml-parser'
49
113
 
50
114
  client.setConfig({
51
- codecs: {
52
- 'application/yaml': { serialize: (body) => stringify(body) },
115
+ mediaTypes: {
116
+ 'application/xml': {
117
+ encode: (body) => new XMLBuilder().build(body),
118
+ decode: async (response) => new XMLParser().parse(await response.text()),
119
+ },
53
120
  },
54
121
  })
55
122
  ```
56
123
 
57
- ## Decode responses
124
+ Handlers merge per key over the built-ins, over earlier `setConfig` calls, and, when passed on a single call, over the client ones. An entry with only `decode` still encodes through the next match, such as `*/*`.
58
125
 
59
- The runtime reads the response `Content-Type` and decodes the body by it: JSON is parsed, text
60
- stays a string, and a binary type becomes a `Blob`. The negotiated media type is on the result as
61
- `contentType`, so a `switch (result.contentType)` narrows `data` for an operation that returns
62
- more than one.
63
-
64
- When that success response declares several content types that do not share one parser, the
65
- generated call sets `responseType` to a map from content type to parser, for example
66
- `{ 'audio/mpeg': 'blob', 'text/event-stream': 'stream' }`. The runtime matches the response
67
- `Content-Type` against the map, including a `type/*` wildcard, before the media-type heuristic.
68
- A schema with `format: binary` is a blob even when the media type is not a well-known binary type.
69
-
70
- To decode a media type the runtime does not handle, register a codec's `deserialize` for it.
71
- `deserialize` receives the raw body and the content type and returns the parsed value. It runs
72
- before validation, so the schema checks the parsed value:
126
+ A streaming format adds `stream`, which reads the 2xx body item by item. The runtime validates each item:
73
127
 
74
128
  ```typescript
75
- import { client } from './gen/.kubb/client'
76
-
77
129
  client.setConfig({
78
- codecs: {
79
- 'application/xml': { deserialize: (raw) => new DOMParser().parseFromString(raw as string, 'application/xml') },
130
+ mediaTypes: {
131
+ 'multipart/mixed': { stream: (body, { contentType }) => readParts(body, boundaryOf(contentType)) },
80
132
  },
81
133
  })
82
134
  ```
83
135
 
84
- Like the other config, `codecs` can also be passed on a single call, and a per-call entry merges
85
- over the client one for that content type.
136
+ ## Decode responses
137
+
138
+ The runtime decodes each body by the response `Content-Type`. The media type is on the result as `contentType`, so a `switch (result.contentType)` narrows `data` for an operation that returns more than one.
139
+
140
+ - A 2xx response without a `Content-Type` is decoded as the media type the call asked for in `contentType.response`, then as `*/*`.
141
+ - A 204, 205, or 304 response, a `HEAD` request, and an empty body give `data: undefined`.
142
+ - Invalid JSON under a JSON media type throws a `ParseError`. Catch it, or register a lenient `application/json` handler.
143
+
144
+ To skip the lookup for one call, set `parseAs` to `'json'`, `'text'`, `'blob'`, `'arrayBuffer'`, or `'stream'`. It applies to a 2xx body only. Kubb sets `parseAs: 'blob'` on an operation with only binary success content:
86
145
 
87
- When the runtime picks the wrong parse mode because a response omits its `Content-Type` or sets a
88
- misleading one, force the mode with `responseType` on the call. It accepts `'json'`, `'text'`,
89
- `'blob'`, `'arraybuffer'`, and `'document'`:
146
+ ```typescript
147
+ const { data } = await getReport({ path: { id: '123' }, parseAs: 'text' })
148
+ ```
149
+
150
+ The generated types follow what the built-in media types return. A response read as text, such as `application/xml`, `application/yaml`, or `text/csv`, is typed `string`, even when the document gives it an object schema. A vendor type such as `application/vnd.github.object` keeps its schema type, because the client parses its JSON body. A media type you register changes the value but not that type, so narrow or cast `data` to what it returns:
90
151
 
91
152
  ```typescript
92
- const { data } = await downloadInvoice({ path: { id: '123' }, responseType: 'blob' })
93
- // data is a Blob even when the server leaves Content-Type unset
153
+ const result = await getPetById({ path: { petId: 1 }, contentType: { response: 'application/xml' } })
154
+ if (result.contentType === 'application/xml') {
155
+ const pet = result.data as unknown as Pet
156
+ }
94
157
  ```
95
158
 
96
- [Server-sent events](https://kubb.dev/extensions/kubb-labs/plugin-fetch/guides/server-sent-events) covers `responseType: 'stream'`.
159
+ [Server-sent events](https://kubb.dev/extensions/kubb-labs/plugin-fetch/guides/server-sent-events) covers stream media types and `client.stream`.
97
160
 
98
- For bidirectional formats, supply both `serialize` and `deserialize` in the media type codec, then set the operation's request and response `contentType`.
161
+ > [!NOTE]
162
+ > With the adapter's default [`integerType: 'bigint'`](https://kubb.dev/extensions/kubb-labs/adapter-oas/options#integertype), a `format: int64` field is typed `bigint`, but `JSON.parse` returns a `number`. Set `integerType: 'number'` on `adapterOas`, or turn on [`validator: 'zod'`](#validate-responses), which converts the field to `bigint`. Either way, `JSON.parse` loses precision above `Number.MAX_SAFE_INTEGER`, so declare an exact 64-bit ID as a string in the document.
99
163
 
100
164
  ## Validate responses
101
165
 
@@ -107,14 +171,10 @@ import { pluginFetch } from '@kubb/plugin-fetch'
107
171
  pluginFetch({ validator: 'zod' })
108
172
  ```
109
173
 
110
- `'zod'` validates the success response body, and the error body when a non-2xx call does not
111
- throw. Use the object form to opt in per direction, where `request` validates the request
112
- body before the call goes out:
174
+ `'zod'` validates the success response body and the error body. Use the object form to opt in per direction, where `request` validates the request body before the call goes out:
113
175
 
114
176
  ```typescript
115
177
  pluginFetch({ validator: { request: 'zod', response: 'zod' } })
116
178
  ```
117
179
 
118
- With a validator set, Kubb passes the matching schema to each generated call, and the runtime
119
- parses the body through it. A body that does not match throws a `ParseError` with the schema's
120
- `issues`.
180
+ With a validator set, Kubb passes the matching schema to each generated call, and the runtime parses the decoded body through it. A body that does not match throws a `ParseError` with the schema's `issues`.