Skip to main content

Documentation Index

Fetch the complete documentation index at: https://mintlify.com/mainser/cindel/llms.txt

Use this file to discover all available pages before exploring further.

Transactions are the mechanism that makes multi-step database operations safe. Without an explicit transaction, a crash or exception halfway through two related writes can leave your data in an inconsistent state — for example, a user row written but its corresponding account row missing. Cindel’s writeTxn and readTxn helpers let you group any number of operations into a single atomic unit so either all of them succeed together or none of them are visible at all.

Write Transactions — writeTxn

Wrap any set of writes in writeTxn to commit them all together. If the callback throws — for any reason — Cindel rolls back all pending changes and watchers are not notified:
await db.writeTxn(() async {
  await db.users.put(ada);
  await db.accounts.put(account);
});
// Both writes are visible now, or neither is.
The callback is typed as Future<T> Function(), so you can return a value:
final id = await db.writeTxn(() async {
  await db.users.put(ada);
  return ada.dbId;
});

Rollback on Exception

If your callback throws, Cindel catches the exception, rolls back the native transaction, rethrows the original error, and skips all watcher notifications for that transaction:
try {
  await db.writeTxn(() async {
    await db.users.put(ada);
    throw Exception('Something went wrong');
    // account is never written; ada's write is also rolled back.
    await db.accounts.put(account);
  });
} catch (e) {
  // Neither user nor account was stored.
}

Read Transactions — readTxn

readTxn provides a consistent snapshot of the database for the duration of the callback. Every read inside readTxn sees the same logical version of the data even if another isolate or write transaction commits in the middle:
final users = await db.readTxn(() {
  return db.users.filter().activeEqualTo(true).findAll();
});
Attempting a write operation (such as put or delete) inside a readTxn throws CindelTransactionError immediately:
await db.readTxn(() async {
  // ❌ This throws CindelTransactionError.
  await db.users.put(ada);
});

Why Transactions Matter

Atomicity Across Collections

When you write to two collections that must always be in sync — for example, creating a user and provisioning its account at the same time — wrapping both writes in a single writeTxn ensures the change is atomic:
await db.writeTxn(() async {
  // Allocates id on ada, then stores the document.
  await db.users.put(ada);

  // Uses ada.dbId (now assigned) to link the account.
  account.userId = ada.dbId;
  await db.accounts.put(account);
});
Without the explicit transaction, a crash between the two put calls would leave a user row without a corresponding account row.

Consistent Multi-Read Snapshots

Without a read transaction, two sequential reads might observe different versions of the data if a concurrent write commits between them. readTxn pins the snapshot:
final (users, accounts) = await db.readTxn(() async {
  final u = await db.users.filter().activeEqualTo(true).findAll();
  final a = await db.accounts.all().findAll();
  return (u, a);
});
// users and accounts reflect the same database version.

Auto-Wrapping for Single Writes

Single writes outside an explicit transaction are auto-wrapped by Cindel internally, so they are still atomic. This means calling db.users.put(ada) directly is safe — it will not partially commit. The recommendation to use explicit writeTxn applies specifically when you need multiple writes to succeed or fail together:
// Safe for one write — Cindel wraps it internally.
await db.users.put(ada);

// Use explicit writeTxn when two or more writes must be atomic.
await db.writeTxn(() async {
  await db.users.put(ada);
  await db.accounts.put(account);
});

Nested Transactions

Cindel does not support nested transactions. Calling writeTxn or readTxn while already inside a transaction on the same database handle throws CindelTransactionError:
await db.writeTxn(() async {
  // ❌ Throws CindelTransactionError — nested transactions are not supported.
  await db.writeTxn(() async {
    await db.users.put(ada);
  });
});
Nested transactions throw on all backends including Web. If helper functions need to perform writes, check db.isInWriteTransaction and skip the outer writeTxn when one is already active, or structure your code so writes are always initiated from the outermost transaction.

Query-Based Mutations Inside writeTxn

Query-based updateAll and deleteAll can be called inside an explicit writeTxn on native backends (both MDBX and SQLite). They participate in the same transaction and will be rolled back on failure:
await db.writeTxn(() async {
  await db.users.put(ada);

  // Query delete inside the same transaction — rolled back if put throws.
  await db.users
      .filter()
      .activeEqualTo(false)
      .deleteAll();
});
Query-based updateAll is not supported when Cindel sync is enabled. Sync requires a canonical per-document snapshot for each outgoing mutation; query updates affect many rows without producing individual snapshots. Rewrite the matching objects individually and call putAll instead, or open the database without sync if you need bulk field updates.

Build docs developers (and LLMs) love