ata-validator 0.18.2 → 0.21.0
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/CHANGELOG.md +44 -0
- package/README.md +67 -0
- package/build.d.ts +28 -0
- package/build.mjs +3 -0
- package/index.browser.mjs +1 -1
- package/index.d.ts +58 -3
- package/index.js +101 -370
- package/index.mjs +1 -1
- package/lib/aot-build.js +30 -1
- package/lib/aot.js +410 -0
- package/lib/error-messages.js +88 -0
- package/lib/native-load.browser.js +8 -0
- package/lib/native-load.js +21 -0
- package/lib/refine.js +68 -0
- package/lib/safe-regex-source.js +8 -0
- package/lib/t.js +149 -0
- package/lib/version.js +10 -0
- package/package.json +14 -5
- package/scripts/regen-safe-regex-source.js +33 -0
- package/t.d.ts +205 -0
- package/t.js +9 -0
- package/t.mjs +6 -0
package/CHANGELOG.md
CHANGED
|
@@ -2,6 +2,50 @@
|
|
|
2
2
|
|
|
3
3
|
All notable changes to ata-validator are documented here. The format follows [Keep a Changelog](https://keepachangelog.com/), and this project adheres to semantic versioning.
|
|
4
4
|
|
|
5
|
+
## 0.20.1 - 2026-05-27
|
|
6
|
+
|
|
7
|
+
### Fixed
|
|
8
|
+
|
|
9
|
+
- `JSONSchema.items` now accepts `boolean` so the typed `t.tuple([...])` output (which sets `items: false` to close the tail) type-checks against `JSONSchema` without a constraint error. `items: false` is valid JSON Schema and the runtime already honoured it; the type definition just had not been widened. Anyone consuming `t.tuple` from outside a project with `skipLibCheck` ran into a `TTuple incorrectly extends JSONSchema` error.
|
|
10
|
+
|
|
11
|
+
## 0.20.0 - 2026-05-27
|
|
12
|
+
|
|
13
|
+
### Added
|
|
14
|
+
|
|
15
|
+
- 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.
|
|
16
|
+
|
|
17
|
+
```ts
|
|
18
|
+
import { t } from 'ata-validator/t'
|
|
19
|
+
import { Validator, type Infer } from 'ata-validator'
|
|
20
|
+
|
|
21
|
+
const User = t.object({
|
|
22
|
+
id: t.integer(),
|
|
23
|
+
name: t.string({ minLength: 1 }),
|
|
24
|
+
email: t.optional(t.string({ format: 'email' })),
|
|
25
|
+
role: t.union([t.literal('admin'), t.literal('user')]),
|
|
26
|
+
})
|
|
27
|
+
type User = Infer<typeof User>
|
|
28
|
+
const v = new Validator(User)
|
|
29
|
+
```
|
|
30
|
+
|
|
31
|
+
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, `Object.keys`, `JSON.stringify`, and ata's codegen never see; the parent `t.object` reads it to compute `required`.
|
|
32
|
+
|
|
33
|
+
### Changed
|
|
34
|
+
|
|
35
|
+
- `Infer<S>` 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.
|
|
36
|
+
|
|
37
|
+
## 0.19.0 - 2026-05-27
|
|
38
|
+
|
|
39
|
+
### Added
|
|
40
|
+
|
|
41
|
+
- `ata-validator/build` now exports the AOT primitives `bundleStandalone`, `bundleCompact`, and `toStandaloneModule` as named functions, so callers that want the build surface in one place (bundler plugins, build scripts) no longer have to go through the `Validator` class. Same code paths as the Validator-bound forms, no behaviour difference.
|
|
42
|
+
- New top-level `ARCHITECTURE.md` reference document covering design principles, runtime dispatch, AOT pipeline, error enrichment, the two TypeScript paths, and the native layer.
|
|
43
|
+
|
|
44
|
+
### Changed
|
|
45
|
+
|
|
46
|
+
- Internal refactor: AOT (`toStandalone`, `toStandaloneModule`, `bundle`, `bundleStandalone`, `bundleCompact`, `loadBundle`) lives in `lib/aot.js`, the native addon loader in `lib/native-load.js`, the version string in `lib/version.js`. `index.js` lazy-requires the AOT module so a plain import never pays for code it does not call. The browser bundle drops `pkg-prebuilds`, `__dirname`, and `package.json` (with its dependency strings) entirely; it is roughly 15 KB smaller and contains no Node-only identifiers outside comments.
|
|
47
|
+
- The safe-regex engine is now 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 (`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 no `readFileSync`, `pkg-prebuilds`, or `__dirname` survives outside comments; sync tests catch drift between the bundled strings and their sources.
|
|
48
|
+
|
|
5
49
|
## 0.18.2 - 2026-05-26
|
|
6
50
|
|
|
7
51
|
### Fixed
|
package/README.md
CHANGED
|
@@ -70,6 +70,27 @@ The `ata` CLI ships `ata validate <schema> <data>` for one-off checks. TTY auto-
|
|
|
70
70
|
|
|
71
71
|
Errors carry a stable `code` field (`ATA####`), see the [error code registry](docs/error-codes.md). Each code has a permalink at `https://ata-validator.com/e/<CODE>`.
|
|
72
72
|
|
|
73
|
+
### Custom messages
|
|
74
|
+
|
|
75
|
+
A subschema can override the human-facing message with an `errorMessage` keyword. 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 dashboards and renderers keep working.
|
|
76
|
+
|
|
77
|
+
```js
|
|
78
|
+
const v = new Validator({
|
|
79
|
+
type: 'object',
|
|
80
|
+
properties: {
|
|
81
|
+
age: { type: 'integer', minimum: 18, errorMessage: { minimum: 'must be 18 or older', type: 'age has to be a number' } },
|
|
82
|
+
email: { type: 'string', format: 'email', errorMessage: 'enter a valid email address' },
|
|
83
|
+
},
|
|
84
|
+
required: ['email'],
|
|
85
|
+
errorMessage: { required: { email: 'email is required' } },
|
|
86
|
+
})
|
|
87
|
+
|
|
88
|
+
v.validate({ age: 5 }).errors[0].message // 'must be 18 or older'
|
|
89
|
+
v.validate({}).errors[0].message // 'email is required'
|
|
90
|
+
```
|
|
91
|
+
|
|
92
|
+
Schemas without an `errorMessage` keyword pay nothing: the override pass is only installed when one is present.
|
|
93
|
+
|
|
73
94
|
### Opting out
|
|
74
95
|
|
|
75
96
|
For consumers who built log dashboards on the v0.14 error shape, `new Validator(schema, { richErrors: false })` returns the legacy shape exactly. For high-throughput paths, `abortEarly: true` continues to short-circuit; the returned error carries `code: 'ATA9000'` and no enrichment.
|
|
@@ -183,6 +204,52 @@ type Event = Infer<typeof event>
|
|
|
183
204
|
|
|
184
205
|
`Infer` resolves `const`/`enum` to literals, `anyOf`/`oneOf` to unions, `allOf` to intersections, `prefixItems` to tuples, and local `$ref` into `#/$defs` or `#/definitions`, including recursive references. An external or unresolvable `$ref` resolves to `unknown` rather than erroring. The exported `JSONSchema` type is available if you want to annotate a schema by hand; custom and vendor keywords are allowed. Requires TypeScript >= 5.0.
|
|
185
206
|
|
|
207
|
+
#### Chainable authoring with `ata-validator/t`
|
|
208
|
+
|
|
209
|
+
If you prefer a chainable builder over JSON Schema literals, `ata-validator/t` ships one whose output is still plain JSON Schema. The runtime validator, `Infer<S>`, and the AOT pipeline all keep working without an adapter. The migration from TypeBox is one import rename, then the same authoring shape:
|
|
210
|
+
|
|
211
|
+
```ts
|
|
212
|
+
import { t } from 'ata-validator/t'
|
|
213
|
+
import { Validator, type Infer } from 'ata-validator'
|
|
214
|
+
|
|
215
|
+
const User = t.object({
|
|
216
|
+
id: t.integer(),
|
|
217
|
+
name: t.string({ minLength: 1 }),
|
|
218
|
+
email: t.optional(t.string({ format: 'email' })),
|
|
219
|
+
role: t.union([t.literal('admin'), t.literal('user')]),
|
|
220
|
+
})
|
|
221
|
+
|
|
222
|
+
type User = Infer<typeof User>
|
|
223
|
+
// { id: number; name: string; role: 'admin' | 'user'; email?: string }
|
|
224
|
+
|
|
225
|
+
const v = new Validator(User)
|
|
226
|
+
```
|
|
227
|
+
|
|
228
|
+
The builder covers 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 marker that the emitted JSON Schema and ata's codegen never see, so the output is still a plain JSON Schema literal that you can pass to anything that takes one.
|
|
229
|
+
|
|
230
|
+
#### Async refinement
|
|
231
|
+
|
|
232
|
+
JSON Schema is synchronous, so checks that need to await, a uniqueness lookup, a remote call, a cross-field rule, attach to a schema with `t.refine` and run through `validateAsync`. The refinement rides on a Symbol marker, so `new Validator(schema)` still does plain structural validation and ignores it; only `validateAsync`/`parseAsync` evaluate it, and only after the value is structurally valid.
|
|
233
|
+
|
|
234
|
+
```ts
|
|
235
|
+
import { t } from 'ata-validator/t'
|
|
236
|
+
import { validateAsync, parseAsync } from 'ata-validator'
|
|
237
|
+
|
|
238
|
+
const Signup = t.refine(
|
|
239
|
+
t.object({ username: t.string({ minLength: 3 }), email: t.string({ format: 'email' }) }),
|
|
240
|
+
async (value) => !(await usernameTaken(value.username)),
|
|
241
|
+
{ message: 'username is already taken', path: '/username' },
|
|
242
|
+
)
|
|
243
|
+
|
|
244
|
+
const r = await validateAsync(Signup, body)
|
|
245
|
+
if (!r.valid) return reply.code(400).send(r.errors)
|
|
246
|
+
|
|
247
|
+
// or: resolves to the typed value, throws on failure with err.errors
|
|
248
|
+
const user = await parseAsync(Signup, body)
|
|
249
|
+
```
|
|
250
|
+
|
|
251
|
+
Refinements compose by wrapping again, and a failing one surfaces as an error with `keyword: 'refine'` carrying your `message` and `path`. A `check` may be sync or async.
|
|
252
|
+
|
|
186
253
|
#### Composes with TypeBox, Zod, or your own types
|
|
187
254
|
|
|
188
255
|
`Validator<T>` is generic, so if you already author schemas with a library, pass the type and ata narrows to it. No library-specific assumption.
|
package/build.d.ts
CHANGED
|
@@ -67,3 +67,31 @@ export interface WatchHandle {
|
|
|
67
67
|
close(): void;
|
|
68
68
|
}
|
|
69
69
|
export function watch(opts: BuildOptions, onReport?: (r: BuildReport) => void): Promise<WatchHandle>;
|
|
70
|
+
|
|
71
|
+
// --- AOT primitives ---
|
|
72
|
+
// Programmatic counterparts to `Validator.bundleStandalone` / `bundleCompact`
|
|
73
|
+
// and `validator.toStandaloneModule`. Kept here so callers that only want the
|
|
74
|
+
// build surface (e.g. a bundler plugin) don't have to import the full runtime.
|
|
75
|
+
|
|
76
|
+
export interface BundleStandaloneOptions {
|
|
77
|
+
format?: 'cjs' | 'esm';
|
|
78
|
+
formats?: Record<string, (value: unknown) => boolean>;
|
|
79
|
+
verbose?: boolean;
|
|
80
|
+
}
|
|
81
|
+
|
|
82
|
+
export interface ToStandaloneModuleOptions {
|
|
83
|
+
format?: 'cjs' | 'esm';
|
|
84
|
+
abortEarly?: boolean;
|
|
85
|
+
source?: boolean;
|
|
86
|
+
sourceMap?: unknown;
|
|
87
|
+
schemaFile?: string;
|
|
88
|
+
}
|
|
89
|
+
|
|
90
|
+
/** Bundle multiple schemas into one self-contained module (no ata-validator runtime). */
|
|
91
|
+
export function bundleStandalone(schemas: unknown[], options?: BundleStandaloneOptions): string;
|
|
92
|
+
|
|
93
|
+
/** Like {@link bundleStandalone} but deduplicates shared bodies for smaller output. */
|
|
94
|
+
export function bundleCompact(schemas: unknown[], options?: BundleStandaloneOptions): string;
|
|
95
|
+
|
|
96
|
+
/** Emit a self-contained `validate`/`isValid` module string for a single schema. */
|
|
97
|
+
export function toStandaloneModule(schema: unknown, options?: ToStandaloneModuleOptions): string | null;
|
package/build.mjs
CHANGED
|
@@ -5,4 +5,7 @@ export const expandGlobs = mod.expandGlobs;
|
|
|
5
5
|
export const parseSchemaFile = mod.parseSchemaFile;
|
|
6
6
|
export const outputPathFor = mod.outputPathFor;
|
|
7
7
|
export const watch = mod.watch;
|
|
8
|
+
export const bundleStandalone = mod.bundleStandalone;
|
|
9
|
+
export const bundleCompact = mod.bundleCompact;
|
|
10
|
+
export const toStandaloneModule = mod.toStandaloneModule;
|
|
8
11
|
export default mod;
|
package/index.browser.mjs
CHANGED
|
@@ -1,4 +1,4 @@
|
|
|
1
1
|
// Browser ESM entry — same code, native addon stubbed out by bundler via "browser" field.
|
|
2
2
|
import mod from './index.js';
|
|
3
|
-
export const { Validator, validate, version, createPaddedBuffer, SIMDJSON_PADDING, renderPretty, renderCompact, renderJSON, toTypeScript } = mod;
|
|
3
|
+
export const { Validator, validate, validateAsync, parseAsync, version, createPaddedBuffer, SIMDJSON_PADDING, renderPretty, renderCompact, renderJSON, toTypeScript } = mod;
|
|
4
4
|
export default mod;
|
package/index.d.ts
CHANGED
|
@@ -75,6 +75,18 @@ export function renderJSON(errors: RichValidationError[], opts?: JSONRenderOptio
|
|
|
75
75
|
/** A user-supplied format checker. Receives the candidate value, returns true if valid. */
|
|
76
76
|
export type FormatChecker = (value: string) => boolean;
|
|
77
77
|
|
|
78
|
+
/**
|
|
79
|
+
* Per-keyword custom error messages for a subschema's `errorMessage`. Keys are
|
|
80
|
+
* keyword names (`type`, `minimum`, `pattern`, ...). `required` may be a single
|
|
81
|
+
* string or a map keyed by the missing property name. `_` is the fallback used
|
|
82
|
+
* when no keyword-specific entry matches.
|
|
83
|
+
*/
|
|
84
|
+
export interface ErrorMessageMap {
|
|
85
|
+
required?: string | Record<string, string>;
|
|
86
|
+
_?: string;
|
|
87
|
+
[keyword: string]: string | Record<string, string> | undefined;
|
|
88
|
+
}
|
|
89
|
+
|
|
78
90
|
/** The seven primitive `type` values defined by JSON Schema. */
|
|
79
91
|
export type JSONSchemaTypeName =
|
|
80
92
|
| 'string'
|
|
@@ -125,7 +137,7 @@ export interface JSONSchema {
|
|
|
125
137
|
dependentSchemas?: Record<string, JSONSchema>;
|
|
126
138
|
|
|
127
139
|
// array
|
|
128
|
-
items?: JSONSchema | ReadonlyArray<JSONSchema
|
|
140
|
+
items?: JSONSchema | ReadonlyArray<JSONSchema> | boolean;
|
|
129
141
|
prefixItems?: ReadonlyArray<JSONSchema>;
|
|
130
142
|
additionalItems?: boolean | JSONSchema;
|
|
131
143
|
contains?: JSONSchema;
|
|
@@ -160,6 +172,15 @@ export interface JSONSchema {
|
|
|
160
172
|
/** OpenAPI compatibility: ata honors `nullable` as a JSON Schema extension. */
|
|
161
173
|
nullable?: boolean;
|
|
162
174
|
|
|
175
|
+
/**
|
|
176
|
+
* Override the human-facing `message` on errors this subschema produces.
|
|
177
|
+
* A string replaces the message for any failing keyword; an object overrides
|
|
178
|
+
* per keyword, with `required` keyed by missing property name (or a single
|
|
179
|
+
* string) and `_` as a fallback. Other fields (`code`, `keyword`, `path`)
|
|
180
|
+
* are unaffected.
|
|
181
|
+
*/
|
|
182
|
+
errorMessage?: string | ErrorMessageMap;
|
|
183
|
+
|
|
163
184
|
/** Custom and vendor keywords are allowed without error. */
|
|
164
185
|
[keyword: string]: unknown;
|
|
165
186
|
}
|
|
@@ -202,13 +223,19 @@ type ResolveRef<R, D> = [RefName<R>] extends [never]
|
|
|
202
223
|
? InferWith<D[RefName<R>], D>
|
|
203
224
|
: unknown;
|
|
204
225
|
|
|
205
|
-
/** Object shape: required keys are required, all other declared keys optional.
|
|
226
|
+
/** Object shape: required keys are required, all other declared keys optional.
|
|
227
|
+
* When `properties` is absent and `additionalProperties` is a schema, infer
|
|
228
|
+
* a `Record<string, V>` (the JSON Schema "dictionary"/"record" shape). */
|
|
206
229
|
type InferObject<S, D> = S extends { properties: infer P }
|
|
207
230
|
? Simplify<
|
|
208
231
|
{ [K in keyof P as K extends RequiredKeys<S> ? K : never]: InferWith<P[K], D> } &
|
|
209
232
|
{ [K in keyof P as K extends RequiredKeys<S> ? never : K]?: InferWith<P[K], D> }
|
|
210
233
|
>
|
|
211
|
-
:
|
|
234
|
+
: S extends { additionalProperties: infer A }
|
|
235
|
+
? A extends boolean
|
|
236
|
+
? Record<string, unknown>
|
|
237
|
+
: Record<string, InferWith<A, D>>
|
|
238
|
+
: Record<string, unknown>;
|
|
212
239
|
|
|
213
240
|
/** Array shape: `prefixItems` -> tuple; `items` (single schema) -> element type; otherwise unknown[]. */
|
|
214
241
|
type InferArray<S, D> = S extends { prefixItems: infer P }
|
|
@@ -438,6 +465,34 @@ export function validate<T = unknown>(
|
|
|
438
465
|
data: unknown
|
|
439
466
|
): ValidationResult<T>;
|
|
440
467
|
|
|
468
|
+
/**
|
|
469
|
+
* Async validate for schemas built with `t.refine(...)`. Structural JSON Schema
|
|
470
|
+
* validation runs synchronously first; refinements are awaited only when the
|
|
471
|
+
* value is structurally valid. Failing refinements surface as errors with
|
|
472
|
+
* `keyword: 'refine'`. Accepts a schema literal or an existing {@link Validator}.
|
|
473
|
+
*/
|
|
474
|
+
export function validateAsync<T>(
|
|
475
|
+
validator: Validator<T>,
|
|
476
|
+
data: unknown
|
|
477
|
+
): Promise<ValidationResult<T>>;
|
|
478
|
+
export function validateAsync<const S extends JSONSchema>(
|
|
479
|
+
schema: S,
|
|
480
|
+
data: unknown
|
|
481
|
+
): Promise<ValidationResult<Infer<S>>>;
|
|
482
|
+
export function validateAsync<T = unknown>(
|
|
483
|
+
schema: object,
|
|
484
|
+
data: unknown
|
|
485
|
+
): Promise<ValidationResult<T>>;
|
|
486
|
+
|
|
487
|
+
/**
|
|
488
|
+
* Like {@link validateAsync}, but resolves to the validated data on success and
|
|
489
|
+
* rejects with an `Error` whose `errors` property holds the
|
|
490
|
+
* {@link ValidationError} list on failure.
|
|
491
|
+
*/
|
|
492
|
+
export function parseAsync<T>(validator: Validator<T>, data: unknown): Promise<T>;
|
|
493
|
+
export function parseAsync<const S extends JSONSchema>(schema: S, data: unknown): Promise<Infer<S>>;
|
|
494
|
+
export function parseAsync<T = unknown>(schema: object, data: unknown): Promise<T>;
|
|
495
|
+
|
|
441
496
|
/** Fast compile: returns a validate function directly. WeakMap cached, second call with same schema is near-zero cost. */
|
|
442
497
|
export function compile<T = unknown>(
|
|
443
498
|
schema: object | string,
|