@helpers4/object 2.0.4 → 2.1.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: 2.0.4
4
+ > Package: `@helpers4/object` — Version: 2.1.0
5
5
  > License: LGPL-3.0-or-later
6
6
 
7
7
  ## Installation
@@ -15,35 +15,74 @@ 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
38
  | `pick` | Creates a new object with only the specified keys. |
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.
@@ -106,98 +145,6 @@ compact({ name: 'Alice', email: '', age: 0, role: 'admin' })
106
145
 
107
146
  ---
108
147
 
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
148
  ### `diff`
202
149
 
203
150
  Structural object diff.
@@ -348,40 +295,59 @@ Gets a value from an object using a dot/bracket-notated path or explicit key arr
348
295
  **Two path forms are supported:**
349
296
 
350
297
  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.
298
+ are both accepted and mixed freely.
299
+ - Dot segments are always string keys: `'a.1'` → key `'1'` (string).
300
+ - Bracket segments are always number keys: `'a[1]'` → key `1` (number).
301
+ - String literal paths give **full compile-time type inference** on the return type.
302
+ - Dynamic (non-literal) strings return `unknown`.
353
303
 
354
304
  2. **Key array** (`PropertyKey[]`) — explicit array of `string | number | symbol` keys,
355
305
  no parsing performed. Enables symbol-keyed traversal and compile-time type inference:
356
306
  `get(obj, ['a', 'b'] as const)` infers the return type from the path.
357
307
 
308
+ Both forms support **all objects** (plain objects, arrays, class instances).
309
+ Symbol keys are only reachable via the key-array form.
310
+
358
311
  ```typescript
359
312
  import { get } from '@helpers4/object';
360
313
 
361
- get<T = unknown>(obj: unknown, path: string, defaultValue?: T): T | undefined
314
+ get<D>(obj: null | undefined, path: string | readonly PropertyKey[], defaultValue: D): D
362
315
  ```
363
316
 
364
317
  **Parameters:**
365
318
 
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`
319
+ - `obj: null | undefined` — The object to read from
320
+ - `path: string | readonly PropertyKey[]` — Dot/bracket-notation string literal or explicit `PropertyKey[]`
321
+ - `defaultValue: D` — Returned when the path is absent or resolves to `undefined`
369
322
 
370
- **Returns:** `T | undefined` — The value at the path, or `defaultValue`
323
+ **Returns:** `D` — The value at the path, or `defaultValue`
371
324
 
372
325
  ```typescript
373
326
  import { get } from '@helpers4/object';
374
327
 
375
- get<T extends object, Path extends readonly PropertyKey[]>(obj: T, path: Path, defaultValue?: DeepGet<T, Path>): DeepGet<T, Path> | undefined
328
+ get(obj: null | undefined, path: string | readonly PropertyKey[]): undefined
376
329
  ```
377
330
 
378
331
  **Parameters:**
379
332
 
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`
333
+ - `obj: null | undefined` — The object to read from
334
+ - `path: string | readonly PropertyKey[]` — Dot/bracket-notation string literal or explicit `PropertyKey[]`
335
+
336
+ **Returns:** `undefined` — The value at the path, or `defaultValue`
383
337
 
384
- **Returns:** `DeepGet<T, Path> | undefined` — The value at the path, or `defaultValue`
338
+ ```typescript
339
+ import { get } from '@helpers4/object';
340
+
341
+ 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
342
+ ```
343
+
344
+ **Parameters:**
345
+
346
+ - `obj: T | null | undefined` — The object to read from
347
+ - `path: P` — Dot/bracket-notation string literal or explicit `PropertyKey[]`
348
+ - `defaultValue?: DeepGet<T, ParsePath<P>>` — Returned when the path is absent or resolves to `undefined`
349
+
350
+ **Returns:** `DeepGet<T, ParsePath<P>> | undefined` — The value at the path, or `defaultValue`
385
351
 
386
352
  **Examples:**
387
353
 
