@helpers4/object 3.0.0-alpha.2 → 3.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.
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: 3.0.0-alpha.2
4
+ > Package: `@helpers4/object` — Version: 3.0.0
5
5
  > License: LGPL-3.0-or-later
6
6
 
7
7
  ## Installation
@@ -15,18 +15,20 @@ pnpm add @helpers4/object
15
15
  ## Usage
16
16
 
17
17
  ```typescript
18
- import { cloneDeep, compact, diff, ... } from '@helpers4/object';
18
+ import { clone, cloneDeep, compact, ... } from '@helpers4/object';
19
19
  ```
20
20
 
21
21
  ## Functions
22
22
 
23
23
  | Function | Description |
24
24
  |---|---|
25
+ | `clone` | Creates a shallow copy of a value — one level deep, unlike cloneDeep. Unlike a plain `{ ...value }` |
25
26
  | `cloneDeep` | Creates a deep copy of an object or array. Recursively clones own enumerable string keys. `Date` in |
26
27
  | `compact` | Removes all entries with falsy values (`false`, `null`, `undefined`, `0`, `""`, `NaN`) from an objec |
27
28
  | `diff` | Structural object diff. Returns `true` when both inputs are deeply equal, otherwise a DiffResult de |
28
29
  | `equalsDeep` | Recursive structural object equality. Boolean wrapper around diff \u2014 returns `true` when the tw |
29
30
  | `equalsShallow` | One-level (shallow) object equality. Two objects are equal when they share the exact same set of ow |
31
+ | `flatten` | Flattens a nested object into a single-level object whose keys are the dot-notation path to each lea |
30
32
  | `get` | Gets a value from an object using a dot/bracket-notated path or explicit key array. **Two path form |
31
33
  | `groupBy` | Groups an array of items by a key derived from each item. A thin, typed wrapper around `Object.grou |
32
34
  | `invert` | Returns a new object with keys and values swapped. If multiple keys share the same value, the last o |
@@ -35,15 +37,67 @@ import { cloneDeep, compact, diff, ... } from '@helpers4/object';
35
37
  | `map` | Transforms the values and/or keys of a plain object in a single pass. Both callbacks are optional a |
36
38
  | `mergeDeep` | Merges two or more objects deeply, returning a **new** object without mutating any input. Sources a |
37
39
  | `omit` | Creates a new object without the specified keys. |
40
+ | `omitBy` | Creates a new object without the own enumerable entries for which `predicate` returns `true`. Compl |
41
+ | `parsePropertyPath` | Parses a dot/bracket-notation property path into an array of string/number key segments — the same n |
38
42
  | `pick` | Creates a new object with only the specified keys. Keys that are prototype-polluting strings (`__pro |
43
+ | `pickBy` | Creates a new object with only the own enumerable entries for which `predicate` returns `true`. Com |
39
44
  | `removeUndefinedNull` | Remove null and undefined values from an object. |
40
45
  | `safeJsonParse` | Parses a JSON string, returning `null` (or a fallback) on any parse failure. Unlike `JSON.parse`, t |
41
46
  | `set` | Sets a value in an object at the given path, creating intermediate objects as needed. **Two path fo |
47
+ | `unflatten` | Rebuilds a nested object from a single-level object whose keys are dot-notation paths. The inverse o |
48
+ | `unset` | Removes the value at a dot/bracket-notation path or explicit key array, mutating the object in place |
49
+ | `update` | Updates the value at a path by applying a function to its current value, creating intermediate objec |
42
50
 
43
51
  ---
44
52
 
45
53
  ## API Reference
46
54
 
