@zudojs/validation 0.1.0 → 1.0.1

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (133) hide show
  1. package/LICENSE +21 -0
  2. package/README.md +31 -0
  3. package/dist/validationComposer/index.d.ts +3 -1
  4. package/dist/validationComposer/index.js +3 -1
  5. package/dist/validationComposer/validationComposer.combinators.d.ts +21 -0
  6. package/dist/validationComposer/validationComposer.combinators.js +78 -0
  7. package/dist/validationComposer/validationComposer.core.d.ts +10 -24
  8. package/dist/validationComposer/validationComposer.core.js +2 -139
  9. package/dist/validationComposer/validationComposer.wrappers.d.ts +38 -0
  10. package/dist/validationComposer/validationComposer.wrappers.js +96 -0
  11. package/dist/validationConstraints/collection/index.d.ts +8 -0
  12. package/dist/validationConstraints/collection/index.js +8 -0
  13. package/dist/validationConstraints/{validationConstraints.array.d.ts → collection/validationConstraints.array.d.ts} +1 -1
  14. package/dist/validationConstraints/{validationConstraints.array.js → collection/validationConstraints.array.js} +23 -3
  15. package/dist/validationConstraints/{validationConstraints.collection.d.ts → collection/validationConstraints.collection.d.ts} +1 -1
  16. package/dist/validationConstraints/{validationConstraints.collection.js → collection/validationConstraints.collection.js} +1 -1
  17. package/dist/validationConstraints/index.d.ts +3 -8
  18. package/dist/validationConstraints/index.js +6 -16
  19. package/dist/validationConstraints/scalar/index.d.ts +9 -0
  20. package/dist/validationConstraints/scalar/index.js +9 -0
  21. package/dist/validationConstraints/scalar/validationConstraints.date.d.ts +13 -0
  22. package/dist/validationConstraints/scalar/validationConstraints.date.js +45 -0
  23. package/dist/validationConstraints/{validationConstraints.number.d.ts → scalar/validationConstraints.number.d.ts} +1 -1
  24. package/dist/validationConstraints/{validationConstraints.number.js → scalar/validationConstraints.number.js} +12 -1
  25. package/dist/validationConstraints/{validationConstraints.string.d.ts → scalar/validationConstraints.string.d.ts} +18 -3
  26. package/dist/validationConstraints/{validationConstraints.string.js → scalar/validationConstraints.string.js} +49 -13
  27. package/dist/validationConstraints/structure/index.d.ts +10 -0
  28. package/dist/validationConstraints/structure/index.js +10 -0
  29. package/dist/validationConstraints/{validationConstraints.circular.d.ts → structure/validationConstraints.circular.d.ts} +8 -4
  30. package/dist/validationConstraints/structure/validationConstraints.circular.js +55 -0
  31. package/dist/validationConstraints/structure/validationConstraints.depth.d.ts +37 -0
  32. package/dist/validationConstraints/structure/validationConstraints.depth.js +58 -0
  33. package/dist/validationConstraints/structure/validationConstraints.size.d.ts +31 -0
  34. package/dist/validationConstraints/structure/validationConstraints.size.js +101 -0
  35. package/dist/validationConstraints/structure/validationConstraints.traverse.d.ts +57 -0
  36. package/dist/validationConstraints/structure/validationConstraints.traverse.js +118 -0
  37. package/dist/validationConstraints/validationConstraints.base.d.ts +18 -3
  38. package/dist/validationConstraints/validationConstraints.base.js +50 -25
  39. package/dist/validationErrors/validationError.base.js +28 -1
  40. package/dist/validationFactory/validationFactory.core.d.ts +9 -8
  41. package/dist/validationFactory/validationFactory.core.js +11 -9
  42. package/dist/validationNormalizer/validationNormalizer.core.js +2 -4
  43. package/dist/validationNormalizer/validationNormalizer.helpers.d.ts +36 -3
  44. package/dist/validationNormalizer/validationNormalizer.helpers.js +48 -7
  45. package/dist/validationParser/index.d.ts +2 -1
  46. package/dist/validationParser/index.js +2 -1
  47. package/dist/validationParser/validationParser.collection.d.ts +28 -0
  48. package/dist/validationParser/validationParser.collection.js +98 -0
  49. package/dist/validationParser/validationParser.core.d.ts +0 -12
  50. package/dist/validationParser/validationParser.core.js +0 -71
  51. package/dist/validationRegistry/validationRegistry.core.d.ts +8 -0
  52. package/dist/validationRegistry/validationRegistry.core.js +15 -1
  53. package/dist/validationResult/validationResult.type.d.ts +8 -1
  54. package/dist/validationResult/validationResult.type.js +20 -5
  55. package/dist/validationTransformer/index.d.ts +2 -1
  56. package/dist/validationTransformer/index.js +2 -1
  57. package/dist/validationTransformer/validationTransformer.core.d.ts +0 -15
  58. package/dist/validationTransformer/validationTransformer.core.js +5 -56
  59. package/dist/validationTransformer/validationTransformer.helpers.d.ts +23 -0
  60. package/dist/validationTransformer/validationTransformer.helpers.js +67 -0
  61. package/package.json +24 -13
  62. package/dist/.tsbuildinfo +0 -1
  63. package/dist/index.d.ts.map +0 -1
  64. package/dist/index.js.map +0 -1
  65. package/dist/validationComposer/index.d.ts.map +0 -1
  66. package/dist/validationComposer/index.js.map +0 -1
  67. package/dist/validationComposer/validationComposer.core.d.ts.map +0 -1
  68. package/dist/validationComposer/validationComposer.core.js.map +0 -1
  69. package/dist/validationConstraints/index.d.ts.map +0 -1
  70. package/dist/validationConstraints/index.js.map +0 -1
  71. package/dist/validationConstraints/validationConstraints.array.d.ts.map +0 -1
  72. package/dist/validationConstraints/validationConstraints.array.js.map +0 -1
  73. package/dist/validationConstraints/validationConstraints.base.d.ts.map +0 -1
  74. package/dist/validationConstraints/validationConstraints.base.js.map +0 -1
  75. package/dist/validationConstraints/validationConstraints.circular.d.ts.map +0 -1
  76. package/dist/validationConstraints/validationConstraints.circular.js +0 -73
  77. package/dist/validationConstraints/validationConstraints.circular.js.map +0 -1
  78. package/dist/validationConstraints/validationConstraints.collection.d.ts.map +0 -1
  79. package/dist/validationConstraints/validationConstraints.collection.js.map +0 -1
  80. package/dist/validationConstraints/validationConstraints.date.d.ts +0 -14
  81. package/dist/validationConstraints/validationConstraints.date.d.ts.map +0 -1
  82. package/dist/validationConstraints/validationConstraints.date.js +0 -30
  83. package/dist/validationConstraints/validationConstraints.date.js.map +0 -1
  84. package/dist/validationConstraints/validationConstraints.depth.d.ts +0 -20
  85. package/dist/validationConstraints/validationConstraints.depth.d.ts.map +0 -1
  86. package/dist/validationConstraints/validationConstraints.depth.js +0 -70
  87. package/dist/validationConstraints/validationConstraints.depth.js.map +0 -1
  88. package/dist/validationConstraints/validationConstraints.number.d.ts.map +0 -1
  89. package/dist/validationConstraints/validationConstraints.number.js.map +0 -1
  90. package/dist/validationConstraints/validationConstraints.size.d.ts +0 -20
  91. package/dist/validationConstraints/validationConstraints.size.d.ts.map +0 -1
  92. package/dist/validationConstraints/validationConstraints.size.js +0 -82
  93. package/dist/validationConstraints/validationConstraints.size.js.map +0 -1
  94. package/dist/validationConstraints/validationConstraints.string.d.ts.map +0 -1
  95. package/dist/validationConstraints/validationConstraints.string.js.map +0 -1
  96. package/dist/validationErrors/index.d.ts.map +0 -1
  97. package/dist/validationErrors/index.js.map +0 -1
  98. package/dist/validationErrors/validationError.base.d.ts.map +0 -1
  99. package/dist/validationErrors/validationError.base.js.map +0 -1
  100. package/dist/validationErrors/validationError.types.d.ts.map +0 -1
  101. package/dist/validationErrors/validationError.types.js.map +0 -1
  102. package/dist/validationFactory/index.d.ts.map +0 -1
  103. package/dist/validationFactory/index.js.map +0 -1
  104. package/dist/validationFactory/validationFactory.core.d.ts.map +0 -1
  105. package/dist/validationFactory/validationFactory.core.js.map +0 -1
  106. package/dist/validationNormalizer/index.d.ts.map +0 -1
  107. package/dist/validationNormalizer/index.js.map +0 -1
  108. package/dist/validationNormalizer/validationNormalizer.core.d.ts.map +0 -1
  109. package/dist/validationNormalizer/validationNormalizer.core.js.map +0 -1
  110. package/dist/validationNormalizer/validationNormalizer.helpers.d.ts.map +0 -1
  111. package/dist/validationNormalizer/validationNormalizer.helpers.js.map +0 -1
  112. package/dist/validationParser/index.d.ts.map +0 -1
  113. package/dist/validationParser/index.js.map +0 -1
  114. package/dist/validationParser/validationParser.core.d.ts.map +0 -1
  115. package/dist/validationParser/validationParser.core.js.map +0 -1
  116. package/dist/validationRegistry/index.d.ts.map +0 -1
  117. package/dist/validationRegistry/index.js.map +0 -1
  118. package/dist/validationRegistry/validationRegistry.core.d.ts.map +0 -1
  119. package/dist/validationRegistry/validationRegistry.core.js.map +0 -1
  120. package/dist/validationRegistry/validationRegistry.readonly.d.ts.map +0 -1
  121. package/dist/validationRegistry/validationRegistry.readonly.js.map +0 -1
  122. package/dist/validationResult/index.d.ts.map +0 -1
  123. package/dist/validationResult/index.js.map +0 -1
  124. package/dist/validationResult/validationResult.type.d.ts.map +0 -1
  125. package/dist/validationResult/validationResult.type.js.map +0 -1
  126. package/dist/validationSchema/index.d.ts.map +0 -1
  127. package/dist/validationSchema/index.js.map +0 -1
  128. package/dist/validationSchema/validationSchema.core.d.ts.map +0 -1
  129. package/dist/validationSchema/validationSchema.core.js.map +0 -1
  130. package/dist/validationTransformer/index.d.ts.map +0 -1
  131. package/dist/validationTransformer/index.js.map +0 -1
  132. package/dist/validationTransformer/validationTransformer.core.d.ts.map +0 -1
  133. package/dist/validationTransformer/validationTransformer.core.js.map +0 -1
