Skip to content
Advertisement

Error cause property not recognized in TypeScript

I stumbled upon the possibility to add a cause to the Error constructor in javascript.

But when I try to use this feature my app does not start as it does not know this “new” constructor arg.

> tsc && node dist/index.js
promo/promo-service/am-promo-request-handler.ts:43:104 - error TS2554: Expected 0-1 arguments, but got 2.        
43         throw new Error(`Can't read Maxmind GeoLite2 City db from mmdb file '${config.pathMmdbCity}'`, { cause: err});
Found 1 error in promo/promo-service/am-promo-request-handler.ts:43 

All of the following commands stop with the above compile error

nodemon
tsc && node dist/index.js
ts-node index.ts

I added the following script to my package.json (to be sure to ask the right instance of node and the other tools for its version)

"check": "nodemon -v && node -v && tsc -v && ts-node -v && npm -v"

It returns

2.0.19
v16.14.2
Version 4.7.4
v10.9.1
8.17.0

The feature should be available since node version 10.9.0

@types/node@18.7.6

My tsconfig.json

{
  "compilerOptions": {
    "target": "es2017",
    "module": "commonjs",
    "sourceMap": true,
    "outDir": "./dist/",
    "removeComments": true,
    "esModuleInterop": true,
    "forceConsistentCasingInFileNames": true,
    "strict": true,
    "skipLibCheck": true
  }
}

I’m using VSC on Windows.

Any idea what I need to change/update?

Advertisement

Answer

Note: I’ll ignore the nodemon and ts-node aspects of your question details because they are additional layers of complexity and not related to the core problem.

Because you haven’t shown your code, I’ll provide a (contrived) example for this answer that both accesses the cause property of an error instance and uses it in construction to demonstrate the issue with your configuration and explain what’s wrong and how to fix it:

Example code and initial configuration

Note: you can manually copy all of the example files in this answer and save them on your storage device to follow along, or you can just copy this script and paste it into your browser’s console to download the entire example project as a zip archive:

(() => {
  function createBase64DataUrl (mediaType, b64Str) {
    return `data:${mediaType};base64,${b64Str}`;
  }

  function download (url, fileName) {
    const a = document.createElement("a");
    a.href = url;
    a.download = fileName;
    a.click();
    a.remove();
  }

  const zipArchiveData = "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";
  const dataUrl = createBase64DataUrl("application/zip", zipArchiveData);
  download(dataUrl, "so-73378375.zip");
})();

Let’s get started. Here’s an example TypeScript module:

./src/module.ts:

import {
  AssertionError,
  equal as assertEqual,
  ok as assert,
} from 'node:assert/strict';

/** Find the innermost nested cause */
function getRootException (exception: unknown): unknown {
  let root = exception;
  let current = exception;
  while (current instanceof Error) {
    current = current.cause;
    if (typeof current !== 'undefined') root = current;
  }
  return root;
}

function getQuotedReason (exception: unknown): string {
  const rootException = getRootException(exception);

  // Get a reason string, preferring an error message
  // if the value is an Error instance:
  const reasonStr = rootException instanceof Error
    ? rootException.message
    : String(rootException);

  // Return a quoted version of the string:
  return JSON.stringify(reasonStr);
}

function example () {
  try {
    throw new Error(`I don't have a cause`);
  }
  catch (ex) {
    console.log(`The final reason was: ${getQuotedReason(ex)}`);
  }

  try {
    const cause = new Error(`I'm the final error cause`);
    throw new Error(`It's not my fault!`, {cause});
  }
  catch (ex) {
    console.log(`The final reason was: ${getQuotedReason(ex)}`);
  }

  try {
    const nestedCause = new Error(
      `I'm the cause of the error that caused the top-level error`,
    );

    const cause = new Error(
      `I'm the cause of the top-level error`,
      {cause: nestedCause},
    );

    throw new Error(`I'm the top-level error`, {cause});
  }
  catch (ex) {
    console.log(`The final reason was: ${getQuotedReason(ex)}`);
  }

  try {
    assertEqual(true, false, `True isn't false`);
  }
  catch (ex) {
    assert(ex instanceof AssertionError, 'Expeted an AssertionError');
    assert(ex.message === `True isn't false`);
    console.log('Encountered the expected AssertionError');
  }
}

example();

Here, I’m using the TSConfig shown in your question, and I’ve just added the include top-level option because the module above (all of the TS source code in this example) is in the ./src directory:

./tsconfig.json:

{
  "compilerOptions": {
    "target": "es2017",
    "module": "commonjs",
    "sourceMap": true,
    "outDir": "./dist/",
    "removeComments": true,
    "esModuleInterop": true,
    "forceConsistentCasingInFileNames": true,
    "strict": true,
    "skipLibCheck": true
  },
  "include": ["src/**/*"]
}

And, for completeness, here’s my npm package file:

./package.json:

{
  "name": "so-73378375",
  "version": "0.1.0",
  "scripts": {
    "compile": "tsc",
    "example": "npm run compile && node dist/module.js"
  },
  "license": "MIT",
  "devDependencies": {
    "@types/node": "^18.7.6",
    "typescript": "^4.7.4"
  }
}

Also, note that I’m using the latest LTS version of Node at the time of this answer (it’s slightly newer than yours, but still 16.x):

% node --version
v16.17.0

If you’re following along, now’s a good time to npm install:

% npm install

added 2 packages, and audited 3 packages in 1s

found 0 vulnerabilities

Determining the “cause” of your issue

