@clementine-solutions/jane-io 1.0.1 → 1.0.2

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 (204) hide show
  1. package/dist/core/analysis/diff.js +88 -0
  2. package/dist/core/analysis/explain.js +117 -0
  3. package/dist/core/analysis/index.js +26 -0
  4. package/dist/core/analysis/replay.js +68 -0
  5. package/dist/core/analysis/telemetry.js +123 -0
  6. package/dist/core/boundary-rules/at-most-one.js +38 -0
  7. package/dist/core/boundary-rules/conditionally-required.js +44 -0
  8. package/dist/core/boundary-rules/date-range.js +39 -0
  9. package/dist/core/boundary-rules/index.js +21 -0
  10. package/dist/core/boundary-rules/mutually-exclusive.js +38 -0
  11. package/dist/core/boundary-rules/no-unknown-fields.js +39 -0
  12. package/dist/core/boundary-rules/require-all.js +39 -0
  13. package/dist/core/boundary-rules/require-one.js +41 -0
  14. package/dist/core/common/events.js +82 -0
  15. package/dist/core/common/fluent.js +429 -0
  16. package/dist/core/common/index.js +31 -0
  17. package/dist/core/common/policy.js +550 -0
  18. package/dist/core/common/utilities.js +177 -0
  19. package/dist/core/common/wildcard.js +63 -0
  20. package/dist/core/field-path/construct.js +189 -0
  21. package/dist/core/field-path/format.js +154 -0
  22. package/dist/core/field-path/index.js +26 -0
  23. package/dist/core/field-path/utilities.js +138 -0
  24. package/dist/core/field-path/walk.js +80 -0
  25. package/dist/core/fluent-registry.js +151 -0
  26. package/dist/core/normalizers/array/compact-sparse-array.js +40 -0
  27. package/dist/core/normalizers/array/flatten-one-level.js +45 -0
  28. package/dist/core/normalizers/array/remove-empty-string-items.js +34 -0
  29. package/dist/core/normalizers/array/remove-null-items.js +34 -0
  30. package/dist/core/normalizers/array/remove-undefined-items.js +34 -0
  31. package/dist/core/normalizers/date/invalid-date-to-undefined.js +35 -0
  32. package/dist/core/normalizers/index.js +46 -0
  33. package/dist/core/normalizers/normalizer-register.js +41 -0
  34. package/dist/core/normalizers/number/infinity-to-undefined.js +35 -0
  35. package/dist/core/normalizers/number/nan-to-undefined.js +34 -0
  36. package/dist/core/normalizers/number/normalize-negative-zero.js +33 -0
  37. package/dist/core/normalizers/object/remove-empty-array-keys.js +38 -0
  38. package/dist/core/normalizers/object/remove-empty-object-keys.js +42 -0
  39. package/dist/core/normalizers/object/remove-empty-string-keys.js +37 -0
  40. package/dist/core/normalizers/object/remove-null-keys.js +37 -0
  41. package/dist/core/normalizers/object/remove-undefined-keys.js +37 -0
  42. package/dist/core/normalizers/string/collapse-whitespace.js +35 -0
  43. package/dist/core/normalizers/string/empty-to-undefined.js +33 -0
  44. package/dist/core/normalizers/string/trim.js +34 -0
  45. package/dist/core/parsers/index.js +43 -0
  46. package/dist/core/parsers/parse-array-string.js +36 -0
  47. package/dist/core/parsers/parse-bigint-string.js +33 -0
  48. package/dist/core/parsers/parse-binary-string.js +33 -0
  49. package/dist/core/parsers/parse-boolean-string.js +27 -0
  50. package/dist/core/parsers/parse-date-string.js +30 -0
  51. package/dist/core/parsers/parse-duration-string.js +37 -0
  52. package/dist/core/parsers/parse-hex-string.js +29 -0
  53. package/dist/core/parsers/parse-integer-string.js +30 -0
  54. package/dist/core/parsers/parse-json-string.js +35 -0
  55. package/dist/core/parsers/parse-numeric-string.js +29 -0
  56. package/dist/core/parsers/parse-object-string.js +36 -0
  57. package/dist/core/parsers/parse-octal-string.js +33 -0
  58. package/dist/core/parsers/parse-scientific-notation-string.js +30 -0
  59. package/dist/core/parsers/parse-url-string.js +36 -0
  60. package/dist/core/pipeline/boundary.js +256 -0
  61. package/dist/core/pipeline/contain.js +339 -0
  62. package/dist/core/pipeline/index.js +37 -0
  63. package/dist/core/pipeline/normalize.js +76 -0
  64. package/dist/core/pipeline/parse.js +91 -0
  65. package/dist/core/pipeline/pipeline.js +418 -0
  66. package/dist/core/pipeline/scan.js +115 -0
  67. package/dist/core/pipeline/validate.js +74 -0
  68. package/dist/core/scanners/any/scan-for-sentinels.js +35 -0
  69. package/dist/core/scanners/array/array-is-deep.js +38 -0
  70. package/dist/core/scanners/array/array-is-heterogenous.js +39 -0
  71. package/dist/core/scanners/array/array-is-large.js +32 -0
  72. package/dist/core/scanners/bigint/bigint-is-large.js +34 -0
  73. package/dist/core/scanners/bigint/bigint-not-safe.js +34 -0
  74. package/dist/core/scanners/date/date-is-before-epoch.js +32 -0
  75. package/dist/core/scanners/date/date-is-far-future.js +32 -0
  76. package/dist/core/scanners/date/date-is-invalid.js +31 -0
  77. package/dist/core/scanners/index.js +58 -0
  78. package/dist/core/scanners/number/number-is-infinite.js +31 -0
  79. package/dist/core/scanners/number/number-is-nan.js +31 -0
  80. package/dist/core/scanners/number/number-is-too-large.js +33 -0
  81. package/dist/core/scanners/number/number-is-unsafe-integer.js +31 -0
  82. package/dist/core/scanners/object/object-has-circular-references.js +43 -0
  83. package/dist/core/scanners/object/object-has-many-keys.js +33 -0
  84. package/dist/core/scanners/object/object-is-deep.js +38 -0
  85. package/dist/core/scanners/scanner-registry.js +36 -0
  86. package/dist/core/scanners/string/string-has-unsafe-unicode.js +32 -0
  87. package/dist/core/scanners/string/string-has-whitespace-edges.js +31 -0
  88. package/dist/core/scanners/string/string-is-long.js +32 -0
  89. package/dist/core/scanners/unknown/unknown-not-scannable.js +34 -0
  90. package/dist/core/shapes/analysis.js +11 -0
  91. package/dist/core/shapes/boundary.js +11 -0
  92. package/dist/core/shapes/events.js +10 -0
  93. package/dist/core/shapes/field-path.js +10 -0
  94. package/dist/core/shapes/index.js +11 -0
  95. package/dist/core/shapes/normalize.js +11 -0
  96. package/dist/core/shapes/parse.js +11 -0
  97. package/dist/core/shapes/pipeline.js +11 -0
  98. package/dist/core/shapes/policy.js +11 -0
  99. package/dist/core/shapes/public.js +10 -0
  100. package/dist/core/shapes/scan.js +11 -0
  101. package/dist/core/shapes/validate.js +11 -0
  102. package/dist/core/validators/array/array-max-items.js +42 -0
  103. package/dist/core/validators/array/array-min-items.js +42 -0
  104. package/dist/core/validators/array/array.js +34 -0
  105. package/dist/core/validators/array/excludes.js +47 -0
  106. package/dist/core/validators/array/has-unique-items.js +46 -0
  107. package/dist/core/validators/array/includes.js +46 -0
  108. package/dist/core/validators/array/items-equal.js +42 -0
  109. package/dist/core/validators/array/no-empty-string-items.js +46 -0
  110. package/dist/core/validators/array/no-null-items.js +46 -0
  111. package/dist/core/validators/array/no-undefined-items.js +45 -0
  112. package/dist/core/validators/array/non-empty-array.js +45 -0
  113. package/dist/core/validators/array/not-sparse.js +44 -0
  114. package/dist/core/validators/bigint/bigint-equals.js +57 -0
  115. package/dist/core/validators/bigint/bigint-max.js +63 -0
  116. package/dist/core/validators/bigint/bigint-min.js +87 -0
  117. package/dist/core/validators/bigint/bigint-negative.js +73 -0
  118. package/dist/core/validators/bigint/bigint-non-negative.js +72 -0
  119. package/dist/core/validators/bigint/bigint-non-positive.js +72 -0
  120. package/dist/core/validators/bigint/bigint-positive.js +72 -0
  121. package/dist/core/validators/bigint/bigint-safe.js +75 -0
  122. package/dist/core/validators/bigint/bigint.js +38 -0
  123. package/dist/core/validators/boolean/boolean.js +39 -0
  124. package/dist/core/validators/boolean/is-false.js +48 -0
  125. package/dist/core/validators/boolean/is-true.js +48 -0
  126. package/dist/core/validators/common/is-country-code.js +36 -0
  127. package/dist/core/validators/common/is-currency-code.js +36 -0
  128. package/dist/core/validators/common/is-email-strict.js +36 -0
  129. package/dist/core/validators/common/is-email.js +36 -0
  130. package/dist/core/validators/common/is-ip.js +37 -0
  131. package/dist/core/validators/common/is-phone-strict.js +36 -0
  132. package/dist/core/validators/common/is-phone.js +36 -0
  133. package/dist/core/validators/common/is-port.js +35 -0
  134. package/dist/core/validators/common/is-postal-code.js +36 -0
  135. package/dist/core/validators/common/is-url.js +38 -0
  136. package/dist/core/validators/common/is-uuid.js +36 -0
  137. package/dist/core/validators/date/before-epoch.js +56 -0
  138. package/dist/core/validators/date/date-now-required.js +48 -0
  139. package/dist/core/validators/date/is-date.js +47 -0
  140. package/dist/core/validators/date/is-far-future.js +46 -0
  141. package/dist/core/validators/date/is-future.js +59 -0
  142. package/dist/core/validators/date/is-past.js +59 -0
  143. package/dist/core/validators/date/not-after.js +66 -0
  144. package/dist/core/validators/date/not-before.js +66 -0
  145. package/dist/core/validators/date/same-day.js +60 -0
  146. package/dist/core/validators/date/same-month.js +59 -0
  147. package/dist/core/validators/date/same-year.js +56 -0
  148. package/dist/core/validators/date/too-early.js +57 -0
  149. package/dist/core/validators/date/too-late.js +57 -0
  150. package/dist/core/validators/date/weekday.js +65 -0
  151. package/dist/core/validators/date/weekend.js +56 -0
  152. package/dist/core/validators/index.js +139 -0
  153. package/dist/core/validators/nullish/is-null-or-undefined.js +40 -0
  154. package/dist/core/validators/nullish/is-null.js +39 -0
  155. package/dist/core/validators/nullish/is-undefined.js +39 -0
  156. package/dist/core/validators/number/finite.js +40 -0
  157. package/dist/core/validators/number/integer.js +40 -0
  158. package/dist/core/validators/number/less-than.js +39 -0
  159. package/dist/core/validators/number/max.js +39 -0
  160. package/dist/core/validators/number/min.js +39 -0
  161. package/dist/core/validators/number/more-than.js +39 -0
  162. package/dist/core/validators/number/negative.js +38 -0
  163. package/dist/core/validators/number/non-negative.js +37 -0
  164. package/dist/core/validators/number/non-positive.js +37 -0
  165. package/dist/core/validators/number/number.js +31 -0
  166. package/dist/core/validators/number/positive.js +38 -0
  167. package/dist/core/validators/number/safe-integer.js +42 -0
  168. package/dist/core/validators/object/deep-equals.js +47 -0
  169. package/dist/core/validators/object/has-key.js +42 -0
  170. package/dist/core/validators/object/has-value.js +59 -0
  171. package/dist/core/validators/object/keys-equal.js +47 -0
  172. package/dist/core/validators/object/max-keys.js +43 -0
  173. package/dist/core/validators/object/min-keys.js +43 -0
  174. package/dist/core/validators/object/missing-key.js +42 -0
  175. package/dist/core/validators/object/no-empty-array-values.js +44 -0
  176. package/dist/core/validators/object/no-empty-object-values.js +44 -0
  177. package/dist/core/validators/object/no-null-values.js +44 -0
  178. package/dist/core/validators/object/no-undefined-values.js +44 -0
  179. package/dist/core/validators/object/non-empty-object.js +40 -0
  180. package/dist/core/validators/object/only-keys.js +43 -0
  181. package/dist/core/validators/object/plain-object.js +35 -0
  182. package/dist/core/validators/string/alpha-num.js +50 -0
  183. package/dist/core/validators/string/alpha.js +51 -0
  184. package/dist/core/validators/string/chars-equal.js +49 -0
  185. package/dist/core/validators/string/ends-with.js +50 -0
  186. package/dist/core/validators/string/is-ascii.js +53 -0
  187. package/dist/core/validators/string/is-printable.js +53 -0
  188. package/dist/core/validators/string/matches.js +50 -0
  189. package/dist/core/validators/string/max-length.js +50 -0
  190. package/dist/core/validators/string/min-length.js +50 -0
  191. package/dist/core/validators/string/no-lead-space.js +50 -0
  192. package/dist/core/validators/string/no-repeat-space.js +52 -0
  193. package/dist/core/validators/string/no-space.js +51 -0
  194. package/dist/core/validators/string/no-trail-space.js +50 -0
  195. package/dist/core/validators/string/non-empty.js +48 -0
  196. package/dist/core/validators/string/not-one-of.js +51 -0
  197. package/dist/core/validators/string/num-string.js +50 -0
  198. package/dist/core/validators/string/one-of.js +50 -0
  199. package/dist/core/validators/string/starts-with.js +50 -0
  200. package/dist/core/validators/string/string.js +39 -0
  201. package/dist/core/validators/string/trimmed.js +51 -0
  202. package/dist/index.js +26 -0
  203. package/dist/test.js +12 -0
  204. package/package.json +1 -1
