Issue with strong typing in Typescript

I have the following code:

export type FooParams = {
  foo1: { x: number };
  foo2: { y: string };
};

export type FooKey = keyof FooParams; // or export type FooKey = "foo1" | "foo2";

export interface FooAction<T extends FooKey> {
  execute: (params: FooParams[T]) => void;
}

const foo1Action: FooAction<"foo1"> = {
  execute: (params) => {
    console.log(params.x);
  },
};

const foo2Action: FooAction<"foo2"> = {
  execute: (params) => {
    console.log(params.y);
  },
};

export const fooActions: Record<FooKey, FooAction<FooKey>> = {
  foo1: foo1Action,
  foo2: foo2Action,
};

I can't strongly type the variable fooActions, in order to force a FooAction for every FooKey. In the above example I have the following error.

Type 'FooAction<"foo1">' is not assignable to type 'FooAction<keyof FooParams>'.
  Type 'keyof FooParams' is not assignable to type '"foo1"'.
    Type '"foo2"' is not assignable to type '"foo1"'.ts(2322)

Any idea how to correctly declare the fooActions type?


FooAction<FooKey> means execute will have type (params: FooParams[FooKey]) => void which in turns would resolve to (params: { x: number } | { y: string }) => void;. This means it will have to be a function that handle both { x: number } and { y: number }. So all values of an object of type Record<FooKey, FooAction<FooKey>> would have to handle both types, while foo1Action and foo2Action can only handle one type or the other.

You want to type fooActions with a correlation between the key and the type. You can do that with a custom mapped type:

type FooActionMap = {
    [P in FooKey]: FooAction<P>
}
export const fooActions: FooActionMap  = {
  foo1: foo1Action,
  foo2: foo2Action,
};

Playground Link