rynex
Version:
A minimalist TypeScript framework for building reactive web applications with no virtual DOM
365 lines • 10.9 kB
JavaScript
/**
* Rynex Developer Tools
* Debugging, logging, and performance profiling utilities
*/
/**
* Logger levels
*/
export var LogLevel;
(function (LogLevel) {
LogLevel[LogLevel["DEBUG"] = 0] = "DEBUG";
LogLevel[LogLevel["INFO"] = 1] = "INFO";
LogLevel[LogLevel["WARN"] = 2] = "WARN";
LogLevel[LogLevel["ERROR"] = 3] = "ERROR";
LogLevel[LogLevel["NONE"] = 4] = "NONE";
})(LogLevel || (LogLevel = {}));
/**
* Logger class for structured logging
*/
class Logger {
constructor(config = {}) {
this.logs = [];
this.config = {
level: LogLevel.INFO,
prefix: '[Rynex]',
timestamp: true,
colors: true,
...config
};
}
shouldLog(level) {
return level >= this.config.level;
}
formatMessage(level, message, data) {
const parts = [];
if (this.config.prefix) {
parts.push(this.config.prefix);
}
if (this.config.timestamp) {
parts.push(`[${new Date().toISOString()}]`);
}
parts.push(`[${level}]`);
parts.push(message);
return parts.join(' ');
}
logToConsole(level, message, data, color) {
try {
const formatted = this.formatMessage(level, message, data);
if (this.config.colors && color) {
console.log(`%c${formatted}`, `color: ${color}`, data || '');
}
else {
console.log(formatted, data || '');
}
}
catch (error) {
console.error('Logger error:', error);
}
}
debug(message, data) {
if (!message)
return;
if (this.shouldLog(LogLevel.DEBUG)) {
this.logToConsole('DEBUG', message, data, '#888');
this.logs.push({ level: 'DEBUG', message, timestamp: Date.now(), data });
}
}
info(message, data) {
if (!message)
return;
if (this.shouldLog(LogLevel.INFO)) {
this.logToConsole('INFO', message, data, '#2196F3');
this.logs.push({ level: 'INFO', message, timestamp: Date.now(), data });
}
}
warn(message, data) {
if (!message)
return;
if (this.shouldLog(LogLevel.WARN)) {
this.logToConsole('WARN', message, data, '#FF9800');
this.logs.push({ level: 'WARN', message, timestamp: Date.now(), data });
}
}
error(message, data) {
if (!message)
return;
if (this.shouldLog(LogLevel.ERROR)) {
this.logToConsole('ERROR', message, data, '#F44336');
this.logs.push({ level: 'ERROR', message, timestamp: Date.now(), data });
}
}
getLogs() {
return [...this.logs];
}
clearLogs() {
this.logs = [];
}
setLevel(level) {
this.config.level = level;
}
}
/**
* Global logger instance
*/
let globalLogger = null;
/**
* Create or get logger instance
*/
export function logger(config) {
if (!globalLogger || config) {
globalLogger = new Logger(config);
}
return globalLogger;
}
class Profiler {
constructor() {
this.profiles = new Map();
this.completed = [];
}
start(name, metadata) {
if (!name || typeof name !== 'string') {
console.warn('Profile name must be a non-empty string');
return;
}
if (this.profiles.has(name)) {
console.warn(`Profile "${name}" is already running`);
return;
}
try {
const entry = {
name,
startTime: performance.now(),
metadata
};
this.profiles.set(name, entry);
if (globalLogger) {
globalLogger.debug(`Profile started: ${name}`, metadata);
}
}
catch (error) {
console.error('Error starting profile:', error);
}
}
end(name) {
if (!name || typeof name !== 'string') {
console.warn('Profile name must be a non-empty string');
return undefined;
}
const entry = this.profiles.get(name);
if (!entry) {
console.warn(`Profile "${name}" not found`);
return undefined;
}
try {
entry.endTime = performance.now();
entry.duration = entry.endTime - entry.startTime;
this.completed.push(entry);
this.profiles.delete(name);
if (globalLogger) {
globalLogger.debug(`Profile ended: ${name}`, {
duration: `${entry.duration.toFixed(2)}ms`,
...entry.metadata
});
}
return entry.duration;
}
catch (error) {
console.error('Error ending profile:', error);
return undefined;
}
}
measure(name, fn, metadata) {
if (!name || typeof name !== 'string') {
console.warn('Profile name must be a non-empty string');
return undefined;
}
if (typeof fn !== 'function') {
console.warn('Second argument must be a function');
return undefined;
}
this.start(name, metadata);
try {
const result = fn();
this.end(name);
return result;
}
catch (error) {
this.end(name);
console.error(`Error in measured function "${name}":`, error);
throw error;
}
}
async measureAsync(name, fn, metadata) {
if (!name || typeof name !== 'string') {
console.warn('Profile name must be a non-empty string');
return undefined;
}
if (typeof fn !== 'function') {
console.warn('Second argument must be a function');
return undefined;
}
this.start(name, metadata);
try {
const result = await fn();
this.end(name);
return result;
}
catch (error) {
this.end(name);
console.error(`Error in async measured function "${name}":`, error);
throw error;
}
}
getProfile(name) {
return this.completed.find(p => p.name === name);
}
getAllProfiles() {
return [...this.completed];
}
getAverageDuration(name) {
const profiles = this.completed.filter(p => p.name === name);
if (profiles.length === 0)
return 0;
const total = profiles.reduce((sum, p) => sum + (p.duration || 0), 0);
return total / profiles.length;
}
clear() {
this.profiles.clear();
this.completed = [];
}
report() {
const report = {
active: Array.from(this.profiles.values()),
completed: this.completed,
summary: this.getSummary()
};
console.table(report.completed);
return report;
}
getSummary() {
const names = new Set(this.completed.map(p => p.name));
const summary = {};
names.forEach(name => {
const profiles = this.completed.filter(p => p.name === name);
const durations = profiles.map(p => p.duration || 0);
summary[name] = {
count: profiles.length,
total: durations.reduce((a, b) => a + b, 0).toFixed(2) + 'ms',
average: (durations.reduce((a, b) => a + b, 0) / profiles.length).toFixed(2) + 'ms',
min: Math.min(...durations).toFixed(2) + 'ms',
max: Math.max(...durations).toFixed(2) + 'ms'
};
});
return summary;
}
}
/**
* Global profiler instance
*/
let globalProfiler = null;
/**
* Get profiler instance
*/
export function profiler() {
if (!globalProfiler) {
globalProfiler = new Profiler();
}
return globalProfiler;
}
class DevTools {
constructor(config = {}) {
this.config = {
enabled: true,
...config
};
this.logger = config.logger || logger();
this.profiler = config.profiler || profiler();
if (this.config.enabled) {
this.attachToWindow();
}
}
attachToWindow() {
if (typeof window !== 'undefined') {
window.__RYNEX_DEVTOOLS__ = {
logger: this.logger,
profiler: this.profiler,
version: '0.1.55',
inspect: this.inspect.bind(this),
getState: this.getState.bind(this)
};
this.logger.info('DevTools attached to window.__RYNEX_DEVTOOLS__');
}
}
inspect(element) {
if (!element || !(element instanceof HTMLElement)) {
this.logger.warn('Invalid element provided to inspect');
return null;
}
try {
const info = {
tagName: element.tagName,
id: element.id,
className: element.className,
attributes: Array.from(element.attributes).map(attr => ({
name: attr.name,
value: attr.value
})),
children: element.children.length,
dataset: { ...element.dataset }
};
console.log('Element Inspector:', info);
return info;
}
catch (error) {
this.logger.error('Error inspecting element:', error);
return null;
}
}
getState() {
// This would integrate with the state management system
// For now, return a placeholder
return {
message: 'State inspection not yet implemented'
};
}
enable() {
this.config.enabled = true;
this.attachToWindow();
}
disable() {
this.config.enabled = false;
if (typeof window !== 'undefined') {
delete window.__RYNEX_DEVTOOLS__;
}
}
}
/**
* Global devtools instance
*/
let globalDevTools = null;
/**
* Initialize or get devtools
*/
export function devtools(config) {
if (!globalDevTools || config) {
globalDevTools = new DevTools(config);
}
return globalDevTools;
}
/**
* Quick access functions
*/
export const log = {
debug: (msg, data) => logger().debug(msg, data),
info: (msg, data) => logger().info(msg, data),
warn: (msg, data) => logger().warn(msg, data),
error: (msg, data) => logger().error(msg, data)
};
export const profile = {
start: (name, metadata) => profiler().start(name, metadata),
end: (name) => profiler().end(name),
measure: (name, fn, metadata) => profiler().measure(name, fn, metadata),
measureAsync: (name, fn, metadata) => profiler().measureAsync(name, fn, metadata),
report: () => profiler().report()
};
//# sourceMappingURL=devtools.js.map