@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,39 @@
1
+ /**
2
+ * ----------------------------------------------------------------------------
3
+ * Boundary Rules | Require All
4
+ * ----------------------------------------------------------------------------
5
+ * @package @clementine-solutions/jane
6
+ * @description Requires that all specified fields be present and accepted.
7
+ * Missing or rejected fields each produce a boundary error.
8
+ * @see https://jane-io.com
9
+ * ----------------------------------------------------------------------------
10
+ */
11
+ import { rootPath } from '../field-path';
12
+ /* ——— * ————— * ————— * ————— * ————— * ————— * ————— * ————— * ————— * ——— *\
13
+ |* Implementation *|
14
+ \* ——— * ————— * ————— * ————— * ————— * ————— * ————— * ————— * ————— * ——— */
15
+ /**
16
+ * Requires that all specified fields be present and accepted. Any missing or
17
+ * rejected field results in a boundary‑level error for that key.
18
+ */
19
+ export function requireAll(...keys) {
20
+ return ({ fields }) => {
21
+ const events = [];
22
+ const issues = [];
23
+ for (const key of keys) {
24
+ const r = fields[key];
25
+ if (!r || r.decision?.code !== 'accept') {
26
+ const ev = {
27
+ phase: 'decide',
28
+ kind: 'error',
29
+ code: 'boundary.does.require-all',
30
+ message: `Field "${key}" is required`,
31
+ path: rootPath(key),
32
+ };
33
+ events.push(ev);
34
+ issues.push(ev);
35
+ }
36
+ }
37
+ return { events, issues };
38
+ };
39
+ }
@@ -0,0 +1,41 @@
1
+ /**
2
+ * ----------------------------------------------------------------------------
3
+ * Boundary Rules | Require One
4
+ * ----------------------------------------------------------------------------
5
+ * @package @clementine-solutions/jane
6
+ * @description Requires that at least one of the specified fields be
7
+ * accepted. If none are present, a single boundary error is
8
+ * emitted.
9
+ * @see https://jane-io.com
10
+ * ----------------------------------------------------------------------------
11
+ */
12
+ import { rootPath } from '../field-path';
13
+ /* ——— * ————— * ————— * ————— * ————— * ————— * ————— * ————— * ————— * ——— *\
14
+ |* Implementation *|
15
+ \* ——— * ————— * ————— * ————— * ————— * ————— * ————— * ————— * ————— * ——— */
16
+ /**
17
+ * Requires that at least one of the specified fields be present and accepted.
18
+ * If none are provided, a single boundary‑level error is emitted.
19
+ */
20
+ export function requireOne(...keys) {
21
+ return ({ fields }) => {
22
+ const events = [];
23
+ const issues = [];
24
+ const ok = keys.some((key) => {
25
+ const r = fields[key];
26
+ return r && r.decision?.code === 'accept';
27
+ });
28
+ if (!ok) {
29
+ const ev = {
30
+ phase: 'decide',
31
+ kind: 'error',
32
+ code: 'boundary.does.require-one',
33
+ message: `At least one of [${keys.join(', ')}] is required`,
34
+ path: rootPath(),
35
+ };
36
+ events.push(ev);
37
+ issues.push(ev);
38
+ }
39
+ return { events, issues };
40
+ };
41
+ }
@@ -0,0 +1,82 @@
1
+ /**
2
+ * ----------------------------------------------------------------------------
3
+ * Common | Events
4
+ * ----------------------------------------------------------------------------
5
+ * @package @clementine-solutions/jane
6
+ * @description Shared event types, factories, and helpers used across all
7
+ * pipeline stages. These modules define how Jane records and
8
+ * communicates observable facts during execution.
9
+ * @see https://jane-io.com
10
+ * ----------------------------------------------------------------------------
11
+ */
12
+ /* ——— * ————— * ————— * ————— * ————— * ————— * ————— * ————— * ————— * ——— *\
13
+ |* Create Event *|
14
+ \* ——— * ————— * ————— * ————— * ————— * ————— * ————— * ————— * ————— * ——— */
15
+ /**
16
+ * Creates a frozen JaneEvent with consistent metadata.
17
+ *
18
+ * This factory centralizes event construction so all stages emit the same
19
+ * shape: stable identifiers, optional context, and a metadata block that
20
+ * tooling can rely on. Callers provide only the facts; the factory attaches
21
+ * timestamps and prepares the object for safe sharing across the pipeline.
22
+ *
23
+ * The returned event is immutable to prevent accidental mutation during
24
+ * scan, parse, normalize, validate, boundary, or policy execution.
25
+ */
26
+ export function createEvent(phase, kind, code, path, message, userMessage, meta) {
27
+ return Object.freeze({
28
+ phase,
29
+ kind,
30
+ code,
31
+ path,
32
+ message,
33
+ userMessage,
34
+ metadata: {
35
+ ...meta,
36
+ runId: undefined,
37
+ createdAt: Date.now(),
38
+ },
39
+ });
40
+ }
41
+ /* ——— * ————— * ————— * ————— * ————— * ————— * ————— * ————— * ————— * ——— *\
42
+ |* Is Jane Event *|
43
+ \* ——— * ————— * ————— * ————— * ————— * ————— * ————— * ————— * ————— * ——— */
44
+ /**
45
+ * Structural type guard for JaneEvent.
46
+ *
47
+ * This check verifies that a value has the minimal shape required for an
48
+ * event: a valid phase, kind, code, and optional context fields. It does not
49
+ * validate semantics or metadata—only that the object is structurally safe to
50
+ * treat as a JaneEvent inside the pipeline.
51
+ *
52
+ * The guard is intentionally strict and literal. It accepts only the phases
53
+ * and kinds defined here, ensuring that stray or misspelled values cannot
54
+ * enter policy, explain, or replay logic.
55
+ */
56
+ export function isJaneEvent(value) {
57
+ if (typeof value !== 'object' || value === null)
58
+ return false;
59
+ const item = value;
60
+ const path = item['path'];
61
+ const kind = item['kind'];
62
+ const phase = item['phase'];
63
+ const kindIsValid = kind === 'info' || kind === 'warn' || kind === 'error' || kind === 'fatal';
64
+ const phaseIsValid = phase === 'scan' ||
65
+ phase === 'normalize' ||
66
+ phase === 'validate' ||
67
+ phase === 'parse' ||
68
+ phase === 'decide';
69
+ const pathIsValid = path === undefined ||
70
+ (typeof path === 'object' &&
71
+ path !== null &&
72
+ 'segments' in path &&
73
+ Array.isArray(path['segments']));
74
+ const messageIsValid = item['message'] === undefined || typeof item['message'] === 'string';
75
+ return (typeof phase === 'string' &&
76
+ phaseIsValid &&
77
+ typeof kind === 'string' &&
78
+ kindIsValid &&
79
+ typeof item['code'] === 'string' &&
80
+ messageIsValid &&
81
+ pathIsValid);
82
+ }
@@ -0,0 +1,429 @@
1
+ /**
2
+ * ----------------------------------------------------------------------------
3
+ * Common | Fluent
4
+ * ----------------------------------------------------------------------------
5
+ * @package @clementine-solutions/jane
6
+ * @description Internal helpers that build Jane’s fluent API surface.
7
+ * These modules attach parser and validator methods and wrap
8
+ * PipelineBuilder instances into the public pipeline.
9
+ * @see https://jane-io.com
10
+ * ----------------------------------------------------------------------------
11
+ */
12
+ import { rootPath } from '../field-path';
13
+ import { boundaryRunner, createPipeline } from '../pipeline';
14
+ import { telemetry } from '../analysis';
15
+ import { defaultPolicy } from '../common';
16
+ import { janeRegistry } from '../fluent-registry';
17
+ import { boundaryPolicyDefault, boundaryPolicyLax, boundaryPolicyStrict, mergeBoundaryPolicies, normalizePolicy, resolvePolicy, } from './policy';
18
+ /* ——— * ————— * ————— * ————— * ————— * ————— * ————— * ————— * ————— * ——— *\
19
+ |* Attach Public Fluent Validators *|
20
+ \* ——— * ————— * ————— * ————— * ————— * ————— * ————— * ————— * ————— * ——— */
21
+ /**
22
+ * Attaches public fluent validator methods to a pipeline instance.
23
+ *
24
+ * Each validator name in the registry becomes a fluent method on the public
25
+ * pipeline. Calls delegate to the underlying builder, then wrap the updated
26
+ * builder to produce the next public pipeline in the chain.
27
+ *
28
+ * Methods are defined as non‑enumerable and immutable so the fluent surface
29
+ * stays stable, predictable, and free of accidental overrides.
30
+ */
31
+ function attachPublicFluentValidators(pub, builder, wrap) {
32
+ for (const name of Object.keys(janeRegistry.validators)) {
33
+ Object.defineProperty(pub, name, {
34
+ enumerable: false,
35
+ configurable: true,
36
+ writable: false,
37
+ value: (...args) => {
38
+ const next = builder.validate(name, ...args);
39
+ return wrap(next);
40
+ },
41
+ });
42
+ }
43
+ }
44
+ /* ——— * ————— * ————— * ————— * ————— * ————— * ————— * ————— * ————— * ——— *\
45
+ |* Builder *|
46
+ \* ——— * ————— * ————— * ————— * ————— * ————— * ————— * ————— * ————— * ——— */
47
+ /**
48
+ * Internal hook used to associate a PipelineBuilder with a PublicPipeline.
49
+ *
50
+ * The unique symbol prevents accidental collisions with user code and keeps
51
+ * the builder hidden from the public API. Only internal helpers read or
52
+ * write this property, and it is never surfaced to consumers.
53
+ *
54
+ * PublicPipelineInternal exists solely to give internal code a typed way to
55
+ * access the builder without widening the public interface.
56
+ */
57
+ const BUILDER = Symbol('JANE_PIPELINE_BUILDER');
58
+ /* ——— * ————— * ————— * ————— * ————— * ————— * ————— * ————— * ————— * ——— *\
59
+ |* Attach Fluent Validators *|
60
+ \* ——— * ————— * ————— * ————— * ————— * ————— * ————— * ————— * ————— * ——— */
61
+ /**
62
+ * Attaches fluent validator methods directly to a PipelineBuilder.
63
+ *
64
+ * Each validator in the registry becomes a fluent method on the builder.
65
+ * Calls record the validator request on the current builder and return the
66
+ * same builder instance, allowing internal code to compose validation steps
67
+ * without wrapping or producing a new public pipeline.
68
+ *
69
+ * Methods are non‑enumerable and immutable so the builder’s shape stays
70
+ * stable and free of accidental overrides.
71
+ */
72
+ export function attachFluentValidators(builder) {
73
+ for (const name of Object.keys(janeRegistry.validators)) {
74
+ Object.defineProperty(builder, name, {
75
+ enumerable: false,
76
+ configurable: true,
77
+ writable: false,
78
+ value: (...args) => builder.validate(name, ...args),
79
+ });
80
+ }
81
+ }
82
+ /* ——— * ————— * ————— * ————— * ————— * ————— * ————— * ————— * ————— * ——— *\
83
+ |* Attach Public Fluent Parsers *|
84
+ \* ——— * ————— * ————— * ————— * ————— * ————— * ————— * ————— * ————— * ——— */
85
+ /**
86
+ * Attaches public fluent parser methods to a pipeline instance.
87
+ *
88
+ * Each parser name in the registry becomes a fluent method on the public
89
+ * pipeline. Calls delegate to the underlying builder, apply the parser, and
90
+ * wrap the updated builder to produce the next public pipeline in the chain.
91
+ *
92
+ * Methods are defined as non‑enumerable and immutable so the fluent surface
93
+ * stays stable, predictable, and free of accidental overrides.
94
+ */
95
+ function attachPublicFluentParsers(pub, builder, wrap) {
96
+ for (const name of Object.keys(janeRegistry.parsers)) {
97
+ Object.defineProperty(pub, name, {
98
+ enumerable: false,
99
+ configurable: true,
100
+ writable: false,
101
+ value: (...args) => {
102
+ const next = builder.parse(name, ...args);
103
+ return wrap(next);
104
+ },
105
+ });
106
+ }
107
+ }
108
+ /* ——— * ————— * ————— * ————— * ————— * ————— * ————— * ————— * ————— * ——— *\
109
+ |* Attach Fluent Parsers *|
110
+ \* ——— * ————— * ————— * ————— * ————— * ————— * ————— * ————— * ————— * ——— */
111
+ /**
112
+ * Attaches fluent parser methods directly to a PipelineBuilder.
113
+ *
114
+ * Each parser in the registry becomes a fluent method on the builder. Calls
115
+ * record the parser request on the current builder and return the same
116
+ * builder instance, allowing internal code to compose parse steps without
117
+ * wrapping or producing a new public pipeline.
118
+ *
119
+ * Methods are non‑enumerable and immutable so the builder’s shape stays
120
+ * stable and free of accidental overrides.
121
+ */
122
+ export function attachFluentParsers(builder) {
123
+ for (const name of Object.keys(janeRegistry.parsers)) {
124
+ Object.defineProperty(builder, name, {
125
+ enumerable: false,
126
+ configurable: true,
127
+ writable: false,
128
+ value: (...args) => builder.parse(name, ...args),
129
+ });
130
+ }
131
+ }
132
+ /* ——— * ————— * ————— * ————— * ————— * ————— * ————— * ————— * ————— * ——— *\
133
+ |* Create Jane *|
134
+ \* ——— * ————— * ————— * ————— * ————— * ————— * ————— * ————— * ————— * ——— */
135
+ /**
136
+ * Creates a policy‑aware Jane instance.
137
+ *
138
+ * Normalizes the base policy, wraps pipeline builders with the public API,
139
+ * and wires in analysis features (diff, explain, replay, telemetry) according
140
+ * to the active policy. Each fluent call returns a new wrapped builder, keeping
141
+ * the pipeline immutable and policy‑driven end‑to‑end.
142
+ */
143
+ export function createJane(basePolicyInput) {
144
+ const basePolicy = normalizePolicy(defaultPolicy, basePolicyInput);
145
+ const wrap = (builder) => {
146
+ const core = {
147
+ async run() {
148
+ const result = await builder.run();
149
+ const policy = builder.policy ?? defaultPolicy;
150
+ if (policy.analysis?.telemetry && builder.overrides.telemetrySink) {
151
+ const tel = telemetry({
152
+ boundaryName: policy.boundaryName ?? 'unknown-boundary',
153
+ pipelineName: policy.pipelineName ?? 'unknown-pipeline',
154
+ result,
155
+ });
156
+ builder.overrides.telemetrySink(tel.records);
157
+ }
158
+ return toJaneResult(result);
159
+ },
160
+ named(name) {
161
+ return wrap(builder.named(name));
162
+ },
163
+ userMessage(userMessage) {
164
+ return wrap(builder.userMessage(userMessage));
165
+ },
166
+ parse(ruleOrName, ...args) {
167
+ const next = typeof ruleOrName === 'string'
168
+ ? builder.parse(ruleOrName, ...args)
169
+ : builder.parse(ruleOrName);
170
+ return wrap(next);
171
+ },
172
+ validate(ruleOrName, ...args) {
173
+ const next = typeof ruleOrName === 'string'
174
+ ? builder.validate(ruleOrName, ...args)
175
+ : builder.validate(ruleOrName);
176
+ return wrap(next);
177
+ },
178
+ scan() {
179
+ return wrap(builder.scan());
180
+ },
181
+ strict() {
182
+ return wrap(builder.strict());
183
+ },
184
+ moderate() {
185
+ return wrap(builder.moderate());
186
+ },
187
+ lax() {
188
+ return wrap(builder.lax());
189
+ },
190
+ withDiff() {
191
+ return wrap(builder.withDiff());
192
+ },
193
+ withExplain() {
194
+ return wrap(builder.withExplain());
195
+ },
196
+ withReplay() {
197
+ return wrap(builder.withReplay());
198
+ },
199
+ withTelemetry(sink) {
200
+ return wrap(builder.withTelemetry(sink));
201
+ },
202
+ withPolicy(input) {
203
+ const merged = normalizePolicy(basePolicy, input);
204
+ return createJane(merged);
205
+ },
206
+ allowBigint() {
207
+ return wrap(builder.allowBigint());
208
+ },
209
+ strictBoundary(shape) {
210
+ const merged = normalizePolicy(basePolicy, {
211
+ boundary: boundaryPolicyStrict,
212
+ });
213
+ return createJane(merged).boundary(shape);
214
+ },
215
+ laxBoundary(shape) {
216
+ const merged = normalizePolicy(basePolicy, {
217
+ boundary: boundaryPolicyLax,
218
+ });
219
+ return createJane(merged).boundary(shape);
220
+ },
221
+ defaultBoundary(shape) {
222
+ const merged = normalizePolicy(basePolicy, {
223
+ boundary: boundaryPolicyDefault,
224
+ });
225
+ return createJane(merged).boundary(shape);
226
+ },
227
+ severity(map) {
228
+ return wrap(builder.withPolicy({ severity: map }));
229
+ },
230
+ reject(...patterns) {
231
+ return wrap(builder.withPolicy({ reject: patterns }));
232
+ },
233
+ review(...patterns) {
234
+ return wrap(builder.withPolicy({ review: patterns }));
235
+ },
236
+ warn(...patterns) {
237
+ return wrap(builder.withPolicy({ warn: patterns }));
238
+ },
239
+ boundaryAccept(mode) {
240
+ return wrap(builder.withPolicy({ boundary: { accept: mode } }));
241
+ },
242
+ boundaryIncludeRejected() {
243
+ return wrap(builder.withPolicy({ boundary: { values: { includeRejected: true } } }));
244
+ },
245
+ boundaryIncludeUndefined() {
246
+ return wrap(builder.withPolicy({ boundary: { values: { includeUndefined: true } } }));
247
+ },
248
+ boundaryTransform(fn) {
249
+ return wrap(builder.withPolicy({ boundary: { values: { transform: fn } } }));
250
+ },
251
+ boundaryIgnore(...patterns) {
252
+ return wrap(builder.withPolicy({ boundary: { ignore: patterns } }));
253
+ },
254
+ boundaryHideEvents(...patterns) {
255
+ return wrap(builder.withPolicy({ boundary: { hideEvents: patterns } }));
256
+ },
257
+ boundaryHideIssues(...patterns) {
258
+ return wrap(builder.withPolicy({ mode: 'lax', boundary: { hideIssues: patterns } }));
259
+ },
260
+ boundaryReducer(fn) {
261
+ return wrap(builder.withPolicy({ boundary: { reducer: fn } }));
262
+ },
263
+ boundaryHideFields() {
264
+ return wrap(builder.withPolicy({ boundary: { metadata: { includeFields: false } } }));
265
+ },
266
+ boundaryHideTimestamps() {
267
+ return wrap(builder.withPolicy({ boundary: { metadata: { includeTimestamps: false } } }));
268
+ },
269
+ boundaryHideRunId() {
270
+ return wrap(builder.withPolicy({ boundary: { metadata: { includeRunId: false } } }));
271
+ },
272
+ boundaryRule(rule) {
273
+ const existing = builder.policy?.boundary?.rules ?? [];
274
+ return wrap(builder.withPolicy({
275
+ boundary: {
276
+ rules: [...existing, rule],
277
+ },
278
+ }));
279
+ },
280
+ boundaryRules(...rules) {
281
+ const existing = builder.policy?.boundary?.rules ?? [];
282
+ return wrap(builder.withPolicy({
283
+ boundary: {
284
+ rules: [...existing, ...rules],
285
+ },
286
+ }));
287
+ },
288
+ };
289
+ const pub = core;
290
+ const internal = pub;
291
+ internal[BUILDER] = builder;
292
+ attachPublicFluentParsers(pub, builder, wrap);
293
+ attachPublicFluentValidators(pub, builder, wrap);
294
+ return pub;
295
+ };
296
+ return {
297
+ value(raw, path, options) {
298
+ const { inputName } = options ?? {};
299
+ const internal = createPipeline(raw, path ?? rootPath(inputName), [], [], [], basePolicy, { inputName });
300
+ return wrap(internal);
301
+ },
302
+ async boundary(shape) {
303
+ const entries = Object.entries(shape);
304
+ const pipelineResults = await Promise.all(entries.map(async ([key, pub]) => {
305
+ const internal = pub;
306
+ const builder = internal[BUILDER];
307
+ if (!builder) {
308
+ throw new Error(`Internal error: missing builder for field "${key}"`);
309
+ }
310
+ const result = await builder.run();
311
+ return [key, result];
312
+ }));
313
+ const fields = Object.fromEntries(pipelineResults);
314
+ const pipelineBoundaryConfigs = pipelineResults
315
+ .map(([_, result]) => result.policy?.boundary)
316
+ .filter(Boolean);
317
+ const builderBoundaryConfigs = entries
318
+ .map(([_key, pub]) => {
319
+ const internal = pub;
320
+ const b = internal[BUILDER];
321
+ return b?.policy?.boundary;
322
+ })
323
+ .filter(Boolean);
324
+ const mergedBoundary = mergeBoundaryPolicies(basePolicy.boundary, ...builderBoundaryConfigs, ...pipelineBoundaryConfigs);
325
+ const effectivePolicy = resolvePolicy(defaultPolicy, {
326
+ ...basePolicy,
327
+ boundary: mergedBoundary,
328
+ }, {});
329
+ const boundary = await boundaryRunner({
330
+ policy: effectivePolicy,
331
+ fields,
332
+ });
333
+ return {
334
+ ...toJaneBoundaryResult(boundary, fields),
335
+ policy: effectivePolicy,
336
+ };
337
+ },
338
+ withPolicy(input) {
339
+ const merged = normalizePolicy(basePolicy, input);
340
+ return createJane(merged);
341
+ },
342
+ strictBoundary(shape) {
343
+ const merged = normalizePolicy(basePolicy, {
344
+ boundary: boundaryPolicyStrict,
345
+ });
346
+ return createJane(merged).boundary(shape);
347
+ },
348
+ laxBoundary(shape) {
349
+ const merged = normalizePolicy(basePolicy, {
350
+ boundary: boundaryPolicyLax,
351
+ });
352
+ return createJane(merged).boundary(shape);
353
+ },
354
+ defaultBoundary(shape) {
355
+ const merged = normalizePolicy(basePolicy, {
356
+ boundary: boundaryPolicyDefault,
357
+ });
358
+ return createJane(merged).boundary(shape);
359
+ },
360
+ };
361
+ }
362
+ /* ——— * ————— * ————— * ————— * ————— * ————— * ————— * ————— * ————— * ——— *\
363
+ |* To Jane Result *|
364
+ \* ——— * ————— * ————— * ————— * ————— * ————— * ————— * ————— * ————— * ——— */
365
+ /**
366
+ * Creates a Jane instance with a normalized base policy and the full fluent API.
367
+ *
368
+ * The factory wraps a PipelineBuilder and exposes a stable, user‑facing
369
+ * pipeline surface. Each fluent call produces a new wrapped builder, while
370
+ * root‑level helpers (value, boundary, withPolicy) create new Jane instances
371
+ * with merged policy state.
372
+ *
373
+ * The wrapper isolates internal builder mechanics from consumers, attaches
374
+ * parser and validator methods from the registry, and ensures that telemetry,
375
+ * diff, explain, replay, and boundary behavior all flow through a consistent
376
+ * entry point. All policy resolution happens here so contributors can reason
377
+ * about effective behavior without tracing through individual pipeline steps.
378
+ */
379
+ export function toJaneResult(r) {
380
+ const issues = r.events.filter((e) => e.kind === 'error' || e.kind === 'fatal');
381
+ const events = r.events.filter((e) => e.kind === 'info' || e.kind === 'warn');
382
+ return {
383
+ ok: issues.length === 0,
384
+ issues: issues.length > 0 ? issues : undefined,
385
+ events: events.length > 0 ? events : undefined,
386
+ value: r.final,
387
+ path: r.path,
388
+ diff: r.diff,
389
+ explain: r.explain,
390
+ replay: r.replay,
391
+ metadata: {
392
+ raw: r.raw,
393
+ rawType: r.rawType,
394
+ safe: r.safe,
395
+ safeType: r.safeType,
396
+ normalized: r.normalized,
397
+ normalizedType: r.normalizedType,
398
+ final: r.final,
399
+ finalType: r.finalType,
400
+ startedAt: r.metadata.startedAt,
401
+ finishedAt: r.metadata.finishedAt,
402
+ durationMs: r.metadata.durationMs,
403
+ inputName: r.metadata.inputName,
404
+ },
405
+ };
406
+ }
407
+ /* ——— * ————— * ————— * ————— * ————— * ————— * ————— * ————— * ————— * ——— *\
408
+ |* To Jane Boundary Result *|
409
+ \* ——— * ————— * ————— * ————— * ————— * ————— * ————— * ————— * ————— * ——— */
410
+ /**
411
+ * Converts an internal BoundaryResult and its field-level PipelineResults
412
+ * into the public JaneBoundaryResult shape.
413
+ *
414
+ * Each pipeline result is first converted to a JaneResult, then assembled
415
+ * alongside the boundary’s own decision, issues, events, values, and metadata.
416
+ * No interpretation or rewriting occurs here—this function preserves the
417
+ * boundary runner’s output exactly while projecting it into the public API.
418
+ */
419
+ export function toJaneBoundaryResult(boundary, pipelines) {
420
+ const janeResults = Object.fromEntries(Object.entries(pipelines).map(([k, p]) => [k, toJaneResult(p)]));
421
+ return {
422
+ ok: boundary.ok,
423
+ issues: boundary.issues,
424
+ events: boundary.events,
425
+ values: boundary.values,
426
+ fields: janeResults,
427
+ metadata: boundary.metadata,
428
+ };
429
+ }
@@ -0,0 +1,31 @@
1
+ /**
2
+ * ----------------------------------------------------------------------------
3
+ * Common | Barrel File
4
+ * ----------------------------------------------------------------------------
5
+ * @package @clementine-solutions/jane
6
+ * @description Re‑exports shared common modules to provide a stable,
7
+ * minimal entry point for internal consumers. This file
8
+ * exposes no logic of its own.
9
+ * @see https://jane-io.com
10
+ * ----------------------------------------------------------------------------
11
+ */
12
+ /* ——— * ————— * ————— * ————— * ————— * ————— * ————— * ————— * ————— * ——— *\
13
+ |* Events *|
14
+ \* ——— * ————— * ————— * ————— * ————— * ————— * ————— * ————— * ————— * ——— */
15
+ export { createEvent, isJaneEvent } from './events';
16
+ /* ——— * ————— * ————— * ————— * ————— * ————— * ————— * ————— * ————— * ——— *\
17
+ |* Fluent *|
18
+ \* ——— * ————— * ————— * ————— * ————— * ————— * ————— * ————— * ————— * ——— */
19
+ export { attachFluentParsers, attachFluentValidators, createJane, toJaneBoundaryResult, toJaneResult, } from './fluent';
20
+ /* ——— * ————— * ————— * ————— * ————— * ————— * ————— * ————— * ————— * ——— *\
21
+ |* Policy *|
22
+ \* ——— * ————— * ————— * ————— * ————— * ————— * ————— * ————— * ————— * ——— */
23
+ export { applyEscalate, applyOverride, boundaryPolicyDefault, boundaryPolicyLax, boundaryPolicyStrict, clampSeverityIndex, compileSeverityMap, decideEvent, defaultPolicy, laxPolicy, normalizePolicy, mergeBoundaryPolicies, policyDecision, resolveLevel, resolvePolicy, severityIndex, SEVERITY_ORDER, strictPolicy, } from './policy';
24
+ /* ——— * ————— * ————— * ————— * ————— * ————— * ————— * ————— * ————— * ——— *\
25
+ |* Utilities *|
26
+ \* ——— * ————— * ————— * ————— * ————— * ————— * ————— * ————— * ————— * ——— */
27
+ export { deepEqual, generateRunId, isObject, isPlainObject, isPrimitive, safeStringify, } from './utilities';
28
+ /* ——— * ————— * ————— * ————— * ————— * ————— * ————— * ————— * ————— * ——— *\
29
+ |* Wildcard *|
30
+ \* ——— * ————— * ————— * ————— * ————— * ————— * ————— * ————— * ————— * ——— */
31
+ export { compileWildcard, getCompiledWildcard, matchesWildcard } from './wildcard';