@angstone/monostone
Version:
monolitic event-sourced framework
167 lines • 6.68 kB
JavaScript
;
// 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