@revolugo/common 6.10.7-beta.0 → 6.10.7-beta.2
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/package.json +1 -1
- package/src/utils/index.ts +1 -0
- package/src/utils/merge.ts +140 -0
- package/src/utils/object-tools.ts +0 -42
package/package.json
CHANGED
package/src/utils/index.ts
CHANGED
|
@@ -13,6 +13,7 @@ export * from './is-empty.ts'
|
|
|
13
13
|
export * from './is-equal.ts'
|
|
14
14
|
export * from './lang-default-fallbacks.ts'
|
|
15
15
|
export * from './math.ts'
|
|
16
|
+
export * from './merge.ts'
|
|
16
17
|
export * from './numbers.ts'
|
|
17
18
|
export * from './object-tools.ts'
|
|
18
19
|
export * from './poller.ts'
|
|
@@ -0,0 +1,140 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Checks if a value is a plain object (created via {} or new Object).
|
|
3
|
+
* Lodash treats only plain objects as recursively mergable.
|
|
4
|
+
*/
|
|
5
|
+
function isPlainObject(value: unknown): value is Record<string, unknown> {
|
|
6
|
+
if (value === null || typeof value !== 'object') {
|
|
7
|
+
return false
|
|
8
|
+
}
|
|
9
|
+
const proto = Object.getPrototypeOf(value)
|
|
10
|
+
return proto === Object.prototype || proto === null
|
|
11
|
+
}
|
|
12
|
+
|
|
13
|
+
/**
|
|
14
|
+
* Deep clones an object, handling arrays and plain objects
|
|
15
|
+
*/
|
|
16
|
+
function cloneDeep<T>(value: T, seen = new WeakSet()): T {
|
|
17
|
+
if (value === null || value === undefined || typeof value !== 'object') {
|
|
18
|
+
return value
|
|
19
|
+
}
|
|
20
|
+
|
|
21
|
+
// Handle circular references
|
|
22
|
+
if (seen.has(value as object)) {
|
|
23
|
+
return value
|
|
24
|
+
}
|
|
25
|
+
seen.add(value as object)
|
|
26
|
+
|
|
27
|
+
if (Array.isArray(value)) {
|
|
28
|
+
return value.map(item => cloneDeep(item, seen)) as T
|
|
29
|
+
}
|
|
30
|
+
|
|
31
|
+
if (isPlainObject(value)) {
|
|
32
|
+
const cloned: Record<string, unknown> = {}
|
|
33
|
+
for (const key in value) {
|
|
34
|
+
if (Object.hasOwn(value, key)) {
|
|
35
|
+
cloned[key] = cloneDeep(value[key], seen)
|
|
36
|
+
}
|
|
37
|
+
}
|
|
38
|
+
return cloned as T
|
|
39
|
+
}
|
|
40
|
+
|
|
41
|
+
// For other object types (Date, RegExp, etc.), return as-is
|
|
42
|
+
return value
|
|
43
|
+
}
|
|
44
|
+
|
|
45
|
+
/**
|
|
46
|
+
* Recursively merges source into target, following lodash.merge semantics.
|
|
47
|
+
*/
|
|
48
|
+
function mergeObject(
|
|
49
|
+
target: Record<string, unknown>,
|
|
50
|
+
source: Record<string, unknown>,
|
|
51
|
+
seen = new WeakSet<object>(),
|
|
52
|
+
): void {
|
|
53
|
+
if (!isPlainObject(source) || !isPlainObject(target)) {
|
|
54
|
+
return
|
|
55
|
+
}
|
|
56
|
+
|
|
57
|
+
// Circular reference guard
|
|
58
|
+
if (source === target || seen.has(source)) {
|
|
59
|
+
return
|
|
60
|
+
}
|
|
61
|
+
seen.add(source)
|
|
62
|
+
|
|
63
|
+
// Lodash merges own + inherited enumerable string-keyed props
|
|
64
|
+
for (const key in source) {
|
|
65
|
+
if (Object.hasOwn(source, key)) {
|
|
66
|
+
const srcVal = source[key]
|
|
67
|
+
const tgtVal = target[key]
|
|
68
|
+
|
|
69
|
+
if (isPlainObject(srcVal) && isPlainObject(tgtVal)) {
|
|
70
|
+
mergeObject(tgtVal as Record<string, unknown>, srcVal, seen)
|
|
71
|
+
} else if (Array.isArray(srcVal) && Array.isArray(tgtVal)) {
|
|
72
|
+
// eslint-disable-next-line @typescript-eslint/no-use-before-define
|
|
73
|
+
target[key] = mergeArrays(tgtVal, srcVal, seen)
|
|
74
|
+
} else {
|
|
75
|
+
// Override with source value - this is the key fix
|
|
76
|
+
target[key] = cloneDeep(srcVal, seen)
|
|
77
|
+
}
|
|
78
|
+
}
|
|
79
|
+
}
|
|
80
|
+
}
|
|
81
|
+
|
|
82
|
+
/**
|
|
83
|
+
* Merges two arrays by index, lodash style.
|
|
84
|
+
* - Recursively merges plain objects/arrays
|
|
85
|
+
* - Overwrites with source otherwise
|
|
86
|
+
*/
|
|
87
|
+
function mergeArrays(
|
|
88
|
+
target: unknown[],
|
|
89
|
+
source: unknown[],
|
|
90
|
+
seen: WeakSet<object>,
|
|
91
|
+
): unknown[] {
|
|
92
|
+
const result = target.slice()
|
|
93
|
+
|
|
94
|
+
for (let i = 0; i < source.length; i++) {
|
|
95
|
+
const srcVal = source[i]
|
|
96
|
+
const tgtVal = result[i]
|
|
97
|
+
|
|
98
|
+
if (isPlainObject(srcVal) && isPlainObject(tgtVal)) {
|
|
99
|
+
const merged: Record<string, unknown> = { ...tgtVal }
|
|
100
|
+
mergeObject(merged, srcVal, seen)
|
|
101
|
+
result[i] = merged
|
|
102
|
+
} else if (Array.isArray(srcVal) && Array.isArray(tgtVal)) {
|
|
103
|
+
result[i] = mergeArrays(tgtVal, srcVal, seen)
|
|
104
|
+
} else {
|
|
105
|
+
result[i] = cloneDeep(srcVal, seen)
|
|
106
|
+
}
|
|
107
|
+
}
|
|
108
|
+
|
|
109
|
+
return result
|
|
110
|
+
}
|
|
111
|
+
|
|
112
|
+
/**
|
|
113
|
+
* A drop-in replacement for lodash-es merge.
|
|
114
|
+
*
|
|
115
|
+
* - Recursively merges own and inherited enumerable string-keyed properties.
|
|
116
|
+
* - Arrays and plain objects are merged by index/key.
|
|
117
|
+
* - Non-plain objects (Date, RegExp, Map, Set, custom classes, etc.)
|
|
118
|
+
* are assigned by reference (not cloned).
|
|
119
|
+
* - `undefined` does not overwrite an existing value.
|
|
120
|
+
* - Symbols and non-enumerables are ignored.
|
|
121
|
+
* - Prototype pollution is *not* prevented (matches lodash behavior).
|
|
122
|
+
*/
|
|
123
|
+
export function merge<T extends Record<string, unknown>>(
|
|
124
|
+
object: T,
|
|
125
|
+
...sources: unknown[]
|
|
126
|
+
): T {
|
|
127
|
+
if (!isPlainObject(object)) {
|
|
128
|
+
throw new TypeError('Target must be a plain object')
|
|
129
|
+
}
|
|
130
|
+
|
|
131
|
+
const seen = new WeakSet<object>()
|
|
132
|
+
|
|
133
|
+
for (const src of sources) {
|
|
134
|
+
if (src !== null && src !== undefined) {
|
|
135
|
+
mergeObject(object, src as Record<string, unknown>, seen)
|
|
136
|
+
}
|
|
137
|
+
}
|
|
138
|
+
|
|
139
|
+
return object
|
|
140
|
+
}
|
|
@@ -1,47 +1,5 @@
|
|
|
1
1
|
import { isNil } from './value-tools.ts'
|
|
2
2
|
|
|
3
|
-
// Utility type to merge T and S, allowing S to add/override properties in T
|
|
4
|
-
type Merge<T, S> = {
|
|
5
|
-
[K in keyof T | keyof S]: K extends keyof S
|
|
6
|
-
? S[K] extends object
|
|
7
|
-
? K extends keyof T
|
|
8
|
-
? T[K] extends object
|
|
9
|
-
? Merge<T[K], S[K]>
|
|
10
|
-
: S[K]
|
|
11
|
-
: S[K]
|
|
12
|
-
: S[K]
|
|
13
|
-
: K extends keyof T
|
|
14
|
-
? T[K]
|
|
15
|
-
: never
|
|
16
|
-
}
|
|
17
|
-
|
|
18
|
-
export function customMerge<T extends object, S extends object>(
|
|
19
|
-
target: T,
|
|
20
|
-
source: S,
|
|
21
|
-
): Merge<T, S> {
|
|
22
|
-
for (const key of Object.keys(source)) {
|
|
23
|
-
const sourceValue = source[key as keyof S]
|
|
24
|
-
// eslint-disable-next-line @typescript-eslint/no-explicit-any
|
|
25
|
-
const targetValue = (target as any)[key]
|
|
26
|
-
|
|
27
|
-
if (sourceValue !== undefined && sourceValue !== null) {
|
|
28
|
-
if (
|
|
29
|
-
targetValue &&
|
|
30
|
-
typeof targetValue === 'object' &&
|
|
31
|
-
!Array.isArray(targetValue) &&
|
|
32
|
-
typeof sourceValue === 'object' &&
|
|
33
|
-
!Array.isArray(sourceValue)
|
|
34
|
-
) {
|
|
35
|
-
customMerge(targetValue as object, sourceValue as object)
|
|
36
|
-
} else {
|
|
37
|
-
// eslint-disable-next-line @typescript-eslint/no-explicit-any
|
|
38
|
-
;(target as any)[key] = sourceValue
|
|
39
|
-
}
|
|
40
|
-
}
|
|
41
|
-
}
|
|
42
|
-
return target as Merge<T, S>
|
|
43
|
-
}
|
|
44
|
-
|
|
45
3
|
export function pick<T extends object, K extends keyof T>(
|
|
46
4
|
obj: T,
|
|
47
5
|
keys: readonly K[],
|