ata-validator 1.30.3 → 1.31.1

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/index.js CHANGED
@@ -694,17 +694,31 @@ function sortErrorsBySchemaOrder(rootSchema, errors) {
694
694
 
695
695
  // Paths repeat across rejections of the same shape, so the parsed segment
696
696
  // list is cached per path string. Entries are frozen: the same array is
697
- // handed to every issue that names the path. The cache is bounded so a
698
- // stream of array indexes cannot grow it without limit.
699
- const _pathCache = new Map();
700
- const PATH_CACHE_MAX = 4096;
697
+ // handed to every issue that names the path.
698
+ //
699
+ // The cache is bucketed by length and searched with ===, not keyed in a Map.
700
+ // A path with an array index in it is concatenated afresh by every rejection,
701
+ // and a Map has to hash each such string from scratch before it can look
702
+ // anything up; that hashing was 30% of a Standard Schema rejection with
703
+ // sixteen issues. Comparing against the few paths of the same length costs
704
+ // less. Both levels are bounded so a stream of array indexes cannot grow it
705
+ // without limit: a full bucket stops caching, too many lengths clear it.
706
+ const _pathBuckets = new Map();
707
+ const PATH_BUCKET_MAX = 32;
708
+ const PATH_LENGTHS_MAX = 256;
701
709
  function parsePointerPath(path) {
702
710
  if (!path) return EMPTY_PATH;
703
- const hit = _pathCache.get(path);
704
- if (hit !== undefined) return hit;
711
+ const n = path.length;
712
+ let bucket = _pathBuckets.get(n);
713
+ if (bucket !== undefined) {
714
+ for (let i = 0; i < bucket.length; i += 2) if (bucket[i] === path) return bucket[i + 1];
715
+ } else {
716
+ if (_pathBuckets.size >= PATH_LENGTHS_MAX) _pathBuckets.clear();
717
+ bucket = [];
718
+ _pathBuckets.set(n, bucket);
719
+ }
705
720
  const segs = Object.freeze(parsePointerPathUncached(path));
706
- if (_pathCache.size >= PATH_CACHE_MAX) _pathCache.clear();
707
- _pathCache.set(path, segs);
721
+ if (bucket.length < PATH_BUCKET_MAX * 2) bucket.push(path, segs);
708
722
  return segs;
709
723
  }
710
724
  const EMPTY_PATH = Object.freeze([]);
@@ -1109,6 +1123,11 @@ class Validator {
1109
1123
  this._lastRawInput = null;
1110
1124
  // undefined: not built yet. null: this schema has no scanner.
1111
1125
  this._scanner = undefined;
1126
+ // Checks a wrapper registered through _extendChecks. Declared here so
1127
+ // registering one does not change the instance's shape.
1128
+ this._verdictTail = null;
1129
+ this._validateTail = null;
1130
+ this._entryExt = null;
1112
1131
 
1113
1132
  // Public methods start as memoized accessors on the prototype; nothing is
1114
1133
  // allocated per instance until one is first read. See _defineLazyMethod
@@ -1117,10 +1136,12 @@ class Validator {
1117
1136
  // "~standard" (Standard Schema V1) is a lazy prototype accessor too;
1118
1137
  // see below the class. Consumers only pay for it if they read it.
1119
1138
 
1120
- // Populate identity cache so repeated `new Validator(sameSchema)` short-circuits.
1121
- if (!opts && typeof schema === "object" && schema !== null) {
1122
- _identityCache.set(schema, this);
1123
- }
1139
+ // The identity cache, which lets a later `new Validator(sameSchema)` return
1140
+ // this instance, is filled on the first compile rather than here. A WeakMap
1141
+ // entry is an ephemeron the collector has to trace separately, and setting
1142
+ // one cost about 780 ns against 150 for the rest of this constructor, five
1143
+ // times over for an instance that may never validate anything. Once an
1144
+ // instance has compiled, the shortcut behaves as before.
1124
1145
  }
1125
1146
 
1126
1147
  // `$vocabulary` says which keywords the dialect has, and answering needs the
@@ -1385,7 +1406,7 @@ class Validator {
1385
1406
  return this._compiled.validate(x);
1386
1407
  });
1387
1408
  }
1388
- return _errOnlyImpl(d);
1409
+ return _mustReject(_errOnlyImpl(d));
1389
1410
  };
1390
1411
 
1391
1412
  // Best path: combined validator (single pass, validates + collects errors)
