openapi-typesafe-fetch 1.0.0-rc.7 → 1.0.0-rc.9
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 +45 -0
- package/dist/helpers.d.cts +54 -16
- package/dist/helpers.d.mts +54 -16
- package/dist/helpers.d.ts +54 -16
- package/dist/index.cjs +20 -8
- package/dist/index.cjs.map +1 -1
- package/dist/index.d.cts +43 -40
- package/dist/index.d.mts +43 -40
- package/dist/index.d.ts +43 -40
- package/dist/index.mjs +20 -8
- package/dist/index.mjs.map +1 -1
- package/package.json +2 -2
- package/src/helpers.ts +91 -19
- package/src/index.d.ts +43 -40
- package/src/index.js +39 -14
package/CHANGELOG.md
CHANGED
|
@@ -2,6 +2,51 @@
|
|
|
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.9
|
|
6
|
+
|
|
7
|
+
### Patch Changes
|
|
8
|
+
|
|
9
|
+
- [#112](https://github.com/openapi-typesafe/openapi-typesafe/pull/112) [`05886b5`](https://github.com/openapi-typesafe/openapi-typesafe/commit/05886b5b8fdbb832f58abd12b9f8c741b0c73ad2) Thanks [@fedorello](https://github.com/fedorello)! - An error body is parsed as JSON only when its `Content-Type` is a JSON media type (`json` or a
|
|
10
|
+
`+json` suffix), or when the response has no `Content-Type` and the body is JSON. Any other error
|
|
11
|
+
body, such as `text/plain` or HTML, is returned as text, as its `string` type says. A `text/plain`
|
|
12
|
+
error body `429`, `true`, `0` or `null` came back as a number, a boolean or `null`, and with `0`
|
|
13
|
+
or `null` the check `if (error)` was skipped. openapi-fetch parses every error body, so this
|
|
14
|
+
differs from it.
|
|
15
|
+
|
|
16
|
+
- [#112](https://github.com/openapi-typesafe/openapi-typesafe/pull/112) [`05886b5`](https://github.com/openapi-typesafe/openapi-typesafe/commit/05886b5b8fdbb832f58abd12b9f8c741b0c73ad2) Thanks [@fedorello](https://github.com/fedorello)! - Code that is generic in the path compiles again. `SuccessResponse`, `ErrorResponse`,
|
|
17
|
+
`ErrorResponseJSON` and the `data` and `error` of `FetchResponse` for `Paths[Path]` gave the whole
|
|
18
|
+
`responses` map next to the response bodies, so assigning them to the bodies failed with TS2322.
|
|
19
|
+
They give the bodies now. This also fixes the error type of hook wrappers generic in the path.
|
|
20
|
+
|
|
21
|
+
- [#112](https://github.com/openapi-typesafe/openapi-typesafe/pull/112) [`05886b5`](https://github.com/openapi-typesafe/openapi-typesafe/commit/05886b5b8fdbb832f58abd12b9f8c741b0c73ad2) Thanks [@fedorello](https://github.com/fedorello)! - Every optional option of `createClient()` and of a request accepts `undefined` in its type, not
|
|
22
|
+
only the members of `querySerializer`: `baseUrl`, `fetch`, `Request`, `FormData`,
|
|
23
|
+
`querySerializer`, `bodySerializer`, `pathSerializer`, `headers`, `requestInitExt`, `parseAs`,
|
|
24
|
+
`middleware` and `params`. The client already counted `undefined` as left out, so a wrapper that
|
|
25
|
+
passes its own optional options on now compiles with `exactOptionalPropertyTypes`.
|
|
26
|
+
|
|
27
|
+
## 1.0.0-rc.8
|
|
28
|
+
|
|
29
|
+
### Patch Changes
|
|
30
|
+
|
|
31
|
+
- [#103](https://github.com/openapi-typesafe/openapi-typesafe/pull/103) [`091b864`](https://github.com/openapi-typesafe/openapi-typesafe/commit/091b864cdb6dd9e1ff5810d4d44cebee8a01a721) Thanks [@fedorello](https://github.com/fedorello)! - `createClient<any>()` types `data` and `error` as `any` again; they were `never`, so
|
|
32
|
+
`if (error)` made the whole result `never`. `SuccessResponse<any>` and `ErrorResponse<any>` are
|
|
33
|
+
`any` too.
|
|
34
|
+
|
|
35
|
+
- [#103](https://github.com/openapi-typesafe/openapi-typesafe/pull/103) [`091b864`](https://github.com/openapi-typesafe/openapi-typesafe/commit/091b864cdb6dd9e1ff5810d4d44cebee8a01a721) Thanks [@fedorello](https://github.com/fedorello)! - Response types follow what the client returns. `ErrorResponseJSON`, `SuccessResponseJSON` and the
|
|
36
|
+
`data` and `error` of a client made with ``createClient<paths, `${string}/json`>()`` keep a
|
|
37
|
+
JSON body of any JSON media type: an `application/problem+json` error, an
|
|
38
|
+
`application/vnd.api+json` success and `application/json; charset=utf-8` bodies were left out,
|
|
39
|
+
so the error could be `never` or miss a status. An error that is not JSON, such as the HTML page
|
|
40
|
+
of a proxy, is typed `string`, the text the client returns, instead of being left out.
|
|
41
|
+
|
|
42
|
+
- [#103](https://github.com/openapi-typesafe/openapi-typesafe/pull/103) [`091b864`](https://github.com/openapi-typesafe/openapi-typesafe/commit/091b864cdb6dd9e1ff5810d4d44cebee8a01a721) Thanks [@fedorello](https://github.com/fedorello)! - Every `querySerializer` option accepts `undefined` in its type, so a wrapper that passes its own
|
|
43
|
+
optional option on (`{ array: { explode: options.explode } }`) compiles with
|
|
44
|
+
`exactOptionalPropertyTypes`.
|
|
45
|
+
|
|
46
|
+
- [#103](https://github.com/openapi-typesafe/openapi-typesafe/pull/103) [`091b864`](https://github.com/openapi-typesafe/openapi-typesafe/commit/091b864cdb6dd9e1ff5810d4d44cebee8a01a721) Thanks [@fedorello](https://github.com/fedorello)! - `serializeArrayParam` and `serializeObjectParam` with a nested value in a path style (`simple`,
|
|
47
|
+
`label`, `matrix`) name `pathSerializer()` in their error, also inside your own `pathSerializer`,
|
|
48
|
+
instead of `querySerializer()`.
|
|
49
|
+
|
|
5
50
|
## 1.0.0-rc.7
|
|
6
51
|
|
|
7
52
|
### Patch Changes
|
package/dist/helpers.d.cts
CHANGED
|
@@ -199,31 +199,66 @@ type OperationRequestBodyContent<T> = FilterKeys<OperationRequestBodyMediaConten
|
|
|
199
199
|
* `default` is left to the errors, as before.
|
|
200
200
|
*/
|
|
201
201
|
type SuccessStatus<T> = [SuccessStatusKey<T>] extends [never] ? Extract<keyof T, "default"> : SuccessStatusKey<T>;
|
|
202
|
+
/**
|
|
203
|
+
* `true` for `any`: a conditional type on `any` takes both branches, so `any` is checked first.
|
|
204
|
+
* The response types check it inside `T extends unknown`: TypeScript relates a conditional type
|
|
205
|
+
* on a type parameter through the one with the parameter's constraint put in, while one on
|
|
206
|
+
* `IsAny<T>` relates through both branches, which for a generic path added the whole map.
|
|
207
|
+
*/
|
|
208
|
+
type IsAny<T> = 0 extends 1 & T ? true : false;
|
|
202
209
|
/**
|
|
203
210
|
* Return all 2XX responses from a Response Object Map, or `default` if it declares no 2XX one. For
|
|
204
211
|
* a union of maps (a call with a union of paths), the union of each map's responses: `keyof` of a
|
|
205
212
|
* union is only the shared statuses, so one map of them lost the body of every path that did not
|
|
206
|
-
* share its statuses, and a `default`-only path typed the data `undefined` (upstream).
|
|
213
|
+
* share its statuses, and a `default`-only path typed the data `undefined` (upstream). An `any`
|
|
214
|
+
* map (`createClient<any>()`) gives `any`: no key of `keyof any` is a status, which gave `never`.
|
|
215
|
+
*/
|
|
216
|
+
type SuccessResponse<T extends Record<string | number, any>, Media extends MediaType = MediaType> = T extends unknown ? IsAny<T> extends true ? T : ResponseBodies<T, Media, SuccessStatus<T>, never> : never;
|
|
217
|
+
/**
|
|
218
|
+
* Return all 1XX, 3XX, 4XX, 5XX and `default` responses from a Response Object Map; for a union
|
|
219
|
+
* of maps, the union of each map's responses, as `SuccessResponse`. An `any` map gives `any`.
|
|
207
220
|
*/
|
|
208
|
-
type
|
|
221
|
+
type ErrorResponse<T extends Record<string | number, any>, Media extends MediaType = MediaType> = T extends unknown ? IsAny<T> extends true ? T : ResponseBodies<T, Media, ErrorStatusKey<T>, string> : never;
|
|
209
222
|
/**
|
|
210
|
-
* The bodies of the responses `ResponseCode`
|
|
211
|
-
*
|
|
212
|
-
* `ErrorResponseJSON` for a `text/html` 500 was `{ "text/html": string }`, a body no response has.
|
|
213
|
-
* A response without `content` keeps its `content` type (`undefined` for `content?: never`).
|
|
223
|
+
* The bodies of the responses `ResponseCode` as the client returns them (`ResponseBody`): each
|
|
224
|
+
* success one as `SuccessResponse` gives it, each other one as `ErrorResponse` does.
|
|
214
225
|
*/
|
|
215
|
-
type GetResponseContent<T extends Record<string | number, any>, Media extends MediaType = MediaType, ResponseCode extends keyof T = keyof T> = ResponseCode extends
|
|
216
|
-
|
|
226
|
+
type GetResponseContent<T extends Record<string | number, any>, Media extends MediaType = MediaType, ResponseCode extends keyof T = keyof T> = ResponseCode extends SuccessStatus<T> ? ResponseBodies<T, Media, ResponseCode, never> : ResponseBodies<T, Media, ResponseCode, string>;
|
|
227
|
+
/**
|
|
228
|
+
* The bodies of the responses `ResponseCode`. A response without `content` keeps its `content`
|
|
229
|
+
* type (`undefined` for `content?: never`). `OtherBody` is the class's body that is not JSON: it
|
|
230
|
+
* is passed in, not read from the status, so the result for a generic map stays a type that
|
|
231
|
+
* TypeScript relates to the one of another client (ADR-030).
|
|
232
|
+
*/
|
|
233
|
+
type ResponseBodies<T extends Record<string | number, any>, Media, ResponseCode extends keyof T, OtherBody> = ResponseCode extends keyof T ? {
|
|
234
|
+
[K in ResponseCode]: T[K]["content"] extends Record<string, any> ? ResponseBody<T[K]["content"], Media, OtherBody> : K extends keyof T ? T[K]["content"] : never;
|
|
217
235
|
}[ResponseCode] : never;
|
|
218
236
|
/**
|
|
219
|
-
*
|
|
220
|
-
*
|
|
237
|
+
* The body of one response with the content map `Content`, as the client returns it. `Media`
|
|
238
|
+
* picks among its media types, as an `Accept` header does, and each one picked gives its schema.
|
|
239
|
+
* A response with none of them can still arrive (a proxy's HTML 500), so it gives what the client
|
|
240
|
+
* makes of its bodies: a JSON body gives its schema, any other body `OtherBody`, a `string` for an
|
|
241
|
+
* error (the client returns the text) and nothing for a success (the default `parseAs: "json"`
|
|
242
|
+
* rejects it; another `parseAs` types the data by itself). `Media` is read only in
|
|
243
|
+
* `keyof Content & Media`, as in `FilterKeys`, so TypeScript still relates the results of two
|
|
244
|
+
* clients with different `Media` in generic code (ADR-030).
|
|
221
245
|
*/
|
|
222
|
-
type
|
|
246
|
+
type ResponseBody<Content, Media, OtherBody> = ([keyof Content & Media] extends [never] ? JSONLike<Content> : Content[keyof Content & Media]) | ([keyof Content & Media] extends [never] ? false extends {
|
|
247
|
+
[Key in keyof Content]: IsJSONMediaType<Key>;
|
|
248
|
+
}[keyof Content] ? OtherBody : never : never);
|
|
249
|
+
/**
|
|
250
|
+
* The media types that the client parses as JSON, as a content map writes them: `json` or a
|
|
251
|
+
* `+json` suffix (RFC 6838), with or without parameters, as the client's `JSON_MEDIA_TYPE_RE`, and
|
|
252
|
+
* the wildcards for any type and any `application` type, which may hold JSON. Not `x-ndjson` or
|
|
253
|
+
* `json-seq`, whose bodies are lines of JSON.
|
|
254
|
+
*/
|
|
255
|
+
type JSONMediaType = `${string}/${"" | `${string}+`}json` | `${string}/${"" | `${string}+`}json${";" | " " | "\t"}${string}` | "*/*" | "application/*";
|
|
256
|
+
/** Whether the client parses a body of the media type `Key` as JSON, in any letter case. */
|
|
257
|
+
type IsJSONMediaType<Key> = Key extends string ? Lowercase<Key> extends JSONMediaType ? true : false : false;
|
|
223
258
|
/** Return first JSON-like 2XX response from a path + HTTP method */
|
|
224
|
-
type SuccessResponseJSON<PathMethod extends Record<string | number, any>> = SuccessResponse<ResponseObjectMap<PathMethod>,
|
|
259
|
+
type SuccessResponseJSON<PathMethod extends Record<string | number, any>> = SuccessResponse<ResponseObjectMap<PathMethod>, JSONMediaType>;
|
|
225
260
|
/** Return JSON-like 1XX, 3XX, 4XX, 5XX and `default` responses from a path + HTTP method */
|
|
226
|
-
type ErrorResponseJSON<PathMethod extends Record<string | number, any>> = ErrorResponse<ResponseObjectMap<PathMethod>,
|
|
261
|
+
type ErrorResponseJSON<PathMethod extends Record<string | number, any>> = ErrorResponse<ResponseObjectMap<PathMethod>, JSONMediaType>;
|
|
227
262
|
/** Return JSON-like request body from a path + HTTP method */
|
|
228
263
|
type RequestBodyJSON<PathMethod> = JSONLike<FilterKeys<OperationRequestBody<PathMethod>, "content">>;
|
|
229
264
|
/** Find first match of multiple keys */
|
|
@@ -240,10 +275,13 @@ type GetValueWithDefault<Obj, KeyPattern, Default> = Obj extends unknown ? Filte
|
|
|
240
275
|
*/
|
|
241
276
|
type MediaType = `${string}/${string}`;
|
|
242
277
|
/**
|
|
243
|
-
*
|
|
244
|
-
* "application/
|
|
278
|
+
* The values of the JSON media types of a content map, as the client parses them: `json` or a
|
|
279
|
+
* `+json` suffix, with or without parameters ("application/json", "application/vnd.api+json",
|
|
280
|
+
* "application/json; charset=utf-8").
|
|
245
281
|
*/
|
|
246
|
-
type JSONLike<T> =
|
|
282
|
+
type JSONLike<T> = {
|
|
283
|
+
[Key in keyof T]: IsJSONMediaType<Key> extends true ? T[Key] : never;
|
|
284
|
+
}[keyof T];
|
|
247
285
|
/**
|
|
248
286
|
* Filter objects that have required keys
|
|
249
287
|
* @deprecated Use `RequiredKeysOf` instead
|
package/dist/helpers.d.mts
CHANGED
|
@@ -199,31 +199,66 @@ type OperationRequestBodyContent<T> = FilterKeys<OperationRequestBodyMediaConten
|
|
|
199
199
|
* `default` is left to the errors, as before.
|
|
200
200
|
*/
|
|
201
201
|
type SuccessStatus<T> = [SuccessStatusKey<T>] extends [never] ? Extract<keyof T, "default"> : SuccessStatusKey<T>;
|
|
202
|
+
/**
|
|
203
|
+
* `true` for `any`: a conditional type on `any` takes both branches, so `any` is checked first.
|
|
204
|
+
* The response types check it inside `T extends unknown`: TypeScript relates a conditional type
|
|
205
|
+
* on a type parameter through the one with the parameter's constraint put in, while one on
|
|
206
|
+
* `IsAny<T>` relates through both branches, which for a generic path added the whole map.
|
|
207
|
+
*/
|
|
208
|
+
type IsAny<T> = 0 extends 1 & T ? true : false;
|
|
202
209
|
/**
|
|
203
210
|
* Return all 2XX responses from a Response Object Map, or `default` if it declares no 2XX one. For
|
|
204
211
|
* a union of maps (a call with a union of paths), the union of each map's responses: `keyof` of a
|
|
205
212
|
* union is only the shared statuses, so one map of them lost the body of every path that did not
|
|
206
|
-
* share its statuses, and a `default`-only path typed the data `undefined` (upstream).
|
|
213
|
+
* share its statuses, and a `default`-only path typed the data `undefined` (upstream). An `any`
|
|
214
|
+
* map (`createClient<any>()`) gives `any`: no key of `keyof any` is a status, which gave `never`.
|
|
215
|
+
*/
|
|
216
|
+
type SuccessResponse<T extends Record<string | number, any>, Media extends MediaType = MediaType> = T extends unknown ? IsAny<T> extends true ? T : ResponseBodies<T, Media, SuccessStatus<T>, never> : never;
|
|
217
|
+
/**
|
|
218
|
+
* Return all 1XX, 3XX, 4XX, 5XX and `default` responses from a Response Object Map; for a union
|
|
219
|
+
* of maps, the union of each map's responses, as `SuccessResponse`. An `any` map gives `any`.
|
|
207
220
|
*/
|
|
208
|
-
type
|
|
221
|
+
type ErrorResponse<T extends Record<string | number, any>, Media extends MediaType = MediaType> = T extends unknown ? IsAny<T> extends true ? T : ResponseBodies<T, Media, ErrorStatusKey<T>, string> : never;
|
|
209
222
|
/**
|
|
210
|
-
* The bodies of the responses `ResponseCode`
|
|
211
|
-
*
|
|
212
|
-
* `ErrorResponseJSON` for a `text/html` 500 was `{ "text/html": string }`, a body no response has.
|
|
213
|
-
* A response without `content` keeps its `content` type (`undefined` for `content?: never`).
|
|
223
|
+
* The bodies of the responses `ResponseCode` as the client returns them (`ResponseBody`): each
|
|
224
|
+
* success one as `SuccessResponse` gives it, each other one as `ErrorResponse` does.
|
|
214
225
|
*/
|
|
215
|
-
type GetResponseContent<T extends Record<string | number, any>, Media extends MediaType = MediaType, ResponseCode extends keyof T = keyof T> = ResponseCode extends
|
|
216
|
-
|
|
226
|
+
type GetResponseContent<T extends Record<string | number, any>, Media extends MediaType = MediaType, ResponseCode extends keyof T = keyof T> = ResponseCode extends SuccessStatus<T> ? ResponseBodies<T, Media, ResponseCode, never> : ResponseBodies<T, Media, ResponseCode, string>;
|
|
227
|
+
/**
|
|
228
|
+
* The bodies of the responses `ResponseCode`. A response without `content` keeps its `content`
|
|
229
|
+
* type (`undefined` for `content?: never`). `OtherBody` is the class's body that is not JSON: it
|
|
230
|
+
* is passed in, not read from the status, so the result for a generic map stays a type that
|
|
231
|
+
* TypeScript relates to the one of another client (ADR-030).
|
|
232
|
+
*/
|
|
233
|
+
type ResponseBodies<T extends Record<string | number, any>, Media, ResponseCode extends keyof T, OtherBody> = ResponseCode extends keyof T ? {
|
|
234
|
+
[K in ResponseCode]: T[K]["content"] extends Record<string, any> ? ResponseBody<T[K]["content"], Media, OtherBody> : K extends keyof T ? T[K]["content"] : never;
|
|
217
235
|
}[ResponseCode] : never;
|
|
218
236
|
/**
|
|
219
|
-
*
|
|
220
|
-
*
|
|
237
|
+
* The body of one response with the content map `Content`, as the client returns it. `Media`
|
|
238
|
+
* picks among its media types, as an `Accept` header does, and each one picked gives its schema.
|
|
239
|
+
* A response with none of them can still arrive (a proxy's HTML 500), so it gives what the client
|
|
240
|
+
* makes of its bodies: a JSON body gives its schema, any other body `OtherBody`, a `string` for an
|
|
241
|
+
* error (the client returns the text) and nothing for a success (the default `parseAs: "json"`
|
|
242
|
+
* rejects it; another `parseAs` types the data by itself). `Media` is read only in
|
|
243
|
+
* `keyof Content & Media`, as in `FilterKeys`, so TypeScript still relates the results of two
|
|
244
|
+
* clients with different `Media` in generic code (ADR-030).
|
|
221
245
|
*/
|
|
222
|
-
type
|
|
246
|
+
type ResponseBody<Content, Media, OtherBody> = ([keyof Content & Media] extends [never] ? JSONLike<Content> : Content[keyof Content & Media]) | ([keyof Content & Media] extends [never] ? false extends {
|
|
247
|
+
[Key in keyof Content]: IsJSONMediaType<Key>;
|
|
248
|
+
}[keyof Content] ? OtherBody : never : never);
|
|
249
|
+
/**
|
|
250
|
+
* The media types that the client parses as JSON, as a content map writes them: `json` or a
|
|
251
|
+
* `+json` suffix (RFC 6838), with or without parameters, as the client's `JSON_MEDIA_TYPE_RE`, and
|
|
252
|
+
* the wildcards for any type and any `application` type, which may hold JSON. Not `x-ndjson` or
|
|
253
|
+
* `json-seq`, whose bodies are lines of JSON.
|
|
254
|
+
*/
|
|
255
|
+
type JSONMediaType = `${string}/${"" | `${string}+`}json` | `${string}/${"" | `${string}+`}json${";" | " " | "\t"}${string}` | "*/*" | "application/*";
|
|
256
|
+
/** Whether the client parses a body of the media type `Key` as JSON, in any letter case. */
|
|
257
|
+
type IsJSONMediaType<Key> = Key extends string ? Lowercase<Key> extends JSONMediaType ? true : false : false;
|
|
223
258
|
/** Return first JSON-like 2XX response from a path + HTTP method */
|
|
224
|
-
type SuccessResponseJSON<PathMethod extends Record<string | number, any>> = SuccessResponse<ResponseObjectMap<PathMethod>,
|
|
259
|
+
type SuccessResponseJSON<PathMethod extends Record<string | number, any>> = SuccessResponse<ResponseObjectMap<PathMethod>, JSONMediaType>;
|
|
225
260
|
/** Return JSON-like 1XX, 3XX, 4XX, 5XX and `default` responses from a path + HTTP method */
|
|
226
|
-
type ErrorResponseJSON<PathMethod extends Record<string | number, any>> = ErrorResponse<ResponseObjectMap<PathMethod>,
|
|
261
|
+
type ErrorResponseJSON<PathMethod extends Record<string | number, any>> = ErrorResponse<ResponseObjectMap<PathMethod>, JSONMediaType>;
|
|
227
262
|
/** Return JSON-like request body from a path + HTTP method */
|
|
228
263
|
type RequestBodyJSON<PathMethod> = JSONLike<FilterKeys<OperationRequestBody<PathMethod>, "content">>;
|
|
229
264
|
/** Find first match of multiple keys */
|
|
@@ -240,10 +275,13 @@ type GetValueWithDefault<Obj, KeyPattern, Default> = Obj extends unknown ? Filte
|
|
|
240
275
|
*/
|
|
241
276
|
type MediaType = `${string}/${string}`;
|
|
242
277
|
/**
|
|
243
|
-
*
|
|
244
|
-
* "application/
|
|
278
|
+
* The values of the JSON media types of a content map, as the client parses them: `json` or a
|
|
279
|
+
* `+json` suffix, with or without parameters ("application/json", "application/vnd.api+json",
|
|
280
|
+
* "application/json; charset=utf-8").
|
|
245
281
|
*/
|
|
246
|
-
type JSONLike<T> =
|
|
282
|
+
type JSONLike<T> = {
|
|
283
|
+
[Key in keyof T]: IsJSONMediaType<Key> extends true ? T[Key] : never;
|
|
284
|
+
}[keyof T];
|
|
247
285
|
/**
|
|
248
286
|
* Filter objects that have required keys
|
|
249
287
|
* @deprecated Use `RequiredKeysOf` instead
|
package/dist/helpers.d.ts
CHANGED
|
@@ -199,31 +199,66 @@ type OperationRequestBodyContent<T> = FilterKeys<OperationRequestBodyMediaConten
|
|
|
199
199
|
* `default` is left to the errors, as before.
|
|
200
200
|
*/
|
|
201
201
|
type SuccessStatus<T> = [SuccessStatusKey<T>] extends [never] ? Extract<keyof T, "default"> : SuccessStatusKey<T>;
|
|
202
|
+
/**
|
|
203
|
+
* `true` for `any`: a conditional type on `any` takes both branches, so `any` is checked first.
|
|
204
|
+
* The response types check it inside `T extends unknown`: TypeScript relates a conditional type
|
|
205
|
+
* on a type parameter through the one with the parameter's constraint put in, while one on
|
|
206
|
+
* `IsAny<T>` relates through both branches, which for a generic path added the whole map.
|
|
207
|
+
*/
|
|
208
|
+
type IsAny<T> = 0 extends 1 & T ? true : false;
|
|
202
209
|
/**
|
|
203
210
|
* Return all 2XX responses from a Response Object Map, or `default` if it declares no 2XX one. For
|
|
204
211
|
* a union of maps (a call with a union of paths), the union of each map's responses: `keyof` of a
|
|
205
212
|
* union is only the shared statuses, so one map of them lost the body of every path that did not
|
|
206
|
-
* share its statuses, and a `default`-only path typed the data `undefined` (upstream).
|
|
213
|
+
* share its statuses, and a `default`-only path typed the data `undefined` (upstream). An `any`
|
|
214
|
+
* map (`createClient<any>()`) gives `any`: no key of `keyof any` is a status, which gave `never`.
|
|
215
|
+
*/
|
|
216
|
+
type SuccessResponse<T extends Record<string | number, any>, Media extends MediaType = MediaType> = T extends unknown ? IsAny<T> extends true ? T : ResponseBodies<T, Media, SuccessStatus<T>, never> : never;
|
|
217
|
+
/**
|
|
218
|
+
* Return all 1XX, 3XX, 4XX, 5XX and `default` responses from a Response Object Map; for a union
|
|
219
|
+
* of maps, the union of each map's responses, as `SuccessResponse`. An `any` map gives `any`.
|
|
207
220
|
*/
|
|
208
|
-
type
|
|
221
|
+
type ErrorResponse<T extends Record<string | number, any>, Media extends MediaType = MediaType> = T extends unknown ? IsAny<T> extends true ? T : ResponseBodies<T, Media, ErrorStatusKey<T>, string> : never;
|
|
209
222
|
/**
|
|
210
|
-
* The bodies of the responses `ResponseCode`
|
|
211
|
-
*
|
|
212
|
-
* `ErrorResponseJSON` for a `text/html` 500 was `{ "text/html": string }`, a body no response has.
|
|
213
|
-
* A response without `content` keeps its `content` type (`undefined` for `content?: never`).
|
|
223
|
+
* The bodies of the responses `ResponseCode` as the client returns them (`ResponseBody`): each
|
|
224
|
+
* success one as `SuccessResponse` gives it, each other one as `ErrorResponse` does.
|
|
214
225
|
*/
|
|
215
|
-
type GetResponseContent<T extends Record<string | number, any>, Media extends MediaType = MediaType, ResponseCode extends keyof T = keyof T> = ResponseCode extends
|
|
216
|
-
|
|
226
|
+
type GetResponseContent<T extends Record<string | number, any>, Media extends MediaType = MediaType, ResponseCode extends keyof T = keyof T> = ResponseCode extends SuccessStatus<T> ? ResponseBodies<T, Media, ResponseCode, never> : ResponseBodies<T, Media, ResponseCode, string>;
|
|
227
|
+
/**
|
|
228
|
+
* The bodies of the responses `ResponseCode`. A response without `content` keeps its `content`
|
|
229
|
+
* type (`undefined` for `content?: never`). `OtherBody` is the class's body that is not JSON: it
|
|
230
|
+
* is passed in, not read from the status, so the result for a generic map stays a type that
|
|
231
|
+
* TypeScript relates to the one of another client (ADR-030).
|
|
232
|
+
*/
|
|
233
|
+
type ResponseBodies<T extends Record<string | number, any>, Media, ResponseCode extends keyof T, OtherBody> = ResponseCode extends keyof T ? {
|
|
234
|
+
[K in ResponseCode]: T[K]["content"] extends Record<string, any> ? ResponseBody<T[K]["content"], Media, OtherBody> : K extends keyof T ? T[K]["content"] : never;
|
|
217
235
|
}[ResponseCode] : never;
|
|
218
236
|
/**
|
|
219
|
-
*
|
|
220
|
-
*
|
|
237
|
+
* The body of one response with the content map `Content`, as the client returns it. `Media`
|
|
238
|
+
* picks among its media types, as an `Accept` header does, and each one picked gives its schema.
|
|
239
|
+
* A response with none of them can still arrive (a proxy's HTML 500), so it gives what the client
|
|
240
|
+
* makes of its bodies: a JSON body gives its schema, any other body `OtherBody`, a `string` for an
|
|
241
|
+
* error (the client returns the text) and nothing for a success (the default `parseAs: "json"`
|
|
242
|
+
* rejects it; another `parseAs` types the data by itself). `Media` is read only in
|
|
243
|
+
* `keyof Content & Media`, as in `FilterKeys`, so TypeScript still relates the results of two
|
|
244
|
+
* clients with different `Media` in generic code (ADR-030).
|
|
221
245
|
*/
|
|
222
|
-
type
|
|
246
|
+
type ResponseBody<Content, Media, OtherBody> = ([keyof Content & Media] extends [never] ? JSONLike<Content> : Content[keyof Content & Media]) | ([keyof Content & Media] extends [never] ? false extends {
|
|
247
|
+
[Key in keyof Content]: IsJSONMediaType<Key>;
|
|
248
|
+
}[keyof Content] ? OtherBody : never : never);
|
|
249
|
+
/**
|
|
250
|
+
* The media types that the client parses as JSON, as a content map writes them: `json` or a
|
|
251
|
+
* `+json` suffix (RFC 6838), with or without parameters, as the client's `JSON_MEDIA_TYPE_RE`, and
|
|
252
|
+
* the wildcards for any type and any `application` type, which may hold JSON. Not `x-ndjson` or
|
|
253
|
+
* `json-seq`, whose bodies are lines of JSON.
|
|
254
|
+
*/
|
|
255
|
+
type JSONMediaType = `${string}/${"" | `${string}+`}json` | `${string}/${"" | `${string}+`}json${";" | " " | "\t"}${string}` | "*/*" | "application/*";
|
|
256
|
+
/** Whether the client parses a body of the media type `Key` as JSON, in any letter case. */
|
|
257
|
+
type IsJSONMediaType<Key> = Key extends string ? Lowercase<Key> extends JSONMediaType ? true : false : false;
|
|
223
258
|
/** Return first JSON-like 2XX response from a path + HTTP method */
|
|
224
|
-
type SuccessResponseJSON<PathMethod extends Record<string | number, any>> = SuccessResponse<ResponseObjectMap<PathMethod>,
|
|
259
|
+
type SuccessResponseJSON<PathMethod extends Record<string | number, any>> = SuccessResponse<ResponseObjectMap<PathMethod>, JSONMediaType>;
|
|
225
260
|
/** Return JSON-like 1XX, 3XX, 4XX, 5XX and `default` responses from a path + HTTP method */
|
|
226
|
-
type ErrorResponseJSON<PathMethod extends Record<string | number, any>> = ErrorResponse<ResponseObjectMap<PathMethod>,
|
|
261
|
+
type ErrorResponseJSON<PathMethod extends Record<string | number, any>> = ErrorResponse<ResponseObjectMap<PathMethod>, JSONMediaType>;
|
|
227
262
|
/** Return JSON-like request body from a path + HTTP method */
|
|
228
263
|
type RequestBodyJSON<PathMethod> = JSONLike<FilterKeys<OperationRequestBody<PathMethod>, "content">>;
|
|
229
264
|
/** Find first match of multiple keys */
|
|
@@ -240,10 +275,13 @@ type GetValueWithDefault<Obj, KeyPattern, Default> = Obj extends unknown ? Filte
|
|
|
240
275
|
*/
|
|
241
276
|
type MediaType = `${string}/${string}`;
|
|
242
277
|
/**
|
|
243
|
-
*
|
|
244
|
-
* "application/
|
|
278
|
+
* The values of the JSON media types of a content map, as the client parses them: `json` or a
|
|
279
|
+
* `+json` suffix, with or without parameters ("application/json", "application/vnd.api+json",
|
|
280
|
+
* "application/json; charset=utf-8").
|
|
245
281
|
*/
|
|
246
|
-
type JSONLike<T> =
|
|
282
|
+
type JSONLike<T> = {
|
|
283
|
+
[Key in keyof T]: IsJSONMediaType<Key> extends true ? T[Key] : never;
|
|
284
|
+
}[keyof T];
|
|
247
285
|
/**
|
|
248
286
|
* Filter objects that have required keys
|
|
249
287
|
* @deprecated Use `RequiredKeysOf` instead
|
package/dist/index.cjs
CHANGED
|
@@ -2,6 +2,7 @@
|
|
|
2
2
|
|
|
3
3
|
Object.defineProperty(exports, '__esModule', { value: true });
|
|
4
4
|
|
|
5
|
+
const CONTENT_TYPE = "Content-Type";
|
|
5
6
|
const PATH_PARAM_RE = /\{[^{}]+\}/g;
|
|
6
7
|
const DOT_SEGMENT_RE = /^\.\.?$/;
|
|
7
8
|
const QUERY_OR_FRAGMENT_RE = /[?#]/;
|
|
@@ -83,7 +84,7 @@ function createClient(clientOptions) {
|
|
|
83
84
|
// with no body, we should not to set Content-Type
|
|
84
85
|
requestBody === void 0 || // a raw body (FormData, Blob, ...) gets its Content-Type from the platform, or none
|
|
85
86
|
isRawBody(requestBody) ? {} : {
|
|
86
|
-
|
|
87
|
+
[CONTENT_TYPE]: "application/json"
|
|
87
88
|
},
|
|
88
89
|
baseHeaders,
|
|
89
90
|
headers,
|
|
@@ -342,8 +343,8 @@ function createPathBasedClient(clientOptions) {
|
|
|
342
343
|
return wrapAsPathBasedClient(createClient(clientOptions));
|
|
343
344
|
}
|
|
344
345
|
function dropFormContentType(headers, isClientBuilt) {
|
|
345
|
-
if (isClientBuilt || !BOUNDARY_PARAMETER_RE.test(headers.get(
|
|
346
|
-
headers.delete(
|
|
346
|
+
if (isClientBuilt || !BOUNDARY_PARAMETER_RE.test(headers.get(CONTENT_TYPE))) {
|
|
347
|
+
headers.delete(CONTENT_TYPE);
|
|
347
348
|
}
|
|
348
349
|
}
|
|
349
350
|
function isHeaders(value) {
|
|
@@ -359,7 +360,7 @@ function parseJSON(raw, response) {
|
|
|
359
360
|
try {
|
|
360
361
|
return JSON.parse(raw);
|
|
361
362
|
} catch {
|
|
362
|
-
const contentType = response.headers.get(
|
|
363
|
+
const contentType = response.headers.get(CONTENT_TYPE);
|
|
363
364
|
const hint = TEXT_MEDIA_TYPE_RE.test(contentType) ? 'set `parseAs: "text"`' : "set `parseAs`";
|
|
364
365
|
const subject = `Response ${response.status} (${contentType ?? "no Content-Type"})`;
|
|
365
366
|
throw new SyntaxError(`${subject} is not JSON (${raw.length} characters); ${hint}`);
|
|
@@ -394,6 +395,10 @@ async function readErrorBody(response) {
|
|
|
394
395
|
if (!raw) {
|
|
395
396
|
return void 0;
|
|
396
397
|
}
|
|
398
|
+
const contentType = response.headers.get(CONTENT_TYPE);
|
|
399
|
+
if (contentType !== null && !JSON_MEDIA_TYPE_RE.test(contentType)) {
|
|
400
|
+
return raw;
|
|
401
|
+
}
|
|
397
402
|
try {
|
|
398
403
|
return JSON.parse(raw);
|
|
399
404
|
} catch {
|
|
@@ -440,6 +445,13 @@ function checkFlatMembers(value, message) {
|
|
|
440
445
|
throw new Error(message);
|
|
441
446
|
}
|
|
442
447
|
}
|
|
448
|
+
const PATH_STYLE_JOINERS = { simple: ",", label: ".", matrix: ";" };
|
|
449
|
+
function checkFlatParam(value, style) {
|
|
450
|
+
checkFlatMembers(
|
|
451
|
+
value,
|
|
452
|
+
PATH_STYLE_JOINERS[style] ? PATH_NESTED_VALUE_ERROR : QUERY_NESTED_VALUE_ERROR
|
|
453
|
+
);
|
|
454
|
+
}
|
|
443
455
|
function encodeValue(value, allowReserved) {
|
|
444
456
|
if (isNestedValue(value)) {
|
|
445
457
|
throw new Error(QUERY_NESTED_VALUE_ERROR);
|
|
@@ -454,7 +466,7 @@ function serializePrimitiveParam(name, value, options) {
|
|
|
454
466
|
return `${name}=${encodeValue(value, options?.allowReserved)}`;
|
|
455
467
|
}
|
|
456
468
|
function joinExploded(values, style) {
|
|
457
|
-
const joiner =
|
|
469
|
+
const joiner = PATH_STYLE_JOINERS[style] || "&";
|
|
458
470
|
const final = values.join(joiner);
|
|
459
471
|
return style === "label" || style === "matrix" ? `${joiner}${final}` : final;
|
|
460
472
|
}
|
|
@@ -491,6 +503,7 @@ function serializeObjectParam(name, value, options) {
|
|
|
491
503
|
if (!value || typeof value !== "object" || !Object.keys(value).length) {
|
|
492
504
|
return "";
|
|
493
505
|
}
|
|
506
|
+
checkFlatParam(value, options.style);
|
|
494
507
|
if (options.style !== "deepObject" && options.explode === false) {
|
|
495
508
|
return serializeUnexplodedObject(name, value, options);
|
|
496
509
|
}
|
|
@@ -530,6 +543,7 @@ function serializeArrayParam(name, value, options) {
|
|
|
530
543
|
if (!Array.isArray(value) || !value.length) {
|
|
531
544
|
return "";
|
|
532
545
|
}
|
|
546
|
+
checkFlatParam(value, options.style);
|
|
533
547
|
if (options.explode === false) {
|
|
534
548
|
return serializeUnexplodedArray(name, value, options);
|
|
535
549
|
}
|
|
@@ -620,11 +634,9 @@ function defaultPathSerializer(pathname, pathParams) {
|
|
|
620
634
|
}
|
|
621
635
|
function serializePathParam(name, value, options) {
|
|
622
636
|
if (Array.isArray(value)) {
|
|
623
|
-
checkFlatMembers(value, PATH_NESTED_VALUE_ERROR);
|
|
624
637
|
return serializeArrayParam(name, value, options);
|
|
625
638
|
}
|
|
626
639
|
if (typeof value === "object" && !isDate(value)) {
|
|
627
|
-
checkFlatMembers(value, PATH_NESTED_VALUE_ERROR);
|
|
628
640
|
return serializeObjectParam(name, value, options);
|
|
629
641
|
}
|
|
630
642
|
if (options.style === "matrix") {
|
|
@@ -724,7 +736,7 @@ function defaultBodySerializer(body, headers) {
|
|
|
724
736
|
if (isRawBody(body)) {
|
|
725
737
|
return body;
|
|
726
738
|
}
|
|
727
|
-
const contentType = headers && (isHeaders(headers) ? headers : mergeHeaders(headers)).get(
|
|
739
|
+
const contentType = headers && (isHeaders(headers) ? headers : mergeHeaders(headers)).get(CONTENT_TYPE);
|
|
728
740
|
if (FORM_MEDIA_TYPE_RE.test(contentType)) {
|
|
729
741
|
return formBody(body);
|
|
730
742
|
}
|