@@ -421,16 +387,19 @@ Groups an array of items by a key derived from each item.
421
387
 
422
388
  A thin, typed wrapper around `Object.groupBy` (ES2024) that works on
423
389
  older targets and provides stricter return-type inference.
390
+ `null` and `undefined` are treated as empty arrays and return `{}`.
391
+ Items whose computed key is a prototype-polluting string (`__proto__`,
392
+ `constructor`, `prototype`) are silently skipped.
424
393
 
425
394
  ```typescript
426
395
  import { groupBy } from '@helpers4/object';
427
396
 
428
- groupBy<T, K extends PropertyKey>(items: readonly T[], keyFn: function): Partial<Record<K, T[]>>
397
+ groupBy<T, K extends PropertyKey>(items: readonly T[] | null | undefined, keyFn: function): Partial<Record<K, T[]>>
429
398
  ```
430
399
 
431
400
  **Parameters:**
432
401
 
433
- - `items: readonly T[]` — The array to group
402
+ - `items: readonly T[] | null | undefined` — The array to group
434
403
  - `keyFn: function` — Function that returns the group key for each item
435
404
 
436
405
  **Returns:** `Partial<Record<K, T[]>>` — A record mapping each key to the array of items with that key
@@ -466,16 +435,19 @@ groupBy(users, u => u.role)
466
435
 
467
436
  Returns a new object with keys and values swapped.
468
437
  If multiple keys share the same value, the last one wins.
438
+ `null` and `undefined` are treated as empty objects and return `{}`.
439
+ Entries whose source key **or** value is a prototype-polluting string
440
+ (`__proto__`, `constructor`, `prototype`) are silently skipped.
469
441
 
470
442
  ```typescript
471
443
  import { invert } from '@helpers4/object';
472
444
 
473
- invert<K extends string, V extends PropertyKey>(obj: Record<K, V>): Record<V, K>
445
+ invert<K extends string, V extends PropertyKey>(obj: Record<K, V> | null | undefined): Record<V, K>
474
446
  ```
475
447
 
476
448
  **Parameters:**
477
449
 
478
- - `obj: Record<K, V>` — The object whose keys and values are to be swapped
450
+ - `obj: Record<K, V> | null | undefined` — The object whose keys and values are to be swapped
479
451
 
480
452
  **Returns:** `Record<V, K>` — A new object with values as keys and original keys as values
481
453
 
@@ -506,6 +478,7 @@ LABEL_TO_CODE['OK']; // => '200'
506
478
  ### `isEmpty`
507
479
 
508
480
  Checks if a plain object has no own enumerable string-keyed properties.
481
+ `null` and `undefined` are treated as empty objects and return `true`.
509
482
 
510
483
  Symbol-keyed properties are not counted. Use `Object.getOwnPropertySymbols`
511
484
  separately if symbol keys matter for your use case.
@@ -513,14 +486,14 @@ separately if symbol keys matter for your use case.
513
486
  ```typescript
514
487
  import { isEmpty } from '@helpers4/object';
515
488
 
516
- isEmpty(value: Record<PropertyKey, unknown>): boolean
489
+ isEmpty(value: Record<PropertyKey, unknown> | null | undefined): boolean
517
490
  ```
518
491
 
519
492
  **Parameters:**
520
493
 
521
- - `value: Record<PropertyKey, unknown>` — The object to check
494
+ - `value: Record<PropertyKey, unknown> | null | undefined` — The object to check
522
495
 
523
- **Returns:** `boolean` — `true` if the object has no own enumerable string-keyed properties
496
+ **Returns:** `boolean` — `true` if the object has no own enumerable string-keyed properties, or if `value` is `null`/`undefined`
524
497
 
525
498
  **Examples:**
526
499
 
@@ -549,6 +522,7 @@ isEmpty(obj) // => true (only string keys are counted)
549
522
  ### `isNonEmpty`
550
523
 
551
524
  Checks if a plain object has at least one own enumerable string-keyed property.
