ata-validator 1.43.0 → 1.44.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/README.md CHANGED
@@ -244,21 +244,21 @@ is the most common way to get a misleading number out of this library.
244
244
 
245
245
  | | compiled with `ata build` | runtime `new Validator(schema)` |
246
246
  |---|---|---|
247
- | In a bundle, gzipped | **2.2 KB** | 95.6 KB |
248
- | Time to a served request | **3.5 ms** | 7.8 ms |
247
+ | In a bundle, gzipped | **2.1 KB** | 110.7 KB |
248
+ | Time to a served request | **3.5 ms** | 8.2 ms |
249
249
  | Schema known when | build time | any time |
250
250
 
251
251
  The bundle row is the ten-field user schema in
252
252
  `tests/fixtures/error-dx/user.schema.json`, every export of the compiled module against
253
- `new Validator(schema)`, built with `bun build --minify --target=browser` on ata 1.36.0.
254
- The startup row is a Hono route on Bun 1.4, median of nine interleaved runs, from
255
- `benchmark/bundle`, against 3.5 ms for the same app doing no validation at all, so the
253
+ `new Validator(schema)`, built with `bun build --minify --target=browser` on ata 1.43.0.
254
+ The startup row is a Hono route on Bun 1.4, the median of three rounds of best-of-seven, from
255
+ `benchmark/bundle`, against 3.4 ms for the same app doing no validation at all, so the
256
256
  compiled path costs nothing measurable to start. The runtime figure is what it is because
257
257
  a schema that arrives at run time can use any keyword, so the whole engine has to be
258
258
  there. The compiled module imports nothing and contains only the checks your schema asks
259
259
  for.
260
260
 
261
- **On a server, use whichever fits your schemas.** 96 KB of JavaScript on a Node or Bun
261
+ **On a server, use whichever fits your schemas.** 111 KB of JavaScript on a Node or Bun
262
262
  process is not a cost anyone notices, and the runtime API is the simpler thing to reach
263
263
  for. Speed is the same either way once warm.
264
264
 
