ata-validator 1.58.0 → 1.59.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/README.md CHANGED
@@ -661,9 +661,11 @@ through a `setFormats()` export the module carries. `docs/API.md` has the
661
661
  details.
662
662
 
663
663
  **Fastify startup, 10 route schemas, from a cold process to the first validated request:
664
- ajv 18.3 ms, ata 2.67 ms, no build step required.** ata registers in 0.22 ms of that and
665
- compiles on the first request, so counting only registration would overstate the gap.
666
- Reproduce with `node benchmark/bench_fastify_boot.mjs`.
664
+ ajv 30.3 ms, ata 9.7 ms, no build step required** (Node 25, ajv 8.20.0, loading each
665
+ library included). ata loads in 3.7 ms, registers the routes in 0.3 ms and compiles on the
666
+ first request, so counting only registration would overstate the gap. The first request
667
+ here comes in the same tick as setup; a server that listens first has the code generator
668
+ loaded by then. Reproduce with `node benchmark/bench_fastify_boot.mjs`.
667
669
 
668
670
  ### Standard Schema V1
669
671
 
package/codegen.js CHANGED
@@ -4,7 +4,8 @@
4
4
  // loads it on demand, once a validator gets hot; importing this once at
5
5
  // startup has every schema compiled to generated code from its first call,
6
6
  // and makes parse() and the ahead-of-time bundle methods available in the
7
- // browser. On Node, ata-validator already includes it and this does nothing.
7
+ // browser. On Node, ata-validator loads it when a validator first compiles or
8
+ // once the event loop is free; importing this loads it now.
8
9
  const core = require('./lib/validator-core.js');
9
10
  if (!core._codegenRegistered()) core._registerCodegen(require('./lib/codegen-engine.js'));
10
11
  module.exports = {};
package/index.js CHANGED
@@ -6,7 +6,19 @@
6
6
  // lib/codegen-engine.js, so that the edge entry (index.edge.js) and
7
7
  // ata-validator/lite can load the core without it.
8
8
  const core = require('./lib/validator-core.js');
9
- core._registerCodegen(require('./lib/codegen-engine.js'));
9
+ // The code generator loads on first need, or once the event loop is free,
10
+ // not with the package: it is most of what requiring the package parsed. The
11
+ // timer is unref'd, so a script that has finished does not stay to load it.
12
+ // The loader lives here rather than in the core, which the browser build
13
+ // shares and which has no use for it there.
14
+ let engine = null;
15
+ const loadCodegen = () => {
16
+ if (engine === null) core._registerCodegen(engine = require('./lib/codegen-engine.js'));
17
+ return engine;
18
+ };
19
+ core._setCodegenLoader(loadCodegen);
20
+ const warm = setTimeout(loadCodegen, 0);
21
+ if (typeof warm === 'object' && typeof warm.unref === 'function') warm.unref();
10
22
 
11
23
  // The public names. lib/public-api.js holds the same for index.edge.js; they
12
24
  // are written out here rather than required so that loading the package in
package/lib/aot-impl.js CHANGED
@@ -371,6 +371,30 @@ function defaultsDecl(validator) {
371
371
  `let _ataFill;\nfunction applyDefaults(d) { (_ataFill || (_ataFill = _ataDefaults(JSON.parse(${text}))))(d); }\n`;
372
372
  }
373
373
 
