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,4 +1,6 @@
1
1
  import {
2
+ type ConstrainedList,
3
+ type HasLengthConstraint,
2
4
  type List,
3
5
  type NonEmptyArray,
4
6
  type NonEmptyTuple,
@@ -6,9 +8,9 @@ import {
6
8
  } from 'ts-type-forge';
7
9
  import { Uint32 } from '../../number/index.mjs';
8
10
  import { type SizeType } from '../../types.mjs';
11
+ import { isEmpty } from './array-utils-length-bounded-array-guard.mjs';
9
12
  import { size } from './array-utils-size.mjs';
10
13
  import { sliceClamped } from './array-utils-slice-clamped.mjs';
11
- import { isEmpty } from './array-utils-validation.mjs';
12
14
 
13
15
  /**
14
16
  * Returns all elements of an array except the first one.
@@ -41,9 +43,9 @@ import { isEmpty } from './array-utils-validation.mjs';
41
43
  */
42
44
  export const tail = <const Ar extends readonly unknown[]>(
43
45
  array: Ar,
44
- ): List.Tail<Ar> =>
46
+ ): ConstrainedList.Tail<Ar> =>
45
47
  // eslint-disable-next-line total-functions/no-unsafe-type-assertion
46
- array.slice(1) as unknown as List.Tail<Ar>;
48
+ array.slice(1) as unknown as ConstrainedList.Tail<Ar>;
47
49
 
48
50
  /**
49
51
  * Returns all elements of an array except the last one.
@@ -62,9 +64,11 @@ export const tail = <const Ar extends readonly unknown[]>(
62
64
  */
63
65
  export const butLast = <const Ar extends readonly unknown[]>(
64
66
  array: Ar,
65
- ): List.ButLast<Ar> =>
67
+ ): ConstrainedList.ButLast<Ar> =>
66
68
  // eslint-disable-next-line total-functions/no-unsafe-type-assertion
67
- (isEmpty(array) ? [] : array.slice(0, -1)) as unknown as List.ButLast<Ar>;
69
+ (isEmpty(array)
70
+ ? []
71
+ : array.slice(0, -1)) as unknown as ConstrainedList.ButLast<Ar>;
68
72
 
