import type { Queue } from "bullmq"; import { inject, injectable } from "@codemation/core"; import { ApplicationTokens } from "../applicationTokens"; import type { AppConfig } from "../presentation/config/AppConfig"; import { RedisConnectionOptionsFactory } from "../infrastructure/scheduler/bullmq/RedisConnectionOptionsFactory"; export const HITL_TIMEOUT_QUEUE_NAME_SUFFIX = "hitl.timeout"; export interface HitlTimeoutJobPayload { readonly kind: "hitl.timeout"; readonly taskId: string; } @injectable() export class HitlTimeoutJobScheduler { private queue: Queue | null = null; private readonly queueName: string; private readonly redisUrl: string | null; constructor(@inject(ApplicationTokens.AppConfig) appConfig: AppConfig) { this.redisUrl = appConfig.scheduler.kind === "bullmq" ? (appConfig.scheduler.redisUrl ?? null) : null; const queuePrefix = appConfig.env.CODEMATION_BULLMQ_PREFIX ?? "codemation"; this.queueName = `${queuePrefix}.${HITL_TIMEOUT_QUEUE_NAME_SUFFIX}`; } async enqueueTimeoutJob(args: { taskId: string; expiresAt: Date }): Promise { const queue = await this.getOrCreateQueue(); if (!queue) return; const delay = Math.max(0, args.expiresAt.getTime() - Date.now()); await queue.add("hitl.timeout", { kind: "hitl.timeout", taskId: args.taskId } satisfies HitlTimeoutJobPayload, { jobId: this.makeJobId(args.taskId), delay, removeOnComplete: true, removeOnFail: true, }); } async cancelTimeoutJob(taskId: string): Promise { const queue = await this.getOrCreateQueue(); if (!queue) return; const job = await queue.getJob(this.makeJobId(taskId)); await job?.remove(); } async close(): Promise { if (this.queue) { await this.queue.close(); this.queue = null; } } getQueueName(): string { return this.queueName; } private async getOrCreateQueue(): Promise { if (this.redisUrl === null) { return null; } if (!this.queue) { const { Queue } = await import("bullmq"); const connectionOptions = RedisConnectionOptionsFactory.fromConfig({ url: this.redisUrl }); this.queue = new Queue(this.queueName, { connection: connectionOptions as never, }); } return this.queue; } private makeJobId(taskId: string): string { return `hitl_timeout__${taskId}`; } }