transaction static method

TransactionScope transaction([
  1. TransactionOptions options = const TransactionOptions()
])

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

  1. Begin: Register participants and (optionally) snapshot values.
  2. Update: Buffer writes – cells are not actually modified yet.
  3. Read: Observe values according to the isolation level.
  4. Commit: Acquire locks, validate, apply all changes atomically.
  5. 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 with readCommitted isolation.

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:

Implementation

static TransactionScope transaction([
  TransactionOptions options = const TransactionOptions(),
]) =>
    _transaction(options);