ts-data-forge 13.0.0 → 14.0.1

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (93) hide show
  1. package/README.md +4 -3
  2. package/dist/array/impl/array-utils-creation.d.mts +2 -2
  3. package/dist/array/impl/array-utils-creation.d.mts.map +1 -1
  4. package/dist/array/impl/array-utils-creation.mjs.map +1 -1
  5. package/dist/array/impl/array-utils-length-bounded-array-cast.d.mts +56 -29
  6. package/dist/array/impl/array-utils-length-bounded-array-cast.d.mts.map +1 -1
  7. package/dist/array/impl/array-utils-length-bounded-array-cast.mjs +23 -15
  8. package/dist/array/impl/array-utils-length-bounded-array-cast.mjs.map +1 -1
  9. package/dist/array/impl/array-utils-length-bounded-array-guard.d.mts +126 -28
  10. package/dist/array/impl/array-utils-length-bounded-array-guard.d.mts.map +1 -1
  11. package/dist/array/impl/array-utils-length-bounded-array-guard.mjs +39 -5
  12. package/dist/array/impl/array-utils-length-bounded-array-guard.mjs.map +1 -1
  13. package/dist/array/impl/array-utils-length-bounded-tuple-cast.d.mts +167 -0
  14. package/dist/array/impl/array-utils-length-bounded-tuple-cast.d.mts.map +1 -0
  15. package/dist/array/impl/array-utils-length-bounded-tuple-cast.mjs +87 -0
  16. package/dist/array/impl/array-utils-length-bounded-tuple-cast.mjs.map +1 -0
  17. package/dist/array/impl/array-utils-length-bounded-tuple-guard.d.mts +135 -0
  18. package/dist/array/impl/array-utils-length-bounded-tuple-guard.d.mts.map +1 -0
  19. package/dist/array/impl/array-utils-length-bounded-tuple-guard.mjs +41 -0
  20. package/dist/array/impl/array-utils-length-bounded-tuple-guard.mjs.map +1 -0
  21. package/dist/array/impl/array-utils-modification.d.mts +45 -18
  22. package/dist/array/impl/array-utils-modification.d.mts.map +1 -1
  23. package/dist/array/impl/array-utils-modification.mjs.map +1 -1
  24. package/dist/array/impl/array-utils-reducing-value.d.mts +3 -3
  25. package/dist/array/impl/array-utils-reducing-value.d.mts.map +1 -1
  26. package/dist/array/impl/array-utils-reducing-value.mjs +1 -1
  27. package/dist/array/impl/array-utils-reducing-value.mjs.map +1 -1
  28. package/dist/array/impl/array-utils-slice-clamped.d.mts.map +1 -1
  29. package/dist/array/impl/array-utils-slice-clamped.mjs.map +1 -1
  30. package/dist/array/impl/array-utils-slicing.d.mts +11 -11
  31. package/dist/array/impl/array-utils-slicing.d.mts.map +1 -1
  32. package/dist/array/impl/array-utils-slicing.mjs +1 -1
  33. package/dist/array/impl/array-utils-slicing.mjs.map +1 -1
  34. package/dist/array/impl/array-utils-transformation.d.mts +36 -19
  35. package/dist/array/impl/array-utils-transformation.d.mts.map +1 -1
  36. package/dist/array/impl/array-utils-transformation.mjs +2 -2
  37. package/dist/array/impl/array-utils-transformation.mjs.map +1 -1
  38. package/dist/array/impl/array-utils-validation.d.mts +2 -124
  39. package/dist/array/impl/array-utils-validation.d.mts.map +1 -1
  40. package/dist/array/impl/array-utils-validation.mjs +1 -7
  41. package/dist/array/impl/array-utils-validation.mjs.map +1 -1
  42. package/dist/array/impl/index.d.mts +2 -0
  43. package/dist/array/impl/index.d.mts.map +1 -1
  44. package/dist/array/impl/index.mjs +5 -3
  45. package/dist/array/impl/index.mjs.map +1 -1
  46. package/dist/collections/imap.d.mts +1 -1
  47. package/dist/collections/imap.mjs.map +1 -1
  48. package/dist/collections/iset-mapped.d.mts +2 -2
  49. package/dist/collections/iset-mapped.d.mts.map +1 -1
  50. package/dist/collections/iset-mapped.mjs.map +1 -1
  51. package/dist/collections/iset.d.mts +2 -2
  52. package/dist/collections/iset.d.mts.map +1 -1
  53. package/dist/collections/iset.mjs.map +1 -1
  54. package/dist/number/num-arithmetic-types.d.mts +2 -2
  55. package/dist/number/num-arithmetic-types.d.mts.map +1 -1
  56. package/dist/object/object.mjs +1 -1
  57. package/dist/string/str.d.mts +44 -44
  58. package/dist/string/str.d.mts.map +1 -1
  59. package/dist/string/str.mjs +17 -17
  60. package/dist/string/str.mjs.map +1 -1
  61. package/package.json +4 -5
  62. package/src/array/impl/array-utils-creation.mts +7 -3
  63. package/src/array/impl/array-utils-creation.test.mts +10 -8
  64. package/src/array/impl/array-utils-length-bounded-array-cast.mts +88 -50
  65. package/src/array/impl/array-utils-length-bounded-array-cast.test.mts +40 -13
  66. package/src/array/impl/array-utils-length-bounded-array-guard.mts +248 -39
  67. package/src/array/impl/array-utils-length-bounded-array-guard.test.mts +135 -24
  68. package/src/array/impl/array-utils-length-bounded-tuple-cast.mts +364 -0
  69. package/src/array/impl/array-utils-length-bounded-tuple-cast.test.mts +207 -0
  70. package/src/array/impl/array-utils-length-bounded-tuple-guard.mts +266 -0
  71. package/src/array/impl/array-utils-length-bounded-tuple-guard.test.mts +441 -0
  72. package/src/array/impl/array-utils-length-constraint-preservation.test.mts +287 -0
  73. package/src/array/impl/array-utils-modification.mts +84 -61
  74. package/src/array/impl/array-utils-modification.test.mts +145 -6
  75. package/src/array/impl/array-utils-reducing-value.mts +5 -5
  76. package/src/array/impl/array-utils-shape-invariants.test.mts +284 -0
  77. package/src/array/impl/array-utils-slice-clamped.mts +2 -1
  78. package/src/array/impl/array-utils-slicing.mts +65 -43
  79. package/src/array/impl/array-utils-slicing.test.mts +55 -0
  80. package/src/array/impl/array-utils-transformation.mts +91 -37
  81. package/src/array/impl/array-utils-transformation.test.mts +96 -23
  82. package/src/array/impl/array-utils-validation.mts +2 -162
  83. package/src/array/impl/array-utils-validation.test.mts +0 -371
  84. package/src/array/impl/index.mts +2 -0
  85. package/src/collections/imap.mts +1 -1
  86. package/src/collections/iset-mapped.mts +7 -3
  87. package/src/collections/iset.mts +7 -3
  88. package/src/collections/iset.test.mts +9 -6
  89. package/src/collections/queue.test.mts +1 -1
  90. package/src/number/num-arithmetic-types.mts +8 -4
  91. package/src/number/num.test.mts +2 -2
  92. package/src/string/str.mts +69 -69
  93. package/src/string/str.test.mts +40 -40
