Skip to content

Commit cd3e971

Browse files
committed
fix: 🐛 useInfiniteQuery type safe fix
1 parent d919262 commit cd3e971

File tree

1 file changed

+68
-95
lines changed
  • packages/openapi-react-query/src

1 file changed

+68
-95
lines changed

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

Lines changed: 68 additions & 95 deletions
Original file line numberDiff line numberDiff line change
@@ -15,20 +15,15 @@ import {
1515
useQuery,
1616
useSuspenseQuery,
1717
useInfiniteQuery,
18-
} from '@tanstack/react-query';
18+
} from "@tanstack/react-query";
1919
import type {
2020
ClientMethod,
2121
FetchResponse,
2222
MaybeOptionalInit,
2323
Client as FetchClient,
2424
DefaultParamsOption,
25-
} from 'openapi-fetch';
26-
import type {
27-
HttpMethod,
28-
MediaType,
29-
PathsWithMethod,
30-
RequiredKeysOf,
31-
} from 'openapi-typescript-helpers';
25+
} from "openapi-fetch";
26+
import type { HttpMethod, MediaType, PathsWithMethod, RequiredKeysOf } from "openapi-typescript-helpers";
3227

3328
// Helper type to dynamically infer the type from the `select` property
3429
type InferSelectReturnType<TData, TSelect> = TSelect extends (data: TData) => infer R ? R : TData;
@@ -42,22 +37,19 @@ export type QueryKey<
4237
Init = MaybeOptionalInit<Paths[Path], Method>,
4338
> = Init extends undefined ? readonly [Method, Path] : readonly [Method, Path, Init];
4439

45-
export type QueryOptionsFunction<
46-
Paths extends Record<string, Record<HttpMethod, {}>>,
47-
Media extends MediaType,
48-
> = <
40+
export type QueryOptionsFunction<Paths extends Record<string, Record<HttpMethod, {}>>, Media extends MediaType> = <
4941
Method extends HttpMethod,
5042
Path extends PathsWithMethod<Paths, Method>,
5143
Init extends MaybeOptionalInit<Paths[Path], Method>,
5244
Response extends Required<FetchResponse<Paths[Path][Method], Init, Media>>, // note: Required is used to avoid repeating NonNullable in UseQuery types
5345
Options extends Omit<
5446
UseQueryOptions<
55-
Response['data'],
56-
Response['error'],
57-
InferSelectReturnType<Response['data'], Options['select']>,
47+
Response["data"],
48+
Response["error"],
49+
InferSelectReturnType<Response["data"], Options["select"]>,
5850
QueryKey<Paths, Method, Path>
5951
>,
60-
'queryKey' | 'queryFn'
52+
"queryKey" | "queryFn"
6153
>,
6254
>(
6355
method: Method,
@@ -68,129 +60,113 @@ export type QueryOptionsFunction<
6860
) => NoInfer<
6961
Omit<
7062
UseQueryOptions<
71-
Response['data'],
72-
Response['error'],
73-
InferSelectReturnType<Response['data'], Options['select']>,
63+
Response["data"],
64+
Response["error"],
65+
InferSelectReturnType<Response["data"], Options["select"]>,
7466
QueryKey<Paths, Method, Path>
7567
>,
76-
'queryFn'
68+
"queryFn"
7769
> & {
7870
queryFn: Exclude<
7971
UseQueryOptions<
80-
Response['data'],
81-
Response['error'],
82-
InferSelectReturnType<Response['data'], Options['select']>,
72+
Response["data"],
73+
Response["error"],
74+
InferSelectReturnType<Response["data"], Options["select"]>,
8375
QueryKey<Paths, Method, Path>
84-
>['queryFn'],
76+
>["queryFn"],
8577
SkipToken | undefined
8678
>;
8779
}
8880
>;
8981

90-
export type UseQueryMethod<
91-
Paths extends Record<string, Record<HttpMethod, {}>>,
92-
Media extends MediaType,
93-
> = <
82+
export type UseQueryMethod<Paths extends Record<string, Record<HttpMethod, {}>>, Media extends MediaType> = <
9483
Method extends HttpMethod,
9584
Path extends PathsWithMethod<Paths, Method>,
9685
Init extends MaybeOptionalInit<Paths[Path], Method>,
9786
Response extends Required<FetchResponse<Paths[Path][Method], Init, Media>>, // note: Required is used to avoid repeating NonNullable in UseQuery types
9887
Options extends Omit<
9988
UseQueryOptions<
100-
Response['data'],
101-
Response['error'],
102-
InferSelectReturnType<Response['data'], Options['select']>,
89+
Response["data"],
90+
Response["error"],
91+
InferSelectReturnType<Response["data"], Options["select"]>,
10392
QueryKey<Paths, Method, Path>
10493
>,
105-
'queryKey' | 'queryFn'
94+
"queryKey" | "queryFn"
10695
>,
10796
>(
10897
method: Method,
10998
url: Path,
11099
...[init, options, queryClient]: RequiredKeysOf<Init> extends never
111100
? [InitWithUnknowns<Init>?, Options?, QueryClient?]
112101
: [InitWithUnknowns<Init>, Options?, QueryClient?]
113-
) => UseQueryResult<InferSelectReturnType<Response['data'], Options['select']>, Response['error']>;
102+
) => UseQueryResult<InferSelectReturnType<Response["data"], Options["select"]>, Response["error"]>;
114103

