@cadence-mq/core
Version:
Modern, type-safe, and performant task queue for Node.js
89 lines (76 loc) • 2.5 kB
text/typescript
import type { Job, JobData, JobRepositoryDriver } from './jobs/jobs.types';
import type { TaskDefinition } from './tasks/tasks.types';
import { customAlphabet } from 'nanoid';
import { serializeError } from './errors/errors.models';
import { processJob } from './jobs/jobs.usecases';
import { createTaskDefinitionRegistry } from './tasks/task-definition.registry';
export function createQueue({
driver,
processingTimeoutMs = 10000,
generateJobId = customAlphabet('1234567890abcdefghijklmnopqrstuvwxyzABCDEFGHIJKLMNOPQRSTUVWXYZ', 32),
}: {
driver: JobRepositoryDriver;
processingTimeoutMs?: number;
generateJobId?: () => string;
}) {
const taskDefinitionRegistry = createTaskDefinitionRegistry();
let status: 'running' | 'stopped' | 'stopping' = 'stopped';
let stopPromiseResolver: (() => void) | null = null;
return {
registerTask: (taskDefinition: TaskDefinition) => {
taskDefinitionRegistry.add(taskDefinition);
},
async scheduleJob({
taskName,
data,
now = new Date(),
scheduleAt = now,
maxRetries,
}: {
taskName: string;
data: JobData;
scheduleAt?: Date;
now?: Date;
maxRetries?: number;
}) {
const job: Job = {
id: generateJobId(),
taskName,
data,
scheduleAt,
status: 'pending',
maxRetries,
};
await driver.saveJob({ job, now });
},
startWorker({ workerId: _ }: { workerId: string }) {
status = 'running';
(async () => {
// eslint-disable-next-line no-unmodified-loop-condition
while (status === 'running') {
const { job } = await driver.fetchNextJob({ processingTimeoutMs });
if (!job) {
await new Promise(resolve => setTimeout(resolve, 1000));
continue;
}
const jobId = job.id;
try {
const result = await processJob({ job, taskDefinitionRegistry });
await driver.markJobAsCompleted({ jobId, result: result ?? undefined });
} catch (error) {
await driver.markJobAsFailed({ jobId, error: serializeError({ error }) });
}
}
status = 'stopped';
stopPromiseResolver?.();
stopPromiseResolver = null;
})();
},
stopWorker(): Promise<void> {
const { promise, resolve } = Promise.withResolvers<void>();
stopPromiseResolver = resolve;
status = 'stopping';
return promise;
},
};
}