ata-validator 1.56.0 → 1.58.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 +6 -2
- package/bin/ata.js +11 -2
- package/build.d.ts +7 -1
- package/lib/aot-build.js +6 -1
- package/lib/aot-impl.js +59 -11
- package/lib/aot-order.js +115 -0
- package/lib/buffer-gate.js +33 -0
- package/lib/clone-emit.js +11 -25
- package/lib/codegen-engine.js +159 -19
- package/lib/defaults.js +213 -56
- package/lib/error-codes.js +6 -0
- package/lib/formats-source.js +1 -1
- package/lib/interpreter-core.js +39 -2
- package/lib/interpreter.browser.js +34 -0
- package/lib/schema-strip.js +15 -8
- package/lib/tiering.js +88 -1
- package/lib/ts-gen.js +6 -2
- package/lib/validator-core.js +58 -30
- package/lib/version.js +1 -1
- package/package.json +10 -9
package/lib/interpreter-core.js
CHANGED
|
@@ -13,6 +13,34 @@
|
|
|
13
13
|
let _safeRe = null;
|
|
14
14
|
const compileSafe = (src) => (_safeRe || (_safeRe = require('./safe-regex'))).compileSafe(src);
|
|
15
15
|
|
|
16
|
+
// A pattern with no group or alternation, few bounded choices in all, and at
|
|
17
|
+
// most one unbounded repeat, on one character or class and only when the
|
|
18
|
+
// pattern starts with `^`, runs in linear time on a backtracking RegExp, so it
|
|
19
|
+
// takes the platform's: built in about a microsecond where the linear engine's
|
|
20
|
+
// automaton took 40 to 100, and faster to test. Every pattern accepted here
|
|
21
|
+
// also gets a RegExp from the code generator (lib/regex-linear.js), so both
|
|
22
|
+
// engines answer with the same matcher; tests/test_regex_plain_linear.js holds
|
|
23
|
+
// that and the answers.
|
|
24
|
+
const PLAIN_ATOM = /(?:\\[dDwWsS\\/.^$|?*+()[\]{}-]|\[\^?(?:[^\\\][^\ud800-\udfff]|\\[dws\\/.^$[\]-])(?:[^\\\][\ud800-\udfff]|\\[dws\\/.^$[\]-])*\]|[^\\[\](){}|*+?^$\ud800-\udfff])(\?|[*+]|\{(\d{1,3})(?:,(\d{1,3}))?\})?/y;
|
|
25
|
+
function plainLinear(src) {
|
|
26
|
+
const n = src.length, anchored = src[0] === '^';
|
|
27
|
+
if (n > 256) return false;
|
|
28
|
+
let i = anchored ? 1 : 0, choices = 1, loops = 0;
|
|
29
|
+
while (i < n) {
|
|
30
|
+
if (src[i] === '$' && i === n - 1) return true;
|
|
31
|
+
PLAIN_ATOM.lastIndex = i;
|
|
32
|
+
const m = PLAIN_ATOM.exec(src);
|
|
33
|
+
if (m === null) return false;
|
|
34
|
+
i = PLAIN_ATOM.lastIndex;
|
|
35
|
+
const q = m[1];
|
|
36
|
+
if (q === '?') choices *= 2;
|
|
37
|
+
else if (q === '*' || q === '+') { if (!anchored || ++loops > 1) return false; }
|
|
38
|
+
else if (q !== undefined) { const lo = +m[2], hi = m[3] === undefined ? lo : +m[3]; if (hi < lo) return false; choices *= hi - lo + 1; }
|
|
39
|
+
if (choices > 64) return false;
|
|
40
|
+
}
|
|
41
|
+
return true;
|
|
42
|
+
}
|
|
43
|
+
|
|
16
44
|
const SCHEMA_KEYWORDS = {
|
|
17
45
|
single: ['additionalProperties', 'contains', 'propertyNames', 'if', 'then', 'else', 'not', 'items', 'unevaluatedItems', 'unevaluatedProperties'],
|
|
18
46
|
maps: ['$defs', 'definitions', 'properties', 'patternProperties', 'dependentSchemas'],
|
|
@@ -875,7 +903,10 @@ class Interpreter {
|
|
|
875
903
|
_fastRoot() {
|
|
876
904
|
if (this._fast === undefined) {
|
|
877
905
|
const compile = planCompiler();
|
|
878
|
-
|
|
906
|
+
// Still on its way (the browser build loads it after the page's first
|
|
907
|
+
// checks, lib/interpreter.browser.js): the plain walk answers, and the
|
|
908
|
+
// next call asks again.
|
|
909
|
+
if (compile === null) { if (planPending) return null; this._fast = null; }
|
|
879
910
|
else {
|
|
880
911
|
try { this._fast = compile(this); } catch (e) {
|
|
881
912
|
// The plain walk answers instead, which tests cannot tell apart;
|
|
@@ -912,6 +943,8 @@ class Interpreter {
|
|
|
912
943
|
// Unicode property escapes need a real RegExp in unicode mode; the
|
|
913
944
|
// linear engine does not understand them.
|
|
914
945
|
try { re = new RegExp(src, 'u'); } catch { re = new RegExp(src); }
|
|
946
|
+
} else if (plainLinear(src)) {
|
|
947
|
+
re = new RegExp(src);
|
|
915
948
|
} else {
|
|
916
949
|
try {
|
|
917
950
|
re = compileSafe(src);
|
|
@@ -1500,9 +1533,12 @@ function err(code, keyword, instancePath, schemaPath, params, message) {
|
|
|
1500
1533
|
const TIER = 1000;
|
|
1501
1534
|
let loadPlanCompiler = null;
|
|
1502
1535
|
let compileInterpreter = null;
|
|
1503
|
-
|
|
1536
|
+
// True while a plan compiler is being loaded and none is set yet.
|
|
1537
|
+
let planPending = false;
|
|
1538
|
+
function setPlanCompiler(load, pending) {
|
|
1504
1539
|
loadPlanCompiler = load;
|
|
1505
1540
|
compileInterpreter = null;
|
|
1541
|
+
planPending = load === null && pending === true;
|
|
1506
1542
|
}
|
|
1507
1543
|
function planCompiler() {
|
|
1508
1544
|
if (compileInterpreter === null && loadPlanCompiler !== null) {
|
|
@@ -1534,6 +1570,7 @@ function createInterpreter(schema, options) {
|
|
|
1534
1570
|
}
|
|
1535
1571
|
|
|
1536
1572
|
module.exports = { createInterpreter };
|
|
1573
|
+
Object.defineProperty(module.exports, '_plainLinear', { value: plainLinear, enumerable: false });
|
|
1537
1574
|
Object.defineProperty(module.exports, '_setPlanCompiler', { value: setPlanCompiler, enumerable: false });
|
|
1538
1575
|
Object.defineProperty(module.exports, '_setCompat', { value: setCompat, enumerable: false });
|
|
1539
1576
|
// The resolver, for the code generator: it resolves references at compile
|
|
@@ -0,0 +1,34 @@
|
|
|
1
|
+
'use strict';
|
|
2
|
+
|
|
3
|
+
// The interpreted engine for the browser build (the package.json browser
|
|
4
|
+
// field maps lib/interpreter.js here). The plan compiler, the closure tree
|
|
5
|
+
// that makes the engine fast on a schema checked many times, is not in the
|
|
6
|
+
// page's first load: it comes through the dynamic import below, which
|
|
7
|
+
// bundlers split into a chunk of its own, once the page is idle after its
|
|
8
|
+
// first validator. Until then the plain walk answers, as it does for every
|
|
9
|
+
// schema's first thousand calls (TIER in lib/interpreter-core.js), with the
|
|
10
|
+
// same answers. Without a bundler that splits, the chunk is inlined and
|
|
11
|
+
// nothing changes.
|
|
12
|
+
const core = require('./interpreter-core');
|
|
13
|
+
|
|
14
|
+
core._setPlanCompiler(null, true);
|
|
15
|
+
let asked = false;
|
|
16
|
+
const idle = (f) => {
|
|
17
|
+
if (typeof requestIdleCallback === 'function') return requestIdleCallback(f, { timeout: 2000 });
|
|
18
|
+
const t = setTimeout(f, 200);
|
|
19
|
+
if (t && typeof t.unref === 'function') t.unref();
|
|
20
|
+
};
|
|
21
|
+
function load() {
|
|
22
|
+
import('./plan-compiler.js').then((m) => {
|
|
23
|
+
const mod = m && m.install ? m : m.default;
|
|
24
|
+
core._setPlanCompiler(() => mod.install);
|
|
25
|
+
}, () => core._setPlanCompiler(null));
|
|
26
|
+
}
|
|
27
|
+
|
|
28
|
+
// The first engine built asks for it, when the page is next idle.
|
|
29
|
+
const create = core.createInterpreter;
|
|
30
|
+
core.createInterpreter = (schema, opts) => {
|
|
31
|
+
if (!asked) { asked = true; idle(load); }
|
|
32
|
+
return create(schema, opts);
|
|
33
|
+
};
|
|
34
|
+
module.exports = core;
|
package/lib/schema-strip.js
CHANGED
|
@@ -63,24 +63,24 @@ function refsIntoDropped(node, seen) {
|
|
|
63
63
|
|
|
64
64
|
// keepTitle: the node is a branch of anyOf or oneOf, whose title names the
|
|
65
65
|
// branch in the collapsed error (params.closestName).
|
|
66
|
-
function stripSchema(node, keepTitle) {
|
|
66
|
+
function stripSchema(node, keepTitle, keepDefault) {
|
|
67
67
|
if (node === null || typeof node !== 'object' || Array.isArray(node)) return node;
|
|
68
68
|
const out = {};
|
|
69
69
|
for (const k of Object.keys(node)) {
|
|
70
|
-
if (dropped(k) && !(keepTitle === true && k === 'title')) continue;
|
|
70
|
+
if (dropped(k) && !(keepTitle === true && k === 'title') && !(keepDefault === true && k === 'default')) continue;
|
|
71
71
|
const v = node[k];
|
|
72
72
|
if (SINGLE.has(k)) {
|
|
73
|
-
setOwn(out, k, Array.isArray(v) ? v.map((x) => stripSchema(x, false)) : stripSchema(v, false));
|
|
73
|
+
setOwn(out, k, Array.isArray(v) ? v.map((x) => stripSchema(x, false, keepDefault)) : stripSchema(v, false, keepDefault));
|
|
74
74
|
} else if (MAPS.has(k) && v !== null && typeof v === 'object' && !Array.isArray(v)) {
|
|
75
75
|
const m = {};
|
|
76
|
-
for (const name of Object.keys(v)) setOwn(m, name, stripSchema(v[name], false));
|
|
76
|
+
for (const name of Object.keys(v)) setOwn(m, name, stripSchema(v[name], false, keepDefault));
|
|
77
77
|
setOwn(out, k, m);
|
|
78
78
|
} else if (LISTS.has(k) && Array.isArray(v)) {
|
|
79
79
|
const branches = k === 'anyOf' || k === 'oneOf';
|
|
80
|
-
setOwn(out, k, v.map((x) => stripSchema(x, branches)));
|
|
80
|
+
setOwn(out, k, v.map((x) => stripSchema(x, branches, keepDefault)));
|
|
81
81
|
} else if (k === 'dependencies' && v !== null && typeof v === 'object' && !Array.isArray(v)) {
|
|
82
82
|
const m = {};
|
|
83
|
-
for (const name of Object.keys(v)) setOwn(m, name, Array.isArray(v[name]) ? v[name] : stripSchema(v[name], false));
|
|
83
|
+
for (const name of Object.keys(v)) setOwn(m, name, Array.isArray(v[name]) ? v[name] : stripSchema(v[name], false, keepDefault));
|
|
84
84
|
setOwn(out, k, m);
|
|
85
85
|
} else if (k === 'propertyDependencies' && v !== null && typeof v === 'object' && !Array.isArray(v)) {
|
|
86
86
|
const m = {};
|
|
@@ -88,7 +88,7 @@ function stripSchema(node, keepTitle) {
|
|
|
88
88
|
const inner = v[name];
|
|
89
89
|
if (inner === null || typeof inner !== 'object' || Array.isArray(inner)) { setOwn(m, name, inner); continue; }
|
|
90
90
|
const mi = {};
|
|
91
|
-
for (const val of Object.keys(inner)) setOwn(mi, val, stripSchema(inner[val], false));
|
|
91
|
+
for (const val of Object.keys(inner)) setOwn(mi, val, stripSchema(inner[val], false, keepDefault));
|
|
92
92
|
setOwn(m, name, mi);
|
|
93
93
|
}
|
|
94
94
|
setOwn(out, k, m);
|
|
@@ -112,4 +112,11 @@ function stripAnnotations(schema) {
|
|
|
112
112
|
return out;
|
|
113
113
|
}
|
|
114
114
|
|
|
115
|
-
module
|
|
115
|
+
// The same copy with every `default` kept, for a module that fills defaults
|
|
116
|
+
// from its embedded schema (lib/aot-impl.js).
|
|
117
|
+
function stripAnnotationsKeepingDefaults(schema) {
|
|
118
|
+
if (schema === null || typeof schema !== 'object') return schema;
|
|
119
|
+
return refsIntoDropped(schema, new Set()) ? schema : stripSchema(schema, false, true);
|
|
120
|
+
}
|
|
121
|
+
|
|
122
|
+
module.exports = { stripAnnotations, stripAnnotationsKeepingDefaults };
|
package/lib/tiering.js
CHANGED
|
@@ -111,6 +111,7 @@ function createTiering (load) {
|
|
|
111
111
|
// A call counter for one validator: the TIER_CALLS-th call asks for the
|
|
112
112
|
// generator and has the validator rebuilt once it is in.
|
|
113
113
|
function counter (v) {
|
|
114
|
+
if (warm === 1) warm = 2;
|
|
114
115
|
let calls = 0, start = 0;
|
|
115
116
|
return () => {
|
|
116
117
|
if (++calls < TIER_CALLS) { if (calls === 1) start = now(); return; }
|
|
@@ -123,8 +124,94 @@ function createTiering (load) {
|
|
|
123
124
|
};
|
|
124
125
|
}
|
|
125
126
|
|
|
127
|
+
// A page's first check pays for V8 compiling the engine and the error
|
|
128
|
+
// builders (3.1 ms of an eight-field form's first submit, 0.4 ms once any
|
|
129
|
+
// other schema had been checked) and for building its own schema's engine.
|
|
130
|
+
// Both move to when the page is idle, or 200 ms later where there is no
|
|
131
|
+
// requestIdleCallback (Safari, workers): one check of a small schema, once
|
|
132
|
+
// per page and dropped when a validator compiles first, then the engine for
|
|
133
|
+
// each validator built since, at most eight, kept aside. A validator adopts
|
|
134
|
+
// it at its first check only if every input is still the same, its schema
|
|
135
|
+
// text included, so addSchema() or a change to the schema in between is
|
|
136
|
+
// honored as before.
|
|
137
|
+
// warm: 0 none, 1 scheduled, 2 done or dropped.
|
|
138
|
+
let warm = 0, scheduled = false, warming = false, adopted = 0;
|
|
139
|
+
const pending = [];
|
|
140
|
+
const shadows = new WeakMap();
|
|
141
|
+
const idle = (f) => {
|
|
142
|
+
if (typeof requestIdleCallback === 'function') return requestIdleCallback(f, { timeout: 2000 });
|
|
143
|
+
const t = setTimeout(f, 200);
|
|
144
|
+
if (t && typeof t.unref === 'function') t.unref();
|
|
145
|
+
};
|
|
146
|
+
function built (v) {
|
|
147
|
+
if (warming) return;
|
|
148
|
+
if (warm === 0) warm = 1;
|
|
149
|
+
if (pending.length < 8 && typeof WeakRef === 'function') pending.push(new WeakRef(v));
|
|
150
|
+
if (!scheduled) { scheduled = true; idle(run); }
|
|
151
|
+
}
|
|
152
|
+
function run (deadline) {
|
|
153
|
+
scheduled = false;
|
|
154
|
+
if (warm === 1) {
|
|
155
|
+
warm = 2;
|
|
156
|
+
warming = true;
|
|
157
|
+
try { warmUp(); } catch { /* a warm-up never reaches the page */ }
|
|
158
|
+
warming = false;
|
|
159
|
+
}
|
|
160
|
+
while (pending.length > 0) {
|
|
161
|
+
if (deadline !== undefined && deadline.timeRemaining() < 1) { scheduled = true; idle(run); return; }
|
|
162
|
+
const v = pending.shift().deref();
|
|
163
|
+
if (v === undefined || core._codegenRegistered()) continue;
|
|
164
|
+
let s = null;
|
|
165
|
+
try { s = prebuild(v); } catch { /* built as usual at the first check */ }
|
|
166
|
+
if (s !== null) shadows.set(v, s);
|
|
167
|
+
}
|
|
168
|
+
}
|
|
169
|
+
|
|
170
|
+
// One check of a small schema, rejected and accepted, compiles the engine's
|
|
171
|
+
// code for the page: the plan builder, the walk, the regex and format
|
|
172
|
+
// checks, the error builders.
|
|
173
|
+
function warmUp () {
|
|
174
|
+
const v = new core.Validator({
|
|
175
|
+
type: 'object',
|
|
176
|
+
properties: {
|
|
177
|
+
a: { type: 'string', minLength: 2, maxLength: 9, pattern: '^[a-z]+$', format: 'email' },
|
|
178
|
+
b: { type: 'integer', minimum: 1, maximum: 9 },
|
|
179
|
+
c: { type: 'array', items: { type: 'string' }, maxItems: 3 },
|
|
180
|
+
d: { const: true },
|
|
181
|
+
e: { type: 'object', properties: { f: { type: 'string' } }, required: ['f'] },
|
|
182
|
+
},
|
|
183
|
+
required: ['a', 'b'],
|
|
184
|
+
});
|
|
185
|
+
void v.validate({ a: '1', b: 0, c: [1], d: false, e: {} }).errors.length;
|
|
186
|
+
v.validate({ a: 'ab@c.de', b: 2, c: ['x'], d: true, e: { f: 'x' } });
|
|
187
|
+
}
|
|
188
|
+
|
|
189
|
+
// The engine a validator will build at its first check, built now from the
|
|
190
|
+
// same inputs and kept aside; null where something besides the schema object
|
|
191
|
+
// decides the build: custom keywords (their compile functions are the
|
|
192
|
+
// caller's code), registered schemas, a schema that needs rewriting.
|
|
193
|
+
function prebuild (v) {
|
|
194
|
+
if (v._initialized || v._keywords !== null || v._schemaMap.size !== 0) return null;
|
|
195
|
+
const raw = v._rawSchema;
|
|
196
|
+
const { _interpArgs, _normalizeCallerSchemaUncached } = core._internals;
|
|
197
|
+
if (raw === null || typeof raw !== 'object' || _normalizeCallerSchemaUncached(raw) !== raw) return null;
|
|
198
|
+
const args = _interpArgs(v, raw);
|
|
199
|
+
return { schema: raw, text: JSON.stringify(raw), args, interp: require('./interpreter').createInterpreter(raw, args) };
|
|
200
|
+
}
|
|
201
|
+
|
|
202
|
+
function adopt (v, schemaObj, args) {
|
|
203
|
+
const s = shadows.get(v);
|
|
204
|
+
if (s === undefined) return null;
|
|
205
|
+
shadows.delete(v);
|
|
206
|
+
if (s.schema !== schemaObj || s.text !== v._schemaStr) return null;
|
|
207
|
+
for (const k of Object.keys(args)) if (args[k] !== s.args[k]) return null;
|
|
208
|
+
adopted++;
|
|
209
|
+
return s.interp;
|
|
210
|
+
}
|
|
211
|
+
|
|
126
212
|
// Count the interpreted engine's calls for this validator.
|
|
127
213
|
function watch (v, interp) {
|
|
214
|
+
if (warm === 1) warm = 2;
|
|
128
215
|
const v0 = interp.validate, i0 = interp.isValid;
|
|
129
216
|
const tick = counter(v);
|
|
130
217
|
interp.validate = (d) => { tick(); return v0.call(interp, d); };
|
|
@@ -143,7 +230,7 @@ function createTiering (load) {
|
|
|
143
230
|
while (draining !== null) await draining;
|
|
144
231
|
}
|
|
145
232
|
|
|
146
|
-
return { watch, counter, aotStub, loadCodegen,
|
|
233
|
+
return { watch, counter, built, aotStub, loadCodegen, settled, TIER_CALLS, WINDOW_MS, get loading () { return loading; }, get warm () { return warm; }, get adopted () { return adopted; }, adopt };
|
|
147
234
|
}
|
|
148
235
|
|
|
149
236
|
module.exports = { createTiering, TIER_CALLS };
|
package/lib/ts-gen.js
CHANGED
|
@@ -197,6 +197,8 @@ function renderJsDoc(schema, indent) {
|
|
|
197
197
|
function toTypeScript(schema, opts) {
|
|
198
198
|
const options = opts || {};
|
|
199
199
|
const rootName = toTypeName(options.name || 'Data');
|
|
200
|
+
// Whether the module beside these types exports applyDefaults (lib/aot-impl.js).
|
|
201
|
+
const fill = options.applyDefaults === true;
|
|
200
202
|
const defs = schema && (schema.$defs || schema.definitions);
|
|
201
203
|
|
|
202
204
|
const defLines = [];
|
|
@@ -240,9 +242,11 @@ export type Result = ValidResult | InvalidResult;
|
|
|
240
242
|
|
|
241
243
|
export declare function isValid(data: unknown): data is ${rootName};
|
|
242
244
|
export declare function validate(data: unknown): Result;
|
|
243
|
-
|
|
245
|
+
${fill ? `/** Fills in the schema's \`default\` values where a key is missing, as the runtime Validator does before checking. Call it before validate() for the runtime's answer. */
|
|
246
|
+
export declare function applyDefaults(data: unknown): void;
|
|
247
|
+
` : ''}export declare const schemaHash: string;
|
|
244
248
|
export declare const ataVersion: string;
|
|
245
|
-
declare const _default: { validate: typeof validate; isValid: typeof isValid; schemaHash: typeof schemaHash; ataVersion: typeof ataVersion };
|
|
249
|
+
declare const _default: { validate: typeof validate; isValid: typeof isValid; ${fill ? 'applyDefaults: typeof applyDefaults; ' : ''}schemaHash: typeof schemaHash; ataVersion: typeof ataVersion };
|
|
246
250
|
export default _default;
|
|
247
251
|
`;
|
|
248
252
|
}
|
package/lib/validator-core.js
CHANGED
|
@@ -333,7 +333,13 @@ function resolveSchemaByPath(rootSchema, schemaPath) {
|
|
|
333
333
|
//
|
|
334
334
|
// A failing route sees the same handful of schemaPaths over and over, which is
|
|
335
335
|
// what makes the first one worth having.
|
|
336
|
-
|
|
336
|
+
// Loaded at the first compile, where every use below is, rather than with the
|
|
337
|
+
// core: with it came the schema order and diagnostic modules, run at import
|
|
338
|
+
// by every page that loads the package, validating or not.
|
|
339
|
+
let LazyRejection, RichRejection, LazyJsonRejection, _enrichLazy, presentErrors, needsOrdering, sortErrorsBySchemaOrder, stripOrdinal;
|
|
340
|
+
function _loadRejections() {
|
|
341
|
+
({ LazyRejection, RichRejection, LazyJsonRejection, _enrichLazy, presentErrors, needsOrdering, sortErrorsBySchemaOrder, stripOrdinal } = require('./rejections'));
|
|
342
|
+
}
|
|
337
343
|
|
|
338
344
|
// Text that does not parse, reported the same way on every path and platform:
|
|
339
345
|
// ATA9001 with the parser's own message. The native addon used to say only
|
|
@@ -716,6 +722,21 @@ function resolveSchemaForPreprocess(schema, schemaMap) {
|
|
|
716
722
|
// touched), `format` stripped under `assertFormat: false`, and the custom
|
|
717
723
|
// keyword scan. The accessors then step aside for own data properties, so
|
|
718
724
|
// every later read is a plain field.
|
|
725
|
+
// What the interpreted engine is built from, besides the schema.
|
|
726
|
+
function _interpArgs(self, schemaObj) {
|
|
727
|
+
const options = self._options;
|
|
728
|
+
return {
|
|
729
|
+
schemaMap: self._schemaMap.size > 0 ? self._schemaMap : null,
|
|
730
|
+
formats: self._userFormats,
|
|
731
|
+
v1: isV1Dialect(schemaObj),
|
|
732
|
+
keywords: self._keywords,
|
|
733
|
+
// compat's coercion where each type is checked (see the interpreter).
|
|
734
|
+
coerce: self._interpretOnly ? options._coerceInWalk : undefined,
|
|
735
|
+
discriminator: self._interpretOnly && options._discriminator === true,
|
|
736
|
+
numberFormats: self._interpretOnly ? options._numberFormats : undefined,
|
|
737
|
+
};
|
|
738
|
+
}
|
|
739
|
+
|
|
719
740
|
function _materializeSchema(self) {
|
|
720
741
|
const raw = self._rawSchema;
|
|
721
742
|
const options = self._options;
|
|
@@ -895,6 +916,8 @@ class Validator {
|
|
|
895
916
|
this._verdictTail = null;
|
|
896
917
|
this._validateTail = null;
|
|
897
918
|
this._entryExt = null;
|
|
919
|
+
// The browser build warms the engine once, when the page is idle (lib/tiering.js).
|
|
920
|
+
if (_tiering !== null) _tiering.built(this);
|
|
898
921
|
|
|
899
922
|
// Public methods start as memoized accessors on the prototype; nothing is
|
|
900
923
|
// allocated per instance until one is first read. See _defineLazyMethod
|
|
@@ -947,6 +970,7 @@ class Validator {
|
|
|
947
970
|
|
|
948
971
|
_ensureCompiled() {
|
|
949
972
|
if (this._initialized) return;
|
|
973
|
+
if (LazyRejection === undefined) _loadRejections();
|
|
950
974
|
this._ensureVocabularies();
|
|
951
975
|
this._initialized = true;
|
|
952
976
|
// Errors behind a reference into a registered document are ordered as
|
|
@@ -1074,22 +1098,22 @@ class Validator {
|
|
|
1074
1098
|
// routes, and generating and compiling the pass again for each was most of
|
|
1075
1099
|
// what a repeated schema cost to set up.
|
|
1076
1100
|
let preprocess = null;
|
|
1101
|
+
// A root schema whose text has no `default` has none to fill, and neither
|
|
1102
|
+
// pass looks for one. One reached through a reference into another
|
|
1103
|
+
// document is walked, since its defaults are not in this text.
|
|
1104
|
+
const mayHaveDefaults = preprocessSchema !== schemaObj || this._schemaStr.includes('"default"');
|
|
1077
1105
|
if (!this._interpretOnly && _codegen !== null) {
|
|
1078
1106
|
const cacheable = !this._userFormats && !this._usesKeywords;
|
|
1079
1107
|
const ppKey = cacheable ? mapKey + '\u0000' + String(options.coerceTypes) + '|' + (options.useDefaults !== false) + '|' + String(options.removeAdditional) : null;
|
|
1080
1108
|
const hit = ppKey !== null ? _preprocessCache.get(ppKey) : undefined;
|
|
1081
1109
|
if (hit !== undefined) preprocess = hit;
|
|
1082
1110
|
else {
|
|
1083
|
-
preprocess = _codegen.buildPreprocess(preprocessSchema, options) || null;
|
|
1111
|
+
preprocess = _codegen.buildPreprocess(preprocessSchema, mayHaveDefaults || options.useDefaults === false ? options : { ...options, useDefaults: false }) || null;
|
|
1084
1112
|
if (ppKey !== null && preprocess !== null) _boundedSet(_preprocessCache, ppKey, preprocess);
|
|
1085
1113
|
}
|
|
1086
1114
|
}
|
|
1087
1115
|
if (!preprocess) {
|
|
1088
|
-
//
|
|
1089
|
-
// defaults module then stays unloaded. One reached through a reference
|
|
1090
|
-
// into another document is walked, since its defaults are not in this
|
|
1091
|
-
// text.
|
|
1092
|
-
const mayHaveDefaults = preprocessSchema !== schemaObj || this._schemaStr.includes('"default"');
|
|
1116
|
+
// Without a `default` in the text the defaults module stays unloaded.
|
|
1093
1117
|
const applyDefaults = options.useDefaults === false || !mayHaveDefaults ? null : buildDefaultsApplier(preprocessSchema);
|
|
1094
1118
|
const applyCoerce = options.coerceTypes ? buildCoercer(preprocessSchema) : null;
|
|
1095
1119
|
const applyRemove = options.removeAdditional
|
|
@@ -1176,11 +1200,11 @@ class Validator {
|
|
|
1176
1200
|
// locals and on this object rather than on the instance: every call reads
|
|
1177
1201
|
// `validate` off the instance, and three more own properties made that
|
|
1178
1202
|
// read slower across the suite's validators.
|
|
1179
|
-
let _rejectBase = null, _rejectPresented = null, _rejectPlain = false, _oneShotRich = null, _richBuilder = null, _oneShotPlain = false, _isCleanShape = null, _onePassOf = null, _isCombined = null;
|
|
1203
|
+
let _rejectBase = null, _rejectPresented = null, _rejectPlain = false, _oneShotRich = null, _richBuilder = null, _oneShotPlain = false, _isCleanShape = null, _onePassOf = null, _isCombined = null, _preVerdict = null, _rawVerdict = null;
|
|
1180
1204
|
if (jsFn) {
|
|
1181
1205
|
const _cg = {
|
|
1182
1206
|
jsFn, _isCodegen, preprocess, fusedRemove, options, schemaObj, useSimdjsonForLarge, _buildCombined, _buildErr,
|
|
1183
|
-
combined: () => jsCombinedFn, err: () => jsErrFn, rejectBase: null, richBuilder: null, oneShotPlain: false, isCleanShape: null, isCombined: null, onePassOf: null,
|
|
1207
|
+
combined: () => jsCombinedFn, err: () => jsErrFn, rejectBase: null, richBuilder: null, oneShotPlain: false, isCleanShape: null, isCombined: null, onePassOf: null, preVerdict: null, rawVerdict: null,
|
|
1184
1208
|
// The combined function with errors built enriched where they happen,
|
|
1185
1209
|
// for a validator whose errors are read with richErrors on and no
|
|
1186
1210
|
// schema source to attach frames from (see makeRich).
|
|
@@ -1196,22 +1220,17 @@ class Validator {
|
|
|
1196
1220
|
_oneShotPlain = _cg.oneShotPlain;
|
|
1197
1221
|
_isCleanShape = _cg.isCleanShape;
|
|
1198
1222
|
_onePassOf = _cg.onePassOf;
|
|
1223
|
+
_preVerdict = _cg.preVerdict;
|
|
1224
|
+
_rawVerdict = _cg.rawVerdict;
|
|
1199
1225
|
_isCombined = _cg.isCombined;
|
|
1200
1226
|
} else {
|
|
1201
1227
|
// No JS codegen: the interpreted engine, with or without the addon. The
|
|
1202
1228
|
// buffer APIs come from _installBufferApis on first use, as they do on
|
|
1203
1229
|
// every other path.
|
|
1204
|
-
const
|
|
1205
|
-
|
|
1206
|
-
|
|
1207
|
-
|
|
1208
|
-
v1: isV1Dialect(schemaObj),
|
|
1209
|
-
keywords: this._keywords,
|
|
1210
|
-
// compat's coercion where each type is checked (see the interpreter).
|
|
1211
|
-
coerce: this._interpretOnly ? options._coerceInWalk : undefined,
|
|
1212
|
-
discriminator: this._interpretOnly && options._discriminator === true,
|
|
1213
|
-
numberFormats: this._interpretOnly ? options._numberFormats : undefined,
|
|
1214
|
-
});
|
|
1230
|
+
const iargs = _interpArgs(this, schemaObj);
|
|
1231
|
+
// The browser build may have built it while the page was idle, from
|
|
1232
|
+
// the same inputs (lib/tiering.js).
|
|
1233
|
+
const interp = (_tiering !== null && _tiering.adopt(this, schemaObj, iargs)) || require('./interpreter').createInterpreter(schemaObj, iargs);
|
|
1215
1234
|
this._engine = 'interpreter';
|
|
1216
1235
|
if (_codegen === null && _tiering !== null && !_forceNapi && !this._v1Dynamic) _tiering.watch(this, interp);
|
|
1217
1236
|
// A keyword that reads its data's context may also change the data (a
|
|
@@ -1220,6 +1239,7 @@ class Validator {
|
|
|
1220
1239
|
// run the keyword twice, the second time on what the first left.
|
|
1221
1240
|
const once = interp.dataContext === true;
|
|
1222
1241
|
if (!preprocess && !once) this._fastVerdict = (d) => interp.isValid(d);
|
|
1242
|
+
if (preprocess && !once) _preVerdict = (d) => { preprocess(d); return interp.isValid(d); };
|
|
1223
1243
|
// abortEarly is a documented contract, not a property of whichever engine
|
|
1224
1244
|
// answered: it promises the frozen ATA9000 stub instead of a detailed
|
|
1225
1245
|
// error, so code that branches on it has to behave the same with and
|
|
@@ -1234,7 +1254,7 @@ class Validator {
|
|
|
1234
1254
|
: (data) => interp.validate(data));
|
|
1235
1255
|
this.validate = this._verbose ? _verboseWrap(run, schemaObj) : run;
|
|
1236
1256
|
// Reading errors runs the interpreter once, directly; see _rejectBase.
|
|
1237
|
-
if (!
|
|
1257
|
+
if (!options.abortEarly && !this._verbose && !once) _rejectBase = (data) => interp.validate(data);
|
|
1238
1258
|
_bindVerdict(this, this._fastVerdict
|
|
1239
1259
|
? this._fastVerdict
|
|
1240
1260
|
: (data) => run(data).valid);
|
|
@@ -1425,10 +1445,14 @@ class Validator {
|
|
|
1425
1445
|
// engine and materializes `errors` through a getter on first access.
|
|
1426
1446
|
// A caller that only reads `.valid`, which is every gateway check and
|
|
1427
1447
|
// every benchmark, skips error construction entirely; a caller that
|
|
1428
|
-
// reads `.errors` pays once and the result is cached.
|
|
1429
|
-
//
|
|
1430
|
-
//
|
|
1431
|
-
//
|
|
1448
|
+
// reads `.errors` pays once and the result is cached. A schema that
|
|
1449
|
+
// coerces or defaults takes it too, with a verdict that runs the
|
|
1450
|
+
// preprocess pass first; the errors are then built from the document as
|
|
1451
|
+
// the pass left it. It built them on every rejection, read or not, which
|
|
1452
|
+
// on a large schema was 25 ms of a first validate() spent ordering
|
|
1453
|
+
// errors nobody asked for. Skipped under abortEarly (already a frozen
|
|
1454
|
+
// stub), for $dynamicRef (the boolean engine is not the authority
|
|
1455
|
+
// there), and with removeAdditional's fused pass.
|
|
1432
1456
|
// A check registered through _extendValidate joins here when it can: the
|
|
1433
1457
|
// verdict comes from the generated function with the check compiled in,
|
|
1434
1458
|
// and the check's own errors are appended when somebody reads them. A
|
|
@@ -1436,7 +1460,8 @@ class Validator {
|
|
|
1436
1460
|
// is not installed, the end of this method wraps validate() instead.
|
|
1437
1461
|
const _vx = this._validateTail !== null ? this._validateTail() : null;
|
|
1438
1462
|
let _vxApplied = false;
|
|
1439
|
-
|
|
1463
|
+
const _verdict0 = preprocess ? _preVerdict : this._fastVerdict;
|
|
1464
|
+
if (_verdict0 && !options.abortEarly && this.validate) {
|
|
1440
1465
|
// The errors of a document the verdict rejected come from the rejection
|
|
1441
1466
|
// path directly, not from validate() deciding a second time. Not with a
|
|
1442
1467
|
// check from _extendValidate: that check can reject what the schema
|
|
@@ -1444,7 +1469,7 @@ class Validator {
|
|
|
1444
1469
|
// Not with removeAdditional's fused pass either, which edits the
|
|
1445
1470
|
// document before the errors are read.
|
|
1446
1471
|
const _full = (_vx === null && !fusedRemove && _rejectPresented) ? _rejectPresented : this.validate;
|
|
1447
|
-
const _fast = _vx ? _fuseTail(
|
|
1472
|
+
const _fast = _vx ? _fuseTail(_verdict0, _vx.check) : _verdict0;
|
|
1448
1473
|
const _extra = _vx ? _vx.errors : null;
|
|
1449
1474
|
_vxApplied = true;
|
|
1450
1475
|
const EMPTY_ERRORS = Object.freeze([]);
|
|
@@ -1536,13 +1561,16 @@ class Validator {
|
|
|
1536
1561
|
// the caller and the checks, and allocation sites of its own. Compiled
|
|
1537
1562
|
// only once the validator is in use, as the hybrid is.
|
|
1538
1563
|
const self = this;
|
|
1539
|
-
|
|
1564
|
+
// With a preprocess pass the factory's function runs after it, as the
|
|
1565
|
+
// verdict does.
|
|
1566
|
+
const _gv = preprocess ? _rawVerdict : _fast === this._fastVerdict ? _fast : null;
|
|
1567
|
+
const _factory = (_vx === null && _gv !== null && typeof _gv._resultFactory === 'function') ? _gv._resultFactory : null;
|
|
1540
1568
|
if (_factory !== null) {
|
|
1541
1569
|
let calls = 0;
|
|
1542
1570
|
this.validate = (data) => {
|
|
1543
1571
|
if (++calls === HYBRID_TIER_CALLS && _onePass === null) {
|
|
1544
1572
|
const gen = _factory(LazyRejection, _errorsOf, _buildRawErrors, EMPTY_ERRORS);
|
|
1545
|
-
if (gen) self.validate = gen;
|
|
1573
|
+
if (gen) self.validate = preprocess ? (data) => { preprocess(data); return gen(data); } : gen;
|
|
1546
1574
|
}
|
|
1547
1575
|
return _plainValidate(data);
|
|
1548
1576
|
};
|
|
@@ -2506,4 +2534,4 @@ Object.defineProperty(module.exports, '_codegenRegistered', { value: () => _code
|
|
|
2506
2534
|
Object.defineProperty(module.exports, '_tiering', { get: () => _tiering, enumerable: false });
|
|
2507
2535
|
Object.defineProperty(module.exports, '_registerAot', { value: _registerAot, enumerable: false });
|
|
2508
2536
|
// What the compiled paths in index.js need from this module.
|
|
2509
|
-
Object.defineProperty(module.exports, '_internals', { value: { _jsonSyntaxRejection, ABORT_EARLY_RESULT, HYBRID_TIER_CALLS, SIMDJSON_THRESHOLD, VALID_RESULT, _bindEntry, _bindVerdict, _compileCache, _compileCacheSet, _mustReject, _rememberInstance, _verboseWrap, compileCacheKey, getNative, isV1Dialect, resolveSchemaByPath }, enumerable: false });
|
|
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 });
|
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.58.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",
|
|
@@ -87,7 +87,8 @@
|
|
|
87
87
|
"browser": {
|
|
88
88
|
"./lib/native-load.js": "./lib/native-load.browser.js",
|
|
89
89
|
"./lib/aot.js": "./lib/aot.browser.js",
|
|
90
|
-
"./lib/buffer-gate.js": "./lib/buffer-gate.browser.js"
|
|
90
|
+
"./lib/buffer-gate.js": "./lib/buffer-gate.browser.js",
|
|
91
|
+
"./lib/interpreter.js": "./lib/interpreter.browser.js"
|
|
91
92
|
},
|
|
92
93
|
"scripts": {
|
|
93
94
|
"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",
|
|
@@ -185,13 +186,13 @@
|
|
|
185
186
|
"codegen.d.ts"
|
|
186
187
|
],
|
|
187
188
|
"optionalDependencies": {
|
|
188
|
-
"@ata-validator/native-darwin-arm64": "1.
|
|
189
|
-
"@ata-validator/native-darwin-x64": "1.
|
|
190
|
-
"@ata-validator/native-linux-arm64-gnu": "1.
|
|
191
|
-
"@ata-validator/native-linux-arm64-musl": "1.
|
|
192
|
-
"@ata-validator/native-linux-x64-gnu": "1.
|
|
193
|
-
"@ata-validator/native-linux-x64-musl": "1.
|
|
194
|
-
"@ata-validator/native-win32-x64": "1.
|
|
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"
|
|
195
196
|
},
|
|
196
197
|
"peerDependencies": {
|
|
197
198
|
"yaml": "^2.0.0"
|