@@ -0,0 +1,36 @@
1
+ /**
2
+ * ----------------------------------------------------------------------------
3
+ * Validators | Is UUID
4
+ * ----------------------------------------------------------------------------
5
+ * @package @clementine-solutions/jane
6
+ * @description Validates UUIDs (versions 1–4).
7
+ * @see https://jane-io.com
8
+ * ----------------------------------------------------------------------------
9
+ */
10
+ import { validationEvent } from '../../pipeline';
11
+ import { detectStructuralType } from '../../pipeline/scan';
12
+ import { safeStringify } from '../../common';
13
+ /* ——— * ————— * ————— * ————— * ————— * ————— * ————— * ————— * ————— * ——— *\
14
+ |* Implementation *|
15
+ \* ——— * ————— * ————— * ————— * ————— * ————— * ————— * ————— * ————— * ——— */
16
+ /**
17
+ * Validates UUIDs (versions 1–4).
18
+ *
19
+ * Enforces canonical hyphenated formatting and correct version/variant bits,
20
+ * ensuring the identifier is structurally sound and standards‑compliant.
21
+ */
22
+ export const isUuid = async (value, path) => {
23
+ const type = detectStructuralType(value);
24
+ if (type !== 'string') {
25
+ return [
26
+ validationEvent('error', 'type.not.valid', path, `Expected 'string' but received '${type}'.`, `Please provide a valid UUID.`, { value, expected: 'string', actual: type }),
27
+ ];
28
+ }
29
+ const re = /^[0-9a-fA-F]{8}-[0-9a-fA-F]{4}-[1-4][0-9a-fA-F]{3}-[89abAB][0-9a-fA-F]{3}-[0-9a-fA-F]{12}$/;
30
+ if (!re.test(value)) {
31
+ return [
32
+ validationEvent('error', 'string.not.uuid', path, `${safeStringify(value)} is not a valid UUID (v1–v4).`, `Please provide a valid UUID.`, { value }),
33
+ ];
34
+ }
35
+ return [];
36
+ };
@@ -0,0 +1,56 @@
1
+ /**
2
+ * ----------------------------------------------------------------------------
3
+ * Validators | Before Epoch
4
+ * ----------------------------------------------------------------------------
5
+ * @package @clementine-solutions/jane
6
+ * @description Validates that a value is a JavaScript Date occurring before
7
+ * the Unix epoch (January 1, 1970, 00:00:00 UTC).
8
+ * @see https://jane-io.com
9
+ * ----------------------------------------------------------------------------
10
+ */
11
+ import { validationEvent } from '../../pipeline';
12
+ import { safeStringify } from '../../common';
13
+ import { detectStructuralType } from '../../pipeline/scan';
14
+ /* ——— * ————— * ————— * ————— * ————— * ————— * ————— * ————— * ————— * ——— *\
15
+ |* Implementation *|
16
+ \* ——— * ————— * ————— * ————— * ————— * ————— * ————— * ————— * ————— * ——— */
17
+ /**
18
+ * Validates that a value is a JavaScript Date occurring before
19
+ * the Unix epoch (January 1, 1970, 00:00:00 UTC).
20
+ *
21
+ * This rule checks:
22
+ * - The value is structurally a Date instance
23
+ * - The date timestamp is strictly less than the Unix epoch
24
+ *
25
+ * Emits:
26
+ * - date.not.date (value is not a Date)
27
+ * - date.not.beforeEpoch (date is on or after the Unix epoch)
28
+ *
29
+ * This rule is async-compatible and returns a readonly array
30
+ * of JaneEvent objects.
31
+ * ----------------------------------------------------------------------------
32
+ */
33
+ export const beforeEpoch = async (value, path) => {
34
+ const structuralType = detectStructuralType(value);
35
+ if (!(value instanceof Date)) {
36
+ return [
37
+ validationEvent('error', 'date.not.date', path, `Expected 'Date' but received '${structuralType}'.`, `Please provide a valid date.`, {
38
+ value,
39
+ expected: 'Date',
40
+ actual: structuralType,
41
+ }),
42
+ ];
43
+ }
44
+ const unixEpochMilliseconds = 0;
45
+ const timestamp = value.getTime();
46
+ if (timestamp >= unixEpochMilliseconds) {
47
+ return [
48
+ validationEvent('error', 'date.is.before-epoch', path, `${safeStringify(value)} must be before the Unix epoch.`, `Please provide a date before January 1, 1970.`, {
49
+ value,
50
+ expected: '< 1970-01-01T00:00:00.000Z',
51
+ actual: value.toISOString(),
52
+ }),
53
+ ];
54
+ }
55
+ return [];
56
+ };
@@ -0,0 +1,48 @@
1
+ /**
2
+ * ----------------------------------------------------------------------------
3
+ * Validators | Date Now Required
4
+ * ----------------------------------------------------------------------------
5
+ * @package @clementine-solutions/jane
6
+ * @description Ensures the input is a Date instance representing today's
7
+ * date.
8
+ * @see https://jane-io.com
9
+ * ----------------------------------------------------------------------------
10
+ */
11
+ import { validationEvent } from '../../pipeline';
12
+ import { safeStringify } from '../../common';
13
+ import { detectStructuralType } from '../../pipeline/scan';
14
+ /* ——— * ————— * ————— * ————— * ————— * ————— * ————— * ————— * ————— * ——— *\
15
+ |* Implementation *|
16
+ \* ——— * ————— * ————— * ————— * ————— * ————— * ————— * ————— * ————— * ——— */
17
+ /**
18
+ * Ensures the input is a Date instance representing today's date.
19
+ *
20
+ * - Non-Date values emit `date.not.date`.
21
+ * - Dates not matching today's year/month/day emit `date.now.undefined`.
22
+ *
23
+ * This rule is pure, total, async-compatible, and preserves the
24
+ * provided path. It supports userMessage overrides applied at the
25
+ * pipeline level.
26
+ */
27
+ export const dateNowRequired = async (value, path) => {
28
+ const structuralType = detectStructuralType(value);
29
+ if (!(value instanceof Date)) {
30
+ return [
31
+ validationEvent('error', 'date.not.date', path, `Expected 'Date' but received '${structuralType}'.`, `Please provide a valid date.`, { value, expected: 'Date', actual: structuralType }),
32
+ ];
33
+ }
34
+ const now = new Date();
35
+ const isSameDay = value.getUTCFullYear() === now.getUTCFullYear() &&
36
+ value.getUTCMonth() === now.getUTCMonth() &&
37
+ value.getUTCDate() === now.getUTCDate();
38
+ if (!isSameDay) {
39
+ return [
40
+ validationEvent('error', 'date.now.undefined', path, `${safeStringify(value)} must be today's date.`, `Please provide today's date.`, {
41
+ value,
42
+ expected: now.toISOString().slice(0, 10),
43
+ actual: value.toISOString().slice(0, 10),
44
+ }),
45
+ ];
46
+ }
47
+ return [];
48
+ };
@@ -0,0 +1,47 @@
1
+ /**
2
+ * ----------------------------------------------------------------------------
3
+ * Validators | Is Date
4
+ * ----------------------------------------------------------------------------
5
+ * @package @clementine-solutions/jane
6
+ * @description Ensures the input is a valid JavaScript Date instance.
7
+ * @see https://jane-io.com
8
+ * ----------------------------------------------------------------------------
9
+ */
10
+ import { validationEvent } from '../../pipeline';
11
+ import { detectStructuralType } from '../../pipeline/scan';
12
+ /* ——— * ————— * ————— * ————— * ————— * ————— * ————— * ————— * ————— * ——— *\
13
+ |* Implementation *|
14
+ \* ——— * ————— * ————— * ————— * ————— * ————— * ————— * ————— * ————— * ——— */
15
+ /**
16
+ * Ensures the input is a valid JavaScript Date instance.
17
+ *
18
+ * - Non-Date values emit `date.not.date`.
19
+ * - Invalid Date instances (NaN timestamp) emit `date.is.invalid`.
20
+ *
21
+ * This rule is pure, total, async-compatible, and preserves the
22
+ * provided path. It supports userMessage overrides applied at the
23
+ * pipeline level.
24
+ */
25
+ export const isDate = async (value, path) => {
26
+ const structuralType = detectStructuralType(value);
27
+ // ------------------------------------------------------------
28
+ // Type check — the input must be a Date instance.
29
+ // ------------------------------------------------------------
30
+ if (!(value instanceof Date)) {
31
+ return [
32
+ validationEvent('error', 'date.not.date', path, `Expected 'Date' but received '${structuralType}'.`, `Please provide a valid date.`, { value, expected: 'Date', actual: structuralType }),
33
+ ];
34
+ }
35
+ // ------------------------------------------------------------
36
+ // Validity check — ensure the Date is not "Invalid Date".
37
+ // ------------------------------------------------------------
38
+ if (isNaN(value.getTime())) {
39
+ return [
40
+ validationEvent('error', 'date.is.invalid', path, `Date value is invalid.`, `Please provide a valid date.`, { value, expected: 'valid Date', actual: value }),
41
+ ];
42
+ }
43
+ // ------------------------------------------------------------
44
+ // Valid — return an empty array to indicate success.
45
+ // ------------------------------------------------------------
46
+ return [];
47
+ };
@@ -0,0 +1,46 @@
1
+ /**
2
+ * ----------------------------------------------------------------------------
3
+ * Validators | Is Far Future
4
+ * ----------------------------------------------------------------------------
5
+ * @package @clementine-solutions/jane
6
+ * @description Ensures the input is a Date instance representing a year
7
+ * strictly greater than 2100.
8
+ * @see https://jane-io.com
9
+ * ----------------------------------------------------------------------------
10
+ */
11
+ import { validationEvent } from '../../pipeline';
12
+ import { detectStructuralType } from '../../pipeline/scan';
13
+ import { safeStringify } from '../../common';
14
+ /* ——— * ————— * ————— * ————— * ————— * ————— * ————— * ————— * ————— * ——— *\
15
+ |* Implementation *|
16
+ \* ——— * ————— * ————— * ————— * ————— * ————— * ————— * ————— * ————— * ——— */
17
+ /**
18
+ * Ensures the input is a Date instance representing a year strictly
19
+ * greater than 2100.
20
+ *
21
+ * - Non-Date values emit `date.not.date`.
22
+ * - Dates with year ≤ 2100 emit `date.is.far-future`.
23
+ *
24
+ * This rule is pure, total, async-compatible, and preserves the
25
+ * provided path. It supports userMessage overrides applied at the
26
+ * pipeline level.
27
+ */
28
+ export const isFarFuture = async (value, path) => {
29
+ const structuralType = detectStructuralType(value);
30
+ if (!(value instanceof Date)) {
31
+ return [
32
+ validationEvent('error', 'date.not.date', path, `Expected 'Date' but received '${structuralType}'.`, `Please provide a valid date.`, { value, expected: 'Date', actual: structuralType }),
33
+ ];
34
+ }
35
+ if (isNaN(value.getTime())) {
36
+ return [
37
+ validationEvent('error', 'date.is.invalid', path, `Date value is invalid.`, `Please provide a valid date.`, { value, expected: 'valid Date', actual: value }),
38
+ ];
39
+ }
40
+ if (value.getUTCFullYear() <= 2100) {
41
+ return [
42
+ validationEvent('error', 'date.is.far-future', path, `${safeStringify(value)} must be after the year 2100.`, `Please provide a far-future date.`, { value, expected: '> 2100', actual: value.getUTCFullYear() }),
43
+ ];
44
+ }
45
+ return [];
46
+ };
@@ -0,0 +1,59 @@
1
+ /**
2
+ * ----------------------------------------------------------------------------
3
+ * Validators | Is Future
4
+ * ----------------------------------------------------------------------------
5
+ * @package @clementine-solutions/jane
6
+ * @description Ensures the input is a Date instance representing a moment
7
+ * strictly after the current system time.
8
+ * @see https://jane-io.com
9
+ * ----------------------------------------------------------------------------
10
+ */
11
+ import { validationEvent } from '../../pipeline';
12
+ import { safeStringify } from '../../common';
13
+ import { detectStructuralType } from '../../pipeline/scan';
14
+ /* ——— * ————— * ————— * ————— * ————— * ————— * ————— * ————— * ————— * ——— *\
15
+ |* Implementation *|
16
+ \* ——— * ————— * ————— * ————— * ————— * ————— * ————— * ————— * ————— * ——— */
17
+ /**
18
+ * Ensures the input is a Date instance representing a moment strictly
19
+ * after the current system time.
20
+ *
21
+ * - Non-Date values emit `date.not.date`.
22
+ * - Invalid Date instances emit `date.is.invalid`.
23
+ * - Dates ≤ now emit `date.in.past`.
24
+ *
25
+ * This rule is pure, total, async-compatible, and preserves the
26
+ * provided path. It supports userMessage overrides applied at the
27
+ * pipeline level.
28
+ */
29
+ export const isFuture = async (value, path) => {
30
+ const structuralType = detectStructuralType(value);
31
+ // ------------------------------------------------------------
32
+ // Type check — the input must be a Date instance.
33
+ // ------------------------------------------------------------
34
+ if (!(value instanceof Date)) {
35
+ return [
36
+ validationEvent('error', 'date.not.date', path, `Expected 'Date' but received '${structuralType}'.`, `Please provide a valid date.`, { value, expected: 'Date', actual: structuralType }),
37
+ ];
38
+ }
39
+ // ------------------------------------------------------------
40
+ // Validity check — reject invalid Date instances.
41
+ // ------------------------------------------------------------
42
+ if (isNaN(value.getTime())) {
43
+ return [
44
+ validationEvent('error', 'date.is.invalid', path, `Date value is invalid.`, `Please provide a valid date.`, { value, expected: 'valid Date', actual: value }),
45
+ ];
46
+ }
47
+ // ------------------------------------------------------------
48
+ // Future check — must be strictly greater than now.
49
+ // ------------------------------------------------------------
50
+ if (value.getTime() <= Date.now()) {
51
+ return [
52
+ validationEvent('error', 'date.in.past', path, `${safeStringify(value)} must be in the future.`, `Please provide a future date.`, { value, expected: 'future date', actual: value }),
53
+ ];
54
+ }
55
+ // ------------------------------------------------------------
56
+ // Valid — return an empty array to indicate success.
57
+ // ------------------------------------------------------------
58
+ return [];
59
+ };
@@ -0,0 +1,59 @@
1
+ /**
2
+ * ----------------------------------------------------------------------------
3
+ * Validators | Is Past
4
+ * ----------------------------------------------------------------------------
5
+ * @package @clementine-solutions/jane
6
+ * @description Ensures the input is a Date instance representing a moment
7
+ * strictly before the current system time.
8
+ * @see https://jane-io.com
9
+ * ----------------------------------------------------------------------------
10
+ */
11
+ import { validationEvent } from '../../pipeline';
12
+ import { detectStructuralType } from '../../pipeline/scan';
13
+ import { safeStringify } from '../../common';
14
+ /* ——— * ————— * ————— * ————— * ————— * ————— * ————— * ————— * ————— * ——— *\
15
+ |* Implementation *|
16
+ \* ——— * ————— * ————— * ————— * ————— * ————— * ————— * ————— * ————— * ——— */
17
+ /**
18
+ * Ensures the input is a Date instance representing a moment strictly
19
+ * before the current system time.
20
+ *
21
+ * - Non-Date values emit `date.not.date`.
22
+ * - Invalid Date instances emit `date.is.invalid`.
23
+ * - Dates ≥ now emit `date.in.future`.
24
+ *
25
+ * This rule is pure, total, async-compatible, and preserves the
26
+ * provided path. It supports userMessage overrides applied at the
27
+ * pipeline level.
28
+ */
29
+ export const isPast = async (value, path) => {
30
+ const structuralType = detectStructuralType(value);
31
+ // ------------------------------------------------------------
32
+ // Type check — the input must be a Date instance.
33
+ // ------------------------------------------------------------
34
+ if (!(value instanceof Date)) {
35
+ return [
36
+ validationEvent('error', 'date.not.date', path, `Expected 'Date' but received '${structuralType}'.`, `Please provide a valid date.`, { value, expected: 'Date', actual: structuralType }),
37
+ ];
38
+ }
39
+ // ------------------------------------------------------------
40
+ // Validity check — reject invalid Date instances.
41
+ // ------------------------------------------------------------
42
+ if (isNaN(value.getTime())) {
43
+ return [
44
+ validationEvent('error', 'date.is.invalid', path, `Date value is invalid.`, `Please provide a valid date.`, { value, expected: 'valid Date', actual: value }),
45
+ ];
46
+ }
47
+ // ------------------------------------------------------------
48
+ // Past check — must be strictly less than now.
49
+ // ------------------------------------------------------------
50
+ if (value.getTime() >= Date.now()) {
51
+ return [
52
+ validationEvent('error', 'date.in.future', path, `${safeStringify(value)} must be in the past.`, `Please provide a past date.`, { value, expected: 'past date', actual: value }),
53
+ ];
54
+ }
55
+ // ------------------------------------------------------------
56
+ // Valid — return an empty array to indicate success.
57
+ // ------------------------------------------------------------
58
+ return [];
59
+ };
@@ -0,0 +1,66 @@
1
+ /**
2
+ * ----------------------------------------------------------------------------
3
+ * Validators | Not After
4
+ * ----------------------------------------------------------------------------
5
+ * @package @clementine-solutions/jane
6
+ * @description Ensures the input is a Date instance that is not after the
7
+ * provided `max` boundary.
8
+ * @see https://jane-io.com
9
+ * ----------------------------------------------------------------------------
10
+ */
11
+ import { validationEvent } from '../../pipeline';
12
+ import { detectStructuralType } from '../../pipeline/scan';
13
+ import { safeStringify } from '../../common';
14
+ /* ——— * ————— * ————— * ————— * ————— * ————— * ————— * ————— * ————— * ——— *\
15
+ |* Implementation *|
16
+ \* ——— * ————— * ————— * ————— * ————— * ————— * ————— * ————— * ————— * ——— */
17
+ /**
18
+ * Ensures the input is a Date instance that is not after the
19
+ * provided `max` boundary. The comparison is strict: value must be
20
+ * ≤ max.
21
+ *
22
+ * - Non-Date values emit `date.not.date`.
23
+ * - Invalid Date instances emit `date.is.invalid`.
24
+ * - Dates > max emit `date.too.late`.
25
+ *
26
+ * This rule is pure, total, async-compatible, and preserves the
27
+ * provided path. It supports userMessage overrides applied at the
28
+ * pipeline level.
29
+ */
30
+ export const notAfter = (max) => {
31
+ const rule = async (value, path) => {
32
+ const structuralType = detectStructuralType(value);
33
+ // ------------------------------------------------------------
34
+ // Type check — the input must be a Date instance.
35
+ // ------------------------------------------------------------
36
+ if (!(value instanceof Date)) {
37
+ return [
38
+ validationEvent('error', 'date.not.date', path, `Expected 'Date' but received '${structuralType}'.`, `Please provide a valid date.`, { value, expected: 'Date', actual: structuralType }),
39
+ ];
40
+ }
41
+ // ------------------------------------------------------------
42
+ // Validity check — reject invalid Date instances.
43
+ // ------------------------------------------------------------
44
+ if (isNaN(value.getTime())) {
45
+ return [
46
+ validationEvent('error', 'date.is.invalid', path, `Date value is invalid.`, `Please provide a valid date.`, { value, expected: 'valid Date', actual: value }),
47
+ ];
48
+ }
49
+ // ------------------------------------------------------------
50
+ // Boundary check — must be ≤ max.
51
+ // ------------------------------------------------------------
52
+ if (value.getTime() > max.getTime()) {
53
+ return [
54
+ validationEvent('error', 'date.too.late', path, `${safeStringify(value)} must not be after ${max.toISOString()}.`, `Please provide an earlier date.`, { value, expected: `<= ${max.toISOString()}`, actual: value.toISOString() }),
55
+ ];
56
+ }
57
+ // ------------------------------------------------------------
58
+ // Valid — return an empty array to indicate success.
59
+ // ------------------------------------------------------------
60
+ return [];
61
+ };
62
+ // Mark as factory to preserve captured `max` value
63
+ // eslint-disable-next-line @typescript-eslint/no-explicit-any
64
+ rule.__janeFactory = true;
65
+ return rule;
66
+ };
@@ -0,0 +1,66 @@
1
+ /**
2
+ * ----------------------------------------------------------------------------
3
+ * Validators | Not Before
4
+ * ----------------------------------------------------------------------------
5
+ * @package @clementine-solutions/jane
6
+ * @description Ensures the input is a Date instance that is not before the
7
+ * provided `min` boundary.
8
+ * @see https://jane-io.com
9
+ * ----------------------------------------------------------------------------
10
+ */
11
+ import { validationEvent } from '../../pipeline';
12
+ import { detectStructuralType } from '../../pipeline/scan';
13
+ import { safeStringify } from '../../common';
14
+ /* ——— * ————— * ————— * ————— * ————— * ————— * ————— * ————— * ————— * ——— *\
15
+ |* Implementation *|
16
+ \* ——— * ————— * ————— * ————— * ————— * ————— * ————— * ————— * ————— * ——— */
17
+ /**
18
+ * Ensures the input is a Date instance that is not before the
19
+ * provided `min` boundary. The comparison is inclusive: value must be
20
+ * ≥ min.
21
+ *
22
+ * - Non-Date values emit `date.not.date`.
23
+ * - Invalid Date instances emit `date.is.invalid`.
24
+ * - Dates < min emit `date.too.early`.
25
+ *
26
+ * This rule is pure, total, async-compatible, and preserves the
27
+ * provided path. It supports userMessage overrides applied at the
28
+ * pipeline level.
29
+ */
30
+ export const notBefore = (min) => {
31
+ const rule = async (value, path) => {
32
+ const structuralType = detectStructuralType(value);
33
+ // ------------------------------------------------------------
34
+ // Type check — the input must be a Date instance.
35
+ // ------------------------------------------------------------
36
+ if (!(value instanceof Date)) {
37
+ return [
38
+ validationEvent('error', 'date.not.date', path, `Expected 'Date' but received '${structuralType}'.`, `Please provide a valid date.`, { value, expected: 'Date', actual: structuralType }),
39
+ ];
40
+ }
41
+ // ------------------------------------------------------------
42
+ // Validity check — reject invalid Date instances.
43
+ // ------------------------------------------------------------
44
+ if (isNaN(value.getTime())) {
45
+ return [
46
+ validationEvent('error', 'date.is.invalid', path, `Date value is invalid.`, `Please provide a valid date.`, { value, expected: 'valid Date', actual: value }),
47
+ ];
48
+ }
49
+ // ------------------------------------------------------------
50
+ // Boundary check — must be ≥ min.
51
+ // ------------------------------------------------------------
52
+ if (value.getTime() < min.getTime()) {
53
+ return [
54
+ validationEvent('error', 'date.too.early', path, `${safeStringify(value)} must not be before ${min.toISOString()}.`, `Please provide a later date.`, { value, expected: `>= ${min.toISOString()}`, actual: value.toISOString() }),
55
+ ];
56
+ }
57
+ // ------------------------------------------------------------
58
+ // Valid — return an empty array to indicate success.
59
+ // ------------------------------------------------------------
60
+ return [];
61
+ };
62
+ // Mark as factory to preserve captured `min` value
63
+ // eslint-disable-next-line @typescript-eslint/no-explicit-any
64
+ rule.__janeFactory = true;
65
+ return rule;
66
+ };
@@ -0,0 +1,60 @@
1
+ /**
2
+ * ----------------------------------------------------------------------------
3
+ * Validators | Same Day
4
+ * ----------------------------------------------------------------------------
5
+ * @package @clementine-solutions/jane
6
+ * @description Ensures the input is a Date instance representing the same
7
+ * calendar day (year, month, day) as the provided `other`
8
+ * date.
9
+ * @see https://jane-io.com
10
+ * ----------------------------------------------------------------------------
11
+ */
12
+ import { validationEvent } from '../../pipeline';
13
+ import { safeStringify } from '../../common';
14
+ import { detectStructuralType } from '../../pipeline/scan';
15
+ /* ——— * ————— * ————— * ————— * ————— * ————— * ————— * ————— * ————— * ——— *\
16
+ |* Implementation *|
17
+ \* ——— * ————— * ————— * ————— * ————— * ————— * ————— * ————— * ————— * ——— */
18
+ /**
19
+ * Ensures the input is a Date instance representing the same
20
+ * calendar day (year, month, day) as the provided `other` date.
21
+ *
22
+ * - Non-Date values emit `date.not.date`.
23
+ * - Invalid Date instances emit `date.is.invalid`.
24
+ * - Dates not matching the same Y/M/D emit `date.not.same-day`.
25
+ *
26
+ * This rule is pure, total, async-compatible, and preserves the
27
+ * provided path. It supports userMessage overrides applied at the
28
+ * pipeline level.
29
+ */
30
+ export const sameDay = (other) => {
31
+ const rule = async (value, path) => {
32
+ const structuralType = detectStructuralType(value);
33
+ if (!(value instanceof Date)) {
34
+ return [
35
+ validationEvent('error', 'date.not.date', path, `Expected 'Date' but received '${structuralType}'.`, `Please provide a valid date.`, { value, expected: 'Date', actual: structuralType }),
36
+ ];
37
+ }
38
+ if (isNaN(value.getTime())) {
39
+ return [
40
+ validationEvent('error', 'date.is.invalid', path, `Date value is invalid.`, `Please provide a valid date.`, { value, expected: 'valid Date', actual: value }),
41
+ ];
42
+ }
43
+ const isSame = value.getUTCFullYear() === other.getUTCFullYear() &&
44
+ value.getUTCMonth() === other.getUTCMonth() &&
45
+ value.getUTCDate() === other.getUTCDate();
46
+ if (!isSame) {
47
+ return [
48
+ validationEvent('error', 'date.not.same-day', path, `${safeStringify(value)} must be the same day as ${other.toISOString()}.`, `Please provide the correct date.`, {
49
+ value,
50
+ expected: other.toISOString().slice(0, 10),
51
+ actual: value.toISOString().slice(0, 10),
52
+ }),
53
+ ];
54
+ }
55
+ return [];
56
+ };
57
+ // eslint-disable-next-line @typescript-eslint/no-explicit-any
58
+ rule.__janeFactory = true;
59
+ return rule;
60
+ };
@@ -0,0 +1,59 @@
1
+ /**
2
+ * ----------------------------------------------------------------------------
3
+ * Validators | Same Month
4
+ * ----------------------------------------------------------------------------
5
+ * @package @clementine-solutions/jane
6
+ * @description Ensures the input is a Date instance representing a date in
7
+ * the same calendar month (year + month) as the provided
8
+ * `other` date.
9
+ * @see https://jane-io.com
10
+ * ----------------------------------------------------------------------------
11
+ */
12
+ import { validationEvent } from '../../pipeline';
13
+ import { safeStringify } from '../../common';
14
+ import { detectStructuralType } from '../../pipeline/scan';
15
+ /* ——— * ————— * ————— * ————— * ————— * ————— * ————— * ————— * ————— * ——— *\
16
+ |* Implementation *|
17
+ \* ——— * ————— * ————— * ————— * ————— * ————— * ————— * ————— * ————— * ——— */
18
+ /**
19
+ * Ensures the input is a Date instance representing a date in the
20
+ * same calendar month (year + month) as the provided `other` date.
21
+ *
22
+ * - Non-Date values emit `date.not.date`.
23
+ * - Invalid Date instances emit `date.is.invalid`.
24
+ * - Dates not matching the same Y/M emit `date.not.same-month`.
25
+ *
26
+ * This rule is pure, total, async-compatible, and preserves the
27
+ * provided path. It supports userMessage overrides applied at the
28
+ * pipeline level.
29
+ */
30
+ export const sameMonth = (other) => {
31
+ const rule = async (value, path) => {
32
+ const structuralType = detectStructuralType(value);
33
+ if (!(value instanceof Date)) {
34
+ return [
35
+ validationEvent('error', 'date.not.date', path, `Expected 'Date' but received '${structuralType}'.`, `Please provide a valid date.`, { value, expected: 'Date', actual: structuralType }),
36
+ ];
37
+ }
38
+ if (isNaN(value.getTime())) {
39
+ return [
40
+ validationEvent('error', 'date.is.invalid', path, `Date value is invalid.`, `Please provide a valid date.`, { value, expected: 'valid Date', actual: value }),
41
+ ];
42
+ }
43
+ const isSame = value.getUTCFullYear() === other.getUTCFullYear() &&
44
+ value.getUTCMonth() === other.getUTCMonth();
45
+ if (!isSame) {
46
+ return [
47
+ validationEvent('error', 'date.not.same-month', path, `${safeStringify(value)} must be in the same month as ${other.toISOString()}.`, `Please provide a date in the correct month.`, {
48
+ value,
49
+ expected: other.toISOString().slice(0, 7), // YYYY-MM
50
+ actual: value.toISOString().slice(0, 7),
51
+ }),
52
+ ];
53
+ }
54
+ return [];
55
+ };
56
+ // eslint-disable-next-line @typescript-eslint/no-explicit-any
57
+ rule.__janeFactory = true;
58
+ return rule;
59
+ };