@actyx/sdk
Version:
Actyx SDK
104 lines • 5.66 kB
JavaScript
;
Object.defineProperty(exports, "__esModule", { value: true });
exports.eventsMonotonic = void 0;
const Ord_1 = require("fp-ts/lib/Ord");
const rxjs_1 = require("../../node_modules/rxjs");
const operators_1 = require("../../node_modules/rxjs/operators");
const types_1 = require("../types");
const util_1 = require("../util");
const log_1 = require("./log");
const bufferOp_1 = require("../util/bufferOp");
const eventKeyGreater = (0, Ord_1.gt)(types_1.EventKey.ord);
const horizonFilter = (fixedStart) => (fixedStart === null || fixedStart === void 0 ? void 0 : fixedStart.horizon) ? `${fixedStart.horizon.lamport}/${fixedStart.horizon.stream}` : undefined;
/**
* Create a new endpoint, based on the given EventStore and SnapshotStore.
* The returned function itself is stateless between subsequent calls --
* all state is within the EventStore itself.
*/
const eventsMonotonic = (eventStore) => {
// Stream realtime events from the given point on.
// Actyx delivers events before `present` (i.e. before the first `offsets` msg) in ascending order.
// As soon as time-travel would occur, the stream terminates with a TimetravelMsg.
const monotonicFrom = (session, subscriptions, fixedStart) => {
const horizon = horizonFilter(fixedStart);
let diagPrinted = false;
return eventStore
.subscribeMonotonic(session, (fixedStart === null || fixedStart === void 0 ? void 0 : fixedStart.from) || {}, subscriptions, horizon)
.pipe((0, bufferOp_1.bufferOp)(1), (0, operators_1.concatMap)((next) => {
const emit = [];
let events = null;
for (const x of next) {
switch (x.type) {
case 'diagnostic': {
if (!diagPrinted) {
diagPrinted = true;
log_1.default.submono.debug(`(${session}) AQL ${x.severity}: ${x.message}`);
}
break;
}
case 'event': {
x.caughtUp && log_1.default.submono.debug('caught up', session);
if (events === null) {
events = { type: types_1.MsgType.events, events: [x], caughtUp: x.caughtUp };
emit.push(events);
}
else {
events.events.push(x);
events.caughtUp = x.caughtUp;
}
break;
}
case 'offsets': {
if (events === null) {
events = { type: types_1.MsgType.events, events: [], caughtUp: true };
emit.push(events);
}
else {
events.caughtUp = true;
}
break;
}
case 'timeTravel': {
events = null;
emit.push({ type: types_1.MsgType.timetravel, trigger: x.newStart });
}
}
}
return emit;
}));
};
// Given a FixedStart point, check whether we can reach `present` without time travel.
// If so, apply whenValid. Otherwise apply whenInvalid to the earliest chunk between start and present.
const validateFixedStart = (subscriptions, present, attemptStartFrom, whenInvalid, whenValid) => {
return eventStore
.query(attemptStartFrom.from, present, subscriptions, types_1.EventsSortOrder.Ascending, horizonFilter(attemptStartFrom))
.pipe((0, operators_1.defaultIfEmpty)(null), (0, operators_1.first)(), (0, operators_1.concatMap)((earliest) => earliest && eventKeyGreater(attemptStartFrom.latestEventKey, earliest)
? whenInvalid(earliest)
: whenValid()));
};
// Client thinks it has valid offsets to start from -- it may be wrong, though!
const startFromFixedOffsets = (session, subscriptions, present) => (attemptStartFrom) => {
const whenValid = () => monotonicFrom(session, subscriptions, attemptStartFrom);
const whenInvalid = (earliest) => {
log_1.default.submono.debug(session, 'discarding outdated requested FixedStart', types_1.EventKey.format(attemptStartFrom.latestEventKey), 'due to', types_1.EventKey.format(earliest));
// TODO this time travel msg should also have a good `high` element
// (consider this if/when ever implementing this Rust-side)
return (0, rxjs_1.of)(timeTravelMsg(session, attemptStartFrom.latestEventKey, [earliest]));
};
return validateFixedStart(subscriptions, present, attemptStartFrom, whenInvalid, whenValid);
};
return (session, subscriptions, attemptStartFrom) => {
// Client explicitly requests us to start at a certain point
return (0, rxjs_1.from)(eventStore.offsets()).pipe((0, operators_1.concatMap)((offsets) => startFromFixedOffsets(session, subscriptions, offsets.present)(attemptStartFrom)));
};
};
exports.eventsMonotonic = eventsMonotonic;
const timeTravelMsg = (session, previousHead, next) => {
log_1.default.submono.info(session, 'must time-travel back to:', types_1.EventKey.format(next[0]));
const high = (0, util_1.getInsertionIndex)(next, previousHead, types_1.EventKey.ord.compare) - 1;
return {
type: types_1.MsgType.timetravel,
trigger: next[0],
};
};
//# sourceMappingURL=subscribe_monotonic.js.map