Skip to content

Commit a13db50

Browse files
committed
chore: improve merging & return types
1 parent 9807c0b commit a13db50

File tree

4 files changed

+58
-19
lines changed

4 files changed

+58
-19
lines changed

.vscode/dictionary.txt

+1
Original file line numberDiff line numberDiff line change
@@ -17,6 +17,7 @@ dtsx
1717
entrypoints
1818
heroicons
1919
lockb
20+
Mergeable
2021
openweb
2122
outdir
2223
pausable

src/index.ts

+1
Original file line numberDiff line numberDiff line change
@@ -34,3 +34,4 @@ export async function loadConfig<T>({ name, cwd, defaultConfig }: Config<T>): Pr
3434
}
3535

3636
export * from './types'
37+
export * from './utils'

src/types.ts

+20
Original file line numberDiff line numberDiff line change
@@ -10,3 +10,23 @@ export interface Config<T> {
1010
cwd?: string
1111
defaultConfig: T
1212
}
13+
14+
export type SimplifyDeep<T> = T extends object
15+
? { [P in keyof T]: SimplifyDeep<T[P]> }
16+
: T
17+
18+
export type DeepMerge<T, S> = {
19+
[P in keyof (T & S)]: P extends keyof T
20+
? P extends keyof S
21+
? DeepMergeable<T[P], S[P]>
22+
: T[P]
23+
: P extends keyof S
24+
? S[P]
25+
: never
26+
}
27+
28+
export type DeepMergeable<T, S> = T extends object
29+
? S extends object
30+
? DeepMerge<T, S>
31+
: S
32+
: S

src/utils.ts

+36-19
Original file line numberDiff line numberDiff line change
@@ -1,39 +1,56 @@
1+
import type { DeepMerge, SimplifyDeep } from './types'
2+
13
/**
2-
* Deep merge objects or arrays
4+
* Deep Merge
5+
*
6+
* Merges arrays if both configs are arrays, otherwise does object deep merge.
37
*
4-
* @param target - The target object or array
5-
* @param sources - The source objects or arrays
6-
* @returns The merged result
8+
* @param target - The target object.
9+
* @param sources - The source objects.
10+
* @returns The merged object.
11+
* @example ```ts
12+
* deepMerge({ foo: 'bar' }, { bar: 'baz' })
13+
* deepMerge([{ foo: 'bar' }], [{ bar: 'baz' }])
14+
* deepMerge({ foo: 'bar' }, [{ foo: 'baz' }])
15+
* ```
716
*/
8-
export function deepMerge<T>(target: T, ...sources: Partial<T>[]): T {
17+
export function deepMerge<T, S>(target: T, ...sources: S[]): T extends object
18+
? S extends any[]
19+
? S
20+
: S extends object
21+
? SimplifyDeep<DeepMerge<T, S>>
22+
: T
23+
: T extends any[]
24+
? S extends any[]
25+
? T
26+
: T
27+
: T {
928
if (!sources.length)
10-
return target
29+
return target as any
1130

1231
const source = sources.shift()
1332
if (!source)
14-
return target
33+
return target as any
34+
35+
if (Array.isArray(source) !== Array.isArray(target)
36+
|| isObject(source) !== isObject(target)) {
37+
return source as any
38+
}
1539

1640
if (Array.isArray(target) && Array.isArray(source)) {
17-
// If both are arrays, concatenate them
18-
return [...target, ...source] as T
41+
return [...target, ...source] as any
1942
}
2043

2144
if (isObject(target) && isObject(source)) {
2245
for (const key in source) {
2346
if (Object.prototype.hasOwnProperty.call(source, key)) {
2447
const sourceValue = source[key]
25-
if (Array.isArray(sourceValue) && Array.isArray((target as any)[key])) {
26-
// Merge arrays within objects
27-
(target as any)[key] = [...(target as any)[key], ...sourceValue]
28-
}
29-
else if (isObject(sourceValue) && isObject((target as any)[key])) {
30-
// Deep merge nested objects
31-
(target as any)[key] = deepMerge((target as any)[key], sourceValue)
32-
}
33-
else {
34-
// Replace primitive values and objects/arrays that don't match in type
48+
if (!Object.prototype.hasOwnProperty.call(target, key)) {
3549
(target as any)[key] = sourceValue
50+
continue
3651
}
52+
53+
(target as any)[key] = deepMerge((target as any)[key], sourceValue)
3754
}
3855
}
3956
}

0 commit comments

Comments
 (0)