'use strict'; Object.defineProperty(exports, '__esModule', { value: true }); var isPlainObject = require('is-plain-object'); /** * Get the type of the given object. * * @param object - The object to get the type of. * @returns The type of the given object. */ function getObjectType(object) { if (typeof object !== "object" || object === null) { return 0 /* NOT */; } if (Array.isArray(object)) { return 2 /* ARRAY */; } if (isPlainObject.isPlainObject(object)) { return 1 /* RECORD */; } if (object instanceof Set) { return 3 /* SET */; } if (object instanceof Map) { return 4 /* MAP */; } return 5 /* OTHER */; } /** * Get the keys of the given objects including symbol keys. * * Note: Only keys to enumerable properties are returned. * * @param objects - An array of objects to get the keys of. * @returns A set containing all the keys of all the given objects. */ function getKeys(objects) { const keys = new Set(); /* eslint-disable functional/no-loop-statement -- using a loop here is more efficient. */ for (const object of objects) { for (const key of [ ...Object.keys(object), ...Object.getOwnPropertySymbols(object), ]) { keys.add(key); } } /* eslint-enable functional/no-loop-statement */ return keys; } /** * Does the given object have the given property. * * @param object - The object to test. * @param property - The property to test. * @returns Whether the object has the property. */ function objectHasProperty(object, property) { return (typeof object === "object" && Object.prototype.propertyIsEnumerable.call(object, property)); } /** * Get an iterable object that iterates over the given iterables. */ function getIterableOfIterables(iterables) { return { *[Symbol.iterator]() { // eslint-disable-next-line functional/no-loop-statement for (const iterable of iterables) { // eslint-disable-next-line functional/no-loop-statement for (const value of iterable) { yield value; } } }, }; } const defaultMergeFunctions = { mergeMaps: defaultMergeMaps, mergeSets: defaultMergeSets, mergeArrays: defaultMergeArrays, mergeRecords: defaultMergeRecords, mergeOthers: leaf, }; /** * Special values that tell deepmerge-ts to perform a certain action. */ const actions = { defaultMerge: Symbol("deepmerge-ts: default merge"), skip: Symbol("deepmerge-ts: skip"), }; /** * The default function to update meta data. */ function defaultMetaDataUpdater(previousMeta, metaMeta) { return metaMeta; } /** * Deeply merge objects. * * @param objects - The objects to merge. */ function deepmerge(...objects) { return deepmergeCustom({})(...objects); } function deepmergeCustom(options, rootMetaData) { const utils = getUtils(options, customizedDeepmerge); /** * The customized deepmerge function. */ function customizedDeepmerge(...objects) { return mergeUnknowns(objects, utils, rootMetaData); } return customizedDeepmerge; } /** * The the full options with defaults apply. * * @param options - The options the user specified */ function getUtils(options, customizedDeepmerge) { var _a, _b; return { defaultMergeFunctions, mergeFunctions: { ...defaultMergeFunctions, ...Object.fromEntries(Object.entries(options) .filter(([key, option]) => Object.prototype.hasOwnProperty.call(defaultMergeFunctions, key)) .map(([key, option]) => option === false ? [key, leaf] : [key, option])), }, metaDataUpdater: ((_a = options.metaDataUpdater) !== null && _a !== void 0 ? _a : defaultMetaDataUpdater), deepmerge: customizedDeepmerge, useImplicitDefaultMerging: (_b = options.enableImplicitDefaultMerging) !== null && _b !== void 0 ? _b : false, actions, }; } /** * Merge unknown things. * * @param values - The values. */ function mergeUnknowns(values, utils, meta) { if (values.length === 0) { return undefined; } if (values.length === 1) { return mergeOthers(values, utils, meta); } const type = getObjectType(values[0]); // eslint-disable-next-line functional/no-conditional-statement -- add an early escape for better performance. if (type !== 0 /* NOT */ && type !== 5 /* OTHER */) { // eslint-disable-next-line functional/no-loop-statement -- using a loop here is more performant than mapping every value and then testing every value. for (let mutableIndex = 1; mutableIndex < values.length; mutableIndex++) { if (getObjectType(values[mutableIndex]) === type) { continue; } return mergeOthers(values, utils, meta); } } switch (type) { case 1 /* RECORD */: return mergeRecords(values, utils, meta); case 2 /* ARRAY */: return mergeArrays(values, utils, meta); case 3 /* SET */: return mergeSets(values, utils, meta); case 4 /* MAP */: return mergeMaps(values, utils, meta); default: return mergeOthers(values, utils, meta); } } /** * Merge records. * * @param values - The records. */ function mergeRecords(values, utils, meta) { const result = utils.mergeFunctions.mergeRecords(values, utils, meta); if (result === actions.defaultMerge || (utils.useImplicitDefaultMerging && result === undefined && utils.mergeFunctions.mergeRecords !== utils.defaultMergeFunctions.mergeRecords)) { return utils.defaultMergeFunctions.mergeRecords(values, utils, meta); } return result; } /** * Merge arrays. * * @param values - The arrays. */ function mergeArrays(values, utils, meta) { const result = utils.mergeFunctions.mergeArrays(values, utils, meta); if (result === actions.defaultMerge || (utils.useImplicitDefaultMerging && result === undefined && utils.mergeFunctions.mergeArrays !== utils.defaultMergeFunctions.mergeArrays)) { return utils.defaultMergeFunctions.mergeArrays(values); } return result; } /** * Merge sets. * * @param values - The sets. */ function mergeSets(values, utils, meta) { const result = utils.mergeFunctions.mergeSets(values, utils, meta); if (result === actions.defaultMerge || (utils.useImplicitDefaultMerging && result === undefined && utils.mergeFunctions.mergeSets !== utils.defaultMergeFunctions.mergeSets)) { return utils.defaultMergeFunctions.mergeSets(values); } return result; } /** * Merge maps. * * @param values - The maps. */ function mergeMaps(values, utils, meta) { const result = utils.mergeFunctions.mergeMaps(values, utils, meta); if (result === actions.defaultMerge || (utils.useImplicitDefaultMerging && result === undefined && utils.mergeFunctions.mergeMaps !== utils.defaultMergeFunctions.mergeMaps)) { return utils.defaultMergeFunctions.mergeMaps(values); } return result; } /** * Merge other things. * * @param values - The other things. */ function mergeOthers(values, utils, meta) { const result = utils.mergeFunctions.mergeOthers(values, utils, meta); if (result === actions.defaultMerge || (utils.useImplicitDefaultMerging && result === undefined && utils.mergeFunctions.mergeOthers !== utils.defaultMergeFunctions.mergeOthers)) { return utils.defaultMergeFunctions.mergeOthers(values); } return result; } /** * The default strategy to merge records. * * @param values - The records. */ function defaultMergeRecords(values, utils, meta) { const result = {}; /* eslint-disable functional/no-loop-statement, functional/no-conditional-statement -- using a loop here is more performant. */ for (const key of getKeys(values)) { const propValues = []; for (const value of values) { if (objectHasProperty(value, key)) { propValues.push(value[key]); } } // assert(propValues.length > 0); const updatedMeta = utils.metaDataUpdater(meta, { key, parents: values, }); const propertyResult = mergeUnknowns(propValues, utils, updatedMeta); if (propertyResult === actions.skip) { continue; } result[key] = propertyResult; } /* eslint-enable functional/no-loop-statement, functional/no-conditional-statement */ return result; } /** * The default strategy to merge arrays. * * @param values - The arrays. */ function defaultMergeArrays(values) { return values.flat(); } /** * The default strategy to merge sets. * * @param values - The sets. */ function defaultMergeSets(values) { return new Set(getIterableOfIterables(values)); } /** * The default strategy to merge maps. * * @param values - The maps. */ function defaultMergeMaps(values) { return new Map(getIterableOfIterables(values)); } /** * Get the last value in the given array. */ function leaf(values) { return values[values.length - 1]; } exports.deepmerge = deepmerge; exports.deepmergeCustom = deepmergeCustom;