jobs: queue scheduler + REST job API (phase 1c + 2)

Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
This commit is contained in:
2026-07-23 13:58:42 +00:00
co-authored by Claude Opus 4.8
parent 234a24ddaf
commit 6d7d928806
6 changed files with 172 additions and 24 deletions
+1
View File
@@ -77,6 +77,7 @@ export {
getPipelineJob,
updatePipelineJob,
getPipelineJobsForUser,
getOldestPendingJob,
markInterruptedJobs,
} from './queries/pipeline-jobs';
@@ -1,4 +1,4 @@
import { eq, and, inArray, desc } from 'drizzle-orm';
import { eq, asc, desc } from 'drizzle-orm';
import { db } from '../db';
import { pipelineJobs } from '../schema/pipeline-jobs';
import type { PipelineJobInsert } from '../types';
@@ -26,11 +26,24 @@ export async function getPipelineJobsForUser(userId: number, limit = 50) {
.limit(limit);
}
// Oldest queued job across everything (single-user → global queue). Used to promote the next job.
export async function getOldestPendingJob() {
const rows = await db
.select()
.from(pipelineJobs)
.where(eq(pipelineJobs.status, 'pending'))
.orderBy(asc(pipelineJobs.createdAt))
.limit(1);
return rows[0] ?? null;
}
// On restart, only RUNNING jobs are orphaned (their process died) → interrupted. PENDING jobs are the
// queue backlog and must survive to be promoted after startup.
export async function markInterruptedJobs() {
const result = await db
.update(pipelineJobs)
.set({ status: 'interrupted', completedAt: new Date() })
.where(inArray(pipelineJobs.status, ['pending', 'running']))
.where(eq(pipelineJobs.status, 'running'))
.returning({ id: pipelineJobs.id });
return result.length;
}