@kubb/plugin-fetch 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.
@@ -7,14 +7,8 @@ import { ParseError, type StandardSchemaValidator, validateStandardSchema } from
7
7
  */
8
8
  export type SuccessStatusCode = '200' | '201' | '202' | '203' | '204' | '205' | '206' | '207' | '208' | '226'
9
9
 
10
- /**
11
- * The success members of a per-status responses record.
12
- */
13
10
  export type SuccessOf<TResponses> = TResponses[Extract<keyof TResponses, SuccessStatusCode>]
14
11
 
15
- /**
16
- * The error members of a per-status responses record, every documented status that is not a 2xx.
17
- */
18
12
  export type ErrorOf<TResponses> = TResponses[Exclude<keyof TResponses, SuccessStatusCode>]
19
13
 
20
14
  /**
@@ -23,12 +17,12 @@ export type ErrorOf<TResponses> = TResponses[Exclude<keyof TResponses, SuccessSt
23
17
  export type ToStatusNumber<TStatus> = TStatus extends `${infer TNumber extends number}` ? TNumber : number
24
18
 
25
19
  /**
26
- * The plain body of a per-status response, unwrapping the `{ contentType; data }` union so an error result keeps the bare body union on `error`.
20
+ * Unwraps content-type variants to keep the bare body on `error`.
27
21
  */
28
22
  export type DataOf<T> = T extends { contentType: string; data: infer TData } ? TData : T
29
23
 
30
24
  /**
31
- * The success variant for a single status, flattened so the negotiated `contentType` sits next to `data` and `switch (result.contentType)` narrows it.
25
+ * Keeps `contentType` beside `data` so switching on it narrows the body.
32
26
  */
33
27
  export type SuccessVariant<TStatus, TEntry, TRequest, TResponse> = TEntry extends { contentType: string; data: unknown }
34
28
  ? TEntry extends { contentType: infer TContentType; data: infer TData }
@@ -37,7 +31,7 @@ export type SuccessVariant<TStatus, TEntry, TRequest, TResponse> = TEntry extend
37
31
  : { status: ToStatusNumber<TStatus>; data: TEntry; error: undefined; contentType: string | undefined; request: TRequest; response: TResponse }
38
32
 
39
33
  /**
40
- * One result variant for a single documented status, keyed by the numeric `status` so a `switch (result.status)` narrows `data` or `error`.
34
+ * Narrows `data` or `error` when switching on the numeric status.
41
35
  */
42
36
  export type ResultByStatus<TResponses, TStatus extends keyof TResponses, TRequest, TResponse> = TStatus extends SuccessStatusCode
43
37
  ? SuccessVariant<TStatus, TResponses[TStatus], TRequest, TResponse>
@@ -50,22 +44,19 @@ export type ResultByStatus<TResponses, TStatus extends keyof TResponses, TReques
50
44
  response: TResponse
51
45
  }
52
46
 
53
- /**
54
- * The union of every documented status' result variant.
55
- */
56
47
  export type ResultUnion<TResponses, TRequest, TResponse> = {
57
48
  [TStatus in keyof TResponses]: ResultByStatus<TResponses, TStatus, TRequest, TResponse>
58
49
  }[keyof TResponses]
59
50
 
60
51
  /**
61
- * The union of just the success (2xx) status variants, selected by status code so an untyped error payload can never widen `data`.
52
+ * Selects success statuses without letting untyped error payloads widen `data`.
62
53
  */
63
54
  export type SuccessResultUnion<TResponses, TRequest, TResponse> = {
64
55
  [TStatus in Extract<keyof TResponses, SuccessStatusCode>]: ResultByStatus<TResponses, TStatus, TRequest, TResponse>
65
56
  }[Extract<keyof TResponses, SuccessStatusCode>]
66
57
 
67
58
  /**
68
- * The shape every generated function returns, discriminated by the top-level `status`, narrowing to the 2xx variants under `throwOnError` and to every documented status without it.
59
+ * Narrows `data` and `error` by status. With `throwOnError`, includes only success variants.
69
60
  */
70
61
  export type RequestResult<TResponses, ThrowOnError extends boolean = true, TRequest = Request, TResponse = Response> = ThrowOnError extends true
71
62
  ? [SuccessResultUnion<TResponses, TRequest, TResponse>] extends [never]
