Transactions
Hysteria ORM provides robust transaction support for SQL databases, allowing you to group multiple operations into a single atomic unit. Transactions ensure data consistency and integrity, supporting features like rollback, isolation levels, nested, concurrent, and global transactions.
Basic Usage
Use SqlDataSource.useTransaction
to run operations within a transaction. If an error is thrown, the transaction is rolled back automatically.
await SqlDataSource.useTransaction(async (trx) => {
await User.insert({ ...UserFactory.getCommonUserData() }, { trx });
});
If an error occurs, changes are not committed:
await SqlDataSource.useTransaction(async (trx) => {
await User.insert({ ...UserFactory.getCommonUserData() }, { trx });
throw new Error("Test error"); // Transaction is rolled back automatically
});
Custom Isolation Level
You can specify a transaction isolation level:
await SqlDataSource.useTransaction(
async (trx) => {
await User.insert({ ...UserFactory.getCommonUserData() }, { trx });
},
{ isolationLevel: "SERIALIZABLE" },
);
Manual Transaction Control
Start, commit, and rollback transactions manually:
const trx = await SqlDataSource.startTransaction();
await User.insert({ ...UserFactory.getCommonUserData() }, { trx });
await trx.commit();
Rollback on error:
const trx = await SqlDataSource.startTransaction();
try {
await User.insert({ ...UserFactory.getCommonUserData() }, { trx });
throw new Error("fail");
await trx.commit();
} catch {
await trx.rollback();
}
Nested Transactions
Some databases (e.g., PostgreSQL, MySQL) support nested transactions. SQLite does not.
const outerTrx = await SqlDataSource.startTransaction();
await User.insert({ ...UserFactory.getCommonUserData() }, { trx: outerTrx });
const innerTrx = await SqlDataSource.startTransaction();
await User.insert({ ...UserFactory.getCommonUserData() }, { trx: innerTrx });
await innerTrx.rollback(); // Only inner changes are rolled back
await outerTrx.commit(); // Outer changes are committed
Concurrent Transactions
You can run multiple transactions in parallel (except in SQLite):
const trx1 = await SqlDataSource.startTransaction();
const trx2 = await SqlDataSource.startTransaction();
await User.insert({ ...UserFactory.getCommonUserData() }, { trx: trx1 });
await User.insert({ ...UserFactory.getCommonUserData() }, { trx: trx2 });
await trx1.commit();
await trx2.commit();
Global Transactions
For integration tests or batch operations, you can use global transactions:
await SqlDataSource.startGlobalTransaction();
// Automatically uses the transaction from the SqlDataSource global transaction
await User.insert({ ...UserFactory.getCommonUserData() });
await SqlDataSource.commitGlobalTransaction();
Rollback global transaction:
await SqlDataSource.startGlobalTransaction();
await User.insert({ ...UserFactory.getCommonUserData() });
await SqlDataSource.rollbackGlobalTransaction();
Error Handling and Transaction State
You can enforce error throwing if a transaction is inactive:
const trx = await SqlDataSource.startTransaction();
await trx.rollback();
await trx.rollback({ throwErrorOnInactiveTransaction: true }); // Throws HysteriaError
Or suppress errors:
const trx = await SqlDataSource.startTransaction();
await trx.rollback();
await trx.rollback({ throwErrorOnInactiveTransaction: false }); // No error
Notes
- SQLite does not support nested or concurrent transactions.
- Always pass the
trx
object to model methods to ensure operations are part of the transaction. - Use isolation levels for advanced consistency requirements.
See also: