import { NextResponse } from "next/server"; import { getPaths } from "yt-dlp-transcript-common/lib/paths"; import { computePulse, type PulsePayload, } from "yt-dlp-transcript-common/views/pulse"; import { observeInputs } from "../../lib/liveInputs"; import { cleanableTotalBytes } from "../../cleanup/lib/loadCleanup"; // The change token the global AutoRefresh polls instead of blindly re-rendering // the whole page tree every 5 seconds. The token itself is // `common/views/pulse.ts`; `observeInputs()` is the observer's reads. // // ⚠️ THIS VIEW OBSERVES; IT MUST NEVER CONSTRUCT. That is now a property of // the two functions it calls rather than a rule this file remembers — see the // header of either one for the ~16 flaky specs that bought it, and // `VIEW_CONTRACT.pulse` for the same promise stated where the dispatcher can // be read against it. `views.test.ts` re-checks it as text. // // Everything on the idle path is either in-memory or a stat(). NO readdir, no // corpus contact, no large JSON parse — asserted by e2e/pulse.spec.ts, because // the entire point of this endpoint is that it is cheap enough to poll forever. // // It is the only view handler that reads the request: `?rev=` is the client's // last token, and the fast path below is what makes an idle editor nearly free. // The rewrite from `/api/pulse` is server-internal, so that query string // arrives here unchanged. export async function pulseView(request: Request): Promise { const known = new URL(request.url).searchParams.get("rev"); const { rev, activeJobs, runningJobs, busy } = computePulse(observeInputs()); // Idle fast path. The client already has this rev, so nothing it displays can // have changed — skip the only part of this endpoint that touches disk. if (known && known === rev) { return NextResponse.json({ rev, changed: false, activeJobs, runningJobs, cleanableBytes: -1, // not recomputed; the client keeps its last value busy, } satisfies PulsePayload); } // Something moved: it's worth the ~40 ms to refresh the badge value too. // (Before the corpus walk was removed this number cost ~4.4 s, which is // precisely why it must never be on the idle path.) const cleanableBytes = await cleanableTotalBytes(getPaths()); return NextResponse.json({ rev, changed: true, activeJobs, runningJobs, cleanableBytes, busy, } satisfies PulsePayload); }