type DeepMergeFn = (target: T1, source: T2) => DeepMerge type DeepMergeAllFn = >(...targets: T) => DeepMergeAll<{}, T> /** * Merge function that preserves required properties from target when source may have undefined. * Used when onlyDefinedProperties: true - undefined values in source don't override target. */ type DeepMergeDefinedFn = (target: T1, source: T2) => DeepMergeDefined type DeepMergeAllDefinedFn = >(...targets: T) => DeepMergeAllDefined<{}, T> type Primitive = | null | undefined | string | number | boolean | symbol | bigint type BuiltIns = Primitive | Date | RegExp type MergeArrays = T extends readonly any[] ? U extends readonly any[] ? [...T, ...U] : never : never type DifferenceKeys< T, U, T0 = Omit & Omit, T1 = { [K in keyof T0]: T0[K] } > = T1 type IntersectionKeys = Omit> type DeepMergeHelper< T, U, T0 = DifferenceKeys & { [K in keyof IntersectionKeys]: DeepMerge }, T1 = { [K in keyof T0]: T0[K] } > = T1 type DeepMerge = U extends BuiltIns ? U : [T, U] extends [readonly any[], readonly any[]] ? MergeArrays : [T, U] extends [{ [key: string]: unknown }, { [key: string]: unknown }] ? DeepMergeHelper : U /** * For onlyDefinedProperties: true mode. * When merging, if source value could be undefined, preserve target's type. * This ensures that merging Partial into T returns T, not Partial. */ type ExcludeUndefined = T extends undefined ? never : T /** * Check if a type is a mergeable object (not array, not primitive, not builtin, not function) * Using 'object' check which works with Partial types (unlike index signatures) */ type IsMergeableObject = T extends BuiltIns ? false : T extends readonly any[] ? false : T extends (...args: any[]) => any ? false : T extends object ? true : false /** * Get keys that exist in both T and U */ type CommonKeys = keyof T & keyof U /** * Get keys only in T (not in U) */ type OnlyTKeys = Exclude /** * Get keys only in U (not in T) */ type OnlyUKeys = Exclude /** * Merge a single property with onlyDefinedProperties semantics: * - If source could be undefined, recursively merge but preserve target's definedness * - If source cannot be undefined, normal merge applies */ type DeepMergeDefinedProperty = // If U could be undefined [undefined] extends [U] // Both T and ExcludeUndefined are mergeable objects - merge them ? [IsMergeableObject, IsMergeableObject>] extends [true, true] ? DeepMergeDefinedHelper> // Otherwise use target's type (preserving non-undefined) : T // U cannot be undefined - normal deep merge : DeepMergeDefined /** * Helper for onlyDefinedProperties mode. * For intersection keys: preserve required-ness from target when source may be undefined. */ type DeepMergeDefinedHelper = { // Keys only in T: preserve from target [K in OnlyTKeys]: T[K] } & { // Keys in both: merge with defined-property semantics [K in CommonKeys]: DeepMergeDefinedProperty } & { // Keys only in U: include only if not potentially undefined [K in OnlyUKeys as [undefined] extends [U[K]] ? never : K]: U[K] } extends infer O ? { [K in keyof O]: O[K] } : never type DeepMergeDefined = U extends BuiltIns ? [undefined] extends [U] ? [undefined] extends [T] ? T : ExcludeUndefined | T : U : [T, U] extends [readonly any[], readonly any[]] ? MergeArrays : [IsMergeableObject, IsMergeableObject] extends [true, true] ? DeepMergeDefinedHelper : [undefined] extends [U] ? [undefined] extends [T] ? T : ExcludeUndefined | T : U type DeepMergeAllDefined = First extends never ? R : DeepMergeAllDefined>, Rest> // eslint-disable-next-line @typescript-eslint/no-unused-vars type First = T extends [infer _I, ...infer _Rest] ? _I : never // eslint-disable-next-line @typescript-eslint/no-unused-vars type Rest = T extends [infer _I, ...infer _Rest] ? _Rest : never type DeepMergeAll = First extends never ? R : DeepMergeAll>, Rest> type MergeArrayFnOptions = { clone: (value: any) => any; isMergeableObject: (value: any) => boolean; deepmerge: DeepMergeFn; getKeys: (value: object) => string[]; } /** * A function responsible handling the cloning logic of the provided prototype object. * * @param value - The proto object to clone. * @returns the resulting clone (can also return the object itself if you do not want clone but replace). */ type CloneProtoObjectFn = (value: any) => any type MergeArrayFn = (options: MergeArrayFnOptions) => (target: any[], source: any[]) => any[] interface Options { cloneProtoObject?: CloneProtoObjectFn; mergeArray?: MergeArrayFn; isMergeableObject?: (value: any) => boolean; symbols?: boolean; all?: boolean; /** * If true, ignore undefined values from source and keep existing target values. * Defaults to false. */ onlyDefinedProperties?: boolean; } type DeepmergeConstructor = typeof deepmerge declare namespace deepmerge { export { Options, DeepMergeFn, DeepMergeAllFn, DeepMergeDefinedFn, DeepMergeAllDefinedFn } export const deepmerge: DeepmergeConstructor export { deepmerge as default } export const isMergeableObject: (value: any) => boolean } declare function deepmerge (options: Options & { all: true; onlyDefinedProperties: true }): DeepMergeAllDefinedFn declare function deepmerge (options: Options & { onlyDefinedProperties: true }): DeepMergeDefinedFn declare function deepmerge (options: Options & { all: true }): DeepMergeAllFn declare function deepmerge (options?: Options): DeepMergeFn export = deepmerge