Skip to content

Commit afb2cf0

Browse files
authored
Merge pull request #12 from tapava/feature/typed-registry
feat: enhance type safety in useComputeQuery and useComputeMutation
2 parents e1692eb + 6ad8de3 commit afb2cf0

File tree

1 file changed

+125
-56
lines changed

1 file changed

+125
-56
lines changed

packages/react-query/src/index.tsx

Lines changed: 125 additions & 56 deletions
Original file line numberDiff line numberDiff line change
@@ -15,6 +15,10 @@ import {
1515
ComputeKit,
1616
type ComputeKitOptions,
1717
type ComputeOptions,
18+
type ComputeFunctionRegistry,
19+
type RegisteredFunctionName,
20+
type FunctionInput,
21+
type FunctionOutput,
1822
} from '@computekit/core';
1923

2024
// ============================================================================
@@ -100,39 +104,48 @@ export interface UseComputeQueryOptions<TOutput> extends Omit<
100104
*
101105
* @example
102106
* ```tsx
103-
* // First, register the function
104-
* kit.register('fibonacci', (n: number) => {
105-
* let a = 0, b = 1;
106-
* for (let i = 2; i <= n; i++) [a, b] = [b, a + b];
107-
* return b;
108-
* });
107+
* // Basic usage with explicit types
108+
* const { data, isLoading } = useComputeQuery<number, number>('fibonacci', 50);
109109
*
110-
* // Then use it in your component
111-
* function Fibonacci({ n }: { n: number }) {
112-
* const { data, isLoading, error } = useComputeQuery('fibonacci', n);
113-
*
114-
* if (isLoading) return <div>Computing...</div>;
115-
* if (error) return <div>Error: {error.message}</div>;
116-
* return <div>Result: {data}</div>;
117-
* }
110+
* // With typed registry - types are inferred!
111+
* // declare module '@computekit/core' {
112+
* // interface ComputeFunctionRegistry {
113+
* // fibonacci: { input: number; output: number };
114+
* // }
115+
* // }
116+
* // const { data } = useComputeQuery('fibonacci', 50); // data is number
118117
* ```
119118
*/
120-
export function useComputeQuery<TInput, TOutput>(
119+
export function useComputeQuery<
120+
TName extends RegisteredFunctionName,
121+
TInput = FunctionInput<TName extends string ? TName : never>,
122+
TOutput = FunctionOutput<TName extends string ? TName : never>,
123+
>(
121124
/** Name of the registered compute function */
122-
name: string,
125+
name: TName,
123126
/** Input to pass to the function */
124-
input: TInput,
127+
input: TName extends keyof ComputeFunctionRegistry
128+
? ComputeFunctionRegistry[TName]['input']
129+
: TInput,
125130
/** React Query and ComputeKit options */
126-
options?: UseComputeQueryOptions<TOutput>
131+
options?: UseComputeQueryOptions<
132+
TName extends keyof ComputeFunctionRegistry
133+
? ComputeFunctionRegistry[TName]['output']
134+
: TOutput
135+
>
127136
) {
137+
type ActualOutput = TName extends keyof ComputeFunctionRegistry
138+
? ComputeFunctionRegistry[TName]['output']
139+
: TOutput;
140+
128141
const kit = useComputeKit();
129142
const { computeOptions, ...queryOptions } = options ?? {};
130143

131-
return useQuery<TOutput, Error>({
144+
return useQuery<ActualOutput, Error>({
132145
queryKey: ['compute', name, input] as const,
133146
queryFn: async () => {
134-
const result = await kit.run(name, input as never, computeOptions);
135-
return result as TOutput;
147+
const result = await kit.run(name, input, computeOptions);
148+
return result as ActualOutput;
136149
},
137150
...queryOptions,
138151
});
@@ -158,33 +171,45 @@ export interface UseComputeMutationOptions<TInput, TOutput> extends Omit<
158171
*
159172
* @example
160173
* ```tsx
161-
* function ImageProcessor() {
162-
* const { mutate, data, isPending, error } = useComputeMutation<ImageData, ImageData>('blur');
174+
* // Basic usage with explicit types
175+
* const { mutate, isPending } = useComputeMutation<ImageData, ImageData>('blur');
163176
*
164-
* return (
165-
* <div>
166-
* <button onClick={() => mutate(imageData)} disabled={isPending}>
167-
* {isPending ? 'Processing...' : 'Apply Blur'}
168-
* </button>
169-
* {data && <img src={data.url} />}
170-
* </div>
171-
* );
172-
* }
177+
* // With typed registry - types are inferred!
178+
* // const { mutate } = useComputeMutation('blur');
179+
* // mutate(imageData); // input type enforced
173180
* ```
174181
*/
175-
export function useComputeMutation<TInput, TOutput>(
182+
export function useComputeMutation<
183+
TName extends RegisteredFunctionName,
184+
TInput = FunctionInput<TName extends string ? TName : never>,
185+
TOutput = FunctionOutput<TName extends string ? TName : never>,
186+
>(
176187
/** Name of the registered compute function */
177-
name: string,
188+
name: TName,
178189
/** React Query and ComputeKit options */
179-
options?: UseComputeMutationOptions<TInput, TOutput>
190+
options?: UseComputeMutationOptions<
191+
TName extends keyof ComputeFunctionRegistry
192+
? ComputeFunctionRegistry[TName]['input']
193+
: TInput,
194+
TName extends keyof ComputeFunctionRegistry
195+
? ComputeFunctionRegistry[TName]['output']
196+
: TOutput
197+
>
180198
) {
199+
type ActualInput = TName extends keyof ComputeFunctionRegistry
200+
? ComputeFunctionRegistry[TName]['input']
201+
: TInput;
202+
type ActualOutput = TName extends keyof ComputeFunctionRegistry
203+
? ComputeFunctionRegistry[TName]['output']
204+
: TOutput;
205+
181206
const kit = useComputeKit();
182207
const { computeOptions, ...mutationOptions } = options ?? {};
183208

184-
return useMutation<TOutput, Error, TInput>({
185-
mutationFn: async (input: TInput) => {
209+
return useMutation<ActualOutput, Error, ActualInput>({
210+
mutationFn: async (input: ActualInput) => {
186211
const result = await kit.run(name, input as never, computeOptions);
187-
return result as TOutput;
212+
return result as ActualOutput;
188213
},
189214
...mutationOptions,
190215
});
@@ -210,31 +235,46 @@ export function useComputeMutation<TInput, TOutput>(
210235
*
211236
* const { useQuery, useMutation } = createComputeHooks(kit);
212237
*
213-
* function MyComponent() {
214-
* const { data } = useQuery('fibonacci', 50);
215-
* return <div>{data}</div>;
216-
* }
238+
* // With typed registry - types are inferred!
239+
* const { data } = useQuery('fibonacci', 50); // data is number
217240
* ```
218241
*/
219242
export function createComputeHooks(kit: ComputeKit) {
220243
return {
221244
/**
222245
* Query hook bound to this ComputeKit instance
223246
*/
224-
useQuery: <TInput, TOutput>(
225-
name: string,
226-
input: TInput,
227-
options?: Omit<UseComputeQueryOptions<TOutput>, 'computeOptions'> & {
247+
useQuery: <
248+
TName extends RegisteredFunctionName,
249+
TInput = FunctionInput<TName extends string ? TName : never>,
250+
TOutput = FunctionOutput<TName extends string ? TName : never>,
251+
>(
252+
name: TName,
253+
input: TName extends keyof ComputeFunctionRegistry
254+
? ComputeFunctionRegistry[TName]['input']
255+
: TInput,
256+
options?: Omit<
257+
UseComputeQueryOptions<
258+
TName extends keyof ComputeFunctionRegistry
259+
? ComputeFunctionRegistry[TName]['output']
260+
: TOutput
261+
>,
262+
'computeOptions'
263+
> & {
228264
computeOptions?: ComputeOptions;
229265
}
230266
) => {
267+
type ActualOutput = TName extends keyof ComputeFunctionRegistry
268+
? ComputeFunctionRegistry[TName]['output']
269+
: TOutput;
270+
231271
const { computeOptions, ...queryOptions } = options ?? {};
232272

233-
return useQuery<TOutput, Error>({
273+
return useQuery<ActualOutput, Error>({
234274
queryKey: ['compute', name, input] as const,
235275
queryFn: async () => {
236-
const result = await kit.run(name, input as never, computeOptions);
237-
return result as TOutput;
276+
const result = await kit.run(name, input, computeOptions);
277+
return result as ActualOutput;
238278
},
239279
...queryOptions,
240280
});
@@ -243,18 +283,39 @@ export function createComputeHooks(kit: ComputeKit) {
243283
/**
244284
* Mutation hook bound to this ComputeKit instance
245285
*/
246-
useMutation: <TInput, TOutput>(
247-
name: string,
248-
options?: Omit<UseComputeMutationOptions<TInput, TOutput>, 'computeOptions'> & {
286+
useMutation: <
287+
TName extends RegisteredFunctionName,
288+
TInput = FunctionInput<TName extends string ? TName : never>,
289+
TOutput = FunctionOutput<TName extends string ? TName : never>,
290+
>(
291+
name: TName,
292+
options?: Omit<
293+
UseComputeMutationOptions<
294+
TName extends keyof ComputeFunctionRegistry
295+
? ComputeFunctionRegistry[TName]['input']
296+
: TInput,
297+
TName extends keyof ComputeFunctionRegistry
298+
? ComputeFunctionRegistry[TName]['output']
299+
: TOutput
300+
>,
301+
'computeOptions'
302+
> & {
249303
computeOptions?: ComputeOptions;
250304
}
251305
) => {
306+
type ActualInput = TName extends keyof ComputeFunctionRegistry
307+
? ComputeFunctionRegistry[TName]['input']
308+
: TInput;
309+
type ActualOutput = TName extends keyof ComputeFunctionRegistry
310+
? ComputeFunctionRegistry[TName]['output']
311+
: TOutput;
312+
252313
const { computeOptions, ...mutationOptions } = options ?? {};
253314

254-
return useMutation<TOutput, Error, TInput>({
255-
mutationFn: async (input: TInput) => {
315+
return useMutation<ActualOutput, Error, ActualInput>({
316+
mutationFn: async (input: ActualInput) => {
256317
const result = await kit.run(name, input as never, computeOptions);
257-
return result as TOutput;
318+
return result as ActualOutput;
258319
},
259320
...mutationOptions,
260321
});
@@ -269,5 +330,13 @@ export function createComputeHooks(kit: ComputeKit) {
269330
// Exports
270331
// ============================================================================
271332

272-
export type { ComputeKitOptions, ComputeOptions } from '@computekit/core';
333+
export type {
334+
ComputeKitOptions,
335+
ComputeOptions,
336+
// Typed registry exports
337+
ComputeFunctionRegistry,
338+
RegisteredFunctionName,
339+
FunctionInput,
340+
FunctionOutput,
341+
} from '@computekit/core';
273342
export { ComputeKit } from '@computekit/core';

0 commit comments

Comments
 (0)