ata-validator 0.4.12 → 0.4.13
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/README.md +13 -15
- package/index.js +38 -12
- package/lib/js-compiler.js +45 -31
- package/package.json +2 -1
package/README.md
CHANGED
|
@@ -10,12 +10,11 @@ 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 |
|
|
14
|
-
| **validate(obj)** invalid |
|
|
15
|
-
| **isValidObject(obj)** |
|
|
16
|
-
| **
|
|
17
|
-
| **
|
|
18
|
-
| **Schema compilation** | 113K ops/sec | 818 ops/sec | **ata 138x faster** |
|
|
13
|
+
| **validate(obj)** valid | 25.5M ops/sec | 19.3M ops/sec | **ata 1.3x faster** |
|
|
14
|
+
| **validate(obj)** invalid | 17.7M ops/sec | 13.5M ops/sec | **ata 1.3x faster** |
|
|
15
|
+
| **isValidObject(obj)** | 39.5M ops/sec | 17.6M ops/sec | **ata 2.2x faster** |
|
|
16
|
+
| **Constructor cold start** | 1.28M ops/sec | 812 ops/sec | **ata 1,580x faster** |
|
|
17
|
+
| **First validation** | 396K ops/sec | 880 ops/sec | **ata 450x faster** |
|
|
19
18
|
|
|
20
19
|
> validate(obj) numbers are isolated single-schema benchmarks. Multi-schema benchmark overhead reduces throughput; real-world numbers depend on workload.
|
|
21
20
|
|
|
@@ -31,17 +30,16 @@ Ultra-fast JSON Schema validator powered by [simdjson](https://github.com/simdjs
|
|
|
31
30
|
|
|
32
31
|
| Scenario | ata | ajv | |
|
|
33
32
|
|---|---|---|---|
|
|
34
|
-
| **Serverless cold start** (50 schemas) |
|
|
33
|
+
| **Serverless cold start** (50 schemas) | 0.1ms | 23ms | **ata 242x faster** |
|
|
35
34
|
| **ReDoS protection** (`^(a+)+$`) | 0.3ms | 765ms | **ata immune (RE2)** |
|
|
36
35
|
| **Batch NDJSON** (10K items, multi-core) | 13.4M/sec | 5.1M/sec | **ata 2.6x faster** |
|
|
37
|
-
| **Fastify
|
|
38
|
-
| **Fastify startup** (500 routes) | 46ms | 77ms (standalone) | **ata 1.7x faster** |
|
|
36
|
+
| **Fastify startup** (5 routes) | 0.5ms | 6.0ms | **ata 12x faster** |
|
|
39
37
|
|
|
40
38
|
> Isolated single-schema benchmarks. Results vary by workload and hardware.
|
|
41
39
|
|
|
42
40
|
### How it works
|
|
43
41
|
|
|
44
|
-
**
|
|
42
|
+
**Combined single-pass validator**: ata compiles schemas into a single function that validates and collects errors in one pass. Valid data returns `VALID_RESULT` with zero allocation. Invalid data collects errors inline - no double validation, no try/catch (3.3x V8 deopt). Lazy compilation defers all work to first usage - constructor is near-zero cost.
|
|
45
43
|
|
|
46
44
|
**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`.
|
|
47
45
|
|
|
@@ -55,8 +53,8 @@ Ultra-fast JSON Schema validator powered by [simdjson](https://github.com/simdjs
|
|
|
55
53
|
|
|
56
54
|
## When to use ata
|
|
57
55
|
|
|
58
|
-
- **High-throughput `validate(obj)`** -
|
|
59
|
-
- **Serverless / cold starts** -
|
|
56
|
+
- **High-throughput `validate(obj)`** - 25.5M ops/sec valid, 17.7M ops/sec invalid
|
|
57
|
+
- **Serverless / cold starts** - 1,580x faster constructor, 450x faster first validation
|
|
60
58
|
- **Security-sensitive apps** - RE2 regex, immune to ReDoS attacks
|
|
61
59
|
- **Batch/streaming validation** - NDJSON log processing, data pipelines (2.6x faster)
|
|
62
60
|
- **Standard Schema V1** - native support for Fastify v5, tRPC, TanStack
|
|
@@ -69,7 +67,7 @@ Ultra-fast JSON Schema validator powered by [simdjson](https://github.com/simdjs
|
|
|
69
67
|
|
|
70
68
|
## Features
|
|
71
69
|
|
|
72
|
-
- **Hybrid validator**:
|
|
70
|
+
- **Hybrid validator**: 25.5M ops/sec valid, 17.7M ops/sec invalid - codegen + single-pass error collection. No try/catch, no double pass. Schema compilation cache for repeated schemas
|
|
73
71
|
- **Multi-core**: Parallel validation across all CPU cores - 13.4M validations/sec
|
|
74
72
|
- **simdjson**: SIMD-accelerated JSON parsing at GB/s speeds, adaptive On Demand for large docs
|
|
75
73
|
- **RE2 regex**: Linear-time guarantees, immune to ReDoS attacks (2391x faster on pathological input)
|
|
@@ -104,7 +102,7 @@ const v = new Validator({
|
|
|
104
102
|
required: ['name', 'email']
|
|
105
103
|
});
|
|
106
104
|
|
|
107
|
-
// Fast boolean check - JS codegen,
|
|
105
|
+
// Fast boolean check - JS codegen, 15.3M ops/sec
|
|
108
106
|
v.isValidObject({ name: 'Mert', email: 'mert@example.com', age: 26 }); // true
|
|
109
107
|
|
|
110
108
|
// Full validation with error details + defaults applied
|
|
@@ -152,7 +150,7 @@ fs.writeFileSync('./bundle.js', Validator.bundleCompact(schemas));
|
|
|
152
150
|
const validators = Validator.loadBundle(require('./bundle.js'), schemas);
|
|
153
151
|
```
|
|
154
152
|
|
|
155
|
-
**Fastify startup (
|
|
153
|
+
**Fastify startup (5 routes): ajv 6.0ms → ata 0.5ms (12x faster, no build step needed)**
|
|
156
154
|
|
|
157
155
|
### Standard Schema V1
|
|
158
156
|
|
package/index.js
CHANGED
|
@@ -205,6 +205,9 @@ function collectRemovals(schema, actions, path) {
|
|
|
205
205
|
}
|
|
206
206
|
}
|
|
207
207
|
|
|
208
|
+
// Schema compilation cache: same schema string -> reuse compiled functions
|
|
209
|
+
const _compileCache = new Map();
|
|
210
|
+
|
|
208
211
|
const SIMDJSON_PADDING = 64;
|
|
209
212
|
const VALID_RESULT = Object.freeze({ valid: true, errors: Object.freeze([]) });
|
|
210
213
|
|
|
@@ -254,7 +257,7 @@ class Validator {
|
|
|
254
257
|
return this.validate(data);
|
|
255
258
|
};
|
|
256
259
|
this.isValidObject = (data) => {
|
|
257
|
-
this.
|
|
260
|
+
this._ensureCodegen();
|
|
258
261
|
return this.isValidObject(data);
|
|
259
262
|
};
|
|
260
263
|
this.validateJSON = (jsonStr) => {
|
|
@@ -299,17 +302,21 @@ class Validator {
|
|
|
299
302
|
const schemaObj = this._schemaObj;
|
|
300
303
|
const options = this._options;
|
|
301
304
|
|
|
302
|
-
//
|
|
303
|
-
|
|
304
|
-
|
|
305
|
-
|
|
306
|
-
|
|
307
|
-
|
|
308
|
-
|
|
309
|
-
|
|
310
|
-
|
|
311
|
-
|
|
312
|
-
|
|
305
|
+
// Check cache first -- reuse compiled functions for same schema
|
|
306
|
+
const cached = _compileCache.get(this._schemaStr);
|
|
307
|
+
let jsFn, jsCombinedFn, jsErrFn;
|
|
308
|
+
if (cached && !process.env.ATA_FORCE_NAPI) {
|
|
309
|
+
jsFn = cached.jsFn;
|
|
310
|
+
jsCombinedFn = cached.combined;
|
|
311
|
+
jsErrFn = cached.errFn;
|
|
312
|
+
} else if (!process.env.ATA_FORCE_NAPI) {
|
|
313
|
+
jsFn = compileToJSCodegen(schemaObj) || compileToJS(schemaObj);
|
|
314
|
+
jsCombinedFn = compileToJSCombined(schemaObj, VALID_RESULT);
|
|
315
|
+
jsErrFn = compileToJSCodegenWithErrors(schemaObj);
|
|
316
|
+
_compileCache.set(this._schemaStr, { jsFn, combined: jsCombinedFn, errFn: jsErrFn });
|
|
317
|
+
} else {
|
|
318
|
+
jsFn = null; jsCombinedFn = null; jsErrFn = null;
|
|
319
|
+
}
|
|
313
320
|
this._jsFn = jsFn;
|
|
314
321
|
|
|
315
322
|
// Data mutators -- applied in-place before validation
|
|
@@ -491,6 +498,25 @@ class Validator {
|
|
|
491
498
|
this._fastSlot = native.fastRegister(this._schemaStr);
|
|
492
499
|
}
|
|
493
500
|
|
|
501
|
+
_ensureCodegen() {
|
|
502
|
+
if (this._jsFn) return;
|
|
503
|
+
if (process.env.ATA_FORCE_NAPI) return;
|
|
504
|
+
const cached = _compileCache.get(this._schemaStr);
|
|
505
|
+
if (cached && cached.jsFn) {
|
|
506
|
+
this._jsFn = cached.jsFn;
|
|
507
|
+
this.isValidObject = cached.jsFn;
|
|
508
|
+
return;
|
|
509
|
+
}
|
|
510
|
+
const jsFn = compileToJSCodegen(this._schemaObj) || compileToJS(this._schemaObj);
|
|
511
|
+
this._jsFn = jsFn;
|
|
512
|
+
if (jsFn) {
|
|
513
|
+
this.isValidObject = jsFn;
|
|
514
|
+
// seed cache with codegen, combined/errFn filled later by _ensureCompiled
|
|
515
|
+
if (!cached) _compileCache.set(this._schemaStr, { jsFn, combined: null, errFn: null });
|
|
516
|
+
else cached.jsFn = jsFn;
|
|
517
|
+
}
|
|
518
|
+
}
|
|
519
|
+
|
|
494
520
|
// --- Standalone pre-compilation ---
|
|
495
521
|
// Generate a JS module string that can be written to a file.
|
|
496
522
|
// On next startup, load with Validator.fromStandalone() -- zero compile time.
|
package/lib/js-compiler.js
CHANGED
|
@@ -518,28 +518,48 @@ function compileToJSCodegen(schema) {
|
|
|
518
518
|
schema.dependentSchemas ||
|
|
519
519
|
schema.propertyNames) return null
|
|
520
520
|
|
|
521
|
-
const ctx = { varCounter: 0, helpers: [], helperCode: [], rootDefs, refStack: new Set() }
|
|
521
|
+
const ctx = { varCounter: 0, helpers: [], helperCode: [], closureVars: [], closureVals: [], rootDefs, refStack: new Set() }
|
|
522
522
|
const lines = []
|
|
523
523
|
genCode(schema, 'd', lines, ctx)
|
|
524
524
|
if (lines.length === 0) return () => true
|
|
525
525
|
|
|
526
|
-
const helperStr = ctx.helperCode.length ? ctx.helperCode.join('\n ') + '\n ' : ''
|
|
527
526
|
const checkStr = lines.join('\n ')
|
|
528
|
-
|
|
527
|
+
|
|
528
|
+
// Regex and helpers are passed as closure variables (not re-created per call)
|
|
529
|
+
const closureNames = ctx.closureVars
|
|
530
|
+
const closureValues = ctx.closureVals
|
|
531
|
+
|
|
532
|
+
// Pre-create regex objects once
|
|
533
|
+
for (const code of ctx.helperCode) {
|
|
534
|
+
const match = code.match(/^const (_re\d+)=new RegExp\((.+)\)$/)
|
|
535
|
+
if (match) {
|
|
536
|
+
closureNames.push(match[1])
|
|
537
|
+
closureValues.push(new RegExp(JSON.parse(match[2])))
|
|
538
|
+
}
|
|
539
|
+
}
|
|
540
|
+
|
|
541
|
+
const body = checkStr + '\n return true'
|
|
529
542
|
|
|
530
543
|
try {
|
|
531
|
-
|
|
544
|
+
let boolFn
|
|
545
|
+
if (closureNames.length > 0) {
|
|
546
|
+
const factory = new Function(...closureNames, `return function(d){${body}}`)
|
|
547
|
+
boolFn = factory(...closureValues)
|
|
548
|
+
} else {
|
|
549
|
+
boolFn = new Function('d', body)
|
|
550
|
+
}
|
|
532
551
|
|
|
533
552
|
// Build hybrid: same body, return R instead of true, return E(d) instead of false.
|
|
534
|
-
const hybridBody = replaceTopLevel(
|
|
553
|
+
const hybridBody = replaceTopLevel(checkStr + '\n return R')
|
|
535
554
|
try {
|
|
536
|
-
const
|
|
537
|
-
boolFn._hybridFactory =
|
|
555
|
+
const hybridFactory = new Function(...closureNames, 'R', 'E', `return function(d){${hybridBody}}`)
|
|
556
|
+
boolFn._hybridFactory = (R, E) => hybridFactory(...closureValues, R, E)
|
|
538
557
|
} catch {}
|
|
539
558
|
|
|
540
|
-
// Store source for standalone compilation (
|
|
541
|
-
|
|
542
|
-
boolFn.
|
|
559
|
+
// Store source for standalone compilation (includes regex inline for file output)
|
|
560
|
+
const helperStr = ctx.helperCode.length ? ctx.helperCode.join('\n ') + '\n ' : ''
|
|
561
|
+
boolFn._source = helperStr + body
|
|
562
|
+
boolFn._hybridSource = helperStr + hybridBody
|
|
543
563
|
|
|
544
564
|
return boolFn
|
|
545
565
|
} catch {
|
|
@@ -656,26 +676,13 @@ function genCode(schema, v, lines, ctx, knownType) {
|
|
|
656
676
|
// Collect required keys so property checks can skip 'in' guard
|
|
657
677
|
const requiredSet = new Set(schema.required || [])
|
|
658
678
|
|
|
659
|
-
// required
|
|
660
|
-
|
|
661
|
-
// `d.key !== undefined` is faster than `'key' in d` (no prototype chain walk).
|
|
662
|
-
const hoisted = {} // key -> local var name
|
|
679
|
+
// required: direct property access (faster than destructuring in V8)
|
|
680
|
+
const hoisted = {} // key -> access expression (no local vars)
|
|
663
681
|
if (schema.required && schema.properties && isObj) {
|
|
664
|
-
const destructKeys = []
|
|
665
682
|
const reqChecks = []
|
|
666
683
|
for (const key of schema.required) {
|
|
667
|
-
|
|
668
|
-
|
|
669
|
-
hoisted[key] = localVar
|
|
670
|
-
destructKeys.push(`${JSON.stringify(key)}:${localVar}`)
|
|
671
|
-
reqChecks.push(`${localVar}===undefined`)
|
|
672
|
-
} else {
|
|
673
|
-
// Required but no property schema — just check existence
|
|
674
|
-
reqChecks.push(`${v}[${JSON.stringify(key)}]===undefined`)
|
|
675
|
-
}
|
|
676
|
-
}
|
|
677
|
-
if (destructKeys.length > 0) {
|
|
678
|
-
lines.push(`const{${destructKeys.join(',')}}=${v}`)
|
|
684
|
+
hoisted[key] = `${v}[${JSON.stringify(key)}]`
|
|
685
|
+
reqChecks.push(`${v}[${JSON.stringify(key)}]===undefined`)
|
|
679
686
|
}
|
|
680
687
|
if (reqChecks.length > 0) {
|
|
681
688
|
lines.push(`if(${reqChecks.join('||')})return false`)
|
|
@@ -740,10 +747,17 @@ function genCode(schema, v, lines, ctx, knownType) {
|
|
|
740
747
|
|
|
741
748
|
// additionalProperties
|
|
742
749
|
if (schema.additionalProperties === false && schema.properties) {
|
|
743
|
-
const
|
|
744
|
-
|
|
745
|
-
|
|
746
|
-
|
|
750
|
+
const propCount = Object.keys(schema.properties).length
|
|
751
|
+
if (!schema.patternProperties) {
|
|
752
|
+
// Fast path: known property count, just check length
|
|
753
|
+
const inner = `if(Object.getOwnPropertyNames(${v}).length!==${propCount})return false`
|
|
754
|
+
lines.push(isObj ? inner : `if(typeof ${v}==='object'&&${v}!==null&&!Array.isArray(${v})){${inner}}`)
|
|
755
|
+
} else {
|
|
756
|
+
const allowed = Object.keys(schema.properties).map(k => `'${esc(k)}'`).join(',')
|
|
757
|
+
const ci = ctx.varCounter++
|
|
758
|
+
const inner = `const _k${ci}=Object.keys(${v});const _a${ci}=new Set([${allowed}]);for(let _i=0;_i<_k${ci}.length;_i++)if(!_a${ci}.has(_k${ci}[_i]))return false`
|
|
759
|
+
lines.push(isObj ? `{${inner}}` : `if(typeof ${v}==='object'&&${v}!==null&&!Array.isArray(${v})){${inner}}`)
|
|
760
|
+
}
|
|
747
761
|
}
|
|
748
762
|
|
|
749
763
|
// dependentRequired
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "ata-validator",
|
|
3
|
-
"version": "0.4.
|
|
3
|
+
"version": "0.4.13",
|
|
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",
|
|
@@ -63,6 +63,7 @@
|
|
|
63
63
|
"node-gyp-build": "^4.8.4"
|
|
64
64
|
},
|
|
65
65
|
"devDependencies": {
|
|
66
|
+
"@sinclair/typebox": "^0.34.49",
|
|
66
67
|
"node-gyp": "^11.0.0",
|
|
67
68
|
"prebuildify": "^6.0.1"
|
|
68
69
|
},
|