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.
- package/README.md +4 -3
- 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 +45 -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-slice-clamped.d.mts.map +1 -1
- package/dist/array/impl/array-utils-slice-clamped.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 +36 -19
- 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/collections/imap.d.mts +1 -1
- package/dist/collections/imap.mjs.map +1 -1
- package/dist/collections/iset-mapped.d.mts +2 -2
- 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 +2 -2
- package/dist/collections/iset.d.mts.map +1 -1
- package/dist/collections/iset.mjs.map +1 -1
- package/dist/number/num-arithmetic-types.d.mts +2 -2
- package/dist/number/num-arithmetic-types.d.mts.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 +4 -5
- package/src/array/impl/array-utils-creation.mts +7 -3
- package/src/array/impl/array-utils-creation.test.mts +10 -8
- 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 +84 -61
- package/src/array/impl/array-utils-modification.test.mts +145 -6
- package/src/array/impl/array-utils-reducing-value.mts +5 -5
- package/src/array/impl/array-utils-shape-invariants.test.mts +284 -0
- package/src/array/impl/array-utils-slice-clamped.mts +2 -1
- 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 +91 -37
- package/src/array/impl/array-utils-transformation.test.mts +96 -23
- 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/imap.mts +1 -1
- package/src/collections/iset-mapped.mts +7 -3
- package/src/collections/iset.mts +7 -3
- package/src/collections/iset.test.mts +9 -6
- package/src/collections/queue.test.mts +1 -1
- package/src/number/num-arithmetic-types.mts +8 -4
- package/src/number/num.test.mts +2 -2
- package/src/string/str.mts +69 -69
- package/src/string/str.test.mts +40 -40
|
@@ -0,0 +1,364 @@
|
|
|
1
|
+
import {
|
|
2
|
+
type BoundedLengthTuple,
|
|
3
|
+
type FixedLengthTuple,
|
|
4
|
+
type MaxLengthTuple,
|
|
5
|
+
type MinLengthTuple,
|
|
6
|
+
} from 'ts-type-forge';
|
|
7
|
+
import { type SizeType } from '../../types.mjs';
|
|
8
|
+
import {
|
|
9
|
+
isBoundedLengthTuple,
|
|
10
|
+
isEmptyTuple,
|
|
11
|
+
isFixedLengthTuple,
|
|
12
|
+
isMaxLengthTuple,
|
|
13
|
+
isMinLengthTuple,
|
|
14
|
+
} from './array-utils-length-bounded-tuple-guard.mjs';
|
|
15
|
+
|
|
16
|
+
/**
|
|
17
|
+
* Casts an array to `FixedLengthTuple<Length, E>` after checking that it has
|
|
18
|
+
* exactly `length` elements.
|
|
19
|
+
*
|
|
20
|
+
* This is the structural counterpart of `asFixedLengthArray`: it produces a
|
|
21
|
+
* tuple type rather than the branded `FixedLengthArray`. Prefer the branded
|
|
22
|
+
* one unless you specifically need the structural type — the brand keeps
|
|
23
|
+
* type-checking cost independent of the element type and of the bound.
|
|
24
|
+
*
|
|
25
|
+
* The length argument comes first, matching the type-parameter order of
|
|
26
|
+
* `FixedLengthTuple<Length, Elm>`. Omitting the array returns a function that
|
|
27
|
+
* casts one.
|
|
28
|
+
*
|
|
29
|
+
* @example
|
|
30
|
+
*
|
|
31
|
+
* ```ts
|
|
32
|
+
* const rgb = Arr.asFixedLengthTuple(3, [255, 128, 0]);
|
|
33
|
+
*
|
|
34
|
+
* const red: number = rgb[0]; // OK — no `undefined`
|
|
35
|
+
*
|
|
36
|
+
* // curried version
|
|
37
|
+
* const asRgb = Arr.asFixedLengthTuple(3);
|
|
38
|
+
*
|
|
39
|
+
* // Arr.asFixedLengthTuple(3, [255, 128]); // throws TypeError
|
|
40
|
+
* ```
|
|
41
|
+
*
|
|
42
|
+
* @template Length - The exact number of elements.
|
|
43
|
+
* @template Xs - The input array type (tuple types are preserved).
|
|
44
|
+
* @param length - The exact number of elements.
|
|
45
|
+
* @param xs - The array to cast.
|
|
46
|
+
* @returns `xs` typed as `FixedLengthTuple<Length, Xs[number]> & Xs`.
|
|
47
|
+
* @throws {TypeError} If `xs.length !== length`.
|
|
48
|
+
*/
|
|
49
|
+
export function asFixedLengthTuple<
|
|
50
|
+
Length extends SizeType.ArgArr,
|
|
51
|
+
Xs extends readonly unknown[],
|
|
52
|
+
>(length: Length, xs: Xs): FixedLengthTuple<Length, Xs[number]> & Xs;
|
|
53
|
+
|
|
54
|
+
// Curried version
|
|
55
|
+
export function asFixedLengthTuple<Length extends SizeType.ArgArr>(
|
|
56
|
+
length: Length,
|
|
57
|
+
): <Xs extends readonly unknown[]>(
|
|
58
|
+
xs: Xs,
|
|
59
|
+
) => FixedLengthTuple<Length, Xs[number]> & Xs;
|
|
60
|
+
|
|
61
|
+
export function asFixedLengthTuple(
|
|
62
|
+
...args:
|
|
63
|
+
| readonly [length: SizeType.ArgArr, xs: readonly unknown[]]
|
|
64
|
+
| readonly [length: SizeType.ArgArr]
|
|
65
|
+
): readonly unknown[] | ((xs: readonly unknown[]) => readonly unknown[]) {
|
|
66
|
+
switch (args.length) {
|
|
67
|
+
case 2:
|
|
68
|
+
return asFixedLengthTupleImpl(...args);
|
|
69
|
+
|
|
70
|
+
case 1:
|
|
71
|
+
return (xs) => asFixedLengthTupleImpl(args[0], xs);
|
|
72
|
+
}
|
|
73
|
+
}
|
|
74
|
+
|
|
75
|
+
const asFixedLengthTupleImpl = (
|
|
76
|
+
length: SizeType.ArgArr,
|
|
77
|
+
xs: readonly unknown[],
|
|
78
|
+
): readonly unknown[] => {
|
|
79
|
+
const actualLength = xs.length;
|
|
80
|
+
|
|
81
|
+
if (!isFixedLengthTuple(length, xs)) {
|
|
82
|
+
throw new TypeError(
|
|
83
|
+
`Expected an array of length ${length}, got an array of length ${actualLength}`,
|
|
84
|
+
);
|
|
85
|
+
}
|
|
86
|
+
|
|
87
|
+
return xs;
|
|
88
|
+
};
|
|
89
|
+
|
|
90
|
+
/**
|
|
91
|
+
* Casts an array to `MinLengthTuple<MinLength, E>` after checking that it has
|
|
92
|
+
* at least `minLength` elements.
|
|
93
|
+
*
|
|
94
|
+
* This is the structural counterpart of `asMinLengthArray`; see
|
|
95
|
+
* {@link asFixedLengthTuple} for when to prefer which.
|
|
96
|
+
*
|
|
97
|
+
* @example
|
|
98
|
+
*
|
|
99
|
+
* ```ts
|
|
100
|
+
* const history = Arr.asMinLengthTuple(3, [0, 1, 2, 3]);
|
|
101
|
+
*
|
|
102
|
+
* const first: number = history[0]; // OK — no `undefined`
|
|
103
|
+
*
|
|
104
|
+
* // curried version
|
|
105
|
+
* const asHistory = Arr.asMinLengthTuple(3);
|
|
106
|
+
*
|
|
107
|
+
* // Arr.asMinLengthTuple(3, [0, 1]); // throws TypeError
|
|
108
|
+
* ```
|
|
109
|
+
*
|
|
110
|
+
* @template MinLength - The minimum number of elements (inclusive).
|
|
111
|
+
* @template Xs - The input array type (tuple types are preserved).
|
|
112
|
+
* @param minLength - The minimum number of elements (inclusive).
|
|
113
|
+
* @param xs - The array to cast.
|
|
114
|
+
* @returns `xs` typed as `MinLengthTuple<MinLength, Xs[number]> & Xs`.
|
|
115
|
+
* @throws {TypeError} If `xs.length < minLength`.
|
|
116
|
+
*/
|
|
117
|
+
export function asMinLengthTuple<
|
|
118
|
+
MinLength extends SizeType.ArgArr,
|
|
119
|
+
Xs extends readonly unknown[],
|
|
120
|
+
>(minLength: MinLength, xs: Xs): MinLengthTuple<MinLength, Xs[number]> & Xs;
|
|
121
|
+
|
|
122
|
+
// Curried version
|
|
123
|
+
export function asMinLengthTuple<MinLength extends SizeType.ArgArr>(
|
|
124
|
+
minLength: MinLength,
|
|
125
|
+
): <Xs extends readonly unknown[]>(
|
|
126
|
+
xs: Xs,
|
|
127
|
+
) => MinLengthTuple<MinLength, Xs[number]> & Xs;
|
|
128
|
+
|
|
129
|
+
export function asMinLengthTuple(
|
|
130
|
+
...args:
|
|
131
|
+
| readonly [minLength: SizeType.ArgArr, xs: readonly unknown[]]
|
|
132
|
+
| readonly [minLength: SizeType.ArgArr]
|
|
133
|
+
): readonly unknown[] | ((xs: readonly unknown[]) => readonly unknown[]) {
|
|
134
|
+
switch (args.length) {
|
|
135
|
+
case 2:
|
|
136
|
+
return asMinLengthTupleImpl(...args);
|
|
137
|
+
|
|
138
|
+
case 1:
|
|
139
|
+
return (xs) => asMinLengthTupleImpl(args[0], xs);
|
|
140
|
+
}
|
|
141
|
+
}
|
|
142
|
+
|
|
143
|
+
const asMinLengthTupleImpl = (
|
|
144
|
+
minLength: SizeType.ArgArr,
|
|
145
|
+
xs: readonly unknown[],
|
|
146
|
+
): readonly unknown[] => {
|
|
147
|
+
const actualLength = xs.length;
|
|
148
|
+
|
|
149
|
+
if (!isMinLengthTuple(minLength, xs)) {
|
|
150
|
+
throw new TypeError(
|
|
151
|
+
`Expected an array of length >= ${minLength}, got an array of length ${actualLength}`,
|
|
152
|
+
);
|
|
153
|
+
}
|
|
154
|
+
|
|
155
|
+
return xs;
|
|
156
|
+
};
|
|
157
|
+
|
|
158
|
+
/**
|
|
159
|
+
* Casts an array to `MaxLengthTuple<MaxLength, E>` after checking that it has
|
|
160
|
+
* at most `maxLength` elements.
|
|
161
|
+
*
|
|
162
|
+
* This is the structural counterpart of `asMaxLengthArray`; see
|
|
163
|
+
* {@link asFixedLengthTuple} for when to prefer which.
|
|
164
|
+
*
|
|
165
|
+
* @example
|
|
166
|
+
*
|
|
167
|
+
* ```ts
|
|
168
|
+
* const tags = Arr.asMaxLengthTuple(8, ['a', 'b', 'c']);
|
|
169
|
+
*
|
|
170
|
+
* // curried version
|
|
171
|
+
* const asTags = Arr.asMaxLengthTuple(8);
|
|
172
|
+
*
|
|
173
|
+
* // Arr.asMaxLengthTuple(2, ['a', 'b', 'c']); // throws TypeError
|
|
174
|
+
* ```
|
|
175
|
+
*
|
|
176
|
+
* @template MaxLength - The maximum number of elements (inclusive).
|
|
177
|
+
* @template Xs - The input array type (tuple types are preserved).
|
|
178
|
+
* @param maxLength - The maximum number of elements (inclusive).
|
|
179
|
+
* @param xs - The array to cast.
|
|
180
|
+
* @returns `xs` typed as `MaxLengthTuple<MaxLength, Xs[number]> & Xs`.
|
|
181
|
+
* @throws {TypeError} If `xs.length > maxLength`.
|
|
182
|
+
*/
|
|
183
|
+
export function asMaxLengthTuple<
|
|
184
|
+
MaxLength extends SizeType.ArgArr,
|
|
185
|
+
Xs extends readonly unknown[],
|
|
186
|
+
>(maxLength: MaxLength, xs: Xs): MaxLengthTuple<MaxLength, Xs[number]> & Xs;
|
|
187
|
+
|
|
188
|
+
// Curried version
|
|
189
|
+
export function asMaxLengthTuple<MaxLength extends SizeType.ArgArr>(
|
|
190
|
+
maxLength: MaxLength,
|
|
191
|
+
): <Xs extends readonly unknown[]>(
|
|
192
|
+
xs: Xs,
|
|
193
|
+
) => MaxLengthTuple<MaxLength, Xs[number]> & Xs;
|
|
194
|
+
|
|
195
|
+
export function asMaxLengthTuple(
|
|
196
|
+
...args:
|
|
197
|
+
| readonly [maxLength: SizeType.ArgArr, xs: readonly unknown[]]
|
|
198
|
+
| readonly [maxLength: SizeType.ArgArr]
|
|
199
|
+
): readonly unknown[] | ((xs: readonly unknown[]) => readonly unknown[]) {
|
|
200
|
+
switch (args.length) {
|
|
201
|
+
case 2:
|
|
202
|
+
return asMaxLengthTupleImpl(...args);
|
|
203
|
+
|
|
204
|
+
case 1:
|
|
205
|
+
return (xs) => asMaxLengthTupleImpl(args[0], xs);
|
|
206
|
+
}
|
|
207
|
+
}
|
|
208
|
+
|
|
209
|
+
const asMaxLengthTupleImpl = (
|
|
210
|
+
maxLength: SizeType.ArgArr,
|
|
211
|
+
xs: readonly unknown[],
|
|
212
|
+
): readonly unknown[] => {
|
|
213
|
+
const actualLength = xs.length;
|
|
214
|
+
|
|
215
|
+
if (!isMaxLengthTuple(maxLength, xs)) {
|
|
216
|
+
throw new TypeError(
|
|
217
|
+
`Expected an array of length <= ${maxLength}, got an array of length ${actualLength}`,
|
|
218
|
+
);
|
|
219
|
+
}
|
|
220
|
+
|
|
221
|
+
return xs;
|
|
222
|
+
};
|
|
223
|
+
|
|
224
|
+
/**
|
|
225
|
+
* Casts an array to `BoundedLengthTuple<Min, Max, E>` after checking that its
|
|
226
|
+
* length is within the inclusive range `[min, max]`.
|
|
227
|
+
*
|
|
228
|
+
* This is the structural counterpart of `asBoundedLengthArray`; see
|
|
229
|
+
* {@link asFixedLengthTuple} for when to prefer which.
|
|
230
|
+
*
|
|
231
|
+
* @example
|
|
232
|
+
*
|
|
233
|
+
* ```ts
|
|
234
|
+
* const selection = Arr.asBoundedLengthTuple(1, 5, [1, 2, 3]);
|
|
235
|
+
*
|
|
236
|
+
* // curried version
|
|
237
|
+
* const asSelection = Arr.asBoundedLengthTuple(1, 5);
|
|
238
|
+
*
|
|
239
|
+
* // Arr.asBoundedLengthTuple(1, 2, [1, 2, 3]); // throws TypeError
|
|
240
|
+
* ```
|
|
241
|
+
*
|
|
242
|
+
* @template Min - The minimum number of elements (inclusive).
|
|
243
|
+
* @template Max - The maximum number of elements (inclusive).
|
|
244
|
+
* @template Xs - The input array type (tuple types are preserved).
|
|
245
|
+
* @param min - The minimum number of elements (inclusive).
|
|
246
|
+
* @param max - The maximum number of elements (inclusive).
|
|
247
|
+
* @param xs - The array to cast.
|
|
248
|
+
* @returns `xs` typed as `BoundedLengthTuple<Min, Max, Xs[number]> & Xs`.
|
|
249
|
+
* @throws {TypeError} If `xs.length` is outside `[min, max]`.
|
|
250
|
+
*/
|
|
251
|
+
export function asBoundedLengthTuple<
|
|
252
|
+
Min extends SizeType.ArgArr,
|
|
253
|
+
Max extends SizeType.ArgArr,
|
|
254
|
+
Xs extends readonly unknown[],
|
|
255
|
+
>(min: Min, max: Max, xs: Xs): BoundedLengthTuple<Min, Max, Xs[number]> & Xs;
|
|
256
|
+
|
|
257
|
+
// Curried version
|
|
258
|
+
export function asBoundedLengthTuple<
|
|
259
|
+
Min extends SizeType.ArgArr,
|
|
260
|
+
Max extends SizeType.ArgArr,
|
|
261
|
+
>(
|
|
262
|
+
min: Min,
|
|
263
|
+
max: Max,
|
|
264
|
+
): <Xs extends readonly unknown[]>(
|
|
265
|
+
xs: Xs,
|
|
266
|
+
) => BoundedLengthTuple<Min, Max, Xs[number]> & Xs;
|
|
267
|
+
|
|
268
|
+
export function asBoundedLengthTuple(
|
|
269
|
+
...args:
|
|
270
|
+
| readonly [
|
|
271
|
+
min: SizeType.ArgArr,
|
|
272
|
+
max: SizeType.ArgArr,
|
|
273
|
+
xs: readonly unknown[],
|
|
274
|
+
]
|
|
275
|
+
| readonly [min: SizeType.ArgArr, max: SizeType.ArgArr]
|
|
276
|
+
): readonly unknown[] | ((xs: readonly unknown[]) => readonly unknown[]) {
|
|
277
|
+
switch (args.length) {
|
|
278
|
+
case 3:
|
|
279
|
+
return asBoundedLengthTupleImpl(...args);
|
|
280
|
+
|
|
281
|
+
case 2:
|
|
282
|
+
return (xs) => asBoundedLengthTupleImpl(...args, xs);
|
|
283
|
+
}
|
|
284
|
+
}
|
|
285
|
+
|
|
286
|
+
const asBoundedLengthTupleImpl = (
|
|
287
|
+
min: SizeType.ArgArr,
|
|
288
|
+
max: SizeType.ArgArr,
|
|
289
|
+
xs: readonly unknown[],
|
|
290
|
+
): readonly unknown[] => {
|
|
291
|
+
const actualLength = xs.length;
|
|
292
|
+
|
|
293
|
+
if (!isBoundedLengthTuple(min, max, xs)) {
|
|
294
|
+
throw new TypeError(
|
|
295
|
+
`Expected an array of length in [${min}, ${max}], got an array of length ${actualLength}`,
|
|
296
|
+
);
|
|
297
|
+
}
|
|
298
|
+
|
|
299
|
+
return xs;
|
|
300
|
+
};
|
|
301
|
+
|
|
302
|
+
/**
|
|
303
|
+
* Casts an array to the structural empty tuple after checking that it is
|
|
304
|
+
* empty.
|
|
305
|
+
*
|
|
306
|
+
* This is the structural counterpart of `asEmptyArray`, and the length-0
|
|
307
|
+
* specialization of {@link asFixedLengthTuple}.
|
|
308
|
+
*
|
|
309
|
+
* @example
|
|
310
|
+
*
|
|
311
|
+
* ```ts
|
|
312
|
+
* const nothing = Arr.asEmptyTuple([]);
|
|
313
|
+
*
|
|
314
|
+
* // Arr.asEmptyTuple([1]); // throws TypeError
|
|
315
|
+
* ```
|
|
316
|
+
*
|
|
317
|
+
* @template Xs - The input array type (tuple types are preserved).
|
|
318
|
+
* @param xs - The array to cast.
|
|
319
|
+
* @returns `xs` typed as `readonly [] & Xs`.
|
|
320
|
+
* @throws {TypeError} If `xs` is not empty.
|
|
321
|
+
*/
|
|
322
|
+
export const asEmptyTuple = <Xs extends readonly unknown[]>(
|
|
323
|
+
xs: Xs,
|
|
324
|
+
): readonly [] & Xs => {
|
|
325
|
+
if (!isEmptyTuple(xs)) {
|
|
326
|
+
throw new TypeError(
|
|
327
|
+
`Expected an empty array, got an array of length ${xs.length}`,
|
|
328
|
+
);
|
|
329
|
+
}
|
|
330
|
+
|
|
331
|
+
return xs;
|
|
332
|
+
};
|
|
333
|
+
|
|
334
|
+
/**
|
|
335
|
+
* Casts an array to `MinLengthTuple<1, E>` after checking that it has at least
|
|
336
|
+
* one element.
|
|
337
|
+
*
|
|
338
|
+
* This is the structural counterpart of `asNonEmptyArray`, and the length-1
|
|
339
|
+
* specialization of {@link asMinLengthTuple}.
|
|
340
|
+
*
|
|
341
|
+
* @example
|
|
342
|
+
*
|
|
343
|
+
* ```ts
|
|
344
|
+
* const values = Arr.asNonEmptyTuple([1, 2, 3]);
|
|
345
|
+
*
|
|
346
|
+
* const first: number = values[0]; // OK — no `undefined`
|
|
347
|
+
*
|
|
348
|
+
* // Arr.asNonEmptyTuple([]); // throws TypeError
|
|
349
|
+
* ```
|
|
350
|
+
*
|
|
351
|
+
* @template Xs - The input array type (tuple types are preserved).
|
|
352
|
+
* @param xs - The array to cast.
|
|
353
|
+
* @returns `xs` typed as `MinLengthTuple<1, Xs[number]> & Xs`.
|
|
354
|
+
* @throws {TypeError} If `xs` is empty.
|
|
355
|
+
*/
|
|
356
|
+
export const asNonEmptyTuple = <Xs extends readonly unknown[]>(
|
|
357
|
+
xs: Xs,
|
|
358
|
+
): MinLengthTuple<1, Xs[number]> & Xs => {
|
|
359
|
+
if (!isMinLengthTuple(1, xs)) {
|
|
360
|
+
throw new TypeError('Expected a non-empty array, got an empty array');
|
|
361
|
+
}
|
|
362
|
+
|
|
363
|
+
return xs;
|
|
364
|
+
};
|
|
@@ -0,0 +1,207 @@
|
|
|
1
|
+
import {
|
|
2
|
+
type BoundedLengthTuple,
|
|
3
|
+
type FixedLengthTuple,
|
|
4
|
+
type MaxLengthTuple,
|
|
5
|
+
type MinLengthTuple,
|
|
6
|
+
} from 'ts-type-forge';
|
|
7
|
+
import { expectType } from '../../expect-type.mjs';
|
|
8
|
+
import {
|
|
9
|
+
asBoundedLengthTuple,
|
|
10
|
+
asEmptyTuple,
|
|
11
|
+
asFixedLengthTuple,
|
|
12
|
+
asMaxLengthTuple,
|
|
13
|
+
asMinLengthTuple,
|
|
14
|
+
asNonEmptyTuple,
|
|
15
|
+
} from './array-utils-length-bounded-tuple-cast.mjs';
|
|
16
|
+
|
|
17
|
+
describe(asFixedLengthTuple, () => {
|
|
18
|
+
test('casts to the structural tuple, so indexed access is defined', () => {
|
|
19
|
+
const values: readonly number[] = [255, 128, 0] as const;
|
|
20
|
+
|
|
21
|
+
const casted = asFixedLengthTuple(3, values);
|
|
22
|
+
|
|
23
|
+
expectType<typeof casted, FixedLengthTuple<3, number>>('~=');
|
|
24
|
+
|
|
25
|
+
const red: number = casted[0];
|
|
26
|
+
|
|
27
|
+
assert.deepStrictEqual(red, 255);
|
|
28
|
+
});
|
|
29
|
+
|
|
30
|
+
test('is the same value it was handed', () => {
|
|
31
|
+
const values: readonly number[] = [1, 2] as const;
|
|
32
|
+
|
|
33
|
+
expect(asFixedLengthTuple(2, values)).toBe(values);
|
|
34
|
+
});
|
|
35
|
+
|
|
36
|
+
test('throws on a length mismatch', () => {
|
|
37
|
+
expect(() => asFixedLengthTuple(3, [255, 128])).toThrow(TypeError);
|
|
38
|
+
|
|
39
|
+
expect(() => asFixedLengthTuple(3, [255, 128])).toThrow(
|
|
40
|
+
'Expected an array of length 3, got an array of length 2',
|
|
41
|
+
);
|
|
42
|
+
});
|
|
43
|
+
|
|
44
|
+
test('curried form applies the same check later', () => {
|
|
45
|
+
const asRgb = asFixedLengthTuple(3);
|
|
46
|
+
|
|
47
|
+
const casted = asRgb([1, 2, 3] as const);
|
|
48
|
+
|
|
49
|
+
// The input type is carried over, so the literal positions survive the cast
|
|
50
|
+
// exactly as they do for `asFixedLengthArray`.
|
|
51
|
+
expectType<
|
|
52
|
+
typeof casted,
|
|
53
|
+
FixedLengthTuple<3, 1 | 2 | 3> & readonly [1, 2, 3]
|
|
54
|
+
>('~=');
|
|
55
|
+
|
|
56
|
+
assert.deepStrictEqual<readonly number[]>(casted, [1, 2, 3]);
|
|
57
|
+
|
|
58
|
+
expect(() => asRgb([1, 2])).toThrow(TypeError);
|
|
59
|
+
});
|
|
60
|
+
});
|
|
61
|
+
|
|
62
|
+
describe(asMinLengthTuple, () => {
|
|
63
|
+
test('casts to the structural minimum-length tuple', () => {
|
|
64
|
+
const values: readonly number[] = [0, 1, 2, 3] as const;
|
|
65
|
+
|
|
66
|
+
const casted = asMinLengthTuple(3, values);
|
|
67
|
+
|
|
68
|
+
expectType<typeof casted, MinLengthTuple<3, number>>('~=');
|
|
69
|
+
|
|
70
|
+
const first: number = casted[0];
|
|
71
|
+
|
|
72
|
+
assert.deepStrictEqual(first, 0);
|
|
73
|
+
});
|
|
74
|
+
|
|
75
|
+
test('throws when the array is too short', () => {
|
|
76
|
+
expect(() => asMinLengthTuple(3, [0, 1])).toThrow(
|
|
77
|
+
'Expected an array of length >= 3, got an array of length 2',
|
|
78
|
+
);
|
|
79
|
+
});
|
|
80
|
+
|
|
81
|
+
test('curried form applies the same check later', () => {
|
|
82
|
+
const asHistory = asMinLengthTuple(3);
|
|
83
|
+
|
|
84
|
+
assert.deepStrictEqual(asHistory([1, 2, 3]), [1, 2, 3]);
|
|
85
|
+
|
|
86
|
+
expect(() => asHistory([1])).toThrow(TypeError);
|
|
87
|
+
});
|
|
88
|
+
});
|
|
89
|
+
|
|
90
|
+
describe(asMaxLengthTuple, () => {
|
|
91
|
+
test('casts to the structural maximum-length tuple', () => {
|
|
92
|
+
const values: readonly string[] = ['a', 'b', 'c'] as const;
|
|
93
|
+
|
|
94
|
+
const casted = asMaxLengthTuple(8, values);
|
|
95
|
+
|
|
96
|
+
expectType<typeof casted, MaxLengthTuple<8, string>>('~=');
|
|
97
|
+
|
|
98
|
+
assert.deepStrictEqual<readonly string[]>(casted, ['a', 'b', 'c']);
|
|
99
|
+
});
|
|
100
|
+
|
|
101
|
+
test('throws when the array is too long', () => {
|
|
102
|
+
expect(() => asMaxLengthTuple(2, ['a', 'b', 'c'])).toThrow(
|
|
103
|
+
'Expected an array of length <= 2, got an array of length 3',
|
|
104
|
+
);
|
|
105
|
+
});
|
|
106
|
+
|
|
107
|
+
test('curried form applies the same check later', () => {
|
|
108
|
+
const asTags = asMaxLengthTuple(2);
|
|
109
|
+
|
|
110
|
+
assert.deepStrictEqual(asTags(['a']), ['a']);
|
|
111
|
+
|
|
112
|
+
expect(() => asTags(['a', 'b', 'c'])).toThrow(TypeError);
|
|
113
|
+
});
|
|
114
|
+
});
|
|
115
|
+
|
|
116
|
+
describe(asBoundedLengthTuple, () => {
|
|
117
|
+
test('casts to the structural bounded-length tuple', () => {
|
|
118
|
+
const values: readonly number[] = [1, 2, 3] as const;
|
|
119
|
+
|
|
120
|
+
const casted = asBoundedLengthTuple(1, 5, values);
|
|
121
|
+
|
|
122
|
+
expectType<typeof casted, BoundedLengthTuple<1, 5, number>>('~=');
|
|
123
|
+
|
|
124
|
+
assert.deepStrictEqual<readonly number[]>(casted, [1, 2, 3]);
|
|
125
|
+
});
|
|
126
|
+
|
|
127
|
+
test('throws when the length is outside the range', () => {
|
|
128
|
+
expect(() => asBoundedLengthTuple(1, 2, [1, 2, 3])).toThrow(
|
|
129
|
+
'Expected an array of length in [1, 2], got an array of length 3',
|
|
130
|
+
);
|
|
131
|
+
});
|
|
132
|
+
|
|
133
|
+
test('curried form applies the same check later', () => {
|
|
134
|
+
const asSelection = asBoundedLengthTuple(1, 2);
|
|
135
|
+
|
|
136
|
+
assert.deepStrictEqual(asSelection([1]), [1]);
|
|
137
|
+
|
|
138
|
+
expect(() => asSelection([1, 2, 3])).toThrow(TypeError);
|
|
139
|
+
});
|
|
140
|
+
});
|
|
141
|
+
|
|
142
|
+
describe(asEmptyTuple, () => {
|
|
143
|
+
test('casts an empty array to the empty tuple', () => {
|
|
144
|
+
const values: readonly number[] = [] as const;
|
|
145
|
+
|
|
146
|
+
const casted = asEmptyTuple(values);
|
|
147
|
+
|
|
148
|
+
expectType<typeof casted, readonly []>('~=');
|
|
149
|
+
|
|
150
|
+
// Like the other casts, this hands back the value it was given.
|
|
151
|
+
expect(casted).toBe(values);
|
|
152
|
+
});
|
|
153
|
+
|
|
154
|
+
test('throws when the array is not empty', () => {
|
|
155
|
+
expect(() => asEmptyTuple([1])).toThrow(
|
|
156
|
+
'Expected an empty array, got an array of length 1',
|
|
157
|
+
);
|
|
158
|
+
});
|
|
159
|
+
});
|
|
160
|
+
|
|
161
|
+
describe(asNonEmptyTuple, () => {
|
|
162
|
+
test('casts to the structural non-empty tuple', () => {
|
|
163
|
+
const values: readonly number[] = [1, 2, 3] as const;
|
|
164
|
+
|
|
165
|
+
const casted = asNonEmptyTuple(values);
|
|
166
|
+
|
|
167
|
+
expectType<typeof casted, MinLengthTuple<1, number>>('~=');
|
|
168
|
+
|
|
169
|
+
const first: number = casted[0];
|
|
170
|
+
|
|
171
|
+
assert.deepStrictEqual(first, 1);
|
|
172
|
+
});
|
|
173
|
+
|
|
174
|
+
test('throws when the array is empty', () => {
|
|
175
|
+
expect(() => asNonEmptyTuple([])).toThrow(
|
|
176
|
+
'Expected a non-empty array, got an empty array',
|
|
177
|
+
);
|
|
178
|
+
});
|
|
179
|
+
});
|
|
180
|
+
|
|
181
|
+
describe('tuple casts preserve the input type, like their branded counterparts', () => {
|
|
182
|
+
test('an exact tuple survives asMinLengthTuple', () => {
|
|
183
|
+
const values = [1, 2, 3, 4, 5] as const;
|
|
184
|
+
|
|
185
|
+
const casted = asMinLengthTuple(3, values);
|
|
186
|
+
|
|
187
|
+
// Before, this widened to `MinLengthTuple<3, 1 | 2 | 3 | 4 | 5>` and the
|
|
188
|
+
// exact length was gone; `asMinLengthArray` never lost it.
|
|
189
|
+
expectType<typeof casted, readonly [1, 2, 3, 4, 5]>('~=');
|
|
190
|
+
|
|
191
|
+
const length: 5 = casted.length;
|
|
192
|
+
|
|
193
|
+
assert.deepStrictEqual(length, 5);
|
|
194
|
+
|
|
195
|
+
assert.deepStrictEqual<readonly number[]>(casted, [1, 2, 3, 4, 5]);
|
|
196
|
+
});
|
|
197
|
+
|
|
198
|
+
test('asNonEmptyTuple keeps the tuple too', () => {
|
|
199
|
+
const casted = asNonEmptyTuple([1, 2, 3] as const);
|
|
200
|
+
|
|
201
|
+
expectType<typeof casted, readonly [1, 2, 3]>('~=');
|
|
202
|
+
|
|
203
|
+
const first: 1 = casted[0];
|
|
204
|
+
|
|
205
|
+
assert.deepStrictEqual(first, 1);
|
|
206
|
+
});
|
|
207
|
+
});
|