Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
2 changes: 1 addition & 1 deletion cjs/deserialize.js
Original file line number Diff line number Diff line change
Expand Up @@ -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);
Expand Down
4 changes: 2 additions & 2 deletions cjs/serialize.js
Original file line number Diff line number Diff line change
Expand Up @@ -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];
};
Expand Down
2 changes: 1 addition & 1 deletion esm/deserialize.js
Original file line number Diff line number Diff line change
Expand Up @@ -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);
Expand Down
4 changes: 2 additions & 2 deletions esm/serialize.js
Original file line number Diff line number Diff line change
Expand Up @@ -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];
};
Expand Down
23 changes: 23 additions & 0 deletions test/index.js
Original file line number Diff line number Diff line change
Expand Up @@ -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');
Loading