@harperfast/rocksdb-js 0.1.11 → 0.1.13

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.d.cts CHANGED
@@ -369,6 +369,18 @@ type TransactionOptions = {
369
369
  * @default false
370
370
  */
371
371
  disableSnapshot?: boolean;
372
+ /**
373
+ * The maximum number of times to retry the transaction.
374
+ *
375
+ * @default 3
376
+ */
377
+ maxRetries?: number;
378
+ /**
379
+ * Whether to retry the transaction if it fails with `IsBusy`.
380
+ *
381
+ * @default `true` when the transaction is bound to a transaction log, otherwise `false`
382
+ */
383
+ retryOnBusy?: boolean;
372
384
  };
373
385
  type NativeTransaction = {
374
386
  id: number;
@@ -889,6 +901,7 @@ declare class DBI<T extends DBITransactional | unknown = unknown> {
889
901
  }
890
902
  //#endregion
891
903
  //#region src/database.d.ts
904
+ type TransactionCallback<T> = (txn: Transaction, attempt: number) => T | PromiseLike<T>;
892
905
  interface RocksDatabaseOptions extends StoreOptions {
893
906
  /**
894
907
  * The column family name.
@@ -1133,7 +1146,7 @@ declare class RocksDatabase extends DBI<DBITransactional> {
1133
1146
  * });
1134
1147
  * ```
1135
1148
  */
1136
- transaction<T>(callback: (txn: Transaction) => T | PromiseLike<T>, options?: TransactionOptions): Promise<T | PromiseLike<T>>;
1149
+ transaction<T>(callback: TransactionCallback<T>, options?: TransactionOptions): Promise<T | void>;
1137
1150
  /**
1138
1151
  * Executes all operations in the callback as a single transaction.
1139
1152
  *
@@ -1151,7 +1164,7 @@ declare class RocksDatabase extends DBI<DBITransactional> {
1151
1164
  * });
1152
1165
  * ```
1153
1166
  */
1154
- transactionSync<T>(callback: (txn: Transaction) => T | PromiseLike<T>, options?: TransactionOptions): T | PromiseLike<T> | undefined;
1167
+ transactionSync<T>(callback: TransactionCallback<T>, options?: TransactionOptions): T | PromiseLike<T> | void;
1155
1168
  /**
1156
1169
  * Attempts to acquire a lock for a given key. If the lock is available,
1157
1170
  * the function returns `true` and the optional callback is never called.
package/dist/index.d.mts CHANGED
@@ -369,6 +369,18 @@ type TransactionOptions = {
369
369
  * @default false
370
370
  */
371
371
  disableSnapshot?: boolean;
372
+ /**
373
+ * The maximum number of times to retry the transaction.
374
+ *
375
+ * @default 3
376
+ */
377
+ maxRetries?: number;
378
+ /**
379
+ * Whether to retry the transaction if it fails with `IsBusy`.
380
+ *
381
+ * @default `true` when the transaction is bound to a transaction log, otherwise `false`
382
+ */
383
+ retryOnBusy?: boolean;
372
384
  };
373
385
  type NativeTransaction = {
374
386
  id: number;
@@ -889,6 +901,7 @@ declare class DBI<T extends DBITransactional | unknown = unknown> {
889
901
  }
890
902
  //#endregion
891
903
  //#region src/database.d.ts
904
+ type TransactionCallback<T> = (txn: Transaction, attempt: number) => T | PromiseLike<T>;
892
905
  interface RocksDatabaseOptions extends StoreOptions {
893
906
  /**
894
907
  * The column family name.
@@ -1133,7 +1146,7 @@ declare class RocksDatabase extends DBI<DBITransactional> {
1133
1146
  * });
1134
1147
  * ```
1135
1148
  */
1136
- transaction<T>(callback: (txn: Transaction) => T | PromiseLike<T>, options?: TransactionOptions): Promise<T | PromiseLike<T>>;
1149
+ transaction<T>(callback: TransactionCallback<T>, options?: TransactionOptions): Promise<T | void>;
1137
1150
  /**
1138
1151
  * Executes all operations in the callback as a single transaction.
1139
1152
  *
@@ -1151,7 +1164,7 @@ declare class RocksDatabase extends DBI<DBITransactional> {
1151
1164
  * });
1152
1165
  * ```
1153
1166
  */
1154
- transactionSync<T>(callback: (txn: Transaction) => T | PromiseLike<T>, options?: TransactionOptions): T | PromiseLike<T> | undefined;
1167
+ transactionSync<T>(callback: TransactionCallback<T>, options?: TransactionOptions): T | PromiseLike<T> | void;
1155
1168
  /**
1156
1169
  * Attempts to acquire a lock for a given key. If the lock is available,
1157
1170
  * the function returns `true` and the optional callback is never called.
package/dist/index.mjs CHANGED
@@ -986,6 +986,29 @@ function getKeyParam(keyBuffer) {
986
986
 
987
987
  //#endregion
988
988
  //#region src/transaction.ts
989
+ var TransactionAlreadyAbortedError = class extends Error {
990
+ code = "ERR_ALREADY_ABORTED";
991
+ };
992
+ var TransactionIsBusyError = class extends Error {
993
+ code = "ERR_BUSY";
994
+ hasLog;
995
+ txn;
996
+ constructor(error, txn) {
997
+ super(error.message);
998
+ this.hasLog = error.hasLog ?? false;
999
+ this.txn = txn;
1000
+ }
1001
+ };
1002
+ var TransactionAbandonedError = class extends Error {
1003
+ code = "ERR_TRANSACTION_ABANDONED";
1004
+ hasLog;
1005
+ txn;
1006
+ constructor(error, txn) {
1007
+ super(error.message);
1008
+ this.hasLog = error.hasLog ?? false;
1009
+ this.txn = txn;
1010
+ }
1011
+ };
989
1012
  /**
990
1013
  * Provides transaction level operations to a transaction callback.
991
1014
  */
@@ -1006,7 +1029,12 @@ var Transaction = class extends DBI {
1006
1029
  * Abort the transaction.
1007
1030
  */
1008
1031
  abort() {
1009
- this.#txn.abort();
1032
+ try {
1033
+ this.#txn.abort();
1034
+ } catch (err) {
1035
+ if (err instanceof Error && "code" in err && err.code === "ERR_TRANSACTION_ABANDONED") throw new TransactionAbandonedError(err, this);
1036
+ throw err;
1037
+ }
1010
1038
  }
1011
1039
  /**
1012
1040
  * Commit the transaction.
@@ -1017,6 +1045,8 @@ var Transaction = class extends DBI {
1017
1045
  this.notify("beforecommit");
1018
1046
  this.#txn.commit(resolve, reject);
1019
1047
  });
1048
+ } catch (err) {
1049
+ throw this.#handleCommitError(err);
1020
1050
  } finally {
1021
1051
  this.notify("aftercommit", {
1022
1052
  next: null,
@@ -1032,6 +1062,8 @@ var Transaction = class extends DBI {
1032
1062
  try {
1033
1063
  this.notify("beforecommit");
1034
1064
  this.#txn.commitSync();
1065
+ } catch (err) {
1066
+ throw this.#handleCommitError(err);
1035
1067
  } finally {
1036
1068
  this.notify("aftercommit", {
1037
1069
  next: null,
@@ -1041,6 +1073,19 @@ var Transaction = class extends DBI {
1041
1073
  }
1042
1074
  }
1043
1075
  /**
1076
+ * Detect if error is an already aborted or busy error and return the appropriate error class.
1077
+ *
1078
+ * @param err - The error to check.
1079
+ * @returns The specialized error.
1080
+ */
1081
+ #handleCommitError(err) {
1082
+ if (err instanceof Error && "code" in err) {
1083
+ if (err.code === "ERR_ALREADY_ABORTED") return new TransactionAlreadyAbortedError(err.message);
1084
+ if (err.code === "ERR_BUSY") return new TransactionIsBusyError(err, this);
1085
+ }
1086
+ return err;
1087
+ }
1088
+ /**
1044
1089
  * Returns the transaction start timestamp in seconds. Defaults to the time at which
1045
1090
  * the transaction was created.
1046
1091
  *
@@ -1447,25 +1492,24 @@ var RocksDatabase = class RocksDatabase extends DBI {
1447
1492
  */
1448
1493
  async transaction(callback, options) {
1449
1494
  if (typeof callback !== "function") throw new TypeError("Callback must be a function");
1495
+ const maxRetries = options?.maxRetries ?? 3;
1450
1496
  const txn = new Transaction(this.store, options);
1451
1497
  let result;
1452
- try {
1453
- this.notify("begin-transaction");
1454
- result = await callback(txn);
1455
- } catch (err) {
1498
+ this.notify("begin-transaction");
1499
+ for (let attempt = 1; attempt <= maxRetries; attempt++) {
1456
1500
  try {
1457
- txn.abort();
1458
- } catch (err) {
1459
- if (err instanceof Error && "code" in err && err.code === "ERR_ALREADY_ABORTED") return;
1501
+ result = await callback(txn, attempt);
1502
+ } catch (callbackErr) {
1503
+ return this.#abortTransaction(txn, callbackErr);
1504
+ }
1505
+ try {
1506
+ await txn.commit();
1507
+ return result;
1508
+ } catch (commitErr) {
1509
+ if (commitErr instanceof TransactionAlreadyAbortedError) return;
1510
+ if (commitErr instanceof TransactionIsBusyError && (options?.retryOnBusy ?? commitErr.hasLog) && attempt <= maxRetries) continue;
1511
+ this.#abandonTransaction(txn, commitErr);
1460
1512
  }
1461
- throw err;
1462
- }
1463
- try {
1464
- await txn.commit();
1465
- return result;
1466
- } catch (err) {
1467
- if (err instanceof Error && "code" in err && err.code === "ERR_ALREADY_ABORTED") return;
1468
- throw err;
1469
1513
  }
1470
1514
  }
1471
1515
  /**
@@ -1487,38 +1531,55 @@ var RocksDatabase = class RocksDatabase extends DBI {
1487
1531
  */
1488
1532
  transactionSync(callback, options) {
1489
1533
  if (typeof callback !== "function") throw new TypeError("Callback must be a function");
1490
- const txn = new Transaction(this.store, options);
1491
- let result;
1492
- try {
1493
- this.notify("begin-transaction");
1494
- result = callback(txn);
1495
- } catch (err) {
1534
+ const maxRetries = options?.maxRetries ?? 3;
1535
+ const isRetryable = (err, attempt) => {
1536
+ return err instanceof TransactionIsBusyError && (options?.retryOnBusy ?? err.hasLog) && attempt <= maxRetries;
1537
+ };
1538
+ const runAttempt = (attempt) => {
1539
+ const txn = new Transaction(this.store, options);
1540
+ let result;
1496
1541
  try {
1497
- txn.abort();
1498
- } catch (err) {
1499
- if (err instanceof Error && "code" in err && err.code === "ERR_ALREADY_ABORTED") return;
1542
+ result = callback(txn, attempt);
1543
+ } catch (callbackErr) {
1544
+ return this.#abortTransaction(txn, callbackErr);
1500
1545
  }
1501
- throw err;
1502
- }
1503
- if (result && typeof result === "object" && "then" in result && typeof result.then === "function") return result.then((value) => {
1546
+ if (typeof result?.then === "function") return result.then((value) => {
1547
+ try {
1548
+ txn.commitSync();
1549
+ return value;
1550
+ } catch (commitErr) {
1551
+ if (commitErr instanceof TransactionAlreadyAbortedError) return;
1552
+ if (isRetryable(commitErr, attempt)) return runAttempt(attempt + 1);
1553
+ this.#abandonTransaction(txn, commitErr);
1554
+ }
1555
+ });
1504
1556
  try {
1505
1557
  txn.commitSync();
1506
- return value;
1507
- } catch (err) {
1508
- if (err instanceof Error && "code" in err && err.code === "ERR_ALREADY_ABORTED") return;
1509
- throw err;
1558
+ return result;
1559
+ } catch (commitErr) {
1560
+ if (commitErr instanceof TransactionAlreadyAbortedError) return;
1561
+ if (isRetryable(commitErr, attempt)) return runAttempt(attempt + 1);
1562
+ this.#abandonTransaction(txn, commitErr);
1510
1563
  }
1511
- });
1564
+ };
1565
+ this.notify("begin-transaction");
1566
+ return runAttempt(1);
1567
+ }
1568
+ #abortTransaction(txn, callbackErr) {
1512
1569
  try {
1513
- txn.commitSync();
1514
- return result;
1515
- } catch (err) {
1516
- if (err instanceof Error && "code" in err && err.code === "ERR_ALREADY_ABORTED") return;
1517
- try {
1518
- txn.abort();
1519
- } catch {}
1520
- throw err;
1570
+ txn.abort();
1571
+ } catch (abortErr) {
1572
+ if (abortErr instanceof TransactionAlreadyAbortedError) return;
1573
+ }
1574
+ throw callbackErr;
1575
+ }
1576
+ #abandonTransaction(txn, commitErr) {
1577
+ try {
1578
+ txn.abort();
1579
+ } catch (abortErr) {
1580
+ if (abortErr instanceof TransactionAbandonedError) throw abortErr;
1521
1581
  }
1582
+ throw commitErr;
1522
1583
  }
1523
1584
  /**
1524
1585
  * Attempts to acquire a lock for a given key. If the lock is available,
@@ -1675,6 +1736,7 @@ Object.defineProperty(TransactionLog.prototype, "query", { value({ start, end, e
1675
1736
  let foundExactStart = false;
1676
1737
  if (start === void 0 && !startFromLastFlushed) {
1677
1738
  position = size;
1739
+ if (position === 0) position = TRANSACTION_LOG_FILE_HEADER_SIZE;
1678
1740
  start = 0;
1679
1741
  } else {
1680
1742
  if (startFromLastFlushed) {
@@ -1684,6 +1746,7 @@ Object.defineProperty(TransactionLog.prototype, "query", { value({ start, end, e
1684
1746
  } else FLOAT_TO_UINT32[0] = this._findPosition(start);
1685
1747
  logId = UINT32_FROM_FLOAT[1];
1686
1748
  position = UINT32_FROM_FLOAT[0];
1749
+ if (position === 0) position = TRANSACTION_LOG_FILE_HEADER_SIZE;
1687
1750
  }
1688
1751
  if (logBuffer === void 0 || logBuffer.logId !== logId) {
1689
1752
  logBuffer = getLogMemoryMap(this, logId);
@@ -1820,7 +1883,7 @@ function loadLastPosition(transactionLog, readUncommitted) {
1820
1883
  //#region src/index.ts
1821
1884
  const versions = {
1822
1885
  rocksdb: version,
1823
- "rocksdb-js": "0.1.11"
1886
+ "rocksdb-js": "0.1.13"
1824
1887
  };
1825
1888
 
1826
1889
  //#endregion