It is not feasible as requested. When you define an enum like
enum X {
A = 'x',
B = 'y'
}
You are creating a value named X
(the enum object, with keys A
and B
) and a type named X
(the union of types of the enum values, such as "x" | "y"
). Types and values exist in separate namespaces, so there is no direct link between them in the language.
Generic types, on the other hand, function like this:
type A<T> = {
prop1: T
prop2: ⋯
}
They accept type arguments, not value arguments. So when you use it like this:
type B = A<X>;
You are passing the type named X
, which results in "x" | "y"
, into A
. This does not include the necessary information to enforce that prop2
must be of type represented by the value named X
. Essentially, the type "x" | "y"
has no knowledge of the keys "A"
and "B"
.
If you wish to maintain the usage of X
, the closest workaround involves specifying that prop2
should be an object type with properties of type X
, but with any keys. Here is how it can be done:
type A<T> = {
prop1: T
prop2: Record<string, T>
}
let r: A<X> = {
prop1: X.A,
prop2: X // permissible
}
r.prop2 = { whoopsieDaisy: X.A } // also allowed
However, this method does not mandate the correct keys.
If you modify the code to pass in typeof X
instead of X
, then you can come closer to the desired outcome:
type A<T extends object> = {
prop1: T[keyof T]
prop2: T
}
Now, prop1
is of type T[keyof T]
, indicating it is the union of property types of T
. If T
is typeof X
, then T[keyof T]
will indeed be X
. This enforces supplying the right keys:
let r: A<typeof X> = {
prop1: X.A,
prop2: X
}
r.prop2 = { whoopsieDaisy: X.A } // error
Nevertheless, this still does not fully meet the requirements; it only ensures that the passed value is of the expected type or a subtype of that type. It remains possible for a user to bypass these restrictions. To prevent this would require further complicated measures, likely more trouble than it's worth due to TypeScript's type system limitations.
Link to Playground with Code Example