@zedux/core
Version:
A high-level, declarative, composable form of Redux
108 lines (107 loc) • 4.16 kB
JavaScript
import { Store } from '../api/createStore.js';
import { detailedTypeof } from '../api/detailedTypeof.js';
import { is } from '../api/is.js';
import { isPlainObject } from '../api/isPlainObject.js';
import { zeduxTypes } from '../api/zeduxTypes.js';
import { BranchNodeType, NullNodeType, ReducerNodeType, StoreNodeType, } from '../utils/general.js';
/**
* Converts a Branch hierarchy descriptor to a HierarchyNode's children
*
* Really should only be used from `hierarchyDescriptorToHierarchy()`
*/
const branchToHierarchyChildren = (branch, registerSubStore, currentPath) => {
const children = {};
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, hierarchy, registerSubStore, currentPath) => {
if (type === NullNodeType) {
return { type };
}
if (type === ReducerNodeType) {
return { type, reducer: hierarchy };
}
// It's a Store hierarchy descriptor
return {
type: type,
destroy: registerSubStore(currentPath, hierarchy),
reducer: wrapStoreInReducer(hierarchy),
store: hierarchy,
};
};
/**
* Determines the HierarchyNodeType of the given hierarchy descriptor.
*
* Throws a TypeError if the descriptor is invalid.
*/
export const getHierarchyType = (descriptor) => {
if (typeof descriptor === 'function')
return ReducerNodeType;
if (descriptor && is(descriptor, Store))
return StoreNodeType;
if (isPlainObject(descriptor))
return BranchNodeType;
if (true /* 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, registerSubStore, currentPath = []) => {
const type = getHierarchyType(hierarchy);
if (type !== BranchNodeType) {
return nonBranchToHierarchyNode(type, hierarchy, 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, 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 = (store) => {
const reducer = (state, 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 = {
metaType: zeduxTypes.inherit,
payload: action,
};
return store.dispatch(inheritedAction);
};
return reducer;
};