UNPKG

@zedux/core

Version:

A high-level, declarative, composable form of Redux

174 lines (154 loc) 4.62 kB
import { Store } from '../api/createStore' import { detailedTypeof } from '../api/detailedTypeof' import { is } from '../api/is' import { isPlainObject } from '../api/isPlainObject' import { zeduxTypes } from '../api/zeduxTypes' import { Action, ActionMeta, Branch, HierarchyDescriptor, Reducer, } from '../types' import { BranchNodeType, NullNodeType, ReducerNodeType, StoreNodeType, } from '../utils/general' import { HierarchyNode, Hierarchy, HierarchyNodeType, RegisterSubStore, } from '../utils/types' /** * Converts a Branch hierarchy descriptor to a HierarchyNode's children * * Really should only be used from `hierarchyDescriptorToHierarchy()` */ const branchToHierarchyChildren = ( branch: Branch, registerSubStore: RegisterSubStore, currentPath: string[] ) => { const children: Hierarchy = {} Object.entries(branch).forEach(([key, val]) => { const newPath = [...currentPath, key] children[key] = hierarchyDescriptorToHierarchy( val, registerSubStore, newPath ) }) return children } /** * Turns a non-branch node from a user-supplied hierarchy descriptor into a * HierarchyNode object */ const nonBranchToHierarchyNode = ( type: HierarchyNodeType, hierarchy: Reducer | Store, registerSubStore: RegisterSubStore, currentPath: string[] ): HierarchyNode => { if (type === NullNodeType) { return { type } } if (type === ReducerNodeType) { return { type, reducer: hierarchy as Reducer } } // It's a Store hierarchy descriptor return { type: type as StoreNodeType, destroy: registerSubStore(currentPath, hierarchy as Store), reducer: wrapStoreInReducer(hierarchy as Store), store: hierarchy as Store, } } /** * Determines the HierarchyNodeType of the given hierarchy descriptor. * * Throws a TypeError if the descriptor is invalid. */ export const getHierarchyType = (descriptor: HierarchyDescriptor) => { if (typeof descriptor === 'function') return ReducerNodeType if (descriptor && is(descriptor, Store)) return StoreNodeType if (isPlainObject(descriptor)) return BranchNodeType if (DEV && descriptor != null) { throw new TypeError( `Zedux: store.use() - Hierarchy descriptor nodes must be reducers, stores, plain objects, or null. Received ${detailedTypeof( descriptor )}` ) } return NullNodeType } /** * Turns a normal, user-supplied hierarchy descriptor into a Hierarchy for easy * reducer hierarchy creating, diffing, merging, and destroying. * * Also figures out the reducer for non-branch nodes. */ export const hierarchyDescriptorToHierarchy = ( hierarchy: HierarchyDescriptor, registerSubStore: RegisterSubStore, currentPath: string[] = [] ): HierarchyNode => { const type = getHierarchyType(hierarchy) if (type !== BranchNodeType) { return nonBranchToHierarchyNode( type, hierarchy as Reducer | Store, registerSubStore, currentPath ) } // It's a Branch; recursively convert the whole tree. We don't need to supply // a reducer for this branch 'cause the merge process does that for us return { type, children: branchToHierarchyChildren( hierarchy as Branch, registerSubStore, currentPath ), } } /** * Creates a reducer that wraps the entry points of the given store. * * This reducer will propagate actions down the child store's reducers. * * Wraps all actions in the special `inherit` meta node to inform the child * store's effects subscribers that this action was received from its parent * store. * * Since the parent store also registers an effects subscriber on this child * store, it will know not to propagate the inherited action from the child * store. UPDATE: Actually, it doesn't even need to check - the parent store * knows that it _isDispatching and can ignore child store actions while it is. */ export const wrapStoreInReducer = <State>(store: Store<State>) => { const reducer: Reducer = (state: State, action: Action) => { // If this is the special hydrate or partial hydrate action, re-create the // action's payload using the current state slice if ( action.type === zeduxTypes.hydrate || action.type === zeduxTypes.merge ) { action = { type: zeduxTypes.hydrate, payload: state, } } // Tell the child store's effect subscribers that this action is inherited const inheritedAction: ActionMeta = { metaType: zeduxTypes.inherit, payload: action, } return store.dispatch(inheritedAction) } return reducer }