ts-data-forge 11.0.1 → 12.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.
Files changed (178) hide show
  1. package/README.md +2 -2
  2. package/dist/array/impl/array-utils-length-bounded-array-cast.d.mts +65 -5
  3. package/dist/array/impl/array-utils-length-bounded-array-cast.d.mts.map +1 -1
  4. package/dist/array/impl/array-utils-length-bounded-array-cast.mjs +69 -27
  5. package/dist/array/impl/array-utils-length-bounded-array-cast.mjs.map +1 -1
  6. package/dist/array/impl/index.mjs +1 -1
  7. package/dist/number/branded-types/int16.d.mts +12 -7
  8. package/dist/number/branded-types/int16.d.mts.map +1 -1
  9. package/dist/number/branded-types/int16.mjs +13 -8
  10. package/dist/number/branded-types/int16.mjs.map +1 -1
  11. package/dist/number/branded-types/int32.d.mts +12 -7
  12. package/dist/number/branded-types/int32.d.mts.map +1 -1
  13. package/dist/number/branded-types/int32.mjs +13 -8
  14. package/dist/number/branded-types/int32.mjs.map +1 -1
  15. package/dist/number/branded-types/negative-finite-number.d.mts +13 -6
  16. package/dist/number/branded-types/negative-finite-number.d.mts.map +1 -1
  17. package/dist/number/branded-types/negative-finite-number.mjs +15 -8
  18. package/dist/number/branded-types/negative-finite-number.mjs.map +1 -1
  19. package/dist/number/branded-types/negative-int.d.mts +11 -6
  20. package/dist/number/branded-types/negative-int.d.mts.map +1 -1
  21. package/dist/number/branded-types/negative-int.mjs +13 -8
  22. package/dist/number/branded-types/negative-int.mjs.map +1 -1
  23. package/dist/number/branded-types/negative-safe-int.d.mts +13 -7
  24. package/dist/number/branded-types/negative-safe-int.d.mts.map +1 -1
  25. package/dist/number/branded-types/negative-safe-int.mjs +15 -9
  26. package/dist/number/branded-types/negative-safe-int.mjs.map +1 -1
  27. package/dist/number/branded-types/non-negative-finite-number.d.mts +11 -6
  28. package/dist/number/branded-types/non-negative-finite-number.d.mts.map +1 -1
  29. package/dist/number/branded-types/non-negative-finite-number.mjs +12 -7
  30. package/dist/number/branded-types/non-negative-finite-number.mjs.map +1 -1
  31. package/dist/number/branded-types/non-negative-int16.d.mts +13 -7
  32. package/dist/number/branded-types/non-negative-int16.d.mts.map +1 -1
  33. package/dist/number/branded-types/non-negative-int16.mjs +14 -8
  34. package/dist/number/branded-types/non-negative-int16.mjs.map +1 -1
  35. package/dist/number/branded-types/non-negative-int32.d.mts +13 -7
  36. package/dist/number/branded-types/non-negative-int32.d.mts.map +1 -1
  37. package/dist/number/branded-types/non-negative-int32.mjs +14 -8
  38. package/dist/number/branded-types/non-negative-int32.mjs.map +1 -1
  39. package/dist/number/branded-types/non-positive-finite-number.d.mts +11 -6
  40. package/dist/number/branded-types/non-positive-finite-number.d.mts.map +1 -1
  41. package/dist/number/branded-types/non-positive-finite-number.mjs +12 -7
  42. package/dist/number/branded-types/non-positive-finite-number.mjs.map +1 -1
  43. package/dist/number/branded-types/non-positive-int.d.mts +11 -6
  44. package/dist/number/branded-types/non-positive-int.d.mts.map +1 -1
  45. package/dist/number/branded-types/non-positive-int.mjs +12 -7
  46. package/dist/number/branded-types/non-positive-int.mjs.map +1 -1
  47. package/dist/number/branded-types/non-positive-safe-int.d.mts +14 -7
  48. package/dist/number/branded-types/non-positive-safe-int.d.mts.map +1 -1
  49. package/dist/number/branded-types/non-positive-safe-int.mjs +15 -8
  50. package/dist/number/branded-types/non-positive-safe-int.mjs.map +1 -1
  51. package/dist/number/branded-types/non-zero-int16.d.mts +13 -7
  52. package/dist/number/branded-types/non-zero-int16.d.mts.map +1 -1
  53. package/dist/number/branded-types/non-zero-int16.mjs +14 -8
  54. package/dist/number/branded-types/non-zero-int16.mjs.map +1 -1
  55. package/dist/number/branded-types/non-zero-int32.d.mts +13 -7
  56. package/dist/number/branded-types/non-zero-int32.d.mts.map +1 -1
  57. package/dist/number/branded-types/non-zero-int32.mjs +14 -8
  58. package/dist/number/branded-types/non-zero-int32.mjs.map +1 -1
  59. package/dist/number/branded-types/non-zero-safe-int.d.mts +13 -7
  60. package/dist/number/branded-types/non-zero-safe-int.d.mts.map +1 -1
  61. package/dist/number/branded-types/non-zero-safe-int.mjs +14 -8
  62. package/dist/number/branded-types/non-zero-safe-int.mjs.map +1 -1
  63. package/dist/number/branded-types/non-zero-uint16.d.mts +13 -7
  64. package/dist/number/branded-types/non-zero-uint16.d.mts.map +1 -1
  65. package/dist/number/branded-types/non-zero-uint16.mjs +14 -8
  66. package/dist/number/branded-types/non-zero-uint16.mjs.map +1 -1
  67. package/dist/number/branded-types/non-zero-uint32.d.mts +13 -7
  68. package/dist/number/branded-types/non-zero-uint32.d.mts.map +1 -1
  69. package/dist/number/branded-types/non-zero-uint32.mjs +14 -8
  70. package/dist/number/branded-types/non-zero-uint32.mjs.map +1 -1
  71. package/dist/number/branded-types/positive-finite-number.d.mts +13 -6
  72. package/dist/number/branded-types/positive-finite-number.d.mts.map +1 -1
  73. package/dist/number/branded-types/positive-finite-number.mjs +14 -7
  74. package/dist/number/branded-types/positive-finite-number.mjs.map +1 -1
  75. package/dist/number/branded-types/positive-int.d.mts +13 -8
  76. package/dist/number/branded-types/positive-int.d.mts.map +1 -1
  77. package/dist/number/branded-types/positive-int.mjs +14 -9
  78. package/dist/number/branded-types/positive-int.mjs.map +1 -1
  79. package/dist/number/branded-types/positive-int16.d.mts +13 -7
  80. package/dist/number/branded-types/positive-int16.d.mts.map +1 -1
  81. package/dist/number/branded-types/positive-int16.mjs +14 -8
  82. package/dist/number/branded-types/positive-int16.mjs.map +1 -1
  83. package/dist/number/branded-types/positive-int32.d.mts +13 -7
  84. package/dist/number/branded-types/positive-int32.d.mts.map +1 -1
  85. package/dist/number/branded-types/positive-int32.mjs +14 -8
  86. package/dist/number/branded-types/positive-int32.mjs.map +1 -1
  87. package/dist/number/branded-types/positive-safe-int.d.mts +16 -10
  88. package/dist/number/branded-types/positive-safe-int.d.mts.map +1 -1
  89. package/dist/number/branded-types/positive-safe-int.mjs +17 -11
  90. package/dist/number/branded-types/positive-safe-int.mjs.map +1 -1
  91. package/dist/number/branded-types/positive-uint16.d.mts +13 -7
  92. package/dist/number/branded-types/positive-uint16.d.mts.map +1 -1
  93. package/dist/number/branded-types/positive-uint16.mjs +14 -8
  94. package/dist/number/branded-types/positive-uint16.mjs.map +1 -1
  95. package/dist/number/branded-types/positive-uint32.d.mts +13 -7
  96. package/dist/number/branded-types/positive-uint32.d.mts.map +1 -1
  97. package/dist/number/branded-types/positive-uint32.mjs +14 -8
  98. package/dist/number/branded-types/positive-uint32.mjs.map +1 -1
  99. package/dist/number/branded-types/safe-int.d.mts +15 -10
  100. package/dist/number/branded-types/safe-int.d.mts.map +1 -1
  101. package/dist/number/branded-types/safe-int.mjs +16 -11
  102. package/dist/number/branded-types/safe-int.mjs.map +1 -1
  103. package/dist/number/branded-types/safe-uint.d.mts +12 -7
  104. package/dist/number/branded-types/safe-uint.d.mts.map +1 -1
  105. package/dist/number/branded-types/safe-uint.mjs +13 -8
  106. package/dist/number/branded-types/safe-uint.mjs.map +1 -1
  107. package/dist/number/branded-types/uint.d.mts +14 -10
  108. package/dist/number/branded-types/uint.d.mts.map +1 -1
  109. package/dist/number/branded-types/uint.mjs +15 -11
  110. package/dist/number/branded-types/uint.mjs.map +1 -1
  111. package/dist/number/branded-types/uint16.d.mts +12 -7
  112. package/dist/number/branded-types/uint16.d.mts.map +1 -1
  113. package/dist/number/branded-types/uint16.mjs +13 -8
  114. package/dist/number/branded-types/uint16.mjs.map +1 -1
  115. package/dist/number/branded-types/uint32.d.mts +12 -7
  116. package/dist/number/branded-types/uint32.d.mts.map +1 -1
  117. package/dist/number/branded-types/uint32.mjs +13 -8
  118. package/dist/number/branded-types/uint32.mjs.map +1 -1
  119. package/dist/number/enum/int8.d.mts +12 -7
  120. package/dist/number/enum/int8.d.mts.map +1 -1
  121. package/dist/number/enum/int8.mjs +19 -14
  122. package/dist/number/enum/int8.mjs.map +1 -1
  123. package/dist/number/enum/uint8.d.mts +12 -7
  124. package/dist/number/enum/uint8.d.mts.map +1 -1
  125. package/dist/number/enum/uint8.mjs +19 -14
  126. package/dist/number/enum/uint8.mjs.map +1 -1
  127. package/dist/number/refined-number-utils.d.mts +9 -9
  128. package/dist/number/refined-number-utils.d.mts.map +1 -1
  129. package/dist/number/refined-number-utils.mjs +7 -7
  130. package/dist/number/refined-number-utils.mjs.map +1 -1
  131. package/dist/string/str.d.mts +70 -5
  132. package/dist/string/str.d.mts.map +1 -1
  133. package/dist/string/str.mjs +72 -26
  134. package/dist/string/str.mjs.map +1 -1
  135. package/package.json +1 -1
  136. package/src/array/impl/array-utils-length-bounded-array-cast.mts +204 -39
  137. package/src/array/impl/array-utils-length-bounded-array-cast.test.mts +126 -0
  138. package/src/number/branded-types/int16.mts +13 -8
  139. package/src/number/branded-types/int32.mts +13 -8
  140. package/src/number/branded-types/negative-finite-number.mts +15 -8
  141. package/src/number/branded-types/negative-finite-number.test.mts +4 -4
  142. package/src/number/branded-types/negative-int.mts +13 -8
  143. package/src/number/branded-types/negative-int.test.mts +5 -5
  144. package/src/number/branded-types/negative-safe-int.mts +15 -9
  145. package/src/number/branded-types/negative-safe-int.test.mts +4 -4
  146. package/src/number/branded-types/non-negative-finite-number.mts +12 -7
  147. package/src/number/branded-types/non-negative-int16.mts +14 -8
  148. package/src/number/branded-types/non-negative-int32.mts +14 -8
  149. package/src/number/branded-types/non-positive-finite-number.mts +12 -7
  150. package/src/number/branded-types/non-positive-finite-number.test.mts +7 -7
  151. package/src/number/branded-types/non-positive-int.mts +12 -7
  152. package/src/number/branded-types/non-positive-int.test.mts +7 -7
  153. package/src/number/branded-types/non-positive-safe-int.mts +15 -8
  154. package/src/number/branded-types/non-positive-safe-int.test.mts +7 -7
  155. package/src/number/branded-types/non-zero-int16.mts +14 -8
  156. package/src/number/branded-types/non-zero-int32.mts +14 -8
  157. package/src/number/branded-types/non-zero-safe-int.mts +14 -8
  158. package/src/number/branded-types/non-zero-uint16.mts +14 -8
  159. package/src/number/branded-types/non-zero-uint32.mts +14 -8
  160. package/src/number/branded-types/positive-finite-number.mts +14 -7
  161. package/src/number/branded-types/positive-int.mts +14 -9
  162. package/src/number/branded-types/positive-int16.mts +14 -8
  163. package/src/number/branded-types/positive-int32.mts +14 -8
  164. package/src/number/branded-types/positive-safe-int.mts +17 -11
  165. package/src/number/branded-types/positive-uint16.mts +14 -8
  166. package/src/number/branded-types/positive-uint32.mts +14 -8
  167. package/src/number/branded-types/safe-int.mts +16 -11
  168. package/src/number/branded-types/safe-uint.mts +13 -8
  169. package/src/number/branded-types/uint.mts +15 -11
  170. package/src/number/branded-types/uint16.mts +13 -8
  171. package/src/number/branded-types/uint32.mts +13 -8
  172. package/src/number/enum/int8.mts +20 -14
  173. package/src/number/enum/int8.test.mts +7 -4
  174. package/src/number/enum/uint8.mts +20 -14
  175. package/src/number/enum/uint8.test.mts +7 -4
  176. package/src/number/refined-number-utils.mts +22 -19
  177. package/src/string/str.mts +201 -39
  178. package/src/string/str.test.mts +76 -0