115-
export type UseInfiniteQueryMethod<
116-
Paths extends Record<string, Record<HttpMethod, {}>>,
117-
Media extends MediaType,
118-
> = <
104+
// Helper type to infer TPageParam type
105+
type InferPageParamType<T> = T extends { initialPageParam: infer P } ? P : unknown;
106+
107+
export type UseInfiniteQueryMethod<Paths extends Record<string, Record<HttpMethod, {}>>, Media extends MediaType> = <
119108
Method extends HttpMethod,
120109
Path extends PathsWithMethod<Paths, Method>,
121110
Init extends MaybeOptionalInit<Paths[Path], Method>,
122111
Response extends Required<FetchResponse<Paths[Path][Method], Init, Media>>,
123-
Select = undefined,
124112
Options extends Omit<
125113
UseInfiniteQueryOptions<
126-
Response['data'],
127-
Response['error'],
128-
InferSelectReturnType<InfiniteData<Response['data']>, Select>,
114+
Response["data"],
115+
Response["error"],
116+
InferSelectReturnType<InfiniteData<Response["data"]>, Options["select"]>,
129117
QueryKey<Paths, Method, Path>,
130-
unknown
118+
InferPageParamType<Options>
131119
>,
132-
'queryKey' | 'queryFn'
120+
"queryKey" | "queryFn"
133121
> & {
134122
pageParamName?: string;
135-
select?: Select;
136-
} = any,
123+
initialPageParam: InferPageParamType<Options>;
124+
},
137125
>(
138126
method: Method,
139127
url: Path,
140128
init: InitWithUnknowns<Init>,
141129
options: Options,
142-
queryClient?: QueryClient
130+
queryClient?: QueryClient,
143131
) => UseInfiniteQueryResult<
144-
InferSelectReturnType<InfiniteData<Response['data']>, Select>,
145-
Response['error']
132+
InferSelectReturnType<InfiniteData<Response["data"]>, Options["select"]>,
133+
Response["error"]
146134
>;
147135

148-
export type UseSuspenseQueryMethod<
149-
Paths extends Record<string, Record<HttpMethod, {}>>,
150-
Media extends MediaType,
151-
> = <
136+
export type UseSuspenseQueryMethod<Paths extends Record<string, Record<HttpMethod, {}>>, Media extends MediaType> = <
152137
Method extends HttpMethod,
153138
Path extends PathsWithMethod<Paths, Method>,
154139
Init extends MaybeOptionalInit<Paths[Path], Method>,
155140
Response extends Required<FetchResponse<Paths[Path][Method], Init, Media>>, // note: Required is used to avoid repeating NonNullable in UseQuery types
156141
Options extends Omit<
157142
UseSuspenseQueryOptions<
158-
Response['data'],
159-
Response['error'],
160-
InferSelectReturnType<Response['data'], Options['select']>,
143+
Response["data"],
144+
Response["error"],
145+
InferSelectReturnType<Response["data"], Options["select"]>,
161146
QueryKey<Paths, Method, Path>
162147
>,
163-
'queryKey' | 'queryFn'
148+
"queryKey" | "queryFn"
164149
>,
165150
>(
166151
method: Method,
167152
url: Path,
168153
...[init, options, queryClient]: RequiredKeysOf<Init> extends never
169154
? [InitWithUnknowns<Init>?, Options?, QueryClient?]
170155
: [InitWithUnknowns<Init>, Options?, QueryClient?]
171-
) => UseSuspenseQueryResult<
172-
InferSelectReturnType<Response['data'], Options['select']>,
173-
Response['error']
174-
>;
156+
) => UseSuspenseQueryResult<InferSelectReturnType<Response["data"], Options["select"]>, Response["error"]>;
175157

176-
export type UseMutationMethod<
177-
Paths extends Record<string, Record<HttpMethod, {}>>,
178-
Media extends MediaType,
179-
> = <
158+
export type UseMutationMethod<Paths extends Record<string, Record<HttpMethod, {}>>, Media extends MediaType> = <
180159
Method extends HttpMethod,
181160
Path extends PathsWithMethod<Paths, Method>,
182161
Init extends MaybeOptionalInit<Paths[Path], Method>,
183162
Response extends Required<FetchResponse<Paths[Path][Method], Init, Media>>, // note: Required is used to avoid repeating NonNullable in UseQuery types
184-
Options extends Omit<
185-
UseMutationOptions<Response['data'], Response['error'], Init>,
186-
'mutationKey' | 'mutationFn'
187-
>,
163+
Options extends Omit<UseMutationOptions<Response["data"], Response["error"], Init>, "mutationKey" | "mutationFn">,
188164
>(
189165
method: Method,
190166
url: Path,
191167
options?: Options,
192-
queryClient?: QueryClient
193-
) => UseMutationResult<Response['data'], Response['error'], Init>;
168+
queryClient?: QueryClient,
169+
) => UseMutationResult<Response["data"], Response["error"], Init>;
194170

