ts-data-forge 13.0.0 → 14.0.1

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (93) hide show
  1. package/README.md +4 -3
  2. package/dist/array/impl/array-utils-creation.d.mts +2 -2
  3. package/dist/array/impl/array-utils-creation.d.mts.map +1 -1
  4. package/dist/array/impl/array-utils-creation.mjs.map +1 -1
  5. package/dist/array/impl/array-utils-length-bounded-array-cast.d.mts +56 -29
  6. package/dist/array/impl/array-utils-length-bounded-array-cast.d.mts.map +1 -1
  7. package/dist/array/impl/array-utils-length-bounded-array-cast.mjs +23 -15
  8. package/dist/array/impl/array-utils-length-bounded-array-cast.mjs.map +1 -1
  9. package/dist/array/impl/array-utils-length-bounded-array-guard.d.mts +126 -28
  10. package/dist/array/impl/array-utils-length-bounded-array-guard.d.mts.map +1 -1
  11. package/dist/array/impl/array-utils-length-bounded-array-guard.mjs +39 -5
  12. package/dist/array/impl/array-utils-length-bounded-array-guard.mjs.map +1 -1
  13. package/dist/array/impl/array-utils-length-bounded-tuple-cast.d.mts +167 -0
  14. package/dist/array/impl/array-utils-length-bounded-tuple-cast.d.mts.map +1 -0
  15. package/dist/array/impl/array-utils-length-bounded-tuple-cast.mjs +87 -0
  16. package/dist/array/impl/array-utils-length-bounded-tuple-cast.mjs.map +1 -0
  17. package/dist/array/impl/array-utils-length-bounded-tuple-guard.d.mts +135 -0
  18. package/dist/array/impl/array-utils-length-bounded-tuple-guard.d.mts.map +1 -0
  19. package/dist/array/impl/array-utils-length-bounded-tuple-guard.mjs +41 -0
  20. package/dist/array/impl/array-utils-length-bounded-tuple-guard.mjs.map +1 -0
  21. package/dist/array/impl/array-utils-modification.d.mts +45 -18
  22. package/dist/array/impl/array-utils-modification.d.mts.map +1 -1
  23. package/dist/array/impl/array-utils-modification.mjs.map +1 -1
  24. package/dist/array/impl/array-utils-reducing-value.d.mts +3 -3
  25. package/dist/array/impl/array-utils-reducing-value.d.mts.map +1 -1
  26. package/dist/array/impl/array-utils-reducing-value.mjs +1 -1
  27. package/dist/array/impl/array-utils-reducing-value.mjs.map +1 -1
  28. package/dist/array/impl/array-utils-slice-clamped.d.mts.map +1 -1
  29. package/dist/array/impl/array-utils-slice-clamped.mjs.map +1 -1
  30. package/dist/array/impl/array-utils-slicing.d.mts +11 -11
  31. package/dist/array/impl/array-utils-slicing.d.mts.map +1 -1
  32. package/dist/array/impl/array-utils-slicing.mjs +1 -1
  33. package/dist/array/impl/array-utils-slicing.mjs.map +1 -1
  34. package/dist/array/impl/array-utils-transformation.d.mts +36 -19
  35. package/dist/array/impl/array-utils-transformation.d.mts.map +1 -1
  36. package/dist/array/impl/array-utils-transformation.mjs +2 -2
  37. package/dist/array/impl/array-utils-transformation.mjs.map +1 -1
  38. package/dist/array/impl/array-utils-validation.d.mts +2 -124
  39. package/dist/array/impl/array-utils-validation.d.mts.map +1 -1
  40. package/dist/array/impl/array-utils-validation.mjs +1 -7
  41. package/dist/array/impl/array-utils-validation.mjs.map +1 -1
  42. package/dist/array/impl/index.d.mts +2 -0
  43. package/dist/array/impl/index.d.mts.map +1 -1
  44. package/dist/array/impl/index.mjs +5 -3
  45. package/dist/array/impl/index.mjs.map +1 -1
  46. package/dist/collections/imap.d.mts +1 -1
  47. package/dist/collections/imap.mjs.map +1 -1
  48. package/dist/collections/iset-mapped.d.mts +2 -2
  49. package/dist/collections/iset-mapped.d.mts.map +1 -1
  50. package/dist/collections/iset-mapped.mjs.map +1 -1
  51. package/dist/collections/iset.d.mts +2 -2
  52. package/dist/collections/iset.d.mts.map +1 -1
  53. package/dist/collections/iset.mjs.map +1 -1
  54. package/dist/number/num-arithmetic-types.d.mts +2 -2
  55. package/dist/number/num-arithmetic-types.d.mts.map +1 -1
  56. package/dist/object/object.mjs +1 -1
  57. package/dist/string/str.d.mts +44 -44
  58. package/dist/string/str.d.mts.map +1 -1
  59. package/dist/string/str.mjs +17 -17
  60. package/dist/string/str.mjs.map +1 -1
  61. package/package.json +4 -5
  62. package/src/array/impl/array-utils-creation.mts +7 -3
  63. package/src/array/impl/array-utils-creation.test.mts +10 -8
  64. package/src/array/impl/array-utils-length-bounded-array-cast.mts +88 -50
  65. package/src/array/impl/array-utils-length-bounded-array-cast.test.mts +40 -13
  66. package/src/array/impl/array-utils-length-bounded-array-guard.mts +248 -39
  67. package/src/array/impl/array-utils-length-bounded-array-guard.test.mts +135 -24
  68. package/src/array/impl/array-utils-length-bounded-tuple-cast.mts +364 -0
  69. package/src/array/impl/array-utils-length-bounded-tuple-cast.test.mts +207 -0
  70. package/src/array/impl/array-utils-length-bounded-tuple-guard.mts +266 -0
  71. package/src/array/impl/array-utils-length-bounded-tuple-guard.test.mts +441 -0
  72. package/src/array/impl/array-utils-length-constraint-preservation.test.mts +287 -0
  73. package/src/array/impl/array-utils-modification.mts +84 -61
  74. package/src/array/impl/array-utils-modification.test.mts +145 -6
  75. package/src/array/impl/array-utils-reducing-value.mts +5 -5
  76. package/src/array/impl/array-utils-shape-invariants.test.mts +284 -0
  77. package/src/array/impl/array-utils-slice-clamped.mts +2 -1
  78. package/src/array/impl/array-utils-slicing.mts +65 -43
  79. package/src/array/impl/array-utils-slicing.test.mts +55 -0
  80. package/src/array/impl/array-utils-transformation.mts +91 -37
  81. package/src/array/impl/array-utils-transformation.test.mts +96 -23
  82. package/src/array/impl/array-utils-validation.mts +2 -162
  83. package/src/array/impl/array-utils-validation.test.mts +0 -371
  84. package/src/array/impl/index.mts +2 -0
  85. package/src/collections/imap.mts +1 -1
  86. package/src/collections/iset-mapped.mts +7 -3
  87. package/src/collections/iset.mts +7 -3
  88. package/src/collections/iset.test.mts +9 -6
  89. package/src/collections/queue.test.mts +1 -1
  90. package/src/number/num-arithmetic-types.mts +8 -4
  91. package/src/number/num.test.mts +2 -2
  92. package/src/string/str.mts +69 -69
  93. package/src/string/str.test.mts +40 -40
