ata-validator 1.32.1 → 1.32.2

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
@@ -53,15 +53,24 @@ The `.compiled.mjs` modules are self-contained: zero runtime dependency on ata-v
53
53
 
54
54
  ## Measured by others
55
55
 
56
- Two public harnesses run ata without ata's involvement. Quote these before anything in this
56
+ Public harnesses run ata without ata's involvement. Quote these before anything in this
57
57
  file.
58
58
 
59
+ - The [runtime type benchmark](https://moltar.github.io/typescript-runtime-type-benchmarks/)
60
+ maintained by moltar, 44 to 56 libraries per test. In its official run on Node 24, with ata
61
+ 1.32.0, ata is first of 45 on assertStrict at 37.2M operations a second, and its
62
+ ahead-of-time entry is first of 44 on parseStrict at 36.3M, with the runtime entry second.
63
+ On assertLoose ata is fifth of 56 and on parseSafe sixth of 45. Some of the entries ahead
64
+ of it on those two rows skip checks JSON Schema requires there, such as rejecting an array
65
+ where an object is declared or NaN where a number is. On Deno the same run puts ata between
66
+ 12th and 23rd; 1.32.1 makes it about 2.5 times faster there, locally, and had not been
67
+ through the harness yet at the time of writing.
59
68
  - [schemabenchmarks.dev](https://schemabenchmarks.dev), a benchmark of runtime validation
60
- libraries maintained outside this project. On its validation page, valid data, the run of
61
- 2026-09-13 against ata 1.14.1 puts ata first at 603 ns, with the next entry at 1.77 times
62
- that. The same site puts ata last on the download page, at 64.9 KB gzipped, because the entry
63
- it bundles is the runtime compiler; the module `ata build` emits for that schema is 5.1 KB
64
- minified and gzipped, and a compiled entry for the harness is in preparation.
69
+ libraries. Its published run of 2026-09-25 still uses ata 1.29.0 and puts it sixth of 30 on
70
+ valid data at 870 ns and tenth on invalid data at 67 ns. Most of that time was the `uri`
71
+ format check and a wrapper in the keywords package, both rewritten since. Run locally on an
72
+ Apple M4 Pro, the same harness takes ata from 365 ns on 1.29.0 to 240 ns on 1.32.1 for valid
73
+ data, with invalid data at 21 ns on both; those are local figures, not the site's.
65
74
  - [Bowtie](https://bowtie.report/), the cross-implementation JSON Schema test harness. ata's
66
75
  harness runs Draft 2020-12 and draft 7 there; on the harness at ata 1.16.1 the official suite
67
76
  passes with nothing failed, errored or skipped under Bowtie's own runner. The v1 dialect is
@@ -71,19 +80,20 @@ file.
71
80
 
72
81
  | Dimension | Schema | ata-AOT | runtime validator | Difference |
73
82
  |---|---|---|---|---|
74
- | Bundle (gzipped) | simple | 1.1 KB | 52.7 KB | 48.1x smaller |
75
- | Bundle (gzipped) | complex | 7.6 KB | 52.7 KB | 7.0x smaller |
76
- | Bundle (gzipped) | nested | 4.1 KB | 52.7 KB | 13.0x smaller |
77
- | Cold start | simple | 21 ms | 40 ms | 1.9x faster |
78
- | Throughput (1M ops) | simple | 257 Mops/s | 114 Mops/s | 2.3x faster |
79
- | Compile time | simple | 14 µs | 1.52 ms | 111x faster |
83
+ | Bundle (gzipped) | simple | 1.3 KB | 52.7 KB | 39.8x smaller |
84
+ | Bundle (gzipped) | complex | 7.8 KB | 52.7 KB | 6.7x smaller |
85
+ | Bundle (gzipped) | nested | 4.5 KB | 52.7 KB | 11.8x smaller |
86
+ | Cold start | simple | 22 ms | 48 ms | 2.2x faster |
87
+ | Throughput (1M ops) | simple | 307 Mops/s | 100 Mops/s | 3.1x faster |
88
+ | Compile time | simple | 17 µs | 1.62 ms | 93x faster |
80
89
 
81
90
  The runtime column is the default validator most frameworks ship. Reproduce on your machine
82
- with `npm run bench:aot-vs-ajv`. Numbers from one run on Apple M4 Pro, Node 25.2.1, 2026-09-17,
83
- on ata-validator 1.25.0, whose emitted modules carry full error detail and a schema hash, which
84
- is where the growth over earlier 1.x module sizes comes from. Across three runs throughput moved
85
- between 257 and 297 Mops/s and the compile ratio between 104x and 112x, so treat the last two
86
- rows as an order of magnitude rather than a constant.
91
+ with `npm run bench:aot-vs-ajv`. Numbers from one run on Apple M4 Pro, Node 25.2.1, 2026-09-27,
92
+ on ata-validator 1.32.1. Emitted modules are about 0.2 to 0.4 KB larger gzipped than on 1.25.0,
93
+ which is what the exact own-property checks added in 1.31.2 cost. Across three runs throughput
94
+ moved between 296 and 307 Mops/s against 99 to 107, cold start between 21 and 22 ms against 41
95
+ to 48, and the compile ratio between 87x and 95x, so treat the last three rows as an order of
96
+ magnitude rather than a constant.
87
97
 
88
98
  The wins are largest on bundle size and compile time because AOT moves work from runtime to
89
99
  build time. Throughput and cold start are also faster because the compiled validator is a
@@ -211,19 +221,20 @@ is the most common way to get a misleading number out of this library.
211
221
 
212
222
  | | compiled with `ata build` | runtime `new Validator(schema)` |
213
223
  |---|---|---|
214
- | In a bundle, gzipped | **4.5 KB** | 87.0 KB |
215
- | Time to a served request | **3.4 ms** | 10.6 ms |
224
+ | In a bundle, gzipped | **2.3 KB** | 91.4 KB |
225
+ | Time to a served request | **3.4 ms** | 11.2 ms |
216
226
  | Schema known when | build time | any time |
217
227
 
218
- The bundle row is a ten-field user schema built with
219
- `bun build --minify --target=browser`. The startup row is a Hono route on Bun 1.4, best
220
- of seven, against 3.7 ms for the same app doing no validation at all, so the compiled
221
- path costs nothing measurable to start. The runtime
228
+ The bundle row is the ten-field user schema in `tests/fixtures/error-dx/user.schema.json`,
229
+ every export of the compiled module against `new Validator(schema)`, built with
230
+ `bun build --minify --target=browser` on ata 1.32.1. The startup row is a Hono route on
231
+ Bun 1.4, best of seven, from `benchmark/bundle`, against 3.5 ms for the same app doing no
232
+ validation at all, so the compiled path costs nothing measurable to start. The runtime
222
233
  figure is what it is because a schema that arrives at run time can use any keyword, so
223
234
  the whole engine has to be there. The compiled module imports nothing and contains only
224
235
  the checks your schema asks for.
225
236
 
226
- **On a server, use whichever fits your schemas.** 87 KB of JavaScript on a Node or Bun
237
+ **On a server, use whichever fits your schemas.** 91 KB of JavaScript on a Node or Bun
227
238
  process is not a cost anyone notices, and the runtime API is the simpler thing to reach
228
239
  for. Speed is the same either way once warm.
229
240
 
@@ -262,7 +273,7 @@ const v = new Validator({
262
273
  required: ['name', 'email']
263
274
  });
264
275
 
265
- // Fast boolean check - JS codegen, 15.3M ops/sec
276
+ // Fast boolean check, compiled to JS
266
277
  v.isValidObject({ name: 'Mert', email: 'mert@example.com', age: 26 }); // true
267
278
 
268
279
  // Full validation with error details + defaults applied
@@ -279,7 +290,7 @@ v.isValid(Buffer.from('{"name": "Mert", "email": "mert@example.com"}'));
279
290
  // Which engine answers this schema: 'codegen', 'closure', 'native' or 'interpreter'
280
291
  v.engine(); // 'codegen'
281
292
 
282
- // Parallel batch - multi-core, NDJSON, 13.4M items/sec
293
+ // Parallel batch, multi-core, NDJSON
283
294
  const ndjson = Buffer.from(lines.join('\n'));
284
295
  v.isValidParallel(ndjson); // bool[]
285
296
  v.countValid(ndjson); // number
@@ -455,7 +466,7 @@ const v = new Validator(schema, {
455
466
 
456
467
  ### Build-time compile (`ata compile`)
457
468
 
458
- 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. Typical output is about 4.5 KB gzipped for a ten-field schema, full error detail included, against 87 KB for the runtime bundled for the browser.
469
+ 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.6 KB gzipped, full error detail included, against 91 KB for the runtime bundled for the browser.
459
470
 
460
471
  ```bash
461
472
  npx ata compile schemas/user.json -o src/generated/user.validator.mjs
@@ -496,14 +507,14 @@ npx ata build 'schemas/*.json' --out-dir build/validators --check
496
507
 
497
508
  Run with `--watch` during development for incremental rebuilds.
498
509
 
499
- Bundle sizes for a 10-field user schema, minified and gzipped, measured with
500
- `bun build --minify --target=browser`:
510
+ Bundle sizes for the 10-field user schema in `tests/fixtures/error-dx/user.schema.json`,
511
+ minified and gzipped, measured with `bun build --minify --target=browser` on ata 1.32.1:
501
512
 
502
513
  | What the app imports | Size | Notes |
503
514
  |---|---|---|
504
- | `Validator` from `ata-validator` | 74.1 KB | The compiler ships with it, because a runtime schema can use any keyword |
505
- | `isValid` from the compiled module | **1.9 KB** | Nothing else is reachable, so the error collector is dropped |
506
- | `validate` from the compiled module | **3.6 KB** | Adds the detailed error collector |
515
+ | `Validator` from `ata-validator` | 91.3 KB | The compiler ships with it, because a runtime schema can use any keyword |
516
+ | `isValid` from the compiled module | **1.2 KB** | Nothing else is reachable, so the error collector is dropped |
517
+ | `validate` from the compiled module | **2.1 KB** | Adds the detailed error collector |
507
518
 
508
519
  `--abort-early` makes the generated source about three times smaller, and after
509
520
  bundling it makes no difference: importing only `isValid` already leaves the error
@@ -526,7 +537,7 @@ through a `setFormats()` export the module carries. `docs/API.md` has the
526
537
  details.
527
538
 
528
539
  **Fastify startup, 10 route schemas, from a cold process to the first validated request:
529
- ajv 19.6 ms, ata 3.1 ms, no build step required.** ata registers in 1.1 ms of that and
540
+ ajv 20.2 ms, ata 3.9 ms, no build step required.** ata registers in 0.2 ms of that and
530
541
  compiles on the first request, so counting only registration would overstate the gap.
531
542
  Reproduce with `node benchmark/bench_fastify_boot.mjs`.
532
543
 
package/index.js CHANGED
@@ -371,6 +371,9 @@ const _identityCache = new WeakMap();
371
371
 
372
372
  const SIMDJSON_PADDING = 64;
373
373
  const VALID_RESULT = Object.freeze({ valid: true, errors: Object.freeze([]) });
374
+ // How many calls a validator answers through its verdict function before its
375
+ // validate() and validateJSON() compile the single-function hybrid.
376
+ const HYBRID_TIER_CALLS = 64;
374
377
  const ABORT_EARLY_RESULT = Object.freeze({
375
378
  valid: false,
376
379
  errors: Object.freeze([Object.freeze({
@@ -1543,8 +1546,20 @@ class Validator {
1543
1546
  if (combined) { impl = combined; return _mustReject(combined(data)); }
1544
1547
  return errOnly(data);
1545
1548
  };
1546
- const hybridFn = jsFn._hybridFactory(VALID_RESULT, onReject);
1547
- impl = hybridFn;
1549
+ // Tiered: the first calls go through the verdict function and the
1550
+ // resolver, and the hybrid is compiled once the validator is in use.
1551
+ // Compiling it up front was a second parse of the whole schema on
1552
+ // every validator, most of which answer a handful of requests or only
1553
+ // verdicts. A refused compile stays on the first tier.
1554
+ let warm = 0;
1555
+ const first = (data) => {
1556
+ if (++warm >= HYBRID_TIER_CALLS && impl === first) {
1557
+ impl = jsFn._hybridFactory(VALID_RESULT, onReject) || ((d) => (jsFn(d) ? VALID_RESULT : onReject(d)));
1558
+ return impl(data);
1559
+ }
1560
+ return jsFn(data) ? VALID_RESULT : onReject(data);
1561
+ };
1562
+ impl = first;
1548
1563
  const run = (data) => impl(data);
1549
1564
  this.validate = preprocess
1550
1565
  ? (data) => { preprocess(data); return run(data); }
@@ -1613,11 +1628,18 @@ class Validator {
1613
1628
  // it validates and collects in one pass, and the error generator behind
1614
1629
  // it. `errPreferCombined` is that order, resolved on the first rejection
1615
1630
  // instead of at compile time.
1616
- const hybridFn = jsFn._hybridFactory
1617
- ? jsFn._hybridFactory(VALID_RESULT, errPreferCombined)
1618
- : null;
1619
- const jsonValidateInner = hybridFn
1620
- || ((obj) => (jsFn(obj) ? VALID_RESULT : errPreferCombined(obj)));
1631
+ // Tiered the same way as validate() above.
1632
+ let jsonHybrid = null;
1633
+ let jsonWarm = 0;
1634
+ const jsonValidateInner = (obj) => {
1635
+ if (jsonHybrid !== null) return jsonHybrid(obj);
1636
+ if (++jsonWarm >= HYBRID_TIER_CALLS) {
1637
+ jsonHybrid = (jsFn._hybridFactory && jsFn._hybridFactory(VALID_RESULT, errPreferCombined))
1638
+ || ((o) => (jsFn(o) ? VALID_RESULT : errPreferCombined(o)));
1639
+ return jsonHybrid(obj);
1640
+ }
1641
+ return jsFn(obj) ? VALID_RESULT : errPreferCombined(obj);
1642
+ };
1621
1643
  // Parsed text takes the same preprocess pass as a parsed object, so
1622
1644
  // validate(obj) and validateJSON(text) answer the same for the same
1623
1645
  // document. Without it, coercion, removal and defaults applied on one
@@ -924,10 +924,22 @@ const PROTO_NAMES = new Set([...Object.getOwnPropertyNames(Object.prototype), '_
924
924
  // hasOwnProperty path, and a ten-field check went from 2.5 to 40 ns. Where the
925
925
  // accessor is missing the prototype comes from Object.getPrototypeOf instead,
926
926
  // chosen by a constant the engine folds, so Node pays nothing for the branch.
927
- const OWN_HELPERS = 'const _PN=({}).__proto__===Object.prototype;' +
928
- 'const _hop=Object.prototype.hasOwnProperty;function _h(o,k){return _hop.call(o,k)}' +
929
- 'function _ok(o,k){return (_PN?o.__proto__:Object.getPrototypeOf(o))===Object.prototype||_hop.call(o,k)}' +
930
- 'function _hall(o,ks){for(let i=0;i<ks.length;i++)if(!_hop.call(o,ks[i]))return false;return true}'
927
+ // Each helper is hoisted only where something emitted uses it, with what it
928
+ // depends on. Hoisting all of them as one block put five declarations into
929
+ // every module that asked about presence at all, about a sixth of a small
930
+ // schema's compiled module.
931
+ const OWN_HELPER_CODE = {
932
+ _PN: 'const _PN=({}).__proto__===Object.prototype;const _OP=Object.prototype;const _gp=Object.getPrototypeOf;',
933
+ _hop: 'const _hop=Object.prototype.hasOwnProperty;',
934
+ _h: 'function _h(o,k){return _hop.call(o,k)}',
935
+ _ok: 'function _ok(o,k){return (_PN?o.__proto__:_gp(o))===_OP||_hop.call(o,k)}',
936
+ _hall: 'function _hall(o,ks){for(let i=0;i<ks.length;i++)if(!_hop.call(o,ks[i]))return false;return true}',
937
+ }
938
+ const OWN_HELPER_DEPS = { _h: ['_hop'], _ok: ['_PN', '_hop'], _hall: ['_hop'] }
939
+ function useOwnHelper (ctx, name) {
940
+ for (const dep of OWN_HELPER_DEPS[name] || []) useOwnHelper(ctx, dep)
941
+ hoistOnce(ctx, '_own' + name, OWN_HELPER_CODE[name])
942
+ }
931
943
  //
932
944
  // Better still, the prototype is read once per object: a node that asks about
933
945
  // presence declares `_pkN`, whether its value is an ordinary object, before
@@ -936,24 +948,25 @@ const OWN_HELPERS = 'const _PN=({}).__proto__===Object.prototype;' +
936
948
  // object shape in the document, and cost the error path about a microsecond
937
949
  // on a product document.
938
950
  // Whether `v`, already known to be an object, has Object.prototype as its
939
- // prototype, read without depending on the __proto__ accessor. Needs OWN_HELPERS.
940
- function protoIs (v) {
941
- return `((_PN?${v}.__proto__:Object.getPrototypeOf(${v}))===Object.prototype)`
951
+ // prototype, read without depending on the __proto__ accessor.
952
+ function protoIs (ctx, v) {
953
+ useOwnHelper(ctx, '_PN')
954
+ return `((_PN?${v}.__proto__:_gp(${v}))===_OP)`
942
955
  }
943
956
  function ownKeyExpr (ctx, v, key) {
944
- hoistOnce(ctx, '_ownHoisted', OWN_HELPERS)
945
957
  const k = JSON.stringify(key)
946
- if (PROTO_NAMES.has(key)) return `_h(${v},${k})`
947
958
  const pk = ctx.plainOf ? ctx.plainOf.get(v) : undefined
959
+ useOwnHelper(ctx, PROTO_NAMES.has(key) || pk !== undefined ? '_h' : '_ok')
960
+ if (PROTO_NAMES.has(key)) return `_h(${v},${k})`
948
961
  return pk !== undefined ? `(${pk}?${k} in ${v}:_h(${v},${k}))` : `(${k} in ${v}&&_ok(${v},${k}))`
949
962
  }
950
963
  // For a value already read as `v[key]` and found not to be undefined: whether
951
964
  // it came from `v` itself. `in` is known to hold, so only the second half runs.
952
965
  function ownGuard (ctx, v, key) {
953
- hoistOnce(ctx, '_ownHoisted', OWN_HELPERS)
954
966
  const k = JSON.stringify(key)
955
- if (PROTO_NAMES.has(key)) return `_h(${v},${k})`
956
967
  const pk = ctx.plainOf ? ctx.plainOf.get(v) : undefined
968
+ useOwnHelper(ctx, PROTO_NAMES.has(key) || pk !== undefined ? '_h' : '_ok')
969
+ if (PROTO_NAMES.has(key)) return `_h(${v},${k})`
957
970
  return pk !== undefined ? `(${pk}||_h(${v},${k}))` : `_ok(${v},${k})`
958
971
  }
959
972
 
@@ -969,7 +982,7 @@ function withPlain (schema, v, lines, ctx, run) {
969
982
  if (!ctx.plainOf) ctx.plainOf = new Map()
970
983
  const name = '_pk' + ctx.varCounter++
971
984
  const at = lines.length
972
- const decl = `const ${name}=typeof ${v}==='object'&&${v}!==null&&${protoIs(v)}`
985
+ const decl = `const ${name}=typeof ${v}==='object'&&${v}!==null&&${protoIs(ctx, v)}`
973
986
  lines.push(decl)
974
987
  const prev = ctx.plainOf.get(v)
975
988
  ctx.plainOf.set(v, name)
@@ -1765,7 +1778,11 @@ function compileToJSCodegen(schema, schemaMap, userFormats, opts) {
1765
1778
  const defSets = ctx.defFns ? Array.from(ctx.defFns.values()) : []
1766
1779
  const needsGuard = ctx.usesRecursion || defSets.length > 0
1767
1780
 
1768
- let body, hybridBody, tailBody
1781
+ // The hybrid and tail forms are the same checks with different returns. They
1782
+ // are built when first asked for, not here: most validators never call
1783
+ // validateJSON or get extended, and rewriting and compiling the source twice
1784
+ // more on every schema was about a third of what a first call cost.
1785
+ let body, hybridThunk, tailThunk
1769
1786
  if (ctx.usesRecursion) {
1770
1787
  // Self-recursive: a named body reached through the cycle guard, so data
1771
1788
  // that points back at itself settles instead of exhausting the stack.
@@ -1774,23 +1791,25 @@ function compileToJSCodegen(schema, schemaMap, userFormats, opts) {
1774
1791
  const run = emitGuardedRun('return _validate(d)', '', defSets)
1775
1792
  body = `${decl}function _run(d){\n ${run}\n }\n return _run(d)`
1776
1793
  // Hybrid: keep the boolean answer, wrap only the outer call
1777
- hybridBody = `${decl}function _run(d){\n ${run}\n }\n return _run(d)?R:E(d)`
1778
- tailBody = `${decl}function _run(d){\n ${run}\n }\n return _run(d)&&__ataTail(d)`
1794
+ hybridThunk = () => `${decl}function _run(d){\n ${run}\n }\n return _run(d)?R:E(d)`
1795
+ tailThunk = () => `${decl}function _run(d){\n ${run}\n }\n return _run(d)&&__ataTail(d)`
1779
1796
  } else if (defSets.length > 0) {
1780
1797
  // No self-reference at the root, but a def on a cycle can still be
1781
1798
  // re-entered with the same value; the guarded functions need their state.
1782
1799
  const run = emitGuardedRun(`return _body(d)`, '', defSets)
1783
1800
  body = `function _body(d){\n ${checkStr}\n return true\n }\n function _run(d){\n ${run}\n }\n return _run(d)`
1784
- hybridBody = `function _body(d){\n ${checkStr}\n return true\n }\n function _run(d){\n ${run}\n }\n return _run(d)?R:E(d)`
1785
- tailBody = `function _body(d){\n ${checkStr}\n return true\n }\n function _run(d){\n ${run}\n }\n return _run(d)&&__ataTail(d)`
1801
+ hybridThunk = () => `function _body(d){\n ${checkStr}\n return true\n }\n function _run(d){\n ${run}\n }\n return _run(d)?R:E(d)`
1802
+ tailThunk = () => `function _body(d){\n ${checkStr}\n return true\n }\n function _run(d){\n ${run}\n }\n return _run(d)&&__ataTail(d)`
1786
1803
  } else {
1787
1804
  body = checkStr + '\n return true'
1788
- hybridBody = replaceTopLevel(checkStr + '\n return R')
1805
+ hybridThunk = () => replaceTopLevel(checkStr + '\n return R')
1789
1806
  // An early accept inside the checks has to reach the tail too, so every
1790
1807
  // top-level `return true` becomes the tail call, by the same rewrite the
1791
1808
  // hybrid above relies on and test_hybrid_agreement holds to the suite.
1792
- tailBody = replaceTopLevel(checkStr + '\n return true', 'return __ataTail(d)', 'return false')
1809
+ tailThunk = () => replaceTopLevel(checkStr + '\n return true', 'return __ataTail(d)', 'return false')
1793
1810
  }
1811
+ let hybridBodyMemo
1812
+ const hybridBody = () => (hybridBodyMemo === undefined ? (hybridBodyMemo = hybridThunk()) : hybridBodyMemo)
1794
1813
 
1795
1814
  const guardStr = needsGuard ? emitGuardState() : ''
1796
1815
  const preambleStr = guardStr +
@@ -1808,11 +1827,20 @@ function compileToJSCodegen(schema, schemaMap, userFormats, opts) {
1808
1827
  boolFn = new Function('d', body)
1809
1828
  }
1810
1829
 
1811
- // Build hybrid: same body, return R instead of true, return E(d) instead of false.
1812
- try {
1813
- const hybridFactory = new Function(...closureNames, 'R', 'E', `${preambleStr}return function(d){${hybridBody}}`)
1814
- boolFn._hybridFactory = (R, E) => hybridFactory(...closureValues, R, E)
1815
- } catch {}
1830
+ // The hybrid: same body, return R instead of true, return E(d) instead of
1831
+ // false. Compiled on first request; null when that compile is refused, and
1832
+ // the caller then assembles from the error functions instead.
1833
+ let hybridFactory
1834
+ boolFn._hybridFactory = (R, E) => {
1835
+ if (hybridFactory === undefined) {
1836
+ try {
1837
+ hybridFactory = new Function(...closureNames, 'R', 'E', `${preambleStr}return function(d){${hybridBody()}}`)
1838
+ } catch {
1839
+ hybridFactory = null
1840
+ }
1841
+ }
1842
+ return hybridFactory === null ? null : hybridFactory(...closureValues, R, E)
1843
+ }
1816
1844
 
1817
1845
  // The same function with one more check where it would return true, for a
1818
1846
  // wrapper that enforces something the schema does not carry (see
@@ -1826,7 +1854,7 @@ function compileToJSCodegen(schema, schemaMap, userFormats, opts) {
1826
1854
  boolFn._withTail = (tail) => {
1827
1855
  if (tailFactory === undefined) {
1828
1856
  try {
1829
- tailFactory = new Function(...closureNames, '__ataTail', `${preambleStr}return function(d){${tailBody}}`)
1857
+ tailFactory = new Function(...closureNames, '__ataTail', `${preambleStr}return function(d){${tailThunk()}}`)
1830
1858
  } catch {
1831
1859
  tailFactory = null
1832
1860
  }
@@ -1847,7 +1875,11 @@ function compileToJSCodegen(schema, schemaMap, userFormats, opts) {
1847
1875
  boolFn._preambleGuard = guardStr
1848
1876
  boolFn._preambleParts = ctx.preamble ? ctx.preamble.slice() : []
1849
1877
  boolFn._sharedHelpers = ctx.shared ? ctx.shared.slice() : []
1850
- boolFn._hybridSource = helperStr + hybridBody
1878
+ Object.defineProperty(boolFn, '_hybridSource', {
1879
+ get: () => helperStr + hybridBody(),
1880
+ configurable: true,
1881
+ enumerable: false,
1882
+ })
1851
1883
  boolFn._usesSafeRe = !!ctx.usesSafeRe
1852
1884
  // Custom-format closure entries that the bundle output needs to recreate.
1853
1885
  // Stored as { name, fn } so consumers can serialize via Function#toString.
@@ -2280,11 +2312,11 @@ function genCodeNode(schema, v, lines, ctx, knownType) {
2280
2312
  }
2281
2313
  // On an object with another prototype the typed ones may be inherited.
2282
2314
  if (typed.length > 0) {
2283
- hoistOnce(ctx, '_ownHoisted', OWN_HELPERS)
2315
+ useOwnHelper(ctx, '_hall')
2284
2316
  // Read directly: the object type is already established here, and a
2285
2317
  // flag declared for this alone costs more than it saves. The list is
2286
2318
  // built only on that path, which an ordinary object never takes.
2287
- lines.push(`if(!${protoIs(v)}&&!_hall(${v},${JSON.stringify(typed)}))return false`)
2319
+ lines.push(`if(!${protoIs(ctx, v)}&&!_hall(${v},${JSON.stringify(typed)}))return false`)
2288
2320
  }
2289
2321
  } else if (schema.required && schema.required.length > 0) {
2290
2322
  const checks = schema.required.map(key => `!${ownKeyExpr(ctx, v, key)}`)
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.32.1';
10
+ module.exports = '1.32.2';
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "ata-validator",
3
- "version": "1.32.1",
3
+ "version": "1.32.2",
4
4
  "description": "JSON Schema validation that compiles for speed and still runs where code generation is blocked. Compiled and interpreted engines answer identically at 100% of the official suite. TypeScript inference, Standard Schema V1, and a build step that emits dependency-free modules.",
5
5
  "main": "index.js",
6
6
  "module": "index.mjs",
@@ -51,7 +51,7 @@
51
51
  "release:check": "node scripts/regen-safe-regex-source.js && node tests/test_pack_purity.js && node scripts/check-doc-coverage.js && node tests/test_error_codes_lock.js && node tests/test_safe_regex_source_sync.js && node tests/test_version_sync.js",
52
52
  "build": "cmake-js build --target ata",
53
53
  "rebuild": "cmake-js rebuild --target ata",
54
- "test": "node test.js && node tests/test_removed_aot_methods.js && node tests/test_no_native.js && node tests/test_no_eval.js && node tests/test_property_dependencies.js && node tests/test_v1_dialect.js && node tests/test_buffer_path_parity.js && node tests/test_buffer_gate.js && node tests/test_scanner_differential.js && node tests/test_validate_json_scanner_cost.js && node tests/test_buffer_reject_cost.js && node tests/test_nan_verdict.js && node tests/test_remove_additional_nested.js && node tests/test_exclusive_bounds.js && node tests/test_engine_differential.js && node tests/test_error_shape_differential.js && node tests/test_output_format.js && node tests/test_retry_message.js && node tests/test_describe_schema.js && node tests/test_aot_parse.js && node tests/test_draft7_semantics.js && node tests/test_metaschema_ref.js && node tests/test_pure_js_unsupported.js && node tests/test_native_load_order.js && node tests/test_pack_purity.js && node tests/test_make_native_package.js && node tests/test_browser_nofs.js && node tests/test_browser_imports_guard.js && node tests/test_version_sync.js && node tests/test_native_loaded.js && node tests/test_safe_regex_source_sync.js && node tests/test_t_builder.js && node tests/test_async_refine.js && node tests/test_safe_regex.js && node tests/test_safe_regex_integration.js && node tests/test_aot_build.js && node tests/test_aot_differential.js && node tests/test_aot_cli_build.js && node tests/test_aot_cli_smoke.js && node tests/test_bundle_standalone.js && node tests/test_standalone_anyof.js && node tests/test_standalone_formats.js && node tests/test_aot_format_mode.js && node tests/test_aot_additional_props_errors.js && node tests/test_id_anchor_refs.js && node tests/test_engine_routing.js && node tests/test_engine_option.js && node tests/test_compile_cache_order.js && node tests/test_engine_diagnostic.js && node tests/test_format_engine_parity.js && node tests/test_error_accumulator_collision.js && node tests/test_uri_helper_parity.js && node tests/test_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_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_hybrid_agreement.js && node tests/test_codegen_edge_shapes.js && node tests/test_deferred_def_scope.js && node tests/test_node_scoped_ctx_flags.js && node tests/test_fast_slot_exhaustion.js && node tests/test_ajv_errors.js && node tests/test_custom_keywords.js && node tests/test_strict_schema.js && node tests/test_aot_external_checks.js && node tests/test_ajv_parity.js && node tests/test_user_format_error_path.js && node tests/test_unevaluated_error_path.js && node tests/test_unevaluated_error_shape.js && node tests/test_pattern_properties_errors.js && node tests/test_additional_properties_scaling.js && node tests/test_additional_props_combined_cost.js && node tests/test_no_input_mutation.js && node tests/test_typed_validator_runner.js && node tests/test_define_schema.js && node tests/test_error_codes_lock.js && node tests/test_error_code_lookup.js && node tests/test_error_order.js && node tests/test_error_order_ordinal.js && node tests/test_rejection_shape.js && node tests/test_lazy_normalization.js && node tests/test_value_equality.js && node tests/test_vocabulary.js && node tests/test_schema_scan.js && node tests/test_lazy_errors.js && node tests/test_single_pass_errors.js && node tests/test_lazy_json_errors.js && node tests/test_lazy_instance.js && node tests/test_cyclic_input.js && node tests/test_native_error_codes.js && node tests/test_verdict_preprocess.js && node tests/test_defaults_own_keys.js && node tests/test_plan_compiler.js && node tests/test_nullable.js && node tests/test_validate_and_parse.js && node tests/test_validate_data.js && node tests/test_enrich_error.js && node tests/test_enrich_received.js && node tests/test_rich_errors_optout.js && node tests/test_error_messages.js && node tests/test_source_positions.js && node tests/fuzz_positions.js && node tests/test_malformed_json_termination.js && node tests/test_data_positions.js && node tests/test_targeted_positions.js && node tests/test_position_map_cost.js && node tests/test_targeted_positions_cost.js && node tests/test_aot_staleness.js && node tests/test_file_positions.js && node tests/test_aot_positions_cost.js && node tests/test_schema_hash.js && node tests/test_render_shared.js && node tests/test_renderers.js && node tests/test_additive_fields.js && node tests/test_diagnostic_source.js && node tests/test_diagnose.js && node tests/test_correlate.js && node tests/test_diagnostics_score.js && node tests/test_runtime_error_dx.js && node tests/test_aot_error_dx.js && node tests/test_abort_early.js && node tests/test_branch_collapse.js && node tests/test_suggestions.js && node tests/test_cli_validate.js && node tests/test_cli_version.js && node benchmark/bench_aot_size.mjs",
54
+ "test": "node test.js && node tests/test_removed_aot_methods.js && node tests/test_no_native.js && node tests/test_no_eval.js && node tests/test_property_dependencies.js && node tests/test_v1_dialect.js && node tests/test_buffer_path_parity.js && node tests/test_buffer_gate.js && node tests/test_scanner_differential.js && node tests/test_validate_json_scanner_cost.js && node tests/test_buffer_reject_cost.js && node tests/test_nan_verdict.js && node tests/test_remove_additional_nested.js && node tests/test_exclusive_bounds.js && node tests/test_engine_differential.js && node tests/test_error_shape_differential.js && node tests/test_output_format.js && node tests/test_retry_message.js && node tests/test_describe_schema.js && node tests/test_aot_parse.js && node tests/test_draft7_semantics.js && node tests/test_metaschema_ref.js && node tests/test_pure_js_unsupported.js && node tests/test_native_load_order.js && node tests/test_pack_purity.js && node tests/test_make_native_package.js && node tests/test_browser_nofs.js && node tests/test_browser_imports_guard.js && node tests/test_version_sync.js && node tests/test_native_loaded.js && node tests/test_safe_regex_source_sync.js && node tests/test_t_builder.js && node tests/test_async_refine.js && node tests/test_safe_regex.js && node tests/test_safe_regex_integration.js && node tests/test_aot_build.js && node tests/test_aot_differential.js && node tests/test_aot_cli_build.js && node tests/test_aot_cli_smoke.js && node tests/test_bundle_standalone.js && node tests/test_standalone_anyof.js && node tests/test_standalone_formats.js && node tests/test_aot_format_mode.js && node tests/test_aot_additional_props_errors.js && node tests/test_id_anchor_refs.js && node tests/test_engine_routing.js && node tests/test_engine_option.js && node tests/test_compile_cache_order.js && node tests/test_engine_diagnostic.js && node tests/test_format_engine_parity.js && node tests/test_error_accumulator_collision.js && node tests/test_uri_helper_parity.js && node tests/test_uri_fast_path.js && node tests/test_extend_verdict.js && node tests/test_ipv4_regex.js && node tests/test_ipv6_fast_path.js && node tests/test_nested_recursion_decline.js && node tests/test_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_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_hybrid_agreement.js && node tests/test_codegen_edge_shapes.js && node tests/test_deferred_def_scope.js && node tests/test_node_scoped_ctx_flags.js && node tests/test_fast_slot_exhaustion.js && node tests/test_ajv_errors.js && node tests/test_custom_keywords.js && node tests/test_strict_schema.js && node tests/test_aot_external_checks.js && node tests/test_ajv_parity.js && node tests/test_user_format_error_path.js && node tests/test_unevaluated_error_path.js && node tests/test_unevaluated_error_shape.js && node tests/test_pattern_properties_errors.js && node tests/test_additional_properties_scaling.js && node tests/test_additional_props_combined_cost.js && node tests/test_no_input_mutation.js && node tests/test_typed_validator_runner.js && node tests/test_define_schema.js && node tests/test_error_codes_lock.js && node tests/test_error_code_lookup.js && node tests/test_error_order.js && node tests/test_error_order_ordinal.js && node tests/test_rejection_shape.js && node tests/test_lazy_normalization.js && node tests/test_value_equality.js && node tests/test_vocabulary.js && node tests/test_schema_scan.js && node tests/test_lazy_errors.js && node tests/test_single_pass_errors.js && node tests/test_lazy_json_errors.js && node tests/test_lazy_instance.js && node tests/test_cyclic_input.js && node tests/test_native_error_codes.js && node tests/test_verdict_preprocess.js && node tests/test_defaults_own_keys.js && node tests/test_plan_compiler.js && node tests/test_nullable.js && node tests/test_validate_and_parse.js && node tests/test_validate_data.js && node tests/test_enrich_error.js && node tests/test_enrich_received.js && node tests/test_rich_errors_optout.js && node tests/test_error_messages.js && node tests/test_source_positions.js && node tests/fuzz_positions.js && node tests/test_malformed_json_termination.js && node tests/test_data_positions.js && node tests/test_targeted_positions.js && node tests/test_position_map_cost.js && node tests/test_targeted_positions_cost.js && node tests/test_aot_staleness.js && node tests/test_file_positions.js && node tests/test_aot_positions_cost.js && node tests/test_schema_hash.js && node tests/test_render_shared.js && node tests/test_renderers.js && node tests/test_additive_fields.js && node tests/test_diagnostic_source.js && node tests/test_diagnose.js && node tests/test_correlate.js && node tests/test_diagnostics_score.js && node tests/test_runtime_error_dx.js && node tests/test_aot_error_dx.js && node tests/test_abort_early.js && node tests/test_branch_collapse.js && node tests/test_suggestions.js && node tests/test_cli_validate.js && node tests/test_cli_version.js && node benchmark/bench_aot_size.mjs",
55
55
  "bench:size": "node benchmark/bench_aot_size.mjs",
56
56
  "test:suite": "node tests/run_suite.js && node tests/run_suite.js draft7 && node tests/run_suite.js v1",
57
57
  "test:compat": "node tests/test_compat.js",
@@ -121,13 +121,13 @@
121
121
  "LICENSE"
122
122
  ],
123
123
  "optionalDependencies": {
124
- "@ata-validator/native-darwin-arm64": "1.32.1",
125
- "@ata-validator/native-darwin-x64": "1.32.1",
126
- "@ata-validator/native-linux-arm64-gnu": "1.32.1",
127
- "@ata-validator/native-linux-arm64-musl": "1.32.1",
128
- "@ata-validator/native-linux-x64-gnu": "1.32.1",
129
- "@ata-validator/native-linux-x64-musl": "1.32.1",
130
- "@ata-validator/native-win32-x64": "1.32.1"
124
+ "@ata-validator/native-darwin-arm64": "1.32.2",
125
+ "@ata-validator/native-darwin-x64": "1.32.2",
126
+ "@ata-validator/native-linux-arm64-gnu": "1.32.2",
127
+ "@ata-validator/native-linux-arm64-musl": "1.32.2",
128
+ "@ata-validator/native-linux-x64-gnu": "1.32.2",
129
+ "@ata-validator/native-linux-x64-musl": "1.32.2",
130
+ "@ata-validator/native-win32-x64": "1.32.2"
131
131
  },
132
132
  "peerDependencies": {
133
133
  "yaml": "^2.0.0"