@arela/uploader 1.0.23 → 1.1.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 (85) hide show
  1. package/docs/AUTO_PROCESSING_PIPELINE.md +258 -0
  2. package/docs/COMPLETE_USAGE_GUIDE.md +1363 -0
  3. package/docs/DATABASESERVICE_IMPROVEMENTS.md +546 -0
  4. package/docs/PASO_2_TEST_RESULTS.md +298 -0
  5. package/docs/PASO_3_PLAN.md +385 -0
  6. package/docs/PHASE_1_FILE_DETECTION.md +366 -0
  7. package/docs/PHASE_2_API_INTEGRATION.md +426 -0
  8. package/docs/PHASE_3_DATABASE_MANAGEMENT.md +480 -0
  9. package/docs/PHASE_4_FILE_OPERATIONS.md +448 -0
  10. package/docs/PHASE_5_WATCH_MODE.md +450 -0
  11. package/docs/PHASE_6_SIGNAL_HANDLING.md +472 -0
  12. package/docs/PHASE_7_ADVANCED_FEATURES.md +560 -0
  13. package/docs/PLAN_WATCH_FEATURE.md +417 -0
  14. package/docs/README.md +480 -0
  15. package/docs/SCHEMA_ALIGNMENT_SUMMARY.md +301 -0
  16. package/docs/SMARTWATCH_DATABASE_REFACTORING.md +181 -0
  17. package/docs/SMART_WATCH_DATABASE_CHANGES.md +502 -0
  18. package/docs/TESTING_WATCH_MODE.md +212 -0
  19. package/docs/WATCHER_API_IMPLEMENTATION.md +520 -0
  20. package/docs/WATCHER_API_INTEGRATION.md +562 -0
  21. package/docs/WATCHER_SETUP_GUIDE.md +614 -0
  22. package/docs/WATCH_ARCHITECTURE.md +395 -0
  23. package/docs/WATCH_AUTO_PIPELINE.md +334 -0
  24. package/docs/WATCH_CONFIGURATION.md +267 -0
  25. package/docs/WATCH_USAGE_GUIDE.md +567 -0
  26. package/docs/commands.md +14 -0
  27. package/package.json +1 -1
  28. package/scripts/scoring-compare.js +243 -0
  29. package/scripts/scoring-phase4-check.js +96 -0
  30. package/src/commands/IdentifyCommand.js +36 -0
  31. package/src/config/config.js +2 -2
  32. package/src/file-detection.js +71 -4
  33. package/src/scoring/db-matcher-adapter.js +98 -0
  34. package/src/scoring/matchers-seed.js +386 -0
  35. package/src/scoring/scoring-engine.js +246 -0
  36. package/src/services/ScanApiService.js +14 -0
  37. package/tests/unit/scoring-engine.test.js +221 -0
  38. package/.vscode/settings.json +0 -1
  39. package/coverage/IdentifyCommand.js.html +0 -1462
  40. package/coverage/PropagateCommand.js.html +0 -1507
  41. package/coverage/PushCommand.js.html +0 -1504
  42. package/coverage/ScanCommand.js.html +0 -1654
  43. package/coverage/UploadCommand.js.html +0 -1846
  44. package/coverage/WatchCommand.js.html +0 -4111
  45. package/coverage/base.css +0 -224
  46. package/coverage/block-navigation.js +0 -87
  47. package/coverage/favicon.png +0 -0
  48. package/coverage/index.html +0 -191
  49. package/coverage/lcov-report/IdentifyCommand.js.html +0 -1462
  50. package/coverage/lcov-report/PropagateCommand.js.html +0 -1507
  51. package/coverage/lcov-report/PushCommand.js.html +0 -1504
  52. package/coverage/lcov-report/ScanCommand.js.html +0 -1654
  53. package/coverage/lcov-report/UploadCommand.js.html +0 -1846
  54. package/coverage/lcov-report/WatchCommand.js.html +0 -4111
  55. package/coverage/lcov-report/base.css +0 -224
  56. package/coverage/lcov-report/block-navigation.js +0 -87
  57. package/coverage/lcov-report/favicon.png +0 -0
  58. package/coverage/lcov-report/index.html +0 -191
  59. package/coverage/lcov-report/prettify.css +0 -1
  60. package/coverage/lcov-report/prettify.js +0 -2
  61. package/coverage/lcov-report/sort-arrow-sprite.png +0 -0
  62. package/coverage/lcov-report/sorter.js +0 -210
  63. package/coverage/lcov.info +0 -1937
  64. package/coverage/prettify.css +0 -1
  65. package/coverage/prettify.js +0 -2
  66. package/coverage/sort-arrow-sprite.png +0 -0
  67. package/coverage/sorter.js +0 -210
  68. package/docs/API_ENDPOINTS_FOR_DETECTION.md +0 -647
  69. package/docs/API_RETRY_MECHANISM.md +0 -338
  70. package/docs/ARELA_IDENTIFY_IMPLEMENTATION.md +0 -489
  71. package/docs/ARELA_IDENTIFY_QUICKREF.md +0 -186
  72. package/docs/ARELA_PROPAGATE_IMPLEMENTATION.md +0 -581
  73. package/docs/ARELA_PROPAGATE_QUICKREF.md +0 -272
  74. package/docs/ARELA_PUSH_IMPLEMENTATION.md +0 -577
  75. package/docs/ARELA_PUSH_QUICKREF.md +0 -322
  76. package/docs/ARELA_SCAN_IMPLEMENTATION.md +0 -373
  77. package/docs/ARELA_SCAN_QUICKREF.md +0 -139
  78. package/docs/CROSS_PLATFORM_PATH_HANDLING.md +0 -597
  79. package/docs/DETECTION_ATTEMPT_TRACKING.md +0 -414
  80. package/docs/MIGRATION_UPLOADER_TO_FILE_STATS.md +0 -1020
  81. package/docs/MULTI_LEVEL_DIRECTORY_SCANNING.md +0 -494
  82. package/docs/QUICK_REFERENCE_API_DETECTION.md +0 -264
  83. package/docs/REFACTORING_SUMMARY_DETECT_PEDIMENTOS.md +0 -200
  84. package/docs/STATS_COMMAND_SEQUENCE_DIAGRAM.md +0 -287
  85. package/docs/STATS_COMMAND_SIMPLE.md +0 -93
