ata-validator 0.5.0 → 0.6.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 +40 -25
- package/binding/ata_napi.cpp +13 -7
- package/compat.js +1 -9
- package/include/ata.h +4 -0
- package/index.d.ts +12 -4
- package/index.js +99 -14
- package/lib/js-compiler.js +951 -190
- package/package.json +2 -2
- package/prebuilds/darwin-arm64/ata-validator.node +0 -0
- package/src/ata.cpp +327 -1
- package/prebuilds/linux-arm64/ata-validator.node +0 -0
- package/prebuilds/linux-x64/ata-validator.node +0 -0
package/README.md
CHANGED
|
@@ -6,23 +6,23 @@ Ultra-fast JSON Schema validator powered by [simdjson](https://github.com/simdjs
|
|
|
6
6
|
|
|
7
7
|
## Performance
|
|
8
8
|
|
|
9
|
-
### Simple Schema (
|
|
9
|
+
### Simple Schema (7 properties, type + format + range + nested object)
|
|
10
10
|
|
|
11
11
|
| Scenario | ata | ajv | |
|
|
12
12
|
|---|---|---|---|
|
|
13
|
-
| **validate(obj)** valid |
|
|
14
|
-
| **validate(obj)** invalid |
|
|
15
|
-
| **isValidObject(obj)** |
|
|
16
|
-
| **Schema compilation** |
|
|
17
|
-
| **First validation** |
|
|
13
|
+
| **validate(obj)** valid | 22ns | 102ns | **ata 4.6x faster** |
|
|
14
|
+
| **validate(obj)** invalid | 87ns | 182ns | **ata 2.1x faster** |
|
|
15
|
+
| **isValidObject(obj)** | 21ns | 100ns | **ata 4.7x faster** |
|
|
16
|
+
| **Schema compilation** | 695ns | 1.30ms | **ata 1,867x faster** |
|
|
17
|
+
| **First validation** | 2.07μs | 1.11ms | **ata 534x faster** |
|
|
18
18
|
|
|
19
19
|
### Complex Schema (patternProperties + dependentSchemas + propertyNames + additionalProperties)
|
|
20
20
|
|
|
21
21
|
| Scenario | ata | ajv | |
|
|
22
22
|
|---|---|---|---|
|
|
23
|
-
| **validate(obj)** valid |
|
|
24
|
-
| **validate(obj)** invalid |
|
|
25
|
-
| **isValidObject(obj)** |
|
|
23
|
+
| **validate(obj)** valid | 17ns | 115ns | **ata 6.8x faster** |
|
|
24
|
+
| **validate(obj)** invalid | 59ns | 194ns | **ata 3.3x faster** |
|
|
25
|
+
| **isValidObject(obj)** | 19ns | 124ns | **ata 6.6x faster** |
|
|
26
26
|
|
|
27
27
|
### Cross-Schema `$ref` (multi-schema with `$id` registry)
|
|
28
28
|
|
|
@@ -33,14 +33,29 @@ Ultra-fast JSON Schema validator powered by [simdjson](https://github.com/simdjs
|
|
|
33
33
|
|
|
34
34
|
> Measured with [mitata](https://github.com/evanwashere/mitata) on Apple M4 Pro (process-isolated). [Benchmark code](benchmark/bench_complex_mitata.mjs)
|
|
35
35
|
|
|
36
|
+
### unevaluatedProperties / unevaluatedItems
|
|
37
|
+
|
|
38
|
+
| Scenario | ata | ajv | |
|
|
39
|
+
|---|---|---|---|
|
|
40
|
+
| **Tier 1** (properties only) valid | 3.3ns | 8.7ns | **ata 2.6x faster** |
|
|
41
|
+
| **Tier 1** invalid | 3.7ns | 19.1ns | **ata 5.2x faster** |
|
|
42
|
+
| **Tier 2** (allOf) valid | 3.3ns | 9.9ns | **ata 3.0x faster** |
|
|
43
|
+
| **Tier 3** (anyOf) valid | 6.7ns | 23.2ns | **ata 3.5x faster** |
|
|
44
|
+
| **Tier 3** invalid | 7.1ns | 42.4ns | **ata 6.0x faster** |
|
|
45
|
+
| **unevaluatedItems** valid | 1.0ns | 5.5ns | **ata 5.4x faster** |
|
|
46
|
+
| **unevaluatedItems** invalid | 0.96ns | 14.2ns | **ata 14.8x faster** |
|
|
47
|
+
| **Compilation** | 375ns | 2.59ms | **ata 6,904x faster** |
|
|
48
|
+
|
|
49
|
+
Three-tier hybrid codegen: static schemas compile to zero-overhead key checks, dynamic schemas (anyOf/oneOf) use bitmask tracking with V8-inlined branch functions. [Benchmark code](benchmark/bench_unevaluated_mitata.mjs)
|
|
50
|
+
|
|
36
51
|
### vs Ecosystem (Zod, Valibot, TypeBox)
|
|
37
52
|
|
|
38
53
|
| Scenario | ata | ajv | typebox | zod | valibot |
|
|
39
54
|
|---|---|---|---|---|---|
|
|
40
|
-
| **validate (valid)** | **
|
|
41
|
-
| **validate (invalid)** | **
|
|
42
|
-
| **compilation** | **
|
|
43
|
-
| **first validation** | **1
|
|
55
|
+
| **validate (valid)** | **9ns** | 38ns | 50ns | 334ns | 326ns |
|
|
56
|
+
| **validate (invalid)** | **37ns** | 103ns | 4ns | 11.8μs | 842ns |
|
|
57
|
+
| **compilation** | **584ns** | 1.20ms | 52μs | — | — |
|
|
58
|
+
| **first validation** | **2.1μs** | 1.11ms | 54μs | — | — |
|
|
44
59
|
|
|
45
60
|
> Different categories: ata/ajv/typebox are JSON Schema validators, zod/valibot are schema-builder DSLs. [Benchmark code](benchmark/bench_all_mitata.mjs)
|
|
46
61
|
|
|
@@ -56,7 +71,7 @@ Ultra-fast JSON Schema validator powered by [simdjson](https://github.com/simdjs
|
|
|
56
71
|
|
|
57
72
|
| Scenario | ata | ajv | |
|
|
58
73
|
|---|---|---|---|
|
|
59
|
-
| **Serverless cold start** (50 schemas) | 0.1ms | 23ms | **ata
|
|
74
|
+
| **Serverless cold start** (50 schemas) | 0.1ms | 23ms | **ata 230x faster** |
|
|
60
75
|
| **ReDoS protection** (`^(a+)+$`) | 0.3ms | 765ms | **ata immune (RE2)** |
|
|
61
76
|
| **Batch NDJSON** (10K items, multi-core) | 13.4M/sec | 5.1M/sec | **ata 2.6x faster** |
|
|
62
77
|
| **Fastify startup** (5 routes) | 0.5ms | 6.0ms | **ata 12x faster** |
|
|
@@ -67,7 +82,7 @@ Ultra-fast JSON Schema validator powered by [simdjson](https://github.com/simdjs
|
|
|
67
82
|
|
|
68
83
|
**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 with pre-allocated frozen error objects - no double validation, no try/catch (3.3x V8 deopt). Lazy compilation defers all work to first usage - constructor is near-zero cost.
|
|
69
84
|
|
|
70
|
-
**JS codegen**: Schemas are compiled to monolithic JS functions (like ajv). Full keyword support including `patternProperties`, `dependentSchemas`, `propertyNames`, cross-schema `$ref` with `$id` registry, and Draft 7 auto-detection. charCodeAt prefix matching replaces regex for simple patterns (4x faster). Merged key iteration loops (patternProperties + propertyNames + additionalProperties in a single `for..in`).
|
|
85
|
+
**JS codegen**: Schemas are compiled to monolithic JS functions (like ajv). Full keyword support including `patternProperties`, `dependentSchemas`, `propertyNames`, `unevaluatedProperties`, `unevaluatedItems`, cross-schema `$ref` with `$id` registry, and Draft 7 auto-detection. Three-tier hybrid approach for unevaluated keywords: compile-time resolution for static schemas, bitmask tracking for dynamic ones. charCodeAt prefix matching replaces regex for simple patterns (4x faster). Merged key iteration loops (patternProperties + propertyNames + additionalProperties in a single `for..in`).
|
|
71
86
|
|
|
72
87
|
**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), inline key comparison for small property sets (no Set.has overhead).
|
|
73
88
|
|
|
@@ -75,15 +90,15 @@ Ultra-fast JSON Schema validator powered by [simdjson](https://github.com/simdjs
|
|
|
75
90
|
|
|
76
91
|
### JSON Schema Test Suite
|
|
77
92
|
|
|
78
|
-
**
|
|
93
|
+
**96.9%** pass rate (1109/1144) on official [JSON Schema Test Suite](https://github.com/json-schema-org/JSON-Schema-Test-Suite) (Draft 2020-12).
|
|
79
94
|
|
|
80
95
|
## When to use ata
|
|
81
96
|
|
|
82
|
-
- **High-throughput `validate(obj)`** -
|
|
83
|
-
- **Complex schemas** - `patternProperties`, `dependentSchemas`, `propertyNames` all inline JS codegen (
|
|
97
|
+
- **High-throughput `validate(obj)`** - 6.8x faster than ajv on complex schemas, 38x faster than zod
|
|
98
|
+
- **Complex schemas** - `patternProperties`, `dependentSchemas`, `propertyNames`, `unevaluatedProperties` all inline JS codegen (6.8x faster than ajv)
|
|
84
99
|
- **Multi-schema projects** - cross-schema `$ref` with `$id` registry, `addSchema()` API
|
|
85
100
|
- **Draft 7 migration** - auto-detects `$schema`, normalizes Draft 7 keywords transparently
|
|
86
|
-
- **Serverless / cold starts** -
|
|
101
|
+
- **Serverless / cold starts** - 6,904x faster compilation, 5,148x faster first validation
|
|
87
102
|
- **Security-sensitive apps** - RE2 regex, immune to ReDoS attacks
|
|
88
103
|
- **Batch/streaming validation** - NDJSON log processing, data pipelines (2.6x faster)
|
|
89
104
|
- **Standard Schema V1** - native support for Fastify v5, tRPC, TanStack
|
|
@@ -91,12 +106,12 @@ Ultra-fast JSON Schema validator powered by [simdjson](https://github.com/simdjs
|
|
|
91
106
|
|
|
92
107
|
## When to use ajv
|
|
93
108
|
|
|
94
|
-
- **100% spec compliance needed** - ajv covers more edge cases (ata:
|
|
95
|
-
- **`$dynamicRef
|
|
109
|
+
- **100% spec compliance needed** - ajv covers more edge cases (ata: 96.9%)
|
|
110
|
+
- **`$dynamicRef`** - not yet supported in ata
|
|
96
111
|
|
|
97
112
|
## Features
|
|
98
113
|
|
|
99
|
-
- **Hybrid validator**:
|
|
114
|
+
- **Hybrid validator**: 6.8x faster than ajv valid, 6.0x faster invalid on complex schemas - jsFn boolean guard for valid path (zero allocation), combined codegen with pre-allocated errors for invalid path. Schema compilation cache for repeated schemas
|
|
100
115
|
- **Cross-schema `$ref`**: `schemas` option and `addSchema()` API. Compile-time resolution with `$id` registry, zero runtime overhead
|
|
101
116
|
- **Draft 7 support**: Auto-detects `$schema` field, normalizes `dependencies`/`additionalItems`/`definitions` transparently
|
|
102
117
|
- **Multi-core**: Parallel validation across all CPU cores - 13.4M validations/sec
|
|
@@ -107,7 +122,7 @@ Ultra-fast JSON Schema validator powered by [simdjson](https://github.com/simdjs
|
|
|
107
122
|
- **Zero-copy paths**: Buffer and pre-padded input support - no unnecessary copies
|
|
108
123
|
- **Defaults + coercion**: `default` values, `coerceTypes`, `removeAdditional` support
|
|
109
124
|
- **C/C++ library**: Native API for non-Node.js environments
|
|
110
|
-
- **
|
|
125
|
+
- **96.9% spec compliant**: Draft 2020-12
|
|
111
126
|
|
|
112
127
|
## Installation
|
|
113
128
|
|
|
@@ -256,8 +271,8 @@ auto result = ata::validate(schema, R"({"name": "Mert"})");
|
|
|
256
271
|
| Type | `type` |
|
|
257
272
|
| Numeric | `minimum`, `maximum`, `exclusiveMinimum`, `exclusiveMaximum`, `multipleOf` |
|
|
258
273
|
| String | `minLength`, `maxLength`, `pattern`, `format` |
|
|
259
|
-
| Array | `items`, `prefixItems`, `minItems`, `maxItems`, `uniqueItems`, `contains`, `minContains`, `maxContains` |
|
|
260
|
-
| Object | `properties`, `required`, `additionalProperties`, `patternProperties`, `minProperties`, `maxProperties`, `propertyNames`, `dependentRequired`, `dependentSchemas` |
|
|
274
|
+
| Array | `items`, `prefixItems`, `minItems`, `maxItems`, `uniqueItems`, `contains`, `minContains`, `maxContains`, `unevaluatedItems` |
|
|
275
|
+
| Object | `properties`, `required`, `additionalProperties`, `patternProperties`, `minProperties`, `maxProperties`, `propertyNames`, `dependentRequired`, `dependentSchemas`, `unevaluatedProperties` |
|
|
261
276
|
| Enum/Const | `enum`, `const` |
|
|
262
277
|
| Composition | `allOf`, `anyOf`, `oneOf`, `not` |
|
|
263
278
|
| Conditional | `if`, `then`, `else` |
|
package/binding/ata_napi.cpp
CHANGED
|
@@ -1040,24 +1040,31 @@ static ThreadPool& pool() {
|
|
|
1040
1040
|
}
|
|
1041
1041
|
|
|
1042
1042
|
// --- Fast Validation Registry ---
|
|
1043
|
-
// Global schema slots for
|
|
1043
|
+
// Global schema slots for pre-compiled validation (bypasses per-call compilation)
|
|
1044
1044
|
static constexpr size_t MAX_FAST_SLOTS = 4096;
|
|
1045
1045
|
static ata::schema_ref g_fast_schemas[MAX_FAST_SLOTS];
|
|
1046
1046
|
static std::string g_fast_schema_jsons[MAX_FAST_SLOTS];
|
|
1047
1047
|
static uint32_t g_fast_slot_count = 0;
|
|
1048
1048
|
|
|
1049
|
-
// Register a compiled schema in a fast slot, returns slot ID
|
|
1049
|
+
// Register a compiled schema in a fast slot, returns slot ID.
|
|
1050
|
+
// Deduplicates: same schema JSON string returns existing slot.
|
|
1050
1051
|
Napi::Value FastRegister(const Napi::CallbackInfo& info) {
|
|
1051
1052
|
Napi::Env env = info.Env();
|
|
1052
1053
|
if (info.Length() < 1 || !info[0].IsString()) {
|
|
1053
1054
|
Napi::TypeError::New(env, "Schema JSON string expected").ThrowAsJavaScriptException();
|
|
1054
1055
|
return env.Undefined();
|
|
1055
1056
|
}
|
|
1057
|
+
std::string schema_json = info[0].As<Napi::String>().Utf8Value();
|
|
1058
|
+
// Deduplicate: return existing slot for identical schema JSON
|
|
1059
|
+
for (uint32_t i = 0; i < g_fast_slot_count; i++) {
|
|
1060
|
+
if (g_fast_schema_jsons[i] == schema_json) {
|
|
1061
|
+
return Napi::Number::New(env, i);
|
|
1062
|
+
}
|
|
1063
|
+
}
|
|
1056
1064
|
if (g_fast_slot_count >= MAX_FAST_SLOTS) {
|
|
1057
1065
|
Napi::Error::New(env, "Max fast schema slots reached").ThrowAsJavaScriptException();
|
|
1058
1066
|
return env.Undefined();
|
|
1059
1067
|
}
|
|
1060
|
-
std::string schema_json = info[0].As<Napi::String>().Utf8Value();
|
|
1061
1068
|
auto schema = ata::compile(schema_json);
|
|
1062
1069
|
if (!schema) {
|
|
1063
1070
|
Napi::Error::New(env, "Failed to compile schema").ThrowAsJavaScriptException();
|
|
@@ -1069,12 +1076,11 @@ Napi::Value FastRegister(const Napi::CallbackInfo& info) {
|
|
|
1069
1076
|
return Napi::Number::New(env, slot);
|
|
1070
1077
|
}
|
|
1071
1078
|
|
|
1072
|
-
// Fast validation: slot +
|
|
1079
|
+
// Fast validation: slot + raw buffer → bool
|
|
1080
|
+
// Routes through is_valid_buf → is_valid_prepadded → On-Demand od_plan fast path
|
|
1073
1081
|
static bool FastValidateImpl(uint32_t slot, const uint8_t* data, size_t length) {
|
|
1074
1082
|
if (slot >= g_fast_slot_count) return false;
|
|
1075
|
-
|
|
1076
|
-
std::string_view(reinterpret_cast<const char*>(data), length));
|
|
1077
|
-
return result.valid;
|
|
1083
|
+
return ata::is_valid_buf(g_fast_schemas[slot], data, length);
|
|
1078
1084
|
}
|
|
1079
1085
|
|
|
1080
1086
|
// Zero-copy validation with pre-padded buffer
|
package/compat.js
CHANGED
|
@@ -10,15 +10,7 @@ class Ata {
|
|
|
10
10
|
const v = new Validator(schema);
|
|
11
11
|
const validate = (data) => {
|
|
12
12
|
const result = v.validate(data);
|
|
13
|
-
validate.errors = result.valid
|
|
14
|
-
? null
|
|
15
|
-
: result.errors.map((e) => ({
|
|
16
|
-
instancePath: e.path ? "/" + e.path.replace(/\//g, "/") : "",
|
|
17
|
-
schemaPath: "",
|
|
18
|
-
keyword: "",
|
|
19
|
-
params: {},
|
|
20
|
-
message: e.message,
|
|
21
|
-
}));
|
|
13
|
+
validate.errors = result.valid ? null : result.errors;
|
|
22
14
|
return result.valid;
|
|
23
15
|
};
|
|
24
16
|
validate.errors = null;
|
package/include/ata.h
CHANGED
|
@@ -91,6 +91,10 @@ validation_result validate(std::string_view schema_json,
|
|
|
91
91
|
// Use this when you only need true/false and can provide pre-padded input.
|
|
92
92
|
bool is_valid_prepadded(const schema_ref& schema, const char* data, size_t length);
|
|
93
93
|
|
|
94
|
+
// Validate raw buffer — handles padding internally via thread-local copy.
|
|
95
|
+
// Use this when input doesn't have simdjson padding (e.g., from V8 TypedArray).
|
|
96
|
+
bool is_valid_buf(const schema_ref& schema, const uint8_t* data, size_t length);
|
|
97
|
+
|
|
94
98
|
// Required padding size for is_valid_prepadded
|
|
95
99
|
inline constexpr size_t REQUIRED_PADDING = 64;
|
|
96
100
|
|
package/index.d.ts
CHANGED
|
@@ -1,6 +1,8 @@
|
|
|
1
1
|
export interface ValidationError {
|
|
2
|
-
|
|
3
|
-
|
|
2
|
+
keyword: string;
|
|
3
|
+
instancePath: string;
|
|
4
|
+
schemaPath: string;
|
|
5
|
+
params: Record<string, unknown>;
|
|
4
6
|
message: string;
|
|
5
7
|
}
|
|
6
8
|
|
|
@@ -43,8 +45,14 @@ export class Validator {
|
|
|
43
45
|
/** Fast boolean check for JSON string */
|
|
44
46
|
isValidJSON(jsonString: string): boolean;
|
|
45
47
|
|
|
46
|
-
/**
|
|
47
|
-
isValid(input: Buffer | Uint8Array): boolean;
|
|
48
|
+
/** Ultra-fast buffer validation via V8 CFunction — zero NAPI overhead */
|
|
49
|
+
isValid(input: Buffer | Uint8Array | string): boolean;
|
|
50
|
+
|
|
51
|
+
/** Count valid documents in an NDJSON buffer */
|
|
52
|
+
countValid(ndjsonBuf: Buffer | Uint8Array | string): number;
|
|
53
|
+
|
|
54
|
+
/** Count valid documents from an array of buffers */
|
|
55
|
+
batchIsValid(buffers: (Buffer | Uint8Array)[]): number;
|
|
48
56
|
|
|
49
57
|
/** Zero-copy validation with pre-padded buffer */
|
|
50
58
|
isValidPrepadded(paddedBuffer: Buffer, jsonLength: number): boolean;
|
package/index.js
CHANGED
|
@@ -1,4 +1,7 @@
|
|
|
1
|
-
|
|
1
|
+
// Native addon: optional. Core validate() uses JS codegen and works without it.
|
|
2
|
+
// Buffer APIs (isValid, countValid, isValidParallel) require native.
|
|
3
|
+
let native;
|
|
4
|
+
try { native = require("node-gyp-build")(__dirname); } catch {}
|
|
2
5
|
const {
|
|
3
6
|
compileToJS,
|
|
4
7
|
compileToJSCodegen,
|
|
@@ -346,6 +349,21 @@ class Validator {
|
|
|
346
349
|
this._ensureCompiled();
|
|
347
350
|
return this.isValidJSON(jsonStr);
|
|
348
351
|
};
|
|
352
|
+
this.isValid = (buf) => {
|
|
353
|
+
if (!native) throw new Error('Native addon required for isValid() — use validate() or isValidObject() instead');
|
|
354
|
+
this._ensureCompiled();
|
|
355
|
+
return this.isValid(buf);
|
|
356
|
+
};
|
|
357
|
+
this.countValid = (ndjsonBuf) => {
|
|
358
|
+
if (!native) throw new Error('Native addon required for countValid()');
|
|
359
|
+
this._ensureCompiled();
|
|
360
|
+
return this.countValid(ndjsonBuf);
|
|
361
|
+
};
|
|
362
|
+
this.batchIsValid = (buffers) => {
|
|
363
|
+
if (!native) throw new Error('Native addon required for batchIsValid()');
|
|
364
|
+
this._ensureCompiled();
|
|
365
|
+
return this.batchIsValid(buffers);
|
|
366
|
+
};
|
|
349
367
|
|
|
350
368
|
// ~standard uses self.validate() -- works with lazy because it goes through
|
|
351
369
|
// the instance property which gets swapped after compilation
|
|
@@ -362,7 +380,7 @@ class Validator {
|
|
|
362
380
|
return {
|
|
363
381
|
issues: result.errors.map((err) => ({
|
|
364
382
|
message: err.message,
|
|
365
|
-
path: parsePointerPath(err.
|
|
383
|
+
path: parsePointerPath(err.instancePath),
|
|
366
384
|
})),
|
|
367
385
|
};
|
|
368
386
|
},
|
|
@@ -443,12 +461,16 @@ class Validator {
|
|
|
443
461
|
} catch {}
|
|
444
462
|
}
|
|
445
463
|
// errFn: use JS codegen if safe, else lazy-native fallback
|
|
464
|
+
// For unevaluated schemas without errFn, use jsFn as boolean-only fallback
|
|
465
|
+
const hasUnevaluated = schemaObj && JSON.stringify(schemaObj).includes('unevaluatedProperties') || JSON.stringify(schemaObj).includes('unevaluatedItems')
|
|
446
466
|
const errFn =
|
|
447
467
|
safeErrFn ||
|
|
448
|
-
(
|
|
449
|
-
|
|
450
|
-
|
|
451
|
-
|
|
468
|
+
(hasUnevaluated
|
|
469
|
+
? (d) => ({ valid: jsFn(d), errors: jsFn(d) ? [] : [{ code: 'unevaluated', path: '', message: 'unevaluated property or item' }] })
|
|
470
|
+
: (d) => {
|
|
471
|
+
this._ensureNative();
|
|
472
|
+
return this._compiled.validate(d);
|
|
473
|
+
});
|
|
452
474
|
|
|
453
475
|
// Best path: combined validator (single pass, validates + collects errors)
|
|
454
476
|
// Valid data: returns VALID_RESULT, no allocation
|
|
@@ -517,7 +539,7 @@ class Validator {
|
|
|
517
539
|
const jsonValidateFn = safeCombinedFn
|
|
518
540
|
|| hybridFn
|
|
519
541
|
|| ((obj) => (jsFn(obj) ? VALID_RESULT : errFn(obj)));
|
|
520
|
-
this.validateJSON = useSimdjsonForLarge
|
|
542
|
+
this.validateJSON = useSimdjsonForLarge && native
|
|
521
543
|
? (jsonStr) => {
|
|
522
544
|
if (jsonStr.length >= SIMDJSON_THRESHOLD) {
|
|
523
545
|
this._ensureNative();
|
|
@@ -539,11 +561,12 @@ class Validator {
|
|
|
539
561
|
return jsonValidateFn(JSON.parse(jsonStr));
|
|
540
562
|
} catch (e) {
|
|
541
563
|
if (!(e instanceof SyntaxError)) throw e;
|
|
564
|
+
if (!native) return { valid: false, errors: [{ keyword: 'syntax', instancePath: '', schemaPath: '#', params: {}, message: e.message }] };
|
|
542
565
|
}
|
|
543
566
|
this._ensureNative();
|
|
544
567
|
return this._compiled.validateJSON(jsonStr);
|
|
545
568
|
};
|
|
546
|
-
this.isValidJSON = useSimdjsonForLarge
|
|
569
|
+
this.isValidJSON = useSimdjsonForLarge && native
|
|
547
570
|
? (jsonStr) => {
|
|
548
571
|
if (jsonStr.length >= SIMDJSON_THRESHOLD) {
|
|
549
572
|
this._ensureNative();
|
|
@@ -567,7 +590,30 @@ class Validator {
|
|
|
567
590
|
return false;
|
|
568
591
|
}
|
|
569
592
|
};
|
|
570
|
-
|
|
593
|
+
// Buffer APIs: lazy native init — only compile native schema on first buffer call.
|
|
594
|
+
// This keeps cold start fast (JS codegen only) for users who only use validate().
|
|
595
|
+
if (native) {
|
|
596
|
+
const self = this;
|
|
597
|
+
this.isValid = (buf) => {
|
|
598
|
+
self._ensureNative();
|
|
599
|
+
const slot = self._fastSlot;
|
|
600
|
+
self.isValid = (b) => { if (typeof b === 'string') b = Buffer.from(b); return native.rawFastValidate(slot, b); };
|
|
601
|
+
return self.isValid(buf);
|
|
602
|
+
};
|
|
603
|
+
this.countValid = (ndjsonBuf) => {
|
|
604
|
+
self._ensureNative();
|
|
605
|
+
const slot = self._fastSlot;
|
|
606
|
+
self.countValid = (b) => { if (typeof b === 'string') b = Buffer.from(b); const r = native.rawNDJSONValidate(slot, b); let c = 0; for (let i = 0; i < r.length; i++) if (r[i]) c++; return c; };
|
|
607
|
+
return self.countValid(ndjsonBuf);
|
|
608
|
+
};
|
|
609
|
+
this.batchIsValid = (buffers) => {
|
|
610
|
+
self._ensureNative();
|
|
611
|
+
const slot = self._fastSlot;
|
|
612
|
+
self.batchIsValid = (bufs) => { let v = 0; for (const b of bufs) if (native.rawFastValidate(slot, b)) v++; return v; };
|
|
613
|
+
return self.batchIsValid(buffers);
|
|
614
|
+
};
|
|
615
|
+
}
|
|
616
|
+
} else if (native) {
|
|
571
617
|
// ATA_FORCE_NAPI path: no JS codegen, use native for everything
|
|
572
618
|
this._ensureNative();
|
|
573
619
|
this.validate = preprocess
|
|
@@ -579,12 +625,40 @@ class Validator {
|
|
|
579
625
|
this.isValidObject = (data) => this._compiled.validate(data).valid;
|
|
580
626
|
this.validateJSON = (jsonStr) => this._compiled.validateJSON(jsonStr);
|
|
581
627
|
this.isValidJSON = (jsonStr) => this._compiled.isValidJSON(jsonStr);
|
|
628
|
+
{
|
|
629
|
+
const slot = this._fastSlot;
|
|
630
|
+
this.isValid = (buf) => {
|
|
631
|
+
if (typeof buf === 'string') buf = Buffer.from(buf);
|
|
632
|
+
return native.rawFastValidate(slot, buf);
|
|
633
|
+
};
|
|
634
|
+
}
|
|
635
|
+
{
|
|
636
|
+
const slot = this._fastSlot;
|
|
637
|
+
this.countValid = (ndjsonBuf) => {
|
|
638
|
+
if (typeof ndjsonBuf === 'string') ndjsonBuf = Buffer.from(ndjsonBuf);
|
|
639
|
+
const results = native.rawNDJSONValidate(slot, ndjsonBuf);
|
|
640
|
+
let count = 0;
|
|
641
|
+
for (let i = 0; i < results.length; i++) if (results[i]) count++;
|
|
642
|
+
return count;
|
|
643
|
+
};
|
|
644
|
+
}
|
|
645
|
+
{
|
|
646
|
+
const slot = this._fastSlot;
|
|
647
|
+
this.batchIsValid = (buffers) => {
|
|
648
|
+
let valid = 0;
|
|
649
|
+
for (const buf of buffers) {
|
|
650
|
+
if (native.rawFastValidate(slot, buf)) valid++;
|
|
651
|
+
}
|
|
652
|
+
return valid;
|
|
653
|
+
};
|
|
654
|
+
}
|
|
582
655
|
}
|
|
583
656
|
}
|
|
584
657
|
|
|
585
658
|
_ensureNative() {
|
|
586
659
|
if (this._nativeReady) return;
|
|
587
660
|
this._nativeReady = true;
|
|
661
|
+
if (!native) return;
|
|
588
662
|
let nativeSchemaStr = this._schemaStr;
|
|
589
663
|
if (this._schemaMap.size > 0) {
|
|
590
664
|
const merged = JSON.parse(this._schemaStr);
|
|
@@ -766,7 +840,7 @@ module.exports = { boolFn, hybridFactory, errFn };
|
|
|
766
840
|
return {
|
|
767
841
|
issues: result.errors.map((e) => ({
|
|
768
842
|
message: e.message,
|
|
769
|
-
path: parsePointerPath(e.
|
|
843
|
+
path: parsePointerPath(e.instancePath),
|
|
770
844
|
})),
|
|
771
845
|
};
|
|
772
846
|
},
|
|
@@ -781,43 +855,54 @@ module.exports = { boolFn, hybridFactory, errFn };
|
|
|
781
855
|
|
|
782
856
|
// Raw NAPI fast path for Buffer/Uint8Array
|
|
783
857
|
isValid(input) {
|
|
858
|
+
if (!native) throw new Error('Native addon required for isValid() — install build tools or use validate() instead');
|
|
784
859
|
this._ensureNative();
|
|
785
860
|
return native.rawFastValidate(this._fastSlot, input);
|
|
786
861
|
}
|
|
787
862
|
|
|
788
863
|
// Zero-copy pre-padded path
|
|
789
864
|
isValidPrepadded(paddedBuffer, jsonLength) {
|
|
865
|
+
if (!native) throw new Error('Native addon required for isValidPrepadded()');
|
|
790
866
|
this._ensureNative();
|
|
791
867
|
return native.rawFastValidate(this._fastSlot, paddedBuffer, jsonLength);
|
|
792
868
|
}
|
|
793
869
|
|
|
794
870
|
// Parallel NDJSON batch (multi-core)
|
|
795
871
|
isValidParallel(buffer) {
|
|
872
|
+
if (!native) throw new Error('Native addon required for isValidParallel()');
|
|
796
873
|
this._ensureNative();
|
|
797
874
|
return native.rawParallelValidate(this._fastSlot, buffer);
|
|
798
875
|
}
|
|
799
876
|
|
|
800
877
|
// Parallel count (fastest -- single uint32 return)
|
|
801
878
|
countValid(buffer) {
|
|
879
|
+
if (!native) throw new Error('Native addon required for countValid()');
|
|
802
880
|
this._ensureNative();
|
|
803
881
|
return native.rawParallelCount(this._fastSlot, buffer);
|
|
804
882
|
}
|
|
805
883
|
|
|
806
884
|
// NDJSON single-thread batch
|
|
807
885
|
isValidNDJSON(buffer) {
|
|
886
|
+
if (!native) throw new Error('Native addon required for isValidNDJSON()');
|
|
808
887
|
this._ensureNative();
|
|
809
888
|
return native.rawNDJSONValidate(this._fastSlot, buffer);
|
|
810
889
|
}
|
|
811
890
|
}
|
|
812
891
|
|
|
813
892
|
function validate(schema, data) {
|
|
814
|
-
|
|
815
|
-
|
|
816
|
-
|
|
893
|
+
if (native) {
|
|
894
|
+
const schemaStr =
|
|
895
|
+
typeof schema === "string" ? schema : JSON.stringify(schema);
|
|
896
|
+
return native.validate(schemaStr, data);
|
|
897
|
+
}
|
|
898
|
+
// JS fallback: compile and validate
|
|
899
|
+
const v = new Validator(typeof schema === "string" ? JSON.parse(schema) : schema);
|
|
900
|
+
return v.validate(data);
|
|
817
901
|
}
|
|
818
902
|
|
|
819
903
|
function version() {
|
|
820
|
-
return native.version();
|
|
904
|
+
if (native) return native.version();
|
|
905
|
+
return require("./package.json").version;
|
|
821
906
|
}
|
|
822
907
|
|
|
823
908
|
// Bundle multiple validators into a single JS file for fast startup.
|