69
73
  /**
70
74
  * Takes the first N elements from an array.
@@ -89,25 +93,29 @@ export function take<
89
93
  >(
90
94
  array: Ar,
91
95
  num: N,
92
- ): N extends StructuralPrefixLength
93
- ? List.Take<N, Ar>
94
- : N extends SizeType.ArgArrPositive
95
- ? Ar extends NonEmptyTuple<unknown>
96
- ? NonEmptyArray<Ar[number]>
97
- : readonly Ar[number][]
98
- : readonly Ar[number][];
96
+ ): HasLengthConstraint<Ar> extends true
97
+ ? readonly Ar[number][]
98
+ : N extends StructuralPrefixLength
99
+ ? List.Take<N, Ar>
100
+ : N extends SizeType.ArgArrPositive
101
+ ? Ar extends NonEmptyTuple<unknown>
102
+ ? NonEmptyArray<Ar[number]>
103
+ : readonly Ar[number][]
104
+ : readonly Ar[number][];
99
105
 
100
106
  export function take<N extends SizeType.ArgArr>(
101
107
  num: N,
102
108
  ): <const Ar extends readonly unknown[]>(
103
109
  array: Ar,
104
- ) => N extends StructuralPrefixLength
105
- ? List.Take<N, Ar>
106
- : N extends SizeType.ArgArrPositive
107
- ? Ar extends NonEmptyTuple<unknown>
108
- ? NonEmptyArray<Ar[number]>
109
- : readonly Ar[number][]
110
- : readonly Ar[number][];
110
+ ) => HasLengthConstraint<Ar> extends true
111
+ ? readonly Ar[number][]
112
+ : N extends StructuralPrefixLength
113
+ ? List.Take<N, Ar>
114
+ : N extends SizeType.ArgArrPositive
115
+ ? Ar extends NonEmptyTuple<unknown>
116
+ ? NonEmptyArray<Ar[number]>
117
+ : readonly Ar[number][]
118
+ : readonly Ar[number][];
111
119
 
112
120
  export function take<E>(
113
121
  ...args:
@@ -151,25 +159,29 @@ export function takeLast<
151
159
  >(
152
160
  array: Ar,
153
161
  num: N,
154
- ): N extends StructuralPrefixLength
155
- ? List.TakeLast<N, Ar>
156
- : N extends SizeType.ArgArrPositive
157
- ? Ar extends NonEmptyTuple<unknown>
158
- ? NonEmptyArray<Ar[number]>
159
- : readonly Ar[number][]
160
- : readonly Ar[number][];
162
+ ): HasLengthConstraint<Ar> extends true
163
+ ? readonly Ar[number][]
164
+ : N extends StructuralPrefixLength
165
+ ? List.TakeLast<N, Ar>
166
+ : N extends SizeType.ArgArrPositive
167
+ ? Ar extends NonEmptyTuple<unknown>
168
+ ? NonEmptyArray<Ar[number]>
169
+ : readonly Ar[number][]
170
+ : readonly Ar[number][];
161
171
 
162
172
  export function takeLast<N extends SizeType.ArgArr>(
163
173
  num: N,
164
174
  ): <const Ar extends readonly unknown[]>(
165
175
  array: Ar,
166
- ) => N extends StructuralPrefixLength
167
- ? List.TakeLast<N, Ar>
168
- : N extends SizeType.ArgArrPositive
169
- ? Ar extends NonEmptyTuple<unknown>
170
- ? NonEmptyArray<Ar[number]>
171
- : readonly Ar[number][]
172
- : readonly Ar[number][];
176
+ ) => HasLengthConstraint<Ar> extends true
177
+ ? readonly Ar[number][]
178
+ : N extends StructuralPrefixLength
179
+ ? List.TakeLast<N, Ar>
180
+ : N extends SizeType.ArgArrPositive
181
+ ? Ar extends NonEmptyTuple<unknown>
182
+ ? NonEmptyArray<Ar[number]>
183
+ : readonly Ar[number][]
184
+ : readonly Ar[number][];
173
185
 
174
186
  export function takeLast<E>(
175
187
  ...args:
@@ -214,16 +226,22 @@ export function skip<
214
226
  >(
215
227
  array: Ar,
216
228
  num: N,
217
- ): N extends StructuralPrefixLength ? List.Skip<N, Ar> : readonly Ar[number][];
229
+ ): HasLengthConstraint<Ar> extends true
230
+ ? readonly Ar[number][]
231
+ : N extends StructuralPrefixLength
232
+ ? List.Skip<N, Ar>
233
+ : readonly Ar[number][];
218
234
 
219
235
  // curried version
220
236
  export function skip<N extends SizeType.ArgArr>(
221
237
  num: N,
222
238
  ): <const Ar extends readonly unknown[]>(
223
239
  array: Ar,
224
- ) => N extends StructuralPrefixLength
225
- ? List.Skip<N, Ar>
226
- : readonly Ar[number][];
240
+ ) => HasLengthConstraint<Ar> extends true
241
+ ? readonly Ar[number][]
242
+ : N extends StructuralPrefixLength
243
+ ? List.Skip<N, Ar>
244
+ : readonly Ar[number][];
227
245
 
228
246
  export function skip<E>(
229
247
  ...args: readonly [readonly E[], SizeType.ArgArr] | readonly [SizeType.ArgArr]
@@ -265,18 +283,22 @@ export function skipLast<
265
283
  >(
266
284
  array: Ar,
267
285
  num: N,
268
- ): N extends StructuralPrefixLength
269
- ? List.SkipLast<N, Ar>
270
- : readonly Ar[number][];
286
+ ): HasLengthConstraint<Ar> extends true
287
+ ? readonly Ar[number][]
288
+ : N extends StructuralPrefixLength
289
+ ? List.SkipLast<N, Ar>
290
+ : readonly Ar[number][];
271
291
 
272
292
  // curried version
273
293
  export function skipLast<N extends SizeType.ArgArr>(
274
294
  num: N,
275
295
  ): <const Ar extends readonly unknown[]>(
276
296
  array: Ar,
277
- ) => N extends StructuralPrefixLength
278
- ? List.SkipLast<N, Ar>
279
- : readonly Ar[number][];
297
+ ) => HasLengthConstraint<Ar> extends true
298
+ ? readonly Ar[number][]
299
+ : N extends StructuralPrefixLength
300
+ ? List.SkipLast<N, Ar>
301
+ : readonly Ar[number][];
280
302
 
281
303
  export function skipLast<E>(
282
304
  ...args:
@@ -1,3 +1,4 @@
1
+ import { type MinLengthArray } from 'ts-type-forge';
1
2
  import { expectType } from '../../expect-type.mjs';
2
3
  import {
3
4
  butLast,
@@ -219,3 +220,57 @@ describe('Arr slicing', () => {
219
220
  });
220
221
  });
221
222
  });
223
+
224
+ /* Length-constrained inputs. `List.*` reports what it can see from a
225
+ fixed-length tuple, and a branded array's `length` is `number`, so it handed
226
+ the input type straight back — `tail` of an "at least 5" array claimed the
227
+ four-element result was still at least five. Never called; see the note in
228
+ `array-utils-modification.test.mts`. */
229
+ export const slicingTypeChecks = (
230
+ brandedFive: MinLengthArray<5, string>,
231
+ brandedAndTuple: MinLengthArray<3, number> & readonly [1, 2, 3, 4, 5],
232
+ ): void => {
233
+ // The bound is recomputed rather than passed through.
234
+ const _tailed = tail(brandedFive);
235
+
236
+ expectType<typeof _tailed, MinLengthArray<4, string>>('~=');
237
+
238
+ const _butLasted = butLast(brandedFive);
239
+
240
+ expectType<typeof _butLasted, MinLengthArray<4, string>>('~=');
241
+
242
+ /* A brand intersected with an exact tuple keeps the positions too, so the
243
+ result is exact on both counts. */
244
+ const _tailedBoth = tail(brandedAndTuple);
245
+
246
+ expectType<(typeof _tailedBoth)[0], 2>('=');
247
+
248
+ expectType<(typeof _tailedBoth)['length'], 4>('=');
249
+
250
+ /* `take` / `takeLast` / `skip` / `skipLast` stay on the `List.*` path and give
251
+ a branded input the sound unconstrained answer instead of its stale brand.
252
+ They cannot use `ConstrainedList.Take` and friends: those rebuild the bounds
253
+ from `N` and cannot be instantiated against a still-generic `Ar` at all —
254
+ even with a literal `N` the deferred result is a union too complex to
255
+ represent. */
256
+ const _taken = take(brandedFive, 2);
257
+
258
+ expectType<typeof _taken, readonly string[]>('=');
259
+
260
+ const _skipped = skip(brandedFive, 2);
261
+
262
+ expectType<typeof _skipped, readonly string[]>('=');
263
+
264
+ // Plain tuples keep mapping exactly as before.
265
+ const _tailedTuple = tail([1, 2, 3] as const);
266
+
267
+ expectType<typeof _tailedTuple, readonly [2, 3]>('=');
268
+
269
+ const _butLastedTuple = butLast([1, 2, 3] as const);
270
+
271
+ expectType<typeof _butLastedTuple, readonly [1, 2]>('=');
272
+
273
+ const _takenTuple = take([1, 2, 3] as const, 2);
274
+
275
+ expectType<typeof _takenTuple, readonly [1, 2]>('=');
276
+ };
@@ -1,13 +1,18 @@
1
1
  import {
2
+ type BoundedLengthArray,
3
+ type ChangeArrayElement,
4
+ type ConstrainedList,
2
5
  type FixedLengthTuple,
6
+ type HasLengthConstraint,
3
7
  type IsFixedLengthList,
4
- type List,
5
8
  type NonEmptyArray,
6
9
  type NonEmptyTuple,
7
10
  type PositiveInt,
8
11
  type PositiveUint32,
9
12
  type Primitive,
10
13
  type SafeUintWithSmallInt,
14
+ type SupportedLength,
15
+ type UnknownBrand,
11
16
  type WithSmallInt,
12
17
  } from 'ts-type-forge';
