For AI agents: the complete documentation index is available at https://silt-db.dev/llms.txt, the full documentation bundle is available at https://silt-db.dev/llms-full.txt, and this page is available as Markdown at https://silt-db.dev/guide/transactions.md.

Transactions

Use a transaction when multiple operations must succeed or fail together. Silt commits when the callback completes successfully and rolls back when it throws or returns a rejected promise.

The callback receives a transaction-scoped database handle, tx. Get every collection used inside the callback from that handle. This explicit scope works across Node and Expo without relying on Node-only asynchronous context APIs.

This complete example moves 40 credits from Alice to Bob. The debit includes its balance check in the SQL filter. If either account update fails, the transaction rolls back both changes.

import { openDatabase } from '@silt-db/sqlite';

const db = await openDatabase(':memory:');
await db.collection('accounts').insertMany([
  { _id: 'alice', balance: 100 },
  { _id: 'bob', balance: 25 },
]);

await db.transaction(async (tx) => {
  const accounts = tx.collection('accounts');

  const debit = await accounts.updateOne(
    { _id: 'alice', balance: { $gte: 40 } },
    { $inc: { balance: -40 } },
  );
  if (debit.matchedCount !== 1) throw new Error('Insufficient balance');

  const credit = await accounts.updateOne({ _id: 'bob' }, { $inc: { balance: 40 } });
  if (credit.matchedCount !== 1) throw new Error('Recipient does not exist');
});

console.log(await db.collection('accounts').find().sort({ _id: 1 }).toArray());
// [{ _id: 'alice', balance: 60 }, { _id: 'bob', balance: 65 }]

The condition on balance and the document updates execute in SQLite. JavaScript makes the application's commit-or-rollback decision from each operation's result.

Do not call db.collection(...) or use a collection obtained from the outer db inside the callback. Those operations join the queue outside the transaction. Awaiting one there can wait for the very transaction whose callback is still running.

Transaction handles expire when the callback finishes. Do not retain a tx collection for later use, close tx yourself, or start unawaited work inside the callback.

Roll back part of a transaction

Nested transactions use SQLite savepoints. An error rolls back the nested callback's changes; the outer callback can catch it and continue. This example continues from the accounts above:

await db.transaction(async (tx) => {
  await tx.collection('accounts').updateOne({ _id: 'alice' }, { $inc: { balance: 5 } });

  try {
    await tx.transaction(async (nested) => {
      await nested.collection('accounts').updateOne({ _id: 'bob' }, { $inc: { balance: 5 } });
      throw new Error('Cancel this bonus');
    });
  } catch (error) {
    if (error.message !== 'Cancel this bonus') throw error;
  }
});

console.log(await db.collection('accounts').find().sort({ _id: 1 }).toArray());
// [{ _id: 'alice', balance: 65 }, { _id: 'bob', balance: 65 }]

await db.close();

Alice's change commits. Bob's nested change rolls back. Without the catch, the error would also reject the outer callback and roll back Alice's change.

Keep transactions short

The driver reserves its connection across asynchronous gaps until the transaction commits or rolls back. Other operations through that managed connection wait. Silt also coordinates supported Node handles for the same file within one process; other processes still use SQLite's locking behavior.

Await each database operation and keep network requests, UI waits, and unrelated slow work outside the transaction. Send notifications or perform external side effects after a successful commit: rolling back SQLite cannot undo an email or HTTP request.

Use Silt's handle consistently when adapting an existing connection. Directly using the native SQLite connection or another transaction manager concurrently bypasses Silt's scheduling. Custom drivers are responsible for providing the same isolation guarantees; see the database and driver reference.

Batch writes already have atomic scope

insertMany() and multi-document updates are atomic across the Silt operation. A failure rolls back the batch. Use an explicit transaction when you need to combine separate calls, operate across collections, or include a checked read and subsequent writes in one scope.

These batch guarantees differ from ordinary MongoDB batches, which can retain earlier successful writes. Silt provides SQLite transactions, not MongoDB sessions or distributed transactions. The compatibility contract documents this distinction.