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.
- package/README.md +2 -2
- package/dist/array/impl/array-utils-length-bounded-array-cast.d.mts +65 -5
- package/dist/array/impl/array-utils-length-bounded-array-cast.d.mts.map +1 -1
- package/dist/array/impl/array-utils-length-bounded-array-cast.mjs +69 -27
- package/dist/array/impl/array-utils-length-bounded-array-cast.mjs.map +1 -1
- package/dist/array/impl/index.mjs +1 -1
- package/dist/number/branded-types/int16.d.mts +12 -7
- package/dist/number/branded-types/int16.d.mts.map +1 -1
- package/dist/number/branded-types/int16.mjs +13 -8
- package/dist/number/branded-types/int16.mjs.map +1 -1
- package/dist/number/branded-types/int32.d.mts +12 -7
- package/dist/number/branded-types/int32.d.mts.map +1 -1
- package/dist/number/branded-types/int32.mjs +13 -8
- package/dist/number/branded-types/int32.mjs.map +1 -1
- package/dist/number/branded-types/negative-finite-number.d.mts +13 -6
- package/dist/number/branded-types/negative-finite-number.d.mts.map +1 -1
- package/dist/number/branded-types/negative-finite-number.mjs +15 -8
- package/dist/number/branded-types/negative-finite-number.mjs.map +1 -1
- package/dist/number/branded-types/negative-int.d.mts +11 -6
- package/dist/number/branded-types/negative-int.d.mts.map +1 -1
- package/dist/number/branded-types/negative-int.mjs +13 -8
- package/dist/number/branded-types/negative-int.mjs.map +1 -1
- package/dist/number/branded-types/negative-safe-int.d.mts +13 -7
- package/dist/number/branded-types/negative-safe-int.d.mts.map +1 -1
- package/dist/number/branded-types/negative-safe-int.mjs +15 -9
- package/dist/number/branded-types/negative-safe-int.mjs.map +1 -1
- package/dist/number/branded-types/non-negative-finite-number.d.mts +11 -6
- package/dist/number/branded-types/non-negative-finite-number.d.mts.map +1 -1
- package/dist/number/branded-types/non-negative-finite-number.mjs +12 -7
- package/dist/number/branded-types/non-negative-finite-number.mjs.map +1 -1
- package/dist/number/branded-types/non-negative-int16.d.mts +13 -7
- package/dist/number/branded-types/non-negative-int16.d.mts.map +1 -1
- package/dist/number/branded-types/non-negative-int16.mjs +14 -8
- package/dist/number/branded-types/non-negative-int16.mjs.map +1 -1
- package/dist/number/branded-types/non-negative-int32.d.mts +13 -7
- package/dist/number/branded-types/non-negative-int32.d.mts.map +1 -1
- package/dist/number/branded-types/non-negative-int32.mjs +14 -8
- package/dist/number/branded-types/non-negative-int32.mjs.map +1 -1
- package/dist/number/branded-types/non-positive-finite-number.d.mts +11 -6
- package/dist/number/branded-types/non-positive-finite-number.d.mts.map +1 -1
- package/dist/number/branded-types/non-positive-finite-number.mjs +12 -7
- package/dist/number/branded-types/non-positive-finite-number.mjs.map +1 -1
- package/dist/number/branded-types/non-positive-int.d.mts +11 -6
- package/dist/number/branded-types/non-positive-int.d.mts.map +1 -1
- package/dist/number/branded-types/non-positive-int.mjs +12 -7
- package/dist/number/branded-types/non-positive-int.mjs.map +1 -1
- package/dist/number/branded-types/non-positive-safe-int.d.mts +14 -7
- package/dist/number/branded-types/non-positive-safe-int.d.mts.map +1 -1
- package/dist/number/branded-types/non-positive-safe-int.mjs +15 -8
- package/dist/number/branded-types/non-positive-safe-int.mjs.map +1 -1
- package/dist/number/branded-types/non-zero-int16.d.mts +13 -7
- package/dist/number/branded-types/non-zero-int16.d.mts.map +1 -1
- package/dist/number/branded-types/non-zero-int16.mjs +14 -8
- package/dist/number/branded-types/non-zero-int16.mjs.map +1 -1
- package/dist/number/branded-types/non-zero-int32.d.mts +13 -7
- package/dist/number/branded-types/non-zero-int32.d.mts.map +1 -1
- package/dist/number/branded-types/non-zero-int32.mjs +14 -8
- package/dist/number/branded-types/non-zero-int32.mjs.map +1 -1
- package/dist/number/branded-types/non-zero-safe-int.d.mts +13 -7
- package/dist/number/branded-types/non-zero-safe-int.d.mts.map +1 -1
- package/dist/number/branded-types/non-zero-safe-int.mjs +14 -8
- package/dist/number/branded-types/non-zero-safe-int.mjs.map +1 -1
- package/dist/number/branded-types/non-zero-uint16.d.mts +13 -7
- package/dist/number/branded-types/non-zero-uint16.d.mts.map +1 -1
- package/dist/number/branded-types/non-zero-uint16.mjs +14 -8
- package/dist/number/branded-types/non-zero-uint16.mjs.map +1 -1
- package/dist/number/branded-types/non-zero-uint32.d.mts +13 -7
- package/dist/number/branded-types/non-zero-uint32.d.mts.map +1 -1
- package/dist/number/branded-types/non-zero-uint32.mjs +14 -8
- package/dist/number/branded-types/non-zero-uint32.mjs.map +1 -1
- package/dist/number/branded-types/positive-finite-number.d.mts +13 -6
- package/dist/number/branded-types/positive-finite-number.d.mts.map +1 -1
- package/dist/number/branded-types/positive-finite-number.mjs +14 -7
- package/dist/number/branded-types/positive-finite-number.mjs.map +1 -1
- package/dist/number/branded-types/positive-int.d.mts +13 -8
- package/dist/number/branded-types/positive-int.d.mts.map +1 -1
- package/dist/number/branded-types/positive-int.mjs +14 -9
- package/dist/number/branded-types/positive-int.mjs.map +1 -1
- package/dist/number/branded-types/positive-int16.d.mts +13 -7
- package/dist/number/branded-types/positive-int16.d.mts.map +1 -1
- package/dist/number/branded-types/positive-int16.mjs +14 -8
- package/dist/number/branded-types/positive-int16.mjs.map +1 -1
- package/dist/number/branded-types/positive-int32.d.mts +13 -7
- package/dist/number/branded-types/positive-int32.d.mts.map +1 -1
- package/dist/number/branded-types/positive-int32.mjs +14 -8
- package/dist/number/branded-types/positive-int32.mjs.map +1 -1
- package/dist/number/branded-types/positive-safe-int.d.mts +16 -10
- package/dist/number/branded-types/positive-safe-int.d.mts.map +1 -1
- package/dist/number/branded-types/positive-safe-int.mjs +17 -11
- package/dist/number/branded-types/positive-safe-int.mjs.map +1 -1
- package/dist/number/branded-types/positive-uint16.d.mts +13 -7
- package/dist/number/branded-types/positive-uint16.d.mts.map +1 -1
- package/dist/number/branded-types/positive-uint16.mjs +14 -8
- package/dist/number/branded-types/positive-uint16.mjs.map +1 -1
- package/dist/number/branded-types/positive-uint32.d.mts +13 -7
- package/dist/number/branded-types/positive-uint32.d.mts.map +1 -1
- package/dist/number/branded-types/positive-uint32.mjs +14 -8
- package/dist/number/branded-types/positive-uint32.mjs.map +1 -1
- package/dist/number/branded-types/safe-int.d.mts +15 -10
- package/dist/number/branded-types/safe-int.d.mts.map +1 -1
- package/dist/number/branded-types/safe-int.mjs +16 -11
- package/dist/number/branded-types/safe-int.mjs.map +1 -1
- package/dist/number/branded-types/safe-uint.d.mts +12 -7
- package/dist/number/branded-types/safe-uint.d.mts.map +1 -1
- package/dist/number/branded-types/safe-uint.mjs +13 -8
- package/dist/number/branded-types/safe-uint.mjs.map +1 -1
- package/dist/number/branded-types/uint.d.mts +14 -10
- package/dist/number/branded-types/uint.d.mts.map +1 -1
- package/dist/number/branded-types/uint.mjs +15 -11
- package/dist/number/branded-types/uint.mjs.map +1 -1
- package/dist/number/branded-types/uint16.d.mts +12 -7
- package/dist/number/branded-types/uint16.d.mts.map +1 -1
- package/dist/number/branded-types/uint16.mjs +13 -8
- package/dist/number/branded-types/uint16.mjs.map +1 -1
- package/dist/number/branded-types/uint32.d.mts +12 -7
- package/dist/number/branded-types/uint32.d.mts.map +1 -1
- package/dist/number/branded-types/uint32.mjs +13 -8
- package/dist/number/branded-types/uint32.mjs.map +1 -1
- package/dist/number/enum/int8.d.mts +12 -7
- package/dist/number/enum/int8.d.mts.map +1 -1
- package/dist/number/enum/int8.mjs +19 -14
- package/dist/number/enum/int8.mjs.map +1 -1
- package/dist/number/enum/uint8.d.mts +12 -7
- package/dist/number/enum/uint8.d.mts.map +1 -1
- package/dist/number/enum/uint8.mjs +19 -14
- package/dist/number/enum/uint8.mjs.map +1 -1
- package/dist/number/refined-number-utils.d.mts +9 -9
- package/dist/number/refined-number-utils.d.mts.map +1 -1
- package/dist/number/refined-number-utils.mjs +7 -7
- package/dist/number/refined-number-utils.mjs.map +1 -1
- package/dist/string/str.d.mts +70 -5
- package/dist/string/str.d.mts.map +1 -1
- package/dist/string/str.mjs +72 -26
- package/dist/string/str.mjs.map +1 -1
- package/package.json +1 -1
- package/src/array/impl/array-utils-length-bounded-array-cast.mts +204 -39
- package/src/array/impl/array-utils-length-bounded-array-cast.test.mts +126 -0
- package/src/number/branded-types/int16.mts +13 -8
- package/src/number/branded-types/int32.mts +13 -8
- package/src/number/branded-types/negative-finite-number.mts +15 -8
- package/src/number/branded-types/negative-finite-number.test.mts +4 -4
- package/src/number/branded-types/negative-int.mts +13 -8
- package/src/number/branded-types/negative-int.test.mts +5 -5
- package/src/number/branded-types/negative-safe-int.mts +15 -9
- package/src/number/branded-types/negative-safe-int.test.mts +4 -4
- package/src/number/branded-types/non-negative-finite-number.mts +12 -7
- package/src/number/branded-types/non-negative-int16.mts +14 -8
- package/src/number/branded-types/non-negative-int32.mts +14 -8
- package/src/number/branded-types/non-positive-finite-number.mts +12 -7
- package/src/number/branded-types/non-positive-finite-number.test.mts +7 -7
- package/src/number/branded-types/non-positive-int.mts +12 -7
- package/src/number/branded-types/non-positive-int.test.mts +7 -7
- package/src/number/branded-types/non-positive-safe-int.mts +15 -8
- package/src/number/branded-types/non-positive-safe-int.test.mts +7 -7
- package/src/number/branded-types/non-zero-int16.mts +14 -8
- package/src/number/branded-types/non-zero-int32.mts +14 -8
- package/src/number/branded-types/non-zero-safe-int.mts +14 -8
- package/src/number/branded-types/non-zero-uint16.mts +14 -8
- package/src/number/branded-types/non-zero-uint32.mts +14 -8
- package/src/number/branded-types/positive-finite-number.mts +14 -7
- package/src/number/branded-types/positive-int.mts +14 -9
- package/src/number/branded-types/positive-int16.mts +14 -8
- package/src/number/branded-types/positive-int32.mts +14 -8
- package/src/number/branded-types/positive-safe-int.mts +17 -11
- package/src/number/branded-types/positive-uint16.mts +14 -8
- package/src/number/branded-types/positive-uint32.mts +14 -8
- package/src/number/branded-types/safe-int.mts +16 -11
- package/src/number/branded-types/safe-uint.mts +13 -8
- package/src/number/branded-types/uint.mts +15 -11
- package/src/number/branded-types/uint16.mts +13 -8
- package/src/number/branded-types/uint32.mts +13 -8
- package/src/number/enum/int8.mts +20 -14
- package/src/number/enum/int8.test.mts +7 -4
- package/src/number/enum/uint8.mts +20 -14
- package/src/number/enum/uint8.test.mts +7 -4
- package/src/number/refined-number-utils.mts +22 -19
- package/src/string/str.mts +201 -39
- package/src/string/str.test.mts +76 -0
package/src/string/str.mts
CHANGED
|
@@ -3,6 +3,7 @@ import {
|
|
|
3
3
|
type FixedLengthString,
|
|
4
4
|
type MaxLengthString,
|
|
5
5
|
type MinLengthString,
|
|
6
|
+
type NonEmptyString,
|
|
6
7
|
type SupportedLength,
|
|
7
8
|
} from 'ts-type-forge';
|
|
8
9
|
|
|
@@ -200,6 +201,9 @@ export namespace Str {
|
|
|
200
201
|
* length is verified at runtime, and the original string type (e.g. literal
|
|
201
202
|
* types) is preserved via intersection.
|
|
202
203
|
*
|
|
204
|
+
* Supports a curried overload: calling it with only `minLength` returns a
|
|
205
|
+
* function that casts a string, which is convenient for `pipe`/`map`.
|
|
206
|
+
*
|
|
203
207
|
* @example
|
|
204
208
|
*
|
|
205
209
|
* ```ts
|
|
@@ -209,6 +213,11 @@ export namespace Str {
|
|
|
209
213
|
*
|
|
210
214
|
* assert.isTrue(nonEmpty.length >= 12);
|
|
211
215
|
*
|
|
216
|
+
* // curried version
|
|
217
|
+
* const asPassword = Str.asMinLengthString(12);
|
|
218
|
+
*
|
|
219
|
+
* assert.strictEqual(asPassword('another-secret'), 'another-secret');
|
|
220
|
+
*
|
|
212
221
|
* assert.throws(() => Str.asMinLengthString('short', 12)); // length 5 < 12
|
|
213
222
|
* ```
|
|
214
223
|
*
|
|
@@ -219,21 +228,41 @@ export namespace Str {
|
|
|
219
228
|
* @returns `s` typed as `MinLengthString<MinLength> & S`.
|
|
220
229
|
* @throws {TypeError} If `s.length < minLength`.
|
|
221
230
|
*/
|
|
222
|
-
export
|
|
231
|
+
export function asMinLengthString<
|
|
223
232
|
S extends string,
|
|
224
233
|
MinLength extends SupportedLength,
|
|
225
|
-
>(
|
|
226
|
-
|
|
234
|
+
>(s: S, minLength: MinLength): MinLengthString<MinLength> & S;
|
|
235
|
+
|
|
236
|
+
// Curried version
|
|
237
|
+
export function asMinLengthString<MinLength extends SupportedLength>(
|
|
227
238
|
minLength: MinLength,
|
|
228
|
-
): MinLengthString<MinLength> & S
|
|
229
|
-
if (!isMinLengthString(s, minLength)) {
|
|
230
|
-
throw new TypeError(
|
|
231
|
-
`Expected a string of length >= ${minLength}, got a string of length ${s.length}`,
|
|
232
|
-
);
|
|
233
|
-
}
|
|
239
|
+
): <S extends string>(s: S) => MinLengthString<MinLength> & S;
|
|
234
240
|
|
|
235
|
-
|
|
236
|
-
|
|
241
|
+
export function asMinLengthString(
|
|
242
|
+
...args:
|
|
243
|
+
| readonly [s: string, minLength: SupportedLength]
|
|
244
|
+
| readonly [minLength: SupportedLength]
|
|
245
|
+
): string | ((s: string) => string) {
|
|
246
|
+
switch (args.length) {
|
|
247
|
+
case 2: {
|
|
248
|
+
const [s, minLength] = args;
|
|
249
|
+
|
|
250
|
+
if (!isMinLengthString(s, minLength)) {
|
|
251
|
+
throw new TypeError(
|
|
252
|
+
`Expected a string of length >= ${minLength}, got a string of length ${s.length}`,
|
|
253
|
+
);
|
|
254
|
+
}
|
|
255
|
+
|
|
256
|
+
return s;
|
|
257
|
+
}
|
|
258
|
+
|
|
259
|
+
case 1: {
|
|
260
|
+
const [minLength] = args;
|
|
261
|
+
|
|
262
|
+
return (s) => asMinLengthString(s, minLength);
|
|
263
|
+
}
|
|
264
|
+
}
|
|
265
|
+
}
|
|
237
266
|
|
|
238
267
|
/**
|
|
239
268
|
* Casts a string to `MaxLengthString<MaxLength>` after checking that it has
|
|
@@ -243,6 +272,9 @@ export namespace Str {
|
|
|
243
272
|
* length is verified at runtime, and the original string type (e.g. literal
|
|
244
273
|
* types) is preserved via intersection.
|
|
245
274
|
*
|
|
275
|
+
* Supports a curried overload: calling it with only `maxLength` returns a
|
|
276
|
+
* function that casts a string, which is convenient for `pipe`/`map`.
|
|
277
|
+
*
|
|
246
278
|
* @example
|
|
247
279
|
*
|
|
248
280
|
* ```ts
|
|
@@ -252,6 +284,11 @@ export namespace Str {
|
|
|
252
284
|
*
|
|
253
285
|
* assert.isTrue(relaxed.length <= 32);
|
|
254
286
|
*
|
|
287
|
+
* // curried version
|
|
288
|
+
* const asUserName = Str.asMaxLengthString(32);
|
|
289
|
+
*
|
|
290
|
+
* assert.strictEqual(asUserName('another-user'), 'another-user');
|
|
291
|
+
*
|
|
255
292
|
* assert.throws(() => Str.asMaxLengthString('noshiro', 3)); // length 7 > 3
|
|
256
293
|
* ```
|
|
257
294
|
*
|
|
@@ -262,21 +299,41 @@ export namespace Str {
|
|
|
262
299
|
* @returns `s` typed as `MaxLengthString<MaxLength> & S`.
|
|
263
300
|
* @throws {TypeError} If `s.length > maxLength`.
|
|
264
301
|
*/
|
|
265
|
-
export
|
|
302
|
+
export function asMaxLengthString<
|
|
266
303
|
S extends string,
|
|
267
304
|
MaxLength extends SupportedLength,
|
|
268
|
-
>(
|
|
269
|
-
|
|
305
|
+
>(s: S, maxLength: MaxLength): MaxLengthString<MaxLength> & S;
|
|
306
|
+
|
|
307
|
+
// Curried version
|
|
308
|
+
export function asMaxLengthString<MaxLength extends SupportedLength>(
|
|
270
309
|
maxLength: MaxLength,
|
|
271
|
-
): MaxLengthString<MaxLength> & S
|
|
272
|
-
if (!isMaxLengthString(s, maxLength)) {
|
|
273
|
-
throw new TypeError(
|
|
274
|
-
`Expected a string of length <= ${maxLength}, got a string of length ${s.length}`,
|
|
275
|
-
);
|
|
276
|
-
}
|
|
310
|
+
): <S extends string>(s: S) => MaxLengthString<MaxLength> & S;
|
|
277
311
|
|
|
278
|
-
|
|
279
|
-
|
|
312
|
+
export function asMaxLengthString(
|
|
313
|
+
...args:
|
|
314
|
+
| readonly [s: string, maxLength: SupportedLength]
|
|
315
|
+
| readonly [maxLength: SupportedLength]
|
|
316
|
+
): string | ((s: string) => string) {
|
|
317
|
+
switch (args.length) {
|
|
318
|
+
case 2: {
|
|
319
|
+
const [s, maxLength] = args;
|
|
320
|
+
|
|
321
|
+
if (!isMaxLengthString(s, maxLength)) {
|
|
322
|
+
throw new TypeError(
|
|
323
|
+
`Expected a string of length <= ${maxLength}, got a string of length ${s.length}`,
|
|
324
|
+
);
|
|
325
|
+
}
|
|
326
|
+
|
|
327
|
+
return s;
|
|
328
|
+
}
|
|
329
|
+
|
|
330
|
+
case 1: {
|
|
331
|
+
const [maxLength] = args;
|
|
332
|
+
|
|
333
|
+
return (s) => asMaxLengthString(s, maxLength);
|
|
334
|
+
}
|
|
335
|
+
}
|
|
336
|
+
}
|
|
280
337
|
|
|
281
338
|
/**
|
|
282
339
|
* Casts a string to `BoundedLengthString<MinLength, MaxLength>` after
|
|
@@ -287,6 +344,10 @@ export namespace Str {
|
|
|
287
344
|
* the length is verified at runtime, and the original string type (e.g.
|
|
288
345
|
* literal types) is preserved via intersection.
|
|
289
346
|
*
|
|
347
|
+
* Supports a curried overload: calling it with only `minLength` and
|
|
348
|
+
* `maxLength` returns a function that casts a string, which is convenient for
|
|
349
|
+
* `pipe`/`map`.
|
|
350
|
+
*
|
|
290
351
|
* @example
|
|
291
352
|
*
|
|
292
353
|
* ```ts
|
|
@@ -296,6 +357,11 @@ export namespace Str {
|
|
|
296
357
|
*
|
|
297
358
|
* assert.isTrue(relaxed.length >= 8 && relaxed.length <= 16);
|
|
298
359
|
*
|
|
360
|
+
* // curried version
|
|
361
|
+
* const asUserId = Str.asBoundedLengthString(8, 16);
|
|
362
|
+
*
|
|
363
|
+
* assert.strictEqual(asUserId('user-87654321'), 'user-87654321');
|
|
364
|
+
*
|
|
299
365
|
* assert.throws(() => Str.asBoundedLengthString('user', 8, 16)); // length 4 < 8
|
|
300
366
|
* ```
|
|
301
367
|
*
|
|
@@ -308,7 +374,7 @@ export namespace Str {
|
|
|
308
374
|
* @returns `s` typed as `BoundedLengthString<MinLength, MaxLength> & S`.
|
|
309
375
|
* @throws {TypeError} If `s.length < minLength` or `s.length > maxLength`.
|
|
310
376
|
*/
|
|
311
|
-
export
|
|
377
|
+
export function asBoundedLengthString<
|
|
312
378
|
S extends string,
|
|
313
379
|
MinLength extends SupportedLength,
|
|
314
380
|
MaxLength extends SupportedLength,
|
|
@@ -316,15 +382,46 @@ export namespace Str {
|
|
|
316
382
|
s: S,
|
|
317
383
|
minLength: MinLength,
|
|
318
384
|
maxLength: MaxLength,
|
|
319
|
-
): BoundedLengthString<MinLength, MaxLength> & S
|
|
320
|
-
if (!isBoundedLengthString(s, minLength, maxLength)) {
|
|
321
|
-
throw new TypeError(
|
|
322
|
-
`Expected a string of length in [${minLength}, ${maxLength}], got a string of length ${s.length}`,
|
|
323
|
-
);
|
|
324
|
-
}
|
|
385
|
+
): BoundedLengthString<MinLength, MaxLength> & S;
|
|
325
386
|
|
|
326
|
-
|
|
327
|
-
|
|
387
|
+
// Curried version
|
|
388
|
+
export function asBoundedLengthString<
|
|
389
|
+
MinLength extends SupportedLength,
|
|
390
|
+
MaxLength extends SupportedLength,
|
|
391
|
+
>(
|
|
392
|
+
minLength: MinLength,
|
|
393
|
+
maxLength: MaxLength,
|
|
394
|
+
): <S extends string>(s: S) => BoundedLengthString<MinLength, MaxLength> & S;
|
|
395
|
+
|
|
396
|
+
export function asBoundedLengthString(
|
|
397
|
+
...args:
|
|
398
|
+
| readonly [
|
|
399
|
+
s: string,
|
|
400
|
+
minLength: SupportedLength,
|
|
401
|
+
maxLength: SupportedLength,
|
|
402
|
+
]
|
|
403
|
+
| readonly [minLength: SupportedLength, maxLength: SupportedLength]
|
|
404
|
+
): string | ((s: string) => string) {
|
|
405
|
+
switch (args.length) {
|
|
406
|
+
case 3: {
|
|
407
|
+
const [s, minLength, maxLength] = args;
|
|
408
|
+
|
|
409
|
+
if (!isBoundedLengthString(s, minLength, maxLength)) {
|
|
410
|
+
throw new TypeError(
|
|
411
|
+
`Expected a string of length in [${minLength}, ${maxLength}], got a string of length ${s.length}`,
|
|
412
|
+
);
|
|
413
|
+
}
|
|
414
|
+
|
|
415
|
+
return s;
|
|
416
|
+
}
|
|
417
|
+
|
|
418
|
+
case 2: {
|
|
419
|
+
const [minLength, maxLength] = args;
|
|
420
|
+
|
|
421
|
+
return (s) => asBoundedLengthString(s, minLength, maxLength);
|
|
422
|
+
}
|
|
423
|
+
}
|
|
424
|
+
}
|
|
328
425
|
|
|
329
426
|
/**
|
|
330
427
|
* Casts a string to `FixedLengthString<Length>` after checking that it has
|
|
@@ -334,6 +431,9 @@ export namespace Str {
|
|
|
334
431
|
* length is verified at runtime, and the original string type (e.g. literal
|
|
335
432
|
* types) is preserved via intersection.
|
|
336
433
|
*
|
|
434
|
+
* Supports a curried overload: calling it with only `length` returns a
|
|
435
|
+
* function that casts a string, which is convenient for `pipe`/`map`.
|
|
436
|
+
*
|
|
337
437
|
* @example
|
|
338
438
|
*
|
|
339
439
|
* ```ts
|
|
@@ -343,6 +443,11 @@ export namespace Str {
|
|
|
343
443
|
*
|
|
344
444
|
* assert.isTrue(atMost5.length === 2);
|
|
345
445
|
*
|
|
446
|
+
* // curried version
|
|
447
|
+
* const asCountryCode = Str.asFixedLengthString(2);
|
|
448
|
+
*
|
|
449
|
+
* assert.strictEqual(asCountryCode('US'), 'US');
|
|
450
|
+
*
|
|
346
451
|
* assert.throws(() => Str.asFixedLengthString('JP', 3)); // length 2 !== 3
|
|
347
452
|
* ```
|
|
348
453
|
*
|
|
@@ -353,17 +458,74 @@ export namespace Str {
|
|
|
353
458
|
* @returns `s` typed as `FixedLengthString<Length> & S`.
|
|
354
459
|
* @throws {TypeError} If `s.length !== length`.
|
|
355
460
|
*/
|
|
356
|
-
export
|
|
461
|
+
export function asFixedLengthString<
|
|
357
462
|
S extends string,
|
|
358
463
|
Length extends SupportedLength,
|
|
359
|
-
>(
|
|
360
|
-
|
|
464
|
+
>(s: S, length: Length): FixedLengthString<Length> & S;
|
|
465
|
+
|
|
466
|
+
// Curried version
|
|
467
|
+
export function asFixedLengthString<Length extends SupportedLength>(
|
|
361
468
|
length: Length,
|
|
362
|
-
): FixedLengthString<Length> & S
|
|
363
|
-
|
|
364
|
-
|
|
365
|
-
|
|
366
|
-
|
|
469
|
+
): <S extends string>(s: S) => FixedLengthString<Length> & S;
|
|
470
|
+
|
|
471
|
+
export function asFixedLengthString(
|
|
472
|
+
...args:
|
|
473
|
+
| readonly [s: string, length: SupportedLength]
|
|
474
|
+
| readonly [length: SupportedLength]
|
|
475
|
+
): string | ((s: string) => string) {
|
|
476
|
+
switch (args.length) {
|
|
477
|
+
case 2: {
|
|
478
|
+
const [s, length] = args;
|
|
479
|
+
|
|
480
|
+
if (!isFixedLengthString(s, length)) {
|
|
481
|
+
throw new TypeError(
|
|
482
|
+
`Expected a string of length ${length}, got a string of length ${s.length}`,
|
|
483
|
+
);
|
|
484
|
+
}
|
|
485
|
+
|
|
486
|
+
return s;
|
|
487
|
+
}
|
|
488
|
+
|
|
489
|
+
case 1: {
|
|
490
|
+
const [length] = args;
|
|
491
|
+
|
|
492
|
+
return (s) => asFixedLengthString(s, length);
|
|
493
|
+
}
|
|
494
|
+
}
|
|
495
|
+
}
|
|
496
|
+
|
|
497
|
+
/**
|
|
498
|
+
* Casts a string to `NonEmptyString` after checking that it has at least one
|
|
499
|
+
* character.
|
|
500
|
+
*
|
|
501
|
+
* `NonEmptyString` is an alias of `MinLengthString<1>`, so this is the
|
|
502
|
+
* length-1 specialization of {@link asMinLengthString}. Use this instead of
|
|
503
|
+
* writing `s as unknown as NonEmptyString`: the length is verified at
|
|
504
|
+
* runtime, and the original string type (e.g. literal types) is preserved
|
|
505
|
+
* via intersection.
|
|
506
|
+
*
|
|
507
|
+
* @example
|
|
508
|
+
*
|
|
509
|
+
* ```ts
|
|
510
|
+
* const name = Str.asNonEmptyString('noshiro');
|
|
511
|
+
*
|
|
512
|
+
* const nonEmpty: NonEmptyString = name; // OK
|
|
513
|
+
*
|
|
514
|
+
* assert.isTrue(nonEmpty.length >= 1);
|
|
515
|
+
*
|
|
516
|
+
* assert.throws(() => Str.asNonEmptyString('')); // length 0 < 1
|
|
517
|
+
* ```
|
|
518
|
+
*
|
|
519
|
+
* @template S - The input string type (literal types are preserved).
|
|
520
|
+
* @param s - The string to cast.
|
|
521
|
+
* @returns `s` typed as `NonEmptyString & S`.
|
|
522
|
+
* @throws {TypeError} If `s` is empty.
|
|
523
|
+
*/
|
|
524
|
+
export const asNonEmptyString = <S extends string>(
|
|
525
|
+
s: S,
|
|
526
|
+
): NonEmptyString & S => {
|
|
527
|
+
if (!isMinLengthString(s, 1)) {
|
|
528
|
+
throw new TypeError('Expected a non-empty string, got an empty string');
|
|
367
529
|
}
|
|
368
530
|
|
|
369
531
|
return s;
|
package/src/string/str.test.mts
CHANGED
|
@@ -213,3 +213,79 @@ describe('Str length-bounded casts', () => {
|
|
|
213
213
|
assert.throws(() => Str.asFixedLengthString('JP', 3), TypeError);
|
|
214
214
|
});
|
|
215
215
|
});
|
|
216
|
+
|
|
217
|
+
describe('Str length-bounded casts (curried)', () => {
|
|
218
|
+
test('asMinLengthString returns the value and narrows its type', () => {
|
|
219
|
+
const result = Str.asMinLengthString(3)('very-secret');
|
|
220
|
+
|
|
221
|
+
expectType<typeof result, MinLengthString<3>>('<=');
|
|
222
|
+
|
|
223
|
+
assert.strictEqual(result, 'very-secret');
|
|
224
|
+
});
|
|
225
|
+
|
|
226
|
+
test('asMinLengthString throws when too short', () => {
|
|
227
|
+
assert.throws(() => Str.asMinLengthString(3)('hi'), TypeError);
|
|
228
|
+
});
|
|
229
|
+
|
|
230
|
+
test('asMaxLengthString returns the value and narrows its type', () => {
|
|
231
|
+
const result = Str.asMaxLengthString(32)('noshiro');
|
|
232
|
+
|
|
233
|
+
expectType<typeof result, MaxLengthString<32>>('<=');
|
|
234
|
+
|
|
235
|
+
assert.strictEqual(result, 'noshiro');
|
|
236
|
+
});
|
|
237
|
+
|
|
238
|
+
test('asMaxLengthString throws when too long', () => {
|
|
239
|
+
assert.throws(() => Str.asMaxLengthString(3)('noshiro'), TypeError);
|
|
240
|
+
});
|
|
241
|
+
|
|
242
|
+
test('asBoundedLengthString returns the value and narrows its type', () => {
|
|
243
|
+
const result = Str.asBoundedLengthString(8, 16)('user-12345678');
|
|
244
|
+
|
|
245
|
+
expectType<typeof result, BoundedLengthString<8, 16>>('<=');
|
|
246
|
+
|
|
247
|
+
assert.strictEqual(result, 'user-12345678');
|
|
248
|
+
});
|
|
249
|
+
|
|
250
|
+
test('asBoundedLengthString throws when out of range', () => {
|
|
251
|
+
assert.throws(() => Str.asBoundedLengthString(8, 16)('user'), TypeError);
|
|
252
|
+
});
|
|
253
|
+
|
|
254
|
+
test('asFixedLengthString returns the value and narrows its type', () => {
|
|
255
|
+
const result = Str.asFixedLengthString(2)('JP');
|
|
256
|
+
|
|
257
|
+
expectType<typeof result, FixedLengthString<2>>('<=');
|
|
258
|
+
|
|
259
|
+
assert.strictEqual(result, 'JP');
|
|
260
|
+
});
|
|
261
|
+
|
|
262
|
+
test('asFixedLengthString throws when the length differs', () => {
|
|
263
|
+
assert.throws(() => Str.asFixedLengthString(3)('JP'), TypeError);
|
|
264
|
+
});
|
|
265
|
+
});
|
|
266
|
+
|
|
267
|
+
describe(Str.asNonEmptyString, () => {
|
|
268
|
+
test('returns the value and narrows its type', () => {
|
|
269
|
+
const result = Str.asNonEmptyString('noshiro');
|
|
270
|
+
|
|
271
|
+
expectType<typeof result, NonEmptyString>('<=');
|
|
272
|
+
|
|
273
|
+
expectType<typeof result, MinLengthString<1>>('<=');
|
|
274
|
+
|
|
275
|
+
assert.strictEqual(result, 'noshiro');
|
|
276
|
+
});
|
|
277
|
+
|
|
278
|
+
test('throws when the string is empty', () => {
|
|
279
|
+
assert.throws(() => Str.asNonEmptyString(''), TypeError);
|
|
280
|
+
});
|
|
281
|
+
|
|
282
|
+
test('preserves literal string types', () => {
|
|
283
|
+
const result = Str.asNonEmptyString('hello');
|
|
284
|
+
|
|
285
|
+
expectType<typeof result, 'hello'>('<=');
|
|
286
|
+
|
|
287
|
+
expectType<typeof result, NonEmptyString>('<=');
|
|
288
|
+
|
|
289
|
+
assert.strictEqual(result, 'hello');
|
|
290
|
+
});
|
|
291
|
+
});
|