Say I've got these types defined:
export type Event = {
name: EventName,
timestamp: Timestamp,
};
export type EventWithMetadata = Event & {
deviceId: ID,
};
I'm looking to define a common function interface like so:
export type SideEffect = (event: Event, run?: Run) => Partial<Run>;
and using it with this function
// Note: many of these side-effect functions exist, with multiple specializations of the base Event
// type as potential arguments. This is just one example.
const updateTiming: SideEffect = (event: EventWithMetadata, run) => {
log.debug('updateTiming: %j', event);
return {
lastUpdated: event.timestamp,
deviceId: event.deviceId,
};
};
but then I hit this error:
error TS2322: Type '(event: EventWithMetadata, run: Run | undefined) => { startTime: number; deviceId: string; }' is not assignable to type 'SideEffect'.
Types of parameters 'event' and 'event' are incompatible.
Type 'Event' is not assignable to type 'EventWithMetadata'.
Property 'deviceId' is missing in type 'Event' but required in type '{ deviceId: string; }'.
which makes sense: Typescript thinks I'm trying to downcast Event into EventWithMetadata, which obviously can't happen.
After a lot of searching on generics and overloads and union types, the best answer I could find is this one. I really hate the result and wouldn't want to make it a pattern:
const updateTiming: SideEffect = (inputEvent: Event, run) => {
const event = inputEvent as EventWithMetadata; // <- This sucks.
log.debug('updateTiming: %j', event);
return {
lastUpdated: event.timestamp,
deviceId: event.deviceId,
};
};
Are there better solutions than casting?
If you need
updateTimingto handleEventWithMetadataspecifically, then the type ofupdateTimingneeds to know aboutEventWithMetadata. In order forSideEffectto know aboutEventWithMetadataand still work with otherEventtypes, you probably want it to be generic in the particular subtype ofEventthat it handles:Then
updateTimingis aSideEffect<EventWithMetadata>and not just aSideEffect:Note that it's not possible to hold onto a
SideEffectwithout knowing what it handles, but that's a good thing, because otherwise you could actually pass it the wrongEvent. If you think you need aSideEffect[]without a type argument, then you will have to refactor so that you can distinguish events and event handlers somehow via a test (ideally both handlers and events would be a discriminated union but otherwise you could write custom type guard functions). But that's out of scope for this question as asked.Playground link to code