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