Packages

ata-validator

1.61.3 · 29 Oct 2026MIT21.8k / weekRepositoryHomepagenpmAtom feed

Your version

1.30.0

24 Sep 2026Where it was published

1.61.3 is the newest release of 1.x.

In short

1 fixed5 changed

Release note

Validating is now cheaper than parsing, errors are paid for when they are read, and a TypeError that shipped in 1.27.1 and 1.29.0 is gone.

If you generate modules with ata build, re-run it after upgrading: everything inside an emitted module, the position walker included, only changes when the file is rebuilt.

Fixed

  • Reading .errors threw a TypeError for an invalid uri-template. new Validator({ format: 'uri-template' }).validate('http://example.com/dictionary/{term:1}/{term').errors raised (_e || _e).push is not a function. The combined generator accumulates errors in _e, and the emitted source for that format declared its own const _e for the index of a closing brace, which shadowed the accumulator inside its block. It was the only emitted format source that declared that name. The verdict was always right, which is why the suite never caught it: the suite reads verdicts, and only a caller reading the errors got the throw. Three checks now hold the class rather than the one format: the case itself, a static check that no emitted format source declares the accumulator's name, and a sweep of the official suite reading .errors on every rejection, 642 schemas and 2230 cases.

Changed

  • additionalProperties: false no longer materialises the object's keys on every document that has nothing extra. The error has to name the offending property, so the keys have to be materialised to report one; they do not have to be materialised to find out whether there is one. A for-in decides, and the keys are built only when something is actually extra. A bare count would be wrong, since an extra key present while a declared one is missing leaves the count matching, so the gate tests each key. The request-body schema on the docs site is a strict one, so this moves the published per-request figures: accepting a body 64.8 to 41.8 nanoseconds, rejecting it 51.5 to 28.6, reading its errors 337.4 to 246.5.

  • validateJSON(text) asks the wire scanner for its verdict, the way isValidJSON already did. Above the simdjson threshold it encoded the whole document to a Buffer and called the native validator instead, 349 microseconds against the scanner's 191 on a 149 KB config. An accepted document now stops at the scanner; a rejected one goes on to the path that produces errors, skipping an encode and a native call that would only return false. Valid 349 to 177 microseconds, invalid verdict 445 to 383, invalid with frames 773 to 712. The valid row is now 0.91x a bare JSON.parse of the same text, because the document is never parsed.

  • A rejection is validated once rather than twice, from the first rejection on. The verdict pass in front of the combined function was validating the document a second time; the combined function decides and collects in one pass. It stays lazy, since compiling it eagerly triples what a first call costs. Reading errors went from 3.15x the verdict-only API to 2.14x, with accepted documents unchanged.

  • The position map is built for the pointers an error names instead of for every node, in the runtime and in emitted modules. A rejection names two or three pointers and nothing reads any other entry. Reading an error with its frame went from 705 to 270 microseconds on a 50 KB config and from 2378 to 772 on a 149 KB one; in a module built with { positions: true }, framing an error went from 8.02x a JSON.parse of the document to 1.72x, and that module is 361 bytes smaller gzipped. A derivation that misses a pointer must not cost a frame, so anything outside the set is answered from the full map rather than reported as having no position.

  • The email format reads both halves of the address in one forward pass. On a 1000-user array with format: email, against ajv with ajv-formats at its strictest mode, five rotating rounds: ata 42 microseconds with none of twelve edge cases wrong, mode:full 42 microseconds with five wrong, mode:fast 41 with seven wrong. The others accept a doubled dot, a leading dot and a local part over the RFC's 64 characters, and reject quoted local parts and address literals.

Suite unchanged at 100% on all three dialects, identical with code generation blocked: draft 2020-12 1301/1301, draft 7 929/929, the v1 dialect 1135/1135.