@harperfast/rocksdb-js 0.1.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/LICENSE +201 -0
- package/README.md +1095 -0
- package/dist/index.cjs +1803 -0
- package/dist/index.cjs.map +1 -0
- package/dist/index.d.cts +1152 -0
- package/dist/index.d.mts +1152 -0
- package/dist/index.mjs +1767 -0
- package/dist/index.mjs.map +1 -0
- package/package.json +94 -0
package/dist/index.cjs
ADDED
|
@@ -0,0 +1,1803 @@
|
|
|
1
|
+
//#region rolldown:runtime
|
|
2
|
+
var __create = Object.create;
|
|
3
|
+
var __defProp = Object.defineProperty;
|
|
4
|
+
var __getOwnPropDesc = Object.getOwnPropertyDescriptor;
|
|
5
|
+
var __getOwnPropNames = Object.getOwnPropertyNames;
|
|
6
|
+
var __getProtoOf = Object.getPrototypeOf;
|
|
7
|
+
var __hasOwnProp = Object.prototype.hasOwnProperty;
|
|
8
|
+
var __copyProps = (to, from, except, desc) => {
|
|
9
|
+
if (from && typeof from === "object" || typeof from === "function") {
|
|
10
|
+
for (var keys = __getOwnPropNames(from), i = 0, n = keys.length, key; i < n; i++) {
|
|
11
|
+
key = keys[i];
|
|
12
|
+
if (!__hasOwnProp.call(to, key) && key !== except) {
|
|
13
|
+
__defProp(to, key, {
|
|
14
|
+
get: ((k) => from[k]).bind(null, key),
|
|
15
|
+
enumerable: !(desc = __getOwnPropDesc(from, key)) || desc.enumerable
|
|
16
|
+
});
|
|
17
|
+
}
|
|
18
|
+
}
|
|
19
|
+
}
|
|
20
|
+
return to;
|
|
21
|
+
};
|
|
22
|
+
var __toESM = (mod, isNodeMode, target) => (target = mod != null ? __create(__getProtoOf(mod)) : {}, __copyProps(isNodeMode || !mod || !mod.__esModule ? __defProp(target, "default", {
|
|
23
|
+
value: mod,
|
|
24
|
+
enumerable: true
|
|
25
|
+
}) : target, mod));
|
|
26
|
+
|
|
27
|
+
//#endregion
|
|
28
|
+
let node_child_process = require("node:child_process");
|
|
29
|
+
let node_fs = require("node:fs");
|
|
30
|
+
let node_module = require("node:module");
|
|
31
|
+
let node_path = require("node:path");
|
|
32
|
+
let node_url = require("node:url");
|
|
33
|
+
let msgpackr = require("msgpackr");
|
|
34
|
+
let ordered_binary = require("ordered-binary");
|
|
35
|
+
ordered_binary = __toESM(ordered_binary);
|
|
36
|
+
let _harperfast_extended_iterable = require("@harperfast/extended-iterable");
|
|
37
|
+
|
|
38
|
+
//#region src/load-binding.ts
|
|
39
|
+
const nativeExtRE = /\.node$/;
|
|
40
|
+
/**
|
|
41
|
+
* Locates the native binding in the `build` directory, then the `prebuilds`
|
|
42
|
+
* directory.
|
|
43
|
+
*
|
|
44
|
+
* @returns The path to the native binding.
|
|
45
|
+
*/
|
|
46
|
+
function locateBinding() {
|
|
47
|
+
const baseDir = (0, node_path.dirname)((0, node_path.dirname)((0, node_url.fileURLToPath)(require("url").pathToFileURL(__filename).href)));
|
|
48
|
+
for (const type of ["Release", "Debug"]) try {
|
|
49
|
+
const dir = (0, node_path.join)(baseDir, "build", type);
|
|
50
|
+
const files = (0, node_fs.readdirSync)(dir);
|
|
51
|
+
for (const file of files) if (nativeExtRE.test(file)) return (0, node_path.resolve)(dir, file);
|
|
52
|
+
} catch {}
|
|
53
|
+
let runtime = "";
|
|
54
|
+
if (process.platform === "linux") {
|
|
55
|
+
let isMusl = false;
|
|
56
|
+
try {
|
|
57
|
+
isMusl = (0, node_fs.readFileSync)("/usr/bin/ldd", "utf8").includes("musl");
|
|
58
|
+
} catch {
|
|
59
|
+
if (typeof process.report?.getReport === "function") {
|
|
60
|
+
process.report.excludeEnv = true;
|
|
61
|
+
const report = process.report.getReport();
|
|
62
|
+
isMusl = (!report?.header || !report.header.glibcVersionRuntime) && Array.isArray(report?.sharedObjects) && report.sharedObjects.some((obj) => obj.includes("libc.musl-") || obj.includes("ld-musl-"));
|
|
63
|
+
}
|
|
64
|
+
isMusl = isMusl || (0, node_child_process.execSync)("ldd --version", { encoding: "utf8" }).includes("musl");
|
|
65
|
+
}
|
|
66
|
+
runtime = isMusl ? "-musl" : "-glibc";
|
|
67
|
+
}
|
|
68
|
+
/* v8 ignore next 10 -- @preserve */
|
|
69
|
+
try {
|
|
70
|
+
const path = (0, node_path.join)(baseDir, "node_modules", "@harperfast", `rocksdb-js-${process.platform}-${process.arch}${runtime}`, "rocksdb-js.node");
|
|
71
|
+
if ((0, node_fs.existsSync)(path)) return (0, node_path.resolve)(path);
|
|
72
|
+
} catch {}
|
|
73
|
+
throw new Error("Unable to locate rocksdb-js native binding");
|
|
74
|
+
}
|
|
75
|
+
const binding = (0, node_module.createRequire)(require("url").pathToFileURL(__filename).href)(locateBinding());
|
|
76
|
+
const config = binding.config;
|
|
77
|
+
const constants = binding.constants;
|
|
78
|
+
const NativeDatabase = binding.Database;
|
|
79
|
+
const NativeIterator = binding.Iterator;
|
|
80
|
+
const NativeTransaction = binding.Transaction;
|
|
81
|
+
const TransactionLog = binding.TransactionLog;
|
|
82
|
+
const version = binding.version;
|
|
83
|
+
const shutdown = binding.shutdown;
|
|
84
|
+
|
|
85
|
+
//#endregion
|
|
86
|
+
//#region src/util.ts
|
|
87
|
+
/**
|
|
88
|
+
* Parses a duration string into milliseconds.
|
|
89
|
+
*
|
|
90
|
+
* @param duration - The duration string to parse.
|
|
91
|
+
* @returns The duration in milliseconds.
|
|
92
|
+
*
|
|
93
|
+
* @example
|
|
94
|
+
* ```typescript
|
|
95
|
+
* parseDuration('1s'); // 1000
|
|
96
|
+
* parseDuration('1m'); // 60000
|
|
97
|
+
* parseDuration('1h'); // 3600000
|
|
98
|
+
* parseDuration('1d'); // 86400000
|
|
99
|
+
* parseDuration('1ms'); // 1
|
|
100
|
+
* parseDuration('1s 1ms'); // 1001
|
|
101
|
+
* parseDuration('1m 1s'); // 61000
|
|
102
|
+
* parseDuration('foo'); // throws error
|
|
103
|
+
*
|
|
104
|
+
* parseDuration(1000); // 1000
|
|
105
|
+
* parseDuration(60000); // 60000
|
|
106
|
+
* parseDuration(3600000); // 3600000
|
|
107
|
+
* parseDuration(86400000); // 86400000
|
|
108
|
+
* parseDuration(NaN); // throws error
|
|
109
|
+
* ```
|
|
110
|
+
*/
|
|
111
|
+
function parseDuration(duration) {
|
|
112
|
+
if (typeof duration === "number") {
|
|
113
|
+
if (isNaN(duration) || !isFinite(duration)) throw new Error(`Invalid duration: ${duration}`);
|
|
114
|
+
return duration;
|
|
115
|
+
}
|
|
116
|
+
let result = 0;
|
|
117
|
+
for (const part of duration.split(" ")) {
|
|
118
|
+
const m = part.match(/^(\d+)\s*(ms|s|m|h|d)?$/);
|
|
119
|
+
if (!m) throw new Error(`Invalid duration: ${duration}`);
|
|
120
|
+
const [, value, unit] = m;
|
|
121
|
+
let num = parseInt(value, 10);
|
|
122
|
+
switch (unit) {
|
|
123
|
+
case "s":
|
|
124
|
+
num *= 1e3;
|
|
125
|
+
break;
|
|
126
|
+
case "m":
|
|
127
|
+
num *= 1e3 * 60;
|
|
128
|
+
break;
|
|
129
|
+
case "h":
|
|
130
|
+
num *= 1e3 * 60 * 60;
|
|
131
|
+
break;
|
|
132
|
+
case "d":
|
|
133
|
+
num *= 1e3 * 60 * 60 * 24;
|
|
134
|
+
break;
|
|
135
|
+
}
|
|
136
|
+
result += num;
|
|
137
|
+
}
|
|
138
|
+
return result;
|
|
139
|
+
}
|
|
140
|
+
/**
|
|
141
|
+
* Helper function handling `MaybePromise` results.
|
|
142
|
+
*
|
|
143
|
+
* If the result originates from a function that could throw an error, wrap it
|
|
144
|
+
* in a function so this function can catch any errors and use a unified error
|
|
145
|
+
* handling mechanism.
|
|
146
|
+
*/
|
|
147
|
+
function when(subject, callback, errback) {
|
|
148
|
+
try {
|
|
149
|
+
let result;
|
|
150
|
+
if (typeof subject === "function") result = subject();
|
|
151
|
+
else result = subject;
|
|
152
|
+
if (result instanceof Promise) return result.then(callback, errback);
|
|
153
|
+
return callback ? callback(result) : result;
|
|
154
|
+
} catch (error) {
|
|
155
|
+
return errback ? errback(error) : Promise.reject(error);
|
|
156
|
+
}
|
|
157
|
+
}
|
|
158
|
+
|
|
159
|
+
//#endregion
|
|
160
|
+
//#region src/dbi.ts
|
|
161
|
+
/**
|
|
162
|
+
* The base class for all database operations. This base class is shared by
|
|
163
|
+
* `RocksDatabase` and `Transaction`.
|
|
164
|
+
*
|
|
165
|
+
* This class is not meant to be used directly.
|
|
166
|
+
*/
|
|
167
|
+
var DBI = class DBI {
|
|
168
|
+
/**
|
|
169
|
+
* The RocksDB context for `get()`, `put()`, and `remove()`.
|
|
170
|
+
*/
|
|
171
|
+
#context;
|
|
172
|
+
/**
|
|
173
|
+
* The database store instance. The store instance is tied to the database
|
|
174
|
+
* instance and shared with transaction instances.
|
|
175
|
+
*/
|
|
176
|
+
store;
|
|
177
|
+
/**
|
|
178
|
+
* Initializes the DBI context.
|
|
179
|
+
*
|
|
180
|
+
* @param store - The store instance.
|
|
181
|
+
* @param transaction - The transaction instance.
|
|
182
|
+
*/
|
|
183
|
+
constructor(store, transaction) {
|
|
184
|
+
if (new.target === DBI) throw new Error("DBI is an abstract class and cannot be instantiated directly");
|
|
185
|
+
this.store = store;
|
|
186
|
+
this.#context = transaction || store.db;
|
|
187
|
+
}
|
|
188
|
+
/**
|
|
189
|
+
* Adds a listener for the given key.
|
|
190
|
+
*
|
|
191
|
+
* @param event - The event name to add the listener for.
|
|
192
|
+
* @param callback - The callback to add.
|
|
193
|
+
*/
|
|
194
|
+
addListener(event, callback) {
|
|
195
|
+
this.store.db.addListener(event, callback);
|
|
196
|
+
return this;
|
|
197
|
+
}
|
|
198
|
+
/**
|
|
199
|
+
* Retrieves the value for the given key, then returns the decoded value.
|
|
200
|
+
*/
|
|
201
|
+
get(key, options) {
|
|
202
|
+
if (this.store.decoderCopies) return when(() => this.getBinaryFast(key, options), (result) => {
|
|
203
|
+
if (result === void 0) return;
|
|
204
|
+
if (options?.skipDecode) return result;
|
|
205
|
+
return this.store.decodeValue(result);
|
|
206
|
+
});
|
|
207
|
+
return when(() => this.getBinary(key, options), (result) => result === void 0 ? void 0 : this.store.encoding === "binary" || !this.store.decoder || options?.skipDecode ? result : this.store.decodeValue(result));
|
|
208
|
+
}
|
|
209
|
+
/**
|
|
210
|
+
* Retrieves the binary data for the given key. This is just like `get()`,
|
|
211
|
+
* but bypasses the decoder.
|
|
212
|
+
*
|
|
213
|
+
* Note: Used by HDBreplication.
|
|
214
|
+
*/
|
|
215
|
+
getBinary(key, options) {
|
|
216
|
+
if (!this.store.isOpen()) return Promise.reject(/* @__PURE__ */ new Error("Database not open"));
|
|
217
|
+
return this.store.get(this.#context, key, true, this.store.getTxnId(options));
|
|
218
|
+
}
|
|
219
|
+
/**
|
|
220
|
+
* Synchronously retrieves the binary data for the given key.
|
|
221
|
+
*/
|
|
222
|
+
getBinarySync(key, options) {
|
|
223
|
+
if (!this.store.isOpen()) throw new Error("Database not open");
|
|
224
|
+
return this.store.getSync(this.#context, key, true, options);
|
|
225
|
+
}
|
|
226
|
+
/**
|
|
227
|
+
* Retrieves the binary data for the given key using a preallocated,
|
|
228
|
+
* reusable buffer. Data in the buffer is only valid until the next get
|
|
229
|
+
* operation (including cursor operations).
|
|
230
|
+
*
|
|
231
|
+
* Note: The reusable buffer slightly differs from a typical buffer:
|
|
232
|
+
* - `.length` is set to the size of the value
|
|
233
|
+
* - `.byteLength` is set to the size of the full allocated memory area for
|
|
234
|
+
* the buffer (usually much larger).
|
|
235
|
+
*/
|
|
236
|
+
getBinaryFast(key, options) {
|
|
237
|
+
if (!this.store.isOpen()) return Promise.reject(/* @__PURE__ */ new Error("Database not open"));
|
|
238
|
+
return this.store.get(this.#context, key, false, this.store.getTxnId(options));
|
|
239
|
+
}
|
|
240
|
+
/**
|
|
241
|
+
* Synchronously retrieves the binary data for the given key using a
|
|
242
|
+
* preallocated, reusable buffer. Data in the buffer is only valid until the
|
|
243
|
+
* next get operation (including cursor operations).
|
|
244
|
+
*/
|
|
245
|
+
getBinaryFastSync(key, options) {
|
|
246
|
+
if (!this.store.isOpen()) throw new Error("Database not open");
|
|
247
|
+
return this.store.getSync(this.#context, key, false, options);
|
|
248
|
+
}
|
|
249
|
+
/**
|
|
250
|
+
* Retrieves all keys within a range.
|
|
251
|
+
*/
|
|
252
|
+
getKeys(options) {
|
|
253
|
+
return this.store.getRange(this.#context, {
|
|
254
|
+
...options,
|
|
255
|
+
values: false
|
|
256
|
+
}).map((item) => item.key);
|
|
257
|
+
}
|
|
258
|
+
/**
|
|
259
|
+
* Retrieves the number of keys within a range.
|
|
260
|
+
*
|
|
261
|
+
* @param options - The range options.
|
|
262
|
+
* @returns The number of keys within the range.
|
|
263
|
+
*
|
|
264
|
+
* @example
|
|
265
|
+
* ```typescript
|
|
266
|
+
* const total = db.getKeysCount();
|
|
267
|
+
* const range = db.getKeysCount({ start: 'a', end: 'z' });
|
|
268
|
+
* ```
|
|
269
|
+
*/
|
|
270
|
+
getKeysCount(options) {
|
|
271
|
+
return this.store.getCount(this.#context, options);
|
|
272
|
+
}
|
|
273
|
+
/**
|
|
274
|
+
* Retrieves a range of keys and their values.
|
|
275
|
+
*
|
|
276
|
+
* @param options - The iterator options.
|
|
277
|
+
* @returns A range iterable.
|
|
278
|
+
*
|
|
279
|
+
* @example
|
|
280
|
+
* ```typescript
|
|
281
|
+
* for (const { key, value } of db.getRange()) {
|
|
282
|
+
* console.log({ key, value });
|
|
283
|
+
* }
|
|
284
|
+
*
|
|
285
|
+
* for (const { key, value } of db.getRange({ start: 'a', end: 'z' })) {
|
|
286
|
+
* console.log({ key, value });
|
|
287
|
+
* }
|
|
288
|
+
* ```
|
|
289
|
+
*/
|
|
290
|
+
getRange(options) {
|
|
291
|
+
return this.store.getRange(this.#context, options);
|
|
292
|
+
}
|
|
293
|
+
/**
|
|
294
|
+
* Synchronously retrieves the value for the given key, then returns the
|
|
295
|
+
* decoded value.
|
|
296
|
+
*/
|
|
297
|
+
getSync(key, options) {
|
|
298
|
+
if (this.store.decoderCopies) {
|
|
299
|
+
const bytes = this.getBinaryFastSync(key, options);
|
|
300
|
+
return bytes === void 0 ? void 0 : this.store.decodeValue(bytes);
|
|
301
|
+
}
|
|
302
|
+
if (this.store.encoding === "binary") return this.getBinarySync(key, options);
|
|
303
|
+
if (this.store.decoder) {
|
|
304
|
+
const result = this.getBinarySync(key, options);
|
|
305
|
+
return result ? this.store.decodeValue(result) : void 0;
|
|
306
|
+
}
|
|
307
|
+
if (!this.store.isOpen()) throw new Error("Database not open");
|
|
308
|
+
return this.store.decodeValue(this.store.getSync(this.#context, key, true, options));
|
|
309
|
+
}
|
|
310
|
+
/**
|
|
311
|
+
* Gets the number of listeners for the given key.
|
|
312
|
+
*
|
|
313
|
+
* @param event - The event name to get the listeners for.
|
|
314
|
+
* @returns The number of listeners for the given key.
|
|
315
|
+
*/
|
|
316
|
+
listeners(event) {
|
|
317
|
+
return this.store.db.listeners(event);
|
|
318
|
+
}
|
|
319
|
+
/**
|
|
320
|
+
* Notifies an event for the given key.
|
|
321
|
+
*
|
|
322
|
+
* @param event - The event name to emit the event for.
|
|
323
|
+
* @param args - The arguments to emit.
|
|
324
|
+
* @returns `true` if there were listeners, `false` otherwise.
|
|
325
|
+
*/
|
|
326
|
+
notify(event, ...args) {
|
|
327
|
+
return this.store.db.notify(event, args);
|
|
328
|
+
}
|
|
329
|
+
/**
|
|
330
|
+
* Alias for `removeListener()`.
|
|
331
|
+
*
|
|
332
|
+
* @param event - The event name to remove the listener for.
|
|
333
|
+
* @param callback - The callback to remove.
|
|
334
|
+
*/
|
|
335
|
+
off(event, callback) {
|
|
336
|
+
this.store.db.removeListener(event, callback);
|
|
337
|
+
return this;
|
|
338
|
+
}
|
|
339
|
+
/**
|
|
340
|
+
* Alias for `addListener()`.
|
|
341
|
+
*
|
|
342
|
+
* @param event - The event name to add the listener for.
|
|
343
|
+
* @param callback - The callback to add.
|
|
344
|
+
*/
|
|
345
|
+
on(event, callback) {
|
|
346
|
+
this.store.db.addListener(event, callback);
|
|
347
|
+
return this;
|
|
348
|
+
}
|
|
349
|
+
/**
|
|
350
|
+
* Adds a one-time listener, then automatically removes it.
|
|
351
|
+
*
|
|
352
|
+
* @param event - The event name to add the listener for.
|
|
353
|
+
* @param callback - The callback to add.
|
|
354
|
+
*/
|
|
355
|
+
once(event, callback) {
|
|
356
|
+
const wrapper = (...args) => {
|
|
357
|
+
this.removeListener(event, wrapper);
|
|
358
|
+
callback(...args);
|
|
359
|
+
};
|
|
360
|
+
this.store.db.addListener(event, wrapper);
|
|
361
|
+
return this;
|
|
362
|
+
}
|
|
363
|
+
/**
|
|
364
|
+
* Stores a value for the given key.
|
|
365
|
+
*
|
|
366
|
+
* @param key - The key to store the value for.
|
|
367
|
+
* @param value - The value to store.
|
|
368
|
+
* @param options - The put options.
|
|
369
|
+
* @returns The key and value.
|
|
370
|
+
*
|
|
371
|
+
* @example
|
|
372
|
+
* ```typescript
|
|
373
|
+
* await db.put('a', 'b');
|
|
374
|
+
* ```
|
|
375
|
+
*/
|
|
376
|
+
async put(key, value, options) {
|
|
377
|
+
return this.store.putSync(this.#context, key, value, options);
|
|
378
|
+
}
|
|
379
|
+
/**
|
|
380
|
+
* Synchronously stores a value for the given key.
|
|
381
|
+
*
|
|
382
|
+
* @param key - The key to store the value for.
|
|
383
|
+
* @param value - The value to store.
|
|
384
|
+
* @param options - The put options.
|
|
385
|
+
* @returns The key and value.
|
|
386
|
+
*
|
|
387
|
+
* @example
|
|
388
|
+
* ```typescript
|
|
389
|
+
* db.putSync('a', 'b');
|
|
390
|
+
* ```
|
|
391
|
+
*/
|
|
392
|
+
putSync(key, value, options) {
|
|
393
|
+
return this.store.putSync(this.#context, key, value, options);
|
|
394
|
+
}
|
|
395
|
+
/**
|
|
396
|
+
* Removes a value for the given key. If the key does not exist, it will
|
|
397
|
+
* not error.
|
|
398
|
+
*
|
|
399
|
+
* @param key - The key to remove the value for.
|
|
400
|
+
* @param options - The remove options.
|
|
401
|
+
* @returns The key and value.
|
|
402
|
+
*
|
|
403
|
+
* @example
|
|
404
|
+
* ```typescript
|
|
405
|
+
* await db.remove('a');
|
|
406
|
+
* ```
|
|
407
|
+
*/
|
|
408
|
+
async remove(key, options) {
|
|
409
|
+
return this.store.removeSync(this.#context, key, options);
|
|
410
|
+
}
|
|
411
|
+
/**
|
|
412
|
+
* Removes a value for the given key. If the key does not exist, it will
|
|
413
|
+
* not error.
|
|
414
|
+
*
|
|
415
|
+
* @param key - The key to remove the value for.
|
|
416
|
+
* @param options - The remove options.
|
|
417
|
+
* @returns The key and value.
|
|
418
|
+
*
|
|
419
|
+
* @example
|
|
420
|
+
* ```typescript
|
|
421
|
+
* db.removeSync('a');
|
|
422
|
+
* ```
|
|
423
|
+
*/
|
|
424
|
+
removeSync(key, options) {
|
|
425
|
+
return this.store.removeSync(this.#context, key, options);
|
|
426
|
+
}
|
|
427
|
+
/**
|
|
428
|
+
* Removes an event listener. You must specify the exact same callback that was
|
|
429
|
+
* used in `addListener()`.
|
|
430
|
+
*
|
|
431
|
+
* @param event - The event name to remove the listener for.
|
|
432
|
+
* @param callback - The callback to remove.
|
|
433
|
+
*/
|
|
434
|
+
removeListener(event, callback) {
|
|
435
|
+
return this.store.db.removeListener(event, callback);
|
|
436
|
+
}
|
|
437
|
+
/**
|
|
438
|
+
* Get or create a transaction log instance.
|
|
439
|
+
*
|
|
440
|
+
* @param name - The name of the transaction log.
|
|
441
|
+
* @returns The transaction log.
|
|
442
|
+
*/
|
|
443
|
+
useLog(name) {
|
|
444
|
+
return this.store.useLog(this.#context, name);
|
|
445
|
+
}
|
|
446
|
+
};
|
|
447
|
+
|
|
448
|
+
//#endregion
|
|
449
|
+
//#region src/dbi-iterator.ts
|
|
450
|
+
/**
|
|
451
|
+
* Wraps an iterator, namely the `NativeIterator` class, and decodes the key
|
|
452
|
+
* and value.
|
|
453
|
+
*/
|
|
454
|
+
var DBIterator = class {
|
|
455
|
+
iterator;
|
|
456
|
+
store;
|
|
457
|
+
#includeValues;
|
|
458
|
+
constructor(iterator, store, options) {
|
|
459
|
+
this.iterator = iterator;
|
|
460
|
+
this.store = store;
|
|
461
|
+
this.#includeValues = options?.values ?? true;
|
|
462
|
+
}
|
|
463
|
+
[Symbol.iterator]() {
|
|
464
|
+
return this;
|
|
465
|
+
}
|
|
466
|
+
next(...[_value]) {
|
|
467
|
+
const result = this.iterator.next();
|
|
468
|
+
if (result.done) return result;
|
|
469
|
+
const value = {};
|
|
470
|
+
value.key = this.store.decodeKey(result.value.key);
|
|
471
|
+
if (this.#includeValues) value.value = this.store.decodeValue(result.value.value);
|
|
472
|
+
return {
|
|
473
|
+
done: false,
|
|
474
|
+
value
|
|
475
|
+
};
|
|
476
|
+
}
|
|
477
|
+
return(value) {
|
|
478
|
+
if (this.iterator.return) return this.iterator.return(value);
|
|
479
|
+
return {
|
|
480
|
+
done: true,
|
|
481
|
+
value
|
|
482
|
+
};
|
|
483
|
+
}
|
|
484
|
+
throw(err) {
|
|
485
|
+
if (this.iterator.throw) return this.iterator.throw(err);
|
|
486
|
+
throw err;
|
|
487
|
+
}
|
|
488
|
+
};
|
|
489
|
+
|
|
490
|
+
//#endregion
|
|
491
|
+
//#region src/encoding.ts
|
|
492
|
+
/**
|
|
493
|
+
* Initializes the key encoder functions.
|
|
494
|
+
*
|
|
495
|
+
* @param keyEncoding - The key encoding to use.
|
|
496
|
+
* @param keyEncoder - The key encoder to use.
|
|
497
|
+
* @returns The key encoder.
|
|
498
|
+
*/
|
|
499
|
+
function initKeyEncoder(requestedKeyEncoding, keyEncoder) {
|
|
500
|
+
const keyEncoding = requestedKeyEncoding ?? "ordered-binary";
|
|
501
|
+
if (keyEncoder) {
|
|
502
|
+
const { readKey, writeKey } = keyEncoder;
|
|
503
|
+
if (!readKey || !writeKey) throw new Error("Custom key encoder must provide both readKey and writeKey");
|
|
504
|
+
return {
|
|
505
|
+
keyEncoding,
|
|
506
|
+
readKey,
|
|
507
|
+
writeKey
|
|
508
|
+
};
|
|
509
|
+
}
|
|
510
|
+
if (keyEncoding === "binary") return {
|
|
511
|
+
keyEncoding,
|
|
512
|
+
readKey(source, start, end) {
|
|
513
|
+
return Uint8Array.prototype.slice.call(source, start, end);
|
|
514
|
+
},
|
|
515
|
+
writeKey(key, target, start) {
|
|
516
|
+
const keyBuffer = key instanceof Buffer ? key : Buffer.from(String(key));
|
|
517
|
+
target.set(keyBuffer, start);
|
|
518
|
+
return keyBuffer.length + start;
|
|
519
|
+
}
|
|
520
|
+
};
|
|
521
|
+
if (keyEncoding === "uint32") return {
|
|
522
|
+
keyEncoding,
|
|
523
|
+
readKey(source, start, _end) {
|
|
524
|
+
if (!source.dataView) source.dataView = new DataView(source.buffer);
|
|
525
|
+
return source.dataView.getUint32(start, true);
|
|
526
|
+
},
|
|
527
|
+
writeKey(key, target, start) {
|
|
528
|
+
const keyNumber = Number(key);
|
|
529
|
+
if (isNaN(keyNumber)) throw new TypeError("Key is not a number");
|
|
530
|
+
target.dataView.setUint32(start, keyNumber, true);
|
|
531
|
+
return start + 4;
|
|
532
|
+
}
|
|
533
|
+
};
|
|
534
|
+
if (keyEncoding === "ordered-binary") return {
|
|
535
|
+
keyEncoding,
|
|
536
|
+
readKey: ordered_binary.readKey,
|
|
537
|
+
writeKey: ordered_binary.writeKey
|
|
538
|
+
};
|
|
539
|
+
throw new Error(`Invalid key encoding: ${keyEncoding}`);
|
|
540
|
+
}
|
|
541
|
+
/**
|
|
542
|
+
* Creates a fixed-size buffer with a data view, start, and end properties.
|
|
543
|
+
*
|
|
544
|
+
* Note: It uses `Buffer.allocUnsafe()` because it's the fastest by using
|
|
545
|
+
* Node.js's preallocated memory pool, though the memory is not zeroed out.
|
|
546
|
+
*
|
|
547
|
+
* @param size - The size of the buffer.
|
|
548
|
+
* @returns The buffer with a data view.
|
|
549
|
+
*/
|
|
550
|
+
function createFixedBuffer(size) {
|
|
551
|
+
const buffer = Buffer.allocUnsafeSlow(size);
|
|
552
|
+
buffer.dataView = new DataView(buffer.buffer);
|
|
553
|
+
buffer.start = 0;
|
|
554
|
+
buffer.end = 0;
|
|
555
|
+
return buffer;
|
|
556
|
+
}
|
|
557
|
+
|
|
558
|
+
//#endregion
|
|
559
|
+
//#region src/store.ts
|
|
560
|
+
const { ONLY_IF_IN_MEMORY_CACHE_FLAG, NOT_IN_MEMORY_CACHE_FLAG, ALWAYS_CREATE_NEW_BUFFER_FLAG } = constants;
|
|
561
|
+
const KEY_BUFFER_SIZE = 4096;
|
|
562
|
+
const KEY_BUFFER = createFixedBuffer(KEY_BUFFER_SIZE);
|
|
563
|
+
const VALUE_BUFFER = createFixedBuffer(64 * 1024);
|
|
564
|
+
const MAX_KEY_SIZE = 1024 * 1024;
|
|
565
|
+
const RESET_BUFFER_MODE = 1024;
|
|
566
|
+
const REUSE_BUFFER_MODE = 512;
|
|
567
|
+
const SAVE_BUFFER_SIZE = 8192;
|
|
568
|
+
/**
|
|
569
|
+
* A store wraps the `NativeDatabase` binding and database settings so that a
|
|
570
|
+
* single database instance can be shared between the main `RocksDatabase`
|
|
571
|
+
* instance and the `Transaction` instance.
|
|
572
|
+
*
|
|
573
|
+
* This store should not be shared between `RocksDatabase` instances.
|
|
574
|
+
*/
|
|
575
|
+
var Store = class {
|
|
576
|
+
/**
|
|
577
|
+
* The database instance.
|
|
578
|
+
*/
|
|
579
|
+
db;
|
|
580
|
+
/**
|
|
581
|
+
* The decoder instance. This is commonly the same as the `encoder`
|
|
582
|
+
* instance.
|
|
583
|
+
*/
|
|
584
|
+
decoder;
|
|
585
|
+
/**
|
|
586
|
+
* Whether the decoder copies the buffer when encoding values.
|
|
587
|
+
*/
|
|
588
|
+
decoderCopies = false;
|
|
589
|
+
/**
|
|
590
|
+
* Whether to disable the write ahead log.
|
|
591
|
+
*/
|
|
592
|
+
disableWAL;
|
|
593
|
+
/**
|
|
594
|
+
* Reusable buffer for encoding values using `writeKey()` when the custom
|
|
595
|
+
* encoder does not provide a `encode()` method.
|
|
596
|
+
*/
|
|
597
|
+
encodeBuffer;
|
|
598
|
+
/**
|
|
599
|
+
* The encoder instance.
|
|
600
|
+
*/
|
|
601
|
+
encoder;
|
|
602
|
+
/**
|
|
603
|
+
* The encoding used to encode values. Defaults to `'msgpack'` in
|
|
604
|
+
* `RocksDatabase.open()`.
|
|
605
|
+
*/
|
|
606
|
+
encoding;
|
|
607
|
+
/**
|
|
608
|
+
* Encoder specific option used to signal that the data should be frozen.
|
|
609
|
+
*/
|
|
610
|
+
freezeData;
|
|
611
|
+
/**
|
|
612
|
+
* Reusable buffer for encoding keys.
|
|
613
|
+
*/
|
|
614
|
+
keyBuffer;
|
|
615
|
+
/**
|
|
616
|
+
* The key encoding to use for keys. Defaults to `'ordered-binary'`.
|
|
617
|
+
*/
|
|
618
|
+
keyEncoding;
|
|
619
|
+
/**
|
|
620
|
+
* The maximum key size.
|
|
621
|
+
*/
|
|
622
|
+
maxKeySize;
|
|
623
|
+
/**
|
|
624
|
+
* The name of the store (e.g. the column family). Defaults to `'default'`.
|
|
625
|
+
*/
|
|
626
|
+
name;
|
|
627
|
+
/**
|
|
628
|
+
* Whether to disable the block cache.
|
|
629
|
+
*/
|
|
630
|
+
noBlockCache;
|
|
631
|
+
/**
|
|
632
|
+
* The number of threads to use for parallel operations. This is a RocksDB
|
|
633
|
+
* option.
|
|
634
|
+
*/
|
|
635
|
+
parallelismThreads;
|
|
636
|
+
/**
|
|
637
|
+
* The path to the database.
|
|
638
|
+
*/
|
|
639
|
+
path;
|
|
640
|
+
/**
|
|
641
|
+
* Whether to use pessimistic locking for transactions. When `true`,
|
|
642
|
+
* transactions will fail as soon as a conflict is detected. When `false`,
|
|
643
|
+
* transactions will only fail when `commit()` is called.
|
|
644
|
+
*/
|
|
645
|
+
pessimistic;
|
|
646
|
+
/**
|
|
647
|
+
* Encoder specific flag used to signal that the encoder should use a random
|
|
648
|
+
* access structure.
|
|
649
|
+
*/
|
|
650
|
+
randomAccessStructure;
|
|
651
|
+
/**
|
|
652
|
+
* The function used to encode keys.
|
|
653
|
+
*/
|
|
654
|
+
readKey;
|
|
655
|
+
/**
|
|
656
|
+
* The key used to store shared structures.
|
|
657
|
+
*/
|
|
658
|
+
sharedStructuresKey;
|
|
659
|
+
/**
|
|
660
|
+
* The threshold for the transaction log file's last modified time to be
|
|
661
|
+
* older than the retention period before it is rotated to the next sequence
|
|
662
|
+
* number. A threshold of 0 means ignore age check.
|
|
663
|
+
*/
|
|
664
|
+
transactionLogMaxAgeThreshold;
|
|
665
|
+
/**
|
|
666
|
+
* The maximum size of a transaction log before it is rotated to the next
|
|
667
|
+
* sequence number.
|
|
668
|
+
*/
|
|
669
|
+
transactionLogMaxSize;
|
|
670
|
+
/**
|
|
671
|
+
* A string containing the amount of time or the number of milliseconds to
|
|
672
|
+
* retain transaction logs before purging.
|
|
673
|
+
*
|
|
674
|
+
* @default '3d' (3 days)
|
|
675
|
+
*/
|
|
676
|
+
transactionLogRetention;
|
|
677
|
+
/**
|
|
678
|
+
* The path to the transaction logs directory.
|
|
679
|
+
*/
|
|
680
|
+
transactionLogsPath;
|
|
681
|
+
/**
|
|
682
|
+
* The function used to encode keys using the shared `keyBuffer`.
|
|
683
|
+
*/
|
|
684
|
+
writeKey;
|
|
685
|
+
/**
|
|
686
|
+
* Initializes the store with a new `NativeDatabase` instance.
|
|
687
|
+
*
|
|
688
|
+
* @param path - The path to the database.
|
|
689
|
+
* @param options - The options for the store.
|
|
690
|
+
*/
|
|
691
|
+
constructor(path, options) {
|
|
692
|
+
if (!path || typeof path !== "string") throw new TypeError("Invalid database path");
|
|
693
|
+
if (options !== void 0 && options !== null && typeof options !== "object") throw new TypeError("Database options must be an object");
|
|
694
|
+
const { keyEncoding, readKey, writeKey } = initKeyEncoder(options?.keyEncoding, options?.keyEncoder);
|
|
695
|
+
this.db = new NativeDatabase();
|
|
696
|
+
this.decoder = options?.decoder ?? null;
|
|
697
|
+
this.disableWAL = options?.disableWAL ?? false;
|
|
698
|
+
this.encodeBuffer = createFixedBuffer(SAVE_BUFFER_SIZE);
|
|
699
|
+
this.encoder = options?.encoder ?? null;
|
|
700
|
+
this.encoding = options?.encoding ?? null;
|
|
701
|
+
this.freezeData = options?.freezeData ?? false;
|
|
702
|
+
this.keyBuffer = KEY_BUFFER;
|
|
703
|
+
this.keyEncoding = keyEncoding;
|
|
704
|
+
this.maxKeySize = options?.maxKeySize ?? MAX_KEY_SIZE;
|
|
705
|
+
this.name = options?.name ?? "default";
|
|
706
|
+
this.noBlockCache = options?.noBlockCache;
|
|
707
|
+
this.parallelismThreads = options?.parallelismThreads ?? 1;
|
|
708
|
+
this.path = path;
|
|
709
|
+
this.pessimistic = options?.pessimistic ?? false;
|
|
710
|
+
this.randomAccessStructure = options?.randomAccessStructure ?? false;
|
|
711
|
+
this.readKey = readKey;
|
|
712
|
+
this.sharedStructuresKey = options?.sharedStructuresKey;
|
|
713
|
+
this.transactionLogMaxAgeThreshold = options?.transactionLogMaxAgeThreshold;
|
|
714
|
+
this.transactionLogMaxSize = options?.transactionLogMaxSize;
|
|
715
|
+
this.transactionLogRetention = options?.transactionLogRetention;
|
|
716
|
+
this.transactionLogsPath = options?.transactionLogsPath;
|
|
717
|
+
this.writeKey = writeKey;
|
|
718
|
+
}
|
|
719
|
+
/**
|
|
720
|
+
* Closes the database.
|
|
721
|
+
*/
|
|
722
|
+
close() {
|
|
723
|
+
this.db.close();
|
|
724
|
+
}
|
|
725
|
+
/**
|
|
726
|
+
* Decodes a key from the database.
|
|
727
|
+
*
|
|
728
|
+
* @param key - The key to decode.
|
|
729
|
+
* @returns The decoded key.
|
|
730
|
+
*/
|
|
731
|
+
decodeKey(key) {
|
|
732
|
+
return this.readKey(key, 0, key.length);
|
|
733
|
+
}
|
|
734
|
+
/**
|
|
735
|
+
* Decodes a value from the database.
|
|
736
|
+
*
|
|
737
|
+
* @param value - The value to decode.
|
|
738
|
+
* @returns The decoded value.
|
|
739
|
+
*/
|
|
740
|
+
decodeValue(value) {
|
|
741
|
+
if (value?.length > 0 && typeof this.decoder?.decode === "function") return this.decoder.decode(value, { end: value.end });
|
|
742
|
+
return value;
|
|
743
|
+
}
|
|
744
|
+
/**
|
|
745
|
+
* Encodes a key for the database.
|
|
746
|
+
*
|
|
747
|
+
* @param key - The key to encode.
|
|
748
|
+
* @returns The encoded key.
|
|
749
|
+
*/
|
|
750
|
+
encodeKey(key) {
|
|
751
|
+
if (key === void 0) throw new Error("Key is required");
|
|
752
|
+
const bytesWritten = this.writeKey(key, this.keyBuffer, 0);
|
|
753
|
+
if (bytesWritten === 0) throw new Error("Zero length key is not allowed");
|
|
754
|
+
this.keyBuffer.end = bytesWritten;
|
|
755
|
+
return this.keyBuffer;
|
|
756
|
+
}
|
|
757
|
+
/**
|
|
758
|
+
* Encodes a value for the database.
|
|
759
|
+
*
|
|
760
|
+
* @param value - The value to encode.
|
|
761
|
+
* @returns The encoded value.
|
|
762
|
+
*/
|
|
763
|
+
encodeValue(value) {
|
|
764
|
+
if (value && value["binary-data"]) return value["binary-data"];
|
|
765
|
+
if (typeof this.encoder?.encode === "function") {
|
|
766
|
+
if (this.encoder.copyBuffers) return this.encoder.encode(value, REUSE_BUFFER_MODE | RESET_BUFFER_MODE);
|
|
767
|
+
const valueBuffer = this.encoder.encode(value);
|
|
768
|
+
if (typeof valueBuffer === "string") return Buffer.from(valueBuffer);
|
|
769
|
+
return valueBuffer;
|
|
770
|
+
}
|
|
771
|
+
if (typeof value === "string") return Buffer.from(value);
|
|
772
|
+
if (value instanceof Uint8Array) return value;
|
|
773
|
+
throw new Error(`Invalid value put in database (${typeof value}), consider using an encoder`);
|
|
774
|
+
}
|
|
775
|
+
get(context, key, alwaysCreateNewBuffer = false, txnId) {
|
|
776
|
+
const keyParam = getKeyParam(this.encodeKey(key));
|
|
777
|
+
let flags = 0;
|
|
778
|
+
if (alwaysCreateNewBuffer) flags |= ALWAYS_CREATE_NEW_BUFFER_FLAG;
|
|
779
|
+
const result = context.getSync(keyParam, flags | ONLY_IF_IN_MEMORY_CACHE_FLAG, txnId);
|
|
780
|
+
if (typeof result === "number") {
|
|
781
|
+
if (result === NOT_IN_MEMORY_CACHE_FLAG) return new Promise((resolve, reject) => {
|
|
782
|
+
context.get(keyParam, resolve, reject, txnId);
|
|
783
|
+
});
|
|
784
|
+
VALUE_BUFFER.end = result;
|
|
785
|
+
return VALUE_BUFFER;
|
|
786
|
+
}
|
|
787
|
+
return result;
|
|
788
|
+
}
|
|
789
|
+
getCount(context, options) {
|
|
790
|
+
options = { ...options };
|
|
791
|
+
if (options?.start !== void 0) {
|
|
792
|
+
const start = this.encodeKey(options.start);
|
|
793
|
+
options.start = Buffer.from(start.subarray(start.start, start.end));
|
|
794
|
+
}
|
|
795
|
+
if (options?.end !== void 0) {
|
|
796
|
+
const end = this.encodeKey(options.end);
|
|
797
|
+
options.end = Buffer.from(end.subarray(end.start, end.end));
|
|
798
|
+
}
|
|
799
|
+
return context.getCount(options, this.getTxnId(options));
|
|
800
|
+
}
|
|
801
|
+
getRange(context, options) {
|
|
802
|
+
if (!this.db.opened) throw new Error("Database not open");
|
|
803
|
+
options = { ...options };
|
|
804
|
+
const unencodedStartKey = options.key ?? options.start;
|
|
805
|
+
if (unencodedStartKey !== void 0) {
|
|
806
|
+
const start = this.encodeKey(unencodedStartKey);
|
|
807
|
+
options.start = Buffer.from(start.subarray(start.start, start.end));
|
|
808
|
+
}
|
|
809
|
+
if (options.key !== void 0) {
|
|
810
|
+
options.end = options.start;
|
|
811
|
+
options.inclusiveEnd = true;
|
|
812
|
+
} else if (options.end !== void 0) {
|
|
813
|
+
const end = this.encodeKey(options.end);
|
|
814
|
+
options.end = Buffer.from(end.subarray(end.start, end.end));
|
|
815
|
+
}
|
|
816
|
+
if (options.reverse) {
|
|
817
|
+
const start = options.start;
|
|
818
|
+
options.start = options.end;
|
|
819
|
+
options.end = start;
|
|
820
|
+
options.exclusiveStart = options.exclusiveStart ?? true;
|
|
821
|
+
options.inclusiveEnd = options.inclusiveEnd ?? true;
|
|
822
|
+
}
|
|
823
|
+
return new _harperfast_extended_iterable.ExtendedIterable(new DBIterator(new NativeIterator(context, options), this, options));
|
|
824
|
+
}
|
|
825
|
+
getSync(context, key, alwaysCreateNewBuffer = false, options) {
|
|
826
|
+
const keyParam = getKeyParam(this.encodeKey(key));
|
|
827
|
+
let flags = 0;
|
|
828
|
+
if (alwaysCreateNewBuffer) flags |= ALWAYS_CREATE_NEW_BUFFER_FLAG;
|
|
829
|
+
const result = context.getSync(keyParam, flags, this.getTxnId(options));
|
|
830
|
+
if (typeof result === "number") {
|
|
831
|
+
VALUE_BUFFER.end = result;
|
|
832
|
+
return VALUE_BUFFER;
|
|
833
|
+
}
|
|
834
|
+
return result;
|
|
835
|
+
}
|
|
836
|
+
/**
|
|
837
|
+
* Checks if the data method options object contains a transaction ID and
|
|
838
|
+
* returns it.
|
|
839
|
+
*/
|
|
840
|
+
getTxnId(options) {
|
|
841
|
+
let txnId;
|
|
842
|
+
if (options?.transaction) {
|
|
843
|
+
txnId = options.transaction.id;
|
|
844
|
+
if (txnId === void 0) throw new TypeError("Invalid transaction");
|
|
845
|
+
}
|
|
846
|
+
return txnId;
|
|
847
|
+
}
|
|
848
|
+
/**
|
|
849
|
+
* Gets or creates a buffer that can be shared across worker threads.
|
|
850
|
+
*
|
|
851
|
+
* @param key - The key to get or create the buffer for.
|
|
852
|
+
* @param defaultBuffer - The default buffer to copy and use if the buffer
|
|
853
|
+
* does not exist.
|
|
854
|
+
* @param [options] - The options for the buffer.
|
|
855
|
+
* @param [options.callback] - A optional callback is called when `notify()`
|
|
856
|
+
* on the returned buffer is called.
|
|
857
|
+
* @returns An `ArrayBuffer` that is internally backed by a rocksdb-js
|
|
858
|
+
* managed buffer. The buffer also has `notify()` and `cancel()` methods
|
|
859
|
+
* that can be used to notify the specified `options.callback`.
|
|
860
|
+
*/
|
|
861
|
+
getUserSharedBuffer(key, defaultBuffer, options) {
|
|
862
|
+
const encodedKey = this.encodeKey(key);
|
|
863
|
+
if (options !== void 0 && typeof options !== "object") throw new TypeError("Options must be an object");
|
|
864
|
+
const buffer = this.db.getUserSharedBuffer(encodedKey, defaultBuffer, options?.callback);
|
|
865
|
+
buffer.notify = (...args) => {
|
|
866
|
+
return this.db.notify(this.encodeKey(key), args);
|
|
867
|
+
};
|
|
868
|
+
buffer.cancel = () => {
|
|
869
|
+
if (options?.callback) this.db.removeListener(this.encodeKey(key), options.callback);
|
|
870
|
+
};
|
|
871
|
+
return buffer;
|
|
872
|
+
}
|
|
873
|
+
/**
|
|
874
|
+
* Checks if a lock exists.
|
|
875
|
+
* @param key The lock key.
|
|
876
|
+
* @returns `true` if the lock exists, `false` otherwise
|
|
877
|
+
*/
|
|
878
|
+
hasLock(key) {
|
|
879
|
+
return this.db.hasLock(this.encodeKey(key));
|
|
880
|
+
}
|
|
881
|
+
/**
|
|
882
|
+
* Checks if the database is open.
|
|
883
|
+
*
|
|
884
|
+
* @returns `true` if the database is open, `false` otherwise.
|
|
885
|
+
*/
|
|
886
|
+
isOpen() {
|
|
887
|
+
return this.db.opened;
|
|
888
|
+
}
|
|
889
|
+
/**
|
|
890
|
+
* Lists all transaction log names.
|
|
891
|
+
*
|
|
892
|
+
* @returns an array of transaction log names.
|
|
893
|
+
*/
|
|
894
|
+
listLogs() {
|
|
895
|
+
return this.db.listLogs();
|
|
896
|
+
}
|
|
897
|
+
/**
|
|
898
|
+
* Opens the database. This must be called before any database operations
|
|
899
|
+
* are performed.
|
|
900
|
+
*/
|
|
901
|
+
open() {
|
|
902
|
+
if (this.db.opened) return true;
|
|
903
|
+
this.db.open(this.path, {
|
|
904
|
+
disableWAL: this.disableWAL,
|
|
905
|
+
mode: this.pessimistic ? "pessimistic" : "optimistic",
|
|
906
|
+
name: this.name,
|
|
907
|
+
noBlockCache: this.noBlockCache,
|
|
908
|
+
parallelismThreads: this.parallelismThreads,
|
|
909
|
+
transactionLogMaxAgeThreshold: this.transactionLogMaxAgeThreshold,
|
|
910
|
+
transactionLogMaxSize: this.transactionLogMaxSize,
|
|
911
|
+
transactionLogRetentionMs: this.transactionLogRetention ? parseDuration(this.transactionLogRetention) : void 0,
|
|
912
|
+
transactionLogsPath: this.transactionLogsPath
|
|
913
|
+
});
|
|
914
|
+
return false;
|
|
915
|
+
}
|
|
916
|
+
putSync(context, key, value, options) {
|
|
917
|
+
if (!this.db.opened) throw new Error("Database not open");
|
|
918
|
+
const valueBuffer = this.encodeValue(value);
|
|
919
|
+
context.putSync(this.encodeKey(key), valueBuffer, this.getTxnId(options));
|
|
920
|
+
}
|
|
921
|
+
removeSync(context, key, options) {
|
|
922
|
+
if (!this.db.opened) throw new Error("Database not open");
|
|
923
|
+
context.removeSync(this.encodeKey(key), this.getTxnId(options));
|
|
924
|
+
}
|
|
925
|
+
/**
|
|
926
|
+
* Attempts to acquire a lock for a given key. If the lock is available,
|
|
927
|
+
* the function returns `true` and the optional callback is never called.
|
|
928
|
+
* If the lock is not available, the function returns `false` and the
|
|
929
|
+
* callback is queued until the lock is released.
|
|
930
|
+
*
|
|
931
|
+
* @param key - The key to lock.
|
|
932
|
+
* @param onUnlocked - A callback to call when the lock is released.
|
|
933
|
+
* @returns `true` if the lock was acquired, `false` otherwise.
|
|
934
|
+
*/
|
|
935
|
+
tryLock(key, onUnlocked) {
|
|
936
|
+
if (onUnlocked !== void 0 && typeof onUnlocked !== "function") throw new TypeError("Callback must be a function");
|
|
937
|
+
return this.db.tryLock(this.encodeKey(key), onUnlocked);
|
|
938
|
+
}
|
|
939
|
+
/**
|
|
940
|
+
* Releases the lock on the given key and calls any queued `onUnlocked`
|
|
941
|
+
* callback handlers.
|
|
942
|
+
*
|
|
943
|
+
* @param key - The key to unlock.
|
|
944
|
+
*/
|
|
945
|
+
unlock(key) {
|
|
946
|
+
return this.db.unlock(this.encodeKey(key));
|
|
947
|
+
}
|
|
948
|
+
/**
|
|
949
|
+
* Gets or creates a transaction log instance.
|
|
950
|
+
*
|
|
951
|
+
* @param context - The context to use for the transaction log.
|
|
952
|
+
* @param name - The name of the transaction log.
|
|
953
|
+
* @returns The transaction log.
|
|
954
|
+
*/
|
|
955
|
+
useLog(context, name) {
|
|
956
|
+
if (typeof name !== "string" && typeof name !== "number") throw new TypeError("Log name must be a string or number");
|
|
957
|
+
return context.useLog(String(name));
|
|
958
|
+
}
|
|
959
|
+
/**
|
|
960
|
+
* Acquires a lock on the given key and calls the callback.
|
|
961
|
+
*
|
|
962
|
+
* @param key - The key to lock.
|
|
963
|
+
* @param callback - The callback to call when the lock is acquired.
|
|
964
|
+
* @returns A promise that resolves when the lock is acquired.
|
|
965
|
+
*/
|
|
966
|
+
withLock(key, callback) {
|
|
967
|
+
if (typeof callback !== "function") return Promise.reject(/* @__PURE__ */ new TypeError("Callback must be a function"));
|
|
968
|
+
return this.db.withLock(this.encodeKey(key), callback);
|
|
969
|
+
}
|
|
970
|
+
};
|
|
971
|
+
/**
|
|
972
|
+
* Ensure that they key has been copied into our shared buffer, and return the ending position
|
|
973
|
+
* @param keyBuffer
|
|
974
|
+
*/
|
|
975
|
+
function getKeyParam(keyBuffer) {
|
|
976
|
+
if (keyBuffer.buffer === KEY_BUFFER.buffer) {
|
|
977
|
+
if (keyBuffer.end >= 0) return keyBuffer.end;
|
|
978
|
+
if (keyBuffer.byteOffset === 0) return keyBuffer.byteLength;
|
|
979
|
+
}
|
|
980
|
+
if (keyBuffer.length > KEY_BUFFER.length) return keyBuffer;
|
|
981
|
+
KEY_BUFFER.set(keyBuffer);
|
|
982
|
+
return keyBuffer.length;
|
|
983
|
+
}
|
|
984
|
+
|
|
985
|
+
//#endregion
|
|
986
|
+
//#region src/transaction.ts
|
|
987
|
+
/**
|
|
988
|
+
* Provides transaction level operations to a transaction callback.
|
|
989
|
+
*/
|
|
990
|
+
var Transaction = class extends DBI {
|
|
991
|
+
#txn;
|
|
992
|
+
/**
|
|
993
|
+
* Create a new transaction.
|
|
994
|
+
*
|
|
995
|
+
* @param store - The base store interface for this transaction.
|
|
996
|
+
* @param options - The options for the transaction.
|
|
997
|
+
*/
|
|
998
|
+
constructor(store, options) {
|
|
999
|
+
const txn = new NativeTransaction(store.db, options);
|
|
1000
|
+
super(store, txn);
|
|
1001
|
+
this.#txn = txn;
|
|
1002
|
+
}
|
|
1003
|
+
/**
|
|
1004
|
+
* Abort the transaction.
|
|
1005
|
+
*/
|
|
1006
|
+
abort() {
|
|
1007
|
+
this.#txn.abort();
|
|
1008
|
+
}
|
|
1009
|
+
/**
|
|
1010
|
+
* Commit the transaction.
|
|
1011
|
+
*/
|
|
1012
|
+
async commit() {
|
|
1013
|
+
try {
|
|
1014
|
+
await new Promise((resolve, reject) => {
|
|
1015
|
+
this.notify("beforecommit");
|
|
1016
|
+
this.#txn.commit(resolve, reject);
|
|
1017
|
+
});
|
|
1018
|
+
} finally {
|
|
1019
|
+
this.notify("aftercommit", {
|
|
1020
|
+
next: null,
|
|
1021
|
+
last: null,
|
|
1022
|
+
txnId: this.#txn.id
|
|
1023
|
+
});
|
|
1024
|
+
}
|
|
1025
|
+
}
|
|
1026
|
+
/**
|
|
1027
|
+
* Commit the transaction synchronously.
|
|
1028
|
+
*/
|
|
1029
|
+
commitSync() {
|
|
1030
|
+
try {
|
|
1031
|
+
this.notify("beforecommit");
|
|
1032
|
+
this.#txn.commitSync();
|
|
1033
|
+
} finally {
|
|
1034
|
+
this.notify("aftercommit", {
|
|
1035
|
+
next: null,
|
|
1036
|
+
last: null,
|
|
1037
|
+
txnId: this.#txn.id
|
|
1038
|
+
});
|
|
1039
|
+
}
|
|
1040
|
+
}
|
|
1041
|
+
/**
|
|
1042
|
+
* Returns the transaction start timestamp in seconds. Defaults to the time at which
|
|
1043
|
+
* the transaction was created.
|
|
1044
|
+
*
|
|
1045
|
+
* @returns The transaction start timestamp in seconds.
|
|
1046
|
+
*/
|
|
1047
|
+
getTimestamp() {
|
|
1048
|
+
return this.#txn.getTimestamp();
|
|
1049
|
+
}
|
|
1050
|
+
/**
|
|
1051
|
+
* Get the transaction id.
|
|
1052
|
+
*/
|
|
1053
|
+
get id() {
|
|
1054
|
+
return this.#txn.id;
|
|
1055
|
+
}
|
|
1056
|
+
/**
|
|
1057
|
+
* Set the transaction start timestamp in seconds.
|
|
1058
|
+
*
|
|
1059
|
+
* @param timestamp - The timestamp to set in seconds.
|
|
1060
|
+
*/
|
|
1061
|
+
setTimestamp(timestamp) {
|
|
1062
|
+
this.#txn.setTimestamp(timestamp);
|
|
1063
|
+
}
|
|
1064
|
+
};
|
|
1065
|
+
|
|
1066
|
+
//#endregion
|
|
1067
|
+
//#region src/database.ts
|
|
1068
|
+
/**
|
|
1069
|
+
* The main class for interacting with a RocksDB database.
|
|
1070
|
+
*
|
|
1071
|
+
* Before using this class, you must open the database first.
|
|
1072
|
+
*
|
|
1073
|
+
* @example
|
|
1074
|
+
* ```typescript
|
|
1075
|
+
* const db = RocksDatabase.open('/path/to/database');
|
|
1076
|
+
* await db.put('key', 'value');
|
|
1077
|
+
* const value = await db.get('key');
|
|
1078
|
+
* db.close();
|
|
1079
|
+
* ```
|
|
1080
|
+
*/
|
|
1081
|
+
var RocksDatabase = class RocksDatabase extends DBI {
|
|
1082
|
+
constructor(pathOrStore, options) {
|
|
1083
|
+
if (typeof pathOrStore === "string") super(new Store(pathOrStore, options));
|
|
1084
|
+
else if (pathOrStore instanceof Store) super(pathOrStore);
|
|
1085
|
+
else throw new TypeError("Invalid database path or store");
|
|
1086
|
+
}
|
|
1087
|
+
/**
|
|
1088
|
+
* Removes all data from the database asynchronously.
|
|
1089
|
+
*
|
|
1090
|
+
* @example
|
|
1091
|
+
* ```typescript
|
|
1092
|
+
* const db = RocksDatabase.open('/path/to/database');
|
|
1093
|
+
* await db.clear();
|
|
1094
|
+
* ```
|
|
1095
|
+
*/
|
|
1096
|
+
clear() {
|
|
1097
|
+
if (!this.store.db.opened) return Promise.reject(/* @__PURE__ */ new Error("Database not open"));
|
|
1098
|
+
if (this.store.encoder?.structures !== void 0) this.store.encoder.structures = [];
|
|
1099
|
+
return new Promise((resolve, reject) => {
|
|
1100
|
+
this.store.db.clear(resolve, reject);
|
|
1101
|
+
});
|
|
1102
|
+
}
|
|
1103
|
+
/**
|
|
1104
|
+
* Removes all entries from the database synchronously.
|
|
1105
|
+
*
|
|
1106
|
+
* @example
|
|
1107
|
+
* ```typescript
|
|
1108
|
+
* const db = RocksDatabase.open('/path/to/database');
|
|
1109
|
+
* db.clearSync();
|
|
1110
|
+
* ```
|
|
1111
|
+
*/
|
|
1112
|
+
clearSync() {
|
|
1113
|
+
if (!this.store.db.opened) throw new Error("Database not open");
|
|
1114
|
+
if (this.store.encoder?.structures !== void 0) this.store.encoder.structures = [];
|
|
1115
|
+
return this.store.db.clearSync();
|
|
1116
|
+
}
|
|
1117
|
+
/**
|
|
1118
|
+
* Closes the database.
|
|
1119
|
+
*
|
|
1120
|
+
* @example
|
|
1121
|
+
* ```typescript
|
|
1122
|
+
* const db = RocksDatabase.open('/path/to/database');
|
|
1123
|
+
* db.close();
|
|
1124
|
+
* ```
|
|
1125
|
+
*/
|
|
1126
|
+
close() {
|
|
1127
|
+
this.store.close();
|
|
1128
|
+
}
|
|
1129
|
+
/**
|
|
1130
|
+
* Set global database settings.
|
|
1131
|
+
*
|
|
1132
|
+
* @param options - The options for the database.
|
|
1133
|
+
*
|
|
1134
|
+
* @example
|
|
1135
|
+
* ```typescript
|
|
1136
|
+
* RocksDatabase.config({ blockCacheSize: 1024 * 1024 });
|
|
1137
|
+
* ```
|
|
1138
|
+
*/
|
|
1139
|
+
static config(options) {
|
|
1140
|
+
config(options);
|
|
1141
|
+
}
|
|
1142
|
+
async drop() {
|
|
1143
|
+
if (!this.store.db.opened) return Promise.reject(/* @__PURE__ */ new Error("Database not open"));
|
|
1144
|
+
return new Promise((resolve, reject) => {
|
|
1145
|
+
this.store.db.drop(resolve, reject);
|
|
1146
|
+
});
|
|
1147
|
+
}
|
|
1148
|
+
dropSync() {
|
|
1149
|
+
if (!this.store.db.opened) throw new Error("Database not open");
|
|
1150
|
+
return this.store.db.dropSync();
|
|
1151
|
+
}
|
|
1152
|
+
get encoder() {
|
|
1153
|
+
return this.store.encoder;
|
|
1154
|
+
}
|
|
1155
|
+
/**
|
|
1156
|
+
* Returns the current timestamp as a monotonically increasing timestamp in
|
|
1157
|
+
* milliseconds represented as a decimal number.
|
|
1158
|
+
*
|
|
1159
|
+
* @returns The current monotonic timestamp in milliseconds.
|
|
1160
|
+
*/
|
|
1161
|
+
getMonotonicTimestamp() {
|
|
1162
|
+
return this.store.db.getMonotonicTimestamp();
|
|
1163
|
+
}
|
|
1164
|
+
/**
|
|
1165
|
+
* Returns a number representing a unix timestamp of the oldest unreleased
|
|
1166
|
+
* snapshot.
|
|
1167
|
+
*
|
|
1168
|
+
* @returns The oldest snapshot timestamp.
|
|
1169
|
+
*/
|
|
1170
|
+
getOldestSnapshotTimestamp() {
|
|
1171
|
+
return this.store.db.getOldestSnapshotTimestamp();
|
|
1172
|
+
}
|
|
1173
|
+
/**
|
|
1174
|
+
* Gets a RocksDB database property as a string.
|
|
1175
|
+
*
|
|
1176
|
+
* @param propertyName - The name of the property to retrieve (e.g., 'rocksdb.levelstats').
|
|
1177
|
+
* @returns The property value as a string.
|
|
1178
|
+
*
|
|
1179
|
+
* @example
|
|
1180
|
+
* ```typescript
|
|
1181
|
+
* const db = RocksDatabase.open('/path/to/database');
|
|
1182
|
+
* const levelStats = db.getDBProperty('rocksdb.levelstats');
|
|
1183
|
+
* const stats = db.getDBProperty('rocksdb.stats');
|
|
1184
|
+
* ```
|
|
1185
|
+
*/
|
|
1186
|
+
getDBProperty(propertyName) {
|
|
1187
|
+
return this.store.db.getDBProperty(propertyName);
|
|
1188
|
+
}
|
|
1189
|
+
/**
|
|
1190
|
+
* Gets a RocksDB database property as an integer.
|
|
1191
|
+
*
|
|
1192
|
+
* @param propertyName - The name of the property to retrieve (e.g., 'rocksdb.num-blob-files').
|
|
1193
|
+
* @returns The property value as a number.
|
|
1194
|
+
*
|
|
1195
|
+
* @example
|
|
1196
|
+
* ```typescript
|
|
1197
|
+
* const db = RocksDatabase.open('/path/to/database');
|
|
1198
|
+
* const blobFiles = db.getDBIntProperty('rocksdb.num-blob-files');
|
|
1199
|
+
* const numKeys = db.getDBIntProperty('rocksdb.estimate-num-keys');
|
|
1200
|
+
* ```
|
|
1201
|
+
*/
|
|
1202
|
+
getDBIntProperty(propertyName) {
|
|
1203
|
+
return this.store.db.getDBIntProperty(propertyName);
|
|
1204
|
+
}
|
|
1205
|
+
/**
|
|
1206
|
+
* Flushes the underlying database by performing a commit or clearing any buffered operations.
|
|
1207
|
+
*
|
|
1208
|
+
* @return {void} Does not return a value.
|
|
1209
|
+
*/
|
|
1210
|
+
flush() {
|
|
1211
|
+
return new Promise((resolve, reject) => this.store.db.flush(resolve, reject));
|
|
1212
|
+
}
|
|
1213
|
+
/**
|
|
1214
|
+
* Synchronously flushes the underlying database by performing a commit or clearing any buffered operations.
|
|
1215
|
+
*
|
|
1216
|
+
* @return {void} Does not return a value.
|
|
1217
|
+
*/
|
|
1218
|
+
flushSync() {
|
|
1219
|
+
return this.store.db.flushSync();
|
|
1220
|
+
}
|
|
1221
|
+
getStats() {
|
|
1222
|
+
return {
|
|
1223
|
+
free: {},
|
|
1224
|
+
root: {}
|
|
1225
|
+
};
|
|
1226
|
+
}
|
|
1227
|
+
/**
|
|
1228
|
+
* Gets or creates a buffer that can be shared across worker threads.
|
|
1229
|
+
*
|
|
1230
|
+
* @param key - The key to get or create the buffer for.
|
|
1231
|
+
* @param defaultBuffer - The default buffer to copy and use if the buffer
|
|
1232
|
+
* does not exist.
|
|
1233
|
+
* @param [options] - The options for the buffer.
|
|
1234
|
+
* @param [options.callback] - A optional callback that receives
|
|
1235
|
+
* key-specific events.
|
|
1236
|
+
* @returns An `ArrayBuffer` that is internally backed by a shared block of
|
|
1237
|
+
* memory. The buffer also has `notify()` and `cancel()` methods that can be
|
|
1238
|
+
* used to notify the specified `options.callback`.
|
|
1239
|
+
*
|
|
1240
|
+
* @example
|
|
1241
|
+
* ```typescript
|
|
1242
|
+
* const db = RocksDatabase.open('/path/to/database');
|
|
1243
|
+
* const buffer = db.getUserSharedBuffer('foo', new ArrayBuffer(10));
|
|
1244
|
+
*/
|
|
1245
|
+
getUserSharedBuffer(key, defaultBuffer, options) {
|
|
1246
|
+
return this.store.getUserSharedBuffer(key, defaultBuffer, options);
|
|
1247
|
+
}
|
|
1248
|
+
/**
|
|
1249
|
+
* Returns whether the database has a lock for the given key.
|
|
1250
|
+
*
|
|
1251
|
+
* @param key - The key to check.
|
|
1252
|
+
* @returns `true` if the database has a lock for the given key, `false`
|
|
1253
|
+
* otherwise.
|
|
1254
|
+
*
|
|
1255
|
+
* @example
|
|
1256
|
+
* ```typescript
|
|
1257
|
+
* const db = RocksDatabase.open('/path/to/database');
|
|
1258
|
+
* db.hasLock('foo'); // false
|
|
1259
|
+
* db.tryLock('foo', () => {});
|
|
1260
|
+
* db.hasLock('foo'); // true
|
|
1261
|
+
* ```
|
|
1262
|
+
*/
|
|
1263
|
+
hasLock(key) {
|
|
1264
|
+
return this.store.hasLock(key);
|
|
1265
|
+
}
|
|
1266
|
+
async ifNoExists(_key) {}
|
|
1267
|
+
/**
|
|
1268
|
+
* Returns whether the database is open.
|
|
1269
|
+
*
|
|
1270
|
+
* @returns `true` if the database is open, `false` otherwise.
|
|
1271
|
+
*/
|
|
1272
|
+
isOpen() {
|
|
1273
|
+
return this.store.isOpen();
|
|
1274
|
+
}
|
|
1275
|
+
/**
|
|
1276
|
+
* Lists all transaction log names.
|
|
1277
|
+
*
|
|
1278
|
+
* @returns an array of transaction log names.
|
|
1279
|
+
*/
|
|
1280
|
+
listLogs() {
|
|
1281
|
+
return this.store.listLogs();
|
|
1282
|
+
}
|
|
1283
|
+
/**
|
|
1284
|
+
* Sugar method for opening a database.
|
|
1285
|
+
*
|
|
1286
|
+
* @param pathOrStore - The filesystem path to the database or a custom store.
|
|
1287
|
+
* @param options - The options for the database.
|
|
1288
|
+
* @returns A new RocksDatabase instance.
|
|
1289
|
+
*
|
|
1290
|
+
* @example
|
|
1291
|
+
* ```typescript
|
|
1292
|
+
* const db = RocksDatabase.open('/path/to/database');
|
|
1293
|
+
* ```
|
|
1294
|
+
*/
|
|
1295
|
+
static open(pathOrStore, options) {
|
|
1296
|
+
return new RocksDatabase(pathOrStore, options).open();
|
|
1297
|
+
}
|
|
1298
|
+
/**
|
|
1299
|
+
* Opens the database. This function returns immediately if the database is
|
|
1300
|
+
* already open.
|
|
1301
|
+
*
|
|
1302
|
+
* @returns A new RocksDatabase instance.
|
|
1303
|
+
*
|
|
1304
|
+
* @example
|
|
1305
|
+
* ```typescript
|
|
1306
|
+
* const db = new RocksDatabase('/path/to/database');
|
|
1307
|
+
* db.open();
|
|
1308
|
+
* ```
|
|
1309
|
+
*/
|
|
1310
|
+
open() {
|
|
1311
|
+
const { store } = this;
|
|
1312
|
+
if (store.open()) return this;
|
|
1313
|
+
store.db.setDefaultValueBuffer(VALUE_BUFFER);
|
|
1314
|
+
store.db.setDefaultKeyBuffer(KEY_BUFFER);
|
|
1315
|
+
/**
|
|
1316
|
+
* The encoder initialization precedence is:
|
|
1317
|
+
* 1. encoder.Encoder
|
|
1318
|
+
* 2. encoder.encode()
|
|
1319
|
+
* 3. encoding === `msgpack`
|
|
1320
|
+
* 4. encoding === `ordered-binary`
|
|
1321
|
+
* 5. encoder.writeKey()
|
|
1322
|
+
*/
|
|
1323
|
+
let EncoderClass = store.encoder?.Encoder;
|
|
1324
|
+
if (store.encoding === false) {
|
|
1325
|
+
store.encoder = null;
|
|
1326
|
+
EncoderClass = void 0;
|
|
1327
|
+
} else if (typeof EncoderClass === "function") store.encoder = null;
|
|
1328
|
+
else if (typeof store.encoder?.encode !== "function" && (!store.encoding || store.encoding === "msgpack")) {
|
|
1329
|
+
store.encoding = "msgpack";
|
|
1330
|
+
EncoderClass = msgpackr.Encoder;
|
|
1331
|
+
}
|
|
1332
|
+
if (EncoderClass) {
|
|
1333
|
+
const opts = {
|
|
1334
|
+
copyBuffers: true,
|
|
1335
|
+
freezeData: store.freezeData,
|
|
1336
|
+
randomAccessStructure: store.randomAccessStructure
|
|
1337
|
+
};
|
|
1338
|
+
const { sharedStructuresKey } = store;
|
|
1339
|
+
if (sharedStructuresKey) {
|
|
1340
|
+
opts.getStructures = () => {
|
|
1341
|
+
const buffer = this.getBinarySync(sharedStructuresKey);
|
|
1342
|
+
return buffer && store.decoder?.decode ? store.decoder.decode(buffer) : void 0;
|
|
1343
|
+
};
|
|
1344
|
+
opts.saveStructures = (structures, isCompatible) => {
|
|
1345
|
+
return this.transactionSync((txn) => {
|
|
1346
|
+
const existingStructuresBuffer = this.getBinarySync(sharedStructuresKey);
|
|
1347
|
+
const existingStructures = existingStructuresBuffer && store.decoder?.decode ? store.decoder.decode(existingStructuresBuffer) : void 0;
|
|
1348
|
+
if (typeof isCompatible == "function") {
|
|
1349
|
+
if (!isCompatible(existingStructures)) return false;
|
|
1350
|
+
} else if (existingStructures && existingStructures.length !== isCompatible) return false;
|
|
1351
|
+
txn.putSync(sharedStructuresKey, structures);
|
|
1352
|
+
});
|
|
1353
|
+
};
|
|
1354
|
+
}
|
|
1355
|
+
store.encoder = new EncoderClass({
|
|
1356
|
+
...opts,
|
|
1357
|
+
...store.encoder
|
|
1358
|
+
});
|
|
1359
|
+
store.decoder = store.encoder;
|
|
1360
|
+
} else if (typeof store.encoder?.encode === "function") {
|
|
1361
|
+
if (!store.decoder) store.decoder = store.encoder;
|
|
1362
|
+
} else if (store.encoding === "ordered-binary") {
|
|
1363
|
+
store.encoder = {
|
|
1364
|
+
readKey: ordered_binary.readKey,
|
|
1365
|
+
writeKey: ordered_binary.writeKey
|
|
1366
|
+
};
|
|
1367
|
+
store.decoder = store.encoder;
|
|
1368
|
+
}
|
|
1369
|
+
if (typeof store.encoder?.writeKey === "function" && !store.encoder?.encode) {
|
|
1370
|
+
store.encoder = {
|
|
1371
|
+
...store.encoder,
|
|
1372
|
+
encode: (value, _mode) => {
|
|
1373
|
+
const bytesWritten = store.writeKey(value, store.encodeBuffer, 0);
|
|
1374
|
+
return store.encodeBuffer.subarray(0, bytesWritten);
|
|
1375
|
+
}
|
|
1376
|
+
};
|
|
1377
|
+
store.encoder.copyBuffers = true;
|
|
1378
|
+
}
|
|
1379
|
+
if (store.decoder && store.decoder.needsStableBuffer !== true) store.decoderCopies = true;
|
|
1380
|
+
if (store.decoder?.readKey && !store.decoder.decode) {
|
|
1381
|
+
store.decoder.decode = (buffer) => {
|
|
1382
|
+
if (store.decoder?.readKey) return store.decoder.readKey(buffer, 0, buffer.end);
|
|
1383
|
+
return buffer;
|
|
1384
|
+
};
|
|
1385
|
+
store.decoderCopies = true;
|
|
1386
|
+
}
|
|
1387
|
+
return this;
|
|
1388
|
+
}
|
|
1389
|
+
/**
|
|
1390
|
+
* Returns the path to the database.
|
|
1391
|
+
*/
|
|
1392
|
+
get path() {
|
|
1393
|
+
return this.store.path;
|
|
1394
|
+
}
|
|
1395
|
+
/**
|
|
1396
|
+
* Purges transaction logs.
|
|
1397
|
+
*/
|
|
1398
|
+
purgeLogs(options) {
|
|
1399
|
+
return this.store.db.purgeLogs(options);
|
|
1400
|
+
}
|
|
1401
|
+
/**
|
|
1402
|
+
* Executes all operations in the callback as a single transaction.
|
|
1403
|
+
*
|
|
1404
|
+
* @param callback - A async function that receives the transaction as an argument.
|
|
1405
|
+
* @returns A promise that resolves the `callback` return value.
|
|
1406
|
+
*
|
|
1407
|
+
* @example
|
|
1408
|
+
* ```typescript
|
|
1409
|
+
* const db = RocksDatabase.open('/path/to/database');
|
|
1410
|
+
* await db.transaction(async (txn) => {
|
|
1411
|
+
* await txn.put('key', 'value');
|
|
1412
|
+
* });
|
|
1413
|
+
* ```
|
|
1414
|
+
*/
|
|
1415
|
+
async transaction(callback, options) {
|
|
1416
|
+
if (typeof callback !== "function") throw new TypeError("Callback must be a function");
|
|
1417
|
+
const txn = new Transaction(this.store, options);
|
|
1418
|
+
let result;
|
|
1419
|
+
try {
|
|
1420
|
+
this.notify("begin-transaction");
|
|
1421
|
+
result = await callback(txn);
|
|
1422
|
+
} catch (err) {
|
|
1423
|
+
try {
|
|
1424
|
+
txn.abort();
|
|
1425
|
+
} catch (err) {
|
|
1426
|
+
if (err instanceof Error && "code" in err && err.code === "ERR_ALREADY_ABORTED") return;
|
|
1427
|
+
}
|
|
1428
|
+
throw err;
|
|
1429
|
+
}
|
|
1430
|
+
try {
|
|
1431
|
+
await txn.commit();
|
|
1432
|
+
return result;
|
|
1433
|
+
} catch (err) {
|
|
1434
|
+
if (err instanceof Error && "code" in err && err.code === "ERR_ALREADY_ABORTED") return;
|
|
1435
|
+
throw err;
|
|
1436
|
+
}
|
|
1437
|
+
}
|
|
1438
|
+
/**
|
|
1439
|
+
* Executes all operations in the callback as a single transaction.
|
|
1440
|
+
*
|
|
1441
|
+
* @param callback - A function that receives the transaction as an
|
|
1442
|
+
* argument. If the callback return promise-like value, it is awaited
|
|
1443
|
+
* before committing the transaction. Otherwise, the callback is treated as
|
|
1444
|
+
* synchronous.
|
|
1445
|
+
* @returns The `callback` return value.
|
|
1446
|
+
*
|
|
1447
|
+
* @example
|
|
1448
|
+
* ```typescript
|
|
1449
|
+
* const db = RocksDatabase.open('/path/to/database');
|
|
1450
|
+
* await db.transaction(async (txn) => {
|
|
1451
|
+
* await txn.put('key', 'value');
|
|
1452
|
+
* });
|
|
1453
|
+
* ```
|
|
1454
|
+
*/
|
|
1455
|
+
transactionSync(callback, options) {
|
|
1456
|
+
if (typeof callback !== "function") throw new TypeError("Callback must be a function");
|
|
1457
|
+
const txn = new Transaction(this.store, options);
|
|
1458
|
+
let result;
|
|
1459
|
+
try {
|
|
1460
|
+
this.notify("begin-transaction");
|
|
1461
|
+
result = callback(txn);
|
|
1462
|
+
} catch (err) {
|
|
1463
|
+
try {
|
|
1464
|
+
txn.abort();
|
|
1465
|
+
} catch (err) {
|
|
1466
|
+
if (err instanceof Error && "code" in err && err.code === "ERR_ALREADY_ABORTED") return;
|
|
1467
|
+
}
|
|
1468
|
+
throw err;
|
|
1469
|
+
}
|
|
1470
|
+
if (result && typeof result === "object" && "then" in result && typeof result.then === "function") return result.then((value) => {
|
|
1471
|
+
try {
|
|
1472
|
+
txn.commitSync();
|
|
1473
|
+
return value;
|
|
1474
|
+
} catch (err) {
|
|
1475
|
+
if (err instanceof Error && "code" in err && err.code === "ERR_ALREADY_ABORTED") return;
|
|
1476
|
+
throw err;
|
|
1477
|
+
}
|
|
1478
|
+
});
|
|
1479
|
+
try {
|
|
1480
|
+
txn.commitSync();
|
|
1481
|
+
return result;
|
|
1482
|
+
} catch (err) {
|
|
1483
|
+
if (err instanceof Error && "code" in err && err.code === "ERR_ALREADY_ABORTED") return;
|
|
1484
|
+
try {
|
|
1485
|
+
txn.abort();
|
|
1486
|
+
} catch {}
|
|
1487
|
+
throw err;
|
|
1488
|
+
}
|
|
1489
|
+
}
|
|
1490
|
+
/**
|
|
1491
|
+
* Attempts to acquire a lock for a given key. If the lock is available,
|
|
1492
|
+
* the function returns `true` and the optional callback is never called.
|
|
1493
|
+
* If the lock is not available, the function returns `false` and the
|
|
1494
|
+
* callback is queued until the lock is released.
|
|
1495
|
+
*
|
|
1496
|
+
* @param key - The key to lock.
|
|
1497
|
+
* @param onUnlocked - A callback to call when the lock is released.
|
|
1498
|
+
*
|
|
1499
|
+
* @example
|
|
1500
|
+
* ```typescript
|
|
1501
|
+
* const db = RocksDatabase.open('/path/to/database');
|
|
1502
|
+
* db.tryLock('foo', () => {
|
|
1503
|
+
* console.log('lock acquired');
|
|
1504
|
+
* });
|
|
1505
|
+
* ```
|
|
1506
|
+
* @returns `true` if the lock was acquired, `false` otherwise.
|
|
1507
|
+
*/
|
|
1508
|
+
tryLock(key, onUnlocked) {
|
|
1509
|
+
return this.store.tryLock(key, onUnlocked);
|
|
1510
|
+
}
|
|
1511
|
+
/**
|
|
1512
|
+
* Releases the lock on the given key and calls any queued `onUnlocked`
|
|
1513
|
+
* callback handlers.
|
|
1514
|
+
*
|
|
1515
|
+
* @param key - The key to unlock.
|
|
1516
|
+
* @returns `true` if the lock was released or `false` if the lock did not
|
|
1517
|
+
* exist.
|
|
1518
|
+
*
|
|
1519
|
+
* @example
|
|
1520
|
+
* ```typescript
|
|
1521
|
+
* const db = RocksDatabase.open('/path/to/database');
|
|
1522
|
+
* db.tryLock('foo', () => {});
|
|
1523
|
+
* db.unlock('foo'); // returns `true`
|
|
1524
|
+
* db.unlock('foo'); // already unlocked, returns `false`
|
|
1525
|
+
* ```
|
|
1526
|
+
*/
|
|
1527
|
+
unlock(key) {
|
|
1528
|
+
return this.store.unlock(key);
|
|
1529
|
+
}
|
|
1530
|
+
/**
|
|
1531
|
+
* Excecutes a function using a thread-safe lock to ensure mutual
|
|
1532
|
+
* exclusion.
|
|
1533
|
+
*
|
|
1534
|
+
* @param callback - A callback to call when the lock is acquired.
|
|
1535
|
+
* @returns A promise that resolves when the lock is acquired.
|
|
1536
|
+
*
|
|
1537
|
+
* @example
|
|
1538
|
+
* ```typescript
|
|
1539
|
+
* const db = RocksDatabase.open('/path/to/database');
|
|
1540
|
+
* await db.withLock(async (waited) => {
|
|
1541
|
+
* console.log('lock acquired', waited);
|
|
1542
|
+
* });
|
|
1543
|
+
* ```
|
|
1544
|
+
*/
|
|
1545
|
+
withLock(key, callback) {
|
|
1546
|
+
return this.store.withLock(key, callback);
|
|
1547
|
+
}
|
|
1548
|
+
};
|
|
1549
|
+
|
|
1550
|
+
//#endregion
|
|
1551
|
+
//#region src/parse-transaction-log.ts
|
|
1552
|
+
const { TRANSACTION_LOG_TOKEN } = constants;
|
|
1553
|
+
/**
|
|
1554
|
+
* Loads an entire transaction log file into memory.
|
|
1555
|
+
* @param path - The path to the transaction log file.
|
|
1556
|
+
* @returns The transaction log.
|
|
1557
|
+
*/
|
|
1558
|
+
function parseTransactionLog(path) {
|
|
1559
|
+
let stats;
|
|
1560
|
+
try {
|
|
1561
|
+
stats = (0, node_fs.statSync)(path);
|
|
1562
|
+
} catch (error) {
|
|
1563
|
+
if (error.code === "ENOENT") throw new Error("Transaction log file does not exist");
|
|
1564
|
+
throw error;
|
|
1565
|
+
}
|
|
1566
|
+
let { size } = stats;
|
|
1567
|
+
if (size === 0) throw new Error("Transaction log file is too small");
|
|
1568
|
+
const fileHandle = (0, node_fs.openSync)(path, "r");
|
|
1569
|
+
let fileOffset = 0;
|
|
1570
|
+
const read = (numBytes) => {
|
|
1571
|
+
const buffer = Buffer.allocUnsafe(numBytes);
|
|
1572
|
+
const bytesRead = (0, node_fs.readSync)(fileHandle, buffer, 0, numBytes, fileOffset);
|
|
1573
|
+
fileOffset += bytesRead;
|
|
1574
|
+
if (bytesRead !== numBytes) throw new Error(`Expected to read ${numBytes} bytes but only read ${bytesRead}, file offset: ${fileOffset}, file size: ${size}, file path: ${path}, buffer: ${buffer.toString("hex")}`);
|
|
1575
|
+
return buffer;
|
|
1576
|
+
};
|
|
1577
|
+
try {
|
|
1578
|
+
if (read(4).readUInt32BE(0) !== TRANSACTION_LOG_TOKEN) throw new Error("Invalid token");
|
|
1579
|
+
const version = read(1).readUInt8(0);
|
|
1580
|
+
if (version !== 1) throw new Error(`Unsupported transaction log file version: ${version}`);
|
|
1581
|
+
const timestamp = read(8).readDoubleBE(0);
|
|
1582
|
+
const entries = [];
|
|
1583
|
+
while (fileOffset < size) {
|
|
1584
|
+
const timestamp = read(8).readDoubleBE(0);
|
|
1585
|
+
if (timestamp === 0) {
|
|
1586
|
+
size = fileOffset - 8;
|
|
1587
|
+
break;
|
|
1588
|
+
}
|
|
1589
|
+
const length = read(4).readUInt32BE(0);
|
|
1590
|
+
const flags = read(1).readUInt8(0);
|
|
1591
|
+
const data = read(length);
|
|
1592
|
+
entries.push({
|
|
1593
|
+
timestamp,
|
|
1594
|
+
length,
|
|
1595
|
+
flags,
|
|
1596
|
+
data
|
|
1597
|
+
});
|
|
1598
|
+
}
|
|
1599
|
+
return {
|
|
1600
|
+
entries,
|
|
1601
|
+
timestamp,
|
|
1602
|
+
size,
|
|
1603
|
+
version
|
|
1604
|
+
};
|
|
1605
|
+
} catch (error) {
|
|
1606
|
+
if (error instanceof Error) error.message = `Invalid transaction log file: ${error.message}`;
|
|
1607
|
+
throw error;
|
|
1608
|
+
} finally {
|
|
1609
|
+
(0, node_fs.closeSync)(fileHandle);
|
|
1610
|
+
}
|
|
1611
|
+
}
|
|
1612
|
+
|
|
1613
|
+
//#endregion
|
|
1614
|
+
//#region src/transaction-log-reader.ts
|
|
1615
|
+
const FLOAT_TO_UINT32 = new Float64Array(1);
|
|
1616
|
+
const UINT32_FROM_FLOAT = new Uint32Array(FLOAT_TO_UINT32.buffer);
|
|
1617
|
+
const { TRANSACTION_LOG_FILE_HEADER_SIZE, TRANSACTION_LOG_ENTRY_HEADER_SIZE } = constants;
|
|
1618
|
+
/**
|
|
1619
|
+
* Returns an iterable for transaction entries within the specified range of timestamps
|
|
1620
|
+
* This iterable can be iterated over multiple times, and subsequent iterations will continue
|
|
1621
|
+
* from where the last iteration left off, allowing for iteration through the log file
|
|
1622
|
+
* to resume after more transactions have been committed.
|
|
1623
|
+
* @param start
|
|
1624
|
+
* @param end
|
|
1625
|
+
* @param exactStart - if this is true, the function will try to find the transaction that
|
|
1626
|
+
* exactly matches the start timestamp, and then return all subsequent transactions in the log
|
|
1627
|
+
* regardless of whether their timestamp is before or after the start
|
|
1628
|
+
*/
|
|
1629
|
+
Object.defineProperty(TransactionLog.prototype, "query", { value({ start, end, exactStart, startFromLastFlushed, readUncommitted, exclusiveStart } = {}) {
|
|
1630
|
+
if (!this._lastCommittedPosition) {
|
|
1631
|
+
const lastCommittedPosition = this._getLastCommittedPosition();
|
|
1632
|
+
this._lastCommittedPosition = new Float64Array(lastCommittedPosition.buffer);
|
|
1633
|
+
this._logBuffers = /* @__PURE__ */ new Map();
|
|
1634
|
+
}
|
|
1635
|
+
end ??= Number.MAX_VALUE;
|
|
1636
|
+
const transactionLog = this;
|
|
1637
|
+
let { logId: latestLogId, size } = loadLastPosition(this, !!readUncommitted);
|
|
1638
|
+
let logId = latestLogId;
|
|
1639
|
+
let position = 0;
|
|
1640
|
+
let dataView;
|
|
1641
|
+
let logBuffer = this._currentLogBuffer;
|
|
1642
|
+
let foundExactStart = false;
|
|
1643
|
+
if (start === void 0 && !startFromLastFlushed) {
|
|
1644
|
+
position = size;
|
|
1645
|
+
start = 0;
|
|
1646
|
+
} else {
|
|
1647
|
+
if (startFromLastFlushed) {
|
|
1648
|
+
FLOAT_TO_UINT32[0] = this._getLastFlushed();
|
|
1649
|
+
if (FLOAT_TO_UINT32[0] === 0) FLOAT_TO_UINT32[0] = this._findPosition(0);
|
|
1650
|
+
start ??= 0;
|
|
1651
|
+
} else FLOAT_TO_UINT32[0] = this._findPosition(start);
|
|
1652
|
+
logId = UINT32_FROM_FLOAT[1];
|
|
1653
|
+
position = UINT32_FROM_FLOAT[0];
|
|
1654
|
+
}
|
|
1655
|
+
if (logBuffer === void 0 || logBuffer.logId !== logId) {
|
|
1656
|
+
logBuffer = getLogMemoryMap(this, logId);
|
|
1657
|
+
if (logBuffer && latestLogId === logId && !readUncommitted) this._currentLogBuffer = logBuffer;
|
|
1658
|
+
if (logBuffer === void 0) {
|
|
1659
|
+
logBuffer = Buffer.alloc(0);
|
|
1660
|
+
logBuffer.logId = 0;
|
|
1661
|
+
logBuffer.size = 0;
|
|
1662
|
+
logBuffer.dataView = new DataView(logBuffer.buffer);
|
|
1663
|
+
}
|
|
1664
|
+
}
|
|
1665
|
+
dataView = logBuffer.dataView;
|
|
1666
|
+
if (latestLogId !== logId) {
|
|
1667
|
+
size = logBuffer.size;
|
|
1668
|
+
if (size === void 0) size = logBuffer.size = this.getLogFileSize(logId);
|
|
1669
|
+
}
|
|
1670
|
+
return {
|
|
1671
|
+
[Symbol.iterator]() {
|
|
1672
|
+
return this;
|
|
1673
|
+
},
|
|
1674
|
+
next() {
|
|
1675
|
+
let timestamp;
|
|
1676
|
+
if (position >= size) {
|
|
1677
|
+
const { logId: latestLogId, size: latestSize } = loadLastPosition(transactionLog, !!readUncommitted);
|
|
1678
|
+
size = latestSize;
|
|
1679
|
+
if (latestLogId > logBuffer.logId) {
|
|
1680
|
+
size = logBuffer.size ?? (logBuffer.size = transactionLog.getLogFileSize(logBuffer.logId));
|
|
1681
|
+
if (position >= size) {
|
|
1682
|
+
const nextLogBuffer = getLogMemoryMap(transactionLog, logBuffer.logId + 1);
|
|
1683
|
+
dataView = nextLogBuffer.dataView;
|
|
1684
|
+
logBuffer = nextLogBuffer;
|
|
1685
|
+
if (latestLogId > logBuffer.logId) size = logBuffer.size ?? (logBuffer.size = transactionLog.getLogFileSize(logBuffer.logId));
|
|
1686
|
+
else size = latestSize;
|
|
1687
|
+
position = TRANSACTION_LOG_FILE_HEADER_SIZE;
|
|
1688
|
+
}
|
|
1689
|
+
}
|
|
1690
|
+
}
|
|
1691
|
+
while (position < size) {
|
|
1692
|
+
do
|
|
1693
|
+
try {
|
|
1694
|
+
timestamp = dataView.getFloat64(position);
|
|
1695
|
+
} catch (error) {
|
|
1696
|
+
error.message += ` at position ${position} of log ${logBuffer.logId} (size=${size}, log buffer length=${logBuffer.length})`;
|
|
1697
|
+
throw error;
|
|
1698
|
+
}
|
|
1699
|
+
while (timestamp < 1 && ++position < size);
|
|
1700
|
+
if (!timestamp) return {
|
|
1701
|
+
done: true,
|
|
1702
|
+
value: void 0
|
|
1703
|
+
};
|
|
1704
|
+
const length = dataView.getUint32(position + 8);
|
|
1705
|
+
position += TRANSACTION_LOG_ENTRY_HEADER_SIZE;
|
|
1706
|
+
let matchesRange;
|
|
1707
|
+
if (foundExactStart) matchesRange = (!exclusiveStart || timestamp !== start) && timestamp < end;
|
|
1708
|
+
else if (exactStart) if (timestamp === start) {
|
|
1709
|
+
matchesRange = !exclusiveStart;
|
|
1710
|
+
foundExactStart = true;
|
|
1711
|
+
} else matchesRange = false;
|
|
1712
|
+
else matchesRange = (exclusiveStart ? timestamp > start : timestamp >= start) && timestamp < end;
|
|
1713
|
+
const entryStart = position;
|
|
1714
|
+
position += length;
|
|
1715
|
+
if (matchesRange) return {
|
|
1716
|
+
done: false,
|
|
1717
|
+
value: {
|
|
1718
|
+
timestamp,
|
|
1719
|
+
endTxn: Boolean(logBuffer[entryStart - 1] & 1),
|
|
1720
|
+
data: logBuffer.subarray(entryStart, position)
|
|
1721
|
+
}
|
|
1722
|
+
};
|
|
1723
|
+
if (position >= size) {
|
|
1724
|
+
const { logId: latestLogId, size: latestSize } = loadLastPosition(transactionLog, !!readUncommitted);
|
|
1725
|
+
size = latestSize;
|
|
1726
|
+
if (latestLogId > logBuffer.logId) {
|
|
1727
|
+
logBuffer = getLogMemoryMap(transactionLog, logBuffer.logId + 1);
|
|
1728
|
+
dataView = logBuffer.dataView;
|
|
1729
|
+
size = logBuffer.size;
|
|
1730
|
+
if (size == void 0) {
|
|
1731
|
+
size = transactionLog.getLogFileSize(logBuffer.logId);
|
|
1732
|
+
if (!readUncommitted) logBuffer.size = size;
|
|
1733
|
+
}
|
|
1734
|
+
position = TRANSACTION_LOG_FILE_HEADER_SIZE;
|
|
1735
|
+
}
|
|
1736
|
+
}
|
|
1737
|
+
}
|
|
1738
|
+
return {
|
|
1739
|
+
done: true,
|
|
1740
|
+
value: void 0
|
|
1741
|
+
};
|
|
1742
|
+
}
|
|
1743
|
+
};
|
|
1744
|
+
} });
|
|
1745
|
+
function getLogMemoryMap(transactionLog, logId) {
|
|
1746
|
+
if (logId <= 0) return;
|
|
1747
|
+
let logBuffer = transactionLog._logBuffers.get(logId)?.deref();
|
|
1748
|
+
if (logBuffer) return logBuffer;
|
|
1749
|
+
try {
|
|
1750
|
+
logBuffer = transactionLog._getMemoryMapOfFile(logId);
|
|
1751
|
+
} catch (error) {
|
|
1752
|
+
error.message += ` (log file ID: ${logId})`;
|
|
1753
|
+
throw error;
|
|
1754
|
+
}
|
|
1755
|
+
if (!logBuffer) return;
|
|
1756
|
+
logBuffer.logId = logId;
|
|
1757
|
+
logBuffer.dataView = new DataView(logBuffer.buffer);
|
|
1758
|
+
transactionLog._logBuffers.set(logId, new WeakRef(logBuffer));
|
|
1759
|
+
let maxMisses = 3;
|
|
1760
|
+
for (const [logId, reference] of transactionLog._logBuffers) if (reference.deref() === void 0) transactionLog._logBuffers.delete(logId);
|
|
1761
|
+
else if (--maxMisses === 0) break;
|
|
1762
|
+
return logBuffer;
|
|
1763
|
+
}
|
|
1764
|
+
function loadLastPosition(transactionLog, readUncommitted) {
|
|
1765
|
+
FLOAT_TO_UINT32[0] = transactionLog._lastCommittedPosition[0];
|
|
1766
|
+
let logId = UINT32_FROM_FLOAT[1];
|
|
1767
|
+
let size = 0;
|
|
1768
|
+
if (readUncommitted) {
|
|
1769
|
+
let nextSize = 0;
|
|
1770
|
+
let nextLogId = logId || 1;
|
|
1771
|
+
while (true) {
|
|
1772
|
+
nextSize = transactionLog.getLogFileSize(nextLogId);
|
|
1773
|
+
if (nextSize === 0) break;
|
|
1774
|
+
else {
|
|
1775
|
+
size = nextSize;
|
|
1776
|
+
logId = nextLogId++;
|
|
1777
|
+
}
|
|
1778
|
+
}
|
|
1779
|
+
} else size = UINT32_FROM_FLOAT[0];
|
|
1780
|
+
return {
|
|
1781
|
+
logId,
|
|
1782
|
+
size
|
|
1783
|
+
};
|
|
1784
|
+
}
|
|
1785
|
+
|
|
1786
|
+
//#endregion
|
|
1787
|
+
//#region src/index.ts
|
|
1788
|
+
const versions = {
|
|
1789
|
+
rocksdb: version,
|
|
1790
|
+
"rocksdb-js": "0.1.0"
|
|
1791
|
+
};
|
|
1792
|
+
|
|
1793
|
+
//#endregion
|
|
1794
|
+
exports.DBIterator = DBIterator;
|
|
1795
|
+
exports.RocksDatabase = RocksDatabase;
|
|
1796
|
+
exports.Store = Store;
|
|
1797
|
+
exports.Transaction = Transaction;
|
|
1798
|
+
exports.TransactionLog = TransactionLog;
|
|
1799
|
+
exports.constants = constants;
|
|
1800
|
+
exports.parseTransactionLog = parseTransactionLog;
|
|
1801
|
+
exports.shutdown = shutdown;
|
|
1802
|
+
exports.versions = versions;
|
|
1803
|
+
//# sourceMappingURL=index.cjs.map
|