@zedux/core
Version:
A high-level, declarative, composable form of Redux
174 lines (154 loc) • 4.62 kB
text/typescript
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
}