ata-validator 1.17.0 → 1.17.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 CHANGED
@@ -2,6 +2,18 @@
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.17.1 - 2026-09-13
6
+
7
+ ### Changed
8
+
9
+ - The rejection object behind a failing `validate()` is an instance of a class with prototype accessors rather than an object literal that defined a getter and a closure on every rejection. Same shape, same laziness, same caching; `JSON.stringify` still sees the errors.
10
+ - Declaration-order sorting of short error lists is a stable in-place insertion sort over the integer keys, with no comparator and no index array. Measured on the raw error list of the schema-benchmarks product schema, 15 errors: 1.49 to 1.10 µs; through the Standard Schema bridge 2.02 to 1.63 µs. Since 1.16.2 that row has gone from 3.30 to 1.63 µs on the same machine, and in the schema-benchmarks harness itself standard invalid went from 3.57 to 1.44 µs (60 percent less), every other row within noise.
11
+
12
+ ### Fixed
13
+
14
+ - `validateJSON()` reports errors in schema declaration order, as `validate()` does. The text path enriched errors in emission order, so the first error could differ between the two entry points for the same document.
15
+ - An instance built with options no longer answers a later `new Validator(sameSchema)` without options. The identity cache was seeded by every instance, so a validator created with `richErrors: false` or `coerceTypes` could be handed back to a caller that asked for the defaults.
16
+
5
17
  ## 1.17.0 - 2026-09-13
6
18
 
7
19
  ### Changed
