@helpers4/object 2.0.0-beta.0 → 2.0.0-beta.3

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/llms.txt CHANGED
@@ -1,7 +1,7 @@
1
1
  # @helpers4/object
2
2
 
3
3
  > Tree-shakable TypeScript utility functions for the `object` domain.
4
- > Package: `@helpers4/object` — Version: 2.0.0-beta.0
4
+ > Package: `@helpers4/object` — Version: 2.0.0-beta.3
5
5
  > License: LGPL-3.0-or-later
6
6
 
7
7
  ## Installation
@@ -15,7 +15,7 @@ pnpm add @helpers4/object
15
15
  ## Usage
16
16
 
17
17
  ```typescript
18
- import { compact, deepClone, deepCompare, ... } from '@helpers4/object';
18
+ import { compact, deepClone, deepMerge, ... } from '@helpers4/object';
19
19
  ```
20
20
 
21
21
  ## Functions
@@ -24,15 +24,19 @@ import { compact, deepClone, deepCompare, ... } from '@helpers4/object';
24
24
  |---|---|
25
25
  | `compact` | Removes all entries with falsy values (`false`, `null`, `undefined`, `0`, `""`, `NaN`) from an objec |
26
26
  | `deepClone` | Creates a deep copy of an object or array |
27
- | `deepCompare` | Deep comparison of two objects that returns detailed information about differences. |
28
27
  | `deepMerge` | Merges two or more objects deeply |
28
+ | `diff` | Structural object diff. Returns `true` when both inputs are deeply equal, otherwise a DiffResult de |
29
+ | `equalsDeep` | Recursive structural object equality. Boolean wrapper around diff \u2014 returns `true` when the tw |
30
+ | `equalsShallow` | One-level (shallow) object equality. Two objects are equal when they share the exact same set of ow |
29
31
  | `get` | Gets a value from an object using a dot-notated path |
32
+ | `groupBy` | Groups an array of items by a key derived from each item. A thin, typed wrapper around `Object.grou |
33
+ | `invert` | Returns a new object with keys and values swapped. If multiple keys share the same value, the last o |
34
+ | `map` | Transforms the values and/or keys of a plain object in a single pass. Both callbacks are optional a |
30
35
  | `omit` | Creates a new object without the specified keys. |
31
36
  | `pick` | Creates a new object with only the specified keys. |
32
37
  | `removeUndefinedNull` | Remove null and undefined values from an object. |
33
38
  | `safeJsonParse` | Parses a JSON string, returning `null` (or a fallback) on any parse failure. Unlike `JSON.parse`, t |
34
39
  | `set` | Sets a value in an object using a dot-notated path |
35
- | `shallowEquals` | Quick comparison of two objects using JSON.stringify. This is a fast but simple comparison that may |
36
40
 
37
41
  ---
38
42
 
@@ -131,22 +135,94 @@ cloned.a.b = 2;
131
135
 
132
136
  ---
133
137
 
134
- ### `deepCompare`
138
+ ### `deepMerge`
139
+
140
+ Merges two or more objects deeply
141
+
142
+ ```typescript
143
+ import { deepMerge } from '@helpers4/object';
144
+
145
+ deepMerge<T extends Record<string, unknown>>(target: T, sources: Record<string, unknown>[]): T
146
+ ```
147
+
148
+ **Parameters:**
149
+
150
+ - `target: T` — The target object
151
+ - `sources: Record<string, unknown>[]` — The source objects to merge
152
+
153
+ **Returns:** `T` — The merged object
154
+
155
+ ```typescript
156
+ import { deepMerge } from '@helpers4/object';
157
+
158
+ deepMerge(target: undefined, sources: Record<string, unknown>[]): undefined
159
+ ```
160
+
161
+ **Parameters:**
162
+
163
+ - `target: undefined` — The target object
164
+ - `sources: Record<string, unknown>[]` — The source objects to merge
165
+
166
+ **Returns:** `undefined` — The merged object
167
+
168
+ ```typescript
169
+ import { deepMerge } from '@helpers4/object';
170
+
171
+ deepMerge(target: null, sources: Record<string, unknown>[]): null
172
+ ```
173
+
174
+ **Parameters:**
175
+
176
+ - `target: null` — The target object
177
+ - `sources: Record<string, unknown>[]` — The source objects to merge
178
+
179
+ **Returns:** `null` — The merged object
180
+
181
+ **Examples:**
182
+
183
+ *Merge two objects deeply*
184
+
185
+ Recursively merges source properties into the target object.
186
+
187
+ ```typescript
188
+ deepMerge({ a: 1, b: { c: 2 } }, { b: { d: 3 }, e: 4 })
189
+ // => { a: 1, b: { c: 2, d: 3 }, e: 4 }
190
+ ```
191
+
192
+ ---
193
+
194
+ ### `diff`
195
+
196
+ Structural object diff.
197
+
198
+ Returns `true` when both inputs are deeply equal, otherwise a
199
+ DiffResult describing the differences key by key.
135
200
 
136
- Deep comparison of two objects that returns detailed information about differences.
201
+ Comparison rules:
202
+ - Same reference \u2192 `true`.
203
+ - Either side is `null`/`undefined` (and not both) \u2192 `false`.
204
+ - Both `Date` \u2192 epoch comparison.
205
+ - Both arrays \u2192 compared with `array/equalsDeep` (leaf, no diff drill-down).
206
+ - Special objects (Map, Set, RegExp, Promise, class instances\u2026) \u2192 reference equality.
207
+ - Plain objects \u2192 key-by-key, recursing up to `options.depth` levels.
208
+ - Mixed types (e.g. array vs object, Date vs object) \u2192 `false`.
209
+
210
+ For a boolean wrapper see equalsDeep from this category.
211
+ For a one-level boolean check see equalsShallow from this category.
137
212
 
138
213
  ```typescript
