ata-validator 1.30.3 → 1.31.0

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
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
@@ -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,216 @@ Object.defineProperty(Validator.prototype, "~standard", {
2615
2666
  },
2616
2667
  });
2617
2668
 
2669
+ // Install the verdict method. Every place that binds isValidObject comes
2670
+ // through here, so a check registered with _extendVerdict survives the method
2671
+ // being replaced as the validator compiles further, which it does more than
2672
+ // once over its life.
2673
+ function _bindVerdict(self, fn) {
2674
+ const resolve = self._verdictTail;
2675
+ if (resolve !== null && typeof fn === 'function') {
2676
+ const tail = resolve();
2677
+ if (typeof tail === 'function') fn = _fuseTail(fn, tail);
2678
+ }
2679
+ self.isValidObject = fn;
2680
+ }
2681
+
2682
+ // Bind one of the JSON entry points, through the extension wrapper when there is one.
2683
+ function _bindEntry(self, name, fn) {
2684
+ const ext = self._entryExt;
2685
+ self[name] = ext !== null && ext[name] ? ext[name](fn) : fn;
2686
+ }
2687
+
2688
+ // The JSON entry points under an extension: the schema answers first, and only
2689
+ // text it accepts is parsed for the check, so a rejection costs nothing extra.
2690
+ function _jsonEntryWrappers({ check, errors }) {
2691
+ const parse = (text) => JSON.parse(typeof text === 'string' ? text : new TextDecoder().decode(text));
2692
+ return {
2693
+ validateJSON: (inner) => (text) => {
2694
+ const res = inner(text);
2695
+ if (!res.valid) return res;
2696
+ let data;
2697
+ try { data = parse(text); } catch { return res; }
2698
+ if (check(data)) return res;
2699
+ const e = errors(data);
2700
+ return e && e.length ? { valid: false, errors: e } : { valid: false, errors: [_EXT_FALLBACK] };
2701
+ },
2702
+ isValidJSON: (inner) => (text) => {
2703
+ if (!inner(text)) return false;
2704
+ let data;
2705
+ try { data = parse(text); } catch { return true; }
2706
+ return check(data);
2707
+ },
2708
+ validateAndParse: (inner) => (text) => {
2709
+ const res = inner(text);
2710
+ if (!res.valid) return res;
2711
+ if (check(res.value)) return res;
2712
+ const e = errors(res.value);
2713
+ return { valid: false, value: res.value, errors: e && e.length ? e : [_EXT_FALLBACK] };
2714
+ },
2715
+ };
2716
+ }
2717
+ const _EXT_FALLBACK = Object.freeze({ keyword: 'validation', instancePath: '', schemaPath: '#', params: {}, message: 'schema validation failed' });
2718
+
2719
+ // A verdict function that also runs `tail` on what it accepts. The generated
2720
+ // function can take the check in place of its final `return true`, one call
2721
+ // per document; anything else is composed.
2722
+ function _fuseTail(fn, tail) {
2723
+ const fused = typeof fn._withTail === 'function' ? fn._withTail(tail) : null;
2724
+ return fused || ((d) => fn(d) && tail(d));
2725
+ }
2726
+
2727
+ // The rejection validate() returns on the paths where an extension check could
2728
+ // not join the lazy layer: the inner result, plus the check's errors appended
2729
+ // on first read. `inner` may itself be valid, when only the check failed.
2730
+ class ExtendedRejection {
2731
+ constructor(inner, data, collect) {
2732
+ this.valid = false;
2733
+ this._inner = inner;
2734
+ this._data = data;
2735
+ this._collect = collect;
2736
+ this._errors = null;
2737
+ }
2738
+ toJSON() {
2739
+ return { valid: false, errors: this.errors };
2740
+ }
2741
+ _ataRaw() {
2742
+ const inner = this._inner;
2743
+ const more = this._collect(this._data) || [];
2744
+ const raw = inner.valid ? more : (typeof inner._ataRaw === 'function' ? inner._ataRaw() : inner.errors).concat(more);
2745
+ return raw.length ? raw : [_EXT_FALLBACK];
2746
+ }
2747
+ }
2748
+ Object.defineProperty(ExtendedRejection.prototype, 'errors', {
2749
+ enumerable: true,
2750
+ configurable: true,
2751
+ get() {
2752
+ if (this._errors === null) {
2753
+ const inner = this._inner;
2754
+ const more = this._collect(this._data) || [];
2755
+ const all = inner.valid ? more : inner.errors.concat(more);
2756
+ this._errors = all.length ? all : [_EXT_FALLBACK];
2757
+ }
2758
+ return this._errors;
2759
+ },
2760
+ });
2761
+
2762
+ // For wrappers that enforce a check the schema does not carry, such as the
2763
+ // `instanceof` keyword of @ata-project/keywords. `resolve` is called whenever
2764
+ // the verdict method is bound, which is after the schema has been normalized,
2765
+ // and returns the check, a function of the document that answers true or
2766
+ // false, or null when there is nothing to add. Only isValidObject takes it;
2767
+ // the other entry points, which report errors, stay the wrapper's to handle.
2768
+ //
2769
+ // Before this, such a wrapper had to hold isValidObject behind an accessor so
2770
+ // that the validator's own rebinding could not drop its check, and every call
2771
+ // paid for the accessor and two more calls: 10.1 ns against 4.2 on a document
2772
+ // the schema rejects at its third property.
2773
+ Validator.prototype._verdictTail = null;
2774
+ Validator.prototype._validateTail = null;
2775
+ Validator.prototype._entryExt = null;
2776
+
2777
+ // Let `new Validator(schema)` with the same schema object return this instance.
2778
+ // Only an instance built without options may answer that call: one built with
2779
+ // options (richErrors: false, coerceTypes, formats, ...) would hand its options
2780
+ // to a caller that asked for none, and an extended one would enforce checks the
2781
+ // caller never registered. Both the caller's object and the normalized one are
2782
+ // keys, since a later caller passes the former.
2783
+ function _rememberInstance(self) {
2784
+ if (!self._noOpts || self._verdictTail !== null || self._validateTail !== null) return;
2785
+ const raw = self._rawSchema;
2786
+ if (raw && typeof raw === 'object' && !_identityCache.has(raw)) _identityCache.set(raw, self);
2787
+ const obj = self._schemaObj;
2788
+ if (obj !== raw && obj && typeof obj === 'object' && !_identityCache.has(obj)) _identityCache.set(obj, self);
2789
+ }
2790
+
2791
+ // An extended validator answers differently from a plain one for the same
2792
+ // schema, so it must not be the instance `new Validator(sameSchema)` hands out.
2793
+ function _leaveIdentityCache(self) {
2794
+ self._noOpts = false;
2795
+ // Nothing is registered before the first compile, so there is nothing to
2796
+ // take back.
2797
+ if (!self._initialized && self._jsFn === null) return;
2798
+ const raw = self._rawSchema;
2799
+ if (raw && typeof raw === 'object' && _identityCache.get(raw) === self) _identityCache.delete(raw);
2800
+ // The compiled form is cached too, at the end of the first compile. Read it
2801
+ // only if it is already materialized: reading it otherwise builds it.
2802
+ if (Object.prototype.hasOwnProperty.call(self, '_schemaObj')) {
2803
+ const obj = self._schemaObj;
2804
+ if (obj && typeof obj === 'object' && _identityCache.get(obj) === self) _identityCache.delete(obj);
2805
+ }
2806
+ }
2807
+
2808
+ // The same kind of extension for validate(): `resolve` returns { check, errors }
2809
+ // or null, where `errors(data)` lists the check's own errors, or returns null
2810
+ // when there are none. The check's errors come after the schema's, and a value
2811
+ // that fails only the check is rejected with the check's errors alone. Must be
2812
+ // called before validate() is first used, which is when it is compiled; later
2813
+ // calls throw rather than being silently ignored.
2814
+ Validator.prototype._extendValidate = function (resolve) {
2815
+ if (typeof resolve !== 'function') throw new TypeError('_extendValidate expects a function');
2816
+ if (this._initialized) throw new Error('_extendValidate must be called before the validator compiles');
2817
+ _leaveIdentityCache(this);
2818
+ const prev = this._validateTail;
2819
+ this._validateTail = prev === null ? resolve : () => {
2820
+ const a = prev(), b = resolve();
2821
+ if (!a) return b;
2822
+ if (!b) return a;
2823
+ return {
2824
+ check: (d) => a.check(d) && b.check(d),
2825
+ errors: (d) => {
2826
+ const x = a.errors(d), y = b.errors(d);
2827
+ if (!x) return y;
2828
+ if (!y) return x;
2829
+ return x.concat(y);
2830
+ },
2831
+ };
2832
+ };
2833
+ return this;
2834
+ };
2835
+
2836
+ // Both extensions in one call, from one resolver that returns { check, errors }
2837
+ // or null. This is the form @ata-project/keywords uses: registering costs one
2838
+ // closure, where wrapping the five entry points cost a closure per entry point,
2839
+ // the wrappers themselves and an accessor, most of what building a wrapped
2840
+ // validator took.
2841
+ Validator.prototype._extendChecks = function (resolve) {
2842
+ if (typeof resolve !== 'function') throw new TypeError('_extendChecks expects a function');
2843
+ this._extendValidate(resolve);
2844
+ return this._extendVerdict(() => {
2845
+ const x = resolve();
2846
+ return x ? x.check : null;
2847
+ });
2848
+ };
2849
+
2850
+ // Whether this instance enforces a check its schema does not carry. The
2851
+ // ahead-of-time emitters build a module from the schema alone, so they refuse
2852
+ // an instance that says yes rather than emit one that accepts too much.
2853
+ // Reading it resolves the registered checks; only an emitter reads it.
2854
+ Object.defineProperty(Validator.prototype, '_externalChecks', {
2855
+ configurable: true,
2856
+ get() {
2857
+ if (this._validateTail !== null && this._validateTail()) return true;
2858
+ if (this._verdictTail !== null && typeof this._verdictTail() === 'function') return true;
2859
+ return false;
2860
+ },
2861
+ });
2862
+
2863
+ Validator.prototype._extendVerdict = function (resolve) {
2864
+ if (typeof resolve !== 'function') throw new TypeError('_extendVerdict expects a function');
2865
+ _leaveIdentityCache(this);
2866
+ const prev = this._verdictTail;
2867
+ this._verdictTail = prev === null ? resolve : () => {
2868
+ const a = prev(), b = resolve();
2869
+ if (typeof a !== 'function') return b;
2870
+ if (typeof b !== 'function') return a;
2871
+ return (d) => a(d) && b(d);
2872
+ };
2873
+ // A method bound before this call was bound without the check. Rebind it.
2874
+ // An unbound one binds through _bindVerdict on its first call.
2875
+ if (Object.prototype.hasOwnProperty.call(this, 'isValidObject')) _bindVerdict(this, this.isValidObject);
2876
+ return this;
2877
+ };
2878
+
2618
2879
  function _defineLazyMethod(name, maker) {
2619
2880
  Object.defineProperty(Validator.prototype, name, {
2620
2881
  configurable: true,
@@ -2660,13 +2921,13 @@ _defineLazyMethod('isValidObject', (self) => (data) => {
2660
2921
  if (_tier.tier === 0) {
2661
2922
  const _plan = buildTier0Plan(self._schemaObj);
2662
2923
  let _n = 0;
2663
- self.isValidObject = (d) => {
2924
+ _bindVerdict(self, (d) => {
2664
2925
  const r = tier0Validate(_plan, d);
2665
2926
  if (++_n === 2) {
2666
2927
  try { self._ensureCodegen(); } catch {}
2667
2928
  }
2668
2929
  return r;
2669
- };
2930
+ });
2670
2931
  } else {
2671
2932
  // `new Function` is a property of the realm, not of the schema: under a
2672
2933
  // 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 };
@@ -1567,7 +1567,7 @@ function compileToJSCodegen(schema, schemaMap, userFormats) {
1567
1567
  const defSets = ctx.defFns ? Array.from(ctx.defFns.values()) : []
1568
1568
  const needsGuard = ctx.usesRecursion || defSets.length > 0
1569
1569
 
1570
- let body, hybridBody
1570
+ let body, hybridBody, tailBody
1571
1571
  if (ctx.usesRecursion) {
1572
1572
  // Self-recursive: a named body reached through the cycle guard, so data
1573
1573
  // that points back at itself settles instead of exhausting the stack.
@@ -1577,15 +1577,21 @@ function compileToJSCodegen(schema, schemaMap, userFormats) {
1577
1577
  body = `${decl}function _run(d){\n ${run}\n }\n return _run(d)`
1578
1578
  // Hybrid: keep the boolean answer, wrap only the outer call
1579
1579
  hybridBody = `${decl}function _run(d){\n ${run}\n }\n return _run(d)?R:E(d)`
1580
+ tailBody = `${decl}function _run(d){\n ${run}\n }\n return _run(d)&&__ataTail(d)`
1580
1581
  } else if (defSets.length > 0) {
1581
1582
  // No self-reference at the root, but a def on a cycle can still be
1582
1583
  // re-entered with the same value; the guarded functions need their state.
1583
1584
  const run = emitGuardedRun(`return _body(d)`, '', defSets)
1584
1585
  body = `function _body(d){\n ${checkStr}\n return true\n }\n function _run(d){\n ${run}\n }\n return _run(d)`
1585
1586
  hybridBody = `function _body(d){\n ${checkStr}\n return true\n }\n function _run(d){\n ${run}\n }\n return _run(d)?R:E(d)`
1587
+ tailBody = `function _body(d){\n ${checkStr}\n return true\n }\n function _run(d){\n ${run}\n }\n return _run(d)&&__ataTail(d)`
1586
1588
  } else {
1587
1589
  body = checkStr + '\n return true'
1588
1590
  hybridBody = replaceTopLevel(checkStr + '\n return R')
1591
+ // An early accept inside the checks has to reach the tail too, so every
1592
+ // top-level `return true` becomes the tail call, by the same rewrite the
1593
+ // hybrid above relies on and test_hybrid_agreement holds to the suite.
1594
+ tailBody = replaceTopLevel(checkStr + '\n return true', 'return __ataTail(d)', 'return false')
1589
1595
  }
1590
1596
 
1591
1597
  const guardStr = needsGuard ? emitGuardState() : ''
@@ -1610,6 +1616,26 @@ function compileToJSCodegen(schema, schemaMap, userFormats) {
1610
1616
  boolFn._hybridFactory = (R, E) => hybridFactory(...closureValues, R, E)
1611
1617
  } catch {}
1612
1618
 
1619
+ // The same function with one more check where it would return true, for a
1620
+ // wrapper that enforces something the schema does not carry (see
1621
+ // Validator#_extendVerdict). Calling the check from here rather than from a
1622
+ // function around this one is the point: this function is too large for
1623
+ // the engine to inline, so a wrapper is a second call on every document,
1624
+ // and a rejection never reaches the check at all. Built on first request,
1625
+ // since almost no validator is extended. Null when code generation is
1626
+ // refused, and the caller composes instead.
1627
+ let tailFactory
1628
+ boolFn._withTail = (tail) => {
1629
+ if (tailFactory === undefined) {
1630
+ try {
1631
+ tailFactory = new Function(...closureNames, '__ataTail', `${preambleStr}return function(d){${tailBody}}`)
1632
+ } catch {
1633
+ tailFactory = null
1634
+ }
1635
+ }
1636
+ return tailFactory === null ? null : tailFactory(...closureValues, tail)
1637
+ }
1638
+
1613
1639
  // Store source for standalone compilation. Regex declarations are NOT
1614
1640
  // inlined into the body: they are carried by _closures and the emitters
1615
1641
  // declare them once at load scope. Inlining them here put a
@@ -1663,7 +1689,7 @@ function compileToJSCodegen(schema, schemaMap, userFormats) {
1663
1689
 
1664
1690
  // Replace top-level `return false` → `return E(d)` and `return true` → `return R`.
1665
1691
  // Tracks function nesting depth to preserve nested function internals.
1666
- function replaceTopLevel(code) {
1692
+ function replaceTopLevel(code, onTrue = 'return R', onFalse = 'return E(d)') {
1667
1693
  let fnDepth = 0, result = '', i = 0
1668
1694
  while (i < code.length) {
1669
1695
  // A nested closure keeps its own returns. Both spellings count: a
@@ -1690,10 +1716,10 @@ function replaceTopLevel(code) {
1690
1716
  i++
1691
1717
  }
1692
1718
  } else if (code.startsWith('return false', i)) {
1693
- result += 'return E(d)'
1719
+ result += onFalse
1694
1720
  i += 12
1695
1721
  } else if (code.startsWith('return true', i) && (i + 11 >= code.length || !/[a-zA-Z_$]/.test(code[i + 11]))) {
1696
- result += 'return R'
1722
+ result += onTrue
1697
1723
  i += 11
1698
1724
  } else {
1699
1725
  result += code[i]
@@ -3147,8 +3173,20 @@ const FORMAT_CODEGEN = {
3147
3173
  'uri-reference': (v, isStr) => isStr
3148
3174
  ? `{${_formats.uriCharsSource(v, '0')}}`
3149
3175
  : `if(typeof ${v}==='string'){${_formats.uriCharsSource(v, '0')}}`,
3150
- ipv4: _formats.ipv4Source,
3151
- ipv6: _formats.ipv6Source,
3176
+ ipv4: (v, isStr, ctx) => {
3177
+ if (!ctx) return _formats.ipv4Source(v, isStr)
3178
+ // Hoisted: a regex literal in the body would build a new RegExp per call.
3179
+ hoistOnce(ctx, '_ip4Hoisted', 'const _ip4=/' + _formats.IPV4.source + '/')
3180
+ return isStr ? `if(!_ip4.test(${v}))return false` : `if(typeof ${v}==='string'&&!_ip4.test(${v}))return false`
3181
+ },
3182
+ ipv6: (v, isStr, ctx) => {
3183
+ if (!ctx) return _formats.ipv6Source(v, isStr)
3184
+ hoistOnce(ctx, '_ip6Hoisted', 'const _ip6f=/' + _formats.IPV6_FULL.source + '/')
3185
+ // The fast accept jumps past the walk; the walk's `return false`s are what
3186
+ // the error generators rewrite, so they stay as they are.
3187
+ const body = `_ip6:{if(${v}.length>=15&&_ip6f.test(${v}))break _ip6;${_formats.ipv6Source(v, true).replace(/^\{|\}$/g, '')}}`
3188
+ return isStr ? body : `if(typeof ${v}==='string'){${body}}`
3189
+ },
3152
3190
  hostname: _formats.hostnameSource,
3153
3191
  }
3154
3192
 
@@ -3512,7 +3550,23 @@ function compileToJSCodegenWithErrors(schema, schemaMap, userFormats, sourceOpts
3512
3550
  // first invalid document threw a ReferenceError instead of reporting.
3513
3551
  const cvars = ctx.closureVars
3514
3552
  const cvals = ctx.closureVals
3515
- if (ctx.usesSafeRe) {
3553
+ // The helpers are constants: the uri tables, the compiled patterns, the
3554
+ // branch-collapse functions, the name sets. In `body` they sit inside the
3555
+ // function and were rebuilt on every call, which on a product schema with
3556
+ // six URLs and one anyOf cost about 450 ns before a single check ran, more
3557
+ // than every check and every error together. Here they run once, in a
3558
+ // factory, as the verdict function's preamble does. Only for the plain
3559
+ // shape: with $defs or recursion the helpers include functions that read
3560
+ // the per-call guard state declared in the body, so those keep `body` as
3561
+ // it is. `_errSource` below stays `body` either way, for the emitters.
3562
+ const hoistable = !ctx.usesRecursion && defSetsE.length === 0 && helpersE !== ''
3563
+ if (hoistable) {
3564
+ const factoryBody = helpersE + 'return function(d,_all){const _e=[];\n ' + checkStr +
3565
+ '\n return{valid:_e.length===0,errors:_e}}'
3566
+ const params = ctx.usesSafeRe ? [...cvars, '__ataSafeRe'] : [...cvars]
3567
+ const args = ctx.usesSafeRe ? [...cvals, compileSafe] : [...cvals]
3568
+ fn = new Function(...params, factoryBody)(...args)
3569
+ } else if (ctx.usesSafeRe) {
3516
3570
  // Inlined helperCode references __ataSafeRe; bind the safe-regex factory.
3517
3571
  // Standalone keeps the inline _errSource — the embed defines __ataSafeRe.
3518
3572
  const built = new Function(...cvars, '__ataSafeRe', 'd', '_all', body)
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.0';
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "ata-validator",
3
- "version": "1.30.3",
3
+ "version": "1.31.0",
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_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.0",
125
+ "@ata-validator/native-darwin-x64": "1.31.0",
126
+ "@ata-validator/native-linux-arm64-gnu": "1.31.0",
127
+ "@ata-validator/native-linux-arm64-musl": "1.31.0",
128
+ "@ata-validator/native-linux-x64-gnu": "1.31.0",
129
+ "@ata-validator/native-linux-x64-musl": "1.31.0",
130
+ "@ata-validator/native-win32-x64": "1.31.0"
131
131
  },
132
132
  "peerDependencies": {
133
133
  "yaml": "^2.0.0"