UNPKG

rxbox

Version:
242 lines 8.82 kB
import { Observable, BehaviorSubject } from 'rxjs'; import { distinctUntilChanged } from 'rxjs/operators'; import { cloneDeep, get, isEqual, assign } from 'lodash-es'; import { stringifyDate } from './stringifyDate'; import { createUUID } from './uuid'; const appState = {}; const store = new BehaviorSubject(appState); const slotInStorage = '__rxbox'; export class RXBox { constructor() { this.lastChanges = null; this.store = store; this.saveToLocalStorage = false; this.saveToSessionStorage = false; // hold the history of the app state // only work when debug is set to true this.history = []; this.subscribers = []; // Observable that watch for any change in the store this.changes = store.asObservable().pipe(distinctUntilChanged()); // ************************** API ************************** // change this for true when you want to push to history this.debug = false; if (RXBox.isWasRun && typeof window !== 'undefined') { throw 'You can only create one instance of RXBox in your app'; } RXBox.isWasRun = true; if (typeof window !== 'undefined') { ((window) => { window.RXBox = { state: this.store, history: this.history, subscribers: this.subscribers }; })(window); } } static preventFunctionsInKey(obj) { for (let i in obj) { if (obj[i] !== null && typeof obj[i] === 'object') { RXBox.preventFunctionsInKey(obj[i]); } if (typeof obj[i] === 'function') { throw { msg: "RXBox error -> can't store function inside RXBox store", object: obj, key: obj[i] }; } } } // push old state to history pushHistory() { // prevent save more then one version in the history when not // running in debug mode if (!this.debug && this.history.length) { this.history.shift(); } const state = this.getState(); this.history.push(state); } // show the history of the state getHistory() { return this.history; } // remove all state history clearHistory() { this.history = []; } // watch for key change in store (you can also use nested key // like "key1.key2.key3") /** * @deprecated Since version 0.5.6. Will be deleted in future version */ watch(key, subscriberName, passByReference) { return new Observable(observer => { const uuid = createUUID(); this.subscribers[uuid] = { name: subscriberName, key }; const sub = this.changes.subscribe(state => { // watch for all change (no key specified) if (typeof key === 'undefined') { if (passByReference) { observer.next(state); } else { observer.next(cloneDeep(state)); } return; } // if we inside this catch meaning that the key to watch // is not inside the last change so we can return // without response to the subscribers const isKeyInLastChange = get(this.lastChanges, key); if (typeof isKeyInLastChange === 'undefined') return; const newValue = get(state, key); const oldState = this.history[this.history.length - 1]; const oldValue = get(oldState, key); const equals = isEqual(newValue, oldValue); if (!equals) { const val = get(state, key); if (passByReference) { observer.next(val); } else { observer.next(cloneDeep(val)); } } }); observer.add(() => { sub.unsubscribe(); delete this.subscribers[uuid]; }); }); } // watch for key change in store (you can also use nested key // like "key1.key2.key3") select(key, subscriberName, passByReference) { return new Observable(observer => { const uuid = createUUID(); this.subscribers[uuid] = { name: subscriberName, key }; // get all keys (no key specified) let skip = false; const STATE = this.store.getValue(); let value; if (typeof key === 'undefined') { if (typeof STATE !== 'undefined') { if (passByReference) { observer.next(STATE); } else { observer.next(cloneDeep(STATE)); } skip = true; } } else { // get specific key value = get(STATE, key); if (typeof value !== 'undefined') { if (passByReference) { observer.next(value); } else { observer.next(cloneDeep(value)); } skip = true; } } const sub = this.changes.subscribe(state => { // watch for all change (no key specified) if (typeof key === 'undefined') { if (skip && isEqual(STATE, state)) { skip = false; return; } if (passByReference) { observer.next(state); } else { observer.next(cloneDeep(state)); } skip = false; return; } // is not inside the last change so we can return // without response to the subscribers const isKeyInLastChange = get(this.lastChanges, key); if (typeof isKeyInLastChange === 'undefined') return; const newValue = get(state, key); const oldState = this.history[this.history.length - 1]; const oldValue = get(oldState, key); const equals = isEqual(newValue, oldValue); if (!equals) { if (skip && isEqual(newValue, value)) { skip = false; return; } const val = get(state, key); if (passByReference) { observer.next(val); } else { observer.next(cloneDeep(val)); } skip = false; } }); observer.add(() => { sub.unsubscribe(); delete this.subscribers[uuid]; }); }); } // return current state getState(passByReference) { if (passByReference) return this.store.value; return cloneDeep(this.store.value); } // remove current state clearState() { this.store.next({}); } // merge new keys to the current state assignState(stateChanges) { if (this.debug) RXBox.preventFunctionsInKey(stateChanges); const newState = assign({}, this.getState(), stateChanges); this.pushHistory(); this.lastChanges = stateChanges; this.store.next(newState); if (this.saveToLocalStorage) { localStorage.setItem(slotInStorage, stringifyDate.stringify(this.getState())); } if (this.saveToSessionStorage) { sessionStorage.setItem(slotInStorage, stringifyDate.stringify(this.getState())); } } assignStateAsync(stateChanges) { return new Promise(resolve => { setTimeout(() => { this.assignState(stateChanges); resolve(); }); }); } getStoreFromSessionStorage() { return stringifyDate.parse(sessionStorage.getItem(slotInStorage)); } getStoreFromLocalStorage() { return stringifyDate.parse(localStorage.getItem(slotInStorage)); } } RXBox.isWasRun = false; //# sourceMappingURL=index.js.map