Given a scenario where I want to be able to 'tag' data coming into my system from various sources how might I go about adding this source information onto any type T within my system given some 'source' function that provides this mapping
The shape of the data I want to append this tagged information onto doesn't particularly matter - all I want users of my API to care about is providing a function that will use their type T to determine where the source is coming from and then a simple translation function of the new unioned type.
I've tried doing something like so:
enum TAG {
EXTERNAL_SYSTEM_A,
EXTERNAL_SYSTEM_B,
EXTERNAL_SYSTEM_C,
UNKNOWN
}
interface TAGSource {
sourceTypes: TAG | TAG[];
}
// This can be literally any type at all
interface ExternalEntity {
name: string;
}
interface CalendarEntity{
id: number;
}
type SourceTagged<T> = T & TAGSource;
abstract class SourceTagMapper {
tagEntity<T>(entity: T): SourceTagged<T> {
return { sourceTypes: this.mapSourceTypes(entity), ...entity };
}
protected abstract mapSourceTypes<T>(entity: T): TAG | TAG[];
}
// Example user implementation
class ExternalEntitySourceTagMapper extends SourceTagMapper{
mapSourceTypes<ExternalEntity>(entity: ExternalEntity): TAG | TAG[] {
// Error: Property 'name' does not exist on type 'ExternalEntity'
console.log(entity.name);
// do some work to map external entity to a tag
return TAG.EXTERNAL_SYSTEM_A;
}
}
// Example user implementation
class CalendarEntitySourceTagMapper extends SourceTagMapper {
mapSourceTypes<CalendarEntity>(entity: CalendarEntity): TAG | TAG[] {
// Error: Property 'id' does not exist on type 'CalendarEntity'
console.log(entity.id);
return [TAG.EXTERNAL_SYSTEM_B, TAG.EXTERNAL_SYSTEM_C];
}
}
But the problem with this approach is I get no type information in the mapSourceTypes function due to the erasure of type information at runtime. I'm struggling to come up with a design that gives users the ability to just supply a function for mapping and then provides a function for 'providing' this mapping back to them with the added type information.
I tried inheriting from an abstract class with a generic function but lost type information in my base classes but due to type erasure the properties no longer exist in my derived class. Any thoughts or consideration for improvement would be greatly appreciated.
CodePudding user response:
The generic should be on the class, not the methods ! That allows you to extends a generic class with a specified type.
abstract class SourceTagMapper<T> {
tagEntity(entity: T): SourceTagged<T> {
return { sourceTypes: this.mapSourceTypes(entity), ...entity };
}
protected abstract mapSourceTypes(entity: T): TAG | TAG[];
}
// Example user implementation
class ExternalEntitySourceTagMapper extends SourceTagMapper<ExternalEntity>{
mapSourceTypes(entity: ExternalEntity): TAG | TAG[] {
// Error: Property 'name' does not exist on type 'ExternalEntity'
console.log(entity.name);
// do some work to map external entity to an lvc type
return TAG.EXTERNAL_SYSTEM_A;
}
}
// Example user implementation
class CalendarEntitySourceTagMapper extends SourceTagMapper<CalendarEntity> {
mapSourceTypes(entity: CalendarEntity): TAG | TAG[] {
// Error: Property 'id' does not exist on type 'CalendarEntity'
console.log(entity.id);
return [TAG.EXTERNAL_SYSTEM_B, TAG.EXTERNAL_SYSTEM_C];
}
}