13
18
  import { IMap } from '../../collections/index.mjs';
@@ -20,8 +25,8 @@ import {
20
25
  type SizeType,
21
26
  } from '../../types.mjs';
22
27
  import { newArray, seq } from './array-utils-creation.mjs';
28
+ import { isNonEmpty } from './array-utils-length-bounded-array-guard.mjs';
23
29
  import { size } from './array-utils-size.mjs';
24
- import { isNonEmpty } from './array-utils-validation.mjs';
25
30
 
26
31
  /**
27
32
  * Creates a new array by transforming each element with a mapping function.
@@ -42,17 +47,41 @@ import { isNonEmpty } from './array-utils-validation.mjs';
42
47
  * assert.deepStrictEqual(indexed, ['0:1', '1:2', '2:3']);
43
48
  * ```
44
49
  */
50
+ export function map<const Ar extends readonly unknown[] & UnknownBrand, B>(
51
+ array: Ar,
52
+ mapFn: (a: Ar[number], index: ArrayIndex<Ar>) => B,
53
+ ): ChangeArrayElement<Ar, B>;
54
+
55
+ // A plain array or tuple carries no length brand, so the homomorphic mapping
56
+ // is the whole answer. Stating it directly — rather than going through
57
+ // `ChangeArrayElement`, whose `HasLengthConstraint` test a *generic* `Ar`
58
+ // cannot decide — is what keeps `map` usable inside a function that is itself
59
+ // generic over the tuple: a deferred conditional is not assignable to the
60
+ // caller's own `{ [K in keyof Ar]: B }`, while this is.
45
61
  export function map<const Ar extends readonly unknown[], B>(
46
62
  array: Ar,
47
63
  mapFn: (a: Ar[number], index: ArrayIndex<Ar>) => B,
48
64
  ): Readonly<{ [K in keyof Ar]: B }>;