@@ -3,6 +3,7 @@ import {
3
3
  type FixedLengthArray,
4
4
  type MaxLengthArray,
5
5
  type MinLengthArray,
6
+ type NonEmptyArray,
6
7
  type SupportedLength,
7
8
  } from 'ts-type-forge';
8
9
  import {
@@ -22,6 +23,9 @@ import {
22
23
  * structural tuple prefix (in-range indexed access without `undefined` under
23
24
  * `noUncheckedIndexedAccess`).
24
25
  *
26
+ * Supports a curried overload: calling it with only `length` returns a
27
+ * function that casts an array, which is convenient for `pipe`/`map`.
28
+ *
25
29
  * @example
26
30
  *
27
31
  * ```ts
@@ -30,6 +34,10 @@ import {
30
34
  * const atMost5: MaxLengthArray<5, number> = rgb; // OK (3 <= 5)
31
35
  * const red: number = rgb[0]; // OK — no `undefined`
32
36
  *
37
+ * // curried version
38
+ * const asRgb = Arr.asFixedLengthArray(3);
39
+ * const green = asRgb([0, 255, 0]);
40
+ *
33
41
  * // Arr.asFixedLengthArray([255, 128], 3); // throws TypeError
34
42
  * ```
35
43
  *
@@ -40,21 +48,43 @@ import {
40
48
  * @returns `xs` typed as `FixedLengthArray<Length, Xs[number]> & Xs`.
41
49
  * @throws {TypeError} If `xs.length !== length`.
42
50
  */
43
- export const asFixedLengthArray = <
51
+ export function asFixedLengthArray<
44
52
  Xs extends readonly unknown[],
45
53
  Length extends SupportedLength,
46
- >(
47
- xs: Xs,
54
+ >(xs: Xs, length: Length): FixedLengthArray<Length, Xs[number]> & Xs;
55
+
56
+ // Curried version
57
+ export function asFixedLengthArray<Length extends SupportedLength>(
48
58
  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
- }
59
+ ): <Xs extends readonly unknown[]>(
60
+ xs: Xs,
61
+ ) => FixedLengthArray<Length, Xs[number]> & Xs;
55
62
 
56
- return xs;
57
- };
63
+ export function asFixedLengthArray<E>(
64
+ ...args:
65
+ | readonly [xs: readonly E[], length: SupportedLength]
66
+ | readonly [length: SupportedLength]
67
+ ): readonly E[] | ((xs: readonly E[]) => readonly E[]) {
68
+ switch (args.length) {
69
+ case 2: {
70
+ const [xs, length] = args;
71
+
72
+ if (!isFixedLengthArray(xs, length)) {
73
+ throw new TypeError(
74
+ `Expected an array of length ${length}, got an array of length ${xs.length}`,
75
+ );
76
+ }
77
+
78
+ return xs;
79
+ }
80
+
81
+ case 1: {
82
+ const [length] = args;
83
+
84
+ return (xs) => asFixedLengthArray(xs, length);
85
+ }
86
+ }
87
+ }
58
88
 
59
89
  /**
60
90
  * Casts an array to `MinLengthArray<MinLength, E>` after checking that it has
@@ -66,6 +96,9 @@ export const asFixedLengthArray = <
66
96
  * structural tuple prefix (indexed access below `min(N, 10)` without
67
97
  * `undefined` under `noUncheckedIndexedAccess`).
68
98
  *
99
+ * Supports a curried overload: calling it with only `minLength` returns a
100
+ * function that casts an array, which is convenient for `pipe`/`map`.
101
+ *
69
102
  * @example
70
103
  *
71
104
  * ```ts
@@ -74,6 +107,10 @@ export const asFixedLengthArray = <
74
107
  * const nonEmpty: MinLengthArray<1, number> = history; // OK (3 >= 1)
75
108
  * const first: number = history[0]; // OK — no `undefined`
76
109
  *
110
+ * // curried version
111
+ * const asHistory = Arr.asMinLengthArray(3);
112
+ * const next = asHistory([4, 5, 6, 7]);
113
+ *
77
114
  * // Arr.asMinLengthArray([0], 3); // throws TypeError
78
115
  * ```
79
116
  *
@@ -84,21 +121,43 @@ export const asFixedLengthArray = <
84
121
  * @returns `xs` typed as `MinLengthArray<MinLength, Xs[number]> & Xs`.
85
122
  * @throws {TypeError} If `xs.length < minLength`.
86
123
  */
87
- export const asMinLengthArray = <
124
+ export function asMinLengthArray<
88
125
  Xs extends readonly unknown[],
89
126
  MinLength extends SupportedLength,
90
- >(
91
- xs: Xs,
127
+ >(xs: Xs, minLength: MinLength): MinLengthArray<MinLength, Xs[number]> & Xs;
128
+
129
+ // Curried version
130
+ export function asMinLengthArray<MinLength extends SupportedLength>(
92
131
  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
- }
132
+ ): <Xs extends readonly unknown[]>(
133
+ xs: Xs,
134
+ ) => MinLengthArray<MinLength, Xs[number]> & Xs;
99
135
 
100
- return xs;
101
- };
136
+ export function asMinLengthArray<E>(
137
+ ...args:
138
+ | readonly [xs: readonly E[], minLength: SupportedLength]
139
+ | readonly [minLength: SupportedLength]
140
+ ): readonly E[] | ((xs: readonly E[]) => readonly E[]) {
141
+ switch (args.length) {
142
+ case 2: {
143
+ const [xs, minLength] = args;
144
+
145
+ if (!isMinLengthArray(xs, minLength)) {
146
+ throw new TypeError(
147
+ `Expected an array of length >= ${minLength}, got an array of length ${xs.length}`,
148
+ );
149
+ }
150
+
151
+ return xs;
152
+ }
153
+
154
+ case 1: {
155
+ const [minLength] = args;
156
+
157
+ return (xs) => asMinLengthArray(xs, minLength);
158
+ }
159
+ }
160
+ }
102
161
 
103
162
  /**
104
163
  * Casts an array to `MaxLengthArray<MaxLength, E>` after checking that it has
@@ -108,6 +167,9 @@ export const asMinLengthArray = <
108
167
  * length is verified at runtime, and the original array type (e.g. tuple
109
168
  * types) is preserved via intersection.
110
169
  *
170
+ * Supports a curried overload: calling it with only `maxLength` returns a
171
+ * function that casts an array, which is convenient for `pipe`/`map`.
172
+ *
111
173
  * @example
112
174
  *
113
175
  * ```ts
@@ -115,6 +177,10 @@ export const asMinLengthArray = <
115
177
  *
116
178
  * const relaxed: MaxLengthArray<16, string> = tags; // OK (8 <= 16)
117
179
  *
180
+ * // curried version
181
+ * const asTags = Arr.asMaxLengthArray(8);
182
+ * const more = asTags(['d', 'e']);
183
+ *
118
184
  * // Arr.asMaxLengthArray(['a', 'b', 'c'], 2); // throws TypeError
119
185
  * ```
120
186
  *
@@ -125,21 +191,43 @@ export const asMinLengthArray = <
125
191
  * @returns `xs` typed as `MaxLengthArray<MaxLength, Xs[number]> & Xs`.
126
192
  * @throws {TypeError} If `xs.length > maxLength`.
127
193
  */
128
- export const asMaxLengthArray = <
194
+ export function asMaxLengthArray<
129
195
  Xs extends readonly unknown[],
130
196
  MaxLength extends SupportedLength,
131
- >(
132
- xs: Xs,
197
+ >(xs: Xs, maxLength: MaxLength): MaxLengthArray<MaxLength, Xs[number]> & Xs;
198
+
199
+ // Curried version
200
+ export function asMaxLengthArray<MaxLength extends SupportedLength>(
133
201
  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
- }
202
+ ): <Xs extends readonly unknown[]>(
203
+ xs: Xs,
204
+ ) => MaxLengthArray<MaxLength, Xs[number]> & Xs;
140
205
 
141
- return xs;
142
- };
206
+ export function asMaxLengthArray<E>(
207
+ ...args:
208
+ | readonly [xs: readonly E[], maxLength: SupportedLength]
209
+ | readonly [maxLength: SupportedLength]
210
+ ): readonly E[] | ((xs: readonly E[]) => readonly E[]) {
211
+ switch (args.length) {
212
+ case 2: {
213
+ const [xs, maxLength] = args;
214
+
215
+ if (!isMaxLengthArray(xs, maxLength)) {
216
+ throw new TypeError(
217
+ `Expected an array of length <= ${maxLength}, got an array of length ${xs.length}`,
218
+ );
219
+ }
220
+
221
+ return xs;
222
+ }
223
+
224
+ case 1: {
225
+ const [maxLength] = args;
226
+
227
+ return (xs) => asMaxLengthArray(xs, maxLength);
228
+ }
229
+ }
230
+ }
143
231
 
