import type { FileDiffMetadata } from "@pierre/diffs"; import { streamPatchInWorker } from "$lib/components/repo/diffRpc"; /** * A patch being parsed in the worker while it downloads. Starting one does not * wait for anything else on the page: the file list and the patch describe the * same commit, and fetching them one after the other costs the sum of both. */ export interface PatchStream { /** parsed files, keyed by current name and by previous name for renames */ readonly byName: Map; /** called with each batch as it lands, and unsubscribed on teardown */ subscribe: (listener: (files: FileDiffMetadata[]) => void) => () => void; /** false when the patch could not be read, so a caller can fall back */ readonly done: Promise; stop: () => void; } // Dedupes in-flight patch streams by URL. const live = new Map(); const KEEP = 2; export const getPatchStream = (url: string): PatchStream => { const held = live.get(url); if (held) return held; const stream = startPatchStream(url); live.set(url, stream); // a navigation leaves its patch behind; keeping the last couple means going // back to a commit is instant, and anything older is dropped for (const [key, value] of live) { if (live.size <= KEEP) break; value.stop(); live.delete(key); } return stream; }; export const startPatchStream = (url: string): PatchStream => { const byName = new Map(); const listeners = new Set<(files: FileDiffMetadata[]) => void>(); const controller = new AbortController(); let stopped = false; const land = (files: FileDiffMetadata[]) => { if (stopped) return; for (const file of files) { byName.set(file.name, file); if (file.prevName) byName.set(file.prevName, file); } for (const listener of listeners) listener(files); }; const stream: PatchStream = { byName, subscribe: (listener) => { listeners.add(listener); return () => listeners.delete(listener); }, // resolved either way: a caller that never asks cannot leave a rejection // unhandled, and one that does gets an answer instead of an exception done: streamPatchInWorker(url, land, controller.signal).then( () => true, () => false ), stop: () => { stopped = true; listeners.clear(); controller.abort(); } }; return stream; };