108 lines
2.7 KiB
JavaScript
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');
|
|
}
|