import type ICommand from "../Bus/Command/Command"; import type DomainMessage from "../../Domain/Event/DomainMessage"; import { SagaStatus } from "./SagaState"; import type { SagaStateSnapshot } from "./SagaState"; /** * Command dispatcher function type. * Provided by the SagaManager to execute commands. */ export type CommandDispatcher = (command: ICommand) => Promise; import type DomainEvent from "../../Domain/Event/DomainEvent"; /** * Abstract base class for implementing sagas (process managers). * * Sagas coordinate long-running business processes across multiple aggregates. * They listen to domain events and dispatch commands to progress the workflow. * * @example * ```typescript * class OrderFulfillmentSaga extends Saga { * readonly sagaType = 'OrderFulfillmentSaga'; * * static startedBy(): string[] { * return ['OrderPlaced']; * } * * protected getEventHandlers(): Map Promise> { * return new Map([ * ['OrderPlaced', this.onOrderPlaced.bind(this)], * ['PaymentReceived', this.onPaymentReceived.bind(this)], * ['ShipmentCreated', this.onShipmentCreated.bind(this)], * ]); * } * * protected getCompensationHandlers(): Map Promise> { * return new Map([ * ['PaymentReceived', this.compensatePayment.bind(this)], * ]); * } * } * ``` */ export default abstract class Saga { /** Unique identifier for this saga instance */ private _sagaId; /** The saga's internal state */ protected state: TState; /** Current status of the saga */ protected status: SagaStatus; /** Correlation ID for grouping related sagas/events */ protected correlationId: string; /** Timestamp when the saga was started */ protected startedAt: Date; /** Timestamp of the last update */ protected updatedAt: Date; /** Timestamp when the saga completed */ protected completedAt?: Date; /** Reason for failure if the saga failed */ protected failureReason?: string; /** List of processed event types for idempotency */ protected processedEvents: string[]; /** Command dispatcher provided by the SagaManager */ private commandDispatcher?; /** * The saga type identifier (class name). * Used for persistence and routing. */ abstract readonly sagaType: string; /** * Get the unique identifier for this saga instance. */ get sagaId(): string; constructor(sagaId: string, initialState: TState, correlationId: string); /** * Returns the event types that can start this saga. * Override in subclass to specify starting events. */ static startedBy(): string[]; /** * Returns the event types this saga listens to. * Derived from the event handlers map. */ interestedIn(): string[]; /** * Handle an incoming domain event. * Routes to the appropriate handler based on event type. * @param message The domain message containing the event */ handle(message: DomainMessage): Promise; /** * Set the command dispatcher. * Called by the SagaManager when the saga is registered. * @internal */ setCommandDispatcher(dispatcher: CommandDispatcher): void; /** * Dispatch a command to progress the saga. * @param command The command to dispatch */ protected dispatch(command: ICommand): Promise; /** * Mark the saga as completed. * No more events will be processed after completion. */ protected complete(): void; /** * Mark the saga as failed and trigger compensation. * @param reason The reason for failure */ protected fail(reason: string): Promise; /** * Run compensation handlers in reverse order of processed events. */ private runCompensation; /** * Get the current status of the saga. */ getStatus(): SagaStatus; /** * Check if the saga is still active (not completed or failed). */ isActive(): boolean; /** * Create a snapshot of the saga state for persistence. */ toSnapshot(): SagaStateSnapshot; /** * Restore saga state from a snapshot. * @param snapshot The snapshot to restore from */ fromSnapshot(snapshot: SagaStateSnapshot): void; /** * Get the map of event type to handler function. * Override in subclass to define event handlers. */ protected abstract getEventHandlers(): Map Promise>; /** * Get the map of event type to compensation handler function. * Override in subclass to define compensation logic. * Compensation handlers are called in reverse order when the saga fails. */ protected getCompensationHandlers(): Map Promise>; }