@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/README.md +2 -2
- package/lib/index.d.ts +295 -50
- package/lib/index.js +163 -76
- package/lib/index.js.map +1 -1
- package/llms.txt +239 -122
- package/meta/api.json +207 -169
- package/meta/examples.json +38 -23
- 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,15 +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
|
-
* @param
|
|
40
|
-
* @
|
|
41
|
-
* @
|
|
42
|
-
*
|
|
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
|
|
43
46
|
*/
|
|
44
|
-
declare function
|
|
45
|
-
declare function
|
|
46
|
-
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;
|
|
47
50
|
|
|
48
51
|
/**
|
|
49
52
|
* This file is part of helpers4.
|
|
@@ -163,14 +166,142 @@ declare function equalsShallow(objA: unknown, objB: unknown): boolean;
|
|
|
163
166
|
* SPDX-License-Identifier: LGPL-3.0-or-later
|
|
164
167
|
*/
|
|
165
168
|
/**
|
|
166
|
-
*
|
|
167
|
-
* @
|
|
168
|
-
|
|
169
|
-
|
|
170
|
-
|
|
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]>;
|
|
238
|
+
/**
|
|
239
|
+
* Resolves the value type at a given path within T.
|
|
240
|
+
* Returns `unknown` when the path doesn't match the shape of T.
|
|
241
|
+
*
|
|
242
|
+
* @ignore
|
|
243
|
+
* @example
|
|
244
|
+
* DeepGet<{ a: { b: number } }, ['a', 'b']> // => number
|
|
245
|
+
* DeepGet<{ a: { b: number } }, ['a', 'x']> // => unknown
|
|
246
|
+
* DeepGet<{ a: number }, []> // => { a: number }
|
|
247
|
+
*/
|
|
248
|
+
type DeepGet<T, Path extends readonly PropertyKey[]> = Path extends readonly [] ? T : Path extends readonly [infer K, ...infer Rest extends readonly PropertyKey[]] ? K extends keyof T ? DeepGet<T[K], Rest> : unknown : unknown;
|
|
249
|
+
/**
|
|
250
|
+
* Produces the type of T after setting the value at Path to V.
|
|
251
|
+
* Resolves to `never` when a key in Path is not present in the corresponding level of T,
|
|
252
|
+
* surfacing invalid paths at the call site rather than silently returning T unchanged.
|
|
253
|
+
*
|
|
254
|
+
* @ignore
|
|
255
|
+
* @example
|
|
256
|
+
* DeepSet<{ a: { b: number } }, ['a', 'b'], string>
|
|
257
|
+
* // => { a: { b: string } }
|
|
258
|
+
*/
|
|
259
|
+
type DeepSet<T, Path extends readonly PropertyKey[], V> = Path extends readonly [] ? T : Path extends readonly [infer K, ...infer Rest extends readonly PropertyKey[]] ? K extends keyof T ? {
|
|
260
|
+
[P in keyof T]: P extends K ? DeepSet<T[K], Rest, V> : T[P];
|
|
261
|
+
} : never : never;
|
|
262
|
+
|
|
263
|
+
/**
|
|
264
|
+
* This file is part of helpers4.
|
|
265
|
+
* Copyright (C) 2025 baxyz
|
|
266
|
+
* SPDX-License-Identifier: LGPL-3.0-or-later
|
|
267
|
+
*/
|
|
268
|
+
|
|
269
|
+
/**
|
|
270
|
+
* Gets a value from an object using a dot/bracket-notated path or explicit key array.
|
|
271
|
+
*
|
|
272
|
+
* **Two path forms are supported:**
|
|
273
|
+
*
|
|
274
|
+
* 1. **String path** — dot notation (`'a.b.c'`) and bracket notation (`'layers[1].name'`)
|
|
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`.
|
|
280
|
+
*
|
|
281
|
+
* 2. **Key array** (`PropertyKey[]`) — explicit array of `string | number | symbol` keys,
|
|
282
|
+
* no parsing performed. Enables symbol-keyed traversal and compile-time type inference:
|
|
283
|
+
* `get(obj, ['a', 'b'] as const)` infers the return type from the path.
|
|
284
|
+
*
|
|
285
|
+
* Both forms support **all objects** (plain objects, arrays, class instances).
|
|
286
|
+
* Symbol keys are only reachable via the key-array form.
|
|
287
|
+
*
|
|
288
|
+
* @param obj - The object to read from
|
|
289
|
+
* @param path - Dot/bracket-notation string literal or explicit `PropertyKey[]`
|
|
290
|
+
* @param defaultValue - Returned when the path is absent or resolves to `undefined`
|
|
291
|
+
* @returns The value at the path, or `defaultValue`
|
|
292
|
+
* @example
|
|
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)
|
|
296
|
+
* get({ a: 1 }, 'b.c', 'default') // => 'default'
|
|
297
|
+
*
|
|
298
|
+
* const id = Symbol('id')
|
|
299
|
+
* get({ [id]: 'alice' }, [id]) // => 'alice' (symbol key, inferred: string | undefined)
|
|
171
300
|
* @since 1.9.0
|
|
172
301
|
*/
|
|
173
|
-
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;
|
|
174
305
|
|
|
175
306
|
/**
|
|
176
307
|
* This file is part of helpers4.
|
|
@@ -182,6 +313,9 @@ declare function get<T = unknown>(obj: unknown, path: string, defaultValue?: T):
|
|
|
182
313
|
*
|
|
183
314
|
* A thin, typed wrapper around `Object.groupBy` (ES2024) that works on
|
|
184
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.
|
|
185
319
|
*
|
|
186
320
|
* @param items - The array to group
|
|
187
321
|
* @param keyFn - Function that returns the group key for each item
|
|
@@ -189,9 +323,11 @@ declare function get<T = unknown>(obj: unknown, path: string, defaultValue?: T):
|
|
|
189
323
|
* @example
|
|
190
324
|
* groupBy([1, 2, 3, 4], n => n % 2 === 0 ? 'even' : 'odd')
|
|
191
325
|
* // => { odd: [1, 3], even: [2, 4] }
|
|
326
|
+
* groupBy(null, n => n % 2 === 0 ? 'even' : 'odd')
|
|
327
|
+
* // => {}
|
|
192
328
|
* @since 2.0.0
|
|
193
329
|
*/
|
|
194
|
-
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[]>>;
|
|
195
331
|
|
|
196
332
|
/**
|
|
197
333
|
* This file is part of helpers4.
|
|
@@ -201,6 +337,9 @@ declare function groupBy<T, K extends PropertyKey>(items: readonly T[], keyFn: (
|
|
|
201
337
|
/**
|
|
202
338
|
* Returns a new object with keys and values swapped.
|
|
203
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.
|
|
204
343
|
*
|
|
205
344
|
* @param obj - The object whose keys and values are to be swapped
|
|
206
345
|
* @returns A new object with values as keys and original keys as values
|
|
@@ -210,9 +349,12 @@ declare function groupBy<T, K extends PropertyKey>(items: readonly T[], keyFn: (
|
|
|
210
349
|
*
|
|
211
350
|
* invert({ one: 1, two: 2 })
|
|
212
351
|
* // => { 1: 'one', 2: 'two' }
|
|
352
|
+
*
|
|
353
|
+
* invert(null)
|
|
354
|
+
* // => {}
|
|
213
355
|
* @since 2.0.0
|
|
214
356
|
*/
|
|
215
|
-
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>;
|
|
216
358
|
|
|
217
359
|
/**
|
|
218
360
|
* This file is part of helpers4.
|
|
@@ -221,19 +363,22 @@ declare function invert<K extends string, V extends PropertyKey>(obj: Record<K,
|
|
|
221
363
|
*/
|
|
222
364
|
/**
|
|
223
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`.
|
|
224
367
|
*
|
|
225
368
|
* Symbol-keyed properties are not counted. Use `Object.getOwnPropertySymbols`
|
|
226
369
|
* separately if symbol keys matter for your use case.
|
|
227
370
|
*
|
|
228
371
|
* @param value - The object to check
|
|
229
|
-
* @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`
|
|
230
373
|
* @example
|
|
231
374
|
* isEmpty({}) // => true
|
|
375
|
+
* isEmpty(null) // => true
|
|
376
|
+
* isEmpty(undefined) // => true
|
|
232
377
|
* isEmpty({ a: 1 }) // => false
|
|
233
378
|
* isEmpty({ a: undefined }) // => false (key exists)
|
|
234
379
|
* @since 2.0.3
|
|
235
380
|
*/
|
|
236
|
-
declare function isEmpty(value: Record<PropertyKey, unknown>): boolean;
|
|
381
|
+
declare function isEmpty(value: Record<PropertyKey, unknown> | null | undefined): boolean;
|
|
237
382
|
|
|
238
383
|
/**
|
|
239
384
|
* This file is part of helpers4.
|
|
@@ -242,18 +387,22 @@ declare function isEmpty(value: Record<PropertyKey, unknown>): boolean;
|
|
|
242
387
|
*/
|
|
243
388
|
/**
|
|
244
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`.
|
|
245
391
|
*
|
|
246
392
|
* Symbol-keyed properties are not counted. Use `Object.getOwnPropertySymbols`
|
|
247
393
|
* separately if symbol keys matter for your use case.
|
|
248
394
|
*
|
|
249
395
|
* @param value - The object to check
|
|
250
|
-
* @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>`.
|
|
251
398
|
* @example
|
|
252
399
|
* isNonEmpty({ a: 1 }) // => true
|
|
253
400
|
* isNonEmpty({}) // => false
|
|
401
|
+
* isNonEmpty(null) // => false
|
|
402
|
+
* isNonEmpty(undefined) // => false
|
|
254
403
|
* @since 2.0.3
|
|
255
404
|
*/
|
|
256
|
-
declare function isNonEmpty(value: Record<PropertyKey, unknown>):
|
|
405
|
+
declare function isNonEmpty(value: Record<PropertyKey, unknown> | null | undefined): value is Record<PropertyKey, unknown>;
|
|
257
406
|
|
|
258
407
|
/**
|
|
259
408
|
* This file is part of helpers4.
|
|
@@ -268,7 +417,8 @@ declare function isNonEmpty(value: Record<PropertyKey, unknown>): boolean;
|
|
|
268
417
|
* when `mapKey` is omitted the original keys are preserved.
|
|
269
418
|
*
|
|
270
419
|
* Note: if two different keys map to the same output key the last one wins
|
|
271
|
-
* (insertion order).
|
|
420
|
+
* (insertion order). Entries whose mapped key is a prototype-polluting string
|
|
421
|
+
* (`__proto__`, `constructor`, `prototype`) are silently skipped.
|
|
272
422
|
*
|
|
273
423
|
* @param obj - The source object
|
|
274
424
|
* @param mapValue - Callback called with `(value, key)` for each entry.
|
|
@@ -287,7 +437,69 @@ declare function isNonEmpty(value: Record<PropertyKey, unknown>): boolean;
|
|
|
287
437
|
* // => { A: 10, B: 20 }
|
|
288
438
|
* @since 2.0.0
|
|
289
439
|
*/
|
|
290
|
-
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>;
|
|
291
503
|
|
|
292
504
|
/**
|
|
293
505
|
* This file is part of helpers4.
|
|
@@ -385,15 +597,48 @@ declare function safeJsonParse<T>(input: string, fallback: T): T;
|
|
|
385
597
|
* Copyright (C) 2025 baxyz
|
|
386
598
|
* SPDX-License-Identifier: LGPL-3.0-or-later
|
|
387
599
|
*/
|
|
600
|
+
|
|
388
601
|
/**
|
|
389
|
-
* Sets a value in an object
|
|
390
|
-
*
|
|
391
|
-
*
|
|
392
|
-
*
|
|
393
|
-
*
|
|
602
|
+
* Sets a value in an object at the given path, creating intermediate objects as needed.
|
|
603
|
+
*
|
|
604
|
+
* **Two path forms are supported:**
|
|
605
|
+
*
|
|
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).
|
|
611
|
+
*
|
|
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.
|
|
614
|
+
*
|
|
615
|
+
* Both forms support **all objects** (plain objects, arrays, class instances).
|
|
616
|
+
* Symbol keys are only reachable via the key-array form.
|
|
617
|
+
*
|
|
618
|
+
* Intermediate nodes that are absent, `null`, or not an object are replaced with `{}`.
|
|
619
|
+
* Any path containing a string segment equal to `__proto__`, `constructor`, or `prototype`
|
|
620
|
+
* is rejected and the original object is returned unchanged (prototype-pollution guard).
|
|
621
|
+
*
|
|
622
|
+
* @param obj - The object to mutate
|
|
623
|
+
* @param path - Dot/bracket-notation string literal or explicit `PropertyKey[]`
|
|
624
|
+
* @param value - Value to assign at the path
|
|
625
|
+
* @returns The mutated object (same reference, narrowed type)
|
|
626
|
+
* @example
|
|
627
|
+
* // Dot notation — "1" stays a string key
|
|
628
|
+
* set({}, 'a.b.c', 42)
|
|
629
|
+
* // => { a: { b: { c: 42 } } }
|
|
630
|
+
*
|
|
631
|
+
* // Bracket notation — [1] becomes a number key
|
|
632
|
+
* set({ layers: [{}, { name: 'old' }] }, 'layers[1].name', 'bg')
|
|
633
|
+
* // => { layers: [{}, { name: 'bg' }] }
|
|
634
|
+
*
|
|
635
|
+
* // Key array — supports symbols
|
|
636
|
+
* const id = Symbol('id')
|
|
637
|
+
* set({}, ['user', id], 'alice')
|
|
638
|
+
* // => { user: { [id]: 'alice' } }
|
|
394
639
|
* @since 1.9.0
|
|
395
640
|
*/
|
|
396
|
-
declare function set(obj:
|
|
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>;
|
|
397
642
|
|
|
398
|
-
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 };
|
|
399
644
|
export type { DiffOptions, DiffResult };
|