@@ -5,20 +5,10 @@
5
5
  */
6
6
  // Base constraints
7
7
  export { createConstraint, checkConstraint, checkConstraints, combineConstraints, not, required, assertNonNegativeInteger, } from "./validationConstraints.base.js";
8
- // String constraints
9
- export { nonEmptyString, minLength, maxLength, lengthBetween, matches, email, uuid, httpUrl, ascii, digits, letters, slug, } from "./validationConstraints.string.js";
10
- // Number constraints
11
- export { min, max, between, finiteNumber, integer, positive, nonNegative, even, odd, } from "./validationConstraints.number.js";
12
- // Collection constraints
13
- export { oneOf, noneOf } from "./validationConstraints.collection.js";
14
- // Date constraints
15
- export { isoDate, futureDate, pastDate } from "./validationConstraints.date.js";
16
- // Array constraints
17
- export { minItems, maxItems, exactItems, everyItem, someItem, } from "./validationConstraints.array.js";
18
- // Circular reference detection
19
- export { assertNoCircularReference, hasCircularReference, } from "./validationConstraints.circular.js";
20
- // Depth checking
21
- export { getSerializationDepth, assertDepthWithinLimit, } from "./validationConstraints.depth.js";
22
- // Size estimation
23
- export { estimateSerializedSize, assertSizeWithinLimit, } from "./validationConstraints.size.js";
8
+ // Scalar constraints: strings, numbers, dates
9
+ export * from "./scalar/index.js";
10
+ // Collection constraints: arrays and membership
11
+ export * from "./collection/index.js";
12
+ // Structural guards: depth, size, and circular references
13
+ export * from "./structure/index.js";
24
14
  //# sourceMappingURL=index.js.map
