ts-ip2region2 1.2.0 → 1.2.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/README.md CHANGED
@@ -69,6 +69,35 @@ new Ip2Region(dbPath: string, options?: Ip2RegionOptions)
69
69
  - `Ip2Region.verify(dbPath: string): boolean` - Verify xdb file
70
70
  - `Ip2Region.verifyDetailed(dbPath: string): VerifyResult` - Verify with detailed info
71
71
 
72
+ ## Performance Benchmark
73
+
74
+ Benchmark results on Windows x64 with Node.js v22.14.0 (10,000 iterations):
75
+
76
+ | Cache Strategy | Avg Time (μs/op) | QPS |
77
+ |---------------|------------------|-----|
78
+ | file | ~31 | ~32,000 |
79
+ | vectorIndex | ~22 | ~45,000 |
80
+ | content | ~1.3 | ~750,000 |
81
+
82
+ **Performance Improvements:**
83
+ - vectorIndex is ~40% faster than file mode
84
+ - content is ~95% faster than vectorIndex mode
85
+ - content is ~96% faster than file mode
86
+
87
+ **Comparison with Native C:**
88
+ - Native C (vectorIndex): ~5 μs/op
89
+ - Node.js Addon (vectorIndex): ~22 μs/op
90
+ - Overhead: ~4.4x (mainly from N-API call overhead)
91
+
92
+ Despite the N-API overhead, the performance is still excellent for most use cases, achieving 45,000+ QPS with vectorIndex mode.
93
+
94
+ **Recommendation:** Use `vectorIndex` for most scenarios as it provides excellent performance with moderate memory usage.
95
+
96
+ Run benchmark yourself:
97
+ ```bash
98
+ npm run benchmark
99
+ ```
100
+
72
101
  ## License
73
102
 
74
103
  Apache-2.0
package/addon.cpp CHANGED
@@ -10,6 +10,33 @@ extern "C" {
10
10
  #include "ip2region/xdb_api.h"
11
11
  }
12
12
 
13
+ #ifdef XDB_WINDOWS
14
+ // Windows UTF-8 path support
15
+ extern "C" {
16
+ __declspec(dllimport) int __stdcall MultiByteToWideChar(unsigned int, unsigned long, const char*, int, wchar_t*, int);
17
+ }
18
+ #define CP_UTF8 65001
19
+
20
+ static FILE* fopen_utf8(const char* path, const char* mode) {
21
+ int path_len = MultiByteToWideChar(CP_UTF8, 0, path, -1, nullptr, 0);
22
+ if (path_len == 0) return nullptr;
23
+
24
+ wchar_t* wpath = new wchar_t[path_len];
25
+ MultiByteToWideChar(CP_UTF8, 0, path, -1, wpath, path_len);
26
+
27
+ int mode_len = MultiByteToWideChar(CP_UTF8, 0, mode, -1, nullptr, 0);
28
+ wchar_t* wmode = new wchar_t[mode_len];
29
+ MultiByteToWideChar(CP_UTF8, 0, mode, -1, wmode, mode_len);
30
+
31
+ FILE* file = _wfopen(wpath, wmode);
32
+ delete[] wpath;
33
+ delete[] wmode;
34
+ return file;
35
+ }
36
+ #else
37
+ #define fopen_utf8 fopen
38
+ #endif
39
+
13
40
  class Ip2RegionSearcher : public Napi::ObjectWrap<Ip2RegionSearcher> {
14
41
  public:
15
42
  static Napi::Object Init(Napi::Env env, Napi::Object exports);
@@ -26,10 +53,59 @@ private:
26
53
  xdb_vector_index_t* v_index;
27
54
  xdb_content_t* c_buffer;
28
55
  bool is_initialized;
56
+
57
+ static xdb_vector_index_t* load_vector_index_utf8(const char* path);
58
+ static xdb_content_t* load_content_utf8(const char* path);
59
+ static int new_with_file_only_utf8(xdb_version_t* version, xdb_searcher_t* searcher, const char* path);
60
+ static int new_with_vector_index_utf8(xdb_version_t* version, xdb_searcher_t* searcher, const char* path, const xdb_vector_index_t* v_index);
29
61
  };
30
62
 
31
63
  Napi::FunctionReference Ip2RegionSearcher::constructor;
32
64
 