139
- import { deepCompare } from '@helpers4/object';
214
+ import { diff } from '@helpers4/object';
140
215
 
141
- deepCompare(objA: object | null | undefined, objB: object | null | undefined): boolean | DeepCompareResult
216
+ diff(objA: object | null | undefined, objB: object | null | undefined, options: DiffOptions): boolean | DiffResult
142
217
  ```
143
218
 
144
219
  **Parameters:**
145
220
 
146
- - `objA: object | null | undefined` — First object to compare (can be object, undefined, or null)
147
- - `objB: object | null | undefined` — Second object to compare (can be object, undefined, or null)
221
+ - `objA: object | null | undefined` — First value (object, `null`, or `undefined`).
222
+ - `objB: object | null | undefined` — Second value (object, `null`, or `undefined`).
223
+ - `options: DiffOptions` (default: `{}`) — See DiffOptions.
148
224
 
149
- **Returns:** `boolean | DeepCompareResult` — `true` if objects are identical, `false` if incompatible types, or a `DeepCompareResult` object detailing differences
225
+ **Returns:** `boolean | DiffResult` — `true` when equal, otherwise a DiffResult, or `false` for incompatible types.
150
226
 
151
227
  **Examples:**
152
228
 
@@ -155,7 +231,7 @@ deepCompare(objA: object | null | undefined, objB: object | null | undefined): b
155
231
  Deeply compares two objects, returning true when they are structurally equal.
156
232
 
157
233
  ```typescript
158
- deepCompare({ a: { b: 1 } }, { a: { b: 1 } })
234
+ diff({ a: { b: 1 } }, { a: { b: 1 } })
159
235
  // => true
160
236
  ```
161
237
 
@@ -164,64 +240,96 @@ deepCompare({ a: { b: 1 } }, { a: { b: 1 } })
164
240
  Returns a detailed diff object when nested values differ.
165
241
 
166
242
  ```typescript
167
- deepCompare({ a: { b: 1 } }, { a: { b: 2 } })
243
+ diff({ a: { b: 1 } }, { a: { b: 2 } })
168
244
  // => { a: { b: false } }
169
245
  ```
170
246
 
171
247
  ---
172
248
 
173
- ### `deepMerge`
249
+ ### `equalsDeep`
174
250
 
175
- Merges two or more objects deeply
251
+ Recursive structural object equality.
252
+
253
+ Boolean wrapper around diff \u2014 returns `true` when the two values
254
+ are deeply equal according to the same rules. Use this when you only
255
+ need a yes/no answer; use diff when you also need to know
256
+ *what* differs.
257
+
258
+ For a one-level boolean check use equalsShallow.
176
259
 
177
260
  ```typescript
178
- import { deepMerge } from '@helpers4/object';
261
+ import { equalsDeep } from '@helpers4/object';
179
262
 
180
- deepMerge<T extends Record<string, unknown>>(target: T, sources: Record<string, unknown>[]): T
263
+ equalsDeep(objA: object | null | undefined, objB: object | null | undefined): boolean
181
264
  ```
182
265
 
183
266
  **Parameters:**
184
267
 
185
- - `target: T` — The target object
186
- - `sources: Record<string, unknown>[]` — The source objects to merge
268
+ - `objA: object | null | undefined` — First value (object, `null`, or `undefined`).
269
+ - `objB: object | null | undefined` — Second value (object, `null`, or `undefined`).
187
270
 
188
- **Returns:** `T` — The merged object
271
+ **Returns:** `boolean` — `true` if both inputs are deeply equal, `false` otherwise.
272
+
273
+ **Examples:**
274
+
275
+ *Compare nested objects*
276
+
277
+ Recursive structural equality. Returns true when the two values are deeply equal.
189
278
 
190
279
  ```typescript
