@arela/uploader 0.2.13 → 1.0.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 (43) hide show
  1. package/.env.template +66 -0
  2. package/README.md +263 -62
  3. package/docs/API_ENDPOINTS_FOR_DETECTION.md +647 -0
  4. package/docs/QUICK_REFERENCE_API_DETECTION.md +264 -0
  5. package/docs/REFACTORING_SUMMARY_DETECT_PEDIMENTOS.md +200 -0
  6. package/package.json +3 -2
  7. package/scripts/cleanup-ds-store.js +109 -0
  8. package/scripts/cleanup-system-files.js +69 -0
  9. package/scripts/tests/phase-7-features.test.js +415 -0
  10. package/scripts/tests/signal-handling.test.js +275 -0
  11. package/scripts/tests/smart-watch-integration.test.js +554 -0
  12. package/scripts/tests/watch-service-integration.test.js +584 -0
  13. package/src/commands/UploadCommand.js +31 -4
  14. package/src/commands/WatchCommand.js +1342 -0
  15. package/src/config/config.js +270 -2
  16. package/src/document-type-shared.js +2 -0
  17. package/src/document-types/support-document.js +200 -0
  18. package/src/file-detection.js +9 -1
  19. package/src/index.js +163 -4
  20. package/src/services/AdvancedFilterService.js +505 -0
  21. package/src/services/AutoProcessingService.js +749 -0
  22. package/src/services/BenchmarkingService.js +381 -0
  23. package/src/services/DatabaseService.js +1019 -539
  24. package/src/services/ErrorMonitor.js +275 -0
  25. package/src/services/LoggingService.js +419 -1
  26. package/src/services/MonitoringService.js +401 -0
  27. package/src/services/PerformanceOptimizer.js +511 -0
  28. package/src/services/ReportingService.js +511 -0
  29. package/src/services/SignalHandler.js +255 -0
  30. package/src/services/SmartWatchDatabaseService.js +527 -0
  31. package/src/services/WatchService.js +783 -0
  32. package/src/services/upload/ApiUploadService.js +447 -3
  33. package/src/services/upload/MultiApiUploadService.js +233 -0
  34. package/src/services/upload/SupabaseUploadService.js +12 -5
  35. package/src/services/upload/UploadServiceFactory.js +24 -0
  36. package/src/utils/CleanupManager.js +262 -0
  37. package/src/utils/FileOperations.js +44 -0
  38. package/src/utils/WatchEventHandler.js +522 -0
  39. package/supabase/migrations/001_create_initial_schema.sql +366 -0
  40. package/supabase/migrations/002_align_with_arela_api_schema.sql +145 -0
  41. package/.envbackup +0 -37
  42. package/SUPABASE_UPLOAD_FIX.md +0 -157
  43. package/commands.md +0 -14
