UNPKG

@juspay/neurolink

Version:

Universal AI Development Platform with working MCP integration, multi-provider support, voice (TTS/STT/realtime), and professional CLI. 58+ external MCP servers discoverable, multimodal file processing, RAG pipelines. Build, test, and deploy AI applicatio

141 lines (140 loc) 4.82 kB
/** * NodeTimeout Backend — Development/zero-dependency task scheduling. * * - Cron tasks → parsed with `croner`, scheduled via setTimeout chains * - Interval tasks → setInterval * - One-shot tasks → setTimeout * - All timers are in-process — lost on restart */ import { Cron } from "croner"; import { logger } from "../../utils/logger.js"; import { TASK_DEFAULTS, } from "../../types/index.js"; export class NodeTimeoutBackend { name = "node-timeout"; scheduled = new Map(); paused = new Map(); disposed = false; activeRuns = 0; maxConcurrentRuns; constructor(config) { this.maxConcurrentRuns = config.maxConcurrentRuns ?? TASK_DEFAULTS.maxConcurrentRuns; } async initialize() { logger.info("[NodeTimeout] Backend initialized"); } async shutdown() { this.disposed = true; for (const entry of this.scheduled.values()) { this.clearEntry(entry); } this.scheduled.clear(); this.paused.clear(); logger.info("[NodeTimeout] Backend shut down"); } async schedule(task, executor) { // Cancel existing schedule for this task if any await this.cancel(task.id); const entry = { taskId: task.id, executor, task }; const schedule = task.schedule; if (schedule.type === "cron") { entry.cronJob = new Cron(schedule.expression, { timezone: schedule.timezone, catch: (err) => { logger.error("[NodeTimeout] Cron execution error", { taskId: task.id, error: String(err), }); }, }, () => { this.executeTask(entry); }); } else if (schedule.type === "interval") { // Wait for the first interval tick before executing entry.intervalId = setInterval(() => { this.executeTask(entry); }, schedule.every); } else if (schedule.type === "once") { const at = typeof schedule.at === "string" ? new Date(schedule.at) : schedule.at; const delay = Math.max(0, at.getTime() - Date.now()); entry.timeoutId = setTimeout(() => { this.executeTask(entry); this.scheduled.delete(task.id); }, delay); } this.scheduled.set(task.id, entry); logger.info("[NodeTimeout] Task scheduled", { taskId: task.id, type: schedule.type, }); } async cancel(taskId) { const entry = this.scheduled.get(taskId); if (entry) { this.clearEntry(entry); this.scheduled.delete(taskId); } this.paused.delete(taskId); logger.debug("[NodeTimeout] Task cancelled", { taskId }); } async pause(taskId) { const entry = this.scheduled.get(taskId); if (!entry) { return; } this.clearEntry(entry); this.scheduled.delete(taskId); // Save the entry so we can re-schedule on resume this.paused.set(taskId, entry); logger.info("[NodeTimeout] Task paused", { taskId }); } async resume(taskId) { const entry = this.paused.get(taskId); if (!entry) { return; } this.paused.delete(taskId); // Re-schedule with the saved task and executor await this.schedule(entry.task, entry.executor); logger.info("[NodeTimeout] Task resumed", { taskId }); } async isHealthy() { return !this.disposed; } // ── Internal ────────────────────────────────────────── executeTask(entry) { if (this.activeRuns >= this.maxConcurrentRuns) { logger.warn("[NodeTimeout] Max concurrent runs reached, skipping tick", { taskId: entry.taskId, activeRuns: this.activeRuns, maxConcurrentRuns: this.maxConcurrentRuns, }); return; } this.activeRuns++; entry .executor(entry.task) .catch((err) => { logger.error("[NodeTimeout] Task execution failed", { taskId: entry.taskId, error: String(err), }); }) .finally(() => { this.activeRuns--; }); } clearEntry(entry) { if (entry.cronJob) { entry.cronJob.stop(); } if (entry.intervalId !== undefined) { clearInterval(entry.intervalId); } if (entry.timeoutId !== undefined) { clearTimeout(entry.timeoutId); } } }