@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
JavaScript
/**
* 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);
}
}
}