function serializeError(
  e: Error,
  seen: Set<Error> = new Set(),
): SerializedError {
  if (seen.has(e)) {
    return {message: '[circular]: ' + e.message, stack: e.stack};
  }
  seen.add(e);
  const {message, stack, cause} = e;
  const serialized: SerializedError = {message, stack};
  if (e instanceof AggregateError) {
    serialized.errors = [...e.errors]
      .map(innerError =>
        innerError instanceof Error ? serializeError(innerError, seen) : null,
      )
      .filter(Boolean);
  }
  if (cause instanceof Error) {
    serialized.cause = serializeError(cause, seen);
  }
  return serialized;
}
There is a special case for errors because they have non-enumerable fields.
(Perhaps we should switch in favor of plain object?)