UNPKG

typedux

Version:

Slightly adjusted Redux (awesome by default) for TS

228 lines 8.19 kB
import { getLogger } from "@3fv/logger-proxy"; import RootReducer from "../reducers/RootReducer"; // Vendor import { createStore } from "redux"; import "symbol-observable"; import { getValue } from "@3fv/guard"; import { isFunction, isString } from "../util"; import { ActionContainer } from "../actions"; import StateObserver from "./StateObserver"; import { DefaultLeafReducer } from "../reducers/DefaultLeafReducer"; import { INTERNAL_KEY } from "../constants"; import { InternalState } from "../internal/InternalState"; import DumbReducer from "../reducers/DumbReducer"; import { selectorChain } from "../selectors"; import _get from "lodash/get"; import { Option } from "@3fv/prelude-ts"; import { isDev } from "../dev"; const log = getLogger(__filename); export function processLeafReducersAndStates(leafReducersOrStates) { const leafReducers = leafReducersOrStates.filter(it => isFunction(getValue(() => it.leaf))), leafStates = leafReducersOrStates.filter(it => !isFunction(getValue(() => it.leaf)) && isString(getValue(() => it.type))), otherReducers = leafReducersOrStates.filter(it => isFunction(it)); return [ ...otherReducers, ...leafReducers, ...leafStates.map(state => new DumbReducer(state)) ]; } /** * Manage the redux store for RADS */ export class ObservableStore { constructor(leafReducersOrStates, enhancer = undefined, rootStateType = undefined, defaultStateValue = undefined) { this.rootStateType = rootStateType; this.defaultStateValue = defaultStateValue; this.observers = []; this.actionFactories = new Map(); this.observable = () => { const store = this; return { /** * The minimal observable subscription method. * @param {Object} observer Any object that can be used as an observer. * The observer object should have a `next` method. * @returns {subscription} An object with an `unsubscribe` method that can * be used to unsubscribe the observable from the store, and prevent further * emission of values from the observable. */ subscribe(observer) { if (typeof observer !== "object" || observer === null) { throw new TypeError("Expected the observer to be an object."); } function observeState() { if (observer.next) { observer.next(store.getState()); } } observeState(); const unsubscribe = store.subscribe(observeState); return { unsubscribe }; }, [Symbol.observable]: store.observable }; }; this[Symbol.observable] = this.observable; this.actionContainer = new ActionContainer(this); this.createRootReducer(ObservableStore.createInternalReducer(), ...processLeafReducersAndStates(leafReducersOrStates)); this.store = createStore(this.rootReducerFn, this.rootReducer.defaultState(defaultStateValue), enhancer !== null && enhancer !== void 0 ? enhancer : (next => next)); this.subscribe(() => this.scheduleNotification()); } /** * Factory method for creating a new observable store * * @param enhancer * @returns {ObservableStore<S>} * @param rootStateType * @param defaultStateValue * @param leafReducersOrStates */ static createObservableStore(leafReducersOrStates, enhancer = undefined, rootStateType = undefined, defaultStateValue = undefined) { return new ObservableStore(leafReducersOrStates, enhancer, rootStateType, defaultStateValue); } /** * Create simple reducers * * @param {string | State} statesOrKeys * @returns {Array<State>} */ static makeSimpleReducers(...statesOrKeys) { return statesOrKeys .map(state => (isString(state) ? { type: state } : state)) .map(state => new DumbReducer(state)); } /** * Create a internal reducer * * @returns {DefaultLeafReducer<InternalState, ActionMessage<InternalState>>} */ static createInternalReducer() { return DefaultLeafReducer.create(INTERNAL_KEY, InternalState); } setOnError(onError) { var _a; (_a = this.rootReducer) === null || _a === void 0 ? void 0 : _a.setOnError(onError); return this; } /** * Get a prepared set of actions * * @param keyOrCtor - class name of class constructor to search for */ getActions(keyOrCtor) { const key = isString(keyOrCtor) ? keyOrCtor : keyOrCtor.name; return Option.ofNullable(this.actionFactories.get(key)).getOrCall(() => { if (isString(keyOrCtor)) { throw Error(`No registered actions with key: ${keyOrCtor}`); } const actions = new keyOrCtor(this); this.actionFactories.set(key, actions); return actions; }); } /** * Create a new root reducer * * @param leafReducers * @returns {any} */ createRootReducer(...leafReducers) { this.rootReducer = new RootReducer(this, this.rootStateType, ...leafReducers); this.rootReducerFn = this.rootReducer.makeGenericHandler(); return this.rootReducerFn; } /** * Retrieve the redux store under everything * * @returns {any} */ getReduxStore() { return this.store; } /** * Update the reducers */ replaceReducers(...leafReducers) { const rootReducerFn = this.createRootReducer(ObservableStore.createInternalReducer(), ...leafReducers); this.store.replaceReducer(rootReducerFn); } subscribe(listener) { return this.getReduxStore().subscribe(listener); } replaceReducer(nextReducer) { throw new Error("We don't play with no regular reducers ;)"); } /** * Retrieve the current state * @returns {*} */ getState() { return this.getReduxStore().getState(); } getInternalState() { return this.getState()[INTERNAL_KEY]; } /** * Dispatch typed message * * @param action * @returns {A|undefined|IAction} */ dispatch(action) { return this.getReduxStore().dispatch(action); } /** * Schedule notifications to go out on next tick */ scheduleNotification() { let state = this.getState(); this.observers.forEach(listener => listener.onChange(state)); } /** * */ onChange() { this.scheduleNotification(); } /** * Create a new selector from the store's state */ selector() { return selectorChain(this, null); } observe(pathOrSelector, handler) { let selector; if (isString(pathOrSelector) || Array.isArray(pathOrSelector)) { const keyPath = pathOrSelector ? Array.isArray(pathOrSelector) ? pathOrSelector : pathOrSelector.split(".") : []; selector = ((state) => this.getValueAtPath(state, keyPath)); } else { selector = pathOrSelector; } let observer = new StateObserver(selector, handler); this.observers.push(observer); return () => { if (observer.removed) { if (log.isDebugEnabled() && isDev) { log.debug("Already removed this observer", observer); } return; } this.observers.find((it, index) => { if (observer === it) { this.observers.splice(index, 1); return true; } return false; }); observer.removed = true; }; } getValueAtPath(state, keyPath) { return _get(state, keyPath); } } //# sourceMappingURL=ObservableStore.js.map