@helpers4/object 3.0.0-alpha.2 → 3.0.1
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 +8 -0
- package/lib/index.d.ts +314 -58
- package/lib/index.js +252 -23
- package/lib/index.js.map +1 -1
- package/llms.txt +381 -2
- package/meta/api.json +351 -1
- package/meta/examples.json +120 -0
- package/package.json +10 -2
package/lib/index.js
CHANGED
|
@@ -4,6 +4,44 @@ var UNSAFE_KEYS = /* @__PURE__ */ new Set([
|
|
|
4
4
|
"prototype"
|
|
5
5
|
]);
|
|
6
6
|
//#endregion
|
|
7
|
+
//#region helpers/object/clone.ts
|
|
8
|
+
/**
|
|
9
|
+
* This file is part of helpers4.
|
|
10
|
+
* Copyright (C) 2025 baxyz
|
|
11
|
+
* SPDX-License-Identifier: LGPL-3.0-or-later
|
|
12
|
+
*/
|
|
13
|
+
/**
|
|
14
|
+
* Creates a shallow copy of a value — one level deep, unlike {@link cloneDeep}.
|
|
15
|
+
*
|
|
16
|
+
* Unlike a plain `{ ...value }` spread, this correctly reconstructs `Date`,
|
|
17
|
+
* `Map`, `Set`, and arrays instead of producing an empty (or wrong-shaped)
|
|
18
|
+
* plain object for them. Primitives are returned as-is. Any other object
|
|
19
|
+
* (including class instances not listed above) has its own enumerable string
|
|
20
|
+
* keys shallow-copied into a plain object — the same fallback `cloneDeep`
|
|
21
|
+
* uses, so the two stay consistent for types neither one special-cases.
|
|
22
|
+
*
|
|
23
|
+
* @param value - The value to shallow-clone
|
|
24
|
+
* @returns A shallow copy of `value`
|
|
25
|
+
* @example
|
|
26
|
+
* clone({ a: { b: 1 } })
|
|
27
|
+
* // => { a: { b: 1 } } — new object, but `a` is the *same* nested reference
|
|
28
|
+
* @example
|
|
29
|
+
* clone(new Date('2024-01-01')) // => new Date with the same timestamp
|
|
30
|
+
* clone(new Map([['a', 1]])) // => new Map with the same entries
|
|
31
|
+
* clone([1, 2, 3]) // => new array, same elements
|
|
32
|
+
* @since 3.0.0
|
|
33
|
+
*/
|
|
34
|
+
function clone(value) {
|
|
35
|
+
if (value === null || typeof value !== "object") return value;
|
|
36
|
+
if (value instanceof Date) return new Date(value.getTime());
|
|
37
|
+
if (value instanceof Map) return new Map(value);
|
|
38
|
+
if (value instanceof Set) return new Set(value);
|
|
39
|
+
if (Array.isArray(value)) return [...value];
|
|
40
|
+
const result = {};
|
|
41
|
+
for (const key of Object.keys(value)) if (!UNSAFE_KEYS.has(key)) result[key] = value[key];
|
|
42
|
+
return result;
|
|
43
|
+
}
|
|
44
|
+
//#endregion
|
|
7
45
|
//#region helpers/object/cloneDeep.ts
|
|
8
46
|
/**
|
|
9
47
|
* This file is part of helpers4.
|
|
@@ -449,43 +487,92 @@ function equalsShallow(objA, objB) {
|
|
|
449
487
|
return true;
|
|
450
488
|
}
|
|
451
489
|
//#endregion
|
|
452
|
-
//#region helpers/object/
|
|
490
|
+
//#region helpers/object/flatten.ts
|
|
453
491
|
/**
|
|
454
492
|
* This file is part of helpers4.
|
|
455
493
|
* Copyright (C) 2025 baxyz
|
|
456
494
|
* SPDX-License-Identifier: LGPL-3.0-or-later
|
|
457
495
|
*/
|
|
496
|
+
function flattenInto(obj, prefix, result) {
|
|
497
|
+
for (const [key, value] of Object.entries(obj)) {
|
|
498
|
+
const path = prefix ? `${prefix}.${key}` : key;
|
|
499
|
+
if (isPlainObject(value) && Object.keys(value).length > 0) flattenInto(value, path, result);
|
|
500
|
+
else result[path] = value;
|
|
501
|
+
}
|
|
502
|
+
}
|
|
458
503
|
/**
|
|
459
|
-
*
|
|
504
|
+
* Flattens a nested object into a single-level object whose keys are the
|
|
505
|
+
* dot-notation path to each leaf value. The inverse of {@link unflatten}.
|
|
506
|
+
*
|
|
507
|
+
* Only **plain objects** are recursed into — arrays, `Date`, `Map`, `RegExp`,
|
|
508
|
+
* class instances, and empty plain objects `{}` are kept as opaque leaf
|
|
509
|
+
* values. This keeps `flatten`/`unflatten` a clean, invertible pair: arrays
|
|
510
|
+
* can't be losslessly told apart from plain objects once reduced to dotted
|
|
511
|
+
* keys, so this implementation doesn't attempt it.
|
|
512
|
+
*
|
|
513
|
+
* Caveat shared by every dotted-path flattening scheme: a key that itself
|
|
514
|
+
* contains a literal `.` is indistinguishable from real nesting once
|
|
515
|
+
* flattened (`{ 'a.b': 1 }` and `{ a: { b: 1 } }` both produce `{ 'a.b': 1 }`).
|
|
516
|
+
*
|
|
517
|
+
* @param obj - The object to flatten
|
|
518
|
+
* @returns A single-level object with dot-notation keys
|
|
519
|
+
* @example
|
|
520
|
+
* flatten({ a: { b: { c: 1 }, d: 2 } })
|
|
521
|
+
* // => { 'a.b.c': 1, 'a.d': 2 }
|
|
522
|
+
* @example
|
|
523
|
+
* flatten({ a: [1, 2], b: new Date(0) })
|
|
524
|
+
* // => { a: [1, 2], b: Date } (arrays and special objects stay as leaves)
|
|
525
|
+
* @since 3.0.0
|
|
526
|
+
*/
|
|
527
|
+
function flatten(obj) {
|
|
528
|
+
const result = {};
|
|
529
|
+
flattenInto(obj, "", result);
|
|
530
|
+
return result;
|
|
531
|
+
}
|
|
532
|
+
//#endregion
|
|
533
|
+
//#region helpers/object/parsePropertyPath.ts
|
|
534
|
+
/**
|
|
535
|
+
* This file is part of helpers4.
|
|
536
|
+
* Copyright (C) 2025 baxyz
|
|
537
|
+
* SPDX-License-Identifier: LGPL-3.0-or-later
|
|
538
|
+
*/
|
|
539
|
+
var BRACKET = /\[(\d+)\]/g;
|
|
540
|
+
var MAX_CACHE_SIZE = 500;
|
|
541
|
+
var pathCache = /* @__PURE__ */ new Map();
|
|
542
|
+
/**
|
|
543
|
+
* Parses a dot/bracket-notation property path into an array of string/number
|
|
544
|
+
* key segments — the same notation accepted by {@link get} and {@link set}.
|
|
460
545
|
*
|
|
461
546
|
* - Dot separators (`.`) split segments; each segment becomes a string key.
|
|
462
547
|
* - Bracket indices (`[n]`) become number keys.
|
|
463
548
|
* - A leading `.` is treated as "current level" and stripped before parsing,
|
|
464
549
|
* so `.[0]` ≡ `[0]` and `.a.b` ≡ `a.b`.
|
|
465
|
-
* - Empty string (or bare `.`) returns `['']` (addresses the `''` key on the root object).
|
|
550
|
+
* - Empty string (or a bare `.`) returns `['']` (addresses the `''` key on the root object).
|
|
466
551
|
* - Consecutive dots (`a..b`) produce an empty-string segment: `['a', '', 'b']`.
|
|
467
|
-
* - Non-bracket text trailing a `]` within the same dot-segment throws a RangeError;
|
|
468
|
-
* use a dot separator instead (`'a[0].b'` not `'a[0]b'`).
|
|
469
552
|
*
|
|
470
|
-
*
|
|
553
|
+
* Results are cached (up to 500 distinct path strings, oldest evicted first)
|
|
554
|
+
* since real-world callers tend to reuse a small, fixed set of literal paths.
|
|
555
|
+
*
|
|
556
|
+
* @param path - The dot/bracket-notation path string to parse
|
|
557
|
+
* @returns The parsed key segments
|
|
558
|
+
* @throws {RangeError} When non-bracket text trails the last `]` within a
|
|
559
|
+
* dot-segment (e.g. `'a[0]b'` is ambiguous — use `'a[0].b'` instead)
|
|
471
560
|
* @example
|
|
472
|
-
*
|
|
473
|
-
*
|
|
474
|
-
*
|
|
475
|
-
*
|
|
476
|
-
*
|
|
477
|
-
* parsePath('.') // => [''] (leading dot → empty-string key)
|
|
561
|
+
* parsePropertyPath('a.b.c') // => ['a', 'b', 'c']
|
|
562
|
+
* parsePropertyPath('layers[1].x') // => ['layers', 1, 'x']
|
|
563
|
+
* parsePropertyPath('.[0]') // => [0] (leading dot stripped)
|
|
564
|
+
* parsePropertyPath('a..b') // => ['a', '', 'b'] (empty segment preserved)
|
|
565
|
+
* @since 3.0.0
|
|
478
566
|
*/
|
|
479
|
-
|
|
480
|
-
|
|
481
|
-
function parsePath(path) {
|
|
482
|
-
const cached = parsePathCache.get(path);
|
|
567
|
+
function parsePropertyPath(path) {
|
|
568
|
+
const cached = pathCache.get(path);
|
|
483
569
|
if (cached !== void 0) return cached;
|
|
484
|
-
const result = Object.freeze(
|
|
485
|
-
|
|
570
|
+
const result = Object.freeze(parseUncached(path));
|
|
571
|
+
if (pathCache.size >= MAX_CACHE_SIZE) pathCache.delete(pathCache.keys().next().value);
|
|
572
|
+
pathCache.set(path, result);
|
|
486
573
|
return result;
|
|
487
574
|
}
|
|
488
|
-
function
|
|
575
|
+
function parseUncached(path) {
|
|
489
576
|
if (path === "") return [""];
|
|
490
577
|
if (path[0] === ".") path = path.slice(1);
|
|
491
578
|
if (path === "") return [""];
|
|
@@ -500,7 +587,7 @@ function parsePathUncached(path) {
|
|
|
500
587
|
last = BRACKET.lastIndex;
|
|
501
588
|
}
|
|
502
589
|
if (last === 0) result.push(dotSegment);
|
|
503
|
-
else if (last < dotSegment.length) throw new RangeError(`
|
|
590
|
+
else if (last < dotSegment.length) throw new RangeError(`parsePropertyPath: non-bracket text '${dotSegment.slice(last)}' after ']' in segment '${dotSegment}' — use 'a[0].b' instead of 'a[0]b'`);
|
|
504
591
|
}
|
|
505
592
|
return result;
|
|
506
593
|
}
|
|
@@ -513,7 +600,7 @@ function parsePathUncached(path) {
|
|
|
513
600
|
*/
|
|
514
601
|
function get(obj, path, defaultValue) {
|
|
515
602
|
if (obj == null) return defaultValue;
|
|
516
|
-
const keys = typeof path === "string" ?
|
|
603
|
+
const keys = typeof path === "string" ? parsePropertyPath(path) : path;
|
|
517
604
|
let result = obj;
|
|
518
605
|
for (const key of keys) {
|
|
519
606
|
if (result == null || typeof result !== "object") return defaultValue;
|
|
@@ -732,6 +819,37 @@ function omit(obj, keys) {
|
|
|
732
819
|
return result;
|
|
733
820
|
}
|
|
734
821
|
//#endregion
|
|
822
|
+
//#region helpers/object/omitBy.ts
|
|
823
|
+
/**
|
|
824
|
+
* This file is part of helpers4.
|
|
825
|
+
* Copyright (C) 2025 baxyz
|
|
826
|
+
* SPDX-License-Identifier: LGPL-3.0-or-later
|
|
827
|
+
*/
|
|
828
|
+
/**
|
|
829
|
+
* Creates a new object without the own enumerable entries for which
|
|
830
|
+
* `predicate` returns `true`.
|
|
831
|
+
*
|
|
832
|
+
* Complements {@link omit} for when the keys to remove aren't known ahead of
|
|
833
|
+
* time — `omit` takes an explicit key list, `omitBy` takes a predicate.
|
|
834
|
+
*
|
|
835
|
+
* @param obj - The source object. `null`/`undefined` pass through unchanged.
|
|
836
|
+
* @param predicate - Called with `(value, key)` for each own enumerable entry
|
|
837
|
+
* @returns A new object without the matching entries
|
|
838
|
+
* @example
|
|
839
|
+
* omitBy({ a: 1, b: undefined, c: 2 }, (value) => value === undefined)
|
|
840
|
+
* // => { a: 1, c: 2 }
|
|
841
|
+
* @since 3.0.0
|
|
842
|
+
*/
|
|
843
|
+
function omitBy(obj, predicate) {
|
|
844
|
+
if (obj == null) return obj;
|
|
845
|
+
const result = {};
|
|
846
|
+
for (const key of Object.keys(obj)) {
|
|
847
|
+
const typedKey = key;
|
|
848
|
+
if (!predicate(obj[typedKey], typedKey)) result[key] = obj[typedKey];
|
|
849
|
+
}
|
|
850
|
+
return result;
|
|
851
|
+
}
|
|
852
|
+
//#endregion
|
|
735
853
|
//#region helpers/object/pick.ts
|
|
736
854
|
/**
|
|
737
855
|
* This file is part of helpers4.
|
|
@@ -748,6 +866,38 @@ function pick(obj, keys) {
|
|
|
748
866
|
return result;
|
|
749
867
|
}
|
|
750
868
|
//#endregion
|
|
869
|
+
//#region helpers/object/pickBy.ts
|
|
870
|
+
/**
|
|
871
|
+
* This file is part of helpers4.
|
|
872
|
+
* Copyright (C) 2025 baxyz
|
|
873
|
+
* SPDX-License-Identifier: LGPL-3.0-or-later
|
|
874
|
+
*/
|
|
875
|
+
/**
|
|
876
|
+
* Creates a new object with only the own enumerable entries for which
|
|
877
|
+
* `predicate` returns `true`.
|
|
878
|
+
*
|
|
879
|
+
* Complements {@link pick} for when the keys to keep aren't known ahead of
|
|
880
|
+
* time — `pick` takes an explicit key list, `pickBy` takes a predicate.
|
|
881
|
+
*
|
|
882
|
+
* @param obj - The source object. `null`/`undefined` pass through unchanged.
|
|
883
|
+
* @param predicate - Called with `(value, key)` for each own enumerable entry
|
|
884
|
+
* @returns A new object with only the matching entries
|
|
885
|
+
* @example
|
|
886
|
+
* pickBy({ a: 1, b: 0, c: 2 }, (value) => value > 0)
|
|
887
|
+
* // => { a: 1, c: 2 }
|
|
888
|
+
* @since 3.0.0
|
|
889
|
+
*/
|
|
890
|
+
function pickBy(obj, predicate) {
|
|
891
|
+
if (obj == null) return obj;
|
|
892
|
+
const result = {};
|
|
893
|
+
for (const key of Object.keys(obj)) {
|
|
894
|
+
if (UNSAFE_KEYS.has(key)) continue;
|
|
895
|
+
const typedKey = key;
|
|
896
|
+
if (predicate(obj[typedKey], typedKey)) result[key] = obj[typedKey];
|
|
897
|
+
}
|
|
898
|
+
return result;
|
|
899
|
+
}
|
|
900
|
+
//#endregion
|
|
751
901
|
//#region helpers/guard/isNullish.ts
|
|
752
902
|
/**
|
|
753
903
|
* This file is part of helpers4.
|
|
@@ -808,7 +958,7 @@ function safeJsonParse(input, fallback) {
|
|
|
808
958
|
* SPDX-License-Identifier: LGPL-3.0-or-later
|
|
809
959
|
*/
|
|
810
960
|
function set(obj, path, value) {
|
|
811
|
-
const keys = typeof path === "string" ?
|
|
961
|
+
const keys = typeof path === "string" ? parsePropertyPath(path) : path;
|
|
812
962
|
if (keys.length === 0) return obj;
|
|
813
963
|
if (keys.some((k) => typeof k === "string" && UNSAFE_KEYS.has(k))) return obj;
|
|
814
964
|
let current = obj;
|
|
@@ -821,6 +971,85 @@ function set(obj, path, value) {
|
|
|
821
971
|
return obj;
|
|
822
972
|
}
|
|
823
973
|
//#endregion
|
|
824
|
-
|
|
974
|
+
//#region helpers/object/unflatten.ts
|
|
975
|
+
/**
|
|
976
|
+
* This file is part of helpers4.
|
|
977
|
+
* Copyright (C) 2025 baxyz
|
|
978
|
+
* SPDX-License-Identifier: LGPL-3.0-or-later
|
|
979
|
+
*/
|
|
980
|
+
/**
|
|
981
|
+
* Rebuilds a nested object from a single-level object whose keys are
|
|
982
|
+
* dot-notation paths. The inverse of {@link flatten}.
|
|
983
|
+
*
|
|
984
|
+
* Uses {@link set} internally, so intermediate nodes are always created as
|
|
985
|
+
* plain objects (never arrays — see {@link flatten}'s doc for why), and any
|
|
986
|
+
* key segment equal to `__proto__`, `constructor`, or `prototype` is silently
|
|
987
|
+
* rejected (same prototype-pollution guard as `set`).
|
|
988
|
+
*
|
|
989
|
+
* @param obj - A single-level object with dot-notation keys
|
|
990
|
+
* @returns The rebuilt nested object
|
|
991
|
+
* @example
|
|
992
|
+
* unflatten({ 'a.b.c': 1, 'a.d': 2 })
|
|
993
|
+
* // => { a: { b: { c: 1 }, d: 2 } }
|
|
994
|
+
* @since 3.0.0
|
|
995
|
+
*/
|
|
996
|
+
function unflatten(obj) {
|
|
997
|
+
const result = {};
|
|
998
|
+
for (const [key, value] of Object.entries(obj)) set(result, key, value);
|
|
999
|
+
return result;
|
|
1000
|
+
}
|
|
1001
|
+
//#endregion
|
|
1002
|
+
//#region helpers/object/unset.ts
|
|
1003
|
+
/**
|
|
1004
|
+
* This file is part of helpers4.
|
|
1005
|
+
* Copyright (C) 2025 baxyz
|
|
1006
|
+
* SPDX-License-Identifier: LGPL-3.0-or-later
|
|
1007
|
+
*/
|
|
1008
|
+
/**
|
|
1009
|
+
* Removes the value at a dot/bracket-notation path or explicit key array,
|
|
1010
|
+
* mutating the object in place. Uses the same path syntax as {@link get}/{@link set}.
|
|
1011
|
+
*
|
|
1012
|
+
* A missing intermediate segment is a no-op (nothing to remove), not an error.
|
|
1013
|
+
* As with `set`, any path containing a string segment equal to `__proto__`,
|
|
1014
|
+
* `constructor`, or `prototype` is rejected and the object is returned unchanged.
|
|
1015
|
+
*
|
|
1016
|
+
* The removed key stops appearing in `Object.keys`/`for...in` — unlike setting
|
|
1017
|
+
* it to `undefined`, which would keep the key present.
|
|
1018
|
+
*
|
|
1019
|
+
* @param obj - The object to mutate
|
|
1020
|
+
* @param path - Dot/bracket-notation string, or explicit `PropertyKey[]`
|
|
1021
|
+
* @returns The same object reference, with the key removed if it existed
|
|
1022
|
+
* @example
|
|
1023
|
+
* unset({ a: { b: 1, c: 2 } }, 'a.b')
|
|
1024
|
+
* // => { a: { c: 2 } }
|
|
1025
|
+
* @example
|
|
1026
|
+
* unset({ a: 1 }, 'x.y') // path doesn't exist — no-op
|
|
1027
|
+
* // => { a: 1 }
|
|
1028
|
+
* @since 3.0.0
|
|
1029
|
+
*/
|
|
1030
|
+
function unset(obj, path) {
|
|
1031
|
+
const keys = typeof path === "string" ? parsePropertyPath(path) : path;
|
|
1032
|
+
if (keys.length === 0) return obj;
|
|
1033
|
+
if (keys.some((key) => typeof key === "string" && UNSAFE_KEYS.has(key))) return obj;
|
|
1034
|
+
let current = obj;
|
|
1035
|
+
for (let i = 0; i < keys.length - 1; i++) {
|
|
1036
|
+
if (current == null || typeof current !== "object") return obj;
|
|
1037
|
+
current = current[keys[i]];
|
|
1038
|
+
}
|
|
1039
|
+
if (current != null && typeof current === "object") delete current[keys[keys.length - 1]];
|
|
1040
|
+
return obj;
|
|
1041
|
+
}
|
|
1042
|
+
//#endregion
|
|
1043
|
+
//#region helpers/object/update.ts
|
|
1044
|
+
/**
|
|
1045
|
+
* This file is part of helpers4.
|
|
1046
|
+
* Copyright (C) 2025 baxyz
|
|
1047
|
+
* SPDX-License-Identifier: LGPL-3.0-or-later
|
|
1048
|
+
*/
|
|
1049
|
+
function update(obj, path, updater) {
|
|
1050
|
+
return set(obj, path, updater(get(obj, path)));
|
|
1051
|
+
}
|
|
1052
|
+
//#endregion
|
|
1053
|
+
export { clone, cloneDeep, compact, diff, equalsDeep, equalsShallow, flatten, get, groupBy, invert, isEmpty, isNonEmpty, map, mergeDeep, omit, omitBy, parsePropertyPath, pick, pickBy, removeUndefinedNull, safeJsonParse, set, unflatten, unset, update };
|
|
825
1054
|
|
|
826
1055
|
//# sourceMappingURL=index.js.map
|