@@ -0,0 +1,9 @@
1
+ /**
2
+ * @zudojs/validation — Scalar constraints.
3
+ *
4
+ * @module validationConstraints/scalar
5
+ */
6
+ export * from "./validationConstraints.string.js";
7
+ export * from "./validationConstraints.number.js";
8
+ export * from "./validationConstraints.date.js";
9
+ //# sourceMappingURL=index.d.ts.map
@@ -0,0 +1,9 @@
1
+ /**
2
+ * @zudojs/validation — Scalar constraints.
3
+ *
4
+ * @module validationConstraints/scalar
5
+ */
6
+ export * from "./validationConstraints.string.js";
7
+ export * from "./validationConstraints.number.js";
8
+ export * from "./validationConstraints.date.js";
9
+ //# sourceMappingURL=index.js.map
@@ -0,0 +1,13 @@
1
+ /**
2
+ * Requires a string to be a valid ISO date representation.
3
+ */
4
+ export declare const isoDate: import("../validationConstraints.base.js").ValidationConstraint<string>;
5
+ /**
6
+ * Requires a date to be in the future.
7
+ */
8
+ export declare const futureDate: import("../validationConstraints.base.js").ValidationConstraint<Date>;
9
+ /**
10
+ * Requires a date to be in the past.
11
+ */
12
+ export declare const pastDate: import("../validationConstraints.base.js").ValidationConstraint<Date>;
13
+ //# sourceMappingURL=validationConstraints.date.d.ts.map
@@ -0,0 +1,45 @@
1
+ import { createConstraint } from "../validationConstraints.base.js";
2
+ /** Narrows an unknown value to a valid Date. */
3
+ const isDate = (value) => value instanceof Date && !Number.isNaN(value.getTime());
4
+ /**
5
+ * Requires a string to be a valid ISO date representation.
6
+ */
7
+ export const isoDate = createConstraint((value) => {
8
+ if (!/^\d{4}-\d{2}-\d{2}(?:[T ]\d{2}:\d{2}(?::\d{2}(?:\.\d{1,9})?)?(?:Z|[+-]\d{2}:\d{2})?)?$/u.test(value)) {
9
+ return false;
10
+ }
11
+ const date = new Date(value);
12
+ if (Number.isNaN(date.getTime()))
13
+ return false;
14
+ // Reject calendar-invalid dates such as 2024-02-31, which Date rolls over
15
+ // into the following month rather than rejecting.
16
+ const [year, month, day] = value.slice(0, 10).split("-").map(Number);
17
+ const asUtc = new Date(Date.UTC(year, month - 1, day));
18
+ return (asUtc.getUTCFullYear() === year &&
19
+ asUtc.getUTCMonth() === month - 1 &&
20
+ asUtc.getUTCDate() === day);
21
+ }, {
22
+ name: "iso_date",
23
+ code: "invalid_date",
24
+ message: "Value must be a valid ISO 8601 date.",
25
+ guard: (value) => typeof value === "string",
26
+ });
27
+ /**
28
+ * Requires a date to be in the future.
29
+ */
30
+ export const futureDate = createConstraint((value) => value.getTime() > Date.now(), {
31
+ name: "future_date",
32
+ code: "invalid_future_date",
33
+ message: "Date must be in the future.",
34
+ guard: isDate,
35
+ });
36
+ /**
37
+ * Requires a date to be in the past.
38
+ */
39
+ export const pastDate = createConstraint((value) => value.getTime() < Date.now(), {
40
+ name: "past_date",
41
+ code: "invalid_past_date",
42
+ message: "Date must be in the past.",
43
+ guard: isDate,
44
+ });
45
+ //# sourceMappingURL=validationConstraints.date.js.map
@@ -1,4 +1,4 @@
1
- import type { ValidationConstraint } from "./validationConstraints.base.js";
1
+ import type { ValidationConstraint } from "../validationConstraints.base.js";
2
2
  /**
3
3
  * Requires a numeric value to be greater than or equal to a minimum.
4
4
  */
