Skip to content

Commit ea12536

Browse files
committed
feat: ✨ mutationOptions add with test code
1 parent 6ed0634 commit ea12536

File tree

2 files changed

+373
-61
lines changed

2 files changed

+373
-61
lines changed

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

Lines changed: 190 additions & 60 deletions
Original file line numberDiff line numberDiff line change
@@ -37,19 +37,27 @@ export type QueryKey<
3737
Init = MaybeOptionalInit<Paths[Path], Method>,
3838
> = Init extends undefined ? readonly [Method, Path] : readonly [Method, Path, Init];
3939

40-
export type QueryOptionsFunction<Paths extends Record<string, Record<HttpMethod, {}>>, Media extends MediaType> = <
40+
export type MutationKey<
41+
Method extends HttpMethod,
42+
Path,
43+
> = readonly [Method, Path];
44+
45+
export type QueryOptionsFunction<
46+
Paths extends Record<string, Record<HttpMethod, {}>>,
47+
Media extends MediaType,
48+
> = <
4149
Method extends HttpMethod,
4250
Path extends PathsWithMethod<Paths, Method>,
4351
Init extends MaybeOptionalInit<Paths[Path], Method>,
4452
Response extends Required<FetchResponse<Paths[Path][Method], Init, Media>>, // note: Required is used to avoid repeating NonNullable in UseQuery types
4553
Options extends Omit<
4654
UseQueryOptions<
47-
Response["data"],
48-
Response["error"],
49-
InferSelectReturnType<Response["data"], Options["select"]>,
55+
Response['data'],
56+
Response['error'],
57+
InferSelectReturnType<Response['data'], Options['select']>,
5058
QueryKey<Paths, Method, Path>
5159
>,
52-
"queryKey" | "queryFn"
60+
'queryKey' | 'queryFn'
5361
>,
5462
>(
5563
method: Method,
@@ -60,61 +68,148 @@ export type QueryOptionsFunction<Paths extends Record<string, Record<HttpMethod,
6068
) => NoInfer<
6169
Omit<
6270
UseQueryOptions<
63-
Response["data"],
64-
Response["error"],
65-
InferSelectReturnType<Response["data"], Options["select"]>,
71+
Response['data'],
72+
Response['error'],
73+
InferSelectReturnType<Response['data'], Options['select']>,
6674
QueryKey<Paths, Method, Path>
6775
>,
68-
"queryFn"
76+
'queryFn'
6977
> & {
7078
queryFn: Exclude<
7179
UseQueryOptions<
72-
Response["data"],
73-
Response["error"],
74-
InferSelectReturnType<Response["data"], Options["select"]>,
80+
Response['data'],
81+
Response['error'],
82+
InferSelectReturnType<Response['data'], Options['select']>,
7583
QueryKey<Paths, Method, Path>
76-
>["queryFn"],
84+
>['queryFn'],
7785
SkipToken | undefined
7886
>;
7987
}
8088
>;
8189

