@kubb/plugin-axios 5.9.0 → 5.9.2
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 +35 -17
- package/dist/index.cjs.map +1 -1
- package/dist/index.d.cts +1 -0
- package/dist/index.d.ts +1 -0
- package/dist/index.js +35 -17
- package/dist/index.js.map +1 -1
- package/dist/templates/axios.d.ts +27 -55
- package/docs/options.md +1 -1
- package/package.json +5 -5
- package/templates/axios.ts +170 -266
- package/templates/serializers.ts +13 -5
package/templates/axios.ts
CHANGED
|
@@ -9,14 +9,8 @@ import { ParseError, type StandardSchemaValidator, validateStandardSchema } from
|
|
|
9
9
|
*/
|
|
10
10
|
export type SuccessStatusCode = '200' | '201' | '202' | '203' | '204' | '205' | '206' | '207' | '208' | '226'
|
|
11
11
|
|
|
12
|
-
/**
|
|
13
|
-
* The success members of a per-status responses record.
|
|
14
|
-
*/
|
|
15
12
|
export type SuccessOf<TResponses> = TResponses[Extract<keyof TResponses, SuccessStatusCode>]
|
|
16
13
|
|
|
17
|
-
/**
|
|
18
|
-
* The error members of a per-status responses record, every documented status that is not a 2xx.
|
|
19
|
-
*/
|
|
20
14
|
export type ErrorOf<TResponses> = TResponses[Exclude<keyof TResponses, SuccessStatusCode>]
|
|
21
15
|
|
|
22
16
|
/**
|
|
@@ -25,12 +19,12 @@ export type ErrorOf<TResponses> = TResponses[Exclude<keyof TResponses, SuccessSt
|
|
|
25
19
|
export type ToStatusNumber<TStatus> = TStatus extends `${infer TNumber extends number}` ? TNumber : number
|
|
26
20
|
|
|
27
21
|
/**
|
|
28
|
-
*
|
|
22
|
+
* Unwraps content-type variants to keep the bare body on `error`.
|
|
29
23
|
*/
|
|
30
24
|
export type DataOf<T> = T extends { contentType: string; data: infer TData } ? TData : T
|
|
31
25
|
|
|
32
26
|
/**
|
|
33
|
-
*
|
|
27
|
+
* Keeps `contentType` beside `data` so switching on it narrows the body.
|
|
34
28
|
*/
|
|
35
29
|
export type SuccessVariant<TStatus, TEntry, TRequest, TResponse> = TEntry extends { contentType: string; data: unknown }
|
|
36
30
|
? TEntry extends { contentType: infer TContentType; data: infer TData }
|
|
@@ -39,7 +33,7 @@ export type SuccessVariant<TStatus, TEntry, TRequest, TResponse> = TEntry extend
|
|
|
39
33
|
: { status: ToStatusNumber<TStatus>; data: TEntry; error: undefined; contentType: string | undefined; request: TRequest; response: TResponse }
|
|
40
34
|
|
|
41
35
|
/**
|
|
42
|
-
*
|
|
36
|
+
* Narrows `data` or `error` when switching on the numeric status.
|
|
43
37
|
*/
|
|
44
38
|
export type ResultByStatus<TResponses, TStatus extends keyof TResponses, TRequest, TResponse> = TStatus extends SuccessStatusCode
|
|
45
39
|
? SuccessVariant<TStatus, TResponses[TStatus], TRequest, TResponse>
|
|
@@ -52,22 +46,19 @@ export type ResultByStatus<TResponses, TStatus extends keyof TResponses, TReques
|
|
|
52
46
|
response: TResponse
|
|
53
47
|
}
|
|
54
48
|
|
|
55
|
-
/**
|
|
56
|
-
* The union of every documented status' result variant.
|
|
57
|
-
*/
|
|
58
49
|
export type ResultUnion<TResponses, TRequest, TResponse> = {
|
|
59
50
|
[TStatus in keyof TResponses]: ResultByStatus<TResponses, TStatus, TRequest, TResponse>
|
|
60
51
|
}[keyof TResponses]
|
|
61
52
|
|
|
62
53
|
/**
|
|
63
|
-
*
|
|
54
|
+
* Selects success statuses without letting untyped error payloads widen `data`.
|
|
64
55
|
*/
|
|
65
56
|
export type SuccessResultUnion<TResponses, TRequest, TResponse> = {
|
|
66
57
|
[TStatus in Extract<keyof TResponses, SuccessStatusCode>]: ResultByStatus<TResponses, TStatus, TRequest, TResponse>
|
|
67
58
|
}[Extract<keyof TResponses, SuccessStatusCode>]
|
|
68
59
|
|
|
69
60
|
/**
|
|
70
|
-
*
|
|
61
|
+
* Narrows `data` and `error` by status. With `throwOnError`, includes only success variants.
|
|
71
62
|
*/
|
|
72
63
|
export type RequestResult<TResponses, ThrowOnError extends boolean = true, TRequest = AxiosRequestConfig, TResponse = AxiosResponse> = ThrowOnError extends true
|
|
73
64
|
? [SuccessResultUnion<TResponses, TRequest, TResponse>] extends [never]
|
|
@@ -94,11 +85,15 @@ export type Unwrappable<T extends { data: unknown; error: unknown }> = Promise<T
|
|
|
94
85
|
/**
|
|
95
86
|
* Attaches `unwrap()` to a result promise, which rejects with `error` when the result carried one.
|
|
96
87
|
*
|
|
97
|
-
* @example
|
|
98
|
-
*
|
|
88
|
+
* @example
|
|
89
|
+
* ```ts
|
|
90
|
+
* const { data, error } = await getPetById({ path: { petId: 1 } })
|
|
91
|
+
* ```
|
|
99
92
|
*
|
|
100
|
-
* @example
|
|
101
|
-
*
|
|
93
|
+
* @example
|
|
94
|
+
* ```ts
|
|
95
|
+
* const pet = await getPetById({ path: { petId: 1 } }).unwrap()
|
|
96
|
+
* ```
|
|
102
97
|
*/
|
|
103
98
|
export function withUnwrap<T extends { data: unknown; error: unknown }>(promise: Promise<T>): Unwrappable<T> {
|
|
104
99
|
const unwrappable = promise as Unwrappable<T>
|
|
@@ -123,33 +118,27 @@ export type UnwrappedResult<
|
|
|
123
118
|
|
|
124
119
|
/**
|
|
125
120
|
* Returns the success body when `throwOnError` is true, or the full result when it is false.
|
|
126
|
-
* Backs generated operations with `returnType: 'data'`.
|
|
127
121
|
*/
|
|
128
122
|
export function unwrapResult<T extends { data: unknown; error: unknown }>(promise: Promise<T>, throwOnError: boolean | undefined): Promise<T | T['data']> {
|
|
129
123
|
return promise.then((result) => ((throwOnError ?? true) ? result.data : result))
|
|
130
124
|
}
|
|
131
125
|
|
|
132
|
-
/**
|
|
133
|
-
* The data-shaped keys of the grouped options object, which `Options` re-adds typed per operation.
|
|
134
|
-
*/
|
|
135
126
|
export type DataShape = { body?: unknown; cookies?: unknown; headers?: unknown; path?: unknown; query?: unknown }
|
|
136
127
|
|
|
137
128
|
export type ResponseType = 'arraybuffer' | 'blob' | 'document' | 'json' | 'text' | 'stream' | 'formdata'
|
|
138
129
|
|
|
139
130
|
/**
|
|
140
|
-
*
|
|
131
|
+
* Decodes a response body for a codec's media type.
|
|
141
132
|
*/
|
|
142
133
|
export type Deserializer<T = unknown> = (raw: unknown, contentType: string) => T | Promise<T>
|
|
143
134
|
|
|
144
135
|
/**
|
|
145
|
-
*
|
|
136
|
+
* Encodes a request body for a codec's media type.
|
|
146
137
|
*/
|
|
147
138
|
export type ContentBodySerializer = (body: unknown, contentType?: string) => unknown
|
|
148
139
|
|
|
149
140
|
/**
|
|
150
|
-
*
|
|
151
|
-
* request body for that media type and `deserialize` decodes the response body. Either half is
|
|
152
|
-
* optional, so a codec can handle one direction.
|
|
141
|
+
* Encodes requests or decodes responses for a media type. Either direction can be omitted.
|
|
153
142
|
*/
|
|
154
143
|
export type Codec = {
|
|
155
144
|
serialize?: ContentBodySerializer
|
|
@@ -157,23 +146,17 @@ export type Codec = {
|
|
|
157
146
|
}
|
|
158
147
|
|
|
159
148
|
/**
|
|
160
|
-
*
|
|
149
|
+
* A string sets the request content type. The object form can also set `Accept`.
|
|
161
150
|
*/
|
|
162
151
|
export type ContentType = string | { request?: string; response?: string }
|
|
163
152
|
|
|
164
153
|
/**
|
|
165
|
-
*
|
|
166
|
-
* it is received. `runValidator` runs it through `validateStandardSchema`. Wired through the per-call
|
|
167
|
-
* `validator.request` / `validator.response` / `validator.error` hooks (`error` runs on the error body when a
|
|
168
|
-
* non-2xx call does not throw), and `validator.path` / `validator.query` / `validator.headers` for the
|
|
169
|
-
* request params.
|
|
154
|
+
* Validates request bodies and params, success bodies, or non-throwing error bodies with Standard Schema.
|
|
170
155
|
*/
|
|
171
156
|
export type Validator<T = unknown> = StandardSchemaValidator<T>
|
|
172
157
|
|
|
173
158
|
/**
|
|
174
|
-
*
|
|
175
|
-
* `direction` says which side rejected it: the request, the success body, or the error body. `source`
|
|
176
|
-
* says which part of it: the body, or one of the request param groups.
|
|
159
|
+
* Identifies the value and request that failed validation in `onValidationError`.
|
|
177
160
|
*/
|
|
178
161
|
export type ValidationErrorContext = {
|
|
179
162
|
value: unknown
|
|
@@ -185,9 +168,7 @@ export type ValidationErrorContext = {
|
|
|
185
168
|
}
|
|
186
169
|
|
|
187
170
|
/**
|
|
188
|
-
*
|
|
189
|
-
* `{ value }` resolves the call with that value instead, so a drifted body can be reported and still
|
|
190
|
-
* delivered. The box keeps an explicit `{ value: undefined }` substitution distinct from declining.
|
|
171
|
+
* Return `{ value }` to substitute a failed value, including `undefined`. Returning nothing rethrows the `ParseError`.
|
|
191
172
|
*/
|
|
192
173
|
export type ValidationErrorHandler = (error: ParseError, context: ValidationErrorContext) => { value: unknown } | void | Promise<{ value: unknown } | void>
|
|
193
174
|
|
|
@@ -203,7 +184,7 @@ export type Auth = {
|
|
|
203
184
|
}
|
|
204
185
|
|
|
205
186
|
/**
|
|
206
|
-
*
|
|
187
|
+
* Return `user:password` for basic auth, or `undefined` to skip a scheme.
|
|
207
188
|
*/
|
|
208
189
|
export type AuthToken = string | undefined
|
|
209
190
|
|
|
@@ -217,9 +198,6 @@ export type AuthResolver = AuthToken | ((auth: Auth) => AuthToken | Promise<Auth
|
|
|
217
198
|
*/
|
|
218
199
|
export type AxiosOptions = AxiosRequestConfig
|
|
219
200
|
|
|
220
|
-
/**
|
|
221
|
-
* The request a generated function hands to the runtime, with `body` / `headers` / `path` / `query` from the grouped options.
|
|
222
|
-
*/
|
|
223
201
|
export type RequestConfig<TBody = unknown, TRequest = AxiosRequestConfig, TResponse = AxiosResponse> = {
|
|
224
202
|
baseURL?: string
|
|
225
203
|
url?: string
|
|
@@ -248,8 +226,7 @@ export type RequestConfig<TBody = unknown, TRequest = AxiosRequestConfig, TRespo
|
|
|
248
226
|
}
|
|
249
227
|
|
|
250
228
|
/**
|
|
251
|
-
*
|
|
252
|
-
* data-shaped keys and the literal `url`, plus the per-operation `<Name>Request`.
|
|
229
|
+
* Combines client options with the operation's typed request params and body.
|
|
253
230
|
*/
|
|
254
231
|
export type Options<TData extends DataShape, ThrowOnError extends boolean = true, TRequest = AxiosRequestConfig, TResponse = AxiosResponse> = Omit<
|
|
255
232
|
RequestConfig<unknown, TRequest, TResponse>,
|
|
@@ -276,9 +253,6 @@ export type ClientConfig = {
|
|
|
276
253
|
auth?: AuthResolver
|
|
277
254
|
}
|
|
278
255
|
|
|
279
|
-
/**
|
|
280
|
-
* The result a resolved call produces before it is cast to `RequestResult` by the generated wrapper.
|
|
281
|
-
*/
|
|
282
256
|
export type CallResult<TRequest = AxiosRequestConfig, TResponse = AxiosResponse> = {
|
|
283
257
|
status: number
|
|
284
258
|
data: unknown
|
|
@@ -294,7 +268,7 @@ export type InterceptorFn<T, TRequest = AxiosRequestConfig, TResponse = AxiosRes
|
|
|
294
268
|
) => T | Promise<T>
|
|
295
269
|
|
|
296
270
|
/**
|
|
297
|
-
*
|
|
271
|
+
* Registers, removes, or replaces handlers on axios's native interceptor managers.
|
|
298
272
|
*/
|
|
299
273
|
export type InterceptorChannel<T, TRequest = AxiosRequestConfig, TResponse = AxiosResponse> = {
|
|
300
274
|
use: (fn: InterceptorFn<T, TRequest, TResponse>) => number
|
|
@@ -303,7 +277,7 @@ export type InterceptorChannel<T, TRequest = AxiosRequestConfig, TResponse = Axi
|
|
|
303
277
|
}
|
|
304
278
|
|
|
305
279
|
/**
|
|
306
|
-
*
|
|
280
|
+
* Maps error interceptors to axios's response rejection handler.
|
|
307
281
|
*/
|
|
308
282
|
export type Interceptors<TRequest = AxiosRequestConfig, TResponse = AxiosResponse> = {
|
|
309
283
|
request: InterceptorChannel<InternalAxiosRequestConfig, TRequest, TResponse>
|
|
@@ -410,13 +384,6 @@ function getHeader(headers: Record<string, string>, name: string): string | unde
|
|
|
410
384
|
return key ? headers[key] : undefined
|
|
411
385
|
}
|
|
412
386
|
|
|
413
|
-
function hasHeader(headers: Record<string, string>, name: string): boolean {
|
|
414
|
-
return Object.keys(headers).some((k) => k.toLowerCase() === name.toLowerCase())
|
|
415
|
-
}
|
|
416
|
-
|
|
417
|
-
/**
|
|
418
|
-
* Joins the URL parts, interpolates URL-encoded `{param}` segments, and appends the serialized query, backing `getUrl`.
|
|
419
|
-
*/
|
|
420
387
|
function serializeUrl({
|
|
421
388
|
parts,
|
|
422
389
|
pathParams,
|
|
@@ -437,69 +404,48 @@ function serializeUrl({
|
|
|
437
404
|
return path + (search ? `?${search}` : '')
|
|
438
405
|
}
|
|
439
406
|
|
|
440
|
-
|
|
441
|
-
|
|
442
|
-
|
|
443
|
-
|
|
444
|
-
|
|
445
|
-
register: (fn: InterceptorFn<T, TRequest, TResponse>) => number
|
|
446
|
-
ejectNative: (id: number) => void
|
|
447
|
-
|
|
448
|
-
|
|
449
|
-
setContextId: (value: T, id: number | undefined) => void,
|
|
450
|
-
nextSeq?: () => number,
|
|
451
|
-
): InterceptorChannel<T, TRequest, TResponse> & {
|
|
407
|
+
function createInterceptorChannel<T, TRequest, TResponse>({
|
|
408
|
+
register,
|
|
409
|
+
ejectNative,
|
|
410
|
+
nextSeq,
|
|
411
|
+
}: {
|
|
412
|
+
register: (fn: InterceptorFn<T, TRequest, TResponse>) => number
|
|
413
|
+
ejectNative: (id: number) => void
|
|
414
|
+
nextSeq: () => number
|
|
415
|
+
}): InterceptorChannel<T, TRequest, TResponse> & {
|
|
452
416
|
detach: () => void
|
|
453
|
-
attach: () => void
|
|
454
417
|
getEntries: () => Array<{ seq: number; attach: () => void }>
|
|
455
418
|
} {
|
|
456
|
-
const
|
|
457
|
-
const handlers = new Map<number, { fn: InterceptorFn<T, TRequest, TResponse>; seq: number }>()
|
|
419
|
+
const handlers = new Map<number, { fn: InterceptorFn<T, TRequest, TResponse>; seq: number; nativeId: number }>()
|
|
458
420
|
let counter = 0
|
|
459
|
-
const getSeq = nextSeq ?? (() => ++counter)
|
|
460
|
-
const registerWithContext = (fn: InterceptorFn<T, TRequest, TResponse>) =>
|
|
461
|
-
register(async (value) => {
|
|
462
|
-
const contextId = getContextId(value)
|
|
463
|
-
const result = await fn(value, getRequestConfig(value))
|
|
464
|
-
if (contextId !== undefined) setContextId(result, contextId)
|
|
465
|
-
return result
|
|
466
|
-
})
|
|
467
421
|
|
|
468
422
|
return {
|
|
469
423
|
use(fn) {
|
|
470
424
|
const id = ++counter
|
|
471
|
-
|
|
472
|
-
handlers.set(id, { fn, seq })
|
|
473
|
-
ids.set(id, registerWithContext(fn))
|
|
425
|
+
handlers.set(id, { fn, seq: nextSeq(), nativeId: register(fn) })
|
|
474
426
|
return id
|
|
475
427
|
},
|
|
476
428
|
eject(id) {
|
|
477
|
-
const
|
|
478
|
-
if (
|
|
479
|
-
ejectNative(nativeId)
|
|
480
|
-
ids.delete(id)
|
|
429
|
+
const entry = handlers.get(id)
|
|
430
|
+
if (!entry) return
|
|
431
|
+
ejectNative(entry.nativeId)
|
|
481
432
|
handlers.delete(id)
|
|
482
433
|
},
|
|
483
434
|
update(id, fn) {
|
|
484
|
-
const
|
|
485
|
-
if (
|
|
486
|
-
ejectNative(nativeId)
|
|
487
|
-
|
|
488
|
-
handlers.set(id, { fn, seq })
|
|
489
|
-
ids.set(id, registerWithContext(fn))
|
|
435
|
+
const entry = handlers.get(id)
|
|
436
|
+
if (!entry) return
|
|
437
|
+
ejectNative(entry.nativeId)
|
|
438
|
+
handlers.set(id, { fn, seq: nextSeq(), nativeId: register(fn) })
|
|
490
439
|
},
|
|
491
440
|
detach() {
|
|
492
|
-
for (const
|
|
493
|
-
ids.clear()
|
|
494
|
-
},
|
|
495
|
-
attach() {
|
|
496
|
-
const sorted = Array.from(handlers.entries()).sort((a, b) => a[1].seq - b[1].seq)
|
|
497
|
-
for (const [id, entry] of sorted) ids.set(id, registerWithContext(entry.fn))
|
|
441
|
+
for (const entry of handlers.values()) ejectNative(entry.nativeId)
|
|
498
442
|
},
|
|
499
443
|
getEntries() {
|
|
500
|
-
return Array.from(handlers.
|
|
444
|
+
return Array.from(handlers.values(), (entry) => ({
|
|
501
445
|
seq: entry.seq,
|
|
502
|
-
attach
|
|
446
|
+
attach() {
|
|
447
|
+
entry.nativeId = register(entry.fn)
|
|
448
|
+
},
|
|
503
449
|
}))
|
|
504
450
|
},
|
|
505
451
|
}
|
|
@@ -521,18 +467,23 @@ export async function resolveAuth(params: {
|
|
|
521
467
|
const token = typeof auth === 'function' ? await auth(scheme) : auth
|
|
522
468
|
if (token === undefined) continue
|
|
523
469
|
|
|
524
|
-
if (scheme.type
|
|
525
|
-
|
|
526
|
-
|
|
527
|
-
if (query[name] === undefined) query[name] = token
|
|
528
|
-
} else if (scheme.in === 'cookie') {
|
|
529
|
-
headers['Cookie'] = [headers['Cookie'], `${name}=${token}`].filter(Boolean).join('; ')
|
|
530
|
-
} else if (!hasHeader(headers, name)) {
|
|
531
|
-
headers[name] = token
|
|
470
|
+
if (scheme.type !== 'apiKey') {
|
|
471
|
+
if (getHeader(headers, 'Authorization') === undefined) {
|
|
472
|
+
headers['Authorization'] = scheme.scheme === 'basic' ? `Basic ${btoa(token)}` : `Bearer ${token}`
|
|
532
473
|
}
|
|
533
|
-
|
|
534
|
-
|
|
474
|
+
return
|
|
475
|
+
}
|
|
476
|
+
|
|
477
|
+
const name = scheme.name ?? 'Authorization'
|
|
478
|
+
if (scheme.in === 'query') {
|
|
479
|
+
if (query[name] === undefined) query[name] = token
|
|
480
|
+
return
|
|
481
|
+
}
|
|
482
|
+
if (scheme.in === 'cookie') {
|
|
483
|
+
headers['Cookie'] = [headers['Cookie'], `${name}=${token}`].filter(Boolean).join('; ')
|
|
484
|
+
return
|
|
535
485
|
}
|
|
486
|
+
if (getHeader(headers, name) === undefined) headers[name] = token
|
|
536
487
|
return
|
|
537
488
|
}
|
|
538
489
|
}
|
|
@@ -559,35 +510,17 @@ async function runValidator<T>({
|
|
|
559
510
|
}
|
|
560
511
|
}
|
|
561
512
|
|
|
562
|
-
/**
|
|
563
|
-
* The base media type of a `Content-Type` value, lowercased and stripped of any `; charset=...` parameters.
|
|
564
|
-
*/
|
|
565
513
|
function baseContentType(value: string | null | undefined): string | undefined {
|
|
566
514
|
if (!value) return undefined
|
|
567
515
|
return value.split(';')[0]!.trim().toLowerCase() || undefined
|
|
568
516
|
}
|
|
569
517
|
|
|
570
|
-
/**
|
|
571
|
-
* Reads the negotiated response content type from the response headers as a base media type.
|
|
572
|
-
*/
|
|
573
518
|
function getResponseContentType(headers: Record<string, unknown> | undefined): string | undefined {
|
|
574
519
|
if (!headers) return undefined
|
|
575
520
|
const value = headers['content-type'] ?? headers['Content-Type']
|
|
576
521
|
return baseContentType(typeof value === 'string' ? value : undefined)
|
|
577
522
|
}
|
|
578
523
|
|
|
579
|
-
/**
|
|
580
|
-
* Normalizes the `contentType` option to its `{ request, response }` form, treating a bare string as the request content type.
|
|
581
|
-
*/
|
|
582
|
-
function resolveContentType(contentType: ContentType | undefined): { request?: string; response?: string } {
|
|
583
|
-
if (typeof contentType === 'string') return { request: contentType }
|
|
584
|
-
return contentType ?? {}
|
|
585
|
-
}
|
|
586
|
-
|
|
587
|
-
/**
|
|
588
|
-
* The per-concern serializers for a call, the per-call serializer winning over the client's and
|
|
589
|
-
* falling back to the defaults.
|
|
590
|
-
*/
|
|
591
524
|
function resolveSerializers({ config, requestConfig }: { config: { serializer?: Serializers }; requestConfig: { serializer?: Serializers } }) {
|
|
592
525
|
return {
|
|
593
526
|
querySerializer: requestConfig.serializer?.query ?? config.serializer?.query ?? defaultQuerySerializer,
|
|
@@ -596,11 +529,6 @@ function resolveSerializers({ config, requestConfig }: { config: { serializer?:
|
|
|
596
529
|
}
|
|
597
530
|
}
|
|
598
531
|
|
|
599
|
-
/**
|
|
600
|
-
* Resolves everything a call needs before it touches axios: merged headers with the negotiated
|
|
601
|
-
* content type, auth on headers or query, serialized cookies, the validated and serialized body,
|
|
602
|
-
* and the final axios request config with `throwOnError` riding `validateStatus`.
|
|
603
|
-
*/
|
|
604
532
|
async function resolveRequest<TBody, TRequest, TResponse>({
|
|
605
533
|
config,
|
|
606
534
|
requestConfig,
|
|
@@ -626,9 +554,10 @@ async function resolveRequest<TBody, TRequest, TResponse>({
|
|
|
626
554
|
)
|
|
627
555
|
|
|
628
556
|
const headers = serializeHeaders(applyHeaderStyles(validatedHeaders as HeadersInit | undefined, requestConfig.styles?.header))
|
|
629
|
-
const
|
|
557
|
+
const contentType = requestConfig.contentType
|
|
558
|
+
const { request: requestContentTypeOption, response: responseContentType } = typeof contentType === 'string' ? { request: contentType } : (contentType ?? {})
|
|
630
559
|
const requestContentType = requestContentTypeOption ?? getHeader(headers, 'content-type')
|
|
631
|
-
if (responseContentType &&
|
|
560
|
+
if (responseContentType && getHeader(headers, 'accept') === undefined) {
|
|
632
561
|
headers['Accept'] = responseContentType
|
|
633
562
|
}
|
|
634
563
|
|
|
@@ -658,17 +587,18 @@ async function resolveRequest<TBody, TRequest, TResponse>({
|
|
|
658
587
|
const body = contentCodec?.serialize
|
|
659
588
|
? contentCodec.serialize(validatedBody, requestContentType)
|
|
660
589
|
: bodySerializer({ body: validatedBody, contentType: requestContentType, encoding: requestConfig.styles?.body })
|
|
661
|
-
|
|
590
|
+
if (requestContentTypeOption) headers['Content-Type'] = requestContentTypeOption
|
|
591
|
+
if (body instanceof URLSearchParams && getHeader(headers, 'content-type') === undefined) {
|
|
592
|
+
headers['Content-Type'] = 'application/x-www-form-urlencoded'
|
|
593
|
+
}
|
|
594
|
+
if (usesDefaultBodySerializer && isDefaultJsonBody(validatedBody) && getHeader(headers, 'content-type') === undefined) {
|
|
595
|
+
headers['Content-Type'] = 'application/json'
|
|
596
|
+
}
|
|
597
|
+
// The transport must set the multipart boundary, even when a content type was explicitly provided.
|
|
662
598
|
if (body instanceof FormData) {
|
|
663
599
|
for (const key of Object.keys(headers)) {
|
|
664
600
|
if (key.toLowerCase() === 'content-type') delete headers[key]
|
|
665
601
|
}
|
|
666
|
-
} else if (requestContentTypeOption) {
|
|
667
|
-
headers['Content-Type'] = requestContentTypeOption
|
|
668
|
-
} else if (body instanceof URLSearchParams && !hasHeader(headers, 'content-type')) {
|
|
669
|
-
headers['Content-Type'] = 'application/x-www-form-urlencoded'
|
|
670
|
-
} else if (usesDefaultBodySerializer && isDefaultJsonBody(validatedBody) && !hasHeader(headers, 'content-type')) {
|
|
671
|
-
headers['Content-Type'] = 'application/json'
|
|
672
602
|
}
|
|
673
603
|
|
|
674
604
|
const pathParams = (validatedPath ?? {}) as Record<string, unknown>
|
|
@@ -683,7 +613,7 @@ async function resolveRequest<TBody, TRequest, TResponse>({
|
|
|
683
613
|
const options = config.options || requestConfig.options ? { ...config.options, ...requestConfig.options } : undefined
|
|
684
614
|
|
|
685
615
|
const axiosConfig: AxiosRequestConfig = {
|
|
686
|
-
...options,
|
|
616
|
+
...options,
|
|
687
617
|
url,
|
|
688
618
|
baseURL: requestConfig.baseURL ?? config.baseURL,
|
|
689
619
|
method: requestConfig.method ?? 'GET',
|
|
@@ -708,61 +638,7 @@ async function resolveRequest<TBody, TRequest, TResponse>({
|
|
|
708
638
|
}
|
|
709
639
|
|
|
710
640
|
/**
|
|
711
|
-
*
|
|
712
|
-
* validates the success or error body. A thrown axios error never reaches this, so `error` here is
|
|
713
|
-
* always the body of a non-2xx response that `validateStatus` let through.
|
|
714
|
-
*/
|
|
715
|
-
async function settleResponse<TRequest, TResponse>({
|
|
716
|
-
response,
|
|
717
|
-
codecs,
|
|
718
|
-
validator,
|
|
719
|
-
onValidationError,
|
|
720
|
-
}: {
|
|
721
|
-
response: AxiosResponse
|
|
722
|
-
codecs: Record<string, Codec>
|
|
723
|
-
validator: { response?: Validator; error?: Validator } | undefined
|
|
724
|
-
onValidationError: ValidationErrorHandler | undefined
|
|
725
|
-
}): Promise<CallResult<TRequest, TResponse>> {
|
|
726
|
-
const isSuccess = response.status >= 200 && response.status < 300
|
|
727
|
-
const contentType = getResponseContentType(response.headers as Record<string, unknown>)
|
|
728
|
-
let decoded: unknown = response.data
|
|
729
|
-
if (contentType) {
|
|
730
|
-
const codec = codecs[contentType]
|
|
731
|
-
if (codec?.deserialize) decoded = await codec.deserialize(response.data, contentType)
|
|
732
|
-
}
|
|
733
|
-
const validationContext = {
|
|
734
|
-
method: response.config?.method?.toUpperCase(),
|
|
735
|
-
url: response.config?.url,
|
|
736
|
-
status: response.status,
|
|
737
|
-
}
|
|
738
|
-
const data = isSuccess
|
|
739
|
-
? await runValidator({
|
|
740
|
-
validator: validator?.response,
|
|
741
|
-
value: decoded,
|
|
742
|
-
context: { direction: 'response', source: 'body', ...validationContext },
|
|
743
|
-
onValidationError,
|
|
744
|
-
})
|
|
745
|
-
: undefined
|
|
746
|
-
const error = isSuccess
|
|
747
|
-
? undefined
|
|
748
|
-
: await runValidator({
|
|
749
|
-
validator: validator?.error,
|
|
750
|
-
value: decoded,
|
|
751
|
-
context: { direction: 'error', source: 'body', ...validationContext },
|
|
752
|
-
onValidationError,
|
|
753
|
-
})
|
|
754
|
-
return {
|
|
755
|
-
status: response.status,
|
|
756
|
-
data,
|
|
757
|
-
error,
|
|
758
|
-
contentType,
|
|
759
|
-
request: response.config as TRequest,
|
|
760
|
-
response: response as TResponse,
|
|
761
|
-
}
|
|
762
|
-
}
|
|
763
|
-
|
|
764
|
-
/**
|
|
765
|
-
* Builds the shared client core bound to an axios instance (defaulting to `defaultTransport`, else `axios.create()`), with `throwOnError` riding axios's `validateStatus`.
|
|
641
|
+
* Uses the configured axios instance or creates one. `throwOnError` sets axios's default `validateStatus`.
|
|
766
642
|
*/
|
|
767
643
|
export function createClientCore<TRequest = AxiosRequestConfig, TResponse = AxiosResponse>(
|
|
768
644
|
options: { defaultTransport?: AxiosInstance } & ClientConfig = {},
|
|
@@ -778,51 +654,54 @@ export function createClientCore<TRequest = AxiosRequestConfig, TResponse = Axio
|
|
|
778
654
|
const axiosConfig = 'config' in value ? value.config : value
|
|
779
655
|
return (axiosConfig as (AxiosRequestConfig & { __kubbRequestContext?: number }) | undefined)?.__kubbRequestContext
|
|
780
656
|
}
|
|
781
|
-
const getRequestConfig = (value: AxiosRequestConfig | AxiosResponse | AxiosError) => {
|
|
782
|
-
const id = getContextId(value)
|
|
783
|
-
return id === undefined ? undefined : requestContexts.get(id)
|
|
784
|
-
}
|
|
785
657
|
const setContextId = (value: AxiosRequestConfig | AxiosResponse | AxiosError, id: number | undefined) => {
|
|
786
658
|
if (!value || typeof value !== 'object') return
|
|
787
659
|
const axiosConfig = 'config' in value ? value.config : value
|
|
788
660
|
if (!axiosConfig) return
|
|
789
661
|
const contextualConfig = axiosConfig as AxiosRequestConfig & { __kubbRequestContext?: number }
|
|
790
|
-
if (id === undefined)
|
|
791
|
-
|
|
662
|
+
if (id === undefined) {
|
|
663
|
+
delete contextualConfig.__kubbRequestContext
|
|
664
|
+
return
|
|
665
|
+
}
|
|
666
|
+
contextualConfig.__kubbRequestContext = id
|
|
792
667
|
}
|
|
793
668
|
|
|
669
|
+
const withContext =
|
|
670
|
+
<T extends AxiosRequestConfig | AxiosResponse | AxiosError>(fn: InterceptorFn<T, TRequest, TResponse>) =>
|
|
671
|
+
async (value: T) => {
|
|
672
|
+
const contextId = getContextId(value)
|
|
673
|
+
const requestConfig = contextId === undefined ? undefined : requestContexts.get(contextId)
|
|
674
|
+
const result = await fn(value, requestConfig)
|
|
675
|
+
if (contextId !== undefined) setContextId(result, contextId)
|
|
676
|
+
return result
|
|
677
|
+
}
|
|
678
|
+
|
|
794
679
|
let sequenceCounter = 0
|
|
795
680
|
const nextSeq = () => ++sequenceCounter
|
|
796
681
|
|
|
797
682
|
// Read at call time so the channels follow a transport swapped in through setConfig.
|
|
798
683
|
const channels = {
|
|
799
|
-
request: createInterceptorChannel<InternalAxiosRequestConfig, TRequest, TResponse>(
|
|
800
|
-
(fn) => instance.interceptors.request.use(fn),
|
|
801
|
-
(id) => instance.interceptors.request.eject(id),
|
|
802
|
-
getRequestConfig,
|
|
803
|
-
getContextId,
|
|
804
|
-
setContextId,
|
|
684
|
+
request: createInterceptorChannel<InternalAxiosRequestConfig, TRequest, TResponse>({
|
|
805
685
|
nextSeq,
|
|
806
|
-
|
|
807
|
-
|
|
808
|
-
|
|
809
|
-
|
|
810
|
-
getRequestConfig,
|
|
811
|
-
getContextId,
|
|
812
|
-
setContextId,
|
|
686
|
+
register: (fn) => instance.interceptors.request.use(withContext(fn)),
|
|
687
|
+
ejectNative: (id) => instance.interceptors.request.eject(id),
|
|
688
|
+
}),
|
|
689
|
+
response: createInterceptorChannel<AxiosResponse, TRequest, TResponse>({
|
|
813
690
|
nextSeq,
|
|
814
|
-
|
|
815
|
-
|
|
816
|
-
|
|
817
|
-
|
|
818
|
-
Promise.resolve(fn(error as AxiosError)).then((result) => Promise.reject(result ?? error)),
|
|
819
|
-
),
|
|
820
|
-
(id) => instance.interceptors.response.eject(id),
|
|
821
|
-
getRequestConfig,
|
|
822
|
-
getContextId,
|
|
823
|
-
setContextId,
|
|
691
|
+
register: (fn) => instance.interceptors.response.use(withContext(fn)),
|
|
692
|
+
ejectNative: (id) => instance.interceptors.response.eject(id),
|
|
693
|
+
}),
|
|
694
|
+
error: createInterceptorChannel<AxiosError, TRequest, TResponse>({
|
|
824
695
|
nextSeq,
|
|
825
|
-
|
|
696
|
+
register: (fn) => {
|
|
697
|
+
const onError = withContext(fn)
|
|
698
|
+
return instance.interceptors.response.use(undefined, async (error: unknown) => {
|
|
699
|
+
const result = await onError(error as AxiosError)
|
|
700
|
+
throw result ?? error
|
|
701
|
+
})
|
|
702
|
+
},
|
|
703
|
+
ejectNative: (id) => instance.interceptors.response.eject(id),
|
|
704
|
+
}),
|
|
826
705
|
}
|
|
827
706
|
const channelList = [channels.request, channels.response, channels.error]
|
|
828
707
|
const interceptors: Interceptors<TRequest, TResponse> = {
|
|
@@ -839,34 +718,52 @@ export function createClientCore<TRequest = AxiosRequestConfig, TResponse = Axio
|
|
|
839
718
|
;(axiosConfig as AxiosRequestConfig & { __kubbRequestContext: number }).__kubbRequestContext = contextId
|
|
840
719
|
|
|
841
720
|
try {
|
|
842
|
-
|
|
843
|
-
|
|
844
|
-
|
|
845
|
-
|
|
846
|
-
|
|
847
|
-
|
|
848
|
-
|
|
721
|
+
const response = await activeInstance.request<unknown, AxiosResponse>(axiosConfig)
|
|
722
|
+
const validator = requestConfig.validator
|
|
723
|
+
const onValidationError = requestConfig.onValidationError ?? config.onValidationError
|
|
724
|
+
const isSuccess = response.status >= 200 && response.status < 300
|
|
725
|
+
const contentType = getResponseContentType(response.headers as Record<string, unknown>)
|
|
726
|
+
const codec = contentType ? codecs[contentType] : undefined
|
|
727
|
+
const decoded: unknown = contentType && codec?.deserialize ? await codec.deserialize(response.data, contentType) : response.data
|
|
728
|
+
const data = await runValidator({
|
|
729
|
+
validator: isSuccess ? validator?.response : validator?.error,
|
|
730
|
+
value: decoded,
|
|
731
|
+
context: {
|
|
732
|
+
direction: isSuccess ? 'response' : 'error',
|
|
733
|
+
source: 'body',
|
|
734
|
+
method: response.config?.method?.toUpperCase(),
|
|
735
|
+
url: response.config?.url,
|
|
736
|
+
status: response.status,
|
|
737
|
+
},
|
|
738
|
+
onValidationError,
|
|
739
|
+
})
|
|
740
|
+
const result = {
|
|
741
|
+
status: response.status,
|
|
742
|
+
data: isSuccess ? data : undefined,
|
|
743
|
+
error: isSuccess ? undefined : data,
|
|
744
|
+
contentType,
|
|
745
|
+
request: response.config as TRequest,
|
|
746
|
+
response: response as TResponse,
|
|
747
|
+
}
|
|
748
|
+
setContextId(response, undefined)
|
|
749
|
+
return result
|
|
750
|
+
} catch (error) {
|
|
751
|
+
const axiosError = error as AxiosError
|
|
752
|
+
setContextId(axiosError, undefined)
|
|
753
|
+
if (axiosError.response) setContextId(axiosError.response, undefined)
|
|
754
|
+
if (throwOnError && axiosError.response) {
|
|
755
|
+
throw new ResponseError({
|
|
756
|
+
data: axiosError.response.data,
|
|
757
|
+
status: axiosError.response.status,
|
|
758
|
+
statusText: axiosError.response.statusText,
|
|
759
|
+
contentType: getResponseContentType(axiosError.response.headers as Record<string, unknown>),
|
|
760
|
+
request: axiosError.config as TRequest,
|
|
761
|
+
response: axiosError.response as TResponse,
|
|
762
|
+
method: axiosError.config?.method?.toUpperCase(),
|
|
763
|
+
url: [axiosError.config?.baseURL?.replace(/\/+$/, ''), axiosError.config?.url].filter(Boolean).join(''),
|
|
849
764
|
})
|
|
850
|
-
setContextId(response, undefined)
|
|
851
|
-
return result
|
|
852
|
-
} catch (error) {
|
|
853
|
-
const axiosError = error as AxiosError
|
|
854
|
-
setContextId(axiosError, undefined)
|
|
855
|
-
if (axiosError.response) setContextId(axiosError.response, undefined)
|
|
856
|
-
if (throwOnError && axiosError.response) {
|
|
857
|
-
throw new ResponseError({
|
|
858
|
-
data: axiosError.response.data,
|
|
859
|
-
status: axiosError.response.status,
|
|
860
|
-
statusText: axiosError.response.statusText,
|
|
861
|
-
contentType: getResponseContentType(axiosError.response.headers as Record<string, unknown>),
|
|
862
|
-
request: axiosError.config as TRequest,
|
|
863
|
-
response: axiosError.response as TResponse,
|
|
864
|
-
method: axiosError.config?.method?.toUpperCase(),
|
|
865
|
-
url: [axiosError.config?.baseURL?.replace(/\/+$/, ''), axiosError.config?.url].filter(Boolean).join(''),
|
|
866
|
-
})
|
|
867
|
-
}
|
|
868
|
-
throw error
|
|
869
765
|
}
|
|
766
|
+
throw error
|
|
870
767
|
} finally {
|
|
871
768
|
setContextId(axiosConfig, undefined)
|
|
872
769
|
requestContexts.delete(contextId)
|
|
@@ -880,9 +777,8 @@ export function createClientCore<TRequest = AxiosRequestConfig, TResponse = Axio
|
|
|
880
777
|
if (nextInstance !== instance) {
|
|
881
778
|
for (const channel of channelList) channel.detach()
|
|
882
779
|
instance = nextInstance
|
|
883
|
-
|
|
884
|
-
const
|
|
885
|
-
for (const entry of responseEntries) entry.attach()
|
|
780
|
+
const entries = channelList.flatMap((channel) => channel.getEntries()).sort((a, b) => a.seq - b.seq)
|
|
781
|
+
for (const entry of entries) entry.attach()
|
|
886
782
|
}
|
|
887
783
|
return config
|
|
888
784
|
}
|
|
@@ -947,10 +843,20 @@ function parseEvent<TData>(raw: string): ServerSentEvent<TData> | undefined {
|
|
|
947
843
|
const index = line.indexOf(':')
|
|
948
844
|
const field = index === -1 ? line : line.slice(0, index)
|
|
949
845
|
const value = index === -1 ? '' : line.slice(index + 1).replace(/^ /, '')
|
|
950
|
-
|
|
951
|
-
|
|
952
|
-
|
|
953
|
-
|
|
846
|
+
switch (field) {
|
|
847
|
+
case 'data':
|
|
848
|
+
data.push(value)
|
|
849
|
+
break
|
|
850
|
+
case 'event':
|
|
851
|
+
event.event = value
|
|
852
|
+
break
|
|
853
|
+
case 'id':
|
|
854
|
+
event.id = value
|
|
855
|
+
break
|
|
856
|
+
case 'retry':
|
|
857
|
+
if (Number.isFinite(Number(value))) event.retry = Number(value)
|
|
858
|
+
break
|
|
859
|
+
}
|
|
954
860
|
}
|
|
955
861
|
|
|
956
862
|
if (!seen) return undefined
|
|
@@ -990,8 +896,7 @@ export async function* parseEventStream<TData = unknown>(
|
|
|
990
896
|
}
|
|
991
897
|
|
|
992
898
|
/**
|
|
993
|
-
*
|
|
994
|
-
* `stream` plus the native `response`.
|
|
899
|
+
* Returns parsed events alongside the native response.
|
|
995
900
|
*/
|
|
996
901
|
export type EventStreamResult<TData = unknown, TResponse = AxiosResponse> = {
|
|
997
902
|
stream: AsyncGenerator<ServerSentEvent<TData>>
|
|
@@ -999,8 +904,7 @@ export type EventStreamResult<TData = unknown, TResponse = AxiosResponse> = {
|
|
|
999
904
|
}
|
|
1000
905
|
|
|
1001
906
|
/**
|
|
1002
|
-
*
|
|
1003
|
-
* the parsed events as a typed async iterator. Generated SSE operations call this.
|
|
907
|
+
* Exposes a streaming response body as typed Server-Sent Events.
|
|
1004
908
|
*/
|
|
1005
909
|
export async function toEventStream<TData = unknown>(result: Promise<{ data: unknown; response: AxiosResponse }>): Promise<EventStreamResult<TData>> {
|
|
1006
910
|
const { data, response } = await result
|