144
232
  /**
145
233
  * Casts an array to `BoundedLengthArray<MinLength, MaxLength, E>` after
@@ -151,6 +239,10 @@ export const asMaxLengthArray = <
151
239
  * at runtime, and the original array type (e.g. tuple types) is preserved via
152
240
  * intersection.
153
241
  *
242
+ * Supports a curried overload: calling it with only `minLength` and
243
+ * `maxLength` returns a function that casts an array, which is convenient for
244
+ * `pipe`/`map`.
245
+ *
154
246
  * @example
155
247
  *
156
248
  * ```ts
@@ -158,6 +250,10 @@ export const asMaxLengthArray = <
158
250
  *
159
251
  * const relaxed: BoundedLengthArray<0, 100, number> = selection; // OK
160
252
  *
253
+ * // curried version
254
+ * const asSelection = Arr.asBoundedLengthArray(1, 5);
255
+ * const next = asSelection([4, 5]);
256
+ *
161
257
  * // Arr.asBoundedLengthArray([], 1, 5); // throws TypeError
162
258
  * ```
163
259
  *
@@ -171,7 +267,7 @@ export const asMaxLengthArray = <
171
267
  * `BoundedLengthArray<MinLength, MaxLength, Xs[number]> & Xs`.
172
268
  * @throws {TypeError} If `xs.length < minLength || xs.length > maxLength`.
173
269
  */
