ata-validator 1.58.1 → 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 +5 -3
- package/codegen.js +2 -1
- package/index.js +13 -1
- package/lib/aot-impl.js +50 -4
- package/lib/validator-core.js +14 -9
- package/lib/version.js +1 -1
- package/package.json +8 -8
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
|
|
665
|
-
|
|
666
|
-
|
|
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
|
|
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
|
-
|
|
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,
|
|
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
|
|
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}
|
|
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}
|
|
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
|
package/lib/validator-core.js
CHANGED
|
@@ -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 (
|
|
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(
|
|
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 &&
|
|
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 ||
|
|
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 (
|
|
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 (
|
|
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 (
|
|
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
|
-
|
|
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
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "ata-validator",
|
|
3
|
-
"version": "1.
|
|
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.
|
|
190
|
-
"@ata-validator/native-darwin-x64": "1.
|
|
191
|
-
"@ata-validator/native-linux-arm64-gnu": "1.
|
|
192
|
-
"@ata-validator/native-linux-arm64-musl": "1.
|
|
193
|
-
"@ata-validator/native-linux-x64-gnu": "1.
|
|
194
|
-
"@ata-validator/native-linux-x64-musl": "1.
|
|
195
|
-
"@ata-validator/native-win32-x64": "1.
|
|
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"
|