191
- import { deepMerge } from '@helpers4/object';
280
+ equalsDeep({ a: { b: 1 } }, { a: { b: 1 } })
281
+ // => true
282
+ ```
192
283
 
193
- deepMerge(target: undefined, sources: Record<string, unknown>[]): undefined
284
+ *Detect deep differences*
285
+
286
+ Returns false when nested values differ.
287
+
288
+ ```typescript
289
+ equalsDeep({ a: { b: 1 } }, { a: { b: 2 } })
290
+ // => false
194
291
  ```
195
292
 
196
- **Parameters:**
293
+ ---
197
294
 
198
- - `target: undefined` — The target object
199
- - `sources: Record<string, unknown>[]` — The source objects to merge
295
+ ### `equalsShallow`
200
296
 
201
- **Returns:** `undefined` — The merged object
297
+ One-level (shallow) object equality.
298
+
299
+ Two objects are equal when they share the exact same set of own
300
+ enumerable string keys and each pair of values satisfies strict equality
301
+ (`===`). No recursion: nested objects/arrays are compared by reference.
302
+
303
+ Falls back to strict equality when either input is `null`, `undefined`
304
+ or not an object \u2014 so primitives match if and only if they are `===`.
305
+ Arrays are not supported; they always return `false` (unless identical
306
+ references). Use `array/equalsShallow` instead.
307
+
308
+ For recursive structural comparison use equalsDeep. For a diff
309
+ structure use diff.
202
310
 
203
311
  ```typescript
204
- import { deepMerge } from '@helpers4/object';
312
+ import { equalsShallow } from '@helpers4/object';
205
313
 
206
- deepMerge(target: null, sources: Record<string, unknown>[]): null
314
+ equalsShallow(objA: unknown, objB: unknown): boolean
207
315
  ```
208
316
 
209
317
  **Parameters:**
210
318
 
211
- - `target: null` — The target object
212
- - `sources: Record<string, unknown>[]` — The source objects to merge
319
+ - `objA: unknown` — First value to compare
320
+ - `objB: unknown` — Second value to compare
213
321
 
214
- **Returns:** `null` — The merged object
322
+ **Returns:** `boolean` — `true` if values are shallowly equal, `false` otherwise.
215
323
 
216
324
  **Examples:**
217
325
 
218
- *Merge two objects deeply*
326
+ *Compare two equal objects*
219
327
 
220
- Recursively merges source properties into the target object.
328
+ Uses JSON.stringify for a fast comparison.
221
329
 
222
330
  ```typescript
