ata-validator 1.31.3 → 1.32.1
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/index.d.ts +10 -0
- package/index.js +53 -0
- package/lib/aot-impl.js +1 -271
- package/lib/clone-emit.js +302 -0
- package/lib/interpreter.js +4 -4
- package/lib/js-compiler.js +47 -18
- package/lib/plan-compiler.js +1 -1
- package/lib/plan-source.js +3 -3
- package/lib/tier0.js +5 -3
- package/lib/version.js +1 -1
- package/package.json +9 -9
package/index.d.ts
CHANGED
|
@@ -497,6 +497,16 @@ export interface Validator<T = unknown> {
|
|
|
497
497
|
/** Parse JSON and validate against the schema. Returns the parsed value and the validation result. Works without the native addon. */
|
|
498
498
|
validateAndParse(jsonString: string | Buffer): ValidateAndParseResult<T>;
|
|
499
499
|
|
|
500
|
+
/**
|
|
501
|
+
* Validate, then return a copy holding only the properties the schema declares, with defaults
|
|
502
|
+
* filled. The input is not modified. Throws an error named `AtaValidationError`, with `errors`,
|
|
503
|
+
* when the value is invalid, and a `TypeError` where the kept key set cannot be proven from the
|
|
504
|
+
* schema (a $ref it cannot inline, patternProperties, an object that allows unknown keys) or the
|
|
505
|
+
* validator rewrites input (`coerceTypes`, custom keywords). Same result as the `parse` export of
|
|
506
|
+
* a module built with `ata build`.
|
|
507
|
+
*/
|
|
508
|
+
parse(data: unknown): T;
|
|
509
|
+
|
|
500
510
|
/** Ultra-fast buffer validation via native addon */
|
|
501
511
|
isValid(input: Buffer | Uint8Array | string): boolean;
|
|
502
512
|
|
package/index.js
CHANGED
|
@@ -2895,6 +2895,59 @@ Validator.prototype._extendValidate = function (resolve) {
|
|
|
2895
2895
|
// closure, where wrapping the five entry points cost a closure per entry point,
|
|
2896
2896
|
// the wrappers themselves and an accessor, most of what building a wrapped
|
|
2897
2897
|
// validator took.
|
|
2898
|
+
// parse(data): validate, then return a copy holding only what the schema
|
|
2899
|
+
// declares, the way the parse() export of an ahead-of-time module does, with
|
|
2900
|
+
// the same emitter behind both. Building the copy from the schema's own key
|
|
2901
|
+
// list costs less than finding and deleting unknown keys, and leaves the
|
|
2902
|
+
// caller's object alone. Where the key set cannot be proven (a $ref it cannot
|
|
2903
|
+
// inline, patternProperties, an open object) the method declines with an
|
|
2904
|
+
// error instead of guessing, as the module ships no parse() there; so it does
|
|
2905
|
+
// under options that rewrite input, whose answers a copy would not reproduce.
|
|
2906
|
+
Validator.prototype.parse = function (data) {
|
|
2907
|
+
let fn = this._parseFn;
|
|
2908
|
+
if (fn === undefined) {
|
|
2909
|
+
fn = _buildParse(this);
|
|
2910
|
+
Object.defineProperty(this, '_parseFn', { value: fn, writable: true, configurable: true, enumerable: false });
|
|
2911
|
+
}
|
|
2912
|
+
return fn(data);
|
|
2913
|
+
};
|
|
2914
|
+
|
|
2915
|
+
function _buildParse(self) {
|
|
2916
|
+
const decline = (why) => () => {
|
|
2917
|
+
throw new TypeError(`parse() is not available for this validator: ${why}. Use validate() with removeAdditional instead.`);
|
|
2918
|
+
};
|
|
2919
|
+
const o = self._options;
|
|
2920
|
+
if (o.coerceTypes || o.removeAdditional === 'all' || self._usesKeywords || self._verdictTail !== null || self._validateTail !== null) {
|
|
2921
|
+
return decline('its options or keywords rewrite or extend what the schema checks');
|
|
2922
|
+
}
|
|
2923
|
+
const { cloneExprFor } = require('./lib/clone-emit');
|
|
2924
|
+
const expr = cloneExprFor(self._schemaObj);
|
|
2925
|
+
if (!expr) return decline('the set of keys to keep cannot be proven from the schema');
|
|
2926
|
+
let copy;
|
|
2927
|
+
try {
|
|
2928
|
+
// eslint-disable-next-line no-new-func
|
|
2929
|
+
copy = new Function('data', 'return ' + expr);
|
|
2930
|
+
} catch {
|
|
2931
|
+
return decline('code generation is not allowed here');
|
|
2932
|
+
}
|
|
2933
|
+
self._ensureCompiled();
|
|
2934
|
+
const verdict = self._jsFn;
|
|
2935
|
+
if (typeof verdict !== 'function') return decline('the schema has no generated verdict function');
|
|
2936
|
+
return (data) => {
|
|
2937
|
+
if (!verdict(data)) {
|
|
2938
|
+
const e = new Error('validation failed');
|
|
2939
|
+
e.name = 'AtaValidationError';
|
|
2940
|
+
let target = data;
|
|
2941
|
+
if (self._mutatesInput) {
|
|
2942
|
+
try { target = structuredClone(data); } catch { target = data; }
|
|
2943
|
+
}
|
|
2944
|
+
e.errors = self.validate(target).errors;
|
|
2945
|
+
throw e;
|
|
2946
|
+
}
|
|
2947
|
+
return copy(data);
|
|
2948
|
+
};
|
|
2949
|
+
}
|
|
2950
|
+
|
|
2898
2951
|
Validator.prototype._extendChecks = function (resolve) {
|
|
2899
2952
|
if (typeof resolve !== 'function') throw new TypeError('_extendChecks expects a function');
|
|
2900
2953
|
this._extendValidate(resolve);
|
package/lib/aot-impl.js
CHANGED
|
@@ -189,277 +189,7 @@ function emitFormatDecls(closures, mode, declKW) {
|
|
|
189
189
|
return { decls: out, exportsSetFormats: false };
|
|
190
190
|
}
|
|
191
191
|
|
|
192
|
-
|
|
193
|
-
// declares, dropping everything else. This is the sanitising counterpart to
|
|
194
|
-
// removeAdditional: instead of enumerating the input and deleting the keys
|
|
195
|
-
// that do not belong, it reads only the keys that do. That is O(schema)
|
|
196
|
-
// rather than O(input), it leaves the caller's object untouched, and it keeps
|
|
197
|
-
// one hidden class instead of walking the object through a transition per
|
|
198
|
-
// delete.
|
|
199
|
-
//
|
|
200
|
-
// Returns null when the clone cannot be proven exact, and the module then
|
|
201
|
-
// simply has no parse(). Declining is recoverable; a sanitiser that quietly
|
|
202
|
-
// drops a property the schema allows is not.
|
|
203
|
-
// Resolve a local JSON pointer ('#', '#/$defs/x', draft-7 '#/definitions/x',
|
|
204
|
-
// any '#/...' path) against the schema root. Returns null for anything else:
|
|
205
|
-
// external documents, anchors, URNs. Those stay with the runtime engines.
|
|
206
|
-
function resolveLocalPointer(root, ref) {
|
|
207
|
-
if (ref === '#') return root;
|
|
208
|
-
if (typeof ref !== 'string' || !ref.startsWith('#/')) return null;
|
|
209
|
-
let cur = root;
|
|
210
|
-
for (const raw of ref.slice(2).split('/')) {
|
|
211
|
-
let seg = raw.replace(/~1/g, '/').replace(/~0/g, '~');
|
|
212
|
-
if (cur === null || typeof cur !== 'object') return null;
|
|
213
|
-
if (!Object.prototype.hasOwnProperty.call(cur, seg)) {
|
|
214
|
-
try { seg = decodeURIComponent(seg); } catch (_) { return null; }
|
|
215
|
-
if (!Object.prototype.hasOwnProperty.call(cur, seg)) return null;
|
|
216
|
-
}
|
|
217
|
-
cur = cur[seg];
|
|
218
|
-
}
|
|
219
|
-
return cur;
|
|
220
|
-
}
|
|
221
|
-
|
|
222
|
-
// Inline local, acyclic $refs before the clone proof runs, so a generated
|
|
223
|
-
// schema ($defs + $ref, the shape every schema generator emits) can still get
|
|
224
|
-
// a parse(). Only a node that is exactly a $ref plus annotations is replaced;
|
|
225
|
-
// a $ref with a constraining sibling, a cycle, an unresolvable target or an
|
|
226
|
-
// external reference is left in place, and emitClone declines it as before.
|
|
227
|
-
// The walk never mutates its input and never descends into data positions
|
|
228
|
-
// (default, const, enum, examples), where an object holding a "$ref" key is
|
|
229
|
-
// a value, not a schema.
|
|
230
|
-
const _INLINE_ANNOTATIONS = new Set(['$ref', 'title', 'description', '$comment', 'examples', 'deprecated', 'readOnly', 'writeOnly', 'default']);
|
|
231
|
-
const _INLINE_DATA_KEYS = new Set(['default', 'const', 'enum', 'examples']);
|
|
232
|
-
function inlineRefsForClone(root) {
|
|
233
|
-
const state = { budget: 512 };
|
|
234
|
-
// `inRef` marks content brought in from behind a reference. The runtime's
|
|
235
|
-
// useDefaults fills a default written next to the $ref, and does not fill
|
|
236
|
-
// defaults written inside the referenced definition, so the inlined copy
|
|
237
|
-
// keeps the first and drops the second. parse() must stay exactly as
|
|
238
|
-
// generous as validate(); a parse that fills more is a disagreement, not a
|
|
239
|
-
// feature.
|
|
240
|
-
const walk = (node, active, inRef) => {
|
|
241
|
-
if (!node || typeof node !== 'object') return node;
|
|
242
|
-
if (Array.isArray(node)) return node.map((n) => walk(n, active, inRef));
|
|
243
|
-
let cur = node;
|
|
244
|
-
let entered = false;
|
|
245
|
-
let siblingDefault;
|
|
246
|
-
while (cur && typeof cur === 'object' && !Array.isArray(cur) && typeof cur.$ref === 'string') {
|
|
247
|
-
for (const k of Object.keys(cur)) if (!_INLINE_ANNOTATIONS.has(k)) return node;
|
|
248
|
-
if (active.has(cur.$ref) || state.budget-- <= 0) return node;
|
|
249
|
-
const target = resolveLocalPointer(root, cur.$ref);
|
|
250
|
-
if (!target || typeof target !== 'object' || Array.isArray(target)) return node;
|
|
251
|
-
if (!entered && !inRef && cur.default !== undefined) siblingDefault = cur.default;
|
|
252
|
-
active = new Set(active);
|
|
253
|
-
active.add(cur.$ref);
|
|
254
|
-
cur = target;
|
|
255
|
-
entered = true;
|
|
256
|
-
}
|
|
257
|
-
const strip = inRef || entered;
|
|
258
|
-
const out = {};
|
|
259
|
-
for (const k of Object.keys(cur)) {
|
|
260
|
-
if (strip && k === 'default') continue;
|
|
261
|
-
const v = cur[k];
|
|
262
|
-
if (_INLINE_DATA_KEYS.has(k) || k === '$defs' || k === 'definitions') { out[k] = v; continue; }
|
|
263
|
-
out[k] = walk(v, active, strip);
|
|
264
|
-
}
|
|
265
|
-
if (siblingDefault !== undefined) out.default = siblingDefault;
|
|
266
|
-
return out;
|
|
267
|
-
};
|
|
268
|
-
return walk(root, new Set(), false);
|
|
269
|
-
}
|
|
270
|
-
|
|
271
|
-
function emitClone(node, access, depth) {
|
|
272
|
-
if (!node || typeof node !== 'object') return null;
|
|
273
|
-
if (depth > 12) return null;
|
|
274
|
-
// Only plain object nodes are cloneable, and only when the schema names
|
|
275
|
-
// every property it keeps. A $ref means the set of allowed keys is not
|
|
276
|
-
// this node's to decide. In-place applicators (allOf, anyOf, oneOf,
|
|
277
|
-
// if/then/else) are admitted under the same proof the unevaluated* error
|
|
278
|
-
// generators use: every property name they contribute must already be
|
|
279
|
-
// declared in this node's own `properties`, so they constrain values but
|
|
280
|
-
// never widen the key set. `not` never widens it. `unevaluatedProperties`
|
|
281
|
-
// is admitted only as `false` under that proof, where it is exactly
|
|
282
|
-
// `additionalProperties: false`.
|
|
283
|
-
if (node.$ref || node.patternProperties || node.additionalProperties === true) {
|
|
284
|
-
return null;
|
|
285
|
-
}
|
|
286
|
-
// additionalProperties as a schema is the record shape (z.record and every
|
|
287
|
-
// generator's map type): the key set is open by declaration, so keeping
|
|
288
|
-
// every key is exact, and every undeclared value is rebuilt against that
|
|
289
|
-
// one schema. That is a proof, not a guess, so it is admitted. What stays
|
|
290
|
-
// out: applicators next to a record (a branch could constrain some keys'
|
|
291
|
-
// values beyond the record schema, and the rebuild would ignore it), and
|
|
292
|
-
// `additionalProperties: true`, where the value is unconstrained and this
|
|
293
|
-
// pass only copies what it can name. An open object with no
|
|
294
|
-
// `additionalProperties` at all also stays out, for the original reason:
|
|
295
|
-
// validate() accepts its unknown keys, so a parse() that strips them would
|
|
296
|
-
// silently drop allowed data, and one that keeps them raw would not be a
|
|
297
|
-
// sanitiser.
|
|
298
|
-
const apSchema = (node.additionalProperties && typeof node.additionalProperties === 'object')
|
|
299
|
-
? node.additionalProperties
|
|
300
|
-
: null;
|
|
301
|
-
if (apSchema) {
|
|
302
|
-
if (node.unevaluatedProperties !== undefined) return null;
|
|
303
|
-
if (node.allOf || node.anyOf || node.oneOf || node.if || node.then || node.else ||
|
|
304
|
-
node.dependentSchemas !== undefined || node.dependencies !== undefined ||
|
|
305
|
-
node.$dynamicRef !== undefined || node.$recursiveRef !== undefined) return null;
|
|
306
|
-
}
|
|
307
|
-
if (node.unevaluatedProperties !== undefined && node.unevaluatedProperties !== false) return null;
|
|
308
|
-
if (node.type !== 'object') return null;
|
|
309
|
-
if (!node.properties && !apSchema) return null;
|
|
310
|
-
const keys = node.properties ? Object.keys(node.properties) : [];
|
|
311
|
-
if (keys.length === 0 && !apSchema) return null;
|
|
312
|
-
if (node.allOf || node.anyOf || node.oneOf || node.if || node.then || node.else ||
|
|
313
|
-
node.unevaluatedProperties === false) {
|
|
314
|
-
if (node.$dynamicRef !== undefined || node.$recursiveRef !== undefined ||
|
|
315
|
-
node.dependentSchemas !== undefined || node.dependencies !== undefined) return null;
|
|
316
|
-
const names = new Set();
|
|
317
|
-
const state = { prefix: 0 };
|
|
318
|
-
for (const k of ['allOf', 'anyOf', 'oneOf']) {
|
|
319
|
-
if (node[k] !== undefined) {
|
|
320
|
-
if (!Array.isArray(node[k])) return null;
|
|
321
|
-
for (const b of node[k]) if (!unevalContributions(b, 'props', names, state, 0)) return null;
|
|
322
|
-
}
|
|
323
|
-
}
|
|
324
|
-
for (const k of ['if', 'then', 'else']) {
|
|
325
|
-
if (node[k] !== undefined && !unevalContributions(node[k], 'props', names, state, 0)) return null;
|
|
326
|
-
}
|
|
327
|
-
for (const n of names) if (!Object.prototype.hasOwnProperty.call(node.properties, n)) return null;
|
|
328
|
-
}
|
|
329
|
-
const required = new Set(Array.isArray(node.required) ? node.required : []);
|
|
330
|
-
|
|
331
|
-
const fixed = [];
|
|
332
|
-
const conditional = [];
|
|
333
|
-
for (const key of keys) {
|
|
334
|
-
const prop = node.properties[key];
|
|
335
|
-
if (!prop || typeof prop !== 'object') return null;
|
|
336
|
-
if (prop.$ref) return null;
|
|
337
|
-
// A property that is required and carries a default is the one place the
|
|
338
|
-
// runtime and a raw-input validation disagree: the runtime fills the
|
|
339
|
-
// default before checking `required`, so `{}` validates there and would
|
|
340
|
-
// throw here. Declining keeps parse() exactly as strict as validate().
|
|
341
|
-
if (required.has(key) && prop.default !== undefined) return null;
|
|
342
|
-
// A default the property's own schema rejects would make parse() hand
|
|
343
|
-
// back a document validate() refuses: the runtime fills defaults before
|
|
344
|
-
// validating, so it catches the bad default, and a raw-input check here
|
|
345
|
-
// never sees it. Probe the default at emit time and decline when it does
|
|
346
|
-
// not hold, or when the subschema cannot be compiled to check it.
|
|
347
|
-
if (prop.default !== undefined) {
|
|
348
|
-
let probe = null;
|
|
349
|
-
try { probe = compileToJSCodegen(prop, null, null); } catch (_) { probe = null; }
|
|
350
|
-
if (!probe || !probe(prop.default)) return null;
|
|
351
|
-
}
|
|
352
|
-
const read = `${access}[${JSON.stringify(key)}]`;
|
|
353
|
-
const value = emitValueExpr(prop, read, depth);
|
|
354
|
-
if (value === null) return null;
|
|
355
|
-
// Computed key syntax: a plain `"__proto__":` in an object literal is
|
|
356
|
-
// the prototype slot, not a property; `["__proto__"]:` is a property.
|
|
357
|
-
if (required.has(key)) fixed.push(`[${JSON.stringify(key)}]: ${value}`);
|
|
358
|
-
else conditional.push({ key, value, dflt: prop.default });
|
|
359
|
-
}
|
|
360
|
-
if (fixed.length === 0 && conditional.length === 0 && !apSchema) return null;
|
|
361
|
-
|
|
362
|
-
const tmp = `_c${depth}`;
|
|
363
|
-
// The record loop: every key of the input that is not a declared property
|
|
364
|
-
// is kept, its value rebuilt against the additionalProperties schema. The
|
|
365
|
-
// input is already validated, so every such value satisfies that schema;
|
|
366
|
-
// the rebuild only strips inside it. Defaults under a record value are
|
|
367
|
-
// dropped before emission because the runtime's useDefaults does not fill
|
|
368
|
-
// them there, and parse() must equal validate().data, not improve on it.
|
|
369
|
-
let recordLoop = '';
|
|
370
|
-
if (apSchema) {
|
|
371
|
-
const kVar = `_k${depth}`;
|
|
372
|
-
const vVar = `_v${depth}`;
|
|
373
|
-
const valueExpr = emitValueExpr(stripDefaultsDeep(apSchema), vVar, depth);
|
|
374
|
-
if (valueExpr === null) return null;
|
|
375
|
-
let skip = '';
|
|
376
|
-
let declSet = '';
|
|
377
|
-
if (keys.length > 8) {
|
|
378
|
-
declSet = `const _d${depth} = new Set(${JSON.stringify(keys)}); `;
|
|
379
|
-
skip = `if (_d${depth}.has(${kVar})) continue; `;
|
|
380
|
-
} else if (keys.length > 0) {
|
|
381
|
-
skip = `if (${keys.map((k) => `${kVar} === ${JSON.stringify(k)}`).join(' || ')}) continue; `;
|
|
382
|
-
}
|
|
383
|
-
// The key comes from the input, so it can be the string "__proto__" (an
|
|
384
|
-
// own key only JSON.parse writes); assignment would hit the prototype
|
|
385
|
-
// setter, so that one key goes through defineProperty.
|
|
386
|
-
recordLoop = ` ${declSet}for (const ${kVar} of Object.keys(${access})) { ${skip}const ${vVar} = ${access}[${kVar}]; if (${kVar} === "__proto__") Object.defineProperty(${tmp}, ${kVar}, { value: ${valueExpr}, writable: true, enumerable: true, configurable: true }); else ${tmp}[${kVar}] = ${valueExpr}; }`;
|
|
387
|
-
}
|
|
388
|
-
|
|
389
|
-
const literal = `{ ${fixed.join(', ')} }`;
|
|
390
|
-
if (conditional.length === 0 && !recordLoop) return literal;
|
|
391
|
-
// Optional properties are added only when present, so the result never
|
|
392
|
-
// gains a key the input did not have.
|
|
393
|
-
const adds = conditional
|
|
394
|
-
.map(({ key, value, dflt }) => {
|
|
395
|
-
// Object.hasOwn, not `in`: `in` also answers for what the instance
|
|
396
|
-
// inherits, so a property named `constructor` would copy the inherited
|
|
397
|
-
// function into the output that validate().data does not carry. And a
|
|
398
|
-
// key named `__proto__` cannot be written by assignment, which hits
|
|
399
|
-
// the prototype setter; defineProperty writes an own key.
|
|
400
|
-
const assign = key === '__proto__'
|
|
401
|
-
? `Object.defineProperty(${tmp}, ${JSON.stringify(key)}, { value: ${value}, writable: true, enumerable: true, configurable: true });`
|
|
402
|
-
: `${tmp}[${JSON.stringify(key)}] = ${value};`;
|
|
403
|
-
const set = `if (Object.hasOwn(${access}, ${JSON.stringify(key)})) ${assign}`;
|
|
404
|
-
// The default is emitted as a literal inside the call, so an object or
|
|
405
|
-
// array default is a fresh value on every parse, never shared state.
|
|
406
|
-
if (dflt === undefined) return set;
|
|
407
|
-
const setDflt = key === '__proto__'
|
|
408
|
-
? `Object.defineProperty(${tmp}, ${JSON.stringify(key)}, { value: ${JSON.stringify(dflt)}, writable: true, enumerable: true, configurable: true });`
|
|
409
|
-
: `${tmp}[${JSON.stringify(key)}] = ${JSON.stringify(dflt)};`;
|
|
410
|
-
return `${set} else ${setDflt}`;
|
|
411
|
-
})
|
|
412
|
-
.join(' ');
|
|
413
|
-
return `(function(){ const ${tmp} = ${literal}; ${adds}${recordLoop} return ${tmp}; })()`;
|
|
414
|
-
}
|
|
415
|
-
|
|
416
|
-
// The value dispatch shared by declared properties and the record loop. A
|
|
417
|
-
// value is copyable only when the emitter can name everything that may live
|
|
418
|
-
// under it: a primitive holds nothing, an object with declared properties or
|
|
419
|
-
// a record schema is rebuilt the same way, an array of primitives has
|
|
420
|
-
// nowhere to hide a key. A $ref, a composition or an untyped node could all
|
|
421
|
-
// carry keys this code cannot see, and copying the reference would smuggle
|
|
422
|
-
// them into a value that claims to be sanitised, so the caller declines the
|
|
423
|
-
// whole clone instead.
|
|
424
|
-
// Drop every schema-position `default` in a subtree, leaving data positions
|
|
425
|
-
// (const, enum, examples) untouched. Used on a record's value schema, where
|
|
426
|
-
// the runtime's useDefaults fills nothing.
|
|
427
|
-
function stripDefaultsDeep(node) {
|
|
428
|
-
if (!node || typeof node !== 'object') return node;
|
|
429
|
-
if (Array.isArray(node)) return node.map(stripDefaultsDeep);
|
|
430
|
-
const out = {};
|
|
431
|
-
for (const k of Object.keys(node)) {
|
|
432
|
-
if (k === 'default') continue;
|
|
433
|
-
out[k] = _INLINE_DATA_KEYS.has(k) ? node[k] : stripDefaultsDeep(node[k]);
|
|
434
|
-
}
|
|
435
|
-
return out;
|
|
436
|
-
}
|
|
437
|
-
|
|
438
|
-
const _CLONE_PRIMITIVE = new Set(['string', 'number', 'integer', 'boolean', 'null']);
|
|
439
|
-
function emitValueExpr(prop, read, depth) {
|
|
440
|
-
if (!prop || typeof prop !== 'object') return null;
|
|
441
|
-
if (prop.$ref) return null;
|
|
442
|
-
const types = Array.isArray(prop.type) ? prop.type : [prop.type];
|
|
443
|
-
if (types.every((t) => _CLONE_PRIMITIVE.has(t))) return read;
|
|
444
|
-
const isRecordable = (s) => s.properties ||
|
|
445
|
-
(s.additionalProperties && typeof s.additionalProperties === 'object');
|
|
446
|
-
if (prop.type === 'object' && isRecordable(prop)) {
|
|
447
|
-
return emitClone(prop, read, depth + 1);
|
|
448
|
-
}
|
|
449
|
-
if (prop.type === 'array' && prop.items && typeof prop.items === 'object' &&
|
|
450
|
-
!Array.isArray(prop.items)) {
|
|
451
|
-
const it = prop.items;
|
|
452
|
-
const itemTypes = Array.isArray(it.type) ? it.type : [it.type];
|
|
453
|
-
if (itemTypes.every((t) => _CLONE_PRIMITIVE.has(t))) return read;
|
|
454
|
-
if (it.type === 'object' && isRecordable(it)) {
|
|
455
|
-
const el = '_e' + depth;
|
|
456
|
-
const inner = emitClone(it, el, depth + 1);
|
|
457
|
-
if (!inner) return null;
|
|
458
|
-
return `${read}.map((${el}) => (${inner}))`;
|
|
459
|
-
}
|
|
460
|
-
}
|
|
461
|
-
return null;
|
|
462
|
-
}
|
|
192
|
+
const { emitClone, inlineRefsForClone } = require('./clone-emit');
|
|
463
193
|
|
|
464
194
|
function toStandaloneModule(validator, opts) {
|
|
465
195
|
assertEmittable(validator, 'toStandaloneModule');
|
|
@@ -0,0 +1,302 @@
|
|
|
1
|
+
'use strict';
|
|
2
|
+
|
|
3
|
+
// The copy behind parse(): shared by the parse() export of an ahead-of-time
|
|
4
|
+
// module (lib/aot-impl.js) and the runtime's Validator#parse (index.js). It
|
|
5
|
+
// lives apart from the module emitter so the runtime can use it without
|
|
6
|
+
// pulling the emitter, and the safe-regex source it embeds, into a browser
|
|
7
|
+
// bundle.
|
|
8
|
+
|
|
9
|
+
const { compileToJSCodegen, unevalContributions } = require('./js-compiler');
|
|
10
|
+
|
|
11
|
+
// Emit an expression that rebuilds `access` from the properties the schema
|
|
12
|
+
// declares, dropping everything else. This is the sanitising counterpart to
|
|
13
|
+
// removeAdditional: instead of enumerating the input and deleting the keys
|
|
14
|
+
// that do not belong, it reads only the keys that do. That is O(schema)
|
|
15
|
+
// rather than O(input), it leaves the caller's object untouched, and it keeps
|
|
16
|
+
// one hidden class instead of walking the object through a transition per
|
|
17
|
+
// delete.
|
|
18
|
+
//
|
|
19
|
+
// Returns null when the clone cannot be proven exact, and the module then
|
|
20
|
+
// simply has no parse(). Declining is recoverable; a sanitiser that quietly
|
|
21
|
+
// drops a property the schema allows is not.
|
|
22
|
+
// Resolve a local JSON pointer ('#', '#/$defs/x', draft-7 '#/definitions/x',
|
|
23
|
+
// any '#/...' path) against the schema root. Returns null for anything else:
|
|
24
|
+
// external documents, anchors, URNs. Those stay with the runtime engines.
|
|
25
|
+
function resolveLocalPointer(root, ref) {
|
|
26
|
+
if (ref === '#') return root;
|
|
27
|
+
if (typeof ref !== 'string' || !ref.startsWith('#/')) return null;
|
|
28
|
+
let cur = root;
|
|
29
|
+
for (const raw of ref.slice(2).split('/')) {
|
|
30
|
+
let seg = raw.replace(/~1/g, '/').replace(/~0/g, '~');
|
|
31
|
+
if (cur === null || typeof cur !== 'object') return null;
|
|
32
|
+
if (!Object.prototype.hasOwnProperty.call(cur, seg)) {
|
|
33
|
+
try { seg = decodeURIComponent(seg); } catch (_) { return null; }
|
|
34
|
+
if (!Object.prototype.hasOwnProperty.call(cur, seg)) return null;
|
|
35
|
+
}
|
|
36
|
+
cur = cur[seg];
|
|
37
|
+
}
|
|
38
|
+
return cur;
|
|
39
|
+
}
|
|
40
|
+
|
|
41
|
+
// Inline local, acyclic $refs before the clone proof runs, so a generated
|
|
42
|
+
// schema ($defs + $ref, the shape every schema generator emits) can still get
|
|
43
|
+
// a parse(). Only a node that is exactly a $ref plus annotations is replaced;
|
|
44
|
+
// a $ref with a constraining sibling, a cycle, an unresolvable target or an
|
|
45
|
+
// external reference is left in place, and emitClone declines it as before.
|
|
46
|
+
// The walk never mutates its input and never descends into data positions
|
|
47
|
+
// (default, const, enum, examples), where an object holding a "$ref" key is
|
|
48
|
+
// a value, not a schema.
|
|
49
|
+
const _INLINE_ANNOTATIONS = new Set(['$ref', 'title', 'description', '$comment', 'examples', 'deprecated', 'readOnly', 'writeOnly', 'default']);
|
|
50
|
+
const _INLINE_DATA_KEYS = new Set(['default', 'const', 'enum', 'examples']);
|
|
51
|
+
function inlineRefsForClone(root) {
|
|
52
|
+
const state = { budget: 512 };
|
|
53
|
+
// `inRef` marks content brought in from behind a reference. The runtime's
|
|
54
|
+
// useDefaults fills a default written next to the $ref, and does not fill
|
|
55
|
+
// defaults written inside the referenced definition, so the inlined copy
|
|
56
|
+
// keeps the first and drops the second. parse() must stay exactly as
|
|
57
|
+
// generous as validate(); a parse that fills more is a disagreement, not a
|
|
58
|
+
// feature.
|
|
59
|
+
const walk = (node, active, inRef) => {
|
|
60
|
+
if (!node || typeof node !== 'object') return node;
|
|
61
|
+
if (Array.isArray(node)) return node.map((n) => walk(n, active, inRef));
|
|
62
|
+
let cur = node;
|
|
63
|
+
let entered = false;
|
|
64
|
+
let siblingDefault;
|
|
65
|
+
while (cur && typeof cur === 'object' && !Array.isArray(cur) && typeof cur.$ref === 'string') {
|
|
66
|
+
for (const k of Object.keys(cur)) if (!_INLINE_ANNOTATIONS.has(k)) return node;
|
|
67
|
+
if (active.has(cur.$ref) || state.budget-- <= 0) return node;
|
|
68
|
+
const target = resolveLocalPointer(root, cur.$ref);
|
|
69
|
+
if (!target || typeof target !== 'object' || Array.isArray(target)) return node;
|
|
70
|
+
if (!entered && !inRef && cur.default !== undefined) siblingDefault = cur.default;
|
|
71
|
+
active = new Set(active);
|
|
72
|
+
active.add(cur.$ref);
|
|
73
|
+
cur = target;
|
|
74
|
+
entered = true;
|
|
75
|
+
}
|
|
76
|
+
const strip = inRef || entered;
|
|
77
|
+
const out = {};
|
|
78
|
+
for (const k of Object.keys(cur)) {
|
|
79
|
+
if (strip && k === 'default') continue;
|
|
80
|
+
const v = cur[k];
|
|
81
|
+
if (_INLINE_DATA_KEYS.has(k) || k === '$defs' || k === 'definitions') { out[k] = v; continue; }
|
|
82
|
+
out[k] = walk(v, active, strip);
|
|
83
|
+
}
|
|
84
|
+
if (siblingDefault !== undefined) out.default = siblingDefault;
|
|
85
|
+
return out;
|
|
86
|
+
};
|
|
87
|
+
return walk(root, new Set(), false);
|
|
88
|
+
}
|
|
89
|
+
|
|
90
|
+
const PROTO_KEY_NAMES = new Set([...Object.getOwnPropertyNames(Object.prototype), '__proto__']);
|
|
91
|
+
|
|
92
|
+
function emitClone(node, access, depth) {
|
|
93
|
+
if (!node || typeof node !== 'object') return null;
|
|
94
|
+
if (depth > 12) return null;
|
|
95
|
+
// Only plain object nodes are cloneable, and only when the schema names
|
|
96
|
+
// every property it keeps. A $ref means the set of allowed keys is not
|
|
97
|
+
// this node's to decide. In-place applicators (allOf, anyOf, oneOf,
|
|
98
|
+
// if/then/else) are admitted under the same proof the unevaluated* error
|
|
99
|
+
// generators use: every property name they contribute must already be
|
|
100
|
+
// declared in this node's own `properties`, so they constrain values but
|
|
101
|
+
// never widen the key set. `not` never widens it. `unevaluatedProperties`
|
|
102
|
+
// is admitted only as `false` under that proof, where it is exactly
|
|
103
|
+
// `additionalProperties: false`.
|
|
104
|
+
if (node.$ref || node.patternProperties || node.additionalProperties === true) {
|
|
105
|
+
return null;
|
|
106
|
+
}
|
|
107
|
+
// additionalProperties as a schema is the record shape (z.record and every
|
|
108
|
+
// generator's map type): the key set is open by declaration, so keeping
|
|
109
|
+
// every key is exact, and every undeclared value is rebuilt against that
|
|
110
|
+
// one schema. That is a proof, not a guess, so it is admitted. What stays
|
|
111
|
+
// out: applicators next to a record (a branch could constrain some keys'
|
|
112
|
+
// values beyond the record schema, and the rebuild would ignore it), and
|
|
113
|
+
// `additionalProperties: true`, where the value is unconstrained and this
|
|
114
|
+
// pass only copies what it can name. An open object with no
|
|
115
|
+
// `additionalProperties` at all also stays out, for the original reason:
|
|
116
|
+
// validate() accepts its unknown keys, so a parse() that strips them would
|
|
117
|
+
// silently drop allowed data, and one that keeps them raw would not be a
|
|
118
|
+
// sanitiser.
|
|
119
|
+
const apSchema = (node.additionalProperties && typeof node.additionalProperties === 'object')
|
|
120
|
+
? node.additionalProperties
|
|
121
|
+
: null;
|
|
122
|
+
if (apSchema) {
|
|
123
|
+
if (node.unevaluatedProperties !== undefined) return null;
|
|
124
|
+
if (node.allOf || node.anyOf || node.oneOf || node.if || node.then || node.else ||
|
|
125
|
+
node.dependentSchemas !== undefined || node.dependencies !== undefined ||
|
|
126
|
+
node.$dynamicRef !== undefined || node.$recursiveRef !== undefined) return null;
|
|
127
|
+
}
|
|
128
|
+
if (node.unevaluatedProperties !== undefined && node.unevaluatedProperties !== false) return null;
|
|
129
|
+
if (node.type !== 'object') return null;
|
|
130
|
+
if (!node.properties && !apSchema) return null;
|
|
131
|
+
const keys = node.properties ? Object.keys(node.properties) : [];
|
|
132
|
+
if (keys.length === 0 && !apSchema) return null;
|
|
133
|
+
if (node.allOf || node.anyOf || node.oneOf || node.if || node.then || node.else ||
|
|
134
|
+
node.unevaluatedProperties === false) {
|
|
135
|
+
if (node.$dynamicRef !== undefined || node.$recursiveRef !== undefined ||
|
|
136
|
+
node.dependentSchemas !== undefined || node.dependencies !== undefined) return null;
|
|
137
|
+
const names = new Set();
|
|
138
|
+
const state = { prefix: 0 };
|
|
139
|
+
for (const k of ['allOf', 'anyOf', 'oneOf']) {
|
|
140
|
+
if (node[k] !== undefined) {
|
|
141
|
+
if (!Array.isArray(node[k])) return null;
|
|
142
|
+
for (const b of node[k]) if (!unevalContributions(b, 'props', names, state, 0)) return null;
|
|
143
|
+
}
|
|
144
|
+
}
|
|
145
|
+
for (const k of ['if', 'then', 'else']) {
|
|
146
|
+
if (node[k] !== undefined && !unevalContributions(node[k], 'props', names, state, 0)) return null;
|
|
147
|
+
}
|
|
148
|
+
for (const n of names) if (!Object.prototype.hasOwnProperty.call(node.properties, n)) return null;
|
|
149
|
+
}
|
|
150
|
+
const required = new Set(Array.isArray(node.required) ? node.required : []);
|
|
151
|
+
|
|
152
|
+
const fixed = [];
|
|
153
|
+
const conditional = [];
|
|
154
|
+
for (const key of keys) {
|
|
155
|
+
const prop = node.properties[key];
|
|
156
|
+
if (!prop || typeof prop !== 'object') return null;
|
|
157
|
+
if (prop.$ref) return null;
|
|
158
|
+
// A property that is required and carries a default is the one place the
|
|
159
|
+
// runtime and a raw-input validation disagree: the runtime fills the
|
|
160
|
+
// default before checking `required`, so `{}` validates there and would
|
|
161
|
+
// throw here. Declining keeps parse() exactly as strict as validate().
|
|
162
|
+
if (required.has(key) && prop.default !== undefined) return null;
|
|
163
|
+
// A default the property's own schema rejects would make parse() hand
|
|
164
|
+
// back a document validate() refuses: the runtime fills defaults before
|
|
165
|
+
// validating, so it catches the bad default, and a raw-input check here
|
|
166
|
+
// never sees it. Probe the default at emit time and decline when it does
|
|
167
|
+
// not hold, or when the subschema cannot be compiled to check it.
|
|
168
|
+
if (prop.default !== undefined) {
|
|
169
|
+
let probe = null;
|
|
170
|
+
try { probe = compileToJSCodegen(prop, null, null); } catch (_) { probe = null; }
|
|
171
|
+
if (!probe || !probe(prop.default)) return null;
|
|
172
|
+
}
|
|
173
|
+
const read = `${access}[${JSON.stringify(key)}]`;
|
|
174
|
+
const value = emitValueExpr(prop, read, depth);
|
|
175
|
+
if (value === null) return null;
|
|
176
|
+
// A plain `"__proto__":` in an object literal is the prototype slot, not
|
|
177
|
+
// a property; `["__proto__"]:` is a property. Only that key is written as
|
|
178
|
+
// a computed one: a literal with computed keys cannot use the engine's
|
|
179
|
+
// literal boilerplate and is built key by key, which made the copy cost
|
|
180
|
+
// about as much as the validation in front of it.
|
|
181
|
+
if (required.has(key)) fixed.push(key === '__proto__' ? `[${JSON.stringify(key)}]: ${value}` : `${JSON.stringify(key)}: ${value}`);
|
|
182
|
+
else conditional.push({ key, value, dflt: prop.default });
|
|
183
|
+
}
|
|
184
|
+
if (fixed.length === 0 && conditional.length === 0 && !apSchema) return null;
|
|
185
|
+
|
|
186
|
+
const tmp = `_c${depth}`;
|
|
187
|
+
// The record loop: every key of the input that is not a declared property
|
|
188
|
+
// is kept, its value rebuilt against the additionalProperties schema. The
|
|
189
|
+
// input is already validated, so every such value satisfies that schema;
|
|
190
|
+
// the rebuild only strips inside it. Defaults under a record value are
|
|
191
|
+
// dropped before emission because the runtime's useDefaults does not fill
|
|
192
|
+
// them there, and parse() must equal validate().data, not improve on it.
|
|
193
|
+
let recordLoop = '';
|
|
194
|
+
if (apSchema) {
|
|
195
|
+
const kVar = `_k${depth}`;
|
|
196
|
+
const vVar = `_v${depth}`;
|
|
197
|
+
const valueExpr = emitValueExpr(stripDefaultsDeep(apSchema), vVar, depth);
|
|
198
|
+
if (valueExpr === null) return null;
|
|
199
|
+
let skip = '';
|
|
200
|
+
let declSet = '';
|
|
201
|
+
if (keys.length > 8) {
|
|
202
|
+
declSet = `const _d${depth} = new Set(${JSON.stringify(keys)}); `;
|
|
203
|
+
skip = `if (_d${depth}.has(${kVar})) continue; `;
|
|
204
|
+
} else if (keys.length > 0) {
|
|
205
|
+
skip = `if (${keys.map((k) => `${kVar} === ${JSON.stringify(k)}`).join(' || ')}) continue; `;
|
|
206
|
+
}
|
|
207
|
+
// The key comes from the input, so it can be the string "__proto__" (an
|
|
208
|
+
// own key only JSON.parse writes); assignment would hit the prototype
|
|
209
|
+
// setter, so that one key goes through defineProperty.
|
|
210
|
+
recordLoop = ` ${declSet}for (const ${kVar} of Object.keys(${access})) { ${skip}const ${vVar} = ${access}[${kVar}]; if (${kVar} === "__proto__") Object.defineProperty(${tmp}, ${kVar}, { value: ${valueExpr}, writable: true, enumerable: true, configurable: true }); else ${tmp}[${kVar}] = ${valueExpr}; }`;
|
|
211
|
+
}
|
|
212
|
+
|
|
213
|
+
const literal = `{ ${fixed.join(', ')} }`;
|
|
214
|
+
if (conditional.length === 0 && !recordLoop) return literal;
|
|
215
|
+
// Optional properties are added only when present, so the result never
|
|
216
|
+
// gains a key the input did not have.
|
|
217
|
+
const adds = conditional
|
|
218
|
+
.map(({ key, value, dflt }) => {
|
|
219
|
+
// Object.hasOwn, not `in`: `in` also answers for what the instance
|
|
220
|
+
// inherits, so a property named `constructor` would copy the inherited
|
|
221
|
+
// function into the output that validate().data does not carry. And a
|
|
222
|
+
// key named `__proto__` cannot be written by assignment, which hits
|
|
223
|
+
// the prototype setter; defineProperty writes an own key.
|
|
224
|
+
const assign = key === '__proto__'
|
|
225
|
+
? `Object.defineProperty(${tmp}, ${JSON.stringify(key)}, { value: ${value}, writable: true, enumerable: true, configurable: true });`
|
|
226
|
+
: `${tmp}[${JSON.stringify(key)}] = ${value};`;
|
|
227
|
+
// The same own-property test the generators emit, since Object.hasOwn
|
|
228
|
+
// is not inlined: `in`, then the prototype, then hasOwnProperty only
|
|
229
|
+
// for an object that is not an ordinary one.
|
|
230
|
+
const k = JSON.stringify(key);
|
|
231
|
+
const own = PROTO_KEY_NAMES.has(key)
|
|
232
|
+
? `Object.prototype.hasOwnProperty.call(${access}, ${k})`
|
|
233
|
+
: `(${k} in ${access} && (${access}.__proto__ === Object.prototype || Object.prototype.hasOwnProperty.call(${access}, ${k})))`;
|
|
234
|
+
const set = `if (${own}) ${assign}`;
|
|
235
|
+
// The default is emitted as a literal inside the call, so an object or
|
|
236
|
+
// array default is a fresh value on every parse, never shared state.
|
|
237
|
+
if (dflt === undefined) return set;
|
|
238
|
+
const setDflt = key === '__proto__'
|
|
239
|
+
? `Object.defineProperty(${tmp}, ${JSON.stringify(key)}, { value: ${JSON.stringify(dflt)}, writable: true, enumerable: true, configurable: true });`
|
|
240
|
+
: `${tmp}[${JSON.stringify(key)}] = ${JSON.stringify(dflt)};`;
|
|
241
|
+
return `${set} else ${setDflt}`;
|
|
242
|
+
})
|
|
243
|
+
.join(' ');
|
|
244
|
+
return `(function(){ const ${tmp} = ${literal}; ${adds}${recordLoop} return ${tmp}; })()`;
|
|
245
|
+
}
|
|
246
|
+
|
|
247
|
+
// The value dispatch shared by declared properties and the record loop. A
|
|
248
|
+
// value is copyable only when the emitter can name everything that may live
|
|
249
|
+
// under it: a primitive holds nothing, an object with declared properties or
|
|
250
|
+
// a record schema is rebuilt the same way, an array of primitives has
|
|
251
|
+
// nowhere to hide a key. A $ref, a composition or an untyped node could all
|
|
252
|
+
// carry keys this code cannot see, and copying the reference would smuggle
|
|
253
|
+
// them into a value that claims to be sanitised, so the caller declines the
|
|
254
|
+
// whole clone instead.
|
|
255
|
+
// Drop every schema-position `default` in a subtree, leaving data positions
|
|
256
|
+
// (const, enum, examples) untouched. Used on a record's value schema, where
|
|
257
|
+
// the runtime's useDefaults fills nothing.
|
|
258
|
+
function stripDefaultsDeep(node) {
|
|
259
|
+
if (!node || typeof node !== 'object') return node;
|
|
260
|
+
if (Array.isArray(node)) return node.map(stripDefaultsDeep);
|
|
261
|
+
const out = {};
|
|
262
|
+
for (const k of Object.keys(node)) {
|
|
263
|
+
if (k === 'default') continue;
|
|
264
|
+
out[k] = _INLINE_DATA_KEYS.has(k) ? node[k] : stripDefaultsDeep(node[k]);
|
|
265
|
+
}
|
|
266
|
+
return out;
|
|
267
|
+
}
|
|
268
|
+
|
|
269
|
+
const _CLONE_PRIMITIVE = new Set(['string', 'number', 'integer', 'boolean', 'null']);
|
|
270
|
+
function emitValueExpr(prop, read, depth) {
|
|
271
|
+
if (!prop || typeof prop !== 'object') return null;
|
|
272
|
+
if (prop.$ref) return null;
|
|
273
|
+
const types = Array.isArray(prop.type) ? prop.type : [prop.type];
|
|
274
|
+
if (types.every((t) => _CLONE_PRIMITIVE.has(t))) return read;
|
|
275
|
+
const isRecordable = (s) => s.properties ||
|
|
276
|
+
(s.additionalProperties && typeof s.additionalProperties === 'object');
|
|
277
|
+
if (prop.type === 'object' && isRecordable(prop)) {
|
|
278
|
+
return emitClone(prop, read, depth + 1);
|
|
279
|
+
}
|
|
280
|
+
if (prop.type === 'array' && prop.items && typeof prop.items === 'object' &&
|
|
281
|
+
!Array.isArray(prop.items)) {
|
|
282
|
+
const it = prop.items;
|
|
283
|
+
const itemTypes = Array.isArray(it.type) ? it.type : [it.type];
|
|
284
|
+
if (itemTypes.every((t) => _CLONE_PRIMITIVE.has(t))) return read;
|
|
285
|
+
if (it.type === 'object' && isRecordable(it)) {
|
|
286
|
+
const el = '_e' + depth;
|
|
287
|
+
const inner = emitClone(it, el, depth + 1);
|
|
288
|
+
if (!inner) return null;
|
|
289
|
+
return `${read}.map((${el}) => (${inner}))`;
|
|
290
|
+
}
|
|
291
|
+
}
|
|
292
|
+
return null;
|
|
293
|
+
}
|
|
294
|
+
|
|
295
|
+
// The copy as an expression over `data`, or null where the kept key set is
|
|
296
|
+
// not provable.
|
|
297
|
+
function cloneExprFor(schema) {
|
|
298
|
+
if (!schema || typeof schema !== 'object') return null;
|
|
299
|
+
return emitClone(inlineRefsForClone(schema), 'data', 0);
|
|
300
|
+
}
|
|
301
|
+
|
|
302
|
+
module.exports = { cloneExprFor, emitClone, inlineRefsForClone, stripDefaultsDeep, resolveLocalPointer };
|
package/lib/interpreter.js
CHANGED
|
@@ -230,7 +230,7 @@ function cpAtMost(s, max) {
|
|
|
230
230
|
function typeMatches(type, d) {
|
|
231
231
|
switch (type) {
|
|
232
232
|
case 'string': return typeof d === 'string';
|
|
233
|
-
case 'number': return
|
|
233
|
+
case 'number': return Number.isFinite(d);
|
|
234
234
|
case 'integer': return typeof d === 'number' && Number.isInteger(d);
|
|
235
235
|
case 'boolean': return typeof d === 'boolean';
|
|
236
236
|
case 'null': return d === null;
|
|
@@ -529,7 +529,7 @@ function evalLeafV(P, data) {
|
|
|
529
529
|
if (!found) return false;
|
|
530
530
|
}
|
|
531
531
|
if (P.hasConst && !deepEqual(P.const, data)) return false;
|
|
532
|
-
if (P.hasNumber &&
|
|
532
|
+
if (P.hasNumber && Number.isFinite(data)) {
|
|
533
533
|
if (P.minimum !== undefined && !(data >= P.minimum)) return false;
|
|
534
534
|
if (P.maximum !== undefined && !(data <= P.maximum)) return false;
|
|
535
535
|
if (P.exclusiveMinimum !== undefined && !(data > P.exclusiveMinimum)) return false;
|
|
@@ -646,7 +646,7 @@ function evalLeaf(P, data, errors, instancePath, schemaPath) {
|
|
|
646
646
|
if (errors !== NOERRORS) errors.push(err('const', 'const', instancePath, schemaPath + '/const', { allowedValue: P.const }, 'must be equal to constant'));
|
|
647
647
|
valid = false;
|
|
648
648
|
}
|
|
649
|
-
if (P.hasNumber &&
|
|
649
|
+
if (P.hasNumber && Number.isFinite(data)) {
|
|
650
650
|
if (P.minimum !== undefined && !(data >= P.minimum)) { errors === NOERRORS || errors.push(err('minimum', 'minimum', instancePath, schemaPath + '/minimum', { comparison: '>=', limit: P.minimum }, `must be >= ${P.minimum}`)); valid = false; }
|
|
651
651
|
if (P.maximum !== undefined && !(data <= P.maximum)) { errors === NOERRORS || errors.push(err('maximum', 'maximum', instancePath, schemaPath + '/maximum', { comparison: '<=', limit: P.maximum }, `must be <= ${P.maximum}`)); valid = false; }
|
|
652
652
|
if (P.exclusiveMinimum !== undefined && !(data > P.exclusiveMinimum)) { errors === NOERRORS || errors.push(err('exclusiveMinimum', 'exclusiveMinimum', instancePath, schemaPath + '/exclusiveMinimum', { comparison: '>', limit: P.exclusiveMinimum }, `must be > ${P.exclusiveMinimum}`)); valid = false; }
|
|
@@ -937,7 +937,7 @@ class Interpreter {
|
|
|
937
937
|
}
|
|
938
938
|
|
|
939
939
|
// numbers
|
|
940
|
-
if (P.hasNumber &&
|
|
940
|
+
if (P.hasNumber && Number.isFinite(data)) {
|
|
941
941
|
if (P.minimum !== undefined && !(data >= P.minimum)) { errors === NOERRORS || errors.push(err('minimum', 'minimum', instancePath, schemaPath + '/minimum', { comparison: '>=', limit: P.minimum }, `must be >= ${P.minimum}`)); valid = false; }
|
|
942
942
|
if (P.maximum !== undefined && !(data <= P.maximum)) { errors === NOERRORS || errors.push(err('maximum', 'maximum', instancePath, schemaPath + '/maximum', { comparison: '<=', limit: P.maximum }, `must be <= ${P.maximum}`)); valid = false; }
|
|
943
943
|
if (P.exclusiveMinimum !== undefined && !(data > P.exclusiveMinimum)) { errors === NOERRORS || errors.push(err('exclusiveMinimum', 'exclusiveMinimum', instancePath, schemaPath + '/exclusiveMinimum', { comparison: '>', limit: P.exclusiveMinimum }, `must be > ${P.exclusiveMinimum}`)); valid = false; }
|
package/lib/js-compiler.js
CHANGED
|
@@ -489,24 +489,24 @@ function compileToJS(schema, defs, schemaMap) {
|
|
|
489
489
|
// answer differently for it.
|
|
490
490
|
if (schema.minimum !== undefined) {
|
|
491
491
|
const min = schema.minimum
|
|
492
|
-
checks.push((d) =>
|
|
492
|
+
checks.push((d) => !Number.isFinite(d) || d >= min)
|
|
493
493
|
}
|
|
494
494
|
if (schema.maximum !== undefined) {
|
|
495
495
|
const max = schema.maximum
|
|
496
|
-
checks.push((d) =>
|
|
496
|
+
checks.push((d) => !Number.isFinite(d) || d <= max)
|
|
497
497
|
}
|
|
498
498
|
if (schema.exclusiveMinimum !== undefined) {
|
|
499
499
|
const min = schema.exclusiveMinimum
|
|
500
|
-
checks.push((d) =>
|
|
500
|
+
checks.push((d) => !Number.isFinite(d) || d > min)
|
|
501
501
|
}
|
|
502
502
|
if (schema.exclusiveMaximum !== undefined) {
|
|
503
503
|
const max = schema.exclusiveMaximum
|
|
504
|
-
checks.push((d) =>
|
|
504
|
+
checks.push((d) => !Number.isFinite(d) || d < max)
|
|
505
505
|
}
|
|
506
506
|
if (schema.multipleOf !== undefined) {
|
|
507
507
|
const div = schema.multipleOf
|
|
508
508
|
checks.push((d) => {
|
|
509
|
-
if (
|
|
509
|
+
if (!Number.isFinite(d)) return true
|
|
510
510
|
if (div === 0) return false
|
|
511
511
|
const q = d / div
|
|
512
512
|
return Number.isInteger(q) || Math.abs(q - Math.round(q)) < 1e-9
|
|
@@ -850,7 +850,7 @@ function multipleOfBad(v, m) {
|
|
|
850
850
|
|
|
851
851
|
const TYPE_CHECKS = {
|
|
852
852
|
string: (d) => typeof d === 'string',
|
|
853
|
-
number: (d) =>
|
|
853
|
+
number: (d) => Number.isFinite(d),
|
|
854
854
|
integer: (d) => Number.isInteger(d),
|
|
855
855
|
boolean: (d) => typeof d === 'boolean',
|
|
856
856
|
null: (d) => d === null,
|
|
@@ -918,8 +918,15 @@ const PROTO_NAMES = new Set([...Object.getOwnPropertyNames(Object.prototype), '_
|
|
|
918
918
|
// and reading the errors of a product document went from 3.9 to 5.8 us. The
|
|
919
919
|
// helper only runs once `in` has said yes, and on an ordinary object it
|
|
920
920
|
// answers from the prototype without looking at the key.
|
|
921
|
-
|
|
922
|
-
|
|
921
|
+
//
|
|
922
|
+
// `__proto__` is read through the accessor on Object.prototype, which Deno
|
|
923
|
+
// removes. There every read came back undefined, every object took the per-key
|
|
924
|
+
// hasOwnProperty path, and a ten-field check went from 2.5 to 40 ns. Where the
|
|
925
|
+
// accessor is missing the prototype comes from Object.getPrototypeOf instead,
|
|
926
|
+
// chosen by a constant the engine folds, so Node pays nothing for the branch.
|
|
927
|
+
const OWN_HELPERS = 'const _PN=({}).__proto__===Object.prototype;' +
|
|
928
|
+
'const _hop=Object.prototype.hasOwnProperty;function _h(o,k){return _hop.call(o,k)}' +
|
|
929
|
+
'function _ok(o,k){return (_PN?o.__proto__:Object.getPrototypeOf(o))===Object.prototype||_hop.call(o,k)}' +
|
|
923
930
|
'function _hall(o,ks){for(let i=0;i<ks.length;i++)if(!_hop.call(o,ks[i]))return false;return true}'
|
|
924
931
|
//
|
|
925
932
|
// Better still, the prototype is read once per object: a node that asks about
|
|
@@ -928,6 +935,11 @@ const OWN_HELPERS = 'const _hop=Object.prototype.hasOwnProperty;function _h(o,k)
|
|
|
928
935
|
// helper reading `__proto__` for each key was megamorphic, since it saw every
|
|
929
936
|
// object shape in the document, and cost the error path about a microsecond
|
|
930
937
|
// on a product document.
|
|
938
|
+
// Whether `v`, already known to be an object, has Object.prototype as its
|
|
939
|
+
// prototype, read without depending on the __proto__ accessor. Needs OWN_HELPERS.
|
|
940
|
+
function protoIs (v) {
|
|
941
|
+
return `((_PN?${v}.__proto__:Object.getPrototypeOf(${v}))===Object.prototype)`
|
|
942
|
+
}
|
|
931
943
|
function ownKeyExpr (ctx, v, key) {
|
|
932
944
|
hoistOnce(ctx, '_ownHoisted', OWN_HELPERS)
|
|
933
945
|
const k = JSON.stringify(key)
|
|
@@ -957,7 +969,8 @@ function withPlain (schema, v, lines, ctx, run) {
|
|
|
957
969
|
if (!ctx.plainOf) ctx.plainOf = new Map()
|
|
958
970
|
const name = '_pk' + ctx.varCounter++
|
|
959
971
|
const at = lines.length
|
|
960
|
-
|
|
972
|
+
const decl = `const ${name}=typeof ${v}==='object'&&${v}!==null&&${protoIs(v)}`
|
|
973
|
+
lines.push(decl)
|
|
961
974
|
const prev = ctx.plainOf.get(v)
|
|
962
975
|
ctx.plainOf.set(v, name)
|
|
963
976
|
try {
|
|
@@ -970,14 +983,15 @@ function withPlain (schema, v, lines, ctx, run) {
|
|
|
970
983
|
const re = new RegExp('\\b' + name + '\\b')
|
|
971
984
|
let used = false
|
|
972
985
|
for (let i = at + 1; i < lines.length && !used; i++) if (re.test(lines[i])) used = true
|
|
973
|
-
if (!used && lines[at] ===
|
|
986
|
+
if (!used && lines[at] === decl) lines.splice(at, 1)
|
|
974
987
|
}
|
|
975
988
|
}
|
|
976
989
|
// The same test at run time, for the closure compiler.
|
|
977
990
|
const _hop = Object.prototype.hasOwnProperty
|
|
991
|
+
const _PN = ({}).__proto__ === Object.prototype
|
|
978
992
|
function hasOwnKey (d, key) {
|
|
979
993
|
if (PROTO_NAMES.has(key)) return _hop.call(d, key)
|
|
980
|
-
return key in d && (d.__proto__ === Object.prototype || _hop.call(d, key))
|
|
994
|
+
return key in d && ((_PN ? d.__proto__ : Object.getPrototypeOf(d)) === Object.prototype || _hop.call(d, key))
|
|
981
995
|
}
|
|
982
996
|
|
|
983
997
|
const UNSAFE_KEYS = new Set(['__proto__', 'constructor', 'toString', 'valueOf',
|
|
@@ -2005,7 +2019,7 @@ function tryGenCombined(schema, access, ctx) {
|
|
|
2005
2019
|
}
|
|
2006
2020
|
|
|
2007
2021
|
if (t === 'number') {
|
|
2008
|
-
const conds = [
|
|
2022
|
+
const conds = [`!Number.isFinite(_v)`]
|
|
2009
2023
|
if (schema.minimum !== undefined) conds.push(`_v<${schema.minimum}`)
|
|
2010
2024
|
if (schema.maximum !== undefined) conds.push(`_v>${schema.maximum}`)
|
|
2011
2025
|
if (schema.exclusiveMinimum !== undefined) conds.push(`_v<=${schema.exclusiveMinimum}`)
|
|
@@ -2190,7 +2204,7 @@ function genCodeNode(schema, v, lines, ctx, knownType) {
|
|
|
2190
2204
|
case 'object': lines.push(`if(typeof ${v}!=='object'||${v}===null||Array.isArray(${v}))return false`); break
|
|
2191
2205
|
case 'array': lines.push(`if(!Array.isArray(${v}))return false`); break
|
|
2192
2206
|
case 'string': lines.push(`if(typeof ${v}!=='string')return false`); break
|
|
2193
|
-
case 'number': lines.push(`if(
|
|
2207
|
+
case 'number': lines.push(`if(!Number.isFinite(${v}))return false`); break
|
|
2194
2208
|
case 'integer': lines.push(`if(!Number.isInteger(${v}))return false`); break
|
|
2195
2209
|
case 'boolean': lines.push(`if(typeof ${v}!=='boolean')return false`); break
|
|
2196
2210
|
case 'null': lines.push(`if(${v}!==null)return false`); break
|
|
@@ -2201,7 +2215,7 @@ function genCodeNode(schema, v, lines, ctx, knownType) {
|
|
|
2201
2215
|
case 'object': return `(typeof ${v}==='object'&&${v}!==null&&!Array.isArray(${v}))`
|
|
2202
2216
|
case 'array': return `Array.isArray(${v})`
|
|
2203
2217
|
case 'string': return `typeof ${v}==='string'`
|
|
2204
|
-
case 'number': return `
|
|
2218
|
+
case 'number': return `Number.isFinite(${v})`
|
|
2205
2219
|
case 'integer': return `Number.isInteger(${v})`
|
|
2206
2220
|
case 'boolean': return `typeof ${v}==='boolean'`
|
|
2207
2221
|
case 'null': return `${v}===null`
|
|
@@ -2270,7 +2284,7 @@ function genCodeNode(schema, v, lines, ctx, knownType) {
|
|
|
2270
2284
|
// Read directly: the object type is already established here, and a
|
|
2271
2285
|
// flag declared for this alone costs more than it saves. The list is
|
|
2272
2286
|
// built only on that path, which an ordinary object never takes.
|
|
2273
|
-
lines.push(`if(
|
|
2287
|
+
lines.push(`if(!${protoIs(v)}&&!_hall(${v},${JSON.stringify(typed)}))return false`)
|
|
2274
2288
|
}
|
|
2275
2289
|
} else if (schema.required && schema.required.length > 0) {
|
|
2276
2290
|
const checks = schema.required.map(key => `!${ownKeyExpr(ctx, v, key)}`)
|
|
@@ -2308,7 +2322,7 @@ function genCodeNode(schema, v, lines, ctx, knownType) {
|
|
|
2308
2322
|
// non-finite value through: it carries no type, an empty schema accepts it,
|
|
2309
2323
|
// and the interpreted engine reads it the same way. With `type: number` the
|
|
2310
2324
|
// type check has already turned it away, so the guard is only needed here.
|
|
2311
|
-
const numGuard = isNum ? '' : `
|
|
2325
|
+
const numGuard = isNum ? '' : `Number.isFinite(${v})&&`
|
|
2312
2326
|
if (schema.minimum !== undefined) lines.push(`if(${numGuard}${v}<${schema.minimum})return false`)
|
|
2313
2327
|
if (schema.maximum !== undefined) lines.push(`if(${numGuard}${v}>${schema.maximum})return false`)
|
|
2314
2328
|
if (schema.exclusiveMinimum !== undefined) lines.push(`if(${numGuard}${v}<=${schema.exclusiveMinimum})return false`)
|
|
@@ -3973,7 +3987,7 @@ function genCodeENode(schema, v, pathExpr, lines, ctx, schemaPrefix) {
|
|
|
3973
3987
|
case 'object': return `(typeof ${v}==='object'&&${v}!==null&&!Array.isArray(${v}))`
|
|
3974
3988
|
case 'array': return `Array.isArray(${v})`
|
|
3975
3989
|
case 'string': return `typeof ${v}==='string'`
|
|
3976
|
-
case 'number': return `
|
|
3990
|
+
case 'number': return `Number.isFinite(${v})`
|
|
3977
3991
|
case 'integer': return `Number.isInteger(${v})`
|
|
3978
3992
|
case 'boolean': return `typeof ${v}==='boolean'`
|
|
3979
3993
|
case 'null': return `${v}===null`
|
|
@@ -4710,7 +4724,7 @@ function genCodeCNode(schema, v, pathExpr, lines, ctx, schemaPrefix) {
|
|
|
4710
4724
|
case 'object': return `(typeof ${v}==='object'&&${v}!==null&&!Array.isArray(${v}))`
|
|
4711
4725
|
case 'array': return `Array.isArray(${v})`
|
|
4712
4726
|
case 'string': return `typeof ${v}==='string'`
|
|
4713
|
-
case 'number': return `
|
|
4727
|
+
case 'number': return `Number.isFinite(${v})`
|
|
4714
4728
|
case 'integer': return `Number.isInteger(${v})`
|
|
4715
4729
|
case 'boolean': return `typeof ${v}==='boolean'`
|
|
4716
4730
|
case 'null': return `${v}===null`
|
|
@@ -4906,6 +4920,21 @@ function genCodeCNode(schema, v, pathExpr, lines, ctx, schemaPrefix) {
|
|
|
4906
4920
|
const allowed = propKeys.map(k => JSON.stringify(k)).join(',')
|
|
4907
4921
|
body = `const _a${ci}=new Set([${allowed}]);let _x${ci}=0;for(const _f${ci} in ${v}){if(!_a${ci}.has(_f${ci})){_x${ci}=1;break}}if(_x${ci}){${detail(`!_a${ci}.has(_k${ci}[_i])`)}}`
|
|
4908
4922
|
}
|
|
4923
|
+
// When every declared property is required, a clean object has exactly the
|
|
4924
|
+
// declared keys: if the enumerated count equals the declared count and
|
|
4925
|
+
// every declared name is there, the enumerated set is the declared set and
|
|
4926
|
+
// nothing is extra. That is the verdict generator's count, and it skips
|
|
4927
|
+
// comparing each key against every name, which kept this function over
|
|
4928
|
+
// twice the verdict's cost on an accepted document. Anything else, a
|
|
4929
|
+
// different count or a missing name, falls through to the scan, which
|
|
4930
|
+
// names the extra keys. A name Object.prototype carries answers `in`
|
|
4931
|
+
// through the prototype without being enumerated, so those stay on the
|
|
4932
|
+
// scan.
|
|
4933
|
+
const req = new Set(Array.isArray(schema.required) ? schema.required : [])
|
|
4934
|
+
if (propKeys.length > 0 && propKeys.every((k) => req.has(k) && !PROTO_NAMES.has(k))) {
|
|
4935
|
+
const present = propKeys.map((k) => `${JSON.stringify(k)} in ${v}`).join('&&')
|
|
4936
|
+
body = `let _n${ci}=0;for(const _f${ci} in ${v})_n${ci}++;if(_n${ci}!==${propKeys.length}||!(${present})){${body}}`
|
|
4937
|
+
}
|
|
4909
4938
|
lines.push(isObj
|
|
4910
4939
|
? `{${body}}`
|
|
4911
4940
|
: `if(typeof ${v}==='object'&&${v}!==null&&!Array.isArray(${v})){${body}}`)
|
package/lib/plan-compiler.js
CHANGED
|
@@ -182,7 +182,7 @@ function install(deps) {
|
|
|
182
182
|
// no type bit, and an empty schema accepts it. So the numeric keywords
|
|
183
183
|
// must not apply to it either, or `{ minimum: 3 }` rejects a value that
|
|
184
184
|
// `{}` accepts and the compiled engine admits.
|
|
185
|
-
if (run !== null) checks.push((data) =>
|
|
185
|
+
if (run !== null) checks.push((data) => !Number.isFinite(data) || run(data));
|
|
186
186
|
}
|
|
187
187
|
if (P.hasString) {
|
|
188
188
|
const strs = [];
|
package/lib/plan-source.js
CHANGED
|
@@ -28,7 +28,7 @@ const HELPERS = 'const _hop=Object.prototype.hasOwnProperty;';
|
|
|
28
28
|
const PROTO_NAMES = new Set([...Object.getOwnPropertyNames(Object.prototype), '__proto__']);
|
|
29
29
|
|
|
30
30
|
function isPrimitive(x) {
|
|
31
|
-
return x === null || typeof x === 'string' || typeof x === 'boolean' ||
|
|
31
|
+
return x === null || typeof x === 'string' || typeof x === 'boolean' || Number.isFinite(x);
|
|
32
32
|
}
|
|
33
33
|
|
|
34
34
|
function lit(x) {
|
|
@@ -58,7 +58,7 @@ function local(ctx) {
|
|
|
58
58
|
function typeTest(mask, v) {
|
|
59
59
|
const parts = [];
|
|
60
60
|
if (mask & T_STRING) parts.push(`typeof ${v}==='string'`);
|
|
61
|
-
if (mask & T_NUMBER) parts.push(`
|
|
61
|
+
if (mask & T_NUMBER) parts.push(`Number.isFinite(${v})`);
|
|
62
62
|
else if (mask & T_INTEGER) parts.push(`Number.isInteger(${v})`);
|
|
63
63
|
if (mask & T_BOOLEAN) parts.push(`typeof ${v}==='boolean'`);
|
|
64
64
|
if (mask & T_NULL) parts.push(`${v}===null`);
|
|
@@ -114,7 +114,7 @@ function emitLeaf(ctx, P, v, known) {
|
|
|
114
114
|
if (P.exclusiveMinimum !== undefined) n += `if(${v}<=${lit(P.exclusiveMinimum)})return false;`;
|
|
115
115
|
if (P.exclusiveMaximum !== undefined) n += `if(${v}>=${lit(P.exclusiveMaximum)})return false;`;
|
|
116
116
|
if (P.multipleOf !== undefined) n += `if(!${arg(ctx, multipleOfOk)}(${v},${lit(P.multipleOf)}))return false;`;
|
|
117
|
-
out += guarded(known, K_NUM, `
|
|
117
|
+
out += guarded(known, K_NUM, `Number.isFinite(${v})`, n);
|
|
118
118
|
}
|
|
119
119
|
if (P.hasString) {
|
|
120
120
|
let s = '';
|
package/lib/tier0.js
CHANGED
|
@@ -46,6 +46,8 @@ const F_MULT = 16;
|
|
|
46
46
|
// itself; see ownKeyExpr in js-compiler.js.
|
|
47
47
|
const PROTO_NAMES = new Set([...Object.getOwnPropertyNames(Object.prototype), '__proto__']);
|
|
48
48
|
const _hop = Object.prototype.hasOwnProperty;
|
|
49
|
+
// Deno removes the __proto__ accessor; read the prototype another way there.
|
|
50
|
+
const _PN = ({}).__proto__ === Object.prototype;
|
|
49
51
|
|
|
50
52
|
function primConstraint(key, propSchema) {
|
|
51
53
|
const t = propSchema.type;
|
|
@@ -137,7 +139,7 @@ function checkPrimitive(c, v) {
|
|
|
137
139
|
if ((f & F_MULT) && !_multipleOk(v, c.multipleOf)) return false;
|
|
138
140
|
}
|
|
139
141
|
} else if (m === T_NUMBER) {
|
|
140
|
-
if (
|
|
142
|
+
if (!Number.isFinite(v)) return false;
|
|
141
143
|
const f = c.numFlags;
|
|
142
144
|
if (f !== 0) {
|
|
143
145
|
if ((f & F_MIN) && v < c.min) return false;
|
|
@@ -169,7 +171,7 @@ function tier0ValidateObject(plan, data) {
|
|
|
169
171
|
let v = data[c.key];
|
|
170
172
|
// Presence is an own-property question: a value the object inherits is
|
|
171
173
|
// treated as absent, as the interpreted engine treats it.
|
|
172
|
-
if (v !== undefined && (c.protoName || data.__proto__ !== Object.prototype) && !_hop.call(data, c.key)) v = undefined;
|
|
174
|
+
if (v !== undefined && (c.protoName || (_PN ? data.__proto__ : Object.getPrototypeOf(data)) !== Object.prototype) && !_hop.call(data, c.key)) v = undefined;
|
|
173
175
|
if (v === undefined) {
|
|
174
176
|
if (reqMask & (1 << i)) return false;
|
|
175
177
|
continue;
|
|
@@ -194,7 +196,7 @@ function tier0ValidateObject(plan, data) {
|
|
|
194
196
|
if ((f & F_MULT) && !_multipleOk(v, c.multipleOf)) return false;
|
|
195
197
|
}
|
|
196
198
|
} else if (m === T_NUMBER) {
|
|
197
|
-
if (
|
|
199
|
+
if (!Number.isFinite(v)) return false;
|
|
198
200
|
const f = c.numFlags;
|
|
199
201
|
if (f !== 0) {
|
|
200
202
|
if ((f & F_MIN) && v < c.min) return 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.32.1",
|
|
4
4
|
"description": "JSON Schema validation that compiles for speed and still runs where code generation is blocked. Compiled and interpreted engines answer identically at 100% of the official suite. TypeScript inference, Standard Schema V1, and a build step that emits dependency-free modules.",
|
|
5
5
|
"main": "index.js",
|
|
6
6
|
"module": "index.mjs",
|
|
@@ -51,7 +51,7 @@
|
|
|
51
51
|
"release:check": "node scripts/regen-safe-regex-source.js && node tests/test_pack_purity.js && node scripts/check-doc-coverage.js && node tests/test_error_codes_lock.js && node tests/test_safe_regex_source_sync.js && node tests/test_version_sync.js",
|
|
52
52
|
"build": "cmake-js build --target ata",
|
|
53
53
|
"rebuild": "cmake-js rebuild --target ata",
|
|
54
|
-
"test": "node test.js && node tests/test_removed_aot_methods.js && node tests/test_no_native.js && node tests/test_no_eval.js && node tests/test_property_dependencies.js && node tests/test_v1_dialect.js && node tests/test_buffer_path_parity.js && node tests/test_buffer_gate.js && node tests/test_scanner_differential.js && node tests/test_validate_json_scanner_cost.js && node tests/test_buffer_reject_cost.js && node tests/test_nan_verdict.js && node tests/test_remove_additional_nested.js && node tests/test_exclusive_bounds.js && node tests/test_engine_differential.js && node tests/test_error_shape_differential.js && node tests/test_output_format.js && node tests/test_retry_message.js && node tests/test_describe_schema.js && node tests/test_aot_parse.js && node tests/test_draft7_semantics.js && node tests/test_metaschema_ref.js && node tests/test_pure_js_unsupported.js && node tests/test_native_load_order.js && node tests/test_pack_purity.js && node tests/test_make_native_package.js && node tests/test_browser_nofs.js && node tests/test_browser_imports_guard.js && node tests/test_version_sync.js && node tests/test_native_loaded.js && node tests/test_safe_regex_source_sync.js && node tests/test_t_builder.js && node tests/test_async_refine.js && node tests/test_safe_regex.js && node tests/test_safe_regex_integration.js && node tests/test_aot_build.js && node tests/test_aot_differential.js && node tests/test_aot_cli_build.js && node tests/test_aot_cli_smoke.js && node tests/test_bundle_standalone.js && node tests/test_standalone_anyof.js && node tests/test_standalone_formats.js && node tests/test_aot_format_mode.js && node tests/test_aot_additional_props_errors.js && node tests/test_id_anchor_refs.js && node tests/test_engine_routing.js && node tests/test_engine_option.js && node tests/test_compile_cache_order.js && node tests/test_engine_diagnostic.js && node tests/test_format_engine_parity.js && node tests/test_error_accumulator_collision.js && node tests/test_uri_helper_parity.js && node tests/test_uri_fast_path.js && node tests/test_extend_verdict.js && node tests/test_ipv4_regex.js && node tests/test_ipv6_fast_path.js && node tests/test_nested_recursion_decline.js && node tests/test_generator_error_content.js && node tests/test_inherited_key_presence.js && node tests/test_plan_source.js && node tests/test_fused_remove_additional.js && node tests/test_formats_single_pass.js && node tests/test_email_format_cost.js && node tests/test_format_single_error.js && node tests/test_defs_pointer_alias.js && node tests/test_cross_doc_root_ref.js && node tests/test_codegen_entrypoint_agreement.js && node tests/test_hybrid_agreement.js && node tests/test_codegen_edge_shapes.js && node tests/test_deferred_def_scope.js && node tests/test_node_scoped_ctx_flags.js && node tests/test_fast_slot_exhaustion.js && node tests/test_ajv_errors.js && node tests/test_custom_keywords.js && node tests/test_strict_schema.js && node tests/test_aot_external_checks.js && node tests/test_ajv_parity.js && node tests/test_user_format_error_path.js && node tests/test_unevaluated_error_path.js && node tests/test_unevaluated_error_shape.js && node tests/test_pattern_properties_errors.js && node tests/test_additional_properties_scaling.js && node tests/test_additional_props_combined_cost.js && node tests/test_no_input_mutation.js && node tests/test_typed_validator_runner.js && node tests/test_define_schema.js && node tests/test_error_codes_lock.js && node tests/test_error_code_lookup.js && node tests/test_error_order.js && node tests/test_error_order_ordinal.js && node tests/test_rejection_shape.js && node tests/test_lazy_normalization.js && node tests/test_value_equality.js && node tests/test_vocabulary.js && node tests/test_schema_scan.js && node tests/test_lazy_errors.js && node tests/test_single_pass_errors.js && node tests/test_lazy_json_errors.js && node tests/test_lazy_instance.js && node tests/test_cyclic_input.js && node tests/test_native_error_codes.js && node tests/test_verdict_preprocess.js && node tests/test_defaults_own_keys.js && node tests/test_plan_compiler.js && node tests/test_nullable.js && node tests/test_validate_and_parse.js && node tests/test_validate_data.js && node tests/test_enrich_error.js && node tests/test_enrich_received.js && node tests/test_rich_errors_optout.js && node tests/test_error_messages.js && node tests/test_source_positions.js && node tests/fuzz_positions.js && node tests/test_malformed_json_termination.js && node tests/test_data_positions.js && node tests/test_targeted_positions.js && node tests/test_position_map_cost.js && node tests/test_targeted_positions_cost.js && node tests/test_aot_staleness.js && node tests/test_file_positions.js && node tests/test_aot_positions_cost.js && node tests/test_schema_hash.js && node tests/test_render_shared.js && node tests/test_renderers.js && node tests/test_additive_fields.js && node tests/test_diagnostic_source.js && node tests/test_diagnose.js && node tests/test_correlate.js && node tests/test_diagnostics_score.js && node tests/test_runtime_error_dx.js && node tests/test_aot_error_dx.js && node tests/test_abort_early.js && node tests/test_branch_collapse.js && node tests/test_suggestions.js && node tests/test_cli_validate.js && node tests/test_cli_version.js && node benchmark/bench_aot_size.mjs",
|
|
54
|
+
"test": "node test.js && node tests/test_removed_aot_methods.js && node tests/test_no_native.js && node tests/test_no_eval.js && node tests/test_property_dependencies.js && node tests/test_v1_dialect.js && node tests/test_buffer_path_parity.js && node tests/test_buffer_gate.js && node tests/test_scanner_differential.js && node tests/test_validate_json_scanner_cost.js && node tests/test_buffer_reject_cost.js && node tests/test_nan_verdict.js && node tests/test_remove_additional_nested.js && node tests/test_exclusive_bounds.js && node tests/test_engine_differential.js && node tests/test_error_shape_differential.js && node tests/test_output_format.js && node tests/test_retry_message.js && node tests/test_describe_schema.js && node tests/test_aot_parse.js && node tests/test_draft7_semantics.js && node tests/test_metaschema_ref.js && node tests/test_pure_js_unsupported.js && node tests/test_native_load_order.js && node tests/test_pack_purity.js && node tests/test_make_native_package.js && node tests/test_browser_nofs.js && node tests/test_browser_imports_guard.js && node tests/test_version_sync.js && node tests/test_native_loaded.js && node tests/test_safe_regex_source_sync.js && node tests/test_t_builder.js && node tests/test_async_refine.js && node tests/test_safe_regex.js && node tests/test_safe_regex_integration.js && node tests/test_aot_build.js && node tests/test_aot_differential.js && node tests/test_aot_cli_build.js && node tests/test_aot_cli_smoke.js && node tests/test_bundle_standalone.js && node tests/test_standalone_anyof.js && node tests/test_standalone_formats.js && node tests/test_aot_format_mode.js && node tests/test_aot_additional_props_errors.js && node tests/test_id_anchor_refs.js && node tests/test_engine_routing.js && node tests/test_engine_option.js && node tests/test_compile_cache_order.js && node tests/test_engine_diagnostic.js && node tests/test_format_engine_parity.js && node tests/test_error_accumulator_collision.js && node tests/test_uri_helper_parity.js && node tests/test_uri_fast_path.js && node tests/test_extend_verdict.js && node tests/test_ipv4_regex.js && node tests/test_ipv6_fast_path.js && node tests/test_nested_recursion_decline.js && node tests/test_generator_error_content.js && node tests/test_inherited_key_presence.js && node tests/test_proto_accessor.js && node tests/test_plan_source.js && node tests/test_fused_remove_additional.js && node tests/test_runtime_parse.js && node tests/test_formats_single_pass.js && node tests/test_email_format_cost.js && node tests/test_format_single_error.js && node tests/test_defs_pointer_alias.js && node tests/test_cross_doc_root_ref.js && node tests/test_codegen_entrypoint_agreement.js && node tests/test_hybrid_agreement.js && node tests/test_codegen_edge_shapes.js && node tests/test_deferred_def_scope.js && node tests/test_node_scoped_ctx_flags.js && node tests/test_fast_slot_exhaustion.js && node tests/test_ajv_errors.js && node tests/test_custom_keywords.js && node tests/test_strict_schema.js && node tests/test_aot_external_checks.js && node tests/test_ajv_parity.js && node tests/test_user_format_error_path.js && node tests/test_unevaluated_error_path.js && node tests/test_unevaluated_error_shape.js && node tests/test_pattern_properties_errors.js && node tests/test_additional_properties_scaling.js && node tests/test_additional_props_combined_cost.js && node tests/test_no_input_mutation.js && node tests/test_typed_validator_runner.js && node tests/test_define_schema.js && node tests/test_error_codes_lock.js && node tests/test_error_code_lookup.js && node tests/test_error_order.js && node tests/test_error_order_ordinal.js && node tests/test_rejection_shape.js && node tests/test_lazy_normalization.js && node tests/test_value_equality.js && node tests/test_vocabulary.js && node tests/test_schema_scan.js && node tests/test_lazy_errors.js && node tests/test_single_pass_errors.js && node tests/test_lazy_json_errors.js && node tests/test_lazy_instance.js && node tests/test_cyclic_input.js && node tests/test_native_error_codes.js && node tests/test_verdict_preprocess.js && node tests/test_defaults_own_keys.js && node tests/test_plan_compiler.js && node tests/test_nullable.js && node tests/test_validate_and_parse.js && node tests/test_validate_data.js && node tests/test_enrich_error.js && node tests/test_enrich_received.js && node tests/test_rich_errors_optout.js && node tests/test_error_messages.js && node tests/test_source_positions.js && node tests/fuzz_positions.js && node tests/test_malformed_json_termination.js && node tests/test_data_positions.js && node tests/test_targeted_positions.js && node tests/test_position_map_cost.js && node tests/test_targeted_positions_cost.js && node tests/test_aot_staleness.js && node tests/test_file_positions.js && node tests/test_aot_positions_cost.js && node tests/test_schema_hash.js && node tests/test_render_shared.js && node tests/test_renderers.js && node tests/test_additive_fields.js && node tests/test_diagnostic_source.js && node tests/test_diagnose.js && node tests/test_correlate.js && node tests/test_diagnostics_score.js && node tests/test_runtime_error_dx.js && node tests/test_aot_error_dx.js && node tests/test_abort_early.js && node tests/test_branch_collapse.js && node tests/test_suggestions.js && node tests/test_cli_validate.js && node tests/test_cli_version.js && node benchmark/bench_aot_size.mjs",
|
|
55
55
|
"bench:size": "node benchmark/bench_aot_size.mjs",
|
|
56
56
|
"test:suite": "node tests/run_suite.js && node tests/run_suite.js draft7 && node tests/run_suite.js v1",
|
|
57
57
|
"test:compat": "node tests/test_compat.js",
|
|
@@ -121,13 +121,13 @@
|
|
|
121
121
|
"LICENSE"
|
|
122
122
|
],
|
|
123
123
|
"optionalDependencies": {
|
|
124
|
-
"@ata-validator/native-darwin-arm64": "1.
|
|
125
|
-
"@ata-validator/native-darwin-x64": "1.
|
|
126
|
-
"@ata-validator/native-linux-arm64-gnu": "1.
|
|
127
|
-
"@ata-validator/native-linux-arm64-musl": "1.
|
|
128
|
-
"@ata-validator/native-linux-x64-gnu": "1.
|
|
129
|
-
"@ata-validator/native-linux-x64-musl": "1.
|
|
130
|
-
"@ata-validator/native-win32-x64": "1.
|
|
124
|
+
"@ata-validator/native-darwin-arm64": "1.32.1",
|
|
125
|
+
"@ata-validator/native-darwin-x64": "1.32.1",
|
|
126
|
+
"@ata-validator/native-linux-arm64-gnu": "1.32.1",
|
|
127
|
+
"@ata-validator/native-linux-arm64-musl": "1.32.1",
|
|
128
|
+
"@ata-validator/native-linux-x64-gnu": "1.32.1",
|
|
129
|
+
"@ata-validator/native-linux-x64-musl": "1.32.1",
|
|
130
|
+
"@ata-validator/native-win32-x64": "1.32.1"
|
|
131
131
|
},
|
|
132
132
|
"peerDependencies": {
|
|
133
133
|
"yaml": "^2.0.0"
|