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