@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.
- package/dist/index.cjs +11 -1771
- package/dist/index.cjs.map +1 -1
- package/dist/index.d.cts +3 -238
- package/dist/index.d.ts +3 -238
- package/dist/index.js +12 -1749
- package/dist/index.js.map +1 -1
- package/dist/templates/client.d.ts +47 -2
- package/docs/guides/authentication.md +14 -2
- package/docs/guides/base-url.md +44 -2
- package/docs/guides/calling-operations.md +4 -0
- package/docs/guides/error-handling.md +31 -8
- package/docs/guides/interceptors.md +34 -14
- package/docs/guides/migrate-to-v6.md +235 -0
- package/docs/guides/serialization.md +117 -57
- package/docs/guides/server-sent-events.md +129 -28
- package/docs/guides/transport.md +27 -19
- package/docs/options.md +14 -4
- package/docs/overview.md +1 -1
- package/kubb-extension.json +1 -1
- package/package.json +8 -10
- package/src/index.ts +0 -1
- package/src/plugin.ts +7 -74
- package/src/types.ts +2 -2
- package/templates/client.ts +65 -84
- package/dist/client-Dn80AjdR.d.ts +0 -39
- package/dist/core-D6FAGTsL.d.ts +0 -2
- package/dist/templates/core.d.ts +0 -2
- package/dist/templates/fetch.d.ts +0 -6
- package/dist/templates/serializers.d.ts +0 -1
- package/dist/templates/standardSchema.d.ts +0 -1
- package/src/generators/clientGenerator.tsx +0 -10
- package/src/templates.ts +0 -16
- package/templates/core.ts +0 -2
- package/templates/fetch.ts +0 -2
- package/templates/serializers.ts +0 -2
- package/templates/standardSchema.ts +0 -2
|
@@ -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
|
|
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`, `
|
|
19
|
+
`credentials`, `options`, `stream`).
|
|
20
20
|
|
|
21
21
|
## Run after the send
|
|
22
22
|
|
|
23
|
-
Response handlers run
|
|
24
|
-
`data` is the raw body. The handler receives a `
|
|
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.
|
|
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
|
-
|
|
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
|
-
|
|
47
|
-
|
|
48
|
-
|
|
49
|
-
|
|
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
|
|
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
|
|
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
|
-
##
|
|
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
|
-
|
|
39
|
+
### Header and cookie
|
|
8
40
|
|
|
9
|
-
|
|
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
|
|
23
|
-
|
|
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
|
-
|
|
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
|
-
|
|
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
|
-
|
|
40
|
-
|
|
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
|
-
|
|
43
|
-
|
|
44
|
-
`
|
|
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 {
|
|
112
|
+
import { XMLBuilder, XMLParser } from 'fast-xml-parser'
|
|
49
113
|
|
|
50
114
|
client.setConfig({
|
|
51
|
-
|
|
52
|
-
'application/
|
|
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
|
-
|
|
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
|
-
|
|
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
|
-
|
|
79
|
-
'
|
|
130
|
+
mediaTypes: {
|
|
131
|
+
'multipart/mixed': { stream: (body, { contentType }) => readParts(body, boundaryOf(contentType)) },
|
|
80
132
|
},
|
|
81
133
|
})
|
|
82
134
|
```
|
|
83
135
|
|
|
84
|
-
|
|
85
|
-
|
|
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
|
-
|
|
88
|
-
|
|
89
|
-
|
|
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
|
|
93
|
-
|
|
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 `
|
|
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
|
-
|
|
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
|
|
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`.
|