I have a string literal type, such as
type ConfigurationTypes = 'test' | 'mock'
and some types
type MockType = { id: string }
type TestType = { code: string }
And I wanted to create a type that "maps" the string literal to this types, so that if ConfigurationTypes changes, my type MappedConfigurationTypes would also be required to change accordingly. Is it even possible?
type MappedConfigurationTypes: {[key in ConfigurationTypes]: any} = {
test: TestType
mock: MockType
}
In some sense you're looking for a type-level satisfies operator. If you write e satisfies T where e is some expression and T is some type, the compiler will make sure that e is assignable to T without widening to T, so e keeps its original type but you'll get an error if is incompatible with T. You want to do the same thing but replace the expression with another type. Something like
// this is invalid TS, don't do this:
type MappedConfigurationTypes = {
test: testType;
mock: MockType
} Satisfies {[K in ConfigurationTypes]: any}
but there is no such Satisfies type operator. Too bad.
Luckily we can essentially build one ourselves: instead of T Satisfies U, we could write Satisfies<U, T> (I'm making "Satisfies U" the syntactic unit of note, so that's why I want Satisfies<U, T> and not Satisfies<T, U>. But you can define it however you want).
Here's the definition:
type Satisfies<U, T extends U> = T;
You can see how Satisfies<U, T> will always evaluate to just T, but since T is constrained to U, the compiler will complain if T is not compatible with U.
Let's try it:
type ConfigurationTypes = 'test' | 'mock';
type MockType = { id: string }
type TestType = { code: string }
type MappedConfigurationTypes = Satisfies<{ [K in ConfigurationTypes]: any }, {
test: TestType
mock: MockType
}>
Looks good. If you hover over MappedConfigurationTypes you see it is equivalent to
/* type MappedConfigurationTypes = {
test: TestType;
mock: MockType;
} */
On the other hand if you add another member to the ConfigurationTypes union, you'll see the desired error:
type ConfigurationTypes = 'test' | 'mock' | 'oops'
type MappedConfigurationTypes = Satisfies<{ [K in ConfigurationTypes]: any }, {
test: TestType
mock: MockType,
}> // error!
// Property 'oops' is missing in type '{ test: TestType; mock: MockType; }' but required
// in type '{ test: any; mock: any; oops: any; }'.
Playground link to code
If you love us? You can donate to us via Paypal or buy me a coffee so we can maintain and grow! Thank you!
Donate Us With