@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/meta/api.json CHANGED
@@ -1,7 +1,43 @@
1
1
  {
2
2
  "category": "object",
3
- "version": "2.0.4",
3
+ "version": "2.1.0",
4
4
  "functions": [
5
+ {
6
+ "name": "cloneDeep",
7
+ "kind": "function",
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.",
9
+ "since": "1.9.0",
10
+ "signatures": [
11
+ {
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.",
14
+ "params": [
15
+ {
16
+ "name": "obj",
17
+ "type": "T",
18
+ "description": "The value to clone"
19
+ }
20
+ ],
21
+ "returns": {
22
+ "type": "T",
23
+ "description": "A deep copy of `obj`"
24
+ },
25
+ "typeParameters": [
26
+ {
27
+ "name": "T"
28
+ }
29
+ ]
30
+ }
31
+ ],
32
+ "examples": [
33
+ {
34
+ "title": "Clone a nested object",
35
+ "description": "Creates a deep copy — modifying the clone does not affect the original.",
36
+ "code": "const original = { a: { b: 1 } };\nconst cloned = cloneDeep(original);\ncloned.a.b = 2;\n// original.a.b is still 1"
37
+ }
38
+ ],
39
+ "sourceFile": "cloneDeep.ts"
40
+ },
5
41
  {
6
42
  "name": "compact",
7
43
  "kind": "function",
@@ -74,124 +110,6 @@
74
110
  ],
75
111
  "sourceFile": "compact.ts"
76
112
  },
77
- {
78
- "name": "deepClone",
79
- "kind": "function",
80
- "description": "Creates a deep copy of an object or array",
81
- "since": "1.9.0",
82
- "signatures": [
83
- {
84
- "signature": "deepClone<T>(obj: T): T",
85
- "description": "Creates a deep copy of an object or array",
86
- "params": [
87
- {
88
- "name": "obj",
89
- "type": "T",
90
- "description": "The object to clone"
91
- }
92
- ],
93
- "returns": {
94
- "type": "T",
95
- "description": "Deep cloned object"
96
- },
97
- "typeParameters": [
98
- {
99
- "name": "T"
100
- }
101
- ]
102
- }
103
- ],
104
- "examples": [
105
- {
106
- "title": "Clone a nested object",
107
- "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"
109
- }
110
- ],
111
- "sourceFile": "deepClone.ts"
112
- },
113
- {
114
- "name": "deepMerge",
115
- "kind": "function",
116
- "description": "Merges two or more objects deeply.\n\nRecursively merges own enumerable properties — both string and symbol keys.\nPlain objects are merged recursively; all other values (arrays, class instances,\nprimitives, etc.) are replaced by the source value.\n`undefined` source values do not overwrite existing target values.",
117
- "since": "1.9.0",
118
- "signatures": [
119
- {
120
- "signature": "deepMerge<T extends Record<PropertyKey, unknown>>(target: T, sources: Record<PropertyKey, unknown>[]): T",
121
- "description": "Merges two or more objects deeply.\n\nRecursively merges own enumerable properties — both string and symbol keys.\nPlain objects are merged recursively; all other values (arrays, class instances,\nprimitives, etc.) are replaced by the source value.\n`undefined` source values do not overwrite existing target values.",
122
- "params": [
123
- {
124
- "name": "target",
125
- "type": "T",
126
- "description": "The target object (mutated in place)"
127
- },
128
- {
129
- "name": "sources",
130
- "type": "Record<PropertyKey, unknown>[]",
131
- "description": "One or more source objects to merge into the target"
132
- }
133
- ],
134
- "returns": {
135
- "type": "T",
136
- "description": "The mutated target"
137
- },
138
- "typeParameters": [
139
- {
140
- "name": "T",
141
- "constraint": "Record<PropertyKey, unknown>"
142
- }
143
- ]
144
- },
145
- {
146
- "signature": "deepMerge(target: undefined, sources: Record<PropertyKey, unknown>[]): undefined",
147
- "description": "Merges two or more objects deeply.\n\nRecursively merges own enumerable properties — both string and symbol keys.\nPlain objects are merged recursively; all other values (arrays, class instances,\nprimitives, etc.) are replaced by the source value.\n`undefined` source values do not overwrite existing target values.",
148
- "params": [
149
- {
150
- "name": "target",
151
- "type": "undefined",
152
- "description": "The target object (mutated in place)"
153
- },
154
- {
155
- "name": "sources",
156
- "type": "Record<PropertyKey, unknown>[]",
157
- "description": "One or more source objects to merge into the target"
158
- }
159
- ],
160
- "returns": {
161
- "type": "undefined",
162
- "description": "The mutated target"
163
- }
164
- },
165
- {
166
- "signature": "deepMerge(target: null, sources: Record<PropertyKey, unknown>[]): null",
167
- "description": "Merges two or more objects deeply.\n\nRecursively merges own enumerable properties — both string and symbol keys.\nPlain objects are merged recursively; all other values (arrays, class instances,\nprimitives, etc.) are replaced by the source value.\n`undefined` source values do not overwrite existing target values.",
168
- "params": [
169
- {
170
- "name": "target",
171
- "type": "null",
172
- "description": "The target object (mutated in place)"
173
- },
174
- {
175
- "name": "sources",
176
- "type": "Record<PropertyKey, unknown>[]",
177
- "description": "One or more source objects to merge into the target"
178
- }
179
- ],
180
- "returns": {
181
- "type": "null",
182
- "description": "The mutated target"
183
- }
184
- }
185
- ],
186
- "examples": [
187
- {
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 }"
191
- }
192
- ],
193
- "sourceFile": "deepMerge.ts"
194
- },
195
113
  {
196
114
  "name": "diff",
197
115
  "kind": "function",
@@ -331,64 +249,82 @@
331
249
  {
332
250
  "name": "get",
333
251
  "kind": "function",
334
- "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. Segments are traversed as string keys; `[n]`\n indices become numeric keys.\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.",
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/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. Segments are traversed as string keys; `[n]`\n indices become numeric keys.\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.",
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",
261
+ "type": "null | undefined",
344
262
  "description": "The object to read from"
345
263
  },
346
264
  {
347
265
  "name": "path",
348
- "type": "string",
349
- "description": "Dot/bracket-notation string or explicit `PropertyKey[]`"
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": "Returned when the path is absent or resolves to `undefined`",
355
- "optional": true
271
+ "type": "D",
272
+ "description": "Returned when the path is absent or resolves to `undefined`"
356
273
  }
357
274
  ],