525
+ `null` and `undefined` are treated as empty objects and return `false`.
552
526
 
553
527
  Symbol-keyed properties are not counted. Use `Object.getOwnPropertySymbols`
554
528
  separately if symbol keys matter for your use case.
@@ -556,14 +530,15 @@ separately if symbol keys matter for your use case.
556
530
  ```typescript
557
531
  import { isNonEmpty } from '@helpers4/object';
558
532
 
559
- isNonEmpty(value: Record<PropertyKey, unknown>): boolean
533
+ isNonEmpty(value: Record<PropertyKey, unknown> | null | undefined): value is Record<PropertyKey, unknown>
560
534
  ```
561
535
 
562
536
  **Parameters:**
563
537
 
564
- - `value: Record<PropertyKey, unknown>` — The object to check
538
+ - `value: Record<PropertyKey, unknown> | null | undefined` — The object to check
565
539
 
566
- **Returns:** `boolean` — `true` if the object has at least one own enumerable string-keyed property
540
+ **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`.
541
+ Acts as a type guard: the `if` branch narrows `Record<PropertyKey, unknown> | null | undefined` to `Record<PropertyKey, unknown>`.
567
542
 
568
543
  **Examples:**
569
544
 
@@ -604,17 +579,18 @@ When `mapValue` is omitted the original values are preserved;
604
579
  when `mapKey` is omitted the original keys are preserved.
605
580
 
606
581
  Note: if two different keys map to the same output key the last one wins
607
- (insertion order).
582
+ (insertion order). Entries whose mapped key is a prototype-polluting string
583
+ (`__proto__`, `constructor`, `prototype`) are silently skipped.
608
584
 
609
585
  ```typescript
610
586
  import { map } from '@helpers4/object';
611
587
 
