mini-rx-store
Version:
MiniRx: The Lightweight RxJS Redux Store
428 lines (412 loc) • 14.7 kB
JavaScript
'use strict';
Object.defineProperty(exports, '__esModule', { value: true });
var rxjs = require('rxjs');
var common = require('@mini-rx/common');
function createSelectFn(state$) {
function select(mapFnOrKey) {
if (!mapFnOrKey) {
return state$;
}
return state$.pipe(rxjs.map(state => {
return common.isKey(state, mapFnOrKey) ? state[mapFnOrKey] : mapFnOrKey(state);
}), rxjs.distinctUntilChanged());
}
return select;
}
function createState(initialState) {
const stateSource = new rxjs.BehaviorSubject(initialState);
const state$ = stateSource.asObservable();
function get() {
return stateSource.value;
}
function set(v) {
stateSource.next(v);
}
return {
select: createSelectFn(state$),
get,
set
};
}
function createLazyState(initialState) {
const stateSource = new rxjs.BehaviorSubject(initialState);
const state$ = stateSource.asObservable().pipe(
// Skip the first (undefined) value of the BehaviorSubject
// Very similar to a ReplaySubject(1), but more lightweight
// Emits a state object (when calling the `set` method)
rxjs.filter(v => !!v));
function get() {
return stateSource.value;
}
function set(v) {
stateSource.next(v);
}
return {
select: createSelectFn(state$),
get,
set
};
}
const storeCore = common.createStore(createState({}));
const actions$ = storeCore.actions$;
const rxEffect = common.createRegisterEffectFn(storeCore.dispatch);
class Store {}
let isStoreConfigured = false;
function configureStore(config) {
if (!isStoreConfigured) {
storeCore.configureStore(config);
isStoreConfigured = true;
return {
feature: storeCore.addFeature,
select: storeCore.appState.select,
dispatch: storeCore.dispatch,
effect: rxEffect
};
}
common.miniRxError('`configureStore` was called multiple times.');
}
function createEffectFn(subSink) {
function effect(effectFn) {
const subject = new rxjs.Subject();
const effect$ = effectFn(subject);
const effectWithDefaultErrorHandler = common.defaultEffectsErrorHandler(effect$);
subSink.sink = effectWithDefaultErrorHandler.subscribe();
return observableOrValue => {
rxjs.isObservable(observableOrValue) ? subSink.sink = observableOrValue.subscribe(v => subject.next(v)) : subject.next(observableOrValue);
};
}
return effect;
}
function createAssertState(constructorName, state) {
function isInitialized() {
const notInitializedErrorMessage = `${constructorName} has no initialState yet. ` + `Please provide an initialState before updating/getting state.`;
if (!state.get()) {
common.miniRxError(notInitializedErrorMessage);
}
}
function isNotInitialized() {
const initializedErrorMessage = `${constructorName} has initialState already.`;
if (state.get()) {
common.miniRxError(initializedErrorMessage);
}
}
return {
isInitialized,
isNotInitialized
};
}
function createConnectFn(updateStateCallback, subSink) {
return dict => {
const keys = Object.keys(dict);
keys.forEach(key => {
const obs$ = dict[key];
subSink.sink = obs$.subscribe(v => {
updateStateCallback({
[key]: v
}, "connection" /* OperationType.CONNECTION */, key);
});
});
};
}
class FeatureStore {
get featureKey() {
return this._featureKey;
}
get state() {
this.assertState.isInitialized();
return this._state.get();
}
constructor(featureKey, initialState, config = {}) {
this._state = createLazyState();
this.updateState = (stateOrCallback, operationType, name) => {
this.assertState.isInitialized();
return storeCore.dispatch({
type: common.createMiniRxActionType(operationType, this.featureKey, name),
stateOrCallback,
featureId: this.featureId
});
};
this.subSink = common.createSubSink();
this.assertState = createAssertState(this.constructor.name, this._state);
this.setState = common.createUpdateFn(this.updateState);
this.connect = createConnectFn(this.updateState, this.subSink);
this.effect = createEffectFn(this.subSink);
this.select = this._state.select;
this.featureId = common.generateId();
this._featureKey = common.generateFeatureKey(featureKey, config.multi);
if (initialState) {
this.setInitialState(initialState);
}
}
setInitialState(initialState) {
this.assertState.isNotInitialized();
storeCore.addFeature(this._featureKey, common.createFeatureStoreReducer(this.featureId, initialState));
this.subSink.sink = storeCore.appState.select(state => state[this.featureKey]).subscribe(v => this._state.set(v));
}
undo(action) {
storeCore.hasUndoExtension ? storeCore.dispatch(common.undo(action)) : common.miniRxError('UndoExtension is not initialized.');
}
destroy() {
this.subSink.unsubscribe();
storeCore.removeFeature(this._featureKey);
}
/**
* @internal
* Can be called by Angular if ComponentStore/FeatureStore is provided in a component
*/
ngOnDestroy() {
this.destroy();
}
}
function createFeatureStore(featureKey, initialState, config = {}) {
return new FeatureStore(featureKey, initialState, config);
}
const globalCsConfig = common.componentStoreConfig();
function configureComponentStores(config) {
globalCsConfig.set(config);
}
class ComponentStore {
get state() {
this.assertState.isInitialized();
return this._state.get();
}
constructor(initialState, config) {
this.config = config;
this.extensions = common.calculateExtensions(this.config, globalCsConfig.get());
this.hasUndoExtension = this.extensions.some(ext => ext.id === 1 /* ExtensionId.UNDO */);
this.actionsOnQueue = common.createActionsOnQueue();
this._state = createLazyState();
this.updateState = (stateOrCallback, operationType, name) => {
this.assertState.isInitialized();
return this.actionsOnQueue.dispatch({
type: common.createMiniRxActionType(operationType, common.componentStoreFeatureKey, name),
stateOrCallback
});
};
this.subSink = common.createSubSink();
this.assertState = createAssertState(this.constructor.name, this._state);
this.setState = common.createUpdateFn(this.updateState);
this.connect = createConnectFn(this.updateState, this.subSink);
this.effect = createEffectFn(this.subSink);
this.select = this._state.select;
if (initialState) {
this.setInitialState(initialState);
}
}
setInitialState(initialState) {
this.assertState.isNotInitialized();
const reducer = common.createComponentStoreReducer(initialState, this.extensions);
this.subSink.sink = this.actionsOnQueue.actions$.subscribe(action => {
const newState = reducer(this._state.get(),
// Initially undefined, but the reducer can handle undefined (by falling back to initial state)
action);
this._state.set(newState);
});
this.actionsOnQueue.dispatch({
type: common.createMiniRxActionType("init" /* OperationType.INIT */, common.componentStoreFeatureKey)
});
}
undo(action) {
this.hasUndoExtension ? this.actionsOnQueue.dispatch(common.undo(action)) : common.miniRxError(`${this.constructor.name} has no UndoExtension yet.`);
}
destroy() {
if (this._state.get()) {
// Dispatch an action really just for logging via LoggerExtension
// Only dispatch if an initial state was provided or setInitialState was called
this.actionsOnQueue.dispatch({
type: common.createMiniRxActionType("destroy" /* OperationType.DESTROY */, common.componentStoreFeatureKey)
});
}
this.subSink.unsubscribe();
}
/**
* @internal
* Can be called by Angular if ComponentStore/FeatureStore is provided in a component
*/
ngOnDestroy() {
this.destroy();
}
}
function createComponentStore(initialState, config) {
return new ComponentStore(initialState, config);
}
function _extends() {
return _extends = Object.assign ? Object.assign.bind() : function (n) {
for (var e = 1; e < arguments.length; e++) {
var t = arguments[e];
for (var r in t) ({}).hasOwnProperty.call(t, r) && (n[r] = t[r]);
}
return n;
}, _extends.apply(null, arguments);
}
// Credits go to Alexander Reardon
// Copied from with small modifications: https://github.com/alexreardon/memoize-one/tree/v6.0.0/src
// MIT License
//
// Copyright (c) 2019 Alexander Reardon
//
// Permission is hereby granted, free of charge, to any person obtaining a copy
// of this software and associated documentation files (the "Software"), to deal
// in the Software without restriction, including without limitation the rights
// to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
// copies of the Software, and to permit persons to whom the Software is
// furnished to do so, subject to the following conditions:
//
// The above copyright notice and this permission notice shall be included in all
// copies or substantial portions of the Software.
//
// THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
// IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
// FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
// AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
// LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
// OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
// SOFTWARE.
function isEqual(first, second) {
if (first === second) {
return true;
}
// Special case for NaN (NaN !== NaN)
if (Number.isNaN(first) && Number.isNaN(second)) {
return true;
}
return false;
}
function areInputsEqual(newInputs, lastInputs) {
// no checks needed if the inputs length has changed
if (newInputs.length !== lastInputs.length) {
return false;
}
// Using for loop for speed. It generally performs better than array.every
// https://github.com/alexreardon/memoize-one/pull/59
for (let i = 0; i < newInputs.length; i++) {
if (!isEqual(newInputs[i], lastInputs[i])) {
return false;
}
}
return true;
}
function memoizeOne(resultFn) {
let cache = null;
// breaking cache when context (this) or arguments change
function memoized(...newArgs) {
if (cache && cache.lastThis === this && areInputsEqual(newArgs, cache.lastArgs)) {
return cache.lastResult;
}
// Throwing during an assignment aborts the assignment: https://codepen.io/alexreardon/pen/RYKoaz
// Doing the lastResult assignment first so that if it throws
// the cache will not be overwritten
const lastResult = resultFn.apply(this, newArgs);
cache = {
lastResult,
lastArgs: newArgs,
lastThis: this
};
return lastResult;
}
return memoized;
}
function createSelector(...args) {
if (args.length === 1 && isSelectorsDictionary(args[0])) {
args = extractArgsFromSelectorsDictionary(args[0]);
}
const selectors = args.slice(0, args.length - 1);
const projector = args[args.length - 1];
const memoizedProjector = memoizeOne(projector);
return memoizeOne(state => {
const selectorResults = selectors.map(fn => fn(state));
return memoizedProjector(...selectorResults);
});
}
function createFeatureStateSelector(featureKey) {
if (featureKey) {
return createSelector(state => state[featureKey], featureState => featureState);
}
return state => state; // Do not memoize: when used with FeatureStore there is a new state object created for every `setState`
}
/** @deprecated Use `createFeatureStateSelector` which is more in line with `createComponentStateSelector` */
function createFeatureSelector(featureKey) {
return createFeatureStateSelector(featureKey);
}
function createComponentStateSelector() {
return state => state;
}
function isSelectorsDictionary(selectors) {
return !!selectors && typeof selectors === 'object' && Object.values(selectors).every(selector => typeof selector === 'function');
}
function extractArgsFromSelectorsDictionary(selectorsDictionary) {
const selectors = Object.values(selectorsDictionary);
const resultKeys = Object.keys(selectorsDictionary);
const projector = (...selectorResults) => resultKeys.reduce((result, key, index) => _extends({}, result, {
[key]: selectorResults[index]
}), {});
return [...selectors, projector];
}
class ReduxDevtoolsExtension extends common.AbstractReduxDevtoolsExtension {
get actions$() {
return actions$;
}
readState() {
return storeCore.appState.get();
}
updateState(state) {
storeCore.appState.set(state);
}
}
Object.defineProperty(exports, 'Actions', {
enumerable: true,
get: function () { return common.Actions; }
});
Object.defineProperty(exports, 'ImmutableStateExtension', {
enumerable: true,
get: function () { return common.ImmutableStateExtension; }
});
Object.defineProperty(exports, 'LoggerExtension', {
enumerable: true,
get: function () { return common.LoggerExtension; }
});
Object.defineProperty(exports, 'StoreExtension', {
enumerable: true,
get: function () { return common.StoreExtension; }
});
Object.defineProperty(exports, 'UndoExtension', {
enumerable: true,
get: function () { return common.UndoExtension; }
});
Object.defineProperty(exports, 'createEffect', {
enumerable: true,
get: function () { return common.createRxEffect; }
});
Object.defineProperty(exports, 'hasEffectMetaData', {
enumerable: true,
get: function () { return common.hasEffectMetaData; }
});
Object.defineProperty(exports, 'mapResponse', {
enumerable: true,
get: function () { return common.mapResponse; }
});
Object.defineProperty(exports, 'ofType', {
enumerable: true,
get: function () { return common.ofType; }
});
Object.defineProperty(exports, 'tapResponse', {
enumerable: true,
get: function () { return common.tapResponse; }
});
Object.defineProperty(exports, 'undo', {
enumerable: true,
get: function () { return common.undo; }
});
exports.ComponentStore = ComponentStore;
exports.FeatureStore = FeatureStore;
exports.ReduxDevtoolsExtension = ReduxDevtoolsExtension;
exports.Store = Store;
exports.actions$ = actions$;
exports.configureComponentStores = configureComponentStores;
exports.configureStore = configureStore;
exports.createComponentStateSelector = createComponentStateSelector;
exports.createComponentStore = createComponentStore;
exports.createFeatureSelector = createFeatureSelector;
exports.createFeatureStateSelector = createFeatureStateSelector;
exports.createFeatureStore = createFeatureStore;
exports.createSelector = createSelector;