@@ -491,7 +491,7 @@ const v = new Validator(schema, {
491
491
 
492
492
  ### Build-time compile (`ata compile`)
493
493
 
494
- The `ata` CLI turns a JSON Schema file into a self-contained JavaScript module. No runtime dependency on `ata-validator`, so only the generated validator ships to the browser. For the ten-field user schema in `tests/fixtures/error-dx/user.schema.json` the module is 2.2 KB gzipped, full error detail included, against 95.6 KB for the runtime bundled for the browser.
494
+ The `ata` CLI turns a JSON Schema file into a self-contained JavaScript module. No runtime dependency on `ata-validator`, so only the generated validator ships to the browser. For the ten-field user schema in `tests/fixtures/error-dx/user.schema.json` the module is 2.1 KB gzipped, full error detail included, against 110.7 KB for the runtime bundled for the browser (ata 1.43.0).
495
495
 
496
496
  ```bash
497
497
  npx ata compile schemas/user.json -o src/generated/user.validator.mjs
@@ -533,13 +533,13 @@ npx ata build 'schemas/*.json' --out-dir build/validators --check
533
533
  Run with `--watch` during development for incremental rebuilds.
534
534
 
535
535
  Bundle sizes for the 10-field user schema in `tests/fixtures/error-dx/user.schema.json`,
536
- minified and gzipped, measured with `bun build --minify --target=browser` on ata 1.36.0:
536
+ minified and gzipped, measured with `bun build --minify --target=browser` on ata 1.43.0:
537
537
 
538
538
  | What the app imports | Size | Notes |
539
539
  |---|---|---|
540
- | `Validator` from `ata-validator` | 95.6 KB | The compiler ships with it, because a runtime schema can use any keyword |
540
+ | `Validator` from `ata-validator` | 110.7 KB | The compiler ships with it, because a runtime schema can use any keyword |
541
541
  | `isValid` from the compiled module | **1.3 KB** | Nothing else is reachable, so the error collector is dropped |
542
- | `validate` from the compiled module | **2.0 KB** | Adds the detailed error collector |
542
+ | `validate` from the compiled module | **1.9 KB** | Adds the detailed error collector |
543
543
 
544
544
  `--abort-early` makes the generated source about three times smaller, and after
545
545
  bundling it makes no difference: importing only `isValid` already leaves the error
package/index.js CHANGED
@@ -434,6 +434,9 @@ function installCodegenPaths (ctx) {
434
434
  }
435
435
  return nativeText ? getNative().rawFastValidate(this._fastSlot, Buffer.from(jsonStr)) : undefined;
436
436
  };
437
+ // The parsed document is left on `_lastParsed` for the rich layer around
438
+ // validateJSON, which hands it to the rejection so reading the errors does
439
+ // not parse the text again (see LazyJsonRejection).
437
440
  const validateText = (jsonStr) => {
438
441
  let obj;
439
442
  try {
@@ -442,6 +445,7 @@ function installCodegenPaths (ctx) {
442
445
  if (!(e instanceof SyntaxError)) throw e;
443
446
  return _jsonSyntaxRejection(e);
444
447
  }
448
+ this._lastParsed = obj;
445
449
  return jsonValidateFn(obj);
446
450
  };
447
451
  this.validateJSON = useSimdjsonForLarge && !preprocess
@@ -878,8 +882,58 @@ function buildParse (self, decline, extended) {
878
882
  // preprocess pass, the JSON-text scanner, the parse() copy and the
879
883
  // ahead-of-time bundle methods. parse() and the bundle methods load on first
880
884
  // use.
885
+ // Cold start. A validator's first COLD_CALLS calls are answered by an
886
+ // interpreted twin, built from the same schema and options with
887
+ // `engine: 'interpreter'`, which the test suite holds to the same answers and
888
+ // errors as the generated code. Generating code and having V8 compile it is
889
+ // the largest part of a first call on a large schema: from process start to
890
+ // one rejection with its error read, SchemaStore's SARIF schema took 87 ms
891
+ // with code generated at the first call and 31 ms with the twin answering.
892
+ // A validator still in use after that compiles as before, so a server reaches
893
+ // the generated code's speed within its first requests, and a command line
894
+ // tool or a short-lived function never pays to compile. Only where compiling
895
+ // costs enough to matter: a schema whose size with its references followed
896
+ // passes COLD_MIN characters (expandedChars in lib/js-compiler.js). A small
897
+ // schema compiles in about a millisecond and is generated at its first call
898
+ // as before, which also keeps every test that compares the generated code
899
+ // with the interpreter comparing the generated code. Not for a validator with
900
+ // an extension (_extendValidate, _extendChecks), which the twin would not
901
+ // carry. Returns the twin to answer this call, or null to compile.
902
+ const COLD_CALLS = 64;
903
+ const COLD_MIN = 16 * 1024;
904
+ // Whether the schema may expand past `limit`, answered from its text where
905
+ // that settles it. Without a `$ref` nothing expands, and expandedChars counts
906
+ // at most 8 for each character of the text, so a short schema is decided
907
+ // without walking it. The text is the one _ensureCompiled needs anyway. Walking
908
+ // every small route schema here cost a Fastify boot of ten routes 0.15 ms.
909
+ function mayExpandPast (self, limit) {
910
+ const str = self._schemaStr || (self._schemaStr = JSON.stringify(self._schemaObj));
911
+ if (str === undefined) return false;
912
+ if (str.length * 8 <= limit && !str.includes('"$ref"')) return false;
913
+ return jsCompiler.expandedChars(self._schemaObj, limit) > limit;
914
+ }
915
+
916
+ function coldTwin (self, Validator) {
917
+ const n = self._coldCalls === undefined ? 0 : self._coldCalls;
918
+ if (n >= COLD_CALLS || self._validateTail !== null || self._verdictTail !== null) return null;
919
+ let t = self._twin;
920
+ if (t === undefined) {
921
+ t = null;
922
+ if (!self._interpretOnly && !self._initialized && mayExpandPast(self, COLD_MIN)) {
923
+ try { t = new Validator(self._rawSchema, Object.assign({}, self._options, { engine: 'interpreter' })); } catch { t = null; }
924
+ }
925
+ self._twin = t;
926
+ }
927
+ if (t === null) return null;
928
+ self._coldCalls = n + 1;
929
+ return t;
930
+ }
931
+
881
932
  core._registerCodegen({
882
933
  jsCompiler,
934
+ coldTwin,
935
+ // The native locator for error frames, loaded with the first frame.
936
+ nativePositions: () => require('./lib/native-positions').nativeTargeted,
883
937
  installPaths: installCodegenPaths,
884
938
  compileVerdict: installCodegenPaths.compileVerdict,
885
939
  installScanner: installCodegenPaths.installScanner,
package/lib/clone-emit.js CHANGED
@@ -78,8 +78,11 @@ function inlineRefsForClone(root) {
78
78
  for (const k of Object.keys(cur)) {
79
79
  if (strip && k === 'default') continue;
80
80
  const v = cur[k];
81
- if (_INLINE_DATA_KEYS.has(k) || k === '$defs' || k === 'definitions') { out[k] = v; continue; }
82
- out[k] = walk(v, active, strip);
81
+ // A property named "__proto__" is an own key of the copy: assigned, it
82
+ // set the copy's prototype and the property was gone from parse().
83
+ const w = (_INLINE_DATA_KEYS.has(k) || k === '$defs' || k === 'definitions') ? v : walk(v, active, strip);
84
+ if (k === '__proto__') Object.defineProperty(out, k, { value: w, writable: true, enumerable: true, configurable: true });
85
+ else out[k] = w;
83
86
  }
84
87
  if (siblingDefault !== undefined) out.default = siblingDefault;
85
88
  return out;
@@ -2,13 +2,17 @@
2
2
 
3
3
  const { buildDataPositionMap, buildTargetedPositionMap } = require('./data-positions');
4
4
 
5
+
5
6
  /**
6
7
  * Memoize the position map for the duration of a single validate() call.
7
8
  * Caller passes the original buffer/string. Identity-keyed: same reference
8
9
  * == same map. No global state, caller holds the cache instance.
9
10
  */
10
11
 
11
- function createCache () {
12
+ // `nativeTargeted`, when given, is a faster source of the same entries: the
13
+ // full entry point passes lib/native-positions.js, lite passes nothing.
14
+ function createCache (nativeTargeted) {
15
+ const _nativeTargeted = typeof nativeTargeted === 'function' ? nativeTargeted : null;
12
16
  const wm = new WeakMap();
13
17
  const sm = new Map(); // strings can't go in WeakMap; clear after each validate
14
18
  let lastInput = null;
@@ -32,11 +36,13 @@ function createCache () {
32
36
  ? sm.get(input)
33
37
  : (Buffer.isBuffer(input) ? wm.get(input) : undefined);
34
38
  if (already) return already;
35
- let filtered;
36
- try {
37
- filtered = buildTargetedPositionMap(input, wanted);
38
- } catch {
39
- return null;
39
+ let filtered = _nativeTargeted === null ? null : _nativeTargeted(input, wanted);
40
+ if (filtered === null) {
41
+ try {
42
+ filtered = buildTargetedPositionMap(input, wanted);
43
+ } catch {
44
+ return null;
45
+ }
40
46
  }
41
47
  let full;
42
48
  const fallback = (key) => {
package/lib/draft7.js CHANGED
@@ -49,13 +49,11 @@ function _normalize(schema) {
49
49
  // dependencies → dependentSchemas + dependentRequired
50
50
  if (schema.dependencies) {
51
51
  for (const [key, value] of Object.entries(schema.dependencies)) {
52
- if (Array.isArray(value)) {
53
- if (!schema.dependentRequired) schema.dependentRequired = {}
54
- schema.dependentRequired[key] = value
55
- } else {
56
- if (!schema.dependentSchemas) schema.dependentSchemas = {}
57
- schema.dependentSchemas[key] = value
58
- }
52
+ // defineProperty: a dependency on a property named "__proto__" is an
53
+ // own key here, where assignment would set the map's prototype.
54
+ const target = Array.isArray(value) ? 'dependentRequired' : 'dependentSchemas'
55
+ if (!schema[target]) schema[target] = {}
56
+ Object.defineProperty(schema[target], key, { value, writable: true, enumerable: true, configurable: true })
59
57
  }
60
58
  delete schema.dependencies
61
59
  }
@@ -1065,6 +1065,16 @@ function hasOwnKey (d, key) {
1065
1065
  return _hop.call(d, key)
1066
1066
  }
1067
1067
 
1068
+ // Writes an own key. A schema can name a property, a definition or even a
1069
+ // keyword "__proto__", and assigning that key sets the copy's prototype
1070
+ // instead: the property was gone from the copy, so a constraint on it was
1071
+ // generated as nothing and accepted anything (a schema with such a property
1072
+ // and a reference the resolver rewrites, 1.40.1 to 1.43.0).
1073
+ function setOwn (o, k, v) {
1074
+ if (k === '__proto__') Object.defineProperty(o, k, { value: v, writable: true, enumerable: true, configurable: true })
1075
+ else o[k] = v
1076
+ }
1077
+
1068
1078
  const UNSAFE_KEYS = new Set(['__proto__', 'constructor', 'toString', 'valueOf',
1069
1079
  'hasOwnProperty', 'isPrototypeOf', 'propertyIsEnumerable', 'toLocaleString'])
1070
1080
 
@@ -1380,7 +1390,7 @@ function aliasChainEnds(def, defs) {
1380
1390
  // source of evaluated properties the schema has; when it does not, the
1381
1391
  // schema is not generated and the interpreter answers.
1382
1392
  function unevalPropsModeled(schema, schemaMap) {
1383
- const own = Object.assign({}, schema)
1393
+ const own = { ...schema }
1384
1394
  delete own.$defs
1385
1395
  delete own.definitions
1386
1396
  if (/"\$(?:ref|dynamicRef|recursiveRef)"/.test(JSON.stringify(own))) return false
@@ -1399,7 +1409,7 @@ function unevalPropsModeled(schema, schemaMap) {
1399
1409
  if (typeof sub !== 'object' || sub === null) return false
1400
1410
  const r = collectEvaluated(sub, schemaMap)
1401
1411
  if (r.dynamic || r.allProps) return false
1402
- const rest = Object.assign({}, sub)
1412
+ const rest = { ...sub }
1403
1413
  delete rest.patternProperties
1404
1414
  const j = JSON.stringify(rest)
1405
1415
  if (j.includes('"patternProperties"') || j.includes('"unevaluatedProperties"')) return false
@@ -1432,7 +1442,7 @@ function unevalPropsModeled(schema, schemaMap) {
1432
1442
  if (typeof schema.if !== 'object' || schema.if === null) return false
1433
1443
  const r = collectEvaluated(schema.if, schemaMap)
1434
1444
  if (r.dynamic || r.allProps || r.props.length) return false
1435
- const rest = Object.assign({}, schema.if)
1445
+ const rest = { ...schema.if }
1436
1446
  delete rest.patternProperties
1437
1447
  const j = JSON.stringify(rest)
1438
1448
  if (j.includes('"patternProperties"') || j.includes('"unevaluatedProperties"')) return false
@@ -1486,17 +1496,7 @@ function codegenSafe(schema, schemaMap) {
1486
1496
  }
1487
1497
 
1488
1498
  // Keys that collide with Object.prototype
1489
- if (schema.required) {
1490
- for (const k of schema.required) {
1491
- if (UNSAFE_KEYS.has(k)) return false
1492
- }
1493
- }
1494
- if (schema.properties) {
1495
- for (const k of Object.keys(schema.properties)) {
1496
- if (UNSAFE_KEYS.has(k)) return false
1497
- if (k === '$ref') return false // property named "$ref" — confusing
1498
- }
1499
- }
1499
+
1500
1500
 
1501
1501
  // Unicode property escapes in pattern need 'u' flag — codegen uses RegExp without it
1502
1502
 
@@ -1627,10 +1627,8 @@ function codegenSafe(schema, schemaMap) {
1627
1627
  if (typeof sub === 'object' && sub !== null && !codegenSafe(sub, schemaMap)) return false
1628
1628
  }
1629
1629
  }
1630
- if (typeof schema.propertyNames === 'object' && schema.propertyNames !== null) {
1631
- const supported = ['maxLength', 'minLength', 'pattern', 'const', 'enum']
1632
- if (Object.keys(schema.propertyNames).some(k => k !== '$schema' && !supported.includes(k))) return false
1633
- }
1630
+ if (typeof schema.propertyNames === 'object' && schema.propertyNames !== null &&
1631
+ !isSimplePN(schema.propertyNames) && !codegenSafe(schema.propertyNames, schemaMap)) return false
1634
1632
 
1635
1633
  // Recurse into sub-schemas — bail on boolean schemas in any position
1636
1634
  // A boolean subschema is a constant that every generator emits at its
@@ -1968,8 +1966,21 @@ function bothDefsContainers(schema) {
1968
1966
  schema.definitions !== null && typeof schema.definitions === 'object'
1969
1967
  }
1970
1968
 
1969
+ // The answer depends on the schema and the map alone, and the three
1970
+ // generators ask it about the same prepared schema in turn, so the last
1971
+ // answer is kept: each check walks the whole schema, and the second and third
1972
+ // asked again what the first had found. One entry, compared by identity,
1973
+ // because a WeakMap entry per schema cost more than the walk it saved on a
1974
+ // small schema that only ever gets its verdict generated.
1975
+ let _gateSchema = null, _gateMap = null, _gateOk = false
1971
1976
  function sharedCodegenGate(schema, schemaMap) {
1972
1977
  if (typeof schema !== 'object' || schema === null) return true
1978
+ if (schema === _gateSchema && schemaMap === _gateMap) return _gateOk
1979
+ const ok = sharedCodegenGateUncached(schema, schemaMap)
1980
+ _gateSchema = schema; _gateMap = schemaMap; _gateOk = ok
1981
+ return ok
1982
+ }
1983
+ function sharedCodegenGateUncached(schema, schemaMap) {
1973
1984
  if (bothDefsContainers(schema)) return false
1974
1985
  if (hasSameInstanceRefCycle(schema)) return false
1975
1986
  if (!codegenSafe(schema, schemaMap)) return false
@@ -1985,6 +1996,15 @@ function sharedCodegenGate(schema, schemaMap) {
1985
1996
  // `type: 'string'` and annotations go, on a copy. Anything else is left as
1986
1997
  // written and the gate declines. Errors keep the interpreted engine's path.
1987
1998
  const PN_SUPPORTED = new Set(['maxLength', 'minLength', 'pattern', 'const', 'enum'])
1999
+ // Whether the generators can write `propertyNames` as checks on the key in the
2000
+ // object's own key loop. Any other subschema is applied to each key as a
2001
+ // value of its own (genPropertyNamesNode and its error and combined
2002
+ // counterparts), which is what the interpreter does.
2003
+ function isSimplePN (pn) {
2004
+ if (pn === null || typeof pn !== 'object' || Array.isArray(pn)) return false
2005
+ for (const k of Object.keys(pn)) if (k !== '$schema' && !PN_SUPPORTED.has(k)) return false
2006
+ return true
2007
+ }
1988
2008
  const PN_ANNOTATIONS = new Set(['$schema', '$comment', 'title', 'description', 'examples', 'default', 'deprecated', 'readOnly', 'writeOnly'])
1989
2009
  function simplePropertyNames(pn, defs) {
1990
2010
  const seen = new Set()
@@ -2006,7 +2026,7 @@ function simplePropertyNames(pn, defs) {
2006
2026
  return null
2007
2027
  }
2008
2028
  if (!PN_SUPPORTED.has(k)) return null
2009
- out[k] = val
2029
+ setOwn(out, k, val)
2010
2030
  }
2011
2031
  return out
2012
2032
  }
@@ -2037,14 +2057,14 @@ function normalizePropertyNamesWalk(root) {
2037
2057
  return out
2038
2058
  }
2039
2059
  let out = node
2040
- const set = (k, v) => { if (out === node) out = { ...node }; out[k] = v }
2060
+ const set = (k, v) => { if (out === node) out = { ...node }; setOwn(out, k, v) }
2041
2061
  for (const k of SUBSCHEMA_MAPS) {
2042
2062
  const map = node[k]
2043
2063
  if (!map || typeof map !== 'object') continue
2044
2064
  let copy = map
2045
2065
  for (const [name, sub] of Object.entries(map)) {
2046
2066
  const w = walk(sub)
2047
- if (w !== sub) { if (copy === map) copy = { ...map }; copy[name] = w }
2067
+ if (w !== sub) { if (copy === map) copy = { ...map }; setOwn(copy, name, w) }
2048
2068
  }
2049
2069
  if (copy !== map) set(k, copy)
2050
2070
  }
@@ -2162,7 +2182,7 @@ function normalizeDeepRefsWalk(root) {
2162
2182
  if (inner) return node
2163
2183
  let out = node
2164
2184
  memo.set(node, out)
2165
- const own = (k, v) => { if (out === node) { out = { ...node }; memo.set(node, out) } out[k] = v }
2185
+ const own = (k, v) => { if (out === node) { out = { ...node }; memo.set(node, out) } setOwn(out, k, v) }
2166
2186
  if (typeof node.$ref === 'string' && names.has(node.$ref)) own('$ref', '#/' + defsKey + '/' + names.get(node.$ref))
2167
2187
  for (const k of Object.keys(node)) {
2168
2188
  if (k === '$ref' || k === 'enum' || k === 'const' || k === 'default' || k === 'examples') continue
@@ -2211,10 +2231,39 @@ function normalizeRefs(root, schemaMap) {
2211
2231
  return out
2212
2232
  }
2213
2233
  const SKIP_VALUE_KEYS = new Set(['enum', 'const', 'default', 'examples'])
2234
+ // A schema whose every reference is `#` or a plain `#/$defs/name` (or
2235
+ // `definitions`), with no `$id` or `$anchor` below the root to move the base
2236
+ // they resolve against, needs nothing from the resolver: each reference
2237
+ // already reads as the generators read it. Telling that from the text skips
2238
+ // loading the interpreted engine and indexing the schema, which was most of
2239
+ // what preparing a small schema for code generation cost.
2240
+ const LOCAL_ONLY_REF = /^#(?:\/(?:\$defs|definitions)\/[^/~%]+)?$/
2241
+ function refsAllPlainLocal (root, text) {
2242
+ const ids = text.split('"$id":').length - 1
2243
+ if (ids > (typeof root.$id === 'string' ? 1 : 0) || text.includes('"$anchor":')) return false
2244
+ const re = /"\$ref":("(?:[^"\\]|\\.)*")/g
2245
+ let m
2246
+ while ((m = re.exec(text)) !== null) {
2247
+ let ref
2248
+ try { ref = JSON.parse(m[1]) } catch { return false }
2249
+ if (!LOCAL_ONLY_REF.test(ref)) return false
2250
+ }
2251
+ return true
2252
+ }
2253
+
2254
+ // What the text of a schema prepared for code generation says about its size,
2255
+ // kept from the stringify normalizeRefsWalk makes anyway: its length when it
2256
+ // holds no `$ref`, -1 when it does. expandedChars counts at most 8 for each
2257
+ // character of a schema without references, so a short one is known not to
2258
+ // reach EXPANDED_SHARE without walking it again at every compile.
2259
+ const _refFreeLength = new WeakMap()
2214
2260
  function normalizeRefsWalk(root, schemaMap) {
2215
2261
  const text = JSON.stringify(root)
2216
- if (!text.includes('"$ref"')) return root
2262
+ const hasRef = text.includes('"$ref"')
2263
+ _refFreeLength.set(root, hasRef ? -1 : text.length)
2264
+ if (!hasRef) return root
2217
2265
  if (/"\$(?:dynamicRef|recursiveRef|dynamicAnchor|recursiveAnchor)"/.test(text)) return null
2266
+ if (refsAllPlainLocal(root, text)) return root
2218
2267
  const { indexSchemas, resolveRef } = require('./interpreter')._refInternals
2219
2268
  const state = indexSchemas(root, schemaMap && schemaMap.size ? schemaMap : null)
2220
2269
  const rootDialect = typeof root.$schema === 'string' ? root.$schema.replace(/#$/, '') : null
@@ -2280,7 +2329,7 @@ function normalizeRefsWalk(root, schemaMap) {
2280
2329
  out.$ref = t === root ? '#' : '#/' + defsKey + '/' + names.get(t)
2281
2330
  continue
2282
2331
  }
2283
- out[k] = SKIP_VALUE_KEYS.has(k) ? node[k] : walk(node[k])
2332
+ setOwn(out, k, SKIP_VALUE_KEYS.has(k) ? node[k] : walk(node[k]))
2284
2333
  }
2285
2334
  return out
2286
2335
  }
@@ -2299,6 +2348,46 @@ function prepareForCodegen(schema, schemaMap) {
2299
2348
  }
2300
2349
 
2301
2350
  const VERDICT_SPLIT = 64 * 1024
2351
+ // The schema's size with every local reference followed, each definition
2352
+ // sized once, annotations left out. A rough measure of the expanded verdict
2353
+ // source: on SchemaStore that runs about 1.4 times it at the median, and far
2354
+ // less where allOf merges. Past EXPANDED_SHARE the verdict is generated with
2355
+ // shared definitions from the start, instead of expanded first and thrown
2356
+ // away: SARIF comes to 2.1 million, and generating its 2.8 MB of expanded
2357
+ // source only to discard it was most of a cold first validate() (70 ms of the
2358
+ // 87 a command line run took to report one error). The bound is set where
2359
+ // only schemas that split anyway reach it: SARIF 2.1 measures about 706
2360
+ // thousand, while utcm-monitor (370 thousand) and accelerator (262 thousand)
2361
+ // stay expanded and keep their speed.
2362
+ const EXPANDED_SHARE = 400 * 1024
2363
+ const SIZE_SKIPS = new Set(['$defs', 'definitions', 'description', 'title', 'examples', 'default', '$comment', 'markdownDescription', 'deprecated', 'readOnly', 'writeOnly'])
2364
+ function expandedChars (root, limit) {
2365
+ const defs = (root && (root.$defs || root.definitions)) || {}
2366
+ const memo = new Map()
2367
+ const size = (n, stack) => {
2368
+ if (n === null || typeof n !== 'object') return 8
2369
+ let t = 2
2370
+ if (Array.isArray(n)) { for (const x of n) { t += size(x, stack) + 1; if (t > limit) return t } return t }
2371
+ for (const k of Object.keys(n)) {
2372
+ if (SIZE_SKIPS.has(k)) continue
2373
+ const v = n[k]
2374
+ if (k === '$ref' && typeof v === 'string') {
2375
+ const m = /^#\/(?:\$defs|definitions)\/([^/]+)$/.exec(v)
2376
+ if (m && defs[m[1]] !== undefined && !stack.has(m[1])) {
2377
+ let d = memo.get(m[1])
2378
+ if (d === undefined) { stack.add(m[1]); d = size(defs[m[1]], stack); stack.delete(m[1]); memo.set(m[1], d) }
2379
+ t += d
2380
+ if (t > limit) return t
2381
+ continue
2382
+ }
2383
+ }
2384
+ t += k.length + 3 + size(v, stack)
2385
+ if (t > limit) return t
2386
+ }
2387
+ return t
2388
+ }
2389
+ return size(root, new Set())
2390
+ }
2302
2391
  function sourceSize (lines, ctx) {
2303
2392
  let n = 0
2304
2393
  for (const l of lines) n += l.length
@@ -2340,13 +2429,7 @@ function compileToJSCodegen(schema, schemaMap, userFormats, opts) {
2340
2429
  if (typeof sub === 'object' && sub !== null && !codegenSafe(sub, schemaMap)) return null
2341
2430
  }
2342
2431
  }
2343
- // propertyNames: only codegen simple string constraints
2344
- if (schema.propertyNames) {
2345
- const pn = schema.propertyNames
2346
- const supported = ['maxLength', 'minLength', 'pattern', 'const', 'enum']
2347
- const keys = Object.keys(pn).filter(k => k !== '$schema')
2348
- if (keys.some(k => !supported.includes(k))) return null
2349
- }
2432
+ if (schema.propertyNames && typeof schema.propertyNames === 'object' && !isSimplePN(schema.propertyNames) && !codegenSafe(schema.propertyNames, schemaMap)) return null
2350
2433
 
2351
2434
  // Build anchors map for $ref/#anchor and $dynamicRef resolution
2352
2435
  const anchors = {}
@@ -2389,7 +2472,11 @@ function compileToJSCodegen(schema, schemaMap, userFormats, opts) {
2389
2472
  // (79 KB of schema) came to 2.8 MB of verdict source, and compiling it was
2390
2473
  // half of the first validate() call.
2391
2474
  let ctx, lines
2392
- for (const share of [false, true]) {
2475
+ const refFree = _refFreeLength.get(inputSchema)
2476
+ const shareFirst = refFree !== undefined && refFree >= 0 && refFree * 8 <= EXPANDED_SHARE
2477
+ ? false
2478
+ : expandedChars(schema, EXPANDED_SHARE) > EXPANDED_SHARE
2479
+ for (const share of shareFirst ? [true] : [false, true]) {
2393
2480
  ctx = { varCounter: 0, helpers: [], helperCode: [], preamble: [], shared: [], closureVars: ['_cpLen'], closureVals: [_cpLen], rootDefs, refStack: new Set(), schemaMap: schemaMap || null, anchors, rootSchema: inputSchema, userFormats: userFormats || null, removeNodes,
2394
2481
  keywords: (opts && opts.keywords) || null, kwEmitted: null, shareDefs: share }
2395
2482
  if (ctx.keywords) ctx.kwEmitted = new Set()
@@ -3438,6 +3525,13 @@ function genPropertyNamesNode(schema, v, lines, ctx, knownType, isObj) {
3438
3525
  const pn = schema.propertyNames
3439
3526
  const ki = ctx.varCounter++
3440
3527
  const guard = isObj ? '' : `if(typeof ${v}==='object'&&${v}!==null&&!Array.isArray(${v}))`
3528
+ if (!isSimplePN(pn)) {
3529
+ const sub = []
3530
+ genCode(pn, '_pv', sub, ctx)
3531
+ if (sub.length === 0) return
3532
+ lines.push(`${guard}{const _pnf${ki}=function(_pv){${sub.join(';')};return true};for(const _k${ki} in ${v}){if(!_pnf${ki}(_k${ki}))return false}}`)
3533
+ return
3534
+ }
3441
3535
  lines.push(`${guard}{for(const _k${ki} in ${v}){`)
3442
3536
  if (pn.minLength !== undefined) lines.push(`if(_k${ki}.length<${pn.minLength})return false`)
3443
3537
  if (pn.maxLength !== undefined) lines.push(`if(_k${ki}.length>${pn.maxLength})return false`)
@@ -3760,7 +3854,7 @@ function genRefNode(schema, v, lines, ctx, knownType, hasSiblings) {
3760
3854
  function genPatternPropertiesNode(schema, v, lines, ctx, knownType, isObj) {
3761
3855
  let handledPropertyNames = false
3762
3856
  const ppEntries = Object.entries(schema.patternProperties)
3763
- const pn = schema.propertyNames && typeof schema.propertyNames === 'object' ? schema.propertyNames : null
3857
+ const pn = isSimplePN(schema.propertyNames) ? schema.propertyNames : null
3764
3858
  const pi = ctx.varCounter++
3765
3859
  const kVar = `_ppk${pi}`
3766
3860
 
@@ -4821,12 +4915,7 @@ function compileToJSCodegenWithErrors(schema, schemaMap, userFormats, sourceOpts
4821
4915
  if (typeof sub === 'object' && sub !== null && !codegenSafe(sub, schemaMap)) return null
4822
4916
  }
4823
4917
  }
4824
- if (schema.propertyNames) {
4825
- const pn = schema.propertyNames
4826
- const supported = ['maxLength', 'minLength', 'pattern', 'const', 'enum']
4827
- const keys = Object.keys(pn).filter(k => k !== '$schema')
4828
- if (keys.some(k => !supported.includes(k))) return null
4829
- }
4918
+ if (schema.propertyNames && typeof schema.propertyNames === 'object' && !isSimplePN(schema.propertyNames) && !codegenSafe(schema.propertyNames, schemaMap)) return null
4830
4919
 
4831
4920
  // Build anchors map for $ref/#anchor and $dynamicRef resolution
4832
4921
  const eRootDefs = schema.$defs || schema.definitions || null
@@ -5791,6 +5880,13 @@ function genPropertyNamesE(schema, v, pathExpr, lines, ctx, schemaPrefix, fail)
5791
5880
  const pn = schema.propertyNames
5792
5881
  const ki = ctx.varCounter++
5793
5882
  lines.push(`if(typeof ${v}==='object'&&${v}!==null&&!Array.isArray(${v})){for(const _k${ki} in ${v}){`)
5883
+ // The subschema checks the key as a value, and its errors are reported at
5884
+ // the object, as the interpreter reports them.
5885
+ if (!isSimplePN(pn)) {
5886
+ genCodeE(pn, `_k${ki}`, pathExpr, lines, ctx, schemaPrefix + '/propertyNames')
5887
+ lines.push(`}}`)
5888
+ return
5889
+ }
5794
5890
  if (pn.minLength !== undefined) {
5795
5891
  lines.push(`if(_k${ki}.length<${pn.minLength}){${fail('minLength', 'propertyNames/minLength', `{limit:${pn.minLength}}`, `'must NOT have fewer than ${pn.minLength} characters'`)}}`)
5796
5892
  }
@@ -5978,12 +6074,7 @@ function compileToJSCombined(schema, VALID_RESULT, schemaMap, userFormats, opts)
5978
6074
  if (typeof sub === 'object' && sub !== null && !codegenSafe(sub, schemaMap)) return null
5979
6075
  }
5980
6076
  }
5981
- if (schema.propertyNames) {
5982
- const pn = schema.propertyNames
5983
- const supported = ['maxLength', 'minLength', 'pattern', 'const', 'enum']
5984
- const keys = Object.keys(pn).filter(k => k !== '$schema')
5985
- if (keys.some(k => !supported.includes(k))) return null
5986
- }
6077
+ if (schema.propertyNames && typeof schema.propertyNames === 'object' && !isSimplePN(schema.propertyNames) && !codegenSafe(schema.propertyNames, schemaMap)) return null
5987
6078
 
5988
6079
  // Build anchors map for $ref/#anchor and $dynamicRef resolution
5989
6080
  const cRootDefs = schema.$defs || schema.definitions || null
@@ -6135,7 +6226,7 @@ function typeMismatchRest(schema, types) {
6135
6226
  for (const k of Object.keys(schema)) {
6136
6227
  if (drop.has(k)) continue
6137
6228
  if (rest === null) rest = {}
6138
- rest[k] = schema[k]
6229
+ setOwn(rest, k, schema[k])
6139
6230
  }
6140
6231
  if (rest === null) return null
6141
6232
  // A node whose reference was already written (genCodeCNode) keeps that
@@ -6236,7 +6327,7 @@ function genCodeCNode(schema, v, pathExpr, lines, ctx, schemaPrefix) {
6236
6327
  if (Object.keys(schema).some((k) => !REF_NEUTRAL_SIBLINGS.has(k) && !k.startsWith('x-'))) {
6237
6328
  if (!ctx.inCombined || ctx.inBranchFn) throw DECLINE
6238
6329
  genCodeCNode({ $ref: schema.$ref }, v, pathExpr, lines, ctx, schemaPrefix)
6239
- const rest = Object.assign({}, schema)
6330
+ const rest = { ...schema }
6240
6331
  Object.defineProperty(rest, REF_DONE, { value: true })
6241
6332
  genCodeCNode(rest, v, pathExpr, lines, ctx, schemaPrefix)
6242
6333
  return
@@ -6905,7 +6996,7 @@ function emitAnnot(sub, ctx) {
6905
6996
  ctx.annFns.set(sub, null)
6906
6997
  const name = `_an${ctx.varCounter++}`
6907
6998
  const local = {}
6908
- for (const k of Object.keys(sub)) if (!ANNOT_APPLICATORS.has(k)) local[k] = sub[k]
6999
+ for (const k of Object.keys(sub)) if (!ANNOT_APPLICATORS.has(k)) setOwn(local, k, sub[k])
6909
7000
  const vl = []
6910
7001
  nestedGenCode(local, '_av', vl, ctx)
6911
7002
  const decls = [`function ${name}_v(_av){${vl.join('\n ')}\n return true}`]
@@ -7053,7 +7144,7 @@ function emitAnnotI(sub, ctx) {
7053
7144
  annItemHelpers(ctx)
7054
7145
  const name = `_ai${ctx.varCounter++}`
7055
7146
  const local = {}
7056
- for (const k of Object.keys(sub)) if (!ANNOT_ITEM_APPLICATORS.has(k)) local[k] = sub[k]
7147
+ for (const k of Object.keys(sub)) if (!ANNOT_ITEM_APPLICATORS.has(k)) setOwn(local, k, sub[k])
7057
7148
  const vl = []
7058
7149
  nestedGenCode(local, '_av', vl, ctx)
7059
7150
  const decls = [`function ${name}_v(_av){${vl.join('\n ')}\n return true}`]
@@ -7169,7 +7260,7 @@ function genAdditionalSchemaC(schema, v, pathExpr, lines, ctx, schemaPrefix, isO
7169
7260
  function genPatternPropertiesC(schema, v, pathExpr, lines, ctx, schemaPrefix, fail, isObj) {
7170
7261
  let handledPropertyNames = false
7171
7262
  const ppEntries = Object.entries(schema.patternProperties)
7172
- const pn = schema.propertyNames && typeof schema.propertyNames === 'object' ? schema.propertyNames : null
7263
+ const pn = isSimplePN(schema.propertyNames) ? schema.propertyNames : null
7173
7264
  const pi = ctx.varCounter++
7174
7265
 
7175
7266
  // Build pattern matchers: prefer charCodeAt for simple prefixes
@@ -7290,7 +7381,16 @@ function genPropertyNamesFalseC(schema, v, pathExpr, lines, ctx, schemaPrefix) {
7290
7381
  function genPropertyNamesC(schema, v, pathExpr, lines, ctx, schemaPrefix, fail) {
7291
7382
  const pn = schema.propertyNames
7292
7383
  const ki = ctx.varCounter++
7384
+ // In the rich function an error carries the value it was raised on, and
7385
+ // here that would be the key where the interpreter's error points at the
7386
+ // object, so that one is left to the plain function and enrichment.
7387
+ if (!isSimplePN(pn) && ctx.rich) throw DECLINE
7293
7388
  lines.push(`if(typeof ${v}==='object'&&${v}!==null&&!Array.isArray(${v})){for(const _k${ki} in ${v}){`)
7389
+ if (!isSimplePN(pn)) {
7390
+ genCodeC(pn, `_k${ki}`, pathExpr, lines, ctx, schemaPrefix + '/propertyNames')
7391
+ lines.push(`}}`)
7392
+ return
7393
+ }
7294
7394
  if (pn.minLength !== undefined) {
7295
7395
  lines.push(`if(_k${ki}.length<${pn.minLength}){${fail('minLength', 'propertyNames/minLength', `{limit:${pn.minLength}}`, `'must NOT have fewer than ${pn.minLength} characters'`)}}`)
7296
7396
  }
@@ -7508,6 +7608,6 @@ function _collectEval(schema, result, defs, schemaMap, refStack, isRoot) {
7508
7608
  // not → contributes nothing (spec: annotations from not are discarded)
7509
7609
  }
7510
7610
 
7511
- module.exports = { compileToJS, compileToJSCodegen, compileToJSCodegenWithErrors, compileToJSCombined, collectEvaluated, unevalContributions, AJV_MESSAGES, _setCodeBudget }
7611
+ module.exports = { compileToJS, compileToJSCodegen, compileToJSCodegenWithErrors, compileToJSCombined, collectEvaluated, unevalContributions, expandedChars, AJV_MESSAGES, _setCodeBudget }
7512
7612
  // For tests/test_custom_keyword_codegen.js, which holds these copies to the interpreter's.
7513
7613
  Object.defineProperty(module.exports, '_kwTypeBits', { value: { kwTypeBit, kwDataBits, KW_T_ANY }, enumerable: false })
@@ -1,7 +1,14 @@
1
1
  'use strict';
2
2
 
3
- // Bounded Levenshtein. Returns Infinity if distance > maxDistance.
4
- // Single-row DP. O(n*m) worst case but typical strings are <30 chars.
3
+ // Bounded Levenshtein. Returns Infinity if distance > maxDistance (or, past
4
+ // the bound, possibly some number greater than it; callers compare).
5
+ //
6
+ // Only the cells within the bound of the diagonal are computed: a path
7
+ // through any other cell already costs more than the bound. A typo hint asks
8
+ // with a bound of one or two and compares the missing name with every key
9
+ // present, so a row is five cells instead of the whole key. Without a bound
10
+ // the band is the whole row.
11
+ //
5
12
  // Two rows of scratch, reused across calls. The function is not recursive
6
13
  // and the library is single-threaded, so nothing else is using them; growing
7
14
  // them once beats allocating two arrays on every comparison, which the
@@ -10,42 +17,47 @@ let scratchA = new Int32Array(64);
10
17
  let scratchB = new Int32Array(64);
11
18
 
12
19
  function levenshtein (a, b, maxDistance) {
13
- const max = maxDistance == null ? Infinity : maxDistance;
14
20
  if (a === b) return 0;
15
- if (Math.abs(a.length - b.length) > max) return Infinity;
16
- if (a.length === 0) return b.length;
17
- if (b.length === 0) return a.length;
18
- if (scratchA.length < b.length + 1) {
19
- scratchA = new Int32Array(b.length + 1);
20
- scratchB = new Int32Array(b.length + 1);
21
+ const n = a.length;
22
+ const m = b.length;
23
+ const max = maxDistance == null || maxDistance > n + m ? n + m : maxDistance;
24
+ if (Math.abs(n - m) > max) return Infinity;
25
+ if (n === 0) return m;
26
+ if (m === 0) return n;
27
+ const big = max + 1;
28
+ if (scratchA.length < m + 2) {
29
+ scratchA = new Int32Array(m + 2);
30
+ scratchB = new Int32Array(m + 2);
21
31
  }
22
32
  let prev = scratchA;
23
33
  let curr = scratchB;
24
- const bn = b.length;
25
- for (let j = 0; j <= bn; j++) prev[j] = j;
26
- for (let i = 1; i <= a.length; i++) {
27
- curr[0] = i;
28
- let rowMin = i;
34
+ for (let j = 0; j <= m; j++) prev[j] = j <= max ? j : big;
35
+ for (let i = 1; i <= n; i++) {
36
+ const lo = i - max > 1 ? i - max : 1;
37
+ const hi = i + max < m ? i + max : m;
38
+ curr[lo - 1] = lo === 1 ? i : big;
39
+ let rowMin = curr[lo - 1];
29
40
  // charCodeAt rather than indexing: reading a character by index allocates
30
41
  // a one-character string, and this is the innermost loop.
31
42
  const ca = a.charCodeAt(i - 1);
32
- for (let j = 1; j <= bn; j++) {
33
- const cost = ca === b.charCodeAt(j - 1) ? 0 : 1;
34
- let m = prev[j - 1] + cost;
43
+ for (let j = lo; j <= hi; j++) {
44
+ let v = prev[j - 1] + (ca === b.charCodeAt(j - 1) ? 0 : 1);
35
45
  const del = curr[j - 1] + 1;
36
- if (del < m) m = del;
46
+ if (del < v) v = del;
37
47
  const ins = prev[j] + 1;
38
- if (ins < m) m = ins;
39
- curr[j] = m;
40
- if (m < rowMin) rowMin = m;
48
+ if (ins < v) v = ins;
49
+ if (v > big) v = big;
50
+ curr[j] = v;
51
+ if (v < rowMin) rowMin = v;
41
52
  }
53
+ if (hi < m) curr[hi + 1] = big;
42
54
  if (rowMin > max) return Infinity;
43
55
  // A destructured swap builds an array per row; a temporary does not.
44
56
  const t = prev;
45
57
  prev = curr;
46
58
  curr = t;
47
59
  }
48
- return prev[bn];
60
+ return prev[m] > max ? Infinity : prev[m];
49
61
  }
50
62
 
51
63
  module.exports = { levenshtein };
@@ -0,0 +1,100 @@
1
+ 'use strict';
2
+
3
+ // The entries buildTargetedPositionMap gives, found by the native addon where
4
+ // it can: simdjson indexes the document's structure once and walks that
5
+ // index to each pointer, where the script walk reads every character. On a
6
+ // 5 MB log with the error near its end that is 3.1 ms against 8.3. Only for
7
+ // a large ASCII string, where the addon's UTF-8 byte offsets are the string's
8
+ // indexes, and only when every wanted pointer is found; otherwise null, and
9
+ // the caller builds the map in script as before.
10
+ const NATIVE_MIN = 32 * 1024;
11
+ let _native;
12
+ function nativeTargeted (text, wanted) {
13
+ if (typeof text !== 'string' || text.length < NATIVE_MIN) return null;
14
+ if (_native === undefined) {
15
+ try { _native = require('./native-load')(); } catch { _native = null; }
16
+ if (_native && typeof _native.locatePointers !== 'function') _native = null;
17
+ }
18
+ if (_native === null || typeof Buffer === 'undefined' || Buffer.byteLength(text, 'utf8') !== text.length) return null;
19
+ const ptrs = Array.from(wanted);
20
+ let found;
21
+ try { found = _native.locatePointers(text, ptrs); } catch { return null; }
22
+ const offs = [];
23
+ for (let k = 0; k < ptrs.length; k++) {
24
+ if (!(found[2 * k] >= 0)) return null;
25
+ offs.push(found[2 * k]);
26
+ }
27
+ // Line starts for the offsets that are asked about, in one forward pass.
28
+ const needed = [];
29
+ for (let k = 0; k < ptrs.length; k++) {
30
+ needed.push(offs[k]);
31
+ const ks = keyStartBefore(text, offs[k]);
32
+ if (ks >= 0) needed.push(ks);
33
+ }
34
+ const lineOf = lineFinder(text, needed);
35
+ const map = Object.create(null);
36
+ for (let k = 0; k < ptrs.length; k++) {
37
+ const off = offs[k];
38
+ const at = lineOf(off);
39
+ const entry = { byteOffset: off, length: found[2 * k + 1], line: at.line, col: off - at.start + 1, text: at.text };
40
+ const ks = keyStartBefore(text, off);
41
+ if (ks >= 0 && ptrs[k] !== '') {
42
+ const kat = lineOf(ks);
43
+ entry.keyOffset = ks;
44
+ entry.keyLength = keyEndAfter(text, ks) - ks + 1;
45
+ entry.keyLine = kat.line;
46
+ entry.keyCol = ks - kat.start + 1;
47
+ }
48
+ map[ptrs[k]] = entry;
49
+ }
50
+ return map;
51
+ }
52
+
53
+ // The opening quote of the member name before a value at `off`, or -1 where
54
+ // the value is an array element or the root.
55
+ function keyStartBefore (text, off) {
56
+ let j = off - 1;
57
+ while (j >= 0) { const c = text.charCodeAt(j); if (c === 0x20 || c === 0x09 || c === 0x0a || c === 0x0d) j--; else break; }
58
+ if (j < 0 || text.charCodeAt(j) !== 0x3a) return -1;
59
+ j--;
60
+ while (j >= 0) { const c = text.charCodeAt(j); if (c === 0x20 || c === 0x09 || c === 0x0a || c === 0x0d) j--; else break; }
61
+ if (j < 0 || text.charCodeAt(j) !== 0x22) return -1;
62
+ // Back to the quote that opens the name: one not preceded by an odd run of
63
+ // backslashes.
64
+ for (let q = j - 1; q >= 0; q--) {
65
+ if (text.charCodeAt(q) !== 0x22) continue;
66
+ let b = q - 1;
67
+ while (b >= 0 && text.charCodeAt(b) === 0x5c) b--;
68
+ if (((q - 1 - b) & 1) === 0) return q;
69
+ }
70
+ return -1;
71
+ }
72
+ function keyEndAfter (text, start) {
73
+ let q = start + 1;
74
+ for (;;) {
75
+ const c = text.charCodeAt(q);
76
+ if (c === 0x5c) { q += 2; continue; }
77
+ if (c === 0x22 || q >= text.length) return q;
78
+ q++;
79
+ }
80
+ }
81
+ // Line number, line start and line text for any of `offsets`, from one pass.
82
+ function lineFinder (text, offsets) {
83
+ const sorted = Array.from(new Set(offsets)).sort((a, b) => a - b);
84
+ const info = new Map();
85
+ let line = 1, start = 0, s = 0;
86
+ while (s < sorted.length) {
87
+ const nl = text.indexOf('\n', start);
88
+ const end = nl === -1 ? text.length : nl;
89
+ while (s < sorted.length && sorted[s] <= end) {
90
+ info.set(sorted[s], { line, start, text: text.slice(start, end) });
91
+ s++;
92
+ }
93
+ if (nl === -1) break;
94
+ line++;
95
+ start = nl + 1;
96
+ }
97
+ return (off) => info.get(off);
98
+ }
99
+
100
+ module.exports = { nativeTargeted };
package/lib/rejections.js CHANGED
@@ -133,13 +133,18 @@ Object.defineProperty(RichRejection.prototype, 'errors', {
133
133
  // touches none of it. The JSON text is held here rather than read back off the
134
134
  // validator, so the result does not depend on what the instance does next.
135
135
  class LazyJsonRejection {
136
- constructor(result, jsonStr, self, enrich) {
136
+ // `parsed`, when given, is the document the inner path parsed from
137
+ // `jsonStr` and validated, after defaults, coercion and removal: reading the
138
+ // errors uses it instead of parsing the text again, which on a 5 MB
139
+ // document was a third of what reading them cost.
140
+ constructor(result, jsonStr, self, enrich, parsed) {
137
141
  this.valid = false;
138
142
  this._result = result;
139
143
  this._jsonStr = jsonStr;
140
144
  this._self = self;
141
145
  this._enrich = enrich;
142
146
  this._cached = null;
147
+ this._parsed = parsed;
143
148
  }
144
149
  toJSON() {
145
150
  return { valid: false, errors: this.errors };
@@ -159,12 +164,14 @@ Object.defineProperty(LazyJsonRejection.prototype, 'errors', {
159
164
 
160
165
  // The enrich pass plucks `received` from the value tree and the suggestion
161
166
  // engine (required-typo, format hints, coercion nudges) walks it too.
162
- let parsedData;
163
- try { parsedData = JSON.parse(jsonStr); } catch { parsedData = undefined; }
164
- // What was checked is the document after defaults, coercion and removal,
165
- // so `received` has to come from that, as it does for validate(). A
166
- // default that failed its own schema has no value in the text at all.
167
- if (parsedData !== undefined && self._preprocess) self._preprocess(parsedData);
167
+ let parsedData = this._parsed;
168
+ if (parsedData === undefined) {
169
+ try { parsedData = JSON.parse(jsonStr); } catch { parsedData = undefined; }
170
+ // What was checked is the document after defaults, coercion and removal,
171
+ // so `received` has to come from that, as it does for validate(). A
172
+ // default that failed its own schema has no value in the text at all.
173
+ if (parsedData !== undefined && self._preprocess) self._preprocess(parsedData);
174
+ }
168
175
 
169
176
  // Errors the inner path already enriched carry a `received` key: enrich()
170
177
  // always sets one, even when there is no value to show. `code` is not a
@@ -29,22 +29,35 @@ const FORMAT_HINTS = {
29
29
  ipv4: (val) => typeof val === 'string' ? 'expected four 0-255 octets separated by dots' : null,
30
30
  };
31
31
 
32
- function suggestEnumTypo (received, enumValues) {
33
- if (typeof received !== 'string') return null;
34
- if (!Array.isArray(enumValues) || enumValues.length === 0 || enumValues.length > 30) return null;
32
+ // Edits that still read as a typo of a name this long. With a flat two, a
33
+ // missing `name` was "corrected" to `age`, and `c` to `a`.
34
+ function typoBudget (name) {
35
+ const n = name.length;
36
+ return n < 3 ? 0 : n < 5 ? 1 : 2;
37
+ }
38
+
39
+ // The one string candidate nearest `target` within the budget, or null when
40
+ // none is near enough or two are equally near.
41
+ function nearest (target, candidates) {
42
+ const budget = typoBudget(target);
35
43
  let best = null;
36
- let bestDist = Infinity;
44
+ let bestDist = budget + 1;
37
45
  let tied = false;
38
- for (const v of enumValues) {
39
- if (typeof v !== 'string') continue;
40
- const d = levenshtein(received, v, 2);
41
- if (d < bestDist) { best = v; bestDist = d; tied = false; }
46
+ for (const c of candidates) {
47
+ if (typeof c !== 'string') continue;
48
+ const d = levenshtein(target, c, budget);
49
+ if (d === 0 || d > budget) continue;
50
+ if (d < bestDist) { best = c; bestDist = d; tied = false; }
42
51
  else if (d === bestDist) tied = true;
43
52
  }
44
- if (best && bestDist <= 2 && !tied) {
45
- return { text: `did you mean \`${best}\`?`, kind: 'typo' };
46
- }
47
- return null;
53
+ return tied ? null : best;
54
+ }
55
+
56
+ function suggestEnumTypo (received, enumValues) {
57
+ if (typeof received !== 'string') return null;
58
+ if (!Array.isArray(enumValues) || enumValues.length === 0 || enumValues.length > 30) return null;
59
+ const best = nearest(received, enumValues);
60
+ return best === null ? null : { text: `did you mean \`${best}\`?`, kind: 'typo' };
48
61
  }
49
62
 
50
63
  // The widest container a typo hint is offered on. `suggestEnumTypo` caps its
@@ -56,14 +69,8 @@ const MAX_TYPO_CANDIDATES = 64;
56
69
  function suggestRequiredTypo (missing, presentKeys) {
57
70
  if (!missing || !Array.isArray(presentKeys)) return null;
58
71
  if (presentKeys.length > MAX_TYPO_CANDIDATES) return null;
59
- for (const k of presentKeys) {
60
- if (typeof k !== 'string') continue;
61
- const d = levenshtein(missing, k, 2);
62
- if (d <= 2 && d > 0) {
63
- return { text: `did you mean \`${missing}\` instead of \`${k}\`?`, kind: 'similar-key' };
64
- }
65
- }
66
- return null;
72
+ const best = nearest(missing, presentKeys);
73
+ return best === null ? null : { text: `did you mean \`${missing}\` instead of \`${best}\`?`, kind: 'similar-key' };
67
74
  }
68
75
 
69
76
  function suggestFormat (format, raw) {
@@ -604,8 +604,10 @@ function resolveSchemaForPreprocess(schema, schemaMap) {
604
604
  if (p && typeof p === 'object' && p.$ref && !p.type) {
605
605
  const t = resolveRefForPreprocess(p.$ref, schemaMap)
606
606
  if (t && typeof t === 'object') {
607
- if (!cloned) { cloned = Object.assign({}, s); cloned.properties = Object.assign({}, s.properties) }
608
- cloned.properties[key] = t
607
+ // Spread and defineProperty: a property named "__proto__" stays an own
608
+ // key of the copy (assignment would set the copy's prototype).
609
+ if (!cloned) { cloned = { ...s }; cloned.properties = { ...s.properties } }
610
+ Object.defineProperty(cloned.properties, key, { value: t, writable: true, enumerable: true, configurable: true })
609
611
  }
610
612
  }
611
613
  }
@@ -775,6 +777,7 @@ class Validator {
775
777
  // to attach dataFrame entries to each enriched error.
776
778
  this._posCache = null; // created by _pos() on first use, only the JSON text path needs it
777
779
  this._lastRawInput = null;
780
+ this._lastParsed = undefined;
778
781
  // undefined: not built yet. null: this schema has no scanner.
779
782
  this._scanner = undefined;
780
783
  // Checks a wrapper registered through _extendChecks. Declared here so
@@ -815,7 +818,7 @@ class Validator {
815
818
  }
816
819
 
817
820
  _pos() {
818
- return this._posCache || (this._posCache = require('./data-position-cache').createCache());
821
+ return this._posCache || (this._posCache = require('./data-position-cache').createCache(_codegen !== null && _codegen.nativePositions ? _codegen.nativePositions() : null));
819
822
  }
820
823
 
821
824
  _ensureVocabularies() {
@@ -847,7 +850,12 @@ class Validator {
847
850
  // "validate this schema against its dialect" needs no network and no
848
851
  // caller-supplied registry. Only schemas that mention json-schema.org in a
849
852
  // reference pay for the lookup.
850
- if (this._schemaStr.includes('json-schema.org/draft')) {
853
+ // A reference, not `$schema`: nearly every schema names its dialect, and
854
+ // matching that loaded the vendored meta-schemas into every validator's
855
+ // map, 27 entries that made its compile cache key a 30 KB string, rebuilt
856
+ // in 20 microseconds at each compile, and sent every compile through the
857
+ // external-document checks.
858
+ if (this._schemaStr.includes('json-schema.org/') && /"\$(?:ref|dynamicRef|recursiveRef)":"[^"]*json-schema\.org\//.test(this._schemaStr)) {
851
859
  const { METASCHEMAS } = require('./metaschemas');
852
860
  this._ownSchemaMap();
853
861
  for (const [id, meta] of METASCHEMAS) {
@@ -1104,6 +1112,17 @@ class Validator {
1104
1112
  _bindVerdict(this, this._fastVerdict
1105
1113
  ? this._fastVerdict
1106
1114
  : (data) => run(data).valid);
1115
+ // Parsed, then the verdict first and the errors only for a document that
1116
+ // fails: collecting them on every document cost an accepted 5 MB one 9 ms
1117
+ // over JSON.parse and validate(). At this layer, below the presentation
1118
+ // that the text path wraps around it: going through validate() as it
1119
+ // stands once every layer is in place handed the text path errors
1120
+ // already presented, and they lost their source anchor.
1121
+ const runText = options.abortEarly
1122
+ ? run
1123
+ : (preprocess
1124
+ ? (data) => { preprocess(data); return interp.isValid(data) ? VALID_RESULT : interp.validate(data); }
1125
+ : (data) => (interp.isValid(data) ? VALID_RESULT : interp.validate(data)));
1107
1126
  this.validateJSON = (jsonStr) => {
1108
1127
  let data;
1109
1128
  try {
@@ -1112,9 +1131,19 @@ class Validator {
1112
1131
  if (!(e instanceof SyntaxError)) throw e;
1113
1132
  return _jsonSyntaxRejection(e);
1114
1133
  }
1115
- return run(data);
1134
+ this._lastParsed = data;
1135
+ return runText(data);
1136
+ };
1137
+ this.isValidJSON = (jsonStr) => {
1138
+ let data;
1139
+ try {
1140
+ data = JSON.parse(jsonStr);
1141
+ } catch (e) {
1142
+ if (!(e instanceof SyntaxError)) throw e;
1143
+ return false;
1144
+ }
1145
+ return this.isValidObject(data);
1116
1146
  };
1117
- this.isValidJSON = (jsonStr) => this.validateJSON(jsonStr).valid;
1118
1147
  }
1119
1148
 
1120
1149
  // Error presentation, one lazy layer: declaration-order sorting, rich
@@ -1163,21 +1192,38 @@ class Validator {
1163
1192
  // rejection it builds. Cleared as soon as it returns: the rejection
1164
1193
  // carries the text itself, so nothing outlives the call.
1165
1194
  this._lastRawInput = jsonStr;
1166
- let result;
1195
+ this._lastParsed = undefined;
1196
+ let result, parsed;
1167
1197
  try {
1168
1198
  result = innerJson(jsonStr);
1169
1199
  } finally {
1170
1200
  this._lastRawInput = null;
1201
+ parsed = this._lastParsed;
1202
+ this._lastParsed = undefined;
1171
1203
  }
1172
1204
  // Every diagnostic the text path adds is deferred. Deciding here
1173
1205
  // whether there is anything to add would mean reading `result.errors`,
1174
1206
  // and on the codegen path that realizes the inner lazy layer, which is
1175
1207
  // the document walk this exists to avoid.
1176
1208
  if (result && result.valid === false) {
1177
- return new LazyJsonRejection(result, jsonStr, this, enrich);
1209
+ return new LazyJsonRejection(result, jsonStr, this, enrich, parsed);
1178
1210
  }
1179
1211
  return result;
1180
1212
  };
1213
+ } else if (this.validateJSON) {
1214
+ // Without enrichment the text path still presents its errors as
1215
+ // validate() does, in declaration order: on generated code they came
1216
+ // out in the order the function found them, so the same document gave
1217
+ // its errors in one order from validate() and another from here.
1218
+ const innerJson = this.validateJSON;
1219
+ this.validateJSON = (jsonStr) => {
1220
+ this._lastParsed = undefined;
1221
+ const result = innerJson(jsonStr);
1222
+ const parsed = this._lastParsed;
1223
+ this._lastParsed = undefined;
1224
+ if (!(result && result.valid === false && parsed !== undefined)) return result;
1225
+ return present(result, parsed);
1226
+ };
1181
1227
  }
1182
1228
  }
1183
1229
 
@@ -1211,10 +1257,12 @@ class Validator {
1211
1257
  // errorMessage keep the validate hot path untouched. Layered after rich
1212
1258
  // enrichment so `code`/`keyword`/`path` are already final and only the
1213
1259
  // human-facing message changes.
1260
+ // The module loads only for a schema that has the keyword: loading it
1261
+ // to ask was a file read on every process's first compile.
1214
1262
  {
1215
- const emLib = require('./error-messages');
1216
1263
  const schemaStr = this._schemaStr || (this._schemaObj ? JSON.stringify(this._schemaObj) : '');
1217
- if (emLib.schemaHasErrorMessages(schemaStr)) {
1264
+ if (schemaStr.indexOf('"errorMessage"') !== -1) {
1265
+ const emLib = require('./error-messages');
1218
1266
  const root = this._schemaObj;
1219
1267
  const wrap = (inner) => (arg) => {
1220
1268
  const result = inner(arg);
@@ -2184,7 +2232,15 @@ for (const [name, pick] of [
2184
2232
  });
2185
2233
  }
2186
2234
 
2235
+ // Cold start: an interpreted twin answers a large schema's first calls where
2236
+ // the code generator is registered (see coldTwin in index.js). Lite has none.
2237
+ Validator.prototype._coldTwin = function () {
2238
+ return _codegen === null || _codegen.coldTwin === undefined ? null : _codegen.coldTwin(this, Validator);
2239
+ };
2240
+
2187
2241
  _defineLazyMethod('validate', (self) => (data) => {
2242
+ const t = self._coldTwin();
2243
+ if (t !== null) return t.validate(data);
2188
2244
  self._ensureCompiled();
2189
2245
  return self.validate(data);
2190
2246
  });
@@ -2195,6 +2251,7 @@ _defineLazyMethod('isValidObject', (self) => {
2195
2251
  // again on every call, about 200 ns, ten times the check.
2196
2252
  let bound = false;
2197
2253
  return (data) => {
2254
+ if (!bound) { const t = self._coldTwin(); if (t !== null) return t.isValidObject(data); }
2198
2255
  if (bound) return self.isValidObject(data);
2199
2256
  bound = true;
2200
2257
  // A validator that rewrites its input goes through the full compile, which
@@ -2234,10 +2291,14 @@ _defineLazyMethod('isValidObject', (self) => {
2234
2291
  };
2235
2292
  });
2236
2293
  _defineLazyMethod('validateJSON', (self) => (jsonStr) => {
2294
+ const t = self._coldTwin();
2295
+ if (t !== null) return t.validateJSON(jsonStr);
2237
2296
  self._ensureCompiled();
2238
2297
  return self.validateJSON(jsonStr);
2239
2298
  });
2240
2299
  _defineLazyMethod('isValidJSON', (self) => (jsonStr) => {
2300
+ const t = self._coldTwin();
2301
+ if (t !== null) return t.isValidJSON(jsonStr);
2241
2302
  self._ensureCompiled();
2242
2303
  return self.isValidJSON(jsonStr);
2243
2304
  });
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.43.0';
10
+ module.exports = '1.44.0';
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "ata-validator",
3
- "version": "1.43.0",
3
+ "version": "1.44.0",
4
4
  "description": "JSON Schema validator that compiles to plain JavaScript, still runs where code generation is blocked, and can be compiled away at build time. Passes the full official test suite.",
5
5
  "main": "index.js",
6
6
  "module": "index.mjs",
@@ -64,7 +64,7 @@
64
64
  "release:check": "node tests/test_pack_purity.js && node scripts/check-doc-coverage.js && node tests/test_error_codes_lock.js && node tests/test_safe_regex_embed.js && node tests/test_version_sync.js",
65
65
  "build": "cmake-js build --target ata",
66
66
  "rebuild": "cmake-js rebuild --target ata",
67
- "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_number_text_parity.js && node tests/test_text_path_errors.js && node tests/test_compiled_parity.js && node tests/test_compiled_extension_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_error_list_differential.js && node tests/test_output_format.js && node tests/test_standard_json_schema.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.js && node tests/test_browser_nofs.js && node tests/test_browser_imports_guard.js && node tests/test_esm_exports.js && node tests/test_version_sync.js && node tests/test_native_loaded.js && node tests/test_native_lazy.js && node tests/test_buffer_apis_without_native.js && node tests/test_cold_start_modules.js && node tests/test_safe_regex_embed.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_regex_ecma_whitespace.js && node tests/test_regex_native_linear.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_generator_error_content.js && node tests/test_inherited_key_presence.js && node tests/test_proto_accessor.js && node tests/test_hybrid_tier.js && node tests/test_first_rejection_builds_one.js && node tests/test_container_local.js && node tests/test_one_shot_validate.js && node tests/test_fixed_pattern_inline.js && node tests/test_plan_source.js && node tests/test_fused_remove_additional.js && node tests/test_runtime_parse.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_encoded_refs.js && node tests/test_cross_doc_root_ref.js && node tests/test_codegen_entrypoint_agreement.js && node tests/test_schema_code_injection.js && node tests/test_aot_html_safe.js && node tests/test_ref_annotation_siblings.js && node tests/test_gate_wrapped_schemas.js && node tests/test_pattern_message_escape.js && node tests/test_nested_defaults_engines.js && node tests/test_nested_coercion_parity.js && node tests/test_preprocess_cache.js && node tests/test_large_schema_codegen.js && node tests/test_base_uri.js && node tests/test_hoisted_def_recursion.js && node tests/test_alias_definitions.js && node tests/test_property_names_ref.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_custom_keyword_codegen.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_unevaluated_differential.js && node tests/fuzz_differential.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_ref_error_order.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_error_read_paths.js && node tests/test_unique_items_error_pair.js && node tests/test_rich_errors_generated.js && node tests/test_captured_verdict_stub.js && node tests/test_generated_returns_rewrite.js && node tests/test_deep_pointer_refs.js && node tests/test_uneval_props_dynamic.js && node tests/test_one_pass_no_fallback.js && node tests/test_scanner_size_cap.js && node tests/test_scanner_index_keys.js && node tests/test_both_defs_containers.js && node tests/test_uneval_branch_counts.js && node tests/test_branch_verdicts.js && node tests/test_shared_definitions.js && node tests/test_compile_cache_bound.js && node tests/test_uneval_items_dynamic.js && node tests/test_escaped_property_names.js && node tests/test_ref_siblings_combined.js && node tests/test_unicode_property_patterns.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_lite_parity.js && node tests/test_lite_bundle_size.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_render_without_process.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_aot_source_frames.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 tests/test_collect_evaluated.js && node tests/test_differential_tiers.js && node tests/test_draft7.js && node tests/test_dual_path.js && node tests/test_keywords_codegen.js && node tests/test_ref_cross.js && node tests/test_shape_classifier.js && node tests/test_tier0.js && node benchmark/bench_aot_size.mjs",
67
+ "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_number_text_parity.js && node tests/test_text_path_errors.js && node tests/test_compiled_parity.js && node tests/test_compiled_extension_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_error_list_differential.js && node tests/test_output_format.js && node tests/test_standard_json_schema.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.js && node tests/test_browser_nofs.js && node tests/test_browser_imports_guard.js && node tests/test_esm_exports.js && node tests/test_version_sync.js && node tests/test_native_loaded.js && node tests/test_native_lazy.js && node tests/test_buffer_apis_without_native.js && node tests/test_cold_start_modules.js && node tests/test_safe_regex_embed.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_regex_ecma_whitespace.js && node tests/test_regex_native_linear.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_generator_error_content.js && node tests/test_inherited_key_presence.js && node tests/test_proto_accessor.js && node tests/test_hybrid_tier.js && node tests/test_first_rejection_builds_one.js && node tests/test_container_local.js && node tests/test_one_shot_validate.js && node tests/test_fixed_pattern_inline.js && node tests/test_plan_source.js && node tests/test_fused_remove_additional.js && node tests/test_runtime_parse.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_encoded_refs.js && node tests/test_cross_doc_root_ref.js && node tests/test_codegen_entrypoint_agreement.js && node tests/test_schema_code_injection.js && node tests/test_aot_html_safe.js && node tests/test_ref_annotation_siblings.js && node tests/test_gate_wrapped_schemas.js && node tests/test_pattern_message_escape.js && node tests/test_nested_defaults_engines.js && node tests/test_nested_coercion_parity.js && node tests/test_preprocess_cache.js && node tests/test_large_schema_codegen.js && node tests/test_base_uri.js && node tests/test_hoisted_def_recursion.js && node tests/test_alias_definitions.js && node tests/test_property_names_ref.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_custom_keyword_codegen.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_unevaluated_differential.js && node tests/fuzz_differential.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_ref_error_order.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_error_read_paths.js && node tests/test_unique_items_error_pair.js && node tests/test_rich_errors_generated.js && node tests/test_captured_verdict_stub.js && node tests/test_generated_returns_rewrite.js && node tests/test_deep_pointer_refs.js && node tests/test_uneval_props_dynamic.js && node tests/test_one_pass_no_fallback.js && node tests/test_scanner_size_cap.js && node tests/test_scanner_index_keys.js && node tests/test_both_defs_containers.js && node tests/test_uneval_branch_counts.js && node tests/test_branch_verdicts.js && node tests/test_property_names_subschema.js && node tests/test_property_named_ref.js && node tests/test_validate_json_engines_same_errors.js && node tests/test_prototype_property_names.js && node tests/test_proto_name_rename_invariance.js && node tests/test_shared_definitions.js && node tests/test_compile_cache_bound.js && node tests/test_uneval_items_dynamic.js && node tests/test_escaped_property_names.js && node tests/test_ref_siblings_combined.js && node tests/test_unicode_property_patterns.js && node tests/test_cold_twin.js && node tests/test_native_positions.js && node tests/test_validate_json_single_parse.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_lite_parity.js && node tests/test_lite_bundle_size.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_render_without_process.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_aot_source_frames.js && node tests/test_abort_early.js && node tests/test_branch_collapse.js && node tests/test_suggestions.js && node tests/test_levenshtein_banded.js && node tests/test_cli_validate.js && node tests/test_cli_version.js && node tests/test_collect_evaluated.js && node tests/test_differential_tiers.js && node tests/test_draft7.js && node tests/test_dual_path.js && node tests/test_keywords_codegen.js && node tests/test_ref_cross.js && node tests/test_shape_classifier.js && node tests/test_tier0.js && node benchmark/bench_aot_size.mjs",
68
68
  "bench:size": "node benchmark/bench_aot_size.mjs",
69
69
  "test:suite": "node tests/run_suite.js && node tests/run_suite.js draft7 && node tests/run_suite.js v1",
70
70
  "test:compat": "node tests/test_compat.js",
@@ -143,13 +143,13 @@
143
143
  "LICENSE"
144
144
  ],
145
145
  "optionalDependencies": {
146
- "@ata-validator/native-darwin-arm64": "1.43.0",
147
- "@ata-validator/native-darwin-x64": "1.43.0",
148
- "@ata-validator/native-linux-arm64-gnu": "1.43.0",
149
- "@ata-validator/native-linux-arm64-musl": "1.43.0",
150
- "@ata-validator/native-linux-x64-gnu": "1.43.0",
151
- "@ata-validator/native-linux-x64-musl": "1.43.0",
152
- "@ata-validator/native-win32-x64": "1.43.0"
146
+ "@ata-validator/native-darwin-arm64": "1.44.0",
147
+ "@ata-validator/native-darwin-x64": "1.44.0",
148
+ "@ata-validator/native-linux-arm64-gnu": "1.44.0",
149
+ "@ata-validator/native-linux-arm64-musl": "1.44.0",
150
+ "@ata-validator/native-linux-x64-gnu": "1.44.0",
151
+ "@ata-validator/native-linux-x64-musl": "1.44.0",
152
+ "@ata-validator/native-win32-x64": "1.44.0"
153
153
  },
154
154
  "peerDependencies": {
155
155
  "yaml": "^2.0.0"