UNPKG

payload

Version:

Node, React, Headless CMS and Application Framework built on Next.js

164 lines (163 loc) 6.1 kB
import { Cron } from 'croner'; import { jobStatsGlobalSlug } from '../../config/global.js'; import { defaultAfterSchedule } from './defaultAfterSchedule.js'; import { defaultBeforeSchedule } from './defaultBeforeSchedule.js'; import { getQueuesWithSchedules } from './getQueuesWithSchedules.js'; /** * On vercel, we cannot auto-schedule jobs using a Cron - instead, we'll use this same endpoint that can * also be called from Vercel Cron for auto-running jobs. * * The benefit of doing it like this instead of a separate endpoint is that we can run jobs immediately * after they are scheduled */ export async function handleSchedules({ allQueues = false, queue: _queue, req }) { const queue = _queue ?? 'default'; const jobsConfig = req.payload.config.jobs; const queuesWithSchedules = getQueuesWithSchedules({ jobsConfig }); if (Object.keys(queuesWithSchedules).length === 0) { // No schedules defined => return early, before fetching jobsStatsGlobal, as the global may not even exist return { errored: [], queued: [], skipped: [] }; } const stats = await req.payload.db.findGlobal({ slug: jobStatsGlobalSlug, req }); /** * Almost last step! Tasks and Workflows added here just need to be constraint-checked (e.g max. 1 running task etc.), * before we can queue them */ const queueables = []; // Need to know when that particular job was last scheduled in that particular queue for (const [queueName, { schedules }] of Object.entries(queuesWithSchedules)){ if (!allQueues && queueName !== queue) { continue; } for (const schedulable of schedules){ const queuable = checkQueueableTimeConstraints({ queue: queueName, scheduleConfig: schedulable.scheduleConfig, stats, taskConfig: schedulable.taskConfig, workflowConfig: schedulable.workflowConfig }); if (queuable) { queueables.push(queuable); } } } const queued = []; const skipped = []; const errored = []; /** * Now queue, but check for constraints (= beforeSchedule) first. * Default constraint (= defaultBeforeSchedule): max. 1 running / scheduled task or workflow per queue */ for (const queueable of queueables){ const { status } = await scheduleQueueable({ queueable, req, stats }); switch(status){ case 'error': errored.push(queueable); break; case 'skipped': skipped.push(queueable); break; case 'success': queued.push(queueable); break; } } return { errored, queued, skipped }; } export function checkQueueableTimeConstraints({ queue, scheduleConfig, stats, taskConfig, workflowConfig }) { const queueScheduleStats = stats?.stats?.scheduledRuns?.queues?.[queue]; const lastScheduledRun = taskConfig ? queueScheduleStats?.tasks?.[taskConfig.slug]?.lastScheduledRun : queueScheduleStats?.workflows?.[workflowConfig?.slug ?? '']?.lastScheduledRun; const nextRun = new Cron(scheduleConfig.cron).nextRun(lastScheduledRun ?? undefined); if (!nextRun) { return false; } return { scheduleConfig, taskConfig, waitUntil: nextRun, workflowConfig }; } export async function scheduleQueueable({ queueable, req, stats }) { if (!queueable.taskConfig && !queueable.workflowConfig) { return { status: 'error' }; } const beforeScheduleFn = queueable.scheduleConfig.hooks?.beforeSchedule; const afterScheduleFN = queueable.scheduleConfig.hooks?.afterSchedule; try { const beforeScheduleResult = await (beforeScheduleFn ?? defaultBeforeSchedule)({ // @ts-expect-error we know defaultBeforeSchedule will never call itself => pass null defaultBeforeSchedule: beforeScheduleFn ? defaultBeforeSchedule : null, jobStats: stats, queueable, req }); if (!beforeScheduleResult.shouldSchedule) { await (afterScheduleFN ?? defaultAfterSchedule)({ // @ts-expect-error we know defaultAfterchedule will never call itself => pass null defaultAfterSchedule: afterScheduleFN ? defaultAfterSchedule : null, jobStats: stats, queueable, req, status: 'skipped' }); return { status: 'skipped' }; } const job = await req.payload.jobs.queue({ input: beforeScheduleResult.input ?? {}, meta: { scheduled: true }, queue: queueable.scheduleConfig.queue, req, task: queueable?.taskConfig?.slug, waitUntil: beforeScheduleResult.waitUntil, workflow: queueable.workflowConfig?.slug }); await (afterScheduleFN ?? defaultAfterSchedule)({ // @ts-expect-error we know defaultAfterchedule will never call itself => pass null defaultAfterSchedule: afterScheduleFN ? defaultAfterSchedule : null, job, jobStats: stats, queueable, req, status: 'success' }); return { status: 'success' }; } catch (error) { await (afterScheduleFN ?? defaultAfterSchedule)({ // @ts-expect-error we know defaultAfterchedule will never call itself => pass null defaultAfterSchedule: afterScheduleFN ? defaultAfterSchedule : null, error: error, jobStats: stats, queueable, req, status: 'error' }); return { status: 'error' }; } } //# sourceMappingURL=index.js.map