UNPKG

unchanged

Version:

A tiny, fast, unopinionated handler for updating JS objects and arrays immutably

721 lines (710 loc) 28.1 kB
(function (global, factory) { typeof exports === 'object' && typeof module !== 'undefined' ? factory(exports, require('curriable'), require('pathington')) : typeof define === 'function' && define.amd ? define(['exports', 'curriable', 'pathington'], factory) : (global = global || self, factory(global.unchanged = {}, global.curriable, global.pathington)); }(this, (function (exports, curriable, pathington) { 'use strict'; /*! ***************************************************************************** Copyright (c) Microsoft Corporation. All rights reserved. Licensed under the Apache License, Version 2.0 (the "License"); you may not use this file except in compliance with the License. You may obtain a copy of the License at http://www.apache.org/licenses/LICENSE-2.0 THIS CODE IS PROVIDED ON AN *AS IS* BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, EITHER EXPRESS OR IMPLIED, INCLUDING WITHOUT LIMITATION ANY IMPLIED WARRANTIES OR CONDITIONS OF TITLE, FITNESS FOR A PARTICULAR PURPOSE, MERCHANTABLITY OR NON-INFRINGEMENT. See the Apache Version 2.0 License for specific language governing permissions and limitations under the License. ***************************************************************************** */ function __spreadArrays() { for (var s = 0, i = 0, il = arguments.length; i < il; i++) s += arguments[i].length; for (var r = Array(s), k = 0, i = 0; i < il; i++) for (var a = arguments[i], j = 0, jl = a.length; j < jl; j++, k++) r[k] = a[j]; return r; } // external dependencies var O = Object; var create = O.create, getOwnPropertySymbols = O.getOwnPropertySymbols, getPrototypeOf = O.getPrototypeOf, keys = O.keys, propertyIsEnumerable = O.propertyIsEnumerable; var isArray = Array.isArray; var toStringFunction = Function.prototype.bind.call(Function.prototype.call, Function.prototype.toString); var toStringObject = Function.prototype.bind.call(Function.prototype.call, O.prototype.toString); /** * @constant HAS_SYMBOL_SUPPORT are Symbols supported */ var HAS_SYMBOL_SUPPORT = typeof Symbol === 'function' && typeof Symbol.for === 'function'; /** * @constant REACT_ELEMENT the symbol / number specific to react elements */ var REACT_ELEMENT = HAS_SYMBOL_SUPPORT ? Symbol.for('react.element') : 0xeac7; /** * @function cloneArray * * @description * clone an array to a new array * * @param array the array to clone * @returns the cloned array */ var cloneArray = function (array) { var Constructor = array.constructor; var cloned = Constructor === Array ? [] : new Constructor(); for (var index = 0, length_1 = array.length; index < length_1; index++) { cloned[index] = array[index]; } return cloned; }; /** * @function reduce * * @description * a targeted reduce method faster than the native * * @param array the array to reduce * @param fn the method to reduce each array value with * @param initialValue the initial value of the reduction * @returns the reduced value */ var reduce = function (array, fn, initialValue) { var value = initialValue; for (var index = 0, length_2 = array.length; index < length_2; index++) { value = fn(value, array[index]); } return value; }; /** * @function getOwnProperties * * @description * get the all properties (keys and symbols) of the object passed * * @param object the object to get the properties of * @returns the keys and symbols the object has */ var getOwnProperties = function (object) { if (!HAS_SYMBOL_SUPPORT) { return keys(object); } var ownSymbols = getOwnPropertySymbols(object); if (!ownSymbols.length) { return keys(object); } return keys(object).concat(reduce(ownSymbols, function (enumerableSymbols, symbol) { if (propertyIsEnumerable.call(object, symbol)) { enumerableSymbols.push(symbol); } return enumerableSymbols; }, [])); }; /** * @function assignFallback * * @description * a targeted fallback if native Object.assign is unavailable * * @param target the object to shallowly merge into * @param source the object to shallowly merge into target * @returns the shallowly merged object */ var assignFallback = function (target, source) { if (!source) { return target; } return reduce(getOwnProperties(source), function (clonedObject, property) { clonedObject[property] = source[property]; return clonedObject; }, Object(target)); }; var assign = typeof O.assign === 'function' ? O.assign : assignFallback; /** * @function createWithProto * * @description * create a new object with the prototype of the object passed * * @param object object whose prototype will be the new object's prototype * @returns object with the prototype of the one passed */ var createWithProto = function (object) { return create(object.__proto__ || getPrototypeOf(object)); }; /** * @function isCloneable * * @description * is the object passed considered cloneable * * @param object the object that is being checked for cloneability * @returns whether the object can be cloned */ var isCloneable = function (object) { if (!object || typeof object !== 'object' || object.$$typeof === REACT_ELEMENT) { return false; } var type = toStringObject(object); return type !== '[object Date]' && type !== '[object RegExp]'; }; /** * @function isEmptyPath * * @description * is the path passed an empty path * * @param path the path to check for emptiness * @returns whether the path passed is considered empty */ var isEmptyPath = function (path) { return path == null || (isArray(path) && !path.length); }; /** * @function isGlobalConstructor * * @description * is the fn passed a global constructor * * @param fn the fn to check if a global constructor * @returns whether the fn passed is a global constructor */ var isGlobalConstructor = function (fn) { return typeof fn === 'function' && !!~toStringFunction(fn).indexOf('[native code]'); }; /** * @function callIfFunction * * @description * if the object passed is a function, call it and return its return, else return undefined * * @param object the object to call if a function * @param context the context to call the function with * @param parameters the parameters to call the function with * @returns the result of the function call, or undefined */ var callIfFunction = function (object, context, parameters) { return typeof object === 'function' ? object.apply(context, parameters) : void 0; }; /** * @function getNewEmptyChild * * @description * get a new empty child object based on the key passed * * @param key the key to base the empty child on * @returns the empty object the child is built from */ var getNewEmptyChild = function (key) { return typeof key === 'number' ? [] : {}; }; /** * @function getNewEmptyObject * * @description * get a new empty object based on the object passed * * @param object the object to base the empty object on * @returns an empty version of the object passed */ var getNewEmptyObject = function (object) { return isArray(object) ? [] : {}; }; /** * @function getShallowClone * * @description * create a shallow clone of the object passed, respecting its prototype * * @param object the object to clone * @returns a shallow clone of the object passed */ var getShallowClone = function (object) { if (object.constructor === O) { return assign({}, object); } if (isArray(object)) { return cloneArray(object); } return isGlobalConstructor(object.constructor) ? {} : assign(createWithProto(object), object); }; /** * @function isSameValueZero * * @description * are the values equal based on SameValueZero * * @param value1 the first value to test * @param value2 the second value to test * @returns are the two values passed equal based on SameValueZero */ var isSameValueZero = function (value1, value2) { return value1 === value2 || (value1 !== value1 && value2 !== value2); }; /** * @function cloneIfPossible * * @description * clone the object if it can be cloned, otherwise return the object itself * * @param object the object to clone * @returns a cloned version of the object, or the object itself if not cloneable */ var cloneIfPossible = function (object) { return isCloneable(object) ? getShallowClone(object) : object; }; /** * @function getCloneOrEmptyObject * * @description * if the object is cloneable, get a clone of the object, else get a new * empty child object based on the key * * @param object the object to clone * @param nextKey the key to base the empty child object on * @returns a clone of the object, or an empty child object */ var getCloneOrEmptyObject = function (object, nextKey) { return isCloneable(object) ? getShallowClone(object) : getNewEmptyChild(nextKey); }; /** * @function getCoalescedValue * * @description * return the value if not undefined, otherwise return the fallback value * * @param value the value to coalesce if undefined * @param fallbackValue the value to coalesce to * @returns the coalesced value */ var getCoalescedValue = function (value, fallbackValue) { return value === void 0 ? fallbackValue : value; }; /** * @function getParsedPath * * @description * parse the path passed into an array path * * @param path the path to parse * @returns the parsed path */ var getParsedPath = function (path) { return isArray(path) ? path : pathington.parse(path); }; /** * @function getCloneAtPath * * @description * get a new object, cloned at the path specified while leveraging * structural sharing for the rest of the properties * * @param path the path to clone at * @param object the object with cloned children at path * @param onMatch the method to call once the end of the path is reached * @param index the path index * @returns the object deeply cloned at the path specified */ var getCloneAtPath = function (path, object, onMatch, index) { var key = path[index]; var nextIndex = index + 1; if (nextIndex === path.length) { onMatch(object, key); } else { object[key] = getCloneAtPath(path, getCloneOrEmptyObject(object[key], path[nextIndex]), onMatch, nextIndex); } return object; }; /** * @function getDeepClone * * @description * get a clone of the object at the path specified * * @param path the path to clone at * @param object the object to clone at the path * @param onMatch once a patch match is found, the callback to fire * @returns the clone of the object at path specified */ var getDeepClone = function (path, object, onMatch) { var parsedPath = getParsedPath(path); var topLevelClone = getCloneOrEmptyObject(object, parsedPath[0]); if (parsedPath.length === 1) { onMatch(topLevelClone, parsedPath[0]); return topLevelClone; } return getCloneAtPath(parsedPath, topLevelClone, onMatch, 0); }; /** * @function getMergedObject * * @description * merge the source into the target, either deeply or shallowly * * @param target the object to merge into * @param source the object being merged into the target * @param isDeep is the merge a deep merge * @returns the merged object */ var getMergedObject = function (target, source, isDeep) { var isObject1Array = isArray(target); if (isObject1Array !== isArray(source) || !isCloneable(target)) { return cloneIfPossible(source); } if (isObject1Array) { return target.concat(source); } var targetClone = target.constructor === O || isGlobalConstructor(target.constructor) ? {} : createWithProto(target); return reduce(getOwnProperties(source), function (clone, key) { clone[key] = isDeep && isCloneable(source[key]) ? getMergedObject(target[key], source[key], isDeep) : source[key]; return clone; }, assign(targetClone, target)); }; /** * @function getValueAtPath * * @description * get the value at the nested property, or the fallback provided * * @param path the path to get the value from * @param object the object to get the value from at path * @param noMatchValue the value returned if no match is found * @returns the matching value, or the fallback provided */ var getValueAtPath = function (path, object, noMatchValue) { var parsedPath = getParsedPath(path); if (parsedPath.length === 1) { return object ? getCoalescedValue(object[parsedPath[0]], noMatchValue) : noMatchValue; } var ref = object; var key = parsedPath[0]; for (var index = 0; index < parsedPath.length - 1; index++) { if (!ref || !ref[key]) { return noMatchValue; } ref = ref[key]; key = parsedPath[index + 1]; } return ref ? getCoalescedValue(ref[key], noMatchValue) : noMatchValue; }; /** * @function getFullPath * * @description * get the path to add to, based on the object and fn passed * * @param path the path to add to * @param object the object traversed by the path * @param fn the function to transform the retrieved value with * @returns the full path to add to */ var getFullPath = function (path, object, fn) { var isPathEmpty = isEmptyPath(path); var valueAtPath = isPathEmpty ? object : fn ? fn(getValueAtPath(path, object)) : getValueAtPath(path, object); return isArray(valueAtPath) ? isArray(path) ? path.concat([valueAtPath.length]) : (isPathEmpty ? '' : path) + "[" + valueAtPath.length + "]" : path; }; /** * @function splice * * @description * a faster, more targeted version of the native splice * * @param array the array to remove the value from * @param splicedIndex the index of the value to remove */ var splice = function (array, splicedIndex) { if (array.length) { var cutoff = array.length - 1; var index = splicedIndex; while (index < cutoff) { array[index] = array[index + 1]; ++index; } array.length = cutoff; } }; /** * @function throwInvalidFnError * * @description * throw the TypeError based on the invalid handler * * @throws */ var throwInvalidFnError = function () { throw new TypeError('handler passed is not of type "function".'); }; var isArray$1 = Array.isArray; var slice = Function.prototype.bind.call(Function.prototype.call, Array.prototype.slice); function createCall(isWithHandler) { if (isWithHandler) { return function callWith(fn, path, parameters, object, context) { if (context === void 0) { context = object; } if (typeof fn !== 'function') { throwInvalidFnError(); } var extraArgs = slice(arguments, 5); if (isEmptyPath(path)) { return callIfFunction(fn.apply(void 0, __spreadArrays([object], extraArgs)), context, parameters); } var value = getValueAtPath(path, object); if (value === void 0) { return; } var result = fn.apply(void 0, __spreadArrays([value], extraArgs)); return callIfFunction(result, context, parameters); }; } return function call(path, parameters, object, context) { if (context === void 0) { context = object; } var callable = isEmptyPath(path) ? object : getValueAtPath(path, object); return callIfFunction(callable, context, parameters); }; } function createGet(isWithHandler) { if (isWithHandler) { return function getWith(fn, path, object) { if (typeof fn !== 'function') { throwInvalidFnError(); } var extraArgs = slice(arguments, 4); if (isEmptyPath(path)) { return fn.apply(void 0, __spreadArrays([object], extraArgs)); } var value = getValueAtPath(path, object); return value === void 0 ? value : fn.apply(void 0, __spreadArrays([value], extraArgs)); }; } return function get(path, object) { return isEmptyPath(path) ? object : getValueAtPath(path, object); }; } function createGetOr(isWithHandler) { if (isWithHandler) { return function getWithOr(fn, noMatchValue, path, object) { if (typeof fn !== 'function') { throwInvalidFnError(); } var extraArgs = slice(arguments, 4); if (isEmptyPath(path)) { return fn.apply(void 0, __spreadArrays([object], extraArgs)); } var value = getValueAtPath(path, object); return value === void 0 ? noMatchValue : fn.apply(void 0, __spreadArrays([value], extraArgs)); }; } return function getOr(noMatchValue, path, object) { return isEmptyPath(path) ? object : getValueAtPath(path, object, noMatchValue); }; } function createHas(isWithHandler) { if (isWithHandler) { return function hasWith(fn, path, object) { if (typeof fn !== 'function') { throwInvalidFnError(); } var extraArgs = slice(arguments, 3); if (isEmptyPath(path)) { return !!fn.apply(void 0, __spreadArrays([object], extraArgs)); } var value = getValueAtPath(path, object); return value !== void 0 && !!fn.apply(void 0, __spreadArrays([value], extraArgs)); }; } return function has(path, object) { return isEmptyPath(path) ? object != null : getValueAtPath(path, object) !== void 0; }; } function createIs(isWithHandler) { if (isWithHandler) { return function isWith(fn, path, value, object) { if (typeof fn !== 'function') { throwInvalidFnError(); } var extraArgs = slice(arguments, 4); if (isEmptyPath(path)) { return isSameValueZero(fn.apply(void 0, __spreadArrays([object], extraArgs)), value); } return isSameValueZero(fn.apply(void 0, __spreadArrays([getValueAtPath(path, object)], extraArgs)), value); }; } return function is(path, value, object) { var _path = isEmptyPath(path) ? object : getValueAtPath(path, object); return isSameValueZero(_path, value); }; } function createMerge(isWithHandler, isDeep) { if (isWithHandler) { return function mergeWith(fn, path, object) { if (typeof fn !== 'function') { throwInvalidFnError(); } var extraArgs = slice(arguments, 3); if (!isCloneable(object)) { return fn.apply(void 0, __spreadArrays([object], extraArgs)); } if (isEmptyPath(path)) { var objectToMerge = fn.apply(void 0, __spreadArrays([object], extraArgs)); return objectToMerge ? getMergedObject(object, objectToMerge, isDeep) : object; } var hasChanged = false; var result = getDeepClone(path, object, function (ref, key) { var objectToMerge = fn.apply(void 0, __spreadArrays([ref[key]], extraArgs)); if (objectToMerge) { ref[key] = getMergedObject(ref[key], objectToMerge, isDeep); hasChanged = true; } }); return hasChanged ? result : object; }; } return function merge(path, objectToMerge, object) { if (!isCloneable(object)) { return objectToMerge; } return isEmptyPath(path) ? getMergedObject(object, objectToMerge, true) : getDeepClone(path, object, function (ref, key) { ref[key] = getMergedObject(ref[key], objectToMerge, isDeep); }); }; } function createNot(isWithHandler) { var is = createIs(isWithHandler); return function not() { return !is.apply(this, arguments); }; } function createRemove(isWithHandler) { if (isWithHandler) { return function removeWith(fn, path, object) { if (typeof fn !== 'function') { throwInvalidFnError(); } var extraArgs = slice(arguments, 3); if (isEmptyPath(path)) { var emptyObject = getNewEmptyObject(object); return fn.apply(void 0, __spreadArrays([emptyObject], extraArgs)) ? emptyObject : object; } var value = getValueAtPath(path, object); return value !== void 0 && fn.apply(void 0, __spreadArrays([value], extraArgs)) ? getDeepClone(path, object, function (ref, key) { if (isArray$1(ref)) { splice(ref, key); } else { delete ref[key]; } }) : object; }; } return function remove(path, object) { if (isEmptyPath(path)) { return getNewEmptyObject(object); } return getValueAtPath(path, object) !== void 0 ? getDeepClone(path, object, function (ref, key) { if (isArray$1(ref)) { splice(ref, key); } else { delete ref[key]; } }) : object; }; } function createSet(isWithHandler) { if (isWithHandler) { return function setWith(fn, path, object) { if (typeof fn !== 'function') { throwInvalidFnError(); } var extraArgs = slice(arguments, 3); return isEmptyPath(path) ? fn.apply(void 0, __spreadArrays([object], extraArgs)) : getDeepClone(path, object, function (ref, key) { ref[key] = fn.apply(void 0, __spreadArrays([ref[key]], extraArgs)); }); }; } return function set(path, value, object) { return isEmptyPath(path) ? value : getDeepClone(path, object, function (ref, key) { ref[key] = value; }); }; } function createAdd(isWithHandler) { var _add = createSet(isWithHandler); if (isWithHandler) { return function addWith(fn, path, object) { return _add.apply(this, [fn, getFullPath(path, object, fn), object].concat(slice(arguments, 3))); }; } return function add(path, value, object) { return _add(getFullPath(path, object), value, object); }; } // external dependencies var add = curriable.curry(createAdd(false)); var addWith = curriable.curry(createAdd(true)); var assign$1 = curriable.curry(createMerge(false, false)); var assignWith = curriable.curry(createMerge(true, false)); var call = curriable.curry(createCall(false), 3); var callWith = curriable.curry(createCall(true), 4); var get = curriable.curry(createGet(false)); var getOr = curriable.curry(createGetOr(false)); var getWith = curriable.curry(createGet(true)); var getWithOr = curriable.curry(createGetOr(true)); var has = curriable.curry(createHas(false)); var hasWith = curriable.curry(createHas(true)); var is = curriable.curry(createIs(false)); var isWith = curriable.curry(createIs(true)); var merge = curriable.curry(createMerge(false, true)); var mergeWith = curriable.curry(createMerge(true, true)); var not = curriable.curry(createNot(false)); var notWith = curriable.curry(createNot(true)); var remove = curriable.curry(createRemove(false)); var removeWith = curriable.curry(createRemove(true)); var set = curriable.curry(createSet(false)); var setWith = curriable.curry(createSet(true)); Object.defineProperty(exports, '__', { enumerable: true, get: function () { return curriable.__; } }); exports.add = add; exports.addWith = addWith; exports.assign = assign$1; exports.assignWith = assignWith; exports.call = call; exports.callWith = callWith; exports.get = get; exports.getOr = getOr; exports.getWith = getWith; exports.getWithOr = getWithOr; exports.has = has; exports.hasWith = hasWith; exports.is = is; exports.isWith = isWith; exports.merge = merge; exports.mergeWith = mergeWith; exports.not = not; exports.notWith = notWith; exports.remove = remove; exports.removeWith = removeWith; exports.set = set; exports.setWith = setWith; Object.defineProperty(exports, '__esModule', { value: true }); }))); //# sourceMappingURL=unchanged.js.map