ata-validator 1.62.0 → 1.63.0

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 (56) hide show
  1. package/README.md +68 -13
  2. package/build.mjs +1 -0
  3. package/index.browser.mjs +23 -4
  4. package/index.bundle.mjs +14 -4
  5. package/index.edge.mjs +14 -4
  6. package/index.js +7 -2
  7. package/lib/aot-build.js +18 -0
  8. package/lib/aot-impl.js +20 -0
  9. package/lib/codegen-engine.js +72 -3
  10. package/lib/compiled-common.js +2 -1
  11. package/lib/email-literal.js +4 -1
  12. package/lib/error-runtime-features.js +41 -0
  13. package/lib/error-runtime.browser.js +15 -0
  14. package/lib/error-runtime.js +1 -32
  15. package/lib/formats-source.js +39 -40
  16. package/lib/interpreter-core.js +17 -9
  17. package/lib/ir/emit-deps.mjs +131 -0
  18. package/lib/ir/emit.mjs +560 -0
  19. package/lib/ir/front.mjs +834 -0
  20. package/lib/ir/index.mjs +10 -0
  21. package/lib/ir/refs.mjs +108 -0
  22. package/lib/ir/run.mjs +520 -0
  23. package/lib/ir/table.mjs +233 -0
  24. package/lib/js-compiler-errors.js +25 -7
  25. package/lib/js-compiler.js +6 -2
  26. package/lib/kernels/collapse.mjs +114 -0
  27. package/lib/kernels/custom.mjs +70 -0
  28. package/lib/kernels/fill.mjs +6 -0
  29. package/lib/kernels/formats-small.mjs +40 -0
  30. package/lib/kernels/formats.mjs +29 -0
  31. package/lib/kernels/index.mjs +11 -0
  32. package/lib/kernels/kernels.mjs +285 -0
  33. package/lib/kernels/leaves.mjs +28 -0
  34. package/lib/kernels/order.mjs +25 -0
  35. package/lib/kernels/plan-rich.mjs +17 -0
  36. package/lib/kernels/plan.d.ts +9 -0
  37. package/lib/kernels/plan.mjs +128 -0
  38. package/lib/kernels/preprocess.mjs +223 -0
  39. package/lib/kernels/rank.mjs +97 -0
  40. package/lib/kernels/rt.mjs +249 -0
  41. package/lib/kernels/table.mjs +182 -0
  42. package/lib/kernels/unique.mjs +157 -0
  43. package/lib/native-positions.js +1 -1
  44. package/lib/openapi.d.ts +2 -0
  45. package/lib/openapi.js +128 -0
  46. package/lib/plan-compiler.js +84 -7
  47. package/lib/public-api.js +7 -2
  48. package/lib/rejections.js +3 -1
  49. package/lib/schema-helpers.js +12 -0
  50. package/lib/schema-helpers.mjs +20 -0
  51. package/lib/schema-order.js +4 -2
  52. package/lib/standard-schema.js +1 -1
  53. package/lib/validator-core.js +35 -30
  54. package/lib/version.js +1 -1
  55. package/lite.js +7 -2
  56. package/package.json +32 -13