65
+ xdb_vector_index_t* Ip2RegionSearcher::load_vector_index_utf8(const char* path) {
66
+ FILE* file = fopen_utf8(path, "rb");
67
+ if (!file) return nullptr;
68
+ xdb_vector_index_t* v_index = xdb_load_vector_index(file);
69
+ fclose(file);
70
+ return v_index;
71
+ }
72
+
73
+ xdb_content_t* Ip2RegionSearcher::load_content_utf8(const char* path) {
74
+ FILE* file = fopen_utf8(path, "rb");
75
+ if (!file) return nullptr;
76
+ xdb_content_t* content = xdb_load_content(file);
77
+ fclose(file);
78
+ return content;
79
+ }
80
+
81
+ int Ip2RegionSearcher::new_with_file_only_utf8(xdb_version_t* version, xdb_searcher_t* searcher, const char* path) {
82
+ FILE* file = fopen_utf8(path, "rb");
83
+ if (!file) return -1;
84
+
85
+ memset(searcher, 0, sizeof(xdb_searcher_t));
86
+ searcher->version = version;
87
+ searcher->handle = file;
88
+ searcher->v_index = nullptr;
89
+ searcher->content = nullptr;
90
+ searcher->io_count = 0;
91
+
92
+ return 0;
93
+ }
94
+
95
+ int Ip2RegionSearcher::new_with_vector_index_utf8(xdb_version_t* version, xdb_searcher_t* searcher, const char* path, const xdb_vector_index_t* v_index) {
96
+ FILE* file = fopen_utf8(path, "rb");
97
+ if (!file) return -1;
98
+
99
+ memset(searcher, 0, sizeof(xdb_searcher_t));
100
+ searcher->version = version;
101
+ searcher->handle = file;
102
+ searcher->v_index = v_index;
103
+ searcher->content = nullptr;
104
+ searcher->io_count = 0;
105
+
106
+ return 0;
107
+ }
108
+
33
109
  Napi::Object Ip2RegionSearcher::Init(Napi::Env env, Napi::Object exports) {
34
110
  Napi::HandleScope scope(env);
35
111
 
@@ -91,17 +167,17 @@ Ip2RegionSearcher::Ip2RegionSearcher(const Napi::CallbackInfo& info)
91
167
 
92
168
  // Initialize searcher based on cache strategy
93
169
  if (cache_policy == "file") {
94
- err = xdb_new_with_file_only(version, &searcher, db_path.c_str());
170
+ err = new_with_file_only_utf8(version, &searcher, db_path.c_str());
95
171
  } else if (cache_policy == "vectorIndex") {
96
- v_index = xdb_load_vector_index_from_file(db_path.c_str());
172
+ v_index = load_vector_index_utf8(db_path.c_str());
97
173
  if (!v_index) {
98
174
  xdb_clean_winsock();
99
175
  Napi::Error::New(env, "Failed to load vector index from database file").ThrowAsJavaScriptException();
100
176
  return;
101
177
  }
102
- err = xdb_new_with_vector_index(version, &searcher, db_path.c_str(), v_index);
178
+ err = new_with_vector_index_utf8(version, &searcher, db_path.c_str(), v_index);
103
179
  } else if (cache_policy == "content") {
104
- c_buffer = xdb_load_content_from_file(db_path.c_str());
180
+ c_buffer = load_content_utf8(db_path.c_str());
105
181
  if (!c_buffer) {
106
182
  xdb_clean_winsock();
107
183
  Napi::Error::New(env, "Failed to load database content into memory").ThrowAsJavaScriptException();
@@ -213,7 +289,12 @@ Napi::Value VerifyXdb(const Napi::CallbackInfo& info) {
213
289
 
214
290
  const std::string db_path = info[0].As<Napi::String>().Utf8Value();
215
291
 
216
- int error_code = xdb_verify_from_file(db_path.c_str());
292
+ FILE* file = fopen_utf8(db_path.c_str(), "rb");
293
+ int error_code = -1;
294
+ if (file) {
295
+ error_code = xdb_verify(file);
296
+ fclose(file);
297
+ }
217
298
 
218
299
  Napi::Object result = Napi::Object::New(env);
219
300
  result.Set("valid", Napi::Boolean::New(env, error_code == 0));
@@ -24,13 +24,16 @@
24
24
  #include <ws2tcpip.h>
25
25
  #include <windows.h>
26
26
  #pragma comment(lib, "ws2_32.lib")
27
- #elif ( defined(linux) || defined(_UNIX) || defined(__APPLE__) )
27
+ #elif (defined(linux) || defined(_UNIX) || defined(__APPLE__) || defined(unix) || defined(__unix) || defined(__unix__) || defined(__linux__) || defined(linux) || defined(__linux))
28
28
  # define XDB_PUBLIC(type) extern type
29
29
  # define XDB_PRIVATE(type) static inline type
30
30
  # define XDB_LINUX
31
31
  #include <sys/socket.h>
32
32
  #include <netinet/in.h>
33
33
  #include <arpa/inet.h>
34
+ #else
35
+ # define XDB_PUBLIC(type) type
36
+ # define XDB_PRIVATE(type) static type
34
37
  #endif
35
38
 
36
39
  #define xdb_calloc( _blocks, _bytes ) calloc( _blocks, _bytes )
@@ -245,9 +245,14 @@ XDB_PUBLIC(int) xdb_verify_from_header(FILE *handle, xdb_header_t *header) {
245
245
  }
246
246
 
247
247
  long int fileBytes = ftell(handle);
248
- long int maxFilePtr = (1L << (runtime_ptr_bytes * 8)) - 1;
249
- // printf("fileBytes: %ld, maxFilePtr: %ld\n", fileBytes, maxFilePtr);
250
- if (fileBytes > maxFilePtr) {
248
+ if (fileBytes < 0) {
249
+ return 3;
250
+ }
251
+
252
+ // Use unsigned long long for maxFilePtr to avoid overflow
253
+ unsigned long long maxFilePtr = (1ULL << (runtime_ptr_bytes * 8)) - 1;
254
+ // printf("fileBytes: %ld, maxFilePtr: %llu\n", fileBytes, maxFilePtr);
255
+ if ((unsigned long long)fileBytes > maxFilePtr) {
251
256
  return 4;
252
257
  }
253
258
 
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "ts-ip2region2",
3
- "version": "1.2.0",
3
+ "version": "1.2.1",
4
4
  "description": "TypeScript-first ip2region implementation with native Node.js addon. Ultra-fast IPv4/IPv6 geolocation queries.",
5
5
  "main": "dist/index.js",
6
6
  "types": "dist/index.d.ts",
@@ -11,6 +11,7 @@
11
11
  "rebuild": "node-gyp rebuild",
12
12
  "clean": "node-gyp clean",
13
13
  "test": "npm run build:all && node test.js",
14
+ "benchmark": "node benchmark.js",
14
15
  "example": "npm run build:all && node example.js",
15
16
  "prepublishOnly": "npm run build:all"
16
17
  },