195171
export interface OpenapiQueryClient<Paths extends {}, Media extends MediaType = MediaType> {
196172
queryOptions: QueryOptionsFunction<Paths, Media>;
@@ -207,62 +183,59 @@ export type MethodResponse<
207183
? PathsWithMethod<Paths, Method>
208184
: never,
209185
Options = object,
210-
> =
211-
CreatedClient extends OpenapiQueryClient<
212-
infer Paths extends { [key: string]: any },
213-
infer Media extends MediaType
214-
>
215-
? NonNullable<FetchResponse<Paths[Path][Method], Options, Media>['data']>
216-
: never;
186+
> = CreatedClient extends OpenapiQueryClient<infer Paths extends { [key: string]: any }, infer Media extends MediaType>
187+
? NonNullable<FetchResponse<Paths[Path][Method], Options, Media>["data"]>
188+
: never;
217189

218190
// TODO: Add the ability to bring queryClient as argument
219191
export default function createClient<Paths extends {}, Media extends MediaType = MediaType>(
220-
client: FetchClient<Paths, Media>
192+
client: FetchClient<Paths, Media>,
221193
): OpenapiQueryClient<Paths, Media> {
222194
const queryFn = async <Method extends HttpMethod, Path extends PathsWithMethod<Paths, Method>>({
223195
queryKey: [method, path, init],
224196
signal,
225197
}: QueryFunctionContext<QueryKey<Paths, Method, Path>>) => {
226198
const mth = method.toUpperCase() as Uppercase<typeof method>;
227199
const fn = client[mth] as ClientMethod<Paths, typeof method, Media>;
228-
const { data, error, response } = await fn(path, { signal, ...(init as any) }); // TODO: find a way to avoid as any
200+
const { data, error, response } = await fn(path, {
201+
signal,
202+
...(init as any),
203+
}); // TODO: find a way to avoid as any
229204
if (error) {
230205
throw error;
231206
}
232-
if (response.status === 204 || response.headers.get('Content-Length') === '0') {
207+
if (response.status === 204 || response.headers.get("Content-Length") === "0") {
233208
return data ?? null;
234209
}
235210

236211
return data;
237212
};
238213

239214
const queryOptions: QueryOptionsFunction<Paths, Media> = (method, path, ...[init, options]) => ({
240-
queryKey: (init === undefined
241-
? ([method, path] as const)
242-
: ([method, path, init] as const)) as QueryKey<Paths, typeof method, typeof path>,
215+
queryKey: (init === undefined ? ([method, path] as const) : ([method, path, init] as const)) as QueryKey<
216+
Paths,
217+
typeof method,
218+
typeof path
219+
>,
243220
queryFn,
244221
...options,
245222
});
246223

247224
return {
248225
queryOptions,
249226
useQuery: (method, path, ...[init, options, queryClient]) =>
250-
useQuery(
251-
queryOptions(method, path, init as InitWithUnknowns<typeof init>, options),
252-
queryClient
253-
),
227+
useQuery(queryOptions(method, path, init as InitWithUnknowns<typeof init>, options), queryClient),
254228
useSuspenseQuery: (method, path, ...[init, options, queryClient]) =>
255-
useSuspenseQuery(
256-
queryOptions(method, path, init as InitWithUnknowns<typeof init>, options),
257-
queryClient
258-
),
229+
useSuspenseQuery(queryOptions(method, path, init as InitWithUnknowns<typeof init>, options), queryClient),
259230
useInfiniteQuery: (method, path, init, options, queryClient) => {
260-
const { pageParamName = 'cursor', ...restOptions } = options;
231+
const { pageParamName = "cursor", initialPageParam, ...restOptions } = options;
261232
const { queryKey } = queryOptions(method, path, init);
233+
262234
return useInfiniteQuery(
263235
{
264236
queryKey,
265-
queryFn: async ({ queryKey: [method, path, init], pageParam = 0, signal }) => {
237+
initialPageParam,
238+
queryFn: async ({ queryKey: [method, path, init], pageParam, signal }) => {
266239
const mth = method.toUpperCase() as Uppercase<typeof method>;
267240
const fn = client[mth] as ClientMethod<Paths, typeof method, Media>;
268241
const mergedInit = {
@@ -285,7 +258,7 @@ export default function createClient<Paths extends {}, Media extends MediaType =
285258
},
286259
...restOptions,
287260
},
288-
queryClient
261+
queryClient,
289262
);
290263
},
291264
useMutation: (method, path, options, queryClient) =>
@@ -304,7 +277,7 @@ export default function createClient<Paths extends {}, Media extends MediaType =
304277
},
305278
...options,
306279
},
307-
queryClient
280+
queryClient,
308281
),
309282
};
310283
}

0 commit comments

Comments
 (0)