49
65
 
50
66
  // curried version
67
+ //
68
+ // The two cases have to be overloads *of the returned function*, not two
69
+ // overloads of `map` itself: both curried signatures take the same single
70
+ // argument, so overload resolution would always pick the first and the second
71
+ // would be dead.
72
+ //
73
+ // Spelled as an intersection of two function types rather than one object type
74
+ // carrying two call signatures — the `convert-to-readonly` codemod wraps an
75
+ // inline object type in `Readonly<…>`, and a mapped type drops call
76
+ // signatures, which would leave the curried form with none at all.
51
77
  export function map<A, B>(
52
78
  mapFn: (a: A, index: SizeType.Arr) => B,
53
- ): <const Ar extends readonly A[]>(
79
+ ): (<const Ar extends readonly A[] & UnknownBrand>(
54
80
  array: Ar,
55
- ) => Readonly<{ [K in keyof Ar]: B }>;
81
+ ) => ChangeArrayElement<Ar, B>) &
82
+ (<const Ar extends readonly A[]>(
83
+ array: Ar,
84
+ ) => Readonly<{ [K in keyof Ar]: B }>);
56
85
 
57
86
  export function map<A, B>(
58
87
  ...args:
@@ -187,7 +216,7 @@ const scanImpl = <E, S>(
187
216
  */
188
217
  export const toReversed = <const Ar extends readonly unknown[]>(
189
218
  array: Ar,
190
- ): List.Reverse<Ar> =>
219
+ ): ConstrainedList.Reverse<Ar> =>
191
220
  // eslint-disable-next-line total-functions/no-unsafe-type-assertion
192
221
  array.toReversed() as never;
193
222
 
@@ -224,11 +253,13 @@ export const toSorted = <const Ar extends readonly unknown[]>(
224
253
  comparator?: (x: Ar[number], y: Ar[number]) => number,
225
254
  ]
226
255
  : readonly [array: Ar, comparator: (x: Ar[number], y: Ar[number]) => number]
227
- ): IsFixedLengthList<Ar> extends true
228
- ? FixedLengthTuple<Ar['length'], Ar[number]>
229
- : Ar extends NonEmptyTuple<unknown>
230
- ? NonEmptyArray<Ar[number]>
231
- : readonly Ar[number][] =>
256
+ ): HasLengthConstraint<Ar> extends true
257
+ ? ChangeArrayElement<Ar, Ar[number]>
258
+ : IsFixedLengthList<Ar> extends true
259
+ ? FixedLengthTuple<Ar['length'], Ar[number]>
260
+ : Ar extends NonEmptyTuple<unknown>
261
+ ? NonEmptyArray<Ar[number]>
262
+ : readonly Ar[number][] =>
232
263
  // eslint-disable-next-line total-functions/no-unsafe-type-assertion
