ts-fortress 4.3.1 → 4.4.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/dist/entry-point.mjs +4 -0
- package/dist/entry-point.mjs.map +1 -1
- package/dist/globals.d.mts +19 -2
- package/dist/index.mjs +4 -0
- package/dist/index.mjs.map +1 -1
- package/dist/predefined/brand/index.d.mts +1 -0
- package/dist/predefined/brand/index.d.mts.map +1 -1
- package/dist/predefined/brand/index.mjs +4 -0
- package/dist/predefined/brand/index.mjs.map +1 -1
- package/dist/predefined/brand/string/email.d.mts +6 -0
- package/dist/predefined/brand/string/email.d.mts.map +1 -0
- package/dist/predefined/brand/string/email.mjs +19 -0
- package/dist/predefined/brand/string/email.mjs.map +1 -0
- package/dist/predefined/brand/string/index.d.mts +5 -0
- package/dist/predefined/brand/string/index.d.mts.map +1 -0
- package/dist/predefined/brand/string/index.mjs +5 -0
- package/dist/predefined/brand/string/index.mjs.map +1 -0
- package/dist/predefined/brand/string/iso-8601.d.mts +69 -0
- package/dist/predefined/brand/string/iso-8601.d.mts.map +1 -0
- package/dist/predefined/brand/string/iso-8601.mjs +82 -0
- package/dist/predefined/brand/string/iso-8601.mjs.map +1 -0
- package/dist/predefined/brand/string/json-string.d.mts +6 -0
- package/dist/predefined/brand/string/json-string.d.mts.map +1 -0
- package/dist/predefined/brand/string/json-string.mjs +24 -0
- package/dist/predefined/brand/string/json-string.mjs.map +1 -0
- package/dist/predefined/brand/string/uuid.d.mts +20 -0
- package/dist/predefined/brand/string/uuid.d.mts.map +1 -0
- package/dist/predefined/brand/string/uuid.mjs +55 -0
- package/dist/predefined/brand/string/uuid.mjs.map +1 -0
- package/dist/predefined/index.mjs +4 -0
- package/dist/predefined/index.mjs.map +1 -1
- package/package.json +1 -1
- package/src/globals.d.mts +19 -2
- package/src/predefined/brand/index.mts +1 -0
- package/src/predefined/brand/string/email.mts +25 -0
- package/src/predefined/brand/string/email.test.mts +88 -0
- package/src/predefined/brand/string/index.mts +4 -0
- package/src/predefined/brand/string/iso-8601.mts +97 -0
- package/src/predefined/brand/string/iso-8601.test.mts +168 -0
- package/src/predefined/brand/string/json-string.mts +32 -0
- package/src/predefined/brand/string/json-string.test.mts +65 -0
- package/src/predefined/brand/string/uuid.mts +97 -0
- package/src/predefined/brand/string/uuid.test.mts +125 -0
package/dist/entry-point.mjs
CHANGED
|
@@ -34,6 +34,10 @@ export { safeUint } from './predefined/brand/number/safe-uint.mjs';
|
|
|
34
34
|
export { uint } from './predefined/brand/number/uint.mjs';
|
|
35
35
|
export { uint16 } from './predefined/brand/number/uint16.mjs';
|
|
36
36
|
export { uint32 } from './predefined/brand/number/uint32.mjs';
|
|
37
|
+
export { email } from './predefined/brand/string/email.mjs';
|
|
38
|
+
export { iso8601 } from './predefined/brand/string/iso-8601.mjs';
|
|
39
|
+
export { jsonString } from './predefined/brand/string/json-string.mjs';
|
|
40
|
+
export { uuid, uuidV4, uuidV6, uuidV7 } from './predefined/brand/string/uuid.mjs';
|
|
37
41
|
export { int8 } from './predefined/int8.mjs';
|
|
38
42
|
export { JsonObject, JsonPrimitive, JsonValue } from './predefined/json.mjs';
|
|
39
43
|
export { nullable } from './predefined/nullable.mjs';
|
package/dist/entry-point.mjs.map
CHANGED
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"entry-point.mjs","sources":[],"sourcesContent":[],"names":[],"mappings":"
|
|
1
|
+
{"version":3,"file":"entry-point.mjs","sources":[],"sourcesContent":[],"names":[],"mappings":";;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;"}
|
package/dist/globals.d.mts
CHANGED
|
@@ -1,5 +1,22 @@
|
|
|
1
|
-
|
|
2
|
-
|
|
3
1
|
/// <reference types="ts-type-forge" />
|
|
4
2
|
|
|
5
3
|
type PartialReadonly<T> = Partial<Readonly<T>>;
|
|
4
|
+
|
|
5
|
+
type Iso8601 = Brand<string, 'Iso8601'>;
|
|
6
|
+
|
|
7
|
+
type Email = Brand<string, 'Email'>;
|
|
8
|
+
|
|
9
|
+
type JsonString = Brand<string, 'JsonString'>;
|
|
10
|
+
|
|
11
|
+
type UuidVersion = UintRangeInclusive<1, 8>;
|
|
12
|
+
|
|
13
|
+
type UuidBaseString<V extends UuidVersion = UuidVersion> =
|
|
14
|
+
`${string}-${string}-${V}${string}-${string}-${string}`;
|
|
15
|
+
|
|
16
|
+
type Uuid = Brand<UuidBaseString, 'Uuid'>;
|
|
17
|
+
|
|
18
|
+
type Uuid4 = Brand<UuidBaseString<4>, 'Uuid'>;
|
|
19
|
+
|
|
20
|
+
type Uuid6 = Brand<UuidBaseString<6>, 'Uuid'>;
|
|
21
|
+
|
|
22
|
+
type Uuid7 = Brand<UuidBaseString<7>, 'Uuid'>;
|
package/dist/index.mjs
CHANGED
|
@@ -33,6 +33,10 @@ export { safeUint } from './predefined/brand/number/safe-uint.mjs';
|
|
|
33
33
|
export { uint } from './predefined/brand/number/uint.mjs';
|
|
34
34
|
export { uint16 } from './predefined/brand/number/uint16.mjs';
|
|
35
35
|
export { uint32 } from './predefined/brand/number/uint32.mjs';
|
|
36
|
+
export { email } from './predefined/brand/string/email.mjs';
|
|
37
|
+
export { iso8601 } from './predefined/brand/string/iso-8601.mjs';
|
|
38
|
+
export { jsonString } from './predefined/brand/string/json-string.mjs';
|
|
39
|
+
export { uuid, uuidV4, uuidV6, uuidV7 } from './predefined/brand/string/uuid.mjs';
|
|
36
40
|
export { int8 } from './predefined/int8.mjs';
|
|
37
41
|
export { JsonObject, JsonPrimitive, JsonValue } from './predefined/json.mjs';
|
|
38
42
|
export { nullable } from './predefined/nullable.mjs';
|
package/dist/index.mjs.map
CHANGED
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"index.mjs","sources":[],"sourcesContent":[],"names":[],"mappings":"
|
|
1
|
+
{"version":3,"file":"index.mjs","sources":[],"sourcesContent":[],"names":[],"mappings":";;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;"}
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"index.d.mts","sourceRoot":"","sources":["../../../src/predefined/brand/index.mts"],"names":[],"mappings":"AAAA,cAAc,oBAAoB,CAAC"}
|
|
1
|
+
{"version":3,"file":"index.d.mts","sourceRoot":"","sources":["../../../src/predefined/brand/index.mts"],"names":[],"mappings":"AAAA,cAAc,oBAAoB,CAAC;AACnC,cAAc,oBAAoB,CAAC"}
|
|
@@ -14,4 +14,8 @@ export { safeUint } from './number/safe-uint.mjs';
|
|
|
14
14
|
export { uint } from './number/uint.mjs';
|
|
15
15
|
export { uint16 } from './number/uint16.mjs';
|
|
16
16
|
export { uint32 } from './number/uint32.mjs';
|
|
17
|
+
export { email } from './string/email.mjs';
|
|
18
|
+
export { iso8601 } from './string/iso-8601.mjs';
|
|
19
|
+
export { jsonString } from './string/json-string.mjs';
|
|
20
|
+
export { uuid, uuidV4, uuidV6, uuidV7 } from './string/uuid.mjs';
|
|
17
21
|
//# sourceMappingURL=index.mjs.map
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"index.mjs","sources":[],"sourcesContent":[],"names":[],"mappings":"
|
|
1
|
+
{"version":3,"file":"index.mjs","sources":[],"sourcesContent":[],"names":[],"mappings":";;;;;;;;;;;;;;;;;;;"}
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"email.d.mts","sourceRoot":"","sources":["../../../../src/predefined/brand/string/email.mts"],"names":[],"mappings":"AACA,OAAO,EAAE,KAAK,IAAI,EAAE,MAAM,mBAAmB,CAAC;AAE9C;;GAEG;AACH,eAAO,MAAM,KAAK,GAAI,eAAe,MAAM,KAAG,IAAI,CAAC,KAAK,CAMpD,CAAC"}
|
|
@@ -0,0 +1,19 @@
|
|
|
1
|
+
import 'ts-data-forge';
|
|
2
|
+
import { brandedString } from '../../../brand/branded-string.mjs';
|
|
3
|
+
|
|
4
|
+
/**
|
|
5
|
+
* @link https://html.spec.whatwg.org/multipage/input.html#valid-e-mail-address
|
|
6
|
+
*/
|
|
7
|
+
const email = (defaultValue) => brandedString({
|
|
8
|
+
is: (s) => regexpEmailAddress.test(s),
|
|
9
|
+
// eslint-disable-next-line total-functions/no-unsafe-type-assertion
|
|
10
|
+
defaultValue: (defaultValue ?? defaultEmail),
|
|
11
|
+
typeName: 'Email',
|
|
12
|
+
});
|
|
13
|
+
const defaultEmail = 'ts-f.o.r.t.r.e.s.s@gmail.com';
|
|
14
|
+
const regexpEmailAddress =
|
|
15
|
+
// eslint-disable-next-line security/detect-unsafe-regex
|
|
16
|
+
/^[a-zA-Z0-9.!#$%&'*+/=?^_`{|}~-]+@[a-zA-Z0-9](?:[a-zA-Z0-9-]{0,61}[a-zA-Z0-9])?(?:\.[a-zA-Z0-9](?:[a-zA-Z0-9-]{0,61}[a-zA-Z0-9])?)*$/gu;
|
|
17
|
+
|
|
18
|
+
export { email };
|
|
19
|
+
//# sourceMappingURL=email.mjs.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"email.mjs","sources":["../../../../src/predefined/brand/string/email.mts"],"sourcesContent":[null],"names":[],"mappings":";;;AAGA;;AAEG;AACI,MAAM,KAAK,GAAG,CAAC,YAAqB,KACzC,aAAa,CAAC;IACZ,EAAE,EAAE,CAAC,CAAC,KAAiB,kBAAkB,CAAC,IAAI,CAAC,CAAC,CAAC;;AAEjD,IAAA,YAAY,GAAG,YAAY,IAAI,YAAY,CAAU;AACrD,IAAA,QAAQ,EAAE,OAAO;AAClB,CAAA;AAEH,MAAM,YAAY,GAAG,8BAA8B;AAEnD,MAAM,kBAAkB;AACtB;AACA,wIAAwI;;;;"}
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"index.d.mts","sourceRoot":"","sources":["../../../../src/predefined/brand/string/index.mts"],"names":[],"mappings":"AAAA,cAAc,aAAa,CAAC;AAC5B,cAAc,gBAAgB,CAAC;AAC/B,cAAc,mBAAmB,CAAC;AAClC,cAAc,YAAY,CAAC"}
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"index.mjs","sources":[],"sourcesContent":[],"names":[],"mappings":";;;"}
|
|
@@ -0,0 +1,69 @@
|
|
|
1
|
+
import { type Type, type TypeOf } from '../../../type.mjs';
|
|
2
|
+
/**
|
|
3
|
+
* @link https://github.com/validatorjs/validator.js/tree/v13.1.17?tab=readme-ov-file#validators
|
|
4
|
+
* @link https://developer.mozilla.org/ja/docs/Web/JavaScript/Reference/Global_Objects/Date/toISOString
|
|
5
|
+
* @link https://tc39.es/ecma262/multipage/numbers-and-dates.html#sec-date-time-string-format
|
|
6
|
+
*/
|
|
7
|
+
export declare const iso8601: (options?: ISO8601ValidatorOption) => Type<Iso8601>;
|
|
8
|
+
declare const ISO8601ValidatorOption: import("../../../type.mjs").RecordType<{
|
|
9
|
+
/**
|
|
10
|
+
* If strict is true, date strings with invalid dates like 2009-02-29 will be invalid.
|
|
11
|
+
*/
|
|
12
|
+
readonly strict: Readonly<{
|
|
13
|
+
typeName: string;
|
|
14
|
+
defaultValue: boolean;
|
|
15
|
+
is: (a: unknown) => a is boolean;
|
|
16
|
+
assertIs: (a: unknown) => asserts a is boolean;
|
|
17
|
+
cast: (a: unknown) => boolean;
|
|
18
|
+
fill: (a: unknown) => boolean;
|
|
19
|
+
validate: (a: unknown) => import("ts-data-forge").Result<boolean, readonly Readonly<{
|
|
20
|
+
path: readonly string[];
|
|
21
|
+
actualValue: unknown;
|
|
22
|
+
expectedType: string;
|
|
23
|
+
typeName: string;
|
|
24
|
+
message: string | undefined;
|
|
25
|
+
}>[]>;
|
|
26
|
+
optional?: true;
|
|
27
|
+
}>;
|
|
28
|
+
/**
|
|
29
|
+
* If strictSeparator is true, date strings with date and time separated by anything other than a T will be invalid.
|
|
30
|
+
*/
|
|
31
|
+
readonly strictSeparator: Readonly<{
|
|
32
|
+
typeName: string;
|
|
33
|
+
defaultValue: boolean;
|
|
34
|
+
is: (a: unknown) => a is boolean;
|
|
35
|
+
assertIs: (a: unknown) => asserts a is boolean;
|
|
36
|
+
cast: (a: unknown) => boolean;
|
|
37
|
+
fill: (a: unknown) => boolean;
|
|
38
|
+
validate: (a: unknown) => import("ts-data-forge").Result<boolean, readonly Readonly<{
|
|
39
|
+
path: readonly string[];
|
|
40
|
+
actualValue: unknown;
|
|
41
|
+
expectedType: string;
|
|
42
|
+
typeName: string;
|
|
43
|
+
message: string | undefined;
|
|
44
|
+
}>[]>;
|
|
45
|
+
optional?: true;
|
|
46
|
+
}>;
|
|
47
|
+
/**
|
|
48
|
+
* @default `new Date().toISOString()`
|
|
49
|
+
*/
|
|
50
|
+
readonly defaultValue: Readonly<{
|
|
51
|
+
typeName: string;
|
|
52
|
+
defaultValue: string;
|
|
53
|
+
is: (a: unknown) => a is string;
|
|
54
|
+
assertIs: (a: unknown) => asserts a is string;
|
|
55
|
+
cast: (a: unknown) => string;
|
|
56
|
+
fill: (a: unknown) => string;
|
|
57
|
+
validate: (a: unknown) => import("ts-data-forge").Result<string, readonly Readonly<{
|
|
58
|
+
path: readonly string[];
|
|
59
|
+
actualValue: unknown;
|
|
60
|
+
expectedType: string;
|
|
61
|
+
typeName: string;
|
|
62
|
+
message: string | undefined;
|
|
63
|
+
}>[]>;
|
|
64
|
+
optional?: true;
|
|
65
|
+
}>;
|
|
66
|
+
}>;
|
|
67
|
+
type ISO8601ValidatorOption = TypeOf<typeof ISO8601ValidatorOption>;
|
|
68
|
+
export {};
|
|
69
|
+
//# sourceMappingURL=iso-8601.d.mts.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"iso-8601.d.mts","sourceRoot":"","sources":["../../../../src/predefined/brand/string/iso-8601.mts"],"names":[],"mappings":"AAGA,OAAO,EAAE,KAAK,IAAI,EAAE,KAAK,MAAM,EAAE,MAAM,mBAAmB,CAAC;AAE3D;;;;GAIG;AACH,eAAO,MAAM,OAAO,GAAI,UAAU,sBAAsB,KAAG,IAAI,CAAC,OAAO,CAUtE,CAAC;AAEF,QAAA,MAAM,sBAAsB;IAC1B;;OAEG;;;;;;;;;;;;;;;;;IAGH;;OAEG;;;;;;;;;;;;;;;;;IAGH;;OAEG;;;;;;;;;;;;;;;;;EAEH,CAAC;AAEH,KAAK,sBAAsB,GAAG,MAAM,CAAC,OAAO,sBAAsB,CAAC,CAAC"}
|
|
@@ -0,0 +1,82 @@
|
|
|
1
|
+
import 'ts-data-forge';
|
|
2
|
+
import { boolean } from '../../../primitives/boolean.mjs';
|
|
3
|
+
import { string } from '../../../primitives/string.mjs';
|
|
4
|
+
import { brandedString } from '../../../brand/branded-string.mjs';
|
|
5
|
+
import { record } from '../../../record/record.mjs';
|
|
6
|
+
|
|
7
|
+
/**
|
|
8
|
+
* @link https://github.com/validatorjs/validator.js/tree/v13.1.17?tab=readme-ov-file#validators
|
|
9
|
+
* @link https://developer.mozilla.org/ja/docs/Web/JavaScript/Reference/Global_Objects/Date/toISOString
|
|
10
|
+
* @link https://tc39.es/ecma262/multipage/numbers-and-dates.html#sec-date-time-string-format
|
|
11
|
+
*/
|
|
12
|
+
const iso8601 = (options) => {
|
|
13
|
+
const filledOptions = ISO8601ValidatorOption.fill(options);
|
|
14
|
+
const is = isISO8601(filledOptions);
|
|
15
|
+
return brandedString({
|
|
16
|
+
is,
|
|
17
|
+
// eslint-disable-next-line total-functions/no-unsafe-type-assertion
|
|
18
|
+
defaultValue: filledOptions.defaultValue,
|
|
19
|
+
typeName: 'Iso8601',
|
|
20
|
+
});
|
|
21
|
+
};
|
|
22
|
+
const ISO8601ValidatorOption = record({
|
|
23
|
+
/**
|
|
24
|
+
* If strict is true, date strings with invalid dates like 2009-02-29 will be invalid.
|
|
25
|
+
*/
|
|
26
|
+
strict: boolean(false),
|
|
27
|
+
/**
|
|
28
|
+
* If strictSeparator is true, date strings with date and time separated by anything other than a T will be invalid.
|
|
29
|
+
*/
|
|
30
|
+
strictSeparator: boolean(false),
|
|
31
|
+
/**
|
|
32
|
+
* @default `new Date().toISOString()`
|
|
33
|
+
*/
|
|
34
|
+
defaultValue: string(new Date().toISOString()),
|
|
35
|
+
});
|
|
36
|
+
const isISO8601 = (options) => (str) => {
|
|
37
|
+
const filledOptions = ISO8601ValidatorOption.fill(options);
|
|
38
|
+
const check = filledOptions.strictSeparator
|
|
39
|
+
? regexpIso8601StrictSeparator.test(str)
|
|
40
|
+
: regexpIso8601.test(str);
|
|
41
|
+
if (check && filledOptions.strict)
|
|
42
|
+
return isValidDate(str);
|
|
43
|
+
return check;
|
|
44
|
+
};
|
|
45
|
+
// from http://goo.gl/0ejHHW
|
|
46
|
+
const regexpIso8601 =
|
|
47
|
+
// eslint-disable-next-line security/detect-unsafe-regex
|
|
48
|
+
/^([+-]?\d{4}(?!\d{2}\b))((-?)((0[1-9]|1[0-2])(\3([12]\d|0[1-9]|3[01]))?|W([0-4]\d|5[0-3])(-?[1-7])?|(00[1-9]|0[1-9]\d|[12]\d{2}|3([0-5]\d|6[1-6])))([\sT]((([01]\d|2[0-3])((:?)[0-5]\d)?|24:?00)([,.]\d+(?!:))?)?(\17[0-5]\d([,.]\d+)?)?([Zz]|([+-])([01]\d|2[0-3]):?([0-5]\d)?)?)?)?$/u;
|
|
49
|
+
// same as above, except with a strict 'T' separator between date and time
|
|
50
|
+
const regexpIso8601StrictSeparator =
|
|
51
|
+
// eslint-disable-next-line security/detect-unsafe-regex
|
|
52
|
+
/^([+-]?\d{4}(?!\d{2}\b))((-?)((0[1-9]|1[0-2])(\3([12]\d|0[1-9]|3[01]))?|W([0-4]\d|5[0-3])(-?[1-7])?|(00[1-9]|0[1-9]\d|[12]\d{2}|3([0-5]\d|6[1-6])))(T((([01]\d|2[0-3])((:?)[0-5]\d)?|24:?00)([,.]\d+(?!:))?)?(\17[0-5]\d([,.]\d+)?)?([Zz]|([+-])([01]\d|2[0-3]):?([0-5]\d)?)?)?)?$/u;
|
|
53
|
+
const isValidDate = (str) => {
|
|
54
|
+
// str must have passed the ISO8601 check
|
|
55
|
+
// this check is meant to catch invalid dates
|
|
56
|
+
// like 2009-02-31
|
|
57
|
+
// first check for ordinal dates
|
|
58
|
+
const ordinalMatch = /^(\d{4})-?(\d{3})([ T]\.*|$)/u.exec(str);
|
|
59
|
+
if (ordinalMatch !== null) {
|
|
60
|
+
const oYear = Number(ordinalMatch[1]);
|
|
61
|
+
const oDay = Number(ordinalMatch[2]);
|
|
62
|
+
// if is leap year
|
|
63
|
+
if ((oYear % 4 === 0 && oYear % 100 !== 0) || oYear % 400 === 0)
|
|
64
|
+
return oDay <= 366;
|
|
65
|
+
return oDay <= 365;
|
|
66
|
+
}
|
|
67
|
+
const match = /(\d{4})-?(\d{0,2})-?(\d*)/u.exec(str)?.map(Number);
|
|
68
|
+
const year = match?.[1];
|
|
69
|
+
const month = match?.[2];
|
|
70
|
+
const date = match?.[3];
|
|
71
|
+
// create a date object and compare
|
|
72
|
+
const d = new Date(`${year}-${`0${month ?? 1}`.slice(-2)}-${`0${date ?? 1}`.slice(-2)}`);
|
|
73
|
+
if (month !== undefined && date !== undefined) {
|
|
74
|
+
return (d.getUTCFullYear() === year &&
|
|
75
|
+
d.getUTCMonth() + 1 === month &&
|
|
76
|
+
d.getUTCDate() === date);
|
|
77
|
+
}
|
|
78
|
+
return true;
|
|
79
|
+
};
|
|
80
|
+
|
|
81
|
+
export { iso8601 };
|
|
82
|
+
//# sourceMappingURL=iso-8601.mjs.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"iso-8601.mjs","sources":["../../../../src/predefined/brand/string/iso-8601.mts"],"sourcesContent":[null],"names":[],"mappings":";;;;;;AAKA;;;;AAIG;AACI,MAAM,OAAO,GAAG,CAAC,OAAgC,KAAmB;IACzE,MAAM,aAAa,GAAG,sBAAsB,CAAC,IAAI,CAAC,OAAO,CAAC;AAC1D,IAAA,MAAM,EAAE,GAAG,SAAS,CAAC,aAAa,CAAC;AAEnC,IAAA,OAAO,aAAa,CAAC;QACnB,EAAE;;QAEF,YAAY,EAAE,aAAa,CAAC,YAAuB;AACnD,QAAA,QAAQ,EAAE,SAAS;AACpB,KAAA,CAAC;AACJ;AAEA,MAAM,sBAAsB,GAAG,MAAM,CAAC;AACpC;;AAEG;AACH,IAAA,MAAM,EAAE,OAAO,CAAC,KAAK,CAAC;AAEtB;;AAEG;AACH,IAAA,eAAe,EAAE,OAAO,CAAC,KAAK,CAAC;AAE/B;;AAEG;IACH,YAAY,EAAE,MAAM,CAAC,IAAI,IAAI,EAAE,CAAC,WAAW,EAAE,CAAC;AAC/C,CAAA,CAAC;AAIF,MAAM,SAAS,GACb,CAAC,OAAqE,KACtE,CAAC,GAAW,KAAoB;IAC9B,MAAM,aAAa,GAAG,sBAAsB,CAAC,IAAI,CAAC,OAAO,CAAC;AAE1D,IAAA,MAAM,KAAK,GAAG,aAAa,CAAC;AAC1B,UAAE,4BAA4B,CAAC,IAAI,CAAC,GAAG;AACvC,UAAE,aAAa,CAAC,IAAI,CAAC,GAAG,CAAC;AAE3B,IAAA,IAAI,KAAK,IAAI,aAAa,CAAC,MAAM;AAAE,QAAA,OAAO,WAAW,CAAC,GAAG,CAAC;AAC1D,IAAA,OAAO,KAAK;AACd,CAAC;AAEH;AACA,MAAM,aAAa;AACjB;AACA,yRAAyR;AAE3R;AACA,MAAM,4BAA4B;AAChC;AACA,qRAAqR;AAEvR,MAAM,WAAW,GAAG,CAAC,GAAW,KAAa;;;;;IAK3C,MAAM,YAAY,GAAG,+BAA+B,CAAC,IAAI,CAAC,GAAG,CAAC;AAC9D,IAAA,IAAI,YAAY,KAAK,IAAI,EAAE;QACzB,MAAM,KAAK,GAAG,MAAM,CAAC,YAAY,CAAC,CAAC,CAAC,CAAC;QACrC,MAAM,IAAI,GAAG,MAAM,CAAC,YAAY,CAAC,CAAC,CAAC,CAAC;;AAEpC,QAAA,IAAI,CAAC,KAAK,GAAG,CAAC,KAAK,CAAC,IAAI,KAAK,GAAG,GAAG,KAAK,CAAC,KAAK,KAAK,GAAG,GAAG,KAAK,CAAC;YAC7D,OAAO,IAAI,IAAI,GAAG;QACpB,OAAO,IAAI,IAAI,GAAG;IACpB;AACA,IAAA,MAAM,KAAK,GAAG,4BAA4B,CAAC,IAAI,CAAC,GAAG,CAAC,EAAE,GAAG,CAAC,MAAM,CAAC;AACjE,IAAA,MAAM,IAAI,GAAG,KAAK,GAAG,CAAC,CAAC;AACvB,IAAA,MAAM,KAAK,GAAuB,KAAK,GAAG,CAAC,CAAC;AAC5C,IAAA,MAAM,IAAI,GAAuB,KAAK,GAAG,CAAC,CAAC;;AAG3C,IAAA,MAAM,CAAC,GAAG,IAAI,IAAI,CAChB,CAAA,EAAG,IAAI,CAAA,CAAA,EAAI,CAAA,CAAA,EAAI,KAAK,IAAI,CAAC,CAAA,CAAE,CAAC,KAAK,CAAC,EAAE,CAAC,CAAA,CAAA,EAAI,CAAA,CAAA,EAAI,IAAI,IAAI,CAAC,CAAA,CAAE,CAAC,KAAK,CAAC,EAAE,CAAC,CAAA,CAAE,CACrE;IAED,IAAI,KAAK,KAAK,SAAS,IAAI,IAAI,KAAK,SAAS,EAAE;AAC7C,QAAA,QACE,CAAC,CAAC,cAAc,EAAE,KAAK,IAAI;AAC3B,YAAA,CAAC,CAAC,WAAW,EAAE,GAAG,CAAC,KAAK,KAAK;AAC7B,YAAA,CAAC,CAAC,UAAU,EAAE,KAAK,IAAI;IAE3B;AACA,IAAA,OAAO,IAAI;AACb,CAAC;;;;"}
|
|
@@ -0,0 +1,6 @@
|
|
|
1
|
+
import { type Type } from '../../../type.mjs';
|
|
2
|
+
/**
|
|
3
|
+
* @link https://github.com/validatorjs/validator.js/tree/v13.1.17?tab=readme-ov-file#validators
|
|
4
|
+
*/
|
|
5
|
+
export declare const jsonString: (defaultValue?: string) => Type<JsonString>;
|
|
6
|
+
//# sourceMappingURL=json-string.d.mts.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"json-string.d.mts","sourceRoot":"","sources":["../../../../src/predefined/brand/string/json-string.mts"],"names":[],"mappings":"AAEA,OAAO,EAAE,KAAK,IAAI,EAAE,MAAM,mBAAmB,CAAC;AAE9C;;GAEG;AACH,eAAO,MAAM,UAAU,GACrB,eAAc,MAA0B,KACvC,IAAI,CAAC,UAAU,CAMd,CAAC"}
|
|
@@ -0,0 +1,24 @@
|
|
|
1
|
+
import { isNonNullObject } from 'ts-data-forge';
|
|
2
|
+
import { brandedString } from '../../../brand/branded-string.mjs';
|
|
3
|
+
|
|
4
|
+
/**
|
|
5
|
+
* @link https://github.com/validatorjs/validator.js/tree/v13.1.17?tab=readme-ov-file#validators
|
|
6
|
+
*/
|
|
7
|
+
const jsonString = (defaultValue = defaultJsonString) => brandedString({
|
|
8
|
+
is: isJsonString,
|
|
9
|
+
// eslint-disable-next-line total-functions/no-unsafe-type-assertion
|
|
10
|
+
defaultValue: defaultValue,
|
|
11
|
+
typeName: 'JsonString',
|
|
12
|
+
});
|
|
13
|
+
const defaultJsonString = '{}';
|
|
14
|
+
const isJsonString = (str) => {
|
|
15
|
+
try {
|
|
16
|
+
return isNonNullObject(JSON.parse(str));
|
|
17
|
+
}
|
|
18
|
+
catch {
|
|
19
|
+
return false;
|
|
20
|
+
}
|
|
21
|
+
};
|
|
22
|
+
|
|
23
|
+
export { jsonString };
|
|
24
|
+
//# sourceMappingURL=json-string.mjs.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"json-string.mjs","sources":["../../../../src/predefined/brand/string/json-string.mts"],"sourcesContent":[null],"names":[],"mappings":";;;AAIA;;AAEG;AACI,MAAM,UAAU,GAAG,CACxB,YAAA,GAAuB,iBAAiB,KAExC,aAAa,CAAC;AACZ,IAAA,EAAE,EAAE,YAAY;;AAEhB,IAAA,YAAY,EAAE,YAA0B;AACxC,IAAA,QAAQ,EAAE,YAAY;AACvB,CAAA;AAEH,MAAM,iBAAiB,GAAG,IAAI;AAE9B,MAAM,YAAY,GAAG,CAAC,GAAW,KAAuB;AACtD,IAAA,IAAI;QACF,OAAO,eAAe,CAAC,IAAI,CAAC,KAAK,CAAC,GAAG,CAAC,CAAC;IACzC;AAAE,IAAA,MAAM;AACN,QAAA,OAAO,KAAK;IACd;AACF,CAAC;;;;"}
|
|
@@ -0,0 +1,20 @@
|
|
|
1
|
+
import { type Type } from '../../../type.mjs';
|
|
2
|
+
/**
|
|
3
|
+
* @link https://github.com/validatorjs/validator.js/tree/v13.1.17?tab=readme-ov-file#validators
|
|
4
|
+
*/
|
|
5
|
+
export declare const uuid: <V extends UuidVersion | UuidVersionAdditionalOption>(options?: UuidValidatorOption<V>) => Type<UuidOf<V>>;
|
|
6
|
+
type UuidVersionAdditionalOption = 'nil' | 'max' | 'all' | 'loose';
|
|
7
|
+
type UuidOf<V extends UuidVersion | UuidVersionAdditionalOption> = TypeEq<V, 4> extends true ? Uuid4 : TypeEq<V, 6> extends true ? Uuid6 : TypeEq<V, 7> extends true ? Uuid7 : Uuid;
|
|
8
|
+
export declare const uuidV4: (defaultValue?: string) => Type<Uuid4>;
|
|
9
|
+
export declare const uuidV6: (defaultValue?: string) => Type<Uuid6>;
|
|
10
|
+
export declare const uuidV7: (defaultValue?: string) => Type<Uuid7>;
|
|
11
|
+
type UuidValidatorOption<V extends UuidVersion | UuidVersionAdditionalOption> = Readonly<{
|
|
12
|
+
/**
|
|
13
|
+
* @param version One of 1-8, 'nil', 'max', 'all' or 'loose'. The 'loose' option checks if the string is a UUID-like string with hexadecimal values, ignoring RFC9562.
|
|
14
|
+
*/
|
|
15
|
+
version?: V;
|
|
16
|
+
typeName?: string;
|
|
17
|
+
defaultValue?: string;
|
|
18
|
+
}>;
|
|
19
|
+
export {};
|
|
20
|
+
//# sourceMappingURL=uuid.d.mts.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"uuid.d.mts","sourceRoot":"","sources":["../../../../src/predefined/brand/string/uuid.mts"],"names":[],"mappings":"AAEA,OAAO,EAAE,KAAK,IAAI,EAAE,MAAM,mBAAmB,CAAC;AAE9C;;GAEG;AACH,eAAO,MAAM,IAAI,GAAI,CAAC,SAAS,WAAW,GAAG,2BAA2B,EACtE,UAAU,mBAAmB,CAAC,CAAC,CAAC,KAC/B,IAAI,CAAC,MAAM,CAAC,CAAC,CAAC,CAyBhB,CAAC;AAEF,KAAK,2BAA2B,GAAG,KAAK,GAAG,KAAK,GAAG,KAAK,GAAG,OAAO,CAAC;AAEnE,KAAK,MAAM,CAAC,CAAC,SAAS,WAAW,GAAG,2BAA2B,IAC7D,MAAM,CAAC,CAAC,EAAE,CAAC,CAAC,SAAS,IAAI,GACrB,KAAK,GACL,MAAM,CAAC,CAAC,EAAE,CAAC,CAAC,SAAS,IAAI,GACvB,KAAK,GACL,MAAM,CAAC,CAAC,EAAE,CAAC,CAAC,SAAS,IAAI,GACvB,KAAK,GACL,IAAI,CAAC;AAEf,eAAO,MAAM,MAAM,GAAI,eAAe,MAAM,KAAG,IAAI,CAAC,KAAK,CACrB,CAAC;AAErC,eAAO,MAAM,MAAM,GAAI,eAAe,MAAM,KAAG,IAAI,CAAC,KAAK,CACrB,CAAC;AAErC,eAAO,MAAM,MAAM,GAAI,eAAe,MAAM,KAAG,IAAI,CAAC,KAAK,CACrB,CAAC;AAErC,KAAK,mBAAmB,CAAC,CAAC,SAAS,WAAW,GAAG,2BAA2B,IAC1E,QAAQ,CAAC;IACP;;OAEG;IACH,OAAO,CAAC,EAAE,CAAC,CAAC;IAEZ,QAAQ,CAAC,EAAE,MAAM,CAAC;IAClB,YAAY,CAAC,EAAE,MAAM,CAAC;CACvB,CAAC,CAAC"}
|
|
@@ -0,0 +1,55 @@
|
|
|
1
|
+
import { brand } from '../../../brand/brand.mjs';
|
|
2
|
+
import 'ts-data-forge';
|
|
3
|
+
import { string } from '../../../primitives/string.mjs';
|
|
4
|
+
|
|
5
|
+
/**
|
|
6
|
+
* @link https://github.com/validatorjs/validator.js/tree/v13.1.17?tab=readme-ov-file#validators
|
|
7
|
+
*/
|
|
8
|
+
const uuid = (options) => {
|
|
9
|
+
const defaultValue = options?.defaultValue ?? nilUuid;
|
|
10
|
+
const version = options?.version ?? 'all';
|
|
11
|
+
const regexp = uuidDef[version];
|
|
12
|
+
// eslint-disable-next-line total-functions/no-unsafe-type-assertion
|
|
13
|
+
return brand({
|
|
14
|
+
// eslint-disable-next-line total-functions/no-unsafe-type-assertion
|
|
15
|
+
baseType: string(defaultValue),
|
|
16
|
+
is: (s) => regexp.test(s),
|
|
17
|
+
// eslint-disable-next-line total-functions/no-unsafe-type-assertion
|
|
18
|
+
defaultValue: defaultValue,
|
|
19
|
+
brandKeys: ['Uuid'],
|
|
20
|
+
typeName: options?.typeName ??
|
|
21
|
+
(version === 4
|
|
22
|
+
? 'UuidV4'
|
|
23
|
+
: version === 6
|
|
24
|
+
? 'UuidV6'
|
|
25
|
+
: version === 7
|
|
26
|
+
? 'UuidV7'
|
|
27
|
+
: 'Uuid'),
|
|
28
|
+
});
|
|
29
|
+
};
|
|
30
|
+
const uuidV4 = (defaultValue) => uuid({ defaultValue, version: 4 });
|
|
31
|
+
const uuidV6 = (defaultValue) => uuid({ defaultValue, version: 6 });
|
|
32
|
+
const uuidV7 = (defaultValue) => uuid({ defaultValue, version: 7 });
|
|
33
|
+
const nilUuid = '00000000-0000-0000-0000-000000000000';
|
|
34
|
+
const uuidDef = {
|
|
35
|
+
1: /^[\da-f]{8}-[\da-f]{4}-1[\da-f]{3}-[89ab][\da-f]{3}-[\da-f]{12}$/iu,
|
|
36
|
+
2: /^[\da-f]{8}-[\da-f]{4}-2[\da-f]{3}-[89ab][\da-f]{3}-[\da-f]{12}$/iu,
|
|
37
|
+
3: /^[\da-f]{8}-[\da-f]{4}-3[\da-f]{3}-[89ab][\da-f]{3}-[\da-f]{12}$/iu,
|
|
38
|
+
4: /^[\da-f]{8}-[\da-f]{4}-4[\da-f]{3}-[89ab][\da-f]{3}-[\da-f]{12}$/iu,
|
|
39
|
+
5: /^[\da-f]{8}-[\da-f]{4}-5[\da-f]{3}-[89ab][\da-f]{3}-[\da-f]{12}$/iu,
|
|
40
|
+
6: /^[\da-f]{8}-[\da-f]{4}-6[\da-f]{3}-[89ab][\da-f]{3}-[\da-f]{12}$/iu,
|
|
41
|
+
7: /^[\da-f]{8}-[\da-f]{4}-7[\da-f]{3}-[89ab][\da-f]{3}-[\da-f]{12}$/iu,
|
|
42
|
+
8: /^[\da-f]{8}-[\da-f]{4}-8[\da-f]{3}-[89ab][\da-f]{3}-[\da-f]{12}$/iu,
|
|
43
|
+
// eslint-disable-next-line security/detect-unsafe-regex
|
|
44
|
+
nil: /^0{8}-(?:0{4}-){3}0{12}$/iu,
|
|
45
|
+
// eslint-disable-next-line security/detect-unsafe-regex
|
|
46
|
+
max: /^f{8}-(?:f{4}-){3}f{12}$/iu,
|
|
47
|
+
// eslint-disable-next-line security/detect-unsafe-regex
|
|
48
|
+
loose: /^[\da-f]{8}(?:-[\da-f]{4}){3}-[\da-f]{12}$/iu,
|
|
49
|
+
// From https://github.com/uuidjs/uuid/blob/main/src/regex.js
|
|
50
|
+
// eslint-disable-next-line security/detect-unsafe-regex
|
|
51
|
+
all: /^(?:[\da-f]{8}-[\da-f]{4}-[1-8][\da-f]{3}-[89ab][\da-f]{3}-[\da-f]{12}|0{8}-(?:0{4}-){3}0{12}|f{8}-(?:f{4}-){3}f{12})$/iu,
|
|
52
|
+
};
|
|
53
|
+
|
|
54
|
+
export { uuid, uuidV4, uuidV6, uuidV7 };
|
|
55
|
+
//# sourceMappingURL=uuid.mjs.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"uuid.mjs","sources":["../../../../src/predefined/brand/string/uuid.mts"],"sourcesContent":[null],"names":[],"mappings":";;;;AAIA;;AAEG;AACI,MAAM,IAAI,GAAG,CAClB,OAAgC,KACb;AAGnB,IAAA,MAAM,YAAY,GAAG,OAAO,EAAE,YAAY,IAAI,OAAO;AACrD,IAAA,MAAM,OAAO,GAAG,OAAO,EAAE,OAAO,IAAI,KAAK;AACzC,IAAA,MAAM,MAAM,GAAG,OAAO,CAAC,OAAO,CAAC;;AAG/B,IAAA,OAAO,KAAK,CAAC;;AAEX,QAAA,QAAQ,EAAE,MAAM,CAAC,YAAY,CAAyB;QACtD,EAAE,EAAE,CAAC,CAAC,KAAa,MAAM,CAAC,IAAI,CAAC,CAAC,CAAC;;AAEjC,QAAA,YAAY,EAAE,YAAiB;QAC/B,SAAS,EAAE,CAAC,MAAM,CAAC;QACnB,QAAQ,EACN,OAAO,EAAE,QAAQ;aAChB,OAAO,KAAK;AACX,kBAAE;kBACA,OAAO,KAAK;AACZ,sBAAE;sBACA,OAAO,KAAK;AACZ,0BAAE;0BACA,MAAM,CAAC;AAClB,KAAA,CAAsC;AACzC;AAaO,MAAM,MAAM,GAAG,CAAC,YAAqB,KAC1C,IAAI,CAAC,EAAE,YAAY,EAAE,OAAO,EAAE,CAAC,EAAE;AAE5B,MAAM,MAAM,GAAG,CAAC,YAAqB,KAC1C,IAAI,CAAC,EAAE,YAAY,EAAE,OAAO,EAAE,CAAC,EAAE;AAE5B,MAAM,MAAM,GAAG,CAAC,YAAqB,KAC1C,IAAI,CAAC,EAAE,YAAY,EAAE,OAAO,EAAE,CAAC,EAAE;AAanC,MAAM,OAAO,GAAG,sCAAsC;AAEtD,MAAM,OAAO,GAAG;AACd,IAAA,CAAC,EAAE,oEAAoE;AACvE,IAAA,CAAC,EAAE,oEAAoE;AACvE,IAAA,CAAC,EAAE,oEAAoE;AACvE,IAAA,CAAC,EAAE,oEAAoE;AACvE,IAAA,CAAC,EAAE,oEAAoE;AACvE,IAAA,CAAC,EAAE,oEAAoE;AACvE,IAAA,CAAC,EAAE,oEAAoE;AACvE,IAAA,CAAC,EAAE,oEAAoE;;AAGvE,IAAA,GAAG,EAAE,4BAA4B;;AAEjC,IAAA,GAAG,EAAE,4BAA4B;;AAEjC,IAAA,KAAK,EAAE,8CAA8C;;;AAKrD,IAAA,GAAG,EAAE,0HAA0H;CACvH;;;;"}
|
|
@@ -14,6 +14,10 @@ export { safeUint } from './brand/number/safe-uint.mjs';
|
|
|
14
14
|
export { uint } from './brand/number/uint.mjs';
|
|
15
15
|
export { uint16 } from './brand/number/uint16.mjs';
|
|
16
16
|
export { uint32 } from './brand/number/uint32.mjs';
|
|
17
|
+
export { email } from './brand/string/email.mjs';
|
|
18
|
+
export { iso8601 } from './brand/string/iso-8601.mjs';
|
|
19
|
+
export { jsonString } from './brand/string/json-string.mjs';
|
|
20
|
+
export { uuid, uuidV4, uuidV6, uuidV7 } from './brand/string/uuid.mjs';
|
|
17
21
|
export { int8 } from './int8.mjs';
|
|
18
22
|
export { JsonObject, JsonPrimitive, JsonValue } from './json.mjs';
|
|
19
23
|
export { nullable } from './nullable.mjs';
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"index.mjs","sources":[],"sourcesContent":[],"names":[],"mappings":"
|
|
1
|
+
{"version":3,"file":"index.mjs","sources":[],"sourcesContent":[],"names":[],"mappings":";;;;;;;;;;;;;;;;;;;;;;;"}
|
package/package.json
CHANGED
package/src/globals.d.mts
CHANGED
|
@@ -1,5 +1,22 @@
|
|
|
1
|
-
|
|
2
|
-
|
|
3
1
|
/// <reference types="ts-type-forge" />
|
|
4
2
|
|
|
5
3
|
type PartialReadonly<T> = Partial<Readonly<T>>;
|
|
4
|
+
|
|
5
|
+
type Iso8601 = Brand<string, 'Iso8601'>;
|
|
6
|
+
|
|
7
|
+
type Email = Brand<string, 'Email'>;
|
|
8
|
+
|
|
9
|
+
type JsonString = Brand<string, 'JsonString'>;
|
|
10
|
+
|
|
11
|
+
type UuidVersion = UintRangeInclusive<1, 8>;
|
|
12
|
+
|
|
13
|
+
type UuidBaseString<V extends UuidVersion = UuidVersion> =
|
|
14
|
+
`${string}-${string}-${V}${string}-${string}-${string}`;
|
|
15
|
+
|
|
16
|
+
type Uuid = Brand<UuidBaseString, 'Uuid'>;
|
|
17
|
+
|
|
18
|
+
type Uuid4 = Brand<UuidBaseString<4>, 'Uuid'>;
|
|
19
|
+
|
|
20
|
+
type Uuid6 = Brand<UuidBaseString<6>, 'Uuid'>;
|
|
21
|
+
|
|
22
|
+
type Uuid7 = Brand<UuidBaseString<7>, 'Uuid'>;
|
|
@@ -0,0 +1,25 @@
|
|
|
1
|
+
import { brandedString } from '../../../brand/index.mjs';
|
|
2
|
+
import { type Type } from '../../../type.mjs';
|
|
3
|
+
|
|
4
|
+
/**
|
|
5
|
+
* @link https://html.spec.whatwg.org/multipage/input.html#valid-e-mail-address
|
|
6
|
+
*/
|
|
7
|
+
export const email = (defaultValue?: string): Type<Email> =>
|
|
8
|
+
brandedString({
|
|
9
|
+
is: (s): s is Email => regexpEmailAddress.test(s),
|
|
10
|
+
// eslint-disable-next-line total-functions/no-unsafe-type-assertion
|
|
11
|
+
defaultValue: (defaultValue ?? defaultEmail) as Email,
|
|
12
|
+
typeName: 'Email',
|
|
13
|
+
});
|
|
14
|
+
|
|
15
|
+
const defaultEmail = 'ts-f.o.r.t.r.e.s.s@gmail.com';
|
|
16
|
+
|
|
17
|
+
const regexpEmailAddress =
|
|
18
|
+
// eslint-disable-next-line security/detect-unsafe-regex
|
|
19
|
+
/^[a-zA-Z0-9.!#$%&'*+/=?^_`{|}~-]+@[a-zA-Z0-9](?:[a-zA-Z0-9-]{0,61}[a-zA-Z0-9])?(?:\.[a-zA-Z0-9](?:[a-zA-Z0-9-]{0,61}[a-zA-Z0-9])?)*$/gu;
|
|
20
|
+
|
|
21
|
+
if (import.meta.vitest !== undefined) {
|
|
22
|
+
test('defaultEmail', () => {
|
|
23
|
+
expect(defaultEmail).toMatch(regexpEmailAddress);
|
|
24
|
+
});
|
|
25
|
+
}
|
|
@@ -0,0 +1,88 @@
|
|
|
1
|
+
/* cSpell:disable */
|
|
2
|
+
|
|
3
|
+
import { expectType } from 'ts-data-forge';
|
|
4
|
+
import { type TypeOf } from '../../../type.mjs';
|
|
5
|
+
import { email } from './email.mjs';
|
|
6
|
+
|
|
7
|
+
// https://github.com/validatorjs/validator.js/blob/13.15.15/test/validators.test.js
|
|
8
|
+
|
|
9
|
+
const validSamples = [
|
|
10
|
+
'foo@bar.com',
|
|
11
|
+
// 'x@x.au',
|
|
12
|
+
// 'foo@bar.com.au',
|
|
13
|
+
// 'foo+bar@bar.com',
|
|
14
|
+
// 'hans.m端ller@test.com',
|
|
15
|
+
// 'hans@m端ller.com',
|
|
16
|
+
// 'test|123@m端ller.com',
|
|
17
|
+
// 'test123+ext@gmail.com',
|
|
18
|
+
// 'some.name.midd.leNa.me.and.locality+extension@GoogleMail.com',
|
|
19
|
+
// '"foobar"@example.com',
|
|
20
|
+
// '" foo m端ller "@example.com',
|
|
21
|
+
// '"foo\\@bar"@example.com',
|
|
22
|
+
// `${'a'.repeat(64)}@${'a'.repeat(63)}.com`,
|
|
23
|
+
// `${'a'.repeat(31)}@gmail.com`,
|
|
24
|
+
// 'test@gmail.com',
|
|
25
|
+
// 'test.1@gmail.com',
|
|
26
|
+
// 'test@1337.com',
|
|
27
|
+
] as const satisfies readonly string[];
|
|
28
|
+
|
|
29
|
+
const invalidSamples = [
|
|
30
|
+
'invalidemail@',
|
|
31
|
+
'invalid.com',
|
|
32
|
+
'@invalid.com',
|
|
33
|
+
'foo@bar.com.',
|
|
34
|
+
'foo@_bar.com',
|
|
35
|
+
'somename@gmail.com',
|
|
36
|
+
'foo@bar.co.uk.',
|
|
37
|
+
// 'z@co.c',
|
|
38
|
+
'gmailgmailgmailgmailgmail@gmail.com',
|
|
39
|
+
`${'a'.repeat(64)}@${'a'.repeat(251)}.com`,
|
|
40
|
+
`${'a'.repeat(65)}@${'a'.repeat(250)}.com`,
|
|
41
|
+
`${'a'.repeat(64)}@${'a'.repeat(64)}.com`,
|
|
42
|
+
// `${'a'.repeat(64)}@${'a'.repeat(63)}.${'a'.repeat(63)}.${'a'.repeat(63)}.${'a'.repeat(58)}.com`,
|
|
43
|
+
'test1@invalid.co m',
|
|
44
|
+
'test2@invalid.co m',
|
|
45
|
+
'test3@invalid.co m',
|
|
46
|
+
'test4@invalid.co m',
|
|
47
|
+
'test5@invalid.co m',
|
|
48
|
+
'test6@invalid.co m',
|
|
49
|
+
'test7@invalid.co m',
|
|
50
|
+
'test8@invalid.co m',
|
|
51
|
+
'test9@invalid.co m',
|
|
52
|
+
'test10@invalid.co m',
|
|
53
|
+
'test11@invalid.co m',
|
|
54
|
+
'test12@invalid.co m',
|
|
55
|
+
'test13@invalid.co m',
|
|
56
|
+
// 'multiple..dots@stillinvalid.com',
|
|
57
|
+
'test123+invalid! sub_address@gmail.com',
|
|
58
|
+
// 'gmail...ignores...dots...@gmail.com',
|
|
59
|
+
// 'ends.with.dot.@gmail.com',
|
|
60
|
+
// 'multiple..dots@gmail.com',
|
|
61
|
+
'wrong()[]",:;<>@@gmail.com',
|
|
62
|
+
'"wrong()[]",:;<>@@gmail.com',
|
|
63
|
+
'username@domain.com�',
|
|
64
|
+
'username@domain.com©',
|
|
65
|
+
'nbsp test@test.com',
|
|
66
|
+
'nbsp_test@te st.com',
|
|
67
|
+
'nbsp_test@test.co m',
|
|
68
|
+
'"foobar@gmail.com',
|
|
69
|
+
'"foo"bar@gmail.com',
|
|
70
|
+
'foo"bar"@gmail.com',
|
|
71
|
+
] as const satisfies readonly string[];
|
|
72
|
+
|
|
73
|
+
describe('email', () => {
|
|
74
|
+
const baseType = email();
|
|
75
|
+
|
|
76
|
+
type Iso8601Type = TypeOf<typeof baseType>;
|
|
77
|
+
|
|
78
|
+
expectType<Iso8601Type, string>('<=');
|
|
79
|
+
expectType<typeof baseType.defaultValue, Iso8601Type>('=');
|
|
80
|
+
|
|
81
|
+
test.each(validSamples)('should accept $0', (e) => {
|
|
82
|
+
expect(baseType.is(e)).toBe(true);
|
|
83
|
+
});
|
|
84
|
+
|
|
85
|
+
test.each(invalidSamples)('should reject $0', (e) => {
|
|
86
|
+
expect(baseType.is(e)).toBe(false);
|
|
87
|
+
});
|
|
88
|
+
});
|
|
@@ -0,0 +1,97 @@
|
|
|
1
|
+
import { brandedString } from '../../../brand/index.mjs';
|
|
2
|
+
import { boolean, string } from '../../../primitives/index.mjs';
|
|
3
|
+
import { record } from '../../../record/index.mjs';
|
|
4
|
+
import { type Type, type TypeOf } from '../../../type.mjs';
|
|
5
|
+
|
|
6
|
+
/**
|
|
7
|
+
* @link https://github.com/validatorjs/validator.js/tree/v13.1.17?tab=readme-ov-file#validators
|
|
8
|
+
* @link https://developer.mozilla.org/ja/docs/Web/JavaScript/Reference/Global_Objects/Date/toISOString
|
|
9
|
+
* @link https://tc39.es/ecma262/multipage/numbers-and-dates.html#sec-date-time-string-format
|
|
10
|
+
*/
|
|
11
|
+
export const iso8601 = (options?: ISO8601ValidatorOption): Type<Iso8601> => {
|
|
12
|
+
const filledOptions = ISO8601ValidatorOption.fill(options);
|
|
13
|
+
const is = isISO8601(filledOptions);
|
|
14
|
+
|
|
15
|
+
return brandedString({
|
|
16
|
+
is,
|
|
17
|
+
// eslint-disable-next-line total-functions/no-unsafe-type-assertion
|
|
18
|
+
defaultValue: filledOptions.defaultValue as Iso8601,
|
|
19
|
+
typeName: 'Iso8601',
|
|
20
|
+
});
|
|
21
|
+
};
|
|
22
|
+
|
|
23
|
+
const ISO8601ValidatorOption = record({
|
|
24
|
+
/**
|
|
25
|
+
* If strict is true, date strings with invalid dates like 2009-02-29 will be invalid.
|
|
26
|
+
*/
|
|
27
|
+
strict: boolean(false),
|
|
28
|
+
|
|
29
|
+
/**
|
|
30
|
+
* If strictSeparator is true, date strings with date and time separated by anything other than a T will be invalid.
|
|
31
|
+
*/
|
|
32
|
+
strictSeparator: boolean(false),
|
|
33
|
+
|
|
34
|
+
/**
|
|
35
|
+
* @default `new Date().toISOString()`
|
|
36
|
+
*/
|
|
37
|
+
defaultValue: string(new Date().toISOString()),
|
|
38
|
+
});
|
|
39
|
+
|
|
40
|
+
type ISO8601ValidatorOption = TypeOf<typeof ISO8601ValidatorOption>;
|
|
41
|
+
|
|
42
|
+
const isISO8601 =
|
|
43
|
+
(options?: Partial<StrictOmit<ISO8601ValidatorOption, 'defaultValue'>>) =>
|
|
44
|
+
(str: string): str is Iso8601 => {
|
|
45
|
+
const filledOptions = ISO8601ValidatorOption.fill(options);
|
|
46
|
+
|
|
47
|
+
const check = filledOptions.strictSeparator
|
|
48
|
+
? regexpIso8601StrictSeparator.test(str)
|
|
49
|
+
: regexpIso8601.test(str);
|
|
50
|
+
|
|
51
|
+
if (check && filledOptions.strict) return isValidDate(str);
|
|
52
|
+
return check;
|
|
53
|
+
};
|
|
54
|
+
|
|
55
|
+
// from http://goo.gl/0ejHHW
|
|
56
|
+
const regexpIso8601 =
|
|
57
|
+
// eslint-disable-next-line security/detect-unsafe-regex
|
|
58
|
+
/^([+-]?\d{4}(?!\d{2}\b))((-?)((0[1-9]|1[0-2])(\3([12]\d|0[1-9]|3[01]))?|W([0-4]\d|5[0-3])(-?[1-7])?|(00[1-9]|0[1-9]\d|[12]\d{2}|3([0-5]\d|6[1-6])))([\sT]((([01]\d|2[0-3])((:?)[0-5]\d)?|24:?00)([,.]\d+(?!:))?)?(\17[0-5]\d([,.]\d+)?)?([Zz]|([+-])([01]\d|2[0-3]):?([0-5]\d)?)?)?)?$/u;
|
|
59
|
+
|
|
60
|
+
// same as above, except with a strict 'T' separator between date and time
|
|
61
|
+
const regexpIso8601StrictSeparator =
|
|
62
|
+
// eslint-disable-next-line security/detect-unsafe-regex
|
|
63
|
+
/^([+-]?\d{4}(?!\d{2}\b))((-?)((0[1-9]|1[0-2])(\3([12]\d|0[1-9]|3[01]))?|W([0-4]\d|5[0-3])(-?[1-7])?|(00[1-9]|0[1-9]\d|[12]\d{2}|3([0-5]\d|6[1-6])))(T((([01]\d|2[0-3])((:?)[0-5]\d)?|24:?00)([,.]\d+(?!:))?)?(\17[0-5]\d([,.]\d+)?)?([Zz]|([+-])([01]\d|2[0-3]):?([0-5]\d)?)?)?)?$/u;
|
|
64
|
+
|
|
65
|
+
const isValidDate = (str: string): boolean => {
|
|
66
|
+
// str must have passed the ISO8601 check
|
|
67
|
+
// this check is meant to catch invalid dates
|
|
68
|
+
// like 2009-02-31
|
|
69
|
+
// first check for ordinal dates
|
|
70
|
+
const ordinalMatch = /^(\d{4})-?(\d{3})([ T]\.*|$)/u.exec(str);
|
|
71
|
+
if (ordinalMatch !== null) {
|
|
72
|
+
const oYear = Number(ordinalMatch[1]);
|
|
73
|
+
const oDay = Number(ordinalMatch[2]);
|
|
74
|
+
// if is leap year
|
|
75
|
+
if ((oYear % 4 === 0 && oYear % 100 !== 0) || oYear % 400 === 0)
|
|
76
|
+
return oDay <= 366;
|
|
77
|
+
return oDay <= 365;
|
|
78
|
+
}
|
|
79
|
+
const match = /(\d{4})-?(\d{0,2})-?(\d*)/u.exec(str)?.map(Number);
|
|
80
|
+
const year = match?.[1];
|
|
81
|
+
const month: number | undefined = match?.[2];
|
|
82
|
+
const date: number | undefined = match?.[3];
|
|
83
|
+
|
|
84
|
+
// create a date object and compare
|
|
85
|
+
const d = new Date(
|
|
86
|
+
`${year}-${`0${month ?? 1}`.slice(-2)}-${`0${date ?? 1}`.slice(-2)}`,
|
|
87
|
+
);
|
|
88
|
+
|
|
89
|
+
if (month !== undefined && date !== undefined) {
|
|
90
|
+
return (
|
|
91
|
+
d.getUTCFullYear() === year &&
|
|
92
|
+
d.getUTCMonth() + 1 === month &&
|
|
93
|
+
d.getUTCDate() === date
|
|
94
|
+
);
|
|
95
|
+
}
|
|
96
|
+
return true;
|
|
97
|
+
};
|
|
@@ -0,0 +1,168 @@
|
|
|
1
|
+
import { expectType, Result } from 'ts-data-forge';
|
|
2
|
+
import { type TypeOf } from '../../../type.mjs';
|
|
3
|
+
import { validationErrorsToMessages } from '../../../utils/index.mjs';
|
|
4
|
+
import { iso8601 } from './iso-8601.mjs';
|
|
5
|
+
|
|
6
|
+
// https://www.myintervals.com/blog/iso-8601-date-validation/
|
|
7
|
+
|
|
8
|
+
const validSamples = [
|
|
9
|
+
'2009-05-19 14:39',
|
|
10
|
+
'2009-12T12:34',
|
|
11
|
+
'2009',
|
|
12
|
+
'2009-05-19',
|
|
13
|
+
'20090519',
|
|
14
|
+
'2009123',
|
|
15
|
+
'2009-05',
|
|
16
|
+
'2009-123',
|
|
17
|
+
'2009-222',
|
|
18
|
+
'2009-001',
|
|
19
|
+
'2009-W01-1',
|
|
20
|
+
'2009-W51-1',
|
|
21
|
+
'2009-W511',
|
|
22
|
+
'2009-W33',
|
|
23
|
+
'2009W511',
|
|
24
|
+
'2009-05-19',
|
|
25
|
+
'2009-05-19 00:00',
|
|
26
|
+
'2009-05-19 14',
|
|
27
|
+
'2009-05-19 14:31',
|
|
28
|
+
'2009-05-19 14:39:22',
|
|
29
|
+
'2009-05-19T14:39Z',
|
|
30
|
+
'2009-W21-2',
|
|
31
|
+
'2009-W21-2T01:22',
|
|
32
|
+
'2009-139',
|
|
33
|
+
'2009-05-19 14:39:22-06:00',
|
|
34
|
+
'2009-05-19 14:39:22+0600',
|
|
35
|
+
'2009-05-19 14:39:22-01',
|
|
36
|
+
'20090621T0545Z',
|
|
37
|
+
'2007-04-06T00:00',
|
|
38
|
+
'2007-04-05T24:00',
|
|
39
|
+
'2010-02-18T16:23:48.5',
|
|
40
|
+
'2010-02-18T16:23:48,444',
|
|
41
|
+
'2010-02-18T16:23:48,3-06:00',
|
|
42
|
+
'2010-02-18T16:23.4',
|
|
43
|
+
'2010-02-18T16:23,25',
|
|
44
|
+
'2010-02-18T16:23.33+0600',
|
|
45
|
+
'2010-02-18T16.23334444',
|
|
46
|
+
'2010-02-18T16,2283',
|
|
47
|
+
'2009-05-19 143922.500',
|
|
48
|
+
'2009-05-19 1439,55',
|
|
49
|
+
] as const satisfies readonly string[];
|
|
50
|
+
|
|
51
|
+
const invalidSamples = [
|
|
52
|
+
'200905',
|
|
53
|
+
'2009367',
|
|
54
|
+
'2009-',
|
|
55
|
+
'2007-04-05T24:50',
|
|
56
|
+
'2009-000',
|
|
57
|
+
'2009-M511',
|
|
58
|
+
'2009M511',
|
|
59
|
+
'2009-05-19T14a39r',
|
|
60
|
+
'2009-05-19T14:3924',
|
|
61
|
+
'2009-0519',
|
|
62
|
+
'2009-05-1914:39',
|
|
63
|
+
'2009-05-19 14:',
|
|
64
|
+
'2009-05-19r14:39',
|
|
65
|
+
'2009-05-19 14a39a22',
|
|
66
|
+
'200912-01',
|
|
67
|
+
'2009-05-19 14:39:22+06a00',
|
|
68
|
+
'2009-05-19 146922.500',
|
|
69
|
+
'2010-02-18T16.5:23.35:48',
|
|
70
|
+
'2010-02-18T16:23.35:48',
|
|
71
|
+
'2010-02-18T16:23.35:48.45',
|
|
72
|
+
'2009-05-19 14.5.44',
|
|
73
|
+
'2010-02-18T16:23.33.600',
|
|
74
|
+
'2021-01-22T13:13:57.494677Z.',
|
|
75
|
+
'2010-02-18T16,25:23:48,44',
|
|
76
|
+
] as const satisfies readonly string[];
|
|
77
|
+
|
|
78
|
+
// https://tc39.es/ecma262/multipage/numbers-and-dates.html#sec-date-time-string-format
|
|
79
|
+
|
|
80
|
+
const expandedYearFormats = [
|
|
81
|
+
'-271821-04-20T00:00:00Z', // 271822 B.C.
|
|
82
|
+
'-000001-01-01T00:00:00Z', // 2 B.C.
|
|
83
|
+
'+000000-01-01T00:00:00Z', // 1 B.C.
|
|
84
|
+
'+000001-01-01T00:00:00Z', // 1 A.D.
|
|
85
|
+
'+001970-01-01T00:00:00Z', // 1970 A.D.
|
|
86
|
+
'+002009-12-15T00:00:00Z', // 2009 A.D.
|
|
87
|
+
'+275760-09-13T00:00:00Z', // 275760 A.D.
|
|
88
|
+
] as const satisfies readonly string[];
|
|
89
|
+
|
|
90
|
+
describe('iso8601', () => {
|
|
91
|
+
const baseType = iso8601();
|
|
92
|
+
const baseDefault = baseType.defaultValue;
|
|
93
|
+
|
|
94
|
+
type Iso8601Type = TypeOf<typeof baseType>;
|
|
95
|
+
|
|
96
|
+
expectType<Iso8601Type, string>('<=');
|
|
97
|
+
expectType<typeof baseType.defaultValue, Iso8601Type>('=');
|
|
98
|
+
|
|
99
|
+
test.each(validSamples)('should accept $0', (e) => {
|
|
100
|
+
expect(baseType.is(e)).toBe(true);
|
|
101
|
+
});
|
|
102
|
+
|
|
103
|
+
test.each(invalidSamples)('should reject $0', (e) => {
|
|
104
|
+
expect(baseType.is(e)).toBe(false);
|
|
105
|
+
});
|
|
106
|
+
|
|
107
|
+
test.each(expandedYearFormats)(
|
|
108
|
+
'currently rejects expanded year formats $0',
|
|
109
|
+
(e) => {
|
|
110
|
+
expect(baseType.is(e)).toBe(false);
|
|
111
|
+
},
|
|
112
|
+
);
|
|
113
|
+
|
|
114
|
+
test('strict mode rejects calendar-invalid dates', () => {
|
|
115
|
+
const strictType = iso8601({
|
|
116
|
+
defaultValue: baseDefault,
|
|
117
|
+
strict: true,
|
|
118
|
+
strictSeparator: false,
|
|
119
|
+
});
|
|
120
|
+
|
|
121
|
+
expect(strictType.is('2008-02-29')).toBe(true);
|
|
122
|
+
expect(strictType.is('2009-02-29')).toBe(false);
|
|
123
|
+
});
|
|
124
|
+
|
|
125
|
+
test('strictSeparator mode enforces the T delimiter', () => {
|
|
126
|
+
const type = iso8601({
|
|
127
|
+
defaultValue: baseDefault,
|
|
128
|
+
strict: false,
|
|
129
|
+
strictSeparator: true,
|
|
130
|
+
});
|
|
131
|
+
|
|
132
|
+
expect(type.is('2009-05-19T14:39:22')).toBe(true);
|
|
133
|
+
expect(type.is('2009-05-19 14:39:22')).toBe(false);
|
|
134
|
+
});
|
|
135
|
+
|
|
136
|
+
test('validate yields detailed errors for invalid strings', () => {
|
|
137
|
+
const result = baseType.validate('not-an-iso-date');
|
|
138
|
+
expect(Result.isErr(result)).toBe(true);
|
|
139
|
+
if (!Result.isErr(result)) {
|
|
140
|
+
throw new Error('Expected validation failure');
|
|
141
|
+
}
|
|
142
|
+
|
|
143
|
+
expect(result.value).toStrictEqual([
|
|
144
|
+
{
|
|
145
|
+
path: [],
|
|
146
|
+
actualValue: 'not-an-iso-date',
|
|
147
|
+
expectedType: 'Iso8601',
|
|
148
|
+
typeName: 'Iso8601',
|
|
149
|
+
message: undefined,
|
|
150
|
+
},
|
|
151
|
+
]);
|
|
152
|
+
|
|
153
|
+
expect(validationErrorsToMessages(result.value)).toStrictEqual([
|
|
154
|
+
'Expected <Iso8601>, got <string> type value "not-an-iso-date".',
|
|
155
|
+
]);
|
|
156
|
+
});
|
|
157
|
+
|
|
158
|
+
test('fill and cast round trip valid values', () => {
|
|
159
|
+
const value = '2021-01-22T13:13:57.494677Z';
|
|
160
|
+
|
|
161
|
+
expect(baseType.fill(value)).toBe(value);
|
|
162
|
+
expect(() => baseType.cast(value)).not.toThrow();
|
|
163
|
+
});
|
|
164
|
+
|
|
165
|
+
test('fill falls back to default for invalid values', () => {
|
|
166
|
+
expect(baseType.fill('invalid')).toBe(baseDefault);
|
|
167
|
+
});
|
|
168
|
+
});
|
|
@@ -0,0 +1,32 @@
|
|
|
1
|
+
import { isNonNullObject } from 'ts-data-forge';
|
|
2
|
+
import { brandedString } from '../../../brand/index.mjs';
|
|
3
|
+
import { type Type } from '../../../type.mjs';
|
|
4
|
+
|
|
5
|
+
/**
|
|
6
|
+
* @link https://github.com/validatorjs/validator.js/tree/v13.1.17?tab=readme-ov-file#validators
|
|
7
|
+
*/
|
|
8
|
+
export const jsonString = (
|
|
9
|
+
defaultValue: string = defaultJsonString,
|
|
10
|
+
): Type<JsonString> =>
|
|
11
|
+
brandedString({
|
|
12
|
+
is: isJsonString,
|
|
13
|
+
// eslint-disable-next-line total-functions/no-unsafe-type-assertion
|
|
14
|
+
defaultValue: defaultValue as JsonString,
|
|
15
|
+
typeName: 'JsonString',
|
|
16
|
+
});
|
|
17
|
+
|
|
18
|
+
const defaultJsonString = '{}';
|
|
19
|
+
|
|
20
|
+
const isJsonString = (str: string): str is JsonString => {
|
|
21
|
+
try {
|
|
22
|
+
return isNonNullObject(JSON.parse(str));
|
|
23
|
+
} catch {
|
|
24
|
+
return false;
|
|
25
|
+
}
|
|
26
|
+
};
|
|
27
|
+
|
|
28
|
+
if (import.meta.vitest !== undefined) {
|
|
29
|
+
test('defaultJsonString', () => {
|
|
30
|
+
expect(isJsonString(defaultJsonString)).toBe(true);
|
|
31
|
+
});
|
|
32
|
+
}
|
|
@@ -0,0 +1,65 @@
|
|
|
1
|
+
import { expectType, Result } from 'ts-data-forge';
|
|
2
|
+
import { type TypeOf } from '../../../type.mjs';
|
|
3
|
+
import { validationErrorsToMessages } from '../../../utils/index.mjs';
|
|
4
|
+
import { jsonString } from './json-string.mjs';
|
|
5
|
+
|
|
6
|
+
describe('jsonString', () => {
|
|
7
|
+
const baseType = jsonString();
|
|
8
|
+
const customDefault = jsonString('{"ok":true}');
|
|
9
|
+
|
|
10
|
+
type JsonStringType = TypeOf<typeof baseType>;
|
|
11
|
+
expectType<JsonStringType, string>('<=');
|
|
12
|
+
expectType<typeof baseType.defaultValue, JsonStringType>('=');
|
|
13
|
+
|
|
14
|
+
test('provides an object literal as default', () => {
|
|
15
|
+
expect(baseType.defaultValue).toBe('{}');
|
|
16
|
+
expect(customDefault.defaultValue).toBe('{"ok":true}');
|
|
17
|
+
});
|
|
18
|
+
|
|
19
|
+
test('recognizes valid JSON objects and arrays', () => {
|
|
20
|
+
const samples = ['{"foo":1}', '{"nested":{"bar":[1,2]}}', '[]'];
|
|
21
|
+
|
|
22
|
+
for (const sample of samples) {
|
|
23
|
+
expect(baseType.is(sample)).toBe(true);
|
|
24
|
+
}
|
|
25
|
+
});
|
|
26
|
+
|
|
27
|
+
test('rejects primitives and malformed JSON', () => {
|
|
28
|
+
const invalid = ['not-json', '{"unterminated"', '123', 'null'];
|
|
29
|
+
|
|
30
|
+
for (const sample of invalid) {
|
|
31
|
+
expect(baseType.is(sample)).toBe(false);
|
|
32
|
+
}
|
|
33
|
+
});
|
|
34
|
+
|
|
35
|
+
test('validate yields detailed errors for invalid strings', () => {
|
|
36
|
+
const result = baseType.validate('not-json');
|
|
37
|
+
expect(Result.isErr(result)).toBe(true);
|
|
38
|
+
if (!Result.isErr(result)) {
|
|
39
|
+
throw new Error('Expected validation failure');
|
|
40
|
+
}
|
|
41
|
+
|
|
42
|
+
expect(result.value).toStrictEqual([
|
|
43
|
+
{
|
|
44
|
+
path: [],
|
|
45
|
+
actualValue: 'not-json',
|
|
46
|
+
expectedType: 'JsonString',
|
|
47
|
+
typeName: 'JsonString',
|
|
48
|
+
message: undefined,
|
|
49
|
+
},
|
|
50
|
+
]);
|
|
51
|
+
|
|
52
|
+
expect(validationErrorsToMessages(result.value)).toStrictEqual([
|
|
53
|
+
'Expected <JsonString>, got <string> type value "not-json".',
|
|
54
|
+
]);
|
|
55
|
+
});
|
|
56
|
+
|
|
57
|
+
test('fill falls back to default for invalid input', () => {
|
|
58
|
+
expect(baseType.fill('invalid')).toBe(baseType.defaultValue);
|
|
59
|
+
});
|
|
60
|
+
|
|
61
|
+
test('cast returns parsed string when valid, throws otherwise', () => {
|
|
62
|
+
expect(baseType.cast('{"foo":1}')).toBe('{"foo":1}');
|
|
63
|
+
expect(() => baseType.cast('invalid')).toThrow('Expected');
|
|
64
|
+
});
|
|
65
|
+
});
|
|
@@ -0,0 +1,97 @@
|
|
|
1
|
+
import { brand } from '../../../brand/index.mjs';
|
|
2
|
+
import { string } from '../../../primitives/index.mjs';
|
|
3
|
+
import { type Type } from '../../../type.mjs';
|
|
4
|
+
|
|
5
|
+
/**
|
|
6
|
+
* @link https://github.com/validatorjs/validator.js/tree/v13.1.17?tab=readme-ov-file#validators
|
|
7
|
+
*/
|
|
8
|
+
export const uuid = <V extends UuidVersion | UuidVersionAdditionalOption>(
|
|
9
|
+
options?: UuidValidatorOption<V>,
|
|
10
|
+
): Type<UuidOf<V>> => {
|
|
11
|
+
type T = Brand<UuidBaseString, 'Uuid'>;
|
|
12
|
+
|
|
13
|
+
const defaultValue = options?.defaultValue ?? nilUuid;
|
|
14
|
+
const version = options?.version ?? 'all';
|
|
15
|
+
const regexp = uuidDef[version];
|
|
16
|
+
|
|
17
|
+
// eslint-disable-next-line total-functions/no-unsafe-type-assertion
|
|
18
|
+
return brand({
|
|
19
|
+
// eslint-disable-next-line total-functions/no-unsafe-type-assertion
|
|
20
|
+
baseType: string(defaultValue) as Type<UuidBaseString>,
|
|
21
|
+
is: (s): s is T => regexp.test(s),
|
|
22
|
+
// eslint-disable-next-line total-functions/no-unsafe-type-assertion
|
|
23
|
+
defaultValue: defaultValue as T,
|
|
24
|
+
brandKeys: ['Uuid'],
|
|
25
|
+
typeName:
|
|
26
|
+
options?.typeName ??
|
|
27
|
+
(version === 4
|
|
28
|
+
? 'UuidV4'
|
|
29
|
+
: version === 6
|
|
30
|
+
? 'UuidV6'
|
|
31
|
+
: version === 7
|
|
32
|
+
? 'UuidV7'
|
|
33
|
+
: 'Uuid'),
|
|
34
|
+
}) satisfies Type<T> as Type<UuidOf<V>>;
|
|
35
|
+
};
|
|
36
|
+
|
|
37
|
+
type UuidVersionAdditionalOption = 'nil' | 'max' | 'all' | 'loose';
|
|
38
|
+
|
|
39
|
+
type UuidOf<V extends UuidVersion | UuidVersionAdditionalOption> =
|
|
40
|
+
TypeEq<V, 4> extends true
|
|
41
|
+
? Uuid4
|
|
42
|
+
: TypeEq<V, 6> extends true
|
|
43
|
+
? Uuid6
|
|
44
|
+
: TypeEq<V, 7> extends true
|
|
45
|
+
? Uuid7
|
|
46
|
+
: Uuid;
|
|
47
|
+
|
|
48
|
+
export const uuidV4 = (defaultValue?: string): Type<Uuid4> =>
|
|
49
|
+
uuid({ defaultValue, version: 4 });
|
|
50
|
+
|
|
51
|
+
export const uuidV6 = (defaultValue?: string): Type<Uuid6> =>
|
|
52
|
+
uuid({ defaultValue, version: 6 });
|
|
53
|
+
|
|
54
|
+
export const uuidV7 = (defaultValue?: string): Type<Uuid7> =>
|
|
55
|
+
uuid({ defaultValue, version: 7 });
|
|
56
|
+
|
|
57
|
+
type UuidValidatorOption<V extends UuidVersion | UuidVersionAdditionalOption> =
|
|
58
|
+
Readonly<{
|
|
59
|
+
/**
|
|
60
|
+
* @param version One of 1-8, 'nil', 'max', 'all' or 'loose'. The 'loose' option checks if the string is a UUID-like string with hexadecimal values, ignoring RFC9562.
|
|
61
|
+
*/
|
|
62
|
+
version?: V;
|
|
63
|
+
|
|
64
|
+
typeName?: string;
|
|
65
|
+
defaultValue?: string;
|
|
66
|
+
}>;
|
|
67
|
+
|
|
68
|
+
const nilUuid = '00000000-0000-0000-0000-000000000000';
|
|
69
|
+
|
|
70
|
+
const uuidDef = {
|
|
71
|
+
1: /^[\da-f]{8}-[\da-f]{4}-1[\da-f]{3}-[89ab][\da-f]{3}-[\da-f]{12}$/iu,
|
|
72
|
+
2: /^[\da-f]{8}-[\da-f]{4}-2[\da-f]{3}-[89ab][\da-f]{3}-[\da-f]{12}$/iu,
|
|
73
|
+
3: /^[\da-f]{8}-[\da-f]{4}-3[\da-f]{3}-[89ab][\da-f]{3}-[\da-f]{12}$/iu,
|
|
74
|
+
4: /^[\da-f]{8}-[\da-f]{4}-4[\da-f]{3}-[89ab][\da-f]{3}-[\da-f]{12}$/iu,
|
|
75
|
+
5: /^[\da-f]{8}-[\da-f]{4}-5[\da-f]{3}-[89ab][\da-f]{3}-[\da-f]{12}$/iu,
|
|
76
|
+
6: /^[\da-f]{8}-[\da-f]{4}-6[\da-f]{3}-[89ab][\da-f]{3}-[\da-f]{12}$/iu,
|
|
77
|
+
7: /^[\da-f]{8}-[\da-f]{4}-7[\da-f]{3}-[89ab][\da-f]{3}-[\da-f]{12}$/iu,
|
|
78
|
+
8: /^[\da-f]{8}-[\da-f]{4}-8[\da-f]{3}-[89ab][\da-f]{3}-[\da-f]{12}$/iu,
|
|
79
|
+
|
|
80
|
+
// eslint-disable-next-line security/detect-unsafe-regex
|
|
81
|
+
nil: /^0{8}-(?:0{4}-){3}0{12}$/iu,
|
|
82
|
+
// eslint-disable-next-line security/detect-unsafe-regex
|
|
83
|
+
max: /^f{8}-(?:f{4}-){3}f{12}$/iu,
|
|
84
|
+
// eslint-disable-next-line security/detect-unsafe-regex
|
|
85
|
+
loose: /^[\da-f]{8}(?:-[\da-f]{4}){3}-[\da-f]{12}$/iu,
|
|
86
|
+
|
|
87
|
+
// From https://github.com/uuidjs/uuid/blob/main/src/regex.js
|
|
88
|
+
|
|
89
|
+
// eslint-disable-next-line security/detect-unsafe-regex
|
|
90
|
+
all: /^(?:[\da-f]{8}-[\da-f]{4}-[1-8][\da-f]{3}-[89ab][\da-f]{3}-[\da-f]{12}|0{8}-(?:0{4}-){3}0{12}|f{8}-(?:f{4}-){3}f{12})$/iu,
|
|
91
|
+
} as const;
|
|
92
|
+
|
|
93
|
+
if (import.meta.vitest !== undefined) {
|
|
94
|
+
test('uuidNil', () => {
|
|
95
|
+
expect(nilUuid).toMatch(uuidDef.nil);
|
|
96
|
+
});
|
|
97
|
+
}
|
|
@@ -0,0 +1,125 @@
|
|
|
1
|
+
import { expectType, Result } from 'ts-data-forge';
|
|
2
|
+
import { type TypeOf } from '../../../type.mjs';
|
|
3
|
+
import { validationErrorsToMessages } from '../../../utils/index.mjs';
|
|
4
|
+
import { uuid, uuidV4, uuidV6, uuidV7 } from './uuid.mjs';
|
|
5
|
+
|
|
6
|
+
const nilUuid = '00000000-0000-0000-0000-000000000000';
|
|
7
|
+
const maxUuid = 'ffffffff-ffff-ffff-ffff-ffffffffffff';
|
|
8
|
+
const uuidV4Example = '550e8400-e29b-41d4-a716-446655440000';
|
|
9
|
+
const uuidV6Example = '550e8400-e29b-61d4-a716-446655440000';
|
|
10
|
+
const uuidV7Example = '550e8400-e29b-71d4-a716-446655440000';
|
|
11
|
+
|
|
12
|
+
describe('uuid', () => {
|
|
13
|
+
const baseType = uuid();
|
|
14
|
+
|
|
15
|
+
type UuidType = TypeOf<typeof baseType>;
|
|
16
|
+
expectType<UuidType, string>('<=');
|
|
17
|
+
expectType<typeof baseType.defaultValue, UuidType>('=');
|
|
18
|
+
|
|
19
|
+
test('provides nil UUID as default', () => {
|
|
20
|
+
expect(baseType.defaultValue).toBe(nilUuid);
|
|
21
|
+
});
|
|
22
|
+
|
|
23
|
+
test('accepts multiple UUID flavours by default', () => {
|
|
24
|
+
const validSamples = [
|
|
25
|
+
uuidV4Example,
|
|
26
|
+
uuidV6Example,
|
|
27
|
+
uuidV7Example,
|
|
28
|
+
nilUuid,
|
|
29
|
+
maxUuid,
|
|
30
|
+
];
|
|
31
|
+
|
|
32
|
+
for (const sample of validSamples) {
|
|
33
|
+
expect(baseType.is(sample)).toBe(true);
|
|
34
|
+
}
|
|
35
|
+
});
|
|
36
|
+
|
|
37
|
+
test('rejects malformed identifiers', () => {
|
|
38
|
+
const invalidSamples = [
|
|
39
|
+
'not-a-uuid',
|
|
40
|
+
'550e8400e29b41d4a716446655440000',
|
|
41
|
+
'550e8400-e29b-41d4-a716-44665544000',
|
|
42
|
+
];
|
|
43
|
+
|
|
44
|
+
for (const sample of invalidSamples) {
|
|
45
|
+
expect(baseType.is(sample)).toBe(false);
|
|
46
|
+
}
|
|
47
|
+
});
|
|
48
|
+
|
|
49
|
+
test('validate surfaces details for invalid strings', () => {
|
|
50
|
+
const result = baseType.validate('not-a-uuid');
|
|
51
|
+
expect(Result.isErr(result)).toBe(true);
|
|
52
|
+
if (!Result.isErr(result)) {
|
|
53
|
+
throw new Error('Expected validation failure');
|
|
54
|
+
}
|
|
55
|
+
|
|
56
|
+
expect(result.value).toStrictEqual([
|
|
57
|
+
{
|
|
58
|
+
path: [],
|
|
59
|
+
actualValue: 'not-a-uuid',
|
|
60
|
+
expectedType: 'Uuid',
|
|
61
|
+
typeName: 'Uuid',
|
|
62
|
+
message: undefined,
|
|
63
|
+
},
|
|
64
|
+
]);
|
|
65
|
+
|
|
66
|
+
expect(validationErrorsToMessages(result.value)).toStrictEqual([
|
|
67
|
+
'Expected <Uuid>, got <string> type value "not-a-uuid".',
|
|
68
|
+
]);
|
|
69
|
+
});
|
|
70
|
+
});
|
|
71
|
+
|
|
72
|
+
describe('uuidV4', () => {
|
|
73
|
+
const v4Type = uuidV4();
|
|
74
|
+
|
|
75
|
+
type UuidV4Type = TypeOf<typeof v4Type>;
|
|
76
|
+
expectType<UuidV4Type, string>('<=');
|
|
77
|
+
expectType<typeof v4Type.defaultValue, UuidV4Type>('=');
|
|
78
|
+
|
|
79
|
+
test('recognizes version 4 UUIDs only', () => {
|
|
80
|
+
expect(v4Type.is(uuidV4Example)).toBe(true);
|
|
81
|
+
expect(v4Type.is(uuidV6Example)).toBe(false);
|
|
82
|
+
});
|
|
83
|
+
|
|
84
|
+
test('validate reports version mismatch', () => {
|
|
85
|
+
const result = v4Type.validate(uuidV6Example);
|
|
86
|
+
expect(Result.isErr(result)).toBe(true);
|
|
87
|
+
if (!Result.isErr(result)) {
|
|
88
|
+
throw new Error('Expected validation failure');
|
|
89
|
+
}
|
|
90
|
+
|
|
91
|
+
expect(result.value).toStrictEqual([
|
|
92
|
+
{
|
|
93
|
+
path: [],
|
|
94
|
+
actualValue: uuidV6Example,
|
|
95
|
+
expectedType: 'UuidV4',
|
|
96
|
+
typeName: 'UuidV4',
|
|
97
|
+
message: undefined,
|
|
98
|
+
},
|
|
99
|
+
]);
|
|
100
|
+
});
|
|
101
|
+
|
|
102
|
+
test('fill returns default for invalid input', () => {
|
|
103
|
+
expect(v4Type.fill('invalid-uuid')).toBe(v4Type.defaultValue);
|
|
104
|
+
});
|
|
105
|
+
});
|
|
106
|
+
|
|
107
|
+
describe('uuidV6', () => {
|
|
108
|
+
const v6Type = uuidV6();
|
|
109
|
+
|
|
110
|
+
test('recognizes only version 6 UUIDs', () => {
|
|
111
|
+
expect(v6Type.is(uuidV6Example)).toBe(true);
|
|
112
|
+
expect(v6Type.is(uuidV4Example)).toBe(false);
|
|
113
|
+
expect(v6Type.is(uuidV7Example)).toBe(false);
|
|
114
|
+
});
|
|
115
|
+
});
|
|
116
|
+
|
|
117
|
+
describe('uuidV7', () => {
|
|
118
|
+
const v7Type = uuidV7();
|
|
119
|
+
|
|
120
|
+
test('recognizes only version 7 UUIDs', () => {
|
|
121
|
+
expect(v7Type.is(uuidV7Example)).toBe(true);
|
|
122
|
+
expect(v7Type.is(uuidV4Example)).toBe(false);
|
|
123
|
+
expect(v7Type.is(uuidV6Example)).toBe(false);
|
|
124
|
+
});
|
|
125
|
+
});
|