@nimel/directorr
Version:
Like Redux but using decorators
1,116 lines (1,084 loc) • 46.1 kB
JavaScript
/* (c) Nikita Melnikov - MIT Licensed 2020 - now */
const errorWhenWrongEnv = () => "Directorr requires Symbol and Map objects. If your environment doesn't support, magic won`t work.";
const notFindStoreName = () => 'Store name not found';
const notObserver = () => 'Expected the observer to be an object';
const callWithArg = (moduleName, arg, errorMessage) => `${moduleName}: call decorator with arg=${arg} ${errorMessage}`;
const callDecoratorWithNotActionType = (moduleName, arg) => callWithArg(moduleName, arg, 'not equal to string/class/MSTModel or array of string/class/MSTModel');
const callDecoratorWithNotConsrtactorType = (moduleName, arg) => callWithArg(moduleName, arg, 'not constuctor');
const callDecoratorWithNotActionChecker = (moduleName, checker) => callWithArg(moduleName, checker, 'not like object or function');
const callDecoratorWithNotConvertPayload = (moduleName, converter) => callWithArg(moduleName, converter, 'not like function');
const useForNotPropDecorator = (moduleName, property) => `${moduleName}: use for property=${property} not like property decorator`;
const callWithPropNotEquallFunc = (moduleName, property) => `${moduleName}: use decorator for prop=${property} equal func`;
const useForPropNotEquallObject = (moduleName, property) => `${moduleName}: use decorator for prop=${property} equal object`;
const notFoundDirectorrStore = (moduleName, StoreConstructor) => `${moduleName}: store with constuctor=${StoreConstructor.name} not add to Dirrector`;
const notFoundStoreInDirectorrStore = (moduleName, StoreConstructor, currentStore) => `${moduleName}: for some reason, not found store or model with constuctor=${StoreConstructor.name}, may be worth adding storage=${StoreConstructor.name} earlier than the current=${currentStore.name}`;
const callWithNotAction = (moduleName, action) => `${moduleName}: call with action=${JSON.stringify(action)} not like action type`;
const dontUseWithAnotherDecorator = moduleName => `${moduleName}: dont use with another decorators`;
const haveCycleInjectedStore = moduleName => `${moduleName}: call stack out of range, this usually happens with cyclical dependency of injected stores`;
const callWithStoreNotConnectedToDirrectorr = (moduleName, store) => { var _a; return `${moduleName}: call with not connected to directorr store=${(_a = store === null || store === void 0 ? void 0 : store.construnctor) === null || _a === void 0 ? void 0 : _a.name}`; };
if (typeof Symbol === 'undefined') {
throw new TypeError(errorWhenWrongEnv());
}
const EFFECTS_FIELD_NAME = Symbol.for('dirrector: effects');
const DISPATCH_ACTION_FIELD_NAME = Symbol.for('dirrector: dispatchAction');
const DISPATCH_EFFECTS_FIELD_NAME = Symbol.for('dirrector: dispatchEffects');
const STORES_FIELD_NAME = Symbol.for('dirrector: stores');
const INJECTED_STORES_FIELD_NAME = Symbol.for('dirrector: injected stores');
const INJECTED_FROM_FIELD_NAME = Symbol.for('dirrector: injected from stores');
const DEPENDENCY_FIELD_NAME = Symbol.for('dirrector: external dependency');
const TIMERS_FIELD_NAME = Symbol.for('dirrector: timers');
const CLEAR_TIMERS_EFFECT_FIELD_NAME = Symbol.for('dirrector: clear timers');
const SUBSCRIBE_FIELD_NAME = Symbol.for('dirrector: subscribe');
const DISPATHERS_FIELD_NAME = Symbol.for('dirrector: dispatchers');
const DISPATHERS_EFFECT_FIELD_NAME = Symbol.for('dirrector: clear dispatchers');
const EMPTY_FUNC = () => { };
const RETURN_ARG_FUNC = (a) => a;
const TRUPHY_FUNC = () => true;
const EMPTY_STRING = '';
const EMPTY_OBJECT = Object.freeze({});
const DIRECTORR_INIT_STORE_ACTION = '@@DIRECTORR.INIT_STORE';
const DIRECTORR_DESTROY_STORE_ACTION = '@@DIRECTORR.DESTROY_STORE';
const DIRECTORR_RELOAD_STORE_ACTION = '@@DIRECTORR.RELOAD_STORE';
const DIRECTORR_ANY_ACTION_TYPE = '@@DIRECTORR.ANY_ACTION';
const ACTION_TYPE_DIVIDER = '.';
const isDev = process.env.NODE_ENV === 'development';
const TYPEOF = {
STRING: 'string',
};
const DESCRIPTOR = {
writable: false,
enumerable: false,
configurable: true,
value: null,
};
const PROPERTY_DESCRIPTOR = {
enumerable: false,
configurable: true,
get: EMPTY_FUNC,
set: EMPTY_FUNC,
};
function createValueDescriptor(value) {
DESCRIPTOR.value = value;
return DESCRIPTOR;
}
function createPropertyDescriptor(get = EMPTY_FUNC, set = EMPTY_FUNC) {
PROPERTY_DESCRIPTOR.get = get;
PROPERTY_DESCRIPTOR.set = set;
return PROPERTY_DESCRIPTOR;
}
const { isArray } = Array;
const { defineProperty, keys, prototype: { toString, hasOwnProperty: hasOwnPropertyFromPrototype }, } = Object;
function createPromiseCancelable(executor) {
let resolvecb = EMPTY_FUNC;
let rejectcb = EMPTY_FUNC;
let callback = EMPTY_FUNC;
let pending = true;
function whenCancel(cb) {
callback = cb;
}
function resolver(arg) {
pending = false;
return resolvecb(arg);
}
function rejector(arg) {
pending = false;
return rejectcb(arg);
}
function cancel() {
resolvecb(Promise.resolve(undefined));
if (pending)
callback();
}
const promise = new Promise((resolve, reject) => {
resolvecb = resolve;
rejectcb = reject;
executor(resolver, rejector, whenCancel);
});
promise.cancel = cancel;
return promise;
}
const STRING_OBJECT = '[object Object]';
function isObject(obj) {
return toString.call(obj) === STRING_OBJECT;
}
function isString(str) {
return typeof str === TYPEOF.STRING;
}
// eslint-disable-next-line @typescript-eslint/ban-types
function isFunction(func) {
return !!(func && func.constructor && func.call && func.apply);
}
function isPromise(promise) {
return !!(promise && promise.then && promise.catch);
}
function hasOwnProperty(target, prop) {
return hasOwnPropertyFromPrototype.call(target, prop);
}
function pickSameStore(payload, store) {
return store === payload.store;
}
function isLikeActionType(actionType) {
if (!actionType)
return false;
if (isArray(actionType)) {
if (!actionType.length)
return false;
for (let i = 0, l = actionType.length, at; i < l; ++i) {
at = actionType[i];
if (!isLikeActionType(at))
return false;
}
return true;
}
return isString(actionType) || isFunction(actionType);
}
function isLikeAction(action) {
if (!action)
return false;
return isObject(action) && action.type !== undefined;
}
function getStoreName(classOrModel) {
if (isFunction(classOrModel))
return classOrModel.storeName || classOrModel.name;
if (classOrModel.constructor)
return getStoreName(classOrModel.constructor);
throw new Error(notFindStoreName());
}
function isDecoratorWithCtx(decorator) {
return !!decorator.type;
}
function calcActionType(someActionType) {
if (isFunction(someActionType)) {
return isDecoratorWithCtx(someActionType) ? someActionType.type : getStoreName(someActionType);
}
return someActionType;
}
function createActionType(actionType, divider) {
if (isArray(actionType)) {
let result = EMPTY_STRING;
for (let i = 0, l = actionType.length, action, type; i < l; ++i) {
type = actionType[i];
action = isArray(type) ? createActionType(type, divider) : calcActionType(type);
result += i > 0 ? divider + action : action;
}
return result;
}
return calcActionType(actionType);
}
function batchFunction(f) {
return f;
}
function createAction(type, payload) {
if (payload)
return { type, payload };
return { type };
}
function isChecker(sample) {
if (!sample)
return false;
return isFunction(sample) || isObject(sample);
}
function isConverter(func) {
return isFunction(func);
}
function dispatchEffects(action) {
const effectsMap = this[EFFECTS_FIELD_NAME];
const effectsForActionType = effectsMap.get(action.type);
if (effectsForActionType) {
for (let i = 0, l = effectsForActionType.length; i < l; ++i) {
this[effectsForActionType[i]](action.payload);
}
}
const effectsForAnyActionType = effectsMap.get(DIRECTORR_ANY_ACTION_TYPE);
if (effectsForAnyActionType) {
for (let i = 0, l = effectsForAnyActionType.length; i < l; ++i) {
this[effectsForAnyActionType[i]](action);
}
}
}
function isLikePropertyDecorator(decorator) {
return !!(decorator === null || decorator === void 0 ? void 0 : decorator.initializer) || !(decorator === null || decorator === void 0 ? void 0 : decorator.value);
}
function createTypescriptDescriptor(descriptor, property, initializer, ctx) {
const key = Symbol.for(property);
const { set, get } = descriptor;
function newSet(newValue) {
let value;
if (set && get) {
set.call(this, newValue);
value = get.call(this);
}
else {
value = newValue;
}
defineProperty(this, key, createValueDescriptor(initializer(this, value, property, ctx)));
}
function newGet() {
return this[key];
}
return createPropertyDescriptor(newGet, newSet);
}
function isBabelDecorator(decorator) {
return !!(decorator && decorator.initializer !== undefined);
}
function isTypescriptDecorator(decorator) {
return !!(decorator && !isBabelDecorator(decorator));
}
function createBabelDescriptor(descriptor, property, initializer, ctx) {
descriptor.writable = false;
const oldInitializer = descriptor.initializer;
descriptor.initializer = function initializerDirectorr() {
return initializer(this, oldInitializer && oldInitializer.call(this), property, ctx);
};
return descriptor;
}
function createDescriptor(d, property, init, ctx) {
return d && isBabelDecorator(d)
? createBabelDescriptor(d, property, init, ctx)
: createTypescriptDescriptor(d || EMPTY_OBJECT, property, init, ctx);
}
function composePropertyDecorators(decorators) {
return (target, property, descriptor) => {
let resultDescriptor = descriptor;
for (let i = decorators.length - 1; i >= 0; --i) {
resultDescriptor = decorators[i](target, property, resultDescriptor);
}
return resultDescriptor;
};
}
function isStoreReady(store) {
return (store.isReady === undefined || (isFunction(store.isReady) ? store.isReady() : store.isReady));
}
function checkStoresState(directorrStores, isStoreState, storesNames) {
if (storesNames) {
for (let i = 0, l = storesNames.length, store; i < l; ++i) {
store = directorrStores.get(storesNames[i]);
if (!store)
continue;
if (!isStoreState(store))
return false;
}
}
else {
for (const store of directorrStores.values()) {
if (!isStoreState(store))
return false;
}
}
return true;
}
function isStoreError(store) {
return store.isError !== undefined && store.isError;
}
function findStoreStateInStores(directorrStores, isStoreState) {
for (const store of directorrStores.values()) {
if (isStoreState(store))
return store;
}
}
function mergeStateToStore(storeState, directorrStore) {
if (directorrStore.fromJSON) {
directorrStore.fromJSON(storeState);
}
else {
for (const prop in storeState) {
if (hasOwnProperty(directorrStore, prop)) {
const store = storeState[prop];
if (isObject(store) && directorrStore[prop]) {
mergeStateToStore(store, directorrStore[prop]);
}
else {
directorrStore[prop] = store;
}
}
}
}
}
function setStateToStore(storeState, directorrStore) {
if (directorrStore.fromJSON) {
directorrStore.fromJSON(storeState);
}
else {
for (const prop in storeState) {
const store = storeState[prop];
if (isObject(store) && directorrStore[prop]) {
setStateToStore(store, directorrStore[prop]);
}
else {
directorrStore[prop] = store;
}
}
}
}
function hydrateStoresToState(directorrStores) {
const obj = {};
for (const [storeName, store] of directorrStores.entries()) {
obj[storeName] = store;
}
return JSON.parse(JSON.stringify(obj));
}
function compareObjectWithPattern(objectPattern, obj) {
if (!obj)
return false;
for (const prop in objectPattern) {
const value = objectPattern[prop];
if (isFunction(value)) {
if (!value(obj[prop]))
return false;
}
else if (obj[prop] !== value) {
return false;
}
}
return true;
}
function isActionHave({ type, payload }, someType, checkPattern) {
if (type === someType) {
return compareObjectWithPattern(checkPattern, payload);
}
return false;
}
function clearTimersEffect(payload) {
if (this === payload.store) {
const timers = this[TIMERS_FIELD_NAME];
for (const timer of timers) {
isFunction(timer) ? timer() : clearTimeout(timer);
}
}
}
function isPayloadChecked(payload, checker = TRUPHY_FUNC) {
if (isFunction(checker))
return checker(payload);
for (const prop in checker) {
const value = checker[prop];
if (!hasOwnProperty(payload, prop))
return false;
if (isFunction(value)) {
if (!value(payload, prop))
return false;
continue;
}
if (payload[prop] !== value)
return false;
}
return true;
}
function createActionTypes(type) {
return {
type,
typeLoading: `${type}_LOADING`,
typeSuccess: `${type}_SUCCESS`,
typeError: `${type}_ERROR`,
};
}
class Config {
constructor() {
this.batchFunction = batchFunction;
this.createAction = createAction;
this.createActionType = actionType => createActionType(actionType, this.actionTypeDivider);
this.actionTypeDivider = ACTION_TYPE_DIVIDER;
this.dispatchEffectsOrig = dispatchEffects;
this.dispatchEffects = dispatchEffects;
this.hydrateStoresToState = hydrateStoresToState;
this.mergeStateToStore = mergeStateToStore;
this.setStateToStore = setStateToStore;
this.configure = ({ batchFunction, createAction, actionTypeDivider, createActionType, dispatchEffects, hydrateStoresToState, mergeStateToStore, setStateToStore, }) => {
if (batchFunction) {
this.batchFunction = batchFunction;
this.dispatchEffects = batchFunction(this.dispatchEffectsOrig);
}
if (createAction)
this.createAction = createAction;
if (createActionType)
this.createActionType = createActionType;
if (actionTypeDivider)
this.actionTypeDivider = actionTypeDivider;
if (dispatchEffects) {
this.dispatchEffectsOrig = dispatchEffects;
this.dispatchEffects = this.batchFunction(dispatchEffects);
}
if (hydrateStoresToState)
this.hydrateStoresToState = hydrateStoresToState;
if (mergeStateToStore)
this.mergeStateToStore = mergeStateToStore;
if (setStateToStore)
this.setStateToStore = setStateToStore;
};
}
}
const config = new Config();
class MiddlewareAdapter {
constructor(middleware, runNextMiddleware, index, directorr) {
this.middleware = middleware;
this.directorr = directorr;
this.next = action => runNextMiddleware(index, action);
}
run(action) {
this.middleware(action, this.next, this.directorr);
}
}
class ReduxMiddlewareAdapter {
constructor(middleware, runNextMiddleware, index, store, reduxStore) {
// Keep for test
this.next = action => runNextMiddleware(index, action);
this.middleware = middleware(reduxStore)(this.next);
}
run(action) {
this.middleware(action);
}
}
const MODULE_NAME = 'Directorr';
const GLOBAL_DEP = { global: true };
const OMIT_ACTIONS = ['@APPLY_SNAPSHOT'];
class Directorr {
constructor({ initState = EMPTY_OBJECT } = EMPTY_OBJECT) {
this.stores = new Map();
this.setStateToStore = config.batchFunction((storeState) => {
for (const [storeName, store] of this.stores.entries()) {
if (hasOwnProperty(storeState, storeName)) {
config.setStateToStore(storeState[storeName], store);
}
}
});
this.mergeStateToStore = config.batchFunction((storeState) => {
for (const [storeName, store] of this.stores.entries()) {
if (hasOwnProperty(storeState, storeName)) {
config.mergeStateToStore(storeState[storeName], store);
}
}
});
this.subscribeHandlers = [];
this.subscribe = (handler) => {
this.subscribeHandlers = [...this.subscribeHandlers, handler];
return () => this.unsubscribe(handler);
};
this.unsubscribe = (handler) => {
this.subscribeHandlers = this.subscribeHandlers.filter(h => h !== handler);
};
this.middlewares = [];
this.findNextMiddleware = (nextIndex, action) => {
const nextMiddleware = this.middlewares[nextIndex + 1];
if (nextMiddleware) {
nextMiddleware.run(action);
}
else {
this.runEffects(action);
}
};
this.dispatch = config.batchFunction(action => {
if (!isLikeAction(action))
throw new Error(callWithNotAction(MODULE_NAME, action));
this.findNextMiddleware(-1, action);
return action;
});
this.dispatchType = (type, payload) => this.dispatch(config.createAction(type, payload));
this.reduxStore = {
getState: () => this.getHydrateStoresState(),
dispatch: this.dispatch,
subscribe: this.subscribe,
replaceReducer: EMPTY_FUNC,
[Symbol.observable]: () => {
const { subscribe } = this;
return {
subscribe(observer) {
if (observer === null || !isObject(observer)) {
throw new TypeError(notObserver());
}
return {
unsubscribe: observer.next ? subscribe(stores => observer.next(stores)) : EMPTY_FUNC,
};
},
[Symbol.observable]() {
return this;
},
};
},
};
this.afterwares = [];
this.initState = initState;
}
getStore(StoreConstructor) {
return this.stores.get(getStoreName(StoreConstructor));
}
getHydrateStoresState() {
return config.hydrateStoresToState(this.stores);
}
addStores(modelOrConstructor) {
modelOrConstructor.forEach(sm => this.addStore(sm));
}
addStore(storeConstructor) {
return this.addStoreDependency(storeConstructor, GLOBAL_DEP);
}
removeStore(storeConstructor) {
return this.removeStoreDependency(storeConstructor, GLOBAL_DEP);
}
addStoreDependency(StoreConstructor, depName) {
const store = this.initStore(StoreConstructor);
store[DEPENDENCY_FIELD_NAME].push(depName);
return store;
}
removeStoreDependency(StoreConstructor, depName) {
const store = this.getStore(StoreConstructor);
if (store) {
const dependency = store[DEPENDENCY_FIELD_NAME];
const index = dependency.indexOf(depName);
if (index !== -1)
dependency.splice(index, 1);
if (!dependency.length)
this.destroyStore(StoreConstructor);
}
}
initStore(StoreConstructor) {
const storeName = getStoreName(StoreConstructor);
if (this.stores.has(storeName))
return this.stores.get(storeName);
// add injected stores
if (hasOwnProperty(StoreConstructor, INJECTED_STORES_FIELD_NAME)) {
const injectedModelsOrConstructors = StoreConstructor[INJECTED_STORES_FIELD_NAME];
try {
for (const modelOrConstructor of injectedModelsOrConstructors) {
const store = this.initStore(modelOrConstructor);
store[INJECTED_FROM_FIELD_NAME].push(StoreConstructor);
}
}
catch (error) {
if (error instanceof RangeError) {
throw new TypeError(haveCycleInjectedStore(MODULE_NAME));
}
throw error;
}
}
// add afterware
if (StoreConstructor.afterware)
this.addAfterware(StoreConstructor.afterware);
// create store
const store = new StoreConstructor(StoreConstructor.storeInitOptions);
if (!hasOwnProperty(store, INJECTED_FROM_FIELD_NAME)) {
defineProperty(store, INJECTED_FROM_FIELD_NAME, createValueDescriptor([]));
defineProperty(store, DEPENDENCY_FIELD_NAME, createValueDescriptor([]));
}
// attach to directorr
defineProperty(store, STORES_FIELD_NAME, createValueDescriptor(this.stores));
defineProperty(store, DISPATCH_ACTION_FIELD_NAME, createValueDescriptor(this.dispatch));
defineProperty(store, SUBSCRIBE_FIELD_NAME, createValueDescriptor(this.subscribe));
if (!hasOwnProperty(store, DISPATCH_EFFECTS_FIELD_NAME))
defineProperty(store, DISPATCH_EFFECTS_FIELD_NAME, createValueDescriptor(EMPTY_FUNC));
// add store
this.stores.set(storeName, store);
// merge init state
if (hasOwnProperty(this.initState, storeName)) {
config.mergeStateToStore(this.initState[storeName], store);
// remove outdated data
delete this.initState[storeName];
}
// call init action
if (hasOwnProperty(StoreConstructor, INJECTED_STORES_FIELD_NAME)) {
const injectedModelsOrConstructors = StoreConstructor[INJECTED_STORES_FIELD_NAME];
const storeNames = injectedModelsOrConstructors.map(s => getStoreName(s));
if (checkStoresState(this.stores, isStoreReady, storeNames)) {
this.dispatchType(DIRECTORR_INIT_STORE_ACTION, { store });
}
else {
void this.waitStoresState(injectedModelsOrConstructors).then(() => {
this.dispatchType(DIRECTORR_INIT_STORE_ACTION, { store });
});
}
}
else {
this.dispatchType(DIRECTORR_INIT_STORE_ACTION, { store });
}
return store;
}
destroyStore(StoreConstructor, FromStoreConstructor) {
const storeName = getStoreName(StoreConstructor);
const store = this.stores.get(storeName);
if (store) {
// remove injected stores
if (hasOwnProperty(StoreConstructor, INJECTED_STORES_FIELD_NAME)) {
const injectedModelsOrConstructors = StoreConstructor[INJECTED_STORES_FIELD_NAME];
try {
for (const modelOrConstructor of injectedModelsOrConstructors) {
this.destroyStore(modelOrConstructor, StoreConstructor);
}
}
catch (error) {
if (error instanceof RangeError) {
throw new TypeError(haveCycleInjectedStore(MODULE_NAME));
}
throw error;
}
}
// remove injected from stores
if (FromStoreConstructor) {
const injectedFrom = store[INJECTED_FROM_FIELD_NAME];
const index = injectedFrom.indexOf(FromStoreConstructor);
injectedFrom.splice(index, 1);
}
// when dont have dependencies
if (!store[INJECTED_FROM_FIELD_NAME].length && !store[DEPENDENCY_FIELD_NAME].length) {
// remove afterware
if (StoreConstructor.afterware)
this.removeAfterware(StoreConstructor.afterware);
this.dispatchType(DIRECTORR_DESTROY_STORE_ACTION, { store });
defineProperty(store, DISPATCH_ACTION_FIELD_NAME, createValueDescriptor(EMPTY_FUNC));
// remove store
this.stores.delete(storeName);
}
}
}
waitAllStoresState(isStoreState = isStoreReady) {
return createPromiseCancelable((res, rej, whenCancel) => {
if (checkStoresState(this.stores, isStoreState))
return res(undefined);
const unsub = this.subscribe(stores => {
if (checkStoresState(stores, isStoreState)) {
unsub();
return res(undefined);
}
});
whenCancel(unsub);
});
}
waitStoresState(stores, isStoreState = isStoreReady) {
const storeNames = stores.map(s => getStoreName(s));
return createPromiseCancelable((res, rej, whenCancel) => {
if (checkStoresState(this.stores, isStoreState, storeNames))
return res(undefined);
const unsub = this.subscribe(stores => {
if (checkStoresState(stores, isStoreState, storeNames)) {
unsub();
return res(undefined);
}
});
whenCancel(unsub);
});
}
findStoreState(isStoreState = isStoreError) {
return createPromiseCancelable((res, rej, whenCancel) => {
const store = findStoreStateInStores(this.stores, isStoreState);
if (store)
return res(store);
const unsub = this.subscribe(stores => {
const store = findStoreStateInStores(stores, isStoreState);
if (store) {
unsub();
return res(store);
}
});
whenCancel(unsub);
});
}
addSomeMiddlewares(middlewares, someMiddlewareAdapter) {
const { length: totalMiddlewares } = this.middlewares;
for (let i = 0, l = middlewares.length, m; i < l; ++i) {
m = middlewares[i];
if (this.middlewares.some(mid => mid.middleware === m))
continue;
this.middlewares.push(new someMiddlewareAdapter(m, this.findNextMiddleware, totalMiddlewares + i, this, this.reduxStore));
}
}
addReduxMiddlewares(middlewares) {
this.addSomeMiddlewares(middlewares, ReduxMiddlewareAdapter);
}
addMiddlewares(middlewares) {
this.addSomeMiddlewares(middlewares, MiddlewareAdapter);
}
removeMiddleware(middleware) {
const idx = this.middlewares.findIndex(m => m.middleware === middleware);
this.middlewares.splice(idx, 1);
}
addAfterware(afterware) {
this.afterwares = [...this.afterwares, afterware];
}
removeAfterware(afterware) {
this.afterwares = this.afterwares.filter(a => a !== afterware);
}
runEffects(action) {
for (const store of this.stores.values()) {
store[DISPATCH_EFFECTS_FIELD_NAME](action);
}
for (const afterware of this.afterwares) {
afterware(action, this.dispatchType, this);
}
for (const handler of this.subscribeHandlers) {
handler(this.stores, action);
}
}
}
function propOneOf(args) {
return (payload, prop) => args.includes(payload[prop]);
}
function propIsNotEqual(arg) {
return (payload, prop) => payload[prop] !== arg;
}
function propIsNotEqualOneOf(arg) {
return (payload, prop) => !arg.includes(payload[prop]);
}
function dispatchEffectInStore(store, actionType, payload) {
if (DISPATCH_EFFECTS_FIELD_NAME in store)
store[DISPATCH_EFFECTS_FIELD_NAME](config.createAction(actionType, payload));
}
function dispatchInitEffectInStore(store) {
dispatchEffectInStore(store, DIRECTORR_INIT_STORE_ACTION, {
store,
});
}
function dispatchDestroyEffectInStore(store) {
dispatchEffectInStore(store, DIRECTORR_DESTROY_STORE_ACTION, {
store,
});
}
function dispatchReloadEffectInStore(store, payload) {
dispatchEffectInStore(store, DIRECTORR_RELOAD_STORE_ACTION, payload);
}
function dispatchActionInStore(store, actionType, payload) {
if (DISPATCH_ACTION_FIELD_NAME in store)
store[DISPATCH_ACTION_FIELD_NAME](config.createAction(actionType, payload));
}
function returnArgs(moduleName, arg1, arg2) {
return [arg1, arg2];
}
function createDecoratorFactory(moduleName, decorator, initializer, createContext = returnArgs, convertDecorator = RETURN_ARG_FUNC) {
return function decoratorFactory(arg1, arg2) {
const context = createContext(moduleName, arg1, arg2);
return convertDecorator((target, property, descriptor) => decorator(target, property, descriptor, moduleName, initializer, context), context);
};
}
function decorator(target, property, descriptor, moduleName, initializerForInitObject, ctx, buildDescriptor = createDescriptor) {
if (!isLikePropertyDecorator(descriptor))
throw new Error(useForNotPropDecorator(moduleName, property));
return buildDescriptor(descriptor, property, initializerForInitObject, ctx);
}
function createPropertyDecoratorFactory(moduleName, initializer, createContext) {
return createDecoratorFactory(moduleName, decorator, initializer, createContext);
}
function createActionTypeContext(moduleName, actionType, options) {
if (!isLikeActionType(actionType)) {
throw new Error(callDecoratorWithNotActionType(moduleName, actionType));
}
return [config.createActionType(actionType), options];
}
function addInitFields(initObject) {
if (!hasOwnProperty(initObject, DISPATCH_EFFECTS_FIELD_NAME)) {
defineProperty(initObject, DISPATCH_EFFECTS_FIELD_NAME, createValueDescriptor(config.dispatchEffects));
defineProperty(initObject, DISPATCH_ACTION_FIELD_NAME, createValueDescriptor(config.dispatchEffects));
defineProperty(initObject, EFFECTS_FIELD_NAME, createValueDescriptor(new Map()));
defineProperty(initObject, DEPENDENCY_FIELD_NAME, createValueDescriptor([]));
defineProperty(initObject, INJECTED_FROM_FIELD_NAME, createValueDescriptor([]));
defineProperty(initObject, STORES_FIELD_NAME, createValueDescriptor(null));
}
}
const MODULE_NAME$1 = 'action';
function runDispatcher(args, actionType, valueFunc, store, addToPayload) {
const result = valueFunc(...args);
if (result !== null) {
if (isPromise(result)) {
void result.then(data => {
if (data !== null) {
store[DISPATCH_ACTION_FIELD_NAME](config.createAction(actionType, addToPayload(data, store)));
}
});
}
else {
store[DISPATCH_ACTION_FIELD_NAME](config.createAction(actionType, addToPayload(result, store)));
}
}
return result;
}
function initializer(initObject, value, property, [actionType, addToPayload = RETURN_ARG_FUNC], dispatcher = runDispatcher, addFields = addInitFields) {
if (!isFunction(value))
throw new Error(callWithPropNotEquallFunc(MODULE_NAME$1, property));
addFields(initObject);
return (...args) => dispatcher(args, actionType, value, initObject, addToPayload);
}
function addTypeToDecorator(decorator, context) {
decorator.type = context[0];
decorator.createAction = payload => config.createAction(decorator.type, payload);
decorator.isAction = (action) => decorator.type === action.type;
return decorator;
}
const action = createDecoratorFactory(MODULE_NAME$1, decorator, initializer, createActionTypeContext, addTypeToDecorator);
const MODULE_NAME$2 = 'effect';
function initializer$1(initObject, value, property, [actionType], addFields = addInitFields) {
if (!isFunction(value))
throw new Error(callWithPropNotEquallFunc(MODULE_NAME$2, property));
addFields(initObject);
const effectsMap = initObject[EFFECTS_FIELD_NAME];
const effects = effectsMap.get(actionType);
if (effects) {
effects.push(property);
}
else {
effectsMap.set(actionType, [property]);
}
return value;
}
function addTypeToDecorator$1(decorator, context) {
decorator.type = context[0];
decorator.createAction = payload => config.createAction(decorator.type, payload);
decorator.isAction = (action) => decorator.type === action.type;
return decorator;
}
const effect = createDecoratorFactory(MODULE_NAME$2, decorator, initializer$1, createActionTypeContext, addTypeToDecorator$1);
function createActionAndEffect(actionType) {
const { type, typeSuccess, typeError, typeLoading } = createActionTypes(config.createActionType(actionType));
return [
action(type),
effect(type),
action(typeSuccess),
effect(typeSuccess),
action(typeError),
effect(typeError),
action(typeLoading),
effect(typeLoading),
];
}
function createActionFactory(type) {
return (payload) => config.createAction(type, payload);
}
const allEffect = effect(DIRECTORR_ANY_ACTION_TYPE);
const MODULE_NAME$3 = 'createDispatcher';
function clearDispatchers(payload) {
if (this === payload.store) {
const dispatchers = this[DISPATHERS_FIELD_NAME];
dispatchers.forEach(d => d());
}
}
function createDispatcher(store) {
if (!hasOwnProperty(store, DISPATHERS_FIELD_NAME)) {
const effectsMap = store[EFFECTS_FIELD_NAME];
const effects = effectsMap.get(DIRECTORR_DESTROY_STORE_ACTION);
if (effects) {
effects.push(DISPATHERS_EFFECT_FIELD_NAME);
}
else {
effectsMap.set(DIRECTORR_DESTROY_STORE_ACTION, [DISPATHERS_EFFECT_FIELD_NAME]);
}
defineProperty(store, DISPATHERS_FIELD_NAME, createValueDescriptor([]));
defineProperty(store, DISPATHERS_EFFECT_FIELD_NAME, createValueDescriptor(clearDispatchers));
}
function dispatcher(actionTypes, payload, checker) {
if (!store[SUBSCRIBE_FIELD_NAME])
throw new Error(callWithStoreNotConnectedToDirrectorr(MODULE_NAME$3, store));
if (!Array.isArray(actionTypes))
return store[DISPATCH_ACTION_FIELD_NAME](config.createAction(config.createActionType(actionTypes), payload));
const [firstActionType, secondActionType, thirdActionType] = actionTypes;
const types = createActionTypes(config.createActionType(firstActionType));
const { type } = types;
const typeSuccess = secondActionType
? config.createActionType(secondActionType)
: types.typeSuccess;
const typeError = thirdActionType ? config.createActionType(thirdActionType) : types.typeError;
const dispatchers = store[DISPATHERS_FIELD_NAME];
const allTypes = [typeSuccess, typeError];
const promise = createPromiseCancelable((res, rej, whenCancel) => {
const unsub = store[SUBSCRIBE_FIELD_NAME]((_, action) => {
if (allTypes.includes(action.type) && isPayloadChecked(action.payload, checker)) {
unsub();
dispatchers.splice(dispatchers.indexOf(promise.cancel), 1);
if (action.type === typeSuccess)
return res(action.payload);
return rej(action.payload);
}
});
whenCancel(unsub);
});
dispatchers.push(promise.cancel);
store[DISPATCH_ACTION_FIELD_NAME](config.createAction(type, payload));
return promise;
}
return dispatcher;
}
class DirectorrMock {
constructor() {
this.stores = new Map();
this.actions = [];
this.addStores = jest.fn().mockImplementation((...Stores) => {
Stores.forEach(Store => {
const store = new Store();
this.stores.set(getStoreName(Store), store);
return store;
});
});
this.addStore = jest.fn().mockImplementation(Store => {
const store = new Store();
this.stores.set(getStoreName(Store), store);
return store;
});
this.addStoreDependency = jest.fn().mockImplementation(Store => {
const store = new Store();
this.stores.set(getStoreName(Store), store);
return store;
});
this.removeStoreDependency = jest.fn();
this.getHydrateStoresState = jest.fn();
this.getStore = jest.fn().mockImplementation(Store => this.stores.get(getStoreName(Store)));
this.waitAllStoresState = jest.fn().mockImplementationOnce(() => {
const promise = Promise.resolve();
promise.cancel = jest.fn();
return promise;
});
this.waitStoresState = jest.fn().mockImplementationOnce(s => {
const promise = Promise.resolve(s);
promise.cancel = jest.fn();
return promise;
});
this.findStoreState = jest.fn().mockImplementationOnce(() => {
const promise = Promise.resolve();
promise.cancel = jest.fn();
return promise;
});
this.dispatch = jest.fn().mockImplementationOnce(action => this.actions.push(action));
this.dispatchType = jest
.fn()
.mockImplementationOnce((type, payload) => this.dispatch({ type, payload }));
this.addInitState = jest.fn();
this.removeStore = jest.fn();
this.addReduxMiddlewares = jest.fn();
this.addMiddlewares = jest.fn();
this.mergeStateToStore = jest.fn();
this.setStateToStore = jest.fn();
this.removeMiddleware = jest.fn();
}
}
const createCheckerContext = (moduleName, checker, converter) => {
if (!isChecker(checker))
throw new Error(callDecoratorWithNotActionChecker(moduleName, checker));
if (converter && !isConverter(converter))
throw new Error(callDecoratorWithNotConvertPayload(moduleName, converter));
return [checker, converter];
};
const MODULE_NAME$4 = 'whenState';
function stateChecker(payload, valueFunc, store, [checker]) {
if (!payload)
return valueFunc(payload);
if (isFunction(checker))
return checker(payload, store) ? valueFunc(payload) : undefined;
for (const prop in checker) {
const value = checker[prop];
if (isFunction(value)) {
if (!hasOwnProperty(store, prop) || !value(store, payload, prop))
return;
}
else if (store[prop] !== value) {
return;
}
}
return valueFunc(payload);
}
function initializer$2(initObject, value, property, checker, stateCheckFunc = stateChecker) {
if (!isFunction(value))
throw new Error(callWithPropNotEquallFunc(MODULE_NAME$4, property));
return (payload) => stateCheckFunc(payload, value, initObject, checker);
}
const whenState = createDecoratorFactory(MODULE_NAME$4, decorator, initializer$2, createCheckerContext);
const MODULE_NAME$5 = 'whenPayload';
function payloadChecker(payload, valueFunc, [checker, converter = RETURN_ARG_FUNC]) {
if (!payload)
return valueFunc(converter(payload));
if (isPayloadChecked(payload, checker))
return valueFunc(converter(payload));
}
function initializer$3(initObject, value, property, args, payloadCheckerFunc = payloadChecker) {
if (!isFunction(value))
throw new Error(callWithPropNotEquallFunc(MODULE_NAME$5, property));
return (payload) => payloadCheckerFunc(payload, value, args);
}
const whenPayload = createDecoratorFactory(MODULE_NAME$5, decorator, initializer$3, createCheckerContext);
const whenDestroy = composePropertyDecorators([
effect(DIRECTORR_DESTROY_STORE_ACTION),
whenState(pickSameStore),
]);
const whenInit = composePropertyDecorators([
effect(DIRECTORR_INIT_STORE_ACTION),
whenState(pickSameStore),
]);
const reloadAction = action(DIRECTORR_RELOAD_STORE_ACTION);
const MODULE_NAME$6 = 'injectStore';
function injectStoreDecorator(StoreConstructor, moduleName) {
function get() {
if (!this[STORES_FIELD_NAME])
throw new Error(notFoundDirectorrStore(moduleName, StoreConstructor));
const store = this[STORES_FIELD_NAME].get(getStoreName(StoreConstructor));
if (!store)
throw new Error(notFoundStoreInDirectorrStore(moduleName, StoreConstructor, this.constructor));
return store;
}
return createPropertyDescriptor(get);
}
function createInjectStore(moduleName, decorator) {
return function injector(StoreConstructor) {
if (!isFunction(StoreConstructor))
throw new Error(callDecoratorWithNotConsrtactorType(moduleName, StoreConstructor));
return (target, property, descriptor) => {
if (isTypescriptDecorator(descriptor))
throw new Error(dontUseWithAnotherDecorator(moduleName));
const { constructor: targetClass } = target;
if (hasOwnProperty(targetClass, INJECTED_STORES_FIELD_NAME)) {
const injectedStores = targetClass[INJECTED_STORES_FIELD_NAME];
if (!injectedStores.includes(StoreConstructor))
injectedStores.push(StoreConstructor);
}
else {
defineProperty(targetClass, INJECTED_STORES_FIELD_NAME, createValueDescriptor([StoreConstructor]));
}
return decorator(StoreConstructor, moduleName);
};
};
}
const injectStore = createInjectStore(MODULE_NAME$6, injectStoreDecorator);
const MODULE_NAME$7 = 'delay';
function initializer$4(initObject, value, property, [delay = 0], addFields = addInitFields) {
if (!isFunction(value))
throw new Error(callWithPropNotEquallFunc(MODULE_NAME$7, property));
addFields(initObject);
if (!hasOwnProperty(initObject, TIMERS_FIELD_NAME)) {
const effectsMap = initObject[EFFECTS_FIELD_NAME];
const effects = effectsMap.get(DIRECTORR_DESTROY_STORE_ACTION);
if (effects) {
effects.push(CLEAR_TIMERS_EFFECT_FIELD_NAME);
}
else {
effectsMap.set(DIRECTORR_DESTROY_STORE_ACTION, [CLEAR_TIMERS_EFFECT_FIELD_NAME]);
}
defineProperty(initObject, TIMERS_FIELD_NAME, createValueDescriptor([]));
defineProperty(initObject, CLEAR_TIMERS_EFFECT_FIELD_NAME, createValueDescriptor(clearTimersEffect));
}
return (...args) => {
const timers = initObject[TIMERS_FIELD_NAME];
const timerID = setTimeout(() => {
timers.splice(timers.indexOf(timerID), 1);
value(...args);
}, delay);
timers.push(timerID);
};
}
const delay = createDecoratorFactory(MODULE_NAME$7, decorator, initializer$4);
const whenReload = effect(DIRECTORR_RELOAD_STORE_ACTION);
function createActionTypeOptionContext(moduleName, actionType) {
if (actionType) {
if (!isLikeActionType(actionType)) {
throw new Error(callDecoratorWithNotActionType(moduleName, actionType));
}
return config.createActionType(actionType);
}
return actionType;
}
const MODULE_NAME$8 = 'connectStore';
function dispatchProxyAction(action, fromStore, toStore, connectStoreProperty, prefixActionType) {
fromStore[DISPATCH_EFFECTS_FIELD_NAME](action);
toStore[DISPATCH_EFFECTS_FIELD_NAME](config.createAction(config.createActionType(prefixActionType ? [prefixActionType, action.type] : action.type), Object.assign(Object.assign({}, action.payload), { connectStoreProperty })));
}
function addDispatchAction(fromStore, toStore, property, prefixActionType, dispatchAction = dispatchProxyAction) {
return defineProperty(fromStore, DISPATCH_ACTION_FIELD_NAME, createValueDescriptor((action) => dispatchAction(action, fromStore, toStore, property, prefixActionType)));
}
function initializer$5(initObject, store, property, prefixActionType, addDispatchActionInStore = addDispatchAction, addFields = addInitFields) {
if (isFunction(store))
throw new Error(useForPropNotEquallObject(MODULE_NAME$8, property));
addFields(initObject);
if (!store)
return store;
return addDispatchActionInStore(store, initObject, property, prefixActionType);
}
const connectStore = createDecoratorFactory(MODULE_NAME$8, decorator, initializer$5, createActionTypeOptionContext);
export { DEPENDENCY_FIELD_NAME, DIRECTORR_DESTROY_STORE_ACTION, DIRECTORR_INIT_STORE_ACTION, DIRECTORR_RELOAD_STORE_ACTION, DISPATCH_ACTION_FIELD_NAME, DISPATCH_EFFECTS_FIELD_NAME, Directorr, DirectorrMock, EMPTY_FUNC, EMPTY_OBJECT, EMPTY_STRING, GLOBAL_DEP, INJECTED_FROM_FIELD_NAME, INJECTED_STORES_FIELD_NAME, MODULE_NAME, OMIT_ACTIONS, STORES_FIELD_NAME, action, allEffect, callWithPropNotEquallFunc, composePropertyDecorators, config, connectStore, createAction, createActionAndEffect, createActionFactory, createDecoratorFactory, createDispatcher, createPropertyDecoratorFactory, delay, dispatchActionInStore, dispatchDestroyEffectInStore, dispatchEffectInStore, dispatchInitEffectInStore, dispatchReloadEffectInStore, effect, getStoreName, injectStore, isActionHave, isFunction, isLikeAction, isStoreError, isStoreReady, isString, propIsNotEqual, propIsNotEqualOneOf, propOneOf, reloadAction, whenDestroy, whenInit, whenPayload, whenReload, whenState };