UNPKG

@angstone/monostone

Version:

monolitic event-sourced framework

321 lines 13.3 kB
"use strict"; /** * 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