ata-validator 0.20.0 → 0.22.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 +19 -0
- package/README.md +67 -0
- package/build.d.ts +1 -1
- package/index.browser.mjs +1 -1
- package/index.d.ts +55 -2
- package/index.js +92 -0
- package/index.mjs +1 -1
- package/lib/aot-build.js +1 -1
- package/lib/error-messages.js +88 -0
- package/lib/refine.js +68 -0
- package/lib/t.js +9 -0
- package/lib/version.js +1 -1
- package/package.json +4 -4
- package/prebuilds/ata-darwin-arm64/node-napi-v10.node +0 -0
- package/prebuilds/ata-linux-arm64/node-napi-v10.node +0 -0
- package/prebuilds/ata-linux-arm64-musl/node-napi-v10.node +0 -0
- package/prebuilds/ata-linux-x64/node-napi-v10.node +0 -0
- package/prebuilds/ata-linux-x64-musl/node-napi-v10.node +0 -0
- package/prebuilds/ata-win32-x64/node-napi-v10.node +0 -0
- package/t.d.ts +20 -1
package/CHANGELOG.md
CHANGED
|
@@ -2,6 +2,25 @@
|
|
|
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.22.0 - 2026-07-14
|
|
6
|
+
|
|
7
|
+
### Deprecated
|
|
8
|
+
|
|
9
|
+
- `Validator.prototype.toStandalone()` and `Validator.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()` from `ata-validator/build`, and the `Validator.bundle*()` statics.
|
|
10
|
+
|
|
11
|
+
## 0.21.0 - 2026-06-01
|
|
12
|
+
|
|
13
|
+
### Added
|
|
14
|
+
|
|
15
|
+
- `errorMessage` keyword for custom error messages. A string on a subschema replaces the message for any failing keyword there; an object overrides per keyword, with `required` keyed by missing property name and `_` as fallback. `code`, `keyword`, and `path` fields are untouched. Schemas without `errorMessage` pay nothing; the override pass is only installed when one is present.
|
|
16
|
+
- Async refinement: `t.refine(schema, fn, { message, path })` attaches an async (or sync) check that runs through `validateAsync`/`parseAsync` after structural validation passes. `new Validator(schema)` ignores the refinement marker, so plain structural validation is unchanged. Failing refinements surface as errors with `keyword: 'refine'`.
|
|
17
|
+
|
|
18
|
+
## 0.20.1 - 2026-05-27
|
|
19
|
+
|
|
20
|
+
### Fixed
|
|
21
|
+
|
|
22
|
+
- `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.
|
|
23
|
+
|
|
5
24
|
## 0.20.0 - 2026-05-27
|
|
6
25
|
|
|
7
26
|
### Added
|
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
|
@@ -75,7 +75,7 @@ export function watch(opts: BuildOptions, onReport?: (r: BuildReport) => void):
|
|
|
75
75
|
|
|
76
76
|
export interface BundleStandaloneOptions {
|
|
77
77
|
format?: 'cjs' | 'esm';
|
|
78
|
-
formats?: Record<string, (value:
|
|
78
|
+
formats?: Record<string, (value: string) => boolean>;
|
|
79
79
|
verbose?: boolean;
|
|
80
80
|
}
|
|
81
81
|
|
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
|
}
|
|
@@ -390,12 +411,16 @@ export interface Validator<T = unknown> {
|
|
|
390
411
|
/** Single-thread NDJSON batch validation. Requires native addon. */
|
|
391
412
|
isValidNDJSON(ndjsonBuffer: Buffer): boolean[];
|
|
392
413
|
|
|
393
|
-
/**
|
|
414
|
+
/**
|
|
415
|
+
* Generate a standalone JS module string for zero-compile loading. Returns null if schema can't be standalone-compiled.
|
|
416
|
+
* @deprecated Removed in 1.0. Use `toStandaloneModule()` from `ata-validator/build` instead.
|
|
417
|
+
*/
|
|
394
418
|
toStandalone(): string | null;
|
|
395
419
|
|
|
396
420
|
/**
|
|
397
421
|
* Generate a self-contained module string with `validate`/`isValid` exports.
|
|
398
422
|
* The output has zero runtime dependency on ata-validator.
|
|
423
|
+
* @deprecated Removed in 1.0. Use `toStandaloneModule()` from `ata-validator/build` instead.
|
|
399
424
|
*/
|
|
400
425
|
toStandaloneModule(options?: { format?: 'esm' | 'cjs'; abortEarly?: boolean }): string | null;
|
|
401
426
|
|
|
@@ -444,6 +469,34 @@ export function validate<T = unknown>(
|
|
|
444
469
|
data: unknown
|
|
445
470
|
): ValidationResult<T>;
|
|
446
471
|
|
|
472
|
+
/**
|
|
473
|
+
* Async validate for schemas built with `t.refine(...)`. Structural JSON Schema
|
|
474
|
+
* validation runs synchronously first; refinements are awaited only when the
|
|
475
|
+
* value is structurally valid. Failing refinements surface as errors with
|
|
476
|
+
* `keyword: 'refine'`. Accepts a schema literal or an existing {@link Validator}.
|
|
477
|
+
*/
|
|
478
|
+
export function validateAsync<T>(
|
|
479
|
+
validator: Validator<T>,
|
|
480
|
+
data: unknown
|
|
481
|
+
): Promise<ValidationResult<T>>;
|
|
482
|
+
export function validateAsync<const S extends JSONSchema>(
|
|
483
|
+
schema: S,
|
|
484
|
+
data: unknown
|
|
485
|
+
): Promise<ValidationResult<Infer<S>>>;
|
|
486
|
+
export function validateAsync<T = unknown>(
|
|
487
|
+
schema: object,
|
|
488
|
+
data: unknown
|
|
489
|
+
): Promise<ValidationResult<T>>;
|
|
490
|
+
|
|
491
|
+
/**
|
|
492
|
+
* Like {@link validateAsync}, but resolves to the validated data on success and
|
|
493
|
+
* rejects with an `Error` whose `errors` property holds the
|
|
494
|
+
* {@link ValidationError} list on failure.
|
|
495
|
+
*/
|
|
496
|
+
export function parseAsync<T>(validator: Validator<T>, data: unknown): Promise<T>;
|
|
497
|
+
export function parseAsync<const S extends JSONSchema>(schema: S, data: unknown): Promise<Infer<S>>;
|
|
498
|
+
export function parseAsync<T = unknown>(schema: object, data: unknown): Promise<T>;
|
|
499
|
+
|
|
447
500
|
/** Fast compile: returns a validate function directly. WeakMap cached, second call with same schema is near-zero cost. */
|
|
448
501
|
export function compile<T = unknown>(
|
|
449
502
|
schema: object | string,
|
package/index.js
CHANGED
|
@@ -446,6 +446,21 @@ function resolveSchemaForPreprocess(schema, schemaMap) {
|
|
|
446
446
|
return cloned || s
|
|
447
447
|
}
|
|
448
448
|
|
|
449
|
+
// Deprecated-method warning: once per method name per process. Guarded so the
|
|
450
|
+
// browser entry (no `process`) never touches it.
|
|
451
|
+
const deprecationWarned = new Set();
|
|
452
|
+
function warnDeprecated(method) {
|
|
453
|
+
if (deprecationWarned.has(method)) return;
|
|
454
|
+
deprecationWarned.add(method);
|
|
455
|
+
if (typeof process !== 'undefined' && typeof process.emitWarning === 'function') {
|
|
456
|
+
process.emitWarning(
|
|
457
|
+
`Validator.prototype.${method}() is deprecated and will be removed in ata-validator 1.0. ` +
|
|
458
|
+
`Use toStandaloneModule()/bundleStandalone() from 'ata-validator/build' instead.`,
|
|
459
|
+
'DeprecationWarning',
|
|
460
|
+
);
|
|
461
|
+
}
|
|
462
|
+
}
|
|
463
|
+
|
|
449
464
|
class Validator {
|
|
450
465
|
constructor(schema, opts) {
|
|
451
466
|
const options = opts || {};
|
|
@@ -1064,6 +1079,44 @@ class Validator {
|
|
|
1064
1079
|
};
|
|
1065
1080
|
}
|
|
1066
1081
|
|
|
1082
|
+
// Custom error messages: if any subschema declares an `errorMessage`
|
|
1083
|
+
// keyword, install an outermost decorator that overrides the `message`
|
|
1084
|
+
// field of the errors it owns. Gated on a one-time scan so schemas without
|
|
1085
|
+
// errorMessage keep the validate hot path untouched. Layered after rich
|
|
1086
|
+
// enrichment so `code`/`keyword`/`path` are already final and only the
|
|
1087
|
+
// human-facing message changes.
|
|
1088
|
+
{
|
|
1089
|
+
const emLib = require('./lib/error-messages');
|
|
1090
|
+
const schemaStr = this._schemaStr || (this._schemaObj ? JSON.stringify(this._schemaObj) : '');
|
|
1091
|
+
if (emLib.schemaHasErrorMessages(schemaStr)) {
|
|
1092
|
+
const root = this._schemaObj;
|
|
1093
|
+
const wrap = (inner) => (arg) => {
|
|
1094
|
+
const result = inner(arg);
|
|
1095
|
+
if (result && result.valid === false && result.errors && result.errors.length && result !== ABORT_EARLY_RESULT) {
|
|
1096
|
+
const overridden = emLib.applyErrorMessages(result.errors, root);
|
|
1097
|
+
if (overridden !== result.errors) return { valid: false, errors: overridden };
|
|
1098
|
+
}
|
|
1099
|
+
return result;
|
|
1100
|
+
};
|
|
1101
|
+
if (this.validate) this.validate = wrap(this.validate);
|
|
1102
|
+
if (this.validateJSON) this.validateJSON = wrap(this.validateJSON);
|
|
1103
|
+
// validateAndParse routes through self.validate on the codegen path, but
|
|
1104
|
+
// the native-only path returns directly from the addon — wrap it so both
|
|
1105
|
+
// paths get overrides. The result shape carries `value`, preserved here.
|
|
1106
|
+
if (this.validateAndParse) {
|
|
1107
|
+
const innerVP = this.validateAndParse;
|
|
1108
|
+
this.validateAndParse = (arg) => {
|
|
1109
|
+
const result = innerVP(arg);
|
|
1110
|
+
if (result && result.valid === false && result.errors && result.errors.length) {
|
|
1111
|
+
const overridden = emLib.applyErrorMessages(result.errors, root);
|
|
1112
|
+
if (overridden !== result.errors) return { valid: false, value: result.value, errors: overridden };
|
|
1113
|
+
}
|
|
1114
|
+
return result;
|
|
1115
|
+
};
|
|
1116
|
+
}
|
|
1117
|
+
}
|
|
1118
|
+
}
|
|
1119
|
+
|
|
1067
1120
|
// Save to identity cache for ultra-fast reuse with same schema object
|
|
1068
1121
|
if (this._schemaObj && typeof this._schemaObj === 'object') {
|
|
1069
1122
|
_identityCache.set(this._schemaObj, this);
|
|
@@ -1134,10 +1187,12 @@ class Validator {
|
|
|
1134
1187
|
// bundlers swap `lib/aot.js` with `lib/aot.browser.js`, which throws a
|
|
1135
1188
|
// pointed error instead of attempting to read source from disk.
|
|
1136
1189
|
toStandalone() {
|
|
1190
|
+
warnDeprecated('toStandalone');
|
|
1137
1191
|
return require('./lib/aot').toStandalone(this);
|
|
1138
1192
|
}
|
|
1139
1193
|
|
|
1140
1194
|
toStandaloneModule(opts) {
|
|
1195
|
+
warnDeprecated('toStandaloneModule');
|
|
1141
1196
|
return require('./lib/aot').toStandaloneModule(this, opts);
|
|
1142
1197
|
}
|
|
1143
1198
|
|
|
@@ -1322,6 +1377,41 @@ function validate(schema, data) {
|
|
|
1322
1377
|
return v.validate(data);
|
|
1323
1378
|
}
|
|
1324
1379
|
|
|
1380
|
+
// Async validation for schemas built with `t.refine(...)`. Structural
|
|
1381
|
+
// validation runs synchronously first; refinements are awaited only when the
|
|
1382
|
+
// value is structurally valid (a refinement body may assume the right shape).
|
|
1383
|
+
// Accepts a schema literal or an existing Validator instance plus its schema.
|
|
1384
|
+
// Returns a Promise<ValidationResult>.
|
|
1385
|
+
async function validateAsync(schemaOrValidator, data) {
|
|
1386
|
+
const refineLib = require('./lib/refine');
|
|
1387
|
+
let validator, schema;
|
|
1388
|
+
if (schemaOrValidator instanceof Validator) {
|
|
1389
|
+
validator = schemaOrValidator;
|
|
1390
|
+
schema = validator._schemaObj;
|
|
1391
|
+
} else {
|
|
1392
|
+
schema = schemaOrValidator;
|
|
1393
|
+
validator = new Validator(schema);
|
|
1394
|
+
}
|
|
1395
|
+
const structural = validator.validate(data);
|
|
1396
|
+
if (!structural.valid) return structural;
|
|
1397
|
+
const refinements = refineLib.getRefinements(schema);
|
|
1398
|
+
if (!refinements) return structural;
|
|
1399
|
+
const issues = await refineLib.runRefinements(refinements, structural.data !== undefined ? structural.data : data);
|
|
1400
|
+
if (issues.length) return { valid: false, errors: issues };
|
|
1401
|
+
return structural;
|
|
1402
|
+
}
|
|
1403
|
+
|
|
1404
|
+
// parseAsync resolves to the validated data, or rejects with an Error whose
|
|
1405
|
+
// `.errors` carries the ValidationError list. Mirrors the parse/validate split
|
|
1406
|
+
// used by Zod-style callers.
|
|
1407
|
+
async function parseAsync(schemaOrValidator, data) {
|
|
1408
|
+
const result = await validateAsync(schemaOrValidator, data);
|
|
1409
|
+
if (result.valid) return result.data !== undefined ? result.data : data;
|
|
1410
|
+
const err = new Error('ata: async validation failed');
|
|
1411
|
+
err.errors = result.errors;
|
|
1412
|
+
throw err;
|
|
1413
|
+
}
|
|
1414
|
+
|
|
1325
1415
|
function version() {
|
|
1326
1416
|
if (native) return native.version();
|
|
1327
1417
|
try { return require("./lib/version"); } catch { return "unknown"; }
|
|
@@ -1420,6 +1510,8 @@ module.exports = {
|
|
|
1420
1510
|
Validator,
|
|
1421
1511
|
compile,
|
|
1422
1512
|
validate,
|
|
1513
|
+
validateAsync,
|
|
1514
|
+
parseAsync,
|
|
1423
1515
|
version,
|
|
1424
1516
|
createPaddedBuffer,
|
|
1425
1517
|
SIMDJSON_PADDING,
|
package/index.mjs
CHANGED
|
@@ -1,3 +1,3 @@
|
|
|
1
1
|
import mod from './index.js';
|
|
2
|
-
export const { Validator, validate, version, createPaddedBuffer, SIMDJSON_PADDING, defineSchema, renderPretty, renderCompact, renderJSON } = mod;
|
|
2
|
+
export const { Validator, validate, validateAsync, parseAsync, version, createPaddedBuffer, SIMDJSON_PADDING, defineSchema, renderPretty, renderCompact, renderJSON } = mod;
|
|
3
3
|
export default mod;
|
package/lib/aot-build.js
CHANGED
|
@@ -0,0 +1,88 @@
|
|
|
1
|
+
'use strict';
|
|
2
|
+
|
|
3
|
+
// Custom error messages via the `errorMessage` keyword.
|
|
4
|
+
//
|
|
5
|
+
// A subschema may declare an `errorMessage` keyword to override the generated
|
|
6
|
+
// `message` on any error it owns:
|
|
7
|
+
//
|
|
8
|
+
// errorMessage: "single message for any failure of this subschema"
|
|
9
|
+
// errorMessage: {
|
|
10
|
+
// <keyword>: "msg", // e.g. minimum, type, pattern, format
|
|
11
|
+
// required: "msg" | { prop: "msg" },// keyed by missing property, or one string
|
|
12
|
+
// additionalProperties: "msg",
|
|
13
|
+
// _: "fallback msg", // used when no keyword-specific entry matches
|
|
14
|
+
// }
|
|
15
|
+
//
|
|
16
|
+
// Resolution: each error's owning subschema is located from its schemaPath (the
|
|
17
|
+
// keyword is the last pointer segment, the owner is everything before it). If the
|
|
18
|
+
// owner declares an errorMessage, the error's `message` is replaced. Errors are
|
|
19
|
+
// cloned, never mutated, so frozen/shared results stay intact. When no schema in
|
|
20
|
+
// the tree carries an errorMessage the decorator is never installed, so the
|
|
21
|
+
// validate hot path keeps its original cost.
|
|
22
|
+
|
|
23
|
+
// Locate the schema object that owns the failing keyword. Mirrors
|
|
24
|
+
// resolveSchemaByPath() in index.js: the last pointer segment is the keyword,
|
|
25
|
+
// so the owner is the node reached by walking every segment before it.
|
|
26
|
+
function resolveOwner (rootSchema, schemaPath) {
|
|
27
|
+
if (!schemaPath || typeof schemaPath !== 'string' || schemaPath[0] !== '#') return undefined;
|
|
28
|
+
const stripped = schemaPath.slice(1);
|
|
29
|
+
if (!stripped || stripped === '/') return rootSchema;
|
|
30
|
+
const parts = stripped.split('/').filter(Boolean).map((s) => s.replace(/~1/g, '/').replace(/~0/g, '~'));
|
|
31
|
+
let target = rootSchema;
|
|
32
|
+
for (let i = 0; i < parts.length - 1; i++) {
|
|
33
|
+
if (target == null || typeof target !== 'object') return undefined;
|
|
34
|
+
target = target[parts[i]];
|
|
35
|
+
}
|
|
36
|
+
return target;
|
|
37
|
+
}
|
|
38
|
+
|
|
39
|
+
// Pick the override string for a single error from its owner's errorMessage.
|
|
40
|
+
// Returns undefined when nothing matches, leaving the default message in place.
|
|
41
|
+
function pickMessage (em, err) {
|
|
42
|
+
if (em == null) return undefined;
|
|
43
|
+
if (typeof em === 'string') return em;
|
|
44
|
+
if (typeof em !== 'object') return undefined;
|
|
45
|
+
|
|
46
|
+
const kw = err.keyword;
|
|
47
|
+
|
|
48
|
+
if (kw === 'required') {
|
|
49
|
+
const r = em.required;
|
|
50
|
+
if (typeof r === 'string') return r;
|
|
51
|
+
if (r && typeof r === 'object') {
|
|
52
|
+
const prop = err.params && err.params.missingProperty;
|
|
53
|
+
if (prop != null && typeof r[prop] === 'string') return r[prop];
|
|
54
|
+
}
|
|
55
|
+
}
|
|
56
|
+
|
|
57
|
+
if (kw != null && typeof em[kw] === 'string') return em[kw];
|
|
58
|
+
if (typeof em._ === 'string') return em._;
|
|
59
|
+
return undefined;
|
|
60
|
+
}
|
|
61
|
+
|
|
62
|
+
// Returns true when the schema string is worth scanning at validate time.
|
|
63
|
+
function schemaHasErrorMessages (schemaStr) {
|
|
64
|
+
return typeof schemaStr === 'string' && schemaStr.indexOf('"errorMessage"') !== -1;
|
|
65
|
+
}
|
|
66
|
+
|
|
67
|
+
// Apply overrides across an error array. Returns the same array reference when
|
|
68
|
+
// nothing changed (so callers can cheaply detect a no-op), or a new array with
|
|
69
|
+
// cloned-and-overridden entries.
|
|
70
|
+
function applyErrorMessages (errors, rootSchema) {
|
|
71
|
+
if (!errors || !errors.length) return errors;
|
|
72
|
+
let changed = false;
|
|
73
|
+
const out = new Array(errors.length);
|
|
74
|
+
for (let i = 0; i < errors.length; i++) {
|
|
75
|
+
const err = errors[i];
|
|
76
|
+
out[i] = err;
|
|
77
|
+
if (!err || typeof err.schemaPath !== 'string') continue;
|
|
78
|
+
const owner = resolveOwner(rootSchema, err.schemaPath);
|
|
79
|
+
if (!owner || typeof owner !== 'object') continue;
|
|
80
|
+
const msg = pickMessage(owner.errorMessage, err);
|
|
81
|
+
if (msg == null) continue;
|
|
82
|
+
out[i] = Object.assign({}, err, { message: msg });
|
|
83
|
+
changed = true;
|
|
84
|
+
}
|
|
85
|
+
return changed ? out : errors;
|
|
86
|
+
}
|
|
87
|
+
|
|
88
|
+
module.exports = { schemaHasErrorMessages, applyErrorMessages, resolveOwner, pickMessage };
|
package/lib/refine.js
ADDED
|
@@ -0,0 +1,68 @@
|
|
|
1
|
+
'use strict';
|
|
2
|
+
|
|
3
|
+
// Async refinement support for the `/t` builder.
|
|
4
|
+
//
|
|
5
|
+
// JSON Schema is synchronous by design and cannot express a check that needs to
|
|
6
|
+
// await (uniqueness, a remote lookup, cross-field business rules). Those live on
|
|
7
|
+
// the builder via `t.refine(schema, check, opts)`. A refinement is carried on a
|
|
8
|
+
// Symbol key (like the OPTIONAL marker), so it is invisible to Object.keys,
|
|
9
|
+
// JSON.stringify, and ata's codegen: `new Validator(schema)` still performs
|
|
10
|
+
// plain structural validation and ignores refinements entirely.
|
|
11
|
+
//
|
|
12
|
+
// `validateAsync()` (in index.js) runs structural validation first and only
|
|
13
|
+
// awaits the refinements when the value is structurally valid — a refinement
|
|
14
|
+
// body may assume the value already has the right shape.
|
|
15
|
+
|
|
16
|
+
const REFINE = Symbol.for('ata.t.refine');
|
|
17
|
+
|
|
18
|
+
// Pull the refinement list off a schema node, or null when there is none.
|
|
19
|
+
function getRefinements (schema) {
|
|
20
|
+
if (!schema || typeof schema !== 'object') return null;
|
|
21
|
+
const r = schema[REFINE];
|
|
22
|
+
return Array.isArray(r) && r.length ? r : null;
|
|
23
|
+
}
|
|
24
|
+
|
|
25
|
+
// Attach a refinement to a schema, composing with any already present. Returns a
|
|
26
|
+
// new object; the input is not mutated. Enumerable Symbol keys (OPTIONAL, prior
|
|
27
|
+
// refinements) are copied by the spread, then the refinement list is replaced.
|
|
28
|
+
function attach (schema, check, opts) {
|
|
29
|
+
if (typeof check !== 'function') {
|
|
30
|
+
throw new TypeError('t.refine(schema, check, opts?) — check must be a function');
|
|
31
|
+
}
|
|
32
|
+
const prev = (schema && Array.isArray(schema[REFINE])) ? schema[REFINE] : [];
|
|
33
|
+
const o = opts || {};
|
|
34
|
+
const entry = { check, message: o.message, path: o.path || '' };
|
|
35
|
+
return Object.assign({}, schema, { [REFINE]: prev.concat(entry) });
|
|
36
|
+
}
|
|
37
|
+
|
|
38
|
+
// Build a single refinement issue in the validator's error shape.
|
|
39
|
+
function issue (entry, message) {
|
|
40
|
+
return {
|
|
41
|
+
keyword: 'refine',
|
|
42
|
+
instancePath: entry.path || '',
|
|
43
|
+
path: entry.path || '',
|
|
44
|
+
schemaPath: '',
|
|
45
|
+
params: {},
|
|
46
|
+
message: message != null ? message : (entry.message || 'value failed refinement'),
|
|
47
|
+
};
|
|
48
|
+
}
|
|
49
|
+
|
|
50
|
+
// Evaluate every refinement against `data`, awaiting async checks concurrently.
|
|
51
|
+
// A check that returns falsy — or throws — yields an issue. Returns the issues
|
|
52
|
+
// array (empty when all pass).
|
|
53
|
+
async function runRefinements (refinements, data) {
|
|
54
|
+
const issues = [];
|
|
55
|
+
await Promise.all(refinements.map(async (entry) => {
|
|
56
|
+
let passed;
|
|
57
|
+
try {
|
|
58
|
+
passed = await entry.check(data);
|
|
59
|
+
} catch (e) {
|
|
60
|
+
issues.push(issue(entry, entry.message || (e && e.message) || 'refinement threw'));
|
|
61
|
+
return;
|
|
62
|
+
}
|
|
63
|
+
if (!passed) issues.push(issue(entry));
|
|
64
|
+
}));
|
|
65
|
+
return issues;
|
|
66
|
+
}
|
|
67
|
+
|
|
68
|
+
module.exports = { REFINE, getRefinements, attach, runRefinements };
|
package/lib/t.js
CHANGED
|
@@ -12,6 +12,7 @@
|
|
|
12
12
|
// to compute `required` and strips it from the emitted property schema.
|
|
13
13
|
|
|
14
14
|
const OPTIONAL = Symbol.for('ata.t.optional');
|
|
15
|
+
const { attach: attachRefine } = require('./refine');
|
|
15
16
|
|
|
16
17
|
function string(opts) {
|
|
17
18
|
return Object.assign({ type: 'string' }, opts);
|
|
@@ -116,6 +117,13 @@ function never() {
|
|
|
116
117
|
return { not: {} };
|
|
117
118
|
}
|
|
118
119
|
|
|
120
|
+
function refine(schema, check, opts) {
|
|
121
|
+
// Attach an async/sync refinement. The schema stays plain JSON Schema for
|
|
122
|
+
// structural validation; the refinement is carried on a Symbol key and only
|
|
123
|
+
// runs via validateAsync()/parseAsync(). Compose by wrapping again.
|
|
124
|
+
return attachRefine(schema, check, opts);
|
|
125
|
+
}
|
|
126
|
+
|
|
119
127
|
module.exports = {
|
|
120
128
|
string,
|
|
121
129
|
number,
|
|
@@ -136,5 +144,6 @@ module.exports = {
|
|
|
136
144
|
any,
|
|
137
145
|
unknown,
|
|
138
146
|
never,
|
|
147
|
+
refine,
|
|
139
148
|
OPTIONAL,
|
|
140
149
|
};
|
package/lib/version.js
CHANGED
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "ata-validator",
|
|
3
|
-
"version": "0.
|
|
3
|
+
"version": "0.22.0",
|
|
4
4
|
"description": "JSON Schema validation with first-class TypeScript and zero runtime cost. AOT compile to per-schema ESM modules with zero validator dependency. Generic Validator<T> for TypeBox/Zod/Valibot composition. Optional runtime API. Standard Schema V1 compatible.",
|
|
5
5
|
"main": "index.js",
|
|
6
6
|
"module": "index.mjs",
|
|
@@ -48,7 +48,7 @@
|
|
|
48
48
|
"rebuild": "cmake-js rebuild --target ata",
|
|
49
49
|
"prebuild": "pkg-prebuilds-copy --baseDir build/Release --source ata.node --name=ata --strip --napi_version=10",
|
|
50
50
|
"prebuild-all": "npm run prebuild -- --arch x64 && npm run prebuild -- --arch arm64",
|
|
51
|
-
"test": "node test.js && node tests/test_no_native.js && node tests/test_browser_nofs.js && node tests/test_browser_imports_guard.js && node tests/test_version_sync.js && node tests/test_safe_regex_source_sync.js && node tests/test_t_builder.js && node tests/test_safe_regex.js && node tests/test_safe_regex_integration.js && node tests/test_aot_build.js && node tests/test_aot_differential.js && node tests/test_aot_cli_build.js && node tests/test_aot_cli_smoke.js && node tests/test_bundle_standalone.js && node tests/test_standalone_anyof.js && node tests/test_standalone_formats.js && node tests/test_aot_additional_props_errors.js && node tests/test_id_anchor_refs.js && node tests/test_typed_validator_runner.js && node tests/test_define_schema.js && node tests/test_error_codes_lock.js && node tests/test_nullable.js && node tests/test_validate_and_parse.js && node tests/test_validate_data.js && node tests/test_enrich_error.js && node tests/test_rich_errors_optout.js && node tests/test_source_positions.js && node tests/fuzz_positions.js && node tests/test_data_positions.js && node tests/test_render_shared.js && node tests/test_renderers.js && node tests/test_runtime_error_dx.js && node tests/test_aot_error_dx.js && node tests/test_abort_early.js && node tests/test_branch_collapse.js && node tests/test_suggestions.js && node tests/test_cli_validate.js && node tests/test_cli_version.js && node benchmark/bench_aot_size.mjs",
|
|
51
|
+
"test": "node test.js && node tests/test_deprecation_warnings.js && node tests/test_no_native.js && node tests/test_browser_nofs.js && node tests/test_browser_imports_guard.js && node tests/test_version_sync.js && node tests/test_safe_regex_source_sync.js && node tests/test_t_builder.js && node tests/test_async_refine.js && node tests/test_safe_regex.js && node tests/test_safe_regex_integration.js && node tests/test_aot_build.js && node tests/test_aot_differential.js && node tests/test_aot_cli_build.js && node tests/test_aot_cli_smoke.js && node tests/test_bundle_standalone.js && node tests/test_standalone_anyof.js && node tests/test_standalone_formats.js && node tests/test_aot_additional_props_errors.js && node tests/test_id_anchor_refs.js && node tests/test_typed_validator_runner.js && node tests/test_define_schema.js && node tests/test_error_codes_lock.js && node tests/test_nullable.js && node tests/test_validate_and_parse.js && node tests/test_validate_data.js && node tests/test_enrich_error.js && node tests/test_rich_errors_optout.js && node tests/test_error_messages.js && node tests/test_source_positions.js && node tests/fuzz_positions.js && node tests/test_data_positions.js && node tests/test_render_shared.js && node tests/test_renderers.js && node tests/test_runtime_error_dx.js && node tests/test_aot_error_dx.js && node tests/test_abort_early.js && node tests/test_branch_collapse.js && node tests/test_suggestions.js && node tests/test_cli_validate.js && node tests/test_cli_version.js && node benchmark/bench_aot_size.mjs",
|
|
52
52
|
"bench:size": "node benchmark/bench_aot_size.mjs",
|
|
53
53
|
"test:suite": "node tests/run_suite.js",
|
|
54
54
|
"test:compat": "node tests/test_compat.js",
|
|
@@ -86,10 +86,10 @@
|
|
|
86
86
|
"license": "MIT",
|
|
87
87
|
"repository": {
|
|
88
88
|
"type": "git",
|
|
89
|
-
"url": "git+https://github.com/
|
|
89
|
+
"url": "git+https://github.com/ata-core/ata-validator.git"
|
|
90
90
|
},
|
|
91
91
|
"bugs": {
|
|
92
|
-
"url": "https://github.com/
|
|
92
|
+
"url": "https://github.com/ata-core/ata-validator/issues"
|
|
93
93
|
},
|
|
94
94
|
"homepage": "https://ata-validator.com",
|
|
95
95
|
"engines": {
|
|
Binary file
|
|
File without changes
|
|
Binary file
|
|
File without changes
|
|
Binary file
|
|
Binary file
|
package/t.d.ts
CHANGED
|
@@ -15,10 +15,21 @@
|
|
|
15
15
|
//
|
|
16
16
|
// const v = new Validator(User) // schema is plain JSON Schema
|
|
17
17
|
|
|
18
|
-
import type { JSONSchema } from './index.js';
|
|
18
|
+
import type { JSONSchema, Infer } from './index.js';
|
|
19
19
|
|
|
20
20
|
export declare const OPTIONAL: unique symbol;
|
|
21
21
|
|
|
22
|
+
/** Options for {@link TBuilder.refine}. */
|
|
23
|
+
export interface RefineOptions {
|
|
24
|
+
/** Message attached to the error when the refinement fails. */
|
|
25
|
+
message?: string;
|
|
26
|
+
/** instancePath the error points at (e.g. `/email`). Defaults to the root. */
|
|
27
|
+
path?: string;
|
|
28
|
+
}
|
|
29
|
+
|
|
30
|
+
/** A refinement check: receives the structurally-valid value, returns (a promise of) a boolean. */
|
|
31
|
+
export type RefineCheck<T> = (value: T) => boolean | Promise<boolean>;
|
|
32
|
+
|
|
22
33
|
export type TOptional<S> = S & { readonly [OPTIONAL]: true };
|
|
23
34
|
|
|
24
35
|
export type TSchema = JSONSchema;
|
|
@@ -181,6 +192,14 @@ export interface TBuilder {
|
|
|
181
192
|
any(): TAny;
|
|
182
193
|
unknown(): TAny;
|
|
183
194
|
never(): TNever;
|
|
195
|
+
|
|
196
|
+
/**
|
|
197
|
+
* Attach an async (or sync) refinement to a schema. The schema stays plain
|
|
198
|
+
* JSON Schema for structural validation; the refinement runs only via
|
|
199
|
+
* `validateAsync()` / `parseAsync()`. Compose by wrapping again. The returned
|
|
200
|
+
* schema infers the same type, so `Infer<typeof refined>` is unchanged.
|
|
201
|
+
*/
|
|
202
|
+
refine<S extends JSONSchema>(schema: S, check: RefineCheck<Infer<S>>, opts?: RefineOptions): S;
|
|
184
203
|
}
|
|
185
204
|
|
|
186
205
|
export declare const t: TBuilder;
|