import type IEventSourced from "./IEventSourced"; import DomainEventStream from "./Event/DomainEventStream"; import EventSourced from "./EventSourced"; import type DomainEvent from "./Event/DomainEvent"; import AggregateRoot from "./AggregateRoot"; export default abstract class EventSourcedAggregateRoot extends AggregateRoot implements IEventSourced { protected eventHandlers: Map void>; private playhead; private events; private children; getUncommittedEvents(): DomainEventStream; fromHistory(stream: DomainEventStream): this; /** * Restores aggregate state from a snapshot. * * Uses a safe rehydration pattern instead of Object.assign to: * - Preserve class prototypes and methods * - Avoid overwriting critical infrastructure (event handlers) * - Only copy data properties, not methods * - Maintain proper child entity relationships * * @param snapshot - Snapshot data to restore from * @returns This aggregate with restored state */ fromSnapshot(snapshot: Record): this; recursiveHandling(event: DomainEvent, eventType?: string): void; version(): number; protected getChildEntities(): EventSourced[]; protected registerChildren(child: EventSourced): void; /** * Register an explicit event handler for a specific event type. * All events raised by the aggregate must have a registered handler. * * Note: Constructor parameters use `any[]` for variance - this allows * registering constructors with any parameter signature. */ protected registerHandler(eventType: new (...args: any[]) => T, handler: (event: T) => void): void; /** * Ensures that a condition is true. Throws an error if the condition is false. * Use this method to validate business invariants before raising events. * * @param condition - The condition to check * @param errorMessage - The error message to throw if the condition is false * @throws {Error} When the condition is false * * @example * ```typescript * class BankAccount extends EventSourcedAggregateRoot { * withdraw(amount: number): void { * this.ensure( * this.balance >= amount, * `Insufficient funds: balance is ${this.balance}, attempted to withdraw ${amount}` * ); * this.raise(new MoneyWithdrawn(amount)); * } * } * ``` */ protected ensure(condition: boolean, errorMessage: string): void; /** * Ensures that a value is not null or undefined. * Use this method to validate required parameters or state. * * @param value - The value to check * @param errorMessage - The error message to throw if the value is null/undefined * @throws {Error} When the value is null or undefined * * @example * ```typescript * class Order extends EventSourcedAggregateRoot { * addItem(productId: string, quantity: number): void { * this.ensureNotNull(productId, 'Product ID is required'); * this.ensureNotNull(quantity, 'Quantity is required'); * this.ensure(quantity > 0, 'Quantity must be positive'); * this.raise(new ItemAdded(productId, quantity)); * } * } * ``` */ protected ensureNotNull(value: T | null | undefined, errorMessage: string): asserts value is T; /** * Raises a domain event and applies it to the aggregate's state. * This is the primary way to record state changes in event-sourced aggregates. * * The event will be: * 1. Applied to this aggregate and all child entities * 2. Stored in the uncommitted events list * 3. Published to the event bus when the aggregate is saved * * @param event - The domain event to raise (must implement DomainEvent) * * @example * ```typescript * class Order extends EventSourcedAggregateRoot { * placeOrder(items: OrderItem[]): void { * this.ensure(items.length > 0, 'Order must have at least one item'); * this.raise(new OrderPlaced(this.getAggregateRootId(), items)); * } * } * ``` */ protected raise(event: DomainEvent): void; private handle; }