@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/README.md +2 -2
- package/lib/index.d.ts +226 -74
- package/lib/index.js +99 -63
- package/lib/index.js.map +1 -1
- package/llms.txt +174 -150
- package/meta/api.json +145 -197
- package/meta/examples.json +20 -20
- package/meta/native-alternatives.json +1 -1
- package/package.json +3 -3
package/README.md
CHANGED
|
@@ -12,9 +12,8 @@ A set of helpers for working with URLs.
|
|
|
12
12
|
[https://helpers4.dev/typescript/categories/object/](https://helpers4.dev/typescript/categories/object/)
|
|
13
13
|
|
|
14
14
|
<!-- AUTOMATIC-METHODS -->
|
|
15
|
+
- cloneDeep
|
|
15
16
|
- compact
|
|
16
|
-
- deepClone
|
|
17
|
-
- deepMerge
|
|
18
17
|
- diff
|
|
19
18
|
- equalsDeep
|
|
20
19
|
- equalsShallow
|
|
@@ -24,6 +23,7 @@ A set of helpers for working with URLs.
|
|
|
24
23
|
- isEmpty
|
|
25
24
|
- isNonEmpty
|
|
26
25
|
- map
|
|
26
|
+
- mergeDeep
|
|
27
27
|
- omit
|
|
28
28
|
- pick
|
|
29
29
|
- removeUndefinedNull
|
package/lib/index.d.ts
CHANGED
|
@@ -4,30 +4,31 @@
|
|
|
4
4
|
* SPDX-License-Identifier: LGPL-3.0-or-later
|
|
5
5
|
*/
|
|
6
6
|
/**
|
|
7
|
-
*
|
|
8
|
-
*
|
|
9
|
-
*
|
|
7
|
+
* Creates a deep copy of an object or array.
|
|
8
|
+
*
|
|
9
|
+
* Recursively clones own enumerable string keys. `Date` instances are
|
|
10
|
+
* reconstructed with the same epoch value. Prototype-polluting keys
|
|
11
|
+
* (`__proto__`, `constructor`, `prototype`) are silently skipped.
|
|
12
|
+
* Does **not** handle circular references.
|
|
13
|
+
*
|
|
14
|
+
* **Limitation:** symbol-keyed own properties are **not** copied — only string
|
|
15
|
+
* keys are processed. Use `structuredClone` if symbol propagation is required.
|
|
16
|
+
*
|
|
17
|
+
* @param obj - The value to clone
|
|
18
|
+
* @returns A deep copy of `obj`
|
|
10
19
|
* @example
|
|
11
|
-
*
|
|
12
|
-
* // => { a: 1,
|
|
13
|
-
*
|
|
14
|
-
|
|
15
|
-
|
|
16
|
-
|
|
17
|
-
|
|
18
|
-
|
|
19
|
-
|
|
20
|
-
* This file is part of helpers4.
|
|
21
|
-
* Copyright (C) 2025 baxyz
|
|
22
|
-
* SPDX-License-Identifier: LGPL-3.0-or-later
|
|
23
|
-
*/
|
|
24
|
-
/**
|
|
25
|
-
* Creates a deep copy of an object or array
|
|
26
|
-
* @param obj - The object to clone
|
|
27
|
-
* @returns Deep cloned object
|
|
20
|
+
* cloneDeep({ a: { b: [1, 2] } })
|
|
21
|
+
* // => { a: { b: [1, 2] } } — fully independent copy
|
|
22
|
+
*
|
|
23
|
+
* cloneDeep(new Date('2024-01-01'))
|
|
24
|
+
* // => Date with the same timestamp
|
|
25
|
+
*
|
|
26
|
+
* // Primitives pass through unchanged
|
|
27
|
+
* cloneDeep(42) // => 42
|
|
28
|
+
* cloneDeep(null) // => null
|
|
28
29
|
* @since 1.9.0
|
|
29
30
|
*/
|
|
30
|
-
declare function
|
|
31
|
+
declare function cloneDeep<T>(obj: T): T;
|
|
31
32
|
|
|
32
33
|
/**
|
|
33
34
|
* This file is part of helpers4.
|
|
@@ -35,21 +36,17 @@ declare function deepClone<T>(obj: T): T;
|
|
|
35
36
|
* SPDX-License-Identifier: LGPL-3.0-or-later
|
|
36
37
|
*/
|
|
37
38
|
/**
|
|
38
|
-
*
|
|
39
|
-
*
|
|
40
|
-
*
|
|
41
|
-
*
|
|
42
|
-
*
|
|
43
|
-
*
|
|
44
|
-
*
|
|
45
|
-
* @param target - The target object (mutated in place)
|
|
46
|
-
* @param sources - One or more source objects to merge into the target
|
|
47
|
-
* @returns The mutated target
|
|
48
|
-
* @since 1.9.0
|
|
39
|
+
* Removes all entries with falsy values (`false`, `null`, `undefined`, `0`, `""`, `NaN`) from an object.
|
|
40
|
+
* @param obj - The source object
|
|
41
|
+
* @returns A new object containing only entries with truthy values
|
|
42
|
+
* @example
|
|
43
|
+
* compact({ a: 1, b: null, c: '', d: 0, e: 'hello' })
|
|
44
|
+
* // => { a: 1, e: 'hello' }
|
|
45
|
+
* @since 2.0.0
|
|
49
46
|
*/
|
|
50
|
-
declare function
|
|
51
|
-
declare function
|
|
52
|
-
declare function
|
|
47
|
+
declare function compact<T extends Record<string, unknown>>(obj: T): Partial<T>;
|
|
48
|
+
declare function compact(obj: undefined): undefined;
|
|
49
|
+
declare function compact(obj: null): null;
|
|
53
50
|
|
|
54
51
|
/**
|
|
55
52
|
* This file is part of helpers4.
|
|
@@ -168,11 +165,81 @@ declare function equalsShallow(objA: unknown, objB: unknown): boolean;
|
|
|
168
165
|
* Copyright (C) 2025 baxyz
|
|
169
166
|
* SPDX-License-Identifier: LGPL-3.0-or-later
|
|
170
167
|
*/
|
|
168
|
+
/**
|
|
169
|
+
* Keys that must never be set on a plain object (prototype-pollution guard).
|
|
170
|
+
* @ignore
|
|
171
|
+
*/
|
|
172
|
+
declare const UNSAFE_KEY_LIST: readonly ["__proto__", "constructor", "prototype"];
|
|
173
|
+
/** @ignore */
|
|
174
|
+
type UnsafeKey = (typeof UNSAFE_KEY_LIST)[number];
|
|
175
|
+
|
|
176
|
+
/**
|
|
177
|
+
* This file is part of helpers4.
|
|
178
|
+
* Copyright (C) 2025 baxyz
|
|
179
|
+
* SPDX-License-Identifier: LGPL-3.0-or-later
|
|
180
|
+
*/
|
|
181
|
+
|
|
182
|
+
/**
|
|
183
|
+
* Parses a `[n]` bracket index: numeric index strings become `number`, anything else stays `string`.
|
|
184
|
+
* @ignore
|
|
185
|
+
*/
|
|
186
|
+
type BracketKey<S extends string> = S extends `${number}` ? number : S;
|
|
187
|
+
/**
|
|
188
|
+
* Parses consecutive bracket segments: `'[0][1]'` → `[0, 1]`.
|
|
189
|
+
* @ignore
|
|
190
|
+
*/
|
|
191
|
+
type ParseBrackets<S extends string> = S extends `[${infer Idx}]${infer Rest}` ? [BracketKey<Idx>, ...ParseBrackets<Rest>] : [];
|
|
192
|
+
/**
|
|
193
|
+
* Parses a single dot-segment (no dots inside): `'arr[0]'` → `['arr', 0]`.
|
|
194
|
+
* @ignore
|
|
195
|
+
*/
|
|
196
|
+
type ParseSegment<S extends string> = S extends `${infer Name}[${infer Idx}]${infer Rest}` ? Name extends '' ? [BracketKey<Idx>, ...ParseBrackets<Rest>] : [Name, BracketKey<Idx>, ...ParseBrackets<Rest>] : [S];
|
|
197
|
+
/**
|
|
198
|
+
* Converts a dot/bracket path string literal into a `PropertyKey` tuple.
|
|
199
|
+
*
|
|
200
|
+
* - Dot segments produce string keys: `'a.1'` → `['a', '1']`
|
|
201
|
+
* - Bracket segments produce number keys: `'a[1]'` → `['a', 1]`
|
|
202
|
+
*
|
|
203
|
+
* Limitation: edge-case paths handled specially at runtime (empty string `''`,
|
|
204
|
+
* bare `'.'`, consecutive dots `'a..b'`) are not modelled at the type level.
|
|
205
|
+
*
|
|
206
|
+
* @ignore
|
|
207
|
+
*/
|
|
208
|
+
type ParseStringPath<S extends string> = string extends S ? readonly PropertyKey[] : S extends `${infer Head}.${infer Tail}` ? [...ParseSegment<Head>, ...ParseStringPath<Tail>] : ParseSegment<S>;
|
|
209
|
+
/**
|
|
210
|
+
* Normalises both path forms into a `PropertyKey` tuple for use with `DeepGet`/`DeepSet`.
|
|
211
|
+
* - `string` literal → parsed via `ParseStringPath`
|
|
212
|
+
* - `readonly PropertyKey[]` → used as-is
|
|
213
|
+
* @ignore
|
|
214
|
+
*/
|
|
215
|
+
type ParsePath<P extends string | readonly PropertyKey[]> = P extends string ? ParseStringPath<P> : P extends readonly PropertyKey[] ? P : never;
|
|
216
|
+
/**
|
|
217
|
+
* Returns `true` if any key in the tuple is a prototype-polluting string.
|
|
218
|
+
* Used to make `set` return `T` unchanged (instead of a misleading `DeepSet` type)
|
|
219
|
+
* when the path is rejected by the runtime guard.
|
|
220
|
+
* @ignore
|
|
221
|
+
*/
|
|
222
|
+
type ContainsUnsafeStringKey<Keys extends readonly PropertyKey[]> = Keys extends readonly [infer K, ...infer Rest extends readonly PropertyKey[]] ? K extends UnsafeKey ? true : ContainsUnsafeStringKey<Rest> : false;
|
|
223
|
+
/**
|
|
224
|
+
* Converts a union to an intersection: `A | B | C` → `A & B & C`.
|
|
225
|
+
* Used to derive the return type of `mergeDeep` from a tuple of source types.
|
|
226
|
+
* @ignore
|
|
227
|
+
*/
|
|
228
|
+
type UnionToIntersection<U> = (U extends any ? (x: U) => void : never) extends (x: infer I) => void ? I : never;
|
|
229
|
+
/**
|
|
230
|
+
* Return type of `mergeDeep`: intersection of all source types.
|
|
231
|
+
*
|
|
232
|
+
* @ignore
|
|
233
|
+
* @example
|
|
234
|
+
* MergeResult<[{ a: number }, { b: string }]> // { a: number } & { b: string }
|
|
235
|
+
* MergeResult<[{ a: { b: number } }, { a: { c: string } }]> // { a: { b: number } & { c: string } }
|
|
236
|
+
*/
|
|
237
|
+
type MergeResult<T extends object[]> = UnionToIntersection<T[number]>;
|
|
171
238
|
/**
|
|
172
239
|
* Resolves the value type at a given path within T.
|
|
173
240
|
* Returns `unknown` when the path doesn't match the shape of T.
|
|
174
241
|
*
|
|
175
|
-
* @
|
|
242
|
+
* @ignore
|
|
176
243
|
* @example
|
|
177
244
|
* DeepGet<{ a: { b: number } }, ['a', 'b']> // => number
|
|
178
245
|
* DeepGet<{ a: { b: number } }, ['a', 'x']> // => unknown
|
|
@@ -184,7 +251,7 @@ type DeepGet<T, Path extends readonly PropertyKey[]> = Path extends readonly []
|
|
|
184
251
|
* Resolves to `never` when a key in Path is not present in the corresponding level of T,
|
|
185
252
|
* surfacing invalid paths at the call site rather than silently returning T unchanged.
|
|
186
253
|
*
|
|
187
|
-
* @
|
|
254
|
+
* @ignore
|
|
188
255
|
* @example
|
|
189
256
|
* DeepSet<{ a: { b: number } }, ['a', 'b'], string>
|
|
190
257
|
* // => { a: { b: string } }
|
|
@@ -205,29 +272,36 @@ type DeepSet<T, Path extends readonly PropertyKey[], V> = Path extends readonly
|
|
|
205
272
|
* **Two path forms are supported:**
|
|
206
273
|
*
|
|
207
274
|
* 1. **String path** — dot notation (`'a.b.c'`) and bracket notation (`'layers[1].name'`)
|
|
208
|
-
* are both accepted and mixed freely.
|
|
209
|
-
*
|
|
275
|
+
* are both accepted and mixed freely.
|
|
276
|
+
* - Dot segments are always string keys: `'a.1'` → key `'1'` (string).
|
|
277
|
+
* - Bracket segments are always number keys: `'a[1]'` → key `1` (number).
|
|
278
|
+
* - String literal paths give **full compile-time type inference** on the return type.
|
|
279
|
+
* - Dynamic (non-literal) strings return `unknown`.
|
|
210
280
|
*
|
|
211
281
|
* 2. **Key array** (`PropertyKey[]`) — explicit array of `string | number | symbol` keys,
|
|
212
282
|
* no parsing performed. Enables symbol-keyed traversal and compile-time type inference:
|
|
213
283
|
* `get(obj, ['a', 'b'] as const)` infers the return type from the path.
|
|
214
284
|
*
|
|
285
|
+
* Both forms support **all objects** (plain objects, arrays, class instances).
|
|
286
|
+
* Symbol keys are only reachable via the key-array form.
|
|
287
|
+
*
|
|
215
288
|
* @param obj - The object to read from
|
|
216
|
-
* @param path - Dot/bracket-notation string or explicit `PropertyKey[]`
|
|
289
|
+
* @param path - Dot/bracket-notation string literal or explicit `PropertyKey[]`
|
|
217
290
|
* @param defaultValue - Returned when the path is absent or resolves to `undefined`
|
|
218
291
|
* @returns The value at the path, or `defaultValue`
|
|
219
292
|
* @example
|
|
220
|
-
* get({ a: { b: { c: 42 } } }, 'a.b.c') // => 42
|
|
221
|
-
* get({
|
|
222
|
-
* get({ a: { b: 0 } }, 'a.b', 99) // => 0 (falsy values
|
|
293
|
+
* get({ a: { b: { c: 42 } } }, 'a.b.c') // => 42 (inferred: number | undefined)
|
|
294
|
+
* get({ layers: [{ name: 'bg' }] }, 'layers[0].name') // => 'bg' (inferred: string | undefined)
|
|
295
|
+
* get({ a: { b: 0 } }, 'a.b', 99) // => 0 (falsy values returned as-is)
|
|
223
296
|
* get({ a: 1 }, 'b.c', 'default') // => 'default'
|
|
224
297
|
*
|
|
225
298
|
* const id = Symbol('id')
|
|
226
|
-
* get({ [id]: 'alice' }, [id]) // => 'alice' (symbol key)
|
|
299
|
+
* get({ [id]: 'alice' }, [id]) // => 'alice' (symbol key, inferred: string | undefined)
|
|
227
300
|
* @since 1.9.0
|
|
228
301
|
*/
|
|
229
|
-
declare function get<
|
|
230
|
-
declare function get
|
|
302
|
+
declare function get<D>(obj: null | undefined, path: string | readonly PropertyKey[], defaultValue: D): D;
|
|
303
|
+
declare function get(obj: null | undefined, path: string | readonly PropertyKey[]): undefined;
|
|
304
|
+
declare function get<T extends object, const P extends string | readonly PropertyKey[]>(obj: T | null | undefined, path: P, defaultValue?: DeepGet<T, ParsePath<P>>): DeepGet<T, ParsePath<P>> | undefined;
|
|
231
305
|
|
|
232
306
|
/**
|
|
233
307
|
* This file is part of helpers4.
|
|
@@ -239,6 +313,9 @@ declare function get<T extends object, const Path extends readonly PropertyKey[]
|
|
|
239
313
|
*
|
|
240
314
|
* A thin, typed wrapper around `Object.groupBy` (ES2024) that works on
|
|
241
315
|
* older targets and provides stricter return-type inference.
|
|
316
|
+
* `null` and `undefined` are treated as empty arrays and return `{}`.
|
|
317
|
+
* Items whose computed key is a prototype-polluting string (`__proto__`,
|
|
318
|
+
* `constructor`, `prototype`) are silently skipped.
|
|
242
319
|
*
|
|
243
320
|
* @param items - The array to group
|
|
244
321
|
* @param keyFn - Function that returns the group key for each item
|
|
@@ -246,9 +323,11 @@ declare function get<T extends object, const Path extends readonly PropertyKey[]
|
|
|
246
323
|
* @example
|
|
247
324
|
* groupBy([1, 2, 3, 4], n => n % 2 === 0 ? 'even' : 'odd')
|
|
248
325
|
* // => { odd: [1, 3], even: [2, 4] }
|
|
326
|
+
* groupBy(null, n => n % 2 === 0 ? 'even' : 'odd')
|
|
327
|
+
* // => {}
|
|
249
328
|
* @since 2.0.0
|
|
250
329
|
*/
|
|
251
|
-
declare function groupBy<T, K extends PropertyKey>(items: readonly T[], keyFn: (item: T) => K): Partial<Record<K, T[]>>;
|
|
330
|
+
declare function groupBy<T, K extends PropertyKey>(items: readonly T[] | null | undefined, keyFn: (item: T) => K): Partial<Record<K, T[]>>;
|
|
252
331
|
|
|
253
332
|
/**
|
|
254
333
|
* This file is part of helpers4.
|
|
@@ -258,6 +337,9 @@ declare function groupBy<T, K extends PropertyKey>(items: readonly T[], keyFn: (
|
|
|
258
337
|
/**
|
|
259
338
|
* Returns a new object with keys and values swapped.
|
|
260
339
|
* If multiple keys share the same value, the last one wins.
|
|
340
|
+
* `null` and `undefined` are treated as empty objects and return `{}`.
|
|
341
|
+
* Entries whose source key **or** value is a prototype-polluting string
|
|
342
|
+
* (`__proto__`, `constructor`, `prototype`) are silently skipped.
|
|
261
343
|
*
|
|
262
344
|
* @param obj - The object whose keys and values are to be swapped
|
|
263
345
|
* @returns A new object with values as keys and original keys as values
|
|
@@ -267,9 +349,12 @@ declare function groupBy<T, K extends PropertyKey>(items: readonly T[], keyFn: (
|
|
|
267
349
|
*
|
|
268
350
|
* invert({ one: 1, two: 2 })
|
|
269
351
|
* // => { 1: 'one', 2: 'two' }
|
|
352
|
+
*
|
|
353
|
+
* invert(null)
|
|
354
|
+
* // => {}
|
|
270
355
|
* @since 2.0.0
|
|
271
356
|
*/
|
|
272
|
-
declare function invert<K extends string, V extends PropertyKey>(obj: Record<K, V>): Record<V, K>;
|
|
357
|
+
declare function invert<K extends string, V extends PropertyKey>(obj: Record<K, V> | null | undefined): Record<V, K>;
|
|
273
358
|
|
|
274
359
|
/**
|
|
275
360
|
* This file is part of helpers4.
|
|
@@ -278,19 +363,22 @@ declare function invert<K extends string, V extends PropertyKey>(obj: Record<K,
|
|
|
278
363
|
*/
|
|
279
364
|
/**
|
|
280
365
|
* Checks if a plain object has no own enumerable string-keyed properties.
|
|
366
|
+
* `null` and `undefined` are treated as empty objects and return `true`.
|
|
281
367
|
*
|
|
282
368
|
* Symbol-keyed properties are not counted. Use `Object.getOwnPropertySymbols`
|
|
283
369
|
* separately if symbol keys matter for your use case.
|
|
284
370
|
*
|
|
285
371
|
* @param value - The object to check
|
|
286
|
-
* @returns `true` if the object has no own enumerable string-keyed properties
|
|
372
|
+
* @returns `true` if the object has no own enumerable string-keyed properties, or if `value` is `null`/`undefined`
|
|
287
373
|
* @example
|
|
288
374
|
* isEmpty({}) // => true
|
|
375
|
+
* isEmpty(null) // => true
|
|
376
|
+
* isEmpty(undefined) // => true
|
|
289
377
|
* isEmpty({ a: 1 }) // => false
|
|
290
378
|
* isEmpty({ a: undefined }) // => false (key exists)
|
|
291
379
|
* @since 2.0.3
|
|
292
380
|
*/
|
|
293
|
-
declare function isEmpty(value: Record<PropertyKey, unknown>): boolean;
|
|
381
|
+
declare function isEmpty(value: Record<PropertyKey, unknown> | null | undefined): boolean;
|
|
294
382
|
|
|
295
383
|
/**
|
|
296
384
|
* This file is part of helpers4.
|
|
@@ -299,18 +387,22 @@ declare function isEmpty(value: Record<PropertyKey, unknown>): boolean;
|
|
|
299
387
|
*/
|
|
300
388
|
/**
|
|
301
389
|
* Checks if a plain object has at least one own enumerable string-keyed property.
|
|
390
|
+
* `null` and `undefined` are treated as empty objects and return `false`.
|
|
302
391
|
*
|
|
303
392
|
* Symbol-keyed properties are not counted. Use `Object.getOwnPropertySymbols`
|
|
304
393
|
* separately if symbol keys matter for your use case.
|
|
305
394
|
*
|
|
306
395
|
* @param value - The object to check
|
|
307
|
-
* @returns `true` if the object has at least one own enumerable string-keyed property
|
|
396
|
+
* @returns `true` if the object has at least one own enumerable string-keyed property; `false` for empty, `null`, or `undefined`.
|
|
397
|
+
* Acts as a type guard: the `if` branch narrows `Record<PropertyKey, unknown> | null | undefined` to `Record<PropertyKey, unknown>`.
|
|
308
398
|
* @example
|
|
309
399
|
* isNonEmpty({ a: 1 }) // => true
|
|
310
400
|
* isNonEmpty({}) // => false
|
|
401
|
+
* isNonEmpty(null) // => false
|
|
402
|
+
* isNonEmpty(undefined) // => false
|
|
311
403
|
* @since 2.0.3
|
|
312
404
|
*/
|
|
313
|
-
declare function isNonEmpty(value: Record<PropertyKey, unknown>):
|
|
405
|
+
declare function isNonEmpty(value: Record<PropertyKey, unknown> | null | undefined): value is Record<PropertyKey, unknown>;
|
|
314
406
|
|
|
315
407
|
/**
|
|
316
408
|
* This file is part of helpers4.
|
|
@@ -325,7 +417,8 @@ declare function isNonEmpty(value: Record<PropertyKey, unknown>): boolean;
|
|
|
325
417
|
* when `mapKey` is omitted the original keys are preserved.
|
|
326
418
|
*
|
|
327
419
|
* Note: if two different keys map to the same output key the last one wins
|
|
328
|
-
* (insertion order).
|
|
420
|
+
* (insertion order). Entries whose mapped key is a prototype-polluting string
|
|
421
|
+
* (`__proto__`, `constructor`, `prototype`) are silently skipped.
|
|
329
422
|
*
|
|
330
423
|
* @param obj - The source object
|
|
331
424
|
* @param mapValue - Callback called with `(value, key)` for each entry.
|
|
@@ -344,7 +437,69 @@ declare function isNonEmpty(value: Record<PropertyKey, unknown>): boolean;
|
|
|
344
437
|
* // => { A: 10, B: 20 }
|
|
345
438
|
* @since 2.0.0
|
|
346
439
|
*/
|
|
347
|
-
declare function map<TObj extends Record<string, unknown>, TVal = TObj[keyof TObj], TKey extends PropertyKey = keyof TObj>(obj: TObj, mapValue?: (value: TObj[keyof TObj], key: keyof TObj) => TVal, mapKey?: (key: keyof TObj, value: TObj[keyof TObj]) => TKey): Record<TKey, TVal>;
|
|
440
|
+
declare function map<TObj extends Record<string, unknown>, TVal = TObj[keyof TObj], TKey extends PropertyKey = keyof TObj>(obj: TObj | null | undefined, mapValue?: (value: TObj[keyof TObj], key: keyof TObj) => TVal, mapKey?: (key: keyof TObj, value: TObj[keyof TObj]) => TKey): Record<TKey, TVal>;
|
|
441
|
+
|
|
442
|
+
/**
|
|
443
|
+
* This file is part of helpers4.
|
|
444
|
+
* Copyright (C) 2025 baxyz
|
|
445
|
+
* SPDX-License-Identifier: LGPL-3.0-or-later
|
|
446
|
+
*/
|
|
447
|
+
|
|
448
|
+
/**
|
|
449
|
+
* Merges two or more objects deeply, returning a **new** object without mutating any input.
|
|
450
|
+
*
|
|
451
|
+
* Sources are applied left to right. For each key:
|
|
452
|
+
* - If both sides are plain objects → merged recursively into a new object.
|
|
453
|
+
* - Otherwise → **later source wins** (the earlier value is overwritten).
|
|
454
|
+
* - `undefined` in a source **never overwrites** an existing value.
|
|
455
|
+
* - `null` in a source **does overwrite** and prevents a later source from
|
|
456
|
+
* deep-merging into that key: `mergeDeep({ a: { x: 1 } }, { a: null }, { a: { y: 2 } })`
|
|
457
|
+
* → `{ a: { y: 2 } }` — `x: 1` is permanently lost.
|
|
458
|
+
* - Arrays, class instances, and all non-plain-object values are **replaced**, not merged.
|
|
459
|
+
*
|
|
460
|
+
* Own enumerable string and symbol keys of each source are processed at the top level;
|
|
461
|
+
* inherited and non-enumerable properties are skipped. Prototype-polluting keys
|
|
462
|
+
* (`__proto__`, `constructor`, `prototype`) are silently ignored.
|
|
463
|
+
* **Note:** symbol keys inside nested plain-object values are not preserved — they are
|
|
464
|
+
* lost when those values are deep-cloned. Only top-level symbol keys survive.
|
|
465
|
+
*
|
|
466
|
+
* **TypeScript return type — intersection semantics**
|
|
467
|
+
*
|
|
468
|
+
* The return type is `A & B & C …` (intersection of all source types). This is accurate
|
|
469
|
+
* when keys are disjoint or share the same type:
|
|
470
|
+
* ```ts
|
|
471
|
+
* mergeDeep({ a: 1 }, { b: 'x' }) // { a: number } & { b: string } ✓
|
|
472
|
+
* mergeDeep({ a: { b: 1 } }, { a: { c: 2 } }) // { a: { b: number } & { c: number } } ✓
|
|
473
|
+
* ```
|
|
474
|
+
* When the same key carries **incompatible types** across sources, the intersection
|
|
475
|
+
* resolves to `never` for that key — TypeScript surfaces the conflict rather than
|
|
476
|
+
* silently picking a type:
|
|
477
|
+
* ```ts
|
|
478
|
+
* mergeDeep({ a: 1 }, { a: 'x' }) // { a: never } ← type conflict detected
|
|
479
|
+
* ```
|
|
480
|
+
* At runtime the later source always wins (`'x'`), but the `never` type signals that
|
|
481
|
+
* the caller should align their source types. If intentional, cast the result.
|
|
482
|
+
*
|
|
483
|
+
* @param sources - Two or more objects to merge (none are mutated)
|
|
484
|
+
* @returns A new object that is the deep merge of all sources
|
|
485
|
+
* @example
|
|
486
|
+
* mergeDeep({ a: 1, b: { c: 2 } }, { b: { d: 3 }, e: 4 })
|
|
487
|
+
* // => { a: 1, b: { c: 2, d: 3 }, e: 4 }
|
|
488
|
+
*
|
|
489
|
+
* // Later source wins for non-object values
|
|
490
|
+
* mergeDeep({ a: 1, arr: [1, 2] }, { a: 2, arr: [3] })
|
|
491
|
+
* // => { a: 2, arr: [3] }
|
|
492
|
+
*
|
|
493
|
+
* // undefined never overwrites
|
|
494
|
+
* mergeDeep({ a: 1 }, { a: undefined })
|
|
495
|
+
* // => { a: 1 }
|
|
496
|
+
*
|
|
497
|
+
* // Three sources, left to right
|
|
498
|
+
* mergeDeep({ x: 1 }, { y: 2 }, { x: 99, z: 3 })
|
|
499
|
+
* // => { x: 99, y: 2, z: 3 }
|
|
500
|
+
* @since 1.9.0
|
|
501
|
+
*/
|
|
502
|
+
declare function mergeDeep<T extends [object, ...object[]]>(...sources: [...T]): MergeResult<T>;
|
|
348
503
|
|
|
349
504
|
/**
|
|
350
505
|
* This file is part of helpers4.
|
|
@@ -446,47 +601,44 @@ declare function safeJsonParse<T>(input: string, fallback: T): T;
|
|
|
446
601
|
/**
|
|
447
602
|
* Sets a value in an object at the given path, creating intermediate objects as needed.
|
|
448
603
|
*
|
|
449
|
-
* **
|
|
604
|
+
* **Two path forms are supported:**
|
|
450
605
|
*
|
|
451
|
-
* 1. **
|
|
452
|
-
* `
|
|
606
|
+
* 1. **String path** — dot notation and bracket notation, mixed freely.
|
|
607
|
+
* - Dot segments are always string keys: `'layers.1.name'` → keys `['layers', '1', 'name']`.
|
|
608
|
+
* - Bracket segments are always number keys: `'layers[1].name'` → keys `['layers', 1, 'name']`.
|
|
609
|
+
* - String literal paths give **full compile-time type inference** on the return type.
|
|
610
|
+
* - Dynamic (non-literal) strings return `T` (same object type).
|
|
453
611
|
*
|
|
454
|
-
* 2. **
|
|
455
|
-
*
|
|
456
|
-
* Dot and bracket can be mixed freely: `"a[0].b[2].c"`.
|
|
612
|
+
* 2. **Key array** (`PropertyKey[]`) — explicit array of `string | number | symbol` keys,
|
|
613
|
+
* no parsing performed. Enables symbol-keyed access and precise return-type inference.
|
|
457
614
|
*
|
|
458
|
-
*
|
|
459
|
-
*
|
|
460
|
-
* `["layers", 1, Symbol('id')]`.
|
|
615
|
+
* Both forms support **all objects** (plain objects, arrays, class instances).
|
|
616
|
+
* Symbol keys are only reachable via the key-array form.
|
|
461
617
|
*
|
|
462
618
|
* Intermediate nodes that are absent, `null`, or not an object are replaced with `{}`.
|
|
463
619
|
* Any path containing a string segment equal to `__proto__`, `constructor`, or `prototype`
|
|
464
620
|
* is rejected and the original object is returned unchanged (prototype-pollution guard).
|
|
465
621
|
*
|
|
466
622
|
* @param obj - The object to mutate
|
|
467
|
-
* @param path - Dot/bracket-notation string or explicit `PropertyKey[]`
|
|
623
|
+
* @param path - Dot/bracket-notation string literal or explicit `PropertyKey[]`
|
|
468
624
|
* @param value - Value to assign at the path
|
|
469
|
-
* @returns The mutated object (same reference)
|
|
625
|
+
* @returns The mutated object (same reference, narrowed type)
|
|
470
626
|
* @example
|
|
471
|
-
* //
|
|
627
|
+
* // Dot notation — "1" stays a string key
|
|
472
628
|
* set({}, 'a.b.c', 42)
|
|
473
629
|
* // => { a: { b: { c: 42 } } }
|
|
474
630
|
*
|
|
475
|
-
*
|
|
476
|
-
* // => { layers: { '1': { name: 'bg' } } }
|
|
477
|
-
*
|
|
478
|
-
* // 2. Bracket notation — [1] becomes a number key
|
|
631
|
+
* // Bracket notation — [1] becomes a number key
|
|
479
632
|
* set({ layers: [{}, { name: 'old' }] }, 'layers[1].name', 'bg')
|
|
480
633
|
* // => { layers: [{}, { name: 'bg' }] }
|
|
481
634
|
*
|
|
482
|
-
* //
|
|
635
|
+
* // Key array — supports symbols
|
|
483
636
|
* const id = Symbol('id')
|
|
484
637
|
* set({}, ['user', id], 'alice')
|
|
485
638
|
* // => { user: { [id]: 'alice' } }
|
|
486
639
|
* @since 1.9.0
|
|
487
640
|
*/
|
|
488
|
-
declare function set(obj:
|
|
489
|
-
declare function set<T extends object, const Path extends readonly PropertyKey[], V extends DeepGet<T, Path>>(obj: T, path: Path, value: V): DeepSet<T, Path, V>;
|
|
641
|
+
declare function set<T extends object, const P extends string | readonly PropertyKey[], V extends DeepGet<T, ParsePath<P>>>(obj: T, path: P, value: V): string extends P ? T : readonly PropertyKey[] extends P ? T : ContainsUnsafeStringKey<ParsePath<P>> extends true ? T : DeepSet<T, ParsePath<P>, V>;
|
|
490
642
|
|
|
491
|
-
export { compact, deepClone, deepMerge, diff, equalsDeep, equalsShallow, get, groupBy, invert, isEmpty, isNonEmpty, map, omit, pick, removeUndefinedNull, safeJsonParse, set };
|
|
643
|
+
export { cloneDeep, compact, cloneDeep as deepClone, mergeDeep as deepMerge, diff, equalsDeep, equalsShallow, get, groupBy, invert, isEmpty, isNonEmpty, map, mergeDeep, omit, pick, removeUndefinedNull, safeJsonParse, set };
|
|
492
644
|
export type { DiffOptions, DiffResult };
|