updeep-remeda/src/update.ts

111 lines
2.4 KiB
TypeScript
Raw Permalink Normal View History

2023-01-03 18:51:35 +00:00
import wrap from "./wrap.js";
import constant from "./constant.js";
2018-11-28 00:56:13 +00:00
2023-01-03 18:51:35 +00:00
import { omitBy, isObject } from "remeda";
2018-11-28 00:56:13 +00:00
2023-01-03 18:51:35 +00:00
const innerOmitted = { __omitted: true };
export const omitted = constant(innerOmitted);
function isEmpty(object) {
2023-01-03 18:51:35 +00:00
return !Object.keys(object).length;
}
function reduce(object, callback, initialValue) {
2017-04-19 00:55:46 +00:00
return Object.keys(object).reduce(
(acc, key) => callback(acc, object[key], key),
initialValue
2023-01-03 18:51:35 +00:00
);
}
function resolveUpdates(updates, object) {
2017-04-19 00:55:46 +00:00
return reduce(
updates,
(acc, value, key) => {
2023-01-03 18:51:35 +00:00
let updatedValue = value;
2017-04-19 00:55:46 +00:00
if (
!Array.isArray(value) &&
value !== null &&
2023-01-03 18:51:35 +00:00
typeof value === "object"
2017-04-19 00:55:46 +00:00
) {
2023-01-03 18:51:35 +00:00
updatedValue = update(object[key], value); // eslint-disable-line no-use-before-define
} else if (typeof value === "function") {
updatedValue = value(object[key]);
2017-04-19 00:55:46 +00:00
}
if (object[key] !== updatedValue) {
2023-01-03 18:51:35 +00:00
acc[key] = updatedValue; // eslint-disable-line no-param-reassign
2017-04-19 00:55:46 +00:00
}
2023-01-03 18:51:35 +00:00
return acc;
2017-04-19 00:55:46 +00:00
},
{}
2023-01-03 18:51:35 +00:00
);
}
2015-08-05 07:27:56 +00:00
function updateArray(updates, object) {
2023-01-03 18:51:35 +00:00
const newArray = [...object];
2020-04-02 14:16:13 +00:00
Object.keys(updates).forEach((key) => {
2023-01-03 18:51:35 +00:00
newArray[key] = updates[key];
});
2023-01-03 18:51:35 +00:00
return newArray;
}
2023-01-03 18:51:35 +00:00
const isPlainObject = (value) => value?.constructor === Object;
/**
* Recursively update an object or array.
*
* Can update with values:
* update({ foo: 3 }, { foo: 1, bar: 2 });
* // => { foo: 3, bar: 2 }
*
* Or with a function:
* update({ foo: x => (x + 1) }, { foo: 2 });
* // => { foo: 3 }
2015-08-02 06:38:27 +00:00
*
2015-08-19 14:29:06 +00:00
* @function
* @name update
2015-08-02 06:38:27 +00:00
* @param {Object|Function} updates
* @param {Object|Array} object to update
* @return {Object|Array} new object with modifications
*/
2023-01-03 18:51:35 +00:00
function update(object, updates) {
if (typeof updates === "function") {
return updates(object);
}
if (!isPlainObject(updates)) {
2023-01-03 18:51:35 +00:00
return updates;
}
const defaultedObject =
2023-01-03 18:51:35 +00:00
typeof object === "undefined" || object === null ? {} : object;
2023-01-03 18:51:35 +00:00
const resolvedUpdates = resolveUpdates(updates, defaultedObject);
if (isEmpty(resolvedUpdates)) {
2023-01-03 18:51:35 +00:00
return defaultedObject;
}
if (Array.isArray(defaultedObject)) {
2018-11-28 00:56:13 +00:00
return updateArray(resolvedUpdates, defaultedObject).filter(
2020-04-02 14:16:13 +00:00
(value) => value !== innerOmitted
2023-01-03 18:51:35 +00:00
);
}
2023-01-03 18:51:35 +00:00
return omitBy(
2018-11-28 00:56:13 +00:00
{ ...defaultedObject, ...resolvedUpdates },
2020-04-02 14:16:13 +00:00
(value) => value === innerOmitted
2023-01-03 18:51:35 +00:00
);
}
2023-01-03 19:31:53 +00:00
export interface Update {
2023-01-03 18:51:35 +00:00
(object, func): any;
(func): (object) => any;
}
2023-01-03 18:51:35 +00:00
export default wrap(update) as Update;