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 +29 -0
- package/addon.cpp +86 -5
- package/ip2region/xdb_api.h +4 -1
- package/ip2region/xdb_util.c +8 -3
- package/package.json +2 -1
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 =
|
|
170
|
+
err = new_with_file_only_utf8(version, &searcher, db_path.c_str());
|
|
95
171
|
} else if (cache_policy == "vectorIndex") {
|
|
96
|
-
v_index =
|
|
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 =
|
|
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 =
|
|
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
|
-
|
|
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));
|
package/ip2region/xdb_api.h
CHANGED
|
@@ -24,13 +24,16 @@
|
|
|
24
24
|
#include <ws2tcpip.h>
|
|
25
25
|
#include <windows.h>
|
|
26
26
|
#pragma comment(lib, "ws2_32.lib")
|
|
27
|
-
#elif (
|
|
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 )
|
package/ip2region/xdb_util.c
CHANGED
|
@@ -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
|
-
|
|
249
|
-
|
|
250
|
-
|
|
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.
|
|
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
|
},
|