Q:

Narrow possible values of a proerty based on keys in the same object

I have the following structure:
export interface AppConfig {
  encryptionKey: string;
  db: TypeOrmModuleOptions;
}

export interface BrandsConfig {
  /**
   * Brand name
   */
  [key: string]: AppConfig;
}

export interface IConfig {
  brands: BrandsConfig;
  master: string;
}

export class Config implements IConfig {
  public readonly brands: BrandsConfig;
  public readonly master: string;
  public constructor(init: IConfig) {
    Object.assign(this, init);
  }
}

The idea is that BrandsConfig is a key/value collection, where the value is a setting for this named instance. Meanwhile, "master" is supposed to give one (and only one) of those instances a special status.
Is there any way I can statically restrict the possible values of "master" (at least in the Config class, ideally in the interface as well), so that they aren't just any string, but a string that's within the BrandConfig keys? I know I could do this check at runtime in the constructor, but I am trying to move as much checks as I can into the compilation phase, and offer proper intellisense for it.

A:

You can do it with Generics and keyof/Index Type Query operator.
1.) Add generic type parameter B to your Config class, that is assignable to/extends BrandConfig:
export class Config<B extends BrandsConfig> implements IConfig<B> {
  public readonly brands: B;

  public readonly master: keyof B;

  public constructor(init: IConfig<B>) {
    // Make it compile. Feel free to replace it by delegates, getters,etc.
    this.brands = init.brands;
    this.master = init.master;
  }
}

2.) Also add a corresponding generic type parameter in IConfig, so we can define master as a key that is dependent on your keys in BrandsConfig:
export interface IConfig<B extends BrandsConfig> {
  brands: B;
  master: keyof B;
}

Let's test the client side. Here we expect an error, if we pass in the wrong key for master. 
const myConfig = {
  brands: {
    brand1: { encryptionKey: "", db: {} },
    brand2: { encryptionKey: "", db: {} }
  },
  master: "brand1"
} as const

const config = new Config(myConfig); // works!

Now change your master property to master: "Wanna be a brand", then you'll get:

Type '"Wanna be a brand"' is not assignable to type '"brand1" | "brand2"'.

Also note, that we use const assertions to narrow down the inferred type of myConfig.
Playground

