openapi-typesafe-fetch 1.0.0-rc.0 → 1.0.0-rc.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/README.md +10 -3
- package/dist/index.cjs +66 -36
- package/dist/index.cjs.map +1 -1
- package/dist/index.d.cts +245 -46
- package/dist/index.d.mts +244 -43
- package/dist/index.d.ts +245 -46
- package/dist/index.mjs +66 -36
- package/dist/index.mjs.map +1 -1
- package/package.json +9 -8
- package/src/index.d.ts +243 -42
- package/src/index.js +121 -47
package/dist/index.d.ts
CHANGED
|
@@ -15,28 +15,59 @@ interface ClientOptions extends Omit<RequestInit, "headers"> {
|
|
|
15
15
|
/** global pathSerializer */
|
|
16
16
|
pathSerializer?: PathSerializer;
|
|
17
17
|
headers?: HeadersOptions;
|
|
18
|
-
/**
|
|
18
|
+
/**
|
|
19
|
+
* RequestInit extension object to pass as 2nd argument to fetch when supported (defaults to
|
|
20
|
+
* undefined)
|
|
21
|
+
*/
|
|
19
22
|
requestInitExt?: Record<string, unknown>;
|
|
20
23
|
}
|
|
21
24
|
|
|
25
|
+
/**
|
|
26
|
+
* `ClientOptions` for a Fetch API implementation with its own types, such as the `undici` package
|
|
27
|
+
* (upstream #2562). TypeScript cannot relate its `Request` and `Response` to the DOM lib's
|
|
28
|
+
* (`duplex`, `RequestDestination`, `BodyInit` differ), so only what the client needs is checked: a
|
|
29
|
+
* `fetch` that returns a promise and a `Request` class that takes a URL. Pass the `fetch` and the
|
|
30
|
+
* `Request` of the same implementation; middleware and results still use the DOM types. The
|
|
31
|
+
* `createClient` overload with `ClientOptions` comes first, so an inline `fetch` gets a typed
|
|
32
|
+
* `Request` input.
|
|
33
|
+
*/
|
|
34
|
+
interface CustomFetchClientOptions extends Omit<ClientOptions, "fetch" | "Request"> {
|
|
35
|
+
/** `fetch` of another implementation (for example `fetch` from `undici`) */
|
|
36
|
+
fetch?: (input: never, init?: never) => PromiseLike<object>;
|
|
37
|
+
/** `Request` of the same implementation (for example `Request` from `undici`) */
|
|
38
|
+
Request?: new (
|
|
39
|
+
input: string,
|
|
40
|
+
init: never,
|
|
41
|
+
) => object;
|
|
42
|
+
}
|
|
43
|
+
|
|
22
44
|
type HeadersOptions =
|
|
23
45
|
| Required<RequestInit>["headers"]
|
|
24
46
|
| Record<string, string | number | boolean | (string | number | boolean)[] | null | undefined>;
|
|
25
47
|
|
|
26
48
|
type QuerySerializer<T> = (
|
|
27
|
-
|
|
49
|
+
// biome-ignore lint/suspicious/noExplicitAny: T["parameters"]["query"] needs an any constraint
|
|
50
|
+
query: T extends { parameters: any }
|
|
51
|
+
? NonNullable<T["parameters"]["query"]>
|
|
52
|
+
: Record<string, unknown>,
|
|
28
53
|
) => string;
|
|
29
54
|
|
|
30
55
|
/** @see https://swagger.io/docs/specification/serialization/#query */
|
|
31
56
|
type QuerySerializerOptions = {
|
|
32
|
-
/**
|
|
57
|
+
/**
|
|
58
|
+
* Set serialization for arrays.
|
|
59
|
+
* @see https://swagger.io/docs/specification/serialization/#query
|
|
60
|
+
*/
|
|
33
61
|
array?: {
|
|
34
62
|
/** default: "form" */
|
|
35
63
|
style: "form" | "spaceDelimited" | "pipeDelimited";
|
|
36
64
|
/** default: true */
|
|
37
65
|
explode: boolean;
|
|
38
66
|
};
|
|
39
|
-
/**
|
|
67
|
+
/**
|
|
68
|
+
* Set serialization for objects.
|
|
69
|
+
* @see https://swagger.io/docs/specification/serialization/#query
|
|
70
|
+
*/
|
|
40
71
|
object?: {
|
|
41
72
|
/** default: "deepObject" */
|
|
42
73
|
style: "form" | "deepObject";
|
|
@@ -53,6 +84,8 @@ type QuerySerializerOptions = {
|
|
|
53
84
|
allowReserved?: boolean;
|
|
54
85
|
};
|
|
55
86
|
|
|
87
|
+
// The result is the fetch body; openapi-fetch types it as `any` (ADR-030).
|
|
88
|
+
// biome-ignore lint/suspicious/noExplicitAny: openapi-fetch's type, kept for parity
|
|
56
89
|
type BodySerializer<T> = (body: OperationRequestBodyContent<T>) => any;
|
|
57
90
|
|
|
58
91
|
type PathSerializer = (pathname: string, pathParams: Record<string, unknown>) => string;
|
|
@@ -78,25 +111,38 @@ interface DefaultParamsOption {
|
|
|
78
111
|
}
|
|
79
112
|
|
|
80
113
|
type ParamsOption<T> = T extends {
|
|
81
|
-
parameters:
|
|
114
|
+
parameters: unknown;
|
|
82
115
|
}
|
|
83
116
|
? RequiredKeysOf<T["parameters"]> extends never
|
|
84
117
|
? { params?: T["parameters"] }
|
|
85
118
|
: { params: T["parameters"] }
|
|
86
119
|
: DefaultParamsOption;
|
|
87
120
|
|
|
88
|
-
|
|
121
|
+
/** `true` for `any`: the operation of `createClient<any>()`, which says nothing about its body. */
|
|
122
|
+
type IsAny<T> = 0 extends 1 & T ? true : false;
|
|
123
|
+
|
|
124
|
+
// Writable<T> strips $Read markers (readOnly properties excluded from request body).
|
|
125
|
+
// An `any` operation is checked first: a conditional type on `any` takes both branches, which
|
|
126
|
+
// gave `{ body?: never } | { body: any }` and so a required init (upstream #2526).
|
|
89
127
|
type RequestBodyOption<T> =
|
|
90
|
-
|
|
91
|
-
?
|
|
92
|
-
|
|
93
|
-
|
|
94
|
-
|
|
128
|
+
IsAny<T> extends true
|
|
129
|
+
? // biome-ignore lint/suspicious/noExplicitAny: an any schema takes any body (upstream #2526)
|
|
130
|
+
{ body?: any }
|
|
131
|
+
: Writable<OperationRequestBodyContent<T>> extends never
|
|
132
|
+
? { body?: never }
|
|
133
|
+
: IsOperationRequestBodyOptional<T> extends true
|
|
134
|
+
? { body?: Writable<OperationRequestBodyContent<T>> }
|
|
135
|
+
: { body: Writable<OperationRequestBodyContent<T>> };
|
|
95
136
|
|
|
96
137
|
type FetchOptions<T> = RequestOptions<T> & Omit<RequestInit, "body" | "headers">;
|
|
97
138
|
|
|
98
139
|
// Readable<T> strips $Write markers (writeOnly properties excluded from response)
|
|
99
|
-
type FetchResponse<
|
|
140
|
+
type FetchResponse<
|
|
141
|
+
// biome-ignore lint/suspicious/noExplicitAny: unknown rejects the generated interfaces
|
|
142
|
+
T extends Record<string | number, any>,
|
|
143
|
+
Options,
|
|
144
|
+
Media extends MediaType,
|
|
145
|
+
> =
|
|
100
146
|
| {
|
|
101
147
|
data: ParseAsResponse<Readable<SuccessResponse<ResponseObjectMap<T>, Media>>, Options>;
|
|
102
148
|
error?: never;
|
|
@@ -145,7 +191,7 @@ interface MiddlewareCallbackParams {
|
|
|
145
191
|
readonly params: MiddlewareRequestParams;
|
|
146
192
|
/** Unique ID for this request */
|
|
147
193
|
readonly id: string;
|
|
148
|
-
/**
|
|
194
|
+
/** The options of this request after merging the client and request options (read-only) */
|
|
149
195
|
readonly options: MergedOptions;
|
|
150
196
|
}
|
|
151
197
|
|
|
@@ -176,29 +222,79 @@ type Middleware =
|
|
|
176
222
|
onError: MiddlewareOnError;
|
|
177
223
|
};
|
|
178
224
|
|
|
179
|
-
/**
|
|
180
|
-
type
|
|
181
|
-
|
|
182
|
-
|
|
183
|
-
|
|
225
|
+
/**
|
|
226
|
+
* This type helper makes the 2nd function param required if params/requestBody are required;
|
|
227
|
+
* otherwise, optional. `Location` is any key: with `Paths extends {}` a method is not known to be a
|
|
228
|
+
* key of `Paths[Path]` (upstream #2267). For a union of operations, the union of their inits: one
|
|
229
|
+
* deep type of every init gave TS2589 on hundreds of paths (upstream #1882).
|
|
230
|
+
*/
|
|
231
|
+
type MaybeOptionalInit<Params, Location extends PropertyKey> = OperationInits<
|
|
232
|
+
FilterKeys<Params, Location>
|
|
233
|
+
>;
|
|
234
|
+
|
|
235
|
+
/**
|
|
236
|
+
* The inits of the operations, and `undefined` only when none of them requires anything. One
|
|
237
|
+
* operation's init is `undefined` too when nothing in it is required, as in openapi-fetch. For a
|
|
238
|
+
* union of operations (a union of paths, or `Parameters<typeof client.request>`), `undefined`
|
|
239
|
+
* would make the whole init optional, although a call to one of the paths sends the init it gets,
|
|
240
|
+
* and a path parameter left out stays `{id}` in the URL.
|
|
241
|
+
* One conditional type, as openapi-fetch's, not a union with a conditional `undefined`: while the
|
|
242
|
+
* operation is generic, a union made `FetchResponse` distribute over its members, so the `data` of
|
|
243
|
+
* a generic wrapper's call no longer related to `FetchResponse<Paths[Path][Method], Init, Media>`.
|
|
244
|
+
*/
|
|
245
|
+
type OperationInits<Operation> =
|
|
246
|
+
RequiredKeysOf<OperationFetchOptions<Operation>> extends never
|
|
247
|
+
? OperationFetchOptions<Operation> | undefined
|
|
248
|
+
: OperationFetchOptions<Operation>;
|
|
249
|
+
|
|
250
|
+
/**
|
|
251
|
+
* Each operation's init. A distributive alias, not `extends infer Operation`: while the path is
|
|
252
|
+
* not known, TypeScript reads an `infer` type as `unknown`, so the init lost its type as the
|
|
253
|
+
* contextual type of an argument and a literal such as `"available"` widened to `string`
|
|
254
|
+
* (swr-openapi's `Exact<Init>` checks).
|
|
255
|
+
*/
|
|
256
|
+
type OperationFetchOptions<Operation> = Operation extends unknown ? FetchOptions<Operation> : never;
|
|
184
257
|
|
|
185
258
|
// The final init param to accept.
|
|
186
259
|
// - Determines if the param is optional or not.
|
|
187
260
|
// - Performs arbitrary [key: string] addition.
|
|
188
|
-
// Note: the addition MUST happen after all the inference happens (otherwise TS can’t infer if
|
|
261
|
+
// Note: the addition MUST happen after all the inference happens (otherwise TS can’t infer if
|
|
262
|
+
// init is required or not).
|
|
263
|
+
// - The condition is openapi-fetch's. TypeScript relates two conditional types only when their
|
|
264
|
+
// `extends` types are the same and their check types relate, so a client type written against
|
|
265
|
+
// openapi-fetch's `InitParam` (a wrapper, a cast) accepted this client only with this condition.
|
|
266
|
+
// A union of inits is required through `MaybeOptionalInit`, which then leaves out `undefined`.
|
|
267
|
+
// - The addition is a named alias: written out in both branches, TypeScript gave `never` for
|
|
268
|
+
// `Parameters` of the methods.
|
|
189
269
|
type InitParam<Init> =
|
|
190
|
-
RequiredKeysOf<Init> extends never ? [
|
|
270
|
+
RequiredKeysOf<Init> extends never ? [WithAnyKeys<Init>?] : [WithAnyKeys<Init>];
|
|
191
271
|
|
|
192
|
-
|
|
193
|
-
|
|
194
|
-
|
|
195
|
-
|
|
196
|
-
|
|
272
|
+
/** The init with any other key (`[key: string]: unknown`), added after inference. */
|
|
273
|
+
type WithAnyKeys<Init> = Init & { [key: string]: unknown };
|
|
274
|
+
|
|
275
|
+
/**
|
|
276
|
+
* @internal Exported for openapi-typesafe-react-query only; not part of the API (ADR-034).
|
|
277
|
+
* The operation of `Method` on `Path`. Not `Paths[Path][Method]`: `Paths` is only `{}`, because
|
|
278
|
+
* the generated `interface paths` has no index signature and failed a `Record<string, …>`
|
|
279
|
+
* constraint with TS2344 (upstream #2267), so TypeScript does not know `Method` is a key. For a
|
|
280
|
+
* union of paths or methods, the union of their operations; a method a path lacks (`put?: never`)
|
|
281
|
+
* adds none.
|
|
282
|
+
*/
|
|
283
|
+
type PathOperation<Paths, Path, Method> = Extract<
|
|
284
|
+
FilterKeys<Paths[Path & keyof Paths], Method>,
|
|
285
|
+
// biome-ignore lint/suspicious/noExplicitAny: unknown rejects the generated interfaces
|
|
286
|
+
Record<string | number, any>
|
|
287
|
+
>;
|
|
288
|
+
|
|
289
|
+
type ClientMethod<Paths extends {}, Method extends HttpMethod, Media extends MediaType> = <
|
|
290
|
+
Path extends PathsWithMethod<Paths, Method>,
|
|
291
|
+
Init extends MaybeOptionalInit<Paths[Path], Method>,
|
|
292
|
+
>(
|
|
197
293
|
url: Path,
|
|
198
294
|
...init: InitParam<Init>
|
|
199
|
-
) => Promise<FetchResponse<Paths
|
|
295
|
+
) => Promise<FetchResponse<PathOperation<Paths, Path, Method>, Init, Media>>;
|
|
200
296
|
|
|
201
|
-
type ClientRequestMethod<Paths extends
|
|
297
|
+
type ClientRequestMethod<Paths extends {}, Media extends MediaType> = <
|
|
202
298
|
Method extends HttpMethod,
|
|
203
299
|
Path extends PathsWithMethod<Paths, Method>,
|
|
204
300
|
Init extends MaybeOptionalInit<Paths[Path], Method>,
|
|
@@ -206,11 +302,21 @@ type ClientRequestMethod<Paths extends Record<string, Record<HttpMethod, {}>>, M
|
|
|
206
302
|
method: Method,
|
|
207
303
|
url: Path,
|
|
208
304
|
...init: InitParam<Init>
|
|
209
|
-
) => Promise<FetchResponse<Paths
|
|
305
|
+
) => Promise<FetchResponse<PathOperation<Paths, Path, Method>, Init, Media>>;
|
|
210
306
|
|
|
211
307
|
// Only HTTP method keys become calls: `parameters` and `additionalOperations` are not methods.
|
|
212
|
-
|
|
213
|
-
|
|
308
|
+
// They are left out by `Pick`, which keeps the `?` of a method, not by the key remapping: that is
|
|
309
|
+
// openapi-fetch's, and TypeScript relates two generic mapped types only when their key remappings
|
|
310
|
+
// are the same, so a path-based client type written like openapi-fetch's failed a cast (TS2352).
|
|
311
|
+
type ClientForPath<
|
|
312
|
+
// biome-ignore lint/suspicious/noExplicitAny: unknown rejects the generated interfaces
|
|
313
|
+
PathInfo extends Record<string | number, any>,
|
|
314
|
+
Media extends MediaType,
|
|
315
|
+
> = PathMethodCalls<Pick<PathInfo, HttpMethod & keyof PathInfo>, Media>;
|
|
316
|
+
|
|
317
|
+
// biome-ignore lint/suspicious/noExplicitAny: unknown rejects the generated interfaces
|
|
318
|
+
type PathMethodCalls<PathInfo extends Record<string | number, any>, Media extends MediaType> = {
|
|
319
|
+
[Method in keyof PathInfo as Uppercase<string & Method>]: <
|
|
214
320
|
Init extends MaybeOptionalInit<PathInfo, Method>,
|
|
215
321
|
>(
|
|
216
322
|
...init: InitParam<Init>
|
|
@@ -243,34 +349,56 @@ interface Client<Paths extends {}, Media extends MediaType = MediaType> {
|
|
|
243
349
|
eject(...middleware: Middleware[]): void;
|
|
244
350
|
}
|
|
245
351
|
|
|
246
|
-
type ClientPathsWithMethod<
|
|
352
|
+
type ClientPathsWithMethod<
|
|
353
|
+
// biome-ignore lint/suspicious/noExplicitAny: matches a client of any paths, as in openapi-fetch
|
|
354
|
+
CreatedClient extends Client<any, any>,
|
|
355
|
+
Method extends HttpMethod,
|
|
356
|
+
> =
|
|
247
357
|
CreatedClient extends Client<infer Paths, infer _Media> ? PathsWithMethod<Paths, Method> : never;
|
|
248
358
|
|
|
249
359
|
type MethodResponse<
|
|
360
|
+
// biome-ignore lint/suspicious/noExplicitAny: matches a client of any paths, as in openapi-fetch
|
|
250
361
|
CreatedClient extends Client<any, any>,
|
|
251
362
|
Method extends HttpMethod,
|
|
252
363
|
Path extends ClientPathsWithMethod<CreatedClient, Method>,
|
|
253
364
|
Options = {},
|
|
254
365
|
> =
|
|
255
|
-
CreatedClient extends Client<
|
|
366
|
+
CreatedClient extends Client<
|
|
367
|
+
// biome-ignore lint/suspicious/noExplicitAny: unknown rejects the generated interfaces
|
|
368
|
+
infer Paths extends { [key: string]: any },
|
|
369
|
+
infer Media extends MediaType
|
|
370
|
+
>
|
|
256
371
|
? NonNullable<FetchResponse<Paths[Path][Method], Options, Media>["data"]>
|
|
257
372
|
: never;
|
|
258
373
|
|
|
259
374
|
declare function createClient<Paths extends {}, Media extends MediaType = MediaType>(
|
|
260
375
|
clientOptions?: ClientOptions,
|
|
261
376
|
): Client<Paths, Media>;
|
|
377
|
+
declare function createClient<Paths extends {}, Media extends MediaType = MediaType>(
|
|
378
|
+
clientOptions?: CustomFetchClientOptions,
|
|
379
|
+
): Client<Paths, Media>;
|
|
262
380
|
|
|
263
|
-
type PathBasedClient<
|
|
381
|
+
type PathBasedClient<
|
|
382
|
+
// biome-ignore lint/suspicious/noExplicitAny: unknown rejects the generated interfaces
|
|
383
|
+
Paths extends Record<string | number, any>,
|
|
384
|
+
Media extends MediaType = MediaType,
|
|
385
|
+
> = {
|
|
264
386
|
[Path in keyof Paths]: ClientForPath<Paths[Path], Media>;
|
|
265
387
|
};
|
|
266
388
|
|
|
267
|
-
declare function wrapAsPathBasedClient<
|
|
268
|
-
|
|
269
|
-
|
|
389
|
+
declare function wrapAsPathBasedClient<
|
|
390
|
+
Paths extends {},
|
|
391
|
+
Media extends MediaType = MediaType,
|
|
392
|
+
>(client: Client<Paths, Media>): PathBasedClient<Paths, Media>;
|
|
270
393
|
|
|
271
|
-
declare function createPathBasedClient<
|
|
272
|
-
|
|
273
|
-
|
|
394
|
+
declare function createPathBasedClient<
|
|
395
|
+
Paths extends {},
|
|
396
|
+
Media extends MediaType = MediaType,
|
|
397
|
+
>(clientOptions?: ClientOptions): PathBasedClient<Paths, Media>;
|
|
398
|
+
declare function createPathBasedClient<
|
|
399
|
+
Paths extends {},
|
|
400
|
+
Media extends MediaType = MediaType,
|
|
401
|
+
>(clientOptions?: CustomFetchClientOptions): PathBasedClient<Paths, Media>;
|
|
274
402
|
|
|
275
403
|
/** Serialize primitive params to string */
|
|
276
404
|
declare function serializePrimitiveParam(
|
|
@@ -311,12 +439,85 @@ declare function createQuerySerializer<T = unknown>(
|
|
|
311
439
|
* @type {import("./index.js").defaultPathSerializer}
|
|
312
440
|
* @see https://swagger.io/docs/specification/serialization/#path
|
|
313
441
|
*/
|
|
314
|
-
declare function defaultPathSerializer(
|
|
442
|
+
declare function defaultPathSerializer(
|
|
443
|
+
pathname: string,
|
|
444
|
+
pathParams: Record<string, unknown>,
|
|
445
|
+
): string;
|
|
446
|
+
|
|
447
|
+
/** Bodies that `fetch` sends as they are, so `defaultBodySerializer` returns them unchanged. */
|
|
448
|
+
type RawRequestBody = FormData | Blob | URLSearchParams | ArrayBuffer | ArrayBufferView;
|
|
449
|
+
|
|
450
|
+
/**
|
|
451
|
+
* The result of `defaultBodySerializer` for a body of type `Body`: the body itself when it is raw,
|
|
452
|
+
* `undefined` for `undefined` (`JSON.stringify` gives it back), `ObjectResult` for an object, and a
|
|
453
|
+
* string otherwise. `unknown` (and `any`) may be any of these.
|
|
454
|
+
*/
|
|
455
|
+
type SerializedBody<Body, ObjectResult> = unknown extends Body
|
|
456
|
+
? RawRequestBody | string | FormData | undefined
|
|
457
|
+
: Body extends RawRequestBody
|
|
458
|
+
? Body
|
|
459
|
+
: Body extends undefined
|
|
460
|
+
? undefined
|
|
461
|
+
: Body extends object
|
|
462
|
+
? ObjectResult
|
|
463
|
+
: string;
|
|
464
|
+
|
|
465
|
+
/** The `Content-Type` values that make `defaultBodySerializer` turn an object into `FormData`. */
|
|
466
|
+
type MultipartMediaType = "multipart/form-data" | `multipart/form-data;${string}`;
|
|
467
|
+
|
|
468
|
+
/** `Headers` drops the spaces and tabs in front of a value before the media type is read. */
|
|
469
|
+
type TrimmedStart<Text extends string> = Text extends ` ${infer Rest}` | `\t${infer Rest}`
|
|
470
|
+
? TrimmedStart<Rest>
|
|
471
|
+
: Text;
|
|
472
|
+
|
|
473
|
+
/**
|
|
474
|
+
* The `Content-Type` of headers written as a record, in any letter case of the name: `never` when
|
|
475
|
+
* the record has none, `unknown` when the type does not tell (`Headers`, pairs, a string record).
|
|
476
|
+
*/
|
|
477
|
+
type ContentTypeOf<GivenHeaders> = GivenHeaders extends undefined
|
|
478
|
+
? never
|
|
479
|
+
: GivenHeaders extends readonly unknown[] | { get(name: string): unknown }
|
|
480
|
+
? unknown
|
|
481
|
+
: string extends keyof GivenHeaders
|
|
482
|
+
? unknown
|
|
483
|
+
: ContentTypeEntries<GivenHeaders>[keyof GivenHeaders];
|
|
484
|
+
|
|
485
|
+
/** For each entry of a header record: its value when it is the `Content-Type`, else `never`. */
|
|
486
|
+
type ContentTypeEntries<Record> = { [Name in keyof Record]: ContentTypeEntry<Name, Record[Name]> };
|
|
487
|
+
|
|
488
|
+
/** The value of a header record entry when its name is `Content-Type` in any letter case. */
|
|
489
|
+
type ContentTypeEntry<Name, Value> =
|
|
490
|
+
Lowercase<Name & string> extends "content-type" ? Value : never;
|
|
491
|
+
|
|
492
|
+
/**
|
|
493
|
+
* What an object becomes with a `Content-Type` of type `ContentType`: `FormData` for a multipart
|
|
494
|
+
* one, a string for any other one or for none, and either when the type is not a literal.
|
|
495
|
+
*/
|
|
496
|
+
type ObjectBodyResult<ContentType> = [ContentType] extends [never]
|
|
497
|
+
? string
|
|
498
|
+
: ContentType extends null | undefined
|
|
499
|
+
? string
|
|
500
|
+
: ContentType extends string
|
|
501
|
+
? string extends ContentType
|
|
502
|
+
? string | FormData
|
|
503
|
+
: Lowercase<TrimmedStart<ContentType>> extends MultipartMediaType
|
|
504
|
+
? FormData
|
|
505
|
+
: string
|
|
506
|
+
: string | FormData;
|
|
315
507
|
|
|
316
508
|
/**
|
|
317
|
-
* Serialize
|
|
509
|
+
* Serialize a request body. A raw body (`FormData`, `Blob`, `URLSearchParams`, `ArrayBuffer`, a
|
|
510
|
+
* typed array) is returned as it is. With `headers`: a form `Content-Type` gives URL-encoded form
|
|
511
|
+
* data, a `multipart/form-data` one turns an object into `FormData`, and another non-JSON one keeps
|
|
512
|
+
* a string as it is. Anything else is JSON. Without `headers` an object can only become JSON.
|
|
513
|
+
* Headers written as a record literal tell the result type of an object body: `FormData` for a
|
|
514
|
+
* multipart `Content-Type`, `string` for another one or none. Other headers: `string | FormData`.
|
|
318
515
|
*/
|
|
319
|
-
declare function defaultBodySerializer<T>(body: T
|
|
516
|
+
declare function defaultBodySerializer<T>(body: T): SerializedBody<T, string>;
|
|
517
|
+
declare function defaultBodySerializer<
|
|
518
|
+
T,
|
|
519
|
+
const GivenHeaders extends HeadersOptions | undefined,
|
|
520
|
+
>(body: T, headers: GivenHeaders): SerializedBody<T, ObjectBodyResult<ContentTypeOf<GivenHeaders>>>;
|
|
320
521
|
|
|
321
522
|
/** Construct URL string from baseUrl and handle path and query params */
|
|
322
523
|
declare function createFinalURL<O>(
|
|
@@ -338,7 +539,5 @@ declare function mergeHeaders(...allHeaders: (HeadersOptions | undefined)[]): He
|
|
|
338
539
|
/** Remove trailing slash from url */
|
|
339
540
|
declare function removeTrailingSlash(url: string): string;
|
|
340
541
|
|
|
341
|
-
|
|
342
|
-
export
|
|
343
|
-
export { createFinalURL, createPathBasedClient, createQuerySerializer, defaultBodySerializer, defaultPathSerializer, mergeHeaders, removeTrailingSlash, serializeArrayParam, serializeObjectParam, serializePrimitiveParam, wrapAsPathBasedClient };
|
|
344
|
-
export type { BodySerializer, Client, ClientForPath, ClientMethod, ClientOptions, ClientPathsWithMethod, ClientRequestMethod, DefaultParamsOption, FetchOptions, FetchResponse, HeadersOptions, MaybeOptionalInit, MergedOptions, MethodResponse, Middleware, MiddlewareCallbackParams, MiddlewareRequestParams, ParamsOption, ParseAs, ParseAsResponse, PathBasedClient, PathSerializer, QuerySerializer, QuerySerializerOptions, RequestBodyOption, RequestOptions };
|
|
542
|
+
export { createFinalURL, createPathBasedClient, createQuerySerializer, createClient as default, defaultBodySerializer, defaultPathSerializer, mergeHeaders, removeTrailingSlash, serializeArrayParam, serializeObjectParam, serializePrimitiveParam, wrapAsPathBasedClient };
|
|
543
|
+
export type { BodySerializer, Client, ClientForPath, ClientMethod, ClientOptions, ClientPathsWithMethod, ClientRequestMethod, CustomFetchClientOptions, DefaultParamsOption, FetchOptions, FetchResponse, HeadersOptions, MaybeOptionalInit, MergedOptions, MethodResponse, Middleware, MiddlewareCallbackParams, MiddlewareRequestParams, ParamsOption, ParseAs, ParseAsResponse, PathBasedClient, PathOperation, PathSerializer, QuerySerializer, QuerySerializerOptions, RequestBodyOption, RequestOptions };
|
package/dist/index.mjs
CHANGED
|
@@ -1,7 +1,8 @@
|
|
|
1
1
|
const PATH_PARAM_RE = /\{[^{}]+\}/g;
|
|
2
2
|
const DOT_SEGMENT_RE = /(^|\/)\.\.?(\/|$)/;
|
|
3
|
+
const QUERY_OR_FRAGMENT_RE = /[?#]/;
|
|
3
4
|
const JSON_MEDIA_TYPE_RE = /json/i;
|
|
4
|
-
const
|
|
5
|
+
const FORM_MEDIA_TYPE_RE = /^application\/x-www-form-urlencoded(;|$)/i;
|
|
5
6
|
const MULTIPART_MEDIA_TYPE_RE = /^multipart\/form-data(;|$)/i;
|
|
6
7
|
const NESTED_FORM_VALUE_MESSAGE = "Nested form values need your own `bodySerializer()`.";
|
|
7
8
|
const BOUNDARY_PARAMETER_RE = /;\s*boundary=/i;
|
|
@@ -57,11 +58,12 @@ function createClient(clientOptions) {
|
|
|
57
58
|
const pathSerializer = requestPathSerializer || globalPathSerializer || defaultPathSerializer;
|
|
58
59
|
const serializedBody = body === void 0 ? void 0 : bodySerializer(
|
|
59
60
|
body,
|
|
60
|
-
// Note: we declare mergeHeaders() both here and below because it’s a bit of a
|
|
61
|
-
// bodySerializer() needs all headers so we aren’t dropping
|
|
62
|
-
// the result of this ALSO sets the lowest-priority
|
|
63
|
-
// setting the content-type at the very
|
|
64
|
-
// Lastly, based on the way headers work, it’s not a simple
|
|
61
|
+
// Note: we declare mergeHeaders() both here and below because it’s a bit of a
|
|
62
|
+
// chicken-or-egg situation: bodySerializer() needs all headers so we aren’t dropping
|
|
63
|
+
// ones set by the user, however, the result of this ALSO sets the lowest-priority
|
|
64
|
+
// content-type header. So we re-merge below, setting the content-type at the very
|
|
65
|
+
// beginning to be overwritten. Lastly, based on the way headers work, it’s not a simple
|
|
66
|
+
// “present-or-not” check because null intentionally un-sets headers.
|
|
65
67
|
mergeHeaders(baseHeaders, headers, params.header)
|
|
66
68
|
);
|
|
67
69
|
const finalHeaders = mergeHeaders(
|
|
@@ -88,7 +90,12 @@ function createClient(clientOptions) {
|
|
|
88
90
|
let id;
|
|
89
91
|
let options;
|
|
90
92
|
let request = new Request(
|
|
91
|
-
createFinalURL(schemaPath, {
|
|
93
|
+
createFinalURL(schemaPath, {
|
|
94
|
+
baseUrl: finalBaseUrl,
|
|
95
|
+
params,
|
|
96
|
+
querySerializer,
|
|
97
|
+
pathSerializer
|
|
98
|
+
}),
|
|
92
99
|
requestInit
|
|
93
100
|
);
|
|
94
101
|
let response;
|
|
@@ -117,13 +124,15 @@ function createClient(clientOptions) {
|
|
|
117
124
|
id
|
|
118
125
|
});
|
|
119
126
|
if (result) {
|
|
120
|
-
if (result
|
|
127
|
+
if (isRequest(result)) {
|
|
121
128
|
request = result;
|
|
122
|
-
} else if (result
|
|
129
|
+
} else if (isResponse(result)) {
|
|
123
130
|
response = result;
|
|
124
131
|
break;
|
|
125
132
|
} else {
|
|
126
|
-
throw new Error(
|
|
133
|
+
throw new Error(
|
|
134
|
+
"onRequest: must return new Request() or Response() when modifying the request"
|
|
135
|
+
);
|
|
127
136
|
}
|
|
128
137
|
}
|
|
129
138
|
}
|
|
@@ -147,7 +156,7 @@ function createClient(clientOptions) {
|
|
|
147
156
|
id
|
|
148
157
|
});
|
|
149
158
|
if (result) {
|
|
150
|
-
if (result
|
|
159
|
+
if (isResponse(result)) {
|
|
151
160
|
errorAfterMiddleware = void 0;
|
|
152
161
|
response = result;
|
|
153
162
|
break;
|
|
@@ -178,8 +187,10 @@ function createClient(clientOptions) {
|
|
|
178
187
|
id
|
|
179
188
|
});
|
|
180
189
|
if (result) {
|
|
181
|
-
if (!(result
|
|
182
|
-
throw new Error(
|
|
190
|
+
if (!isResponse(result)) {
|
|
191
|
+
throw new Error(
|
|
192
|
+
"onResponse: must return new Response() when modifying the response"
|
|
193
|
+
);
|
|
183
194
|
}
|
|
184
195
|
response = result;
|
|
185
196
|
}
|
|
@@ -262,7 +273,9 @@ function createClient(clientOptions) {
|
|
|
262
273
|
continue;
|
|
263
274
|
}
|
|
264
275
|
if (typeof m !== "object" || !("onRequest" in m || "onResponse" in m || "onError" in m)) {
|
|
265
|
-
throw new Error(
|
|
276
|
+
throw new Error(
|
|
277
|
+
"Middleware must be an object with one of `onRequest()`, `onResponse()` or `onError()`"
|
|
278
|
+
);
|
|
266
279
|
}
|
|
267
280
|
globalMiddlewares.push(m);
|
|
268
281
|
}
|
|
@@ -335,6 +348,15 @@ function wrapAsPathBasedClient(coreClient) {
|
|
|
335
348
|
function createPathBasedClient(clientOptions) {
|
|
336
349
|
return wrapAsPathBasedClient(createClient(clientOptions));
|
|
337
350
|
}
|
|
351
|
+
function isHeaders(value) {
|
|
352
|
+
return typeof value?.get === "function";
|
|
353
|
+
}
|
|
354
|
+
function isRequest(value) {
|
|
355
|
+
return typeof value?.method === "string" && typeof value.url === "string" && isHeaders(value.headers);
|
|
356
|
+
}
|
|
357
|
+
function isResponse(value) {
|
|
358
|
+
return typeof value?.status === "number" && isHeaders(value.headers);
|
|
359
|
+
}
|
|
338
360
|
const KEPT_NAME_ESCAPES = /%(?:5B|5D|24|3A|40|2C)/g;
|
|
339
361
|
function encodeName(name) {
|
|
340
362
|
return encodeURIComponent(name).replace(KEPT_NAME_ESCAPES, decodeURIComponent);
|
|
@@ -362,7 +384,10 @@ function serializeObjectParam(name, value, options) {
|
|
|
362
384
|
}[options.style] || "&";
|
|
363
385
|
if (options.style !== "deepObject" && options.explode === false) {
|
|
364
386
|
for (const k in value) {
|
|
365
|
-
values.push(
|
|
387
|
+
values.push(
|
|
388
|
+
encodeName(k),
|
|
389
|
+
options.allowReserved === true ? value[k] : encodeURIComponent(value[k])
|
|
390
|
+
);
|
|
366
391
|
}
|
|
367
392
|
const final2 = values.join(",");
|
|
368
393
|
switch (options.style) {
|
|
@@ -484,35 +509,44 @@ function defaultPathSerializer(pathname, pathParams) {
|
|
|
484
509
|
continue;
|
|
485
510
|
}
|
|
486
511
|
const value = pathParams[name];
|
|
512
|
+
let serialized;
|
|
487
513
|
if (Array.isArray(value)) {
|
|
488
|
-
|
|
489
|
-
|
|
490
|
-
|
|
491
|
-
if (
|
|
492
|
-
|
|
493
|
-
|
|
494
|
-
|
|
495
|
-
if (style === "matrix") {
|
|
496
|
-
nextURL = nextURL.replace(match, `;${serializePrimitiveParam(name, value)}`);
|
|
497
|
-
continue;
|
|
514
|
+
serialized = serializeArrayParam(name, value, { style, explode });
|
|
515
|
+
} else if (typeof value === "object") {
|
|
516
|
+
serialized = serializeObjectParam(name, value, { style, explode });
|
|
517
|
+
} else if (style === "matrix") {
|
|
518
|
+
serialized = `;${serializePrimitiveParam(name, value)}`;
|
|
519
|
+
} else {
|
|
520
|
+
serialized = style === "label" ? `.${encodeURIComponent(value)}` : encodeURIComponent(value);
|
|
498
521
|
}
|
|
499
|
-
nextURL = nextURL.replace(match,
|
|
522
|
+
nextURL = nextURL.replace(match, () => serialized);
|
|
500
523
|
}
|
|
501
|
-
|
|
524
|
+
const path = nextURL.split(QUERY_OR_FRAGMENT_RE, 1)[0];
|
|
525
|
+
if (DOT_SEGMENT_RE.test(path)) {
|
|
502
526
|
throw new Error(`Dot path segment in ${nextURL}`);
|
|
503
527
|
}
|
|
528
|
+
if (path.startsWith("//")) {
|
|
529
|
+
throw new Error(`Empty first path segment in ${nextURL}`);
|
|
530
|
+
}
|
|
504
531
|
return nextURL;
|
|
505
532
|
}
|
|
506
533
|
function isRawBody(body) {
|
|
507
534
|
return body instanceof FormData || body instanceof Blob || body instanceof URLSearchParams || body instanceof ArrayBuffer || ArrayBuffer.isView(body);
|
|
508
535
|
}
|
|
509
536
|
function formBody(body) {
|
|
510
|
-
|
|
537
|
+
if (!body || typeof body !== "object" || Array.isArray(body)) {
|
|
538
|
+
return new URLSearchParams(body).toString();
|
|
539
|
+
}
|
|
540
|
+
const form = new URLSearchParams();
|
|
541
|
+
for (const [name, value] of Object.entries(body)) {
|
|
511
542
|
if (value && typeof value === "object" && !Array.isArray(value)) {
|
|
512
543
|
throw new Error(NESTED_FORM_VALUE_MESSAGE);
|
|
513
544
|
}
|
|
545
|
+
if (value != null) {
|
|
546
|
+
form.append(name, value);
|
|
547
|
+
}
|
|
514
548
|
}
|
|
515
|
-
return
|
|
549
|
+
return form.toString();
|
|
516
550
|
}
|
|
517
551
|
function multipartBody(body) {
|
|
518
552
|
const formData = new FormData();
|
|
@@ -533,12 +567,8 @@ function defaultBodySerializer(body, headers) {
|
|
|
533
567
|
if (isRawBody(body)) {
|
|
534
568
|
return body;
|
|
535
569
|
}
|
|
536
|
-
const contentType = headers ? (
|
|
537
|
-
|
|
538
|
-
// `get` that is not an instance of this realm's Function (upstream #2637).
|
|
539
|
-
typeof headers.get === "function" ? headers.get("Content-Type") ?? headers.get("content-type") : headers["Content-Type"] ?? headers["content-type"]
|
|
540
|
-
) : void 0;
|
|
541
|
-
if (contentType === FORM_MEDIA_TYPE) {
|
|
570
|
+
const contentType = headers && (isHeaders(headers) ? headers : mergeHeaders(headers)).get("Content-Type");
|
|
571
|
+
if (FORM_MEDIA_TYPE_RE.test(contentType)) {
|
|
542
572
|
return formBody(body);
|
|
543
573
|
}
|
|
544
574
|
if (body && typeof body === "object" && MULTIPART_MEDIA_TYPE_RE.test(contentType)) {
|
|
@@ -569,7 +599,7 @@ function mergeHeaders(...allHeaders) {
|
|
|
569
599
|
if (!h || typeof h !== "object") {
|
|
570
600
|
continue;
|
|
571
601
|
}
|
|
572
|
-
const iterator = h
|
|
602
|
+
const iterator = isHeaders(h) ? h.entries() : Symbol.iterator in h ? new Headers(h).entries() : Object.entries(h);
|
|
573
603
|
for (const [k, v] of iterator) {
|
|
574
604
|
if (v === null) {
|
|
575
605
|
finalHeaders.delete(k);
|