drizzle-orm icon indicating copy to clipboard operation
drizzle-orm copied to clipboard

[FEATURE]: Allow tx.rollback() to provide custom error or return

Open iyxan23 opened this issue 11 months ago • 4 comments

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 avatar Mar 04 '24 02:03 iyxan23

@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?

dankochetov avatar Mar 20 '24 23:03 dankochetov

@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?

Sounds like that's it! Thank you for responding :smile:

iyxan23 avatar Mar 24 '24 15:03 iyxan23

@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 throws 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 avatar Apr 25 '24 17:04 cdcarson

@cdcarson yes, you can throw any error and it will be re-thrown from db.transaction()

dankochetov avatar May 09 '24 12:05 dankochetov

Up ! would definitely need this feature. Simple as tx.rollback({error : ""}) would do the job to me

laurent512 avatar Jul 05 '24 08:07 laurent512

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() image
throwing an error Screenshot 2024-07-21 001755
function returns never Screenshot 2024-07-21 001900

AhmedBaset avatar Jul 20 '24 21:07 AhmedBaset