ata-validator 0.4.9 → 0.4.11

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
package/index.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,237 @@ 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 {}
263
354
  }
264
- const errFn = safeErrFn || ((d) => compiled.validate(d));
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
+ });
265
362
 
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.
270
363
  const hybridFn = jsFn._hybridFactory
271
364
  ? jsFn._hybridFactory(VALID_RESULT, errFn)
272
365
  : null;
273
366
  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));
367
+ ? preprocess
368
+ ? (data) => {
369
+ preprocess(data);
370
+ return hybridFn(data);
371
+ }
372
+ : hybridFn
373
+ : preprocess
374
+ ? (data) => {
375
+ preprocess(data);
376
+ return jsFn(data) ? VALID_RESULT : errFn(data);
377
+ }
378
+ : (data) => (jsFn(data) ? VALID_RESULT : errFn(data));
278
379
  this.isValidObject = jsFn;
279
- const jsonValidateFn = hybridFn || ((obj) => jsFn(obj) ? VALID_RESULT : errFn(obj));
380
+ const jsonValidateFn =
381
+ hybridFn || ((obj) => (jsFn(obj) ? VALID_RESULT : errFn(obj)));
280
382
  this.validateJSON = useSimdjsonForLarge
281
383
  ? (jsonStr) => {
282
384
  if (jsonStr.length >= SIMDJSON_THRESHOLD) {
385
+ this._ensureNative();
283
386
  const buf = Buffer.from(jsonStr);
284
- if (native.rawFastValidate(fastSlot, buf)) return VALID_RESULT;
285
- return compiled.validateJSON(jsonStr);
387
+ if (native.rawFastValidate(this._fastSlot, buf))
388
+ return VALID_RESULT;
389
+ return this._compiled.validateJSON(jsonStr);
390
+ }
391
+ try {
392
+ return jsonValidateFn(JSON.parse(jsonStr));
393
+ } catch (e) {
394
+ if (!(e instanceof SyntaxError)) throw e;
286
395
  }
287
- try { return jsonValidateFn(JSON.parse(jsonStr)); }
288
- catch (e) { if (!(e instanceof SyntaxError)) throw e; }
289
- return compiled.validateJSON(jsonStr);
396
+ this._ensureNative();
397
+ return this._compiled.validateJSON(jsonStr);
290
398
  }
291
399
  : (jsonStr) => {
292
- try { return jsonValidateFn(JSON.parse(jsonStr)); }
293
- catch (e) { if (!(e instanceof SyntaxError)) throw e; }
294
- return compiled.validateJSON(jsonStr);
400
+ try {
401
+ return jsonValidateFn(JSON.parse(jsonStr));
402
+ } catch (e) {
403
+ if (!(e instanceof SyntaxError)) throw e;
404
+ }
405
+ this._ensureNative();
406
+ return this._compiled.validateJSON(jsonStr);
295
407
  };
296
408
  this.isValidJSON = useSimdjsonForLarge
297
409
  ? (jsonStr) => {
298
410
  if (jsonStr.length >= SIMDJSON_THRESHOLD) {
299
- return native.rawFastValidate(fastSlot, Buffer.from(jsonStr));
411
+ this._ensureNative();
412
+ return native.rawFastValidate(
413
+ this._fastSlot,
414
+ Buffer.from(jsonStr),
415
+ );
416
+ }
417
+ try {
418
+ return jsFn(JSON.parse(jsonStr));
419
+ } catch (e) {
420
+ if (!(e instanceof SyntaxError)) throw e;
421
+ return false;
300
422
  }
301
- try { return jsFn(JSON.parse(jsonStr)); }
302
- catch (e) { if (!(e instanceof SyntaxError)) throw e; return false; }
303
423
  }
