openapi-typesafe-fetch 1.0.0-rc.10 → 1.0.0-rc.12
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 +36 -0
- package/dist/helpers.d.cts +50 -9
- package/dist/helpers.d.mts +50 -9
- package/dist/helpers.d.ts +50 -9
- package/dist/index.d.cts +87 -19
- package/dist/index.d.mts +87 -19
- package/dist/index.d.ts +87 -19
- package/package.json +2 -2
- package/src/helpers.ts +71 -16
- package/src/index.d.ts +87 -19
package/CHANGELOG.md
CHANGED
|
@@ -2,6 +2,42 @@
|
|
|
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.12
|
|
6
|
+
|
|
7
|
+
### Patch Changes
|
|
8
|
+
|
|
9
|
+
- [#136](https://github.com/openapi-typesafe/openapi-typesafe/pull/136) [`68ce29a`](https://github.com/openapi-typesafe/openapi-typesafe/commit/68ce29ad4b8f8490d38990af62bbc830b54df015) Thanks [@fedorello](https://github.com/fedorello)! - A wrapper generic in the path that sets `parseAs`, such as
|
|
10
|
+
`client.GET(path, { ...init, parseAs: "text" })` or an `init` typed
|
|
11
|
+
`FetchOptions<…> & { parseAs: "text" }`, types `data` as the body of that `parseAs` again
|
|
12
|
+
(`string` for `"text"`), as openapi-fetch does. Before, it was the text or any JSON body of the
|
|
13
|
+
paths, so assigning it to `string | undefined` failed. Each member of a union of options now
|
|
14
|
+
types its own data: `{ parseAs: ParseAs } | undefined` gives every body, as in openapi-fetch.
|
|
15
|
+
|
|
16
|
+
- [#136](https://github.com/openapi-typesafe/openapi-typesafe/pull/136) [`68ce29a`](https://github.com/openapi-typesafe/openapi-typesafe/commit/68ce29ad4b8f8490d38990af62bbc830b54df015) Thanks [@fedorello](https://github.com/fedorello)! - `SuccessResponseJSON` and `ErrorResponseJSON` of a union of operations are the union of each
|
|
17
|
+
operation's JSON bodies again. Before, only the status codes the operations share counted, so
|
|
18
|
+
`ErrorResponseJSON<OpA | OpB>` could gain a `string` that neither operation has alone.
|
|
19
|
+
|
|
20
|
+
## 1.0.0-rc.11
|
|
21
|
+
|
|
22
|
+
### Patch Changes
|
|
23
|
+
|
|
24
|
+
- [#128](https://github.com/openapi-typesafe/openapi-typesafe/pull/128) [`fad3163`](https://github.com/openapi-typesafe/openapi-typesafe/commit/fad31635ed28508082d59c2e0a3ba6e881b20d02) Thanks [@fedorello](https://github.com/fedorello)! - A `json` inside the parameters of a media type no longer makes it a JSON media type in the types,
|
|
25
|
+
as at runtime: an error declared as `text/plain; profile=urn:a+json` or
|
|
26
|
+
`message/http; msgtype=application/json` is typed as the text (`string`) the client returns, and
|
|
27
|
+
`JSONLike`, `RequestBodyJSON`, `SuccessResponseJSON` and `ErrorResponseJSON` leave such media
|
|
28
|
+
types out. Only the media type before the `;` counts, for media ranges (`text/*`) too.
|
|
29
|
+
|
|
30
|
+
- [#128](https://github.com/openapi-typesafe/openapi-typesafe/pull/128) [`fad3163`](https://github.com/openapi-typesafe/openapi-typesafe/commit/fad31635ed28508082d59c2e0a3ba6e881b20d02) Thanks [@fedorello](https://github.com/fedorello)! - An optional `parseAs` with only some values, such as a wrapper's `options: { parseAs?: "text" }`
|
|
31
|
+
passed to `client.GET()`, types `data` as the body of each value or the JSON body (`string | T`),
|
|
32
|
+
as the client returns it. Before, it was typed as the JSON body only. Options whose `parseAs` may
|
|
33
|
+
be any value, or that have none, still type `data` as the JSON body. With and without
|
|
34
|
+
`exactOptionalPropertyTypes`.
|
|
35
|
+
|
|
36
|
+
- [#128](https://github.com/openapi-typesafe/openapi-typesafe/pull/128) [`fad3163`](https://github.com/openapi-typesafe/openapi-typesafe/commit/fad31635ed28508082d59c2e0a3ba6e881b20d02) Thanks [@fedorello](https://github.com/fedorello)! - `client.request(method, path, { parseAs })` types `data` by `parseAs`, as `client.GET()` and the
|
|
37
|
+
other method calls do: `{ parseAs: "text" }` gives a `string`, `"stream"` the body stream. Before,
|
|
38
|
+
`data` was always the JSON body. A literal in `params` or `body` next to `parseAs`, such as an
|
|
39
|
+
enum value, no longer loses the `parseAs` type either.
|
|
40
|
+
|
|
5
41
|
## 1.0.0-rc.10
|
|
6
42
|
|
|
7
43
|
### Patch Changes
|
package/dist/helpers.d.cts
CHANGED
|
@@ -272,12 +272,26 @@ type JSONErrorMediaTypes<Content> = {
|
|
|
272
272
|
type TextErrorMediaTypes<Content> = {
|
|
273
273
|
[Key in keyof Content]: IsJSONMediaPattern<Key> extends true ? never : Key;
|
|
274
274
|
}[keyof Content];
|
|
275
|
-
/**
|
|
276
|
-
|
|
275
|
+
/**
|
|
276
|
+
* Whether the client parses every body of the media type `Key` as JSON, in any letter case: its
|
|
277
|
+
* subtype is `json` or ends in `+json`, as `JSON_MEDIA_TYPE_RE` reads it. A `json` in a parameter
|
|
278
|
+
* (`text/plain; profile=urn:a+json`) does not count; `JSONMediaPattern` alone matched it.
|
|
279
|
+
*/
|
|
280
|
+
type IsJSONMediaPattern<Key> = Key extends string ? MediaSubtype<Lowercase<Key>> extends "json" | `${string}+json` ? true : false : false;
|
|
281
|
+
/**
|
|
282
|
+
* The subtype of a media type: the text after the first `/` and before the parameters (the first
|
|
283
|
+
* `;`), without the spaces and tabs at its end. Empty without a type before a `/`, as
|
|
284
|
+
* `JSON_MEDIA_TYPE_RE` wants one. A subtype may hold a `/`, as the regular expression allows.
|
|
285
|
+
*/
|
|
286
|
+
type MediaSubtype<Text extends string> = Text extends `${infer Type}/${infer Rest}` ? Type extends "" ? "" : TrimmedEnd<Rest extends `${infer Subtype};${string}` ? Subtype : Rest> : "";
|
|
287
|
+
/** The text without the spaces and tabs at its end (OWS before parameters, RFC 9110). */
|
|
288
|
+
type TrimmedEnd<Text extends string> = Text extends `${infer Rest} ` | `${infer Rest}\t` ? TrimmedEnd<Rest> : Text;
|
|
277
289
|
/**
|
|
278
290
|
* The media types that the client parses as JSON, as a content map writes them: `json` or a
|
|
279
291
|
* `+json` suffix (RFC 6838), with or without parameters, as the client's `JSON_MEDIA_TYPE_RE`.
|
|
280
|
-
* Not `x-ndjson` or `json-seq`, whose bodies are lines of JSON.
|
|
292
|
+
* Not `x-ndjson` or `json-seq`, whose bodies are lines of JSON. A template cannot keep its
|
|
293
|
+
* `${string}` out of the parameters, so it only picks the schema of an index signature
|
|
294
|
+
* (`JSONMediaKey`); whether a media type is JSON is `IsJSONMediaPattern`'s answer.
|
|
281
295
|
*/
|
|
282
296
|
type JSONMediaPattern = `${string}/${"" | `${string}+`}json` | `${string}/${"" | `${string}+`}json${";" | " " | "\t"}${string}`;
|
|
283
297
|
/**
|
|
@@ -287,12 +301,39 @@ type JSONMediaPattern = `${string}/${"" | `${string}+`}json` | `${string}/${"" |
|
|
|
287
301
|
type JSONMediaRange = `${string}/*` | `${string}/*${";" | " " | "\t"}${string}`;
|
|
288
302
|
/** The media types that may hold JSON, in lower case. */
|
|
289
303
|
type JSONMediaType = JSONMediaPattern | JSONMediaRange;
|
|
290
|
-
/**
|
|
291
|
-
|
|
292
|
-
|
|
293
|
-
|
|
294
|
-
|
|
295
|
-
type
|
|
304
|
+
/**
|
|
305
|
+
* Whether a body of the media type `Key` may be one the client parses as JSON, in any letter
|
|
306
|
+
* case: a JSON media type (`IsJSONMediaPattern`) or a media range (subtype `*`), with or without
|
|
307
|
+
* parameters, which do not count.
|
|
308
|
+
*/
|
|
309
|
+
type IsJSONMediaType<Key> = Key extends string ? IsJSONMediaPattern<Key> extends true ? true : MediaSubtype<Lowercase<Key>> extends "*" ? true : false : false;
|
|
310
|
+
/**
|
|
311
|
+
* Return first JSON-like 2XX response from a path + HTTP method; for a union of operations, the
|
|
312
|
+
* union of each one's.
|
|
313
|
+
*/
|
|
314
|
+
type SuccessResponseJSON<PathMethod extends Record<string | number, any>> = PathMethod extends unknown ? SuccessResponse<ResponseObjectMap<PathMethod>, JSONMediaKeys<ResponseObjectMap<PathMethod>>> : never;
|
|
315
|
+
/**
|
|
316
|
+
* Return JSON-like 1XX, 3XX, 4XX, 5XX and `default` responses from a path + HTTP method; for a
|
|
317
|
+
* union of operations, the union of each one's.
|
|
318
|
+
*/
|
|
319
|
+
type ErrorResponseJSON<PathMethod extends Record<string | number, any>> = PathMethod extends unknown ? ErrorResponse<ResponseObjectMap<PathMethod>, JSONMediaKeys<ResponseObjectMap<PathMethod>>> : never;
|
|
320
|
+
/**
|
|
321
|
+
* The media types of the responses `T` (of one operation) that may hold JSON (`IsJSONMediaType`),
|
|
322
|
+
* for the JSON helpers to pick by: their keys, as `JSONMediaType` cannot leave out a `json` in
|
|
323
|
+
* parameters. A content map with an index signature has no such key, so it gives
|
|
324
|
+
* `JSONMediaType`, which picks the schema of its signature. The helpers pick per operation: the
|
|
325
|
+
* keys of a union of response maps are only the status codes they share, so the media types of
|
|
326
|
+
* the other statuses were lost, and the media types of one operation could pick in another.
|
|
327
|
+
*/
|
|
328
|
+
type JSONMediaKeys<T> = {
|
|
329
|
+
[Status in keyof T]: T[Status] extends {
|
|
330
|
+
content: infer Content;
|
|
331
|
+
} ? {
|
|
332
|
+
[Key in keyof Content]: JSONMediaKey<Key>;
|
|
333
|
+
}[keyof Content] : never;
|
|
334
|
+
}[keyof T];
|
|
335
|
+
/** `Key` when it may hold JSON; `JSONMediaType` for the wide key of an index signature. */
|
|
336
|
+
type JSONMediaKey<Key> = string extends Key ? JSONMediaType : IsJSONMediaType<Key> extends true ? Key : never;
|
|
296
337
|
/** Return JSON-like request body from a path + HTTP method */
|
|
297
338
|
type RequestBodyJSON<PathMethod> = JSONLike<FilterKeys<OperationRequestBody<PathMethod>, "content">>;
|
|
298
339
|
/** Find first match of multiple keys */
|
package/dist/helpers.d.mts
CHANGED
|
@@ -272,12 +272,26 @@ type JSONErrorMediaTypes<Content> = {
|
|
|
272
272
|
type TextErrorMediaTypes<Content> = {
|
|
273
273
|
[Key in keyof Content]: IsJSONMediaPattern<Key> extends true ? never : Key;
|
|
274
274
|
}[keyof Content];
|
|
275
|
-
/**
|
|
276
|
-
|
|
275
|
+
/**
|
|
276
|
+
* Whether the client parses every body of the media type `Key` as JSON, in any letter case: its
|
|
277
|
+
* subtype is `json` or ends in `+json`, as `JSON_MEDIA_TYPE_RE` reads it. A `json` in a parameter
|
|
278
|
+
* (`text/plain; profile=urn:a+json`) does not count; `JSONMediaPattern` alone matched it.
|
|
279
|
+
*/
|
|
280
|
+
type IsJSONMediaPattern<Key> = Key extends string ? MediaSubtype<Lowercase<Key>> extends "json" | `${string}+json` ? true : false : false;
|
|
281
|
+
/**
|
|
282
|
+
* The subtype of a media type: the text after the first `/` and before the parameters (the first
|
|
283
|
+
* `;`), without the spaces and tabs at its end. Empty without a type before a `/`, as
|
|
284
|
+
* `JSON_MEDIA_TYPE_RE` wants one. A subtype may hold a `/`, as the regular expression allows.
|
|
285
|
+
*/
|
|
286
|
+
type MediaSubtype<Text extends string> = Text extends `${infer Type}/${infer Rest}` ? Type extends "" ? "" : TrimmedEnd<Rest extends `${infer Subtype};${string}` ? Subtype : Rest> : "";
|
|
287
|
+
/** The text without the spaces and tabs at its end (OWS before parameters, RFC 9110). */
|
|
288
|
+
type TrimmedEnd<Text extends string> = Text extends `${infer Rest} ` | `${infer Rest}\t` ? TrimmedEnd<Rest> : Text;
|
|
277
289
|
/**
|
|
278
290
|
* The media types that the client parses as JSON, as a content map writes them: `json` or a
|
|
279
291
|
* `+json` suffix (RFC 6838), with or without parameters, as the client's `JSON_MEDIA_TYPE_RE`.
|
|
280
|
-
* Not `x-ndjson` or `json-seq`, whose bodies are lines of JSON.
|
|
292
|
+
* Not `x-ndjson` or `json-seq`, whose bodies are lines of JSON. A template cannot keep its
|
|
293
|
+
* `${string}` out of the parameters, so it only picks the schema of an index signature
|
|
294
|
+
* (`JSONMediaKey`); whether a media type is JSON is `IsJSONMediaPattern`'s answer.
|
|
281
295
|
*/
|
|
282
296
|
type JSONMediaPattern = `${string}/${"" | `${string}+`}json` | `${string}/${"" | `${string}+`}json${";" | " " | "\t"}${string}`;
|
|
283
297
|
/**
|
|
@@ -287,12 +301,39 @@ type JSONMediaPattern = `${string}/${"" | `${string}+`}json` | `${string}/${"" |
|
|
|
287
301
|
type JSONMediaRange = `${string}/*` | `${string}/*${";" | " " | "\t"}${string}`;
|
|
288
302
|
/** The media types that may hold JSON, in lower case. */
|
|
289
303
|
type JSONMediaType = JSONMediaPattern | JSONMediaRange;
|
|
290
|
-
/**
|
|
291
|
-
|
|
292
|
-
|
|
293
|
-
|
|
294
|
-
|
|
295
|
-
type
|
|
304
|
+
/**
|
|
305
|
+
* Whether a body of the media type `Key` may be one the client parses as JSON, in any letter
|
|
306
|
+
* case: a JSON media type (`IsJSONMediaPattern`) or a media range (subtype `*`), with or without
|
|
307
|
+
* parameters, which do not count.
|
|
308
|
+
*/
|
|
309
|
+
type IsJSONMediaType<Key> = Key extends string ? IsJSONMediaPattern<Key> extends true ? true : MediaSubtype<Lowercase<Key>> extends "*" ? true : false : false;
|
|
310
|
+
/**
|
|
311
|
+
* Return first JSON-like 2XX response from a path + HTTP method; for a union of operations, the
|
|
312
|
+
* union of each one's.
|
|
313
|
+
*/
|
|
314
|
+
type SuccessResponseJSON<PathMethod extends Record<string | number, any>> = PathMethod extends unknown ? SuccessResponse<ResponseObjectMap<PathMethod>, JSONMediaKeys<ResponseObjectMap<PathMethod>>> : never;
|
|
315
|
+
/**
|
|
316
|
+
* Return JSON-like 1XX, 3XX, 4XX, 5XX and `default` responses from a path + HTTP method; for a
|
|
317
|
+
* union of operations, the union of each one's.
|
|
318
|
+
*/
|
|
319
|
+
type ErrorResponseJSON<PathMethod extends Record<string | number, any>> = PathMethod extends unknown ? ErrorResponse<ResponseObjectMap<PathMethod>, JSONMediaKeys<ResponseObjectMap<PathMethod>>> : never;
|
|
320
|
+
/**
|
|
321
|
+
* The media types of the responses `T` (of one operation) that may hold JSON (`IsJSONMediaType`),
|
|
322
|
+
* for the JSON helpers to pick by: their keys, as `JSONMediaType` cannot leave out a `json` in
|
|
323
|
+
* parameters. A content map with an index signature has no such key, so it gives
|
|
324
|
+
* `JSONMediaType`, which picks the schema of its signature. The helpers pick per operation: the
|
|
325
|
+
* keys of a union of response maps are only the status codes they share, so the media types of
|
|
326
|
+
* the other statuses were lost, and the media types of one operation could pick in another.
|
|
327
|
+
*/
|
|
328
|
+
type JSONMediaKeys<T> = {
|
|
329
|
+
[Status in keyof T]: T[Status] extends {
|
|
330
|
+
content: infer Content;
|
|
331
|
+
} ? {
|
|
332
|
+
[Key in keyof Content]: JSONMediaKey<Key>;
|
|
333
|
+
}[keyof Content] : never;
|
|
334
|
+
}[keyof T];
|
|
335
|
+
/** `Key` when it may hold JSON; `JSONMediaType` for the wide key of an index signature. */
|
|
336
|
+
type JSONMediaKey<Key> = string extends Key ? JSONMediaType : IsJSONMediaType<Key> extends true ? Key : never;
|
|
296
337
|
/** Return JSON-like request body from a path + HTTP method */
|
|
297
338
|
type RequestBodyJSON<PathMethod> = JSONLike<FilterKeys<OperationRequestBody<PathMethod>, "content">>;
|
|
298
339
|
/** Find first match of multiple keys */
|
package/dist/helpers.d.ts
CHANGED
|
@@ -272,12 +272,26 @@ type JSONErrorMediaTypes<Content> = {
|
|
|
272
272
|
type TextErrorMediaTypes<Content> = {
|
|
273
273
|
[Key in keyof Content]: IsJSONMediaPattern<Key> extends true ? never : Key;
|
|
274
274
|
}[keyof Content];
|
|
275
|
-
/**
|
|
276
|
-
|
|
275
|
+
/**
|
|
276
|
+
* Whether the client parses every body of the media type `Key` as JSON, in any letter case: its
|
|
277
|
+
* subtype is `json` or ends in `+json`, as `JSON_MEDIA_TYPE_RE` reads it. A `json` in a parameter
|
|
278
|
+
* (`text/plain; profile=urn:a+json`) does not count; `JSONMediaPattern` alone matched it.
|
|
279
|
+
*/
|
|
280
|
+
type IsJSONMediaPattern<Key> = Key extends string ? MediaSubtype<Lowercase<Key>> extends "json" | `${string}+json` ? true : false : false;
|
|
281
|
+
/**
|
|
282
|
+
* The subtype of a media type: the text after the first `/` and before the parameters (the first
|
|
283
|
+
* `;`), without the spaces and tabs at its end. Empty without a type before a `/`, as
|
|
284
|
+
* `JSON_MEDIA_TYPE_RE` wants one. A subtype may hold a `/`, as the regular expression allows.
|
|
285
|
+
*/
|
|
286
|
+
type MediaSubtype<Text extends string> = Text extends `${infer Type}/${infer Rest}` ? Type extends "" ? "" : TrimmedEnd<Rest extends `${infer Subtype};${string}` ? Subtype : Rest> : "";
|
|
287
|
+
/** The text without the spaces and tabs at its end (OWS before parameters, RFC 9110). */
|
|
288
|
+
type TrimmedEnd<Text extends string> = Text extends `${infer Rest} ` | `${infer Rest}\t` ? TrimmedEnd<Rest> : Text;
|
|
277
289
|
/**
|
|
278
290
|
* The media types that the client parses as JSON, as a content map writes them: `json` or a
|
|
279
291
|
* `+json` suffix (RFC 6838), with or without parameters, as the client's `JSON_MEDIA_TYPE_RE`.
|
|
280
|
-
* Not `x-ndjson` or `json-seq`, whose bodies are lines of JSON.
|
|
292
|
+
* Not `x-ndjson` or `json-seq`, whose bodies are lines of JSON. A template cannot keep its
|
|
293
|
+
* `${string}` out of the parameters, so it only picks the schema of an index signature
|
|
294
|
+
* (`JSONMediaKey`); whether a media type is JSON is `IsJSONMediaPattern`'s answer.
|
|
281
295
|
*/
|
|
282
296
|
type JSONMediaPattern = `${string}/${"" | `${string}+`}json` | `${string}/${"" | `${string}+`}json${";" | " " | "\t"}${string}`;
|
|
283
297
|
/**
|
|
@@ -287,12 +301,39 @@ type JSONMediaPattern = `${string}/${"" | `${string}+`}json` | `${string}/${"" |
|
|
|
287
301
|
type JSONMediaRange = `${string}/*` | `${string}/*${";" | " " | "\t"}${string}`;
|
|
288
302
|
/** The media types that may hold JSON, in lower case. */
|
|
289
303
|
type JSONMediaType = JSONMediaPattern | JSONMediaRange;
|
|
290
|
-
/**
|
|
291
|
-
|
|
292
|
-
|
|
293
|
-
|
|
294
|
-
|
|
295
|
-
type
|
|
304
|
+
/**
|
|
305
|
+
* Whether a body of the media type `Key` may be one the client parses as JSON, in any letter
|
|
306
|
+
* case: a JSON media type (`IsJSONMediaPattern`) or a media range (subtype `*`), with or without
|
|
307
|
+
* parameters, which do not count.
|
|
308
|
+
*/
|
|
309
|
+
type IsJSONMediaType<Key> = Key extends string ? IsJSONMediaPattern<Key> extends true ? true : MediaSubtype<Lowercase<Key>> extends "*" ? true : false : false;
|
|
310
|
+
/**
|
|
311
|
+
* Return first JSON-like 2XX response from a path + HTTP method; for a union of operations, the
|
|
312
|
+
* union of each one's.
|
|
313
|
+
*/
|
|
314
|
+
type SuccessResponseJSON<PathMethod extends Record<string | number, any>> = PathMethod extends unknown ? SuccessResponse<ResponseObjectMap<PathMethod>, JSONMediaKeys<ResponseObjectMap<PathMethod>>> : never;
|
|
315
|
+
/**
|
|
316
|
+
* Return JSON-like 1XX, 3XX, 4XX, 5XX and `default` responses from a path + HTTP method; for a
|
|
317
|
+
* union of operations, the union of each one's.
|
|
318
|
+
*/
|
|
319
|
+
type ErrorResponseJSON<PathMethod extends Record<string | number, any>> = PathMethod extends unknown ? ErrorResponse<ResponseObjectMap<PathMethod>, JSONMediaKeys<ResponseObjectMap<PathMethod>>> : never;
|
|
320
|
+
/**
|
|
321
|
+
* The media types of the responses `T` (of one operation) that may hold JSON (`IsJSONMediaType`),
|
|
322
|
+
* for the JSON helpers to pick by: their keys, as `JSONMediaType` cannot leave out a `json` in
|
|
323
|
+
* parameters. A content map with an index signature has no such key, so it gives
|
|
324
|
+
* `JSONMediaType`, which picks the schema of its signature. The helpers pick per operation: the
|
|
325
|
+
* keys of a union of response maps are only the status codes they share, so the media types of
|
|
326
|
+
* the other statuses were lost, and the media types of one operation could pick in another.
|
|
327
|
+
*/
|
|
328
|
+
type JSONMediaKeys<T> = {
|
|
329
|
+
[Status in keyof T]: T[Status] extends {
|
|
330
|
+
content: infer Content;
|
|
331
|
+
} ? {
|
|
332
|
+
[Key in keyof Content]: JSONMediaKey<Key>;
|
|
333
|
+
}[keyof Content] : never;
|
|
334
|
+
}[keyof T];
|
|
335
|
+
/** `Key` when it may hold JSON; `JSONMediaType` for the wide key of an index signature. */
|
|
336
|
+
type JSONMediaKey<Key> = string extends Key ? JSONMediaType : IsJSONMediaType<Key> extends true ? Key : never;
|
|
296
337
|
/** Return JSON-like request body from a path + HTTP method */
|
|
297
338
|
type RequestBodyJSON<PathMethod> = JSONLike<FilterKeys<OperationRequestBody<PathMethod>, "content">>;
|
|
298
339
|
/** Find first match of multiple keys */
|
package/dist/index.d.cts
CHANGED
|
@@ -129,16 +129,71 @@ type BodyType<T = unknown> = {
|
|
|
129
129
|
};
|
|
130
130
|
type ParseAs = keyof BodyType;
|
|
131
131
|
/**
|
|
132
|
-
* The data of a success body `T` read with the `parseAs` of `Options`. Each value
|
|
133
|
-
* its body, and `undefined` is the default `"json"`, as at runtime: a
|
|
134
|
-
* `parseAs
|
|
135
|
-
* declared init type, as for a call without
|
|
136
|
-
*
|
|
137
|
-
*
|
|
132
|
+
* The data of a success body `T` read with the `parseAs` of `Options`. Each value it may have gives
|
|
133
|
+
* its body, and `undefined` or a left-out `parseAs` is the default `"json"`, as at runtime: a
|
|
134
|
+
* wrapper that passes on `parseAs?: "text"` gets `string | T`, not `T` only. Options without
|
|
135
|
+
* `parseAs`, or whose `parseAs` may be any value (the declared init type, as for a call without
|
|
136
|
+
* options), give `T`, as in openapi-fetch; each member of a union of options gives its own data.
|
|
137
|
+
* No `infer`: TypeScript relates a type to a deferred conditional type through both of its
|
|
138
|
+
* branches only when it infers nothing, and generic hooks return the data of a call as this type.
|
|
139
|
+
* It distributes over `Options`, as openapi-fetch's does, so the options of a generic call (a
|
|
140
|
+
* wrapper's `FetchOptions` of a generic path with `parseAs: "text"`) give the data of their
|
|
141
|
+
* constraint: TypeScript reads a deferred distributive type through its check type's constraint.
|
|
138
142
|
*/
|
|
139
|
-
type ParseAsResponse<T, Options> = Options extends
|
|
140
|
-
? BodyType<T>[
|
|
141
|
-
:
|
|
143
|
+
type ParseAsResponse<T, Options> = Options extends unknown
|
|
144
|
+
? BodyType<T>[ParseAsKey<Options>]
|
|
145
|
+
: never;
|
|
146
|
+
|
|
147
|
+
/**
|
|
148
|
+
* The key of `BodyType` that the options `Options` read the body with. A conditional type of its
|
|
149
|
+
* own, inside the index: one in the branch of `ParseAsResponse` that picked `T` would be read
|
|
150
|
+
* through both of its branches while TypeScript reads the constraint above, as `never` fits any
|
|
151
|
+
* check type, so a generic `parseAs: "text"` gave the JSON body too.
|
|
152
|
+
*/
|
|
153
|
+
type ParseAsKey<Options> = [MissingParseAsValues<Options>] extends [never]
|
|
154
|
+
? "json"
|
|
155
|
+
: ParseAsValue<ParseAsValues<Options>>;
|
|
156
|
+
|
|
157
|
+
/**
|
|
158
|
+
* The `parseAs` values that `Options` may have; `undefined` when it may be left out. One
|
|
159
|
+
* conditional type, not a union: `BodyType<T>` indexed by a union of generic keys is read as the
|
|
160
|
+
* intersection of their bodies when another type is checked against it. `any` options give `any`,
|
|
161
|
+
* so their data is `any`, as in openapi-fetch: a caller that passes an `any` init (as the generic
|
|
162
|
+
* hooks of openapi-typesafe-react-query do) returns it as the data type it declares. The branch
|
|
163
|
+
* is `any` itself, not `Options`: a type parameter there stops TypeScript relating the data of a
|
|
164
|
+
* generic call to a wrapper's (ADR-030).
|
|
165
|
+
*/
|
|
166
|
+
type ParseAsValues<Options> = Options extends unknown
|
|
167
|
+
? IsAny<Options> extends true
|
|
168
|
+
? // biome-ignore lint/suspicious/noExplicitAny: any options give any data, as in openapi-fetch
|
|
169
|
+
any
|
|
170
|
+
:
|
|
171
|
+
| (Options extends RequiredParseAs ? never : undefined)
|
|
172
|
+
| { [Value in ParseAs]: Options extends WithoutParseAs<Value> ? never : Value }[ParseAs]
|
|
173
|
+
: never;
|
|
174
|
+
|
|
175
|
+
/**
|
|
176
|
+
* The `parseAs` values that the options `Options` (one member of a union) cannot have, as
|
|
177
|
+
* `ParseAsValues` reads them.
|
|
178
|
+
*/
|
|
179
|
+
type MissingParseAsValues<Options> =
|
|
180
|
+
| ([Options] extends [RequiredParseAs] ? undefined : never)
|
|
181
|
+
| { [Value in ParseAs]: [Options] extends [WithoutParseAs<Value>] ? Value : never }[ParseAs];
|
|
182
|
+
|
|
183
|
+
/** Options that always have a `parseAs`. */
|
|
184
|
+
type RequiredParseAs = { parseAs: ParseAs };
|
|
185
|
+
|
|
186
|
+
/**
|
|
187
|
+
* Options that cannot have the `parseAs` value `Value`: `undefined` options, or options whose
|
|
188
|
+
* `parseAs` (if any) leaves it out. Each
|
|
189
|
+
* check takes `Options` as its checked type and a fixed type here, so TypeScript relates the
|
|
190
|
+
* data of a generic call to a wrapper's (ADR-030); one union of these types instead would let
|
|
191
|
+
* TypeScript split `parseAs?: ParseAs` into its values and match each one. The `object` keeps
|
|
192
|
+
* options without `parseAs` (`{ headers }`) from failing as a weak type.
|
|
193
|
+
*/
|
|
194
|
+
type WithoutParseAs<Value extends ParseAs> =
|
|
195
|
+
| undefined
|
|
196
|
+
| (object & { parseAs?: Exclude<ParseAs, Value> | undefined });
|
|
142
197
|
|
|
143
198
|
/** How a `parseAs` value reads the body, for each member of a union; `undefined` reads JSON. */
|
|
144
199
|
type ParseAsValue<Value> = Value extends ParseAs ? Value : "json";
|
|
@@ -178,10 +233,16 @@ type RequestBodyOption<T> =
|
|
|
178
233
|
* gave a conditional type no `FetchOptions` argument was assignable to (TS2345). The operations
|
|
179
234
|
* of a union are not merged into one deep init type, which gave TS2589 on hundreds of paths
|
|
180
235
|
* (upstream #1882).
|
|
236
|
+
* Only the options that depend on the operation distribute; the others stay outside the
|
|
237
|
+
* conditional type. While `request()` has not fixed its method, TypeScript reads the init's
|
|
238
|
+
* contextual type through the constraints of the method and the path, and stops after ten nested
|
|
239
|
+
* instantiations of one conditional type: with every option inside it, `parseAs: "text"` had no
|
|
240
|
+
* contextual type, widened to `string`, and the init fell back to its constraint (`data` the JSON
|
|
241
|
+
* body). `FetchOptions<never>` is still `never`.
|
|
181
242
|
*/
|
|
182
|
-
type FetchOptions<T> = T extends unknown
|
|
183
|
-
|
|
184
|
-
|
|
243
|
+
type FetchOptions<T> = (T extends unknown ? OperationRequestOptions<T> : never) &
|
|
244
|
+
SharedRequestOptions &
|
|
245
|
+
Omit<RequestInit, "body" | "headers">;
|
|
185
246
|
|
|
186
247
|
// Readable<T> strips $Write markers (writeOnly properties excluded from response)
|
|
187
248
|
type FetchResponse<
|
|
@@ -201,18 +262,25 @@ type FetchResponse<
|
|
|
201
262
|
response: Response;
|
|
202
263
|
};
|
|
203
264
|
|
|
204
|
-
type RequestOptions<T> =
|
|
265
|
+
type RequestOptions<T> = OperationRequestOptions<T> & SharedRequestOptions;
|
|
266
|
+
|
|
267
|
+
/** The options of a call that depend on its operation `T`. */
|
|
268
|
+
type OperationRequestOptions<T> = ParamsOption<T> &
|
|
205
269
|
RequestBodyOption<T> & {
|
|
206
|
-
baseUrl?: string | undefined;
|
|
207
270
|
querySerializer?: QuerySerializer<T> | QuerySerializerOptions | undefined;
|
|
208
271
|
bodySerializer?: BodySerializer<T> | undefined;
|
|
209
|
-
pathSerializer?: PathSerializer | undefined;
|
|
210
|
-
parseAs?: ParseAs | undefined;
|
|
211
|
-
fetch?: ClientOptions["fetch"];
|
|
212
|
-
headers?: HeadersOptions | undefined;
|
|
213
|
-
middleware?: Middleware[] | undefined;
|
|
214
272
|
};
|
|
215
273
|
|
|
274
|
+
/** The options of a call that are the same for every operation. */
|
|
275
|
+
type SharedRequestOptions = {
|
|
276
|
+
baseUrl?: string | undefined;
|
|
277
|
+
pathSerializer?: PathSerializer | undefined;
|
|
278
|
+
parseAs?: ParseAs | undefined;
|
|
279
|
+
fetch?: ClientOptions["fetch"];
|
|
280
|
+
headers?: HeadersOptions | undefined;
|
|
281
|
+
middleware?: Middleware[] | undefined;
|
|
282
|
+
};
|
|
283
|
+
|
|
216
284
|
type MergedOptions<T = unknown> = {
|
|
217
285
|
baseUrl: string;
|
|
218
286
|
parseAs: ParseAs;
|
package/dist/index.d.mts
CHANGED
|
@@ -129,16 +129,71 @@ type BodyType<T = unknown> = {
|
|
|
129
129
|
};
|
|
130
130
|
type ParseAs = keyof BodyType;
|
|
131
131
|
/**
|
|
132
|
-
* The data of a success body `T` read with the `parseAs` of `Options`. Each value
|
|
133
|
-
* its body, and `undefined` is the default `"json"`, as at runtime: a
|
|
134
|
-
* `parseAs
|
|
135
|
-
* declared init type, as for a call without
|
|
136
|
-
*
|
|
137
|
-
*
|
|
132
|
+
* The data of a success body `T` read with the `parseAs` of `Options`. Each value it may have gives
|
|
133
|
+
* its body, and `undefined` or a left-out `parseAs` is the default `"json"`, as at runtime: a
|
|
134
|
+
* wrapper that passes on `parseAs?: "text"` gets `string | T`, not `T` only. Options without
|
|
135
|
+
* `parseAs`, or whose `parseAs` may be any value (the declared init type, as for a call without
|
|
136
|
+
* options), give `T`, as in openapi-fetch; each member of a union of options gives its own data.
|
|
137
|
+
* No `infer`: TypeScript relates a type to a deferred conditional type through both of its
|
|
138
|
+
* branches only when it infers nothing, and generic hooks return the data of a call as this type.
|
|
139
|
+
* It distributes over `Options`, as openapi-fetch's does, so the options of a generic call (a
|
|
140
|
+
* wrapper's `FetchOptions` of a generic path with `parseAs: "text"`) give the data of their
|
|
141
|
+
* constraint: TypeScript reads a deferred distributive type through its check type's constraint.
|
|
138
142
|
*/
|
|
139
|
-
type ParseAsResponse<T, Options> = Options extends
|
|
140
|
-
? BodyType<T>[
|
|
141
|
-
:
|
|
143
|
+
type ParseAsResponse<T, Options> = Options extends unknown
|
|
144
|
+
? BodyType<T>[ParseAsKey<Options>]
|
|
145
|
+
: never;
|
|
146
|
+
|
|
147
|
+
/**
|
|
148
|
+
* The key of `BodyType` that the options `Options` read the body with. A conditional type of its
|
|
149
|
+
* own, inside the index: one in the branch of `ParseAsResponse` that picked `T` would be read
|
|
150
|
+
* through both of its branches while TypeScript reads the constraint above, as `never` fits any
|
|
151
|
+
* check type, so a generic `parseAs: "text"` gave the JSON body too.
|
|
152
|
+
*/
|
|
153
|
+
type ParseAsKey<Options> = [MissingParseAsValues<Options>] extends [never]
|
|
154
|
+
? "json"
|
|
155
|
+
: ParseAsValue<ParseAsValues<Options>>;
|
|
156
|
+
|
|
157
|
+
/**
|
|
158
|
+
* The `parseAs` values that `Options` may have; `undefined` when it may be left out. One
|
|
159
|
+
* conditional type, not a union: `BodyType<T>` indexed by a union of generic keys is read as the
|
|
160
|
+
* intersection of their bodies when another type is checked against it. `any` options give `any`,
|
|
161
|
+
* so their data is `any`, as in openapi-fetch: a caller that passes an `any` init (as the generic
|
|
162
|
+
* hooks of openapi-typesafe-react-query do) returns it as the data type it declares. The branch
|
|
163
|
+
* is `any` itself, not `Options`: a type parameter there stops TypeScript relating the data of a
|
|
164
|
+
* generic call to a wrapper's (ADR-030).
|
|
165
|
+
*/
|
|
166
|
+
type ParseAsValues<Options> = Options extends unknown
|
|
167
|
+
? IsAny<Options> extends true
|
|
168
|
+
? // biome-ignore lint/suspicious/noExplicitAny: any options give any data, as in openapi-fetch
|
|
169
|
+
any
|
|
170
|
+
:
|
|
171
|
+
| (Options extends RequiredParseAs ? never : undefined)
|
|
172
|
+
| { [Value in ParseAs]: Options extends WithoutParseAs<Value> ? never : Value }[ParseAs]
|
|
173
|
+
: never;
|
|
174
|
+
|
|
175
|
+
/**
|
|
176
|
+
* The `parseAs` values that the options `Options` (one member of a union) cannot have, as
|
|
177
|
+
* `ParseAsValues` reads them.
|
|
178
|
+
*/
|
|
179
|
+
type MissingParseAsValues<Options> =
|
|
180
|
+
| ([Options] extends [RequiredParseAs] ? undefined : never)
|
|
181
|
+
| { [Value in ParseAs]: [Options] extends [WithoutParseAs<Value>] ? Value : never }[ParseAs];
|
|
182
|
+
|
|
183
|
+
/** Options that always have a `parseAs`. */
|
|
184
|
+
type RequiredParseAs = { parseAs: ParseAs };
|
|
185
|
+
|
|
186
|
+
/**
|
|
187
|
+
* Options that cannot have the `parseAs` value `Value`: `undefined` options, or options whose
|
|
188
|
+
* `parseAs` (if any) leaves it out. Each
|
|
189
|
+
* check takes `Options` as its checked type and a fixed type here, so TypeScript relates the
|
|
190
|
+
* data of a generic call to a wrapper's (ADR-030); one union of these types instead would let
|
|
191
|
+
* TypeScript split `parseAs?: ParseAs` into its values and match each one. The `object` keeps
|
|
192
|
+
* options without `parseAs` (`{ headers }`) from failing as a weak type.
|
|
193
|
+
*/
|
|
194
|
+
type WithoutParseAs<Value extends ParseAs> =
|
|
195
|
+
| undefined
|
|
196
|
+
| (object & { parseAs?: Exclude<ParseAs, Value> | undefined });
|
|
142
197
|
|
|
143
198
|
/** How a `parseAs` value reads the body, for each member of a union; `undefined` reads JSON. */
|
|
144
199
|
type ParseAsValue<Value> = Value extends ParseAs ? Value : "json";
|
|
@@ -178,10 +233,16 @@ type RequestBodyOption<T> =
|
|
|
178
233
|
* gave a conditional type no `FetchOptions` argument was assignable to (TS2345). The operations
|
|
179
234
|
* of a union are not merged into one deep init type, which gave TS2589 on hundreds of paths
|
|
180
235
|
* (upstream #1882).
|
|
236
|
+
* Only the options that depend on the operation distribute; the others stay outside the
|
|
237
|
+
* conditional type. While `request()` has not fixed its method, TypeScript reads the init's
|
|
238
|
+
* contextual type through the constraints of the method and the path, and stops after ten nested
|
|
239
|
+
* instantiations of one conditional type: with every option inside it, `parseAs: "text"` had no
|
|
240
|
+
* contextual type, widened to `string`, and the init fell back to its constraint (`data` the JSON
|
|
241
|
+
* body). `FetchOptions<never>` is still `never`.
|
|
181
242
|
*/
|
|
182
|
-
type FetchOptions<T> = T extends unknown
|
|
183
|
-
|
|
184
|
-
|
|
243
|
+
type FetchOptions<T> = (T extends unknown ? OperationRequestOptions<T> : never) &
|
|
244
|
+
SharedRequestOptions &
|
|
245
|
+
Omit<RequestInit, "body" | "headers">;
|
|
185
246
|
|
|
186
247
|
// Readable<T> strips $Write markers (writeOnly properties excluded from response)
|
|
187
248
|
type FetchResponse<
|
|
@@ -201,18 +262,25 @@ type FetchResponse<
|
|
|
201
262
|
response: Response;
|
|
202
263
|
};
|
|
203
264
|
|
|
204
|
-
type RequestOptions<T> =
|
|
265
|
+
type RequestOptions<T> = OperationRequestOptions<T> & SharedRequestOptions;
|
|
266
|
+
|
|
267
|
+
/** The options of a call that depend on its operation `T`. */
|
|
268
|
+
type OperationRequestOptions<T> = ParamsOption<T> &
|
|
205
269
|
RequestBodyOption<T> & {
|
|
206
|
-
baseUrl?: string | undefined;
|
|
207
270
|
querySerializer?: QuerySerializer<T> | QuerySerializerOptions | undefined;
|
|
208
271
|
bodySerializer?: BodySerializer<T> | undefined;
|
|
209
|
-
pathSerializer?: PathSerializer | undefined;
|
|
210
|
-
parseAs?: ParseAs | undefined;
|
|
211
|
-
fetch?: ClientOptions["fetch"];
|
|
212
|
-
headers?: HeadersOptions | undefined;
|
|
213
|
-
middleware?: Middleware[] | undefined;
|
|
214
272
|
};
|
|
215
273
|
|
|
274
|
+
/** The options of a call that are the same for every operation. */
|
|
275
|
+
type SharedRequestOptions = {
|
|
276
|
+
baseUrl?: string | undefined;
|
|
277
|
+
pathSerializer?: PathSerializer | undefined;
|
|
278
|
+
parseAs?: ParseAs | undefined;
|
|
279
|
+
fetch?: ClientOptions["fetch"];
|
|
280
|
+
headers?: HeadersOptions | undefined;
|
|
281
|
+
middleware?: Middleware[] | undefined;
|
|
282
|
+
};
|
|
283
|
+
|
|
216
284
|
type MergedOptions<T = unknown> = {
|
|
217
285
|
baseUrl: string;
|
|
218
286
|
parseAs: ParseAs;
|
package/dist/index.d.ts
CHANGED
|
@@ -129,16 +129,71 @@ type BodyType<T = unknown> = {
|
|
|
129
129
|
};
|
|
130
130
|
type ParseAs = keyof BodyType;
|
|
131
131
|
/**
|
|
132
|
-
* The data of a success body `T` read with the `parseAs` of `Options`. Each value
|
|
133
|
-
* its body, and `undefined` is the default `"json"`, as at runtime: a
|
|
134
|
-
* `parseAs
|
|
135
|
-
* declared init type, as for a call without
|
|
136
|
-
*
|
|
137
|
-
*
|
|
132
|
+
* The data of a success body `T` read with the `parseAs` of `Options`. Each value it may have gives
|
|
133
|
+
* its body, and `undefined` or a left-out `parseAs` is the default `"json"`, as at runtime: a
|
|
134
|
+
* wrapper that passes on `parseAs?: "text"` gets `string | T`, not `T` only. Options without
|
|
135
|
+
* `parseAs`, or whose `parseAs` may be any value (the declared init type, as for a call without
|
|
136
|
+
* options), give `T`, as in openapi-fetch; each member of a union of options gives its own data.
|
|
137
|
+
* No `infer`: TypeScript relates a type to a deferred conditional type through both of its
|
|
138
|
+
* branches only when it infers nothing, and generic hooks return the data of a call as this type.
|
|
139
|
+
* It distributes over `Options`, as openapi-fetch's does, so the options of a generic call (a
|
|
140
|
+
* wrapper's `FetchOptions` of a generic path with `parseAs: "text"`) give the data of their
|
|
141
|
+
* constraint: TypeScript reads a deferred distributive type through its check type's constraint.
|
|
138
142
|
*/
|
|
139
|
-
type ParseAsResponse<T, Options> = Options extends
|
|
140
|
-
? BodyType<T>[
|
|
141
|
-
:
|
|
143
|
+
type ParseAsResponse<T, Options> = Options extends unknown
|
|
144
|
+
? BodyType<T>[ParseAsKey<Options>]
|
|
145
|
+
: never;
|
|
146
|
+
|
|
147
|
+
/**
|
|
148
|
+
* The key of `BodyType` that the options `Options` read the body with. A conditional type of its
|
|
149
|
+
* own, inside the index: one in the branch of `ParseAsResponse` that picked `T` would be read
|
|
150
|
+
* through both of its branches while TypeScript reads the constraint above, as `never` fits any
|
|
151
|
+
* check type, so a generic `parseAs: "text"` gave the JSON body too.
|
|
152
|
+
*/
|
|
153
|
+
type ParseAsKey<Options> = [MissingParseAsValues<Options>] extends [never]
|
|
154
|
+
? "json"
|
|
155
|
+
: ParseAsValue<ParseAsValues<Options>>;
|
|
156
|
+
|
|
157
|
+
/**
|
|
158
|
+
* The `parseAs` values that `Options` may have; `undefined` when it may be left out. One
|
|
159
|
+
* conditional type, not a union: `BodyType<T>` indexed by a union of generic keys is read as the
|
|
160
|
+
* intersection of their bodies when another type is checked against it. `any` options give `any`,
|
|
161
|
+
* so their data is `any`, as in openapi-fetch: a caller that passes an `any` init (as the generic
|
|
162
|
+
* hooks of openapi-typesafe-react-query do) returns it as the data type it declares. The branch
|
|
163
|
+
* is `any` itself, not `Options`: a type parameter there stops TypeScript relating the data of a
|
|
164
|
+
* generic call to a wrapper's (ADR-030).
|
|
165
|
+
*/
|
|
166
|
+
type ParseAsValues<Options> = Options extends unknown
|
|
167
|
+
? IsAny<Options> extends true
|
|
168
|
+
? // biome-ignore lint/suspicious/noExplicitAny: any options give any data, as in openapi-fetch
|
|
169
|
+
any
|
|
170
|
+
:
|
|
171
|
+
| (Options extends RequiredParseAs ? never : undefined)
|
|
172
|
+
| { [Value in ParseAs]: Options extends WithoutParseAs<Value> ? never : Value }[ParseAs]
|
|
173
|
+
: never;
|
|
174
|
+
|
|
175
|
+
/**
|
|
176
|
+
* The `parseAs` values that the options `Options` (one member of a union) cannot have, as
|
|
177
|
+
* `ParseAsValues` reads them.
|
|
178
|
+
*/
|
|
179
|
+
type MissingParseAsValues<Options> =
|
|
180
|
+
| ([Options] extends [RequiredParseAs] ? undefined : never)
|
|
181
|
+
| { [Value in ParseAs]: [Options] extends [WithoutParseAs<Value>] ? Value : never }[ParseAs];
|
|
182
|
+
|
|
183
|
+
/** Options that always have a `parseAs`. */
|
|
184
|
+
type RequiredParseAs = { parseAs: ParseAs };
|
|
185
|
+
|
|
186
|
+
/**
|
|
187
|
+
* Options that cannot have the `parseAs` value `Value`: `undefined` options, or options whose
|
|
188
|
+
* `parseAs` (if any) leaves it out. Each
|
|
189
|
+
* check takes `Options` as its checked type and a fixed type here, so TypeScript relates the
|
|
190
|
+
* data of a generic call to a wrapper's (ADR-030); one union of these types instead would let
|
|
191
|
+
* TypeScript split `parseAs?: ParseAs` into its values and match each one. The `object` keeps
|
|
192
|
+
* options without `parseAs` (`{ headers }`) from failing as a weak type.
|
|
193
|
+
*/
|
|
194
|
+
type WithoutParseAs<Value extends ParseAs> =
|
|
195
|
+
| undefined
|
|
196
|
+
| (object & { parseAs?: Exclude<ParseAs, Value> | undefined });
|
|
142
197
|
|
|
143
198
|
/** How a `parseAs` value reads the body, for each member of a union; `undefined` reads JSON. */
|
|
144
199
|
type ParseAsValue<Value> = Value extends ParseAs ? Value : "json";
|
|
@@ -178,10 +233,16 @@ type RequestBodyOption<T> =
|
|
|
178
233
|
* gave a conditional type no `FetchOptions` argument was assignable to (TS2345). The operations
|
|
179
234
|
* of a union are not merged into one deep init type, which gave TS2589 on hundreds of paths
|
|
180
235
|
* (upstream #1882).
|
|
236
|
+
* Only the options that depend on the operation distribute; the others stay outside the
|
|
237
|
+
* conditional type. While `request()` has not fixed its method, TypeScript reads the init's
|
|
238
|
+
* contextual type through the constraints of the method and the path, and stops after ten nested
|
|
239
|
+
* instantiations of one conditional type: with every option inside it, `parseAs: "text"` had no
|
|
240
|
+
* contextual type, widened to `string`, and the init fell back to its constraint (`data` the JSON
|
|
241
|
+
* body). `FetchOptions<never>` is still `never`.
|
|
181
242
|
*/
|
|
182
|
-
type FetchOptions<T> = T extends unknown
|
|
183
|
-
|
|
184
|
-
|
|
243
|
+
type FetchOptions<T> = (T extends unknown ? OperationRequestOptions<T> : never) &
|
|
244
|
+
SharedRequestOptions &
|
|
245
|
+
Omit<RequestInit, "body" | "headers">;
|
|
185
246
|
|
|
186
247
|
// Readable<T> strips $Write markers (writeOnly properties excluded from response)
|
|
187
248
|
type FetchResponse<
|
|
@@ -201,18 +262,25 @@ type FetchResponse<
|
|
|
201
262
|
response: Response;
|
|
202
263
|
};
|
|
203
264
|
|
|
204
|
-
type RequestOptions<T> =
|
|
265
|
+
type RequestOptions<T> = OperationRequestOptions<T> & SharedRequestOptions;
|
|
266
|
+
|
|
267
|
+
/** The options of a call that depend on its operation `T`. */
|
|
268
|
+
type OperationRequestOptions<T> = ParamsOption<T> &
|
|
205
269
|
RequestBodyOption<T> & {
|
|
206
|
-
baseUrl?: string | undefined;
|
|
207
270
|
querySerializer?: QuerySerializer<T> | QuerySerializerOptions | undefined;
|
|
208
271
|
bodySerializer?: BodySerializer<T> | undefined;
|
|
209
|
-
pathSerializer?: PathSerializer | undefined;
|
|
210
|
-
parseAs?: ParseAs | undefined;
|
|
211
|
-
fetch?: ClientOptions["fetch"];
|
|
212
|
-
headers?: HeadersOptions | undefined;
|
|
213
|
-
middleware?: Middleware[] | undefined;
|
|
214
272
|
};
|
|
215
273
|
|
|
274
|
+
/** The options of a call that are the same for every operation. */
|
|
275
|
+
type SharedRequestOptions = {
|
|
276
|
+
baseUrl?: string | undefined;
|
|
277
|
+
pathSerializer?: PathSerializer | undefined;
|
|
278
|
+
parseAs?: ParseAs | undefined;
|
|
279
|
+
fetch?: ClientOptions["fetch"];
|
|
280
|
+
headers?: HeadersOptions | undefined;
|
|
281
|
+
middleware?: Middleware[] | undefined;
|
|
282
|
+
};
|
|
283
|
+
|
|
216
284
|
type MergedOptions<T = unknown> = {
|
|
217
285
|
baseUrl: string;
|
|
218
286
|
parseAs: ParseAs;
|
package/package.json
CHANGED
|
@@ -1,7 +1,7 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "openapi-typesafe-fetch",
|
|
3
3
|
"description": "Fast, type-safe fetch client for your OpenAPI schema. About 10.5 kB minified, 4 kB gzipped. Works with React, Vue, Svelte, or vanilla JS.",
|
|
4
|
-
"version": "1.0.0-rc.
|
|
4
|
+
"version": "1.0.0-rc.12",
|
|
5
5
|
"author": {
|
|
6
6
|
"name": "Fedor Chishchin",
|
|
7
7
|
"url": "https://github.com/fedorello"
|
|
@@ -67,7 +67,7 @@
|
|
|
67
67
|
"express": "5.3.0",
|
|
68
68
|
"feature-fetch": "0.1.2",
|
|
69
69
|
"node-forge": "1.4.3",
|
|
70
|
-
"openapi-typesafe": "^1.0.0-rc.
|
|
70
|
+
"openapi-typesafe": "^1.0.0-rc.12",
|
|
71
71
|
"openapi-typescript-fetch": "2.2.1",
|
|
72
72
|
"typescript": "^6.0.3",
|
|
73
73
|
"undici": "7.30.2",
|
package/src/helpers.ts
CHANGED
|
@@ -394,17 +394,39 @@ type TextErrorMediaTypes<Content> = {
|
|
|
394
394
|
[Key in keyof Content]: IsJSONMediaPattern<Key> extends true ? never : Key;
|
|
395
395
|
}[keyof Content];
|
|
396
396
|
|
|
397
|
-
/**
|
|
397
|
+
/**
|
|
398
|
+
* Whether the client parses every body of the media type `Key` as JSON, in any letter case: its
|
|
399
|
+
* subtype is `json` or ends in `+json`, as `JSON_MEDIA_TYPE_RE` reads it. A `json` in a parameter
|
|
400
|
+
* (`text/plain; profile=urn:a+json`) does not count; `JSONMediaPattern` alone matched it.
|
|
401
|
+
*/
|
|
398
402
|
type IsJSONMediaPattern<Key> = Key extends string
|
|
399
|
-
? Lowercase<Key
|
|
403
|
+
? MediaSubtype<Lowercase<Key>> extends "json" | `${string}+json`
|
|
400
404
|
? true
|
|
401
405
|
: false
|
|
402
406
|
: false;
|
|
403
407
|
|
|
408
|
+
/**
|
|
409
|
+
* The subtype of a media type: the text after the first `/` and before the parameters (the first
|
|
410
|
+
* `;`), without the spaces and tabs at its end. Empty without a type before a `/`, as
|
|
411
|
+
* `JSON_MEDIA_TYPE_RE` wants one. A subtype may hold a `/`, as the regular expression allows.
|
|
412
|
+
*/
|
|
413
|
+
type MediaSubtype<Text extends string> = Text extends `${infer Type}/${infer Rest}`
|
|
414
|
+
? Type extends ""
|
|
415
|
+
? ""
|
|
416
|
+
: TrimmedEnd<Rest extends `${infer Subtype};${string}` ? Subtype : Rest>
|
|
417
|
+
: "";
|
|
418
|
+
|
|
419
|
+
/** The text without the spaces and tabs at its end (OWS before parameters, RFC 9110). */
|
|
420
|
+
type TrimmedEnd<Text extends string> = Text extends `${infer Rest} ` | `${infer Rest}\t`
|
|
421
|
+
? TrimmedEnd<Rest>
|
|
422
|
+
: Text;
|
|
423
|
+
|
|
404
424
|
/**
|
|
405
425
|
* The media types that the client parses as JSON, as a content map writes them: `json` or a
|
|
406
426
|
* `+json` suffix (RFC 6838), with or without parameters, as the client's `JSON_MEDIA_TYPE_RE`.
|
|
407
|
-
* Not `x-ndjson` or `json-seq`, whose bodies are lines of JSON.
|
|
427
|
+
* Not `x-ndjson` or `json-seq`, whose bodies are lines of JSON. A template cannot keep its
|
|
428
|
+
* `${string}` out of the parameters, so it only picks the schema of an index signature
|
|
429
|
+
* (`JSONMediaKey`); whether a media type is JSON is `IsJSONMediaPattern`'s answer.
|
|
408
430
|
*/
|
|
409
431
|
type JSONMediaPattern =
|
|
410
432
|
| `${string}/${"" | `${string}+`}json`
|
|
@@ -419,26 +441,59 @@ type JSONMediaRange = `${string}/*` | `${string}/*${";" | " " | "\t"}${string}`;
|
|
|
419
441
|
/** The media types that may hold JSON, in lower case. */
|
|
420
442
|
type JSONMediaType = JSONMediaPattern | JSONMediaRange;
|
|
421
443
|
|
|
422
|
-
/**
|
|
444
|
+
/**
|
|
445
|
+
* Whether a body of the media type `Key` may be one the client parses as JSON, in any letter
|
|
446
|
+
* case: a JSON media type (`IsJSONMediaPattern`) or a media range (subtype `*`), with or without
|
|
447
|
+
* parameters, which do not count.
|
|
448
|
+
*/
|
|
423
449
|
type IsJSONMediaType<Key> = Key extends string
|
|
424
|
-
?
|
|
450
|
+
? IsJSONMediaPattern<Key> extends true
|
|
425
451
|
? true
|
|
426
|
-
:
|
|
452
|
+
: MediaSubtype<Lowercase<Key>> extends "*"
|
|
453
|
+
? true
|
|
454
|
+
: false
|
|
427
455
|
: false;
|
|
428
456
|
|
|
429
|
-
/**
|
|
457
|
+
/**
|
|
458
|
+
* Return first JSON-like 2XX response from a path + HTTP method; for a union of operations, the
|
|
459
|
+
* union of each one's.
|
|
460
|
+
*/
|
|
430
461
|
// biome-ignore lint/suspicious/noExplicitAny: unknown rejects the generated interfaces
|
|
431
|
-
export type SuccessResponseJSON<PathMethod extends Record<string | number, any>> =
|
|
432
|
-
|
|
433
|
-
|
|
434
|
-
|
|
462
|
+
export type SuccessResponseJSON<PathMethod extends Record<string | number, any>> =
|
|
463
|
+
PathMethod extends unknown
|
|
464
|
+
? SuccessResponse<ResponseObjectMap<PathMethod>, JSONMediaKeys<ResponseObjectMap<PathMethod>>>
|
|
465
|
+
: never;
|
|
435
466
|
|
|
436
|
-
/**
|
|
467
|
+
/**
|
|
468
|
+
* Return JSON-like 1XX, 3XX, 4XX, 5XX and `default` responses from a path + HTTP method; for a
|
|
469
|
+
* union of operations, the union of each one's.
|
|
470
|
+
*/
|
|
437
471
|
// biome-ignore lint/suspicious/noExplicitAny: unknown rejects the generated interfaces
|
|
438
|
-
export type ErrorResponseJSON<PathMethod extends Record<string | number, any>> =
|
|
439
|
-
|
|
440
|
-
|
|
441
|
-
|
|
472
|
+
export type ErrorResponseJSON<PathMethod extends Record<string | number, any>> =
|
|
473
|
+
PathMethod extends unknown
|
|
474
|
+
? ErrorResponse<ResponseObjectMap<PathMethod>, JSONMediaKeys<ResponseObjectMap<PathMethod>>>
|
|
475
|
+
: never;
|
|
476
|
+
|
|
477
|
+
/**
|
|
478
|
+
* The media types of the responses `T` (of one operation) that may hold JSON (`IsJSONMediaType`),
|
|
479
|
+
* for the JSON helpers to pick by: their keys, as `JSONMediaType` cannot leave out a `json` in
|
|
480
|
+
* parameters. A content map with an index signature has no such key, so it gives
|
|
481
|
+
* `JSONMediaType`, which picks the schema of its signature. The helpers pick per operation: the
|
|
482
|
+
* keys of a union of response maps are only the status codes they share, so the media types of
|
|
483
|
+
* the other statuses were lost, and the media types of one operation could pick in another.
|
|
484
|
+
*/
|
|
485
|
+
type JSONMediaKeys<T> = {
|
|
486
|
+
[Status in keyof T]: T[Status] extends { content: infer Content }
|
|
487
|
+
? { [Key in keyof Content]: JSONMediaKey<Key> }[keyof Content]
|
|
488
|
+
: never;
|
|
489
|
+
}[keyof T];
|
|
490
|
+
|
|
491
|
+
/** `Key` when it may hold JSON; `JSONMediaType` for the wide key of an index signature. */
|
|
492
|
+
type JSONMediaKey<Key> = string extends Key
|
|
493
|
+
? JSONMediaType
|
|
494
|
+
: IsJSONMediaType<Key> extends true
|
|
495
|
+
? Key
|
|
496
|
+
: never;
|
|
442
497
|
|
|
443
498
|
/** Return JSON-like request body from a path + HTTP method */
|
|
444
499
|
export type RequestBodyJSON<PathMethod> = JSONLike<
|
package/src/index.d.ts
CHANGED
|
@@ -139,16 +139,71 @@ type BodyType<T = unknown> = {
|
|
|
139
139
|
};
|
|
140
140
|
export type ParseAs = keyof BodyType;
|
|
141
141
|
/**
|
|
142
|
-
* The data of a success body `T` read with the `parseAs` of `Options`. Each value
|
|
143
|
-
* its body, and `undefined` is the default `"json"`, as at runtime: a
|
|
144
|
-
* `parseAs
|
|
145
|
-
* declared init type, as for a call without
|
|
146
|
-
*
|
|
147
|
-
*
|
|
142
|
+
* The data of a success body `T` read with the `parseAs` of `Options`. Each value it may have gives
|
|
143
|
+
* its body, and `undefined` or a left-out `parseAs` is the default `"json"`, as at runtime: a
|
|
144
|
+
* wrapper that passes on `parseAs?: "text"` gets `string | T`, not `T` only. Options without
|
|
145
|
+
* `parseAs`, or whose `parseAs` may be any value (the declared init type, as for a call without
|
|
146
|
+
* options), give `T`, as in openapi-fetch; each member of a union of options gives its own data.
|
|
147
|
+
* No `infer`: TypeScript relates a type to a deferred conditional type through both of its
|
|
148
|
+
* branches only when it infers nothing, and generic hooks return the data of a call as this type.
|
|
149
|
+
* It distributes over `Options`, as openapi-fetch's does, so the options of a generic call (a
|
|
150
|
+
* wrapper's `FetchOptions` of a generic path with `parseAs: "text"`) give the data of their
|
|
151
|
+
* constraint: TypeScript reads a deferred distributive type through its check type's constraint.
|
|
148
152
|
*/
|
|
149
|
-
export type ParseAsResponse<T, Options> = Options extends
|
|
150
|
-
? BodyType<T>[
|
|
151
|
-
:
|
|
153
|
+
export type ParseAsResponse<T, Options> = Options extends unknown
|
|
154
|
+
? BodyType<T>[ParseAsKey<Options>]
|
|
155
|
+
: never;
|
|
156
|
+
|
|
157
|
+
/**
|
|
158
|
+
* The key of `BodyType` that the options `Options` read the body with. A conditional type of its
|
|
159
|
+
* own, inside the index: one in the branch of `ParseAsResponse` that picked `T` would be read
|
|
160
|
+
* through both of its branches while TypeScript reads the constraint above, as `never` fits any
|
|
161
|
+
* check type, so a generic `parseAs: "text"` gave the JSON body too.
|
|
162
|
+
*/
|
|
163
|
+
type ParseAsKey<Options> = [MissingParseAsValues<Options>] extends [never]
|
|
164
|
+
? "json"
|
|
165
|
+
: ParseAsValue<ParseAsValues<Options>>;
|
|
166
|
+
|
|
167
|
+
/**
|
|
168
|
+
* The `parseAs` values that `Options` may have; `undefined` when it may be left out. One
|
|
169
|
+
* conditional type, not a union: `BodyType<T>` indexed by a union of generic keys is read as the
|
|
170
|
+
* intersection of their bodies when another type is checked against it. `any` options give `any`,
|
|
171
|
+
* so their data is `any`, as in openapi-fetch: a caller that passes an `any` init (as the generic
|
|
172
|
+
* hooks of openapi-typesafe-react-query do) returns it as the data type it declares. The branch
|
|
173
|
+
* is `any` itself, not `Options`: a type parameter there stops TypeScript relating the data of a
|
|
174
|
+
* generic call to a wrapper's (ADR-030).
|
|
175
|
+
*/
|
|
176
|
+
type ParseAsValues<Options> = Options extends unknown
|
|
177
|
+
? IsAny<Options> extends true
|
|
178
|
+
? // biome-ignore lint/suspicious/noExplicitAny: any options give any data, as in openapi-fetch
|
|
179
|
+
any
|
|
180
|
+
:
|
|
181
|
+
| (Options extends RequiredParseAs ? never : undefined)
|
|
182
|
+
| { [Value in ParseAs]: Options extends WithoutParseAs<Value> ? never : Value }[ParseAs]
|
|
183
|
+
: never;
|
|
184
|
+
|
|
185
|
+
/**
|
|
186
|
+
* The `parseAs` values that the options `Options` (one member of a union) cannot have, as
|
|
187
|
+
* `ParseAsValues` reads them.
|
|
188
|
+
*/
|
|
189
|
+
type MissingParseAsValues<Options> =
|
|
190
|
+
| ([Options] extends [RequiredParseAs] ? undefined : never)
|
|
191
|
+
| { [Value in ParseAs]: [Options] extends [WithoutParseAs<Value>] ? Value : never }[ParseAs];
|
|
192
|
+
|
|
193
|
+
/** Options that always have a `parseAs`. */
|
|
194
|
+
type RequiredParseAs = { parseAs: ParseAs };
|
|
195
|
+
|
|
196
|
+
/**
|
|
197
|
+
* Options that cannot have the `parseAs` value `Value`: `undefined` options, or options whose
|
|
198
|
+
* `parseAs` (if any) leaves it out. Each
|
|
199
|
+
* check takes `Options` as its checked type and a fixed type here, so TypeScript relates the
|
|
200
|
+
* data of a generic call to a wrapper's (ADR-030); one union of these types instead would let
|
|
201
|
+
* TypeScript split `parseAs?: ParseAs` into its values and match each one. The `object` keeps
|
|
202
|
+
* options without `parseAs` (`{ headers }`) from failing as a weak type.
|
|
203
|
+
*/
|
|
204
|
+
type WithoutParseAs<Value extends ParseAs> =
|
|
205
|
+
| undefined
|
|
206
|
+
| (object & { parseAs?: Exclude<ParseAs, Value> | undefined });
|
|
152
207
|
|
|
153
208
|
/** How a `parseAs` value reads the body, for each member of a union; `undefined` reads JSON. */
|
|
154
209
|
type ParseAsValue<Value> = Value extends ParseAs ? Value : "json";
|
|
@@ -188,10 +243,16 @@ export type RequestBodyOption<T> =
|
|
|
188
243
|
* gave a conditional type no `FetchOptions` argument was assignable to (TS2345). The operations
|
|
189
244
|
* of a union are not merged into one deep init type, which gave TS2589 on hundreds of paths
|
|
190
245
|
* (upstream #1882).
|
|
246
|
+
* Only the options that depend on the operation distribute; the others stay outside the
|
|
247
|
+
* conditional type. While `request()` has not fixed its method, TypeScript reads the init's
|
|
248
|
+
* contextual type through the constraints of the method and the path, and stops after ten nested
|
|
249
|
+
* instantiations of one conditional type: with every option inside it, `parseAs: "text"` had no
|
|
250
|
+
* contextual type, widened to `string`, and the init fell back to its constraint (`data` the JSON
|
|
251
|
+
* body). `FetchOptions<never>` is still `never`.
|
|
191
252
|
*/
|
|
192
|
-
export type FetchOptions<T> = T extends unknown
|
|
193
|
-
|
|
194
|
-
|
|
253
|
+
export type FetchOptions<T> = (T extends unknown ? OperationRequestOptions<T> : never) &
|
|
254
|
+
SharedRequestOptions &
|
|
255
|
+
Omit<RequestInit, "body" | "headers">;
|
|
195
256
|
|
|
196
257
|
// Readable<T> strips $Write markers (writeOnly properties excluded from response)
|
|
197
258
|
export type FetchResponse<
|
|
@@ -211,18 +272,25 @@ export type FetchResponse<
|
|
|
211
272
|
response: Response;
|
|
212
273
|
};
|
|
213
274
|
|
|
214
|
-
export type RequestOptions<T> =
|
|
275
|
+
export type RequestOptions<T> = OperationRequestOptions<T> & SharedRequestOptions;
|
|
276
|
+
|
|
277
|
+
/** The options of a call that depend on its operation `T`. */
|
|
278
|
+
type OperationRequestOptions<T> = ParamsOption<T> &
|
|
215
279
|
RequestBodyOption<T> & {
|
|
216
|
-
baseUrl?: string | undefined;
|
|
217
280
|
querySerializer?: QuerySerializer<T> | QuerySerializerOptions | undefined;
|
|
218
281
|
bodySerializer?: BodySerializer<T> | undefined;
|
|
219
|
-
pathSerializer?: PathSerializer | undefined;
|
|
220
|
-
parseAs?: ParseAs | undefined;
|
|
221
|
-
fetch?: ClientOptions["fetch"];
|
|
222
|
-
headers?: HeadersOptions | undefined;
|
|
223
|
-
middleware?: Middleware[] | undefined;
|
|
224
282
|
};
|
|
225
283
|
|
|
284
|
+
/** The options of a call that are the same for every operation. */
|
|
285
|
+
type SharedRequestOptions = {
|
|
286
|
+
baseUrl?: string | undefined;
|
|
287
|
+
pathSerializer?: PathSerializer | undefined;
|
|
288
|
+
parseAs?: ParseAs | undefined;
|
|
289
|
+
fetch?: ClientOptions["fetch"];
|
|
290
|
+
headers?: HeadersOptions | undefined;
|
|
291
|
+
middleware?: Middleware[] | undefined;
|
|
292
|
+
};
|
|
293
|
+
|
|
226
294
|
export type MergedOptions<T = unknown> = {
|
|
227
295
|
baseUrl: string;
|
|
228
296
|
parseAs: ParseAs;
|