ata-validator 0.4.10 → 0.4.12

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.
Files changed (3) hide show
  1. package/README.md +43 -21
  2. package/index.js +405 -186
  3. package/package.json +1 -1
package/README.md CHANGED
@@ -10,16 +10,16 @@ Ultra-fast JSON Schema validator powered by [simdjson](https://github.com/simdjs
10
10
 
11
11
  | Scenario | ata | ajv | |
12
12
  |---|---|---|---|
13
- | **validate(obj)** valid | 76M ops/sec | 8M ops/sec | **ata 9.5x faster** |
14
- | **validate(obj)** invalid | 34M ops/sec | 8M ops/sec | **ata 4.3x faster** |
13
+ | **validate(obj)** valid | 68M ops/sec | 8M ops/sec | **ata 8.5x faster** |
14
+ | **validate(obj)** invalid | 17M ops/sec | 8M ops/sec | **ata 2.1x faster** |
15
15
  | **isValidObject(obj)** | 15.4M ops/sec | 9.2M ops/sec | **ata 1.7x faster** |
16
- | **validateJSON(str)** valid | 2.15M ops/sec | 1.88M ops/sec | **ata 1.1x faster** |
17
- | **validateJSON(str)** invalid | 2.17M ops/sec | 2.29M ops/sec | **ata 1.1x faster** |
16
+ | **validateJSON(str)** valid | 3.0M ops/sec | 1.9M ops/sec | **ata 1.6x faster** |
17
+ | **validateJSON(str)** invalid | 2.7M ops/sec | 2.3M ops/sec | **ata 1.2x faster** |
18
18
  | **Schema compilation** | 113K ops/sec | 818 ops/sec | **ata 138x faster** |
19
19
 
20
20
  > validate(obj) numbers are isolated single-schema benchmarks. Multi-schema benchmark overhead reduces throughput; real-world numbers depend on workload.
21
21
 
22
- ### Large Data JS Object Validation
22
+ ### Large Data - JS Object Validation
23
23
 
24
24
  | Size | ata | ajv | |
25
25
  |---|---|---|---|
@@ -35,18 +35,19 @@ Ultra-fast JSON Schema validator powered by [simdjson](https://github.com/simdjs
35
35
  | **ReDoS protection** (`^(a+)+$`) | 0.3ms | 765ms | **ata immune (RE2)** |
36
36
  | **Batch NDJSON** (10K items, multi-core) | 13.4M/sec | 5.1M/sec | **ata 2.6x faster** |
37
37
  | **Fastify HTTP** (100 users POST) | 24.6K req/sec | 22.6K req/sec | **ata 9% faster** |
38
+ | **Fastify startup** (500 routes) | 46ms | 77ms (standalone) | **ata 1.7x faster** |
38
39
 
39
- > ata is faster than ajv on **every** benchmark — valid and invalid data, objects and JSON strings, single documents and parallel batches.
40
+ > Isolated single-schema benchmarks. Results vary by workload and hardware.
40
41
 
41
42
  ### How it works
42
43
 
43
- **Hybrid validator**: ata compiles schemas into monolithic JS functions identical to the boolean fast path, but returning `VALID_RESULT` on success and calling the error collector on failure. V8 TurboFan optimizes it identically to a pure boolean function error code is dead code on the valid path. No try/catch (3.3x V8 deopt), no lazy arrays, no double-pass.
44
+ **Hybrid validator**: ata compiles schemas into monolithic JS functions identical to the boolean fast path, but returning `VALID_RESULT` on success and calling the error collector on failure. V8 TurboFan optimizes it identically to a pure boolean function - error code is dead code on the valid path. No try/catch (3.3x V8 deopt), no lazy arrays, no double-pass.
44
45
 
45
46
  **JS codegen**: Schemas are compiled to monolithic JS functions (like ajv). Supported keywords: `type`, `required`, `properties`, `items`, `enum`, `const`, `allOf`, `anyOf`, `oneOf`, `not`, `if/then/else`, `uniqueItems`, `contains`, `prefixItems`, `additionalProperties`, `dependentRequired`, `$ref` (local), `minimum/maximum`, `minLength/maxLength`, `pattern`, `format`.
46
47
 
47
48
  **V8 TurboFan optimizations**: Destructuring batch reads, `undefined` checks instead of `in` operator, context-aware type guard elimination, property hoisting to local variables, tiered uniqueItems (nested loop for small arrays).
48
49
 
49
- **Adaptive simdjson**: For large documents (>8KB) with selective schemas, simdjson On Demand seeks only the needed fields skipping irrelevant data at GB/s speeds.
50
+ **Adaptive simdjson**: For large documents (>8KB) with selective schemas, simdjson On Demand seeks only the needed fields - skipping irrelevant data at GB/s speeds.
50
51
 
51
52
  ### JSON Schema Test Suite
52
53
 
@@ -54,27 +55,27 @@ Ultra-fast JSON Schema validator powered by [simdjson](https://github.com/simdjs
54
55
 
55
56
  ## When to use ata
56
57
 
57
- - **Any `validate(obj)` workload** 4.3x–9.5x faster than ajv
58
- - **Serverless / cold starts** 12.5x faster schema compilation
59
- - **Security-sensitive apps** RE2 regex, immune to ReDoS attacks
60
- - **Batch/streaming validation** NDJSON log processing, data pipelines (2.6x faster)
61
- - **Standard Schema V1** native support for Fastify v5, tRPC, TanStack
62
- - **C/C++ embedding** native library, no JS runtime needed
58
+ - **High-throughput `validate(obj)`** - 68M ops/sec valid, 17M ops/sec invalid
59
+ - **Serverless / cold starts** - 12.5x faster schema compilation
60
+ - **Security-sensitive apps** - RE2 regex, immune to ReDoS attacks
61
+ - **Batch/streaming validation** - NDJSON log processing, data pipelines (2.6x faster)
62
+ - **Standard Schema V1** - native support for Fastify v5, tRPC, TanStack
63
+ - **C/C++ embedding** - native library, no JS runtime needed
63
64
 
64
65
  ## When to use ajv
65
66
 
66
- - **Schemas with `patternProperties`, `dependentSchemas`** these bypass JS codegen and hit the slower NAPI path
67
- - **100% spec compliance needed** ajv covers more edge cases (ata: 98.4%)
67
+ - **Schemas with `patternProperties`, `dependentSchemas`** - these bypass JS codegen and hit the slower NAPI path
68
+ - **100% spec compliance needed** - ajv covers more edge cases (ata: 98.4%)
68
69
 
69
70
  ## Features
70
71
 
71
- - **Hybrid validator**: 76M ops/sec same function body as boolean check, returns result or calls error collector. No try/catch, no double pass
72
- - **Multi-core**: Parallel validation across all CPU cores 13.4M validations/sec
72
+ - **Hybrid validator**: 68M ops/sec - same function body as boolean check, returns result or calls error collector. No try/catch, no double pass
73
+ - **Multi-core**: Parallel validation across all CPU cores - 13.4M validations/sec
73
74
  - **simdjson**: SIMD-accelerated JSON parsing at GB/s speeds, adaptive On Demand for large docs
74
75
  - **RE2 regex**: Linear-time guarantees, immune to ReDoS attacks (2391x faster on pathological input)
75
76
  - **V8-optimized codegen**: Destructuring batch reads, type guard elimination, property hoisting
76
77
  - **Standard Schema V1**: Compatible with Fastify, tRPC, TanStack, Drizzle
77
- - **Zero-copy paths**: Buffer and pre-padded input support no unnecessary copies
78
+ - **Zero-copy paths**: Buffer and pre-padded input support - no unnecessary copies
78
79
  - **Defaults + coercion**: `default` values, `coerceTypes`, `removeAdditional` support
79
80
  - **C/C++ library**: Native API for non-Node.js environments
80
81
  - **98.4% spec compliant**: Draft 2020-12
@@ -103,7 +104,7 @@ const v = new Validator({
103
104
  required: ['name', 'email']
104
105
  });
105
106
 
106
- // Fast boolean check JS codegen (9.5x faster than ajv)
107
+ // Fast boolean check - JS codegen, 68M ops/sec
107
108
  v.isValidObject({ name: 'Mert', email: 'mert@example.com', age: 26 }); // true
108
109
 
109
110
  // Full validation with error details + defaults applied
@@ -117,7 +118,7 @@ v.isValidJSON('{"name": "Mert", "email": "mert@example.com"}'); // true
117
118
  // Buffer input (zero-copy, raw NAPI)
118
119
  v.isValid(Buffer.from('{"name": "Mert", "email": "mert@example.com"}'));
119
120
 
120
- // Parallel batch multi-core, NDJSON (2.6x faster than ajv)
121
+ // Parallel batch - multi-core, NDJSON, 13.4M items/sec
121
122
  const ndjson = Buffer.from(lines.join('\n'));
122
123
  v.isValidParallel(ndjson); // bool[]
123
124
  v.countValid(ndjson); // number
@@ -132,6 +133,27 @@ const v = new Validator(schema, {
132
133
  });
133
134
  ```
134
135
 
136
+ ### Standalone Pre-compilation
137
+
138
+ Pre-compile schemas to JS files for near-zero startup. No native addon needed at runtime.
139
+
140
+ ```javascript
141
+ const fs = require('fs');
142
+
143
+ // Build phase (once)
144
+ const v = new Validator(schema);
145
+ fs.writeFileSync('./compiled.js', v.toStandalone());
146
+
147
+ // Read phase (every startup) - 0.6μs per schema, pure JS
148
+ const v2 = Validator.fromStandalone(require('./compiled.js'), schema);
149
+
150
+ // Bundle multiple schemas - deduplicated, single file
151
+ fs.writeFileSync('./bundle.js', Validator.bundleCompact(schemas));
152
+ const validators = Validator.loadBundle(require('./bundle.js'), schemas);
153
+ ```
154
+
155
+ **Fastify startup (500 routes): ajv standalone 77ms → ata standalone 46ms (1.7x faster)**
156
+
135
157
  ### Standard Schema V1
136
158
 
137
159
  ```javascript
package/index.js CHANGED
@@ -1,10 +1,15 @@
1
1
  const native = require("node-gyp-build")(__dirname);
2
- const { compileToJS, compileToJSCodegen, compileToJSCodegenWithErrors, compileToJSCombined } = require("./lib/js-compiler");
2
+ const {
3
+ compileToJS,
4
+ compileToJSCodegen,
5
+ compileToJSCodegenWithErrors,
6
+ compileToJSCombined,
7
+ } = require("./lib/js-compiler");
3
8
 
4
9
  // Extract default values from a schema tree. Returns a function that applies
5
10
  // defaults to an object in-place (mutates), or null if no defaults exist.
6
11
  function buildDefaultsApplier(schema) {
7
- if (typeof schema !== 'object' || schema === null) return null;
12
+ if (typeof schema !== "object" || schema === null) return null;
8
13
  const actions = [];
9
14
  collectDefaults(schema, actions);
10
15
  if (actions.length === 0) return null;
@@ -14,17 +19,19 @@ function buildDefaultsApplier(schema) {
14
19
  }
15
20
 
16
21
  function collectDefaults(schema, actions, path) {
17
- if (typeof schema !== 'object' || schema === null) return;
22
+ if (typeof schema !== "object" || schema === null) return;
18
23
  const props = schema.properties;
19
24
  if (!props) return;
20
25
  for (const [key, prop] of Object.entries(props)) {
21
- if (prop && typeof prop === 'object' && prop.default !== undefined) {
26
+ if (prop && typeof prop === "object" && prop.default !== undefined) {
22
27
  const defaultVal = prop.default;
23
28
  if (!path) {
24
29
  actions.push((data) => {
25
- if (typeof data === 'object' && data !== null && !(key in data)) {
26
- data[key] = typeof defaultVal === 'object' && defaultVal !== null
27
- ? JSON.parse(JSON.stringify(defaultVal)) : defaultVal;
30
+ if (typeof data === "object" && data !== null && !(key in data)) {
31
+ data[key] =
32
+ typeof defaultVal === "object" && defaultVal !== null
33
+ ? JSON.parse(JSON.stringify(defaultVal))
34
+ : defaultVal;
28
35
  }
29
36
  });
30
37
  } else {
@@ -32,18 +39,24 @@ function collectDefaults(schema, actions, path) {
32
39
  actions.push((data) => {
33
40
  let target = data;
34
41
  for (let j = 0; j < parentPath.length; j++) {
35
- if (typeof target !== 'object' || target === null) return;
42
+ if (typeof target !== "object" || target === null) return;
36
43
  target = target[parentPath[j]];
37
44
  }
38
- if (typeof target === 'object' && target !== null && !(key in target)) {
39
- target[key] = typeof defaultVal === 'object' && defaultVal !== null
40
- ? JSON.parse(JSON.stringify(defaultVal)) : defaultVal;
45
+ if (
46
+ typeof target === "object" &&
47
+ target !== null &&
48
+ !(key in target)
49
+ ) {
50
+ target[key] =
51
+ typeof defaultVal === "object" && defaultVal !== null
52
+ ? JSON.parse(JSON.stringify(defaultVal))
53
+ : defaultVal;
41
54
  }
42
55
  });
43
56
  }
44
57
  }
45
58
  // Recurse into nested object schemas
46
- if (prop && typeof prop === 'object' && prop.properties) {
59
+ if (prop && typeof prop === "object" && prop.properties) {
47
60
  collectDefaults(prop, actions, (path || []).concat(key));
48
61
  }
49
62
  }
@@ -52,7 +65,7 @@ function collectDefaults(schema, actions, path) {
52
65
  // Build a function that coerces property values to match schema types in-place.
53
66
  // Handles string→number, string→integer, string→boolean, number→string, boolean→string.
54
67
  function buildCoercer(schema) {
55
- if (typeof schema !== 'object' || schema === null) return null;
68
+ if (typeof schema !== "object" || schema === null) return null;
56
69
  const actions = [];
57
70
  collectCoercions(schema, actions);
58
71
  if (actions.length === 0) return null;
@@ -62,11 +75,11 @@ function buildCoercer(schema) {
62
75
  }
63
76
 
64
77
  function collectCoercions(schema, actions, path) {
65
- if (typeof schema !== 'object' || schema === null) return;
78
+ if (typeof schema !== "object" || schema === null) return;
66
79
  const props = schema.properties;
67
80
  if (!props) return;
68
81
  for (const [key, prop] of Object.entries(props)) {
69
- if (!prop || typeof prop !== 'object' || !prop.type) continue;
82
+ if (!prop || typeof prop !== "object" || !prop.type) continue;
70
83
  const targetType = Array.isArray(prop.type) ? null : prop.type;
71
84
  if (!targetType) continue;
72
85
 
@@ -75,7 +88,7 @@ function collectCoercions(schema, actions, path) {
75
88
 
76
89
  if (!path) {
77
90
  actions.push((data) => {
78
- if (typeof data === 'object' && data !== null && key in data) {
91
+ if (typeof data === "object" && data !== null && key in data) {
79
92
  const coerced = coerce(data[key]);
80
93
  if (coerced !== undefined) data[key] = coerced;
81
94
  }
@@ -85,10 +98,10 @@ function collectCoercions(schema, actions, path) {
85
98
  actions.push((data) => {
86
99
  let target = data;
87
100
  for (let j = 0; j < parentPath.length; j++) {
88
- if (typeof target !== 'object' || target === null) return;
101
+ if (typeof target !== "object" || target === null) return;
89
102
  target = target[parentPath[j]];
90
103
  }
91
- if (typeof target === 'object' && target !== null && key in target) {
104
+ if (typeof target === "object" && target !== null && key in target) {
92
105
  const coerced = coerce(target[key]);
93
106
  if (coerced !== undefined) target[key] = coerced;
94
107
  }
@@ -104,29 +117,40 @@ function collectCoercions(schema, actions, path) {
104
117
 
105
118
  function buildSingleCoercion(targetType) {
106
119
  switch (targetType) {
107
- case 'number': return (v) => {
108
- if (typeof v === 'string') { const n = Number(v); if (v !== '' && !isNaN(n)) return n; }
109
- if (typeof v === 'boolean') return v ? 1 : 0;
110
- };
111
- case 'integer': return (v) => {
112
- if (typeof v === 'string') { const n = Number(v); if (v !== '' && Number.isInteger(n)) return n; }
113
- if (typeof v === 'boolean') return v ? 1 : 0;
114
- };
115
- case 'string': return (v) => {
116
- if (typeof v === 'number' || typeof v === 'boolean') return String(v);
117
- };
118
- case 'boolean': return (v) => {
119
- if (v === 'true' || v === '1') return true;
120
- if (v === 'false' || v === '0') return false;
121
- };
122
- default: return null;
120
+ case "number":
121
+ return (v) => {
122
+ if (typeof v === "string") {
123
+ const n = Number(v);
124
+ if (v !== "" && !isNaN(n)) return n;
125
+ }
126
+ if (typeof v === "boolean") return v ? 1 : 0;
127
+ };
128
+ case "integer":
129
+ return (v) => {
130
+ if (typeof v === "string") {
131
+ const n = Number(v);
132
+ if (v !== "" && Number.isInteger(n)) return n;
133
+ }
134
+ if (typeof v === "boolean") return v ? 1 : 0;
135
+ };
136
+ case "string":
137
+ return (v) => {
138
+ if (typeof v === "number" || typeof v === "boolean") return String(v);
139
+ };
140
+ case "boolean":
141
+ return (v) => {
142
+ if (v === "true" || v === "1") return true;
143
+ if (v === "false" || v === "0") return false;
144
+ };
145
+ default:
146
+ return null;
123
147
  }
124
148
  }
125
149
 
126
150
  // Build a function that removes properties not defined in schema.properties.
127
151
  // Walks nested objects recursively.
128
152
  function buildRemover(schema) {
129
- if (typeof schema !== 'object' || schema === null) return null;
153
+ if (typeof schema !== "object" || schema === null) return null;
130
154
  const actions = [];
131
155
  collectRemovals(schema, actions);
132
156
  if (actions.length === 0) return null;
@@ -136,14 +160,16 @@ function buildRemover(schema) {
136
160
  }
137
161
 
138
162
  function collectRemovals(schema, actions, path) {
139
- if (typeof schema !== 'object' || schema === null || !schema.properties) return;
163
+ if (typeof schema !== "object" || schema === null || !schema.properties)
164
+ return;
140
165
 
141
166
  // If this level has additionalProperties: false, add a removal action
142
167
  if (schema.additionalProperties === false) {
143
168
  const allowed = new Set(Object.keys(schema.properties));
144
169
  if (!path) {
145
170
  actions.push((data) => {
146
- if (typeof data !== 'object' || data === null || Array.isArray(data)) return;
171
+ if (typeof data !== "object" || data === null || Array.isArray(data))
172
+ return;
147
173
  const keys = Object.keys(data);
148
174
  for (let i = 0; i < keys.length; i++) {
149
175
  if (!allowed.has(keys[i])) delete data[keys[i]];
@@ -154,10 +180,15 @@ function collectRemovals(schema, actions, path) {
154
180
  actions.push((data) => {
155
181
  let target = data;
156
182
  for (let j = 0; j < parentPath.length; j++) {
157
- if (typeof target !== 'object' || target === null) return;
183
+ if (typeof target !== "object" || target === null) return;
158
184
  target = target[parentPath[j]];
159
185
  }
160
- if (typeof target !== 'object' || target === null || Array.isArray(target)) return;
186
+ if (
187
+ typeof target !== "object" ||
188
+ target === null ||
189
+ Array.isArray(target)
190
+ )
191
+ return;
161
192
  const keys = Object.keys(target);
162
193
  for (let i = 0; i < keys.length; i++) {
163
194
  if (!allowed.has(keys[i])) delete target[keys[i]];
@@ -168,7 +199,7 @@ function collectRemovals(schema, actions, path) {
168
199
 
169
200
  // Always recurse into nested properties (they may have their own additionalProperties: false)
170
201
  for (const [key, prop] of Object.entries(schema.properties)) {
171
- if (prop && typeof prop === 'object' && prop.properties) {
202
+ if (prop && typeof prop === "object" && prop.properties) {
172
203
  collectRemovals(prop, actions, (path || []).concat(key));
173
204
  }
174
205
  }
@@ -204,147 +235,277 @@ class Validator {
204
235
  const options = opts || {};
205
236
  const schemaStr =
206
237
  typeof schema === "string" ? schema : JSON.stringify(schema);
207
- const compiled = new native.CompiledSchema(schemaStr);
208
- this._compiled = compiled;
209
- this._fastSlot = native.fastRegister(schemaStr);
210
-
211
- // Pure JS fast path — no NAPI, runs in V8 JIT
212
- // Set ATA_FORCE_NAPI=1 to disable JS codegen (for correctness testing)
213
238
  const schemaObj = typeof schema === "string" ? JSON.parse(schema) : schema;
239
+
240
+ this._schemaStr = schemaStr;
214
241
  this._schemaObj = schemaObj;
242
+ this._options = options;
243
+ this._initialized = false;
244
+ this._nativeReady = false;
245
+ this._compiled = null;
246
+ this._fastSlot = -1;
247
+ this._jsFn = null;
248
+ this._preprocess = null;
249
+ this._applyDefaults = null;
250
+
251
+ // Lazy stubs: trigger compilation on first call, then re-dispatch
252
+ this.validate = (data) => {
253
+ this._ensureCompiled();
254
+ return this.validate(data);
255
+ };
256
+ this.isValidObject = (data) => {
257
+ this._ensureCompiled();
258
+ return this.isValidObject(data);
259
+ };
260
+ this.validateJSON = (jsonStr) => {
261
+ this._ensureCompiled();
262
+ return this.validateJSON(jsonStr);
263
+ };
264
+ this.isValidJSON = (jsonStr) => {
265
+ this._ensureCompiled();
266
+ return this.isValidJSON(jsonStr);
267
+ };
268
+
269
+ // ~standard uses self.validate() -- works with lazy because it goes through
270
+ // the instance property which gets swapped after compilation
271
+ const self = this;
272
+ Object.defineProperty(this, "~standard", {
273
+ value: Object.freeze({
274
+ version: 1,
275
+ vendor: "ata-validator",
276
+ validate(value) {
277
+ const result = self.validate(value);
278
+ if (result.valid) {
279
+ return { value };
280
+ }
281
+ return {
282
+ issues: result.errors.map((err) => ({
283
+ message: err.message,
284
+ path: parsePointerPath(err.path),
285
+ })),
286
+ };
287
+ },
288
+ }),
289
+ writable: false,
290
+ enumerable: false,
291
+ configurable: false,
292
+ });
293
+ }
294
+
295
+ _ensureCompiled() {
296
+ if (this._initialized) return;
297
+ this._initialized = true;
298
+
299
+ const schemaObj = this._schemaObj;
300
+ const options = this._options;
301
+
302
+ // Pure JS fast path -- no NAPI, runs in V8 JIT
303
+ // Set ATA_FORCE_NAPI=1 to disable JS codegen (for correctness testing)
215
304
  const jsFn = process.env.ATA_FORCE_NAPI
216
305
  ? null
217
- : (compileToJSCodegen(schemaObj) || compileToJS(schemaObj));
218
- // Combined validator: single pass, validates + collects errors, all optimized
306
+ : compileToJSCodegen(schemaObj) || compileToJS(schemaObj);
219
307
  const jsCombinedFn = process.env.ATA_FORCE_NAPI
220
308
  ? null
221
309
  : compileToJSCombined(schemaObj, VALID_RESULT);
222
- // Fallback error-collecting codegen (less optimized, for schemas combined can't handle)
223
310
  const jsErrFn = process.env.ATA_FORCE_NAPI
224
311
  ? null
225
312
  : compileToJSCodegenWithErrors(schemaObj);
226
313
  this._jsFn = jsFn;
227
314
 
228
- // Data mutators applied in-place before validation
315
+ // Data mutators -- applied in-place before validation
229
316
  const applyDefaults = buildDefaultsApplier(schemaObj);
230
317
  const applyCoerce = options.coerceTypes ? buildCoercer(schemaObj) : null;
231
- const applyRemove = options.removeAdditional ? buildRemover(schemaObj) : null;
318
+ const applyRemove = options.removeAdditional
319
+ ? buildRemover(schemaObj)
320
+ : null;
232
321
  this._applyDefaults = applyDefaults;
233
322
 
234
323
  // Combine all mutators into a single pre-validation step
235
324
  const mutators = [applyRemove, applyCoerce, applyDefaults].filter(Boolean);
236
- const preprocess = mutators.length === 0 ? null
237
- : mutators.length === 1 ? mutators[0]
238
- : (data) => { for (let i = 0; i < mutators.length; i++) mutators[i](data); };
325
+ const preprocess =
326
+ mutators.length === 0
327
+ ? null
328
+ : mutators.length === 1
329
+ ? mutators[0]
330
+ : (data) => {
331
+ for (let i = 0; i < mutators.length; i++) mutators[i](data);
332
+ };
239
333
  this._preprocess = preprocess;
240
334
 
241
- // Closure-capture: avoid `this` property lookup on every call.
242
- // V8 keeps closure vars in registers — no hidden class traversal.
243
- const fastSlot = this._fastSlot;
244
-
245
- // Detect if schema is "selective" — doesn't recurse into arrays/deep objects.
246
- // Selective schemas benefit from simdjson On Demand (seeks only needed fields).
247
- // Non-selective schemas (items, allOf with nested) touch everything — JSON.parse + jsFn wins.
248
- const hasArrayTraversal = schemaObj && (schemaObj.items || schemaObj.prefixItems ||
249
- schemaObj.contains || (schemaObj.properties && Object.values(schemaObj.properties).some(
250
- p => p && (p.items || p.prefixItems || p.contains))));
335
+ // Detect if schema is "selective" -- doesn't recurse into arrays/deep objects.
336
+ const hasArrayTraversal =
337
+ schemaObj &&
338
+ (schemaObj.items ||
339
+ schemaObj.prefixItems ||
340
+ schemaObj.contains ||
341
+ (schemaObj.properties &&
342
+ Object.values(schemaObj.properties).some(
343
+ (p) => p && (p.items || p.prefixItems || p.contains),
344
+ )));
251
345
  const useSimdjsonForLarge = !hasArrayTraversal;
252
346
 
253
347
  if (jsFn) {
254
- // Best path: combined validator (single pass, lazy error array)
255
- // Valid data: no array allocation, returns VALID_RESULT
256
- // Invalid data: collects errors in one pass (no double validation)
257
- // Fallback: jsFn + errFn for schemas combined can't handle
258
- // errFn: JS error codegen or NAPI fallback. No try/catch (V8 3.3x deopt).
259
- // jsErrFn tested at compile time — if it throws, don't use it.
260
348
  let safeErrFn = null;
261
349
  if (jsErrFn) {
262
- try { jsErrFn({}, true); safeErrFn = (d) => jsErrFn(d, true); } catch {}
350
+ try {
351
+ jsErrFn({}, true);
352
+ safeErrFn = (d) => jsErrFn(d, true);
353
+ } catch {}
354
+ }
355
+ // errFn: use JS codegen if safe, else lazy-native fallback
356
+ const errFn =
357
+ safeErrFn ||
358
+ ((d) => {
359
+ this._ensureNative();
360
+ return this._compiled.validate(d);
361
+ });
362
+
363
+ // Best path: combined validator (single pass, validates + collects errors)
364
+ // Valid data: returns VALID_RESULT, no allocation
365
+ // Invalid data: collects errors in one pass (no double validation)
366
+ // Fallback: hybridFn or jsFn + errFn for schemas combined can't handle
367
+ // Test combined at compile time -- some schemas produce broken combined code
368
+ // Test combined at compile time -- some schemas (e.g. if/then/else)
369
+ // produce broken combined code that crashes on certain inputs.
370
+ // We probe with diverse data; if any throws, fall back to hybrid.
371
+ let safeCombinedFn = null;
372
+ if (jsCombinedFn) {
373
+ try {
374
+ const probe = {};
375
+ // Populate probe with one key per known property to trigger nested paths
376
+ if (schemaObj && schemaObj.properties) {
377
+ for (const k of Object.keys(schemaObj.properties)) probe[k] = "";
378
+ }
379
+ if (schemaObj && schemaObj.if && schemaObj.if.properties) {
380
+ for (const k of Object.keys(schemaObj.if.properties)) probe[k] = "";
381
+ }
382
+ jsCombinedFn(probe);
383
+ jsCombinedFn({});
384
+ jsCombinedFn(null);
385
+ jsCombinedFn(0);
386
+ safeCombinedFn = jsCombinedFn;
387
+ } catch {}
263
388
  }
264
- const errFn = safeErrFn || ((d) => compiled.validate(d));
265
389
 
266
- // Hybrid validator: jsFn body with return R / return E(d).
267
- // V8 optimizes identically to jsFn (83M) — E(d) is dead code on valid path.
268
- // Invalid: E(d) calls errFn once (34M vs 6M two-pass).
269
- // Fallback: jsFn + errFn speculative if hybrid unavailable.
390
+ if (safeCombinedFn) {
391
+ this.validate = preprocess
392
+ ? (data) => {
393
+ preprocess(data);
394
+ return safeCombinedFn(data);
395
+ }
396
+ : safeCombinedFn;
397
+ } else {
398
+ const hybridFn = jsFn._hybridFactory
399
+ ? jsFn._hybridFactory(VALID_RESULT, errFn)
400
+ : null;
401
+ this.validate = hybridFn
402
+ ? preprocess
403
+ ? (data) => {
404
+ preprocess(data);
405
+ return hybridFn(data);
406
+ }
407
+ : hybridFn
408
+ : preprocess
409
+ ? (data) => {
410
+ preprocess(data);
411
+ return jsFn(data) ? VALID_RESULT : errFn(data);
412
+ }
413
+ : (data) => (jsFn(data) ? VALID_RESULT : errFn(data));
414
+ }
415
+ this.isValidObject = jsFn;
270
416
  const hybridFn = jsFn._hybridFactory
271
417
  ? jsFn._hybridFactory(VALID_RESULT, errFn)
272
418
  : null;
273
- this.validate = hybridFn
274
- ? (preprocess ? (data) => { preprocess(data); return hybridFn(data); } : hybridFn)
275
- : (preprocess
276
- ? (data) => { preprocess(data); return jsFn(data) ? VALID_RESULT : errFn(data); }
277
- : (data) => jsFn(data) ? VALID_RESULT : errFn(data));
278
- this.isValidObject = jsFn;
279
- const jsonValidateFn = hybridFn || ((obj) => jsFn(obj) ? VALID_RESULT : errFn(obj));
419
+ const jsonValidateFn = safeCombinedFn
420
+ || hybridFn
421
+ || ((obj) => (jsFn(obj) ? VALID_RESULT : errFn(obj)));
280
422
  this.validateJSON = useSimdjsonForLarge
281
423
  ? (jsonStr) => {
282
424
  if (jsonStr.length >= SIMDJSON_THRESHOLD) {
425
+ this._ensureNative();
283
426
  const buf = Buffer.from(jsonStr);
284
- if (native.rawFastValidate(fastSlot, buf)) return VALID_RESULT;
285
- return compiled.validateJSON(jsonStr);
427
+ if (native.rawFastValidate(this._fastSlot, buf))
428
+ return VALID_RESULT;
429
+ return this._compiled.validateJSON(jsonStr);
430
+ }
431
+ try {
432
+ return jsonValidateFn(JSON.parse(jsonStr));
433
+ } catch (e) {
434
+ if (!(e instanceof SyntaxError)) throw e;
286
435
  }
287
- try { return jsonValidateFn(JSON.parse(jsonStr)); }
288
- catch (e) { if (!(e instanceof SyntaxError)) throw e; }
289
- return compiled.validateJSON(jsonStr);
436
+ this._ensureNative();
437
+ return this._compiled.validateJSON(jsonStr);
290
438
  }
291
439
  : (jsonStr) => {
292
- try { return jsonValidateFn(JSON.parse(jsonStr)); }
293
- catch (e) { if (!(e instanceof SyntaxError)) throw e; }
294
- return compiled.validateJSON(jsonStr);
440
+ try {
441
+ return jsonValidateFn(JSON.parse(jsonStr));
442
+ } catch (e) {
443
+ if (!(e instanceof SyntaxError)) throw e;
444
+ }
445
+ this._ensureNative();
446
+ return this._compiled.validateJSON(jsonStr);
295
447
  };
296
448
  this.isValidJSON = useSimdjsonForLarge
297
449
  ? (jsonStr) => {
298
450
  if (jsonStr.length >= SIMDJSON_THRESHOLD) {
299
- return native.rawFastValidate(fastSlot, Buffer.from(jsonStr));
451
+ this._ensureNative();
452
+ return native.rawFastValidate(
453
+ this._fastSlot,
454
+ Buffer.from(jsonStr),
455
+ );
456
+ }
457
+ try {
458
+ return jsFn(JSON.parse(jsonStr));
459
+ } catch (e) {
460
+ if (!(e instanceof SyntaxError)) throw e;
461
+ return false;
300
462
  }
301
- try { return jsFn(JSON.parse(jsonStr)); }
302
- catch (e) { if (!(e instanceof SyntaxError)) throw e; return false; }
303
463
  }
304
464
  : (jsonStr) => {
305
- try { return jsFn(JSON.parse(jsonStr)); }
306
- catch (e) { if (!(e instanceof SyntaxError)) throw e; return false; }
465
+ try {
466
+ return jsFn(JSON.parse(jsonStr));
467
+ } catch (e) {
468
+ if (!(e instanceof SyntaxError)) throw e;
469
+ return false;
470
+ }
307
471
  };
472
+ } else {
473
+ // ATA_FORCE_NAPI path: no JS codegen, use native for everything
474
+ this._ensureNative();
475
+ this.validate = preprocess
476
+ ? (data) => {
477
+ preprocess(data);
478
+ return this._compiled.validate(data);
479
+ }
480
+ : (data) => this._compiled.validate(data);
481
+ this.isValidObject = (data) => this._compiled.validate(data).valid;
482
+ this.validateJSON = (jsonStr) => this._compiled.validateJSON(jsonStr);
483
+ this.isValidJSON = (jsonStr) => this._compiled.isValidJSON(jsonStr);
308
484
  }
485
+ }
309
486
 
310
- const self = this;
311
- Object.defineProperty(this, "~standard", {
312
- value: Object.freeze({
313
- version: 1,
314
- vendor: "ata-validator",
315
- validate(value) {
316
- const result = self.validate(value);
317
- if (result.valid) {
318
- return { value };
319
- }
320
- return {
321
- issues: result.errors.map((err) => ({
322
- message: err.message,
323
- path: parsePointerPath(err.path),
324
- })),
325
- };
326
- },
327
- }),
328
- writable: false,
329
- enumerable: false,
330
- configurable: false,
331
- });
487
+ _ensureNative() {
488
+ if (this._nativeReady) return;
489
+ this._nativeReady = true;
490
+ this._compiled = new native.CompiledSchema(this._schemaStr);
491
+ this._fastSlot = native.fastRegister(this._schemaStr);
332
492
  }
333
493
 
334
494
  // --- Standalone pre-compilation ---
335
495
  // Generate a JS module string that can be written to a file.
336
- // On next startup, load with Validator.fromStandalone() zero compile time.
496
+ // On next startup, load with Validator.fromStandalone() -- zero compile time.
337
497
  toStandalone() {
498
+ this._ensureCompiled();
338
499
  const jsFn = this._jsFn;
339
500
  if (!jsFn || !jsFn._source) return null;
340
501
  const src = jsFn._source;
341
- const hybridSrc = jsFn._hybridSource || '';
502
+ const hybridSrc = jsFn._hybridSource || "";
342
503
 
343
504
  // Also capture error function source for zero-compile standalone load
344
505
  const jsErrFn = compileToJSCodegenWithErrors(
345
- typeof this._schemaObj === 'object' ? this._schemaObj : {}
506
+ typeof this._schemaObj === "object" ? this._schemaObj : {},
346
507
  );
347
- const errSrc = jsErrFn && jsErrFn._errSource ? jsErrFn._errSource : '';
508
+ const errSrc = jsErrFn && jsErrFn._errSource ? jsErrFn._errSource : "";
348
509
 
349
510
  return `// Auto-generated by ata-validator — do not edit
350
511
  'use strict';
@@ -356,7 +517,7 @@ const hybridFactory = function(R, E) {
356
517
  ${hybridSrc}
357
518
  };
358
519
  };
359
- ${errSrc ? `const errFn = function(d, _all) {\n ${errSrc}\n};` : 'const errFn = null;'}
520
+ ${errSrc ? `const errFn = function(d, _all) {\n ${errSrc}\n};` : "const errFn = null;"}
360
521
  module.exports = { boolFn, hybridFactory, errFn };
361
522
  `;
362
523
  }
@@ -377,21 +538,36 @@ module.exports = { boolFn, hybridFactory, errFn };
377
538
  // Mutators
378
539
  const applyDefaults = buildDefaultsApplier(schemaObj);
379
540
  const applyCoerce = options.coerceTypes ? buildCoercer(schemaObj) : null;
380
- const applyRemove = options.removeAdditional ? buildRemover(schemaObj) : null;
541
+ const applyRemove = options.removeAdditional
542
+ ? buildRemover(schemaObj)
543
+ : null;
381
544
  const mutators = [applyRemove, applyCoerce, applyDefaults].filter(Boolean);
382
- const preprocess = mutators.length === 0 ? null
383
- : mutators.length === 1 ? mutators[0]
384
- : (data) => { for (let i = 0; i < mutators.length; i++) mutators[i](data); };
545
+ const preprocess =
546
+ mutators.length === 0
547
+ ? null
548
+ : mutators.length === 1
549
+ ? mutators[0]
550
+ : (data) => {
551
+ for (let i = 0; i < mutators.length; i++) mutators[i](data);
552
+ };
385
553
  v._preprocess = preprocess;
386
554
 
387
555
  // Error function — use pre-compiled from standalone if available, else compile
388
- let errFn = (d) => ({ valid: false, errors: [{ code: 'validation_failed', path: '', message: 'validation failed' }] });
556
+ let errFn = (d) => ({
557
+ valid: false,
558
+ errors: [
559
+ { code: "validation_failed", path: "", message: "validation failed" },
560
+ ],
561
+ });
389
562
  if (mod.errFn) {
390
563
  errFn = (d) => mod.errFn(d, true);
391
564
  } else {
392
565
  const jsErrFn = compileToJSCodegenWithErrors(schemaObj);
393
566
  if (jsErrFn) {
394
- try { jsErrFn({}, true); errFn = (d) => jsErrFn(d, true); } catch {}
567
+ try {
568
+ jsErrFn({}, true);
569
+ errFn = (d) => jsErrFn(d, true);
570
+ } catch {}
395
571
  }
396
572
  }
397
573
 
@@ -401,77 +577,93 @@ module.exports = { boolFn, hybridFactory, errFn };
401
577
  : null;
402
578
 
403
579
  v.validate = hybridFn
404
- ? (preprocess ? (data) => { preprocess(data); return hybridFn(data); } : hybridFn)
405
- : (preprocess
406
- ? (data) => { preprocess(data); return mod.boolFn(data) ? VALID_RESULT : errFn(data); }
407
- : (data) => mod.boolFn(data) ? VALID_RESULT : errFn(data));
580
+ ? preprocess
581
+ ? (data) => {
582
+ preprocess(data);
583
+ return hybridFn(data);
584
+ }
585
+ : hybridFn
586
+ : preprocess
587
+ ? (data) => {
588
+ preprocess(data);
589
+ return mod.boolFn(data) ? VALID_RESULT : errFn(data);
590
+ }
591
+ : (data) => (mod.boolFn(data) ? VALID_RESULT : errFn(data));
408
592
  v.isValidObject = mod.boolFn;
409
593
  v.isValidJSON = (jsonStr) => {
410
- try { return mod.boolFn(JSON.parse(jsonStr)); } catch { return false; }
594
+ try {
595
+ return mod.boolFn(JSON.parse(jsonStr));
596
+ } catch {
597
+ return false;
598
+ }
411
599
  };
412
600
  v.validateJSON = (jsonStr) => {
413
601
  try {
414
602
  const obj = JSON.parse(jsonStr);
415
- return hybridFn ? hybridFn(obj) : (mod.boolFn(obj) ? VALID_RESULT : errFn(obj));
416
- } catch { return { valid: false, errors: [{ code: 'invalid_json', path: '', message: 'invalid JSON' }] }; }
603
+ return hybridFn
604
+ ? hybridFn(obj)
605
+ : mod.boolFn(obj)
606
+ ? VALID_RESULT
607
+ : errFn(obj);
608
+ } catch {
609
+ return {
610
+ valid: false,
611
+ errors: [{ code: "invalid_json", path: "", message: "invalid JSON" }],
612
+ };
613
+ }
417
614
  };
418
615
 
419
616
  // Standard Schema V1
420
617
  Object.defineProperty(v, "~standard", {
421
618
  value: Object.freeze({
422
- version: 1, vendor: "ata-validator",
619
+ version: 1,
620
+ vendor: "ata-validator",
423
621
  validate(value) {
424
622
  const result = v.validate(value);
425
623
  if (result.valid) return { value };
426
- return { issues: result.errors.map(e => ({ message: e.message, path: parsePointerPath(e.path) })) };
624
+ return {
625
+ issues: result.errors.map((e) => ({
626
+ message: e.message,
627
+ path: parsePointerPath(e.path),
628
+ })),
629
+ };
427
630
  },
428
631
  }),
429
- writable: false, enumerable: false, configurable: false,
632
+ writable: false,
633
+ enumerable: false,
634
+ configurable: false,
430
635
  });
431
636
 
432
637
  return v;
433
638
  }
434
639
 
435
- // Fallback methods — only used when JS codegen is unavailable
436
- validate(data) {
437
- if (this._preprocess) this._preprocess(data);
438
- return this._compiled.validate(data);
439
- }
440
-
441
- isValidObject(data) {
442
- return this._compiled.validate(data).valid;
443
- }
444
-
445
- validateJSON(jsonStr) {
446
- return this._compiled.validateJSON(jsonStr);
447
- }
448
-
449
- isValidJSON(jsonStr) {
450
- return this._compiled.isValidJSON(jsonStr);
451
- }
452
-
453
640
  // Raw NAPI fast path for Buffer/Uint8Array
454
641
  isValid(input) {
642
+ this._ensureNative();
455
643
  return native.rawFastValidate(this._fastSlot, input);
456
644
  }
457
645
 
458
646
  // Zero-copy pre-padded path
459
647
  isValidPrepadded(paddedBuffer, jsonLength) {
648
+ this._ensureNative();
460
649
  return native.rawFastValidate(this._fastSlot, paddedBuffer, jsonLength);
461
650
  }
462
651
 
463
652
  // Parallel NDJSON batch (multi-core)
464
653
  isValidParallel(buffer) {
654
+ this._ensureNative();
465
655
  return native.rawParallelValidate(this._fastSlot, buffer);
466
656
  }
467
657
 
468
- // Parallel count (fastest single uint32 return)
658
+ // Parallel count (fastest -- single uint32 return)
469
659
  countValid(buffer) {
660
+ this._ensureNative();
470
661
  return native.rawParallelCount(this._fastSlot, buffer);
471
662
  }
472
663
 
473
664
  // NDJSON single-thread batch
474
665
  isValidNDJSON(buffer) {
666
+ this._ensureNative();
475
667
  return native.rawNDJSONValidate(this._fastSlot, buffer);
476
668
  }
477
669
  }
@@ -492,44 +684,53 @@ function version() {
492
684
  // fs.writeFileSync('validators.js', bundle);
493
685
  // // On startup:
494
686
  // const validators = Validator.loadBundle(require('./validators.js'), [schema1, schema2, ...]);
495
- Validator.bundle = function(schemas, opts) {
496
- const parts = schemas.map(schema => {
687
+ Validator.bundle = function (schemas, opts) {
688
+ const parts = schemas.map((schema) => {
497
689
  const v = new Validator(schema, opts);
498
690
  const standalone = v.toStandalone();
499
- if (!standalone) return 'null';
500
- return '(function(){' + standalone.replace("'use strict';", '').replace('module.exports = ', 'return ') + '})()';
691
+ if (!standalone) return "null";
692
+ return (
693
+ "(function(){" +
694
+ standalone
695
+ .replace("'use strict';", "")
696
+ .replace("module.exports = ", "return ") +
697
+ "})()"
698
+ );
501
699
  });
502
- return "'use strict';\nmodule.exports = [\n" + parts.join(',\n') + '\n];\n';
700
+ return "'use strict';\nmodule.exports = [\n" + parts.join(",\n") + "\n];\n";
503
701
  };
504
702
 
505
703
  // Zero-dependency self-contained bundle — no require('ata-validator') needed at runtime.
506
- Validator.bundleStandalone = function(schemas, opts) {
704
+ Validator.bundleStandalone = function (schemas, opts) {
507
705
  const R = "Object.freeze({valid:true,errors:Object.freeze([])})";
508
- const fns = schemas.map(schema => {
706
+ const fns = schemas.map((schema) => {
509
707
  const v = new Validator(schema, opts);
708
+ v._ensureCompiled();
510
709
  const jsFn = v._jsFn;
511
- if (!jsFn || !jsFn._hybridSource) return 'null';
710
+ if (!jsFn || !jsFn._hybridSource) return "null";
512
711
  const jsErrFn = compileToJSCodegenWithErrors(
513
- typeof schema === 'string' ? JSON.parse(schema) : schema
712
+ typeof schema === "string" ? JSON.parse(schema) : schema,
514
713
  );
515
- const errBody = jsErrFn && jsErrFn._errSource
516
- ? jsErrFn._errSource
517
- : "return{valid:false,errors:[{code:'error',path:'',message:'validation failed'}]}";
714
+ const errBody =
715
+ jsErrFn && jsErrFn._errSource
716
+ ? jsErrFn._errSource
717
+ : "return{valid:false,errors:[{code:'error',path:'',message:'validation failed'}]}";
518
718
  return `(function(R){var E=function(d){var _all=true;${errBody}};return function(d){${jsFn._hybridSource}}})(R)`;
519
719
  });
520
- return `'use strict';\nvar R=${R};\nmodule.exports=[${fns.join(',')}];\n`;
720
+ return `'use strict';\nvar R=${R};\nmodule.exports=[${fns.join(",")}];\n`;
521
721
  };
522
722
 
523
723
  // Compact bundle: deduplicated code. Shared template functions + per-schema params.
524
724
  // Much smaller file → faster V8 parse → faster startup.
525
- Validator.bundleCompact = function(schemas, opts) {
725
+ Validator.bundleCompact = function (schemas, opts) {
526
726
  // Analyze schemas and group by structure
527
- const entries = schemas.map(schema => {
727
+ const entries = schemas.map((schema) => {
528
728
  const v = new Validator(schema, opts);
729
+ v._ensureCompiled();
529
730
  const jsFn = v._jsFn;
530
731
  if (!jsFn || !jsFn._hybridSource) return null;
531
732
  const jsErrFn = compileToJSCodegenWithErrors(
532
- typeof schema === 'string' ? JSON.parse(schema) : schema
733
+ typeof schema === "string" ? JSON.parse(schema) : schema,
533
734
  );
534
735
  return {
535
736
  hybrid: jsFn._hybridSource,
@@ -543,14 +744,22 @@ Validator.bundleCompact = function(schemas, opts) {
543
744
  const errMap = new Map();
544
745
  const errBodies = [];
545
746
 
546
- const indices = entries.map(e => {
747
+ const indices = entries.map((e) => {
547
748
  if (!e) return [-1, -1];
548
749
  let hi = bodyMap.get(e.hybrid);
549
- if (hi === undefined) { hi = bodies.length; bodies.push(e.hybrid); bodyMap.set(e.hybrid, hi); }
750
+ if (hi === undefined) {
751
+ hi = bodies.length;
752
+ bodies.push(e.hybrid);
753
+ bodyMap.set(e.hybrid, hi);
754
+ }
550
755
  let ei = -1;
551
756
  if (e.err) {
552
757
  ei = errMap.get(e.err);
553
- if (ei === undefined) { ei = errBodies.length; errBodies.push(e.err); errMap.set(e.err, ei); }
758
+ if (ei === undefined) {
759
+ ei = errBodies.length;
760
+ errBodies.push(e.err);
761
+ errMap.set(e.err, ei);
762
+ }
554
763
  }
555
764
  return [hi, ei];
556
765
  });
@@ -561,31 +770,41 @@ Validator.bundleCompact = function(schemas, opts) {
561
770
 
562
771
  // Shared hybrid factories
563
772
  out += "var H=[\n";
564
- out += bodies.map(b => `function(R,E){return function(d){${b}}}`).join(',\n');
773
+ out += bodies
774
+ .map((b) => `function(R,E){return function(d){${b}}}`)
775
+ .join(",\n");
565
776
  out += "\n];\n";
566
777
 
567
778
  // Shared error functions
568
779
  out += "var EF=[\n";
569
- out += errBodies.map(b => `function(d){var _all=true;${b}}`).join(',\n');
780
+ out += errBodies.map((b) => `function(d){var _all=true;${b}}`).join(",\n");
570
781
  out += "\n];\n";
571
782
 
572
783
  // Build validators from shared templates
573
784
  out += "module.exports=[";
574
- out += indices.map(([hi, ei]) => {
575
- if (hi < 0) return 'null';
576
- if (ei >= 0) return `H[${hi}](R,EF[${ei}])`;
577
- return `H[${hi}](R,function(){return{valid:false,errors:[]}})`;
578
- }).join(',');
785
+ out += indices
786
+ .map(([hi, ei]) => {
787
+ if (hi < 0) return "null";
788
+ if (ei >= 0) return `H[${hi}](R,EF[${ei}])`;
789
+ return `H[${hi}](R,function(){return{valid:false,errors:[]}})`;
790
+ })
791
+ .join(",");
579
792
  out += "];\n";
580
793
 
581
794
  return out;
582
795
  };
583
796
 
584
- Validator.loadBundle = function(mods, schemas, opts) {
797
+ Validator.loadBundle = function (mods, schemas, opts) {
585
798
  return schemas.map((schema, i) => {
586
799
  if (mods[i]) return Validator.fromStandalone(mods[i], schema, opts);
587
800
  return new Validator(schema, opts);
588
801
  });
589
802
  };
590
803
 
591
- module.exports = { Validator, validate, version, createPaddedBuffer, SIMDJSON_PADDING };
804
+ module.exports = {
805
+ Validator,
806
+ validate,
807
+ version,
808
+ createPaddedBuffer,
809
+ SIMDJSON_PADDING,
810
+ };
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "ata-validator",
3
- "version": "0.4.10",
3
+ "version": "0.4.12",
4
4
  "description": "Ultra-fast JSON Schema validator. Beats ajv on every valid-path benchmark: 1.1x–2.7x faster validate(obj), 151x faster compilation, 5.9x faster parallel batch. Speculative validation with V8-optimized JS codegen, simdjson, multi-core. Standard Schema V1 compatible.",
5
5
  "main": "index.js",
6
6
  "types": "index.d.ts",