Releases: ata-core/ata-validator
Release list
v0.21.0
Two additions that round out the validation surface: custom error messages and async refinement.
Custom error messages
A subschema can now carry an errorMessage keyword to override the message on the errors it produces. A string replaces the message for any failing keyword on that subschema; an object overrides per keyword, with required keyed by the missing property name (or a single string) and _ as a fallback. The code, keyword, and path fields are untouched, so existing renderers and log dashboards keep working. The override only runs when a schema actually declares errorMessage, so schemas without it pay nothing.
const v = new Validator({
type: 'object',
properties: {
age: { type: 'integer', minimum: 18, errorMessage: { minimum: 'must be 18 or older' } },
},
required: ['age'],
errorMessage: { required: { age: 'age is required' } },
})
v.validate({ age: 5 }).errors[0].message // 'must be 18 or older'Async refinement
JSON Schema is synchronous, so checks that need to await (a uniqueness lookup, a remote call, a cross-field rule) attach to a schema through t.refine on the ata-validator/t builder and run via the new validateAsync / parseAsync. The refinement rides on a Symbol marker that the emitted JSON Schema and the codegen never see, so new Validator(schema) still does plain structural validation and ignores it. validateAsync runs the structural pass first and awaits the refinements only when the value is structurally valid, so a refinement body can assume the right shape.
import { t } from 'ata-validator/t'
import { validateAsync } from 'ata-validator'
const Signup = t.refine(
t.object({ username: t.string({ minLength: 3 }) }),
async (value) => !(await usernameTaken(value.username)),
{ message: 'username is already taken', path: '/username' },
)
const r = await validateAsync(Signup, body)Refinements compose by wrapping again, a check may be sync or async, and a failure surfaces as an error with keyword: 'refine' carrying the supplied message and path. parseAsync resolves to the validated value or throws with the errors attached.
Both features keep the existing validate path synchronous and allocation-free, and neither changes behaviour for schemas that do not use them.
v0.20.1
Patch fix for the typed t.tuple API: JSONSchema.items now accepts boolean so TTuple<S> (which sets items: false to close the tail) extends JSONSchema cleanly. The runtime already honoured items: false; the type definition just had not been widened. Anyone consuming t.tuple from outside a project that uses skipLibCheck ran into a TTuple incorrectly extends JSONSchema error in TypeScript.
v0.20.0
Added
-
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.```ts
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
const v = new Validator(User)
```Covered: primitives (`string`, `number`, `integer`, `boolean`, `null`), composites (`object` with `optional` keys, `array`, `tuple`, `record`, `union`, `intersect`, `literal`, `const`, `enum`), and refs (`ref`). Optionality is carried by a Symbol-keyed marker that the emitted JSON Schema and ata's codegen never see; the parent `t.object` reads it to compute `required`.
Changed
- `Infer
` now resolves object schemas without `properties` but with a schema-valued `additionalProperties` to `Record<string, V>` instead of `Record<string, unknown>`. Closes the last common JSON Schema shape that was not inferred.
v0.19.0
Internal refactor and small surface-area additions. No breaking changes.
Added
ata-validator/buildnow exportsbundleStandalone,bundleCompact, andtoStandaloneModuleas named functions. Same code paths as the Validator-bound forms, useful when a build script or bundler plugin wants the AOT surface in one place.- Top-level
ARCHITECTURE.mdcovering design principles, the three-tier runtime dispatch, the AOT build pipeline, error enrichment, the two TypeScript paths (Infer<S>versusts-gen), and the native layer.
Changed
- AOT (
toStandalone,toStandaloneModule,bundle,bundleStandalone,bundleCompact,loadBundle) lives inlib/aot.js. The native addon loader lives inlib/native-load.jswith a browser stub.index.jslazy-requires the AOT module so a plain import never pays for code it does not call. - The browser bundle no longer pulls in
pkg-prebuilds,__dirname, orpackage.json. It is about 15 KB smaller and contains no Node-only identifiers outside comments. - The safe-regex engine is 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 work in any bundler without an fs polyfill.
Tests
tests/test_browser_imports_guard.jsbundles both entries with esbuild and asserts the output never containsreadFileSync,pkg-prebuilds, or__dirnameoutside comments.tests/test_version_sync.jsandtests/test_safe_regex_source_sync.jscatch drift between the bundled strings and their sources.
v0.18.2
Fixes the browser and edge build: it no longer touches the filesystem at import. The safe-regex engine source was embedded into standalone output through a fs.readFileSync that ran at module load, which crashed bundlers that stub fs/path for the browser (a regression from 0.17.3). The read is deferred to the first standalone compile that 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.
v0.18.1
The browser entry (index.browser.mjs) re-exports toTypeScript, so the inferred TypeScript type for a schema can be generated client-side (for example in a web playground) alongside Validator.toStandaloneModule(). Pure re-export, no runtime change.
v0.18.0
Infer resolves the shapes 0.17.0 left as unknown.
anyOf and oneOf map to unions, allOf to an intersection, prefixItems to a tuple, and a $ref to a local #/$defs/... or #/definitions/... entry resolves to the referenced type, including recursive references. An external or unresolvable $ref still resolves to unknown rather than erroring.
new Validator(schema) carries the wider inference, so handlers narrow result.data for these schemas with no manual annotation. The Fastify type provider built on Infer gets the same upgrade for free.
Pure .d.ts change, no runtime impact: zero bundle growth, full suite green.
v0.17.5
Fixed
- 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.
v0.17.4
Fixed
- 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.
v0.17.3
Security
- 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.