@@ -1,4 +1,6 @@
1
- import { createConstraint } from "./validationConstraints.base.js";
1
+ import { createConstraint } from "../validationConstraints.base.js";
2
+ /** Narrows an unknown value to a number. */
3
+ const isNumber = (value) => typeof value === "number";
2
4
  /**
3
5
  * Requires a numeric value to be greater than or equal to a minimum.
4
6
  */
@@ -10,6 +12,7 @@ export function min(minimum) {
10
12
  name: `min_${minimum}`,
11
13
  code: "min_value",
12
14
  message: `Value must be greater than or equal to ${minimum}.`,
15
+ guard: isNumber,
13
16
  });
14
17
  }
15
18
  /**
@@ -23,6 +26,7 @@ export function max(maximum) {
23
26
  name: `max_${maximum}`,
24
27
  code: "max_value",
25
28
  message: `Value must be less than or equal to ${maximum}.`,
29
+ guard: isNumber,
26
30
  });
27
31
  }
28
32
  /**
@@ -39,6 +43,7 @@ export function between(minimum, maximum) {
39
43
  name: `between_${minimum}_${maximum}`,
40
44
  code: "value_out_of_range",
41
45
  message: `Value must be between ${minimum} and ${maximum}.`,
46
+ guard: isNumber,
42
47
  });
43
48
  }
44
49
  /**
@@ -48,6 +53,7 @@ export const finiteNumber = createConstraint((value) => Number.isFinite(value),
48
53
  name: "finite_number",
49
54
  code: "invalid_number",
50
55
  message: "Value must be a finite number.",
56
+ guard: isNumber,
51
57
  });
52
58
  /**
53
59
  * Requires an integer.
@@ -56,6 +62,7 @@ export const integer = createConstraint((value) => Number.isInteger(value), {
56
62
  name: "integer",
57
63
  code: "invalid_integer",
58
64
  message: "Value must be an integer.",
65
+ guard: isNumber,
59
66
  });
60
67
  /**
61
68
  * Requires a positive number.
@@ -64,6 +71,7 @@ export const positive = createConstraint((value) => value > 0, {
64
71
  name: "positive",
65
72
  code: "invalid_positive",
66
73
  message: "Value must be greater than zero.",
74
+ guard: isNumber,
67
75
  });
68
76
  /**
69
77
  * Requires a non-negative number.
@@ -72,6 +80,7 @@ export const nonNegative = createConstraint((value) => value >= 0, {
72
80
  name: "non_negative",
73
81
  code: "invalid_non_negative",
74
82
  message: "Value must be zero or greater.",
83
+ guard: isNumber,
75
84
  });
76
85
  /**
77
86
  * Requires a number to be even.
@@ -80,6 +89,7 @@ export const even = createConstraint((value) => Number.isInteger(value) && value
80
89
  name: "even",
81
90
  code: "invalid_even",
82
91
  message: "Value must be an even integer.",
92
+ guard: isNumber,
83
93
  });
84
94
  /**
85
95
  * Requires a number to be odd.
@@ -88,5 +98,6 @@ export const odd = createConstraint((value) => Number.isInteger(value) && Math.a
88
98
  name: "odd",
89
99
  code: "invalid_odd",
90
100
  message: "Value must be an odd integer.",
101
+ guard: isNumber,
91
102
  });
92
103
  //# sourceMappingURL=validationConstraints.number.js.map
@@ -1,10 +1,12 @@
1
- import type { ValidationConstraint } from "./validationConstraints.base.js";
1
+ import type { ValidationConstraint } from "../validationConstraints.base.js";
2
2
  /**
3
3
  * Requires a string to be non-empty after trimming.
4
4
  */
5
5
  export declare const nonEmptyString: ValidationConstraint<string>;
6
6
  /**
7
7
  * Requires a string to contain at least the specified number of characters.
8
+ *
9
+ * Counts code points, so an emoji costs one character rather than two.
8
10
  */
9
11
  export declare function minLength(minimum: number): ValidationConstraint<string>;
