Skip to content

Commit

Permalink
chore: improve merging & return types
Browse files Browse the repository at this point in the history
  • Loading branch information
chrisbbreuer committed Nov 18, 2024
1 parent 9807c0b commit a13db50
Show file tree
Hide file tree
Showing 4 changed files with 58 additions and 19 deletions.
1 change: 1 addition & 0 deletions .vscode/dictionary.txt
Original file line number Diff line number Diff line change
Expand Up @@ -17,6 +17,7 @@ dtsx
entrypoints
heroicons
lockb
Mergeable
openweb
outdir
pausable
Expand Down
1 change: 1 addition & 0 deletions src/index.ts
Original file line number Diff line number Diff line change
Expand Up @@ -34,3 +34,4 @@ export async function loadConfig<T>({ name, cwd, defaultConfig }: Config<T>): Pr
}

export * from './types'
export * from './utils'
20 changes: 20 additions & 0 deletions src/types.ts
Original file line number Diff line number Diff line change
Expand Up @@ -10,3 +10,23 @@ export interface Config<T> {
cwd?: string
defaultConfig: T
}

export type SimplifyDeep<T> = T extends object
? { [P in keyof T]: SimplifyDeep<T[P]> }
: T

export type DeepMerge<T, S> = {
[P in keyof (T & S)]: P extends keyof T
? P extends keyof S
? DeepMergeable<T[P], S[P]>
: T[P]
: P extends keyof S
? S[P]
: never
}

export type DeepMergeable<T, S> = T extends object
? S extends object
? DeepMerge<T, S>
: S
: S
55 changes: 36 additions & 19 deletions src/utils.ts
Original file line number Diff line number Diff line change
@@ -1,39 +1,56 @@
import type { DeepMerge, SimplifyDeep } from './types'

/**
* Deep merge objects or arrays
* Deep Merge
*
* Merges arrays if both configs are arrays, otherwise does object deep merge.
*
* @param target - The target object or array
* @param sources - The source objects or arrays
* @returns The merged result
* @param target - The target object.
* @param sources - The source objects.
* @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>(target: T, ...sources: Partial<T>[]): T {
export function deepMerge<T, S>(target: T, ...sources: S[]): T extends object
? S extends any[]
? S
: S extends object
? SimplifyDeep<DeepMerge<T, S>>
: T
: T extends any[]
? S extends any[]
? T
: T
: T {
if (!sources.length)
return target
return target as any

const source = sources.shift()
if (!source)
return target
return target as any

if (Array.isArray(source) !== Array.isArray(target)
|| isObject(source) !== isObject(target)) {
return source as any
}

if (Array.isArray(target) && Array.isArray(source)) {
// If both are arrays, concatenate them
return [...target, ...source] as T
return [...target, ...source] as any
}

if (isObject(target) && isObject(source)) {
for (const key in source) {
if (Object.prototype.hasOwnProperty.call(source, key)) {
const sourceValue = source[key]
if (Array.isArray(sourceValue) && Array.isArray((target as any)[key])) {
// Merge arrays within objects
(target as any)[key] = [...(target as any)[key], ...sourceValue]
}
else if (isObject(sourceValue) && isObject((target as any)[key])) {
// Deep merge nested objects
(target as any)[key] = deepMerge((target as any)[key], sourceValue)
}
else {
// Replace primitive values and objects/arrays that don't match in type
if (!Object.prototype.hasOwnProperty.call(target, key)) {
(target as any)[key] = sourceValue
continue
}

(target as any)[key] = deepMerge((target as any)[key], sourceValue)
}
}
}
Expand Down

0 comments on commit a13db50

Please sign in to comment.