diff --git a/packages/common/NEWS.md b/packages/common/NEWS.md index fd9f706c3c..aac7ee8c7b 100644 --- a/packages/common/NEWS.md +++ b/packages/common/NEWS.md @@ -1,5 +1,9 @@ User-visible changes in `@endo/common`: +# Next release + +- TODO explain further generalization of `throwLabeled` + # v1.1.0 (2024-02-22) - `throwLabeled` parameterized error construction diff --git a/packages/errors/NEWS.md b/packages/errors/NEWS.md index 99c636c9da..6a1c99e21a 100644 --- a/packages/errors/NEWS.md +++ b/packages/errors/NEWS.md @@ -1,5 +1,10 @@ User-visible changes in `@endo/errors`: +# Next release + +- TODO explain `SuppressedError` support + + # v1.1.0 (2024-02-22) - `AggegateError` support diff --git a/packages/eslint-plugin/lib/configs/recommended.js b/packages/eslint-plugin/lib/configs/recommended.js index 6e0da94c8a..2c0acd2a1c 100644 --- a/packages/eslint-plugin/lib/configs/recommended.js +++ b/packages/eslint-plugin/lib/configs/recommended.js @@ -63,6 +63,8 @@ module.exports = { HandledPromise: 'readonly', // https://github.com/endojs/endo/issues/550 AggregateError: 'readonly', + // https://github.com/tc39/proposal-explicit-resource-management + SuppressedError: 'readonly', }, rules: { '@endo/assert-fail-as-throw': 'error', diff --git a/packages/marshal/NEWS.md b/packages/marshal/NEWS.md index 4b5c8ac543..c2ed375bdc 100644 --- a/packages/marshal/NEWS.md +++ b/packages/marshal/NEWS.md @@ -1,5 +1,9 @@ User-visible changes in `@endo/marshal`: +# Next release + +TODO explain `SuppressedError` support + # v1.5.1 (2024-07-30) - `deeplyFulfilled` moved from @endo/marshal to @endo/pass-style. @endo/marshal still reexports it, to avoid breaking old importers. But importers should be upgraded to import `deeplyFulfilled` directly from @endo/pass-style. diff --git a/packages/marshal/src/marshal-justin.js b/packages/marshal/src/marshal-justin.js index 681c14e962..0a6c3da8ab 100644 --- a/packages/marshal/src/marshal-justin.js +++ b/packages/marshal/src/marshal-justin.js @@ -399,8 +399,10 @@ const decodeToJustin = (encoding, shouldIndent = false, slots = []) => { Fail`error cause not yet implemented in marshal-justin`; name !== `AggregateError` || Fail`AggregateError not yet implemented in marshal-justin`; + // TODO SuppressedError errors === undefined || Fail`error errors not yet implemented in marshal-justin`; + // TODO error,suppressed return out.next(`${name}(${quote(message)})`); } diff --git a/packages/marshal/src/marshal.js b/packages/marshal/src/marshal.js index 86ad6891ac..fe67a69f45 100644 --- a/packages/marshal/src/marshal.js +++ b/packages/marshal/src/marshal.js @@ -112,7 +112,7 @@ export const makeMarshal = ( assert.typeof(message, 'string'); const name = encodeRecur(`${err.name}`); assert.typeof(name, 'string'); - // TODO Must encode `cause`, `errors`, but + // TODO Must encode `cause`,`errors`,`error`,`suppressed` but // only once all possible counterparty decoders are tolerant of // receiving them. if (errorTagging === 'on') { @@ -262,8 +262,10 @@ export const makeMarshal = ( * errorId?: string, * message: string, * name: string, - * cause: unknown, - * errors: unknown, + * cause?: unknown, + * errors?: unknown, + * error?: unknown, + * suppressed?: unknown, * }} errData * @param {(e: unknown) => Passable} decodeRecur * @returns {Error} @@ -275,6 +277,8 @@ export const makeMarshal = ( name, cause = undefined, errors = undefined, + error = undefined, + suppressed = undefined, ...rest } = errData; // See https://github.com/endojs/endo/pull/2052 @@ -306,6 +310,12 @@ export const makeMarshal = ( if (errors) { options.errors = decodeRecur(errors); } + if (error) { + options.error = decodeRecur(error); + } + if (suppressed) { + options.suppressed = decodeRecur(suppressed); + } const rawError = makeError(dMessage, errConstructor, options); // Note that this does not decodeRecur rest's property names. // This would be inconsistent with smallcaps' expected handling, diff --git a/packages/marshal/src/types.js b/packages/marshal/src/types.js index c70818bf34..a8a110c70f 100644 --- a/packages/marshal/src/types.js +++ b/packages/marshal/src/types.js @@ -38,6 +38,8 @@ export {}; * errorId?: string, * cause?: Encoding, * errors?: Encoding[], + * error?: Encoding, + * suppressed?: Encoding, * } | * EncodingClass<'slot'> & { index: number, * iface?: string diff --git a/packages/marshal/test/marshal-capdata.test.js b/packages/marshal/test/marshal-capdata.test.js index 3e14762753..d7d0492099 100644 --- a/packages/marshal/test/marshal-capdata.test.js +++ b/packages/marshal/test/marshal-capdata.test.js @@ -219,6 +219,8 @@ testIfAggregateError('unserialize errors w recognized extensions', t => { t.is(getPrototypeOf(unkErr.errors[0]), URIError.prototype); }); +// TODO SuppressedError + test('passStyleOf null is "null"', t => { t.assert(passStyleOf(null), 'null'); }); diff --git a/packages/marshal/test/marshal-smallcaps.test.js b/packages/marshal/test/marshal-smallcaps.test.js index ce5f1a763a..151d73d143 100644 --- a/packages/marshal/test/marshal-smallcaps.test.js +++ b/packages/marshal/test/marshal-smallcaps.test.js @@ -226,6 +226,8 @@ test('smallcaps unserialize errors w recognized extensions', t => { t.is(getPrototypeOf(refErr.errors[0]), URIError.prototype); }); +// TODO SuppressedError + test('smallcaps mal-formed @qclass', t => { const { unserialize } = makeTestMarshal(); const uns = body => unserialize({ body, slots: [] }); diff --git a/packages/pass-style/NEWS.md b/packages/pass-style/NEWS.md index cea0c9aae4..7ff9590bdd 100644 --- a/packages/pass-style/NEWS.md +++ b/packages/pass-style/NEWS.md @@ -1,5 +1,9 @@ User-visible changes in `@endo/pass-style`: +# Next release + +TODO explain `SuppressedError` support + # v1.4.1 (2024-07-30) - `deeplyFulfilled` moved from @endo/marshal to @endo/pass-style. @endo/marshal still reexports it, to avoid breaking old importers. But importers should be upgraded to import `deeplyFulfilled` directly from @endo/pass-style. diff --git a/packages/pass-style/src/error.js b/packages/pass-style/src/error.js index 83fecc90ac..969f256bcf 100644 --- a/packages/pass-style/src/error.js +++ b/packages/pass-style/src/error.js @@ -27,6 +27,8 @@ const errorConstructors = new Map( // To accommodate platforms prior to AggregateError, we comment out the // following line and instead conditionally add it to the map below. // ['AggregateError', AggregateError], + // Likewise https://github.com/tc39/proposal-explicit-resource-management + // ['SuppressedError', SuppressedError], ]), ); @@ -35,9 +37,15 @@ if (typeof AggregateError !== 'undefined') { errorConstructors.set('AggregateError', AggregateError); } +if (typeof SuppressedError !== 'undefined') { + // Conditional, to accommodate platforms prior to SuppressedError + errorConstructors.set('SuppressedError', SuppressedError); +} + /** * Because the error constructor returned by this function might be - * `AggregateError`, which has different construction parameters + * `AggregateError` or `SuppressedError`, + * each of which has different construction parameters * from the other error constructors, do not use it directly to try * to make an error instance. Rather, use `makeError` which encapsulates * this non-uniformity. @@ -125,7 +133,9 @@ export const checkRecursivelyPassableErrorPropertyDesc = ( )} own property must be a string: ${value}`) ); } - case 'cause': { + case 'cause': + case 'error': + case 'suppressed': { // eslint-disable-next-line no-use-before-define return checkRecursivelyPassableError(value, passStyleOfRecur, check); } diff --git a/packages/pass-style/src/passStyleOf.js b/packages/pass-style/src/passStyleOf.js index 7c4dd78b2e..ae9eb40152 100644 --- a/packages/pass-style/src/passStyleOf.js +++ b/packages/pass-style/src/passStyleOf.js @@ -281,22 +281,39 @@ export const toPassableError = err => { return err; } const { name, message } = err; - const { cause: causeDesc, errors: errorsDesc } = - getOwnPropertyDescriptors(err); + const { + cause: causeDesc, + errors: errorsDesc, + error: errorDesc, + suppressed: suppressedDesc, + } = getOwnPropertyDescriptors(err); let cause; let errors; + let error; + let suppressed; if (causeDesc && isPassableErrorPropertyDesc('cause', causeDesc)) { cause = causeDesc.value; } if (errorsDesc && isPassableErrorPropertyDesc('errors', errorsDesc)) { errors = errorsDesc.value; } + if (errorDesc && isPassableErrorPropertyDesc('error', errorDesc)) { + error = errorDesc.value; + } + if ( + suppressedDesc && + isPassableErrorPropertyDesc('suppressed', suppressedDesc) + ) { + suppressed = suppressedDesc.value; + } const errConstructor = getErrorConstructor(`${name}`) || Error; const newError = makeError(`${message}`, errConstructor, { // @ts-ignore Assuming cause is Error | undefined cause, errors, + error, + suppressed, }); // Still needed, because `makeError` only does a shallow freeze. harden(newError); diff --git a/packages/pass-style/test/errors.test.js b/packages/pass-style/test/errors.test.js index 2c0fd0fcce..c94e3a67b6 100644 --- a/packages/pass-style/test/errors.test.js +++ b/packages/pass-style/test/errors.test.js @@ -32,6 +32,11 @@ test('style of extended errors', t => { const a4 = harden(AggregateError([e2, u3], 'a4', { cause: e1 })); t.is(passStyleOf(a4), 'error'); } + if (typeof SuppressedError !== 'undefined') { + // Conditional, to accommodate platforms prior to SuppressedError + const a4 = harden(SuppressedError(e2, u3, 'a4')); + t.is(passStyleOf(a4), 'error'); + } }); test('toPassableError, toThrowable', t => { diff --git a/packages/ses/NEWS.md b/packages/ses/NEWS.md index 730956fbfe..ab849482da 100644 --- a/packages/ses/NEWS.md +++ b/packages/ses/NEWS.md @@ -2,6 +2,8 @@ User-visible changes in `ses`: # v1.8.0 (2024-08-27) +- TODO explain `SuppressedError` support + - New `legacyRegeneratorRuntimeTaming: 'unsafe-ignore'` lockdown option to tame old `regenerator-runtime` (from 0.10.5 to 0.13.7). - If lockdown's `errorTrapping: 'report'` mode is selected (possibly via the diff --git a/packages/ses/src/commons.js b/packages/ses/src/commons.js index 3d696f3a77..6ec71f24f3 100644 --- a/packages/ses/src/commons.js +++ b/packages/ses/src/commons.js @@ -48,6 +48,7 @@ export const { SyntaxError, TypeError, AggregateError, + SuppressedError, } = globalThis; export const { diff --git a/packages/ses/src/enablements.js b/packages/ses/src/enablements.js index 9124238aa7..457ecad23c 100644 --- a/packages/ses/src/enablements.js +++ b/packages/ses/src/enablements.js @@ -155,6 +155,12 @@ export const moderateEnablements = { name: true, // set by "node 14"? }, + // https://github.com/tc39/proposal-explicit-resource-management + '%SuppressedErrorPrototype%': { + message: true, // to match TypeErrorPrototype.message + name: true, // set by some Node? + }, + '%PromisePrototype%': { constructor: true, // set by "core-js" }, diff --git a/packages/ses/src/error/assert.js b/packages/ses/src/error/assert.js index 06d764da2c..2c8b471338 100644 --- a/packages/ses/src/error/assert.js +++ b/packages/ses/src/error/assert.js @@ -35,6 +35,7 @@ import { weakmapHas, weakmapSet, AggregateError, + SuppressedError, getOwnPropertyDescriptors, ownKeys, create, @@ -268,12 +269,13 @@ const tagError = (err, optErrorName = err.name) => { * such as `stack` on v8 (Chrome, Brave, Edge?) * - `sanitizeError` will freeze the error, preventing any correct engine from * adding or - * altering any of the error's own properties `sanitizeError` is done. + * altering any of the error's own properties after `sanitizeError` is done. * * However, `sanitizeError` will not, for example, `harden` * (i.e., deeply freeze) - * or ensure that the `cause` or `errors` property satisfy the `Passable` - * constraints. The purpose of `sanitizeError` is only to protect against + * or ensure that the `cause`, `errors`, `error`, or `suppressed` properties + * satisfy the `Passable` constraints. + * The purpose of `sanitizeError` is only to protect against * mischief the host may have already added to the error as created, * not to ensure that the error is actually Passable. For that, * see `toPassableError` in `@endo/pass-style`. @@ -285,8 +287,10 @@ export const sanitizeError = error => { const { name: _nameDesc, message: _messageDesc, - errors: _errorsDesc = undefined, cause: _causeDesc = undefined, + errors: _errorsDesc = undefined, + error: _errorDesc = undefined, + suppressed: _suppressedDesc = undefined, stack: _stackDesc = undefined, ...restDescs } = descs; @@ -316,6 +320,8 @@ export const sanitizeError = error => { }; /** + * TODO rewrite to be more general + * * @type {AssertionUtilities['error']} */ const makeError = ( @@ -325,6 +331,8 @@ const makeError = ( errorName = undefined, cause = undefined, errors = undefined, + error = undefined, + suppressed = undefined, sanitize = true, } = {}, ) => { @@ -339,37 +347,41 @@ const makeError = ( } const messageString = getMessageString(hiddenDetails); const opts = cause && { cause }; - let error; + let err; if ( typeof AggregateError !== 'undefined' && errConstructor === AggregateError ) { - error = AggregateError(errors || [], messageString, opts); + err = AggregateError(errors || [], messageString, opts); + } else if ( + typeof SuppressedError !== 'undefined' && + errConstructor === SuppressedError + ) { + err = SuppressedError(error, suppressed, messageString); + // TODO what about errors, cause? } else { - error = /** @type {ErrorConstructor} */ (errConstructor)( - messageString, - opts, - ); + err = /** @type {ErrorConstructor} */ (errConstructor)(messageString, opts); if (errors !== undefined) { // Since we need to tolerate `errors` on an AggregateError, may as // well tolerate it on all errors. - defineProperty(error, 'errors', { + defineProperty(err, 'errors', { value: errors, writable: true, enumerable: false, configurable: true, }); } + // TODO similarly tolerate error,suppressed ? } - weakmapSet(hiddenMessageLogArgs, error, getLogArgs(hiddenDetails)); + weakmapSet(hiddenMessageLogArgs, err, getLogArgs(hiddenDetails)); if (errorName !== undefined) { - tagError(error, errorName); + tagError(err, errorName); } if (sanitize) { - sanitizeError(error); + sanitizeError(err); } // The next line is a particularly fruitful place to put a breakpoint. - return error; + return err; }; freeze(makeError); diff --git a/packages/ses/src/error/console.js b/packages/ses/src/error/console.js index 8ad8853805..fdf70d5160 100644 --- a/packages/ses/src/error/console.js +++ b/packages/ses/src/error/console.js @@ -207,6 +207,8 @@ const ErrorInfo = { MESSAGE: 'ERROR_MESSAGE:', CAUSE: 'cause:', ERRORS: 'errors:', + ERROR: 'error:', + SUPPRESSED: 'suppressed:', }; freeze(ErrorInfo); @@ -354,6 +356,7 @@ export const makeCausalConsole = (baseConsole, loggedErrorHandler) => { // @ts-expect-error AggregateError has an `errors` property. logErrorInfo(severity, error, ErrorInfo.ERRORS, error.errors, subErrors); } + // TODO SuppressedError for (const noteLogArgs of noteLogArgsArray) { logErrorInfo(severity, error, ErrorInfo.NOTE, noteLogArgs, subErrors); } diff --git a/packages/ses/src/error/internal-types.js b/packages/ses/src/error/internal-types.js index 9cfe79413b..c4e94290a5 100644 --- a/packages/ses/src/error/internal-types.js +++ b/packages/ses/src/error/internal-types.js @@ -74,6 +74,8 @@ * MESSAGE: 'ERROR_MESSAGE:', * CAUSE: 'cause:', * ERRORS: 'errors:', + * ERROR: 'error:', + * SUPPRESSED: 'suppressed:', * }} ErrorInfo */ diff --git a/packages/ses/src/permits.js b/packages/ses/src/permits.js index 585d3c0c3f..878cf76d35 100644 --- a/packages/ses/src/permits.js +++ b/packages/ses/src/permits.js @@ -89,6 +89,13 @@ export const universalPropertyNames = { // https://github.com/endojs/endo/issues/550 AggregateError: 'AggregateError', + // https://github.com/tc39/proposal-explicit-resource-management + AsyncDisposableStack: 'AsyncDisposableStack', + // https://github.com/tc39/proposal-explicit-resource-management + DisposableStack: 'DisposableStack', + // https://github.com/tc39/proposal-explicit-resource-management + SuppressedError: 'SuppressedError', + // *** Other Properties of the Global Object JSON: 'JSON', @@ -203,6 +210,8 @@ const NativeErrors = [ // Commented out to accommodate platforms prior to AggregateError. // Instead, conditional push below. // AggregateError, + // Likewise https://github.com/tc39/proposal-explicit-resource-management + // SuppressedError, ]; if (typeof AggregateError !== 'undefined') { @@ -210,6 +219,11 @@ if (typeof AggregateError !== 'undefined') { arrayPush(NativeErrors, AggregateError); } +if (typeof SuppressedError !== 'undefined') { + // Conditional, to accommodate platforms prior to SuppressedError + arrayPush(NativeErrors, SuppressedError); +} + export { NativeErrors }; /** @@ -533,8 +547,10 @@ export const permitted = { '%SharedSymbol%': { // Properties of the Symbol Constructor '[[Proto]]': '%FunctionPrototype%', + // https://github.com/tc39/proposal-explicit-resource-management asyncDispose: 'symbol', asyncIterator: 'symbol', + // https://github.com/tc39/proposal-explicit-resource-management dispose: 'symbol', for: fn, hasInstance: 'symbol', @@ -618,6 +634,8 @@ export const permitted = { URIError: NativeError('%URIErrorPrototype%'), // https://github.com/endojs/endo/issues/550 AggregateError: NativeError('%AggregateErrorPrototype%'), + // https://github.com/tc39/proposal-explicit-resource-management + SuppressedError: NativeError('%SuppressedErrorPrototype%'), '%EvalErrorPrototype%': NativeErrorPrototype('EvalError'), '%RangeErrorPrototype%': NativeErrorPrototype('RangeError'), @@ -627,6 +645,8 @@ export const permitted = { '%URIErrorPrototype%': NativeErrorPrototype('URIError'), // https://github.com/endojs/endo/issues/550 '%AggregateErrorPrototype%': NativeErrorPrototype('AggregateError'), + // https://github.com/tc39/proposal-explicit-resource-management + '%SuppressedErrorPrototype%': NativeErrorPrototype('SuppressedError'), // *** Numbers and Dates @@ -1289,6 +1309,44 @@ export const permitted = { SharedArrayBuffer: false, // UNSAFE and purposely suppressed. '%SharedArrayBufferPrototype%': false, // UNSAFE and purposely suppressed. + // https://github.com/tc39/proposal-explicit-resource-management + DisposableStack: { + '[[Proto]]': '%FunctionPrototype%', + prototype: '%DisposableStackPrototype%', + }, + + // https://github.com/tc39/proposal-explicit-resource-management + '%DisposableStackPrototype%': { + constructor: 'DisposableStack', + adopt: fn, + defer: fn, + dispose: fn, + disposed: getter, + move: fn, + use: fn, + '@@dispose': fn, + '@@toStringTag': 'string', + }, + + // https://github.com/tc39/proposal-explicit-resource-management + AsyncDisposableStack: { + '[[Proto]]': '%FunctionPrototype%', + prototype: '%AsyncDisposableStackPrototype%', + }, + + // https://github.com/tc39/proposal-explicit-resource-management + '%AsyncDisposableStackPrototype%': { + constructor: 'AsyncDisposableStack', + adopt: fn, + defer: fn, + disposeAsync: fn, + disposed: getter, + move: fn, + use: fn, + '@@asyncDispose': fn, + '@@toStringTag': 'string', + }, + DataView: { // Properties of the DataView Constructor '[[Proto]]': '%FunctionPrototype%', @@ -1369,8 +1427,8 @@ export const permitted = { '@@toStringTag': 'string', // https://github.com/tc39/proposal-async-iterator-helpers toAsync: fn, - // See https://github.com/Moddable-OpenSource/moddable/issues/523#issuecomment-1942904505 - '@@dispose': false, + // https://github.com/tc39/proposal-explicit-resource-management + '@@dispose': fn, }, // https://github.com/tc39/proposal-iterator-helpers @@ -1413,8 +1471,8 @@ export const permitted = { every: fn, find: fn, '@@toStringTag': 'string', - // See https://github.com/Moddable-OpenSource/moddable/issues/523#issuecomment-1942904505 - '@@asyncDispose': false, + // https://github.com/tc39/proposal-explicit-resource-management + '@@asyncDispose': fn, }, // https://github.com/tc39/proposal-async-iterator-helpers diff --git a/packages/ses/test/error/aggregate-error-console-demo.test.js b/packages/ses/test/error/aggregate-error-console-demo.test.js index ba2da6ae16..05a3dd4468 100644 --- a/packages/ses/test/error/aggregate-error-console-demo.test.js +++ b/packages/ses/test/error/aggregate-error-console-demo.test.js @@ -22,3 +22,5 @@ test('aggregate error console demo', t => { console.log('log1', a1); t.is(a1.cause, e2); }); + +// TODO SuppressedError diff --git a/packages/ses/test/error/aggregate-error-console.test.js b/packages/ses/test/error/aggregate-error-console.test.js index ba192546be..12c58820d3 100644 --- a/packages/ses/test/error/aggregate-error-console.test.js +++ b/packages/ses/test/error/aggregate-error-console.test.js @@ -46,3 +46,5 @@ test('aggregate error console', t => { { wrapWithCausal: true }, ); }); + +// TODO SuppressedError diff --git a/packages/ses/test/error/aggregate-error.test.js b/packages/ses/test/error/aggregate-error.test.js index 82f397799c..fd02cba3c9 100644 --- a/packages/ses/test/error/aggregate-error.test.js +++ b/packages/ses/test/error/aggregate-error.test.js @@ -53,3 +53,5 @@ test('Promise.any aggregate error', async t => { }); } }); + +// TODO SuppressedError diff --git a/packages/ses/test/error/error-cause.test.js b/packages/ses/test/error/error-cause.test.js index 5cd32091f5..7952d2f035 100644 --- a/packages/ses/test/error/error-cause.test.js +++ b/packages/ses/test/error/error-cause.test.js @@ -41,3 +41,5 @@ test('error cause', t => { configurable: true, }); }); + +// TODO SuppressedError diff --git a/packages/ses/test/get-global-intrinsics.test.js b/packages/ses/test/get-global-intrinsics.test.js index 19b0531612..9ea264ae15 100644 --- a/packages/ses/test/get-global-intrinsics.test.js +++ b/packages/ses/test/get-global-intrinsics.test.js @@ -62,6 +62,8 @@ test.skip('getGlobalIntrinsics', () => { 'WeakSet', // https://github.com/endojs/endo/issues/550 'AggregateError', + // https://github.com/tc39/proposal-explicit-resource-management + 'SuppressedError', // *** 18.4 Other Properties of the Global Object diff --git a/packages/ses/types.d.ts b/packages/ses/types.d.ts index 1cd2f57702..b98fef95f5 100644 --- a/packages/ses/types.d.ts +++ b/packages/ses/types.d.ts @@ -189,6 +189,24 @@ export interface AssertMakeErrorOptions { */ errors?: Error[]; + /** + * Normally only used when the ErrorConstuctor is `SuppressedError`, to + * represent the error exiting a disposal context where an error also happens + * during disposing. But `makeError` allows it on any error. + * This is represented by a public `error` data property on the error, + * not a hidden annotation. + */ + error?: Error; + + /** + * Normally only used when the ErrorConstuctor is `SuppressedError`, to + * represent an error happens during a disposing that is itself caused + * by an error. But `makeError` allows it on any error. + * This is represented by a public `suppressed` data property on the error, + * not a hidden annotation. + */ + suppressed?: Error; + /** * Defaults to true. If true, `makeError` will apply `sanitizeError` * to the error before returning it. See the comments on @@ -264,7 +282,8 @@ interface StringablePayload { */ export type GenericErrorConstructor = | ErrorConstructor - | AggregateErrorConstructor; + | AggregateErrorConstructor + | SuppressedErrorConstructor; /** * To make an `assert` which terminates some larger unit of computation