"use client"; import type { ChannelDigestsManifest, VideoDigest } from "../lib/digests"; import { manifestHasDigest } from "../lib/digests"; import { PromiseMap } from "../lib/archive/reader"; import { channelRef, readerFor } from "../lib/archive/readers"; import { idbGet, idbPut } from "./digestStore"; import { makeId, splitId } from "./originId"; // Fetch layer for the derived corpus, mirroring transcriptCache.ts: a memory // map, in-flight dedupe, manifest -> page resolution and opportunistic warming // of every record in a fetched page. // // The one structural difference is that THE LAYER IS SPARSE. A transcript // exists for every indexed video, so transcriptCache treats a missing one as an // error. Digests cover 102 of ~76,000 videos, so "absent" is the normal answer // and must be a value, not a throw: // // hasDigest() -> boolean, answered from the channel manifest alone // fetchDigest() -> VideoDigest | null, null meaning "not digested" // // A channel with no digests has no manifest at all, so its 404 is also a normal // answer and is cached as `null` — otherwise every video opened in an // undigested channel would re-request the same missing file. const resolved = new Map(); const inFlight = new Map>(); // PromiseMap memoises a RESOLVED value (including `null` — the cached negative // this layer depends on) and drops a REJECTION, which is exactly the policy // this file hand-rolled: absence is an answer worth keeping, a transport // failure is not proof of absence. const channelManifests = new PromiseMap(); const pagePromises = new PromiseMap(); // `id` is an OriginId: a bare "channelSlug/videoId" for same-origin content, or // "origin\tchannelSlug/videoId" for a federated cross-origin video. export function fetchDigest(id: string): Promise { const hit = resolved.get(id); if (hit !== undefined) return Promise.resolve(hit); const flying = inFlight.get(id); if (flying) return flying; const p = load(id).then((digest) => { resolved.set(id, digest); inFlight.delete(id); return digest; }); p.catch(() => { inFlight.delete(id); }); inFlight.set(id, p); return p; } // Does this video have a digest? Answered from the channel manifest, which is // one small cached request per channel — never a per-video fetch. This is what // gates the viewer's Digest control: with coverage at 0.1% of the corpus, an // always-present button would be a dead end almost everywhere. export async function hasDigest(id: string): Promise { const parts = parseId(id); if (!parts) return false; try { const manifest = await fetchChannelManifest( parts.channelSlug, parts.origin, ); return manifestHasDigest(manifest, parts.videoId); } catch { return false; } } function parseId( id: string, ): { origin: string; slug: string; channelSlug: string; videoId: string } | null { const { origin, slug } = splitId(id); const slashIdx = slug.indexOf("/"); if (slashIdx < 0) return null; return { origin, slug, channelSlug: slug.slice(0, slashIdx), videoId: slug.slice(slashIdx + 1), }; } // Resolves to null (not a rejection) when the channel ships no digests, so the // absence is cached like any other answer. function fetchChannelManifest( channelSlug: string, origin: string, ): Promise { return channelManifests.take(makeId(origin, channelSlug), () => readerFor(origin).readChannelDigestsManifest(channelSlug), ); } function fetchPage( channelSlug: string, pageIndex: number, origin: string, ): Promise { return pagePromises.take(`${makeId(origin, channelSlug)}:${pageIndex}`, () => readerFor(origin).digestPage(channelRef(channelSlug, origin), pageIndex), ); } async function load(id: string): Promise { const parts = parseId(id); if (!parts) return null; const { origin, slug, channelSlug, videoId } = parts; const manifest = await fetchChannelManifest(channelSlug, origin); if (!manifest) return null; const pageIndex = manifest.slugToPage[videoId]; // Not in slugToPage = not digested. The normal case, and not an error. if (pageIndex === undefined) return null; // The page's content hash is this record's cache version. Absent (an older // manifest) means every cached entry misses, which is the safe direction. const version = manifest.pageHashes?.[pageIndex] ?? ""; const stored = await idbGet(id, version); if (stored) return stored; const page = await fetchPage(channelSlug, pageIndex, origin); let found: VideoDigest | null = null; for (const entry of page) { // Re-key warmed entries by OriginId so a cross-origin channelSlug/videoId // can't shadow a same-origin one with the same slug. const entryId = makeId(origin, entry.slug); if (entry.slug === slug) found = entry; resolved.set(entryId, entry); idbPut(entryId, entry, version); } // In slugToPage but missing from the page means the manifest and the page // tree disagree — a real build fault, not a coverage gap, so it throws. if (!found) { throw new Error(`Digest ${slug} missing from page ${pageIndex}`); } return found; }