@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/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