@kubb/plugin-fetch 5.5.9 → 5.5.10
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/README.md +2 -2
- package/dist/index.cjs +4 -3
- package/dist/index.cjs.map +1 -1
- package/dist/index.js +5 -4
- package/dist/index.js.map +1 -1
- package/docs/guides/authentication.md +77 -0
- package/docs/guides/base-url.md +63 -0
- package/docs/guides/calling-operations.md +147 -0
- package/docs/guides/error-handling.md +124 -0
- package/docs/guides/interceptors.md +78 -0
- package/docs/guides/serialization.md +120 -0
- package/docs/guides/server-sent-events.md +99 -0
- package/docs/guides/transport.md +71 -0
- package/docs/options.md +200 -0
- package/{OVERVIEW.md → docs/overview.md} +22 -0
- package/kubb-extension.json +9 -2
- package/package.json +7 -7
- package/src/plugin.ts +3 -2
|
@@ -0,0 +1,77 @@
|
|
|
1
|
+
# Authenticate requests
|
|
2
|
+
|
|
3
|
+
Set an `auth` resolver on the generated client. Operations use the [security schemes](https://spec.openapis.org/oas/v3.1.0#security-scheme-object) declared in your OpenAPI spec. Requests remain unauthenticated until you provide credentials.
|
|
4
|
+
|
|
5
|
+
## Set the auth resolver
|
|
6
|
+
|
|
7
|
+
Set the resolver for all secured operations:
|
|
8
|
+
|
|
9
|
+
```typescript
|
|
10
|
+
import { client } from './gen/.kubb/client'
|
|
11
|
+
|
|
12
|
+
client.setConfig({
|
|
13
|
+
auth: () => localStorage.getItem('token') ?? undefined,
|
|
14
|
+
})
|
|
15
|
+
```
|
|
16
|
+
|
|
17
|
+
`auth` takes a token string or a function. The function can be async and returns a token, or `undefined` to skip that scheme. When an operation accepts several schemes, the runtime tries each until one returns a token.
|
|
18
|
+
|
|
19
|
+
## Return the right token per scheme
|
|
20
|
+
|
|
21
|
+
Use the scheme passed to the resolver to select a credential:
|
|
22
|
+
|
|
23
|
+
```typescript
|
|
24
|
+
type Auth = {
|
|
25
|
+
type: 'http' | 'apiKey' | 'oauth2' | 'openIdConnect'
|
|
26
|
+
scheme?: 'bearer' | 'basic'
|
|
27
|
+
name?: string
|
|
28
|
+
in?: 'header' | 'query' | 'cookie'
|
|
29
|
+
}
|
|
30
|
+
```
|
|
31
|
+
|
|
32
|
+
The scheme decides where the runtime puts the token:
|
|
33
|
+
|
|
34
|
+
| Scheme in the spec | `Auth` object | Where the token goes |
|
|
35
|
+
| ------------------ | ---------------------------------------- | ------------------------------- |
|
|
36
|
+
| `http` bearer | `{ type: 'http', scheme: 'bearer' }` | `Authorization: Bearer <token>` |
|
|
37
|
+
| `http` basic | `{ type: 'http', scheme: 'basic' }` | `Authorization: Basic <token>` |
|
|
38
|
+
| `apiKey` header | `{ type: 'apiKey', name, in: 'header' }` | request header named `name` |
|
|
39
|
+
| `apiKey` query | `{ type: 'apiKey', name, in: 'query' }` | query parameter named `name` |
|
|
40
|
+
| `apiKey` cookie | `{ type: 'apiKey', name, in: 'cookie' }` | `Cookie` header |
|
|
41
|
+
| `oauth2` | `{ type: 'oauth2' }` | `Authorization: Bearer <token>` |
|
|
42
|
+
| `openIdConnect` | `{ type: 'openIdConnect' }` | `Authorization: Bearer <token>` |
|
|
43
|
+
|
|
44
|
+
> [!NOTE]
|
|
45
|
+
> For basic auth, return the raw `username:password` string. The runtime base64-encodes it and writes the `Basic` prefix, so you never build the header yourself.
|
|
46
|
+
|
|
47
|
+
When a spec mixes schemes, branch on the `Auth` object and return the matching credential:
|
|
48
|
+
|
|
49
|
+
```typescript
|
|
50
|
+
client.setConfig({
|
|
51
|
+
auth: (auth) => (auth.type === 'apiKey' ? apiKey : accessToken),
|
|
52
|
+
})
|
|
53
|
+
```
|
|
54
|
+
|
|
55
|
+
## Use a separate client per environment
|
|
56
|
+
|
|
57
|
+
To give each environment its own token, such as one per tenant in a multi-tenant app, build an isolated client with `createClient`:
|
|
58
|
+
|
|
59
|
+
```typescript
|
|
60
|
+
import { createClient } from './gen/.kubb/client'
|
|
61
|
+
|
|
62
|
+
const tenant = createClient({
|
|
63
|
+
baseURL: 'https://tenant.example.com',
|
|
64
|
+
auth: () => tenantToken,
|
|
65
|
+
})
|
|
66
|
+
|
|
67
|
+
const { data } = await getPetById({ path: { petId: 1 }, client: tenant })
|
|
68
|
+
```
|
|
69
|
+
|
|
70
|
+
To override the client for one request, pass `auth` on that single call, which suits a one-off token refresh. An explicit `headers` value you set on a call always wins over the resolved token.
|
|
71
|
+
|
|
72
|
+
For authentication outside OpenAPI security schemes, use an async request interceptor to sign requests or supply custom headers.
|
|
73
|
+
|
|
74
|
+
## See also
|
|
75
|
+
|
|
76
|
+
- [Add interceptors](https://kubb.dev/extensions/kubb-labs/plugin-fetch/guides/interceptors)
|
|
77
|
+
- [Set the base URL](https://kubb.dev/extensions/kubb-labs/plugin-fetch/guides/base-url)
|
|
@@ -0,0 +1,63 @@
|
|
|
1
|
+
# Set the base URL
|
|
2
|
+
|
|
3
|
+
Set `baseURL` on the client plugin or on the generated client.
|
|
4
|
+
|
|
5
|
+
> [!NOTE]
|
|
6
|
+
> OpenAPI server URLs do not configure the client automatically. The `server` option of `adapterOas` only sets document metadata. It does not set `baseURL`.
|
|
7
|
+
|
|
8
|
+
## Use the baseURL option
|
|
9
|
+
|
|
10
|
+
Pass `baseURL` to `pluginFetch` to set the generated default. Add `pluginTs` for the operation types.
|
|
11
|
+
|
|
12
|
+
```typescript [kubb.config.ts]
|
|
13
|
+
import { defineConfig } from 'kubb/config'
|
|
14
|
+
import { pluginTs } from '@kubb/plugin-ts'
|
|
15
|
+
import { pluginFetch } from '@kubb/plugin-fetch'
|
|
16
|
+
|
|
17
|
+
export default defineConfig({
|
|
18
|
+
input: './petStore.yaml',
|
|
19
|
+
output: { path: './src/gen' },
|
|
20
|
+
plugins: [
|
|
21
|
+
pluginTs(),
|
|
22
|
+
pluginFetch({ baseURL: 'https://localhost:8080/api/v1' }),
|
|
23
|
+
],
|
|
24
|
+
})
|
|
25
|
+
```
|
|
26
|
+
|
|
27
|
+
A value containing a `${...}` interpolation stays dynamic. The plugin emits it as a template literal in the generated client config, so `baseURL: '${process.env.API_URL}'` reads the environment variable when the app runs instead of baking in the build-time value.
|
|
28
|
+
|
|
29
|
+
## Set it at runtime
|
|
30
|
+
|
|
31
|
+
Use `client.setConfig` to configure all operations at startup:
|
|
32
|
+
|
|
33
|
+
```typescript
|
|
34
|
+
import { client } from './gen/.kubb/client'
|
|
35
|
+
|
|
36
|
+
client.setConfig({ baseURL: import.meta.env.VITE_API_URL })
|
|
37
|
+
```
|
|
38
|
+
|
|
39
|
+
Use `createClient` for an isolated client, then pass it to an operation:
|
|
40
|
+
|
|
41
|
+
```typescript
|
|
42
|
+
import { createClient } from './gen/.kubb/client'
|
|
43
|
+
import { getPetById } from './gen/clients/getPetById'
|
|
44
|
+
|
|
45
|
+
const staging = createClient({ baseURL: 'https://staging.petstore.swagger.io/v2' })
|
|
46
|
+
|
|
47
|
+
const { data } = await getPetById({ path: { petId: 1 }, client: staging })
|
|
48
|
+
```
|
|
49
|
+
|
|
50
|
+
Pass `baseURL` on a single call to override the client for that one request:
|
|
51
|
+
|
|
52
|
+
```typescript
|
|
53
|
+
import { getPetById } from './gen/clients/getPetById'
|
|
54
|
+
|
|
55
|
+
const { data } = await getPetById({ path: { petId: 1 }, baseURL: 'https://localhost:3000' })
|
|
56
|
+
```
|
|
57
|
+
|
|
58
|
+
A `baseURL` set on the call wins over `createClient`, which wins over `setConfig`, which wins over the build-time value.
|
|
59
|
+
|
|
60
|
+
## See also
|
|
61
|
+
|
|
62
|
+
- [Authenticate requests](https://kubb.dev/extensions/kubb-labs/plugin-fetch/guides/authentication)
|
|
63
|
+
- [Use a custom transport](https://kubb.dev/extensions/kubb-labs/plugin-fetch/guides/transport)
|
|
@@ -0,0 +1,147 @@
|
|
|
1
|
+
# Call operations
|
|
2
|
+
|
|
3
|
+
The generated client accepts grouped request options on every operation and returns a typed `RequestResult` by default.
|
|
4
|
+
|
|
5
|
+
## Call an operation
|
|
6
|
+
|
|
7
|
+
Pass parameters under `path`, `query`, `headers`, `cookies`, or `body`, matching the OpenAPI operation.
|
|
8
|
+
|
|
9
|
+
```typescript
|
|
10
|
+
import { getPetById } from './gen/clients/getPetById'
|
|
11
|
+
|
|
12
|
+
const { data } = await getPetById({ path: { petId: 1 } })
|
|
13
|
+
// ^ the parsed pet, typed from the 200 response
|
|
14
|
+
```
|
|
15
|
+
|
|
16
|
+
Query, header, and cookie parameters sit under their own keys, and a request body goes under
|
|
17
|
+
`body`:
|
|
18
|
+
|
|
19
|
+
```typescript
|
|
20
|
+
import { searchPets } from './gen/clients/searchPets'
|
|
21
|
+
import { updatePet } from './gen/clients/updatePet'
|
|
22
|
+
|
|
23
|
+
await searchPets({
|
|
24
|
+
query: { status: 'available', category: 'dogs', limit: 10, offset: 0 },
|
|
25
|
+
})
|
|
26
|
+
|
|
27
|
+
await updatePet({
|
|
28
|
+
path: { petId: '123' },
|
|
29
|
+
headers: { 'X-Request-ID': 'req-123456' },
|
|
30
|
+
body: { name: 'Updated name', status: 'sold' },
|
|
31
|
+
})
|
|
32
|
+
```
|
|
33
|
+
|
|
34
|
+
Each key is optional and only appears when the operation declares it. Call an operation with no parameters with an empty object: `getStatus({})`.
|
|
35
|
+
|
|
36
|
+
## Read the result
|
|
37
|
+
|
|
38
|
+
A resolved call returns a `RequestResult` discriminated by the numeric `status`:
|
|
39
|
+
|
|
40
|
+
```typescript
|
|
41
|
+
type RequestResult = {
|
|
42
|
+
status: number
|
|
43
|
+
data: TData // the parsed success body, undefined on an error result
|
|
44
|
+
error: TError // the parsed error body, undefined on a success result
|
|
45
|
+
contentType: string | undefined // the negotiated response media type
|
|
46
|
+
request: Request // the native request
|
|
47
|
+
response: Response // the native response
|
|
48
|
+
}
|
|
49
|
+
```
|
|
50
|
+
|
|
51
|
+
When `throwOnError` is `true` (the generated default), a resolved call is always a success, so you
|
|
52
|
+
read `data` straight away:
|
|
53
|
+
|
|
54
|
+
```typescript
|
|
55
|
+
const { data, status, response } = await getPetById({ path: { petId: 1 } })
|
|
56
|
+
|
|
57
|
+
console.info(status) // 200
|
|
58
|
+
console.info(response.headers.get('x-ratelimit-remaining'))
|
|
59
|
+
```
|
|
60
|
+
|
|
61
|
+
When an operation documents more than one success status, narrow on `status` to reach the body
|
|
62
|
+
for that case, and TypeScript follows the check:
|
|
63
|
+
|
|
64
|
+
```typescript
|
|
65
|
+
const result = await getPetById({ path: { petId: 1 } })
|
|
66
|
+
|
|
67
|
+
if (result.status === 200) {
|
|
68
|
+
console.info(result.data.name)
|
|
69
|
+
}
|
|
70
|
+
```
|
|
71
|
+
|
|
72
|
+
## Unwrap the success body
|
|
73
|
+
|
|
74
|
+
Call `.unwrap()` to return the success body. Awaiting the operation directly returns the full result.
|
|
75
|
+
|
|
76
|
+
```typescript
|
|
77
|
+
import { getPetById } from './gen/clients/getPetById'
|
|
78
|
+
|
|
79
|
+
const pet = await getPetById({ path: { petId: 1 } }).unwrap()
|
|
80
|
+
// ^ the parsed pet, not the full RequestResult
|
|
81
|
+
```
|
|
82
|
+
|
|
83
|
+
With `throwOnError: true`, a non-2xx response throws the same `ResponseError` a plain `await` would. With `throwOnError: false`, `.unwrap()` rejects with the parsed error body instead:
|
|
84
|
+
|
|
85
|
+
```typescript
|
|
86
|
+
try {
|
|
87
|
+
const pet = await getPetById({ path: { petId: 1 }, throwOnError: false }).unwrap()
|
|
88
|
+
} catch (error) {
|
|
89
|
+
// the parsed error body, not a ResponseError
|
|
90
|
+
console.error(error)
|
|
91
|
+
}
|
|
92
|
+
```
|
|
93
|
+
|
|
94
|
+
## Set the content type
|
|
95
|
+
|
|
96
|
+
When an operation accepts or returns more than one media type, set `contentType` on the call. A
|
|
97
|
+
bare string sets the request content type. The object form also sends an `Accept` header for the
|
|
98
|
+
response:
|
|
99
|
+
|
|
100
|
+
```typescript
|
|
101
|
+
await uploadAvatar({
|
|
102
|
+
path: { petId: '123' },
|
|
103
|
+
body: avatarBlob,
|
|
104
|
+
contentType: 'image/png',
|
|
105
|
+
})
|
|
106
|
+
|
|
107
|
+
await getPet({
|
|
108
|
+
path: { petId: '123' },
|
|
109
|
+
contentType: { request: 'application/json', response: 'application/xml' },
|
|
110
|
+
})
|
|
111
|
+
```
|
|
112
|
+
|
|
113
|
+
An operation that declares a single content type has it built in, so a multipart upload such as `uploadFile({ path: { petId: '123' }, body: { file: pngBlob } })` needs only the body.
|
|
114
|
+
|
|
115
|
+
## Pass native client options
|
|
116
|
+
|
|
117
|
+
Pass `options` on an operation for native fetch settings, such as `cache`, `mode`, `redirect`, `keepalive`, `duplex`, or `next`:
|
|
118
|
+
|
|
119
|
+
```typescript [src/app.ts]
|
|
120
|
+
import { getPetById } from './gen/clients/getPetById'
|
|
121
|
+
|
|
122
|
+
await getPetById({ path: { petId: 1 }, options: { cache: 'no-store' } })
|
|
123
|
+
```
|
|
124
|
+
|
|
125
|
+
Set `client.setConfig({ options })` for shared defaults. Per-call options take precedence. Kubb keeps control of serialization and HTTP error handling. See [Use a custom transport](https://kubb.dev/extensions/kubb-labs/plugin-fetch/guides/transport).
|
|
126
|
+
|
|
127
|
+
## Build a URL without sending
|
|
128
|
+
|
|
129
|
+
Use `client.getUrl` to build a URL with the same base URL, path interpolation, and query serialization as a request:
|
|
130
|
+
|
|
131
|
+
```typescript
|
|
132
|
+
import { client } from './gen/.kubb/client'
|
|
133
|
+
|
|
134
|
+
const url = client.getUrl({
|
|
135
|
+
url: '/pets/{petId}',
|
|
136
|
+
path: { petId: 1 },
|
|
137
|
+
query: { fields: 'name' },
|
|
138
|
+
})
|
|
139
|
+
// https://api.example.com/v1/pets/1?fields=name
|
|
140
|
+
```
|
|
141
|
+
|
|
142
|
+
## See also
|
|
143
|
+
|
|
144
|
+
- [Handle errors](https://kubb.dev/extensions/kubb-labs/plugin-fetch/guides/error-handling)
|
|
145
|
+
- [Configure serialization](https://kubb.dev/extensions/kubb-labs/plugin-fetch/guides/serialization)
|
|
146
|
+
- [Set the base URL](https://kubb.dev/extensions/kubb-labs/plugin-fetch/guides/base-url)
|
|
147
|
+
- [Server-sent events](https://kubb.dev/extensions/kubb-labs/plugin-fetch/guides/server-sent-events)
|
|
@@ -0,0 +1,124 @@
|
|
|
1
|
+
# Handle errors
|
|
2
|
+
|
|
3
|
+
Handle failures from the client Kubb generates. Catch a `ResponseError`, switch to a returned error result with `throwOnError`, and tell a network failure apart from a 4xx or 5xx response.
|
|
4
|
+
|
|
5
|
+
The generated client throws a `ResponseError` on non-2xx responses by default:
|
|
6
|
+
|
|
7
|
+
```typescript
|
|
8
|
+
import { getPetById } from './gen/clients/getPetById'
|
|
9
|
+
import { ResponseError } from './gen/.kubb/client'
|
|
10
|
+
|
|
11
|
+
try {
|
|
12
|
+
const { data } = await getPetById({ path: { petId: 1 } })
|
|
13
|
+
console.info(data.name)
|
|
14
|
+
} catch (error) {
|
|
15
|
+
if (ResponseError.is(error)) {
|
|
16
|
+
console.error(error.status) // 404
|
|
17
|
+
console.error(error.data) // the parsed error body
|
|
18
|
+
}
|
|
19
|
+
}
|
|
20
|
+
```
|
|
21
|
+
|
|
22
|
+
The `message` holds the HTTP method and the URL without its query string, so query credentials stay out of logs. For example: `GET https://api.example.com/pets/1 failed with status 404 Not Found`.
|
|
23
|
+
|
|
24
|
+
The error exposes these fields:
|
|
25
|
+
|
|
26
|
+
```typescript
|
|
27
|
+
class ResponseError extends Error {
|
|
28
|
+
static is(error: unknown): error is ResponseError<unknown, unknown, unknown>
|
|
29
|
+
data: TError // the parsed error body
|
|
30
|
+
status: number
|
|
31
|
+
statusText: string
|
|
32
|
+
contentType: string | undefined
|
|
33
|
+
request: Request
|
|
34
|
+
response: Response
|
|
35
|
+
}
|
|
36
|
+
```
|
|
37
|
+
|
|
38
|
+
Each generated package bundles its own `ResponseError` class, so narrow with `ResponseError.is(error)` instead of `instanceof`. `is()` checks the error name.
|
|
39
|
+
|
|
40
|
+
## Return the error instead
|
|
41
|
+
|
|
42
|
+
Pass `throwOnError: false` and the call resolves for every documented status. The result is a
|
|
43
|
+
discriminated union: `data` is set on success and `error` on failure. Branch on `status`,
|
|
44
|
+
because an empty error body can leave `error` empty or undefined.
|
|
45
|
+
|
|
46
|
+
```typescript
|
|
47
|
+
const result = await getPetById({ path: { petId: 1 }, throwOnError: false })
|
|
48
|
+
|
|
49
|
+
if (result.status === 200) {
|
|
50
|
+
console.info(result.data.name)
|
|
51
|
+
}
|
|
52
|
+
if (result.status !== 200) {
|
|
53
|
+
console.error(result.status, result.error)
|
|
54
|
+
}
|
|
55
|
+
```
|
|
56
|
+
|
|
57
|
+
## Set the generated default
|
|
58
|
+
|
|
59
|
+
Set `throwOnErrorDefault` on the client plugin to choose how generated operations handle non-2xx
|
|
60
|
+
responses by default. Each call can still override that setting with `throwOnError`:
|
|
61
|
+
|
|
62
|
+
```typescript
|
|
63
|
+
import { pluginFetch } from '@kubb/plugin-fetch'
|
|
64
|
+
|
|
65
|
+
pluginFetch({ throwOnErrorDefault: false })
|
|
66
|
+
|
|
67
|
+
// resolves with an error result
|
|
68
|
+
const list = await searchPets({ query: { status: 'available' } })
|
|
69
|
+
|
|
70
|
+
// opt one call back into throwing
|
|
71
|
+
const pet = await getPetById({ path: { petId: 1 }, throwOnError: true })
|
|
72
|
+
```
|
|
73
|
+
|
|
74
|
+
`client.setConfig({ throwOnError })` does not change this default, because each generated
|
|
75
|
+
operation passes its own value. Set it on the plugin.
|
|
76
|
+
|
|
77
|
+
## Network failures still throw
|
|
78
|
+
|
|
79
|
+
> [!IMPORTANT]
|
|
80
|
+
> `throwOnError` only applies when the server answers. A dropped connection, a DNS failure, or an
|
|
81
|
+
> aborted `AbortSignal` always rejects. A `ResponseError` means the server answered with a non-2xx
|
|
82
|
+
> status, and a `ParseError` means a body failed validation. Any other error usually means the
|
|
83
|
+
> request never completed.
|
|
84
|
+
|
|
85
|
+
```typescript
|
|
86
|
+
try {
|
|
87
|
+
const { data } = await getPetById({ path: { petId: 1 }, throwOnError: false })
|
|
88
|
+
use(data)
|
|
89
|
+
} catch (error) {
|
|
90
|
+
// not a ResponseError: the request never completed
|
|
91
|
+
console.error('request failed to send', error)
|
|
92
|
+
}
|
|
93
|
+
```
|
|
94
|
+
|
|
95
|
+
Pass an `AbortSignal` to cancel a request. The call rejects with the abort reason.
|
|
96
|
+
|
|
97
|
+
## Validation failures
|
|
98
|
+
|
|
99
|
+
When you turn on the `validator` option, a body that does not match its Zod schema throws a
|
|
100
|
+
`ParseError` instead of returning. It carries the schema's raw `issues`:
|
|
101
|
+
|
|
102
|
+
```typescript
|
|
103
|
+
import { ParseError } from './gen/.kubb/standardSchema'
|
|
104
|
+
|
|
105
|
+
try {
|
|
106
|
+
const { data } = await getPetById({ path: { petId: 1 } })
|
|
107
|
+
use(data)
|
|
108
|
+
} catch (error) {
|
|
109
|
+
if (error instanceof ParseError) {
|
|
110
|
+
console.error(error.issues) // [{ message, path }]
|
|
111
|
+
}
|
|
112
|
+
}
|
|
113
|
+
```
|
|
114
|
+
|
|
115
|
+
A `ParseError` reports schema validation issues. A `ResponseError` reports a non-2xx status. With `throwOnError: false`, the error body is checked against its own error schema.
|
|
116
|
+
|
|
117
|
+
Calling `.unwrap()` on a `throwOnError: false` call turns that same `error` into a rejection, so a
|
|
118
|
+
`try`/`catch` works there too.
|
|
119
|
+
|
|
120
|
+
## See also
|
|
121
|
+
|
|
122
|
+
- [Call operations](https://kubb.dev/extensions/kubb-labs/plugin-fetch/guides/calling-operations)
|
|
123
|
+
- [Configure serialization](https://kubb.dev/extensions/kubb-labs/plugin-fetch/guides/serialization)
|
|
124
|
+
- [Add interceptors](https://kubb.dev/extensions/kubb-labs/plugin-fetch/guides/interceptors)
|
|
@@ -0,0 +1,78 @@
|
|
|
1
|
+
# Add interceptors
|
|
2
|
+
|
|
3
|
+
Register handlers on `client.interceptors.request`, `.response`, or `.error` to apply behavior across calls. Handlers receive the resolved request and result objects.
|
|
4
|
+
|
|
5
|
+
## Run before the send
|
|
6
|
+
|
|
7
|
+
Request handlers run after URL construction, serialization, and authentication. Return the modified request:
|
|
8
|
+
|
|
9
|
+
```typescript
|
|
10
|
+
import { client } from './gen/.kubb/client'
|
|
11
|
+
|
|
12
|
+
client.interceptors.request.use((request) => {
|
|
13
|
+
request.headers['X-Request-ID'] = crypto.randomUUID()
|
|
14
|
+
return request
|
|
15
|
+
})
|
|
16
|
+
```
|
|
17
|
+
|
|
18
|
+
The handler receives a `ResolvedRequest` (`url`, `method`, `headers`, `body`, `signal`,
|
|
19
|
+
`credentials`, `options`, `responseType`).
|
|
20
|
+
|
|
21
|
+
## Run after the send
|
|
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`):
|
|
26
|
+
|
|
27
|
+
```typescript
|
|
28
|
+
client.interceptors.response.use((result) => {
|
|
29
|
+
console.info(`${result.status} ${result.request.url}`)
|
|
30
|
+
return result
|
|
31
|
+
})
|
|
32
|
+
```
|
|
33
|
+
|
|
34
|
+
## React to an error
|
|
35
|
+
|
|
36
|
+
Use an error handler to react to HTTP errors on the throwing path. The handler receives the
|
|
37
|
+
`ResponseError`:
|
|
38
|
+
|
|
39
|
+
```typescript
|
|
40
|
+
client.interceptors.error.use((error) => {
|
|
41
|
+
if (error.status === 401) scheduleTokenRefresh()
|
|
42
|
+
return error
|
|
43
|
+
})
|
|
44
|
+
```
|
|
45
|
+
|
|
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.
|
|
50
|
+
|
|
51
|
+
## Add, replace, and remove handlers
|
|
52
|
+
|
|
53
|
+
`use` registers a handler and returns an id. Pass that id to `eject` to remove it, or to `update`
|
|
54
|
+
to swap its function in place.
|
|
55
|
+
|
|
56
|
+
> [!NOTE]
|
|
57
|
+
> Handlers run in the order you register them.
|
|
58
|
+
|
|
59
|
+
```typescript
|
|
60
|
+
const id = client.interceptors.request.use((request) => request)
|
|
61
|
+
|
|
62
|
+
client.interceptors.request.update(id, (request) => {
|
|
63
|
+
request.headers['X-Trace'] = 'on'
|
|
64
|
+
return request
|
|
65
|
+
})
|
|
66
|
+
|
|
67
|
+
client.interceptors.request.eject(id)
|
|
68
|
+
```
|
|
69
|
+
|
|
70
|
+
## Where to set them
|
|
71
|
+
|
|
72
|
+
Register handlers on the shared `client` for all operations, or on a separate `createClient` instance for isolated calls. Interceptors are instance-level settings.
|
|
73
|
+
|
|
74
|
+
## See also
|
|
75
|
+
|
|
76
|
+
- [Handle errors](https://kubb.dev/extensions/kubb-labs/plugin-fetch/guides/error-handling)
|
|
77
|
+
- [Authenticate requests](https://kubb.dev/extensions/kubb-labs/plugin-fetch/guides/authentication)
|
|
78
|
+
- [Use a custom transport](https://kubb.dev/extensions/kubb-labs/plugin-fetch/guides/transport)
|
|
@@ -0,0 +1,120 @@
|
|
|
1
|
+
# Configure serialization
|
|
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.
|
|
4
|
+
|
|
5
|
+
## Override parameter serialization
|
|
6
|
+
|
|
7
|
+
Check the [default parameter styles and encoding](https://kubb.dev/docs/5.x/reference/serialization#parameter-styles) before replacing a serializer.
|
|
8
|
+
|
|
9
|
+
Override `serializer.query`, `.body`, or `.path` on the client. Omitted functions retain their defaults:
|
|
10
|
+
|
|
11
|
+
```typescript
|
|
12
|
+
import { client } from './gen/.kubb/client'
|
|
13
|
+
import qs from 'qs'
|
|
14
|
+
|
|
15
|
+
client.setConfig({
|
|
16
|
+
serializer: {
|
|
17
|
+
query: (params) => qs.stringify(params, { arrayFormat: 'brackets' }),
|
|
18
|
+
},
|
|
19
|
+
})
|
|
20
|
+
```
|
|
21
|
+
|
|
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.
|
|
24
|
+
|
|
25
|
+
## Encode request bodies
|
|
26
|
+
|
|
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.
|
|
32
|
+
|
|
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.
|
|
36
|
+
|
|
37
|
+
> [!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.
|
|
41
|
+
|
|
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:
|
|
45
|
+
|
|
46
|
+
```typescript
|
|
47
|
+
import { client } from './gen/.kubb/client'
|
|
48
|
+
import { stringify } from 'yaml'
|
|
49
|
+
|
|
50
|
+
client.setConfig({
|
|
51
|
+
codecs: {
|
|
52
|
+
'application/yaml': { serialize: (body) => stringify(body) },
|
|
53
|
+
},
|
|
54
|
+
})
|
|
55
|
+
```
|
|
56
|
+
|
|
57
|
+
## Decode responses
|
|
58
|
+
|
|
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
|
+
To decode a media type the runtime does not handle, register a codec's `deserialize` for it.
|
|
65
|
+
`deserialize` receives the raw body and the content type and returns the parsed value. It runs
|
|
66
|
+
before validation, so the schema checks the parsed value:
|
|
67
|
+
|
|
68
|
+
```typescript
|
|
69
|
+
import { client } from './gen/.kubb/client'
|
|
70
|
+
|
|
71
|
+
client.setConfig({
|
|
72
|
+
codecs: {
|
|
73
|
+
'application/xml': { deserialize: (raw) => new DOMParser().parseFromString(raw as string, 'application/xml') },
|
|
74
|
+
},
|
|
75
|
+
})
|
|
76
|
+
```
|
|
77
|
+
|
|
78
|
+
Like the other config, `codecs` can also be passed on a single call, and a per-call entry merges
|
|
79
|
+
over the client one for that content type.
|
|
80
|
+
|
|
81
|
+
When the runtime picks the wrong parse mode because a response omits its `Content-Type` or sets a
|
|
82
|
+
misleading one, force the mode with `responseType` on the call. It accepts `'json'`, `'text'`,
|
|
83
|
+
`'blob'`, `'arraybuffer'`, and `'document'`:
|
|
84
|
+
|
|
85
|
+
```typescript
|
|
86
|
+
const { data } = await downloadInvoice({ path: { id: '123' }, responseType: 'blob' })
|
|
87
|
+
// data is a Blob even when the server leaves Content-Type unset
|
|
88
|
+
```
|
|
89
|
+
|
|
90
|
+
[Server-sent events](https://kubb.dev/extensions/kubb-labs/plugin-fetch/guides/server-sent-events) covers `responseType: 'stream'`.
|
|
91
|
+
|
|
92
|
+
For bidirectional formats, supply both `serialize` and `deserialize` in the media type codec, then set the operation's request and response `contentType`.
|
|
93
|
+
|
|
94
|
+
## Validate responses
|
|
95
|
+
|
|
96
|
+
Enable `validator` on the client plugin and register `pluginZod` in the same configuration. Validation is disabled by default.
|
|
97
|
+
|
|
98
|
+
```typescript
|
|
99
|
+
import { pluginFetch } from '@kubb/plugin-fetch'
|
|
100
|
+
|
|
101
|
+
pluginFetch({ validator: 'zod' })
|
|
102
|
+
```
|
|
103
|
+
|
|
104
|
+
`'zod'` validates the success response body, and the error body when a non-2xx call does not
|
|
105
|
+
throw. Use the object form to opt in per direction, where `request` validates the request
|
|
106
|
+
body before the call goes out:
|
|
107
|
+
|
|
108
|
+
```typescript
|
|
109
|
+
pluginFetch({ validator: { request: 'zod', response: 'zod' } })
|
|
110
|
+
```
|
|
111
|
+
|
|
112
|
+
With a validator set, Kubb passes the matching schema to each generated call, and the runtime
|
|
113
|
+
parses the body through it. A body that does not match throws a `ParseError` with the schema's
|
|
114
|
+
`issues`.
|
|
115
|
+
|
|
116
|
+
## See also
|
|
117
|
+
|
|
118
|
+
- [Call operations](https://kubb.dev/extensions/kubb-labs/plugin-fetch/guides/calling-operations)
|
|
119
|
+
- [Handle errors](https://kubb.dev/extensions/kubb-labs/plugin-fetch/guides/error-handling)
|
|
120
|
+
- [Server-sent events](https://kubb.dev/extensions/kubb-labs/plugin-fetch/guides/server-sent-events)
|