import { remove } from "bottom-line-utils"; import iassign from "immutable-assign"; type Listener = (s: S) => void; // @TODO freeze state on dev class Truth { private readonly _listeners: Listener[]; private _state: State; public constructor(initialState: State) { this._listeners = []; this._state = initialState; iassign.setOption({ ignoreIfNoChange: true }); } public addChangeListener(fn: Listener) { if (this._listeners.includes(fn)) { return; } this._listeners.push(fn); } public removeChangeListener(fn: Listener) { remove(this._listeners, (item) => item === fn); } private _notify() { // Spreading listeners to make sure every listener is called for a specific update, even if one of the listeners // Will remove another [...this._listeners].forEach(fn => { fn(this._state); }); } public update = (deepSelector: (state: State) => TProp, setter: (oldVal: TProp) => TProp) => { if (process.env.NODE_ENV !== "production") { if (typeof deepSelector !== "function") { throw new TypeError( "`react-source-of-truth` update function expected function as a first argument. " + "If you are updating from v1 take note this single API is not backwards compatible. " + "Please refer to the docs on how to upgrade your code."); } } const newState = iassign(this._state, deepSelector, setter); if (newState !== this._state) { this._state = newState; this._notify(); } return newState; }; public replace = (newState: State) => { if (newState !== this._state) { this._state = newState; this._notify(); } return newState; }; public getState = () => this._state; } export { Truth, };