Skip to content

znikola/trpc-error-handling-reproduction

Folders and files

NameName
Last commit message
Last commit date

Latest commit

 

History

1 Commit
 
 
 
 
 
 
 
 
 
 
 
 

Repository files navigation

Improve tRPC's error handling

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.

Problem

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"
    }
  }
}

Setup

npm ci

Start

npm run start

Reproduce

curl http://localhost:3000/trpc/greet

You 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"
    }
  }
}

Proposed solution

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;
}
...

About

No description, website, or topics provided.

Resources

Stars

Watchers

Forks

Releases

No releases published

Packages

 
 
 

Contributors