transaction static method
Creates an atomic multi-cell transaction with configurable isolation.
A transaction allows you to group multiple cell updates into a single atomic unit – either all changes are applied, or none are. This is essential for maintaining consistency across related cells when a logical operation spans multiple state atoms.
When to use
Use this when you need to update several cells together atomically. The simplest usage is:
final tx = Cell.transaction();
await tx.begin([accountA, accountB]);
tx.update(accountA, 100);
tx.update(accountB, 200);
await tx.commit();
Use transactions when:
- Multiple cells must change together (all or nothing).
- You need to read values and base updates on them consistently.
- You want to avoid partial updates that could leave the system in an inconsistent state.
- You're implementing financial transfers, inventory adjustments, or any operation with invariants across cells.
How it works
- Begin: Register participants and (optionally) snapshot values.
- Update: Buffer writes – cells are not actually modified yet.
- Read: Observe values according to the isolation level.
- Commit: Acquire locks, validate, apply all changes atomically.
- Rollback: Discard buffered changes.
Non‑obvious
- Locks are held only during commit, not across begin→commit. This means you can perform long-running logic between begin and commit without holding locks.
- Validation happens during commit, not during update. This allows you to stage changes and then decide whether to commit.
- If any validation fails, the entire transaction is rolled back automatically – no partial updates.
- The transaction scope is not reentrant – you must commit or rollback before starting another transaction.
Parameters:
options: Configuration for isolation level, lock ordering, timeout, validation, and application logic. Defaults to TransactionOptions withreadCommittedisolation.
Returns:
A TransactionScope handle with methods to control the transaction.
Example 1: Basic Transfer
final tx = Cell.transaction();
// Begin with two accounts
await tx.begin([fromAccount, toAccount]);
// Read current balances
final fromBalance = tx.read(fromAccount) as int;
final toBalance = tx.read(toAccount) as int;
// Update balances
tx.update(fromAccount, fromBalance - 50);
tx.update(toAccount, toBalance + 50);
// Commit atomically
await tx.commit();
Example 2: With Savepoint
final tx = Cell.transaction();
await tx.begin([cell1, cell2, cell3]);
tx.update(cell1, 10);
tx.update(cell2, 20);
// Create a checkpoint
final sp = tx.savepoint();
// Speculative updates
tx.update(cell2, 30);
tx.update(cell3, 40);
// Something went wrong – rollback to checkpoint
await tx.rollback(savepoint: sp);
// cell1 = 10, cell2 = 20, cell3 unchanged
await tx.commit();
Example 3: Repeatable Read Isolation
final tx = Cell.transaction(TransactionOptions(
isolation: IsolationLevel.repeatableRead,
timeout: Duration(seconds: 5),
onEvent: (e) => print(e),
));
await tx.begin([accountA, accountB]);
// Both reads return the snapshot from begin
final a = tx.read(accountA) as int;
final b = tx.read(accountB) as int;
// If another transaction changed accountA after begin,
// commit will throw TransactionConflictException
tx.update(accountA, a + 100);
await tx.commit();
See also:
- TransactionOptions – configuration for the transaction.
- TransactionScope – the handle returned by this factory.
- IsolationLevel – consistency guarantees.
- TransactionValidationException – thrown when validation fails.
- TransactionConflictException – thrown on isolation conflicts.
- Example: See
example/transaction_demo.dartfor a complete executable walkthrough.
Implementation
static TransactionScope transaction([
TransactionOptions options = const TransactionOptions(),
]) =>
_transaction(options);