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
andts-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 = "UEsDBBQAAAAIAEF6EVXEOj6TwwAAAEcBAAATABwAdHNjb25maWcgZmluYWwuanNvblVUCQAD+kz9YvpM/WJ1eAsAAQT1AQAABBQAAABdkLuOwkAMRft8RTRlhDaQkjYrJCQeH4AowsTLGjLjyPbQIP4dB0JByjln7rXse5bnzlPosQPe94oUxS3zu2ETHZ7scXAg1byq3HH2xtrwGdTMWyzcyAO1qYOBW2OgeJGPEUrsYdv0JpUTjJiS/iIPgZ+yRdHy858h0A1qa4Go8h0C2b7mrKMC06Txj2xObUtYm0XrRjCe13Fl6+2aAJMqUUavE3bFfoOn+h/8dTQmHoN1GH2XWnjdRNiXRVEW7pg9sidQSwMEFAAAAAgAinERVTrL97SrAAAA/QAAAAwAHABwYWNrYWdlLmpzb25VVAkAA5Q9/WKUPf1idXgLAAEE9QEAAAQUAAAATY49D4IwEIZ3fsWlA5MWCCjEycHFwc3ZhLQ31NCPcIVoCP/dtmjieM9z7723ZADM9BrZCRjZfVvXbVe3B7aLYsaRlDXRlbzi5UZJjMp5CnQJYwDCaqeGdMKTSEuB4qvXbqPGaRgnA99FyHMwViJIRb7QVk4D8iexkFtTw6AEGkrR2/W+tUqcL+jQSDRC4V/72b8dUhEPxsCj6njLj78vkkwPJ9kE16SibM0+UEsDBBQAAAAIAEh6EVXj6E+2vAAAADABAAANABwAdHNjb25maWcuanNvblVUCQADB039YgdN/WJ1eAsAAQT1AQAABBQAAABdj7FuwkAMhvc8RXRjhAhlQeqaqhISKQ+AGNKLS01y58j2sSDeHadNBjLe993/275nee48hQF74OOgSFHce343bEIbvoDa24FsN287t/rngdrUw8gtGiheZTZCiT3UzWBSOcGEKekH8hhYly2KlvN/hkA3qKwFosprCKT+m7OPCkyLxh+yOZVta20WrRrBeNnHT7vjqwmwqBJl9LpgHQ4H/K5+wXeTMfEYrcPo+9SOF56csC+LoizcOXtkT1BLAwQKAAAAAADPaRFVAAAAAAAAAAAAAAAABAAcAHNyYy9VVAkAAwYw/WIbMP1idXgLAAEE9QEAAAQUAAAAUEsDBBQAAAAIAHhuEVUKRVk4fgIAAA0HAAANABwAc3JjL21vZHVsZS50c1VUCQADwzj9YsU4/WJ1eAsAAQT1AQAABBQAAAC9VduOm0AMfecrvFIlIKLJO1G0qqq0ah9aNbsfkBGYBC3MsDNDLor493oukJDLPq6UKMQe28fHx0NZN0JqOAUA35RCqUvBl1IKmZAF31tWAVP0Ma6l+Wvs4u1sTIIOCilqCLnIMXXGmdKyzHQ4D4LZZAI/Sp6D3iKUnKOshdLAUWnMIWOtQpjMgqLlmakNG9QrIfTykGFjDRH2jym0/I2LPY+HJwu8Qg2SYmABw9m5t2etlMhvXPttWSFEvbfkSjOeoSjANh/bvHAR7Z+mFvDcOssCIn1sTFB/7mmxgLDlORYlxzyMe1jeb+I6+krUreTWOQ+6YNT8v1YQMStk6mHvhly+sRAzQchtojNhixsOz2limgjAbAY/iRtGQGwZlzCBRmKB0iZnHNAwATUqxTbooqhlM8Ydq1oapjKnLF8DgekZk039oiXhGeO7JtuS+Tw+ND2XBUjhxQKMRkeGVlaOTQbvljrYoVSmjnBgXXPpmfbfL3//TJ21LI7RADQezwIPrG6MSJwYtDx6UeitFHsS8N7Bj9a/IBc81LBlOyQYViLruB92xnS2NZMcREX8iAqnldhE61dCSGKhNfOz2DOVwpfTlRJMeNfnHKFxbLs9WoxQhbXt32V3w7yAdq8PHSrgJNj6CAVrK/20TuBkY7rPbcfdDt9vmrJHAIbeXNt+0K5FvWWeDnflaNF8rXCHnoJ1YnM48Txm78NC91OCpyq9RN+Ny91S7vPfpPxs3i9u+EjLFhNSQKXoZ/0q7a4bgVvTB8p2SchwueNXrxUIl4cGzZ7S5TH2hV6YQ5r+FoAF3asPgIyJCJc8Ey3XKP34kYplptq9Up1ZeL/nEVn+A1BLAQIeAxQAAAAIAEF6EVXEOj6TwwAAAEcBAAATABgAAAAAAAEAAACkgQAAAAB0c2NvbmZpZyBmaW5hbC5qc29uVVQFAAP6TP1idXgLAAEE9QEAAAQUAAAAUEsBAh4DFAAAAAgAinERVTrL97SrAAAA/QAAAAwAGAAAAAAAAQAAAKSBEAEAAHBhY2thZ2UuanNvblVUBQADlD39YnV4CwABBPUBAAAEFAAAAFBLAQIeAxQAAAAIAEh6EVXj6E+2vAAAADABAAANABgAAAAAAAEAAACkgQECAAB0c2NvbmZpZy5qc29uVVQFAAMHTf1idXgLAAEE9QEAAAQUAAAAUEsBAh4DCgAAAAAAz2kRVQAAAAAAAAAAAAAAAAQAGAAAAAAAAAAQAO1BBAMAAHNyYy9VVAUAAwYw/WJ1eAsAAQT1AQAABBQAAABQSwECHgMUAAAACAB4bhFVCkVZOH4CAAANBwAADQAYAAAAAAABAAAApIFCAwAAc3JjL21vZHVsZS50c1VUBQADwzj9YnV4CwABBPUBAAAEFAAAAFBLBQYAAAAABQAFAJsBAAAHBgAAAAA="; 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 forMap
is available iftarget
isES6
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 equivalentfunction
expression iftarget
is ES5 or lower.Changing
target
also changes the default value oflib
. You may “mix and match”target
andlib
settings as desired, but you could just settarget
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.