Imagine this class to describe a store of objects referenced by a key being one of its attributes:
class Store<T, K extends keyof T> {
readonly key: K;
readonly items: Map<T[K], T> = new Map();
constructor(key: K) {
this.key = key;
}
put(item: T) {
this.items.set(item[this.key], item);
}
get(key: T[K]): T | undefined {
return this.items.get(key);
}
}
To make the example more concrete, let's say we have two types of data we want to keep in Store:s:
interface Person {
name: string;
address: string;
}
interface Product {
id: number;
name: string;
category: string;
}
const personStore = new Store<Person, 'name'>('name'); // Stores Persons indexable by their name
const productStore = new Store<Product, 'id'>('id'); // Stores Products indexable by their id
personStore.put({name: 'Homer Simpson', address: '742 Evergreen Terrace'})
const homer = personStore.get('Homer Simpson');
productStore.put({id: 42, name: 'Pizza', category: 'Food'});
const pizza = productStore.get(42);
This works, but it bothers me that on creation, Store:s must state the attribute used as key twice - once as a type argument and once as a literal value.
Now, type arguments can be inferred from the given argument values, but in this case, T is not part of the arguments so it must be stated as a type argument. K, however is the type of an argument to the constructor so it could be inferred. But it doesn't seem possible to infer K while stating T?
If I omit the type arguments completely, T is inferred to never, giving a useless object, and also an error during construction:
const someStore = new Store('name'); // Gives "argument of type name is not assignable to never"
What I want is to be able to do this:
const personStore = new Store<Person>('name'); // T is Person, name is keyof Person.
I considered declaring a constructor interface but that doesn't help. Creating a static factory method enables returning a fully typed generic object but also suffers from not being able to specify T while inferring K from key.
I also obviously don't want to supply a dummy item in the constructor merely to infer T.
So: Is it at all possible to infer one generic type from arguments while stating another? Or is there some clever workaround?