UNPKG

@feugene/mu

Version:

Helpful TS utilities without dependencies

74 lines 2.92 kB
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