import { getRegistry } from "yt-dlp-transcript-common/jobs/registry"; import { SYNC_OPERATION, operationLabel, } from "yt-dlp-transcript-common/lib/operations"; import type { SchedulerStatusPayload } from "yt-dlp-transcript-common/views/schedulerStatus"; import { buildSchedulerStatusPayload } from "../scheduler/status"; // THE SYNC ROW ON THE OPERATIONS RAIL, folded down to what a one-line row can // say. Sync is channel-scoped, so these are channel figures and there is no // band to draw — see SyncRailRow. The full per-channel schedule is the sync // operation's own page, /operations/sync. // // The row and that page's console are two readings of ONE scheduler payload, // which is why buildSyncRow takes an optional one: /operations/sync builds the // payload itself (the console needs the whole thing) and hands it here, so the // page reads it once rather than listing every channel config twice. export type SyncRowView = { // Off the descriptor, so the row's link and its name cannot drift from the // catalog the rest of the rail is built from. `label` travels as a prop // because the row is a client component and operations.ts reaches node:fs. id: string; label: string; enabled: boolean; // Channels the scheduler would sync on a cadence — not the channel count. eligible: number; // Eligible and past due right now. due: number; // Past due AND flagged overdue by the scheduler's own rule, which is the // stronger statement: a channel that keeps missing its window. overdue: number; // Sync jobs running or queued this instant, from the registry rather than // from the schedule — a manual "Sync all" is sync work too, and a board that // only counted the scheduler's own picks would read "Idle" while the box was // saturated. running: number; // 0 = no internal timer; ticks come from an external `pnpm sync:tick`. heartbeatSeconds: number; }; export async function buildSyncRow( // Absent on every page that wants only the row — the board and the non-sync // operation pages — where building the payload here is the whole cost. payload?: SchedulerStatusPayload, ): Promise { const status = payload ?? (await buildSchedulerStatusPayload()); const eligible = status.channels.filter((c) => c.autoSyncEligible); return { id: SYNC_OPERATION.id, label: operationLabel(SYNC_OPERATION.id), enabled: status.scheduler.enabled, eligible: eligible.length, due: eligible.filter( (c) => c.nextDueAt !== null && c.nextDueAt <= status.now, ).length, overdue: eligible.filter((c) => c.overdue).length, running: getRegistry() .list() .filter( (j) => j.kind === "sync" && (j.status === "running" || j.status === "queued"), ).length, heartbeatSeconds: status.heartbeatSeconds, }; }