@arela/uploader 1.1.4 → 1.2.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 (95) hide show
  1. package/.claude/worktrees/agent-multi-profile/.env.template +224 -0
  2. package/.claude/worktrees/agent-multi-profile/.prettierrc +13 -0
  3. package/.claude/worktrees/agent-multi-profile/README.md +405 -0
  4. package/.claude/worktrees/agent-multi-profile/package-lock.json +7096 -0
  5. package/.claude/worktrees/agent-multi-profile/package.json +78 -0
  6. package/.claude/worktrees/agent-multi-profile/scripts/cleanup-ds-store.js +109 -0
  7. package/.claude/worktrees/agent-multi-profile/scripts/cleanup-system-files.js +69 -0
  8. package/.claude/worktrees/agent-multi-profile/scripts/scoring-compare.js +243 -0
  9. package/.claude/worktrees/agent-multi-profile/scripts/scoring-phase4-check.js +96 -0
  10. package/.claude/worktrees/agent-multi-profile/scripts/tests/phase-7-features.test.js +415 -0
  11. package/.claude/worktrees/agent-multi-profile/scripts/tests/signal-handling.test.js +275 -0
  12. package/.claude/worktrees/agent-multi-profile/scripts/tests/smart-watch-integration.test.js +554 -0
  13. package/.claude/worktrees/agent-multi-profile/scripts/tests/watch-service-integration.test.js +584 -0
  14. package/.claude/worktrees/agent-multi-profile/src/commands/AgentCommand.js +229 -0
  15. package/.claude/worktrees/agent-multi-profile/src/commands/AgentInitCommand.js +316 -0
  16. package/.claude/worktrees/agent-multi-profile/src/commands/DatastageCommand.js +164 -0
  17. package/.claude/worktrees/agent-multi-profile/src/commands/GDriveSyncCommand.js +475 -0
  18. package/.claude/worktrees/agent-multi-profile/src/commands/IdentifyCommand.js +708 -0
  19. package/.claude/worktrees/agent-multi-profile/src/commands/PollWorkerCommand.js +169 -0
  20. package/.claude/worktrees/agent-multi-profile/src/commands/PropagateCommand.js +636 -0
  21. package/.claude/worktrees/agent-multi-profile/src/commands/PushCommand.js +743 -0
  22. package/.claude/worktrees/agent-multi-profile/src/commands/ScanCommand.js +722 -0
  23. package/.claude/worktrees/agent-multi-profile/src/commands/UploadCommand.js +587 -0
  24. package/.claude/worktrees/agent-multi-profile/src/commands/WatchCommand.js +1342 -0
  25. package/.claude/worktrees/agent-multi-profile/src/commands/WorkerCommand.js +337 -0
  26. package/.claude/worktrees/agent-multi-profile/src/config/config.js +862 -0
  27. package/.claude/worktrees/agent-multi-profile/src/document-type-shared.js +131 -0
  28. package/.claude/worktrees/agent-multi-profile/src/document-types/_pedimento-shared-extractors.js +348 -0
  29. package/.claude/worktrees/agent-multi-profile/src/document-types/doda-pdf.js +121 -0
  30. package/.claude/worktrees/agent-multi-profile/src/document-types/doda-xml.js +118 -0
  31. package/.claude/worktrees/agent-multi-profile/src/document-types/factura-inter-agencia.js +186 -0
  32. package/.claude/worktrees/agent-multi-profile/src/document-types/facturas-comerciales.js +233 -0
  33. package/.claude/worktrees/agent-multi-profile/src/document-types/pedimento-completo-xml.js +372 -0
  34. package/.claude/worktrees/agent-multi-profile/src/document-types/pedimento-completo.js +108 -0
  35. package/.claude/worktrees/agent-multi-profile/src/document-types/pedimento-simplificado.js +76 -0
  36. package/.claude/worktrees/agent-multi-profile/src/document-types/proforma.js +29 -0
  37. package/.claude/worktrees/agent-multi-profile/src/document-types/support-document.js +200 -0
  38. package/.claude/worktrees/agent-multi-profile/src/errors/ErrorHandler.js +278 -0
  39. package/.claude/worktrees/agent-multi-profile/src/errors/ErrorTypes.js +104 -0
  40. package/.claude/worktrees/agent-multi-profile/src/file-detection.js +338 -0
  41. package/.claude/worktrees/agent-multi-profile/src/index.js +890 -0
  42. package/.claude/worktrees/agent-multi-profile/src/scoring/db-matcher-adapter.js +98 -0
  43. package/.claude/worktrees/agent-multi-profile/src/scoring/matchers-seed.js +386 -0
  44. package/.claude/worktrees/agent-multi-profile/src/scoring/scoring-engine.js +251 -0
  45. package/.claude/worktrees/agent-multi-profile/src/services/AdvancedFilterService.js +505 -0
  46. package/.claude/worktrees/agent-multi-profile/src/services/AutoProcessingService.js +749 -0
  47. package/.claude/worktrees/agent-multi-profile/src/services/BenchmarkingService.js +381 -0
  48. package/.claude/worktrees/agent-multi-profile/src/services/DatabaseService.js +2173 -0
  49. package/.claude/worktrees/agent-multi-profile/src/services/DatastageApiService.js +240 -0
  50. package/.claude/worktrees/agent-multi-profile/src/services/ErrorMonitor.js +275 -0
  51. package/.claude/worktrees/agent-multi-profile/src/services/GoogleDriveService.js +217 -0
  52. package/.claude/worktrees/agent-multi-profile/src/services/LoggingService.js +649 -0
  53. package/.claude/worktrees/agent-multi-profile/src/services/MonitoringService.js +401 -0
  54. package/.claude/worktrees/agent-multi-profile/src/services/PerformanceOptimizer.js +511 -0
  55. package/.claude/worktrees/agent-multi-profile/src/services/PipelineApiService.js +274 -0
  56. package/.claude/worktrees/agent-multi-profile/src/services/PipelineJobRunner.js +389 -0
  57. package/.claude/worktrees/agent-multi-profile/src/services/ProfileManager.js +164 -0
  58. package/.claude/worktrees/agent-multi-profile/src/services/ReportingService.js +511 -0
  59. package/.claude/worktrees/agent-multi-profile/src/services/ScanApiService.js +775 -0
  60. package/.claude/worktrees/agent-multi-profile/src/services/SignalHandler.js +255 -0
  61. package/.claude/worktrees/agent-multi-profile/src/services/SmartWatchDatabaseService.js +527 -0
  62. package/.claude/worktrees/agent-multi-profile/src/services/WatchService.js +783 -0
  63. package/.claude/worktrees/agent-multi-profile/src/services/upload/ApiUploadService.js +676 -0
  64. package/.claude/worktrees/agent-multi-profile/src/services/upload/BaseUploadService.js +36 -0
  65. package/.claude/worktrees/agent-multi-profile/src/services/upload/MultiApiUploadService.js +233 -0
  66. package/.claude/worktrees/agent-multi-profile/src/services/upload/SupabaseUploadService.js +148 -0
  67. package/.claude/worktrees/agent-multi-profile/src/services/upload/UploadServiceFactory.js +100 -0
  68. package/.claude/worktrees/agent-multi-profile/src/utils/CleanupManager.js +262 -0
  69. package/.claude/worktrees/agent-multi-profile/src/utils/FileOperations.js +192 -0
  70. package/.claude/worktrees/agent-multi-profile/src/utils/FileSanitizer.js +99 -0
  71. package/.claude/worktrees/agent-multi-profile/src/utils/PathDetector.js +198 -0
  72. package/.claude/worktrees/agent-multi-profile/src/utils/PathNormalizer.js +274 -0
  73. package/.claude/worktrees/agent-multi-profile/src/utils/WatchEventHandler.js +522 -0
  74. package/.claude/worktrees/agent-multi-profile/supabase/migrations/001_create_initial_schema.sql +366 -0
  75. package/.claude/worktrees/agent-multi-profile/supabase/migrations/002_align_with_arela_api_schema.sql +145 -0
  76. package/.claude/worktrees/agent-multi-profile/tests/commands/IdentifyCommand.test.js +570 -0
  77. package/.claude/worktrees/agent-multi-profile/tests/commands/PropagateCommand.test.js +568 -0
  78. package/.claude/worktrees/agent-multi-profile/tests/commands/PushCommand.test.js +754 -0
  79. package/.claude/worktrees/agent-multi-profile/tests/commands/ScanCommand.test.js +382 -0
  80. package/.claude/worktrees/agent-multi-profile/tests/unit/PathAndTableNameGeneration.test.js +1211 -0
  81. package/.claude/worktrees/agent-multi-profile/tests/unit/factura-inter-agencia.test.js +218 -0
  82. package/.claude/worktrees/agent-multi-profile/tests/unit/pedimento-completo-xml-matcher.test.js +271 -0
  83. package/.claude/worktrees/agent-multi-profile/tests/unit/pedimento-simplificado-matcher.test.js +185 -0
  84. package/.claude/worktrees/agent-multi-profile/tests/unit/scoring-engine.test.js +221 -0
  85. package/README.md +65 -0
  86. package/package.json +1 -1
  87. package/src/commands/AgentCommand.js +210 -0
  88. package/src/commands/AgentInitCommand.js +316 -0
  89. package/src/commands/PollWorkerCommand.js +11 -322
  90. package/src/config/config.js +44 -6
  91. package/src/index.js +55 -0
  92. package/src/services/LoggingService.js +35 -0
  93. package/src/services/PipelineApiService.js +7 -1
  94. package/src/services/PipelineJobRunner.js +389 -0
  95. package/src/services/ProfileManager.js +164 -0
