ata-validator 1.50.0 → 1.52.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.
@@ -57,17 +57,53 @@ function rankFor(rootSchema, schemaPath) {
57
57
  return rank;
58
58
  }
59
59
 
60
+ // The documents a root's references reach beyond itself, id to schema, from
61
+ // the validators that own the root (the `schemas` option, addSchema) or a
62
+ // schema module's registry. Several validators of one root schema register
63
+ // their maps into one; an id that two of them give different schemas makes the
64
+ // root resolve nothing outside itself, as it did before any was registered.
65
+ const _documents = new WeakMap();
66
+ const CONFLICT = new Map();
67
+ function registerDocuments(rootSchema, map) {
68
+ if (rootSchema === null || typeof rootSchema !== 'object' || !map || map.size === 0) return;
69
+ const had = _documents.get(rootSchema);
70
+ if (had === CONFLICT) return;
71
+ if (had === undefined) { _documents.set(rootSchema, new Map(map)); _rankCache.delete(rootSchema); return; }
72
+ let added = false;
73
+ for (const [id, doc] of map) {
74
+ const cur = had.get(id);
75
+ if (cur === undefined) { had.set(id, doc); added = true; } else if (cur !== doc) { _documents.set(rootSchema, CONFLICT); _rankCache.delete(rootSchema); return; }
76
+ }
77
+ if (added) _rankCache.delete(rootSchema);
78
+ }
79
+
80
+ function withoutEmptyFragment(uri) {
81
+ return uri.endsWith('#') ? uri.slice(0, -1) : uri;
82
+ }
83
+
84
+ function resolveUri(ref, base) {
85
+ try { return base ? new URL(ref, base).href : new URL(ref).href; } catch { return null; }
86
+ }
87
+
60
88
  function computeRank(rootSchema, schemaPath) {
61
89
  const rank = [];
62
90
  let node = rootSchema;
63
91
  let start = 1;
64
92
  let hops = 0;
93
+ const docs = _documents.get(rootSchema);
94
+ // The document the walk is in and the base URI references resolve against,
95
+ // for following a reference into another document.
96
+ let doc = rootSchema;
97
+ // A root without an $id can still name another document by absolute URI.
98
+ const follow = docs !== undefined && docs !== CONFLICT;
99
+ let base = follow && typeof rootSchema.$id === 'string' ? resolveUri(rootSchema.$id, null) : null;
65
100
  while (start <= schemaPath.length) {
66
101
  let end = schemaPath.indexOf('/', start);
67
102
  if (end < 0) end = schemaPath.length;
68
103
  if (end === start) { start = end + 1; continue; }
69
104
  const seg = unescapePointerSegment(schemaPath.slice(start, end));
70
105
  if (node == null || typeof node !== 'object') break;
106
+ if (follow && node !== doc && !Array.isArray(node) && typeof node.$id === 'string' && node.$id[0] !== '#') base = resolveUri(node.$id, base);
71
107
  if (Array.isArray(node)) {
72
108
  const idx = Number(seg);
73
109
  if (!Number.isInteger(idx) || idx < 0 || idx >= node.length) break;
@@ -80,7 +116,11 @@ function computeRank(rootSchema, schemaPath) {
80
116
  // naming it. Ranking the rest inside the target, as if the target sat
81
117
  // under the `$ref` key, orders errors behind a reference by the
82
118
  // target's declaration order, as they are ordered inline.
83
- const target = hops < 64 ? localTarget(rootSchema, node) : null;
119
+ let target = hops < 64 ? localTarget(doc, node) : null;
120
+ if (target === null && hops < 64 && follow) {
121
+ const remote = remoteTarget(docs, base, node);
122
+ if (remote !== null) { target = remote.node; doc = remote.doc; base = remote.base; }
123
+ }
84
124
  if (target === null) break;
85
125
  hops++;
86
126
  rank.push(keyIndex(node, '$ref'));
@@ -95,6 +135,28 @@ function computeRank(rootSchema, schemaPath) {
95
135
  return rank;
96
136
  }
97
137
 
138
+ // The schema a node's `$ref` names in a registered document: { node, doc,
139
+ // base }, or null when it names none (an anchor, an unknown document).
140
+ function remoteTarget(docs, base, node) {
141
+ const ref = node.$ref;
142
+ if (typeof ref !== 'string' || ref.charCodeAt(0) === 35) return null;
143
+ const abs = resolveUri(ref, base);
144
+ if (abs === null) return null;
145
+ const hash = abs.indexOf('#');
146
+ const uri = hash < 0 ? abs : abs.slice(0, hash);
147
+ let target = docs.get(uri);
148
+ if (target === undefined) target = docs.get(uri + '#');
149
+ if (target === undefined) {
150
+ for (const [id, s] of docs) if (withoutEmptyFragment(resolveUri(id, null) || '') === uri) { target = s; break; }
151
+ }
152
+ if (target === null || typeof target !== 'object') return null;
153
+ const frag = hash < 0 ? '' : abs.slice(hash + 1);
154
+ if (frag === '') return { node: target, doc: target, base: uri };
155
+ if (frag.charCodeAt(0) !== 47) return null;
156
+ const t = localTarget(target, { $ref: '#' + frag });
157
+ return t === null ? null : { node: t, doc: target, base: uri };
158
+ }
159
+
98
160
  // The schema a node's `$ref` names, when it is a pointer into this document.
99
161
  function localTarget(rootSchema, node) {
100
162
  const ref = node.$ref;
@@ -191,4 +253,4 @@ function ordinalFor(rootSchema, schemaPath) {
191
253
  const _missCache = new WeakMap();
192
254
  const MISS_CACHE_LIMIT = 4096;
193
255
 
194
- module.exports = { rankFor, ordinalFor, unescapePointerSegment };
256
+ module.exports = { rankFor, ordinalFor, unescapePointerSegment, registerDocuments };
@@ -10,8 +10,32 @@
10
10
  const { createInterpreter } = require('./interpreter');
11
11
  const { errorsOf, interpreterOptions } = require('./error-runtime-entry');
12
12
 
13
+ // A registry is the array of schemas a build writes beside schema modules
14
+ // whose schemas reference each other by `$id` (ata-registry.mjs). It becomes
15
+ // one Map, id to schema, shared by every module of the build.
16
+ const _maps = new WeakMap();
17
+ function registryMap(registry) {
18
+ let map = _maps.get(registry);
19
+ if (map === undefined) {
20
+ map = new Map();
21
+ for (const s of registry) map.set(s.$id, s);
22
+ _maps.set(registry, map);
23
+ }
24
+ return map;
25
+ }
26
+
27
+ // options.registry and options.id: the module's schema is the registry entry
28
+ // with that `$id`, and its references resolve against the registry.
13
29
  function createValidator(schema, codeOf, options) {
14
- const interp = createInterpreter(schema, interpreterOptions(options));
30
+ const opts = interpreterOptions(options);
31
+ if (options && options.registry) {
32
+ const map = registryMap(options.registry);
33
+ if (schema === null || schema === undefined) schema = map.get(options.id);
34
+ opts.schemaMap = map;
35
+ opts.flat = false;
36
+ require('./schema-order').registerDocuments(schema, map);
37
+ }
38
+ const interp = createInterpreter(schema, opts);
15
39
  return {
16
40
  isValid: (data) => interp.isValid(data),
17
41
  errors: (data) => errorsOf(interp, schema, codeOf, data),
@@ -864,6 +864,9 @@ class Validator {
864
864
  if (this._initialized) return;
865
865
  this._ensureVocabularies();
866
866
  this._initialized = true;
867
+ // Errors behind a reference into a registered document are ordered as
868
+ // they would be inline: the ordering follows the reference there.
869
+ if (this._schemaMap && this._schemaMap.size > 0) require('./schema-order').registerDocuments(this._schemaObj, this._schemaMap);
867
870
 
868
871
  const schemaObj = this._schemaObj;
869
872
  const options = this._options;
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.50.0';
10
+ module.exports = '1.52.0';
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "ata-validator",
3
- "version": "1.50.0",
3
+ "version": "1.52.0",
4
4
  "description": "JSON Schema validator that compiles to plain JavaScript, still runs where code generation is blocked, and can be compiled away at build time. Passes the full official test suite.",
5
5
  "type": "commonjs",
6
6
  "main": "index.js",
@@ -24,6 +24,7 @@
24
24
  "require": "./index.js"
25
25
  },
26
26
  "import": {
27
+ "module": "./index.bundle.mjs",
27
28
  "module-sync": "./index.node.mjs",
28
29
  "default": "./index.mjs"
29
30
  },
@@ -137,6 +138,7 @@
137
138
  "index.js",
138
139
  "index.mjs",
139
140
  "index.node.mjs",
141
+ "index.bundle.mjs",
140
142
  "index.browser.mjs",
141
143
  "index.edge.js",
142
144
  "index.edge.mjs",
@@ -172,13 +174,13 @@
172
174
  "LICENSE"
173
175
  ],
174
176
  "optionalDependencies": {
175
- "@ata-validator/native-darwin-arm64": "1.50.0",
176
- "@ata-validator/native-darwin-x64": "1.50.0",
177
- "@ata-validator/native-linux-arm64-gnu": "1.50.0",
178
- "@ata-validator/native-linux-arm64-musl": "1.50.0",
179
- "@ata-validator/native-linux-x64-gnu": "1.50.0",
180
- "@ata-validator/native-linux-x64-musl": "1.50.0",
181
- "@ata-validator/native-win32-x64": "1.50.0"
177
+ "@ata-validator/native-darwin-arm64": "1.52.0",
178
+ "@ata-validator/native-darwin-x64": "1.52.0",
179
+ "@ata-validator/native-linux-arm64-gnu": "1.52.0",
180
+ "@ata-validator/native-linux-arm64-musl": "1.52.0",
181
+ "@ata-validator/native-linux-x64-gnu": "1.52.0",
182
+ "@ata-validator/native-linux-x64-musl": "1.52.0",
183
+ "@ata-validator/native-win32-x64": "1.52.0"
182
184
  },
183
185
  "peerDependencies": {
184
186
  "yaml": "^2.0.0"