UNPKG

smart-saga-pattern

Version:

A library implementing the Saga pattern for microservice architecture

191 lines (190 loc) 7.1 kB
"use strict"; Object.defineProperty(exports, "__esModule", { value: true }); exports.StructuredSagaCoordinator = void 0; const Saga_1 = require("../core/Saga"); const types_1 = require("../types"); /** * Coordinates a choreography-based Saga with structured context * The context is structured as: * { * global: { ... }, * steps: { * "Step Name": { * input: { ... }, * output: { ... }, * compensation: { ... } * } * } * } * @template T Type of the input data * @template R Type of the result data */ class StructuredSagaCoordinator extends Saga_1.Saga { /** * Creates a new StructuredSagaCoordinator * @param participant Saga participant for communication * @param options Options for the coordinator * @param stateStore Optional state store for persistence */ constructor(participant, options, stateStore) { super(options); this.participant = participant; this.stateStore = stateStore; // Set up event handlers this.setupEventHandlers(); } /** * Executes the Saga * @param initialData Initial data for the Saga */ async execute(initialData) { try { // Update context with initial data if (initialData) { // Structure the initial data const structuredData = this.structureInitialData(initialData); this.updateContext(structuredData); } else { // Initialize empty structured data this.updateContext({ global: {}, steps: {} }); } // Save initial state if state store is provided if (this.stateStore) { await this.stateStore.save(this.getId(), this.getContext()); } // Set up completion promise this.completionPromise = new Promise((resolve, reject) => { this.completionResolve = resolve; this.completionReject = reject; }); // Start the Saga by publishing the initial event await this.participant.publish('SAGA_STARTED', this.context.data, this.getId()); // Set status to executing this.setStatus(types_1.SagaStatus.EXECUTING); // Update state store if provided if (this.stateStore) { await this.stateStore.update(this.getId(), this.context); } // Wait for completion return await this.completionPromise; } catch (error) { this.setStatus(types_1.SagaStatus.FAILED, error instanceof Error ? error.message : String(error)); // Update state store if provided if (this.stateStore) { await this.stateStore.update(this.getId(), this.context); } return this.getContext(); } } /** * Structures the initial data into the format: * { * global: initialData, * steps: {} * } * @param initialData Initial data */ structureInitialData(initialData) { // If the data is already structured, return it as is if (initialData && typeof initialData === 'object' && 'global' in initialData && 'steps' in initialData) { return initialData; } // Otherwise, structure it return { global: initialData || {}, steps: {} }; } /** * Compensates the Saga (rolls back) */ async compensate() { try { this.setStatus(types_1.SagaStatus.COMPENSATING); // Update state store if provided if (this.stateStore) { await this.stateStore.update(this.getId(), this.context); } // Publish compensation event await this.participant.publish('SAGA_COMPENSATE', this.context.data, this.getId()); // Wait for completion return await this.completionPromise; } catch (error) { this.setStatus(types_1.SagaStatus.COMPENSATION_FAILED, error instanceof Error ? error.message : String(error)); // Update state store if provided if (this.stateStore) { await this.stateStore.update(this.getId(), this.context); } return this.getContext(); } } /** * Sets up event handlers for the coordinator */ setupEventHandlers() { // Handle saga completion this.participant.on('SAGA_COMPLETED', async (event) => { this.setStatus(types_1.SagaStatus.COMPLETED); // Update context with event payload this.updateContext(event.payload); // Update state store if provided if (this.stateStore) { await this.stateStore.update(this.getId(), this.context); } // Resolve completion promise if (this.completionResolve) { this.completionResolve(this.getContext()); } }); // Handle saga failure this.participant.on('SAGA_FAILED', async (event) => { this.setStatus(types_1.SagaStatus.FAILED, event.payload.error); // Update context with event payload this.updateContext(event.payload); // Update state store if provided if (this.stateStore) { await this.stateStore.update(this.getId(), this.context); } // Reject completion promise if (this.completionReject) { this.completionReject(new Error(event.payload.error)); } }); // Handle saga compensation completed this.participant.on('SAGA_COMPENSATED', async (event) => { this.setStatus(types_1.SagaStatus.COMPENSATED); // Update context with event payload this.updateContext(event.payload); // Update state store if provided if (this.stateStore) { await this.stateStore.update(this.getId(), this.context); } // Resolve completion promise if (this.completionResolve) { this.completionResolve(this.getContext()); } }); // Handle saga compensation failed this.participant.on('SAGA_COMPENSATION_FAILED', async (event) => { this.setStatus(types_1.SagaStatus.COMPENSATION_FAILED, event.payload.error); // Update context with event payload this.updateContext(event.payload); // Update state store if provided if (this.stateStore) { await this.stateStore.update(this.getId(), this.context); } // Reject completion promise if (this.completionReject) { this.completionReject(new Error(event.payload.error)); } }); } } exports.StructuredSagaCoordinator = StructuredSagaCoordinator;