ata-validator 1.22.1 → 1.23.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 CHANGED
@@ -2,6 +2,22 @@
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.23.0 - 2026-09-16
6
+
7
+ ### Added
8
+
9
+ - `strictSchema: true | 'log'`, and the compat shim's `strict`/`strictSchema` now enforce it: the authoring-time checks for the mistakes that fail open. An unknown keyword throws at construction with its path and the nearest known spelling, `strict mode: unknown keyword "maxLenght" (did you mean "maxLength"?) at #/properties/a/maxLenght`, and a local `$ref` that does not resolve in its own document throws instead of compiling a validator that rejects everything. `'log'` warns through the logger and continues. Off by default. The known-keyword list is derived from the vendored meta-schema documents rather than maintained by hand, plus the keywords ata implements beyond them; `x-` prefixed names and keywords registered through the `keywords` option pass. Property names, `enum` and `const` values, `default` and `examples` are data, not keywords, and the walk knows the difference, including the draft-07 `dependencies` form. `strictTypes`, `strictTuples` and `strictRequired` remain accepted and ignored, and `compat.d.ts` now says which is which. Reported as issue #44 by a migration that fuzzed 1,364 mutations against both validators, found zero divergence, and then shipped a typo.
10
+
11
+ ### Changed
12
+
13
+ - The error and combined generators compile on the first rejection instead of at startup. Every schema compiled three functions eagerly: the verdict, the combined validate-and-collect form, and the error generator, which on a cold first call cost 8.2, 10.4 and 7.8 ms on a 120-property config schema, most of it V8 compiling each generator's own code the first time it is entered. A caller that never reads an error never needs the last two, and `validate()` on a passing document does not either, so they are built on the first rejection. Compile plus first verdict on that schema: 30.7 to 10.8 ms, interleaved runs of committed builds. The warm path measured the same on all ten cases checked. The shared compile cache keeps `undefined` meaning not built yet and `null` meaning the compiler declined, in both places that seed it, and `tests/test_compile_cache_order.js` caught the one seam where the two seeders disagreed during this change.
14
+ - The static `unevaluatedProperties` tier stopped checking membership with a first-character switch. Keys sharing a first character, `ENV_0` through `ENV_999` say, all land in one case whose body chains every name, per key of the document: the `additionalProperties` quadratic in a different coat. Above 128 declared names the generated code reads the hoisted lookup that fixed the first one; below that the switch stays. A 1300-property config document with both of its objects closed: 87.7 to 24.5 µs, and the keyword now costs what `additionalProperties` costs, which is the membership test itself. Only the boolean generator has this code; the error and combined generators decline `unevaluated*` and the interpreted engine produces those errors.
15
+ - The compat shim derives each error's pointer segments and kinds once, into a key map shared by the reference-order sort and the shaping loop. The sort comparator used to re-split and re-classify both errors' pointers on every one of its comparisons, and the loop derived the same segments again per error, with the `if` wrapper's flush doing it a third time. A seven-error rejection through `ata-validator/compat` with `allErrors`: 5.79 to 3.27 µs. The shim's own overhead over `Validator.validate` fell from 4.4 µs to 1.9. The compat surface is pinned by `test_compat`, 106 error-format cases, 36 parity cases and the error-shape differential, all green before and after.
16
+
17
+ ### Fixed
18
+
19
+ - The API table said `verbose` attaches only `parentSchema`; it attaches `parentSchema`, `schema` and `data`, and the migration guide now lists which `params` key each keyword carries. The performance notes recorded "length-bucketed key comparison for `additionalProperties: false`" as measured and dropped on the grounds that each comparison is a pointer compare; that was right about one comparison and wrong about the total, and the note now carries the measurements that replaced it.
20
+
5
21
  ## 1.22.1 - 2026-09-16
6
22
 
7
23
  ### Fixed
