@harperfast/rocksdb-js 0.1.12 → 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,30 +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 (abortErr) {
1459
- if (abortErr instanceof Error && "code" in abortErr && abortErr.code === "ERR_ALREADY_ABORTED") return;
1501
+ result = await callback(txn, attempt);
1502
+ } catch (callbackErr) {
1503
+ return this.#abortTransaction(txn, callbackErr);
1460
1504
  }
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
1505
  try {
1469
- txn.abort();
1470
- } catch (abortErr) {
1471
- if (abortErr instanceof Error && "code" in abortErr && abortErr.code === "ERR_TRANSACTION_ABANDONED") throw abortErr;
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);
1472
1512
  }
1473
- throw err;
1474
1513
  }
1475
1514
  }
1476
1515
  /**
@@ -1492,38 +1531,55 @@ var RocksDatabase = class RocksDatabase extends DBI {
1492
1531
  */
1493
1532
  transactionSync(callback, options) {
1494
1533
  if (typeof callback !== "function") throw new TypeError("Callback must be a function");
1495
- const txn = new Transaction(this.store, options);
1496
- let result;
1497
- try {
1498
- this.notify("begin-transaction");
1499
- result = callback(txn);
1500
- } 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;
1501
1541
  try {
1502
- txn.abort();
1503
- } catch (err) {
1504
- 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);
1505
1545
  }
1506
- throw err;
1507
- }
1508
- 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
+ });
1509
1556
  try {
1510
1557
  txn.commitSync();
1511
- return value;
1512
- } catch (err) {
1513
- if (err instanceof Error && "code" in err && err.code === "ERR_ALREADY_ABORTED") return;
1514
- 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);
1515
1563
  }
1516
- });
1564
+ };
1565
+ this.notify("begin-transaction");
1566
+ return runAttempt(1);
1567
+ }
1568
+ #abortTransaction(txn, callbackErr) {
1517
1569
  try {
1518
- txn.commitSync();
1519
- return result;
1520
- } catch (err) {
1521
- if (err instanceof Error && "code" in err && err.code === "ERR_ALREADY_ABORTED") return;
1522
- try {
1523
- txn.abort();
1524
- } catch {}
1525
- 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;
1526
1581
  }
1582
+ throw commitErr;
1527
1583
  }
1528
1584
  /**
1529
1585
  * Attempts to acquire a lock for a given key. If the lock is available,
@@ -1827,7 +1883,7 @@ function loadLastPosition(transactionLog, readUncommitted) {
1827
1883
  //#region src/index.ts
1828
1884
  const versions = {
1829
1885
  rocksdb: version,
1830
- "rocksdb-js": "0.1.12"
1886
+ "rocksdb-js": "0.1.13"
1831
1887
  };
1832
1888
 
1833
1889
  //#endregion