Skip to content

Releases: ata-core/ata-validator

v0.21.0

Choose a tag to compare

@mertcanaltin mertcanaltin released this 01 Jun 19:51

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

Choose a tag to compare

@mertcanaltin mertcanaltin released this 27 May 17:24

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

Choose a tag to compare

@mertcanaltin mertcanaltin released this 27 May 16:52

Added

  • New chainable schema builder at ata-validator/t. Each t.X(...) returns a plain JSON Schema literal, so the output drops straight into new Validator(...), defineSchema, Infer<S>, and the AOT pipeline with no adapter. The migration target is TypeBox: rename import { Type } from '@sinclair/typebox' to import { 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

Choose a tag to compare

@mertcanaltin mertcanaltin released this 27 May 16:30

Internal refactor and small surface-area additions. No breaking changes.

Added

  • ata-validator/build now exports bundleStandalone, bundleCompact, and toStandaloneModule as 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.md covering design principles, the three-tier runtime dispatch, the AOT build pipeline, error enrichment, the two TypeScript paths (Infer<S> versus ts-gen), and the native layer.

Changed

  • AOT (toStandalone, toStandaloneModule, bundle, bundleStandalone, bundleCompact, loadBundle) lives in lib/aot.js. The native addon loader lives in lib/native-load.js with a browser stub. index.js lazy-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, or package.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 from lib/safe-regex.js) instead of a runtime fs.readFileSync. Browser AOT calls work in any bundler without an fs polyfill.

Tests

  • tests/test_browser_imports_guard.js bundles both entries with esbuild and asserts the output never contains readFileSync, pkg-prebuilds, or __dirname outside comments.
  • tests/test_version_sync.js and tests/test_safe_regex_source_sync.js catch drift between the bundled strings and their sources.

v0.18.2

Choose a tag to compare

@mertcanaltin mertcanaltin released this 25 May 21:33

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

Choose a tag to compare

@mertcanaltin mertcanaltin released this 25 May 21:11

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

Choose a tag to compare

@mertcanaltin mertcanaltin released this 25 May 15:19

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

Choose a tag to compare

@mertcanaltin mertcanaltin released this 25 May 11:43

Fixed

  • Compiled validators resolve draft-07 plain-name anchors. A $defs/definitions entry 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 as cannot resolve $ref. This is how shared schemas are referenced under Fastify.

v0.17.4

Choose a tag to compare

@mertcanaltin mertcanaltin released this 25 May 11:43

Fixed

  • Standalone output now embeds user-supplied format functions. toStandaloneModule and bundleCompact referenced the _uf_<name> format helpers without declaring them, so the generated module threw _uf_<name> is not defined on the first validation, and their error path skipped the custom format entirely (so validate disagreed with isValid). Both now serialize the format functions via Function#toString and run them on the error path too, matching bundleStandalone.
  • Compiled validators report per-property errors for schema-valued additionalProperties. The AOT error path previously emitted a single generic validation 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 with unknown command.

v0.17.3

Choose a tag to compare

@mertcanaltin mertcanaltin released this 25 May 11:43

Security

  • User-supplied pattern, patternProperties, and propertyNames regexes 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 native RegExp. The built-in format checks (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.