package/compat.d.ts CHANGED
@@ -78,10 +78,15 @@ declare namespace Ata {
78
78
  formats?: Record<string, Format>;
79
79
  keywords?: KeywordDefinition[];
80
80
  schemas?: object[] | Record<string, object>;
81
+ /** Enforced: unknown keywords and dangling local $refs throw at compile ('log' warns instead). */
81
82
  strict?: boolean | 'log';
83
+ /** Same checks as `strict`; the specific option wins over the umbrella one. */
82
84
  strictSchema?: boolean | 'log';
85
+ /** Accepted for API compatibility; not enforced. */
83
86
  strictTypes?: boolean | 'log';
87
+ /** Accepted for API compatibility; not enforced. */
84
88
  strictTuples?: boolean | 'log';
89
+ /** Accepted for API compatibility; not enforced. */
85
90
  strictRequired?: boolean | 'log';
86
91
  allowUnionTypes?: boolean;
87
92
  logger?: { log(...args: unknown[]): void; warn(...args: unknown[]): void; error(...args: unknown[]): void } | false;
package/compat.js CHANGED
@@ -15,8 +15,10 @@ const { resolvePointer } = require('./lib/pointer.js');
15
15
  // - `$data` references;
16
16
  // - keywords defined only through `code` (a code generator hook);
17
17
  // - the reference formats plugin, whose formats are built in here.
18
- // Strict-mode schema checks (`strict`, `strictTypes`, ...) are accepted and
19
- // ignored: an unknown keyword is an annotation, as the specification says.
18
+ // Strict-mode schema checks: `strict` and `strictSchema` are enforced for the
19
+ // checks that fail open, an unknown keyword (`maxLenght` compiles and the
20
+ // constraint is simply absent) and a dangling local `$ref`. `strictTypes`,
21
+ // `strictTuples` and `strictRequired` are accepted and ignored.
20
22
 
21
23
  const { Validator } = require('./index');
22
24
  const { METASCHEMAS } = require('./lib/metaschemas');
@@ -99,6 +101,13 @@ class Ata {
99
101
  removeAdditional: !!o.removeAdditional,
100
102
  verbose: !!o.verbose,
101
103
  };
104
+ // `strict` and `strictSchema`: the unknown-keyword and dangling-local-$ref
105
+ // checks are enforced; `strictTypes`, `strictTuples` and `strictRequired`
106
+ // are still accepted and ignored. The specific keyword option wins over
107
+ // the umbrella one, which is how the reference class reads them.
108
+ const strictness = o.strictSchema !== undefined ? o.strictSchema : o.strict;
109
+ if (strictness === true || strictness === 'log') out.strictSchema = strictness;
110
+ if (o.logger !== undefined) out.logger = o.logger;
102
111
  if (o.validateFormats === false) out.assertFormat = false;
103
112
  const formatNames = Object.keys(this._formats);
104
113
  if (formatNames.length > 0) out.formats = { ...this._formats };
package/index.js CHANGED
@@ -885,6 +885,29 @@ class Validator {
885
885
  // produced the error). Matches ajv's `verbose: true` behavior.
886
886
  this._verbose = !!options.verbose;
887
887
 
888
+ // strictSchema: authoring-time checks, off by default. A mistyped keyword
889
+ // is the one schema mistake that fails open: to every dialect `maxLenght`
890
+ // is an annotation, so the constraint the author meant is simply absent
891
+ // and previously invalid data validates. `true` throws here, at
892
+ // construction, with every finding; `'log'` reports through
893
+ // `options.logger` or the console and continues. The check runs on the
894
+ // schema as written, before any normalization touches it.
895
+ if (options.strictSchema === true || options.strictSchema === 'log') {
896
+ const { checkSchemaStrict } = require('./lib/strict-check');
897
+ const problems = checkSchemaStrict(schema, { userKeywords: options.keywords || null });
898
+ if (problems.length > 0) {
899
+ const text = problems.map((x) => `strict mode: ${x.message} at ${x.path}`).join('\n');
900
+ if (options.strictSchema === true) {
901
+ throw new Error(text);
902
+ }
903
+ const logger = options.logger;
904
+ if (logger !== false) {
905
+ const warn = logger && typeof logger.warn === 'function' ? logger.warn.bind(logger) : console.warn;
906
+ warn(text);
907
+ }
908
+ }
909
+ }
910
+
888
911
  // richErrors: default true. Only the literal `false` opts back into the
889
912
  // v0.14 error shape (no code/expected/received/docUrl, no aliases).
890
913
  this._richErrors = options && options.richErrors === false ? false : true;
@@ -1014,27 +1037,40 @@ class Validator {
1014
1037
  // because nothing has tried to build them yet. Both halves of that
1015
1038
  // distinction are null, and reading the second as the first costs this
1016
1039
  // schema its generated error function for the life of the process.
1017
- } else if (cached && cached.full && !_forceNapi) {
1040
+ } else if (cached && cached.jsFn !== undefined && !_forceNapi) {
1041
+ // `full` says the error and combined functions exist too. An entry
1042
+ // without it still carries a verdict function worth reusing; the pair is
1043
+ // built by _buildDeferred below if something asks for an error, and the
1044
+ // entry is upgraded then. `undefined` in `combined`/`errFn` means not
1045
+ // built yet; `null` means the compiler declined. Those two must never
1046
+ // blur: reading the first as the second is the bug this cache had once
1047
+ // already, and it silently cost schemas their generated error function.
1018
1048
  jsFn = cached.jsFn;
1019
1049
  jsCombinedFn = cached.combined;
1020
1050
  jsErrFn = cached.errFn;
1021
1051
  _isCodegen = !!cached.isCodegen;
1052
+ this._engine = _isCodegen ? 'codegen' : jsFn ? 'closure' : null;
1022
1053
  } else if (!_forceNapi) {
1023
1054
  const uf = this._userFormats;
1024
1055
  const _cgFn = compileToJSCodegen(schemaObj, sm, uf);
1025
1056
  jsFn = _cgFn || compileToJS(schemaObj, null, sm);
1026
- jsCombinedFn = compileToJSCombined(schemaObj, VALID_RESULT, sm, uf);
1027
- jsErrFn = compileToJSCodegenWithErrors(schemaObj, sm, uf);
1057
+ // Only the verdict is compiled here. The error and combined generators
1058
+ // are the other two thirds of a cold first call (8.2, 10.4 and 7.8 ms on
1059
+ // a 120-property config schema, most of it V8 compiling each generator
1060
+ // the first time it is entered), and a caller that never reads an error
1061
+ // never needs them. _buildDeferred compiles them on the first rejection.
1062
+ jsCombinedFn = undefined;
1063
+ jsErrFn = undefined;
1028
1064
  _isCodegen = !!_cgFn;
1029
1065
  this._engine = _cgFn ? 'codegen' : jsFn ? 'closure' : null;
1030
1066
  if (!uf) {
1031
- _compileCache.set(mapKey, { jsFn, combined: jsCombinedFn, errFn: jsErrFn, isCodegen: _isCodegen, full: true });
1067
+ _compileCache.set(mapKey, { jsFn, combined: undefined, errFn: undefined, isCodegen: _isCodegen, full: false });
1032
1068
  }
1033
1069
  } else {
1034
1070
  jsFn = null; jsCombinedFn = null; jsErrFn = null;
1035
1071
  }
1036
1072
  this._jsFn = jsFn;
1037
- if (this._engine === undefined) this._engine = (cached && cached.full) ? (cached.isCodegen ? 'codegen' : jsFn ? 'closure' : null) : null;
1073
+ if (this._engine === undefined) this._engine = null;
1038
1074
 
1039
1075
  // Data mutators -- try codegen first (12x faster), fallback to closure arrays.
1040
1076
  // Follow cross-refs so coercion/defaults/removeAdditional see the referenced
@@ -1080,14 +1116,26 @@ class Validator {
1080
1116
  )));
