ata-validator 0.7.3 → 0.9.0
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/CMakeLists.txt +41 -23
- package/README.md +22 -9
- package/binding/ata_napi.cpp +206 -5
- package/include/ata.h +11 -3
- package/index.js +121 -27
- package/lib/js-compiler.js +692 -83
- package/package.json +3 -2
- package/prebuilds/ata-darwin-arm64/node-napi-v10.node +0 -0
- package/prebuilds/darwin-arm64/ata-validator.node +0 -0
- package/src/ata.cpp +607 -154
- package/prebuilds/ata-linux-arm64/node-napi-v10.node +0 -0
- package/prebuilds/ata-linux-x64/node-napi-v10.node +0 -0
package/index.js
CHANGED
|
@@ -264,6 +264,10 @@ function buildPreprocessCodegen(schema, options) {
|
|
|
264
264
|
// Schema compilation cache: same schema string -> reuse compiled functions
|
|
265
265
|
const _compileCache = new Map();
|
|
266
266
|
|
|
267
|
+
// Object identity cache: same schema object reference -> reuse entire compiled state
|
|
268
|
+
// Skips JSON.stringify, cache lookup, and all setup. Near-zero cost for repeated schemas.
|
|
269
|
+
const _identityCache = new WeakMap();
|
|
270
|
+
|
|
267
271
|
const SIMDJSON_PADDING = 64;
|
|
268
272
|
const VALID_RESULT = Object.freeze({ valid: true, errors: Object.freeze([]) });
|
|
269
273
|
|
|
@@ -309,17 +313,31 @@ function buildSchemaMap(schemas) {
|
|
|
309
313
|
return map
|
|
310
314
|
}
|
|
311
315
|
|
|
316
|
+
// Resolve a relative URI ref against a base URI
|
|
317
|
+
function resolveRelativeRef(ref, baseId) {
|
|
318
|
+
if (!baseId || ref.includes('://') || ref.startsWith('#')) return ref
|
|
319
|
+
const lastSlash = baseId.lastIndexOf('/')
|
|
320
|
+
if (lastSlash < 0) return ref
|
|
321
|
+
return baseId.substring(0, lastSlash + 1) + ref
|
|
322
|
+
}
|
|
323
|
+
|
|
312
324
|
class Validator {
|
|
313
325
|
constructor(schema, opts) {
|
|
314
326
|
const options = opts || {};
|
|
315
327
|
const schemaObj = typeof schema === "string" ? JSON.parse(schema) : schema;
|
|
316
328
|
|
|
329
|
+
// Ultra-fast path: same schema object reference -> return cached instance
|
|
330
|
+
// JS constructor returning an object makes `new` return that object
|
|
331
|
+
// Cost: one WeakMap lookup. No property copy, no setup, nothing.
|
|
332
|
+
if (!opts && typeof schema === "object" && schema !== null) {
|
|
333
|
+
const hit = _identityCache.get(schema);
|
|
334
|
+
if (hit) return hit;
|
|
335
|
+
}
|
|
336
|
+
|
|
317
337
|
// Draft 7 normalization — convert keywords to 2020-12 equivalents in-place
|
|
318
338
|
normalizeDraft7(schemaObj);
|
|
319
339
|
|
|
320
|
-
|
|
321
|
-
|
|
322
|
-
this._schemaStr = schemaStr;
|
|
340
|
+
this._schemaStr = null; // lazy: computed on first use
|
|
323
341
|
this._schemaObj = schemaObj;
|
|
324
342
|
this._options = options;
|
|
325
343
|
this._initialized = false;
|
|
@@ -350,6 +368,11 @@ class Validator {
|
|
|
350
368
|
this._ensureCompiled();
|
|
351
369
|
return this.isValidJSON(jsonStr);
|
|
352
370
|
};
|
|
371
|
+
this.validateAndParse = (jsonStr) => {
|
|
372
|
+
if (!native) throw new Error('Native addon required for validateAndParse()');
|
|
373
|
+
this._ensureCompiled();
|
|
374
|
+
return this.validateAndParse(jsonStr);
|
|
375
|
+
};
|
|
353
376
|
this.isValid = (buf) => {
|
|
354
377
|
if (!native) throw new Error('Native addon required for isValid() — use validate() or isValidObject() instead');
|
|
355
378
|
this._ensureCompiled();
|
|
@@ -399,23 +422,29 @@ class Validator {
|
|
|
399
422
|
const schemaObj = this._schemaObj;
|
|
400
423
|
const options = this._options;
|
|
401
424
|
|
|
425
|
+
// Lazy stringify — only computed here, not in constructor
|
|
426
|
+
if (!this._schemaStr) this._schemaStr = JSON.stringify(schemaObj);
|
|
427
|
+
|
|
402
428
|
// Check cache first -- reuse compiled functions for same schema
|
|
403
429
|
const sm = this._schemaMap.size > 0 ? this._schemaMap : null;
|
|
404
430
|
const mapKey = this._schemaMap.size > 0
|
|
405
431
|
? this._schemaStr + '\0' + [...this._schemaMap.keys()].sort().join('\0')
|
|
406
432
|
: this._schemaStr;
|
|
407
433
|
const cached = _compileCache.get(mapKey);
|
|
408
|
-
let jsFn, jsCombinedFn, jsErrFn;
|
|
434
|
+
let jsFn, jsCombinedFn, jsErrFn, _isCodegen = false;
|
|
409
435
|
var _forceNapi = typeof process !== 'undefined' && process.env && process.env.ATA_FORCE_NAPI;
|
|
410
436
|
if (cached && !_forceNapi) {
|
|
411
437
|
jsFn = cached.jsFn;
|
|
412
438
|
jsCombinedFn = cached.combined;
|
|
413
439
|
jsErrFn = cached.errFn;
|
|
440
|
+
_isCodegen = !!cached.isCodegen;
|
|
414
441
|
} else if (!_forceNapi) {
|
|
415
|
-
|
|
442
|
+
const _cgFn = compileToJSCodegen(schemaObj, sm);
|
|
443
|
+
jsFn = _cgFn || compileToJS(schemaObj, null, sm);
|
|
416
444
|
jsCombinedFn = compileToJSCombined(schemaObj, VALID_RESULT, sm);
|
|
417
445
|
jsErrFn = compileToJSCodegenWithErrors(schemaObj, sm);
|
|
418
|
-
|
|
446
|
+
_isCodegen = !!_cgFn;
|
|
447
|
+
_compileCache.set(mapKey, { jsFn, combined: jsCombinedFn, errFn: jsErrFn, isCodegen: _isCodegen });
|
|
419
448
|
} else {
|
|
420
449
|
jsFn = null; jsCombinedFn = null; jsErrFn = null;
|
|
421
450
|
}
|
|
@@ -464,15 +493,21 @@ class Validator {
|
|
|
464
493
|
}
|
|
465
494
|
// errFn: use JS codegen if safe, else lazy-native fallback
|
|
466
495
|
// For unevaluated schemas without errFn, use jsFn as boolean-only fallback
|
|
467
|
-
const hasUnevaluated = schemaObj &&
|
|
496
|
+
const hasUnevaluated = schemaObj && (schemaObj.unevaluatedProperties !== undefined || schemaObj.unevaluatedItems !== undefined || this._schemaStr.includes('unevaluatedProperties') || this._schemaStr.includes('unevaluatedItems'))
|
|
497
|
+
const hasDynRef = this._schemaStr.includes('"$dynamicRef"') || this._schemaStr.includes('"$dynamicAnchor"')
|
|
468
498
|
const errFn =
|
|
469
499
|
safeErrFn ||
|
|
470
500
|
(hasUnevaluated
|
|
471
501
|
? (d) => ({ valid: jsFn(d), errors: jsFn(d) ? [] : [{ code: 'unevaluated', path: '', message: 'unevaluated property or item' }] })
|
|
472
|
-
:
|
|
473
|
-
|
|
474
|
-
|
|
475
|
-
|
|
502
|
+
: hasDynRef
|
|
503
|
+
? (d) => {
|
|
504
|
+
this._ensureNative();
|
|
505
|
+
return this._compiled.validateJSON(JSON.stringify(d));
|
|
506
|
+
}
|
|
507
|
+
: (d) => {
|
|
508
|
+
this._ensureNative();
|
|
509
|
+
return this._compiled.validate(d);
|
|
510
|
+
});
|
|
476
511
|
|
|
477
512
|
// Best path: combined validator (single pass, validates + collects errors)
|
|
478
513
|
// Valid data: returns VALID_RESULT, no allocation
|
|
@@ -501,23 +536,30 @@ class Validator {
|
|
|
501
536
|
} catch {}
|
|
502
537
|
}
|
|
503
538
|
|
|
504
|
-
if (
|
|
505
|
-
//
|
|
539
|
+
if (hasDynRef && _isCodegen && jsFn) {
|
|
540
|
+
// $dynamicRef with JS codegen: direct path, no wrapper layers
|
|
541
|
+
const _fn = jsFn, _efn = safeErrFn || errFn, _R = VALID_RESULT;
|
|
506
542
|
this.validate = preprocess
|
|
507
|
-
? (data) => {
|
|
508
|
-
|
|
509
|
-
|
|
510
|
-
|
|
511
|
-
|
|
543
|
+
? (data) => { preprocess(data); return _fn(data) ? _R : _efn(data); }
|
|
544
|
+
: (data) => _fn(data) ? _R : _efn(data);
|
|
545
|
+
} else if (hasDynRef) {
|
|
546
|
+
// $dynamicRef without codegen: delegate to native C++ (interpretive path unreliable)
|
|
547
|
+
this.validate = preprocess
|
|
548
|
+
? (data) => { preprocess(data); return errFn(data); }
|
|
549
|
+
: errFn;
|
|
550
|
+
} else if (jsFn && jsFn._hybridFactory) {
|
|
551
|
+
// Zero-wrapper: hybridFactory bakes VALID_RESULT + errFn into a single function
|
|
552
|
+
// No arrow function wrapper, no ternary, one function call
|
|
553
|
+
const hybridFn = jsFn._hybridFactory(VALID_RESULT, safeCombinedFn || errFn);
|
|
554
|
+
this.validate = preprocess
|
|
555
|
+
? (data) => { preprocess(data); return hybridFn(data); }
|
|
556
|
+
: hybridFn;
|
|
512
557
|
} else if (safeCombinedFn) {
|
|
513
558
|
this.validate = preprocess
|
|
514
|
-
? (data) => {
|
|
515
|
-
preprocess(data);
|
|
516
|
-
return safeCombinedFn(data);
|
|
517
|
-
}
|
|
559
|
+
? (data) => { preprocess(data); return safeCombinedFn(data); }
|
|
518
560
|
: safeCombinedFn;
|
|
519
561
|
} else {
|
|
520
|
-
const hybridFn = jsFn._hybridFactory
|
|
562
|
+
const hybridFn = jsFn && jsFn._hybridFactory
|
|
521
563
|
? jsFn._hybridFactory(VALID_RESULT, errFn)
|
|
522
564
|
: null;
|
|
523
565
|
this.validate = hybridFn
|
|
@@ -592,6 +634,17 @@ class Validator {
|
|
|
592
634
|
return false;
|
|
593
635
|
}
|
|
594
636
|
};
|
|
637
|
+
// validateAndParse: requires native addon for simdjson parsing
|
|
638
|
+
if (native) {
|
|
639
|
+
const self = this;
|
|
640
|
+
this.validateAndParse = (jsonStr) => {
|
|
641
|
+
self._ensureNative();
|
|
642
|
+
self.validateAndParse = (s) => self._compiled.validateAndParse(s);
|
|
643
|
+
return self.validateAndParse(jsonStr);
|
|
644
|
+
};
|
|
645
|
+
} else {
|
|
646
|
+
this.validateAndParse = () => { throw new Error('Native addon required for validateAndParse()'); };
|
|
647
|
+
}
|
|
595
648
|
// Buffer APIs: lazy native init — only compile native schema on first buffer call.
|
|
596
649
|
// This keeps cold start fast (JS codegen only) for users who only use validate().
|
|
597
650
|
if (native) {
|
|
@@ -616,17 +669,24 @@ class Validator {
|
|
|
616
669
|
};
|
|
617
670
|
}
|
|
618
671
|
} else if (native) {
|
|
619
|
-
//
|
|
672
|
+
// Native-only path: no JS codegen, use native for everything
|
|
620
673
|
this._ensureNative();
|
|
674
|
+
const _hasDynamic = this._schemaStr.includes('"$dynamicRef"') || this._schemaStr.includes('"$dynamicAnchor"') || this._schemaStr.includes('"$anchor"')
|
|
675
|
+
// For schemas with dynamic refs/anchors, use validateJSON (C++ path with full support)
|
|
676
|
+
// instead of validate (NAPI direct V8 path without anchor maps)
|
|
677
|
+
const _validate = _hasDynamic
|
|
678
|
+
? (data) => this._compiled.validateJSON(JSON.stringify(data))
|
|
679
|
+
: (data) => this._compiled.validate(data);
|
|
621
680
|
this.validate = preprocess
|
|
622
681
|
? (data) => {
|
|
623
682
|
preprocess(data);
|
|
624
|
-
return
|
|
683
|
+
return _validate(data);
|
|
625
684
|
}
|
|
626
|
-
:
|
|
627
|
-
this.isValidObject = (data) =>
|
|
685
|
+
: _validate;
|
|
686
|
+
this.isValidObject = (data) => _validate(data).valid;
|
|
628
687
|
this.validateJSON = (jsonStr) => this._compiled.validateJSON(jsonStr);
|
|
629
688
|
this.isValidJSON = (jsonStr) => this._compiled.isValidJSON(jsonStr);
|
|
689
|
+
this.validateAndParse = (jsonStr) => this._compiled.validateAndParse(jsonStr);
|
|
630
690
|
{
|
|
631
691
|
const slot = this._fastSlot;
|
|
632
692
|
this.isValid = (buf) => {
|
|
@@ -655,6 +715,11 @@ class Validator {
|
|
|
655
715
|
};
|
|
656
716
|
}
|
|
657
717
|
}
|
|
718
|
+
|
|
719
|
+
// Save to identity cache for ultra-fast reuse with same schema object
|
|
720
|
+
if (this._schemaObj && typeof this._schemaObj === 'object') {
|
|
721
|
+
_identityCache.set(this._schemaObj, this);
|
|
722
|
+
}
|
|
658
723
|
}
|
|
659
724
|
|
|
660
725
|
_ensureNative() {
|
|
@@ -831,6 +896,14 @@ module.exports = { boolFn, hybridFactory, errFn };
|
|
|
831
896
|
}
|
|
832
897
|
};
|
|
833
898
|
|
|
899
|
+
v.validateAndParse = native
|
|
900
|
+
? (jsonStr) => {
|
|
901
|
+
v._ensureNative();
|
|
902
|
+
v.validateAndParse = (s) => v._compiled.validateAndParse(s);
|
|
903
|
+
return v.validateAndParse(jsonStr);
|
|
904
|
+
}
|
|
905
|
+
: () => { throw new Error('Native addon required for validateAndParse()'); };
|
|
906
|
+
|
|
834
907
|
// Standard Schema V1
|
|
835
908
|
Object.defineProperty(v, "~standard", {
|
|
836
909
|
value: Object.freeze({
|
|
@@ -1030,10 +1103,31 @@ Validator.loadBundle = function (mods, schemas, opts) {
|
|
|
1030
1103
|
});
|
|
1031
1104
|
};
|
|
1032
1105
|
|
|
1106
|
+
const parseJSON = native ? native.parseJSON : JSON.parse;
|
|
1107
|
+
|
|
1108
|
+
// Ultra-fast compile: returns validate function directly, no Validator wrapper
|
|
1109
|
+
// WeakMap cached — second call with same schema object is ~3ns
|
|
1110
|
+
const _compileFnCache = new WeakMap();
|
|
1111
|
+
function compile(schema, opts) {
|
|
1112
|
+
if (!opts && typeof schema === 'object' && schema !== null) {
|
|
1113
|
+
const hit = _compileFnCache.get(schema);
|
|
1114
|
+
if (hit) return hit;
|
|
1115
|
+
}
|
|
1116
|
+
const v = new Validator(schema, opts);
|
|
1117
|
+
v._ensureCompiled();
|
|
1118
|
+
const fn = v.validate;
|
|
1119
|
+
if (!opts && typeof schema === 'object' && schema !== null) {
|
|
1120
|
+
_compileFnCache.set(schema, fn);
|
|
1121
|
+
}
|
|
1122
|
+
return fn;
|
|
1123
|
+
}
|
|
1124
|
+
|
|
1033
1125
|
module.exports = {
|
|
1034
1126
|
Validator,
|
|
1127
|
+
compile,
|
|
1035
1128
|
validate,
|
|
1036
1129
|
version,
|
|
1037
1130
|
createPaddedBuffer,
|
|
1038
1131
|
SIMDJSON_PADDING,
|
|
1132
|
+
parseJSON,
|
|
1039
1133
|
};
|