@@ -0,0 +1,337 @@
1
+ import { Worker } from 'bullmq';
2
+
3
+ import logger from '../services/LoggingService.js';
4
+
5
+ import appConfig from '../config/config.js';
6
+ import ErrorHandler from '../errors/ErrorHandler.js';
7
+
8
+ /**
9
+ * Worker Command Handler
10
+ * Runs arela-uploader as a BullMQ worker, listening for jobs from the API
11
+ *
12
+ * This enables UI-controlled execution of:
13
+ * - scan: Filesystem scanning and file cataloging
14
+ * - identify: Document type detection (pedimento-simplificado)
15
+ * - propagate: Spread arela_path to related files
16
+ * - push: Upload files to storage
17
+ */
18
+ export class WorkerCommand {
19
+ constructor() {
20
+ this.errorHandler = new ErrorHandler(logger);
21
+ this.workers = new Map();
22
+ this.isShuttingDown = false;
23
+ }
24
+
25
+ /**
26
+ * Execute the worker command
27
+ * @param {Object} options - Command options
28
+ * @param {string} options.queues - Comma-separated list of queues to listen to
29
+ * @param {number} options.concurrency - Number of concurrent jobs per queue
30
+ */
31
+ async execute(options = {}) {
32
+ try {
33
+ // Load configuration
34
+ const redisConfig = appConfig.getRedisConfig();
35
+ const workerConfig = appConfig.getWorkerConfig();
36
+
37
+ // Determine which queues to listen to
38
+ const queueNames = options.queues
39
+ ? options.queues.split(',').map((q) => q.trim())
40
+ : workerConfig.queues;
41
+
42
+ const concurrency =
43
+ parseInt(options.concurrency) || workerConfig.concurrency;
44
+
45
+ logger.info('šŸ”§ Starting Arela Worker');
46
+ logger.info(`šŸ“” Redis: ${redisConfig.host}:${redisConfig.port}`);
47
+ logger.info(`šŸ“‹ Queues: ${queueNames.join(', ')}`);
48
+ logger.info(`⚔ Concurrency: ${concurrency}`);
49
+
50
+ // Setup graceful shutdown handlers
51
+ this.#setupShutdownHandlers();
52
+
53
+ // Start workers for each queue
54
+ for (const queueName of queueNames) {
55
+ await this.#startWorker(queueName, redisConfig, concurrency);
56
+ }
57
+
58
+ logger.success('\nāœ… Worker is running. Press Ctrl+C to stop.\n');
59
+
60
+ // Keep the process alive
61
+ await this.#keepAlive();
62
+ } catch (error) {
63
+ logger.error('āŒ Worker failed to start:', error.message);
64
+ throw error;
65
+ }
66
+ }
67
+
68
+ /**
69
+ * Start a worker for a specific queue
70
+ * @private
71
+ */
72
+ async #startWorker(queueName, redisConfig, concurrency) {
73
+ const processor = this.#getProcessor(queueName);
74
+
75
+ if (!processor) {
76
+ logger.warn(`āš ļø No processor found for queue: ${queueName}`);
77
+ return;
78
+ }
79
+
80
+ const worker = new Worker(queueName, processor, {
81
+ connection: redisConfig,
82
+ concurrency,
83
+ lockDuration: appConfig.getWorkerConfig().lockDuration,
84
+ stalledInterval: appConfig.getWorkerConfig().stalledInterval,
85
+ });
86
+
87
+ // Event handlers
88
+ worker.on('completed', (job) => {
89
+ logger.success(`āœ… Job ${job.id} completed on ${queueName}`);
90
+ });
91
+
92
+ worker.on('failed', (job, err) => {
93
+ logger.error(`āŒ Job ${job?.id} failed on ${queueName}: ${err.message}`);
94
+ });
95
+
96
+ worker.on('progress', (job, progress) => {
97
+ logger.info(`šŸ“Š Job ${job.id} progress: ${JSON.stringify(progress)}`);
98
+ });
99
+
100
+ worker.on('error', (err) => {
101
+ logger.error(`šŸ”“ Worker error on ${queueName}: ${err.message}`);
102
+ });
103
+
104
+ this.workers.set(queueName, worker);
105
+ logger.info(` āœ“ Worker started for queue: ${queueName}`);
106
+ }
107
+
108
+ /**
109
+ * Get the processor function for a queue
110
+ * @private
111
+ */
112
+ #getProcessor(queueName) {
113
+ const processors = {
114
+ 'pipeline-scan': this.#processScanJob.bind(this),
115
+ 'pipeline-identify': this.#processIdentifyJob.bind(this),
116
+ 'pipeline-propagate': this.#processPropagateJob.bind(this),
117
+ 'pipeline-push': this.#processPushJob.bind(this),
118
+ };
119
+
120
+ return processors[queueName];
121
+ }
122
+
123
+ /**
124
+ * Process a scan job
125
+ * @private
126
+ */
127
+ async #processScanJob(job) {
128
+ logger.info(`šŸ” Processing scan job ${job.id}`);
129
+
130
+ const { ScanCommand } = await import('./ScanCommand.js');
131
+ const scanCommand = new ScanCommand();
132
+
133
+ // Create progress callback for the job
134
+ const onProgress = (percent, message) => {
135
+ job.updateProgress({ percent, message });
136
+ job.log(message);
137
+ };
138
+
139
+ try {
140
+ // Extract options from job data
141
+ const options = {
142
+ api: job.data.apiTarget || 'default',
143
+ countFirst: job.data.countFirst || false,
144
+ stream: job.data.stream !== false,
145
+ // Pass progress callback
146
+ onProgress,
147
+ };
148
+
149
+ // Override scan config if provided in job
150
+ if (job.data.scanConfig) {
151
+ this.#overrideScanConfig(job.data.scanConfig);
152
+ }
153
+
154
+ const result = await scanCommand.execute(options);
155
+ return { success: true, ...result };
156
+ } catch (error) {
157
+ logger.error(`Scan job ${job.id} failed: ${error.message}`);
158
+ throw error;
159
+ }
160
+ }
161
+
162
+ /**
163
+ * Process an identify job
164
+ * @private
165
+ */
166
+ async #processIdentifyJob(job) {
167
+ logger.info(`šŸ” Processing identify job ${job.id}`);
168
+
169
+ const { IdentifyCommand } = await import('./IdentifyCommand.js');
170
+ const identifyCommand = new IdentifyCommand();
171
+
172
+ const onProgress = (percent, message) => {
173
+ job.updateProgress({ percent, message });
174
+ job.log(message);
175
+ };
176
+
177
+ try {
178
+ const options = {
179
+ api: job.data.apiTarget || 'default',
180
+ batchSize: job.data.batchSize || 100,
181
+ showStats: job.data.showStats || false,
182
+ onProgress,
183
+ };
184
+
185
+ if (job.data.scanConfig) {
186
+ this.#overrideScanConfig(job.data.scanConfig);
187
+ }
188
+
189
+ const result = await identifyCommand.execute(options);
190
+ return { success: true, ...result };
191
+ } catch (error) {
192
+ logger.error(`Identify job ${job.id} failed: ${error.message}`);
193
+ throw error;
194
+ }
195
+ }
196
+
197
+ /**
198
+ * Process a propagate job
199
+ * @private
200
+ */
201
+ async #processPropagateJob(job) {
202
+ logger.info(`šŸ”„ Processing propagate job ${job.id}`);
203
+
204
+ const { PropagateCommand } = await import('./PropagateCommand.js');
205
+
206
+ const onProgress = (percent, message) => {
207
+ job.updateProgress({ percent, message });
208
+ job.log(message);
209
+ };
210
+
211
+ try {
212
+ const options = {
213
+ api: job.data.apiTarget || 'default',
214
+ batchSize: job.data.batchSize || 50,
215
+ showStats: job.data.showStats || false,
216
+ onProgress,
217
+ };
218
+
219
+ if (job.data.scanConfig) {
220
+ this.#overrideScanConfig(job.data.scanConfig);
221
+ }
222
+
223
+ const propagateCommand = new PropagateCommand(options);
224
+ const result = await propagateCommand.execute();
225
+ return { success: true, ...result };
226
+ } catch (error) {
227
+ logger.error(`Propagate job ${job.id} failed: ${error.message}`);
228
+ throw error;
229
+ }
230
+ }
231
+
232
+ /**
233
+ * Process a push job
234
+ * @private
235
+ */
236
+ async #processPushJob(job) {
237
+ logger.info(`šŸ“¤ Processing push job ${job.id}`);
238
+
239
+ const { PushCommand } = await import('./PushCommand.js');
240
+ const pushCommand = new PushCommand();
241
+
242
+ const onProgress = (percent, message) => {
243
+ job.updateProgress({ percent, message });
244
+ job.log(message);
245
+ };
246
+
247
+ try {
248
+ const options = {
249
+ api: job.data.apiTarget || 'default',
250
+ scanApi: job.data.scanApi,
251
+ pushApi: job.data.pushApi,
252
+ batchSize: job.data.batchSize || 100,
253
+ uploadBatchSize: job.data.uploadBatchSize || 10,
254
+ rfcs: job.data.rfcs,
255
+ years: job.data.years,
256
+ folderStructure: job.data.folderStructure,
257
+ autoOrganize: job.data.autoOrganize !== false,
258
+ showStats: job.data.showStats || false,
259
+ onProgress,
260
+ };
261
+
262
+ if (job.data.scanConfig) {
263
+ this.#overrideScanConfig(job.data.scanConfig);
264
+ }
265
+
266
+ const result = await pushCommand.execute(options);
267
+ return { success: true, ...result };
268
+ } catch (error) {
269
+ logger.error(`Push job ${job.id} failed: ${error.message}`);
270
+ throw error;
271
+ }
272
+ }
273
+
274
+ /**
275
+ * Override scan configuration from job data
276
+ * @private
277
+ */
278
+ #overrideScanConfig(scanConfig) {
279
+ // Temporarily override environment variables for this job
280
+ if (scanConfig.companySlug) {
281
+ process.env.ARELA_COMPANY_SLUG = scanConfig.companySlug;
282
+ }
283
+ if (scanConfig.serverId) {
284
+ process.env.ARELA_SERVER_ID = scanConfig.serverId;
285
+ }
286
+ if (scanConfig.basePath) {
287
+ process.env.UPLOAD_BASE_PATH = scanConfig.basePath;
288
+ process.env.ARELA_BASE_PATH_LABEL = scanConfig.basePath;
289
+ }
290
+ if (scanConfig.directoryLevel !== undefined) {
291
+ process.env.SCAN_DIRECTORY_LEVEL = String(scanConfig.directoryLevel);
292
+ }
293
+
294
+ // Reload cached config from the updated env vars
295
+ appConfig.reloadScanConfig();
296
+ }
297
+
298
+ /**
299
+ * Setup graceful shutdown handlers
300
+ * @private
301
+ */
302
+ #setupShutdownHandlers() {
303
+ const shutdown = async (signal) => {
304
+ if (this.isShuttingDown) return;
305
+ this.isShuttingDown = true;
306
+
307
+ logger.info(`\nšŸ‘‹ Received ${signal}. Shutting down workers...`);
308
+
309
+ // Close all workers gracefully
310
+ const closePromises = [];
311
+ for (const [queueName, worker] of this.workers) {
312
+ logger.info(` ā³ Closing worker: ${queueName}`);
313
+ closePromises.push(worker.close());
314
+ }
315
+
316
+ await Promise.all(closePromises);
317
+ logger.info('āœ… All workers closed gracefully');
318
+ process.exit(0);
319
+ };
320
+
321
+ process.on('SIGINT', () => shutdown('SIGINT'));
322
+ process.on('SIGTERM', () => shutdown('SIGTERM'));
323
+ }
324
+
325
+ /**
326
+ * Keep the process alive while workers are running
327
+ * @private
328
+ */
329
+ async #keepAlive() {
330
+ return new Promise((resolve) => {
331
+ // This promise never resolves, keeping the process alive
332
+ // The process will exit via the shutdown handlers
333
+ });
334
+ }
335
+ }
336
+
337
+ export default new WorkerCommand();