openapi-typesafe-fetch 1.0.0-rc.4 → 1.0.0-rc.5
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/CHANGELOG.md +53 -0
- package/README.md +2 -2
- package/dist/helpers.d.cts +34 -11
- package/dist/helpers.d.mts +34 -11
- package/dist/helpers.d.ts +34 -11
- package/dist/index.cjs +113 -45
- package/dist/index.cjs.map +1 -1
- package/dist/index.d.cts +31 -8
- package/dist/index.d.mts +31 -8
- package/dist/index.d.ts +31 -8
- package/dist/index.mjs +113 -45
- package/dist/index.mjs.map +1 -1
- package/package.json +3 -3
- package/src/helpers.ts +50 -11
- package/src/index.d.ts +31 -8
- package/src/index.js +205 -61
package/CHANGELOG.md
CHANGED
|
@@ -2,6 +2,59 @@
|
|
|
2
2
|
|
|
3
3
|
This package is a fork of `openapi-fetch`. The entries above `0.17.0` are releases of openapi-typesafe-fetch; `0.17.0` and the entries below it are the upstream history up to the fork point (upstream commit `d46a319a`), with `openapi-fetch` versions.
|
|
4
4
|
|
|
5
|
+
## 1.0.0-rc.5
|
|
6
|
+
|
|
7
|
+
### Minor Changes
|
|
8
|
+
|
|
9
|
+
- [#68](https://github.com/openapi-typesafe/openapi-typesafe/pull/68) [`0f0792b`](https://github.com/openapi-typesafe/openapi-typesafe/commit/0f0792b8efa4f5139653cd285455f7d6165ddb95) Thanks [@fedorello](https://github.com/fedorello)! - A `FormData`, `Blob`, `File` or `ReadableStream` body from `undici` or another realm (an iframe) is
|
|
10
|
+
sent as it is: the client told a body by `instanceof`, so it JSON-encoded one of another class as
|
|
11
|
+
`{}`. `createClient` takes a `FormData` class next to `Request`: a multipart body is built with it,
|
|
12
|
+
and a FormData body of another class is copied into one of it, since undici's `Request` read the
|
|
13
|
+
platform's FormData as the text `[object FormData]`. With undici, pass
|
|
14
|
+
`createClient({ fetch, Request, FormData })` from the package. An instance of the platform's own
|
|
15
|
+
global class is still sent as it is, so the FormData, Blob and URLSearchParams of React Native and
|
|
16
|
+
the Blob and File of jsdom 20 (Jest 29), which have no type tag or no `arrayBuffer`, keep working.
|
|
17
|
+
|
|
18
|
+
- [#67](https://github.com/openapi-typesafe/openapi-typesafe/pull/67) [`fbaded2`](https://github.com/openapi-typesafe/openapi-typesafe/commit/fbaded241385d032d5b28d2c93d5e0a7d6ae292b) Thanks [@fedorello](https://github.com/fedorello)! - The client has a read-only `baseUrl`: the `baseUrl` option without its trailing slash, or `""`.
|
|
19
|
+
|
|
20
|
+
### Patch Changes
|
|
21
|
+
|
|
22
|
+
- [#68](https://github.com/openapi-typesafe/openapi-typesafe/pull/68) [`0f0792b`](https://github.com/openapi-typesafe/openapi-typesafe/commit/0f0792b8efa4f5139653cd285455f7d6165ddb95) Thanks [@fedorello](https://github.com/fedorello)! - An empty success body read with `parseAs: "text"`, `"blob"`, `"arrayBuffer"` or `"stream"` gives
|
|
23
|
+
`""`, an empty `Blob` or `ArrayBuffer`, or `response.body`, as its type says. A 204, an answer to
|
|
24
|
+
HEAD or a `Content-Length: 0` gave `undefined`, while the same empty body without a length gave
|
|
25
|
+
`""`. With the default `parseAs: "json"` an empty body is still `undefined`.
|
|
26
|
+
|
|
27
|
+
- [#68](https://github.com/openapi-typesafe/openapi-typesafe/pull/68) [`0f0792b`](https://github.com/openapi-typesafe/openapi-typesafe/commit/0f0792b8efa4f5139653cd285455f7d6165ddb95) Thanks [@fedorello](https://github.com/fedorello)! - `params.header` values are written in the `simple` style, the only style OpenAPI has for headers:
|
|
28
|
+
a `Date` as ISO 8601 (it was local time), an array as `a,b` (it was `a, b`) and an object as
|
|
29
|
+
`role,admin` (it was `[object Object]`), not percent-encoded, also in the names. A nested object or
|
|
30
|
+
array inside one throws, as in the query. An empty array or object sends no header, and `null`
|
|
31
|
+
still removes one.
|
|
32
|
+
|
|
33
|
+
- [#68](https://github.com/openapi-typesafe/openapi-typesafe/pull/68) [`0f0792b`](https://github.com/openapi-typesafe/openapi-typesafe/commit/0f0792b8efa4f5139653cd285455f7d6165ddb95) Thanks [@fedorello](https://github.com/fedorello)! - A form or multipart `Content-Type` with spaces or tabs before its parameters
|
|
34
|
+
(`application/x-www-form-urlencoded ; charset=utf-8`, RFC 9110 OWS) gives form data or `FormData`
|
|
35
|
+
again, in the runtime and in the result type of `defaultBodySerializer`; the object was sent as
|
|
36
|
+
JSON. The JSON check already took them.
|
|
37
|
+
|
|
38
|
+
- [#68](https://github.com/openapi-typesafe/openapi-typesafe/pull/68) [`0f0792b`](https://github.com/openapi-typesafe/openapi-typesafe/commit/0f0792b8efa4f5139653cd285455f7d6165ddb95) Thanks [@fedorello](https://github.com/fedorello)! - `style` and `explode` of the `array` and `object` options of `QuerySerializerOptions` are optional,
|
|
39
|
+
and one left out takes the client's value, else its default (`form` or `deepObject`, exploded): a
|
|
40
|
+
request's `{ array: { explode: false } }` keeps the client's `style`. `onError` may return an
|
|
41
|
+
`Error` of another realm (an iframe, `node:vm`); it was rejected as not an error.
|
|
42
|
+
|
|
43
|
+
- [#68](https://github.com/openapi-typesafe/openapi-typesafe/pull/68) [`0f0792b`](https://github.com/openapi-typesafe/openapi-typesafe/commit/0f0792b8efa4f5139653cd285455f7d6165ddb95) Thanks [@fedorello](https://github.com/fedorello)! - The query of a request whose path template has a fragment (`/docs#intro`) goes before the
|
|
44
|
+
fragment, joined with a query the template already has. It was added after the `#`, where it is
|
|
45
|
+
part of the fragment, so `fetch` never sent it.
|
|
46
|
+
|
|
47
|
+
- [#75](https://github.com/openapi-typesafe/openapi-typesafe/pull/75) [`a74608e`](https://github.com/openapi-typesafe/openapi-typesafe/commit/a74608ec36b0a938594e0d0fac10c9fb222c1742) Thanks [@fedorello](https://github.com/fedorello)! - `Readable` and `Writable` find the `$Read` and `$Write` properties of an object with an index
|
|
48
|
+
signature (`additionalProperties`). The keys were read through the index signature, so no
|
|
49
|
+
property was marked: a request body required the `readOnly` `id`, and a response kept the
|
|
50
|
+
`writeOnly` password. The helper types that `--read-write-markers` writes are fixed the same way.
|
|
51
|
+
|
|
52
|
+
- [#68](https://github.com/openapi-typesafe/openapi-typesafe/pull/68) [`0f0792b`](https://github.com/openapi-typesafe/openapi-typesafe/commit/0f0792b8efa4f5139653cd285455f7d6165ddb95) Thanks [@fedorello](https://github.com/fedorello)! - A response is typed as data or an error by the first digit of its status, as `response.ok` reads
|
|
53
|
+
it: a declared 205, 208 or 226 types `data` with its body, and a 419, a 3XX (read with
|
|
54
|
+
`redirect: "manual"`) or a 1XX types `error` with its body.
|
|
55
|
+
The types classified by two closed lists of statuses, `OkStatus` and `ErrorStatus`, which stay
|
|
56
|
+
exported. New helper types `SuccessStatusKey` and `ErrorStatusKey` name the statuses of each kind.
|
|
57
|
+
|
|
5
58
|
## 1.0.0-rc.4
|
|
6
59
|
|
|
7
60
|
### Minor Changes
|
package/README.md
CHANGED
|
@@ -1,7 +1,7 @@
|
|
|
1
1
|
# openapi-typesafe-fetch
|
|
2
2
|
|
|
3
|
-
A type-safe `fetch` client for types generated by [`openapi-typesafe`](../openapi-typesafe). About
|
|
4
|
-
(
|
|
3
|
+
A type-safe `fetch` client for types generated by [`openapi-typesafe`](../openapi-typesafe). About 10.7 kB minified
|
|
4
|
+
(4 kB gzipped), no runtime dependencies, works in browsers, Node.js, Bun and Deno, and with React, Vue, Svelte or plain JavaScript.
|
|
5
5
|
|
|
6
6
|
A maintained, drop-in successor to `openapi-fetch` (see the [repository README](../../README.md)).
|
|
7
7
|
|
package/dist/helpers.d.cts
CHANGED
|
@@ -2,12 +2,32 @@
|
|
|
2
2
|
type HttpMethod = "get" | "put" | "post" | "delete" | "options" | "head" | "patch" | "trace"
|
|
3
3
|
/** OpenAPI 3.2: a safe, idempotent request with a body. */
|
|
4
4
|
| "query";
|
|
5
|
-
/**
|
|
5
|
+
/**
|
|
6
|
+
* Some 2XX statuses, kept for code that names them. The client does not classify by this list: a
|
|
7
|
+
* status is a success or an error by its first digit (`SuccessStatusKey`, `ErrorStatusKey`).
|
|
8
|
+
*/
|
|
6
9
|
type OkStatus = 200 | 201 | 202 | 203 | 204 | 206 | 207 | "2XX";
|
|
7
|
-
/** 4XX and 5XX statuses */
|
|
10
|
+
/** Some 4XX and 5XX statuses and `default`, kept like `OkStatus`. */
|
|
8
11
|
type ErrorStatus = 500 | 501 | 502 | 503 | 504 | 505 | 506 | 507 | 508 | 510 | 511 | "5XX" | 400 | 401 | 402 | 403 | 404 | 405 | 406 | 407 | 408 | 409 | 410 | 411 | 412 | 413 | 414 | 415 | 416 | 417 | 418 | 420 | 421 | 422 | 423 | 424 | 425 | 426 | 427 | 428 | 429 | 430 | 431 | 444 | 450 | 451 | 497 | 498 | 499 | "4XX" | "default";
|
|
12
|
+
type StatusDigit = 0 | 1 | 2 | 3 | 4 | 5 | 6 | 7 | 8 | 9;
|
|
13
|
+
/**
|
|
14
|
+
* The keys of a Response Object Map whose text matches a pattern. A generated map has numeric keys
|
|
15
|
+
* (`200`) and string ones (`"2XX"`, `"default"`), and a numeric literal matches no template
|
|
16
|
+
* literal type, so each key is matched by its text.
|
|
17
|
+
*/
|
|
18
|
+
type StatusKeysMatching<Keys, Pattern extends string> = Keys extends string | number ? `${Keys}` extends Pattern ? Keys : never : never;
|
|
19
|
+
/**
|
|
20
|
+
* The success statuses of a Response Object Map: every 2XX one. A closed list of statuses left out
|
|
21
|
+
* the ones it did not name (205, 208, 226), and their bodies were typed as no data.
|
|
22
|
+
*/
|
|
23
|
+
type SuccessStatusKey<T> = StatusKeysMatching<keyof T, `2${StatusDigit}${StatusDigit}` | "2XX">;
|
|
24
|
+
/**
|
|
25
|
+
* The error statuses of a Response Object Map: every 1XX, 3XX, 4XX and 5XX one, and `default`. The
|
|
26
|
+
* client returns each response that is not `ok` as `error`, also a 3XX with `redirect: "manual"`.
|
|
27
|
+
*/
|
|
28
|
+
type ErrorStatusKey<T> = StatusKeysMatching<keyof T, `${1 | 3 | 4 | 5}${StatusDigit}${StatusDigit}` | `${1 | 3 | 4 | 5}XX` | "default">;
|
|
9
29
|
/** Get a union of OK Statuses */
|
|
10
|
-
type OKStatusUnion<T> = FilterKeys<T,
|
|
30
|
+
type OKStatusUnion<T> = FilterKeys<T, SuccessStatusKey<T>>;
|
|
11
31
|
/** Get first error status, in order */
|
|
12
32
|
type FirstErrorStatus<T> = T extends {
|
|
13
33
|
500: unknown;
|
|
@@ -178,7 +198,7 @@ type OperationRequestBodyContent<T> = FilterKeys<OperationRequestBodyMediaConten
|
|
|
178
198
|
* Responses Object), so then it covers every success (upstream #1862). With a 2XX status declared,
|
|
179
199
|
* `default` is left to the errors, as before.
|
|
180
200
|
*/
|
|
181
|
-
type SuccessStatus<T> = [
|
|
201
|
+
type SuccessStatus<T> = [SuccessStatusKey<T>] extends [never] ? Extract<keyof T, "default"> : SuccessStatusKey<T>;
|
|
182
202
|
/**
|
|
183
203
|
* Return all 2XX responses from a Response Object Map, or `default` if it declares no 2XX one. For
|
|
184
204
|
* a union of maps (a call with a union of paths), the union of each map's responses: `keyof` of a
|
|
@@ -190,10 +210,10 @@ type GetResponseContent<T extends Record<string | number, any>, Media extends Me
|
|
|
190
210
|
[K in ResponseCode]: T[K]["content"] extends Record<string, any> ? FilterKeys<T[K]["content"], Media> extends never ? T[K]["content"] : FilterKeys<T[K]["content"], Media> : K extends keyof T ? T[K]["content"] : never;
|
|
191
211
|
}[ResponseCode] : never;
|
|
192
212
|
/**
|
|
193
|
-
* Return all
|
|
194
|
-
* the union of each map's responses, as `SuccessResponse`.
|
|
213
|
+
* Return all 1XX, 3XX, 4XX, 5XX and `default` responses from a Response Object Map; for a union
|
|
214
|
+
* of maps, the union of each map's responses, as `SuccessResponse`.
|
|
195
215
|
*/
|
|
196
|
-
type ErrorResponse<T extends Record<string | number, any>, Media extends MediaType = MediaType> = T extends unknown ? GetResponseContent<T, Media,
|
|
216
|
+
type ErrorResponse<T extends Record<string | number, any>, Media extends MediaType = MediaType> = T extends unknown ? GetResponseContent<T, Media, ErrorStatusKey<T>> : never;
|
|
197
217
|
/** Return first JSON-like 2XX response from a path + HTTP method */
|
|
198
218
|
type SuccessResponseJSON<PathMethod extends Record<string | number, any>> = SuccessResponse<ResponseObjectMap<PathMethod>, `${string}/json`>;
|
|
199
219
|
/** Return first JSON-like 5XX or 4XX response from a path + HTTP method */
|
|
@@ -258,10 +278,13 @@ type ReadWriteLeaf = ReadWritePrimitive | ((...args: never) => unknown);
|
|
|
258
278
|
* `any` key is not marked either (`0 extends 1 & T[K]` is true only for `any`): a check on `any`
|
|
259
279
|
* takes both branches, so `Readable` dropped the key and `Writable` typed it `undefined`. The
|
|
260
280
|
* checks are not distributive (their check types are not type parameters), so `never` is seen.
|
|
281
|
+
* The keys are read by remapping, not as `{ ... }[keyof T]`: with an index signature (from
|
|
282
|
+
* `additionalProperties`) `keyof T` is `string`, which indexes the signature only, so no property
|
|
283
|
+
* was marked and `Writable` made a readOnly property a required `never`.
|
|
261
284
|
*/
|
|
262
|
-
type ReadWriteMarkedKeys<T, Marker> = {
|
|
263
|
-
[K in keyof T
|
|
264
|
-
}
|
|
285
|
+
type ReadWriteMarkedKeys<T, Marker> = keyof {
|
|
286
|
+
[K in keyof T as 0 extends 1 & T[K] ? never : NonNullable<T[K]> extends never ? never : NonNullable<T[K]> extends Marker ? K : never]: never;
|
|
287
|
+
};
|
|
265
288
|
/**
|
|
266
289
|
* Resolve an array or a tuple element by element. An array uses `T[number]`, not `infer E`: for an
|
|
267
290
|
* intersection of arrays (`allOf`) inference keeps one member and narrowing breaks (upstream
|
|
@@ -296,4 +319,4 @@ type Writable<T> = T extends $Read<any> ? never : T extends $Write<infer U> ? Wr
|
|
|
296
319
|
[K in ReadWriteMarkedKeys<T, $Read<any>>]?: never;
|
|
297
320
|
} : T;
|
|
298
321
|
|
|
299
|
-
export type { $Read, $Write, ErrorResponse, ErrorResponseJSON, ErrorStatus, FilterKeys, FindRequiredKeys, FirstErrorStatus, GetResponseContent, GetValueWithDefault, HasRequiredKeys, HttpMethod, IsOperationRequestBodyOptional, JSONLike, MediaType, OKStatusUnion, OkStatus, OperationObject, OperationRequestBody, OperationRequestBodyContent, OperationRequestBodyMediaContent, PathItemObject, PathsWithMethod, Readable, RequestBodyJSON, RequiredKeysOf, ResponseContent, ResponseObjectMap, SuccessResponse, SuccessResponseJSON, Writable };
|
|
322
|
+
export type { $Read, $Write, ErrorResponse, ErrorResponseJSON, ErrorStatus, ErrorStatusKey, FilterKeys, FindRequiredKeys, FirstErrorStatus, GetResponseContent, GetValueWithDefault, HasRequiredKeys, HttpMethod, IsOperationRequestBodyOptional, JSONLike, MediaType, OKStatusUnion, OkStatus, OperationObject, OperationRequestBody, OperationRequestBodyContent, OperationRequestBodyMediaContent, PathItemObject, PathsWithMethod, Readable, RequestBodyJSON, RequiredKeysOf, ResponseContent, ResponseObjectMap, SuccessResponse, SuccessResponseJSON, SuccessStatusKey, Writable };
|
package/dist/helpers.d.mts
CHANGED
|
@@ -2,12 +2,32 @@
|
|
|
2
2
|
type HttpMethod = "get" | "put" | "post" | "delete" | "options" | "head" | "patch" | "trace"
|
|
3
3
|
/** OpenAPI 3.2: a safe, idempotent request with a body. */
|
|
4
4
|
| "query";
|
|
5
|
-
/**
|
|
5
|
+
/**
|
|
6
|
+
* Some 2XX statuses, kept for code that names them. The client does not classify by this list: a
|
|
7
|
+
* status is a success or an error by its first digit (`SuccessStatusKey`, `ErrorStatusKey`).
|
|
8
|
+
*/
|
|
6
9
|
type OkStatus = 200 | 201 | 202 | 203 | 204 | 206 | 207 | "2XX";
|
|
7
|
-
/** 4XX and 5XX statuses */
|
|
10
|
+
/** Some 4XX and 5XX statuses and `default`, kept like `OkStatus`. */
|
|
8
11
|
type ErrorStatus = 500 | 501 | 502 | 503 | 504 | 505 | 506 | 507 | 508 | 510 | 511 | "5XX" | 400 | 401 | 402 | 403 | 404 | 405 | 406 | 407 | 408 | 409 | 410 | 411 | 412 | 413 | 414 | 415 | 416 | 417 | 418 | 420 | 421 | 422 | 423 | 424 | 425 | 426 | 427 | 428 | 429 | 430 | 431 | 444 | 450 | 451 | 497 | 498 | 499 | "4XX" | "default";
|
|
12
|
+
type StatusDigit = 0 | 1 | 2 | 3 | 4 | 5 | 6 | 7 | 8 | 9;
|
|
13
|
+
/**
|
|
14
|
+
* The keys of a Response Object Map whose text matches a pattern. A generated map has numeric keys
|
|
15
|
+
* (`200`) and string ones (`"2XX"`, `"default"`), and a numeric literal matches no template
|
|
16
|
+
* literal type, so each key is matched by its text.
|
|
17
|
+
*/
|
|
18
|
+
type StatusKeysMatching<Keys, Pattern extends string> = Keys extends string | number ? `${Keys}` extends Pattern ? Keys : never : never;
|
|
19
|
+
/**
|
|
20
|
+
* The success statuses of a Response Object Map: every 2XX one. A closed list of statuses left out
|
|
21
|
+
* the ones it did not name (205, 208, 226), and their bodies were typed as no data.
|
|
22
|
+
*/
|
|
23
|
+
type SuccessStatusKey<T> = StatusKeysMatching<keyof T, `2${StatusDigit}${StatusDigit}` | "2XX">;
|
|
24
|
+
/**
|
|
25
|
+
* The error statuses of a Response Object Map: every 1XX, 3XX, 4XX and 5XX one, and `default`. The
|
|
26
|
+
* client returns each response that is not `ok` as `error`, also a 3XX with `redirect: "manual"`.
|
|
27
|
+
*/
|
|
28
|
+
type ErrorStatusKey<T> = StatusKeysMatching<keyof T, `${1 | 3 | 4 | 5}${StatusDigit}${StatusDigit}` | `${1 | 3 | 4 | 5}XX` | "default">;
|
|
9
29
|
/** Get a union of OK Statuses */
|
|
10
|
-
type OKStatusUnion<T> = FilterKeys<T,
|
|
30
|
+
type OKStatusUnion<T> = FilterKeys<T, SuccessStatusKey<T>>;
|
|
11
31
|
/** Get first error status, in order */
|
|
12
32
|
type FirstErrorStatus<T> = T extends {
|
|
13
33
|
500: unknown;
|
|
@@ -178,7 +198,7 @@ type OperationRequestBodyContent<T> = FilterKeys<OperationRequestBodyMediaConten
|
|
|
178
198
|
* Responses Object), so then it covers every success (upstream #1862). With a 2XX status declared,
|
|
179
199
|
* `default` is left to the errors, as before.
|
|
180
200
|
*/
|
|
181
|
-
type SuccessStatus<T> = [
|
|
201
|
+
type SuccessStatus<T> = [SuccessStatusKey<T>] extends [never] ? Extract<keyof T, "default"> : SuccessStatusKey<T>;
|
|
182
202
|
/**
|
|
183
203
|
* Return all 2XX responses from a Response Object Map, or `default` if it declares no 2XX one. For
|
|
184
204
|
* a union of maps (a call with a union of paths), the union of each map's responses: `keyof` of a
|
|
@@ -190,10 +210,10 @@ type GetResponseContent<T extends Record<string | number, any>, Media extends Me
|
|
|
190
210
|
[K in ResponseCode]: T[K]["content"] extends Record<string, any> ? FilterKeys<T[K]["content"], Media> extends never ? T[K]["content"] : FilterKeys<T[K]["content"], Media> : K extends keyof T ? T[K]["content"] : never;
|
|
191
211
|
}[ResponseCode] : never;
|
|
192
212
|
/**
|
|
193
|
-
* Return all
|
|
194
|
-
* the union of each map's responses, as `SuccessResponse`.
|
|
213
|
+
* Return all 1XX, 3XX, 4XX, 5XX and `default` responses from a Response Object Map; for a union
|
|
214
|
+
* of maps, the union of each map's responses, as `SuccessResponse`.
|
|
195
215
|
*/
|
|
196
|
-
type ErrorResponse<T extends Record<string | number, any>, Media extends MediaType = MediaType> = T extends unknown ? GetResponseContent<T, Media,
|
|
216
|
+
type ErrorResponse<T extends Record<string | number, any>, Media extends MediaType = MediaType> = T extends unknown ? GetResponseContent<T, Media, ErrorStatusKey<T>> : never;
|
|
197
217
|
/** Return first JSON-like 2XX response from a path + HTTP method */
|
|
198
218
|
type SuccessResponseJSON<PathMethod extends Record<string | number, any>> = SuccessResponse<ResponseObjectMap<PathMethod>, `${string}/json`>;
|
|
199
219
|
/** Return first JSON-like 5XX or 4XX response from a path + HTTP method */
|
|
@@ -258,10 +278,13 @@ type ReadWriteLeaf = ReadWritePrimitive | ((...args: never) => unknown);
|
|
|
258
278
|
* `any` key is not marked either (`0 extends 1 & T[K]` is true only for `any`): a check on `any`
|
|
259
279
|
* takes both branches, so `Readable` dropped the key and `Writable` typed it `undefined`. The
|
|
260
280
|
* checks are not distributive (their check types are not type parameters), so `never` is seen.
|
|
281
|
+
* The keys are read by remapping, not as `{ ... }[keyof T]`: with an index signature (from
|
|
282
|
+
* `additionalProperties`) `keyof T` is `string`, which indexes the signature only, so no property
|
|
283
|
+
* was marked and `Writable` made a readOnly property a required `never`.
|
|
261
284
|
*/
|
|
262
|
-
type ReadWriteMarkedKeys<T, Marker> = {
|
|
263
|
-
[K in keyof T
|
|
264
|
-
}
|
|
285
|
+
type ReadWriteMarkedKeys<T, Marker> = keyof {
|
|
286
|
+
[K in keyof T as 0 extends 1 & T[K] ? never : NonNullable<T[K]> extends never ? never : NonNullable<T[K]> extends Marker ? K : never]: never;
|
|
287
|
+
};
|
|
265
288
|
/**
|
|
266
289
|
* Resolve an array or a tuple element by element. An array uses `T[number]`, not `infer E`: for an
|
|
267
290
|
* intersection of arrays (`allOf`) inference keeps one member and narrowing breaks (upstream
|
|
@@ -296,4 +319,4 @@ type Writable<T> = T extends $Read<any> ? never : T extends $Write<infer U> ? Wr
|
|
|
296
319
|
[K in ReadWriteMarkedKeys<T, $Read<any>>]?: never;
|
|
297
320
|
} : T;
|
|
298
321
|
|
|
299
|
-
export type { $Read, $Write, ErrorResponse, ErrorResponseJSON, ErrorStatus, FilterKeys, FindRequiredKeys, FirstErrorStatus, GetResponseContent, GetValueWithDefault, HasRequiredKeys, HttpMethod, IsOperationRequestBodyOptional, JSONLike, MediaType, OKStatusUnion, OkStatus, OperationObject, OperationRequestBody, OperationRequestBodyContent, OperationRequestBodyMediaContent, PathItemObject, PathsWithMethod, Readable, RequestBodyJSON, RequiredKeysOf, ResponseContent, ResponseObjectMap, SuccessResponse, SuccessResponseJSON, Writable };
|
|
322
|
+
export type { $Read, $Write, ErrorResponse, ErrorResponseJSON, ErrorStatus, ErrorStatusKey, FilterKeys, FindRequiredKeys, FirstErrorStatus, GetResponseContent, GetValueWithDefault, HasRequiredKeys, HttpMethod, IsOperationRequestBodyOptional, JSONLike, MediaType, OKStatusUnion, OkStatus, OperationObject, OperationRequestBody, OperationRequestBodyContent, OperationRequestBodyMediaContent, PathItemObject, PathsWithMethod, Readable, RequestBodyJSON, RequiredKeysOf, ResponseContent, ResponseObjectMap, SuccessResponse, SuccessResponseJSON, SuccessStatusKey, Writable };
|
package/dist/helpers.d.ts
CHANGED
|
@@ -2,12 +2,32 @@
|
|
|
2
2
|
type HttpMethod = "get" | "put" | "post" | "delete" | "options" | "head" | "patch" | "trace"
|
|
3
3
|
/** OpenAPI 3.2: a safe, idempotent request with a body. */
|
|
4
4
|
| "query";
|
|
5
|
-
/**
|
|
5
|
+
/**
|
|
6
|
+
* Some 2XX statuses, kept for code that names them. The client does not classify by this list: a
|
|
7
|
+
* status is a success or an error by its first digit (`SuccessStatusKey`, `ErrorStatusKey`).
|
|
8
|
+
*/
|
|
6
9
|
type OkStatus = 200 | 201 | 202 | 203 | 204 | 206 | 207 | "2XX";
|
|
7
|
-
/** 4XX and 5XX statuses */
|
|
10
|
+
/** Some 4XX and 5XX statuses and `default`, kept like `OkStatus`. */
|
|
8
11
|
type ErrorStatus = 500 | 501 | 502 | 503 | 504 | 505 | 506 | 507 | 508 | 510 | 511 | "5XX" | 400 | 401 | 402 | 403 | 404 | 405 | 406 | 407 | 408 | 409 | 410 | 411 | 412 | 413 | 414 | 415 | 416 | 417 | 418 | 420 | 421 | 422 | 423 | 424 | 425 | 426 | 427 | 428 | 429 | 430 | 431 | 444 | 450 | 451 | 497 | 498 | 499 | "4XX" | "default";
|
|
12
|
+
type StatusDigit = 0 | 1 | 2 | 3 | 4 | 5 | 6 | 7 | 8 | 9;
|
|
13
|
+
/**
|
|
14
|
+
* The keys of a Response Object Map whose text matches a pattern. A generated map has numeric keys
|
|
15
|
+
* (`200`) and string ones (`"2XX"`, `"default"`), and a numeric literal matches no template
|
|
16
|
+
* literal type, so each key is matched by its text.
|
|
17
|
+
*/
|
|
18
|
+
type StatusKeysMatching<Keys, Pattern extends string> = Keys extends string | number ? `${Keys}` extends Pattern ? Keys : never : never;
|
|
19
|
+
/**
|
|
20
|
+
* The success statuses of a Response Object Map: every 2XX one. A closed list of statuses left out
|
|
21
|
+
* the ones it did not name (205, 208, 226), and their bodies were typed as no data.
|
|
22
|
+
*/
|
|
23
|
+
type SuccessStatusKey<T> = StatusKeysMatching<keyof T, `2${StatusDigit}${StatusDigit}` | "2XX">;
|
|
24
|
+
/**
|
|
25
|
+
* The error statuses of a Response Object Map: every 1XX, 3XX, 4XX and 5XX one, and `default`. The
|
|
26
|
+
* client returns each response that is not `ok` as `error`, also a 3XX with `redirect: "manual"`.
|
|
27
|
+
*/
|
|
28
|
+
type ErrorStatusKey<T> = StatusKeysMatching<keyof T, `${1 | 3 | 4 | 5}${StatusDigit}${StatusDigit}` | `${1 | 3 | 4 | 5}XX` | "default">;
|
|
9
29
|
/** Get a union of OK Statuses */
|
|
10
|
-
type OKStatusUnion<T> = FilterKeys<T,
|
|
30
|
+
type OKStatusUnion<T> = FilterKeys<T, SuccessStatusKey<T>>;
|
|
11
31
|
/** Get first error status, in order */
|
|
12
32
|
type FirstErrorStatus<T> = T extends {
|
|
13
33
|
500: unknown;
|
|
@@ -178,7 +198,7 @@ type OperationRequestBodyContent<T> = FilterKeys<OperationRequestBodyMediaConten
|
|
|
178
198
|
* Responses Object), so then it covers every success (upstream #1862). With a 2XX status declared,
|
|
179
199
|
* `default` is left to the errors, as before.
|
|
180
200
|
*/
|
|
181
|
-
type SuccessStatus<T> = [
|
|
201
|
+
type SuccessStatus<T> = [SuccessStatusKey<T>] extends [never] ? Extract<keyof T, "default"> : SuccessStatusKey<T>;
|
|
182
202
|
/**
|
|
183
203
|
* Return all 2XX responses from a Response Object Map, or `default` if it declares no 2XX one. For
|
|
184
204
|
* a union of maps (a call with a union of paths), the union of each map's responses: `keyof` of a
|
|
@@ -190,10 +210,10 @@ type GetResponseContent<T extends Record<string | number, any>, Media extends Me
|
|
|
190
210
|
[K in ResponseCode]: T[K]["content"] extends Record<string, any> ? FilterKeys<T[K]["content"], Media> extends never ? T[K]["content"] : FilterKeys<T[K]["content"], Media> : K extends keyof T ? T[K]["content"] : never;
|
|
191
211
|
}[ResponseCode] : never;
|
|
192
212
|
/**
|
|
193
|
-
* Return all
|
|
194
|
-
* the union of each map's responses, as `SuccessResponse`.
|
|
213
|
+
* Return all 1XX, 3XX, 4XX, 5XX and `default` responses from a Response Object Map; for a union
|
|
214
|
+
* of maps, the union of each map's responses, as `SuccessResponse`.
|
|
195
215
|
*/
|
|
196
|
-
type ErrorResponse<T extends Record<string | number, any>, Media extends MediaType = MediaType> = T extends unknown ? GetResponseContent<T, Media,
|
|
216
|
+
type ErrorResponse<T extends Record<string | number, any>, Media extends MediaType = MediaType> = T extends unknown ? GetResponseContent<T, Media, ErrorStatusKey<T>> : never;
|
|
197
217
|
/** Return first JSON-like 2XX response from a path + HTTP method */
|
|
198
218
|
type SuccessResponseJSON<PathMethod extends Record<string | number, any>> = SuccessResponse<ResponseObjectMap<PathMethod>, `${string}/json`>;
|
|
199
219
|
/** Return first JSON-like 5XX or 4XX response from a path + HTTP method */
|
|
@@ -258,10 +278,13 @@ type ReadWriteLeaf = ReadWritePrimitive | ((...args: never) => unknown);
|
|
|
258
278
|
* `any` key is not marked either (`0 extends 1 & T[K]` is true only for `any`): a check on `any`
|
|
259
279
|
* takes both branches, so `Readable` dropped the key and `Writable` typed it `undefined`. The
|
|
260
280
|
* checks are not distributive (their check types are not type parameters), so `never` is seen.
|
|
281
|
+
* The keys are read by remapping, not as `{ ... }[keyof T]`: with an index signature (from
|
|
282
|
+
* `additionalProperties`) `keyof T` is `string`, which indexes the signature only, so no property
|
|
283
|
+
* was marked and `Writable` made a readOnly property a required `never`.
|
|
261
284
|
*/
|
|
262
|
-
type ReadWriteMarkedKeys<T, Marker> = {
|
|
263
|
-
[K in keyof T
|
|
264
|
-
}
|
|
285
|
+
type ReadWriteMarkedKeys<T, Marker> = keyof {
|
|
286
|
+
[K in keyof T as 0 extends 1 & T[K] ? never : NonNullable<T[K]> extends never ? never : NonNullable<T[K]> extends Marker ? K : never]: never;
|
|
287
|
+
};
|
|
265
288
|
/**
|
|
266
289
|
* Resolve an array or a tuple element by element. An array uses `T[number]`, not `infer E`: for an
|
|
267
290
|
* intersection of arrays (`allOf`) inference keeps one member and narrowing breaks (upstream
|
|
@@ -296,4 +319,4 @@ type Writable<T> = T extends $Read<any> ? never : T extends $Write<infer U> ? Wr
|
|
|
296
319
|
[K in ReadWriteMarkedKeys<T, $Read<any>>]?: never;
|
|
297
320
|
} : T;
|
|
298
321
|
|
|
299
|
-
export type { $Read, $Write, ErrorResponse, ErrorResponseJSON, ErrorStatus, FilterKeys, FindRequiredKeys, FirstErrorStatus, GetResponseContent, GetValueWithDefault, HasRequiredKeys, HttpMethod, IsOperationRequestBodyOptional, JSONLike, MediaType, OKStatusUnion, OkStatus, OperationObject, OperationRequestBody, OperationRequestBodyContent, OperationRequestBodyMediaContent, PathItemObject, PathsWithMethod, Readable, RequestBodyJSON, RequiredKeysOf, ResponseContent, ResponseObjectMap, SuccessResponse, SuccessResponseJSON, Writable };
|
|
322
|
+
export type { $Read, $Write, ErrorResponse, ErrorResponseJSON, ErrorStatus, ErrorStatusKey, FilterKeys, FindRequiredKeys, FirstErrorStatus, GetResponseContent, GetValueWithDefault, HasRequiredKeys, HttpMethod, IsOperationRequestBodyOptional, JSONLike, MediaType, OKStatusUnion, OkStatus, OperationObject, OperationRequestBody, OperationRequestBodyContent, OperationRequestBodyMediaContent, PathItemObject, PathsWithMethod, Readable, RequestBodyJSON, RequiredKeysOf, ResponseContent, ResponseObjectMap, SuccessResponse, SuccessResponseJSON, SuccessStatusKey, Writable };
|
package/dist/index.cjs
CHANGED
|
@@ -7,11 +7,18 @@ const DOT_SEGMENT_RE = /^\.\.?$/;
|
|
|
7
7
|
const QUERY_OR_FRAGMENT_RE = /[?#]/;
|
|
8
8
|
const JSON_MEDIA_TYPE_RE = /^[^/]+\/([^;]*\+)?json\s*(;|$)/i;
|
|
9
9
|
const TEXT_MEDIA_TYPE_RE = /^text\//i;
|
|
10
|
-
const FORM_MEDIA_TYPE_RE = /^application\/x-www-form-urlencoded(;|$)/i;
|
|
11
|
-
const MULTIPART_MEDIA_TYPE_RE = /^multipart\/form-data(;|$)/i;
|
|
10
|
+
const FORM_MEDIA_TYPE_RE = /^application\/x-www-form-urlencoded[ \t]*(;|$)/i;
|
|
11
|
+
const MULTIPART_MEDIA_TYPE_RE = /^multipart\/form-data[ \t]*(;|$)/i;
|
|
12
12
|
const NESTED_FORM_VALUE_MESSAGE = "Nested form values need your own `bodySerializer()`.";
|
|
13
13
|
const BOUNDARY_PARAMETER_RE = /;\s*boundary=/i;
|
|
14
14
|
const CLIENT_BUILT_FORM = Symbol.for("openapi-typesafe-fetch/form");
|
|
15
|
+
const TYPE_TAG_START = "[object ".length;
|
|
16
|
+
const TYPE_TAG_END = -1;
|
|
17
|
+
const BLOB_CLASSES = ["Blob", "File"];
|
|
18
|
+
const RAW_BODY_CLASSES = ["URLSearchParams", "ArrayBuffer"];
|
|
19
|
+
const FORM_DATA_METHODS = ["append", "entries"];
|
|
20
|
+
const BLOB_METHODS = ["arrayBuffer"];
|
|
21
|
+
const STREAM_METHODS = ["getReader"];
|
|
15
22
|
const supportsRequestInitExt = () => {
|
|
16
23
|
return typeof process === "object" && process.versions?.undici;
|
|
17
24
|
};
|
|
@@ -24,6 +31,7 @@ function createClient(clientOptions) {
|
|
|
24
31
|
let {
|
|
25
32
|
baseUrl = "",
|
|
26
33
|
Request: CustomRequest = globalThis.Request,
|
|
34
|
+
FormData: CustomFormData = globalThis.FormData,
|
|
27
35
|
fetch: baseFetch = globalThis.fetch,
|
|
28
36
|
querySerializer: globalQuerySerializer,
|
|
29
37
|
bodySerializer: globalBodySerializer,
|
|
@@ -56,12 +64,10 @@ function createClient(clientOptions) {
|
|
|
56
64
|
}
|
|
57
65
|
let querySerializer = typeof globalQuerySerializer === "function" ? globalQuerySerializer : createQuerySerializer(globalQuerySerializer);
|
|
58
66
|
if (requestQuerySerializer) {
|
|
59
|
-
querySerializer = typeof requestQuerySerializer === "function" ? requestQuerySerializer : createQuerySerializer(
|
|
60
|
-
...typeof globalQuerySerializer === "object" ? globalQuerySerializer : {},
|
|
61
|
-
...requestQuerySerializer
|
|
62
|
-
});
|
|
67
|
+
querySerializer = typeof requestQuerySerializer === "function" ? requestQuerySerializer : createQuerySerializer(mergeQueryOptions(globalQuerySerializer, requestQuerySerializer));
|
|
63
68
|
}
|
|
64
69
|
const pathSerializer = requestPathSerializer || globalPathSerializer || defaultPathSerializer;
|
|
70
|
+
const headerParams = serializeHeaderParams(params.header);
|
|
65
71
|
const serializedBody = body === void 0 ? void 0 : bodySerializer(
|
|
66
72
|
body,
|
|
67
73
|
// Note: we declare mergeHeaders() both here and below because it’s a bit of a
|
|
@@ -70,29 +76,30 @@ function createClient(clientOptions) {
|
|
|
70
76
|
// content-type header. So we re-merge below, setting the content-type at the very
|
|
71
77
|
// beginning to be overwritten. Lastly, based on the way headers work, it’s not a simple
|
|
72
78
|
// “present-or-not” check because null intentionally un-sets headers.
|
|
73
|
-
mergeHeaders(baseHeaders, headers,
|
|
79
|
+
mergeHeaders(baseHeaders, headers, headerParams)
|
|
74
80
|
);
|
|
81
|
+
const requestBody = isFormData(serializedBody) ? asFormDataOf(CustomFormData, serializedBody) : serializedBody;
|
|
75
82
|
const finalHeaders = mergeHeaders(
|
|
76
83
|
// with no body, we should not to set Content-Type
|
|
77
|
-
|
|
78
|
-
isRawBody(
|
|
84
|
+
requestBody === void 0 || // a raw body (FormData, Blob, ...) gets its Content-Type from the platform, or none
|
|
85
|
+
isRawBody(requestBody) ? {} : {
|
|
79
86
|
"Content-Type": "application/json"
|
|
80
87
|
},
|
|
81
88
|
baseHeaders,
|
|
82
89
|
headers,
|
|
83
|
-
|
|
90
|
+
headerParams
|
|
84
91
|
);
|
|
85
|
-
if (
|
|
86
|
-
dropFormContentType(finalHeaders,
|
|
92
|
+
if (isFormData(requestBody)) {
|
|
93
|
+
dropFormContentType(finalHeaders, requestBody[CLIENT_BUILT_FORM] === true);
|
|
87
94
|
}
|
|
88
95
|
const finalMiddlewares = [...globalMiddlewares, ...requestMiddlewares];
|
|
89
96
|
const requestInit = {
|
|
90
97
|
redirect: "follow",
|
|
91
98
|
// The Fetch standard requires `duplex: "half"` (its only value) for a stream body.
|
|
92
|
-
duplex: isStreamBody(
|
|
99
|
+
duplex: isStreamBody(requestBody) ? "half" : void 0,
|
|
93
100
|
...baseOptions,
|
|
94
101
|
...init,
|
|
95
|
-
body:
|
|
102
|
+
body: requestBody,
|
|
96
103
|
headers: finalHeaders
|
|
97
104
|
};
|
|
98
105
|
let id;
|
|
@@ -169,7 +176,7 @@ function createClient(clientOptions) {
|
|
|
169
176
|
response = result;
|
|
170
177
|
break;
|
|
171
178
|
}
|
|
172
|
-
if (result
|
|
179
|
+
if (isError(result)) {
|
|
173
180
|
errorAfterMiddleware = result;
|
|
174
181
|
continue;
|
|
175
182
|
}
|
|
@@ -209,16 +216,10 @@ function createClient(clientOptions) {
|
|
|
209
216
|
}
|
|
210
217
|
}
|
|
211
218
|
}
|
|
212
|
-
|
|
213
|
-
if (response.status === 204 || request.method === "HEAD" || contentLength === "0" && !response.headers.get("Transfer-Encoding")?.includes("chunked")) {
|
|
214
|
-
return response.ok ? { data: void 0, response } : { error: void 0, response };
|
|
215
|
-
}
|
|
216
|
-
if (response.ok) {
|
|
217
|
-
return { data: await readSuccessBody(response, parseAs), response };
|
|
218
|
-
}
|
|
219
|
-
return { error: await readErrorBody(response), response };
|
|
219
|
+
return readResult(response, parseAs, request.method);
|
|
220
220
|
}
|
|
221
221
|
return {
|
|
222
|
+
baseUrl,
|
|
222
223
|
request(method, url, init) {
|
|
223
224
|
return coreFetch(url, { ...init, method: method.toUpperCase() });
|
|
224
225
|
},
|
|
@@ -364,6 +365,20 @@ function parseJSON(raw, response) {
|
|
|
364
365
|
throw new SyntaxError(`${subject} is not JSON (${raw.length} characters); ${hint}`);
|
|
365
366
|
}
|
|
366
367
|
}
|
|
368
|
+
function hasEmptyBody(response, method) {
|
|
369
|
+
const isDeclaredEmpty = response.headers.get("Content-Length") === "0" && !response.headers.get("Transfer-Encoding")?.includes("chunked");
|
|
370
|
+
return method === "HEAD" || isDeclaredEmpty;
|
|
371
|
+
}
|
|
372
|
+
async function readResult(response, parseAs, method) {
|
|
373
|
+
const isEmpty = hasEmptyBody(response, method);
|
|
374
|
+
if (!response.ok) {
|
|
375
|
+
return { error: isEmpty ? void 0 : await readErrorBody(response), response };
|
|
376
|
+
}
|
|
377
|
+
if (parseAs === "json" && isEmpty) {
|
|
378
|
+
return { data: void 0, response };
|
|
379
|
+
}
|
|
380
|
+
return { data: await readSuccessBody(response, parseAs), response };
|
|
381
|
+
}
|
|
367
382
|
async function readSuccessBody(response, parseAs) {
|
|
368
383
|
if (parseAs === "stream") {
|
|
369
384
|
return response.body;
|
|
@@ -395,7 +410,27 @@ function isDate(value) {
|
|
|
395
410
|
function valueText(value) {
|
|
396
411
|
return isDate(value) ? value.toISOString() : value;
|
|
397
412
|
}
|
|
413
|
+
const HEADER_PARAM_STYLE = { style: "simple", explode: false, allowReserved: true };
|
|
414
|
+
function headerParamValue(value) {
|
|
415
|
+
if (value === null || typeof value !== "object" || isDate(value)) {
|
|
416
|
+
return valueText(value);
|
|
417
|
+
}
|
|
418
|
+
const serialize = Array.isArray(value) ? serializeArrayParam : serializeObjectParam;
|
|
419
|
+
return serialize("", value, HEADER_PARAM_STYLE) || void 0;
|
|
420
|
+
}
|
|
421
|
+
function serializeHeaderParams(header) {
|
|
422
|
+
if (!header || typeof header !== "object") {
|
|
423
|
+
return header;
|
|
424
|
+
}
|
|
425
|
+
const entries = Object.entries(header).map(([name, value]) => [name, headerParamValue(value)]);
|
|
426
|
+
return Object.fromEntries(entries);
|
|
427
|
+
}
|
|
398
428
|
function encodeValue(value, allowReserved) {
|
|
429
|
+
if (value && typeof value === "object" && !isDate(value)) {
|
|
430
|
+
throw new Error(
|
|
431
|
+
"Deeply-nested arrays/objects aren\u2019t supported. Provide your own `querySerializer()` to handle these."
|
|
432
|
+
);
|
|
433
|
+
}
|
|
399
434
|
const text = valueText(value);
|
|
400
435
|
return allowReserved === true ? text : encodeURIComponent(text);
|
|
401
436
|
}
|
|
@@ -403,11 +438,6 @@ function serializePrimitiveParam(name, value, options) {
|
|
|
403
438
|
if (value === void 0 || value === null) {
|
|
404
439
|
return "";
|
|
405
440
|
}
|
|
406
|
-
if (typeof value === "object" && !isDate(value)) {
|
|
407
|
-
throw new Error(
|
|
408
|
-
"Deeply-nested arrays/objects aren\u2019t supported. Provide your own `querySerializer()` to handle these."
|
|
409
|
-
);
|
|
410
|
-
}
|
|
411
441
|
return `${name}=${encodeValue(value, options?.allowReserved)}`;
|
|
412
442
|
}
|
|
413
443
|
function joinExploded(values, style) {
|
|
@@ -418,7 +448,10 @@ function joinExploded(values, style) {
|
|
|
418
448
|
function serializeUnexplodedObject(name, value, options) {
|
|
419
449
|
const values = [];
|
|
420
450
|
for (const k in value) {
|
|
421
|
-
values.push(
|
|
451
|
+
values.push(
|
|
452
|
+
encodeValue(k, options.allowReserved),
|
|
453
|
+
encodeValue(value[k], options.allowReserved)
|
|
454
|
+
);
|
|
422
455
|
}
|
|
423
456
|
const final = values.join(",");
|
|
424
457
|
switch (options.style) {
|
|
@@ -492,17 +525,15 @@ function serializeQueryParam(name, value, options) {
|
|
|
492
525
|
const allowReserved = options?.allowReserved || false;
|
|
493
526
|
if (Array.isArray(value)) {
|
|
494
527
|
return serializeArrayParam(name, value, {
|
|
495
|
-
style: "form",
|
|
496
|
-
explode: true,
|
|
497
|
-
...options?.array,
|
|
528
|
+
style: options?.array?.style ?? "form",
|
|
529
|
+
explode: options?.array?.explode ?? true,
|
|
498
530
|
allowReserved
|
|
499
531
|
});
|
|
500
532
|
}
|
|
501
533
|
if (value && typeof value === "object" && !isDate(value)) {
|
|
502
534
|
return serializeObjectParam(name, value, {
|
|
503
|
-
style: "deepObject",
|
|
504
|
-
explode: true,
|
|
505
|
-
...options?.object,
|
|
535
|
+
style: options?.object?.style ?? "deepObject",
|
|
536
|
+
explode: options?.object?.explode ?? true,
|
|
506
537
|
allowReserved
|
|
507
538
|
});
|
|
508
539
|
}
|
|
@@ -522,6 +553,15 @@ function createQuerySerializer(options) {
|
|
|
522
553
|
return search.join("&");
|
|
523
554
|
};
|
|
524
555
|
}
|
|
556
|
+
function mergeQueryOptions(clientOptions, requestOptions) {
|
|
557
|
+
const base = typeof clientOptions === "function" ? {} : clientOptions ?? {};
|
|
558
|
+
return {
|
|
559
|
+
...base,
|
|
560
|
+
...requestOptions,
|
|
561
|
+
array: { ...base.array, ...requestOptions.array },
|
|
562
|
+
object: { ...base.object, ...requestOptions.object }
|
|
563
|
+
};
|
|
564
|
+
}
|
|
525
565
|
function defaultPathSerializer(pathname, pathParams) {
|
|
526
566
|
const matches = pathname.match(PATH_PARAM_RE);
|
|
527
567
|
if (!matches) {
|
|
@@ -576,17 +616,41 @@ function checkSerializedPath(template, url) {
|
|
|
576
616
|
throw new Error(`${problem} path segment in "${name}"`);
|
|
577
617
|
}
|
|
578
618
|
}
|
|
579
|
-
function
|
|
619
|
+
function typeTag(value) {
|
|
620
|
+
return Object.prototype.toString.call(value).slice(TYPE_TAG_START, TYPE_TAG_END);
|
|
621
|
+
}
|
|
622
|
+
function isClass(value, name, methods = []) {
|
|
580
623
|
const type = globalThis[name];
|
|
581
|
-
return typeof type === "function" && value instanceof type;
|
|
624
|
+
return typeof type === "function" && value instanceof type || typeTag(value) === name && methods.every((method) => typeof value[method] === "function");
|
|
625
|
+
}
|
|
626
|
+
function isError(value) {
|
|
627
|
+
return isClass(value, "Error");
|
|
628
|
+
}
|
|
629
|
+
function isFormData(value) {
|
|
630
|
+
return isClass(value, "FormData", FORM_DATA_METHODS);
|
|
631
|
+
}
|
|
632
|
+
function isBlob(value) {
|
|
633
|
+
return BLOB_CLASSES.some((name) => isClass(value, name, BLOB_METHODS));
|
|
634
|
+
}
|
|
635
|
+
function isReadableStream(value) {
|
|
636
|
+
return isClass(value, "ReadableStream", STREAM_METHODS);
|
|
637
|
+
}
|
|
638
|
+
function asFormDataOf(FormDataClass, form) {
|
|
639
|
+
if (typeof FormDataClass !== "function" || form instanceof FormDataClass) {
|
|
640
|
+
return form;
|
|
641
|
+
}
|
|
642
|
+
const copy = new FormDataClass();
|
|
643
|
+
for (const [name, value] of form.entries()) {
|
|
644
|
+
copy.append(name, value);
|
|
645
|
+
}
|
|
646
|
+
copy[CLIENT_BUILT_FORM] = true;
|
|
647
|
+
return copy;
|
|
582
648
|
}
|
|
583
649
|
function isStreamBody(body) {
|
|
584
|
-
return
|
|
650
|
+
return isReadableStream(body) || typeof body?.[Symbol.asyncIterator] === "function";
|
|
585
651
|
}
|
|
586
652
|
function isRawBody(body) {
|
|
587
|
-
return
|
|
588
|
-
(name) => isPlatformInstance(body, name)
|
|
589
|
-
) || ArrayBuffer.isView(body) || isStreamBody(body);
|
|
653
|
+
return isFormData(body) || isBlob(body) || RAW_BODY_CLASSES.some((name) => isClass(body, name)) || ArrayBuffer.isView(body) || isStreamBody(body);
|
|
590
654
|
}
|
|
591
655
|
function isNestedFormValue(value) {
|
|
592
656
|
return value && typeof value === "object" && !isDate(value);
|
|
@@ -615,7 +679,7 @@ function multipartBody(body) {
|
|
|
615
679
|
formData[CLIENT_BUILT_FORM] = true;
|
|
616
680
|
for (const [name, value] of Object.entries(body)) {
|
|
617
681
|
for (const item of formItems(value)) {
|
|
618
|
-
if (
|
|
682
|
+
if (isBlob(item)) {
|
|
619
683
|
formData.append(name, item);
|
|
620
684
|
} else if (isNestedFormValue(item)) {
|
|
621
685
|
throw new Error(NESTED_FORM_VALUE_MESSAGE);
|
|
@@ -651,10 +715,14 @@ function createFinalURL(pathname, options) {
|
|
|
651
715
|
if (search.startsWith("?")) {
|
|
652
716
|
search = search.substring(1);
|
|
653
717
|
}
|
|
654
|
-
if (search) {
|
|
655
|
-
|
|
718
|
+
if (!search) {
|
|
719
|
+
return finalURL;
|
|
656
720
|
}
|
|
657
|
-
|
|
721
|
+
const hashIndex = finalURL.indexOf("#");
|
|
722
|
+
const fragmentStart = hashIndex === -1 ? finalURL.length : hashIndex;
|
|
723
|
+
const beforeFragment = finalURL.slice(0, fragmentStart);
|
|
724
|
+
const joiner = beforeFragment.includes("?") ? "&" : "?";
|
|
725
|
+
return `${beforeFragment}${joiner}${search}${finalURL.slice(fragmentStart)}`;
|
|
658
726
|
}
|
|
659
727
|
function mergeHeaders(...allHeaders) {
|
|
660
728
|
const finalHeaders = new Headers();
|