js-powerkit 1.0.0

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.
@@ -0,0 +1,423 @@
1
+ /**
2
+ * Object Utility Functions
3
+ * @module object
4
+ */
5
+
6
+
7
+ /**
8
+ * Deep clones an object.
9
+ * @param {Object} obj - The object to clone.
10
+ * @returns {Object} The cloned object.
11
+ * @example
12
+ * deepClone({a: 1, b: {c: 2}}); // {a: 1, b: {c: 2}}
13
+ */
14
+ export const deepClone = (obj) => {
15
+ if (obj === null || typeof obj !== 'object') return obj;
16
+ if (obj instanceof Date) return new Date(obj.getTime());
17
+ if (obj instanceof Array) return obj.map(item => deepClone(item));
18
+ if (typeof obj === 'object') {
19
+ const cloned = {};
20
+ Object.keys(obj).forEach(key => {
21
+ cloned[key] = deepClone(obj[key]);
22
+ });
23
+ return cloned;
24
+ }
25
+ }
26
+
27
+ /**
28
+ * Deep merges two or more objects
29
+ * @param {...Object} objects - The objects to merge.
30
+ * @returns {Object} The merged object.
31
+ * @example
32
+ * deepMerge({a: 1}, {b: 2}); // {a: 1, b: 2}
33
+ */
34
+ export const deepMerge = (...objects) => {
35
+ return objects.reduce((merged, obj) => {
36
+ if (obj && typeof obj === 'object') {
37
+ Object.keys(obj).forEach(key => {
38
+ if (obj[key] && typeof obj[key] === 'object' && !Array.isArray(obj[key])) {
39
+ merged[key] = deepMerge(merged[key] || {}, obj[key]);
40
+ } else {
41
+ merged[key] = obj[key];
42
+ }
43
+ });
44
+ }
45
+ return merged;
46
+ }, {});
47
+ }
48
+
49
+ /**
50
+ * Picks specified properties from an object.
51
+ * @param {Object} obj - The source object.
52
+ * @param {Array} keys - The keys to pick.
53
+ * @returns {Object} The object with picked properties.
54
+ * @example
55
+ * pick({a: 1, b: 2, c: 3}, ['a', 'c']); // {a: 1, c: 3}
56
+ */
57
+ export const pick = (obj, keys) => {
58
+ if (!obj || typeof obj !== 'object') throw new TypeError('First argument must be an object');
59
+ if (!Array.isArray(keys)) throw new TypeError('Second argument must be an array');
60
+ const result = {};
61
+ keys.forEach(key => {
62
+ if (key in obj) result[key] = obj[key];
63
+ });
64
+ return result;
65
+ }
66
+
67
+ /**
68
+ * Omits specified properties from an object.
69
+ * @param {Object} obj - The source object.
70
+ * @param {Array} keys - The keys to omit.
71
+ * @returns {Object} The object without omitted properties.
72
+ * @example
73
+ * omit({a: 1, b: 2, c: 3}, ['b']); // {a: 1, c: 3}
74
+ */
75
+ export const omit = (obj, keys) => {
76
+ if (!obj || typeof obj !== 'object') throw new TypeError('First argument must be an object');
77
+ if (!Array.isArray(keys)) throw new TypeError('Second argument must be an array');
78
+ const result = { ...obj };
79
+ keys.forEach(key => delete result[key]);
80
+ return result;
81
+ }
82
+
83
+ /**
84
+ * Checks if an object is empty.
85
+ * @param {Object} obj - The object to check.
86
+ * @returns {boolean} True if the object is empty.
87
+ * @example
88
+ * isEmpty({}); // true
89
+ * isEmpty({a: 1}); // false
90
+ */
91
+ export const isEmpty = (obj) => {
92
+ return obj != null && typeof obj === 'object' && Object.keys(obj).length === 0;
93
+ }
94
+
95
+ /**
96
+ * Inverts the keys and values of an object.
97
+ * @param {Object} obj - The object to invert.
98
+ * @returns {Object} The inverted object.
99
+ * @example
100
+ * invert({a: 1, b: 2}); // {1: 'a', 2: 'b'}
101
+ */
102
+ export const invert = (obj) => {
103
+ if (!obj || typeof obj !== 'object') throw new TypeError('Input must be an object');
104
+ const result = {};
105
+ Object.keys(obj).forEach(key => {
106
+ result[obj[key]] = key;
107
+ });
108
+ return result;
109
+ }
110
+
111
+ /**
112
+ * Maps the keys of an object using a function.
113
+ * @param {Object} obj - The source object.
114
+ * @param {Function} fn - The mapping function.
115
+ * @returns {Object} The object with mapped keys.
116
+ * @example
117
+ * mapKeys({a: 1, b: 2}, key => key.toUpperCase()); // {A: 1, B: 2}
118
+ */
119
+ export const mapKeys = (obj, fn) => {
120
+ if (!obj || typeof obj !== 'object') throw new TypeError('First argument must be an object');
121
+ if (typeof fn !== 'function') throw new TypeError('Second argument must be a function');
122
+ const result = {};
123
+ Object.keys(obj).forEach(key => {
124
+ result[fn(key)] = obj[key];
125
+ });
126
+ return result;
127
+ }
128
+
129
+ /**
130
+ * Maps the values of an object using a function.
131
+ * @param {Object} obj - The source object.
132
+ * @param {Function} fn - The mapping function.
133
+ * @returns {Object} The object with mapped values.
134
+ * @example
135
+ * mapValues({a: 1, b: 2}, val => val * 2); // {a: 2, b: 4}
136
+ */
137
+ export const mapValues = (obj, fn) => {
138
+ if (!obj || typeof obj !== 'object') throw new TypeError('First argument must be an object');
139
+ if (typeof fn !== 'function') throw new TypeError('Second argument must be a function');
140
+ const result = {};
141
+ Object.keys(obj).forEach(key => {
142
+ result[key] = fn(obj[key]);
143
+ });
144
+ return result;
145
+ }
146
+
147
+ /**
148
+ * Sets default values for an object.
149
+ * @param {Object} obj - The target object.
150
+ * @param {Object} defaults - The default values.
151
+ * @returns {Object} The object with defaults applied.
152
+ * @example
153
+ * defaults({a: 1}, {a: 2, b: 3}); // {a: 1, b: 3}
154
+ */
155
+ export const defaults = (obj, defaults) => {
156
+ if (!obj || typeof obj !== 'object') throw new TypeError('First argument must be an object');
157
+ if (!defaults || typeof defaults !== 'object') throw new TypeError('Second argument must be an object');
158
+ const result = { ...defaults };
159
+ Object.keys(obj).forEach(key => {
160
+ if (obj[key] !== undefined) result[key] = obj[key];
161
+ });
162
+ return result;
163
+ }
164
+
165
+ /**
166
+ * Gets a nested property value using dot notation
167
+ * @param {Object} obj - The object
168
+ * @param {string} path - Path to property (e.g., 'user.address.city')
169
+ * @param {*} defaultValue - Default value if not found
170
+ * @returns {*} Property value
171
+ * @example
172
+ * getNestedValue({ a: { b: { c: 42 } } }, 'a.b.c'); // 42
173
+ * getNestedValue({ a: { b: { c: 42 } } }, 'a.c', 'default'); // 'default'
174
+ */
175
+ export const getNestedValue = (obj, path, defaultValue = undefined) => {
176
+ if (!obj || typeof path !== 'string') return defaultValue;
177
+
178
+ const keys = path.split('.');
179
+ let result = obj;
180
+
181
+ for (const key of keys) {
182
+ result = (result && result[key]) ? result[key] : defaultValue;
183
+ if (result === undefined) return defaultValue;
184
+ }
185
+
186
+ return result;
187
+ }
188
+
189
+ /**
190
+ * Sets a nested property value using dot notation
191
+ * @param {Object} obj - The object
192
+ * @param {string} path - Path to property
193
+ * @param {*} value - Value to set
194
+ * @returns {Object} Modified object
195
+ * @example
196
+ * let obj = { a: { b: 1 } };
197
+ * setNestedValue(obj, 'a.c.d', 42);
198
+ * console.log(obj.a.c.d); // 42
199
+ */
200
+ export const setNestedValue = (obj, path, value) => {
201
+ if (!obj || typeof path !== 'string') return obj;
202
+
203
+ const keys = path.split('.');
204
+ const lastKey = keys.pop();
205
+ let current = obj;
206
+
207
+ for (const key of keys) {
208
+ if (!current[key] || typeof current[key] !== 'object') {
209
+ current[key] = {};
210
+ }
211
+ current = current[key];
212
+ }
213
+
214
+ current[lastKey] = value;
215
+ return obj;
216
+ }
217
+
218
+ /**
219
+ * Gets all keys from nested object (flattened)
220
+ * @param {Object} obj - The object
221
+ * @param {string} prefix - Prefix for keys
222
+ * @returns {Array<string>} Array of all keys
223
+ * @example
224
+ * getAllKeys({ a: 1, b: { c: 2, d: { e: 3 } } }); // ['a', 'b.c', 'b.d.e']
225
+ */
226
+ export const getAllKeys = (obj, prefix = '') => {
227
+ if (!obj || typeof obj !== 'object') return [];
228
+
229
+ return Object.keys(obj).reduce((keys, key) => {
230
+ const newKey = prefix ? `${prefix}.${key}` : key;
231
+
232
+ if (typeof obj[key] === 'object' && obj[key] !== null && !Array.isArray(obj[key])) {
233
+ return keys.concat(getAllKeys(obj[key], newKey));
234
+ }
235
+
236
+ return keys.concat(newKey);
237
+ }, []);
238
+ }
239
+
240
+ /**
241
+ * Flattens a nested object
242
+ * @param {Object} obj - The object to flatten
243
+ * @param {string} prefix - Prefix for keys
244
+ * @returns {Object} Flattened object
245
+ * @example
246
+ * flattenObject({ a: 1, b: { c: 2, d: { e: 3 } } }); // {'a': 1, 'b.c': 2, 'b.d.e': 3 }
247
+ */
248
+ export const flattenObject = (obj, prefix = '') => {
249
+ if (!obj || typeof obj !== 'object') return {};
250
+
251
+ return Object.keys(obj).reduce((acc, key) => {
252
+ const newKey = prefix ? `${prefix}.${key}` : key;
253
+
254
+ if (typeof obj[key] === 'object' && obj[key] !== null && !Array.isArray(obj[key])) {
255
+ Object.assign(acc, flattenObject(obj[key], newKey));
256
+ } else {
257
+ acc[newKey] = obj[key];
258
+ }
259
+
260
+ return acc;
261
+ }, {});
262
+ }
263
+
264
+ /**
265
+ * Unflattens a flattened object
266
+ * @param {Object} obj - The flattened object
267
+ * @returns {Object} Nested object
268
+ * @example
269
+ * unflattenObject({ 'a': 1, 'b.c': 2, 'b.d.e': 3 }); // { a: 1, b: { c: 2, d: { e: 3 } } }
270
+ */
271
+ export const unflattenObject = (obj) => {
272
+ if (!obj || typeof obj !== 'object') return {};
273
+
274
+ const result = {};
275
+
276
+ for (const key in obj) {
277
+ setNestedValue(result, key, obj[key]);
278
+ }
279
+
280
+ return result;
281
+ }
282
+
283
+ /**
284
+ * Removes null and undefined values from object
285
+ * @param {Object} obj - The object
286
+ * @returns {Object} Object without null/undefined values
287
+ * @example
288
+ * removeNullish({ a: 1, b: null, c: undefined, d: 0, e: '' }); // { a: 1, d: 0, e: '' }
289
+ */
290
+ export const removeNullish = (obj) => {
291
+ if (!obj || typeof obj !== 'object') return obj;
292
+
293
+ return Object.keys(obj).reduce((result, key) => {
294
+ if (obj[key] != null) {
295
+ result[key] = obj[key];
296
+ }
297
+ return result;
298
+ }, {});
299
+ }
300
+
301
+ /**
302
+ * Checks if two objects are deeply equal
303
+ * @param {Object} obj1 - First object
304
+ * @param {Object} obj2 - Second object
305
+ * @returns {boolean} True if equal
306
+ * @example
307
+ * isEqual({ a: 1 }, { a: 1 }); // true
308
+ */
309
+ export const isEqual = (obj1, obj2) => {
310
+ if (obj1 === obj2) return true;
311
+
312
+ if (obj1 == null || obj2 == null) return false;
313
+ if (typeof obj1 !== 'object' || typeof obj2 !== 'object') return false;
314
+
315
+ const keys1 = Object.keys(obj1);
316
+ const keys2 = Object.keys(obj2);
317
+
318
+ if (keys1.length !== keys2.length) return false;
319
+
320
+ for (const key of keys1) {
321
+ if (!keys2.includes(key)) return false;
322
+ if (!isEqual(obj1[key], obj2[key])) return false;
323
+ }
324
+
325
+ return true;
326
+ }
327
+
328
+ /**
329
+ * Filters object by predicate function
330
+ * @param {Object} obj - The object
331
+ * @param {Function} fn - Predicate function
332
+ * @returns {Object} Filtered object
333
+ * @example
334
+ * filterObject({ a: 1, b: 2, c: 3, d: 4 }, val => val % 2 === 0); // { b: 2, d: 4 }
335
+ */
336
+ export const filterObject = (obj, fn) => {
337
+ if (!obj || typeof obj !== 'object' || typeof fn !== 'function') return {};
338
+
339
+ return Object.keys(obj).reduce((result, key) => {
340
+ if (fn(obj[key], key, obj)) {
341
+ result[key] = obj[key];
342
+ }
343
+ return result;
344
+ }, {});
345
+ }
346
+
347
+ /**
348
+ * Converts object to query string
349
+ * @param {Object} obj - The object
350
+ * @returns {string} Query string
351
+ * @example
352
+ * toQueryString({ name: 'John', age: 30 }); // 'name=John&age=30'
353
+ */
354
+ export const toQueryString = (obj) => {
355
+ if (!obj || typeof obj !== 'object') return '';
356
+
357
+ return Object.keys(obj)
358
+ .filter(key => obj[key] != null)
359
+ .map(key => `${encodeURIComponent(key)}=${encodeURIComponent(obj[key])}`)
360
+ .join('&');
361
+ }
362
+
363
+ /**
364
+ * Converts query string to object
365
+ * @param {string} queryString - The query string
366
+ * @returns {Object} Parsed object
367
+ * @example
368
+ * fromQueryString('name=John&age=30'); // { name: 'John', age: 30 }
369
+ */
370
+ export const fromQueryString = (queryString) => {
371
+ if (!queryString || typeof queryString !== 'string') return {};
372
+
373
+ const params = new URLSearchParams(queryString);
374
+ const result = {};
375
+
376
+ for (const [key, value] of params) {
377
+ result[key] = value;
378
+ }
379
+
380
+ return result;
381
+ }
382
+
383
+ /**
384
+ * Gets object size (number of properties)
385
+ * @param {Object} obj - The object
386
+ * @returns {number} Number of properties
387
+ * @example
388
+ * size({ a: 1, b: 2, c: 3 }); // 3
389
+ * size({}); // 0
390
+ */
391
+ export const size = (obj) => {
392
+ if (!obj || typeof obj !== 'object') return 0;
393
+ return Object.keys(obj).length;
394
+ }
395
+
396
+ /**
397
+ * Checks if an object has a property.
398
+ * @param {Object} obj - The object to check.
399
+ * @param {string} prop - The property to check.
400
+ * @returns {boolean} True if the property exists.
401
+ * @example
402
+ * has({a: 1}, 'a'); // true
403
+ * has({ a: 1 }, 'b') // false
404
+ */
405
+ export const has = (obj, prop) => {
406
+ if (!obj || typeof obj !== 'object') throw new TypeError('First argument must be an object');
407
+ return prop in obj;
408
+ }
409
+
410
+ /**
411
+ * Checks if object has a nested property
412
+ * @param {Object} obj - The object
413
+ * @param {string} path - Path to check
414
+ * @returns {boolean} True if property exists
415
+ * @example
416
+ * hasPath({ a: { b: { c: 1 } } }, 'a.b.c'); // true
417
+ * hasPath({ a: { b: { c: 1 } } }, 'a.b.d'); // false
418
+ */
419
+ export const hasPath = (obj, path) => {
420
+ if (!obj || typeof path !== 'string') return false;
421
+ const notFound = Symbol('not-found');
422
+ return getNestedValue(obj, path, notFound) !== notFound;
423
+ };