304
424
  : (jsonStr) => {
305
- try { return jsFn(JSON.parse(jsonStr)); }
306
- catch (e) { if (!(e instanceof SyntaxError)) throw e; return false; }
425
+ try {
426
+ return jsFn(JSON.parse(jsonStr));
427
+ } catch (e) {
428
+ if (!(e instanceof SyntaxError)) throw e;
429
+ return false;
430
+ }
307
431
  };
432
+ } else {
433
+ // ATA_FORCE_NAPI path: no JS codegen, use native for everything
434
+ this._ensureNative();
435
+ this.validate = preprocess
436
+ ? (data) => {
437
+ preprocess(data);
438
+ return this._compiled.validate(data);
439
+ }
440
+ : (data) => this._compiled.validate(data);
441
+ this.isValidObject = (data) => this._compiled.validate(data).valid;
442
+ this.validateJSON = (jsonStr) => this._compiled.validateJSON(jsonStr);
443
+ this.isValidJSON = (jsonStr) => this._compiled.isValidJSON(jsonStr);
308
444
  }
445
+ }
309
446
 
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
- });
447
+ _ensureNative() {
448
+ if (this._nativeReady) return;
449
+ this._nativeReady = true;
450
+ this._compiled = new native.CompiledSchema(this._schemaStr);
451
+ this._fastSlot = native.fastRegister(this._schemaStr);
332
452
  }
333
453
 
334
454
  // --- Standalone pre-compilation ---
335
455
  // Generate a JS module string that can be written to a file.
336
- // On next startup, load with Validator.fromStandalone() zero compile time.
456
+ // On next startup, load with Validator.fromStandalone() -- zero compile time.
337
457
  toStandalone() {
458
+ this._ensureCompiled();
338
459
  const jsFn = this._jsFn;
339
460
  if (!jsFn || !jsFn._source) return null;
340
461
  const src = jsFn._source;
341
- const hybridSrc = jsFn._hybridSource || '';
462
+ const hybridSrc = jsFn._hybridSource || "";
342
463
 
343
464
  // Also capture error function source for zero-compile standalone load
344
465
  const jsErrFn = compileToJSCodegenWithErrors(
345
- typeof this._schemaObj === 'object' ? this._schemaObj : {}
466
+ typeof this._schemaObj === "object" ? this._schemaObj : {},
346
467
  );
347
- const errSrc = jsErrFn && jsErrFn._errSource ? jsErrFn._errSource : '';
468
+ const errSrc = jsErrFn && jsErrFn._errSource ? jsErrFn._errSource : "";
348
469
 
349
470
  return `// Auto-generated by ata-validator — do not edit
350
471
  'use strict';
@@ -356,7 +477,7 @@ const hybridFactory = function(R, E) {
356
477
  ${hybridSrc}
357
478
  };
358
479
  };
359
- ${errSrc ? `const errFn = function(d, _all) {\n ${errSrc}\n};` : 'const errFn = null;'}
480
+ ${errSrc ? `const errFn = function(d, _all) {\n ${errSrc}\n};` : "const errFn = null;"}
360
481
  module.exports = { boolFn, hybridFactory, errFn };
361
482
  `;
362
483
  }
@@ -377,21 +498,36 @@ module.exports = { boolFn, hybridFactory, errFn };
377
498
  // Mutators
378
499
  const applyDefaults = buildDefaultsApplier(schemaObj);
379
500
  const applyCoerce = options.coerceTypes ? buildCoercer(schemaObj) : null;
380
- const applyRemove = options.removeAdditional ? buildRemover(schemaObj) : null;
501
+ const applyRemove = options.removeAdditional
502
+ ? buildRemover(schemaObj)
503
+ : null;
381
504
  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); };
505
+ const preprocess =
506
+ mutators.length === 0
507
+ ? null
508
+ : mutators.length === 1
509
+ ? mutators[0]
510
+ : (data) => {
511
+ for (let i = 0; i < mutators.length; i++) mutators[i](data);
512
+ };
385
513
  v._preprocess = preprocess;
