xypriss 9.12.11 → 9.12.13

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.
@@ -1,9 +1,455 @@
1
1
  import { XStringify } from 'xypriss-security';
2
2
 
3
+ class ObjectWrapper {
4
+ constructor(obj) {
5
+ this.current = obj;
6
+ }
7
+ /**
8
+ * **value**
9
+ *
10
+ * Unwraps and returns the underlying plain object held by this wrapper.
11
+ * Call this at the end of a chain to get back a normal object.
12
+ *
13
+ * @returns The current wrapped object.
14
+ *
15
+ * @example
16
+ * ```ts
17
+ * const obj = __sys__.utils.obj.of({ a: 1 });
18
+ * obj.value(); // { a: 1 }
19
+ * ```
20
+ */
21
+ value() {
22
+ return this.current;
23
+ }
24
+ /**
25
+ * **raw**
26
+ *
27
+ * Alias for {@link value}. Useful when `value` reads awkwardly in context.
28
+ *
29
+ * @returns The current wrapped object.
30
+ */
31
+ raw() {
32
+ return this.current;
33
+ }
34
+ /**
35
+ * **clone**
36
+ *
37
+ * Deep-clones the wrapped object (via `XStringify`) and continues
38
+ * the chain on the cloned copy, leaving the original untouched.
39
+ *
40
+ * @returns `this`, now wrapping a deep copy of the previous value.
41
+ *
42
+ * @example
43
+ * ```ts
44
+ * const source = { nested: { count: 1 } };
45
+ * const obj = __sys__.utils.obj.of(source).clone();
46
+ * obj.value().nested.count = 99;
47
+ * // source.nested.count is still 1
48
+ * ```
49
+ */
50
+ clone() {
51
+ this.current = JSON.parse(XStringify(this.current));
52
+ return this;
53
+ }
54
+ /**
55
+ * **pick**
56
+ *
57
+ * Narrows the wrapped object down to only the given keys.
58
+ *
59
+ * @param keys - The keys to keep.
60
+ * @returns `this`, now wrapping only the picked keys.
61
+ *
62
+ * @example
63
+ * ```ts
64
+ * __sys__.utils.obj.of({ a: 1, b: 2, c: 3 }).pick(["a", "c"]).value();
65
+ * // { a: 1, c: 3 }
66
+ * ```
67
+ */
68
+ pick(keys) {
69
+ const result = keys.reduce((acc, key) => {
70
+ if (key in this.current)
71
+ acc[key] = this.current[key];
72
+ return acc;
73
+ }, {});
74
+ return new ObjectWrapper(result);
75
+ }
76
+ /**
77
+ * **omit**
78
+ *
79
+ * Removes the given keys from the wrapped object.
80
+ *
81
+ * @param keys - The keys to remove.
82
+ * @returns `this`, now wrapping the object without those keys.
83
+ *
84
+ * @example
85
+ * ```ts
86
+ * __sys__.utils.obj.of({ a: 1, b: 2, c: 3 }).omit(["b"]).value();
87
+ * // { a: 1, c: 3 }
88
+ * ```
89
+ */
90
+ omit(keys) {
91
+ const result = { ...this.current };
92
+ keys.forEach((key) => delete result[key]);
93
+ return new ObjectWrapper(result);
94
+ }
95
+ /**
96
+ * **isEmpty**
97
+ *
98
+ * Checks whether the wrapped object has no own enumerable keys.
99
+ * This is a terminal read (does not return the wrapper), since it
100
+ * yields a boolean rather than an object.
101
+ *
102
+ * @returns `true` if the object has no own keys.
103
+ *
104
+ * @example
105
+ * ```ts
106
+ * __sys__.utils.obj.of({}).isEmpty(); // true
107
+ * ```
108
+ */
109
+ isEmpty() {
110
+ return Object.keys(this.current).length === 0;
111
+ }
112
+ /**
113
+ * **flatten**
114
+ *
115
+ * Collapses the wrapped object's nested structure into flat
116
+ * dot-notation (or custom separator) keys.
117
+ *
118
+ * @param separator - Path separator (default: `"."`).
119
+ * @returns A new wrapper around the flattened object.
120
+ *
121
+ * @example
122
+ * ```ts
123
+ * __sys__.utils.obj.of({ a: { b: 1 } }).flatten().value();
124
+ * // { "a.b": 1 }
125
+ * ```
126
+ */
127
+ flatten(separator = ".") {
128
+ const result = {};
129
+ const recurse = (current, path = "") => {
130
+ for (const [key, val] of Object.entries(current)) {
131
+ const newPath = path ? `${path}${separator}${key}` : key;
132
+ if (val &&
133
+ typeof val === "object" &&
134
+ !Array.isArray(val) &&
135
+ Object.keys(val).length > 0) {
136
+ recurse(val, newPath);
137
+ }
138
+ else {
139
+ result[newPath] = val;
140
+ }
141
+ }
142
+ };
143
+ recurse(this.current);
144
+ return new ObjectWrapper(result);
145
+ }
146
+ /**
147
+ * **unflatten**
148
+ *
149
+ * Reverses {@link flatten}, expanding dot-notation (or custom
150
+ * separator) keys back into a nested object.
151
+ *
152
+ * @param separator - Path separator used in the flat keys (default: `"."`).
153
+ * @returns A new wrapper around the nested object.
154
+ *
155
+ * @example
156
+ * ```ts
157
+ * __sys__.utils.obj.of({ "a.b": 1, "a.c": 2 }).unflatten().value();
158
+ * // { a: { b: 1, c: 2 } }
159
+ * ```
160
+ */
161
+ unflatten(separator = ".") {
162
+ const result = {};
163
+ for (const [flatKey, val] of Object.entries(this.current)) {
164
+ const parts = flatKey.split(separator);
165
+ let cursor = result;
166
+ parts.forEach((part, i) => {
167
+ if (i === parts.length - 1) {
168
+ cursor[part] = val;
169
+ }
170
+ else {
171
+ if (typeof cursor[part] !== "object" ||
172
+ cursor[part] === null) {
173
+ cursor[part] = {};
174
+ }
175
+ cursor = cursor[part];
176
+ }
177
+ });
178
+ }
179
+ return new ObjectWrapper(result);
180
+ }
181
+ /**
182
+ * **merge**
183
+ *
184
+ * Deep-merges one or more source objects into the wrapped object.
185
+ * Plain object values are merged recursively; arrays and primitives
186
+ * are overwritten by the last source that defines them.
187
+ *
188
+ * @param sources - One or more partial objects to merge in.
189
+ * @returns `this`, now wrapping the merged result.
190
+ *
191
+ * @example
192
+ * ```ts
193
+ * __sys__.utils.obj.of({ a: 1, nested: { x: 1 } })
194
+ * .merge({ nested: { y: 2 } })
195
+ * .value();
196
+ * // { a: 1, nested: { x: 1, y: 2 } }
197
+ * ```
198
+ */
199
+ merge(...sources) {
200
+ const isPlainObject = (val) => !!val && typeof val === "object" && !Array.isArray(val);
201
+ const deepMerge = (target, source) => {
202
+ for (const key of Object.keys(source)) {
203
+ if (isPlainObject(source[key]) && isPlainObject(target[key])) {
204
+ deepMerge(target[key], source[key]);
205
+ }
206
+ else {
207
+ target[key] = source[key];
208
+ }
209
+ }
210
+ return target;
211
+ };
212
+ this.current = sources.reduce((acc, src) => deepMerge(acc, src), this.current);
213
+ return this;
214
+ }
215
+ /**
216
+ * **mapValues**
217
+ *
218
+ * Transforms every value of the wrapped object using `fn`, keeping
219
+ * the same keys.
220
+ *
221
+ * @param fn - Mapping function receiving `(value, key)`.
222
+ * @returns A new wrapper around the transformed object.
223
+ *
224
+ * @example
225
+ * ```ts
226
+ * __sys__.utils.obj.of({ a: 1, b: 2 }).mapValues((v) => v * 10).value();
227
+ * // { a: 10, b: 20 }
228
+ * ```
229
+ */
230
+ mapValues(fn) {
231
+ const result = {};
232
+ for (const key of Object.keys(this.current)) {
233
+ result[key] = fn(this.current[key], key);
234
+ }
235
+ return new ObjectWrapper(result);
236
+ }
237
+ /**
238
+ * **mapKeys**
239
+ *
240
+ * Transforms every key of the wrapped object using `fn`, keeping
241
+ * the same values.
242
+ *
243
+ * @param fn - Mapping function receiving `(key, value)`. Must return a string.
244
+ * @returns A new wrapper around the object with renamed keys.
245
+ *
246
+ * @example
247
+ * ```ts
248
+ * __sys__.utils.obj.of({ a: 1, b: 2 })
249
+ * .mapKeys((k) => k.toUpperCase())
250
+ * .value();
251
+ * // { A: 1, B: 2 }
252
+ * ```
253
+ */
254
+ mapKeys(fn) {
255
+ const result = {};
256
+ for (const key of Object.keys(this.current)) {
257
+ result[fn(key, this.current[key])] = this.current[key];
258
+ }
259
+ return new ObjectWrapper(result);
260
+ }
261
+ /**
262
+ * **filter**
263
+ *
264
+ * Keeps only the key-value pairs for which `predicate` returns `true`.
265
+ *
266
+ * @param predicate - Function receiving `(value, key)`.
267
+ * @returns A new wrapper around the filtered object.
268
+ *
269
+ * @example
270
+ * ```ts
271
+ * __sys__.utils.obj.of({ a: 1, b: 2, c: 3 })
272
+ * .filter((v) => v > 1)
273
+ * .value();
274
+ * // { b: 2, c: 3 }
275
+ * ```
276
+ */
277
+ filter(predicate) {
278
+ const result = {};
279
+ for (const key of Object.keys(this.current)) {
280
+ if (predicate(this.current[key], key)) {
281
+ result[key] = this.current[key];
282
+ }
283
+ }
284
+ return new ObjectWrapper(result);
285
+ }
286
+ /**
287
+ * **keys**
288
+ *
289
+ * Returns the own enumerable keys of the wrapped object.
290
+ * This is a terminal read.
291
+ *
292
+ * @returns Array of keys.
293
+ */
294
+ keys() {
295
+ return Object.keys(this.current);
296
+ }
297
+ /**
298
+ * **values**
299
+ *
300
+ * Returns the own enumerable values of the wrapped object.
301
+ * This is a terminal read.
302
+ *
303
+ * @returns Array of values.
304
+ */
305
+ values() {
306
+ return Object.values(this.current);
307
+ }
308
+ /**
309
+ * **entries**
310
+ *
311
+ * Returns the own enumerable `[key, value]` pairs of the wrapped object.
312
+ * This is a terminal read.
313
+ *
314
+ * @returns Array of `[key, value]` tuples.
315
+ */
316
+ entries() {
317
+ return Object.entries(this.current);
318
+ }
319
+ /**
320
+ * **has**
321
+ *
322
+ * Checks whether the wrapped object has the given own key.
323
+ * This is a terminal read.
324
+ *
325
+ * @param key - The key to check.
326
+ * @returns `true` if the key exists on the object.
327
+ */
328
+ has(key) {
329
+ return Object.prototype.hasOwnProperty.call(this.current, key);
330
+ }
331
+ /**
332
+ * **get**
333
+ *
334
+ * Safely reads a possibly-nested value using dot-notation path,
335
+ * returning `fallback` if any part of the path is missing.
336
+ * This is a terminal read.
337
+ *
338
+ * @param path - Dot-notation path (e.g. `"a.b.c"`).
339
+ * @param fallback - Value returned if the path can't be resolved.
340
+ * @returns The resolved value or the fallback.
341
+ *
342
+ * @example
343
+ * ```ts
344
+ * __sys__.utils.obj.of({ a: { b: { c: 42 } } }).get("a.b.c"); // 42
345
+ * __sys__.utils.obj.of({ a: {} }).get("a.b.c", "missing"); // "missing"
346
+ * ```
347
+ */
348
+ get(path, fallback = undefined) {
349
+ const parts = path.split(".");
350
+ let cursor = this.current;
351
+ for (const part of parts) {
352
+ if (cursor === null || cursor === undefined)
353
+ return fallback;
354
+ cursor = cursor[part];
355
+ }
356
+ return cursor === undefined ? fallback : cursor;
357
+ }
358
+ /**
359
+ * **set**
360
+ *
361
+ * Sets a possibly-nested value using dot-notation path, creating
362
+ * intermediate objects as needed.
363
+ *
364
+ * @param path - Dot-notation path (e.g. `"a.b.c"`).
365
+ * @param value - The value to set.
366
+ * @returns `this`, with the value set at the given path.
367
+ *
368
+ * @example
369
+ * ```ts
370
+ * __sys__.utils.obj.of<any>({}).set("a.b.c", 42).value();
371
+ * // { a: { b: { c: 42 } } }
372
+ * ```
373
+ */
374
+ set(path, value) {
375
+ const parts = path.split(".");
376
+ let cursor = this.current;
377
+ parts.forEach((part, i) => {
378
+ if (i === parts.length - 1) {
379
+ cursor[part] = value;
380
+ }
381
+ else {
382
+ if (typeof cursor[part] !== "object" || cursor[part] === null) {
383
+ cursor[part] = {};
384
+ }
385
+ cursor = cursor[part];
386
+ }
387
+ });
388
+ return this;
389
+ }
390
+ /**
391
+ * **equals**
392
+ *
393
+ * Performs a deep structural equality check between the wrapped
394
+ * object and `other`. This is a terminal read.
395
+ *
396
+ * @param other - The object to compare against.
397
+ * @returns `true` if both objects are deeply equal.
398
+ *
399
+ * @example
400
+ * ```ts
401
+ * __sys__.utils.obj.of({ a: { b: 1 } }).equals({ a: { b: 1 } }); // true
402
+ * ```
403
+ */
404
+ equals(other) {
405
+ return __sys__.utils.obj.deepEqual(this.current, other);
406
+ }
407
+ }
3
408
  /**
4
409
  * **ObjectUtils — XyPriss Object Utilities**
410
+ *
411
+ * All original methods remain fully supported and are **not deprecated**;
412
+ * they are the right choice for one-off, single-call operations. For
413
+ * chaining several operations on the same object without repeating it as
414
+ * an argument each time, use {@link __sys__.utils.obj.of} to get a fluent
415
+ * {@link ObjectWrapper} instance instead:
416
+ *
417
+ * @example
418
+ * ```ts
419
+ * // Classic style (still fully supported):
420
+ * const utils = new ObjectUtils();
421
+ * utils.omit(utils.pick(obj, ["a", "b", "c"]), ["c"]);
422
+ *
423
+ * // Fluent style (new, optional):
424
+ * const result = __sys__.utils.obj.of(obj)
425
+ * .pick(["a", "b", "c"])
426
+ * .omit(["c"])
427
+ * .value();
428
+ * ```
5
429
  */