package/index.js CHANGED
@@ -425,6 +425,63 @@ function resolveSchemaByPath(rootSchema, schemaPath) {
425
425
  const _rankCache = new WeakMap();
426
426
  const { rankFor: schemaOrderRank, ordinalFor: schemaOrdinal } = require('./lib/schema-order');
427
427
 
428
+ // The rejection the rich-errors wrapper returns. `errors` is built on first
429
+ // read and cached; `_ataRaw()` is the raw, schema-ordered list for consumers
430
+ // that carry only message and path, such as the Standard Schema bridge.
431
+ // Prototype accessors, not per-instance ones: see LazyRejection.
432
+ class RichRejection {
433
+ constructor(result, data, positions, self, root, enrich) {
434
+ this.valid = false;
435
+ this._result = result;
436
+ this._data = data;
437
+ this._positions = positions;
438
+ this._self = self;
439
+ this._root = root;
440
+ this._enrich = enrich;
441
+ this._cached = null;
442
+ }
443
+ toJSON() {
444
+ return { valid: false, errors: this.errors };
445
+ }
446
+ _ataRaw() {
447
+ let raw = this._result.errors || [];
448
+ if (raw.length > 1) raw = sortErrorsBySchemaOrder(this._root, raw);
449
+ return raw;
450
+ }
451
+ }
452
+ Object.defineProperty(RichRejection.prototype, 'errors', {
453
+ enumerable: true,
454
+ configurable: true,
455
+ get() {
456
+ if (this._cached === null) {
457
+ const self = this._self;
458
+ const enrich = this._enrich;
459
+ let raw = this._result.errors || [];
460
+ if (raw.length > 1) raw = sortErrorsBySchemaOrder(this._root, raw);
461
+ const cached = (enrich && raw.length)
462
+ ? raw.map((e) => enrich(e, {
463
+ data: this._data,
464
+ positions: this._positions,
465
+ schemaPositions: self._schemaPositions,
466
+ schemaFile: self._source ? self._source.path : undefined,
467
+ }))
468
+ // The v0.14 shape is a fixed key set; the ordering key the
469
+ // generated code carries is dropped from it here.
470
+ : raw.map(stripOrdinal);
471
+ // Correlation is published, never applied. Both halves of a typo pair
472
+ // stay in the array; `related` only says they are one mistake, so a
473
+ // wrong pairing costs a sentence rather than a hidden violation.
474
+ if (enrich && cached.length > 1) attachRelated(cached);
475
+ // No diagnostic payload here. validate(data) is the library hot path,
476
+ // and attaching one cost about 100 ns per rejection for a consumer
477
+ // that never renders. The text path attaches it, and a renderer given
478
+ // `{ data }` builds frames for object input on request.
479
+ this._cached = cached;
480
+ }
481
+ return this._cached;
482
+ },
483
+ });
484
+
428
485
  // A raw error without the `_o` ordering key, for the legacy error shape.
429
486
  function stripOrdinal(e) {
430
487
  if (e === null || typeof e !== 'object' || e._o === undefined) return e;
@@ -454,11 +511,17 @@ function sortErrorsBySchemaOrder(rootSchema, errors) {
454
511
  prev = o;
455
512
  }
456
513
  if (sorted) return errors;
457
- const idx = new Array(n);
458
- for (let i = 0; i < n; i++) idx[i] = i;
459
- idx.sort((a, b) => keys[a] - keys[b] || a - b);
460
- const out = new Array(n);
461
- for (let i = 0; i < n; i++) out[i] = errors[idx[i]];
514
+ // Error lists are short. A stable insertion sort over the integer keys
515
+ // moves the errors in tandem with no comparator calls and no index array.
516
+ const out = errors.slice();
517
+ for (let i = 1; i < n; i++) {
518
+ const k = keys[i];
519
+ const e = out[i];
520
+ let j = i - 1;
521
+ while (j >= 0 && keys[j] > k) { keys[j + 1] = keys[j]; out[j + 1] = out[j]; j--; }
522
+ keys[j + 1] = k;
523
+ out[j + 1] = e;
524
+ }
462
525
  return out;
463
526
  }
464
527
 
@@ -785,6 +848,7 @@ class Validator {
785
848
  this._schemaStr = null; // lazy: computed on first use
786
849
  this._schemaObj = schemaObj;
787
850
  this._options = options;
851
+ this._noOpts = !opts;
788
852
  this._initialized = false;
789
853
  this._nativeReady = false;
790
854
  this._compiled = null;
@@ -1422,45 +1486,11 @@ class Validator {
1422
1486
  // resolved eagerly since the cache is reset per call.
1423
1487
  const positions = (enrich && self._lastRawInput != null) ? self._pos().get(self._lastRawInput) : null;
1424
1488
  if (positions) self._posCache.reset();
1425
- let cached = null;
1426
- return {
1427
- valid: false,
1428
- // Raw shape for consumers that carry only message and path, such
1429
- // as the Standard Schema bridge: schema order, no enrichment.
1430
- // Reading `errors` afterwards still enriches from the same build.
1431
- _ataRaw() {
1432
- let raw = result.errors || [];
1433
- if (raw.length > 1) raw = sortErrorsBySchemaOrder(root, raw);
1434
- return raw;
1435
- },
1436
- get errors() {
1437
- if (cached === null) {
1438
- let raw = result.errors || [];
1439
- if (raw.length > 1) raw = sortErrorsBySchemaOrder(root, raw);
1440
- cached = (enrich && raw.length)
1441
- ? raw.map((e) => enrich(e, {
1442
- data,
1443
- positions,
1444
- schemaPositions: self._schemaPositions,
1445
- schemaFile: self._source ? self._source.path : undefined,
1446
- }))
1447
- // The v0.14 shape is a fixed key set; the ordering key the
1448
- // generated code carries is dropped from it here.
1449
- : raw.map(stripOrdinal);
1450
- // Correlation is published, never applied. Both halves of a
1451
- // typo pair stay in the array; `related` only says they are
1452
- // one mistake, so a wrong pairing costs a sentence rather
1453
- // than a hidden violation.
1454
- if (enrich && cached.length > 1) attachRelated(cached);
1455
- // No diagnostic payload here. validate(data) is the library
1456
- // hot path, and attaching one cost about 100 ns per rejection
1457
- // for a consumer that never renders. The text path attaches
1458
- // it below, and a renderer given `{ data }` builds frames for
1459
- // object input on request.
1460
- }
1461
- return cached;
1462
- },
1463
- };
1489
+ // One instance of a class with prototype accessors. An object
1490
+ // literal with a getter here cost a closure plus an accessor
1491
+ // definition on every rejection, several hundred nanoseconds
1492
+ // before any error was read.
1493
+ return new RichRejection(result, data, positions, self, root, enrich);
1464
1494
  }
1465
1495
  return result;
1466
1496
  };
@@ -1496,7 +1526,10 @@ class Validator {
1496
1526
  try { parsedData = JSON.parse(jsonStr); } catch { parsedData = undefined; }
1497
1527
  if (!first || !first.docUrl) {
1498
1528
  const positions = (this._lastRawInput != null) ? this._pos().get(this._lastRawInput) : null;
1499
- const enriched = result.errors.map((e) => enrich(e, {
1529
+ // Declaration order, as validate() applies it; the text path
1530
+ // used to enrich in emission order.
1531
+ const ordered = result.errors.length > 1 ? sortErrorsBySchemaOrder(this._schemaObj, result.errors) : result.errors;
1532
+ const enriched = ordered.map((e) => enrich(e, {
1500
1533
  data: parsedData,
1501
1534
  positions,
1502
1535
  schemaPositions: this._schemaPositions,
@@ -1635,8 +1668,12 @@ class Validator {
1635
1668
  if (bufferNeedsSlowPath(schemaObj, this._schemaMap, this._keywords)) installSlowBufferApis(this);
1636
1669
  }
1637
1670
 
1638
- // Save to identity cache for ultra-fast reuse with same schema object
1639
- if (this._schemaObj && typeof this._schemaObj === 'object') {
1671
+ // Save to identity cache for ultra-fast reuse with same schema object.
1672
+ // Only an instance built without options may answer a later
1673
+ // `new Validator(schema)`: one built with options (richErrors: false,
1674
+ // coerceTypes, formats, ...) would hand its options to a caller that
1675
+ // asked for none.
1676
+ if (this._noOpts && this._schemaObj && typeof this._schemaObj === 'object') {
1640
1677
  _identityCache.set(this._schemaObj, this);
1641
1678
  }
1642
1679
  }
package/lib/version.js CHANGED
@@ -7,4 +7,4 @@
7
7
  //
8
8
  // Kept in lockstep with package.json by `tests/test_version_sync.js`.
9
9
 
10
- module.exports = '1.17.0';
10
+ module.exports = '1.17.1';
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "ata-validator",
3
- "version": "1.17.0",
3
+ "version": "1.17.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",
@@ -51,7 +51,7 @@
51
51
  "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",
52
52
  "build": "cmake-js build --target ata",
53
53
  "rebuild": "cmake-js rebuild --target ata",
54
- "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_buffer_reject_cost.js && node tests/test_nan_verdict.js && node tests/test_remove_additional_nested.js && node tests/test_aot_parse.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_formats_single_pass.js && node tests/test_format_single_error.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_codegen_edge_shapes.js && node tests/test_ajv_errors.js && node tests/test_custom_keywords.js && node tests/test_ajv_parity.js && node tests/test_user_format_error_path.js && node tests/test_unevaluated_error_path.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_error_order_ordinal.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_lazy_instance.js && node tests/test_cyclic_input.js && node tests/test_native_error_codes.js && node tests/test_verdict_preprocess.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_additive_fields.js && node tests/test_diagnostic_source.js && node tests/test_diagnose.js && node tests/test_correlate.js && node tests/test_diagnostics_score.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",
54
+ "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_buffer_reject_cost.js && node tests/test_nan_verdict.js && node tests/test_remove_additional_nested.js && node tests/test_aot_parse.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_formats_single_pass.js && node tests/test_format_single_error.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_codegen_edge_shapes.js && node tests/test_ajv_errors.js && node tests/test_custom_keywords.js && node tests/test_ajv_parity.js && node tests/test_user_format_error_path.js && node tests/test_unevaluated_error_path.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_error_order_ordinal.js && node tests/test_rejection_shape.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_lazy_instance.js && node tests/test_cyclic_input.js && node tests/test_native_error_codes.js && node tests/test_verdict_preprocess.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_additive_fields.js && node tests/test_diagnostic_source.js && node tests/test_diagnose.js && node tests/test_correlate.js && node tests/test_diagnostics_score.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",
55
55
  "bench:size": "node benchmark/bench_aot_size.mjs",
56
56
  "test:suite": "node tests/run_suite.js && node tests/run_suite.js draft7 && node tests/run_suite.js v1",
57
57
  "test:compat": "node tests/test_compat.js",
@@ -122,13 +122,13 @@
122
122
  "LICENSE"
123
123
  ],
124
124
  "optionalDependencies": {
125
- "@ata-validator/native-darwin-arm64": "1.17.0",
126
- "@ata-validator/native-darwin-x64": "1.17.0",
127
- "@ata-validator/native-linux-arm64-gnu": "1.17.0",
128
- "@ata-validator/native-linux-arm64-musl": "1.17.0",
129
- "@ata-validator/native-linux-x64-gnu": "1.17.0",
130
- "@ata-validator/native-linux-x64-musl": "1.17.0",
131
- "@ata-validator/native-win32-x64": "1.17.0"
125
+ "@ata-validator/native-darwin-arm64": "1.17.1",
126
+ "@ata-validator/native-darwin-x64": "1.17.1",
127
+ "@ata-validator/native-linux-arm64-gnu": "1.17.1",
128
+ "@ata-validator/native-linux-arm64-musl": "1.17.1",
129
+ "@ata-validator/native-linux-x64-gnu": "1.17.1",
130
+ "@ata-validator/native-linux-x64-musl": "1.17.1",
131
+ "@ata-validator/native-win32-x64": "1.17.1"
132
132
  },
133
133
  "peerDependencies": {
134
134
  "yaml": "^2.0.0"