'Constrain the keys of sub-classes' static variable and instance variable types to match

I have a base class whose type signature is something like this:

class System {
  queries: Record<string, Query>

  static queries: Record<string, any[]>;

  constructor() {
    for (const queryName in queries /* refers to static queries */) {
      const Components = queries[queryName];
      this.queries[queryName] = createQuery(Components) /* returns Query object */;
    }
  }
}

I can then create create a subclass that defines a set of queries statically, to be converted to an instance variable with corresponding keys:

class ColliderSystem extends System {
  static queries = {
    added: [Collider, Not(ColliderRef), RigidBodyRef],
    removed: [Not(Collider), ColliderRef]
  };

  update() {
    this.queries.aded /* type system says this is fine, but I don't want it to be! */;
  }
}

When referring to this.queries inside ColliderSystem, I want the type system's knowledge about the keys of static queries to be propagated to this.queries, so that I can't make mistakes like this.queries.aded.

The closest I've been able to get is to create additional type information specifically to prevent this mistake, but ideally, I'd like the base class to be as complex as necessary so that "users" of the base class don't need to think about the types:

type QueryKeys = "added" | "removed";

export class ColliderSystem extends System {
  queries: Record<QueryKeys, Query>;

  static queries: Record<QueryKeys, any[]> = {
    added: [Collider, Not(ColliderRef), RigidBodyRef],
    removed: [Not(Collider), ColliderRef]
  };

  update() {
    this.queries.aded /* type system says this wrong... great! */;
  }
}


Sources

This article follows the attribution requirements of Stack Overflow and is licensed under CC BY-SA 3.0.

Source: Stack Overflow

Solution Source