Skip to content

ata-core/ata-validator

Folders and files

NameName
Last commit message
Last commit date

Latest commit

 

History

382 Commits
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 

ata-validator

Compile JSON Schema files into per-schema ESM modules at build time. Drop the runtime validator from your production bundle. Optional runtime API for dynamic schemas.

npm License

Quick start

npm install --save-dev ata-validator
npx ata build 'schemas/*.json' --out-dir src/generated

In your code:

import { validate, isValid, type User } from './generated/user.compiled.mjs'

if (isValid(req.body)) {
  const user: User = req.body
  // ...
}

The .compiled.mjs modules are self-contained: zero runtime dependency on ata-validator, fully tree-shakeable, with TypeScript types emitted alongside.

Why AOT

Dimension Schema ata-AOT AJV-runtime Difference
Bundle (gzipped) simple 955 B 52.7 KB 56x smaller
Bundle (gzipped) complex 1.6 KB 52.7 KB 32x smaller
Cold start simple 21 ms 38 ms 1.8x faster
Throughput (10M ops) simple 345 Mops/s 116 Mops/s 3.0x faster
Compile time simple 6 µs 1.5 ms 246x faster

Reproduce on your machine with npm run bench:aot-vs-ajv. Numbers measured on Apple M4 Pro, Node 25.2.1.

The wins are largest on bundle size and compile time because AOT moves work from runtime to build time. Throughput and cold start are also faster because the compiled validator is a tight straight-line function with no schema-walk overhead.

When to use the runtime API instead

ata build is for schemas you know at build time. If your schemas are user-supplied at runtime (form builders, no-code platforms, dynamic API ingestion), use the runtime API:

import { Validator } from 'ata-validator'

const v = new Validator(schema)
const result = v.validate(data)

The runtime API is unchanged from previous releases. AJV-shim users continue importing from ata-validator/compat.

Usage

Node.js

const { Validator } = require('ata-validator');

const v = new Validator({
  type: 'object',
  properties: {
    name: { type: 'string', minLength: 1 },
    email: { type: 'string', format: 'email' },
    age: { type: 'integer', minimum: 0 },
    role: { type: 'string', default: 'user' }
  },
  required: ['name', 'email']
});

// Fast boolean check - JS codegen, 15.3M ops/sec
v.isValidObject({ name: 'Mert', email: 'mert@example.com', age: 26 }); // true

// Full validation with error details + defaults applied
const result = v.validate({ name: 'Mert', email: 'mert@example.com' });
// result.valid === true, data.role === 'user' (default applied)

// JSON string validation (simdjson fast path)
v.validateJSON('{"name": "Mert", "email": "mert@example.com"}');
v.isValidJSON('{"name": "Mert", "email": "mert@example.com"}'); // true

// Buffer input (zero-copy, raw NAPI)
v.isValid(Buffer.from('{"name": "Mert", "email": "mert@example.com"}'));

// Parallel batch - multi-core, NDJSON, 13.4M items/sec
const ndjson = Buffer.from(lines.join('\n'));
v.isValidParallel(ndjson);  // bool[]
v.countValid(ndjson);        // number

Cross-Schema $ref

const addressSchema = {
  $id: 'https://example.com/address',
  type: 'object',
  properties: { street: { type: 'string' }, city: { type: 'string' } },
  required: ['street', 'city']
};

const v = new Validator({
  type: 'object',
  properties: {
    name: { type: 'string' },
    address: { $ref: 'https://example.com/address' }
  }
}, { schemas: [addressSchema] });

// Or use addSchema()
const v2 = new Validator(mainSchema);
v2.addSchema(addressSchema);

Options

const v = new Validator(schema, {
  coerceTypes: true,       // "42" → 42 for integer fields
  removeAdditional: true,  // strip properties not in schema
  schemas: [otherSchema],  // cross-schema $ref registry
  abortEarly: true,        // skip detailed error collection on failure (~4x faster on invalid data)
});

abortEarly returns a shared { valid: false, errors: [{ message: 'validation failed' }] } on failure instead of running the detailed error collector. Useful when the caller only needs a pass/fail decision (Fastify route guards, high-throughput gatekeepers, request rejection at the edge).

Build-time compile (ata compile)

The ata CLI turns a JSON Schema file into a self-contained JavaScript module. No runtime dependency on ata-validator, so only the generated validator ships to the browser. Typical output is ~1 KB gzipped compared to ~27 KB for the full runtime.

npx ata compile schemas/user.json -o src/generated/user.validator.mjs

The CLI emits two files: the validator itself and a paired .d.mts (or .d.cts) with the inferred TypeScript type plus an isValid type predicate.

import { isValid, validate, type User } from './user.validator.mjs'

const incoming: unknown = JSON.parse(req.body)