@@ -92,11 +83,15 @@ export type Unwrappable<T extends { data: unknown; error: unknown }> = Promise<T
92
83
  /**
93
84
  * Attaches `unwrap()` to a result promise, which rejects with `error` when the result carried one.
94
85
  *
95
- * @example Full result
96
- * `const { data, error } = await getPetById({ path: { petId: 1 } })`
86
+ * @example
87
+ * ```ts
88
+ * const { data, error } = await getPetById({ path: { petId: 1 } })
89
+ * ```
97
90
  *
98
- * @example Success body only
99
- * `const pet = await getPetById({ path: { petId: 1 } }).unwrap()`
91
+ * @example
92
+ * ```ts
93
+ * const pet = await getPetById({ path: { petId: 1 } }).unwrap()
94
+ * ```
100
95
  */
101
96
  export function withUnwrap<T extends { data: unknown; error: unknown }>(promise: Promise<T>): Unwrappable<T> {
102
97
  const unwrappable = promise as Unwrappable<T>
@@ -118,34 +113,28 @@ export type UnwrappedResult<TResponses, ThrowOnError extends boolean = true, TRe
118
113
 
119
114
  /**
120
115
  * Returns the success body when `throwOnError` is true, or the full result when it is false.
121
- * Backs generated operations with `returnType: 'data'`.
122
116
  */
123
117
  export function unwrapResult<T extends { data: unknown; error: unknown }>(promise: Promise<T>, throwOnError: boolean | undefined): Promise<T | T['data']> {
124
118
  return promise.then((result) => ((throwOnError ?? true) ? result.data : result))
125
119
  }
126
120
 
127
- /**
128
- * The data-shaped keys of the grouped options object, which `Options` re-adds typed per operation.
129
- */
130
121
  export type DataShape = { body?: unknown; cookies?: unknown; headers?: unknown; path?: unknown; query?: unknown }
131
122
 
132
123
  export type RequestCredentials = 'omit' | 'same-origin' | 'include'
133
124
  export type ResponseType = 'arraybuffer' | 'blob' | 'document' | 'json' | 'text' | 'stream'
134
125
 
135
126
  /**
136
- * Turns a raw response body into a parsed value, registered per media type as a codec's `deserialize` to handle formats the runtime does not decode itself.
127
+ * Decodes a response body for a codec's media type.
137
128
  */
138
129
  export type Deserializer<T = unknown> = (raw: unknown, contentType: string) => T | Promise<T>
139
130
 
140
131
  /**
141
- * Serializes a request body for a single media type, registered per content type as a codec's `serialize` to encode formats the default serializer does not handle.
132
+ * Encodes a request body for a codec's media type.
142
133
  */
143
134
  export type ContentBodySerializer = (body: unknown, contentType?: string) => RequestBody | undefined
144
135
 
145
136
  /**
146
- * A per-content-type codec registered on `codecs`, keyed by content type. `serialize` encodes the
147
- * request body for that media type and `deserialize` decodes the response body. Either half is
148
- * optional, so a codec can handle one direction.
137
+ * Encodes requests or decodes responses for a media type. Either direction can be omitted.
149
138
  */
150
139
  export type Codec = {
151
140
  serialize?: ContentBodySerializer
@@ -153,23 +142,17 @@ export type Codec = {
153
142
  }
154
143
 
155
144
  /**
156
- * The per-call content type selection, where a bare string sets the request content type and the object form also sets the response format sent as `Accept`.
145
+ * A string sets the request content type. The object form can also set `Accept`.
157
146
  */
158
147
  export type ContentType = string | { request?: string; response?: string }
159
148
 
160
149
  /**
161
- * A Standard Schema validator (zod, valibot, arktype) that parses a value before it is sent or after
162
- * it is received. `runValidator` runs it through `validateStandardSchema`. Wired through the per-call
163
- * `validator.request` / `validator.response` / `validator.error` hooks (`error` runs on the error body when a
164
- * non-2xx call does not throw), and `validator.path` / `validator.query` / `validator.headers` for the
165
- * request params.
150
+ * Validates request bodies and params, success bodies, or non-throwing error bodies with Standard Schema.
166
151
  */
167
152
  export type Validator<T = unknown> = StandardSchemaValidator<T>
168
153
 
169
154
  /**
170
- * The failing value and the call it came from, handed to `onValidationError` alongside the `ParseError`.
171
- * `direction` says which side rejected it: the request, the success body, or the error body. `source`
172
- * says which part of it: the body, or one of the request param groups.
155
+ * Identifies the value and request that failed validation in `onValidationError`.
173
156
  */
174
157
  export type ValidationErrorContext = {
175
158
  value: unknown
@@ -181,9 +164,7 @@ export type ValidationErrorContext = {
181
164
  }
182
165
 
183
166
  /**
184
- * Decides what a failed validation does. Returning nothing rethrows the `ParseError`; returning
185
- * `{ value }` resolves the call with that value instead, so a drifted body can be reported and still
186
- * delivered. The box keeps an explicit `{ value: undefined }` substitution distinct from declining.
167
+ * Return `{ value }` to substitute a failed value, including `undefined`. Returning nothing rethrows the `ParseError`.
187
168
  */
188
169
  export type ValidationErrorHandler = (error: ParseError, context: ValidationErrorContext) => { value: unknown } | void | Promise<{ value: unknown } | void>
189
170
 
@@ -199,7 +180,7 @@ export type Auth = {
199
180
  }
200
181
 
201
182
  /**
202
- * The raw token a consumer returns for a scheme (or `user:password` for basic), or `undefined` to skip it.
183
+ * Return `user:password` for basic auth, or `undefined` to skip a scheme.
203
184
  */
204
185
  export type AuthToken = string | undefined
205
186
 
@@ -213,9 +194,6 @@ export type AuthResolver = AuthToken | ((auth: Auth) => AuthToken | Promise<Auth
213
194
  */
214
195
  export type FetchOptions = RequestInit & { next?: Record<string, unknown> }
215
196
 
216
- /**
217
- * The request a generated function hands to the runtime, with `body` / `headers` / `path` / `query` from the grouped options.
218
- */
219
197
  export type RequestConfig<TBody = unknown, TRequest = Request, TResponse = Response> = {
220
198
  baseURL?: string
221
199
  url?: string
@@ -244,8 +222,7 @@ export type RequestConfig<TBody = unknown, TRequest = Request, TResponse = Respo
244
222
  }
245
223
 
246
224
  /**
247
- * The grouped options object passed to every generated function: the request config minus the
248
- * data-shaped keys and the literal `url`, plus the per-operation `<Name>Request`.
225
+ * Combines client options with the operation's typed request params and body.
249
226
  */
250
227
  export type Options<TData extends DataShape, ThrowOnError extends boolean = true, TRequest = Request, TResponse = Response> = Omit<
251
228
  RequestConfig<unknown, TRequest, TResponse>,
@@ -304,9 +281,6 @@ export type TransportResult<TData = unknown, TRequest = Request, TResponse = Res
304
281
  */
305
282
  export type Transport<TRequest = Request, TResponse = Response> = (request: ResolvedRequest) => Promise<TransportResult<unknown, TRequest, TResponse>>
306
283
 
307
- /**
308
- * The result a resolved call produces before it is cast to `RequestResult` by the generated wrapper.
309
- */
310
284
  export type CallResult<TRequest = Request, TResponse = Response> = {
311
285
  status: number
312
286
  data: unknown
@@ -328,9 +302,6 @@ export type InterceptorStack<T, TContext = never> = {
328
302
  run: (value: T, context?: TContext) => Promise<T>
329
303
  }
330
304
 
331
- /**
332
- * The three interceptor channels every client instance exposes.
333
- */
334
305
  export type Interceptors<TRequest = Request, TResponse = Response> = {
335
306
  request: InterceptorStack<ResolvedRequest, RequestConfig<unknown, TRequest, TResponse>>
336
307
  response: InterceptorStack<TransportResult<unknown, TRequest, TResponse>, RequestConfig<unknown, TRequest, TResponse>>
@@ -436,13 +407,6 @@ function getHeader(headers: Record<string, string>, name: string): string | unde
436
407
  return key ? headers[key] : undefined
437
408
  }
438
409
 
439
- function hasHeader(headers: Record<string, string>, name: string): boolean {
440
- return Object.keys(headers).some((k) => k.toLowerCase() === name.toLowerCase())
441
- }
442
-
443
- /**
444
- * Joins the URL parts, interpolates URL-encoded `{param}` segments, and appends the serialized query, shared by the send path and `getUrl`.
445
- */
446
410
  function serializeUrl({
447
411
  parts,
448
412
  pathParams,
@@ -508,18 +472,23 @@ export async function resolveAuth(params: {
508
472
  const token = typeof auth === 'function' ? await auth(scheme) : auth
509
473
  if (token === undefined) continue
510
474
 
511
- if (scheme.type === 'apiKey') {
512
- const name = scheme.name ?? 'Authorization'
513
- if (scheme.in === 'query') {
514
- if (query[name] === undefined) query[name] = token
515
- } else if (scheme.in === 'cookie') {
516
- headers['Cookie'] = [headers['Cookie'], `${name}=${token}`].filter(Boolean).join('; ')
517
- } else if (!hasHeader(headers, name)) {
518
- headers[name] = token
475
+ if (scheme.type !== 'apiKey') {
476
+ if (getHeader(headers, 'Authorization') === undefined) {
477
+ headers['Authorization'] = scheme.scheme === 'basic' ? `Basic ${btoa(token)}` : `Bearer ${token}`
519
478
  }
520
- } else if (!hasHeader(headers, 'Authorization')) {
521
- headers['Authorization'] = scheme.scheme === 'basic' ? `Basic ${btoa(token)}` : `Bearer ${token}`
479
+ return
480
+ }
481
+
482
+ const name = scheme.name ?? 'Authorization'
483
+ if (scheme.in === 'query') {
484
+ if (query[name] === undefined) query[name] = token
485
+ return
486
+ }
487
+ if (scheme.in === 'cookie') {
488
+ headers['Cookie'] = [headers['Cookie'], `${name}=${token}`].filter(Boolean).join('; ')
489
+ return
522
490
  }
491
+ if (getHeader(headers, name) === undefined) headers[name] = token
523
492
  return
524
493
  }
525
494
  }
@@ -546,35 +515,17 @@ async function runValidator<T>({
546
515
  }
547
516
  }
548
517
 
549
- /**
550
- * The base media type of a `Content-Type` value, lowercased and stripped of any `; charset=...` parameters.
551
- */
552
518
  function baseContentType(value: string | null | undefined): string | undefined {
553
519
  if (!value) return undefined
554
520
  return value.split(';')[0]!.trim().toLowerCase() || undefined
555
521
  }
556
522
 
557
- /**
558
- * Reads the negotiated response content type from the response headers as a base media type.
559
- */
560
523
  function getResponseContentType(headers: Headers | Record<string, string> | undefined): string | undefined {
561
524
  if (!headers) return undefined
562
525
  const value = headers instanceof Headers ? headers.get('Content-Type') : (headers['Content-Type'] ?? headers['content-type'])
563
526
  return baseContentType(value)
564
527
  }
565
528
 
566
- /**
567
- * Normalizes the `contentType` option to its `{ request, response }` form, treating a bare string as the request content type.
568
- */
569
- function resolveContentType(contentType: ContentType | undefined): { request?: string; response?: string } {
570
- if (typeof contentType === 'string') return { request: contentType }
571
- return contentType ?? {}
572
- }
573
-
574
- /**
575
- * The per-concern serializers for a call, the per-call serializer winning over the client's and
576
- * falling back to the defaults.
577
- */
578
529
  function resolveSerializers({ config, requestConfig }: { config: { serializer?: Serializers }; requestConfig: { serializer?: Serializers } }) {
579
530
  return {
580
531
  querySerializer: requestConfig.serializer?.query ?? config.serializer?.query ?? defaultQuerySerializer,
@@ -583,11 +534,6 @@ function resolveSerializers({ config, requestConfig }: { config: { serializer?:
583
534
  }
584
535
  }
585
536
 
586
- /**
587
- * Resolves everything a call needs before it touches the transport: merged headers with the
588
- * negotiated content type, auth on headers or query, serialized cookies, the validated and
589
- * serialized body, and the full URL.
590
- */
591
537
  async function resolveRequest<TBody, TRequest, TResponse>({
592
538
  config,
593
539
  requestConfig,
@@ -613,9 +559,10 @@ async function resolveRequest<TBody, TRequest, TResponse>({
613
559
  )
614
560
 
615
561
  const headers = serializeHeaders(applyHeaderStyles(validatedHeaders as HeadersInit | undefined, requestConfig.styles?.header))
616
- const { request: requestContentTypeOption, response: responseContentType } = resolveContentType(requestConfig.contentType)
562
+ const contentType = requestConfig.contentType
563
+ const { request: requestContentTypeOption, response: responseContentType } = typeof contentType === 'string' ? { request: contentType } : (contentType ?? {})
617
564
  const requestContentType = requestContentTypeOption ?? getHeader(headers, 'content-type')
618
- if (responseContentType && !hasHeader(headers, 'accept')) {
565
+ if (responseContentType && getHeader(headers, 'accept') === undefined) {
619
566
  headers['Accept'] = responseContentType
620
567
  }
621
568
 
@@ -645,15 +592,15 @@ async function resolveRequest<TBody, TRequest, TResponse>({
645
592
  const body = contentCodec?.serialize
646
593
  ? contentCodec.serialize(validatedBody, requestContentType)
647
594
  : bodySerializer({ body: validatedBody, contentType: requestContentType, encoding: requestConfig.styles?.body })
648
- // A FormData body must keep its Content-Type unset so the runtime appends the multipart boundary.
595
+ if (requestContentTypeOption) headers['Content-Type'] = requestContentTypeOption
596
+ if (usesDefaultBodySerializer && isDefaultJsonBody(validatedBody) && getHeader(headers, 'content-type') === undefined) {
597
+ headers['Content-Type'] = 'application/json'
598
+ }
599
+ // The transport must set the multipart boundary, even when a content type was explicitly provided.
649
600
  if (body instanceof FormData) {
650
601
  for (const key of Object.keys(headers)) {
651
602
  if (key.toLowerCase() === 'content-type') delete headers[key]
652
603
  }
653
- } else if (requestContentTypeOption) {
654
- headers['Content-Type'] = requestContentTypeOption
655
- } else if (usesDefaultBodySerializer && isDefaultJsonBody(validatedBody) && !hasHeader(headers, 'content-type')) {
656
- headers['Content-Type'] = 'application/json'
657
604
  }
658
605
 
659
606
  const url = serializeUrl({
@@ -682,74 +629,7 @@ async function resolveRequest<TBody, TRequest, TResponse>({
682
629
  }
683
630
 
684
631
  /**
685
- * Turns a transport result into the call result: decodes the body through the matching codec,
686
- * validates it, and throws a `ResponseError` (after running the error interceptors) for a non-2xx
687
- * response under `throwOnError`.
688
- */
689
- async function settleResult<TRequest, TResponse>({
690
- result,
691
- request,
692
- codecs,
693
- throwOnError,
694
- validator,
695
- onValidationError,
696
- errorInterceptors,
697
- requestConfig,
698
- }: {
699
- result: TransportResult<unknown, TRequest, TResponse>
700
- request: ResolvedRequest
701
- codecs: Record<string, Codec>
702
- throwOnError: boolean
703
- validator: { response?: Validator; error?: Validator } | undefined
704
- onValidationError: ValidationErrorHandler | undefined
705
- errorInterceptors: InterceptorStack<ResponseError<unknown, TRequest, TResponse>, RequestConfig<unknown, TRequest, TResponse>>
706
- requestConfig: RequestConfig<unknown, TRequest, TResponse>
707
- }): Promise<CallResult<TRequest, TResponse>> {
708
- const isSuccess = result.status >= 200 && result.status < 300
709
- const contentType = result.contentType ?? getResponseContentType(result.headers)
710
- let decoded = result.data
711
- if (contentType) {
712
- const codec = codecs[contentType]
713
- if (codec?.deserialize) decoded = await codec.deserialize(result.data, contentType)
714
- }
715
-
716
- const validationContext = { method: request.method, url: request.url, status: result.status }
717
-
718
- if (isSuccess) {
719
- const data = await runValidator({
720
- validator: validator?.response,
721
- value: decoded,
722
- context: { direction: 'response', source: 'body', ...validationContext },
723
- onValidationError,
724
- })
725
- return { status: result.status, data, error: undefined, contentType, request: result.request, response: result.response }
726
- }
727
-
728
- const error = await runValidator({
729
- validator: validator?.error,
730
- value: decoded,
731
- context: { direction: 'error', source: 'body', ...validationContext },
732
- onValidationError,
733
- })
734
- if (throwOnError) {
735
- const responseError = new ResponseError({
736
- data: error,
737
- status: result.status,
738
- statusText: result.statusText,
739
- contentType,
740
- request: result.request,
741
- response: result.response,
742
- method: request.method,
743
- url: request.url,
744
- })
745
- await errorInterceptors.run(responseError, requestConfig)
746
- throw responseError
747
- }
748
- return { status: result.status, data: undefined, error, contentType, request: result.request, response: result.response }
749
- }
750
-
751
- /**
752
- * Builds the shared client core bound to a transport, exported by each plugin as `client` plus a `createClient` factory.
632
+ * Creates an isolated client using the supplied default transport.
753
633
  */
754
634
  export function createClientCore<TRequest = Request, TResponse = Response>(
755
635
  options: { defaultTransport: Transport<TRequest, TResponse> } & ClientConfig<TRequest, TResponse>,
@@ -770,16 +650,41 @@ export function createClientCore<TRequest = Request, TResponse = Response>(
770
650
  const resolvedRequest = await interceptors.request.run(request, requestConfig)
771
651
  const result = await interceptors.response.run(await transport(resolvedRequest), requestConfig)
772
652
 
773
- return settleResult({
774
- result,
775
- request: resolvedRequest,
776
- codecs,
777
- throwOnError: requestConfig.throwOnError ?? config.throwOnError ?? true,
778
- validator: requestConfig.validator,
779
- onValidationError: requestConfig.onValidationError ?? config.onValidationError,
780
- errorInterceptors: interceptors.error,
781
- requestConfig,
653
+ const throwOnError = requestConfig.throwOnError ?? config.throwOnError ?? true
654
+ const validator = requestConfig.validator
655
+ const onValidationError = requestConfig.onValidationError ?? config.onValidationError
656
+ const isSuccess = result.status >= 200 && result.status < 300
657
+ const contentType = result.contentType ?? getResponseContentType(result.headers)
658
+ const codec = contentType ? codecs[contentType] : undefined
659
+ const decoded = contentType && codec?.deserialize ? await codec.deserialize(result.data, contentType) : result.data
660
+ const data = await runValidator({
661
+ validator: isSuccess ? validator?.response : validator?.error,
662
+ value: decoded,
663
+ context: { direction: isSuccess ? 'response' : 'error', source: 'body', method: resolvedRequest.method, url: resolvedRequest.url, status: result.status },
664
+ onValidationError,
782
665
  })
666
+ if (!isSuccess && throwOnError) {
667
+ const responseError = new ResponseError({
668
+ data,
669
+ status: result.status,
670
+ statusText: result.statusText,
671
+ contentType,
672
+ request: result.request,
673
+ response: result.response,
674
+ method: resolvedRequest.method,
675
+ url: resolvedRequest.url,
676
+ })
677
+ await interceptors.error.run(responseError, requestConfig)
678
+ throw responseError
679
+ }
680
+ return {
681
+ status: result.status,
682
+ data: isSuccess ? data : undefined,
683
+ error: isSuccess ? undefined : data,
684
+ contentType,
685
+ request: result.request,
686
+ response: result.response,
687
+ }
783
688
  }) as ClientInstance<TRequest, TResponse>
784
689
 
785
690
  client.getConfig = () => config
@@ -804,9 +709,6 @@ export function createClientCore<TRequest = Request, TResponse = Response>(
804
709
  return client
805
710
  }
806
711
 
807
- /**
808
- * Picks a `responseType` from a `Content-Type` header, or `undefined` when it is not recognized.
809
- */
810
712
  function detectResponseType(contentType: string | null): ResponseType | undefined {
811
713
  if (!contentType) return undefined
812
714
  if (contentType.includes('text/event-stream')) return 'stream'
@@ -816,9 +718,6 @@ function detectResponseType(contentType: string | null): ResponseType | undefine
816
718
  return undefined
817
719
  }
818
720
 
819
- /**
820
- * Parses a `fetch` response body using the `responseType` (explicit or detected from the `Content-Type`), falling back to JSON-then-text.
821
- */
822
721
  async function parseResponse(response: Response, responseType?: ResponseType): Promise<unknown> {
823
722
  if (response.status === 204 || response.status === 205 || response.status === 304 || !response.body) {
824
723
  return undefined
@@ -855,7 +754,7 @@ async function parseResponse(response: Response, responseType?: ResponseType): P
855
754
  */
856
755
  export const defaultTransport: Transport = async (request: ResolvedRequest): Promise<TransportResult> => {
857
756
  const init: RequestInit = {
858
- ...request.options, // cache, mode, redirect, keepalive, duplex, next, …
757
+ ...request.options,
859
758
  method: request.method,
860
759
  headers: request.headers,
861
760
  body: request.body,
@@ -917,10 +816,20 @@ function parseEvent<TData>(raw: string): ServerSentEvent<TData> | undefined {
917
816
  const index = line.indexOf(':')
918
817
  const field = index === -1 ? line : line.slice(0, index)
919
818
  const value = index === -1 ? '' : line.slice(index + 1).replace(/^ /, '')
920
- if (field === 'data') data.push(value)
921
- else if (field === 'event') event.event = value
922
- else if (field === 'id') event.id = value
923
- else if (field === 'retry' && Number.isFinite(Number(value))) event.retry = Number(value)
819
+ switch (field) {
820
+ case 'data':
821
+ data.push(value)
822
+ break
823
+ case 'event':
824
+ event.event = value
825
+ break
826
+ case 'id':
827
+ event.id = value
828
+ break
829
+ case 'retry':
830
+ if (Number.isFinite(Number(value))) event.retry = Number(value)
831
+ break
832
+ }
924
833
  }
925
834
 
926
835
  if (!seen) return undefined
@@ -960,8 +869,7 @@ export async function* parseEventStream<TData = unknown>(
960
869
  }
961
870
 
962
871
  /**
963
- * The resolved shape returned by a generated `text/event-stream` operation: the typed event
964
- * `stream` plus the native `response`.
872
+ * Returns parsed events alongside the native response.
965
873
  */
966
874
  export type EventStreamResult<TData = unknown, TResponse = Response> = {
967
875
  stream: AsyncGenerator<ServerSentEvent<TData>>
@@ -969,8 +877,7 @@ export type EventStreamResult<TData = unknown, TResponse = Response> = {
969
877
  }
970
878
 
971
879
  /**
972
- * Wraps a transport result whose `data` is a streaming body into an `EventStreamResult`, exposing
973
- * the parsed events as a typed async iterator. Generated SSE operations call this.
880
+ * Exposes a streaming response body as typed Server-Sent Events.
974
881
  */
975
882
  export async function toEventStream<TData = unknown>(result: Promise<{ data: unknown; response: Response }>): Promise<EventStreamResult<TData>> {
976
883
  const { data, response } = await result
@@ -150,12 +150,17 @@ function jsonReplacer(_key: string, value: unknown): unknown {
150
150
 
151
151
  function appendFormDataValue({ formData, key, value, contentType }: { formData: FormData; key: string; value: unknown; contentType?: string }): void {
152
152
  if (value === undefined || value === null) return
153
- if (value instanceof Blob) formData.append(key, value)
154
- else if (typeof value === 'object' && !(value instanceof Date)) {
153
+ if (value instanceof Blob) {
154
+ formData.append(key, value)
155
+ return
156
+ }
157
+ if (typeof value === 'object' && !(value instanceof Date)) {
155
158
  const json = JSON.stringify(value, jsonReplacer)
156
159
  // A part's media type can only be set by wrapping the value in a typed Blob.
157
160
  formData.append(key, contentType ? new Blob([json], { type: contentType }) : json)
158
- } else formData.append(key, toValue(value))
161
+ return
162
+ }
163
+ formData.append(key, toValue(value))
159
164
  }
160
165
 
161
166
  /**
@@ -179,8 +184,11 @@ export const defaultBodySerializer: BodySerializer = ({ body, contentType, encod
179
184
  const formData = new FormData()
180
185
  for (const [key, value] of Object.entries(body as Record<string, unknown>)) {
181
186
  const partContentType = encoding?.[key]?.contentType
182
- if (Array.isArray(value)) for (const item of value) appendFormDataValue({ formData, key, value: item, contentType: partContentType })
183
- else appendFormDataValue({ formData, key, value, contentType: partContentType })
187
+ if (Array.isArray(value)) {
188
+ for (const item of value) appendFormDataValue({ formData, key, value: item, contentType: partContentType })
189
+ continue
190
+ }
191
+ appendFormDataValue({ formData, key, value, contentType: partContentType })
184
192
  }
185
193
  return formData
186
194
  }