@angstone/monostone
Version:
monolitic event-sourced framework
321 lines • 13.3 kB
JavaScript
;
/**
* Controlls the events toolchain
*/
var __awaiter = (this && this.__awaiter) || function (thisArg, _arguments, P, generator) {
return new (P || (P = Promise))(function (resolve, reject) {
function fulfilled(value) { try { step(generator.next(value)); } catch (e) { reject(e); } }
function rejected(value) { try { step(generator["throw"](value)); } catch (e) { reject(e); } }
function step(result) { result.done ? resolve(result.value) : new P(function (resolve) { resolve(result.value); }).then(fulfilled, rejected); }
step((generator = generator.apply(thisArg, _arguments || [])).next());
});
};
Object.defineProperty(exports, "__esModule", { value: true });
const ast = require("@angstone/node-util");
const asyncFunctions = require("async");
const error_1 = require("../error");
const _1 = require("./");
const store_1 = require("../store");
const tools_1 = require("../tools");
const SystemCommands = require("../system_commands");
const EFFECT_CONTROLLER_NAME = "effect_control";
const EFFECT_REST_TIME = 100;
const MAX_TASKS_AT_TIME = 30;
const MAX_TASK_ATTEMPTS = 3;
const TIME_BETWEEN_ATTEMPTS = 250;
const STACK_FULLFILL_STABILITY_REST_TIME = 100;
// process.env.NODE_ENV == 'development' ? 10 : 200
const EFFECT_DONE_EVENT_TYPE = SystemCommands.effectDone.featureName + ' ' +
SystemCommands.effectDone.commandName;
const EFFECT_FAILED_EVENT_TYPE = SystemCommands.effectFailed.featureName + ' ' +
SystemCommands.effectFailed.commandName;
const POSSIBILY_DOUBLE_EFFECT_ERROR = `error sending confirmation of effect to event store.
It is posible that you endup with a double execution of effect:
`;
const EFFECT_SOLVING_ERROR_MESSAGE = `error solving effect:
`;
const state = {
effectWatchers: [],
firstEventNumberToEffect: 0,
effectsToSolve: [],
moduleStopped: false,
solving: false,
lastTimeSolving: Date.now(),
};
const config = () => {
state.effectWatchers = [];
state.firstEventNumberToEffect = 0;
state.effectsToSolve = [];
state.moduleStopped = false;
state.solving = false;
state.lastTimeSolving = Date.now();
};
const loadFeatures = (features) => {
features.forEach((feature) => {
if (feature.effects) {
feature.effects.forEach((effect) => {
loadEffectsAfterCommands(effect);
});
}
});
};
const loadEffectsAfterCommands = (effect) => {
if (effect.triggerAfterCommand) {
let commandTriggers = [];
if (Array.isArray(effect.triggerAfterCommand))
commandTriggers = effect.triggerAfterCommand;
else
commandTriggers.push(effect.triggerAfterCommand);
commandTriggers.forEach((commandTrigger) => {
makeShureTriggerIsSigned(commandTrigger);
state.effectWatchers.map((effectWatcher) => {
if (effectWatcher.eventTrigger === commandTrigger) {
effectWatcher.effects.push({
effectName: effect.name,
effectMethodRun: effect.run
});
}
return effectWatcher;
});
});
}
};
const makeShureTriggerIsSigned = (commandTrigger) => {
const alreadyAddedTriggerArray = state.effectWatchers.filter((effectWatcher) => {
effectWatcher.eventTrigger === commandTrigger;
});
if (alreadyAddedTriggerArray.length == 0) {
const effectWatcher = {
eventTrigger: commandTrigger,
effects: [],
};
state.effectWatchers.push(effectWatcher);
}
};
const start = () => __awaiter(this, void 0, void 0, function* () {
fullfillStackOfEffectsToSolve();
yield _1.EventModule.awaitStreamInLive();
yield fullfillOfStackOfEffectsToSolveStability();
const listenerDone = removeFromStackSolvedEvents();
const listenerFailed = removeFromStackFailedEvents();
yield fullfillOfStackOfEffectsToSolveStability();
removeListenerToSolvedEvents(listenerDone);
removeListenerToFailedEvents(listenerFailed);
solve();
});
const stop = () => __awaiter(this, void 0, void 0, function* () {
state.moduleStopped = true;
while (!(yield isFree())) {
yield ast.delay(EFFECT_REST_TIME);
}
});
const fullfillOfStackOfEffectsToSolveStability = () => __awaiter(this, void 0, void 0, function* () {
const lengthNow = state.effectsToSolve.length;
const lengthAfterRestTime = yield new Promise((resolve) => {
setTimeout(() => {
resolve(state.effectsToSolve.length);
}, STACK_FULLFILL_STABILITY_REST_TIME);
});
if (lengthNow != lengthAfterRestTime)
yield fullfillOfStackOfEffectsToSolveStability();
});
const removeListenerToSolvedEvents = (listenerDone) => {
_1.EventModule.getEventReadStream().removeListener(EFFECT_DONE_EVENT_TYPE, listenerDone);
};
const removeListenerToFailedEvents = (listenerFailed) => {
_1.EventModule.getEventReadStream().removeListener(EFFECT_FAILED_EVENT_TYPE, listenerFailed);
};
const removeFromStackSolvedEvents = () => {
const resolveDoneListener = (eventRead) => {
const effectDoneInfo = eventRead.request;
const effectDoneInStackArray = state.effectsToSolve.filter(effectToSolve => {
return effectToSolve.eventRead.eventNumber
== effectDoneInfo.eventNumber;
});
if (effectDoneInStackArray.length != 0)
takeEffectFromToSolveStack(effectDoneInStackArray[0]);
};
_1.EventModule.getEventReadStream().addListener(EFFECT_DONE_EVENT_TYPE, resolveDoneListener);
return resolveDoneListener;
};
const removeFromStackFailedEvents = () => {
const resolveFailedListener = (eventRead) => {
const effectFailedInfo = eventRead.request;
const effectFailedInStackArray = state.effectsToSolve.filter(effectToSolve => {
return effectToSolve.eventRead.eventNumber
== effectFailedInfo.eventNumber;
});
if (effectFailedInStackArray.length != 0)
takeEffectFromToSolveStack(effectFailedInStackArray[0]);
};
_1.EventModule.getEventReadStream().addListener(EFFECT_FAILED_EVENT_TYPE, resolveFailedListener);
return resolveFailedListener;
};
const fullfillStackOfEffectsToSolve = () => {
state.effectWatchers.forEach(effectWatcher => {
_1.EventModule.getEventReadStream().addListener(effectWatcher.eventTrigger, (eventRead) => {
if (eventRead.eventNumber > state.firstEventNumberToEffect) {
effectWatcher.effects.forEach(effect => {
state.effectsToSolve.push({
effectName: effect.effectName,
effectMethod: effect.effectMethodRun,
eventRead,
attempts: 0,
});
});
}
});
});
};
const solve = () => __awaiter(this, void 0, void 0, function* () {
// console.log('solve called at '+Date.now())
if (state.effectsToSolve.length !== 0 &&
!state.moduleStopped &&
!state.solving) {
// console.log('effectsToSolve before the solve '+Date.now())
// console.log(state.effectsToSolve)
state.solving = true;
yield new Promise((resolveParallel) => {
asyncFunctions.parallelLimit(state.effectsToSolve.map(effectToSolve => (callback) => {
effectToSolve.effectMethod(effectToSolve.eventRead.eventNumber, effectToSolve.eventRead.request).then(() => {
const request = {
eventNumber: effectToSolve.eventRead.eventNumber,
effectName: effectToSolve.effectName
};
tools_1.EventTools.send({
command: SystemCommands.effectDone, request
}).then(() => {
// console.log('solved '+effectToSolve.eventRead.eventNumber+' at '+Date.now())
takeEffectFromToSolveStack(effectToSolve);
// console.log('effectsToSolve after this solve')
// console.log(state.effectsToSolve)
callback(null);
}).catch((sendError) => {
dealWithDoubleEffectError(effectToSolve, sendError);
callback(null);
});
}).catch((e) => {
dealWithSolvingError(effectToSolve, e);
if (effectToSolve.attempts < MAX_TASK_ATTEMPTS) {
countEffectToSolveAttempt(effectToSolve);
takeEffectFromToSolveStack(effectToSolve);
setTimeout(() => {
putEffectInSolveStack(effectToSolve);
}, TIME_BETWEEN_ATTEMPTS);
callback(null);
}
else {
takeEffectFromToSolveStack(effectToSolve);
const request = {
eventNumber: effectToSolve.eventRead.eventNumber,
effectName: effectToSolve.effectName,
attempts: effectToSolve.attempts + 1
};
tools_1.EventTools.send({
command: SystemCommands.effectFailed, request
}).then(() => {
callback(null);
}).catch((sendError) => {
error_1.error.op(sendError);
callback(null);
});
}
});
}), MAX_TASKS_AT_TIME, () => {
state.solving = false;
// console.log('NOW IT IS '+Date.now())
ast.delay(EFFECT_REST_TIME).then(() => {
if (!state.moduleStopped) {
solve();
}
// console.log('AFTER IT IS '+Date.now())
resolveParallel();
});
});
});
}
else {
// console.log('NOW IT IS '+Date.now())
yield ast.delay(EFFECT_REST_TIME);
if (!state.moduleStopped) {
solve();
}
// console.log('AFTER IT IS '+Date.now())
}
});
const putEffectInSolveStack = (effectToSolve) => {
state.effectsToSolve.push(effectToSolve);
};
const takeEffectFromToSolveStack = (effectToTake) => {
// console.log('effectToSolve before:')
// console.log(state.effectsToSolve)
state.effectsToSolve =
state.effectsToSolve.filter(effectToSolve => effectToSolve.eventRead.eventNumber !=
effectToTake.eventRead.eventNumber);
// console.log('effectToSolve after:')
// console.log(state.effectsToSolve)
// console.log('tooked of '+effectToTake.eventRead.eventNumber+' at '+Date.now())
};
const countEffectToSolveAttempt = (effectToCount) => {
state.effectsToSolve =
state.effectsToSolve.map(effectToSolve => {
if (effectToSolve.eventRead.eventNumber
=== effectToCount.eventRead.eventNumber)
effectToSolve.attempts++;
return effectToSolve;
});
};
const dealWithDoubleEffectError = (effectToSolve, sendError) => {
const errorDetails = {
effectName: effectToSolve.effectName,
eventRead: effectToSolve.eventRead,
originalError: sendError
};
error_1.error.op(error_1.error.is(POSSIBILY_DOUBLE_EFFECT_ERROR + effectToSolve.effectName), errorDetails);
};
const dealWithSolvingError = (effectToSolve, effectError) => {
const errorDetails = {
originalError: effectError,
eventRead: effectToSolve.eventRead
};
error_1.error.op(error_1.error.is(EFFECT_SOLVING_ERROR_MESSAGE + effectToSolve.effectName), errorDetails);
};
const getFirstEventNumberToEffect = () => __awaiter(this, void 0, void 0, function* () {
yield registerDbControl();
return state.firstEventNumberToEffect;
});
/**
* Register the control data on database. This is necessary to coordinate
* and grant stability and roughness to the system
*/
const registerDbControl = () => __awaiter(this, void 0, void 0, function* () {
ast.log("registering db control for effects");
const oldRegister = yield store_1.db().collection(EFFECT_CONTROLLER_NAME)
.find({ id: EFFECT_CONTROLLER_NAME }).toArray();
if (oldRegister.length === 0) {
yield store_1.db().collection(EFFECT_CONTROLLER_NAME)
.insertOne({
id: EFFECT_CONTROLLER_NAME,
startEvent: 0,
});
state.firstEventNumberToEffect = 0;
}
else
state.firstEventNumberToEffect = oldRegister[0].startEvent;
});
const isFree = () => __awaiter(this, void 0, void 0, function* () {
if (state.solving)
return false;
else {
if ((Date.now() - state.lastTimeSolving) > 3 * EFFECT_REST_TIME)
return !state.solving;
return false;
}
});
exports.EffectModule = Object.assign({}, _1.BasicModule, { config,
loadFeatures,
start,
stop,
isFree,
getFirstEventNumberToEffect });
//# sourceMappingURL=effect.module.js.map