I think there is some small misconception here on how classes are typed.
Let's assume, for abbreviation here, that we have two classes ClassA and ClassB.
class ClassA {
constructor() {
console.log("Class A");
}
}
class ClassB {
constructor() {
console.log("Class B");
}
}
Creating the map object is easy, but I think, it would have been easier to first describe an interface on what this object is supposed to hold. But as in your example, I proceed without one.
const map = {
a: ClassA,
b: ClassB,
};
Now comes the tricky part - Typings.
First I want to extract all keys of the map object.
Therefore I'll create a new type called: Keys
// Type contains: "a" | "b"
type Keys = keyof typeof map;
Next I need all associated values for each key.
To achieve this, create a new type at the index of Keys.
// Type contains: typeof ClassA | typeof ClassB
type Values = typeof map[Keys];
The getter function should return an instance of a class T. But the Values type contains all classes as typeof ClassA | typeof ClassB which is equal to new() => ClassA | new() => ClassB.
Thus the type of ClassA | ClassB is not compatible.
So let's craft a seperate type to get rid of this. The type should return the instance type of a class - just ClassA or ClassB.
Or so to say, the return type of new SomeClass() operation.
type ReturnType<T> = T extends new() => infer R ? R : never;
If you want to read up on conditional types in TypeScript, I highly recommend reading this blog post: https://mariusschulz.com/blog/conditional-types-in-typescript
Let's add the final piece:
const conf = {};
const getter = (ver: Keys): ReturnType<Values> => new map[ver]();
FYI: I did change the naming of VersionMap to Values and Version to Keys as I think it's easier to understand what each of those types is supposed to do. But feel free to change them to your needs.