ata-validator 0.2.0 → 0.4.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/README.md +118 -185
- package/binding/ata_napi.cpp +610 -7
- package/binding.gyp +1 -1
- package/include/ata.h +10 -2
- package/index.js +114 -3
- package/lib/js-compiler.js +845 -0
- package/package.json +3 -2
- package/prebuilds/darwin-arm64/ata-validator.node +0 -0
- package/src/ata.cpp +78 -11
package/index.js
CHANGED
|
@@ -1,4 +1,12 @@
|
|
|
1
1
|
const native = require("node-gyp-build")(__dirname);
|
|
2
|
+
const { compileToJS, compileToJSCodegen } = require("./lib/js-compiler");
|
|
3
|
+
|
|
4
|
+
const SIMDJSON_PADDING = 64;
|
|
5
|
+
const VALID_RESULT = Object.freeze({ valid: true, errors: Object.freeze([]) });
|
|
6
|
+
|
|
7
|
+
// Above this size, simdjson On Demand (selective field access) beats JSON.parse
|
|
8
|
+
// (which must materialize the full JS object tree). Buffer.from + NAPI ~2x faster.
|
|
9
|
+
const SIMDJSON_THRESHOLD = 8192;
|
|
2
10
|
|
|
3
11
|
function parsePointerPath(path) {
|
|
4
12
|
if (!path) return [];
|
|
@@ -10,11 +18,84 @@ function parsePointerPath(path) {
|
|
|
10
18
|
}));
|
|
11
19
|
}
|
|
12
20
|
|
|
21
|
+
function createPaddedBuffer(jsonStr) {
|
|
22
|
+
const jsonBuf = Buffer.from(jsonStr);
|
|
23
|
+
const padded = Buffer.allocUnsafe(jsonBuf.length + SIMDJSON_PADDING);
|
|
24
|
+
jsonBuf.copy(padded);
|
|
25
|
+
padded.fill(0, jsonBuf.length);
|
|
26
|
+
return { buffer: padded, length: jsonBuf.length };
|
|
27
|
+
}
|
|
28
|
+
|
|
13
29
|
class Validator {
|
|
14
30
|
constructor(schema) {
|
|
15
31
|
const schemaStr =
|
|
16
32
|
typeof schema === "string" ? schema : JSON.stringify(schema);
|
|
17
|
-
|
|
33
|
+
const compiled = new native.CompiledSchema(schemaStr);
|
|
34
|
+
this._compiled = compiled;
|
|
35
|
+
this._fastSlot = native.fastRegister(schemaStr);
|
|
36
|
+
|
|
37
|
+
// Pure JS fast path — no NAPI, runs in V8 JIT
|
|
38
|
+
// Set ATA_FORCE_NAPI=1 to disable JS codegen (for correctness testing)
|
|
39
|
+
const schemaObj = typeof schema === "string" ? JSON.parse(schema) : schema;
|
|
40
|
+
const jsFn = process.env.ATA_FORCE_NAPI
|
|
41
|
+
? null
|
|
42
|
+
: (compileToJSCodegen(schemaObj) || compileToJS(schemaObj));
|
|
43
|
+
this._jsFn = jsFn;
|
|
44
|
+
|
|
45
|
+
// Closure-capture: avoid `this` property lookup on every call.
|
|
46
|
+
// V8 keeps closure vars in registers — no hidden class traversal.
|
|
47
|
+
const fastSlot = this._fastSlot;
|
|
48
|
+
|
|
49
|
+
// Detect if schema is "selective" — doesn't recurse into arrays/deep objects.
|
|
50
|
+
// Selective schemas benefit from simdjson On Demand (seeks only needed fields).
|
|
51
|
+
// Non-selective schemas (items, allOf with nested) touch everything — JSON.parse + jsFn wins.
|
|
52
|
+
const hasArrayTraversal = schemaObj && (schemaObj.items || schemaObj.prefixItems ||
|
|
53
|
+
schemaObj.contains || (schemaObj.properties && Object.values(schemaObj.properties).some(
|
|
54
|
+
p => p && (p.items || p.prefixItems || p.contains))));
|
|
55
|
+
const useSimdjsonForLarge = !hasArrayTraversal;
|
|
56
|
+
|
|
57
|
+
if (jsFn) {
|
|
58
|
+
this.validate = (data) => jsFn(data) ? VALID_RESULT : compiled.validate(data);
|
|
59
|
+
this.isValidObject = jsFn;
|
|
60
|
+
this.validateJSON = useSimdjsonForLarge
|
|
61
|
+
? (jsonStr) => {
|
|
62
|
+
// Selective schema: large docs use simdjson (skips irrelevant data)
|
|
63
|
+
if (jsonStr.length >= SIMDJSON_THRESHOLD) {
|
|
64
|
+
const buf = Buffer.from(jsonStr);
|
|
65
|
+
if (native.rawFastValidate(fastSlot, buf)) return VALID_RESULT;
|
|
66
|
+
return compiled.validateJSON(jsonStr);
|
|
67
|
+
}
|
|
68
|
+
try {
|
|
69
|
+
const obj = JSON.parse(jsonStr);
|
|
70
|
+
if (jsFn(obj)) return VALID_RESULT;
|
|
71
|
+
} catch (e) {
|
|
72
|
+
if (!(e instanceof SyntaxError)) throw e;
|
|
73
|
+
}
|
|
74
|
+
return compiled.validateJSON(jsonStr);
|
|
75
|
+
}
|
|
76
|
+
: (jsonStr) => {
|
|
77
|
+
// Non-selective schema: JSON.parse + jsFn always wins
|
|
78
|
+
try {
|
|
79
|
+
const obj = JSON.parse(jsonStr);
|
|
80
|
+
if (jsFn(obj)) return VALID_RESULT;
|
|
81
|
+
} catch (e) {
|
|
82
|
+
if (!(e instanceof SyntaxError)) throw e;
|
|
83
|
+
}
|
|
84
|
+
return compiled.validateJSON(jsonStr);
|
|
85
|
+
};
|
|
86
|
+
this.isValidJSON = useSimdjsonForLarge
|
|
87
|
+
? (jsonStr) => {
|
|
88
|
+
if (jsonStr.length >= SIMDJSON_THRESHOLD) {
|
|
89
|
+
return native.rawFastValidate(fastSlot, Buffer.from(jsonStr));
|
|
90
|
+
}
|
|
91
|
+
try { return jsFn(JSON.parse(jsonStr)); }
|
|
92
|
+
catch (e) { if (!(e instanceof SyntaxError)) throw e; return false; }
|
|
93
|
+
}
|
|
94
|
+
: (jsonStr) => {
|
|
95
|
+
try { return jsFn(JSON.parse(jsonStr)); }
|
|
96
|
+
catch (e) { if (!(e instanceof SyntaxError)) throw e; return false; }
|
|
97
|
+
};
|
|
98
|
+
}
|
|
18
99
|
|
|
19
100
|
const self = this;
|
|
20
101
|
Object.defineProperty(this, "~standard", {
|
|
@@ -22,7 +103,7 @@ class Validator {
|
|
|
22
103
|
version: 1,
|
|
23
104
|
vendor: "ata-validator",
|
|
24
105
|
validate(value) {
|
|
25
|
-
const result = self.
|
|
106
|
+
const result = self.validate(value);
|
|
26
107
|
if (result.valid) {
|
|
27
108
|
return { value };
|
|
28
109
|
}
|
|
@@ -40,10 +121,15 @@ class Validator {
|
|
|
40
121
|
});
|
|
41
122
|
}
|
|
42
123
|
|
|
124
|
+
// Fallback methods — only used when JS codegen is unavailable
|
|
43
125
|
validate(data) {
|
|
44
126
|
return this._compiled.validate(data);
|
|
45
127
|
}
|
|
46
128
|
|
|
129
|
+
isValidObject(data) {
|
|
130
|
+
return this._compiled.validate(data).valid;
|
|
131
|
+
}
|
|
132
|
+
|
|
47
133
|
validateJSON(jsonStr) {
|
|
48
134
|
return this._compiled.validateJSON(jsonStr);
|
|
49
135
|
}
|
|
@@ -51,6 +137,31 @@ class Validator {
|
|
|
51
137
|
isValidJSON(jsonStr) {
|
|
52
138
|
return this._compiled.isValidJSON(jsonStr);
|
|
53
139
|
}
|
|
140
|
+
|
|
141
|
+
// Raw NAPI fast path for Buffer/Uint8Array
|
|
142
|
+
isValid(input) {
|
|
143
|
+
return native.rawFastValidate(this._fastSlot, input);
|
|
144
|
+
}
|
|
145
|
+
|
|
146
|
+
// Zero-copy pre-padded path
|
|
147
|
+
isValidPrepadded(paddedBuffer, jsonLength) {
|
|
148
|
+
return native.rawFastValidate(this._fastSlot, paddedBuffer, jsonLength);
|
|
149
|
+
}
|
|
150
|
+
|
|
151
|
+
// Parallel NDJSON batch (multi-core)
|
|
152
|
+
isValidParallel(buffer) {
|
|
153
|
+
return native.rawParallelValidate(this._fastSlot, buffer);
|
|
154
|
+
}
|
|
155
|
+
|
|
156
|
+
// Parallel count (fastest — single uint32 return)
|
|
157
|
+
countValid(buffer) {
|
|
158
|
+
return native.rawParallelCount(this._fastSlot, buffer);
|
|
159
|
+
}
|
|
160
|
+
|
|
161
|
+
// NDJSON single-thread batch
|
|
162
|
+
isValidNDJSON(buffer) {
|
|
163
|
+
return native.rawNDJSONValidate(this._fastSlot, buffer);
|
|
164
|
+
}
|
|
54
165
|
}
|
|
55
166
|
|
|
56
167
|
function validate(schema, data) {
|
|
@@ -63,4 +174,4 @@ function version() {
|
|
|
63
174
|
return native.version();
|
|
64
175
|
}
|
|
65
176
|
|
|
66
|
-
module.exports = { Validator, validate, version };
|
|
177
|
+
module.exports = { Validator, validate, version, createPaddedBuffer, SIMDJSON_PADDING };
|