55
+ ### `clone`
56
+
57
+ Creates a shallow copy of a value — one level deep, unlike cloneDeep.
58
+
59
+ Unlike a plain `{ ...value }` spread, this correctly reconstructs `Date`,
60
+ `Map`, `Set`, and arrays instead of producing an empty (or wrong-shaped)
61
+ plain object for them. Primitives are returned as-is. Any other object
62
+ (including class instances not listed above) has its own enumerable string
63
+ keys shallow-copied into a plain object — the same fallback `cloneDeep`
64
+ uses, so the two stay consistent for types neither one special-cases.
65
+
66
+ ```typescript
67
+ import { clone } from '@helpers4/object';
68
+
69
+ clone<T>(value: T): T
70
+ ```
71
+
72
+ **Parameters:**
73
+
74
+ - `value: T` — The value to shallow-clone
75
+
76
+ **Returns:** `T` — A shallow copy of `value`
77
+
78
+ **Examples:**
79
+
80
+ *Shallow-copy a plain object*
81
+
82
+ Top-level keys are copied; nested values still share references (shallow).
83
+
84
+ ```typescript
85
+ const obj = { a: 1, b: { c: 2 } };
86
+ const copy = clone(obj);
87
+ copy.b === obj.b // => true (same nested reference)
88
+ ```
89
+
90
+ *Correctly clones Date/Map/Set, unlike a spread*
91
+
92
+ { ...new Date() } produces {} — clone() doesn't have that problem.
93
+
94
+ ```typescript
95
+ clone(new Map([['a', 1]]))
96
+ // => new Map with the same entries, not {}
97
+ ```
98
+
99
+ ---
100
+
47
101
  ### `cloneDeep`
48
102
 
49
103
  Creates a deep copy of an object or array.
@@ -290,6 +344,55 @@ equalsShallow({ a: 1, b: 2 }, { a: 1, b: 2 })
290
344
 
291
345
  ---
292
346
 
347
+ ### `flatten`
348
+
349
+ Flattens a nested object into a single-level object whose keys are the
350
+ dot-notation path to each leaf value. The inverse of unflatten.
351
+
352
+ Only **plain objects** are recursed into — arrays, `Date`, `Map`, `RegExp`,
353
+ class instances, and empty plain objects `{}` are kept as opaque leaf
354
+ values. This keeps `flatten`/`unflatten` a clean, invertible pair: arrays
355
+ can't be losslessly told apart from plain objects once reduced to dotted
356
+ keys, so this implementation doesn't attempt it.
357
+
358
+ Caveat shared by every dotted-path flattening scheme: a key that itself
359
+ contains a literal `.` is indistinguishable from real nesting once
360
+ flattened (`{ 'a.b': 1 }` and `{ a: { b: 1 } }` both produce `{ 'a.b': 1 }`).
361
+
362
+ ```typescript
363
+ import { flatten } from '@helpers4/object';
364
+
365
+ flatten(obj: Record<string, unknown>): Record<string, unknown>
366
+ ```
367
+
368
+ **Parameters:**
369
+
370
+ - `obj: Record<string, unknown>` — The object to flatten
371
+
372
+ **Returns:** `Record<string, unknown>` — A single-level object with dot-notation keys
373
+
374
+ **Examples:**
375
+
376
+ *Flatten a nested config object*
377
+
378
+ Each key in the result is the full dot-notation path to a leaf value.
379
+
380
+ ```typescript
381
+ flatten({ server: { host: 'localhost', port: 8080 } })
382
+ // => { 'server.host': 'localhost', 'server.port': 8080 }
383
+ ```
384
+
385
+ *Arrays and special objects are kept as leaves*
386
+
387
+ Only plain objects are recursed into — arrays stay intact.
388
+
389
+ ```typescript
390
+ flatten({ tags: ['a', 'b'], meta: { owner: 'x' } })
391
+ // => { tags: ['a', 'b'], 'meta.owner': 'x' }
392
+ ```
393
+
394
+ ---
395
+
293
396
  ### `get`
294
397
 
295
398
  Gets a value from an object using a dot/bracket-notated path or explicit key array.
@@ -762,6 +865,98 @@ omit(user, ['password', 'token'])
762
865
 
