update.js

import {
  nodePathToKeys,
  nodePathToKeysChildren,
  isLeaf,
  asList
} from './utils';

/**
 * @module update
 */

/**
 * Once fully applied, this returns a new tree with the node at `nodePath` set to the return value of `updater`.
 * If any keys on `nodePath` do not exist, a new Immutable `Map` will be created at the missing key.
 * If the node at `nodePath` doesn't exist, `updater` will be called with the value of `notSetValue`.
 *
 * Be aware that by updating values on deep nodes that don't yet exist,
 * this may cause `Maps` to be created for child `Iterables` where other types of `Iterables` may be used for children elsewhere in the tree.
 * This will be addressed in a future release.
 *
 * This is intended to be used with a `childPath`, but if no `childPath` is provided
 * then this is functionally equivalent to Immutable's `updateIn()` method.
 *
 * @param {NodePath} nodePath A `NodePath` used to uniquely identify the node to update.
 * @param {Updater} updater The function to update the node, which is passed the existing node and must return the updated value of the node.
 * @param {ChildPath} [childPath=null] An `Array` or `List` of keys indicating where to find each node's children from within each node.
 * @param {*} [notSetValue=null] When there is no node corresponding to `nodePath`, this value is passed into `updater`.
 * @return {InputFunction} A partially applied function which accepts a single tree `Iterable`, and returns the modified tree `Iterable`.
 */

function deepUpdate(nodePath, updater, childPath=null, notSetValue=null) {
  return (tree) => tree.updateIn(nodePathToKeys(nodePath, childPath), notSetValue, updater);
}

/**
 * Once fully applied, this returns a new tree with the children of the node at `nodePath` set to the return value of `updater`.
 *
 * If no `childPath` is used there will be no change if the node at `nodePath` is not `Iterable`.
 *
 * If no node exists at `nodePath` then an Immutable `Map` will be created at that location.
 * If the node at `nodePath` doesn't exist, `updater` will be called with the value of `notSetValue`.
 *
 * This is intended to be used with a `childPath`, but if no `childPath` is provided
 * then this is functionally equivalent to both Immutable's 'updateIn()` method and `deepUpdate()`.
 *
 * @param {NodePath} nodePath A `NodePath` used to uniquely identify a node whose children will be updated.
 * @param {Updater} updater The function to update the node, which is passed the existing node's children and must return the updated value of the node's children.
 * @param {ChildPath} [childPath=null] An `Array` or `List` of keys indicating where to find each node's children from within each node.
 * @param {*} [notSetValue=null] When there is no node corresponding to `nodePath`, this value is passed into `updater`.
 * @return {InputFunction} A partially applied function which accepts a single tree `Iterable`, and returns the modified tree `Iterable`.
 */

function deepUpdateChildren(nodePath, updater, childPath=null, notSetValue=null) {
  return (tree) => {
  	if(asList(childPath).isEmpty() && isLeaf(tree.getIn(nodePath))) {
	  return tree;
    }
  	return tree.updateIn(nodePathToKeysChildren(nodePath, childPath), notSetValue, updater);
  }
}

export {
  deepUpdate,
  deepUpdateChildren
}