UNPKG

@nimel/directorr

Version:
1,116 lines (1,084 loc) 46.1 kB
/* (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 };