@@ -0,0 +1,520 @@
1
+ # Archivo de Ejemplo: watcher.routes.js para arela-api
2
+
3
+ **Ubicación:** `arela-api/src/routes/watcher.routes.js`
4
+
5
+ Este archivo contiene la implementación completa de los endpoints REST para exposer el WatchService.
6
+
7
+ ```javascript
8
+ import express from 'express';
9
+ import logger from '../services/LoggingService.js';
10
+
11
+ const router = express.Router();
12
+
13
+ /**
14
+ * Inicializar rutas del watcher
15
+ * Requiere que watchService esté disponible en el contexto de la app
16
+ * @param {Object} watchService - Instancia de WatchService desde arela-uploader
17
+ */
18
+ export function initWatcherRoutes(app, watchService) {
19
+ /**
20
+ * GET /api/watcher/status
21
+ * Obtener estado actual del watcher
22
+ *
23
+ * @returns {Object} Estado del watcher incluyendo estadísticas
24
+ */
25
+ router.get('/status', (req, res) => {
26
+ try {
27
+ const stats = watchService.getStats();
28
+
29
+ res.json({
30
+ isRunning: stats.isRunning,
31
+ autoProcessingEnabled: stats.autoProcessingEnabled,
32
+ watchedDirectories: stats.watchedDirectories,
33
+ activeWatchers: stats.activeWatchers,
34
+ allWatchersReady: watchService.allWatchersReady,
35
+ lastUpdated: new Date().toISOString(),
36
+ stats: {
37
+ filesAdded: stats.filesAdded,
38
+ filesModified: stats.filesModified,
39
+ filesRemoved: stats.filesRemoved,
40
+ uploadsTriggered: stats.uploadsTriggered,
41
+ pipelinesTriggered: stats.pipelinesTriggered,
42
+ errorsEncountered: stats.errorsEncountered,
43
+ },
44
+ });
45
+ } catch (error) {
46
+ logger.error(`Error getting watcher status: ${error.message}`);
47
+ res.status(500).json({
48
+ error: 'Failed to get watcher status',
49
+ details: error.message,
50
+ });
51
+ }
52
+ });
53
+
54
+ /**
55
+ * GET /api/watcher/queue-stats
56
+ * Obtener estadísticas de la cola de procesamiento
57
+ *
58
+ * @returns {Object} Estadísticas de la cola y progreso
59
+ */
60
+ router.get('/queue-stats', async (req, res) => {
61
+ try {
62
+ const queueStats = await watchService.getQueueStats();
63
+
64
+ if (!queueStats) {
65
+ return res.status(500).json({
66
+ error: 'Could not fetch queue stats',
67
+ });
68
+ }
69
+
70
+ res.json({
71
+ queue: queueStats.queue,
72
+ progress: queueStats.progress,
73
+ timestamp: queueStats.timestamp,
74
+ metadata: {
75
+ totalCapacity: queueStats.queue.total,
76
+ utilization:
77
+ ((queueStats.queue.total - queueStats.queue.pending) /
78
+ queueStats.queue.total) *
79
+ 100,
80
+ },
81
+ });
82
+ } catch (error) {
83
+ logger.error(`Error getting queue stats: ${error.message}`);
84
+ res.status(500).json({
85
+ error: 'Failed to get queue stats',
86
+ details: error.message,
87
+ });
88
+ }
89
+ });
90
+
91
+ /**
92
+ * GET /api/watcher/directories
93
+ * Obtener lista de directorios monitoreados
94
+ *
95
+ * @returns {Object} Lista de directorios con detalles
96
+ */
97
+ router.get('/directories', (req, res) => {
98
+ try {
99
+ const directories = Array.from(watchService.watchedDirs).map((dir) => {
100
+ const config = watchService.directoryConfigs.get(dir);
101
+ const isReady = watchService.watcherReady.get(dir) === true;
102
+
103
+ return {
104
+ path: dir,
105
+ isReady,
106
+ folderStructure: config?.folderStructure || 'default',
107
+ config: {
108
+ ignored: config?.ignored || 'hidden files, node_modules, .git',
109
+ awaitWriteFinish: config?.awaitWriteFinish || {
110
+ stabilityThreshold: 300,
111
+ pollInterval: 100,
112
+ },
113
+ },
114
+ };
115
+ });
116
+
117
+ res.json({
118
+ directories,
119
+ totalDirectories: directories.length,
120
+ readyCount: directories.filter((d) => d.isReady).length,
121
+ timestamp: new Date().toISOString(),
122
+ });
123
+ } catch (error) {
124
+ logger.error(`Error getting watched directories: ${error.message}`);
125
+ res.status(500).json({
126
+ error: 'Failed to get watched directories',
127
+ details: error.message,
128
+ });
129
+ }
130
+ });
131
+
132
+ /**
133
+ * POST /api/watcher/start
134
+ * Iniciar el watcher con directorios específicos
135
+ *
136
+ * @param {string[]} directories - Array de rutas a monitorear
137
+ * @param {boolean} autoProcessing - Habilitar procesamiento automático
138
+ * @param {string} folderStructure - Estructura de carpetas (arela, custom, etc)
139
+ * @returns {Object} Estado después de iniciar
140
+ */
141
+ router.post('/start', async (req, res) => {
142
+ try {
143
+ const {
144
+ directories,
145
+ autoProcessing = true,
146
+ folderStructure = 'arela',
147
+ } = req.body;
148
+
149
+ // Validación
150
+ if (!directories || !Array.isArray(directories) || directories.length === 0) {
151
+ return res.status(400).json({
152
+ error: 'Invalid request',
153
+ details: 'directories must be a non-empty array of strings',
154
+ });
155
+ }
156
+
157
+ // Validar que sean rutas válidas
158
+ const invalidPaths = directories.filter(
159
+ (dir) => typeof dir !== 'string' || dir.trim().length === 0
160
+ );
161
+
162
+ if (invalidPaths.length > 0) {
163
+ return res.status(400).json({
164
+ error: 'Invalid paths provided',
165
+ details: `${invalidPaths.length} invalid path(s)`,
166
+ });
167
+ }
168
+
169
+ logger.info(`[API] Starting watcher for ${directories.length} directories`);
170
+
171
+ // Agregar watchers para cada directorio
172
+ for (const dir of directories) {
173
+ await watchService.addWatcher(
174
+ dir,
175
+ {},
176
+ { folderStructure: folderStructure || 'arela' }
177
+ );
178
+ }
179
+
180
+ // Habilitar procesamiento automático si se solicita
181
+ if (autoProcessing) {
182
+ watchService.enableAutoProcessing({ batchSize: 10 });
183
+ }
184
+
185
+ // Iniciar watchers
186
+ await watchService.start();
187
+
188
+ // Esperar a que todos los watchers estén listos
189
+ await watchService.waitForWatchersReady();
190
+
191
+ res.json({
192
+ success: true,
193
+ message: 'Watcher started successfully',
194
+ stats: watchService.getStats(),
195
+ directoryCount: directories.length,
196
+ timestamp: new Date().toISOString(),
197
+ });
198
+ } catch (error) {
199
+ logger.error(`Error starting watcher: ${error.message}`);
200
+ res.status(500).json({
201
+ error: 'Failed to start watcher',
202
+ details: error.message,
203
+ });
204
+ }
205
+ });
206
+
207
+ /**
208
+ * POST /api/watcher/stop
209
+ * Detener el watcher
210
+ *
211
+ * @returns {Object} Estadísticas finales
212
+ */
213
+ router.post('/stop', async (req, res) => {
214
+ try {
215
+ logger.info('[API] Stopping watcher');
216
+
217
+ const finalStats = watchService.getStats();
218
+ await watchService.stop('API request');
219
+
220
+ res.json({
221
+ success: true,
222
+ message: 'Watcher stopped successfully',
223
+ finalStats,
224
+ timestamp: new Date().toISOString(),
225
+ });
226
+ } catch (error) {
227
+ logger.error(`Error stopping watcher: ${error.message}`);
228
+ res.status(500).json({
229
+ error: 'Failed to stop watcher',
230
+ details: error.message,
231
+ });
232
+ }
233
+ });
234
+
235
+ /**
236
+ * POST /api/watcher/enable-auto-processing
237
+ * Habilitar procesamiento automático
238
+ *
239
+ * @param {number} batchSize - Tamaño de lote para procesamiento
240
+ * @returns {Object} Estado actualizado
241
+ */
242
+ router.post('/enable-auto-processing', (req, res) => {
243
+ try {
244
+ const { batchSize = 10 } = req.body;
245
+
246
+ if (typeof batchSize !== 'number' || batchSize < 1) {
247
+ return res.status(400).json({
248
+ error: 'Invalid batchSize',
249
+ details: 'batchSize must be a positive number',
250
+ });
251
+ }
252
+
253
+ logger.info(`[API] Enabling auto-processing with batchSize=${batchSize}`);
254
+
255
+ watchService.enableAutoProcessing({ batchSize });
256
+
257
+ res.json({
258
+ success: true,
259
+ message: 'Auto-processing enabled',
260
+ autoProcessingEnabled: watchService.isAutoProcessingEnabled(),
261
+ batchSize,
262
+ timestamp: new Date().toISOString(),
263
+ });
264
+ } catch (error) {
265
+ logger.error(`Error enabling auto-processing: ${error.message}`);
266
+ res.status(500).json({
267
+ error: 'Failed to enable auto-processing',
268
+ details: error.message,
269
+ });
270
+ }
271
+ });
272
+
273
+ /**
274
+ * POST /api/watcher/disable-auto-processing
275
+ * Deshabilitar procesamiento automático
276
+ *
277
+ * @returns {Object} Estado actualizado
278
+ */
279
+ router.post('/disable-auto-processing', (req, res) => {
280
+ try {
281
+ logger.info('[API] Disabling auto-processing');
282
+
283
+ watchService.disableAutoProcessing();
284
+
285
+ res.json({
286
+ success: true,
287
+ message: 'Auto-processing disabled',
288
+ autoProcessingEnabled: watchService.isAutoProcessingEnabled(),
289
+ timestamp: new Date().toISOString(),
290
+ });
291
+ } catch (error) {
292
+ logger.error(`Error disabling auto-processing: ${error.message}`);
293
+ res.status(500).json({
294
+ error: 'Failed to disable auto-processing',
295
+ details: error.message,
296
+ });
297
+ }
298
+ });
299
+
300
+ /**
301
+ * GET /api/watcher/pipeline-status
302
+ * Obtener estado de pipelines en ejecución y recientes
303
+ *
304
+ * @returns {Object} Estado de pipelines y recientes
305
+ */
306
+ router.get('/pipeline-status', (req, res) => {
307
+ try {
308
+ const stats = watchService.getStats();
309
+
310
+ res.json({
311
+ isProcessing: stats.pipelinesTriggered > 0,
312
+ activePipelines: 0, // Valor placeholder - actualizar si se mantiene historial
313
+ recentPipelines: {
314
+ total: stats.pipelinesTriggered,
315
+ successful: stats.pipelinesTriggered - stats.errorsEncountered,
316
+ failed: stats.errorsEncountered,
317
+ },
318
+ errorStats: {
319
+ totalErrors: stats.errorsEncountered,
320
+ lastError: null, // Obtener de errorTracker si es necesario
321
+ },
322
+ timestamp: new Date().toISOString(),
323
+ });
324
+ } catch (error) {
325
+ logger.error(`Error getting pipeline status: ${error.message}`);
326
+ res.status(500).json({
327
+ error: 'Failed to get pipeline status',
328
+ details: error.message,
329
+ });
330
+ }
331
+ });
332
+
333
+ /**
334
+ * GET /api/watcher/health
335
+ * Health check del watcher
336
+ *
337
+ * @returns {Object} Estado de salud
338
+ */
339
+ router.get('/health', (req, res) => {
340
+ try {
341
+ const stats = watchService.getStats();
342
+
343
+ const health = {
344
+ status: stats.isRunning ? 'healthy' : 'stopped',
345
+ isRunning: stats.isRunning,
346
+ activeWatchers: stats.activeWatchers,
347
+ errors: stats.errorsEncountered,
348
+ uptime: stats.isRunning ? 'tracking' : 'not running',
349
+ timestamp: new Date().toISOString(),
350
+ };
351
+
352
+ const statusCode = stats.isRunning && stats.errorsEncountered === 0 ? 200 : 503;
353
+ res.status(statusCode).json(health);
354
+ } catch (error) {
355
+ res.status(500).json({
356
+ status: 'error',
357
+ error: error.message,
358
+ });
359
+ }
360
+ });
361
+
362
+ // Montar el router en la app
363
+ app.use('/api/watcher', router);
364
+
365
+ logger.info('✅ Watcher API routes initialized');
366
+ }
367
+
368
+ export default router;
369
+ ```
370
+
371
+ ---
372
+
373
+ ## 📋 Integración en app.js
374
+
375
+ ```javascript
376
+ // app.js (o main.js, index.js)
377
+
378
+ import express from 'express';
379
+ import { initWatcherRoutes } from './routes/watcher.routes.js';
380
+ import watchService from '@arela/uploader/services'; // O importar directamente
381
+
382
+ const app = express();
383
+
384
+ // Middleware
385
+ app.use(express.json());
386
+ app.use(express.urlencoded({ extended: true }));
387
+
388
+ // ... otras rutas ...
389
+
390
+ // Inicializar rutas del watcher
391
+ initWatcherRoutes(app, watchService);
392
+
393
+ // Error handling
394
+ app.use((err, req, res, next) => {
395
+ console.error(err);
396
+ res.status(500).json({
397
+ error: 'Internal server error',
398
+ message: err.message,
399
+ });
400
+ });
401
+
402
+ // Iniciar servidor
403
+ const PORT = process.env.PORT || 3000;
404
+ app.listen(PORT, () => {
405
+ console.log(`🚀 Server running on port ${PORT}`);
406
+ });
407
+
408
+ export default app;
409
+ ```
410
+
411
+ ---
412
+
413
+ ## 🧪 Ejemplo de uso desde el frontend
414
+
415
+ ```javascript
416
+ // hooks/useWatcherMonitor.js
417
+ import { useState, useEffect, useCallback } from 'react';
418
+
419
+ export const useWatcherMonitor = (pollInterval = 5000) => {
420
+ const [status, setStatus] = useState(null);
421
+ const [queue, setQueue] = useState(null);
422
+ const [directories, setDirectories] = useState([]);
423
+ const [loading, setLoading] = useState(false);
424
+ const [error, setError] = useState(null);
425
+
426
+ // Refrescar datos
427
+ const refresh = useCallback(async () => {
428
+ try {
429
+ setLoading(true);
430
+ setError(null);
431
+
432
+ const [statusRes, queueRes, dirRes] = await Promise.all([
433
+ fetch('/api/watcher/status'),
434
+ fetch('/api/watcher/queue-stats'),
435
+ fetch('/api/watcher/directories'),
436
+ ]);
437
+
438
+ if (!statusRes.ok || !queueRes.ok || !dirRes.ok) {
439
+ throw new Error('Failed to fetch watcher data');
440
+ }
441
+
442
+ setStatus(await statusRes.json());
443
+ setQueue(await queueRes.json());
444
+ setDirectories(await dirRes.json());
445
+ } catch (err) {
446
+ setError(err.message);
447
+ console.error('Error fetching watcher data:', err);
448
+ } finally {
449
+ setLoading(false);
450
+ }
451
+ }, []);
452
+
453
+ // Poll automático
454
+ useEffect(() => {
455
+ refresh(); // Fetch inicial
456
+
457
+ const interval = setInterval(refresh, pollInterval);
458
+ return () => clearInterval(interval);
459
+ }, [pollInterval, refresh]);
460
+
461
+ // Acciones
462
+ const startWatcher = useCallback(
463
+ async (dirs) => {
464
+ const res = await fetch('/api/watcher/start', {
465
+ method: 'POST',
466
+ headers: { 'Content-Type': 'application/json' },
467
+ body: JSON.stringify({
468
+ directories: dirs,
469
+ autoProcessing: true,
470
+ }),
471
+ });
472
+ if (!res.ok) throw new Error('Failed to start watcher');
473
+ await refresh();
474
+ return res.json();
475
+ },
476
+ [refresh]
477
+ );
478
+
479
+ const stopWatcher = useCallback(async () => {
480
+ const res = await fetch('/api/watcher/stop', { method: 'POST' });
481
+ if (!res.ok) throw new Error('Failed to stop watcher');
482
+ await refresh();
483
+ return res.json();
484
+ }, [refresh]);
485
+
486
+ const enableAutoProcessing = useCallback(async () => {
487
+ const res = await fetch('/api/watcher/enable-auto-processing', {
488
+ method: 'POST',
489
+ headers: { 'Content-Type': 'application/json' },
490
+ body: JSON.stringify({ batchSize: 10 }),
491
+ });
492
+ if (!res.ok) throw new Error('Failed to enable auto-processing');
493
+ await refresh();
494
+ return res.json();
495
+ }, [refresh]);
496
+
497
+ return {
498
+ status,
499
+ queue,
500
+ directories,
501
+ loading,
502
+ error,
503
+ refresh,
504
+ startWatcher,
505
+ stopWatcher,
506
+ enableAutoProcessing,
507
+ };
508
+ };
509
+ ```
510
+
511
+ ---
512
+
513
+ ## 📌 Resumen de cambios necesarios en arela-api
514
+
515
+ 1. ✅ Crear `routes/watcher.routes.js` (archivo completo arriba)
516
+ 2. ✅ Importar en `app.js` o `index.js`
517
+ 3. ✅ Asegurar que WatchService de arela-uploader esté disponible
518
+ 4. ✅ Agregar autenticación si es necesario
519
+ 5. ✅ Documentar endpoints en Swagger/OpenAPI
520
+