Something went wrong. Try again.
Monorepo for Tangled tangled.org
Something went wrong. Try again.
123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100import 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<string, FileDiffMetadata>; /** 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<boolean>; readonly lines: number; readonly subscribed: boolean; stop: () => void;}
// Dedupes in-flight patch streams by URL.const live = new Map<string, PatchStream>();const KEEP = 2;const MAX_RETAINED_LINES = 20_000;
// a navigation leaves its patch behind; keeping the last couple means going// back to a commit is instant, and anything older is droppedconst trim = () => { for (;;) { const evictable = [...live].filter( ([, stream], index, all) => index < all.length - 1 && !stream.subscribed ); if (!evictable.length) return; let victim: [string, PatchStream] | undefined; if (live.size > KEEP) victim = evictable[0]; else if ( [...live.values()].reduce((sum, stream) => sum + stream.lines, 0) > MAX_RETAINED_LINES ) victim = evictable.reduce((a, b) => (b[1].lines > a[1].lines ? b : a)); if (!victim) return; victim[1].stop(); live.delete(victim[0]); }};
export const getPatchStream = (url: string): PatchStream => { let stream = live.get(url); if (stream) live.delete(url); else stream = startPatchStream(url, trim); live.set(url, stream); trim(); return stream;};
export const startPatchStream = (url: string, onGrow?: () => void): PatchStream => { const byName = new Map<string, FileDiffMetadata>(); const listeners = new Set<(files: FileDiffMetadata[]) => void>(); const controller = new AbortController(); let stopped = false; let lines = 0;
const land = (files: FileDiffMetadata[]) => { if (stopped) return; for (const file of files) { lines += file.unifiedLineCount; byName.set(file.name, file); if (file.prevName) byName.set(file.prevName, file); } for (const listener of listeners) listener(files); onGrow?.(); };
const stream: PatchStream = { byName, get lines() { return lines; }, get subscribed() { return listeners.size > 0; }, 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;};