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 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 (5 properties, type + format + range checks)
9
+ ### Simple Schema (7 properties, type + format + range + nested object)
10
10
 
11
11
  | Scenario | ata | ajv | |
12
12
  |---|---|---|---|
13
- | **validate(obj)** valid | 28ns | 104ns | **ata 3.6x faster** |
14
- | **validate(obj)** invalid | 79ns | 108ns | **ata 2.3x faster** |
15
- | **isValidObject(obj)** | 28ns | 102ns | **ata 3.7x faster** |
16
- | **Schema compilation** | 554ns | 1.21ms | **ata 2,184x faster** |
17
- | **First validation** | 1.70μs | 1.18ms | **ata 719x faster** |
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 | 20ns | 121ns | **ata 5.9x faster** |
24
- | **validate(obj)** invalid | 53ns | 196ns | **ata 3.2x faster** |
25
- | **isValidObject(obj)** | 20ns | 128ns | **ata 5.9x faster** |
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)** | **13ns** | 37ns | 48ns | 328ns | 316ns |
41
- | **validate (invalid)** | **35ns** | 104ns | 4ns | 11.7μs | 838ns |
42
- | **compilation** | **533ns** | 1.14ms | 52μs | — | — |
43
- | **first validation** | **1.3μs** | 1.07ms | 53μs | — | — |
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 242x faster** |
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
- **98.4%** pass rate (937/952) on official [JSON Schema Test Suite](https://github.com/json-schema-org/JSON-Schema-Test-Suite) (Draft 2020-12).
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)`** - 5.9x faster than ajv on complex schemas, 27x faster than zod
83
- - **Complex schemas** - `patternProperties`, `dependentSchemas`, `propertyNames` all inline JS codegen (5.9x faster than ajv)
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** - 2,184x faster compilation, 719x faster first validation
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: 98.4%)
95
- - **`$dynamicRef` / `unevaluatedProperties`** - not yet supported in ata
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**: 5.9x faster than ajv valid, 3.2x 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
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
- - **98.4% spec compliant**: Draft 2020-12
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` |
@@ -1040,24 +1040,31 @@ static ThreadPool& pool() {
1040
1040
  }
1041
1041
 
1042
1042
  // --- Fast Validation Registry ---
1043
- // Global schema slots for V8 Fast API (bypasses NAPI overhead)
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 + Uint8Array → bool (called via V8 Fast API)
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
- auto result = ata::validate(g_fast_schemas[slot],
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
- code: string;
3
- path: string;
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
- /** Validate Buffer/Uint8Arrayraw NAPI fast path */
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
- const native = require("node-gyp-build")(__dirname);
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.path),
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
- ((d) => {
449
- this._ensureNative();
450
- return this._compiled.validate(d);
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
- } else {
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.path),
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
- const schemaStr =
815
- typeof schema === "string" ? schema : JSON.stringify(schema);
816
- return native.validate(schemaStr, data);
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.