diff --git a/src/utils.ts b/src/utils.ts index d64c896..a91757e 100644 --- a/src/utils.ts +++ b/src/utils.ts @@ -3,61 +3,67 @@ import type { DeepMerge, SimplifyDeep } from './types' /** * Deep Merge * - * Merges arrays if both configs are arrays, otherwise does object deep merge. + * Merges two objects or arrays deeply. * - * @param target - The target object. - * @param sources - The source objects. + * @param target - The target object (default config). + * @param source - The source objects (loaded configs that should override defaults). * @returns The merged object. - * @example ```ts - * deepMerge({ foo: 'bar' }, { bar: 'baz' }) - * deepMerge([{ foo: 'bar' }], [{ bar: 'baz' }]) - * deepMerge({ foo: 'bar' }, [{ foo: 'baz' }]) - * ``` */ -export function deepMerge<T, S>(target: T, ...sources: S[]): T extends object +export function deepMerge<T, S>(target: T, source: S): T extends any[] ? S extends any[] - ? S - : S extends object - ? SimplifyDeep<DeepMerge<T, S>> - : T - : T extends any[] + ? Array<SimplifyDeep<DeepMerge<T[number], S[number]>>> + : S + : T extends object ? S extends any[] - ? T - : T + ? S + : S extends object + ? SimplifyDeep<DeepMerge<T, S>> + : T : T { - if (!sources.length) - return target as any + // If source is an array and target isn't, return source + if (Array.isArray(source) && !Array.isArray(target)) { + return source as any + } - const source = sources.shift() - if (!source) - return target as any + // If both are arrays, merge their contents + if (Array.isArray(source) && Array.isArray(target)) { + return source.map((sourceItem, index) => { + const targetItem = target[index] + if (isObject(sourceItem) && isObject(targetItem)) { + return deepMerge(targetItem, sourceItem) + } + return sourceItem + }) as any + } - if (Array.isArray(source) !== Array.isArray(target) - || isObject(source) !== isObject(target)) { + // Handle non-objects (primitives) + if (!isObject(source) || !isObject(target)) { return source as any } - if (Array.isArray(target) && Array.isArray(source)) { - return [...target, ...source] as any - } + // Handle objects + const merged = { ...target } as any - if (isObject(target) && isObject(source)) { - for (const key in source) { - if (Object.prototype.hasOwnProperty.call(source, key)) { - const sourceValue = source[key] - if (!Object.prototype.hasOwnProperty.call(target, key)) { - (target as any)[key] = sourceValue - continue - } + for (const key in source) { + if (Object.prototype.hasOwnProperty.call(source, key)) { + const sourceValue = source[key] + const targetValue = merged[key] - (target as any)[key] = deepMerge((target as any)[key], sourceValue) + if (sourceValue === null || sourceValue === undefined) { + merged[key] = sourceValue + } + else if (isObject(sourceValue) && isObject(targetValue)) { + merged[key] = deepMerge(targetValue, sourceValue) + } + else { + merged[key] = sourceValue } } } - return deepMerge(target, ...sources) + return merged } function isObject(item: unknown): item is Record<string, unknown> { - return (item && typeof item === 'object' && !Array.isArray(item)) as boolean + return Boolean(item && typeof item === 'object' && !Array.isArray(item)) }