A social knowledge tool for researchers built on ATProto

feat: implement event mapper for centralized event serialization

Co-authored-by: aider (anthropic/claude-sonnet-4-20250514) <aider@aider.chat>

+58 -35
+2 -3
src/modules/cards/domain/events/CardAddedToLibraryEvent.ts
··· 1 - import { IDomainEvent, IDomainEventClass } from '../../../../shared/domain/events/IDomainEvent'; 1 + import { IDomainEvent } from '../../../../shared/domain/events/IDomainEvent'; 2 2 import { UniqueEntityID } from '../../../../shared/domain/UniqueEntityID'; 3 3 import { CardId } from '../value-objects/CardId'; 4 4 import { CuratorId } from '../value-objects/CuratorId'; 5 - import { CardTypeEnum } from '../value-objects/CardType'; 6 5 import { EventNames } from '../../../../shared/infrastructure/events/EventConfig'; 7 6 8 7 export class CardAddedToLibraryEvent implements IDomainEvent { 9 - public static readonly eventName = EventNames.CARD_ADDED_TO_LIBRARY; 8 + public readonly eventName = EventNames.CARD_ADDED_TO_LIBRARY; 10 9 public readonly dateTimeOccurred: Date; 11 10 12 11 constructor(
+1 -1
src/shared/domain/events/IDomainEvent.ts
··· 1 - import { EventName } from 'src/shared/infrastructure/events/EventConfig'; 1 + import { EventName } from '../../infrastructure/events/EventConfig'; 2 2 import { UniqueEntityID } from '../UniqueEntityID'; 3 3 4 4 export interface IDomainEvent {
+3 -13
src/shared/infrastructure/events/BullMQEventPublisher.ts
··· 4 4 import { IDomainEvent } from '../../domain/events/IDomainEvent'; 5 5 import { Result, ok, err } from '../../core/Result'; 6 6 import { QueueNames, QueueOptions } from './QueueConfig'; 7 - import { EventName } from './EventConfig'; 7 + import { EventMapper } from './EventMapper'; 8 8 9 9 export class BullMQEventPublisher implements IEventPublisher { 10 10 private queues: Map<string, Queue> = new Map(); ··· 34 34 } 35 35 36 36 const queue = this.queues.get(QueueNames.EVENTS)!; 37 - const eventType = this.getEventType(event); 38 - await queue.add(eventType, { 39 - eventType: eventType, 40 - aggregateId: event.getAggregateId().toString(), 41 - dateTimeOccurred: event.dateTimeOccurred.toISOString(), 42 - // Serialize the event data 43 - cardId: (event as any).cardId?.getValue?.()?.toString(), 44 - curatorId: (event as any).curatorId?.value, 45 - }); 37 + const serializedEvent = EventMapper.toSerialized(event); 38 + await queue.add(serializedEvent.eventType, serializedEvent); 46 39 } 47 40 48 - private getEventType(event: IDomainEvent): EventName { 49 - return event.eventName; 50 - } 51 41 52 42 async close(): Promise<void> { 53 43 await Promise.all(
+2 -18
src/shared/infrastructure/events/BullMQEventSubscriber.ts
··· 5 5 IEventHandler, 6 6 } from '../../application/events/IEventSubscriber'; 7 7 import { IDomainEvent } from '../../domain/events/IDomainEvent'; 8 - import { CardAddedToLibraryEvent } from '../../../modules/cards/domain/events/CardAddedToLibraryEvent'; 9 - import { CardId } from '../../../modules/cards/domain/value-objects/CardId'; 10 - import { CuratorId } from '../../../modules/cards/domain/value-objects/CuratorId'; 11 8 import { QueueNames } from './QueueConfig'; 12 - import { EventNames } from './EventConfig'; 9 + import { EventMapper } from './EventMapper'; 13 10 14 11 export class BullMQEventSubscriber implements IEventSubscriber { 15 12 private workers: Worker[] = []; ··· 75 72 } 76 73 77 74 private reconstructEvent(eventData: any): IDomainEvent { 78 - switch (eventData.eventType) { 79 - case EventNames.CARD_ADDED_TO_LIBRARY: { 80 - const cardId = CardId.create(eventData.cardId).unwrap(); 81 - const curatorId = CuratorId.create(eventData.curatorId).unwrap(); 82 - 83 - const event = new CardAddedToLibraryEvent(cardId, curatorId); 84 - (event as any).dateTimeOccurred = new Date(eventData.dateTimeOccurred); 85 - (event as any).eventName = EventNames.CARD_ADDED_TO_LIBRARY; 86 - 87 - return event; 88 - } 89 - default: 90 - throw new Error(`Unknown event type: ${eventData.eventType}`); 91 - } 75 + return EventMapper.fromSerialized(eventData); 92 76 } 93 77 }
+50
src/shared/infrastructure/events/EventMapper.ts
··· 1 + import { IDomainEvent } from '../../domain/events/IDomainEvent'; 2 + import { CardAddedToLibraryEvent } from '../../../modules/cards/domain/events/CardAddedToLibraryEvent'; 3 + import { CardId } from '../../../modules/cards/domain/value-objects/CardId'; 4 + import { CuratorId } from '../../../modules/cards/domain/value-objects/CuratorId'; 5 + import { EventNames } from './EventConfig'; 6 + 7 + export interface SerializedEvent { 8 + eventType: string; 9 + aggregateId: string; 10 + dateTimeOccurred: string; 11 + [key: string]: any; 12 + } 13 + 14 + export class EventMapper { 15 + static toSerialized(event: IDomainEvent): SerializedEvent { 16 + const baseData: SerializedEvent = { 17 + eventType: event.eventName, 18 + aggregateId: event.getAggregateId().toString(), 19 + dateTimeOccurred: event.dateTimeOccurred.toISOString(), 20 + }; 21 + 22 + // Add event-specific data based on event type 23 + if (event instanceof CardAddedToLibraryEvent) { 24 + return { 25 + ...baseData, 26 + cardId: event.cardId.getValue().toString(), 27 + curatorId: event.curatorId.value, 28 + }; 29 + } 30 + 31 + throw new Error(`Unknown event type for serialization: ${event.constructor.name}`); 32 + } 33 + 34 + static fromSerialized(eventData: SerializedEvent): IDomainEvent { 35 + switch (eventData.eventType) { 36 + case EventNames.CARD_ADDED_TO_LIBRARY: { 37 + const cardId = CardId.create(eventData.cardId).unwrap(); 38 + const curatorId = CuratorId.create(eventData.curatorId).unwrap(); 39 + 40 + const event = new CardAddedToLibraryEvent(cardId, curatorId); 41 + // Override the dateTimeOccurred with the serialized value 42 + (event as any).dateTimeOccurred = new Date(eventData.dateTimeOccurred); 43 + 44 + return event; 45 + } 46 + default: 47 + throw new Error(`Unknown event type for deserialization: ${eventData.eventType}`); 48 + } 49 + } 50 + }