import { type TaskTriggerSource, type PrismaClient, type PrismaClientOrTransaction, } from "@trigger.dev/database"; import { $replica, prisma } from "~/db.server"; import { getAllTaskIdentifiers } from "~/models/task.server"; import { logger } from "./logger.server"; import { getTaskIdentifiersFromCache, populateTaskIdentifierCache, type TaskIdentifierEntry, } from "./taskIdentifierCache.server"; function toTriggerSource(source: string | undefined): TaskTriggerSource { const normalized = source?.toUpperCase(); if (normalized === "AGENT") return "AGENT"; if (normalized === "SCHEDULED" || normalized === "SCHEDULE") return "SCHEDULED"; return "STANDARD"; } export async function syncTaskIdentifiers( environmentId: string, projectId: string, workerId: string, tasks: { id: string; triggerSource?: string }[], db: PrismaClient = prisma ): Promise { const slugs = tasks.map((t) => t.id); const now = new Date(); // Group slugs by resolved triggerSource for bulk updates const slugsBySource = new Map(); for (const task of tasks) { const source = toTriggerSource(task.triggerSource); const existing = slugsBySource.get(source); if (existing) { existing.push(task.id); } else { slugsBySource.set(source, [task.id]); } } // Batch: insert new rows, update existing rows per source group, archive removed tasks await db.$transaction([ // Insert any new task identifiers (skips rows that already exist) db.taskIdentifier.createMany({ data: tasks.map((task) => ({ runtimeEnvironmentId: environmentId, projectId, slug: task.id, currentTriggerSource: toTriggerSource(task.triggerSource), currentWorkerId: workerId, })), skipDuplicates: true, }), // Update existing rows — one updateMany per distinct triggerSource value ...Array.from(slugsBySource.entries()).map(([source, taskSlugs]) => db.taskIdentifier.updateMany({ where: { runtimeEnvironmentId: environmentId, slug: { in: taskSlugs }, }, data: { currentTriggerSource: source, currentWorkerId: workerId, lastSeenAt: now, isInLatestDeployment: true, }, }) ), // Archive tasks no longer in this deploy db.taskIdentifier.updateMany({ where: { runtimeEnvironmentId: environmentId, slug: { notIn: slugs }, isInLatestDeployment: true, }, data: { isInLatestDeployment: false }, }), ]); const allIdentifiers = await db.taskIdentifier.findMany({ where: { runtimeEnvironmentId: environmentId }, select: { slug: true, currentTriggerSource: true, isInLatestDeployment: true, }, }); populateTaskIdentifierCache( environmentId, allIdentifiers.map((t) => ({ slug: t.slug, triggerSource: t.currentTriggerSource, isInLatestDeployment: t.isInLatestDeployment, })) ).catch((error) => { logger.error("Failed to populate task identifier cache after sync", { environmentId, error }); }); } function sortEntries(entries: TaskIdentifierEntry[]): TaskIdentifierEntry[] { return entries.sort((a, b) => { if (a.isInLatestDeployment !== b.isInLatestDeployment) return a.isInLatestDeployment ? -1 : 1; return a.slug.localeCompare(b.slug); }); } export async function getTaskIdentifiers( environmentId: string, db: PrismaClientOrTransaction = $replica ): Promise { const cached = await getTaskIdentifiersFromCache(environmentId); if (cached) return sortEntries(cached); const dbRows = await db.taskIdentifier.findMany({ where: { runtimeEnvironmentId: environmentId }, select: { slug: true, currentTriggerSource: true, isInLatestDeployment: true, }, }); if (dbRows.length > 0) { const entries: TaskIdentifierEntry[] = dbRows.map((t) => ({ slug: t.slug, triggerSource: t.currentTriggerSource, isInLatestDeployment: t.isInLatestDeployment, })); populateTaskIdentifierCache(environmentId, entries).catch((error) => { logger.error("Failed to populate task identifier cache after DB read", { environmentId, error, }); }); return sortEntries(entries); } const legacyRows = await getAllTaskIdentifiers(db, environmentId); const entries: TaskIdentifierEntry[] = legacyRows.map((t) => ({ slug: t.slug, triggerSource: t.triggerSource, isInLatestDeployment: true, })); if (entries.length > 0) { populateTaskIdentifierCache(environmentId, entries).catch((error) => { logger.error("Failed to populate task identifier cache after legacy fallback", { environmentId, error, }); }); } return sortEntries(entries); }