import { assign, blankObject, _differs, _differsImmutable, get, on, fire } from './shared.js'; function Store(state, options) { this._handlers = {}; this._dependents = []; this._computed = blankObject(); this._sortedComputedProperties = []; this._state = assign({}, state); this._differs = options && options.immutable ? _differsImmutable : _differs; } assign(Store.prototype, { _add(component, props) { this._dependents.push({ component: component, props: props }); }, _init(props) { const state = {}; for (let i = 0; i < props.length; i += 1) { const prop = props[i]; state['$' + prop] = this._state[prop]; } return state; }, _remove(component) { let i = this._dependents.length; while (i--) { if (this._dependents[i].component === component) { this._dependents.splice(i, 1); return; } } }, _set(newState, changed) { const previous = this._state; this._state = assign(assign({}, previous), newState); for (let i = 0; i < this._sortedComputedProperties.length; i += 1) { this._sortedComputedProperties[i].update(this._state, changed); } this.fire('state', { changed, previous, current: this._state }); const dependents = this._dependents.slice(); // guard against mutations for (let i = 0; i < dependents.length; i += 1) { const dependent = dependents[i]; const componentState = {}; let dirty = false; for (let j = 0; j < dependent.props.length; j += 1) { const prop = dependent.props[j]; if (prop in changed) { componentState['$' + prop] = this._state[prop]; dirty = true; } } if (dirty) dependent.component.set(componentState); } this.fire('update', { changed, previous, current: this._state }); }, _sortComputedProperties() { const computed = this._computed; const sorted = this._sortedComputedProperties = []; const visited = blankObject(); let currentKey; function visit(key) { const c = computed[key]; if (c) { c.deps.forEach(dep => { if (dep === currentKey) { throw new Error(`Cyclical dependency detected between ${dep} <-> ${key}`); } visit(dep); }); if (!visited[key]) { visited[key] = true; sorted.push(c); } } } for (const key in this._computed) { visit(currentKey = key); } }, compute(key, deps, fn) { let value; const c = { deps, update: (state, changed, dirty) => { const values = deps.map(dep => { if (dep in changed) dirty = true; return state[dep]; }); if (dirty) { const newValue = fn.apply(null, values); if (this._differs(newValue, value)) { value = newValue; changed[key] = true; state[key] = value; } } } }; this._computed[key] = c; this._sortComputedProperties(); const state = assign({}, this._state); const changed = {}; c.update(state, changed, true); this._set(state, changed); }, fire, get, on, set(newState) { const oldState = this._state; const changed = this._changed = {}; let dirty = false; for (const key in newState) { if (this._computed[key]) throw new Error(`'${key}' is a read-only property`); if (this._differs(newState[key], oldState[key])) changed[key] = dirty = true; } if (!dirty) return; this._set(newState, changed); } }); export { Store };