rynex
Version:
A minimalist TypeScript framework for building reactive web applications with no virtual DOM
348 lines • 12.5 kB
JavaScript
/**
* Rynex Error System
* Comprehensive error handling and validation for the framework
*/
// Error severity levels
export var ErrorSeverity;
(function (ErrorSeverity) {
ErrorSeverity["WARNING"] = "warning";
ErrorSeverity["ERROR"] = "error";
ErrorSeverity["CRITICAL"] = "critical";
})(ErrorSeverity || (ErrorSeverity = {}));
// Error categories
export var ErrorCategory;
(function (ErrorCategory) {
ErrorCategory["VALIDATION"] = "validation";
ErrorCategory["RUNTIME"] = "runtime";
ErrorCategory["DOM"] = "dom";
ErrorCategory["STATE"] = "state";
ErrorCategory["COMPONENT"] = "component";
ErrorCategory["ROUTER"] = "router";
ErrorCategory["LIFECYCLE"] = "lifecycle";
ErrorCategory["PROPS"] = "props";
ErrorCategory["CHILDREN"] = "children";
})(ErrorCategory || (ErrorCategory = {}));
// Base error class for Rynex
export class RynexError extends Error {
constructor(message, category, severity = ErrorSeverity.ERROR, context) {
super(message);
this.name = 'RynexError';
this.severity = severity;
this.category = category;
this.timestamp = Date.now();
this.context = context;
// Maintain proper stack trace
if (Error.captureStackTrace) {
Error.captureStackTrace(this, RynexError);
}
}
toString() {
return `[${this.severity.toUpperCase()}] [${this.category}] ${this.message}`;
}
}
// Specific error types
export class ValidationError extends RynexError {
constructor(message, context) {
super(message, ErrorCategory.VALIDATION, ErrorSeverity.ERROR, context);
this.name = 'ValidationError';
}
}
export class DOMError extends RynexError {
constructor(message, severity = ErrorSeverity.ERROR, context) {
super(message, ErrorCategory.DOM, severity, context);
this.name = 'DOMError';
}
}
export class StateError extends RynexError {
constructor(message, context) {
super(message, ErrorCategory.STATE, ErrorSeverity.ERROR, context);
this.name = 'StateError';
}
}
export class ComponentError extends RynexError {
constructor(message, severity = ErrorSeverity.ERROR, context) {
super(message, ErrorCategory.COMPONENT, severity, context);
this.name = 'ComponentError';
}
}
export class RouterError extends RynexError {
constructor(message, context) {
super(message, ErrorCategory.ROUTER, ErrorSeverity.ERROR, context);
this.name = 'RouterError';
}
}
export class LifecycleError extends RynexError {
constructor(message, context) {
super(message, ErrorCategory.LIFECYCLE, ErrorSeverity.ERROR, context);
this.name = 'LifecycleError';
}
}
class ErrorHandler {
constructor() {
this.config = {
throwOnError: true,
logErrors: true,
captureStackTrace: true
};
this.errorLog = [];
this.maxLogSize = 100;
}
configure(config) {
this.config = { ...this.config, ...config };
}
handle(error) {
// Add to log
this.errorLog.push(error);
if (this.errorLog.length > this.maxLogSize) {
this.errorLog.shift();
}
// Log to console if enabled
if (this.config.logErrors) {
const style = this.getConsoleStyle(error.severity);
console.error(`%c${error.toString()}`, style, error.context ? `\nContext:` : '', error.context || '');
if (this.config.captureStackTrace && error.stack) {
console.error(error.stack);
}
}
// Call custom error handler
if (this.config.onError) {
this.config.onError(error);
}
// Throw if configured
if (this.config.throwOnError && error.severity === ErrorSeverity.CRITICAL) {
throw error;
}
}
getConsoleStyle(severity) {
switch (severity) {
case ErrorSeverity.WARNING:
return 'color: #ffc107; font-weight: bold;';
case ErrorSeverity.ERROR:
return 'color: #dc3545; font-weight: bold;';
case ErrorSeverity.CRITICAL:
return 'color: #ff0000; font-weight: bold; font-size: 14px;';
default:
return '';
}
}
getErrors() {
return [...this.errorLog];
}
clearErrors() {
this.errorLog = [];
}
getErrorsByCategory(category) {
return this.errorLog.filter(err => err.category === category);
}
getErrorsBySeverity(severity) {
return this.errorLog.filter(err => err.severity === severity);
}
}
// Global error handler instance
export const errorHandler = new ErrorHandler();
// Validation utilities
export const validators = {
/**
* Validate that a value is not null or undefined
*/
required(value, fieldName, context) {
if (value === null || value === undefined) {
throw new ValidationError(`${fieldName} is required but received ${value}`, { fieldName, value, ...context });
}
},
/**
* Validate type of a value
*/
type(value, expectedType, fieldName, context) {
const actualType = typeof value;
if (actualType !== expectedType) {
throw new ValidationError(`${fieldName} expected type '${expectedType}' but received '${actualType}'`, { fieldName, expectedType, actualType, value, ...context });
}
},
/**
* Validate that value is a function
*/
isFunction(value, fieldName, context) {
if (typeof value !== 'function') {
throw new ValidationError(`${fieldName} must be a function but received ${typeof value}`, { fieldName, value, ...context });
}
},
/**
* Validate that value is an object
*/
isObject(value, fieldName, context) {
if (typeof value !== 'object' || value === null || Array.isArray(value)) {
throw new ValidationError(`${fieldName} must be an object but received ${typeof value}`, { fieldName, value, ...context });
}
},
/**
* Validate that value is an array
*/
isArray(value, fieldName, context) {
if (!Array.isArray(value)) {
throw new ValidationError(`${fieldName} must be an array but received ${typeof value}`, { fieldName, value, ...context });
}
},
/**
* Validate that value is a string
*/
isString(value, fieldName, context) {
if (typeof value !== 'string') {
throw new ValidationError(`${fieldName} must be a string but received ${typeof value}`, { fieldName, value, ...context });
}
},
/**
* Validate that value is a number
*/
isNumber(value, fieldName, context) {
if (typeof value !== 'number' || isNaN(value)) {
throw new ValidationError(`${fieldName} must be a valid number but received ${typeof value}`, { fieldName, value, ...context });
}
},
/**
* Validate that value is a boolean
*/
isBoolean(value, fieldName, context) {
if (typeof value !== 'boolean') {
throw new ValidationError(`${fieldName} must be a boolean but received ${typeof value}`, { fieldName, value, ...context });
}
},
/**
* Validate that value is an HTMLElement
*/
isHTMLElement(value, fieldName, context) {
if (!(value instanceof HTMLElement)) {
throw new DOMError(`${fieldName} must be an HTMLElement but received ${value?.constructor?.name || typeof value}`, ErrorSeverity.ERROR, { fieldName, value, ...context });
}
},
/**
* Validate that element exists in DOM
*/
elementExists(element, fieldName, context) {
if (!element) {
throw new DOMError(`${fieldName} element not found in DOM`, ErrorSeverity.ERROR, { fieldName, ...context });
}
},
/**
* Validate that selector is valid
*/
validSelector(selector, fieldName, context) {
try {
document.querySelector(selector);
}
catch (error) {
throw new DOMError(`${fieldName} contains invalid CSS selector: ${selector}`, ErrorSeverity.ERROR, { fieldName, selector, error, ...context });
}
},
/**
* Validate that value is one of allowed values
*/
oneOf(value, allowedValues, fieldName, context) {
if (!allowedValues.includes(value)) {
throw new ValidationError(`${fieldName} must be one of [${allowedValues.join(', ')}] but received '${value}'`, { fieldName, value, allowedValues, ...context });
}
},
/**
* Validate array is not empty
*/
notEmpty(arr, fieldName, context) {
if (!Array.isArray(arr) || arr.length === 0) {
throw new ValidationError(`${fieldName} must be a non-empty array`, { fieldName, value: arr, ...context });
}
},
/**
* Validate string is not empty
*/
notEmptyString(value, fieldName, context) {
if (typeof value !== 'string' || value.trim().length === 0) {
throw new ValidationError(`${fieldName} must be a non-empty string`, { fieldName, value, ...context });
}
},
/**
* Validate number is in range
*/
inRange(value, min, max, fieldName, context) {
if (typeof value !== 'number' || value < min || value > max) {
throw new ValidationError(`${fieldName} must be between ${min} and ${max} but received ${value}`, { fieldName, value, min, max, ...context });
}
},
/**
* Validate props object
*/
validProps(props, fieldName = 'props', context) {
if (props !== null && props !== undefined) {
if (typeof props !== 'object' || Array.isArray(props)) {
throw new ValidationError(`${fieldName} must be an object or null/undefined but received ${typeof props}`, { fieldName, props, ...context });
}
}
},
/**
* Validate children
*/
validChildren(children, fieldName = 'children', context) {
const validateChild = (child) => {
return (child === null ||
child === undefined ||
child === false ||
child === true ||
typeof child === 'string' ||
typeof child === 'number' ||
child instanceof HTMLElement ||
child instanceof SVGElement ||
child instanceof Text);
};
if (Array.isArray(children)) {
const flatChildren = children.flat(Infinity);
for (let i = 0; i < flatChildren.length; i++) {
if (!validateChild(flatChildren[i])) {
throw new ValidationError(`Invalid child at index ${i}: expected HTMLElement, string, number, or null/undefined but received ${typeof flatChildren[i]}`, { fieldName, index: i, child: flatChildren[i], ...context });
}
}
}
else if (!validateChild(children)) {
throw new ValidationError(`${fieldName} must be a valid DOM child (HTMLElement, string, number, or null/undefined) but received ${typeof children}`, { fieldName, children, ...context });
}
}
};
// Safe execution wrapper
export function safeExecute(fn, errorMessage, category = ErrorCategory.RUNTIME, context) {
try {
return fn();
}
catch (error) {
const rynexError = new RynexError(`${errorMessage}: ${error instanceof Error ? error.message : String(error)}`, category, ErrorSeverity.ERROR, { originalError: error, ...context });
errorHandler.handle(rynexError);
return null;
}
}
// Development mode flag
let isDevelopment = true;
export function setDevelopmentMode(enabled) {
isDevelopment = enabled;
}
export function isDevelopmentMode() {
return isDevelopment;
}
// Validation wrapper that only runs in development
export function devValidate(validationFn) {
if (isDevelopment) {
try {
validationFn();
}
catch (error) {
if (error instanceof RynexError) {
errorHandler.handle(error);
}
else {
throw error;
}
}
}
}
// Assert function for critical validations
export function assert(condition, message, context) {
if (!condition) {
throw new RynexError(message, ErrorCategory.VALIDATION, ErrorSeverity.CRITICAL, context);
}
}
//# sourceMappingURL=errors.js.map