@cadence-mq/core
Version:
Modern, type-safe, and performant task queue for Node.js
119 lines (113 loc) • 3.29 kB
JavaScript
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