drizzle-orm
drizzle-orm copied to clipboard
[FEATURE]: Allow tx.rollback() to provide custom error or return
Describe what you want
Suppose we have the following code:
db.transaction(async (tx) => {
// a lot of operations with tx
if (!someCondition) {
await tx.rollback();
}
// some more operations
if (!otherCondition) {
await tx.rollback();
}
})
The transaction would throw the same error for both rollbacks. We couldn't get any info about what the cause was, and how far the transaction had done (even though it will be discarded).
I believe there should be a way to put the reason/cause of a rollback from happening.
The first time I use drizzle, I thought tx.rollback()
would just do a regular sql ROLLBACK
and still allows us to run the code inside the transaction (seeing the return
statement after tx.rollback()
in the examples from docs). I thought I could return an error or something on that return
statement.
I was trying to do this:
const res = db.transaction(async (tx) => {
if (...) {
await tx.rollback();
return { status: "error" as const, msg: "Something didn't exist" }
}
});
if (res.status === "error") {
// actually throw it
throw new TRPCError({ ... });
}
But apparently it's not possible with how tx.rollback()
actually works.
Maybe you guys could implement a feature similar to that? Perhaps treat throw new AnyError()
inside a transaction as a rollback as well? Thanks!
@iyxan23 I'll probably work on this one soon, since I need it on a project I'm working on as well 😅
All tx.rollback()
does is throwing TransactionRollbackError
, so I was thinking I could add the originally thrown error (either manually from inside the transaction callback, or by the database) as the .cause
field on the TransactionRollbackError
instance, so that you could just throw any error manually instead of calling tx.rollback()
. Maybe I'd also add an optional error argument to tx.rollback()
that would serve the same purpose. Would it solve your issue?
@iyxan23 I'll probably work on this one soon, since I need it on a project I'm working on as well 😅 All
tx.rollback()
does is throwingTransactionRollbackError
, so I was thinking I could add the originally thrown error (either manually from inside the transaction callback, or by the database) as the.cause
field on theTransactionRollbackError
instance, so that you could just throw any error manually instead of callingtx.rollback()
. Maybe I'd also add an optional error argument totx.rollback()
that would serve the same purpose. Would it solve your issue?
Sounds like that's it! Thank you for responding :smile:
@dankochetov Quick related question based on your response above. Can I throw my own error rather than calling tx.rollback()
, i.e. something like this...
await db.transaction(async (tx) => {
await tx
.insert(userBalanceItem)
.values({ amount: -123, type: 'purchase', userId });
// this query may be wrong, i'm new to drizzle, but you get the idea...
const [newBalance] = await tx
.select({ value: sum(userBalanceItem.amount) })
.from(userBalanceItem)
.where(eq(userBalanceItem.userId, userId));
if (newBalance.value || -1 < 0) {
// no call to tx.rollback()
throw new SomeCustomErr('some message');
}
});
This is the pattern I've used with other ORMs. From looking at the code, tx.rollback()
only throws TransactionRollbackError
, so I assume it would work as I expect. But it's unclear from the docs, especially since it's called with await
.
BTW, I'm not a fan of hiding throw
s in this way. I get tx.rollback
returns never
, but it makes the code less legible, especially if you're looking at it outside of an IDE.
Thanks!
@cdcarson yes, you can throw any error and it will be re-thrown from db.transaction()
Up ! would definitely need this feature. Simple as tx.rollback({error : ""}) would do the job to me
Related: I noticed that rollback(): never
doesn't mark the following code as unreachable code in TS due to a limitiation in TypeScript https://github.com/microsoft/TypeScript/issues/50363 There's a workaround in the issue but I'm not familiar with the drizzle codebase. It'd be great if this is fixed.
x | y |
---|---|
transaction.rollback() |
|
throwing an error | |
function returns never |