ts-data-forge 13.0.0 → 14.0.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 +1 -1
- package/dist/array/impl/array-utils-creation.d.mts +2 -2
- package/dist/array/impl/array-utils-creation.d.mts.map +1 -1
- package/dist/array/impl/array-utils-creation.mjs.map +1 -1
- package/dist/array/impl/array-utils-length-bounded-array-cast.d.mts +56 -29
- package/dist/array/impl/array-utils-length-bounded-array-cast.d.mts.map +1 -1
- package/dist/array/impl/array-utils-length-bounded-array-cast.mjs +23 -15
- package/dist/array/impl/array-utils-length-bounded-array-cast.mjs.map +1 -1
- package/dist/array/impl/array-utils-length-bounded-array-guard.d.mts +126 -28
- package/dist/array/impl/array-utils-length-bounded-array-guard.d.mts.map +1 -1
- package/dist/array/impl/array-utils-length-bounded-array-guard.mjs +39 -5
- package/dist/array/impl/array-utils-length-bounded-array-guard.mjs.map +1 -1
- package/dist/array/impl/array-utils-length-bounded-tuple-cast.d.mts +167 -0
- package/dist/array/impl/array-utils-length-bounded-tuple-cast.d.mts.map +1 -0
- package/dist/array/impl/array-utils-length-bounded-tuple-cast.mjs +87 -0
- package/dist/array/impl/array-utils-length-bounded-tuple-cast.mjs.map +1 -0
- package/dist/array/impl/array-utils-length-bounded-tuple-guard.d.mts +135 -0
- package/dist/array/impl/array-utils-length-bounded-tuple-guard.d.mts.map +1 -0
- package/dist/array/impl/array-utils-length-bounded-tuple-guard.mjs +41 -0
- package/dist/array/impl/array-utils-length-bounded-tuple-guard.mjs.map +1 -0
- package/dist/array/impl/array-utils-modification.d.mts +32 -18
- package/dist/array/impl/array-utils-modification.d.mts.map +1 -1
- package/dist/array/impl/array-utils-modification.mjs.map +1 -1
- package/dist/array/impl/array-utils-reducing-value.d.mts +3 -3
- package/dist/array/impl/array-utils-reducing-value.d.mts.map +1 -1
- package/dist/array/impl/array-utils-reducing-value.mjs +1 -1
- package/dist/array/impl/array-utils-reducing-value.mjs.map +1 -1
- package/dist/array/impl/array-utils-slicing.d.mts +11 -11
- package/dist/array/impl/array-utils-slicing.d.mts.map +1 -1
- package/dist/array/impl/array-utils-slicing.mjs +1 -1
- package/dist/array/impl/array-utils-slicing.mjs.map +1 -1
- package/dist/array/impl/array-utils-transformation.d.mts +35 -23
- package/dist/array/impl/array-utils-transformation.d.mts.map +1 -1
- package/dist/array/impl/array-utils-transformation.mjs +2 -2
- package/dist/array/impl/array-utils-transformation.mjs.map +1 -1
- package/dist/array/impl/array-utils-validation.d.mts +2 -124
- package/dist/array/impl/array-utils-validation.d.mts.map +1 -1
- package/dist/array/impl/array-utils-validation.mjs +1 -7
- package/dist/array/impl/array-utils-validation.mjs.map +1 -1
- package/dist/array/impl/index.d.mts +2 -0
- package/dist/array/impl/index.d.mts.map +1 -1
- package/dist/array/impl/index.mjs +5 -3
- package/dist/array/impl/index.mjs.map +1 -1
- package/dist/object/object.mjs +1 -1
- package/dist/string/str.d.mts +44 -44
- package/dist/string/str.d.mts.map +1 -1
- package/dist/string/str.mjs +17 -17
- package/dist/string/str.mjs.map +1 -1
- package/package.json +2 -2
- package/src/array/impl/array-utils-creation.mts +7 -3
- package/src/array/impl/array-utils-length-bounded-array-cast.mts +88 -50
- package/src/array/impl/array-utils-length-bounded-array-cast.test.mts +40 -13
- package/src/array/impl/array-utils-length-bounded-array-guard.mts +248 -39
- package/src/array/impl/array-utils-length-bounded-array-guard.test.mts +135 -24
- package/src/array/impl/array-utils-length-bounded-tuple-cast.mts +364 -0
- package/src/array/impl/array-utils-length-bounded-tuple-cast.test.mts +207 -0
- package/src/array/impl/array-utils-length-bounded-tuple-guard.mts +266 -0
- package/src/array/impl/array-utils-length-bounded-tuple-guard.test.mts +441 -0
- package/src/array/impl/array-utils-length-constraint-preservation.test.mts +287 -0
- package/src/array/impl/array-utils-modification.mts +72 -57
- package/src/array/impl/array-utils-modification.test.mts +93 -6
- package/src/array/impl/array-utils-reducing-value.mts +5 -5
- package/src/array/impl/array-utils-slicing.mts +65 -43
- package/src/array/impl/array-utils-slicing.test.mts +55 -0
- package/src/array/impl/array-utils-transformation.mts +66 -39
- package/src/array/impl/array-utils-transformation.test.mts +62 -16
- package/src/array/impl/array-utils-validation.mts +2 -162
- package/src/array/impl/array-utils-validation.test.mts +0 -371
- package/src/array/impl/index.mts +2 -0
- package/src/collections/queue.test.mts +1 -1
- package/src/string/str.mts +69 -69
- package/src/string/str.test.mts +40 -40
|
@@ -1,4 +1,6 @@
|
|
|
1
1
|
import {
|
|
2
|
+
type ConstrainedList,
|
|
3
|
+
type HasLengthConstraint,
|
|
2
4
|
type List,
|
|
3
5
|
type NonEmptyArray,
|
|
4
6
|
type NonEmptyTuple,
|
|
@@ -6,9 +8,9 @@ import {
|
|
|
6
8
|
} from 'ts-type-forge';
|
|
7
9
|
import { Uint32 } from '../../number/index.mjs';
|
|
8
10
|
import { type SizeType } from '../../types.mjs';
|
|
11
|
+
import { isEmpty } from './array-utils-length-bounded-array-guard.mjs';
|
|
9
12
|
import { size } from './array-utils-size.mjs';
|
|
10
13
|
import { sliceClamped } from './array-utils-slice-clamped.mjs';
|
|
11
|
-
import { isEmpty } from './array-utils-validation.mjs';
|
|
12
14
|
|
|
13
15
|
/**
|
|
14
16
|
* Returns all elements of an array except the first one.
|
|
@@ -41,9 +43,9 @@ import { isEmpty } from './array-utils-validation.mjs';
|
|
|
41
43
|
*/
|
|
42
44
|
export const tail = <const Ar extends readonly unknown[]>(
|
|
43
45
|
array: Ar,
|
|
44
|
-
):
|
|
46
|
+
): ConstrainedList.Tail<Ar> =>
|
|
45
47
|
// eslint-disable-next-line total-functions/no-unsafe-type-assertion
|
|
46
|
-
array.slice(1) as unknown as
|
|
48
|
+
array.slice(1) as unknown as ConstrainedList.Tail<Ar>;
|
|
47
49
|
|
|
48
50
|
/**
|
|
49
51
|
* Returns all elements of an array except the last one.
|
|
@@ -62,9 +64,11 @@ export const tail = <const Ar extends readonly unknown[]>(
|
|
|
62
64
|
*/
|
|
63
65
|
export const butLast = <const Ar extends readonly unknown[]>(
|
|
64
66
|
array: Ar,
|
|
65
|
-
):
|
|
67
|
+
): ConstrainedList.ButLast<Ar> =>
|
|
66
68
|
// eslint-disable-next-line total-functions/no-unsafe-type-assertion
|
|
67
|
-
(isEmpty(array)
|
|
69
|
+
(isEmpty(array)
|
|
70
|
+
? []
|
|
71
|
+
: array.slice(0, -1)) as unknown as ConstrainedList.ButLast<Ar>;
|
|
68
72
|
|
|
69
73
|
/**
|
|
70
74
|
* Takes the first N elements from an array.
|
|
@@ -89,25 +93,29 @@ export function take<
|
|
|
89
93
|
>(
|
|
90
94
|
array: Ar,
|
|
91
95
|
num: N,
|
|
92
|
-
):
|
|
93
|
-
?
|
|
94
|
-
: N extends
|
|
95
|
-
? Ar
|
|
96
|
-
|
|
97
|
-
|
|
98
|
-
|
|
96
|
+
): HasLengthConstraint<Ar> extends true
|
|
97
|
+
? readonly Ar[number][]
|
|
98
|
+
: N extends StructuralPrefixLength
|
|
99
|
+
? List.Take<N, Ar>
|
|
100
|
+
: N extends SizeType.ArgArrPositive
|
|
101
|
+
? Ar extends NonEmptyTuple<unknown>
|
|
102
|
+
? NonEmptyArray<Ar[number]>
|
|
103
|
+
: readonly Ar[number][]
|
|
104
|
+
: readonly Ar[number][];
|
|
99
105
|
|
|
100
106
|
export function take<N extends SizeType.ArgArr>(
|
|
101
107
|
num: N,
|
|
102
108
|
): <const Ar extends readonly unknown[]>(
|
|
103
109
|
array: Ar,
|
|
104
|
-
) =>
|
|
105
|
-
?
|
|
106
|
-
: N extends
|
|
107
|
-
? Ar
|
|
108
|
-
|
|
109
|
-
|
|
110
|
-
|
|
110
|
+
) => HasLengthConstraint<Ar> extends true
|
|
111
|
+
? readonly Ar[number][]
|
|
112
|
+
: N extends StructuralPrefixLength
|
|
113
|
+
? List.Take<N, Ar>
|
|
114
|
+
: N extends SizeType.ArgArrPositive
|
|
115
|
+
? Ar extends NonEmptyTuple<unknown>
|
|
116
|
+
? NonEmptyArray<Ar[number]>
|
|
117
|
+
: readonly Ar[number][]
|
|
118
|
+
: readonly Ar[number][];
|
|
111
119
|
|
|
112
120
|
export function take<E>(
|
|
113
121
|
...args:
|
|
@@ -151,25 +159,29 @@ export function takeLast<
|
|
|
151
159
|
>(
|
|
152
160
|
array: Ar,
|
|
153
161
|
num: N,
|
|
154
|
-
):
|
|
155
|
-
?
|
|
156
|
-
: N extends
|
|
157
|
-
? Ar
|
|
158
|
-
|
|
159
|
-
|
|
160
|
-
|
|
162
|
+
): HasLengthConstraint<Ar> extends true
|
|
163
|
+
? readonly Ar[number][]
|
|
164
|
+
: N extends StructuralPrefixLength
|
|
165
|
+
? List.TakeLast<N, Ar>
|
|
166
|
+
: N extends SizeType.ArgArrPositive
|
|
167
|
+
? Ar extends NonEmptyTuple<unknown>
|
|
168
|
+
? NonEmptyArray<Ar[number]>
|
|
169
|
+
: readonly Ar[number][]
|
|
170
|
+
: readonly Ar[number][];
|
|
161
171
|
|
|
162
172
|
export function takeLast<N extends SizeType.ArgArr>(
|
|
163
173
|
num: N,
|
|
164
174
|
): <const Ar extends readonly unknown[]>(
|
|
165
175
|
array: Ar,
|
|
166
|
-
) =>
|
|
167
|
-
?
|
|
168
|
-
: N extends
|
|
169
|
-
? Ar
|
|
170
|
-
|
|
171
|
-
|
|
172
|
-
|
|
176
|
+
) => HasLengthConstraint<Ar> extends true
|
|
177
|
+
? readonly Ar[number][]
|
|
178
|
+
: N extends StructuralPrefixLength
|
|
179
|
+
? List.TakeLast<N, Ar>
|
|
180
|
+
: N extends SizeType.ArgArrPositive
|
|
181
|
+
? Ar extends NonEmptyTuple<unknown>
|
|
182
|
+
? NonEmptyArray<Ar[number]>
|
|
183
|
+
: readonly Ar[number][]
|
|
184
|
+
: readonly Ar[number][];
|
|
173
185
|
|
|
174
186
|
export function takeLast<E>(
|
|
175
187
|
...args:
|
|
@@ -214,16 +226,22 @@ export function skip<
|
|
|
214
226
|
>(
|
|
215
227
|
array: Ar,
|
|
216
228
|
num: N,
|
|
217
|
-
):
|
|
229
|
+
): HasLengthConstraint<Ar> extends true
|
|
230
|
+
? readonly Ar[number][]
|
|
231
|
+
: N extends StructuralPrefixLength
|
|
232
|
+
? List.Skip<N, Ar>
|
|
233
|
+
: readonly Ar[number][];
|
|
218
234
|
|
|
219
235
|
// curried version
|
|
220
236
|
export function skip<N extends SizeType.ArgArr>(
|
|
221
237
|
num: N,
|
|
222
238
|
): <const Ar extends readonly unknown[]>(
|
|
223
239
|
array: Ar,
|
|
224
|
-
) =>
|
|
225
|
-
?
|
|
226
|
-
:
|
|
240
|
+
) => HasLengthConstraint<Ar> extends true
|
|
241
|
+
? readonly Ar[number][]
|
|
242
|
+
: N extends StructuralPrefixLength
|
|
243
|
+
? List.Skip<N, Ar>
|
|
244
|
+
: readonly Ar[number][];
|
|
227
245
|
|
|
228
246
|
export function skip<E>(
|
|
229
247
|
...args: readonly [readonly E[], SizeType.ArgArr] | readonly [SizeType.ArgArr]
|
|
@@ -265,18 +283,22 @@ export function skipLast<
|
|
|
265
283
|
>(
|
|
266
284
|
array: Ar,
|
|
267
285
|
num: N,
|
|
268
|
-
):
|
|
269
|
-
?
|
|
270
|
-
:
|
|
286
|
+
): HasLengthConstraint<Ar> extends true
|
|
287
|
+
? readonly Ar[number][]
|
|
288
|
+
: N extends StructuralPrefixLength
|
|
289
|
+
? List.SkipLast<N, Ar>
|
|
290
|
+
: readonly Ar[number][];
|
|
271
291
|
|
|
272
292
|
// curried version
|
|
273
293
|
export function skipLast<N extends SizeType.ArgArr>(
|
|
274
294
|
num: N,
|
|
275
295
|
): <const Ar extends readonly unknown[]>(
|
|
276
296
|
array: Ar,
|
|
277
|
-
) =>
|
|
278
|
-
?
|
|
279
|
-
:
|
|
297
|
+
) => HasLengthConstraint<Ar> extends true
|
|
298
|
+
? readonly Ar[number][]
|
|
299
|
+
: N extends StructuralPrefixLength
|
|
300
|
+
? List.SkipLast<N, Ar>
|
|
301
|
+
: readonly Ar[number][];
|
|
280
302
|
|
|
281
303
|
export function skipLast<E>(
|
|
282
304
|
...args:
|
|
@@ -1,3 +1,4 @@
|
|
|
1
|
+
import { type MinLengthArray } from 'ts-type-forge';
|
|
1
2
|
import { expectType } from '../../expect-type.mjs';
|
|
2
3
|
import {
|
|
3
4
|
butLast,
|
|
@@ -219,3 +220,57 @@ describe('Arr slicing', () => {
|
|
|
219
220
|
});
|
|
220
221
|
});
|
|
221
222
|
});
|
|
223
|
+
|
|
224
|
+
/* Length-constrained inputs. `List.*` reports what it can see from a
|
|
225
|
+
fixed-length tuple, and a branded array's `length` is `number`, so it handed
|
|
226
|
+
the input type straight back — `tail` of an "at least 5" array claimed the
|
|
227
|
+
four-element result was still at least five. Never called; see the note in
|
|
228
|
+
`array-utils-modification.test.mts`. */
|
|
229
|
+
export const slicingTypeChecks = (
|
|
230
|
+
brandedFive: MinLengthArray<5, string>,
|
|
231
|
+
brandedAndTuple: MinLengthArray<3, number> & readonly [1, 2, 3, 4, 5],
|
|
232
|
+
): void => {
|
|
233
|
+
// The bound is recomputed rather than passed through.
|
|
234
|
+
const _tailed = tail(brandedFive);
|
|
235
|
+
|
|
236
|
+
expectType<typeof _tailed, MinLengthArray<4, string>>('~=');
|
|
237
|
+
|
|
238
|
+
const _butLasted = butLast(brandedFive);
|
|
239
|
+
|
|
240
|
+
expectType<typeof _butLasted, MinLengthArray<4, string>>('~=');
|
|
241
|
+
|
|
242
|
+
/* A brand intersected with an exact tuple keeps the positions too, so the
|
|
243
|
+
result is exact on both counts. */
|
|
244
|
+
const _tailedBoth = tail(brandedAndTuple);
|
|
245
|
+
|
|
246
|
+
expectType<(typeof _tailedBoth)[0], 2>('=');
|
|
247
|
+
|
|
248
|
+
expectType<(typeof _tailedBoth)['length'], 4>('=');
|
|
249
|
+
|
|
250
|
+
/* `take` / `takeLast` / `skip` / `skipLast` stay on the `List.*` path and give
|
|
251
|
+
a branded input the sound unconstrained answer instead of its stale brand.
|
|
252
|
+
They cannot use `ConstrainedList.Take` and friends: those rebuild the bounds
|
|
253
|
+
from `N` and cannot be instantiated against a still-generic `Ar` at all —
|
|
254
|
+
even with a literal `N` the deferred result is a union too complex to
|
|
255
|
+
represent. */
|
|
256
|
+
const _taken = take(brandedFive, 2);
|
|
257
|
+
|
|
258
|
+
expectType<typeof _taken, readonly string[]>('=');
|
|
259
|
+
|
|
260
|
+
const _skipped = skip(brandedFive, 2);
|
|
261
|
+
|
|
262
|
+
expectType<typeof _skipped, readonly string[]>('=');
|
|
263
|
+
|
|
264
|
+
// Plain tuples keep mapping exactly as before.
|
|
265
|
+
const _tailedTuple = tail([1, 2, 3] as const);
|
|
266
|
+
|
|
267
|
+
expectType<typeof _tailedTuple, readonly [2, 3]>('=');
|
|
268
|
+
|
|
269
|
+
const _butLastedTuple = butLast([1, 2, 3] as const);
|
|
270
|
+
|
|
271
|
+
expectType<typeof _butLastedTuple, readonly [1, 2]>('=');
|
|
272
|
+
|
|
273
|
+
const _takenTuple = take([1, 2, 3] as const, 2);
|
|
274
|
+
|
|
275
|
+
expectType<typeof _takenTuple, readonly [1, 2]>('=');
|
|
276
|
+
};
|
|
@@ -1,13 +1,17 @@
|
|
|
1
1
|
import {
|
|
2
|
+
type BoundedLengthArray,
|
|
3
|
+
type ChangeArrayElement,
|
|
4
|
+
type ConstrainedList,
|
|
2
5
|
type FixedLengthTuple,
|
|
6
|
+
type HasLengthConstraint,
|
|
3
7
|
type IsFixedLengthList,
|
|
4
|
-
type List,
|
|
5
8
|
type NonEmptyArray,
|
|
6
9
|
type NonEmptyTuple,
|
|
7
10
|
type PositiveInt,
|
|
8
11
|
type PositiveUint32,
|
|
9
12
|
type Primitive,
|
|
10
13
|
type SafeUintWithSmallInt,
|
|
14
|
+
type SupportedLength,
|
|
11
15
|
type WithSmallInt,
|
|
12
16
|
} from 'ts-type-forge';
|
|
13
17
|
import { IMap } from '../../collections/index.mjs';
|
|
@@ -20,8 +24,8 @@ import {
|
|
|
20
24
|
type SizeType,
|
|
21
25
|
} from '../../types.mjs';
|
|
22
26
|
import { newArray, seq } from './array-utils-creation.mjs';
|
|
27
|
+
import { isNonEmpty } from './array-utils-length-bounded-array-guard.mjs';
|
|
23
28
|
import { size } from './array-utils-size.mjs';
|
|
24
|
-
import { isNonEmpty } from './array-utils-validation.mjs';
|
|
25
29
|
|
|
26
30
|
/**
|
|
27
31
|
* Creates a new array by transforming each element with a mapping function.
|
|
@@ -45,14 +49,12 @@ import { isNonEmpty } from './array-utils-validation.mjs';
|
|
|
45
49
|
export function map<const Ar extends readonly unknown[], B>(
|
|
46
50
|
array: Ar,
|
|
47
51
|
mapFn: (a: Ar[number], index: ArrayIndex<Ar>) => B,
|
|
48
|
-
):
|
|
52
|
+
): ChangeArrayElement<Ar, B>;
|
|
49
53
|
|
|
50
54
|
// curried version
|
|
51
55
|
export function map<A, B>(
|
|
52
56
|
mapFn: (a: A, index: SizeType.Arr) => B,
|
|
53
|
-
): <const Ar extends readonly A[]>(
|
|
54
|
-
array: Ar,
|
|
55
|
-
) => Readonly<{ [K in keyof Ar]: B }>;
|
|
57
|
+
): <const Ar extends readonly A[]>(array: Ar) => ChangeArrayElement<Ar, B>;
|
|
56
58
|
|
|
57
59
|
export function map<A, B>(
|
|
58
60
|
...args:
|
|
@@ -187,7 +189,7 @@ const scanImpl = <E, S>(
|
|
|
187
189
|
*/
|
|
188
190
|
export const toReversed = <const Ar extends readonly unknown[]>(
|
|
189
191
|
array: Ar,
|
|
190
|
-
):
|
|
192
|
+
): ConstrainedList.Reverse<Ar> =>
|
|
191
193
|
// eslint-disable-next-line total-functions/no-unsafe-type-assertion
|
|
192
194
|
array.toReversed() as never;
|
|
193
195
|
|
|
@@ -224,11 +226,13 @@ export const toSorted = <const Ar extends readonly unknown[]>(
|
|
|
224
226
|
comparator?: (x: Ar[number], y: Ar[number]) => number,
|
|
225
227
|
]
|
|
226
228
|
: readonly [array: Ar, comparator: (x: Ar[number], y: Ar[number]) => number]
|
|
227
|
-
):
|
|
228
|
-
?
|
|
229
|
-
: Ar extends
|
|
230
|
-
?
|
|
231
|
-
:
|
|
229
|
+
): HasLengthConstraint<Ar> extends true
|
|
230
|
+
? ChangeArrayElement<Ar, Ar[number]>
|
|
231
|
+
: IsFixedLengthList<Ar> extends true
|
|
232
|
+
? FixedLengthTuple<Ar['length'], Ar[number]>
|
|
233
|
+
: Ar extends NonEmptyTuple<unknown>
|
|
234
|
+
? NonEmptyArray<Ar[number]>
|
|
235
|
+
: readonly Ar[number][] =>
|
|
232
236
|
// eslint-disable-next-line total-functions/no-unsafe-type-assertion
|
|
233
237
|
array.toSorted(
|
|
234
238
|
// eslint-disable-next-line total-functions/no-unsafe-type-assertion
|
|
@@ -282,21 +286,25 @@ export function toSortedBy<const Ar extends readonly unknown[]>(
|
|
|
282
286
|
comparatorValueMapper: (value: Ar[number]) => number,
|
|
283
287
|
// If the array elements are mapped to numbers, comparator is optional.
|
|
284
288
|
comparator?: (x: number, y: number) => number,
|
|
285
|
-
):
|
|
286
|
-
?
|
|
287
|
-
: Ar extends
|
|
288
|
-
?
|
|
289
|
-
:
|
|
289
|
+
): HasLengthConstraint<Ar> extends true
|
|
290
|
+
? ChangeArrayElement<Ar, Ar[number]>
|
|
291
|
+
: IsFixedLengthList<Ar> extends true
|
|
292
|
+
? FixedLengthTuple<Ar['length'], Ar[number]>
|
|
293
|
+
: Ar extends NonEmptyTuple<unknown>
|
|
294
|
+
? NonEmptyArray<Ar[number]>
|
|
295
|
+
: readonly Ar[number][];
|
|
290
296
|
|
|
291
297
|
export function toSortedBy<const Ar extends readonly unknown[], const V>(
|
|
292
298
|
array: Ar,
|
|
293
299
|
comparatorValueMapper: (value: Ar[number]) => V,
|
|
294
300
|
comparator: (x: V, y: V) => number,
|
|
295
|
-
):
|
|
296
|
-
?
|
|
297
|
-
: Ar extends
|
|
298
|
-
?
|
|
299
|
-
:
|
|
301
|
+
): HasLengthConstraint<Ar> extends true
|
|
302
|
+
? ChangeArrayElement<Ar, Ar[number]>
|
|
303
|
+
: IsFixedLengthList<Ar> extends true
|
|
304
|
+
? FixedLengthTuple<Ar['length'], Ar[number]>
|
|
305
|
+
: Ar extends NonEmptyTuple<unknown>
|
|
306
|
+
? NonEmptyArray<Ar[number]>
|
|
307
|
+
: readonly Ar[number][];
|
|
300
308
|
|
|
301
309
|
export function toSortedBy<E, const V>(
|
|
302
310
|
array: readonly E[],
|
|
@@ -627,18 +635,24 @@ export function flatMap<A, B>(
|
|
|
627
635
|
*
|
|
628
636
|
* const triples = Arr.partition(3)(values);
|
|
629
637
|
*
|
|
638
|
+
* // Every chunk is branded `BoundedLengthArray<1, N, …>` — `partition` never
|
|
639
|
+
* // emits an empty chunk — so the expected values are annotated with the
|
|
640
|
+
* // unbranded type.
|
|
630
641
|
* const expectedPairs = [[1, 2], [3, 4], [5]] as const;
|
|
631
642
|
*
|
|
632
|
-
* assert.deepStrictEqual(
|
|
643
|
+
* assert.deepStrictEqual<readonly (readonly number[])[]>(
|
|
644
|
+
* pairs,
|
|
645
|
+
* expectedPairs,
|
|
646
|
+
* );
|
|
633
647
|
*
|
|
634
|
-
* assert.deepStrictEqual(triples, [
|
|
648
|
+
* assert.deepStrictEqual<readonly (readonly number[])[]>(triples, [
|
|
635
649
|
* [1, 2, 3],
|
|
636
650
|
* [4, 5],
|
|
637
651
|
* ]);
|
|
638
652
|
*
|
|
639
653
|
* const pairs2 = Arr.chunk([1, 2, 3, 4, 5, 6], 2);
|
|
640
654
|
*
|
|
641
|
-
* assert.deepStrictEqual(pairs2, [
|
|
655
|
+
* assert.deepStrictEqual<readonly (readonly number[])[]>(pairs2, [
|
|
642
656
|
* [1, 2],
|
|
643
657
|
* [3, 4],
|
|
644
658
|
* [5, 6],
|
|
@@ -646,17 +660,17 @@ export function flatMap<A, B>(
|
|
|
646
660
|
* ```
|
|
647
661
|
*/
|
|
648
662
|
export function partition<
|
|
649
|
-
N extends WithSmallInt<PositiveInt & SizeType.Arr>,
|
|
650
663
|
E,
|
|
651
|
-
|
|
664
|
+
N extends WithSmallInt<PositiveInt & SizeType.Arr>,
|
|
665
|
+
>(array: readonly E[], chunkSize: N): readonly PartitionChunk<N, E>[];
|
|
652
666
|
|
|
653
667
|
export function partition<N extends WithSmallInt<PositiveInt & SizeType.Arr>>(
|
|
654
668
|
chunkSize: N,
|
|
655
|
-
): <E>(array: readonly E[]) => readonly
|
|
669
|
+
): <E>(array: readonly E[]) => readonly PartitionChunk<N, E>[];
|
|
656
670
|
|
|
657
671
|
export function partition<
|
|
658
|
-
N extends WithSmallInt<PositiveInt & SizeType.Arr>,
|
|
659
672
|
E,
|
|
673
|
+
N extends WithSmallInt<PositiveInt & SizeType.Arr>,
|
|
660
674
|
>(
|
|
661
675
|
...args:
|
|
662
676
|
readonly [array: readonly E[], chunkSize: N] | readonly [chunkSize: N]
|
|
@@ -672,7 +686,20 @@ export function partition<
|
|
|
672
686
|
}
|
|
673
687
|
}
|
|
674
688
|
|
|
675
|
-
|
|
689
|
+
/**
|
|
690
|
+
* Element type of {@link partition}'s result: a chunk holds between 1 and `N`
|
|
691
|
+
* elements. The lower bound is what makes this worth spelling out — `partition`
|
|
692
|
+
* never emits an empty chunk, so indexed access into a chunk is not
|
|
693
|
+
* `undefined` at position `0`, unlike with a plain `readonly E[]`.
|
|
694
|
+
*
|
|
695
|
+
* Falls back to {@link NonEmptyArray} when `N` is not a bound the brand can
|
|
696
|
+
* express, which keeps that lower bound in every case.
|
|
697
|
+
*/
|
|
698
|
+
type PartitionChunk<N, E> = N extends SupportedLength
|
|
699
|
+
? BoundedLengthArray<1, N, E>
|
|
700
|
+
: NonEmptyArray<E>;
|
|
701
|
+
|
|
702
|
+
const partitionImpl = <E, N extends WithSmallInt<PositiveInt & SizeType.Arr>>(
|
|
676
703
|
array: readonly E[],
|
|
677
704
|
chunkSize: N,
|
|
678
705
|
): readonly (readonly E[])[] =>
|
|
@@ -683,6 +710,13 @@ const partitionImpl = <N extends WithSmallInt<PositiveInt & SizeType.Arr>, E>(
|
|
|
683
710
|
array.slice(chunkSize * i, chunkSize * (i + 1)),
|
|
684
711
|
);
|
|
685
712
|
|
|
713
|
+
/**
|
|
714
|
+
* Alias for `partition`.
|
|
715
|
+
*
|
|
716
|
+
* @see {@link partition}
|
|
717
|
+
*/
|
|
718
|
+
export const chunk = partition;
|
|
719
|
+
|
|
686
720
|
/**
|
|
687
721
|
* Concatenates two arrays.
|
|
688
722
|
*
|
|
@@ -853,20 +887,13 @@ export const zip = <
|
|
|
853
887
|
>(
|
|
854
888
|
array1: Ar1,
|
|
855
889
|
array2: Ar2,
|
|
856
|
-
):
|
|
890
|
+
): ConstrainedList.Zip<Ar1, Ar2> =>
|
|
857
891
|
// eslint-disable-next-line total-functions/no-unsafe-type-assertion
|
|
858
892
|
seq(Uint32.min(size(array1), size(array2))).map((i: Uint32) =>
|
|
859
893
|
// Non-null assertion is safe here because `i` is always within bounds of both arrays up to the length of the shorter one.
|
|
860
894
|
// eslint-disable-next-line @typescript-eslint/no-non-null-assertion
|
|
861
895
|
tp(array1[i]!, array2[i]!),
|
|
862
|
-
) as unknown as
|
|
863
|
-
|
|
864
|
-
/**
|
|
865
|
-
* Alias for `partition`.
|
|
866
|
-
*
|
|
867
|
-
* @see {@link partition}
|
|
868
|
-
*/
|
|
869
|
-
export const chunk = partition;
|
|
896
|
+
) as unknown as ConstrainedList.Zip<Ar1, Ar2>;
|
|
870
897
|
|
|
871
898
|
/**
|
|
872
899
|
* Computes the cartesian product of arrays.
|
|
@@ -1,4 +1,9 @@
|
|
|
1
|
-
import {
|
|
1
|
+
import {
|
|
2
|
+
type BoundedLengthArray,
|
|
3
|
+
type FixedLengthTuple,
|
|
4
|
+
type MinLengthArray,
|
|
5
|
+
type NonEmptyArray,
|
|
6
|
+
} from 'ts-type-forge';
|
|
2
7
|
import { IMap } from '../../collections/index.mjs';
|
|
3
8
|
import { expectType } from '../../expect-type.mjs';
|
|
4
9
|
import { Optional } from '../../functional/index.mjs';
|
|
@@ -644,13 +649,15 @@ describe('Arr transformations', () => {
|
|
|
644
649
|
|
|
645
650
|
expectType<
|
|
646
651
|
typeof result,
|
|
647
|
-
readonly
|
|
652
|
+
readonly BoundedLengthArray<
|
|
653
|
+
1,
|
|
654
|
+
4,
|
|
648
655
|
1 | 2 | 3 | 4 | 5 | 6 | 7 | 8 | 9 | 10 | 11 | 12
|
|
649
|
-
|
|
650
|
-
>('
|
|
656
|
+
>[]
|
|
657
|
+
>('~=');
|
|
651
658
|
|
|
652
659
|
test('case 1', () => {
|
|
653
|
-
assert.deepStrictEqual(result, [
|
|
660
|
+
assert.deepStrictEqual<readonly (readonly number[])[]>(result, [
|
|
654
661
|
[1, 2, 3, 4],
|
|
655
662
|
[5, 6, 7, 8],
|
|
656
663
|
[9, 10, 11, 12],
|
|
@@ -663,13 +670,15 @@ describe('Arr transformations', () => {
|
|
|
663
670
|
|
|
664
671
|
expectType<
|
|
665
672
|
typeof result,
|
|
666
|
-
readonly
|
|
673
|
+
readonly BoundedLengthArray<
|
|
674
|
+
1,
|
|
675
|
+
3,
|
|
667
676
|
1 | 2 | 3 | 4 | 5 | 6 | 7 | 8 | 9 | 10 | 11 | 12
|
|
668
|
-
|
|
669
|
-
>('
|
|
677
|
+
>[]
|
|
678
|
+
>('~=');
|
|
670
679
|
|
|
671
680
|
test('case 2', () => {
|
|
672
|
-
assert.deepStrictEqual(result, [
|
|
681
|
+
assert.deepStrictEqual<readonly (readonly number[])[]>(result, [
|
|
673
682
|
[1, 2, 3],
|
|
674
683
|
[4, 5, 6],
|
|
675
684
|
[7, 8, 9],
|
|
@@ -683,13 +692,15 @@ describe('Arr transformations', () => {
|
|
|
683
692
|
|
|
684
693
|
expectType<
|
|
685
694
|
typeof result,
|
|
686
|
-
readonly
|
|
695
|
+
readonly BoundedLengthArray<
|
|
696
|
+
1,
|
|
697
|
+
5,
|
|
687
698
|
1 | 2 | 3 | 4 | 5 | 6 | 7 | 8 | 9 | 10 | 11 | 12
|
|
688
|
-
|
|
689
|
-
>('
|
|
699
|
+
>[]
|
|
700
|
+
>('~=');
|
|
690
701
|
|
|
691
702
|
test('case 3', () => {
|
|
692
|
-
assert.deepStrictEqual(result, [
|
|
703
|
+
assert.deepStrictEqual<readonly (readonly number[])[]>(result, [
|
|
693
704
|
[1, 2, 3, 4, 5],
|
|
694
705
|
[6, 7, 8, 9, 10],
|
|
695
706
|
[11, 12],
|
|
@@ -702,7 +713,7 @@ describe('Arr transformations', () => {
|
|
|
702
713
|
|
|
703
714
|
const result = partition(numbers, 2);
|
|
704
715
|
|
|
705
|
-
assert.deepStrictEqual(result, [
|
|
716
|
+
assert.deepStrictEqual<readonly (readonly number[])[]>(result, [
|
|
706
717
|
[1, 2],
|
|
707
718
|
[3, 4],
|
|
708
719
|
[5, 6],
|
|
@@ -714,7 +725,11 @@ describe('Arr transformations', () => {
|
|
|
714
725
|
|
|
715
726
|
const result = partition(numbers, 2);
|
|
716
727
|
|
|
717
|
-
assert.deepStrictEqual(
|
|
728
|
+
assert.deepStrictEqual<readonly (readonly number[])[]>(result, [
|
|
729
|
+
[1, 2],
|
|
730
|
+
[3, 4],
|
|
731
|
+
[5],
|
|
732
|
+
]);
|
|
718
733
|
});
|
|
719
734
|
|
|
720
735
|
test('should work with chunk size < 2 (returns empty)', () => {
|
|
@@ -731,7 +746,7 @@ describe('Arr transformations', () => {
|
|
|
731
746
|
|
|
732
747
|
const result = partition(numbers, 5);
|
|
733
748
|
|
|
734
|
-
assert.deepStrictEqual(result, [[1, 2]]);
|
|
749
|
+
assert.deepStrictEqual<readonly (readonly number[])[]>(result, [[1, 2]]);
|
|
735
750
|
});
|
|
736
751
|
|
|
737
752
|
test('partition should work with empty array', () => {
|
|
@@ -1162,3 +1177,34 @@ describe('Arr transformations', () => {
|
|
|
1162
1177
|
});
|
|
1163
1178
|
});
|
|
1164
1179
|
});
|
|
1180
|
+
|
|
1181
|
+
/* `toReversed` is a known permutation, so the tuple half can be reversed
|
|
1182
|
+
alongside the brand. It used to fall back to `ChangeArrayElement`, which kept
|
|
1183
|
+
the length but collapsed the positions to their union. Never called; see the
|
|
1184
|
+
note in `array-utils-modification.test.mts`. */
|
|
1185
|
+
export const reversedTypeChecks = (
|
|
1186
|
+
brandedFive: MinLengthArray<5, string>,
|
|
1187
|
+
brandedAndTuple: MinLengthArray<3, number> & readonly [1, 2, 3, 4, 5],
|
|
1188
|
+
): void => {
|
|
1189
|
+
const _reversedBranded = toReversed(brandedFive);
|
|
1190
|
+
|
|
1191
|
+
expectType<typeof _reversedBranded, MinLengthArray<5, string>>('~=');
|
|
1192
|
+
|
|
1193
|
+
// Brand and positions both survive, and the positions really are reversed.
|
|
1194
|
+
const _reversedBoth = toReversed(brandedAndTuple);
|
|
1195
|
+
|
|
1196
|
+
expectType<(typeof _reversedBoth)[0], 5>('=');
|
|
1197
|
+
|
|
1198
|
+
expectType<(typeof _reversedBoth)[4], 1>('=');
|
|
1199
|
+
|
|
1200
|
+
const _reversedTuple = toReversed([1, 2, 3] as const);
|
|
1201
|
+
|
|
1202
|
+
expectType<typeof _reversedTuple, readonly [3, 2, 1]>('=');
|
|
1203
|
+
|
|
1204
|
+
/* `toSorted` deliberately does NOT take this path: an arbitrary permutation
|
|
1205
|
+
cannot keep positions, so the honest answer stays "same length, element
|
|
1206
|
+
type widened to the union". */
|
|
1207
|
+
const _sortedTuple = toSorted([1, 2, 3] as const);
|
|
1208
|
+
|
|
1209
|
+
expectType<typeof _sortedTuple, FixedLengthTuple<3, 1 | 2 | 3>>('~=');
|
|
1210
|
+
};
|