UNPKG

tsbase

Version:

Base class libraries for TypeScript

166 lines 6.75 kB
"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