svelte
Version:
Cybernetically enhanced web apps
115 lines (95 loc) • 2.94 kB
JavaScript
/** @import { Derived, Effect } from '#client' */
/** @import { Boundary } from './dom/blocks/boundary.js' */
import { DEV } from 'esm-env';
import { FILENAME } from '../../constants.js';
import { is_firefox } from './dom/operations.js';
import { ERROR_VALUE, BOUNDARY_EFFECT, EFFECT_RAN } from './constants.js';
import { define_property, get_descriptor } from '../shared/utils.js';
import { active_effect, active_reaction } from './runtime.js';
const adjustments = new WeakMap();
/**
* @param {unknown} error
*/
export function handle_error(error) {
var effect = active_effect;
// for unowned deriveds, don't throw until we read the value
if (effect === null) {
/** @type {Derived} */ (active_reaction).f |= ERROR_VALUE;
return error;
}
if (DEV && error instanceof Error && !adjustments.has(error)) {
adjustments.set(error, get_adjustments(error, effect));
}
if ((effect.f & EFFECT_RAN) === 0) {
// if the error occurred while creating this subtree, we let it
// bubble up until it hits a boundary that can handle it
if ((effect.f & BOUNDARY_EFFECT) === 0) {
if (!effect.parent && error instanceof Error) {
apply_adjustments(error);
}
throw error;
}
/** @type {Boundary} */ (effect.b).error(error);
} else {
// otherwise we bubble up the effect tree ourselves
invoke_error_boundary(error, effect);
}
}
/**
* @param {unknown} error
* @param {Effect | null} effect
*/
export function invoke_error_boundary(error, effect) {
while (effect !== null) {
if ((effect.f & BOUNDARY_EFFECT) !== 0) {
try {
/** @type {Boundary} */ (effect.b).error(error);
return;
} catch {}
}
effect = effect.parent;
}
if (error instanceof Error) {
apply_adjustments(error);
}
throw error;
}
/**
* Add useful information to the error message/stack in development
* @param {Error} error
* @param {Effect} effect
*/
function get_adjustments(error, effect) {
const message_descriptor = get_descriptor(error, 'message');
// if the message was already changed and it's not configurable we can't change it
// or it will throw a different error swallowing the original error
if (message_descriptor && !message_descriptor.configurable) return;
var indent = is_firefox ? ' ' : '\t';
var component_stack = `\n${indent}in ${effect.fn?.name || '<unknown>'}`;
var context = effect.ctx;
while (context !== null) {
component_stack += `\n${indent}in ${context.function?.[FILENAME].split('/').pop()}`;
context = context.p;
}
return {
message: error.message + `\n${component_stack}\n`,
stack: error.stack
?.split('\n')
.filter((line) => !line.includes('svelte/src/internal'))
.join('\n')
};
}
/**
* @param {Error} error
*/
function apply_adjustments(error) {
const adjusted = adjustments.get(error);
if (adjusted) {
define_property(error, 'message', {
value: adjusted.message
});
define_property(error, 'stack', {
value: adjusted.stack
});
}
}