ngrx-run
Version:
Return side-effects as data from your NgRx reducers
283 lines (270 loc) • 9.88 kB
JavaScript
import { InjectionToken, Injector, NgModule } from '@angular/core';
import { Store, compose, StoreModule, META_REDUCERS, USER_PROVIDED_META_REDUCERS, combineReducers, REDUCER_FACTORY } from '@ngrx/store';
import { isObservable, Subscription } from 'rxjs';
function inject(token) {
return token;
}
/* General */
function createEffect(type, config) {
// tslint:disable-next-line:only-arrow-functions typedef
return function (params) {
const hasParams = arguments.length > 0;
const hasDependencies = config.using;
if (hasParams && hasDependencies) {
return {
__isEffect: true,
type,
call: (...deps) => config.call(...deps)(params),
using: config.using,
params
};
}
else if (!hasParams) {
return {
__isEffect: true,
type,
call: (...deps) => config.call(...deps),
using: config.using,
params
};
}
else {
return {
__isEffect: true,
type,
call: () => config.call(params),
using: config.using,
params
};
}
};
}
const unsubscribeBrand = 'Unsubscribe';
function unsubscribe(subscriptionToken, type) {
return {
__isEffect: true,
type: type !== null && type !== void 0 ? type : 'Unsubscribe',
call: () => ({
__brand: unsubscribeBrand,
subscriptionToken
})
};
}
function run(effect, handler) {
return Object.assign(Object.assign({}, effect), handler);
}
const handler = {
get() {
return proxy;
}
};
const target = {};
const proxy = new Proxy(target, handler);
function addEffectDescriptions(state, effects) {
const effectDescriptions = effects.map((effect) => ({
type: effect.type,
params: effect.params,
nextAction: hasNextAction(effect) ? effect.next(proxy).type : undefined,
errorAction: hasErrorAction(effect) ? effect.error(proxy).type : undefined,
completeAction: hasCompleteAction(effect) ? effect.complete().type : undefined,
subscribeAction: hasSubscribeAction(effect)
? effect.subscribed(0).type
: undefined
}));
return Object.assign(state, { __effects: effectDescriptions });
}
function hasNextAction(effect) {
return effect.next !== undefined;
}
function hasErrorAction(effect) {
return effect.error !== undefined;
}
function hasCompleteAction(effect) {
return effect.complete !== undefined;
}
function hasSubscribeAction(effect) {
return effect.subscribed !== undefined;
}
function hasDependencies(effectLike) {
return effectLike.using != null;
}
function isObservableEffect(effect, operand) {
return isObservable(operand);
}
function isObservableOperand(operand) {
return isObservable(operand);
}
function isPromiseEffect(effect, operand) {
return operand instanceof Promise;
}
function isPromiseOperand(operand) {
return operand instanceof Promise;
}
function isUnsubscriptionEffect(effect, operand) {
var _a;
return ((_a = operand) === null || _a === void 0 ? void 0 : _a.__brand) === 'Unsubscribe';
}
function handleEffects(injector, runtime) {
return (reduced) => {
let newState = handleSliceEffects(reduced);
// tslint:disable-next-line:forin
for (const key in newState) {
newState = Object.assign(Object.assign({}, newState), { [key]: handleSliceEffects(newState[key]) });
}
return newState;
};
function handleSliceEffects(slicedState) {
if (isStateWithEffects(slicedState)) {
const [state, ...effects] = slicedState;
effects.map(effect => handleStateWithEffect(effect, runtime, injector.get(Store), injector));
return addEffectDescriptions(state, effects);
}
else {
return slicedState;
}
}
}
function isStateWithEffects(state) {
return state && state[1] && state[1].__isEffect === true;
}
function handleStateWithEffect(effect, runtime, store, injector) {
let operand;
const deps = hasDependencies(effect)
? effect.using.map((dep) => injector.get(dep))
: [];
try {
// @ts-ignore
operand = effect.call(...deps);
}
catch (err) {
if (effect.error) {
try {
store.dispatch(effect.error(err));
}
catch (innerError) {
console.error(`Unhandled error occurred when creating error action for effect "${effect.type}"`, innerError);
}
}
else {
console.error(`Unhandled error occurred when creating operation for effect "${effect.type}"`, err);
}
}
if (isObservableEffect(effect, operand) && isObservableOperand(operand)) {
const token = (Math.max(...runtime.keys()) + 1);
const subscription = operand.subscribe({
next: (value) => effect.next && store.dispatch(effect.next(value)),
error: (err) => effect.error && store.dispatch(effect.error(err)),
complete: () => effect.complete && store.dispatch(effect.complete())
});
runtime.set(token, subscription);
if (effect.subscribed) {
store.dispatch(effect.subscribed(token));
}
}
else if (isPromiseEffect(effect, operand) && isPromiseOperand(operand)) {
operand.then((value) => effect.complete && store.dispatch(effect.complete(value)), (err) => effect.error && store.dispatch(effect.error(err)));
}
else if (isUnsubscriptionEffect(effect, operand)) {
handleUnsubscribe(operand, effect, runtime, store);
}
else {
const complete = effect.complete;
if (complete) {
store.dispatch(complete());
}
}
}
function handleUnsubscribe(operation, effect, runtime, store) {
const cancellable = runtime.get(operation.subscriptionToken);
if (cancellable instanceof Subscription) {
runtime.delete(operation.subscriptionToken);
cancellable.unsubscribe();
}
else if (cancellable instanceof AbortController) {
runtime.delete(operation.subscriptionToken);
cancellable.abort();
}
else {
console.warn(`SubscriptionToken ${operation.subscriptionToken} not recognized. Did you cancel this already?`);
}
if (effect.complete) {
store.dispatch(effect.complete());
}
}
function createRuntimeReducerFactory(reducerFactory, injector, metaReducers) {
const runtime = new Map();
// TODO: Set default counter value
runtime.set(31415, new Subscription());
if (Array.isArray(metaReducers) && metaReducers.length > 0) {
reducerFactory = compose.apply(null, [...metaReducers, reducerFactory]);
}
return (reducers, initialState) => {
const reducer = reducerFactory(reducers);
return (state, action) => {
state = state === undefined ? initialState : state;
return handleEffects(injector, runtime)(reducer(state, action));
};
};
}
const _REDUCER_FACTORY = new InjectionToken('ngrx-run Internal Reducer Factory Provider');
const _RESOLVED_META_REDUCERS = new InjectionToken('ngrx-run Internal Resolved Meta Reducers');
class EffectStoreModule extends StoreModule {
static forRoot(reducers, config = {}) {
var _a;
const store = StoreModule.forRoot(reducers, Object.assign(Object.assign({}, config), { runtimeChecks: Object.assign(Object.assign({}, config.runtimeChecks), { strictStateImmutability: false }) }));
return {
ngModule: store.ngModule,
providers: [
...((_a = store.providers) !== null && _a !== void 0 ? _a : []),
{
provide: _RESOLVED_META_REDUCERS,
deps: [META_REDUCERS, USER_PROVIDED_META_REDUCERS],
useFactory: _concatMetaReducers
},
{
provide: _REDUCER_FACTORY,
useValue: config.reducerFactory ? config.reducerFactory : combineReducers
},
{
provide: REDUCER_FACTORY,
deps: [_REDUCER_FACTORY, Injector, _RESOLVED_META_REDUCERS],
useFactory: createRuntimeReducerFactory
}
]
};
}
}
EffectStoreModule.decorators = [
{ type: NgModule, args: [{},] }
];
function _concatMetaReducers(metaReducers, userProvidedMetaReducers) {
return metaReducers.concat(userProvidedMetaReducers);
}
function isActionOf(action, actions) {
return Object.values(actions)
.map((a) => a.type)
.includes(action.type);
}
function childReducer(state, key, action, reducer) {
return Object.assign(Object.assign({}, state), { [key]: reducer(state[key], action) });
}
function tokenized(actionCreator, token) {
return ((p) => Object.assign(actionCreator(p), { token }));
}
function hasToken(maybeTokenized, token) {
return isTokenized(maybeTokenized) && maybeTokenized.token === token;
}
function createToken(token) {
return token;
}
function isTokenized(maybeTokenized) {
return maybeTokenized.token != null;
}
/*
* Public API Surface of ngrx-run
*/
/**
* Generated bundle index. Do not edit.
*/
export { EffectStoreModule, childReducer, createEffect, createToken, hasToken, isActionOf, run, tokenized, unsubscribe };
//# sourceMappingURL=ngrx-run.js.map