UNPKG

imuter

Version:
481 lines 15 kB
/// <amd-module name="imuter" /> "use strict"; Object.defineProperty(exports, "__esModule", { value: true }); exports.removeValues = exports.removeValue = exports.writeValues = exports.writeValue = exports.write = exports.array_sort = exports.array_filter = exports.array_map = exports.array_insert = exports.array_slice = exports.array_unshift = exports.array_pop = exports.array_shift = exports.array_push = exports.array_replace = exports.array_exclude = exports.array_remove = exports.array_delete = exports.array_set = exports.object_assign = exports.object_delete = exports.object_set = exports.imuter = void 0; var tslib_1 = require("tslib"); //Should be determined at compile time to allow tree-shaking var FREEZING_ENABLED = !(typeof process !== "undefined" && process.env.NODE_ENV === "production"); // eslint-disable-next-line @typescript-eslint/unbound-method var toString = {}.toString; function recursiveFreeze(value) { // Primitives, naturally frozen and already frozen. // Assume it was deep frozen already as this must stop the recursion. if (Object.isFrozen(value)) { return value; } // Unfreezable nodes, assuming a numeric nodeType is a DOM Node if (+value.nodeType) { return value; } switch (toString.call(value)) { // Unfreezable types via toString() case "[object Int8Array]": case "[object Int16Array]": case "[object Int32Array]": case "[object Float32Array]": case "[object Float64Array]": case "[object Uint8Array]": case "[object Uint8ClampedArray]": case "[object Uint16Array]": case "[object Uint32Array]": case "[object ArrayBuffer]": case "[object Blob]": case "[object DOMWindow]": case "[object Window]": case "[object global]": case "[object XMLHttpRequest]": return value; // No need to recurse case "[object Boolean]": case "[object Number]": case "[object String]": case "[object Date]": case "[object RegExp]": return Object.freeze(value); } //Freeze before recursing in case of recursive references Object.freeze(value); if (Array.isArray(value)) { for (var _i = 0, value_1 = value; _i < value_1.length; _i++) { var entry = value_1[_i]; recursiveFreeze(entry); } } else { for (var key in value) { recursiveFreeze(value[key]); } } return value; } function identity(value) { return value; } function valueFn(v) { return function () { return v; }; } var shallowFreeze = FREEZING_ENABLED ? Object.freeze : identity; var deepFreeze = FREEZING_ENABLED ? recursiveFreeze : identity; /** * Freezes the passed object. * * NOTE: in production this is a noop/identity function. * * @param value the object to freeze * @returns the passed object, now frozen */ exports.imuter = deepFreeze; var DELETE_VALUE = deepFreeze({}); var REMOVE_VALUE = deepFreeze({}); var REMOVE_VALUE_FN = valueFn(REMOVE_VALUE); function shallowCloneObject(obj) { return Object.assign(Object.create(Object.getPrototypeOf(obj)), obj); } // Objects /** * Sets a property on an object. * * If the `obj[prop]` already equals the `value` the existing object is returned. * * @param obj the object * @param prop the property * @param value the value * @returns a new (frozen) instance of the object with the property updated */ function object_set(obj, prop, value) { if ((value === DELETE_VALUE || value === REMOVE_VALUE) ? !(prop in obj) : obj[prop] === value) { return obj; } var newObj = shallowCloneObject(obj); if (value === DELETE_VALUE || value === REMOVE_VALUE) { delete newObj[prop]; } else { FREEZING_ENABLED && deepFreeze(value); newObj[prop] = value; } FREEZING_ENABLED && shallowFreeze(newObj); return newObj; } exports.object_set = object_set; /** * `delete`s a property from an object. * * If the `obj[prop]` already does not exist the existing object is returned. * * @param obj the object * @param prop the property to delete * @returns a new (frozen) instance of the object with the property deleted */ function object_delete(obj, prop) { return object_set(obj, prop, DELETE_VALUE); } exports.object_delete = object_delete; function object_assign() { var sources = []; for (var _i = 0; _i < arguments.length; _i++) { sources[_i] = arguments[_i]; } var newObj = Object.assign.apply(Object, tslib_1.__spreadArray([{}], sources)); FREEZING_ENABLED && deepFreeze(newObj); return newObj; } exports.object_assign = object_assign; // Arrays /** * Assign to an index of the passed array. * * If the `array[index]` already equals the `value` the existing array is returned. * * @param arr the array * @param index the index assign to * @param value the value * @returns a new (frozen) instance of the array with the specified `index` set to `value` */ function array_set(arr, index, value) { if ((value === DELETE_VALUE || value === REMOVE_VALUE) ? !(index in arr) : arr[index] === value) { return arr; } var newArr = arr.slice(); if (value === DELETE_VALUE) { delete newArr[index]; } else if (value === REMOVE_VALUE) { newArr.splice(index, 1); } else { FREEZING_ENABLED && deepFreeze(value); newArr[index] = value; } FREEZING_ENABLED && shallowFreeze(newArr); return newArr; } exports.array_set = array_set; /** * `delete`s an index from the passed array. * * If the `array[index]` already does not exist the existing array is returned. * * @param arr the array * @param index the index to delete * @returns a new (frozen) instance of the array with `index` `delete`ed */ function array_delete(arr, index) { return array_set(arr, index, DELETE_VALUE); } exports.array_delete = array_delete; /** * Removes entries from an array. Equivelent to the standard `splice`. * * If the `array[index]` does not exist the existing array is returned. * * @param arr the array * @param index the index to remove from * @param deleteCount the number of entries to remove (default: 1) * @returns a new (frozen) instance of the array with `deleteCount` entries removed at `index` */ function array_remove(arr, index, deleteCount) { if (deleteCount === void 0) { deleteCount = 1; } if (arr.length <= index || deleteCount === 0) { return arr; } var newArr = arr.slice(); newArr.splice(index, deleteCount); FREEZING_ENABLED && shallowFreeze(newArr); return newArr; } exports.array_remove = array_remove; function notEqualThis(x) { return x !== this; } /** * Remove all occurances of a value from an array. * * If no occurances exist the existing array is returned. * * @param arr the array * @param value the value to remove from the array * @returns a new (frozen) instance of the array with `value` removed */ function array_exclude(arr, value) { return array_filter(arr, notEqualThis, value); } exports.array_exclude = array_exclude; /** * Replace all occurances of a value with a new value. * * @param arr the array * @param oldValue the value to replace * @param newValue the new value * @returns a new (frozen) instance of the array with `oldValue` replaced with `newValue` */ function array_replace(arr, oldValue, newValue) { FREEZING_ENABLED && deepFreeze(newValue); var found = false; var n = array_map(arr, function (v) { return v === oldValue ? (found = true) && newValue : v; }); return found ? n : arr; } exports.array_replace = array_replace; /** * Push values onto an array. * * @param arr the array * @param values the values to push * @returns a new (frozen) instance of the array with the values pushed */ function array_push(arr) { var values = []; for (var _i = 1; _i < arguments.length; _i++) { values[_i - 1] = arguments[_i]; } if (values.length === 0) { return arr; } var newArr = arr.slice(); newArr.push.apply(newArr, values); FREEZING_ENABLED && deepFreeze(values); FREEZING_ENABLED && shallowFreeze(newArr); return newArr; } exports.array_push = array_push; /** * Shift a value off the array. * * If the array is empty the existing array is returned. * * @param arr the array * @returns a new (frozen) instance of the array with an entry shifted */ function array_shift(arr) { if (arr.length === 0) { return arr; } var newArr = arr.slice(); newArr.shift(); FREEZING_ENABLED && shallowFreeze(newArr); return newArr; } exports.array_shift = array_shift; /** * Pops a value off the array. * * If the array is empty the existing array is returned. * * @param arr the array * @returns a new (frozen) instance of the array with an entry popped */ function array_pop(arr) { if (arr.length === 0) { return arr; } var newArr = arr.slice(0, -1); FREEZING_ENABLED && shallowFreeze(newArr); return newArr; } exports.array_pop = array_pop; /** * Unshift values onto the array. * * @param arr the array * @returns a new (frozen) instance of the array with the `values` `unshift`ed */ function array_unshift(arr) { var values = []; for (var _i = 1; _i < arguments.length; _i++) { values[_i - 1] = arguments[_i]; } if (values.length === 0) { return arr; } var newArr = arr.slice(); newArr.unshift.apply(newArr, values); FREEZING_ENABLED && deepFreeze(values); FREEZING_ENABLED && shallowFreeze(newArr); return newArr; } exports.array_unshift = array_unshift; /** * Slice off a portion of the array. * * @param arr the array * @param start the start of the slice * @param end the end of the slice (default: the end) * @returns a slice of the array */ function array_slice(arr, start, end) { if (start === 0 && (end === undefined || arr.length <= end)) { return arr; } var newArr = arr.slice(start, end); FREEZING_ENABLED && shallowFreeze(newArr); return newArr; } exports.array_slice = array_slice; /** * Insert values into an array. * * @param arr the array * @param index the index to insert at * @param values the values to insert * @returns a new (frozen) instance of the array with the `values` inserted at `index` */ function array_insert(arr, index) { var values = []; for (var _i = 2; _i < arguments.length; _i++) { values[_i - 2] = arguments[_i]; } if (values.length === 0) { return arr; } var newArr = arr.slice(); newArr.splice.apply(newArr, tslib_1.__spreadArray([index, 0], values)); FREEZING_ENABLED && deepFreeze(values); FREEZING_ENABLED && shallowFreeze(newArr); return newArr; } exports.array_insert = array_insert; /** * Map the entries of the array to (potentially) new values. * * @param arr the array * @param callbackFn the mapping function * @param context the context to execute `callbackFn` * @returns a new mapped array */ function array_map(arr, callbackFn, context) { if (arr.length === 0) { return arr; } var mapped = arr.map(callbackFn, context); FREEZING_ENABLED && deepFreeze(mapped); return mapped; } exports.array_map = array_map; /** * Filter the entries of an array. * * @param arr the array * @param filterFn the filter function * @param context the context to execute `filterFn` * @returns a new filtered array */ function array_filter(arr, filterFn, context) { if (arr.length === 0) { return arr; } var filtered = arr.filter(filterFn, context); var newArr = (filtered.length === arr.length) ? arr : filtered; FREEZING_ENABLED && shallowFreeze(newArr); return newArr; } exports.array_filter = array_filter; function arrayEqualsThis(o, i) { return o === this[i]; } /** * Sort an array * * @param arr the array * @param sortFn the sort comparison function * @returns a new sorted array */ function array_sort(arr, sortFn) { if (arr.length <= 1) { return arr; } var newArr = arr .slice() .sort(sortFn); if (newArr.every(arrayEqualsThis, arr)) { return arr; } FREEZING_ENABLED && shallowFreeze(newArr); return newArr; } exports.array_sort = array_sort; function write(data, pathOrKey, factory) { var path = Array.isArray(pathOrKey) ? pathOrKey : [pathOrKey]; //Follow the path into the object, except for the last value being replaced var objs = [data]; for (var i = 0; i < path.length; i++) { // Support non-existing parent values for the removeValue case if (factory === REMOVE_VALUE_FN && !objs[i].hasOwnProperty(path[i])) { return data; } objs.push(objs[i][path[i]]); } //Replace the last object with the new value objs[objs.length - 1] = factory(objs[objs.length - 1], data); //Write the new immutable data back into the objects for (var i = objs.length - 2; i >= 0; i--) { var key = path[i]; var obj = objs[i]; var val = objs[i + 1]; if (Array.isArray(obj)) { objs[i] = array_set(obj, key, val); } else { objs[i] = object_set(obj, key, val); } } return objs[0]; } exports.write = write; function read(data, path) { var obj = data; for (var _i = 0, path_1 = path; _i < path_1.length; _i++) { var p = path_1[_i]; obj = obj[p]; } return obj; } function writeValue(data, pathOrKey, value) { return write(data, pathOrKey, valueFn(value)); } exports.writeValue = writeValue; function writeValues(data, pathOrKey, values) { var path = Array.isArray(pathOrKey) ? pathOrKey : [pathOrKey]; var oldValue = read(data, path); var newValue = object_assign(oldValue, values); return writeValue(data, path, newValue); } exports.writeValues = writeValues; function removeValue(data, pathOrKey) { return write(data, pathOrKey, REMOVE_VALUE_FN); } exports.removeValue = removeValue; /** * `delete` multiple properties from an object. * * If none of the values exist the existing object is returned. * * @param data the object * @param keys the properties to `delete` * @returns a new (frozen) instance of the object with all `keys` deleted */ function removeValues(data) { var keys = []; for (var _i = 1; _i < arguments.length; _i++) { keys[_i - 1] = arguments[_i]; } var newValue; for (var _a = 0, keys_1 = keys; _a < keys_1.length; _a++) { var key = keys_1[_a]; if (data.hasOwnProperty(key)) { if (newValue === undefined) { newValue = shallowCloneObject(data); } delete newValue[key]; } } FREEZING_ENABLED && shallowFreeze(newValue); return newValue ? newValue : data; } exports.removeValues = removeValues; //# sourceMappingURL=imuter.js.map