diff --git a/README.md b/README.md index 3c6d4e5..c92868e 100644 --- a/README.md +++ b/README.md @@ -28,7 +28,7 @@ - 🚀 **Non-blocking** : Everything runs in Web Workers - 🔧 **Zero config** : No manual worker files or postMessage handlers - 📦 **Tiny** : Core library is ~5KB gzipped -- 🎯 **TypeScript** : Full type safety for your compute functions and WASM bindings +- 🎯 **TypeScript** : Full type safety with [typed registry](#-typed-registry) for autocomplete and compile-time checks - 📊 **Progress tracking** : Built-in progress reporting for long-running tasks --- @@ -313,6 +313,53 @@ const { progress, run } = useCompute('longTask', { --- +## 🏷️ Typed Registry + +Get **autocomplete** and **type safety** for your compute functions by extending the `ComputeFunctionRegistry` interface: + +```typescript +// Extend the registry (in a .d.ts file or at the top of your file) +declare module '@computekit/core' { + interface ComputeFunctionRegistry { + fibonacci: { input: number; output: number }; + sum: { input: number[]; output: number }; + } +} +``` + +Now you get full type inference: + +```typescript +// ✅ Types are inferred - no need for generics! +kit.register('fibonacci', (n) => { + // n is inferred as number + if (n <= 1) return n; + let a = 0, + b = 1; + for (let i = 2; i <= n; i++) { + [a, b] = [b, a + b]; + } + return b; +}); + +const result = await kit.run('fibonacci', 50); // result is number + +// ❌ TypeScript error: Argument of type 'string' is not assignable +await kit.run('fibonacci', 'not a number'); +``` + +Works with React hooks too: + +```tsx +// Types inferred from registry +const { data, run } = useCompute('fibonacci'); +// data: number | null, run: (n: number) => void +``` + +See the [API Reference](https://tapava.github.io/compute-kit/api-reference#typed-registry) for more details. + +--- + ## 📖 API ### `ComputeKit` diff --git a/docs/api-reference.md b/docs/api-reference.md index 5635d46..79310ac 100644 --- a/docs/api-reference.md +++ b/docs/api-reference.md @@ -46,6 +46,122 @@ new ComputeKit(options?: ComputeKitOptions) --- +## Typed Registry + +ComputeKit supports **type-safe function registration** using TypeScript's declaration merging. This provides: + +- **Autocomplete** for registered function names +- **Type inference** for input and output types +- **Compile-time errors** for incorrect usage + +### Setting Up the Typed Registry + +Extend the `ComputeFunctionRegistry` interface to define your function signatures: + +```typescript +// In a .d.ts file or at the top of your main file +declare module '@computekit/core' { + interface ComputeFunctionRegistry { + fibonacci: { input: number; output: number }; + sum: { input: number[]; output: number }; + processData: { input: { items: string[] }; output: { count: number } }; + } +} +``` + +### Using DefineFunction Helper + +For cleaner syntax, use the `DefineFunction` type helper: + +```typescript +import type { DefineFunction } from '@computekit/core'; + +declare module '@computekit/core' { + interface ComputeFunctionRegistry { + fibonacci: DefineFunction; + sum: DefineFunction; + processData: DefineFunction<{ items: string[] }, { count: number }>; + } +} +``` + +### Benefits + +Once you've extended the registry, you get full type safety: + +```typescript +import { ComputeKit } from '@computekit/core'; + +const kit = new ComputeKit(); + +// ✅ Types are inferred - no need for generics! +kit.register('fibonacci', (n) => { + // n is inferred as number + if (n <= 1) return n; + let a = 0, + b = 1; + for (let i = 2; i <= n; i++) { + [a, b] = [b, a + b]; + } + return b; // return type must be number +}); + +// ✅ Input type is enforced +const result = await kit.run('fibonacci', 50); // result is number + +// ❌ TypeScript error: 'unknownFunc' is not in the registry +await kit.run('unknownFunc', 42); + +// ❌ TypeScript error: Argument of type 'string' is not assignable to 'number' +await kit.run('fibonacci', 'not a number'); +``` + +### With React Hooks + +The typed registry works seamlessly with React hooks: + +```tsx +import { useCompute, useComputeCallback } from '@computekit/react'; + +function FibonacciCalculator() { + // ✅ Types are inferred from the registry + const { data, loading, run } = useCompute('fibonacci'); + // data is number | null + // run expects (input: number) => void + + return ( + + ); +} +``` + +### Registry Type Helpers + +| Type | Description | +| ------------------------- | ---------------------------------------------------------- | +| `ComputeFunctionRegistry` | The base interface to extend with your functions | +| `RegisteredFunctionName` | Union of all registered function names | +| `FunctionInput` | Get the input type for a function | +| `FunctionOutput` | Get the output type for a function | +| `DefineFunction` | Helper to define `{ input: I; output: O }` | +| `ComputeFn` | Type for compute functions `(input: I) => O \| Promise` | +| `InferComputeFn` | Infer the full function type from a name | +| `HasRegisteredFunctions` | Boolean type indicating if registry has entries | + +### Fallback Behavior + +If you don't extend the registry, ComputeKit falls back to the original behavior with explicit generics: + +```typescript +// Still works without registry extension +const { data, run } = useCompute('fibonacci'); +const result = await kit.run('fibonacci', 50); +``` + +--- + ## Methods ### initialize() diff --git a/examples/react-demo/src/demos/ParallelBlurDemo.tsx b/examples/react-demo/src/demos/ParallelBlurDemo.tsx index 8ec8a6f..2fc62b8 100644 --- a/examples/react-demo/src/demos/ParallelBlurDemo.tsx +++ b/examples/react-demo/src/demos/ParallelBlurDemo.tsx @@ -168,12 +168,12 @@ export function ParallelBlurDemo() { const promises = sourceImages.map(async (img, index) => { const start = performance.now(); - const result = await kit.run('blurImage', { + const result = (await kit.run('blurImage', { imageData: Array.from(img), width: imageSize, height: imageSize, passes: blurPasses, - }); + })) as number[]; const time = performance.now() - start; return { id: index, diff --git a/packages/core/src/index.ts b/packages/core/src/index.ts index cce2b7c..b878bd7 100644 --- a/packages/core/src/index.ts +++ b/packages/core/src/index.ts @@ -11,6 +11,14 @@ import type { ComputeKitEvents, } from './types'; +import type { + ComputeFunctionRegistry, + RegisteredFunctionName, + FunctionInput, + FunctionOutput, + ComputeFn, +} from './registry'; + import { WorkerPool } from './pool'; import { EventEmitter, isWasmSupported, createLogger } from './utils'; @@ -60,48 +68,79 @@ export class ComputeKit extends EventEmitter { /** * Register a compute function * - * @param name - Unique name for the function + * @param name - Unique name for the function (autocompletes if registry is extended) * @param fn - The function to execute (will run in a Web Worker) * * @example * ```ts + * // Basic usage * kit.register('sum', (arr: number[]) => arr.reduce((a, b) => a + b, 0)); + * + * // With typed registry (extend ComputeFunctionRegistry for autocomplete) + * // declare module '@computekit/core' { + * // interface ComputeFunctionRegistry { + * // sum: { input: number[]; output: number }; + * // } + * // } + * // kit.register('sum', (arr) => arr.reduce((a, b) => a + b, 0)); * ``` */ - register( - name: string, - fn: (input: TInput) => TOutput | Promise + register< + TName extends RegisteredFunctionName, + TInput = FunctionInput, + TOutput = FunctionOutput, + >( + name: TName, + fn: TName extends keyof ComputeFunctionRegistry + ? ComputeFn< + ComputeFunctionRegistry[TName]['input'], + ComputeFunctionRegistry[TName]['output'] + > + : ComputeFn ): this { - this.pool.register(name, fn); + this.pool.register(name as string, fn as ComputeFn); return this; } /** * Execute a registered compute function * - * @param name - Name of the registered function - * @param input - Input data for the function + * @param name - Name of the registered function (autocompletes if registry is extended) + * @param input - Input data for the function (type-safe if registry is extended) * @param options - Execution options - * @returns Promise resolving to the function result + * @returns Promise resolving to the function result (type-safe if registry is extended) * * @example * ```ts * const sum = await kit.run('sum', [1, 2, 3, 4, 5]); + * + * // With typed registry, input/output types are inferred: + * // const result = await kit.run('fibonacci', 50); // result: number * ``` */ - async run( - name: string, - input: TInput, + async run< + TName extends RegisteredFunctionName, + TInput = FunctionInput, + TOutput = FunctionOutput, + >( + name: TName, + input: TName extends keyof ComputeFunctionRegistry + ? ComputeFunctionRegistry[TName]['input'] + : TInput, options?: ComputeOptions - ): Promise { - return this.pool.execute(name, input, options); + ): Promise< + TName extends keyof ComputeFunctionRegistry + ? ComputeFunctionRegistry[TName]['output'] + : TOutput + > { + return this.pool.execute(name as string, input, options); } /** * Execute a registered compute function with full result metadata * - * @param name - Name of the registered function - * @param input - Input data for the function + * @param name - Name of the registered function (autocompletes if registry is extended) + * @param input - Input data for the function (type-safe if registry is extended) * @param options - Execution options * @returns Promise resolving to ComputeResult with metadata * @@ -111,13 +150,33 @@ export class ComputeKit extends EventEmitter { * console.log(`Took ${result.duration}ms`); * ``` */ - async runWithMetadata( - name: string, - input: TInput, + async runWithMetadata< + TName extends RegisteredFunctionName, + TInput = FunctionInput, + TOutput = FunctionOutput, + >( + name: TName, + input: TName extends keyof ComputeFunctionRegistry + ? ComputeFunctionRegistry[TName]['input'] + : TInput, options?: ComputeOptions - ): Promise> { + ): Promise< + ComputeResult< + TName extends keyof ComputeFunctionRegistry + ? ComputeFunctionRegistry[TName]['output'] + : TOutput + > + > { + type ActualOutput = TName extends keyof ComputeFunctionRegistry + ? ComputeFunctionRegistry[TName]['output'] + : TOutput; + const startTime = performance.now(); - const data = await this.pool.execute(name, input, options); + const data = (await this.pool.execute( + name as string, + input, + options + )) as ActualOutput; const duration = performance.now() - startTime; return { @@ -175,23 +234,64 @@ export function getDefaultInstance(): ComputeKit { /** * Register a function on the default instance + * + * @example + * ```ts + * import { register } from '@computekit/core'; + * + * register('fibonacci', (n: number) => { + * if (n <= 1) return n; + * let a = 0, b = 1; + * for (let i = 2; i <= n; i++) { + * [a, b] = [b, a + b]; + * } + * return b; + * }); + * ``` */ -export function register( - name: string, - fn: (input: TInput) => TOutput | Promise +export function register< + TName extends RegisteredFunctionName, + TInput = FunctionInput, + TOutput = FunctionOutput, +>( + name: TName, + fn: TName extends keyof ComputeFunctionRegistry + ? ComputeFn< + ComputeFunctionRegistry[TName]['input'], + ComputeFunctionRegistry[TName]['output'] + > + : ComputeFn ): void { getDefaultInstance().register(name, fn); } /** * Run a function on the default instance + * + * @example + * ```ts + * import { run } from '@computekit/core'; + * + * const result = await run('fibonacci', 50); + * console.log(result); // Type is inferred if registry is extended + * ``` */ -export async function run( - name: string, - input: TInput, +export async function run< + TName extends RegisteredFunctionName, + TInput = FunctionInput, + TOutput = FunctionOutput, +>( + name: TName, + input: TName extends keyof ComputeFunctionRegistry + ? ComputeFunctionRegistry[TName]['input'] + : TInput, options?: ComputeOptions -): Promise { - return getDefaultInstance().run(name, input, options); +): Promise< + TName extends keyof ComputeFunctionRegistry + ? ComputeFunctionRegistry[TName]['output'] + : TOutput +> { + return getDefaultInstance().run(name, input, options); } // Re-export types @@ -221,6 +321,18 @@ export type { ParallelBatchResult, } from './types'; +// Re-export typed registry types +export type { + ComputeFunctionRegistry, + RegisteredFunctionName, + FunctionInput, + FunctionOutput, + ComputeFn, + InferComputeFn, + DefineFunction, + HasRegisteredFunctions, +} from './registry'; + // Re-export utilities export { isWasmSupported, diff --git a/packages/core/src/registry.ts b/packages/core/src/registry.ts new file mode 100644 index 0000000..472e187 --- /dev/null +++ b/packages/core/src/registry.ts @@ -0,0 +1,132 @@ +/** + * ComputeKit Typed Registry + * + * This module provides type-safe function registration and execution. + * Users can extend the ComputeFunctionRegistry interface to get autocomplete + * and type safety for their registered functions. + * + * @example + * ```ts + * // Extend the registry interface (in a .d.ts file or at the top of your file) + * declare module '@computekit/core' { + * interface ComputeFunctionRegistry { + * fibonacci: { input: number; output: number }; + * sum: { input: number[]; output: number }; + * processData: { input: { items: string[] }; output: { count: number } }; + * } + * } + * + * // Now you get autocomplete and type safety! + * const kit = new ComputeKit(); + * kit.register('fibonacci', (n) => ...); // n is inferred as number + * const result = await kit.run('fibonacci', 42); // result is number + * ``` + */ + +/** + * Registry interface for compute functions. + * Extend this interface using module augmentation to add your own functions. + * + * Each entry should be in the format: + * ```ts + * functionName: { input: InputType; output: OutputType } + * ``` + * + * @example + * ```ts + * declare module '@computekit/core' { + * interface ComputeFunctionRegistry { + * myFunction: { input: string; output: number }; + * } + * } + * ``` + */ +// eslint-disable-next-line @typescript-eslint/no-empty-object-type +export interface ComputeFunctionRegistry {} + +/** + * Helper type to get all registered function names. + * If no functions are registered, falls back to string. + */ +export type RegisteredFunctionName = keyof ComputeFunctionRegistry extends never + ? string + : keyof ComputeFunctionRegistry; + +/** + * Helper type to check if the registry has any entries. + */ +export type HasRegisteredFunctions = keyof ComputeFunctionRegistry extends never + ? false + : true; + +/** + * Get the input type for a registered function. + * Falls back to TFallback if the function is not registered. + */ +export type FunctionInput< + TName extends string, + TFallback = unknown, +> = TName extends keyof ComputeFunctionRegistry + ? ComputeFunctionRegistry[TName]['input'] + : TFallback; + +/** + * Get the output type for a registered function. + * Falls back to TFallback if the function is not registered. + */ +export type FunctionOutput< + TName extends string, + TFallback = unknown, +> = TName extends keyof ComputeFunctionRegistry + ? ComputeFunctionRegistry[TName]['output'] + : TFallback; + +/** + * Type for a compute function based on registry or explicit types. + */ +export type ComputeFn = (input: TInput) => TOutput | Promise; + +/** + * Infer the compute function type for a registered function name. + */ +export type InferComputeFn = + TName extends keyof ComputeFunctionRegistry + ? ComputeFn< + ComputeFunctionRegistry[TName]['input'], + ComputeFunctionRegistry[TName]['output'] + > + : ComputeFn; + +/** + * Type helper for creating registry entries. + * Use this to define your function types more easily. + * + * @example + * ```ts + * declare module '@computekit/core' { + * interface ComputeFunctionRegistry { + * fibonacci: DefineFunction; + * sum: DefineFunction; + * } + * } + * ``` + */ +export type DefineFunction = { + input: TInput; + output: TOutput; +}; + +/** + * Helper type for typing the register method. + * Provides proper type inference based on whether the function is in the registry. + */ +export type RegisterFn< + TName extends string, + TInput, + TOutput, +> = TName extends keyof ComputeFunctionRegistry + ? ComputeFn< + ComputeFunctionRegistry[TName]['input'], + ComputeFunctionRegistry[TName]['output'] + > + : ComputeFn; diff --git a/packages/react-query/src/index.tsx b/packages/react-query/src/index.tsx index 13c75c6..7acd37c 100644 --- a/packages/react-query/src/index.tsx +++ b/packages/react-query/src/index.tsx @@ -131,8 +131,8 @@ export function useComputeQuery( return useQuery({ queryKey: ['compute', name, input] as const, queryFn: async () => { - const result = await kit.run(name, input, computeOptions); - return result; + const result = await kit.run(name, input as never, computeOptions); + return result as TOutput; }, ...queryOptions, }); @@ -183,8 +183,8 @@ export function useComputeMutation( return useMutation({ mutationFn: async (input: TInput) => { - const result = await kit.run(name, input, computeOptions); - return result; + const result = await kit.run(name, input as never, computeOptions); + return result as TOutput; }, ...mutationOptions, }); @@ -232,7 +232,10 @@ export function createComputeHooks(kit: ComputeKit) { return useQuery({ queryKey: ['compute', name, input] as const, - queryFn: async () => kit.run(name, input, computeOptions), + queryFn: async () => { + const result = await kit.run(name, input as never, computeOptions); + return result as TOutput; + }, ...queryOptions, }); }, @@ -249,8 +252,10 @@ export function createComputeHooks(kit: ComputeKit) { const { computeOptions, ...mutationOptions } = options ?? {}; return useMutation({ - mutationFn: async (input: TInput) => - kit.run(name, input, computeOptions), + mutationFn: async (input: TInput) => { + const result = await kit.run(name, input as never, computeOptions); + return result as TOutput; + }, ...mutationOptions, }); }, diff --git a/packages/react/src/index.tsx b/packages/react/src/index.tsx index 63e87a5..be4a846 100644 --- a/packages/react/src/index.tsx +++ b/packages/react/src/index.tsx @@ -20,6 +20,11 @@ import { type ComputeOptions, type ComputeProgress, type PoolStats, + type ComputeFunctionRegistry, + type RegisteredFunctionName, + type FunctionInput, + type FunctionOutput, + type ComputeFn, } from '@computekit/core'; // ============================================================================ @@ -303,6 +308,7 @@ export interface UseComputeOptions extends ComputeOptions { * * @example * ```tsx + * // Basic usage with explicit types * function FibonacciCalculator() { * const { data, loading, error, run } = useCompute('fibonacci'); * @@ -317,17 +323,43 @@ export interface UseComputeOptions extends ComputeOptions { * * ); * } + * + * // With typed registry (extend ComputeFunctionRegistry for autocomplete) + * // declare module '@computekit/core' { + * // interface ComputeFunctionRegistry { + * // fibonacci: { input: number; output: number }; + * // } + * // } + * // const { data, run } = useCompute('fibonacci'); // Types are inferred! * ``` */ -export function useCompute( - functionName: string, +export function useCompute< + TName extends RegisteredFunctionName, + TInput = FunctionInput, + TOutput = FunctionOutput, +>( + functionName: TName, options: UseComputeOptions = {} -): UseComputeReturn { +): UseComputeReturn< + TName extends keyof ComputeFunctionRegistry + ? ComputeFunctionRegistry[TName]['input'] + : TInput, + TName extends keyof ComputeFunctionRegistry + ? ComputeFunctionRegistry[TName]['output'] + : TOutput +> { + type ActualInput = TName extends keyof ComputeFunctionRegistry + ? ComputeFunctionRegistry[TName]['input'] + : TInput; + type ActualOutput = TName extends keyof ComputeFunctionRegistry + ? ComputeFunctionRegistry[TName]['output'] + : TOutput; + const kit = useComputeKit(); const abortControllerRef = useRef(null); const cancelledRef = useRef(false); - const [state, setState] = useState>({ + const [state, setState] = useState>({ data: null, loading: false, error: null, @@ -360,7 +392,7 @@ export function useCompute( }, []); const run = useCallback( - async (input: TInput, runOptions?: ComputeOptions) => { + async (input: ActualInput, runOptions?: ComputeOptions) => { // Cancel any ongoing computation cancel(); cancelledRef.current = false; @@ -383,7 +415,7 @@ export function useCompute( } try { - const result = await kit.run(functionName, input, { + const result = (await kit.run(functionName, input, { ...options, ...runOptions, signal: runOptions?.signal ?? abortController.signal, @@ -392,7 +424,7 @@ export function useCompute( options.onProgress?.(progress); runOptions?.onProgress?.(progress); }, - }); + })) as ActualOutput; if (!abortController.signal.aborted) { setState({ @@ -421,7 +453,7 @@ export function useCompute( // Auto-run on mount if configured useEffect(() => { if (options.autoRun && options.initialInput !== undefined) { - run(options.initialInput as TInput); + run(options.initialInput as ActualInput); } // Only run on mount // eslint-disable-next-line react-hooks/exhaustive-deps @@ -439,7 +471,7 @@ export function useCompute( run, reset, cancel, - }; + } as UseComputeReturn; } // ============================================================================ @@ -451,6 +483,7 @@ export function useCompute( * * @example * ```tsx + * // Basic usage with explicit types * function Calculator() { * const calculate = useComputeCallback('sum'); * @@ -461,20 +494,43 @@ export function useCompute( * * return ; * } + * + * // With typed registry - types are inferred! + * // const calculate = useComputeCallback('sum'); * ``` */ -export function useComputeCallback( - functionName: string, +export function useComputeCallback< + TName extends RegisteredFunctionName, + TInput = FunctionInput, + TOutput = FunctionOutput, +>( + functionName: TName, options?: ComputeOptions -): (input: TInput, runOptions?: ComputeOptions) => Promise { +): ( + input: TName extends keyof ComputeFunctionRegistry + ? ComputeFunctionRegistry[TName]['input'] + : TInput, + runOptions?: ComputeOptions +) => Promise< + TName extends keyof ComputeFunctionRegistry + ? ComputeFunctionRegistry[TName]['output'] + : TOutput +> { + type ActualInput = TName extends keyof ComputeFunctionRegistry + ? ComputeFunctionRegistry[TName]['input'] + : TInput; + type ActualOutput = TName extends keyof ComputeFunctionRegistry + ? ComputeFunctionRegistry[TName]['output'] + : TOutput; + const kit = useComputeKit(); return useCallback( - (input: TInput, runOptions?: ComputeOptions) => { - return kit.run(functionName, input, { + (input: ActualInput, runOptions?: ComputeOptions): Promise => { + return kit.run(functionName, input, { ...options, ...runOptions, - }); + }) as Promise; }, [kit, functionName, options] ); @@ -489,6 +545,7 @@ export function useComputeCallback( * * @example * ```tsx + * // Basic usage * function MyComponent() { * const { run, loading, data } = useComputeFunction( * 'myFunction', @@ -501,13 +558,36 @@ export function useComputeCallback( * * ); * } + * + * // With typed registry - provides autocomplete and type safety + * // declare module '@computekit/core' { + * // interface ComputeFunctionRegistry { + * // myFunction: { input: number; output: number }; + * // } + * // } * ``` */ -export function useComputeFunction( - name: string, - fn: (input: TInput) => TOutput | Promise, +export function useComputeFunction< + TName extends RegisteredFunctionName, + TInput = FunctionInput, + TOutput = FunctionOutput, +>( + name: TName, + fn: TName extends keyof ComputeFunctionRegistry + ? ComputeFn< + ComputeFunctionRegistry[TName]['input'], + ComputeFunctionRegistry[TName]['output'] + > + : ComputeFn, options?: UseComputeOptions -): UseComputeReturn { +): UseComputeReturn< + TName extends keyof ComputeFunctionRegistry + ? ComputeFunctionRegistry[TName]['input'] + : TInput, + TName extends keyof ComputeFunctionRegistry + ? ComputeFunctionRegistry[TName]['output'] + : TOutput +> { const kit = useComputeKit(); // Register function on mount @@ -515,7 +595,7 @@ export function useComputeFunction( kit.register(name, fn); }, [kit, name, fn]); - return useCompute(name, options); + return useCompute(name, options); } // ============================================================================ @@ -1397,6 +1477,7 @@ export interface UseParallelBatchReturn { * * @example * ```tsx + * // Basic usage with explicit types * function BatchProcessor() { * const batch = useParallelBatch('processFile', { * concurrency: 4 @@ -1427,20 +1508,41 @@ export interface UseParallelBatchReturn { * * ); * } + * + * // With typed registry - types are inferred! + * // const batch = useParallelBatch('processFile'); * ``` */ -export function useParallelBatch( - functionName: string, +export function useParallelBatch< + TName extends RegisteredFunctionName, + TItem = FunctionInput, + TOutput = FunctionOutput, +>( + functionName: TName, options: { concurrency?: number; computeOptions?: ComputeOptions; } = {} -): UseParallelBatchReturn { +): UseParallelBatchReturn< + TName extends keyof ComputeFunctionRegistry + ? ComputeFunctionRegistry[TName]['input'] + : TItem, + TName extends keyof ComputeFunctionRegistry + ? ComputeFunctionRegistry[TName]['output'] + : TOutput +> { + type ActualItem = TName extends keyof ComputeFunctionRegistry + ? ComputeFunctionRegistry[TName]['input'] + : TItem; + type ActualOutput = TName extends keyof ComputeFunctionRegistry + ? ComputeFunctionRegistry[TName]['output'] + : TOutput; + const kit = useComputeKit(); const abortControllerRef = useRef(null); const [state, setState] = useState<{ - result: ParallelBatchResult | null; + result: ParallelBatchResult | null; loading: boolean; progress: number; completedCount: number; @@ -1454,7 +1556,7 @@ export function useParallelBatch( }); const run = useCallback( - async (items: TItem[]): Promise> => { + async (items: ActualItem[]): Promise> => { // Cancel any existing batch if (abortControllerRef.current) { abortControllerRef.current.abort(); @@ -1472,7 +1574,7 @@ export function useParallelBatch( }); const startTime = performance.now(); - const results: BatchItemResult[] = []; + const results: BatchItemResult[] = []; const concurrency = options.concurrency ?? items.length; // Process in batches based on concurrency @@ -1487,12 +1589,12 @@ export function useParallelBatch( const itemStart = performance.now(); try { - const data = await kit.run(functionName, item, { + const data = (await kit.run(functionName, item, { ...options.computeOptions, signal: abortController.signal, - }); + })) as ActualOutput; - const itemResult: BatchItemResult = { + const itemResult: BatchItemResult = { index, success: true, data, @@ -1501,7 +1603,7 @@ export function useParallelBatch( return itemResult; } catch (err) { - const itemResult: BatchItemResult = { + const itemResult: BatchItemResult = { index, success: false, error: err instanceof Error ? err : new Error(String(err)), @@ -1527,12 +1629,12 @@ export function useParallelBatch( const totalDuration = performance.now() - startTime; const successful = results .filter((r) => r.success && r.data !== undefined) - .map((r) => r.data as TOutput); + .map((r) => r.data as ActualOutput); const failed = results .filter((r) => !r.success) .map((r) => ({ index: r.index, error: r.error! })); - const finalResult: ParallelBatchResult = { + const finalResult: ParallelBatchResult = { results, successful, failed, @@ -1587,7 +1689,7 @@ export function useParallelBatch( run, cancel, reset, - }; + } as UseParallelBatchReturn; } // ============================================================================ @@ -1599,6 +1701,15 @@ export type { ComputeOptions, ComputeProgress, PoolStats, + // Typed registry exports + ComputeFunctionRegistry, + RegisteredFunctionName, + FunctionInput, + FunctionOutput, + ComputeFn, + InferComputeFn, + DefineFunction, + HasRegisteredFunctions, } from '@computekit/core'; export { ComputeKit } from '@computekit/core';