You can use a mapped type to generate an object type on the fly given the types of its keys and associated values. For example, the type {[P in "x" | "y"]: [P]} produces the type {x: ["x"], y: ["y"]}. For the case where the value type does not depend on the key, you can use the built in Record<K, V> utility type, described in more detail here. So Record<"x" | "y", number> is equivalent to {x: number, y: number}.
In your case, you want Foo<T> to have the key T, which means we need to constrain T to just keylike types (that is string | number | symbol, or you can use the built in PropertyKey alias). In such cases, K is a more conventional parameter name than T. Let me start over:
You want Foo<K extends PropertyKey> to have K as a key. I'm not sure what value type you want at that key; for now I'll just use any, the ultimate 🤷♂️ type. And you walso want Foo<K> to have a boolean property at the "someProp" key. So it should be both Record<K, any>, and a {someProp: boolean}. That implies you want it to be the intersection of those:
type Foo<K extends PropertyKey> = {
someProp: boolean
} & Record<K, any>
And you can verify that this works:
const d: Foo<MyEnum.blue> = {
blue: 123,
someProp: true
}
Note that you cannot make Foo an interface. In both class and interface types, the compiler needs to know the keys statically, in advance of usage:
interface Oops<K extends PropertyKey>
extends Foo<K> { } // error!
// -----> ~~~~~~
// An interface can only extend an object type or
// intersection of object types with statically known members.
For a particular specification of K, though, you can make an interface:
interface FooBlue extends Foo<MyEnum.blue> { } // okay
const e: FooBlue = {
someProp: true,
blue: new Date()
}
Playground link to code
Recordutility type as in this?interface XYZ extends Foo<MyEnum.blue> {}but you cannot writeinterface XYZ<T extends PropertyKey> extends Foo<T> {}.