@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 +1 -1
- package/dist/index.js +1 -1
- package/dist/templates/fetch.d.ts +25 -56
- package/package.json +5 -5
- package/templates/fetch.ts +97 -190
- package/templates/serializers.ts +13 -5
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.
|
|
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.
|
|
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
|
-
*
|
|
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
|
-
*
|
|
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
|
-
*
|
|
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
|
-
*
|
|
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
|
-
*
|
|
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
|
|
103
|
-
*
|
|
93
|
+
* @example
|
|
94
|
+
* ```ts
|
|
95
|
+
* const { data, error } = await getPetById({ path: { petId: 1 } })
|
|
96
|
+
* ```
|
|
104
97
|
*
|
|
105
|
-
* @example
|
|
106
|
-
*
|
|
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
|
-
*
|
|
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
|
-
*
|
|
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
|
-
*
|
|
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
|
-
*
|
|
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
|
-
*
|
|
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
|
-
*
|
|
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
|
-
*
|
|
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
|
-
*
|
|
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
|
-
*
|
|
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
|
-
*
|
|
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
|
-
*
|
|
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
|
-
*
|
|
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.
|
|
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.
|
|
84
|
-
"@kubb/plugin-zod": "^5.7.
|
|
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.
|
|
89
|
+
"kubb": "^5.14.0",
|
|
90
90
|
"typescript": "^7.0.2"
|
|
91
91
|
},
|
|
92
92
|
"peerDependencies": {
|
|
93
|
-
"kubb": "^5.
|
|
93
|
+
"kubb": "^5.14.0"
|
|
94
94
|
},
|
|
95
95
|
"engines": {
|
|
96
96
|
"node": ">=22.22"
|
package/templates/fetch.ts
CHANGED
|
@@ -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
|
-
*
|
|
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
|
-
*
|
|
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
|
-
*
|
|
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
|
-
*
|
|
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
|
-
*
|
|
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
|
|
96
|
-
*
|
|
86
|
+
* @example
|
|
87
|
+
* ```ts
|
|
88
|
+
* const { data, error } = await getPetById({ path: { petId: 1 } })
|
|
89
|
+
* ```
|
|
97
90
|
*
|
|
98
|
-
* @example
|
|
99
|
-
*
|
|
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
|
-
*
|
|
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
|
-
*
|
|
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
|
-
*
|
|
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
|
-
*
|
|
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
|
-
*
|
|
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
|
-
*
|
|
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
|
-
*
|
|
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
|
-
*
|
|
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
|
-
*
|
|
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
|
|
512
|
-
|
|
513
|
-
|
|
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
|
-
|
|
521
|
-
|
|
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
|
|
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 &&
|
|
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
|
-
|
|
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
|
-
*
|
|
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
|
-
|
|
774
|
-
|
|
775
|
-
|
|
776
|
-
|
|
777
|
-
|
|
778
|
-
|
|
779
|
-
|
|
780
|
-
|
|
781
|
-
|
|
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,
|
|
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
|
-
|
|
921
|
-
|
|
922
|
-
|
|
923
|
-
|
|
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
|
-
*
|
|
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
|
-
*
|
|
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
|
package/templates/serializers.ts
CHANGED
|
@@ -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)
|
|
154
|
-
|
|
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
|
-
|
|
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))
|
|
183
|
-
|
|
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
|
}
|