Assuming I have an enum:
enum Cars {
Volvo = 'Volvo',
Hyundai = 'Hyundai',
Kia = 'Kia',
Tesla = 'Tesla'
}
I want to define an interface or a type, which should have every enum value as a key (types for them may be different or same):
interface CarToFactory {
[Cars.Volvo]: SwedishFactoryType,
[Cars.Hyundai]: KoreanFactoryType,
[Cars.Kia]: KoreanFactoryType,
// <- Error: Missing Cars.Tesla
}
So adding a new car should throw an error unless you add a mapping to factory.
I tried to define an interface that extends Record<Cars, Factory>
, but this doesn't give an error on missing property.
Also, the usage may not be obvious, but I have another interface with generic param, like:
interface CarPassport<TCar extends Cars> {
company: TCar,
manufacturer: CarToFactory[TCar],
year: number,
}
This is not a question about defining JS objects, I need to solve it on TS side
It looks like you want a type-level satisfies
operator, which checks against a type without widening to that type.
If you were doing this with a value instead of a type, then satisfies
would work exactly how you want:
const carToFactory = {
[Cars.Volvo]: 1,
[Cars.Hyundai]: 2,
[Cars.Kia]: 3,
// [Cars.Tesla]: 4 // <-- uncomment this and your errors go away
} satisfies Record<Cars, unknown>; // error!
//~~~~~~~~~
// Property '[Cars.Tesla]' is missing in type
// '{ Volvo: number; Hyundai: number; Kia: number; }'
// but required in type 'Record<Cars, unknown>'
There is no built-in thing that works this way at the type level, but you can define one yourself like this:
type Satisfies<T, U extends T> = U;
And use it like this:
type CarToFactory = Satisfies<Record<Cars, unknown>, {
[Cars.Volvo]: 1,
[Cars.Hyundai]: 2,
[Cars.Kia]: 3,
// [Cars.Tesla]: 4 // <-- uncomment this and your errors go away
}>;
// error! Property '[Cars.Tesla]' is missing in type
// { Volvo: 1; Hyundai: 2; Kia: 3; }'
// but required in type 'Record<Cars, unknown>'.
Satisfies<T, U>
checks T
against U
and reports an error if T
is not assignable to U
, but it evaluates to T
without changing it. So if you forget a key as shown above, you get an error. The location of the error message isn't ideal (it underlines the entire type argument instead of a single piece of it) but the message is what you're looking for.