node-sagas-orchestrator
Version:
Library for handling distributed transactions using an orchestrator
239 lines (227 loc) • 6.89 kB
JavaScript
(function (global, factory) {
typeof exports === 'object' && typeof module !== 'undefined' ? factory(exports) :
typeof define === 'function' && define.amd ? define(['exports'], factory) :
(global = typeof globalThis !== 'undefined' ? globalThis : global || self, factory(global.heap = {}));
})(this, (function (exports) { 'use strict';
class SagaCompensationFailed extends Error {
originalError;
constructor(e) {
super(e.message);
this.stack = e.stack;
this.originalError = e;
}
}
class SagaExecutionFailed extends Error {
originalError;
constructor(e) {
super(e.message);
this.stack = e.stack;
this.originalError = e;
}
}
exports.SagaStates = void 0;
(function (SagaStates) {
SagaStates["New"] = "New";
SagaStates["InProgress"] = "In progress";
SagaStates["InCompensation"] = "In compensation";
SagaStates["Complete"] = "Complete";
SagaStates["CompensationComplete"] = "Compensation complete";
SagaStates["CompensationError"] = "Compensation error";
})(exports.SagaStates || (exports.SagaStates = {}));
class Saga {
sagaFlow;
state;
invokeError;
compensationError;
constructor(sagaFlow) {
this.sagaFlow = sagaFlow;
this.state = exports.SagaStates.New;
}
getState() {
return this.state;
}
async execute() {
this.state = exports.SagaStates.InProgress;
try {
await this.sagaFlow.invoke();
this.state = exports.SagaStates.Complete;
}
catch (e) {
this.state = exports.SagaStates.InCompensation;
this.invokeError = e;
await this.runCompensationFlow();
throw new SagaExecutionFailed(e);
}
}
async runCompensationFlow() {
try {
await this.sagaFlow.compensate();
this.state = exports.SagaStates.CompensationComplete;
}
catch (e) {
this.state = exports.SagaStates.CompensationError;
this.compensationError = e;
throw new SagaCompensationFailed(e);
}
}
}
class Step {
invocation;
compensation;
name;
key;
constructor(name = '') {
this.name = name;
}
setInvocation(method) {
this.invocation = method;
}
setCompensation(method) {
this.compensation = method;
}
setKey(key) {
this.key = key;
}
getKey() {
return this.key;
}
async invoke(sagaContextWrapper) {
if (this.invocation) {
return this.invocation(sagaContextWrapper);
}
}
async compensate(sagaContextWrapper) {
if (this.compensation) {
return this.compensation(sagaContextWrapper);
}
}
getName() {
return this.name;
}
}
class SagaContext {
steps;
_context;
currentStepIndex = -1;
disabledSteps = new Set();
constructor(steps = [], _context = null) {
this.steps = steps;
this._context = _context;
}
set context(ctx) {
this._context = ctx;
}
get context() {
return this._context;
}
set currentStep(index) {
this.currentStepIndex = index;
}
get currentStep() {
return this.currentStepIndex;
}
disableStep(key) {
this.disabledSteps.add(key);
}
enableStep(key) {
this.disabledSteps.delete(key);
}
isStepDisabled(key) {
return this.disabledSteps.has(key);
}
}
class SagaContextMediator {
sagaContext;
constructor(sagaContext) {
this.sagaContext = sagaContext;
}
disableStep(key) {
return this.sagaContext.disableStep(key);
}
enableStep(key) {
return this.sagaContext.enableStep(key);
}
isStepDisable(key) {
return this.sagaContext.isStepDisabled(key);
}
getContext() {
return this.sagaContext.context;
}
setContext(context) {
return (this.sagaContext.context = context);
}
}
class SagaFlow {
steps;
compensationSteps = [];
context;
sagaContextMediator;
constructor(steps = [], ctx = new SagaContext()) {
this.steps = steps;
this.context = ctx;
this.sagaContextMediator = new SagaContextMediator(this.context);
}
async invoke() {
for (const step of this.steps) {
if (!this.context.isStepDisabled(step.getKey())) {
this.compensationSteps.push(step);
await step.invoke(this.sagaContextMediator);
}
}
}
async compensate() {
for (const step of this.compensationSteps.reverse()) {
await step.compensate(this.sagaContextMediator);
}
}
}
class Factory {
createSaga(steps, ctx) {
return new Saga(this.createSagaFlow(steps, ctx));
}
createSagaFlow(steps, ctx) {
return new SagaFlow(steps, ctx);
}
createStep(name = '') {
return new Step(name);
}
}
class SagaBuilder {
currentStep;
steps = [];
factory = new Factory();
context;
setFactory(factory) {
this.factory = factory;
}
step(name = '') {
this.currentStep = this.factory.createStep(name);
this.steps.push(this.currentStep);
return this;
}
invoke(method) {
this.currentStep.setInvocation(method);
return this;
}
withCompensation(method) {
this.currentStep.setCompensation(method);
return this;
}
withKey(key) {
this.currentStep.setKey(key);
return this;
}
setContext(ctx) {
this.context = ctx;
return this;
}
build() {
const ctx = new SagaContext(this.steps, this.context);
return this.factory.createSaga(this.steps, ctx);
}
}
exports.Saga = Saga;
exports.SagaBuilder = SagaBuilder;
exports.SagaCompensationFailed = SagaCompensationFailed;
exports.SagaExecutionFailed = SagaExecutionFailed;
}));