@@ -0,0 +1,511 @@
1
+ /**
2
+ * PerformanceOptimizer.js
3
+ * Phase 6 - Task 5: Performance Optimization
4
+ *
5
+ * Optimizes signal handling, cleanup operations, and resource management.
6
+ * Implements monitoring, profiling, and optimization strategies.
7
+ */
8
+
9
+ class PerformanceOptimizer {
10
+ constructor(logger, cleanupManager, errorMonitor) {
11
+ this.logger = logger;
12
+ this.cleanupManager = cleanupManager;
13
+ this.errorMonitor = errorMonitor;
14
+
15
+ // Performance metrics
16
+ this.metrics = {
17
+ signalResponseTimes: [],
18
+ cleanupTimes: [],
19
+ resourceCleanupTimes: {},
20
+ memoryUsage: [],
21
+ errorRate: 0,
22
+ };
23
+
24
+ // Performance thresholds (milliseconds)
25
+ this.thresholds = {
26
+ signalResponse: 100, // Signal should be handled within 100ms
27
+ totalCleanup: 5000, // Total cleanup should complete within 5 seconds
28
+ resourceCleanup: 2000, // Individual resource cleanup < 2 seconds
29
+ memoryIncrease: 50 * 1024 * 1024, // Max 50MB increase during cleanup
30
+ };
31
+
32
+ // Optimization settings
33
+ this.optimizations = {
34
+ parallelizeCleanup: false, // Can we run some cleanups in parallel?
35
+ cacheDependencies: true,
36
+ compressSessions: true,
37
+ pruneOldLogs: true,
38
+ };
39
+ }
40
+
41
+ /**
42
+ * Monitor signal response time
43
+ */
44
+ startSignalMonitoring() {
45
+ const signalStart = Date.now();
46
+
47
+ return () => {
48
+ const responseTime = Date.now() - signalStart;
49
+ this.metrics.signalResponseTimes.push(responseTime);
50
+
51
+ if (responseTime > this.thresholds.signalResponse) {
52
+ this.logger.warn(
53
+ `Slow signal response: ${responseTime}ms (threshold: ${this.thresholds.signalResponse}ms)`,
54
+ );
55
+ }
56
+
57
+ return responseTime;
58
+ };
59
+ }
60
+
61
+ /**
62
+ * Analyze cleanup performance
63
+ */
64
+ async analyzeCleanupPerformance(cleanupResult) {
65
+ const analysis = {
66
+ timestamp: new Date().toISOString(),
67
+ totalDuration: cleanupResult.totalDuration,
68
+ resourceCount: cleanupResult.results.length,
69
+ successCount: cleanupResult.successCount,
70
+ failureCount: cleanupResult.failureCount,
71
+ slowResources: [],
72
+ recommendations: [],
73
+ };
74
+
75
+ // Identify slow resources
76
+ for (const res of cleanupResult.results) {
77
+ if (res.duration > this.thresholds.resourceCleanup) {
78
+ analysis.slowResources.push({
79
+ name: res.name,
80
+ duration: res.duration,
81
+ threshold: this.thresholds.resourceCleanup,
82
+ overage: res.duration - this.thresholds.resourceCleanup,
83
+ });
84
+ }
85
+ }
86
+
87
+ // Check total cleanup time
88
+ if (cleanupResult.totalDuration > this.thresholds.totalCleanup) {
89
+ analysis.recommendations.push(
90
+ `Total cleanup took ${cleanupResult.totalDuration}ms (threshold: ${this.thresholds.totalCleanup}ms). ` +
91
+ `Consider optimizing resource cleanup functions.`,
92
+ );
93
+ }
94
+
95
+ // Analyze error correlation with performance
96
+ const stats = this.errorMonitor.getStatistics();
97
+ if (stats.totalErrors > 0) {
98
+ const errorRate =
99
+ (stats.totalErrors / (stats.totalErrors + stats.recoveredErrors)) * 100;
100
+ analysis.errorRate = errorRate.toFixed(2) + '%';
101
+
102
+ if (errorRate > 5) {
103
+ analysis.recommendations.push(
104
+ `High error rate (${errorRate.toFixed(2)}%). ` +
105
+ `Improve error handling in cleanup functions.`,
106
+ );
107
+ }
108
+ }
109
+
110
+ // Memory analysis
111
+ const memUsage = process.memoryUsage();
112
+ if (memUsage.heapUsed > this.thresholds.memoryIncrease) {
113
+ analysis.recommendations.push(
114
+ `High memory usage (${(memUsage.heapUsed / 1024 / 1024).toFixed(2)}MB). ` +
115
+ `Consider clearing error records or session data periodically.`,
116
+ );
117
+ }
118
+
119
+ this.metrics.cleanupTimes.push(cleanupResult.totalDuration);
120
+ return analysis;
121
+ }
122
+
123
+ /**
124
+ * Get performance report
125
+ */
126
+ getPerformanceReport() {
127
+ const report = {
128
+ timestamp: new Date().toISOString(),
129
+ summary: this._generateSummary(),
130
+ signalMetrics: this._analyzeSignalMetrics(),
131
+ cleanupMetrics: this._analyzeCleanupMetrics(),
132
+ memoryMetrics: this._analyzeMemoryMetrics(),
133
+ recommendations: this._getRecommendations(),
134
+ };
135
+
136
+ return report;
137
+ }
138
+
139
+ /**
140
+ * Optimize cleanup timeout based on historical data
141
+ */
142
+ optimizeCleanupTimeout() {
143
+ if (this.metrics.cleanupTimes.length === 0) {
144
+ return this.cleanupManager.cleanupTimeout;
145
+ }
146
+
147
+ // Calculate 95th percentile of cleanup times
148
+ const sorted = [...this.metrics.cleanupTimes].sort((a, b) => a - b);
149
+ const p95Index = Math.ceil(sorted.length * 0.95) - 1;
150
+ const p95Time = sorted[Math.max(0, p95Index)];
151
+
152
+ // Add 20% buffer for safety
153
+ const optimizedTimeout = Math.ceil(p95Time * 1.2);
154
+
155
+ // Don't increase timeout beyond maximum of 60 seconds
156
+ const finalTimeout = Math.min(optimizedTimeout, 60000);
157
+
158
+ if (finalTimeout !== this.cleanupManager.cleanupTimeout) {
159
+ this.logger.info(
160
+ `Optimizing cleanup timeout: ${this.cleanupManager.cleanupTimeout}ms → ${finalTimeout}ms ` +
161
+ `(based on 95th percentile: ${p95Time}ms)`,
162
+ );
163
+ this.cleanupManager.cleanupTimeout = finalTimeout;
164
+ }
165
+
166
+ return finalTimeout;
167
+ }
168
+
169
+ /**
170
+ * Monitor and detect resource leaks
171
+ */
172
+ detectResourceLeaks() {
173
+ const report = {
174
+ timestamp: new Date().toISOString(),
175
+ registeredResources: this.cleanupManager.getResourceCount(),
176
+ resourceNames: this.cleanupManager.getResourceNames(),
177
+ possibleLeaks: [],
178
+ memoryTrend: this._analyzeMemoryTrend(),
179
+ };
180
+
181
+ // Track memory growth
182
+ const currentMemory = process.memoryUsage().heapUsed;
183
+ this.metrics.memoryUsage.push(currentMemory);
184
+
185
+ // Detect memory leaks (increasing trend over time)
186
+ if (this.metrics.memoryUsage.length >= 5) {
187
+ const recentValues = this.metrics.memoryUsage.slice(-5);
188
+ const isIncreasing = recentValues.every(
189
+ (val, i) => i === 0 || val >= recentValues[i - 1],
190
+ );
191
+
192
+ if (isIncreasing) {
193
+ report.possibleLeaks.push({
194
+ type: 'memory_leak',
195
+ severity: 'warning',
196
+ message: 'Memory usage is continuously increasing',
197
+ recentValues: recentValues.map(
198
+ (v) => (v / 1024 / 1024).toFixed(2) + 'MB',
199
+ ),
200
+ recommendation: 'Check for resource leaks in cleanup functions',
201
+ });
202
+ }
203
+ }
204
+
205
+ // Check for unreleased resources
206
+ if (this.cleanupManager.getResourceCount() > 10) {
207
+ report.possibleLeaks.push({
208
+ type: 'resource_accumulation',
209
+ severity: 'info',
210
+ message: `${this.cleanupManager.getResourceCount()} resources are registered`,
211
+ recommendation:
212
+ 'Consider limiting resource registrations or clearing old resources',
213
+ });
214
+ }
215
+
216
+ return report;
217
+ }
218
+
219
+ /**
220
+ * Generate optimization plan
221
+ */
222
+ generateOptimizationPlan() {
223
+ const currentState = {
224
+ avgSignalResponseTime: this._calculateAverage(
225
+ this.metrics.signalResponseTimes,
226
+ ),
227
+ avgCleanupTime: this._calculateAverage(this.metrics.cleanupTimes),
228
+ totalErrors: this.errorMonitor.getStatistics().totalErrors,
229
+ resourceCount: this.cleanupManager.getResourceCount(),
230
+ };
231
+
232
+ const plan = {
233
+ timestamp: new Date().toISOString(),
234
+ currentState,
235
+ optimizationSteps: [],
236
+ };
237
+
238
+ // Recommend signal optimization if response is slow
239
+ if (currentState.avgSignalResponseTime > this.thresholds.signalResponse) {
240
+ plan.optimizationSteps.push({
241
+ priority: 'high',
242
+ area: 'signal_handling',
243
+ issue: `Average signal response time: ${currentState.avgSignalResponseTime.toFixed(0)}ms`,
244
+ recommendations: [
245
+ 'Reduce number of operations in signal handlers',
246
+ 'Defer non-critical cleanup to after signal handling',
247
+ 'Use async operations more efficiently',
248
+ ],
249
+ });
250
+ }
251
+
252
+ // Recommend cleanup optimization if too slow
253
+ if (currentState.avgCleanupTime > this.thresholds.totalCleanup) {
254
+ plan.optimizationSteps.push({
255
+ priority: 'high',
256
+ area: 'cleanup_performance',
257
+ issue: `Average cleanup time: ${currentState.avgCleanupTime.toFixed(0)}ms`,
258
+ recommendations: [
259
+ 'Profile individual resource cleanup times',
260
+ 'Consider parallel cleanup for independent resources',
261
+ 'Optimize database disconnect procedures',
262
+ ],
263
+ });
264
+ }
265
+
266
+ // Recommend error handling optimization if rate is high
267
+ const stats = this.errorMonitor.getStatistics();
268
+ if (currentState.totalErrors > 5) {
269
+ plan.optimizationSteps.push({
270
+ priority: 'medium',
271
+ area: 'error_handling',
272
+ issue: `Total errors recorded: ${currentState.totalErrors}`,
273
+ recommendations: [
274
+ 'Implement retry logic for transient errors',
275
+ 'Improve error context tracking',
276
+ 'Create error-specific recovery procedures',
277
+ ],
278
+ });
279
+ }
280
+
281
+ // Recommend memory optimization
282
+ const memUsage = process.memoryUsage();
283
+ if (memUsage.heapUsed > 100 * 1024 * 1024) {
284
+ plan.optimizationSteps.push({
285
+ priority: 'medium',
286
+ area: 'memory_management',
287
+ issue: `Heap memory: ${(memUsage.heapUsed / 1024 / 1024).toFixed(2)}MB`,
288
+ recommendations: [
289
+ 'Clear error records periodically',
290
+ 'Implement session data pruning',
291
+ 'Monitor error array growth',
292
+ ],
293
+ });
294
+ }
295
+
296
+ return plan;
297
+ }
298
+
299
+ /**
300
+ * Benchmark cleanup operations
301
+ */
302
+ async benchmarkCleanup(iterations = 3) {
303
+ const results = [];
304
+
305
+ for (let i = 0; i < iterations; i++) {
306
+ // Reset for clean benchmark
307
+ this.cleanupManager.reset();
308
+ this.errorMonitor.reset();
309
+
310
+ // Register test resources
311
+ this.cleanupManager.registerResource('TestResource1', {}, async () => {
312
+ return new Promise((resolve) => setTimeout(resolve, 100));
313
+ });
314
+
315
+ this.cleanupManager.registerResource('TestResource2', {}, async () => {
316
+ return new Promise((resolve) => setTimeout(resolve, 100));
317
+ });
318
+
319
+ // Measure cleanup
320
+ const start = Date.now();
321
+ const result = await this.cleanupManager.performCleanup('benchmark');
322
+ const duration = Date.now() - start;
323
+
324
+ results.push({
325
+ iteration: i + 1,
326
+ duration,
327
+ success: result.success,
328
+ resourceCount: result.results.length,
329
+ });
330
+ }
331
+
332
+ return {
333
+ timestamp: new Date().toISOString(),
334
+ iterations,
335
+ results,
336
+ avgDuration:
337
+ results.reduce((sum, r) => sum + r.duration, 0) / results.length,
338
+ minDuration: Math.min(...results.map((r) => r.duration)),
339
+ maxDuration: Math.max(...results.map((r) => r.duration)),
340
+ };
341
+ }
342
+
343
+ /**
344
+ * Health check
345
+ */
346
+ getHealthStatus() {
347
+ const stats = this.errorMonitor.getStatistics();
348
+ const memUsage = process.memoryUsage();
349
+ const avgCleanup = this._calculateAverage(this.metrics.cleanupTimes);
350
+
351
+ const health = {
352
+ timestamp: new Date().toISOString(),
353
+ overall: 'healthy',
354
+ components: {
355
+ signalHandling: {
356
+ status: 'healthy',
357
+ avgResponseTime:
358
+ this._calculateAverage(this.metrics.signalResponseTimes).toFixed(
359
+ 2,
360
+ ) + 'ms',
361
+ threshold: this.thresholds.signalResponse + 'ms',
362
+ },
363
+ cleanup: {
364
+ status:
365
+ avgCleanup > this.thresholds.totalCleanup ? 'degraded' : 'healthy',
366
+ avgDuration: avgCleanup.toFixed(0) + 'ms',
367
+ threshold: this.thresholds.totalCleanup + 'ms',
368
+ },
369
+ errorHandling: {
370
+ status: stats.totalErrors > 10 ? 'degraded' : 'healthy',
371
+ totalErrors: stats.totalErrors,
372
+ recoveryRate: stats.successRate + '%',
373
+ },
374
+ memory: {
375
+ status: memUsage.heapUsed > 100 * 1024 * 1024 ? 'warning' : 'healthy',
376
+ heapUsed: (memUsage.heapUsed / 1024 / 1024).toFixed(2) + 'MB',
377
+ heapTotal: (memUsage.heapTotal / 1024 / 1024).toFixed(2) + 'MB',
378
+ },
379
+ },
380
+ };
381
+
382
+ // Determine overall health
383
+ const degradedCount = Object.values(health.components).filter(
384
+ (c) => c.status === 'degraded' || c.status === 'warning',
385
+ ).length;
386
+
387
+ if (degradedCount > 1) {
388
+ health.overall = 'warning';
389
+ } else if (degradedCount > 2) {
390
+ health.overall = 'critical';
391
+ }
392
+
393
+ return health;
394
+ }
395
+
396
+ /**
397
+ * Private helper methods
398
+ */
399
+
400
+ _generateSummary() {
401
+ return {
402
+ signalCount: this.metrics.signalResponseTimes.length,
403
+ cleanupCount: this.metrics.cleanupTimes.length,
404
+ totalErrors: this.errorMonitor.getStatistics().totalErrors,
405
+ avgMemoryUsage:
406
+ (
407
+ this._calculateAverage(this.metrics.memoryUsage) /
408
+ 1024 /
409
+ 1024
410
+ ).toFixed(2) + 'MB',
411
+ };
412
+ }
413
+
414
+ _analyzeSignalMetrics() {
415
+ const times = this.metrics.signalResponseTimes;
416
+ return {
417
+ sampleCount: times.length,
418
+ avgTime: this._calculateAverage(times).toFixed(2) + 'ms',
419
+ minTime: Math.min(...times, Infinity).toFixed(2) + 'ms',
420
+ maxTime: Math.max(...times, -Infinity).toFixed(2) + 'ms',
421
+ p95Time: this._calculatePercentile(times, 0.95).toFixed(2) + 'ms',
422
+ };
423
+ }
424
+
425
+ _analyzeCleanupMetrics() {
426
+ const times = this.metrics.cleanupTimes;
427
+ return {
428
+ sampleCount: times.length,
429
+ avgTime: this._calculateAverage(times).toFixed(0) + 'ms',
430
+ minTime: Math.min(...times, Infinity).toFixed(0) + 'ms',
431
+ maxTime: Math.max(...times, -Infinity).toFixed(0) + 'ms',
432
+ p95Time: this._calculatePercentile(times, 0.95).toFixed(0) + 'ms',
433
+ threshold: this.thresholds.totalCleanup + 'ms',
434
+ };
435
+ }
436
+
437
+ _analyzeMemoryMetrics() {
438
+ const usage = this.metrics.memoryUsage;
439
+ const current = process.memoryUsage();
440
+
441
+ return {
442
+ currentHeapUsed: (current.heapUsed / 1024 / 1024).toFixed(2) + 'MB',
443
+ currentHeapTotal: (current.heapTotal / 1024 / 1024).toFixed(2) + 'MB',
444
+ avgMemoryUsage: this._calculateAverage(usage).toFixed(0) + ' bytes',
445
+ trend: this._analyzeMemoryTrend(),
446
+ };
447
+ }
448
+
449
+ _analyzeMemoryTrend() {
450
+ if (this.metrics.memoryUsage.length < 2) {
451
+ return 'insufficient_data';
452
+ }
453
+
454
+ const recent = this.metrics.memoryUsage.slice(-5);
455
+ const avg = recent.reduce((a, b) => a + b, 0) / recent.length;
456
+ const older = this.metrics.memoryUsage.slice(-10, -5);
457
+ const avgOlder =
458
+ older.length > 0 ? older.reduce((a, b) => a + b, 0) / older.length : avg;
459
+
460
+ if (avg > avgOlder * 1.1) {
461
+ return 'increasing';
462
+ } else if (avg < avgOlder * 0.9) {
463
+ return 'decreasing';
464
+ }
465
+ return 'stable';
466
+ }
467
+
468
+ _getRecommendations() {
469
+ const recommendations = [];
470
+ const stats = this.errorMonitor.getStatistics();
471
+ const avgCleanup = this._calculateAverage(this.metrics.cleanupTimes);
472
+
473
+ if (avgCleanup > this.thresholds.totalCleanup) {
474
+ recommendations.push(
475
+ `Cleanup performance needs improvement. ` +
476
+ `Average: ${avgCleanup.toFixed(0)}ms, ` +
477
+ `Target: ${this.thresholds.totalCleanup}ms`,
478
+ );
479
+ }
480
+
481
+ if (stats.totalErrors > 0) {
482
+ recommendations.push(
483
+ `Review ${stats.totalErrors} recorded errors. ` +
484
+ `Recovery rate: ${stats.successRate}%`,
485
+ );
486
+ }
487
+
488
+ if (this.metrics.memoryUsage.length > 3) {
489
+ const trend = this._analyzeMemoryTrend();
490
+ if (trend === 'increasing') {
491
+ recommendations.push('Memory usage is increasing. Check for leaks.');
492
+ }
493
+ }
494
+
495
+ return recommendations;
496
+ }
497
+
498
+ _calculateAverage(values) {
499
+ if (values.length === 0) return 0;
500
+ return values.reduce((a, b) => a + b, 0) / values.length;
501
+ }
502
+
503
+ _calculatePercentile(values, percentile) {
504
+ if (values.length === 0) return 0;
505
+ const sorted = [...values].sort((a, b) => a - b);
506
+ const index = Math.ceil(sorted.length * percentile) - 1;
507
+ return sorted[Math.max(0, index)];
508
+ }
509
+ }
510
+
511
+ export default PerformanceOptimizer;