ata-validator 0.7.3 → 0.9.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/CMakeLists.txt CHANGED
@@ -19,43 +19,48 @@ endif()
19
19
  option(ATA_TESTING "Build test suite" ON)
20
20
  option(ATA_BENCHMARKS "Build benchmarks" OFF)
21
21
  option(ATA_SANITIZE "Enable address sanitizer" OFF)
22
+ option(ATA_NO_RE2 "Build without RE2 regex engine (disables pattern keyword)" OFF)
22
23
 
23
24
  # Fetch simdjson
24
25
  include(FetchContent)
25
26
  FetchContent_Declare(
26
27
  simdjson
27
28
  GIT_REPOSITORY https://github.com/simdjson/simdjson.git
28
- GIT_TAG v3.12.2
29
+ GIT_TAG v4.5.0
29
30
  GIT_SHALLOW TRUE
30
31
  )
31
32
 
32
- # RE2 — fast regex engine (replaces std::regex)
33
- set(RE2_BUILD_TESTING OFF CACHE BOOL "" FORCE)
34
- FetchContent_Declare(
35
- re2
36
- GIT_REPOSITORY https://github.com/google/re2.git
37
- GIT_TAG 2024-07-02
38
- GIT_SHALLOW TRUE
39
- EXCLUDE_FROM_ALL
40
- )
33
+ if(NOT ATA_NO_RE2)
34
+ # RE2 fast regex engine (replaces std::regex)
35
+ set(RE2_BUILD_TESTING OFF CACHE BOOL "" FORCE)
36
+ FetchContent_Declare(
37
+ re2
38
+ GIT_REPOSITORY https://github.com/google/re2.git
39
+ GIT_TAG 2024-07-02
40
+ GIT_SHALLOW TRUE
41
+ EXCLUDE_FROM_ALL
42
+ )
41
43
 
42
- # Abseil — required by RE2
43
- set(ABSL_PROPAGATE_CXX_STD ON CACHE BOOL "" FORCE)
44
- set(ABSL_BUILD_TESTING OFF CACHE BOOL "" FORCE)
45
- set(ABSL_ENABLE_INSTALL ON CACHE BOOL "" FORCE)
46
- FetchContent_Declare(
47
- abseil-cpp
48
- GIT_REPOSITORY https://github.com/abseil/abseil-cpp.git
49
- GIT_TAG 20240722.0
50
- GIT_SHALLOW TRUE
51
- )
44
+ # Abseil — required by RE2
45
+ set(ABSL_PROPAGATE_CXX_STD ON CACHE BOOL "" FORCE)
46
+ set(ABSL_BUILD_TESTING OFF CACHE BOOL "" FORCE)
47
+ set(ABSL_ENABLE_INSTALL ON CACHE BOOL "" FORCE)
48
+ FetchContent_Declare(
49
+ abseil-cpp
50
+ GIT_REPOSITORY https://github.com/abseil/abseil-cpp.git
51
+ GIT_TAG 20240722.0
52
+ GIT_SHALLOW TRUE
53
+ )
52
54
 
53
- FetchContent_MakeAvailable(abseil-cpp simdjson re2)
55
+ FetchContent_MakeAvailable(abseil-cpp simdjson re2)
56
+ else()
57
+ FetchContent_MakeAvailable(simdjson)
58
+ endif()
54
59
 
55
60
  if (CMAKE_JS_VERSION)
56
61
  # add_definitions(-DNAPI_VERSION=10)
57
62
  include_directories(${CMAKE_JS_INC})
58
- file(GLOB SOURCE_FILES "binding/*.cpp" "src/*.cpp" "deps/simdjson/*.cpp")
63
+ file(GLOB SOURCE_FILES "binding/*.cpp" "src/*.cpp")
59
64
  else()
60
65
  file(GLOB SOURCE_FILES "src/*.cpp")
61
66
  endif()
@@ -88,7 +93,12 @@ else()
88
93
  endif()
89
94
 
90
95
  target_include_directories(${PROJECT_NAME} PUBLIC include)
