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