diff --git a/cjs/deserialize.js b/cjs/deserialize.js index 6ce37b0..bef963b 100644 --- a/cjs/deserialize.js +++ b/cjs/deserialize.js @@ -65,7 +65,7 @@ const deserializer = ($, _) => { } case ERROR: { const {name, message} = value; - return as(guard(name, message), index); + return as(typeof env[name] === 'function' ? guard(name, message) : new Error(message), index); } case BIGINT: return as(BigInt(value), index); diff --git a/cjs/serialize.js b/cjs/serialize.js index ac6cec1..0998efe 100644 --- a/cjs/serialize.js +++ b/cjs/serialize.js @@ -34,8 +34,8 @@ const typeOf = value => { if (asString.includes('Array')) return [ARRAY, asString]; - if (asString.includes('Error')) - return [ERROR, asString]; + if (value instanceof Error) + return [ERROR, value.name]; return [OBJECT, asString]; }; diff --git a/esm/deserialize.js b/esm/deserialize.js index 440fb30..e6d00c9 100644 --- a/esm/deserialize.js +++ b/esm/deserialize.js @@ -67,7 +67,7 @@ const deserializer = ($, _) => { } case ERROR: { const {name, message} = value; - return as(guard(name, message), index); + return as(typeof env[name] === 'function' ? guard(name, message) : new Error(message), index); } case BIGINT: return as(BigInt(value), index); diff --git a/esm/serialize.js b/esm/serialize.js index 24cfe2c..98fb6dd 100644 --- a/esm/serialize.js +++ b/esm/serialize.js @@ -36,8 +36,8 @@ const typeOf = value => { if (asString.includes('Array')) return [ARRAY, asString]; - if (asString.includes('Error')) - return [ERROR, asString]; + if (value instanceof Error) + return [ERROR, value.name]; return [OBJECT, asString]; }; diff --git a/test/index.js b/test/index.js index 0277401..87b54b4 100644 --- a/test/index.js +++ b/test/index.js @@ -168,4 +168,27 @@ const invalidViaJSON = parse(stringify(invalidDate)); assert(invalidViaJSON instanceof Date, true); assert(Number.isNaN(invalidViaJSON.getTime()), true); +// error subtypes must round-trip to their own constructor like native structuredClone +for (const Class of [Error, EvalError, RangeError, ReferenceError, SyntaxError, TypeError, URIError]) { + const errorViaRecord = deserialize(serialize(new Class('boom'))); + assert(errorViaRecord instanceof Class, true, `${Class.name} must clone as ${Class.name}`); + assert(errorViaRecord.name, Class.name); + assert(errorViaRecord.message, 'boom'); +} + +// a custom-named error (name is not a global constructor) must serialize its real name, +// then revive as a plain Error, exactly like native structuredClone does +{ + const custom = new Error('boom'); + custom.name = 'CustomBunError'; + // serialize must preserve the actual name, not fall back to 'Error' + assert(serialize(custom)[0][1].name, 'CustomBunError', 'serialize must preserve the custom error name'); + const viaRecord = deserialize(serialize(custom)); + const native = globalThis.structuredClone(custom); + assert(viaRecord instanceof Error, true, 'custom error must revive as Error'); + assert(viaRecord.constructor, native.constructor, 'custom error must match native constructor'); + assert(viaRecord.message, native.message); + assert(viaRecord.name, native.name); +} + require('./eval.js');