233
264
  array.toSorted(
234
265
  // eslint-disable-next-line total-functions/no-unsafe-type-assertion
@@ -282,21 +313,25 @@ export function toSortedBy<const Ar extends readonly unknown[]>(
282
313
  comparatorValueMapper: (value: Ar[number]) => number,
283
314
  // If the array elements are mapped to numbers, comparator is optional.
284
315
  comparator?: (x: number, y: number) => number,
285
- ): IsFixedLengthList<Ar> extends true
286
- ? FixedLengthTuple<Ar['length'], Ar[number]>
287
- : Ar extends NonEmptyTuple<unknown>
288
- ? NonEmptyArray<Ar[number]>
289
- : readonly Ar[number][];
316
+ ): HasLengthConstraint<Ar> extends true
317
+ ? ChangeArrayElement<Ar, Ar[number]>
318
+ : IsFixedLengthList<Ar> extends true
319
+ ? FixedLengthTuple<Ar['length'], Ar[number]>
320
+ : Ar extends NonEmptyTuple<unknown>
321
+ ? NonEmptyArray<Ar[number]>
322
+ : readonly Ar[number][];
290
323
 
291
324
  export function toSortedBy<const Ar extends readonly unknown[], const V>(
292
325
  array: Ar,
293
326
  comparatorValueMapper: (value: Ar[number]) => V,
294
327
  comparator: (x: V, y: V) => number,
295
- ): IsFixedLengthList<Ar> extends true
296
- ? FixedLengthTuple<Ar['length'], Ar[number]>
297
- : Ar extends NonEmptyTuple<unknown>
298
- ? NonEmptyArray<Ar[number]>
299
- : readonly Ar[number][];
328
+ ): HasLengthConstraint<Ar> extends true
329
+ ? ChangeArrayElement<Ar, Ar[number]>
330
+ : IsFixedLengthList<Ar> extends true
331
+ ? FixedLengthTuple<Ar['length'], Ar[number]>
332
+ : Ar extends NonEmptyTuple<unknown>
333
+ ? NonEmptyArray<Ar[number]>
334
+ : readonly Ar[number][];
300
335
 
