Enumerations and interfaces are an important part of my codebase:
enum EventId {
FOO = 'FOO',
BAR = 'BAR',
}
interface EventIdOptionsMap {
[EventId.FOO]: {
fooOption: string;
},
[EventId.BAR]: {
barOption: number;
}
}
interface EventRequest<K extends EventId> {
eventId: K;
options: EventIdOptionsMap[K];
}
To ensure type safety, I often create objects like this:
const test: EventRequest<EventId.FOO> = {
eventId: EventId.FOO,
options: {
fooOption: 'test',
good: true, // this would not cause an error
}
}
Despite the benefits, I find myself typing EventId.FOO
twice. I wish there was a way to streamline this process, such as through a utility function:
function generateEvent<K extends EventId>(
eventId: K,
options: EventIdOptionsMap[K]
): EventRequest<K> {
return {
eventId,
options
};
}
Unfortunately, even with the utility function, I still need to specify the enum twice for it to work:
const event = generateEvent<EventId.FOO>(EventId.FOO, {
fooOption: 'test'
});
// With certainty, `event` is of type `EventRequest<EventId.FOO>`
This redundancy in specifying the enum both as a generic type and as a runtime argument bothers me. Surely, there must be a more efficient way to achieve the same level of type safety without repeating ourselves?
Considering that enums have a presence at runtime, I wonder if there's a method to communicate to TypeScript that "this type corresponds to an enum, therefore usable at runtime." This would potentially eliminate the need for dual enumeration declaration.
Duplicating specifications is counterproductive. Are there alternative approaches to reaching the desired outcome without this duplication? I aim to simplify our object creation process to reduce boilerplate.
If you know of any strategies that could help, please share your insights.