All notable changes to ata-validator are documented here. The format follows Keep a Changelog, and this project adheres to semantic versioning.
- The Standard Schema surface carried no output type.
~standard.validate()returned{ value: unknown }and thetypescarrier the specification defines for inference was missing, so every consumer that reads the validated type off~standard(Fastify, tRPC, TanStack Form, Drizzle) sawunknownand needed a cast.~standardis now typed against the validator's own data type, andtypes.outputcarries it. Type-only: the runtime object is unchanged, and the specification definestypesas never present at runtime. - Boolean schemas were rejected by the
Validatorconstructor's TypeScript signature.trueandfalseare schemas anywhere JSON Schema allows one, and both have always worked at runtime; only the types disagreed, which made a schema of unknown shape (object | boolean) impossible to pass without a cast. Nested boolean subschemas are still typed as objects, so{ properties: { a: true } }needsdefineSchemaor a cast. - The
tbuilder's option bags rejected vendor keywords.t.object({}, { instanceof: 'Date' })is what a custom keyword package expects to be given, and the option types only allowed the keywords the builder itself emits, so callers wroteas never. Every option bag now accepts unknown keywords alongside the typed ones. tests/test_interop_types.tscovers all three undertsc --noEmit.
- A
$refinto another document whose own root carries a fragment-only$refvalidated everything. The code generator has three entry points and only one of them, the boolean path, ran the bail that routes a reference it cannot follow to the interpreted engine. The other two emitted no lines for the reference and returned the empty program as always-valid, so every constraint behind it was dropped without an error.{ "$ref": "other.json" }against a document holding its constraints under#/$defs/...accepted any input at all. Both paths now run the same two bails the boolean path runs. This is the third silent-accept defect in$refresolution after the two fixed in 1.3.0, and it affects Draft 2020-12 as well as the v1 dialect. tests/test_cross_doc_root_ref.jsstates the case directly, including a cross-document reference to a target that holds its constraints inline, which must stay on the compiled path so the bail is not widened into "any cross-document reference".
- The official test suite moved on five months, from the March snapshot to 6 August. Every published figure is remeasured against it. Draft 2020-12 is 1294 of 1299, draft 7 is 916 of 927, and the v1 dialect is 1131 of 1133. With code generation blocked, Draft 2020-12 is 1295 of 1299 and v1 is 1132 of 1133. One case that the fix above corrects is no longer a known failure under v1.
- The buffer path disagrees with
validate()on 245 of 2222 suite cases rather than 243 of 2208. The two additional disagreements are new suite cases, not a widening: measured against the March snapshot the number is still exactly 243.
- The JSON Schema v1 dialect. A schema declaring
"$schema": "https://json-schema.org/v1", or the datedhttps://json-schema.org/v1/2026the specification repository's meta-schema carries, is now validated under v1 rather than under 2020-12. The difference ata implements is$dynamicRef: v1 removes the bookending requirement, so a reference resolves through the dynamic scope whether or not the schema it initially lands on carries a matching$dynamicAnchor, and also when it resolves to nothing on its own. The outermost matching anchor still in scope wins, as before.propertyDependencies, the other v1 addition, shipped in 1.5.0. - Against the suite's
v1directory with nothing excluded, ata scores 1123 of 1127. The four it misses are the same four that fail on 2020-12: one$dynamicRefscope corner each engine misses, a definition validated against the meta-schema, and two remote-reference cases.npm run test:suitenow runs the dialect alongside 2020-12 and draft 7,tests/test_no_eval.jsruns it with code generation blocked (1124 of 1127 there), andtests/test_v1_dialect.jschecks the switch itself: the same document must not validate the same way under both dialects. - Only
$dynamicRefrouting changes. Everything else ata implements is identical under v1 and 2020-12, so a v1 schema that does not use the keyword takes the same compiled path it always did. One that does use it validates on the interpreted engine, since the JS compiler and the native addon both resolve the 2020-12 way. The native engine does not implement bookending at all, which is invisible to the official 2020-12 suite but means it cannot be trusted to answer for either dialect here.
isValid()on a buffer disagreed withvalidate()on the parsed value for 294 of 2208 cases in the official suite, in both directions. Two causes: the path returned the code generator'sfalsedirectly, which is ambiguous between "invalid" and "the plan stopped at a composition opcode and the walker should finish", so every schema usingallOf,anyOf,oneOfor$refwas rejected outright; and it ended in a second, simpler walker that had drifted from the onevalidate()uses. It now calls the same walker with all errors off, so there is one set of semantics rather than two kept in step by hand. The disagreement drops to 243 cases, the rest being the on-demand plan answering before the walker runs, which is engine work rather than a setting.tests/test_buffer_path_parity.jsrecords that number so it cannot widen, and the README and the edge runtimes guide now state the gap, sinceisValid,countValidandbatchIsValidare shipped APIs and a caller has no way to know otherwise.
ATA_NO_MIMALLOCskips the bundledmimalloc-new-delete.hinclude. A toolchain that ships the mimalloc headers along with its ownoperator new/deleteover the same allocator hits a duplicate symbol at link time; Emscripten with-sMALLOC=mimallocis that case, so the source could not be compiled to WebAssembly at all. The native build does not define it and is unchanged.
propertyDependencies, a JSON Schema v1 proposal, selects a subschema by the value of a property rather than by its presence. It replaces theoneOfandif/thenpatterns normally used to branch on a discriminator field, and reads as{ "propertyDependencies": { "type": { "customer": { ... }, "employee": { ... } } } }. The proposal defines the keyword as equivalent to anif/thenonconst, andtests/test_property_dependencies.jschecks that equivalence case by case rather than asserting a separate expectation, alongside the proposal's own test files from the official suite: 36 of 38, the two remaining being a$dynamicRefscope gap theif/thenform of the same schema hits identically.- The keyword is implemented in the interpreted engine. Both JS compiler paths decline a schema that uses it rather than emitting nothing for a keyword they do not know, which would make the constraint vacuous.
- Where
new Functionis unavailable, validation now runs on the interpreted engine instead of degrading quietly. Cloudflare Workers, Deno Deploy and pages under a strict Content-Security-Policy refuse code generation; the closure-based path does not callnew Functionitself, so it survived the refusal and went on to handle schemas it gets wrong. Against the full suite with code generation blocked, ata scored 1188 of 1290 with 30 schemas failing to build. It now scores 1286 of 1290 with none, which is the same as the compiled path within one case. Draft 7 is 910 of 922.tests/test_no_eval.jsruns the whole suite withevalandnew Functionblocked so this is checked rather than assumed.
useDefaultsandassertFormatvalidator options, both defaulting totrueso existing behavior is unchanged.useDefaults: falsestops missing properties being filled in from theirdefaultbefore validation, which matters because adefaultthat does not satisfy its own subschema currently makes an otherwise valid instance fail.assertFormat: falsetreatsformatas an annotation rather than an assertion, the Draft 2020-12 reading when the format-assertion vocabulary is not in use. With both off, ata passes every case in the suite'sformatanddefaultfiles.
- A
$refthat could not be resolved validated everything instead of failing. The JS compiler emitted no check at all for an unresolved reference, so a typo in a$ref, or a reference into a schema that was never registered, silently turned off every constraint behind it rather than reporting an error. Both compiler paths now decline to compile such a schema and it validates on the interpreted engine, which reportsATA5001naming the reference it could not resolve. - References that resolve relative to an enclosing base URI are no longer compiled by the JS paths, which match registry entries by exact key or path suffix and have no notion of a base. Nested
$idscopes, relative references, and documents registered under a URI different from the$idthey declare now route to the interpreted engine, which tracks the base properly. Schemas that reference a flat registry of ids, the common$ref: 'shared#'shape, still take the compiled fast path. - A schema supplied through the
schemasoption as a URI-keyed record was registered only under the$idit declared, so references to the URI it was registered under could not resolve. It is now addressable by both. - Official draft 2020-12 remote-reference suite: 30 of 31 cases, up from 21. Pure-JS configuration on the full suite: 1189 of 1190, up from 1188. The native configuration stays at 1190 of 1190.
- The
uriformat was too permissive: it only checked for a scheme prefix, so a string likehttps: not a urlpassed. It now also rejects any whitespace or control character, so values that carry a scheme but are not URIs are caught. Real URIs, includingmailto:andurn:forms and hyphenated hosts, still pass.uri-referencenow rejects the same characters and accepts the empty string (a valid same-document reference), which the codegen path had wrongly rejected. date-timenow accepts lowercasetandzseparators per RFC 3339, matching the interpreted engine.durationnow rejects a trailingTwith no time component (PT,P1DT), which are not valid ISO 8601 durations.- These format checks are emitted in two places, the JS compiler and the interpreted engine, and had drifted apart on the cases above. A new differential test (
tests/test_format_engine_parity.js) runs every built-in format through both engines over a shared corpus and fails on any disagreement, so the two stay in lockstep.
- Schemas the JS compiler cannot represent now validate on the engine that gets them right, instead of always preferring the native addon when it is installed. The native resolver mishandles several
$idbase-URI corners (URN bases, absolute-path references, empty JSON-pointer tokens, nested$idscopes) and silently skips regex patterns its engine cannot parse; all of these now route to the interpreted engine. Pure dynamic-ref schemas stay on the native path, which tracks$dynamicRefscopes more completely. Buffer and parallel APIs are unchanged. - The JS compiler no longer compiles two shapes it got wrong:
unevaluatedItemswithcontainsin scope (contains-matched items were never credited as evaluated, rejecting valid arrays) and plain-anchor schemas that open nested$idscopes (same-named anchors in different base-URI scopes resolved to the wrong target). Both now go to the interpreted engine. - Official draft 2020-12 suite: 1190 of 1190 applicable cases with the native accelerator (up from 1175), 1188 of 1190 (99.8%) pure-JS (up from 1184). Draft 7 suite gains four
ref.jsoncases.
- An interpreted engine (
lib/interpreter.js) now backs schemas the JS compiler cannot represent when the native addon is absent (browser, edge workers,ATA_NO_NATIVE=1). These schemas validated only with the native engine before, and 1.1.0 made them throw a clear error in native-less environments; they now just validate. Full draft 2020-12 semantics:$id/$anchorresolution,$dynamicRefdynamic scoping, annotation tracking forunevaluatedProperties/unevaluatedItems. The pure-JS configuration now passes 1184 of 1190 applicable cases (99.5%) in the official test suite, up from 974, and the six remaining failures are shared with the native engine. Error results on the native-less path also carry full per-keyword detail now instead of a single generic message.
- Validation errors now follow the schema's keyword declaration order instead of a fixed required-first order: a schema declaring
propertiesbeforerequiredreports the property errors first, matching what schema authors read top to bottom and what the previous default validator emitted. Order within one keyword is unchanged (requirederrors still follow the array). Single-error andabortEarlyresults are untouched. With this change ata passes every applicable test in Fastify's validation suite (181 of 187; the remaining 6 test the incumbent validator's own extension API rather than validation behavior).
- TypeBox-style modifier combinators on
ata-validator/t:t.pick,t.omit,t.partial,t.required,t.composite, andt.recursive. All six emit plain JSON Schema, soInfer, the runtime validator, and the AOT pipeline consume them with no adapter. This closes the authoring-parity gap for TypeBox migrations. Note:t.recursiveschemas validate through the interpreted engine and are not AOT-precompilable; the other five combinators AOT-compile like any schema.
- The
ata-validatorpackage is now pure JavaScript: no bundled binaries, no vendored C++ sources, no install script. The native engine moved to per-platform@ata-validator/native-*packages, installed automatically as optional dependencies (the same pattern Vite uses for esbuild). The tarball shrinks from ~5.3 MB to under 300 KB.npm install --omit=optionalorATA_NO_NATIVE=1gives a guaranteed zero-binary setup; validation behavior is identical for every schema shape the JS engine compiles, and the few shapes that still need the native engine now throw a clear error instead (see Fixed below).
- Schemas the JS engine cannot compile (some
$dynamicRef, cyclic$ref, and unusual keyword interactions) crashed withMaximum call stack size exceededin environments without the native addon: the lazyvalidatestub and the rich-error wrapper dispatched to each other forever. These schemas now throw a clear "not supported by the pure-JS engine" error on first use. The same cycle could hitisValidObjecteven with the native addon present when it was the first method called; it now falls through to the full compile and validates correctly.
- Cross-schema
$refpointers into#/definitions/...resolved to nothing after draft-07 normalization renamed the target to$defs, and the generated validator silently accepted invalid data. The pointer walk now treatsdefinitionsand$defsas aliases. - Schemas passed by the caller (the
schemasoption,addSchema(), and the root schema) were normalized in place, mutating objects the caller still owns. Anyone reusing those objects afterwards, such as Fastify handing the same shared schema to its serializer, saw corrupted keys. Normalization now works on a copy; caller objects are never touched. - The new copy preserves symbol-keyed markers, so
t.refinerefinements survive normalization andvalidateAsynckeeps enforcing them.
version()reported 0.10.4 on platforms with a native prebuild: theATA_VERSIONconstant ininclude/ata.hhad not been bumped since 0.10.4 and the native answer takes precedence overlib/version.js. The header now carries the real version and the version-sync test checks it, so it cannot drift again.
1.0 is a stability commitment, not a feature release. The API surface, the error result shape, and the error code registry are now covered by the semver guarantees in docs/STABILITY.md.
Validator.prototype.toStandalone()andValidator.prototype.toStandaloneModule(), deprecated in 0.22.0. UsetoStandaloneModule()/bundleStandalone()/bundleCompact()fromata-validator/build. See docs/migration-to-1.0.md.
- Node.js 20 or newer is required. Node 18 reached end of life in April 2025.
- docs/STABILITY.md: semver, deprecation, and error-code guarantees.
- README "Known limitations" section documenting the deliberate 1.x scope edges.
Validator.prototype.toStandalone()andValidator.prototype.toStandaloneModule()now emit a one-time DeprecationWarning. Both will be removed in 1.0. The replacements have been stable since 0.19:toStandaloneModule()/bundleStandalone()/bundleCompact()fromata-validator/build, and theValidator.bundle*()statics.
errorMessagekeyword for custom error messages. A string on a subschema replaces the message for any failing keyword there; an object overrides per keyword, withrequiredkeyed by missing property name and_as fallback.code,keyword, andpathfields are untouched. Schemas withouterrorMessagepay nothing; the override pass is only installed when one is present.- Async refinement:
t.refine(schema, fn, { message, path })attaches an async (or sync) check that runs throughvalidateAsync/parseAsyncafter structural validation passes.new Validator(schema)ignores the refinement marker, so plain structural validation is unchanged. Failing refinements surface as errors withkeyword: 'refine'.
JSONSchema.itemsnow acceptsbooleanso the typedt.tuple([...])output (which setsitems: falseto close the tail) type-checks againstJSONSchemawithout a constraint error.items: falseis valid JSON Schema and the runtime already honoured it; the type definition just had not been widened. Anyone consumingt.tuplefrom outside a project withskipLibCheckran into aTTuple incorrectly extends JSONSchemaerror.
-
New chainable schema builder at
ata-validator/t. Eacht.X(...)returns a plain JSON Schema literal, so the output drops straight intonew Validator(...),defineSchema,Infer<S>, and the AOT pipeline with no adapter. The migration target is TypeBox: renameimport { Type } from '@sinclair/typebox'toimport { t } from 'ata-validator/t'and keep the same authoring shape while picking up ata's runtime and AOT precompile.import { t } from 'ata-validator/t' import { Validator, type Infer } from 'ata-validator' const User = t.object({ id: t.integer(), name: t.string({ minLength: 1 }), email: t.optional(t.string({ format: 'email' })), role: t.union([t.literal('admin'), t.literal('user')]), }) type User = Infer<typeof User> const v = new Validator(User)
Covered: primitives (
string,number,integer,boolean,null), composites (objectwithoptionalkeys,array,tuple,record,union,intersect,literal,const,enum), and refs (ref). Optionality is carried by a Symbol-keyed marker that the emitted JSON Schema,Object.keys,JSON.stringify, and ata's codegen never see; the parentt.objectreads it to computerequired.
Infer<S>now resolves object schemas withoutpropertiesbut with a schema-valuedadditionalPropertiestoRecord<string, V>instead ofRecord<string, unknown>. Closes the last common JSON Schema shape that was not inferred.
ata-validator/buildnow exports the AOT primitivesbundleStandalone,bundleCompact, andtoStandaloneModuleas named functions, so callers that want the build surface in one place (bundler plugins, build scripts) no longer have to go through theValidatorclass. Same code paths as the Validator-bound forms, no behaviour difference.- New top-level
ARCHITECTURE.mdreference document covering design principles, runtime dispatch, AOT pipeline, error enrichment, the two TypeScript paths, and the native layer.
- Internal refactor: AOT (
toStandalone,toStandaloneModule,bundle,bundleStandalone,bundleCompact,loadBundle) lives inlib/aot.js, the native addon loader inlib/native-load.js, the version string inlib/version.js.index.jslazy-requires the AOT module so a plain import never pays for code it does not call. The browser bundle dropspkg-prebuilds,__dirname, andpackage.json(with its dependency strings) entirely; it is roughly 15 KB smaller and contains no Node-only identifiers outside comments. - The safe-regex engine is now embedded into standalone output from a baked string (
lib/safe-regex-source.js, generated fromlib/safe-regex.js) instead of a runtimefs.readFileSync. Browser AOT calls (Validator.bundle,toStandaloneModule, …) work in any bundler without an fs polyfill. A structural test (tests/test_browser_imports_guard.js) bundles both entries with esbuild and asserts noreadFileSync,pkg-prebuilds, or__dirnamesurvives outside comments; sync tests catch drift between the bundled strings and their sources.
- The browser and edge build no longer touches the filesystem at import. The safe-regex engine source was embedded into standalone output through a
fs.readFileSyncthat ran at module load, which crashed bundlers that stubfs/pathfor the browser (a regression from 0.17.3). The read is deferred to the first standalone compile that actually embeds the engine, so importing ata, validating, generating types, and compiling pattern-free schemas now run with no filesystem access in browsers and Cloudflare Workers. Added a regression test that bundles the browser entry and runs it withfs/pathstubbed.
- The browser entry (
index.browser.mjs) re-exportstoTypeScript, so the inferred TypeScript type for a schema can be generated client-side (for example in a web playground) alongsideValidator.toStandaloneModule(). Pure re-export, no runtime change.
Infer<S>resolves the shapes 0.17.0 left asunknown.anyOfandoneOfmap to unions,allOfto an intersection,prefixItemsto a tuple, and a$refto a local#/$defs/...or#/definitions/...entry resolves to the referenced type, including recursive references. An external or otherwise unresolvable$refstill resolves tounknownrather than erroring.new Validator(schema)carries the wider inference, so handlers narrowresult.datafor these schemas with no manual annotation, and the same applies to the Fastify type provider that builds onInfer. Pure.d.tschange, no runtime impact.
- Compiled validators resolve draft-07 plain-name anchors. A
$defs/definitionsentry that declares an anchor with$id: "#name"and is referenced by$ref: "#name"now compiles through the codegen on every path (boolean, error, combined) instead of bailing. The bail forced a fallback that could not resolve sibling cross-schema refs, which surfaced ascannot resolve $ref. This is how shared schemas are referenced under Fastify.
- Standalone output now embeds user-supplied format functions.
toStandaloneModuleandbundleCompactreferenced the_uf_<name>format helpers without declaring them, so the generated module threw_uf_<name> is not definedon the first validation, and their error path skipped the custom format entirely (sovalidatedisagreed withisValid). Both now serialize the format functions viaFunction#toStringand run them on the error path too, matchingbundleStandalone. - Compiled validators report per-property errors for schema-valued
additionalProperties. The AOT error path previously emitted a single genericvalidation failed; it now validates each undeclared property against the subschema and reports the precise/<key>error, matching the runtime validator. ata --version(and-V) prints the CLI version instead of failing withunknown command.
- User-supplied
pattern,patternProperties, andpropertyNamesregexes now run through a linear-time matching engine, so a crafted schema or input can no longer trigger catastrophic backtracking (ReDoS). Patterns the engine cannot represent, such as those using backreferences, fall back to the nativeRegExp. The built-informatchecks (email,uri,uri-reference,hostname,ipv4,ipv6,date,date-time,time,duration,uuid) were routed through the same engine and stay linear on adversarial input.
validate()now returns the typeddataon success, the validated input after any in-place coercion or defaults. TheValidationResult<T>type has carrieddata: Tsince 0.17.0, but the runtime never populated it, soresult.datawasundefined.isValidObject()andabortEarlystay allocation-free for callers that only need a boolean.
- Standalone output for schemas with
anyOforoneOfno longer references undefined branch helpers.toStandaloneModuleandbundleCompactnow emit the hoisted branch functions, so the generated module runs instead of throwing on the first validation.bundleStandalonealready emitted them.
- Static type inference from JSON Schema literals. The new exported
Infer<S>type maps a schema literal to its data type, andnew Validator(defineSchema({...}))now returnsValidator<Infer<S>>, sovalidate()narrowsresult.datawith no manual type annotation. Write plain JSON Schema, get the type for free, no builder DSL. Covers primitives, type-array unions,const,enum, objects (required vs optional keys), and arrays;$ref, tuples, andanyOf/oneOfinferunknownfor now. Pure.d.tschange, no runtime impact.
validateAndParse()is now implemented in JavaScript (JSON.parsethen validate) and returns{ valid, value, errors }. It previously called a native method that does not exist and threw on every call. It now works with or without the native addon and in the browser; malformed JSON returnsvalid: falsewith anATA9001error instead of throwing.
defineSchemahelper and the exportedJSONSchematype. Wrap a plain schema object indefineSchema(...)to author it inline in TypeScript with keyword autocomplete and value checking, noas constneeded. It is an identity function at runtime, so the returned object drops straight intoValidator,toStandaloneModule, and the rest of the API. Requires TypeScript >= 5.0 for theconsttype parameter.- OpenAPI
nullablekeyword.{ type: 'string', nullable: true }acceptsnullalongside the declared type, matching OpenAPI 3.0 schemas.
coerceTypeswithtype: 'array'wraps a scalar into a single-element array instead of leaving it unchanged.- Codegen resolves a
$defsentry that carries a fragment$idand is reached through a pointer$ref. - Preprocessing (defaults, coercion,
removeAdditional) guards againstnulland non-object data instead of throwing.
- Coercion, defaults, and
removeAdditionalnow follow a cross-schema$refto the referenced shape. A whole-schema reference like{ $ref: 'shared#' }(used for shared route schemas) or a property reference like{ id: { $ref: 'shared#/properties/id' } }is preprocessed instead of skipped. - The compile cache now keys on referenced schema content, not just the
$id. Two validators that share a root schema string and an$idpointing at different schemas no longer reuse the wrong compiled function.
- Compiler-grade error output. Every validation error now carries a stable
code(ATA####), anexpected/receivedpair, adocUrl, and, when the input came in as a JSON string or Buffer, adataFramepointing at the offending bytes. The full registry of 46 codes lives atdocs/error-codes.mdwith permalinks athttps://ata-validator.com/e/<CODE>. - Renderer API.
renderPretty,renderCompact, andrenderJSONare exported fromata-validator. Pretty output mirrors rustc-style code frames with carets, help, and note lines; compact collapses to one line per error; JSON is structured for tooling. ata validatesubcommand.ata validate <schema> <data>runs a schema against a JSON data file and prints renderer output. TTY auto-renders pretty; pipes default to compact;--format=jsonreturns structured output.--pretty,--compact,--max-errors,--color,--no-colorcover the rest of the surface.- Runtime source maps.
new Validator(schema, { source: { path, content } })attaches per-errorschemaSource(file, line, col, text) by re-parsing the schema with a position-aware scanner. - AOT source maps. AOT-compiled validators carry the structured error fields (
code,docUrl) and embed per-errorschemaSourcewhen built with the source map enabled. On by default in development, off whenNODE_ENV=productionor--no-sourceis passed. ata compile/ata buildflags. New--source/--no-sourceflags.ata build --dualemits both a source-mapped artifact (*.compiled.mjs) and a stripped one (*.compiled.min.mjs) in a single run.- Size budget gate.
npm run bench:sizeenforces a gzipped-byte budget over the AOT codegen output to catch silent bundle bloat. Baseline atbenchmark/baselines/aot-size.json, gates derive from the baseline with 1.5x headroom. oneOf/anyOfcollapse. Branching failures collapse to a single best-branch error (ATA4001/ATA4002/ATA4003) instead of the full branch-tree. The closest matching variant's errors are still available underbranchErrors.allOferrors continue to surface every failing branch.- Suggestions. A new
suggestionfield nudges users when ata is confident: typo against enum, missing-required typo, format-violation hint, type-coercion nudge. Runtime validators populate automatically; AOT validators exposeattachSuggestions(errors, data)to keep AOT bundles small. richErrors: falseopt-out.new Validator(schema, { richErrors: false })preserves the v0.14 error shape byte-for-byte.abortEarly: truecontinues to short-circuit; the returned error carriescode: 'ATA9000'and no enrichment.release:checknpm script. Runs the prebuilds, doc-coverage, and error-code lockfile checks in strict mode before a publish.
prepublishOnlynow chainscheck-prebuilds,check-doc-coverage(lenient until per-code prose lands), and the error-code lockfile test.ata compileandata buildfailures route through the renderer with codeATA9002, so command-line schema errors look the same as runtime ones.
- Log scrapers: errors now carry
code,dataFrame,suggestion, anddocUrlfields. If you serializeresult.errorsdirectly into logs, line size will grow. PassrichErrors: falsefor the v0.14 shape, or pipe throughrenderCompactfor a stable one-line format. - AOT bundle size: source-mapped variants (
.compiled.mjs) add up to 200 bytes gzipped for a 10-field schema. Production builds (NODE_ENV=productionor--no-source) emit the no-source variant. Useata build --dualto emit both. - Fastify: a companion
fastify-atarelease wires the new format into route error responses.
- Generic
Validator<T>with type predicateisValidObject(data): data is T. Pairs naturally with TypeBox, Zod-from-JSON-Schema, Valibot, or hand-written types over JSON Schema literals. ValidationResult<T>andValidateAndParseResult<T>are discriminated unions. On thevalid: truebranch the parsed data is typed; onvalid: falsetheerrorsarray carries the diagnostic information.
- Type-level only: accessing
result.data(orresult.valueonvalidateAndParse) without first checkingresult.validis now a TypeScript compile error. Runtime behavior is unchanged. The previous shape returnedundefinedin that position, so this surfaces an existing latent bug at compile time.
- Pure
.d.tschange. No JS, C++, AOT, or CLI behavior is affected. Bundle size unchanged. Runtime performance unchanged.
- macOS arm64 prebuild shipped with an invalid code signature. The release workflow runs
pkg-prebuilds-copy --strip, which on macOS runsstrip -Sxon the addon.striprewrites the Mach-O and invalidates the ad-hoc signature the linker applied, and it does not re-sign. arm64 macOS refuses to load unsigned code, sorequire('ata-validator')was killed withSIGKILL (Code Signature Invalid)the moment the binding loader called into the addon. Only0.12.6reached users this way because it was the one release published through CI rather than locally. The workflow now re-signs and verifies the macOS prebuild afterstrip, andcodesign --verifygates the job so a broken signature cannot ship. Fixes #23. - macOS x64 prebuild was never produced. The prebuild matrix used
macos-14for the x64 leg, butmacos-14runners are Apple Silicon only, so that leg built an arm64 binary mislabeled as x64 and nodarwin-x64prebuild ever ended up in the tarball.
- macOS x64 prebuild.
macos-13GitHub runners, the only ones that build x64 natively, are no longer reliably available. Since no published version ever shipped a workingdarwin-x64prebuild, this is not a regression. Intel Mac users fall back to the JS engine, which still works, only the buffer APIs are slower.
prepublishOnlynow blocks tarballs missing platform prebuilds. A localnpm publishonly carries the publisher's own platform, silently dropping every other prebuild. Publishing now fails unless all seven platform prebuilds are present, and when run on a Mac it also verifies the darwin code signatures.
Validator.bundleStandalonedropped hoisted anyOf/oneOf branch helpers from the bundle output. Schemas whose codegen hoists branch functions like_af1_b0to the per-schema preamble (e.g. allOf wrapping an anyOf, or schemas pulled into a cross-$refbundle) emitted JS that referenced these helpers without defining them, so loading the bundle threwReferenceError: _af1_b0 is not definedon first validation. The standalone preamble now propagates through to the bundle alongside the format-closure serialization that was already there.toStandalone(single-schema) was unaffected. Fixes #24.
- Invalid validation crashed in environments without the native addon (Cloudflare Workers, browsers, Bun without N-API). When the JS error-codegen probe couldn't produce a safe error function,
errFnfell through tothis._compiled.validate(d). With no native addon_compiledstaysnull, so the call threwTypeError: Cannot read properties of null (reading 'validate'). Valid inputs were unaffected because they short-circuited before reachingerrFn. The fallback now stays on a JS-only path whennativeisn't present, returning the boolean result with a generic detail-not-available error so callers see{ valid: false, errors: [...] }instead of a crash. Addedtests/test_no_native.js(Workers-style sandbox) to lock the behavior. Fixes #22.
- Custom format checkers in
validate()are now actually applied. The combined codegen path (used byValidator#validateand one-shotvalidate()) silently dropped theuserFormatsargument, so schemas withformat: <user-defined>returned{ valid: true }regardless of the checker function's return value. The boolean (isValidObject) and error-only paths were already wired correctly. Fixes RJSF integration where custom formats are routed throughcustomFormats(rjsf-team/react-jsonschema-form#5052). - Glob patterns with backslash separators on Windows now resolve correctly in
ata build. The Node 18 fallback regex only recognized forward slashes, sopath.join(dir, '*.json')produced patterns the matcher couldn't parse onwindows-latestrunners.
ata build <glob>subcommand for project-wide AOT compilation. Compiles each matched schema to a per-file.compiled.mjsESM module with a sibling.d.mtsTypeScript declaration. Production bundles can drop the runtime ata-validator dependency entirely and import compiled validators as plain ESM modules.ata-validator/buildprogrammatic subpath export.import { build, watch } from 'ata-validator/build'exposes the same engine the CLI uses, so build pipelines and bundler plugins can integrate without going through the CLI.- CLI flags for
ata build:--out-dir,--suffix,--format esm|cjs,--abort-early,--no-types,--cache-file,--check,--watch,--max-size,--strict. - Incremental cache via content-hashed
--cache-file. Second run on unchanged inputs skips compilation. - YAML schema support when the
yamlpeer dependency is installed (optional)..yamland.ymlinputs parse the same as.json. - AOT vs AJV-runtime benchmark at
benchmark/bench_aot_vs_ajv.mjs. On the included fixtures, ata-AOT outputs are 25-56x smaller gzipped than the AJV runtime, cold start is ~2x faster, throughput is 2-4.5x faster, and compile time is 71-246x shorter.
- Standalone modules now correctly serialize closure-bound helpers (RegExp, Set, sub-validator functions, branch-property arrays) into the emitted
.mjs. Previously, schemas usingpatternProperties,propertyNameswith regex, orunevaluatedPropertieswithanyOf/oneOfproduced standalone output that referenced undefined variables (_ppf0_0,_re*,_es*,_bk*) and threwReferenceErrorat runtime. The runtime validation path was unaffected.
- The runtime
ValidatorAPI and theata-validator/compatAJV-shim remain unchanged. Existing dynamic-schema users have no migration to do. - Bundler plugins (ata-vite v0.2.0, ata-webpack, ata-codemod-ajv) are out of scope for this release and will land in 0.14.0+.