@arela/uploader 0.2.12 → 0.3.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.
Files changed (38) hide show
  1. package/.env.template +66 -0
  2. package/.vscode/settings.json +1 -0
  3. package/README.md +134 -58
  4. package/SUPABASE_UPLOAD_FIX.md +157 -0
  5. package/package.json +3 -2
  6. package/scripts/cleanup-ds-store.js +109 -0
  7. package/scripts/cleanup-system-files.js +69 -0
  8. package/scripts/tests/phase-7-features.test.js +415 -0
  9. package/scripts/tests/signal-handling.test.js +275 -0
  10. package/scripts/tests/smart-watch-integration.test.js +554 -0
  11. package/scripts/tests/watch-service-integration.test.js +584 -0
  12. package/src/commands/UploadCommand.js +36 -2
  13. package/src/commands/WatchCommand.js +1305 -0
  14. package/src/config/config.js +113 -0
  15. package/src/document-type-shared.js +2 -0
  16. package/src/document-types/support-document.js +201 -0
  17. package/src/file-detection.js +2 -1
  18. package/src/index.js +44 -0
  19. package/src/services/AdvancedFilterService.js +505 -0
  20. package/src/services/AutoProcessingService.js +639 -0
  21. package/src/services/BenchmarkingService.js +381 -0
  22. package/src/services/DatabaseService.js +723 -170
  23. package/src/services/ErrorMonitor.js +275 -0
  24. package/src/services/LoggingService.js +419 -1
  25. package/src/services/MonitoringService.js +401 -0
  26. package/src/services/PerformanceOptimizer.js +511 -0
  27. package/src/services/ReportingService.js +511 -0
  28. package/src/services/SignalHandler.js +255 -0
  29. package/src/services/SmartWatchDatabaseService.js +527 -0
  30. package/src/services/WatchService.js +783 -0
  31. package/src/services/upload/ApiUploadService.js +30 -4
  32. package/src/services/upload/SupabaseUploadService.js +28 -6
  33. package/src/utils/CleanupManager.js +262 -0
  34. package/src/utils/FileOperations.js +41 -0
  35. package/src/utils/WatchEventHandler.js +517 -0
  36. package/supabase/migrations/001_create_initial_schema.sql +366 -0
  37. package/supabase/migrations/002_align_with_arela_api_schema.sql +145 -0
  38. package/commands.md +0 -6
