Skip to content

fix: serialize SuppressedError error and suppressed - #207

Open
imharjot wants to merge 3 commits into
pinojs:masterfrom
imharjot:fix/serialize-suppressederror
Open

imharjot wants to merge 3 commits into
pinojs:masterfrom
imharjot:fix/serialize-suppressederror

Conversation

@imharjot

@imharjot imharjot commented Jun 18, 2026 •

Copy link
Copy Markdown

Preserve the non-enumerable error and suppressed properties of SuppressedError in both error serializers. A shared helper handles both serializers; nested error-like values use each serializer’s existing cause handling and cycle guard; non-error values are retained. The err() serializer still leaves custom toJSON() output in control.

Added regressions for nested and circular errors, shared references, repeated serialization, non-error values and toJSON(). Tests use native SuppressedError when available and matching property descriptors on older Node versions.

Validation: npm run lint-ci, npm run test-types and npm run test-ci passed (88 tests). The 15 suppressed-error regressions also passed under Node 18.20.8, 20.20.2 and 24.11.1.

Closes #199

@mcollina

Copy link
Copy Markdown
Member

Can you rebase? This is now conflicting.

Comment thread test/err.test.js Outdated
test('serializes SuppressedError error and suppressed', { skip: !global.SuppressedError }, () => {
const error = new Error('the operation failed')
const suppressed = new Error('disposal also failed')
const err = new global.SuppressedError(error, suppressed, 'an error was suppressed during disposal')

Copy link
Copy Markdown
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

As I linked in the original issue: this error object is 1. not actually a standard yet and 2. is not available until Node.js v24.11.1.

node-version: [18, 20]

Copy link
Copy Markdown
Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Thanks for raising the Node-version concern. The new tests no longer skip this behavior on Node 18 or 20: they use matching non-enumerable property descriptors there, and the native constructor when available. I ran all 13 new regressions under Node 18.20.8, 20.20.2 and 24.11.1; all passed. The implementation only reads the properties and does not require a global SuppressedError constructor.

@imharjot
imharjot force-pushed the fix/serialize-suppressederror branch from 72203e5 to 3978b40 Compare October 4, 2026 13:50
@imharjot

imharjot commented Oct 4, 2026

Copy link
Copy Markdown
Author

Rebased onto current master. I replaced the skipped test with coverage that runs on Node 18 and 20 as well as native SuppressedError on Node 24. Both serializers are covered, including cycles and nested errors. I also kept the existing toJSON() behavior intact. Lint, type checks and the full 86-test suite pass locally.

Comment thread lib/err.js Outdated
Comment on lines +78 to +91
// SuppressedError stores these properties as non-enumerable own properties.
for (const key of ['error', 'suppressed']) {
if (!Object.prototype.hasOwnProperty.call(err, key) || Object.prototype.propertyIsEnumerable.call(err, key)) {
continue
}
const val = err[key]
if (isErrorLike(val)) {
if (!Object.prototype.hasOwnProperty.call(val, seen)) {
_err[key] = errSerializer(val)
}
} else {
_err[key] = val
}
}

Copy link
Copy Markdown
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

The lines between 64 and 76, on cursory review, look like duplicated lines but there is a subtle difference between them: the cause key is skipped in a without cause serialization. So the blocks have to be maintained indepdendently.

The same cannot be said for the lines between 78 and 91. In both cases these lines are identical. They should really call out to a common method:

maybeSerializeSuppressedError(error, target) {
	/* loop from 78 - 91 */
}

Copy link
Copy Markdown
Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Moved the common handling into maybeSerializeSuppressedError, with each serializer passing its own recursive callback. I added coverage for nested causes so the two serializers keep their different cause behavior. Lint, type checks and all 88 tests pass; the 15 suppressed-error regressions also pass on Node 18, 20 and 24.

Comment thread lib/suppressed-error.js Resolved
@imharjot

Copy link
Copy Markdown
Author

Thanks for the review. The latest CI checks are green, and the serializer and type coverage requested in the review is included. Is there anything else you would like me to address before this can be merged?

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

SuppressedError

3 participants