374
+ // What a bundled validator runs on the data before it checks it, for the
375
+ // options the caller of bundleStandalone named: the runtime's pass for
376
+ // `coerceTypes`, `removeAdditional` and `useDefaults: true`, as one generated
377
+ // function (preprocessSource), in the runtime's order. A bundle used to leave
378
+ // coerceTypes and removeAdditional out without a word, and then rejected what
379
+ // the runtime accepts. Where the generated pass declines and the runtime
380
+ // would change the data with its closure pass, there is nothing to carry it
381
+ // in, and the bundle refuses. `useDefaults` alone runs applyDefaults.
382
+ // Returns { decl, call }.
383
+ function bundlePreprocess(validator, opts, fillDecl, label) {
384
+ const coerceTypes = opts && opts.coerceTypes ? opts.coerceTypes : false;
385
+ const removeAdditional = opts && opts.removeAdditional ? opts.removeAdditional : false;
386
+ const useDefaults = !!(opts && opts.useDefaults === true);
387
+ if (!coerceTypes && !removeAdditional) return { decl: '', call: useDefaults && fillDecl ? 'applyDefaults(d);' : '' };
388
+ const internals = require('./validator-core')._internals;
389
+ const schema = internals.resolveSchemaForPreprocess(validator._schemaObj, validator._schemaMap);
390
+ let src = null;
391
+ try { src = require('./codegen-engine').preprocessSource(schema, { coerceTypes, removeAdditional, useDefaults }); } catch { src = null; }
392
+ if (src !== null) return { decl: `function _ataPre(d) {\n${src}\n}\n`, call: '_ataPre(d);' };
393
+ const changes = (coerceTypes && internals.buildCoercer(schema) !== null) || (removeAdditional && internals.buildRemover(schema) !== null);
394
+ if (changes) throw new Error(`${label}: ${coerceTypes ? 'coerceTypes' : 'removeAdditional'} cannot be compiled into a standalone module for this schema; validate with the runtime Validator`);
395
+ return { decl: '', call: useDefaults && fillDecl ? 'applyDefaults(d);' : '' };
396
+ }
397
+
374
398
  function toStandaloneModule(validator, opts) {
375
399
  assertEmittable(validator, 'toStandaloneModule');
376
400
  validator._ensureCompiled();
@@ -912,6 +936,12 @@ function bundle(Validator, schemas, opts) {
912
936
  // Zero-dependency self-contained bundle — no require('ata-validator') needed at runtime.
913
937
  // opts.format: 'cjs' (default) or 'esm'.
914
938
  // opts.formats: { name: fn } — embedded in the output via Function#toString.
939
+ // opts.order: true hands each validator's errors out in the runtime's order,
940
+ // as toStandaloneModule does (lib/aot-order.js).
941
+ // Each validator of a schema with a default carries `applyDefaults(data)`, the
942
+ // runtime's defaults pass, as a property; validate does not run it unless
943
+ // `useDefaults: true` is passed. `coerceTypes` and `removeAdditional` run
944
+ // before the check, as at runtime (bundlePreprocess).
915
945
  function bundleStandalone(Validator, schemas, opts) {
916
946
  // A custom keyword is a function, and a standalone module imports nothing,
917
947
  // so there is no way to carry it. Refuse rather than emit a module that
@@ -930,11 +960,14 @@ function bundleStandalone(Validator, schemas, opts) {
930
960
  // Pass one compiles; pass two emits. The helpers that several schemas hoist
931
961
  // are the same text declaring the same name, so the bundle keeps one copy at
932
962
  // module scope, and pass two needs to know the set before it writes anything.
963
+ const ordered = !!(opts && opts.order === true);
964
+ const validatorOpts = ordered ? Object.fromEntries(Object.entries(bundleOpts).filter(([k]) => k !== 'order')) : bundleOpts;
933
965
  const compiledEntries = schemas.map((schema) => {
934
- const v = new Validator(schema, bundleOpts);
966
+ const v = new Validator(schema, validatorOpts);
935
967
  v._ensureCompiled();
936
968
  const jsFn = v._jsFn;
937
969
  if (!jsFn || !jsFn._hybridSource) return null;
970
+ const noOrdinal = !ordered || !require(AOT_ORDER_MODULE).wantsOrdinals(v._schemaObj);
938
971
  // The schema the validator compiled, not the one the caller passed. They
939
972
  // differ whenever anything prepared the schema: draft-07 normalization,
940
973
  // `format` removed under assertFormat: false, keywords outside the
@@ -944,7 +977,7 @@ function bundleStandalone(Validator, schemas, opts) {
944
977
  v._schemaObj,
945
978
  v._schemaMap,
946
979
  v._userFormats,
947
- { noOrdinal: true },
980
+ { noOrdinal },
948
981
  );
949
982
  if (jsFn._usesSafeRe || (jsErrFn && jsErrFn._usesSafeRe)) bundleUsesSafeRe = true;
950
983
  return { v, jsFn, jsErrFn };
@@ -990,12 +1023,25 @@ function bundleStandalone(Validator, schemas, opts) {
990
1023
  if (preambleSrc) preamble = preamble ? `${preamble}\n${preambleSrc}` : preambleSrc;
991
1024
  const closureSrc = closureDeclLines(jsFn, 'var').join('\n');
992
1025
  if (closureSrc) preamble = preamble ? `${preamble}\n${closureSrc}\n` : closureSrc + '\n';
1026
+ // The runtime's passes over the data, for the options asked for, and its
1027
+ // defaults pass as the validator's applyDefaults.
1028
+ const fillDecl = defaultsDecl(v);
1029
+ const pre = bundlePreprocess(v, opts, fillDecl, 'bundleStandalone');
1030
+ preamble = preamble ? `${preamble}\n${fillDecl}${pre.decl}` : fillDecl + pre.decl;
1031
+ const ret = (body) => fillDecl
1032
+ ? `var _f=function(d){${pre.call}${body}};_f.applyDefaults=applyDefaults;return _f`
1033
+ : `return function(d){${pre.call}${body}}`;
1034
+ // In order: the collector's errors sorted as the runtime sorts them.
1035
+ const hasErr = !!(jsErrFn && jsErrFn._errSource);
1036
+ const errDecl = ordered && hasErr
1037
+ ? `${require(AOT_ORDER_MODULE).orderDecl(v._schemaObj)}var E=function(d){var r=(function(d){var _all=true;${errBody}})(d);return r&&r.errors?{valid:false,errors:_ataOrder(r.errors)}:r};`
1038
+ : `var E=function(d){var _all=true;${errBody}};`;
993
1039
  if (opts && opts.verbose) {
994
1040
  // Embed the schema and a small resolver so errors carry parentSchema.
995
1041
  const schemaLit = JSON.stringify(v._schemaObj);
996
- return `(function(R){${preamble}var _S=${schemaLit};function _PS(p){if(!p||p[0]!=='#')return undefined;var s=p.slice(1);if(!s)return _S;var ps=s.split('/').filter(Boolean).map(function(x){return x.replace(/~1/g,'/').replace(/~0/g,'~')});var t=_S;for(var i=0;i<ps.length-1;i++){if(t==null||typeof t!=='object')return undefined;t=t[ps[i]]}return t}var E=function(d){var _all=true;${errBody}};var _v=function(d){${jsFn._hybridSource}};return function(d){var r=_v(d);if(r&&r.valid===false&&r.errors){var es=[];for(var i=0;i<r.errors.length;i++){var e=r.errors[i];es.push(Object.assign({},e,{parentSchema:_PS(e.schemaPath)}))}return{valid:false,errors:es}}return r}})(R)`;
1042
+ return `(function(R){${preamble}var _S=${schemaLit};function _PS(p){if(!p||p[0]!=='#')return undefined;var s=p.slice(1);if(!s)return _S;var ps=s.split('/').filter(Boolean).map(function(x){return x.replace(/~1/g,'/').replace(/~0/g,'~')});var t=_S;for(var i=0;i<ps.length-1;i++){if(t==null||typeof t!=='object')return undefined;t=t[ps[i]]}return t}${errDecl}var _v=function(d){${jsFn._hybridSource}};${ret('var r=_v(d);if(r&&r.valid===false&&r.errors){var es=[];for(var i=0;i<r.errors.length;i++){var e=r.errors[i];es.push(Object.assign({},e,{parentSchema:_PS(e.schemaPath)}))}return{valid:false,errors:es}}return r')}})(R)`;
997
1043
  }
998
- return `(function(R){${preamble}var E=function(d){var _all=true;${errBody}};return function(d){${jsFn._hybridSource}}})(R)`;
1044
+ return `(function(R){${preamble}${errDecl}${ret(jsFn._hybridSource)}})(R)`;
999
1045
  });
1000
1046
  const arr = `[${fns.join(',')}]`;
1001
1047
  // The code-point counter the length checks call. toStandaloneModule embeds
@@ -25,6 +25,9 @@ function _registerCodegen(engine) {
25
25
  _codegen = engine;
26
26
  ({ compileToJS, compileToJSCodegen, compileToJSCodegenWithErrors, compileToJSCombined } = engine.jsCompiler);
27
27
  }
28
+ // The code generator, loading it first where the entry loads it on need
29
+ // (index.js); everywhere else, whatever is registered.
30
+ let _loadCodegen = () => _codegen;
28
31
  // The ahead-of-time loaders without the code generator: the edge entry
29
32
  // (index.edge.js) registers lib/aot.browser.js here, so modules written by
30
33
  // `ata build` or Validator.bundle() load where generated code cannot run.
@@ -219,7 +222,7 @@ function collectRemovals(schema, actions, path) {
219
222
  // change within a realm and the probe itself is a code generation attempt.
220
223
  let _codegenAvailable = null;
221
224
  function codegenAvailable() {
222
- if (_codegen === null) return false;
225
+ if (_loadCodegen() === null) return false;
223
226
  if (_codegenAvailable === null) {
224
227
  try {
225
228
  _codegenAvailable = compileFunction('return 1')() === 1;
@@ -951,7 +954,7 @@ class Validator {
951
954
  }
952
955
 
953
956
  _pos() {
954
- return this._posCache || (this._posCache = require('./data-position-cache').createCache(_codegen !== null && _codegen.nativePositions ? _codegen.nativePositions() : null));
957
+ return this._posCache || (this._posCache = require('./data-position-cache').createCache(_loadCodegen() !== null && _codegen.nativePositions ? _codegen.nativePositions() : null));
955
958
  }
956
959
 
957
960
  _ensureVocabularies() {
@@ -1102,7 +1105,7 @@ class Validator {
1102
1105
  // pass looks for one. One reached through a reference into another
1103
1106
  // document is walked, since its defaults are not in this text.
1104
1107
  const mayHaveDefaults = preprocessSchema !== schemaObj || this._schemaStr.includes('"default"');
1105
- if (!this._interpretOnly && _codegen !== null) {
1108
+ if (!this._interpretOnly && _loadCodegen() !== null) {
1106
1109
  const cacheable = !this._userFormats && !this._usesKeywords;
1107
1110
  const ppKey = cacheable ? mapKey + '\u0000' + String(options.coerceTypes) + '|' + (options.useDefaults !== false) + '|' + String(options.removeAdditional) : null;
1108
1111
  const hit = ppKey !== null ? _preprocessCache.get(ppKey) : undefined;
@@ -1726,7 +1729,7 @@ class Validator {
1726
1729
  return;
1727
1730
  }
1728
1731
  this._ensureVocabularies();
1729
- if (this._interpretOnly || _codegen === null || env('ATA_FORCE_NAPI')) return;
1732
+ if (this._interpretOnly || env('ATA_FORCE_NAPI') || _loadCodegen() === null) return;
1730
1733
  _codegen.compileVerdict.call(this);
1731
1734
  }
1732
1735
 
@@ -1736,7 +1739,7 @@ class Validator {
1736
1739
  static fromStandalone(mod, schema, opts) {
1737
1740
  // It loads what Validator.bundleStandalone() writes, and compiles an error
1738
1741
  // function when the module carries none, so it goes with those methods.
1739
- if (_codegen === null && _aotLoader === null && _tiering === null) throw new TypeError('Validator.fromStandalone is not part of ata-validator/lite, like the bundle methods it loads for. Import it from ata-validator, or import a module from `ata build` directly.');
1742
+ if (_loadCodegen() === null && _aotLoader === null && _tiering === null) throw new TypeError('Validator.fromStandalone is not part of ata-validator/lite, like the bundle methods it loads for. Import it from ata-validator, or import a module from `ata build` directly.');
1740
1743
  const options = opts || {};
1741
1744
  const schemaObj = typeof schema === "string" ? JSON.parse(schema) : schema;
1742
1745
 
@@ -1922,7 +1925,7 @@ function version() {
1922
1925
  // generated module shapes; browser bundles get `lib/aot.browser.js` (a stub
1923
1926
  // that throws) via the package.json `browser` field.
1924
1927
  function _aot() {
1925
- if (_codegen !== null) return _codegen.aot();
1928
+ if (_loadCodegen() !== null) return _codegen.aot();
1926
1929
  if (_aotLoader !== null) return _aotLoader();
1927
1930
  // The browser build: emitters stay out of the page, as they did when the
1928
1931
  // generator was in it (the package.json browser field maps lib/aot.js to
@@ -2346,7 +2349,7 @@ function _buildParse(self) {
2346
2349
  later._untilCodegen = true;
2347
2350
  return later;
2348
2351
  }
2349
- if (_codegen === null) return decline('this entry has no code generator to build the copy with', 'Use validate(); a valid result carries the input as it is.');
2352
+ if (_loadCodegen() === null) return decline('this entry has no code generator to build the copy with', 'Use validate(); a valid result carries the input as it is.');
2350
2353
  return _codegen.buildParse(self, decline, extended);
2351
2354
  }
2352
2355
 
@@ -2417,7 +2420,8 @@ for (const [name, pick] of [
2417
2420
  // Cold start: an interpreted twin answers a large schema's first calls where
2418
2421
  // the code generator is registered (see coldTwin in index.js). Lite has none.
2419
2422
  Validator.prototype._coldTwin = function () {
2420
- return _codegen === null || _codegen.coldTwin === undefined ? null : _codegen.coldTwin(this, Validator);
2423
+ const cg = _loadCodegen();
2424
+ return cg === null || cg.coldTwin === undefined ? null : cg.coldTwin(this, Validator);
2421
2425
  };
2422
2426
 
2423
2427
  _defineLazyMethod('validate', (self) => (data) => {
@@ -2529,9 +2533,10 @@ module.exports = {
2529
2533
  // How an entry registers the code generator. Not enumerable, so the exported
2530
2534
  // surface of `require('ata-validator')` is what it was.
2531
2535
  Object.defineProperty(module.exports, '_registerCodegen', { value: _registerCodegen, enumerable: false });
2536
+ Object.defineProperty(module.exports, '_setCodegenLoader', { value: (load) => { _loadCodegen = load; }, enumerable: false });
2532
2537
  Object.defineProperty(module.exports, '_registerTiering', { value: _registerTiering, enumerable: false });
2533
2538
  Object.defineProperty(module.exports, '_codegenRegistered', { value: () => _codegen !== null, enumerable: false });
2534
2539
  Object.defineProperty(module.exports, '_tiering', { get: () => _tiering, enumerable: false });
2535
2540
  Object.defineProperty(module.exports, '_registerAot', { value: _registerAot, enumerable: false });
2536
2541
  // What the compiled paths in index.js need from this module.
2537
- Object.defineProperty(module.exports, '_internals', { value: { _interpArgs, _normalizeCallerSchemaUncached, resolveSchemaForPreprocess, _jsonSyntaxRejection, ABORT_EARLY_RESULT, HYBRID_TIER_CALLS, SIMDJSON_THRESHOLD, VALID_RESULT, _bindEntry, _bindVerdict, _compileCache, _compileCacheSet, _mustReject, _rememberInstance, _verboseWrap, compileCacheKey, getNative, isV1Dialect, resolveSchemaByPath }, enumerable: false });
2542
+ Object.defineProperty(module.exports, '_internals', { value: { _interpArgs, _normalizeCallerSchemaUncached, resolveSchemaForPreprocess, buildCoercer, buildRemover, _jsonSyntaxRejection, ABORT_EARLY_RESULT, HYBRID_TIER_CALLS, SIMDJSON_THRESHOLD, VALID_RESULT, _bindEntry, _bindVerdict, _compileCache, _compileCacheSet, _mustReject, _rememberInstance, _verboseWrap, compileCacheKey, getNative, isV1Dialect, resolveSchemaByPath }, enumerable: false });
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.58.0';
10
+ module.exports = '1.59.0';
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "ata-validator",
3
- "version": "1.58.0",
3
+ "version": "1.59.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",
@@ -186,13 +186,13 @@
186
186
  "codegen.d.ts"
187
187
  ],
188
188
  "optionalDependencies": {
189
- "@ata-validator/native-darwin-arm64": "1.58.0",
190
- "@ata-validator/native-darwin-x64": "1.58.0",
191
- "@ata-validator/native-linux-arm64-gnu": "1.58.0",
192
- "@ata-validator/native-linux-arm64-musl": "1.58.0",
193
- "@ata-validator/native-linux-x64-gnu": "1.58.0",
194
- "@ata-validator/native-linux-x64-musl": "1.58.0",
195
- "@ata-validator/native-win32-x64": "1.58.0"
189
+ "@ata-validator/native-darwin-arm64": "1.59.0",
190
+ "@ata-validator/native-darwin-x64": "1.59.0",
191
+ "@ata-validator/native-linux-arm64-gnu": "1.59.0",
192
+ "@ata-validator/native-linux-arm64-musl": "1.59.0",
193
+ "@ata-validator/native-linux-x64-gnu": "1.59.0",
194
+ "@ata-validator/native-linux-x64-musl": "1.59.0",
195
+ "@ata-validator/native-win32-x64": "1.59.0"
196
196
  },
197
197
  "peerDependencies": {
198
198
  "yaml": "^2.0.0"