UNPKG

@fastify/deepmerge

Version:

Merges the enumerable properties of two or more objects deeply.

197 lines (164 loc) 6.24 kB
type DeepMergeFn = <T1, T2>(target: T1, source: T2) => DeepMerge<T1, T2> type DeepMergeAllFn = <T extends Array<any>>(...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 = <T1, T2>(target: T1, source: T2) => DeepMergeDefined<T1, T2> type DeepMergeAllDefinedFn = <T extends Array<any>>(...targets: T) => DeepMergeAllDefined<{}, T> type Primitive = | null | undefined | string | number | boolean | symbol | bigint type BuiltIns = Primitive | Date | RegExp type MergeArrays<T, U> = T extends readonly any[] ? U extends readonly any[] ? [...T, ...U] : never : never type DifferenceKeys< T, U, T0 = Omit<T, keyof U> & Omit<U, keyof T>, T1 = { [K in keyof T0]: T0[K] } > = T1 type IntersectionKeys<T, U> = Omit<T | U, keyof DifferenceKeys<T, U>> type DeepMergeHelper< T, U, T0 = DifferenceKeys<T, U> & { [K in keyof IntersectionKeys<T, U>]: DeepMerge<K extends keyof T ? T[K] : never, K extends keyof U ? U[K] : never> }, T1 = { [K in keyof T0]: T0[K] } > = T1 type DeepMerge<T, U> = U extends BuiltIns ? U : [T, U] extends [readonly any[], readonly any[]] ? MergeArrays<T, U> : [T, U] extends [{ [key: string]: unknown }, { [key: string]: unknown }] ? DeepMergeHelper<T, U> : U /** * For onlyDefinedProperties: true mode. * When merging, if source value could be undefined, preserve target's type. * This ensures that merging Partial<T> into T returns T, not Partial<T>. */ type ExcludeUndefined<T> = 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> = 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<T, U> = keyof T & keyof U /** * Get keys only in T (not in U) */ type OnlyTKeys<T, U> = Exclude<keyof T, keyof U> /** * Get keys only in U (not in T) */ type OnlyUKeys<T, U> = Exclude<keyof U, keyof T> /** * 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<T, U> = // If U could be undefined [undefined] extends [U] // Both T and ExcludeUndefined<U> are mergeable objects - merge them ? [IsMergeableObject<T>, IsMergeableObject<ExcludeUndefined<U>>] extends [true, true] ? DeepMergeDefinedHelper<T, ExcludeUndefined<U>> // Otherwise use target's type (preserving non-undefined) : T // U cannot be undefined - normal deep merge : DeepMergeDefined<T, U> /** * Helper for onlyDefinedProperties mode. * For intersection keys: preserve required-ness from target when source may be undefined. */ type DeepMergeDefinedHelper<T, U> = { // Keys only in T: preserve from target [K in OnlyTKeys<T, U>]: T[K] } & { // Keys in both: merge with defined-property semantics [K in CommonKeys<T, U>]: DeepMergeDefinedProperty<T[K], U[K]> } & { // Keys only in U: include only if not potentially undefined [K in OnlyUKeys<T, U> as [undefined] extends [U[K]] ? never : K]: U[K] } extends infer O ? { [K in keyof O]: O[K] } : never type DeepMergeDefined<T, U> = U extends BuiltIns ? [undefined] extends [U] ? [undefined] extends [T] ? T : ExcludeUndefined<U> | T : U : [T, U] extends [readonly any[], readonly any[]] ? MergeArrays<T, U> : [IsMergeableObject<T>, IsMergeableObject<U>] extends [true, true] ? DeepMergeDefinedHelper<T, U> : [undefined] extends [U] ? [undefined] extends [T] ? T : ExcludeUndefined<U> | T : U type DeepMergeAllDefined<R, T> = First<T> extends never ? R : DeepMergeAllDefined<DeepMergeDefined<R, First<T>>, Rest<T>> // eslint-disable-next-line @typescript-eslint/no-unused-vars type First<T> = T extends [infer _I, ...infer _Rest] ? _I : never // eslint-disable-next-line @typescript-eslint/no-unused-vars type Rest<T> = T extends [infer _I, ...infer _Rest] ? _Rest : never type DeepMergeAll<R, T> = First<T> extends never ? R : DeepMergeAll<DeepMerge<R, First<T>>, Rest<T>> 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