import type { JobRun, Task, TaskAttempt, TaskTriggerSource } from "@trigger.dev/database"; import { CachedTask, ServerTask } from "@trigger.dev/core"; import { PrismaClientOrTransaction, sqlDatabaseSchema } from "~/db.server"; export type TaskWithAttempts = Task & { attempts: TaskAttempt[]; run: { forceYieldImmediately: boolean }; }; export function taskWithAttemptsToServerTask(task: TaskWithAttempts): ServerTask { return { id: task.id, name: task.name, icon: task.icon, noop: task.noop, startedAt: task.startedAt, completedAt: task.completedAt, delayUntil: task.delayUntil, status: task.status, description: task.description, params: task.params as any, output: task.outputIsUndefined ? undefined : (task.output as any), context: task.context as any, properties: task.properties as any, style: task.style as any, error: task.error, parentId: task.parentId, attempts: task.attempts.length, idempotencyKey: task.idempotencyKey, operation: task.operation, callbackUrl: task.callbackUrl, forceYield: task.run.forceYieldImmediately, childExecutionMode: task.childExecutionMode, }; } export type TaskForCaching = Pick< Task, "id" | "status" | "idempotencyKey" | "noop" | "output" | "parentId" | "outputIsUndefined" >; export function prepareTasksForCaching( possibleTasks: TaskForCaching[], maxSize: number ): { tasks: CachedTask[]; cursor: string | undefined; } { const tasks = possibleTasks.filter((task) => task.status === "COMPLETED" && !task.noop); // Select tasks using greedy approach const tasksToRun: CachedTask[] = []; let remainingSize = maxSize; for (const task of tasks) { const cachedTask = prepareTaskForCaching(task); const size = calculateCachedTaskSize(cachedTask); if (size <= remainingSize) { tasksToRun.push(cachedTask); remainingSize -= size; } } return { tasks: tasksToRun, cursor: tasks.length > tasksToRun.length ? tasks[tasksToRun.length].id : undefined, }; } export function prepareTasksForCachingLegacy( possibleTasks: TaskForCaching[], maxSize: number ): { tasks: CachedTask[]; cursor: string | undefined; } { const tasks = possibleTasks.filter((task) => task.status === "COMPLETED"); // Prepare tasks and calculate their sizes const availableTasks = tasks.map((task) => { const cachedTask = prepareTaskForCaching(task); return { task: cachedTask, size: calculateCachedTaskSize(cachedTask) }; }); // Sort tasks in ascending order by size availableTasks.sort((a, b) => a.size - b.size); // Select tasks using greedy approach const tasksToRun: CachedTask[] = []; let remainingSize = maxSize; for (const { task, size } of availableTasks) { if (size <= remainingSize) { tasksToRun.push(task); remainingSize -= size; } } return { tasks: tasksToRun, cursor: undefined, }; } function prepareTaskForCaching(task: TaskForCaching): CachedTask { return { id: task.idempotencyKey, // We should eventually move this back to task.id status: task.status, idempotencyKey: task.idempotencyKey, noop: task.noop, output: task.outputIsUndefined ? undefined : (task.output as any), parentId: task.parentId, }; } function calculateCachedTaskSize(task: CachedTask): number { return JSON.stringify(task).length; } /** * * @param prisma An efficient query to get all task identifiers for a project. * It has indexes for fast performance. * It does NOT care about versions, so includes all tasks ever created. */ export function getAllTaskIdentifiers(prisma: PrismaClientOrTransaction, projectId: string) { return prisma.$queryRaw< { slug: string; triggerSource: TaskTriggerSource; }[] >` SELECT DISTINCT(slug), "triggerSource" FROM ${sqlDatabaseSchema}."BackgroundWorkerTask" WHERE "projectId" = ${projectId} ORDER BY slug ASC;`; }