10
12
  /**
@@ -17,14 +19,23 @@ export declare function maxLength(maximum: number): ValidationConstraint<string>
17
19
  export declare function lengthBetween(minimum: number, maximum: number): ValidationConstraint<string>;
18
20
  /**
19
21
  * Requires a string to match a regular expression.
22
+ *
23
+ * The pattern is copied without the `g` and `y` flags. Those make `test()`
24
+ * stateful through `lastIndex`, so a shared pattern would alternate between
25
+ * accepting and rejecting the very same value.
20
26
  */
21
27
  export declare function matches(pattern: RegExp, message?: string): ValidationConstraint<string>;
22
28
  /**
23
29
  * Requires a valid email-like format.
30
+ *
31
+ * Deliberately structural, not a full RFC 5322 parser: it rejects the shapes
32
+ * that are certainly wrong and leaves deliverability to a verification step.
24
33
  */
25
34
  export declare const email: ValidationConstraint<string>;
26
35
  /**
27
36
  * Requires a UUID-like format.
37
+ *
38
+ * Accepts versions 1 through 8, covering UUIDv7, plus the nil and max UUIDs.
28
39
  */
29
40
  export declare const uuid: ValidationConstraint<string>;
30
41
  /**
@@ -32,7 +43,11 @@ export declare const uuid: ValidationConstraint<string>;
32
43
  */
33
44
  export declare const httpUrl: ValidationConstraint<string>;
34
45
  /**
35
- * Requires a string to contain only ASCII characters.
46
+ * Requires a string to contain only printable ASCII characters.
47
+ *
48
+ * Control characters are excluded. A value that passes this is safe to place
49
+ * in a header or a single log line, which is what callers assume of a check
50
+ * named "ascii".
36
51
  */
37
52
  export declare const ascii: ValidationConstraint<string>;
38
53
  /**
@@ -44,7 +59,7 @@ export declare const digits: ValidationConstraint<string>;
44
59
  */
45
60
  export declare const letters: ValidationConstraint<string>;
46
61
  /**
47
- * Requires a string to contain only letters, numbers, underscores, or hyphens.
62
+ * Requires a lowercase, hyphen-separated slug.
48
63
  */
49
64
  export declare const slug: ValidationConstraint<string>;
50
65
  //# sourceMappingURL=validationConstraints.string.d.ts.map
@@ -1,4 +1,8 @@
1
- import { createConstraint, assertNonNegativeInteger, } from "./validationConstraints.base.js";
1
+ import { createConstraint, assertNonNegativeInteger, } from "../validationConstraints.base.js";
2
+ /** Counts Unicode code points rather than UTF-16 code units. */
3
+ function characterLength(value) {
4
+ return [...value].length;
5
+ }
2
6
  /**
3
7
  * Requires a string to be non-empty after trimming.
4
8
  */
@@ -9,13 +13,16 @@ export const nonEmptyString = createConstraint((value) => typeof value === "stri
9
13
  });
10
14
  /**
11
15
  * Requires a string to contain at least the specified number of characters.
16
+ *
17
+ * Counts code points, so an emoji costs one character rather than two.
12
18
  */
