UNPKG

evolve-ts

Version:

Immutably update nested objects with patches containing values or functions to update values

86 lines (82 loc) 3.91 kB
'use strict'; class Unset { } // using class so the type will be unique /** sentinel value that will cause its key to be removed */ const unset = Unset; const toString = {}.toString; const isObj = (o) => toString.call(o) === "[object Object]"; const isFn = (x) => x != unset && typeof x === "function"; // this will return false positives for classes other than unset const curry = (fn) => (...args) => args.length < fn.length ? (...moreArgs) => curry(fn)(...args, ...moreArgs) : fn(...args); const baseEvolve = (patch, target) => { if (patch && isObj(patch)) { // shave bytes by reassigning, but not mutating, arguments target = isObj(target) ? { ...target } : {}; Object.keys(patch).forEach((key) => { target[key] = baseEvolve(patch[key], target[key]); if (target[key] == unset) delete target[key]; }); return target; } else if (isFn(patch)) { return patch(target); } else { return patch; } }; /** deeply merges changes from a patch object into a target object, the patch object can contain new values or functions to update values */ const evolve = curry(baseEvolve); /** polymorphic type alias for evolve: deeply merges changes from a patch object into a target object, the patch object can contain new values or functions to update values */ const evolve_ = evolve; const baseShallowEvolve = (patch, target) => { // shave bytes by reassigning, but not mutating, arguments target = { ...target }; Object.keys(patch).forEach((key) => { const update = patch[key]; target[key] = isFn(update) ? update(target[key]) : update; if (target[key] == unset) delete target[key]; }); return target; }; /** merges changes from a patch object into a target object, the patch object can contain new values or functions to update values */ const shallowEvolve = curry(baseShallowEvolve); const createAdjust = (ev) => (predicateOrIndex, updaterOrPatch, array) => { // shave bytes by reassigning, but not mutating, arguments if (!isFn(updaterOrPatch)) { updaterOrPatch = ev(updaterOrPatch); } // track if any item was changed let changed; const updater = (item) => ((changed = 1), updaterOrPatch(item)); if (predicateOrIndex < 0) { // allow using negative indexes as offsets from end predicateOrIndex = array.length + predicateOrIndex; } const mapped = array.map(isFn(predicateOrIndex) ? (item) => (predicateOrIndex(item) ? updater(item) : item) : (item, i) => (i === predicateOrIndex ? updater(item) : item)); return changed ? mapped : array; }; /** conditionally maps values in an array with a callback function or patch. Value(s) to map can be specified with an index or predicate function. Negative indexes are treated as offsets from the array length */ const adjust = curry(createAdjust(evolve)); /** conditionally maps values in an array with a callback function or patch. Value(s) to map can be specified with an index or predicate function. Negative indexes are treated as offsets from the array length */ const shallowAdjust = curry(createAdjust(shallowEvolve)); const createMap = (ev) => (updaterOrPatch, array) => array.map(isFn(updaterOrPatch) ? (item) => updaterOrPatch(item) // clamp args to 1 : ev(updaterOrPatch)); /** maps values in an array with a callback function or patch */ const map = curry(createMap(evolve)); /** maps values in an array with a callback function or patch */ const shallowMap = curry(createMap(shallowEvolve)); exports.adjust = adjust; exports.evolve = evolve; exports.evolve_ = evolve_; exports.map = map; exports.shallowAdjust = shallowAdjust; exports.shallowEvolve = shallowEvolve; exports.shallowMap = shallowMap; exports.unset = unset;