Skip to content

fix: preserve error subtype through serialize#26

Merged
WebReflection merged 1 commit into
ungap:mainfrom
spokodev:fix/error-subtype-name
Jul 10, 2026
Merged

fix: preserve error subtype through serialize#26
WebReflection merged 1 commit into
ungap:mainfrom
spokodev:fix/error-subtype-name

Conversation

@spokodev

@spokodev spokodev commented Jul 9, 2026

Copy link
Copy Markdown
Contributor

typeOf builds the serialized error name from Object.prototype.toString.call(value), whose tag is Error for every native error subtype. So TypeError, RangeError, SyntaxError, ReferenceError, EvalError and URIError are all serialized as {name: "Error"} and come back as a plain Error:

const {serialize, deserialize} = require('@ungap/structured-clone');

deserialize(serialize(new TypeError('x'))) instanceof TypeError; // false, expected true

Native structuredClone preserves the subtype, and the deserializer here already rebuilds the correct constructor via new env[name] once it is given the right name — it just never was, so the type was lost on every round-trip.

The fix reads the instance name and keeps it when it is one of the seven standard error names, otherwise falls back to Error. That matches structuredClone, which normalizes custom subclasses and AggregateError to Error as well, and it keeps the deserializer safe since those seven names are always constructable globals.

Plain Error serialization is unchanged, so existing output is unaffected. Added a round-trip test over all seven subtypes; reverting the serialize.js change makes it fail.

@WebReflection

Copy link
Copy Markdown
Member

a few things not ideal:

  • different envs can have different errors ... the fallback should be on recreating the error but I don't think filtering a limited set of error names makes sense ... if Bun has its own errors, in Bun serialize/deserialize should revive that error, even if not in that list of 7 kinds, if that makes sense
  • the toString call is completely useless ... for errors we can use value instanceof Error instead and pass ERROR, value.name without much fuss around
  • arguably once I refactor that I should also go for isArray to avoid toString call over references easy to screen out ...

Accordingly, I would rather use isArray, fallback to instanceof for Error, then do that dance but still I am not sure that's better than a list of knnown kind to check against instanceof ... or faster ...

`typeOf` derived the serialized error `name` from
`Object.prototype.toString`, whose tag is `Error` for every native error
subtype, so `TypeError`, `RangeError`, `SyntaxError`, `ReferenceError`,
`EvalError` and `URIError` all serialized as `{name: "Error"}` and revived
as a plain `Error`:

    deserialize(serialize(new TypeError('x'))) instanceof TypeError; // false

Native `structuredClone` keeps the subtype, and the deserializer already
rebuilds the right constructor when handed the correct name.

Use the instance `name` when it is one of the standard error names,
otherwise `Error` — matching `structuredClone`, which also normalizes
custom and aggregate errors to `Error`.
@spokodev spokodev force-pushed the fix/error-subtype-name branch from 4256e56 to 9f67bfa Compare July 10, 2026 11:30
@spokodev

Copy link
Copy Markdown
Contributor Author

Done. Switched to value instanceof Error and serialize value.name for any error, so a custom/Bun error keeps its name, and dropped the 7-name list. On deserialize an unknown name (constructor not on env) recreates as a plain Error with the message, matching native structuredClone.

On the exact form: I kept the guard(name, message) path when env[name] is a constructor instead of new env[name] directly, because test/eval.js relies on guard to block Function/eval/setTimeout. So it ended up as typeof env[name] === 'function' ? guard(name, message) : new Error(message).

@WebReflection WebReflection merged commit 11e96bf into ungap:main Jul 10, 2026
2 checks passed
@WebReflection

Copy link
Copy Markdown
Member

@spokodev it's up and running

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.

2 participants