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 +44 -33
- package/index.js +29 -7
- package/lib/js-compiler.js +60 -28
- package/lib/version.js +1 -1
- package/package.json +9 -9
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
|
-
|
|
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
|
|
61
|
-
|
|
62
|
-
|
|
63
|
-
|
|
64
|
-
|
|
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.
|
|
75
|
-
| Bundle (gzipped) | complex | 7.
|
|
76
|
-
| Bundle (gzipped) | nested | 4.
|
|
77
|
-
| Cold start | simple |
|
|
78
|
-
| Throughput (1M ops) | simple |
|
|
79
|
-
| Compile time | simple |
|
|
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-
|
|
83
|
-
on ata-validator 1.
|
|
84
|
-
is
|
|
85
|
-
between
|
|
86
|
-
rows as an order of
|
|
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 | **
|
|
215
|
-
| Time to a served request | **3.4 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
|
|
219
|
-
|
|
220
|
-
|
|
221
|
-
|
|
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.**
|
|
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
|
|
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
|
|
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.
|
|
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
|
|
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` |
|
|
505
|
-
| `isValid` from the compiled module | **1.
|
|
506
|
-
| `validate` from the compiled module | **
|
|
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
|
|
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
|
-
|
|
1547
|
-
|
|
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
|
-
|
|
1617
|
-
|
|
1618
|
-
|
|
1619
|
-
const jsonValidateInner =
|
|
1620
|
-
|
|
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
|
package/lib/js-compiler.js
CHANGED
|
@@ -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
|
-
|
|
928
|
-
|
|
929
|
-
|
|
930
|
-
|
|
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.
|
|
940
|
-
function protoIs (v) {
|
|
941
|
-
|
|
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
|
-
|
|
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
|
-
|
|
1778
|
-
|
|
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
|
-
|
|
1785
|
-
|
|
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
|
-
|
|
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
|
-
|
|
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
|
-
//
|
|
1812
|
-
|
|
1813
|
-
|
|
1814
|
-
|
|
1815
|
-
|
|
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){${
|
|
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
|
|
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
|
-
|
|
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
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "ata-validator",
|
|
3
|
-
"version": "1.32.
|
|
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.
|
|
125
|
-
"@ata-validator/native-darwin-x64": "1.32.
|
|
126
|
-
"@ata-validator/native-linux-arm64-gnu": "1.32.
|
|
127
|
-
"@ata-validator/native-linux-arm64-musl": "1.32.
|
|
128
|
-
"@ata-validator/native-linux-x64-gnu": "1.32.
|
|
129
|
-
"@ata-validator/native-linux-x64-musl": "1.32.
|
|
130
|
-
"@ata-validator/native-win32-x64": "1.32.
|
|
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"
|