ts-data-forge 9.0.1 → 11.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 +4 -4
- 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-element-access.d.mts +4 -4
- package/dist/array/impl/array-utils-element-access.mjs.map +1 -1
- package/dist/array/impl/array-utils-length-bounded-array-cast.d.mts +117 -0
- package/dist/array/impl/array-utils-length-bounded-array-cast.d.mts.map +1 -0
- package/dist/array/impl/array-utils-length-bounded-array-cast.mjs +37 -0
- package/dist/array/impl/array-utils-length-bounded-array-cast.mjs.map +1 -0
- package/dist/array/impl/array-utils-length-bounded-array-guard.d.mts +159 -0
- package/dist/array/impl/array-utils-length-bounded-array-guard.d.mts.map +1 -0
- package/dist/array/impl/array-utils-length-bounded-array-guard.mjs +7 -0
- package/dist/array/impl/array-utils-length-bounded-array-guard.mjs.map +1 -0
- package/dist/array/impl/array-utils-modification.d.mts +11 -11
- 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 +9 -9
- package/dist/array/impl/array-utils-reducing-value.mjs.map +1 -1
- package/dist/array/impl/array-utils-size.d.mts +3 -3
- package/dist/array/impl/array-utils-size.mjs.map +1 -1
- package/dist/array/impl/array-utils-slicing.d.mts +9 -9
- package/dist/array/impl/array-utils-slicing.d.mts.map +1 -1
- package/dist/array/impl/array-utils-slicing.mjs.map +1 -1
- package/dist/array/impl/array-utils-transformation.d.mts +16 -10
- package/dist/array/impl/array-utils-transformation.d.mts.map +1 -1
- package/dist/array/impl/array-utils-transformation.mjs.map +1 -1
- package/dist/array/impl/array-utils-validation.d.mts +17 -17
- package/dist/array/impl/array-utils-validation.d.mts.map +1 -1
- package/dist/array/impl/array-utils-validation.mjs +5 -5
- 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 +3 -1
- package/dist/array/impl/index.mjs.map +1 -1
- package/dist/entry-point.mjs +1 -1
- package/dist/guard/index.d.mts +0 -1
- package/dist/guard/index.d.mts.map +1 -1
- package/dist/guard/index.mjs +0 -1
- package/dist/guard/index.mjs.map +1 -1
- package/dist/index.d.mts +1 -0
- package/dist/index.d.mts.map +1 -1
- package/dist/index.mjs +1 -1
- package/dist/string/index.d.mts +2 -0
- package/dist/string/index.d.mts.map +1 -0
- package/dist/string/index.mjs +2 -0
- package/dist/string/index.mjs.map +1 -0
- package/dist/string/str.d.mts +273 -0
- package/dist/string/str.d.mts.map +1 -0
- package/dist/string/str.mjs +42 -0
- package/dist/string/str.mjs.map +1 -0
- package/package.json +30 -92
- package/src/array/impl/array-utils-creation.mts +5 -4
- package/src/array/impl/array-utils-element-access.mts +3 -3
- package/src/array/impl/array-utils-element-access.test.mts +3 -3
- package/src/array/impl/array-utils-length-bounded-array-cast.mts +190 -0
- package/src/array/impl/array-utils-length-bounded-array-cast.test.mts +118 -0
- package/src/array/impl/array-utils-length-bounded-array-guard.mts +195 -0
- package/src/array/impl/array-utils-length-bounded-array-guard.test.mts +169 -0
- package/src/array/impl/array-utils-modification.mts +16 -15
- package/src/array/impl/array-utils-modification.test.mts +10 -5
- package/src/array/impl/array-utils-reducing-value.mts +9 -9
- package/src/array/impl/array-utils-size.mts +2 -2
- package/src/array/impl/array-utils-slicing.mts +24 -13
- package/src/array/impl/array-utils-transformation.mts +22 -16
- package/src/array/impl/array-utils-transformation.test.mts +19 -19
- package/src/array/impl/array-utils-validation.mts +24 -24
- package/src/array/impl/array-utils-validation.test.mts +94 -92
- package/src/array/impl/index.mts +2 -0
- package/src/collections/queue.test.mts +1 -1
- package/src/guard/index.mts +0 -1
- package/src/index.mts +1 -0
- package/src/object/object.test.mts +3 -3
- package/src/string/index.mts +1 -0
- package/src/string/str.mts +371 -0
- package/src/string/str.test.mts +215 -0
- package/dist/guard/is-length-bounded-string.d.mts +0 -164
- package/dist/guard/is-length-bounded-string.d.mts.map +0 -1
- package/dist/guard/is-length-bounded-string.mjs +0 -7
- package/dist/guard/is-length-bounded-string.mjs.map +0 -1
- package/src/guard/is-length-bounded-string.mts +0 -199
- package/src/guard/is-length-bounded-string.test.mts +0 -170
|
@@ -0,0 +1,190 @@
|
|
|
1
|
+
import {
|
|
2
|
+
type BoundedLengthArray,
|
|
3
|
+
type FixedLengthArray,
|
|
4
|
+
type MaxLengthArray,
|
|
5
|
+
type MinLengthArray,
|
|
6
|
+
type SupportedLength,
|
|
7
|
+
} from 'ts-type-forge';
|
|
8
|
+
import {
|
|
9
|
+
isBoundedLengthArray,
|
|
10
|
+
isFixedLengthArray,
|
|
11
|
+
isMaxLengthArray,
|
|
12
|
+
isMinLengthArray,
|
|
13
|
+
} from './array-utils-length-bounded-array-guard.mjs';
|
|
14
|
+
|
|
15
|
+
/**
|
|
16
|
+
* Casts an array to `FixedLengthArray<Length, E>` after checking that it has
|
|
17
|
+
* exactly `length` elements.
|
|
18
|
+
*
|
|
19
|
+
* Use this instead of writing `xs as unknown as FixedLengthArray<N, E>`: the
|
|
20
|
+
* length is verified at runtime, and the original array type (e.g. tuple
|
|
21
|
+
* types) is preserved via intersection, so the result also benefits from the
|
|
22
|
+
* structural tuple prefix (in-range indexed access without `undefined` under
|
|
23
|
+
* `noUncheckedIndexedAccess`).
|
|
24
|
+
*
|
|
25
|
+
* @example
|
|
26
|
+
*
|
|
27
|
+
* ```ts
|
|
28
|
+
* const rgb = Arr.asFixedLengthArray([255, 128, 0], 3);
|
|
29
|
+
*
|
|
30
|
+
* const atMost5: MaxLengthArray<5, number> = rgb; // OK (3 <= 5)
|
|
31
|
+
* const red: number = rgb[0]; // OK — no `undefined`
|
|
32
|
+
*
|
|
33
|
+
* // Arr.asFixedLengthArray([255, 128], 3); // throws TypeError
|
|
34
|
+
* ```
|
|
35
|
+
*
|
|
36
|
+
* @template Xs - The input array type (tuple types are preserved).
|
|
37
|
+
* @template Length - The exact number of elements.
|
|
38
|
+
* @param xs - The array to cast.
|
|
39
|
+
* @param length - The exact number of elements.
|
|
40
|
+
* @returns `xs` typed as `FixedLengthArray<Length, Xs[number]> & Xs`.
|
|
41
|
+
* @throws {TypeError} If `xs.length !== length`.
|
|
42
|
+
*/
|
|
43
|
+
export const asFixedLengthArray = <
|
|
44
|
+
Xs extends readonly unknown[],
|
|
45
|
+
Length extends SupportedLength,
|
|
46
|
+
>(
|
|
47
|
+
xs: Xs,
|
|
48
|
+
length: Length,
|
|
49
|
+
): FixedLengthArray<Length, Xs[number]> & Xs => {
|
|
50
|
+
if (!isFixedLengthArray(xs, length)) {
|
|
51
|
+
throw new TypeError(
|
|
52
|
+
`Expected an array of length ${length}, got an array of length ${xs.length}`,
|
|
53
|
+
);
|
|
54
|
+
}
|
|
55
|
+
|
|
56
|
+
return xs;
|
|
57
|
+
};
|
|
58
|
+
|
|
59
|
+
/**
|
|
60
|
+
* Casts an array to `MinLengthArray<MinLength, E>` after checking that it has
|
|
61
|
+
* at least `minLength` elements.
|
|
62
|
+
*
|
|
63
|
+
* Use this instead of writing `xs as unknown as MinLengthArray<N, E>`: the
|
|
64
|
+
* length is verified at runtime, and the original array type (e.g. tuple
|
|
65
|
+
* types) is preserved via intersection, so the result also benefits from the
|
|
66
|
+
* structural tuple prefix (indexed access below `min(N, 10)` without
|
|
67
|
+
* `undefined` under `noUncheckedIndexedAccess`).
|
|
68
|
+
*
|
|
69
|
+
* @example
|
|
70
|
+
*
|
|
71
|
+
* ```ts
|
|
72
|
+
* const history = Arr.asMinLengthArray([0, 1, 2, 3], 3);
|
|
73
|
+
*
|
|
74
|
+
* const nonEmpty: MinLengthArray<1, number> = history; // OK (3 >= 1)
|
|
75
|
+
* const first: number = history[0]; // OK — no `undefined`
|
|
76
|
+
*
|
|
77
|
+
* // Arr.asMinLengthArray([0], 3); // throws TypeError
|
|
78
|
+
* ```
|
|
79
|
+
*
|
|
80
|
+
* @template Xs - The input array type (tuple types are preserved).
|
|
81
|
+
* @template MinLength - The minimum number of elements (inclusive).
|
|
82
|
+
* @param xs - The array to cast.
|
|
83
|
+
* @param minLength - The minimum number of elements (inclusive).
|
|
84
|
+
* @returns `xs` typed as `MinLengthArray<MinLength, Xs[number]> & Xs`.
|
|
85
|
+
* @throws {TypeError} If `xs.length < minLength`.
|
|
86
|
+
*/
|
|
87
|
+
export const asMinLengthArray = <
|
|
88
|
+
Xs extends readonly unknown[],
|
|
89
|
+
MinLength extends SupportedLength,
|
|
90
|
+
>(
|
|
91
|
+
xs: Xs,
|
|
92
|
+
minLength: MinLength,
|
|
93
|
+
): MinLengthArray<MinLength, Xs[number]> & Xs => {
|
|
94
|
+
if (!isMinLengthArray(xs, minLength)) {
|
|
95
|
+
throw new TypeError(
|
|
96
|
+
`Expected an array of length >= ${minLength}, got an array of length ${xs.length}`,
|
|
97
|
+
);
|
|
98
|
+
}
|
|
99
|
+
|
|
100
|
+
return xs;
|
|
101
|
+
};
|
|
102
|
+
|
|
103
|
+
/**
|
|
104
|
+
* Casts an array to `MaxLengthArray<MaxLength, E>` after checking that it has
|
|
105
|
+
* at most `maxLength` elements.
|
|
106
|
+
*
|
|
107
|
+
* Use this instead of writing `xs as unknown as MaxLengthArray<N, E>`: the
|
|
108
|
+
* length is verified at runtime, and the original array type (e.g. tuple
|
|
109
|
+
* types) is preserved via intersection.
|
|
110
|
+
*
|
|
111
|
+
* @example
|
|
112
|
+
*
|
|
113
|
+
* ```ts
|
|
114
|
+
* const tags = Arr.asMaxLengthArray(['a', 'b', 'c'], 8);
|
|
115
|
+
*
|
|
116
|
+
* const relaxed: MaxLengthArray<16, string> = tags; // OK (8 <= 16)
|
|
117
|
+
*
|
|
118
|
+
* // Arr.asMaxLengthArray(['a', 'b', 'c'], 2); // throws TypeError
|
|
119
|
+
* ```
|
|
120
|
+
*
|
|
121
|
+
* @template Xs - The input array type (tuple types are preserved).
|
|
122
|
+
* @template MaxLength - The maximum number of elements (inclusive).
|
|
123
|
+
* @param xs - The array to cast.
|
|
124
|
+
* @param maxLength - The maximum number of elements (inclusive).
|
|
125
|
+
* @returns `xs` typed as `MaxLengthArray<MaxLength, Xs[number]> & Xs`.
|
|
126
|
+
* @throws {TypeError} If `xs.length > maxLength`.
|
|
127
|
+
*/
|
|
128
|
+
export const asMaxLengthArray = <
|
|
129
|
+
Xs extends readonly unknown[],
|
|
130
|
+
MaxLength extends SupportedLength,
|
|
131
|
+
>(
|
|
132
|
+
xs: Xs,
|
|
133
|
+
maxLength: MaxLength,
|
|
134
|
+
): MaxLengthArray<MaxLength, Xs[number]> & Xs => {
|
|
135
|
+
if (!isMaxLengthArray(xs, maxLength)) {
|
|
136
|
+
throw new TypeError(
|
|
137
|
+
`Expected an array of length <= ${maxLength}, got an array of length ${xs.length}`,
|
|
138
|
+
);
|
|
139
|
+
}
|
|
140
|
+
|
|
141
|
+
return xs;
|
|
142
|
+
};
|
|
143
|
+
|
|
144
|
+
/**
|
|
145
|
+
* Casts an array to `BoundedLengthArray<MinLength, MaxLength, E>` after
|
|
146
|
+
* checking that its length is within the inclusive range
|
|
147
|
+
* `[minLength, maxLength]`.
|
|
148
|
+
*
|
|
149
|
+
* Use this instead of writing
|
|
150
|
+
* `xs as unknown as BoundedLengthArray<Min, Max, E>`: the length is verified
|
|
151
|
+
* at runtime, and the original array type (e.g. tuple types) is preserved via
|
|
152
|
+
* intersection.
|
|
153
|
+
*
|
|
154
|
+
* @example
|
|
155
|
+
*
|
|
156
|
+
* ```ts
|
|
157
|
+
* const selection = Arr.asBoundedLengthArray([1, 2, 3], 1, 5);
|
|
158
|
+
*
|
|
159
|
+
* const relaxed: BoundedLengthArray<0, 100, number> = selection; // OK
|
|
160
|
+
*
|
|
161
|
+
* // Arr.asBoundedLengthArray([], 1, 5); // throws TypeError
|
|
162
|
+
* ```
|
|
163
|
+
*
|
|
164
|
+
* @template Xs - The input array type (tuple types are preserved).
|
|
165
|
+
* @template MinLength - The minimum number of elements (inclusive).
|
|
166
|
+
* @template MaxLength - The maximum number of elements (inclusive).
|
|
167
|
+
* @param xs - The array to cast.
|
|
168
|
+
* @param minLength - The minimum number of elements (inclusive).
|
|
169
|
+
* @param maxLength - The maximum number of elements (inclusive).
|
|
170
|
+
* @returns `xs` typed as
|
|
171
|
+
* `BoundedLengthArray<MinLength, MaxLength, Xs[number]> & Xs`.
|
|
172
|
+
* @throws {TypeError} If `xs.length < minLength || xs.length > maxLength`.
|
|
173
|
+
*/
|
|
174
|
+
export const asBoundedLengthArray = <
|
|
175
|
+
Xs extends readonly unknown[],
|
|
176
|
+
MinLength extends SupportedLength,
|
|
177
|
+
MaxLength extends SupportedLength,
|
|
178
|
+
>(
|
|
179
|
+
xs: Xs,
|
|
180
|
+
minLength: MinLength,
|
|
181
|
+
maxLength: MaxLength,
|
|
182
|
+
): BoundedLengthArray<MinLength, MaxLength, Xs[number]> & Xs => {
|
|
183
|
+
if (!isBoundedLengthArray(xs, minLength, maxLength)) {
|
|
184
|
+
throw new TypeError(
|
|
185
|
+
`Expected an array of length in [${minLength}, ${maxLength}], got an array of length ${xs.length}`,
|
|
186
|
+
);
|
|
187
|
+
}
|
|
188
|
+
|
|
189
|
+
return xs;
|
|
190
|
+
};
|
|
@@ -0,0 +1,118 @@
|
|
|
1
|
+
import {
|
|
2
|
+
type BoundedLengthArray,
|
|
3
|
+
type FixedLengthArray,
|
|
4
|
+
type MaxLengthArray,
|
|
5
|
+
type MinLengthArray,
|
|
6
|
+
} from 'ts-type-forge';
|
|
7
|
+
import { expectType } from '../../expect-type.mjs';
|
|
8
|
+
import {
|
|
9
|
+
asBoundedLengthArray,
|
|
10
|
+
asFixedLengthArray,
|
|
11
|
+
asMaxLengthArray,
|
|
12
|
+
asMinLengthArray,
|
|
13
|
+
} from './array-utils-length-bounded-array-cast.mjs';
|
|
14
|
+
|
|
15
|
+
describe(asFixedLengthArray, () => {
|
|
16
|
+
test('should return the input when the length matches exactly', () => {
|
|
17
|
+
const xs: readonly number[] = [0, 1, 2, 3] as const;
|
|
18
|
+
|
|
19
|
+
const casted = asFixedLengthArray(xs, 4);
|
|
20
|
+
|
|
21
|
+
expect(casted).toBe(xs);
|
|
22
|
+
|
|
23
|
+
expectType<typeof casted, FixedLengthArray<4, number>>('<=');
|
|
24
|
+
|
|
25
|
+
// in-range indexed access does not include `undefined`
|
|
26
|
+
const _first = casted[0];
|
|
27
|
+
|
|
28
|
+
expectType<typeof _first, number>('=');
|
|
29
|
+
});
|
|
30
|
+
|
|
31
|
+
test('should throw when the length does not match', () => {
|
|
32
|
+
expect(() => asFixedLengthArray([0, 1, 2], 4)).toThrow(TypeError);
|
|
33
|
+
|
|
34
|
+
expect(() => asFixedLengthArray([0, 1, 2], 4)).toThrow(
|
|
35
|
+
'Expected an array of length 4, got an array of length 3',
|
|
36
|
+
);
|
|
37
|
+
});
|
|
38
|
+
|
|
39
|
+
test('should preserve the original array type', () => {
|
|
40
|
+
const tuple = [1, 2, 3] as const;
|
|
41
|
+
|
|
42
|
+
const casted = asFixedLengthArray(tuple, 3);
|
|
43
|
+
|
|
44
|
+
expect(casted).toBe(tuple);
|
|
45
|
+
|
|
46
|
+
expectType<typeof casted, readonly [1, 2, 3]>('<=');
|
|
47
|
+
|
|
48
|
+
expectType<typeof casted, FixedLengthArray<3, 1 | 2 | 3>>('<=');
|
|
49
|
+
});
|
|
50
|
+
});
|
|
51
|
+
|
|
52
|
+
describe(asMinLengthArray, () => {
|
|
53
|
+
test('should return the input when the array is long enough', () => {
|
|
54
|
+
const xs: readonly number[] = [0, 1, 2, 3] as const;
|
|
55
|
+
|
|
56
|
+
const casted = asMinLengthArray(xs, 3);
|
|
57
|
+
|
|
58
|
+
expect(casted).toBe(xs);
|
|
59
|
+
|
|
60
|
+
expectType<typeof casted, MinLengthArray<3, number>>('<=');
|
|
61
|
+
|
|
62
|
+
expectType<typeof casted, MinLengthArray<1, number>>('<='); // 3 >= 1
|
|
63
|
+
});
|
|
64
|
+
|
|
65
|
+
test('should throw when the array is too short', () => {
|
|
66
|
+
expect(() => asMinLengthArray([0], 3)).toThrow(TypeError);
|
|
67
|
+
|
|
68
|
+
expect(() => asMinLengthArray([0], 3)).toThrow(
|
|
69
|
+
'Expected an array of length >= 3, got an array of length 1',
|
|
70
|
+
);
|
|
71
|
+
});
|
|
72
|
+
});
|
|
73
|
+
|
|
74
|
+
describe(asMaxLengthArray, () => {
|
|
75
|
+
test('should return the input when the array is short enough', () => {
|
|
76
|
+
const xs: readonly number[] = [0, 1, 2] as const;
|
|
77
|
+
|
|
78
|
+
const casted = asMaxLengthArray(xs, 8);
|
|
79
|
+
|
|
80
|
+
expect(casted).toBe(xs);
|
|
81
|
+
|
|
82
|
+
expectType<typeof casted, MaxLengthArray<8, number>>('<=');
|
|
83
|
+
|
|
84
|
+
expectType<typeof casted, MaxLengthArray<16, number>>('<='); // 8 <= 16
|
|
85
|
+
});
|
|
86
|
+
|
|
87
|
+
test('should throw when the array is too long', () => {
|
|
88
|
+
expect(() => asMaxLengthArray([0, 1, 2], 2)).toThrow(TypeError);
|
|
89
|
+
|
|
90
|
+
expect(() => asMaxLengthArray([0, 1, 2], 2)).toThrow(
|
|
91
|
+
'Expected an array of length <= 2, got an array of length 3',
|
|
92
|
+
);
|
|
93
|
+
});
|
|
94
|
+
});
|
|
95
|
+
|
|
96
|
+
describe(asBoundedLengthArray, () => {
|
|
97
|
+
test('should return the input when the length is within the range', () => {
|
|
98
|
+
const xs: readonly number[] = [1, 2, 3] as const;
|
|
99
|
+
|
|
100
|
+
const casted = asBoundedLengthArray(xs, 1, 5);
|
|
101
|
+
|
|
102
|
+
expect(casted).toBe(xs);
|
|
103
|
+
|
|
104
|
+
expectType<typeof casted, BoundedLengthArray<1, 5, number>>('<=');
|
|
105
|
+
|
|
106
|
+
expectType<typeof casted, MinLengthArray<1, number>>('<=');
|
|
107
|
+
|
|
108
|
+
expectType<typeof casted, MaxLengthArray<5, number>>('<=');
|
|
109
|
+
});
|
|
110
|
+
|
|
111
|
+
test('should throw when the length is out of the range', () => {
|
|
112
|
+
expect(() => asBoundedLengthArray([], 1, 5)).toThrow(TypeError);
|
|
113
|
+
|
|
114
|
+
expect(() => asBoundedLengthArray([1, 2, 3, 4, 5, 6], 1, 5)).toThrow(
|
|
115
|
+
'Expected an array of length in [1, 5], got an array of length 6',
|
|
116
|
+
);
|
|
117
|
+
});
|
|
118
|
+
});
|
|
@@ -0,0 +1,195 @@
|
|
|
1
|
+
import {
|
|
2
|
+
type BoundedLengthArray,
|
|
3
|
+
type FixedLengthArray,
|
|
4
|
+
type MaxLengthArray,
|
|
5
|
+
type MinLengthArray,
|
|
6
|
+
type SupportedLength,
|
|
7
|
+
} from 'ts-type-forge';
|
|
8
|
+
|
|
9
|
+
/**
|
|
10
|
+
* Type guard that checks if an array has at least `minLength` elements.
|
|
11
|
+
*
|
|
12
|
+
* **Type Narrowing Behavior:**
|
|
13
|
+
*
|
|
14
|
+
* - Narrows the input to `MinLengthArray<MinLength, E>` while preserving the
|
|
15
|
+
* original array type (e.g. tuple types) via intersection.
|
|
16
|
+
* - The length constraint lives only in the brand, so this stays cheap for the
|
|
17
|
+
* type checker even when the element type is large — unlike
|
|
18
|
+
* `isMinLengthTuple`, which narrows to a structural tuple type.
|
|
19
|
+
* - The result participates in the length-constraint subtyping relation: for
|
|
20
|
+
* example, a value narrowed to `MinLengthArray<3, E>` is assignable to
|
|
21
|
+
* `MinLengthArray<1, E>`.
|
|
22
|
+
*
|
|
23
|
+
* @example
|
|
24
|
+
*
|
|
25
|
+
* ```ts
|
|
26
|
+
* const input: readonly number[] = [0, 1, 2, 3];
|
|
27
|
+
*
|
|
28
|
+
* assert.isTrue(Arr.isMinLengthArray(input, 3));
|
|
29
|
+
*
|
|
30
|
+
* assert.isFalse(Arr.isMinLengthArray([0], 3));
|
|
31
|
+
*
|
|
32
|
+
* if (Arr.isMinLengthArray(input, 3)) {
|
|
33
|
+
* const history: MinLengthArray<3, number> = input;
|
|
34
|
+
*
|
|
35
|
+
* const nonEmpty: MinLengthArray<1, number> = input; // OK (3 >= 1)
|
|
36
|
+
* }
|
|
37
|
+
* ```
|
|
38
|
+
*
|
|
39
|
+
* @template Xs - The input array type (tuple types are preserved).
|
|
40
|
+
* @template MinLength - The minimum number of elements (inclusive).
|
|
41
|
+
* @param xs - The array to check.
|
|
42
|
+
* @param minLength - The minimum number of elements (inclusive).
|
|
43
|
+
* @returns `true` if `xs.length >= minLength`, `false` otherwise. When `true`,
|
|
44
|
+
* TypeScript narrows `xs` to `MinLengthArray<MinLength, Xs[number]> & Xs`.
|
|
45
|
+
*/
|
|
46
|
+
export const isMinLengthArray = <
|
|
47
|
+
Xs extends readonly unknown[],
|
|
48
|
+
MinLength extends SupportedLength,
|
|
49
|
+
>(
|
|
50
|
+
xs: Xs,
|
|
51
|
+
minLength: MinLength,
|
|
52
|
+
): xs is MinLengthArray<MinLength, Xs[number]> & Xs => xs.length >= minLength;
|
|
53
|
+
|
|
54
|
+
/**
|
|
55
|
+
* Type guard that checks if an array has at most `maxLength` elements.
|
|
56
|
+
*
|
|
57
|
+
* **Type Narrowing Behavior:**
|
|
58
|
+
*
|
|
59
|
+
* - Narrows the input to `MaxLengthArray<MaxLength, E>` while preserving the
|
|
60
|
+
* original array type (e.g. tuple types) via intersection.
|
|
61
|
+
* - The length constraint lives only in the brand, so this stays cheap for the
|
|
62
|
+
* type checker even when the element type is large — unlike
|
|
63
|
+
* `isMaxLengthTuple`, which narrows to a union of tuple types.
|
|
64
|
+
* - The result participates in the length-constraint subtyping relation: for
|
|
65
|
+
* example, a value narrowed to `MaxLengthArray<8, E>` is assignable to
|
|
66
|
+
* `MaxLengthArray<16, E>`.
|
|
67
|
+
*
|
|
68
|
+
* @example
|
|
69
|
+
*
|
|
70
|
+
* ```ts
|
|
71
|
+
* const input: readonly string[] = ['a', 'b', 'c'];
|
|
72
|
+
*
|
|
73
|
+
* assert.isTrue(Arr.isMaxLengthArray(input, 8));
|
|
74
|
+
*
|
|
75
|
+
* assert.isFalse(Arr.isMaxLengthArray(input, 2));
|
|
76
|
+
*
|
|
77
|
+
* if (Arr.isMaxLengthArray(input, 8)) {
|
|
78
|
+
* const tags: MaxLengthArray<8, string> = input;
|
|
79
|
+
*
|
|
80
|
+
* const relaxed: MaxLengthArray<16, string> = input; // OK (8 <= 16)
|
|
81
|
+
* }
|
|
82
|
+
* ```
|
|
83
|
+
*
|
|
84
|
+
* @template Xs - The input array type (tuple types are preserved).
|
|
85
|
+
* @template MaxLength - The maximum number of elements (inclusive).
|
|
86
|
+
* @param xs - The array to check.
|
|
87
|
+
* @param maxLength - The maximum number of elements (inclusive).
|
|
88
|
+
* @returns `true` if `xs.length <= maxLength`, `false` otherwise. When `true`,
|
|
89
|
+
* TypeScript narrows `xs` to `MaxLengthArray<MaxLength, Xs[number]> & Xs`.
|
|
90
|
+
*/
|
|
91
|
+
export const isMaxLengthArray = <
|
|
92
|
+
Xs extends readonly unknown[],
|
|
93
|
+
MaxLength extends SupportedLength,
|
|
94
|
+
>(
|
|
95
|
+
xs: Xs,
|
|
96
|
+
maxLength: MaxLength,
|
|
97
|
+
): xs is MaxLengthArray<MaxLength, Xs[number]> & Xs => xs.length <= maxLength;
|
|
98
|
+
|
|
99
|
+
/**
|
|
100
|
+
* Type guard that checks if an array's length is within the inclusive range
|
|
101
|
+
* `[minLength, maxLength]`.
|
|
102
|
+
*
|
|
103
|
+
* **Type Narrowing Behavior:**
|
|
104
|
+
*
|
|
105
|
+
* - Narrows the input to `BoundedLengthArray<MinLength, MaxLength, E>` while
|
|
106
|
+
* preserving the original array type (e.g. tuple types) via intersection.
|
|
107
|
+
* - Since `BoundedLengthArray` is the intersection of `MinLengthArray` and
|
|
108
|
+
* `MaxLengthArray`, the narrowed value is assignable to both, and both
|
|
109
|
+
* bounds can be weakened independently.
|
|
110
|
+
* - The length constraint lives only in the brand, so this stays cheap for the
|
|
111
|
+
* type checker even when the element type is large — unlike
|
|
112
|
+
* `isBoundedLengthTuple`, which narrows to a union of tuple types.
|
|
113
|
+
*
|
|
114
|
+
* @example
|
|
115
|
+
*
|
|
116
|
+
* ```ts
|
|
117
|
+
* const input: readonly number[] = [1, 2, 3];
|
|
118
|
+
*
|
|
119
|
+
* assert.isTrue(Arr.isBoundedLengthArray(input, 1, 5));
|
|
120
|
+
*
|
|
121
|
+
* assert.isFalse(Arr.isBoundedLengthArray([], 1, 5));
|
|
122
|
+
*
|
|
123
|
+
* if (Arr.isBoundedLengthArray(input, 1, 5)) {
|
|
124
|
+
* const selection: BoundedLengthArray<1, 5, number> = input;
|
|
125
|
+
*
|
|
126
|
+
* const relaxed: BoundedLengthArray<0, 100, number> = input; // OK ([1, 5] ⊆ [0, 100])
|
|
127
|
+
* }
|
|
128
|
+
* ```
|
|
129
|
+
*
|
|
130
|
+
* @template Xs - The input array type (tuple types are preserved).
|
|
131
|
+
* @template MinLength - The minimum number of elements (inclusive).
|
|
132
|
+
* @template MaxLength - The maximum number of elements (inclusive).
|
|
133
|
+
* @param xs - The array to check.
|
|
134
|
+
* @param minLength - The minimum number of elements (inclusive).
|
|
135
|
+
* @param maxLength - The maximum number of elements (inclusive).
|
|
136
|
+
* @returns `true` if `minLength <= xs.length && xs.length <= maxLength`,
|
|
137
|
+
* `false` otherwise. When `true`, TypeScript narrows `xs` to
|
|
138
|
+
* `BoundedLengthArray<MinLength, MaxLength, Xs[number]> & Xs`.
|
|
139
|
+
*/
|
|
140
|
+
export const isBoundedLengthArray = <
|
|
141
|
+
Xs extends readonly unknown[],
|
|
142
|
+
MinLength extends SupportedLength,
|
|
143
|
+
MaxLength extends SupportedLength,
|
|
144
|
+
>(
|
|
145
|
+
xs: Xs,
|
|
146
|
+
minLength: MinLength,
|
|
147
|
+
maxLength: MaxLength,
|
|
148
|
+
): xs is BoundedLengthArray<MinLength, MaxLength, Xs[number]> & Xs =>
|
|
149
|
+
xs.length >= minLength && xs.length <= maxLength;
|
|
150
|
+
|
|
151
|
+
/**
|
|
152
|
+
* Type guard that checks if an array has exactly `length` elements.
|
|
153
|
+
*
|
|
154
|
+
* **Type Narrowing Behavior:**
|
|
155
|
+
*
|
|
156
|
+
* - Narrows the input to `FixedLengthArray<Length, E>` while preserving the
|
|
157
|
+
* original array type (e.g. tuple types) via intersection.
|
|
158
|
+
* - Since `FixedLengthArray<Length, E>` is defined as
|
|
159
|
+
* `BoundedLengthArray<Length, Length, E>`, the narrowed value is assignable
|
|
160
|
+
* to any `MinLengthArray<N, E>` with `N <= Length` and any
|
|
161
|
+
* `MaxLengthArray<N, E>` with `N >= Length`.
|
|
162
|
+
* - The length constraint lives only in the brand, so this stays cheap for the
|
|
163
|
+
* type checker even when the element type is large — unlike
|
|
164
|
+
* `isFixedLengthTuple`, which narrows to a structural tuple type.
|
|
165
|
+
*
|
|
166
|
+
* @example
|
|
167
|
+
*
|
|
168
|
+
* ```ts
|
|
169
|
+
* const input: readonly number[] = [255, 128, 0];
|
|
170
|
+
*
|
|
171
|
+
* assert.isTrue(Arr.isFixedLengthArray(input, 3));
|
|
172
|
+
*
|
|
173
|
+
* assert.isFalse(Arr.isFixedLengthArray(input, 4));
|
|
174
|
+
*
|
|
175
|
+
* if (Arr.isFixedLengthArray(input, 3)) {
|
|
176
|
+
* const rgb: FixedLengthArray<3, number> = input;
|
|
177
|
+
*
|
|
178
|
+
* const atMost5: MaxLengthArray<5, number> = input; // OK (3 <= 5)
|
|
179
|
+
* }
|
|
180
|
+
* ```
|
|
181
|
+
*
|
|
182
|
+
* @template Xs - The input array type (tuple types are preserved).
|
|
183
|
+
* @template Length - The exact number of elements.
|
|
184
|
+
* @param xs - The array to check.
|
|
185
|
+
* @param length - The exact number of elements.
|
|
186
|
+
* @returns `true` if `xs.length === length`, `false` otherwise. When `true`,
|
|
187
|
+
* TypeScript narrows `xs` to `FixedLengthArray<Length, Xs[number]> & Xs`.
|
|
188
|
+
*/
|
|
189
|
+
export const isFixedLengthArray = <
|
|
190
|
+
Xs extends readonly unknown[],
|
|
191
|
+
Length extends SupportedLength,
|
|
192
|
+
>(
|
|
193
|
+
xs: Xs,
|
|
194
|
+
length: Length,
|
|
195
|
+
): xs is FixedLengthArray<Length, Xs[number]> & Xs => xs.length === length;
|
|
@@ -0,0 +1,169 @@
|
|
|
1
|
+
import {
|
|
2
|
+
type BoundedLengthArray,
|
|
3
|
+
type FixedLengthArray,
|
|
4
|
+
type MaxLengthArray,
|
|
5
|
+
type MinLengthArray,
|
|
6
|
+
} from 'ts-type-forge';
|
|
7
|
+
import { expectType } from '../../expect-type.mjs';
|
|
8
|
+
import {
|
|
9
|
+
isBoundedLengthArray,
|
|
10
|
+
isFixedLengthArray,
|
|
11
|
+
isMaxLengthArray,
|
|
12
|
+
isMinLengthArray,
|
|
13
|
+
} from './array-utils-length-bounded-array-guard.mjs';
|
|
14
|
+
|
|
15
|
+
describe(isMinLengthArray, () => {
|
|
16
|
+
test('should return true when the array is long enough', () => {
|
|
17
|
+
assert.isTrue(isMinLengthArray([1, 2, 3], 3));
|
|
18
|
+
|
|
19
|
+
assert.isTrue(isMinLengthArray([1, 2, 3], 2));
|
|
20
|
+
|
|
21
|
+
assert.isTrue(isMinLengthArray([], 0));
|
|
22
|
+
});
|
|
23
|
+
|
|
24
|
+
test('should return false when the array is too short', () => {
|
|
25
|
+
assert.isFalse(isMinLengthArray([1, 2], 3));
|
|
26
|
+
|
|
27
|
+
assert.isFalse(isMinLengthArray([], 1));
|
|
28
|
+
});
|
|
29
|
+
|
|
30
|
+
test('should act as a type guard', () => {
|
|
31
|
+
const value: readonly number[] = [0, 1, 2, 3] as const;
|
|
32
|
+
|
|
33
|
+
if (isMinLengthArray(value, 3)) {
|
|
34
|
+
expectType<typeof value, MinLengthArray<3, number>>('<=');
|
|
35
|
+
|
|
36
|
+
expectType<typeof value, MinLengthArray<1, number>>('<='); // 3 >= 1
|
|
37
|
+
|
|
38
|
+
expectType<typeof value, MinLengthArray<5, number>>('!<=');
|
|
39
|
+
|
|
40
|
+
// indexed access below the minimum length does not include `undefined`
|
|
41
|
+
// (even under `noUncheckedIndexedAccess`)
|
|
42
|
+
const _first = value[0];
|
|
43
|
+
|
|
44
|
+
expectType<typeof _first, number>('=');
|
|
45
|
+
|
|
46
|
+
const _third = value[2];
|
|
47
|
+
|
|
48
|
+
expectType<typeof _third, number>('=');
|
|
49
|
+
|
|
50
|
+
const _fourth = value[3];
|
|
51
|
+
|
|
52
|
+
expectType<typeof _fourth, number | undefined>('=');
|
|
53
|
+
|
|
54
|
+
expect(value.length).toBeGreaterThanOrEqual(3);
|
|
55
|
+
}
|
|
56
|
+
});
|
|
57
|
+
|
|
58
|
+
test('should preserve the original array type', () => {
|
|
59
|
+
const tuple = [1, 2, 3] as const;
|
|
60
|
+
|
|
61
|
+
if (isMinLengthArray(tuple, 2)) {
|
|
62
|
+
expectType<typeof tuple, readonly [1, 2, 3]>('<=');
|
|
63
|
+
|
|
64
|
+
expectType<typeof tuple, MinLengthArray<2, 1 | 2 | 3>>('<=');
|
|
65
|
+
}
|
|
66
|
+
});
|
|
67
|
+
});
|
|
68
|
+
|
|
69
|
+
describe(isMaxLengthArray, () => {
|
|
70
|
+
test('should return true when the array is short enough', () => {
|
|
71
|
+
assert.isTrue(isMaxLengthArray([1, 2, 3], 3));
|
|
72
|
+
|
|
73
|
+
assert.isTrue(isMaxLengthArray([1, 2, 3], 4));
|
|
74
|
+
|
|
75
|
+
assert.isTrue(isMaxLengthArray([], 0));
|
|
76
|
+
});
|
|
77
|
+
|
|
78
|
+
test('should return false when the array is too long', () => {
|
|
79
|
+
assert.isFalse(isMaxLengthArray([1, 2, 3], 2));
|
|
80
|
+
|
|
81
|
+
assert.isFalse(isMaxLengthArray([1], 0));
|
|
82
|
+
});
|
|
83
|
+
|
|
84
|
+
test('should act as a type guard', () => {
|
|
85
|
+
const value: readonly number[] = [0, 1, 2] as const;
|
|
86
|
+
|
|
87
|
+
if (isMaxLengthArray(value, 8)) {
|
|
88
|
+
expectType<typeof value, MaxLengthArray<8, number>>('<=');
|
|
89
|
+
|
|
90
|
+
expectType<typeof value, MaxLengthArray<16, number>>('<='); // 8 <= 16
|
|
91
|
+
|
|
92
|
+
expectType<typeof value, MaxLengthArray<2, number>>('!<=');
|
|
93
|
+
|
|
94
|
+
expect(value.length).toBeLessThanOrEqual(8);
|
|
95
|
+
}
|
|
96
|
+
});
|
|
97
|
+
});
|
|
98
|
+
|
|
99
|
+
describe(isBoundedLengthArray, () => {
|
|
100
|
+
test('should return true when the length is within the range', () => {
|
|
101
|
+
assert.isTrue(isBoundedLengthArray([1, 2, 3], 1, 5));
|
|
102
|
+
|
|
103
|
+
assert.isTrue(isBoundedLengthArray([1], 1, 5));
|
|
104
|
+
|
|
105
|
+
assert.isTrue(isBoundedLengthArray([1, 2, 3, 4, 5], 1, 5));
|
|
106
|
+
});
|
|
107
|
+
|
|
108
|
+
test('should return false when the length is out of the range', () => {
|
|
109
|
+
assert.isFalse(isBoundedLengthArray([], 1, 5));
|
|
110
|
+
|
|
111
|
+
assert.isFalse(isBoundedLengthArray([1, 2, 3, 4, 5, 6], 1, 5));
|
|
112
|
+
});
|
|
113
|
+
|
|
114
|
+
test('should act as a type guard', () => {
|
|
115
|
+
const value: readonly number[] = [1, 2, 3] as const;
|
|
116
|
+
|
|
117
|
+
if (isBoundedLengthArray(value, 1, 5)) {
|
|
118
|
+
expectType<typeof value, BoundedLengthArray<1, 5, number>>('<=');
|
|
119
|
+
|
|
120
|
+
expectType<typeof value, BoundedLengthArray<0, 100, number>>('<=');
|
|
121
|
+
|
|
122
|
+
expectType<typeof value, MinLengthArray<1, number>>('<=');
|
|
123
|
+
|
|
124
|
+
expectType<typeof value, MaxLengthArray<5, number>>('<=');
|
|
125
|
+
|
|
126
|
+
expectType<typeof value, BoundedLengthArray<2, 5, number>>('!<=');
|
|
127
|
+
}
|
|
128
|
+
});
|
|
129
|
+
});
|
|
130
|
+
|
|
131
|
+
describe(isFixedLengthArray, () => {
|
|
132
|
+
test('should return true when the length matches exactly', () => {
|
|
133
|
+
assert.isTrue(isFixedLengthArray([1, 2, 3], 3));
|
|
134
|
+
|
|
135
|
+
assert.isTrue(isFixedLengthArray([], 0));
|
|
136
|
+
});
|
|
137
|
+
|
|
138
|
+
test('should return false when the length does not match', () => {
|
|
139
|
+
assert.isFalse(isFixedLengthArray([1, 2, 3], 2));
|
|
140
|
+
|
|
141
|
+
assert.isFalse(isFixedLengthArray([1, 2, 3], 4));
|
|
142
|
+
});
|
|
143
|
+
|
|
144
|
+
test('should act as a type guard', () => {
|
|
145
|
+
const value: readonly number[] = [255, 128, 0] as const;
|
|
146
|
+
|
|
147
|
+
if (isFixedLengthArray(value, 3)) {
|
|
148
|
+
expectType<typeof value, FixedLengthArray<3, number>>('<=');
|
|
149
|
+
|
|
150
|
+
expectType<typeof value, MaxLengthArray<5, number>>('<='); // 3 <= 5
|
|
151
|
+
|
|
152
|
+
expectType<typeof value, MinLengthArray<1, number>>('<='); // 3 >= 1
|
|
153
|
+
|
|
154
|
+
expectType<typeof value, FixedLengthArray<4, number>>('!<=');
|
|
155
|
+
|
|
156
|
+
// `length` is narrowed to the literal and in-range indexed access does
|
|
157
|
+
// not include `undefined` (even under `noUncheckedIndexedAccess`)
|
|
158
|
+
const _len = value.length;
|
|
159
|
+
|
|
160
|
+
expectType<typeof _len, 3>('=');
|
|
161
|
+
|
|
162
|
+
const _second = value[1];
|
|
163
|
+
|
|
164
|
+
expectType<typeof _second, number>('=');
|
|
165
|
+
|
|
166
|
+
expect(value).toHaveLength(3);
|
|
167
|
+
}
|
|
168
|
+
});
|
|
169
|
+
});
|