import invariant from 'invariant' import { mapValues, isArray } from 'lodash' import { Store } from 'redux' import { KOP_GLOBAL_SELECTOR_LOOP_CHECK } from '../../const' import { getStateByNamespace } from '../../utils' declare global { interface Window { KOP_GLOBAL_SELECTOR_LOOP_CHECK: any } } window[KOP_GLOBAL_SELECTOR_LOOP_CHECK] = window[KOP_GLOBAL_SELECTOR_LOOP_CHECK] || {} let currentState = null // check if selector has circular dependency const selectorLoopChecker = { start: (namespace: string, key: string) => { if (!window[KOP_GLOBAL_SELECTOR_LOOP_CHECK]) { // eslint-disable-next-line @typescript-eslint/camelcase window[KOP_GLOBAL_SELECTOR_LOOP_CHECK] = {} } if (window[KOP_GLOBAL_SELECTOR_LOOP_CHECK][namespace + key] === 1) { throw Error( `module ${namespace}'s selector ${key} has circular dependency` ) } else { window[KOP_GLOBAL_SELECTOR_LOOP_CHECK][namespace + key] = 1 } }, end: (namespace: string, key: string) => { delete window[KOP_GLOBAL_SELECTOR_LOOP_CHECK][namespace + key] } } function _getStateValue( key: string, currState: object | null, namespace: string, initialState: object ) { let stateValue if (key === 'getGlobalState') { stateValue = currState invariant(stateValue, 'Please check the kop store') } else { stateValue = getStateByNamespace(currState, namespace, initialState) invariant( stateValue, `Please check the kop-module ${namespace} is added, or you have return wrong state in last reducer` ) } return stateValue } export default function initSelectorHelper(store: Store) { currentState = store.getState() store.subscribe(() => { currentState = store.getState() }) } export function createSelectors(namespace, selectors, presenter, initialState) { const globalizeSelector = (selector, key) => (...params) => { const stateValue = _getStateValue( key, currentState, namespace, initialState ) selectorLoopChecker.start(namespace, key) if (params[0] === currentState) { params.shift() } const res = presenter.loaded ? selector(stateValue, presenter.selectors, ...params) : selector(stateValue, null, ...params) selectorLoopChecker.end(namespace, key) return res } return mapValues(selectors, globalizeSelector) } function _transformSelectors(namespace, selectors, initialState) { const globalizeSelector = (selector, key) => (...params) => { if (isArray(params) && params.length > 1 && params[0] === currentState) { params.shift() } const stateValue = _getStateValue( key, currentState, namespace, initialState ) const res = selector(stateValue, { payload: params.length === 1 ? params[0] : params }) return res } return mapValues(selectors, globalizeSelector) } export const createPageSelectors = _transformSelectors export const createGlobalSelectors = _transformSelectors export function createDomainSelectors(selectors, entities) { const globalizeSelector = selector => (...params) => { if (isArray(params) && params.length > 1 && params[0] === currentState) { params.shift() } const res = selector({ entities, payload: params.length === 1 ? params[0] : params }) return res } return mapValues(selectors, globalizeSelector) } export function createContainerSelectors( namespace: string, selectors: object, presenter: { loaded: boolean; selectors: object }, initialState: object ) { const globalizeSelector = (selector: Function, key: string) => ( ...params ) => { if (isArray(params) && params.length > 1 && params[0] === currentState) { params.shift() } const stateValue = _getStateValue( key, currentState, namespace, initialState ) let res if (presenter.loaded) { res = selector(stateValue, { pageSelectors: presenter.selectors, payload: params.length === 1 ? params[0] : params }) } else { res = selector(stateValue, { payload: params.length === 1 ? params[0] : params }) } return res } return mapValues(selectors, globalizeSelector) }