UNPKG

@cadence-mq/core

Version:

Modern, type-safe, and performant task queue for Node.js

119 lines (113 loc) 3.29 kB
import { customAlphabet } from "nanoid"; //#region src/errors/errors.models.ts function serializeError({ error }) { if (error instanceof Error) return `${error.message}\n\n${error.stack}`; if (typeof error === "object" && error !== null) return JSON.stringify(error); return String(error); } //#endregion //#region src/shared/retry.ts async function retry(fn, { maxRetries = 0 } = {}) { if (maxRetries < 0) throw new TypeError("maxRetries must be greater than 0"); let attempts = 0; let lastError; while (attempts < maxRetries + 1) try { return await fn(); } catch (error) { attempts++; lastError = error; } throw lastError; } //#endregion //#region src/jobs/jobs.usecases.ts async function processJob({ job, taskDefinitionRegistry }) { const taskDefinition = taskDefinitionRegistry.get(job.taskName); const maxRetries = job.maxRetries ?? taskDefinition.options?.maxRetries ?? 0; return await retry(() => taskDefinition.handler({ data: job.data, context: { workerId: job.id } }), { maxRetries }); } //#endregion //#region src/tasks/task-definition.registry.ts function createTaskDefinitionRegistry() { const taskDefinitions = /* @__PURE__ */ new Map(); return { add: (taskDefinition) => { if (taskDefinitions.has(taskDefinition.name)) throw new Error(`Task definition already exists: ${taskDefinition.name}`); taskDefinitions.set(taskDefinition.name, taskDefinition); }, get: (name) => { const taskDefinition = taskDefinitions.get(name); if (!taskDefinition) throw new Error(`Task definition not found: ${name}`); return taskDefinition; } }; } //#endregion //#region src/queue.ts function createQueue({ driver, processingTimeoutMs = 1e4, generateJobId = customAlphabet("1234567890abcdefghijklmnopqrstuvwxyzABCDEFGHIJKLMNOPQRSTUVWXYZ", 32) }) { const taskDefinitionRegistry = createTaskDefinitionRegistry(); let status = "stopped"; let stopPromiseResolver = null; return { registerTask: (taskDefinition) => { taskDefinitionRegistry.add(taskDefinition); }, async scheduleJob({ taskName, data, now = /* @__PURE__ */ new Date(), scheduleAt = now, maxRetries }) { const job = { id: generateJobId(), taskName, data, scheduleAt, status: "pending", maxRetries }; await driver.saveJob({ job, now }); }, startWorker({ workerId: _ }) { status = "running"; (async () => { while (status === "running") { const { job } = await driver.fetchNextJob({ processingTimeoutMs }); if (!job) { await new Promise((resolve) => setTimeout(resolve, 1e3)); continue; } const jobId = job.id; try { const result = await processJob({ job, taskDefinitionRegistry }); await driver.markJobAsCompleted({ jobId, result: result ?? void 0 }); } catch (error) { await driver.markJobAsFailed({ jobId, error: serializeError({ error }) }); } } status = "stopped"; stopPromiseResolver?.(); stopPromiseResolver = null; })(); }, stopWorker() { const { promise, resolve } = Promise.withResolvers(); stopPromiseResolver = resolve; status = "stopping"; return promise; } }; } //#endregion export { createQueue }; //# sourceMappingURL=index.js.map