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.
@@ -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
- if (compile === null) this._fast = null;
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
- function setPlanCompiler(load) {
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;
@@ -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.exports = { stripAnnotations };
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, tierUp, settled, TIER_CALLS, WINDOW_MS, get loading () { return loading; } };
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
- export declare const schemaHash: string;
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
  }
@@ -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
- const { LazyRejection, RichRejection, LazyJsonRejection, _enrichLazy, presentErrors, needsOrdering, sortErrorsBySchemaOrder, stripOrdinal } = require('./rejections');
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
- // A root schema whose text has no `default` has none to fill, and the
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 { createInterpreter } = require('./interpreter');
1205
- const interp = createInterpreter(schemaObj, {
1206
- schemaMap: this._schemaMap.size > 0 ? this._schemaMap : null,
1207
- formats: this._userFormats,
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 (!preprocess && !options.abortEarly && !this._verbose && !once) _rejectBase = (data) => interp.validate(data);
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. Skipped when the
1429
- // schema coerces or defaults (preprocess mutates before the verdict),
1430
- // under abortEarly (already a frozen stub), and for $dynamicRef (the
1431
- // boolean engine is not the authority there).
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
- if (this._fastVerdict && !preprocess && !options.abortEarly && this.validate) {
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(this._fastVerdict, _vx.check) : this._fastVerdict;
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
- const _factory = (_vx === null && _fast === this._fastVerdict && typeof _fast._resultFactory === 'function') ? _fast._resultFactory : null;
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
@@ -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.56.0';
10
+ module.exports = '1.58.0';
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "ata-validator",
3
- "version": "1.56.0",
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.56.0",
189
- "@ata-validator/native-darwin-x64": "1.56.0",
190
- "@ata-validator/native-linux-arm64-gnu": "1.56.0",
191
- "@ata-validator/native-linux-arm64-musl": "1.56.0",
192
- "@ata-validator/native-linux-x64-gnu": "1.56.0",
193
- "@ata-validator/native-linux-x64-musl": "1.56.0",
194
- "@ata-validator/native-win32-x64": "1.56.0"
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"