Let’s start out by looking at your TSConfig: you’ve set compilerOptions.target to "es2017". I’m not sure what influenced this decision, but the TypeScript GitHub repo wiki provides Recommended Node TSConfig settings that you can use for guidance:

Node 16

{
  "compilerOptions": {
    "lib": ["ES2021"],
    "module": "commonjs",
    "target": "ES2021"
  }
}

There are also base configs for various environments here.

Some observations:

The lib option is set in addition to target. The documentation for compilerOptions.lib in the TSConfig reference includes this information:

TypeScript also includes APIs for newer JS features matching the target you specify; for example the definition for Map is available if target is ES6 or newer.

What this means is that (according to the way that TS resolves configuration right now) not setting lib in your config is the same as specifying an array with a single value equal to your target. That means we can update your TSConfig’s compilerOptions with these values and get the same behavior:

{
  "compilerOptions": {
    "lib": ["es2017"],
    "target": "es2017",
    // --- snip ---
  },
  // --- snip ---
}

We’re not done making changes yet (we still need to change the actual lib and target values), but let’s run the example script now to see what the compiler tells us:

% npm run example

> so-73378375@0.1.0 example
> npm run compile && node dist/module.js


> so-73378375@0.1.0 compile
> tsc

src/module.ts:12:23 - error TS2550: Property 'cause' does not exist on type 'Error'. Do you need to change your target library? Try changing the 'lib' compiler option to 'es2022' or later.

12     current = current.cause;
                         ~~~~~

src/module.ts:41:43 - error TS2554: Expected 0-1 arguments, but got 2.

41     throw new Error(`It's not my fault!`, {cause});
                                             ~~~~~~~

src/module.ts:54:7 - error TS2554: Expected 0-1 arguments, but got 2.

54       {cause: nestedCause},
         ~~~~~~~~~~~~~~~~~~~~

src/module.ts:57:48 - error TS2554: Expected 0-1 arguments, but got 2.

57     throw new Error(`I'm the top-level error`, {cause});
                                                  ~~~~~~~


Found 4 errors in the same file, starting at: src/module.ts:12

The last 3 compilation errors look very much like the one you described in your question. Now that we’ve reproduced your issue, let’s adjust the lib and target values to the one suggested by the TS wiki "ES2021" — note that the capitalization for this value doesn’t matter (e.g. "ES2021" is the same as "es2021"):

{
  "compilerOptions": {
    "lib": ["es2021"],
    "target": "es2021",
    // --- snip ---
  },
  // --- snip ---
}

Now that we’ve changed those values, let’s try again:

% npm run example

> so-73378375@0.1.0 example
> npm run compile && node dist/module.js


> so-73378375@0.1.0 compile
> tsc

src/module.ts:12:23 - error TS2550: Property 'cause' does not exist on type 'Error'. Do you need to change your target library? Try changing the 'lib' compiler option to 'es2022' or later.

12     current = current.cause;
                         ~~~~~

src/module.ts:41:43 - error TS2554: Expected 0-1 arguments, but got 2.

41     throw new Error(`It's not my fault!`, {cause});
                                             ~~~~~~~

src/module.ts:54:7 - error TS2554: Expected 0-1 arguments, but got 2.

54       {cause: nestedCause},
         ~~~~~~~~~~~~~~~~~~~~

src/module.ts:57:48 - error TS2554: Expected 0-1 arguments, but got 2.

57     throw new Error(`I'm the top-level error`, {cause});
                                                  ~~~~~~~


Found 4 errors in the same file, starting at: src/module.ts:12

Still the same 4 compilation errors! However, there’s a useful suggestion in there after the first error (it was also there the first time we ran it):

Try changing the 'lib' compiler option to 'es2022' or later.

Arriving at the solution

If we examine the core TS library declaration files for ES features (in v4.7.4), we find that the types related to the error cause property are located in lib.es2022.error.d.ts. These types are used in lib.es2022.d.ts and later versions, but are actually not used in lib.es2021.d.ts.

Therefore, we need to set the lib option to at least "es2022".

Is the TS wiki recommendation outdated? Sort of (it’s not exactly that simple). Node is always evolving, and that recommendation was likely written for the first release of Node 16. However, support for the Error.cause property was added to Node in release version 16.9.0 on 2021-09-07.

There’s also another useful site that tracks Node.js ES feature support: https://node.green.

We only need to update the compilerOptions.lib value, and we can actually set it to anything later than "es2022" (e.g. "esnext"), and TS will still use the compilerOptions.target value to appropriately downlevel incompatible language features. Here’s a snippet from the documentation:

The target setting changes which JS features are downleveled and which are left intact. For example, an arrow function () => this will be turned into an equivalent function expression if target is ES5 or lower.

Changing target also changes the default value of lib. You may “mix and match” target and lib settings as desired, but you could just set target for convenience [if newer language features aren’t needed in your source code].

That last bracketed clause is mine (not in the docs) and describes the exception of your case.

Let’s actually update the lib value now:

{
  "compilerOptions": {
    "lib": ["es2022"],
    // --- snip ---
  },
  // --- snip ---
}

and run the example one more time:

% npm run example

> so-73378375@0.1.0 example
> npm run compile && node dist/module.js


> so-73378375@0.1.0 compile
> tsc

The final reason was: "I don't have a cause"
The final reason was: "I'm the final error cause"
The final reason was: "I'm the cause of the error that caused the top-level error"
Encountered the expected AssertionError

Compilation succeeds, so the program is run and we get the expected output — great! 🎉

If you’re reading this — thanks for following along. I hope that clarifies everything for you.

User contributions licensed under: CC BY-SA
8 People found this is helpful
Advertisement