diff --git a/packages/openapi-react-query/src/index.ts b/packages/openapi-react-query/src/index.ts index 855b249c9..3569b3854 100644 --- a/packages/openapi-react-query/src/index.ts +++ b/packages/openapi-react-query/src/index.ts @@ -11,10 +11,13 @@ import { type QueryClient, type QueryFunctionContext, type SkipToken, + useInfiniteQuery, + type DataTag, useMutation, useQuery, useSuspenseQuery, - useInfiniteQuery, + dataTagSymbol, + dataTagErrorSymbol, } from "@tanstack/react-query"; import type { ClientMethod, @@ -34,8 +37,12 @@ export type QueryKey< Paths extends Record>, Method extends HttpMethod, Path extends PathsWithMethod, - Init = MaybeOptionalInit, -> = Init extends undefined ? readonly [Method, Path] : readonly [Method, Path, Init]; + Media extends MediaType, + Init extends MaybeOptionalInit = MaybeOptionalInit, + Response extends Required> = Required< + FetchResponse + >, +> = DataTag; export type QueryOptionsFunction>, Media extends MediaType> = < Method extends HttpMethod, @@ -47,7 +54,7 @@ export type QueryOptionsFunction, - QueryKey + QueryKey >, "queryKey" | "queryFn" >, @@ -63,7 +70,7 @@ export type QueryOptionsFunction, - QueryKey + QueryKey >, "queryFn" > & { @@ -72,7 +79,7 @@ export type QueryOptionsFunction, - QueryKey + QueryKey >["queryFn"], SkipToken | undefined >; @@ -89,7 +96,7 @@ export type UseQueryMethod>, Response["data"], Response["error"], InferSelectReturnType, - QueryKey + QueryKey >, "queryKey" | "queryFn" >, @@ -112,7 +119,7 @@ export type UseInfiniteQueryMethod, Response["data"], - QueryKey, + QueryKey, unknown >, "queryKey" | "queryFn" @@ -137,7 +144,7 @@ export type UseSuspenseQueryMethod, - QueryKey + QueryKey >, "queryKey" | "queryFn" >, @@ -188,7 +195,7 @@ export default function createClient>({ queryKey: [method, path, init], signal, - }: QueryFunctionContext>) => { + }: QueryFunctionContext>) => { const mth = method.toUpperCase() as Uppercase; const fn = client[mth] as ClientMethod; const { data, error } = await fn(path, { signal, ...(init as any) }); // TODO: find a way to avoid as any @@ -200,11 +207,10 @@ export default function createClient = (method, path, ...[init, options]) => ({ - queryKey: (init === undefined ? ([method, path] as const) : ([method, path, init] as const)) as QueryKey< - Paths, - typeof method, - typeof path - >, + queryKey: Object.assign(init === undefined ? ([method, path] as const) : ([method, path, init] as const), { + [dataTagSymbol]: {} as any, + [dataTagErrorSymbol]: {} as any, + }) as QueryKey, queryFn, ...options, }); diff --git a/packages/openapi-react-query/test/index.test.tsx b/packages/openapi-react-query/test/index.test.tsx index 65420164a..59c8cf183 100644 --- a/packages/openapi-react-query/test/index.test.tsx +++ b/packages/openapi-react-query/test/index.test.tsx @@ -91,6 +91,49 @@ describe("client", () => { client.queryOptions("get", "/blogposts/{post_id}", {}); }); + it("correctly infers return type from query key", async () => { + const fetchClient = createFetchClient({ baseUrl }); + const client = createClient(fetchClient); + + const initialData = { title: "Initial data", body: "Initial data" }; + + const options = client.queryOptions( + "get", + "/blogposts/{post_id}", + { + params: { + path: { + post_id: "1", + }, + }, + }, + { + initialData: () => initialData, + }, + ); + + const data = queryClient.getQueryData(options.queryKey); + + expectTypeOf(data).toEqualTypeOf< + | { + title: string; + body: string; + publish_date?: number; + } + | undefined + >(); + expect(data).toEqual(undefined); + + const { result } = renderHook(() => useQuery({ ...options, enabled: false }), { + wrapper, + }); + + await waitFor(() => expect(result.current.isFetching).toBe(false)); + + expect(result.current.data).toEqual(initialData); + expect(result.current.error).toBeNull(); + }); + it("returns query options that can resolve data correctly with fetchQuery", async () => { const response = { title: "title", body: "body" }; const fetchClient = createFetchClient({ baseUrl });