@kubb/plugin-fetch 5.9.1 → 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 CHANGED
@@ -1506,7 +1506,7 @@ var compatibility = {
1506
1506
  };
1507
1507
  //#endregion
1508
1508
  //#region package.json
1509
- var version = "5.9.1";
1509
+ var version = "5.9.2";
1510
1510
  //#endregion
1511
1511
  //#region src/templates.ts
1512
1512
  /** Absolute path to the fetch client template, copied into `.kubb/client.ts`. */
package/dist/index.js CHANGED
@@ -1480,7 +1480,7 @@ var compatibility = {
1480
1480
  };
1481
1481
  //#endregion
1482
1482
  //#region package.json
1483
- var version = "5.9.1";
1483
+ var version = "5.9.2";
1484
1484
  //#endregion
1485
1485
  //#region src/templates.ts
1486
1486
  /** Absolute path to the fetch client template, copied into `.kubb/client.ts`. */
@@ -5,27 +5,21 @@ import { ParseError, StandardSchemaValidator } from "./standardSchema.js";
5
5
  * HTTP status codes treated as a success, everything else is an error.
6
6
  */
7
7
  export type SuccessStatusCode = '200' | '201' | '202' | '203' | '204' | '205' | '206' | '207' | '208' | '226';
8
- /**
9
- * The success members of a per-status responses record.
10
- */
11
8
  export type SuccessOf<TResponses> = TResponses[Extract<keyof TResponses, SuccessStatusCode>];
12
- /**
13
- * The error members of a per-status responses record, every documented status that is not a 2xx.
14
- */
15
9
  export type ErrorOf<TResponses> = TResponses[Exclude<keyof TResponses, SuccessStatusCode>];
16
10
  /**
17
11
  * Converts a response record's string status key to its numeric literal, leaving non-numeric keys like `default` as `number`.
18
12
  */
19
13
  export type ToStatusNumber<TStatus> = TStatus extends `${infer TNumber extends number}` ? TNumber : number;
20
14
  /**
21
- * The plain body of a per-status response, unwrapping the `{ contentType; data }` union so an error result keeps the bare body union on `error`.
15
+ * Unwraps content-type variants to keep the bare body on `error`.
22
16
  */
23
17
  export type DataOf<T> = T extends {
24
18
  contentType: string;
25
19
  data: infer TData;
26
20
  } ? TData : T;
27
21
  /**
28
- * The success variant for a single status, flattened so the negotiated `contentType` sits next to `data` and `switch (result.contentType)` narrows it.
22
+ * Keeps `contentType` beside `data` so switching on it narrows the body.
29
23
  */
30
24
  export type SuccessVariant<TStatus, TEntry, TRequest, TResponse> = TEntry extends {
31
25
  contentType: string;
@@ -49,7 +43,7 @@ export type SuccessVariant<TStatus, TEntry, TRequest, TResponse> = TEntry extend
49
43
  response: TResponse;
50
44
  };
51
45
  /**
52
- * One result variant for a single documented status, keyed by the numeric `status` so a `switch (result.status)` narrows `data` or `error`.
46
+ * Narrows `data` or `error` when switching on the numeric status.
53
47
  */
54
48
  export type ResultByStatus<TResponses, TStatus extends keyof TResponses, TRequest, TResponse> = TStatus extends SuccessStatusCode ? SuccessVariant<TStatus, TResponses[TStatus], TRequest, TResponse> : {
55
49
  status: ToStatusNumber<TStatus>;
@@ -59,16 +53,13 @@ export type ResultByStatus<TResponses, TStatus extends keyof TResponses, TReques
59
53
  request: TRequest;
60
54
  response: TResponse;
61
55
  };
62
- /**
63
- * The union of every documented status' result variant.
64
- */
65
56
  export type ResultUnion<TResponses, TRequest, TResponse> = { [TStatus in keyof TResponses]: ResultByStatus<TResponses, TStatus, TRequest, TResponse>; }[keyof TResponses];
66
57
  /**
67
- * The union of just the success (2xx) status variants, selected by status code so an untyped error payload can never widen `data`.
58
+ * Selects success statuses without letting untyped error payloads widen `data`.
68
59
  */
69
60
  export type SuccessResultUnion<TResponses, TRequest, TResponse> = { [TStatus in Extract<keyof TResponses, SuccessStatusCode>]: ResultByStatus<TResponses, TStatus, TRequest, TResponse>; }[Extract<keyof TResponses, SuccessStatusCode>];
70
61
  /**
71
- * 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.
62
+ * Narrows `data` and `error` by status. With `throwOnError`, includes only success variants.
72
63
  */
