From db4e2ecfe713323e0045b65e16853122ae7eaa11 Mon Sep 17 00:00:00 2001 From: Jacek Tomaszewski Date: Thu, 20 Nov 2025 20:15:50 +0100 Subject: [PATCH] fix(helpers): handle any type in RequiredKeysOf MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit 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 --- packages/openapi-fetch/test/types.test.ts | 30 ++++++++++++++++++- packages/openapi-react-query/src/index.ts | 5 ++-- .../openapi-typescript-helpers/src/index.ts | 8 ++++- 3 files changed, 39 insertions(+), 4 deletions(-) diff --git a/packages/openapi-fetch/test/types.test.ts b/packages/openapi-fetch/test/types.test.ts index 102099ea5..7539a68fe 100644 --- a/packages/openapi-fetch/test/types.test.ts +++ b/packages/openapi-fetch/test/types.test.ts @@ -1,4 +1,10 @@ -import type { ErrorResponse, GetResponseContent, OkStatus, SuccessResponse } from "openapi-typescript-helpers"; +import type { + ErrorResponse, + GetResponseContent, + OkStatus, + RequiredKeysOf, + SuccessResponse, +} from "openapi-typescript-helpers"; import { assertType, describe, test } from "vitest"; describe("types", () => { @@ -280,4 +286,26 @@ describe("types", () => { assertType({ error: "default application/json" }); }); }); + + describe("RequiredKeysOf", () => { + test("returns never for any type", () => { + assertType>(undefined as never); + }); + + test("returns required keys for objects with required properties", () => { + interface WithRequired { + required: string; + optional?: number; + } + assertType>("required" as const); + }); + + test("returns never for objects with only optional properties", () => { + interface AllOptional { + optional1?: string; + optional2?: number; + } + assertType>(undefined as never); + }); + }); }); diff --git a/packages/openapi-react-query/src/index.ts b/packages/openapi-react-query/src/index.ts index 337919ac3..7f3707e62 100644 --- a/packages/openapi-react-query/src/index.ts +++ b/packages/openapi-react-query/src/index.ts @@ -229,13 +229,14 @@ export default function createClient { const mth = method.toUpperCase() as Uppercase; const fn = client[mth] as ClientMethod; + const initWithParams = init as { params?: { query?: DefaultParamsOption } } | undefined; const mergedInit = { ...init, signal, params: { - ...(init?.params || {}), + ...(initWithParams?.params || {}), query: { - ...(init?.params as { query?: DefaultParamsOption })?.query, + ...(initWithParams?.params?.query || {}), [pageParamName]: pageParam, }, }, diff --git a/packages/openapi-typescript-helpers/src/index.ts b/packages/openapi-typescript-helpers/src/index.ts index 3c8a004ce..90424bd90 100644 --- a/packages/openapi-typescript-helpers/src/index.ts +++ b/packages/openapi-typescript-helpers/src/index.ts @@ -197,5 +197,11 @@ type RequiredKeysOfHelper = { // biome-ignore lint/complexity/noBannedTypes: `{}` is necessary here [K in keyof T]: {} extends Pick ? never : K; }[keyof T]; +/** Helper to detect `any` type (0 extends 1 & any is true, but 0 extends 1 & T is false for other types) */ +type IsAny = 0 extends 1 & T ? true : false; /** Get the required keys of an object, or `never` if no keys are required */ -export type RequiredKeysOf = RequiredKeysOfHelper extends undefined ? never : RequiredKeysOfHelper; +export type RequiredKeysOf = IsAny extends true + ? never + : RequiredKeysOfHelper extends undefined + ? never + : RequiredKeysOfHelper;