if (isValid(incoming)) {
  // TypeScript narrows to User here
  incoming.id      // number
  incoming.role    // 'admin' | 'user' | 'guest' | undefined
}

const r = validate(incoming)
// { valid: true, errors: [] } | { valid: false, errors: ValidationError[] }

CLI options:

Flag Default Description
-o, --output <file> <schema>.validator.mjs Output path
-f, --format <fmt> esm esm or cjs
--name <TypeName> from filename Root type name in the .d.ts
--abort-early off Generate the stub-error variant (~0.5 KB gzipped)
--no-types off Skip the .d.mts / .d.cts output

For a project with many schemas, ata build <glob> compiles them all in one command:

npx ata build 'schemas/*.json' --out-dir build/validators --check

Run with --watch during development for incremental rebuilds.

Typical bundle sizes (10-field user schema, gzipped):

Variant Size Notes
ata-validator runtime ~27 KB Full compiler + all keywords
ata compile (standard) ~1.1 KB Validator + detailed error collector
ata compile --abort-early ~0.5 KB Validator + stub errors only

Programmatic API if you prefer to script it:

const fs = require('fs');
const { Validator } = require('ata-validator');

const v = new Validator(schema);
fs.writeFileSync('./user.validator.mjs', v.toStandaloneModule({ format: 'esm' }));

Fastify startup (10 routes cold): ajv 12.6ms → ata 0.5ms (24x faster boot, no build step required)

Standard Schema V1

const v = new Validator(schema);

// Works with Fastify, tRPC, TanStack, etc.
const result = v['~standard'].validate(data);
// { value: data } on success
// { issues: [{ message, path }] } on failure

Fastify Plugin

npm install fastify-ata
const fastify = require('fastify')();
fastify.register(require('fastify-ata'), {
  coerceTypes: true,
  removeAdditional: true,
});

// All existing JSON Schema route definitions work as-is

C++

#include "ata.h"

auto schema = ata::compile(R"({
  "type": "object",
  "properties": { "name": {"type": "string"} },
  "required": ["name"]
})");

auto result = ata::validate(schema, R"({"name": "Mert"})");
// result.valid == true

Framework integrations

Copy-paste recipes for the common frameworks. Most need 10-20 lines of glue. See docs/integrations for the full set.

Framework Pattern Recipe
Fastify dedicated plugin fastify-ata
Vite (build-time compile) dedicated plugin ata-vite
Hono async middleware docs/integrations/hono.md
Elysia direct handler check docs/integrations/elysia.md
tRPC Standard Schema V1 input docs/integrations/trpc.md
TanStack Form Standard Schema V1 validator docs/integrations/tanstack-form.md
Express sync middleware docs/integrations/express.md
Koa async ctx middleware docs/integrations/koa.md
NestJS validation pipe docs/integrations/nestjs.md
SvelteKit form action, API route docs/integrations/sveltekit.md
Astro API route, server action docs/integrations/astro.md

Supported Keywords

Category Keywords
Type type
Numeric minimum, maximum, exclusiveMinimum, exclusiveMaximum, multipleOf
String minLength, maxLength, pattern, format
Array items, prefixItems, minItems, maxItems, uniqueItems, contains, minContains, maxContains, unevaluatedItems
Object properties, required, additionalProperties, patternProperties, minProperties, maxProperties, propertyNames, dependentRequired, dependentSchemas, unevaluatedProperties
Enum/Const enum, const
Composition allOf, anyOf, oneOf, not
Conditional if, then, else
References $ref, $defs, definitions, $id
Boolean true, false

Format Validators (hand-written, no regex)

email, date, date-time, time, uri, uri-reference, ipv4, ipv6, uuid, hostname

Building from Source

Development prerequisites

Native builds require C/C++ toolchain support and the following libraries:

  • re2
  • abseil
  • mimalloc

Install them before running npm install / npm run build:

# macOS (Homebrew)
brew install re2 abseil mimalloc
# Ubuntu/Debian (apt)
sudo apt-get update
sudo apt-get install -y libre2-dev libabsl-dev libmimalloc-dev
# C++ library + tests
cmake -B build
cmake --build build
./build/ata_tests

# Node.js addon
npm install
npm run build
npm test

# JSON Schema Test Suite
npm run test:suite

License

MIT

Authors

Mert Can Altin Daniel Lemire

About

Native C++ validator built on simdjson and RE2. Hybrid JS codegen with V8 TurboFan optimizations. Up to 94x faster on $dynamicRef, 5.3x on normal schemas, 2,729x faster compilation. Full $dynamicRef/$anchor support, Draft 2020-12 + Draft 7 compatible

Topics

Resources

License

Code of conduct

Contributing

Security policy

Stars

Watchers

Forks

Packages

 
 
 

Contributors