ts-data-forge 6.4.0 → 6.6.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 +5 -3
- package/dist/array/impl/array-utils-element-access.d.mts +5 -5
- package/dist/array/impl/array-utils-element-access.mjs +4 -4
- package/dist/array/impl/array-utils-iterators.d.mts +1 -1
- package/dist/array/impl/array-utils-iterators.mjs +1 -1
- package/dist/array/impl/array-utils-modification.d.mts +4 -4
- package/dist/array/impl/array-utils-reducing-value.d.mts +5 -5
- package/dist/array/impl/array-utils-reducing-value.d.mts.map +1 -1
- package/dist/array/impl/array-utils-reducing-value.mjs +1 -0
- package/dist/array/impl/array-utils-reducing-value.mjs.map +1 -1
- package/dist/array/impl/array-utils-search.d.mts +8 -8
- package/dist/array/impl/array-utils-set-op.d.mts.map +1 -1
- package/dist/array/impl/array-utils-set-op.mjs +3 -1
- package/dist/array/impl/array-utils-set-op.mjs.map +1 -1
- package/dist/array/impl/array-utils-size.d.mts +1 -1
- package/dist/array/impl/array-utils-size.mjs +1 -1
- package/dist/array/impl/array-utils-slice-clamped.d.mts +1 -1
- package/dist/array/impl/array-utils-slicing.d.mts +3 -3
- package/dist/array/impl/array-utils-transformation.d.mts.map +1 -1
- package/dist/array/impl/array-utils-validation.d.mts +7 -7
- package/dist/array/impl/array-utils-validation.d.mts.map +1 -1
- package/dist/array/impl/array-utils-validation.mjs +22 -12
- package/dist/array/impl/array-utils-validation.mjs.map +1 -1
- package/dist/collections/imap-mapped.d.mts +5 -12
- package/dist/collections/imap-mapped.d.mts.map +1 -1
- package/dist/collections/imap-mapped.mjs.map +1 -1
- package/dist/collections/imap.d.mts +24 -28
- package/dist/collections/imap.d.mts.map +1 -1
- package/dist/collections/imap.mjs +1 -1
- package/dist/collections/imap.mjs.map +1 -1
- package/dist/collections/iset-mapped.d.mts +8 -14
- package/dist/collections/iset-mapped.d.mts.map +1 -1
- package/dist/collections/iset-mapped.mjs.map +1 -1
- package/dist/collections/iset.d.mts +9 -23
- package/dist/collections/iset.d.mts.map +1 -1
- package/dist/collections/iset.mjs +1 -0
- package/dist/collections/iset.mjs.map +1 -1
- package/dist/entry-point.mjs +1 -0
- package/dist/entry-point.mjs.map +1 -1
- package/dist/functional/optional/impl/optional-is-optional.d.mts +1 -1
- package/dist/functional/optional/impl/optional-is-optional.d.mts.map +1 -1
- package/dist/functional/optional/impl/optional-is-optional.mjs +6 -5
- package/dist/functional/optional/impl/optional-is-optional.mjs.map +1 -1
- package/dist/functional/optional/impl/optional-some.d.mts.map +1 -1
- package/dist/functional/optional/impl/optional-some.mjs.map +1 -1
- package/dist/functional/optional/impl/optional-zip.d.mts +1 -1
- package/dist/functional/optional/impl/optional-zip.mjs +1 -1
- package/dist/functional/result/impl/result-err.d.mts.map +1 -1
- package/dist/functional/result/impl/result-err.mjs.map +1 -1
- package/dist/functional/result/impl/result-is-result.d.mts +1 -1
- package/dist/functional/result/impl/result-is-result.mjs +1 -1
- package/dist/functional/result/impl/result-ok.d.mts.map +1 -1
- package/dist/functional/result/impl/result-ok.mjs.map +1 -1
- package/dist/functional/result/impl/result-unwrap-err-throw.mjs +1 -0
- package/dist/functional/result/impl/result-unwrap-err-throw.mjs.map +1 -1
- package/dist/functional/result/impl/result-unwrap-throw.mjs +1 -0
- package/dist/functional/result/impl/result-unwrap-throw.mjs.map +1 -1
- package/dist/functional/ternary-result/impl/ternary-result-err.d.mts.map +1 -1
- package/dist/functional/ternary-result/impl/ternary-result-err.mjs.map +1 -1
- package/dist/functional/ternary-result/impl/ternary-result-is-ternary-result.d.mts +1 -1
- package/dist/functional/ternary-result/impl/ternary-result-is-ternary-result.mjs +1 -1
- package/dist/functional/ternary-result/impl/ternary-result-ok.d.mts.map +1 -1
- package/dist/functional/ternary-result/impl/ternary-result-ok.mjs.map +1 -1
- package/dist/functional/ternary-result/impl/ternary-result-unwrap-err-throw.mjs +1 -0
- package/dist/functional/ternary-result/impl/ternary-result-unwrap-err-throw.mjs.map +1 -1
- package/dist/functional/ternary-result/impl/ternary-result-unwrap-throw.mjs +1 -0
- package/dist/functional/ternary-result/impl/ternary-result-unwrap-throw.mjs.map +1 -1
- package/dist/functional/ternary-result/impl/ternary-result-unwrap-warn-throw.mjs +1 -0
- package/dist/functional/ternary-result/impl/ternary-result-unwrap-warn-throw.mjs.map +1 -1
- package/dist/functional/ternary-result/impl/ternary-result-warn.d.mts.map +1 -1
- package/dist/functional/ternary-result/impl/ternary-result-warn.mjs.map +1 -1
- package/dist/guard/has-key.d.mts +3 -1
- package/dist/guard/has-key.d.mts.map +1 -1
- package/dist/guard/has-key.mjs +6 -2
- package/dist/guard/has-key.mjs.map +1 -1
- package/dist/guard/is-non-null-object.d.mts.map +1 -1
- package/dist/guard/is-non-null-object.mjs +3 -1
- package/dist/guard/is-non-null-object.mjs.map +1 -1
- package/dist/guard/is-record.d.mts +2 -2
- package/dist/guard/is-record.mjs +2 -2
- package/dist/guard/is-type.d.mts +15 -15
- package/dist/guard/is-type.mjs +15 -15
- package/dist/guard/key-is-in.d.mts.map +1 -1
- package/dist/guard/key-is-in.mjs +3 -1
- package/dist/guard/key-is-in.mjs.map +1 -1
- package/dist/index.mjs +1 -0
- package/dist/index.mjs.map +1 -1
- package/dist/json/json.d.mts +3 -3
- package/dist/json/json.mjs +4 -3
- package/dist/json/json.mjs.map +1 -1
- package/dist/number/branded-types/non-zero-safe-int.mjs +2 -2
- package/dist/number/branded-types/positive-safe-int.mjs +1 -1
- package/dist/number/branded-types/safe-int.mjs +2 -2
- package/dist/number/branded-types/safe-uint.mjs +1 -1
- package/dist/number/num.mjs +1 -1
- package/dist/object/object.d.mts +36 -8
- package/dist/object/object.d.mts.map +1 -1
- package/dist/object/object.mjs +28 -6
- package/dist/object/object.mjs.map +1 -1
- package/dist/others/fast-deep-equal.d.mts +8 -0
- package/dist/others/fast-deep-equal.d.mts.map +1 -0
- package/dist/others/fast-deep-equal.mjs +72 -0
- package/dist/others/fast-deep-equal.mjs.map +1 -0
- package/dist/others/index.d.mts +1 -0
- package/dist/others/index.d.mts.map +1 -1
- package/dist/others/index.mjs +1 -0
- package/dist/others/index.mjs.map +1 -1
- package/package.json +34 -29
- package/src/array/impl/array-utils-creation.test.mts +13 -10
- package/src/array/impl/array-utils-element-access.mts +5 -5
- package/src/array/impl/array-utils-element-access.test.mts +5 -5
- package/src/array/impl/array-utils-iterators.mts +1 -1
- package/src/array/impl/array-utils-iterators.test.mts +6 -6
- package/src/array/impl/array-utils-modification.mts +4 -4
- package/src/array/impl/array-utils-modification.test.mts +12 -12
- package/src/array/impl/array-utils-overload-type-error.test.mts +1 -1
- package/src/array/impl/array-utils-reducing-value.mts +6 -5
- package/src/array/impl/array-utils-reducing-value.test.mts +9 -9
- package/src/array/impl/array-utils-search.mts +8 -8
- package/src/array/impl/array-utils-search.test.mts +35 -35
- package/src/array/impl/array-utils-set-op.mts +1 -0
- package/src/array/impl/array-utils-set-op.test.mts +2 -2
- package/src/array/impl/array-utils-size.mts +1 -1
- package/src/array/impl/array-utils-size.test.mts +1 -1
- package/src/array/impl/array-utils-slice-clamped.mts +1 -1
- package/src/array/impl/array-utils-slice-clamped.test.mts +5 -5
- package/src/array/impl/array-utils-slicing.mts +3 -3
- package/src/array/impl/array-utils-slicing.test.mts +5 -5
- package/src/array/impl/array-utils-transformation.mts +1 -1
- package/src/array/impl/array-utils-transformation.test.mts +53 -53
- package/src/array/impl/array-utils-validation.mts +18 -10
- package/src/array/impl/array-utils-validation.test.mts +34 -29
- package/src/array/impl/array.test.mts +1 -1
- package/src/collections/imap-mapped.mts +4 -10
- package/src/collections/imap-mapped.test.mts +9 -9
- package/src/collections/imap.mts +24 -27
- package/src/collections/imap.test.mts +6 -6
- package/src/collections/iset-mapped.mts +12 -16
- package/src/collections/iset-mapped.test.mts +6 -6
- package/src/collections/iset.mts +14 -25
- package/src/collections/queue.test.mts +2 -1
- package/src/collections/stack.test.mts +3 -3
- package/src/functional/optional/impl/optional-is-optional.mts +6 -6
- package/src/functional/optional/impl/optional-some.mts +5 -4
- package/src/functional/optional/impl/optional-zip.mts +1 -1
- package/src/functional/result/impl/result-err.mts +5 -4
- package/src/functional/result/impl/result-is-result.mts +1 -1
- package/src/functional/result/impl/result-ok.mts +5 -4
- package/src/functional/result.test.mts +2 -2
- package/src/functional/ternary-result/impl/ternary-result-err.mts +5 -4
- package/src/functional/ternary-result/impl/ternary-result-is-ternary-result.mts +1 -1
- package/src/functional/ternary-result/impl/ternary-result-ok.mts +5 -4
- package/src/functional/ternary-result/impl/ternary-result-warn.mts +6 -5
- package/src/guard/has-key.mts +6 -2
- package/src/guard/is-error.test.mts +1 -1
- package/src/guard/is-non-empty-string.test.mts +1 -1
- package/src/guard/is-non-null-object.mts +1 -0
- package/src/guard/is-non-null-object.test.mts +4 -3
- package/src/guard/is-primitive.test.mts +1 -1
- package/src/guard/is-record.mts +2 -2
- package/src/guard/is-record.test.mts +2 -2
- package/src/guard/is-type.mts +15 -15
- package/src/guard/is-type.test.mts +2 -2
- package/src/guard/key-is-in.mts +3 -1
- package/src/json/json.mts +4 -3
- package/src/json/json.test.mts +20 -19
- package/src/number/branded-types/non-zero-safe-int.mts +2 -2
- package/src/number/branded-types/positive-safe-int.mts +1 -1
- package/src/number/branded-types/safe-int.mts +2 -2
- package/src/number/branded-types/safe-uint.mts +1 -1
- package/src/number/num.mts +1 -1
- package/src/object/object.mts +54 -8
- package/src/object/object.test.mts +70 -7
- package/src/others/cast-mutable.test.mts +3 -3
- package/src/others/cast-readonly.test.mts +10 -5
- package/src/others/fast-deep-equal.mts +98 -0
- package/src/others/fast-deep-equal.test.mts +771 -0
- package/src/others/index.mts +1 -0
- package/src/others/map-nullable.test.mts +8 -8
- package/src/others/memoize-function.test.mts +20 -8
- package/src/others/unknown-to-string.test.mts +8 -8
package/src/json/json.test.mts
CHANGED
|
@@ -54,7 +54,7 @@ describe('parse', () => {
|
|
|
54
54
|
array: [1, 2, { level3: 'deep' }],
|
|
55
55
|
},
|
|
56
56
|
},
|
|
57
|
-
};
|
|
57
|
+
} as const;
|
|
58
58
|
|
|
59
59
|
assert.deepStrictEqual(Json.parse(json), Result.ok(expected));
|
|
60
60
|
});
|
|
@@ -227,7 +227,7 @@ describe('stringify', () => {
|
|
|
227
227
|
array: [1, 2, { level3: 'deep' }],
|
|
228
228
|
},
|
|
229
229
|
},
|
|
230
|
-
};
|
|
230
|
+
} as const;
|
|
231
231
|
|
|
232
232
|
assert.deepStrictEqual(
|
|
233
233
|
Json.stringify(nested),
|
|
@@ -301,7 +301,7 @@ describe('stringify', () => {
|
|
|
301
301
|
test('should handle objects with toJSON method', () => {
|
|
302
302
|
const obj = {
|
|
303
303
|
toJSON: () => ({ custom: 'value' }),
|
|
304
|
-
};
|
|
304
|
+
} as const;
|
|
305
305
|
|
|
306
306
|
assert.deepStrictEqual(
|
|
307
307
|
Json.stringify(obj),
|
|
@@ -333,7 +333,7 @@ describe('stringify', () => {
|
|
|
333
333
|
name: 'John',
|
|
334
334
|
password: 'secret123',
|
|
335
335
|
email: 'john@example.com',
|
|
336
|
-
};
|
|
336
|
+
} as const;
|
|
337
337
|
|
|
338
338
|
const secureReplacer = (key: string, value: unknown): unknown => {
|
|
339
339
|
if (key === 'password') return '[REDACTED]';
|
|
@@ -351,7 +351,7 @@ describe('stringify', () => {
|
|
|
351
351
|
});
|
|
352
352
|
|
|
353
353
|
test('should format output with space parameter (number)', () => {
|
|
354
|
-
const data = { a: 1, b: 2 };
|
|
354
|
+
const data = { a: 1, b: 2 } as const;
|
|
355
355
|
|
|
356
356
|
const result = Json.stringify(data, undefined, 2);
|
|
357
357
|
|
|
@@ -363,7 +363,7 @@ describe('stringify', () => {
|
|
|
363
363
|
});
|
|
364
364
|
|
|
365
365
|
test('should format output with space parameter (string)', () => {
|
|
366
|
-
const data = { a: 1, b: 2 };
|
|
366
|
+
const data = { a: 1, b: 2 } as const;
|
|
367
367
|
|
|
368
368
|
const result = Json.stringify(data, undefined, '\t');
|
|
369
369
|
|
|
@@ -383,7 +383,7 @@ describe('stringifySelected', () => {
|
|
|
383
383
|
email: 'alice@example.com',
|
|
384
384
|
password: 'secret123',
|
|
385
385
|
lastLogin: '2023-12-01',
|
|
386
|
-
};
|
|
386
|
+
} as const;
|
|
387
387
|
|
|
388
388
|
const result = Json.stringifySelected(user, ['id', 'name', 'email']);
|
|
389
389
|
|
|
@@ -411,7 +411,7 @@ describe('stringifySelected', () => {
|
|
|
411
411
|
{ id: 2, name: 'Bob', secret: 'hidden2' },
|
|
412
412
|
],
|
|
413
413
|
metadata: { total: 2, page: 1, internal: 'secret' },
|
|
414
|
-
};
|
|
414
|
+
} as const;
|
|
415
415
|
|
|
416
416
|
const result = Json.stringifySelected(data, [
|
|
417
417
|
'users',
|
|
@@ -453,7 +453,7 @@ describe('stringifySelected', () => {
|
|
|
453
453
|
[1, 2, 3, 4],
|
|
454
454
|
[5, 6, 7, 8],
|
|
455
455
|
[9, 10, 11, 12],
|
|
456
|
-
];
|
|
456
|
+
] as const;
|
|
457
457
|
|
|
458
458
|
const result = Json.stringifySelected(matrix, [0, 1]);
|
|
459
459
|
|
|
@@ -465,13 +465,14 @@ describe('stringifySelected', () => {
|
|
|
465
465
|
|
|
466
466
|
// Note: stringifySelected works with JSON.stringify's replacer parameter
|
|
467
467
|
// which may not work as expected with arrays
|
|
468
|
+
// eslint-disable-next-line ts-data-forge/prefer-arr-is-array
|
|
468
469
|
assert.isTrue(Array.isArray(parsed));
|
|
469
470
|
|
|
470
471
|
expect(parsed).toHaveLength(3);
|
|
471
472
|
});
|
|
472
473
|
|
|
473
474
|
test('should handle formatting with space parameter', () => {
|
|
474
|
-
const data = { a: 1, b: { c: 2 } };
|
|
475
|
+
const data = { a: 1, b: { c: 2 } } as const;
|
|
475
476
|
|
|
476
477
|
const result = Json.stringifySelected(data, ['a', 'b', 'c'], 2);
|
|
477
478
|
|
|
@@ -483,7 +484,7 @@ describe('stringifySelected', () => {
|
|
|
483
484
|
});
|
|
484
485
|
|
|
485
486
|
test('should handle empty selection array', () => {
|
|
486
|
-
const data = { a: 1, b: 2, c: 3 };
|
|
487
|
+
const data = { a: 1, b: 2, c: 3 } as const;
|
|
487
488
|
|
|
488
489
|
const result = Json.stringifySelected(data, []);
|
|
489
490
|
|
|
@@ -493,7 +494,7 @@ describe('stringifySelected', () => {
|
|
|
493
494
|
});
|
|
494
495
|
|
|
495
496
|
test('should handle undefined properties parameter', () => {
|
|
496
|
-
const data = { a: 1, b: 2 };
|
|
497
|
+
const data = { a: 1, b: 2 } as const;
|
|
497
498
|
|
|
498
499
|
const result = Json.stringifySelected(data, undefined);
|
|
499
500
|
|
|
@@ -532,7 +533,7 @@ describe('stringifySortedKey', () => {
|
|
|
532
533
|
apple: 'fruit',
|
|
533
534
|
banana: 'fruit',
|
|
534
535
|
aardvark: 'animal',
|
|
535
|
-
};
|
|
536
|
+
} as const;
|
|
536
537
|
|
|
537
538
|
const result = Json.stringifySortedKey(unsortedObj);
|
|
538
539
|
|
|
@@ -558,7 +559,7 @@ describe('stringifySortedKey', () => {
|
|
|
558
559
|
theme: 'dark',
|
|
559
560
|
language: 'en',
|
|
560
561
|
},
|
|
561
|
-
};
|
|
562
|
+
} as const;
|
|
562
563
|
|
|
563
564
|
const result = Json.stringifySortedKey(nestedObj);
|
|
564
565
|
|
|
@@ -598,7 +599,7 @@ describe('stringifySortedKey', () => {
|
|
|
598
599
|
created: '2023-12-01',
|
|
599
600
|
author: 'system',
|
|
600
601
|
},
|
|
601
|
-
};
|
|
602
|
+
} as const;
|
|
602
603
|
|
|
603
604
|
const result = Json.stringifySortedKey(dataWithArrays);
|
|
604
605
|
|
|
@@ -639,7 +640,7 @@ describe('stringifySortedKey', () => {
|
|
|
639
640
|
});
|
|
640
641
|
|
|
641
642
|
test('should handle formatting with space parameter', () => {
|
|
642
|
-
const obj = { b: 2, a: 1 };
|
|
643
|
+
const obj = { b: 2, a: 1 } as const;
|
|
643
644
|
|
|
644
645
|
const result = Json.stringifySortedKey(obj, 2);
|
|
645
646
|
|
|
@@ -653,9 +654,9 @@ describe('stringifySortedKey', () => {
|
|
|
653
654
|
});
|
|
654
655
|
|
|
655
656
|
test('should produce deterministic output', () => {
|
|
656
|
-
const obj1 = { c: 3, a: 1, b: 2 };
|
|
657
|
+
const obj1 = { c: 3, a: 1, b: 2 } as const;
|
|
657
658
|
|
|
658
|
-
const obj2 = { b: 2, a: 1, c: 3 };
|
|
659
|
+
const obj2 = { b: 2, a: 1, c: 3 } as const;
|
|
659
660
|
|
|
660
661
|
const result1 = Json.stringifySortedKey(obj1);
|
|
661
662
|
|
|
@@ -716,7 +717,7 @@ describe('stringifySortedKey', () => {
|
|
|
716
717
|
},
|
|
717
718
|
},
|
|
718
719
|
},
|
|
719
|
-
};
|
|
720
|
+
} as const;
|
|
720
721
|
|
|
721
722
|
const result = Json.stringifySortedKey(deep);
|
|
722
723
|
|
|
@@ -27,9 +27,9 @@ const {
|
|
|
27
27
|
>({
|
|
28
28
|
integerOrSafeInteger: 'SafeInteger',
|
|
29
29
|
nonZero: true,
|
|
30
|
-
// eslint-disable-next-line total-functions/no-unsafe-type-assertion, math/prefer-number-min-safe-integer
|
|
30
|
+
// eslint-disable-next-line total-functions/no-unsafe-type-assertion, math/prefer-number-min-safe-integer, ts-data-forge/prefer-as-int
|
|
31
31
|
MIN_VALUE: Number.MIN_SAFE_INTEGER as SafeInt,
|
|
32
|
-
// eslint-disable-next-line total-functions/no-unsafe-type-assertion, math/prefer-number-max-safe-integer
|
|
32
|
+
// eslint-disable-next-line total-functions/no-unsafe-type-assertion, math/prefer-number-max-safe-integer, ts-data-forge/prefer-as-int
|
|
33
33
|
MAX_VALUE: Number.MAX_SAFE_INTEGER as SafeUint,
|
|
34
34
|
typeNameInMessage,
|
|
35
35
|
} as const);
|
|
@@ -26,7 +26,7 @@ const {
|
|
|
26
26
|
>({
|
|
27
27
|
integerOrSafeInteger: 'SafeInteger',
|
|
28
28
|
MIN_VALUE: 1,
|
|
29
|
-
// eslint-disable-next-line total-functions/no-unsafe-type-assertion, math/prefer-number-max-safe-integer
|
|
29
|
+
// eslint-disable-next-line total-functions/no-unsafe-type-assertion, math/prefer-number-max-safe-integer, ts-data-forge/prefer-as-int
|
|
30
30
|
MAX_VALUE: Number.MAX_SAFE_INTEGER as SafeUint,
|
|
31
31
|
typeNameInMessage,
|
|
32
32
|
} as const);
|
|
@@ -26,9 +26,9 @@ const {
|
|
|
26
26
|
SafeUint
|
|
27
27
|
>({
|
|
28
28
|
integerOrSafeInteger: 'SafeInteger',
|
|
29
|
-
// eslint-disable-next-line total-functions/no-unsafe-type-assertion, math/prefer-number-min-safe-integer
|
|
29
|
+
// eslint-disable-next-line total-functions/no-unsafe-type-assertion, math/prefer-number-min-safe-integer, ts-data-forge/prefer-as-int
|
|
30
30
|
MIN_VALUE: Number.MIN_SAFE_INTEGER as SafeInt,
|
|
31
|
-
// eslint-disable-next-line total-functions/no-unsafe-type-assertion, math/prefer-number-max-safe-integer
|
|
31
|
+
// eslint-disable-next-line total-functions/no-unsafe-type-assertion, math/prefer-number-max-safe-integer, ts-data-forge/prefer-as-int
|
|
32
32
|
MAX_VALUE: Number.MAX_SAFE_INTEGER as SafeUint,
|
|
33
33
|
typeNameInMessage,
|
|
34
34
|
} as const);
|
|
@@ -26,7 +26,7 @@ const {
|
|
|
26
26
|
>({
|
|
27
27
|
integerOrSafeInteger: 'SafeInteger',
|
|
28
28
|
MIN_VALUE: 0,
|
|
29
|
-
// eslint-disable-next-line total-functions/no-unsafe-type-assertion, math/prefer-number-max-safe-integer
|
|
29
|
+
// eslint-disable-next-line total-functions/no-unsafe-type-assertion, math/prefer-number-max-safe-integer, ts-data-forge/prefer-as-int
|
|
30
30
|
MAX_VALUE: Number.MAX_SAFE_INTEGER as SafeUint,
|
|
31
31
|
typeNameInMessage,
|
|
32
32
|
} as const);
|
package/src/number/num.mts
CHANGED
|
@@ -391,7 +391,7 @@ export namespace Num {
|
|
|
391
391
|
* @param num - The number to round
|
|
392
392
|
* @returns The rounded integer as an Int branded type
|
|
393
393
|
*/
|
|
394
|
-
// eslint-disable-next-line total-functions/no-unsafe-type-assertion
|
|
394
|
+
// eslint-disable-next-line total-functions/no-unsafe-type-assertion, ts-data-forge/prefer-as-int
|
|
395
395
|
export const roundToInt = (num: number): Int => Math.trunc(num + 0.5) as Int;
|
|
396
396
|
|
|
397
397
|
/**
|
package/src/object/object.mts
CHANGED
|
@@ -16,20 +16,20 @@ export namespace Obj {
|
|
|
16
16
|
* @example
|
|
17
17
|
*
|
|
18
18
|
* ```ts
|
|
19
|
-
* const obj1 = { name: 'Alice', age: 30 };
|
|
19
|
+
* const obj1 = { name: 'Alice', age: 30 } as const;
|
|
20
20
|
*
|
|
21
|
-
* const obj2 = { name: 'Alice', age: 30 };
|
|
21
|
+
* const obj2 = { name: 'Alice', age: 30 } as const;
|
|
22
22
|
*
|
|
23
|
-
* const obj3 = { name: 'Alice', age: 31 };
|
|
23
|
+
* const obj3 = { name: 'Alice', age: 31 } as const;
|
|
24
24
|
*
|
|
25
25
|
* assert.isTrue(Obj.shallowEq(obj1, obj2));
|
|
26
26
|
*
|
|
27
27
|
* assert.isFalse(Obj.shallowEq(obj1, obj3));
|
|
28
28
|
*
|
|
29
29
|
* // Custom equality function
|
|
30
|
-
* const obj4 = { value: 1 };
|
|
30
|
+
* const obj4 = { value: 1 } as const;
|
|
31
31
|
*
|
|
32
|
-
* const obj5 = { value: 1.00001 };
|
|
32
|
+
* const obj5 = { value: 1.00001 } as const;
|
|
33
33
|
*
|
|
34
34
|
* const closeEnough = (a: unknown, b: unknown): boolean => {
|
|
35
35
|
* if (typeof a === 'number' && typeof b === 'number') {
|
|
@@ -58,6 +58,7 @@ export namespace Obj {
|
|
|
58
58
|
|
|
59
59
|
const bEntries = Object.entries(b);
|
|
60
60
|
|
|
61
|
+
// eslint-disable-next-line ts-data-forge/prefer-arr-is-array-of-length
|
|
61
62
|
if (aEntries.length !== bEntries.length) return false;
|
|
62
63
|
|
|
63
64
|
return aEntries.every(([k, v]) => eq(b[k], v));
|
|
@@ -80,7 +81,7 @@ export namespace Obj {
|
|
|
80
81
|
* email: 'bob@example.com',
|
|
81
82
|
* password: 'secret',
|
|
82
83
|
* role: 'admin',
|
|
83
|
-
* };
|
|
84
|
+
* } as const;
|
|
84
85
|
*
|
|
85
86
|
* // Direct usage
|
|
86
87
|
* const publicInfo = Obj.pick(user, ['id', 'name', 'role']);
|
|
@@ -166,7 +167,7 @@ export namespace Obj {
|
|
|
166
167
|
* email: 'charlie@example.com',
|
|
167
168
|
* password: 'secret123',
|
|
168
169
|
* internalNote: 'VIP customer',
|
|
169
|
-
* };
|
|
170
|
+
* } as const;
|
|
170
171
|
*
|
|
171
172
|
* // Direct usage - remove sensitive fields
|
|
172
173
|
* const safeUser = Obj.omit(user, ['password', 'internalNote']);
|
|
@@ -277,7 +278,7 @@ export namespace Obj {
|
|
|
277
278
|
* const dynamicEntries: readonly (readonly ['x' | 'y', number])[] = [
|
|
278
279
|
* ['x', 10],
|
|
279
280
|
* ['y', 20],
|
|
280
|
-
* ];
|
|
281
|
+
* ] as const;
|
|
281
282
|
*
|
|
282
283
|
* const obj2 = Obj.fromEntries(dynamicEntries);
|
|
283
284
|
*
|
|
@@ -377,5 +378,50 @@ export namespace Obj {
|
|
|
377
378
|
|
|
378
379
|
export type PartialIfKeyIsUnion<K, T> =
|
|
379
380
|
IsUnion<K> extends true ? Partial<T> : T;
|
|
381
|
+
|
|
382
|
+
/** Merges two object types where keys in B override keys in A. */
|
|
383
|
+
type MergeTwo<A extends UnknownRecord, B extends UnknownRecord> = Readonly<{
|
|
384
|
+
[K in keyof A | keyof B]: K extends keyof B
|
|
385
|
+
? B[K]
|
|
386
|
+
: K extends keyof A
|
|
387
|
+
? A[K]
|
|
388
|
+
: never;
|
|
389
|
+
}>;
|
|
390
|
+
|
|
391
|
+
/** Sequentially merges a tuple of object types from left to right. */
|
|
392
|
+
export type MergeAll<
|
|
393
|
+
Records extends readonly UnknownRecord[],
|
|
394
|
+
// eslint-disable-next-line @typescript-eslint/no-empty-object-type
|
|
395
|
+
Acc extends UnknownRecord = {},
|
|
396
|
+
> = Records extends readonly [
|
|
397
|
+
infer First extends UnknownRecord,
|
|
398
|
+
...infer Rest extends readonly UnknownRecord[],
|
|
399
|
+
]
|
|
400
|
+
? MergeAll<Rest, MergeTwo<Acc, First>>
|
|
401
|
+
: Acc;
|
|
380
402
|
}
|
|
403
|
+
|
|
404
|
+
/**
|
|
405
|
+
* Merges multiple records into a single record using `Object.assign`.
|
|
406
|
+
* Later records override properties from earlier records with the same key.
|
|
407
|
+
*
|
|
408
|
+
* @example
|
|
409
|
+
*
|
|
410
|
+
* ```ts
|
|
411
|
+
* const a = { a: 0, b: 0 } as const;
|
|
412
|
+
* const b = { b: 1, c: 0 } as const;
|
|
413
|
+
*
|
|
414
|
+
* const result = Obj.merge(a, b);
|
|
415
|
+
*
|
|
416
|
+
* assert.deepStrictEqual(result, { a: 0, b: 1, c: 0 });
|
|
417
|
+
* ```
|
|
418
|
+
*
|
|
419
|
+
* @param records - The records to merge
|
|
420
|
+
* @returns A new record with all properties merged
|
|
421
|
+
*/
|
|
422
|
+
export const merge = <const Records extends readonly UnknownRecord[]>(
|
|
423
|
+
...records: Records
|
|
424
|
+
): TsDataForgeInternals.MergeAll<Records> =>
|
|
425
|
+
// eslint-disable-next-line total-functions/no-unsafe-type-assertion
|
|
426
|
+
Object.fromEntries(records.flatMap((r) => Object.entries(r))) as never;
|
|
381
427
|
}
|
|
@@ -47,7 +47,12 @@ describe('pick', () => {
|
|
|
47
47
|
test('pick should work with pipe when curried', () => {
|
|
48
48
|
const pickIdAndName = Obj.pick(['id', 'name']);
|
|
49
49
|
|
|
50
|
-
const user = {
|
|
50
|
+
const user = {
|
|
51
|
+
id: 1,
|
|
52
|
+
name: 'Alice',
|
|
53
|
+
email: 'alice@example.com',
|
|
54
|
+
age: 30,
|
|
55
|
+
} as const;
|
|
51
56
|
|
|
52
57
|
const result = pipe(user).map(pickIdAndName).value;
|
|
53
58
|
|
|
@@ -103,7 +108,7 @@ describe('omit', () => {
|
|
|
103
108
|
name: 'Alice',
|
|
104
109
|
email: 'alice@example.com',
|
|
105
110
|
password: 'secret123',
|
|
106
|
-
};
|
|
111
|
+
} as const;
|
|
107
112
|
|
|
108
113
|
const result = pipe(user).map(omitSensitive).value;
|
|
109
114
|
|
|
@@ -113,7 +118,7 @@ describe('omit', () => {
|
|
|
113
118
|
test('omit should handle empty keys in curried form', () => {
|
|
114
119
|
const omitNone = Obj.omit([]);
|
|
115
120
|
|
|
116
|
-
const original = { a: 1, b: 2, c: 3 };
|
|
121
|
+
const original = { a: 1, b: 2, c: 3 } as const;
|
|
117
122
|
|
|
118
123
|
const result = omitNone(original);
|
|
119
124
|
|
|
@@ -149,9 +154,9 @@ describe('omit', () => {
|
|
|
149
154
|
describe('fromEntries', () => {
|
|
150
155
|
test('should build readonly object from fixed entries', () => {
|
|
151
156
|
const entries = [
|
|
152
|
-
['name', 'Alice']
|
|
153
|
-
['age', 30
|
|
154
|
-
['active', true
|
|
157
|
+
['name', 'Alice'],
|
|
158
|
+
['age', 30],
|
|
159
|
+
['active', true],
|
|
155
160
|
] as const;
|
|
156
161
|
|
|
157
162
|
const result = Obj.fromEntries(entries);
|
|
@@ -167,7 +172,7 @@ describe('fromEntries', () => {
|
|
|
167
172
|
test('should produce partial record when keys are unions', () => {
|
|
168
173
|
const dynamicEntries: readonly (readonly ['name' | 'email', string])[] = [
|
|
169
174
|
['name', 'Alice'],
|
|
170
|
-
];
|
|
175
|
+
] as const;
|
|
171
176
|
|
|
172
177
|
const result = Obj.fromEntries(dynamicEntries) satisfies Partial<
|
|
173
178
|
Readonly<Record<'name' | 'email', string>>
|
|
@@ -176,3 +181,61 @@ describe('fromEntries', () => {
|
|
|
176
181
|
assert.deepStrictEqual(result, { name: 'Alice' });
|
|
177
182
|
});
|
|
178
183
|
});
|
|
184
|
+
|
|
185
|
+
describe('merge', () => {
|
|
186
|
+
test('should merge two objects, later overriding earlier', () => {
|
|
187
|
+
const a = { a: 0, b: 0 } as const;
|
|
188
|
+
|
|
189
|
+
const b = { b: 1, c: 0 } as const;
|
|
190
|
+
|
|
191
|
+
const result = Obj.merge(a, b);
|
|
192
|
+
|
|
193
|
+
expectType<typeof result, Readonly<{ a: 0; b: 1; c: 0 }>>('=');
|
|
194
|
+
|
|
195
|
+
assert.deepStrictEqual(result, { a: 0, b: 1, c: 0 });
|
|
196
|
+
});
|
|
197
|
+
|
|
198
|
+
test('should merge three objects', () => {
|
|
199
|
+
const a = { x: 1, y: 2 } as const;
|
|
200
|
+
|
|
201
|
+
const b = { y: 3, z: 4 } as const;
|
|
202
|
+
|
|
203
|
+
const c = { z: 5, w: 6 } as const;
|
|
204
|
+
|
|
205
|
+
const result = Obj.merge(a, b, c);
|
|
206
|
+
|
|
207
|
+
expectType<typeof result, Readonly<{ x: 1; y: 3; z: 5; w: 6 }>>('=');
|
|
208
|
+
|
|
209
|
+
assert.deepStrictEqual(result, { x: 1, y: 3, z: 5, w: 6 });
|
|
210
|
+
});
|
|
211
|
+
|
|
212
|
+
test('should return empty object when called with no arguments', () => {
|
|
213
|
+
const result = Obj.merge();
|
|
214
|
+
|
|
215
|
+
expectType<typeof result, {}>('=');
|
|
216
|
+
|
|
217
|
+
assert.deepStrictEqual(result, {});
|
|
218
|
+
});
|
|
219
|
+
|
|
220
|
+
test('should return the same shape for a single argument', () => {
|
|
221
|
+
const a = { a: 1, b: 2 } as const;
|
|
222
|
+
|
|
223
|
+
const result = Obj.merge(a);
|
|
224
|
+
|
|
225
|
+
expectType<typeof result, Readonly<{ a: 1; b: 2 }>>('=');
|
|
226
|
+
|
|
227
|
+
assert.deepStrictEqual(result, { a: 1, b: 2 });
|
|
228
|
+
});
|
|
229
|
+
|
|
230
|
+
test('type: later key completely overrides earlier key type', () => {
|
|
231
|
+
const a = { key: 'hello' } as const;
|
|
232
|
+
|
|
233
|
+
const b = { key: 42 } as const;
|
|
234
|
+
|
|
235
|
+
const result = Obj.merge(a, b);
|
|
236
|
+
|
|
237
|
+
expectType<typeof result, Readonly<{ key: 42 }>>('=');
|
|
238
|
+
|
|
239
|
+
assert.deepStrictEqual(result, { key: 42 });
|
|
240
|
+
});
|
|
241
|
+
});
|
|
@@ -3,7 +3,7 @@ import { castDeepMutable, castMutable } from './cast-mutable.mjs';
|
|
|
3
3
|
|
|
4
4
|
describe(castMutable, () => {
|
|
5
5
|
test('should allow mutating arrays that were readonly', () => {
|
|
6
|
-
const readonlyArray: readonly number[] = [1, 2, 3];
|
|
6
|
+
const readonlyArray: readonly number[] = [1, 2, 3] as const;
|
|
7
7
|
|
|
8
8
|
const mut_array = castMutable(readonlyArray);
|
|
9
9
|
|
|
@@ -21,7 +21,7 @@ describe(castMutable, () => {
|
|
|
21
21
|
const readonlyUser: Readonly<{ name: string; age: number }> = {
|
|
22
22
|
name: 'Alice',
|
|
23
23
|
age: 30,
|
|
24
|
-
};
|
|
24
|
+
} as const;
|
|
25
25
|
|
|
26
26
|
const mut_user = castMutable(readonlyUser);
|
|
27
27
|
|
|
@@ -56,7 +56,7 @@ describe(castDeepMutable, () => {
|
|
|
56
56
|
tags: ['admin', 'owner'],
|
|
57
57
|
},
|
|
58
58
|
},
|
|
59
|
-
};
|
|
59
|
+
} as const;
|
|
60
60
|
|
|
61
61
|
const mut_state = castDeepMutable(readonlyState);
|
|
62
62
|
|
|
@@ -2,7 +2,7 @@ import { castDeepReadonly, castReadonly } from './cast-readonly.mjs';
|
|
|
2
2
|
|
|
3
3
|
describe(castReadonly, () => {
|
|
4
4
|
test('should cast mutable array to readonly', () => {
|
|
5
|
-
const mutableArr = [1, 2, 3];
|
|
5
|
+
const mutableArr = [1, 2, 3] as const;
|
|
6
6
|
|
|
7
7
|
const readonlyArr = castReadonly(mutableArr);
|
|
8
8
|
|
|
@@ -12,7 +12,7 @@ describe(castReadonly, () => {
|
|
|
12
12
|
});
|
|
13
13
|
|
|
14
14
|
test('should cast mutable object to readonly', () => {
|
|
15
|
-
const mutableObj = { x: 1, y: 2 };
|
|
15
|
+
const mutableObj = { x: 1, y: 2 } as const;
|
|
16
16
|
|
|
17
17
|
const readonlyObj = castReadonly(mutableObj);
|
|
18
18
|
|
|
@@ -22,7 +22,7 @@ describe(castReadonly, () => {
|
|
|
22
22
|
});
|
|
23
23
|
|
|
24
24
|
test('should preserve the runtime value', () => {
|
|
25
|
-
const original = { value: 42 };
|
|
25
|
+
const original = { value: 42 } as const;
|
|
26
26
|
|
|
27
27
|
const readonly = castReadonly(original);
|
|
28
28
|
|
|
@@ -52,7 +52,7 @@ describe(castDeepReadonly, () => {
|
|
|
52
52
|
const mutableNested = {
|
|
53
53
|
a: { b: [1, 2, 3] },
|
|
54
54
|
c: { d: { e: 'value' } },
|
|
55
|
-
};
|
|
55
|
+
} as const;
|
|
56
56
|
|
|
57
57
|
const readonlyNested = castDeepReadonly(mutableNested);
|
|
58
58
|
|
|
@@ -64,9 +64,13 @@ describe(castDeepReadonly, () => {
|
|
|
64
64
|
});
|
|
65
65
|
|
|
66
66
|
test('should preserve runtime value for complex structures', () => {
|
|
67
|
+
// transformer-ignore-next-line convert-to-readonly
|
|
67
68
|
const complex = {
|
|
68
69
|
users: [{ id: 1, profile: { name: 'Alice' } }],
|
|
69
70
|
settings: { theme: 'dark', options: { debug: true } },
|
|
71
|
+
} as {
|
|
72
|
+
users: { id: number; profile: { name: string } }[];
|
|
73
|
+
settings: { theme: string; options: { debug: boolean } };
|
|
70
74
|
};
|
|
71
75
|
|
|
72
76
|
const readonly = castDeepReadonly(complex);
|
|
@@ -79,10 +83,11 @@ describe(castDeepReadonly, () => {
|
|
|
79
83
|
});
|
|
80
84
|
|
|
81
85
|
test('should work with arrays of objects', () => {
|
|
86
|
+
// transformer-ignore-next-line convert-to-readonly
|
|
82
87
|
const data = [
|
|
83
88
|
{ id: 1, meta: { active: true } },
|
|
84
89
|
{ id: 2, meta: { active: false } },
|
|
85
|
-
];
|
|
90
|
+
] as { id: number; meta: { active: boolean } }[];
|
|
86
91
|
|
|
87
92
|
const readonly = castDeepReadonly(data);
|
|
88
93
|
|
|
@@ -0,0 +1,98 @@
|
|
|
1
|
+
/* eslint-disable ts-data-forge/prefer-arr-is-array-of-length */
|
|
2
|
+
/* eslint-disable ts-data-forge/prefer-arr-is-array */
|
|
3
|
+
|
|
4
|
+
import { isMap, isRegExp, isSet, isTypedArray } from '@sindresorhus/is';
|
|
5
|
+
import { hasKey, isRecord } from '../guard/index.mjs';
|
|
6
|
+
|
|
7
|
+
/**
|
|
8
|
+
* The fastest deep equal with ES6 Map, Set and Typed arrays support.
|
|
9
|
+
* Checks equality of Date and RegExp objects by value.
|
|
10
|
+
*
|
|
11
|
+
* Forked from https://github.com/epoberezkin/fast-deep-equal/blob/v3.1.3/src/index.jst
|
|
12
|
+
*/
|
|
13
|
+
export const fastDeepEqual = <T,>(a: T, b: T): boolean => {
|
|
14
|
+
if (a === b) {
|
|
15
|
+
return true;
|
|
16
|
+
}
|
|
17
|
+
|
|
18
|
+
if (typeof a !== typeof b) {
|
|
19
|
+
return false;
|
|
20
|
+
}
|
|
21
|
+
|
|
22
|
+
if (isRecord(a)) {
|
|
23
|
+
if (!isRecord(b)) {
|
|
24
|
+
return false;
|
|
25
|
+
}
|
|
26
|
+
|
|
27
|
+
if (a.constructor !== b.constructor) {
|
|
28
|
+
return false;
|
|
29
|
+
}
|
|
30
|
+
|
|
31
|
+
if (Array.isArray(a)) {
|
|
32
|
+
return (
|
|
33
|
+
Array.isArray(b) &&
|
|
34
|
+
a.length === b.length &&
|
|
35
|
+
a.every((ai, index) => fastDeepEqual(ai, b[index]))
|
|
36
|
+
);
|
|
37
|
+
}
|
|
38
|
+
|
|
39
|
+
if (isMap(a)) {
|
|
40
|
+
return (
|
|
41
|
+
isMap(b) &&
|
|
42
|
+
a.size === b.size &&
|
|
43
|
+
Array.from(a.entries()).every(
|
|
44
|
+
([key, value]) => b.has(key) && fastDeepEqual(value, b.get(key)),
|
|
45
|
+
)
|
|
46
|
+
);
|
|
47
|
+
}
|
|
48
|
+
|
|
49
|
+
if (isSet(a)) {
|
|
50
|
+
return (
|
|
51
|
+
isSet(b) &&
|
|
52
|
+
a.size === b.size &&
|
|
53
|
+
Array.from(a.entries()).every(([value]) => b.has(value))
|
|
54
|
+
);
|
|
55
|
+
}
|
|
56
|
+
|
|
57
|
+
if (isTypedArray(a)) {
|
|
58
|
+
return (
|
|
59
|
+
isTypedArray(b) &&
|
|
60
|
+
a.length === b.length &&
|
|
61
|
+
a.every((value, index) => value === b[index])
|
|
62
|
+
);
|
|
63
|
+
}
|
|
64
|
+
|
|
65
|
+
if (isRegExp(a)) {
|
|
66
|
+
return isRegExp(b) && a.source === b.source && a.flags === b.flags;
|
|
67
|
+
}
|
|
68
|
+
|
|
69
|
+
if (a.valueOf !== Object.prototype.valueOf) {
|
|
70
|
+
return a.valueOf() === b.valueOf();
|
|
71
|
+
}
|
|
72
|
+
|
|
73
|
+
if (a.toString !== Object.prototype.toString) {
|
|
74
|
+
return a.toString() === b.toString();
|
|
75
|
+
}
|
|
76
|
+
|
|
77
|
+
const aKeys: readonly string[] = Object.keys(a);
|
|
78
|
+
|
|
79
|
+
const bKeys: readonly string[] = Object.keys(b);
|
|
80
|
+
|
|
81
|
+
return (
|
|
82
|
+
aKeys.length === bKeys.length &&
|
|
83
|
+
aKeys.every(
|
|
84
|
+
(key) =>
|
|
85
|
+
hasKey(b, key) &&
|
|
86
|
+
// React-specific: avoid traversing React elements' _owner.
|
|
87
|
+
// _owner contains circular references
|
|
88
|
+
// and is not needed when comparing the actual elements (and not their owners)
|
|
89
|
+
// eslint-disable-next-line @typescript-eslint/strict-boolean-expressions
|
|
90
|
+
!(key === '_owner' && a['$$typeof']) &&
|
|
91
|
+
fastDeepEqual(a[key], b[key]),
|
|
92
|
+
)
|
|
93
|
+
);
|
|
94
|
+
}
|
|
95
|
+
|
|
96
|
+
// true if both NaN, false otherwise
|
|
97
|
+
return Number.isNaN(a) && Number.isNaN(b);
|
|
98
|
+
};
|