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.
Files changed (181) hide show
  1. package/README.md +5 -3
  2. package/dist/array/impl/array-utils-element-access.d.mts +5 -5
  3. package/dist/array/impl/array-utils-element-access.mjs +4 -4
  4. package/dist/array/impl/array-utils-iterators.d.mts +1 -1
  5. package/dist/array/impl/array-utils-iterators.mjs +1 -1
  6. package/dist/array/impl/array-utils-modification.d.mts +4 -4
  7. package/dist/array/impl/array-utils-reducing-value.d.mts +5 -5
  8. package/dist/array/impl/array-utils-reducing-value.d.mts.map +1 -1
  9. package/dist/array/impl/array-utils-reducing-value.mjs +1 -0
  10. package/dist/array/impl/array-utils-reducing-value.mjs.map +1 -1
  11. package/dist/array/impl/array-utils-search.d.mts +8 -8
  12. package/dist/array/impl/array-utils-set-op.d.mts.map +1 -1
  13. package/dist/array/impl/array-utils-set-op.mjs +3 -1
  14. package/dist/array/impl/array-utils-set-op.mjs.map +1 -1
  15. package/dist/array/impl/array-utils-size.d.mts +1 -1
  16. package/dist/array/impl/array-utils-size.mjs +1 -1
  17. package/dist/array/impl/array-utils-slice-clamped.d.mts +1 -1
  18. package/dist/array/impl/array-utils-slicing.d.mts +3 -3
  19. package/dist/array/impl/array-utils-transformation.d.mts.map +1 -1
  20. package/dist/array/impl/array-utils-validation.d.mts +7 -7
  21. package/dist/array/impl/array-utils-validation.d.mts.map +1 -1
  22. package/dist/array/impl/array-utils-validation.mjs +22 -12
  23. package/dist/array/impl/array-utils-validation.mjs.map +1 -1
  24. package/dist/collections/imap-mapped.d.mts +5 -12
  25. package/dist/collections/imap-mapped.d.mts.map +1 -1
  26. package/dist/collections/imap-mapped.mjs.map +1 -1
  27. package/dist/collections/imap.d.mts +24 -28
  28. package/dist/collections/imap.d.mts.map +1 -1
  29. package/dist/collections/imap.mjs +1 -1
  30. package/dist/collections/imap.mjs.map +1 -1
  31. package/dist/collections/iset-mapped.d.mts +8 -14
  32. package/dist/collections/iset-mapped.d.mts.map +1 -1
  33. package/dist/collections/iset-mapped.mjs.map +1 -1
  34. package/dist/collections/iset.d.mts +9 -23
  35. package/dist/collections/iset.d.mts.map +1 -1
  36. package/dist/collections/iset.mjs +1 -0
  37. package/dist/collections/iset.mjs.map +1 -1
  38. package/dist/entry-point.mjs +1 -0
  39. package/dist/entry-point.mjs.map +1 -1
  40. package/dist/functional/optional/impl/optional-is-optional.d.mts +1 -1
  41. package/dist/functional/optional/impl/optional-is-optional.d.mts.map +1 -1
  42. package/dist/functional/optional/impl/optional-is-optional.mjs +6 -5
  43. package/dist/functional/optional/impl/optional-is-optional.mjs.map +1 -1
  44. package/dist/functional/optional/impl/optional-some.d.mts.map +1 -1
  45. package/dist/functional/optional/impl/optional-some.mjs.map +1 -1
  46. package/dist/functional/optional/impl/optional-zip.d.mts +1 -1
  47. package/dist/functional/optional/impl/optional-zip.mjs +1 -1
  48. package/dist/functional/result/impl/result-err.d.mts.map +1 -1
  49. package/dist/functional/result/impl/result-err.mjs.map +1 -1
  50. package/dist/functional/result/impl/result-is-result.d.mts +1 -1
  51. package/dist/functional/result/impl/result-is-result.mjs +1 -1
  52. package/dist/functional/result/impl/result-ok.d.mts.map +1 -1
  53. package/dist/functional/result/impl/result-ok.mjs.map +1 -1
  54. package/dist/functional/result/impl/result-unwrap-err-throw.mjs +1 -0
  55. package/dist/functional/result/impl/result-unwrap-err-throw.mjs.map +1 -1
  56. package/dist/functional/result/impl/result-unwrap-throw.mjs +1 -0
  57. package/dist/functional/result/impl/result-unwrap-throw.mjs.map +1 -1
  58. package/dist/functional/ternary-result/impl/ternary-result-err.d.mts.map +1 -1
  59. package/dist/functional/ternary-result/impl/ternary-result-err.mjs.map +1 -1
  60. package/dist/functional/ternary-result/impl/ternary-result-is-ternary-result.d.mts +1 -1
  61. package/dist/functional/ternary-result/impl/ternary-result-is-ternary-result.mjs +1 -1
  62. package/dist/functional/ternary-result/impl/ternary-result-ok.d.mts.map +1 -1
  63. package/dist/functional/ternary-result/impl/ternary-result-ok.mjs.map +1 -1
  64. package/dist/functional/ternary-result/impl/ternary-result-unwrap-err-throw.mjs +1 -0
  65. package/dist/functional/ternary-result/impl/ternary-result-unwrap-err-throw.mjs.map +1 -1
  66. package/dist/functional/ternary-result/impl/ternary-result-unwrap-throw.mjs +1 -0
  67. package/dist/functional/ternary-result/impl/ternary-result-unwrap-throw.mjs.map +1 -1
  68. package/dist/functional/ternary-result/impl/ternary-result-unwrap-warn-throw.mjs +1 -0
  69. package/dist/functional/ternary-result/impl/ternary-result-unwrap-warn-throw.mjs.map +1 -1
  70. package/dist/functional/ternary-result/impl/ternary-result-warn.d.mts.map +1 -1
  71. package/dist/functional/ternary-result/impl/ternary-result-warn.mjs.map +1 -1
  72. package/dist/guard/has-key.d.mts +3 -1
  73. package/dist/guard/has-key.d.mts.map +1 -1
  74. package/dist/guard/has-key.mjs +6 -2
  75. package/dist/guard/has-key.mjs.map +1 -1
  76. package/dist/guard/is-non-null-object.d.mts.map +1 -1
  77. package/dist/guard/is-non-null-object.mjs +3 -1
  78. package/dist/guard/is-non-null-object.mjs.map +1 -1
  79. package/dist/guard/is-record.d.mts +2 -2
  80. package/dist/guard/is-record.mjs +2 -2
  81. package/dist/guard/is-type.d.mts +15 -15
  82. package/dist/guard/is-type.mjs +15 -15
  83. package/dist/guard/key-is-in.d.mts.map +1 -1
  84. package/dist/guard/key-is-in.mjs +3 -1
  85. package/dist/guard/key-is-in.mjs.map +1 -1
  86. package/dist/index.mjs +1 -0
  87. package/dist/index.mjs.map +1 -1
  88. package/dist/json/json.d.mts +3 -3
  89. package/dist/json/json.mjs +4 -3
  90. package/dist/json/json.mjs.map +1 -1
  91. package/dist/number/branded-types/non-zero-safe-int.mjs +2 -2
  92. package/dist/number/branded-types/positive-safe-int.mjs +1 -1
  93. package/dist/number/branded-types/safe-int.mjs +2 -2
  94. package/dist/number/branded-types/safe-uint.mjs +1 -1
  95. package/dist/number/num.mjs +1 -1
  96. package/dist/object/object.d.mts +36 -8
  97. package/dist/object/object.d.mts.map +1 -1
  98. package/dist/object/object.mjs +28 -6
  99. package/dist/object/object.mjs.map +1 -1
  100. package/dist/others/fast-deep-equal.d.mts +8 -0
  101. package/dist/others/fast-deep-equal.d.mts.map +1 -0
  102. package/dist/others/fast-deep-equal.mjs +72 -0
  103. package/dist/others/fast-deep-equal.mjs.map +1 -0
  104. package/dist/others/index.d.mts +1 -0
  105. package/dist/others/index.d.mts.map +1 -1
  106. package/dist/others/index.mjs +1 -0
  107. package/dist/others/index.mjs.map +1 -1
  108. package/package.json +34 -29
  109. package/src/array/impl/array-utils-creation.test.mts +13 -10
  110. package/src/array/impl/array-utils-element-access.mts +5 -5
  111. package/src/array/impl/array-utils-element-access.test.mts +5 -5
  112. package/src/array/impl/array-utils-iterators.mts +1 -1
  113. package/src/array/impl/array-utils-iterators.test.mts +6 -6
  114. package/src/array/impl/array-utils-modification.mts +4 -4
  115. package/src/array/impl/array-utils-modification.test.mts +12 -12
  116. package/src/array/impl/array-utils-overload-type-error.test.mts +1 -1
  117. package/src/array/impl/array-utils-reducing-value.mts +6 -5
  118. package/src/array/impl/array-utils-reducing-value.test.mts +9 -9
  119. package/src/array/impl/array-utils-search.mts +8 -8
  120. package/src/array/impl/array-utils-search.test.mts +35 -35
  121. package/src/array/impl/array-utils-set-op.mts +1 -0
  122. package/src/array/impl/array-utils-set-op.test.mts +2 -2
  123. package/src/array/impl/array-utils-size.mts +1 -1
  124. package/src/array/impl/array-utils-size.test.mts +1 -1
  125. package/src/array/impl/array-utils-slice-clamped.mts +1 -1
  126. package/src/array/impl/array-utils-slice-clamped.test.mts +5 -5
  127. package/src/array/impl/array-utils-slicing.mts +3 -3
  128. package/src/array/impl/array-utils-slicing.test.mts +5 -5
  129. package/src/array/impl/array-utils-transformation.mts +1 -1
  130. package/src/array/impl/array-utils-transformation.test.mts +53 -53
  131. package/src/array/impl/array-utils-validation.mts +18 -10
  132. package/src/array/impl/array-utils-validation.test.mts +34 -29
  133. package/src/array/impl/array.test.mts +1 -1
  134. package/src/collections/imap-mapped.mts +4 -10
  135. package/src/collections/imap-mapped.test.mts +9 -9
  136. package/src/collections/imap.mts +24 -27
  137. package/src/collections/imap.test.mts +6 -6
  138. package/src/collections/iset-mapped.mts +12 -16
  139. package/src/collections/iset-mapped.test.mts +6 -6
  140. package/src/collections/iset.mts +14 -25
  141. package/src/collections/queue.test.mts +2 -1
  142. package/src/collections/stack.test.mts +3 -3
  143. package/src/functional/optional/impl/optional-is-optional.mts +6 -6
  144. package/src/functional/optional/impl/optional-some.mts +5 -4
  145. package/src/functional/optional/impl/optional-zip.mts +1 -1
  146. package/src/functional/result/impl/result-err.mts +5 -4
  147. package/src/functional/result/impl/result-is-result.mts +1 -1
  148. package/src/functional/result/impl/result-ok.mts +5 -4
  149. package/src/functional/result.test.mts +2 -2
  150. package/src/functional/ternary-result/impl/ternary-result-err.mts +5 -4
  151. package/src/functional/ternary-result/impl/ternary-result-is-ternary-result.mts +1 -1
  152. package/src/functional/ternary-result/impl/ternary-result-ok.mts +5 -4
  153. package/src/functional/ternary-result/impl/ternary-result-warn.mts +6 -5
  154. package/src/guard/has-key.mts +6 -2
  155. package/src/guard/is-error.test.mts +1 -1
  156. package/src/guard/is-non-empty-string.test.mts +1 -1
  157. package/src/guard/is-non-null-object.mts +1 -0
  158. package/src/guard/is-non-null-object.test.mts +4 -3
  159. package/src/guard/is-primitive.test.mts +1 -1
  160. package/src/guard/is-record.mts +2 -2
  161. package/src/guard/is-record.test.mts +2 -2
  162. package/src/guard/is-type.mts +15 -15
  163. package/src/guard/is-type.test.mts +2 -2
  164. package/src/guard/key-is-in.mts +3 -1
  165. package/src/json/json.mts +4 -3
  166. package/src/json/json.test.mts +20 -19
  167. package/src/number/branded-types/non-zero-safe-int.mts +2 -2
  168. package/src/number/branded-types/positive-safe-int.mts +1 -1
  169. package/src/number/branded-types/safe-int.mts +2 -2
  170. package/src/number/branded-types/safe-uint.mts +1 -1
  171. package/src/number/num.mts +1 -1
  172. package/src/object/object.mts +54 -8
  173. package/src/object/object.test.mts +70 -7
  174. package/src/others/cast-mutable.test.mts +3 -3
  175. package/src/others/cast-readonly.test.mts +10 -5
  176. package/src/others/fast-deep-equal.mts +98 -0
  177. package/src/others/fast-deep-equal.test.mts +771 -0
  178. package/src/others/index.mts +1 -0
  179. package/src/others/map-nullable.test.mts +8 -8
  180. package/src/others/memoize-function.test.mts +20 -8
  181. package/src/others/unknown-to-string.test.mts +8 -8
@@ -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);
@@ -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
  /**
@@ -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 = { id: 1, name: 'Alice', email: 'alice@example.com', age: 30 };
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'] as const,
153
- ['age', 30 as const],
154
- ['active', true as const],
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
+ };