ata-validator 0.4.10 → 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/README.md +43 -21
- package/index.js +363 -184
- 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 |
|
|
14
|
-
| **validate(obj)** invalid |
|
|
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 |
|
|
17
|
-
| **validateJSON(str)** invalid | 2.
|
|
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
|
|
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
|
-
>
|
|
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
|
|
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
|
|
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
|
-
- **
|
|
58
|
-
- **Serverless / cold starts**
|
|
59
|
-
- **Security-sensitive apps**
|
|
60
|
-
- **Batch/streaming validation**
|
|
61
|
-
- **Standard Schema V1**
|
|
62
|
-
- **C/C++ embedding**
|
|
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`**
|
|
67
|
-
- **100% spec compliance needed**
|
|
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**:
|
|
72
|
-
- **Multi-core**: Parallel validation across all CPU cores
|
|
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
|
|
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
|
|
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
|
|
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 {
|
|
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 !==
|
|
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 !==
|
|
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 ===
|
|
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 ===
|
|
26
|
-
data[key] =
|
|
27
|
-
|
|
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 !==
|
|
42
|
+
if (typeof target !== "object" || target === null) return;
|
|
36
43
|
target = target[parentPath[j]];
|
|
37
44
|
}
|
|
38
|
-
if (
|
|
39
|
-
|
|
40
|
-
|
|
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 ===
|
|
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 !==
|
|
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 !==
|
|
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 !==
|
|
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 ===
|
|
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 !==
|
|
101
|
+
if (typeof target !== "object" || target === null) return;
|
|
89
102
|
target = target[parentPath[j]];
|
|
90
103
|
}
|
|
91
|
-
if (typeof 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
|
|
108
|
-
|
|
109
|
-
|
|
110
|
-
|
|
111
|
-
|
|
112
|
-
|
|
113
|
-
|
|
114
|
-
|
|
115
|
-
case
|
|
116
|
-
|
|
117
|
-
|
|
118
|
-
|
|
119
|
-
|
|
120
|
-
|
|
121
|
-
|
|
122
|
-
|
|
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 !==
|
|
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 !==
|
|
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 !==
|
|
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 !==
|
|
183
|
+
if (typeof target !== "object" || target === null) return;
|
|
158
184
|
target = target[parentPath[j]];
|
|
159
185
|
}
|
|
160
|
-
if (
|
|
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 ===
|
|
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
|
-
:
|
|
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
|
|
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
|
|
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 =
|
|
237
|
-
|
|
238
|
-
|
|
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
|
-
//
|
|
242
|
-
|
|
243
|
-
|
|
244
|
-
|
|
245
|
-
|
|
246
|
-
|
|
247
|
-
|
|
248
|
-
|
|
249
|
-
|
|
250
|
-
|
|
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 {
|
|
350
|
+
try {
|
|
351
|
+
jsErrFn({}, true);
|
|
352
|
+
safeErrFn = (d) => jsErrFn(d, true);
|
|
353
|
+
} catch {}
|
|
263
354
|
}
|
|
264
|
-
|
|
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
|
-
?
|
|
275
|
-
|
|
276
|
-
|
|
277
|
-
|
|
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 =
|
|
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(
|
|
285
|
-
|
|
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
|
-
|
|
288
|
-
|
|
289
|
-
return compiled.validateJSON(jsonStr);
|
|
396
|
+
this._ensureNative();
|
|
397
|
+
return this._compiled.validateJSON(jsonStr);
|
|
290
398
|
}
|
|
291
399
|
: (jsonStr) => {
|
|
292
|
-
try {
|
|
293
|
-
|
|
294
|
-
|
|
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
|
-
|
|
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 {
|
|
306
|
-
|
|
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
|
-
|
|
311
|
-
|
|
312
|
-
|
|
313
|
-
|
|
314
|
-
|
|
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()
|
|
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 ===
|
|
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};` :
|
|
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
|
|
501
|
+
const applyRemove = options.removeAdditional
|
|
502
|
+
? buildRemover(schemaObj)
|
|
503
|
+
: null;
|
|
381
504
|
const mutators = [applyRemove, applyCoerce, applyDefaults].filter(Boolean);
|
|
382
|
-
const preprocess =
|
|
383
|
-
|
|
384
|
-
|
|
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) => ({
|
|
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 {
|
|
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
|
-
?
|
|
405
|
-
|
|
406
|
-
|
|
407
|
-
|
|
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 {
|
|
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
|
|
416
|
-
|
|
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,
|
|
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 {
|
|
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,
|
|
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
|
|
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
|
|
500
|
-
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(
|
|
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
|
|
670
|
+
if (!jsFn || !jsFn._hybridSource) return "null";
|
|
512
671
|
const jsErrFn = compileToJSCodegenWithErrors(
|
|
513
|
-
typeof schema ===
|
|
672
|
+
typeof schema === "string" ? JSON.parse(schema) : schema,
|
|
514
673
|
);
|
|
515
|
-
const errBody =
|
|
516
|
-
|
|
517
|
-
|
|
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(
|
|
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 ===
|
|
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) {
|
|
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) {
|
|
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
|
|
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(
|
|
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
|
|
575
|
-
|
|
576
|
-
|
|
577
|
-
|
|
578
|
-
|
|
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 = {
|
|
764
|
+
module.exports = {
|
|
765
|
+
Validator,
|
|
766
|
+
validate,
|
|
767
|
+
version,
|
|
768
|
+
createPaddedBuffer,
|
|
769
|
+
SIMDJSON_PADDING,
|
|
770
|
+
};
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "ata-validator",
|
|
3
|
-
"version": "0.4.
|
|
3
|
+
"version": "0.4.11",
|
|
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",
|