UNPKG

@penkov/tasks_queue

Version:

A lightweight PostgreSQL-backed task queue system with scheduling, retries, backoff strategies, and priority handling. Designed for efficiency and observability in modern Node.js applications.

84 lines (83 loc) 3.45 kB
import { MultiStepTask } from "./multi-step-task.js"; import { option } from "scats"; export class ManagedWorkflowTask extends MultiStepTask { maxRuns; constructor(maxRuns) { super(); this.maxRuns = ManagedWorkflowTask.validateMaxRuns(option(maxRuns).getOrElseValue(100)); } static validateMaxRuns(value) { const normalized = Number(value); if (!Number.isFinite(normalized) || normalized <= 0 || !Number.isInteger(normalized)) { throw new Error("Managed workflow maxRuns must be a positive integer"); } return normalized; } readCurrentRunCount(payload) { const rawValue = option(payload.workflowPayload.runCount).orUndefined; if (rawValue === undefined) { return 0; } const parsed = Number(rawValue); if (!Number.isFinite(parsed) || parsed < 0 || !Number.isInteger(parsed)) { throw new Error(`Invalid workflow runCount value: ${String(rawValue)}. runCount must be a non-negative integer`); } return parsed; } async processNext(payload, context) { const currentRunCount = this.readCurrentRunCount(payload); const nextRunCount = currentRunCount + 1; if (nextRunCount > this.maxRuns) { throw new Error(`Workflow exceeded maximum parent runs: ${nextRunCount} > ${this.maxRuns}`); } const nextPayload = payload.copy({ workflowPayload: { ...payload.workflowPayload, runCount: nextRunCount, }, }); context.setPayload(nextPayload.toJson); let childSpawned = false; let resultSubmitted = false; const managedContext = { taskId: context.taskId, currentAttempt: context.currentAttempt, maxAttempts: context.maxAttempts, resolvedChildTask: context.resolvedChildTask, ping: () => context.ping(), findTask: (taskId) => context.findTask(taskId), setPayload: (nextUserPayload) => { const wrapped = nextPayload.copy({ userPayload: nextUserPayload, }); context.setPayload(wrapped.toJson); }, spawnChild: (task) => { if (childSpawned) { throw new Error("Managed workflow cannot spawn more than one child in a single run"); } if (resultSubmitted) { throw new Error("Managed workflow cannot spawn a child after submitting result"); } childSpawned = true; context.spawnChild(task); }, submitResult: (result) => { if (resultSubmitted) { throw new Error("Managed workflow cannot submit more than one result in a single run"); } if (childSpawned) { throw new Error("Managed workflow cannot submit result after spawning a child"); } resultSubmitted = true; context.submitResult(result); }, }; await this.run(nextPayload.userPayload, managedContext); if (!childSpawned && !resultSubmitted) { throw new Error("Managed workflow must either spawn a child task or submit a result in each run"); } } }