ata-validator 1.40.1 → 1.41.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
@@ -5,6 +5,8 @@ JSON Schema validation that compiles for speed and still runs where code generat
5
5
  [![npm](https://img.shields.io/npm/v/ata-validator)](https://www.npmjs.com/package/ata-validator)
6
6
  [![License](https://img.shields.io/npm/l/ata-validator)](LICENSE)
7
7
  [![OpenSSF Scorecard](https://api.scorecard.dev/projects/github.com/ata-core/ata-validator/badge)](https://scorecard.dev/viewer/?uri=github.com/ata-core/ata-validator)
8
+ [![OpenSSF Best Practices](https://www.bestpractices.dev/projects/15196/badge)](https://www.bestpractices.dev/projects/15196)
9
+ [![Socket Badge](https://badge.socket.dev/npm/package/ata-validator)](https://socket.dev/npm/package/ata-validator)
8
10
 
9
11
  1.0 is a stability commitment: see [docs/STABILITY.md](docs/STABILITY.md) for the semver, deprecation, and error-code guarantees.
10
12
 
package/index.js CHANGED
@@ -395,8 +395,9 @@ installCodegenPaths.compileVerdict = function compileVerdict () {
395
395
  return;
396
396
  }
397
397
  const uf = this._userFormats;
398
- const _cg = compileToJSCodegen(this._schemaObj, sm, uf);
399
- const jsFn = _cg || compileToJS(this._schemaObj, null, sm);
398
+ // Custom keywords: see the note on the same call in lib/validator-core.js.
399
+ const _cg = compileToJSCodegen(this._schemaObj, sm, uf, this._keywords ? { keywords: this._keywords } : undefined);
400
+ const jsFn = _cg || (this._usesKeywords ? null : compileToJS(this._schemaObj, null, sm));
400
401
  this._jsFn = jsFn;
401
402
  if (jsFn) {
402
403
  _bindVerdict(this, jsFn);
@@ -408,7 +409,7 @@ installCodegenPaths.compileVerdict = function compileVerdict () {
408
409
  // is the bug this cache had once already. `isCodegen` rides along so a
409
410
  // validator that later reuses this entry reports the same engine it
410
411
  // would have compiled to.
411
- if (!uf) {
412
+ if (!uf && !this._keywords) {
412
413
  if (!cached) _compileCache.set(mapKey, { jsFn, combined: undefined, errFn: undefined, isCodegen: !!_cg, full: false });
413
414
  else cached.jsFn = jsFn;
414
415
  }
@@ -433,6 +434,9 @@ installCodegenPaths.installScanner = function installScanner (schemaObj, options
433
434
  // builds it now, which is how the differential test reaches it.
434
435
  this._ensureScanner = (now) => {
435
436
  if (self._scanner === undefined) {
437
+ // The scanner reads the schema's own keywords from the text and knows
438
+ // nothing of custom ones, so it would accept what they reject.
439
+ if (self._usesKeywords) { self._scanner = null; return null; }
436
440
  if (!now && ++calls < SCAN_AFTER) return undefined;
437
441
  const built = require('./lib/scan-compiler').compileScanner(schemaObj, { userFormats: self._userFormats });
438
442
  self._scanner = built ? built.scan : null;
package/lib/aot-build.js CHANGED
@@ -30,9 +30,13 @@ async function expandGlobs(globs) {
30
30
  out.push(path.resolve(g));
31
31
  continue;
32
32
  }
33
- const m = g.match(/^(.*?)(?:\/\*\.(.+))?$/);
34
- const dir = m && m[1] ? m[1] : '.';
35
- const ext = m && m[2] ? '.' + m[2] : null;
33
+ // Split on the first "/*." that has something after it, by index rather
34
+ // than a regular expression, which backtracked polynomially on a long
35
+ // run of "/*.a".
36
+ const star = g.indexOf('/*.');
37
+ const split = star !== -1 && star + 3 < g.length;
38
+ const dir = (split ? g.slice(0, star) : g) || '.';
39
+ const ext = split ? '.' + g.slice(star + 3) : null;
36
40
  if (!fs.existsSync(dir)) continue;
37
41
  for (const entry of fs.readdirSync(dir)) {
38
42
  if (ext && !entry.endsWith(ext)) continue;
package/lib/aot-impl.js CHANGED
@@ -727,11 +727,28 @@ function loadBundle(Validator, mods, schemas, opts) {
727
727
  });
728
728
  }
729
729
 
730
+ // A module written here may end up inline in an HTML page, inside a <script>
731
+ // element, and the HTML parser ends that element at the first `</script` it
732
+ // sees, whatever JavaScript context it is in. A schema string carrying one, an
733
+ // enum value, a pattern, a property name, closed the element early and handed
734
+ // the rest of the module to the HTML parser. `<!--` changes how the parser
735
+ // reads the element too. Both are written with `\x3C` for the `<`, an escape
736
+ // that means the same character in a string, a template and a regular
737
+ // expression, with or without the u flag. An escaped `\<`, which the emitters
738
+ // do not write but a pattern could carry, has its backslash dropped, since
739
+ // `\x3C` already is the escape. A module with neither sequence, which is
740
+ // nearly every one, comes back as the same string.
741
+ function htmlSafe(src) {
742
+ if (typeof src !== 'string' || src.indexOf('<') === -1) return src;
743
+ return src.replace(/(\\*)<(\/script|!--)/gi, (m, bs, rest) => (bs.length % 2 ? bs.slice(0, -1) : bs) + '\\x3C' + rest);
744
+ }
745
+ const htmlSafeResult = (fn) => function () { return htmlSafe(fn.apply(this, arguments)); };
746
+
730
747
  module.exports = {
731
- toStandalone,
732
- toStandaloneModule,
733
- bundle,
734
- bundleStandalone,
735
- bundleCompact,
748
+ toStandalone: htmlSafeResult(toStandalone),
749
+ toStandaloneModule: htmlSafeResult(toStandaloneModule),
750
+ bundle: htmlSafeResult(bundle),
751
+ bundleStandalone: htmlSafeResult(bundleStandalone),
752
+ bundleCompact: htmlSafeResult(bundleCompact),
736
753
  loadBundle,
737
754
  };
package/lib/draft7.js CHANGED
@@ -166,6 +166,22 @@ function _normalizeNullable(schema) {
166
166
  }
167
167
  }
168
168
  if ('nullable' in schema) delete schema.nullable
169
+ // A JSON Pointer in a URI fragment is percent-encoded, so a definition named
170
+ // `Node<Row>` is reached by `#/definitions/Node%3CRow%3E` (RFC 6901, section
171
+ // 6). The interpreter decodes the fragment; the code generator compared the
172
+ // encoded text with the definition names and declined the whole document, so
173
+ // one such reference sent every validator built from it to the interpreter and
174
+ // made `ata compile` refuse it. Rewritten here to the decoded pointer, every
175
+ // engine reads the same text. A decoded form still holding a `%` is left
176
+ // alone: the engines decode once more, and `100%` must not be decoded twice.
177
+ // This walk is the one every normalization runs, so it costs no second pass;
178
+ // a reference it does not reach keeps the old behaviour, the interpreter.
179
+ if (/^#.*%/.test(schema.$ref)) {
180
+ try {
181
+ const decoded = decodeURIComponent(schema.$ref)
182
+ if (!decoded.includes('%')) schema.$ref = decoded
183
+ } catch {}
184
+ }
169
185
 
170
186
  const objSubs = ['properties', 'patternProperties', '$defs', 'definitions', 'dependentSchemas']
171
187
  for (const key of objSubs) {
@@ -1258,6 +1258,6 @@ module.exports = { createInterpreter };
1258
1258
  // For lib/plan-source.js, which emits source from the same plans and must call
1259
1259
  // the same helpers so the two cannot answer differently.
1260
1260
  Object.defineProperty(module.exports, '_planInternals', {
1261
- value: { Plan, deepEqual, cpAtLeast, cpAtMost, multipleOfOk, dataBits, T_STRING, T_NUMBER, T_INTEGER, T_BOOLEAN, T_NULL, T_OBJECT, T_ARRAY, T_ANY },
1261
+ value: { Plan, deepEqual, cpAtLeast, cpAtMost, multipleOfOk, dataBits, typeBit, T_STRING, T_NUMBER, T_INTEGER, T_BOOLEAN, T_NULL, T_OBJECT, T_ARRAY, T_ANY },
1262
1262
  enumerable: false,
1263
1263
  });
@@ -2093,7 +2093,14 @@ function compileToJSCodegen(schema, schemaMap, userFormats, opts) {
2093
2093
  if (hasUnresolvableRef(schema, rootDefs, anchors, schemaMap, new Set())) return null
2094
2094
  if (needsBaseTracking(schema, schemaMap, new Set())) return null
2095
2095
 
2096
- const 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 }
2096
+ const 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,
2097
+ keywords: (opts && opts.keywords) || null, kwEmitted: null }
2098
+ if (ctx.keywords) {
2099
+ // Macro keywords expand into a schema of their own, which this generator
2100
+ // does not apply; such a schema stays on the interpreted engine.
2101
+ if (usesMacroKeyword(schema, schemaMap, ctx.keywords)) return null
2102
+ ctx.kwEmitted = new Set()
2103
+ }
2097
2104
  const lines = []
2098
2105
  try {
2099
2106
  genCode(schema, 'd', lines, ctx)
@@ -2101,6 +2108,16 @@ function compileToJSCodegen(schema, schemaMap, userFormats, opts) {
2101
2108
  if (e === DECLINE) return null
2102
2109
  throw e
2103
2110
  }
2111
+ // Every schema node that carries a custom keyword must have had its check
2112
+ // written. A path that inlined a node without going through genCodeNode
2113
+ // would drop the keyword and accept what it should reject, so a node that
2114
+ // was not reached declines the whole schema to the interpreted engine
2115
+ // instead. Unreferenced definitions decline too: slower, never wrong.
2116
+ if (ctx.keywords) {
2117
+ for (const node of keywordNodes(schema, schemaMap, ctx.keywords)) {
2118
+ if (!ctx.kwEmitted.has(node)) return null
2119
+ }
2120
+ }
2104
2121
 
2105
2122
  // Append deferred checks (additionalProperties, unevaluatedProperties) at the end
2106
2123
  if (ctx.deferredChecks) {
@@ -2449,6 +2466,115 @@ function _deferOrInline(ctx, lines, v, check) {
2449
2466
  // 'object' = we know v is a non-null non-array object
2450
2467
  // 'array' = we know v is an array
2451
2468
  // 'string' / 'number' / 'integer' = we know the primitive type
2469
+ // Custom keywords (the `keywords` option, normalized by lib/keywords.js) as
2470
+ // calls into the caller's functions, threaded in as closure variables the way
2471
+ // user formats are. Each check is built here with the interpreter's own
2472
+ // semantics, so the two engines cannot disagree on one: the keyword is skipped
2473
+ // for data outside its declared types (the interpreter's type bits), the
2474
+ // compile form is built once per schema node, and any errors the function
2475
+ // leaves on itself are cleared after the call, as runCustomV does. Written
2476
+ // first in the node, before anything that could return early.
2477
+ // The interpreter's type bits (lib/interpreter.js typeBit and dataBits),
2478
+ // copied rather than required: loading the interpreter added about a
2479
+ // millisecond to the first call of a schema that never needs it.
2480
+ // tests/test_custom_keyword_codegen.js holds the copies equal to the originals.
2481
+ const KW_T_ANY = 127
2482
+ function kwTypeBit (name) {
2483
+ switch (name) {
2484
+ case 'string': return 1
2485
+ case 'number': return 2
2486
+ case 'integer': return 4
2487
+ case 'boolean': return 8
2488
+ case 'null': return 16
2489
+ case 'object': return 32
2490
+ case 'array': return 64
2491
+ default: return KW_T_ANY
2492
+ }
2493
+ }
2494
+ function kwDataBits (d) {
2495
+ switch (typeof d) {
2496
+ case 'string': return 1
2497
+ case 'number':
2498
+ if (!isFinite(d)) return 0
2499
+ return Number.isInteger(d) ? 2 | 4 : 2
2500
+ case 'boolean': return 8
2501
+ case 'object':
2502
+ if (d === null) return 16
2503
+ return Array.isArray(d) ? 64 : 32
2504
+ default: return 0
2505
+ }
2506
+ }
2507
+ function genCustomKeywords (schema, v, lines, ctx) {
2508
+ for (const key of Object.keys(schema)) {
2509
+ const def = ctx.keywords[key]
2510
+ if (def === undefined || def.macro !== null) continue
2511
+ const value = schema[key]
2512
+ const dataBits = kwDataBits
2513
+ const T_ANY = KW_T_ANY
2514
+ let mask = T_ANY
2515
+ if (def.types !== null) {
2516
+ mask = 0
2517
+ for (const t of def.types) mask |= kwTypeBit(t)
2518
+ }
2519
+ let fn
2520
+ if (def.compile !== null) {
2521
+ fn = def.compile(value, schema)
2522
+ if (typeof fn !== 'function') throw new Error(`keyword "${key}": compile must return a function`)
2523
+ } else {
2524
+ const validate = def.validate
2525
+ fn = (data) => validate(value, data, schema)
2526
+ fn.source = validate
2527
+ }
2528
+ const holder = () => fn.source || fn
2529
+ const check = mask === T_ANY
2530
+ ? (data) => { const ok = fn(data); const h = holder(); if (h.errors) h.errors = null; return !!ok }
2531
+ : (data) => {
2532
+ if ((dataBits(data) & mask) === 0) return true
2533
+ const ok = fn(data); const h = holder(); if (h.errors) h.errors = null; return !!ok
2534
+ }
2535
+ const name = '_kwc' + ctx.closureVars.length
2536
+ ctx.closureVars.push(name)
2537
+ ctx.closureVals.push(check)
2538
+ lines.push(`if(!${name}(${v}))return false`)
2539
+ }
2540
+ ctx.kwEmitted.add(schema)
2541
+ }
2542
+
2543
+ // Map keys under these are names, not keywords (lib/keywords.js NAME_MAPS).
2544
+ const KW_NAME_MAPS = new Set([
2545
+ 'properties', 'patternProperties', '$defs', 'definitions',
2546
+ 'dependentSchemas', 'dependentRequired', 'dependencies', 'propertyDependencies',
2547
+ ])
2548
+ function forEachKeywordNode (schema, schemaMap, keywords, visit) {
2549
+ const seen = new Set()
2550
+ const walk = (node, keysAreNames) => {
2551
+ if (node === null || typeof node !== 'object' || seen.has(node)) return
2552
+ seen.add(node)
2553
+ if (Array.isArray(node)) { for (const item of node) walk(item, false); return }
2554
+ let has = false
2555
+ for (const key of Object.keys(node)) {
2556
+ if (!keysAreNames && keywords[key] !== undefined) has = true
2557
+ const val = node[key]
2558
+ if (val !== null && typeof val === 'object') walk(val, !keysAreNames && KW_NAME_MAPS.has(key))
2559
+ }
2560
+ if (has) visit(node)
2561
+ }
2562
+ walk(schema, false)
2563
+ if (schemaMap) for (const s of schemaMap.values()) walk(s, false)
2564
+ }
2565
+ function keywordNodes (schema, schemaMap, keywords) {
2566
+ const out = []
2567
+ forEachKeywordNode(schema, schemaMap, keywords, (n) => out.push(n))
2568
+ return out
2569
+ }
2570
+ function usesMacroKeyword (schema, schemaMap, keywords) {
2571
+ let macro = false
2572
+ forEachKeywordNode(schema, schemaMap, keywords, (n) => {
2573
+ for (const key of Object.keys(n)) if (keywords[key] !== undefined && keywords[key].macro !== null) macro = true
2574
+ })
2575
+ return macro
2576
+ }
2577
+
2452
2578
  function genCode(schema, v, lines, ctx, knownType) {
2453
2579
  return withPlain(schema, v, lines, ctx, () => genCodeNode(schema, v, lines, ctx, knownType))
2454
2580
  }
@@ -2459,6 +2585,7 @@ function genCodeNode(schema, v, lines, ctx, knownType) {
2459
2585
  if (schema === false) { lines.push('return false'); return }
2460
2586
  if (schema === true) return
2461
2587
  if (typeof schema !== 'object' || schema === null) return
2588
+ if (ctx.keywords) genCustomKeywords(schema, v, lines, ctx)
2462
2589
  if (!ctx.regExpMap) {
2463
2590
  ctx.regExpMap = new Map();
2464
2591
  }
@@ -6162,3 +6289,5 @@ function _collectEval(schema, result, defs, schemaMap, refStack, isRoot) {
6162
6289
  }
6163
6290
 
6164
6291
  module.exports = { compileToJS, compileToJSCodegen, compileToJSCodegenWithErrors, compileToJSCombined, collectEvaluated, unevalContributions, AJV_MESSAGES, _setCodeBudget }
6292
+ // For tests/test_custom_keyword_codegen.js, which holds these copies to the interpreter's.
6293
+ Object.defineProperty(module.exports, '_kwTypeBits', { value: { kwTypeBit, kwDataBits, KW_T_ANY }, enumerable: false })
@@ -28,6 +28,7 @@ const DEFINITIONS = 1 << 3; // `definitions`, renamed to `$defs`
28
28
  const DEPENDENCIES = 1 << 4; // `dependencies`, split into two keywords
29
29
  const TUPLE_ITEMS = 1 << 5; // array-valued `items`, split into `prefixItems`
30
30
  const EXCLUSIVE_BOOL = 1 << 6; // draft-04's boolean `exclusiveMinimum`/`exclusiveMaximum`
31
+ const ENCODED_REF = 1 << 7; // a fragment `$ref` holding a percent escape, decoded by normalizeNullable's walk
31
32
 
32
33
  // Everything only draft-07 normalization acts on. A schema of another dialect
33
34
  // can carry these without them meaning anything, so they are read together
@@ -84,6 +85,7 @@ function scan(schema) {
84
85
  // to contribute here, and walking it is the slower path in V8.
85
86
  const keys = Object.keys(node);
86
87
  if (typeof node.$ref === 'string') {
88
+ if (node.$ref[0] === '#' && node.$ref.includes('%')) bits |= ENCODED_REF;
87
89
  for (let i = 0; i < keys.length; i++) {
88
90
  if (!REF_SIBLINGS_KEPT.has(keys[i])) { bits |= REF_SIBLINGS; break; }
89
91
  }
@@ -102,11 +104,11 @@ function scan(schema) {
102
104
  // rules apply at all, which the caller already knows.
103
105
  function needsNormalization(schema, isDraft7) {
104
106
  const bits = scan(schema);
105
- if (bits & NULLABLE) return true;
106
107
  // The boolean form of the exclusive bounds is draft-04's, and it is
107
108
  // unambiguous wherever it appears: no later dialect gives a boolean any
108
- // meaning there. Normalizing it is not gated on the declared dialect.
109
- if (bits & EXCLUSIVE_BOOL) return true;
109
+ // meaning there. Percent-encoded fragments mean the same in every dialect
110
+ // too. Neither is gated on the declared dialect.
111
+ if (bits & (NULLABLE | EXCLUSIVE_BOOL | ENCODED_REF)) return true;
110
112
  return isDraft7 ? (bits & DRAFT7_WORK) !== 0 : false;
111
113
  }
112
114
 
@@ -120,5 +122,6 @@ module.exports = {
120
122
  DEPENDENCIES,
121
123
  TUPLE_ITEMS,
122
124
  EXCLUSIVE_BOOL,
125
+ ENCODED_REF,
123
126
  DRAFT7_WORK,
124
127
  };
@@ -609,7 +609,17 @@ function _materializeSchema(self) {
609
609
  if (options.assertFormat === false) {
610
610
  schemaObj = stripFormatAssertions(isCallers ? _deepCloneWithSymbols(schemaObj) : schemaObj);
611
611
  }
612
- const usesKeywords = self._keywords !== null && schemaUsesKeywords(schemaObj, self._keywords);
612
+ // A keyword used only in a schema reached through $ref counts too: the
613
+ // interpreted engine applies it there, and every path that does not know the
614
+ // keyword has to stand aside for it. Looking at the root alone, a keyword in
615
+ // a referenced schema was skipped by every compiled path and accepted
616
+ // whatever it should have rejected.
617
+ let usesKeywords = self._keywords !== null && schemaUsesKeywords(schemaObj, self._keywords);
618
+ if (!usesKeywords && self._keywords !== null && self._schemaMap && self._schemaMap.size > 0) {
619
+ for (const ext of self._schemaMap.values()) {
620
+ if (schemaUsesKeywords(ext, self._keywords)) { usesKeywords = true; break; }
621
+ }
622
+ }
613
623
  Object.defineProperty(self, '_schemaObj', { value: schemaObj, writable: true, configurable: true, enumerable: true });
614
624
  Object.defineProperty(self, '_usesKeywords', { value: usesKeywords, writable: true, configurable: true, enumerable: true });
615
625
  Object.defineProperty(self, '_schemaIsCallers', { value: isCallers && schemaObj === raw, writable: true, configurable: true, enumerable: true });
@@ -849,7 +859,7 @@ class Validator {
849
859
  // interpreted engine is both eval-free and more correct, so go straight
850
860
  // there. The forced case is decided before the probe: the probe is a
851
861
  // `new Function` too, and a validator that promised none must not run it.
852
- if (_forceNapi || this._v1Dynamic || this._usesKeywords || !codegenAvailable()) {
862
+ if (_forceNapi || this._v1Dynamic || !codegenAvailable()) {
853
863
  jsFn = null; jsCombinedFn = null; jsErrFn = null;
854
864
  // `full` separates an entry that holds every compiled function from one
855
865
  // the verdict-only fast path seeded, where `combined` and `errFn` are null
@@ -871,8 +881,17 @@ class Validator {
871
881
  this._engine = _isCodegen ? 'codegen' : jsFn ? 'closure' : null;
872
882
  } else {
873
883
  const uf = this._userFormats;
874
- const _cgFn = compileToJSCodegen(schemaObj, sm, uf);
875
- jsFn = _cgFn || compileToJS(schemaObj, null, sm);
884
+ // Custom keywords compile into the verdict function as calls to the
885
+ // caller's functions. Only the verdict: the error and combined
886
+ // generators, the closure compiler and the text scanner know nothing of
887
+ // them and would skip the keyword, accepting what it rejects, so for a
888
+ // schema that uses one they are not built, and errors come from the
889
+ // interpreted engine, which applies the keywords. Where the generator
890
+ // declines the schema, so does everything else, and the interpreted
891
+ // engine answers alone, as it always did.
892
+ const kw = this._keywords ? { keywords: this._keywords } : undefined;
893
+ const _cgFn = compileToJSCodegen(schemaObj, sm, uf, kw);
894
+ jsFn = _cgFn || (this._usesKeywords ? null : compileToJS(schemaObj, null, sm));
876
895
  // Only the verdict is compiled here. The error and combined generators
877
896
  // are the other two thirds of a cold first call (8.2, 10.4 and 7.8 ms on
878
897
  // a 120-property config schema, most of it V8 compiling each generator
@@ -882,7 +901,9 @@ class Validator {
882
901
  jsErrFn = undefined;
883
902
  _isCodegen = !!_cgFn;
884
903
  this._engine = _cgFn ? 'codegen' : jsFn ? 'closure' : null;
885
- if (!uf) {
904
+ // A function bound to this validator's keyword functions or formats is
905
+ // not the schema's alone, so it is not shared through the cache.
906
+ if (!uf && !this._keywords) {
886
907
  _compileCache.set(mapKey, { jsFn, combined: undefined, errFn: undefined, isCodegen: _isCodegen, full: false });
887
908
  }
888
909
  }
@@ -991,12 +1012,12 @@ class Validator {
991
1012
  };
992
1013
  const _buildCombined = () => {
993
1014
  if (jsCombinedFn !== undefined) return;
994
- jsCombinedFn = compileToJSCombined(schemaObj, VALID_RESULT, sm, this._userFormats, { runtimeShape: true }) || null;
1015
+ jsCombinedFn = this._usesKeywords ? null : (compileToJSCombined(schemaObj, VALID_RESULT, sm, this._userFormats, { runtimeShape: true }) || null);
995
1016
  _upgradeCacheEntry();
996
1017
  };
997
1018
  const _buildErr = () => {
998
1019
  if (jsErrFn !== undefined) return;
999
- jsErrFn = compileToJSCodegenWithErrors(schemaObj, sm, this._userFormats, { runtimeShape: true }) || null;
1020
+ jsErrFn = this._usesKeywords ? null : (compileToJSCodegenWithErrors(schemaObj, sm, this._userFormats, { runtimeShape: true }) || null);
1000
1021
  _upgradeCacheEntry();
1001
1022
  };
1002
1023
 
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.40.1';
10
+ module.exports = '1.41.0';
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "ata-validator",
3
- "version": "1.40.1",
3
+ "version": "1.41.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_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_cross_doc_root_ref.js && node tests/test_codegen_entrypoint_agreement.js && node tests/test_schema_code_injection.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_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_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_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",
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.40.1",
147
- "@ata-validator/native-darwin-x64": "1.40.1",
148
- "@ata-validator/native-linux-arm64-gnu": "1.40.1",
149
- "@ata-validator/native-linux-arm64-musl": "1.40.1",
150
- "@ata-validator/native-linux-x64-gnu": "1.40.1",
151
- "@ata-validator/native-linux-x64-musl": "1.40.1",
152
- "@ata-validator/native-win32-x64": "1.40.1"
146
+ "@ata-validator/native-darwin-arm64": "1.41.0",
147
+ "@ata-validator/native-darwin-x64": "1.41.0",
148
+ "@ata-validator/native-linux-arm64-gnu": "1.41.0",
149
+ "@ata-validator/native-linux-arm64-musl": "1.41.0",
150
+ "@ata-validator/native-linux-x64-gnu": "1.41.0",
151
+ "@ata-validator/native-linux-x64-musl": "1.41.0",
152
+ "@ata-validator/native-win32-x64": "1.41.0"
153
153
  },
154
154
  "peerDependencies": {
155
155
  "yaml": "^2.0.0"