73
64
  export type RequestResult<TResponses, ThrowOnError extends boolean = true, TRequest = Request, TResponse = Response> = ThrowOnError extends true ? [SuccessResultUnion<TResponses, TRequest, TResponse>] extends [never] ? {
74
65
  status: number;
@@ -99,11 +90,15 @@ export type Unwrappable<T extends {
99
90
  /**
100
91
  * Attaches `unwrap()` to a result promise, which rejects with `error` when the result carried one.
101
92
  *
102
- * @example Full result
103
- * `const { data, error } = await getPetById({ path: { petId: 1 } })`
93
+ * @example
94
+ * ```ts
95
+ * const { data, error } = await getPetById({ path: { petId: 1 } })
96
+ * ```
104
97
  *
105
- * @example Success body only
106
- * `const pet = await getPetById({ path: { petId: 1 } }).unwrap()`
98
+ * @example
99
+ * ```ts
100
+ * const pet = await getPetById({ path: { petId: 1 } }).unwrap()
101
+ * ```
107
102
  */
108
103
  export declare function withUnwrap<T extends {
109
104
  data: unknown;
@@ -116,15 +111,11 @@ export declare function withUnwrap<T extends {
116
111
  export type UnwrappedResult<TResponses, ThrowOnError extends boolean = true, TRequest = Request, TResponse = Response> = ThrowOnError extends true ? RequestResult<TResponses, true, TRequest, TResponse>['data'] : RequestResult<TResponses, ThrowOnError, TRequest, TResponse>;
117
112
  /**
118
113
  * Returns the success body when `throwOnError` is true, or the full result when it is false.
119
- * Backs generated operations with `returnType: 'data'`.
120
114
  */
121
115
  export declare function unwrapResult<T extends {
122
116
  data: unknown;
123
117
  error: unknown;
124
118
  }>(promise: Promise<T>, throwOnError: boolean | undefined): Promise<T | T['data']>;
125
- /**
126
- * The data-shaped keys of the grouped options object, which `Options` re-adds typed per operation.
127
- */
128
119
  export type DataShape = {
129
120
  body?: unknown;
130
121
  cookies?: unknown;
@@ -135,41 +126,33 @@ export type DataShape = {
135
126
  export type RequestCredentials = 'omit' | 'same-origin' | 'include';
136
127
  export type ResponseType = 'arraybuffer' | 'blob' | 'document' | 'json' | 'text' | 'stream';
137
128
  /**
138
- * 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.
129
+ * Decodes a response body for a codec's media type.
139
130
  */
140
131
  export type Deserializer<T = unknown> = (raw: unknown, contentType: string) => T | Promise<T>;
141
132
  /**
142
- * 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.
133
+ * Encodes a request body for a codec's media type.
143
134
  */
144
135
  export type ContentBodySerializer = (body: unknown, contentType?: string) => RequestBody | undefined;
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;
152
141
  deserialize?: Deserializer;
153
142
  };
154
143
  /**
155
- * 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`.
144
+ * A string sets the request content type. The object form can also set `Accept`.
156
145
  */
157
146
  export type ContentType = string | {
158
147
  request?: string;
159
148
  response?: string;
160
149
  };
161
150
  /**
162
- * A Standard Schema validator (zod, valibot, arktype) that parses a value before it is sent or after
163
- * it is received. `runValidator` runs it through `validateStandardSchema`. Wired through the per-call
164
- * `validator.request` / `validator.response` / `validator.error` hooks (`error` runs on the error body when a
165
- * non-2xx call does not throw), and `validator.path` / `validator.query` / `validator.headers` for the
166
- * request params.
151
+ * Validates request bodies and params, success bodies, or non-throwing error bodies with Standard Schema.
167
152
  */
168
153
  export type Validator<T = unknown> = StandardSchemaValidator<T>;
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;
@@ -180,9 +163,7 @@ export type ValidationErrorContext = {
180
163
  status?: number;
181
164
  };
182
165
  /**
183
- * Decides what a failed validation does. Returning nothing rethrows the `ParseError`; returning
184
- * `{ value }` resolves the call with that value instead, so a drifted body can be reported and still
185
- * delivered. The box keeps an explicit `{ value: undefined }` substitution distinct from declining.
166
+ * Return `{ value }` to substitute a failed value, including `undefined`. Returning nothing rethrows the `ParseError`.
186
167
  */
187
168
  export type ValidationErrorHandler = (error: ParseError, context: ValidationErrorContext) => {
188
169
  value: unknown;
@@ -200,7 +181,7 @@ export type Auth = {
200
181
  scopes?: Array<string>;
201
182
  };
202
183
  /**
203
- * The raw token a consumer returns for a scheme (or `user:password` for basic), or `undefined` to skip it.
184
+ * Return `user:password` for basic auth, or `undefined` to skip a scheme.
204
185
  */
205
186
  export type AuthToken = string | undefined;
206
187
  /**
@@ -213,9 +194,6 @@ export type AuthResolver = AuthToken | ((auth: Auth) => AuthToken | Promise<Auth
213
194
  export type FetchOptions = RequestInit & {
214
195
  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;
@@ -250,8 +228,7 @@ export type RequestConfig<TBody = unknown, TRequest = Request, TResponse = Respo
250
228
  auth?: AuthResolver;
251
229
  };
252
230
  /**
253
- * The grouped options object passed to every generated function: the request config minus the
254
- * data-shaped keys and the literal `url`, plus the per-operation `<Name>Request`.
231
+ * Combines client options with the operation's typed request params and body.
255
232
  */
256
233
  export type Options<TData extends DataShape, ThrowOnError extends boolean = true, TRequest = Request, TResponse = Response> = Omit<RequestConfig<unknown, TRequest, TResponse>, keyof DataShape | 'url'> & TData & {
257
234
  client?: ClientInstance<TRequest, TResponse>;
@@ -301,9 +278,6 @@ export type TransportResult<TData = unknown, TRequest = Request, TResponse = Res
301
278
  * The per-plugin send, supplied to `createClientCore` as `defaultTransport`.
302
279
  */
303
280
  export type Transport<TRequest = Request, TResponse = Response> = (request: ResolvedRequest) => Promise<TransportResult<unknown, TRequest, TResponse>>;
304
- /**
305
- * The result a resolved call produces before it is cast to `RequestResult` by the generated wrapper.
306
- */
307
281
  export type CallResult<TRequest = Request, TResponse = Response> = {
308
282
  status: number;
309
283
  data: unknown;
@@ -322,9 +296,6 @@ export type InterceptorStack<T, TContext = never> = {
322
296
  update: (id: number, fn: InterceptorFn<T, TContext>) => void;
323
297
  run: (value: T, context?: TContext) => Promise<T>;
324
298
  };
325
- /**
326
- * The three interceptor channels every client instance exposes.
327
- */
328
299
  export type Interceptors<TRequest = Request, TResponse = Response> = {
329
300
  request: InterceptorStack<ResolvedRequest, RequestConfig<unknown, TRequest, TResponse>>;
330
301
  response: InterceptorStack<TransportResult<unknown, TRequest, TResponse>, RequestConfig<unknown, TRequest, TResponse>>;
@@ -383,7 +354,7 @@ export declare function resolveAuth(params: {
383
354
  query: Record<string, unknown>;
384
355
  }): Promise<void>;
385
356
  /**
386
- * Builds the shared client core bound to a transport, exported by each plugin as `client` plus a `createClient` factory.
357
+ * Creates an isolated client using the supplied default transport.
387
358
  */
388
359
  export declare function createClientCore<TRequest = Request, TResponse = Response>(options: {
389
360
  defaultTransport: Transport<TRequest, TResponse>;
@@ -406,16 +377,14 @@ export type ServerSentEvent<TData = unknown> = {
406
377
  */
407
378
  export declare function parseEventStream<TData = unknown>(stream: ReadableStream<Uint8Array> | AsyncIterable<Uint8Array>): AsyncGenerator<ServerSentEvent<TData>>;
408
379
  /**
409
- * The resolved shape returned by a generated `text/event-stream` operation: the typed event
410
- * `stream` plus the native `response`.
380
+ * Returns parsed events alongside the native response.
411
381
  */
412
382
  export type EventStreamResult<TData = unknown, TResponse = Response> = {
413
383
  stream: AsyncGenerator<ServerSentEvent<TData>>;
414
384
  response: TResponse;
415
385
  };
416
386
  /**
417
- * Wraps a transport result whose `data` is a streaming body into an `EventStreamResult`, exposing
418
- * the parsed events as a typed async iterator. Generated SSE operations call this.
387
+ * Exposes a streaming response body as typed Server-Sent Events.
419
388
  */
420
389
  export declare function toEventStream<TData = unknown>(result: Promise<{
421
390
  data: unknown;
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@kubb/plugin-fetch",
3
- "version": "5.9.1",
3
+ "version": "5.9.2",
4
4
  "description": "Generate a slim, type-safe HTTP client with Kubb based on the native Fetch api.",
5
5
  "keywords": [
6
6
  "api-client",
@@ -80,17 +80,17 @@
80
80
  "registry": "https://registry.npmjs.org/"
81
81
  },
82
82
  "dependencies": {
83
- "@kubb/plugin-ts": "^5.1.0",
84
- "@kubb/plugin-zod": "^5.7.1"
83
+ "@kubb/plugin-ts": "^5.2.0",
84
+ "@kubb/plugin-zod": "^5.7.2"
85
85
  },
86
86
  "devDependencies": {
87
87
  "@internals/client": "0.0.0",
88
88
  "@internals/shared": "0.0.0",
89
- "kubb": "^5.13.0",
89
+ "kubb": "^5.14.0",
90
90
  "typescript": "^7.0.2"
91
91
  },
92
92
  "peerDependencies": {
93
- "kubb": "^5.13.0"
93
+ "kubb": "^5.14.0"
94
94
  },
95
95
  "engines": {
96
96
  "node": ">=22.22"
@@ -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
  }