@helpers4/object 2.0.4 → 3.0.0-alpha.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/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.4
4
+ > Package: `@helpers4/object` — Version: 3.0.0-alpha.1
5
5
  > License: LGPL-3.0-or-later
6
6
 
7
7
  ## Installation
@@ -15,38 +15,79 @@ pnpm add @helpers4/object
15
15
  ## Usage
16
16
 
17
17
  ```typescript
18
- import { compact, deepClone, deepMerge, ... } from '@helpers4/object';
18
+ import { cloneDeep, compact, diff, ... } from '@helpers4/object';
19
19
  ```
20
20
 
21
21
  ## Functions
22
22
 
23
23
  | Function | Description |
24
24
  |---|---|
25
+ | `cloneDeep` | Creates a deep copy of an object or array. Recursively clones own enumerable string keys. `Date` in |
25
26
  | `compact` | Removes all entries with falsy values (`false`, `null`, `undefined`, `0`, `""`, `NaN`) from an objec |
26
- | `deepClone` | Creates a deep copy of an object or array |
27
- | `deepMerge` | Merges two or more objects deeply. Recursively merges own enumerable properties — both string and s |
28
27
  | `diff` | Structural object diff. Returns `true` when both inputs are deeply equal, otherwise a DiffResult de |
29
28
  | `equalsDeep` | Recursive structural object equality. Boolean wrapper around diff \u2014 returns `true` when the tw |
30
29
  | `equalsShallow` | One-level (shallow) object equality. Two objects are equal when they share the exact same set of ow |
31
30
  | `get` | Gets a value from an object using a dot/bracket-notated path or explicit key array. **Two path form |
32
31
  | `groupBy` | Groups an array of items by a key derived from each item. A thin, typed wrapper around `Object.grou |
33
32
  | `invert` | Returns a new object with keys and values swapped. If multiple keys share the same value, the last o |
34
- | `isEmpty` | Checks if a plain object has no own enumerable string-keyed properties. Symbol-keyed properties are |
35
- | `isNonEmpty` | Checks if a plain object has at least one own enumerable string-keyed property. Symbol-keyed proper |
33
+ | `isEmpty` | Checks if a plain object has no own enumerable string-keyed properties. `null` and `undefined` are t |
34
+ | `isNonEmpty` | Checks if a plain object has at least one own enumerable string-keyed property. `null` and `undefine |
36
35
  | `map` | Transforms the values and/or keys of a plain object in a single pass. Both callbacks are optional a |
36
+ | `mergeDeep` | Merges two or more objects deeply, returning a **new** object without mutating any input. Sources a |
37
37
  | `omit` | Creates a new object without the specified keys. |
