@mantiq/helpers 0.0.1
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/README.md +19 -0
- package/package.json +52 -0
- package/src/Arr.ts +264 -0
- package/src/Collection.ts +804 -0
- package/src/Duration.ts +172 -0
- package/src/Http.ts +573 -0
- package/src/HttpFake.ts +361 -0
- package/src/Num.ts +187 -0
- package/src/Result.ts +196 -0
- package/src/Str.ts +457 -0
- package/src/async.ts +209 -0
- package/src/functions.ts +153 -0
- package/src/index.ts +65 -0
- package/src/is.ts +195 -0
- package/src/match.ts +78 -0
- package/src/objects.ts +180 -0
package/src/objects.ts
ADDED
|
@@ -0,0 +1,180 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Deep object utility functions.
|
|
3
|
+
*
|
|
4
|
+
* @example
|
|
5
|
+
* ```ts
|
|
6
|
+
* const merged = deepMerge({ a: 1, b: { c: 2 } }, { b: { d: 3 } })
|
|
7
|
+
* // { a: 1, b: { c: 2, d: 3 } }
|
|
8
|
+
*
|
|
9
|
+
* const picked = pick(user, ['name', 'email'])
|
|
10
|
+
* const changes = diff(oldConfig, newConfig)
|
|
11
|
+
* ```
|
|
12
|
+
*/
|
|
13
|
+
|
|
14
|
+
/** Deep clone a value (handles objects, arrays, Date, Map, Set, RegExp) */
|
|
15
|
+
export function deepClone<T>(value: T): T {
|
|
16
|
+
if (value === null || typeof value !== 'object') return value
|
|
17
|
+
if (value instanceof Date) return new Date(value.getTime()) as T
|
|
18
|
+
if (value instanceof RegExp) return new RegExp(value.source, value.flags) as T
|
|
19
|
+
if (value instanceof Map) {
|
|
20
|
+
const map = new Map()
|
|
21
|
+
for (const [k, v] of value) map.set(deepClone(k), deepClone(v))
|
|
22
|
+
return map as T
|
|
23
|
+
}
|
|
24
|
+
if (value instanceof Set) {
|
|
25
|
+
const set = new Set()
|
|
26
|
+
for (const v of value) set.add(deepClone(v))
|
|
27
|
+
return set as T
|
|
28
|
+
}
|
|
29
|
+
if (Array.isArray(value)) return value.map(deepClone) as T
|
|
30
|
+
|
|
31
|
+
const result: any = {}
|
|
32
|
+
for (const key of Object.keys(value)) {
|
|
33
|
+
result[key] = deepClone((value as any)[key])
|
|
34
|
+
}
|
|
35
|
+
return result
|
|
36
|
+
}
|
|
37
|
+
|
|
38
|
+
/** Deep merge objects (later sources override earlier) */
|
|
39
|
+
export function deepMerge<T extends Record<string, any>>(...sources: Partial<T>[]): T {
|
|
40
|
+
const result: any = {}
|
|
41
|
+
|
|
42
|
+
for (const source of sources) {
|
|
43
|
+
if (!source) continue
|
|
44
|
+
for (const key of Object.keys(source)) {
|
|
45
|
+
const sourceVal = (source as any)[key]
|
|
46
|
+
const resultVal = result[key]
|
|
47
|
+
|
|
48
|
+
if (isPlainObject(sourceVal) && isPlainObject(resultVal)) {
|
|
49
|
+
result[key] = deepMerge(resultVal, sourceVal)
|
|
50
|
+
} else {
|
|
51
|
+
result[key] = deepClone(sourceVal)
|
|
52
|
+
}
|
|
53
|
+
}
|
|
54
|
+
}
|
|
55
|
+
|
|
56
|
+
return result
|
|
57
|
+
}
|
|
58
|
+
|
|
59
|
+
/** Deep freeze an object (make immutable recursively) */
|
|
60
|
+
export function deepFreeze<T extends Record<string, any>>(obj: T): Readonly<T> {
|
|
61
|
+
Object.freeze(obj)
|
|
62
|
+
for (const key of Object.keys(obj)) {
|
|
63
|
+
const val = obj[key]
|
|
64
|
+
if (val !== null && typeof val === 'object' && !Object.isFrozen(val)) {
|
|
65
|
+
deepFreeze(val)
|
|
66
|
+
}
|
|
67
|
+
}
|
|
68
|
+
return obj
|
|
69
|
+
}
|
|
70
|
+
|
|
71
|
+
/** Deep equality check */
|
|
72
|
+
export function deepEqual(a: any, b: any): boolean {
|
|
73
|
+
if (Object.is(a, b)) return true
|
|
74
|
+
if (a === null || b === null || typeof a !== 'object' || typeof b !== 'object') return false
|
|
75
|
+
if (a instanceof Date && b instanceof Date) return a.getTime() === b.getTime()
|
|
76
|
+
if (a instanceof RegExp && b instanceof RegExp) return a.source === b.source && a.flags === b.flags
|
|
77
|
+
|
|
78
|
+
const keysA = Object.keys(a)
|
|
79
|
+
const keysB = Object.keys(b)
|
|
80
|
+
if (keysA.length !== keysB.length) return false
|
|
81
|
+
|
|
82
|
+
for (const key of keysA) {
|
|
83
|
+
if (!keysB.includes(key) || !deepEqual(a[key], b[key])) return false
|
|
84
|
+
}
|
|
85
|
+
return true
|
|
86
|
+
}
|
|
87
|
+
|
|
88
|
+
/** Pick specific keys from an object */
|
|
89
|
+
export function pick<T extends Record<string, any>, K extends keyof T>(
|
|
90
|
+
obj: T,
|
|
91
|
+
keys: K[],
|
|
92
|
+
): Pick<T, K> {
|
|
93
|
+
const result: any = {}
|
|
94
|
+
for (const key of keys) {
|
|
95
|
+
if (key in obj) result[key] = obj[key]
|
|
96
|
+
}
|
|
97
|
+
return result
|
|
98
|
+
}
|
|
99
|
+
|
|
100
|
+
/** Omit specific keys from an object */
|
|
101
|
+
export function omit<T extends Record<string, any>, K extends keyof T>(
|
|
102
|
+
obj: T,
|
|
103
|
+
keys: K[],
|
|
104
|
+
): Omit<T, K> {
|
|
105
|
+
const excluded = new Set(keys as string[])
|
|
106
|
+
const result: any = {}
|
|
107
|
+
for (const key of Object.keys(obj)) {
|
|
108
|
+
if (!excluded.has(key)) result[key] = obj[key]
|
|
109
|
+
}
|
|
110
|
+
return result
|
|
111
|
+
}
|
|
112
|
+
|
|
113
|
+
/** Get the differences between two objects */
|
|
114
|
+
export function diff(
|
|
115
|
+
original: Record<string, any>,
|
|
116
|
+
modified: Record<string, any>,
|
|
117
|
+
): Record<string, { from: any; to: any }> {
|
|
118
|
+
const changes: Record<string, { from: any; to: any }> = {}
|
|
119
|
+
const allKeys = new Set([...Object.keys(original), ...Object.keys(modified)])
|
|
120
|
+
|
|
121
|
+
for (const key of allKeys) {
|
|
122
|
+
if (!deepEqual(original[key], modified[key])) {
|
|
123
|
+
changes[key] = { from: original[key], to: modified[key] }
|
|
124
|
+
}
|
|
125
|
+
}
|
|
126
|
+
|
|
127
|
+
return changes
|
|
128
|
+
}
|
|
129
|
+
|
|
130
|
+
/** Map over an object's values */
|
|
131
|
+
export function mapValues<T, U>(
|
|
132
|
+
obj: Record<string, T>,
|
|
133
|
+
fn: (value: T, key: string) => U,
|
|
134
|
+
): Record<string, U> {
|
|
135
|
+
const result: Record<string, U> = {}
|
|
136
|
+
for (const [key, value] of Object.entries(obj)) {
|
|
137
|
+
result[key] = fn(value, key)
|
|
138
|
+
}
|
|
139
|
+
return result
|
|
140
|
+
}
|
|
141
|
+
|
|
142
|
+
/** Map over an object's keys */
|
|
143
|
+
export function mapKeys<T>(
|
|
144
|
+
obj: Record<string, T>,
|
|
145
|
+
fn: (key: string, value: T) => string,
|
|
146
|
+
): Record<string, T> {
|
|
147
|
+
const result: Record<string, T> = {}
|
|
148
|
+
for (const [key, value] of Object.entries(obj)) {
|
|
149
|
+
result[fn(key, value)] = value
|
|
150
|
+
}
|
|
151
|
+
return result
|
|
152
|
+
}
|
|
153
|
+
|
|
154
|
+
/** Filter an object's entries */
|
|
155
|
+
export function filterObject<T>(
|
|
156
|
+
obj: Record<string, T>,
|
|
157
|
+
fn: (value: T, key: string) => boolean,
|
|
158
|
+
): Record<string, T> {
|
|
159
|
+
const result: Record<string, T> = {}
|
|
160
|
+
for (const [key, value] of Object.entries(obj)) {
|
|
161
|
+
if (fn(value, key)) result[key] = value
|
|
162
|
+
}
|
|
163
|
+
return result
|
|
164
|
+
}
|
|
165
|
+
|
|
166
|
+
/** Invert keys and values */
|
|
167
|
+
export function invert(obj: Record<string, string | number>): Record<string, string> {
|
|
168
|
+
const result: Record<string, string> = {}
|
|
169
|
+
for (const [key, value] of Object.entries(obj)) {
|
|
170
|
+
result[String(value)] = key
|
|
171
|
+
}
|
|
172
|
+
return result
|
|
173
|
+
}
|
|
174
|
+
|
|
175
|
+
/** Check if a value is a plain object */
|
|
176
|
+
export function isPlainObject(value: any): value is Record<string, any> {
|
|
177
|
+
if (value === null || typeof value !== 'object') return false
|
|
178
|
+
const proto = Object.getPrototypeOf(value)
|
|
179
|
+
return proto === Object.prototype || proto === null
|
|
180
|
+
}
|