358
275
  "returns": {
359
- "type": "T | undefined",
276
+ "type": "D",
360
277
  "description": "The value at the path, or `defaultValue`"
361
278
  },
362
279
  "typeParameters": [
363
280
  {
364
- "name": "T",
365
- "default": "unknown"
281
+ "name": "D"
366
282
  }
367
283
  ]
368
284
  },
369
285
  {
370
- "signature": "get<T extends object, Path extends readonly PropertyKey[]>(obj: T, path: Path, defaultValue?: DeepGet<T, Path>): DeepGet<T, Path> | undefined",
371
- "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. Segments are traversed as string keys; `[n]`\n indices become numeric keys.\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.",
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.",
372
288
  "params": [
373
289
  {
374
290
  "name": "obj",
375
- "type": "T",
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",
376
312
  "description": "The object to read from"
377
313
  },
378
314
  {
379
315
  "name": "path",
380
- "type": "Path",
381
- "description": "Dot/bracket-notation string or explicit `PropertyKey[]`"
316
+ "type": "P",
317
+ "description": "Dot/bracket-notation string literal or explicit `PropertyKey[]`"
382
318
  },
383
319
  {
384
320
  "name": "defaultValue",
385
- "type": "DeepGet<T, Path>",
321
+ "type": "DeepGet<T, ParsePath<P>>",
386
322
  "description": "Returned when the path is absent or resolves to `undefined`",
387
323
  "optional": true
388
324
  }
389
325
  ],
390
326
  "returns": {
391
- "type": "DeepGet<T, Path> | undefined",
327
+ "type": "DeepGet<T, ParsePath<P>> | undefined",
392
328
  "description": "The value at the path, or `defaultValue`"
393
329
  },
394
330
  "typeParameters": [
@@ -397,8 +333,8 @@
397
333
  "constraint": "object"
398
334
  },
399
335
  {
400
- "name": "Path",
401
- "constraint": "readonly PropertyKey[]"
336
+ "name": "P",
337
+ "constraint": "string | readonly PropertyKey[]"
402
338
  }
403
339
  ]
