@clementine-solutions/jane-io 1.0.1 → 1.0.3

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/package.json +28 -3
  204. package/dist/test.d.ts +0 -1
@@ -0,0 +1,30 @@
1
+ /**
2
+ * ----------------------------------------------------------------------------
3
+ * Parsers | Parse ISO Date String
4
+ * ----------------------------------------------------------------------------
5
+ * @package @clementine-solutions/jane
6
+ * @description Parses strict ISO‑8601 date strings into `Date` objects,
7
+ * rejecting malformed or non‑standard formats.
8
+ * @see https://jane-io.com
9
+ * ----------------------------------------------------------------------------
10
+ */
11
+ import { parseEvent } from '../pipeline';
12
+ /* ——— * ————— * ————— * ————— * ————— * ————— * ————— * ————— * ————— * ——— *\
13
+ |* Implementation *|
14
+ \* ——— * ————— * ————— * ————— * ————— * ————— * ————— * ————— * ————— * ——— */
15
+ /**
16
+ * Parses strict ISO‑8601 date strings into `Date` objects, rejecting malformed
17
+ * or non‑standard formats and ensuring the resulting timestamp is valid.
18
+ */
19
+ export const parseIsoDateString = (value, path) => {
20
+ if (typeof value !== 'string')
21
+ return [];
22
+ const iso = /^\d{4}-\d{2}-\d{2}(?:[T ]\d{2}:\d{2}(?::\d{2}(?:\.\d{1,3})?)?(?:Z|[+-]\d{2}:\d{2})?)?$/;
23
+ if (!iso.test(value))
24
+ return [];
25
+ const parsed = new Date(value);
26
+ if (Number.isNaN(parsed.getTime()))
27
+ return [];
28
+ const event = parseEvent('info', 'string.now.date', `String parsed into Date ${parsed.toISOString()}.`, path, 'Converted text into a date.', { before: value, after: parsed });
29
+ return [{ path, nextValue: parsed, events: [event] }];
30
+ };
@@ -0,0 +1,37 @@
1
+ /**
2
+ * ----------------------------------------------------------------------------
3
+ * Parsers | Parse Duration String
4
+ * ----------------------------------------------------------------------------
5
+ * @package @clementine-solutions/jane
6
+ * @description Parses duration strings of the form `<number><unit>` (s, m,
7
+ * h, d) into structured duration objects.
8
+ * @see https://jane-io.com
9
+ * ----------------------------------------------------------------------------
10
+ */
11
+ import { parseEvent } from '../pipeline';
12
+ /* ——— * ————— * ————— * ————— * ————— * ————— * ————— * ————— * ————— * ——— *\
13
+ |* Implementation *|
14
+ \* ——— * ————— * ————— * ————— * ————— * ————— * ————— * ————— * ————— * ——— */
15
+ /**
16
+ * Parses duration strings of the form `<number><unit>` (s, m, h, d) into
17
+ * structured duration objects, rejecting ambiguous or partial formats.
18
+ */
19
+ export const parseDurationString = (value, path) => {
20
+ if (typeof value !== 'string')
21
+ return [];
22
+ // Strict: number + unit (s, m, h, d)
23
+ const match = /^(-?\d+)([smhd])$/.exec(value);
24
+ if (!match)
25
+ return [];
26
+ const amount = Number(match[1]);
27
+ const unit = match[2];
28
+ const parsed = unit === 's'
29
+ ? { seconds: amount }
30
+ : unit === 'm'
31
+ ? { minutes: amount }
32
+ : unit === 'h'
33
+ ? { hours: amount }
34
+ : { days: amount };
35
+ const event = parseEvent('info', 'string.now.duration', `String parsed into duration object (${JSON.stringify(parsed)}).`, path, 'Converted text into a time duration.', { before: value, after: parsed });
36
+ return [{ path, nextValue: parsed, events: [event] }];
37
+ };
@@ -0,0 +1,29 @@
1
+ /**
2
+ * ----------------------------------------------------------------------------
3
+ * Parsers | Parse Hex String
4
+ * ----------------------------------------------------------------------------
5
+ * @package @clementine-solutions/jane
6
+ * @description Parses hexadecimal string literals (0x…) into `bigint`
7
+ * values, enforcing canonical formatting.
8
+ * @see https://jane-io.com
9
+ * ----------------------------------------------------------------------------
10
+ */
11
+ import { parseEvent } from '../pipeline';
12
+ /* ——— * ————— * ————— * ————— * ————— * ————— * ————— * ————— * ————— * ——— *\
13
+ |* Implementation *|
14
+ \* ——— * ————— * ————— * ————— * ————— * ————— * ————— * ————— * ————— * ——— */
15
+ /**
16
+ * Parses hexadecimal string literals (`0x…`) into numeric values, rejecting
17
+ * malformed or non‑hexadecimal input.
18
+ */
19
+ export const parseHexString = (value, path) => {
20
+ if (typeof value !== 'string')
21
+ return [];
22
+ if (!/^0x[0-9a-fA-F]+$/.test(value))
23
+ return [];
24
+ const parsed = Number(value);
25
+ if (Number.isNaN(parsed))
26
+ return [];
27
+ const event = parseEvent('info', 'string.now.hex', `Hex string parsed into number ${parsed}.`, path, 'Converted hexadecimal text into a number.', { before: value, after: parsed });
28
+ return [{ path, nextValue: parsed, events: [event] }];
29
+ };
@@ -0,0 +1,30 @@
1
+ /**
2
+ * ----------------------------------------------------------------------------
3
+ * Parsers | Parse Integer String
4
+ * ----------------------------------------------------------------------------
5
+ * @package @clementine-solutions/jane
6
+ * @description Parses strict integer strings into numeric values, rejecting
7
+ * floats, scientific notation, and ambiguous formats.
8
+ * @see https://jane-io.com
9
+ * ----------------------------------------------------------------------------
10
+ */
11
+ import { parseEvent } from '../pipeline';
12
+ /* ——— * ————— * ————— * ————— * ————— * ————— * ————— * ————— * ————— * ——— *\
13
+ |* Implementation *|
14
+ \* ——— * ————— * ————— * ————— * ————— * ————— * ————— * ————— * ————— * ——— */
15
+ /**
16
+ * Parses strict integer strings into numeric values, rejecting floats,
17
+ * scientific notation, and any non‑digit characters.
18
+ */
19
+ export const parseIntegerString = (value, path) => {
20
+ if (typeof value !== 'string')
21
+ return [];
22
+ // Strict integer: optional leading minus, digits only
23
+ if (!/^-?\d+$/.test(value))
24
+ return [];
25
+ const parsed = Number(value);
26
+ if (!Number.isInteger(parsed))
27
+ return [];
28
+ const event = parseEvent('info', 'string.now.integer', `String parsed into integer ${parsed}.`, path, 'Converted text into a whole number.', { before: value, after: parsed });
29
+ return [{ path, nextValue: parsed, events: [event] }];
30
+ };
@@ -0,0 +1,35 @@
1
+ /**
2
+ * ----------------------------------------------------------------------------
3
+ * Parsers | Parse JSON String
4
+ * ----------------------------------------------------------------------------
5
+ * @package @clementine-solutions/jane
6
+ * @description Parses JSON‑compatible strings into structured data,
7
+ * accepting only valid objects, arrays, or quoted strings.
8
+ * @see https://jane-io.com
9
+ * ----------------------------------------------------------------------------
10
+ */
11
+ import { parseEvent } from '../pipeline';
12
+ /* ——— * ————— * ————— * ————— * ————— * ————— * ————— * ————— * ————— * ——— *\
13
+ |* Implementation *|
14
+ \* ——— * ————— * ————— * ————— * ————— * ————— * ————— * ————— * ————— * ——— */
15
+ /**
16
+ * Parses JSON‑compatible strings into structured data, accepting only objects,
17
+ * arrays, or quoted strings and rejecting anything that cannot be parsed
18
+ * safely.
19
+ */
20
+ export const parseJsonString = (value, path) => {
21
+ if (typeof value !== 'string')
22
+ return [];
23
+ const first = value.trim()[0];
24
+ if (first !== '{' && first !== '[' && first !== '"')
25
+ return [];
26
+ let parsed;
27
+ try {
28
+ parsed = JSON.parse(value);
29
+ }
30
+ catch {
31
+ return [];
32
+ }
33
+ const event = parseEvent('info', 'string.now.json', 'String parsed as JSON.', path, 'Converted text into structured data.', { before: value, after: parsed });
34
+ return [{ path, nextValue: parsed, events: [event] }];
35
+ };
@@ -0,0 +1,29 @@
1
+ /**
2
+ * ----------------------------------------------------------------------------
3
+ * Parsers | Parse Numeric String
4
+ * ----------------------------------------------------------------------------
5
+ * @package @clementine-solutions/jane
6
+ * @description Parses strict numeric strings into numbers, supporting
7
+ * optional decimals while rejecting ambiguous formats.
8
+ * @see https://jane-io.com
9
+ * ----------------------------------------------------------------------------
10
+ */
11
+ import { parseEvent } from '../pipeline';
12
+ /* ——— * ————— * ————— * ————— * ————— * ————— * ————— * ————— * ————— * ——— *\
13
+ |* Implementation *|
14
+ \* ——— * ————— * ————— * ————— * ————— * ————— * ————— * ————— * ————— * ——— */
15
+ /**
16
+ * Parses strict numeric strings into JavaScript numbers, supporting optional
17
+ * decimals while rejecting scientific notation and non‑numeric characters.
18
+ */
19
+ export const parseNumericString = (value, path) => {
20
+ if (typeof value !== 'string')
21
+ return [];
22
+ if (!/^-?\d+(\.\d+)?$/.test(value))
23
+ return [];
24
+ const parsed = Number(value);
25
+ if (Number.isNaN(parsed))
26
+ return [];
27
+ const event = parseEvent('info', 'string.now.number', `String parsed into number ${parsed}.`, path, 'Converted text into a number.', { before: value, after: parsed });
28
+ return [{ path, nextValue: parsed, events: [event] }];
29
+ };
@@ -0,0 +1,36 @@
1
+ /**
2
+ * ----------------------------------------------------------------------------
3
+ * Parsers | Parse Object String
4
+ * ----------------------------------------------------------------------------
5
+ * @package @clementine-solutions/jane
6
+ * @description Parses JSON object strings into plain objects, rejecting
7
+ * arrays, null, and non‑object structures.
8
+ * @see https://jane-io.com
9
+ * ----------------------------------------------------------------------------
10
+ */
11
+ import { parseEvent } from '../pipeline';
12
+ /* ——— * ————— * ————— * ————— * ————— * ————— * ————— * ————— * ————— * ——— *\
13
+ |* Implementation *|
14
+ \* ——— * ————— * ————— * ————— * ————— * ————— * ————— * ————— * ————— * ——— */
15
+ /**
16
+ * Parses JSON object strings into plain objects, rejecting arrays, null, and
17
+ * any non‑object structures.
18
+ */
19
+ export const parseObjectString = (value, path) => {
20
+ if (typeof value !== 'string')
21
+ return [];
22
+ const trimmed = value.trim();
23
+ if (!trimmed.startsWith('{') || !trimmed.endsWith('}'))
24
+ return [];
25
+ let parsed;
26
+ try {
27
+ parsed = JSON.parse(trimmed);
28
+ }
29
+ catch {
30
+ return [];
31
+ }
32
+ if (parsed === null || typeof parsed !== 'object' || Array.isArray(parsed))
33
+ return [];
34
+ const event = parseEvent('info', 'string.now.object', 'String parsed into JSON object.', path, 'Converted text into an object.', { before: value, after: parsed });
35
+ return [{ path, nextValue: parsed, events: [event] }];
36
+ };
@@ -0,0 +1,33 @@
1
+ /**
2
+ * ----------------------------------------------------------------------------
3
+ * Parsers | Parse Octal String
4
+ * ----------------------------------------------------------------------------
5
+ * @package @clementine-solutions/jane
6
+ * @description Parses octal string literals (`0o…`) into `bigint` values,
7
+ * enforcing strict base‑8 formatting.
8
+ * @see https://jane-io.com
9
+ * ----------------------------------------------------------------------------
10
+ */
11
+ import { parseEvent } from '../pipeline';
12
+ /* ——— * ————— * ————— * ————— * ————— * ————— * ————— * ————— * ————— * ——— *\
13
+ |* Implementation *|
14
+ \* ——— * ————— * ————— * ————— * ————— * ————— * ————— * ————— * ————— * ——— */
15
+ /**
16
+ * Parses octal string literals (`0o…`) into `bigint` values, validating format
17
+ * and rejecting malformed or non‑octal input.
18
+ */
19
+ export const parseOctalString = (value, path) => {
20
+ if (typeof value !== 'string')
21
+ return [];
22
+ if (!/^0o[0-7]+$/.test(value))
23
+ return [];
24
+ let parsed;
25
+ try {
26
+ parsed = BigInt(value);
27
+ }
28
+ catch {
29
+ return [];
30
+ }
31
+ const event = parseEvent('info', 'string.now.octal', `Octal string parsed into bigint ${parsed}.`, path, 'Converted octal text into a number.', { before: value, after: parsed });
32
+ return [{ path, nextValue: parsed, events: [event] }];
33
+ };
@@ -0,0 +1,30 @@
1
+ /**
2
+ * ----------------------------------------------------------------------------
3
+ * Parsers | Parse Scientific Notation String
4
+ * ----------------------------------------------------------------------------
5
+ * @package @clementine-solutions/jane
6
+ * @description Parses strict scientific‑notation strings into numbers,
7
+ * validating both mantissa and exponent format.
8
+ * @see https://jane-io.com
9
+ * ----------------------------------------------------------------------------
10
+ */
11
+ import { parseEvent } from '../pipeline';
12
+ /* ——— * ————— * ————— * ————— * ————— * ————— * ————— * ————— * ————— * ——— *\
13
+ |* Implementation *|
14
+ \* ——— * ————— * ————— * ————— * ————— * ————— * ————— * ————— * ————— * ——— */
15
+ /**
16
+ * Parses strict scientific‑notation strings into numbers, enforcing canonical
17
+ * formatting for both mantissa and exponent.
18
+ */
19
+ export const parseScientificNotationString = (value, path) => {
20
+ if (typeof value !== 'string')
21
+ return [];
22
+ // Strict scientific notation: digits, optional decimal, 'e' or 'E', signed exponent
23
+ if (!/^-?\d+(\.\d+)?[eE][+-]?\d+$/.test(value))
24
+ return [];
25
+ const parsed = Number(value);
26
+ if (Number.isNaN(parsed))
27
+ return [];
28
+ const event = parseEvent('info', 'string.now.scientific', `String parsed into number ${parsed} using scientific notation.`, path, 'Converted scientific notation into a number.', { before: value, after: parsed });
29
+ return [{ path, nextValue: parsed, events: [event] }];
30
+ };
@@ -0,0 +1,36 @@
1
+ /**
2
+ * ----------------------------------------------------------------------------
3
+ * Parsers | Parse URL String
4
+ * ----------------------------------------------------------------------------
5
+ * @package @clementine-solutions/jane
6
+ * @description Parses URL strings into normalized URL values, ensuring the
7
+ * text represents a valid, fully qualified URL.
8
+ * @see https://jane-io.com
9
+ * ----------------------------------------------------------------------------
10
+ */
11
+ import { parseEvent } from '../pipeline';
12
+ /* ——— * ————— * ————— * ————— * ————— * ————— * ————— * ————— * ————— * ——— *\
13
+ |* Implementation *|
14
+ \* ——— * ————— * ————— * ————— * ————— * ————— * ————— * ————— * ————— * ——— */
15
+ /**
16
+ * Parses URL strings into normalized URL values, ensuring the text represents
17
+ * a syntactically valid and fully qualified URL.
18
+ */
19
+ export const parseUrlString = (value, path) => {
20
+ if (typeof value !== 'string')
21
+ return [];
22
+ try {
23
+ const normalized = new URL(value).toString();
24
+ const event = parseEvent('info', 'string.now.url', `String parsed into URL (${normalized}).`, path, 'Converted text into a URL.', { before: value, after: normalized });
25
+ return [
26
+ {
27
+ path,
28
+ nextValue: normalized,
29
+ events: [event],
30
+ },
31
+ ];
32
+ }
33
+ catch {
34
+ return [];
35
+ }
36
+ };
@@ -0,0 +1,256 @@
1
+ /**
2
+ * ----------------------------------------------------------------------------
3
+ * Pipeline | Boundary
4
+ * ----------------------------------------------------------------------------
5
+ * @package @clementine-solutions/jane
6
+ * @description Aggregates field results and applies boundary‑level policy
7
+ * to produce a final decision.
8
+ * @see https://jane-io.com
9
+ * ----------------------------------------------------------------------------
10
+ */
11
+ import { applyEscalate, applyOverride, defaultPolicy, generateRunId, matchesWildcard, resolveLevel, } from '../common';
12
+ import { boundaryPolicyDefault } from '../common/policy';
13
+ /* ——— * ————— * ————— * ————— * ————— * ————— * ————— * ————— * ————— * ——— *\
14
+ |* Boundary Rule Registry *|
15
+ \* ——— * ————— * ————— * ————— * ————— * ————— * ————— * ————— * ————— * ——— */
16
+ /**
17
+ * Holds all registered boundary‑level rules.
18
+ *
19
+ * Provides a central lookup for rule names used in boundary policies,
20
+ * allowing contributors to extend or customize boundary evaluation
21
+ * without modifying the core engine.
22
+ */
23
+ export const boundaryRuleRegistry = {};
24
+ /* ——— * ————— * ————— * ————— * ————— * ————— * ————— * ————— * ————— * ——— *\
25
+ |* Boundary Runner *|
26
+ \* ——— * ————— * ————— * ————— * ————— * ————— * ————— * ————— * ————— * ——— */
27
+ /**
28
+ * Executes boundary evaluation across all field results.
29
+ *
30
+ * Aggregates issues and events from each pipeline result, applies
31
+ * boundary‑level rules, shapes values and metadata according to policy,
32
+ * and produces the final boundary result used for acceptance decisions.
33
+ */
34
+ export async function boundaryRunner(input) {
35
+ const startedAt = new Date();
36
+ const runId = generateRunId();
37
+ const { policy = defaultPolicy, fields } = input;
38
+ const boundary = policy.boundary ?? boundaryPolicyDefault;
39
+ const allIssues = [];
40
+ const allEvents = [];
41
+ const metadata = {};
42
+ const value = {};
43
+ for (const key of Object.keys(fields)) {
44
+ const result = fields[key];
45
+ metadata[key] = {
46
+ raw: result.raw,
47
+ safe: result.safe,
48
+ final: result.final,
49
+ path: result.path,
50
+ rawType: result.rawType,
51
+ finalType: result.finalType,
52
+ startedAt: result.metadata.startedAt,
53
+ finishedAt: result.metadata.finishedAt,
54
+ durationMs: result.metadata.durationMs,
55
+ inputName: result.metadata.inputName,
56
+ };
57
+ if (result.decision?.code === 'accept') {
58
+ value[key] = result.final;
59
+ }
60
+ const events = result.events ?? [];
61
+ const issues = events.filter((e) => (e.kind === 'error' || e.kind === 'fatal') &&
62
+ typeof e.code === 'string' &&
63
+ e.code.trim().length > 0);
64
+ allIssues.push(...issues);
65
+ allEvents.push(...events);
66
+ }
67
+ if (boundary?.rules && boundary.rules.length > 0) {
68
+ for (const rule of boundary.rules) {
69
+ const ctx = {
70
+ policy,
71
+ fields,
72
+ events: allEvents,
73
+ issues: allIssues,
74
+ values: value,
75
+ };
76
+ try {
77
+ const result = await rule(ctx);
78
+ if (!result)
79
+ continue;
80
+ if (result.events)
81
+ allEvents.push(...result.events);
82
+ if (result.issues)
83
+ allIssues.push(...result.issues);
84
+ }
85
+ catch (error) {
86
+ allEvents.push({
87
+ phase: 'decide',
88
+ kind: 'fatal',
89
+ code: 'boundary.fatal.error',
90
+ message: error?.message ?? 'Unknown error',
91
+ });
92
+ }
93
+ }
94
+ }
95
+ const finishedAt = new Date();
96
+ const decision = await decideBoundary({
97
+ policy,
98
+ fields,
99
+ issues: allIssues,
100
+ events: allEvents,
101
+ values: value,
102
+ });
103
+ let shapedValues = { ...value };
104
+ if (boundary?.values?.includeRejected) {
105
+ for (const key of Object.keys(fields)) {
106
+ const r = fields[key];
107
+ if (r.decision?.code !== 'accept') {
108
+ shapedValues[key] = r.final;
109
+ }
110
+ }
111
+ }
112
+ if (boundary?.values?.includeUndefined) {
113
+ for (const key of Object.keys(fields)) {
114
+ if (!(key in shapedValues)) {
115
+ shapedValues[key] = undefined;
116
+ }
117
+ }
118
+ }
119
+ if (boundary?.values?.transform) {
120
+ shapedValues = boundary.values.transform(shapedValues);
121
+ }
122
+ let shapedMetadata = {
123
+ runId,
124
+ startedAt: startedAt.toISOString(),
125
+ finishedAt: finishedAt.toISOString(),
126
+ durationMs: finishedAt.getTime() - startedAt.getTime(),
127
+ fields: metadata,
128
+ boundaryName: policy.boundaryName,
129
+ pipelineName: policy.pipelineName,
130
+ analysis: {
131
+ diff: policy.analysis?.diff,
132
+ explain: policy.analysis?.explain,
133
+ replay: policy.analysis?.replay,
134
+ telemetry: policy.analysis?.telemetry,
135
+ },
136
+ };
137
+ if (boundary?.metadata) {
138
+ const m = boundary.metadata;
139
+ if (m.includeFields === false) {
140
+ shapedMetadata = { ...shapedMetadata, fields: {} };
141
+ }
142
+ if (m.includeTimestamps === false) {
143
+ shapedMetadata = {
144
+ ...shapedMetadata,
145
+ startedAt: undefined,
146
+ finishedAt: undefined,
147
+ durationMs: undefined,
148
+ };
149
+ }
150
+ if (m.includeRunId === false) {
151
+ shapedMetadata = { ...shapedMetadata, runId: undefined };
152
+ }
153
+ }
154
+ return {
155
+ ok: decision.ok,
156
+ issues: decision.issues ?? (allIssues.length > 0 ? allIssues : undefined),
157
+ events: decision.events ?? (allEvents.length > 0 ? allEvents : undefined),
158
+ values: decision.values ?? shapedValues,
159
+ fields,
160
+ metadata: decision.metadata ?? shapedMetadata,
161
+ };
162
+ }
163
+ /* ——— * ————— * ————— * ————— * ————— * ————— * ————— * ————— * ————— * ——— *\
164
+ |* Decide Boundary *|
165
+ \* ——— * ————— * ————— * ————— * ————— * ————— * ————— * ————— * ————— * ——— */
166
+ /**
167
+ * Applies boundary decision logic to the aggregated context.
168
+ *
169
+ * Shapes events and issues through ignore, override, escalate, and
170
+ * visibility rules, evaluates acceptance modes, and optionally invokes
171
+ * a reducer to refine or override the final decision.
172
+ */
173
+ export async function decideBoundary(context) {
174
+ const { policy, fields } = context;
175
+ const boundary = policy.boundary;
176
+ let shapedEvents = [...context.events];
177
+ if (boundary?.ignore) {
178
+ shapedEvents = shapedEvents.filter((ev) => !matchesWildcard(ev.code, boundary.ignore));
179
+ }
180
+ if (boundary?.override) {
181
+ shapedEvents = shapedEvents.map((ev) => {
182
+ const level = resolveLevel(ev.code, boundary.override);
183
+ return level === undefined ? ev : { ...ev, kind: applyOverride(ev.kind, level) };
184
+ });
185
+ }
186
+ if (boundary?.escalate) {
187
+ shapedEvents = shapedEvents.map((ev) => {
188
+ const level = resolveLevel(ev.code, boundary.escalate);
189
+ return level === undefined ? ev : { ...ev, kind: applyEscalate(ev.kind, level) };
190
+ });
191
+ }
192
+ if (boundary?.hideEvents) {
193
+ shapedEvents = shapedEvents.filter((ev) => !matchesWildcard(ev.code, boundary.hideEvents));
194
+ }
195
+ let shapedIssues = shapedEvents.filter((ev) => (ev.kind === 'error' || ev.kind === 'fatal') &&
196
+ typeof ev.code === 'string' &&
197
+ ev.code.trim().length > 0);
198
+ if (boundary?.hideIssues) {
199
+ shapedIssues = shapedIssues.filter((ev) => !matchesWildcard(ev.code, boundary.hideIssues));
200
+ }
201
+ const rejectPatterns = boundary?.reject ?? policy.decide?.reject ?? [];
202
+ const reviewPatterns = boundary?.review ?? policy.decide?.review ?? [];
203
+ let hasReject = false;
204
+ let hasReview = false;
205
+ for (const ev of shapedEvents) {
206
+ if (ev.kind === 'fatal') {
207
+ hasReject = true;
208
+ continue;
209
+ }
210
+ if (matchesWildcard(ev.code, rejectPatterns)) {
211
+ hasReject = true;
212
+ continue;
213
+ }
214
+ if (matchesWildcard(ev.code, reviewPatterns)) {
215
+ hasReview = true;
216
+ }
217
+ }
218
+ const acceptMode = boundary?.accept ?? 'all';
219
+ let ok;
220
+ switch (acceptMode) {
221
+ case 'strict':
222
+ ok = !hasReject && !hasReview && shapedIssues.length === 0;
223
+ break;
224
+ case 'any':
225
+ ok = !hasReject && Object.values(fields).some((r) => r.decision?.code === 'accept');
226
+ break;
227
+ case 'partial':
228
+ ok = !hasReject;
229
+ break;
230
+ case 'all':
231
+ default:
232
+ ok =
233
+ !hasReject &&
234
+ shapedIssues.length === 0 &&
235
+ Object.values(fields).every((r) => r.decision?.code === 'accept');
236
+ break;
237
+ }
238
+ const shapedContext = {
239
+ ...context,
240
+ events: shapedEvents,
241
+ issues: shapedIssues,
242
+ };
243
+ if (boundary?.reducer) {
244
+ const result = await boundary.reducer(shapedContext);
245
+ if (result !== undefined) {
246
+ if ('ok' in result && typeof result.ok === 'boolean') {
247
+ return result;
248
+ }
249
+ return {
250
+ ok,
251
+ ...result,
252
+ };
253
+ }
254
+ }
255
+ return { ok };
256
+ }