13
19
  export function minLength(minimum) {
14
20
  assertNonNegativeInteger(minimum, "minimum");
15
- return createConstraint((value) => value.length >= minimum, {
21
+ return createConstraint((value) => characterLength(value) >= minimum, {
16
22
  name: `min_length_${minimum}`,
17
23
  code: "min_length",
18
24
  message: `Value must contain at least ${minimum} characters.`,
25
+ guard: (value) => typeof value === "string",
19
26
  });
20
27
  }
21
28
  /**
@@ -23,10 +30,11 @@ export function minLength(minimum) {
23
30
  */
24
31
  export function maxLength(maximum) {
25
32
  assertNonNegativeInteger(maximum, "maximum");
26
- return createConstraint((value) => value.length <= maximum, {
33
+ return createConstraint((value) => characterLength(value) <= maximum, {
27
34
  name: `max_length_${maximum}`,
28
35
  code: "max_length",
29
36
  message: `Value must contain at most ${maximum} characters.`,
37
+ guard: (value) => typeof value === "string",
30
38
  });
31
39
  }
32
40
  /**
@@ -38,37 +46,56 @@ export function lengthBetween(minimum, maximum) {
38
46
  if (minimum > maximum) {
39
47
  throw new RangeError("minimum cannot be greater than maximum.");
40
48
  }
41
- return createConstraint((value) => value.length >= minimum && value.length <= maximum, {
49
+ return createConstraint((value) => {
50
+ const length = characterLength(value);
51
+ return length >= minimum && length <= maximum;
52
+ }, {
42
53
  name: `length_between_${minimum}_${maximum}`,
43
54
  code: "length_between",
44
55
  message: `Value must contain between ${minimum} and ${maximum} characters.`,
56
+ guard: (value) => typeof value === "string",
45
57
  });
46
58
  }
47
59
  /**
48
60
  * Requires a string to match a regular expression.
61
+ *
62
+ * The pattern is copied without the `g` and `y` flags. Those make `test()`
63
+ * stateful through `lastIndex`, so a shared pattern would alternate between
64
+ * accepting and rejecting the very same value.
49
65
  */
50
66
  export function matches(pattern, message = "Value has an invalid format.") {
51
- return createConstraint((value) => pattern.test(value), {
67
+ const stateless = new RegExp(pattern.source, pattern.flags.replace(/[gy]/gu, ""));
68
+ return createConstraint((value) => stateless.test(value), {
52
69
  name: "matches",
53
70
  code: "invalid_format",
54
71
  message,
72
+ guard: (value) => typeof value === "string",
55
73
  });
56
74
  }
57
75
  /**
58
76
  * Requires a valid email-like format.
77
+ *
78
+ * Deliberately structural, not a full RFC 5322 parser: it rejects the shapes
79
+ * that are certainly wrong and leaves deliverability to a verification step.
59
80
  */
60
- export const email = createConstraint((value) => /^[^\s@]+@[^\s@]+\.[^\s@]+$/u.test(value), {
81
+ export const email = createConstraint((value) => /^[^\s@,;<>"[\]\\]+@[a-z0-9](?:[a-z0-9-]*[a-z0-9])?(?:\.[a-z0-9](?:[a-z0-9-]*[a-z0-9])?)+$/iu.test(value) && !value.includes(".."), {
61
82
  name: "email",
62
83
  code: "invalid_email",
63
84
  message: "Value must be a valid email address.",
85
+ guard: (value) => typeof value === "string",
64
86
  });
65
87
  /**
66
88
  * Requires a UUID-like format.
89
+ *
90
+ * Accepts versions 1 through 8, covering UUIDv7, plus the nil and max UUIDs.
67
91
  */
68
- export const uuid = createConstraint((value) => /^[0-9a-f]{8}-[0-9a-f]{4}-[1-5][0-9a-f]{3}-[89ab][0-9a-f]{3}-[0-9a-f]{12}$/iu.test(value), {
92
+ export const uuid = createConstraint((value) => /^[0-9a-f]{8}-[0-9a-f]{4}-[1-8][0-9a-f]{3}-[89ab][0-9a-f]{3}-[0-9a-f]{12}$/iu.test(value) ||
93
+ value === "00000000-0000-0000-0000-000000000000" ||
94
+ value.toLowerCase() === "ffffffff-ffff-ffff-ffff-ffffffffffff", {
69
95
  name: "uuid",
70
96
  code: "invalid_uuid",
71
97
  message: "Value must be a valid UUID.",
98
+ guard: (value) => typeof value === "string",
72
99
  });
73
100
  /**
74
101
  * Requires an HTTP or HTTPS URL.
@@ -85,22 +112,29 @@ export const httpUrl = createConstraint((value) => {
85
112
  name: "http_url",
86
113
  code: "invalid_url",
87
114
  message: "Value must be a valid HTTP or HTTPS URL.",
115
+ guard: (value) => typeof value === "string",
88
116
  });
89
117
  /**
90
- * Requires a string to contain only ASCII characters.
118
+ * Requires a string to contain only printable ASCII characters.
119
+ *
120
+ * Control characters are excluded. A value that passes this is safe to place
121
+ * in a header or a single log line, which is what callers assume of a check
122
+ * named "ascii".
91
123
  */
92
- export const ascii = createConstraint((value) => /^[\x00-\x7F]*$/u.test(value), {
124
+ export const ascii = createConstraint((value) => /^[\x20-\x7E]*$/u.test(value), {
93
125
  name: "ascii",
94
126
  code: "invalid_ascii",
95
- message: "Value must contain only ASCII characters.",
127
+ message: "Value must contain only printable ASCII characters.",
128
+ guard: (value) => typeof value === "string",
96
129
  });
97
130
  /**
98
131
  * Requires a string to contain only digits.
99
132
  */
100
- export const digits = createConstraint((value) => /^\d+$/u.test(value), {
133
+ export const digits = createConstraint((value) => /^[0-9]+$/u.test(value), {
101
134
  name: "digits",
102
135
  code: "invalid_digits",
103
136
  message: "Value must contain only digits.",
137
+ guard: (value) => typeof value === "string",
104
138
  });
105
139
  /**
106
140
  * Requires a string to contain only letters.
@@ -109,13 +143,15 @@ export const letters = createConstraint((value) => /^\p{L}+$/u.test(value), {
109
143
  name: "letters",
110
144
  code: "invalid_letters",
111
145
  message: "Value must contain only letters.",
146
+ guard: (value) => typeof value === "string",
112
147
  });
113
148
  /**
114
- * Requires a string to contain only letters, numbers, underscores, or hyphens.
149
+ * Requires a lowercase, hyphen-separated slug.
115
150
  */
116
151
  export const slug = createConstraint((value) => /^[a-z0-9]+(?:-[a-z0-9]+)*$/u.test(value), {
117
152
  name: "slug",
118
153
  code: "invalid_slug",
119
- message: "Value must be a valid slug.",
154
+ message: "Value must be a slug: lowercase letters and digits, separated by single hyphens.",
155
+ guard: (value) => typeof value === "string",
120
156
  });
121
157
  //# sourceMappingURL=validationConstraints.string.js.map
@@ -0,0 +1,10 @@
1
+ /**
2
+ * @zudojs/validation — Structural guards for untrusted object graphs.
3
+ *
4
+ * @module validationConstraints/structure
5
+ */
6
+ export * from "./validationConstraints.traverse.js";
7
+ export * from "./validationConstraints.depth.js";
8
+ export * from "./validationConstraints.size.js";
9
+ export * from "./validationConstraints.circular.js";
10
+ //# sourceMappingURL=index.d.ts.map
@@ -0,0 +1,10 @@
1
+ /**
2
+ * @zudojs/validation — Structural guards for untrusted object graphs.
3
+ *
4
+ * @module validationConstraints/structure
5
+ */
6
+ export * from "./validationConstraints.traverse.js";
7
+ export * from "./validationConstraints.depth.js";
8
+ export * from "./validationConstraints.size.js";
9
+ export * from "./validationConstraints.circular.js";
10
+ //# sourceMappingURL=index.js.map
@@ -7,14 +7,18 @@
7
7
  /**
8
8
  * Detect circular references in a value graph.
9
9
  *
10
- * Uses a WeakSet to track visited objects without preventing GC.
11
- * Throws CircularReferenceError on first cycle detected.
10
+ * Only a value that references *itself* through the current path is a cycle.
11
+ * The same object appearing twice in sibling positions is an ordinary shared
12
+ * reference and is allowed: rejecting it turned away every valid payload that
13
+ * reused a config object or a lookup record.
12
14
  *
13
15
  * @param value - The value to check for circular references.
14
16
  * @param path - Current traversal path for error reporting.
15
- * @param seen - WeakSet tracking visited objects (internal).
17
+ * @param maxDepth - Depth ceiling, so a deep graph cannot exhaust the stack
18
+ * before a cycle is reported.
19
+ * @throws {CircularReferenceError} on the first cycle found.
16
20
  */
17
- export declare function assertNoCircularReference(value: unknown, path?: string, seen?: WeakSet<object>): void;
21
+ export declare function assertNoCircularReference(value: unknown, path?: string, maxDepth?: number): void;
18
22
  /**
19
23
  * Check whether a value contains circular references.
20
24
  *
@@ -0,0 +1,55 @@
1
+ /**
2
+ * @zudojs/validation — Circular reference detection.
3
+ *
4
+ * Detects circular references in object graphs before serialization
5
+ * or validation to prevent stack overflows and provide clear error messages.
6
+ */
7
+ import { CircularReferenceError } from "@zudojs/errors";
8
+ import { MAX_MEASURABLE_DEPTH } from "./validationConstraints.depth.js";
9
+ import { TraversalLimitError, traverse, } from "./validationConstraints.traverse.js";
10
+ /**
11
+ * Detect circular references in a value graph.
12
+ *
13
+ * Only a value that references *itself* through the current path is a cycle.
14
+ * The same object appearing twice in sibling positions is an ordinary shared
15
+ * reference and is allowed: rejecting it turned away every valid payload that
16
+ * reused a config object or a lookup record.
17
+ *
18
+ * @param value - The value to check for circular references.
19
+ * @param path - Current traversal path for error reporting.
20
+ * @param maxDepth - Depth ceiling, so a deep graph cannot exhaust the stack
21
+ * before a cycle is reported.
22
+ * @throws {CircularReferenceError} on the first cycle found.
23
+ */
24
+ export function assertNoCircularReference(value, path = "root", maxDepth = MAX_MEASURABLE_DEPTH) {
25
+ try {
26
+ traverse(value, { maxDepth, failOnCycle: true }, path);
27
+ }
28
+ catch (error) {
29
+ if (error instanceof TraversalLimitError && error.halt === "cycle") {
30
+ throw new CircularReferenceError(error.path);
31
+ }
32
+ if (error instanceof TraversalLimitError && error.halt === "depth") {
33
+ throw new CircularReferenceError(`${error.path} (exceeded ${maxDepth} levels)`);
34
+ }
35
+ throw error;
36
+ }
37
+ }
38
+ /**
39
+ * Check whether a value contains circular references.
40
+ *
41
+ * Returns true if a cycle is found, false otherwise.
42
+ * Does not throw — use assertNoCircularReference for throwing behavior.
43
+ */
44
+ export function hasCircularReference(value) {
45
+ try {
46
+ assertNoCircularReference(value);
47
+ return false;
48
+ }
49
+ catch (error) {
50
+ if (error instanceof CircularReferenceError)
51
+ return true;
52
+ throw error;
53
+ }
54
+ }
55
+ //# sourceMappingURL=validationConstraints.circular.js.map
@@ -0,0 +1,37 @@
1
+ /**
2
+ * @zudojs/validation — Depth checking.
3
+ *
4
+ * Computes and validates the nesting depth of object graphs
5
+ * to prevent stack overflows from deeply nested payloads.
6
+ */
7
+ /**
8
+ * Hard ceiling on how deep {@link getSerializationDepth} will descend.
9
+ *
10
+ * The measurement itself is recursive, so it needs a bound of its own: a guard
11
+ * that overflows the stack on the input it was checking protects nothing.
12
+ */
13
+ export declare const MAX_MEASURABLE_DEPTH = 512;
14
+ /**
15
+ * Compute the maximum nesting depth of a value.
16
+ *
17
+ * Primitives return 0. Arrays and objects return 1 + the maximum
18
+ * depth of their children. Cycles are not followed.
19
+ *
20
+ * @param value - The value to measure.
21
+ * @param limit - Stop measuring beyond this depth. Defaults to
22
+ * {@link MAX_MEASURABLE_DEPTH}; the limit itself is returned when reached.
23
+ * @returns The observed depth, capped at `limit`.
24
+ */
25
+ export declare function getSerializationDepth(value: unknown, limit?: number): number;
26
+ /**
27
+ * Assert that a value does not exceed the maximum allowed depth.
28
+ *
29
+ * Stops descending the moment the limit is passed, so the cost of the check is
30
+ * bounded by `maxDepth` rather than by the size of the input.
31
+ *
32
+ * @param value - The value to check.
33
+ * @param maxDepth - Maximum permitted nesting depth.
34
+ * @throws {SerializationDepthError} when depth exceeds the limit.
35
+ */
36
+ export declare function assertDepthWithinLimit(value: unknown, maxDepth: number): void;
37
+ //# sourceMappingURL=validationConstraints.depth.d.ts.map
@@ -0,0 +1,58 @@
1
+ /**
2
+ * @zudojs/validation — Depth checking.
3
+ *
4
+ * Computes and validates the nesting depth of object graphs
5
+ * to prevent stack overflows from deeply nested payloads.
6
+ */
7
+ import { SerializationDepthError } from "@zudojs/errors";
8
+ import { TraversalLimitError, traverse, } from "./validationConstraints.traverse.js";
9
+ /**
10
+ * Hard ceiling on how deep {@link getSerializationDepth} will descend.
11
+ *
12
+ * The measurement itself is recursive, so it needs a bound of its own: a guard
13
+ * that overflows the stack on the input it was checking protects nothing.
14
+ */
15
+ export const MAX_MEASURABLE_DEPTH = 512;
16
+ /**
17
+ * Compute the maximum nesting depth of a value.
18
+ *
19
+ * Primitives return 0. Arrays and objects return 1 + the maximum
20
+ * depth of their children. Cycles are not followed.
21
+ *
22
+ * @param value - The value to measure.
23
+ * @param limit - Stop measuring beyond this depth. Defaults to
24
+ * {@link MAX_MEASURABLE_DEPTH}; the limit itself is returned when reached.
25
+ * @returns The observed depth, capped at `limit`.
26
+ */
27
+ export function getSerializationDepth(value, limit = MAX_MEASURABLE_DEPTH) {
28
+ try {
29
+ return traverse(value, { maxDepth: limit }).depth;
30
+ }
31
+ catch (error) {
32
+ if (error instanceof TraversalLimitError)
33
+ return limit;
34
+ throw error;
35
+ }
36
+ }
37
+ /**
38
+ * Assert that a value does not exceed the maximum allowed depth.
39
+ *
40
+ * Stops descending the moment the limit is passed, so the cost of the check is
41
+ * bounded by `maxDepth` rather than by the size of the input.
42
+ *
43
+ * @param value - The value to check.
44
+ * @param maxDepth - Maximum permitted nesting depth.
45
+ * @throws {SerializationDepthError} when depth exceeds the limit.
46
+ */
47
+ export function assertDepthWithinLimit(value, maxDepth) {
48
+ try {
49
+ traverse(value, { maxDepth });
50
+ }
51
+ catch (error) {
52
+ if (error instanceof TraversalLimitError && error.halt === "depth") {
53
+ throw new SerializationDepthError(error.observed, maxDepth);
54
+ }
55
+ throw error;
56
+ }
57
+ }
58
+ //# sourceMappingURL=validationConstraints.depth.js.map
@@ -0,0 +1,31 @@
1
+ /**
2
+ * @zudojs/validation — Size checking.
3
+ *
4
+ * Estimates serialized payload size to prevent memory exhaustion
5
+ * from excessively large payloads.
6
+ */
7
+ /**
8
+ * Estimate the byte size of a value as JSON without allocating a string.
9
+ *
10
+ * A value referenced from several places is charged once per occurrence, the
11
+ * way a serializer expands it. Counting it once let a compact payload built
12
+ * from shared subtrees estimate at a few hundred bytes while serializing to
13
+ * hundreds of megabytes.
14
+ *
15
+ * @param value - The value to measure.
16
+ * @param maxBytes - Stop counting past this budget; the budget is returned.
17
+ * @returns The estimated serialized size in bytes.
18
+ */
19
+ export declare function estimateSerializedSize(value: unknown, maxBytes?: number): number;
20
+ /**
21
+ * Assert that a value's estimated serialized size is within limits.
22
+ *
23
+ * Aborts as soon as the budget is passed, so an oversized payload is rejected
24
+ * without first being measured in full.
25
+ *
26
+ * @param value - The value to check.
27
+ * @param maxSize - Maximum permitted size in bytes.
28
+ * @throws {SerializationPayloadTooLargeError} when the estimate exceeds maxSize.
29
+ */
30
+ export declare function assertSizeWithinLimit(value: unknown, maxSize: number): void;
31
+ //# sourceMappingURL=validationConstraints.size.d.ts.map