386
514
 
387
515
  // 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' }] });
516
+ let errFn = (d) => ({
517
+ valid: false,
518
+ errors: [
519
+ { code: "validation_failed", path: "", message: "validation failed" },
520
+ ],
521
+ });
389
522
  if (mod.errFn) {
390
523
  errFn = (d) => mod.errFn(d, true);
391
524
  } else {
392
525
  const jsErrFn = compileToJSCodegenWithErrors(schemaObj);
393
526
  if (jsErrFn) {
394
- try { jsErrFn({}, true); errFn = (d) => jsErrFn(d, true); } catch {}
527
+ try {
528
+ jsErrFn({}, true);
529
+ errFn = (d) => jsErrFn(d, true);
530
+ } catch {}
395
531
  }
396
532
  }
397
533
 
@@ -401,77 +537,93 @@ module.exports = { boolFn, hybridFactory, errFn };
401
537
  : null;
402
538
 
403
539
  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));
540
+ ? preprocess
541
+ ? (data) => {
542
+ preprocess(data);
543
+ return hybridFn(data);
544
+ }
545
+ : hybridFn
546
+ : preprocess
547
+ ? (data) => {
548
+ preprocess(data);
549
+ return mod.boolFn(data) ? VALID_RESULT : errFn(data);
550
+ }
551
+ : (data) => (mod.boolFn(data) ? VALID_RESULT : errFn(data));
408
552
  v.isValidObject = mod.boolFn;
409
553
  v.isValidJSON = (jsonStr) => {
410
- try { return mod.boolFn(JSON.parse(jsonStr)); } catch { return false; }
554
+ try {
555
+ return mod.boolFn(JSON.parse(jsonStr));
556
+ } catch {
557
+ return false;
558
+ }
411
559
  };
412
560
  v.validateJSON = (jsonStr) => {
413
561
  try {
414
562
  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' }] }; }
563
+ return hybridFn
564
+ ? hybridFn(obj)
565
+ : mod.boolFn(obj)
566
+ ? VALID_RESULT
567
+ : errFn(obj);
568
+ } catch {
569
+ return {
570
+ valid: false,
571
+ errors: [{ code: "invalid_json", path: "", message: "invalid JSON" }],
572
+ };
573
+ }
417
574
  };
418
575
 
419
576
  // Standard Schema V1
420
577
  Object.defineProperty(v, "~standard", {
421
578
  value: Object.freeze({
422
- version: 1, vendor: "ata-validator",
579
+ version: 1,
580
+ vendor: "ata-validator",
423
581
  validate(value) {
424
582
  const result = v.validate(value);
425
583
  if (result.valid) return { value };
426
- return { issues: result.errors.map(e => ({ message: e.message, path: parsePointerPath(e.path) })) };
584
+ return {
585
+ issues: result.errors.map((e) => ({
586
+ message: e.message,
587
+ path: parsePointerPath(e.path),
588
+ })),
589
+ };
427
590
  },
428
591
  }),
429
- writable: false, enumerable: false, configurable: false,
592
+ writable: false,
593
+ enumerable: false,
594
+ configurable: false,
430
595
  });
431
596
 
432
597
  return v;
433
598
  }
434
599
 
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
600
  // Raw NAPI fast path for Buffer/Uint8Array
454
601
  isValid(input) {
602
+ this._ensureNative();
455
603
  return native.rawFastValidate(this._fastSlot, input);
456
604
  }
457
605
 
458
606
  // Zero-copy pre-padded path
459
607
  isValidPrepadded(paddedBuffer, jsonLength) {
608
+ this._ensureNative();
460
609
  return native.rawFastValidate(this._fastSlot, paddedBuffer, jsonLength);
461
610
  }
462
611
 
463
612
  // Parallel NDJSON batch (multi-core)
464
613
  isValidParallel(buffer) {
614
+ this._ensureNative();
465
615
  return native.rawParallelValidate(this._fastSlot, buffer);
466
616
  }
