bowheart/zedux

View on GitHub
src/hierarchy/traverse.ts

Summary

Maintainability
A
0 mins
Test Coverage
A
100%
import { metaTypes } from '../api/constants'
import { ActionChain, HierarchyConfig } from '../types'
import { invalidDelegation } from '../utils/errors'
import { HierarchyType } from '../utils/general'
import { getMetaData, removeMeta } from '../api/meta'
import { DiffNode } from '../utils/types'

/**
  Delegates an action to a child store.

  Does nothing if the special DELEGATE meta node is not present
  in the action meta chain.

  This expects the `metaData` of the DELEGATE meta node to be
  an array containing a path of nodes describing the child store's
  location in the parent store's current hierarchy descriptor.

  Delegated actions will not be handled by the parent store at all.
*/
export const delegate = (diffTree: DiffNode, action: ActionChain) => {
  const subStorePath = getMetaData(action, metaTypes.DELEGATE)

  if (!subStorePath) return false

  const child = findChild(diffTree, subStorePath, invalidDelegation)

  if (child.type !== HierarchyType.Store) {
    throw new TypeError(invalidDelegation(subStorePath))
  }

  return child.store.dispatch(removeMeta(action, metaTypes.DELEGATE))
}

/**
  Finds a node in a diffTree given a node path (array of nodes).
*/
export const findChild = (
  diffTree: DiffNode,
  nodePath: string[],
  getErrorMessage: (nodePath: string[]) => string
) => {
  for (const node of nodePath) {
    if (!diffTree.children) {
      throw new ReferenceError(getErrorMessage(nodePath))
    }

    diffTree = diffTree.children[node]

    if (!diffTree) {
      throw new ReferenceError(getErrorMessage(nodePath))
    }
  }

  return diffTree
}

/**
  Propagates a state change from a child store to a parent.

  Recursively finds the child store's node in the parent store's
  state tree and re-creates all the nodes down that path.

  #immutability
*/
export const propagateChange = <State = any>(
  currentState: State,
  subStorePath: string[],
  newSubStoreState: any,
  hierarchyConfig: HierarchyConfig
): State => {
  if (!subStorePath.length) return newSubStoreState

  // at this point we can assume that currentState is a hierarhical structure
  // these "currentState as any" casts should be fine
  const newNode = hierarchyConfig.clone(currentState as any)
  const nextNodeKey = subStorePath[0]

  return hierarchyConfig.set(
    newNode,
    nextNodeKey,
    propagateChange(
      hierarchyConfig.get(currentState as any, nextNodeKey),
      subStorePath.slice(1),
      newSubStoreState,
      hierarchyConfig
    )
  ) as any
}