openapi-typesafe-fetch 1.0.0-rc.5 → 1.0.0-rc.7
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 +23 -0
- package/README.md +2 -2
- package/dist/helpers.d.cts +8 -2
- package/dist/helpers.d.mts +8 -2
- package/dist/helpers.d.ts +8 -2
- package/dist/index.cjs +64 -34
- package/dist/index.cjs.map +1 -1
- package/dist/index.d.cts +1 -1
- package/dist/index.d.mts +1 -1
- package/dist/index.d.ts +1 -1
- package/dist/index.mjs +64 -34
- package/dist/index.mjs.map +1 -1
- package/package.json +2 -2
- package/src/helpers.ts +8 -4
- package/src/index.d.ts +1 -1
- package/src/index.js +99 -38
package/CHANGELOG.md
CHANGED
|
@@ -2,6 +2,29 @@
|
|
|
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.7
|
|
6
|
+
|
|
7
|
+
### Patch Changes
|
|
8
|
+
|
|
9
|
+
- [#96](https://github.com/openapi-typesafe/openapi-typesafe/pull/96) [`171fa71`](https://github.com/openapi-typesafe/openapi-typesafe/commit/171fa71311107e4e26a9ae7a62b431662fb13de5) Thanks [@fedorello](https://github.com/fedorello)! - `SuccessResponseJSON`, `ErrorResponseJSON` and the `data` and `error` of a client made with
|
|
10
|
+
``createClient<paths, `${string}/json`>()`` leave out a response that has no JSON media type. It
|
|
11
|
+
added its whole `content` map, so a `text/html` 500 typed the error as `{ "text/html": string }`,
|
|
12
|
+
a value the client never returns.
|
|
13
|
+
|
|
14
|
+
- [#96](https://github.com/openapi-typesafe/openapi-typesafe/pull/96) [`171fa71`](https://github.com/openapi-typesafe/openapi-typesafe/commit/171fa71311107e4e26a9ae7a62b431662fb13de5) Thanks [@fedorello](https://github.com/fedorello)! - A nested array or object in a path parameter (`{ id: [[1]] }`) now fails with an error that names
|
|
15
|
+
`pathSerializer()`, the option that sends such values. It told you to write a `querySerializer()`,
|
|
16
|
+
which does not apply to the path.
|
|
17
|
+
|
|
18
|
+
- [#96](https://github.com/openapi-typesafe/openapi-typesafe/pull/96) [`171fa71`](https://github.com/openapi-typesafe/openapi-typesafe/commit/171fa71311107e4e26a9ae7a62b431662fb13de5) Thanks [@fedorello](https://github.com/fedorello)! - A request's `querySerializer` option set to `undefined` (`{ array: { explode: undefined } }`, as a
|
|
19
|
+
wrapper passes its own optional option on) now takes the client's value, as an option left out
|
|
20
|
+
does. It replaced the client's value, so the default style was sent.
|
|
21
|
+
|
|
22
|
+
## 1.0.0-rc.6
|
|
23
|
+
|
|
24
|
+
### Patch Changes
|
|
25
|
+
|
|
26
|
+
- [#87](https://github.com/openapi-typesafe/openapi-typesafe/pull/87) [`dfb0fe3`](https://github.com/openapi-typesafe/openapi-typesafe/commit/dfb0fe3c61a75d2b8ca512577f4b699049171a64) Thanks [@fedorello](https://github.com/fedorello)! - Leave out `null` and `undefined` items and members of arrays and objects in every parameter style, also in the path and headers (`ids=1,null`, `filter=owner,undefined` and `x-p: a,,b,1` were sent before), and default `explode` to `true` for the `form` query style only, as OpenAPI does, so `{ array: { style: "pipeDelimited" } }` sends `id=3|4|5`. A nested value in a header parameter now fails with an error that names header parameters, not `querySerializer()`.
|
|
27
|
+
|
|
5
28
|
## 1.0.0-rc.5
|
|
6
29
|
|
|
7
30
|
### Minor Changes
|
package/README.md
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
# openapi-typesafe-fetch
|
|
2
2
|
|
|
3
|
-
A type-safe `fetch` client for types generated by [`openapi-typesafe`](../openapi-typesafe). About
|
|
3
|
+
A type-safe `fetch` client for types generated by [`openapi-typesafe`](../openapi-typesafe). About 11 kB minified
|
|
4
4
|
(4 kB gzipped), no runtime dependencies, works in browsers, Node.js, Bun and Deno, and with React, Vue, Svelte or plain JavaScript.
|
|
5
5
|
|
|
6
6
|
A maintained, drop-in successor to `openapi-fetch` (see the [repository README](../../README.md)).
|
|
@@ -26,7 +26,7 @@ const client = createClient<paths>({ baseUrl: "https://api.example.com/v1/" });
|
|
|
26
26
|
|
|
27
27
|
const {
|
|
28
28
|
data, // present on a 2xx response
|
|
29
|
-
error, // present on a
|
|
29
|
+
error, // present on a response that is not 2xx
|
|
30
30
|
} = await client.GET("/posts/{post_id}", {
|
|
31
31
|
params: { path: { post_id: "123" } },
|
|
32
32
|
});
|
package/dist/helpers.d.cts
CHANGED
|
@@ -206,8 +206,14 @@ type SuccessStatus<T> = [SuccessStatusKey<T>] extends [never] ? Extract<keyof T,
|
|
|
206
206
|
* share its statuses, and a `default`-only path typed the data `undefined` (upstream).
|
|
207
207
|
*/
|
|
208
208
|
type SuccessResponse<T extends Record<string | number, any>, Media extends MediaType = MediaType> = T extends unknown ? GetResponseContent<T, Media, SuccessStatus<T>> : never;
|
|
209
|
+
/**
|
|
210
|
+
* The bodies of the responses `ResponseCode` in the media types `Media`. A response with a
|
|
211
|
+
* `content` map but no media type in `Media` adds nothing: it gave its whole map, so the error of
|
|
212
|
+
* `ErrorResponseJSON` for a `text/html` 500 was `{ "text/html": string }`, a body no response has.
|
|
213
|
+
* A response without `content` keeps its `content` type (`undefined` for `content?: never`).
|
|
214
|
+
*/
|
|
209
215
|
type GetResponseContent<T extends Record<string | number, any>, Media extends MediaType = MediaType, ResponseCode extends keyof T = keyof T> = ResponseCode extends keyof T ? {
|
|
210
|
-
[K in ResponseCode]: T[K]["content"] extends Record<string, any> ? FilterKeys<T[K]["content"], Media>
|
|
216
|
+
[K in ResponseCode]: T[K]["content"] extends Record<string, any> ? FilterKeys<T[K]["content"], Media> : K extends keyof T ? T[K]["content"] : never;
|
|
211
217
|
}[ResponseCode] : never;
|
|
212
218
|
/**
|
|
213
219
|
* Return all 1XX, 3XX, 4XX, 5XX and `default` responses from a Response Object Map; for a union
|
|
@@ -216,7 +222,7 @@ type GetResponseContent<T extends Record<string | number, any>, Media extends Me
|
|
|
216
222
|
type ErrorResponse<T extends Record<string | number, any>, Media extends MediaType = MediaType> = T extends unknown ? GetResponseContent<T, Media, ErrorStatusKey<T>> : never;
|
|
217
223
|
/** Return first JSON-like 2XX response from a path + HTTP method */
|
|
218
224
|
type SuccessResponseJSON<PathMethod extends Record<string | number, any>> = SuccessResponse<ResponseObjectMap<PathMethod>, `${string}/json`>;
|
|
219
|
-
/** Return
|
|
225
|
+
/** Return JSON-like 1XX, 3XX, 4XX, 5XX and `default` responses from a path + HTTP method */
|
|
220
226
|
type ErrorResponseJSON<PathMethod extends Record<string | number, any>> = ErrorResponse<ResponseObjectMap<PathMethod>, `${string}/json`>;
|
|
221
227
|
/** Return JSON-like request body from a path + HTTP method */
|
|
222
228
|
type RequestBodyJSON<PathMethod> = JSONLike<FilterKeys<OperationRequestBody<PathMethod>, "content">>;
|
package/dist/helpers.d.mts
CHANGED
|
@@ -206,8 +206,14 @@ type SuccessStatus<T> = [SuccessStatusKey<T>] extends [never] ? Extract<keyof T,
|
|
|
206
206
|
* share its statuses, and a `default`-only path typed the data `undefined` (upstream).
|
|
207
207
|
*/
|
|
208
208
|
type SuccessResponse<T extends Record<string | number, any>, Media extends MediaType = MediaType> = T extends unknown ? GetResponseContent<T, Media, SuccessStatus<T>> : never;
|
|
209
|
+
/**
|
|
210
|
+
* The bodies of the responses `ResponseCode` in the media types `Media`. A response with a
|
|
211
|
+
* `content` map but no media type in `Media` adds nothing: it gave its whole map, so the error of
|
|
212
|
+
* `ErrorResponseJSON` for a `text/html` 500 was `{ "text/html": string }`, a body no response has.
|
|
213
|
+
* A response without `content` keeps its `content` type (`undefined` for `content?: never`).
|
|
214
|
+
*/
|
|
209
215
|
type GetResponseContent<T extends Record<string | number, any>, Media extends MediaType = MediaType, ResponseCode extends keyof T = keyof T> = ResponseCode extends keyof T ? {
|
|
210
|
-
[K in ResponseCode]: T[K]["content"] extends Record<string, any> ? FilterKeys<T[K]["content"], Media>
|
|
216
|
+
[K in ResponseCode]: T[K]["content"] extends Record<string, any> ? FilterKeys<T[K]["content"], Media> : K extends keyof T ? T[K]["content"] : never;
|
|
211
217
|
}[ResponseCode] : never;
|
|
212
218
|
/**
|
|
213
219
|
* Return all 1XX, 3XX, 4XX, 5XX and `default` responses from a Response Object Map; for a union
|
|
@@ -216,7 +222,7 @@ type GetResponseContent<T extends Record<string | number, any>, Media extends Me
|
|
|
216
222
|
type ErrorResponse<T extends Record<string | number, any>, Media extends MediaType = MediaType> = T extends unknown ? GetResponseContent<T, Media, ErrorStatusKey<T>> : never;
|
|
217
223
|
/** Return first JSON-like 2XX response from a path + HTTP method */
|
|
218
224
|
type SuccessResponseJSON<PathMethod extends Record<string | number, any>> = SuccessResponse<ResponseObjectMap<PathMethod>, `${string}/json`>;
|
|
219
|
-
/** Return
|
|
225
|
+
/** Return JSON-like 1XX, 3XX, 4XX, 5XX and `default` responses from a path + HTTP method */
|
|
220
226
|
type ErrorResponseJSON<PathMethod extends Record<string | number, any>> = ErrorResponse<ResponseObjectMap<PathMethod>, `${string}/json`>;
|
|
221
227
|
/** Return JSON-like request body from a path + HTTP method */
|
|
222
228
|
type RequestBodyJSON<PathMethod> = JSONLike<FilterKeys<OperationRequestBody<PathMethod>, "content">>;
|
package/dist/helpers.d.ts
CHANGED
|
@@ -206,8 +206,14 @@ type SuccessStatus<T> = [SuccessStatusKey<T>] extends [never] ? Extract<keyof T,
|
|
|
206
206
|
* share its statuses, and a `default`-only path typed the data `undefined` (upstream).
|
|
207
207
|
*/
|
|
208
208
|
type SuccessResponse<T extends Record<string | number, any>, Media extends MediaType = MediaType> = T extends unknown ? GetResponseContent<T, Media, SuccessStatus<T>> : never;
|
|
209
|
+
/**
|
|
210
|
+
* The bodies of the responses `ResponseCode` in the media types `Media`. A response with a
|
|
211
|
+
* `content` map but no media type in `Media` adds nothing: it gave its whole map, so the error of
|
|
212
|
+
* `ErrorResponseJSON` for a `text/html` 500 was `{ "text/html": string }`, a body no response has.
|
|
213
|
+
* A response without `content` keeps its `content` type (`undefined` for `content?: never`).
|
|
214
|
+
*/
|
|
209
215
|
type GetResponseContent<T extends Record<string | number, any>, Media extends MediaType = MediaType, ResponseCode extends keyof T = keyof T> = ResponseCode extends keyof T ? {
|
|
210
|
-
[K in ResponseCode]: T[K]["content"] extends Record<string, any> ? FilterKeys<T[K]["content"], Media>
|
|
216
|
+
[K in ResponseCode]: T[K]["content"] extends Record<string, any> ? FilterKeys<T[K]["content"], Media> : K extends keyof T ? T[K]["content"] : never;
|
|
211
217
|
}[ResponseCode] : never;
|
|
212
218
|
/**
|
|
213
219
|
* Return all 1XX, 3XX, 4XX, 5XX and `default` responses from a Response Object Map; for a union
|
|
@@ -216,7 +222,7 @@ type GetResponseContent<T extends Record<string | number, any>, Media extends Me
|
|
|
216
222
|
type ErrorResponse<T extends Record<string | number, any>, Media extends MediaType = MediaType> = T extends unknown ? GetResponseContent<T, Media, ErrorStatusKey<T>> : never;
|
|
217
223
|
/** Return first JSON-like 2XX response from a path + HTTP method */
|
|
218
224
|
type SuccessResponseJSON<PathMethod extends Record<string | number, any>> = SuccessResponse<ResponseObjectMap<PathMethod>, `${string}/json`>;
|
|
219
|
-
/** Return
|
|
225
|
+
/** Return JSON-like 1XX, 3XX, 4XX, 5XX and `default` responses from a path + HTTP method */
|
|
220
226
|
type ErrorResponseJSON<PathMethod extends Record<string | number, any>> = ErrorResponse<ResponseObjectMap<PathMethod>, `${string}/json`>;
|
|
221
227
|
/** Return JSON-like request body from a path + HTTP method */
|
|
222
228
|
type RequestBodyJSON<PathMethod> = JSONLike<FilterKeys<OperationRequestBody<PathMethod>, "content">>;
|
package/dist/index.cjs
CHANGED
|
@@ -415,6 +415,7 @@ function headerParamValue(value) {
|
|
|
415
415
|
if (value === null || typeof value !== "object" || isDate(value)) {
|
|
416
416
|
return valueText(value);
|
|
417
417
|
}
|
|
418
|
+
checkFlatMembers(value, HEADER_NESTED_VALUE_ERROR);
|
|
418
419
|
const serialize = Array.isArray(value) ? serializeArrayParam : serializeObjectParam;
|
|
419
420
|
return serialize("", value, HEADER_PARAM_STYLE) || void 0;
|
|
420
421
|
}
|
|
@@ -425,11 +426,23 @@ function serializeHeaderParams(header) {
|
|
|
425
426
|
const entries = Object.entries(header).map(([name, value]) => [name, headerParamValue(value)]);
|
|
426
427
|
return Object.fromEntries(entries);
|
|
427
428
|
}
|
|
429
|
+
const NESTED_VALUE_ERROR = "Deeply-nested arrays/objects aren\u2019t supported";
|
|
430
|
+
const HEADER_NESTED_VALUE_ERROR = `${NESTED_VALUE_ERROR} in header parameters.`;
|
|
431
|
+
const PATH_HOOK_HINT = "Provide your own `pathSerializer()` to handle these.";
|
|
432
|
+
const PATH_NESTED_VALUE_ERROR = `${NESTED_VALUE_ERROR} in path parameters. ${PATH_HOOK_HINT}`;
|
|
433
|
+
const QUERY_HOOK_HINT = "Provide your own `querySerializer()` to handle these.";
|
|
434
|
+
const QUERY_NESTED_VALUE_ERROR = `${NESTED_VALUE_ERROR}. ${QUERY_HOOK_HINT}`;
|
|
435
|
+
function isNestedValue(value) {
|
|
436
|
+
return Boolean(value) && typeof value === "object" && !isDate(value);
|
|
437
|
+
}
|
|
438
|
+
function checkFlatMembers(value, message) {
|
|
439
|
+
if (Object.values(value).some((member) => isNestedValue(member))) {
|
|
440
|
+
throw new Error(message);
|
|
441
|
+
}
|
|
442
|
+
}
|
|
428
443
|
function encodeValue(value, allowReserved) {
|
|
429
|
-
if (
|
|
430
|
-
throw new Error(
|
|
431
|
-
"Deeply-nested arrays/objects aren\u2019t supported. Provide your own `querySerializer()` to handle these."
|
|
432
|
-
);
|
|
444
|
+
if (isNestedValue(value)) {
|
|
445
|
+
throw new Error(QUERY_NESTED_VALUE_ERROR);
|
|
433
446
|
}
|
|
434
447
|
const text = valueText(value);
|
|
435
448
|
return allowReserved === true ? text : encodeURIComponent(text);
|
|
@@ -448,10 +461,15 @@ function joinExploded(values, style) {
|
|
|
448
461
|
function serializeUnexplodedObject(name, value, options) {
|
|
449
462
|
const values = [];
|
|
450
463
|
for (const k in value) {
|
|
451
|
-
|
|
452
|
-
|
|
453
|
-
|
|
454
|
-
|
|
464
|
+
if (value[k] != null) {
|
|
465
|
+
values.push(
|
|
466
|
+
encodeValue(k, options.allowReserved),
|
|
467
|
+
encodeValue(value[k], options.allowReserved)
|
|
468
|
+
);
|
|
469
|
+
}
|
|
470
|
+
}
|
|
471
|
+
if (!values.length) {
|
|
472
|
+
return "";
|
|
455
473
|
}
|
|
456
474
|
const final = values.join(",");
|
|
457
475
|
switch (options.style) {
|
|
@@ -488,7 +506,11 @@ function serializeObjectParam(name, value, options) {
|
|
|
488
506
|
}
|
|
489
507
|
function serializeUnexplodedArray(name, value, options) {
|
|
490
508
|
const joiner = { form: ",", spaceDelimited: "%20", pipeDelimited: "|" }[options.style] || ",";
|
|
491
|
-
const
|
|
509
|
+
const items = value.filter((v) => v != null);
|
|
510
|
+
if (!items.length) {
|
|
511
|
+
return "";
|
|
512
|
+
}
|
|
513
|
+
const final = items.map((v) => encodeValue(v, options.allowReserved)).join(joiner);
|
|
492
514
|
switch (options.style) {
|
|
493
515
|
case "simple": {
|
|
494
516
|
return final;
|
|
@@ -513,29 +535,29 @@ function serializeArrayParam(name, value, options) {
|
|
|
513
535
|
}
|
|
514
536
|
const values = [];
|
|
515
537
|
for (const v of value) {
|
|
538
|
+
if (v == null) {
|
|
539
|
+
continue;
|
|
540
|
+
}
|
|
516
541
|
if (options.style === "simple" || options.style === "label") {
|
|
517
542
|
values.push(encodeValue(v, options.allowReserved));
|
|
518
|
-
} else
|
|
543
|
+
} else {
|
|
519
544
|
values.push(serializePrimitiveParam(name, v, options));
|
|
520
545
|
}
|
|
521
546
|
}
|
|
522
547
|
return values.length ? joinExploded(values, options.style) : "";
|
|
523
548
|
}
|
|
549
|
+
function queryStyle(options, defaultStyle, allowReserved) {
|
|
550
|
+
const style = options?.style ?? defaultStyle;
|
|
551
|
+
return { style, explode: options?.explode ?? style === "form", allowReserved };
|
|
552
|
+
}
|
|
524
553
|
function serializeQueryParam(name, value, options) {
|
|
525
554
|
const allowReserved = options?.allowReserved || false;
|
|
526
555
|
if (Array.isArray(value)) {
|
|
527
|
-
return serializeArrayParam(name, value,
|
|
528
|
-
style: options?.array?.style ?? "form",
|
|
529
|
-
explode: options?.array?.explode ?? true,
|
|
530
|
-
allowReserved
|
|
531
|
-
});
|
|
556
|
+
return serializeArrayParam(name, value, queryStyle(options?.array, "form", allowReserved));
|
|
532
557
|
}
|
|
533
558
|
if (value && typeof value === "object" && !isDate(value)) {
|
|
534
|
-
|
|
535
|
-
|
|
536
|
-
explode: options?.object?.explode ?? true,
|
|
537
|
-
allowReserved
|
|
538
|
-
});
|
|
559
|
+
const style = queryStyle(options?.object, "deepObject", allowReserved);
|
|
560
|
+
return serializeObjectParam(name, value, style);
|
|
539
561
|
}
|
|
540
562
|
return serializePrimitiveParam(name, value, options);
|
|
541
563
|
}
|
|
@@ -557,11 +579,15 @@ function mergeQueryOptions(clientOptions, requestOptions) {
|
|
|
557
579
|
const base = typeof clientOptions === "function" ? {} : clientOptions ?? {};
|
|
558
580
|
return {
|
|
559
581
|
...base,
|
|
560
|
-
...requestOptions,
|
|
561
|
-
array: { ...base.array, ...requestOptions.array },
|
|
562
|
-
object: { ...base.object, ...requestOptions.object }
|
|
582
|
+
...definedOptions(requestOptions),
|
|
583
|
+
array: { ...base.array, ...definedOptions(requestOptions.array) },
|
|
584
|
+
object: { ...base.object, ...definedOptions(requestOptions.object) }
|
|
563
585
|
};
|
|
564
586
|
}
|
|
587
|
+
function definedOptions(options) {
|
|
588
|
+
const entries = Object.entries(options ?? {}).filter(([, value]) => value !== void 0);
|
|
589
|
+
return Object.fromEntries(entries);
|
|
590
|
+
}
|
|
565
591
|
function defaultPathSerializer(pathname, pathParams) {
|
|
566
592
|
const matches = pathname.match(PATH_PARAM_RE);
|
|
567
593
|
if (!matches) {
|
|
@@ -586,22 +612,26 @@ function defaultPathSerializer(pathname, pathParams) {
|
|
|
586
612
|
if (!pathParams || pathParams[name] === void 0 || pathParams[name] === null) {
|
|
587
613
|
continue;
|
|
588
614
|
}
|
|
589
|
-
const
|
|
590
|
-
let serialized;
|
|
591
|
-
if (Array.isArray(value)) {
|
|
592
|
-
serialized = serializeArrayParam(name, value, { style, explode });
|
|
593
|
-
} else if (typeof value === "object" && !isDate(value)) {
|
|
594
|
-
serialized = serializeObjectParam(name, value, { style, explode });
|
|
595
|
-
} else if (style === "matrix") {
|
|
596
|
-
serialized = `;${serializePrimitiveParam(name, value)}`;
|
|
597
|
-
} else {
|
|
598
|
-
serialized = style === "label" ? `.${encodeValue(value)}` : encodeValue(value);
|
|
599
|
-
}
|
|
615
|
+
const serialized = serializePathParam(name, pathParams[name], { style, explode });
|
|
600
616
|
nextURL = nextURL.replace(match, () => serialized);
|
|
601
617
|
}
|
|
602
618
|
checkSerializedPath(pathname, nextURL);
|
|
603
619
|
return nextURL;
|
|
604
620
|
}
|
|
621
|
+
function serializePathParam(name, value, options) {
|
|
622
|
+
if (Array.isArray(value)) {
|
|
623
|
+
checkFlatMembers(value, PATH_NESTED_VALUE_ERROR);
|
|
624
|
+
return serializeArrayParam(name, value, options);
|
|
625
|
+
}
|
|
626
|
+
if (typeof value === "object" && !isDate(value)) {
|
|
627
|
+
checkFlatMembers(value, PATH_NESTED_VALUE_ERROR);
|
|
628
|
+
return serializeObjectParam(name, value, options);
|
|
629
|
+
}
|
|
630
|
+
if (options.style === "matrix") {
|
|
631
|
+
return `;${serializePrimitiveParam(name, value)}`;
|
|
632
|
+
}
|
|
633
|
+
return options.style === "label" ? `.${encodeValue(value)}` : encodeValue(value);
|
|
634
|
+
}
|
|
605
635
|
function checkSerializedPath(template, url) {
|
|
606
636
|
const fixed = template.slice(0, template.search(PATH_PARAM_RE));
|
|
607
637
|
const path = url.split(QUERY_OR_FRAGMENT_RE, 1)[0];
|