import { filterInPlace } from "./_utils.ts";
const { hasOwn } = Object;
export function deepMerge< T extends Record<PropertyKey, unknown>,>( record: Partial<Readonly<T>>, other: Partial<Readonly<T>>, options?: Readonly<DeepMergeOptions>,): T;
export function deepMerge< T extends Record<PropertyKey, unknown>, U extends Record<PropertyKey, unknown>, Options extends DeepMergeOptions,>( record: Readonly<T>, other: Readonly<U>, options?: Readonly<Options>,): DeepMerge<T, U, Options>;
export function deepMerge< T extends Record<PropertyKey, unknown>, U extends Record<PropertyKey, unknown>, Options extends DeepMergeOptions = { arrays: "merge"; sets: "merge"; maps: "merge"; },>( record: Readonly<T>, other: Readonly<U>, options?: Readonly<Options>,): DeepMerge<T, U, Options> { return deepMergeInternal(record, other, new Set(), options);}
function deepMergeInternal< T extends Record<PropertyKey, unknown>, U extends Record<PropertyKey, unknown>, Options extends DeepMergeOptions = { arrays: "merge"; sets: "merge"; maps: "merge"; },>( record: Readonly<T>, other: Readonly<U>, seen: Set<NonNullable<object>>, options?: Readonly<Options>,) { type Result = DeepMerge<T, U, Options>; const result: Partial<Result> = {};
const keys = new Set([ ...getKeys(record), ...getKeys(other), ]) as Set<keyof Result>;
for (const key of keys) { if (key === "__proto__") { continue; }
type ResultMember = Result[typeof key];
const a = record[key] as ResultMember;
if (!hasOwn(other, key)) { result[key] = a;
continue; }
const b = other[key] as ResultMember;
if ( isNonNullObject(a) && isNonNullObject(b) && !seen.has(a) && !seen.has(b) ) { seen.add(a); seen.add(b); result[key] = mergeObjects(a, b, seen, options) as ResultMember;
continue; }
result[key] = b; }
return result as Result;}
function mergeObjects( left: Readonly<NonNullable<object>>, right: Readonly<NonNullable<object>>, seen: Set<NonNullable<object>>, options: Readonly<DeepMergeOptions> = { arrays: "merge", sets: "merge", maps: "merge", },): Readonly<NonNullable<object>> { if (isMergeable(left) && isMergeable(right)) { return deepMergeInternal(left, right, seen, options); }
if (isIterable(left) && isIterable(right)) { if ((Array.isArray(left)) && (Array.isArray(right))) { if (options.arrays === "merge") { return left.concat(right); }
return right; }
if ((left instanceof Map) && (right instanceof Map)) { if (options.maps === "merge") { return new Map([ ...left, ...right, ]); }
return right; }
if ((left instanceof Set) && (right instanceof Set)) { if (options.sets === "merge") { return new Set([ ...left, ...right, ]); }
return right; } }
return right;}
function isMergeable( value: NonNullable<object>,): value is Record<PropertyKey, unknown> { return Object.getPrototypeOf(value) === Object.prototype;}
function isIterable( value: NonNullable<object>,): value is Iterable<unknown> { return typeof (value as Iterable<unknown>)[Symbol.iterator] === "function";}
function isNonNullObject(value: unknown): value is NonNullable<object> { return value !== null && typeof value === "object";}
function getKeys<T extends object>(record: T): Array<keyof T> { const ret = Object.getOwnPropertySymbols(record) as Array<keyof T>; filterInPlace( ret, (key) => Object.prototype.propertyIsEnumerable.call(record, key), ); ret.push(...(Object.keys(record) as Array<keyof T>));
return ret;}
export type MergingStrategy = "replace" | "merge";
export type DeepMergeOptions = { arrays?: MergingStrategy; maps?: MergingStrategy; sets?: MergingStrategy;};
type ExpandRecursively<T> = T extends Record<PropertyKey, unknown> ? T extends infer O ? { [K in keyof O]: ExpandRecursively<O[K]> } : never : T;
type PartialByType<T, U> = { [K in keyof T as T[K] extends U ? K : never]: T[K];};
type SetValueType<T> = T extends Set<infer V> ? V : never;
type MergeAllSets< T, U, X = PartialByType<T, Set<unknown>>, Y = PartialByType<U, Set<unknown>>, Z = { [K in keyof X & keyof Y]: Set<SetValueType<X[K]> | SetValueType<Y[K]>>; },> = Z;
type ArrayValueType<T> = T extends Array<infer V> ? V : never;
type MergeAllArrays< T, U, X = PartialByType<T, Array<unknown>>, Y = PartialByType<U, Array<unknown>>, Z = { [K in keyof X & keyof Y]: Array< ArrayValueType<X[K]> | ArrayValueType<Y[K]> >; },> = Z;
type MapKeyType<T> = T extends Map<infer K, unknown> ? K : never;
type MapValueType<T> = T extends Map<unknown, infer V> ? V : never;
type MergeAllMaps< T, U, X = PartialByType<T, Map<unknown, unknown>>, Y = PartialByType<U, Map<unknown, unknown>>, Z = { [K in keyof X & keyof Y]: Map< MapKeyType<X[K]> | MapKeyType<Y[K]>, MapValueType<X[K]> | MapValueType<Y[K]> >; },> = Z;
type MergeAllRecords< T, U, Options, X = PartialByType<T, Record<PropertyKey, unknown>>, Y = PartialByType<U, Record<PropertyKey, unknown>>, Z = { [K in keyof X & keyof Y]: DeepMerge<X[K], Y[K], Options>; },> = Z;
type OmitComplexes<T> = Omit< T, keyof PartialByType< T, | Map<unknown, unknown> | Set<unknown> | Array<unknown> | Record<PropertyKey, unknown> >>;
type ObjectXorKeys< T, U, X = Omit<T, keyof U> & Omit<U, keyof T>, Y = { [K in keyof X]: X[K] },> = Y;
type MergeRightOmitComplexes< T, U, X = ObjectXorKeys<T, U> & OmitComplexes<{ [K in keyof U]: U[K] }>,> = X;
type Merge< T, U, Options, X = & MergeRightOmitComplexes<T, U> & MergeAllRecords<T, U, Options> & (Options extends { sets: "replace" } ? PartialByType<U, Set<unknown>> : MergeAllSets<T, U>) & (Options extends { arrays: "replace" } ? PartialByType<U, Array<unknown>> : MergeAllArrays<T, U>) & (Options extends { maps: "replace" } ? PartialByType<U, Map<unknown, unknown>> : MergeAllMaps<T, U>),> = ExpandRecursively<X>;
export type DeepMerge< T, U, Options = Record<string, MergingStrategy>,> = [T, U] extends [Record<PropertyKey, unknown>, Record<PropertyKey, unknown>] ? Merge<T, U, Options> : T | U;