typedux
Version:
Slightly adjusted Redux (awesome by default) for TS
228 lines • 8.19 kB
JavaScript
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