38
- | `pick` | Creates a new object with only the specified keys. |
38
+ | `pick` | Creates a new object with only the specified keys. Keys that are prototype-polluting strings (`__pro |
39
39
  | `removeUndefinedNull` | Remove null and undefined values from an object. |
40
40
  | `safeJsonParse` | Parses a JSON string, returning `null` (or a fallback) on any parse failure. Unlike `JSON.parse`, t |
41
- | `set` | Sets a value in an object at the given path, creating intermediate objects as needed. **Three path |
41
+ | `set` | Sets a value in an object at the given path, creating intermediate objects as needed. **Two path fo |
42
42
 
43
43
  ---
44
44
 
45
45
  ## API Reference
46
46
 
47
+ ### `cloneDeep`
48
+
49
+ Creates a deep copy of an object or array.
50
+
51
+ Recursively clones own enumerable string keys. `Date` instances are
52
+ reconstructed with the same epoch value. Prototype-polluting keys
53
+ (`__proto__`, `constructor`, `prototype`) are silently skipped.
54
+ Does **not** handle circular references.
55
+
56
+ **Limitation:** symbol-keyed own properties are **not** copied — only string
57
+ keys are processed. Use `structuredClone` if symbol propagation is required.
58
+
59
+ ```typescript
60
+ import { cloneDeep } from '@helpers4/object';
61
+
62
+ cloneDeep<T>(obj: T): T
63
+ ```
64
+
65
+ **Parameters:**
66
+
67
+ - `obj: T` — The value to clone
68
+
69
+ **Returns:** `T` — A deep copy of `obj`
70
+
71
+ **Examples:**
72
+
73
+ *Clone a nested object*
74
+
75
+ Creates a deep copy — modifying the clone does not affect the original.
76
+
77
+ ```typescript
78
+ const original = { a: { b: 1 } };
79
+ const cloned = cloneDeep(original);
80
+ cloned.a.b = 2;
81
+ // original.a.b is still 1
82
+ ```
83
+
84
+ ---
85
+
47
86
  ### `compact`
48
87
 
49
88
  Removes all entries with falsy values (`false`, `null`, `undefined`, `0`, `""`, `NaN`) from an object.
89
+ Own keys that are prototype-polluting strings (`__proto__`, `constructor`, `prototype`) are
90
+ silently skipped.
50
91
 
51
92
  ```typescript
52
93
  import { compact } from '@helpers4/object';
@@ -106,98 +147,6 @@ compact({ name: 'Alice', email: '', age: 0, role: 'admin' })
106
147
 
107
148
  ---
108
149
 
109
- ### `deepClone`
110
-
111
- Creates a deep copy of an object or array
112
-
113
- ```typescript
114
- import { deepClone } from '@helpers4/object';
115
-
116
- deepClone<T>(obj: T): T
117
- ```
118
-
119
- **Parameters:**
120
-
121
- - `obj: T` — The object to clone
122
-
123
- **Returns:** `T` — Deep cloned object
124
-
125
- **Examples:**
126
-
127
- *Clone a nested object*
128
-
129
- Creates a deep copy — modifying the clone does not affect the original.
130
-
131
- ```typescript
132
- const original = { a: { b: 1 } };
133
- const cloned = deepClone(original);
134
- cloned.a.b = 2;
135
- // original.a.b is still 1
136
- ```
137
-
138
- ---
139
-
140
- ### `deepMerge`
141
-
142
- Merges two or more objects deeply.
143
-
144
- Recursively merges own enumerable properties — both string and symbol keys.
145
- Plain objects are merged recursively; all other values (arrays, class instances,
146
- primitives, etc.) are replaced by the source value.
147
- `undefined` source values do not overwrite existing target values.
148
-
149
- ```typescript
150
- import { deepMerge } from '@helpers4/object';
151
-
152
- deepMerge<T extends Record<PropertyKey, unknown>>(target: T, sources: Record<PropertyKey, unknown>[]): T
153
- ```
154
-
155
- **Parameters:**
156
-
157
- - `target: T` — The target object (mutated in place)
158
- - `sources: Record<PropertyKey, unknown>[]` — One or more source objects to merge into the target
159
-
160
- **Returns:** `T` — The mutated target
161
-
162
- ```typescript
163
- import { deepMerge } from '@helpers4/object';
164
-
165
- deepMerge(target: undefined, sources: Record<PropertyKey, unknown>[]): undefined
166
- ```
167
-
168
- **Parameters:**
169
-
170
- - `target: undefined` — The target object (mutated in place)
171
- - `sources: Record<PropertyKey, unknown>[]` — One or more source objects to merge into the target
172
-
173
- **Returns:** `undefined` — The mutated target
174
-
175
- ```typescript
176
- import { deepMerge } from '@helpers4/object';
177
-
178
- deepMerge(target: null, sources: Record<PropertyKey, unknown>[]): null
179
- ```
180
-
181
- **Parameters:**
182
-
183
- - `target: null` — The target object (mutated in place)
184
- - `sources: Record<PropertyKey, unknown>[]` — One or more source objects to merge into the target
185
-
186
- **Returns:** `null` — The mutated target
187
-
188
- **Examples:**
189
-
190
- *Merge two objects deeply*
191
-
192
- Recursively merges source properties into the target object.
193
-
194
- ```typescript
195
- deepMerge({ a: 1, b: { c: 2 } }, { b: { d: 3 }, e: 4 })
196
- // => { a: 1, b: { c: 2, d: 3 }, e: 4 }
197
- ```
198
-
199
- ---
200
-
201
150
  ### `diff`
202
151
 
203
152
  Structural object diff.
@@ -348,40 +297,59 @@ Gets a value from an object using a dot/bracket-notated path or explicit key arr
348
297
  **Two path forms are supported:**
349
298
 
350
299
  1. **String path** — dot notation (`'a.b.c'`) and bracket notation (`'layers[1].name'`)
351
- are both accepted and mixed freely. Segments are traversed as string keys; `[n]`
352
- indices become numeric keys.
300
+ are both accepted and mixed freely.
301
+ - Dot segments are always string keys: `'a.1'` → key `'1'` (string).
302
+ - Bracket segments are always number keys: `'a[1]'` → key `1` (number).
303
+ - String literal paths give **full compile-time type inference** on the return type.
304
+ - Dynamic (non-literal) strings return `unknown`.
353
305
 
354
306
  2. **Key array** (`PropertyKey[]`) — explicit array of `string | number | symbol` keys,
355
307
  no parsing performed. Enables symbol-keyed traversal and compile-time type inference:
356
308
  `get(obj, ['a', 'b'] as const)` infers the return type from the path.
357
309
 
310
+ Both forms support **all objects** (plain objects, arrays, class instances).
311
+ Symbol keys are only reachable via the key-array form.
312
+
358
313
  ```typescript
359
314
  import { get } from '@helpers4/object';
360
315
 
361
- get<T = unknown>(obj: unknown, path: string, defaultValue?: T): T | undefined
316
+ get<D>(obj: null | undefined, path: string | readonly PropertyKey[], defaultValue: D): D
362
317
  ```
363
318
 
364
319
  **Parameters:**
365
320
 
366
- - `obj: unknown` — The object to read from
367
- - `path: string` — Dot/bracket-notation string or explicit `PropertyKey[]`
368
- - `defaultValue?: T` — Returned when the path is absent or resolves to `undefined`
321
+ - `obj: null | undefined` — The object to read from
322
+ - `path: string | readonly PropertyKey[]` — Dot/bracket-notation string literal or explicit `PropertyKey[]`
323
+ - `defaultValue: D` — Returned when the path is absent or resolves to `undefined`
369
324
 
370
- **Returns:** `T | undefined` — The value at the path, or `defaultValue`
325
+ **Returns:** `D` — The value at the path, or `defaultValue`
371
326
 
372
327
  ```typescript
373
328
  import { get } from '@helpers4/object';
374
329
 
375
- get<T extends object, Path extends readonly PropertyKey[]>(obj: T, path: Path, defaultValue?: DeepGet<T, Path>): DeepGet<T, Path> | undefined
330
+ get(obj: null | undefined, path: string | readonly PropertyKey[]): undefined
376
331
  ```
377
332
 
378
333
  **Parameters:**
379
334
 
380
- - `obj: T` — The object to read from
381
- - `path: Path` — Dot/bracket-notation string or explicit `PropertyKey[]`
382
- - `defaultValue?: DeepGet<T, Path>` — Returned when the path is absent or resolves to `undefined`
335
+ - `obj: null | undefined` — The object to read from
336
+ - `path: string | readonly PropertyKey[]` — Dot/bracket-notation string literal or explicit `PropertyKey[]`
337
+
338
+ **Returns:** `undefined` — The value at the path, or `defaultValue`
383
339
 
384
- **Returns:** `DeepGet<T, Path> | undefined` — The value at the path, or `defaultValue`
340
+ ```typescript
341
+ import { get } from '@helpers4/object';
342
+
343
+ get<T extends object, P extends string | readonly PropertyKey[]>(obj: T | null | undefined, path: P, defaultValue?: DeepGet<T, ParsePath<P>>): DeepGet<T, ParsePath<P>> | undefined
344
+ ```
345
+
346
+ **Parameters:**
347
+
348
+ - `obj: T | null | undefined` — The object to read from
349
+ - `path: P` — Dot/bracket-notation string literal or explicit `PropertyKey[]`
350
+ - `defaultValue?: DeepGet<T, ParsePath<P>>` — Returned when the path is absent or resolves to `undefined`
351
+
352
+ **Returns:** `DeepGet<T, ParsePath<P>> | undefined` — The value at the path, or `defaultValue`
385
353
 
386
354
  **Examples:**
387
355
 
@@ -421,16 +389,19 @@ Groups an array of items by a key derived from each item.
421
389
 
422
390
  A thin, typed wrapper around `Object.groupBy` (ES2024) that works on
423
391
  older targets and provides stricter return-type inference.
392
+ `null` and `undefined` are treated as empty arrays and return `{}`.
393
+ Items whose computed key is a prototype-polluting string (`__proto__`,
394
+ `constructor`, `prototype`) are silently skipped.
424
395
 
425
396
  ```typescript
426
397
  import { groupBy } from '@helpers4/object';
427
398
 
428
- groupBy<T, K extends PropertyKey>(items: readonly T[], keyFn: function): Partial<Record<K, T[]>>
399
+ groupBy<T, K extends PropertyKey>(items: readonly T[] | null | undefined, keyFn: function): Partial<Record<K, T[]>>
429
400
  ```
430
401
 
431
402
  **Parameters:**
432
403
 
433
- - `items: readonly T[]` — The array to group
404
+ - `items: readonly T[] | null | undefined` — The array to group
434
405
  - `keyFn: function` — Function that returns the group key for each item
435
406
 
436
407
  **Returns:** `Partial<Record<K, T[]>>` — A record mapping each key to the array of items with that key
@@ -466,16 +437,19 @@ groupBy(users, u => u.role)
466
437
 
467
438
  Returns a new object with keys and values swapped.
468
439
  If multiple keys share the same value, the last one wins.
440
+ `null` and `undefined` are treated as empty objects and return `{}`.
441
+ Entries whose source key **or** value is a prototype-polluting string
442
+ (`__proto__`, `constructor`, `prototype`) are silently skipped.
469
443
 
470
444
  ```typescript
471
445
  import { invert } from '@helpers4/object';
472
446
 
473
- invert<K extends string, V extends PropertyKey>(obj: Record<K, V>): Record<V, K>
447
+ invert<K extends string, V extends PropertyKey>(obj: Record<K, V> | null | undefined): Record<V, K>
474
448
  ```
475
449
 
476
450
  **Parameters:**
477
451
 
478
- - `obj: Record<K, V>` — The object whose keys and values are to be swapped
452
+ - `obj: Record<K, V> | null | undefined` — The object whose keys and values are to be swapped
479
453
 
480
454
  **Returns:** `Record<V, K>` — A new object with values as keys and original keys as values
481
455
 
@@ -506,6 +480,7 @@ LABEL_TO_CODE['OK']; // => '200'
506
480
  ### `isEmpty`
507
481
 
508
482
  Checks if a plain object has no own enumerable string-keyed properties.
483
+ `null` and `undefined` are treated as empty objects and return `true`.
509
484
 
510
485
  Symbol-keyed properties are not counted. Use `Object.getOwnPropertySymbols`
511
486
  separately if symbol keys matter for your use case.
@@ -513,14 +488,14 @@ separately if symbol keys matter for your use case.
513
488
  ```typescript
514
489
  import { isEmpty } from '@helpers4/object';
515
490
 
516
- isEmpty(value: Record<PropertyKey, unknown>): boolean
491
+ isEmpty(value: Record<PropertyKey, unknown> | null | undefined): boolean
517
492
  ```
518
493
 
519
494
  **Parameters:**
520
495
 
521
- - `value: Record<PropertyKey, unknown>` — The object to check
496
+ - `value: Record<PropertyKey, unknown> | null | undefined` — The object to check
522
497
 
523
- **Returns:** `boolean` — `true` if the object has no own enumerable string-keyed properties
498
+ **Returns:** `boolean` — `true` if the object has no own enumerable string-keyed properties, or if `value` is `null`/`undefined`
524
499
 
525
500
  **Examples:**
526
501
 
@@ -549,6 +524,7 @@ isEmpty(obj) // => true (only string keys are counted)
549
524
  ### `isNonEmpty`
550
525
 
551
526
  Checks if a plain object has at least one own enumerable string-keyed property.
527
+ `null` and `undefined` are treated as empty objects and return `false`.
552
528
 
553
529
  Symbol-keyed properties are not counted. Use `Object.getOwnPropertySymbols`
554
530
  separately if symbol keys matter for your use case.
@@ -556,14 +532,15 @@ separately if symbol keys matter for your use case.
556
532
  ```typescript
557
533
  import { isNonEmpty } from '@helpers4/object';
558
534
 
559
- isNonEmpty(value: Record<PropertyKey, unknown>): boolean
535
+ isNonEmpty(value: Record<PropertyKey, unknown> | null | undefined): value is Record<PropertyKey, unknown>
560
536
  ```
561
537
 
562
538
  **Parameters:**
563
539
 
564
- - `value: Record<PropertyKey, unknown>` — The object to check
540
+ - `value: Record<PropertyKey, unknown> | null | undefined` — The object to check
565
541
 
566
- **Returns:** `boolean` — `true` if the object has at least one own enumerable string-keyed property
542
+ **Returns:** `value is Record<PropertyKey, unknown>` — `true` if the object has at least one own enumerable string-keyed property; `false` for empty, `null`, or `undefined`.
543
+ Acts as a type guard: the `if` branch narrows `Record<PropertyKey, unknown> | null | undefined` to `Record<PropertyKey, unknown>`.
567
544
 
568
545
  **Examples:**
569
546
 
@@ -604,17 +581,18 @@ When `mapValue` is omitted the original values are preserved;
604
581
  when `mapKey` is omitted the original keys are preserved.
605
582
 
606
583
  Note: if two different keys map to the same output key the last one wins
607
- (insertion order).
584
+ (insertion order). Entries whose mapped key is a prototype-polluting string
585
+ (`__proto__`, `constructor`, `prototype`) are silently skipped.
608
586
 
609
587
  ```typescript
610
588
  import { map } from '@helpers4/object';
611
589
 
612
- map<TObj extends Record<string, unknown>, TVal = indexedAccess, TKey extends PropertyKey = keyof TObj>(obj: TObj, mapValue?: function, mapKey?: function): Record<TKey, TVal>
590
+ map<TObj extends Record<string, unknown>, TVal = indexedAccess, TKey extends PropertyKey = keyof TObj>(obj: TObj | null | undefined, mapValue?: function, mapKey?: function): Record<TKey, TVal>
613
591
  ```
614
592
 
615
593
  **Parameters:**
616
594
 
617
- - `obj: TObj` — The source object
595
+ - `obj: TObj | null | undefined` — The source object
618
596
  - `mapValue?: function` — Callback called with `(value, key)` for each entry.
619
597
  Defaults to identity.
620
598
  - `mapKey?: function` — Callback called with `(key, value)` for each entry.
@@ -657,6 +635,67 @@ map(
657
635
 
658
636
  ---
659
637
 
638
+ ### `mergeDeep`
639
+
640
+ Merges two or more objects deeply, returning a **new** object without mutating any input.
641
+
642
+ Sources are applied left to right. For each key:
643
+ - If both sides are plain objects → merged recursively into a new object.
644
+ - Otherwise → **later source wins** (the earlier value is overwritten).
645
+ - `undefined` in a source **never overwrites** an existing value.
646
+ - `null` in a source **does overwrite** and prevents a later source from
647
+ deep-merging into that key: `mergeDeep({ a: { x: 1 } }, { a: null }, { a: { y: 2 } })`
648
+ → `{ a: { y: 2 } }` — `x: 1` is permanently lost.
649
+ - Arrays, class instances, and all non-plain-object values are **replaced**, not merged.
650
+
651
+ Own enumerable string and symbol keys of each source are processed at the top level;
652
+ inherited and non-enumerable properties are skipped. Prototype-polluting keys
653
+ (`__proto__`, `constructor`, `prototype`) are silently ignored.
654
+ **Note:** symbol keys inside nested plain-object values are not preserved — they are
655
+ lost when those values are deep-cloned. Only top-level symbol keys survive.
656
+
657
+ **TypeScript return type — intersection semantics**
658
+
659
+ The return type is `A & B & C …` (intersection of all source types). This is accurate
660
+ when keys are disjoint or share the same type:
661
+ ```ts
662
+ mergeDeep({ a: 1 }, { b: 'x' }) // { a: number } & { b: string } ✓
663
+ mergeDeep({ a: { b: 1 } }, { a: { c: 2 } }) // { a: { b: number } & { c: number } } ✓
664
+ ```
665
+ When the same key carries **incompatible types** across sources, the intersection
666
+ resolves to `never` for that key — TypeScript surfaces the conflict rather than
667
+ silently picking a type:
668
+ ```ts
669
+ mergeDeep({ a: 1 }, { a: 'x' }) // { a: never } ← type conflict detected
670
+ ```
671
+ At runtime the later source always wins (`'x'`), but the `never` type signals that
672
+ the caller should align their source types. If intentional, cast the result.
673
+
674
+ ```typescript
675
+ import { mergeDeep } from '@helpers4/object';
676
+
677
+ mergeDeep<T extends [object, rest]>(sources: [rest]): MergeResult<T>
678
+ ```
679
+
680
+ **Parameters:**
681
+
682
+ - `sources: [rest]` — Two or more objects to merge (none are mutated)
683
+
684
+ **Returns:** `MergeResult<T>` — A new object that is the deep merge of all sources
685
+
686
+ **Examples:**
687
+
688
+ *Merge two objects deeply*
689
+
690
+ Returns a new object — neither input is mutated.
691
+
692
+ ```typescript
693
+ mergeDeep({ a: 1, b: { c: 2 } }, { b: { d: 3 }, e: 4 })
694
+ // => { a: 1, b: { c: 2, d: 3 }, e: 4 }
695
+ ```
696
+
697
+ ---
698
+
660
699
  ### `omit`
661
700
 
662
701
  Creates a new object without the specified keys.
@@ -726,6 +765,8 @@ omit(user, ['password', 'token'])
726
765
  ### `pick`
727
766
 
728
767
  Creates a new object with only the specified keys.
768
+ Keys that are prototype-polluting strings (`__proto__`, `constructor`, `prototype`) are
769
+ silently skipped.
729
770
 
730
771
  ```typescript
731
772
  import { pick } from '@helpers4/object';
@@ -909,18 +950,19 @@ safeJsonParse('invalid', [])
909
950
 
910
951
  Sets a value in an object at the given path, creating intermediate objects as needed.
911
952
 
912
- **Three path forms are supported:**
953
+ **Two path forms are supported:**
913
954
 
914
- 1. **Dot notation** (`string`) — segments split on `.` are kept as-is string keys.
915
- `"layers.1.name"` → keys `["layers", "1", "name"]` (all strings, including `"1"`).
955
+ 1. **String path** — dot notation and bracket notation, mixed freely.
956
+ - Dot segments are always string keys: `'layers.1.name'` → keys `['layers', '1', 'name']`.
957
+ - Bracket segments are always number keys: `'layers[1].name'` → keys `['layers', 1, 'name']`.
958
+ - String literal paths give **full compile-time type inference** on the return type.
959
+ - Dynamic (non-literal) strings return `T` (same object type).
916
960
 
917
- 2. **Bracket notation** (`string`) — `[n]` segments are parsed as numeric keys.
918
- `"layers[1].name"` → keys `["layers", 1, "name"]` (index `1` becomes a number).
919
- Dot and bracket can be mixed freely: `"a[0].b[2].c"`.
961
+ 2. **Key array** (`PropertyKey[]`) — explicit array of `string | number | symbol` keys,
962
+ no parsing performed. Enables symbol-keyed access and precise return-type inference.
920
963
 
921
- 3. **Key array** (`PropertyKey[]`) — explicit array of `string | number | symbol` keys,
922
- no parsing performed. Enables full key-type control, including symbols:
923
- `["layers", 1, Symbol('id')]`.
964
+ Both forms support **all objects** (plain objects, arrays, class instances).
965
+ Symbol keys are only reachable via the key-array form.
924
966
 
925
967
  Intermediate nodes that are absent, `null`, or not an object are replaced with `{}`.
926
968
  Any path containing a string segment equal to `__proto__`, `constructor`, or `prototype`
@@ -929,30 +971,16 @@ is rejected and the original object is returned unchanged (prototype-pollution g
929
971
  ```typescript
930
972
  import { set } from '@helpers4/object';
931
973
 
932
- set(obj: Record<string, unknown>, path: string, value: unknown): Record<string, unknown>
933
- ```
934
-
935
- **Parameters:**
936
-
937
- - `obj: Record<string, unknown>` — The object to mutate
938
- - `path: string` — Dot/bracket-notation string or explicit `PropertyKey[]`
939
- - `value: unknown` — Value to assign at the path
940
-
941
- **Returns:** `Record<string, unknown>` — The mutated object (same reference)
942
-
943
- ```typescript
944
- import { set } from '@helpers4/object';
945
-
946
- set<T extends object, Path extends readonly PropertyKey[], V extends unknown>(obj: T, path: Path, value: V): DeepSet<T, Path, V>
974
+ set<T extends object, P extends string | readonly PropertyKey[], V extends unknown>(obj: T, path: P, value: V): conditional
947
975
  ```
948
976
 
949
977
  **Parameters:**
950
978
 
951
979
  - `obj: T` — The object to mutate
952
- - `path: Path` — Dot/bracket-notation string or explicit `PropertyKey[]`
980
+ - `path: P` — Dot/bracket-notation string literal or explicit `PropertyKey[]`
953
981
  - `value: V` — Value to assign at the path
954
982
 
955
- **Returns:** `DeepSet<T, Path, V>` — The mutated object (same reference)
983
+ **Returns:** `conditional` — The mutated object (same reference, narrowed type)
956
984
 
957
985
  **Examples:**
958
986