This is a minimal tRPC repository which demonstrates how to improve tRPC's error handling in case of an error thrown from a procedure running in Node's VM.
When an error occurs in the Node's VM, error that are implicitly thrown by the JavaScript engine (e.g. TypeError) use VM's own internal built-in constructors, meaning instanceof checks for Error will fail because the constructors are different:
// packages/server/src/unstable-core-do-not-import/error/TRPCError.ts
export function getCauseFromUnknown(cause: unknown): Error | undefined {
if (cause instanceof Error) { // 👈 returns false
return cause;
}
...
}Because of this, tRPC ends up throwing an error without a message:
{
"error": {
"message": "",
"code": -32603,
"data": {
"code": "INTERNAL_SERVER_ERROR",
"httpStatus": 500,
"stack": "TRPCError: \n at getTRPCErrorFromUnknown ...",
"path": "greet"
}
}
}npm cinpm run startcurl http://localhost:3000/trpc/greetYou will see an error without a message:
{
"error": {
"message": "",
"code": -32603,
"data": {
"code": "INTERNAL_SERVER_ERROR",
"httpStatus": 500,
"stack": "TRPCError: \n at getTRPCErrorFromUnknown ...",
"path": "greet"
}
}
}Since Error's message and stack properties are non-enumerable, they won't be copied by Object.assign().
// packages/server/src/unstable-core-do-not-import/error/TRPCError.ts
export function getCauseFromUnknown(cause: unknown): Error | undefined {
...
// If it's an object, we'll create a synthetic error
if (isObject(cause)) {
return Object.assign(new UnknownCauseError(), cause); // 👈 message and stack won't be copied
}This is the proposed change to the UnknownCauseError:
// packages/server/src/unstable-core-do-not-import/error/TRPCError.ts
class UnknownCauseError extends Error {
[key: string]: unknown;
constructor(cause: object) {
super(getMessage(cause));
Object.assign(this, cause);
}
}
function getMessage(cause: object) {
return (cause as Error).message;
}and throw it like this:
// packages/server/src/unstable-core-do-not-import/error/TRPCError.ts
export function getCauseFromUnknown(cause: unknown): Error | undefined {
...
// If it's an object, we'll create a synthetic error
if (isObject(cause)) {
return new UnknownCauseError(cause);
}
return undefined;
}This was verified by patching the getCauseFromUnknown() function in the latest version of @trpc/server (11.13.4) in the node_modules:
// node_modules/@trpc/server/dist/tracked-Bjtgv3wJ.mjs
...
var UnknownCauseError = class extends Error {
constructor(cause) {
super(getMessage(cause));
Object.assign(this, cause);
}
};
function getMessage(cause) {
return (cause).message;
}
function getCauseFromUnknown(cause) {
...
// 👇 old
// if (isObject(cause)) return Object.assign(new UnknownCauseError(), cause);
// 👇 new
if (isObject(cause)) return new UnknownCauseError(cause);
return void 0;
}
...