ata-validator 1.5.1 → 1.6.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 +8 -0
- package/README.md +12 -3
- package/index.js +21 -6
- package/lib/dialect.js +31 -0
- package/lib/interpreter.js +15 -4
- package/lib/version.js +1 -1
- package/package.json +9 -9
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.6.0 - 2026-08-08
|
|
6
|
+
|
|
7
|
+
### Added
|
|
8
|
+
|
|
9
|
+
- The JSON Schema v1 dialect. A schema declaring `"$schema": "https://json-schema.org/v1"`, or the dated `https://json-schema.org/v1/2026` the specification repository's meta-schema carries, is now validated under v1 rather than under 2020-12. The difference ata implements is `$dynamicRef`: v1 removes the bookending requirement, so a reference resolves through the dynamic scope whether or not the schema it initially lands on carries a matching `$dynamicAnchor`, and also when it resolves to nothing on its own. The outermost matching anchor still in scope wins, as before. `propertyDependencies`, the other v1 addition, shipped in 1.5.0.
|
|
10
|
+
- Against the suite's `v1` directory with nothing excluded, ata scores 1123 of 1127. The four it misses are the same four that fail on 2020-12: one `$dynamicRef` scope corner each engine misses, a definition validated against the meta-schema, and two remote-reference cases. `npm run test:suite` now runs the dialect alongside 2020-12 and draft 7, `tests/test_no_eval.js` runs it with code generation blocked (1124 of 1127 there), and `tests/test_v1_dialect.js` checks the switch itself: the same document must not validate the same way under both dialects.
|
|
11
|
+
- Only `$dynamicRef` routing changes. Everything else ata implements is identical under v1 and 2020-12, so a v1 schema that does not use the keyword takes the same compiled path it always did. One that does use it validates on the interpreted engine, since the JS compiler and the native addon both resolve the 2020-12 way. The native engine does not implement bookending at all, which is invisible to the official 2020-12 suite but means it cannot be trusted to answer for either dialect here.
|
|
12
|
+
|
|
5
13
|
## 1.5.1 - 2026-08-07
|
|
6
14
|
|
|
7
15
|
### Fixed
|
package/README.md
CHANGED
|
@@ -461,8 +461,17 @@ Copy-paste recipes for the common frameworks. Most need 10-20 lines of glue. See
|
|
|
461
461
|
| Enum/Const | `enum`, `const` |
|
|
462
462
|
| Composition | `allOf`, `anyOf`, `oneOf`, `not` |
|
|
463
463
|
| Conditional | `if`, `then`, `else` |
|
|
464
|
-
| References | `$ref`, `$defs`, `definitions`, `$id` |
|
|
464
|
+
| References | `$ref`, `$defs`, `definitions`, `$id`, `$anchor`, `$dynamicRef`, `$dynamicAnchor` |
|
|
465
465
|
| Boolean | `true`, `false` |
|
|
466
|
+
| v1 | `propertyDependencies` |
|
|
467
|
+
|
|
468
|
+
### Dialects
|
|
469
|
+
|
|
470
|
+
`$schema` selects the dialect. Draft 2020-12 is the default, draft-07 is normalized on the way in, and `https://json-schema.org/v1` (or the dated `https://json-schema.org/v1/2026`) selects JSON Schema v1.
|
|
471
|
+
|
|
472
|
+
Two things differ under v1. `propertyDependencies` selects a subschema by the value of a property rather than by its presence, which is what `dependentSchemas` does. And `$dynamicRef` no longer requires bookending: the reference resolves through the dynamic scope whether or not the schema it first lands on carries a matching `$dynamicAnchor`, so the outermost matching anchor still in scope wins. Everything else ata implements is identical under both dialects, so a schema that declares no `$schema` behaves exactly as before.
|
|
473
|
+
|
|
474
|
+
Both are implemented in the interpreted engine, so a v1 schema that uses `$dynamicRef` validates there rather than through the compiler or the native addon, which resolve the 2020-12 way. Schemas that use neither keyword take the same compiled path they always did.
|
|
466
475
|
|
|
467
476
|
### Format Validators (hand-written, no regex)
|
|
468
477
|
|
|
@@ -470,12 +479,12 @@ Copy-paste recipes for the common frameworks. Most need 10-20 lines of glue. See
|
|
|
470
479
|
|
|
471
480
|
### Known limitations
|
|
472
481
|
|
|
473
|
-
Running the whole Draft 2020-12 suite with nothing excluded, `format` and `default` under specification semantics (`assertFormat: false`, `useDefaults: false`), gives 1285 of 1290 cases, 99.6%. Draft 7 gives 911 of 922, 98.8%. `npm run test:suite` reproduces
|
|
482
|
+
Running the whole Draft 2020-12 suite with nothing excluded, `format` and `default` under specification semantics (`assertFormat: false`, `useDefaults: false`), gives 1285 of 1290 cases, 99.6%. Draft 7 gives 911 of 922, 98.8%. The v1 dialect gives 1123 of 1127, 99.6%, and the four it misses are the same four that fail on 2020-12. `npm run test:suite` reproduces all three and lists the remaining failures by name.
|
|
474
483
|
|
|
475
484
|
Areas that remain deliberate scope decisions for 1.x:
|
|
476
485
|
|
|
477
486
|
- **Remote `$ref` over the network** is not fetched. Register cross-schema refs explicitly with `schemas: [...]` or `addSchema()`.
|
|
478
|
-
- **`$vocabulary`** is not implemented; custom vocabularies are ignored rather than enforced.
|
|
487
|
+
- **`$vocabulary`** is not implemented; custom vocabularies are ignored rather than enforced. The keyword was extracted from the specification for the stable release as incomplete, so it is not part of v1.
|
|
479
488
|
- **`contentEncoding` / `contentMediaType` / `contentSchema`** are annotation-only, as the spec permits, and are not validated.
|
|
480
489
|
- **`minLength`/`maxLength`** count UTF-16 code units, not grapheme clusters.
|
|
481
490
|
- **Infinite-loop detection** relies on a recursion depth guard that cuts off circular `$ref` chains.
|
package/index.js
CHANGED
|
@@ -10,6 +10,7 @@ const {
|
|
|
10
10
|
compileToJSCombined,
|
|
11
11
|
} = require("./lib/js-compiler");
|
|
12
12
|
const { normalizeDraft7, normalizeNullable, stripFormatAssertions } = require("./lib/draft7");
|
|
13
|
+
const { isV1Dialect } = require("./lib/dialect");
|
|
13
14
|
const { classify } = require("./lib/shape-classifier");
|
|
14
15
|
const { buildTier0Plan, tier0Validate } = require("./lib/tier0");
|
|
15
16
|
|
|
@@ -724,11 +725,22 @@ class Validator {
|
|
|
724
725
|
const cached = this._userFormats ? null : _compileCache.get(mapKey);
|
|
725
726
|
let jsFn, jsCombinedFn, jsErrFn, _isCodegen = false;
|
|
726
727
|
var _forceNapi = typeof process !== 'undefined' && process.env && process.env.ATA_FORCE_NAPI;
|
|
727
|
-
//
|
|
728
|
-
//
|
|
729
|
-
//
|
|
730
|
-
//
|
|
731
|
-
|
|
728
|
+
// v1 removes the bookending requirement for $dynamicRef. Only the
|
|
729
|
+
// interpreted engine implements that; the JS compiler and the native
|
|
730
|
+
// engine both resolve the 2020-12 way, so a v1 schema using the keyword
|
|
731
|
+
// goes to the interpreter rather than being validated under the wrong
|
|
732
|
+
// dialect. Schemas without $dynamicRef are unaffected: v1 and 2020-12
|
|
733
|
+
// agree on everything else ata implements.
|
|
734
|
+
this._v1Dynamic =
|
|
735
|
+
isV1Dialect(schemaObj) &&
|
|
736
|
+
(this._schemaStr.includes('"$dynamicRef"') || this._schemaStr.includes('"$dynamicAnchor"'));
|
|
737
|
+
//
|
|
738
|
+
// Where source cannot be turned into a function, neither JS path is usable
|
|
739
|
+
// either. The closure path does not call `new Function` itself, so it
|
|
740
|
+
// survives the block and would quietly handle schemas it gets wrong; the
|
|
741
|
+
// interpreted engine is both eval-free and more correct, so go straight
|
|
742
|
+
// there.
|
|
743
|
+
if (this._v1Dynamic || !codegenAvailable()) {
|
|
732
744
|
jsFn = null; jsCombinedFn = null; jsErrFn = null;
|
|
733
745
|
} else if (cached && !_forceNapi) {
|
|
734
746
|
jsFn = cached.jsFn;
|
|
@@ -812,6 +824,7 @@ class Validator {
|
|
|
812
824
|
_interp = createInterpreter(schemaObj, {
|
|
813
825
|
schemaMap: this._schemaMap.size > 0 ? this._schemaMap : null,
|
|
814
826
|
formats: this._userFormats,
|
|
827
|
+
v1: isV1Dialect(schemaObj),
|
|
815
828
|
});
|
|
816
829
|
}
|
|
817
830
|
const r = _interp.validate(d);
|
|
@@ -1065,7 +1078,7 @@ class Validator {
|
|
|
1065
1078
|
// using it goes there even when it also uses $dynamicRef.
|
|
1066
1079
|
const _hasPropDeps = this._schemaStr.includes('"propertyDependencies"')
|
|
1067
1080
|
let _validate;
|
|
1068
|
-
if (_hasDynRef && !_hasUneval && !_hasPropDeps) {
|
|
1081
|
+
if (_hasDynRef && !_hasUneval && !_hasPropDeps && !this._v1Dynamic) {
|
|
1069
1082
|
// validateJSON is the C++ path with full anchor-map support; the NAPI
|
|
1070
1083
|
// direct V8 `validate` path has no anchor maps.
|
|
1071
1084
|
_validate = (data) => this._compiled.validateJSON(JSON.stringify(data));
|
|
@@ -1076,6 +1089,7 @@ class Validator {
|
|
|
1076
1089
|
const interp = createInterpreter(schemaObj, {
|
|
1077
1090
|
schemaMap: this._schemaMap.size > 0 ? this._schemaMap : null,
|
|
1078
1091
|
formats: this._userFormats,
|
|
1092
|
+
v1: isV1Dialect(schemaObj),
|
|
1079
1093
|
});
|
|
1080
1094
|
_validate = (data) => interp.validate(data);
|
|
1081
1095
|
this.validateJSON = (jsonStr) => {
|
|
@@ -1133,6 +1147,7 @@ class Validator {
|
|
|
1133
1147
|
const interp = createInterpreter(schemaObj, {
|
|
1134
1148
|
schemaMap: this._schemaMap.size > 0 ? this._schemaMap : null,
|
|
1135
1149
|
formats: this._userFormats,
|
|
1150
|
+
v1: isV1Dialect(schemaObj),
|
|
1136
1151
|
});
|
|
1137
1152
|
const run = preprocess
|
|
1138
1153
|
? (data) => { preprocess(data); return interp.validate(data); }
|
package/lib/dialect.js
ADDED
|
@@ -0,0 +1,31 @@
|
|
|
1
|
+
'use strict';
|
|
2
|
+
|
|
3
|
+
// Dialect detection.
|
|
4
|
+
//
|
|
5
|
+
// Only the v1 dialect is distinguished here, and only because v1 changes
|
|
6
|
+
// behavior that is otherwise identical to 2020-12: the bookending requirement
|
|
7
|
+
// for `$dynamicRef` is removed. Everything else ata does under 2020-12 is
|
|
8
|
+
// unchanged under v1, so a schema that declares no `$schema` keeps the
|
|
9
|
+
// existing default rather than being guessed at.
|
|
10
|
+
//
|
|
11
|
+
// The official test suite states the dialect as `https://json-schema.org/v1`.
|
|
12
|
+
// The meta-schema in the specification repository carries the dated
|
|
13
|
+
// `https://json-schema.org/v1/2026`. Both name the same dialect, and
|
|
14
|
+
// `draft/next` is the name the same work carried before the stable release was
|
|
15
|
+
// numbered, so all three are accepted.
|
|
16
|
+
const V1_DIALECTS = new Set([
|
|
17
|
+
'https://json-schema.org/v1',
|
|
18
|
+
'https://json-schema.org/v1/schema',
|
|
19
|
+
'https://json-schema.org/v1/2026',
|
|
20
|
+
'https://json-schema.org/draft/next/schema',
|
|
21
|
+
]);
|
|
22
|
+
|
|
23
|
+
function isV1Dialect(schema) {
|
|
24
|
+
if (typeof schema !== 'object' || schema === null) return false;
|
|
25
|
+
if (typeof schema.$schema !== 'string') return false;
|
|
26
|
+
// A trailing '#' is an empty fragment naming the same document.
|
|
27
|
+
const uri = schema.$schema.endsWith('#') ? schema.$schema.slice(0, -1) : schema.$schema;
|
|
28
|
+
return V1_DIALECTS.has(uri);
|
|
29
|
+
}
|
|
30
|
+
|
|
31
|
+
module.exports = { isV1Dialect, V1_DIALECTS };
|
package/lib/interpreter.js
CHANGED
|
@@ -265,6 +265,9 @@ class Interpreter {
|
|
|
265
265
|
this.root = rootSchema;
|
|
266
266
|
this.state = indexSchemas(rootSchema, opts.schemaMap);
|
|
267
267
|
this.userFormats = opts.formats || null;
|
|
268
|
+
// 2020-12 requires bookending for `$dynamicRef`; v1 removes the
|
|
269
|
+
// requirement. See the `$dynamicRef` branch in eval().
|
|
270
|
+
this.bookending = !opts.v1;
|
|
268
271
|
this.patternCache = new Map();
|
|
269
272
|
}
|
|
270
273
|
|
|
@@ -337,11 +340,19 @@ class Interpreter {
|
|
|
337
340
|
const ref = schema.$dynamicRef;
|
|
338
341
|
let { node, base: refBase } = resolveRef(ref, base, this.state);
|
|
339
342
|
const [, fragment] = splitFragment(resolveUri(base, ref));
|
|
340
|
-
if (
|
|
341
|
-
//
|
|
342
|
-
//
|
|
343
|
+
if (fragment && !fragment.startsWith('/')) {
|
|
344
|
+
// Under 2020-12 the initial target must itself carry the matching
|
|
345
|
+
// $dynamicAnchor before the dynamic scope is consulted (bookending);
|
|
346
|
+
// otherwise the keyword behaves like $ref. v1 removes that
|
|
347
|
+
// requirement, so the scope is searched whether or not the initial
|
|
348
|
+
// target carries the anchor, and also when the reference does not
|
|
349
|
+
// resolve on its own, which is the shape the suite uses: a
|
|
350
|
+
// `$dynamicRef` in a resource that declares no anchor of that name.
|
|
343
351
|
const initialDyn = this.state.dynamicAnchors.get(refBase);
|
|
344
|
-
|
|
352
|
+
const bookended = node !== undefined && initialDyn && initialDyn.get(fragment) === node;
|
|
353
|
+
if (bookended || !this.bookending) {
|
|
354
|
+
// Outermost scope first: the first matching $dynamicAnchor
|
|
355
|
+
// encountered while evaluating wins.
|
|
345
356
|
for (const scopeBase of dynScope) {
|
|
346
357
|
const dyn = this.state.dynamicAnchors.get(scopeBase);
|
|
347
358
|
if (dyn && dyn.has(fragment)) {
|
package/lib/version.js
CHANGED
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "ata-validator",
|
|
3
|
-
"version": "1.
|
|
3
|
+
"version": "1.6.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",
|
|
@@ -44,9 +44,9 @@
|
|
|
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_no_eval.js && node tests/test_property_dependencies.js && node tests/test_buffer_path_parity.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_format_engine_parity.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_no_eval.js && node tests/test_property_dependencies.js && node tests/test_v1_dialect.js && node tests/test_buffer_path_parity.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_format_engine_parity.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
|
-
"test:suite": "node tests/run_suite.js && node tests/run_suite.js draft7",
|
|
49
|
+
"test:suite": "node tests/run_suite.js && node tests/run_suite.js draft7 && node tests/run_suite.js v1",
|
|
50
50
|
"test:compat": "node tests/test_compat.js",
|
|
51
51
|
"test:standard-schema": "node tests/test_standard_schema.js",
|
|
52
52
|
"test:browser": "node tests/test_browser.js",
|
|
@@ -112,12 +112,12 @@
|
|
|
112
112
|
"LICENSE"
|
|
113
113
|
],
|
|
114
114
|
"optionalDependencies": {
|
|
115
|
-
"@ata-validator/native-darwin-arm64": "1.
|
|
116
|
-
"@ata-validator/native-linux-x64-gnu": "1.
|
|
117
|
-
"@ata-validator/native-linux-arm64-gnu": "1.
|
|
118
|
-
"@ata-validator/native-linux-x64-musl": "1.
|
|
119
|
-
"@ata-validator/native-linux-arm64-musl": "1.
|
|
120
|
-
"@ata-validator/native-win32-x64": "1.
|
|
115
|
+
"@ata-validator/native-darwin-arm64": "1.6.0",
|
|
116
|
+
"@ata-validator/native-linux-x64-gnu": "1.6.0",
|
|
117
|
+
"@ata-validator/native-linux-arm64-gnu": "1.6.0",
|
|
118
|
+
"@ata-validator/native-linux-x64-musl": "1.6.0",
|
|
119
|
+
"@ata-validator/native-linux-arm64-musl": "1.6.0",
|
|
120
|
+
"@ata-validator/native-win32-x64": "1.6.0"
|
|
121
121
|
},
|
|
122
122
|
"peerDependencies": {
|
|
123
123
|
"yaml": "^2.0.0"
|