unchanged
Version:
A tiny, fast, unopinionated handler for updating JS objects and arrays immutably
688 lines (679 loc) • 24.2 kB
JavaScript
import { curry } from 'curriable';
export { __ } from 'curriable';
import { parse } from 'pathington';
/*! *****************************************************************************
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 : 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 = curry(createAdd(false));
var addWith = curry(createAdd(true));
var assign$1 = curry(createMerge(false, false));
var assignWith = curry(createMerge(true, false));
var call = curry(createCall(false), 3);
var callWith = curry(createCall(true), 4);
var get = curry(createGet(false));
var getOr = curry(createGetOr(false));
var getWith = curry(createGet(true));
var getWithOr = curry(createGetOr(true));
var has = curry(createHas(false));
var hasWith = curry(createHas(true));
var is = curry(createIs(false));
var isWith = curry(createIs(true));
var merge = curry(createMerge(false, true));
var mergeWith = curry(createMerge(true, true));
var not = curry(createNot(false));
var notWith = curry(createNot(true));
var remove = curry(createRemove(false));
var removeWith = curry(createRemove(true));
var set = curry(createSet(false));
var setWith = curry(createSet(true));
export { add, addWith, assign$1 as assign, assignWith, call, callWith, get, getOr, getWith, getWithOr, has, hasWith, is, isWith, merge, mergeWith, not, notWith, remove, removeWith, set, setWith };
//# sourceMappingURL=unchanged.esm.js.map