"use strict"; /** * SchedulerService — manages cron-based scheduled explorations. * Loads schedules from DB on startup, registers cron jobs, and triggers sessions. */ var __createBinding = (this && this.__createBinding) || (Object.create ? (function(o, m, k, k2) { if (k2 === undefined) k2 = k; var desc = Object.getOwnPropertyDescriptor(m, k); if (!desc || ("get" in desc ? !m.__esModule : desc.writable || desc.configurable)) { desc = { enumerable: true, get: function() { return m[k]; } }; } Object.defineProperty(o, k2, desc); }) : (function(o, m, k, k2) { if (k2 === undefined) k2 = k; o[k2] = m[k]; })); var __setModuleDefault = (this && this.__setModuleDefault) || (Object.create ? (function(o, v) { Object.defineProperty(o, "default", { enumerable: true, value: v }); }) : function(o, v) { o["default"] = v; }); var __importStar = (this && this.__importStar) || (function () { var ownKeys = function(o) { ownKeys = Object.getOwnPropertyNames || function (o) { var ar = []; for (var k in o) if (Object.prototype.hasOwnProperty.call(o, k)) ar[ar.length] = k; return ar; }; return ownKeys(o); }; return function (mod) { if (mod && mod.__esModule) return mod; var result = {}; if (mod != null) for (var k = ownKeys(mod), i = 0; i < k.length; i++) if (k[i] !== "default") __createBinding(result, mod, k[i]); __setModuleDefault(result, mod); return result; }; })(); Object.defineProperty(exports, "__esModule", { value: true }); exports.SchedulerService = void 0; const cron = __importStar(require("node-cron")); const logger_1 = require("../logger"); class SchedulerService { constructor(scheduleRepo, sessionStore) { this.scheduleRepo = scheduleRepo; this.sessionStore = sessionStore; this.jobs = new Map(); } /** Load all enabled schedules and start cron jobs. */ start() { const schedules = this.scheduleRepo.findAll(true); for (const schedule of schedules) { this.register(schedule); } logger_1.log.info({ count: schedules.length }, 'SchedulerService started'); } /** Stop all cron jobs. */ stop() { for (const [id, task] of this.jobs) { task.stop(); logger_1.log.info({ scheduleId: id }, 'Cron job stopped'); } this.jobs.clear(); } /** Register (or re-register) a cron job for a schedule. */ register(schedule) { this.unregister(schedule.id); if (!schedule.enabled) return; if (!cron.validate(schedule.cronExpression)) { logger_1.log.warn({ scheduleId: schedule.id, cron: schedule.cronExpression }, 'Invalid cron expression, skipping'); return; } const task = cron.schedule(schedule.cronExpression, () => { void this.fire(schedule.id); }); this.jobs.set(schedule.id, task); logger_1.log.info({ scheduleId: schedule.id, cron: schedule.cronExpression }, 'Cron job registered'); } /** Unregister a cron job. */ unregister(scheduleId) { const existing = this.jobs.get(scheduleId); if (existing) { existing.stop(); this.jobs.delete(scheduleId); } } /** Fire a scheduled run. */ async fire(scheduleId) { const schedule = this.scheduleRepo.findById(scheduleId); if (!schedule || !schedule.enabled) return; // Check if a session from this schedule is still running const running = this.sessionStore.getAllSessions().filter((s) => s.status === 'running'); if (running.length > 0) { // Check if any running session was created from this schedule const scheduleConfig = JSON.parse(schedule.configJson); const alreadyRunning = running.some((s) => { try { const cfg = JSON.parse(s.config_json ?? '{}'); return cfg.scheduleId === scheduleId; } catch { return false; } }); if (alreadyRunning) { logger_1.log.warn({ scheduleId }, 'Previous session still running, skipping scheduled tick'); return; } void scheduleConfig; // suppress unused warning } logger_1.log.info({ scheduleId, url: schedule.url }, 'Firing scheduled exploration'); const now = Date.now(); this.scheduleRepo.update(scheduleId, { lastRunAt: now }); try { const config = JSON.parse(schedule.configJson); // Inject scheduleId into config for tracking config.scheduleId = scheduleId; await this.sessionStore.startSession({ url: schedule.url, seed: Math.floor(Math.random() * 0x7fffffff), maxStates: config.maxStates ?? 50, explorationConfig: config, }); } catch (err) { logger_1.log.error({ scheduleId, err: err instanceof Error ? err.message : String(err) }, 'Scheduled session failed to start'); } } /** Compute approximate next run time for a cron expression. */ static computeNextRunAt(cronExpression) { if (!cron.validate(cronExpression)) return null; // Simple heuristic: use current time + 60s as a placeholder // A proper implementation would parse the cron and compute the next trigger return Date.now() + 60000; } } exports.SchedulerService = SchedulerService;