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