763
866
  ---
764
867
 
868
+ ### `omitBy`
869
+
870
+ Creates a new object without the own enumerable entries for which
871
+ `predicate` returns `true`.
872
+
873
+ Complements omit for when the keys to remove aren't known ahead of
874
+ time — `omit` takes an explicit key list, `omitBy` takes a predicate.
875
+
876
+ ```typescript
877
+ import { omitBy } from '@helpers4/object';
878
+
879
+ omitBy<T extends Record<string, unknown>>(obj: T | null | undefined, predicate: function): Partial<T> | null | undefined
880
+ ```
881
+
882
+ **Parameters:**
883
+
884
+ - `obj: T | null | undefined` — The source object. `null`/`undefined` pass through unchanged.
885
+ - `predicate: function` — Called with `(value, key)` for each own enumerable entry
886
+
887
+ **Returns:** `Partial<T> | null | undefined` — A new object without the matching entries
888
+
889
+ **Examples:**
890
+
891
+ *Drop entries matching a predicate*
892
+
893
+ Unlike omit(), the keys to drop don’t need to be known ahead of time.
894
+
895
+ ```typescript
896
+ omitBy({ a: 1, b: undefined, c: 2 }, (value) => value === undefined)
897
+ // => { a: 1, c: 2 }
898
+ ```
899
+
900
+ *Drop entries by key name*
901
+
902
+ The predicate also receives the key, so filtering by name works too.
903
+
904
+ ```typescript
905
+ omitBy({ id: 1, name: 'x', _internal: true }, (_v, key) => key.startsWith('_'))
906
+ // => { id: 1, name: 'x' }
907
+ ```
908
+
909
+ ---
910
+
911
+ ### `parsePropertyPath`
912
+
913
+ Parses a dot/bracket-notation property path into an array of string/number
914
+ key segments — the same notation accepted by get and set.
915
+
916
+ - Dot separators (`.`) split segments; each segment becomes a string key.
917
+ - Bracket indices (`[n]`) become number keys.
918
+ - A leading `.` is treated as "current level" and stripped before parsing,
919
+ so `.[0]` ≡ `[0]` and `.a.b` ≡ `a.b`.
920
+ - Empty string (or a bare `.`) returns `['']` (addresses the `''` key on the root object).
921
+ - Consecutive dots (`a..b`) produce an empty-string segment: `['a', '', 'b']`.
922
+
923
+ Results are cached (up to 500 distinct path strings, oldest evicted first)
924
+ since real-world callers tend to reuse a small, fixed set of literal paths.
925
+
926
+ ```typescript
927
+ import { parsePropertyPath } from '@helpers4/object';
928
+
929
+ parsePropertyPath(path: string): readonly string | number[]
930
+ ```
931
+
932
+ **Parameters:**
933
+
934
+ - `path: string` — The dot/bracket-notation path string to parse
935
+
936
+ **Returns:** `readonly string | number[]` — The parsed key segments
937
+
938
+ **Examples:**
939
+
940
+ *Parse a dot/bracket-notation path into key segments*
941
+
942
+ The same notation accepted by get() and set() — bracket indices become numbers.
943
+
944
+ ```typescript
945
+ parsePropertyPath('layers[1].name')
946
+ // => ['layers', 1, 'name']
947
+ ```
948
+
949
+ *Malformed paths throw instead of silently misparsing*
950
+
951
+ Text trailing a closing bracket within a segment is ambiguous — use 'a[0].b' instead.
952
+
953
+ ```typescript
954
+ parsePropertyPath('a[0]b')
955
+ // => throws RangeError
956
+ ```
957
+
958
+ ---
959
+
765
960
  ### `pick`
766
961
 
767
962
  Creates a new object with only the specified keys.
@@ -830,6 +1025,49 @@ pick(user, ['id', 'name', 'email'])
830
1025
 
