@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,339 @@
1
+ /**
2
+ * ----------------------------------------------------------------------------
3
+ * Pipeline | Contain
4
+ * ----------------------------------------------------------------------------
5
+ * @package @clementine-solutions/jane
6
+ * @description Enforces structural limits and produces a safe, cycle‑free
7
+ * representation of input.
8
+ * @see https://jane-io.com
9
+ * ----------------------------------------------------------------------------
10
+ */
11
+ import { appendSegment, rootPath, setIndex, setKey } from '../field-path';
12
+ import { defaultPolicy } from '../common';
13
+ /* ——— * ————— * ————— * ————— * ————— * ————— * ————— * ————— * ————— * ——— *\
14
+ |* Containment Sentinel *|
15
+ \* ——— * ————— * ————— * ————— * ————— * ————— * ————— * ————— * ————— * ——— */
16
+ /**
17
+ * Sentinel markers used when a value cannot be safely contained.
18
+ *
19
+ * These appear when encountering cycles, excessive depth or size, unsafe
20
+ * structures, or disallowed types such as functions, symbols, and proxies.
21
+ */
22
+ export const ContainmentSentinel = {
23
+ Circular: '[Circular]',
24
+ TooDeep: '[TooDeep]',
25
+ TooLarge: '[TooLarge]',
26
+ Proxy: '[Proxy]',
27
+ Getter: '[Getter]',
28
+ Function: '[Function]',
29
+ Symbol: '[Symbol]',
30
+ BigInt: '[BigInt]',
31
+ };
32
+ /* ——— * ————— * ————— * ————— * ————— * ————— * ————— * ————— * ————— * ——— *\
33
+ |* Contain Array *|
34
+ \* ——— * ————— * ————— * ————— * ————— * ————— * ————— * ————— * ————— * ——— */
35
+ /**
36
+ * Contains an array by applying depth and length limits.
37
+ *
38
+ * Recursively contains each element, records the container for cycle
39
+ * detection, and appends a TooLarge sentinel when the array exceeds
40
+ * the configured maximum length.
41
+ */
42
+ export function containArray(value, path, context, depth) {
43
+ const limit = context.options.maxArrayLength;
44
+ const length = value.length;
45
+ const out = [];
46
+ context.seen.set(value, out);
47
+ const max = Math.min(length, limit);
48
+ for (let index = 0; index < max; index++) {
49
+ const child = value[index];
50
+ const childPath = appendSegment(path, setIndex(index));
51
+ out.push(containRecursive(child, childPath, context, depth));
52
+ }
53
+ if (length > limit) {
54
+ out.push(ContainmentSentinel.TooLarge);
55
+ }
56
+ return out;
57
+ }
58
+ /* ——— * ————— * ————— * ————— * ————— * ————— * ————— * ————— * ————— * ——— *\
59
+ |* Contain Bigint *|
60
+ \* ——— * ————— * ————— * ————— * ————— * ————— * ————— * ————— * ————— * ——— */
61
+ /**
62
+ * Represents a bigint value in a safe, JSON‑compatible form.
63
+ *
64
+ * Used only when bigint is explicitly allowed by policy; otherwise
65
+ * containment falls back to the BigInt sentinel.
66
+ */
67
+ export function containBigInt(value) {
68
+ return {
69
+ kind: 'bigint',
70
+ asString: value.toString(),
71
+ };
72
+ }
73
+ /* ——— * ————— * ————— * ————— * ————— * ————— * ————— * ————— * ————— * ——— *\
74
+ |* Contain Object *|
75
+ \* ——— * ————— * ————— * ————— * ————— * ————— * ————— * ————— * ————— * ——— */
76
+ /**
77
+ * Contains a plain object by enforcing key limits and safe property access.
78
+ *
79
+ * Skips prototype‑pollution vectors, replaces getters/setters with a
80
+ * Getter sentinel, and recursively contains each value. Adds a TooLarge
81
+ * sentinel entry when the object exceeds the configured key limit.
82
+ */
83
+ export function containObject(value, path, ctx, depth) {
84
+ const out = Object.create(null);
85
+ // 1. Get keys safely
86
+ const rawKeys = safeObjectKeys(value);
87
+ // 2. Filter dangerous keys BEFORE doing anything else
88
+ const keys = rawKeys.filter((k) => k !== '__proto__' && k !== 'constructor' && k !== 'prototype');
89
+ const limit = ctx.options.maxObjectKeys;
90
+ const max = Math.min(keys.length, limit);
91
+ for (let i = 0; i < max; i++) {
92
+ const key = keys[i];
93
+ const desc = safePropertyDescriptor(value, key);
94
+ const childPath = appendSegment(path, setKey(key));
95
+ // 3. Getter/setter detection ALWAYS runs now
96
+ if (!desc || typeof desc.get === 'function' || typeof desc.set === 'function') {
97
+ out[key] = ContainmentSentinel.Getter;
98
+ continue;
99
+ }
100
+ out[key] = containRecursive(desc.value, childPath, ctx, depth);
101
+ }
102
+ if (keys.length > limit) {
103
+ out['[TooLarge]'] = ContainmentSentinel.TooLarge;
104
+ }
105
+ return out;
106
+ }
107
+ /* ——— * ————— * ————— * ————— * ————— * ————— * ————— * ————— * ————— * ——— *\
108
+ |* Contain Recursive *|
109
+ \* ——— * ————— * ————— * ————— * ————— * ————— * ————— * ————— * ————— * ——— */
110
+ /**
111
+ * Recursively contains any value into a safe, JSON‑like structure.
112
+ *
113
+ * Enforces depth limits, detects cycles, handles primitives directly,
114
+ * applies policy‑controlled exceptions (e.g., bigint), and replaces
115
+ * unsafe or non‑plain objects with appropriate sentinels.
116
+ */
117
+ export function containRecursive(value, path, context, depth) {
118
+ if (depth > context.options.maxDepth) {
119
+ return ContainmentSentinel.TooDeep;
120
+ }
121
+ if (value === null)
122
+ return null;
123
+ const type = typeof value;
124
+ if (type === 'number' || type === 'string' || type === 'boolean') {
125
+ return value;
126
+ }
127
+ if (type === 'undefined') {
128
+ return null;
129
+ }
130
+ if (type === 'bigint') {
131
+ if (context.policy.exception?.bigint) {
132
+ return containBigInt(value);
133
+ }
134
+ return ContainmentSentinel.BigInt;
135
+ }
136
+ if (type === 'symbol')
137
+ return ContainmentSentinel.Symbol;
138
+ if (type === 'function')
139
+ return ContainmentSentinel.Function;
140
+ if (typeof value === 'object') {
141
+ const object = value;
142
+ const existing = context.seen.get(object);
143
+ if (existing !== undefined) {
144
+ return ContainmentSentinel.Circular;
145
+ }
146
+ if (Array.isArray(object)) {
147
+ const container = [];
148
+ context.seen.set(object, container);
149
+ return containArray(object, path, context, depth + 1);
150
+ }
151
+ if (object instanceof Date) {
152
+ return new Date(object.getTime()).toISOString();
153
+ }
154
+ const proto = Object.getPrototypeOf(object);
155
+ const isPlain = proto === Object.prototype || proto === null;
156
+ if (!isPlain) {
157
+ return ContainmentSentinel.Proxy;
158
+ }
159
+ const container = Object.create(null);
160
+ context.seen.set(object, container);
161
+ return containObject(object, path, context, depth + 1);
162
+ }
163
+ return ContainmentSentinel.Proxy;
164
+ }
165
+ /* ——— * ————— * ————— * ————— * ————— * ————— * ————— * ————— * ————— * ——— *\
166
+ |* Contain Value *|
167
+ \* ——— * ————— * ————— * ————— * ————— * ————— * ————— * ————— * ————— * ——— */
168
+ /**
169
+ * Entry point for containment.
170
+ *
171
+ * Creates a fresh containment context and produces a safe representation
172
+ * of the input value. Falls back to the Proxy sentinel if containment
173
+ * fails unexpectedly.
174
+ */
175
+ export function containValue(value, path = rootPath(), options = defaultContainmentOptions, policy = defaultPolicy) {
176
+ const context = createContainmentContext(options, policy);
177
+ try {
178
+ return containRecursive(value, path, context, 0);
179
+ }
180
+ catch {
181
+ return ContainmentSentinel.Proxy;
182
+ }
183
+ }
184
+ /* ——— * ————— * ————— * ————— * ————— * ————— * ————— * ————— * ————— * ——— *\
185
+ |* Create Containment Context *|
186
+ \* ——— * ————— * ————— * ————— * ————— * ————— * ————— * ————— * ————— * ——— */
187
+ /**
188
+ * Constructs a containment context with structural limits, policy,
189
+ * and a fresh cycle‑detection map.
190
+ */
191
+ export function createContainmentContext(options, policy) {
192
+ return {
193
+ options,
194
+ policy,
195
+ seen: new WeakMap(),
196
+ };
197
+ }
198
+ /* ——— * ————— * ————— * ————— * ————— * ————— * ————— * ————— * ————— * ——— *\
199
+ |* Deep Clone *|
200
+ \* ——— * ————— * ————— * ————— * ————— * ————— * ————— * ————— * ————— * ——— */
201
+ /**
202
+ * Produces a deep, acyclic clone of an internal JSON value.
203
+ *
204
+ * Preserves primitives, arrays, plain objects, and Date instances while
205
+ * tracking references to avoid infinite recursion. Used to safely copy
206
+ * containment output without re‑running containment.
207
+ */
208
+ export function deepClone(input, seen = new WeakMap()) {
209
+ if (input === null || typeof input !== 'object') {
210
+ return input;
211
+ }
212
+ if (seen.has(input)) {
213
+ return seen.get(input);
214
+ }
215
+ if (input instanceof Date) {
216
+ return new Date(input.getTime());
217
+ }
218
+ if (Array.isArray(input)) {
219
+ const clone = new Array(input.length);
220
+ seen.set(input, clone);
221
+ for (let i = 0; i < input.length; i++) {
222
+ if (i in input) {
223
+ clone[i] = deepClone(input[i], seen);
224
+ }
225
+ }
226
+ return clone;
227
+ }
228
+ const clone = {};
229
+ seen.set(input, clone);
230
+ for (const key of Object.keys(input)) {
231
+ const value = input[key];
232
+ clone[key] = deepClone(value, seen); // ← FIXED
233
+ }
234
+ return clone;
235
+ }
236
+ /* ——— * ————— * ————— * ————— * ————— * ————— * ————— * ————— * ————— * ——— *\
237
+ |* Default Containment Options *|
238
+ \* ——— * ————— * ————— * ————— * ————— * ————— * ————— * ————— * ————— * ——— */
239
+ /**
240
+ * Default structural limits applied during containment.
241
+ *
242
+ * These bounds prevent excessive recursion and traversal by capping depth,
243
+ * array length, and object key count.
244
+ */
245
+ export const defaultContainmentOptions = {
246
+ maxDepth: 10,
247
+ maxArrayLength: 1,
248
+ maxObjectKeys: 5,
249
+ };
250
+ /* ——— * ————— * ————— * ————— * ————— * ————— * ————— * ————— * ————— * ——— *\
251
+ |* Is Sentinel *|
252
+ \* ——— * ————— * ————— * ————— * ————— * ————— * ————— * ————— * ————— * ——— */
253
+ /**
254
+ * Determines whether a value is a containment sentinel.
255
+ *
256
+ * Sentinels represent structural hazards or disallowed types encountered
257
+ * during containment and are encoded as well‑known string markers.
258
+ */
259
+ export function isSentinel(value) {
260
+ if (typeof value !== 'string')
261
+ return false;
262
+ switch (value) {
263
+ case '[Circular]':
264
+ case '[TooDeep]':
265
+ case '[TooLarge]':
266
+ case '[Proxy]':
267
+ case '[Getter]':
268
+ case '[Function]':
269
+ case '[Symbol]':
270
+ case '[BigInt]':
271
+ return true;
272
+ default:
273
+ return false;
274
+ }
275
+ }
276
+ /* ——— * ————— * ————— * ————— * ————— * ————— * ————— * ————— * ————— * ——— *\
277
+ |* Safe Object Keys *|
278
+ \* ——— * ————— * ————— * ————— * ————— * ————— * ————— * ————— * ————— * ——— */
279
+ /**
280
+ * Safely retrieves an object's own enumerable keys.
281
+ *
282
+ * Returns an empty array if key enumeration fails, protecting containment
283
+ * from objects with hostile or unusual prototypes.
284
+ */
285
+ export function safeObjectKeys(value) {
286
+ try {
287
+ return Object.keys(value);
288
+ }
289
+ catch {
290
+ return [];
291
+ }
292
+ }
293
+ /* ——— * ————— * ————— * ————— * ————— * ————— * ————— * ————— * ————— * ——— *\
294
+ |* Safe Property Descriptor *|
295
+ \* ——— * ————— * ————— * ————— * ————— * ————— * ————— * ————— * ————— * ——— */
296
+ /**
297
+ * Safely retrieves a property descriptor.
298
+ *
299
+ * Returns undefined if descriptor access throws, allowing containment to
300
+ * gracefully handle objects with unsafe or non‑standard behavior.
301
+ */
302
+ export function safePropertyDescriptor(value, key) {
303
+ try {
304
+ return Object.getOwnPropertyDescriptor(value, key);
305
+ }
306
+ catch {
307
+ return undefined;
308
+ }
309
+ }
310
+ /* ——— * ————— * ————— * ————— * ————— * ————— * ————— * ————— * ————— * ——— *\
311
+ |* Sentinel Event *|
312
+ \* ——— * ————— * ————— * ————— * ————— * ————— * ————— * ————— * ————— * ——— */
313
+ /**
314
+ * Maps a containment sentinel to its corresponding event.
315
+ *
316
+ * Used to surface structural hazards—such as cycles, excessive depth,
317
+ * unsafe objects, or disallowed types—as typed events during scanning
318
+ * and policy evaluation.
319
+ */
320
+ export function sentinelEvent(kind) {
321
+ switch (kind) {
322
+ case ContainmentSentinel.Circular:
323
+ return { code: 'contain.circular', severity: 'fatal' };
324
+ case ContainmentSentinel.TooDeep:
325
+ return { code: 'contain.too.deep', severity: 'warn' };
326
+ case ContainmentSentinel.TooLarge:
327
+ return { code: 'contain.too.large', severity: 'warn' };
328
+ case ContainmentSentinel.Proxy:
329
+ return { code: 'contain.proxy', severity: 'info' };
330
+ case ContainmentSentinel.Getter:
331
+ return { code: 'contain.getter', severity: 'info' };
332
+ case ContainmentSentinel.Function:
333
+ return { code: 'contain.function', severity: 'info' };
334
+ case ContainmentSentinel.Symbol:
335
+ return { code: 'contain.symbol', severity: 'info' };
336
+ case ContainmentSentinel.BigInt:
337
+ return { code: 'contain.bigint', severity: 'info' };
338
+ }
339
+ }
@@ -0,0 +1,37 @@
1
+ /**
2
+ * ----------------------------------------------------------------------------
3
+ * Pipeline | Barrel File
4
+ * ----------------------------------------------------------------------------
5
+ * @package @clementine-solutions/jane
6
+ * @description
7
+ * @see https://jane-io.com
8
+ * ----------------------------------------------------------------------------
9
+ */
10
+ /* ——— * ————— * ————— * ————— * ————— * ————— * ————— * ————— * ————— * ——— *\
11
+ |* Boundary *|
12
+ \* ——— * ————— * ————— * ————— * ————— * ————— * ————— * ————— * ————— * ——— */
13
+ export { boundaryRunner, decideBoundary } from './boundary';
14
+ /* ——— * ————— * ————— * ————— * ————— * ————— * ————— * ————— * ————— * ——— *\
15
+ |* Contain *|
16
+ \* ——— * ————— * ————— * ————— * ————— * ————— * ————— * ————— * ————— * ——— */
17
+ export { ContainmentSentinel, containArray, containBigInt, containObject, containRecursive, containValue, createContainmentContext, deepClone, defaultContainmentOptions, isSentinel, safeObjectKeys, safePropertyDescriptor, sentinelEvent, } from './contain';
18
+ /* ——— * ————— * ————— * ————— * ————— * ————— * ————— * ————— * ————— * ——— *\
19
+ |* Normalize *|
20
+ \* ——— * ————— * ————— * ————— * ————— * ————— * ————— * ————— * ————— * ——— */
21
+ export { normalizationEvent, normalizationRunner, selectNormalizationRules } from './normalize';
22
+ /* ——— * ————— * ————— * ————— * ————— * ————— * ————— * ————— * ————— * ——— *\
23
+ |* Parse *|
24
+ \* ——— * ————— * ————— * ————— * ————— * ————— * ————— * ————— * ————— * ——— */
25
+ export { isFluentParser, isParseFactory, parseEvent, parseRunner } from './parse';
26
+ /* ——— * ————— * ————— * ————— * ————— * ————— * ————— * ————— * ————— * ——— *\
27
+ |* Pipeline *|
28
+ \* ——— * ————— * ————— * ————— * ————— * ————— * ————— * ————— * ————— * ——— */
29
+ export { createPipeline, pipelineRunner } from './pipeline';
30
+ /* ——— * ————— * ————— * ————— * ————— * ————— * ————— * ————— * ————— * ——— *\
31
+ |* Scan *|
32
+ \* ——— * ————— * ————— * ————— * ————— * ————— * ————— * ————— * ————— * ——— */
33
+ export { detectStructuralType, measureDepth, scanEvent, scanRunner, selectScanRules } from './scan';
34
+ /* ——— * ————— * ————— * ————— * ————— * ————— * ————— * ————— * ————— * ——— *\
35
+ |* Validate *|
36
+ \* ——— * ————— * ————— * ————— * ————— * ————— * ————— * ————— * ————— * ——— */
37
+ export { isFluentValidator, isValidationFactory, validationEvent, validationRunner, } from './validate';
@@ -0,0 +1,76 @@
1
+ /**
2
+ * ----------------------------------------------------------------------------
3
+ * Pipeline | Normalize
4
+ * ----------------------------------------------------------------------------
5
+ * @package @clementine-solutions/jane
6
+ * @description Applies mode‑aware transformations to the safe value
7
+ * before validation.
8
+ * @see https://jane-io.com
9
+ * ----------------------------------------------------------------------------
10
+ */
11
+ import { createEvent } from '../common';
12
+ import { rootPath } from '../field-path';
13
+ import { normalizationRuleRegistry } from '../normalizers';
14
+ import { detectStructuralType } from './scan';
15
+ /* ——— * ————— * ————— * ————— * ————— * ————— * ————— * ————— * ————— * ——— *\
16
+ |* Normalization Event *|
17
+ \* ——— * ————— * ————— * ————— * ————— * ————— * ————— * ————— * ————— * ——— */
18
+ /**
19
+ * Creates a normalization‑stage event.
20
+ *
21
+ * Wraps createEvent with the 'normalize' stage tag, producing structured
22
+ * messages emitted during value‑shaping operations.
23
+ */
24
+ export const normalizationEvent = (kind, code, path, message, userMessage, meta) => createEvent('normalize', kind, code, path, message, userMessage, meta);
25
+ /* ——— * ————— * ————— * ————— * ————— * ————— * ————— * ————— * ————— * ——— *\
26
+ |* Normalization Runner *|
27
+ \* ——— * ————— * ————— * ————— * ————— * ————— * ————— * ————— * ————— * ——— */
28
+ /**
29
+ * Executes the normalization stage.
30
+ *
31
+ * Applies each normalization rule in sequence, collects emitted events,
32
+ * and updates the working value according to the active mode. Lossy
33
+ * transformations are skipped in moderate mode, with a corresponding
34
+ * warning event recorded.
35
+ */
36
+ export const normalizationRunner = async (input) => {
37
+ const { safe, path: inputPath, rules, mode } = input;
38
+ const path = inputPath ?? rootPath();
39
+ let current = safe;
40
+ const events = [];
41
+ for (const rule of rules) {
42
+ let results;
43
+ try {
44
+ results = await rule(current, mode, path);
45
+ }
46
+ catch (err) {
47
+ events.push(createEvent('normalize', 'fatal', 'error.while.normalizing', path, String(err)));
48
+ continue;
49
+ }
50
+ for (const result of results) {
51
+ if (result.events) {
52
+ events.push(...result.events);
53
+ }
54
+ if (mode === 'moderate' && result.lossy === 'lossy') {
55
+ const warn = createEvent('normalize', 'warn', 'policy.is.moderate', path, 'A lossy normalization step was skipped in moderate mode.', undefined, { attempted: result.nextValue, mode });
56
+ events.push(warn);
57
+ continue;
58
+ }
59
+ current = result.nextValue;
60
+ }
61
+ }
62
+ return { events, normalized: current };
63
+ };
64
+ /* ——— * ————— * ————— * ————— * ————— * ————— * ————— * ————— * ————— * ——— *\
65
+ |* Select Normalization Rules *|
66
+ \* ——— * ————— * ————— * ————— * ————— * ————— * ————— * ————— * ————— * ——— */
67
+ /**
68
+ * Selects normalization rules based on the structural type of the value.
69
+ *
70
+ * Uses detectStructuralType to choose the appropriate rule set from the
71
+ * registry, ensuring predictable and type‑specific normalization behavior.
72
+ */
73
+ export function selectNormalizationRules(value) {
74
+ const type = detectStructuralType(value);
75
+ return normalizationRuleRegistry[type] ?? [];
76
+ }
@@ -0,0 +1,91 @@
1
+ /**
2
+ * ----------------------------------------------------------------------------
3
+ * Pipeline | Parse
4
+ * ----------------------------------------------------------------------------
5
+ * @package @clementine-solutions/jane
6
+ * @description Converts raw input into typed values using registered parse
7
+ * rules.
8
+ * @see https://jane-io.com
9
+ * ----------------------------------------------------------------------------
10
+ */
11
+ import { rootPath } from '../field-path';
12
+ /* ——— * ————— * ————— * ————— * ————— * ————— * ————— * ————— * ————— * ——— *\
13
+ |* Parse Event *|
14
+ \* ——— * ————— * ————— * ————— * ————— * ————— * ————— * ————— * ————— * ——— */
15
+ /**
16
+ * Creates a parse‑stage event.
17
+ *
18
+ * Produces a structured event tagged with the 'parse' phase, used to
19
+ * surface observations or errors encountered while interpreting input.
20
+ */
21
+ export function parseEvent(kind, code, message, path, userMessage, metadata) {
22
+ return {
23
+ phase: 'parse',
24
+ kind,
25
+ code,
26
+ message,
27
+ userMessage,
28
+ path,
29
+ metadata,
30
+ };
31
+ }
32
+ /* ——— * ————— * ————— * ————— * ————— * ————— * ————— * ————— * ————— * ——— *\
33
+ |* Is Parse Factory *|
34
+ \* ——— * ————— * ————— * ————— * ————— * ————— * ————— * ————— * ————— * ——— */
35
+ /**
36
+ * Determines whether a fluent parser entry is a factory.
37
+ *
38
+ * Factories accept arbitrary parameters and return a ParseRule, while
39
+ * fluent parsers expose a fixed two‑argument rule signature.
40
+ */
41
+ export function isParseFactory(fn) {
42
+ return fn.length !== 2;
43
+ }
44
+ /* ——— * ————— * ————— * ————— * ————— * ————— * ————— * ————— * ————— * ——— *\
45
+ |* Is Fluent Parser *|
46
+ \* ——— * ————— * ————— * ————— * ————— * ————— * ————— * ————— * ————— * ——— */
47
+ /**
48
+ * Determines whether a fluent parser entry is a direct parse rule.
49
+ *
50
+ * Fluent rules always accept exactly two parameters—value and path—
51
+ * distinguishing them from parameterized factories.
52
+ */
53
+ export function isFluentParser(fn) {
54
+ return typeof fn === 'function' && fn.length === 2;
55
+ }
56
+ /* ——— * ————— * ————— * ————— * ————— * ————— * ————— * ————— * ————— * ——— *\
57
+ |* Parse Runner *|
58
+ \* ——— * ————— * ————— * ————— * ————— * ————— * ————— * ————— * ————— * ——— */
59
+ /**
60
+ * Executes the parse stage.
61
+ *
62
+ * Applies each parse rule in sequence, collects emitted events, and
63
+ * updates the working value as rules propose transformations. Errors
64
+ * thrown by rules are captured as fatal parse events.
65
+ */
66
+ export async function parseRunner(ctx) {
67
+ const { value, path = rootPath(), rules } = ctx;
68
+ let current = value;
69
+ const events = [];
70
+ for (const rule of rules) {
71
+ try {
72
+ const results = await rule(current, path);
73
+ if (!results || results.length === 0) {
74
+ continue;
75
+ }
76
+ for (const r of results) {
77
+ if (r.events) {
78
+ events.push(...r.events);
79
+ }
80
+ current = r.nextValue;
81
+ }
82
+ }
83
+ catch (err) {
84
+ events.push(parseEvent('fatal', 'error.while.parsing', String(err), path));
85
+ }
86
+ }
87
+ return {
88
+ parsed: current,
89
+ events,
90
+ };
91
+ }