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 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*`, run on the
68
- interpreted engine: about 343 ns for a passing payload on a six-field object schema, eight
69
- times the compiled path, measured warm on the same machine.
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
- ## Authors
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
- [Mert Can Altin](https://github.com/mertcanaltin)
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.$id
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
- if (normalized.$id && normalized.$id !== key) map.set(normalized.$id, normalized)
507
+ const id = declaredId(s, normalized)
508
+ if (id && id !== key) map.set(id, normalized)
491
509
  }
492
510
  }
493
511
  return map
@@ -607,9 +607,10 @@ class Interpreter {
607
607
  this._fast = undefined;
608
608
  }
609
609
 
610
- // The closure-tree compiler covers single-resource schemas without
611
- // unevaluated keywords; everything else stays on eval(). Compiled lazily so
612
- // construction stays cheap for schemas that never validate.
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, deepEqual, dataBits, escapePointer,
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
 
@@ -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
- const vals = schema.enum
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
- // Key-order independent comparison, defined inline so every emitter
1726
- // (runtime, hybrid, standalone) carries it.
1727
- const ci = ctx.varCounter++
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
- const inner = isPrimItems && maxItems && maxItems <= 16
1886
- ? `for(let _i=1;_i<${v}.length;_i++){for(let _k=0;_k<_i;_k++){if(${v}[_i]===${v}[_k])return false}}`
1887
- : isPrimItems
1888
- ? `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])}`
1889
- : `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)}`
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
- const vals = schema.enum
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
- // Pre-compute canonical form of const value
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
- const vals = schema.enum
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 ci = ctx.varCounter++
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
 
@@ -14,7 +14,10 @@
14
14
  const VERSION = require('./version');
15
15
 
16
16
  function nativePackageName(platform, arch, isMusl) {
17
- if (platform === 'darwin') return arch === 'arm64' ? '@ata-validator/native-darwin-arm64' : null;
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;