@feugene/mu
Version:
Helpful TS utilities without dependencies
74 lines • 2.92 kB
JavaScript
import clone from '../core/clone.mjs';
import isObject from '../is/isObject.mjs';
// Узкое определение plain object без опоры на constructor
function isPlainObject(val) {
if (!isObject(val))
return false;
const proto = Object.getPrototypeOf(val);
return proto === Object.prototype || proto === null;
}
const FORBIDDEN_KEYS = new Set(['__proto__', 'prototype', 'constructor']);
function isForbiddenKey(key) {
return typeof key === 'string' ? FORBIDDEN_KEYS.has(key) : false;
}
function ownEnumerableKeys(obj) {
const keys = Object.keys(obj);
const symbols = Object.getOwnPropertySymbols(obj).filter(sym => {
const desc = Object.getOwnPropertyDescriptor(obj, sym);
return !!desc?.enumerable;
});
return keys.concat(symbols);
}
/**
* Merge two or more objects into a new one (immutable deep merge for plain objects).
*
* Semantics (v5, ESM-only, Node 22+):
* - Immutability: returns a new object; inputs are not mutated.
* - Keys: only own enumerable string and symbol keys are processed.
* - Guards: forbidden keys "__proto__", "prototype", "constructor" are ignored (proto-pollution safe).
* - Depth: deep merge only occurs for plain objects (prototype is Object.prototype or null).
* - Arrays: arrays from sources replace destination values with a cloned array (no element-wise merge).
* - Other types (Date, Map, Set, functions, class instances): copied as values; plain-object rules do not apply.
*
* @example
* merge({ a: 1, o: { x: 1 } }, { b: 2, o: { y: 2 } })
* // => { a: 1, b: 2, o: { x: 1, y: 2 } }
*/
export default function merge(original, ...values) {
// Иммутабельность: не мутируем входной объект
const result = isPlainObject(original) ? { ...original } : original;
for (let i = 0; i < values.length; i++) {
const source = values[i];
if (!isObject(source))
continue;
for (const key of ownEnumerableKeys(source)) {
if (isForbiddenKey(key))
continue;
const value = source[key];
const target = result[key];
// Arrays: replace with a cloned array for predictability
if (Array.isArray(value)) {
;
result[key] = clone(value);
continue;
}
// Plain objects: deep merge
if (isPlainObject(value)) {
if (isPlainObject(target)) {
;
result[key] = merge(target, value);
}
else {
;
result[key] = clone(value);
}
continue;
}
// Primitives and other types: direct assignment
;
result[key] = value;
}
}
return result;
}
//# sourceMappingURL=merge.mjs.map