ts-sql-query
ts-sql-query copied to clipboard
Understanding the Purpose of ChainedError in transaction Method in src/connections/AbstractConnection.ts
Hello!
I'm having trouble understanding the purpose of ChainedError in the src/connections/AbstractConnection.ts file, specifically in the transaction method.
In my situation, my services can throw various errors within a transaction, including validation errors, and I would like to get the original errors immediately. However, I'm getting something like this:
ChainedError: [object Object] at ... Caused By: undefined Transaction source: Error: Transaction executed at at DBConnection.transaction ... [cause]: ValidationErrors { fields: Map(1) { 'code' => [ { message: 'Invalid coupon code' } ] } }
I had to override the transaction method in DBConnection to avoid adding try-catch blocks everywhere:
class DBConnection extends MySqlConnection<'Connection'> {
allowEmptyString = true;
protected compatibilityMode = true;
transaction<P extends Promise<any>[]>(fn: () => [...P]): Promise<UnwrapPromiseTuple<P>>;
transaction<T>(fn: () => Promise<T>): Promise<T>;
async transaction(fn: any): Promise<any> {
try {
return await super.transaction(fn);
} catch (error) {
if (error.cause) {
throw error.cause;
}
throw error;
}
}
}
My question is, what is the purpose of ChainedError, and is it possible to work without it?
Is there any specific reason for using ChainedError in this context, or can it be removed (can avoid wrapping errors and simply return them as they are)?
Hi,
Stack traces in node are tricky; when an error occurs node doesn't include the full stack trace; instead, the last n-deep-elements are included (you can configure it); then you will not see when the error already occurs in your code because that is already consumed inside the database connector itself (like pg). This gets more complicated when the errors happen in an async logic; async strict trace was added not so long ago before you got nothing.
In order to make it possible to identify where an error occurs, ts-sql-query wraps the error in a ChainedError in the most external method you call the library; in this, you get a stack trace that shows your code execution (instead of the connector internal execution) and, in the stack trace is added and additional items "Query executed at" that shows the stack trace that was before the query was executed (the async stack trace doesn't display this). Having both information, you know that was the path in your code after and before the execution of the query, allowing you to point with better precision what causes the error.
This information is very important, especially if you have a running system in production when an error occurs from the database, and now you need to fix it.
I don't recommend you remove what the ChainedError is providing. Instead, I recommend you create a utility function that returns the database error from a chained error. I suppose you want to get the database error to get what constraint failed (or something similar).
In the case of a transaction, the ChainedError is created to show you where the transaction was started, as well as to provide information when the transaction failed. But I recognise this creates issues when you throw your own errors. I'm thinking, for version 2 of ts-sql-query, in the specific case of transactions, I should remove that wrapping, putting the wrapping on the "begin transaction", "commit", or "rollback" operations only (I will need to figure out this). If you have your own base Error in your project, it will be better if you unwrap the error only when the cause is your own base class.
I'm investigating for ts-sql-query 2 change the error management in ts-sql-query, keeping the same enrichment of the ChainedError when a query is executed but, additionally, making it possible to read the most important information in the native error coming from the database and making it easier to access the native database error.
Let me know if you have any ideas on this.
Thank you for your response and the explanation of how error handling works in ts-sql-query. Your information has been very helpful.
In my case, it makes sense for me to retrieve the original error only from the transaction method since that's where I have the opportunity to generate and throw my own error. That's why I've overridden the transaction method.
I look forward to seeing the suggested improvements in future versions of ts-sql-query, especially regarding error management and access to custom errors.
It might be worth considering how approaches like Drizzle or Prisma handle these aspects.