2
votes

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?

The workaround seems to be the best bet, although it requires the static factory method. Thanks. - JHH