82-
export type UseQueryMethod<Paths extends Record<string, Record<HttpMethod, {}>>, Media extends MediaType> = <
90+
export type MutationOptionsFunction<
91+
Paths extends Record<string, Record<HttpMethod, {}>>,
92+
Media extends MediaType,
93+
> = <
94+
Method extends HttpMethod,
95+
Path extends PathsWithMethod<Paths, Method>,
96+
Init extends MaybeOptionalInit<Paths[Path], Method>,
97+
Response extends Required<FetchResponse<Paths[Path][Method], Init, Media>>,
98+
Options extends Omit<
99+
UseMutationOptions<Response['data'], Response['error'], Init>,
100+
'mutationKey' | 'mutationFn'
101+
>,
102+
>(
103+
method: Method,
104+
path: Path,
105+
options?: Options
106+
) => NoInfer<
107+
Omit<
108+
UseMutationOptions<Response['data'], Response['error'], Init>,
109+
'mutationFn'
110+
> & {
111+
mutationFn: Exclude<
112+
UseMutationOptions<Response['data'], Response['error'], Init>['mutationFn'],
113+
undefined
114+
>;
115+
}
116+
>;
117+
118+
// Helper type to infer TPageParam type
119+
type InferPageParamType<T> = T extends { initialPageParam: infer P } ? P : unknown;
120+
121+
export type InfiniteQueryOptionsFunction<
122+
Paths extends Record<string, Record<HttpMethod, {}>>,
123+
Media extends MediaType,
124+
> = <
125+
Method extends HttpMethod,
126+
Path extends PathsWithMethod<Paths, Method>,
127+
Init extends MaybeOptionalInit<Paths[Path], Method>,
128+
Response extends Required<FetchResponse<Paths[Path][Method], Init, Media>>,
129+
Options extends Omit<
130+
UseInfiniteQueryOptions<
131+
Response['data'],
132+
Response['error'],
133+
InferSelectReturnType<InfiniteData<Response['data']>, Options['select']>,
134+
QueryKey<Paths, Method, Path>,
135+
InferPageParamType<Options>
136+
>,
137+
'queryKey' | 'queryFn'
138+
> & {
139+
pageParamName?: string;
140+
initialPageParam: InferPageParamType<Options>;
141+
},
142+
>(
143+
method: Method,
144+
path: Path,
145+
init: InitWithUnknowns<Init>,
146+
options: Options
147+
) => NoInfer<
148+
Omit<
149+
UseInfiniteQueryOptions<
150+
Response['data'],
151+
Response['error'],
152+
InferSelectReturnType<InfiniteData<Response['data']>, Options['select']>,
153+
QueryKey<Paths, Method, Path>,
154+
InferPageParamType<Options>
155+
>,
156+
'queryFn'
157+
> & {
158+
queryFn: Exclude<
159+
UseInfiniteQueryOptions<
160+
Response['data'],
161+
Response['error'],
162+
InferSelectReturnType<InfiniteData<Response['data']>, Options['select']>,
163+
QueryKey<Paths, Method, Path>,
164+
InferPageParamType<Options>
165+
>['queryFn'],
166+
SkipToken | undefined
167+
>;
168+
}
169+
>;
170+
171+
export type UseQueryMethod<
172+
Paths extends Record<string, Record<HttpMethod, {}>>,
173+
Media extends MediaType,
174+
> = <
83175
Method extends HttpMethod,
84176
Path extends PathsWithMethod<Paths, Method>,
85177
Init extends MaybeOptionalInit<Paths[Path], Method>,
86178
Response extends Required<FetchResponse<Paths[Path][Method], Init, Media>>, // note: Required is used to avoid repeating NonNullable in UseQuery types
87179
Options extends Omit<
88180
UseQueryOptions<
89-
Response["data"],
90-
Response["error"],
91-
InferSelectReturnType<Response["data"], Options["select"]>,
181+
Response['data'],
182+
Response['error'],
183+
InferSelectReturnType<Response['data'], Options['select']>,
92184
QueryKey<Paths, Method, Path>
93185
>,
94-
"queryKey" | "queryFn"
186+
'queryKey' | 'queryFn'
95187
>,
96188
>(
97189
method: Method,
98190
url: Path,
99191
...[init, options, queryClient]: RequiredKeysOf<Init> extends never
100192
? [InitWithUnknowns<Init>?, Options?, QueryClient?]
101193
: [InitWithUnknowns<Init>, Options?, QueryClient?]
102-
) => UseQueryResult<InferSelectReturnType<Response["data"], Options["select"]>, Response["error"]>;
194+
) => UseQueryResult<InferSelectReturnType<Response['data'], Options['select']>, Response['error']>;
103195

104-
export type UseInfiniteQueryMethod<Paths extends Record<string, Record<HttpMethod, {}>>, Media extends MediaType> = <
196+
export type UseInfiniteQueryMethod<
197+
Paths extends Record<string, Record<HttpMethod, {}>>,
198+
Media extends MediaType,
199+
> = <
105200
Method extends HttpMethod,
106201
Path extends PathsWithMethod<Paths, Method>,
107202
Init extends MaybeOptionalInit<Paths[Path], Method>,
108203
Response extends Required<FetchResponse<Paths[Path][Method], Init, Media>>,
109204
Options extends Omit<
110205
UseInfiniteQueryOptions<
111-
Response["data"],
112-
Response["error"],
113-
InferSelectReturnType<InfiniteData<Response["data"]>, Options["select"]>,
206+
Response['data'],
207+
Response['error'],
208+
InferSelectReturnType<InfiniteData<Response['data']>, Options['select']>,
114209
QueryKey<Paths, Method, Path>,
115210
unknown
116211
>,
117-
"queryKey" | "queryFn"
212+
'queryKey' | 'queryFn'
118213
> & {
119214
pageParamName?: string;
120215
},
@@ -123,49 +218,62 @@ export type UseInfiniteQueryMethod<Paths extends Record<string, Record<HttpMetho
123218
url: Path,
124219
init: InitWithUnknowns<Init>,
125220
options: Options,
126-
queryClient?: QueryClient,
221+
queryClient?: QueryClient
127222
) => UseInfiniteQueryResult<
128-
InferSelectReturnType<InfiniteData<Response["data"]>, Options["select"]>,
129-
Response["error"]
223+
InferSelectReturnType<InfiniteData<Response['data']>, Options['select']>,
224+
Response['error']
130225
>;
131226