301
336
  export function toSortedBy<E, const V>(
302
337
  array: readonly E[],
@@ -627,18 +662,24 @@ export function flatMap<A, B>(
627
662
  *
628
663
  * const triples = Arr.partition(3)(values);
629
664
  *
665
+ * // Every chunk is branded `BoundedLengthArray<1, N, …>` — `partition` never
666
+ * // emits an empty chunk — so the expected values are annotated with the
667
+ * // unbranded type.
630
668
  * const expectedPairs = [[1, 2], [3, 4], [5]] as const;
631
669
  *
632
- * assert.deepStrictEqual(pairs, expectedPairs);
670
+ * assert.deepStrictEqual<readonly (readonly number[])[]>(
671
+ * pairs,
672
+ * expectedPairs,
673
+ * );
633
674
  *
634
- * assert.deepStrictEqual(triples, [
675
+ * assert.deepStrictEqual<readonly (readonly number[])[]>(triples, [
635
676
  * [1, 2, 3],
636
677
  * [4, 5],
637
678
  * ]);
638
679
  *
639
680
  * const pairs2 = Arr.chunk([1, 2, 3, 4, 5, 6], 2);
640
681
  *
641
- * assert.deepStrictEqual(pairs2, [
682
+ * assert.deepStrictEqual<readonly (readonly number[])[]>(pairs2, [
642
683
  * [1, 2],
643
684
  * [3, 4],
644
685
  * [5, 6],
@@ -646,17 +687,17 @@ export function flatMap<A, B>(
646
687
  * ```
647
688
  */
648
689
  export function partition<
649
- N extends WithSmallInt<PositiveInt & SizeType.Arr>,
650
690
  E,
651
- >(array: readonly E[], chunkSize: N): readonly (readonly E[])[];
691
+ N extends WithSmallInt<PositiveInt & SizeType.Arr>,
692
+ >(array: readonly E[], chunkSize: N): readonly PartitionChunk<N, E>[];
652
693
 
653
694
  export function partition<N extends WithSmallInt<PositiveInt & SizeType.Arr>>(
654
695
  chunkSize: N,
655
- ): <E>(array: readonly E[]) => readonly (readonly E[])[];
696
+ ): <E>(array: readonly E[]) => readonly PartitionChunk<N, E>[];
656
697
 
657
698
  export function partition<
658
- N extends WithSmallInt<PositiveInt & SizeType.Arr>,
659
699
  E,
700
+ N extends WithSmallInt<PositiveInt & SizeType.Arr>,
660
701
  >(
661
702
  ...args:
662
703
  readonly [array: readonly E[], chunkSize: N] | readonly [chunkSize: N]
@@ -672,7 +713,20 @@ export function partition<
672
713
  }
673
714
  }
674
715
 
675
- const partitionImpl = <N extends WithSmallInt<PositiveInt & SizeType.Arr>, E>(
716
+ /**
717
+ * Element type of {@link partition}'s result: a chunk holds between 1 and `N`
718
+ * elements. The lower bound is what makes this worth spelling out — `partition`
719
+ * never emits an empty chunk, so indexed access into a chunk is not
720
+ * `undefined` at position `0`, unlike with a plain `readonly E[]`.
721
+ *
722
+ * Falls back to {@link NonEmptyArray} when `N` is not a bound the brand can
723
+ * express, which keeps that lower bound in every case.
724
+ */
725
+ type PartitionChunk<N, E> = N extends SupportedLength
726
+ ? BoundedLengthArray<1, N, E>
727
+ : NonEmptyArray<E>;
728
+
729
+ const partitionImpl = <E, N extends WithSmallInt<PositiveInt & SizeType.Arr>>(
676
730
  array: readonly E[],
677
731
  chunkSize: N,
678
732
  ): readonly (readonly E[])[] =>
@@ -683,6 +737,13 @@ const partitionImpl = <N extends WithSmallInt<PositiveInt & SizeType.Arr>, E>(
683
737
  array.slice(chunkSize * i, chunkSize * (i + 1)),
684
738
  );
685
739
 
740
+ /**
741
+ * Alias for `partition`.
742
+ *
743
+ * @see {@link partition}
744
+ */
745
+ export const chunk = partition;
746
+
686
747
  /**
687
748
  * Concatenates two arrays.
688
749
  *
@@ -853,20 +914,13 @@ export const zip = <
853
914
  >(
854
915
  array1: Ar1,
855
916
  array2: Ar2,
856
- ): List.Zip<Ar1, Ar2> =>
917
+ ): ConstrainedList.Zip<Ar1, Ar2> =>
857
918
  // eslint-disable-next-line total-functions/no-unsafe-type-assertion
858
919
  seq(Uint32.min(size(array1), size(array2))).map((i: Uint32) =>
859
920
  // Non-null assertion is safe here because `i` is always within bounds of both arrays up to the length of the shorter one.
860
921
  // eslint-disable-next-line @typescript-eslint/no-non-null-assertion
861
922
  tp(array1[i]!, array2[i]!),
862
- ) as unknown as List.Zip<Ar1, Ar2>;
863
-
864
- /**
865
- * Alias for `partition`.
866
- *
867
- * @see {@link partition}
868
- */
869
- export const chunk = partition;
923
+ ) as unknown as ConstrainedList.Zip<Ar1, Ar2>;
870
924
 
871
925
  /**
872
926
  * Computes the cartesian product of arrays.