ata-validator 1.7.0 → 1.7.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.
- package/CHANGELOG.md +29 -0
- package/README.md +13 -3
- package/index.d.ts +3 -3
- package/index.js +141 -47
- package/lib/aot.js +1 -1
- package/lib/interpreter.js +251 -45
- package/lib/js-compiler.js +99 -44
- package/lib/plan-compiler.js +969 -0
- package/lib/version.js +1 -1
- package/package.json +8 -8
package/lib/js-compiler.js
CHANGED
|
@@ -1,5 +1,77 @@
|
|
|
1
1
|
'use strict'
|
|
2
2
|
|
|
3
|
+
// uniqueItems helpers for the boolean generator, hoisted once per compiled
|
|
4
|
+
// function (and carried into standalone output through the preamble).
|
|
5
|
+
// _deq is JSON equality: numbers by value, objects by own keys regardless of
|
|
6
|
+
// order, arrays positionally. _uq answers whether every item is distinct.
|
|
7
|
+
// Structural JSON equality, hoisted once per compiled function. `const` and
|
|
8
|
+
// `enum` compare instances by value: two objects with the same members are
|
|
9
|
+
// equal whatever order their keys were written in. Comparing serialized
|
|
10
|
+
// forms answers a different question and costs a string per call.
|
|
11
|
+
const DEQ_HELPER = "function _deq(a,b){if(a===b)return true;if(a===null||b===null||typeof a!=='object'||typeof b!=='object')return false;var aa=Array.isArray(a);if(aa!==Array.isArray(b))return false;var i;if(aa){if(a.length!==b.length)return false;for(i=0;i<a.length;i++)if(!_deq(a[i],b[i]))return false;return true}var ka=Object.keys(a);if(ka.length!==Object.keys(b).length)return false;for(i=0;i<ka.length;i++){var k=ka[i];if(!Object.prototype.hasOwnProperty.call(b,k)||!_deq(a[k],b[k]))return false}return true}"
|
|
12
|
+
|
|
13
|
+
// uniqueItems helpers for the boolean generator. `_uq` answers whether every
|
|
14
|
+
// item is distinct: short arrays compare pairwise and allocate nothing,
|
|
15
|
+
// longer ones use a Set of primitives and fall back to sorted-key canonical
|
|
16
|
+
// strings only once an object actually appears.
|
|
17
|
+
const UQ_HELPERS = "function _cn(x){if(x===null||typeof x!=='object')return typeof x+':'+x;if(Array.isArray(x))return'['+x.map(_cn).join(',')+']';return'{'+Object.keys(x).sort().map(function(k){return JSON.stringify(k)+':'+_cn(x[k])}).join(',')+'}'}" +
|
|
18
|
+
"function _uq(a){var n=a.length,i,k;if(n<2)return true;if(n<=12){for(i=1;i<n;i++)for(k=0;k<i;k++)if(_deq(a[i],a[k]))return false;return true}var s=new Set();for(i=0;i<n;i++){var x=a[i];if(x!==null&&typeof x==='object')break;if(s.has(x))return false;s.add(x)}if(i===n)return true;s=new Set();for(i=0;i<n;i++){var c=_cn(a[i]);if(s.has(c))return false;s.add(c)}return true}"
|
|
19
|
+
|
|
20
|
+
// Where a generator hoists a declaration so it runs once per compiled
|
|
21
|
+
// function rather than once per call. The boolean generator has a preamble
|
|
22
|
+
// for exactly this; the error and combined generators emit `helperCode` into
|
|
23
|
+
// their function source. The boolean generator's `helperCode` is only
|
|
24
|
+
// scanned for regex entries and must not be used here.
|
|
25
|
+
function hoistOnce(ctx, key, code) {
|
|
26
|
+
if (ctx[key]) return
|
|
27
|
+
ctx[key] = true
|
|
28
|
+
if (ctx.preamble) ctx.preamble.push(code)
|
|
29
|
+
else if (ctx.helperCode) ctx.helperCode.push(code)
|
|
30
|
+
}
|
|
31
|
+
|
|
32
|
+
function emitDeq(ctx) {
|
|
33
|
+
hoistOnce(ctx, '_deqHoisted', DEQ_HELPER)
|
|
34
|
+
return '_deq'
|
|
35
|
+
}
|
|
36
|
+
|
|
37
|
+
// `_uq` compares items with `_deq`, so it can never be hoisted alone.
|
|
38
|
+
function emitUq(ctx) {
|
|
39
|
+
emitDeq(ctx)
|
|
40
|
+
hoistOnce(ctx, '_uqHoisted', UQ_HELPERS)
|
|
41
|
+
return '_uq'
|
|
42
|
+
}
|
|
43
|
+
|
|
44
|
+
// The compared value, parsed once at compile time. `JSON.parse` rather than a
|
|
45
|
+
// JS object literal so a `__proto__` member stays an ordinary property.
|
|
46
|
+
function emitConstant(ctx, value) {
|
|
47
|
+
if (!ctx._constPool) ctx._constPool = new Map()
|
|
48
|
+
const json = JSON.stringify(value)
|
|
49
|
+
let name = ctx._constPool.get(json)
|
|
50
|
+
if (name === undefined) {
|
|
51
|
+
name = '_kv' + ctx._constPool.size
|
|
52
|
+
ctx._constPool.set(json, name)
|
|
53
|
+
const decl = `const ${name}=JSON.parse(${JSON.stringify(json)})`
|
|
54
|
+
if (ctx.preamble) ctx.preamble.push(decl)
|
|
55
|
+
else if (ctx.helperCode) ctx.helperCode.push(decl)
|
|
56
|
+
}
|
|
57
|
+
return name
|
|
58
|
+
}
|
|
59
|
+
|
|
60
|
+
// One `enum` test: primitives compare with `===`, which needs no call, and
|
|
61
|
+
// object members go through structural equality.
|
|
62
|
+
function enumCondition(ctx, vals, v) {
|
|
63
|
+
const parts = []
|
|
64
|
+
const prims = vals.filter((x) => x === null || typeof x !== 'object')
|
|
65
|
+
const objs = vals.filter((x) => x !== null && typeof x === 'object')
|
|
66
|
+
if (prims.length) parts.push(prims.map((x) => `${v}===${JSON.stringify(x)}`).join('||'))
|
|
67
|
+
if (objs.length) {
|
|
68
|
+
const deq = emitDeq(ctx)
|
|
69
|
+
parts.push(objs.map((o) => `${deq}(${v},${emitConstant(ctx, o)})`).join('||'))
|
|
70
|
+
}
|
|
71
|
+
return parts.filter(Boolean).join('||') || 'false'
|
|
72
|
+
}
|
|
73
|
+
|
|
74
|
+
|
|
3
75
|
const { codeFor } = require('./error-codes')
|
|
4
76
|
const { compileSafe, patternIsSafe } = require('./safe-regex')
|
|
5
77
|
|
|
@@ -53,7 +125,8 @@ function buildErrorLiteral (opts) {
|
|
|
53
125
|
function _cpLen(s) {
|
|
54
126
|
const len = s.length;
|
|
55
127
|
for (let i = 0; i < len; i++) {
|
|
56
|
-
|
|
128
|
+
// Wraparound test: one compare classifies a high surrogate.
|
|
129
|
+
if (((s.charCodeAt(i) - 0xD800) >>> 0) < 0x400) {
|
|
57
130
|
// Found a high surrogate — count code points the slow way
|
|
58
131
|
let n = 0; for (const _ of s) n++; return n;
|
|
59
132
|
}
|
|
@@ -354,7 +427,7 @@ function compileToJS(schema, defs, schemaMap) {
|
|
|
354
427
|
}
|
|
355
428
|
if (schema.maxLength !== undefined) {
|
|
356
429
|
const max = schema.maxLength
|
|
357
|
-
checks.push((d) => typeof d !== 'string' || d.length <= max || _cpLen(d) <= max)
|
|
430
|
+
checks.push((d) => typeof d !== 'string' || d.length <= max || (d.length <= 2 * max + 1 && _cpLen(d) <= max))
|
|
358
431
|
}
|
|
359
432
|
if (schema.pattern) {
|
|
360
433
|
try {
|
|
@@ -1706,13 +1779,7 @@ function genCode(schema, v, lines, ctx, knownType) {
|
|
|
1706
1779
|
|
|
1707
1780
|
// enum
|
|
1708
1781
|
if (schema.enum) {
|
|
1709
|
-
|
|
1710
|
-
const primitives = vals.filter(v => v === null || typeof v !== 'object')
|
|
1711
|
-
const objects = vals.filter(v => v !== null && typeof v === 'object')
|
|
1712
|
-
const primChecks = primitives.map(p => `${v}===${JSON.stringify(p)}`).join('||')
|
|
1713
|
-
const objChecks = objects.map(o => `JSON.stringify(${v})===${JSON.stringify(JSON.stringify(o))}`).join('||')
|
|
1714
|
-
const allChecks = [primChecks, objChecks].filter(Boolean).join('||')
|
|
1715
|
-
lines.push(`if(!(${allChecks || 'false'}))return false`)
|
|
1782
|
+
lines.push(`if(!(${enumCondition(ctx, schema.enum, v)}))return false`)
|
|
1716
1783
|
}
|
|
1717
1784
|
|
|
1718
1785
|
// const
|
|
@@ -1721,12 +1788,9 @@ function genCode(schema, v, lines, ctx, knownType) {
|
|
|
1721
1788
|
if (cv === null || typeof cv !== 'object') {
|
|
1722
1789
|
lines.push(`if(${v}!==${JSON.stringify(cv)})return false`)
|
|
1723
1790
|
} else {
|
|
1724
|
-
//
|
|
1725
|
-
//
|
|
1726
|
-
|
|
1727
|
-
const canonFn = `_cnB${ci}`
|
|
1728
|
-
const expected = _canonical(cv)
|
|
1729
|
-
lines.push(`{const ${canonFn}=function(x){if(x===null||typeof x!=='object')return JSON.stringify(x);if(Array.isArray(x))return'['+x.map(${canonFn}).join(',')+']';return'{'+Object.keys(x).sort().map(function(k){return JSON.stringify(k)+':'+${canonFn}(x[k])}).join(',')+'}'};if(${canonFn}(${v})!==${JSON.stringify(expected)})return false}`)
|
|
1791
|
+
// Structural comparison against a value parsed once, so the hot path
|
|
1792
|
+
// neither builds a closure nor serializes the instance.
|
|
1793
|
+
lines.push(`if(!${emitDeq(ctx)}(${v},${emitConstant(ctx, cv)}))return false`)
|
|
1730
1794
|
}
|
|
1731
1795
|
}
|
|
1732
1796
|
|
|
@@ -1881,11 +1945,22 @@ function genCode(schema, v, lines, ctx, knownType) {
|
|
|
1881
1945
|
const maxItems = schema.maxItems
|
|
1882
1946
|
// Small primitive arrays (maxItems <= 16): nested loop is 6x faster than Set
|
|
1883
1947
|
// No allocation, no hash computation — just direct === comparison
|
|
1884
|
-
|
|
1885
|
-
|
|
1886
|
-
|
|
1887
|
-
|
|
1888
|
-
|
|
1948
|
+
let inner
|
|
1949
|
+
if (isPrimItems && maxItems && maxItems <= 16) {
|
|
1950
|
+
inner = `for(let _i=1;_i<${v}.length;_i++){for(let _k=0;_k<_i;_k++){if(${v}[_i]===${v}[_k])return false}}`
|
|
1951
|
+
} else if (isPrimItems) {
|
|
1952
|
+
inner = `const _s${si}=new Set();for(let _i=0;_i<${v}.length;_i++){if(_s${si}.has(${v}[_i]))return false;_s${si}.add(${v}[_i])}`
|
|
1953
|
+
} else if (ctx.preamble) {
|
|
1954
|
+
// Unknown item types: one hoisted helper per compiled function. Short
|
|
1955
|
+
// arrays compare pairwise with a structural equality that ignores key
|
|
1956
|
+
// order and allocates nothing; longer ones fall back to a Set of
|
|
1957
|
+
// primitives, or to sorted-key canonical strings once an object shows
|
|
1958
|
+
// up. The previous form built the canonical closure and a Set on
|
|
1959
|
+
// every call, even for [1, 2, 3].
|
|
1960
|
+
inner = `if(!${emitUq(ctx)}(${v}))return false`
|
|
1961
|
+
} else {
|
|
1962
|
+
inner = `const _cn${si}=function(x){if(x===null||typeof x!=='object')return typeof x+':'+x;if(Array.isArray(x))return'['+x.map(_cn${si}).join(',')+']';return'{'+Object.keys(x).sort().map(function(k){return JSON.stringify(k)+':'+_cn${si}(x[k])}).join(',')+'}'};const _s${si}=new Set();for(let _i=0;_i<${v}.length;_i++){const _k=_cn${si}(${v}[_i]);if(_s${si}.has(_k))return false;_s${si}.add(_k)}`
|
|
1963
|
+
}
|
|
1889
1964
|
lines.push(isArr ? `{${inner}}` : `if(Array.isArray(${v})){${inner}}`)
|
|
1890
1965
|
}
|
|
1891
1966
|
|
|
@@ -3187,13 +3262,7 @@ function genCodeE(schema, v, pathExpr, lines, ctx, schemaPrefix) {
|
|
|
3187
3262
|
|
|
3188
3263
|
// enum
|
|
3189
3264
|
if (schema.enum) {
|
|
3190
|
-
|
|
3191
|
-
const primitives = vals.filter(v => v === null || typeof v !== 'object')
|
|
3192
|
-
const objects = vals.filter(v => v !== null && typeof v === 'object')
|
|
3193
|
-
const primChecks = primitives.map(p => `${v}===${JSON.stringify(p)}`).join('||')
|
|
3194
|
-
const objChecks = objects.map(o => `JSON.stringify(${v})===${JSON.stringify(JSON.stringify(o))}`).join('||')
|
|
3195
|
-
const allChecks = [primChecks, objChecks].filter(Boolean).join('||')
|
|
3196
|
-
lines.push(`if(!(${allChecks || 'false'})){${fail('enum', 'enum', `{allowedValues:${JSON.stringify(schema.enum)}}`, "'must be equal to one of the allowed values'")}}`)
|
|
3265
|
+
lines.push(`if(!(${enumCondition(ctx, schema.enum, v)})){${fail('enum', 'enum', `{allowedValues:${JSON.stringify(schema.enum)}}`, "'must be equal to one of the allowed values'")}}`)
|
|
3197
3266
|
}
|
|
3198
3267
|
|
|
3199
3268
|
// const — use canonical (sorted-key) comparison for objects
|
|
@@ -3202,12 +3271,7 @@ function genCodeE(schema, v, pathExpr, lines, ctx, schemaPrefix) {
|
|
|
3202
3271
|
if (cv === null || typeof cv !== 'object') {
|
|
3203
3272
|
lines.push(`if(${v}!==${JSON.stringify(cv)}){${fail('const', 'const', `{allowedValue:${JSON.stringify(schema.const)}}`, "'must be equal to constant'")}}`)
|
|
3204
3273
|
} else {
|
|
3205
|
-
|
|
3206
|
-
const ci = ctx.varCounter++
|
|
3207
|
-
const canonFn = `_cnE${ci}`
|
|
3208
|
-
ctx.helperCode.push(`const ${canonFn}=function(x){if(x===null||typeof x!=='object')return JSON.stringify(x);if(Array.isArray(x))return'['+x.map(${canonFn}).join(',')+']';return'{'+Object.keys(x).sort().map(function(k){return JSON.stringify(k)+':'+${canonFn}(x[k])}).join(',')+'}'};`)
|
|
3209
|
-
const expected = canonFn + '(JSON.parse(' + JSON.stringify(JSON.stringify(cv)) + '))'
|
|
3210
|
-
lines.push(`if(${canonFn}(${v})!==${expected}){${fail('const', 'const', `{allowedValue:JSON.parse(${JSON.stringify(JSON.stringify(schema.const))})}`, "'must be equal to constant'")}}`)
|
|
3274
|
+
lines.push(`if(!${emitDeq(ctx)}(${v},${emitConstant(ctx, cv)})){${fail('const', 'const', `{allowedValue:JSON.parse(${JSON.stringify(JSON.stringify(schema.const))})}`, "'must be equal to constant'")}}`)
|
|
3211
3275
|
}
|
|
3212
3276
|
}
|
|
3213
3277
|
|
|
@@ -3797,13 +3861,7 @@ function genCodeC(schema, v, pathExpr, lines, ctx, schemaPrefix) {
|
|
|
3797
3861
|
|
|
3798
3862
|
// enum
|
|
3799
3863
|
if (schema.enum) {
|
|
3800
|
-
|
|
3801
|
-
const primitives = vals.filter(v => v === null || typeof v !== 'object')
|
|
3802
|
-
const objects = vals.filter(v => v !== null && typeof v === 'object')
|
|
3803
|
-
const primChecks = primitives.map(p => `${v}===${JSON.stringify(p)}`).join('||')
|
|
3804
|
-
const objChecks = objects.map(o => `JSON.stringify(${v})===${JSON.stringify(JSON.stringify(o))}`).join('||')
|
|
3805
|
-
const allChecks = [primChecks, objChecks].filter(Boolean).join('||')
|
|
3806
|
-
lines.push(`if(!(${allChecks || 'false'})){${fail('enum', 'enum', `{allowedValues:${JSON.stringify(schema.enum)}}`, "'must be equal to one of the allowed values'")}}`)
|
|
3864
|
+
lines.push(`if(!(${enumCondition(ctx, schema.enum, v)})){${fail('enum', 'enum', `{allowedValues:${JSON.stringify(schema.enum)}}`, "'must be equal to one of the allowed values'")}}`)
|
|
3807
3865
|
}
|
|
3808
3866
|
|
|
3809
3867
|
// const
|
|
@@ -3812,10 +3870,7 @@ function genCodeC(schema, v, pathExpr, lines, ctx, schemaPrefix) {
|
|
|
3812
3870
|
if (cv === null || typeof cv !== 'object') {
|
|
3813
3871
|
lines.push(`if(${v}!==${JSON.stringify(cv)}){${fail('const', 'const', `{allowedValue:${JSON.stringify(schema.const)}}`, "'must be equal to constant'")}}`)
|
|
3814
3872
|
} else {
|
|
3815
|
-
const
|
|
3816
|
-
const canonFn = `_cn${ci}`
|
|
3817
|
-
ctx.helperCode.push(`const ${canonFn}=function(x){if(x===null||typeof x!=='object')return JSON.stringify(x);if(Array.isArray(x))return'['+x.map(${canonFn}).join(',')+']';return'{'+Object.keys(x).sort().map(function(k){return JSON.stringify(k)+':'+${canonFn}(x[k])}).join(',')+'}'};`)
|
|
3818
|
-
lines.push(`if(${canonFn}(${v})!==${canonFn}(JSON.parse(${JSON.stringify(JSON.stringify(cv))}))){${fail('const', 'const', `{allowedValue:JSON.parse(${JSON.stringify(JSON.stringify(schema.const))})}`, "'must be equal to constant'")}}`)
|
|
3873
|
+
lines.push(`if(!${emitDeq(ctx)}(${v},${emitConstant(ctx, cv)})){${fail('const', 'const', `{allowedValue:JSON.parse(${JSON.stringify(JSON.stringify(schema.const))})}`, "'must be equal to constant'")}}`)
|
|
3819
3874
|
}
|
|
3820
3875
|
}
|
|
3821
3876
|
|