@@ -1429,7 +1450,7 @@ class Validator {
1429
1450
  let _errPreferredImpl = null;
1430
1451
  const errPreferCombined = (d) => {
1431
1452
  if (_errPreferredImpl === null) _errPreferredImpl = combinedIfSafe() || errOnly;
1432
- return _errPreferredImpl(d);
1453
+ return _mustReject(_errPreferredImpl(d));
1433
1454
  };
1434
1455
 
1435
1456
  // The boolean engine is the verdict authority for these paths; the
@@ -1492,7 +1513,7 @@ class Validator {
1492
1513
  let impl = null;
1493
1514
  const onReject = (data) => {
1494
1515
  const combined = combinedIfSafe();
1495
- if (combined) { impl = combined; return combined(data); }
1516
+ if (combined) { impl = combined; return _mustReject(combined(data)); }
1496
1517
  return errOnly(data);
1497
1518
  };
1498
1519
  const hybridFn = jsFn._hybridFactory(VALID_RESULT, onReject);
@@ -1556,9 +1577,9 @@ class Validator {
1556
1577
  // The verdict methods answer validate()'s question without building the
1557
1578
  // error list, so they run the same preprocess pass. Skipping it made the
1558
1579
  // two disagree on input that coercion or a default would have fixed.
1559
- this.isValidObject = preprocess
1580
+ _bindVerdict(this, preprocess
1560
1581
  ? (data) => { preprocess(data); return jsFn(data) }
1561
- : jsFn;
1582
+ : jsFn);
1562
1583
  // Same preference as the object path: the combined function first, since
1563
1584
  // it validates and collects in one pass, and the error generator behind
1564
1585
  // it. `errPreferCombined` is that order, resolved on the first rejection
@@ -1771,9 +1792,9 @@ class Validator {
1771
1792
  return _validate(data);
1772
1793
  }
1773
1794
  : _validate;
1774
- this.isValidObject = this._fastVerdict
1795
+ _bindVerdict(this, this._fastVerdict
1775
1796
  ? this._fastVerdict
1776
- : (data) => _validate(data).valid;
1797
+ : (data) => _validate(data).valid);
1777
1798
  this.validateAndParse = (jsonStr) => this._compiled.validateAndParse(jsonStr);
1778
1799
  {
1779
1800
  const slot = this._fastSlot;
@@ -1831,9 +1852,9 @@ class Validator {
1831
1852
  ? (data) => { preprocess(data); return interp.validate(data); }
1832
1853
  : (data) => interp.validate(data));
1833
1854
  this.validate = run;
1834
- this.isValidObject = this._fastVerdict
1855
+ _bindVerdict(this, this._fastVerdict
1835
1856
  ? this._fastVerdict
1836
- : (data) => run(data).valid;
1857
+ : (data) => run(data).valid);
1837
1858
  this.validateJSON = (jsonStr) => {
1838
1859
  try {
1839
1860
  return run(JSON.parse(jsonStr));
@@ -1962,30 +1983,54 @@ class Validator {
1962
1983
  // schema coerces or defaults (preprocess mutates before the verdict),
1963
1984
  // under abortEarly (already a frozen stub), and for $dynamicRef (the
1964
1985
  // boolean engine is not the authority there).
1986
+ // A check registered through _extendValidate joins here when it can: the
1987
+ // verdict comes from the generated function with the check compiled in,
1988
+ // and the check's own errors are appended when somebody reads them. A
1989
+ // rejection then costs what it costs without the check. Where this layer
1990
+ // is not installed, the end of this method wraps validate() instead.
1991
+ const _vx = this._validateTail !== null ? this._validateTail() : null;
1992
+ let _vxApplied = false;
1965
1993
  if (this._fastVerdict && !preprocess && !options.abortEarly && this.validate) {
1966
1994
  const _full = this.validate;
1967
- const _fast = this._fastVerdict;
1995
+ const _fast = _vx ? _fuseTail(this._fastVerdict, _vx.check) : this._fastVerdict;
1996
+ const _extra = _vx ? _vx.errors : null;
1997
+ _vxApplied = true;
1968
1998
  const EMPTY_ERRORS = Object.freeze([]);
1969
1999
  const _verdictFallback = [{ keyword: 'validation', instancePath: '', schemaPath: '#', params: {}, message: 'schema validation failed' }];
2000
+ const _withExtra = (own, data) => {
2001
+ const more = _extra === null ? null : _extra(data);
2002
+ if (more && more.length) return own ? own.concat(more) : more;
2003
+ // The data changed between the verdict and this read; keep the
2004
+ // verdict and say so rather than inventing a specific error.
2005
+ return own || _verdictFallback;
2006
+ };
1970
2007
  const _buildErrors = (data) => {
1971
2008
  const r = _full(data);
1972
- return (r && r.valid === false && r.errors && r.errors.length)
1973
- ? r.errors
1974
- // The data changed between the verdict and this read; keep the
1975
- // verdict and say so rather than inventing a specific error.
1976
- : _verdictFallback;
2009
+ return _withExtra((r && r.valid === false && r.errors && r.errors.length) ? r.errors : null, data);
1977
2010
  };
1978
2011
  const _buildRawErrors = (data) => {
1979
2012
  const r = _full(data);
1980
- if (!r || r.valid !== false) return _verdictFallback;
1981
- const raw = typeof r._ataRaw === 'function' ? r._ataRaw() : r.errors;
1982
- return raw && raw.length ? raw : _verdictFallback;
2013
+ let raw = null;
2014
+ if (r && r.valid === false) {
2015
+ raw = typeof r._ataRaw === 'function' ? r._ataRaw() : r.errors;
2016
+ if (!raw || !raw.length) raw = null;
2017
+ }
2018
+ return _withExtra(raw, data);
1983
2019
  };
1984
2020
  this.validate = (data) => {
1985
2021
  if (_fast(data)) return { valid: true, data, errors: EMPTY_ERRORS };
1986
2022
  return new LazyRejection(_buildErrors, data, _buildRawErrors);
1987
2023
  };
1988
2024
  }
2025
+ if (_vx && !_vxApplied && this.validate) {
2026
+ const inner = this.validate;
2027
+ const { check, errors } = _vx;
2028
+ this.validate = (data) => {
2029
+ const r = inner(data);
2030
+ if (r.valid && check(data)) return r;
2031
+ return new ExtendedRejection(r, data, errors);
2032
+ };
2033
+ }
1989
2034
 
1990
2035
  // The buffer APIs answer from the native walker, which disagrees with
1991
2036
  // validate() on shapes listed in lib/buffer-gate.js. For those schemas
@@ -2033,8 +2078,8 @@ class Validator {
2033
2078
  this.isValidJSON = (jsonStr) => {
2034
2079
  const scan = self._ensureScanner();
2035
2080
  if (scan === undefined) return byParsing(jsonStr);
2036
- if (scan === null) { self.isValidJSON = byParsing; return byParsing(jsonStr); }
2037
- self.isValidJSON = memoizable
2081
+ if (scan === null) { _bindEntry(self, 'isValidJSON', byParsing); return byParsing(jsonStr); }
2082
+ _bindEntry(self, 'isValidJSON', memoizable
2038
2083
  ? (text) => {
2039
2084
  if (typeof text !== 'string') return byParsing(text);
2040
2085
  if (text === _memoText) return _memoVerdict;
@@ -2049,7 +2094,7 @@ class Validator {
2049
2094
  const r = scan(text);
2050
2095
  if (r === -1) return byParsing(text);
2051
2096
  return r === 1;
2052
- };
2097
+ });
2053
2098
  return self.isValidJSON(jsonStr);
2054
2099
  };
2055
2100
  // validateJSON gets the same short-circuit isValidJSON has. The verdict is
@@ -2072,8 +2117,8 @@ class Validator {
2072
2117
  this.validateJSON = (jsonStr) => {
2073
2118
  const scan = self._ensureScanner();
2074
2119
  if (scan === undefined) return validateByParsing(jsonStr);
2075
- if (scan === null) { self.validateJSON = validateByParsing; return validateByParsing(jsonStr); }
2076
- self.validateJSON = (text) => {
2120
+ if (scan === null) { _bindEntry(self, 'validateJSON', validateByParsing); return validateByParsing(jsonStr); }
2121
+ _bindEntry(self, 'validateJSON', (text) => {
2077
2122
  if (typeof text === 'string') {
2078
2123
  const r = scan(text);
2079
2124
  if (r === 1) return VALID_RESULT;
@@ -2088,21 +2133,25 @@ class Validator {
2088
2133
  }
2089
2134
  }
2090
2135
  return validateByParsing(text);
2091
- };
2136
+ });
2092
2137
  return self.validateJSON(jsonStr);
2093
2138
  };
2094
2139
  }
2095
2140
  }
2096
2141
 
2097
-
2098
- // Save to identity cache for ultra-fast reuse with same schema object.
2099
- // Only an instance built without options may answer a later
2100
- // `new Validator(schema)`: one built with options (richErrors: false,
2101
- // coerceTypes, formats, ...) would hand its options to a caller that
2102
- // asked for none.
2103
- if (this._noOpts && this._schemaObj && typeof this._schemaObj === 'object') {
2104
- _identityCache.set(this._schemaObj, this);
2142
+ // An extension registered through _extendValidate covers the JSON entry
2143
+ // points too. They are final here except for the scanner stubs above,
2144
+ // which rebind themselves on first use through _bindEntry, so the
2145
+ // extension is applied to whatever each one is now and again on rebind.
2146
+ if (_vx) {
2147
+ this._entryExt = _jsonEntryWrappers(_vx);
2148
+ for (const name of ['validateJSON', 'isValidJSON', 'validateAndParse']) {
2149
+ if (Object.prototype.hasOwnProperty.call(this, name) && typeof this[name] === 'function') _bindEntry(this, name, this[name]);
2150
+ }
2105
2151
  }
2152
+
2153
+
2154
+ _rememberInstance(this);
2106
2155
  }
2107
2156
 
2108
2157
  // Which engine answers validate() for this schema: 'codegen' (generated
@@ -2205,7 +2254,8 @@ class Validator {
2205
2254
  const cached = (this._userFormats || this._usesKeywords) ? null : _compileCache.get(mapKey);
2206
2255
  if (cached && cached.jsFn) {
2207
2256
  this._jsFn = cached.jsFn;
2208
- this.isValidObject = cached.jsFn;
2257
+ _bindVerdict(this, cached.jsFn);
2258
+ _rememberInstance(this);
2209
2259
  return;
2210
2260
  }
2211
2261
  const uf = this._userFormats;
@@ -2213,7 +2263,8 @@ class Validator {
2213
2263
  const jsFn = _cg || compileToJS(this._schemaObj, null, sm);
2214
2264
  this._jsFn = jsFn;
2215
2265
  if (jsFn) {
2216
- this.isValidObject = jsFn;
2266
+ _bindVerdict(this, jsFn);
2267
+ _rememberInstance(this);
2217
2268
  // A partial entry: the verdict function is real, the other two are not
2218
2269
  // built yet rather than declined. `undefined` is the not-built marker
2219
2270
  // the full compile's _buildDeferred looks for; `null` would read as
@@ -2615,6 +2666,229 @@ Object.defineProperty(Validator.prototype, "~standard", {
2615
2666
  },
2616
2667
  });
2617
2668
 
2669
+ // The error resolvers run only after a verdict function has said no. If one
2670
+ // answers valid anyway, two generators disagree, and the verdict is the one
2671
+ // to keep: returning the resolver's answer is how a vacuous combined function
2672
+ // turned a rejection into an acceptance in validateJSON. The disagreement is
2673
+ // reported as a generic failure rather than hidden.
2674
+ const _VERDICT_DISAGREES = Object.freeze({
2675
+ valid: false,
2676
+ errors: Object.freeze([Object.freeze({ keyword: 'validation', instancePath: '', schemaPath: '#', params: Object.freeze({}), message: 'schema validation failed' })]),
2677
+ });
2678
+ function _mustReject(r) {
2679
+ return r && r.valid === false ? r : _VERDICT_DISAGREES;
2680
+ }
2681
+
2682
+ // Install the verdict method. Every place that binds isValidObject comes
2683
+ // through here, so a check registered with _extendVerdict survives the method
2684
+ // being replaced as the validator compiles further, which it does more than
2685
+ // once over its life.
2686
+ function _bindVerdict(self, fn) {
2687
+ const resolve = self._verdictTail;
2688
+ if (resolve !== null && typeof fn === 'function') {
2689
+ const tail = resolve();
2690
+ if (typeof tail === 'function') fn = _fuseTail(fn, tail);
2691
+ }
2692
+ self.isValidObject = fn;
2693
+ }
2694
+
2695
+ // Bind one of the JSON entry points, through the extension wrapper when there is one.
2696
+ function _bindEntry(self, name, fn) {
2697
+ const ext = self._entryExt;
2698
+ self[name] = ext !== null && ext[name] ? ext[name](fn) : fn;
2699
+ }
2700
+
2701
+ // The JSON entry points under an extension: the schema answers first, and only
2702
+ // text it accepts is parsed for the check, so a rejection costs nothing extra.
2703
+ function _jsonEntryWrappers({ check, errors }) {
2704
+ const parse = (text) => JSON.parse(typeof text === 'string' ? text : new TextDecoder().decode(text));
2705
+ return {
2706
+ validateJSON: (inner) => (text) => {
2707
+ const res = inner(text);
2708
+ if (!res.valid) return res;
2709
+ let data;
2710
+ try { data = parse(text); } catch { return res; }
2711
+ if (check(data)) return res;
2712
+ const e = errors(data);
2713
+ return e && e.length ? { valid: false, errors: e } : { valid: false, errors: [_EXT_FALLBACK] };
2714
+ },
2715
+ isValidJSON: (inner) => (text) => {
2716
+ if (!inner(text)) return false;
2717
+ let data;
2718
+ try { data = parse(text); } catch { return true; }
2719
+ return check(data);
2720
+ },
2721
+ validateAndParse: (inner) => (text) => {
2722
+ const res = inner(text);
2723
+ if (!res.valid) return res;
2724
+ if (check(res.value)) return res;
2725
+ const e = errors(res.value);
2726
+ return { valid: false, value: res.value, errors: e && e.length ? e : [_EXT_FALLBACK] };
2727
+ },
2728
+ };
2729
+ }
2730
+ const _EXT_FALLBACK = Object.freeze({ keyword: 'validation', instancePath: '', schemaPath: '#', params: {}, message: 'schema validation failed' });
2731
+
2732
+ // A verdict function that also runs `tail` on what it accepts. The generated
2733
+ // function can take the check in place of its final `return true`, one call
2734
+ // per document; anything else is composed.
2735
+ function _fuseTail(fn, tail) {
2736
+ const fused = typeof fn._withTail === 'function' ? fn._withTail(tail) : null;
2737
+ return fused || ((d) => fn(d) && tail(d));
2738
+ }
2739
+
2740
+ // The rejection validate() returns on the paths where an extension check could
2741
+ // not join the lazy layer: the inner result, plus the check's errors appended
2742
+ // on first read. `inner` may itself be valid, when only the check failed.
2743
+ class ExtendedRejection {
2744
+ constructor(inner, data, collect) {
2745
+ this.valid = false;
2746
+ this._inner = inner;
2747
+ this._data = data;
2748
+ this._collect = collect;
2749
+ this._errors = null;
2750
+ }
2751
+ toJSON() {
2752
+ return { valid: false, errors: this.errors };
2753
+ }
2754
+ _ataRaw() {
2755
+ const inner = this._inner;
2756
+ const more = this._collect(this._data) || [];
2757
+ const raw = inner.valid ? more : (typeof inner._ataRaw === 'function' ? inner._ataRaw() : inner.errors).concat(more);
2758
+ return raw.length ? raw : [_EXT_FALLBACK];
2759
+ }
2760
+ }
2761
+ Object.defineProperty(ExtendedRejection.prototype, 'errors', {
2762
+ enumerable: true,
2763
+ configurable: true,
2764
+ get() {
2765
+ if (this._errors === null) {
2766
+ const inner = this._inner;
2767
+ const more = this._collect(this._data) || [];
2768
+ const all = inner.valid ? more : inner.errors.concat(more);
2769
+ this._errors = all.length ? all : [_EXT_FALLBACK];
2770
+ }
2771
+ return this._errors;
2772
+ },
2773
+ });
2774
+
2775
+ // For wrappers that enforce a check the schema does not carry, such as the
2776
+ // `instanceof` keyword of @ata-project/keywords. `resolve` is called whenever
2777
+ // the verdict method is bound, which is after the schema has been normalized,
2778
+ // and returns the check, a function of the document that answers true or
2779
+ // false, or null when there is nothing to add. Only isValidObject takes it;
2780
+ // the other entry points, which report errors, stay the wrapper's to handle.
2781
+ //
2782
+ // Before this, such a wrapper had to hold isValidObject behind an accessor so
2783
+ // that the validator's own rebinding could not drop its check, and every call
2784
+ // paid for the accessor and two more calls: 10.1 ns against 4.2 on a document
2785
+ // the schema rejects at its third property.
2786
+ Validator.prototype._verdictTail = null;
2787
+ Validator.prototype._validateTail = null;
2788
+ Validator.prototype._entryExt = null;
2789
+
2790
+ // Let `new Validator(schema)` with the same schema object return this instance.
2791
+ // Only an instance built without options may answer that call: one built with
2792
+ // options (richErrors: false, coerceTypes, formats, ...) would hand its options
2793
+ // to a caller that asked for none, and an extended one would enforce checks the
2794
+ // caller never registered. Both the caller's object and the normalized one are
2795
+ // keys, since a later caller passes the former.
2796
+ function _rememberInstance(self) {
2797
+ if (!self._noOpts || self._verdictTail !== null || self._validateTail !== null) return;
2798
+ const raw = self._rawSchema;
2799
+ if (raw && typeof raw === 'object' && !_identityCache.has(raw)) _identityCache.set(raw, self);
2800
+ const obj = self._schemaObj;
2801
+ if (obj !== raw && obj && typeof obj === 'object' && !_identityCache.has(obj)) _identityCache.set(obj, self);
2802
+ }
2803
+
2804
+ // An extended validator answers differently from a plain one for the same
2805
+ // schema, so it must not be the instance `new Validator(sameSchema)` hands out.
2806
+ function _leaveIdentityCache(self) {
2807
+ self._noOpts = false;
2808
+ // Nothing is registered before the first compile, so there is nothing to
2809
+ // take back.
2810
+ if (!self._initialized && self._jsFn === null) return;
2811
+ const raw = self._rawSchema;
2812
+ if (raw && typeof raw === 'object' && _identityCache.get(raw) === self) _identityCache.delete(raw);
2813
+ // The compiled form is cached too, at the end of the first compile. Read it
2814
+ // only if it is already materialized: reading it otherwise builds it.
2815
+ if (Object.prototype.hasOwnProperty.call(self, '_schemaObj')) {
2816
+ const obj = self._schemaObj;
2817
+ if (obj && typeof obj === 'object' && _identityCache.get(obj) === self) _identityCache.delete(obj);
2818
+ }
2819
+ }
2820
+
2821
+ // The same kind of extension for validate(): `resolve` returns { check, errors }
2822
+ // or null, where `errors(data)` lists the check's own errors, or returns null
2823
+ // when there are none. The check's errors come after the schema's, and a value
2824
+ // that fails only the check is rejected with the check's errors alone. Must be
2825
+ // called before validate() is first used, which is when it is compiled; later
2826
+ // calls throw rather than being silently ignored.
2827
+ Validator.prototype._extendValidate = function (resolve) {
2828
+ if (typeof resolve !== 'function') throw new TypeError('_extendValidate expects a function');
2829
+ if (this._initialized) throw new Error('_extendValidate must be called before the validator compiles');
2830
+ _leaveIdentityCache(this);
2831
+ const prev = this._validateTail;
2832
+ this._validateTail = prev === null ? resolve : () => {
2833
+ const a = prev(), b = resolve();
2834
+ if (!a) return b;
2835
+ if (!b) return a;
2836
+ return {
2837
+ check: (d) => a.check(d) && b.check(d),
2838
+ errors: (d) => {
2839
+ const x = a.errors(d), y = b.errors(d);
2840
+ if (!x) return y;
2841
+ if (!y) return x;
2842
+ return x.concat(y);
2843
+ },
2844
+ };
2845
+ };
2846
+ return this;
2847
+ };
2848
+
2849
+ // Both extensions in one call, from one resolver that returns { check, errors }
2850
+ // or null. This is the form @ata-project/keywords uses: registering costs one
2851
+ // closure, where wrapping the five entry points cost a closure per entry point,
2852
+ // the wrappers themselves and an accessor, most of what building a wrapped
2853
+ // validator took.
2854
+ Validator.prototype._extendChecks = function (resolve) {
2855
+ if (typeof resolve !== 'function') throw new TypeError('_extendChecks expects a function');
2856
+ this._extendValidate(resolve);
2857
+ return this._extendVerdict(() => {
2858
+ const x = resolve();
2859
+ return x ? x.check : null;
2860
+ });
2861
+ };
2862
+
2863
+ // Whether this instance enforces a check its schema does not carry. The
2864
+ // ahead-of-time emitters build a module from the schema alone, so they refuse
2865
+ // an instance that says yes rather than emit one that accepts too much.
2866
+ // Reading it resolves the registered checks; only an emitter reads it.
2867
+ Object.defineProperty(Validator.prototype, '_externalChecks', {
2868
+ configurable: true,
2869
+ get() {
2870
+ if (this._validateTail !== null && this._validateTail()) return true;
2871
+ if (this._verdictTail !== null && typeof this._verdictTail() === 'function') return true;
2872
+ return false;
2873
+ },
2874
+ });
2875
+
2876
+ Validator.prototype._extendVerdict = function (resolve) {
2877
+ if (typeof resolve !== 'function') throw new TypeError('_extendVerdict expects a function');
2878
+ _leaveIdentityCache(this);
2879
+ const prev = this._verdictTail;
2880
+ this._verdictTail = prev === null ? resolve : () => {
2881
+ const a = prev(), b = resolve();
2882
+ if (typeof a !== 'function') return b;
2883
+ if (typeof b !== 'function') return a;
2884
+ return (d) => a(d) && b(d);
2885
+ };
2886
+ // A method bound before this call was bound without the check. Rebind it.
2887
+ // An unbound one binds through _bindVerdict on its first call.
2888
+ if (Object.prototype.hasOwnProperty.call(this, 'isValidObject')) _bindVerdict(this, this.isValidObject);
2889
+ return this;
2890
+ };
2891
+
2618
2892
  function _defineLazyMethod(name, maker) {
2619
2893
  Object.defineProperty(Validator.prototype, name, {
2620
2894
  configurable: true,
@@ -2660,13 +2934,13 @@ _defineLazyMethod('isValidObject', (self) => (data) => {
2660
2934
  if (_tier.tier === 0) {
2661
2935
  const _plan = buildTier0Plan(self._schemaObj);
2662
2936
  let _n = 0;
2663
- self.isValidObject = (d) => {
2937
+ _bindVerdict(self, (d) => {
2664
2938
  const r = tier0Validate(_plan, d);
2665
2939
  if (++_n === 2) {
2666
2940
  try { self._ensureCodegen(); } catch {}
2667
2941
  }
2668
2942
  return r;
2669
- };
2943
+ });
2670
2944
  } else {
2671
2945
  // `new Function` is a property of the realm, not of the schema: under a
2672
2946
  // strict CSP or `--disallow-code-generation-from-strings` this throws
package/lib/formats.js CHANGED
@@ -117,8 +117,16 @@ function dateTime (s) {
117
117
  // Also used for the dotted tail of an IPv4-mapped IPv6 address, which is why
118
118
  // the work is done over a range rather than a whole string: taking a slice for
119
119
  // it would allocate on a path that exists to avoid allocating.
120
+ // Four dotted decimal octets, 0 to 255, no leading zeros: the same language
121
+ // ipv4Range walks, as one regular expression. The engine answers a dotted quad
122
+ // in about 12 ns where the walk takes 17, for the reason `uri` above found:
123
+ // its character-class loop beats a charCodeAt loop. ipv4Range stays for the
124
+ // embedded form inside an IPv6 address, where the octets are a slice.
125
+ // tests/test_ipv4_regex.js holds the two to the same answers.
126
+ const IPV4 = /^(?:(?:25[0-5]|2[0-4]\d|1\d\d|[1-9]?\d)\.){3}(?:25[0-5]|2[0-4]\d|1\d\d|[1-9]?\d)$/;
127
+
120
128
  function ipv4 (s) {
121
- return ipv4Range(s, 0, s.length);
129
+ return IPV4.test(s);
122
130
  }
123
131
 
124
132
  function ipv4Range (s, from, to) {
@@ -147,8 +155,17 @@ function ipv4Range (s, from, to) {
147
155
  // allocated twice and accepted things like "12345::1"; they also disagreed
148
156
  // with each other about an IPv4 tail. Answers match Node's own `net.isIPv6`,
149
157
  // which the test uses as its oracle.
158
+ // Eight groups of one to four hex digits and nothing else: the uncompressed
159
+ // form, which is always a valid address. The walk below takes 64 ns on one;
160
+ // this answers in about 30, and anything it does not match, compressed forms
161
+ // and embedded IPv4 included, goes to the walk as before. It is only tried from
162
+ // 15 characters, the shortest string it can match, so a short rejection does
163
+ // not pay for it. tests/test_ipv6_fast_path.js holds the subset relation.
164
+ const IPV6_FULL = /^(?:[0-9A-Fa-f]{1,4}:){7}[0-9A-Fa-f]{1,4}$/;
165
+
150
166
  function ipv6 (s) {
151
167
  const n = s.length;
168
+ if (n >= 15 && IPV6_FULL.test(s)) return true;
152
169
  if (n < 2 || n > 45) return false;
153
170
 
154
171
  let end = n;
@@ -256,7 +273,22 @@ SCHEME_CHAR[43] = 1; SCHEME_CHAR[45] = 1; SCHEME_CHAR[46] = 1;
256
273
  // string or scanned again. Reading it out with `slice` and asking the copy for
257
274
  // `lastIndexOf` and two regular expressions allocated a string per URI, and a
258
275
  // document carrying a handful of URLs paid that per field.
276
+ // The shape almost every URI in a document has, as one regular expression: a
277
+ // scheme, then either "//" and a host with no userinfo, port, brackets or
278
+ // percent escapes, or no authority at all, then a tail of characters RFC 3986
279
+ // allows, again without "%". Everything it accepts the walk below accepts too,
280
+ // and it answers the common case in about 25 ns where the walk takes 43 on a
281
+ // 34-character URL, because the engine scans a character class faster than a
282
+ // charCodeAt loop does. Anything it declines, the walk decides as before.
283
+ //
284
+ // The two branches after the colon are exclusive on purpose. With the "//"
285
+ // group merely optional, "http://[::1" could skip it and match "//[::1" as a
286
+ // path, accepting a string the walk rejects for its unclosed bracket.
287
+ // tests/test_uri_fast_path.js holds the subset relation.
288
+ const URI_FAST = /^[A-Za-z][A-Za-z0-9+.\-]*:(?:\/\/[!$&-.0-9;=A-Z_a-z~]*(?:[\/?#][!#$&-;=?-\[\]_a-z~]*)?|(?!\/\/)[!#$&-;=?-\[\]_a-z~]*)$/;
289
+
259
290
  function uri (s) {
291
+ if (URI_FAST.test(s)) return true;
260
292
  const n = s.length;
261
293
  if (n === 0) return false;
262
294
  let c = s.charCodeAt(0);
@@ -762,8 +794,8 @@ const URI_HELPER_TABLES = 'const _uct=new Uint8Array(128);for(let _i=33;_i<127;_
762
794
  'for(let _i=97;_i<103;_i++)_uch[_i]=1;for(let _i=65;_i<71;_i++)_uch[_i]=1;' +
763
795
  'const _ucs=new Uint8Array(128);for(let _i=48;_i<58;_i++)_ucs[_i]=1;' +
764
796
  'for(let _i=97;_i<123;_i++)_ucs[_i]=1;for(let _i=65;_i<91;_i++)_ucs[_i]=1;' +
765
- '_ucs[43]=1;_ucs[45]=1;_ucs[46]=1';
766
- const URI_HELPER_BODY = 'const _n=_s.length;if(_n===0)return false;let _c=_s.charCodeAt(0);if(_c>127||_ucs[_c]===0||(_c>=48&&_c<=57)||_c===43||_c===45||_c===46)return false;let _co=-1;for(let _i=1;_i<_n;_i++){_c=_s.charCodeAt(_i);if(_c===58){_co=_i;break}if(_c>127||_ucs[_c]===0)return false}if(_co===-1)return false;let _i=_co+1;let _ae=_n;if(_s.charCodeAt(_i)===47&&_s.charCodeAt(_i+1)===47){const _as=_i+2;let _at=-1,_fc=-1,_lc=-1,_br=0,_ba=0,_hs=_as;_ae=-1;for(_i=_as;_i<_n;_i++){_c=_s.charCodeAt(_i);if(_c>127||_uct[_c]===0)return false;if(_c===47||_c===63||_c===35){_ae=_i;break}if(_c===37){const _h1=_s.charCodeAt(_i+1),_h2=_s.charCodeAt(_i+2);if(!(_h1<=127)||!(_h2<=127)||_uch[_h1]===0||_uch[_h2]===0)return false;_i+=2;continue}if(_c===64){_ba=_br;_at=_i;_fc=-1;_lc=-1;_hs=_i+1}else if(_c===58){if(_fc===-1)_fc=_i;_lc=_i}else if(_c===91||_c===93){_br=1}}if(_ae===-1)_ae=_n;if(_at!==-1&&_ba)return false;if(_s.charCodeAt(_hs)===91){let _cl=-1;for(let _j=_hs+1;_j<_ae;_j++){if(_s.charCodeAt(_j)===93){_cl=_j;break}}if(_cl===-1)return false;if(_cl+1!==_ae){if(_s.charCodeAt(_cl+1)!==58)return false;for(let _j=_cl+2;_j<_ae;_j++){const _d=_s.charCodeAt(_j);if(_d<48||_d>57)return false}}}else if(_lc!==-1){if(_fc!==_lc)return false;for(let _j=_lc+1;_j<_ae;_j++){const _d=_s.charCodeAt(_j);if(_d<48||_d>57)return false}}_i=_ae}for(;_i<_n;_i++){_c=_s.charCodeAt(_i);if(_c>127||_uct[_c]===0)return false;if(_c===37){const _h1=_s.charCodeAt(_i+1),_h2=_s.charCodeAt(_i+2);if(!(_h1<=127)||!(_h2<=127)||_uch[_h1]===0||_uch[_h2]===0)return false;_i+=2}}return true;';
797
+ '_ucs[43]=1;_ucs[45]=1;_ucs[46]=1;const _ufa=/' + URI_FAST.source + '/';
798
+ const URI_HELPER_BODY = 'if(_ufa.test(_s))return true;const _n=_s.length;if(_n===0)return false;let _c=_s.charCodeAt(0);if(_c>127||_ucs[_c]===0||(_c>=48&&_c<=57)||_c===43||_c===45||_c===46)return false;let _co=-1;for(let _i=1;_i<_n;_i++){_c=_s.charCodeAt(_i);if(_c===58){_co=_i;break}if(_c>127||_ucs[_c]===0)return false}if(_co===-1)return false;let _i=_co+1;let _ae=_n;if(_s.charCodeAt(_i)===47&&_s.charCodeAt(_i+1)===47){const _as=_i+2;let _at=-1,_fc=-1,_lc=-1,_br=0,_ba=0,_hs=_as;_ae=-1;for(_i=_as;_i<_n;_i++){_c=_s.charCodeAt(_i);if(_c>127||_uct[_c]===0)return false;if(_c===47||_c===63||_c===35){_ae=_i;break}if(_c===37){const _h1=_s.charCodeAt(_i+1),_h2=_s.charCodeAt(_i+2);if(!(_h1<=127)||!(_h2<=127)||_uch[_h1]===0||_uch[_h2]===0)return false;_i+=2;continue}if(_c===64){_ba=_br;_at=_i;_fc=-1;_lc=-1;_hs=_i+1}else if(_c===58){if(_fc===-1)_fc=_i;_lc=_i}else if(_c===91||_c===93){_br=1}}if(_ae===-1)_ae=_n;if(_at!==-1&&_ba)return false;if(_s.charCodeAt(_hs)===91){let _cl=-1;for(let _j=_hs+1;_j<_ae;_j++){if(_s.charCodeAt(_j)===93){_cl=_j;break}}if(_cl===-1)return false;if(_cl+1!==_ae){if(_s.charCodeAt(_cl+1)!==58)return false;for(let _j=_cl+2;_j<_ae;_j++){const _d=_s.charCodeAt(_j);if(_d<48||_d>57)return false}}}else if(_lc!==-1){if(_fc!==_lc)return false;for(let _j=_lc+1;_j<_ae;_j++){const _d=_s.charCodeAt(_j);if(_d<48||_d>57)return false}}_i=_ae}for(;_i<_n;_i++){_c=_s.charCodeAt(_i);if(_c>127||_uct[_c]===0)return false;if(_c===37){const _h1=_s.charCodeAt(_i+1),_h2=_s.charCodeAt(_i+2);if(!(_h1<=127)||!(_h2<=127)||_uch[_h1]===0||_uch[_h2]===0)return false;_i+=2}}return true;';
767
799
  const uriHelperSource = (name) =>
768
800
  URI_HELPER_TABLES + ';function ' + name + '(_s){' + URI_HELPER_BODY + '}';
769
801
 
@@ -1010,4 +1042,4 @@ function durationSource (v, isStr) {
1010
1042
  return isStr ? inner : `if(typeof ${v}==='string'&&${inner.slice(3)}`;
1011
1043
  }
1012
1044
 
1013
- module.exports = { uriHelperSource, date, ipv4, dateTime, ipv6, hostname, uri, uriReference, uuid, time, noReserved, jsonPointer, relativeJsonPointer, uriTemplate, email, duration, uriChars, uriAuthority, uriCharsSource, uriAuthoritySource, iri, iriReference, idnEmail, iriSource, iriReferenceSource, idnEmailSource, dateSource, ipv4Source, dateTimeSource, ipv6Source, hostnameSource, uriSource, uuidSource, timeSource, noReservedSource, jsonPointerSource, relativeJsonPointerSource, uriTemplateSource, emailSource, durationSource };
1045
+ module.exports = { IPV4, IPV6_FULL, ipv4Range, URI_FAST, uriHelperSource, date, ipv4, dateTime, ipv6, hostname, uri, uriReference, uuid, time, noReserved, jsonPointer, relativeJsonPointer, uriTemplate, email, duration, uriChars, uriAuthority, uriCharsSource, uriAuthoritySource, iri, iriReference, idnEmail, iriSource, iriReferenceSource, idnEmailSource, dateSource, ipv4Source, dateTimeSource, ipv6Source, hostnameSource, uriSource, uuidSource, timeSource, noReservedSource, jsonPointerSource, relativeJsonPointerSource, uriTemplateSource, emailSource, durationSource };
@@ -968,6 +968,23 @@ function hasNestedIdScope(node, isRoot = true) {
968
968
  // only safe way out.
969
969
  const DECLINE = Symbol('ata.decline')
970
970
 
971
+ // The error and combined generators compile the subschemas of not, if, contains
972
+ // and their anyOf/oneOf closures with the boolean generator. That generator
973
+ // follows a reference back to the root with `_validate` and a reference cycle
974
+ // with a named function in its preamble, and neither exists inside the other
975
+ // two generators' functions: the first threw a ReferenceError on the first
976
+ // document that reached it, and the second cut the cycle, so the reference
977
+ // checked nothing. While `nestedBoolean` is set, genCode declines at those
978
+ // points instead, and the caller's generator declines the schema.
979
+ function nestedGenCode(schema, v, lines, ctx) {
980
+ ctx.nestedBoolean = (ctx.nestedBoolean || 0) + 1
981
+ try {
982
+ genCode(schema, v, lines, ctx)
983
+ } finally {
984
+ ctx.nestedBoolean--
985
+ }
986
+ }
987
+
971
988
  const CYCLE_DEPTH = 512
972
989
 
973
990
  // Declarations the guarded functions close over. Emitted once, ahead of them.
@@ -1567,7 +1584,7 @@ function compileToJSCodegen(schema, schemaMap, userFormats) {
1567
1584
  const defSets = ctx.defFns ? Array.from(ctx.defFns.values()) : []
1568
1585
  const needsGuard = ctx.usesRecursion || defSets.length > 0
1569
1586
 
1570
- let body, hybridBody
1587
+ let body, hybridBody, tailBody
1571
1588
  if (ctx.usesRecursion) {
1572
1589
  // Self-recursive: a named body reached through the cycle guard, so data
1573
1590
  // that points back at itself settles instead of exhausting the stack.
@@ -1577,15 +1594,21 @@ function compileToJSCodegen(schema, schemaMap, userFormats) {
1577
1594
  body = `${decl}function _run(d){\n ${run}\n }\n return _run(d)`
1578
1595
  // Hybrid: keep the boolean answer, wrap only the outer call
1579
1596
  hybridBody = `${decl}function _run(d){\n ${run}\n }\n return _run(d)?R:E(d)`
1597
+ tailBody = `${decl}function _run(d){\n ${run}\n }\n return _run(d)&&__ataTail(d)`
1580
1598
  } else if (defSets.length > 0) {
1581
1599
  // No self-reference at the root, but a def on a cycle can still be
1582
1600
  // re-entered with the same value; the guarded functions need their state.
1583
1601
  const run = emitGuardedRun(`return _body(d)`, '', defSets)
1584
1602
  body = `function _body(d){\n ${checkStr}\n return true\n }\n function _run(d){\n ${run}\n }\n return _run(d)`
1585
1603
  hybridBody = `function _body(d){\n ${checkStr}\n return true\n }\n function _run(d){\n ${run}\n }\n return _run(d)?R:E(d)`
1604
+ tailBody = `function _body(d){\n ${checkStr}\n return true\n }\n function _run(d){\n ${run}\n }\n return _run(d)&&__ataTail(d)`
1586
1605
  } else {
1587
1606
  body = checkStr + '\n return true'
1588
1607
  hybridBody = replaceTopLevel(checkStr + '\n return R')
1608
+ // An early accept inside the checks has to reach the tail too, so every
1609
+ // top-level `return true` becomes the tail call, by the same rewrite the
1610
+ // hybrid above relies on and test_hybrid_agreement holds to the suite.
1611
+ tailBody = replaceTopLevel(checkStr + '\n return true', 'return __ataTail(d)', 'return false')
1589
1612
  }
1590
1613
 
1591
1614
  const guardStr = needsGuard ? emitGuardState() : ''
@@ -1610,6 +1633,26 @@ function compileToJSCodegen(schema, schemaMap, userFormats) {
1610
1633
  boolFn._hybridFactory = (R, E) => hybridFactory(...closureValues, R, E)
1611
1634
  } catch {}
1612
1635
 
1636
+ // The same function with one more check where it would return true, for a
1637
+ // wrapper that enforces something the schema does not carry (see
1638
+ // Validator#_extendVerdict). Calling the check from here rather than from a
1639
+ // function around this one is the point: this function is too large for
1640
+ // the engine to inline, so a wrapper is a second call on every document,
1641
+ // and a rejection never reaches the check at all. Built on first request,
1642
+ // since almost no validator is extended. Null when code generation is
1643
+ // refused, and the caller composes instead.
1644
+ let tailFactory
1645
+ boolFn._withTail = (tail) => {
1646
+ if (tailFactory === undefined) {
1647
+ try {
1648
+ tailFactory = new Function(...closureNames, '__ataTail', `${preambleStr}return function(d){${tailBody}}`)
1649
+ } catch {
1650
+ tailFactory = null
1651
+ }
1652
+ }
1653
+ return tailFactory === null ? null : tailFactory(...closureValues, tail)
1654
+ }
1655
+
1613
1656
  // Store source for standalone compilation. Regex declarations are NOT
1614
1657
  // inlined into the body: they are carried by _closures and the emitters
1615
1658
  // declare them once at load scope. Inlining them here put a
@@ -1663,7 +1706,7 @@ function compileToJSCodegen(schema, schemaMap, userFormats) {
1663
1706
 
1664
1707
  // Replace top-level `return false` → `return E(d)` and `return true` → `return R`.
1665
1708
  // Tracks function nesting depth to preserve nested function internals.
1666
- function replaceTopLevel(code) {
1709
+ function replaceTopLevel(code, onTrue = 'return R', onFalse = 'return E(d)') {
1667
1710
  let fnDepth = 0, result = '', i = 0
1668
1711
  while (i < code.length) {
1669
1712
  // A nested closure keeps its own returns. Both spellings count: a
@@ -1690,10 +1733,10 @@ function replaceTopLevel(code) {
1690
1733
  i++
1691
1734
  }
1692
1735
  } else if (code.startsWith('return false', i)) {
1693
- result += 'return E(d)'
1736
+ result += onFalse
1694
1737
  i += 12
1695
1738
  } else if (code.startsWith('return true', i) && (i + 11 >= code.length || !/[a-zA-Z_$]/.test(code[i + 11]))) {
1696
- result += 'return R'
1739
+ result += onTrue
1697
1740
  i += 11
1698
1741
  } else {
1699
1742
  result += code[i]
@@ -1852,6 +1895,7 @@ function genCode(schema, v, lines, ctx, knownType) {
1852
1895
  if (schema.$ref) {
1853
1896
  // Self-reference "#" — recursive call to root validator
1854
1897
  if (schema.$ref === '#') {
1898
+ if (ctx.nestedBoolean) throw DECLINE
1855
1899
  ctx.usesRecursion = true
1856
1900
  lines.push(`if(!_validate(${v}))return false`)
1857
1901
  if (!hasSiblings) return
@@ -1894,7 +1938,7 @@ function genCode(schema, v, lines, ctx, knownType) {
1894
1938
  }
1895
1939
  lines.push(`if(!${fnName}(${v}))return false`)
1896
1940
  if (!hasSiblings) return
1897
- } else if (ctx.refStack.has(schema.$ref)) { if (!hasSiblings) return }
1941
+ } else if (ctx.refStack.has(schema.$ref)) { if (ctx.nestedBoolean) throw DECLINE; if (!hasSiblings) return }
1898
1942
  else {
1899
1943
  ctx.refStack.add(schema.$ref)
1900
1944
  genCode(ctx.rootDefs[defName], v, lines, ctx, knownType)
@@ -1906,7 +1950,7 @@ function genCode(schema, v, lines, ctx, knownType) {
1906
1950
  const entry = ctx.rootDefs && ctx.rootDefs[schema.$ref]
1907
1951
  const anchorTarget = entry && entry.raw ? entry.raw : (ctx.anchors && ctx.anchors[schema.$ref])
1908
1952
  if (anchorTarget) {
1909
- if (ctx.refStack.has(schema.$ref)) { if (!hasSiblings) return }
1953
+ if (ctx.refStack.has(schema.$ref)) { if (ctx.nestedBoolean) throw DECLINE; if (!hasSiblings) return }
1910
1954
  else {
1911
1955
  ctx.refStack.add(schema.$ref)
1912
1956
  genCode(anchorTarget, v, lines, ctx, knownType)
@@ -1949,11 +1993,13 @@ function genCode(schema, v, lines, ctx, knownType) {
1949
1993
  const target = ctx.anchors[anchorKey]
1950
1994
  if (target === ctx.rootSchema) {
1951
1995
  // Self-recursive: generate _validate(v) call
1996
+ if (ctx.nestedBoolean) throw DECLINE
1952
1997
  ctx.usesRecursion = true
1953
1998
  lines.push(`if(!_validate(${v}))return false`)
1954
1999
  } else {
1955
2000
  // Different schema: inline the target validation
1956
2001
  const refKey = '$dynamicRef:' + anchorKey
2002
+ if (ctx.refStack.has(refKey) && ctx.nestedBoolean) throw DECLINE
1957
2003
  if (!ctx.refStack.has(refKey)) {
1958
2004
  ctx.refStack.add(refKey)
1959
2005
  genCode(target, v, lines, ctx, knownType)
@@ -3147,8 +3193,20 @@ const FORMAT_CODEGEN = {
3147
3193
  'uri-reference': (v, isStr) => isStr
3148
3194
  ? `{${_formats.uriCharsSource(v, '0')}}`
3149
3195
  : `if(typeof ${v}==='string'){${_formats.uriCharsSource(v, '0')}}`,
3150
- ipv4: _formats.ipv4Source,
3151
- ipv6: _formats.ipv6Source,
3196
+ ipv4: (v, isStr, ctx) => {
3197
+ if (!ctx) return _formats.ipv4Source(v, isStr)
3198
+ // Hoisted: a regex literal in the body would build a new RegExp per call.
3199
+ hoistOnce(ctx, '_ip4Hoisted', 'const _ip4=/' + _formats.IPV4.source + '/')
3200
+ return isStr ? `if(!_ip4.test(${v}))return false` : `if(typeof ${v}==='string'&&!_ip4.test(${v}))return false`
3201
+ },
3202
+ ipv6: (v, isStr, ctx) => {
3203
+ if (!ctx) return _formats.ipv6Source(v, isStr)
3204
+ hoistOnce(ctx, '_ip6Hoisted', 'const _ip6f=/' + _formats.IPV6_FULL.source + '/')
3205
+ // The fast accept jumps past the walk; the walk's `return false`s are what
3206
+ // the error generators rewrite, so they stay as they are.
3207
+ const body = `_ip6:{if(${v}.length>=15&&_ip6f.test(${v}))break _ip6;${_formats.ipv6Source(v, true).replace(/^\{|\}$/g, '')}}`
3208
+ return isStr ? body : `if(typeof ${v}==='string'){${body}}`
3209
+ },
3152
3210
  hostname: _formats.hostnameSource,
3153
3211
  }
3154
3212
 
@@ -3512,7 +3570,23 @@ function compileToJSCodegenWithErrors(schema, schemaMap, userFormats, sourceOpts
3512
3570
  // first invalid document threw a ReferenceError instead of reporting.
3513
3571
  const cvars = ctx.closureVars
3514
3572
  const cvals = ctx.closureVals
3515
- if (ctx.usesSafeRe) {
3573
+ // The helpers are constants: the uri tables, the compiled patterns, the
3574
+ // branch-collapse functions, the name sets. In `body` they sit inside the
3575
+ // function and were rebuilt on every call, which on a product schema with
3576
+ // six URLs and one anyOf cost about 450 ns before a single check ran, more
3577
+ // than every check and every error together. Here they run once, in a
3578
+ // factory, as the verdict function's preamble does. Only for the plain
3579
+ // shape: with $defs or recursion the helpers include functions that read
3580
+ // the per-call guard state declared in the body, so those keep `body` as
3581
+ // it is. `_errSource` below stays `body` either way, for the emitters.
3582
+ const hoistable = !ctx.usesRecursion && defSetsE.length === 0 && helpersE !== ''
3583
+ if (hoistable) {
3584
+ const factoryBody = helpersE + 'return function(d,_all){const _e=[];\n ' + checkStr +
3585
+ '\n return{valid:_e.length===0,errors:_e}}'
3586
+ const params = ctx.usesSafeRe ? [...cvars, '__ataSafeRe'] : [...cvars]
3587
+ const args = ctx.usesSafeRe ? [...cvals, compileSafe] : [...cvals]
3588
+ fn = new Function(...params, factoryBody)(...args)
3589
+ } else if (ctx.usesSafeRe) {
3516
3590
  // Inlined helperCode references __ataSafeRe; bind the safe-regex factory.
3517
3591
  // Standalone keeps the inline _errSource — the embed defines __ataSafeRe.
3518
3592
  const built = new Function(...cvars, '__ataSafeRe', 'd', '_all', body)
@@ -4124,7 +4198,7 @@ function genCodeE(schema, v, pathExpr, lines, ctx, schemaPrefix) {
4124
4198
  if (schema.contains !== undefined) {
4125
4199
  const ci = ctx.varCounter++
4126
4200
  const subLines = []
4127
- genCode(schema.contains, `_cv`, subLines, ctx)
4201
+ nestedGenCode(schema.contains, `_cv`, subLines, ctx)
4128
4202
  const fnBody = subLines.length === 0 ? `return true` : `${subLines.join(';')};return true`
4129
4203
  const minC = schema.minContains !== undefined ? schema.minContains : 1
4130
4204
  const maxC = schema.maxContains
@@ -4156,7 +4230,7 @@ function genCodeE(schema, v, pathExpr, lines, ctx, schemaPrefix) {
4156
4230
  // not
4157
4231
  if (schema.not !== undefined) {
4158
4232
  const subLines = []
4159
- genCode(schema.not, '_nv', subLines, ctx)
4233
+ nestedGenCode(schema.not, '_nv', subLines, ctx)
4160
4234
  const nfn = subLines.length === 0 ? `function(_nv){return true}` : `function(_nv){${subLines.join(';')};return true}`
4161
4235
  const fi = ctx.varCounter++
4162
4236
  lines.push(`{const _nf${fi}=${nfn};if(_nf${fi}(${v})){${fail('not', 'not', '{}', "'must NOT be valid'")}}}`)
@@ -4165,7 +4239,7 @@ function genCodeE(schema, v, pathExpr, lines, ctx, schemaPrefix) {
4165
4239
  // if/then/else
4166
4240
  if (schema.if !== undefined) {
4167
4241
  const ifLines = []
4168
- genCode(schema.if, '_iv', ifLines, ctx)
4242
+ nestedGenCode(schema.if, '_iv', ifLines, ctx)
4169
4243
  const fi = ctx.varCounter++
4170
4244
  const ifFn = ifLines.length === 0
4171
4245
  ? `function(_iv){return true}`
@@ -4270,22 +4344,34 @@ function compileToJSCombined(schema, VALID_RESULT, schemaMap, userFormats) {
4270
4344
  const ctx = { varCounter: 0, helperCode: [], shared: [], closureVars: ['_cpLen'], closureVals: [_cpLen],
4271
4345
  rootDefs: cRootDefs, refStack: new Set(), schemaMap: schemaMap || null, anchors: cAnchors, rootSchema: schema, userFormats: userFormats || null }
4272
4346
  const lines = []
4273
- genCodeC(schema, 'd', '', lines, ctx, '#')
4347
+ try {
4348
+ genCodeC(schema, 'd', '', lines, ctx, '#')
4349
+ } catch (e) {
4350
+ if (e === DECLINE) return null
4351
+ throw e
4352
+ }
4274
4353
  if (lines.length === 0) return () => VALID_RESULT
4275
4354
 
4276
- // Use factory pattern: closure vars (regexes, etc.) created once, not per call
4277
- const closureParams = ctx.closureVars.join(',')
4278
- // Lazy error array — no allocation for valid data (the common case)
4355
+ // Use factory pattern: closure vars (regexes, etc.) created once, not per call.
4356
+ // The helpers are constants too and are declared in the factory: inside the
4357
+ // function they were rebuilt on every call. `__ataSafeRe` is bound when a
4358
+ // helper compiles a pattern through it, which the boolean generator does for
4359
+ // subschemas under not/if/contains; without the binding those threw a
4360
+ // ReferenceError on the first document that reached them.
4361
+ const closureNames = ctx.usesSafeRe ? [...ctx.closureVars, '__ataSafeRe'] : ctx.closureVars
4362
+ const closureArgs = ctx.usesSafeRe ? [...ctx.closureVals, compileSafe] : ctx.closureVals
4363
+ const closureParams = closureNames.join(',')
4364
+ const helpers = ctx.helperCode.length ? ctx.helperCode.join('\n ') + '\n ' : ''
4365
+ // Lazy error array: no allocation for valid data (the common case)
4279
4366
  const inner = `let _e;\n ` +
4280
- (ctx.helperCode.length ? ctx.helperCode.join('\n ') + '\n ' : '') +
4281
4367
  lines.join('\n ') +
4282
4368
  `\n return _e?{valid:false,errors:_e}:R`
4283
4369
 
4284
4370
  try {
4285
- if (typeof process !== 'undefined' && process.env && process.env.ATA_DUMP_CODEGEN) console.log('=== COMBINED CODEGEN ===\n' + inner + '\n=== CLOSURE VARS: ' + ctx.closureVars.length + ' ===')
4371
+ if (typeof process !== 'undefined' && process.env && process.env.ATA_DUMP_CODEGEN) console.log('=== COMBINED CODEGEN ===\n' + helpers + inner + '\n=== CLOSURE VARS: ' + ctx.closureVars.length + ' ===')
4286
4372
  const factory = new Function('R' + (closureParams ? ',' + closureParams : ''),
4287
- `return function(d){${inner}}`)
4288
- return factory(VALID_RESULT, ...ctx.closureVals)
4373
+ `${helpers}return function(d){${inner}}`)
4374
+ return factory(VALID_RESULT, ...closureArgs)
4289
4375
  } catch (e) {
4290
4376
  if (typeof process !== 'undefined' && process.env && process.env.ATA_DEBUG) console.error('compileToJSCombined error:', e.message, '\n', inner.slice(0, 500))
4291
4377
  return null
@@ -4357,17 +4443,17 @@ function genCodeC(schema, v, pathExpr, lines, ctx, schemaPrefix) {
4357
4443
  const anchorKey = schema.$dynamicRef.startsWith('#') ? schema.$dynamicRef : '#' + schema.$dynamicRef
4358
4444
  if (ctx.anchors && ctx.anchors[anchorKey]) {
4359
4445
  const target = ctx.anchors[anchorKey]
4360
- if (target === ctx.rootSchema) {
4361
- // Self-recursive: bail to non-combined path (combined doesn't support named recursion)
4362
- // Just skip — the hybrid path will handle this via jsFn + errFn
4363
- } else {
4364
- const refKey = '$dynamicRef:' + anchorKey
4365
- if (!ctx.refStack.has(refKey)) {
4366
- ctx.refStack.add(refKey)
4367
- genCodeC(target, v, pathExpr, lines, ctx, schemaPrefix)
4368
- ctx.refStack.delete(refKey)
4369
- }
4370
- }
4446
+ // This generator has no named functions, so it cannot follow a reference
4447
+ // back to the root or around a cycle. It used to emit nothing in both
4448
+ // cases, which the function then answered as valid: a document the other
4449
+ // generators reject passed through validateJSON. It declines instead,
4450
+ // and the schema takes the error generator.
4451
+ if (target === ctx.rootSchema) throw DECLINE
4452
+ const refKey = '$dynamicRef:' + anchorKey
4453
+ if (ctx.refStack.has(refKey)) throw DECLINE
4454
+ ctx.refStack.add(refKey)
4455
+ genCodeC(target, v, pathExpr, lines, ctx, schemaPrefix)
4456
+ ctx.refStack.delete(refKey)
4371
4457
  }
4372
4458
  }
4373
4459
 
@@ -4850,7 +4936,7 @@ function genCodeC(schema, v, pathExpr, lines, ctx, schemaPrefix) {
4850
4936
  if (schema.contains !== undefined) {
4851
4937
  const ci = ctx.varCounter++
4852
4938
  const subLines = []
4853
- genCode(schema.contains, `_cv`, subLines, ctx)
4939
+ nestedGenCode(schema.contains, `_cv`, subLines, ctx)
4854
4940
  const fnBody = subLines.length === 0 ? `return true` : `${subLines.join(';')};return true`
4855
4941
  const minC = schema.minContains !== undefined ? schema.minContains : 1
4856
4942
  const maxC = schema.maxContains
@@ -4866,20 +4952,20 @@ function genCodeC(schema, v, pathExpr, lines, ctx, schemaPrefix) {
4866
4952
  // anyOf
4867
4953
  if (schema.anyOf) {
4868
4954
  const fi = ctx.varCounter++
4869
- const fns = schema.anyOf.map(sub => { const sl = []; genCode(sub, '_av', sl, ctx); return sl.length === 0 ? `function(_av){return true}` : `function(_av){${sl.join(';')};return true}` })
4955
+ const fns = schema.anyOf.map(sub => { const sl = []; nestedGenCode(sub, '_av', sl, ctx); return sl.length === 0 ? `function(_av){return true}` : `function(_av){${sl.join(';')};return true}` })
4870
4956
  lines.push(`{const _af${fi}=[${fns.join(',')}];let _am=false;for(let _ai=0;_ai<_af${fi}.length;_ai++){if(_af${fi}[_ai](${v})){_am=true;break}}if(!_am){${fail('anyOf', 'anyOf', '{}', "'must match a schema in anyOf'")}}}`)
4871
4957
  }
4872
4958
 
4873
4959
  // oneOf
4874
4960
  if (schema.oneOf) {
4875
4961
  const fi = ctx.varCounter++
4876
- const fns = schema.oneOf.map(sub => { const sl = []; genCode(sub, '_ov', sl, ctx); return sl.length === 0 ? `function(_ov){return true}` : `function(_ov){${sl.join(';')};return true}` })
4962
+ const fns = schema.oneOf.map(sub => { const sl = []; nestedGenCode(sub, '_ov', sl, ctx); return sl.length === 0 ? `function(_ov){return true}` : `function(_ov){${sl.join(';')};return true}` })
4877
4963
  lines.push(`{const _of${fi}=[${fns.join(',')}];let _oc=0;for(let _oi=0;_oi<_of${fi}.length;_oi++){if(_of${fi}[_oi](${v}))_oc++;if(_oc>1)break}if(_oc!==1){${fail('oneOf', 'oneOf', '{}', "'must match exactly one schema in oneOf'")}}}`)
4878
4964
  }
4879
4965
 
4880
4966
  // not
4881
4967
  if (schema.not !== undefined) {
4882
- const sl = []; genCode(schema.not, '_nv', sl, ctx)
4968
+ const sl = []; nestedGenCode(schema.not, '_nv', sl, ctx)
4883
4969
  const nfn = sl.length === 0 ? `function(_nv){return true}` : `function(_nv){${sl.join(';')};return true}`
4884
4970
  const fi = ctx.varCounter++
4885
4971
  lines.push(`{const _nf${fi}=${nfn};if(_nf${fi}(${v})){${fail('not', 'not', '{}', "'must NOT be valid'")}}}`)
@@ -4887,7 +4973,7 @@ function genCodeC(schema, v, pathExpr, lines, ctx, schemaPrefix) {
4887
4973
 
4888
4974
  // if/then/else
4889
4975
  if (schema.if !== undefined) {
4890
- const sl = []; genCode(schema.if, '_iv', sl, ctx)
4976
+ const sl = []; nestedGenCode(schema.if, '_iv', sl, ctx)
4891
4977
  const fi = ctx.varCounter++
4892
4978
  const ifFn = sl.length === 0 ? `function(_iv){return true}` : `function(_iv){${sl.join(';')};return true}`
4893
4979
  lines.push(`{const _if${fi}=${ifFn}`)
package/lib/t.js CHANGED
@@ -15,15 +15,15 @@ const OPTIONAL = Symbol.for('ata.t.optional');
15
15
  const { attach: attachRefine } = require('./refine');
16
16
 
17
17
  function string(opts) {
18
- return Object.assign({ type: 'string' }, opts);
18
+ return opts === undefined ? { type: 'string' } : { type: 'string', ...opts };
19
19
  }
20
20
 
21
21
  function number(opts) {
22
- return Object.assign({ type: 'number' }, opts);
22
+ return opts === undefined ? { type: 'number' } : { type: 'number', ...opts };
23
23
  }
24
24
 
25
25
  function integer(opts) {
26
- return Object.assign({ type: 'integer' }, opts);
26
+ return opts === undefined ? { type: 'integer' } : { type: 'integer', ...opts };
27
27
  }
28
28
 
29
29
  function boolean() {
@@ -47,7 +47,7 @@ function enumOf(values) {
47
47
  }
48
48
 
49
49
  function array(items, opts) {
50
- return Object.assign({ type: 'array', items }, opts);
50
+ return opts === undefined ? { type: 'array', items } : { type: 'array', items, ...opts };
51
51
  }
52
52
 
53
53
  function tuple(items, opts) {
@@ -63,26 +63,46 @@ function tuple(items, opts) {
63
63
  }
64
64
 
65
65
  function record(values, opts) {
66
- return Object.assign({ type: 'object', additionalProperties: values }, opts);
66
+ return opts === undefined ? { type: 'object', additionalProperties: values } : { type: 'object', additionalProperties: values, ...opts };
67
67
  }
68
68
 
69
69
  function object(properties, opts) {
70
- const props = {};
71
- const required = [];
70
+ // The common case has no optional member: then `properties` is copied as it
71
+ // is and the fresh key list is `required`. The values come from one
72
+ // Object.values call because reading them back key by key was the larger
73
+ // part of this function's cost. A member marked optional sends the whole
74
+ // object down the slower path that rewrites it.
72
75
  const keys = Object.keys(properties);
73
- for (const key of keys) {
74
- const schema = properties[key];
75
- if (schema && schema[OPTIONAL] === true) {
76
- const { [OPTIONAL]: _drop, ...rest } = schema;
77
- props[key] = rest;
78
- } else {
79
- props[key] = schema;
80
- required.push(key);
76
+ const values = Object.values(properties);
77
+ let anyOptional = false;
78
+ for (let i = 0; i < values.length; i++) {
79
+ const schema = values[i];
80
+ if (schema && schema[OPTIONAL] === true) { anyOptional = true; break; }
81
+ }
82
+ let out;
83
+ if (!anyOptional) {
84
+ out = keys.length
85
+ ? { type: 'object', properties: { ...properties }, required: keys }
86
+ : { type: 'object', properties: { ...properties } };
87
+ } else {
88
+ const props = {};
89
+ const required = [];
90
+ for (let i = 0; i < keys.length; i++) {
91
+ const key = keys[i];
92
+ const schema = values[i];
93
+ if (schema && schema[OPTIONAL] === true) {
94
+ const { [OPTIONAL]: _drop, ...rest } = schema;
95
+ props[key] = rest;
96
+ } else {
97
+ props[key] = schema;
98
+ required.push(key);
99
+ }
81
100
  }
101
+ out = required.length
102
+ ? { type: 'object', properties: props, required }
103
+ : { type: 'object', properties: props };
82
104
  }
83
- const out = { type: 'object', properties: props };
84
- if (required.length) out.required = required;
85
- return Object.assign(out, opts);
105
+ return opts === undefined ? out : Object.assign(out, opts);
86
106
  }
87
107
 
88
108
  function optional(schema) {
package/lib/version.js CHANGED
@@ -7,4 +7,4 @@
7
7
  //
8
8
  // Kept in lockstep with package.json by `tests/test_version_sync.js`.
9
9
 
10
- module.exports = '1.30.3';
10
+ module.exports = '1.31.1';
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "ata-validator",
3
- "version": "1.30.3",
3
+ "version": "1.31.1",
4
4
  "description": "JSON Schema validation that compiles for speed and still runs where code generation is blocked. Compiled and interpreted engines answer identically at 100% of the official suite. TypeScript inference, Standard Schema V1, and a build step that emits dependency-free modules.",
5
5
  "main": "index.js",
6
6
  "module": "index.mjs",
@@ -51,7 +51,7 @@
51
51
  "release:check": "node scripts/regen-safe-regex-source.js && node tests/test_pack_purity.js && node scripts/check-doc-coverage.js && node tests/test_error_codes_lock.js && node tests/test_safe_regex_source_sync.js && node tests/test_version_sync.js",
52
52
  "build": "cmake-js build --target ata",
53
53
  "rebuild": "cmake-js rebuild --target ata",
54
- "test": "node test.js && node tests/test_removed_aot_methods.js && node tests/test_no_native.js && node tests/test_no_eval.js && node tests/test_property_dependencies.js && node tests/test_v1_dialect.js && node tests/test_buffer_path_parity.js && node tests/test_buffer_gate.js && node tests/test_scanner_differential.js && node tests/test_validate_json_scanner_cost.js && node tests/test_buffer_reject_cost.js && node tests/test_nan_verdict.js && node tests/test_remove_additional_nested.js && node tests/test_exclusive_bounds.js && node tests/test_engine_differential.js && node tests/test_error_shape_differential.js && node tests/test_output_format.js && node tests/test_retry_message.js && node tests/test_describe_schema.js && node tests/test_aot_parse.js && node tests/test_draft7_semantics.js && node tests/test_metaschema_ref.js && node tests/test_pure_js_unsupported.js && node tests/test_native_load_order.js && node tests/test_pack_purity.js && node tests/test_make_native_package.js && node tests/test_browser_nofs.js && node tests/test_browser_imports_guard.js && node tests/test_version_sync.js && node tests/test_native_loaded.js && node tests/test_safe_regex_source_sync.js && node tests/test_t_builder.js && node tests/test_async_refine.js && node tests/test_safe_regex.js && node tests/test_safe_regex_integration.js && node tests/test_aot_build.js && node tests/test_aot_differential.js && node tests/test_aot_cli_build.js && node tests/test_aot_cli_smoke.js && node tests/test_bundle_standalone.js && node tests/test_standalone_anyof.js && node tests/test_standalone_formats.js && node tests/test_aot_format_mode.js && node tests/test_aot_additional_props_errors.js && node tests/test_id_anchor_refs.js && node tests/test_engine_routing.js && node tests/test_engine_option.js && node tests/test_compile_cache_order.js && node tests/test_engine_diagnostic.js && node tests/test_format_engine_parity.js && node tests/test_error_accumulator_collision.js && node tests/test_uri_helper_parity.js && node tests/test_formats_single_pass.js && node tests/test_email_format_cost.js && node tests/test_format_single_error.js && node tests/test_defs_pointer_alias.js && node tests/test_cross_doc_root_ref.js && node tests/test_codegen_entrypoint_agreement.js && node tests/test_hybrid_agreement.js && node tests/test_codegen_edge_shapes.js && node tests/test_deferred_def_scope.js && node tests/test_node_scoped_ctx_flags.js && node tests/test_fast_slot_exhaustion.js && node tests/test_ajv_errors.js && node tests/test_custom_keywords.js && node tests/test_strict_schema.js && node tests/test_aot_external_checks.js && node tests/test_ajv_parity.js && node tests/test_user_format_error_path.js && node tests/test_unevaluated_error_path.js && node tests/test_unevaluated_error_shape.js && node tests/test_pattern_properties_errors.js && node tests/test_additional_properties_scaling.js && node tests/test_additional_props_combined_cost.js && node tests/test_no_input_mutation.js && node tests/test_typed_validator_runner.js && node tests/test_define_schema.js && node tests/test_error_codes_lock.js && node tests/test_error_code_lookup.js && node tests/test_error_order.js && node tests/test_error_order_ordinal.js && node tests/test_rejection_shape.js && node tests/test_lazy_normalization.js && node tests/test_value_equality.js && node tests/test_vocabulary.js && node tests/test_schema_scan.js && node tests/test_lazy_errors.js && node tests/test_single_pass_errors.js && node tests/test_lazy_json_errors.js && node tests/test_lazy_instance.js && node tests/test_cyclic_input.js && node tests/test_native_error_codes.js && node tests/test_verdict_preprocess.js && node tests/test_defaults_own_keys.js && node tests/test_plan_compiler.js && node tests/test_nullable.js && node tests/test_validate_and_parse.js && node tests/test_validate_data.js && node tests/test_enrich_error.js && node tests/test_enrich_received.js && node tests/test_rich_errors_optout.js && node tests/test_error_messages.js && node tests/test_source_positions.js && node tests/fuzz_positions.js && node tests/test_malformed_json_termination.js && node tests/test_data_positions.js && node tests/test_targeted_positions.js && node tests/test_position_map_cost.js && node tests/test_targeted_positions_cost.js && node tests/test_aot_staleness.js && node tests/test_file_positions.js && node tests/test_aot_positions_cost.js && node tests/test_schema_hash.js && node tests/test_render_shared.js && node tests/test_renderers.js && node tests/test_additive_fields.js && node tests/test_diagnostic_source.js && node tests/test_diagnose.js && node tests/test_correlate.js && node tests/test_diagnostics_score.js && node tests/test_runtime_error_dx.js && node tests/test_aot_error_dx.js && node tests/test_abort_early.js && node tests/test_branch_collapse.js && node tests/test_suggestions.js && node tests/test_cli_validate.js && node tests/test_cli_version.js && node benchmark/bench_aot_size.mjs",
54
+ "test": "node test.js && node tests/test_removed_aot_methods.js && node tests/test_no_native.js && node tests/test_no_eval.js && node tests/test_property_dependencies.js && node tests/test_v1_dialect.js && node tests/test_buffer_path_parity.js && node tests/test_buffer_gate.js && node tests/test_scanner_differential.js && node tests/test_validate_json_scanner_cost.js && node tests/test_buffer_reject_cost.js && node tests/test_nan_verdict.js && node tests/test_remove_additional_nested.js && node tests/test_exclusive_bounds.js && node tests/test_engine_differential.js && node tests/test_error_shape_differential.js && node tests/test_output_format.js && node tests/test_retry_message.js && node tests/test_describe_schema.js && node tests/test_aot_parse.js && node tests/test_draft7_semantics.js && node tests/test_metaschema_ref.js && node tests/test_pure_js_unsupported.js && node tests/test_native_load_order.js && node tests/test_pack_purity.js && node tests/test_make_native_package.js && node tests/test_browser_nofs.js && node tests/test_browser_imports_guard.js && node tests/test_version_sync.js && node tests/test_native_loaded.js && node tests/test_safe_regex_source_sync.js && node tests/test_t_builder.js && node tests/test_async_refine.js && node tests/test_safe_regex.js && node tests/test_safe_regex_integration.js && node tests/test_aot_build.js && node tests/test_aot_differential.js && node tests/test_aot_cli_build.js && node tests/test_aot_cli_smoke.js && node tests/test_bundle_standalone.js && node tests/test_standalone_anyof.js && node tests/test_standalone_formats.js && node tests/test_aot_format_mode.js && node tests/test_aot_additional_props_errors.js && node tests/test_id_anchor_refs.js && node tests/test_engine_routing.js && node tests/test_engine_option.js && node tests/test_compile_cache_order.js && node tests/test_engine_diagnostic.js && node tests/test_format_engine_parity.js && node tests/test_error_accumulator_collision.js && node tests/test_uri_helper_parity.js && node tests/test_uri_fast_path.js && node tests/test_extend_verdict.js && node tests/test_ipv4_regex.js && node tests/test_ipv6_fast_path.js && node tests/test_nested_recursion_decline.js && node tests/test_formats_single_pass.js && node tests/test_email_format_cost.js && node tests/test_format_single_error.js && node tests/test_defs_pointer_alias.js && node tests/test_cross_doc_root_ref.js && node tests/test_codegen_entrypoint_agreement.js && node tests/test_hybrid_agreement.js && node tests/test_codegen_edge_shapes.js && node tests/test_deferred_def_scope.js && node tests/test_node_scoped_ctx_flags.js && node tests/test_fast_slot_exhaustion.js && node tests/test_ajv_errors.js && node tests/test_custom_keywords.js && node tests/test_strict_schema.js && node tests/test_aot_external_checks.js && node tests/test_ajv_parity.js && node tests/test_user_format_error_path.js && node tests/test_unevaluated_error_path.js && node tests/test_unevaluated_error_shape.js && node tests/test_pattern_properties_errors.js && node tests/test_additional_properties_scaling.js && node tests/test_additional_props_combined_cost.js && node tests/test_no_input_mutation.js && node tests/test_typed_validator_runner.js && node tests/test_define_schema.js && node tests/test_error_codes_lock.js && node tests/test_error_code_lookup.js && node tests/test_error_order.js && node tests/test_error_order_ordinal.js && node tests/test_rejection_shape.js && node tests/test_lazy_normalization.js && node tests/test_value_equality.js && node tests/test_vocabulary.js && node tests/test_schema_scan.js && node tests/test_lazy_errors.js && node tests/test_single_pass_errors.js && node tests/test_lazy_json_errors.js && node tests/test_lazy_instance.js && node tests/test_cyclic_input.js && node tests/test_native_error_codes.js && node tests/test_verdict_preprocess.js && node tests/test_defaults_own_keys.js && node tests/test_plan_compiler.js && node tests/test_nullable.js && node tests/test_validate_and_parse.js && node tests/test_validate_data.js && node tests/test_enrich_error.js && node tests/test_enrich_received.js && node tests/test_rich_errors_optout.js && node tests/test_error_messages.js && node tests/test_source_positions.js && node tests/fuzz_positions.js && node tests/test_malformed_json_termination.js && node tests/test_data_positions.js && node tests/test_targeted_positions.js && node tests/test_position_map_cost.js && node tests/test_targeted_positions_cost.js && node tests/test_aot_staleness.js && node tests/test_file_positions.js && node tests/test_aot_positions_cost.js && node tests/test_schema_hash.js && node tests/test_render_shared.js && node tests/test_renderers.js && node tests/test_additive_fields.js && node tests/test_diagnostic_source.js && node tests/test_diagnose.js && node tests/test_correlate.js && node tests/test_diagnostics_score.js && node tests/test_runtime_error_dx.js && node tests/test_aot_error_dx.js && node tests/test_abort_early.js && node tests/test_branch_collapse.js && node tests/test_suggestions.js && node tests/test_cli_validate.js && node tests/test_cli_version.js && node benchmark/bench_aot_size.mjs",
55
55
  "bench:size": "node benchmark/bench_aot_size.mjs",
56
56
  "test:suite": "node tests/run_suite.js && node tests/run_suite.js draft7 && node tests/run_suite.js v1",
57
57
  "test:compat": "node tests/test_compat.js",
@@ -121,13 +121,13 @@
121
121
  "LICENSE"
122
122
  ],
123
123
  "optionalDependencies": {
124
- "@ata-validator/native-darwin-arm64": "1.30.3",
125
- "@ata-validator/native-darwin-x64": "1.30.3",
126
- "@ata-validator/native-linux-arm64-gnu": "1.30.3",
127
- "@ata-validator/native-linux-arm64-musl": "1.30.3",
128
- "@ata-validator/native-linux-x64-gnu": "1.30.3",
129
- "@ata-validator/native-linux-x64-musl": "1.30.3",
130
- "@ata-validator/native-win32-x64": "1.30.3"
124
+ "@ata-validator/native-darwin-arm64": "1.31.1",
125
+ "@ata-validator/native-darwin-x64": "1.31.1",
126
+ "@ata-validator/native-linux-arm64-gnu": "1.31.1",
127
+ "@ata-validator/native-linux-arm64-musl": "1.31.1",
128
+ "@ata-validator/native-linux-x64-gnu": "1.31.1",
129
+ "@ata-validator/native-linux-x64-musl": "1.31.1",
130
+ "@ata-validator/native-win32-x64": "1.31.1"
131
131
  },
132
132
  "peerDependencies": {
133
133
  "yaml": "^2.0.0"