UNPKG

@pulzar/core

Version:

Next-generation Node.js framework for ultra-fast web applications with zero-reflection DI, GraphQL, WebSockets, events, and edge runtime support

251 lines 7.63 kB
import MemoryTaskAdapter from "./adapters/memory"; export class TaskScheduler { adapter; options; runningTasks = new Map(); taskDefinitions = new Map(); constructor(options = {}) { this.adapter = options.adapter || new MemoryTaskAdapter(); this.options = { adapter: this.adapter, maxConcurrency: 10, retryAttempts: 3, retryDelay: 1000, timeout: 30000, enableMetrics: true, ...options, }; } /** * Register a task definition */ register(name, handler, options = {}) { const definition = { name, handler, options: { timeout: this.options.timeout, retryAttempts: this.options.retryAttempts, retryDelay: this.options.retryDelay, ...options, }, }; this.taskDefinitions.set(name, definition); } /** * Schedule a task to run */ async schedule(name, data, scheduleOptions = {}) { const definition = this.taskDefinitions.get(name); if (!definition) { throw new Error(`Task '${name}' not found`); } const taskId = this.generateTaskId(); const task = { id: taskId, name, data, status: "pending", createdAt: new Date(), scheduledFor: this.calculateScheduledTime(scheduleOptions), attempts: 0, options: { ...definition.options, ...scheduleOptions, }, }; await this.adapter.add(task); return taskId; } /** * Execute a task immediately */ async execute(name, data) { const definition = this.taskDefinitions.get(name); if (!definition) { throw new Error(`Task '${name}' not found`); } const taskId = this.generateTaskId(); const task = { id: taskId, name, data, status: "running", createdAt: new Date(), scheduledFor: new Date(), options: definition.options, }; return this.executeTask(task); } /** * Cancel a scheduled task */ async cancel(taskId) { await this.adapter.remove(taskId); } /** * Get task status */ async getStatus(taskId) { return this.adapter.get(taskId); } /** * Get all tasks */ async getTasks(filter) { return this.adapter.list(filter); } /** * Get task statistics */ async getStats() { const adapterStats = await this.adapter.getStats(); return { total: adapterStats.total, pending: adapterStats.pending, running: this.runningTasks.size, completed: adapterStats.completed, failed: adapterStats.failed, cancelled: adapterStats.cancelled, averageDuration: adapterStats.averageDuration, throughput: adapterStats.throughput, errorRate: adapterStats.errorRate, }; } /** * Start the scheduler */ async start() { await this.adapter.start(); this.processScheduledTasks(); } /** * Stop the scheduler */ async stop() { await this.adapter.stop(); // Wait for running tasks to complete const runningPromises = Array.from(this.runningTasks.values()); if (runningPromises.length > 0) { await Promise.allSettled(runningPromises); } } /** * Process scheduled tasks */ async processScheduledTasks() { setInterval(async () => { try { const dueTasks = await this.adapter.getDueTasks(); for (const task of dueTasks) { if (this.runningTasks.size >= this.options.maxConcurrency) { break; // Wait for next interval } this.executeTask(task); } } catch (error) { console.error("Error processing scheduled tasks:", error); } }, 1000); // Check every second } /** * Execute a task with retry logic */ async executeTask(task) { const definition = this.taskDefinitions.get(task.name); if (!definition) { throw new Error(`Task definition not found for '${task.name}'`); } const executePromise = this.executeWithRetry(task, definition); this.runningTasks.set(task.id, executePromise); try { const result = await executePromise; return result; } finally { this.runningTasks.delete(task.id); } } /** * Execute task with retry logic */ async executeWithRetry(task, definition) { const { handler, options } = definition; let lastError = null; const retryAttempts = options.retryAttempts ?? this.options.retryAttempts; const retryDelay = options.retryDelay ?? this.options.retryDelay; for (let attempt = 0; attempt < retryAttempts; attempt++) { try { const startTime = Date.now(); // Apply timeout const result = await Promise.race([ handler(task.data), new Promise((_, reject) => setTimeout(() => reject(new Error("Task timeout")), options.timeout)), ]); const duration = Date.now() - startTime; await this.adapter.complete(task.id, { status: "completed", result, duration, attempts: attempt + 1, }); return { taskId: task.id, status: "completed", result, duration, attempts: attempt + 1, }; } catch (error) { lastError = error instanceof Error ? error : new Error(String(error)); if (attempt < retryAttempts - 1) { await this.delay(retryDelay * Math.pow(2, attempt)); } } } // All retries failed await this.adapter.fail(task.id, { status: "failed", error: lastError?.message || "Unknown error", attempts: retryAttempts, failedAt: new Date(), }); return { taskId: task.id, status: "failed", error: lastError?.message || "Unknown error", attempts: retryAttempts, }; } /** * Calculate scheduled time based on options */ calculateScheduledTime(options) { const now = new Date(); if (options.delay) { return new Date(now.getTime() + options.delay); } if (options.interval) { return new Date(now.getTime() + options.interval); } return now; } /** * Generate unique task ID */ generateTaskId() { return `task-${Date.now()}-${Math.random().toString(36).substr(2, 9)}`; } /** * Delay utility */ delay(ms) { return new Promise((resolve) => setTimeout(resolve, ms)); } } export function createTaskScheduler(options) { return new TaskScheduler(options); } //# sourceMappingURL=scheduler.js.map