ata-validator 1.61.0 → 1.61.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.
Files changed (43) hide show
  1. package/README.md +34 -7
  2. package/compiled-bare-rich.d.ts +6 -0
  3. package/compiled-bare.d.ts +7 -0
  4. package/compiled-bare.js +5 -0
  5. package/compiled-bare.mjs +35 -0
  6. package/compiled-object.d.ts +11 -0
  7. package/compiled-object.js +6 -0
  8. package/compiled-object.mjs +3 -0
  9. package/compiled-plain.d.ts +16 -0
  10. package/compiled-plain.js +5 -0
  11. package/compiled-plain.mjs +3 -0
  12. package/index.aot.js +8 -0
  13. package/index.aot.mjs +5 -0
  14. package/lib/aot-build.js +9 -1
  15. package/lib/aot-impl.js +10 -2
  16. package/lib/aot-mark.js +7 -0
  17. package/lib/compiled-bare-rich.js +54 -0
  18. package/lib/compiled-bare.js +44 -0
  19. package/lib/compiled-common.js +17 -2
  20. package/lib/compiled-defaults.browser.js +9 -0
  21. package/lib/compiled-defaults.js +8 -0
  22. package/lib/compiled-object.js +123 -0
  23. package/lib/compiled-plain.js +57 -0
  24. package/lib/compiled.js +2 -1
  25. package/lib/email-literal.js +29 -0
  26. package/lib/enrich-error.js +5 -1
  27. package/lib/error-codes.js +6 -76
  28. package/lib/formats-source.js +20 -3
  29. package/lib/formats.js +28 -1
  30. package/lib/interpreter-core.js +4 -28
  31. package/lib/js-compiler-errors.js +4 -0
  32. package/lib/json-rejections.js +159 -0
  33. package/lib/native-codes.js +67 -0
  34. package/lib/native-message.js +8 -0
  35. package/lib/native.browser.js +11 -0
  36. package/lib/rejections.js +13 -152
  37. package/lib/shape-classifier.js +6 -1
  38. package/lib/standard-schema.js +1 -1
  39. package/lib/tier0.js +17 -4
  40. package/lib/validator-core.js +50 -19
  41. package/lib/verify.js +68 -3
  42. package/lib/version.js +1 -1
  43. package/package.json +54 -10
package/lib/rejections.js CHANGED
@@ -1,13 +1,12 @@
1
1
  'use strict';
2
2
 
3
- // The rejection objects validate() and validateJSON() return, and the helpers
3
+ // The rejection objects validate() returns, and the helpers
4
4
  // that build their errors on first read: declaration-order sorting,
5
5
  // enrichment, typo correlation and source frames. Shared by the validator core
6
6
  // and by the wrapper around a compiled module, so both report errors through
7
- // the same code.
7
+ // the same code. The one validateJSON() returns is in lib/json-rejections.js.
8
8
 
9
9
  const { ordinalFor: schemaOrdinal, rankFor: schemaRank } = require('./schema-order');
10
- const { setDiagnosticSource: attachDiagnosticSource } = require('./diagnostic-source');
11
10
 
12
11
  // Rejection result with errors materialized on first read. The accessor
13
12
  // lives on the prototype so constructing one is a plain allocation; an
@@ -80,6 +79,15 @@ class RichRejection {
80
79
  return raw;
81
80
  }
82
81
  }
82
+ // The position map builder of lib/json-rejections.js, which sets it when it
83
+ // loads. Only validateJSON passes JSON text here, and the code that calls it
84
+ // loads that module first (were it not, the call below throws, it does not
85
+ // skip the frames); validate() and a compiled wrapper without the JSON
86
+ // entry points never load it, so their bundles carry neither it nor the
87
+ // rejection validateJSON returns (532 bytes gzipped of the wrapper).
88
+ let _positionsFor = null;
89
+ function _setPositionsFor (fn) { _positionsFor = fn; }
90
+
83
91
  // Orders a raw error list and gives it the shape the caller asked for: the
84
92
  // legacy key set, or enriched. Shared by both rejection classes below.
