Transações e savepoints¶
Problema: algumas operações só fazem sentido juntas — debitar de uma conta e creditar em outra, criar um pedido e baixar o estoque. Se a segunda falha, a primeira não pode ficar de pé.
Solução: engine.transaction(fn) roda seu bloco numa transação: commit se tudo
der certo, rollback automático se qualquer coisa lançar. Pra rollback parcial, há
beginNested (savepoints).
A teoria em uma frase¶
transaction(fn) faz BEGIN, roda fn, e então COMMIT — mas se fn lança, faz
ROLLBACK e re-lança o erro. Você nunca precisa lembrar de fechar a transação na mão.
Tudo ou nada¶
import { createEngine, insert, update } from "tempest-db-js";
const engine = createEngine("sqlite:///bank.db");
await engine.transaction(async (tx) => {
await tx.execute(update(Account).set({ balance: 90 }).where({ id: 1 }));
await tx.execute(update(Account).set({ balance: 110 }).where({ id: 2 }));
// chegou aqui sem lançar → COMMIT automático
});
Se a segunda linha lançar (saldo insuficiente, violação de constraint, etc.), a primeira é desfeita — as duas contas voltam ao estado anterior.
Não capture o erro silenciosamente dentro do bloco
O rollback é disparado pela exceção propagando pra fora de fn. Se você
try/catch tudo lá dentro e engole o erro, o transaction acha que deu certo e
faz commit. Deixe o erro subir (ou re-lance) quando quiser abortar.
Savepoints: rollback parcial¶
beginNested cria um savepoint — uma sub-transação que pode reverter sozinha sem
derrubar a transação externa. Útil pra "tente isso; se falhar, siga sem ele":
await engine.transaction(async (tx) => {
await tx.execute(insert(Order).values({ userId: 1, total: "100.00" }));
try {
await tx.beginNested(async (sp) => {
await sp.execute(insert(Coupon).values({ orderId: 1, code: "INVALIDO" }));
// se isso violar uma constraint, só ESTE savepoint reverte
});
} catch {
// o cupom falhou, mas o pedido continua de pé — seguimos sem desconto
}
await tx.execute(update(Order).set({ status: "confirmado" }).where({ id: 1 }));
// COMMIT: pedido criado + confirmado; cupom descartado
});
Sync também tem transação
Em SQLite síncrono (createSyncEngine), engine.transaction((tx) => { ... })
funciona igual — sem await. Ótimo pra seeds e scripts.
Recap¶
engine.transaction(fn)→COMMITno sucesso,ROLLBACKsefnlançar.- Deixe o erro propagar pra abortar — engolir o erro causa commit indevido.
tx.beginNested(fn)→ savepoint; reverte só a sub-transação.- Vale pra async (
createEngine) e sync (createSyncEngine).