-
Notifications
You must be signed in to change notification settings - Fork 0
Expand file tree
/
Copy pathmutate.ts
More file actions
128 lines (118 loc) · 3.77 KB
/
mutate.ts
File metadata and controls
128 lines (118 loc) · 3.77 KB
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
/**
* @fileoverview Object mutation helpers — `merge` (deep recursive),
* `objectAssign` (alias for native), `objectFreeze` (alias for native).
*
* `merge` includes infinite-loop detection via `LOOP_SENTINEL` because
* `__proto__` and self-referential graphs would otherwise blow the
* stack on a recursive descent.
*/
import { LOOP_SENTINEL } from '../constants/sentinels'
import { isArray } from '../arrays/predicates'
import { ErrorCtor } from '../primordials/error'
import { ReflectOwnKeys } from '../primordials/reflect'
import { isObject } from './predicates'
/**
* Deep merge source object into target object.
*
* Recursively merges properties from `source` into `target`. Arrays in source
* completely replace arrays in target (no element-wise merging). Objects are
* merged recursively. Includes infinite loop detection for safety.
*
* @param target - The object to merge into (will be modified)
* @param source - The object to merge from
* @returns The modified target object
*
* @example
* ```ts
* merge(
* { config: { api: 'v1', timeout: 1000 } },
* { config: { api: 'v2', retries: 3 } }
* )
* // { config: { api: 'v2', timeout: 1000, retries: 3 } }
* ```
*
* @example
* ```ts
* // Arrays are replaced, not merged
* merge({ arr: [1, 2] }, { arr: [3] }) // { arr: [3] }
* ```
*/
/*@__NO_SIDE_EFFECTS__*/
export function merge<T extends object, U extends object>(
target: T,
source: U,
): T & U {
if (!isObject(target) || !isObject(source)) {
return target as T & U
}
const queue: Array<[unknown, unknown]> = [[target, source]]
let pos = 0
let { length: queueLength } = queue
while (pos < queueLength) {
if (pos === LOOP_SENTINEL) {
throw new ErrorCtor('Detected infinite loop in object crawl of merge')
}
const { 0: currentTarget, 1: currentSource } = queue[pos++] as [
Record<PropertyKey, unknown>,
Record<PropertyKey, unknown>,
]
if (!currentSource || !currentTarget) {
continue
}
const isSourceArray = isArray(currentSource)
const isTargetArray = isArray(currentTarget)
// Skip array merging - arrays in source replace arrays in target
if (isSourceArray || isTargetArray) {
continue
}
const keys = ReflectOwnKeys(currentSource as object)
for (let i = 0, { length } = keys; i < length; i += 1) {
const key = keys[i] as PropertyKey
const srcVal = currentSource[key]
const targetVal = currentTarget[key]
if (isArray(srcVal)) {
// Replace arrays entirely
currentTarget[key] = srcVal
} else if (isObject(srcVal)) {
if (isObject(targetVal) && !isArray(targetVal)) {
queue[queueLength++] = [targetVal, srcVal]
} else {
currentTarget[key] = srcVal
}
} else {
currentTarget[key] = srcVal
}
}
}
return target as T & U
}
// IMPORTANT: Do not use destructuring here - use direct assignment instead.
// tsgo has a bug that incorrectly transpiles destructured exports, resulting in
// `exports.SomeName = void 0;` which causes runtime errors.
// See: https://github.com/SocketDev/socket-packageurl-js/issues/3
/**
* Alias for native `Object.assign`.
*
* Copies all enumerable own properties from one or more source objects
* to a target object and returns the modified target object.
*
* @example
* ```ts
* objectAssign({ a: 1 }, { b: 2 }) // { a: 1, b: 2 }
* ```
*/
export const objectAssign = Object.assign
/**
* Alias for native `Object.freeze`.
*
* Freezes an object, preventing new properties from being added and existing
* properties from being removed or modified. Makes the object immutable.
*
* @example
* ```ts
* const obj = { a: 1 }
* objectFreeze(obj)
* obj.a = 2 // Silently fails (or throws in strict mode)
* ```
*/
export const objectFreeze = Object.freeze