@kubb/plugin-fetch 5.0.0-beta.75 → 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.
@@ -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` hooks.
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
  }
@@ -283,13 +284,30 @@ function isFormBody(body: unknown): body is BodyInit {
283
284
  )
284
285
  }
285
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
+
286
295
  /**
287
296
  * Default body serializer: passes binary/form bodies through and JSON-serializes everything else.
288
- * For `application/x-www-form-urlencoded` plain objects become `URLSearchParams`.
297
+ * For `multipart/form-data` plain objects become `FormData` and for
298
+ * `application/x-www-form-urlencoded` they become `URLSearchParams`.
289
299
  */
290
300
  export const defaultBodySerializer: BodySerializer = (body, contentType) => {
291
301
  if (body === undefined || body === null) return undefined
292
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
+ }
293
311
  if (contentType?.includes('application/x-www-form-urlencoded')) {
294
312
  return new URLSearchParams(body as Record<string, string>)
295
313
  }
@@ -439,9 +457,7 @@ export function createClientCore<TRequest = Request, TResponse = Response>(
439
457
  const bodySerializer = requestConfig.bodySerializer ?? config.bodySerializer ?? defaultBodySerializer
440
458
 
441
459
  const headers = mergeHeaders(config.headers, requestConfig.headers)
442
- if (requestConfig.contentType && requestConfig.contentType !== 'multipart/form-data') {
443
- headers['Content-Type'] = requestConfig.contentType
444
- }
460
+ const requestContentType = requestConfig.contentType ?? headers['Content-Type'] ?? headers['content-type']
445
461
 
446
462
  const query: Record<string, unknown> = { ...((requestConfig.query ?? requestConfig.params) as Record<string, unknown> | undefined) }
447
463
 
@@ -454,13 +470,21 @@ export function createClientCore<TRequest = Request, TResponse = Response>(
454
470
 
455
471
  const rawBody = requestConfig.body
456
472
  const validatedBody = await runParser(requestConfig.parser?.request, rawBody)
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
+ }
457
481
  const url = serializeUrl([config.baseURL, requestConfig.baseURL, requestConfig.url], requestConfig.path ?? {}, querySerializer(query))
458
482
 
459
483
  let resolvedRequest: ResolvedRequest = {
460
484
  url,
461
485
  method: (requestConfig.method ?? 'GET').toUpperCase(),
462
486
  headers,
463
- body: bodySerializer(validatedBody, headers['Content-Type'] ?? headers['content-type']),
487
+ body,
464
488
  signal: requestConfig.signal,
465
489
  credentials: requestConfig.credentials,
466
490
  responseType: requestConfig.responseType,
@@ -486,11 +510,12 @@ export function createClientCore<TRequest = Request, TResponse = Response>(
486
510
  }
487
511
 
488
512
  const data = isSuccess ? await runParser(requestConfig.parser?.response, result.data) : undefined
513
+ const error = isSuccess ? undefined : await runParser(requestConfig.parser?.error, result.data)
489
514
 
490
515
  return {
491
516
  status: result.status,
492
517
  data,
493
- error: isSuccess ? undefined : result.data,
518
+ error,
494
519
  request: result.request,
495
520
  response: result.response,
496
521
  }