UNPKG

smart-saga-pattern

Version:

A library implementing the Saga pattern for microservice architecture

159 lines (158 loc) 5.84 kB
"use strict"; Object.defineProperty(exports, "__esModule", { value: true }); exports.StructuredSagaExecutor = void 0; const types_1 = require("../types"); /** * Executes a Saga definition 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 StructuredSagaExecutor { /** * Creates a new StructuredSagaExecutor * @param definition Saga definition to execute * @param context Initial Saga context */ constructor(definition, context) { this.currentStepIndex = -1; this.definition = definition; this.context = { ...context }; // Initialize steps object if it doesn't exist if (!this.hasStepsObject()) { this.initializeStepsObject(); } } /** * Checks if the context has a steps object */ hasStepsObject() { return this.context.data && typeof this.context.data === 'object' && 'steps' in this.context.data && typeof this.context.data.steps === 'object'; } /** * Initializes the steps object in the context */ initializeStepsObject() { if (!this.context.data) { this.context.data = {}; } this.context.data.steps = {}; } /** * Executes the Saga */ async execute() { const steps = this.definition.getSteps(); try { this.context.status = types_1.SagaStatus.EXECUTING; for (let i = 0; i < steps.length; i++) { this.currentStepIndex = i; const step = steps[i]; try { // Execute the transaction with the requiresCompensation flag const requiresCompensation = step.requiresCompensation !== undefined ? step.requiresCompensation : true; // Initialize step data if it doesn't exist this.initializeStepData(step.transaction.getName()); // Execute the transaction const result = await step.transaction.execute(this.context.data, requiresCompensation); // Store the result in the step's output field this.updateStepOutput(step.transaction.getName(), result); } catch (error) { // Transaction failed, start compensation await this.compensate(); return this.context; } } // All steps completed successfully this.context.status = types_1.SagaStatus.COMPLETED; this.context.endTime = Date.now(); return this.context; } catch (error) { // Something went wrong during execution or compensation this.context.status = types_1.SagaStatus.FAILED; this.context.error = error instanceof Error ? error.message : String(error); this.context.endTime = Date.now(); return this.context; } } /** * Initializes step data in the context * @param stepName Name of the step */ initializeStepData(stepName) { const steps = this.context.data.steps; if (!steps[stepName]) { steps[stepName] = { input: {}, output: {}, compensation: {} }; } } /** * Updates step output in the context * @param stepName Name of the step * @param output Output data */ updateStepOutput(stepName, output) { const steps = this.context.data.steps; if (steps[stepName]) { steps[stepName].output = output; } } /** * Compensates the Saga */ async compensate() { var _a; const steps = this.definition.getSteps(); this.context.status = types_1.SagaStatus.COMPENSATING; try { // Compensate steps in reverse order for (let i = this.currentStepIndex; i >= 0; i--) { const step = steps[i]; // Only compensate if the step requires compensation if (step.compensation && (step.requiresCompensation !== false)) { try { const stepName = step.transaction.getName(); const steps = this.context.data.steps; // Get the transaction output from the step's output field const transactionOutput = (_a = steps[stepName]) === null || _a === void 0 ? void 0 : _a.output; // Execute compensation with transaction output and name await step.compensation.execute(this.context.data, transactionOutput, step.transaction.getName()); } catch (error) { // Compensation failed this.context.status = types_1.SagaStatus.COMPENSATION_FAILED; this.context.error = error instanceof Error ? error.message : String(error); this.context.endTime = Date.now(); throw error; } } } // All compensations completed successfully this.context.status = types_1.SagaStatus.COMPENSATED; this.context.endTime = Date.now(); } catch (error) { // Re-throw the error to be caught by the execute method throw error; } } } exports.StructuredSagaExecutor = StructuredSagaExecutor;