@helpers4/object 2.0.3 → 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/meta/api.json CHANGED
@@ -1,98 +1,26 @@
1
1
  {
2
2
  "category": "object",
3
- "version": "2.0.3",
3
+ "version": "2.1.0",
4
4
  "functions": [
5
5
  {
6
- "name": "compact",
7
- "kind": "function",
8
- "description": "Removes all entries with falsy values (`false`, `null`, `undefined`, `0`, `\"\"`, `NaN`) from an object.",
9
- "since": "2.0.0",
10
- "signatures": [
11
- {
12
- "signature": "compact<T extends Record<string, unknown>>(obj: T): Partial<T>",
13
- "description": "Removes all entries with falsy values (`false`, `null`, `undefined`, `0`, `\"\"`, `NaN`) from an object.",
14
- "params": [
15
- {
16
- "name": "obj",
17
- "type": "T",
18
- "description": "The source object"
19
- }
20
- ],
21
- "returns": {
22
- "type": "Partial<T>",
23
- "description": "A new object containing only entries with truthy values"
24
- },
25
- "typeParameters": [
26
- {
27
- "name": "T",
28
- "constraint": "Record<string, unknown>"
29
- }
30
- ]
31
- },
32
- {
33
- "signature": "compact(obj: undefined): undefined",
34
- "description": "Removes all entries with falsy values (`false`, `null`, `undefined`, `0`, `\"\"`, `NaN`) from an object.",
35
- "params": [
36
- {
37
- "name": "obj",
38
- "type": "undefined",
39
- "description": "The source object"
40
- }
41
- ],
42
- "returns": {
43
- "type": "undefined",
44
- "description": "A new object containing only entries with truthy values"
45
- }
46
- },
47
- {
48
- "signature": "compact(obj: null): null",
49
- "description": "Removes all entries with falsy values (`false`, `null`, `undefined`, `0`, `\"\"`, `NaN`) from an object.",
50
- "params": [
51
- {
52
- "name": "obj",
53
- "type": "null",
54
- "description": "The source object"
55
- }
56
- ],
57
- "returns": {
58
- "type": "null",
59
- "description": "A new object containing only entries with truthy values"
60
- }
61
- }
62
- ],
63
- "examples": [
64
- {
65
- "title": "Remove falsy values from object",
66
- "description": "Removes all entries with falsy values (false, null, undefined, 0, \"\", NaN).",
67
- "code": "compact({ a: 1, b: null, c: '', d: 0, e: 'hello' })\n// => { a: 1, e: 'hello' }"
68
- },
69
- {
70
- "title": "Clean up API response",
71
- "description": "Useful to strip empty or missing fields before sending data.",
72
- "code": "compact({ name: 'Alice', email: '', age: 0, role: 'admin' })\n// => { name: 'Alice', role: 'admin' }"
73
- }
74
- ],
75
- "sourceFile": "compact.ts"
76
- },
77
- {
78
- "name": "deepClone",
6
+ "name": "cloneDeep",
79
7
  "kind": "function",
80
- "description": "Creates a deep copy of an object or array",
8
+ "description": "Creates a deep copy of an object or array.\n\nRecursively clones own enumerable string keys. `Date` instances are\nreconstructed with the same epoch value. Prototype-polluting keys\n(`__proto__`, `constructor`, `prototype`) are silently skipped.\nDoes **not** handle circular references.\n\n**Limitation:** symbol-keyed own properties are **not** copied — only string\nkeys are processed. Use `structuredClone` if symbol propagation is required.",
81
9
  "since": "1.9.0",
82
10
  "signatures": [
83
11
  {
84
- "signature": "deepClone<T>(obj: T): T",
85
- "description": "Creates a deep copy of an object or array",
12
+ "signature": "cloneDeep<T>(obj: T): T",
13
+ "description": "Creates a deep copy of an object or array.\n\nRecursively clones own enumerable string keys. `Date` instances are\nreconstructed with the same epoch value. Prototype-polluting keys\n(`__proto__`, `constructor`, `prototype`) are silently skipped.\nDoes **not** handle circular references.\n\n**Limitation:** symbol-keyed own properties are **not** copied — only string\nkeys are processed. Use `structuredClone` if symbol propagation is required.",
86
14
  "params": [
87
15
  {
88
16
  "name": "obj",
89
17
  "type": "T",
90
- "description": "The object to clone"
18
+ "description": "The value to clone"
91
19
  }
92
20
  ],
93
21
  "returns": {
94
22
  "type": "T",
95
- "description": "Deep cloned object"
23
+ "description": "A deep copy of `obj`"
96
24
  },
97
25
  "typeParameters": [
98
26
  {
@@ -105,35 +33,30 @@
105
33
  {
106
34
  "title": "Clone a nested object",
107
35
  "description": "Creates a deep copy — modifying the clone does not affect the original.",
108
- "code": "const original = { a: { b: 1 } };\nconst cloned = deepClone(original);\ncloned.a.b = 2;\n// original.a.b is still 1"
36
+ "code": "const original = { a: { b: 1 } };\nconst cloned = cloneDeep(original);\ncloned.a.b = 2;\n// original.a.b is still 1"
109
37
  }
110
38
  ],
111
- "sourceFile": "deepClone.ts"
39
+ "sourceFile": "cloneDeep.ts"
112
40
  },
113
41
  {
114
- "name": "deepMerge",
42
+ "name": "compact",
115
43
  "kind": "function",
116
- "description": "Merges two or more objects deeply",
117
- "since": "1.9.0",
44
+ "description": "Removes all entries with falsy values (`false`, `null`, `undefined`, `0`, `\"\"`, `NaN`) from an object.",
45
+ "since": "2.0.0",
118
46
  "signatures": [
119
47
  {
120
- "signature": "deepMerge<T extends Record<string, unknown>>(target: T, sources: Record<string, unknown>[]): T",
121
- "description": "Merges two or more objects deeply",
48
+ "signature": "compact<T extends Record<string, unknown>>(obj: T): Partial<T>",
49
+ "description": "Removes all entries with falsy values (`false`, `null`, `undefined`, `0`, `\"\"`, `NaN`) from an object.",
122
50
  "params": [
123
51
  {
124
- "name": "target",
52
+ "name": "obj",
125
53
  "type": "T",
126
- "description": "The target object"
127
- },
128
- {
129
- "name": "sources",
130
- "type": "Record<string, unknown>[]",
131
- "description": "The source objects to merge"
54
+ "description": "The source object"
132
55
  }
133
56
  ],
134
57
  "returns": {
135
- "type": "T",
136
- "description": "The merged object"
58
+ "type": "Partial<T>",
59
+ "description": "A new object containing only entries with truthy values"
137
60
  },
138
61
  "typeParameters": [
139
62
  {
@@ -143,54 +66,49 @@
143
66
  ]
144
67
  },
145
68
  {
146
- "signature": "deepMerge(target: undefined, sources: Record<string, unknown>[]): undefined",
147
- "description": "Merges two or more objects deeply",
69
+ "signature": "compact(obj: undefined): undefined",
70
+ "description": "Removes all entries with falsy values (`false`, `null`, `undefined`, `0`, `\"\"`, `NaN`) from an object.",
148
71
  "params": [
149
72
  {
150
- "name": "target",
73
+ "name": "obj",
151
74
  "type": "undefined",
152
- "description": "The target object"
153
- },
154
- {
155
- "name": "sources",
156
- "type": "Record<string, unknown>[]",
157
- "description": "The source objects to merge"
75
+ "description": "The source object"
158
76
  }
159
77
  ],
160
78
  "returns": {
161
79
  "type": "undefined",
162
- "description": "The merged object"
80
+ "description": "A new object containing only entries with truthy values"
163
81
  }
164
82
  },
165
83
  {
166
- "signature": "deepMerge(target: null, sources: Record<string, unknown>[]): null",
167
- "description": "Merges two or more objects deeply",
84
+ "signature": "compact(obj: null): null",
85
+ "description": "Removes all entries with falsy values (`false`, `null`, `undefined`, `0`, `\"\"`, `NaN`) from an object.",
168
86
  "params": [
169
87
  {
170
- "name": "target",
88
+ "name": "obj",
171
89
  "type": "null",
172
- "description": "The target object"
173
- },
174
- {
175
- "name": "sources",
176
- "type": "Record<string, unknown>[]",
177
- "description": "The source objects to merge"
90
+ "description": "The source object"
178
91
  }
179
92
  ],
180
93
  "returns": {
181
94
  "type": "null",
182
- "description": "The merged object"
95
+ "description": "A new object containing only entries with truthy values"
183
96
  }
184
97
  }
185
98
  ],
186
99
  "examples": [
187
100
  {
188
- "title": "Merge two objects deeply",
189
- "description": "Recursively merges source properties into the target object.",
190
- "code": "deepMerge({ a: 1, b: { c: 2 } }, { b: { d: 3 }, e: 4 })\n// => { a: 1, b: { c: 2, d: 3 }, e: 4 }"
101
+ "title": "Remove falsy values from object",
102
+ "description": "Removes all entries with falsy values (false, null, undefined, 0, \"\", NaN).",
103
+ "code": "compact({ a: 1, b: null, c: '', d: 0, e: 'hello' })\n// => { a: 1, e: 'hello' }"
104
+ },
105
+ {
106
+ "title": "Clean up API response",
107
+ "description": "Useful to strip empty or missing fields before sending data.",
108
+ "code": "compact({ name: 'Alice', email: '', age: 0, role: 'admin' })\n// => { name: 'Alice', role: 'admin' }"
191
109
  }
192
110
  ],
193
- "sourceFile": "deepMerge.ts"
111
+ "sourceFile": "compact.ts"
194
112
  },
195
113
  {
196
114
  "name": "diff",
@@ -331,38 +249,92 @@
331
249
  {
332
250
  "name": "get",
333
251
  "kind": "function",
334
- "description": "Gets a value from an object using a dot-notated path",
252
+ "description": "Gets a value from an object using a dot/bracket-notated path or explicit key array.\n\n**Two path forms are supported:**\n\n1. **String path** — dot notation (`'a.b.c'`) and bracket notation (`'layers[1].name'`)\n are both accepted and mixed freely.\n - Dot segments are always string keys: `'a.1'` → key `'1'` (string).\n - Bracket segments are always number keys: `'a[1]'` → key `1` (number).\n - String literal paths give **full compile-time type inference** on the return type.\n - Dynamic (non-literal) strings return `unknown`.\n\n2. **Key array** (`PropertyKey[]`) — explicit array of `string | number | symbol` keys,\n no parsing performed. Enables symbol-keyed traversal and compile-time type inference:\n `get(obj, ['a', 'b'] as const)` infers the return type from the path.\n\nBoth forms support **all objects** (plain objects, arrays, class instances).\nSymbol keys are only reachable via the key-array form.",
335
253
  "since": "1.9.0",
336
254
  "signatures": [
337
255
  {
338
- "signature": "get<T = unknown>(obj: unknown, path: string, defaultValue?: T): T | undefined",
339
- "description": "Gets a value from an object using a dot-notated path",
256
+ "signature": "get<D>(obj: null | undefined, path: string | readonly PropertyKey[], defaultValue: D): D",
257
+ "description": "Gets a value from an object using a dot/bracket-notated path or explicit key array.\n\n**Two path forms are supported:**\n\n1. **String path** — dot notation (`'a.b.c'`) and bracket notation (`'layers[1].name'`)\n are both accepted and mixed freely.\n - Dot segments are always string keys: `'a.1'` → key `'1'` (string).\n - Bracket segments are always number keys: `'a[1]'` → key `1` (number).\n - String literal paths give **full compile-time type inference** on the return type.\n - Dynamic (non-literal) strings return `unknown`.\n\n2. **Key array** (`PropertyKey[]`) — explicit array of `string | number | symbol` keys,\n no parsing performed. Enables symbol-keyed traversal and compile-time type inference:\n `get(obj, ['a', 'b'] as const)` infers the return type from the path.\n\nBoth forms support **all objects** (plain objects, arrays, class instances).\nSymbol keys are only reachable via the key-array form.",
340
258
  "params": [
341
259
  {
342
260
  "name": "obj",
343
- "type": "unknown",
344
- "description": "The object to get value from"
261
+ "type": "null | undefined",
262
+ "description": "The object to read from"
345
263
  },
346
264
  {
347
265
  "name": "path",
348
- "type": "string",
349
- "description": "The dot-notated path (e.g., 'a.b.c')"
266
+ "type": "string | readonly PropertyKey[]",
267
+ "description": "Dot/bracket-notation string literal or explicit `PropertyKey[]`"
350
268
  },
351
269
  {
352
270
  "name": "defaultValue",
353
- "type": "T",
354
- "description": "Default value if path doesn't exist",
271
+ "type": "D",
272
+ "description": "Returned when the path is absent or resolves to `undefined`"
273
+ }
274
+ ],
275
+ "returns": {
276
+ "type": "D",
277
+ "description": "The value at the path, or `defaultValue`"
278
+ },
279
+ "typeParameters": [
280
+ {
281
+ "name": "D"
282
+ }
283
+ ]
284
+ },
285
+ {
286
+ "signature": "get(obj: null | undefined, path: string | readonly PropertyKey[]): undefined",
287
+ "description": "Gets a value from an object using a dot/bracket-notated path or explicit key array.\n\n**Two path forms are supported:**\n\n1. **String path** — dot notation (`'a.b.c'`) and bracket notation (`'layers[1].name'`)\n are both accepted and mixed freely.\n - Dot segments are always string keys: `'a.1'` → key `'1'` (string).\n - Bracket segments are always number keys: `'a[1]'` → key `1` (number).\n - String literal paths give **full compile-time type inference** on the return type.\n - Dynamic (non-literal) strings return `unknown`.\n\n2. **Key array** (`PropertyKey[]`) — explicit array of `string | number | symbol` keys,\n no parsing performed. Enables symbol-keyed traversal and compile-time type inference:\n `get(obj, ['a', 'b'] as const)` infers the return type from the path.\n\nBoth forms support **all objects** (plain objects, arrays, class instances).\nSymbol keys are only reachable via the key-array form.",
288
+ "params": [
289
+ {
290
+ "name": "obj",
291
+ "type": "null | undefined",
292
+ "description": "The object to read from"
293
+ },
294
+ {
295
+ "name": "path",
296
+ "type": "string | readonly PropertyKey[]",
297
+ "description": "Dot/bracket-notation string literal or explicit `PropertyKey[]`"
298
+ }
299
+ ],
300
+ "returns": {
301
+ "type": "undefined",
302
+ "description": "The value at the path, or `defaultValue`"
303
+ }
304
+ },
305
+ {
306
+ "signature": "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",
307
+ "description": "Gets a value from an object using a dot/bracket-notated path or explicit key array.\n\n**Two path forms are supported:**\n\n1. **String path** — dot notation (`'a.b.c'`) and bracket notation (`'layers[1].name'`)\n are both accepted and mixed freely.\n - Dot segments are always string keys: `'a.1'` → key `'1'` (string).\n - Bracket segments are always number keys: `'a[1]'` → key `1` (number).\n - String literal paths give **full compile-time type inference** on the return type.\n - Dynamic (non-literal) strings return `unknown`.\n\n2. **Key array** (`PropertyKey[]`) — explicit array of `string | number | symbol` keys,\n no parsing performed. Enables symbol-keyed traversal and compile-time type inference:\n `get(obj, ['a', 'b'] as const)` infers the return type from the path.\n\nBoth forms support **all objects** (plain objects, arrays, class instances).\nSymbol keys are only reachable via the key-array form.",
308
+ "params": [
309
+ {
310
+ "name": "obj",
311
+ "type": "T | null | undefined",
312
+ "description": "The object to read from"
313
+ },
314
+ {
315
+ "name": "path",
316
+ "type": "P",
317
+ "description": "Dot/bracket-notation string literal or explicit `PropertyKey[]`"
318
+ },
319
+ {
320
+ "name": "defaultValue",
321
+ "type": "DeepGet<T, ParsePath<P>>",
322
+ "description": "Returned when the path is absent or resolves to `undefined`",
355
323
  "optional": true
356
324
  }
357
325
  ],
358
326
  "returns": {
359
- "type": "T | undefined",
360
- "description": "The value at the path or default value"
327
+ "type": "DeepGet<T, ParsePath<P>> | undefined",
328
+ "description": "The value at the path, or `defaultValue`"
361
329
  },
362
330
  "typeParameters": [
363
331
  {
364
332
  "name": "T",
365
- "default": "unknown"
333
+ "constraint": "object"
334
+ },
335
+ {
336
+ "name": "P",
337
+ "constraint": "string | readonly PropertyKey[]"
366
338
  }
367
339
  ]
368
340
  }
@@ -377,6 +349,11 @@
377
349
  "title": "Return default for missing path",
378
350
  "description": "Returns the default value when the path does not exist.",
379
351
  "code": "get({ a: 1 }, 'b.c', 'default')\n// => 'default'"
352
+ },
353
+ {
354
+ "title": "Get via key array (supports symbols)",
355
+ "description": "Pass an explicit PropertyKey[] to bypass parsing. Supports string, number, and symbol keys.",
356
+ "code": "const id = Symbol('id')\nget({ [id]: 'alice' }, [id])\n// => 'alice'"
380
357
  }
381
358
  ],
382
359
  "sourceFile": "get.ts"
@@ -384,16 +361,16 @@
384
361
  {
385
362
  "name": "groupBy",
386
363
  "kind": "function",
387
- "description": "Groups an array of items by a key derived from each item.\n\nA thin, typed wrapper around `Object.groupBy` (ES2024) that works on\nolder targets and provides stricter return-type inference.",
364
+ "description": "Groups an array of items by a key derived from each item.\n\nA thin, typed wrapper around `Object.groupBy` (ES2024) that works on\nolder targets and provides stricter return-type inference.\n`null` and `undefined` are treated as empty arrays and return `{}`.\nItems whose computed key is a prototype-polluting string (`__proto__`,\n`constructor`, `prototype`) are silently skipped.",
388
365
  "since": "2.0.0",
389
366
  "signatures": [
390
367
  {
391
- "signature": "groupBy<T, K extends PropertyKey>(items: readonly T[], keyFn: function): Partial<Record<K, T[]>>",
392
- "description": "Groups an array of items by a key derived from each item.\n\nA thin, typed wrapper around `Object.groupBy` (ES2024) that works on\nolder targets and provides stricter return-type inference.",
368
+ "signature": "groupBy<T, K extends PropertyKey>(items: readonly T[] | null | undefined, keyFn: function): Partial<Record<K, T[]>>",
369
+ "description": "Groups an array of items by a key derived from each item.\n\nA thin, typed wrapper around `Object.groupBy` (ES2024) that works on\nolder targets and provides stricter return-type inference.\n`null` and `undefined` are treated as empty arrays and return `{}`.\nItems whose computed key is a prototype-polluting string (`__proto__`,\n`constructor`, `prototype`) are silently skipped.",
393
370
  "params": [
394
371
  {
395
372
  "name": "items",
396
- "type": "readonly T[]",
373
+ "type": "readonly T[] | null | undefined",
397
374
  "description": "The array to group"
398
375
  },
399
376
  {
@@ -434,16 +411,16 @@
434
411
  {
435
412
  "name": "invert",
436
413
  "kind": "function",
437
- "description": "Returns a new object with keys and values swapped.\nIf multiple keys share the same value, the last one wins.",
414
+ "description": "Returns a new object with keys and values swapped.\nIf multiple keys share the same value, the last one wins.\n`null` and `undefined` are treated as empty objects and return `{}`.\nEntries whose source key **or** value is a prototype-polluting string\n(`__proto__`, `constructor`, `prototype`) are silently skipped.",
438
415
  "since": "2.0.0",
439
416
  "signatures": [
440
417
  {
441
- "signature": "invert<K extends string, V extends PropertyKey>(obj: Record<K, V>): Record<V, K>",
442
- "description": "Returns a new object with keys and values swapped.\nIf multiple keys share the same value, the last one wins.",
418
+ "signature": "invert<K extends string, V extends PropertyKey>(obj: Record<K, V> | null | undefined): Record<V, K>",
419
+ "description": "Returns a new object with keys and values swapped.\nIf multiple keys share the same value, the last one wins.\n`null` and `undefined` are treated as empty objects and return `{}`.\nEntries whose source key **or** value is a prototype-polluting string\n(`__proto__`, `constructor`, `prototype`) are silently skipped.",
443
420
  "params": [
444
421
  {
445
422
  "name": "obj",
446
- "type": "Record<K, V>",
423
+ "type": "Record<K, V> | null | undefined",
447
424
  "description": "The object whose keys and values are to be swapped"
448
425
  }
449
426
  ],
@@ -480,22 +457,22 @@
480
457
  {
481
458
  "name": "isEmpty",
482
459
  "kind": "function",
483
- "description": "Checks if a plain object has no own enumerable string-keyed properties.\n\nSymbol-keyed properties are not counted. Use `Object.getOwnPropertySymbols`\nseparately if symbol keys matter for your use case.",
460
+ "description": "Checks if a plain object has no own enumerable string-keyed properties.\n`null` and `undefined` are treated as empty objects and return `true`.\n\nSymbol-keyed properties are not counted. Use `Object.getOwnPropertySymbols`\nseparately if symbol keys matter for your use case.",
484
461
  "since": "2.0.3",
485
462
  "signatures": [
486
463
  {
487
- "signature": "isEmpty(value: Record<PropertyKey, unknown>): boolean",
488
- "description": "Checks if a plain object has no own enumerable string-keyed properties.\n\nSymbol-keyed properties are not counted. Use `Object.getOwnPropertySymbols`\nseparately if symbol keys matter for your use case.",
464
+ "signature": "isEmpty(value: Record<PropertyKey, unknown> | null | undefined): boolean",
465
+ "description": "Checks if a plain object has no own enumerable string-keyed properties.\n`null` and `undefined` are treated as empty objects and return `true`.\n\nSymbol-keyed properties are not counted. Use `Object.getOwnPropertySymbols`\nseparately if symbol keys matter for your use case.",
489
466
  "params": [
490
467
  {
491
468
  "name": "value",
492
- "type": "Record<PropertyKey, unknown>",
469
+ "type": "Record<PropertyKey, unknown> | null | undefined",
493
470
  "description": "The object to check"
494
471
  }
495
472
  ],
496
473
  "returns": {
497
474
  "type": "boolean",
498
- "description": "`true` if the object has no own enumerable string-keyed properties"
475
+ "description": "`true` if the object has no own enumerable string-keyed properties, or if `value` is `null`/`undefined`"
499
476
  }
500
477
  }
501
478
  ],
@@ -516,22 +493,22 @@
516
493
  {
517
494
  "name": "isNonEmpty",
518
495
  "kind": "function",
519
- "description": "Checks if a plain object has at least one own enumerable string-keyed property.\n\nSymbol-keyed properties are not counted. Use `Object.getOwnPropertySymbols`\nseparately if symbol keys matter for your use case.",
496
+ "description": "Checks if a plain object has at least one own enumerable string-keyed property.\n`null` and `undefined` are treated as empty objects and return `false`.\n\nSymbol-keyed properties are not counted. Use `Object.getOwnPropertySymbols`\nseparately if symbol keys matter for your use case.",
520
497
  "since": "2.0.3",
521
498
  "signatures": [
522
499
  {
523
- "signature": "isNonEmpty(value: Record<PropertyKey, unknown>): boolean",
524
- "description": "Checks if a plain object has at least one own enumerable string-keyed property.\n\nSymbol-keyed properties are not counted. Use `Object.getOwnPropertySymbols`\nseparately if symbol keys matter for your use case.",
500
+ "signature": "isNonEmpty(value: Record<PropertyKey, unknown> | null | undefined): value is Record<PropertyKey, unknown>",
501
+ "description": "Checks if a plain object has at least one own enumerable string-keyed property.\n`null` and `undefined` are treated as empty objects and return `false`.\n\nSymbol-keyed properties are not counted. Use `Object.getOwnPropertySymbols`\nseparately if symbol keys matter for your use case.",
525
502
  "params": [
526
503
  {
527
504
  "name": "value",
528
- "type": "Record<PropertyKey, unknown>",
505
+ "type": "Record<PropertyKey, unknown> | null | undefined",
529
506
  "description": "The object to check"
530
507
  }
531
508
  ],
532
509
  "returns": {
533
- "type": "boolean",
534
- "description": "`true` if the object has at least one own enumerable string-keyed property"
510
+ "type": "value is Record<PropertyKey, unknown>",
511
+ "description": "`true` if the object has at least one own enumerable string-keyed property; `false` for empty, `null`, or `undefined`.\nActs as a type guard: the `if` branch narrows `Record<PropertyKey, unknown> | null | undefined` to `Record<PropertyKey, unknown>`."
535
512
  }
536
513
  }
537
514
  ],
@@ -552,16 +529,16 @@
552
529
  {
553
530
  "name": "map",
554
531
  "kind": "function",
555
- "description": "Transforms the values and/or keys of a plain object in a single pass.\n\nBoth callbacks are optional and default to identity (no transformation).\nWhen `mapValue` is omitted the original values are preserved;\nwhen `mapKey` is omitted the original keys are preserved.\n\nNote: if two different keys map to the same output key the last one wins\n(insertion order).",
532
+ "description": "Transforms the values and/or keys of a plain object in a single pass.\n\nBoth callbacks are optional and default to identity (no transformation).\nWhen `mapValue` is omitted the original values are preserved;\nwhen `mapKey` is omitted the original keys are preserved.\n\nNote: if two different keys map to the same output key the last one wins\n(insertion order). Entries whose mapped key is a prototype-polluting string\n(`__proto__`, `constructor`, `prototype`) are silently skipped.",
556
533
  "since": "2.0.0",
557
534
  "signatures": [
558
535
  {
559
- "signature": "map<TObj extends Record<string, unknown>, TVal = indexedAccess, TKey extends PropertyKey = keyof TObj>(obj: TObj, mapValue?: function, mapKey?: function): Record<TKey, TVal>",
560
- "description": "Transforms the values and/or keys of a plain object in a single pass.\n\nBoth callbacks are optional and default to identity (no transformation).\nWhen `mapValue` is omitted the original values are preserved;\nwhen `mapKey` is omitted the original keys are preserved.\n\nNote: if two different keys map to the same output key the last one wins\n(insertion order).",
536
+ "signature": "map<TObj extends Record<string, unknown>, TVal = indexedAccess, TKey extends PropertyKey = keyof TObj>(obj: TObj | null | undefined, mapValue?: function, mapKey?: function): Record<TKey, TVal>",
537
+ "description": "Transforms the values and/or keys of a plain object in a single pass.\n\nBoth callbacks are optional and default to identity (no transformation).\nWhen `mapValue` is omitted the original values are preserved;\nwhen `mapKey` is omitted the original keys are preserved.\n\nNote: if two different keys map to the same output key the last one wins\n(insertion order). Entries whose mapped key is a prototype-polluting string\n(`__proto__`, `constructor`, `prototype`) are silently skipped.",
561
538
  "params": [
562
539
  {
563
540
  "name": "obj",
564
- "type": "TObj",
541
+ "type": "TObj | null | undefined",
565
542
  "description": "The source object"
566
543
  },
567
544
  {
@@ -617,6 +594,43 @@
617
594
  ],
618
595
  "sourceFile": "map.ts"
619
596
  },
597
+ {
598
+ "name": "mergeDeep",
599
+ "kind": "function",
600
+ "description": "Merges two or more objects deeply, returning a **new** object without mutating any input.\n\nSources are applied left to right. For each key:\n- If both sides are plain objects → merged recursively into a new object.\n- Otherwise → **later source wins** (the earlier value is overwritten).\n- `undefined` in a source **never overwrites** an existing value.\n- `null` in a source **does overwrite** and prevents a later source from\n deep-merging into that key: `mergeDeep({ a: { x: 1 } }, { a: null }, { a: { y: 2 } })`\n → `{ a: { y: 2 } }` — `x: 1` is permanently lost.\n- Arrays, class instances, and all non-plain-object values are **replaced**, not merged.\n\nOwn enumerable string and symbol keys of each source are processed at the top level;\ninherited and non-enumerable properties are skipped. Prototype-polluting keys\n(`__proto__`, `constructor`, `prototype`) are silently ignored.\n**Note:** symbol keys inside nested plain-object values are not preserved — they are\nlost when those values are deep-cloned. Only top-level symbol keys survive.\n\n**TypeScript return type — intersection semantics**\n\nThe return type is `A & B & C …` (intersection of all source types). This is accurate\nwhen keys are disjoint or share the same type:\n```ts\nmergeDeep({ a: 1 }, { b: 'x' }) // { a: number } & { b: string } ✓\nmergeDeep({ a: { b: 1 } }, { a: { c: 2 } }) // { a: { b: number } & { c: number } } ✓\n```\nWhen the same key carries **incompatible types** across sources, the intersection\nresolves to `never` for that key — TypeScript surfaces the conflict rather than\nsilently picking a type:\n```ts\nmergeDeep({ a: 1 }, { a: 'x' }) // { a: never } ← type conflict detected\n```\nAt runtime the later source always wins (`'x'`), but the `never` type signals that\nthe caller should align their source types. If intentional, cast the result.",
601
+ "since": "1.9.0",
602
+ "signatures": [
603
+ {
604
+ "signature": "mergeDeep<T extends [object, rest]>(sources: [rest]): MergeResult<T>",
605
+ "description": "Merges two or more objects deeply, returning a **new** object without mutating any input.\n\nSources are applied left to right. For each key:\n- If both sides are plain objects → merged recursively into a new object.\n- Otherwise → **later source wins** (the earlier value is overwritten).\n- `undefined` in a source **never overwrites** an existing value.\n- `null` in a source **does overwrite** and prevents a later source from\n deep-merging into that key: `mergeDeep({ a: { x: 1 } }, { a: null }, { a: { y: 2 } })`\n → `{ a: { y: 2 } }` — `x: 1` is permanently lost.\n- Arrays, class instances, and all non-plain-object values are **replaced**, not merged.\n\nOwn enumerable string and symbol keys of each source are processed at the top level;\ninherited and non-enumerable properties are skipped. Prototype-polluting keys\n(`__proto__`, `constructor`, `prototype`) are silently ignored.\n**Note:** symbol keys inside nested plain-object values are not preserved — they are\nlost when those values are deep-cloned. Only top-level symbol keys survive.\n\n**TypeScript return type — intersection semantics**\n\nThe return type is `A & B & C …` (intersection of all source types). This is accurate\nwhen keys are disjoint or share the same type:\n```ts\nmergeDeep({ a: 1 }, { b: 'x' }) // { a: number } & { b: string } ✓\nmergeDeep({ a: { b: 1 } }, { a: { c: 2 } }) // { a: { b: number } & { c: number } } ✓\n```\nWhen the same key carries **incompatible types** across sources, the intersection\nresolves to `never` for that key — TypeScript surfaces the conflict rather than\nsilently picking a type:\n```ts\nmergeDeep({ a: 1 }, { a: 'x' }) // { a: never } ← type conflict detected\n```\nAt runtime the later source always wins (`'x'`), but the `never` type signals that\nthe caller should align their source types. If intentional, cast the result.",
606
+ "params": [
607
+ {
608
+ "name": "sources",
609
+ "type": "[rest]",
610
+ "description": "Two or more objects to merge (none are mutated)"
611
+ }
612
+ ],
613
+ "returns": {
614
+ "type": "MergeResult<T>",
615
+ "description": "A new object that is the deep merge of all sources"
616
+ },
617
+ "typeParameters": [
618
+ {
619
+ "name": "T",
620
+ "constraint": "[object, rest]"
621
+ }
622
+ ]
623
+ }
624
+ ],
625
+ "examples": [
626
+ {
627
+ "title": "Merge two objects deeply",
628
+ "description": "Returns a new object — neither input is mutated.",
629
+ "code": "mergeDeep({ a: 1, b: { c: 2 } }, { b: { d: 3 }, e: 4 })\n// => { a: 1, b: { c: 2, d: 3 }, e: 4 }"
630
+ }
631
+ ],
632
+ "sourceFile": "mergeDeep.ts"
633
+ },
620
634
  {
621
635
  "name": "omit",
622
636
  "kind": "function",
@@ -940,40 +954,64 @@
940
954
  {
941
955
  "name": "set",
942
956
  "kind": "function",
943
- "description": "Sets a value in an object using a dot-notated path",
957
+ "description": "Sets a value in an object at the given path, creating intermediate objects as needed.\n\n**Two path forms are supported:**\n\n1. **String path** — dot notation and bracket notation, mixed freely.\n - Dot segments are always string keys: `'layers.1.name'` → keys `['layers', '1', 'name']`.\n - Bracket segments are always number keys: `'layers[1].name'` → keys `['layers', 1, 'name']`.\n - String literal paths give **full compile-time type inference** on the return type.\n - Dynamic (non-literal) strings return `T` (same object type).\n\n2. **Key array** (`PropertyKey[]`) — explicit array of `string | number | symbol` keys,\n no parsing performed. Enables symbol-keyed access and precise return-type inference.\n\nBoth forms support **all objects** (plain objects, arrays, class instances).\nSymbol keys are only reachable via the key-array form.\n\nIntermediate nodes that are absent, `null`, or not an object are replaced with `{}`.\nAny path containing a string segment equal to `__proto__`, `constructor`, or `prototype`\nis rejected and the original object is returned unchanged (prototype-pollution guard).",
944
958
  "since": "1.9.0",
945
959
  "signatures": [
946
960
  {
947
- "signature": "set(obj: Record<string, unknown>, path: string, value: unknown): Record<string, unknown>",
948
- "description": "Sets a value in an object using a dot-notated path",
961
+ "signature": "set<T extends object, P extends string | readonly PropertyKey[], V extends unknown>(obj: T, path: P, value: V): conditional",
962
+ "description": "Sets a value in an object at the given path, creating intermediate objects as needed.\n\n**Two path forms are supported:**\n\n1. **String path** — dot notation and bracket notation, mixed freely.\n - Dot segments are always string keys: `'layers.1.name'` → keys `['layers', '1', 'name']`.\n - Bracket segments are always number keys: `'layers[1].name'` → keys `['layers', 1, 'name']`.\n - String literal paths give **full compile-time type inference** on the return type.\n - Dynamic (non-literal) strings return `T` (same object type).\n\n2. **Key array** (`PropertyKey[]`) — explicit array of `string | number | symbol` keys,\n no parsing performed. Enables symbol-keyed access and precise return-type inference.\n\nBoth forms support **all objects** (plain objects, arrays, class instances).\nSymbol keys are only reachable via the key-array form.\n\nIntermediate nodes that are absent, `null`, or not an object are replaced with `{}`.\nAny path containing a string segment equal to `__proto__`, `constructor`, or `prototype`\nis rejected and the original object is returned unchanged (prototype-pollution guard).",
949
963
  "params": [
950
964
  {
951
965
  "name": "obj",
952
- "type": "Record<string, unknown>",
953
- "description": "The object to set value in"
966
+ "type": "T",
967
+ "description": "The object to mutate"
954
968
  },
955
969
  {
956
970
  "name": "path",
957
- "type": "string",
958
- "description": "The dot-notated path (e.g., 'a.b.c')"
971
+ "type": "P",
972
+ "description": "Dot/bracket-notation string literal or explicit `PropertyKey[]`"
959
973
  },
960
974
  {
961
975
  "name": "value",
962
- "type": "unknown",
963
- "description": "The value to set"
976
+ "type": "V",
977
+ "description": "Value to assign at the path"
964
978
  }
965
979
  ],
966
980
  "returns": {
967
- "type": "Record<string, unknown>",
968
- "description": "The modified object"
969
- }
981
+ "type": "conditional",
982
+ "description": "The mutated object (same reference, narrowed type)"
983
+ },
984
+ "typeParameters": [
985
+ {
986
+ "name": "T",
987
+ "constraint": "object"
988
+ },
989
+ {
990
+ "name": "P",
991
+ "constraint": "string | readonly PropertyKey[]"
992
+ },
993
+ {
994
+ "name": "V",
995
+ "constraint": "unknown"
996
+ }
997
+ ]
970
998
  }
971
999
  ],
972
1000
  "examples": [
973
1001
  {
974
- "title": "Set a nested property",
975
- "description": "Creates intermediate objects as needed along the dot-notated path.",
976
- "code": "set({}, 'a.b.c', 42)\n// => { a: { b: { c: 42 } } }"
1002
+ "title": "Set a nested property (dot notation)",
1003
+ "description": "Creates intermediate objects as needed. All segments are string keys — including numeric-looking ones like \"1\".",
1004
+ "code": "set({}, 'a.b.c', 42)\n// => { a: { b: { c: 42 } } }\n\nset({}, 'layers.1.name', 'bg')\n// => { layers: { '1': { name: 'bg' } } } // '1' is a string key"
1005
+ },
1006
+ {
1007
+ "title": "Set via bracket notation",
1008
+ "description": "Square-bracket indices become numeric keys. Useful when the path targets an array element.",
1009
+ "code": "const obj = { layers: [{}, { name: 'old' }] }\nset(obj, 'layers[1].name', 'new')\n// => { layers: [{}, { name: 'new' }] }"
1010
+ },
1011
+ {
1012
+ "title": "Set via key array (supports symbols)",
1013
+ "description": "Pass an explicit PropertyKey[] to bypass parsing. Supports string, number, and symbol keys.",
1014
+ "code": "const id = Symbol('id')\nset({}, ['user', id], 'alice')\n// => { user: { [id]: 'alice' } }"
977
1015
  }
978
1016
  ],
979
1017
  "sourceFile": "set.ts"