vanillaforge 1.9.0

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
@@ -0,0 +1,375 @@
1
+ /**
2
+ * Framework Debug Utility
3
+ *
4
+ * Debugging utilities for the VanillaForge framework.
5
+ * Provides insights into component state, event bus activity, and performance.
6
+ *
7
+ * @author VanillaForge Team
8
+ * @version 1.0.0
9
+ * @since 2025-06-15
10
+ */
11
+
12
+ import { Logger } from './logger.js';
13
+
14
+ /**
15
+ * Framework Debug class for development debugging
16
+ */
17
+ export class FrameworkDebug {
18
+ constructor(app) {
19
+ this.app = app;
20
+ this.logger = new Logger('FrameworkDebug');
21
+ this.isEnabled = false;
22
+ this.performanceMarks = new Map();
23
+
24
+ this.logger.info('Framework debug utility initialized');
25
+ }
26
+
27
+ /**
28
+ * Enable debug mode
29
+ */ enable() {
30
+ this.isEnabled = true;
31
+
32
+ // Enable event bus debug mode
33
+ if (this.app.eventBus) {
34
+ this.app.eventBus.setDebugMode(true);
35
+ }
36
+ // Add debug tools to window for console access
37
+ window.VanillaForgeDebug = this;
38
+
39
+ this.logger.info('Framework debug mode enabled');
40
+
41
+ // Only show console message in development
42
+ if (window.location.hostname === 'localhost' || window.location.hostname === '127.0.0.1') {
43
+ console.log('VanillaForge Debug mode enabled. Access via window.VanillaForgeDebug');
44
+ }
45
+ }
46
+
47
+ /**
48
+ * Disable debug mode
49
+ */
50
+ disable() {
51
+ this.isEnabled = false;
52
+
53
+ // Disable event bus debug mode
54
+ if (this.app.eventBus) {
55
+ this.app.eventBus.setDebugMode(false);
56
+ }
57
+ // Remove debug tools from window
58
+ delete window.VanillaForgeDebug;
59
+
60
+ this.logger.info('Framework debug mode disabled');
61
+ }
62
+
63
+ /**
64
+ * Get component manager statistics
65
+ */
66
+ getComponentStats() {
67
+ if (!this.app.componentManager) {
68
+ return { error: 'Component manager not available' };
69
+ }
70
+
71
+ const activeComponents = this.app.componentManager.getActiveComponents();
72
+ const registeredComponents = this.app.componentManager.getRegisteredComponents();
73
+
74
+ return {
75
+ registeredComponents: registeredComponents,
76
+ activeComponents: Array.from(activeComponents.keys()),
77
+ totalRegistered: registeredComponents.length,
78
+ totalActive: activeComponents.size,
79
+ components: Array.from(activeComponents.entries()).map(([id, instance]) => ({
80
+ id,
81
+ name: instance.name,
82
+ isInitialized: instance.isInitialized,
83
+ isRendered: instance.isRendered,
84
+ isDestroyed: instance.isDestroyed,
85
+ state: instance.state,
86
+ props: instance.props
87
+ }))
88
+ };
89
+ }
90
+
91
+ /**
92
+ * Get event bus statistics
93
+ */
94
+ getEventBusStats() {
95
+ if (!this.app.eventBus) {
96
+ return { error: 'Event bus not available' };
97
+ }
98
+
99
+ return {
100
+ ...this.app.eventBus.getStats(),
101
+ recentEvents: this.app.eventBus.getHistory(20)
102
+ };
103
+ }
104
+
105
+ /**
106
+ * Get router information
107
+ */
108
+ getRouterInfo() {
109
+ if (!this.app.router) {
110
+ return { error: 'Router not available' };
111
+ }
112
+
113
+ return {
114
+ currentRoute: this.app.router.getCurrentRoute(),
115
+ isNavigating: this.app.router.isNavigating,
116
+ targetPath: this.app.router.targetPath
117
+ };
118
+ }
119
+
120
+ /**
121
+ * Get error handler statistics
122
+ */
123
+ getErrorStats() {
124
+ if (!this.app.errorHandler) {
125
+ return { error: 'Error handler not available' };
126
+ }
127
+
128
+ return this.app.errorHandler.getErrorStats();
129
+ }
130
+
131
+ /**
132
+ * Get performance information
133
+ */
134
+ getPerformanceInfo() {
135
+ const navigation = performance.getEntriesByType('navigation')[0];
136
+ const marks = performance.getEntriesByType('mark');
137
+ const measures = performance.getEntriesByType('measure');
138
+
139
+ return {
140
+ navigation: navigation ? {
141
+ domContentLoaded: navigation.domContentLoadedEventEnd - navigation.domContentLoadedEventStart,
142
+ load: navigation.loadEventEnd - navigation.loadEventStart,
143
+ domComplete: navigation.domComplete - navigation.fetchStart,
144
+ totalLoadTime: navigation.loadEventEnd - navigation.fetchStart
145
+ } : null,
146
+ marks: marks.map(mark => ({
147
+ name: mark.name,
148
+ startTime: mark.startTime
149
+ })),
150
+ measures: measures.map(measure => ({
151
+ name: measure.name,
152
+ duration: measure.duration,
153
+ startTime: measure.startTime
154
+ })),
155
+ memory: performance.memory ? {
156
+ usedJSHeapSize: performance.memory.usedJSHeapSize,
157
+ totalJSHeapSize: performance.memory.totalJSHeapSize,
158
+ jsHeapSizeLimit: performance.memory.jsHeapSizeLimit
159
+ } : null
160
+ };
161
+ }
162
+
163
+ /**
164
+ * Mark performance milestone
165
+ */
166
+ markPerformance(name) {
167
+ try {
168
+ performance.mark(name);
169
+ this.performanceMarks.set(name, performance.now());
170
+ this.logger.debug(`Performance mark: ${name}`);
171
+ } catch (error) {
172
+ this.logger.warn(`Failed to mark performance: ${name}`, error);
173
+ }
174
+ }
175
+
176
+ /**
177
+ * Measure performance between two marks
178
+ */
179
+ measurePerformance(name, startMark, endMark) {
180
+ try {
181
+ performance.measure(name, startMark, endMark);
182
+ this.logger.debug(`Performance measure: ${name}`);
183
+ } catch (error) {
184
+ this.logger.warn(`Failed to measure performance: ${name}`, error);
185
+ }
186
+ }
187
+ /**
188
+ * Log detailed framework state
189
+ */
190
+ logFrameworkState() {
191
+ const stats = {
192
+ components: this.getComponentStats(),
193
+ eventBus: this.getEventBusStats(),
194
+ router: this.getRouterInfo(),
195
+ errors: this.getErrorStats(),
196
+ performance: this.getPerformanceInfo()
197
+ };
198
+
199
+ console.group('VanillaForge Framework State');
200
+ console.table(stats);
201
+ console.groupEnd();
202
+
203
+ return stats;
204
+ }
205
+ /**
206
+ * Simulate component stress test
207
+ */ async stressTestComponents(iterations = 10, componentName = null) {
208
+ if (!this.isEnabled) {
209
+ console.warn('Debug mode must be enabled for stress testing');
210
+ return;
211
+ }
212
+
213
+ // Auto-detect a registered component if none specified
214
+ if (!componentName) {
215
+ const registeredComponents = this.app.componentManager?.getRegisteredComponents() || [];
216
+ if (registeredComponents.length === 0) {
217
+ console.warn('No registered components found for stress testing');
218
+ return { error: 'No components registered' };
219
+ }
220
+ componentName = registeredComponents[0]; // Use first registered component
221
+ }
222
+
223
+ this.logger.info(`Starting component stress test (${iterations} iterations) on component: ${componentName}`);
224
+
225
+ const startTime = performance.now();
226
+ const results = {
227
+ iterations,
228
+ componentName,
229
+ successes: 0,
230
+ failures: 0,
231
+ errors: []
232
+ };
233
+
234
+ for (let i = 0; i < iterations; i++) {
235
+ try {
236
+ // Check if component is registered before trying to load it
237
+ if (!this.app.componentManager.components.has(componentName)) {
238
+ throw new Error(`Component '${componentName}' is not registered`);
239
+ }
240
+
241
+ // Simulate rapid component loading/unloading
242
+ await this.app.componentManager.loadComponent(componentName, {}, 'main-content');
243
+ await new Promise(resolve => setTimeout(resolve, 10)); // Small delay
244
+
245
+ results.successes++;
246
+ } catch (error) {
247
+ results.failures++;
248
+ results.errors.push(error.message);
249
+ }
250
+ }
251
+
252
+ const endTime = performance.now();
253
+ results.duration = endTime - startTime;
254
+ results.averageTime = results.duration / iterations;
255
+
256
+ console.log('Stress test results:', results);
257
+ return results;
258
+ }
259
+
260
+ /**
261
+ * Test event bus performance
262
+ */ testEventBusPerformance(eventCount = 1000) {
263
+ if (!this.isEnabled) {
264
+ console.warn('Debug mode must be enabled for performance testing');
265
+ return;
266
+ }
267
+
268
+ this.logger.info(`Testing event bus performance (${eventCount} events)`);
269
+
270
+ const startTime = performance.now();
271
+
272
+ // Create multiple listeners
273
+ const listeners = [];
274
+ for (let i = 0; i < 10; i++) {
275
+ const unsubscribe = this.app.eventBus.on(`test-event-${i}`, () => {});
276
+ listeners.push(unsubscribe);
277
+ }
278
+
279
+ // Emit events
280
+ for (let i = 0; i < eventCount; i++) {
281
+ this.app.eventBus.emit(`test-event-${i % 10}`, { data: i });
282
+ }
283
+
284
+ const endTime = performance.now();
285
+
286
+ // Clean up listeners
287
+ listeners.forEach(unsubscribe => unsubscribe());
288
+
289
+ const results = {
290
+ eventCount,
291
+ duration: endTime - startTime,
292
+ eventsPerSecond: eventCount / ((endTime - startTime) / 1000)
293
+ };
294
+
295
+ console.log('Event bus performance results:', results);
296
+ return results;
297
+ }
298
+
299
+ /**
300
+ * Monitor memory usage over time
301
+ */ startMemoryMonitoring(interval = 5000) {
302
+ if (!this.isEnabled) {
303
+ console.warn('Debug mode must be enabled for memory monitoring');
304
+ return;
305
+ }
306
+
307
+ if (this.memoryMonitor) {
308
+ clearInterval(this.memoryMonitor);
309
+ }
310
+
311
+ this.logger.info(`Starting memory monitoring (${interval}ms interval)`);
312
+
313
+ this.memoryMonitor = setInterval(() => {
314
+ const memory = this.getPerformanceInfo().memory;
315
+ if (memory) {
316
+ this.logger.debug('Memory usage:', {
317
+ used: Math.round(memory.usedJSHeapSize / 1024 / 1024) + ' MB',
318
+ total: Math.round(memory.totalJSHeapSize / 1024 / 1024) + ' MB',
319
+ limit: Math.round(memory.jsHeapSizeLimit / 1024 / 1024) + ' MB'
320
+ });
321
+ }
322
+ }, interval);
323
+
324
+ return this.memoryMonitor;
325
+ }
326
+
327
+ /**
328
+ * Stop memory monitoring
329
+ */
330
+ stopMemoryMonitoring() {
331
+ if (this.memoryMonitor) {
332
+ clearInterval(this.memoryMonitor);
333
+ this.memoryMonitor = null;
334
+ this.logger.info('Memory monitoring stopped');
335
+ }
336
+ }
337
+
338
+ /**
339
+ * Get framework health check
340
+ */ healthCheck() {
341
+ const health = {
342
+ timestamp: new Date().toISOString(),
343
+ framework: {
344
+ initialized: !!this.app.isInitialized,
345
+ version: '1.0.0'
346
+ },
347
+ components: {
348
+ managerInitialized: !!this.app.componentManager?.isInitialized,
349
+ activeCount: this.app.componentManager?.getActiveComponents().size || 0
350
+ },
351
+ eventBus: {
352
+ available: !!this.app.eventBus,
353
+ stats: this.app.eventBus ? this.app.eventBus.getStats() : null
354
+ },
355
+ router: {
356
+ initialized: !!this.app.router?.isInitialized,
357
+ currentRoute: this.app.router?.getCurrentRoute()?.name || null
358
+ },
359
+ memory: this.getPerformanceInfo().memory,
360
+ errors: this.getErrorStats()
361
+ };
362
+
363
+ // Only log to console in development mode
364
+ if (window.location.hostname === 'localhost' || window.location.hostname === '127.0.0.1') {
365
+ console.log('Framework Health Check:', health);
366
+ }
367
+
368
+ return health;
369
+ }
370
+ }
371
+
372
+ // Auto-expose in development
373
+ if (typeof window !== 'undefined' && window.location.hostname === 'localhost') {
374
+ window.VanillaForgeDebug = FrameworkDebug;
375
+ }
@@ -0,0 +1,324 @@
1
+ /**
2
+ * Logger Utility
3
+ *
4
+ * Provides structured logging functionality with different log levels,
5
+ * formatting, and optional remote logging capabilities.
6
+ *
7
+ * @author VanillaForge Team
8
+ * @version 3.0.0
9
+ * @since 2025-06-14
10
+ */
11
+
12
+ import { LocalStorageAdapter } from './storage.js';
13
+
14
+ /**
15
+ * Log levels enum
16
+ * @readonly
17
+ * @enum {string}
18
+ */
19
+ export const LogLevel = {
20
+ DEBUG: 'debug',
21
+ INFO: 'info',
22
+ WARN: 'warn',
23
+ ERROR: 'error'
24
+ };
25
+
26
+ /**
27
+ * Logger class for structured application logging
28
+ *
29
+ * Provides different log levels, formatting, and contextual information.
30
+ * In production, logs can be sent to external logging services.
31
+ */
32
+ export class Logger {
33
+ /**
34
+ * Create a new Logger instance
35
+ *
36
+ * @param {string} context - The context/module name for this logger
37
+ * @param {string} [level=LogLevel.INFO] - Minimum log level to output
38
+ */
39
+ constructor(context = 'App', level = LogLevel.INFO, storageAdapter) {
40
+ this.context = context;
41
+ this.level = level;
42
+ this.startTime = Date.now();
43
+ this.storage = storageAdapter || (typeof window !== 'undefined' ? new LocalStorageAdapter() : null);
44
+
45
+ this.levelPriorities = {
46
+ [LogLevel.DEBUG]: 0,
47
+ [LogLevel.INFO]: 1,
48
+ [LogLevel.WARN]: 2,
49
+ [LogLevel.ERROR]: 3
50
+ };
51
+
52
+ this.consoleMethods = {
53
+ [LogLevel.DEBUG]: 'debug',
54
+ [LogLevel.INFO]: 'log',
55
+ [LogLevel.WARN]: 'warn',
56
+ [LogLevel.ERROR]: 'error'
57
+ };
58
+
59
+ this.colors = {
60
+ [LogLevel.DEBUG]: '#6b7280',
61
+ [LogLevel.INFO]: '#2563eb',
62
+ [LogLevel.WARN]: '#d97706',
63
+ [LogLevel.ERROR]: '#dc2626'
64
+ };
65
+ }
66
+
67
+ /**
68
+ * Check if a log level should be output
69
+ *
70
+ * @private
71
+ * @param {string} level - Log level to check
72
+ * @returns {boolean} True if level should be logged
73
+ */
74
+ shouldLog(level) {
75
+ return this.levelPriorities[level] >= this.levelPriorities[this.level];
76
+ }
77
+
78
+ /**
79
+ * Format log message with timestamp and context
80
+ *
81
+ * @private
82
+ * @param {string} level - Log level
83
+ * @param {string} message - Log message
84
+ * @param {Object} [data] - Additional data to log
85
+ * @returns {Object} Formatted log entry
86
+ */
87
+ formatMessage(level, message, data = {}) {
88
+ const timestamp = new Date().toISOString();
89
+ const uptime = Date.now() - this.startTime;
90
+
91
+ return {
92
+ timestamp,
93
+ uptime: `${uptime}ms`,
94
+ level: level.toUpperCase(),
95
+ context: this.context,
96
+ message,
97
+ data: Object.keys(data).length > 0 ? data : undefined,
98
+ userAgent: navigator.userAgent,
99
+ url: window.location.href
100
+ };
101
+ }
102
+
103
+ /**
104
+ * Output log to console with styling
105
+ *
106
+ * @private
107
+ * @param {string} level - Log level
108
+ * @param {Object} logEntry - Formatted log entry
109
+ */
110
+ outputToConsole(level, logEntry) {
111
+ const consoleMethod = this.consoleMethods[level];
112
+ const color = this.colors[level];
113
+
114
+ // Create styled output for development
115
+ const prefix = `%c[${logEntry.level}] ${logEntry.context}`;
116
+ const style = `color: ${color}; font-weight: bold;`;
117
+
118
+ if (logEntry.data) {
119
+ console[consoleMethod](prefix, style, logEntry.message, logEntry.data); } else {
120
+ console[consoleMethod](prefix, style, logEntry.message);
121
+ }
122
+
123
+ // Only log full structured data in development mode
124
+ if (level === LogLevel.DEBUG && (window.location.hostname === 'localhost' || window.location.hostname === '127.0.0.1')) {
125
+ console.debug('Full log entry:', logEntry);
126
+ }
127
+ }
128
+
129
+ /**
130
+ * Send log to remote logging service (if configured)
131
+ *
132
+ * @private
133
+ * @param {Object} logEntry - Formatted log entry
134
+ */
135
+ async sendToRemoteLogging(logEntry) {
136
+ if (!this.storage || (logEntry.level !== 'WARN' && logEntry.level !== 'ERROR')) {
137
+ return;
138
+ }
139
+
140
+ try {
141
+ const logs = this.storage.getLogs('ucm_logs');
142
+ logs.push(logEntry);
143
+ this.storage.saveLogs('ucm_logs', logs);
144
+ } catch (error) {
145
+ console.error('Failed to send log to remote service:', error);
146
+ }
147
+ }
148
+
149
+ /**
150
+ * Core logging method
151
+ *
152
+ * @private
153
+ * @param {string} level - Log level
154
+ * @param {string} message - Log message
155
+ * @param {Object} [data] - Additional data to log
156
+ */
157
+ log(level, message, data = {}) {
158
+ if (!this.shouldLog(level)) {
159
+ return;
160
+ }
161
+
162
+ const logEntry = this.formatMessage(level, message, data);
163
+
164
+ // Output to console
165
+ this.outputToConsole(level, logEntry);
166
+
167
+ // Send to remote logging (async, don't wait)
168
+ this.sendToRemoteLogging(logEntry);
169
+ }
170
+
171
+ /**
172
+ * Log debug message
173
+ *
174
+ * @param {string} message - Debug message
175
+ * @param {Object} [data] - Additional debug data
176
+ */
177
+ debug(message, data = {}) {
178
+ this.log(LogLevel.DEBUG, message, data);
179
+ }
180
+
181
+ /**
182
+ * Log info message
183
+ *
184
+ * @param {string} message - Info message
185
+ * @param {Object} [data] - Additional info data
186
+ */
187
+ info(message, data = {}) {
188
+ this.log(LogLevel.INFO, message, data);
189
+ }
190
+
191
+ /**
192
+ * Log warning message
193
+ *
194
+ * @param {string} message - Warning message
195
+ * @param {Object} [data] - Additional warning data
196
+ */
197
+ warn(message, data = {}) {
198
+ this.log(LogLevel.WARN, message, data);
199
+ }
200
+
201
+ /**
202
+ * Log error message
203
+ *
204
+ * @param {string} message - Error message
205
+ * @param {Error|Object} [data] - Error object or additional error data
206
+ */
207
+ error(message, data = {}) {
208
+ // If data is an Error object, extract useful information
209
+ if (data instanceof Error) {
210
+ data = {
211
+ name: data.name,
212
+ message: data.message,
213
+ stack: data.stack,
214
+ cause: data.cause
215
+ };
216
+ }
217
+
218
+ this.log(LogLevel.ERROR, message, data);
219
+ }
220
+
221
+ /**
222
+ * Create a timer for performance measurement
223
+ *
224
+ * @param {string} label - Timer label
225
+ * @returns {Function} Function to call to end the timer
226
+ */
227
+ timer(label) {
228
+ const startTime = performance.now();
229
+ this.debug(`Timer started: ${label}`);
230
+
231
+ return () => {
232
+ const endTime = performance.now();
233
+ const duration = endTime - startTime;
234
+ this.info(`Timer completed: ${label}`, {
235
+ duration: `${duration.toFixed(2)}ms`
236
+ });
237
+ return duration;
238
+ };
239
+ }
240
+
241
+ /**
242
+ * Log method execution time
243
+ *
244
+ * @param {string} methodName - Name of the method being timed
245
+ * @param {Function} fn - Function to execute and time
246
+ * @param {...any} args - Arguments to pass to the function
247
+ * @returns {any} Result of the function execution
248
+ */
249
+ async timeMethod(methodName, fn, ...args) {
250
+ const endTimer = this.timer(methodName);
251
+
252
+ try {
253
+ const result = await fn(...args);
254
+ endTimer();
255
+ return result;
256
+ } catch (error) {
257
+ endTimer();
258
+ this.error(`Method ${methodName} failed`, error);
259
+ throw error;
260
+ }
261
+ }
262
+
263
+ /**
264
+ * Set the minimum log level
265
+ *
266
+ * @param {string} level - New minimum log level
267
+ */
268
+ setLevel(level) {
269
+ if (Object.prototype.hasOwnProperty.call(this.levelPriorities, level)) {
270
+ this.level = level;
271
+ this.info(`Log level changed to: ${level}`);
272
+ } else {
273
+ this.warn(`Invalid log level: ${level}`);
274
+ }
275
+ }
276
+
277
+ /**
278
+ * Create a child logger with additional context
279
+ *
280
+ * @param {string} childContext - Additional context for the child logger
281
+ * @returns {Logger} New logger instance
282
+ */
283
+ child(childContext) {
284
+ const fullContext = `${this.context}:${childContext}`;
285
+ return new Logger(fullContext, this.level, this.storage);
286
+ }
287
+
288
+ getLogs() {
289
+ return this.storage ? this.storage.getLogs('ucm_logs') : [];
290
+ }
291
+
292
+ clearLogs() {
293
+ if (this.storage) {
294
+ this.storage.removeItem('ucm_logs');
295
+ this.info('Logs cleared');
296
+ }
297
+ }
298
+
299
+ exportLogs() {
300
+ if (typeof window === 'undefined' || !this.storage) {
301
+ this.warn('Log export is only available in a browser environment with storage.');
302
+ return;
303
+ }
304
+
305
+ try {
306
+ const logs = this.getLogs();
307
+ const logData = JSON.stringify(logs, null, 2);
308
+ const blob = new Blob([logData], { type: 'application/json' });
309
+ const url = URL.createObjectURL(blob);
310
+
311
+ const a = document.createElement('a');
312
+ a.href = url;
313
+ a.download = `ucm-logs-${new Date().toISOString().split('T')[0]}.json`;
314
+ document.body.appendChild(a);
315
+ a.click();
316
+ document.body.removeChild(a);
317
+ URL.revokeObjectURL(url);
318
+
319
+ this.info('Logs exported successfully');
320
+ } catch (error) {
321
+ this.error('Failed to export logs', error);
322
+ }
323
+ }
324
+ }