Skip to content

feat: Type-safe function registry with automatic input/output type inference #7

@tapava

Description

@tapava

Currently, both kit.run() and React hooks like useCompute require manually specifying generic type parameters. There is no automatic type inference based on registered function names:

// Current: manual type specification required
kit.register('sum', (nums: number[]) => nums.reduce((a, b) => a + b, 0));
const result = await kit.run<number[], number>('sum', [1, 2, 3]); // have to specify types

// React
const { data } = useCompute<number[], number>('sum'); // data is typed, but manually

The return value is effectively unknown unless the user explicitly provides the types.

Potential solution:
Implement a builder pattern with type accumulation so that register() builds up a type registry that run() can infer from

// Desired: automatic inference
const kit = new ComputeKit()
  .register('sum', (nums: number[]) => nums.reduce((a, b) => a + b, 0))
  .register('uppercase', (str: string) => str.toUpperCase());

const sum = await kit.run('sum', [1, 2, 3]);       //  inferred as number
const upper = await kit.run('uppercase', 'hello'); //  inferred as string
await kit.run('sum', 'wrong');                     // Type error
await kit.run('notExist', data);                   // Type error: invalid function name

Potential approach:

class ComputeKit<TRegistry extends Record<string, { input: unknown; output: unknown }> = {}> {
  
  register<TName extends string, TInput, TOutput>(
    name: TName,
    fn: (input: TInput) => TOutput | Promise<TOutput>
  ): ComputeKit<TRegistry & { [K in TName]: { input: TInput; output: TOutput } }> {
    // ... registration logic
    return this as any;
  }

  async run<TName extends keyof TRegistry>(
    name: TName,
    input: TRegistry[TName]['input']
  ): Promise<TRegistry[TName]['output']> {
    // ... execution logic
  }
}

This enhancement will impact:

  • Core: ComputeKit.register() and ComputeKit.run()
  • React: useCompute, useComputeCallback, useComputeFunction
  • React: usePipeline, useParallelBatch
  • Documentation updates

Let's think of these items before implementation:

  • Each .register() call returns a new type (builder pattern).. this requires good understanding of the builder pattern ( Used in libraries Zod)
  • Users must chain registrations or reassign: kit = kit.register(...) => not sure if this way is the cleanest.
  • Do we need to maintain backward compatibility with current untyped usage ?!

Metadata

Metadata

Assignees

No one assigned

    Labels

    enhancementNew feature or requesthelp wantedExtra attention is needed

    Projects

    No projects

    Milestone

    Relationships

    None yet

    Development

    No branches or pull requests

    Issue actions