@kubb/plugin-fetch 5.0.0-beta.75 → 5.0.0-beta.77
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 +178 -119
- package/dist/index.cjs.map +1 -1
- package/dist/index.d.ts +4 -2
- package/dist/index.js +178 -119
- package/dist/index.js.map +1 -1
- package/package.json +3 -3
- package/src/generators/clientGenerator.tsx +2 -1
- package/templates/fetch.ts +48 -8
package/templates/fetch.ts
CHANGED
|
@@ -84,7 +84,8 @@ export type BodySerializer = (body: unknown, contentType?: string) => BodyInit |
|
|
|
84
84
|
|
|
85
85
|
/**
|
|
86
86
|
* Parses a value before it is sent or after it is received, returning the parsed (and optionally
|
|
87
|
-
* transformed) value. Wires zod parsing through the per-call `parser.request` / `parser.response`
|
|
87
|
+
* transformed) value. Wires zod parsing through the per-call `parser.request` / `parser.response` /
|
|
88
|
+
* `parser.error` hooks (`error` runs on the error body when a non-2xx call does not throw).
|
|
88
89
|
*/
|
|
89
90
|
export type Parser<T = unknown> = (value: T) => T | Promise<T>
|
|
90
91
|
|
|
@@ -111,6 +112,14 @@ export type AuthToken = string | undefined
|
|
|
111
112
|
*/
|
|
112
113
|
export type AuthResolver = AuthToken | ((auth: Auth) => AuthToken | Promise<AuthToken>)
|
|
113
114
|
|
|
115
|
+
/**
|
|
116
|
+
* Extra `fetch` init the transport spreads onto every `Request`, an escape hatch for the fields the
|
|
117
|
+
* runtime does not set itself: `cache`, `mode`, `redirect`, `keepalive`, `duplex`, and Next.js's
|
|
118
|
+
* non-standard `next` (`{ revalidate, tags }`). `method`, `headers`, `body`, `signal`, and
|
|
119
|
+
* `credentials` are always controlled by the runtime and override anything set here.
|
|
120
|
+
*/
|
|
121
|
+
export type FetchOptions = RequestInit & { next?: Record<string, unknown> }
|
|
122
|
+
|
|
114
123
|
/**
|
|
115
124
|
* The request a generated function hands to the runtime. `body` / `headers` / `path` / `query` come
|
|
116
125
|
* from the grouped options; everything else is plain request configuration.
|
|
@@ -126,6 +135,7 @@ export type RequestConfig<TBody = unknown, TRequest = Request, TResponse = Respo
|
|
|
126
135
|
headers?: HeadersInit
|
|
127
136
|
signal?: AbortSignal
|
|
128
137
|
credentials?: RequestCredentials
|
|
138
|
+
options?: FetchOptions
|
|
129
139
|
contentType?: string
|
|
130
140
|
responseType?: ResponseType
|
|
131
141
|
throwOnError?: boolean
|
|
@@ -133,7 +143,7 @@ export type RequestConfig<TBody = unknown, TRequest = Request, TResponse = Respo
|
|
|
133
143
|
transport?: Transport<TRequest, TResponse>
|
|
134
144
|
querySerializer?: QuerySerializer
|
|
135
145
|
bodySerializer?: BodySerializer
|
|
136
|
-
parser?: { request?: Parser; response?: Parser }
|
|
146
|
+
parser?: { request?: Parser; response?: Parser; error?: Parser }
|
|
137
147
|
security?: Array<Auth>
|
|
138
148
|
auth?: AuthResolver
|
|
139
149
|
}
|
|
@@ -159,6 +169,7 @@ export type ClientConfig<TRequest = Request, TResponse = Response> = {
|
|
|
159
169
|
baseURL?: string
|
|
160
170
|
headers?: HeadersInit
|
|
161
171
|
credentials?: RequestCredentials
|
|
172
|
+
options?: FetchOptions
|
|
162
173
|
throwOnError?: boolean
|
|
163
174
|
transport?: Transport<TRequest, TResponse>
|
|
164
175
|
querySerializer?: QuerySerializer
|
|
@@ -177,6 +188,7 @@ export type ResolvedRequest = {
|
|
|
177
188
|
body?: BodyInit
|
|
178
189
|
signal?: AbortSignal
|
|
179
190
|
credentials?: RequestCredentials
|
|
191
|
+
options?: FetchOptions
|
|
180
192
|
responseType?: ResponseType
|
|
181
193
|
}
|
|
182
194
|
|
|
@@ -283,13 +295,30 @@ function isFormBody(body: unknown): body is BodyInit {
|
|
|
283
295
|
)
|
|
284
296
|
}
|
|
285
297
|
|
|
298
|
+
function appendFormDataValue(formData: FormData, key: string, value: unknown): void {
|
|
299
|
+
if (value === undefined || value === null) return
|
|
300
|
+
if (value instanceof Blob) formData.append(key, value)
|
|
301
|
+
else if (value instanceof Date) formData.append(key, value.toISOString())
|
|
302
|
+
else if (typeof value === 'object') formData.append(key, JSON.stringify(value))
|
|
303
|
+
else formData.append(key, String(value))
|
|
304
|
+
}
|
|
305
|
+
|
|
286
306
|
/**
|
|
287
307
|
* Default body serializer: passes binary/form bodies through and JSON-serializes everything else.
|
|
288
|
-
* For `
|
|
308
|
+
* For `multipart/form-data` plain objects become `FormData` and for
|
|
309
|
+
* `application/x-www-form-urlencoded` they become `URLSearchParams`.
|
|
289
310
|
*/
|
|
290
311
|
export const defaultBodySerializer: BodySerializer = (body, contentType) => {
|
|
291
312
|
if (body === undefined || body === null) return undefined
|
|
292
313
|
if (isFormBody(body)) return body as BodyInit
|
|
314
|
+
if (contentType?.includes('multipart/form-data')) {
|
|
315
|
+
const formData = new FormData()
|
|
316
|
+
for (const [key, value] of Object.entries(body as Record<string, unknown>)) {
|
|
317
|
+
if (Array.isArray(value)) for (const item of value) appendFormDataValue(formData, key, item)
|
|
318
|
+
else appendFormDataValue(formData, key, value)
|
|
319
|
+
}
|
|
320
|
+
return formData
|
|
321
|
+
}
|
|
293
322
|
if (contentType?.includes('application/x-www-form-urlencoded')) {
|
|
294
323
|
return new URLSearchParams(body as Record<string, string>)
|
|
295
324
|
}
|
|
@@ -439,9 +468,7 @@ export function createClientCore<TRequest = Request, TResponse = Response>(
|
|
|
439
468
|
const bodySerializer = requestConfig.bodySerializer ?? config.bodySerializer ?? defaultBodySerializer
|
|
440
469
|
|
|
441
470
|
const headers = mergeHeaders(config.headers, requestConfig.headers)
|
|
442
|
-
|
|
443
|
-
headers['Content-Type'] = requestConfig.contentType
|
|
444
|
-
}
|
|
471
|
+
const requestContentType = requestConfig.contentType ?? headers['Content-Type'] ?? headers['content-type']
|
|
445
472
|
|
|
446
473
|
const query: Record<string, unknown> = { ...((requestConfig.query ?? requestConfig.params) as Record<string, unknown> | undefined) }
|
|
447
474
|
|
|
@@ -454,15 +481,26 @@ export function createClientCore<TRequest = Request, TResponse = Response>(
|
|
|
454
481
|
|
|
455
482
|
const rawBody = requestConfig.body
|
|
456
483
|
const validatedBody = await runParser(requestConfig.parser?.request, rawBody)
|
|
484
|
+
const body = bodySerializer(validatedBody, requestContentType)
|
|
485
|
+
// A FormData body must keep its Content-Type unset so the runtime appends the multipart boundary.
|
|
486
|
+
if (body instanceof FormData) {
|
|
487
|
+
delete headers['Content-Type']
|
|
488
|
+
delete headers['content-type']
|
|
489
|
+
} else if (requestConfig.contentType) {
|
|
490
|
+
headers['Content-Type'] = requestConfig.contentType
|
|
491
|
+
}
|
|
457
492
|
const url = serializeUrl([config.baseURL, requestConfig.baseURL, requestConfig.url], requestConfig.path ?? {}, querySerializer(query))
|
|
458
493
|
|
|
494
|
+
const options = config.options || requestConfig.options ? { ...config.options, ...requestConfig.options } : undefined
|
|
495
|
+
|
|
459
496
|
let resolvedRequest: ResolvedRequest = {
|
|
460
497
|
url,
|
|
461
498
|
method: (requestConfig.method ?? 'GET').toUpperCase(),
|
|
462
499
|
headers,
|
|
463
|
-
body
|
|
500
|
+
body,
|
|
464
501
|
signal: requestConfig.signal,
|
|
465
502
|
credentials: requestConfig.credentials,
|
|
503
|
+
options,
|
|
466
504
|
responseType: requestConfig.responseType,
|
|
467
505
|
}
|
|
468
506
|
resolvedRequest = await interceptors.request.run(resolvedRequest)
|
|
@@ -486,11 +524,12 @@ export function createClientCore<TRequest = Request, TResponse = Response>(
|
|
|
486
524
|
}
|
|
487
525
|
|
|
488
526
|
const data = isSuccess ? await runParser(requestConfig.parser?.response, result.data) : undefined
|
|
527
|
+
const error = isSuccess ? undefined : await runParser(requestConfig.parser?.error, result.data)
|
|
489
528
|
|
|
490
529
|
return {
|
|
491
530
|
status: result.status,
|
|
492
531
|
data,
|
|
493
|
-
error
|
|
532
|
+
error,
|
|
494
533
|
request: result.request,
|
|
495
534
|
response: result.response,
|
|
496
535
|
}
|
|
@@ -565,6 +604,7 @@ async function parseResponse(response: Response, responseType?: ResponseType): P
|
|
|
565
604
|
*/
|
|
566
605
|
const defaultTransport: Transport = async (request: ResolvedRequest): Promise<TransportResult> => {
|
|
567
606
|
const init: RequestInit = {
|
|
607
|
+
...request.options, // cache, mode, redirect, keepalive, duplex, next, …
|
|
568
608
|
method: request.method,
|
|
569
609
|
headers: request.headers,
|
|
570
610
|
body: request.body,
|