import { EventHandler, getEventsToHandle, getListenMethods, } from '@hg-ts/events'; import { BaseEntity } from './base.entity.js'; import { CreatedEventNotFoundException } from './created-event.not-found.exception.js'; import { AggregateCreatedEvent, DomainEvent, STORED_PROPERTY, } from './events/index.js'; import type { Identifiable } from './types.js'; const AGGREGATE_SYMBOL = Symbol('AGGREGATE_SYMBOL'); export const AGGREGATE_EVENTS_SYMBOL = Symbol('AGGREGATE_EVENTS_SYMBOL'); export abstract class BaseAggregate extends BaseEntity { protected readonly [AGGREGATE_SYMBOL]: string; protected readonly [AGGREGATE_EVENTS_SYMBOL]: DomainEvent[] = []; protected constructor(params: Identifiable) { super(params); Object.defineProperty(this, AGGREGATE_SYMBOL, { value: params.id, configurable: false, enumerable: false, writable: true, }); } public getEvents(): DomainEvent[] { return this[AGGREGATE_EVENTS_SYMBOL]; } public getNewEvets(): DomainEvent[] { return this[AGGREGATE_EVENTS_SYMBOL].filter(item => !item[STORED_PROPERTY]); } public static restoreFromEvents< Dto extends Identifiable, Aggregate extends BaseAggregate, >( aggregateCtor: Class, createdEventCtor: Class>, any[]>, events: DomainEvent[], ): Aggregate { const createdEvent = events.find(event => event instanceof createdEventCtor); if (!createdEvent) { throw new CreatedEventNotFoundException(aggregateCtor.name); } const aggregate = new aggregateCtor({ id: createdEvent.entityId, ...createdEvent.body, } as Dto); aggregate.applyEvents(events); return aggregate; } protected applyEvents(events: DomainEvent[]): void { events.forEach(event => this.produceEvent(event)); } protected produceEvent(event: DomainEvent): void { this.triggerListeners(event); this[AGGREGATE_EVENTS_SYMBOL].push(event); } private triggerListeners(event: DomainEvent): void { const proto = Object.getPrototypeOf(this)!; const listenMethods = getListenMethods(proto); listenMethods.forEach(methodName => { const method = (this as any)[methodName].bind(this) as EventHandler; const eventsToHandle = getEventsToHandle(proto, methodName); eventsToHandle.forEach(eventCtor => { if (event instanceof eventCtor) { method(event); } }); }); } }