@@ -1,8 +1,15 @@
1
- import { type FixedLengthTuple, type NonEmptyArray } from 'ts-type-forge';
1
+ import {
2
+ type BoundedLengthArray,
3
+ type FixedLengthTuple,
4
+ type MinLengthArray,
5
+ type NonEmptyArray,
6
+ type NonEmptyTuple,
7
+ } from 'ts-type-forge';
2
8
  import { IMap } from '../../collections/index.mjs';
3
9
  import { expectType } from '../../expect-type.mjs';
4
10
  import { Optional } from '../../functional/index.mjs';
5
11
  import { SafeUint } from '../../number/index.mjs';
12
+ import { asMinLengthArray } from './array-utils-length-bounded-array-cast.mjs';
6
13
  import {
7
14
  cartesianProduct,
8
15
  concat,
@@ -47,7 +54,7 @@ describe('Arr transformations', () => {
47
54
 
48
55
  const mappedMixed = map(mixed, (x) => typeof x);
49
56
 
50
- expectType<typeof mappedMixed, readonly [string, string, string]>('<=');
57
+ expectType<typeof mappedMixed, FixedLengthTuple<3, string>>('<=');
51
58
 
52
59
  assert.deepStrictEqual(mappedMixed, ['number', 'string', 'boolean']);
53
60
  });
@@ -57,12 +64,37 @@ describe('Arr transformations', () => {
57
64
 
58
65
  const mappedWithIndex = map(tuple, (x, i) => `${i}:${x}`);
59
66
 
60
- expectType<typeof mappedWithIndex, readonly [string, string, string]>(
61
- '<=',
62
- );
67
+ expectType<typeof mappedWithIndex, FixedLengthTuple<3, string>>('<=');
63
68
 
64
69
  assert.deepStrictEqual(mappedWithIndex, ['0:a', '1:b', '2:c']);
65
70
  });
71
+
72
+ test('should preserve the tuple shape under a generic parameter', () => {
73
+ // A caller that is itself generic over the tuple cannot resolve a
74
+ // conditional return type, so `map` has to report the homomorphic
75
+ // mapping directly for a parameter that carries no length brand.
76
+ const mapValues = <const T extends readonly Readonly<{ v: unknown }>[]>(
77
+ boxes: T,
78
+ ): Readonly<{ [K in keyof T]: unknown }> => map(boxes, (b) => b.v);
79
+
80
+ const result = mapValues([{ v: 1 }, { v: 'a' }] as const);
81
+
82
+ expectType<typeof result, FixedLengthTuple<2, unknown>>('=');
83
+
84
+ assert.deepStrictEqual<FixedLengthTuple<2, unknown>>(result, [1, 'a']);
85
+ });
86
+
87
+ test('should keep the length brand of a branded array', () => {
88
+ const lower: MinLengthArray<2, number> = asMinLengthArray(2, [1, 2, 3]);
89
+
90
+ const mappedLower = map(lower, String);
91
+
92
+ expectType<typeof mappedLower, MinLengthArray<2, string>>('~=');
93
+
94
+ // The brand is not part of the runtime value, so compare structurally —
95
+ // the type argument is what widens it, no intermediate binding needed.
96
+ assert.deepStrictEqual<readonly string[]>(mappedLower, ['1', '2', '3']);
97
+ });
66
98
  });
