Packages

ata-validator

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

Your version

1.22.1

16 Sep 2026Where it was published

1.61.3 is the newest release of 1.x.

In short

3 fixed1 changed

Release note

Fixed

  • validateJSON() did not return on a document that ends inside an array. validateJSON('[') was one byte, any schema, the default configuration, and it never came back. On a rejection the error carries a caret, so a map from pointer to position is built from the raw text; its array loop asked for the next element without checking that the input had ended, walk returned without moving, and nothing else could close the array. '[}' hung the same way, because a character that begins no value leaves the position where it was. The object loop escaped only by accident: an unterminated string throws, and lib/data-position-cache.js catches throws. It cannot catch a loop. Both container loops now stop at the end of the input, and the array loop stops when an iteration does not advance, so a truncated document still yields a map of what was there.

    • A regression here is an infinite loop, which an ordinary test cannot report because it never reaches a failure. tests/test_malformed_json_termination.js runs its corpus in a child process under a watchdog, so a hang becomes a timeout and a red test. The corpus is every prefix of ten documents plus characters inserted where they cannot begin a value: 1793 inputs, 7172 verdicts. It was confirmed to fail before this change and pass after.

    • tests/fuzz_positions.js had never fed malformed text to this walker; it fuzzes schema positions with JSON.stringify output, which is always well formed. Nothing else changes: on the same five-field schema, validate() on a valid document, validate() on an invalid one with the errors read, and validateJSON() on both are all within measurement noise of 1.22.0.

  • unevaluatedProperties: false reported the boolean schema underneath the keyword instead of the keyword itself: keyword: 'not', empty params, and an instancePath pointing at the offending member rather than the object holding it. Nothing in that error said which key was unexpected, so a caller branching on params.unevaluatedProperty to flag unknown keys saw nothing at all. It now reports unevaluatedProperties on the object, with params.unevaluatedProperty naming the key, one error per key. unevaluatedItems: false had the same shape and now reports one error for the array, with params.limit at the first unevaluated position. Both engines were changed together and tests/test_unevaluated_error_shape.js holds them to it. Reported by a config linter reading err.params.unevaluatedProperty through ata-validator/compat.

  • verbose: true attached only parentSchema. It now also attaches data, the value the error points at, and schema, the failing keyword's own value, which is the whole set the option is supposed to carry. Without data a caller had to walk the document by the instance path itself. The default error shape is unchanged: the two fields appear only when verbose is on, including on the errors ata-validator/compat synthesises for an if wrapper.

Changed

  • additionalProperties: false no longer compiles to a comparison per declared name. It was one comparison per name per key of the document, which is quadratic in the number of properties: a 1000-property schema spent 75 µs on a document where the same schema without the keyword took 3, and doubling the properties quadrupled that. Above 128 declared names the generated code now reads a name lookup built once per compiled function; below that the comparison chain stays, because it allocates nothing and the two are the same within noise there.

    • Cross-process medians, one variant per process, properties of type string: 250 names 8.76 µs to 5.49, 500 names 26.29 to 12.47. With no per-property work, so the membership test is all that is measured: 1000 names 77.3 µs to 18.1, 2000 names 273.6 to 38.9.

    • Measured and not taken: for...in over the document instead of an indexed loop over Object.keys was about a quarter slower at every size, even though it allocates no key array.

    • tests/test_additional_properties_scaling.js guards this structurally, on the emitted source: the lookup is present above the threshold, absent below it, and declared outside the function that runs per call. It asserts no timing, because a wall-clock ratio was not a guard here: the quadratic shape measured 7.7x for four times the properties on the machine the test was written on, where a CI runner measured 8.3x for the fixed code. The comparison chain short-circuits as soon as a key matches, so the ratio never separated the two. Found by a config linter whose schema is generated from a project's environment variables, so its property count is whatever the project has.