@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
JavaScript
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