174
- export const asBoundedLengthArray = <
270
+ export function asBoundedLengthArray<
175
271
  Xs extends readonly unknown[],
176
272
  MinLength extends SupportedLength,
177
273
  MaxLength extends SupportedLength,
@@ -179,11 +275,80 @@ export const asBoundedLengthArray = <
179
275
  xs: Xs,
180
276
  minLength: MinLength,
181
277
  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
- );
278
+ ): BoundedLengthArray<MinLength, MaxLength, Xs[number]> & Xs;
279
+
280
+ // Curried version
281
+ export function asBoundedLengthArray<
282
+ MinLength extends SupportedLength,
283
+ MaxLength extends SupportedLength,
284
+ >(
285
+ minLength: MinLength,
286
+ maxLength: MaxLength,
287
+ ): <Xs extends readonly unknown[]>(
288
+ xs: Xs,
289
+ ) => BoundedLengthArray<MinLength, MaxLength, Xs[number]> & Xs;
290
+
291
+ export function asBoundedLengthArray<E>(
292
+ ...args:
293
+ | readonly [
294
+ xs: readonly E[],
295
+ minLength: SupportedLength,
296
+ maxLength: SupportedLength,
297
+ ]
298
+ | readonly [minLength: SupportedLength, maxLength: SupportedLength]
299
+ ): readonly E[] | ((xs: readonly E[]) => readonly E[]) {
300
+ switch (args.length) {
301
+ case 3: {
302
+ const [xs, minLength, maxLength] = args;
303
+
304
+ if (!isBoundedLengthArray(xs, minLength, maxLength)) {
305
+ throw new TypeError(
306
+ `Expected an array of length in [${minLength}, ${maxLength}], got an array of length ${xs.length}`,
307
+ );
308
+ }
309
+
310
+ return xs;
311
+ }
312
+
313
+ case 2: {
314
+ const [minLength, maxLength] = args;
315
+
316
+ return (xs) => asBoundedLengthArray(xs, minLength, maxLength);
317
+ }
318
+ }
319
+ }
320
+
321
+ /**
322
+ * Casts an array to `NonEmptyArray<E>` after checking that it has at least one
323
+ * element.
324
+ *
325
+ * `NonEmptyArray<E>` is an alias of `MinLengthArray<1, E>`, so this is the
326
+ * length-1 specialization of {@link asMinLengthArray}. Use this instead of
327
+ * writing `xs as unknown as NonEmptyArray<E>`: the length is verified at
328
+ * runtime, and the original array type (e.g. tuple types) is preserved via
329
+ * intersection, so the result also benefits from the structural tuple prefix
330
+ * (`xs[0]` without `undefined` under `noUncheckedIndexedAccess`).
331
+ *
332
+ * @example
333
+ *
334
+ * ```ts
335
+ * const history = Arr.asNonEmptyArray([0, 1, 2, 3]);
336
+ *
337
+ * const first: number = history[0]; // OK — no `undefined`
338
+ *
339
+ * // Arr.asNonEmptyArray([]); // throws TypeError
340
+ * ```
341
+ *
342
+ * @template Xs - The input array type (tuple types are preserved).
343
+ * @param xs - The array to cast.
344
+ * @returns `xs` typed as `NonEmptyArray<Xs[number]> & Xs`.
345
+ * @throws {TypeError} If `xs` is empty.
346
+ */
347
+ export const asNonEmptyArray = <Xs extends readonly unknown[]>(
348
+ xs: Xs,
349
+ ): NonEmptyArray<Xs[number]> & Xs => {
350
+ if (!isMinLengthArray(xs, 1)) {
351
+ throw new TypeError('Expected a non-empty array, got an empty array');
187
352
  }
188
353
 
189
354
  return xs;
@@ -3,6 +3,7 @@ import {
3
3
  type FixedLengthArray,
4
4
  type MaxLengthArray,
5
5
  type MinLengthArray,
6
+ type NonEmptyArray,
6
7
  } from 'ts-type-forge';
7
8
  import { expectType } from '../../expect-type.mjs';
8
9
  import {
@@ -10,6 +11,7 @@ import {
10
11
  asFixedLengthArray,
11
12
  asMaxLengthArray,
12
13
  asMinLengthArray,
14
+ asNonEmptyArray,
13
15
  } from './array-utils-length-bounded-array-cast.mjs';
14
16
 
15
17
  describe(asFixedLengthArray, () => {
@@ -47,6 +49,29 @@ describe(asFixedLengthArray, () => {
47
49
 
48
50
  expectType<typeof casted, FixedLengthArray<3, 1 | 2 | 3>>('<=');
49
51
  });
52
+
53
+ test('curried version casts an array', () => {
54
+ const xs: readonly number[] = [0, 1, 2, 3] as const;
55
+
56
+ const casted = asFixedLengthArray(4)(xs);
57
+
58
+ expect(casted).toBe(xs);
59
+
60
+ expectType<typeof casted, FixedLengthArray<4, number>>('<=');
61
+
62
+ // in-range indexed access does not include `undefined`
63
+ const _first = casted[0];
64
+
65
+ expectType<typeof _first, number>('=');
66
+ });
67
+
68
+ test('curried version throws when the length does not match', () => {
69
+ expect(() => asFixedLengthArray(4)([0, 1, 2])).toThrow(TypeError);
70
+
71
+ expect(() => asFixedLengthArray(4)([0, 1, 2])).toThrow(
72
+ 'Expected an array of length 4, got an array of length 3',
73
+ );
74
+ });
50
75
  });
51
76
 
52
77
  describe(asMinLengthArray, () => {
@@ -69,6 +94,26 @@ describe(asMinLengthArray, () => {
69
94
  'Expected an array of length >= 3, got an array of length 1',
70
95
  );
71
96
  });
97
+
98
+ test('curried version casts an array', () => {
99
+ const xs: readonly number[] = [0, 1, 2, 3] as const;
100
+
101
+ const casted = asMinLengthArray(3)(xs);
102
+
103
+ expect(casted).toBe(xs);
104
+
105
+ expectType<typeof casted, MinLengthArray<3, number>>('<=');
106
+
107
+ expectType<typeof casted, MinLengthArray<1, number>>('<='); // 3 >= 1
108
+ });
109
+
110
+ test('curried version throws when the array is too short', () => {
111
+ expect(() => asMinLengthArray(3)([0])).toThrow(TypeError);
112
+
113
+ expect(() => asMinLengthArray(3)([0])).toThrow(
114
+ 'Expected an array of length >= 3, got an array of length 1',
115
+ );
116
+ });
72
117
  });
73
118
 
74
119
  describe(asMaxLengthArray, () => {
@@ -91,6 +136,26 @@ describe(asMaxLengthArray, () => {
91
136
  'Expected an array of length <= 2, got an array of length 3',
92
137
  );
93
138
  });
139
+
140
+ test('curried version casts an array', () => {
141
+ const xs: readonly number[] = [0, 1, 2] as const;
142
+
143
+ const casted = asMaxLengthArray(8)(xs);
144
+
145
+ expect(casted).toBe(xs);
146
+
147
+ expectType<typeof casted, MaxLengthArray<8, number>>('<=');
148
+
149
+ expectType<typeof casted, MaxLengthArray<16, number>>('<='); // 8 <= 16
150
+ });
151
+
152
+ test('curried version throws when the array is too long', () => {
153
+ expect(() => asMaxLengthArray(2)([0, 1, 2])).toThrow(TypeError);
154
+
155
+ expect(() => asMaxLengthArray(2)([0, 1, 2])).toThrow(
156
+ 'Expected an array of length <= 2, got an array of length 3',
157
+ );
158
+ });
94
159
  });
