Skip to content

Commit 6b2a908

Browse files
committed
fix identification of required properties when strictNullChecks is disabled
# Conflicts: # packages/openapi-fetch/src/index.d.ts
1 parent f66be91 commit 6b2a908

File tree

4 files changed

+42
-13
lines changed

4 files changed

+42
-13
lines changed

.changeset/lazy-dancers-push.md

+7
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,7 @@
1+
---
2+
"openapi-typescript-helpers": patch
3+
"openapi-react-query": patch
4+
"openapi-fetch": patch
5+
---
6+
7+
Fix identification of required properties when `strictNullChecks` is disabled

packages/openapi-fetch/src/index.d.ts

+6-5
Original file line numberDiff line numberDiff line change
@@ -1,12 +1,13 @@
11
import type {
22
ErrorResponse,
33
FilterKeys,
4-
HasRequiredKeys,
54
HttpMethod,
5+
IsOperationRequestBodyOptional,
66
MediaType,
77
OperationRequestBodyContent,
88
PathsWithMethod,
99
ResponseObjectMap,
10+
RequiredKeysOf,
1011
SuccessResponse,
1112
} from "openapi-typescript-helpers";
1213

@@ -82,14 +83,14 @@ export interface DefaultParamsOption {
8283
export type ParamsOption<T> = T extends {
8384
parameters: any;
8485
}
85-
? HasRequiredKeys<T["parameters"]> extends never
86+
? RequiredKeysOf<T["parameters"]> extends never
8687
? { params?: T["parameters"] }
8788
: { params: T["parameters"] }
8889
: DefaultParamsOption;
8990

9091
export type RequestBodyOption<T> = OperationRequestBodyContent<T> extends never
9192
? { body?: never }
92-
: undefined extends OperationRequestBodyContent<T>
93+
: IsOperationRequestBodyOptional<T> extends true
9394
? { body?: OperationRequestBodyContent<T> }
9495
: { body: OperationRequestBodyContent<T> };
9596

@@ -150,7 +151,7 @@ export interface Middleware {
150151
}
151152

152153
/** This type helper makes the 2nd function param required if params/requestBody are required; otherwise, optional */
153-
export type MaybeOptionalInit<Params extends Record<HttpMethod, {}>, Location extends keyof Params> = HasRequiredKeys<
154+
export type MaybeOptionalInit<Params extends Record<HttpMethod, {}>, Location extends keyof Params> = RequiredKeysOf<
154155
FetchOptions<FilterKeys<Params, Location>>
155156
> extends never
156157
? FetchOptions<FilterKeys<Params, Location>> | undefined
@@ -160,7 +161,7 @@ export type MaybeOptionalInit<Params extends Record<HttpMethod, {}>, Location ex
160161
// - Determines if the param is optional or not.
161162
// - Performs arbitrary [key: string] addition.
162163
// Note: the addition It MUST happen after all the inference happens (otherwise TS can’t infer if init is required or not).
163-
type InitParam<Init> = HasRequiredKeys<Init> extends never
164+
type InitParam<Init> = RequiredKeysOf<Init> extends never
164165
? [(Init & { [key: string]: unknown })?]
165166
: [Init & { [key: string]: unknown }];
166167

packages/openapi-react-query/src/index.ts

+3-3
Original file line numberDiff line numberDiff line change
@@ -11,7 +11,7 @@ import {
1111
useSuspenseQuery,
1212
} from "@tanstack/react-query";
1313
import type { ClientMethod, FetchResponse, MaybeOptionalInit, Client as FetchClient } from "openapi-fetch";
14-
import type { HasRequiredKeys, HttpMethod, MediaType, PathsWithMethod } from "openapi-typescript-helpers";
14+
import type { HttpMethod, MediaType, PathsWithMethod, RequiredKeysOf } from "openapi-typescript-helpers";
1515

1616
export type UseQueryMethod<Paths extends Record<string, Record<HttpMethod, {}>>, Media extends MediaType> = <
1717
Method extends HttpMethod,
@@ -22,7 +22,7 @@ export type UseQueryMethod<Paths extends Record<string, Record<HttpMethod, {}>>,
2222
>(
2323
method: Method,
2424
url: Path,
25-
...[init, options, queryClient]: HasRequiredKeys<Init> extends never
25+
...[init, options, queryClient]: RequiredKeysOf<Init> extends never
2626
? [(Init & { [key: string]: unknown })?, Options?, QueryClient?]
2727
: [Init & { [key: string]: unknown }, Options?, QueryClient?]
2828
) => UseQueryResult<Response["data"], Response["error"]>;
@@ -36,7 +36,7 @@ export type UseSuspenseQueryMethod<Paths extends Record<string, Record<HttpMetho
3636
>(
3737
method: Method,
3838
url: Path,
39-
...[init, options, queryClient]: HasRequiredKeys<Init> extends never
39+
...[init, options, queryClient]: RequiredKeysOf<Init> extends never
4040
? [(Init & { [key: string]: unknown })?, Options?, QueryClient?]
4141
: [Init & { [key: string]: unknown }, Options?, QueryClient?]
4242
) => UseSuspenseQueryResult<Response["data"], Response["error"]>;

packages/openapi-typescript-helpers/index.d.ts

+26-5
Original file line numberDiff line numberDiff line change
@@ -97,11 +97,17 @@ export type ResponseObjectMap<T> = T extends { responses: any } ? T["responses"]
9797
/** Return `content` for a Response Object */
9898
export type ResponseContent<T> = T extends { content: any } ? T["content"] : unknown;
9999

100-
/** Return `requestBody` for an Operation Object */
101-
export type OperationRequestBody<T> = T extends { requestBody?: any } ? T["requestBody"] : never;
100+
/** Return type of `requestBody` for an Operation Object */
101+
export type OperationRequestBody<T> = "requestBody" extends keyof T ? T["requestBody"] : never;
102+
103+
/** Internal helper to get object type with only the `requestBody` property */
104+
type PickRequestBody<T> = "requestBody" extends keyof T ? Pick<T, "requestBody"> : never;
105+
106+
/** Resolve to `true` if request body is optional, else `false` */
107+
export type IsOperationRequestBodyOptional<T> = RequiredKeysOf<PickRequestBody<T>> extends never ? true : false;
102108

103109
/** Internal helper used in OperationRequestBodyContent */
104-
export type OperationRequestBodyMediaContent<T> = undefined extends OperationRequestBody<T>
110+
export type OperationRequestBodyMediaContent<T> = IsOperationRequestBodyOptional<T> extends true
105111
? ResponseContent<NonNullable<OperationRequestBody<T>>> | undefined
106112
: ResponseContent<OperationRequestBody<T>>;
107113

@@ -152,7 +158,22 @@ export type GetValueWithDefault<Obj, KeyPattern, Default> = Obj extends any
152158
export type MediaType = `${string}/${string}`;
153159
/** Return any media type containing "json" (works for "application/json", "application/vnd.api+json", "application/vnd.oai.openapi+json") */
154160
export type JSONLike<T> = FilterKeys<T, `${string}/json`>;
155-
/** Filter objects that have required keys */
161+
162+
/**
163+
* Filter objects that have required keys
164+
* @deprecated Use `RequiredKeysOf` instead
165+
*/
156166
export type FindRequiredKeys<T, K extends keyof T> = K extends unknown ? (undefined extends T[K] ? never : K) : K;
157-
/** Does this object contain required keys? */
167+
/**
168+
* Does this object contain required keys?
169+
* @deprecated Use `RequiredKeysOf` instead
170+
*/
158171
export type HasRequiredKeys<T> = FindRequiredKeys<T, keyof T>;
172+
173+
/** Helper to get the required keys of an object. If no keys are required, will be `undefined` with strictNullChecks enabled, else `never` */
174+
type RequiredKeysOfHelper<T> = {
175+
// biome-ignore lint/complexity/noBannedTypes: `{}` is necessary here
176+
[K in keyof T]: {} extends Pick<T, K> ? never : K;
177+
}[keyof T];
178+
/** Get the required keys of an object, or `never` if no keys are required */
179+
export type RequiredKeysOf<T> = RequiredKeysOfHelper<T> extends undefined ? never : RequiredKeysOfHelper<T>;

0 commit comments

Comments
 (0)