tsbase
Version:
Base class libraries for TypeScript
166 lines • 6.75 kB
JavaScript
"use strict";
Object.defineProperty(exports, "__esModule", { value: true });
exports.EventStore = exports.NoTransactionToRedo = exports.NoTransactionToUndo = exports.StateChangeUnnecessary = void 0;
const tslib_1 = require("tslib");
const dlv_1 = tslib_1.__importDefault(require("dlv"));
const dset_1 = require("dset");
const Queryable_1 = require("../../Collections/Queryable/Queryable");
const Strings_1 = require("../../System/Strings");
const Query_1 = require("../CommandQuery/Query");
const Observable_1 = require("../Observable/Observable");
exports.StateChangeUnnecessary = 'State change unnecessary - nothing changed';
exports.NoTransactionToUndo = 'No transaction to undo';
exports.NoTransactionToRedo = 'No transaction to redo';
class EventStore {
constructor(state) {
this.state = state;
this.stateObservers = new Map();
this.ledger = new Array();
}
GetState(member) {
if (typeof member === 'string') {
return this.getStateAtPath(member);
}
const clone = this.cloneOf(this.state);
return member ? member(clone) : clone;
}
SetState(memberOrState, state) {
return new Query_1.Query(() => {
const isGranularUpdate = ['function', 'string'].includes(typeof memberOrState) && state !== undefined;
const getCurrentState = () => {
if (isGranularUpdate && typeof memberOrState === 'function') {
return memberOrState(this.cloneOf(this.state));
}
else if (isGranularUpdate && typeof memberOrState === 'string') {
return this.getStateAtPath(memberOrState);
}
else {
return this.cloneOf(this.state);
}
};
const newState = isGranularUpdate ? state : memberOrState;
if (JSON.stringify(getCurrentState()) !== JSON.stringify(newState)) {
this.updateState(isGranularUpdate ? memberOrState : Strings_1.Strings.Empty, getCurrentState(), newState);
}
else {
throw new Error(exports.StateChangeUnnecessary);
}
return getCurrentState();
}).Execute();
}
ObservableAt(member) {
const path = typeof member === 'string' ? member : this.getPathFromMemberFunction(member);
if (!this.stateObservers.has(path)) {
this.stateObservers.set(path, new Observable_1.Observable());
}
;
return this.stateObservers.get(path);
}
GetLedger() {
return this.ledger.slice();
}
Undo() {
return new Query_1.Query(() => {
const queryableLedger = Queryable_1.Queryable.From(this.ledger);
const lastVoidableTransaction = queryableLedger.Last(t => !t.voided);
if (lastVoidableTransaction) {
lastVoidableTransaction.voided = true;
this.updateState(lastVoidableTransaction.path, lastVoidableTransaction.toState, lastVoidableTransaction.fromState, false);
}
else {
throw new Error(exports.NoTransactionToUndo);
}
return this.getStateAtPath(lastVoidableTransaction.path);
}).Execute();
}
Redo() {
return new Query_1.Query(() => {
let lastRedoableTransaction = null;
for (let index = this.ledger.length - 1; index >= 0; index--) {
const element = this.ledger[index];
if (!element.voided) {
break;
}
else {
lastRedoableTransaction = element;
}
}
if (lastRedoableTransaction) {
lastRedoableTransaction.voided = false;
this.updateState(lastRedoableTransaction.path, lastRedoableTransaction.fromState, lastRedoableTransaction.toState, false);
}
else {
throw new Error(exports.NoTransactionToRedo);
}
return this.getStateAtPath(lastRedoableTransaction.path);
}).Execute();
}
cloneOf(value) {
return value ? JSON.parse(JSON.stringify(value)) : value;
}
getPathFromMemberFunction(member) {
const regex = /[.][a-zA-Z0-9]*/g;
let path = Strings_1.Strings.Empty;
if (member) {
let match;
while ((match = regex.exec(member.toString())) !== null) {
const matchValue = path.length ? match[0] : match[0].replace('.', Strings_1.Strings.Empty);
path += matchValue;
}
}
return path;
}
getStateAtPath(path) {
return this.cloneOf((0, dlv_1.default)(this.state, path));
}
updateState(member, previousState, value, updateLedger = true) {
if (typeof member !== 'string') {
member = this.getPathFromMemberFunction(member);
}
if (updateLedger) {
this.ledger.push({
path: member,
timestamp: Date.now(),
toState: this.cloneOf(value),
fromState: previousState,
voided: false
});
}
const rootUpdate = Strings_1.Strings.IsEmptyOrWhiteSpace(member.toString());
if (rootUpdate) {
this.state = value;
}
else {
(0, dset_1.dset)(this.state, member.toString(), value);
}
this.publishToDependentObservers(member);
}
publishToDependentObservers(path) {
const targetObserverKeys = this.getTargetObservers(path);
targetObserverKeys.forEach(key => {
this.stateObservers.get(key).Publish(Strings_1.Strings.IsEmptyOrWhiteSpace(key) ?
this.GetState() :
this.cloneOf((0, dlv_1.default)(this.state, key)));
});
}
getTargetObservers(path) {
const observerKeys = Array.from(this.stateObservers.keys());
return Strings_1.Strings.IsEmptyOrWhiteSpace(path) ? observerKeys :
observerKeys.filter(k => k === Strings_1.Strings.Empty ||
k === path ||
this.childOf(k, path) ||
this.parentOf(k, path));
}
childOf(parentPath, childPath) {
return parentPath.startsWith(`${childPath}.`) ||
parentPath.startsWith(`.${childPath}`) ||
parentPath.includes(`.${childPath}.`);
}
parentOf(parentPath, childPath) {
return childPath.startsWith(`${parentPath}.`) ||
childPath.startsWith(`.${parentPath}`) ||
childPath.includes(`.${parentPath}.`);
}
}
exports.EventStore = EventStore;
//# sourceMappingURL=EventStore.js.map