@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 CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@revolugo/common",
3
- "version": "6.10.7-beta.0",
3
+ "version": "6.10.7-beta.2",
4
4
  "private": false,
5
5
  "description": "Revolugo common",
6
6
  "author": "Revolugo",
@@ -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[],