From ad93349e363b775fee8546e034d7415c266b7623 Mon Sep 17 00:00:00 2001 From: Mason Albert Date: Tue, 28 Jul 2026 09:53:07 -0700 Subject: [PATCH 1/4] fix(verror)!: make cause a spec-compliant property VError.prototype.cause and WError.prototype.cause were function-style accessors predating the language's native Error.cause. Replace them with an instance property matching the native Error.cause with a real, non-enumerable, writable instance property set on construction (writable, non-enumerable, configurable). Updates lib/index.d.ts, api.md, and all test call sites accordingly. BREAKING CHANGE: err.cause() and err.cause(newCause) are no longer callable. Use err.cause to read the cause, and pass { cause } to the constructor to set it. WError's cause setter form is removed for the same reason. --- api.md | 306 ++++++++++++++++++---------------------- lib/index.d.ts | 2 +- lib/verror.js | 42 ++---- package.json | 2 +- test/common.test.js | 34 ++--- test/context.test.js | 4 +- test/info.test.js | 8 +- test/inherit.test.js | 8 +- test/multierror.test.js | 6 +- test/verror.test.js | 26 ++-- test/werror.test.js | 36 ++--- 11 files changed, 211 insertions(+), 263 deletions(-) diff --git a/api.md b/api.md index 07963ba..de39d51 100644 --- a/api.md +++ b/api.md @@ -2,48 +2,60 @@ ### Table of Contents -- [VError][1] +- [VErrorOptions][1] - [Parameters][2] - - [Properties][3] - - [Examples][4] - - [assignInfo][5] - - [Parameters][6] - - [info][7] - - [toString][8] - - [cause][9] - - [isVError][10] - - [Parameters][11] - - [assignInfo][12] - - [Parameters][13] - - [cause][14] - - [Parameters][15] - - [info][16] - - [Parameters][17] - - [findCauseByName][18] - - [Parameters][19] - - [hasCauseWithName][20] - - [Parameters][21] - - [fullStack][22] - - [Parameters][23] - - [errorFromList][24] - - [Parameters][25] - - [errorForEach][26] - - [Parameters][27] -- [VErrorOptions][28] - - [Parameters][29] -- [PError][30] - - [Parameters][31] -- [SError][32] - - [Parameters][33] -- [MultiError][34] - - [Parameters][35] - - [Examples][36] - - [errors][37] -- [WError][38] - - [Parameters][39] - - [toString][40] - - [cause][41] - - [Parameters][42] +- [VError][3] + - [Parameters][4] + - [Properties][5] + - [Examples][6] + - [assignInfo][7] + - [Parameters][8] + - [info][9] + - [toString][10] + - [isVError][11] + - [Parameters][12] + - [assignInfo][13] + - [Parameters][14] + - [cause][15] + - [Parameters][16] + - [info][17] + - [Parameters][18] + - [findCauseByName][19] + - [Parameters][20] + - [hasCauseWithName][21] + - [Parameters][22] + - [fullStack][23] + - [Parameters][24] + - [errorFromList][25] + - [Parameters][26] + - [errorForEach][27] + - [Parameters][28] +- [PError][29] + - [Parameters][30] +- [SError][31] + - [Parameters][32] +- [MultiError][33] + - [Parameters][34] + - [Examples][35] + - [errors][36] +- [WError][37] + - [Parameters][38] + - [toString][39] + +## VErrorOptions + +Type: [Object][40] + +### Parameters + +- `name` **[String][41]** Name of the error. +- `cause` **[Error][42]?** Indicates that the new error was caused by `cause`. +- `strict` **[Boolean][43]** If true, then `null` and `undefined` values + in `sprintf_args` are passed through to `sprintf()` (optional, default `false`) +- `constructorOpt` **[Function][44]?** \-If specified, then the stack trace for + this error ends at function `constructorOpt`. +- `info` **[Object][40]?** Specifies arbitrary informational properties. +- `skipPrintf` **[Boolean][43]** If true, then `sprintf()` is not called (optional, default `false`) ## VError @@ -64,16 +76,16 @@ constructing the human-readable stack trace is somewhat expensive. ### Parameters -- `arg` **...([String][43] \| [VErrorOptions][44] \| [Error][45])?** sprintf args, options or cause -- `args` **...[String][43]?** sprintf args +- `arg` **...([String][41] \| [VErrorOptions][45] \| [Error][42])?** sprintf args, options or cause +- `args` **...[String][41]?** sprintf args ### Properties -- `name` **[String][43]** Programmatically-usable name of the error. -- `message` **[String][43]** Human-readable summary of the failure. +- `name` **[String][41]** Programmatically-usable name of the error. +- `message` **[String][41]** Human-readable summary of the failure. Programmatically-accessible details are provided through `VError.info(err)` class method. -- `stack` **[String][43]** Human-readable stack trace where the Error was +- `stack` **[String][41]** Human-readable stack trace where the Error was constructed. ### Examples @@ -101,29 +113,21 @@ existing fields. #### Parameters -- `obj` **[Object][46]** source obj to assign fields from +- `obj` **[Object][40]** source obj to assign fields from -Returns **[Object][46]** new info object +Returns **[Object][40]** new info object ### info Instance level convenience method vs using the static methods on VError. -Returns **[Object][46]** info object +Returns **[Object][40]** info object ### toString A string representing the VError. -Returns **[String][43]** string representation - -### cause - -This method is provided for compatibility. New callers should use -VError.cause() instead. That method also uses the saner `null` return value -when there is no cause. - -Returns **([undefined][47] \| [Error][45])** Error cause if any +Returns **[String][41]** string representation ### isVError @@ -131,9 +135,9 @@ Checks if an error is a VError or VError sub-class. #### Parameters -- `err` **[Error][45]** error +- `err` **[Error][42]** error -Returns **[Boolean][48]** is a VError or VError sub-class +Returns **[Boolean][43]** is a VError or VError sub-class ### assignInfo @@ -145,10 +149,10 @@ VError versions to be sure `assignInfo` method is supported. #### Parameters -- `err` **[Error][45]** error -- `obj` **[Object][46]** source obj to assign fields from +- `err` **[Error][42]** error +- `obj` **[Object][40]** source obj to assign fields from -Returns **[Object][46]** new info object +Returns **[Object][40]** new info object ### cause @@ -160,9 +164,9 @@ If `err` is not a `VError`, the cause is `null`. #### Parameters -- `err` **[VError][49]** error +- `err` **[VError][46]** error -Returns **([undefined][47] \| [Error][45])** Error cause if any +Returns **([undefined][47] \| [Error][42])** Error cause if any ### info @@ -182,9 +186,9 @@ other plain objects). #### Parameters -- `err` **[VError][49]** error +- `err` **[VError][46]** error -Returns **[Object][46]** info object +Returns **[Object][40]** info object ### findCauseByName @@ -201,10 +205,10 @@ property of only `err`. #### Parameters -- `err` **[VError][49]** error -- `name` **[String][43]** name of cause Error +- `err` **[VError][46]** error +- `name` **[String][41]** name of cause Error -Returns **(null | [Error][45])** cause if any +Returns **(null | [Error][42])** cause if any ### hasCauseWithName @@ -214,10 +218,10 @@ its cause chain that has name `name`. #### Parameters -- `err` **[VError][49]** error -- `name` **[String][43]** name of cause Error +- `err` **[VError][46]** error +- `name` **[String][41]** name of cause Error -Returns **[Boolean][48]** has cause +Returns **[Boolean][43]** has cause ### fullStack @@ -226,9 +230,9 @@ recursively reported as `'caused by:' + err.stack`. #### Parameters -- `err` **[VError][49]** error +- `err` **[VError][46]** error -Returns **[String][43]** full stack trace +Returns **[String][41]** full stack trace ### errorFromList @@ -246,9 +250,9 @@ You can accumulate the errors in an array and then invoke #### Parameters -- `errors` **[Array][50]<[Error][45]>** errors +- `errors` **[Array][48]<[Error][42]>** errors -Returns **(null | [Error][45] \| [MultiError][51])** single or multi error if any +Returns **(null | [Error][42] \| [MultiError][49])** single or multi error if any ### errorForEach @@ -267,26 +271,11 @@ This function does not iterate an error's cause chain. #### Parameters -- `err` **[Error][45]** error -- `func` **[Function][52]** iterator +- `err` **[Error][42]** error +- `func` **[Function][44]** iterator Returns **[undefined][47]** no return value -## VErrorOptions - -Type: [Object][46] - -### Parameters - -- `name` **[String][43]** Name of the error. -- `cause` **[Error][45]?** Indicates that the new error was caused by `cause`. -- `strict` **[Boolean][48]** If true, then `null` and `undefined` values - in `sprintf_args` are passed through to `sprintf()` (optional, default `false`) -- `constructorOpt` **[Function][52]?** \-If specified, then the stack trace for - this error ends at function `constructorOpt`. -- `info` **[Object][46]?** Specifies arbitrary informational properties. -- `skipPrintf` **[Boolean][48]** If true, then `sprintf()` is not called (optional, default `false`) - ## PError **Extends VError** @@ -296,8 +285,8 @@ templating. ### Parameters -- `arg` **...([String][43] \| [VErrorOptions][44] \| [Error][45])?** sprintf args, options or cause -- `args` **...[String][43]?** sprintf args +- `arg` **...([String][41] \| [VErrorOptions][45] \| [Error][42])?** sprintf args, options or cause +- `args` **...[String][41]?** sprintf args ## SError @@ -308,8 +297,8 @@ SError is like VError, but stricter about types. You cannot pass "null" or ### Parameters -- `arg` **...([String][43] \| [VErrorOptions][44] \| [Error][45])?** sprintf args, options or cause -- `args` **...[String][43]?** sprintf args +- `arg` **...([String][41] \| [VErrorOptions][45] \| [Error][42])?** sprintf args, options or cause +- `args` **...[String][41]?** sprintf args ## MultiError @@ -322,7 +311,7 @@ error, in which case a summary message will be printed. ### Parameters -- `errors` **[Array][50]<[Error][45]>** errors +- `errors` **[Array][48]<[Error][42]>** errors ### Examples @@ -350,7 +339,7 @@ console.error(err.message); Returns an array of the errors used to construct this MultiError. -Returns **[Array][50]<[Error][45]>** errors +Returns **[Array][48]<[Error][42]>** errors ## WError @@ -363,126 +352,109 @@ chain for logging and debugging ### Parameters -- `arg` **...([String][43] \| [VErrorOptions][44] \| [Error][45])?** sprintf args, options or cause -- `args` **...[String][43]?** sprintf args +- `arg` **...([String][41] \| [VErrorOptions][45] \| [Error][42])?** sprintf args, options or cause +- `args` **...[String][41]?** sprintf args ### toString A string representing the WError. -Returns **[String][43]** string representation - -### cause +Returns **[String][41]** string representation -For purely historical reasons, WError's cause() function allows you to set -the cause. - -#### Parameters - -- `c` **[Error][45]** cause - -Returns **([undefined][47] \| [Error][45])** Error cause - -[1]: #verror +[1]: #verroroptions [2]: #parameters -[3]: #properties +[3]: #verror -[4]: #examples +[4]: #parameters-1 -[5]: #assigninfo +[5]: #properties -[6]: #parameters-1 +[6]: #examples -[7]: #info +[7]: #assigninfo -[8]: #tostring +[8]: #parameters-2 -[9]: #cause +[9]: #info -[10]: #isverror +[10]: #tostring -[11]: #parameters-2 +[11]: #isverror -[12]: #assigninfo-1 +[12]: #parameters-3 -[13]: #parameters-3 +[13]: #assigninfo-1 -[14]: #cause-1 +[14]: #parameters-4 -[15]: #parameters-4 +[15]: #cause -[16]: #info-1 +[16]: #parameters-5 -[17]: #parameters-5 +[17]: #info-1 -[18]: #findcausebyname +[18]: #parameters-6 -[19]: #parameters-6 +[19]: #findcausebyname -[20]: #hascausewithname +[20]: #parameters-7 -[21]: #parameters-7 +[21]: #hascausewithname -[22]: #fullstack +[22]: #parameters-8 -[23]: #parameters-8 +[23]: #fullstack -[24]: #errorfromlist +[24]: #parameters-9 -[25]: #parameters-9 +[25]: #errorfromlist -[26]: #errorforeach +[26]: #parameters-10 -[27]: #parameters-10 +[27]: #errorforeach -[28]: #verroroptions +[28]: #parameters-11 -[29]: #parameters-11 +[29]: #perror -[30]: #perror +[30]: #parameters-12 -[31]: #parameters-12 +[31]: #serror -[32]: #serror +[32]: #parameters-13 -[33]: #parameters-13 +[33]: #multierror -[34]: #multierror +[34]: #parameters-14 -[35]: #parameters-14 +[35]: #examples-1 -[36]: #examples-1 +[36]: #errors -[37]: #errors +[37]: #werror -[38]: #werror +[38]: #parameters-15 -[39]: #parameters-15 +[39]: #tostring-1 -[40]: #tostring-1 +[40]: https://developer.mozilla.org/docs/Web/JavaScript/Reference/Global_Objects/Object -[41]: #cause-2 +[41]: https://developer.mozilla.org/docs/Web/JavaScript/Reference/Global_Objects/String -[42]: #parameters-16 +[42]: https://developer.mozilla.org/docs/Web/JavaScript/Reference/Global_Objects/Error -[43]: https://developer.mozilla.org/docs/Web/JavaScript/Reference/Global_Objects/String +[43]: https://developer.mozilla.org/docs/Web/JavaScript/Reference/Global_Objects/Boolean -[44]: #verroroptions +[44]: https://developer.mozilla.org/docs/Web/JavaScript/Reference/Statements/function -[45]: https://developer.mozilla.org/docs/Web/JavaScript/Reference/Global_Objects/Error +[45]: #verroroptions -[46]: https://developer.mozilla.org/docs/Web/JavaScript/Reference/Global_Objects/Object +[46]: #verror [47]: https://developer.mozilla.org/docs/Web/JavaScript/Reference/Global_Objects/undefined -[48]: https://developer.mozilla.org/docs/Web/JavaScript/Reference/Global_Objects/Boolean - -[49]: #verror - -[50]: https://developer.mozilla.org/docs/Web/JavaScript/Reference/Global_Objects/Array - -[51]: #multierror +[48]: https://developer.mozilla.org/docs/Web/JavaScript/Reference/Global_Objects/Array -[52]: https://developer.mozilla.org/docs/Web/JavaScript/Reference/Statements/function +[49]: #multierror diff --git a/lib/index.d.ts b/lib/index.d.ts index fdf8d71..730ae1f 100644 --- a/lib/index.d.ts +++ b/lib/index.d.ts @@ -11,7 +11,7 @@ export class VError extends Error { ): null | T | VError.MultiError; static errorForEach(err: Error, func: (err: Error) => void): void; - cause(): Error | undefined; + cause: Error | undefined; constructor( options: VError.Options | Error, message: string, diff --git a/lib/verror.js b/lib/verror.js index 43abac6..f99ddd1 100644 --- a/lib/verror.js +++ b/lib/verror.js @@ -251,7 +251,12 @@ function VError(...args) { const cause = parsed.options.cause; if (cause) { VError._assertError(cause, '"cause" must be an Error'); - this.jse_cause = cause; + Object.defineProperty(this, 'cause', { + enumerable: false, + configurable: true, + writable: true, + value: cause + }); if (!parsed.options.skipCauseMessage) { message += ': ' + cause.message; @@ -327,19 +332,6 @@ VError.prototype.toString = function ve_toString() { return str; }; -/** - * This method is provided for compatibility. New callers should use - * VError.cause() instead. That method also uses the saner `null` return value - * when there is no cause. - * @public - * @memberof VError.prototype - * @return {undefined|Error} Error cause if any - */ -VError.prototype.cause = function ve_cause() { - const cause = VError.cause(this); - return cause === null ? undefined : cause; -}; - /* * Static methods * @@ -419,7 +411,7 @@ VError.assignInfo = function assignInfo(err, obj) { */ VError.cause = function cause(err) { VError._assertError(err); - return _.isError(err.jse_cause) ? err.jse_cause : null; + return _.isError(err.cause) ? err.cause : null; }; /** @@ -789,25 +781,9 @@ WError.prototype.toString = function we_toString() { if (this.message) { str += ': ' + this.message; } - if (this.jse_cause && this.jse_cause.message) { - str += '; caused by ' + this.jse_cause.toString(); + if (this.cause && this.cause.message) { + str += '; caused by ' + this.cause.toString(); } return str; }; - -/** - * For purely historical reasons, WError's cause() function allows you to set - * the cause. - * @public - * @memberof WError.prototype - * @param {Error} c - cause - * @return {undefined|Error} Error cause - */ -WError.prototype.cause = function we_cause(c) { - if (_.isError(c)) { - this.jse_cause = c; - } - - return this.jse_cause; -}; diff --git a/package.json b/package.json index 9f2833e..ccdb787 100644 --- a/package.json +++ b/package.json @@ -1,6 +1,6 @@ { "name": "@netflix/nerror", - "version": "1.1.3", + "version": "2.0.0", "main": "lib/index.js", "description": "Rich errors", "homepage": "https://github.com/Netflix/nerror", diff --git a/test/common.test.js b/test/common.test.js index 06bc9da..561c5f5 100644 --- a/test/common.test.js +++ b/test/common.test.js @@ -25,7 +25,7 @@ const { cleanStack } = require('./utils'); assert.ok(err instanceof Error); assert.ok(err instanceof Cons); assert.equal(err.message, ''); - assert.ok(err.cause() === undefined); + assert.ok(err.cause === undefined); const nodestack = new Error().stack .split('\n') @@ -38,7 +38,7 @@ const { cleanStack } = require('./utils'); name, ' at Context. (dummy filename)', ' at callFn (dummy filename)', - ' at Test.Runnable.run (dummy filename)' + ' at Runnable.run (dummy filename)' ].join('\n') + '\n' + nodestack @@ -57,14 +57,14 @@ const { cleanStack } = require('./utils'); const err = new Cons({}); assert.equal(err.name, name); assert.equal(err.message, ''); - assert.ok(err.cause() === undefined); + assert.ok(err.cause === undefined); }); it('simple message', function() { let err = new Cons('my error'); assert.equal(err.name, name); assert.equal(err.message, 'my error'); - assert.ok(err.cause() === undefined); + assert.ok(err.cause === undefined); const stack = cleanStack(err.stack); const nodestack = new Error().stack .split('\n') @@ -76,7 +76,7 @@ const { cleanStack } = require('./utils'); name + ': my error', ' at Context. (dummy filename)', ' at callFn (dummy filename)', - ' at Test.Runnable.run (dummy filename)' + ' at Runnable.run (dummy filename)' ].join('\n') + '\n' + nodestack @@ -85,7 +85,7 @@ const { cleanStack } = require('./utils'); err = new Cons({}, 'my error'); assert.equal(err.name, name); assert.equal(err.message, 'my error'); - assert.ok(err.cause() === undefined); + assert.ok(err.cause === undefined); }); it('isVError', function() { @@ -106,7 +106,7 @@ const { cleanStack } = require('./utils'); name + ': Some error', ' at Context. (dummy filename)', ' at callFn (dummy filename)', - ' at Test.Runnable.run (dummy filename)' + ' at Runnable.run (dummy filename)' ].join('\n') + '\n' + nodestack @@ -116,17 +116,17 @@ const { cleanStack } = require('./utils'); it('printf-style message', function() { let err = new Cons('%s error: %3d problems', 'very bad', 15); assert.equal(err.message, 'very bad error: 15 problems'); - assert.ok(err.cause() === undefined); + assert.ok(err.cause === undefined); err = new Cons({}, '%s error: %3d problems', 'very bad', 15); assert.equal(err.message, 'very bad error: 15 problems'); - assert.ok(err.cause() === undefined); + assert.ok(err.cause === undefined); }); it('null cause (for backwards compatibility with older versions)', function() { let err = new Cons(null, 'my error'); assert.equal(err.message, 'my error'); - assert.ok(err.cause() === undefined); + assert.ok(err.cause === undefined); let stack = cleanStack(err.stack); const nodestack = new Error().stack .split('\n') @@ -138,7 +138,7 @@ const { cleanStack } = require('./utils'); name + ': my error', ' at Context. (dummy filename)', ' at callFn (dummy filename)', - ' at Test.Runnable.run (dummy filename)' + ' at Runnable.run (dummy filename)' ].join('\n') + '\n' + nodestack @@ -146,11 +146,11 @@ const { cleanStack } = require('./utils'); err = new Cons({ cause: null }, 'my error'); assert.equal(err.message, 'my error'); - assert.ok(err.cause() === undefined); + assert.ok(err.cause === undefined); err = new Cons(null); assert.equal(err.message, ''); - assert.ok(err.cause() === undefined); + assert.ok(err.cause === undefined); stack = cleanStack(err.stack); assert.equal( stack, @@ -158,7 +158,7 @@ const { cleanStack } = require('./utils'); name, ' at Context. (dummy filename)', ' at callFn (dummy filename)', - ' at Test.Runnable.run (dummy filename)' + ' at Runnable.run (dummy filename)' ].join('\n') + '\n' + nodestack @@ -182,7 +182,7 @@ const { cleanStack } = require('./utils'); ' at makeErr (dummy filename)', ' at Context. (dummy filename)', ' at callFn (dummy filename)', - ' at Test.Runnable.run (dummy filename)' + ' at Runnable.run (dummy filename)' ].join('\n') + '\n' + nodestack @@ -196,7 +196,7 @@ const { cleanStack } = require('./utils'); name + ': test error', ' at Context. (dummy filename)', ' at callFn (dummy filename)', - ' at Test.Runnable.run (dummy filename)' + ' at Runnable.run (dummy filename)' ].join('\n') + '\n' + nodestack @@ -223,7 +223,7 @@ const { cleanStack } = require('./utils'); 'SomeOtherError: another kind of error', ' at Context. (dummy filename)', ' at callFn (dummy filename)', - ' at Test.Runnable.run (dummy filename)' + ' at Runnable.run (dummy filename)' ].join('\n') + '\n' + nodestack diff --git a/test/context.test.js b/test/context.test.js index 58fcc41..a7c429a 100644 --- a/test/context.test.js +++ b/test/context.test.js @@ -14,13 +14,13 @@ describe('context', function() { it('should work with errors from different contexts', function(done) { const err = new Error(); const verr = new VError(err); - assert.ok(_.isError(verr.cause())); + assert.ok(_.isError(verr.cause)); const context = vm.createContext({ callback: function callback(err2) { assert.ok(_.isError(err2)); const verr2 = new VError(err); - assert.ok(_.isError(verr2.cause())); + assert.ok(_.isError(verr2.cause)); done(); } }); diff --git a/test/info.test.js b/test/info.test.js index fb5faa1..d5acd2d 100644 --- a/test/info.test.js +++ b/test/info.test.js @@ -18,7 +18,7 @@ describe('info', function() { }, 'worse' ); - assert.equal(err2.cause(), err1); + assert.equal(err2.cause, err1); assert.equal(err2.message, 'worse: bad'); assert.deepEqual(VError.info(err2), {}); }); @@ -52,7 +52,7 @@ describe('info', function() { 'bad' ); const err2 = new VError(err1, 'worse'); - assert.equal(err2.cause(), err1); + assert.equal(err2.cause, err1); assert.equal(err2.message, 'worse: bad'); assert.deepEqual(VError.info(err2), { errno: 'EDEADLK', @@ -79,7 +79,7 @@ describe('info', function() { }, 'worse' ); - assert.equal(err2.cause(), err1); + assert.equal(err2.cause, err1); assert.equal(err2.message, 'worse: bad'); assert.deepEqual(VError.info(err2), { errno: 'EDEADLK', @@ -118,7 +118,7 @@ describe('info', function() { ); assert.equal(err3.name, 'BigError'); assert.equal(VError.info(err3).remote_ip, '127.0.0.1'); - assert.equal(err3.cause(), err2); + assert.equal(err3.cause, err2); assert.equal(err3.message, 'what next: worse: bad'); assert.equal(VError.info(err3).errno, 'EDEADLK'); assert.deepEqual(VError.info(err3).anobject, { hello: 'moon' }); diff --git a/test/inherit.test.js b/test/inherit.test.js index 4f7d631..0ac5c97 100644 --- a/test/inherit.test.js +++ b/test/inherit.test.js @@ -36,7 +36,7 @@ describe('inherit', function() { assert.ok(err instanceof Error); assert.ok(err instanceof VError); assert.ok(err instanceof VErrorChild); - assert.equal(err.cause(), suberr); + assert.equal(err.cause, suberr); assert.equal(err.message, 'top: root cause'); assert.equal(err.toString(), 'VErrorChild: top: root cause'); stack = cleanStack(err.stack); @@ -46,7 +46,7 @@ describe('inherit', function() { 'VErrorChild: top: root cause', ' at Context. (dummy filename)', ' at callFn (dummy filename)', - ' at Test.Runnable.run (dummy filename)', + ' at Runnable.run (dummy filename)', nodestack ].join('\n') ); @@ -56,7 +56,7 @@ describe('inherit', function() { assert.ok(err instanceof Error); assert.ok(err instanceof WError); assert.ok(err instanceof WErrorChild); - assert.equal(err.cause(), suberr); + assert.equal(err.cause, suberr); assert.equal(err.message, 'top'); assert.equal( err.toString(), @@ -70,7 +70,7 @@ describe('inherit', function() { 'WErrorChild: top', ' at Context. (dummy filename)', ' at callFn (dummy filename)', - ' at Test.Runnable.run (dummy filename)', + ' at Runnable.run (dummy filename)', nodestack ].join('\n') ); diff --git a/test/multierror.test.js b/test/multierror.test.js index dcb42b0..0330af8 100644 --- a/test/multierror.test.js +++ b/test/multierror.test.js @@ -26,7 +26,7 @@ describe('multierror', function() { }, /must be at least one error/); merr = new MultiError([err1, err2, err3]); - assert.equal(err1, merr.cause()); + assert.equal(err1, merr.cause); assert.equal(merr.message, 'first of 3 errors: error one'); assert.equal(merr.name, 'MultiError'); stack = cleanStack(merr.stack); @@ -36,7 +36,7 @@ describe('multierror', function() { 'MultiError: first of 3 errors: error one', ' at Context. (dummy filename)', ' at callFn (dummy filename)', - ' at Test.Runnable.run (dummy filename)' + ' at Runnable.run (dummy filename)' ].join('\n') + '\n' + nodestack @@ -52,7 +52,7 @@ describe('multierror', function() { 'MultiError: first of 1 error: error one', ' at Context. (dummy filename)', ' at callFn (dummy filename)', - ' at Test.Runnable.run (dummy filename)' + ' at Runnable.run (dummy filename)' ].join('\n') + '\n' + nodestack diff --git a/test/verror.test.js b/test/verror.test.js index 6dd341f..01d16e5 100644 --- a/test/verror.test.js +++ b/test/verror.test.js @@ -36,24 +36,24 @@ describe('verror', function() { it("don't call extsprintf if pass single string to VError constructor", function() { const errString = 'my %s error'; err = new VError(errString); - assert.equal(err.message, 'errString'); + assert.equal(err.message, errString); }); it('caused by another error, with no additional message */', function() { suberr = new Error('root cause'); err = new VError(suberr); assert.equal(err.message, ': root cause'); - assert.ok(err.cause() === suberr); + assert.ok(err.cause === suberr); err = new VError({ cause: suberr }); assert.equal(err.message, ': root cause'); - assert.ok(err.cause() === suberr); + assert.ok(err.cause === suberr); }); it('caused by another error, with annotation */', function() { err = new VError(suberr, 'proximate cause: %d issues', 3); assert.equal(err.message, 'proximate cause: 3 issues: root cause'); - assert.ok(err.cause() === suberr); + assert.ok(err.cause === suberr); stack = cleanStack(err.stack); const nodestack = new Error().stack .split('\n') @@ -65,7 +65,7 @@ describe('verror', function() { 'VError: proximate cause: 3 issues: root cause', ' at Context. (dummy filename)', ' at callFn (dummy filename)', - ' at Test.Runnable.run (dummy filename)' + ' at Runnable.run (dummy filename)' ].join('\n') + '\n' + nodestack @@ -73,7 +73,7 @@ describe('verror', function() { err = new SError({ cause: suberr }, 'proximate cause: %d issues', 3); assert.equal(err.message, 'proximate cause: 3 issues: root cause'); - assert.ok(err.cause() === suberr); + assert.ok(err.cause === suberr); stack = cleanStack(err.stack); }); @@ -88,7 +88,7 @@ describe('verror', function() { 'VError: proximate cause: 3 issues: root cause', ' at Context. (dummy filename)', ' at callFn (dummy filename)', - ' at Test.Runnable.run (dummy filename)' + ' at Runnable.run (dummy filename)' ].join('\n') + '\n' + nodestack @@ -99,18 +99,18 @@ describe('verror', function() { suberr = err; err = new VError(suberr, 'top'); assert.equal(err.message, 'top: proximate cause: 3 issues: root cause'); - assert.ok(err.cause() === suberr); + assert.ok(err.cause === suberr); err = new VError({ cause: suberr }, 'top'); assert.equal(err.message, 'top: proximate cause: 3 issues: root cause'); - assert.ok(err.cause() === suberr); + assert.ok(err.cause === suberr); }); it('caused by a WError */', function() { suberr = new WError(new Error('root cause'), 'mid'); err = new VError(suberr, 'top'); assert.equal(err.message, 'top: mid'); - assert.ok(err.cause() === suberr); + assert.ok(err.cause === suberr); }); it('fullStack */', function() { @@ -127,7 +127,7 @@ describe('verror', function() { 'VError: top: mid: root cause', ' at Context. (dummy filename)', ' at callFn (dummy filename)', - ' at Test.Runnable.run (dummy filename)' + ' at Runnable.run (dummy filename)' ].join('\n') + '\n' + nodestack + @@ -136,7 +136,7 @@ describe('verror', function() { 'caused by: VError: mid: root cause', ' at Context. (dummy filename)', ' at callFn (dummy filename)', - ' at Test.Runnable.run (dummy filename)' + ' at Runnable.run (dummy filename)' ].join('\n') + '\n' + nodestack + @@ -145,7 +145,7 @@ describe('verror', function() { 'caused by: Error: root cause', ' at Context. (dummy filename)', ' at callFn (dummy filename)', - ' at Test.Runnable.run (dummy filename)' + ' at Runnable.run (dummy filename)' ].join('\n') + '\n' + nodestack diff --git a/test/werror.test.js b/test/werror.test.js index e891a2a..139cd6d 100644 --- a/test/werror.test.js +++ b/test/werror.test.js @@ -20,7 +20,7 @@ describe('werror', function() { it('no arguments */', function() { err = new WError(); assert.equal(err.toString(), 'WError'); - assert.ok(err.cause() === undefined); + assert.ok(err.cause === undefined); stack = cleanStack(err.stack); const nodestack = new Error().stack .split('\n') @@ -32,7 +32,7 @@ describe('werror', function() { 'WError', ' at Context. (dummy filename)', ' at callFn (dummy filename)', - ' at Test.Runnable.run (dummy filename)' + ' at Runnable.run (dummy filename)' ].join('\n') + '\n' + nodestack @@ -42,14 +42,14 @@ describe('werror', function() { it('options-argument form */', function() { err = new WError({}); assert.equal(err.toString(), 'WError'); - assert.ok(err.cause() === undefined); + assert.ok(err.cause === undefined); }); it('simple message */', function() { err = new WError('my error'); assert.equal(err.message, 'my error'); assert.equal(err.toString(), 'WError: my error'); - assert.ok(err.cause() === undefined); + assert.ok(err.cause === undefined); stack = cleanStack(err.stack); const nodestack = new Error().stack .split('\n') @@ -61,7 +61,7 @@ describe('werror', function() { 'WError: my error', ' at Context. (dummy filename)', ' at callFn (dummy filename)', - ' at Test.Runnable.run (dummy filename)' + ' at Runnable.run (dummy filename)' ].join('\n') + '\n' + nodestack @@ -69,7 +69,7 @@ describe('werror', function() { err = new WError({}, 'my error'); assert.equal(err.toString(), 'WError: my error'); - assert.ok(err.cause() === undefined); + assert.ok(err.cause === undefined); }); it('caused by another error, with no additional message */', function() { @@ -77,12 +77,12 @@ describe('werror', function() { err = new WError(suberr); assert.equal(err.message, ''); assert.equal(err.toString(), 'WError; caused by Error: root cause'); - assert.ok(err.cause() === suberr); + assert.ok(err.cause === suberr); err = new WError({ cause: suberr }); assert.equal(err.message, ''); assert.equal(err.toString(), 'WError; caused by Error: root cause'); - assert.ok(err.cause() === suberr); + assert.ok(err.cause === suberr); }); it('caused by another error, with annotation */', function() { @@ -92,7 +92,7 @@ describe('werror', function() { err.toString(), 'WError: proximate cause: 3 issues; caused by Error: root cause' ); - assert.ok(err.cause() === suberr); + assert.ok(err.cause === suberr); stack = cleanStack(err.stack); }); @@ -108,7 +108,7 @@ describe('werror', function() { stackmessageTop, ' at Context. (dummy filename)', ' at callFn (dummy filename)', - ' at Test.Runnable.run (dummy filename)' + ' at Runnable.run (dummy filename)' ].join('\n') + '\n' + nodestack @@ -120,7 +120,7 @@ describe('werror', function() { err.toString(), 'WError: proximate cause: 3 issues; caused by Error: root cause' ); - assert.ok(err.cause() === suberr); + assert.ok(err.cause === suberr); stack = cleanStack(err.stack); assert.equal( stack, @@ -128,7 +128,7 @@ describe('werror', function() { stackmessageTop, ' at Context. (dummy filename)', ' at callFn (dummy filename)', - ' at Test.Runnable.run (dummy filename)' + ' at Runnable.run (dummy filename)' ].join('\n') + '\n' + nodestack @@ -144,7 +144,7 @@ describe('werror', function() { 'WError: top; caused by WError: ' + 'proximate cause: 3 issues; caused by Error: root cause' ); - assert.ok(err.cause() === suberr); + assert.ok(err.cause === suberr); err = new WError({ cause: suberr }, 'top'); assert.equal(err.message, 'top'); @@ -153,7 +153,7 @@ describe('werror', function() { 'WError: top; caused by WError: ' + 'proximate cause: 3 issues; caused by Error: root cause' ); - assert.ok(err.cause() === suberr); + assert.ok(err.cause === suberr); }); it('caused by a VError */', function() { @@ -164,7 +164,7 @@ describe('werror', function() { err.toString(), 'WError: top; caused by VError: mid: root cause' ); - assert.ok(err.cause() === suberr); + assert.ok(err.cause === suberr); }); it('fullStack */', function() { @@ -186,7 +186,7 @@ describe('werror', function() { stackmessageTop, ' at Context. (dummy filename)', ' at callFn (dummy filename)', - ' at Test.Runnable.run (dummy filename)' + ' at Runnable.run (dummy filename)' ].join('\n') + '\n' + nodestack + @@ -195,7 +195,7 @@ describe('werror', function() { 'caused by: ' + stackmessageMid, ' at Context. (dummy filename)', ' at callFn (dummy filename)', - ' at Test.Runnable.run (dummy filename)' + ' at Runnable.run (dummy filename)' ].join('\n') + '\n' + nodestack + @@ -204,7 +204,7 @@ describe('werror', function() { 'caused by: Error: root cause', ' at Context. (dummy filename)', ' at callFn (dummy filename)', - ' at Test.Runnable.run (dummy filename)' + ' at Runnable.run (dummy filename)' ].join('\n') + '\n' + nodestack From 31d01eb3d61160786d219e515cee9633ae4d7eb4 Mon Sep 17 00:00:00 2001 From: Mason Albert Date: Tue, 28 Jul 2026 09:57:49 -0700 Subject: [PATCH 2/4] chore(deps): upgrade tsd to fix type-checking tsd@0.7.x bundles typescript@^3.0.1, too old to parse current @types/node syntax, causing thousands of errors. Upgrading to tsd@^0.33.0 fixes this and surfaces a few real type-test inaccuracies, also fixed here. --- package.json | 2 +- test/index.test-d.ts | 8 ++++---- 2 files changed, 5 insertions(+), 5 deletions(-) diff --git a/package.json b/package.json index ccdb787..30be27a 100644 --- a/package.json +++ b/package.json @@ -31,7 +31,7 @@ "mocha": "^5.2.0", "nyc": "^12.0.2", "prettier": "^1.13.5", - "tsd": "^0.7.3", + "tsd": "^0.33.0", "unleash": "^2.0.1" }, "dependencies": { diff --git a/test/index.test-d.ts b/test/index.test-d.ts index 93c1d87..fa17ff4 100644 --- a/test/index.test-d.ts +++ b/test/index.test-d.ts @@ -1,12 +1,12 @@ -import { expectType } from 'tsd'; +import { expectType, expectAssignable } from 'tsd'; import { VError } from '../lib'; expectType(new VError()); expectType(new VError.PError()); expectType(new VError.SError()); expectType(new VError.WError()); -expectType(new VError.MultiError([new Error()])); -expectType(new Error()); +expectType(new VError.MultiError([new Error()])); +expectAssignable(new Error()); const error = new VError( { @@ -24,5 +24,5 @@ expectType(VError.info(error)); expectType(VError.fullStack(error)); expectType(VError.findCauseByName(error, 'RefusedConnect')); expectType(VError.hasCauseWithName(error, 'RefusedConnect')); -expectType(VError.errorFromList([error])); +expectType(VError.errorFromList([error])); expectType(VError.errorForEach(error, () => {})); From b9bcc184ad68b77d953cc00d835964396a9d602b Mon Sep 17 00:00:00 2001 From: Mason Albert Date: Tue, 28 Jul 2026 09:59:04 -0700 Subject: [PATCH 3/4] chore(package): add engines floor for Node >=20 nerror has no committed lockfile, so installs can pull transitive deps that require newer Node (e.g. brace-expansion@5) and fail unpredictably. Declaring an engines floor surfaces this explicitly. This feels reasonable as nerror 2.0.0 is not expected to be used or work with node versions older than 20. --- package.json | 3 +++ 1 file changed, 3 insertions(+) diff --git a/package.json b/package.json index 30be27a..66e84d9 100644 --- a/package.json +++ b/package.json @@ -9,6 +9,9 @@ "url": "https://github.com/Netflix/nerror" }, "license": "MIT", + "engines": { + "node": ">=20" + }, "files": [ "lib", "lib/index.d.ts" From 0297fbc05a42417da40449eb14ee145942c0685d Mon Sep 17 00:00:00 2001 From: Mason Albert Date: Tue, 28 Jul 2026 10:42:55 -0700 Subject: [PATCH 4/4] chore(ci): test against Node 22/26 instead of EOL 8/10 --- .travis.yml | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/.travis.yml b/.travis.yml index 6834fc5..f8c824f 100644 --- a/.travis.yml +++ b/.travis.yml @@ -3,7 +3,7 @@ cache: sudo: false language: node_js node_js: - - '8' - - '10' + - '22' + - '26' - "lts/*" # Active LTS release - "node" # Latest stable release