Advanced Topics

Transactions

Transactions in Tauri SQLite ORM.

A transaction is a sequence of operations performed as a single logical unit of work. The key property of a transaction is atomicity: either all of its operations succeed and are saved (COMMIT), or none of them are (ROLLBACK). This guarantees that your database is never left in a partially updated, inconsistent state.

This is essential for operations like transferring funds between two accounts, where you must both debit one account and credit another. If the credit operation fails, the debit must be undone.

The db.transaction() Method

The ORM provides a db.transaction() method that makes managing transactions straightforward and safe. It takes a single argument: an async callback function.

try {
  const result = await db.transaction(async (tx) => {
    // All your database logic goes here
    // ...
    return 'Success!';
  });
  console.log('Transaction committed:', result);
} catch (error) {
  console.error('Transaction rolled back:', error.message);
}

How It Works

  1. BEGIN TRANSACTION: Before your callback is executed, the ORM issues a BEGIN TRANSACTION command.
  2. The tx Object: Your callback receives a special tx object. This object is a transactional version of the main db instance. You must use tx for all queries inside the callback (tx.select, tx.update, etc.) to ensure they are part of the transaction.
  3. COMMIT: If your callback function runs to completion and returns a value (or a resolving Promise), the ORM automatically issues a COMMIT command, making all changes permanent.
  4. ROLLBACK: If your callback function throws an error, the ORM catches it and issues a ROLLBACK command, undoing all changes made since the transaction began. The error is then re-thrown so you can handle it in a catch block.

Example: A Safe Bank Transfer

Here is a practical example of transferring a balance between two user accounts.

import { db } from '$lib/db';
import { users } from '$lib/db/schema';
import { eq, sql } from '@type32/tauri-sqlite-orm';

async function transferFunds(fromUserId: number, toUserId: number, amount: number) {
  if (amount <= 0) {
    throw new Error('Transfer amount must be positive.');
  }

  try {
    await db.transaction(async (tx) => {
      // 1. Debit the sender's account using decrement
      const result = await tx.update(users)
        .decrement('balance', amount)
        .where(eq(users._.columns.id, fromUserId))
        .execute();

      // If the sender didn't exist, rowsAffected will be 0
      if (result[0].rowsAffected === 0) {
        throw new Error(`Sender with ID ${fromUserId} not found.`);
      }

      // 2. Check if the sender has sufficient funds
      const sender = await tx.select(users, ['balance'])
        .where(eq(users._.columns.id, fromUserId))
        .get();
      
      if (sender.balance < 0) {
        // This error will trigger an automatic rollback!
        throw new Error('Insufficient funds.');
      }
      
      // 3. Credit the receiver's account using increment
      await tx.update(users)
        .increment('balance', amount)
        .where(eq(users._.columns.id, toUserId))
        .execute();
      
      console.log('Transfer steps completed within transaction.');
    });
    
    console.log('Transaction successful! Funds have been transferred.');

  } catch (error) {
    console.error(`Transaction failed: ${error.message}. All changes were rolled back.`);
  }
}
Copyright © 2026