831
1026
  ---
832
1027
 
1028
+ ### `pickBy`
1029
+
1030
+ Creates a new object with only the own enumerable entries for which
1031
+ `predicate` returns `true`.
1032
+
1033
+ Complements pick for when the keys to keep aren't known ahead of
1034
+ time — `pick` takes an explicit key list, `pickBy` takes a predicate.
1035
+
1036
+ ```typescript
1037
+ import { pickBy } from '@helpers4/object';
1038
+
1039
+ pickBy<T extends Record<string, unknown>>(obj: T | null | undefined, predicate: function): Partial<T> | null | undefined
1040
+ ```
1041
+
1042
+ **Parameters:**
1043
+
1044
+ - `obj: T | null | undefined` — The source object. `null`/`undefined` pass through unchanged.
1045
+ - `predicate: function` — Called with `(value, key)` for each own enumerable entry
1046
+
1047
+ **Returns:** `Partial<T> | null | undefined` — A new object with only the matching entries
1048
+
1049
+ **Examples:**
1050
+
1051
+ *Keep only entries matching a predicate*
1052
+
1053
+ Unlike pick(), the keys to keep don’t need to be known ahead of time.
1054
+
1055
+ ```typescript
1056
+ pickBy({ a: 1, b: 0, c: 2 }, (value) => value > 0)
1057
+ // => { a: 1, c: 2 }
1058
+ ```
1059
+
1060
+ *Keep entries by key name*
1061
+
1062
+ The predicate also receives the key, so filtering by name works too.
1063
+
1064
+ ```typescript
1065
+ pickBy({ _id: 1, name: 'x', _internal: true }, (_v, key) => !key.startsWith('_'))
1066
+ // => { name: 'x' }
1067
+ ```
1068
+
1069
+ ---
1070
+
833
1071
  ### `removeUndefinedNull`
834
1072
 
835
1073
  Remove null and undefined values from an object.
