ata-validator 0.2.0 → 0.4.1

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,4 +1,60 @@
1
1
  const native = require("node-gyp-build")(__dirname);
2
+ const { compileToJS, compileToJSCodegen } = require("./lib/js-compiler");
3
+
4
+ // Extract default values from a schema tree. Returns a function that applies
5
+ // defaults to an object in-place (mutates), or null if no defaults exist.
6
+ function buildDefaultsApplier(schema) {
7
+ if (typeof schema !== 'object' || schema === null) return null;
8
+ const actions = [];
9
+ collectDefaults(schema, actions);
10
+ if (actions.length === 0) return null;
11
+ return (data) => {
12
+ for (let i = 0; i < actions.length; i++) actions[i](data);
13
+ };
14
+ }
15
+
16
+ function collectDefaults(schema, actions, path) {
17
+ if (typeof schema !== 'object' || schema === null) return;
18
+ const props = schema.properties;
19
+ if (!props) return;
20
+ for (const [key, prop] of Object.entries(props)) {
21
+ if (prop && typeof prop === 'object' && prop.default !== undefined) {
22
+ const defaultVal = prop.default;
23
+ if (!path) {
24
+ 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;
28
+ }
29
+ });
30
+ } else {
31
+ const parentPath = path;
32
+ actions.push((data) => {
33
+ let target = data;
34
+ for (let j = 0; j < parentPath.length; j++) {
35
+ if (typeof target !== 'object' || target === null) return;
36
+ target = target[parentPath[j]];
37
+ }
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;
41
+ }
42
+ });
43
+ }
44
+ }
45
+ // Recurse into nested object schemas
46
+ if (prop && typeof prop === 'object' && prop.properties) {
47
+ collectDefaults(prop, actions, (path || []).concat(key));
48
+ }
49
+ }
50
+ }
51
+
52
+ const SIMDJSON_PADDING = 64;
53
+ const VALID_RESULT = Object.freeze({ valid: true, errors: Object.freeze([]) });
54
+
55
+ // Above this size, simdjson On Demand (selective field access) beats JSON.parse
56
+ // (which must materialize the full JS object tree). Buffer.from + NAPI ~2x faster.
57
+ const SIMDJSON_THRESHOLD = 8192;
2
58
 
3
59
  function parsePointerPath(path) {
4
60
  if (!path) return [];
@@ -10,11 +66,90 @@ function parsePointerPath(path) {
10
66
  }));
11
67
  }
12
68
 