6
430
  class ObjectUtils {
431
+ /**
432
+ * **of**
433
+ *
434
+ * Wraps `obj` in a chainable {@link ObjectWrapper}, so multiple
435
+ * operations can be applied in sequence without re-declaring the
436
+ * object as an argument each time.
437
+ *
438
+ * This does not mutate or replace the classic API — it's an
439
+ * additive convenience entry point.
440
+ *
441
+ * @param obj - The object to wrap.
442
+ * @returns An {@link ObjectWrapper} bound to `obj`.
443
+ *
444
+ * @example
445
+ * ```ts
446
+ * const obj = __sys__.utils.obj.of({ a: 1, b: 2, c: 3 });
447
+ * const picked = obj.pick(["a", "b"]).value(); // { a: 1, b: 2 }
448
+ * ```
449
+ */
450
+ static of(obj) {
451
+ return new ObjectWrapper(obj);
452
+ }
7
453
  /**
8
454
  * **Deep Clone an Object**
9
455
  *
@@ -95,6 +541,12 @@ class ObjectUtils {
95
541
  * @param obj - The object to flatten.
96
542
  * @param separator - Optional path separator (default: `"."`).
97
543
  * @returns A shallow object with path-based keys.
544
+ *
545
+ * @example
546
+ * ```ts
547
+ * utils.flattenObject({ a: { b: 1, c: { d: 2 } } });
548
+ * // { "a.b": 1, "a.c.d": 2 }
549
+ * ```
98
550
  */
99
551
  flattenObject(obj, separator = ".") {
100
552
  const result = {};
@@ -115,6 +567,42 @@ class ObjectUtils {
115
567
  recurse(obj);
116
568
  return result;
117
569
  }
570
+ /**
571
+ * **unflattenObject**
572
+ *
573
+ * Reverses {@link flattenObject}, expanding dot-notation (or custom
574
+ * separator) keys back into a nested object structure.
575
+ *
576
+ * @param obj - The flat object to expand.
577
+ * @param separator - Path separator used in the flat keys (default: `"."`).
578
+ * @returns A nested object reconstructed from the flat keys.
579
+ *
580
+ * @example
581
+ * ```ts
582
+ * utils.unflattenObject({ "a.b": 1, "a.c.d": 2 });
583
+ * // { a: { b: 1, c: { d: 2 } } }
584
+ * ```
585
+ */
586
+ unflattenObject(obj, separator = ".") {
587
+ const result = {};
588
+ for (const [flatKey, val] of Object.entries(obj)) {
589
+ const parts = flatKey.split(separator);
590
+ let cursor = result;
591
+ parts.forEach((part, i) => {
592
+ if (i === parts.length - 1) {
593
+ cursor[part] = val;
594
+ }
595
+ else {
596
+ if (typeof cursor[part] !== "object" ||
597
+ cursor[part] === null) {
598
+ cursor[part] = {};
599
+ }
600
+ cursor = cursor[part];
601
+ }
602
+ });
603
+ }
604
+ return result;
605
+ }
118
606
  /**
119
607
  * **parse**
120
608
  *
@@ -124,6 +612,12 @@ class ObjectUtils {
124
612
  * @param json The JSON string to parse.
125
613
  * @param fallback The fallback value to return on failure.
126
614
  * @returns The parsed object or the fallback value.
615
+ *
616
+ * @example
617
+ * ```ts
618
+ * utils.parse('{"a":1}'); // { a: 1 }
619
+ * utils.parse("not json", {}); // {}
620
+ * ```
127
621
  */
128
622
  parse(json, fallback = null) {
129
623
  try {
@@ -133,7 +627,344 @@ class ObjectUtils {
133
627
  return fallback;
134
628
  }
135
629
  }
630
+ /**
631
+ * **merge**
632
+ *
633
+ * Deep-merges one or more source objects into a copy of `target`.
634
+ * Plain object values are merged recursively; arrays and primitive
635
+ * values are overwritten by the last source that defines them.
636
+ * The original `target` and `sources` are never mutated.
637
+ *
638
+ * @param target - The base object.
639
+ * @param sources - One or more objects to merge into the base.
640
+ * @returns A new object containing the deep-merged result.
641
+ *
642
+ * @example
643
+ * ```ts
644
+ * utils.merge({ a: 1, nested: { x: 1 } }, { nested: { y: 2 } });
645
+ * // { a: 1, nested: { x: 1, y: 2 } }
646
+ * ```
647
+ */
648
+ merge(target, ...sources) {
649
+ const isPlainObject = (val) => !!val && typeof val === "object" && !Array.isArray(val);
650
+ const deepMerge = (t, s) => {
651
+ for (const key of Object.keys(s)) {
652
+ if (isPlainObject(s[key]) && isPlainObject(t[key])) {
653
+ deepMerge(t[key], s[key]);
654
+ }
655
+ else {
656
+ t[key] = s[key];
657
+ }
658
+ }
659
+ return t;
660
+ };
661
+ const base = JSON.parse(XStringify(target));
662
+ return sources.reduce((acc, src) => deepMerge(acc, src), base);
663
+ }
664
+ /**
665
+ * **mapValues**
666
+ *
667
+ * Builds a new object by transforming every value of `obj` with `fn`,
668
+ * keeping the same keys.
669
+ *
670
+ * @param obj - The source object.
671
+ * @param fn - Mapping function receiving `(value, key)`.
672
+ * @returns A new object with transformed values.
673
+ *
674
+ * @example
675
+ * ```ts
676
+ * utils.mapValues({ a: 1, b: 2 }, (v) => v * 10);
677
+ * // { a: 10, b: 20 }
678
+ * ```
679
+ */
680
+ mapValues(obj, fn) {
681
+ const result = {};
682
+ for (const key of Object.keys(obj)) {
683
+ result[key] = fn(obj[key], key);
684
+ }
685
+ return result;
686
+ }
687
+ /**
688
+ * **mapKeys**
689
+ *
690
+ * Builds a new object by transforming every key of `obj` with `fn`,
691
+ * keeping the same values.
692
+ *
693
+ * @param obj - The source object.
694
+ * @param fn - Mapping function receiving `(key, value)`. Must return a string.
695
+ * @returns A new object with renamed keys.
696
+ *
697
+ * @example
698
+ * ```ts
699
+ * utils.mapKeys({ a: 1, b: 2 }, (k) => k.toUpperCase());
700
+ * // { A: 1, B: 2 }
701
+ * ```
702
+ */
703
+ mapKeys(obj, fn) {
704
+ const result = {};
705
+ for (const key of Object.keys(obj)) {
706
+ result[fn(key, obj[key])] = obj[key];
707
+ }
708
+ return result;
709
+ }
710
+ /**
711
+ * **filter**
712
+ *
713
+ * Builds a new object containing only the key-value pairs of `obj`
714
+ * for which `predicate` returns `true`.
715
+ *
716
+ * @param obj - The source object.
717
+ * @param predicate - Function receiving `(value, key)`.
718
+ * @returns A new, filtered object.
719
+ *
720
+ * @example
721
+ * ```ts
722
+ * utils.filter({ a: 1, b: 2, c: 3 }, (v) => v > 1);
723
+ * // { b: 2, c: 3 }
724
+ * ```
725
+ */
726
+ filter(obj, predicate) {
727
+ const result = {};
728
+ for (const key of Object.keys(obj)) {
729
+ if (predicate(obj[key], key)) {
730
+ result[key] = obj[key];
731
+ }
732
+ }
733
+ return result;
734
+ }
735
+ /**
736
+ * **get**
737
+ *
738
+ * Safely reads a possibly-nested value from `obj` using a dot-notation
739
+ * path, returning `fallback` if any part of the path is missing.
740
+ *
741
+ * @param obj - The source object.
742
+ * @param path - Dot-notation path (e.g. `"a.b.c"`).
743
+ * @param fallback - Value returned if the path can't be resolved.
744
+ * @returns The resolved value or the fallback.
745
+ *
746
+ * @example
747
+ * ```ts
748
+ * utils.get({ a: { b: { c: 42 } } }, "a.b.c"); // 42
749
+ * utils.get({ a: {} }, "a.b.c", "missing"); // "missing"
750
+ * ```
751
+ */
752
+ get(obj, path, fallback = undefined) {
753
+ const parts = path.split(".");
754
+ let cursor = obj;
755
+ for (const part of parts) {
756
+ if (cursor === null || cursor === undefined)
757
+ return fallback;
758
+ cursor = cursor[part];
759
+ }
760
+ return cursor === undefined ? fallback : cursor;
761
+ }
762
+ /**
763
+ * **set**
764
+ *
765
+ * Returns a new object equal to `obj` but with a possibly-nested value
766
+ * set at `path` (dot-notation), creating intermediate objects as needed.
767
+ * The original `obj` is not mutated.
768
+ *
769
+ * @param obj - The source object.
770
+ * @param path - Dot-notation path (e.g. `"a.b.c"`).
771
+ * @param value - The value to set.
772
+ * @returns A new object with the value set at the given path.
773
+ *
774
+ * @example
775
+ * ```ts
776
+ * utils.set({}, "a.b.c", 42);
777
+ * // { a: { b: { c: 42 } } }
778
+ * ```
779
+ */
780
+ set(obj, path, value) {
781
+ const clone = JSON.parse(XStringify(obj));
782
+ const parts = path.split(".");
783
+ let cursor = clone;
784
+ parts.forEach((part, i) => {
785
+ if (i === parts.length - 1) {
786
+ cursor[part] = value;
787
+ }
788
+ else {
789
+ if (typeof cursor[part] !== "object" || cursor[part] === null) {
790
+ cursor[part] = {};
791
+ }
792
+ cursor = cursor[part];
793
+ }
794
+ });
795
+ return clone;
796
+ }
797
+ /**
798
+ * **has**
799
+ *
800
+ * Checks whether `obj` has the given own key (shortcut over
801
+ * `Object.prototype.hasOwnProperty`).
802
+ *
803
+ * @param obj - The object to inspect.
804
+ * @param key - The key to check.
805
+ * @returns `true` if the key exists as an own property.
806
+ *
807
+ * @example
808
+ * ```ts
809
+ * utils.has({ a: 1 }, "a"); // true
810
+ * utils.has({ a: 1 }, "b"); // false
811
+ * ```
812
+ */
813
+ has(obj, key) {
814
+ return Object.prototype.hasOwnProperty.call(obj, key);
815
+ }
816
+ /**
817
+ * **deepEqual**
818
+ *
819
+ * Performs a deep structural equality check between two values,
820
+ * comparing plain objects and arrays recursively by value rather
821
+ * than by reference.
822
+ *
823
+ * @param a - First value.
824
+ * @param b - Second value.
825
+ * @returns `true` if both values are deeply equal.
826
+ *
827
+ * @example
828
+ * ```ts
829
+ * utils.deepEqual({ a: { b: 1 } }, { a: { b: 1 } }); // true
830
+ * utils.deepEqual({ a: 1 }, { a: 2 }); // false
831
+ * ```
832
+ */
833
+ static deepEqual(a, b) {
834
+ if (a === b)
835
+ return true;
836
+ if (typeof a !== typeof b)
837
+ return false;
838
+ if (a === null || b === null)
839
+ return a === b;
840
+ if (typeof a !== "object")
841
+ return false;
842
+ if (Array.isArray(a) || Array.isArray(b)) {
843
+ if (!Array.isArray(a) || !Array.isArray(b))
844
+ return false;
845
+ if (a.length !== b.length)
846
+ return false;
847
+ return a.every((item, i) => __sys__.utils.obj.deepEqual(item, b[i]));
848
+ }
849
+ const aKeys = Object.keys(a);
850
+ const bKeys = Object.keys(b);
851
+ if (aKeys.length !== bKeys.length)
852
+ return false;
853
+ return aKeys.every((key) => __sys__.utils.obj.deepEqual(a[key], b[key]));
854
+ }
855
+ /**
856
+ * **deepEqual** (instance method)
857
+ *
858
+ * Instance-bound convenience wrapper around the static
859
+ * {@link __sys__.utils.obj.deepEqual}, for callers already holding an
860
+ * `ObjectUtils` instance.
861
+ *
862
+ * @param a - First value.
863
+ * @param b - Second value.
864
+ * @returns `true` if both values are deeply equal.
865
+ */
866
+ deepEqual(a, b) {
867
+ return __sys__.utils.obj.deepEqual(a, b);
868
+ }
869
+ /**
870
+ * **invert**
871
+ *
872
+ * Swaps keys and values: returns a new object where each original
873
+ * value (stringified) becomes a key, and each original key becomes
874
+ * its value. If multiple keys share a value, the last one wins.
875
+ *
876
+ * @param obj - The source object. Values are coerced to strings for keys.
877
+ * @returns A new object with keys and values swapped.
878
+ *
879
+ * @example
880
+ * ```ts
881
+ * utils.invert({ a: "x", b: "y" });
882
+ * // { x: "a", y: "b" }
883
+ * ```
884
+ */
885
+ invert(obj) {
886
+ const result = {};
887
+ for (const [key, value] of Object.entries(obj)) {
888
+ result[String(value)] = key;
889
+ }
890
+ return result;
891
+ }
892
+ /**
893
+ * **defaults**
894
+ *
895
+ * Returns a new object built from `obj`, filling in any keys that are
896
+ * `undefined` with the corresponding value from `defaultsObj`.
897
+ * Does not overwrite keys that already have a defined value.
898
+ *
899
+ * @param obj - The source object.
900
+ * @param defaultsObj - Object providing fallback values.
901
+ * @returns A new object with defaults applied.
902
+ *
903
+ * @example
904
+ * ```ts
905
+ * utils.defaults({ a: 1, b: undefined }, { b: 2, c: 3 });
906
+ * // { a: 1, b: 2, c: 3 }
907
+ * ```
908
+ */
909
+ defaults(obj, defaultsObj) {
910
+ const result = { ...defaultsObj, ...obj };
911
+ for (const key of Object.keys(result)) {
912
+ if (result[key] === undefined && key in defaultsObj) {
913
+ result[key] = defaultsObj[key];
914
+ }
915
+ }
916
+ return result;
917
+ }
918
+ /**
919
+ * **compact**
920
+ *
921
+ * Returns a new object with all keys whose value is `null` or
922
+ * `undefined` removed.
923
+ *
924
+ * @param obj - The source object.
925
+ * @returns A new object without null/undefined values.
926
+ *
927
+ * @example
928
+ * ```ts
929
+ * utils.compact({ a: 1, b: null, c: undefined, d: 0 });
930
+ * // { a: 1, d: 0 }
931
+ * ```
932
+ */
933
+ compact(obj) {
934
+ const result = {};
935
+ for (const key of Object.keys(obj)) {
936
+ const val = obj[key];
937
+ if (val !== null && val !== undefined) {
938
+ result[key] = val;
939
+ }
940
+ }
941
+ return result;
942
+ }
943
+ /**
944
+ * **isPlainObject**
945
+ *
946
+ * Checks whether `value` is a plain object (i.e. not `null`, not an
947
+ * array, not a class instance created from a custom prototype, and
948
+ * not a built-in like `Date`, `Map`, or `Set`).
949
+ *
950
+ * @param value - The value to check.
951
+ * @returns `true` if `value` is a plain object.
952
+ *
953
+ * @example
954
+ * ```ts
955
+ * utils.isPlainObject({}); // true
956
+ * utils.isPlainObject([]); // false
957
+ * utils.isPlainObject(new Date()); // false
958
+ * utils.isPlainObject(null); // false
959
+ * ```
960
+ */
961
+ isPlainObject(value) {
962
+ if (value === null || typeof value !== "object")
963
+ return false;
964
+ const proto = Object.getPrototypeOf(value);
965
+ return proto === Object.prototype || proto === null;
966
+ }
136
967
  }
137
968
 
138
- export { ObjectUtils };
969
+ export { ObjectUtils, ObjectWrapper };
139
970
  //# sourceMappingURL=ObjectUtils.js.map