UNPKG

@graphql-tools/utils

Version:

Common package containing utils and types for GraphQL tools

201 lines (200 loc) • 9.07 kB
"use strict"; Object.defineProperty(exports, "__esModule", { value: true }); exports.mergeDeep = mergeDeep; const helpers_js_1 = require("./helpers.js"); function isMergeDeepOptions(value) { return value != null && typeof value === 'object' && !Array.isArray(value); } function normalizeRespectSymbols(respectSymbols) { return { enumerable: respectSymbols?.enumerable ?? false, nonEnumerable: respectSymbols?.nonEnumerable ?? false, }; } function normalizeMergeDeepOptions(respectPrototypeOrOptions, respectArrays = false, respectArrayLength = false, respectNonEnumerableSymbols = false) { if (isMergeDeepOptions(respectPrototypeOrOptions)) { return { respectPrototype: respectPrototypeOrOptions.respectPrototype ?? false, respectArrays: respectPrototypeOrOptions.respectArrays ?? false, respectArrayLength: respectPrototypeOrOptions.respectArrayLength ?? false, respectSymbols: normalizeRespectSymbols(respectPrototypeOrOptions.respectSymbols), }; } return { respectPrototype: respectPrototypeOrOptions ?? false, respectArrays, respectArrayLength, // Legacy 5th positional arg maps to non-enumerable-only symbol handling. respectSymbols: { enumerable: false, nonEnumerable: respectNonEnumerableSymbols, }, }; } function mergeDeep(sources, respectPrototypeOrOptions = false, respectArrays = false, respectArrayLength = false, respectNonEnumerableSymbols = false) { const options = normalizeMergeDeepOptions(respectPrototypeOrOptions, respectArrays, respectArrayLength, respectNonEnumerableSymbols); return mergeDeepWithOptions(sources, options); } function mergeDeepWithOptions(sources, options) { const { respectPrototype, respectArrayLength, respectSymbols } = options; const respectEnumerableSymbols = respectSymbols.enumerable; const respectNonEnumerableSymbols = respectSymbols.nonEnumerable; if (sources.length === 0) { return; } // Single-source is an identity return (historical behavior). Symbol policy below only // applies when merging two or more sources; stripping here would require cloning. if (sources.length === 1) { return sources[0]; } let expectedLength; let allArrays = true; const areArraysInTheSameLength = sources.every(source => { if (Array.isArray(source)) { if (expectedLength === undefined) { expectedLength = source.length; return true; } else if (expectedLength === source.length) { return true; } } else { allArrays = false; } return false; }); if (respectArrayLength && areArraysInTheSameLength) { return new Array(expectedLength).fill(null).map((_, index) => mergeDeepWithOptions(sources.map(source => source[index]), options)); } if (allArrays) { return sources.flat(1); } let output; let firstObjectSource; if (respectPrototype) { firstObjectSource = sources.find(source => isObject(source)); if (firstObjectSource) { if (output == null) { output = {}; } Object.setPrototypeOf(output, Object.create(Object.getPrototypeOf(firstObjectSource))); } } for (const source of sources) { if (source === undefined) { continue; } if (isObject(source)) { if (output == null) { output = {}; } if (firstObjectSource) { const outputPrototype = Object.getPrototypeOf(output); const sourcePrototype = Object.getPrototypeOf(source); if (sourcePrototype) { for (const key of Object.getOwnPropertyNames(sourcePrototype)) { const descriptor = Object.getOwnPropertyDescriptor(sourcePrototype, key); if ((0, helpers_js_1.isSome)(descriptor)) { Object.defineProperty(outputPrototype, key, descriptor); } } } } for (const key in source) { // never let a source key reach the prototype chain if (key === '__proto__' || key === 'constructor' || key === 'prototype') { continue; } // An own key present with value `undefined` is an explicit override and must win, // distinct from the key being altogether absent from `source`. Assigning directly // (rather than recursing through mergeDeep) avoids the top-level `source === undefined` // skip above, which would otherwise silently discard this override and keep the prior // value. if (Object.prototype.hasOwnProperty.call(output, key) && source[key] !== undefined) { output[key] = mergeDeepWithOptions([output[key], source[key]], options); } else { output[key] = source[key]; } } if (output != null && (respectEnumerableSymbols || respectNonEnumerableSymbols)) { for (const sym of Object.getOwnPropertySymbols(source)) { const descriptor = Object.getOwnPropertyDescriptor(source, sym); if (!descriptor.enumerable) { if (respectNonEnumerableSymbols) { if (Object.prototype.hasOwnProperty.call(output, sym)) { const existing = Object.getOwnPropertyDescriptor(output, sym); // Later sources should win. Non-configurable properties cannot be redefined, // so fall back to value assignment when writable or setter-backed. if (existing.configurable) { Object.defineProperty(output, sym, descriptor); } else if (existing.writable || existing.set) { output[sym] = source[sym]; } } else { Object.defineProperty(output, sym, descriptor); } } continue; } if (!respectEnumerableSymbols) { continue; } const hasOwnSymbol = Object.prototype.hasOwnProperty.call(output, sym); if (hasOwnSymbol && source[sym] !== undefined) { const existing = Object.getOwnPropertyDescriptor(output, sym); const mergedValue = mergeDeepWithOptions([output[sym], source[sym]], options); if (existing.writable || existing.set) { output[sym] = mergedValue; } else if (existing.configurable) { Object.defineProperty(output, sym, { value: mergedValue, writable: true, enumerable: true, configurable: true, }); } // Non-configurable read-only properties cannot be updated; keep the prior value. } else if (!hasOwnSymbol) { output[sym] = source[sym]; } else { // Explicit undefined override, matching string-key behavior. const existing = Object.getOwnPropertyDescriptor(output, sym); if (existing.writable || existing.set) { output[sym] = source[sym]; } else if (existing.configurable) { Object.defineProperty(output, sym, { value: source[sym], writable: true, enumerable: true, configurable: true, }); } } } } } else if (Array.isArray(source)) { if (!Array.isArray(output)) { output = source; } else { output = mergeDeepWithOptions([output, source], options); } } else { output = source; } } return output; } function isObject(item) { return item && typeof item === 'object' && !Array.isArray(item); }