@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/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
+ }