Packages

ata-validator

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

Upgrading ata-validator from 0.12.1 to 1.61.3

133 releases in betweennotes for 101

Other targets from 0.12.1Newest 0.x0.22.0
2+
changes to make
0
advisories fixed
78+
new
140+
fixes

Counted from 101 of 133 release notes. Advisories are counted from the advisory database, in full.

Start here

Before you upgrade

Tick each one off as you go
  • breaking1.29.0
  • breaking1.0.0

Security

  • A string in enum that needs escaping in JSON could be matched by a different string through the native engine. Its fast path for string enums compared the value's bytes with the entry's JSON text between the quotes, so for enum: ['a"b'] the value a\"b, with a backslash, matched. The fast path now takes only entries with no escape in them; the others are compared as canonical text, as every other path does. This reaches the same APIs as the native fixes in 1.58.0 (isValid, isValidJSON and validateJSON on 8 KB or more, the NDJSON and batch methods); validate() and isValidObject() were not affected. tests/test_native_security.js and tests/test.cpp cover it.

    1.58.1
  • An object in enum or const could be matched by a different object. The native engine compared objects through a canonical text that put each member name between quotes without escaping it, so {"a\":1,\"b": 2}, an object with one member, read the same as {"a": 1, "b": 2} and passed enum: [{ "a": 1, "b": 2 }]. Member names are now escaped as JSON strings, which makes the text one-to-one. The same text decided uniqueItems, where it made two different objects count as equal and rejected the array.

    1.58.0
  • A violation deep enough in the document was accepted. Past a depth limit (100 schema frames, which a recursive schema reaches about 50 levels into the data) the native walker stopped and took the rest of the document as valid. It now rejects there, with the error code depth_exceeded in the C++ API, and the JavaScript APIs above send a schema that refers back into itself, or that is nested more than 40 levels, to the JavaScript engine, which has no such limit and answers as validate() does at any depth. That route was faster for such schemas, not slower: 4,638 to 2,495 ns for isValid on a 20-level document.

    1.58.0
  • isValidParallel and countValid read up to 64 bytes past the end of the caller's buffer for a line ending near it, and isValidPrepadded(buffer, length) read as many bytes as length said whatever the buffer's size. A line without 64 bytes of the buffer after it is now copied to a padded buffer first, and isValidPrepadded returns false for a length past the buffer and copies when the padding is short. The read did not fault, since the engine checks for a page boundary, and the bytes past the end did not decide the answer.

    1.58.0
  • isValidParallel could return false for a valid line: its workers wrote their answers into a std::vector<bool>, which packs eight lines to a byte, so neighbouring lines raced. One byte per line now; 20 runs of 200 lines all agree with validate().

    1.58.0
  • With code generation blocked, validating and reading the errors takes 4.7 µs per tsconfig.json sample where it took 8.5, and 427 µs per SARIF log where it took 568; the other schemas measured are unchanged;

    1.45.0
  • On the valid samples of five popular schemas, a verdict takes 9.9 µs where it took 16.2, and a validation that collects errors 16.0 µs where it took 23.4.

    1.45.0
  • A property name in a schema could run as code in the validator ata generates for it. Where required or dependentRequired sits under items, patternProperties, additionalProperties or a $ref, and in every standalone module, the error for a missing property put its name in a single-quoted string literal escaped only for double quotes, so a name holding a single quote closed the literal and the rest of the name was compiled into the validator. It ran when a document missing that property was validated. Only a schema written to do this is affected, so an application that compiles schemas it does not control, at run time or with ata build, should upgrade; one whose schemas are its own is not exposed. Affected: 0.4.3 to 1.40.0. The helper that escapes these strings now escapes the single quote too; error messages and params are unchanged. tests/test_schema_code_injection.js puts strings built to break out of a literal into every string a schema can carry, runs each schema through every path that generates code (the runtime compiler, the interpreter, standalone modules, bundles, the compiled wrapper, parse()), validates documents carrying the same strings and renders the errors, and checks that none of it ran.

    1.40.1
  • Security patch.

    1.27.1