467
617
 
468
- // Parallel count (fastest single uint32 return)
618
+ // Parallel count (fastest -- single uint32 return)
469
619
  countValid(buffer) {
620
+ this._ensureNative();
470
621
  return native.rawParallelCount(this._fastSlot, buffer);
471
622
  }
472
623
 
473
624
  // NDJSON single-thread batch
474
625
  isValidNDJSON(buffer) {
626
+ this._ensureNative();
475
627
  return native.rawNDJSONValidate(this._fastSlot, buffer);
476
628
  }
477
629
  }
@@ -492,44 +644,53 @@ function version() {
492
644
  // fs.writeFileSync('validators.js', bundle);
493
645
  // // On startup:
494
646
  // const validators = Validator.loadBundle(require('./validators.js'), [schema1, schema2, ...]);
495
- Validator.bundle = function(schemas, opts) {
496
- const parts = schemas.map(schema => {
647
+ Validator.bundle = function (schemas, opts) {
648
+ const parts = schemas.map((schema) => {
497
649
  const v = new Validator(schema, opts);
498
650
  const standalone = v.toStandalone();
499
- if (!standalone) return 'null';
500
- return '(function(){' + standalone.replace("'use strict';", '').replace('module.exports = ', 'return ') + '})()';
651
+ if (!standalone) return "null";
652
+ return (
653
+ "(function(){" +
654
+ standalone
655
+ .replace("'use strict';", "")
656
+ .replace("module.exports = ", "return ") +
657
+ "})()"
658
+ );
501
659
  });
502
- return "'use strict';\nmodule.exports = [\n" + parts.join(',\n') + '\n];\n';
660
+ return "'use strict';\nmodule.exports = [\n" + parts.join(",\n") + "\n];\n";
503
661
  };
504
662
 
505
663
  // Zero-dependency self-contained bundle — no require('ata-validator') needed at runtime.
506
- Validator.bundleStandalone = function(schemas, opts) {
664
+ Validator.bundleStandalone = function (schemas, opts) {
507
665
  const R = "Object.freeze({valid:true,errors:Object.freeze([])})";
508
- const fns = schemas.map(schema => {
666
+ const fns = schemas.map((schema) => {
509
667
  const v = new Validator(schema, opts);
668
+ v._ensureCompiled();
510
669
  const jsFn = v._jsFn;
511
- if (!jsFn || !jsFn._hybridSource) return 'null';
670
+ if (!jsFn || !jsFn._hybridSource) return "null";
512
671
  const jsErrFn = compileToJSCodegenWithErrors(
513
- typeof schema === 'string' ? JSON.parse(schema) : schema
672
+ typeof schema === "string" ? JSON.parse(schema) : schema,
514
673
  );
515
- const errBody = jsErrFn && jsErrFn._errSource
516
- ? jsErrFn._errSource
517
- : "return{valid:false,errors:[{code:'error',path:'',message:'validation failed'}]}";
674
+ const errBody =
675
+ jsErrFn && jsErrFn._errSource
676
+ ? jsErrFn._errSource
677
+ : "return{valid:false,errors:[{code:'error',path:'',message:'validation failed'}]}";
518
678
  return `(function(R){var E=function(d){var _all=true;${errBody}};return function(d){${jsFn._hybridSource}}})(R)`;
519
679
  });
520
- return `'use strict';\nvar R=${R};\nmodule.exports=[${fns.join(',')}];\n`;
680
+ return `'use strict';\nvar R=${R};\nmodule.exports=[${fns.join(",")}];\n`;
521
681
  };
522
682
 
523
683
  // Compact bundle: deduplicated code. Shared template functions + per-schema params.
524
684
  // Much smaller file → faster V8 parse → faster startup.
525
- Validator.bundleCompact = function(schemas, opts) {
685
+ Validator.bundleCompact = function (schemas, opts) {
526
686
  // Analyze schemas and group by structure
527
- const entries = schemas.map(schema => {
687
+ const entries = schemas.map((schema) => {
528
688
  const v = new Validator(schema, opts);
689
+ v._ensureCompiled();
529
690
  const jsFn = v._jsFn;
530
691
  if (!jsFn || !jsFn._hybridSource) return null;
531
692
  const jsErrFn = compileToJSCodegenWithErrors(
532
- typeof schema === 'string' ? JSON.parse(schema) : schema
693
+ typeof schema === "string" ? JSON.parse(schema) : schema,
533
694
  );
534
695
  return {
535
696
  hybrid: jsFn._hybridSource,
@@ -543,14 +704,22 @@ Validator.bundleCompact = function(schemas, opts) {
543
704
  const errMap = new Map();
544
705
  const errBodies = [];
545
706
 
546
- const indices = entries.map(e => {
707
+ const indices = entries.map((e) => {
547
708
  if (!e) return [-1, -1];
548
709
  let hi = bodyMap.get(e.hybrid);
549
- if (hi === undefined) { hi = bodies.length; bodies.push(e.hybrid); bodyMap.set(e.hybrid, hi); }
710
+ if (hi === undefined) {
711
+ hi = bodies.length;
712
+ bodies.push(e.hybrid);
713
+ bodyMap.set(e.hybrid, hi);
714
+ }
550
715
  let ei = -1;
551
716
  if (e.err) {
552
717
  ei = errMap.get(e.err);
553
- if (ei === undefined) { ei = errBodies.length; errBodies.push(e.err); errMap.set(e.err, ei); }
718
+ if (ei === undefined) {
719
+ ei = errBodies.length;
720
+ errBodies.push(e.err);
721
+ errMap.set(e.err, ei);
722
+ }
554
723
  }
555
724
  return [hi, ei];
556
725
  });
@@ -561,31 +730,41 @@ Validator.bundleCompact = function(schemas, opts) {
561
730
 
562
731
  // Shared hybrid factories
563
732
  out += "var H=[\n";
564
- out += bodies.map(b => `function(R,E){return function(d){${b}}}`).join(',\n');
733
+ out += bodies
734
+ .map((b) => `function(R,E){return function(d){${b}}}`)
735
+ .join(",\n");
565
736
  out += "\n];\n";
566
737
 
567
738
  // Shared error functions
568
739
  out += "var EF=[\n";
569
- out += errBodies.map(b => `function(d){var _all=true;${b}}`).join(',\n');
740
+ out += errBodies.map((b) => `function(d){var _all=true;${b}}`).join(",\n");
570
741
  out += "\n];\n";
571
742
 
572
743
  // Build validators from shared templates
573
744
  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(',');
745
+ out += indices
746
+ .map(([hi, ei]) => {
747
+ if (hi < 0) return "null";
748
+ if (ei >= 0) return `H[${hi}](R,EF[${ei}])`;
749
+ return `H[${hi}](R,function(){return{valid:false,errors:[]}})`;
750
+ })
751
+ .join(",");
579
752
  out += "];\n";
580
753
 
581
754
  return out;
582
755
  };
583
756
 
584
- Validator.loadBundle = function(mods, schemas, opts) {
757
+ Validator.loadBundle = function (mods, schemas, opts) {
585
758
  return schemas.map((schema, i) => {
586
759
  if (mods[i]) return Validator.fromStandalone(mods[i], schema, opts);
587
760
  return new Validator(schema, opts);
588
761
  });
589
762
  };
590
763
 
591
- module.exports = { Validator, validate, version, createPaddedBuffer, SIMDJSON_PADDING };
764
+ module.exports = {
765
+ Validator,
766
+ validate,
767
+ version,
768
+ createPaddedBuffer,
769
+ SIMDJSON_PADDING,
770
+ };