events.ts raw

   1  import { Timestamp } from './value-objects'
   2  
   3  /**
   4   * Base class for all domain events
   5   *
   6   * Domain events capture something that happened in the domain that domain experts
   7   * care about. They are named in past tense (e.g., UserFollowed, NotePinned).
   8   */
   9  export abstract class DomainEvent {
  10    readonly occurredAt: Timestamp
  11  
  12    constructor() {
  13      this.occurredAt = Timestamp.now()
  14    }
  15  
  16    /**
  17     * Unique identifier for the event type
  18     * Format: context.event_name (e.g., 'social.user_followed')
  19     */
  20    abstract get eventType(): string
  21  }
  22  
  23  /**
  24   * Handler for domain events
  25   */
  26  export type EventHandler<T extends DomainEvent = DomainEvent> = (event: T) => void | Promise<void>
  27  
  28  /**
  29   * Event dispatcher interface
  30   */
  31  export interface EventDispatcher {
  32    /**
  33     * Dispatch an event to all registered handlers
  34     */
  35    dispatch(event: DomainEvent): Promise<void>
  36  
  37    /**
  38     * Register a handler for a specific event type
  39     */
  40    on<T extends DomainEvent>(eventType: string, handler: EventHandler<T>): void
  41  
  42    /**
  43     * Remove a handler for a specific event type
  44     */
  45    off<T extends DomainEvent>(eventType: string, handler: EventHandler<T>): void
  46  
  47    /**
  48     * Register a handler for all events
  49     */
  50    onAll(handler: EventHandler): void
  51  
  52    /**
  53     * Remove a handler for all events
  54     */
  55    offAll(handler: EventHandler): void
  56  }
  57  
  58  /**
  59   * Simple in-memory event dispatcher
  60   *
  61   * Dispatches events synchronously to handlers. Handlers are called in registration order.
  62   * Errors in handlers are logged but don't prevent other handlers from being called.
  63   */
  64  export class SimpleEventDispatcher implements EventDispatcher {
  65    private handlers: Map<string, Set<EventHandler>> = new Map()
  66    private allHandlers: Set<EventHandler> = new Set()
  67  
  68    async dispatch(event: DomainEvent): Promise<void> {
  69      const eventType = event.eventType
  70  
  71      // Call type-specific handlers
  72      const typeHandlers = this.handlers.get(eventType)
  73      if (typeHandlers) {
  74        for (const handler of typeHandlers) {
  75          try {
  76            await handler(event)
  77          } catch (error) {
  78            console.error(`Error in event handler for ${eventType}:`, error)
  79          }
  80        }
  81      }
  82  
  83      // Call all-event handlers
  84      for (const handler of this.allHandlers) {
  85        try {
  86          await handler(event)
  87        } catch (error) {
  88          console.error(`Error in all-event handler for ${eventType}:`, error)
  89        }
  90      }
  91    }
  92  
  93    on<T extends DomainEvent>(eventType: string, handler: EventHandler<T>): void {
  94      let handlers = this.handlers.get(eventType)
  95      if (!handlers) {
  96        handlers = new Set()
  97        this.handlers.set(eventType, handlers)
  98      }
  99      handlers.add(handler as EventHandler)
 100    }
 101  
 102    off<T extends DomainEvent>(eventType: string, handler: EventHandler<T>): void {
 103      const handlers = this.handlers.get(eventType)
 104      if (handlers) {
 105        handlers.delete(handler as EventHandler)
 106        if (handlers.size === 0) {
 107          this.handlers.delete(eventType)
 108        }
 109      }
 110    }
 111  
 112    onAll(handler: EventHandler): void {
 113      this.allHandlers.add(handler)
 114    }
 115  
 116    offAll(handler: EventHandler): void {
 117      this.allHandlers.delete(handler)
 118    }
 119  
 120    /**
 121     * Clear all handlers (useful for testing)
 122     */
 123    clear(): void {
 124      this.handlers.clear()
 125      this.allHandlers.clear()
 126    }
 127  }
 128  
 129  /**
 130   * Global event dispatcher instance
 131   *
 132   * This is a singleton that can be used throughout the application.
 133   * For testing, you can create a new SimpleEventDispatcher instance.
 134   */
 135  export const eventDispatcher = new SimpleEventDispatcher()
 136