91
- target_link_libraries(${PROJECT_NAME} PRIVATE simdjson re2)
96
+ if(ATA_NO_RE2)
97
+ target_link_libraries(${PROJECT_NAME} PRIVATE simdjson)
98
+ target_compile_definitions(${PROJECT_NAME} PRIVATE ATA_NO_RE2)
99
+ else()
100
+ target_link_libraries(${PROJECT_NAME} PRIVATE simdjson re2)
101
+ endif()
92
102
 
93
103
  if (CMAKE_JS_VERSION)
94
104
  target_include_directories(${PROJECT_NAME} PRIVATE ${CMAKE_JS_INC})
@@ -130,3 +140,11 @@ if(ATA_BENCHMARKS)
130
140
  add_executable(ata_bench benchmark/bench.cpp)
131
141
  target_link_libraries(ata_bench PRIVATE ata simdjson)
132
142
  endif()
143
+
144
+ # Fuzz targets (for OSS-Fuzz / libFuzzer)
145
+ if(ATA_FUZZING)
146
+ foreach(fuzzer compile_fuzzer validate_fuzzer roundtrip_fuzzer)
147
+ add_executable(${fuzzer} fuzz/${fuzzer}.cpp)
148
+ target_link_libraries(${fuzzer} PRIVATE ata simdjson ${LIB_FUZZING_ENGINE})
149
+ endforeach()
150
+ endif()
package/README.md CHANGED
@@ -13,7 +13,7 @@ Ultra-fast JSON Schema validator powered by [simdjson](https://github.com/simdjs
13
13
  | **validate(obj)** valid | 22ns | 102ns | **ata 4.6x faster** |
14
14
  | **validate(obj)** invalid | 87ns | 182ns | **ata 2.1x faster** |
15
15
  | **isValidObject(obj)** | 21ns | 100ns | **ata 4.7x faster** |
16
- | **Schema compilation** | 695ns | 1.30ms | **ata 1,867x faster** |
16
+ | **Schema compilation** | 453ns | 1.24ms | **ata 2,729x faster** |
17
17
  | **First validation** | 2.07μs | 1.11ms | **ata 534x faster** |
18
18
 
19
19
  ### Complex Schema (patternProperties + dependentSchemas + propertyNames + additionalProperties)
@@ -54,7 +54,7 @@ Three-tier hybrid codegen: static schemas compile to zero-overhead key checks, d
54
54
  |---|---|---|---|---|---|
55
55
  | **validate (valid)** | **9ns** | 38ns | 50ns | 334ns | 326ns |
56
56
  | **validate (invalid)** | **37ns** | 103ns | 4ns | 11.8μs | 842ns |
57
- | **compilation** | **584ns** | 1.20ms | 52μs | — | — |
57
+ | **compilation** | **453ns** | 1.24ms | 52μs | — | — |
58
58
  | **first validation** | **2.1μs** | 1.11ms | 54μs | — | — |
59
59
 
60
60
  > Different categories: ata/ajv/typebox are JSON Schema validators, zod/valibot are schema-builder DSLs. [Benchmark code](benchmark/bench_all_mitata.mjs)
@@ -71,7 +71,7 @@ Three-tier hybrid codegen: static schemas compile to zero-overhead key checks, d
71
71
 
72
72
  | Scenario | ata | ajv | |
73
73
  |---|---|---|---|
74
- | **Serverless cold start** (50 schemas) | 0.1ms | 23ms | **ata 230x faster** |
74
+ | **Serverless cold start** (50 schemas) | 0.087ms | 3.67ms | **ata 42x faster** |
75
75
  | **ReDoS protection** (`^(a+)+$`) | 0.3ms | 765ms | **ata immune (RE2)** |
76
76
  | **Batch NDJSON** (10K items, multi-core) | 13.4M/sec | 5.1M/sec | **ata 2.6x faster** |
77
77
  | **Fastify startup** (5 routes) | 0.5ms | 6.0ms | **ata 12x faster** |
@@ -88,14 +88,26 @@ Three-tier hybrid codegen: static schemas compile to zero-overhead key checks, d
88
88
 
89
89
  **Adaptive simdjson**: For large documents (>8KB) with selective schemas, simdjson On Demand seeks only the needed fields - skipping irrelevant data at GB/s speeds.
90
90
 
91
+ ### $dynamicRef / $dynamicAnchor / $anchor
92
+
93
+ | Scenario | ata | ajv | |
94
+ |---|---|---|---|
95
+ | **$dynamicRef tree** valid | 22ns | 54ns | **ata 2.4x faster** |
96
+ | **$dynamicRef tree** invalid | 70ns | 76ns | **ata 1.1x faster** |
97
+ | **$dynamicRef override** valid | 2.6ns | 183ns | **ata 70x faster** |
98
+ | **$dynamicRef override** invalid | 48ns | 185ns | **ata 3.8x faster** |
99
+ | **$anchor array** valid | 2.3ns | 3.1ns | **ata 1.4x faster** |
100
+
101
+ Self-recursive named functions for $dynamicRef, compile-time cross-schema resolution, zero-wrapper hybrid path. [Benchmark code](benchmark/bench_dynamicref_vs_ajv.mjs)
102
+
91
103
  ### JSON Schema Test Suite
92
104
 
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).
105
+ **95.3%** pass rate (1170/1227) on official [JSON Schema Test Suite](https://github.com/json-schema-org/JSON-Schema-Test-Suite) (Draft 2020-12). **95.3%** on [@exodus/schemasafe](https://github.com/ExodusMovement/schemasafe) test suite.
94
106
 
95
107
  ## When to use ata
96
108
 
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)
109
+ - **High-throughput `validate(obj)`** - 3.1x faster than ajv, 38x faster than zod
110
+ - **Complex schemas** - `patternProperties`, `dependentSchemas`, `propertyNames`, `unevaluatedProperties` all inline JS codegen
99
111
  - **Multi-schema projects** - cross-schema `$ref` with `$id` registry, `addSchema()` API