@@ -0,0 +1,285 @@
1
+ // One definition per keyword. The check, its params, its message and
2
+ // its enrichment live together, and both IR backends (lib/ir/run.mjs
3
+ // and lib/ir/emit.mjs) read this one definition.
4
+ //
5
+ // A leaf kernel:
6
+ // code the public error code (a function of the value for format)
7
+ // on(v) the instance types the keyword constrains; others pass
8
+ // ok(v, n) the check. One expression over v, n and exported helpers:
9
+ // the specializer writes its source in with n replaced by
10
+ // the schema's value, so there is no second copy of it.
11
+ // prepare(n) optional: the value ok() receives, built once
12
+ // params, msg, exp functions of the schema value only: evaluated at build
13
+ // rank, detail, suggest the enrichment; detail and suggest are named
14
+ // exports, so a module imports only the ones it uses.
15
+
16
+ import { cpLen, nearest, isObj, typeName, MISSING } from './rt.mjs'
17
+ // The helpers here are ES modules all the way down: a CommonJS file reachable
18
+ // from ata-validator/kernels stays in some bundlers' output unused (see
19
+ // scripts/gen-kernels-cjs.js). What reads the current engine's CommonJS
20
+ // files at compile time lives in ./leaves.mjs.
21
+ import { _deq, duplicatePair } from './unique.mjs'
22
+ import { repr } from './rt.mjs'
23
+
24
+ // JSON equality and the duplicate search, the functions every engine shares.
25
+ export const deq = _deq
26
+ export const dupPair = duplicatePair
27
+
28
+ export const isStr = (v) => typeof v === 'string'
29
+ export const isNum = (v) => typeof v === 'number'
30
+
31
+ // One predicate per JSON type. isType reads this table, and the specializer
32
+ // writes a type's own predicate in place, so both use the same definition.
33
+ export const TYPES = {
34
+ string: (v) => typeof v === 'string',
35
+ integer: (v) => Number.isInteger(v),
36
+ number: (v) => typeof v === 'number' && Number.isFinite(v),
37
+ boolean: (v) => typeof v === 'boolean',
38
+ null: (v) => v === null,
39
+ object: (v) => typeof v === 'object' && v !== null && !Array.isArray(v),
40
+ array: (v) => Array.isArray(v),
41
+ }
42
+ export const isType = (v, t) => { const f = TYPES[t]; return f !== undefined && f(v) }
43
+ export const isTypes = (v, ts) => { for (let i = 0; i < ts.length; i++) if (isType(v, ts[i])) return true; return false }
44
+
45
+ export const typeDetail = (received, v, p) => `expected ${p.type}, found ${typeName(v, received)}`
46
+ export const typeSuggest = (v, p) => {
47
+ if (typeof v !== 'string') return null
48
+ const t = p.type
49
+ let ok = false; let what
50
+ if (t === 'integer') { ok = /^-?\d+$/.test(v); what = 'an integer' } else if (t === 'number') { ok = /^-?\d+(\.\d+)?$/.test(v); what = 'a number' } else if (t === 'boolean') { ok = v === 'true' || v === 'false'; what = 'a boolean' }
51
+ return ok ? { text: 'value would coerce; enable `coerceTypes` or pass ' + what, kind: 'coercion' } : null
52
+ }
53
+ const typeOk = (v, n) => typeof n === 'string' ? isType(v, n) : isTypes(v, n)
54
+ const typeParams = (n) => ({ type: n })
55
+ const typeMsg = (n) => `must be ${typeof n === 'string' ? n : n.join(', ')}`
56
+ export const type = {
57
+ kw: 'type', code: 'ATA1001', on: null, ok: typeOk, params: typeParams, msg: typeMsg, exp: (n) => String(n),
58
+ rank: 1, detail: typeDetail, suggest: typeSuggest,
59
+ }
60
+ // The plain shape's view of a descriptor (richErrors: false): the keyword,
61
+ // its check, its params and its message, the same functions, without the
62
+ // detail and hint the rich shape adds (lib/kernels/table.mjs).
63
+ export const typeP = { kw: 'type', ok: typeOk, params: typeParams, msg: typeMsg }
64
+
65
+ const minLengthOk = (v, n) => cpLen(v) >= n
66
+ const minLengthParams = (n) => ({ limit: n })
67
+ const minLengthMsg = (n) => `must NOT have fewer than ${n} characters`
68
+ export const minLength = {
69
+ kw: 'minLength', code: 'ATA2001', on: isStr, ok: minLengthOk,
70
+ params: minLengthParams, msg: minLengthMsg, exp: (n) => `string with ≥${n} chars`, rank: 2,
71
+ }
72
+ export const minLengthP = { kw: 'minLength', on: isStr, ok: minLengthOk, params: minLengthParams, msg: minLengthMsg }
73
+ const maxLengthOk = (v, n) => cpLen(v) <= n
74
+ const maxLengthParams = (n) => ({ limit: n })
75
+ const maxLengthMsg = (n) => `must NOT have more than ${n} characters`
76
+ export const maxLength = {
77
+ kw: 'maxLength', code: 'ATA2002', on: isStr, ok: maxLengthOk,
78
+ params: maxLengthParams, msg: maxLengthMsg, exp: (n) => `string with ≤${n} chars`, rank: 2,
79
+ }
80
+ export const maxLengthP = { kw: 'maxLength', on: isStr, ok: maxLengthOk, params: maxLengthParams, msg: maxLengthMsg }
81
+ const minimumOk = (v, n) => !(v < n)
82
+ const minimumParams = (n) => ({ comparison: '>=', limit: n })
83
+ const minimumMsg = (n) => `must be >= ${n}`
84
+ export const minimum = {
85
+ kw: 'minimum', code: 'ATA2003', on: isNum, ok: minimumOk,
86
+ params: minimumParams, msg: minimumMsg, exp: (n) => `≥${n}`, rank: 2,
87
+ }
88
+ export const minimumP = { kw: 'minimum', on: isNum, ok: minimumOk, params: minimumParams, msg: minimumMsg }
89
+ const maximumOk = (v, n) => !(v > n)
90
+ const maximumParams = (n) => ({ comparison: '<=', limit: n })
91
+ const maximumMsg = (n) => `must be <= ${n}`
92
+ export const maximum = {
93
+ kw: 'maximum', code: 'ATA2004', on: isNum, ok: maximumOk,
94
+ params: maximumParams, msg: maximumMsg, exp: (n) => `≤${n}`, rank: 2,
95
+ }
96
+ export const maximumP = { kw: 'maximum', on: isNum, ok: maximumOk, params: maximumParams, msg: maximumMsg }
97
+ const patternOk = (v, n) => n.test(v)
98
+ const patternParams = (n) => ({ pattern: n })
99
+ const patternMsg = (n) => `must match pattern "${n}"`
100
+ export const pattern = {
101
+ kw: 'pattern', code: 'ATA2013', on: isStr, ok: patternOk, prepare: (n) => new RegExp(n, 'u'),
102
+ params: patternParams, msg: patternMsg, exp: (n) => `string matching /${n}/`, rank: 2,
103
+ }
104
+ export const patternP = { kw: 'pattern', on: isStr, ok: patternOk, prepare: (n) => new RegExp(n, 'u'), params: patternParams, msg: patternMsg }
105
+
106
+ // A format's detail; the format keyword itself is in ./leaves.mjs.
107
+ export const formatDetail = (received, v, p) => `not a valid ${p.format}: ${received}`
108
+ // What a format's error says, shared by the compile-time descriptor in
109
+ // ./leaves.mjs and the table interpreter (./table.mjs); the check itself is
110
+ // the format's function, the code and hint come with it.
111
+ const formatOk = (v, f) => f(v)
112
+ const formatParams = (n) => ({ format: n })
113
+ const formatMsg = (n) => `must match format "${n}"`
114
+ export const formatK = { kw: 'format', on: isStr, ok: formatOk, params: formatParams, msg: formatMsg, exp: (n) => `format '${n}'`, rank: 2, detail: formatDetail }
115
+ export const formatP = { kw: 'format', on: isStr, ok: formatOk, params: formatParams, msg: formatMsg }
116
+ export const makeHintSuggest = (hint) => (v) => { if (typeof v !== 'string') return null; const t = hint(v); return t ? { text: t, kind: 'format' } : null }
117
+
118
+ // Structural kernels: their errors name the container, not a child.
119
+ export const requiredDetail = (received, v, p) => `missing required property "${p.missingProperty}"`
120
+ export const requiredSuggest = (v, p) => {
121
+ if (v === MISSING || !v || typeof v !== 'object') return null
122
+ const keys = Object.keys(v)
123
+ if (keys.length > 64) return null
124
+ const best = nearest(p.missingProperty, keys)
125
+ return best === null ? null : { text: `did you mean \`${p.missingProperty}\` instead of \`${best}\`?`, kind: 'similar-key' }
126
+ }
127
+ const requiredParams = (k) => ({ missingProperty: k })
128
+ const requiredMsg = (k) => `must have required property '${k}'`
129
+ export const required = {
130
+ kw: 'required', code: 'ATA7001', rank: 0,
131
+ params: requiredParams, msg: requiredMsg, exp: (k) => `property '${k}'`,
132
+ detail: requiredDetail, suggest: requiredSuggest,
133
+ }
134
+ export const requiredP = { kw: 'required', params: requiredParams, msg: requiredMsg }
135
+
136
+ export const additionalDetail = (received, v, p) => `unknown property "${p.additionalProperty}"`
137
+ const additionalParams = (k) => ({ additionalProperty: k })
138
+ const additionalMsg = () => 'must NOT have additional properties'
139
+ export const additionalProperties = {
140
+ kw: 'additionalProperties', code: 'ATA7002', rank: 0,
141
+ params: additionalParams, msg: additionalMsg, exp: () => undefined,
142
+ detail: additionalDetail,
143
+ }
144
+ export const additionalP = { kw: 'additionalProperties', params: additionalParams, msg: additionalMsg }
145
+
146
+ // The current engine has no expected text for the exclusive bounds but words
147
+ // their detail as if it had one: "expected undefined, found 1".
148
+ export const exclusiveDetail = (received) => `expected undefined, found ${received}`
149
+ const exclusiveMinimumOk = (v, n) => !(v <= n)
150
+ const exclusiveMinimumParams = (n) => ({ comparison: '>', limit: n })
151
+ const exclusiveMinimumMsg = (n) => `must be > ${n}`
152
+ export const exclusiveMinimum = {
153
+ kw: 'exclusiveMinimum', code: 'ATA2005', on: isNum, ok: exclusiveMinimumOk,
154
+ params: exclusiveMinimumParams, msg: exclusiveMinimumMsg, exp: () => undefined, rank: 2,
155
+ detail: exclusiveDetail,
156
+ }
157
+ export const exclusiveMinimumP = { kw: 'exclusiveMinimum', on: isNum, ok: exclusiveMinimumOk, params: exclusiveMinimumParams, msg: exclusiveMinimumMsg }
158
+ const exclusiveMaximumOk = (v, n) => !(v >= n)
159
+ const exclusiveMaximumParams = (n) => ({ comparison: '<', limit: n })
160
+ const exclusiveMaximumMsg = (n) => `must be < ${n}`
161
+ export const exclusiveMaximum = {
162
+ kw: 'exclusiveMaximum', code: 'ATA2006', on: isNum, ok: exclusiveMaximumOk,
163
+ params: exclusiveMaximumParams, msg: exclusiveMaximumMsg, exp: () => undefined, rank: 2,
164
+ detail: exclusiveDetail,
165
+ }
166
+ export const exclusiveMaximumP = { kw: 'exclusiveMaximum', on: isNum, ok: exclusiveMaximumOk, params: exclusiveMaximumParams, msg: exclusiveMaximumMsg }
167
+ const multipleOfOk = (v, n) => n !== 0 && (Number.isInteger(v / n) || Math.abs(v / n - Math.round(v / n)) < 1e-9)
168
+ const multipleOfParams = (n) => ({ multipleOf: n })
169
+ const multipleOfMsg = (n) => `must be multiple of ${n}`
170
+ export const multipleOf = {
171
+ kw: 'multipleOf', code: 'ATA2007', on: isNum, ok: multipleOfOk,
172
+ params: multipleOfParams, msg: multipleOfMsg, exp: () => undefined, rank: 2,
173
+ }
174
+ export const multipleOfP = { kw: 'multipleOf', on: isNum, ok: multipleOfOk, params: multipleOfParams, msg: multipleOfMsg }
175
+ export const isArr = (v) => Array.isArray(v)
176
+ export const isObjV = (v) => typeof v === 'object' && v !== null && !Array.isArray(v)
177
+ const minItemsOk = (v, n) => v.length >= n
178
+ const minItemsParams = (n) => ({ limit: n })
179
+ const minItemsMsg = (n) => `must NOT have fewer than ${n} items`
180
+ export const minItems = {
181
+ kw: 'minItems', code: 'ATA2008', on: isArr, ok: minItemsOk,
182
+ params: minItemsParams, msg: minItemsMsg, exp: () => undefined, rank: 2,
183
+ }
184
+ export const minItemsP = { kw: 'minItems', on: isArr, ok: minItemsOk, params: minItemsParams, msg: minItemsMsg }
185
+ const maxItemsOk = (v, n) => v.length <= n
186
+ const maxItemsParams = (n) => ({ limit: n })
187
+ const maxItemsMsg = (n) => `must NOT have more than ${n} items`
188
+ export const maxItems = {
189
+ kw: 'maxItems', code: 'ATA2009', on: isArr, ok: maxItemsOk,
190
+ params: maxItemsParams, msg: maxItemsMsg, exp: () => undefined, rank: 2,
191
+ }
192
+ export const maxItemsP = { kw: 'maxItems', on: isArr, ok: maxItemsOk, params: maxItemsParams, msg: maxItemsMsg }
193
+ const minPropertiesOk = (v, n) => Object.keys(v).length >= n
194
+ const minPropertiesParams = (n) => ({ limit: n })
195
+ const minPropertiesMsg = (n) => `must NOT have fewer than ${n} properties`
196
+ export const minProperties = {
197
+ kw: 'minProperties', code: 'ATA2010', on: isObjV, ok: minPropertiesOk,
198
+ params: minPropertiesParams, msg: minPropertiesMsg, exp: () => undefined, rank: 2,
199
+ }
200
+ export const minPropertiesP = { kw: 'minProperties', on: isObjV, ok: minPropertiesOk, params: minPropertiesParams, msg: minPropertiesMsg }
201
+ const maxPropertiesOk = (v, n) => Object.keys(v).length <= n
202
+ const maxPropertiesParams = (n) => ({ limit: n })
203
+ const maxPropertiesMsg = (n) => `must NOT have more than ${n} properties`
204
+ export const maxProperties = {
205
+ kw: 'maxProperties', code: 'ATA2011', on: isObjV, ok: maxPropertiesOk,
206
+ params: maxPropertiesParams, msg: maxPropertiesMsg, exp: () => undefined, rank: 2,
207
+ }
208
+ export const maxPropertiesP = { kw: 'maxProperties', on: isObjV, ok: maxPropertiesOk, params: maxPropertiesParams, msg: maxPropertiesMsg }
209
+ export const isAny = () => true
210
+ // The guard a kernel's `on` names, by identity: the IR writes the name, and a
211
+ // minifier that renames the functions (the front end in a browser bundle)
212
+ // must not change it.
213
+ export const GUARD_NAME = /* @__PURE__ */ (() => new Map([[isStr, 'isStr'], [isNum, 'isNum'], [isArr, 'isArr'], [isObjV, 'isObjV'], [isAny, 'isAny']]))()
214
+ const constOk = (v, n) => deq(v, n)
215
+ const constParams = (n) => ({ allowedValue: n })
216
+ const constMsg = () => 'must be equal to constant'
217
+ const constExp = (n) => repr(n)
218
+ export const constK = {
219
+ kw: 'const', code: 'ATA6002', on: isAny, ok: constOk,
220
+ params: constParams, msg: constMsg, exp: constExp, rank: 2,
221
+ }
222
+ export const constP = { kw: 'const', on: isAny, ok: constOk, params: constParams, msg: constMsg }
223
+ // const and enum whose values are all primitives: equal to one exactly when
224
+ // === to it (JSON numbers have no NaN, and 0 and -0 are one value to JSON
225
+ // equality too), so a bundle of such schemas carries no deep equality.
226
+ const constPrimOk = (v, n) => v === n
227
+ export const constPrimK = {
228
+ kw: 'const', code: 'ATA6002', on: isAny, ok: constPrimOk,
229
+ params: constParams, msg: constMsg, exp: constExp, rank: 2,
230
+ }
231
+ export const constPrimP = { kw: 'const', on: isAny, ok: constPrimOk, params: constParams, msg: constMsg }
232
+ export const enumSuggest = (v, p) => {
233
+ if (typeof v !== 'string') return null
234
+ const a = p.allowedValues
235
+ if (!Array.isArray(a) || a.length === 0 || a.length > 30) return null
236
+ const best = nearest(v, a)
237
+ return best === null ? null : { text: `did you mean \`${best}\`?`, kind: 'typo' }
238
+ }
239
+ const enumOk = (v, n) => n.some((x) => deq(v, x))
240
+ const enumParams = (n) => ({ allowedValues: n })
241
+ const enumMsg = () => 'must be equal to one of the allowed values'
242
+ const enumExp = (n) => `one of [${n.map(repr).join(', ')}]`
243
+ export const enumK = {
244
+ kw: 'enum', code: 'ATA6001', on: isAny, ok: enumOk,
245
+ params: enumParams, msg: enumMsg, exp: enumExp, rank: 2,
246
+ suggest: enumSuggest,
247
+ }
248
+ export const enumP = { kw: 'enum', on: isAny, ok: enumOk, params: enumParams, msg: enumMsg }
249
+ const enumPrimOk = (v, n) => n.indexOf(v) !== -1
250
+ export const enumPrimK = {
251
+ kw: 'enum', code: 'ATA6001', on: isAny, ok: enumPrimOk,
252
+ params: enumParams, msg: enumMsg, exp: enumExp, rank: 2,
253
+ suggest: enumSuggest,
254
+ }
255
+ export const enumPrimP = { kw: 'enum', on: isAny, ok: enumPrimOk, params: enumParams, msg: enumMsg }
256
+ // A keyword whose message names what it found: fail() returns the params and
257
+ // message of the failure, or null.
258
+ export const uniqueFail = (v, n) => { if (n !== true) return null; const p = dupPair(v); return p === null ? null : { params: { i: p[0], j: p[1] }, msg: `must NOT have duplicate items (items ## ${p[1]} and ${p[0]} are identical)` } }
259
+ export const uniqueItems = {
260
+ kw: 'uniqueItems', code: 'ATA2012', on: isArr, rank: 2, exp: () => undefined,
261
+ fail: uniqueFail,
262
+ }
263
+
264
+
265
+ // anyOf and oneOf: the current engine's own collapse (lib/branch-collapse.js),
266
+ // which picks the closest branch by error count and severity.
267
+ import { __ataCollapse } from './collapse.mjs'
268
+ export const collapse = __ataCollapse
269
+ export const not = { code: 'ATA4005', keyword: 'not', msg: 'must NOT be valid', rank: 3 }
270
+ export const branchRank = 3
271
+
272
+ // unevaluatedProperties false: one error per property; unevaluatedItems
273
+ // false: one error naming the first unevaluated index, worded as a length.
274
+ export const unevaluatedDetail = (received, v, p) => `unevaluated property "${p.unevaluatedProperty}"`
275
+ export const unevaluatedProperties = { code: 'ATA7003', msg: 'must NOT have unevaluated properties', rank: 0, detail: unevaluatedDetail }
276
+ export const unevaluatedItems = { code: 'ATA7004', rank: 0 }
277
+
278
+ // The false schema.
279
+ export const falseSchema = { code: 'ATA4005', keyword: 'not', msg: 'boolean schema is false', rank: 3 }
280
+ // contains, minContains, maxContains: one error, keyword contains.
281
+ export const contains = { code: 'ATA7007', rank: 2 }
282
+
283
+
284
+ // Keywords that carry no constraint.
285
+ export const ANNOTATIONS = new Set(['$schema', '$id', '$comment', 'title', 'description', 'default', 'examples', 'deprecated', 'readOnly', 'writeOnly'])
@@ -0,0 +1,28 @@
1
+ // The leaf keywords as the IR's front end and closures read them
2
+ // (lib/ir/front.mjs), with the format keyword, which reads the current
3
+ // engine's CommonJS files: its run-time checks, codes and hints. This module
4
+ // is not part of ata-validator/kernels; a module written by the IR imports
5
+ // none of it.
6
+ import { minLength, maxLength, minimum, maximum, exclusiveMinimum, exclusiveMaximum, multipleOf, pattern, minItems, maxItems, minProperties, maxProperties, constK, enumK, uniqueItems, formatK } from './kernels.mjs'
7
+
8
+ // Formats are the current engine's own: its run-time checks (lib/formats.js
9
+ // formatChecks), its codes (lib/error-codes.js codeFor) and its hints
10
+ // (lib/suggestions.js; generated code carries only its format's hint, see lib/ir/emit.mjs). The generated code is its code generator's
11
+ // (FORMAT_CODEGEN, written in by ir/emit.mjs). A format it does not know is
12
+ // not checked, as there.
13
+ import formats from '../formats.js'
14
+ import errorCodes from '../error-codes.js'
15
+ import suggestions from '../suggestions.js'
16
+ const formatCheck = (n) => { const fc = formats.formatChecks(); return Object.prototype.hasOwnProperty.call(fc, n) ? fc[n] : null }
17
+ // The closures call it; a written module carries only its format's hint (lib/ir/emit.mjs).
18
+ export function formatSuggest (v, p) { return suggestions.suggestFormat(p.format, v) }
19
+ export const format = {
20
+ code: (n) => errorCodes.codeFor('format', n), on: formatK.on, ok: formatK.ok,
21
+ // null: a format the current engine does not know, which it does not check.
22
+ prepare: (n) => formatCheck(n),
23
+ params: formatK.params, msg: formatK.msg, exp: formatK.exp, rank: formatK.rank,
24
+ detail: formatK.detail,
25
+ suggest: formatSuggest,
26
+ }
27
+
28
+ export const LEAVES = { minLength, maxLength, minimum, maximum, exclusiveMinimum, exclusiveMaximum, multipleOf, pattern, format, minItems, maxItems, minProperties, maxProperties, const: constK, enum: enumK, uniqueItems }
@@ -0,0 +1,25 @@
1
+ // The errors into schema order, a module of its own so a page that never
2
+ // sorts (most forms) does not carry it, and ata-validator/plan can load it
3
+ // when a plan asks.
4
+ import { rankFor } from './rank.mjs'
5
+
6
+ // The errors in schema declaration order, as the current engine orders them:
7
+ // by the rank of each error's schemaPath in the root schema (rankFor of
8
+ // lib/schema-order.js, copied in ./rank.mjs; lib/aot-order.js sorts by it too), stable, and
9
+ // a collapsed error's branch errors the same way, in a copy.
10
+ const after = (a, b) => { const m = Math.min(a.length, b.length); for (let i = 0; i < m; i++) if (a[i] !== b[i]) return a[i] > b[i]; return a.length > b.length }
11
+ export function ordered (root, list) {
12
+ const n = list.length
13
+ if (n === 1 && !Array.isArray(list[0].branchErrors)) return list
14
+ const out = new Array(n); const keys = new Array(n)
15
+ for (let i = 0; i < n; i++) {
16
+ let e = list[i]
17
+ if (Array.isArray(e.branchErrors) && e.branchErrors.length) { const b = ordered(root, e.branchErrors); if (b !== e.branchErrors) e = Object.assign({}, e, { branchErrors: b }) }
18
+ const k = rankFor(root, e.schemaPath) || []
19
+ let j = i - 1
20
+ while (j >= 0 && after(keys[j], k)) { keys[j + 1] = keys[j]; out[j + 1] = out[j]; j-- }
21
+ keys[j + 1] = k; out[j + 1] = e
22
+ }
23
+ for (let i = 0; i < n; i++) if (out[i] !== list[i]) return out
24
+ return list
25
+ }
@@ -0,0 +1,17 @@
1
+ // ata-validator/plan-rich: fromPlan for plans with ata's rich errors (code,
2
+ // expected, received, detail, suggestion), and with plain ones. A separate
3
+ // entry so a page reading plain plans carries none of this: the descriptors
4
+ // that carry the rich fields, and the format hints the module writer writes
5
+ // into its modules (lib/ir/emit-deps.mjs).
6
+ import { tableRich } from './table.mjs'
7
+ import { fromPlan as fromAnyPlan, PLAN_VERSION } from './plan.mjs'
8
+ import { type, required, additionalProperties, enumK, constK, formatK, minLength, maxLength, minimum, maximum, exclusiveMinimum, exclusiveMaximum, multipleOf, pattern, minItems, maxItems, minProperties, maxProperties } from './kernels.mjs'
9
+ import { FORMAT_HINTS } from '../ir/emit-deps.mjs'
10
+
11
+ const RICH = { tableRich, type, required, additionalProperties, enumK, constK, formatK, minLength, maxLength, minimum, maximum, exclusiveMinimum, exclusiveMaximum, multipleOf, pattern, minItems, maxItems, minProperties, maxProperties }
12
+ // A format's hint, as the module writer builds it from the same source.
13
+ const hint = (name) => { const h = Object.prototype.hasOwnProperty.call(FORMAT_HINTS, name) ? FORMAT_HINTS[name] : null; return h === null ? undefined : (v) => { const t = h(v); return t ? { text: t, kind: 'format' } : null } }
14
+
15
+ export { PLAN_VERSION }
16
+ // The validator for a plan, rich or plain.
17
+ export const fromPlan = (plan) => fromAnyPlan(plan, { RICH, hint, tableRich })
@@ -0,0 +1,9 @@
1
+ /** A plan written by tablePlan in ata-validator/ir. */
2
+ export interface Plan { v: 1; ata?: string; rich: boolean; P: unknown[]; F: unknown[]; rel: boolean; sk: unknown; uses: string[] }
3
+ export interface PlanValidator {
4
+ validate(data: unknown): { valid: true; data: unknown; errors: readonly [] } | { valid: false; errors: Array<Record<string, unknown>> }
5
+ isValid(data: unknown): boolean
6
+ }
7
+ export declare const PLAN_VERSION: 1
8
+ /** The validator for a plan, once the kernels it names are loaded. */
9
+ export declare function fromPlan(plan: Plan): Promise<PlanValidator>
@@ -0,0 +1,128 @@
1
+ // ata-validator/plan: validators for schemas that arrive at run time, as
2
+ // plans a server writes with tablePlan (ata-validator/ir) instead of schemas.
3
+ // The heavy work (references, dialects, normalization) is done where the plan
4
+ // is written; the page carries the table interpreter and, loaded when a plan
5
+ // names them, the format checks, the error order and the rich error shape.
6
+ // The verdicts, errors and filled defaults are those of the server's
7
+ // validator for the same schema. This entry reads plans with plain errors
8
+ // (richErrors: false); ata-validator/plan-rich reads both, and only a page
9
+ // that imports it carries the rich error shape.
10
+ //
11
+ // const v = await fromPlan(await (await fetch('/plans/signup.json')).json())
12
+ // v.validate(data)
13
+ //
14
+ // A plan names kernels in fixed places only (each step's handler, a leaf's
15
+ // descriptor, check and hint); every other value is the schema's own data
16
+ // and is used as data.
17
+ import { tablePlain, tableFill, tType, tProps, tReq, tClosed, tAddl, tLeaf, tItems, tTuple, tAll, tRef, tNot, tIf, tDepReq, tFalse } from './table.mjs'
18
+ import { typeP, requiredP, additionalP, enumP, constP, formatP, uniqueItems, minLengthP, maxLengthP, minimumP, maximumP, exclusiveMinimumP, exclusiveMaximumP, multipleOfP, patternP, minItemsP, maxItemsP, minPropertiesP, maxPropertiesP } from './kernels.mjs'
19
+
20
+ import ATA_VERSION from '../version.js'
21
+
22
+ const CORE = { tablePlain, tableFill, tType, tProps, tReq, tClosed, tAddl, tLeaf, tItems, tTuple, tAll, tRef, tNot, tIf, tDepReq, tFalse, typeP, requiredP, additionalP, enumP, constP, formatP, uniqueItems, minLengthP, maxLengthP, minimumP, maximumP, exclusiveMinimumP, exclusiveMaximumP, multipleOfP, patternP, minItemsP, maxItemsP, minPropertiesP, maxPropertiesP }
23
+ export const PLAN_VERSION = 1
24
+
25
+ const warned = new Set()
26
+ const fail = (why) => { throw new Error('ata-validator/plan: ' + why) }
27
+ // Names an object literal would reorder or treat specially; a plan never
28
+ // carries them (lib/ir/table.mjs refuses such schemas).
29
+ const safeName = (k) => k !== '__proto__' && !/^(0|[1-9][0-9]*)$/.test(k)
30
+
31
+ const HANDLERS = new Set(['tType', 'tProps', 'tReq', 'tClosed', 'tAddl', 'tLeaf', 'tItems', 'tTuple', 'tAll', 'tRef', 'tNot', 'tIf', 'tDepReq', 'tFalse'])
32
+ const isInt = (n) => Number.isInteger(n) && n >= 0
33
+ const isStrings = (a) => Array.isArray(a) && a.every((x) => typeof x === 'string')
34
+
35
+ // Every step checked as it is read, so a plan that is not one the writer
36
+ // makes is refused here, by what is wrong with it, and never runs.
37
+ function decode (P, K, hints) {
38
+ if (!Array.isArray(P) || P.length === 0) fail('a plan has at least its root node')
39
+ const kernel = (n) => typeof n === 'string' && K[n] !== undefined ? K[n] : fail(`unknown kernel ${JSON.stringify(n)}`)
40
+ const descriptor = (n) => { const D = kernel(n); return D !== null && typeof D === 'object' && typeof D.kw === 'string' && (typeof D.ok === 'function' || typeof D.fail === 'function') ? D : fail(`${JSON.stringify(n)} is not a keyword descriptor`) }
41
+ const ref = (r) => typeof r === 'number' ? (isInt(r) && r < P.length ? r : fail('a node index out of range')) : node(r)
42
+ // The node's properties step, for tReq, tClosed and tAddl to name.
43
+ const propsStep = (N, j) => j === -1 || (isInt(j) && j < N.length && Array.isArray(N[j]) && N[j][0] === 'tProps') ? j : fail('a step names a properties step that is not one')
44
+ function node (N) {
45
+ if (!Array.isArray(N)) fail('a node is not a list of steps')
46
+ return N.map((t) => step(t, N))
47
+ }
48
+ function step (t, N) {
49
+ if (!Array.isArray(t) || !HANDLERS.has(t[0])) fail(`a step whose handler is ${JSON.stringify(Array.isArray(t) ? t[0] : t)}`)
50
+ const out = t.slice(); out[0] = kernel(t[0])
51
+ switch (t[0]) {
52
+ // A list may hold what draft 3 allowed there; the check only reads it.
53
+ case 'tType': if (!(typeof t[1] === 'string' || Array.isArray(t[1]))) fail('tType takes a type or a list'); break
54
+ case 'tProps': {
55
+ if (t[1] === null || typeof t[1] !== 'object' || Array.isArray(t[1])) fail('tProps takes an object')
56
+ const o = {}
57
+ for (const k of Object.keys(t[1])) { if (!safeName(k)) fail(`a property name ${JSON.stringify(k)} a plan cannot carry`); o[k] = ref(t[1][k]) }
58
+ out[1] = o; break
59
+ }
60
+ case 'tReq':
61
+ if (isStrings(t[1])) break
62
+ if (!(isInt(t[1]) && t[1] < 2 ** 31)) fail('tReq takes names or a mask')
63
+ if (propsStep(N, t[2]) === -1) fail('a required mask with no properties step'); break
64
+ case 'tClosed': propsStep(N, t[1]); break
65
+ case 'tAddl': out[1] = ref(t[1]); propsStep(N, t[2]); break
66
+ case 'tItems': out[1] = ref(t[1]); if (!isInt(t[2])) fail('tItems takes a first index'); break
67
+ case 'tRef': case 'tNot': out[1] = ref(t[1]); break
68
+ case 'tTuple': case 'tAll': if (!Array.isArray(t[1])) fail(t[0] + ' takes a list of nodes'); out[1] = t[1].map(ref); break
69
+ case 'tIf': out[1] = ref(t[1]); out[2] = t[2] === -1 ? -1 : ref(t[2]); out[3] = t[3] === -1 ? -1 : ref(t[3]); break
70
+ case 'tDepReq':
71
+ if (!Array.isArray(t[1]) || t[1].length % 2 !== 0) fail('tDepReq takes names and lists of names')
72
+ for (let j = 0; j < t[1].length; j += 2) if (typeof t[1][j] !== 'string' || !isStrings(t[1][j + 1])) fail('tDepReq takes names and lists of names')
73
+ break
74
+ case 'tFalse': break
75
+ case 'tLeaf':
76
+ out[1] = descriptor(t[1])
77
+ if (t.length === 6) {
78
+ if (out[1].kw !== 'format' || typeof t[3] !== 'string' || !/^fmt_\w+$/.test(t[3]) || typeof t[4] !== 'string') fail('a format step is not one')
79
+ out[3] = kernel(t[3]); out[5] = t[5] == null ? undefined : hints(t[5])
80
+ } else if (t.length !== 3) fail('a leaf step has a descriptor and its argument')
81
+ break
82
+ }
83
+ return out
84
+ }
85
+ return P.map(node)
86
+ }
87
+
88
+ // The defaults table: [defaults [k, v, ...], properties [k, f, ...],
89
+ // prefixItems [f or -1, ...], items f or -1, dependentSchemas [k, f, ...],
90
+ // in place [f, ...]], every f an index into it.
91
+ function checkFill (F) {
92
+ if (!Array.isArray(F)) fail('the defaults table is not a list')
93
+ const f = (i) => i === -1 || (isInt(i) && i < F.length) || fail('a defaults index out of range')
94
+ const pairs = (a, idx) => { if (!Array.isArray(a) || a.length % 2 !== 0) fail('a defaults entry is not pairs'); for (let j = 0; j < a.length; j += 2) { if (typeof a[j] !== 'string') fail('a defaults name is not a string'); if (idx) f(a[j + 1]) } }
95
+ for (const e of F) {
96
+ if (!Array.isArray(e) || e.length !== 6) fail('a defaults entry has six parts')
97
+ pairs(e[0], false); pairs(e[1], true)
98
+ if (!Array.isArray(e[2])) fail('a defaults entry is not well formed'); e[2].forEach(f)
99
+ f(e[3]); pairs(e[4], true)
100
+ if (!Array.isArray(e[5])) fail('a defaults entry is not well formed'); e[5].forEach(f)
101
+ }
102
+ return F
103
+ }
104
+
105
+ // The validator for a plan, once the kernels it names are loaded. `rich`:
106
+ // what ata-validator/plan-rich passes in.
107
+ export async function fromPlan (plan, rich = null) {
108
+ if (plan === null || typeof plan !== 'object' || plan.v !== PLAN_VERSION) fail(`a plan of version ${PLAN_VERSION} is expected`)
109
+ const uses = Array.isArray(plan.uses) ? plan.uses : fail('the plan does not list its kernels')
110
+ // The page and the server decide alike when both run the same ata-validator.
111
+ if (typeof plan.ata === 'string' && plan.ata !== ATA_VERSION && !warned.has(plan.ata)) {
112
+ warned.add(plan.ata)
113
+ console.warn(`ata-validator/plan: this plan was written by ata-validator ${plan.ata} and is read by ${ATA_VERSION}; a page and a server agree on every document only on the same version.`)
114
+ }
115
+ const K = Object.assign(Object.create(null), CORE)
116
+ const loads = []
117
+ if (plan.rich) { if (rich === null) fail('this plan has rich errors: read it with ata-validator/plan-rich'); Object.assign(K, rich.RICH) }
118
+ if (uses.some((n) => /^fmt_\w+_s$/.test(n))) loads.push(import('./formats-small.mjs').then((m) => Object.assign(K, m)))
119
+ if (uses.some((n) => /^fmt_\w+$/.test(n) && !/_s$/.test(n))) loads.push(import('./formats.mjs').then((m) => Object.assign(K, m)))
120
+ let order
121
+ if (plan.sk) loads.push(import('./order.mjs').then((m) => { order = (e) => m.ordered(plan.sk, e) }))
122
+ await Promise.all(loads)
123
+ for (const n of uses) if (n !== 'ordered' && K[n] === undefined) fail(`this plan needs "${n}", which this version of ata-validator does not have`)
124
+ const P = decode(plan.P, K, (name) => rich ? rich.hint(name) : undefined)
125
+ const F = checkFill(plan.F === undefined ? [] : plan.F)
126
+ const fill = F.length ? tableFill : null
127
+ return plan.rich ? rich.tableRich(P, F, fill, plan.rel, order) : tablePlain(P, F, fill, order)
128
+ }