@@ -6,39 +6,71 @@ import {
6
6
type UseSuspenseQueryOptions ,
7
7
type UseSuspenseQueryResult ,
8
8
type QueryClient ,
9
+ type QueryFunctionContext ,
9
10
useMutation ,
10
11
useQuery ,
11
12
useSuspenseQuery ,
12
13
} from "@tanstack/react-query" ;
13
14
import type { ClientMethod , FetchResponse , MaybeOptionalInit , Client as FetchClient } from "openapi-fetch" ;
14
15
import type { HttpMethod , MediaType , PathsWithMethod , RequiredKeysOf } from "openapi-typescript-helpers" ;
15
16
17
+ type InitWithUnknowns < Init > = Init & { [ key : string ] : unknown } ;
18
+
19
+ export type QueryKey <
20
+ Paths extends Record < string , Record < HttpMethod , { } > > ,
21
+ Method extends HttpMethod ,
22
+ Path extends PathsWithMethod < Paths , Method > ,
23
+ > = readonly [ Method , Path , MaybeOptionalInit < Paths [ Path ] , Method > ] ;
24
+
25
+ export type QueryOptionsFunction < Paths extends Record < string , Record < HttpMethod , { } > > , Media extends MediaType > = <
26
+ Method extends HttpMethod ,
27
+ Path extends PathsWithMethod < Paths , Method > ,
28
+ Init extends MaybeOptionalInit < Paths [ Path ] , Method > ,
29
+ Response extends Required < FetchResponse < Paths [ Path ] [ Method ] , Init , Media > > , // note: Required is used to avoid repeating NonNullable in UseQuery types
30
+ Options extends Omit <
31
+ UseQueryOptions < Response [ "data" ] , Response [ "error" ] , Response [ "data" ] , QueryKey < Paths , Method , Path > > ,
32
+ "queryKey" | "queryFn"
33
+ > ,
34
+ > (
35
+ method : Method ,
36
+ path : Path ,
37
+ ...[ init , options ] : RequiredKeysOf < Init > extends never
38
+ ? [ InitWithUnknowns < Init > ?, Options ?]
39
+ : [ InitWithUnknowns < Init > , Options ?]
40
+ ) => UseQueryOptions < Response [ "data" ] , Response [ "error" ] , Response [ "data" ] , QueryKey < Paths , Method , Path > > ;
41
+
16
42
export type UseQueryMethod < Paths extends Record < string , Record < HttpMethod , { } > > , Media extends MediaType > = <
17
43
Method extends HttpMethod ,
18
44
Path extends PathsWithMethod < Paths , Method > ,
19
45
Init extends MaybeOptionalInit < Paths [ Path ] , Method > ,
20
46
Response extends Required < FetchResponse < Paths [ Path ] [ Method ] , Init , Media > > , // note: Required is used to avoid repeating NonNullable in UseQuery types
21
- Options extends Omit < UseQueryOptions < Response [ "data" ] , Response [ "error" ] > , "queryKey" | "queryFn" > ,
47
+ Options extends Omit <
48
+ UseQueryOptions < Response [ "data" ] , Response [ "error" ] , Response [ "data" ] , QueryKey < Paths , Method , Path > > ,
49
+ "queryKey" | "queryFn"
50
+ > ,
22
51
> (
23
52
method : Method ,
24
53
url : Path ,
25
54
...[ init , options , queryClient ] : RequiredKeysOf < Init > extends never
26
- ? [ ( Init & { [ key : string ] : unknown } ) ?, Options ?, QueryClient ?]
27
- : [ Init & { [ key : string ] : unknown } , Options ?, QueryClient ?]
55
+ ? [ InitWithUnknowns < Init > ?, Options ?, QueryClient ?]
56
+ : [ InitWithUnknowns < Init > , Options ?, QueryClient ?]
28
57
) => UseQueryResult < Response [ "data" ] , Response [ "error" ] > ;
29
58
30
59
export type UseSuspenseQueryMethod < Paths extends Record < string , Record < HttpMethod , { } > > , Media extends MediaType > = <
31
60
Method extends HttpMethod ,
32
61
Path extends PathsWithMethod < Paths , Method > ,
33
62
Init extends MaybeOptionalInit < Paths [ Path ] , Method > ,
34
63
Response extends Required < FetchResponse < Paths [ Path ] [ Method ] , Init , Media > > , // note: Required is used to avoid repeating NonNullable in UseQuery types
35
- Options extends Omit < UseSuspenseQueryOptions < Response [ "data" ] , Response [ "error" ] > , "queryKey" | "queryFn" > ,
64
+ Options extends Omit <
65
+ UseSuspenseQueryOptions < Response [ "data" ] , Response [ "error" ] , Response [ "data" ] , QueryKey < Paths , Method , Path > > ,
66
+ "queryKey" | "queryFn"
67
+ > ,
36
68
> (
37
69
method : Method ,
38
70
url : Path ,
39
71
...[ init , options , queryClient ] : RequiredKeysOf < Init > extends never
40
- ? [ ( Init & { [ key : string ] : unknown } ) ?, Options ?, QueryClient ?]
41
- : [ Init & { [ key : string ] : unknown } , Options ?, QueryClient ?]
72
+ ? [ InitWithUnknowns < Init > ?, Options ?, QueryClient ?]
73
+ : [ InitWithUnknowns < Init > , Options ?, QueryClient ?]
42
74
) => UseSuspenseQueryResult < Response [ "data" ] , Response [ "error" ] > ;
43
75
44
76
export type UseMutationMethod < Paths extends Record < string , Record < HttpMethod , { } > > , Media extends MediaType > = <
@@ -55,62 +87,49 @@ export type UseMutationMethod<Paths extends Record<string, Record<HttpMethod, {}
55
87
) => UseMutationResult < Response [ "data" ] , Response [ "error" ] , Init > ;
56
88
57
89
export interface OpenapiQueryClient < Paths extends { } , Media extends MediaType = MediaType > {
90
+ queryOptions : QueryOptionsFunction < Paths , Media > ;
58
91
useQuery : UseQueryMethod < Paths , Media > ;
59
92
useSuspenseQuery : UseSuspenseQueryMethod < Paths , Media > ;
60
93
useMutation : UseMutationMethod < Paths , Media > ;
61
94
}
62
95
63
- // TODO: Move the client[method]() fn outside for reusability
64
96
// TODO: Add the ability to bring queryClient as argument
65
97
export default function createClient < Paths extends { } , Media extends MediaType = MediaType > (
66
98
client : FetchClient < Paths , Media > ,
67
99
) : OpenapiQueryClient < Paths , Media > {
100
+ const queryFn = async < Method extends HttpMethod , Path extends PathsWithMethod < Paths , Method > > ( {
101
+ queryKey : [ method , path , init ] ,
102
+ signal,
103
+ } : QueryFunctionContext < QueryKey < Paths , Method , Path > > ) => {
104
+ const mth = method . toUpperCase ( ) as Uppercase < typeof method > ;
105
+ const fn = client [ mth ] as ClientMethod < Paths , typeof method , Media > ;
106
+ const { data, error } = await fn ( path , { signal, ...( init as any ) } ) ; // TODO: find a way to avoid as any
107
+ if ( error || ! data ) {
108
+ throw error ;
109
+ }
110
+ return data ;
111
+ } ;
112
+
113
+ const queryOptions : QueryOptionsFunction < Paths , Media > = ( method , path , ...[ init , options ] ) => ( {
114
+ queryKey : [ method , path , init as InitWithUnknowns < typeof init > ] as const ,
115
+ queryFn,
116
+ ...options ,
117
+ } ) ;
118
+
68
119
return {
69
- useQuery : ( method , path , ...[ init , options , queryClient ] ) => {
70
- return useQuery (
71
- {
72
- queryKey : [ method , path , init ] ,
73
- queryFn : async ( { signal } ) => {
74
- const mth = method . toUpperCase ( ) as keyof typeof client ;
75
- const fn = client [ mth ] as ClientMethod < Paths , typeof method , Media > ;
76
- const { data, error } = await fn ( path , { signal, ...( init as any ) } ) ; // TODO: find a way to avoid as any
77
- if ( error || ! data ) {
78
- throw error ;
79
- }
80
- return data ;
81
- } ,
82
- ...options ,
83
- } ,
84
- queryClient ,
85
- ) ;
86
- } ,
87
- useSuspenseQuery : ( method , path , ...[ init , options , queryClient ] ) => {
88
- return useSuspenseQuery (
89
- {
90
- queryKey : [ method , path , init ] ,
91
- queryFn : async ( { signal } ) => {
92
- const mth = method . toUpperCase ( ) as keyof typeof client ;
93
- const fn = client [ mth ] as ClientMethod < Paths , typeof method , Media > ;
94
- const { data, error } = await fn ( path , { signal, ...( init as any ) } ) ; // TODO: find a way to avoid as any
95
- if ( error || ! data ) {
96
- throw error ;
97
- }
98
- return data ;
99
- } ,
100
- ...options ,
101
- } ,
102
- queryClient ,
103
- ) ;
104
- } ,
105
- useMutation : ( method , path , options , queryClient ) => {
106
- return useMutation (
120
+ queryOptions,
121
+ useQuery : ( method , path , ...[ init , options , queryClient ] ) =>
122
+ useQuery ( queryOptions ( method , path , init as InitWithUnknowns < typeof init > , options ) , queryClient ) ,
123
+ useSuspenseQuery : ( method , path , ...[ init , options , queryClient ] ) =>
124
+ useSuspenseQuery ( queryOptions ( method , path , init as InitWithUnknowns < typeof init > , options ) , queryClient ) ,
125
+ useMutation : ( method , path , options , queryClient ) =>
126
+ useMutation (
107
127
{
108
128
mutationKey : [ method , path ] ,
109
129
mutationFn : async ( init ) => {
110
- // TODO: Put in external fn for reusability
111
- const mth = method . toUpperCase ( ) as keyof typeof client ;
130
+ const mth = method . toUpperCase ( ) as Uppercase < typeof method > ;
112
131
const fn = client [ mth ] as ClientMethod < Paths , typeof method , Media > ;
113
- const { data, error } = await fn ( path , init as any ) ; // TODO: find a way to avoid as any
132
+ const { data, error } = await fn ( path , init as InitWithUnknowns < typeof init > ) ;
114
133
if ( error || ! data ) {
115
134
throw error ;
116
135
}
@@ -119,7 +138,6 @@ export default function createClient<Paths extends {}, Media extends MediaType =
119
138
...options ,
120
139
} ,
121
140
queryClient ,
122
- ) ;
123
- } ,
141
+ ) ,
124
142
} ;
125
143
}
0 commit comments