612
- map<TObj extends Record<string, unknown>, TVal = indexedAccess, TKey extends PropertyKey = keyof TObj>(obj: TObj, mapValue?: function, mapKey?: function): Record<TKey, TVal>
588
+ 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
589
  ```
614
590
 
615
591
  **Parameters:**
616
592
 
617
- - `obj: TObj` — The source object
593
+ - `obj: TObj | null | undefined` — The source object
618
594
  - `mapValue?: function` — Callback called with `(value, key)` for each entry.
619
595
  Defaults to identity.
620
596
  - `mapKey?: function` — Callback called with `(key, value)` for each entry.
@@ -657,6 +633,67 @@ map(
657
633
 
658
634
  ---
659
635
 
636
+ ### `mergeDeep`
637
+
638
+ Merges two or more objects deeply, returning a **new** object without mutating any input.
639
+
640
+ Sources are applied left to right. For each key:
641
+ - If both sides are plain objects → merged recursively into a new object.
642
+ - Otherwise → **later source wins** (the earlier value is overwritten).
643
+ - `undefined` in a source **never overwrites** an existing value.
644
+ - `null` in a source **does overwrite** and prevents a later source from
645
+ deep-merging into that key: `mergeDeep({ a: { x: 1 } }, { a: null }, { a: { y: 2 } })`
646
+ → `{ a: { y: 2 } }` — `x: 1` is permanently lost.
647
+ - Arrays, class instances, and all non-plain-object values are **replaced**, not merged.
648
+
649
+ Own enumerable string and symbol keys of each source are processed at the top level;
650
+ inherited and non-enumerable properties are skipped. Prototype-polluting keys
651
+ (`__proto__`, `constructor`, `prototype`) are silently ignored.
652
+ **Note:** symbol keys inside nested plain-object values are not preserved — they are
653
+ lost when those values are deep-cloned. Only top-level symbol keys survive.
654
+
655
+ **TypeScript return type — intersection semantics**
656
+
657
+ The return type is `A & B & C …` (intersection of all source types). This is accurate
658
+ when keys are disjoint or share the same type:
659
+ ```ts
660
+ mergeDeep({ a: 1 }, { b: 'x' }) // { a: number } & { b: string } ✓
661
+ mergeDeep({ a: { b: 1 } }, { a: { c: 2 } }) // { a: { b: number } & { c: number } } ✓
662
+ ```
663
+ When the same key carries **incompatible types** across sources, the intersection
664
+ resolves to `never` for that key — TypeScript surfaces the conflict rather than
665
+ silently picking a type:
666
+ ```ts
667
+ mergeDeep({ a: 1 }, { a: 'x' }) // { a: never } ← type conflict detected
668
+ ```
669
+ At runtime the later source always wins (`'x'`), but the `never` type signals that
670
+ the caller should align their source types. If intentional, cast the result.
671
+
672
+ ```typescript
673
+ import { mergeDeep } from '@helpers4/object';
674
+
675
+ mergeDeep<T extends [object, rest]>(sources: [rest]): MergeResult<T>
676
+ ```
677
+
678
+ **Parameters:**
679
+
680
+ - `sources: [rest]` — Two or more objects to merge (none are mutated)
681
+
682
+ **Returns:** `MergeResult<T>` — A new object that is the deep merge of all sources
683
+
684
+ **Examples:**
685
+
686
+ *Merge two objects deeply*
687
+
688
+ Returns a new object — neither input is mutated.
689
+
690
+ ```typescript
691
+ mergeDeep({ a: 1, b: { c: 2 } }, { b: { d: 3 }, e: 4 })
692
+ // => { a: 1, b: { c: 2, d: 3 }, e: 4 }
693
+ ```
694
+
695
+ ---
696
+
660
697
  ### `omit`
661
698
 
662
699
  Creates a new object without the specified keys.
@@ -909,18 +946,19 @@ safeJsonParse('invalid', [])
909
946
 
910
947
  Sets a value in an object at the given path, creating intermediate objects as needed.
911
948
 
912
- **Three path forms are supported:**
949
+ **Two path forms are supported:**
913
950
 
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"`).
951
+ 1. **String path** — dot notation and bracket notation, mixed freely.
952
+ - Dot segments are always string keys: `'layers.1.name'` → keys `['layers', '1', 'name']`.
953
+ - Bracket segments are always number keys: `'layers[1].name'` → keys `['layers', 1, 'name']`.
954
+ - String literal paths give **full compile-time type inference** on the return type.
955
+ - Dynamic (non-literal) strings return `T` (same object type).
916
956
 
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"`.
957
+ 2. **Key array** (`PropertyKey[]`) — explicit array of `string | number | symbol` keys,
958
+ no parsing performed. Enables symbol-keyed access and precise return-type inference.
920
959
 
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')]`.
960
+ Both forms support **all objects** (plain objects, arrays, class instances).
961
+ Symbol keys are only reachable via the key-array form.
924
962
 
925
963
  Intermediate nodes that are absent, `null`, or not an object are replaced with `{}`.
926
964
  Any path containing a string segment equal to `__proto__`, `constructor`, or `prototype`
@@ -929,30 +967,16 @@ is rejected and the original object is returned unchanged (prototype-pollution g
929
967
  ```typescript
930
968
  import { set } from '@helpers4/object';
931
969
 
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>
970
+ set<T extends object, P extends string | readonly PropertyKey[], V extends unknown>(obj: T, path: P, value: V): conditional
947
971
  ```
948
972
 
949
973
  **Parameters:**
950
974
 
951
975
  - `obj: T` — The object to mutate
952
- - `path: Path` — Dot/bracket-notation string or explicit `PropertyKey[]`
976
+ - `path: P` — Dot/bracket-notation string literal or explicit `PropertyKey[]`
953
977
  - `value: V` — Value to assign at the path
954
978
 
955
- **Returns:** `DeepSet<T, Path, V>` — The mutated object (same reference)
979
+ **Returns:** `conditional` — The mutated object (same reference, narrowed type)
956
980
 
957
981
  **Examples:**
958
982