Packages

ata-validator

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

Your version

1.11.0

31 Aug 2026Where it was published

1.61.3 is the newest release of 1.x.

In short

3 faster6 fixed

Release note

Fixed

  • The verdict methods answer the same question as validate() again. With coerceTypes, removeAdditional or a schema default in play, validate() ran the preprocess pass and isValidObject(), isValidJSON() and validateJSON() did not, so the same validator answered true from one and false from another for the same document: validate({ age: '26' }) accepted where isValidObject({ age: '26' }) rejected. Every path now runs the same pass. Verdict methods on a validator configured this way rewrite the input in place, as validate() already did, and the cost of the correction is 0.5 ns on isValidObject and 2.7 ns on isValidJSON, measured interleaved; validators without those options are unchanged. tests/test_verdict_preprocess.js holds all four methods to the same answers.

  • The native engine's error codes no longer reach callers untranslated. A type failure answered by the addon came back as code: 3 with no keyword and a docUrl pointing at a page that does not exist, while the same failure from the JavaScript engines came back as ATA1001; both now report the documented code, keyword and link. tests/test_native_error_codes.js holds the table against the enum in include/ata.h, so the two cannot drift apart silently.

  • The error generator declined self-referencing schemas by emitting nothing for the reference, which accepted whatever that reference guarded: { properties: { foo: { $ref: "#" } }, additionalProperties: false } accepted { foo: { bar: false } } on that path. It now declines the schema outright and the validator falls back to an engine that answers it correctly. The entry-point agreement test covers the shape.

  • ipv6 gave two different answers depending on which engine ran it, and neither was right. The compiled path refused an IPv4-mapped address such as ::ffff:192.168.1.1, the interpreted path accepted ::ffff:1.2.3.4.5, and both accepted a group of five hex digits like 12345::1. One implementation now answers for every engine, following RFC 4291, checked against Node's own net.isIPv6 and the suite's corpus.

  • date-time refuses dates that do not exist. The check ran a regular expression for the shape and then handed the string to Date.parse, which rolls an out-of-range day into the next month, so 2026-02-30T00:00:00Z, 2026-02-29T00:00:00Z and 2026-12-31T24:00:00Z were all accepted. The month, the day count for that month in that year, the clock and the offset are now checked directly, per RFC 3339. Schemas that relied on the old leniency will see those values rejected.

  • Data that points back at itself no longer exhausts the stack. A document with a cycle, which JSON text cannot express but an in-memory object graph can, threw RangeError: Maximum call stack size exceeded on the compiled path while the interpreted engine settled on an answer, so the two engines disagreed. Both now follow the same rule: a value already being checked against a schema is a fixed point and counts as satisfied, and a cycle no longer hides a real violation elsewhere in the document. Validation runs a fast pass that only counts depth and a guarded pass that runs when that depth is exceeded, so ordinary documents pay one integer operation per recursive call. Measured interleaved on a self-referencing schema: a four-node document 22.2 to 30.6 ns, a 200-node document 2077 to 1178 ns, non-recursive schemas unchanged at 3.8 ns. tests/test_cyclic_input.js holds all three engines to the same answers.

Performance

  • ipv6 and hostname read the string once as well: 54.7 to 29.6 ns and 45.5 to 28.2 ns, interleaved medians. ipv6 no longer allocates two arrays per check; hostname keeps the answers of the expression it replaces, fuzzed over 300k strings with 0 mismatches.

  • date-time reads the string once, with no regular expression, no date object and no allocation: 95.0 to 39.4 ns on a valid value with a Z, 103.8 to 45.3 ns with a numeric offset, interleaved medians. Fuzzed against a reference that spells out RFC 3339, with 0 mismatches over 300k strings; tests/test_formats_single_pass.js keeps both the predicate and the generated form on it.

  • A constructed Validator is roughly three times smaller on the heap until it is used. The public methods and the Standard Schema entry moved from per-instance closures built in the constructor to memoized prototype accessors, and the JSON position cache is only allocated when the JSON text path first needs it. Measured per instance on a 10-key object schema, double-gc deltas over 2000 instances: 1.61 KB to 0.43 KB with a shared schema object, 2.33 KB to 1.12 KB when each instance owns its schema, 3.93 KB to 3.30 KB once compiled and used. Construction alone went from 1504 to 855 ns; construction plus first validate pays about 0.9 microseconds more, once, because the compile step's method assignments now go through a defining setter. The hot validate() path is unchanged, measured interleaved. Detached method references (const f = v.validate) still work; tests/test_lazy_instance.js pins the shape.