ata-validator 1.8.1 → 1.8.2
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 +14 -0
- package/index.js +55 -3
- package/lib/js-compiler.js +11 -3
- package/lib/version.js +1 -1
- package/package.json +9 -9
package/CHANGELOG.md
CHANGED
|
@@ -2,6 +2,20 @@
|
|
|
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.8.2 - 2026-08-28
|
|
6
|
+
|
|
7
|
+
### Fixed
|
|
8
|
+
|
|
9
|
+
- A `pattern` of the form `^[class]+$`, `^[class]{m,n}$` or `^[class]{n}$` with n above 16 could be violated without `validate()` saying so. The boolean program compiles those patterns to an inline character loop wrapped in an arrow function, and the rewrite that turns the boolean program into the error-reporting one replaced the `return false` and `return true` inside that arrow with `return E(d)` and `return R`. Both are objects, so the arrow started returning a truthy value on mismatch, the enclosing `!(cond && obj)` went false, and the error program accepted what the boolean program had rejected.
|
|
10
|
+
|
|
11
|
+
What that looked like from outside depended on the path. Without preprocessing, `validate()` still rejected, because the boolean verdict runs first, but the error it produced was a generic `schema validation failed` with no keyword, no `params.pattern` and no path. With preprocessing, which means any schema carrying a `default` or a validator built with `coerceTypes` or `removeAdditional`, the error program is installed as `validate()` directly and the result was `valid: true`. `Validator.bundleStandalone` and `bundleCompact` embed the same program and had the same silent accept. `ata compile` output is built from the boolean program and was not affected. The rewrite has been wrong since 0.6.0; the official suite never exercised these pattern shapes, so nothing caught it.
|
|
12
|
+
|
|
13
|
+
The rewrite now leaves the body of an arrow function alone, the same way it already left `function` bodies alone. `tests/test_hybrid_agreement.js` compares the rewritten program with the boolean it came from on every affected shape, at the root, in a property, in array items, through `bundleStandalone` and through a compiled module, and checks the two preprocessing cases directly. `tests/test_codegen_entrypoint_agreement.js` now compares the rewritten program as a fifth participant across the whole official suite. `tests/test_ajv_errors.js`, which had been failing on exactly this for as long as the bug existed, is now part of `npm test`.
|
|
14
|
+
|
|
15
|
+
### Changed
|
|
16
|
+
|
|
17
|
+
- Ranking an error into schema declaration order no longer re-derives the rank on every error of every failing document. It was splitting the schema path with two regular expressions per segment and then scanning `Object.keys(node)` at each level, which was 8.5% of the error path in a profile, for an answer that depends only on the schema and the path. The rank is now cached per path under its root, each node's keys are indexed once, and escape handling is skipped for segments with no tilde. That function drops from 8.5% to 1.4% of the error path, and the error path as a whole gets 3.5% faster, median of eleven interleaved runs. `docs/performance-notes.md` records the larger gap this was measured against; it is not closed by this change.
|
|
18
|
+
|
|
5
19
|
## 1.8.1 - 2026-08-27
|
|
6
20
|
|
|
7
21
|
### Changed
|
package/index.js
CHANGED
|
@@ -373,12 +373,64 @@ function resolveSchemaByPath(rootSchema, schemaPath) {
|
|
|
373
373
|
// order AJV emits and what schema authors read top to bottom. Segments that
|
|
374
374
|
// cannot be resolved (cross-schema refs, normalized keys) end the walk; the
|
|
375
375
|
// stable sort then keeps such errors in engine emission order.
|
|
376
|
+
// The rank of a `schemaPath` under a given root is fixed: the schema does not
|
|
377
|
+
// change between validations, so neither does the answer. It was recomputed for
|
|
378
|
+
// every error of every failing document, and computing it is not cheap. Two
|
|
379
|
+
// caches, both keyed on things that do not change:
|
|
380
|
+
//
|
|
381
|
+
// rootSchema -> schemaPath -> rank, so a path is walked once ever
|
|
382
|
+
// node -> key -> its index, so the walk stops calling Object.keys and
|
|
383
|
+
// scanning the result for a string
|
|
384
|
+
//
|
|
385
|
+
// A failing route sees the same handful of schemaPaths over and over, which is
|
|
386
|
+
// what makes the first one worth having.
|
|
387
|
+
const _rankCache = new WeakMap();
|
|
388
|
+
const _keyIndexCache = new WeakMap();
|
|
389
|
+
|
|
390
|
+
function keyIndex(node, seg) {
|
|
391
|
+
let index = _keyIndexCache.get(node);
|
|
392
|
+
if (index === undefined) {
|
|
393
|
+
index = new Map();
|
|
394
|
+
const keys = Object.keys(node);
|
|
395
|
+
for (let i = 0; i < keys.length; i++) index.set(keys[i], i);
|
|
396
|
+
_keyIndexCache.set(node, index);
|
|
397
|
+
}
|
|
398
|
+
const at = index.get(seg);
|
|
399
|
+
return at === undefined ? -1 : at;
|
|
400
|
+
}
|
|
401
|
+
|
|
402
|
+
// `~1` and `~0` are the only escapes a JSON pointer has, and almost no schema
|
|
403
|
+
// key contains a tilde. Looking for one is far cheaper than two regex passes
|
|
404
|
+
// over every segment of every path.
|
|
405
|
+
function unescapePointerSegment(seg) {
|
|
406
|
+
return seg.indexOf('~') < 0 ? seg : seg.replace(/~1/g, '/').replace(/~0/g, '~');
|
|
407
|
+
}
|
|
408
|
+
|
|
376
409
|
function schemaOrderRank(rootSchema, schemaPath) {
|
|
377
410
|
if (!schemaPath || typeof schemaPath !== 'string' || !schemaPath.startsWith('#')) return null;
|
|
378
|
-
|
|
411
|
+
if (rootSchema === null || typeof rootSchema !== 'object') return null;
|
|
412
|
+
|
|
413
|
+
let byPath = _rankCache.get(rootSchema);
|
|
414
|
+
if (byPath === undefined) { byPath = new Map(); _rankCache.set(rootSchema, byPath); }
|
|
415
|
+
const hit = byPath.get(schemaPath);
|
|
416
|
+
if (hit !== undefined) return hit;
|
|
417
|
+
|
|
418
|
+
const rank = _computeRank(rootSchema, schemaPath);
|
|
419
|
+
byPath.set(schemaPath, rank);
|
|
420
|
+
return rank;
|
|
421
|
+
}
|
|
422
|
+
|
|
423
|
+
function _computeRank(rootSchema, schemaPath) {
|
|
379
424
|
const rank = [];
|
|
380
425
|
let node = rootSchema;
|
|
381
|
-
|
|
426
|
+
let start = 1;
|
|
427
|
+
while (start <= schemaPath.length) {
|
|
428
|
+
let end = schemaPath.indexOf('/', start);
|
|
429
|
+
if (end < 0) end = schemaPath.length;
|
|
430
|
+
if (end === start) { start = end + 1; continue; } // what filter(Boolean) dropped
|
|
431
|
+
const seg = unescapePointerSegment(schemaPath.slice(start, end));
|
|
432
|
+
start = end + 1;
|
|
433
|
+
|
|
382
434
|
if (node == null || typeof node !== 'object') break;
|
|
383
435
|
if (Array.isArray(node)) {
|
|
384
436
|
const idx = Number(seg);
|
|
@@ -386,7 +438,7 @@ function schemaOrderRank(rootSchema, schemaPath) {
|
|
|
386
438
|
rank.push(idx);
|
|
387
439
|
node = node[idx];
|
|
388
440
|
} else {
|
|
389
|
-
const idx =
|
|
441
|
+
const idx = keyIndex(node, seg);
|
|
390
442
|
if (idx < 0) break;
|
|
391
443
|
rank.push(idx);
|
|
392
444
|
node = node[seg];
|
package/lib/js-compiler.js
CHANGED
|
@@ -1492,9 +1492,17 @@ function compileToJSCodegen(schema, schemaMap, userFormats) {
|
|
|
1492
1492
|
function replaceTopLevel(code) {
|
|
1493
1493
|
let fnDepth = 0, result = '', i = 0
|
|
1494
1494
|
while (i < code.length) {
|
|
1495
|
-
|
|
1496
|
-
|
|
1497
|
-
|
|
1495
|
+
// A nested closure keeps its own returns. Both spellings count: a
|
|
1496
|
+
// `function` keyword and an arrow with a block body. The inline pattern
|
|
1497
|
+
// compiler emits `(()=>{...return false...return true})()` and rewriting
|
|
1498
|
+
// those returns turned the arrow's boolean into an object, which made
|
|
1499
|
+
// `!(cond && obj)` false and let the hybrid accept what the boolean
|
|
1500
|
+
// program rejected.
|
|
1501
|
+
const isFunctionKw = code.startsWith('function', i) && (i === 0 || /[^a-zA-Z_$]/.test(code[i - 1]))
|
|
1502
|
+
const isArrowBlock = code.startsWith('=>{', i)
|
|
1503
|
+
if (isFunctionKw || isArrowBlock) {
|
|
1504
|
+
// Skip to the opening brace, then track all braces inside the body.
|
|
1505
|
+
let j = isArrowBlock ? i + 2 : i + 8
|
|
1498
1506
|
while (j < code.length && code[j] !== '{') j++
|
|
1499
1507
|
result += code.slice(i, j + 1)
|
|
1500
1508
|
i = j + 1
|
package/lib/version.js
CHANGED
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "ata-validator",
|
|
3
|
-
"version": "1.8.
|
|
3
|
+
"version": "1.8.2",
|
|
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_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_buffer_gate.js && node tests/test_draft7_semantics.js && node tests/test_metaschema_ref.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_native_loaded.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_format_mode.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_engine_diagnostic.js && node tests/test_format_engine_parity.js && node tests/test_defs_pointer_alias.js && node tests/test_cross_doc_root_ref.js && node tests/test_codegen_entrypoint_agreement.js && node tests/test_pattern_properties_errors.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_code_lookup.js && node tests/test_error_order.js && node tests/test_value_equality.js && node tests/test_vocabulary.js && node tests/test_schema_scan.js && node tests/test_lazy_errors.js && node tests/test_plan_compiler.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_enrich_received.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_buffer_gate.js && node tests/test_draft7_semantics.js && node tests/test_metaschema_ref.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_native_loaded.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_format_mode.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_engine_diagnostic.js && node tests/test_format_engine_parity.js && node tests/test_defs_pointer_alias.js && node tests/test_cross_doc_root_ref.js && node tests/test_codegen_entrypoint_agreement.js && node tests/test_hybrid_agreement.js && node tests/test_ajv_errors.js && node tests/test_pattern_properties_errors.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_code_lookup.js && node tests/test_error_order.js && node tests/test_value_equality.js && node tests/test_vocabulary.js && node tests/test_schema_scan.js && node tests/test_lazy_errors.js && node tests/test_plan_compiler.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_enrich_received.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 && node tests/run_suite.js draft7 && node tests/run_suite.js v1",
|
|
50
50
|
"test:compat": "node tests/test_compat.js",
|
|
@@ -112,13 +112,13 @@
|
|
|
112
112
|
"LICENSE"
|
|
113
113
|
],
|
|
114
114
|
"optionalDependencies": {
|
|
115
|
-
"@ata-validator/native-darwin-arm64": "1.8.
|
|
116
|
-
"@ata-validator/native-darwin-x64": "1.8.
|
|
117
|
-
"@ata-validator/native-linux-x64-gnu": "1.8.
|
|
118
|
-
"@ata-validator/native-linux-arm64-gnu": "1.8.
|
|
119
|
-
"@ata-validator/native-linux-x64-musl": "1.8.
|
|
120
|
-
"@ata-validator/native-linux-arm64-musl": "1.8.
|
|
121
|
-
"@ata-validator/native-win32-x64": "1.8.
|
|
115
|
+
"@ata-validator/native-darwin-arm64": "1.8.2",
|
|
116
|
+
"@ata-validator/native-darwin-x64": "1.8.2",
|
|
117
|
+
"@ata-validator/native-linux-x64-gnu": "1.8.2",
|
|
118
|
+
"@ata-validator/native-linux-arm64-gnu": "1.8.2",
|
|
119
|
+
"@ata-validator/native-linux-x64-musl": "1.8.2",
|
|
120
|
+
"@ata-validator/native-linux-arm64-musl": "1.8.2",
|
|
121
|
+
"@ata-validator/native-win32-x64": "1.8.2"
|
|
122
122
|
},
|
|
123
123
|
"peerDependencies": {
|
|
124
124
|
"yaml": "^2.0.0"
|