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
- BEGIN TRANSACTION: Before your callback is executed, the ORM issues a
BEGIN TRANSACTIONcommand. - The
txObject: Your callback receives a specialtxobject. This object is a transactional version of the maindbinstance. You must usetxfor all queries inside the callback (tx.select,tx.update, etc.) to ensure they are part of the transaction. - COMMIT: If your callback function runs to completion and returns a value (or a resolving
Promise), the ORM automatically issues aCOMMITcommand, making all changes permanent. - ROLLBACK: If your callback function throws an error, the ORM catches it and issues a
ROLLBACKcommand, undoing all changes made since the transaction began. The error is then re-thrown so you can handle it in acatchblock.
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.`);
}
}