From db243c67cf24e04aa759cf6d9c641c5e023d943a Mon Sep 17 00:00:00 2001
From: "Gianmarco Rengucci (freshgiammi)" <rengucci.gianmarco@gmail.com>
Date: Wed, 16 Oct 2024 20:15:48 +0200
Subject: [PATCH 1/2] feat(openapi-react-query): use queryOptions helper

---
 packages/openapi-react-query/src/index.ts     | 43 ++++++++++++-------
 .../openapi-react-query/test/index.test.tsx   | 43 +++++++++++++++++++
 2 files changed, 70 insertions(+), 16 deletions(-)

diff --git a/packages/openapi-react-query/src/index.ts b/packages/openapi-react-query/src/index.ts
index 855b249c9..be1a227a2 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,
+  useSuspenseQuery,  
+  dataTagSymbol,
+  dataTagErrorSymbol,
 } from "@tanstack/react-query";
 import type {
   ClientMethod,
@@ -34,8 +37,12 @@ export type QueryKey<
   Paths extends Record<string, Record<HttpMethod, {}>>,
   Method extends HttpMethod,
   Path extends PathsWithMethod<Paths, Method>,
-  Init = MaybeOptionalInit<Paths[Path], Method>,
-> = Init extends undefined ? readonly [Method, Path] : readonly [Method, Path, Init];
+  Media extends MediaType,
+  Init extends MaybeOptionalInit<Paths[Path], Method> = MaybeOptionalInit<Paths[Path], Method>,
+Response extends Required<FetchResponse<Paths[Path][Method], Init, Media>> = Required<
+  FetchResponse<Paths[Path][Method], Init, Media>
+>,
+> = DataTag<readonly [Method, Path, Init], Response["data"]>
 
 export type QueryOptionsFunction<Paths extends Record<string, Record<HttpMethod, {}>>, Media extends MediaType> = <
   Method extends HttpMethod,
@@ -47,7 +54,7 @@ export type QueryOptionsFunction<Paths extends Record<string, Record<HttpMethod,
       Response["data"],
       Response["error"],
       InferSelectReturnType<Response["data"], Options["select"]>,
-      QueryKey<Paths, Method, Path>
+      QueryKey<Paths, Method, Path, Media>
     >,
     "queryKey" | "queryFn"
   >,
@@ -63,7 +70,7 @@ export type QueryOptionsFunction<Paths extends Record<string, Record<HttpMethod,
       Response["data"],
       Response["error"],
       InferSelectReturnType<Response["data"], Options["select"]>,
-      QueryKey<Paths, Method, Path>
+      QueryKey<Paths, Method, Path, Media>
     >,
     "queryFn"
   > & {
@@ -72,7 +79,7 @@ export type QueryOptionsFunction<Paths extends Record<string, Record<HttpMethod,
         Response["data"],
         Response["error"],
         InferSelectReturnType<Response["data"], Options["select"]>,
-        QueryKey<Paths, Method, Path>
+        QueryKey<Paths, Method, Path, Media>
       >["queryFn"],
       SkipToken | undefined
     >;
@@ -89,7 +96,7 @@ export type UseQueryMethod<Paths extends Record<string, Record<HttpMethod, {}>>,
       Response["data"],
       Response["error"],
       InferSelectReturnType<Response["data"], Options["select"]>,
-      QueryKey<Paths, Method, Path>
+      QueryKey<Paths, Method, Path, Media>
     >,
     "queryKey" | "queryFn"
   >,
@@ -112,7 +119,7 @@ export type UseInfiniteQueryMethod<Paths extends Record<string, Record<HttpMetho
       Response["error"],
       InfiniteData<Response["data"]>,
       Response["data"],
-      QueryKey<Paths, Method, Path>,
+      QueryKey<Paths, Method, Path, Media>,
       unknown
     >,
     "queryKey" | "queryFn"
@@ -137,7 +144,7 @@ export type UseSuspenseQueryMethod<Paths extends Record<string, Record<HttpMetho
       Response["data"],
       Response["error"],
       InferSelectReturnType<Response["data"], Options["select"]>,
-      QueryKey<Paths, Method, Path>
+      QueryKey<Paths, Method, Path, Media>
     >,
     "queryKey" | "queryFn"
   >,
@@ -188,7 +195,7 @@ export default function createClient<Paths extends {}, Media extends MediaType =
   const queryFn = async <Method extends HttpMethod, Path extends PathsWithMethod<Paths, Method>>({
     queryKey: [method, path, init],
     signal,
-  }: QueryFunctionContext<QueryKey<Paths, Method, Path>>) => {
+  }: QueryFunctionContext<QueryKey<Paths, Method, Path, Media>>) => {
     const mth = method.toUpperCase() as Uppercase<typeof method>;
     const fn = client[mth] as ClientMethod<Paths, typeof method, Media>;
     const { data, error } = await fn(path, { signal, ...(init as any) }); // TODO: find a way to avoid as any
@@ -200,11 +207,15 @@ export default function createClient<Paths extends {}, Media extends MediaType =
   };
 
   const queryOptions: QueryOptionsFunction<Paths, Media> = (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<
+    Paths,
+    typeof method,
+    typeof path,
+    Media
+  >,
     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<paths>({ 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<paths>({ baseUrl });

From fb53233e5fc4a71c26574ed3565592f4e08257e8 Mon Sep 17 00:00:00 2001
From: "Gianmarco Rengucci (freshgiammi)" <rengucci.gianmarco@gmail.com>
Date: Mon, 27 Jan 2025 18:34:07 +0100
Subject: [PATCH 2/2] fix lint

---
 packages/openapi-react-query/src/index.ts | 19 +++++++------------
 1 file changed, 7 insertions(+), 12 deletions(-)

diff --git a/packages/openapi-react-query/src/index.ts b/packages/openapi-react-query/src/index.ts
index be1a227a2..3569b3854 100644
--- a/packages/openapi-react-query/src/index.ts
+++ b/packages/openapi-react-query/src/index.ts
@@ -15,7 +15,7 @@ import {
   type DataTag,
   useMutation,
   useQuery,
-  useSuspenseQuery,  
+  useSuspenseQuery,
   dataTagSymbol,
   dataTagErrorSymbol,
 } from "@tanstack/react-query";
@@ -39,10 +39,10 @@ export type QueryKey<
   Path extends PathsWithMethod<Paths, Method>,
   Media extends MediaType,
   Init extends MaybeOptionalInit<Paths[Path], Method> = MaybeOptionalInit<Paths[Path], Method>,
-Response extends Required<FetchResponse<Paths[Path][Method], Init, Media>> = Required<
-  FetchResponse<Paths[Path][Method], Init, Media>
->,
-> = DataTag<readonly [Method, Path, Init], Response["data"]>
+  Response extends Required<FetchResponse<Paths[Path][Method], Init, Media>> = Required<
+    FetchResponse<Paths[Path][Method], Init, Media>
+  >,
+> = DataTag<readonly [Method, Path, Init], Response["data"]>;
 
 export type QueryOptionsFunction<Paths extends Record<string, Record<HttpMethod, {}>>, Media extends MediaType> = <
   Method extends HttpMethod,
@@ -207,15 +207,10 @@ export default function createClient<Paths extends {}, Media extends MediaType =
   };
 
   const queryOptions: QueryOptionsFunction<Paths, Media> = (method, path, ...[init, options]) => ({
-    queryKey: Object.assign(init === undefined ? [method, path] as const : [method, path, init] as const, {
+    queryKey: Object.assign(init === undefined ? ([method, path] as const) : ([method, path, init] as const), {
       [dataTagSymbol]: {} as any,
       [dataTagErrorSymbol]: {} as any,
-    }) as QueryKey<
-    Paths,
-    typeof method,
-    typeof path,
-    Media
-  >,
+    }) as QueryKey<Paths, typeof method, typeof path, Media>,
     queryFn,
     ...options,
   });