UNPKG

payload

Version:

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

258 lines (257 loc) • 10.2 kB
import ObjectIdImport from 'bson-objectid'; import { calculateBackoffWaitUntil } from './calculateBackoffWaitUntil.js'; import { importHandlerPath } from './importHandlerPath.js'; const ObjectId = ObjectIdImport.default || ObjectIdImport; async function getTaskHandlerFromConfig(taskConfig) { let handler; if (typeof taskConfig.handler === 'function') { handler = taskConfig.handler; } else { handler = await importHandlerPath(taskConfig.handler); } return handler; } export async function handleTaskFailed({ error, executedAt, input, job, maxRetries, output, parent, req, retriesConfig, state, taskConfig, taskHandlerResult, taskID, taskSlug, taskStatus, updateJob }) { req.payload.logger.error({ err: error, job, msg: `Error running task ${taskID}`, taskSlug }); if (taskConfig?.onFail) { await taskConfig.onFail(); } if (!job.log) { job.log = []; } const errorJSON = error ? { name: error.name, message: error.message, stack: error.stack } : { message: taskHandlerResult.state === 'failed' ? taskHandlerResult.errorMessage ?? taskHandlerResult.state : 'failed' }; job.log.push({ id: new ObjectId().toHexString(), completedAt: new Date().toISOString(), error: errorJSON, executedAt: executedAt.toISOString(), input, output, parent: req?.payload?.config?.jobs?.addParentToTaskLog ? parent : undefined, state: 'failed', taskID, taskSlug }); if (job.waitUntil) { // Check if waitUntil is in the past const waitUntil = new Date(job.waitUntil); if (waitUntil < new Date()) { // Outdated waitUntil, remove it delete job.waitUntil; } } if (!taskStatus?.complete && (taskStatus?.totalTried ?? 0) >= maxRetries) { state.reachedMaxRetries = true; await updateJob({ error, hasError: true, log: job.log, processing: false, waitUntil: job.waitUntil }); throw new Error(`Task ${taskSlug} has failed more than the allowed retries in workflow ${job.workflowSlug}${error ? `. Error: ${String(error)}` : ''}`); } else { // Job will retry. Let's determine when! const waitUntil = calculateBackoffWaitUntil({ retriesConfig, totalTried: taskStatus?.totalTried ?? 0 }); // Update job's waitUntil only if this waitUntil is later than the current one if (!job.waitUntil || waitUntil > new Date(job.waitUntil)) { job.waitUntil = waitUntil.toISOString(); } await updateJob({ log: job.log, processing: false, waitUntil: job.waitUntil }); throw error ?? new Error('Task failed'); } } export const getRunTaskFunction = (state, job, workflowConfig, req, isInline, updateJob, parent)=>{ const runTask = (taskSlug)=>async (taskID, { input, retries, task })=>{ const executedAt = new Date(); let inlineRunner = null; if (isInline) { inlineRunner = task; } let taskConfig; if (!isInline) { taskConfig = req.payload.config.jobs.tasks.find((t)=>t.slug === taskSlug); if (!taskConfig) { throw new Error(`Task ${taskSlug} not found in workflow ${job.workflowSlug}`); } } const retriesConfigFromPropsNormalized = retries == undefined || retries == null ? {} : typeof retries === 'number' ? { attempts: retries } : retries; const retriesConfigFromTaskConfigNormalized = taskConfig ? typeof taskConfig.retries === 'number' ? { attempts: taskConfig.retries } : taskConfig.retries : {}; const finalRetriesConfig = { ...retriesConfigFromTaskConfigNormalized, ...retriesConfigFromPropsNormalized }; const taskStatus = job?.taskStatus?.[taskSlug] ? job.taskStatus[taskSlug][taskID] : null; // Handle restoration of task if it succeeded in a previous run if (taskStatus && taskStatus.complete === true) { let shouldRestore = true; if (finalRetriesConfig?.shouldRestore === false) { shouldRestore = false; } else if (typeof finalRetriesConfig?.shouldRestore === 'function') { shouldRestore = await finalRetriesConfig.shouldRestore({ input, job, req, taskStatus }); } if (shouldRestore) { return taskStatus.output; } } let runner; if (isInline) { runner = inlineRunner; } else { if (!taskConfig) { throw new Error(`Task ${taskSlug} not found in workflow ${job.workflowSlug}`); } runner = await getTaskHandlerFromConfig(taskConfig); } if (!runner || typeof runner !== 'function') { const errorMessage = isInline ? `Can't find runner for inline task with ID ${taskID}` : `Can't find runner while importing with the path ${typeof workflowConfig.handler === 'string' ? workflowConfig.handler : 'unknown - no string path'} in job type ${job.workflowSlug} for task ${taskSlug}.`; req.payload.logger.error(errorMessage); await updateJob({ error: { error: errorMessage }, hasError: true, log: [ ...job.log, { id: new ObjectId().toHexString(), completedAt: new Date().toISOString(), error: errorMessage, executedAt: executedAt.toISOString(), parent: req?.payload?.config?.jobs?.addParentToTaskLog ? parent : undefined, state: 'failed', taskID, taskSlug } ], processing: false }); throw new Error(errorMessage); } let maxRetries = finalRetriesConfig?.attempts; if (maxRetries === undefined || maxRetries === null) { // Inherit retries from workflow config, if they are undefined and the workflow config has retries configured if (workflowConfig.retries !== undefined && workflowConfig.retries !== null) { maxRetries = typeof workflowConfig.retries === 'object' ? workflowConfig.retries.attempts : workflowConfig.retries; } else { maxRetries = 0; } } let taskHandlerResult; let output = {}; try { taskHandlerResult = await runner({ inlineTask: getRunTaskFunction(state, job, workflowConfig, req, true, updateJob, { taskID, taskSlug }), input, job: job, req, tasks: getRunTaskFunction(state, job, workflowConfig, req, false, updateJob, { taskID, taskSlug }) }); } catch (err) { await handleTaskFailed({ error: err, executedAt, input, job, maxRetries, output, parent, req, retriesConfig: finalRetriesConfig, state, taskConfig, taskID, taskSlug, taskStatus, updateJob }); throw new Error('Task failed'); } if (taskHandlerResult.state === 'failed') { await handleTaskFailed({ executedAt, input, job, maxRetries, output, parent, req, retriesConfig: finalRetriesConfig, state, taskConfig, taskHandlerResult, taskID, taskSlug, taskStatus, updateJob }); throw new Error('Task failed'); } else { output = taskHandlerResult.output; } if (taskConfig?.onSuccess) { await taskConfig.onSuccess(); } if (!job.log) { job.log = []; } job.log.push({ id: new ObjectId().toHexString(), completedAt: new Date().toISOString(), executedAt: executedAt.toISOString(), input, output, parent: req?.payload?.config?.jobs?.addParentToTaskLog ? parent : undefined, state: 'succeeded', taskID, taskSlug }); await updateJob({ log: job.log }); return output; }; if (isInline) { return runTask('inline'); } else { const tasks = {}; for (const task of req?.payload?.config?.jobs?.tasks ?? []){ tasks[task.slug] = runTask(task.slug); } return tasks; } }; //# sourceMappingURL=getRunTaskFunction.js.map