ata-validator 1.7.1 → 1.7.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.
- package/CHANGELOG.md +21 -0
- package/README.md +24 -6
- package/index.js +20 -2
- package/lib/interpreter.js +8 -6
- package/lib/js-compiler.js +96 -42
- package/lib/native-load.js +4 -1
- package/lib/plan-compiler.js +651 -227
- package/lib/version.js +1 -1
- package/package.json +9 -8
package/CHANGELOG.md
CHANGED
|
@@ -2,6 +2,27 @@
|
|
|
2
2
|
|
|
3
3
|
All notable changes to ata-validator are documented here. The format follows [Keep a Changelog](https://keepachangelog.com/), and this project adheres to semantic versioning.
|
|
4
4
|
|
|
5
|
+
## 1.7.3 - 2026-08-26
|
|
6
|
+
|
|
7
|
+
### Added
|
|
8
|
+
|
|
9
|
+
- A prebuilt native engine for Intel macOS again, `@ata-validator/native-darwin-x64`. The target was dropped in 0.13.4 because GitHub's x64 macOS runners had stopped being reliably available; `macos-15-intel` brought them back, so the matrix is now seven targets: darwin arm64 and x64, linux x64 and arm64 on both glibc and musl, and win32 x64. Intel Macs went to the pure-JS engine in the meantime, which validates identically but leaves the buffer APIs unavailable, so this only changes speed and reach, not answers.
|
|
10
|
+
|
|
11
|
+
## 1.7.2 - 2026-08-24
|
|
12
|
+
|
|
13
|
+
### Fixed
|
|
14
|
+
|
|
15
|
+
- `enum` compared object members by their serialized form, so `{"b": 2, "a": 1}` was rejected against `enum: [{"a": 1, "b": 2}]`. JSON Schema compares instances by value: two objects with the same members are equal whatever order their keys were written in. All three code generation paths did this, so they agreed with each other and disagreed with the interpreted engine, which has always compared structurally. `const` was already correct but reached the answer by building a canonical string on every call. Both now use one structural comparison hoisted per compiled function. The official suite does not cover reordered keys for `enum`, and the entry-point agreement test compares the generators against each other, so neither would have caught this; `tests/test_value_equality.js` pins the answer through every engine that can produce one and runs as part of `npm test`.
|
|
16
|
+
- A draft-07 document passed through the `schemas` option was rejected with "Schema in schemas option must have $id" whenever it carried `$ref` next to `$id`. Draft-07 ignores every keyword sitting beside `$ref`, so normalization drops them and `$id` goes with them, which is the right reading for evaluation: the reference resolves against the URI the document was retrieved from, not the `$id` it declares. It is the wrong reading for registration, where `$id` is the only name the caller gave the document. The identity is now read from what the caller passed when normalization has dropped it, so such a document registers under its `$id` in the array form and is addressable by both its retrieval URI and its `$id` in the map form. A document that declares no `$id` at all is still refused, and a fragment-only `$id` is still an anchor rather than a document name. This is what made all 23 remote-reference cases of the draft-07 suite error out in the Bowtie compliance report; they now answer, and answer correctly.
|
|
17
|
+
|
|
18
|
+
### Changed
|
|
19
|
+
|
|
20
|
+
- The closure-tree compiler now covers every schema the interpreted engine accepts. It previously declined `unevaluatedProperties`/`unevaluatedItems` and `$dynamicRef` in any schema with more than one resource, which left 192 of the 987 suite schemas walking the generic evaluator; that count is now zero. Annotations flow through the compiled tree: the compiler decides at compile time which nodes anyone reads annotations from, and those nodes carry a record that in-place children write into directly, with a failed child rolled back by truncating the record rather than by allocating a fresh one per child. `$dynamicRef` resolves against the dynamic scope computed at compile time, so multi-resource schemas no longer search it per call. A schema whose compiled graph has no cycle drops the recursion guard and the `(schema, data)` stack entirely. An unresolvable `$ref` compiles into the same runtime rejection the evaluator produces instead of declining the schema.
|
|
21
|
+
- Measured on the official suite with prebuilt validators, several runs each on the same machine: draft 2020-12 went from 94 ns to 77-80 ns per case overall, and the cases that route to the interpreted engine from about 240 ns to about 125 ns when they accept and 235 ns to 160 ns when they reject. Draft 7 went from 59 ns to 49 ns overall. The metaschema-validation case, the worst outlier in the suite, went from 5.5 µs to about 1.4 µs. On a longer steady-state loop over the same suite, where construction is not part of what is timed, draft 2020-12 went from 46 ns to 43 ns per case and draft 7 from 33 ns to 29 ns.
|
|
22
|
+
- The value-level keywords of a schema node are compiled into closures over their own operands instead of being answered by a generic evaluator that re-reads a dozen presence flags off the plan on every call. The type of the value is computed once per node rather than once per keyword group, and a node whose keywords never need it does not compute it at all.
|
|
23
|
+
- `uniqueItems` on arrays whose item type is not known ahead of time no longer builds a canonicalizing closure and a `Set` on every call. The helpers are hoisted once per compiled function; arrays of twelve items or fewer compare pairwise with a structural equality that ignores key order and allocates nothing, and longer arrays keep the `Set`, using canonical strings only once an object actually appears. Over the draft 7 suite the `uniqueItems` cases went from 125 ns to 24 ns each.
|
|
24
|
+
- `ARCHITECTURE.md` documents the interpreted engine and the closure-tree compiler, and no longer claims `$dynamicRef` falls back to the native engine, which stopped being true in 1.7.1.
|
|
25
|
+
|
|
5
26
|
## 1.7.1 - 2026-08-23
|
|
6
27
|
|
|
7
28
|
### Added
|
package/README.md
CHANGED
|
@@ -64,9 +64,10 @@ passes, and a rejection costs more than a verdict: ata builds an error carrying
|
|
|
64
64
|
offending value, a documentation link and a suggestion. On a five-field object schema a
|
|
65
65
|
passing payload costs about 17 ns and a rejected one about 155 ns. `abortEarly: true` or
|
|
66
66
|
`isValidObject()` skips that work when only the verdict matters. Schemas ata declines to
|
|
67
|
-
compile, mostly cross-document `$ref`, `$dynamicRef` and `unevaluated
|
|
68
|
-
interpreted engine
|
|
69
|
-
|
|
67
|
+
compile, mostly cross-document `$ref`, `$dynamicRef` and the harder `unevaluated*` shapes,
|
|
68
|
+
run on the interpreted engine. That engine compiles each schema into a tree of closures, so
|
|
69
|
+
on a six-field object schema a passing payload costs about 136 ns against 8 ns for the same
|
|
70
|
+
shape on the compiled path, both measured warm in one run on the same machine.
|
|
70
71
|
|
|
71
72
|
## Error messages
|
|
72
73
|
|
|
@@ -560,11 +561,28 @@ npm test
|
|
|
560
561
|
npm run test:suite
|
|
561
562
|
```
|
|
562
563
|
|
|
564
|
+
## Project
|
|
565
|
+
|
|
566
|
+
- [CONTRIBUTING.md](CONTRIBUTING.md) explains how to build the project and what a
|
|
567
|
+
pull request needs before it can be merged.
|
|
568
|
+
- [GOVERNANCE.md](GOVERNANCE.md) says who decides what, which changes the project
|
|
569
|
+
will not accept and why, how releases are cut, and how someone becomes a
|
|
570
|
+
maintainer. It states the bus factor plainly rather than leaving you to find
|
|
571
|
+
it out.
|
|
572
|
+
- [SECURITY.md](SECURITY.md) is where vulnerabilities go. Not the issue tracker.
|
|
573
|
+
- [CODE_OF_CONDUCT.md](CODE_OF_CONDUCT.md) applies everywhere the project is
|
|
574
|
+
discussed.
|
|
575
|
+
|
|
563
576
|
## License
|
|
564
577
|
|
|
565
578
|
MIT
|
|
566
579
|
|
|
567
|
-
##
|
|
580
|
+
## Contributors
|
|
581
|
+
|
|
582
|
+
[@mertcanaltin](https://github.com/mertcanaltin) ·
|
|
583
|
+
[@SukeshP1995](https://github.com/SukeshP1995) ·
|
|
584
|
+
[@lemire](https://github.com/lemire) ·
|
|
585
|
+
[@armagandalkiran](https://github.com/armagandalkiran) ·
|
|
586
|
+
[@pnodet](https://github.com/pnodet)
|
|
568
587
|
|
|
569
|
-
|
|
570
|
-
[Daniel Lemire](https://github.com/lemire)
|
|
588
|
+
Missing from this list after landing a patch? Send a pull request.
|
package/index.js
CHANGED
|
@@ -468,6 +468,23 @@ function _normalizeCallerSchema(s, inheritDraft7) {
|
|
|
468
468
|
return JSON.stringify(copy) === str ? s : copy
|
|
469
469
|
}
|
|
470
470
|
|
|
471
|
+
// The identity a document is registered under. Draft-07 ignores every
|
|
472
|
+
// keyword sitting next to `$ref`, so normalization drops them, `$id` among
|
|
473
|
+
// them: that is the right reading for evaluation, where the reference
|
|
474
|
+
// resolves against the retrieval URI rather than the declared `$id`. It is
|
|
475
|
+
// the wrong reading for registration, since `$id` is how the caller names
|
|
476
|
+
// the document. So the identity is read from the normalized copy first and
|
|
477
|
+
// from what the caller passed second. A bare-fragment `$id` is a draft-07
|
|
478
|
+
// anchor rather than a document identity, and normalization has already
|
|
479
|
+
// turned it into `$anchor`, so it is not used here.
|
|
480
|
+
function declaredId(original, normalized) {
|
|
481
|
+
const n = normalized && typeof normalized === 'object' ? normalized.$id : undefined
|
|
482
|
+
if (typeof n === 'string' && n !== '') return n
|
|
483
|
+
const o = original && typeof original === 'object' ? original.$id : undefined
|
|
484
|
+
if (typeof o === 'string' && o !== '' && o[0] !== '#') return o
|
|
485
|
+
return undefined
|
|
486
|
+
}
|
|
487
|
+
|
|
471
488
|
// `inheritDraft7` is true when the root schema is draft-07: a retrieved
|
|
472
489
|
// document that declares no dialect is read under the root's draft.
|
|
473
490
|
function buildSchemaMap(schemas, inheritDraft7) {
|
|
@@ -476,7 +493,7 @@ function buildSchemaMap(schemas, inheritDraft7) {
|
|
|
476
493
|
if (Array.isArray(schemas)) {
|
|
477
494
|
for (const s of schemas) {
|
|
478
495
|
const normalized = _normalizeCallerSchema(s, inheritDraft7)
|
|
479
|
-
const id = normalized
|
|
496
|
+
const id = declaredId(s, normalized)
|
|
480
497
|
if (!id) throw new Error('Schema in schemas option must have $id')
|
|
481
498
|
map.set(id, normalized)
|
|
482
499
|
}
|
|
@@ -487,7 +504,8 @@ function buildSchemaMap(schemas, inheritDraft7) {
|
|
|
487
504
|
// under and by the $id it declares. Registering only the $id makes
|
|
488
505
|
// references to the retrieval URI unresolvable.
|
|
489
506
|
map.set(key, normalized)
|
|
490
|
-
|
|
507
|
+
const id = declaredId(s, normalized)
|
|
508
|
+
if (id && id !== key) map.set(id, normalized)
|
|
491
509
|
}
|
|
492
510
|
}
|
|
493
511
|
return map
|
package/lib/interpreter.js
CHANGED
|
@@ -607,9 +607,10 @@ class Interpreter {
|
|
|
607
607
|
this._fast = undefined;
|
|
608
608
|
}
|
|
609
609
|
|
|
610
|
-
// The closure-tree compiler covers
|
|
611
|
-
//
|
|
612
|
-
// construction stays cheap for schemas that never
|
|
610
|
+
// The closure-tree compiler covers every schema the interpreter accepts;
|
|
611
|
+
// eval() stays as the reference the compiled tree is diffed against.
|
|
612
|
+
// Compiled lazily so construction stays cheap for schemas that never
|
|
613
|
+
// validate.
|
|
613
614
|
_fastRoot() {
|
|
614
615
|
if (this._fast === undefined) {
|
|
615
616
|
try { this._fast = compileInterpreter(this); } catch { this._fast = null; }
|
|
@@ -690,7 +691,7 @@ class Interpreter {
|
|
|
690
691
|
// Verdict only: same walk as validate(), no error construction at all.
|
|
691
692
|
isValid(data) {
|
|
692
693
|
const fast = this._fastRoot();
|
|
693
|
-
if (fast !== null) return fast.v(data, []);
|
|
694
|
+
if (fast !== null) return fast.v(data, fast.cyclic ? [] : null);
|
|
694
695
|
const dynScope = [this.state.rootBase];
|
|
695
696
|
return this.eval(this.rootNode, data, this.state.rootBase, dynScope, NOERRORS, '', '#', [], DISCARD);
|
|
696
697
|
}
|
|
@@ -699,7 +700,7 @@ class Interpreter {
|
|
|
699
700
|
const fast = this._fastRoot();
|
|
700
701
|
if (fast !== null) {
|
|
701
702
|
const errors = [];
|
|
702
|
-
const valid = fast.c(data, errors, '', '#', []);
|
|
703
|
+
const valid = fast.c(data, errors, '', '#', fast.cyclic ? [] : null);
|
|
703
704
|
return valid ? { valid: true, data, errors: [] } : { valid: false, errors };
|
|
704
705
|
}
|
|
705
706
|
const errors = [];
|
|
@@ -1080,7 +1081,8 @@ function err(code, keyword, instancePath, schemaPath, params, message) {
|
|
|
1080
1081
|
}
|
|
1081
1082
|
|
|
1082
1083
|
const { compileInterpreter } = require('./plan-compiler').install({
|
|
1083
|
-
Plan, NOERRORS, err, evalLeaf, evalLeafV,
|
|
1084
|
+
Plan, NOERRORS, err, evalLeaf, evalLeafV, dataBits, escapePointer,
|
|
1085
|
+
deepEqual, multipleOfOk, cpAtLeast, cpAtMost,
|
|
1084
1086
|
T_STRING, T_ARRAY, T_OBJECT, resolveRef, splitFragment, resolveUri,
|
|
1085
1087
|
});
|
|
1086
1088
|
|
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
|
|
|
@@ -1707,13 +1779,7 @@ function genCode(schema, v, lines, ctx, knownType) {
|
|
|
1707
1779
|
|
|
1708
1780
|
// enum
|
|
1709
1781
|
if (schema.enum) {
|
|
1710
|
-
|
|
1711
|
-
const primitives = vals.filter(v => v === null || typeof v !== 'object')
|
|
1712
|
-
const objects = vals.filter(v => v !== null && typeof v === 'object')
|
|
1713
|
-
const primChecks = primitives.map(p => `${v}===${JSON.stringify(p)}`).join('||')
|
|
1714
|
-
const objChecks = objects.map(o => `JSON.stringify(${v})===${JSON.stringify(JSON.stringify(o))}`).join('||')
|
|
1715
|
-
const allChecks = [primChecks, objChecks].filter(Boolean).join('||')
|
|
1716
|
-
lines.push(`if(!(${allChecks || 'false'}))return false`)
|
|
1782
|
+
lines.push(`if(!(${enumCondition(ctx, schema.enum, v)}))return false`)
|
|
1717
1783
|
}
|
|
1718
1784
|
|
|
1719
1785
|
// const
|
|
@@ -1722,12 +1788,9 @@ function genCode(schema, v, lines, ctx, knownType) {
|
|
|
1722
1788
|
if (cv === null || typeof cv !== 'object') {
|
|
1723
1789
|
lines.push(`if(${v}!==${JSON.stringify(cv)})return false`)
|
|
1724
1790
|
} else {
|
|
1725
|
-
//
|
|
1726
|
-
//
|
|
1727
|
-
|
|
1728
|
-
const canonFn = `_cnB${ci}`
|
|
1729
|
-
const expected = _canonical(cv)
|
|
1730
|
-
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`)
|
|
1731
1794
|
}
|
|
1732
1795
|
}
|
|
1733
1796
|
|
|
@@ -1882,11 +1945,22 @@ function genCode(schema, v, lines, ctx, knownType) {
|
|
|
1882
1945
|
const maxItems = schema.maxItems
|
|
1883
1946
|
// Small primitive arrays (maxItems <= 16): nested loop is 6x faster than Set
|
|
1884
1947
|
// No allocation, no hash computation — just direct === comparison
|
|
1885
|
-
|
|
1886
|
-
|
|
1887
|
-
|
|
1888
|
-
|
|
1889
|
-
|
|
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
|
+
}
|
|
1890
1964
|
lines.push(isArr ? `{${inner}}` : `if(Array.isArray(${v})){${inner}}`)
|
|
1891
1965
|
}
|
|
1892
1966
|
|
|
@@ -3188,13 +3262,7 @@ function genCodeE(schema, v, pathExpr, lines, ctx, schemaPrefix) {
|
|
|
3188
3262
|
|
|
3189
3263
|
// enum
|
|
3190
3264
|
if (schema.enum) {
|
|
3191
|
-
|
|
3192
|
-
const primitives = vals.filter(v => v === null || typeof v !== 'object')
|
|
3193
|
-
const objects = vals.filter(v => v !== null && typeof v === 'object')
|
|
3194
|
-
const primChecks = primitives.map(p => `${v}===${JSON.stringify(p)}`).join('||')
|
|
3195
|
-
const objChecks = objects.map(o => `JSON.stringify(${v})===${JSON.stringify(JSON.stringify(o))}`).join('||')
|
|
3196
|
-
const allChecks = [primChecks, objChecks].filter(Boolean).join('||')
|
|
3197
|
-
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'")}}`)
|
|
3198
3266
|
}
|
|
3199
3267
|
|
|
3200
3268
|
// const — use canonical (sorted-key) comparison for objects
|
|
@@ -3203,12 +3271,7 @@ function genCodeE(schema, v, pathExpr, lines, ctx, schemaPrefix) {
|
|
|
3203
3271
|
if (cv === null || typeof cv !== 'object') {
|
|
3204
3272
|
lines.push(`if(${v}!==${JSON.stringify(cv)}){${fail('const', 'const', `{allowedValue:${JSON.stringify(schema.const)}}`, "'must be equal to constant'")}}`)
|
|
3205
3273
|
} else {
|
|
3206
|
-
|
|
3207
|
-
const ci = ctx.varCounter++
|
|
3208
|
-
const canonFn = `_cnE${ci}`
|
|
3209
|
-
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(',')+'}'};`)
|
|
3210
|
-
const expected = canonFn + '(JSON.parse(' + JSON.stringify(JSON.stringify(cv)) + '))'
|
|
3211
|
-
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'")}}`)
|
|
3212
3275
|
}
|
|
3213
3276
|
}
|
|
3214
3277
|
|
|
@@ -3798,13 +3861,7 @@ function genCodeC(schema, v, pathExpr, lines, ctx, schemaPrefix) {
|
|
|
3798
3861
|
|
|
3799
3862
|
// enum
|
|
3800
3863
|
if (schema.enum) {
|
|
3801
|
-
|
|
3802
|
-
const primitives = vals.filter(v => v === null || typeof v !== 'object')
|
|
3803
|
-
const objects = vals.filter(v => v !== null && typeof v === 'object')
|
|
3804
|
-
const primChecks = primitives.map(p => `${v}===${JSON.stringify(p)}`).join('||')
|
|
3805
|
-
const objChecks = objects.map(o => `JSON.stringify(${v})===${JSON.stringify(JSON.stringify(o))}`).join('||')
|
|
3806
|
-
const allChecks = [primChecks, objChecks].filter(Boolean).join('||')
|
|
3807
|
-
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'")}}`)
|
|
3808
3865
|
}
|
|
3809
3866
|
|
|
3810
3867
|
// const
|
|
@@ -3813,10 +3870,7 @@ function genCodeC(schema, v, pathExpr, lines, ctx, schemaPrefix) {
|
|
|
3813
3870
|
if (cv === null || typeof cv !== 'object') {
|
|
3814
3871
|
lines.push(`if(${v}!==${JSON.stringify(cv)}){${fail('const', 'const', `{allowedValue:${JSON.stringify(schema.const)}}`, "'must be equal to constant'")}}`)
|
|
3815
3872
|
} else {
|
|
3816
|
-
const
|
|
3817
|
-
const canonFn = `_cn${ci}`
|
|
3818
|
-
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(',')+'}'};`)
|
|
3819
|
-
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'")}}`)
|
|
3820
3874
|
}
|
|
3821
3875
|
}
|
|
3822
3876
|
|
package/lib/native-load.js
CHANGED
|
@@ -14,7 +14,10 @@
|
|
|
14
14
|
const VERSION = require('./version');
|
|
15
15
|
|
|
16
16
|
function nativePackageName(platform, arch, isMusl) {
|
|
17
|
-
if (platform === 'darwin')
|
|
17
|
+
if (platform === 'darwin') {
|
|
18
|
+
if (arch !== 'x64' && arch !== 'arm64') return null;
|
|
19
|
+
return `@ata-validator/native-darwin-${arch}`;
|
|
20
|
+
}
|
|
18
21
|
if (platform === 'win32') return arch === 'x64' ? '@ata-validator/native-win32-x64' : null;
|
|
19
22
|
if (platform === 'linux') {
|
|
20
23
|
if (arch !== 'x64' && arch !== 'arm64') return null;
|