Release by release

  1. 1.61.3

    29 Oct 20262 new
    • new

      ata diff <old> <new>: what a change to a schema does to documents. The documents ata verify makes, from both schemas, plus the project's samples, are checked by a Validator built from each schema; the ones the old schema accepts and the new one rejects, and the reverse, are reported grouped by the rule that decides them, with one example each. Exit code 1 when the new schema rejects what the old one accepted, so a pull request that tightens a schema fails the check until someone looks. lib/schema-diff.js, tests/test_schema_diff.js.

    • new

      ata-validator/compiled-standard: bareStandard(mod, schema), the compiled-module wrapper for a validator that is only ever handed to a Standard Schema consumer (a router's validator middleware, a form library, an RPC layer) and never called by its own code. Such a consumer reads ~standard alone, and an issue is a message and a path, so the wrapper is built on a module written with plainErrors: true and carries no error pipeline: 733 bytes gzipped, standing alone as an ES module. The issues, the input after defaults and the Standard JSON Schema document are the runtime's on every suite case the compiler takes (tests/test_compiled_standard.js). @ata-project/unplugin uses it where it sees that handover, through Hono's sValidator, hono-openapi, TanStack Form, oRPC, tRPC's .input() and Elysia's hooks: a Hono route bundled for Cloudflare Workers goes from 21.0 to 12.2 KB gzipped against 7.7 KB for Hono alone, an Elysia route from 120.1 to 110.1 against 106.8, a tRPC procedure from 18.0 to 7.8 against 4.6.

  2. 1.61.2

    28 Oct 20261 fix
    • fixed

      Under Bun's ahead-of-time compiler a runtime Validator took generated code again, which the compiler interprets: 487 ns a document against 78 on the interpreted engine. ata told the two apart by Bun.isAOT, which the compiler's pull request has since removed in favour of an "aot" export condition. The package now leads its exports with that condition, naming index.aot.js and index.aot.mjs, which mark the process before the package loads (lib/aot-mark.js), and lists them as side effects so a bundler keeps them. Measured on the pull request's build of 2026-10-27; tests/test_aot_entry.js holds the condition, the entries and the mark.

  3. 1.61.1

    26 Oct 20265 new · 1 fix
    • new

      ata-validator/compiled-bare-rich: bareRich(mod, schema), the bare wrapper for a validator that reads ata's own errors, the same errors as fromCompiledObject without Standard Schema and the extension hook. @ata-project/unplugin uses it for a validator only ever called; the two-validator form app went from 13.5 to 10.7 KB gzipped. Held to a default Validator in tests/test_compiled_bare.js.

    • new

      compiledModuleFor(schema, { verdictOnly: true }) writes the module for a reader that only ever calls isValid(), without its error collector (its validate() hands out the abort-early stub): 954 to 539 bytes gzipped on a one-field object. @ata-project/unplugin writes verdict-wrapper modules this way, and keeps the full module for verify. The richErrors: false form app went from 3.8 to 3.5 KB gzipped.

    • new

      ata-validator/compiled-bare: bareVerdict(mod) and barePlain(mod), the smallest compiled-module wrappers (315 bytes gzipped for both), for a validator the bundler plugin sees used only through direct calls of validate() or isValidObject() and never handed to other code, so nothing reads ~standard or registers a check on it. Defaults are filled with the module's own pass; barePlain hands out the plain errors of a plainErrors module. @ata-project/unplugin uses them where that holds: the form app with richErrors: false went from 6.1 to 3.8 KB gzipped. tests/test_compiled_bare.js holds them to the runtime on 22,315 answers over the suite.

    • new

      ata-validator/compiled-plain: fromCompiledPlain(mod, schema, { richErrors: false }), the compiled-module wrapper for a Validator built with richErrors: false, around a module written with compiledModuleFor(schema, { plainErrors: true }), whose one-pass program writes errors in that plain shape and order. It hands them out as they are and carries no rejection classes, enrichment or suggestions: 2.6 KB gzipped. @ata-project/unplugin compiles such a validator away with it, where it kept it on the runtime before. A form app with one validator that reads errors and one that asks for a boolean went from 16.7 KB gzipped on 1.61.0 to 6.1 with richErrors: false. tests/test_compiled_plain.js holds it to the runtime on 17,852 answers over the suite in five dialects. Standard Schema reaches the enrichment only for a native engine's message-less error, now through lib/native-message.js, which the browser build maps to a stub, since a browser has no native engine.

    • new

      ata-validator/compiled-object: fromCompiledObject(mod, schema), the compiled-module wrapper without validateJSON() and isValidJSON(), for code that reads errors but never validates JSON text. The position map those entry points use to frame an error in the text stays out of the bundle: 10.9 KB gzipped against 12.8 for the full wrapper. @ata-project/unplugin picks it where the replaced validator is never asked to validate JSON text. tests/test_compiled_parity.js holds it to the runtime over the suite.

    • fixed

      ata verify also tries, for every bound a schema states (minimum, maximum, the exclusive ones, multipleOf, minLength, maxLength), a value on either side of it, on a document the runtime accepts. Its random documents rarely land on a bound, so a module that read one off by one (a minimum of 13 written as < 12) answered all 200 of them as the runtime did and passed; it is now caught, with the runtime saying invalid and the module valid. tests/test_verify.js holds the case.

  4. 1.61.0

    26 Oct 20267 new · 2 fixes
    • new

      A schema with a default anywhere (ata fills defaults without being asked, so it has a preprocess pass) now builds its rich errors where they fail, as other schemas do: the path was closed to schemas with a preprocess pass, though it reads the document validate() has already processed. One required error on a four-field user with a defaulted newsletter went from 313 to 248 ns with its error list read; only the one-pass swap, which replaces validate() itself, stays off for such schemas. Found through the fastest-validator comparison in ata-core/validator-benchmarks (issue #74).

    • new

      The Node entry warms the engine once the event loop is free: where a validator was built and is still untouched by then (not compiled, no method read off it), one check of a small schema runs, accepted and rejected, so that V8 compiles the generator's functions and the error builders before the first request instead of on it. On a four-field schema with the generator already loaded, a server's first verdict went from 3.1 to 3.4 ms to 0.5 to 0.6, the first error list from 6.7 ms to 0.8, at 1.5 to 2 ms of CPU once. A script that builds and checks in the same tick never runs it, the timer is unref'd, and ATA_NO_WARM=1 turns it off. The browser build has done the same since 1.57.0 (lib/tiering.js); the cold-start benchmarks, which check in the same tick as they build, are unchanged by it. tests/test_node_warm.js holds the conditions.

    • new

      ata-validator/build exports bunTarget (the schema as a TypeScript type and the .bun.ts module ata build --target bun writes; with tags: true the split into a type with Bun's Schema tags and a residual schema, made for the typed validator the compiler's pull request carried until 2026-10-25) and verify (verifyOne, verifySource, generate), for a build plugin that replaces new Validator(schema) inside a Bun.build({ compile: { aot: true } }) and holds what it wrote against the runtime.

    • new

      ata verify <glob> holds each schema's compiled module against a runtime Validator built from the schema now: documents made from the schema (--generate, 200 by default, half with one rule broken), the project's sample documents (--samples, JSON or JSON Lines files or a directory), verdicts and error lists compared, a module whose schema hash no longer matches the schema reported stale, a non-zero exit on any disagreement, stale or missing module, --json for machines. The check behind the fixes of 1.59.0 and 1.60.0, as a command a project runs in CI.

  5. 1.60.0

    24 Oct 20261 new · 1 fix
    • new

      A validator the build compiled away (fromCompiled around a module written ahead of time, what @ata-project/unplugin puts in place of new Validator(schema)) carries Standard Schema V1 as the runtime Validator does: the same ~standard object, built by lib/standard-schema.js for both, with the same issues, value and published schema. A compiled validator can now be handed to a form library, a router or an RPC layer that reads ~standard. tests/test_compiled_standard.js compares the two.

    • fixed

      toStandaloneModule(schema, { coerceTypes }) and toStandaloneModule(validator) for a validator built with coerceTypes or removeAdditional wrote a module that checked the data as it was, so it rejected { id: "5" } where the validator accepts it. The module now runs the validator's pass over the data before the check, as bundleStandalone does since 1.59.0, and refuses where that pass cannot be written into a module. fastify-ata/standalone builds its files through this call.

  6. 1.59.0

    24 Oct 20262 new · 1 fix
    • new

      bundleStandalone(schemas, { order: true }) hands each validator's errors out in the runtime's order, as toStandaloneModule does with the same option. rjsf's precompiled validator writes its module with bundleStandalone, and its error list showed an additionalProperties error before the properties errors where the runtime validator shows it after. tests/test_aot_order_defaults.js holds 553 bundled groups of the 2020-12 and draft-07 suites to the runtime's errors, in order.

    • new

      Each validator of a bundle whose schema has a default carries applyDefaults(data), the runtime's defaults pass, as a property, as standalone modules export it. rjsf's precompiled validator can call it to match its runtime validator. tests/test_bundle_preprocess.js compares 8400 generated documents with the runtime: the data afterwards, the verdict and the errors in order.

    • fixed

      bundleStandalone left coerceTypes and removeAdditional out without a word, so a bundle built with coerceTypes: true rejected { age: "5" }, which the runtime accepts. Each bundled validator now runs the runtime's pass over the data for those options before it checks, and with useDefaults: true fills defaults the same way. Where the generated pass cannot carry what the runtime would do (coercing the items of an array at the root, for one), bundleStandalone throws instead of writing a module that answers differently. Without these options a bundle checks the data as it is, as before.

  7. 1.56.0 – 1.58.14 releases, nothing new or fixed in the notes

  8. 1.55.0

    17 Oct 20261 fix
    • fixed

      bundleStandalone and bundleCompact modules threw ReferenceError: _cpLen is not defined on a string whose minLength or maxLength needed counting code points: a one-character value under minLength: 1, an emoji under a maxLength. The length checks call a code-point counter that toStandaloneModule embeds and the two bundles did not. This goes back to 1.10.0 at least, and reached forms built on rjsf's precompiled validator, which uses bundleStandalone: typing one character into a required name field threw. The bundles now embed the counter when their code calls it; tests/test_bundle_cplen.js runs both bundles in both formats.

237 other lines we could not sort, mostly version bumps
  • Tier 0, the shared verdict for flat object schemas, checks format on a string property with the table the interpreted engine reads (formats.formatChecks, moved out of lib/interpreter-core.js) or the caller's own function, so a schema with an email field stays on it; and on the interpreted engine it answers the verdict whether or not validate() came first, which validate()'s own verdict-first path then uses too. On Node's interpreted engine, the ten-field user schema with format: email: an accepted document 86 to 70 ns, a rejected one 33 to 17, validate() 91 to 75 and 35 to 19. Under Bun's ahead-of-time compiler, with the fix below, isValidObject() 78 to 48 ns and validate() 94 to 51, against 483 for zod 4 there. tests/test_tier0_format.js holds tier 0, the interpreted engine and the generated code to the same verdicts on the 6,558 format cases of the official suite. ata-validator/lite grows by 85 bytes gzipped.

    1.61.2
  • A browser build carries no code for the native engine: lib/native-positions.js, lib/native-codes.js and lib/native-message.js map to one stub (lib/native.browser.js), since the native loader returns null there. The runtime's code generator chunk went from 78,733 to 78,237 bytes gzipped and the whole runtime from 139,438 to 138,944; the first load is unchanged at 54,383.

    1.61.1
  • A browser's generator checks an email address literal with the IPv6 and IPv4 parsers it carries, and the expression a compiled module uses (lib/email-literal.js) maps to nothing there. The same answers on either form (tests/test_ipv6_expression.js).

    1.61.1
  • The format codes in lib/error-codes.js are written by one loop; get() and all(), which only tests and scripts read, are gone from it. The table is unchanged, key order included.

    1.61.1
  • ata-validator/compiled-bare is an ES module of its own rather than a re-export of the CommonJS file, so a bundler adds no interop code: 662 to 315 bytes gzipped.

    1.61.1
  • The rejection validateJSON() returns moved to lib/json-rejections.js, loaded with the first rejection that carries JSON text, so validate() through a compiled wrapper does not bundle it (532 bytes gzipped) and a process that never rejects JSON text no longer loads lib/diagnostic-source.js. The native engine's ordinal table moved to lib/native-codes.js, which a browser build maps to a stub. ata-validator/lite went from 57,790 to 57,769 bytes gzipped.

    1.61.1
  • A built-in format under type: string is no longer repeated in the branch for a value of another type, where it could never fire.

    1.61.1
  • A compiled module that checks format: email reads an address literal (a@[IPv6:...], a@[1.2.3.4]) with two regular expressions instead of the IPv6 and IPv4 parsers it carried inline, which were 72% of the check's source: an email schema's module went from 1,743 to 1,524 bytes gzipped. The answers are the same (tests/test_ipv6_expression.js: the expressions against ipv6() and ipv4() on 400,000 near-addresses each, the emitted check against formats.email on 100,000 literals); the expression lives with the code generator, so the interpreted engine and ata-validator/lite do not carry it.

    1.61.1
  • The compiled wrappers fill defaults with the module's own applyDefaults (exported by modules from 1.58.0 on when the schema has a default, the same pass as the runtime's), so the defaults engine is no longer in a bundle that wraps current modules: the verdict wrapper went from 2.7 to 1.6 KB gzipped, the full one from 13.8 to 12.8. A module from before 1.58.0, or without its version stamp, takes the runtime's pass as before; in the browser build such a module with defaults is refused with an error that says to rebuild it rather than run without its defaults. tests/test_compiled_defaults_source.js.

    1.61.1
  • The standalone module's error collector (the default form, with schema-source frames) reads each property once into a local, names a closed object's declared keys outright instead of a Set lookup per key when there are at most eight, and reuses the node's plain-object flag in that walk instead of reading the prototype again. A rejected ten-item order with its error list built went from 378 to 244 ns on Node (the one-pass form, 102, is unchanged and still opt-in for its size); the module's size does not move. Found by profiling line by line: one closedness walk held 31% of the samples.

    1.61.0

13 internal lines (docs, tests, tooling) left out.