69
+ function createPaddedBuffer(jsonStr) {
70
+ const jsonBuf = Buffer.from(jsonStr);
71
+ const padded = Buffer.allocUnsafe(jsonBuf.length + SIMDJSON_PADDING);
72
+ jsonBuf.copy(padded);
73
+ padded.fill(0, jsonBuf.length);
74
+ return { buffer: padded, length: jsonBuf.length };
75
+ }
76
+
13
77
  class Validator {
14
78
  constructor(schema) {
15
79
  const schemaStr =
16
80
  typeof schema === "string" ? schema : JSON.stringify(schema);
17
- this._compiled = new native.CompiledSchema(schemaStr);
81
+ const compiled = new native.CompiledSchema(schemaStr);
82
+ this._compiled = compiled;
83
+ this._fastSlot = native.fastRegister(schemaStr);
84
+
85
+ // Pure JS fast path — no NAPI, runs in V8 JIT
86
+ // Set ATA_FORCE_NAPI=1 to disable JS codegen (for correctness testing)
87
+ const schemaObj = typeof schema === "string" ? JSON.parse(schema) : schema;
88
+ const jsFn = process.env.ATA_FORCE_NAPI
89
+ ? null
90
+ : (compileToJSCodegen(schemaObj) || compileToJS(schemaObj));
91
+ this._jsFn = jsFn;
92
+
93
+ // Default value applier — applies schema defaults to objects in-place
94
+ const applyDefaults = buildDefaultsApplier(schemaObj);
95
+ this._applyDefaults = applyDefaults;
96
+
97
+ // Closure-capture: avoid `this` property lookup on every call.
98
+ // V8 keeps closure vars in registers — no hidden class traversal.
99
+ const fastSlot = this._fastSlot;
100
+
101
+ // Detect if schema is "selective" — doesn't recurse into arrays/deep objects.
102
+ // Selective schemas benefit from simdjson On Demand (seeks only needed fields).
103
+ // Non-selective schemas (items, allOf with nested) touch everything — JSON.parse + jsFn wins.
104
+ const hasArrayTraversal = schemaObj && (schemaObj.items || schemaObj.prefixItems ||
105
+ schemaObj.contains || (schemaObj.properties && Object.values(schemaObj.properties).some(
106
+ p => p && (p.items || p.prefixItems || p.contains))));
107
+ const useSimdjsonForLarge = !hasArrayTraversal;
108
+
109
+ if (jsFn) {
110
+ this.validate = applyDefaults
111
+ ? (data) => { applyDefaults(data); return jsFn(data) ? VALID_RESULT : compiled.validate(data); }
112
+ : (data) => jsFn(data) ? VALID_RESULT : compiled.validate(data);
113
+ this.isValidObject = jsFn;
114
+ this.validateJSON = useSimdjsonForLarge
115
+ ? (jsonStr) => {
116
+ // Selective schema: large docs use simdjson (skips irrelevant data)
117
+ if (jsonStr.length >= SIMDJSON_THRESHOLD) {
118
+ const buf = Buffer.from(jsonStr);
119
+ if (native.rawFastValidate(fastSlot, buf)) return VALID_RESULT;
120
+ return compiled.validateJSON(jsonStr);
121
+ }
122
+ try {
123
+ const obj = JSON.parse(jsonStr);
124
+ if (jsFn(obj)) return VALID_RESULT;
125
+ } catch (e) {
126
+ if (!(e instanceof SyntaxError)) throw e;
127
+ }
128
+ return compiled.validateJSON(jsonStr);
129
+ }
130
+ : (jsonStr) => {
131
+ // Non-selective schema: JSON.parse + jsFn always wins
132
+ try {
133
+ const obj = JSON.parse(jsonStr);
134
+ if (jsFn(obj)) return VALID_RESULT;
135
+ } catch (e) {
136
+ if (!(e instanceof SyntaxError)) throw e;
137
+ }
138
+ return compiled.validateJSON(jsonStr);
139
+ };
140
+ this.isValidJSON = useSimdjsonForLarge
141
+ ? (jsonStr) => {
142
+ if (jsonStr.length >= SIMDJSON_THRESHOLD) {
143
+ return native.rawFastValidate(fastSlot, Buffer.from(jsonStr));
144
+ }
145
+ try { return jsFn(JSON.parse(jsonStr)); }
146
+ catch (e) { if (!(e instanceof SyntaxError)) throw e; return false; }
147
+ }
148
+ : (jsonStr) => {
149
+ try { return jsFn(JSON.parse(jsonStr)); }
150
+ catch (e) { if (!(e instanceof SyntaxError)) throw e; return false; }
151
+ };
152
+ }
18
153
 
19
154
  const self = this;
20
155
  Object.defineProperty(this, "~standard", {
@@ -22,7 +157,7 @@ class Validator {
22
157
  version: 1,
23
158
  vendor: "ata-validator",
24
159
  validate(value) {
25
- const result = self._compiled.validate(value);
160
+ const result = self.validate(value);
26
161
  if (result.valid) {
27
162
  return { value };
28
163
  }
@@ -40,10 +175,16 @@ class Validator {
40
175
  });
41
176
  }
42
177
 
178
+ // Fallback methods — only used when JS codegen is unavailable
43
179
  validate(data) {
180
+ if (this._applyDefaults) this._applyDefaults(data);
44
181
  return this._compiled.validate(data);
45
182
  }
46
183
 
184
+ isValidObject(data) {
185
+ return this._compiled.validate(data).valid;
186
+ }
187
+
47
188
  validateJSON(jsonStr) {
48
189
  return this._compiled.validateJSON(jsonStr);
49
190
  }
@@ -51,6 +192,31 @@ class Validator {
51
192
  isValidJSON(jsonStr) {
52
193
  return this._compiled.isValidJSON(jsonStr);
53
194
  }
195
+
196
+ // Raw NAPI fast path for Buffer/Uint8Array
197
+ isValid(input) {
198
+ return native.rawFastValidate(this._fastSlot, input);
199
+ }
200
+
201
+ // Zero-copy pre-padded path
202
+ isValidPrepadded(paddedBuffer, jsonLength) {
203
+ return native.rawFastValidate(this._fastSlot, paddedBuffer, jsonLength);
204
+ }
205
+
206
+ // Parallel NDJSON batch (multi-core)
207
+ isValidParallel(buffer) {
208
+ return native.rawParallelValidate(this._fastSlot, buffer);
209
+ }
210
+
211
+ // Parallel count (fastest — single uint32 return)
212
+ countValid(buffer) {
213
+ return native.rawParallelCount(this._fastSlot, buffer);
214
+ }
215
+
216
+ // NDJSON single-thread batch
217
+ isValidNDJSON(buffer) {
218
+ return native.rawNDJSONValidate(this._fastSlot, buffer);
219
+ }
54
220
  }
55
221
 
56
222
  function validate(schema, data) {
@@ -63,4 +229,4 @@ function version() {
63
229
  return native.version();
64
230
  }
65
231
 
66
- module.exports = { Validator, validate, version };
232
+ module.exports = { Validator, validate, version, createPaddedBuffer, SIMDJSON_PADDING };