100
112
  - **Draft 7 migration** - auto-detects `$schema`, normalizes Draft 7 keywords transparently
101
113
  - **Serverless / cold starts** - 6,904x faster compilation, 5,148x faster first validation
@@ -106,12 +118,13 @@ Three-tier hybrid codegen: static schemas compile to zero-overhead key checks, d
106
118
 
107
119
  ## When to use ajv
108
120
 
109
- - **100% spec compliance needed** - ajv covers more edge cases (ata: 96.9%)
110
- - **`$dynamicRef`** - not yet supported in ata
121
+ - **Existing ajv ecosystem** - plugins, custom keywords, large community
122
+ - **Full unevaluatedProperties/Items** - ata covers most cases but some edge cases remain
111
123
 
112
124
  ## Features
113
125
 
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
126
+ - **Hybrid validator**: 4.1x faster than ajv, up to 70x faster on $dynamicRef - zero-wrapper hybrid path for valid data (no allocation), combined codegen for error collection. Schema compilation cache for repeated schemas
127
+ - **$dynamicRef / $dynamicAnchor / $anchor**: Full Draft 2020-12 dynamic reference support. Self-recursive named functions, compile-time cross-schema resolution (42/42 spec tests)
115
128
  - **Cross-schema `$ref`**: `schemas` option and `addSchema()` API. Compile-time resolution with `$id` registry, zero runtime overhead
116
129
  - **Draft 7 support**: Auto-detects `$schema` field, normalizes `dependencies`/`additionalItems`/`definitions` transparently
117
130
  - **Multi-core**: Parallel validation across all CPU cores - 13.4M validations/sec
@@ -9,12 +9,15 @@
9
9
  #include <functional>
10
10
  #include <queue>
11
11
  #include <atomic>
12
+ #ifndef ATA_NO_RE2
12
13
  #include <re2/re2.h>
14
+ #endif
13
15
  #include <set>
14
16
  #include <string>
15
17
  #include <vector>
16
18
 
17
19
  #include "ata.h"
20
+ #include <simdjson.h>
18
21
 
19
22
  // ============================================================================
20
23
  // V8 Direct Object Traversal Engine