67
99
 
68
100
  describe(scan, () => {
@@ -71,7 +103,7 @@ describe('Arr transformations', () => {
71
103
 
72
104
  const runningSum = scan(numbers, (acc, curr) => acc + curr, 0);
73
105
 
74
- expectType<typeof runningSum, readonly [number, ...number[]]>('<=');
106
+ expectType<typeof runningSum, NonEmptyTuple<number>>('<=');
75
107
 
76
108
  assert.deepStrictEqual<readonly number[]>(runningSum, [0, 1, 3, 6, 10]);
77
109
  });
@@ -129,7 +161,7 @@ describe('Arr transformations', () => {
129
161
 
130
162
  const result = scan(strings, (acc, curr) => acc + curr.length, 0);
131
163
 
132
- expectType<typeof result, readonly [number, ...number[]]>('<=');
164
+ expectType<typeof result, NonEmptyTuple<number>>('<=');
133
165
 
134
166
  assert.deepStrictEqual<readonly number[]>(result, [0, 1, 2, 3]);
135
167
  });
@@ -644,13 +676,15 @@ describe('Arr transformations', () => {
644
676
 
645
677
  expectType<
646
678
  typeof result,
647
- readonly (readonly (
679
+ readonly BoundedLengthArray<
680
+ 1,
681
+ 4,
648
682
  1 | 2 | 3 | 4 | 5 | 6 | 7 | 8 | 9 | 10 | 11 | 12
649
- )[])[]
650
- >('=');
683
+ >[]
684
+ >('~=');
651
685
 
652
686
  test('case 1', () => {
653
- assert.deepStrictEqual(result, [
687
+ assert.deepStrictEqual<readonly (readonly number[])[]>(result, [
654
688
  [1, 2, 3, 4],
655
689
  [5, 6, 7, 8],
656
690
  [9, 10, 11, 12],
@@ -663,13 +697,15 @@ describe('Arr transformations', () => {
663
697
 
664
698
  expectType<
665
699
  typeof result,
666
- readonly (readonly (
700
+ readonly BoundedLengthArray<
701
+ 1,
702
+ 3,
667
703
  1 | 2 | 3 | 4 | 5 | 6 | 7 | 8 | 9 | 10 | 11 | 12
668
- )[])[]
669
- >('=');
704
+ >[]
705
+ >('~=');
670
706
 
671
707
  test('case 2', () => {
672
- assert.deepStrictEqual(result, [
708
+ assert.deepStrictEqual<readonly (readonly number[])[]>(result, [
673
709
  [1, 2, 3],
674
710
  [4, 5, 6],
675
711
  [7, 8, 9],
@@ -683,13 +719,15 @@ describe('Arr transformations', () => {
683
719
 
684
720
  expectType<
685
721
  typeof result,
686
- readonly (readonly (
722
+ readonly BoundedLengthArray<
723
+ 1,
724
+ 5,
687
725
  1 | 2 | 3 | 4 | 5 | 6 | 7 | 8 | 9 | 10 | 11 | 12
688
- )[])[]
689
- >('=');
726
+ >[]
727
+ >('~=');
690
728
 
691
729
  test('case 3', () => {
692
- assert.deepStrictEqual(result, [
730
+ assert.deepStrictEqual<readonly (readonly number[])[]>(result, [
693
731
  [1, 2, 3, 4, 5],
694
732
  [6, 7, 8, 9, 10],
695
733
  [11, 12],
@@ -702,7 +740,7 @@ describe('Arr transformations', () => {
702
740
 
703
741
  const result = partition(numbers, 2);
704
742
 
705
- assert.deepStrictEqual(result, [
743
+ assert.deepStrictEqual<readonly (readonly number[])[]>(result, [
706
744
  [1, 2],
707
745
  [3, 4],
708
746
  [5, 6],
@@ -714,7 +752,11 @@ describe('Arr transformations', () => {
714
752
 
715
753
  const result = partition(numbers, 2);
716
754
 
717
- assert.deepStrictEqual(result, [[1, 2], [3, 4], [5]]);
755
+ assert.deepStrictEqual<readonly (readonly number[])[]>(result, [
756
+ [1, 2],
757
+ [3, 4],
758
+ [5],
759
+ ]);
718
760
  });
719
761
 
720
762
  test('should work with chunk size < 2 (returns empty)', () => {
@@ -731,7 +773,7 @@ describe('Arr transformations', () => {
731
773
 
732
774
  const result = partition(numbers, 5);
733
775
 
734
- assert.deepStrictEqual(result, [[1, 2]]);
776
+ assert.deepStrictEqual<readonly (readonly number[])[]>(result, [[1, 2]]);
735
777
  });
736
778
 
737
779
  test('partition should work with empty array', () => {
@@ -1000,7 +1042,7 @@ describe('Arr transformations', () => {
1000
1042
 
1001
1043
  const zipped = zip(xs, ys);
1002
1044
 
1003
- expectType<typeof zipped, readonly (readonly [number, number])[]>('=');
1045
+ expectType<typeof zipped, readonly FixedLengthTuple<2, number>[]>('=');
1004
1046
 
1005
1047
  test('case 2', () => {
1006
1048
  assert.deepStrictEqual(zipped, [[1, 4]]);
@@ -1162,3 +1204,34 @@ describe('Arr transformations', () => {
1162
1204
  });
1163
1205
  });
1164
1206
  });
1207
+
1208
+ /* `toReversed` is a known permutation, so the tuple half can be reversed
1209
+ alongside the brand. It used to fall back to `ChangeArrayElement`, which kept
1210
+ the length but collapsed the positions to their union. Never called; see the
1211
+ note in `array-utils-modification.test.mts`. */
1212
+ export const reversedTypeChecks = (
1213
+ brandedFive: MinLengthArray<5, string>,
1214
+ brandedAndTuple: MinLengthArray<3, number> & readonly [1, 2, 3, 4, 5],
1215
+ ): void => {
1216
+ const _reversedBranded = toReversed(brandedFive);
1217
+
1218
+ expectType<typeof _reversedBranded, MinLengthArray<5, string>>('~=');
1219
+
1220
+ // Brand and positions both survive, and the positions really are reversed.
1221
+ const _reversedBoth = toReversed(brandedAndTuple);
1222
+
1223
+ expectType<(typeof _reversedBoth)[0], 5>('=');
1224
+
1225
+ expectType<(typeof _reversedBoth)[4], 1>('=');
1226
+
1227
+ const _reversedTuple = toReversed([1, 2, 3] as const);
1228
+
1229
+ expectType<typeof _reversedTuple, readonly [3, 2, 1]>('=');
1230
+
1231
+ /* `toSorted` deliberately does NOT take this path: an arbitrary permutation
1232
+ cannot keep positions, so the honest answer stays "same length, element
1233
+ type widened to the union". */
1234
+ const _sortedTuple = toSorted([1, 2, 3] as const);
1235
+
1236
+ expectType<typeof _sortedTuple, FixedLengthTuple<3, 1 | 2 | 3>>('~=');
1237
+ };
@@ -1,13 +1,4 @@
1
- import {
2
- type BoolOr,
3
- type BoundedLengthTuple,
4
- type FixedLengthArray,
5
- type FixedLengthTuple,
6
- type MaxLengthTuple,
7
- type MinLengthArray,
8
- type MinLengthTuple,
9
- type TypeEq,
10
- } from 'ts-type-forge';
1
+ import { type BoolOr, type IsAny, type IsUnknown } from 'ts-type-forge';
11
2
  import { asUint32, Num } from '../../number/index.mjs';
12
3
  import { type ArrayIndex, type SizeType } from '../../types.mjs';
13
4
 
@@ -34,8 +25,7 @@ export const isArray = <E,>(value: E): value is FilterArray<E> =>
34
25
  Array.isArray(value);
35
26
 
36
27
  type FilterArray<T> = T extends T
37
- ? // eslint-disable-next-line @typescript-eslint/no-explicit-any
38
- BoolOr<TypeEq<T, unknown>, TypeEq<T, any>> extends true
28
+ ? BoolOr<IsUnknown<T>, IsAny<T>> extends true
39
29
  ? Cast<readonly unknown[], T>
40
30
  : T extends readonly unknown[]
41
31
  ? T
@@ -46,156 +36,6 @@ type Cast<A, B> = A extends B ? A : never;
46
36
 
47
37
  // validation
48
38
 
49
- /**
50
- * Type guard that checks if an array is empty.
51
- *
52
- * @example
53
- *
54
- * ```ts
55
- * const emptyNumbers: readonly number[] = [] as const;
56
- *
57
- * const words = ['Ada', 'Lovelace'] as const;
58
- *
59
- * assert.isTrue(Arr.isEmpty(emptyNumbers));
60
- *
61
- * assert.isFalse(Arr.isEmpty(words));
62
- *
63
- * if (Arr.isEmpty(emptyNumbers)) {
64
- * // `isEmpty` narrows to the branded `FixedLengthArray<0, number>`, so the
65
- * // expected value is annotated with the unbranded type.
66
- * assert.deepStrictEqual<readonly number[]>(emptyNumbers, []);
67
- * }
68
- * ```
69
- */
70
- export const isEmpty = <Xs extends readonly unknown[]>(
71
- array: Xs,
72
- ): array is FixedLengthArray<0, Xs[number]> & Xs => array.length === 0;
73
-
74
- /**
75
- * Type guard that checks if an array is non-empty.
76
- *
77
- * @example
78
- *
79
- * ```ts
80
- * const users: readonly Readonly<{ id: number }>[] = [{ id: 1 }] as const;
81
- *
82
- * const emptyUsers: readonly Readonly<{ id: number }>[] = [] as const;
83
- *
84
- * assert.isTrue(Arr.isNonEmpty(users));
85
- *
86
- * assert.isFalse(Arr.isNonEmpty(emptyUsers));
87
- *
88
- * if (Arr.isNonEmpty(users)) {
89
- * assert.deepStrictEqual(users[0], { id: 1 });
90
- * }
91
- * ```
92
- */
93
- export const isNonEmpty = <Xs extends readonly unknown[]>(
94
- array: Xs,
95
- ): array is MinLengthArray<1, Xs[number]> & Xs => array.length > 0;
96
-
97
- /**
98
- * Checks if an array has a specific length.
99
- *
100
- * @example
101
- *
102
- * ```ts
103
- * const pair: readonly number[] = [1, 2] as const;
104
- *
105
- * const triple: readonly number[] = [1, 2, 3] as const;
106
- *
107
- * assert.isTrue(Arr.isFixedLengthTuple(pair, 2));
108
- *
109
- * assert.isFalse(Arr.isFixedLengthTuple(triple, 2));
110
- *
111
- * if (Arr.isFixedLengthTuple(pair, 2)) {
112
- * assert.deepStrictEqual(pair, [1, 2]);
113
- * }
114
- * ```
115
- */
116
- export const isFixedLengthTuple = <E, N extends SizeType.ArgArr>(
117
- array: readonly E[],
118
- len: N,
119
- ): array is FixedLengthTuple<N, E> => array.length === len;
120
-
121
- /**
122
- * Checks if an array has at least a specific length.
123
- *
124
- * @example
125
- *
126
- * ```ts
127
- * const queue: readonly string[] = ['task-1', 'task-2'] as const;
128
- *
129
- * const emptyQueue: readonly string[] = [] as const;
130
- *
131
- * assert.isTrue(Arr.isMinLengthTuple(queue, 1));
132
- *
133
- * assert.isFalse(Arr.isMinLengthTuple(emptyQueue, 1));
134
- *
135
- * if (Arr.isMinLengthTuple(queue, 1)) {
136
- * assert.isTrue(queue[0] === 'task-1');
137
- * }
138
- * ```
139
- */
140
- export const isMinLengthTuple = <E, N extends SizeType.ArgArr>(
141
- array: readonly E[],
142
- len: N,
143
- ): array is MinLengthTuple<N, E> => array.length >= len;
144
-
145
- /**
146
- * Checks if an array has at most a specific length.
147
- *
148
- * @example
149
- *
150
- * ```ts
151
- * const pair: readonly number[] = [1, 2] as const;
152
- *
153
- * const triple: readonly number[] = [1, 2, 3] as const;
154
- *
155
- * assert.isTrue(Arr.isMaxLengthTuple(pair, 2));
156
- *
157
- * assert.isFalse(Arr.isMaxLengthTuple(triple, 2));
158
- *
159
- * if (Arr.isMaxLengthTuple(pair, 2)) {
160
- * assert.isTrue(pair.length <= 2);
161
- * }
162
- * ```
163
- */
164
- export const isMaxLengthTuple = <E, N extends SizeType.ArgArr>(
165
- array: readonly E[],
166
- len: N,
167
- ): array is MaxLengthTuple<N, E> => array.length <= len;
168
-
169
- /**
170
- * Checks if an array's length is within a specific inclusive range.
171
- *
172
- * @example
173
- *
174
- * ```ts
175
- * const pair: readonly number[] = [1, 2] as const;
176
- *
177
- * const quad: readonly number[] = [1, 2, 3, 4] as const;
178
- *
179
- * assert.isTrue(Arr.isBoundedLengthTuple(pair, 1, 3));
180
- *
181
- * assert.isFalse(Arr.isBoundedLengthTuple(quad, 1, 3));
182
- *
183
- * if (Arr.isBoundedLengthTuple(pair, 1, 3)) {
184
- * assert.isTrue(pair.length >= 1 && pair.length <= 3);
185
- * }
186
- * ```
187
- */
188
- export const isBoundedLengthTuple = <
189
- E,
190
- Min extends SizeType.ArgArr,
191
- Max extends SizeType.ArgArr,
192
- >(
193
- array: readonly E[],
194
- min: Min,
195
- max: Max,
196
- ): array is BoundedLengthTuple<Min, Max, E> =>
197
- array.length >= min && array.length <= max;
198
-
199
39
  /**
200
40
  * Tests whether all elements in an array pass a test implemented by a predicate.
201
41
  *