Skip to content

Commit db4e2ec

Browse files
jtomaszewskiclaude
andcommitted
fix(helpers): handle any type in RequiredKeysOf
Use standard TypeScript pattern `0 extends 1 & T` to detect `any` type instead of broken `extends any` check. Also fix type issue in openapi-react-query useInfiniteQuery that was exposed by this change. 🤖 Generated with [Claude Code](https://claude.com/claude-code) Co-Authored-By: Claude Opus 4.5 <[email protected]>
1 parent f06fb17 commit db4e2ec

File tree

3 files changed

+39
-4
lines changed

3 files changed

+39
-4
lines changed

packages/openapi-fetch/test/types.test.ts

Lines changed: 29 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -1,4 +1,10 @@
1-
import type { ErrorResponse, GetResponseContent, OkStatus, SuccessResponse } from "openapi-typescript-helpers";
1+
import type {
2+
ErrorResponse,
3+
GetResponseContent,
4+
OkStatus,
5+
RequiredKeysOf,
6+
SuccessResponse,
7+
} from "openapi-typescript-helpers";
28
import { assertType, describe, test } from "vitest";
39

410
describe("types", () => {
@@ -280,4 +286,26 @@ describe("types", () => {
280286
assertType<Response>({ error: "default application/json" });
281287
});
282288
});
289+
290+
describe("RequiredKeysOf", () => {
291+
test("returns never for any type", () => {
292+
assertType<RequiredKeysOf<any>>(undefined as never);
293+
});
294+
295+
test("returns required keys for objects with required properties", () => {
296+
interface WithRequired {
297+
required: string;
298+
optional?: number;
299+
}
300+
assertType<RequiredKeysOf<WithRequired>>("required" as const);
301+
});
302+
303+
test("returns never for objects with only optional properties", () => {
304+
interface AllOptional {
305+
optional1?: string;
306+
optional2?: number;
307+
}
308+
assertType<RequiredKeysOf<AllOptional>>(undefined as never);
309+
});
310+
});
283311
});

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

Lines changed: 3 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -229,13 +229,14 @@ export default function createClient<Paths extends {}, Media extends MediaType =
229229
queryFn: async ({ queryKey: [method, path, init], pageParam = 0, signal }) => {
230230
const mth = method.toUpperCase() as Uppercase<typeof method>;
231231
const fn = client[mth] as ClientMethod<Paths, typeof method, Media>;
232+
const initWithParams = init as { params?: { query?: DefaultParamsOption } } | undefined;
232233
const mergedInit = {
233234
...init,
234235
signal,
235236
params: {
236-
...(init?.params || {}),
237+
...(initWithParams?.params || {}),
237238
query: {
238-
...(init?.params as { query?: DefaultParamsOption })?.query,
239+
...(initWithParams?.params?.query || {}),
239240
[pageParamName]: pageParam,
240241
},
241242
},

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

Lines changed: 7 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -197,5 +197,11 @@ type RequiredKeysOfHelper<T> = {
197197
// biome-ignore lint/complexity/noBannedTypes: `{}` is necessary here
198198
[K in keyof T]: {} extends Pick<T, K> ? never : K;
199199
}[keyof T];
200+
/** Helper to detect `any` type (0 extends 1 & any is true, but 0 extends 1 & T is false for other types) */
201+
type IsAny<T> = 0 extends 1 & T ? true : false;
200202
/** Get the required keys of an object, or `never` if no keys are required */
201-
export type RequiredKeysOf<T> = RequiredKeysOfHelper<T> extends undefined ? never : RequiredKeysOfHelper<T>;
203+
export type RequiredKeysOf<T> = IsAny<T> extends true
204+
? never
205+
: RequiredKeysOfHelper<T> extends undefined
206+
? never
207+
: RequiredKeysOfHelper<T>;

0 commit comments

Comments
 (0)