Skip to content

Add c.db.transaction((tx) => ...) for safe actor-local SQLite transactions #4232

@NathanFlurry

Description

@NathanFlurry

Problem

Today transactions are expressed manually with raw SQL:

await c.db.execute("BEGIN");
try {
  // ...queries...
  await c.db.execute("COMMIT");
} catch (error) {
  await c.db.execute("ROLLBACK");
  throw error;
}

c.db is a shared actor-local database handle, so other c.db.execute(...) calls can interleave while a transaction is open (for example from concurrent actions). This makes transaction safety easy to get wrong.

Proposal

Add a first-class transaction API:

await c.db.transaction(async (tx) => {
  await tx.execute("INSERT INTO todos (title) VALUES (?)", title);
  await tx.execute(
    "INSERT INTO comments (todo_id, body) VALUES (last_insert_rowid(), ?)",
    body,
  );
});

Expected behavior

  • transaction acquires exclusive access for the callback duration.
  • Other c.db operations are queued until transaction commit/rollback.
  • Automatically COMMIT on success.
  • Automatically ROLLBACK on throw.
  • tx only exposes transaction-safe operations (e.g. tx.execute).

Why this helps

  • Prevents interleaving bugs with BEGIN/COMMIT.
  • Makes docs/examples safer and simpler.
  • Aligns with ergonomics from other SQLite libraries.

Optional follow-ups

  • Decide nested transaction behavior (SAVEPOINT or disallow nesting).
  • Add typed return support from callback (transaction<T>(...) => Promise<T>).

Metadata

Metadata

Assignees

No one assigned

    Labels

    No labels
    No labels

    Type

    No type

    Projects

    No projects

    Milestone

    No milestone

    Relationships

    None yet

    Development

    No branches or pull requests

    Issue actions