ts-data-forge 14.0.0 → 14.1.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 +8 -7
- package/dist/array/impl/array-utils-modification.d.mts +18 -5
- 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-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-transformation.d.mts +12 -4
- package/dist/array/impl/array-utils-transformation.d.mts.map +1 -1
- package/dist/array/impl/array-utils-transformation.mjs +4 -5
- package/dist/array/impl/array-utils-transformation.mjs.map +1 -1
- package/dist/array/impl/array-utils-validation.d.mts +8 -3
- package/dist/array/impl/array-utils-validation.d.mts.map +1 -1
- package/dist/array/impl/array-utils-validation.mjs.map +1 -1
- package/dist/collections/imap-mapped.mjs +1 -1
- package/dist/collections/imap-mapped.mjs.map +1 -1
- package/dist/collections/imap.d.mts +1 -1
- package/dist/collections/imap.mjs +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/functional/match.d.mts.map +1 -1
- package/dist/functional/match.mjs.map +1 -1
- package/dist/number/branded-types/int.d.mts +1 -1
- package/dist/number/branded-types/int16.d.mts +1 -1
- package/dist/number/branded-types/int32.d.mts +1 -1
- package/dist/number/branded-types/non-negative-int16.d.mts +1 -1
- package/dist/number/branded-types/non-negative-int32.d.mts +1 -1
- package/dist/number/branded-types/non-positive-finite-number.mjs +1 -1
- package/dist/number/branded-types/non-positive-finite-number.mjs.map +1 -1
- package/dist/number/branded-types/positive-int.d.mts +1 -1
- package/dist/number/branded-types/positive-int16.d.mts +1 -1
- package/dist/number/branded-types/positive-int32.d.mts +1 -1
- package/dist/number/branded-types/positive-safe-int.d.mts +1 -1
- package/dist/number/branded-types/positive-uint16.d.mts +1 -1
- package/dist/number/branded-types/positive-uint32.d.mts +1 -1
- package/dist/number/branded-types/safe-int.d.mts +1 -1
- package/dist/number/branded-types/safe-uint.d.mts +1 -1
- package/dist/number/branded-types/uint.d.mts +1 -1
- package/dist/number/branded-types/uint16.d.mts +1 -1
- package/dist/number/branded-types/uint32.d.mts +1 -1
- package/dist/number/enum/int8.d.mts +2 -2
- package/dist/number/enum/int8.d.mts.map +1 -1
- package/dist/number/enum/int8.mjs.map +1 -1
- package/dist/number/enum/uint8.d.mts +2 -2
- package/dist/number/enum/uint8.d.mts.map +1 -1
- package/dist/number/enum/uint8.mjs.map +1 -1
- package/dist/number/num-arithmetic-types.d.mts +9 -10
- package/dist/number/num-arithmetic-types.d.mts.map +1 -1
- package/dist/number/refined-number-utils.d.mts +2 -2
- package/dist/number/refined-number-utils.d.mts.map +1 -1
- package/dist/number/refined-number-utils.mjs.map +1 -1
- package/dist/object/object.d.mts +12 -12
- package/dist/object/object.d.mts.map +1 -1
- package/dist/object/object.mjs +5 -1
- package/dist/object/object.mjs.map +1 -1
- package/dist/others/fast-deep-equal.d.mts.map +1 -1
- package/dist/others/fast-deep-equal.mjs +2 -2
- package/dist/others/fast-deep-equal.mjs.map +1 -1
- package/package.json +4 -5
- package/src/array/impl/array-utils-creation.test.mts +10 -8
- package/src/array/impl/array-utils-length-constraint-preservation.test.mts +2 -2
- package/src/array/impl/array-utils-modification.mts +41 -33
- package/src/array/impl/array-utils-modification.test.mts +77 -4
- package/src/array/impl/array-utils-shape-invariants.test.mts +526 -0
- package/src/array/impl/array-utils-slice-clamped.mts +2 -1
- package/src/array/impl/array-utils-slicing.test.mts +11 -3
- package/src/array/impl/array-utils-transformation.mts +70 -20
- package/src/array/impl/array-utils-transformation.test.mts +44 -10
- package/src/array/impl/array-utils-validation.mts +54 -10
- package/src/array/impl/array-utils-validation.test.mts +153 -3
- package/src/collections/imap-mapped.mts +1 -1
- package/src/collections/imap-mapped.test.mts +2 -2
- package/src/collections/imap.mts +2 -2
- package/src/collections/iset-mapped.mts +7 -3
- package/src/collections/iset-mapped.test.mts +1 -1
- package/src/collections/iset.mts +7 -3
- package/src/collections/iset.test.mts +9 -6
- package/src/functional/match.mts +5 -4
- package/src/functional/result.test.mts +5 -2
- package/src/guard/is-primitive.test.mts +1 -1
- package/src/guard/is-record.test.mts +4 -4
- package/src/guard/is-type.test.mts +2 -2
- package/src/number/branded-types/finite-number.test.mts +2 -2
- package/src/number/branded-types/non-negative-finite-number.test.mts +2 -2
- package/src/number/branded-types/non-positive-finite-number.mts +1 -1
- package/src/number/branded-types/non-positive-finite-number.test.mts +5 -9
- package/src/number/branded-types/non-zero-finite-number.test.mts +2 -2
- package/src/number/branded-types/positive-finite-number.test.mts +2 -2
- package/src/number/enum/int8.mts +2 -1
- package/src/number/enum/uint8.mts +6 -2
- package/src/number/num-arithmetic-types.mts +129 -24
- package/src/number/num.test.mts +11 -11
- package/src/number/refined-number-utils.mts +2 -1
- package/src/object/object.mts +29 -17
- package/src/object/object.test.mts +2 -1
- package/src/others/fast-deep-equal.mts +6 -4
|
@@ -3,11 +3,13 @@ import {
|
|
|
3
3
|
type FixedLengthTuple,
|
|
4
4
|
type MinLengthArray,
|
|
5
5
|
type NonEmptyArray,
|
|
6
|
+
type NonEmptyTuple,
|
|
6
7
|
} from 'ts-type-forge';
|
|
7
8
|
import { IMap } from '../../collections/index.mjs';
|
|
8
9
|
import { expectType } from '../../expect-type.mjs';
|
|
9
10
|
import { Optional } from '../../functional/index.mjs';
|
|
10
11
|
import { SafeUint } from '../../number/index.mjs';
|
|
12
|
+
import { asMinLengthArray } from './array-utils-length-bounded-array-cast.mjs';
|
|
11
13
|
import {
|
|
12
14
|
cartesianProduct,
|
|
13
15
|
concat,
|
|
@@ -52,7 +54,7 @@ describe('Arr transformations', () => {
|
|
|
52
54
|
|
|
53
55
|
const mappedMixed = map(mixed, (x) => typeof x);
|
|
54
56
|
|
|
55
|
-
expectType<typeof mappedMixed,
|
|
57
|
+
expectType<typeof mappedMixed, FixedLengthTuple<3, string>>('<=');
|
|
56
58
|
|
|
57
59
|
assert.deepStrictEqual(mappedMixed, ['number', 'string', 'boolean']);
|
|
58
60
|
});
|
|
@@ -62,12 +64,37 @@ describe('Arr transformations', () => {
|
|
|
62
64
|
|
|
63
65
|
const mappedWithIndex = map(tuple, (x, i) => `${i}:${x}`);
|
|
64
66
|
|
|
65
|
-
expectType<typeof mappedWithIndex,
|
|
66
|
-
'<=',
|
|
67
|
-
);
|
|
67
|
+
expectType<typeof mappedWithIndex, FixedLengthTuple<3, string>>('<=');
|
|
68
68
|
|
|
69
69
|
assert.deepStrictEqual(mappedWithIndex, ['0:a', '1:b', '2:c']);
|
|
70
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
|
+
});
|
|
71
98
|
});
|
|
72
99
|
|
|
73
100
|
describe(scan, () => {
|
|
@@ -76,7 +103,7 @@ describe('Arr transformations', () => {
|
|
|
76
103
|
|
|
77
104
|
const runningSum = scan(numbers, (acc, curr) => acc + curr, 0);
|
|
78
105
|
|
|
79
|
-
expectType<typeof runningSum,
|
|
106
|
+
expectType<typeof runningSum, NonEmptyTuple<number>>('<=');
|
|
80
107
|
|
|
81
108
|
assert.deepStrictEqual<readonly number[]>(runningSum, [0, 1, 3, 6, 10]);
|
|
82
109
|
});
|
|
@@ -134,7 +161,7 @@ describe('Arr transformations', () => {
|
|
|
134
161
|
|
|
135
162
|
const result = scan(strings, (acc, curr) => acc + curr.length, 0);
|
|
136
163
|
|
|
137
|
-
expectType<typeof result,
|
|
164
|
+
expectType<typeof result, NonEmptyTuple<number>>('<=');
|
|
138
165
|
|
|
139
166
|
assert.deepStrictEqual<readonly number[]>(result, [0, 1, 2, 3]);
|
|
140
167
|
});
|
|
@@ -1015,7 +1042,7 @@ describe('Arr transformations', () => {
|
|
|
1015
1042
|
|
|
1016
1043
|
const zipped = zip(xs, ys);
|
|
1017
1044
|
|
|
1018
|
-
expectType<typeof zipped, readonly
|
|
1045
|
+
expectType<typeof zipped, readonly FixedLengthTuple<2, number>[]>('=');
|
|
1019
1046
|
|
|
1020
1047
|
test('case 2', () => {
|
|
1021
1048
|
assert.deepStrictEqual(zipped, [[1, 4]]);
|
|
@@ -1180,9 +1207,9 @@ describe('Arr transformations', () => {
|
|
|
1180
1207
|
|
|
1181
1208
|
/* `toReversed` is a known permutation, so the tuple half can be reversed
|
|
1182
1209
|
alongside the brand. It used to fall back to `ChangeArrayElement`, which kept
|
|
1183
|
-
the length but collapsed the positions to their union.
|
|
1184
|
-
note in `array-utils-modification.test.mts`. */
|
|
1185
|
-
|
|
1210
|
+
the length but collapsed the positions to their union. Shape-per-parameter;
|
|
1211
|
+
see the note in `array-utils-modification.test.mts`. */
|
|
1212
|
+
const reversedTypeChecks = (
|
|
1186
1213
|
brandedFive: MinLengthArray<5, string>,
|
|
1187
1214
|
brandedAndTuple: MinLengthArray<3, number> & readonly [1, 2, 3, 4, 5],
|
|
1188
1215
|
): void => {
|
|
@@ -1208,3 +1235,10 @@ export const reversedTypeChecks = (
|
|
|
1208
1235
|
|
|
1209
1236
|
expectType<typeof _sortedTuple, FixedLengthTuple<3, 1 | 2 | 3>>('~=');
|
|
1210
1237
|
};
|
|
1238
|
+
|
|
1239
|
+
test('toReversed / toSorted type-level checks', () => {
|
|
1240
|
+
reversedTypeChecks(
|
|
1241
|
+
asMinLengthArray(5, ['a', 'b', 'c', 'd', 'e'] as const),
|
|
1242
|
+
asMinLengthArray(3, [1, 2, 3, 4, 5] as const),
|
|
1243
|
+
);
|
|
1244
|
+
});
|
|
@@ -1,4 +1,10 @@
|
|
|
1
|
-
import {
|
|
1
|
+
import {
|
|
2
|
+
type BoolOr,
|
|
3
|
+
type ChangeArrayElement,
|
|
4
|
+
type IsAny,
|
|
5
|
+
type IsUnknown,
|
|
6
|
+
type UnknownBrand,
|
|
7
|
+
} from 'ts-type-forge';
|
|
2
8
|
import { asUint32, Num } from '../../number/index.mjs';
|
|
3
9
|
import { type ArrayIndex, type SizeType } from '../../types.mjs';
|
|
4
10
|
|
|
@@ -55,15 +61,30 @@ type Cast<A, B> = A extends B ? A : never;
|
|
|
55
61
|
* assert.isFalse(allStartWithA);
|
|
56
62
|
* ```
|
|
57
63
|
*/
|
|
58
|
-
// Type guard overloads - narrow the entire array type
|
|
59
|
-
|
|
60
|
-
|
|
61
|
-
|
|
62
|
-
|
|
63
|
-
|
|
64
|
-
|
|
65
|
-
|
|
66
|
-
|
|
64
|
+
// Type guard overloads - narrow the entire array type.
|
|
65
|
+
//
|
|
66
|
+
// Both narrowed types are intersected with `Ar`: a type predicate's type has
|
|
67
|
+
// to be assignable to the parameter's type, and neither the deferred
|
|
68
|
+
// conditional `ChangeArrayElement<Ar, S>` nor the mapped type over a generic
|
|
69
|
+
// `Ar` is (TS2677). The intersection also keeps whatever the caller already
|
|
70
|
+
// knew about the array — a branded five-tuple stays a five-tuple.
|
|
71
|
+
export function every<
|
|
72
|
+
const Ar extends readonly unknown[] & UnknownBrand,
|
|
73
|
+
S extends Ar[number],
|
|
74
|
+
>(
|
|
75
|
+
array: Ar,
|
|
76
|
+
predicate: (a: Ar[number], index: ArrayIndex<Ar>) => a is S,
|
|
77
|
+
): array is ChangeArrayElement<Ar, S> & Ar;
|
|
78
|
+
|
|
79
|
+
// A plain array or tuple carries no length brand, so the homomorphic mapping
|
|
80
|
+
// is the whole answer — same reasoning as in `map`.
|
|
81
|
+
export function every<
|
|
82
|
+
const Ar extends readonly unknown[],
|
|
83
|
+
S extends Ar[number],
|
|
84
|
+
>(
|
|
85
|
+
array: Ar,
|
|
86
|
+
predicate: (a: Ar[number], index: ArrayIndex<Ar>) => a is S,
|
|
87
|
+
): array is Readonly<{ [K in keyof Ar]: S }> & Ar;
|
|
67
88
|
|
|
68
89
|
// Regular boolean predicate overloads
|
|
69
90
|
export function every<const Ar extends readonly unknown[]>(
|
|
@@ -71,6 +92,29 @@ export function every<const Ar extends readonly unknown[]>(
|
|
|
71
92
|
predicate: (a: Ar[number], index: ArrayIndex<Ar>) => boolean,
|
|
72
93
|
): boolean;
|
|
73
94
|
|
|
95
|
+
// curried version
|
|
96
|
+
//
|
|
97
|
+
// As in `map`, the branded and the plain case have to be overloads *of the
|
|
98
|
+
// returned function*, not two overloads of `every` itself: both curried
|
|
99
|
+
// signatures take the same single predicate, so overload resolution would
|
|
100
|
+
// always pick the first and the second would be dead — which is what left a
|
|
101
|
+
// plain `readonly unknown[]` unable to satisfy the `UnknownBrand` constraint
|
|
102
|
+
// at the call site of the returned guard.
|
|
103
|
+
//
|
|
104
|
+
// The intersection is also what keeps the group compatible with the
|
|
105
|
+
// implementation signature. A lone `<Ar extends readonly E[] & UnknownBrand>`
|
|
106
|
+
// signature is not something `(array: readonly E[]) => boolean` can implement
|
|
107
|
+
// (TS2394); the unbranded member of the intersection is, and one matching
|
|
108
|
+
// member is enough.
|
|
109
|
+
export function every<E, S extends E>(
|
|
110
|
+
predicate: (a: E, index: SizeType.Arr) => a is S,
|
|
111
|
+
): (<const Ar extends readonly E[] & UnknownBrand>(
|
|
112
|
+
array: Ar,
|
|
113
|
+
) => array is ChangeArrayElement<Ar, S> & Ar) &
|
|
114
|
+
(<const Ar extends readonly E[]>(
|
|
115
|
+
array: Ar,
|
|
116
|
+
) => array is Readonly<{ [K in keyof Ar]: S }> & Ar);
|
|
117
|
+
|
|
74
118
|
export function every<E>(
|
|
75
119
|
predicate: (a: E, index: SizeType.Arr) => boolean,
|
|
76
120
|
): (array: readonly E[]) => boolean;
|
|
@@ -1,4 +1,16 @@
|
|
|
1
|
+
import {
|
|
2
|
+
type BoundedLengthArray,
|
|
3
|
+
type FixedLengthArray,
|
|
4
|
+
type FixedLengthTuple,
|
|
5
|
+
type MinLengthArray,
|
|
6
|
+
type ReadonlyRecord,
|
|
7
|
+
} from 'ts-type-forge';
|
|
1
8
|
import { expectType } from '../../expect-type.mjs';
|
|
9
|
+
import {
|
|
10
|
+
asBoundedLengthArray,
|
|
11
|
+
asFixedLengthArray,
|
|
12
|
+
asMinLengthArray,
|
|
13
|
+
} from './array-utils-length-bounded-array-cast.mjs';
|
|
2
14
|
import {
|
|
3
15
|
every,
|
|
4
16
|
indexIsInRange,
|
|
@@ -294,13 +306,12 @@ describe('Arr validations', () => {
|
|
|
294
306
|
type ComplexUnion =
|
|
295
307
|
| Readonly<
|
|
296
308
|
| { type: 'array'; data: readonly string[] }
|
|
297
|
-
| { type: 'object'; data:
|
|
309
|
+
| { type: 'object'; data: ReadonlyRecord<string, unknown> }
|
|
298
310
|
>
|
|
299
311
|
| readonly number[]
|
|
300
312
|
| string
|
|
301
313
|
| null;
|
|
302
314
|
|
|
303
|
-
// eslint-disable-next-line @typescript-eslint/prefer-readonly-parameter-types
|
|
304
315
|
const processComplex = (value: ComplexUnion): number => {
|
|
305
316
|
if (isArray(value)) {
|
|
306
317
|
expectType<typeof value, readonly number[]>('=');
|
|
@@ -324,7 +335,7 @@ describe('Arr validations', () => {
|
|
|
324
335
|
typeof value,
|
|
325
336
|
Readonly<
|
|
326
337
|
| { type: 'array'; data: readonly string[] }
|
|
327
|
-
| { type: 'object'; data:
|
|
338
|
+
| { type: 'object'; data: ReadonlyRecord<string, unknown> }
|
|
328
339
|
>
|
|
329
340
|
>('=');
|
|
330
341
|
|
|
@@ -436,6 +447,145 @@ describe('Arr validations', () => {
|
|
|
436
447
|
|
|
437
448
|
assert.isTrue(indexMatchesValue);
|
|
438
449
|
});
|
|
450
|
+
|
|
451
|
+
/* What `every` reports about the array it narrowed, across the shapes the
|
|
452
|
+
two type-guard overloads have to cope with. It used to answer a bare
|
|
453
|
+
`readonly S[]` for every one of them, dropping both the tuple length and
|
|
454
|
+
the length-constraint brand. `assert.isTrue` narrows for the rest of the
|
|
455
|
+
block, so each case checks the runtime answer and the narrowed type
|
|
456
|
+
together. */
|
|
457
|
+
describe('narrowing', () => {
|
|
458
|
+
const isString = (x: string | number): x is string =>
|
|
459
|
+
typeof x === 'string';
|
|
460
|
+
|
|
461
|
+
const values: readonly (string | number)[] = [
|
|
462
|
+
'Ada',
|
|
463
|
+
'Grace',
|
|
464
|
+
'Katherine',
|
|
465
|
+
] as const;
|
|
466
|
+
|
|
467
|
+
test('should keep a plain array as a plain array', () => {
|
|
468
|
+
const shapeArray: readonly (string | number)[] = values;
|
|
469
|
+
|
|
470
|
+
assert.isTrue(every(shapeArray, isString));
|
|
471
|
+
|
|
472
|
+
expectType<typeof shapeArray, readonly string[]>('~=');
|
|
473
|
+
});
|
|
474
|
+
|
|
475
|
+
test('should keep the length of a tuple', () => {
|
|
476
|
+
const shapeTuple: FixedLengthTuple<2, string | number> = [
|
|
477
|
+
'Ada',
|
|
478
|
+
'Grace',
|
|
479
|
+
] as const;
|
|
480
|
+
|
|
481
|
+
assert.isTrue(every(shapeTuple, isString));
|
|
482
|
+
|
|
483
|
+
expectType<typeof shapeTuple, FixedLengthTuple<2, string>>('~=');
|
|
484
|
+
|
|
485
|
+
expectType<(typeof shapeTuple)['length'], 2>('=');
|
|
486
|
+
});
|
|
487
|
+
|
|
488
|
+
test('should keep a MinLengthArray brand', () => {
|
|
489
|
+
const shapeBranded = asMinLengthArray(3, values);
|
|
490
|
+
|
|
491
|
+
assert.isTrue(every(shapeBranded, isString));
|
|
492
|
+
|
|
493
|
+
expectType<typeof shapeBranded, MinLengthArray<3, string>>('~=');
|
|
494
|
+
|
|
495
|
+
// The brand still rules out `undefined` below the minimum length.
|
|
496
|
+
expectType<(typeof shapeBranded)[0], string>('=');
|
|
497
|
+
});
|
|
498
|
+
|
|
499
|
+
test('should keep a BoundedLengthArray brand', () => {
|
|
500
|
+
const shapeBounded = asBoundedLengthArray(2, 5, values);
|
|
501
|
+
|
|
502
|
+
assert.isTrue(every(shapeBounded, isString));
|
|
503
|
+
|
|
504
|
+
expectType<typeof shapeBounded, BoundedLengthArray<2, 5, string>>('~=');
|
|
505
|
+
});
|
|
506
|
+
|
|
507
|
+
test('should keep a FixedLengthArray brand', () => {
|
|
508
|
+
const shapeFixed = asFixedLengthArray(3, values);
|
|
509
|
+
|
|
510
|
+
assert.isTrue(every(shapeFixed, isString));
|
|
511
|
+
|
|
512
|
+
expectType<typeof shapeFixed, FixedLengthArray<3, string>>('~=');
|
|
513
|
+
|
|
514
|
+
expectType<(typeof shapeFixed)['length'], 3>('=');
|
|
515
|
+
});
|
|
516
|
+
|
|
517
|
+
/* Brand intersected with an exact tuple — the shape the `as*` casts and
|
|
518
|
+
the `is*` guards produce. Brand, length and positions all survive. */
|
|
519
|
+
test('should keep brand, length and positions of a branded tuple', () => {
|
|
520
|
+
const tuple: readonly [string, string | number] = [
|
|
521
|
+
'Ada',
|
|
522
|
+
'Grace',
|
|
523
|
+
] as const;
|
|
524
|
+
|
|
525
|
+
const shapeBrandedTuple = asMinLengthArray(2, tuple);
|
|
526
|
+
|
|
527
|
+
assert.isTrue(every(shapeBrandedTuple, isString));
|
|
528
|
+
|
|
529
|
+
expectType<(typeof shapeBrandedTuple)[0], string>('=');
|
|
530
|
+
|
|
531
|
+
expectType<(typeof shapeBrandedTuple)[1], string>('=');
|
|
532
|
+
|
|
533
|
+
expectType<(typeof shapeBrandedTuple)['length'], 2>('=');
|
|
534
|
+
});
|
|
535
|
+
|
|
536
|
+
/* The curried form has to reach the same conclusions. Its two cases are
|
|
537
|
+
overloads of the *returned* guard rather than of `every` itself, so a
|
|
538
|
+
single `every(predicate)` value serves branded and unbranded arrays
|
|
539
|
+
alike — spelled as two overloads of `every`, the branded one shadowed
|
|
540
|
+
the other and passing a plain array to the result was an error. */
|
|
541
|
+
describe('curried', () => {
|
|
542
|
+
const allStrings = every(isString);
|
|
543
|
+
|
|
544
|
+
test('should keep a plain array as a plain array', () => {
|
|
545
|
+
const shapeArray: readonly (string | number)[] = values;
|
|
546
|
+
|
|
547
|
+
assert.isTrue(allStrings(shapeArray));
|
|
548
|
+
|
|
549
|
+
expectType<typeof shapeArray, readonly string[]>('~=');
|
|
550
|
+
});
|
|
551
|
+
|
|
552
|
+
test('should keep the length of a tuple', () => {
|
|
553
|
+
const shapeTuple: FixedLengthTuple<2, string | number> = [
|
|
554
|
+
'Ada',
|
|
555
|
+
'Grace',
|
|
556
|
+
] as const;
|
|
557
|
+
|
|
558
|
+
assert.isTrue(allStrings(shapeTuple));
|
|
559
|
+
|
|
560
|
+
expectType<typeof shapeTuple, FixedLengthTuple<2, string>>('~=');
|
|
561
|
+
|
|
562
|
+
expectType<(typeof shapeTuple)['length'], 2>('=');
|
|
563
|
+
});
|
|
564
|
+
|
|
565
|
+
test('should keep a MinLengthArray brand', () => {
|
|
566
|
+
const shapeBranded = asMinLengthArray(3, values);
|
|
567
|
+
|
|
568
|
+
assert.isTrue(allStrings(shapeBranded));
|
|
569
|
+
|
|
570
|
+
expectType<typeof shapeBranded, MinLengthArray<3, string>>('~=');
|
|
571
|
+
});
|
|
572
|
+
|
|
573
|
+
test('should keep brand, length and positions of a branded tuple', () => {
|
|
574
|
+
const tuple: readonly [string, string | number] = [
|
|
575
|
+
'Ada',
|
|
576
|
+
'Grace',
|
|
577
|
+
] as const;
|
|
578
|
+
|
|
579
|
+
const shapeBrandedTuple = asMinLengthArray(2, tuple);
|
|
580
|
+
|
|
581
|
+
assert.isTrue(allStrings(shapeBrandedTuple));
|
|
582
|
+
|
|
583
|
+
expectType<(typeof shapeBrandedTuple)[0], string>('=');
|
|
584
|
+
|
|
585
|
+
expectType<(typeof shapeBrandedTuple)['length'], 2>('=');
|
|
586
|
+
});
|
|
587
|
+
});
|
|
588
|
+
});
|
|
439
589
|
});
|
|
440
590
|
|
|
441
591
|
describe(some, () => {
|
|
@@ -1157,7 +1157,7 @@ class IMapMappedClass<K, V, KM extends MapSetKeyType>
|
|
|
1157
1157
|
case 'update': {
|
|
1158
1158
|
const curr = mut_result.get(key);
|
|
1159
1159
|
|
|
1160
|
-
if (!mut_result.has(key)
|
|
1160
|
+
if (curr === undefined || !mut_result.has(key)) {
|
|
1161
1161
|
if (this.#showNotFoundMessage) {
|
|
1162
1162
|
console.warn(
|
|
1163
1163
|
`IMapMapped.withMutations::update: key not found: ${String(key)}`,
|
|
@@ -13,7 +13,7 @@ const testKeyToString = (key: Readonly<TestKey>): string =>
|
|
|
13
13
|
`${key.type}_${key.id}` as const;
|
|
14
14
|
|
|
15
15
|
const stringToTestKey = (str: string): TestKey => {
|
|
16
|
-
const [type, idStr] = str.split('_');
|
|
16
|
+
const [type, idStr] = str.split('_', 2);
|
|
17
17
|
|
|
18
18
|
return {
|
|
19
19
|
type: type ?? '',
|
|
@@ -113,7 +113,7 @@ describe('IMapMapped.create', () => {
|
|
|
113
113
|
): string => `${key.nested.id}_${key.arr.join(',')}` as const;
|
|
114
114
|
|
|
115
115
|
const stringToComplexKey = (str: string): ComplexKey => {
|
|
116
|
-
const [idStr, arrStr] = str.split('_');
|
|
116
|
+
const [idStr, arrStr] = str.split('_', 2);
|
|
117
117
|
|
|
118
118
|
return {
|
|
119
119
|
nested: {
|
package/src/collections/imap.mts
CHANGED
|
@@ -549,7 +549,7 @@ type IMapInterface<K extends MapSetKeyType, V> = Readonly<{
|
|
|
549
549
|
* const entries = [
|
|
550
550
|
* ['k1', 'v1'],
|
|
551
551
|
* ['k2', 'v2'],
|
|
552
|
-
* ] as const satisfies readonly
|
|
552
|
+
* ] as const satisfies readonly FixedLengthTuple<2, string>[];
|
|
553
553
|
*
|
|
554
554
|
* const map = IMap.create(entries);
|
|
555
555
|
*
|
|
@@ -877,7 +877,7 @@ class IMapClass<K extends MapSetKeyType, V>
|
|
|
877
877
|
|
|
878
878
|
const curr = mut_result.get(key);
|
|
879
879
|
|
|
880
|
-
if (!mut_result.has(key)
|
|
880
|
+
if (curr === undefined || !mut_result.has(key)) {
|
|
881
881
|
if (this.#showNotFoundMessage) {
|
|
882
882
|
const keyStr = unknownToString(key);
|
|
883
883
|
|
|
@@ -1,5 +1,9 @@
|
|
|
1
1
|
/* eslint-disable unicorn/prefer-iterator-to-array-at-end */
|
|
2
|
-
import {
|
|
2
|
+
import {
|
|
3
|
+
type FixedLengthTuple,
|
|
4
|
+
type ReadonlyRecord,
|
|
5
|
+
type WidenLiteral,
|
|
6
|
+
} from 'ts-type-forge';
|
|
3
7
|
import { asUint32 } from '../number/index.mjs';
|
|
4
8
|
import { type MapSetKeyType, type SizeType } from '../types.mjs';
|
|
5
9
|
|
|
@@ -788,7 +792,7 @@ type ISetMappedInterface<K, KM extends MapSetKeyType> = Readonly<{
|
|
|
788
792
|
*
|
|
789
793
|
* @returns An iterable iterator of entries.
|
|
790
794
|
*/
|
|
791
|
-
entries: () => IterableIterator<
|
|
795
|
+
entries: () => IterableIterator<FixedLengthTuple<2, K>>;
|
|
792
796
|
|
|
793
797
|
// Conversion
|
|
794
798
|
|
|
@@ -1444,7 +1448,7 @@ class ISetMappedClass<K, KM extends MapSetKeyType>
|
|
|
1444
1448
|
}
|
|
1445
1449
|
|
|
1446
1450
|
/** @inheritdoc */
|
|
1447
|
-
*entries(): IterableIterator<
|
|
1451
|
+
*entries(): IterableIterator<FixedLengthTuple<2, K>> {
|
|
1448
1452
|
for (const km of this.#set) {
|
|
1449
1453
|
// JavaScript Set's entries() yields [value, value]
|
|
1450
1454
|
const a = this.#fromKey(km);
|
|
@@ -13,7 +13,7 @@ const testElementToString = (el: Readonly<TestElement>): string =>
|
|
|
13
13
|
`${el.type}_${el.id}` as const;
|
|
14
14
|
|
|
15
15
|
const stringToTestElement = (str: string): TestElement => {
|
|
16
|
-
const [type, idStr] = str.split('_');
|
|
16
|
+
const [type, idStr] = str.split('_', 2);
|
|
17
17
|
|
|
18
18
|
return {
|
|
19
19
|
type: type ?? '',
|
package/src/collections/iset.mts
CHANGED
|
@@ -1,5 +1,9 @@
|
|
|
1
1
|
/* eslint-disable unicorn/prefer-iterator-to-array-at-end */
|
|
2
|
-
import {
|
|
2
|
+
import {
|
|
3
|
+
type FixedLengthTuple,
|
|
4
|
+
type ReadonlyRecord,
|
|
5
|
+
type WidenLiteral,
|
|
6
|
+
} from 'ts-type-forge';
|
|
3
7
|
import { asUint32 } from '../number/index.mjs';
|
|
4
8
|
import { unknownToString } from '../others/index.mjs';
|
|
5
9
|
import { type MapSetKeyType, type SizeType } from '../types.mjs';
|
|
@@ -460,7 +464,7 @@ type ISetInterface<K extends MapSetKeyType> = Readonly<{
|
|
|
460
464
|
*
|
|
461
465
|
* @returns An iterable iterator of entries.
|
|
462
466
|
*/
|
|
463
|
-
entries: () => IterableIterator<
|
|
467
|
+
entries: () => IterableIterator<FixedLengthTuple<2, K>>;
|
|
464
468
|
|
|
465
469
|
// Conversion
|
|
466
470
|
|
|
@@ -913,7 +917,7 @@ class ISetClass<K extends MapSetKeyType> implements ISet<K>, Iterable<K> {
|
|
|
913
917
|
}
|
|
914
918
|
|
|
915
919
|
/** @inheritdoc */
|
|
916
|
-
entries(): IterableIterator<
|
|
920
|
+
entries(): IterableIterator<FixedLengthTuple<2, K>> {
|
|
917
921
|
return this.#set.entries();
|
|
918
922
|
}
|
|
919
923
|
|
|
@@ -1,4 +1,9 @@
|
|
|
1
|
-
import {
|
|
1
|
+
import {
|
|
2
|
+
type DeepReadonly,
|
|
3
|
+
type FixedLengthTuple,
|
|
4
|
+
type MutableFixedLengthTuple,
|
|
5
|
+
type Primitive,
|
|
6
|
+
} from 'ts-type-forge';
|
|
2
7
|
import { expectType } from '../expect-type.mjs';
|
|
3
8
|
import { isString } from '../guard/index.mjs';
|
|
4
9
|
import { type MapSetKeyType } from '../types.mjs';
|
|
@@ -974,13 +979,11 @@ describe('ISet.entries', () => {
|
|
|
974
979
|
|
|
975
980
|
expectType<
|
|
976
981
|
typeof s0.entries,
|
|
977
|
-
() => IterableIterator<
|
|
982
|
+
() => IterableIterator<FixedLengthTuple<2, 1 | 2 | 3>>
|
|
978
983
|
>('<=');
|
|
979
984
|
|
|
980
|
-
const result: DeepReadonly<
|
|
981
|
-
s0.entries(),
|
|
982
|
-
(x) => [x[0], x[1]],
|
|
983
|
-
);
|
|
985
|
+
const result: DeepReadonly<MutableFixedLengthTuple<2, 1 | 2 | 3>[]> =
|
|
986
|
+
Array.from(s0.entries(), (x) => [x[0], x[1]]);
|
|
984
987
|
|
|
985
988
|
assert.deepStrictEqual(
|
|
986
989
|
result.toSorted((a, b) => a[0] - b[0]),
|
package/src/functional/match.mts
CHANGED
|
@@ -121,7 +121,7 @@ expectType<AllCasesCovered<'a' | 'b', Readonly<{ a: 1; b: 2; c: 3 }>>, false>(
|
|
|
121
121
|
'=',
|
|
122
122
|
);
|
|
123
123
|
|
|
124
|
-
expectType<AllCasesCovered<string,
|
|
124
|
+
expectType<AllCasesCovered<string, ReadonlyRecord<string, string>>, true>('=');
|
|
125
125
|
|
|
126
126
|
/**
|
|
127
127
|
* @template Case A union of string literal types.
|
|
@@ -152,9 +152,10 @@ expectType<
|
|
|
152
152
|
false
|
|
153
153
|
>('=');
|
|
154
154
|
|
|
155
|
-
expectType<
|
|
156
|
-
|
|
157
|
-
|
|
155
|
+
expectType<
|
|
156
|
+
IsLiteralUnionFullyCovered<string, ReadonlyRecord<string, string>>,
|
|
157
|
+
false
|
|
158
|
+
>('=');
|
|
158
159
|
|
|
159
160
|
expectType<
|
|
160
161
|
// eslint-disable-next-line @typescript-eslint/no-redundant-type-constituents
|
|
@@ -407,8 +407,11 @@ describe('Result test', () => {
|
|
|
407
407
|
|
|
408
408
|
describe('fromPromise', () => {
|
|
409
409
|
test('handles async functions that resolve', async () => {
|
|
410
|
-
const asyncFn = async (): Promise<number> =>
|
|
411
|
-
Promise.resolve()
|
|
410
|
+
const asyncFn = async (): Promise<number> => {
|
|
411
|
+
await Promise.resolve();
|
|
412
|
+
|
|
413
|
+
return 42;
|
|
414
|
+
};
|
|
412
415
|
|
|
413
416
|
const result = await Result.fromPromise(asyncFn());
|
|
414
417
|
|
|
@@ -1,4 +1,4 @@
|
|
|
1
|
-
import { type UnknownRecord } from 'ts-type-forge';
|
|
1
|
+
import { type ReadonlyRecord, type UnknownRecord } from 'ts-type-forge';
|
|
2
2
|
import { expectType } from '../expect-type.mjs';
|
|
3
3
|
import { hasKey } from './has-key.mjs';
|
|
4
4
|
import { isRecord } from './is-record.mjs';
|
|
@@ -31,13 +31,13 @@ describe(isRecord, () => {
|
|
|
31
31
|
describe('arrays are not records', () => {
|
|
32
32
|
test('type-level: arrays are not assignable to UnknownRecord', () => {
|
|
33
33
|
// Arrays have a number index signature but no string index signature,
|
|
34
|
-
// so they are assignable to `
|
|
34
|
+
// so they are assignable to `ReadonlyRecord<number, unknown>` but not to
|
|
35
35
|
// `UnknownRecord` (= `ReadonlyRecord<string, unknown>`).
|
|
36
|
-
expectType<readonly number[],
|
|
36
|
+
expectType<readonly number[], ReadonlyRecord<number, unknown>>('<=');
|
|
37
37
|
|
|
38
38
|
expectType<readonly number[], UnknownRecord>('!<=');
|
|
39
39
|
|
|
40
|
-
expectType<readonly string[],
|
|
40
|
+
expectType<readonly string[], ReadonlyRecord<number, unknown>>('<=');
|
|
41
41
|
|
|
42
42
|
expectType<readonly string[], UnknownRecord>('!<=');
|
|
43
43
|
|
|
@@ -157,7 +157,7 @@ describe(isNumber, () => {
|
|
|
157
157
|
|
|
158
158
|
assert.isTrue(isNumber(42));
|
|
159
159
|
|
|
160
|
-
assert.isTrue(isNumber(-
|
|
160
|
+
assert.isTrue(isNumber(-1.23));
|
|
161
161
|
|
|
162
162
|
assert.isTrue(isNumber(Number.NaN));
|
|
163
163
|
|
|
@@ -322,7 +322,7 @@ describe(isNotNumber, () => {
|
|
|
322
322
|
|
|
323
323
|
assert.isFalse(isNotNumber(42));
|
|
324
324
|
|
|
325
|
-
assert.isFalse(isNotNumber(-
|
|
325
|
+
assert.isFalse(isNotNumber(-1.23));
|
|
326
326
|
|
|
327
327
|
assert.isFalse(isNotNumber(Number.NaN));
|
|
328
328
|
|
|
@@ -12,7 +12,7 @@ describe('FiniteNumber test', () => {
|
|
|
12
12
|
|
|
13
13
|
expect(() => asFiniteNumber(-1)).not.toThrow();
|
|
14
14
|
|
|
15
|
-
expect(() => asFiniteNumber(
|
|
15
|
+
expect(() => asFiniteNumber(1.23)).not.toThrow();
|
|
16
16
|
|
|
17
17
|
expect(() => asFiniteNumber(-2.5)).not.toThrow();
|
|
18
18
|
|
|
@@ -59,7 +59,7 @@ describe('FiniteNumber test', () => {
|
|
|
59
59
|
|
|
60
60
|
assert.isTrue(FiniteNumber.is(-1));
|
|
61
61
|
|
|
62
|
-
assert.isTrue(FiniteNumber.is(
|
|
62
|
+
assert.isTrue(FiniteNumber.is(1.23));
|
|
63
63
|
|
|
64
64
|
assert.isTrue(FiniteNumber.is(-2.5));
|
|
65
65
|
|
|
@@ -13,7 +13,7 @@ describe('NonNegativeFiniteNumber test', () => {
|
|
|
13
13
|
|
|
14
14
|
expect(() => asNonNegativeFiniteNumber(1)).not.toThrow();
|
|
15
15
|
|
|
16
|
-
expect(() => asNonNegativeFiniteNumber(
|
|
16
|
+
expect(() => asNonNegativeFiniteNumber(1.23)).not.toThrow();
|
|
17
17
|
|
|
18
18
|
expect(() => asNonNegativeFiniteNumber(0.5)).not.toThrow();
|
|
19
19
|
|
|
@@ -71,7 +71,7 @@ describe('NonNegativeFiniteNumber test', () => {
|
|
|
71
71
|
|
|
72
72
|
assert.isTrue(isNonNegativeFiniteNumber(1));
|
|
73
73
|
|
|
74
|
-
assert.isTrue(isNonNegativeFiniteNumber(
|
|
74
|
+
assert.isTrue(isNonNegativeFiniteNumber(1.23));
|
|
75
75
|
|
|
76
76
|
assert.isTrue(isNonNegativeFiniteNumber(0.5));
|
|
77
77
|
|