ata-validator
1.61.3 · 29 Oct 2026MIT21.8k / weekRepositoryHomepagenpmAtom feed
Upgrading ata-validator from 1.53.0 to 1.54.0
1 release in betweenevery note read
Reading releases, release notes and advisories…
- 0
- changes to make
- 0
- advisories fixed
- 0
- new
- 7
- fixes
Release by release
- fixed
The
uri,uri-reference,iriandiri-referenceformats follow RFC 3986 in full. They checked the characters and, foruri, the shape of the authority; a bracketed host was not read, sohttp://[::ffff:01.2.3.4]passed, anduri-referencechecked only the characters, so//example.com:abc/p,//a@b@example.com/,/[::1]and1:bpassed. One walk now parses scheme, authority (userinfo, a host that is a name, an IPv6 literal with its embedded IPv4, an RFC 6874 zone id or an IPvFuture, and a digits-only port), path, query and fragment, and rejects brackets outside the host, a colon in the first segment of a relative path and a second#. It lives inlib/uri-core.jsand is the same text in the interpreted engine, generated code and standalone modules. Theuriexpression in front of it now takes a host with a port, which most of SchemaStore's misses were (http://localhost:5000, 73 to 34 ns in generated code), and a reference made only of plain characters, a file name or a path, is answered by one scan before the walk, so those checks cost what the character scan did. On the 26321 strings in SchemaStore's sample documentsuriandirigive the same answers as before;uri-referencenow rejects 346 of them, every one a string that is not a URI reference ([string],00:00:01,2022-01-19T22:02:33Z), 277 of which the default validator still accepts. - fixed
uri-template: a%in a literal or a variable name must start a percent-encoded triplet (a%,a%4,a%GG,{%}were accepted). - fixed
idn-email: a domain label that starts or ends with a hyphen is refused, asemailrefuses it (the official suite's 7de0e6a adds these cases). - fixed
email: the inside of a quoted local part is checked ("test\©"@iana.orgwas accepted, a quoted pair takes one printable ASCII character), and theIPv6:tag of an address literal is read in any case (a@[ipv6:::1]was rejected). - fixed
A node without
type: objectchecked an array against itsproperties:[1, 2]was rejected by{ properties: { length: { maximum: 1 } } }, through the array's ownlength, while the interpreter and the default validator accept it. Generated code now skips arrays there when a property name is one an array can own (lengthor an index); other names were already safe, since an array only inherits them. A required key on such a node is also read before it is asked whether it is the object's own, which saves a test.tests/test_untyped_properties.jscovers both on 189 documents. - fixed
Draft-04 is read as draft-04. A schema that declared it was read as 2020-12:
iddid not set the base URI, tupleitems,additionalItemsanddependencieswere not applied, andconst,containsandpropertyNames, which draft-04 does not have, were. It now goes through the draft-06 rules plus its own (idfor$id, those three keywords dropped), and its meta-schema is vendored with the others. The official suite's draft-04 tests pass, 618 of 618, also with code generation blocked;npm run test:suiteruns them. SchemaStore's draft-04 schemas answer their sample documents as before. - fixed
A declared property holding
undefinedstill got different answers in 1.53.0: the generated code checked the value where the key was required, where the node had notype, and insideif,notand similar subschemas, and the engines disagreed on whether a required key holdingundefinedwas there. Every engine now reads such a key as the default validator does, and as it reads once serialized: absent. Its value is not checked, and a required key holding it is missing, so{ a: undefined }no longer passesrequired: ['a']on any engine.{ if: { properties: { t: { const: 'x' } } }, then: false }accepted{ t: undefined }on generated code and rejected it on the interpreter; both reject it now.tests/test_undefined_property.jscovers 21 shapes on every engine, with and without code generation.
- fixed
19 other lines we could not sort, mostly version bumps
- 1.54.0
Two schemas that generate the same code no longer slow each other down. V8 keeps one compiled function per source text of a
new Functioncall, with one set of inline caches, so two copies of a record schema under different$ids, each validating lists of a different shape, ran each other's caches megamorphic: 500 records took 2.8 us alone and 18.0 with the twin in the same process. Every program compiled at run time now opens with a comment that differs per compile, 2.8 us either way. Standalone builds carry no such comment, so a build is the same text whatever was compiled before it. - 1.54.0
Reading the errors of a list of records with many optional properties, where the records do not share one shape, took 20.6 ms for 2000 records and now takes 0.57. The error program guards each record with a verdict and collects only the one that fails; the guard for a 150-property record was written one read per property, 16.9 KB, over the size the program accepts, so there was no guard and every record was collected in full with reads that change shape at each one. The guard now takes the key-loop form the verdict uses for such records, 10 KB, and runs only on a list whose first, middle and last records list different keys: records of one shape are read as before (16 us for the same 2000). On the way the key loop's switch, pushed as separate lines, read
switch(k){;caseonce joined with;, a syntax error that made the rich error program fall back to the plain one without a word; it is one line now andtests/test_rich_key_loop_guard.jsholds the compile. - 1.54.0
A oneOf under
type: objectwrites its branches for an object already checked, without the type test and the second own-key read each branch made for its required keys (vercel's now.json spends 23% of a validation in two such branches). A branch reached where the type is not known keeps its test, as a separate function. - 1.54.0
A pattern that only names keys,
^cdn$or^(cdn|Cdn)$, is a string comparison in generated code, and.*is no test at all. appsettings carries eight such patterns and answered each with a regex run per key; its samples validate 15% faster.tests/test_pattern_literal_keys.jsholds the shapes that must still be a regex (^a|b$,^(a|b)c$, a$not at the end). - 1.54.0
The linear regex engine's matcher loop reads its state tables through locals and reads one flag per character for both a match and a dead state. The tables live in the matcher's closure and were reloaded on every character, about a third of the loop; SchemaStore's popxf-1.0, whose eleven patterns all run on this engine, validates its samples 18% faster. The engine's answers are unchanged (88000 random strings against the platform RegExp, 0 differences).
- 1.54.0
A definition on a reference cycle that refers back to the root (
$ref: "#") compiles. Such a definition is a function above the entry, where the entry is not in scope, so the schema declined code generation and ran on the interpreter: SchemaStore's jsone, the one schema with sample documents that did, was 0.60x the default validator's speed. The definition now reaches the root through a forwarder the entry fills in, named per compile, so two such schemas in one bundle do not share it.tests/test_def_root_forwarder.jsholds generated code, a standalone module and a two-schema bundle to the interpreter's answers. - 1.54.0
An array of records with 32 or more optional properties is checked one of two ways per call. When three of its records (first, middle, last) list their keys alike, which records of one shape do, each declared property is read as before; when they do not, each record is checked from its own keys. Reading every declared property is fastest while the records share a shape and slowest once their shapes vary, because V8's property reads then stop being specialised. On 2000 records of 150 typed fields, records that each carry a different handful of keys go from 20.13 ms to 0.29 ms an array, and records of one shape from 7.41 to 7.19 µs.
tests/test_item_key_loop.jsholds both forms to the interpreter's answers on 648 arrays. Error lists for such arrays are still built the old way. - 1.54.0
A
patternPropertiesentry whose schema checks nothing ({}ortrue), as OpenAPI's^x-extensions are on nearly every object, no longer writes a loop over the object's keys: the loop read each matching key's value and did nothing with it. A valid OpenAPI 3.1 document goes from 4.49 to 3.47 µs and a rejected one with its errors read from 3.73 to 3.35 µs; the first validation is unchanged (10.82 against 10.81 ms, median of 21). A pattern that still checks something, apropertyNamesandadditionalPropertieskeep the loop. - 1.54.0
OpenAPI 3.1's own schema runs on generated code. It has a property named
exampleswhose schema is a$ref, and the passes that rewrite references skippedexamples,default,enumandconstas value keywords even where they are property names, so the copy pointed at a definition it no longer had and the whole schema fell back to the interpreted engine. A valid OpenAPI document goes from 9.81 to 4.62 µs, and to 3.47 µs with the next entry, a rejected one with its errors read from 14.06 to 3.85 µs; the first validation is unchanged (10.83 to 10.69 ms).tests/test_name_map_value_keys.jsholds those four names as properties, through$dynamicRefand through a reference into another document, to generated code and to the interpreter's answers. - 1.54.0
Reading a rejected document's errors costs about half what it did when the list has more than one error. The
detailtext is worked out where each error site is compiled, as far as the site allows, instead of from the keyword at every failure, and the list built where the errors happen says whether it came out in schema order, so it is sorted only when it did not. A migration-style schema with two errors goes from 159.9 to 79.6 ns a document; the request benchmark's rejected body with its error list read from 135.3 to 96.1 µs against 1.53.0. The errors are unchanged:tests/test_rich_errors_generated.jscompares them withenrich()on 10,553 rejections, and SchemaStore's 26,563 rejections keep their order with the order check on.