@kubb/plugin-fetch 5.0.0-beta.73 → 5.0.0-beta.76
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/LICENSE +21 -0
- package/dist/index.cjs +179 -120
- package/dist/index.cjs.map +1 -1
- package/dist/index.d.ts +4 -2
- package/dist/index.js +179 -120
- package/dist/index.js.map +1 -1
- package/package.json +21 -21
- package/src/generators/clientGenerator.tsx +2 -1
- package/src/plugin.ts +1 -3
- package/templates/fetch.ts +53 -15
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
|
|
|
@@ -133,7 +134,7 @@ export type RequestConfig<TBody = unknown, TRequest = Request, TResponse = Respo
|
|
|
133
134
|
transport?: Transport<TRequest, TResponse>
|
|
134
135
|
querySerializer?: QuerySerializer
|
|
135
136
|
bodySerializer?: BodySerializer
|
|
136
|
-
parser?: { request?: Parser; response?: Parser }
|
|
137
|
+
parser?: { request?: Parser; response?: Parser; error?: Parser }
|
|
137
138
|
security?: Array<Auth>
|
|
138
139
|
auth?: AuthResolver
|
|
139
140
|
}
|
|
@@ -243,6 +244,7 @@ export type ClientInstance<TRequest = Request, TResponse = Response> = {
|
|
|
243
244
|
<TBody = unknown>(config: RequestConfig<TBody, TRequest, TResponse>): Promise<CallResult<TRequest, TResponse>>
|
|
244
245
|
getConfig: () => ClientConfig<TRequest, TResponse>
|
|
245
246
|
setConfig: (config: ClientConfig<TRequest, TResponse>) => ClientConfig<TRequest, TResponse>
|
|
247
|
+
getUrl: <TBody = unknown>(config: RequestConfig<TBody, TRequest, TResponse>) => string
|
|
246
248
|
interceptors: Interceptors<TRequest, TResponse>
|
|
247
249
|
createClient: (config?: ClientConfig<TRequest, TResponse>) => ClientInstance<TRequest, TResponse>
|
|
248
250
|
}
|
|
@@ -282,13 +284,30 @@ function isFormBody(body: unknown): body is BodyInit {
|
|
|
282
284
|
)
|
|
283
285
|
}
|
|
284
286
|
|
|
287
|
+
function appendFormDataValue(formData: FormData, key: string, value: unknown): void {
|
|
288
|
+
if (value === undefined || value === null) return
|
|
289
|
+
if (value instanceof Blob) formData.append(key, value)
|
|
290
|
+
else if (value instanceof Date) formData.append(key, value.toISOString())
|
|
291
|
+
else if (typeof value === 'object') formData.append(key, JSON.stringify(value))
|
|
292
|
+
else formData.append(key, String(value))
|
|
293
|
+
}
|
|
294
|
+
|
|
285
295
|
/**
|
|
286
296
|
* Default body serializer: passes binary/form bodies through and JSON-serializes everything else.
|
|
287
|
-
* For `
|
|
297
|
+
* For `multipart/form-data` plain objects become `FormData` and for
|
|
298
|
+
* `application/x-www-form-urlencoded` they become `URLSearchParams`.
|
|
288
299
|
*/
|
|
289
300
|
export const defaultBodySerializer: BodySerializer = (body, contentType) => {
|
|
290
301
|
if (body === undefined || body === null) return undefined
|
|
291
302
|
if (isFormBody(body)) return body as BodyInit
|
|
303
|
+
if (contentType?.includes('multipart/form-data')) {
|
|
304
|
+
const formData = new FormData()
|
|
305
|
+
for (const [key, value] of Object.entries(body as Record<string, unknown>)) {
|
|
306
|
+
if (Array.isArray(value)) for (const item of value) appendFormDataValue(formData, key, item)
|
|
307
|
+
else appendFormDataValue(formData, key, value)
|
|
308
|
+
}
|
|
309
|
+
return formData
|
|
310
|
+
}
|
|
292
311
|
if (contentType?.includes('application/x-www-form-urlencoded')) {
|
|
293
312
|
return new URLSearchParams(body as Record<string, string>)
|
|
294
313
|
}
|
|
@@ -337,6 +356,19 @@ function mergeHeaders(...sources: Array<HeadersInit | undefined>): Record<string
|
|
|
337
356
|
return Object.assign({}, ...sources.map(serializeHeaders))
|
|
338
357
|
}
|
|
339
358
|
|
|
359
|
+
/**
|
|
360
|
+
* Joins the base and request URL parts, interpolates `{param}` segments from the path params
|
|
361
|
+
* (URL-encoded), and appends the serialized query. Shared by the send path and `getUrl` so both
|
|
362
|
+
* produce an identical URL.
|
|
363
|
+
*/
|
|
364
|
+
function serializeUrl(parts: Array<string | undefined>, pathParams: Record<string, unknown>, search: string): string {
|
|
365
|
+
const path = parts
|
|
366
|
+
.filter(Boolean)
|
|
367
|
+
.join('')
|
|
368
|
+
.replace(/\{([^{}]+)\}/g, (_, key: string) => encodeURIComponent(String(pathParams[key] ?? '')))
|
|
369
|
+
return path + (search ? `?${search}` : '')
|
|
370
|
+
}
|
|
371
|
+
|
|
340
372
|
/**
|
|
341
373
|
* Creates a transport-agnostic interceptor channel. Interceptors run in registration order; `eject`
|
|
342
374
|
* removes one by id and `update` swaps its function in place without reordering.
|
|
@@ -425,9 +457,7 @@ export function createClientCore<TRequest = Request, TResponse = Response>(
|
|
|
425
457
|
const bodySerializer = requestConfig.bodySerializer ?? config.bodySerializer ?? defaultBodySerializer
|
|
426
458
|
|
|
427
459
|
const headers = mergeHeaders(config.headers, requestConfig.headers)
|
|
428
|
-
|
|
429
|
-
headers['Content-Type'] = requestConfig.contentType
|
|
430
|
-
}
|
|
460
|
+
const requestContentType = requestConfig.contentType ?? headers['Content-Type'] ?? headers['content-type']
|
|
431
461
|
|
|
432
462
|
const query: Record<string, unknown> = { ...((requestConfig.query ?? requestConfig.params) as Record<string, unknown> | undefined) }
|
|
433
463
|
|
|
@@ -440,19 +470,21 @@ export function createClientCore<TRequest = Request, TResponse = Response>(
|
|
|
440
470
|
|
|
441
471
|
const rawBody = requestConfig.body
|
|
442
472
|
const validatedBody = await runParser(requestConfig.parser?.request, rawBody)
|
|
443
|
-
const
|
|
444
|
-
|
|
445
|
-
|
|
446
|
-
|
|
447
|
-
|
|
448
|
-
|
|
449
|
-
|
|
473
|
+
const body = bodySerializer(validatedBody, requestContentType)
|
|
474
|
+
// A FormData body must keep its Content-Type unset so the runtime appends the multipart boundary.
|
|
475
|
+
if (body instanceof FormData) {
|
|
476
|
+
delete headers['Content-Type']
|
|
477
|
+
delete headers['content-type']
|
|
478
|
+
} else if (requestConfig.contentType) {
|
|
479
|
+
headers['Content-Type'] = requestConfig.contentType
|
|
480
|
+
}
|
|
481
|
+
const url = serializeUrl([config.baseURL, requestConfig.baseURL, requestConfig.url], requestConfig.path ?? {}, querySerializer(query))
|
|
450
482
|
|
|
451
483
|
let resolvedRequest: ResolvedRequest = {
|
|
452
484
|
url,
|
|
453
485
|
method: (requestConfig.method ?? 'GET').toUpperCase(),
|
|
454
486
|
headers,
|
|
455
|
-
body
|
|
487
|
+
body,
|
|
456
488
|
signal: requestConfig.signal,
|
|
457
489
|
credentials: requestConfig.credentials,
|
|
458
490
|
responseType: requestConfig.responseType,
|
|
@@ -478,11 +510,12 @@ export function createClientCore<TRequest = Request, TResponse = Response>(
|
|
|
478
510
|
}
|
|
479
511
|
|
|
480
512
|
const data = isSuccess ? await runParser(requestConfig.parser?.response, result.data) : undefined
|
|
513
|
+
const error = isSuccess ? undefined : await runParser(requestConfig.parser?.error, result.data)
|
|
481
514
|
|
|
482
515
|
return {
|
|
483
516
|
status: result.status,
|
|
484
517
|
data,
|
|
485
|
-
error
|
|
518
|
+
error,
|
|
486
519
|
request: result.request,
|
|
487
520
|
response: result.response,
|
|
488
521
|
}
|
|
@@ -493,6 +526,11 @@ export function createClientCore<TRequest = Request, TResponse = Response>(
|
|
|
493
526
|
config = { ...config, ...next, headers: { ...serializeHeaders(config.headers), ...serializeHeaders(next.headers) } }
|
|
494
527
|
return config
|
|
495
528
|
}
|
|
529
|
+
client.getUrl = (requestConfig) => {
|
|
530
|
+
const querySerializer = requestConfig.querySerializer ?? config.querySerializer ?? defaultQuerySerializer
|
|
531
|
+
const query: Record<string, unknown> = { ...((requestConfig.query ?? requestConfig.params) as Record<string, unknown> | undefined) }
|
|
532
|
+
return serializeUrl([config.baseURL, requestConfig.baseURL, requestConfig.url], requestConfig.path ?? {}, querySerializer(query))
|
|
533
|
+
}
|
|
496
534
|
client.interceptors = interceptors
|
|
497
535
|
client.createClient = (next) => createClientCore({ defaultTransport, ...config, ...next })
|
|
498
536
|
|