Follow

Keep Up to Date with the Most Important News

By pressing the Subscribe button, you confirm that you have read and are agreeing to our Privacy Policy and Terms of Use
Contact

strongly typed dispatcher function

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 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?

MEDevel.com: Open-source for Healthcare and Education

Collecting and validating open-source software for healthcare, education, enterprise, development, medical imaging, medical records, and digital pathology.

Visit Medevel

>Solution :

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

Add a comment

Leave a Reply

Keep Up to Date with the Most Important News

By pressing the Subscribe button, you confirm that you have read and are agreeing to our Privacy Policy and Terms of Use

Discover more from Dev solutions

Subscribe now to keep reading and get access to the full archive.

Continue reading