85
93
  function presentErrors(raw, data, rawInput, self, root, enrich) {
@@ -87,7 +95,7 @@ function presentErrors(raw, data, rawInput, self, root, enrich) {
87
95
  // The position map, resolved on the first read of an error's frame (see
88
96
  // defineLazyFrames in lib/enrich-error.js): a walk of the document that a
89
97
  // caller reading messages and paths never needs.
90
- const lazyPositions = enrich && raw.length && rawInput != null ? lazyPositionsFor(self, rawInput, raw) : null;
98
+ const lazyPositions = enrich && raw.length && rawInput != null ? _positionsFor(self, rawInput, raw) : null;
91
99
  // One options object for the whole list, not one per error.
92
100
  const opts = enrich && raw.length
93
101
  ? {
@@ -126,153 +134,6 @@ Object.defineProperty(RichRejection.prototype, 'errors', {
126
134
  });
127
135
 
128
136
 
129
- // The rejection validateJSON returns. Everything the text path adds over
130
- // validate(data), the value tree enrichment needs, the position map, the
131
- // diagnostic payload, happens on first access to `.errors`. Reading `.valid`
132
- // touches none of it. The JSON text is held here rather than read back off the
133
- // validator, so the result does not depend on what the instance does next.
134
- class LazyJsonRejection {
135
- // `parsed`, when given, is the document the inner path parsed from
136
- // `jsonStr` and validated, after defaults, coercion and removal: reading the
137
- // errors uses it instead of parsing the text again, which on a 5 MB
138
- // document was a third of what reading them cost.
139
- constructor(result, jsonStr, self, enrich, parsed) {
140
- this.valid = false;
141
- this._result = result;
142
- this._jsonStr = jsonStr;
143
- this._self = self;
144
- this._enrich = enrich;
145
- this._cached = null;
146
- this._parsed = parsed;
147
- }
148
- toJSON() {
149
- return { valid: false, errors: this.errors };
150
- }
151
- }
152
- Object.defineProperty(LazyJsonRejection.prototype, 'errors', {
153
- enumerable: true,
154
- configurable: true,
155
- get() {
156
- if (this._cached !== null) return this._cached;
157
- const self = this._self;
158
- const enrich = this._enrich;
159
- const jsonStr = this._jsonStr;
160
- // Reading the inner errors realizes the inner lazy layer, if there was one.
161
- const raw = this._result.errors || [];
162
- if (!raw.length) { this._cached = raw; return raw; }
163
-
164
- // The enrich pass plucks `received` from the value tree and the suggestion
165
- // engine (required-typo, format hints, coercion nudges) walks it too.
166
- let parsedData = this._parsed;
167
- if (parsedData === undefined) {
168
- try { parsedData = JSON.parse(jsonStr); } catch { parsedData = undefined; }
169
- // What was checked is the document after defaults, coercion and removal,
170
- // so `received` has to come from that, as it does for validate(). A
171
- // default that failed its own schema has no value in the text at all.
172
- if (parsedData !== undefined && self._preprocess) self._preprocess(parsedData);
173
- }
174
-
175
- // Errors the inner path already enriched carry a `received` key: enrich()
176
- // always sets one, even when there is no value to show. `code` is not a
177
- // safe signal, because branch-collapse attaches codes to raw errors, and
178
- // `docUrl` is not either, because the generated error functions stamp it
179
- // on raw errors; taking it as the signal left those errors without
180
- // `received` or a suggestion on the text path while validate() had both.
181
- if (!raw[0] || !('received' in raw[0])) {
182
- // Declaration order, as validate() applies it; the text path used to
183
- // enrich in emission order.
184
- const ordered = needsOrdering(raw) ? sortErrorsBySchemaOrder(self._schemaObj, raw) : raw;
185
- // The position map waits for the first read of a frame (lib/enrich-error
186
- // defineLazyFrames); until then reading the list costs the enrichment alone.
187
- const lazyPositions = lazyPositionsFor(self, jsonStr, ordered);
188
- const enrichOpts = {
189
- data: parsedData,
190
- positions: null,
191
- lazyPositions,
192
- schemaPositions: self._schemaPositions,
193
- schemaFile: self._source ? self._source.path : undefined,
194
- };
195
- const enriched = ordered.map((e) => enrich(e, enrichOpts));
196
- if (enriched.length > 1) attachRelated(enriched);
197
- attachDiagnosticSource(enriched, {
198
- data: parsedData,
199
- text: jsonStr,
200
- // Read by the diagnostic renderer only, which resolves the same map.
201
- get positions () { return lazyPositions(); },
202
- schema: self._schemaObj,
203
- mutatesInput: self._mutatesInput === true,
204
- });
205
- this._cached = enriched;
206
- return enriched;
207
- }
208
-
209
- // Already enriched, so the frames are usually attached too. Only a gap in
210
- // them is worth another walk of the document: resolving the map to discover
211
- // there was nothing to fill cost a second full walk on every rejection.
212
- if (raw.some((e) => e && !e.dataFrame)) {
213
- const positions = self._pos().targeted(jsonStr, wantedPointersFor(raw.filter((e) => e && !e.dataFrame)));
214
- if (positions) {
215
- for (const e of raw) {
216
- if (e && !e.dataFrame) {
217
- const path = e.path != null ? e.path : (e.instancePath || '');
218
- const p = positions[path];
219
- if (p) e.dataFrame = { byteOffset: p.byteOffset, length: p.length, line: p.line, col: p.col, text: p.text };
220
- }
221
- }
222
- self._posCache.reset();
223
- }
224
- }
225
- if (raw.length > 1) attachRelated(raw);
226
- attachDiagnosticSource(raw, {
227
- data: parsedData,
228
- text: jsonStr,
229
- schema: self._schemaObj,
230
- mutatesInput: self._mutatesInput === true,
231
- });
232
- this._cached = raw;
233
- return raw;
234
- },
235
- });
236
-
237
- // The targeted position map for a list, built on the first call and shared
238
- // by every error's frame accessor (lib/enrich-error defineLazyFrames).
239
- function lazyPositionsFor (self, text, errors) {
240
- let done = false;
241
- let positions = null;
242
- return () => {
243
- if (done) return positions;
244
- done = true;
245
- positions = self._pos().targeted(text, wantedPointersFor(errors));
246
- if (positions) self._posCache.reset();
247
- return positions;
248
- };
249
- }
250
-
251
- // The pointers a set of errors will ask the position map about. lib/enrich-error
252
- // looks up the error's own path, and for an additional or unevaluated property
253
- // the child pointer named in `params`, unescaped, which is the form it asks for.
254
- // The escaped form goes in too: including a pointer that is never read costs
255
- // nothing, and the cache answers anything outside this set from the full map
256
- // rather than reporting no position.
257
- function wantedPointersFor (errors) {
258
- const wanted = new Set();
259
- for (const e of errors) {
260
- if (!e) continue;
261
- const path = e.path != null ? e.path : (e.instancePath || '');
262
- wanted.add(path);
263
- if (e.instancePath != null && e.instancePath !== path) wanted.add(e.instancePath);
264
- const params = e.params;
265
- const named = params && (params.additionalProperty || params.unevaluatedProperty);
266
- if (typeof named === 'string') {
267
- wanted.add(path + '/' + named);
268
- if (named.indexOf('~') !== -1 || named.indexOf('/') !== -1) {
269
- wanted.add(path + '/' + named.replace(/~/g, '~0').replace(/\//g, '~1'));
270
- }
271
- }
272
- }
273
- return wanted;
274
- }
275
-
276
137
  // A raw error in the legacy shape. The error generator writes `code` and
277
138
  // `docUrl` into its literals because a standalone module returns them as they
278
139
  // are; the runtime's legacy shape never had them, and the interpreter leaves
@@ -411,4 +272,4 @@ function attachRelated (errors) {
411
272
  return errors;
412
273
  }
413
274
 
414
- module.exports = { LazyRejection, RichRejection, presentErrors, needsOrdering, LazyJsonRejection, _enrichLazy, attachRelated, sortErrorsBySchemaOrder, stripOrdinal, wantedPointersFor };
275
+ module.exports = { LazyRejection, RichRejection, presentErrors, needsOrdering, _enrichLazy, attachRelated, sortErrorsBySchemaOrder, stripOrdinal, _setPositionsFor };
@@ -24,7 +24,7 @@ const TIER0_PRIMITIVE_ALLOWED = new Set([
24
24
  'type', 'enum', 'const',
25
25
  'minLength', 'maxLength',
26
26
  'minimum', 'maximum', 'exclusiveMinimum', 'exclusiveMaximum',
27
- 'multipleOf',
27
+ 'multipleOf', 'format',
28
28
  ...META_KEYS,
29
29
  ]);
30
30
 
@@ -54,6 +54,11 @@ function isTier0Primitive(schema) {
54
54
  }
55
55
  }
56
56
  if (schema.const !== undefined && !isPrimitiveEnumValue(schema.const)) return false;
57
+ // `format` on a string, by name: tier 0 checks it with the table the
58
+ // interpreted engine reads (formats.formatChecks) or the caller's own
59
+ // function. A format on another type is a compat check for numbers, which
60
+ // the full engines handle.
61
+ if (schema.format !== undefined && (schema.type !== 'string' || typeof schema.format !== 'string')) return false;
57
62
  return true;
58
63
  }
59
64
 
@@ -117,7 +117,7 @@ function standardSchemaFor(self, getSchema, validate) {
117
117
  if (!message) {
118
118
  // The native engine reports numeric codes without a message; the
119
119
  // enrich pass knows how to word those. Rare, so required lazily.
120
- message = require('./enrich-error').enrich(err, {}).message;
120
+ message = require('./native-message')(err);
121
121
  }
122
122
  issues[i] = { message, path: parsePointerPath(path) };
123
123
  }
package/lib/tier0.js CHANGED
@@ -49,7 +49,17 @@ const _hop = Object.prototype.hasOwnProperty;
49
49
  // Deno removes the __proto__ accessor; read the prototype another way there.
50
50
  const _PN = typeof Bun === 'undefined' && ({}).__proto__ === Object.prototype; // JavaScriptCore goes through Object.getPrototypeOf, see js-compiler's _PN
51
51
 
52
- function primConstraint(key, propSchema) {
52
+ // The check for a string's `format`: the caller's own function by that name,
53
+ // else the built-in one, else none (an unknown format is an annotation), as
54
+ // the interpreted engine picks it.
55
+ function formatFnOf(propSchema, userFormats) {
56
+ const f = propSchema.format;
57
+ if (typeof f !== 'string') return null;
58
+ if (userFormats && typeof userFormats[f] === 'function') return userFormats[f];
59
+ return require('./formats').formatChecks()[f] || null;
60
+ }
61
+
62
+ function primConstraint(key, propSchema, userFormats) {
53
63
  const t = propSchema.type;
54
64
  const hasEnum = Array.isArray(propSchema.enum);
55
65
  const hasConst = propSchema.const !== undefined;
@@ -75,14 +85,15 @@ function primConstraint(key, propSchema) {
75
85
  exclMin: typeof propSchema.exclusiveMinimum === 'number' ? propSchema.exclusiveMinimum : 0,
76
86
  exclMax: typeof propSchema.exclusiveMaximum === 'number' ? propSchema.exclusiveMaximum : 0,
77
87
  multipleOf: typeof propSchema.multipleOf === 'number' ? propSchema.multipleOf : 0,
88
+ formatFn: propSchema.type === 'string' ? formatFnOf(propSchema, userFormats) : null,
78
89
  };
79
90
  }
80
91
 
81
- function buildTier0Plan(schema) {
92
+ function buildTier0Plan(schema, userFormats) {
82
93
  if (schema.type !== 'object') {
83
94
  return {
84
95
  isPrimitive: true,
85
- constraints: [primConstraint('__root__', schema)],
96
+ constraints: [primConstraint('__root__', schema, userFormats)],
86
97
  requiredMask: 0,
87
98
  additionalAllowed: true,
88
99
  knownKeys: null,
@@ -96,7 +107,7 @@ function buildTier0Plan(schema) {
96
107
  let requiredMask = 0;
97
108
  for (let i = 0; i < keys.length; i++) {
98
109
  const k = keys[i];
99
- constraints[i] = primConstraint(k, props[k]);
110
+ constraints[i] = primConstraint(k, props[k], userFormats);
100
111
  knownKeys.add(k);
101
112
  if (required && required.has(k)) requiredMask |= (1 << i);
102
113
  }
@@ -128,6 +139,7 @@ function checkPrimitive(c, v) {
128
139
  const maxLen = c.maxLen;
129
140
  if (minLen >= 0) { const l = v.length; if (l < minLen) return false; if (l < minLen * 2 && codePointLength(v) < minLen) return false; }
130
141
  if (maxLen >= 0) { const l = v.length; if (l > maxLen && codePointLength(v) > maxLen) return false; }
142
+ if (c.formatFn !== null && !c.formatFn(v)) return false;
131
143
  } else if (m === T_INTEGER) {
132
144
  if (typeof v !== 'number' || !Number.isInteger(v)) return false;
133
145
  const f = c.numFlags;
@@ -186,6 +198,7 @@ function tier0ValidateObject(plan, data) {
186
198
  const maxLen = c.maxLen;
187
199
  if (minLen >= 0) { const l = v.length; if (l < minLen) return false; if (l < minLen * 2 && codePointLength(v) < minLen) return false; }
188
200
  if (maxLen >= 0) { const l = v.length; if (l > maxLen && codePointLength(v) > maxLen) return false; }
201
+ if (c.formatFn !== null && !c.formatFn(v)) return false;
189
202
  } else if (m === T_INTEGER) {
190
203
  if (typeof v !== 'number' || !Number.isInteger(v)) return false;
191
204
  const f = c.numFlags;
@@ -19,10 +19,17 @@ const { normalizeKeywords, schemaUsesKeywords } = require('./keywords');
19
19
  // bundle built from lite.js never contains it. Until something registers, the
20
20
  // core behaves exactly as it does where `new Function` is blocked: every
21
21
  // schema runs on the interpreted engine, which passes the same test suite.
22
- // Whether this process runs code compiled ahead of time, where generated
23
- // code falls to an interpreter: Bun's flag for its compiler, or the
24
- // environment saying so (an executable built before the flag existed).
22
+ // Whether this process runs code compiled ahead of time, where code made at
23
+ // run time with `new Function` falls to an interpreter and ata's interpreted
24
+ // engine, compiled with the program, is six times faster (481 against 78 ns
25
+ // a document under bun build --compile --aot). Bun resolves the package's
26
+ // `"aot"` export condition there, which loads index.aot.js and marks the
27
+ // process (_markAotHost); `Bun.isAOT` was the earlier signal, and
28
+ // ATA_ENGINE=interpreter says so from outside.
29
+ let _aotEntry = false;
30
+ function _markAotHost() { _aotEntry = true; }
25
31
  function aotHost() {
32
+ if (_aotEntry) return true;
26
33
  if (env('ATA_ENGINE') === 'interpreter') return true;
27
34
  const b = typeof Bun !== 'undefined' ? Bun : undefined;
28
35
  return !!(b && b.isAOT === true);
@@ -353,7 +360,14 @@ function resolveSchemaByPath(rootSchema, schemaPath) {
353
360
  // by every page that loads the package, validating or not.
354
361
  let LazyRejection, RichRejection, LazyJsonRejection, _enrichLazy, presentErrors, needsOrdering, sortErrorsBySchemaOrder, stripOrdinal;
355
362
  function _loadRejections() {
356
- ({ LazyRejection, RichRejection, LazyJsonRejection, _enrichLazy, presentErrors, needsOrdering, sortErrorsBySchemaOrder, stripOrdinal } = require('./rejections'));
363
+ ({ LazyRejection, RichRejection, _enrichLazy, presentErrors, needsOrdering, sortErrorsBySchemaOrder, stripOrdinal } = require('./rejections'));
364
+ }
365
+ // The rejection validateJSON returns, and the position map an error's frame
366
+ // reads, loaded with the first rejection that carries JSON text: a process
367
+ // that only ever calls validate() never parses them (lib/json-rejections.js,
368
+ // lib/diagnostic-source.js).
369
+ function _loadJsonRejections() {
370
+ if (LazyJsonRejection === undefined) ({ LazyJsonRejection } = require('./json-rejections'));
357
371
  }
358
372
 
359
373
  // Text that does not parse, reported the same way on every path and platform:
@@ -1206,7 +1220,13 @@ class Validator {
1206
1220
  // once per call: deciding first and walking again for the errors would
1207
1221
  // run the keyword twice, the second time on what the first left.
1208
1222
  const once = interp.dataContext === true;
1209
- if (!preprocess && !once) this._fastVerdict = (d) => interp.isValid(d);
1223
+ // A schema tier 0 takes answers its verdict there, with or without a
1224
+ // call to validate() first: the shared walk over a flat plan beats the
1225
+ // closure tree, 76 to 60 ns on Node and 85 to 49 under Bun's compiler,
1226
+ // where the closures are not typed. Not for custom keywords, which
1227
+ // tier 0 does not know.
1228
+ const t0 = !preprocess && !once && !this._usesKeywords ? _tier0Verdict(this) : null;
1229
+ if (!preprocess && !once) this._fastVerdict = t0 !== null ? t0 : (d) => interp.isValid(d);
1210
1230
  if (preprocess && !once) _preVerdict = (d) => { preprocess(d); return interp.isValid(d); };
1211
1231
  // abortEarly is a documented contract, not a property of whichever engine
1212
1232
  // answered: it promises the frozen ATA9000 stub instead of a detailed
@@ -1284,6 +1304,7 @@ class Validator {
1284
1304
  // literal with a getter here cost a closure plus an accessor
1285
1305
  // definition on every rejection, several hundred nanoseconds
1286
1306
  // before any error was read.
1307
+ if (rawInput != null) _loadJsonRejections();
1287
1308
  return new RichRejection(result, data, rawInput, self, root, enrich);
1288
1309
  }
1289
1310
  return result;
@@ -1320,6 +1341,7 @@ class Validator {
1320
1341
  // and on the codegen path that realizes the inner lazy layer, which is
1321
1342
  // the document walk this exists to avoid.
1322
1343
  if (result && result.valid === false) {
1344
+ _loadJsonRejections();
1323
1345
  return new LazyJsonRejection(result, jsonStr, this, enrich, parsed);
1324
1346
  }
1325
1347
  return result;
@@ -2323,6 +2345,22 @@ for (const [name, pick] of [
2323
2345
 
2324
2346
  // Cold start: an interpreted twin answers a large schema's first calls where
2325
2347
  // the code generator is registered (see coldTwin in index.js). Lite has none.
2348
+ // Whether this validator, in the browser build before the generator has
2349
+ // come, counts its calls so a busy one moves to generated code (lib/tiering.js).
2350
+ function _ticks(self) {
2351
+ return _codegen === null && _tiering !== null && !self._interpretOnly && !env('ATA_FORCE_NAPI');
2352
+ }
2353
+ // The tier-0 verdict for a schema it takes, or null: counting calls where
2354
+ // _ticks says so.
2355
+ function _tier0Verdict(self) {
2356
+ if (require('./shape-classifier').classify(self._schemaObj).tier !== 0) return null;
2357
+ const { buildTier0Plan, tier0Validate } = require('./tier0');
2358
+ const plan = buildTier0Plan(self._schemaObj, self._userFormats);
2359
+ if (!_ticks(self)) return (d) => tier0Validate(plan, d);
2360
+ const tick = _tiering.counter(self);
2361
+ return (d) => { tick(); return tier0Validate(plan, d); };
2362
+ }
2363
+
2326
2364
  Validator.prototype._coldTwin = function () {
2327
2365
  const cg = _loadCodegen();
2328
2366
  return cg === null || cg.coldTwin === undefined ? null : cg.coldTwin(this, Validator);
@@ -2354,21 +2392,13 @@ _defineLazyMethod('isValidObject', (self) => {
2354
2392
  return self.isValidObject(data);
2355
2393
  }
2356
2394
  // Lazy: classify + build tier 0 plan on first call, not in constructor.
2357
- const _tier = require("./shape-classifier").classify(self._schemaObj);
2358
- if (_tier.tier === 0) {
2359
- // Bound once here, so the verdict below reads a local, not the module.
2360
- const { buildTier0Plan, tier0Validate } = require("./tier0");
2361
- const _plan = buildTier0Plan(self._schemaObj);
2395
+ const _t0 = _tier0Verdict(self);
2396
+ if (_t0 !== null) {
2397
+ // In the browser build the verdict counts its calls (see _tier0Verdict);
2398
+ // elsewhere the second call builds the generated code behind it.
2362
2399
  let _n = 0;
2363
- // In the browser build the generator is not there yet: count the calls
2364
- // like the interpreted engine does, or a schema checked only through
2365
- // this method stays on the plan walk, four times slower, for good.
2366
- const _tick = _codegen === null && _tiering !== null && !self._interpretOnly && !env('ATA_FORCE_NAPI') ? _tiering.counter(self) : null;
2367
- _bindVerdict(self, _tick !== null ? (d) => {
2368
- _tick();
2369
- return tier0Validate(_plan, d);
2370
- } : (d) => {
2371
- const r = tier0Validate(_plan, d);
2400
+ _bindVerdict(self, _ticks(self) ? _t0 : (d) => {
2401
+ const r = _t0(d);
2372
2402
  if (++_n === 2) {
2373
2403
  try { self._ensureCodegen(); } catch {}
2374
2404
  }
@@ -2441,6 +2471,7 @@ Object.defineProperty(module.exports, '_registerCodegen', { value: _registerCode
2441
2471
  Object.defineProperty(module.exports, '_setCodegenLoader', { value: (load) => { _loadCodegen = load; }, enumerable: false });
2442
2472
  Object.defineProperty(module.exports, '_registerTiering', { value: _registerTiering, enumerable: false });
2443
2473
  Object.defineProperty(module.exports, '_registerBuiltHook', { value: _registerBuiltHook, enumerable: false });
2474
+ Object.defineProperty(module.exports, '_markAotHost', { value: _markAotHost, enumerable: false });
2444
2475
  Object.defineProperty(module.exports, '_codegenRegistered', { value: () => _codegen !== null, enumerable: false });
2445
2476
  Object.defineProperty(module.exports, '_tiering', { get: () => _tiering, enumerable: false });
2446
2477
  Object.defineProperty(module.exports, '_registerAot', { value: _registerAot, enumerable: false });
package/lib/verify.js CHANGED
@@ -195,8 +195,72 @@ function mutate (doc, r) {
195
195
  }
196
196
  }
197
197
 
198
- // Documents for `schema`: `count` made, half of them with one rule broken.
199
- function generate (schema, count, seed) {
198
+ // The values on either side of each bound the schema states, for the value
199
+ // at one place: a module that wrote a bound off by one (`< 12` for a minimum
200
+ // of 13) answers every random document as the runtime does, and differs only
201
+ // here. A bound is where a hand edit, a stale module or a generator bug shows.
202
+ function boundaryValues (schema, type) {
203
+ const out = []
204
+ const int = type === 'integer'
205
+ const step = int ? 1 : 0.5
206
+ const num = (b) => typeof b === 'number' && Number.isFinite(b)
207
+ if (type === 'integer' || type === 'number') {
208
+ if (num(schema.minimum)) out.push(schema.minimum, schema.minimum - step)
209
+ if (num(schema.maximum)) out.push(schema.maximum, schema.maximum + step)
210
+ if (num(schema.exclusiveMinimum)) out.push(schema.exclusiveMinimum, schema.exclusiveMinimum + step)
211
+ if (num(schema.exclusiveMaximum)) out.push(schema.exclusiveMaximum, schema.exclusiveMaximum - step)
212
+ if (num(schema.multipleOf) && schema.multipleOf > 0) out.push(schema.multipleOf, schema.multipleOf * 1.5)
213
+ }
214
+ if (type === 'string') {
215
+ const str = (n) => 'abcdefghij'.repeat(Math.ceil(n / 10) + 1).slice(0, n)
216
+ if (num(schema.minLength)) { out.push(str(schema.minLength)); if (schema.minLength > 0) out.push(str(schema.minLength - 1)) }
217
+ if (num(schema.maxLength) && schema.maxLength < 4096) out.push(str(schema.maxLength), str(schema.maxLength + 1))
218
+ }
219
+ return out
220
+ }
221
+
222
+ // Documents that differ from a valid one at a single place, by a value on
223
+ // either side of a bound stated there: properties, nested objects, the first
224
+ // element of an array, through local references. At most `limit` of them.
225
+ function boundaryDocs (schema, r, defs, limit, accepts) {
226
+ // A base the runtime accepts, where the caller can say, so that a bound
227
+ // read wrong changes the verdict and not only the error list.
228
+ let base = makeValid(schema, r, defs, 0)
229
+ if (accepts) for (let i = 0; i < 50 && base !== undefined && !accepts(base); i++) base = makeValid(schema, r, defs, 0)
230
+ if (base === undefined) return []
231
+ const out = []
232
+ const visit = (sub, path, depth) => {
233
+ if (out.length >= limit || depth > 8 || !sub || typeof sub !== 'object') return
234
+ if (typeof sub.$ref === 'string') { const t = resolveLocal(sub.$ref, defs); if (t !== undefined) visit(t, path, depth + 1); return }
235
+ if (Array.isArray(sub.allOf)) { visit(mergeAllOf(sub), path, depth + 1); return }
236
+ const types = Array.isArray(sub.type) ? sub.type : sub.type !== undefined ? [sub.type] : []
237
+ for (const t of types.length ? types : ['integer', 'number', 'string']) {
238
+ for (const val of boundaryValues(sub, t)) {
239
+ if (out.length >= limit) return
240
+ const d = clone(base)
241
+ if (path.length === 0) { out.push(val); continue }
242
+ let o = d
243
+ for (let i = 0; i < path.length - 1; i++) {
244
+ if (o[path[i]] === undefined || o[path[i]] === null || typeof o[path[i]] !== 'object') o[path[i]] = typeof path[i + 1] === 'number' ? [] : {}
245
+ o = o[path[i]]
246
+ }
247
+ o[path[path.length - 1]] = val
248
+ out.push(d)
249
+ }
250
+ }
251
+ if (sub.properties && typeof sub.properties === 'object') {
252
+ for (const k of Object.keys(sub.properties)) if (k !== '__proto__') visit(sub.properties[k], [...path, k], depth + 1)
253
+ }
254
+ const item = Array.isArray(sub.prefixItems) ? sub.prefixItems[0] : sub.items && typeof sub.items === 'object' && !Array.isArray(sub.items) ? sub.items : null
255
+ if (item) visit(item, [...path, 0], depth + 1)
256
+ }
257
+ visit(schema, [], 0)
258
+ return out
259
+ }
260
+
261
+ // Documents for `schema`: `count` made, half of them with one rule broken,
262
+ // then the documents on either side of each bound (up to `count` more).
263
+ function generate (schema, count, seed, accepts) {
200
264
  const r = rng(seed)
201
265
  const defs = { $defs: schema && schema.$defs, definitions: schema && schema.definitions }
202
266
  const docs = []
@@ -205,6 +269,7 @@ function generate (schema, count, seed) {
205
269
  if (i % 2 === 1 && d !== undefined) d = mutate(clone(d), r)
206
270
  docs.push(d)
207
271
  }
272
+ for (const d of boundaryDocs(schema, r, defs, count, accepts)) docs.push(d)
208
273
  return docs
209
274
  }
210
275
 
@@ -256,7 +321,7 @@ async function verifyOne ({ schemaFile, moduleFile, schema, generate: count = 20
256
321
  if (mod.schemaHash !== undefined && mod.schemaHash !== hash) report.stale = true
257
322
  let runtime
258
323
  try { runtime = new Validator(schema, {}) } catch (e) { report.error = `schema does not compile: ${e.message}`; return report }
259
- const docs = generate(schema, count, seed).map((d, i) => ({ source: `generated #${i + 1}`, doc: d }))
324
+ const docs = generate(schema, count, seed, (d) => { try { return runtime.isValidObject(clone(d)) } catch { return false } }).map((d, i) => ({ source: `generated #${i + 1}`, doc: d }))
260
325
  report.generated = docs.length
261
326
  const sampleDocs = readSamples(samples)
262
327
  report.samples = sampleDocs.length
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.61.0';
10
+ module.exports = '1.61.2';
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "ata-validator",
3
- "version": "1.61.0",
3
+ "version": "1.61.2",
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",
@@ -8,6 +8,11 @@
8
8
  "types": "index.d.ts",
9
9
  "exports": {
10
10
  ".": {
11
+ "aot": {
12
+ "types": "./index.d.ts",
13
+ "import": "./index.aot.mjs",
14
+ "require": "./index.aot.js"
15
+ },
11
16
  "workerd": {
12
17
  "types": "./index.d.ts",
13
18
  "import": "./index.edge.mjs",
@@ -60,6 +65,25 @@
60
65
  "import": "./compiled-verdict.mjs",
61
66
  "require": "./compiled-verdict.js"
62
67
  },
68
+ "./compiled-object": {
69
+ "types": "./compiled-object.d.ts",
70
+ "import": "./compiled-object.mjs",
71
+ "require": "./compiled-object.js"
72
+ },
73
+ "./compiled-plain": {
74
+ "types": "./compiled-plain.d.ts",
75
+ "import": "./compiled-plain.mjs",
76
+ "require": "./compiled-plain.js"
77
+ },
78
+ "./compiled-bare": {
79
+ "types": "./compiled-bare.d.ts",
80
+ "import": "./compiled-bare.mjs",
81
+ "require": "./compiled-bare.js"
82
+ },
83
+ "./compiled-bare-rich": {
84
+ "types": "./compiled-bare-rich.d.ts",
85
+ "default": "./lib/compiled-bare-rich.js"
86
+ },
63
87
  "./lite": {
64
88
  "types": "./lite.d.ts",
65
89
  "import": "./lite.mjs",
@@ -79,7 +103,10 @@
79
103
  },
80
104
  "sideEffects": [
81
105
  "./codegen.js",
82
- "./codegen.mjs"
106
+ "./codegen.mjs",
107
+ "./lib/aot-mark.js",
108
+ "./index.aot.js",
109
+ "./index.aot.mjs"
83
110
  ],
84
111
  "bin": {
85
112
  "ata": "bin/ata.js"
@@ -89,7 +116,12 @@
89
116
  "./lib/aot.js": "./lib/aot.browser.js",
90
117
  "./lib/buffer-gate.js": "./lib/buffer-gate.browser.js",
91
118
  "./lib/interpreter.js": "./lib/interpreter.browser.js",
92
- "./lib/aot-order.js": false
119
+ "./lib/aot-order.js": false,
120
+ "./lib/compiled-defaults.js": "./lib/compiled-defaults.browser.js",
121
+ "./lib/native-message.js": "./lib/native.browser.js",
122
+ "./lib/native-codes.js": "./lib/native.browser.js",
123
+ "./lib/email-literal.js": false,
124
+ "./lib/native-positions.js": "./lib/native.browser.js"
93
125
  },
94
126
  "scripts": {
95
127
  "prepublishOnly": "node tests/test_pack_purity.js && ATA_ALLOW_DOC_PLACEHOLDERS=1 node scripts/check-doc-coverage.js && node tests/test_error_codes_lock.js && node tests/test_safe_regex_embed.js && node tests/test_version_sync.js",
@@ -146,6 +178,8 @@
146
178
  },
147
179
  "files": [
148
180
  "index.js",
181
+ "index.aot.js",
182
+ "index.aot.mjs",
149
183
  "index.mjs",
150
184
  "index.node.mjs",
151
185
  "index.bundle.mjs",
@@ -173,6 +207,16 @@
173
207
  "compiled-verdict.js",
174
208
  "compiled-verdict.mjs",
175
209
  "compiled-verdict.d.ts",
210
+ "compiled-object.js",
211
+ "compiled-object.mjs",
212
+ "compiled-object.d.ts",
213
+ "compiled-plain.js",
214
+ "compiled-plain.mjs",
215
+ "compiled-plain.d.ts",
216
+ "compiled-bare.js",
217
+ "compiled-bare.mjs",
218
+ "compiled-bare.d.ts",
219
+ "compiled-bare-rich.d.ts",
176
220
  "lite.js",
177
221
  "lite.mjs",
178
222
  "lite.d.ts",
@@ -187,13 +231,13 @@
187
231
  "codegen.d.ts"
188
232
  ],
189
233
  "optionalDependencies": {
190
- "@ata-validator/native-darwin-arm64": "1.61.0",
191
- "@ata-validator/native-darwin-x64": "1.61.0",
192
- "@ata-validator/native-linux-arm64-gnu": "1.61.0",
193
- "@ata-validator/native-linux-arm64-musl": "1.61.0",
194
- "@ata-validator/native-linux-x64-gnu": "1.61.0",
195
- "@ata-validator/native-linux-x64-musl": "1.61.0",
196
- "@ata-validator/native-win32-x64": "1.61.0"
234
+ "@ata-validator/native-darwin-arm64": "1.61.2",
235
+ "@ata-validator/native-darwin-x64": "1.61.2",
236
+ "@ata-validator/native-linux-arm64-gnu": "1.61.2",
237
+ "@ata-validator/native-linux-arm64-musl": "1.61.2",
238
+ "@ata-validator/native-linux-x64-gnu": "1.61.2",
239
+ "@ata-validator/native-linux-x64-musl": "1.61.2",
240
+ "@ata-validator/native-win32-x64": "1.61.2"
197
241
  },
198
242
  "peerDependencies": {
199
243
  "yaml": "^2.0.0"