@oschwald/maxminddb 0.2.1 → 0.3.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/CHANGELOG.md +86 -0
- package/README.md +37 -15
- package/index.d.ts +27 -4
- package/index.darwin-arm64.node +0 -0
- package/index.darwin-x64.node +0 -0
- package/index.js +144 -111
- package/index.linux-arm64-gnu.node +0 -0
- package/index.linux-x64-gnu.node +0 -0
- package/index.win32-arm64-msvc.node +0 -0
- package/index.win32-x64-msvc.node +0 -0
- package/package.json +2 -2
package/CHANGELOG.md
CHANGED
|
@@ -5,6 +5,89 @@ All notable changes to this project will be documented in this file.
|
|
|
5
5
|
The format is based on [Keep a Changelog](https://keepachangelog.com/en/1.0.0/),
|
|
6
6
|
and this project adheres to [Semantic Versioning](https://semver.org/spec/v2.0.0.html).
|
|
7
7
|
|
|
8
|
+
## [0.3.1] - 2026-07-19
|
|
9
|
+
|
|
10
|
+
### Fixed
|
|
11
|
+
|
|
12
|
+
- Updated the packed-package smoke test to accept the npm 12
|
|
13
|
+
`npm pack --json` output format, allowing trusted publishing validation to
|
|
14
|
+
complete with the current npm release.
|
|
15
|
+
|
|
16
|
+
## [0.3.0] - 2026-07-19
|
|
17
|
+
|
|
18
|
+
### Added
|
|
19
|
+
|
|
20
|
+
- Added `reloadAsync()` to open and validate replacement databases without
|
|
21
|
+
blocking the JavaScript event loop.
|
|
22
|
+
- Added `getPaths()` and `getManyPaths()` for decoding multiple selected values
|
|
23
|
+
with one database lookup per IP.
|
|
24
|
+
- Added `networksPath()` and `withinPath()` for selectively decoding one value
|
|
25
|
+
while iterating database networks.
|
|
26
|
+
|
|
27
|
+
### Changed
|
|
28
|
+
|
|
29
|
+
- Numeric path components now reject fractional, non-finite, and unsafe
|
|
30
|
+
JavaScript numbers instead of relying on native integer coercion.
|
|
31
|
+
- Updated the Rust `maxminddb` dependency to 0.30.0, inheriting safer corrupt
|
|
32
|
+
database traversal and faster path and record decoding.
|
|
33
|
+
- Invalid UTF-8 in decoded MMDB string values and map keys now follows Node's
|
|
34
|
+
native conversion behavior and is represented with replacement characters.
|
|
35
|
+
- `MODE_BUFFER` now uses the same native owned-memory reader as `MODE_MEMORY`;
|
|
36
|
+
the constant remains available as a compatibility alias.
|
|
37
|
+
- Network iterators now close their native cursor when a `for...of` loop exits
|
|
38
|
+
early.
|
|
39
|
+
- Compiled `PathLookup` instances can be closed explicitly, release their
|
|
40
|
+
native path automatically after collection, and survive watched reader swaps.
|
|
41
|
+
- Consolidated mutable native reader state behind one runtime borrow, keeping
|
|
42
|
+
reentrant JavaScript access safe without per-field borrow checks.
|
|
43
|
+
- Stopped exposing undocumented native reader constructors and helpers from the
|
|
44
|
+
JavaScript entry point, leaving `nativeVersion()` as the supported diagnostic.
|
|
45
|
+
|
|
46
|
+
### Fixed
|
|
47
|
+
|
|
48
|
+
- Preserved complete invalid IP values in errors when an overlong string crosses
|
|
49
|
+
the native UTF-8 buffer boundary at a multibyte character.
|
|
50
|
+
|
|
51
|
+
### Performance
|
|
52
|
+
|
|
53
|
+
- Moved asynchronous reload work, including memory-mapped reloads triggered by
|
|
54
|
+
file watchers, off the JavaScript event loop.
|
|
55
|
+
- Added multi-field projections that reuse one search-tree lookup per IP,
|
|
56
|
+
improving batch throughput when several fields are needed without decoding
|
|
57
|
+
the full record.
|
|
58
|
+
- Moved gzip input detection into the native readers, checking owned-file
|
|
59
|
+
prefixes before allocating their buffers and eliminating an extra
|
|
60
|
+
asynchronous file open, read, and close during path-based opens.
|
|
61
|
+
- Decode MMDB strings and map keys directly from raw bytes into JavaScript
|
|
62
|
+
strings, avoiding redundant UTF-8 decoding in Rust and Node.
|
|
63
|
+
- Reduced lookup and batch allocation by reading IP strings directly from V8
|
|
64
|
+
and constructing batch result arrays in place.
|
|
65
|
+
- Reuse records with shared data offsets within cached network cursor pages,
|
|
66
|
+
avoiding repeated decoding during large network walks.
|
|
67
|
+
- Format network strings on the stack and construct network-result pairs in
|
|
68
|
+
place, avoiding two heap allocations per iterated network.
|
|
69
|
+
- Construct decoded arrays and objects in a single pass and carry JavaScript
|
|
70
|
+
decode context through nested values, reducing allocations and TLS access.
|
|
71
|
+
- Use a protected LRU segment for caches up to 10,000 records, retaining
|
|
72
|
+
frequently reused records during broad scans without increasing capacity.
|
|
73
|
+
- Open owned-memory file modes on a worker thread and read directly into Rust
|
|
74
|
+
memory, avoiding a full intermediate Node `Buffer` and event-loop blocking.
|
|
75
|
+
- Store common compiled paths inline, avoiding a heap allocation on each hot
|
|
76
|
+
path lookup.
|
|
77
|
+
- Return native network pages directly when no buffered records remain,
|
|
78
|
+
avoiding an extra JavaScript array allocation and copy per page.
|
|
79
|
+
- Enable thin link-time optimization for release builds, reducing the native
|
|
80
|
+
binary size and allowing optimization across crate boundaries.
|
|
81
|
+
|
|
82
|
+
### Development
|
|
83
|
+
|
|
84
|
+
- Corrected projection benchmark found counts to exclude rows containing only
|
|
85
|
+
missing values.
|
|
86
|
+
- Added focused unit coverage for the custom IPv4 parser, IPv4 prefix
|
|
87
|
+
translation, signed path indexes, and inline path storage boundary.
|
|
88
|
+
- Removed the unused eager native network collector; the public API continues
|
|
89
|
+
to use the lower-memory native cursor implementation.
|
|
90
|
+
|
|
8
91
|
## [0.2.1] - 2026-07-03
|
|
9
92
|
|
|
10
93
|
* Test fix. No other changes.
|
|
@@ -83,5 +166,8 @@ and this project adheres to [Semantic Versioning](https://semver.org/spec/v2.0.0
|
|
|
83
166
|
- Added Rust formatting, `cargo check`, clippy, TypeScript, Node test, npm pack,
|
|
84
167
|
and packed-package smoke-test validation.
|
|
85
168
|
|
|
169
|
+
[0.3.1]: https://github.com/oschwald/maxminddb-node/releases/tag/v0.3.1
|
|
170
|
+
[0.3.0]: https://github.com/oschwald/maxminddb-node/releases/tag/v0.3.0
|
|
171
|
+
[0.2.1]: https://github.com/oschwald/maxminddb-node/releases/tag/v0.2.1
|
|
86
172
|
[0.2.0]: https://github.com/oschwald/maxminddb-node/releases/tag/v0.2.0
|
|
87
173
|
[0.1.0]: https://github.com/oschwald/maxminddb-node/releases/tag/v0.1.0
|
package/README.md
CHANGED
|
@@ -61,9 +61,9 @@ cache sizes can therefore retain significant heap memory when the database
|
|
|
61
61
|
records are large or lookups touch many distinct data offsets. Cached records
|
|
62
62
|
are returned by reference, so mutating a cached record can affect later lookups
|
|
63
63
|
for the same data offset until that entry is evicted, `reader.clearCache()` is
|
|
64
|
-
called, or the reader is closed. `getPath()`, `getManyPath()`,
|
|
65
|
-
`reader.path()` lookups decode only the requested
|
|
66
|
-
full-record cache.
|
|
64
|
+
called, or the reader is closed. `getPath()`, `getPaths()`, `getManyPath()`,
|
|
65
|
+
`getManyPaths()`, and compiled `reader.path()` lookups decode only the requested
|
|
66
|
+
paths and do not populate the full-record cache.
|
|
67
67
|
|
|
68
68
|
When `watchForUpdates` is enabled, file-change reloads run serially. A failed
|
|
69
69
|
watched reload leaves the existing reader active, stores the failure on
|
|
@@ -71,12 +71,24 @@ watched reload leaves the existing reader active, stores the failure on
|
|
|
71
71
|
reload clears `lastReloadError` and calls the hook. Close watched readers with
|
|
72
72
|
`reader.close()` to remove the file watcher.
|
|
73
73
|
|
|
74
|
+
Call `await reader.reloadAsync()` to open and validate a replacement database
|
|
75
|
+
on a worker thread before atomically swapping it into the reader. The existing
|
|
76
|
+
`reader.reload()` remains available when synchronous replacement is preferred.
|
|
77
|
+
|
|
74
78
|
## Extensions
|
|
75
79
|
|
|
76
80
|
```js
|
|
77
81
|
reader.getPath('8.8.8.8', ['country', 'iso_code']);
|
|
78
82
|
reader.getMany(['8.8.8.8', '1.1.1.1']);
|
|
79
83
|
reader.getManyPath(['8.8.8.8', '1.1.1.1'], ['country', 'iso_code']);
|
|
84
|
+
reader.getPaths('8.8.8.8', [
|
|
85
|
+
['country', 'iso_code'],
|
|
86
|
+
['continent', 'code'],
|
|
87
|
+
]);
|
|
88
|
+
reader.getManyPaths(['8.8.8.8', '1.1.1.1'], [
|
|
89
|
+
['country', 'iso_code'],
|
|
90
|
+
['continent', 'code'],
|
|
91
|
+
]);
|
|
80
92
|
|
|
81
93
|
const countryCode = reader.path(['country', 'iso_code']);
|
|
82
94
|
countryCode.get('8.8.8.8');
|
|
@@ -91,20 +103,32 @@ for (const page of reader.withinPages('81.2.69.0/24', { pageSize: 100 })) {
|
|
|
91
103
|
console.log(network, record);
|
|
92
104
|
}
|
|
93
105
|
}
|
|
106
|
+
|
|
107
|
+
for (const [network, country] of reader.withinPath(
|
|
108
|
+
'81.2.69.0/24',
|
|
109
|
+
['country', 'iso_code']
|
|
110
|
+
)) {
|
|
111
|
+
console.log(network, country);
|
|
112
|
+
}
|
|
94
113
|
```
|
|
95
114
|
|
|
96
|
-
Path elements are strings for map keys and numbers for
|
|
97
|
-
indexes count from the end of an array.
|
|
115
|
+
Path elements are strings for map keys and finite, safe-integer numbers for
|
|
116
|
+
array indexes. Negative indexes count from the end of an array.
|
|
98
117
|
|
|
99
118
|
Create compiled path lookups once and reuse them in hot paths. `reader.path()`
|
|
100
119
|
parses and stores the path, and the returned `PathLookup` avoids reparsing the
|
|
101
120
|
path array on each lookup.
|
|
102
121
|
|
|
103
|
-
For high-volume lookup workloads, prefer `getMany()
|
|
104
|
-
you can batch IPs. They cross the native boundary once
|
|
105
|
-
are significantly faster than calling `get()` in a
|
|
122
|
+
For high-volume lookup workloads, prefer `getMany()`, `getManyPath()`, or
|
|
123
|
+
`getManyPaths()` when you can batch IPs. They cross the native boundary once
|
|
124
|
+
for the whole batch and are significantly faster than calling `get()` in a
|
|
125
|
+
JavaScript loop. `getPaths()` and `getManyPaths()` perform one database lookup
|
|
126
|
+
per IP and return projected values in the same order as the requested paths;
|
|
127
|
+
missing paths are returned as `null`.
|
|
106
128
|
|
|
107
129
|
`networks()` and `within()` return lazy iterators backed by native cursors.
|
|
130
|
+
Use `networksPath()` or `withinPath()` to decode only a selected field while
|
|
131
|
+
walking networks.
|
|
108
132
|
For large network walks, use `networkPages()`, `withinPages()`, or
|
|
109
133
|
`NetworkIterator#nextPage()` to cross the native boundary once per page rather
|
|
110
134
|
than once per network.
|
|
@@ -118,8 +142,8 @@ Path-based `open()` defaults to memory-mapped reads:
|
|
|
118
142
|
- `MODE_MEMORY`
|
|
119
143
|
- `MODE_BUFFER`
|
|
120
144
|
|
|
121
|
-
|
|
122
|
-
|
|
145
|
+
`MODE_MEMORY` and `MODE_BUFFER` asynchronously read the file once into
|
|
146
|
+
Rust-owned memory. `MODE_BUFFER` is retained as a compatibility alias.
|
|
123
147
|
|
|
124
148
|
```js
|
|
125
149
|
import maxmind from '@oschwald/maxminddb';
|
|
@@ -133,11 +157,9 @@ Mode tradeoffs:
|
|
|
133
157
|
|
|
134
158
|
- `MODE_MMAP`/`MODE_AUTO` opens quickly and keeps RSS low by mapping the
|
|
135
159
|
database file. Replace database files atomically when using watched reloads.
|
|
136
|
-
- `MODE_MEMORY`
|
|
137
|
-
at open time but
|
|
138
|
-
|
|
139
|
-
native reader. Use it when you need Node-side file loading behavior or when
|
|
140
|
-
tests need to mutate a watched temporary file safely.
|
|
160
|
+
- `MODE_MEMORY`/`MODE_BUFFER` asynchronously read the database into Rust-owned
|
|
161
|
+
memory. They cost more memory at open time but are independent of the source
|
|
162
|
+
file after open.
|
|
141
163
|
|
|
142
164
|
## Performance Notes
|
|
143
165
|
|
package/index.d.ts
CHANGED
|
@@ -193,15 +193,20 @@ export interface CacheStats {
|
|
|
193
193
|
readonly evictions: number;
|
|
194
194
|
}
|
|
195
195
|
|
|
196
|
+
export type LookupPath = ReadonlyArray<string | number>;
|
|
197
|
+
|
|
196
198
|
export declare class PathLookup<TValue = unknown> {
|
|
197
199
|
readonly path: ReadonlyArray<string | number>;
|
|
198
200
|
get(ipAddress: string): TValue | null;
|
|
199
201
|
getMany(ipAddresses: ReadonlyArray<string>): Array<TValue | null>;
|
|
202
|
+
close(): void;
|
|
200
203
|
}
|
|
201
204
|
|
|
202
|
-
export declare class NetworkIterator<T
|
|
205
|
+
export declare class NetworkIterator<T = Response>
|
|
203
206
|
implements IterableIterator<[string, T | null]> {
|
|
207
|
+
readonly path: ReadonlyArray<string | number> | null;
|
|
204
208
|
next(): IteratorResult<[string, T | null]>;
|
|
209
|
+
return(): IteratorReturnResult<undefined>;
|
|
205
210
|
nextPage(pageSize?: number): Array<[string, T | null]>;
|
|
206
211
|
pages(pageSize?: number): IterableIterator<Array<[string, T | null]>>;
|
|
207
212
|
close(): void;
|
|
@@ -220,22 +225,40 @@ export declare class Reader<T extends Response = Response> {
|
|
|
220
225
|
metadata: Metadata;
|
|
221
226
|
load(database: Buffer): void;
|
|
222
227
|
reload(): void;
|
|
228
|
+
reloadAsync(): Promise<void>;
|
|
223
229
|
close(): void;
|
|
224
230
|
clearCache(): void;
|
|
225
231
|
cacheStats(): CacheStats;
|
|
226
232
|
get(ipAddress: string): T | null;
|
|
227
|
-
getPath(ipAddress: string, path:
|
|
233
|
+
getPath(ipAddress: string, path: LookupPath): unknown;
|
|
234
|
+
getPaths<TValues extends ReadonlyArray<unknown> = unknown[]>(
|
|
235
|
+
ipAddress: string,
|
|
236
|
+
paths: ReadonlyArray<LookupPath>,
|
|
237
|
+
): TValues;
|
|
228
238
|
path<TValue = unknown>(
|
|
229
|
-
path:
|
|
239
|
+
path: LookupPath,
|
|
230
240
|
): PathLookup<TValue>;
|
|
231
241
|
getWithPrefixLength(ipAddress: string): [T | null, number];
|
|
232
242
|
getMany(ipAddresses: ReadonlyArray<string>): Array<T | null>;
|
|
233
243
|
getManyPath(
|
|
234
244
|
ipAddresses: ReadonlyArray<string>,
|
|
235
|
-
path:
|
|
245
|
+
path: LookupPath,
|
|
236
246
|
): unknown[];
|
|
247
|
+
getManyPaths<TValues extends ReadonlyArray<unknown> = unknown[]>(
|
|
248
|
+
ipAddresses: ReadonlyArray<string>,
|
|
249
|
+
paths: ReadonlyArray<LookupPath>,
|
|
250
|
+
): TValues[];
|
|
237
251
|
networks(options?: NetworkPagesOptions): NetworkIterator<T>;
|
|
238
252
|
within(cidr: string, options?: NetworkPagesOptions): NetworkIterator<T>;
|
|
253
|
+
networksPath<TValue = unknown>(
|
|
254
|
+
path: ReadonlyArray<string | number>,
|
|
255
|
+
options?: NetworkPagesOptions,
|
|
256
|
+
): NetworkIterator<TValue>;
|
|
257
|
+
withinPath<TValue = unknown>(
|
|
258
|
+
cidr: string,
|
|
259
|
+
path: ReadonlyArray<string | number>,
|
|
260
|
+
options?: NetworkPagesOptions,
|
|
261
|
+
): NetworkIterator<TValue>;
|
|
239
262
|
networkPages(
|
|
240
263
|
options?: NetworkPagesOptions,
|
|
241
264
|
): IterableIterator<Array<[string, T | null]>>;
|
package/index.darwin-arm64.node
CHANGED
|
Binary file
|
package/index.darwin-x64.node
CHANGED
|
Binary file
|
package/index.js
CHANGED
|
@@ -54,12 +54,14 @@ function loadNativeBinding() {
|
|
|
54
54
|
}
|
|
55
55
|
|
|
56
56
|
const native = loadNativeBinding();
|
|
57
|
+
const pathFinalizer = new FinalizationRegistry(({ nativeReader, pathId }) => {
|
|
58
|
+
try {
|
|
59
|
+
nativeReader.releasePath(pathId);
|
|
60
|
+
} catch {
|
|
61
|
+
// Reader shutdown may have already released all compiled paths.
|
|
62
|
+
}
|
|
63
|
+
});
|
|
57
64
|
|
|
58
|
-
const DEFAULT_LARGE_FILE_THRESHOLD = 512 * 1024 * 1024;
|
|
59
|
-
const LARGE_FILE_THRESHOLD = normalizeLargeFileThreshold(
|
|
60
|
-
process.env.MAXMINDDB_LARGE_FILE_THRESHOLD_BYTES
|
|
61
|
-
);
|
|
62
|
-
const STREAM_WATERMARK = 8 * 1024 * 1024;
|
|
63
65
|
const DEFAULT_CACHE_MAX = 10_000;
|
|
64
66
|
const MAX_CACHE_MAX = 0xffffffff;
|
|
65
67
|
const legacyErrorMessage = `Maxmind v2 module has changed API.
|
|
@@ -74,36 +76,6 @@ const MODE_MMAP = 'mmap';
|
|
|
74
76
|
const MODE_MEMORY = 'memory';
|
|
75
77
|
const MODE_BUFFER = 'buffer';
|
|
76
78
|
|
|
77
|
-
function normalizeLargeFileThreshold(value) {
|
|
78
|
-
if (value == null || value === '') {
|
|
79
|
-
return DEFAULT_LARGE_FILE_THRESHOLD;
|
|
80
|
-
}
|
|
81
|
-
|
|
82
|
-
const threshold = Number(value);
|
|
83
|
-
return Number.isSafeInteger(threshold) && threshold >= 0
|
|
84
|
-
? threshold
|
|
85
|
-
: DEFAULT_LARGE_FILE_THRESHOLD;
|
|
86
|
-
}
|
|
87
|
-
|
|
88
|
-
function isGzipBuffer(buffer) {
|
|
89
|
-
return buffer.length >= 2 && buffer[0] === 0x1f && buffer[1] === 0x8b;
|
|
90
|
-
}
|
|
91
|
-
|
|
92
|
-
async function assertNotGzipFile(filepath) {
|
|
93
|
-
const handle = await fs.promises.open(filepath, 'r');
|
|
94
|
-
try {
|
|
95
|
-
const buffer = Buffer.alloc(2);
|
|
96
|
-
const { bytesRead } = await handle.read(buffer, 0, 2, 0);
|
|
97
|
-
if (bytesRead === 2 && isGzipBuffer(buffer)) {
|
|
98
|
-
throw new Error(
|
|
99
|
-
'Looks like you are passing in a file in gzip format, please use mmdb database instead.'
|
|
100
|
-
);
|
|
101
|
-
}
|
|
102
|
-
} finally {
|
|
103
|
-
await handle.close();
|
|
104
|
-
}
|
|
105
|
-
}
|
|
106
|
-
|
|
107
79
|
function normalizeMode(mode) {
|
|
108
80
|
if (mode == null || mode === MODE_AUTO) {
|
|
109
81
|
return MODE_MMAP;
|
|
@@ -178,93 +150,74 @@ function waitForFile(filepath) {
|
|
|
178
150
|
});
|
|
179
151
|
}
|
|
180
152
|
|
|
181
|
-
async function readLargeFile(filepath, size) {
|
|
182
|
-
return new Promise((resolve, reject) => {
|
|
183
|
-
const buffer = Buffer.allocUnsafe(size);
|
|
184
|
-
let offset = 0;
|
|
185
|
-
let settled = false;
|
|
186
|
-
const stream = fs.createReadStream(filepath, {
|
|
187
|
-
highWaterMark: STREAM_WATERMARK,
|
|
188
|
-
});
|
|
189
|
-
|
|
190
|
-
const finish = (error, value) => {
|
|
191
|
-
if (settled) {
|
|
192
|
-
return;
|
|
193
|
-
}
|
|
194
|
-
settled = true;
|
|
195
|
-
if (error) {
|
|
196
|
-
stream.destroy();
|
|
197
|
-
reject(error);
|
|
198
|
-
} else {
|
|
199
|
-
resolve(value);
|
|
200
|
-
}
|
|
201
|
-
};
|
|
202
|
-
|
|
203
|
-
stream.on('data', (chunk) => {
|
|
204
|
-
const bufferChunk = Buffer.isBuffer(chunk) ? chunk : Buffer.from(chunk);
|
|
205
|
-
if (offset + bufferChunk.length > size) {
|
|
206
|
-
finish(
|
|
207
|
-
new Error(
|
|
208
|
-
`File changed while reading ${filepath}: expected ${size} bytes but read more`
|
|
209
|
-
)
|
|
210
|
-
);
|
|
211
|
-
return;
|
|
212
|
-
}
|
|
213
|
-
bufferChunk.copy(buffer, offset);
|
|
214
|
-
offset += bufferChunk.length;
|
|
215
|
-
});
|
|
216
|
-
stream.on('end', () => {
|
|
217
|
-
if (offset !== size) {
|
|
218
|
-
finish(
|
|
219
|
-
new Error(
|
|
220
|
-
`File changed while reading ${filepath}: expected ${size} bytes but read ${offset}`
|
|
221
|
-
)
|
|
222
|
-
);
|
|
223
|
-
return;
|
|
224
|
-
}
|
|
225
|
-
finish(null, buffer);
|
|
226
|
-
});
|
|
227
|
-
stream.on('error', (error) => finish(error));
|
|
228
|
-
});
|
|
229
|
-
}
|
|
230
|
-
|
|
231
|
-
async function readFile(filepath) {
|
|
232
|
-
const stat = await fs.promises.stat(filepath);
|
|
233
|
-
return stat.size < LARGE_FILE_THRESHOLD
|
|
234
|
-
? fs.promises.readFile(filepath)
|
|
235
|
-
: readLargeFile(filepath, stat.size);
|
|
236
|
-
}
|
|
237
|
-
|
|
238
153
|
class PathLookup {
|
|
239
154
|
constructor(reader, path) {
|
|
240
155
|
this._reader = reader;
|
|
241
|
-
this._pathId = reader._reader.compilePath(path);
|
|
242
156
|
this.path = Object.freeze([...path]);
|
|
157
|
+
this._closed = false;
|
|
158
|
+
this._compileFor(reader._reader);
|
|
243
159
|
}
|
|
244
160
|
|
|
245
161
|
get(ipAddress) {
|
|
246
|
-
|
|
162
|
+
this._ensureCompiled();
|
|
163
|
+
return this._nativeReader.getCompiledPath(ipAddress, this._pathId);
|
|
247
164
|
}
|
|
248
165
|
|
|
249
166
|
getMany(ipAddresses) {
|
|
250
|
-
|
|
167
|
+
this._ensureCompiled();
|
|
168
|
+
return this._nativeReader.getManyCompiledPath(ipAddresses, this._pathId);
|
|
169
|
+
}
|
|
170
|
+
|
|
171
|
+
close() {
|
|
172
|
+
if (!this._closed) {
|
|
173
|
+
pathFinalizer.unregister(this);
|
|
174
|
+
this._nativeReader.releasePath(this._pathId);
|
|
175
|
+
this._closed = true;
|
|
176
|
+
}
|
|
177
|
+
}
|
|
178
|
+
|
|
179
|
+
_ensureCompiled() {
|
|
180
|
+
if (this._closed) {
|
|
181
|
+
throw new Error('Path lookup is closed.');
|
|
182
|
+
}
|
|
183
|
+
if (this._nativeReader !== this._reader._reader) {
|
|
184
|
+
pathFinalizer.unregister(this);
|
|
185
|
+
this._nativeReader.releasePath(this._pathId);
|
|
186
|
+
this._compileFor(this._reader._reader);
|
|
187
|
+
}
|
|
188
|
+
}
|
|
189
|
+
|
|
190
|
+
_compileFor(nativeReader) {
|
|
191
|
+
this._nativeReader = nativeReader;
|
|
192
|
+
this._pathId = nativeReader.compilePath(this.path);
|
|
193
|
+
pathFinalizer.register(
|
|
194
|
+
this,
|
|
195
|
+
{ nativeReader: this._nativeReader, pathId: this._pathId },
|
|
196
|
+
this
|
|
197
|
+
);
|
|
251
198
|
}
|
|
252
199
|
}
|
|
253
200
|
|
|
254
201
|
class NetworkIterator {
|
|
255
|
-
constructor(reader, cidr, options = {}) {
|
|
202
|
+
constructor(reader, cidr, options = {}, path = null) {
|
|
256
203
|
const [networkOptions, pageSize] = normalizeNetworkIteratorOptions(options);
|
|
257
|
-
this._cursor = reader._reader.networkCursor(cidr, ...networkOptions);
|
|
204
|
+
this._cursor = reader._reader.networkCursor(cidr, ...networkOptions, path);
|
|
258
205
|
this._pageSize = pageSize;
|
|
259
206
|
this._page = [];
|
|
260
207
|
this._index = 0;
|
|
261
208
|
this._done = false;
|
|
209
|
+
this.path = path == null ? null : Object.freeze([...path]);
|
|
262
210
|
}
|
|
263
211
|
|
|
264
212
|
[Symbol.iterator]() {
|
|
265
213
|
return this;
|
|
266
214
|
}
|
|
267
215
|
|
|
216
|
+
return() {
|
|
217
|
+
this.close();
|
|
218
|
+
return { done: true, value: undefined };
|
|
219
|
+
}
|
|
220
|
+
|
|
268
221
|
next() {
|
|
269
222
|
if (this._done) {
|
|
270
223
|
return { done: true, value: undefined };
|
|
@@ -290,6 +243,16 @@ class NetworkIterator {
|
|
|
290
243
|
return [];
|
|
291
244
|
}
|
|
292
245
|
|
|
246
|
+
if (this._index >= this._page.length) {
|
|
247
|
+
this._page = [];
|
|
248
|
+
this._index = 0;
|
|
249
|
+
const page = this._cursor.nextPage(pageSize);
|
|
250
|
+
if (page.length === 0) {
|
|
251
|
+
this.close();
|
|
252
|
+
}
|
|
253
|
+
return page;
|
|
254
|
+
}
|
|
255
|
+
|
|
293
256
|
const page = [];
|
|
294
257
|
while (page.length < pageSize && this._index < this._page.length) {
|
|
295
258
|
page.push(this._page[this._index]);
|
|
@@ -298,7 +261,9 @@ class NetworkIterator {
|
|
|
298
261
|
|
|
299
262
|
if (page.length < pageSize) {
|
|
300
263
|
const nativePage = this._cursor.nextPage(pageSize - page.length);
|
|
301
|
-
|
|
264
|
+
for (const record of nativePage) {
|
|
265
|
+
page.push(record);
|
|
266
|
+
}
|
|
302
267
|
if (nativePage.length === 0) {
|
|
303
268
|
this.close();
|
|
304
269
|
}
|
|
@@ -349,6 +314,28 @@ class Reader {
|
|
|
349
314
|
|
|
350
315
|
static open(filepath, options = {}) {
|
|
351
316
|
const mode = normalizeMode(options.mode);
|
|
317
|
+
return Reader._fromNative(
|
|
318
|
+
filepath,
|
|
319
|
+
mode,
|
|
320
|
+
options,
|
|
321
|
+
native.openReader(filepath, mode, normalizeCacheCapacity(options))
|
|
322
|
+
);
|
|
323
|
+
}
|
|
324
|
+
|
|
325
|
+
static async openOwned(filepath, mode, options = {}) {
|
|
326
|
+
return Reader._fromNative(
|
|
327
|
+
filepath,
|
|
328
|
+
mode,
|
|
329
|
+
options,
|
|
330
|
+
await native.openReaderAsync(
|
|
331
|
+
filepath,
|
|
332
|
+
mode,
|
|
333
|
+
normalizeCacheCapacity(options)
|
|
334
|
+
)
|
|
335
|
+
);
|
|
336
|
+
}
|
|
337
|
+
|
|
338
|
+
static _fromNative(filepath, mode, options, nativeReader) {
|
|
352
339
|
const reader = Object.create(Reader.prototype);
|
|
353
340
|
reader._mode = mode;
|
|
354
341
|
reader._filepath = filepath;
|
|
@@ -359,7 +346,7 @@ class Reader {
|
|
|
359
346
|
reader._watchReloadPromise = Promise.resolve();
|
|
360
347
|
reader._lastReloadError = null;
|
|
361
348
|
reader._cacheCapacity = normalizeCacheCapacity(options);
|
|
362
|
-
reader._reader =
|
|
349
|
+
reader._reader = nativeReader;
|
|
363
350
|
reader.metadata = normalizeMetadata(reader._reader.metadata());
|
|
364
351
|
reader.options = options;
|
|
365
352
|
return reader;
|
|
@@ -398,6 +385,36 @@ class Reader {
|
|
|
398
385
|
}
|
|
399
386
|
}
|
|
400
387
|
|
|
388
|
+
async reloadAsync() {
|
|
389
|
+
if (!this._filepath) {
|
|
390
|
+
throw new Error('Cannot reload a buffer-backed Reader');
|
|
391
|
+
}
|
|
392
|
+
try {
|
|
393
|
+
const replacement = await native.openReaderAsync(
|
|
394
|
+
this._filepath,
|
|
395
|
+
this._mode,
|
|
396
|
+
this._cacheCapacity
|
|
397
|
+
);
|
|
398
|
+
if (this.closed) {
|
|
399
|
+
replacement.close();
|
|
400
|
+
throw new Error('Cannot reload a closed Reader');
|
|
401
|
+
}
|
|
402
|
+
this._replaceNativeReader(replacement);
|
|
403
|
+
} catch (error) {
|
|
404
|
+
this._lastReloadError = error;
|
|
405
|
+
throw error;
|
|
406
|
+
}
|
|
407
|
+
}
|
|
408
|
+
|
|
409
|
+
_replaceNativeReader(replacement) {
|
|
410
|
+
const metadata = normalizeMetadata(replacement.metadata());
|
|
411
|
+
const previous = this._reader;
|
|
412
|
+
this._reader = replacement;
|
|
413
|
+
this.metadata = metadata;
|
|
414
|
+
this._lastReloadError = null;
|
|
415
|
+
previous.close();
|
|
416
|
+
}
|
|
417
|
+
|
|
401
418
|
close() {
|
|
402
419
|
if (this._watchFilepath && this._watchListener) {
|
|
403
420
|
fs.unwatchFile(this._watchFilepath, this._watchListener);
|
|
@@ -444,15 +461,16 @@ class Reader {
|
|
|
444
461
|
}
|
|
445
462
|
|
|
446
463
|
try {
|
|
447
|
-
|
|
448
|
-
|
|
449
|
-
|
|
450
|
-
|
|
451
|
-
|
|
452
|
-
|
|
453
|
-
|
|
454
|
-
|
|
464
|
+
const replacement = await native.openReaderAsync(
|
|
465
|
+
filepath,
|
|
466
|
+
mode,
|
|
467
|
+
this._cacheCapacity
|
|
468
|
+
);
|
|
469
|
+
if (this.closed || this._watchFilepath !== filepath) {
|
|
470
|
+
replacement.close();
|
|
471
|
+
return;
|
|
455
472
|
}
|
|
473
|
+
this._replaceNativeReader(replacement);
|
|
456
474
|
if (!this.closed && this._watchFilepath === filepath && hook) {
|
|
457
475
|
hook();
|
|
458
476
|
}
|
|
@@ -477,6 +495,10 @@ class Reader {
|
|
|
477
495
|
return this._reader.getPath(ipAddress, path);
|
|
478
496
|
}
|
|
479
497
|
|
|
498
|
+
getPaths(ipAddress, paths) {
|
|
499
|
+
return this._reader.getPaths(ipAddress, paths);
|
|
500
|
+
}
|
|
501
|
+
|
|
480
502
|
path(path) {
|
|
481
503
|
return new PathLookup(this, path);
|
|
482
504
|
}
|
|
@@ -493,6 +515,10 @@ class Reader {
|
|
|
493
515
|
return this._reader.getManyPath(ipAddresses, path);
|
|
494
516
|
}
|
|
495
517
|
|
|
518
|
+
getManyPaths(ipAddresses, paths) {
|
|
519
|
+
return this._reader.getManyPaths(ipAddresses, paths);
|
|
520
|
+
}
|
|
521
|
+
|
|
496
522
|
networks(options = {}) {
|
|
497
523
|
return new NetworkIterator(this, null, options);
|
|
498
524
|
}
|
|
@@ -501,6 +527,14 @@ class Reader {
|
|
|
501
527
|
return new NetworkIterator(this, cidr, options);
|
|
502
528
|
}
|
|
503
529
|
|
|
530
|
+
networksPath(path, options = {}) {
|
|
531
|
+
return new NetworkIterator(this, null, options, path);
|
|
532
|
+
}
|
|
533
|
+
|
|
534
|
+
withinPath(cidr, path, options = {}) {
|
|
535
|
+
return new NetworkIterator(this, cidr, options, path);
|
|
536
|
+
}
|
|
537
|
+
|
|
504
538
|
*networkPages(options = {}) {
|
|
505
539
|
yield* this.networks(options).pages(options.pageSize ?? 1000);
|
|
506
540
|
}
|
|
@@ -513,12 +547,11 @@ class Reader {
|
|
|
513
547
|
async function open(filepath, opts, cb) {
|
|
514
548
|
assert(!cb, legacyErrorMessage);
|
|
515
549
|
const options = opts || {};
|
|
516
|
-
await assertNotGzipFile(filepath);
|
|
517
550
|
|
|
518
551
|
const mode = normalizeMode(options.mode);
|
|
519
552
|
const reader =
|
|
520
|
-
mode === MODE_BUFFER
|
|
521
|
-
?
|
|
553
|
+
mode === MODE_MEMORY || mode === MODE_BUFFER
|
|
554
|
+
? await Reader.openOwned(filepath, mode, options)
|
|
522
555
|
: Reader.open(filepath, options);
|
|
523
556
|
|
|
524
557
|
if (options.watchForUpdates) {
|
|
@@ -559,7 +592,7 @@ function validate(ipAddress) {
|
|
|
559
592
|
}
|
|
560
593
|
|
|
561
594
|
module.exports = {
|
|
562
|
-
|
|
595
|
+
nativeVersion: native.nativeVersion,
|
|
563
596
|
NetworkIterator,
|
|
564
597
|
PathLookup,
|
|
565
598
|
Reader,
|
|
Binary file
|
package/index.linux-x64-gnu.node
CHANGED
|
Binary file
|
|
Binary file
|
|
Binary file
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@oschwald/maxminddb",
|
|
3
|
-
"version": "0.
|
|
3
|
+
"version": "0.3.1",
|
|
4
4
|
"description": "Rust-backed Node.js reader for MaxMind DB files",
|
|
5
5
|
"main": "index.js",
|
|
6
6
|
"types": "index.d.ts",
|
|
@@ -53,7 +53,7 @@
|
|
|
53
53
|
"devDependencies": {
|
|
54
54
|
"@napi-rs/cli": "^3.7.2",
|
|
55
55
|
"@types/node": "^26.1.0",
|
|
56
|
-
"typescript": "^
|
|
56
|
+
"typescript": "^7.0.2"
|
|
57
57
|
},
|
|
58
58
|
"engines": {
|
|
59
59
|
"node": ">=20"
|