ata-validator 1.31.3 → 1.32.0

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
package/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
- // Emit an expression that rebuilds `access` from the properties the schema
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 };
@@ -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 typeof d === 'number' && isFinite(d);
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 && typeof data === 'number' && isFinite(data)) {
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 && typeof data === 'number' && isFinite(data)) {
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 && typeof data === 'number' && isFinite(data)) {
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; }
@@ -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) => typeof d !== 'number' || !isFinite(d) || d >= min)
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) => typeof d !== 'number' || !isFinite(d) || d <= max)
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) => typeof d !== 'number' || !isFinite(d) || d > min)
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) => typeof d !== 'number' || !isFinite(d) || d < max)
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 (typeof d !== 'number' || !isFinite(d)) return true
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) => typeof d === 'number' && isFinite(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,
@@ -2005,7 +2005,7 @@ function tryGenCombined(schema, access, ctx) {
2005
2005
  }
2006
2006
 
2007
2007
  if (t === 'number') {
2008
- const conds = [`typeof _v!=='number'||!isFinite(_v)`]
2008
+ const conds = [`!Number.isFinite(_v)`]
2009
2009
  if (schema.minimum !== undefined) conds.push(`_v<${schema.minimum}`)
2010
2010
  if (schema.maximum !== undefined) conds.push(`_v>${schema.maximum}`)
2011
2011
  if (schema.exclusiveMinimum !== undefined) conds.push(`_v<=${schema.exclusiveMinimum}`)
@@ -2190,7 +2190,7 @@ function genCodeNode(schema, v, lines, ctx, knownType) {
2190
2190
  case 'object': lines.push(`if(typeof ${v}!=='object'||${v}===null||Array.isArray(${v}))return false`); break
2191
2191
  case 'array': lines.push(`if(!Array.isArray(${v}))return false`); break
2192
2192
  case 'string': lines.push(`if(typeof ${v}!=='string')return false`); break
2193
- case 'number': lines.push(`if(typeof ${v}!=='number'||!isFinite(${v}))return false`); break
2193
+ case 'number': lines.push(`if(!Number.isFinite(${v}))return false`); break
2194
2194
  case 'integer': lines.push(`if(!Number.isInteger(${v}))return false`); break
2195
2195
  case 'boolean': lines.push(`if(typeof ${v}!=='boolean')return false`); break
2196
2196
  case 'null': lines.push(`if(${v}!==null)return false`); break
@@ -2201,7 +2201,7 @@ function genCodeNode(schema, v, lines, ctx, knownType) {
2201
2201
  case 'object': return `(typeof ${v}==='object'&&${v}!==null&&!Array.isArray(${v}))`
2202
2202
  case 'array': return `Array.isArray(${v})`
2203
2203
  case 'string': return `typeof ${v}==='string'`
2204
- case 'number': return `(typeof ${v}==='number'&&isFinite(${v}))`
2204
+ case 'number': return `Number.isFinite(${v})`
2205
2205
  case 'integer': return `Number.isInteger(${v})`
2206
2206
  case 'boolean': return `typeof ${v}==='boolean'`
2207
2207
  case 'null': return `${v}===null`
@@ -2308,7 +2308,7 @@ function genCodeNode(schema, v, lines, ctx, knownType) {
2308
2308
  // non-finite value through: it carries no type, an empty schema accepts it,
2309
2309
  // and the interpreted engine reads it the same way. With `type: number` the
2310
2310
  // type check has already turned it away, so the guard is only needed here.
2311
- const numGuard = isNum ? '' : `typeof ${v}==='number'&&isFinite(${v})&&`
2311
+ const numGuard = isNum ? '' : `Number.isFinite(${v})&&`
2312
2312
  if (schema.minimum !== undefined) lines.push(`if(${numGuard}${v}<${schema.minimum})return false`)
2313
2313
  if (schema.maximum !== undefined) lines.push(`if(${numGuard}${v}>${schema.maximum})return false`)
2314
2314
  if (schema.exclusiveMinimum !== undefined) lines.push(`if(${numGuard}${v}<=${schema.exclusiveMinimum})return false`)
@@ -3973,7 +3973,7 @@ function genCodeENode(schema, v, pathExpr, lines, ctx, schemaPrefix) {
3973
3973
  case 'object': return `(typeof ${v}==='object'&&${v}!==null&&!Array.isArray(${v}))`
3974
3974
  case 'array': return `Array.isArray(${v})`
3975
3975
  case 'string': return `typeof ${v}==='string'`
3976
- case 'number': return `(typeof ${v}==='number'&&isFinite(${v}))`
3976
+ case 'number': return `Number.isFinite(${v})`
3977
3977
  case 'integer': return `Number.isInteger(${v})`
3978
3978
  case 'boolean': return `typeof ${v}==='boolean'`
3979
3979
  case 'null': return `${v}===null`
@@ -4710,7 +4710,7 @@ function genCodeCNode(schema, v, pathExpr, lines, ctx, schemaPrefix) {
4710
4710
  case 'object': return `(typeof ${v}==='object'&&${v}!==null&&!Array.isArray(${v}))`
4711
4711
  case 'array': return `Array.isArray(${v})`
4712
4712
  case 'string': return `typeof ${v}==='string'`
4713
- case 'number': return `(typeof ${v}==='number'&&isFinite(${v}))`
4713
+ case 'number': return `Number.isFinite(${v})`
4714
4714
  case 'integer': return `Number.isInteger(${v})`
4715
4715
  case 'boolean': return `typeof ${v}==='boolean'`
4716
4716
  case 'null': return `${v}===null`
@@ -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) => typeof data !== 'number' || !isFinite(data) || run(data));
185
+ if (run !== null) checks.push((data) => !Number.isFinite(data) || run(data));
186
186
  }
187
187
  if (P.hasString) {
188
188
  const strs = [];
@@ -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' || (typeof x === 'number' && isFinite(x));
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(`(typeof ${v}==='number'&&isFinite(${v}))`);
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, `typeof ${v}==='number'&&isFinite(${v})`, n);
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
@@ -137,7 +137,7 @@ function checkPrimitive(c, v) {
137
137
  if ((f & F_MULT) && !_multipleOk(v, c.multipleOf)) return false;
138
138
  }
139
139
  } else if (m === T_NUMBER) {
140
- if (typeof v !== 'number' || !isFinite(v)) return false;
140
+ if (!Number.isFinite(v)) return false;
141
141
  const f = c.numFlags;
142
142
  if (f !== 0) {
143
143
  if ((f & F_MIN) && v < c.min) return false;
@@ -194,7 +194,7 @@ function tier0ValidateObject(plan, data) {
194
194
  if ((f & F_MULT) && !_multipleOk(v, c.multipleOf)) return false;
195
195
  }
196
196
  } else if (m === T_NUMBER) {
197
- if (typeof v !== 'number' || !isFinite(v)) return false;
197
+ if (!Number.isFinite(v)) return false;
198
198
  const f = c.numFlags;
199
199
  if (f !== 0) {
200
200
  if ((f & F_MIN) && v < c.min) return 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.31.3';
10
+ module.exports = '1.32.0';
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "ata-validator",
3
- "version": "1.31.3",
3
+ "version": "1.32.0",
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_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.31.3",
125
- "@ata-validator/native-darwin-x64": "1.31.3",
126
- "@ata-validator/native-linux-arm64-gnu": "1.31.3",
127
- "@ata-validator/native-linux-arm64-musl": "1.31.3",
128
- "@ata-validator/native-linux-x64-gnu": "1.31.3",
129
- "@ata-validator/native-linux-x64-musl": "1.31.3",
130
- "@ata-validator/native-win32-x64": "1.31.3"
124
+ "@ata-validator/native-darwin-arm64": "1.32.0",
125
+ "@ata-validator/native-darwin-x64": "1.32.0",
126
+ "@ata-validator/native-linux-arm64-gnu": "1.32.0",
127
+ "@ata-validator/native-linux-arm64-musl": "1.32.0",
128
+ "@ata-validator/native-linux-x64-gnu": "1.32.0",
129
+ "@ata-validator/native-linux-x64-musl": "1.32.0",
130
+ "@ata-validator/native-win32-x64": "1.32.0"
131
131
  },
132
132
  "peerDependencies": {
133
133
  "yaml": "^2.0.0"