@@ -0,0 +1,401 @@
1
+ /**
2
+ * MonitoringService.js
3
+ * Phase 7 - Task 4: Advanced Monitoring & Alerting
4
+ *
5
+ * Provides advanced monitoring and alerting including:
6
+ * - Real-time alerts
7
+ * - Threshold-based notifications
8
+ * - Health monitoring
9
+ * - Alert history and escalation
10
+ * - Custom alert rules
11
+ */
12
+
13
+ class MonitoringService {
14
+ constructor(logger) {
15
+ this.logger = logger;
16
+ this.alerts = [];
17
+ this.alertRules = [];
18
+ this.subscribers = [];
19
+
20
+ this.metrics = {
21
+ cpuUsage: 0,
22
+ memoryUsage: 0,
23
+ diskUsage: 0,
24
+ activeConnections: 0,
25
+ errorRate: 0,
26
+ };
27
+
28
+ this.thresholds = {
29
+ memoryWarning: 75, // 75% heap used
30
+ memoryCritical: 90, // 90% heap used
31
+ errorRateWarning: 5, // 5% error rate
32
+ errorRateCritical: 10, // 10% error rate
33
+ responseTimeWarning: 5000, // 5 seconds
34
+ responseTimeCritical: 10000, // 10 seconds
35
+ };
36
+
37
+ this._initializeDefaultRules();
38
+ }
39
+
40
+ /**
41
+ * Initialize default alert rules
42
+ */
43
+ _initializeDefaultRules() {
44
+ this.addAlertRule({
45
+ name: 'High Memory Usage',
46
+ metric: 'memoryUsage',
47
+ operator: 'greaterThan',
48
+ threshold: this.thresholds.memoryWarning,
49
+ severity: 'warning',
50
+ enabled: true,
51
+ });
52
+
53
+ this.addAlertRule({
54
+ name: 'Critical Memory Usage',
55
+ metric: 'memoryUsage',
56
+ operator: 'greaterThan',
57
+ threshold: this.thresholds.memoryCritical,
58
+ severity: 'critical',
59
+ enabled: true,
60
+ });
61
+
62
+ this.addAlertRule({
63
+ name: 'High Error Rate',
64
+ metric: 'errorRate',
65
+ operator: 'greaterThan',
66
+ threshold: this.thresholds.errorRateWarning,
67
+ severity: 'warning',
68
+ enabled: true,
69
+ });
70
+
71
+ this.addAlertRule({
72
+ name: 'Critical Error Rate',
73
+ metric: 'errorRate',
74
+ operator: 'greaterThan',
75
+ threshold: this.thresholds.errorRateCritical,
76
+ severity: 'critical',
77
+ enabled: true,
78
+ });
79
+ }
80
+
81
+ /**
82
+ * Add alert rule
83
+ */
84
+ addAlertRule(rule) {
85
+ const fullRule = {
86
+ id: `rule_${Date.now()}_${Math.random().toString(36).substr(2, 9)}`,
87
+ ...rule,
88
+ createdAt: new Date().toISOString(),
89
+ triggeredCount: 0,
90
+ lastTriggered: null,
91
+ };
92
+
93
+ this.alertRules.push(fullRule);
94
+ return fullRule;
95
+ }
96
+
97
+ /**
98
+ * Subscribe to alerts
99
+ */
100
+ subscribe(severity, callback) {
101
+ const subscription = {
102
+ id: `sub_${Date.now()}_${Math.random().toString(36).substr(2, 9)}`,
103
+ severity,
104
+ callback,
105
+ subscribed: true,
106
+ };
107
+
108
+ this.subscribers.push(subscription);
109
+ return subscription.id;
110
+ }
111
+
112
+ /**
113
+ * Unsubscribe from alerts
114
+ */
115
+ unsubscribe(subscriptionId) {
116
+ const index = this.subscribers.findIndex((s) => s.id === subscriptionId);
117
+ if (index > -1) {
118
+ this.subscribers.splice(index, 1);
119
+ return true;
120
+ }
121
+ return false;
122
+ }
123
+
124
+ /**
125
+ * Update metrics
126
+ */
127
+ updateMetrics(newMetrics) {
128
+ const previousMetrics = { ...this.metrics };
129
+ this.metrics = { ...this.metrics, ...newMetrics };
130
+
131
+ // Check rules
132
+ this._checkAlertRules();
133
+
134
+ return {
135
+ previous: previousMetrics,
136
+ current: this.metrics,
137
+ changed: Object.keys(newMetrics),
138
+ };
139
+ }
140
+
141
+ /**
142
+ * Check all alert rules
143
+ */
144
+ _checkAlertRules() {
145
+ for (const rule of this.alertRules) {
146
+ if (!rule.enabled) continue;
147
+
148
+ const metricValue = this.metrics[rule.metric];
149
+ let triggered = false;
150
+
151
+ switch (rule.operator) {
152
+ case 'greaterThan':
153
+ triggered = metricValue > rule.threshold;
154
+ break;
155
+ case 'lessThan':
156
+ triggered = metricValue < rule.threshold;
157
+ break;
158
+ case 'equals':
159
+ triggered = metricValue === rule.threshold;
160
+ break;
161
+ case 'between':
162
+ triggered =
163
+ metricValue >= rule.threshold[0] &&
164
+ metricValue <= rule.threshold[1];
165
+ break;
166
+ }
167
+
168
+ if (triggered) {
169
+ this._triggerAlert(rule, metricValue);
170
+ }
171
+ }
172
+ }
173
+
174
+ /**
175
+ * Trigger an alert
176
+ */
177
+ _triggerAlert(rule, value) {
178
+ const alert = {
179
+ id: `alert_${Date.now()}_${Math.random().toString(36).substr(2, 9)}`,
180
+ ruleId: rule.id,
181
+ ruleName: rule.name,
182
+ severity: rule.severity,
183
+ metric: rule.metric,
184
+ value,
185
+ threshold: rule.threshold,
186
+ timestamp: new Date().toISOString(),
187
+ message: `${rule.name}: ${rule.metric} = ${value} (threshold: ${rule.threshold})`,
188
+ acknowledged: false,
189
+ };
190
+
191
+ this.alerts.push(alert);
192
+ rule.triggeredCount++;
193
+ rule.lastTriggered = new Date().toISOString();
194
+
195
+ // Notify subscribers
196
+ this._notifySubscribers(alert);
197
+
198
+ this.logger.warn(`Alert triggered: ${alert.message}`);
199
+
200
+ return alert;
201
+ }
202
+
203
+ /**
204
+ * Notify subscribers
205
+ */
206
+ _notifySubscribers(alert) {
207
+ const severityOrder = { info: 0, warning: 1, critical: 2 };
208
+ const alertSeverityLevel = severityOrder[alert.severity] || 0;
209
+
210
+ for (const subscriber of this.subscribers) {
211
+ const subscriberSeverityLevel = severityOrder[subscriber.severity] || 0;
212
+
213
+ if (alertSeverityLevel >= subscriberSeverityLevel) {
214
+ try {
215
+ subscriber.callback(alert);
216
+ } catch (error) {
217
+ this.logger.error(`Subscriber callback error:`, error);
218
+ }
219
+ }
220
+ }
221
+ }
222
+
223
+ /**
224
+ * Acknowledge alert
225
+ */
226
+ acknowledgeAlert(alertId) {
227
+ const alert = this.alerts.find((a) => a.id === alertId);
228
+ if (alert) {
229
+ alert.acknowledged = true;
230
+ alert.acknowledgedAt = new Date().toISOString();
231
+ return true;
232
+ }
233
+ return false;
234
+ }
235
+
236
+ /**
237
+ * Get active alerts
238
+ */
239
+ getActiveAlerts(severity = null) {
240
+ let alerts = this.alerts.filter((a) => !a.acknowledged);
241
+
242
+ if (severity) {
243
+ alerts = alerts.filter((a) => a.severity === severity);
244
+ }
245
+
246
+ return alerts;
247
+ }
248
+
249
+ /**
250
+ * Get alert history
251
+ */
252
+ getAlertHistory(limit = 50) {
253
+ return this.alerts.slice(-limit).reverse();
254
+ }
255
+
256
+ /**
257
+ * Get health status
258
+ */
259
+ getHealthStatus() {
260
+ const activeAlerts = this.getActiveAlerts();
261
+ const criticalAlerts = activeAlerts.filter(
262
+ (a) => a.severity === 'critical',
263
+ );
264
+
265
+ let healthStatus = 'healthy';
266
+ if (criticalAlerts.length > 0) {
267
+ healthStatus = 'critical';
268
+ } else if (activeAlerts.length > 0) {
269
+ healthStatus = 'warning';
270
+ }
271
+
272
+ return {
273
+ status: healthStatus,
274
+ timestamp: new Date().toISOString(),
275
+ metrics: this.metrics,
276
+ activeAlerts: activeAlerts.length,
277
+ criticalAlerts: criticalAlerts.length,
278
+ totalAlerts: this.alerts.length,
279
+ uptime: process.uptime() + 's',
280
+ };
281
+ }
282
+
283
+ /**
284
+ * Generate health report
285
+ */
286
+ generateHealthReport() {
287
+ const health = this.getHealthStatus();
288
+ const recentAlerts = this.getAlertHistory(10);
289
+ const ruleStats = this.alertRules.map((rule) => ({
290
+ name: rule.name,
291
+ severity: rule.severity,
292
+ triggeredCount: rule.triggeredCount,
293
+ lastTriggered: rule.lastTriggered,
294
+ }));
295
+
296
+ return {
297
+ timestamp: new Date().toISOString(),
298
+ health,
299
+ recentAlerts,
300
+ ruleStatistics: ruleStats,
301
+ alertsSummary: {
302
+ total: this.alerts.length,
303
+ active: health.activeAlerts,
304
+ critical: health.criticalAlerts,
305
+ acknowledged: this.alerts.filter((a) => a.acknowledged).length,
306
+ },
307
+ };
308
+ }
309
+
310
+ /**
311
+ * Set metric threshold
312
+ */
313
+ setThreshold(metric, level, value) {
314
+ const thresholdKey = `${metric}${level.charAt(0).toUpperCase() + level.slice(1)}`;
315
+ if (thresholdKey in this.thresholds) {
316
+ this.thresholds[thresholdKey] = value;
317
+ return true;
318
+ }
319
+ return false;
320
+ }
321
+
322
+ /**
323
+ * Get alert rules
324
+ */
325
+ getAlertRules(enabled = null) {
326
+ let rules = this.alertRules;
327
+
328
+ if (enabled !== null) {
329
+ rules = rules.filter((r) => r.enabled === enabled);
330
+ }
331
+
332
+ return rules;
333
+ }
334
+
335
+ /**
336
+ * Enable/disable alert rule
337
+ */
338
+ toggleAlertRule(ruleId, enabled) {
339
+ const rule = this.alertRules.find((r) => r.id === ruleId);
340
+ if (rule) {
341
+ rule.enabled = enabled;
342
+ return true;
343
+ }
344
+ return false;
345
+ }
346
+
347
+ /**
348
+ * Clear old alerts
349
+ */
350
+ clearOldAlerts(daysOld = 30) {
351
+ const cutoffTime = Date.now() - daysOld * 24 * 60 * 60 * 1000;
352
+ const beforeCount = this.alerts.length;
353
+
354
+ this.alerts = this.alerts.filter(
355
+ (alert) => new Date(alert.timestamp).getTime() > cutoffTime,
356
+ );
357
+
358
+ const removed = beforeCount - this.alerts.length;
359
+ this.logger.info(`Cleared ${removed} alerts older than ${daysOld} days`);
360
+
361
+ return { removed, remaining: this.alerts.length };
362
+ }
363
+
364
+ /**
365
+ * Simulate monitoring scenario for testing
366
+ */
367
+ async simulateMonitoring(duration = 5000, intervals = 10) {
368
+ const intervalMs = duration / intervals;
369
+ const results = [];
370
+
371
+ for (let i = 0; i < intervals; i++) {
372
+ await new Promise((resolve) => setTimeout(resolve, intervalMs));
373
+
374
+ // Simulate changing metrics
375
+ const memoryUsage = 50 + Math.random() * 50;
376
+ const errorRate = Math.random() * 12;
377
+ const activeConnections = Math.floor(Math.random() * 100);
378
+
379
+ const update = this.updateMetrics({
380
+ memoryUsage: Math.round(memoryUsage),
381
+ errorRate: Math.round(errorRate * 10) / 10,
382
+ activeConnections,
383
+ });
384
+
385
+ results.push({
386
+ timestamp: new Date().toISOString(),
387
+ metrics: update.current,
388
+ alerts: this.getActiveAlerts(),
389
+ });
390
+ }
391
+
392
+ return {
393
+ duration,
394
+ intervals,
395
+ results,
396
+ finalHealth: this.getHealthStatus(),
397
+ };
398
+ }
399
+ }
400
+
401
+ export default MonitoringService;