132-
export type UseSuspenseQueryMethod<Paths extends Record<string, Record<HttpMethod, {}>>, Media extends MediaType> = <
227+
export type UseSuspenseQueryMethod<
228+
Paths extends Record<string, Record<HttpMethod, {}>>,
229+
Media extends MediaType,
230+
> = <
133231
Method extends HttpMethod,
134232
Path extends PathsWithMethod<Paths, Method>,
135233
Init extends MaybeOptionalInit<Paths[Path], Method>,
136234
Response extends Required<FetchResponse<Paths[Path][Method], Init, Media>>, // note: Required is used to avoid repeating NonNullable in UseQuery types
137235
Options extends Omit<
138236
UseSuspenseQueryOptions<
139-
Response["data"],
140-
Response["error"],
141-
InferSelectReturnType<Response["data"], Options["select"]>,
237+
Response['data'],
238+
Response['error'],
239+
InferSelectReturnType<Response['data'], Options['select']>,
142240
QueryKey<Paths, Method, Path>
143241
>,
144-
"queryKey" | "queryFn"
242+
'queryKey' | 'queryFn'
145243
>,
146244
>(
147245
method: Method,
148246
url: Path,
149247
...[init, options, queryClient]: RequiredKeysOf<Init> extends never
150248
? [InitWithUnknowns<Init>?, Options?, QueryClient?]
151249
: [InitWithUnknowns<Init>, Options?, QueryClient?]
152-
) => UseSuspenseQueryResult<InferSelectReturnType<Response["data"], Options["select"]>, Response["error"]>;
250+
) => UseSuspenseQueryResult<
251+
InferSelectReturnType<Response['data'], Options['select']>,
252+
Response['error']
253+
>;
153254

154-
export type UseMutationMethod<Paths extends Record<string, Record<HttpMethod, {}>>, Media extends MediaType> = <
255+
export type UseMutationMethod<
256+
Paths extends Record<string, Record<HttpMethod, {}>>,
257+
Media extends MediaType,
258+
> = <
155259
Method extends HttpMethod,
156260
Path extends PathsWithMethod<Paths, Method>,
157261
Init extends MaybeOptionalInit<Paths[Path], Method>,
158262
Response extends Required<FetchResponse<Paths[Path][Method], Init, Media>>, // note: Required is used to avoid repeating NonNullable in UseQuery types
159-
Options extends Omit<UseMutationOptions<Response["data"], Response["error"], Init>, "mutationKey" | "mutationFn">,
263+
Options extends Omit<
264+
UseMutationOptions<Response['data'], Response['error'], Init>,
265+
'mutationKey' | 'mutationFn'
266+
>,
160267
>(
161268
method: Method,
162269
url: Path,
163270
options?: Options,
164-
queryClient?: QueryClient,
165-
) => UseMutationResult<Response["data"], Response["error"], Init>;
271+
queryClient?: QueryClient
272+
) => UseMutationResult<Response['data'], Response['error'], Init>;
166273

