ata-validator 1.33.3 → 1.35.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.
@@ -0,0 +1,2052 @@
1
+ // Native addon: optional. Core validate() uses JS codegen and works without it.
2
+ // Buffer APIs (isValid, countValid, isValidParallel) require native.
3
+ // Loading is delegated to lib/native-load.js so this file stays free of
4
+ // platform probing and `path` (the browser entry must not pull those in).
5
+ // The addon loads on first use: the buffer APIs, a document past the simdjson
6
+ // threshold, version(). Loading it at require cost about 1.9 ms of a 9.7 ms
7
+ // serverless start, for every process that never calls any of those.
8
+ let _nativeMod;
9
+ function getNative() {
10
+ if (_nativeMod === undefined) _nativeMod = require("./native-load")();
11
+ return _nativeMod;
12
+ }
13
+ const { normalizeKeywords, schemaUsesKeywords } = require('./keywords');
14
+ // The code generator (lib/js-compiler and what builds on it: the scanner, the
15
+ // parse() copy, the ahead-of-time emitters) is registered by the entry that
16
+ // wants it, not required here. index.js registers it; lite.js does not, so a
17
+ // bundle built from lite.js never contains it. Until something registers, the
18
+ // core behaves exactly as it does where `new Function` is blocked: every
19
+ // schema runs on the interpreted engine, which passes the same test suite.
20
+ let _codegen = null;
21
+ let compileToJS, compileToJSCodegen, compileToJSCodegenWithErrors, compileToJSCombined;
22
+ function _registerCodegen(engine) {
23
+ _codegen = engine;
24
+ ({ compileToJS, compileToJSCodegen, compileToJSCodegenWithErrors, compileToJSCombined } = engine.jsCompiler);
25
+ }
26
+ const { normalizeDraft7, normalizeNullable, normalizeExclusiveBounds, stripFormatAssertions } = require("./draft7");
27
+ const { enabledKeywords, stripDisabledKeywords } = require("./vocabularies");
28
+ const { needsNormalization } = require("./schema-scan");
29
+ const { isV1Dialect } = require("./dialect");
30
+ const { classify } = require("./shape-classifier");
31
+ const { buildTier0Plan, tier0Validate } = require("./tier0");
32
+
33
+ // Extract default values from a schema tree. Returns a function that applies
34
+ // defaults to an object in-place (mutates), or null if no defaults exist.
35
+ function buildDefaultsApplier(schema) {
36
+ if (typeof schema !== "object" || schema === null) return null;
37
+ const actions = [];
38
+ collectDefaults(schema, actions);
39
+ if (actions.length === 0) return null;
40
+ return (data) => {
41
+ for (let i = 0; i < actions.length; i++) actions[i](data);
42
+ };
43
+ }
44
+
45
+ // Write an own property. Plain assignment of a key named `__proto__` does
46
+ // not create a property at all: it hits the Object.prototype setter and
47
+ // rewrites the object's prototype, which is how a schema could reach
48
+ // Object.prototype itself. defineProperty has no such special case.
49
+ function setOwn(obj, key, val) {
50
+ if (key === "__proto__") {
51
+ Object.defineProperty(obj, key, {
52
+ value: val,
53
+ writable: true,
54
+ enumerable: true,
55
+ configurable: true,
56
+ });
57
+ } else {
58
+ obj[key] = val;
59
+ }
60
+ }
61
+
62
+ function collectDefaults(schema, actions, path) {
63
+ if (typeof schema !== "object" || schema === null) return;
64
+ const props = schema.properties;
65
+ if (!props) return;
66
+ for (const [key, prop] of Object.entries(props)) {
67
+ if (prop && typeof prop === "object" && prop.default !== undefined) {
68
+ const defaultVal = prop.default;
69
+ if (!path) {
70
+ actions.push((data) => {
71
+ if (typeof data === "object" && data !== null && !Object.hasOwn(data, key)) {
72
+ setOwn(data,
73
+ key,
74
+ typeof defaultVal === "object" && defaultVal !== null
75
+ ? JSON.parse(JSON.stringify(defaultVal))
76
+ : defaultVal);
77
+ }
78
+ });
79
+ } else {
80
+ const parentPath = path;
81
+ actions.push((data) => {
82
+ let target = data;
83
+ for (let j = 0; j < parentPath.length; j++) {
84
+ if (typeof target !== "object" || target === null) return;
85
+ // Own keys only. `target[key]` for an inherited name walks the
86
+ // prototype chain: a parent named `__proto__` that the instance
87
+ // does not carry resolved to Object.prototype, and the child
88
+ // defaults were written onto it, for every object in the realm.
89
+ if (!Object.hasOwn(target, parentPath[j])) return;
90
+ target = target[parentPath[j]];
91
+ }
92
+ if (
93
+ typeof target === "object" &&
94
+ target !== null &&
95
+ !Object.hasOwn(target, key)
96
+ ) {
97
+ setOwn(target,
98
+ key,
99
+ typeof defaultVal === "object" && defaultVal !== null
100
+ ? JSON.parse(JSON.stringify(defaultVal))
101
+ : defaultVal);
102
+ }
103
+ });
104
+ }
105
+ }
106
+ // Recurse into nested object schemas
107
+ if (prop && typeof prop === "object" && prop.properties) {
108
+ collectDefaults(prop, actions, (path || []).concat(key));
109
+ }
110
+ }
111
+ }
112
+
113
+ // Build a function that coerces property values to match schema types in-place.
114
+ // Handles string→number, string→integer, string→boolean, number→string, boolean→string.
115
+ function buildCoercer(schema) {
116
+ if (typeof schema !== "object" || schema === null) return null;
117
+ const actions = [];
118
+ collectCoercions(schema, actions);
119
+ if (actions.length === 0) return null;
120
+ return (data) => {
121
+ for (let i = 0; i < actions.length; i++) actions[i](data);
122
+ };
123
+ }
124
+
125
+ function collectCoercions(schema, actions, path) {
126
+ if (typeof schema !== "object" || schema === null) return;
127
+ const props = schema.properties;
128
+ if (!props) return;
129
+ for (const [key, prop] of Object.entries(props)) {
130
+ if (!prop || typeof prop !== "object" || !prop.type) continue;
131
+ const targetType = Array.isArray(prop.type) ? null : prop.type;
132
+ if (!targetType) continue;
133
+
134
+ const coerce = buildSingleCoercion(targetType);
135
+ if (!coerce) continue;
136
+
137
+ if (!path) {
138
+ actions.push((data) => {
139
+ if (typeof data === "object" && data !== null && key in data) {
140
+ const coerced = coerce(data[key]);
141
+ if (coerced !== undefined) data[key] = coerced;
142
+ }
143
+ });
144
+ } else {
145
+ const parentPath = path;
146
+ actions.push((data) => {
147
+ let target = data;
148
+ for (let j = 0; j < parentPath.length; j++) {
149
+ if (typeof target !== "object" || target === null) return;
150
+ target = target[parentPath[j]];
151
+ }
152
+ if (typeof target === "object" && target !== null && key in target) {
153
+ const coerced = coerce(target[key]);
154
+ if (coerced !== undefined) target[key] = coerced;
155
+ }
156
+ });
157
+ }
158
+
159
+ // Recurse into nested object properties
160
+ if (prop.properties) {
161
+ collectCoercions(prop, actions, (path || []).concat(key));
162
+ }
163
+ }
164
+ }
165
+
166
+ function buildSingleCoercion(targetType) {
167
+ switch (targetType) {
168
+ case "number":
169
+ return (v) => {
170
+ if (typeof v === "string") {
171
+ const n = Number(v);
172
+ if (v !== "" && !isNaN(n)) return n;
173
+ }
174
+ if (typeof v === "boolean") return v ? 1 : 0;
175
+ };
176
+ case "integer":
177
+ return (v) => {
178
+ if (typeof v === "string") {
179
+ const n = Number(v);
180
+ if (v !== "" && Number.isInteger(n)) return n;
181
+ }
182
+ if (typeof v === "boolean") return v ? 1 : 0;
183
+ };
184
+ case "string":
185
+ return (v) => {
186
+ if (typeof v === "number" || typeof v === "boolean") return String(v);
187
+ };
188
+ case "boolean":
189
+ return (v) => {
190
+ if (v === "true" || v === "1") return true;
191
+ if (v === "false" || v === "0") return false;
192
+ };
193
+ default:
194
+ return null;
195
+ }
196
+ }
197
+
198
+ // Build a function that removes properties not defined in schema.properties.
199
+ // Walks nested objects recursively.
200
+ function buildRemover(schema) {
201
+ if (typeof schema !== "object" || schema === null) return null;
202
+ const actions = [];
203
+ collectRemovals(schema, actions);
204
+ if (actions.length === 0) return null;
205
+ return (data) => {
206
+ for (let i = 0; i < actions.length; i++) actions[i](data);
207
+ };
208
+ }
209
+
210
+ function collectRemovals(schema, actions, path) {
211
+ if (typeof schema !== "object" || schema === null || !schema.properties)
212
+ return;
213
+
214
+ // If this level has additionalProperties: false, add a removal action
215
+ if (schema.additionalProperties === false) {
216
+ const allowed = new Set(Object.keys(schema.properties));
217
+ if (!path) {
218
+ actions.push((data) => {
219
+ if (typeof data !== "object" || data === null || Array.isArray(data))
220
+ return;
221
+ const keys = Object.keys(data);
222
+ for (let i = 0; i < keys.length; i++) {
223
+ if (!allowed.has(keys[i])) delete data[keys[i]];
224
+ }
225
+ });
226
+ } else {
227
+ const parentPath = path;
228
+ actions.push((data) => {
229
+ let target = data;
230
+ for (let j = 0; j < parentPath.length; j++) {
231
+ if (typeof target !== "object" || target === null) return;
232
+ target = target[parentPath[j]];
233
+ }
234
+ if (
235
+ typeof target !== "object" ||
236
+ target === null ||
237
+ Array.isArray(target)
238
+ )
239
+ return;
240
+ const keys = Object.keys(target);
241
+ for (let i = 0; i < keys.length; i++) {
242
+ if (!allowed.has(keys[i])) delete target[keys[i]];
243
+ }
244
+ });
245
+ }
246
+ }
247
+
248
+ // Always recurse into nested properties (they may have their own additionalProperties: false)
249
+ for (const [key, prop] of Object.entries(schema.properties)) {
250
+ if (prop && typeof prop === "object" && prop.properties) {
251
+ collectRemovals(prop, actions, (path || []).concat(key));
252
+ }
253
+ }
254
+ }
255
+
256
+ // Cloudflare Workers, Deno Deploy and pages under a strict Content-Security-
257
+ // Policy refuse `new Function`. Probed once, lazily, because the answer cannot
258
+ // change within a realm and the probe itself is a code generation attempt.
259
+ let _codegenAvailable = null;
260
+ function codegenAvailable() {
261
+ if (_codegen === null) return false;
262
+ if (_codegenAvailable === null) {
263
+ try {
264
+ _codegenAvailable = new Function('return 1')() === 1;
265
+ } catch {
266
+ _codegenAvailable = false;
267
+ }
268
+ }
269
+ return _codegenAvailable;
270
+ }
271
+
272
+ // Schema compilation cache: same schema string -> reuse compiled functions
273
+ const _compileCache = new Map();
274
+
275
+ // Object identity cache: same schema object reference -> reuse entire compiled state
276
+ // Skips JSON.stringify, cache lookup, and all setup. Near-zero cost for repeated schemas.
277
+ const _identityCache = new WeakMap();
278
+
279
+ const SIMDJSON_PADDING = 64;
280
+ const VALID_RESULT = Object.freeze({ valid: true, errors: Object.freeze([]) });
281
+ // How many calls a validator answers through its verdict function before its
282
+ // validate() and validateJSON() compile the single-function hybrid.
283
+ const HYBRID_TIER_CALLS = 64;
284
+ const ABORT_EARLY_RESULT = Object.freeze({
285
+ valid: false,
286
+ errors: Object.freeze([Object.freeze({
287
+ code: 'ATA9000',
288
+ message: 'validation failed',
289
+ keyword: '__abort_early__',
290
+ path: '',
291
+ })]),
292
+ });
293
+
294
+ // `_CP_LEN_SOURCE`, the safe-regex embed, and the AOT helpers that consume them
295
+ // now live in `lib/aot.js` — keeping this file free of `fs`/`path`/`__dirname`
296
+ // references so a default import never touches disk. The static AOT methods
297
+ // further down lazily require `./lib/aot`, so they pay nothing until a user
298
+ // calls `bundleStandalone`/`bundle`/etc.
299
+
300
+ // Above this size, simdjson On Demand (selective field access) beats JSON.parse
301
+ // (which must materialize the full JS object tree). Buffer.from + NAPI ~2x faster.
302
+
303
+
304
+ const SIMDJSON_THRESHOLD = 8192;
305
+
306
+ // Resolve a JSON Schema path like "#/properties/name/type" to the schema object
307
+ // that *contains* the failing keyword. Used by verbose mode to populate
308
+ // `parentSchema` on validation errors. Returns undefined if the path can't be
309
+ // walked (malformed pointer or missing intermediate node).
310
+ function resolveSchemaByPath(rootSchema, schemaPath) {
311
+ if (!schemaPath || typeof schemaPath !== 'string' || !schemaPath.startsWith('#')) {
312
+ return undefined;
313
+ }
314
+ const stripped = schemaPath.slice(1);
315
+ if (!stripped || stripped === '/') return rootSchema;
316
+ const parts = stripped.split('/').filter(Boolean).map(s => s.replace(/~1/g, '/').replace(/~0/g, '~'));
317
+ // The last segment is the keyword that failed (e.g. "type"); parentSchema is
318
+ // the schema object that owns that keyword, so walk all but the last segment.
319
+ let target = rootSchema;
320
+ for (let i = 0; i < parts.length - 1; i++) {
321
+ if (target == null || typeof target !== 'object') return undefined;
322
+ target = target[parts[i]];
323
+ }
324
+ return target;
325
+ }
326
+
327
+ // Rank an error by walking its schemaPath through the schema object: at each
328
+ // level the segment's index among the node's declared keys. Comparing ranks
329
+ // lexicographically orders errors by keyword declaration order, which is the
330
+ // order AJV emits and what schema authors read top to bottom. Segments that
331
+ // cannot be resolved (cross-schema refs, normalized keys) end the walk; the
332
+ // stable sort then keeps such errors in engine emission order.
333
+ // The rank of a `schemaPath` under a given root is fixed: the schema does not
334
+ // change between validations, so neither does the answer. It was recomputed for
335
+ // every error of every failing document, and computing it is not cheap. Two
336
+ // caches, both keyed on things that do not change:
337
+ //
338
+ // rootSchema -> schemaPath -> rank, so a path is walked once ever
339
+ // node -> key -> its index, so the walk stops calling Object.keys and
340
+ // scanning the result for a string
341
+ //
342
+ // A failing route sees the same handful of schemaPaths over and over, which is
343
+ // what makes the first one worth having.
344
+ const { LazyRejection, RichRejection, LazyJsonRejection, _enrichLazy } = require('./rejections');
345
+
346
+ // Text that does not parse, reported the same way on every path and platform:
347
+ // ATA9001 with the parser's own message. The native addon used to say only
348
+ // "invalid JSON document", and the pure-JS paths used a keyword of their own.
349
+ function _jsonSyntaxRejection(e) {
350
+ return { valid: false, errors: [{ keyword: '__parse__', instancePath: '', schemaPath: '', params: {}, message: 'invalid JSON: ' + e.message }] };
351
+ }
352
+
353
+
354
+
355
+
356
+
357
+
358
+
359
+
360
+ // Paths repeat across rejections of the same shape, so the parsed segment
361
+ // list is cached per path string. Entries are frozen: the same array is
362
+ // handed to every issue that names the path.
363
+ //
364
+ // The cache is bucketed by length and searched with ===, not keyed in a Map.
365
+ // A path with an array index in it is concatenated afresh by every rejection,
366
+ // and a Map has to hash each such string from scratch before it can look
367
+ // anything up; that hashing was 30% of a Standard Schema rejection with
368
+ // sixteen issues. Comparing against the few paths of the same length costs
369
+ // less. Both levels are bounded so a stream of array indexes cannot grow it
370
+ // without limit: a full bucket stops caching, too many lengths clear it.
371
+ const _pathBuckets = new Map();
372
+ const PATH_BUCKET_MAX = 32;
373
+ const PATH_LENGTHS_MAX = 256;
374
+ function parsePointerPath(path) {
375
+ if (!path) return EMPTY_PATH;
376
+ const n = path.length;
377
+ let bucket = _pathBuckets.get(n);
378
+ if (bucket !== undefined) {
379
+ for (let i = 0; i < bucket.length; i += 2) if (bucket[i] === path) return bucket[i + 1];
380
+ } else {
381
+ if (_pathBuckets.size >= PATH_LENGTHS_MAX) _pathBuckets.clear();
382
+ bucket = [];
383
+ _pathBuckets.set(n, bucket);
384
+ }
385
+ const segs = Object.freeze(parsePointerPathUncached(path));
386
+ if (bucket.length < PATH_BUCKET_MAX * 2) bucket.push(path, segs);
387
+ return segs;
388
+ }
389
+ const EMPTY_PATH = Object.freeze([]);
390
+
391
+ function parsePointerPathUncached(path) {
392
+ // One pass, no intermediate arrays. Per Standard Schema V1 an array index
393
+ // is emitted as a number and an object key as a string; a segment is an
394
+ // index when it is all digits with no leading zero.
395
+ const out = [];
396
+ const n = path.length;
397
+ let start = 1;
398
+ for (let i = 1; i <= n; i++) {
399
+ if (i !== n && path.charCodeAt(i) !== 47) continue;
400
+ if (i > start) {
401
+ let seg = path.slice(start, i);
402
+ if (seg.indexOf('~') >= 0) seg = seg.replace(/~1/g, '/').replace(/~0/g, '~');
403
+ const c0 = seg.charCodeAt(0);
404
+ let numeric = c0 >= 48 && c0 <= 57 && (seg.length === 1 || c0 !== 48);
405
+ if (numeric) {
406
+ for (let k = 1; k < seg.length; k++) {
407
+ const c = seg.charCodeAt(k);
408
+ if (c < 48 || c > 57) { numeric = false; break; }
409
+ }
410
+ }
411
+ out.push({ key: numeric ? Number(seg) : seg });
412
+ }
413
+ start = i + 1;
414
+ }
415
+ return out;
416
+ }
417
+
418
+ function createPaddedBuffer(jsonStr) {
419
+ if (typeof Buffer === 'undefined') throw new Error('createPaddedBuffer requires Node.js Buffer');
420
+ const jsonBuf = Buffer.from(jsonStr);
421
+ const padded = Buffer.allocUnsafe(jsonBuf.length + SIMDJSON_PADDING);
422
+ jsonBuf.copy(padded);
423
+ padded.fill(0, jsonBuf.length);
424
+ return { buffer: padded, length: jsonBuf.length };
425
+ }
426
+
427
+ // Deep-clone a value, copying own symbol keys by reference at every level.
428
+ // Arrays and plain objects are cloned recursively; primitives, RegExp,
429
+ // functions, and other non-plain values are returned as-is. Symbol values
430
+ // (e.g. refinement lists, OPTIONAL markers) are owned by the caller's builder
431
+ // and sharing them is correct — they are never mutated by normalization.
432
+ function _deepCloneWithSymbols(v) {
433
+ if (v === null || typeof v !== 'object') return v;
434
+ if (Array.isArray(v)) {
435
+ const a = new Array(v.length);
436
+ for (let i = 0; i < v.length; i++) a[i] = _deepCloneWithSymbols(v[i]);
437
+ return a;
438
+ }
439
+ // Only clone plain objects (skip RegExp, Date, etc.).
440
+ if (Object.getPrototypeOf(v) !== Object.prototype && Object.getPrototypeOf(v) !== null) return v;
441
+ const out = Object.create(null);
442
+ for (const k of Object.keys(v)) Object.defineProperty(out, k, { value: _deepCloneWithSymbols(v[k]), writable: true, enumerable: true, configurable: true });
443
+ for (const sym of Object.getOwnPropertySymbols(v)) out[sym] = v[sym];
444
+ return Object.setPrototypeOf(out, Object.prototype);
445
+ }
446
+
447
+ // Normalize a caller-provided schema without mutating the original.
448
+ // Clones only when normalization would change the object (draft-07 keys
449
+ // present or nullable fields present). Internal-only — not exported.
450
+ function _normalizeCallerSchema(s, inheritDraft7) {
451
+ const declares = s && typeof s === 'object' && s.$schema !== undefined
452
+ const needsDraft7 = declares
453
+ ? (s.$schema === 'http://json-schema.org/draft-07/schema#' || s.$schema === 'http://json-schema.org/draft-07/schema')
454
+ : !!inheritDraft7
455
+ // One walk answers whether there is anything to do. Almost always there is
456
+ // not, and then the serialize, clone, normalize, serialize, compare below is
457
+ // work spent to find that out. The walk over-reports rather than under, so a
458
+ // schema it clears is one no normalizer would have touched;
459
+ // `tests/test_schema_scan.js` holds that direction against the whole suite.
460
+ if (!needsNormalization(s, needsDraft7)) return s
461
+
462
+ const str = JSON.stringify(s)
463
+ const copy = _deepCloneWithSymbols(s)
464
+ if (needsDraft7) normalizeDraft7(copy, true)
465
+ normalizeNullable(copy)
466
+ normalizeExclusiveBounds(copy)
467
+ // Return original when normalization produced no change, copy otherwise.
468
+ // Kept even though the walk has already said there is work, so that a walk
469
+ // which over-reports still returns exactly what it returned before.
470
+ // Change-detection uses JSON content only; symbols do not affect it.
471
+ return JSON.stringify(copy) === str ? s : copy
472
+ }
473
+
474
+ // The identity a document is registered under. Draft-07 ignores every
475
+ // keyword sitting next to `$ref`, so normalization drops them, `$id` among
476
+ // them: that is the right reading for evaluation, where the reference
477
+ // resolves against the retrieval URI rather than the declared `$id`. It is
478
+ // the wrong reading for registration, since `$id` is how the caller names
479
+ // the document. So the identity is read from the normalized copy first and
480
+ // from what the caller passed second. A bare-fragment `$id` is a draft-07
481
+ // anchor rather than a document identity, and normalization has already
482
+ // turned it into `$anchor`, so it is not used here.
483
+ function declaredId(original, normalized) {
484
+ const n = normalized && typeof normalized === 'object' ? normalized.$id : undefined
485
+ if (typeof n === 'string' && n !== '') return n
486
+ const o = original && typeof original === 'object' ? original.$id : undefined
487
+ if (typeof o === 'string' && o !== '' && o[0] !== '#') return o
488
+ return undefined
489
+ }
490
+
491
+ // `inheritDraft7` is true when the root schema is draft-07: a retrieved
492
+ // document that declares no dialect is read under the root's draft.
493
+ // The map is derived entirely from what the caller passed, so the same
494
+ // `schemas` gives the same map. A server building one validator per route over
495
+ // a shared registry rebuilt it once per route, normalizing and re-reading the
496
+ // `$id` of every registered schema each time. Keyed by the registry object,
497
+ // and by the draft it is read under, since that changes what normalization
498
+ // does to a document which declares no dialect of its own.
499
+ //
500
+ // Validators share the returned map, so anything that mutates one calls
501
+ // `_ownSchemaMap()` first. There are two such places: registering the vendored
502
+ // meta-schemas during compilation, and `addSchema()`.
503
+ const _schemaMapCache = new WeakMap()
504
+
505
+ function buildSchemaMap(schemas, inheritDraft7) {
506
+ if (!schemas) return null
507
+ const byDraft = _schemaMapCache.get(schemas)
508
+ if (byDraft) {
509
+ const hit = byDraft[inheritDraft7 ? 1 : 0]
510
+ if (hit) return hit
511
+ }
512
+ const map = _buildSchemaMap(schemas, inheritDraft7)
513
+ const slot = byDraft || [null, null]
514
+ slot[inheritDraft7 ? 1 : 0] = map
515
+ if (!byDraft) _schemaMapCache.set(schemas, slot)
516
+ return map
517
+ }
518
+
519
+ function _buildSchemaMap(schemas, inheritDraft7) {
520
+ const map = new Map()
521
+ if (Array.isArray(schemas)) {
522
+ for (const s of schemas) {
523
+ const normalized = _normalizeCallerSchema(s, inheritDraft7)
524
+ const id = declaredId(s, normalized)
525
+ if (!id) throw new Error('Schema in schemas option must have $id')
526
+ map.set(id, normalized)
527
+ }
528
+ } else {
529
+ for (const [key, s] of Object.entries(schemas)) {
530
+ const normalized = _normalizeCallerSchema(s, inheritDraft7)
531
+ // A retrieved document is addressable both by the URI it was registered
532
+ // under and by the $id it declares. Registering only the $id makes
533
+ // references to the retrieval URI unresolvable.
534
+ map.set(key, normalized)
535
+ const id = declaredId(s, normalized)
536
+ if (id && id !== key) map.set(id, normalized)
537
+ }
538
+ }
539
+ return map
540
+ }
541
+
542
+ // A schema which names a custom meta-schema in `$schema` is written against
543
+ // whatever dialect that meta-schema declares. A keyword from a vocabulary the
544
+ // dialect does not have is not part of the dialect, so it is an unknown
545
+ // keyword and does not apply. Removing it here means every engine sees the
546
+ // same schema and none of them needs to know about vocabularies.
547
+ //
548
+ // Only the root is consulted. A subschema naming its own `$schema` is its own
549
+ // resource under its own dialect, and the walk stops there rather than
550
+ // applying this dialect's answer to it.
551
+ function _applyVocabularies(schemaObj, original, schemaMap) {
552
+ if (!schemaObj || typeof schemaObj !== 'object') return schemaObj
553
+ const declared = schemaObj.$schema
554
+ if (typeof declared !== 'string') return schemaObj
555
+ const enabled = enabledKeywords(schemaMap.get(declared))
556
+ if (!enabled) return schemaObj
557
+ // `original` is the caller's own object when it reached here unchanged, and
558
+ // that one is never mutated.
559
+ const copy = schemaObj === original
560
+ ? _deepCloneWithSymbols(schemaObj)
561
+ : schemaObj
562
+ return stripDisabledKeywords(copy, enabled)
563
+ }
564
+
565
+ // Compile-cache key for a root schema plus its external schemas. Must include
566
+ // the external schema CONTENT, not just their $ids: two validators can share a
567
+ // root schema string and the same $id while pointing that $id at different
568
+ // schemas (separate app instances, test suites, multi-tenant). Keying on $id
569
+ // alone reuses the wrong compiled validator and silently mis-validates.
570
+ function compileCacheKey(schemaStr, schemaMap) {
571
+ if (!schemaMap || schemaMap.size === 0) return schemaStr
572
+ const parts = []
573
+ for (const [id, s] of schemaMap) parts.push(id + '=' + JSON.stringify(s))
574
+ parts.sort()
575
+ return schemaStr + '\0' + parts.join('\0')
576
+ }
577
+
578
+ // Resolve a cross-schema $ref to its target schema for preprocessing purposes.
579
+ // Handles whole-schema refs (`shared#`), relative-id matching, and JSON pointer
580
+ // fragments (`shared#/properties/id`). Returns null for local-only refs or when
581
+ // the target cannot be found. Used only to read `type`/`properties` for
582
+ // coercion/defaults/removeAdditional, never for validation.
583
+ function resolveRefForPreprocess(ref, schemaMap) {
584
+ if (!schemaMap || schemaMap.size === 0 || typeof ref !== 'string') return null
585
+ const hashIdx = ref.indexOf('#')
586
+ const baseId = hashIdx >= 0 ? ref.slice(0, hashIdx) : ref
587
+ const fragment = hashIdx >= 0 ? ref.slice(hashIdx + 1) : ''
588
+ if (!baseId) return null
589
+ let base = null
590
+ if (schemaMap.has(baseId)) base = schemaMap.get(baseId)
591
+ else if (!ref.includes('://')) {
592
+ for (const [id, s] of schemaMap) {
593
+ if (id.endsWith('/' + baseId)) { base = s; break }
594
+ }
595
+ }
596
+ if (!base) return null
597
+ if (!fragment) return base
598
+ let target = base
599
+ for (const part of fragment.split('/')) {
600
+ if (part === '') continue
601
+ if (target == null || typeof target !== 'object') return null
602
+ target = target[part.replace(/~1/g, '/').replace(/~0/g, '~')]
603
+ }
604
+ return target == null ? null : target
605
+ }
606
+
607
+ // Preprocessing (coerce/defaults/removeAdditional) reads `schema.properties` and
608
+ // each property's `type`. When the data shape lives behind a cross-schema $ref
609
+ // (a whole-schema ref like Fastify's `params: { $ref: 'shared#' }`, or a
610
+ // property ref like `{ id: { $ref: 'shared#/properties/id' } }`), follow the
611
+ // ref so the preprocessor can see the referenced shape. Returns the schema with
612
+ // such refs resolved, cloning only when a substitution is made.
613
+ function resolveSchemaForPreprocess(schema, schemaMap) {
614
+ if (!schema || typeof schema !== 'object' || !schemaMap || schemaMap.size === 0) return schema
615
+ let s = schema
616
+ // Whole-schema ref (only when it has no own properties, to avoid dropping
617
+ // sibling keywords on schemas that mix $ref with properties).
618
+ if (s.$ref && !s.properties) {
619
+ const t = resolveRefForPreprocess(s.$ref, schemaMap)
620
+ if (t && typeof t === 'object') s = t
621
+ }
622
+ if (!s.properties) return s
623
+ // Property-level refs: substitute the resolved target so coercion sees `type`.
624
+ let cloned = null
625
+ for (const key of Object.keys(s.properties)) {
626
+ const p = s.properties[key]
627
+ if (p && typeof p === 'object' && p.$ref && !p.type) {
628
+ const t = resolveRefForPreprocess(p.$ref, schemaMap)
629
+ if (t && typeof t === 'object') {
630
+ if (!cloned) { cloned = Object.assign({}, s); cloned.properties = Object.assign({}, s.properties) }
631
+ cloned.properties[key] = t
632
+ }
633
+ }
634
+ }
635
+ return cloned || s
636
+ }
637
+
638
+ // `_schemaObj` and `_usesKeywords` are materialized together on first read:
639
+ // the caller's schema normalized on a clone (the caller's object is never
640
+ // touched), `format` stripped under `assertFormat: false`, and the custom
641
+ // keyword scan. The accessors then step aside for own data properties, so
642
+ // every later read is a plain field.
643
+ function _materializeSchema(self) {
644
+ const raw = self._rawSchema;
645
+ const options = self._options;
646
+ let schemaObj = _normalizeCallerSchema(raw);
647
+ const isCallers = self._rawIsCallers && schemaObj === raw;
648
+ if (options.assertFormat === false) {
649
+ schemaObj = stripFormatAssertions(isCallers ? _deepCloneWithSymbols(schemaObj) : schemaObj);
650
+ }
651
+ const usesKeywords = self._keywords !== null && schemaUsesKeywords(schemaObj, self._keywords);
652
+ Object.defineProperty(self, '_schemaObj', { value: schemaObj, writable: true, configurable: true, enumerable: true });
653
+ Object.defineProperty(self, '_usesKeywords', { value: usesKeywords, writable: true, configurable: true, enumerable: true });
654
+ Object.defineProperty(self, '_schemaIsCallers', { value: isCallers && schemaObj === raw, writable: true, configurable: true, enumerable: true });
655
+ return schemaObj;
656
+ }
657
+
658
+ class Validator {
659
+ constructor(schema, opts) {
660
+ const options = opts || {};
661
+
662
+ // Ultra-fast path: same schema object reference -> return cached instance
663
+ // JS constructor returning an object makes `new` return that object
664
+ // Cost: one WeakMap lookup. No property copy, no setup, nothing.
665
+ if (!opts && typeof schema === "object" && schema !== null) {
666
+ const hit = _identityCache.get(schema);
667
+ if (hit) return hit;
668
+ }
669
+
670
+ // The schema is not walked here. Normalization (draft-07 rewrites,
671
+ // nullable, `assertFormat: false`) and the scan that decides whether any
672
+ // of it is needed run on the first read of `_schemaObj`, which is the
673
+ // first compile. Construction is the object and its fields; a server
674
+ // building a validator per request pays nothing for a schema it never
675
+ // uses, and a benchmark timing construction measures construction.
676
+ // When schema is a string, JSON.parse already produces a fresh object.
677
+ const raw = typeof schema === "string" ? JSON.parse(schema) : schema;
678
+ const rootIsDraft7 = !!(raw && typeof raw === 'object' && typeof raw.$schema === 'string' &&
679
+ (raw.$schema === 'http://json-schema.org/draft-07/schema#' || raw.$schema === 'http://json-schema.org/draft-07/schema'));
680
+
681
+ // Built here rather than below because `$vocabulary` is resolved against
682
+ // it, and that resolution waits until compilation so a meta-schema
683
+ // registered by addSchema() still counts.
684
+ const shared = buildSchemaMap(options.schemas, rootIsDraft7);
685
+ const schemaMap = shared || new Map();
686
+ this._schemaMapShared = shared !== null;
687
+ this._vocabulariesApplied = false;
688
+
689
+ // Custom keywords, normalized once. `_usesKeywords` (resolved with the
690
+ // schema) is what routes the schema to the interpreted engine and keeps
691
+ // it out of the shared compile cache; a schema that registers keywords
692
+ // but uses none of them takes the ordinary path.
693
+ this._keywords = normalizeKeywords(options.keywords);
694
+
695
+ this._schemaStr = null; // lazy: computed on first use
696
+ this._rawSchema = raw;
697
+ this._rawIsCallers = typeof schema !== "string";
698
+ this._options = options;
699
+ this._noOpts = !opts;
700
+ // engine: 'interpreter' keeps this validator off code generation: no
701
+ // `new Function`, no shared compile cache, the eval-free interpreted
702
+ // engine answers validate(), isValidObject() and validateJSON(). For a
703
+ // schema that arrives from outside the trust boundary (a plugin's
704
+ // declared config shape, a tenant's upload), where turning it into source
705
+ // is not an acceptable execution model. ATA_FORCE_NAPI does this for the
706
+ // whole process; the option does it for one validator. A misspelling
707
+ // must not fall through to codegen, so anything else is refused.
708
+ if (options.engine !== undefined && options.engine !== 'auto' && options.engine !== 'interpreter') {
709
+ throw new TypeError("engine must be 'auto' or 'interpreter', got " + JSON.stringify(options.engine));
710
+ }
711
+ this._interpretOnly = options.engine === 'interpreter';
712
+ this._initialized = false;
713
+ this._nativeReady = false;
714
+ this._compiled = null;
715
+ this._fastSlot = -1;
716
+ this._jsFn = null;
717
+ this._engine = undefined;
718
+ this._preprocess = null;
719
+ this._applyDefaults = null;
720
+
721
+ // Schema map for cross-schema $ref resolution
722
+ this._schemaMap = schemaMap;
723
+
724
+ // User-supplied format checkers: { formatName: (value) => boolean }.
725
+ // Looked up at runtime when a schema references a format the built-in
726
+ // registry does not know about.
727
+ this._userFormats = options.formats || null;
728
+
729
+ // Verbose mode: when on, errors carry parentSchema (the schema object that
730
+ // produced the error). Matches ajv's `verbose: true` behavior.
731
+ this._verbose = !!options.verbose;
732
+
733
+ // strictSchema: authoring-time checks, off by default. A mistyped keyword
734
+ // is the one schema mistake that fails open: to every dialect `maxLenght`
735
+ // is an annotation, so the constraint the author meant is simply absent
736
+ // and previously invalid data validates. `true` throws here, at
737
+ // construction, with every finding; `'log'` reports through
738
+ // `options.logger` or the console and continues. The check runs on the
739
+ // schema as written, before any normalization touches it.
740
+ if (options.strictSchema === true || options.strictSchema === 'log') {
741
+ const { checkSchemaStrict } = require('./strict-check');
742
+ const problems = checkSchemaStrict(schema, { userKeywords: options.keywords || null });
743
+ if (problems.length > 0) {
744
+ const text = problems.map((x) => `strict mode: ${x.message} at ${x.path}`).join('\n');
745
+ if (options.strictSchema === true) {
746
+ throw new Error(text);
747
+ }
748
+ const logger = options.logger;
749
+ if (logger !== false) {
750
+ const warn = logger && typeof logger.warn === 'function' ? logger.warn.bind(logger) : console.warn;
751
+ warn(text);
752
+ }
753
+ }
754
+ }
755
+
756
+ // richErrors: default true. Only the literal `false` opts back into the
757
+ // v0.14 error shape (no code/expected/received/docUrl, no aliases).
758
+ this._richErrors = options && options.richErrors === false ? false : true;
759
+
760
+ // Optional schema source descriptor. When supplied, the renderer pipeline
761
+ // can attach a `schemaSource` frame to enriched errors.
762
+ this._source = options && options.source && typeof options.source === 'object'
763
+ ? { path: String(options.source.path || ''), content: String(options.source.content || '') }
764
+ : null;
765
+
766
+ // Build a JSON pointer -> position map for the schema text once at
767
+ // construction so each runtime error can resolve `schemaSource` without
768
+ // re-scanning the source on every validate() call.
769
+ if (this._source) {
770
+ const { buildPositionMap } = require('./source-positions');
771
+ this._schemaPositions = buildPositionMap(this._source.content);
772
+ } else {
773
+ this._schemaPositions = null;
774
+ }
775
+
776
+ // Per-validate data position cache. Populated by validateJSON before
777
+ // dispatching to inner validate(); consulted by the rich-error wrap
778
+ // to attach dataFrame entries to each enriched error.
779
+ this._posCache = null; // created by _pos() on first use, only the JSON text path needs it
780
+ this._lastRawInput = null;
781
+ // undefined: not built yet. null: this schema has no scanner.
782
+ this._scanner = undefined;
783
+ // Checks a wrapper registered through _extendChecks. Declared here so
784
+ // registering one does not change the instance's shape.
785
+ this._verdictTail = null;
786
+ this._validateTail = null;
787
+ this._entryExt = null;
788
+
789
+ // Public methods start as memoized accessors on the prototype; nothing is
790
+ // allocated per instance until one is first read. See _defineLazyMethod
791
+ // below the class.
792
+
793
+ // "~standard" (Standard Schema V1) is a lazy prototype accessor too;
794
+ // see below the class. Consumers only pay for it if they read it.
795
+
796
+ // The identity cache, which lets a later `new Validator(sameSchema)` return
797
+ // this instance, is filled on the first compile rather than here. A WeakMap
798
+ // entry is an ephemeron the collector has to trace separately, and setting
799
+ // one cost about 780 ns against 150 for the rest of this constructor, five
800
+ // times over for an instance that may never validate anything. Once an
801
+ // instance has compiled, the shortcut behaves as before.
802
+ }
803
+
804
+ // `$vocabulary` says which keywords the dialect has, and answering needs the
805
+ // meta-schema, which addSchema() may only have registered just now. Run once,
806
+ // before anything reads the schema, and before `_schemaStr` is computed from
807
+ // it. After this addSchema() is refused, so the answer cannot go stale.
808
+ // Whether validation is preceded by a pass that rewrites the input:
809
+ // coercion, removal of undeclared keys, or filling in defaults. The verdict
810
+ // methods have to take the same path when it is, so the quick bindings that
811
+ // answer from the compiled function alone are not used for these validators.
812
+ _needsPreprocess() {
813
+ const o = this._options;
814
+ if (o.coerceTypes || o.removeAdditional) return true;
815
+ if (o.useDefaults === false) return false;
816
+ if (!this._schemaStr) this._schemaStr = JSON.stringify(this._schemaObj);
817
+ return this._schemaStr.includes('"default"');
818
+ }
819
+
820
+ _pos() {
821
+ return this._posCache || (this._posCache = require('./data-position-cache').createCache());
822
+ }
823
+
824
+ _ensureVocabularies() {
825
+ if (this._vocabulariesApplied) return;
826
+ this._vocabulariesApplied = true;
827
+ const stripped = _applyVocabularies(
828
+ this._schemaObj,
829
+ this._schemaIsCallers ? this._schemaObj : null,
830
+ this._schemaMap,
831
+ );
832
+ if (stripped !== this._schemaObj) {
833
+ this._schemaObj = stripped;
834
+ this._schemaStr = null;
835
+ }
836
+ }
837
+
838
+ _ensureCompiled() {
839
+ if (this._initialized) return;
840
+ this._ensureVocabularies();
841
+ this._initialized = true;
842
+
843
+ const schemaObj = this._schemaObj;
844
+ const options = this._options;
845
+
846
+ // Lazy stringify — only computed here, not in constructor
847
+ if (!this._schemaStr) this._schemaStr = JSON.stringify(schemaObj);
848
+
849
+ // A $ref to a meta-schema resolves from the vendored copies, so
850
+ // "validate this schema against its dialect" needs no network and no
851
+ // caller-supplied registry. Only schemas that mention json-schema.org in a
852
+ // reference pay for the lookup.
853
+ if (this._schemaStr.includes('json-schema.org/draft')) {
854
+ const { METASCHEMAS } = require('./metaschemas');
855
+ this._ownSchemaMap();
856
+ for (const [id, meta] of METASCHEMAS) {
857
+ const bare = id.replace(/#$/, '');
858
+ for (const key of [id, bare, bare + '#', bare.replace(/^https:/, 'http:'), bare.replace(/^http:/, 'https:')]) {
859
+ if (!this._schemaMap.has(key)) this._schemaMap.set(key, meta);
860
+ }
861
+ }
862
+ }
863
+
864
+ // Check cache first -- reuse compiled functions for same schema
865
+ const sm = this._schemaMap.size > 0 ? this._schemaMap : null;
866
+ const mapKey = compileCacheKey(this._schemaStr, this._schemaMap);
867
+ var _forceNapi = this._interpretOnly || (typeof process !== 'undefined' && process.env && process.env.ATA_FORCE_NAPI);
868
+ // Custom formats are JS functions: bypass the compile cache since they can
869
+ // differ between validators that share the same schema string. An
870
+ // interpreter-only validator never touches it either way: a function a
871
+ // trusted validator compiled for the same schema string must not answer
872
+ // for it.
873
+ const cached = (this._userFormats || this._usesKeywords || _forceNapi) ? null : _compileCache.get(mapKey);
874
+ let jsFn, jsCombinedFn, jsErrFn, _isCodegen = false;
875
+ // v1 removes the bookending requirement for $dynamicRef. Only the
876
+ // interpreted engine implements that; the JS compiler and the native
877
+ // engine both resolve the 2020-12 way, so a v1 schema using the keyword
878
+ // goes to the interpreter rather than being validated under the wrong
879
+ // dialect. Schemas without $dynamicRef are unaffected: v1 and 2020-12
880
+ // agree on everything else ata implements.
881
+ this._v1Dynamic =
882
+ isV1Dialect(schemaObj) &&
883
+ (this._schemaStr.includes('"$dynamicRef"') || this._schemaStr.includes('"$dynamicAnchor"'));
884
+ //
885
+ // Where source cannot be turned into a function, neither JS path is usable
886
+ // either. The closure path does not call `new Function` itself, so it
887
+ // survives the block and would quietly handle schemas it gets wrong; the
888
+ // interpreted engine is both eval-free and more correct, so go straight
889
+ // there. The forced case is decided before the probe: the probe is a
890
+ // `new Function` too, and a validator that promised none must not run it.
891
+ if (_forceNapi || this._v1Dynamic || this._usesKeywords || !codegenAvailable()) {
892
+ jsFn = null; jsCombinedFn = null; jsErrFn = null;
893
+ // `full` separates an entry that holds every compiled function from one
894
+ // the verdict-only fast path seeded, where `combined` and `errFn` are null
895
+ // because nothing has tried to build them yet. Both halves of that
896
+ // distinction are null, and reading the second as the first costs this
897
+ // schema its generated error function for the life of the process.
898
+ } else if (cached && cached.jsFn !== undefined) {
899
+ // `full` says the error and combined functions exist too. An entry
900
+ // without it still carries a verdict function worth reusing; the pair is
901
+ // built by _buildErr/_buildCombined below when something asks, and the
902
+ // entry is upgraded then. `undefined` in `combined`/`errFn` means not
903
+ // built yet; `null` means the compiler declined. Those two must never
904
+ // blur: reading the first as the second is the bug this cache had once
905
+ // already, and it silently cost schemas their generated error function.
906
+ jsFn = cached.jsFn;
907
+ jsCombinedFn = cached.combined;
908
+ jsErrFn = cached.errFn;
909
+ _isCodegen = !!cached.isCodegen;
910
+ this._engine = _isCodegen ? 'codegen' : jsFn ? 'closure' : null;
911
+ } else {
912
+ const uf = this._userFormats;
913
+ const _cgFn = compileToJSCodegen(schemaObj, sm, uf);
914
+ jsFn = _cgFn || compileToJS(schemaObj, null, sm);
915
+ // Only the verdict is compiled here. The error and combined generators
916
+ // are the other two thirds of a cold first call (8.2, 10.4 and 7.8 ms on
917
+ // a 120-property config schema, most of it V8 compiling each generator
918
+ // the first time it is entered), and a caller that never reads an error
919
+ // never needs them. _buildErr/_buildCombined compile them on demand.
920
+ jsCombinedFn = undefined;
921
+ jsErrFn = undefined;
922
+ _isCodegen = !!_cgFn;
923
+ this._engine = _cgFn ? 'codegen' : jsFn ? 'closure' : null;
924
+ if (!uf) {
925
+ _compileCache.set(mapKey, { jsFn, combined: undefined, errFn: undefined, isCodegen: _isCodegen, full: false });
926
+ }
927
+ }
928
+ this._jsFn = jsFn;
929
+ if (this._engine === undefined) this._engine = null;
930
+
931
+ // Data mutators -- try codegen first (12x faster), fallback to closure arrays.
932
+ // Follow cross-refs so coercion/defaults/removeAdditional see the referenced
933
+ // shape (e.g. Fastify `params: { $ref: 'shared#' }` or property refs like
934
+ // `{ id: { $ref: 'shared#/properties/id' } }`).
935
+ const preprocessSchema = resolveSchemaForPreprocess(schemaObj, this._schemaMap);
936
+ // The mutator pass (defaults, coercion, removal) is generated source too,
937
+ // with the schema's `default` values embedded; an interpreter-only
938
+ // validator takes the closure mutators instead.
939
+ let preprocess = this._interpretOnly || _codegen === null ? null : _codegen.buildPreprocess(preprocessSchema, options);
940
+ if (!preprocess) {
941
+ const applyDefaults = options.useDefaults === false ? null : buildDefaultsApplier(preprocessSchema);
942
+ const applyCoerce = options.coerceTypes ? buildCoercer(preprocessSchema) : null;
943
+ const applyRemove = options.removeAdditional
944
+ ? buildRemover(preprocessSchema)
945
+ : null;
946
+ const mutators = [applyRemove, applyCoerce, applyDefaults].filter(Boolean);
947
+ preprocess =
948
+ mutators.length === 0
949
+ ? null
950
+ : mutators.length === 1
951
+ ? mutators[0]
952
+ : (data) => {
953
+ for (let i = 0; i < mutators.length; i++) mutators[i](data);
954
+ };
955
+ }
956
+ this._applyDefaults = preprocess;
957
+ // Whether validate() can change the caller's object before the verdict.
958
+ // This is a capability, not an option: `useDefaults` is on by default, but
959
+ // buildDefaultsApplier returns null when the schema declares no defaults,
960
+ // so a plain schema is genuinely non-mutating. The renderers refuse to
961
+ // synthesize a frame when this is true, because a frame built from mutated
962
+ // data would show the reader a value they never sent.
963
+ this._mutatesInput = !!(preprocess || options.coerceTypes || options.removeAdditional);
964
+ this._preprocess = preprocess;
965
+
966
+ // removeAdditional alone, the common parse-and-strip use: a verdict function
967
+ // that deletes unknown keys in the walk it already makes, where the pass
968
+ // above walks every object a second time just to find them. It answers
969
+ // the documents it accepts; anything it rejects takes the full path below,
970
+ // which removes, validates and reports exactly as before, so a rejected
971
+ // document is left as clean as it always was. The generator declines any
972
+ // schema where deleting during the walk could change an answer.
973
+ let fusedRemove = null;
974
+ if (preprocess && options.removeAdditional && !options.coerceTypes && !this._interpretOnly &&
975
+ !this._userFormats && !this._usesKeywords && !this._schemaStr.includes('"default"')) {
976
+ try {
977
+ fusedRemove = compileToJSCodegen(schemaObj, this._schemaMap.size > 0 ? this._schemaMap : null, null, { removeAdditional: true });
978
+ } catch {
979
+ fusedRemove = null;
980
+ }
981
+ }
982
+
983
+ // Detect if schema is "selective" -- doesn't recurse into arrays/deep objects.
984
+ const hasArrayTraversal =
985
+ schemaObj &&
986
+ (schemaObj.items ||
987
+ schemaObj.prefixItems ||
988
+ schemaObj.contains ||
989
+ (schemaObj.properties &&
990
+ Object.values(schemaObj.properties).some(
991
+ (p) => p && (p.items || p.prefixItems || p.contains),
992
+ )));
993
+ const useSimdjsonForLarge = !hasArrayTraversal;
994
+
995
+ // Build the generators the compile step left out, each only when something
996
+ // asks for it, and upgrade the shared cache entry. A first rejection needs
997
+ // one of the two, not both; building both was a millisecond of V8 compiling
998
+ // a generator nobody called. `undefined` means not built yet; `null` means
999
+ // the compiler declined. Conflating those is what once cost every schema
1000
+ // its generated error function for the life of the process, so they stay
1001
+ // apart, and `full` is set only once both exist.
1002
+ const _upgradeCacheEntry = () => {
1003
+ if (this._userFormats) return;
1004
+ const entry = _compileCache.get(mapKey);
1005
+ if (entry && entry.jsFn === jsFn) {
1006
+ if (jsCombinedFn !== undefined) entry.combined = jsCombinedFn;
1007
+ if (jsErrFn !== undefined) entry.errFn = jsErrFn;
1008
+ if (jsCombinedFn !== undefined && jsErrFn !== undefined) entry.full = true;
1009
+ }
1010
+ };
1011
+ const _buildCombined = () => {
1012
+ if (jsCombinedFn !== undefined) return;
1013
+ jsCombinedFn = compileToJSCombined(schemaObj, VALID_RESULT, sm, this._userFormats) || null;
1014
+ _upgradeCacheEntry();
1015
+ };
1016
+ const _buildErr = () => {
1017
+ if (jsErrFn !== undefined) return;
1018
+ jsErrFn = compileToJSCodegenWithErrors(schemaObj, sm, this._userFormats) || null;
1019
+ _upgradeCacheEntry();
1020
+ };
1021
+
1022
+ if (jsFn) {
1023
+ _codegen.installPaths.call(this, {
1024
+ jsFn, _isCodegen, preprocess, fusedRemove, options, schemaObj, useSimdjsonForLarge, _buildCombined, _buildErr,
1025
+ combined: () => jsCombinedFn, err: () => jsErrFn,
1026
+ });
1027
+ } else {
1028
+ // No JS codegen: the interpreted engine, with or without the addon. The
1029
+ // buffer APIs come from _installBufferApis on first use, as they do on
1030
+ // every other path.
1031
+ const { createInterpreter } = require('./interpreter');
1032
+ const interp = createInterpreter(schemaObj, {
1033
+ schemaMap: this._schemaMap.size > 0 ? this._schemaMap : null,
1034
+ formats: this._userFormats,
1035
+ v1: isV1Dialect(schemaObj),
1036
+ keywords: this._keywords,
1037
+ });
1038
+ this._engine = 'interpreter';
1039
+ if (!preprocess) this._fastVerdict = (d) => interp.isValid(d);
1040
+ // abortEarly is a documented contract, not a property of whichever engine
1041
+ // answered: it promises the frozen ATA9000 stub instead of a detailed
1042
+ // error, so code that branches on it has to behave the same with and
1043
+ // without code generation. Taking the verdict path here also skips
1044
+ // building the errors the caller said it did not want.
1045
+ const run = options.abortEarly
1046
+ ? (preprocess
1047
+ ? (data) => { preprocess(data); return interp.isValid(data) ? VALID_RESULT : ABORT_EARLY_RESULT; }
1048
+ : (data) => (interp.isValid(data) ? VALID_RESULT : ABORT_EARLY_RESULT))
1049
+ : (preprocess
1050
+ ? (data) => { preprocess(data); return interp.validate(data); }
1051
+ : (data) => interp.validate(data));
1052
+ this.validate = run;
1053
+ _bindVerdict(this, this._fastVerdict
1054
+ ? this._fastVerdict
1055
+ : (data) => run(data).valid);
1056
+ this.validateJSON = (jsonStr) => {
1057
+ let data;
1058
+ try {
1059
+ data = JSON.parse(jsonStr);
1060
+ } catch (e) {
1061
+ if (!(e instanceof SyntaxError)) throw e;
1062
+ return _jsonSyntaxRejection(e);
1063
+ }
1064
+ return run(data);
1065
+ };
1066
+ this.isValidJSON = (jsonStr) => this.validateJSON(jsonStr).valid;
1067
+ }
1068
+
1069
+ // Error presentation, one lazy layer: declaration-order sorting, rich
1070
+ // enrichment (received value, suggestions, source frames, docUrl), or the
1071
+ // raw v0.14 shape under `richErrors: false`. All of it is work a caller
1072
+ // that only reads `.valid` never sees, so it runs on first access to
1073
+ // `.errors` and is cached. One wrapper, one allocation per rejection.
1074
+ if (this.validate) {
1075
+ const inner = this.validate;
1076
+ // Loaded on the first error read rather than at compile: a process that
1077
+ // only ever accepts never needs it.
1078
+ const enrich = this._richErrors ? _enrichLazy : null;
1079
+ const root = this._schemaObj;
1080
+ const self = this;
1081
+ this.validate = (data) => {
1082
+ const result = inner(data);
1083
+ // abortEarly returns the shared ATA9000 stub; preserve it as-is so the
1084
+ // perf fast path stays allocation-free and the documented code stays stable.
1085
+ if (result && result.valid === false && result !== ABORT_EARLY_RESULT) {
1086
+ // The raw input travels with the rejection when validateJSON set one.
1087
+ // The map it implies is built on first access to `.errors`, so a
1088
+ // caller reading only `.valid` does not pay for a document walk.
1089
+ const rawInput = enrich ? self._lastRawInput : null;
1090
+ // One instance of a class with prototype accessors. An object
1091
+ // literal with a getter here cost a closure plus an accessor
1092
+ // definition on every rejection, several hundred nanoseconds
1093
+ // before any error was read.
1094
+ return new RichRejection(result, data, rawInput, self, root, enrich);
1095
+ }
1096
+ return result;
1097
+ };
1098
+
1099
+ // validateJSON also enriches: set _lastRawInput so the position cache
1100
+ // can lazily build a map for dataFrame attachment. Only validateJSON
1101
+ // wires this — validate(data) takes a pre-parsed object, by design.
1102
+ if (this._richErrors && this.validateJSON) {
1103
+ const innerJson = this.validateJSON;
1104
+ this.validateJSON = (jsonStr) => {
1105
+ // The inner path reads _lastRawInput to hand the raw text to the
1106
+ // rejection it builds. Cleared as soon as it returns: the rejection
1107
+ // carries the text itself, so nothing outlives the call.
1108
+ this._lastRawInput = jsonStr;
1109
+ let result;
1110
+ try {
1111
+ result = innerJson(jsonStr);
1112
+ } finally {
1113
+ this._lastRawInput = null;
1114
+ }
1115
+ // Every diagnostic the text path adds is deferred. Deciding here
1116
+ // whether there is anything to add would mean reading `result.errors`,
1117
+ // and on the codegen path that realizes the inner lazy layer, which is
1118
+ // the document walk this exists to avoid.
1119
+ if (result && result.valid === false) {
1120
+ return new LazyJsonRejection(result, jsonStr, this, enrich);
1121
+ }
1122
+ return result;
1123
+ };
1124
+ }
1125
+ }
1126
+
1127
+ // validate() resolves a typed `data` on success: the validated input, after
1128
+ // any in-place coercion/defaults. This matches the ValidationResult<T>
1129
+ // contract. isValidObject() and abortEarly stay allocation-free for hot
1130
+ // paths that only need a boolean.
1131
+ if (this.validate) {
1132
+ const _bare = this.validate;
1133
+ this.validate = fusedRemove
1134
+ // A document the removing verdict accepts is answered here, one call
1135
+ // deep; anything else takes the full path, see fusedRemove above.
1136
+ ? (data) => {
1137
+ if (fusedRemove(data)) return { valid: true, data, errors: VALID_RESULT.errors };
1138
+ const r = _bare(data);
1139
+ return (r.valid === true && r.data === undefined)
1140
+ ? { valid: true, data, errors: r.errors }
1141
+ : r;
1142
+ }
1143
+ : (data) => {
1144
+ const r = _bare(data);
1145
+ return (r.valid === true && r.data === undefined)
1146
+ ? { valid: true, data, errors: r.errors }
1147
+ : r;
1148
+ };
1149
+ }
1150
+
1151
+ // Custom error messages: if any subschema declares an `errorMessage`
1152
+ // keyword, install an outermost decorator that overrides the `message`
1153
+ // field of the errors it owns. Gated on a one-time scan so schemas without
1154
+ // errorMessage keep the validate hot path untouched. Layered after rich
1155
+ // enrichment so `code`/`keyword`/`path` are already final and only the
1156
+ // human-facing message changes.
1157
+ {
1158
+ const emLib = require('./error-messages');
1159
+ const schemaStr = this._schemaStr || (this._schemaObj ? JSON.stringify(this._schemaObj) : '');
1160
+ if (emLib.schemaHasErrorMessages(schemaStr)) {
1161
+ const root = this._schemaObj;
1162
+ const wrap = (inner) => (arg) => {
1163
+ const result = inner(arg);
1164
+ if (result && result.valid === false && result.errors && result.errors.length && result !== ABORT_EARLY_RESULT) {
1165
+ const overridden = emLib.applyErrorMessages(result.errors, root);
1166
+ if (overridden !== result.errors) return { valid: false, errors: overridden };
1167
+ }
1168
+ return result;
1169
+ };
1170
+ if (this.validate) this.validate = wrap(this.validate);
1171
+ if (this.validateJSON) this.validateJSON = wrap(this.validateJSON);
1172
+ // validateAndParse routes through self.validate on the codegen path, but
1173
+ // the native-only path returns directly from the addon — wrap it so both
1174
+ // paths get overrides. The result shape carries `value`, preserved here.
1175
+ if (this.validateAndParse) {
1176
+ const innerVP = this.validateAndParse;
1177
+ this.validateAndParse = (arg) => {
1178
+ const result = innerVP(arg);
1179
+ if (result && result.valid === false && result.errors && result.errors.length) {
1180
+ const overridden = emLib.applyErrorMessages(result.errors, root);
1181
+ if (overridden !== result.errors) return { valid: false, value: result.value, errors: overridden };
1182
+ }
1183
+ return result;
1184
+ };
1185
+ }
1186
+ }
1187
+ }
1188
+
1189
+ // Errors are paid for when read, not when produced. The full pipeline
1190
+ // above (error codegen, enrichment, custom messages, verbose) stays
1191
+ // intact, but validate() now answers the verdict from the boolean
1192
+ // engine and materializes `errors` through a getter on first access.
1193
+ // A caller that only reads `.valid`, which is every gateway check and
1194
+ // every benchmark, skips error construction entirely; a caller that
1195
+ // reads `.errors` pays once and the result is cached. Skipped when the
1196
+ // schema coerces or defaults (preprocess mutates before the verdict),
1197
+ // under abortEarly (already a frozen stub), and for $dynamicRef (the
1198
+ // boolean engine is not the authority there).
1199
+ // A check registered through _extendValidate joins here when it can: the
1200
+ // verdict comes from the generated function with the check compiled in,
1201
+ // and the check's own errors are appended when somebody reads them. A
1202
+ // rejection then costs what it costs without the check. Where this layer
1203
+ // is not installed, the end of this method wraps validate() instead.
1204
+ const _vx = this._validateTail !== null ? this._validateTail() : null;
1205
+ let _vxApplied = false;
1206
+ if (this._fastVerdict && !preprocess && !options.abortEarly && this.validate) {
1207
+ const _full = this.validate;
1208
+ const _fast = _vx ? _fuseTail(this._fastVerdict, _vx.check) : this._fastVerdict;
1209
+ const _extra = _vx ? _vx.errors : null;
1210
+ _vxApplied = true;
1211
+ const EMPTY_ERRORS = Object.freeze([]);
1212
+ const _verdictFallback = [{ keyword: 'validation', instancePath: '', schemaPath: '#', params: {}, message: 'schema validation failed' }];
1213
+ const _withExtra = (own, data) => {
1214
+ const more = _extra === null ? null : _extra(data);
1215
+ if (more && more.length) return own ? own.concat(more) : more;
1216
+ // The data changed between the verdict and this read; keep the
1217
+ // verdict and say so rather than inventing a specific error.
1218
+ return own || _verdictFallback;
1219
+ };
1220
+ const _buildErrors = (data) => {
1221
+ const r = _full(data);
1222
+ return _withExtra((r && r.valid === false && r.errors && r.errors.length) ? r.errors : null, data);
1223
+ };
1224
+ const _buildRawErrors = (data) => {
1225
+ const r = _full(data);
1226
+ let raw = null;
1227
+ if (r && r.valid === false) {
1228
+ raw = typeof r._ataRaw === 'function' ? r._ataRaw() : r.errors;
1229
+ if (!raw || !raw.length) raw = null;
1230
+ }
1231
+ return _withExtra(raw, data);
1232
+ };
1233
+ this.validate = (data) => {
1234
+ if (_fast(data)) return { valid: true, data, errors: EMPTY_ERRORS };
1235
+ return new LazyRejection(_buildErrors, data, _buildRawErrors);
1236
+ };
1237
+ }
1238
+ if (_vx && !_vxApplied && this.validate) {
1239
+ const inner = this.validate;
1240
+ const { check, errors } = _vx;
1241
+ this.validate = (data) => {
1242
+ const r = inner(data);
1243
+ if (r.valid && check(data)) return r;
1244
+ return new ExtendedRejection(r, data, errors);
1245
+ };
1246
+ }
1247
+
1248
+ // The scanner wraps whatever answers isValidJSON and validateJSON by now.
1249
+ if (this._jsFn && !this._preprocess) _codegen.installScanner.call(this, schemaObj, options);
1250
+
1251
+ // An extension registered through _extendValidate covers the JSON entry
1252
+ // points too. They are final here except for the scanner stubs above,
1253
+ // which rebind themselves on first use through _bindEntry, so the
1254
+ // extension is applied to whatever each one is now and again on rebind.
1255
+ if (_vx) {
1256
+ this._entryExt = _jsonEntryWrappers(_vx);
1257
+ for (const name of ['validateJSON', 'isValidJSON', 'validateAndParse']) {
1258
+ if (Object.prototype.hasOwnProperty.call(this, name) && typeof this[name] === 'function') _bindEntry(this, name, this[name]);
1259
+ }
1260
+ }
1261
+
1262
+
1263
+ _rememberInstance(this);
1264
+ }
1265
+
1266
+ // Which engine answers validate() for this schema: 'codegen' (generated
1267
+ // JS), 'closure' (the closure compiler, the boolean fallback), 'native'
1268
+ // (the C++ engine, only for some $dynamicRef schemas), or 'interpreter'.
1269
+ // A diagnostic: the answer is the same on every engine, the cost is not.
1270
+ engine() {
1271
+ this._ensureCompiled();
1272
+ return this._engine || 'interpreter';
1273
+ }
1274
+
1275
+ _ensureNative() {
1276
+ if (this._nativeReady) return;
1277
+ this._nativeReady = true;
1278
+ const native = getNative();
1279
+ if (!native) return;
1280
+ if (!this._schemaStr) this._schemaStr = JSON.stringify(this._schemaObj);
1281
+ let nativeSchemaStr = this._schemaStr;
1282
+ // A boolean root references nothing, so the registry has nowhere to go and
1283
+ // is left out; merging it used to throw on `true` and `false`.
1284
+ if (this._schemaMap.size > 0 && typeof this._schemaObj === 'object' && this._schemaObj !== null) {
1285
+ const merged = JSON.parse(this._schemaStr);
1286
+ if (!merged.$defs) merged.$defs = {};
1287
+ for (const [id, s] of this._schemaMap) {
1288
+ merged.$defs['__ext_' + id.replace(/[^a-zA-Z0-9]/g, '_')] = s;
1289
+ }
1290
+ nativeSchemaStr = JSON.stringify(merged);
1291
+ }
1292
+ this._compiled = new native.CompiledSchema(nativeSchemaStr);
1293
+ // The fast registry is a fixed array of slots in the addon, and registering
1294
+ // a distinct schema past the last one throws. It is an accelerator for the
1295
+ // buffer path, not a requirement, so a full registry leaves the slot at -1
1296
+ // and the buffer methods answer from the JS engine instead. Letting this
1297
+ // throw made every validator built after the 4096th unusable on that path.
1298
+ try {
1299
+ this._fastSlot = native.fastRegister(nativeSchemaStr);
1300
+ } catch {
1301
+ this._fastSlot = -1;
1302
+ }
1303
+ }
1304
+
1305
+ // The buffer APIs, installed on the first call to any of them. The schema
1306
+ // is compiled first, so the gate sees the final schema and the answers
1307
+ // below never run ahead of validate(). The shapes lib/buffer-gate.js lists,
1308
+ // and a validator the addon had no fast slot left for, answer through
1309
+ // validate() instead of the native walker: the same answers, slower. A
1310
+ // negative slot must never reach rawFastValidate, whose bounds check would
1311
+ // report a valid document as invalid.
1312
+ _installBufferApis(name) {
1313
+ this._ensureCompiled();
1314
+ const native = getNative();
1315
+ if (!native) throw new Error(`Native addon required for ${name}(). Use validate(), isValidObject() or validateJSON(), which do not need it.`);
1316
+ const { bufferNeedsSlowPath, installSlowBufferApis } = require('./buffer-gate.js');
1317
+ if (bufferNeedsSlowPath(this._schemaObj, this._schemaMap, this._keywords)) return installSlowBufferApis(this);
1318
+ this._ensureNative();
1319
+ const slot = this._fastSlot;
1320
+ if (!(slot >= 0)) return installSlowBufferApis(this);
1321
+ const bytes = (b, who) => {
1322
+ if (typeof b === 'string') return Buffer.from(b);
1323
+ if (b instanceof Uint8Array) return b;
1324
+ throw new TypeError(`${who}() requires a Buffer, Uint8Array, or string. For parsed objects, use isValidObject().`);
1325
+ };
1326
+ this.isValid = (b) => native.rawFastValidate(slot, bytes(b, 'isValid'));
1327
+ this.isValidPrepadded = (paddedBuffer, jsonLength) => native.rawFastValidate(slot, paddedBuffer, jsonLength);
1328
+ this.isValidParallel = (b) => native.rawParallelValidate(slot, bytes(b, 'isValidParallel'));
1329
+ this.isValidNDJSON = (b) => native.rawNDJSONValidate(slot, bytes(b, 'isValidNDJSON'));
1330
+ this.countValid = (b) => {
1331
+ const r = native.rawNDJSONValidate(slot, bytes(b, 'countValid'));
1332
+ let n = 0;
1333
+ for (let i = 0; i < r.length; i++) if (r[i]) n++;
1334
+ return n;
1335
+ };
1336
+ this.batchIsValid = (buffers) => {
1337
+ let n = 0;
1338
+ for (const b of buffers) {
1339
+ if (!(b instanceof Uint8Array)) throw new TypeError('batchIsValid() requires Buffer or Uint8Array elements');
1340
+ if (native.rawFastValidate(slot, b)) n++;
1341
+ }
1342
+ return n;
1343
+ };
1344
+ }
1345
+
1346
+ addSchema(schema) {
1347
+ if (this._initialized) {
1348
+ throw new Error('Cannot add schema after compilation — call addSchema() before validate()')
1349
+ }
1350
+ if (!schema || !schema.$id) {
1351
+ throw new Error('Schema must have $id')
1352
+ }
1353
+ // Normalize a copy so the caller's object is never mutated. A document
1354
+ // without a dialect of its own is read under the root's draft.
1355
+ const root = this._schemaObj
1356
+ const rootIsDraft7 = !!(root && typeof root === 'object' && typeof root.$schema === 'string' &&
1357
+ (root.$schema === 'http://json-schema.org/draft-07/schema#' || root.$schema === 'http://json-schema.org/draft-07/schema'))
1358
+ const normalized = _normalizeCallerSchema(schema, rootIsDraft7)
1359
+ this._ownSchemaMap()
1360
+ this._schemaMap.set(normalized.$id, normalized)
1361
+ }
1362
+
1363
+ // buildSchemaMap hands the same map to every validator built from the same
1364
+ // registry. Take a private copy before writing to it.
1365
+ _ownSchemaMap() {
1366
+ if (!this._schemaMapShared) return
1367
+ this._schemaMap = new Map(this._schemaMap)
1368
+ this._schemaMapShared = false
1369
+ }
1370
+
1371
+ _ensureCodegen() {
1372
+ if (this._jsFn) return;
1373
+ // A validator that rewrites its input cannot use the binding below: that
1374
+ // one answers from the compiled function alone and would skip the rewrite,
1375
+ // so isValidObject() and validate() would disagree.
1376
+ if (this._needsPreprocess() || this._usesKeywords) {
1377
+ this._ensureCompiled();
1378
+ return;
1379
+ }
1380
+ this._ensureVocabularies();
1381
+ if (this._interpretOnly || _codegen === null || (typeof process !== 'undefined' && process.env && process.env.ATA_FORCE_NAPI)) return;
1382
+ _codegen.compileVerdict.call(this);
1383
+ }
1384
+
1385
+ // Load a pre-compiled standalone module. Zero schema compilation.
1386
+ // No NAPI, no native compile — pure JS. Startup in microseconds.
1387
+ // Usage: const v = Validator.fromStandalone(require('./compiled.js'), schema, opts)
1388
+ static fromStandalone(mod, schema, opts) {
1389
+ // It loads what Validator.bundleStandalone() writes, and compiles an error
1390
+ // function when the module carries none, so it goes with those methods.
1391
+ if (_codegen === null) throw new TypeError('Validator.fromStandalone is not part of ata-validator/lite, like the bundle methods it loads for. Import it from ata-validator, or import a module from `ata build` directly.');
1392
+ const options = opts || {};
1393
+ const schemaObj = typeof schema === "string" ? JSON.parse(schema) : schema;
1394
+
1395
+ // Create a lightweight instance — skip NAPI compile entirely
1396
+ const v = Object.create(Validator.prototype);
1397
+ v._jsFn = mod.boolFn;
1398
+ v._compiled = null;
1399
+ v._fastSlot = -1;
1400
+
1401
+ // Mutators
1402
+ const applyDefaults = buildDefaultsApplier(schemaObj);
1403
+ const applyCoerce = options.coerceTypes ? buildCoercer(schemaObj) : null;
1404
+ const applyRemove = options.removeAdditional
1405
+ ? buildRemover(schemaObj)
1406
+ : null;
1407
+ const mutators = [applyRemove, applyCoerce, applyDefaults].filter(Boolean);
1408
+ const preprocess =
1409
+ mutators.length === 0
1410
+ ? null
1411
+ : mutators.length === 1
1412
+ ? mutators[0]
1413
+ : (data) => {
1414
+ for (let i = 0; i < mutators.length; i++) mutators[i](data);
1415
+ };
1416
+ v._preprocess = preprocess;
1417
+
1418
+ // Error function — use pre-compiled from standalone if available, else compile
1419
+ let errFn = (d) => ({
1420
+ valid: false,
1421
+ errors: [
1422
+ { code: "validation_failed", path: "", message: "validation failed" },
1423
+ ],
1424
+ });
1425
+ if (mod.errFn) {
1426
+ errFn = (d) => mod.errFn(d, true);
1427
+ } else {
1428
+ const jsErrFn = compileToJSCodegenWithErrors(schemaObj);
1429
+ if (jsErrFn) {
1430
+ try {
1431
+ jsErrFn({}, true);
1432
+ errFn = (d) => jsErrFn(d, true);
1433
+ } catch {}
1434
+ }
1435
+ }
1436
+
1437
+ // Hybrid or speculative
1438
+ const hybridFn = mod.hybridFactory
1439
+ ? mod.hybridFactory(VALID_RESULT, errFn)
1440
+ : null;
1441
+
1442
+ v.validate = hybridFn
1443
+ ? preprocess
1444
+ ? (data) => {
1445
+ preprocess(data);
1446
+ return hybridFn(data);
1447
+ }
1448
+ : hybridFn
1449
+ : preprocess
1450
+ ? (data) => {
1451
+ preprocess(data);
1452
+ return mod.boolFn(data) ? VALID_RESULT : errFn(data);
1453
+ }
1454
+ : (data) => (mod.boolFn(data) ? VALID_RESULT : errFn(data));
1455
+ {
1456
+ const _bare = v.validate;
1457
+ v.validate = (data) => {
1458
+ const r = _bare(data);
1459
+ return (r.valid === true && r.data === undefined)
1460
+ ? { valid: true, data, errors: r.errors }
1461
+ : r;
1462
+ };
1463
+ }
1464
+ v.isValidObject = mod.boolFn;
1465
+ v.isValidJSON = (jsonStr) => {
1466
+ try {
1467
+ return mod.boolFn(JSON.parse(jsonStr));
1468
+ } catch {
1469
+ return false;
1470
+ }
1471
+ };
1472
+ v.validateJSON = (jsonStr) => {
1473
+ try {
1474
+ const obj = JSON.parse(jsonStr);
1475
+ return hybridFn
1476
+ ? hybridFn(obj)
1477
+ : mod.boolFn(obj)
1478
+ ? VALID_RESULT
1479
+ : errFn(obj);
1480
+ } catch {
1481
+ return {
1482
+ valid: false,
1483
+ errors: [{ code: "invalid_json", path: "", message: "invalid JSON" }],
1484
+ };
1485
+ }
1486
+ };
1487
+
1488
+ // Standard Schema V1
1489
+ Object.defineProperty(v, "~standard", {
1490
+ value: Object.freeze({
1491
+ version: 1,
1492
+ vendor: "ata-validator",
1493
+ validate(value) {
1494
+ const result = v.validate(value);
1495
+ if (result.valid) return { value };
1496
+ return {
1497
+ issues: result.errors.map((e) => ({
1498
+ message: e.message,
1499
+ path: parsePointerPath(e.instancePath),
1500
+ })),
1501
+ };
1502
+ },
1503
+ }),
1504
+ writable: false,
1505
+ enumerable: false,
1506
+ configurable: false,
1507
+ });
1508
+
1509
+ return v;
1510
+ }
1511
+ }
1512
+
1513
+ // One-shot validate. It goes through a Validator like every other entry
1514
+ // point, so the result has one shape everywhere: `data` on success, errors
1515
+ // with a code, a keyword and an instancePath. From the first native binding
1516
+ // until 1.33.3 it handed the schema straight to the native engine whenever
1517
+ // the addon was loaded, which is every default install on a supported
1518
+ // platform, and returned that engine's raw result: numeric codes, `path`
1519
+ // instead of `instancePath`, no keyword, and no `data`. The compile cache keeps
1520
+ // a schema passed again from compiling again.
1521
+ function validate(schema, data) {
1522
+ if (schema instanceof Validator) return schema.validate(data);
1523
+ const v = new Validator(typeof schema === "string" ? JSON.parse(schema) : schema);
1524
+ return v.validate(data);
1525
+ }
1526
+
1527
+ // Async validation for schemas built with `t.refine(...)`. Structural
1528
+ // validation runs synchronously first; refinements are awaited only when the
1529
+ // value is structurally valid (a refinement body may assume the right shape).
1530
+ // Accepts a schema literal or an existing Validator instance plus its schema.
1531
+ // Returns a Promise<ValidationResult>.
1532
+ async function validateAsync(schemaOrValidator, data) {
1533
+ const refineLib = require('./refine');
1534
+ let validator, schema;
1535
+ if (schemaOrValidator instanceof Validator) {
1536
+ validator = schemaOrValidator;
1537
+ schema = validator._schemaObj;
1538
+ } else {
1539
+ schema = schemaOrValidator;
1540
+ validator = new Validator(schema);
1541
+ }
1542
+ const structural = validator.validate(data);
1543
+ if (!structural.valid) return structural;
1544
+ const refinements = refineLib.getRefinements(schema);
1545
+ if (!refinements) return structural;
1546
+ const issues = await refineLib.runRefinements(refinements, structural.data !== undefined ? structural.data : data);
1547
+ if (issues.length) return { valid: false, errors: issues };
1548
+ return structural;
1549
+ }
1550
+
1551
+ // parseAsync resolves to the validated data, or rejects with an Error whose
1552
+ // `.errors` carries the ValidationError list. Mirrors the parse/validate split
1553
+ // used by Zod-style callers.
1554
+ async function parseAsync(schemaOrValidator, data) {
1555
+ const result = await validateAsync(schemaOrValidator, data);
1556
+ if (result.valid) return result.data !== undefined ? result.data : data;
1557
+ const err = new Error('ata: async validation failed');
1558
+ err.errors = result.errors;
1559
+ throw err;
1560
+ }
1561
+
1562
+ function version() {
1563
+ const native = getNative();
1564
+ if (native) return native.version();
1565
+ try { return require("./version"); } catch { return "unknown"; }
1566
+ }
1567
+
1568
+ // Static AOT entry points are thin lazy-loaders into `lib/aot.js`. The
1569
+ // implementation files (and the `fs`/`path` reads they perform) only enter
1570
+ // the process when one of these is actually called. See `lib/aot.js` for the
1571
+ // generated module shapes; browser bundles get `lib/aot.browser.js` (a stub
1572
+ // that throws) via the package.json `browser` field.
1573
+ function _aot() {
1574
+ if (_codegen === null) throw new TypeError('The ahead-of-time bundle methods are not part of ata-validator/lite. Import them from ata-validator.');
1575
+ return _codegen.aot();
1576
+ }
1577
+ Validator.bundle = function (schemas, opts) {
1578
+ return _aot().bundle(Validator, schemas, opts);
1579
+ };
1580
+
1581
+ Validator.bundleStandalone = function (schemas, opts) {
1582
+ return _aot().bundleStandalone(Validator, schemas, opts);
1583
+ };
1584
+
1585
+ Validator.bundleCompact = function (schemas, opts) {
1586
+ return _aot().bundleCompact(Validator, schemas, opts);
1587
+ };
1588
+
1589
+ Validator.loadBundle = function (mods, schemas, opts) {
1590
+ return _aot().loadBundle(Validator, mods, schemas, opts);
1591
+ };
1592
+
1593
+ // simdjson when the addon is there, JSON.parse otherwise; the addon loads on
1594
+ // the first call rather than when the package is required.
1595
+ function parseJSON(input) {
1596
+ const native = getNative();
1597
+ return native ? native.parseJSON(input) : JSON.parse(input);
1598
+ }
1599
+
1600
+ // Ultra-fast compile: returns validate function directly, no Validator wrapper
1601
+ // WeakMap cached — second call with same schema object is ~3ns
1602
+ const _compileFnCache = new WeakMap();
1603
+ function compile(schema, opts) {
1604
+ if (!opts && typeof schema === 'object' && schema !== null) {
1605
+ const hit = _compileFnCache.get(schema);
1606
+ if (hit) return hit;
1607
+ }
1608
+ const v = new Validator(schema, opts);
1609
+ v._ensureCompiled();
1610
+ const fn = v.validate;
1611
+ if (!opts && typeof schema === 'object' && schema !== null) {
1612
+ _compileFnCache.set(schema, fn);
1613
+ }
1614
+ return fn;
1615
+ }
1616
+
1617
+ // The renderers, TypeScript generation, the output formats, the retry message
1618
+ // and the suggestion helper are exported by index.js. None of them is called
1619
+ // by a Validator, so ata-validator/lite leaves them out.
1620
+
1621
+ // Authoring helper: identity at runtime. Its only job is to attach the
1622
+ // JSONSchema type (see index.d.ts) to an inline schema object so TypeScript
1623
+ // gives autocomplete and value checking while authoring. Returns the schema
1624
+ // untouched so it can be passed straight to Validator, toStandaloneModule, etc.
1625
+ function defineSchema (schema) {
1626
+ return schema;
1627
+ }
1628
+
1629
+ // Public methods start as memoized accessors on the prototype. A fresh
1630
+ // Validator allocates none of them; the first read of a method builds the
1631
+ // bound closure, stores it on the instance as an ordinary writable property
1632
+ // and returns it. The setter keeps the compile step's plain assignments
1633
+ // (`this.validate = fn`) working before the getter has ever run. Detached
1634
+ // use (`const f = v.validate`) keeps working because the closure binds the
1635
+ // instance.
1636
+ // Standard Schema V1. Built on first read, then pinned to the instance with
1637
+ // the same descriptor the constructor used to install eagerly.
1638
+ Object.defineProperty(Validator.prototype, "~standard", {
1639
+ configurable: true,
1640
+ get() {
1641
+ const self = this;
1642
+ const std = Object.freeze({
1643
+ version: 1,
1644
+ vendor: "ata-validator",
1645
+ validate(value) {
1646
+ const result = self.validate(value);
1647
+ if (result.valid) {
1648
+ return { value };
1649
+ }
1650
+ // An issue carries a message and a path and nothing else, so the
1651
+ // suggestion and source-frame work the rich error path does would be
1652
+ // thrown away here. Take the raw, schema-ordered list when the result
1653
+ // offers one; fall back to the public list otherwise.
1654
+ const raw = typeof result._ataRaw === 'function' ? result._ataRaw() : result.errors;
1655
+ const issues = new Array(raw.length);
1656
+ for (let i = 0; i < raw.length; i++) {
1657
+ const err = raw[i];
1658
+ const path = err.instancePath != null ? err.instancePath : (err.path || '');
1659
+ let message = err.message;
1660
+ if (!message) {
1661
+ // The native engine reports numeric codes without a message; the
1662
+ // enrich pass knows how to word those. Rare, so required lazily.
1663
+ message = require('./enrich-error').enrich(err, {}).message;
1664
+ }
1665
+ issues[i] = { message, path: parsePointerPath(path) };
1666
+ }
1667
+ return { issues };
1668
+ },
1669
+ });
1670
+ Object.defineProperty(this, "~standard", {
1671
+ value: std,
1672
+ writable: false,
1673
+ enumerable: false,
1674
+ configurable: false,
1675
+ });
1676
+ return std;
1677
+ },
1678
+ });
1679
+
1680
+ // The error resolvers run only after a verdict function has said no. If one
1681
+ // answers valid anyway, two generators disagree, and the verdict is the one
1682
+ // to keep: returning the resolver's answer is how a vacuous combined function
1683
+ // turned a rejection into an acceptance in validateJSON. The disagreement is
1684
+ // reported as a generic failure rather than hidden.
1685
+ const _VERDICT_DISAGREES = Object.freeze({
1686
+ valid: false,
1687
+ errors: Object.freeze([Object.freeze({ keyword: 'validation', instancePath: '', schemaPath: '#', params: Object.freeze({}), message: 'schema validation failed' })]),
1688
+ });
1689
+ function _mustReject(r) {
1690
+ return r && r.valid === false ? r : _VERDICT_DISAGREES;
1691
+ }
1692
+
1693
+ // Install the verdict method. Every place that binds isValidObject comes
1694
+ // through here, so a check registered with _extendVerdict survives the method
1695
+ // being replaced as the validator compiles further, which it does more than
1696
+ // once over its life.
1697
+ function _bindVerdict(self, fn) {
1698
+ const resolve = self._verdictTail;
1699
+ if (resolve !== null && typeof fn === 'function') {
1700
+ const tail = resolve();
1701
+ if (typeof tail === 'function') fn = _fuseTail(fn, tail);
1702
+ }
1703
+ self.isValidObject = fn;
1704
+ }
1705
+
1706
+ // Bind one of the JSON entry points, through the extension wrapper when there is one.
1707
+ function _bindEntry(self, name, fn) {
1708
+ const ext = self._entryExt;
1709
+ self[name] = ext !== null && ext[name] ? ext[name](fn) : fn;
1710
+ }
1711
+
1712
+ // The JSON entry points under an extension: the schema answers first, and only
1713
+ // text it accepts is parsed for the check, so a rejection costs nothing extra.
1714
+ function _jsonEntryWrappers({ check, errors }) {
1715
+ const parse = (text) => JSON.parse(typeof text === 'string' ? text : new TextDecoder().decode(text));
1716
+ return {
1717
+ validateJSON: (inner) => (text) => {
1718
+ const res = inner(text);
1719
+ if (!res.valid) return res;
1720
+ let data;
1721
+ try { data = parse(text); } catch { return res; }
1722
+ if (check(data)) return res;
1723
+ const e = errors(data);
1724
+ return e && e.length ? { valid: false, errors: e } : { valid: false, errors: [_EXT_FALLBACK] };
1725
+ },
1726
+ isValidJSON: (inner) => (text) => {
1727
+ if (!inner(text)) return false;
1728
+ let data;
1729
+ try { data = parse(text); } catch { return true; }
1730
+ return check(data);
1731
+ },
1732
+ validateAndParse: (inner) => (text) => {
1733
+ const res = inner(text);
1734
+ if (!res.valid) return res;
1735
+ if (check(res.value)) return res;
1736
+ const e = errors(res.value);
1737
+ return { valid: false, value: res.value, errors: e && e.length ? e : [_EXT_FALLBACK] };
1738
+ },
1739
+ };
1740
+ }
1741
+ const _EXT_FALLBACK = Object.freeze({ keyword: 'validation', instancePath: '', schemaPath: '#', params: {}, message: 'schema validation failed' });
1742
+
1743
+ // A verdict function that also runs `tail` on what it accepts. The generated
1744
+ // function can take the check in place of its final `return true`, one call
1745
+ // per document; anything else is composed.
1746
+ function _fuseTail(fn, tail) {
1747
+ const fused = typeof fn._withTail === 'function' ? fn._withTail(tail) : null;
1748
+ return fused || ((d) => fn(d) && tail(d));
1749
+ }
1750
+
1751
+ // The rejection validate() returns on the paths where an extension check could
1752
+ // not join the lazy layer: the inner result, plus the check's errors appended
1753
+ // on first read. `inner` may itself be valid, when only the check failed.
1754
+ class ExtendedRejection {
1755
+ constructor(inner, data, collect) {
1756
+ this.valid = false;
1757
+ this._inner = inner;
1758
+ this._data = data;
1759
+ this._collect = collect;
1760
+ this._errors = null;
1761
+ }
1762
+ toJSON() {
1763
+ return { valid: false, errors: this.errors };
1764
+ }
1765
+ _ataRaw() {
1766
+ const inner = this._inner;
1767
+ const more = this._collect(this._data) || [];
1768
+ const raw = inner.valid ? more : (typeof inner._ataRaw === 'function' ? inner._ataRaw() : inner.errors).concat(more);
1769
+ return raw.length ? raw : [_EXT_FALLBACK];
1770
+ }
1771
+ }
1772
+ Object.defineProperty(ExtendedRejection.prototype, 'errors', {
1773
+ enumerable: true,
1774
+ configurable: true,
1775
+ get() {
1776
+ if (this._errors === null) {
1777
+ const inner = this._inner;
1778
+ const more = this._collect(this._data) || [];
1779
+ const all = inner.valid ? more : inner.errors.concat(more);
1780
+ this._errors = all.length ? all : [_EXT_FALLBACK];
1781
+ }
1782
+ return this._errors;
1783
+ },
1784
+ });
1785
+
1786
+ // For wrappers that enforce a check the schema does not carry, such as the
1787
+ // `instanceof` keyword of @ata-project/keywords. `resolve` is called whenever
1788
+ // the verdict method is bound, which is after the schema has been normalized,
1789
+ // and returns the check, a function of the document that answers true or
1790
+ // false, or null when there is nothing to add. Only isValidObject takes it;
1791
+ // the other entry points, which report errors, stay the wrapper's to handle.
1792
+ //
1793
+ // Before this, such a wrapper had to hold isValidObject behind an accessor so
1794
+ // that the validator's own rebinding could not drop its check, and every call
1795
+ // paid for the accessor and two more calls: 10.1 ns against 4.2 on a document
1796
+ // the schema rejects at its third property.
1797
+ Validator.prototype._verdictTail = null;
1798
+ Validator.prototype._validateTail = null;
1799
+ Validator.prototype._entryExt = null;
1800
+
1801
+ // Let `new Validator(schema)` with the same schema object return this instance.
1802
+ // Only an instance built without options may answer that call: one built with
1803
+ // options (richErrors: false, coerceTypes, formats, ...) would hand its options
1804
+ // to a caller that asked for none, and an extended one would enforce checks the
1805
+ // caller never registered. Both the caller's object and the normalized one are
1806
+ // keys, since a later caller passes the former.
1807
+ function _rememberInstance(self) {
1808
+ if (!self._noOpts || self._verdictTail !== null || self._validateTail !== null) return;
1809
+ const raw = self._rawSchema;
1810
+ if (raw && typeof raw === 'object' && !_identityCache.has(raw)) _identityCache.set(raw, self);
1811
+ const obj = self._schemaObj;
1812
+ if (obj !== raw && obj && typeof obj === 'object' && !_identityCache.has(obj)) _identityCache.set(obj, self);
1813
+ }
1814
+
1815
+ // An extended validator answers differently from a plain one for the same
1816
+ // schema, so it must not be the instance `new Validator(sameSchema)` hands out.
1817
+ function _leaveIdentityCache(self) {
1818
+ self._noOpts = false;
1819
+ // Nothing is registered before the first compile, so there is nothing to
1820
+ // take back.
1821
+ if (!self._initialized && self._jsFn === null) return;
1822
+ const raw = self._rawSchema;
1823
+ if (raw && typeof raw === 'object' && _identityCache.get(raw) === self) _identityCache.delete(raw);
1824
+ // The compiled form is cached too, at the end of the first compile. Read it
1825
+ // only if it is already materialized: reading it otherwise builds it.
1826
+ if (Object.prototype.hasOwnProperty.call(self, '_schemaObj')) {
1827
+ const obj = self._schemaObj;
1828
+ if (obj && typeof obj === 'object' && _identityCache.get(obj) === self) _identityCache.delete(obj);
1829
+ }
1830
+ }
1831
+
1832
+ // The same kind of extension for validate(): `resolve` returns { check, errors }
1833
+ // or null, where `errors(data)` lists the check's own errors, or returns null
1834
+ // when there are none. The check's errors come after the schema's, and a value
1835
+ // that fails only the check is rejected with the check's errors alone. Must be
1836
+ // called before validate() is first used, which is when it is compiled; later
1837
+ // calls throw rather than being silently ignored.
1838
+ Validator.prototype._extendValidate = function (resolve) {
1839
+ if (typeof resolve !== 'function') throw new TypeError('_extendValidate expects a function');
1840
+ if (this._initialized) throw new Error('_extendValidate must be called before the validator compiles');
1841
+ _leaveIdentityCache(this);
1842
+ const prev = this._validateTail;
1843
+ this._validateTail = prev === null ? resolve : () => {
1844
+ const a = prev(), b = resolve();
1845
+ if (!a) return b;
1846
+ if (!b) return a;
1847
+ return {
1848
+ check: (d) => a.check(d) && b.check(d),
1849
+ errors: (d) => {
1850
+ const x = a.errors(d), y = b.errors(d);
1851
+ if (!x) return y;
1852
+ if (!y) return x;
1853
+ return x.concat(y);
1854
+ },
1855
+ };
1856
+ };
1857
+ return this;
1858
+ };
1859
+
1860
+ // Both extensions in one call, from one resolver that returns { check, errors }
1861
+ // or null. This is the form @ata-project/keywords uses: registering costs one
1862
+ // closure, where wrapping the five entry points cost a closure per entry point,
1863
+ // the wrappers themselves and an accessor, most of what building a wrapped
1864
+ // validator took.
1865
+ // parse(data): validate, then return a copy holding only what the schema
1866
+ // declares, the way the parse() export of an ahead-of-time module does, with
1867
+ // the same emitter behind both. Building the copy from the schema's own key
1868
+ // list costs less than finding and deleting unknown keys, and leaves the
1869
+ // caller's object alone. Where the key set cannot be proven (a $ref it cannot
1870
+ // inline, patternProperties, an open object) the method declines with an
1871
+ // error instead of guessing, as the module ships no parse() there; so it does
1872
+ // under options that rewrite input, whose answers a copy would not reproduce.
1873
+ Validator.prototype.parse = function (data) {
1874
+ let fn = this._parseFn;
1875
+ if (fn === undefined) {
1876
+ fn = _buildParse(this);
1877
+ Object.defineProperty(this, '_parseFn', { value: fn, writable: true, configurable: true, enumerable: false });
1878
+ }
1879
+ return fn(data);
1880
+ };
1881
+
1882
+ function _buildParse(self) {
1883
+ const decline = (why, instead = 'Use validate() with removeAdditional instead.') => () => {
1884
+ throw new TypeError(`parse() is not available for this validator: ${why}. ${instead}`);
1885
+ };
1886
+ const o = self._options;
1887
+ // Each refusal names its own reason: a caller who hit the combined one could
1888
+ // not tell a coercion option from a keyword package, and read a deliberate
1889
+ // refusal as a failure of valid data.
1890
+ if (o.coerceTypes) return decline('coerceTypes rewrites the input before it is checked');
1891
+ if (o.removeAdditional === 'all') return decline("removeAdditional: 'all' decides the kept keys at check time");
1892
+ if (self._usesKeywords) return decline('custom keywords are in use, and what they accept is not known to the copy');
1893
+ // Checks added to the validator (withKeywords from @ata-project/keywords
1894
+ // adds instanceof and typeof) only narrow what passes; they do not change
1895
+ // which keys the schema declares. The verdict then goes through
1896
+ // isValidObject, which runs them, and a property they check with instanceof
1897
+ // is carried over as it is (see clone-emit).
1898
+ const extended = self._verdictTail !== null || self._validateTail !== null;
1899
+ if (_codegen === null) return decline('ata-validator/lite has no code generator to build the copy with', 'Use parse() from ata-validator, or validate(); a valid result carries the input as it is.');
1900
+ return _codegen.buildParse(self, decline, extended);
1901
+ }
1902
+
1903
+ Validator.prototype._extendChecks = function (resolve) {
1904
+ if (typeof resolve !== 'function') throw new TypeError('_extendChecks expects a function');
1905
+ this._extendValidate(resolve);
1906
+ return this._extendVerdict(() => {
1907
+ const x = resolve();
1908
+ return x ? x.check : null;
1909
+ });
1910
+ };
1911
+
1912
+ // Whether this instance enforces a check its schema does not carry. The
1913
+ // ahead-of-time emitters build a module from the schema alone, so they refuse
1914
+ // an instance that says yes rather than emit one that accepts too much.
1915
+ // Reading it resolves the registered checks; only an emitter reads it.
1916
+ Object.defineProperty(Validator.prototype, '_externalChecks', {
1917
+ configurable: true,
1918
+ get() {
1919
+ if (this._validateTail !== null && this._validateTail()) return true;
1920
+ if (this._verdictTail !== null && typeof this._verdictTail() === 'function') return true;
1921
+ return false;
1922
+ },
1923
+ });
1924
+
1925
+ Validator.prototype._extendVerdict = function (resolve) {
1926
+ if (typeof resolve !== 'function') throw new TypeError('_extendVerdict expects a function');
1927
+ _leaveIdentityCache(this);
1928
+ const prev = this._verdictTail;
1929
+ this._verdictTail = prev === null ? resolve : () => {
1930
+ const a = prev(), b = resolve();
1931
+ if (typeof a !== 'function') return b;
1932
+ if (typeof b !== 'function') return a;
1933
+ return (d) => a(d) && b(d);
1934
+ };
1935
+ // A method bound before this call was bound without the check. Rebind it.
1936
+ // An unbound one binds through _bindVerdict on its first call.
1937
+ if (Object.prototype.hasOwnProperty.call(this, 'isValidObject')) _bindVerdict(this, this.isValidObject);
1938
+ return this;
1939
+ };
1940
+
1941
+ function _defineLazyMethod(name, maker) {
1942
+ Object.defineProperty(Validator.prototype, name, {
1943
+ configurable: true,
1944
+ get() {
1945
+ const fn = maker(this);
1946
+ Object.defineProperty(this, name, { value: fn, writable: true, configurable: true, enumerable: true });
1947
+ return fn;
1948
+ },
1949
+ set(fn) {
1950
+ Object.defineProperty(this, name, { value: fn, writable: true, configurable: true, enumerable: true });
1951
+ },
1952
+ });
1953
+ }
1954
+
1955
+ for (const [name, pick] of [
1956
+ ['_schemaObj', (self) => _materializeSchema(self)],
1957
+ ['_usesKeywords', (self) => { _materializeSchema(self); return self._usesKeywords; }],
1958
+ ['_schemaIsCallers', (self) => { _materializeSchema(self); return self._schemaIsCallers; }],
1959
+ ]) {
1960
+ Object.defineProperty(Validator.prototype, name, {
1961
+ configurable: true,
1962
+ get() { return pick(this); },
1963
+ set(v) { Object.defineProperty(this, name, { value: v, writable: true, configurable: true, enumerable: true }); },
1964
+ });
1965
+ }
1966
+
1967
+ _defineLazyMethod('validate', (self) => (data) => {
1968
+ self._ensureCompiled();
1969
+ return self.validate(data);
1970
+ });
1971
+ _defineLazyMethod('isValidObject', (self) => (data) => {
1972
+ // A validator that rewrites its input goes through the full compile, which
1973
+ // binds a verdict method that runs the rewrite first. So does one whose
1974
+ // schema uses a custom keyword: neither the tier-0 plan nor the code
1975
+ // generator knows the keyword, and either would accept what validate()
1976
+ // rejects.
1977
+ if (self._needsPreprocess() || self._usesKeywords) {
1978
+ self._ensureCompiled();
1979
+ return self.isValidObject(data);
1980
+ }
1981
+ // Lazy: classify + build tier 0 plan on first call, not in constructor.
1982
+ const _tier = classify(self._schemaObj);
1983
+ if (_tier.tier === 0) {
1984
+ const _plan = buildTier0Plan(self._schemaObj);
1985
+ let _n = 0;
1986
+ _bindVerdict(self, (d) => {
1987
+ const r = tier0Validate(_plan, d);
1988
+ if (++_n === 2) {
1989
+ try { self._ensureCodegen(); } catch {}
1990
+ }
1991
+ return r;
1992
+ });
1993
+ } else {
1994
+ // `new Function` is a property of the realm, not of the schema: under a
1995
+ // strict CSP or `--disallow-code-generation-from-strings` this throws
1996
+ // rather than declining, and the EvalError reached the caller of a verdict
1997
+ // method. The full compile can answer without code generation, so fall
1998
+ // through to it. The tier-0 branch above already guarded its own call.
1999
+ try { self._ensureCodegen(); } catch { /* no codegen in this realm */ }
2000
+ // Codegen can bail on shapes it cannot represent; the full compile
2001
+ // binds the native path or the unsupported thrower instead of
2002
+ // leaving this stub to re-dispatch to itself.
2003
+ if (!self._jsFn) self._ensureCompiled();
2004
+ }
2005
+ return self.isValidObject(data);
2006
+ });
2007
+ _defineLazyMethod('validateJSON', (self) => (jsonStr) => {
2008
+ self._ensureCompiled();
2009
+ return self.validateJSON(jsonStr);
2010
+ });
2011
+ _defineLazyMethod('isValidJSON', (self) => (jsonStr) => {
2012
+ self._ensureCompiled();
2013
+ return self.isValidJSON(jsonStr);
2014
+ });
2015
+ // Parse, then validate: JSON.parse and validate(), so it answers the same with
2016
+ // or without the addon and in a browser. The addon's own validateAndParse was
2017
+ // used where there was no code generator, and needed the addon for nothing
2018
+ // validate() cannot do.
2019
+ _defineLazyMethod('validateAndParse', (self) => (jsonStr) => {
2020
+ let value;
2021
+ try {
2022
+ value = JSON.parse(typeof jsonStr === 'string' ? jsonStr : new TextDecoder().decode(jsonStr));
2023
+ } catch (e) {
2024
+ return { valid: false, value: undefined, errors: [{ code: 'ATA9001', message: 'invalid JSON: ' + e.message, keyword: '__parse__', instancePath: '', schemaPath: '', params: {} }] };
2025
+ }
2026
+ const r = self.validate(value);
2027
+ return { valid: r.valid, value, errors: r.errors };
2028
+ });
2029
+ for (const name of ['isValid', 'isValidPrepadded', 'isValidParallel', 'isValidNDJSON', 'countValid', 'batchIsValid']) {
2030
+ _defineLazyMethod(name, (self) => (...args) => {
2031
+ self._installBufferApis(name);
2032
+ return self[name](...args);
2033
+ });
2034
+ }
2035
+
2036
+ module.exports = {
2037
+ Validator,
2038
+ compile,
2039
+ validate,
2040
+ validateAsync,
2041
+ parseAsync,
2042
+ version,
2043
+ createPaddedBuffer,
2044
+ SIMDJSON_PADDING,
2045
+ parseJSON,
2046
+ defineSchema,
2047
+ };
2048
+ // How an entry registers the code generator. Not enumerable, so the exported
2049
+ // surface of `require('ata-validator')` is what it was.
2050
+ Object.defineProperty(module.exports, '_registerCodegen', { value: _registerCodegen, enumerable: false });
2051
+ // What the compiled paths in index.js need from this module.
2052
+ Object.defineProperty(module.exports, '_internals', { value: { _jsonSyntaxRejection, ABORT_EARLY_RESULT, HYBRID_TIER_CALLS, SIMDJSON_THRESHOLD, VALID_RESULT, _bindEntry, _bindVerdict, _compileCache, _mustReject, _rememberInstance, compileCacheKey, getNative, isV1Dialect, resolveSchemaByPath }, enumerable: false });