@@ -1,6 +1,14 @@
1
- import { type FixedLengthTuple, type NonEmptyArray } from 'ts-type-forge';
1
+ import {
2
+ type BoundedLengthArray,
3
+ type FixedLengthArray,
4
+ type FixedLengthTuple,
5
+ type MinLengthArray,
6
+ type NonEmptyArray,
7
+ } from 'ts-type-forge';
2
8
  import { expectType } from '../../expect-type.mjs';
3
9
  import { asUint32 } from '../../number/index.mjs';
10
+ import { type SizeType } from '../../types.mjs';
11
+ import { asMinLengthArray } from './array-utils-length-bounded-array-cast.mjs';
4
12
  import {
5
13
  set,
6
14
  toFilled,
@@ -18,7 +26,7 @@ describe('Arr modifications', () => {
18
26
 
19
27
  const result = toUpdated(xs, 1, (x) => x + 2);
20
28
 
21
- expectType<typeof result, FixedLengthTuple<3, number>>('=');
29
+ expectType<typeof result, readonly [1, number, 3]>('=');
22
30
 
23
31
  test('case 1', () => {
24
32
  assert.deepStrictEqual(result, [1, 4, 3]);
@@ -236,6 +244,45 @@ describe('Arr modifications', () => {
236
244
 
237
245
  assert.deepStrictEqual(result, [0, 0, 0]);
238
246
  });
247
+
248
+ test('should preserve the tuple shape under a generic parameter', () => {
249
+ // A caller that is itself generic over the tuple cannot resolve a
250
+ // conditional return type, so the unbranded overload has to state the
251
+ // homomorphic mapping directly.
252
+ const blank = <const T extends readonly number[]>(
253
+ xs: T,
254
+ ): Readonly<{ [K in keyof T]: 0 }> => toFilled(xs, 0);
255
+
256
+ const result = blank([1, 2, 3] as const);
257
+
258
+ expectType<typeof result, FixedLengthTuple<3, 0>>('=');
259
+
260
+ assert.deepStrictEqual<FixedLengthTuple<3, 0>>(result, [0, 0, 0]);
261
+ });
262
+
263
+ test('should preserve the tuple shape under a generic parameter when curried', () => {
264
+ const blank = <const T extends readonly number[]>(
265
+ xs: T,
266
+ ): Readonly<{ [K in keyof T]: 0 }> => toFilled(0)(xs);
267
+
268
+ const result = blank([1, 2] as const);
269
+
270
+ expectType<typeof result, FixedLengthTuple<2, 0>>('=');
271
+
272
+ assert.deepStrictEqual<FixedLengthTuple<2, 0>>(result, [0, 0]);
273
+ });
274
+
275
+ test('should keep the length brand of a branded array', () => {
276
+ const lower: MinLengthArray<2, number> = asMinLengthArray(2, [1, 2, 3]);
277
+
278
+ const filled = toFilled(lower, 0);
279
+
280
+ expectType<typeof filled, MinLengthArray<2, 0>>('~=');
281
+
282
+ // The brand is not part of the runtime value, so compare structurally —
283
+ // the type argument is what widens it, no intermediate binding needed.
284
+ assert.deepStrictEqual<readonly number[]>(filled, [0, 0, 0]);
285
+ });
239
286
  });
240
287
 
241
288
  describe(toRangeFilled, () => {
@@ -263,6 +310,18 @@ describe('Arr modifications', () => {
263
310
  assert.deepStrictEqual(result, [1, 2, 3]);
264
311
  });
265
312
 
313
+ test('should preserve the tuple shape under a generic parameter', () => {
314
+ const blankMiddle = <const T extends readonly number[]>(
315
+ xs: T,
316
+ ): Readonly<{ [K in keyof T]: number }> => toRangeFilled(xs, 0, [1, 2]);
317
+
318
+ const result = blankMiddle([1, 2, 3] as const);
319
+
320
+ expectType<typeof result, FixedLengthTuple<3, number>>('=');
321
+
322
+ assert.deepStrictEqual<FixedLengthTuple<3, number>>(result, [1, 0, 3]);
323
+ });
324
+
266
325
  test('should clamp range to array bounds', () => {
267
326
  const arr = [1, 2, 3] as readonly number[];
268
327
 
@@ -305,7 +364,7 @@ describe('Arr modifications', () => {
305
364
  describe(set, () => {
306
365
  const result = set([1, 2, 3], 1, 4);
307
366
 
308
- expectType<typeof result, readonly [1 | 4, 2 | 4, 3 | 4]>('=');
367
+ expectType<typeof result, readonly [1, 4, 3]>('=');
309
368
 
310
369
  test('case 1', () => {
311
370
  assert.deepStrictEqual(result, [1, 4, 3]);
@@ -316,9 +375,7 @@ describe('Arr modifications', () => {
316
375
 
317
376
  const withString = set(nums, 1, 'two');
318
377
 
319
- expectType<typeof withString, readonly [1 | 'two', 2 | 'two', 3 | 'two']>(
320
- '=',
321
- );
378
+ expectType<typeof withString, readonly [1, 'two', 3]>('=');
322
379
 
323
380
  assert.deepStrictEqual(withString, [1, 'two', 3]);
324
381
  });
@@ -348,3 +405,85 @@ describe('Arr modifications', () => {
348
405
  });
349
406
  });
350
407
  });
408
+
409
+ /* `Arr.set` / `Arr.toUpdated` across the input shapes the positional
410
+ `ConstrainedList.SetAt` annotation has to cope with; the suite previously
411
+ covered only the plain-tuple case. Written as an unexported-by-value function
412
+ so the assertions get real overload resolution — it is never called, and the
413
+ branded shapes have no runtime representation to build. */
414
+ export const setTypeChecks = (
415
+ shapeTuple: readonly [1, 2, 3],
416
+ shapeArray: readonly number[],
417
+ shapeMutableArray: readonly number[],
418
+ shapeBranded: MinLengthArray<3, string>,
419
+ shapeBounded: BoundedLengthArray<2, 5, string>,
420
+ shapeFixed: FixedLengthArray<3, string>,
421
+ shapeBrandedTuple: MinLengthArray<3, number> & readonly [1, 2, 3, 4, 5],
422
+ unionIndex: 0 | 2,
423
+ widenedIndex: SizeType.ArgArr,
424
+ ): void => {
425
+ /* Plain tuple — the point of the positional form: only the touched position
426
+ changes, the rest keep their literal types. */
427
+ const _onTuple = set(shapeTuple, 1, 'x');
428
+
429
+ expectType<typeof _onTuple, readonly [1, 'x', 3]>('=');
430
+
431
+ // Plain array: no positions to pin, so the element type widens.
432
+ const _onArray = set(shapeArray, widenedIndex, 'x');
433
+
434
+ expectType<typeof _onArray, readonly (number | 'x')[]>('=');
435
+
436
+ // A mutable array is not brand-carrying, and behaves like the readonly one.
437
+ const _onMutable = set(shapeMutableArray, widenedIndex, 'x');
438
+
439
+ expectType<typeof _onMutable, readonly (number | 'x')[]>('=');
440
+
441
+ /* Branded with no exact tuple: the length constraint survives, and with no
442
+ positions to pin the element type absorbs the new value. */
443
+ const _onBranded = set(shapeBranded, 1, 'x');
444
+
445
+ expectType<typeof _onBranded, MinLengthArray<3, string>>('<=');
446
+
447
+ const _onBounded = set(shapeBounded, 1, 'x');
448
+
449
+ expectType<typeof _onBounded, BoundedLengthArray<2, 5, string>>('<=');
450
+
451
+ // Fixed length: brand *and* exact positions both survive.
452
+ const _onFixed = set(shapeFixed, 1, 'x');
453
+
454
+ expectType<
455
+ typeof _onFixed,
456
+ FixedLengthArray<3, string> & readonly [string, 'x', string]
457
+ >('~=');
458
+
459
+ /* Brand intersected with an exact tuple — the shape `Arr.isMinLengthArray`
460
+ produces. All three survive: the brand, the exact length, and the
461
+ positions, the last as a structural conjunct beside the widened one. */
462
+ const _onBrandedTuple = set(shapeBrandedTuple, 1, 'x');
463
+
464
+ expectType<(typeof _onBrandedTuple)[0], 1>('=');
465
+
466
+ expectType<(typeof _onBrandedTuple)[1], 'x'>('=');
467
+
468
+ expectType<(typeof _onBrandedTuple)[4], 5>('=');
469
+
470
+ expectType<(typeof _onBrandedTuple)['length'], 5>('=');
471
+
472
+ /* A union index must NOT be replaced positionally. The call sets one of the
473
+ positions and never both, so `readonly ['x', 2, 'x']` — what the upstream
474
+ `List.SetAt` used to answer for a union index — is a type that no possible
475
+ result satisfies. ts-type-forge 9.1.1 widens the candidate positions
476
+ instead, and only those: index 1 is not among `0 | 2`, so it stays `2`. */
477
+ const _onUnionIndex = set(shapeTuple, unionIndex, 'x');
478
+
479
+ expectType<typeof _onUnionIndex, readonly [1 | 'x', 2, 3 | 'x']>('=');
480
+
481
+ // `toUpdated` follows `set`, with the updater's return as the value.
482
+ const _updatedTuple = toUpdated(shapeTuple, 1, (x) => x + 1);
483
+
484
+ expectType<typeof _updatedTuple, readonly [1, number, 3]>('=');
485
+
486
+ const _updatedUnion = toUpdated(shapeTuple, unionIndex, (x) => x + 1);
487
+
488
+ expectType<typeof _updatedUnion, readonly [number, 2, number]>('=');
489
+ };
@@ -11,7 +11,7 @@ import {
11
11
  type SizeType,
12
12
  type Some,
13
13
  } from '../../types.mjs';
14
- import { isNonEmpty } from './array-utils-validation.mjs';
14
+ import { isNonEmpty } from './array-utils-length-bounded-array-guard.mjs';
15
15
 
16
16
  /**
17
17
  * Finds the minimum value in an array.
@@ -543,22 +543,22 @@ export function sum(array: readonly number[]): number {
543
543
  */
544
544
  export function sumBy(array: readonly [], mapFn: (item: unknown) => number): 0;
545
545
 
546
- export function sumBy<N extends Uint, E>(
546
+ export function sumBy<E, N extends Uint>(
547
547
  array: readonly E[],
548
548
  mapFn: (item: E) => N,
549
549
  ): Uint;
550
550
 
551
- export function sumBy<N extends Int, E>(
551
+ export function sumBy<E, N extends Int>(
552
552
  array: readonly E[],
553
553
  mapFn: (item: E) => N,
554
554
  ): Int;
555
555
 
556
- export function sumBy<N extends number, E>(
556
+ export function sumBy<E, N extends number>(
557
557
  array: readonly E[],
558
558
  mapFn: (item: E) => N,
559
559
  ): number;
560
560
 
561
- export function sumBy<N extends number, E>(
561
+ export function sumBy<E, N extends number>(
562
562
  array: readonly E[],
563
563
  mapFn: (item: E) => N,
564
564
  ): number {
@@ -0,0 +1,284 @@
1
+ import {
2
+ type BoundedLengthArray,
3
+ type FixedLengthArray,
4
+ type FixedLengthTuple,
5
+ type MaxLengthArray,
6
+ type MaxLengthOf,
7
+ type MinLengthArray,
8
+ type MinLengthOf,
9
+ } from 'ts-type-forge';
10
+ import { expectType } from '../../expect-type.mjs';
11
+ import {
12
+ asBoundedLengthArray,
13
+ asFixedLengthArray,
14
+ asMaxLengthArray,
15
+ asMinLengthArray,
16
+ } from './array-utils-length-bounded-array-cast.mjs';
17
+ import { toFilled, toRangeFilled } from './array-utils-modification.mjs';
18
+ import { map, toReversed, toSorted } from './array-utils-transformation.mjs';
19
+
20
+ /**
21
+ * Invariant-based coverage for the array -> array transforms.
22
+ *
23
+ * Rather than pinning an expected type for every (brand x shape x context)
24
+ * combination — which is a large table whose expected side is easy to derive
25
+ * from the implementation and therefore prove nothing — this asserts the small
26
+ * set of properties every shape-preserving transform must satisfy, across a
27
+ * fixed list of sample inputs that covers the axes.
28
+ *
29
+ * The properties are:
30
+ *
31
+ * 1. **Length** — the result reports the same `length` as the input.
32
+ * 2. **Bounds** — a length brand on the input survives onto the result.
33
+ * 3. **Generic context** — from a caller that is itself generic over the
34
+ * array, the result is assignable to the caller's own homomorphic mapping.
35
+ *
36
+ * Property 3 is the one with no coverage before this file, and is what the
37
+ * `Arr.map` regression fixed in this PR violated: a conditional return type a
38
+ * generic `Ar` cannot decide stays deferred, and its brand-carrying branch is
39
+ * then not assignable to the caller's mapping.
40
+ */
41
+
42
+ // The sample inputs. Between them they cover the axes: empty / singleton /
43
+ // fixed tuple / non-empty tuple / unbounded array on the shape axis, and
44
+ // unbranded / lower bound / upper bound / both / exact on the brand axis.
45
+ const emptyTuple: readonly [] = [] as const;
46
+
47
+ const singleton: readonly [1] = [1] as const;
48
+
49
+ const fixedTuple: readonly [1, 2, 3] = [1, 2, 3] as const;
50
+
51
+ const nonEmptyTuple: readonly [1, ...number[]] = [1, 2, 3] as const;
52
+
53
+ const plainArray: readonly number[] = [1, 2, 3] as const;
54
+
55
+ const lowerBounded: MinLengthArray<2, number> = asMinLengthArray(2, [1, 2, 3]);
56
+
57
+ const upperBounded: MaxLengthArray<5, number> = asMaxLengthArray(5, [1, 2, 3]);
58
+
59
+ const bounded: BoundedLengthArray<2, 5, number> = asBoundedLengthArray(
60
+ 2,
61
+ 5,
62
+ [1, 2, 3],
63
+ );
64
+
65
+ const exactLength: FixedLengthArray<3, number> = asFixedLengthArray(
66
+ 3,
67
+ [1, 2, 3],
68
+ );
69
+
70
+ /**
71
+ * Property 3 — generic context.
72
+ *
73
+ * Each of these is an assertion by construction: the annotated return type is
74
+ * the caller's own homomorphic mapping, so the declaration only compiles while
75
+ * the transform reports something assignable to it. There is nothing to run.
76
+ *
77
+ * Only the transforms that satisfy this today are listed. `toSorted`,
78
+ * `toSortedBy`, `toReversed`, `toUpdated`, `tail`, `butLast` and `zip` do not
79
+ * yet — see the PR description for why each one resists the same fix. Add them
80
+ * here as they are fixed; that is what makes this list the coverage record.
81
+ */
82
+
83
+ const mapInGenericContext = <const T extends readonly number[]>(
84
+ xs: T,
85
+ ): Readonly<{ [K in keyof T]: string }> => map(xs, String);
86
+
87
+ const mapCurriedInGenericContext = <const T extends readonly number[]>(
88
+ xs: T,
89
+ ): Readonly<{ [K in keyof T]: string }> => map(String)(xs);
90
+
91
+ const toFilledInGenericContext = <const T extends readonly number[]>(
92
+ xs: T,
93
+ ): Readonly<{ [K in keyof T]: 0 }> => toFilled(xs, 0);
94
+
95
+ const toFilledCurriedInGenericContext = <const T extends readonly number[]>(
96
+ xs: T,
97
+ ): Readonly<{ [K in keyof T]: 0 }> => toFilled(0)(xs);
98
+
99
+ const toRangeFilledInGenericContext = <const T extends readonly number[]>(
100
+ xs: T,
101
+ ): Readonly<{ [K in keyof T]: number }> => toRangeFilled(xs, 0, [0, 1]);
102
+
103
+ describe('Arr shape invariants', () => {
104
+ // The type-level assertions below are checked by `tsc`; this pins the same
105
+ // property at runtime, so a transform that silently drops or adds an element
106
+ // fails here even where the type says otherwise.
107
+ test('every shape-preserving transform keeps the length at runtime', () => {
108
+ const input = [3, 1, 2] as const;
109
+
110
+ assert.deepStrictEqual<FixedLengthTuple<5, 3>>(
111
+ [
112
+ map(input, String).length,
113
+ toFilled(input, 0).length,
114
+ toRangeFilled(input, 0, [1, 2]).length,
115
+ toSorted(input).length,
116
+ toReversed(input).length,
117
+ ] as const,
118
+ [3, 3, 3, 3, 3] as const,
119
+ );
120
+ });
121
+
122
+ test('the generic-context callers agree with their annotations', () => {
123
+ const input = [3, 1, 2] as const;
124
+
125
+ assert.deepStrictEqual<FixedLengthTuple<3, string>>(
126
+ mapInGenericContext(input),
127
+ ['3', '1', '2'],
128
+ );
129
+
130
+ assert.deepStrictEqual<FixedLengthTuple<3, string>>(
131
+ mapCurriedInGenericContext(input),
132
+ ['3', '1', '2'],
133
+ );
134
+
135
+ assert.deepStrictEqual<FixedLengthTuple<3, 0>>(
136
+ toFilledInGenericContext(input),
137
+ [0, 0, 0],
138
+ );
139
+
140
+ assert.deepStrictEqual<FixedLengthTuple<3, 0>>(
141
+ toFilledCurriedInGenericContext(input),
142
+ [0, 0, 0],
143
+ );
144
+
145
+ assert.deepStrictEqual<FixedLengthTuple<3, number>>(
146
+ toRangeFilledInGenericContext(input),
147
+ [0, 1, 2],
148
+ );
149
+ });
150
+ });
151
+
152
+ // `map` — property 1 (length) across every unbranded sample.
153
+ {
154
+ const _a = map(emptyTuple, String);
155
+
156
+ const _b = map(singleton, String);
157
+
158
+ const _c = map(fixedTuple, String);
159
+
160
+ const _d = map(nonEmptyTuple, String);
161
+
162
+ const _e = map(plainArray, String);
163
+
164
+ expectType<(typeof _a)['length'], (typeof emptyTuple)['length']>('=');
165
+
166
+ expectType<(typeof _b)['length'], (typeof singleton)['length']>('=');
167
+
168
+ expectType<(typeof _c)['length'], (typeof fixedTuple)['length']>('=');
169
+
170
+ expectType<(typeof _d)['length'], (typeof nonEmptyTuple)['length']>('=');
171
+
172
+ expectType<(typeof _e)['length'], (typeof plainArray)['length']>('=');
173
+ }
174
+
175
+ // `map` — property 2 (bounds) across every branded sample.
176
+ {
177
+ const _a = map(lowerBounded, String);
178
+
179
+ const _b = map(upperBounded, String);
180
+
181
+ const _c = map(bounded, String);
182
+
183
+ const _d = map(exactLength, String);
184
+
185
+ expectType<MinLengthOf<typeof _a>, MinLengthOf<typeof lowerBounded>>('=');
186
+
187
+ expectType<MaxLengthOf<typeof _b>, MaxLengthOf<typeof upperBounded>>('=');
188
+
189
+ expectType<MinLengthOf<typeof _c>, MinLengthOf<typeof bounded>>('=');
190
+
191
+ expectType<MaxLengthOf<typeof _c>, MaxLengthOf<typeof bounded>>('=');
192
+
193
+ expectType<MinLengthOf<typeof _d>, MinLengthOf<typeof exactLength>>('=');
194
+
195
+ expectType<MaxLengthOf<typeof _d>, MaxLengthOf<typeof exactLength>>('=');
196
+ }
197
+
198
+ // `toFilled` — properties 1 and 2.
199
+ {
200
+ const _a = toFilled(fixedTuple, 0);
201
+
202
+ const _b = toFilled(nonEmptyTuple, 0);
203
+
204
+ const _c = toFilled(plainArray, 0);
205
+
206
+ const _d = toFilled(lowerBounded, 0);
207
+
208
+ const _e = toFilled(bounded, 0);
209
+
210
+ expectType<(typeof _a)['length'], (typeof fixedTuple)['length']>('=');
211
+
212
+ expectType<(typeof _b)['length'], (typeof nonEmptyTuple)['length']>('=');
213
+
214
+ expectType<(typeof _c)['length'], (typeof plainArray)['length']>('=');
215
+
216
+ expectType<MinLengthOf<typeof _d>, MinLengthOf<typeof lowerBounded>>('=');
217
+
218
+ expectType<MinLengthOf<typeof _e>, MinLengthOf<typeof bounded>>('=');
219
+
220
+ expectType<MaxLengthOf<typeof _e>, MaxLengthOf<typeof bounded>>('=');
221
+ }
222
+
223
+ // `toRangeFilled` — properties 1 and 2.
224
+ {
225
+ const _a = toRangeFilled(fixedTuple, 0, [1, 2]);
226
+
227
+ const _b = toRangeFilled(plainArray, 0, [0, 1]);
228
+
229
+ const _c = toRangeFilled(lowerBounded, 0, [0, 1]);
230
+
231
+ expectType<(typeof _a)['length'], (typeof fixedTuple)['length']>('=');
232
+
233
+ expectType<(typeof _b)['length'], (typeof plainArray)['length']>('=');
234
+
235
+ expectType<MinLengthOf<typeof _c>, MinLengthOf<typeof lowerBounded>>('=');
236
+ }
237
+
238
+ // `toSorted` — properties 1 and 2. Sorting neither adds nor drops elements, so
239
+ // it is held to the same contract even though it is not fixed for property 3.
240
+ {
241
+ const _a = toSorted(fixedTuple);
242
+
243
+ const _b = toSorted(nonEmptyTuple);
244
+
245
+ const _c = toSorted(plainArray);
246
+
247
+ const _d = toSorted(lowerBounded);
248
+
249
+ const _e = toSorted(bounded);
250
+
251
+ expectType<(typeof _a)['length'], (typeof fixedTuple)['length']>('=');
252
+
253
+ expectType<(typeof _b)['length'], (typeof nonEmptyTuple)['length']>('=');
254
+
255
+ expectType<(typeof _c)['length'], (typeof plainArray)['length']>('=');
256
+
257
+ expectType<MinLengthOf<typeof _d>, MinLengthOf<typeof lowerBounded>>('=');
258
+
259
+ expectType<MinLengthOf<typeof _e>, MinLengthOf<typeof bounded>>('=');
260
+
261
+ expectType<MaxLengthOf<typeof _e>, MaxLengthOf<typeof bounded>>('=');
262
+ }
263
+
264
+ // `toReversed` — properties 1 and 2. Reversal permutes, so the length and the
265
+ // bounds are exactly what must not move.
266
+ {
267
+ const _a = toReversed(fixedTuple);
268
+
269
+ const _b = toReversed(plainArray);
270
+
271
+ const _c = toReversed(lowerBounded);
272
+
273
+ const _d = toReversed(bounded);
274
+
275
+ expectType<(typeof _a)['length'], (typeof fixedTuple)['length']>('=');
276
+
277
+ expectType<(typeof _b)['length'], (typeof plainArray)['length']>('=');
278
+
279
+ expectType<MinLengthOf<typeof _c>, MinLengthOf<typeof lowerBounded>>('=');
280
+
281
+ expectType<MinLengthOf<typeof _d>, MinLengthOf<typeof bounded>>('=');
282
+
283
+ expectType<MaxLengthOf<typeof _d>, MaxLengthOf<typeof bounded>>('=');
284
+ }
@@ -1,3 +1,4 @@
1
+ import { type FixedLengthTuple } from 'ts-type-forge';
1
2
  import { Num } from '../../number/index.mjs';
2
3
  import { type ArgArrayIndexWithNegative, type SizeType } from '../../types.mjs';
3
4
 
@@ -36,7 +37,7 @@ export function sliceClamped<E>(
36
37
  SizeType.ArgArrWithNegative,
37
38
  SizeType.ArgArrWithNegative,
38
39
  ]
39
- | readonly [SizeType.ArgArrWithNegative, SizeType.ArgArrWithNegative]
40
+ | FixedLengthTuple<2, SizeType.ArgArrWithNegative>
40
41
  ): readonly E[] | ((array: readonly E[]) => readonly E[]) {
41
42
  switch (args.length) {
42
43
  case 3: