dataply 0.0.26-alpha.7 → 0.0.26-alpha.8

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/cjs/index.js CHANGED
@@ -10967,6 +10967,7 @@ var Transaction = class {
10967
10967
  this.pfs = pfs;
10968
10968
  this.id = id;
10969
10969
  this.rootTx = rootTx;
10970
+ this.mvccTx = rootTx.createNested();
10970
10971
  }
10971
10972
  /** Transaction ID */
10972
10973
  id;
@@ -10977,22 +10978,11 @@ var Transaction = class {
10977
10978
  /** List of callbacks to execute on commit */
10978
10979
  commitHooks = [];
10979
10980
  /** Nested MVCC Transaction for snapshot isolation (lazy init) */
10980
- mvccTx = null;
10981
+ mvccTx;
10981
10982
  /** Root MVCC Transaction reference */
10982
10983
  rootTx;
10983
10984
  /** Release function for global write lock, set by DataplyAPI */
10984
10985
  _writeLockRelease = null;
10985
- /**
10986
- * Lazily initializes the nested MVCC transaction.
10987
- * This ensures the snapshot is taken at the time of first access,
10988
- * picking up the latest committed root version.
10989
- */
10990
- ensureMvccTx() {
10991
- if (!this.mvccTx) {
10992
- this.mvccTx = this.rootTx.createNested();
10993
- }
10994
- return this.mvccTx;
10995
- }
10996
10986
  /**
10997
10987
  * Registers a commit hook.
10998
10988
  * @param hook Function to execute
@@ -11019,8 +11009,7 @@ var Transaction = class {
11019
11009
  * @returns Page data
11020
11010
  */
11021
11011
  async __readPage(pageId) {
11022
- const tx = this.ensureMvccTx();
11023
- const data = await tx.read(pageId);
11012
+ const data = await this.mvccTx.read(pageId);
11024
11013
  if (data === null) {
11025
11014
  return new Uint8Array(this.pfs.pageSize);
11026
11015
  }
@@ -11034,14 +11023,13 @@ var Transaction = class {
11034
11023
  * @param data Page data
11035
11024
  */
11036
11025
  async __writePage(pageId, data) {
11037
- const tx = this.ensureMvccTx();
11038
- const exists = await tx.exists(pageId);
11026
+ const exists = await this.mvccTx.exists(pageId);
11039
11027
  if (exists) {
11040
11028
  const copy = new Uint8Array(data.length);
11041
11029
  copy.set(data);
11042
- await tx.write(pageId, copy);
11030
+ await this.mvccTx.write(pageId, copy);
11043
11031
  } else {
11044
- await tx.create(pageId, data);
11032
+ await this.mvccTx.create(pageId, data);
11045
11033
  }
11046
11034
  }
11047
11035
  /**
@@ -11069,10 +11057,9 @@ var Transaction = class {
11069
11057
  await hook();
11070
11058
  }
11071
11059
  });
11072
- const tx = this.ensureMvccTx();
11073
11060
  let shouldTriggerCheckpoint = false;
11074
11061
  await this.pfs.runGlobalLock(async () => {
11075
- const entries = tx.getResultEntries();
11062
+ const entries = this.mvccTx.getResultEntries();
11076
11063
  const dirtyPages = /* @__PURE__ */ new Map();
11077
11064
  for (const entry of [...entries.created, ...entries.updated]) {
11078
11065
  dirtyPages.set(entry.key, entry.data);
@@ -11082,7 +11069,7 @@ var Transaction = class {
11082
11069
  await this.pfs.wal.prepareCommit(dirtyPages);
11083
11070
  await this.pfs.wal.writeCommitMarker();
11084
11071
  }
11085
- await tx.commit();
11072
+ await this.mvccTx.commit();
11086
11073
  if (hasDirtyPages) {
11087
11074
  await this.rootTx.commit();
11088
11075
  }
@@ -11396,7 +11383,7 @@ var DataplyAPI = class {
11396
11383
  if (this.initialized) {
11397
11384
  return;
11398
11385
  }
11399
- await this.runWithDefault(async (tx) => {
11386
+ await this.withReadTransaction(async (tx) => {
11400
11387
  await this.hook.trigger("init", tx, async (tx2) => {
11401
11388
  await this.pfs.init();
11402
11389
  await this.rowTableEngine.init();
@@ -11421,15 +11408,6 @@ var DataplyAPI = class {
11421
11408
  this.pfs
11422
11409
  );
11423
11410
  }
11424
- /**
11425
- * Runs a callback function within a transaction context.
11426
- * If no transaction is provided, a new transaction is created.
11427
- * The transaction is committed if the callback completes successfully,
11428
- * or rolled back if an error occurs.
11429
- * @param callback The callback function to run within the transaction context.
11430
- * @param tx The transaction to use. If not provided, a new transaction is created.
11431
- * @returns The result of the callback function.
11432
- */
11433
11411
  /**
11434
11412
  * Acquires the global write lock.
11435
11413
  * Returns a release function that MUST be called to unlock.
@@ -11454,7 +11432,7 @@ var DataplyAPI = class {
11454
11432
  * @param tx Optional external transaction.
11455
11433
  * @returns The result of the callback.
11456
11434
  */
11457
- async runWithDefaultWrite(callback, tx) {
11435
+ async withWriteTransaction(callback, tx) {
11458
11436
  this.logger.debug("Running with default write transaction");
11459
11437
  if (!tx) {
11460
11438
  const release = await this.acquireWriteLock();
@@ -11478,7 +11456,7 @@ var DataplyAPI = class {
11478
11456
  }
11479
11457
  return result;
11480
11458
  }
11481
- async runWithDefault(callback, tx) {
11459
+ async withReadTransaction(callback, tx) {
11482
11460
  this.logger.debug("Running with default transaction");
11483
11461
  const isInternalTx = !tx;
11484
11462
  if (!tx) {
@@ -11506,7 +11484,7 @@ var DataplyAPI = class {
11506
11484
  * @param tx The transaction to use. If not provided, a new transaction is created.
11507
11485
  * @returns An AsyncGenerator that yields values from the callback.
11508
11486
  */
11509
- async *streamWithDefault(callback, tx) {
11487
+ async *withReadStreamTransaction(callback, tx) {
11510
11488
  this.logger.debug("Streaming with default transaction");
11511
11489
  const isInternalTx = !tx;
11512
11490
  if (!tx) {
@@ -11539,7 +11517,7 @@ var DataplyAPI = class {
11539
11517
  if (!this.initialized) {
11540
11518
  throw new Error("Dataply instance is not initialized");
11541
11519
  }
11542
- return this.runWithDefault((tx2) => this.rowTableEngine.getMetadata(tx2), tx);
11520
+ return this.withReadTransaction((tx2) => this.rowTableEngine.getMetadata(tx2), tx);
11543
11521
  }
11544
11522
  /**
11545
11523
  * Inserts data. Returns the PK of the added row.
@@ -11553,7 +11531,7 @@ var DataplyAPI = class {
11553
11531
  if (!this.initialized) {
11554
11532
  throw new Error("Dataply instance is not initialized");
11555
11533
  }
11556
- return this.runWithDefaultWrite(async (tx2) => {
11534
+ return this.withWriteTransaction(async (tx2) => {
11557
11535
  incrementRowCount = incrementRowCount ?? true;
11558
11536
  if (typeof data === "string") {
11559
11537
  data = this.textCodec.encode(data);
@@ -11574,7 +11552,7 @@ var DataplyAPI = class {
11574
11552
  if (!this.initialized) {
11575
11553
  throw new Error("Dataply instance is not initialized");
11576
11554
  }
11577
- return this.runWithDefaultWrite(async (tx2) => {
11555
+ return this.withWriteTransaction(async (tx2) => {
11578
11556
  incrementRowCount = incrementRowCount ?? true;
11579
11557
  if (typeof data === "string") {
11580
11558
  data = this.textCodec.encode(data);
@@ -11596,7 +11574,7 @@ var DataplyAPI = class {
11596
11574
  if (!this.initialized) {
11597
11575
  throw new Error("Dataply instance is not initialized");
11598
11576
  }
11599
- return this.runWithDefaultWrite(async (tx2) => {
11577
+ return this.withWriteTransaction(async (tx2) => {
11600
11578
  incrementRowCount = incrementRowCount ?? true;
11601
11579
  const encodedList = dataList.map(
11602
11580
  (data) => typeof data === "string" ? this.textCodec.encode(data) : data
@@ -11615,7 +11593,7 @@ var DataplyAPI = class {
11615
11593
  if (!this.initialized) {
11616
11594
  throw new Error("Dataply instance is not initialized");
11617
11595
  }
11618
- return this.runWithDefaultWrite(async (tx2) => {
11596
+ return this.withWriteTransaction(async (tx2) => {
11619
11597
  if (typeof data === "string") {
11620
11598
  data = this.textCodec.encode(data);
11621
11599
  }
@@ -11633,7 +11611,7 @@ var DataplyAPI = class {
11633
11611
  if (!this.initialized) {
11634
11612
  throw new Error("Dataply instance is not initialized");
11635
11613
  }
11636
- return this.runWithDefaultWrite(async (tx2) => {
11614
+ return this.withWriteTransaction(async (tx2) => {
11637
11615
  decrementRowCount = decrementRowCount ?? true;
11638
11616
  await this.rowTableEngine.delete(pk, decrementRowCount, tx2);
11639
11617
  }, tx);
@@ -11643,7 +11621,7 @@ var DataplyAPI = class {
11643
11621
  if (!this.initialized) {
11644
11622
  throw new Error("Dataply instance is not initialized");
11645
11623
  }
11646
- return this.runWithDefault(async (tx2) => {
11624
+ return this.withReadTransaction(async (tx2) => {
11647
11625
  const data = await this.rowTableEngine.selectByPK(pk, tx2);
11648
11626
  if (data === null) return null;
11649
11627
  if (asRaw) return data;
@@ -11655,7 +11633,7 @@ var DataplyAPI = class {
11655
11633
  if (!this.initialized) {
11656
11634
  throw new Error("Dataply instance is not initialized");
11657
11635
  }
11658
- return this.runWithDefault(async (tx2) => {
11636
+ return this.withReadTransaction(async (tx2) => {
11659
11637
  const results = await this.rowTableEngine.selectMany(pks, tx2);
11660
11638
  return results.map((data) => {
11661
11639
  if (data === null) return null;
@@ -11672,7 +11650,7 @@ var DataplyAPI = class {
11672
11650
  if (!this.initialized) {
11673
11651
  throw new Error("Dataply instance is not initialized");
11674
11652
  }
11675
- return this.runWithDefaultWrite(() => {
11653
+ return this.withWriteTransaction(() => {
11676
11654
  return this.hook.trigger("close", void 0, async () => {
11677
11655
  await this.pfs.close();
11678
11656
  import_node_fs3.default.closeSync(this.fileHandle);
@@ -11703,6 +11681,24 @@ var Dataply = class {
11703
11681
  createTransaction() {
11704
11682
  return this.api.createTransaction();
11705
11683
  }
11684
+ /**
11685
+ * Runs a write callback within a transaction context.
11686
+ */
11687
+ async withWriteTransaction(callback, tx) {
11688
+ return this.api.withWriteTransaction(callback, tx);
11689
+ }
11690
+ /**
11691
+ * Runs a read callback within a transaction context.
11692
+ */
11693
+ async withReadTransaction(callback, tx) {
11694
+ return this.api.withReadTransaction(callback, tx);
11695
+ }
11696
+ /**
11697
+ * Runs a generator callback function within a transaction context.
11698
+ */
11699
+ async *withReadStreamTransaction(callback, tx) {
11700
+ return this.api.withReadStreamTransaction(callback, tx);
11701
+ }
11706
11702
  /**
11707
11703
  * Initializes the dataply instance.
11708
11704
  * Must be called before using the dataply instance.
@@ -11769,10 +11765,42 @@ var Dataply = class {
11769
11765
  };
11770
11766
 
11771
11767
  // src/core/transaction/GlobalTransaction.ts
11772
- var GlobalTransaction = class {
11768
+ var GlobalTransaction = class _GlobalTransaction {
11773
11769
  transactions = [];
11774
11770
  isCommitted = false;
11775
11771
  isRolledBack = false;
11772
+ /**
11773
+ * Executes a global transaction across multiple Dataply instances using a callback.
11774
+ * Locks are acquired in the order instances are provided.
11775
+ * @param dbs Array of Dataply instances
11776
+ * @param callback Function to execute with the array of Transactions
11777
+ */
11778
+ static async Run(dbs, callback) {
11779
+ const globalTx = new _GlobalTransaction();
11780
+ const txs = [];
11781
+ const releases = [];
11782
+ try {
11783
+ for (const db of dbs) {
11784
+ const release = await db.api.acquireWriteLock();
11785
+ releases.push(release);
11786
+ const tx = db.api.createTransaction();
11787
+ tx.__setWriteLockRelease(release);
11788
+ txs.push(tx);
11789
+ globalTx.add(tx);
11790
+ }
11791
+ const result = await callback(txs);
11792
+ await globalTx.commit();
11793
+ return result;
11794
+ } catch (e) {
11795
+ await globalTx.rollback();
11796
+ for (let i = txs.length; i < releases.length; i++) {
11797
+ releases[i]();
11798
+ }
11799
+ throw e;
11800
+ }
11801
+ }
11802
+ constructor() {
11803
+ }
11776
11804
  /**
11777
11805
  * Adds a transaction to the global transaction.
11778
11806
  * @param tx Transaction to add
@@ -18,7 +18,19 @@ export declare class Dataply {
18
18
  * A transaction must be terminated by calling either `commit` or `rollback`.
19
19
  * @returns Transaction object
20
20
  */
21
- createTransaction(): Transaction;
21
+ protected createTransaction(): Transaction;
22
+ /**
23
+ * Runs a write callback within a transaction context.
24
+ */
25
+ withWriteTransaction<T>(callback: (tx: Transaction) => Promise<T>, tx?: Transaction): Promise<T>;
26
+ /**
27
+ * Runs a read callback within a transaction context.
28
+ */
29
+ withReadTransaction<T>(callback: (tx: Transaction) => Promise<T>, tx?: Transaction): Promise<T>;
30
+ /**
31
+ * Runs a generator callback function within a transaction context.
32
+ */
33
+ withReadStreamTransaction<T>(callback: (tx: Transaction) => AsyncGenerator<T>, tx?: Transaction): AsyncGenerator<T>;
22
34
  /**
23
35
  * Initializes the dataply instance.
24
36
  * Must be called before using the dataply instance.
@@ -89,22 +89,13 @@ export declare class DataplyAPI {
89
89
  * @returns Transaction object
90
90
  */
91
91
  createTransaction(): Transaction;
92
- /**
93
- * Runs a callback function within a transaction context.
94
- * If no transaction is provided, a new transaction is created.
95
- * The transaction is committed if the callback completes successfully,
96
- * or rolled back if an error occurs.
97
- * @param callback The callback function to run within the transaction context.
98
- * @param tx The transaction to use. If not provided, a new transaction is created.
99
- * @returns The result of the callback function.
100
- */
101
92
  /**
102
93
  * Acquires the global write lock.
103
94
  * Returns a release function that MUST be called to unlock.
104
95
  * Used internally by runWithDefaultWrite.
105
96
  * @returns A release function
106
97
  */
107
- protected acquireWriteLock(): Promise<() => void>;
98
+ acquireWriteLock(): Promise<() => void>;
108
99
  /**
109
100
  * Runs a write callback within a transaction context with global write serialization.
110
101
  * If no transaction is provided, a new transaction is created, committed on success, rolled back on error.
@@ -114,8 +105,8 @@ export declare class DataplyAPI {
114
105
  * @param tx Optional external transaction.
115
106
  * @returns The result of the callback.
116
107
  */
117
- protected runWithDefaultWrite<T>(callback: (tx: Transaction) => Promise<T>, tx?: Transaction): Promise<T>;
118
- protected runWithDefault<T>(callback: (tx: Transaction) => Promise<T>, tx?: Transaction): Promise<T>;
108
+ withWriteTransaction<T>(callback: (tx: Transaction) => Promise<T>, tx?: Transaction): Promise<T>;
109
+ withReadTransaction<T>(callback: (tx: Transaction) => Promise<T>, tx?: Transaction): Promise<T>;
119
110
  /**
120
111
  * Runs a generator callback function within a transaction context.
121
112
  * Similar to runWithDefault but allows yielding values from an AsyncGenerator.
@@ -126,7 +117,7 @@ export declare class DataplyAPI {
126
117
  * @param tx The transaction to use. If not provided, a new transaction is created.
127
118
  * @returns An AsyncGenerator that yields values from the callback.
128
119
  */
129
- protected streamWithDefault<T>(callback: (tx: Transaction) => AsyncGenerator<T>, tx?: Transaction): AsyncGenerator<T>;
120
+ withReadStreamTransaction<T>(callback: (tx: Transaction) => AsyncGenerator<T>, tx?: Transaction): AsyncGenerator<T>;
130
121
  /**
131
122
  * Retrieves metadata from the dataply.
132
123
  * @returns Metadata of the dataply.
@@ -1,4 +1,5 @@
1
1
  import { Transaction } from './Transaction';
2
+ import { Dataply } from '../Dataply';
2
3
  /**
3
4
  * Global Transaction Manager.
4
5
  * Coordinates transactions across multiple instances (shards).
@@ -9,19 +10,27 @@ export declare class GlobalTransaction {
9
10
  private transactions;
10
11
  private isCommitted;
11
12
  private isRolledBack;
13
+ /**
14
+ * Executes a global transaction across multiple Dataply instances using a callback.
15
+ * Locks are acquired in the order instances are provided.
16
+ * @param dbs Array of Dataply instances
17
+ * @param callback Function to execute with the array of Transactions
18
+ */
19
+ static Run<T>(dbs: Dataply[], callback: (txs: Transaction[]) => Promise<T>): Promise<T>;
20
+ protected constructor();
12
21
  /**
13
22
  * Adds a transaction to the global transaction.
14
23
  * @param tx Transaction to add
15
24
  */
16
- add(tx: Transaction): void;
25
+ protected add(tx: Transaction): void;
17
26
  /**
18
27
  * Commits all transactions.
19
28
  * Note: This is now a single-phase commit. For true atomicity across shards,
20
29
  * each instance's WAL provides durability, but cross-shard atomicity is best-effort.
21
30
  */
22
- commit(): Promise<void>;
31
+ protected commit(): Promise<void>;
23
32
  /**
24
33
  * Rolls back all transactions.
25
34
  */
26
- rollback(): Promise<void>;
35
+ protected rollback(): Promise<void>;
27
36
  }
@@ -21,7 +21,7 @@ export declare class Transaction {
21
21
  /** List of callbacks to execute on commit */
22
22
  private commitHooks;
23
23
  /** Nested MVCC Transaction for snapshot isolation (lazy init) */
24
- private mvccTx;
24
+ private readonly mvccTx;
25
25
  /** Root MVCC Transaction reference */
26
26
  private readonly rootTx;
27
27
  /** Release function for global write lock, set by DataplyAPI */
@@ -34,12 +34,6 @@ export declare class Transaction {
34
34
  * @param pfs Page File System
35
35
  */
36
36
  constructor(id: number, context: TransactionContext, rootTx: AsyncMVCCTransaction<PageMVCCStrategy, number, Uint8Array>, lockManager: LockManager, pfs: PageFileSystem);
37
- /**
38
- * Lazily initializes the nested MVCC transaction.
39
- * This ensures the snapshot is taken at the time of first access,
40
- * picking up the latest committed root version.
41
- */
42
- private ensureMvccTx;
43
37
  /**
44
38
  * Registers a commit hook.
45
39
  * @param hook Function to execute
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "dataply",
3
- "version": "0.0.26-alpha.7",
3
+ "version": "0.0.26-alpha.8",
4
4
  "description": "A lightweight storage engine for Node.js with support for MVCC, WAL.",
5
5
  "license": "MIT",
6
6
  "author": "izure <admin@izure.org>",
@@ -51,4 +51,4 @@
51
51
  "ryoiki": "^1.2.0",
52
52
  "serializable-bptree": "^9.0.1"
53
53
  }
54
- }
54
+ }
package/readme.md CHANGED
@@ -105,23 +105,17 @@ db.init().then(() => {
105
105
  ## Transaction Management
106
106
 
107
107
  ### Explicit Transactions
108
- You can group multiple operations into a single unit of work to ensure atomicity.
108
+ You can group multiple operations into a single unit of work to ensure atomicity. The `withWriteTransaction` method handles the transaction lifecycle automatically, committing on success and rolling back on failure.
109
109
 
110
110
  ```typescript
111
- const tx = dataply.createTransaction()
112
-
113
- try {
114
- await dataply.insert('Data 1', tx)
111
+ await dataply.withWriteTransaction(async (tx) => {
112
+ const pk = await dataply.insert('Data 1', tx)
115
113
  await dataply.update(pk, 'Updated Data', tx)
116
-
117
- await tx.commit() // Persist changes to disk and clear WAL on success
118
- } catch (error) {
119
- await tx.rollback() // Revert all changes on failure (Undo)
120
- }
114
+ }) // Persists changes automatically on success or rolls back on failure
121
115
  ```
122
116
 
123
117
  ### Global Transactions
124
- You can perform atomic operations across multiple `Dataply` instances using the `GlobalTransaction` class. This uses a **2-Phase Commit (2PC)** mechanism to ensure that either all instances commit successfully or all are rolled back.
118
+ You can perform atomic operations across multiple `Dataply` instances using the `GlobalTransaction` class. This safely acquires write locks on all instances sequentially and manages the transaction lifecycle to ensure either all instances commit successfully or all are rolled back.
125
119
 
126
120
  ```typescript
127
121
  import { Dataply, GlobalTransaction } from 'dataply'
@@ -132,22 +126,13 @@ const db2 = new Dataply('./db2.db', { wal: './db2.wal' })
132
126
  await db1.init()
133
127
  await db2.init()
134
128
 
135
- const tx1 = db1.createTransaction()
136
- const tx2 = db2.createTransaction()
137
-
138
- const globalTx = new GlobalTransaction()
139
- globalTx.add(tx1)
140
- globalTx.add(tx2)
141
-
142
129
  try {
143
- await db1.insert('Data for DB1', tx1)
144
- await db2.insert('Data for DB2', tx2)
145
-
146
- // Commit transactions across all instances
147
- // Note: This is a best-effort atomic commit.
148
- await globalTx.commit()
130
+ await GlobalTransaction.Run([db1, db2], async ([tx1, tx2]) => {
131
+ await db1.insert('Data for DB1', tx1)
132
+ await db2.insert('Data for DB2', tx2)
133
+ })
149
134
  } catch (error) {
150
- await globalTx.rollback()
135
+ console.error('Global transaction failed and rolled back.', error)
151
136
  }
152
137
  ```
153
138
 
@@ -196,30 +181,22 @@ Marks data as deleted.
196
181
  #### `async getMetadata(tx?: Transaction): Promise<DataplyMetadata>`
197
182
  Returns the current metadata of the dataply, including `pageSize`, `pageCount`, and `rowCount`.
198
183
 
199
- #### `createTransaction(): Transaction`
200
- Creates a new transaction instance.
184
+ #### `async withWriteTransaction<T>(callback: (tx: Transaction) => Promise<T>, tx?: Transaction): Promise<T>`
185
+ Executes write operations within a serialized write-lock transaction. Automatically commits on success and rolls back on failure if creating a new internal transaction.
201
186
 
202
- #### `async close(): Promise<void>`
203
- Closes the file handles and shuts down safely.
204
-
205
- ### Transaction Class
187
+ #### `async withReadTransaction<T>(callback: (tx: Transaction) => Promise<T>, tx?: Transaction): Promise<T>`
188
+ Executes read operations within a transaction context.
206
189
 
207
- #### `async commit(): Promise<void>`
208
- Permanently reflects all changes made during the transaction to disk and releases locks.
190
+ #### `async *withReadStreamTransaction<T>(callback: (tx: Transaction) => AsyncGenerator<T>, tx?: Transaction): AsyncGenerator<T>`
191
+ Executes streaming read operations using an async generator in a transaction.
209
192
 
210
- #### `async rollback(): Promise<void>`
211
- Cancels all changes made during the transaction and restores the original state.
193
+ #### `async close(): Promise<void>`
194
+ Closes the file handles and shuts down safely.
212
195
 
213
196
  ### GlobalTransaction Class
214
197
 
215
- #### `add(tx: Transaction): void`
216
- Registers a transaction from a Dataply instance to the global unit.
217
-
218
- #### `async commit(): Promise<void>`
219
- Executes a coordinated commit across all registered transactions. Note that without a prepare phase, this is a best-effort atomic commit.
220
-
221
- #### `async rollback(): Promise<void>`
222
- Rolls back all registered transactions simultaneously.
198
+ #### `static async Run<T>(dbs: Dataply[], callback: (txs: Transaction[]) => Promise<T>): Promise<T>`
199
+ Executes a callback-based global transaction across multiple Dataply instances sequentially locking them to prevent deadlocks, providing true atomicity within the Dataply nodes. Automatically commits on success and rolls back on failure.
223
200
 
224
201
  ## Extending Dataply
225
202