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.
- package/CHANGELOG.md +466 -0
- package/README.md +198 -0
- package/package.json +91 -0
- package/src/components/base-component.js +925 -0
- package/src/core/component-manager.js +306 -0
- package/src/core/dom-morph.js +234 -0
- package/src/core/event-bus.js +229 -0
- package/src/core/router.js +487 -0
- package/src/core/signal.js +114 -0
- package/src/framework.js +323 -0
- package/src/plugins/alerts/alerts-plugin.js +427 -0
- package/src/plugins/fonts/files/inter.js +4 -0
- package/src/plugins/fonts/files/jetbrains-mono.js +4 -0
- package/src/plugins/fonts/font-manifests.js +53 -0
- package/src/plugins/fonts/fonts-plugin.js +246 -0
- package/src/plugins/icons/default-icons.js +51 -0
- package/src/plugins/icons/icons-plugin.js +130 -0
- package/src/plugins/store/store-plugin.js +127 -0
- package/src/plugins/theme/base-styles.js +58 -0
- package/src/plugins/theme/theme-plugin.js +160 -0
- package/src/utils/decorators.js +51 -0
- package/src/utils/dom.js +40 -0
- package/src/utils/error-handler.js +442 -0
- package/src/utils/framework-debug.js +375 -0
- package/src/utils/logger.js +324 -0
- package/src/utils/notification.js +123 -0
- package/src/utils/performance.js +281 -0
- package/src/utils/storage.js +86 -0
- package/src/utils/sweet-alert.js +84 -0
- package/src/utils/validation.js +70 -0
- package/src/utils/validators.js +129 -0
- package/types/index.d.ts +524 -0
|
@@ -0,0 +1,442 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Error Handler Utility
|
|
3
|
+
*
|
|
4
|
+
* Centralized error handling system for the VanillaForge.
|
|
5
|
+
* Provides consistent error processing, user notifications, and error reporting.
|
|
6
|
+
*
|
|
7
|
+
* @author VanillaForge Team
|
|
8
|
+
* @version 3.0.0
|
|
9
|
+
* @since 2025-06-14
|
|
10
|
+
*/
|
|
11
|
+
|
|
12
|
+
import { Logger } from './logger.js';
|
|
13
|
+
import { Notification } from './notification.js';
|
|
14
|
+
|
|
15
|
+
/**
|
|
16
|
+
* Error types enum
|
|
17
|
+
* @readonly
|
|
18
|
+
* @enum {string}
|
|
19
|
+
*/
|
|
20
|
+
export const ErrorType = {
|
|
21
|
+
VALIDATION: 'validation',
|
|
22
|
+
AUTHENTICATION: 'authentication',
|
|
23
|
+
AUTHORIZATION: 'authorization',
|
|
24
|
+
NETWORK: 'network',
|
|
25
|
+
DATABASE: 'database',
|
|
26
|
+
SYSTEM: 'system',
|
|
27
|
+
USER_INPUT: 'user_input',
|
|
28
|
+
CONFIGURATION: 'configuration'
|
|
29
|
+
};
|
|
30
|
+
|
|
31
|
+
/**
|
|
32
|
+
* Error severity levels
|
|
33
|
+
* @readonly
|
|
34
|
+
* @enum {string}
|
|
35
|
+
*/
|
|
36
|
+
export const ErrorSeverity = {
|
|
37
|
+
LOW: 'low',
|
|
38
|
+
MEDIUM: 'medium',
|
|
39
|
+
HIGH: 'high',
|
|
40
|
+
CRITICAL: 'critical'
|
|
41
|
+
};
|
|
42
|
+
|
|
43
|
+
/**
|
|
44
|
+
* Error Handler class for centralized error management
|
|
45
|
+
*
|
|
46
|
+
* Handles all application errors, provides user-friendly messages,
|
|
47
|
+
* and manages error reporting and recovery strategies.
|
|
48
|
+
*/
|
|
49
|
+
export class ErrorHandler { /**
|
|
50
|
+
* Initialize the error handler
|
|
51
|
+
*/
|
|
52
|
+
constructor(notification) {
|
|
53
|
+
this.logger = new Logger('ErrorHandler');
|
|
54
|
+
|
|
55
|
+
// Safely initialize notification system
|
|
56
|
+
if (notification) {
|
|
57
|
+
this.notification = notification;
|
|
58
|
+
} else if (typeof window !== 'undefined' && typeof document !== 'undefined') {
|
|
59
|
+
// Only create Notification in browser environment
|
|
60
|
+
this.notification = new Notification();
|
|
61
|
+
} else {
|
|
62
|
+
// Fallback for non-browser environments
|
|
63
|
+
this.notification = {
|
|
64
|
+
showToast: (message, type) => console.log(`[${type.toUpperCase()}] ${message}`),
|
|
65
|
+
showModal: (title, message, _options) => console.log(`[MODAL] ${title}: ${message}`)
|
|
66
|
+
};
|
|
67
|
+
}
|
|
68
|
+
|
|
69
|
+
this.errorCounts = new Map();
|
|
70
|
+
this.lastErrors = [];
|
|
71
|
+
this.maxLastErrors = 10;
|
|
72
|
+
this.logger.info('Error handler initialized');
|
|
73
|
+
}
|
|
74
|
+
/**
|
|
75
|
+
* Handle application errors
|
|
76
|
+
*
|
|
77
|
+
* @param {Error} error - The error object
|
|
78
|
+
* @param {Object} [context={}] - Additional context about the error
|
|
79
|
+
* @param {boolean} [showToUser=true] - Whether to show error to user
|
|
80
|
+
* @returns {Object} Processed error information
|
|
81
|
+
*/
|
|
82
|
+
handleError(error, context = {}, showToUser = true) {
|
|
83
|
+
try {
|
|
84
|
+
const errorInfo = this.createErrorInfo(error, context);
|
|
85
|
+
|
|
86
|
+
this.logError(errorInfo);
|
|
87
|
+
this.trackError(errorInfo);
|
|
88
|
+
|
|
89
|
+
if (showToUser) {
|
|
90
|
+
this.showUserNotification(errorInfo);
|
|
91
|
+
}
|
|
92
|
+
|
|
93
|
+
this.reportError(errorInfo);
|
|
94
|
+
|
|
95
|
+
return errorInfo;
|
|
96
|
+
} catch (handlingError) {
|
|
97
|
+
this.logger.error('Error in error handler:', { error: handlingError, originalError: error });
|
|
98
|
+
this.notification.showToast('An unexpected error occurred.', 'error');
|
|
99
|
+
return this.createFallbackErrorInfo(error);
|
|
100
|
+
}
|
|
101
|
+
}
|
|
102
|
+
|
|
103
|
+
/**
|
|
104
|
+
* Create fallback error info when error handler fails
|
|
105
|
+
*
|
|
106
|
+
* @private
|
|
107
|
+
* @param {Error} error - Original error
|
|
108
|
+
* @returns {Object} Fallback error information
|
|
109
|
+
*/
|
|
110
|
+
createFallbackErrorInfo(error) {
|
|
111
|
+
return {
|
|
112
|
+
id: this.generateErrorId(),
|
|
113
|
+
timestamp: new Date().toISOString(),
|
|
114
|
+
type: ErrorType.SYSTEM,
|
|
115
|
+
severity: ErrorSeverity.HIGH,
|
|
116
|
+
message: error?.message || 'Unknown error occurred',
|
|
117
|
+
handled: false,
|
|
118
|
+
fallback: true
|
|
119
|
+
};
|
|
120
|
+
}
|
|
121
|
+
|
|
122
|
+
/**
|
|
123
|
+
* Handle global errors (uncaught exceptions)
|
|
124
|
+
*
|
|
125
|
+
* @param {Error} error - The error object
|
|
126
|
+
* @param {Object} [context={}] - Additional context
|
|
127
|
+
*/
|
|
128
|
+
handleGlobalError(error, context = {}) {
|
|
129
|
+
const errorInfo = this.handleError(error, {
|
|
130
|
+
...context,
|
|
131
|
+
global: true,
|
|
132
|
+
severity: ErrorSeverity.HIGH
|
|
133
|
+
}, true);
|
|
134
|
+
|
|
135
|
+
this.logger.error('Global error caught', errorInfo);
|
|
136
|
+
}
|
|
137
|
+
|
|
138
|
+
/**
|
|
139
|
+
* Create structured error information
|
|
140
|
+
*
|
|
141
|
+
* @private
|
|
142
|
+
* @param {Error} error - The error object
|
|
143
|
+
* @param {Object} context - Additional context
|
|
144
|
+
* @returns {Object} Structured error information
|
|
145
|
+
*/
|
|
146
|
+
createErrorInfo(error, context) {
|
|
147
|
+
const errorId = this.generateErrorId();
|
|
148
|
+
const timestamp = new Date().toISOString();
|
|
149
|
+
|
|
150
|
+
// Determine error type
|
|
151
|
+
const type = this.determineErrorType(error, context);
|
|
152
|
+
|
|
153
|
+
// Determine severity
|
|
154
|
+
const severity = this.determineSeverity(error, context, type);
|
|
155
|
+
|
|
156
|
+
return {
|
|
157
|
+
id: errorId,
|
|
158
|
+
timestamp,
|
|
159
|
+
type,
|
|
160
|
+
severity,
|
|
161
|
+
message: error?.message || 'Unknown error',
|
|
162
|
+
stack: error?.stack,
|
|
163
|
+
name: error?.name,
|
|
164
|
+
code: error?.code,
|
|
165
|
+
context: {
|
|
166
|
+
...context,
|
|
167
|
+
url: window.location.href,
|
|
168
|
+
userAgent: navigator.userAgent,
|
|
169
|
+
timestamp: timestamp
|
|
170
|
+
},
|
|
171
|
+
user: this.getCurrentUserContext(),
|
|
172
|
+
handled: true
|
|
173
|
+
};
|
|
174
|
+
}
|
|
175
|
+
|
|
176
|
+
/**
|
|
177
|
+
* Determine error type based on error object and context
|
|
178
|
+
*
|
|
179
|
+
* @private
|
|
180
|
+
* @param {Error} error - The error object
|
|
181
|
+
* @param {Object} context - Error context
|
|
182
|
+
* @returns {string} Error type
|
|
183
|
+
*/
|
|
184
|
+
determineErrorType(error, context) {
|
|
185
|
+
// Check context type first
|
|
186
|
+
if (context.type) {
|
|
187
|
+
return context.type;
|
|
188
|
+
}
|
|
189
|
+
|
|
190
|
+
// Check error properties
|
|
191
|
+
if (error?.code) {
|
|
192
|
+
if (error.code.includes('auth/')) {
|
|
193
|
+
return ErrorType.AUTHENTICATION;
|
|
194
|
+
}
|
|
195
|
+
if (error.code.includes('permission')) {
|
|
196
|
+
return ErrorType.AUTHORIZATION;
|
|
197
|
+
}
|
|
198
|
+
if (error.code.includes('network') || error.code.includes('offline')) {
|
|
199
|
+
return ErrorType.NETWORK;
|
|
200
|
+
}
|
|
201
|
+
}
|
|
202
|
+
|
|
203
|
+
// Check error message
|
|
204
|
+
const message = error?.message?.toLowerCase() || '';
|
|
205
|
+
if (message.includes('validation') || message.includes('invalid')) {
|
|
206
|
+
return ErrorType.VALIDATION;
|
|
207
|
+
}
|
|
208
|
+
if (message.includes('network') || message.includes('fetch')) {
|
|
209
|
+
return ErrorType.NETWORK;
|
|
210
|
+
}
|
|
211
|
+
if (message.includes('permission') || message.includes('unauthorized')) {
|
|
212
|
+
return ErrorType.AUTHORIZATION;
|
|
213
|
+
}
|
|
214
|
+
|
|
215
|
+
return ErrorType.SYSTEM;
|
|
216
|
+
}
|
|
217
|
+
|
|
218
|
+
/**
|
|
219
|
+
* Determine error severity
|
|
220
|
+
*
|
|
221
|
+
* @private
|
|
222
|
+
* @param {Error} error - The error object
|
|
223
|
+
* @param {Object} context - Error context
|
|
224
|
+
* @param {string} type - Error type
|
|
225
|
+
* @returns {string} Error severity
|
|
226
|
+
*/
|
|
227
|
+
determineSeverity(error, context, type) {
|
|
228
|
+
// Check context severity first
|
|
229
|
+
if (context.severity) {
|
|
230
|
+
return context.severity;
|
|
231
|
+
}
|
|
232
|
+
|
|
233
|
+
// Global errors are high severity
|
|
234
|
+
if (context.global) {
|
|
235
|
+
return ErrorSeverity.HIGH;
|
|
236
|
+
}
|
|
237
|
+
|
|
238
|
+
// Type-based severity
|
|
239
|
+
switch (type) {
|
|
240
|
+
case ErrorType.SYSTEM:
|
|
241
|
+
case ErrorType.DATABASE:
|
|
242
|
+
return ErrorSeverity.HIGH;
|
|
243
|
+
case ErrorType.NETWORK:
|
|
244
|
+
case ErrorType.AUTHENTICATION:
|
|
245
|
+
return ErrorSeverity.MEDIUM;
|
|
246
|
+
case ErrorType.VALIDATION:
|
|
247
|
+
case ErrorType.USER_INPUT:
|
|
248
|
+
return ErrorSeverity.LOW;
|
|
249
|
+
default:
|
|
250
|
+
return ErrorSeverity.MEDIUM;
|
|
251
|
+
}
|
|
252
|
+
}
|
|
253
|
+
|
|
254
|
+
/**
|
|
255
|
+
* Log error information
|
|
256
|
+
*
|
|
257
|
+
* @private
|
|
258
|
+
* @param {Object} errorInfo - Structured error information
|
|
259
|
+
*/
|
|
260
|
+
logError(errorInfo) {
|
|
261
|
+
const logData = {
|
|
262
|
+
errorId: errorInfo.id,
|
|
263
|
+
type: errorInfo.type,
|
|
264
|
+
severity: errorInfo.severity,
|
|
265
|
+
message: errorInfo.message,
|
|
266
|
+
context: errorInfo.context
|
|
267
|
+
};
|
|
268
|
+
|
|
269
|
+
// Log based on severity
|
|
270
|
+
switch (errorInfo.severity) {
|
|
271
|
+
case ErrorSeverity.CRITICAL:
|
|
272
|
+
case ErrorSeverity.HIGH:
|
|
273
|
+
this.logger.error(`[${errorInfo.type.toUpperCase()}] ${errorInfo.message}`, logData);
|
|
274
|
+
break;
|
|
275
|
+
case ErrorSeverity.MEDIUM:
|
|
276
|
+
this.logger.warn(`[${errorInfo.type.toUpperCase()}] ${errorInfo.message}`, logData);
|
|
277
|
+
break;
|
|
278
|
+
case ErrorSeverity.LOW:
|
|
279
|
+
this.logger.info(`[${errorInfo.type.toUpperCase()}] ${errorInfo.message}`, logData);
|
|
280
|
+
break;
|
|
281
|
+
}
|
|
282
|
+
}
|
|
283
|
+
|
|
284
|
+
/**
|
|
285
|
+
* Track error occurrence for analytics
|
|
286
|
+
*
|
|
287
|
+
* @private
|
|
288
|
+
* @param {Object} errorInfo - Structured error information
|
|
289
|
+
*/
|
|
290
|
+
trackError(errorInfo) {
|
|
291
|
+
// Count error occurrences
|
|
292
|
+
const errorKey = `${errorInfo.type}:${errorInfo.message}`;
|
|
293
|
+
const count = this.errorCounts.get(errorKey) || 0;
|
|
294
|
+
this.errorCounts.set(errorKey, count + 1);
|
|
295
|
+
|
|
296
|
+
// Store recent errors
|
|
297
|
+
this.lastErrors.unshift(errorInfo);
|
|
298
|
+
if (this.lastErrors.length > this.maxLastErrors) {
|
|
299
|
+
this.lastErrors = this.lastErrors.slice(0, this.maxLastErrors);
|
|
300
|
+
}
|
|
301
|
+
}
|
|
302
|
+
|
|
303
|
+
showUserNotification(errorInfo) {
|
|
304
|
+
const message = this.getUserFriendlyMessage(errorInfo);
|
|
305
|
+
|
|
306
|
+
switch (errorInfo.severity) {
|
|
307
|
+
case ErrorSeverity.CRITICAL:
|
|
308
|
+
case ErrorSeverity.HIGH:
|
|
309
|
+
this.notification.showModal('Error', message, {
|
|
310
|
+
details: JSON.stringify(errorInfo, null, 2),
|
|
311
|
+
buttons: [
|
|
312
|
+
{ label: 'Report Issue', action: 'report', onClick: () => this.reportIssue(errorInfo) },
|
|
313
|
+
{ label: 'Close', action: 'close' }
|
|
314
|
+
]
|
|
315
|
+
});
|
|
316
|
+
break;
|
|
317
|
+
case ErrorSeverity.MEDIUM:
|
|
318
|
+
this.notification.showToast(message, 'error');
|
|
319
|
+
break;
|
|
320
|
+
case ErrorSeverity.LOW:
|
|
321
|
+
this.notification.showToast(message, 'warning');
|
|
322
|
+
break;
|
|
323
|
+
}
|
|
324
|
+
}
|
|
325
|
+
|
|
326
|
+
/**
|
|
327
|
+
* Get user-friendly error message
|
|
328
|
+
*
|
|
329
|
+
* @private
|
|
330
|
+
* @param {Object} errorInfo - Structured error information
|
|
331
|
+
* @returns {string} User-friendly message
|
|
332
|
+
*/
|
|
333
|
+
getUserFriendlyMessage(errorInfo) {
|
|
334
|
+
const messages = {
|
|
335
|
+
[ErrorType.VALIDATION]: `Please check the ${errorInfo.context.field || 'input'} field and try again.`,
|
|
336
|
+
[ErrorType.AUTHENTICATION]: 'Please sign in to continue.',
|
|
337
|
+
[ErrorType.AUTHORIZATION]: "You don't have permission to perform this action.",
|
|
338
|
+
[ErrorType.NETWORK]: 'Please check your internet connection and try again.',
|
|
339
|
+
[ErrorType.DATABASE]: "We're experiencing technical difficulties. Please try again later.",
|
|
340
|
+
[ErrorType.SYSTEM]: 'Something went wrong. Please try again later.',
|
|
341
|
+
[ErrorType.USER_INPUT]: 'Invalid input provided. Please correct and try again.',
|
|
342
|
+
[ErrorType.CONFIGURATION]: 'Configuration error. Please contact support.'
|
|
343
|
+
};
|
|
344
|
+
|
|
345
|
+
return messages[errorInfo.type] || messages[ErrorType.SYSTEM];
|
|
346
|
+
}
|
|
347
|
+
|
|
348
|
+
|
|
349
|
+
/**
|
|
350
|
+
* Report error to external services
|
|
351
|
+
*
|
|
352
|
+
* @private
|
|
353
|
+
* @param {Object} errorInfo - Error information
|
|
354
|
+
*/
|
|
355
|
+
async reportError(errorInfo) {
|
|
356
|
+
if (typeof localStorage === 'undefined') return;
|
|
357
|
+
try {
|
|
358
|
+
const reports = JSON.parse(localStorage.getItem('ucm_error_reports') || '[]');
|
|
359
|
+
reports.push(errorInfo);
|
|
360
|
+
if (reports.length > 50) {
|
|
361
|
+
reports.shift();
|
|
362
|
+
}
|
|
363
|
+
localStorage.setItem('ucm_error_reports', JSON.stringify(reports));
|
|
364
|
+
} catch (_e) {
|
|
365
|
+
// Non-critical — storage quota exceeded or private mode.
|
|
366
|
+
}
|
|
367
|
+
}
|
|
368
|
+
|
|
369
|
+
/**
|
|
370
|
+
* Report issue via user action
|
|
371
|
+
*
|
|
372
|
+
* @private
|
|
373
|
+
* @param {Object} errorInfo - Error information
|
|
374
|
+
*/
|
|
375
|
+
reportIssue(errorInfo) {
|
|
376
|
+
// This would open a support ticket or email
|
|
377
|
+
const subject = `Error Report: ${errorInfo.type}`;
|
|
378
|
+
const body = `Error ID: ${errorInfo.id}\nTimestamp: ${errorInfo.timestamp}\nMessage: ${errorInfo.message}\n\nTechnical Details:\n${JSON.stringify(errorInfo, null, 2)}`;
|
|
379
|
+
|
|
380
|
+
const mailtoLink = `mailto:support@example.com?subject=${encodeURIComponent(subject)}&body=${encodeURIComponent(body)}`;
|
|
381
|
+
window.open(mailtoLink);
|
|
382
|
+
}
|
|
383
|
+
|
|
384
|
+
/**
|
|
385
|
+
* Get current user context for error reporting
|
|
386
|
+
*
|
|
387
|
+
* @private
|
|
388
|
+
* @returns {Object} User context
|
|
389
|
+
*/
|
|
390
|
+
getCurrentUserContext() {
|
|
391
|
+
try {
|
|
392
|
+
// This would get actual user information
|
|
393
|
+
return {
|
|
394
|
+
authenticated: false, // Would check actual auth state
|
|
395
|
+
timestamp: new Date().toISOString()
|
|
396
|
+
};
|
|
397
|
+
} catch (_e) {
|
|
398
|
+
return { error: 'Failed to get user context' };
|
|
399
|
+
}
|
|
400
|
+
}
|
|
401
|
+
|
|
402
|
+
/**
|
|
403
|
+
* Generate unique error ID
|
|
404
|
+
*
|
|
405
|
+
* @private
|
|
406
|
+
* @returns {string} Unique error ID
|
|
407
|
+
*/
|
|
408
|
+
generateErrorId() {
|
|
409
|
+
return `err_${Date.now()}_${Math.random().toString(36).substr(2, 9)}`;
|
|
410
|
+
}
|
|
411
|
+
|
|
412
|
+
/**
|
|
413
|
+
* Get error statistics
|
|
414
|
+
*
|
|
415
|
+
* @returns {Object} Error statistics
|
|
416
|
+
*/
|
|
417
|
+
getErrorStats() {
|
|
418
|
+
return {
|
|
419
|
+
totalErrors: this.lastErrors.length,
|
|
420
|
+
errorCounts: Object.fromEntries(this.errorCounts),
|
|
421
|
+
recentErrors: this.lastErrors.slice(0, 5).map(err => ({
|
|
422
|
+
id: err.id,
|
|
423
|
+
type: err.type,
|
|
424
|
+
severity: err.severity,
|
|
425
|
+
timestamp: err.timestamp,
|
|
426
|
+
message: err.message
|
|
427
|
+
}))
|
|
428
|
+
};
|
|
429
|
+
}
|
|
430
|
+
|
|
431
|
+
/**
|
|
432
|
+
* Clear error history
|
|
433
|
+
*/
|
|
434
|
+
clearErrorHistory() {
|
|
435
|
+
this.errorCounts.clear();
|
|
436
|
+
this.lastErrors = [];
|
|
437
|
+
if (typeof localStorage !== 'undefined') {
|
|
438
|
+
localStorage.removeItem('ucm_error_reports');
|
|
439
|
+
}
|
|
440
|
+
this.logger.info('Error history cleared');
|
|
441
|
+
}
|
|
442
|
+
}
|