167274
export interface OpenapiQueryClient<Paths extends {}, Media extends MediaType = MediaType> {
168275
queryOptions: QueryOptionsFunction<Paths, Media>;
276+
mutationOptions: MutationOptionsFunction<Paths, Media>;
169277
useQuery: UseQueryMethod<Paths, Media>;
170278
useSuspenseQuery: UseSuspenseQueryMethod<Paths, Media>;
171279
useInfiniteQuery: UseInfiniteQueryMethod<Paths, Media>;
@@ -179,13 +287,17 @@ export type MethodResponse<
179287
? PathsWithMethod<Paths, Method>
180288
: never,
181289
Options = object,
182-
> = CreatedClient extends OpenapiQueryClient<infer Paths extends { [key: string]: any }, infer Media extends MediaType>
183-
? NonNullable<FetchResponse<Paths[Path][Method], Options, Media>["data"]>
184-
: never;
290+
> =
291+
CreatedClient extends OpenapiQueryClient<
292+
infer Paths extends { [key: string]: any },
293+
infer Media extends MediaType
294+
>
295+
? NonNullable<FetchResponse<Paths[Path][Method], Options, Media>['data']>
296+
: never;
185297

186298
// TODO: Add the ability to bring queryClient as argument
187299
export default function createClient<Paths extends {}, Media extends MediaType = MediaType>(
188-
client: FetchClient<Paths, Media>,
300+
client: FetchClient<Paths, Media>
189301
): OpenapiQueryClient<Paths, Media> {
190302
const queryFn = async <Method extends HttpMethod, Path extends PathsWithMethod<Paths, Method>>({
191303
queryKey: [method, path, init],
@@ -197,27 +309,54 @@ export default function createClient<Paths extends {}, Media extends MediaType =
197309
if (error) {
198310
throw error;
199311
}
200-
if (response.status === 204 || response.headers.get("Content-Length") === "0") {
312+
if (response.status === 204 || response.headers.get('Content-Length') === '0') {
201313
return data ?? null;
202314
}
203315

204316
return data;
205317
};
206318

319+
const createMutationFn = <
320+
Method extends HttpMethod,
321+
Path extends PathsWithMethod<Paths, Method>
322+
>(
323+
method: Method,
324+
path: Path
325+
) => {
326+
return async (init: any) => {
327+
const mth = method.toUpperCase() as Uppercase<typeof method>;
328+
const fn = client[mth] as ClientMethod<Paths, typeof method, Media>;
329+
const { data, error } = await fn(path, init as any);
330+
if (error) {
331+
throw error;
332+
}
333+
334+
return data as Exclude<typeof data, undefined>;
335+
};
336+
};
337+
207338
const queryOptions: QueryOptionsFunction<Paths, Media> = (method, path, ...[init, options]) => ({
208-
queryKey: (init === undefined ? ([method, path] as const) : ([method, path, init] as const)) as QueryKey<
209-
Paths,
210-
typeof method,
211-
typeof path
212-
>,
339+
queryKey: (init === undefined
340+
? ([method, path] as const)
341+
: ([method, path, init] as const)) as QueryKey<Paths, typeof method, typeof path>,
213342
queryFn,
214343
...options,
215344
});
216345

346+
const mutationOptions: MutationOptionsFunction<Paths, Media> = (method, path, options) => ({
347+
mutationKey: [method, path] as MutationKey<typeof method, typeof path>,
348+
mutationFn: createMutationFn(method, path),
349+
...options,
350+
});
351+
217352
return {
218353
queryOptions,
354+
mutationOptions,
219355
useQuery: (method, path, ...[init, options, queryClient]) =>
220-
useQuery(queryOptions(method, path, init as InitWithUnknowns<typeof init>, options), queryClient),
356+
useQuery(
357+
queryOptions(method, path, init as InitWithUnknowns<typeof init>, options),
358+
queryClient
359+
),
221360
useSuspenseQuery: (method, path, ...[init, options, queryClient]) =>
222361
useSuspenseQuery(queryOptions(method, path, init as InitWithUnknowns<typeof init>, options), queryClient),
223362
useInfiniteQuery: (method, path, init, options, queryClient) => {
@@ -256,19 +395,10 @@ export default function createClient<Paths extends {}, Media extends MediaType =
256395
useMutation(
257396
{
258397
mutationKey: [method, path],
259-
mutationFn: async (init) => {
260-
const mth = method.toUpperCase() as Uppercase<typeof method>;
261-
const fn = client[mth] as ClientMethod<Paths, typeof method, Media>;
262-
const { data, error } = await fn(path, init as InitWithUnknowns<typeof init>);
263-
if (error) {
264-
throw error;
265-
}
266-
267-
return data as Exclude<typeof data, undefined>;
268-
},
398+
mutationFn: createMutationFn(method, path),
269399
...options,
270400
},
271-
queryClient,
401+
queryClient
272402
),
273403
};
274-
}
404+
}

0 commit comments

Comments
 (0)