| 1 | import { Worker, WorkerOptions, Processor } from "bullmq";
|
| 2 | import { getQueue, QueueName } from "@app/lib/queue/QueueRegistry";
|
| 3 | import { mailProcessor } from "@app/lib/queue/processors/mail";
|
| 4 | import { insightProcessor } from "@app/lib/queue/processors/insights";
|
| 5 | import { medicineReminderProcessor } from "@app/lib/queue/processors/medicineReminders";
|
| 6 | import { dailyReminderProcessor } from "@app/lib/queue/processors/dailyReminders";
|
| 7 | import { exportProcessor } from "@app/lib/queue/processors/exports";
|
| 8 | import { InsightJobName } from "@app/lib/queue/types";
|
| 9 | import { logger } from "@app/lib/logger";
|
| 10 |
|
| 11 | type ProcessorMap = Record<QueueName, Processor>;
|
| 12 |
|
| 13 | const processorMap: ProcessorMap = {
|
| 14 | mail: mailProcessor,
|
| 15 | insights: insightProcessor,
|
| 16 | "medicine-reminders": medicineReminderProcessor,
|
| 17 | "daily-reminders": dailyReminderProcessor,
|
| 18 | exports: exportProcessor,
|
| 19 | };
|
| 20 |
|
| 21 | // Per-queue concurrency
|
| 22 | // TODO: When we process images, we need to bump for that queue
|
| 23 | const concurrencyMap: Record<QueueName, number> = {
|
| 24 | mail: 10,
|
| 25 | insights: 5,
|
| 26 | "medicine-reminders": 5,
|
| 27 | "daily-reminders": 5,
|
| 28 | exports: 5,
|
| 29 | };
|
| 30 |
|
| 31 | const workers: Worker[] = [];
|
| 32 |
|
| 33 | export function bootWorkers(): void {
|
| 34 | logger.info("Booting workers...");
|
| 35 | for (const [name, processor] of Object.entries(processorMap)) {
|
| 36 | const queueName = name as QueueName;
|
| 37 |
|
| 38 | logger.info({ queue: queueName }, "Worker booting...");
|
| 39 | const worker = new Worker(queueName, processor, {
|
| 40 | connection: {
|
| 41 | host: process.env.VALKEY_HOST,
|
| 42 | port: Number(process.env.VALKEY_PORT),
|
| 43 | },
|
| 44 | concurrency: concurrencyMap[queueName],
|
| 45 | } satisfies WorkerOptions);
|
| 46 |
|
| 47 | worker.on("failed", (job, err) => {
|
| 48 | logger.error({ err, queue: queueName, jobId: job?.id }, "job failed");
|
| 49 | });
|
| 50 |
|
| 51 | worker.on("error", (err) => {
|
| 52 | logger.error({ err, queue: queueName }, "worker error");
|
| 53 | });
|
| 54 |
|
| 55 | workers.push(worker);
|
| 56 | }
|
| 57 | }
|
| 58 |
|
| 59 | export async function scheduleInsights(): Promise<void> {
|
| 60 | const queue = getQueue("insights");
|
| 61 | await queue.add(
|
| 62 | InsightJobName.FanOut,
|
| 63 | {},
|
| 64 | {
|
| 65 | repeat: { pattern: "0 3 * * 1" }, // Mondays at 3am
|
| 66 | jobId: "insight-fan-out-weekly", // prevents duplicate repeatable jobs on restart
|
| 67 | },
|
| 68 | );
|
| 69 | }
|
| 70 |
|
| 71 | export async function closeAllWorkers(): Promise<void> {
|
| 72 | await Promise.all(workers.map((w) => w.close()));
|
| 73 | }
|