ata-validator 1.2.0 → 1.2.1
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 +8 -0
- package/README.md +4 -2
- package/index.js +31 -9
- package/lib/js-compiler.js +27 -1
- package/lib/version.js +1 -1
- package/package.json +8 -8
package/CHANGELOG.md
CHANGED
|
@@ -2,6 +2,14 @@
|
|
|
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.1 - 2026-07-18
|
|
6
|
+
|
|
7
|
+
### Fixed
|
|
8
|
+
|
|
9
|
+
- Schemas the JS compiler cannot represent now validate on the engine that gets them right, instead of always preferring the native addon when it is installed. The native resolver mishandles several `$id` base-URI corners (URN bases, absolute-path references, empty JSON-pointer tokens, nested `$id` scopes) and silently skips regex patterns its engine cannot parse; all of these now route to the interpreted engine. Pure dynamic-ref schemas stay on the native path, which tracks `$dynamicRef` scopes more completely. Buffer and parallel APIs are unchanged.
|
|
10
|
+
- The JS compiler no longer compiles two shapes it got wrong: `unevaluatedItems` with `contains` in scope (contains-matched items were never credited as evaluated, rejecting valid arrays) and plain-anchor schemas that open nested `$id` scopes (same-named anchors in different base-URI scopes resolved to the wrong target). Both now go to the interpreted engine.
|
|
11
|
+
- Official draft 2020-12 suite: 1190 of 1190 applicable cases with the native accelerator (up from 1175), 1188 of 1190 (99.8%) pure-JS (up from 1184). Draft 7 suite gains four `ref.json` cases.
|
|
12
|
+
|
|
5
13
|
## 1.2.0 - 2026-07-18
|
|
6
14
|
|
|
7
15
|
### Added
|
package/README.md
CHANGED
|
@@ -20,7 +20,7 @@ The `ata-validator` package itself is pure JavaScript. The native accelerator (s
|
|
|
20
20
|
npm install ata-validator --omit=optional
|
|
21
21
|
```
|
|
22
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.
|
|
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.8% 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
24
|
|
|
25
25
|
In your code:
|
|
26
26
|
|
|
@@ -396,6 +396,8 @@ const result = v['~standard'].validate(data);
|
|
|
396
396
|
|
|
397
397
|
### Fastify Plugin
|
|
398
398
|
|
|
399
|
+
Measured against Fastify's own schema and validation test files with ata swapped in as the default validator: 181 of 187 tests pass, and the remaining six assert the default validator's own extension API rather than validation behavior. Validation errors follow the schema's keyword declaration order, so error-message plugins and `transformErrors` hooks written against the default validator see the same order.
|
|
400
|
+
|
|
399
401
|
```bash
|
|
400
402
|
npm install fastify-ata
|
|
401
403
|
```
|
|
@@ -464,7 +466,7 @@ Copy-paste recipes for the common frameworks. Most need 10-20 lines of glue. See
|
|
|
464
466
|
|
|
465
467
|
### Known limitations
|
|
466
468
|
|
|
467
|
-
The
|
|
469
|
+
The Draft 2020-12 pass rate (1188 of 1190 applicable cases, 99.8%, in the pure-JS configuration; 1190 of 1190 with the native accelerator installed, which covers the two remaining `$dynamicRef` scope corners) excludes areas that are deliberate scope decisions for 1.x:
|
|
468
470
|
|
|
469
471
|
- **Remote `$ref` over the network** is not fetched. Register cross-schema refs explicitly with `schemas: [...]` or `addSchema()`.
|
|
470
472
|
- **`$vocabulary`** is not implemented; custom vocabularies are ignored rather than enforced.
|
package/index.js
CHANGED
|
@@ -1017,14 +1017,38 @@ class Validator {
|
|
|
1017
1017
|
};
|
|
1018
1018
|
}
|
|
1019
1019
|
} else if (native) {
|
|
1020
|
-
//
|
|
1020
|
+
// No JS codegen: buffer/parallel APIs always come from the native
|
|
1021
|
+
// engine, but the object-validation entry points go to whichever
|
|
1022
|
+
// engine is more correct for the schema shape. Pure dynamic-ref
|
|
1023
|
+
// schemas stay on the C++ path (full $dynamicRef scope tracking);
|
|
1024
|
+
// everything else uses the interpreted engine, which handles the
|
|
1025
|
+
// $id/URN base-URI resolution corners the native resolver gets wrong.
|
|
1021
1026
|
this._ensureNative();
|
|
1022
|
-
const
|
|
1023
|
-
|
|
1024
|
-
|
|
1025
|
-
|
|
1026
|
-
|
|
1027
|
-
|
|
1027
|
+
const _hasDynRef = this._schemaStr.includes('"$dynamicRef"') || this._schemaStr.includes('"$dynamicAnchor"')
|
|
1028
|
+
const _hasUneval = this._schemaStr.includes('"unevaluatedProperties"') || this._schemaStr.includes('"unevaluatedItems"')
|
|
1029
|
+
let _validate;
|
|
1030
|
+
if (_hasDynRef && !_hasUneval) {
|
|
1031
|
+
// validateJSON is the C++ path with full anchor-map support; the NAPI
|
|
1032
|
+
// direct V8 `validate` path has no anchor maps.
|
|
1033
|
+
_validate = (data) => this._compiled.validateJSON(JSON.stringify(data));
|
|
1034
|
+
this.validateJSON = (jsonStr) => this._compiled.validateJSON(jsonStr);
|
|
1035
|
+
this.isValidJSON = (jsonStr) => this._compiled.isValidJSON(jsonStr);
|
|
1036
|
+
} else {
|
|
1037
|
+
const { createInterpreter } = require('./lib/interpreter');
|
|
1038
|
+
const interp = createInterpreter(schemaObj, {
|
|
1039
|
+
schemaMap: this._schemaMap.size > 0 ? this._schemaMap : null,
|
|
1040
|
+
formats: this._userFormats,
|
|
1041
|
+
});
|
|
1042
|
+
_validate = (data) => interp.validate(data);
|
|
1043
|
+
this.validateJSON = (jsonStr) => {
|
|
1044
|
+
try {
|
|
1045
|
+
return _validate(JSON.parse(jsonStr));
|
|
1046
|
+
} catch (e) {
|
|
1047
|
+
return { valid: false, errors: [{ keyword: 'syntax', instancePath: '', schemaPath: '#', params: {}, message: e.message }] };
|
|
1048
|
+
}
|
|
1049
|
+
};
|
|
1050
|
+
this.isValidJSON = (jsonStr) => this.validateJSON(jsonStr).valid;
|
|
1051
|
+
}
|
|
1028
1052
|
this.validate = preprocess
|
|
1029
1053
|
? (data) => {
|
|
1030
1054
|
preprocess(data);
|
|
@@ -1032,8 +1056,6 @@ class Validator {
|
|
|
1032
1056
|
}
|
|
1033
1057
|
: _validate;
|
|
1034
1058
|
this.isValidObject = (data) => _validate(data).valid;
|
|
1035
|
-
this.validateJSON = (jsonStr) => this._compiled.validateJSON(jsonStr);
|
|
1036
|
-
this.isValidJSON = (jsonStr) => this._compiled.isValidJSON(jsonStr);
|
|
1037
1059
|
this.validateAndParse = (jsonStr) => this._compiled.validateAndParse(jsonStr);
|
|
1038
1060
|
{
|
|
1039
1061
|
const slot = this._fastSlot;
|
package/lib/js-compiler.js
CHANGED
|
@@ -99,7 +99,12 @@ function compileToJS(schema, defs, schemaMap) {
|
|
|
99
99
|
// Exception: $dynamicRef/$anchor are handled by the interpretive path even though codegen can't
|
|
100
100
|
if (!defs && !codegenSafe(schema, schemaMap)) {
|
|
101
101
|
const str = JSON.stringify(schema)
|
|
102
|
-
|
|
102
|
+
const hasDynamic = str.includes('"$dynamicRef"') || str.includes('"$dynamicAnchor"')
|
|
103
|
+
if (!hasDynamic && !str.includes('"$anchor"')) return null
|
|
104
|
+
// The flat anchor map resolves "#name" without regard to $id base-URI
|
|
105
|
+
// scopes. When a plain-anchor schema opens nested $id scopes, same-named
|
|
106
|
+
// anchors can shadow each other, so those schemas must not use codegen.
|
|
107
|
+
if (!hasDynamic && hasNestedIdScope(schema)) return null
|
|
103
108
|
}
|
|
104
109
|
|
|
105
110
|
// Collect $defs early so sub-schemas can resolve $ref
|
|
@@ -723,6 +728,23 @@ function subtreeRefersToLocalDef(node, defNames) {
|
|
|
723
728
|
return false
|
|
724
729
|
}
|
|
725
730
|
|
|
731
|
+
// Returns true if any sub-schema below the root declares a non-fragment $id,
|
|
732
|
+
// i.e. opens a new base-URI scope. Fragment-only ids ('#name') are draft-07
|
|
733
|
+
// anchors, not scope changes.
|
|
734
|
+
function hasNestedIdScope(node, isRoot = true) {
|
|
735
|
+
if (typeof node !== 'object' || node === null) return false
|
|
736
|
+
if (!isRoot && typeof node.$id === 'string' && !node.$id.startsWith('#')) return true
|
|
737
|
+
for (const val of Object.values(node)) {
|
|
738
|
+
if (typeof val !== 'object' || val === null) continue
|
|
739
|
+
if (Array.isArray(val)) {
|
|
740
|
+
for (const item of val) { if (hasNestedIdScope(item, false)) return true }
|
|
741
|
+
} else if (hasNestedIdScope(val, false)) {
|
|
742
|
+
return true
|
|
743
|
+
}
|
|
744
|
+
}
|
|
745
|
+
return false
|
|
746
|
+
}
|
|
747
|
+
|
|
726
748
|
// Recursively check if a schema can be safely compiled to JS codegen.
|
|
727
749
|
// Returns false if any sub-schema contains features codegen gets wrong.
|
|
728
750
|
function codegenSafe(schema, schemaMap) {
|
|
@@ -822,6 +844,10 @@ function codegenSafe(schema, schemaMap) {
|
|
|
822
844
|
}
|
|
823
845
|
// unevaluatedItems: allow boolean and schema values
|
|
824
846
|
if (schema.unevaluatedItems !== undefined) {
|
|
847
|
+
// contains marks matched items as evaluated; the generated evaluated-item
|
|
848
|
+
// tracking never credits contains matches, so any contains in scope of an
|
|
849
|
+
// unevaluatedItems keyword must go to the interpreted engine.
|
|
850
|
+
if (JSON.stringify(schema).includes('"contains"')) return false
|
|
825
851
|
if (typeof schema.unevaluatedItems === 'object' && schema.unevaluatedItems !== null) {
|
|
826
852
|
if (!codegenSafe(schema.unevaluatedItems, schemaMap)) return false
|
|
827
853
|
}
|
package/lib/version.js
CHANGED
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "ata-validator",
|
|
3
|
-
"version": "1.2.
|
|
3
|
+
"version": "1.2.1",
|
|
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",
|
|
@@ -44,7 +44,7 @@
|
|
|
44
44
|
"release:check": "node scripts/regen-safe-regex-source.js && node tests/test_pack_purity.js && node scripts/check-doc-coverage.js && node tests/test_error_codes_lock.js && node tests/test_safe_regex_source_sync.js && node tests/test_version_sync.js",
|
|
45
45
|
"build": "cmake-js build --target ata",
|
|
46
46
|
"rebuild": "cmake-js rebuild --target ata",
|
|
47
|
-
"test": "node test.js && node tests/test_removed_aot_methods.js && node tests/test_no_native.js && node tests/test_pure_js_unsupported.js && node tests/test_native_load_order.js && node tests/test_pack_purity.js && node tests/test_make_native_package.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_defs_pointer_alias.js && node tests/test_no_input_mutation.js && node tests/test_typed_validator_runner.js && node tests/test_define_schema.js && node tests/test_error_codes_lock.js && node tests/test_error_order.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",
|
|
47
|
+
"test": "node test.js && node tests/test_removed_aot_methods.js && node tests/test_no_native.js && node tests/test_pure_js_unsupported.js && node tests/test_native_load_order.js && node tests/test_pack_purity.js && node tests/test_make_native_package.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_engine_routing.js && node tests/test_defs_pointer_alias.js && node tests/test_no_input_mutation.js && node tests/test_typed_validator_runner.js && node tests/test_define_schema.js && node tests/test_error_codes_lock.js && node tests/test_error_order.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",
|
|
48
48
|
"bench:size": "node benchmark/bench_aot_size.mjs",
|
|
49
49
|
"test:suite": "node tests/run_suite.js",
|
|
50
50
|
"test:compat": "node tests/test_compat.js",
|
|
@@ -112,12 +112,12 @@
|
|
|
112
112
|
"LICENSE"
|
|
113
113
|
],
|
|
114
114
|
"optionalDependencies": {
|
|
115
|
-
"@ata-validator/native-darwin-arm64": "1.2.
|
|
116
|
-
"@ata-validator/native-linux-x64-gnu": "1.2.
|
|
117
|
-
"@ata-validator/native-linux-arm64-gnu": "1.2.
|
|
118
|
-
"@ata-validator/native-linux-x64-musl": "1.2.
|
|
119
|
-
"@ata-validator/native-linux-arm64-musl": "1.2.
|
|
120
|
-
"@ata-validator/native-win32-x64": "1.2.
|
|
115
|
+
"@ata-validator/native-darwin-arm64": "1.2.1",
|
|
116
|
+
"@ata-validator/native-linux-x64-gnu": "1.2.1",
|
|
117
|
+
"@ata-validator/native-linux-arm64-gnu": "1.2.1",
|
|
118
|
+
"@ata-validator/native-linux-x64-musl": "1.2.1",
|
|
119
|
+
"@ata-validator/native-linux-arm64-musl": "1.2.1",
|
|
120
|
+
"@ata-validator/native-win32-x64": "1.2.1"
|
|
121
121
|
},
|
|
122
122
|
"peerDependencies": {
|
|
123
123
|
"yaml": "^2.0.0"
|