404
340
  }
@@ -425,16 +361,16 @@
425
361
  {
426
362
  "name": "groupBy",
427
363
  "kind": "function",
428
- "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.",
429
365
  "since": "2.0.0",
430
366
  "signatures": [
431
367
  {
432
- "signature": "groupBy<T, K extends PropertyKey>(items: readonly T[], keyFn: function): Partial<Record<K, T[]>>",
433
- "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.",
434
370
  "params": [
435
371
  {
436
372
  "name": "items",
437
- "type": "readonly T[]",
373
+ "type": "readonly T[] | null | undefined",
438
374
  "description": "The array to group"
439
375
  },
440
376
  {
@@ -475,16 +411,16 @@
475
411
  {
476
412
  "name": "invert",
477
413
  "kind": "function",
478
- "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.",
479
415
  "since": "2.0.0",
480
416
  "signatures": [
481
417
  {
482
- "signature": "invert<K extends string, V extends PropertyKey>(obj: Record<K, V>): Record<V, K>",
483
- "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.",
484
420
  "params": [
485
421
  {
486
422
  "name": "obj",
487
- "type": "Record<K, V>",
423
+ "type": "Record<K, V> | null | undefined",
488
424
  "description": "The object whose keys and values are to be swapped"
489
425
  }
490
426
  ],
@@ -521,22 +457,22 @@
521
457
  {
522
458
  "name": "isEmpty",
523
459
  "kind": "function",
524
- "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.",
525
461
  "since": "2.0.3",
526
462
  "signatures": [
527
463
  {
528
- "signature": "isEmpty(value: Record<PropertyKey, unknown>): boolean",
529
- "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.",
530
466
  "params": [
531
467
  {
532
468
  "name": "value",
533
- "type": "Record<PropertyKey, unknown>",
469
+ "type": "Record<PropertyKey, unknown> | null | undefined",
534
470
  "description": "The object to check"
535
471
  }
536
472
  ],
537
473
  "returns": {
538
474
  "type": "boolean",
539
- "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`"
540
476
  }
541
477
  }
542
478
  ],
@@ -557,22 +493,22 @@
557
493
  {
558
494
  "name": "isNonEmpty",
559
495
  "kind": "function",
560
- "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.",
561
497
  "since": "2.0.3",
562
498
  "signatures": [
563
499
  {
564
- "signature": "isNonEmpty(value: Record<PropertyKey, unknown>): boolean",
565
- "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.",
566
502
  "params": [
567
503
  {
568
504
  "name": "value",
569
- "type": "Record<PropertyKey, unknown>",
505
+ "type": "Record<PropertyKey, unknown> | null | undefined",
570
506
  "description": "The object to check"
571
507
  }
572
508
  ],
573
509
  "returns": {
574
- "type": "boolean",
575
- "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>`."
576
512
  }
577
513
  }
578
514
  ],
@@ -593,16 +529,16 @@
593
529
  {
594
530
  "name": "map",
595
531
  "kind": "function",
596
- "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.",
597
533
  "since": "2.0.0",
598
534
  "signatures": [
599
535
  {
600
- "signature": "map<TObj extends Record<string, unknown>, TVal = indexedAccess, TKey extends PropertyKey = keyof TObj>(obj: TObj, mapValue?: function, mapKey?: function): Record<TKey, TVal>",
601
- "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.",
602
538
  "params": [
603
539
  {
604
540
  "name": "obj",
605
- "type": "TObj",
541
+ "type": "TObj | null | undefined",
606
542
  "description": "The source object"
607
543
  },
608
544
  {
@@ -658,6 +594,43 @@
658
594
  ],
659
595
  "sourceFile": "map.ts"
660
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
+ },
661
634
  {
662
635
  "name": "omit",
663
636
  "kind": "function",
@@ -981,37 +954,12 @@
981
954
  {
982
955
  "name": "set",
983
956
  "kind": "function",
984
- "description": "Sets a value in an object at the given path, creating intermediate objects as needed.\n\n**Three path forms are supported:**\n\n1. **Dot notation** (`string`) — segments split on `.` are kept as-is string keys.\n `\"layers.1.name\"` → keys `[\"layers\", \"1\", \"name\"]` (all strings, including `\"1\"`).\n\n2. **Bracket notation** (`string`) — `[n]` segments are parsed as numeric keys.\n `\"layers[1].name\"` → keys `[\"layers\", 1, \"name\"]` (index `1` becomes a number).\n Dot and bracket can be mixed freely: `\"a[0].b[2].c\"`.\n\n3. **Key array** (`PropertyKey[]`) — explicit array of `string | number | symbol` keys,\n no parsing performed. Enables full key-type control, including symbols:\n `[\"layers\", 1, Symbol('id')]`.\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).",
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).",
985
958
  "since": "1.9.0",
986
959
  "signatures": [
987
960
  {
988
- "signature": "set(obj: Record<string, unknown>, path: string, value: unknown): Record<string, unknown>",
989
- "description": "Sets a value in an object at the given path, creating intermediate objects as needed.\n\n**Three path forms are supported:**\n\n1. **Dot notation** (`string`) — segments split on `.` are kept as-is string keys.\n `\"layers.1.name\"` → keys `[\"layers\", \"1\", \"name\"]` (all strings, including `\"1\"`).\n\n2. **Bracket notation** (`string`) — `[n]` segments are parsed as numeric keys.\n `\"layers[1].name\"` → keys `[\"layers\", 1, \"name\"]` (index `1` becomes a number).\n Dot and bracket can be mixed freely: `\"a[0].b[2].c\"`.\n\n3. **Key array** (`PropertyKey[]`) — explicit array of `string | number | symbol` keys,\n no parsing performed. Enables full key-type control, including symbols:\n `[\"layers\", 1, Symbol('id')]`.\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).",
990
- "params": [
991
- {
992
- "name": "obj",
993
- "type": "Record<string, unknown>",
994
- "description": "The object to mutate"
995
- },
996
- {
997
- "name": "path",
998
- "type": "string",
999
- "description": "Dot/bracket-notation string or explicit `PropertyKey[]`"
1000
- },
1001
- {
1002
- "name": "value",
1003
- "type": "unknown",
1004
- "description": "Value to assign at the path"
1005
- }
1006
- ],
1007
- "returns": {
1008
- "type": "Record<string, unknown>",
1009
- "description": "The mutated object (same reference)"
1010
- }
1011
- },
1012
- {
1013
- "signature": "set<T extends object, Path extends readonly PropertyKey[], V extends unknown>(obj: T, path: Path, value: V): DeepSet<T, Path, V>",
1014
- "description": "Sets a value in an object at the given path, creating intermediate objects as needed.\n\n**Three path forms are supported:**\n\n1. **Dot notation** (`string`) — segments split on `.` are kept as-is string keys.\n `\"layers.1.name\"` → keys `[\"layers\", \"1\", \"name\"]` (all strings, including `\"1\"`).\n\n2. **Bracket notation** (`string`) — `[n]` segments are parsed as numeric keys.\n `\"layers[1].name\"` → keys `[\"layers\", 1, \"name\"]` (index `1` becomes a number).\n Dot and bracket can be mixed freely: `\"a[0].b[2].c\"`.\n\n3. **Key array** (`PropertyKey[]`) — explicit array of `string | number | symbol` keys,\n no parsing performed. Enables full key-type control, including symbols:\n `[\"layers\", 1, Symbol('id')]`.\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).",
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).",
1015
963
  "params": [
1016
964
  {
1017
965
  "name": "obj",
@@ -1020,8 +968,8 @@
1020
968
  },
1021
969
  {
1022
970
  "name": "path",
1023
- "type": "Path",
1024
- "description": "Dot/bracket-notation string or explicit `PropertyKey[]`"
971
+ "type": "P",
972
+ "description": "Dot/bracket-notation string literal or explicit `PropertyKey[]`"
1025
973
  },
1026
974
  {
1027
975
  "name": "value",
@@ -1030,8 +978,8 @@
1030
978
  }
1031
979
  ],
1032
980
  "returns": {
1033
- "type": "DeepSet<T, Path, V>",
1034
- "description": "The mutated object (same reference)"
981
+ "type": "conditional",
982
+ "description": "The mutated object (same reference, narrowed type)"
1035
983
  },
1036
984
  "typeParameters": [
1037
985
  {
@@ -1039,8 +987,8 @@
1039
987
  "constraint": "object"
1040
988
  },
1041
989
  {
1042
- "name": "Path",
1043
- "constraint": "readonly PropertyKey[]"
990
+ "name": "P",
991
+ "constraint": "string | readonly PropertyKey[]"
1044
992
  },
1045
993
  {
1046
994
  "name": "V",
@@ -1,6 +1,16 @@
1
1
  {
2
2
  "category": "object",
3
3
  "functions": [
4
+ {
5
+ "name": "cloneDeep",
6
+ "examples": [
7
+ {
8
+ "title": "Clone a nested object",
9
+ "description": "Creates a deep copy — modifying the clone does not affect the original.",
10
+ "code": "const original = { a: { b: 1 } };\nconst cloned = cloneDeep(original);\ncloned.a.b = 2;\n// original.a.b is still 1"
11
+ }
12
+ ]
13
+ },
4
14
  {
5
15
  "name": "compact",
6
16
  "examples": [
@@ -16,26 +26,6 @@
16
26
  }
17
27
  ]
18
28
  },
19
- {
20
- "name": "deepClone",
21
- "examples": [
22
- {
23
- "title": "Clone a nested object",
24
- "description": "Creates a deep copy — modifying the clone does not affect the original.",
25
- "code": "const original = { a: { b: 1 } };\nconst cloned = deepClone(original);\ncloned.a.b = 2;\n// original.a.b is still 1"
26
- }
27
- ]
28
- },
29
- {
30
- "name": "deepMerge",
31
- "examples": [
32
- {
33
- "title": "Merge two objects deeply",
34
- "description": "Recursively merges source properties into the target object.",
35
- "code": "deepMerge({ a: 1, b: { c: 2 } }, { b: { d: 3 }, e: 4 })\n// => { a: 1, b: { c: 2, d: 3 }, e: 4 }"
36
- }
37
- ]
38
- },
39
29
  {
40
30
  "name": "diff",
41
31
  "examples": [
@@ -176,6 +166,16 @@
176
166
  }
177
167
  ]
178
168
  },
169
+ {
170
+ "name": "mergeDeep",
171
+ "examples": [
172
+ {
173
+ "title": "Merge two objects deeply",
174
+ "description": "Returns a new object — neither input is mutated.",
175
+ "code": "mergeDeep({ a: 1, b: { c: 2 } }, { b: { d: 3 }, e: 4 })\n// => { a: 1, b: { c: 2, d: 3 }, e: 4 }"
176
+ }
177
+ ]
178
+ },
179
179
  {
180
180
  "name": "omit",
181
181
  "examples": [
@@ -44,7 +44,7 @@
44
44
  "native": "{ ...a, ...b } or Object.assign({}, a, b)",
45
45
  "since": "ES2015",
46
46
  "example": "const merged = { ...defaults, ...options }",
47
- "notes": "For deep merge, use deepMerge from helpers4"
47
+ "notes": "For deep merge, use mergeDeep from helpers4"
48
48
  }
49
49
  ]
50
50
  }
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@helpers4/object",
3
- "version": "2.0.4",
3
+ "version": "2.1.0",
4
4
  "description": "A set of helpers in TS/JS, compatible with tree-shaking, for object.",
5
5
  "author": "baxyz <baxy@etik.com>",
6
6
  "license": "LGPL-3.0",
@@ -27,9 +27,8 @@
27
27
  "keywords": [
28
28
  "helpers",
29
29
  "object",
30
+ "cloneDeep",
30
31
  "compact",
31
- "deepClone",
32
- "deepMerge",
33
32
  "diff",
34
33
  "equalsDeep",
35
34
  "equalsShallow",
@@ -39,6 +38,7 @@
39
38
  "isEmpty",
40
39
  "isNonEmpty",
41
40
  "map",
41
+ "mergeDeep",
42
42
  "omit",
43
43
  "pick",
44
44
  "removeUndefinedNull",