verae-middleware/src/workers/inProcessJobPoller.js

108 lines
2.7 KiB
JavaScript

/**
* @fileoverview In-process job poller when NATS_ENABLED=false.
* @module workers/inProcessJobPoller
*/
import { config } from '../config.js';
import { veraeClient } from '../clients/veraeClient.js';
import {
listPendingJobs,
updateJobWatcher,
removeJobWatcher,
} from '../store/jobWatchers.js';
import { getActiveWebhooks } from '../store/webhooks.js';
import { deliverWebhook } from '../services/webhookService.js';
import { createDebugger } from '../debug/logger.js';
const log = createDebugger('jobs');
let timer = null;
let running = false;
/**
* @param {import('../store/jobWatchers.js').JobWatcher} job
*/
async function processJob(job) {
const attempts = job.attempts + 1;
updateJobWatcher(job.id, { attempts });
if (attempts > config.jobPollMaxAttempts) {
updateJobWatcher(job.id, { status: 'timeout' });
removeJobWatcher(job.id);
log.warn('job timeout', { jobId: job.jobId });
return;
}
let status;
try {
status = await veraeClient.getStatus(job.veraeToken, job.jobId);
} catch (err) {
log.debug('poll error', { jobId: job.jobId, error: err.message });
return;
}
if (status.status === 'pending') {
return;
}
const event = status.status === 'completed' ? 'timestamp.completed' : 'timestamp.failed';
const hooks = getActiveWebhooks(job.tenantId, event);
for (const hook of hooks) {
try {
await deliverWebhook(hook.targetUrl, {
event,
jobId: job.jobId,
tenantId: job.tenantId,
status,
});
} catch (err) {
log.error('webhook deliver failed', { hookId: hook.id, error: err.message });
}
}
updateJobWatcher(job.id, { status: status.status });
removeJobWatcher(job.id);
log.debug('job terminal', { jobId: job.jobId, status: status.status, hooks: hooks.length });
}
async function tick() {
if (running) return;
running = true;
try {
const jobs = listPendingJobs();
await Promise.all(jobs.map((job) => processJob(job)));
} finally {
running = false;
}
}
/**
* Start interval poller (no-op if already started or NATS enabled).
* @returns {void}
*/
export function startInProcessJobPoller() {
if (config.natsEnabled) {
log.info('in-process poller skipped (NATS_ENABLED=true)');
return;
}
if (timer) return;
const interval = config.jobPollIntervalMs;
timer = setInterval(() => {
tick().catch((err) => log.error('poller tick failed', { error: err.message }));
}, interval);
log.info('in-process job poller started', { intervalMs: interval });
}
/**
* Stop interval poller.
* @returns {void}
*/
export function stopInProcessJobPoller() {
if (!timer) return;
clearInterval(timer);
timer = null;
log.info('in-process job poller stopped');
}