UNPKG

@re-shell/cli

Version:

Full-stack development platform uniting microservices and microfrontends. Build complete applications with .NET (ASP.NET Core Web API, Minimal API), Java (Spring Boot, Quarkus, Micronaut, Vert.x), Rust (Actix-Web, Warp, Rocket, Axum), Python (FastAPI, Dja

362 lines (361 loc) 12 kB
"use strict"; Object.defineProperty(exports, "__esModule", { value: true }); exports.operationQueue = exports.OperationQueue = void 0; /** * Operation queue management with priority scheduling */ const events_1 = require("events"); class OperationQueue extends events_1.EventEmitter { constructor(maxConcurrent = 3) { super(); this.tasks = new Map(); this.runningTasks = new Map(); this.completedTasks = []; this.processing = false; this.maxConcurrent = maxConcurrent; } static getInstance(maxConcurrent) { if (!OperationQueue.instance) { OperationQueue.instance = new OperationQueue(maxConcurrent); } return OperationQueue.instance; } /** * Add a task to the queue */ add(name, operation, options = {}) { const id = this.generateId(); const task = { id, name, priority: options.priority || OperationQueue.PRIORITY.NORMAL, operation, dependencies: options.dependencies || [], timeout: options.timeout || 30000, retries: options.retries || 2, retryDelay: options.retryDelay || 1000, metadata: options.metadata || {}, createdAt: Date.now(), attempts: 0, status: 'pending' }; this.tasks.set(id, task); this.emit('taskAdded', { id, name, priority: task.priority }); // Start processing if not already running if (!this.processing) { this.startProcessing(); } return id; } /** * Add multiple related tasks as a batch */ addBatch(tasks) { const ids = []; for (const task of tasks) { const id = this.add(task.name, task.operation, { priority: task.priority, dependencies: task.dependencies, timeout: task.timeout, retries: task.retries, metadata: task.metadata }); ids.push(id); } this.emit('batchAdded', { ids, count: tasks.length }); return ids; } /** * Cancel a task */ cancel(id) { const task = this.tasks.get(id); if (!task) return false; if (task.status === 'running') { // Can't cancel running tasks directly, but mark for cancellation task.status = 'cancelled'; this.emit('taskCancelled', { id, name: task.name }); return true; } if (task.status === 'pending' || task.status === 'scheduled') { task.status = 'cancelled'; this.tasks.delete(id); this.emit('taskCancelled', { id, name: task.name }); return true; } return false; } /** * Cancel all tasks with specific priority or lower */ cancelByPriority(maxPriority) { let cancelled = 0; for (const [id, task] of this.tasks) { if (task.priority <= maxPriority && (task.status === 'pending' || task.status === 'scheduled')) { if (this.cancel(id)) { cancelled++; } } } return cancelled; } /** * Get task by ID */ getTask(id) { return this.tasks.get(id) || this.runningTasks.get(id) || this.completedTasks.find(t => t.id === id); } /** * Get all tasks by status */ getTasksByStatus(status) { const allTasks = [ ...Array.from(this.tasks.values()), ...Array.from(this.runningTasks.values()), ...this.completedTasks ]; return allTasks.filter(task => task.status === status); } /** * Get queue statistics */ getStats() { const allTasks = [ ...Array.from(this.tasks.values()), ...Array.from(this.runningTasks.values()), ...this.completedTasks ]; const total = allTasks.length; const pending = allTasks.filter(t => t.status === 'pending').length; const scheduled = allTasks.filter(t => t.status === 'scheduled').length; const running = allTasks.filter(t => t.status === 'running').length; const completed = allTasks.filter(t => t.status === 'completed').length; const failed = allTasks.filter(t => t.status === 'failed').length; const cancelled = allTasks.filter(t => t.status === 'cancelled').length; // Calculate average wait time (from created to started) const waitTimes = allTasks .filter(t => t.startedAt) .map(t => t.startedAt - t.createdAt); const averageWaitTime = waitTimes.length > 0 ? waitTimes.reduce((sum, time) => sum + time, 0) / waitTimes.length : 0; // Calculate average execution time const execTimes = allTasks .filter(t => t.completedAt && t.startedAt) .map(t => t.completedAt - t.startedAt); const averageExecutionTime = execTimes.length > 0 ? execTimes.reduce((sum, time) => sum + time, 0) / execTimes.length : 0; // Calculate throughput (completed tasks per minute) const now = Date.now(); const recentlyCompleted = allTasks.filter(t => t.status === 'completed' && t.completedAt && (now - t.completedAt) < 60000).length; return { total, pending, scheduled, running, completed, failed, cancelled, averageWaitTime, averageExecutionTime, throughput: recentlyCompleted }; } /** * Wait for all tasks to complete */ async waitForCompletion(timeout) { return new Promise((resolve, reject) => { const check = () => { const stats = this.getStats(); if (stats.pending + stats.scheduled + stats.running === 0) { resolve(); return; } setTimeout(check, 100); }; if (timeout) { setTimeout(() => reject(new Error('Timeout waiting for queue completion')), timeout); } check(); }); } /** * Clear completed tasks from memory */ clearCompleted() { const count = this.completedTasks.length; this.completedTasks = []; this.emit('completedTasksCleared', { count }); return count; } /** * Start processing queue */ startProcessing() { if (this.processing) return; this.processing = true; this.processingInterval = setInterval(() => { this.processQueue(); }, 100); this.emit('processingStarted'); } /** * Stop processing queue */ stopProcessing() { this.processing = false; if (this.processingInterval) { clearInterval(this.processingInterval); this.processingInterval = undefined; } this.emit('processingStopped'); } /** * Process tasks from queue */ async processQueue() { // Check if we can run more tasks if (this.runningTasks.size >= this.maxConcurrent) { return; } // Get next executable task const nextTask = this.getNextExecutableTask(); if (!nextTask) { // No executable tasks - stop processing if queue is empty if (this.tasks.size === 0 && this.runningTasks.size === 0) { this.stopProcessing(); } return; } // Move task to running this.tasks.delete(nextTask.id); this.runningTasks.set(nextTask.id, nextTask); nextTask.status = 'running'; nextTask.startedAt = Date.now(); nextTask.attempts++; this.emit('taskStarted', { id: nextTask.id, name: nextTask.name, attempt: nextTask.attempts }); // Execute task this.executeTask(nextTask); } /** * Get next executable task considering priority and dependencies */ getNextExecutableTask() { const pendingTasks = Array.from(this.tasks.values()) .filter(task => task.status === 'pending'); if (pendingTasks.length === 0) return null; // Filter tasks that have all dependencies completed const executableTasks = pendingTasks.filter(task => this.areDependenciesCompleted(task.dependencies || [])); if (executableTasks.length === 0) return null; // Sort by priority (highest first), then by creation time (oldest first) executableTasks.sort((a, b) => { if (a.priority !== b.priority) { return b.priority - a.priority; } return a.createdAt - b.createdAt; }); return executableTasks[0]; } /** * Check if all dependencies are completed */ areDependenciesCompleted(dependencies) { for (const depId of dependencies) { const depTask = this.getTask(depId); if (!depTask || depTask.status !== 'completed') { return false; } } return true; } /** * Execute a single task */ async executeTask(task) { try { // Set up timeout const timeoutPromise = new Promise((_, reject) => { setTimeout(() => { reject(new Error(`Task "${task.name}" timed out after ${task.timeout}ms`)); }, task.timeout); }); // Execute with timeout await Promise.race([task.operation(), timeoutPromise]); // Task completed successfully task.status = 'completed'; task.completedAt = Date.now(); this.runningTasks.delete(task.id); this.completedTasks.push(task); this.emit('taskCompleted', { id: task.id, name: task.name, duration: task.completedAt - task.startedAt, attempts: task.attempts }); } catch (error) { // Task failed const duration = Date.now() - task.startedAt; // Check if we should retry if (task.attempts < (task.retries || 0)) { // Retry the task task.status = 'pending'; this.runningTasks.delete(task.id); setTimeout(() => { this.tasks.set(task.id, task); }, task.retryDelay || 1000); this.emit('taskRetry', { id: task.id, name: task.name, attempt: task.attempts, error: error.message }); } else { // Task failed permanently task.status = 'failed'; task.completedAt = Date.now(); this.runningTasks.delete(task.id); this.completedTasks.push(task); this.emit('taskFailed', { id: task.id, name: task.name, duration, attempts: task.attempts, error: error.message }); } } } /** * Generate unique task ID */ generateId() { return `task_${Date.now()}_${Math.random().toString(36).substr(2, 9)}`; } } exports.OperationQueue = OperationQueue; // Priority levels OperationQueue.PRIORITY = { CRITICAL: 100, HIGH: 75, NORMAL: 50, LOW: 25, BACKGROUND: 0 }; // Export singleton instance exports.operationQueue = OperationQueue.getInstance();