ata-validator 1.0.2 → 1.2.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 +24 -0
- package/README.md +19 -2
- package/index.js +110 -12
- package/lib/aot.js +1 -1
- package/lib/interpreter.js +617 -0
- package/lib/js-compiler.js +32 -0
- package/lib/native-load.browser.js +1 -1
- package/lib/native-load.js +83 -11
- package/lib/t.js +90 -0
- package/lib/version.js +3 -3
- package/package.json +14 -20
- package/t.d.ts +84 -0
- package/CMakeLists.txt +0 -150
- package/binding/ata_napi.cpp +0 -1574
- package/binding-options.js +0 -4
- package/deps/simdjson/simdjson.cpp +0 -56221
- package/deps/simdjson/simdjson.h +0 -122784
- package/include/ata.h +0 -111
- package/include/ata_c.h +0 -57
- 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/scripts/check-doc-coverage.js +0 -34
- package/scripts/check-prebuilds.js +0 -54
- package/scripts/install.js +0 -23
- package/scripts/regen-error-codes-doc.js +0 -40
- package/scripts/regen-lock.js +0 -19
- package/scripts/regen-safe-regex-source.js +0 -33
- package/src/ata.cpp +0 -3146
- package/src/ata_c.cpp +0 -63
package/CHANGELOG.md
CHANGED
|
@@ -2,6 +2,30 @@
|
|
|
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
|
+
## 1.2.0 - 2026-07-18
|
|
6
|
+
|
|
7
|
+
### Added
|
|
8
|
+
|
|
9
|
+
- An interpreted engine (`lib/interpreter.js`) now backs schemas the JS compiler cannot represent when the native addon is absent (browser, edge workers, `ATA_NO_NATIVE=1`). These schemas validated only with the native engine before, and 1.1.0 made them throw a clear error in native-less environments; they now just validate. Full draft 2020-12 semantics: `$id`/`$anchor` resolution, `$dynamicRef` dynamic scoping, annotation tracking for `unevaluatedProperties`/`unevaluatedItems`. The pure-JS configuration now passes 1184 of 1190 applicable cases (99.5%) in the official test suite, up from 974, and the six remaining failures are shared with the native engine. Error results on the native-less path also carry full per-keyword detail now instead of a single generic message.
|
|
10
|
+
|
|
11
|
+
### Changed
|
|
12
|
+
|
|
13
|
+
- Validation errors now follow the schema's keyword declaration order instead of a fixed required-first order: a schema declaring `properties` before `required` reports the property errors first, matching what schema authors read top to bottom and what the previous default validator emitted. Order within one keyword is unchanged (`required` errors still follow the array). Single-error and `abortEarly` results are untouched. With this change ata passes every applicable test in Fastify's validation suite (181 of 187; the remaining 6 test the incumbent validator's own extension API rather than validation behavior).
|
|
14
|
+
|
|
15
|
+
## 1.1.0 - 2026-07-18
|
|
16
|
+
|
|
17
|
+
### Added
|
|
18
|
+
|
|
19
|
+
- TypeBox-style modifier combinators on `ata-validator/t`: `t.pick`, `t.omit`, `t.partial`, `t.required`, `t.composite`, and `t.recursive`. All six emit plain JSON Schema, so `Infer`, the runtime validator, and the AOT pipeline consume them with no adapter. This closes the authoring-parity gap for TypeBox migrations. Note: `t.recursive` schemas validate through the interpreted engine and are not AOT-precompilable; the other five combinators AOT-compile like any schema.
|
|
20
|
+
|
|
21
|
+
### Changed
|
|
22
|
+
|
|
23
|
+
- The `ata-validator` package is now pure JavaScript: no bundled binaries, no vendored C++ sources, no install script. The native engine moved to per-platform `@ata-validator/native-*` packages, installed automatically as optional dependencies (the same pattern Vite uses for esbuild). The tarball shrinks from ~5.3 MB to under 300 KB. `npm install --omit=optional` or `ATA_NO_NATIVE=1` gives a guaranteed zero-binary setup; validation behavior is identical for every schema shape the JS engine compiles, and the few shapes that still need the native engine now throw a clear error instead (see Fixed below).
|
|
24
|
+
|
|
25
|
+
### Fixed
|
|
26
|
+
|
|
27
|
+
- Schemas the JS engine cannot compile (some `$dynamicRef`, cyclic `$ref`, and unusual keyword interactions) crashed with `Maximum call stack size exceeded` in environments without the native addon: the lazy `validate` stub and the rich-error wrapper dispatched to each other forever. These schemas now throw a clear "not supported by the pure-JS engine" error on first use. The same cycle could hit `isValidObject` even with the native addon present when it was the first method called; it now falls through to the full compile and validates correctly.
|
|
28
|
+
|
|
5
29
|
## 1.0.2 - 2026-07-17
|
|
6
30
|
|
|
7
31
|
### Fixed
|
package/README.md
CHANGED
|
@@ -14,6 +14,14 @@ npm install --save-dev ata-validator
|
|
|
14
14
|
npx ata build 'schemas/*.json' --out-dir src/generated
|
|
15
15
|
```
|
|
16
16
|
|
|
17
|
+
The `ata-validator` package itself is pure JavaScript. The native accelerator (simdjson parsing, parallel NDJSON, buffer APIs) ships as per-platform optional packages that npm installs automatically where they fit, the same pattern Vite uses for esbuild. For a guaranteed zero-binary install:
|
|
18
|
+
|
|
19
|
+
```bash
|
|
20
|
+
npm install ata-validator --omit=optional
|
|
21
|
+
```
|
|
22
|
+
|
|
23
|
+
or set `ATA_NO_NATIVE=1` at runtime. Typical schemas compile to specialized JS; shapes the compiler cannot represent (some `$dynamicRef`, cyclic `$ref`, unusual keyword interactions) fall back to an interpreted engine, so every schema validates in every environment. The pure-JS setup passes 99.5% of the official draft 2020-12 test suite. Only the buffer and parallel APIs (`isValid` on raw buffers, `countValid`, `batchIsValid`, `validateAndParse`) need the native engine and say so with a clear error.
|
|
24
|
+
|
|
17
25
|
In your code:
|
|
18
26
|
|
|
19
27
|
```ts
|
|
@@ -227,7 +235,14 @@ type User = Infer<typeof User>
|
|
|
227
235
|
const v = new Validator(User)
|
|
228
236
|
```
|
|
229
237
|
|
|
230
|
-
The builder covers primitives (`string`, `number`, `integer`, `boolean`, `null`), composites (`object` with
|
|
238
|
+
The builder covers primitives (`string`, `number`, `integer`, `boolean`, `null`), composites (`object` with optional keys, `array`, `tuple`, `record`, `union`, `intersect`, `literal`, `const`, `enum`), plus the TypeBox-style modifiers `pick`, `omit`, `partial`, `required`, `composite`, and `recursive`. 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.
|
|
239
|
+
|
|
240
|
+
```ts
|
|
241
|
+
const User = t.object({ id: t.integer(), name: t.string(), email: t.optional(t.string()) })
|
|
242
|
+
const Patch = t.partial(t.omit(User, ['id']))
|
|
243
|
+
type Patch = Infer<typeof Patch>
|
|
244
|
+
// { name?: string; email?: string }
|
|
245
|
+
```
|
|
231
246
|
|
|
232
247
|
#### Async refinement
|
|
233
248
|
|
|
@@ -462,6 +477,8 @@ If one of these blocks you, open an issue; scope decisions get revisited with re
|
|
|
462
477
|
|
|
463
478
|
## Building from Source
|
|
464
479
|
|
|
480
|
+
This section applies to contributors building the repository. Regular `npm install` users need not have a C++ toolchain.
|
|
481
|
+
|
|
465
482
|
### Development prerequisites
|
|
466
483
|
|
|
467
484
|
Native builds require C/C++ toolchain support and the following libraries:
|
|
@@ -470,7 +487,7 @@ Native builds require C/C++ toolchain support and the following libraries:
|
|
|
470
487
|
- `abseil`
|
|
471
488
|
- `mimalloc`
|
|
472
489
|
|
|
473
|
-
Install them before running `npm
|
|
490
|
+
Install them before running `npm run build`:
|
|
474
491
|
|
|
475
492
|
```bash
|
|
476
493
|
# macOS (Homebrew)
|
package/index.js
CHANGED
|
@@ -1,7 +1,7 @@
|
|
|
1
1
|
// Native addon: optional. Core validate() uses JS codegen and works without it.
|
|
2
2
|
// Buffer APIs (isValid, countValid, isValidParallel) require native.
|
|
3
|
-
// Loading is delegated so this file stays free of
|
|
4
|
-
// `path` (the browser entry must not pull those in
|
|
3
|
+
// Loading is delegated to lib/native-load.js so this file stays free of
|
|
4
|
+
// platform probing and `path` (the browser entry must not pull those in).
|
|
5
5
|
const native = require("./lib/native-load")();
|
|
6
6
|
const {
|
|
7
7
|
compileToJS,
|
|
@@ -319,6 +319,47 @@ function resolveSchemaByPath(rootSchema, schemaPath) {
|
|
|
319
319
|
return target;
|
|
320
320
|
}
|
|
321
321
|
|
|
322
|
+
// Rank an error by walking its schemaPath through the schema object: at each
|
|
323
|
+
// level the segment's index among the node's declared keys. Comparing ranks
|
|
324
|
+
// lexicographically orders errors by keyword declaration order, which is the
|
|
325
|
+
// order AJV emits and what schema authors read top to bottom. Segments that
|
|
326
|
+
// cannot be resolved (cross-schema refs, normalized keys) end the walk; the
|
|
327
|
+
// stable sort then keeps such errors in engine emission order.
|
|
328
|
+
function schemaOrderRank(rootSchema, schemaPath) {
|
|
329
|
+
if (!schemaPath || typeof schemaPath !== 'string' || !schemaPath.startsWith('#')) return null;
|
|
330
|
+
const parts = schemaPath.slice(1).split('/').filter(Boolean).map((s) => s.replace(/~1/g, '/').replace(/~0/g, '~'));
|
|
331
|
+
const rank = [];
|
|
332
|
+
let node = rootSchema;
|
|
333
|
+
for (const seg of parts) {
|
|
334
|
+
if (node == null || typeof node !== 'object') break;
|
|
335
|
+
if (Array.isArray(node)) {
|
|
336
|
+
const idx = Number(seg);
|
|
337
|
+
if (!Number.isInteger(idx) || idx < 0 || idx >= node.length) break;
|
|
338
|
+
rank.push(idx);
|
|
339
|
+
node = node[idx];
|
|
340
|
+
} else {
|
|
341
|
+
const idx = Object.keys(node).indexOf(seg);
|
|
342
|
+
if (idx < 0) break;
|
|
343
|
+
rank.push(idx);
|
|
344
|
+
node = node[seg];
|
|
345
|
+
}
|
|
346
|
+
}
|
|
347
|
+
return rank;
|
|
348
|
+
}
|
|
349
|
+
|
|
350
|
+
function sortErrorsBySchemaOrder(rootSchema, errors) {
|
|
351
|
+
const ranked = errors.map((e, i) => ({ e, i, rank: schemaOrderRank(rootSchema, e.schemaPath) }));
|
|
352
|
+
ranked.sort((a, b) => {
|
|
353
|
+
if (!a.rank || !b.rank) return a.i - b.i;
|
|
354
|
+
const n = Math.min(a.rank.length, b.rank.length);
|
|
355
|
+
for (let k = 0; k < n; k++) {
|
|
356
|
+
if (a.rank[k] !== b.rank[k]) return a.rank[k] - b.rank[k];
|
|
357
|
+
}
|
|
358
|
+
return a.i - b.i;
|
|
359
|
+
});
|
|
360
|
+
return ranked.map((r) => r.e);
|
|
361
|
+
}
|
|
362
|
+
|
|
322
363
|
function parsePointerPath(path) {
|
|
323
364
|
if (!path) return [];
|
|
324
365
|
return path
|
|
@@ -569,6 +610,10 @@ class Validator {
|
|
|
569
610
|
};
|
|
570
611
|
} else {
|
|
571
612
|
this._ensureCodegen();
|
|
613
|
+
// Codegen can bail on shapes it cannot represent; the full compile
|
|
614
|
+
// binds the native path or the unsupported thrower instead of
|
|
615
|
+
// leaving this stub to re-dispatch to itself.
|
|
616
|
+
if (!this._jsFn) this._ensureCompiled();
|
|
572
617
|
}
|
|
573
618
|
return this.isValidObject(data);
|
|
574
619
|
};
|
|
@@ -721,16 +766,32 @@ class Validator {
|
|
|
721
766
|
// invalid path doesn't dereference a null _compiled.
|
|
722
767
|
const hasUnevaluated = schemaObj && (schemaObj.unevaluatedProperties !== undefined || schemaObj.unevaluatedItems !== undefined || this._schemaStr.includes('unevaluatedProperties') || this._schemaStr.includes('unevaluatedItems'))
|
|
723
768
|
const hasDynRef = this._schemaStr.includes('"$dynamicRef"') || this._schemaStr.includes('"$dynamicAnchor"')
|
|
724
|
-
|
|
725
|
-
|
|
726
|
-
|
|
727
|
-
|
|
728
|
-
|
|
729
|
-
|
|
730
|
-
|
|
731
|
-
|
|
732
|
-
|
|
733
|
-
|
|
769
|
+
// Native-less error path: the interpreted engine re-validates failing
|
|
770
|
+
// data to produce full errors. If it disagrees with the codegen verdict
|
|
771
|
+
// (it should not), a generic error keeps the result consistent.
|
|
772
|
+
let _interp = null;
|
|
773
|
+
const jsOnlyFallback = (d) => {
|
|
774
|
+
if (jsFn(d)) return { valid: true, data: d, errors: [] };
|
|
775
|
+
if (!_interp) {
|
|
776
|
+
const { createInterpreter } = require('./lib/interpreter');
|
|
777
|
+
_interp = createInterpreter(schemaObj, {
|
|
778
|
+
schemaMap: this._schemaMap.size > 0 ? this._schemaMap : null,
|
|
779
|
+
formats: this._userFormats,
|
|
780
|
+
});
|
|
781
|
+
}
|
|
782
|
+
const r = _interp.validate(d);
|
|
783
|
+
if (!r.valid) return r;
|
|
784
|
+
return {
|
|
785
|
+
valid: false,
|
|
786
|
+
errors: [{
|
|
787
|
+
keyword: 'validation',
|
|
788
|
+
instancePath: '',
|
|
789
|
+
schemaPath: '',
|
|
790
|
+
params: {},
|
|
791
|
+
message: 'schema validation failed'
|
|
792
|
+
}]
|
|
793
|
+
};
|
|
794
|
+
};
|
|
734
795
|
const errFn =
|
|
735
796
|
safeErrFn ||
|
|
736
797
|
(hasUnevaluated
|
|
@@ -1004,6 +1065,43 @@ class Validator {
|
|
|
1004
1065
|
return valid;
|
|
1005
1066
|
};
|
|
1006
1067
|
}
|
|
1068
|
+
} else {
|
|
1069
|
+
// No JS codegen and no native engine: fall back to the interpreted
|
|
1070
|
+
// engine. Slow but correct, and strictly better than the previous
|
|
1071
|
+
// behavior (the lazy stubs re-dispatched to themselves forever).
|
|
1072
|
+
const { createInterpreter } = require('./lib/interpreter');
|
|
1073
|
+
const interp = createInterpreter(schemaObj, {
|
|
1074
|
+
schemaMap: this._schemaMap.size > 0 ? this._schemaMap : null,
|
|
1075
|
+
formats: this._userFormats,
|
|
1076
|
+
});
|
|
1077
|
+
const run = preprocess
|
|
1078
|
+
? (data) => { preprocess(data); return interp.validate(data); }
|
|
1079
|
+
: (data) => interp.validate(data);
|
|
1080
|
+
this.validate = run;
|
|
1081
|
+
this.isValidObject = (data) => run(data).valid;
|
|
1082
|
+
this.validateJSON = (jsonStr) => {
|
|
1083
|
+
try {
|
|
1084
|
+
return run(JSON.parse(jsonStr));
|
|
1085
|
+
} catch (e) {
|
|
1086
|
+
return { valid: false, errors: [{ keyword: 'syntax', instancePath: '', schemaPath: '#', params: {}, message: e.message }] };
|
|
1087
|
+
}
|
|
1088
|
+
};
|
|
1089
|
+
this.isValidJSON = (jsonStr) => this.validateJSON(jsonStr).valid;
|
|
1090
|
+
}
|
|
1091
|
+
|
|
1092
|
+
// Declaration-order errors: whichever engine produced them, multi-error
|
|
1093
|
+
// results are sorted by the schema's keyword declaration order before
|
|
1094
|
+
// enrichment. Single-error and abortEarly results pass through untouched.
|
|
1095
|
+
if (this.validate) {
|
|
1096
|
+
const inner = this.validate;
|
|
1097
|
+
const root = this._schemaObj;
|
|
1098
|
+
this.validate = (data) => {
|
|
1099
|
+
const result = inner(data);
|
|
1100
|
+
if (result && !result.valid && result.errors && result.errors.length > 1 && result !== ABORT_EARLY_RESULT) {
|
|
1101
|
+
return { valid: false, errors: sortErrorsBySchemaOrder(root, result.errors) };
|
|
1102
|
+
}
|
|
1103
|
+
return result;
|
|
1104
|
+
};
|
|
1007
1105
|
}
|
|
1008
1106
|
|
|
1009
1107
|
// richErrors enrichment: layered on top of whichever validate path was
|
package/lib/aot.js
CHANGED
|
@@ -1,7 +1,7 @@
|
|
|
1
1
|
'use strict';
|
|
2
2
|
|
|
3
3
|
// All ahead-of-time (AOT) code generation lives here so the default and browser
|
|
4
|
-
// entries can stay free of `fs`, `path`,
|
|
4
|
+
// entries can stay free of `fs`, `path`, and `__dirname`.
|
|
5
5
|
// Browser bundles get `lib/aot.browser.js` instead (see package.json `browser`
|
|
6
6
|
// field), which throws if anyone tries to call an AOT function from the browser.
|
|
7
7
|
//
|