1081
1117
  const useSimdjsonForLarge = !hasArrayTraversal;
1082
1118
 
1083
- if (jsFn) {
1084
- let safeErrFn = null;
1085
- if (jsErrFn) {
1086
- try {
1087
- jsErrFn({}, true);
1088
- safeErrFn = (d) => jsErrFn(d, true);
1089
- } catch {}
1119
+ // Builds the two generators the compile step left out, once, and upgrades
1120
+ // the shared cache entry. `undefined` means not built yet; `null` means the
1121
+ // compiler declined. Conflating those is what once cost every schema its
1122
+ // generated error function for the life of the process, so they stay apart.
1123
+ const _buildDeferred = () => {
1124
+ if (jsCombinedFn !== undefined && jsErrFn !== undefined) return;
1125
+ const uf2 = this._userFormats;
1126
+ if (jsCombinedFn === undefined) jsCombinedFn = compileToJSCombined(schemaObj, VALID_RESULT, sm, uf2) || null;
1127
+ if (jsErrFn === undefined) jsErrFn = compileToJSCodegenWithErrors(schemaObj, sm, uf2) || null;
1128
+ if (!uf2) {
1129
+ const entry = _compileCache.get(mapKey);
1130
+ if (entry && entry.jsFn === jsFn) {
1131
+ entry.combined = jsCombinedFn;
1132
+ entry.errFn = jsErrFn;
1133
+ entry.full = true;
1134
+ }
1090
1135
  }
1136
+ };
1137
+
1138
+ if (jsFn) {
1091
1139
  // errFn: use JS codegen if safe, else native fallback (only when native
1092
1140
  // is available). Environments without the native addon — Cloudflare
1093
1141
  // Workers, browser, Bun without N-API — get a JS-only fallback so the
@@ -1126,19 +1174,38 @@ class Validator {
1126
1174
  // reports those schemas correctly, so failing data is re-validated
1127
1175
  // there. This used to be a placeholder error with no keyword and no
1128
1176
  // path, which hid whatever had actually failed.
1129
- const errFn =
1130
- safeErrFn ||
1131
- (hasUnevaluated || !native
1132
- ? jsOnlyFallback
1133
- : hasDynRef
1134
- ? (d) => {
1135
- this._ensureNative();
1136
- return this._compiled.validateJSON(JSON.stringify(d));
1137
- }
1138
- : (d) => {
1139
- this._ensureNative();
1140
- return this._compiled.validate(d);
1141
- });
1177
+ // Resolved on the first rejection rather than at compile time, because
1178
+ // building the generator behind it is two thirds of what a first call
1179
+ // costs and a caller that never reads an error never needs it. The probe
1180
+ // moves here with it: it calls the generated function, so it cannot run
1181
+ // before the function exists.
1182
+ let _errOnlyImpl = null;
1183
+ const errOnly = (d) => {
1184
+ if (_errOnlyImpl === null) {
1185
+ _buildDeferred();
1186
+ let safe = null;
1187
+ if (jsErrFn) {
1188
+ try {
1189
+ jsErrFn({}, true);
1190
+ safe = (x) => jsErrFn(x, true);
1191
+ } catch {}
1192
+ }
1193
+ _errOnlyImpl =
1194
+ safe ||
1195
+ (hasUnevaluated || !native
1196
+ ? jsOnlyFallback
1197
+ : hasDynRef
1198
+ ? (x) => {
1199
+ this._ensureNative();
1200
+ return this._compiled.validateJSON(JSON.stringify(x));
1201
+ }
1202
+ : (x) => {
1203
+ this._ensureNative();
1204
+ return this._compiled.validate(x);
1205
+ });
1206
+ }
1207
+ return _errOnlyImpl(d);
1208
+ };
1142
1209
 
1143
1210
  // Best path: combined validator (single pass, validates + collects errors)
1144
1211
  // Valid data: returns VALID_RESULT, no allocation
@@ -1148,24 +1215,41 @@ class Validator {
1148
1215
  // Test combined at compile time -- some schemas (e.g. if/then/else)
1149
1216
  // produce broken combined code that crashes on certain inputs.
1150
1217
  // We probe with diverse data; if any throws, fall back to hybrid.
1151
- let safeCombinedFn = null;
1152
- if (jsCombinedFn) {
1153
- try {
1154
- const probe = {};
1155
- // Populate probe with one key per known property to trigger nested paths
1156
- if (schemaObj && schemaObj.properties) {
1157
- for (const k of Object.keys(schemaObj.properties)) probe[k] = "";
1158
- }
1159
- if (schemaObj && schemaObj.if && schemaObj.if.properties) {
1160
- for (const k of Object.keys(schemaObj.if.properties)) probe[k] = "";
1161
- }
1162
- jsCombinedFn(probe);
1163
- jsCombinedFn({});
1164
- jsCombinedFn(null);
1165
- jsCombinedFn(0);
1166
- safeCombinedFn = jsCombinedFn;
1167
- } catch {}
1168
- }
1218
+ let _combinedProbed = false;
1219
+ let _safeCombined = null;
1220
+ const combinedIfSafe = () => {
1221
+ if (_combinedProbed) return _safeCombined;
1222
+ _combinedProbed = true;
1223
+ _buildDeferred();
1224
+ if (jsCombinedFn) {
1225
+ try {
1226
+ const probe = {};
1227
+ // Populate probe with one key per known property to trigger nested paths
1228
+ if (schemaObj && schemaObj.properties) {
1229
+ for (const k of Object.keys(schemaObj.properties)) probe[k] = "";
1230
+ }
1231
+ if (schemaObj && schemaObj.if && schemaObj.if.properties) {
1232
+ for (const k of Object.keys(schemaObj.if.properties)) probe[k] = "";
1233
+ }
1234
+ jsCombinedFn(probe);
1235
+ jsCombinedFn({});
1236
+ jsCombinedFn(null);
1237
+ jsCombinedFn(0);
1238
+ _safeCombined = jsCombinedFn;
1239
+ } catch {}
1240
+ }
1241
+ return _safeCombined;
1242
+ };
1243
+
1244
+ // What the hybrid path hands to its error slot: the combined function
1245
+ // when it is usable, since it validates and collects in one pass, and
1246
+ // the error generator otherwise. Same order the eager code chose, just
1247
+ // chosen on the first rejection.
1248
+ let _errPreferredImpl = null;
1249
+ const errPreferCombined = (d) => {
1250
+ if (_errPreferredImpl === null) _errPreferredImpl = combinedIfSafe() || errOnly;
1251
+ return _errPreferredImpl(d);
1252
+ };
1169
1253
 
1170
1254
  // The boolean engine is the verdict authority for these paths; the
1171
1255
  // final lazy wrapper uses it to skip error construction entirely.
@@ -1182,7 +1266,7 @@ class Validator {
1182
1266
  : (data) => (_fn(data) ? VALID_RESULT : ABORT_EARLY_RESULT);
1183
1267
  } else if (hasDynRef && _isCodegen && jsFn) {
1184
1268
  // $dynamicRef with JS codegen: direct path, no wrapper layers
1185
- const _fn = jsFn, _efn = safeErrFn || errFn, _R = VALID_RESULT;
1269
+ const _fn = jsFn, _efn = errOnly, _R = VALID_RESULT;
1186
1270
  this.validate = preprocess
1187
1271
  ? (data) => { preprocess(data); return _fn(data) ? _R : _efn(data); }
1188
1272
  : (data) => _fn(data) ? _R : _efn(data);
@@ -1207,31 +1291,29 @@ class Validator {
1207
1291
  } else if (jsFn && jsFn._hybridFactory) {
1208
1292
  // Zero-wrapper: hybridFactory bakes VALID_RESULT + errFn into a single function
1209
1293
  // No arrow function wrapper, no ternary, one function call
1210
- const hybridFn = jsFn._hybridFactory(VALID_RESULT, safeCombinedFn || errFn);
1294
+ // The factory bakes the error function in as an argument and never
1295
+ // calls it for a document that passes, so a resolver here costs the
1296
+ // accepted path nothing and keeps the compile off the first call.
1297
+ const hybridFn = jsFn._hybridFactory(VALID_RESULT, errPreferCombined);
1211
1298
  this.validate = preprocess
1212
1299
  ? (data) => { preprocess(data); return hybridFn(data); }
1213
1300
  : hybridFn;
1214
- } else if (safeCombinedFn) {
1215
- this.validate = preprocess
1216
- ? (data) => { preprocess(data); return safeCombinedFn(data); }
1217
- : safeCombinedFn;
1218
1301
  } else {
1219
- const hybridFn = jsFn && jsFn._hybridFactory
1220
- ? jsFn._hybridFactory(VALID_RESULT, errFn)
1221
- : null;
1222
- this.validate = hybridFn
1223
- ? preprocess
1302
+ // No hybrid factory, so the assembly needs the function itself rather
1303
+ // than a reference it can call later: build it now.
1304
+ const safeCombinedFn = combinedIfSafe();
1305
+ if (safeCombinedFn) {
1306
+ this.validate = preprocess
1307
+ ? (data) => { preprocess(data); return safeCombinedFn(data); }
1308
+ : safeCombinedFn;
1309
+ } else {
1310
+ this.validate = preprocess
1224
1311
  ? (data) => {
1225
1312
  preprocess(data);
1226
- return hybridFn(data);
1313
+ return jsFn(data) ? VALID_RESULT : errOnly(data);
1227
1314
  }
1228
- : hybridFn
1229
- : preprocess
1230
- ? (data) => {
1231
- preprocess(data);
1232
- return jsFn(data) ? VALID_RESULT : errFn(data);
1233
- }
1234
- : (data) => (jsFn(data) ? VALID_RESULT : errFn(data));
1315
+ : (data) => (jsFn(data) ? VALID_RESULT : errOnly(data));
1316
+ }
1235
1317
  }
1236
1318
  // Verbose mode: populate parentSchema, schema and data on each error, the
1237
1319
  // three fields the default error shape carries under the same option.
@@ -1274,12 +1356,15 @@ class Validator {
1274
1356
  this.isValidObject = preprocess
1275
1357
  ? (data) => { preprocess(data); return jsFn(data) }
1276
1358
  : jsFn;
1359
+ // Same preference as the object path: the combined function first, since
1360
+ // it validates and collects in one pass, and the error generator behind
1361
+ // it. `errPreferCombined` is that order, resolved on the first rejection
1362
+ // instead of at compile time.
1277
1363
  const hybridFn = jsFn._hybridFactory
1278
- ? jsFn._hybridFactory(VALID_RESULT, errFn)
1364
+ ? jsFn._hybridFactory(VALID_RESULT, errPreferCombined)
1279
1365
  : null;
1280
- const jsonValidateInner = safeCombinedFn
1281
- || hybridFn
1282
- || ((obj) => (jsFn(obj) ? VALID_RESULT : errFn(obj)));
1366
+ const jsonValidateInner = hybridFn
1367
+ || ((obj) => (jsFn(obj) ? VALID_RESULT : errPreferCombined(obj)));
1283
1368
  // Parsed text takes the same preprocess pass as a parsed object, so
1284
1369
  // validate(obj) and validateJSON(text) answer the same for the same
1285
1370
  // document. Without it, coercion, removal and defaults applied on one
@@ -1799,15 +1884,20 @@ class Validator {
1799
1884
  return;
1800
1885
  }
1801
1886
  const uf = this._userFormats;
1802
- const jsFn = compileToJSCodegen(this._schemaObj, sm, uf) || compileToJS(this._schemaObj, null, sm);
1887
+ const _cg = compileToJSCodegen(this._schemaObj, sm, uf);
1888
+ const jsFn = _cg || compileToJS(this._schemaObj, null, sm);
1803
1889
  this._jsFn = jsFn;
1804
1890
  if (jsFn) {
1805
1891
  this.isValidObject = jsFn;
1806
1892
  // A partial entry: the verdict function is real, the other two are not
1807
- // built yet rather than declined. `full: false` says so, so the next
1808
- // caller that needs errors compiles them instead of inheriting nulls.
1893
+ // built yet rather than declined. `undefined` is the not-built marker
1894
+ // the full compile's _buildDeferred looks for; `null` would read as
1895
+ // "the compiler declined" and cost the schema its error function, which
1896
+ // is the bug this cache had once already. `isCodegen` rides along so a
1897
+ // validator that later reuses this entry reports the same engine it
1898
+ // would have compiled to.
1809
1899
  if (!uf) {
1810
- if (!cached) _compileCache.set(mapKey, { jsFn, combined: null, errFn: null, full: false });
1900
+ if (!cached) _compileCache.set(mapKey, { jsFn, combined: undefined, errFn: undefined, isCodegen: !!_cg, full: false });
1811
1901
  else cached.jsFn = jsFn;
1812
1902
  }
1813
1903
  }
@@ -49,7 +49,14 @@ function pointerOf(schemaPath) {
49
49
  }
50
50
 
51
51
  function segments(pointer) {
52
- return pointer === '' ? [] : pointer.split('/').slice(1);
52
+ // split-then-slice built two arrays per call, and this is called for every
53
+ // error's pointer and every pointer walk. Dropping everything before the
54
+ // first '/' first builds one. Same answer for every input shape, including
55
+ // a pointer with no slash at all, which the old form turned into [].
56
+ if (pointer === '') return [];
57
+ const i = pointer.indexOf('/');
58
+ if (i < 0) return [];
59
+ return pointer.slice(i + 1).split('/');
53
60
  }
54
61
 
55
62
  // Walks a pointer's segments and reports, per position, whether that segment
@@ -67,24 +74,41 @@ function classify(segs) {
67
74
  return kinds;
68
75
  }
69
76
 
70
- function compareReferenceOrder(a, b) {
71
- const pa = segments(pointerOf(a.schemaPath || ''));
72
- const pb = segments(pointerOf(b.schemaPath || ''));
73
- const ka = classify(pa);
74
- const n = Math.min(pa.length, pb.length);
75
- for (let i = 0; i < n; i++) {
76
- if (pa[i] === pb[i]) continue;
77
- if (ka[i] !== 'kw') return 0;
78
- const ra = REFERENCE_RANK.get(pa[i]);
79
- const rb = REFERENCE_RANK.get(pb[i]);
80
- if (ra === undefined || rb === undefined) return 0;
81
- return ra - rb;
82
- }
83
- return 0;
77
+ // One derived key per error: its pointer relative to the hash, the split
78
+ // segments, and each segment's kind. Everything below used to re-derive these
79
+ // wherever it needed them, which for one shaped rejection meant splitting the
80
+ // same pointer three times per error.
81
+ function keyFor(e) {
82
+ const rel = pointerOf(e && e.schemaPath ? e.schemaPath : '#');
83
+ const segs = segments(rel);
84
+ return { rel, segs, kinds: classify(segs) };
85
+ }
86
+
87
+ function sortByKeys(errors, keys) {
88
+ if (errors.length < 2) return errors.slice();
89
+ const keyed = errors.map((e) => ({ e, k: keys.get(e) }));
90
+ keyed.sort((A, B) => {
91
+ const pa = A.k.segs, pb = B.k.segs, ka = A.k.kinds;
92
+ const n = Math.min(pa.length, pb.length);
93
+ for (let i = 0; i < n; i++) {
94
+ if (pa[i] === pb[i]) continue;
95
+ if (ka[i] !== 'kw') return 0;
96
+ const ra = REFERENCE_RANK.get(pa[i]);
97
+ const rb = REFERENCE_RANK.get(pb[i]);
98
+ if (ra === undefined || rb === undefined) return 0;
99
+ return ra - rb;
100
+ }
101
+ return 0;
102
+ });
103
+ for (let i = 0; i < keyed.length; i++) keyed[i] = keyed[i].e;
104
+ return keyed;
84
105
  }
85
106
 
86
107
  function sortLikeReference(errors) {
87
- return errors.length < 2 ? errors : errors.slice().sort(compareReferenceOrder);
108
+ if (errors.length < 2) return errors;
109
+ const keys = new Map();
110
+ for (const e of errors) keys.set(e, keyFor(e));
111
+ return sortByKeys(errors, keys);
88
112
  }
89
113
 
90
114
  function walkPointer(node, pointer) {
@@ -151,14 +175,16 @@ function createShaper({ Validator, rootId, rootDoc, schemas, options, allErrors,
151
175
  // `basePointer` is where `errors` were produced relative to the root
152
176
  // document; a branch validator reports paths relative to its branch.
153
177
  function shape(errors, data, basePointer) {
154
- const sorted = sortLikeReference(errors);
178
+ const keys = new Map();
179
+ for (const e of errors) keys.set(e, keyFor(e));
180
+ const sorted = sortByKeys(errors, keys);
155
181
  const out = [];
156
182
  const consumed = new Set();
157
183
  let thenGroup = null; // { prefix, which } for the pending `if` wrapper
158
184
 
159
185
  const flushIf = (e) => {
160
186
  if (thenGroup === null) return;
161
- const segs = segments(pointerOf(e ? e.schemaPath : ''));
187
+ const segs = e ? keys.get(e).segs : [];
162
188
  const still = e && segs.length > thenGroup.depth && segs[thenGroup.depth] === thenGroup.which &&
163
189
  segs.slice(0, thenGroup.depth).join('/') === thenGroup.prefixSegs;
164
190
  if (still) return;
@@ -170,10 +196,11 @@ function createShaper({ Validator, rootId, rootDoc, schemas, options, allErrors,
170
196
  for (let idx = 0; idx < sorted.length; idx++) {
171
197
  const e = sorted[idx];
172
198
  if (consumed.has(e)) continue;
173
- const rel = pointerOf(e.schemaPath || '#');
199
+ const K = keys.get(e);
200
+ const rel = K.rel;
174
201
  const abs = basePointer + rel;
175
- const segs = segments(rel);
176
- const kinds = classify(segs);
202
+ const segs = K.segs;
203
+ const kinds = K.kinds;
177
204
  const schemaPrefix = (e.schemaPath || '#').slice(0, (e.schemaPath || '#').indexOf('#') + 1);
178
205
 
179
206
  if (allErrors) flushIf(e);
@@ -234,7 +261,7 @@ function createShaper({ Validator, rootId, rootDoc, schemas, options, allErrors,
234
261
  // instance is replaced by the regenerated sequence.
235
262
  for (let j = idx; j < sorted.length; j++) {
236
263
  const o = sorted[j];
237
- if ((o.instancePath || '') === (e.instancePath || '') && pointerOf(o.schemaPath || '#').startsWith(pnPointer + '/')) consumed.add(o);
264
+ if ((o.instancePath || '') === (e.instancePath || '') && keys.get(o).rel.startsWith(pnPointer + '/')) consumed.add(o);
238
265
  }
239
266
  const pnSchemaPath = schemaPrefix + pnPointer;
240
267
  for (const name of Object.keys(object)) {
@@ -2612,8 +2612,14 @@ function genCode(schema, v, lines, ctx, knownType) {
2612
2612
  }
2613
2613
  // else: already emitted early (before properties)
2614
2614
  } else if (propCount > 0) {
2615
- // TRICK 3: charCodeAt switch tree
2616
- inner = genCharCodeSwitch(knownKeys, v)
2615
+ // TRICK 3: charCodeAt switch tree. Above the lookup threshold it is
2616
+ // the additionalProperties quadratic wearing a disguise: keys that
2617
+ // share a first character, ENV_0 through ENV_999 say, all land in
2618
+ // one case whose body is a chain of every name, per key of the
2619
+ // document. The hoisted name lookup is the same cure it was there.
2620
+ inner = propCount >= AP_LOOKUP_MIN
2621
+ ? apMembershipCheck(ctx, knownKeys, v)
2622
+ : genCharCodeSwitch(knownKeys, v)
2617
2623
  _deferOrInline(ctx, lines, v, isObj ? inner : `if(typeof ${v}==='object'&&${v}!==null&&!Array.isArray(${v})){${inner}}`)
2618
2624
  } else {
2619
2625
  inner = `for(var _k in ${v})return false`
@@ -2627,8 +2633,13 @@ function genCode(schema, v, lines, ctx, knownType) {
2627
2633
  genCode(schema.unevaluatedProperties, `${v}[${ukVar}]`, subLines, ctx)
2628
2634
  if (subLines.length > 0) {
2629
2635
  const check = subLines.join(';')
2630
- const keyChecks = knownKeys.map(k => `${ukVar}===${JSON.stringify(k)}`).join('||')
2631
- const skipKnown = knownKeys.length > 0 ? `if(${keyChecks})continue;` : ''
2636
+ let skipKnown = ''
2637
+ if (knownKeys.length >= AP_LOOKUP_MIN) {
2638
+ const id = emitNameLookup(ctx, knownKeys)
2639
+ skipKnown = id !== null ? `if(${id}[${ukVar}]!==undefined)continue;` : `if(${knownKeys.map(k => `${ukVar}===${JSON.stringify(k)}`).join('||')})continue;`
2640
+ } else if (knownKeys.length > 0) {
2641
+ skipKnown = `if(${knownKeys.map(k => `${ukVar}===${JSON.stringify(k)}`).join('||')})continue;`
2642
+ }
2632
2643
  const inner = `for(var ${ukVar} in ${v}){${skipKnown}${check}}`
2633
2644
  _deferOrInline(ctx, lines, v, isObj ? inner : `if(typeof ${v}==='object'&&${v}!==null&&!Array.isArray(${v})){${inner}}`)
2634
2645
  }
@@ -0,0 +1,142 @@
1
+ 'use strict';
2
+
3
+ // The authoring-time check behind `strictSchema`. A mistyped keyword is the
4
+ // one schema mistake that fails open: `maxLenght` is not an error to any
5
+ // JSON Schema dialect, it is an annotation, so the intended constraint is
6
+ // simply absent and previously invalid data starts validating. A validator
7
+ // cannot make that safe at validation time. It can be loud about it when the
8
+ // schema is compiled, which is what this is.
9
+ //
10
+ // The known-keyword list is not written out here. Each vendored meta-schema's
11
+ // `properties` are precisely its dialect's keywords, so the list comes from
12
+ // the specification documents in `metaschemas.js` rather than from a copy
13
+ // someone keeps in step. The union across vendored dialects is used, not the
14
+ // schema's own dialect: a draft-07 keyword in a 2020-12 schema is a real
15
+ // authoring question but not a fail-open one, and a union cannot produce a
16
+ // false positive on a valid document.
17
+
18
+ const { METASCHEMAS } = require('./metaschemas');
19
+ const { levenshtein } = require('./levenshtein');
20
+
21
+ // Keywords ata implements beyond the official vocabularies.
22
+ const ATA_KEYWORDS = ['nullable', 'errorMessage', 'propertyDependencies', 'discriminator'];
23
+
24
+ let SPEC_KEYWORDS = null;
25
+ function specKeywords() {
26
+ if (SPEC_KEYWORDS === null) {
27
+ SPEC_KEYWORDS = new Set(ATA_KEYWORDS);
28
+ for (const doc of METASCHEMAS.values()) {
29
+ if (doc && doc.properties) for (const k of Object.keys(doc.properties)) SPEC_KEYWORDS.add(k);
30
+ }
31
+ }
32
+ return SPEC_KEYWORDS;
33
+ }
34
+
35
+ // Where subschemas live, so the walk knows a key under `properties` is a
36
+ // property name and a key directly on a schema object is a keyword.
37
+ const SCHEMA_MAPS = new Set(['properties', 'patternProperties', '$defs', 'definitions', 'dependentSchemas']);
38
+ const SCHEMA_LISTS = new Set(['allOf', 'anyOf', 'oneOf', 'prefixItems']);
39
+ const SCHEMA_SINGLE = new Set([
40
+ 'items', 'additionalItems', 'additionalProperties', 'unevaluatedProperties',
41
+ 'unevaluatedItems', 'contains', 'propertyNames', 'not', 'if', 'then', 'else',
42
+ 'contentSchema',
43
+ ]);
44
+ // Values that are data, never schemas, however object-shaped they look.
45
+ const DATA_VALUED = new Set(['enum', 'const', 'default', 'examples', 'required', 'type', '$vocabulary']);
46
+
47
+ function nearest(word, known) {
48
+ let best = null;
49
+ let bestD = Infinity;
50
+ for (const k of known) {
51
+ // A candidate further away than half the word is noise, not a typo.
52
+ const d = levenshtein(word, k);
53
+ if (d < bestD) { bestD = d; best = k; }
54
+ }
55
+ return bestD > 0 && bestD <= 2 && best !== null ? best : null;
56
+ }
57
+
58
+ function resolveLocalPointer(root, ref) {
59
+ let node = root;
60
+ for (const raw of ref.slice(2).split('/')) {
61
+ const token = raw.replace(/~1/g, '/').replace(/~0/g, '~');
62
+ if (node === null || typeof node !== 'object') return false;
63
+ if (Array.isArray(node)) {
64
+ if (!/^\d+$/.test(token) || Number(token) >= node.length) return false;
65
+ node = node[Number(token)];
66
+ } else {
67
+ if (!Object.prototype.hasOwnProperty.call(node, token)) return false;
68
+ node = node[token];
69
+ }
70
+ }
71
+ return node !== undefined;
72
+ }
73
+
74
+ /**
75
+ * Walks a schema and reports the authoring mistakes the validator would
76
+ * otherwise ignore. Returns an array of { path, message }, empty when clean.
77
+ *
78
+ * `userKeywords` are names registered through the `keywords` option, and
79
+ * `x-` prefixed names pass without comment: they are the conventional
80
+ * extension namespace and rejecting them would flag real documents.
81
+ */
82
+ function checkSchemaStrict(root, options) {
83
+ const known = specKeywords();
84
+ const user = options && options.userKeywords ? options.userKeywords : null;
85
+ const problems = [];
86
+ const seen = new Set();
87
+
88
+ function isKnown(key) {
89
+ if (known.has(key)) return true;
90
+ if (user && (Array.isArray(user) ? user.includes(key) : Object.prototype.hasOwnProperty.call(user, key))) return true;
91
+ if (key.startsWith('x-')) return true;
92
+ return false;
93
+ }
94
+
95
+ function walkSchema(node, path) {
96
+ if (node === null || typeof node !== 'object' || Array.isArray(node)) return;
97
+ if (seen.has(node)) return;
98
+ seen.add(node);
99
+ for (const key of Object.keys(node)) {
100
+ const value = node[key];
101
+ const at = path + '/' + key;
102
+ if (!isKnown(key)) {
103
+ const hint = nearest(key, known);
104
+ problems.push({
105
+ path: at,
106
+ message: hint
107
+ ? `unknown keyword "${key}" (did you mean "${hint}"?)`
108
+ : `unknown keyword "${key}"`,
109
+ });
110
+ continue;
111
+ }
112
+ if (key === '$ref' && typeof value === 'string' && value.startsWith('#/') && !resolveLocalPointer(root, value)) {
113
+ problems.push({ path: at, message: `$ref "${value}" does not resolve in this document` });
114
+ continue;
115
+ }
116
+ if (DATA_VALUED.has(key)) continue;
117
+ if (SCHEMA_MAPS.has(key)) {
118
+ if (value !== null && typeof value === 'object' && !Array.isArray(value)) {
119
+ for (const name of Object.keys(value)) walkSchema(value[name], at + '/' + name.replace(/~/g, '~0').replace(/\//g, '~1'));
120
+ }
121
+ } else if (SCHEMA_LISTS.has(key)) {
122
+ if (Array.isArray(value)) for (let i = 0; i < value.length; i++) walkSchema(value[i], at + '/' + i);
123
+ } else if (SCHEMA_SINGLE.has(key)) {
124
+ // draft-07 lets `items` be an array of schemas.
125
+ if (Array.isArray(value)) { for (let i = 0; i < value.length; i++) walkSchema(value[i], at + '/' + i); }
126
+ else walkSchema(value, at);
127
+ } else if (key === 'dependencies') {
128
+ // draft-07: each value is either a schema or an array of names.
129
+ if (value !== null && typeof value === 'object' && !Array.isArray(value)) {
130
+ for (const name of Object.keys(value)) {
131
+ if (!Array.isArray(value[name])) walkSchema(value[name], at + '/' + name);
132
+ }
133
+ }
134
+ }
135
+ }
136
+ }
137
+
138
+ walkSchema(root, '#');
139
+ return problems;
140
+ }
141
+
142
+ module.exports = { checkSchemaStrict };
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.22.1';
10
+ module.exports = '1.23.0';
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "ata-validator",
3
- "version": "1.22.1",
3
+ "version": "1.23.0",
4
4
  "description": "JSON Schema validation that compiles for speed and still runs where code generation is blocked. Compiled and interpreted engines answer identically at 100% of the official suite. TypeScript inference, Standard Schema V1, and a build step that emits dependency-free modules.",
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_exclusive_bounds.js && node tests/test_engine_differential.js && node tests/test_error_shape_differential.js && node tests/test_output_format.js && node tests/test_retry_message.js && node tests/test_describe_schema.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_compile_cache_order.js && node tests/test_engine_diagnostic.js && node tests/test_format_engine_parity.js && node tests/test_uri_helper_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_aot_external_checks.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_unevaluated_error_shape.js && node tests/test_pattern_properties_errors.js && node tests/test_additional_properties_scaling.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_lazy_normalization.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_malformed_json_termination.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_exclusive_bounds.js && node tests/test_engine_differential.js && node tests/test_error_shape_differential.js && node tests/test_output_format.js && node tests/test_retry_message.js && node tests/test_describe_schema.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_compile_cache_order.js && node tests/test_engine_diagnostic.js && node tests/test_format_engine_parity.js && node tests/test_uri_helper_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_strict_schema.js && node tests/test_aot_external_checks.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_unevaluated_error_shape.js && node tests/test_pattern_properties_errors.js && node tests/test_additional_properties_scaling.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_lazy_normalization.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_malformed_json_termination.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.22.1",
126
- "@ata-validator/native-darwin-x64": "1.22.1",
127
- "@ata-validator/native-linux-arm64-gnu": "1.22.1",
128
- "@ata-validator/native-linux-arm64-musl": "1.22.1",
129
- "@ata-validator/native-linux-x64-gnu": "1.22.1",
130
- "@ata-validator/native-linux-x64-musl": "1.22.1",
131
- "@ata-validator/native-win32-x64": "1.22.1"
125
+ "@ata-validator/native-darwin-arm64": "1.23.0",
126
+ "@ata-validator/native-darwin-x64": "1.23.0",
127
+ "@ata-validator/native-linux-arm64-gnu": "1.23.0",
128
+ "@ata-validator/native-linux-arm64-musl": "1.23.0",
129
+ "@ata-validator/native-linux-x64-gnu": "1.23.0",
130
+ "@ata-validator/native-linux-x64-musl": "1.23.0",
131
+ "@ata-validator/native-win32-x64": "1.23.0"
132
132
  },
133
133
  "peerDependencies": {
134
134
  "yaml": "^2.0.0"