Transactions
jed has a single writer: at most one write transaction at a time, with readers never blocked except during the brief commit. A transaction’s changes apply all-or-nothing.
The update helper runs a read-write transaction that commits on success and rolls back if your code
signals an error — the safest default; there’s a read-only view helper too. Both are available in
every language (Rust, Go, and TypeScript), on the Database handle (where each call mints a fresh
session) or on a session you’ve minted. For finer control, the explicit begin / commit / rollback form lives on a session — mint one with db.session(...) and drive the block across
calls.
use jed::Database;
fn main() -> jed::Result<()> {
let mut db = Database::open("bank.jed")?;
// update() runs a read-write transaction: it mints a session, runs the closure, commits on
// success, and rolls back if the closure returns an error — so the two writes are atomic. view()
// is the read-only sibling. (For an explicit block spanning calls, mint a Session and drive
// begin/commit/rollback on it: `let mut s = db.session(SessionOptions::default());`.)
db.update(|tx| {
tx.execute("UPDATE account SET balance = balance - 100 WHERE id = 1", &[])?;
tx.execute("UPDATE account SET balance = balance + 100 WHERE id = 2", &[])?;
Ok(())
})?;
Ok(())
}
package main
import (
"context"
"log"
jed "github.com/jackc/jed/impl/go"
)
func main() {
db, err := jed.OpenDatabase("bank.jed")
if err != nil {
log.Fatal(err)
}
defer db.Close()
ctx := context.Background()
// Update runs a read-write transaction: it mints a session, runs the callback, commits on success,
// and rolls back if the callback returns an error — so the two writes are atomic. View is the
// read-only sibling. (For an explicit block spanning calls, mint a Session with db.Session(...)
// and drive Begin/Commit/Rollback on it.)
err = db.Update(func(tx *jed.Transaction) error {
if _, err := tx.Exec(ctx, "UPDATE account SET balance = balance - 100 WHERE id = 1"); err != nil {
return err
}
_, err := tx.Exec(ctx, "UPDATE account SET balance = balance + 100 WHERE id = 2")
return err
})
if err != nil {
log.Fatal(err)
}
}
import { openDatabase } from 'jed-ts';
const db = openDatabase('bank.jed');
// update() runs a read-write transaction: it mints a session, runs the callback, commits on success,
// and rolls back if the callback throws — so the two writes are atomic. view() is the read-only
// sibling. (For an explicit block spanning calls, mint a session with db.session({}) and drive
// begin/commit/rollback on it.)
db.update((tx) => {
tx.execute('UPDATE account SET balance = balance - 100 WHERE id = 1');
tx.execute('UPDATE account SET balance = balance + 100 WHERE id = 2');
});
db.close();
Isolation
Readers see the last committed state and run without blocking against an in-flight writer; the only exclusive moment is the commit itself. This is not MVCC — there is exactly one committed version plus the current writer’s pending changes. It keeps the model simple and the read path nearly lock-free.