openapi-typesafe-fetch 1.0.0-rc.10 → 1.0.0-rc.11
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 +21 -0
- package/dist/helpers.d.cts +40 -7
- package/dist/helpers.d.mts +40 -7
- package/dist/helpers.d.ts +40 -7
- package/dist/index.d.cts +74 -19
- package/dist/index.d.mts +74 -19
- package/dist/index.d.ts +74 -19
- package/package.json +2 -2
- package/src/helpers.ts +55 -8
- package/src/index.d.ts +74 -19
package/CHANGELOG.md
CHANGED
|
@@ -2,6 +2,27 @@
|
|
|
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.11
|
|
6
|
+
|
|
7
|
+
### Patch Changes
|
|
8
|
+
|
|
9
|
+
- [#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,
|
|
10
|
+
as at runtime: an error declared as `text/plain; profile=urn:a+json` or
|
|
11
|
+
`message/http; msgtype=application/json` is typed as the text (`string`) the client returns, and
|
|
12
|
+
`JSONLike`, `RequestBodyJSON`, `SuccessResponseJSON` and `ErrorResponseJSON` leave such media
|
|
13
|
+
types out. Only the media type before the `;` counts, for media ranges (`text/*`) too.
|
|
14
|
+
|
|
15
|
+
- [#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" }`
|
|
16
|
+
passed to `client.GET()`, types `data` as the body of each value or the JSON body (`string | T`),
|
|
17
|
+
as the client returns it. Before, it was typed as the JSON body only. Options whose `parseAs` may
|
|
18
|
+
be any value, or that have none, still type `data` as the JSON body. With and without
|
|
19
|
+
`exactOptionalPropertyTypes`.
|
|
20
|
+
|
|
21
|
+
- [#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
|
|
22
|
+
other method calls do: `{ parseAs: "text" }` gives a `string`, `"stream"` the body stream. Before,
|
|
23
|
+
`data` was always the JSON body. A literal in `params` or `body` next to `parseAs`, such as an
|
|
24
|
+
enum value, no longer loses the `parseAs` type either.
|
|
25
|
+
|
|
5
26
|
## 1.0.0-rc.10
|
|
6
27
|
|
|
7
28
|
### 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,31 @@ 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
|
-
|
|
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;
|
|
292
310
|
/** Return first JSON-like 2XX response from a path + HTTP method */
|
|
293
|
-
type SuccessResponseJSON<PathMethod extends Record<string | number, any>> = SuccessResponse<ResponseObjectMap<PathMethod>,
|
|
311
|
+
type SuccessResponseJSON<PathMethod extends Record<string | number, any>> = SuccessResponse<ResponseObjectMap<PathMethod>, JSONMediaKeys<ResponseObjectMap<PathMethod>>>;
|
|
294
312
|
/** Return JSON-like 1XX, 3XX, 4XX, 5XX and `default` responses from a path + HTTP method */
|
|
295
|
-
type ErrorResponseJSON<PathMethod extends Record<string | number, any>> = ErrorResponse<ResponseObjectMap<PathMethod>,
|
|
313
|
+
type ErrorResponseJSON<PathMethod extends Record<string | number, any>> = ErrorResponse<ResponseObjectMap<PathMethod>, JSONMediaKeys<ResponseObjectMap<PathMethod>>>;
|
|
314
|
+
/**
|
|
315
|
+
* The media types of the responses `T` that may hold JSON (`IsJSONMediaType`), for the JSON
|
|
316
|
+
* helpers to pick by: their keys, as `JSONMediaType` cannot leave out a `json` in parameters. A
|
|
317
|
+
* content map with an index signature has no such key, so it gives `JSONMediaType`, which picks
|
|
318
|
+
* the schema of its signature.
|
|
319
|
+
*/
|
|
320
|
+
type JSONMediaKeys<T> = {
|
|
321
|
+
[Status in keyof T]: T[Status] extends {
|
|
322
|
+
content: infer Content;
|
|
323
|
+
} ? {
|
|
324
|
+
[Key in keyof Content]: JSONMediaKey<Key>;
|
|
325
|
+
}[keyof Content] : never;
|
|
326
|
+
}[keyof T];
|
|
327
|
+
/** `Key` when it may hold JSON; `JSONMediaType` for the wide key of an index signature. */
|
|
328
|
+
type JSONMediaKey<Key> = string extends Key ? JSONMediaType : IsJSONMediaType<Key> extends true ? Key : never;
|
|
296
329
|
/** Return JSON-like request body from a path + HTTP method */
|
|
297
330
|
type RequestBodyJSON<PathMethod> = JSONLike<FilterKeys<OperationRequestBody<PathMethod>, "content">>;
|
|
298
331
|
/** 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,31 @@ 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
|
-
|
|
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;
|
|
292
310
|
/** Return first JSON-like 2XX response from a path + HTTP method */
|
|
293
|
-
type SuccessResponseJSON<PathMethod extends Record<string | number, any>> = SuccessResponse<ResponseObjectMap<PathMethod>,
|
|
311
|
+
type SuccessResponseJSON<PathMethod extends Record<string | number, any>> = SuccessResponse<ResponseObjectMap<PathMethod>, JSONMediaKeys<ResponseObjectMap<PathMethod>>>;
|
|
294
312
|
/** Return JSON-like 1XX, 3XX, 4XX, 5XX and `default` responses from a path + HTTP method */
|
|
295
|
-
type ErrorResponseJSON<PathMethod extends Record<string | number, any>> = ErrorResponse<ResponseObjectMap<PathMethod>,
|
|
313
|
+
type ErrorResponseJSON<PathMethod extends Record<string | number, any>> = ErrorResponse<ResponseObjectMap<PathMethod>, JSONMediaKeys<ResponseObjectMap<PathMethod>>>;
|
|
314
|
+
/**
|
|
315
|
+
* The media types of the responses `T` that may hold JSON (`IsJSONMediaType`), for the JSON
|
|
316
|
+
* helpers to pick by: their keys, as `JSONMediaType` cannot leave out a `json` in parameters. A
|
|
317
|
+
* content map with an index signature has no such key, so it gives `JSONMediaType`, which picks
|
|
318
|
+
* the schema of its signature.
|
|
319
|
+
*/
|
|
320
|
+
type JSONMediaKeys<T> = {
|
|
321
|
+
[Status in keyof T]: T[Status] extends {
|
|
322
|
+
content: infer Content;
|
|
323
|
+
} ? {
|
|
324
|
+
[Key in keyof Content]: JSONMediaKey<Key>;
|
|
325
|
+
}[keyof Content] : never;
|
|
326
|
+
}[keyof T];
|
|
327
|
+
/** `Key` when it may hold JSON; `JSONMediaType` for the wide key of an index signature. */
|
|
328
|
+
type JSONMediaKey<Key> = string extends Key ? JSONMediaType : IsJSONMediaType<Key> extends true ? Key : never;
|
|
296
329
|
/** Return JSON-like request body from a path + HTTP method */
|
|
297
330
|
type RequestBodyJSON<PathMethod> = JSONLike<FilterKeys<OperationRequestBody<PathMethod>, "content">>;
|
|
298
331
|
/** 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,31 @@ 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
|
-
|
|
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;
|
|
292
310
|
/** Return first JSON-like 2XX response from a path + HTTP method */
|
|
293
|
-
type SuccessResponseJSON<PathMethod extends Record<string | number, any>> = SuccessResponse<ResponseObjectMap<PathMethod>,
|
|
311
|
+
type SuccessResponseJSON<PathMethod extends Record<string | number, any>> = SuccessResponse<ResponseObjectMap<PathMethod>, JSONMediaKeys<ResponseObjectMap<PathMethod>>>;
|
|
294
312
|
/** Return JSON-like 1XX, 3XX, 4XX, 5XX and `default` responses from a path + HTTP method */
|
|
295
|
-
type ErrorResponseJSON<PathMethod extends Record<string | number, any>> = ErrorResponse<ResponseObjectMap<PathMethod>,
|
|
313
|
+
type ErrorResponseJSON<PathMethod extends Record<string | number, any>> = ErrorResponse<ResponseObjectMap<PathMethod>, JSONMediaKeys<ResponseObjectMap<PathMethod>>>;
|
|
314
|
+
/**
|
|
315
|
+
* The media types of the responses `T` that may hold JSON (`IsJSONMediaType`), for the JSON
|
|
316
|
+
* helpers to pick by: their keys, as `JSONMediaType` cannot leave out a `json` in parameters. A
|
|
317
|
+
* content map with an index signature has no such key, so it gives `JSONMediaType`, which picks
|
|
318
|
+
* the schema of its signature.
|
|
319
|
+
*/
|
|
320
|
+
type JSONMediaKeys<T> = {
|
|
321
|
+
[Status in keyof T]: T[Status] extends {
|
|
322
|
+
content: infer Content;
|
|
323
|
+
} ? {
|
|
324
|
+
[Key in keyof Content]: JSONMediaKey<Key>;
|
|
325
|
+
}[keyof Content] : never;
|
|
326
|
+
}[keyof T];
|
|
327
|
+
/** `Key` when it may hold JSON; `JSONMediaType` for the wide key of an index signature. */
|
|
328
|
+
type JSONMediaKey<Key> = string extends Key ? JSONMediaType : IsJSONMediaType<Key> extends true ? Key : never;
|
|
296
329
|
/** Return JSON-like request body from a path + HTTP method */
|
|
297
330
|
type RequestBodyJSON<PathMethod> = JSONLike<FilterKeys<OperationRequestBody<PathMethod>, "content">>;
|
|
298
331
|
/** Find first match of multiple keys */
|
package/dist/index.d.cts
CHANGED
|
@@ -129,16 +129,58 @@ 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
|
-
* when it infers nothing, and generic hooks
|
|
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. No `infer`: TypeScript relates a type to a deferred
|
|
137
|
+
* conditional type through both of its branches only when it infers nothing, and generic hooks
|
|
138
|
+
* return the data of a call as this type.
|
|
138
139
|
*/
|
|
139
|
-
type ParseAsResponse<T, Options> = Options extends
|
|
140
|
-
?
|
|
141
|
-
: T;
|
|
140
|
+
type ParseAsResponse<T, Options> = [MissingParseAsValues<Options>] extends [never]
|
|
141
|
+
? T
|
|
142
|
+
: BodyType<T>[ParseAsValue<ParseAsValues<Options>>];
|
|
143
|
+
|
|
144
|
+
/**
|
|
145
|
+
* The `parseAs` values that `Options` may have; `undefined` when it may be left out. One
|
|
146
|
+
* conditional type, not a union: `BodyType<T>` indexed by a union of generic keys is read as the
|
|
147
|
+
* intersection of their bodies when another type is checked against it. `any` options give `any`,
|
|
148
|
+
* so their data is `any`, as in openapi-fetch: a caller that passes an `any` init (as the generic
|
|
149
|
+
* hooks of openapi-typesafe-react-query do) returns it as the data type it declares. The branch
|
|
150
|
+
* is `any` itself, not `Options`: a type parameter there stops TypeScript relating the data of a
|
|
151
|
+
* generic call to a wrapper's (ADR-030).
|
|
152
|
+
*/
|
|
153
|
+
type ParseAsValues<Options> = Options extends unknown
|
|
154
|
+
? IsAny<Options> extends true
|
|
155
|
+
? // biome-ignore lint/suspicious/noExplicitAny: any options give any data, as in openapi-fetch
|
|
156
|
+
any
|
|
157
|
+
:
|
|
158
|
+
| (Options extends RequiredParseAs ? never : undefined)
|
|
159
|
+
| { [Value in ParseAs]: Options extends WithoutParseAs<Value> ? never : Value }[ParseAs]
|
|
160
|
+
: never;
|
|
161
|
+
|
|
162
|
+
/**
|
|
163
|
+
* The `parseAs` values that `Options` cannot have, as `ParseAsValues` reads them. It does not
|
|
164
|
+
* distribute: a union of options cannot have a value only when none of its members can.
|
|
165
|
+
*/
|
|
166
|
+
type MissingParseAsValues<Options> =
|
|
167
|
+
| ([Options] extends [RequiredParseAs] ? undefined : never)
|
|
168
|
+
| { [Value in ParseAs]: [Options] extends [WithoutParseAs<Value>] ? Value : never }[ParseAs];
|
|
169
|
+
|
|
170
|
+
/** Options that always have a `parseAs`. */
|
|
171
|
+
type RequiredParseAs = { parseAs: ParseAs };
|
|
172
|
+
|
|
173
|
+
/**
|
|
174
|
+
* Options that cannot have the `parseAs` value `Value`: `undefined` options, or options whose
|
|
175
|
+
* `parseAs` (if any) leaves it out. Each
|
|
176
|
+
* check takes `Options` as its checked type and a fixed type here, so TypeScript relates the
|
|
177
|
+
* data of a generic call to a wrapper's (ADR-030); one union of these types instead would let
|
|
178
|
+
* TypeScript split `parseAs?: ParseAs` into its values and match each one. The `object` keeps
|
|
179
|
+
* options without `parseAs` (`{ headers }`) from failing as a weak type.
|
|
180
|
+
*/
|
|
181
|
+
type WithoutParseAs<Value extends ParseAs> =
|
|
182
|
+
| undefined
|
|
183
|
+
| (object & { parseAs?: Exclude<ParseAs, Value> | undefined });
|
|
142
184
|
|
|
143
185
|
/** How a `parseAs` value reads the body, for each member of a union; `undefined` reads JSON. */
|
|
144
186
|
type ParseAsValue<Value> = Value extends ParseAs ? Value : "json";
|
|
@@ -178,10 +220,16 @@ type RequestBodyOption<T> =
|
|
|
178
220
|
* gave a conditional type no `FetchOptions` argument was assignable to (TS2345). The operations
|
|
179
221
|
* of a union are not merged into one deep init type, which gave TS2589 on hundreds of paths
|
|
180
222
|
* (upstream #1882).
|
|
223
|
+
* Only the options that depend on the operation distribute; the others stay outside the
|
|
224
|
+
* conditional type. While `request()` has not fixed its method, TypeScript reads the init's
|
|
225
|
+
* contextual type through the constraints of the method and the path, and stops after ten nested
|
|
226
|
+
* instantiations of one conditional type: with every option inside it, `parseAs: "text"` had no
|
|
227
|
+
* contextual type, widened to `string`, and the init fell back to its constraint (`data` the JSON
|
|
228
|
+
* body). `FetchOptions<never>` is still `never`.
|
|
181
229
|
*/
|
|
182
|
-
type FetchOptions<T> = T extends unknown
|
|
183
|
-
|
|
184
|
-
|
|
230
|
+
type FetchOptions<T> = (T extends unknown ? OperationRequestOptions<T> : never) &
|
|
231
|
+
SharedRequestOptions &
|
|
232
|
+
Omit<RequestInit, "body" | "headers">;
|
|
185
233
|
|
|
186
234
|
// Readable<T> strips $Write markers (writeOnly properties excluded from response)
|
|
187
235
|
type FetchResponse<
|
|
@@ -201,18 +249,25 @@ type FetchResponse<
|
|
|
201
249
|
response: Response;
|
|
202
250
|
};
|
|
203
251
|
|
|
204
|
-
type RequestOptions<T> =
|
|
252
|
+
type RequestOptions<T> = OperationRequestOptions<T> & SharedRequestOptions;
|
|
253
|
+
|
|
254
|
+
/** The options of a call that depend on its operation `T`. */
|
|
255
|
+
type OperationRequestOptions<T> = ParamsOption<T> &
|
|
205
256
|
RequestBodyOption<T> & {
|
|
206
|
-
baseUrl?: string | undefined;
|
|
207
257
|
querySerializer?: QuerySerializer<T> | QuerySerializerOptions | undefined;
|
|
208
258
|
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
259
|
};
|
|
215
260
|
|
|
261
|
+
/** The options of a call that are the same for every operation. */
|
|
262
|
+
type SharedRequestOptions = {
|
|
263
|
+
baseUrl?: string | undefined;
|
|
264
|
+
pathSerializer?: PathSerializer | undefined;
|
|
265
|
+
parseAs?: ParseAs | undefined;
|
|
266
|
+
fetch?: ClientOptions["fetch"];
|
|
267
|
+
headers?: HeadersOptions | undefined;
|
|
268
|
+
middleware?: Middleware[] | undefined;
|
|
269
|
+
};
|
|
270
|
+
|
|
216
271
|
type MergedOptions<T = unknown> = {
|
|
217
272
|
baseUrl: string;
|
|
218
273
|
parseAs: ParseAs;
|
package/dist/index.d.mts
CHANGED
|
@@ -129,16 +129,58 @@ 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
|
-
* when it infers nothing, and generic hooks
|
|
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. No `infer`: TypeScript relates a type to a deferred
|
|
137
|
+
* conditional type through both of its branches only when it infers nothing, and generic hooks
|
|
138
|
+
* return the data of a call as this type.
|
|
138
139
|
*/
|
|
139
|
-
type ParseAsResponse<T, Options> = Options extends
|
|
140
|
-
?
|
|
141
|
-
: T;
|
|
140
|
+
type ParseAsResponse<T, Options> = [MissingParseAsValues<Options>] extends [never]
|
|
141
|
+
? T
|
|
142
|
+
: BodyType<T>[ParseAsValue<ParseAsValues<Options>>];
|
|
143
|
+
|
|
144
|
+
/**
|
|
145
|
+
* The `parseAs` values that `Options` may have; `undefined` when it may be left out. One
|
|
146
|
+
* conditional type, not a union: `BodyType<T>` indexed by a union of generic keys is read as the
|
|
147
|
+
* intersection of their bodies when another type is checked against it. `any` options give `any`,
|
|
148
|
+
* so their data is `any`, as in openapi-fetch: a caller that passes an `any` init (as the generic
|
|
149
|
+
* hooks of openapi-typesafe-react-query do) returns it as the data type it declares. The branch
|
|
150
|
+
* is `any` itself, not `Options`: a type parameter there stops TypeScript relating the data of a
|
|
151
|
+
* generic call to a wrapper's (ADR-030).
|
|
152
|
+
*/
|
|
153
|
+
type ParseAsValues<Options> = Options extends unknown
|
|
154
|
+
? IsAny<Options> extends true
|
|
155
|
+
? // biome-ignore lint/suspicious/noExplicitAny: any options give any data, as in openapi-fetch
|
|
156
|
+
any
|
|
157
|
+
:
|
|
158
|
+
| (Options extends RequiredParseAs ? never : undefined)
|
|
159
|
+
| { [Value in ParseAs]: Options extends WithoutParseAs<Value> ? never : Value }[ParseAs]
|
|
160
|
+
: never;
|
|
161
|
+
|
|
162
|
+
/**
|
|
163
|
+
* The `parseAs` values that `Options` cannot have, as `ParseAsValues` reads them. It does not
|
|
164
|
+
* distribute: a union of options cannot have a value only when none of its members can.
|
|
165
|
+
*/
|
|
166
|
+
type MissingParseAsValues<Options> =
|
|
167
|
+
| ([Options] extends [RequiredParseAs] ? undefined : never)
|
|
168
|
+
| { [Value in ParseAs]: [Options] extends [WithoutParseAs<Value>] ? Value : never }[ParseAs];
|
|
169
|
+
|
|
170
|
+
/** Options that always have a `parseAs`. */
|
|
171
|
+
type RequiredParseAs = { parseAs: ParseAs };
|
|
172
|
+
|
|
173
|
+
/**
|
|
174
|
+
* Options that cannot have the `parseAs` value `Value`: `undefined` options, or options whose
|
|
175
|
+
* `parseAs` (if any) leaves it out. Each
|
|
176
|
+
* check takes `Options` as its checked type and a fixed type here, so TypeScript relates the
|
|
177
|
+
* data of a generic call to a wrapper's (ADR-030); one union of these types instead would let
|
|
178
|
+
* TypeScript split `parseAs?: ParseAs` into its values and match each one. The `object` keeps
|
|
179
|
+
* options without `parseAs` (`{ headers }`) from failing as a weak type.
|
|
180
|
+
*/
|
|
181
|
+
type WithoutParseAs<Value extends ParseAs> =
|
|
182
|
+
| undefined
|
|
183
|
+
| (object & { parseAs?: Exclude<ParseAs, Value> | undefined });
|
|
142
184
|
|
|
143
185
|
/** How a `parseAs` value reads the body, for each member of a union; `undefined` reads JSON. */
|
|
144
186
|
type ParseAsValue<Value> = Value extends ParseAs ? Value : "json";
|
|
@@ -178,10 +220,16 @@ type RequestBodyOption<T> =
|
|
|
178
220
|
* gave a conditional type no `FetchOptions` argument was assignable to (TS2345). The operations
|
|
179
221
|
* of a union are not merged into one deep init type, which gave TS2589 on hundreds of paths
|
|
180
222
|
* (upstream #1882).
|
|
223
|
+
* Only the options that depend on the operation distribute; the others stay outside the
|
|
224
|
+
* conditional type. While `request()` has not fixed its method, TypeScript reads the init's
|
|
225
|
+
* contextual type through the constraints of the method and the path, and stops after ten nested
|
|
226
|
+
* instantiations of one conditional type: with every option inside it, `parseAs: "text"` had no
|
|
227
|
+
* contextual type, widened to `string`, and the init fell back to its constraint (`data` the JSON
|
|
228
|
+
* body). `FetchOptions<never>` is still `never`.
|
|
181
229
|
*/
|
|
182
|
-
type FetchOptions<T> = T extends unknown
|
|
183
|
-
|
|
184
|
-
|
|
230
|
+
type FetchOptions<T> = (T extends unknown ? OperationRequestOptions<T> : never) &
|
|
231
|
+
SharedRequestOptions &
|
|
232
|
+
Omit<RequestInit, "body" | "headers">;
|
|
185
233
|
|
|
186
234
|
// Readable<T> strips $Write markers (writeOnly properties excluded from response)
|
|
187
235
|
type FetchResponse<
|
|
@@ -201,18 +249,25 @@ type FetchResponse<
|
|
|
201
249
|
response: Response;
|
|
202
250
|
};
|
|
203
251
|
|
|
204
|
-
type RequestOptions<T> =
|
|
252
|
+
type RequestOptions<T> = OperationRequestOptions<T> & SharedRequestOptions;
|
|
253
|
+
|
|
254
|
+
/** The options of a call that depend on its operation `T`. */
|
|
255
|
+
type OperationRequestOptions<T> = ParamsOption<T> &
|
|
205
256
|
RequestBodyOption<T> & {
|
|
206
|
-
baseUrl?: string | undefined;
|
|
207
257
|
querySerializer?: QuerySerializer<T> | QuerySerializerOptions | undefined;
|
|
208
258
|
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
259
|
};
|
|
215
260
|
|
|
261
|
+
/** The options of a call that are the same for every operation. */
|
|
262
|
+
type SharedRequestOptions = {
|
|
263
|
+
baseUrl?: string | undefined;
|
|
264
|
+
pathSerializer?: PathSerializer | undefined;
|
|
265
|
+
parseAs?: ParseAs | undefined;
|
|
266
|
+
fetch?: ClientOptions["fetch"];
|
|
267
|
+
headers?: HeadersOptions | undefined;
|
|
268
|
+
middleware?: Middleware[] | undefined;
|
|
269
|
+
};
|
|
270
|
+
|
|
216
271
|
type MergedOptions<T = unknown> = {
|
|
217
272
|
baseUrl: string;
|
|
218
273
|
parseAs: ParseAs;
|
package/dist/index.d.ts
CHANGED
|
@@ -129,16 +129,58 @@ 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
|
-
* when it infers nothing, and generic hooks
|
|
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. No `infer`: TypeScript relates a type to a deferred
|
|
137
|
+
* conditional type through both of its branches only when it infers nothing, and generic hooks
|
|
138
|
+
* return the data of a call as this type.
|
|
138
139
|
*/
|
|
139
|
-
type ParseAsResponse<T, Options> = Options extends
|
|
140
|
-
?
|
|
141
|
-
: T;
|
|
140
|
+
type ParseAsResponse<T, Options> = [MissingParseAsValues<Options>] extends [never]
|
|
141
|
+
? T
|
|
142
|
+
: BodyType<T>[ParseAsValue<ParseAsValues<Options>>];
|
|
143
|
+
|
|
144
|
+
/**
|
|
145
|
+
* The `parseAs` values that `Options` may have; `undefined` when it may be left out. One
|
|
146
|
+
* conditional type, not a union: `BodyType<T>` indexed by a union of generic keys is read as the
|
|
147
|
+
* intersection of their bodies when another type is checked against it. `any` options give `any`,
|
|
148
|
+
* so their data is `any`, as in openapi-fetch: a caller that passes an `any` init (as the generic
|
|
149
|
+
* hooks of openapi-typesafe-react-query do) returns it as the data type it declares. The branch
|
|
150
|
+
* is `any` itself, not `Options`: a type parameter there stops TypeScript relating the data of a
|
|
151
|
+
* generic call to a wrapper's (ADR-030).
|
|
152
|
+
*/
|
|
153
|
+
type ParseAsValues<Options> = Options extends unknown
|
|
154
|
+
? IsAny<Options> extends true
|
|
155
|
+
? // biome-ignore lint/suspicious/noExplicitAny: any options give any data, as in openapi-fetch
|
|
156
|
+
any
|
|
157
|
+
:
|
|
158
|
+
| (Options extends RequiredParseAs ? never : undefined)
|
|
159
|
+
| { [Value in ParseAs]: Options extends WithoutParseAs<Value> ? never : Value }[ParseAs]
|
|
160
|
+
: never;
|
|
161
|
+
|
|
162
|
+
/**
|
|
163
|
+
* The `parseAs` values that `Options` cannot have, as `ParseAsValues` reads them. It does not
|
|
164
|
+
* distribute: a union of options cannot have a value only when none of its members can.
|
|
165
|
+
*/
|
|
166
|
+
type MissingParseAsValues<Options> =
|
|
167
|
+
| ([Options] extends [RequiredParseAs] ? undefined : never)
|
|
168
|
+
| { [Value in ParseAs]: [Options] extends [WithoutParseAs<Value>] ? Value : never }[ParseAs];
|
|
169
|
+
|
|
170
|
+
/** Options that always have a `parseAs`. */
|
|
171
|
+
type RequiredParseAs = { parseAs: ParseAs };
|
|
172
|
+
|
|
173
|
+
/**
|
|
174
|
+
* Options that cannot have the `parseAs` value `Value`: `undefined` options, or options whose
|
|
175
|
+
* `parseAs` (if any) leaves it out. Each
|
|
176
|
+
* check takes `Options` as its checked type and a fixed type here, so TypeScript relates the
|
|
177
|
+
* data of a generic call to a wrapper's (ADR-030); one union of these types instead would let
|
|
178
|
+
* TypeScript split `parseAs?: ParseAs` into its values and match each one. The `object` keeps
|
|
179
|
+
* options without `parseAs` (`{ headers }`) from failing as a weak type.
|
|
180
|
+
*/
|
|
181
|
+
type WithoutParseAs<Value extends ParseAs> =
|
|
182
|
+
| undefined
|
|
183
|
+
| (object & { parseAs?: Exclude<ParseAs, Value> | undefined });
|
|
142
184
|
|
|
143
185
|
/** How a `parseAs` value reads the body, for each member of a union; `undefined` reads JSON. */
|
|
144
186
|
type ParseAsValue<Value> = Value extends ParseAs ? Value : "json";
|
|
@@ -178,10 +220,16 @@ type RequestBodyOption<T> =
|
|
|
178
220
|
* gave a conditional type no `FetchOptions` argument was assignable to (TS2345). The operations
|
|
179
221
|
* of a union are not merged into one deep init type, which gave TS2589 on hundreds of paths
|
|
180
222
|
* (upstream #1882).
|
|
223
|
+
* Only the options that depend on the operation distribute; the others stay outside the
|
|
224
|
+
* conditional type. While `request()` has not fixed its method, TypeScript reads the init's
|
|
225
|
+
* contextual type through the constraints of the method and the path, and stops after ten nested
|
|
226
|
+
* instantiations of one conditional type: with every option inside it, `parseAs: "text"` had no
|
|
227
|
+
* contextual type, widened to `string`, and the init fell back to its constraint (`data` the JSON
|
|
228
|
+
* body). `FetchOptions<never>` is still `never`.
|
|
181
229
|
*/
|
|
182
|
-
type FetchOptions<T> = T extends unknown
|
|
183
|
-
|
|
184
|
-
|
|
230
|
+
type FetchOptions<T> = (T extends unknown ? OperationRequestOptions<T> : never) &
|
|
231
|
+
SharedRequestOptions &
|
|
232
|
+
Omit<RequestInit, "body" | "headers">;
|
|
185
233
|
|
|
186
234
|
// Readable<T> strips $Write markers (writeOnly properties excluded from response)
|
|
187
235
|
type FetchResponse<
|
|
@@ -201,18 +249,25 @@ type FetchResponse<
|
|
|
201
249
|
response: Response;
|
|
202
250
|
};
|
|
203
251
|
|
|
204
|
-
type RequestOptions<T> =
|
|
252
|
+
type RequestOptions<T> = OperationRequestOptions<T> & SharedRequestOptions;
|
|
253
|
+
|
|
254
|
+
/** The options of a call that depend on its operation `T`. */
|
|
255
|
+
type OperationRequestOptions<T> = ParamsOption<T> &
|
|
205
256
|
RequestBodyOption<T> & {
|
|
206
|
-
baseUrl?: string | undefined;
|
|
207
257
|
querySerializer?: QuerySerializer<T> | QuerySerializerOptions | undefined;
|
|
208
258
|
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
259
|
};
|
|
215
260
|
|
|
261
|
+
/** The options of a call that are the same for every operation. */
|
|
262
|
+
type SharedRequestOptions = {
|
|
263
|
+
baseUrl?: string | undefined;
|
|
264
|
+
pathSerializer?: PathSerializer | undefined;
|
|
265
|
+
parseAs?: ParseAs | undefined;
|
|
266
|
+
fetch?: ClientOptions["fetch"];
|
|
267
|
+
headers?: HeadersOptions | undefined;
|
|
268
|
+
middleware?: Middleware[] | undefined;
|
|
269
|
+
};
|
|
270
|
+
|
|
216
271
|
type MergedOptions<T = unknown> = {
|
|
217
272
|
baseUrl: string;
|
|
218
273
|
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.11",
|
|
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.11",
|
|
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,27 +441,52 @@ 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
|
/** Return first JSON-like 2XX response from a path + HTTP method */
|
|
430
458
|
// biome-ignore lint/suspicious/noExplicitAny: unknown rejects the generated interfaces
|
|
431
459
|
export type SuccessResponseJSON<PathMethod extends Record<string | number, any>> = SuccessResponse<
|
|
432
460
|
ResponseObjectMap<PathMethod>,
|
|
433
|
-
|
|
461
|
+
JSONMediaKeys<ResponseObjectMap<PathMethod>>
|
|
434
462
|
>;
|
|
435
463
|
|
|
436
464
|
/** Return JSON-like 1XX, 3XX, 4XX, 5XX and `default` responses from a path + HTTP method */
|
|
437
465
|
// biome-ignore lint/suspicious/noExplicitAny: unknown rejects the generated interfaces
|
|
438
466
|
export type ErrorResponseJSON<PathMethod extends Record<string | number, any>> = ErrorResponse<
|
|
439
467
|
ResponseObjectMap<PathMethod>,
|
|
440
|
-
|
|
468
|
+
JSONMediaKeys<ResponseObjectMap<PathMethod>>
|
|
441
469
|
>;
|
|
442
470
|
|
|
471
|
+
/**
|
|
472
|
+
* The media types of the responses `T` that may hold JSON (`IsJSONMediaType`), for the JSON
|
|
473
|
+
* helpers to pick by: their keys, as `JSONMediaType` cannot leave out a `json` in parameters. A
|
|
474
|
+
* content map with an index signature has no such key, so it gives `JSONMediaType`, which picks
|
|
475
|
+
* the schema of its signature.
|
|
476
|
+
*/
|
|
477
|
+
type JSONMediaKeys<T> = {
|
|
478
|
+
[Status in keyof T]: T[Status] extends { content: infer Content }
|
|
479
|
+
? { [Key in keyof Content]: JSONMediaKey<Key> }[keyof Content]
|
|
480
|
+
: never;
|
|
481
|
+
}[keyof T];
|
|
482
|
+
|
|
483
|
+
/** `Key` when it may hold JSON; `JSONMediaType` for the wide key of an index signature. */
|
|
484
|
+
type JSONMediaKey<Key> = string extends Key
|
|
485
|
+
? JSONMediaType
|
|
486
|
+
: IsJSONMediaType<Key> extends true
|
|
487
|
+
? Key
|
|
488
|
+
: never;
|
|
489
|
+
|
|
443
490
|
/** Return JSON-like request body from a path + HTTP method */
|
|
444
491
|
export type RequestBodyJSON<PathMethod> = JSONLike<
|
|
445
492
|
FilterKeys<OperationRequestBody<PathMethod>, "content">
|
package/src/index.d.ts
CHANGED
|
@@ -139,16 +139,58 @@ 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
|
-
* when it infers nothing, and generic hooks
|
|
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. No `infer`: TypeScript relates a type to a deferred
|
|
147
|
+
* conditional type through both of its branches only when it infers nothing, and generic hooks
|
|
148
|
+
* return the data of a call as this type.
|
|
148
149
|
*/
|
|
149
|
-
export type ParseAsResponse<T, Options> = Options extends
|
|
150
|
-
?
|
|
151
|
-
: T;
|
|
150
|
+
export type ParseAsResponse<T, Options> = [MissingParseAsValues<Options>] extends [never]
|
|
151
|
+
? T
|
|
152
|
+
: BodyType<T>[ParseAsValue<ParseAsValues<Options>>];
|
|
153
|
+
|
|
154
|
+
/**
|
|
155
|
+
* The `parseAs` values that `Options` may have; `undefined` when it may be left out. One
|
|
156
|
+
* conditional type, not a union: `BodyType<T>` indexed by a union of generic keys is read as the
|
|
157
|
+
* intersection of their bodies when another type is checked against it. `any` options give `any`,
|
|
158
|
+
* so their data is `any`, as in openapi-fetch: a caller that passes an `any` init (as the generic
|
|
159
|
+
* hooks of openapi-typesafe-react-query do) returns it as the data type it declares. The branch
|
|
160
|
+
* is `any` itself, not `Options`: a type parameter there stops TypeScript relating the data of a
|
|
161
|
+
* generic call to a wrapper's (ADR-030).
|
|
162
|
+
*/
|
|
163
|
+
type ParseAsValues<Options> = Options extends unknown
|
|
164
|
+
? IsAny<Options> extends true
|
|
165
|
+
? // biome-ignore lint/suspicious/noExplicitAny: any options give any data, as in openapi-fetch
|
|
166
|
+
any
|
|
167
|
+
:
|
|
168
|
+
| (Options extends RequiredParseAs ? never : undefined)
|
|
169
|
+
| { [Value in ParseAs]: Options extends WithoutParseAs<Value> ? never : Value }[ParseAs]
|
|
170
|
+
: never;
|
|
171
|
+
|
|
172
|
+
/**
|
|
173
|
+
* The `parseAs` values that `Options` cannot have, as `ParseAsValues` reads them. It does not
|
|
174
|
+
* distribute: a union of options cannot have a value only when none of its members can.
|
|
175
|
+
*/
|
|
176
|
+
type MissingParseAsValues<Options> =
|
|
177
|
+
| ([Options] extends [RequiredParseAs] ? undefined : never)
|
|
178
|
+
| { [Value in ParseAs]: [Options] extends [WithoutParseAs<Value>] ? Value : never }[ParseAs];
|
|
179
|
+
|
|
180
|
+
/** Options that always have a `parseAs`. */
|
|
181
|
+
type RequiredParseAs = { parseAs: ParseAs };
|
|
182
|
+
|
|
183
|
+
/**
|
|
184
|
+
* Options that cannot have the `parseAs` value `Value`: `undefined` options, or options whose
|
|
185
|
+
* `parseAs` (if any) leaves it out. Each
|
|
186
|
+
* check takes `Options` as its checked type and a fixed type here, so TypeScript relates the
|
|
187
|
+
* data of a generic call to a wrapper's (ADR-030); one union of these types instead would let
|
|
188
|
+
* TypeScript split `parseAs?: ParseAs` into its values and match each one. The `object` keeps
|
|
189
|
+
* options without `parseAs` (`{ headers }`) from failing as a weak type.
|
|
190
|
+
*/
|
|
191
|
+
type WithoutParseAs<Value extends ParseAs> =
|
|
192
|
+
| undefined
|
|
193
|
+
| (object & { parseAs?: Exclude<ParseAs, Value> | undefined });
|
|
152
194
|
|
|
153
195
|
/** How a `parseAs` value reads the body, for each member of a union; `undefined` reads JSON. */
|
|
154
196
|
type ParseAsValue<Value> = Value extends ParseAs ? Value : "json";
|
|
@@ -188,10 +230,16 @@ export type RequestBodyOption<T> =
|
|
|
188
230
|
* gave a conditional type no `FetchOptions` argument was assignable to (TS2345). The operations
|
|
189
231
|
* of a union are not merged into one deep init type, which gave TS2589 on hundreds of paths
|
|
190
232
|
* (upstream #1882).
|
|
233
|
+
* Only the options that depend on the operation distribute; the others stay outside the
|
|
234
|
+
* conditional type. While `request()` has not fixed its method, TypeScript reads the init's
|
|
235
|
+
* contextual type through the constraints of the method and the path, and stops after ten nested
|
|
236
|
+
* instantiations of one conditional type: with every option inside it, `parseAs: "text"` had no
|
|
237
|
+
* contextual type, widened to `string`, and the init fell back to its constraint (`data` the JSON
|
|
238
|
+
* body). `FetchOptions<never>` is still `never`.
|
|
191
239
|
*/
|
|
192
|
-
export type FetchOptions<T> = T extends unknown
|
|
193
|
-
|
|
194
|
-
|
|
240
|
+
export type FetchOptions<T> = (T extends unknown ? OperationRequestOptions<T> : never) &
|
|
241
|
+
SharedRequestOptions &
|
|
242
|
+
Omit<RequestInit, "body" | "headers">;
|
|
195
243
|
|
|
196
244
|
// Readable<T> strips $Write markers (writeOnly properties excluded from response)
|
|
197
245
|
export type FetchResponse<
|
|
@@ -211,18 +259,25 @@ export type FetchResponse<
|
|
|
211
259
|
response: Response;
|
|
212
260
|
};
|
|
213
261
|
|
|
214
|
-
export type RequestOptions<T> =
|
|
262
|
+
export type RequestOptions<T> = OperationRequestOptions<T> & SharedRequestOptions;
|
|
263
|
+
|
|
264
|
+
/** The options of a call that depend on its operation `T`. */
|
|
265
|
+
type OperationRequestOptions<T> = ParamsOption<T> &
|
|
215
266
|
RequestBodyOption<T> & {
|
|
216
|
-
baseUrl?: string | undefined;
|
|
217
267
|
querySerializer?: QuerySerializer<T> | QuerySerializerOptions | undefined;
|
|
218
268
|
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
269
|
};
|
|
225
270
|
|
|
271
|
+
/** The options of a call that are the same for every operation. */
|
|
272
|
+
type SharedRequestOptions = {
|
|
273
|
+
baseUrl?: string | undefined;
|
|
274
|
+
pathSerializer?: PathSerializer | undefined;
|
|
275
|
+
parseAs?: ParseAs | undefined;
|
|
276
|
+
fetch?: ClientOptions["fetch"];
|
|
277
|
+
headers?: HeadersOptions | undefined;
|
|
278
|
+
middleware?: Middleware[] | undefined;
|
|
279
|
+
};
|
|
280
|
+
|
|
226
281
|
export type MergedOptions<T = unknown> = {
|
|
227
282
|
baseUrl: string;
|
|
228
283
|
parseAs: ParseAs;
|