95
160
 
96
161
  describe(asBoundedLengthArray, () => {
@@ -115,4 +180,65 @@ describe(asBoundedLengthArray, () => {
115
180
  'Expected an array of length in [1, 5], got an array of length 6',
116
181
  );
117
182
  });
183
+
184
+ test('curried version casts an array', () => {
185
+ const xs: readonly number[] = [1, 2, 3] as const;
186
+
187
+ const casted = asBoundedLengthArray(1, 5)(xs);
188
+
189
+ expect(casted).toBe(xs);
190
+
191
+ expectType<typeof casted, BoundedLengthArray<1, 5, number>>('<=');
192
+
193
+ expectType<typeof casted, MinLengthArray<1, number>>('<=');
194
+
195
+ expectType<typeof casted, MaxLengthArray<5, number>>('<=');
196
+ });
197
+
198
+ test('curried version throws when the length is out of the range', () => {
199
+ expect(() => asBoundedLengthArray(1, 5)([])).toThrow(TypeError);
200
+
201
+ expect(() => asBoundedLengthArray(1, 5)([1, 2, 3, 4, 5, 6])).toThrow(
202
+ 'Expected an array of length in [1, 5], got an array of length 6',
203
+ );
204
+ });
205
+ });
206
+
207
+ describe(asNonEmptyArray, () => {
208
+ test('should return the input when the array is non-empty', () => {
209
+ const xs: readonly number[] = [0, 1, 2, 3] as const;
210
+
211
+ const casted = asNonEmptyArray(xs);
212
+
213
+ expect(casted).toBe(xs);
214
+
215
+ expectType<typeof casted, NonEmptyArray<number>>('<=');
216
+
217
+ expectType<typeof casted, MinLengthArray<1, number>>('<=');
218
+
219
+ // in-range indexed access does not include `undefined`
220
+ const _first = casted[0];
221
+
222
+ expectType<typeof _first, number>('=');
223
+ });
224
+
225
+ test('should throw when the array is empty', () => {
226
+ expect(() => asNonEmptyArray([])).toThrow(TypeError);
227
+
228
+ expect(() => asNonEmptyArray([])).toThrow(
229
+ 'Expected a non-empty array, got an empty array',
230
+ );
231
+ });
232
+
233
+ test('should preserve the original array type', () => {
234
+ const tuple = [1, 2, 3] as const;
235
+
236
+ const casted = asNonEmptyArray(tuple);
237
+
238
+ expect(casted).toBe(tuple);
239
+
240
+ expectType<typeof casted, readonly [1, 2, 3]>('<=');
241
+
242
+ expectType<typeof casted, NonEmptyArray<1 | 2 | 3>>('<=');
243
+ });
118
244
  });