@@ -1017,3 +1255,144 @@ set({}, ['user', id], 'alice')
1017
1255
  ```
1018
1256
 
1019
1257
  ---
1258
+
1259
+ ### `unflatten`
1260
+
1261
+ Rebuilds a nested object from a single-level object whose keys are
1262
+ dot-notation paths. The inverse of flatten.
1263
+
1264
+ Uses set internally, so intermediate nodes are always created as
1265
+ plain objects (never arrays — see flatten's doc for why), and any
1266
+ key segment equal to `__proto__`, `constructor`, or `prototype` is silently
1267
+ rejected (same prototype-pollution guard as `set`).
1268
+
1269
+ ```typescript
1270
+ import { unflatten } from '@helpers4/object';
1271
+
1272
+ unflatten(obj: Record<string, unknown>): Record<string, unknown>
1273
+ ```
1274
+
1275
+ **Parameters:**
1276
+
1277
+ - `obj: Record<string, unknown>` — A single-level object with dot-notation keys
1278
+
1279
+ **Returns:** `Record<string, unknown>` — The rebuilt nested object
1280
+
1281
+ **Examples:**
1282
+
1283
+ *Rebuild a nested object from dotted keys*
1284
+
1285
+ The inverse of flatten() — useful for parsing flat config sources (env vars, .ini files).
1286
+
1287
+ ```typescript
1288
+ unflatten({ 'server.host': 'localhost', 'server.port': 8080 })
1289
+ // => { server: { host: 'localhost', port: 8080 } }
1290
+ ```
1291
+
1292
+ *Multiple keys under the same parent are merged*
1293
+
1294
+ Every dotted key contributes to the same nested structure.
1295
+
1296
+ ```typescript
1297
+ unflatten({ 'a.x': 1, 'a.y': 2 })
1298
+ // => { a: { x: 1, y: 2 } }
1299
+ ```
1300
+
1301
+ ---
1302
+
1303
+ ### `unset`
1304
+
1305
+ Removes the value at a dot/bracket-notation path or explicit key array,
1306
+ mutating the object in place. Uses the same path syntax as get/set.
1307
+
1308
+ A missing intermediate segment is a no-op (nothing to remove), not an error.
1309
+ As with `set`, any path containing a string segment equal to `__proto__`,
1310
+ `constructor`, or `prototype` is rejected and the object is returned unchanged.
1311
+
1312
+ The removed key stops appearing in `Object.keys`/`for...in` — unlike setting
1313
+ it to `undefined`, which would keep the key present.
1314
+
1315
+ ```typescript
1316
+ import { unset } from '@helpers4/object';
1317
+
1318
+ unset<T extends object>(obj: T, path: string | readonly PropertyKey[]): T
1319
+ ```
1320
+
1321
+ **Parameters:**
1322
+
1323
+ - `obj: T` — The object to mutate
1324
+ - `path: string | readonly PropertyKey[]` — Dot/bracket-notation string, or explicit `PropertyKey[]`
1325
+
1326
+ **Returns:** `T` — The same object reference, with the key removed if it existed
1327
+
1328
+ **Examples:**
1329
+
1330
+ *Remove a nested value by path*
1331
+
1332
+ Uses the same dot/bracket path syntax as get() and set().
1333
+
1334
+ ```typescript
1335
+ const config = { server: { host: 'localhost', debug: true } };
1336
+ unset(config, 'server.debug')
1337
+ // => { server: { host: 'localhost' } }
1338
+ ```
1339
+
1340
+ *Missing paths are a safe no-op*
1341
+
1342
+ Unlike set(), unset() never creates intermediate objects — there is nothing to remove.
1343
+
1344
+ ```typescript
1345
+ unset({ a: 1 }, 'x.y.z')
1346
+ // => { a: 1 } (unchanged)
1347
+ ```
1348
+
1349
+ ---
1350
+
1351
+ ### `update`
1352
+
1353
+ Updates the value at a path by applying a function to its current value,
1354
+ creating intermediate objects as needed. Equivalent to
1355
+ `set(obj, path, updater(get(obj, path)))` in a single call.
1356
+
1357
+ Uses the same path syntax and type-inference rules as get and
1358
+ set — see those for the full behavior (string vs. `PropertyKey[]`
1359
+ paths, prototype-pollution guarding, etc.).
1360
+
1361
+ ```typescript
1362
+ import { update } from '@helpers4/object';
1363
+
1364
+ update<T extends object, P extends string | readonly PropertyKey[], V extends unknown>(obj: T, path: P, updater: function): conditional
1365
+ ```
1366
+
1367
+ **Parameters:**
1368
+
1369
+ - `obj: T` — The object to mutate
1370
+ - `path: P` — Dot/bracket-notation string literal or explicit `PropertyKey[]`
1371
+ - `updater: function` — Called with the current value (`undefined` if the path is
1372
+ absent); its return value is written back at the path
1373
+
1374
+ **Returns:** `conditional` — The mutated object (same reference, narrowed type)
1375
+
1376
+ **Examples:**
1377
+
1378
+ *Increment a counter in one call*
1379
+
1380
+ Equivalent to set(obj, path, updater(get(obj, path))), without repeating the path.
1381
+
1382
+ ```typescript
1383
+ const state = { count: 1 };
1384
+ update(state, 'count', (n) => (n ?? 0) + 1)
1385
+ // => { count: 2 }
1386
+ ```
1387
+
1388
+ *Missing paths create intermediate objects, like set()*
1389
+
1390
+ The updater receives undefined when the path does not exist yet.
1391
+
1392
+ ```typescript
1393
+ const stats: Record<string, unknown> = {};
1394
+ update(stats, 'hits.total', (n: number | undefined) => (n ?? 0) + 1)
1395
+ // => { hits: { total: 1 } }
1396
+ ```
1397
+
1398
+ ---