Question: Specify function parameter types in TypeScript mapped trype

I have two functions that take a single options argument with differing properties, except for type, which is used to identify the function.
type FuncAOptions = {
    type: 'A'
    opt1: string
    opt2: boolean
}

function funcA(options: FuncAOptions): number {
    if (!options.opt1) throw new Error('Missing required option')
    return 1
}

type FuncBOptions = {
    type: 'B'
    opt3: number
    opt4: (a: number) => number
}

function funcB(options: FuncBOptions): string {
    if (!options.opt3) throw new Error('Missing required option')
    return 'B'
}

I then have a map of these functions, with an associated mapped type, so that I can call the functions conditionally with variable runtime data.
type AllFunctions = FuncAOptions | FuncBOptions

type FunctionMap = { [K in AllFunctions['type']]: (options: any) => any; }

const functionMap: FunctionMap = {
    A: funcA,
    B: funcB
}

function callFunction(type: keyof typeof functionMap, options: any) {
    return functionMap[type](options)
}

When I call the functions directly, I get the proper type-checking to know if I'm passing an incorrect set of options. I want to be able to do the same when calling the function through the intermediary method.
callFunction('A', { type: 'B', opt3: 'Hello' }) // NO TS ERROR
funcA({ type: 'B', opt3: 'Hello' }) // TS ERROR: The expected type comes from property 'type' which is declared here on type 'FuncAOptions'

I like having the map typed with K in AllFunctions['type'] because when I add a function to AllFunctions, I am reminded that I need to add the key-value pair to functionMap.
Full example here


Answer:
If you want functionMap[type](options) to type check without loosening things up with type assertions or the any type, then you'll need to write it terms of generic indexes into a base key-value type or mapped types of that type.  This is as described in microsoft/TypeScript#47109.
Essentially you want type to be seen as some generic type K, and functionMap to be seen as some mapped type like {[P in keyof FuncOptionMap]: (arg: FuncOptionMap[P]) => FuncRetMap[P]} and for options to be seen as type FuncOptionMap[K].  Then the compiler can conclude that the function functionMap[type] is of type (arg: FuncOptionMap[K]) => FuncRetMap[K], and is therefore callable with options as an argument, and will return the corresponding return type FuncRetMap[K].  So we'll need to define FuncOptionMap and FuncRetMap in terms of the values you have.
You might have hoped that you could have done this with type merely being the union type equivalent to keyof FuncOptionMap without needing generics.  But TypeScript can't follow that sort of logic, as described in microsoft/TypeScript#30581.  The recommended approach is to use generic indexing into mapped types instead; indeed microsoft/TypeScript#47109 is the solution to microsoft/TypeScript#30581 (or at least the closest we have to a solution).
It could look like this:
const _functionMap = {
    A: funcA,
    B: funcB
}

type FuncOptionMap =
    { [K in keyof typeof _functionMap]: Parameters<typeof _functionMap[K]>[0] }
type FuncRetMap =
    { [K in keyof typeof _functionMap]: ReturnType<typeof _functionMap[K]> }

const functionMap: { [K in keyof FuncOptionMap]: (options: FuncOptionMap[K]) => FuncRetMap[K] }
    = _functionMap

Essentially we rename your functionMap to _functionMap and then later assign it back with the FuncOptionMap and FuncRetMap types computed from it.  It might look like a no-op, since the type of _functionMap and the type of functionMap appear to be identical.  But this is actually very important; the compiler can only follow the logic when things are written as this mapping. If you try to use _functionMap instead of functionMap in what follows, the compiler will lose the thread and output an error.
Continuing:
function callFunction<K extends keyof FuncOptionMap>(
    type: K, options: FuncOptionMap[K]
) {
    return functionMap[type](options); // okay
}

That type checks, and the return type is FuncRetMap[K].  Now you get the error you expected:
callFunction('A', { type: 'B', opt3: 'Hello' }) // error!
// ---------------------> ~~~ B is not A

and when you call it the compiler knows how the output type as depends on the input type:
console.log(callFunction(
    "B",
    { type: "B", opt3: 1, opt4: x => x + 1 }
).toLowerCase()); // okay, the compiler knows it's string and not number

Playground link to code