openapi-typesafe-fetch 1.0.0-rc.6 → 1.0.0-rc.8
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 +40 -0
- package/dist/helpers.d.cts +51 -12
- package/dist/helpers.d.mts +51 -12
- package/dist/helpers.d.ts +51 -12
- package/dist/index.cjs +42 -21
- package/dist/index.cjs.map +1 -1
- package/dist/index.d.cts +22 -14
- package/dist/index.d.mts +22 -14
- package/dist/index.d.ts +22 -14
- package/dist/index.mjs +42 -21
- package/dist/index.mjs.map +1 -1
- package/package.json +2 -2
- package/src/helpers.ts +90 -17
- package/src/index.d.ts +22 -14
- package/src/index.js +69 -23
package/CHANGELOG.md
CHANGED
|
@@ -2,6 +2,46 @@
|
|
|
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.8
|
|
6
|
+
|
|
7
|
+
### Patch Changes
|
|
8
|
+
|
|
9
|
+
- [#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
|
|
10
|
+
`if (error)` made the whole result `never`. `SuccessResponse<any>` and `ErrorResponse<any>` are
|
|
11
|
+
`any` too.
|
|
12
|
+
|
|
13
|
+
- [#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
|
|
14
|
+
`data` and `error` of a client made with ``createClient<paths, `${string}/json`>()`` keep a
|
|
15
|
+
JSON body of any JSON media type: an `application/problem+json` error, an
|
|
16
|
+
`application/vnd.api+json` success and `application/json; charset=utf-8` bodies were left out,
|
|
17
|
+
so the error could be `never` or miss a status. An error that is not JSON, such as the HTML page
|
|
18
|
+
of a proxy, is typed `string`, the text the client returns, instead of being left out.
|
|
19
|
+
|
|
20
|
+
- [#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
|
|
21
|
+
optional option on (`{ array: { explode: options.explode } }`) compiles with
|
|
22
|
+
`exactOptionalPropertyTypes`.
|
|
23
|
+
|
|
24
|
+
- [#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`,
|
|
25
|
+
`label`, `matrix`) name `pathSerializer()` in their error, also inside your own `pathSerializer`,
|
|
26
|
+
instead of `querySerializer()`.
|
|
27
|
+
|
|
28
|
+
## 1.0.0-rc.7
|
|
29
|
+
|
|
30
|
+
### Patch Changes
|
|
31
|
+
|
|
32
|
+
- [#96](https://github.com/openapi-typesafe/openapi-typesafe/pull/96) [`171fa71`](https://github.com/openapi-typesafe/openapi-typesafe/commit/171fa71311107e4e26a9ae7a62b431662fb13de5) Thanks [@fedorello](https://github.com/fedorello)! - `SuccessResponseJSON`, `ErrorResponseJSON` and the `data` and `error` of a client made with
|
|
33
|
+
``createClient<paths, `${string}/json`>()`` leave out a response that has no JSON media type. It
|
|
34
|
+
added its whole `content` map, so a `text/html` 500 typed the error as `{ "text/html": string }`,
|
|
35
|
+
a value the client never returns.
|
|
36
|
+
|
|
37
|
+
- [#96](https://github.com/openapi-typesafe/openapi-typesafe/pull/96) [`171fa71`](https://github.com/openapi-typesafe/openapi-typesafe/commit/171fa71311107e4e26a9ae7a62b431662fb13de5) Thanks [@fedorello](https://github.com/fedorello)! - A nested array or object in a path parameter (`{ id: [[1]] }`) now fails with an error that names
|
|
38
|
+
`pathSerializer()`, the option that sends such values. It told you to write a `querySerializer()`,
|
|
39
|
+
which does not apply to the path.
|
|
40
|
+
|
|
41
|
+
- [#96](https://github.com/openapi-typesafe/openapi-typesafe/pull/96) [`171fa71`](https://github.com/openapi-typesafe/openapi-typesafe/commit/171fa71311107e4e26a9ae7a62b431662fb13de5) Thanks [@fedorello](https://github.com/fedorello)! - A request's `querySerializer` option set to `undefined` (`{ array: { explode: undefined } }`, as a
|
|
42
|
+
wrapper passes its own optional option on) now takes the client's value, as an option left out
|
|
43
|
+
does. It replaced the client's value, so the default style was sent.
|
|
44
|
+
|
|
5
45
|
## 1.0.0-rc.6
|
|
6
46
|
|
|
7
47
|
### Patch Changes
|
package/dist/helpers.d.cts
CHANGED
|
@@ -199,25 +199,61 @@ 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
|
+
/** `true` for `any`: a conditional type on `any` takes both branches, so `any` is checked first. */
|
|
203
|
+
type IsAny<T> = 0 extends 1 & T ? true : false;
|
|
202
204
|
/**
|
|
203
205
|
* Return all 2XX responses from a Response Object Map, or `default` if it declares no 2XX one. For
|
|
204
206
|
* a union of maps (a call with a union of paths), the union of each map's responses: `keyof` of a
|
|
205
207
|
* 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).
|
|
208
|
+
* share its statuses, and a `default`-only path typed the data `undefined` (upstream). An `any`
|
|
209
|
+
* map (`createClient<any>()`) gives `any`: no key of `keyof any` is a status, which gave `never`.
|
|
207
210
|
*/
|
|
208
|
-
type SuccessResponse<T extends Record<string | number, any>, Media extends MediaType = MediaType> = T extends unknown ?
|
|
209
|
-
type GetResponseContent<T extends Record<string | number, any>, Media extends MediaType = MediaType, ResponseCode extends keyof T = keyof T> = ResponseCode extends keyof T ? {
|
|
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;
|
|
211
|
-
}[ResponseCode] : never;
|
|
211
|
+
type SuccessResponse<T extends Record<string | number, any>, Media extends MediaType = MediaType> = IsAny<T> extends true ? T : T extends unknown ? ResponseBodies<T, Media, SuccessStatus<T>, never> : never;
|
|
212
212
|
/**
|
|
213
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`.
|
|
214
|
+
* of maps, the union of each map's responses, as `SuccessResponse`. An `any` map gives `any`.
|
|
215
|
+
*/
|
|
216
|
+
type ErrorResponse<T extends Record<string | number, any>, Media extends MediaType = MediaType> = IsAny<T> extends true ? T : T extends unknown ? ResponseBodies<T, Media, ErrorStatusKey<T>, string> : never;
|
|
217
|
+
/**
|
|
218
|
+
* The bodies of the responses `ResponseCode` as the client returns them (`ResponseBody`): each
|
|
219
|
+
* success one as `SuccessResponse` gives it, each other one as `ErrorResponse` does.
|
|
215
220
|
*/
|
|
216
|
-
type
|
|
221
|
+
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>;
|
|
222
|
+
/**
|
|
223
|
+
* The bodies of the responses `ResponseCode`. A response without `content` keeps its `content`
|
|
224
|
+
* type (`undefined` for `content?: never`). `OtherBody` is the class's body that is not JSON: it
|
|
225
|
+
* is passed in, not read from the status, so the result for a generic map stays a type that
|
|
226
|
+
* TypeScript relates to the one of another client (ADR-030).
|
|
227
|
+
*/
|
|
228
|
+
type ResponseBodies<T extends Record<string | number, any>, Media, ResponseCode extends keyof T, OtherBody> = ResponseCode extends keyof T ? {
|
|
229
|
+
[K in ResponseCode]: T[K]["content"] extends Record<string, any> ? ResponseBody<T[K]["content"], Media, OtherBody> : K extends keyof T ? T[K]["content"] : never;
|
|
230
|
+
}[ResponseCode] : never;
|
|
231
|
+
/**
|
|
232
|
+
* The body of one response with the content map `Content`, as the client returns it. `Media`
|
|
233
|
+
* picks among its media types, as an `Accept` header does, and each one picked gives its schema.
|
|
234
|
+
* A response with none of them can still arrive (a proxy's HTML 500), so it gives what the client
|
|
235
|
+
* makes of its bodies: a JSON body gives its schema, any other body `OtherBody`, a `string` for an
|
|
236
|
+
* error (the client returns the text) and nothing for a success (the default `parseAs: "json"`
|
|
237
|
+
* rejects it; another `parseAs` types the data by itself). `Media` is read only in
|
|
238
|
+
* `keyof Content & Media`, as in `FilterKeys`, so TypeScript still relates the results of two
|
|
239
|
+
* clients with different `Media` in generic code (ADR-030).
|
|
240
|
+
*/
|
|
241
|
+
type ResponseBody<Content, Media, OtherBody> = ([keyof Content & Media] extends [never] ? JSONLike<Content> : Content[keyof Content & Media]) | ([keyof Content & Media] extends [never] ? false extends {
|
|
242
|
+
[Key in keyof Content]: IsJSONMediaType<Key>;
|
|
243
|
+
}[keyof Content] ? OtherBody : never : never);
|
|
244
|
+
/**
|
|
245
|
+
* The media types that the client parses as JSON, as a content map writes them: `json` or a
|
|
246
|
+
* `+json` suffix (RFC 6838), with or without parameters, as the client's `JSON_MEDIA_TYPE_RE`, and
|
|
247
|
+
* the wildcards for any type and any `application` type, which may hold JSON. Not `x-ndjson` or
|
|
248
|
+
* `json-seq`, whose bodies are lines of JSON.
|
|
249
|
+
*/
|
|
250
|
+
type JSONMediaType = `${string}/${"" | `${string}+`}json` | `${string}/${"" | `${string}+`}json${";" | " " | "\t"}${string}` | "*/*" | "application/*";
|
|
251
|
+
/** Whether the client parses a body of the media type `Key` as JSON, in any letter case. */
|
|
252
|
+
type IsJSONMediaType<Key> = Key extends string ? Lowercase<Key> extends JSONMediaType ? true : false : false;
|
|
217
253
|
/** Return first JSON-like 2XX response from a path + HTTP method */
|
|
218
|
-
type SuccessResponseJSON<PathMethod extends Record<string | number, any>> = SuccessResponse<ResponseObjectMap<PathMethod>,
|
|
254
|
+
type SuccessResponseJSON<PathMethod extends Record<string | number, any>> = SuccessResponse<ResponseObjectMap<PathMethod>, JSONMediaType>;
|
|
219
255
|
/** Return JSON-like 1XX, 3XX, 4XX, 5XX and `default` responses from a path + HTTP method */
|
|
220
|
-
type ErrorResponseJSON<PathMethod extends Record<string | number, any>> = ErrorResponse<ResponseObjectMap<PathMethod>,
|
|
256
|
+
type ErrorResponseJSON<PathMethod extends Record<string | number, any>> = ErrorResponse<ResponseObjectMap<PathMethod>, JSONMediaType>;
|
|
221
257
|
/** Return JSON-like request body from a path + HTTP method */
|
|
222
258
|
type RequestBodyJSON<PathMethod> = JSONLike<FilterKeys<OperationRequestBody<PathMethod>, "content">>;
|
|
223
259
|
/** Find first match of multiple keys */
|
|
@@ -234,10 +270,13 @@ type GetValueWithDefault<Obj, KeyPattern, Default> = Obj extends unknown ? Filte
|
|
|
234
270
|
*/
|
|
235
271
|
type MediaType = `${string}/${string}`;
|
|
236
272
|
/**
|
|
237
|
-
*
|
|
238
|
-
* "application/
|
|
273
|
+
* The values of the JSON media types of a content map, as the client parses them: `json` or a
|
|
274
|
+
* `+json` suffix, with or without parameters ("application/json", "application/vnd.api+json",
|
|
275
|
+
* "application/json; charset=utf-8").
|
|
239
276
|
*/
|
|
240
|
-
type JSONLike<T> =
|
|
277
|
+
type JSONLike<T> = {
|
|
278
|
+
[Key in keyof T]: IsJSONMediaType<Key> extends true ? T[Key] : never;
|
|
279
|
+
}[keyof T];
|
|
241
280
|
/**
|
|
242
281
|
* Filter objects that have required keys
|
|
243
282
|
* @deprecated Use `RequiredKeysOf` instead
|
package/dist/helpers.d.mts
CHANGED
|
@@ -199,25 +199,61 @@ 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
|
+
/** `true` for `any`: a conditional type on `any` takes both branches, so `any` is checked first. */
|
|
203
|
+
type IsAny<T> = 0 extends 1 & T ? true : false;
|
|
202
204
|
/**
|
|
203
205
|
* Return all 2XX responses from a Response Object Map, or `default` if it declares no 2XX one. For
|
|
204
206
|
* a union of maps (a call with a union of paths), the union of each map's responses: `keyof` of a
|
|
205
207
|
* 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).
|
|
208
|
+
* share its statuses, and a `default`-only path typed the data `undefined` (upstream). An `any`
|
|
209
|
+
* map (`createClient<any>()`) gives `any`: no key of `keyof any` is a status, which gave `never`.
|
|
207
210
|
*/
|
|
208
|
-
type SuccessResponse<T extends Record<string | number, any>, Media extends MediaType = MediaType> = T extends unknown ?
|
|
209
|
-
type GetResponseContent<T extends Record<string | number, any>, Media extends MediaType = MediaType, ResponseCode extends keyof T = keyof T> = ResponseCode extends keyof T ? {
|
|
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;
|
|
211
|
-
}[ResponseCode] : never;
|
|
211
|
+
type SuccessResponse<T extends Record<string | number, any>, Media extends MediaType = MediaType> = IsAny<T> extends true ? T : T extends unknown ? ResponseBodies<T, Media, SuccessStatus<T>, never> : never;
|
|
212
212
|
/**
|
|
213
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`.
|
|
214
|
+
* of maps, the union of each map's responses, as `SuccessResponse`. An `any` map gives `any`.
|
|
215
|
+
*/
|
|
216
|
+
type ErrorResponse<T extends Record<string | number, any>, Media extends MediaType = MediaType> = IsAny<T> extends true ? T : T extends unknown ? ResponseBodies<T, Media, ErrorStatusKey<T>, string> : never;
|
|
217
|
+
/**
|
|
218
|
+
* The bodies of the responses `ResponseCode` as the client returns them (`ResponseBody`): each
|
|
219
|
+
* success one as `SuccessResponse` gives it, each other one as `ErrorResponse` does.
|
|
215
220
|
*/
|
|
216
|
-
type
|
|
221
|
+
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>;
|
|
222
|
+
/**
|
|
223
|
+
* The bodies of the responses `ResponseCode`. A response without `content` keeps its `content`
|
|
224
|
+
* type (`undefined` for `content?: never`). `OtherBody` is the class's body that is not JSON: it
|
|
225
|
+
* is passed in, not read from the status, so the result for a generic map stays a type that
|
|
226
|
+
* TypeScript relates to the one of another client (ADR-030).
|
|
227
|
+
*/
|
|
228
|
+
type ResponseBodies<T extends Record<string | number, any>, Media, ResponseCode extends keyof T, OtherBody> = ResponseCode extends keyof T ? {
|
|
229
|
+
[K in ResponseCode]: T[K]["content"] extends Record<string, any> ? ResponseBody<T[K]["content"], Media, OtherBody> : K extends keyof T ? T[K]["content"] : never;
|
|
230
|
+
}[ResponseCode] : never;
|
|
231
|
+
/**
|
|
232
|
+
* The body of one response with the content map `Content`, as the client returns it. `Media`
|
|
233
|
+
* picks among its media types, as an `Accept` header does, and each one picked gives its schema.
|
|
234
|
+
* A response with none of them can still arrive (a proxy's HTML 500), so it gives what the client
|
|
235
|
+
* makes of its bodies: a JSON body gives its schema, any other body `OtherBody`, a `string` for an
|
|
236
|
+
* error (the client returns the text) and nothing for a success (the default `parseAs: "json"`
|
|
237
|
+
* rejects it; another `parseAs` types the data by itself). `Media` is read only in
|
|
238
|
+
* `keyof Content & Media`, as in `FilterKeys`, so TypeScript still relates the results of two
|
|
239
|
+
* clients with different `Media` in generic code (ADR-030).
|
|
240
|
+
*/
|
|
241
|
+
type ResponseBody<Content, Media, OtherBody> = ([keyof Content & Media] extends [never] ? JSONLike<Content> : Content[keyof Content & Media]) | ([keyof Content & Media] extends [never] ? false extends {
|
|
242
|
+
[Key in keyof Content]: IsJSONMediaType<Key>;
|
|
243
|
+
}[keyof Content] ? OtherBody : never : never);
|
|
244
|
+
/**
|
|
245
|
+
* The media types that the client parses as JSON, as a content map writes them: `json` or a
|
|
246
|
+
* `+json` suffix (RFC 6838), with or without parameters, as the client's `JSON_MEDIA_TYPE_RE`, and
|
|
247
|
+
* the wildcards for any type and any `application` type, which may hold JSON. Not `x-ndjson` or
|
|
248
|
+
* `json-seq`, whose bodies are lines of JSON.
|
|
249
|
+
*/
|
|
250
|
+
type JSONMediaType = `${string}/${"" | `${string}+`}json` | `${string}/${"" | `${string}+`}json${";" | " " | "\t"}${string}` | "*/*" | "application/*";
|
|
251
|
+
/** Whether the client parses a body of the media type `Key` as JSON, in any letter case. */
|
|
252
|
+
type IsJSONMediaType<Key> = Key extends string ? Lowercase<Key> extends JSONMediaType ? true : false : false;
|
|
217
253
|
/** Return first JSON-like 2XX response from a path + HTTP method */
|
|
218
|
-
type SuccessResponseJSON<PathMethod extends Record<string | number, any>> = SuccessResponse<ResponseObjectMap<PathMethod>,
|
|
254
|
+
type SuccessResponseJSON<PathMethod extends Record<string | number, any>> = SuccessResponse<ResponseObjectMap<PathMethod>, JSONMediaType>;
|
|
219
255
|
/** Return JSON-like 1XX, 3XX, 4XX, 5XX and `default` responses from a path + HTTP method */
|
|
220
|
-
type ErrorResponseJSON<PathMethod extends Record<string | number, any>> = ErrorResponse<ResponseObjectMap<PathMethod>,
|
|
256
|
+
type ErrorResponseJSON<PathMethod extends Record<string | number, any>> = ErrorResponse<ResponseObjectMap<PathMethod>, JSONMediaType>;
|
|
221
257
|
/** Return JSON-like request body from a path + HTTP method */
|
|
222
258
|
type RequestBodyJSON<PathMethod> = JSONLike<FilterKeys<OperationRequestBody<PathMethod>, "content">>;
|
|
223
259
|
/** Find first match of multiple keys */
|
|
@@ -234,10 +270,13 @@ type GetValueWithDefault<Obj, KeyPattern, Default> = Obj extends unknown ? Filte
|
|
|
234
270
|
*/
|
|
235
271
|
type MediaType = `${string}/${string}`;
|
|
236
272
|
/**
|
|
237
|
-
*
|
|
238
|
-
* "application/
|
|
273
|
+
* The values of the JSON media types of a content map, as the client parses them: `json` or a
|
|
274
|
+
* `+json` suffix, with or without parameters ("application/json", "application/vnd.api+json",
|
|
275
|
+
* "application/json; charset=utf-8").
|
|
239
276
|
*/
|
|
240
|
-
type JSONLike<T> =
|
|
277
|
+
type JSONLike<T> = {
|
|
278
|
+
[Key in keyof T]: IsJSONMediaType<Key> extends true ? T[Key] : never;
|
|
279
|
+
}[keyof T];
|
|
241
280
|
/**
|
|
242
281
|
* Filter objects that have required keys
|
|
243
282
|
* @deprecated Use `RequiredKeysOf` instead
|
package/dist/helpers.d.ts
CHANGED
|
@@ -199,25 +199,61 @@ 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
|
+
/** `true` for `any`: a conditional type on `any` takes both branches, so `any` is checked first. */
|
|
203
|
+
type IsAny<T> = 0 extends 1 & T ? true : false;
|
|
202
204
|
/**
|
|
203
205
|
* Return all 2XX responses from a Response Object Map, or `default` if it declares no 2XX one. For
|
|
204
206
|
* a union of maps (a call with a union of paths), the union of each map's responses: `keyof` of a
|
|
205
207
|
* 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).
|
|
208
|
+
* share its statuses, and a `default`-only path typed the data `undefined` (upstream). An `any`
|
|
209
|
+
* map (`createClient<any>()`) gives `any`: no key of `keyof any` is a status, which gave `never`.
|
|
207
210
|
*/
|
|
208
|
-
type SuccessResponse<T extends Record<string | number, any>, Media extends MediaType = MediaType> = T extends unknown ?
|
|
209
|
-
type GetResponseContent<T extends Record<string | number, any>, Media extends MediaType = MediaType, ResponseCode extends keyof T = keyof T> = ResponseCode extends keyof T ? {
|
|
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;
|
|
211
|
-
}[ResponseCode] : never;
|
|
211
|
+
type SuccessResponse<T extends Record<string | number, any>, Media extends MediaType = MediaType> = IsAny<T> extends true ? T : T extends unknown ? ResponseBodies<T, Media, SuccessStatus<T>, never> : never;
|
|
212
212
|
/**
|
|
213
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`.
|
|
214
|
+
* of maps, the union of each map's responses, as `SuccessResponse`. An `any` map gives `any`.
|
|
215
|
+
*/
|
|
216
|
+
type ErrorResponse<T extends Record<string | number, any>, Media extends MediaType = MediaType> = IsAny<T> extends true ? T : T extends unknown ? ResponseBodies<T, Media, ErrorStatusKey<T>, string> : never;
|
|
217
|
+
/**
|
|
218
|
+
* The bodies of the responses `ResponseCode` as the client returns them (`ResponseBody`): each
|
|
219
|
+
* success one as `SuccessResponse` gives it, each other one as `ErrorResponse` does.
|
|
215
220
|
*/
|
|
216
|
-
type
|
|
221
|
+
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>;
|
|
222
|
+
/**
|
|
223
|
+
* The bodies of the responses `ResponseCode`. A response without `content` keeps its `content`
|
|
224
|
+
* type (`undefined` for `content?: never`). `OtherBody` is the class's body that is not JSON: it
|
|
225
|
+
* is passed in, not read from the status, so the result for a generic map stays a type that
|
|
226
|
+
* TypeScript relates to the one of another client (ADR-030).
|
|
227
|
+
*/
|
|
228
|
+
type ResponseBodies<T extends Record<string | number, any>, Media, ResponseCode extends keyof T, OtherBody> = ResponseCode extends keyof T ? {
|
|
229
|
+
[K in ResponseCode]: T[K]["content"] extends Record<string, any> ? ResponseBody<T[K]["content"], Media, OtherBody> : K extends keyof T ? T[K]["content"] : never;
|
|
230
|
+
}[ResponseCode] : never;
|
|
231
|
+
/**
|
|
232
|
+
* The body of one response with the content map `Content`, as the client returns it. `Media`
|
|
233
|
+
* picks among its media types, as an `Accept` header does, and each one picked gives its schema.
|
|
234
|
+
* A response with none of them can still arrive (a proxy's HTML 500), so it gives what the client
|
|
235
|
+
* makes of its bodies: a JSON body gives its schema, any other body `OtherBody`, a `string` for an
|
|
236
|
+
* error (the client returns the text) and nothing for a success (the default `parseAs: "json"`
|
|
237
|
+
* rejects it; another `parseAs` types the data by itself). `Media` is read only in
|
|
238
|
+
* `keyof Content & Media`, as in `FilterKeys`, so TypeScript still relates the results of two
|
|
239
|
+
* clients with different `Media` in generic code (ADR-030).
|
|
240
|
+
*/
|
|
241
|
+
type ResponseBody<Content, Media, OtherBody> = ([keyof Content & Media] extends [never] ? JSONLike<Content> : Content[keyof Content & Media]) | ([keyof Content & Media] extends [never] ? false extends {
|
|
242
|
+
[Key in keyof Content]: IsJSONMediaType<Key>;
|
|
243
|
+
}[keyof Content] ? OtherBody : never : never);
|
|
244
|
+
/**
|
|
245
|
+
* The media types that the client parses as JSON, as a content map writes them: `json` or a
|
|
246
|
+
* `+json` suffix (RFC 6838), with or without parameters, as the client's `JSON_MEDIA_TYPE_RE`, and
|
|
247
|
+
* the wildcards for any type and any `application` type, which may hold JSON. Not `x-ndjson` or
|
|
248
|
+
* `json-seq`, whose bodies are lines of JSON.
|
|
249
|
+
*/
|
|
250
|
+
type JSONMediaType = `${string}/${"" | `${string}+`}json` | `${string}/${"" | `${string}+`}json${";" | " " | "\t"}${string}` | "*/*" | "application/*";
|
|
251
|
+
/** Whether the client parses a body of the media type `Key` as JSON, in any letter case. */
|
|
252
|
+
type IsJSONMediaType<Key> = Key extends string ? Lowercase<Key> extends JSONMediaType ? true : false : false;
|
|
217
253
|
/** Return first JSON-like 2XX response from a path + HTTP method */
|
|
218
|
-
type SuccessResponseJSON<PathMethod extends Record<string | number, any>> = SuccessResponse<ResponseObjectMap<PathMethod>,
|
|
254
|
+
type SuccessResponseJSON<PathMethod extends Record<string | number, any>> = SuccessResponse<ResponseObjectMap<PathMethod>, JSONMediaType>;
|
|
219
255
|
/** Return JSON-like 1XX, 3XX, 4XX, 5XX and `default` responses from a path + HTTP method */
|
|
220
|
-
type ErrorResponseJSON<PathMethod extends Record<string | number, any>> = ErrorResponse<ResponseObjectMap<PathMethod>,
|
|
256
|
+
type ErrorResponseJSON<PathMethod extends Record<string | number, any>> = ErrorResponse<ResponseObjectMap<PathMethod>, JSONMediaType>;
|
|
221
257
|
/** Return JSON-like request body from a path + HTTP method */
|
|
222
258
|
type RequestBodyJSON<PathMethod> = JSONLike<FilterKeys<OperationRequestBody<PathMethod>, "content">>;
|
|
223
259
|
/** Find first match of multiple keys */
|
|
@@ -234,10 +270,13 @@ type GetValueWithDefault<Obj, KeyPattern, Default> = Obj extends unknown ? Filte
|
|
|
234
270
|
*/
|
|
235
271
|
type MediaType = `${string}/${string}`;
|
|
236
272
|
/**
|
|
237
|
-
*
|
|
238
|
-
* "application/
|
|
273
|
+
* The values of the JSON media types of a content map, as the client parses them: `json` or a
|
|
274
|
+
* `+json` suffix, with or without parameters ("application/json", "application/vnd.api+json",
|
|
275
|
+
* "application/json; charset=utf-8").
|
|
239
276
|
*/
|
|
240
|
-
type JSONLike<T> =
|
|
277
|
+
type JSONLike<T> = {
|
|
278
|
+
[Key in keyof T]: IsJSONMediaType<Key> extends true ? T[Key] : never;
|
|
279
|
+
}[keyof T];
|
|
241
280
|
/**
|
|
242
281
|
* Filter objects that have required keys
|
|
243
282
|
* @deprecated Use `RequiredKeysOf` instead
|
package/dist/index.cjs
CHANGED
|
@@ -415,9 +415,7 @@ function headerParamValue(value) {
|
|
|
415
415
|
if (value === null || typeof value !== "object" || isDate(value)) {
|
|
416
416
|
return valueText(value);
|
|
417
417
|
}
|
|
418
|
-
|
|
419
|
-
throw new Error(`${NESTED_VALUE_ERROR} in header parameters.`);
|
|
420
|
-
}
|
|
418
|
+
checkFlatMembers(value, HEADER_NESTED_VALUE_ERROR);
|
|
421
419
|
const serialize = Array.isArray(value) ? serializeArrayParam : serializeObjectParam;
|
|
422
420
|
return serialize("", value, HEADER_PARAM_STYLE) || void 0;
|
|
423
421
|
}
|
|
@@ -429,14 +427,29 @@ function serializeHeaderParams(header) {
|
|
|
429
427
|
return Object.fromEntries(entries);
|
|
430
428
|
}
|
|
431
429
|
const NESTED_VALUE_ERROR = "Deeply-nested arrays/objects aren\u2019t supported";
|
|
430
|
+
const HEADER_NESTED_VALUE_ERROR = `${NESTED_VALUE_ERROR} in header parameters.`;
|
|
431
|
+
const PATH_HOOK_HINT = "Provide your own `pathSerializer()` to handle these.";
|
|
432
|
+
const PATH_NESTED_VALUE_ERROR = `${NESTED_VALUE_ERROR} in path parameters. ${PATH_HOOK_HINT}`;
|
|
433
|
+
const QUERY_HOOK_HINT = "Provide your own `querySerializer()` to handle these.";
|
|
434
|
+
const QUERY_NESTED_VALUE_ERROR = `${NESTED_VALUE_ERROR}. ${QUERY_HOOK_HINT}`;
|
|
432
435
|
function isNestedValue(value) {
|
|
433
436
|
return Boolean(value) && typeof value === "object" && !isDate(value);
|
|
434
437
|
}
|
|
438
|
+
function checkFlatMembers(value, message) {
|
|
439
|
+
if (Object.values(value).some((member) => isNestedValue(member))) {
|
|
440
|
+
throw new Error(message);
|
|
441
|
+
}
|
|
442
|
+
}
|
|
443
|
+
const PATH_STYLE_JOINERS = { simple: ",", label: ".", matrix: ";" };
|
|
444
|
+
function checkFlatParam(value, style) {
|
|
445
|
+
checkFlatMembers(
|
|
446
|
+
value,
|
|
447
|
+
PATH_STYLE_JOINERS[style] ? PATH_NESTED_VALUE_ERROR : QUERY_NESTED_VALUE_ERROR
|
|
448
|
+
);
|
|
449
|
+
}
|
|
435
450
|
function encodeValue(value, allowReserved) {
|
|
436
451
|
if (isNestedValue(value)) {
|
|
437
|
-
throw new Error(
|
|
438
|
-
`${NESTED_VALUE_ERROR}. Provide your own \`querySerializer()\` to handle these.`
|
|
439
|
-
);
|
|
452
|
+
throw new Error(QUERY_NESTED_VALUE_ERROR);
|
|
440
453
|
}
|
|
441
454
|
const text = valueText(value);
|
|
442
455
|
return allowReserved === true ? text : encodeURIComponent(text);
|
|
@@ -448,7 +461,7 @@ function serializePrimitiveParam(name, value, options) {
|
|
|
448
461
|
return `${name}=${encodeValue(value, options?.allowReserved)}`;
|
|
449
462
|
}
|
|
450
463
|
function joinExploded(values, style) {
|
|
451
|
-
const joiner =
|
|
464
|
+
const joiner = PATH_STYLE_JOINERS[style] || "&";
|
|
452
465
|
const final = values.join(joiner);
|
|
453
466
|
return style === "label" || style === "matrix" ? `${joiner}${final}` : final;
|
|
454
467
|
}
|
|
@@ -485,6 +498,7 @@ function serializeObjectParam(name, value, options) {
|
|
|
485
498
|
if (!value || typeof value !== "object" || !Object.keys(value).length) {
|
|
486
499
|
return "";
|
|
487
500
|
}
|
|
501
|
+
checkFlatParam(value, options.style);
|
|
488
502
|
if (options.style !== "deepObject" && options.explode === false) {
|
|
489
503
|
return serializeUnexplodedObject(name, value, options);
|
|
490
504
|
}
|
|
@@ -524,6 +538,7 @@ function serializeArrayParam(name, value, options) {
|
|
|
524
538
|
if (!Array.isArray(value) || !value.length) {
|
|
525
539
|
return "";
|
|
526
540
|
}
|
|
541
|
+
checkFlatParam(value, options.style);
|
|
527
542
|
if (options.explode === false) {
|
|
528
543
|
return serializeUnexplodedArray(name, value, options);
|
|
529
544
|
}
|
|
@@ -573,11 +588,15 @@ function mergeQueryOptions(clientOptions, requestOptions) {
|
|
|
573
588
|
const base = typeof clientOptions === "function" ? {} : clientOptions ?? {};
|
|
574
589
|
return {
|
|
575
590
|
...base,
|
|
576
|
-
...requestOptions,
|
|
577
|
-
array: { ...base.array, ...requestOptions.array },
|
|
578
|
-
object: { ...base.object, ...requestOptions.object }
|
|
591
|
+
...definedOptions(requestOptions),
|
|
592
|
+
array: { ...base.array, ...definedOptions(requestOptions.array) },
|
|
593
|
+
object: { ...base.object, ...definedOptions(requestOptions.object) }
|
|
579
594
|
};
|
|
580
595
|
}
|
|
596
|
+
function definedOptions(options) {
|
|
597
|
+
const entries = Object.entries(options ?? {}).filter(([, value]) => value !== void 0);
|
|
598
|
+
return Object.fromEntries(entries);
|
|
599
|
+
}
|
|
581
600
|
function defaultPathSerializer(pathname, pathParams) {
|
|
582
601
|
const matches = pathname.match(PATH_PARAM_RE);
|
|
583
602
|
if (!matches) {
|
|
@@ -602,22 +621,24 @@ function defaultPathSerializer(pathname, pathParams) {
|
|
|
602
621
|
if (!pathParams || pathParams[name] === void 0 || pathParams[name] === null) {
|
|
603
622
|
continue;
|
|
604
623
|
}
|
|
605
|
-
const
|
|
606
|
-
let serialized;
|
|
607
|
-
if (Array.isArray(value)) {
|
|
608
|
-
serialized = serializeArrayParam(name, value, { style, explode });
|
|
609
|
-
} else if (typeof value === "object" && !isDate(value)) {
|
|
610
|
-
serialized = serializeObjectParam(name, value, { style, explode });
|
|
611
|
-
} else if (style === "matrix") {
|
|
612
|
-
serialized = `;${serializePrimitiveParam(name, value)}`;
|
|
613
|
-
} else {
|
|
614
|
-
serialized = style === "label" ? `.${encodeValue(value)}` : encodeValue(value);
|
|
615
|
-
}
|
|
624
|
+
const serialized = serializePathParam(name, pathParams[name], { style, explode });
|
|
616
625
|
nextURL = nextURL.replace(match, () => serialized);
|
|
617
626
|
}
|
|
618
627
|
checkSerializedPath(pathname, nextURL);
|
|
619
628
|
return nextURL;
|
|
620
629
|
}
|
|
630
|
+
function serializePathParam(name, value, options) {
|
|
631
|
+
if (Array.isArray(value)) {
|
|
632
|
+
return serializeArrayParam(name, value, options);
|
|
633
|
+
}
|
|
634
|
+
if (typeof value === "object" && !isDate(value)) {
|
|
635
|
+
return serializeObjectParam(name, value, options);
|
|
636
|
+
}
|
|
637
|
+
if (options.style === "matrix") {
|
|
638
|
+
return `;${serializePrimitiveParam(name, value)}`;
|
|
639
|
+
}
|
|
640
|
+
return options.style === "label" ? `.${encodeValue(value)}` : encodeValue(value);
|
|
641
|
+
}
|
|
621
642
|
function checkSerializedPath(template, url) {
|
|
622
643
|
const fixed = template.slice(0, template.search(PATH_PARAM_RE));
|
|
623
644
|
const path = url.split(QUERY_OR_FRAGMENT_RE, 1)[0];
|