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.
Files changed (43) hide show
  1. package/dist/entry-point.mjs +4 -0
  2. package/dist/entry-point.mjs.map +1 -1
  3. package/dist/globals.d.mts +19 -2
  4. package/dist/index.mjs +4 -0
  5. package/dist/index.mjs.map +1 -1
  6. package/dist/predefined/brand/index.d.mts +1 -0
  7. package/dist/predefined/brand/index.d.mts.map +1 -1
  8. package/dist/predefined/brand/index.mjs +4 -0
  9. package/dist/predefined/brand/index.mjs.map +1 -1
  10. package/dist/predefined/brand/string/email.d.mts +6 -0
  11. package/dist/predefined/brand/string/email.d.mts.map +1 -0
  12. package/dist/predefined/brand/string/email.mjs +19 -0
  13. package/dist/predefined/brand/string/email.mjs.map +1 -0
  14. package/dist/predefined/brand/string/index.d.mts +5 -0
  15. package/dist/predefined/brand/string/index.d.mts.map +1 -0
  16. package/dist/predefined/brand/string/index.mjs +5 -0
  17. package/dist/predefined/brand/string/index.mjs.map +1 -0
  18. package/dist/predefined/brand/string/iso-8601.d.mts +69 -0
  19. package/dist/predefined/brand/string/iso-8601.d.mts.map +1 -0
  20. package/dist/predefined/brand/string/iso-8601.mjs +82 -0
  21. package/dist/predefined/brand/string/iso-8601.mjs.map +1 -0
  22. package/dist/predefined/brand/string/json-string.d.mts +6 -0
  23. package/dist/predefined/brand/string/json-string.d.mts.map +1 -0
  24. package/dist/predefined/brand/string/json-string.mjs +24 -0
  25. package/dist/predefined/brand/string/json-string.mjs.map +1 -0
  26. package/dist/predefined/brand/string/uuid.d.mts +20 -0
  27. package/dist/predefined/brand/string/uuid.d.mts.map +1 -0
  28. package/dist/predefined/brand/string/uuid.mjs +55 -0
  29. package/dist/predefined/brand/string/uuid.mjs.map +1 -0
  30. package/dist/predefined/index.mjs +4 -0
  31. package/dist/predefined/index.mjs.map +1 -1
  32. package/package.json +1 -1
  33. package/src/globals.d.mts +19 -2
  34. package/src/predefined/brand/index.mts +1 -0
  35. package/src/predefined/brand/string/email.mts +25 -0
  36. package/src/predefined/brand/string/email.test.mts +88 -0
  37. package/src/predefined/brand/string/index.mts +4 -0
  38. package/src/predefined/brand/string/iso-8601.mts +97 -0
  39. package/src/predefined/brand/string/iso-8601.test.mts +168 -0
  40. package/src/predefined/brand/string/json-string.mts +32 -0
  41. package/src/predefined/brand/string/json-string.test.mts +65 -0
  42. package/src/predefined/brand/string/uuid.mts +97 -0
  43. package/src/predefined/brand/string/uuid.test.mts +125 -0
@@ -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';
@@ -1 +1 @@
1
- {"version":3,"file":"entry-point.mjs","sources":[],"sourcesContent":[],"names":[],"mappings":";;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;"}
1
+ {"version":3,"file":"entry-point.mjs","sources":[],"sourcesContent":[],"names":[],"mappings":";;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;"}
@@ -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';
@@ -1 +1 @@
1
- {"version":3,"file":"index.mjs","sources":[],"sourcesContent":[],"names":[],"mappings":";;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;"}
1
+ {"version":3,"file":"index.mjs","sources":[],"sourcesContent":[],"names":[],"mappings":";;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;"}
@@ -1,2 +1,3 @@
1
1
  export * from './number/index.mjs';
2
+ export * from './string/index.mjs';
2
3
  //# sourceMappingURL=index.d.mts.map
@@ -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,6 @@
1
+ import { type Type } from '../../../type.mjs';
2
+ /**
3
+ * @link https://html.spec.whatwg.org/multipage/input.html#valid-e-mail-address
4
+ */
5
+ export declare const email: (defaultValue?: string) => Type<Email>;
6
+ //# sourceMappingURL=email.d.mts.map
@@ -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,5 @@
1
+ export * from './email.mjs';
2
+ export * from './iso-8601.mjs';
3
+ export * from './json-string.mjs';
4
+ export * from './uuid.mjs';
5
+ //# sourceMappingURL=index.d.mts.map
@@ -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,5 @@
1
+ export { email } from './email.mjs';
2
+ export { iso8601 } from './iso-8601.mjs';
3
+ export { jsonString } from './json-string.mjs';
4
+ export { uuid, uuidV4, uuidV6, uuidV7 } from './uuid.mjs';
5
+ //# sourceMappingURL=index.mjs.map
@@ -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
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "ts-fortress",
3
- "version": "4.3.1",
3
+ "version": "4.4.0",
4
4
  "private": false,
5
5
  "keywords": [
6
6
  "typescript"
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'>;
@@ -1 +1,2 @@
1
1
  export * from './number/index.mjs';
2
+ export * from './string/index.mjs';
@@ -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,4 @@
1
+ export * from './email.mjs';
2
+ export * from './iso-8601.mjs';
3
+ export * from './json-string.mjs';
4
+ export * from './uuid.mjs';
@@ -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
+ });