223
- deepMerge({ a: 1, b: { c: 2 } }, { b: { d: 3 }, e: 4 })
224
- // => { a: 1, b: { c: 2, d: 3 }, e: 4 }
331
+ equalsShallow({ a: 1, b: 2 }, { a: 1, b: 2 })
332
+ // => true
225
333
  ```
226
334
 
227
335
  ---
@@ -266,6 +374,156 @@ get({ a: 1 }, 'b.c', 'default')
266
374
 
267
375
  ---
268
376
 
377
+ ### `groupBy`
378
+
379
+ Groups an array of items by a key derived from each item.
380
+
381
+ A thin, typed wrapper around `Object.groupBy` (ES2024) that works on
382
+ older targets and provides stricter return-type inference.
383
+
384
+ ```typescript
385
+ import { groupBy } from '@helpers4/object';
386
+
387
+ groupBy<T, K extends PropertyKey>(items: readonly T[], keyFn: function): Partial<Record<K, T[]>>
388
+ ```
389
+
390
+ **Parameters:**
391
+
392
+ - `items: readonly T[]` — The array to group
393
+ - `keyFn: function` — Function that returns the group key for each item
394
+
395
+ **Returns:** `Partial<Record<K, T[]>>` — A record mapping each key to the array of items with that key
396
+
397
+ **Examples:**
398
+
399
+ *Group numbers by parity*
400
+
401
+ Groups elements by the string key returned by the callback.
402
+
403
+ ```typescript
404
+ groupBy([1, 2, 3, 4], n => n % 2 === 0 ? 'even' : 'odd')
405
+ // => { odd: [1, 3], even: [2, 4] }
406
+ ```
407
+
408
+ *Group objects by a property*
409
+
410
+ Use a property accessor as the grouping key.
411
+
412
+ ```typescript
413
+ const users = [
414
+ { name: 'Alice', role: 'admin' },
415
+ { name: 'Bob', role: 'user' },
416
+ { name: 'Carol', role: 'admin' },
417
+ ];
418
+ groupBy(users, u => u.role)
419
+ // => { admin: [{...Alice}, {...Carol}], user: [{...Bob}] }
420
+ ```
421
+
422
+ ---
423
+
424
+ ### `invert`
425
+
426
+ Returns a new object with keys and values swapped.
427
+ If multiple keys share the same value, the last one wins.
428
+
429
+ ```typescript
430
+ import { invert } from '@helpers4/object';
431
+
432
+ invert<K extends string, V extends PropertyKey>(obj: Record<K, V>): Record<V, K>
433
+ ```
434
+
435
+ **Parameters:**
436
+
437
+ - `obj: Record<K, V>` — The object whose keys and values are to be swapped
438
+
439
+ **Returns:** `Record<V, K>` — A new object with values as keys and original keys as values
440
+
441
+ **Examples:**
442
+
443
+ *Swap keys and values*
444
+
445
+ Returns a new object with keys and values swapped.
446
+
447
+ ```typescript
448
+ invert({ a: 'x', b: 'y', c: 'z' })
449
+ // => { x: 'a', y: 'b', z: 'c' }
450
+ ```
451
+
452
+ *Build a reverse lookup map*
453
+
454
+ Useful when you have a code-to-label map and need label-to-code.
455
+
456
+ ```typescript
457
+ const STATUS_LABELS = { 200: 'OK', 404: 'Not Found', 500: 'Internal Server Error' };
458
+ const LABEL_TO_CODE = invert(STATUS_LABELS);
459
+
460
+ LABEL_TO_CODE['OK']; // => '200'
461
+ ```
462
+
463
+ ---
464
+
465
+ ### `map`
466
+
467
+ Transforms the values and/or keys of a plain object in a single pass.
468
+
469
+ Both callbacks are optional and default to identity (no transformation).
470
+ When `mapValue` is omitted the original values are preserved;
471
+ when `mapKey` is omitted the original keys are preserved.
472
+
473
+ Note: if two different keys map to the same output key the last one wins
474
+ (insertion order).
475
+
476
+ ```typescript
477
+ import { map } from '@helpers4/object';
478
+
479
+ map<TObj extends Record<string, unknown>, TVal = indexedAccess, TKey extends PropertyKey = keyof TObj>(obj: TObj, mapValue?: function, mapKey?: function): Record<TKey, TVal>
480
+ ```
481
+
482
+ **Parameters:**
483
+
484
+ - `obj: TObj` — The source object
485
+ - `mapValue?: function` — Callback called with `(value, key)` for each entry.
486
+ Defaults to identity.
487
+ - `mapKey?: function` — Callback called with `(key, value)` for each entry.
488
+ Defaults to identity.
489
+
490
+ **Returns:** `Record<TKey, TVal>` — A new object with transformed keys and/or values
491
+
492
+ **Examples:**
493
+
494
+ *Transform values*
495
+
496
+ Maps each value of an object through a transform function.
497
+
498
+ ```typescript
499
+ map({ a: 1, b: 2 }, v => v * 10)
500
+ // => { a: 10, b: 20 }
501
+ ```
502
+
503
+ *Transform keys*
504
+
505
+ Maps each key of an object through a transform function.
506
+
507
+ ```typescript
508
+ map({ a: 1, b: 2 }, undefined, k => k.toUpperCase())
509
+ // => { A: 1, B: 2 }
510
+ ```
511
+
512
+ *Transform both keys and values in a single pass*
513
+
514
+ Provide both a value mapper and a key mapper to rewrite the whole object.
515
+
516
+ ```typescript
517
+ map(
518
+ { price: 100, discount: 20 },
519
+ v => v / 100,
520
+ k => `${k}Ratio`
521
+ )
522
+ // => { priceRatio: 1, discountRatio: 0.2 }
523
+ ```
524
+
525
+ ---
526
+
269
527
  ### `omit`
270
528
 
271
529
  Creates a new object without the specified keys.
@@ -544,34 +802,3 @@ set({}, 'a.b.c', 42)
544
802
  ```
545
803
 
546
804
  ---
547
-
548
- ### `shallowEquals`
549
-
550
- Quick comparison of two objects using JSON.stringify.
551
- This is a fast but simple comparison that may not work for all edge cases.
552
-
553
- ```typescript
554
- import { shallowEquals } from '@helpers4/object';
555
-
556
- shallowEquals(objA: unknown, objB: unknown): boolean
557
- ```
558
-
559
- **Parameters:**
560
-
561
- - `objA: unknown` — First object to compare
562
- - `objB: unknown` — Second object to compare
563
-
564
- **Returns:** `boolean` — `true` if objects are identical according to JSON.stringify, `false` otherwise
565
-
566
- **Examples:**
567
-
568
- *Compare two equal objects*
569
-
570
- Uses JSON.stringify for a fast comparison.
571
-
572
- ```typescript
573
- shallowEquals({ a: 1, b: 2 }, { a: 1, b: 2 })
574
- // => true
575
- ```
576
-
577
- ---