ata-validator 0.17.4 → 0.17.5

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,12 @@
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
+ ## 0.17.5 - 2026-05-25
6
+
7
+ ### Fixed
8
+
9
+ - Compiled validators resolve draft-07 plain-name anchors. A `$defs`/`definitions` entry that declares an anchor with `$id: "#name"` and is referenced by `$ref: "#name"` now compiles through the codegen on every path (boolean, error, combined) instead of bailing. The bail forced a fallback that could not resolve sibling cross-schema refs, which surfaced as `cannot resolve $ref`. This is how shared schemas are referenced under Fastify.
10
+
5
11
  ## 0.17.4 - 2026-05-25
6
12
 
7
13
  ### Fixed
@@ -699,23 +699,6 @@ function canResolveDynamicRefs(target, callingSchema, schemaMap) {
699
699
 
700
700
  // Recursively check if a schema can be safely compiled to JS codegen.
701
701
  // Returns false if any sub-schema contains features codegen gets wrong.
702
- // Does the schema (recursively) contain an anchor-style `$ref` equal to anchorId
703
- // (e.g. `$ref: '#address'`)? Used to keep codegen away from anchor refs it can't
704
- // resolve in the combined/error paths.
705
- function schemaHasAnchorRef(node, anchorId) {
706
- if (!node || typeof node !== 'object') return false
707
- if (Array.isArray(node)) {
708
- for (const x of node) if (schemaHasAnchorRef(x, anchorId)) return true
709
- return false
710
- }
711
- if (node.$ref === anchorId) return true
712
- for (const k in node) {
713
- if (k === '$ref') continue
714
- if (schemaHasAnchorRef(node[k], anchorId)) return true
715
- }
716
- return false
717
- }
718
-
719
702
  function codegenSafe(schema, schemaMap) {
720
703
  if (typeof schema === 'boolean') return true
721
704
  if (typeof schema !== 'object' || schema === null) return true
@@ -826,14 +809,10 @@ function codegenSafe(schema, schemaMap) {
826
809
  if (typeof def === 'boolean') return false
827
810
  if (typeof def === 'object' && def !== null) {
828
811
  // A non-fragment $id (e.g. 'sub.json', 'http://...') opens a new base URI
829
- // scope — bail. A fragment-only $id ('#name') is a draft-07 anchor; it is
830
- // safe to codegen when reached by JSON pointer (#/definitions/name), but the
831
- // combined/error codegen paths do not resolve anchor refs (`$ref: '#name'`)
832
- // to it, so bail if the schema anchor-references this def.
833
- if (def.$id) {
834
- if (!def.$id.startsWith('#')) return false
835
- if (schemaHasAnchorRef(schema, def.$id)) return false
836
- }
812
+ // scope — bail. A fragment-only $id ('#name') is a draft-07 anchor; the
813
+ // codegen anchor maps register it (see "Build anchors map"), so anchor
814
+ // refs (`$ref: '#name'`) resolve to it on all paths.
815
+ if (def.$id && !def.$id.startsWith('#')) return false
837
816
  if (def.$ref) return false // nested ref chain — bail
838
817
  if (!codegenSafe(def, schemaMap)) return false
839
818
  }
@@ -933,12 +912,15 @@ function compileToJSCodegen(schema, schemaMap, userFormats) {
933
912
  // Root schema's own $dynamicAnchor / $anchor
934
913
  if (schema.$dynamicAnchor) anchors['#' + schema.$dynamicAnchor] = schema
935
914
  if (schema.$anchor) anchors['#' + schema.$anchor] = schema
915
+ // Draft-07 plain-name anchor: declared as `$id: "#name"`.
916
+ if (typeof schema.$id === 'string' && schema.$id.startsWith('#')) anchors[schema.$id] = schema
936
917
  // Anchors from $defs
937
918
  if (rootDefs) {
938
919
  for (const def of Object.values(rootDefs)) {
939
920
  if (def && typeof def === 'object') {
940
921
  if (def.$dynamicAnchor) anchors['#' + def.$dynamicAnchor] = def
941
922
  if (def.$anchor) anchors['#' + def.$anchor] = def
923
+ if (typeof def.$id === 'string' && def.$id.startsWith('#')) anchors[def.$id] = def
942
924
  }
943
925
  }
944
926
  }
@@ -948,6 +930,7 @@ function compileToJSCodegen(schema, schemaMap, userFormats) {
948
930
  if (ext && typeof ext === 'object') {
949
931
  if (ext.$dynamicAnchor && !anchors['#' + ext.$dynamicAnchor]) anchors['#' + ext.$dynamicAnchor] = ext
950
932
  if (ext.$anchor && !anchors['#' + ext.$anchor]) anchors['#' + ext.$anchor] = ext
933
+ if (typeof ext.$id === 'string' && ext.$id.startsWith('#') && !anchors[ext.$id]) anchors[ext.$id] = ext
951
934
  }
952
935
  }
953
936
  }
@@ -2592,11 +2575,13 @@ function compileToJSCodegenWithErrors(schema, schemaMap, userFormats, sourceOpts
2592
2575
  const eAnchors = {}
2593
2576
  if (schema.$dynamicAnchor) eAnchors['#' + schema.$dynamicAnchor] = schema
2594
2577
  if (schema.$anchor) eAnchors['#' + schema.$anchor] = schema
2578
+ if (typeof schema.$id === 'string' && schema.$id.startsWith('#')) eAnchors[schema.$id] = schema
2595
2579
  if (eRootDefs) {
2596
2580
  for (const def of Object.values(eRootDefs)) {
2597
2581
  if (def && typeof def === 'object') {
2598
2582
  if (def.$dynamicAnchor) eAnchors['#' + def.$dynamicAnchor] = def
2599
2583
  if (def.$anchor) eAnchors['#' + def.$anchor] = def
2584
+ if (typeof def.$id === 'string' && def.$id.startsWith('#')) eAnchors[def.$id] = def
2600
2585
  }
2601
2586
  }
2602
2587
  }
@@ -2605,6 +2590,7 @@ function compileToJSCodegenWithErrors(schema, schemaMap, userFormats, sourceOpts
2605
2590
  if (ext && typeof ext === 'object') {
2606
2591
  if (ext.$dynamicAnchor && !eAnchors['#' + ext.$dynamicAnchor]) eAnchors['#' + ext.$dynamicAnchor] = ext
2607
2592
  if (ext.$anchor && !eAnchors['#' + ext.$anchor]) eAnchors['#' + ext.$anchor] = ext
2593
+ if (typeof ext.$id === 'string' && ext.$id.startsWith('#') && !eAnchors[ext.$id]) eAnchors[ext.$id] = ext
2608
2594
  }
2609
2595
  }
2610
2596
  }
@@ -3210,11 +3196,13 @@ function compileToJSCombined(schema, VALID_RESULT, schemaMap, userFormats) {
3210
3196
  const cAnchors = {}
3211
3197
  if (schema.$dynamicAnchor) cAnchors['#' + schema.$dynamicAnchor] = schema
3212
3198
  if (schema.$anchor) cAnchors['#' + schema.$anchor] = schema
3199
+ if (typeof schema.$id === 'string' && schema.$id.startsWith('#')) cAnchors[schema.$id] = schema
3213
3200
  if (cRootDefs) {
3214
3201
  for (const def of Object.values(cRootDefs)) {
3215
3202
  if (def && typeof def === 'object') {
3216
3203
  if (def.$dynamicAnchor) cAnchors['#' + def.$dynamicAnchor] = def
3217
3204
  if (def.$anchor) cAnchors['#' + def.$anchor] = def
3205
+ if (typeof def.$id === 'string' && def.$id.startsWith('#')) cAnchors[def.$id] = def
3218
3206
  }
3219
3207
  }
3220
3208
  }
@@ -3223,6 +3211,7 @@ function compileToJSCombined(schema, VALID_RESULT, schemaMap, userFormats) {
3223
3211
  if (ext && typeof ext === 'object') {
3224
3212
  if (ext.$dynamicAnchor && !cAnchors['#' + ext.$dynamicAnchor]) cAnchors['#' + ext.$dynamicAnchor] = ext
3225
3213
  if (ext.$anchor && !cAnchors['#' + ext.$anchor]) cAnchors['#' + ext.$anchor] = ext
3214
+ if (typeof ext.$id === 'string' && ext.$id.startsWith('#') && !cAnchors[ext.$id]) cAnchors[ext.$id] = ext
3226
3215
  }
3227
3216
  }
3228
3217
  }
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "ata-validator",
3
- "version": "0.17.4",
3
+ "version": "0.17.5",
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",
@@ -42,7 +42,7 @@
42
42
  "rebuild": "cmake-js rebuild --target ata",
43
43
  "prebuild": "pkg-prebuilds-copy --baseDir build/Release --source ata.node --name=ata --strip --napi_version=10",
44
44
  "prebuild-all": "npm run prebuild -- --arch x64 && npm run prebuild -- --arch arm64",
45
- "test": "node test.js && node tests/test_no_native.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_typed_validator_runner.js && node tests/test_define_schema.js && node tests/test_error_codes_lock.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_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",
45
+ "test": "node test.js && node tests/test_no_native.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_typed_validator_runner.js && node tests/test_define_schema.js && node tests/test_error_codes_lock.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_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",
46
46
  "bench:size": "node benchmark/bench_aot_size.mjs",
47
47
  "test:suite": "node tests/run_suite.js",
48
48
  "test:compat": "node tests/test_compat.js",