@@ -14,8 +14,8 @@ const {
14
14
  abs,
15
15
  add,
16
16
  castType,
17
- clamp,
18
17
  div,
18
+ fromNumber,
19
19
  is,
20
20
  max: max_,
21
21
  min: min_,
@@ -74,12 +74,14 @@ export const Int16 = {
74
74
  is,
75
75
 
76
76
  /**
77
- * The smallest value representable as `Int16`.
77
+ * The smallest value representable as `Int16` (the lower saturation target of
78
+ * `fromNumber`).
78
79
  */
79
80
  MIN_VALUE,
80
81
 
81
82
  /**
82
- * The largest value representable as `Int16`.
83
+ * The largest value representable as `Int16` (the upper saturation target of
84
+ * `fromNumber`).
83
85
  */
84
86
  MAX_VALUE,
85
87
 
@@ -110,13 +112,16 @@ export const Int16 = {
110
112
  max: max_,
111
113
 
112
114
  /**
113
- * Clamps a `number` into the `Int16` range, rounding to the nearest integer
114
- * and constraining the result to `[MIN_VALUE, MAX_VALUE]`.
115
+ * Converts an arbitrary `number` into an `Int16`, rounding to the nearest
116
+ * integer and saturating the result into the range `[MIN_VALUE, MAX_VALUE]`.
115
117
  *
116
- * @param value - The value to clamp
117
- * @returns The clamped value as an `Int16`
118
+ * Unlike `asInt16`, this is total: out-of-range inputs are clamped to the
119
+ * nearest representable `Int16` instead of throwing.
120
+ *
121
+ * @param value - The value to convert
122
+ * @returns The value as an `Int16`
118
123
  */
119
- clamp,
124
+ fromNumber,
120
125
 
121
126
  /**
122
127
  * Generates a random `Int16` within the given range.
@@ -14,8 +14,8 @@ const {
14
14
  abs,
15
15
  add,
16
16
  castType,
17
- clamp,
18
17
  div,
18
+ fromNumber,
19
19
  is,
20
20
  max: max_,
21
21
  min: min_,
@@ -74,12 +74,14 @@ export const Int32 = {
74
74
  is,
75
75
 
76
76
  /**
77
- * The smallest value representable as `Int32`.
77
+ * The smallest value representable as `Int32` (the lower saturation target of
78
+ * `fromNumber`).
78
79
  */
79
80
  MIN_VALUE,
80
81
 
81
82
  /**
82
- * The largest value representable as `Int32`.
83
+ * The largest value representable as `Int32` (the upper saturation target of
84
+ * `fromNumber`).
83
85
  */
84
86
  MAX_VALUE,
85
87
 
@@ -110,13 +112,16 @@ export const Int32 = {
110
112
  max: max_,
111
113
 
112
114
  /**
113
- * Clamps a `number` into the `Int32` range, rounding to the nearest integer
114
- * and constraining the result to `[MIN_VALUE, MAX_VALUE]`.
115
+ * Converts an arbitrary `number` into an `Int32`, rounding to the nearest
116
+ * integer and saturating the result into the range `[MIN_VALUE, MAX_VALUE]`.
115
117
  *
116
- * @param value - The value to clamp
117
- * @returns The clamped value as an `Int32`
118
+ * Unlike `asInt32`, this is total: out-of-range inputs are clamped to the
119
+ * nearest representable `Int32` instead of throwing.
120
+ *
121
+ * @param value - The value to convert
122
+ * @returns The value as an `Int32`
118
123
  */
119
- clamp,
124
+ fromNumber,
120
125
 
121
126
  /**
122
127
  * Generates a random `Int32` within the given range.
@@ -12,7 +12,7 @@ const {
12
12
  MAX_VALUE,
13
13
  add,
14
14
  castType,
15
- clamp,
15
+ fromNumber,
16
16
  is,
17
17
  max: max_,
18
18
  min: min_,
@@ -31,7 +31,7 @@ const {
31
31
  } as const);
32
32
 
33
33
  const abs = (x: ElementType): PositiveFiniteNumber =>
34
- PositiveFiniteNumber.clamp(Math.abs(x));
34
+ PositiveFiniteNumber.fromNumber(Math.abs(x));
35
35
 
36
36
  const floor = (
37
37
  x: ElementType,
@@ -101,7 +101,10 @@ export const NegativeFiniteNumber = {
101
101
  is,
102
102
 
103
103
  /**
104
- * The largest value representable as `NegativeFiniteNumber`.
104
+ * The largest value representable as `NegativeFiniteNumber` (the upper
105
+ * saturation target of `fromNumber`). The domain is open at `0` (excluded),
106
+ * so this is `-Number.MIN_VALUE` — the negative double closest to `0` —
107
+ * rather than `0`.
105
108
  */
106
109
  MAX_VALUE,
107
110
 
@@ -132,13 +135,17 @@ export const NegativeFiniteNumber = {
132
135
  max: max_,
133
136
 
134
137
  /**
135
- * Clamps a `number` into the `NegativeFiniteNumber` range `[MIN_VALUE,
136
- * MAX_VALUE]`.
138
+ * Converts an arbitrary `number` into a `NegativeFiniteNumber`, saturating it
139
+ * into the range `[MIN_VALUE, MAX_VALUE]`.
137
140
  *
138
- * @param value - The value to clamp
139
- * @returns The clamped value as a `NegativeFiniteNumber`
141
+ * Unlike `asNegativeFiniteNumber`, this is total: out-of-range inputs are
142
+ * clamped to the nearest representable `NegativeFiniteNumber` instead of
143
+ * throwing.
144
+ *
145
+ * @param value - The value to convert
146
+ * @returns The value as a `NegativeFiniteNumber`
140
147
  */
141
- clamp,
148
+ fromNumber,
142
149
 
143
150
  /**
144
151
  * Returns the greatest integer less than or equal to a negative finite
@@ -93,13 +93,13 @@ describe('NegativeFiniteNumber test', () => {
93
93
  });
94
94
  });
95
95
 
96
- describe('NegativeFiniteNumber.clamp', () => {
96
+ describe('NegativeFiniteNumber.fromNumber', () => {
97
97
  test('clamps non-negative values into range', () => {
98
- expect(NegativeFiniteNumber.clamp(-5.5)).toBeCloseTo(-5.5, 5);
98
+ expect(NegativeFiniteNumber.fromNumber(-5.5)).toBeCloseTo(-5.5, 5);
99
99
 
100
- expect(NegativeFiniteNumber.clamp(0)).toBeLessThan(0);
100
+ expect(NegativeFiniteNumber.fromNumber(0)).toBeLessThan(0);
101
101
 
102
- expect(NegativeFiniteNumber.clamp(5)).toBeLessThan(0);
102
+ expect(NegativeFiniteNumber.fromNumber(5)).toBeLessThan(0);
103
103
  });
104
104
  });
105
105