Home > Back-end >  strongly typed dispatcher function
strongly typed dispatcher function

Time:08-18

I have a createDispatcher function that accepts a record of functions.
It will return a dispatch function that expects a record with a key corresponding to a key in the record fed to createDispatcher before.
Kinda hard to explain, but have a look at the examples below, I think it should become obvious.

const funcs = {
    upCase: (s: string) => s.toUpperCase(),
    double: (n: number) => 2*n,
}

const dispatch = createDispatcher(funcs)  // TBD

dispatch({upCase: "test"})             // OK: TEST
dispatch({double: 42})                 // OK: 84
dispatch({double: "test"})             // should be compile error
dispatch({foo: 0})                     // should be compile error
dispatch({upCase: "test", double: 42}) // should be compile error, exactly one key expected
dispatch({})                           // should be compile error, exactly one key expected

Below is my current implementation of createDispatcher.

function createDispatcher(funcRecord: Record<string, (input: any) => any>) {

    function dispatch(inputRecord: Record<string, any>) {
        for (const i in inputRecord) {
            const func = funcRecord[i]
            if (func !== undefined)
                return func(inputRecord[i])
        }
    }

    return dispatch
}

It works, but it's too weakly typed.
All the examples above type-check, whereas I only want 1 and 2 to be allowed.
Can anybody help?

CodePudding user response:

You can create a union starting from the original func record.

You can do this using a mapped type to step over all the keys in the func record. For each key we will create an object type that contains the key. We can then create a union with all these object types using an index operator (keyof T).

Now because of the way unions work, where you can specify a key of any constituent of the union without running into excess property checks, we need to add to each object type all properties from the func record as optional with type undefined, to ensure these are not assigned. You can read more here about the problem and the solution which is similar to what I used here:


type InputRecord<T extends Record<string, (input: any) => any>> = {
    [P in keyof T]: 
        // create an object type with the current key, typed as the first parameter of the function
        Record<P, Parameters<T[P]>[0]> 
        // Ensure no other fields are possible
        & Partial<Record<Exclude<keyof T, P>, undefined>>
}[keyof T]

function createDispatcher<T extends Record<string, (input: any) => any>>(funcRecord: T) {

    function dispatch(inputRecord: InputRecord<T>) {
        for (const i in inputRecord) {
            const func = funcRecord[i]
            if (func !== undefined)
                return func(inputRecord[i])
        }
    }

    return dispatch
}

Playground Link

  • Related