UNPKG

@angstone/monostone

Version:

monolitic event-sourced framework

167 lines 6.68 kB
"use strict"; // TODO: Make a field on reducer to tell how to recover from reducer error in db 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 error_1 = require("../error"); const _1 = require("./"); const events_1 = require("events"); const store_1 = require("../store"); const REDUCER_CONTROLLER_NAME = "reducer_control"; const REDUCER_REST_TIME = 10; const SECURITY_TIME_DELAY_FOR_EMIT_REDUCED = 3; const state = { firstEventNumberToReduce: 0, reducers: {}, eventStack: [], reducing: false, lastTimeReducing: Date.now(), moduleStopped: false, eventReduced$: new events_1.EventEmitter().setMaxListeners(Infinity), }; const config = () => { state.firstEventNumberToReduce = 0; state.reducers = {}; state.eventStack = []; state.reducing = false; state.lastTimeReducing = Date.now(); state.moduleStopped = false; state.eventReduced$ = new events_1.EventEmitter().setMaxListeners(Infinity); }; const loadFeatures = (features) => __awaiter(this, void 0, void 0, function* () { features.forEach((feature) => { if (feature.commands) { feature.commands.forEach((command) => { if (command.reducer !== undefined) { state.reducers[command.commandType] = { reducer: command.reducer, subscription: _1.EventModule.getEventReadStream().addListener(command.commandType, ((eventRead) => { if (eventRead.eventNumber > state.firstEventNumberToReduce) { state.eventStack.push({ commandType: command.commandType, eventRead, reducer: command.reducer, }); } })), }; } }); } }); }); const getFirstEventNumberToReduce = () => __awaiter(this, void 0, void 0, function* () { yield registerDbControl(); return state.firstEventNumberToReduce; }); /** * 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"); const oldRegister = yield store_1.db().collection(REDUCER_CONTROLLER_NAME) .find({ id: REDUCER_CONTROLLER_NAME }).toArray(); if (oldRegister.length === 0) { yield store_1.db().collection(REDUCER_CONTROLLER_NAME) .insertOne({ endEvent: 0, id: REDUCER_CONTROLLER_NAME, startEvent: 0, }); state.firstEventNumberToReduce = 0; } else { if (oldRegister[0].startEvent === oldRegister[0].endEvent) { state.firstEventNumberToReduce = oldRegister[0].endEvent; } else { error_1.error.fatal("unsyncronization in event reduce", "you must apply manual correction in db"); } } }); /** * starts the reducer reading and reducing all past events */ const start = () => __awaiter(this, void 0, void 0, function* () { reduce(); yield completePastReducing(); ast.log("reducer controller started"); }); /** * reduces all non reduced yet events on stack */ const reduce = () => __awaiter(this, void 0, void 0, function* () { while (state.eventStack.length !== 0 && !state.moduleStopped) { const reduceRecipe = state.eventStack.shift(); if (reduceRecipe && reduceRecipe.reducer) { yield reduceMarkStart(reduceRecipe.eventRead.eventNumber); yield reduceRecipe.reducer.process(reduceRecipe.eventRead.request, reduceRecipe.eventRead.eventNumber).catch((err) => { error_1.error.fatal(err, "failed reducing command " + reduceRecipe.commandType); }); yield reduceMarkEnd(reduceRecipe.eventRead.eventNumber); setTimeout(() => { state.eventReduced$.emit(reduceRecipe.commandType, reduceRecipe.eventRead); }, SECURITY_TIME_DELAY_FOR_EMIT_REDUCED); } } yield ast.delay(REDUCER_REST_TIME); if (!state.moduleStopped) { reduce(); } }); const reduceMarkStart = (eventNumber) => __awaiter(this, void 0, void 0, function* () { state.reducing = true; while (store_1.db() === undefined) { yield ast.delay(REDUCER_REST_TIME); } yield store_1.db().collection(REDUCER_CONTROLLER_NAME) .updateOne({ id: REDUCER_CONTROLLER_NAME }, { $set: { startEvent: eventNumber } }); }); const reduceMarkEnd = (eventNumber) => __awaiter(this, void 0, void 0, function* () { yield store_1.db().collection(REDUCER_CONTROLLER_NAME) .updateOne({ id: REDUCER_CONTROLLER_NAME }, { $set: { endEvent: eventNumber } }); state.lastTimeReducing = Date.now(); state.reducing = false; }); /** * await all past events to be reduced */ const completePastReducing = () => __awaiter(this, void 0, void 0, function* () { while (!(state.eventStack.length === 0 && _1.EventModule.isStreamInLive())) { yield new Promise((resolve) => { setTimeout(resolve, REDUCER_REST_TIME * 5); }); } }); const stop = () => __awaiter(this, void 0, void 0, function* () { state.moduleStopped = true; while (!(yield isFree())) { yield ast.delay(REDUCER_REST_TIME); } }); const isFree = () => __awaiter(this, void 0, void 0, function* () { if (state.reducing) return false; else { if ((Date.now() - state.lastTimeReducing) > 3 * REDUCER_REST_TIME) return !state.reducing; return false; } }); const getEventReducedStream = () => state.eventReduced$; exports.ReducerModule = Object.assign({}, _1.BasicModule, { config, loadFeatures, start, stop, isFree, getFirstEventNumberToReduce, getEventReducedStream }); //# sourceMappingURL=reducer.module.js.map