@@ -39,7 +42,9 @@ struct schema_node {
39
42
  std::optional<uint64_t> min_length;
40
43
  std::optional<uint64_t> max_length;
41
44
  std::optional<std::string> pattern;
45
+ #ifndef ATA_NO_RE2
42
46
  std::shared_ptr<re2::RE2> compiled_pattern;
47
+ #endif
43
48
 
44
49
  std::optional<uint64_t> min_items;
45
50
  std::optional<uint64_t> max_items;
@@ -63,7 +68,9 @@ struct schema_node {
63
68
  struct pattern_prop {
64
69
  std::string pattern;
65
70
  schema_node_ptr schema;
71
+ #ifndef ATA_NO_RE2
66
72
  std::shared_ptr<re2::RE2> compiled;
73
+ #endif
67
74
  };
68
75
  std::vector<pattern_prop> pattern_properties;
69
76
 
@@ -83,6 +90,8 @@ struct schema_node {
83
90
  schema_node_ptr else_schema;
84
91
 
85
92
  std::string ref;
93
+ std::string dynamic_ref;
94
+ std::string id;
86
95
 
87
96
  std::unordered_map<std::string, schema_node_ptr> defs;
88
97
 
@@ -114,10 +123,14 @@ static bool napi_check_format(const std::string& sv, const std::string& fmt) {
114
123
  (sv.size() - dot - 1) >= 2;
115
124
  }
116
125
  if (fmt == "date") {
117
- return sv.size() == 10 && nb_is_digit(sv[0]) && nb_is_digit(sv[1]) &&
118
- nb_is_digit(sv[2]) && nb_is_digit(sv[3]) && sv[4] == '-' &&
119
- nb_is_digit(sv[5]) && nb_is_digit(sv[6]) && sv[7] == '-' &&
120
- nb_is_digit(sv[8]) && nb_is_digit(sv[9]);
126
+ if (sv.size() != 10 || !nb_is_digit(sv[0]) || !nb_is_digit(sv[1]) ||
127
+ !nb_is_digit(sv[2]) || !nb_is_digit(sv[3]) || sv[4] != '-' ||
128
+ !nb_is_digit(sv[5]) || !nb_is_digit(sv[6]) || sv[7] != '-' ||
129
+ !nb_is_digit(sv[8]) || !nb_is_digit(sv[9]))
130
+ return false;
131
+ int month = (sv[5] - '0') * 10 + (sv[6] - '0');
132
+ int day = (sv[8] - '0') * 10 + (sv[9] - '0');
133
+ return month >= 1 && month <= 12 && day >= 1 && day <= 31;
121
134
  }
122
135
  if (fmt == "time") {
123
136
  if (sv.size() < 8) return false;
@@ -286,6 +299,15 @@ static void validate_napi(const schema_node_ptr& node,
286
299
  const compiled_schema_internal& ctx,
287
300
  std::vector<ata::validation_error>& errors);
288
301
 
302
+ // Recursion depth guard — prevents stack overflow on self-referencing schemas
303
+ struct NapiDepthGuard {
304
+ static thread_local int depth;
305
+ bool overflow;
306
+ NapiDepthGuard() : overflow(++depth > 100) {}
307
+ ~NapiDepthGuard() { --depth; }
308
+ };
309
+ thread_local int NapiDepthGuard::depth = 0;
310
+
289
311
  static void validate_napi(const schema_node_ptr& node,
290
312
  Napi::Value value,
291
313
  Napi::Env env,
@@ -294,6 +316,9 @@ static void validate_napi(const schema_node_ptr& node,
294
316
  std::vector<ata::validation_error>& errors) {
295
317
  if (!node) return;
296
318
 
319
+ NapiDepthGuard guard;
320
+ if (guard.overflow) return;
321
+
297
322
  // Boolean schema
298
323
  if (node->boolean_schema.has_value()) {
299
324
  if (!node->boolean_schema.value()) {
@@ -516,6 +541,7 @@ static void validate_napi(const schema_node_ptr& node,
516
541
  " > maxLength " +
517
542
  std::to_string(node->max_length.value())});
518
543
  }
544
+ #ifndef ATA_NO_RE2
519
545
  if (node->compiled_pattern) {
520
546
  if (!re2::RE2::PartialMatch(sv, *node->compiled_pattern)) {
521
547
  errors.push_back({ata::error_code::pattern_mismatch, path,
@@ -523,6 +549,7 @@ static void validate_napi(const schema_node_ptr& node,
523
549
  node->pattern.value()});
524
550
  }
525
551
  }
552
+ #endif
526
553
  if (node->format.has_value()) {
527
554
  const auto& fmt = node->format.value();
528
555
  bool format_ok = napi_check_format(sv, fmt);
@@ -644,11 +671,13 @@ static void validate_napi(const schema_node_ptr& node,
644
671
  }
645
672
 
646
673
  for (const auto& pp : node->pattern_properties) {
674
+ #ifndef ATA_NO_RE2
647
675
  if (pp.compiled && re2::RE2::PartialMatch(key_str, *pp.compiled)) {
648
676
  validate_napi(pp.schema, val, env, path + "/" + key_str, ctx,
649
677
  errors);
650
678
  matched = true;
651
679
  }
680
+ #endif
652
681
  }
653
682
 
654
683
  if (!matched) {
@@ -769,6 +798,67 @@ static void validate_napi(const schema_node_ptr& node,
769
798
  // N-API Binding
770
799
  // ============================================================================
771
800
 
801
+ // ============================================================================
802
+ // simdjson DOM to V8 JS Object conversion
803
+ // ============================================================================
804
+
805
+ static Napi::Value dom_to_napi(Napi::Env env, simdjson::dom::element el) {
806
+ using namespace simdjson;
807
+ switch (el.type()) {
808
+ case dom::element_type::OBJECT: {
809
+ auto obj = Napi::Object::New(env);
810
+ for (auto [key, val] : dom::object(el)) {
811
+ obj.Set(std::string(key), dom_to_napi(env, val));
812
+ }
813
+ return obj;
814
+ }
815
+ case dom::element_type::ARRAY: {
816
+ dom::array arr = el;
817
+ auto jsArr = Napi::Array::New(env, arr.size());
818
+ uint32_t i = 0;
819
+ for (auto val : arr) {
820
+ jsArr.Set(i++, dom_to_napi(env, val));
821
+ }
822
+ return jsArr;
823
+ }
824
+ case dom::element_type::STRING: {
825
+ std::string_view sv;
826
+ el.get(sv);
827
+ return Napi::String::New(env, sv.data(), sv.length());
828
+ }
829
+ case dom::element_type::INT64: {
830
+ int64_t v;
831
+ el.get(v);
832
+ return Napi::Number::New(env, static_cast<double>(v));
833
+ }
834
+ case dom::element_type::UINT64: {
835
+ uint64_t v;
836
+ el.get(v);
837
+ return Napi::Number::New(env, static_cast<double>(v));
838
+ }
839
+ case dom::element_type::DOUBLE: {
840
+ double v;
841
+ el.get(v);
842
+ return Napi::Number::New(env, v);
843
+ }
844
+ case dom::element_type::BOOL: {
845
+ bool v;
846
+ el.get(v);
847
+ return Napi::Boolean::New(env, v);
848
+ }
849
+ case dom::element_type::NULL_VALUE:
850
+ return env.Null();
851
+ default:
852
+ return env.Undefined();
853
+ }
854
+ }
855
+
856
+ // Thread-local simdjson DOM parser for parseJSON / validateAndParse
857
+ static simdjson::dom::parser& tl_dom_parser() {
858
+ thread_local simdjson::dom::parser parser;
859
+ return parser;
860
+ }
861
+
772
862
  static Napi::Object make_result(Napi::Env env,
773
863
  const ata::validation_result& result) {
774
864
  Napi::Object obj = Napi::Object::New(env);
@@ -794,7 +884,8 @@ class CompiledSchema : public Napi::ObjectWrap<CompiledSchema> {
794
884
  {InstanceMethod("validate", &CompiledSchema::Validate),
795
885
  InstanceMethod("validateJSON", &CompiledSchema::ValidateJSON),
796
886
  InstanceMethod("validateDirect", &CompiledSchema::ValidateDirect),
797
- InstanceMethod("isValidJSON", &CompiledSchema::IsValidJSON)});
887
+ InstanceMethod("isValidJSON", &CompiledSchema::IsValidJSON),
888
+ InstanceMethod("validateAndParse", &CompiledSchema::ValidateAndParse)});
798
889
  auto* constructor = new Napi::FunctionReference();
799
890
  *constructor = Napi::Persistent(func);
800
891
  env.SetInstanceData(constructor);
@@ -916,6 +1007,77 @@ class CompiledSchema : public Napi::ObjectWrap<CompiledSchema> {
916
1007
  return ValidateDirectImpl(env, info[0]);
917
1008
  }
918
1009
 
1010
+ // Parse JSON with simdjson, validate against schema, return parsed JS object
1011
+ Napi::Value ValidateAndParse(const Napi::CallbackInfo& info) {
1012
+ Napi::Env env = info.Env();
1013
+ if (info.Length() < 1) {
1014
+ Napi::TypeError::New(env, "JSON string or Buffer expected")
1015
+ .ThrowAsJavaScriptException();
1016
+ return env.Undefined();
1017
+ }
1018
+
1019
+ const char* data;
1020
+ size_t len;
1021
+
1022
+ if (info[0].IsBuffer()) {
1023
+ auto buf = info[0].As<Napi::Buffer<char>>();
1024
+ data = buf.Data();
1025
+ len = buf.Length();
1026
+ } else if (info[0].IsString()) {
1027
+ auto [d, l] = extract_string(env, info[0]);
1028
+ data = d;
1029
+ len = l;
1030
+ } else {
1031
+ Napi::TypeError::New(env, "JSON string or Buffer expected")
1032
+ .ThrowAsJavaScriptException();
1033
+ return env.Undefined();
1034
+ }
1035
+
1036
+ // Parse with simdjson
1037
+ simdjson::padded_string padded(data, len);
1038
+ auto& parser = tl_dom_parser();
1039
+ auto doc_result = parser.parse(padded);
1040
+ if (doc_result.error()) {
1041
+ auto obj = Napi::Object::New(env);
1042
+ obj.Set("valid", false);
1043
+ obj.Set("value", env.Null());
1044
+ auto errors = Napi::Array::New(env, 1);
1045
+ auto err = Napi::Object::New(env);
1046
+ err.Set("code", Napi::Number::New(env, static_cast<int>(ata::error_code::invalid_json)));
1047
+ err.Set("path", Napi::String::New(env, ""));
1048
+ err.Set("message", Napi::String::New(env, "Invalid JSON"));
1049
+ errors[0u] = err;
1050
+ obj.Set("errors", errors);
1051
+ return obj;
1052
+ }
1053
+
1054
+ // Validate
1055
+ auto valResult = ata::validate(schema_, std::string_view(data, len));
1056
+
1057
+ // Convert DOM to JS object
1058
+ Napi::Value jsValue = dom_to_napi(env, doc_result.value());
1059
+
1060
+ // Build result
1061
+ auto obj = Napi::Object::New(env);
1062
+ obj.Set("valid", valResult.valid);
1063
+ obj.Set("value", jsValue);
1064
+ if (valResult.valid) {
1065
+ obj.Set("errors", Napi::Array::New(env, 0));
1066
+ } else {
1067
+ Napi::Array errors = Napi::Array::New(env, valResult.errors.size());
1068
+ for (size_t i = 0; i < valResult.errors.size(); ++i) {
1069
+ Napi::Object err = Napi::Object::New(env);
1070
+ err.Set("code",
1071
+ Napi::Number::New(env, static_cast<int>(valResult.errors[i].code)));
1072
+ err.Set("path", Napi::String::New(env, valResult.errors[i].path));
1073
+ err.Set("message", Napi::String::New(env, valResult.errors[i].message));
1074
+ errors[i] = err;
1075
+ }
1076
+ obj.Set("errors", errors);
1077
+ }
1078
+ return obj;
1079
+ }
1080
+
919
1081
  private:
920
1082
  Napi::Value ValidateDirectImpl(Napi::Env env, Napi::Value value) {
921
1083
  compiled_schema_internal ctx;
@@ -968,6 +1130,44 @@ Napi::Value GetVersion(const Napi::CallbackInfo& info) {
968
1130
  return Napi::String::New(info.Env(), std::string(ata::version()));
969
1131
  }
970
1132
 
1133
+ // Standalone JSON parser using simdjson — returns parsed JS object
1134
+ Napi::Value ParseJSON(const Napi::CallbackInfo& info) {
1135
+ Napi::Env env = info.Env();
1136
+ if (info.Length() < 1) {
1137
+ Napi::TypeError::New(env, "JSON string or Buffer expected")
1138
+ .ThrowAsJavaScriptException();
1139
+ return env.Undefined();
1140
+ }
1141
+
1142
+ const char* data;
1143
+ size_t len;
1144
+
1145
+ if (info[0].IsBuffer()) {
1146
+ auto buf = info[0].As<Napi::Buffer<char>>();
1147
+ data = buf.Data();
1148
+ len = buf.Length();
1149
+ } else if (info[0].IsString()) {
1150
+ auto [d, l] = CompiledSchema::extract_string(env, info[0]);
1151
+ data = d;
1152
+ len = l;
1153
+ } else {
1154
+ Napi::TypeError::New(env, "JSON string or Buffer expected")
1155
+ .ThrowAsJavaScriptException();
1156
+ return env.Undefined();
1157
+ }
1158
+
1159
+ // Parse with simdjson using thread-local parser
1160
+ simdjson::padded_string padded(data, len);
1161
+ auto& parser = tl_dom_parser();
1162
+ auto result = parser.parse(padded);
1163
+ if (result.error()) {
1164
+ Napi::Error::New(env, "Invalid JSON").ThrowAsJavaScriptException();
1165
+ return env.Undefined();
1166
+ }
1167
+
1168
+ return dom_to_napi(env, result.value());
1169
+ }
1170
+
971
1171
  // --- Thread Pool ---
972
1172
  class ThreadPool {
973
1173
  public:
@@ -1503,6 +1703,7 @@ Napi::Object Init(Napi::Env env, Napi::Object exports) {
1503
1703
  CompiledSchema::Init(env, exports);
1504
1704
  exports.Set("validate", Napi::Function::New(env, ValidateOneShot));
1505
1705
  exports.Set("version", Napi::Function::New(env, GetVersion));
1706
+ exports.Set("parseJSON", Napi::Function::New(env, ParseJSON));
1506
1707
  exports.Set("fastRegister", Napi::Function::New(env, FastRegister));
1507
1708
  exports.Set("fastValidate", Napi::Function::New(env, FastValidateSlow));
1508
1709
 
package/include/ata.h CHANGED
@@ -8,14 +8,16 @@
8
8
  #include <variant>
9
9
  #include <vector>
10
10
 
11
+ #define ATA_VERSION "0.9.0"
12
+
11
13
  namespace ata {
12
14
 
13
15
  inline constexpr uint32_t VERSION_MAJOR = 0;
14
- inline constexpr uint32_t VERSION_MINOR = 4;
15
- inline constexpr uint32_t VERSION_REVISION = 3;
16
+ inline constexpr uint32_t VERSION_MINOR = 9;
17
+ inline constexpr uint32_t VERSION_REVISION = 0;
16
18
 
17
19
  inline constexpr std::string_view version() noexcept {
18
- return "0.4.3";
20
+ return "0.9.0";
19
21
  }
20
22
 
21
23
  enum class error_code : uint8_t {
@@ -64,8 +66,14 @@ struct validation_result {
64
66
 
65
67
  struct compiled_schema;
66
68
 
69
+ struct schema_warning {
70
+ std::string path;
71
+ std::string message;
72
+ };
73
+
67
74
  struct schema_ref {
68
75
  std::shared_ptr<compiled_schema> impl;
76
+ std::vector<schema_warning> warnings;
69
77
 
70
78
  explicit operator bool() const noexcept { return impl != nullptr; }
71
79
  };