Fix flaky document preview blob URL reuse

This commit is contained in:
Richard R 2026-05-28 04:23:12 -06:00
parent 495c197c4c
commit d59e911ca2

View file

@ -8,6 +8,7 @@ import { documentPreviewFallbackUrl, documentPreviewPresignUrl } from '@/lib/cli
const inMemoryPreviewUrlCache = new Map<string, string>(); const inMemoryPreviewUrlCache = new Map<string, string>();
const inFlightPreviewPrime = new Map<string, Promise<string | null>>(); const inFlightPreviewPrime = new Map<string, Promise<string | null>>();
const inFlightPersistedPreviewUrl = new Map<string, Promise<string | null>>();
const PREVIEW_CACHE_SCHEMA_VERSION = 4; const PREVIEW_CACHE_SCHEMA_VERSION = 4;
function revokeIfBlobUrl(url: string | null | undefined): void { function revokeIfBlobUrl(url: string | null | undefined): void {
@ -37,6 +38,7 @@ export function clearInMemoryDocumentPreviewCache(): void {
revokeIfBlobUrl(value); revokeIfBlobUrl(value);
} }
inMemoryPreviewUrlCache.clear(); inMemoryPreviewUrlCache.clear();
inFlightPersistedPreviewUrl.clear();
} }
export async function getPersistedDocumentPreviewUrl( export async function getPersistedDocumentPreviewUrl(
@ -44,6 +46,16 @@ export async function getPersistedDocumentPreviewUrl(
lastModified: number, lastModified: number,
cacheKey: string, cacheKey: string,
): Promise<string | null> { ): Promise<string | null> {
const cachedUrl = getInMemoryDocumentPreviewUrl(cacheKey);
if (cachedUrl) return cachedUrl;
const persistedKey = `${cacheKey}:${Number(lastModified)}`;
const existing = inFlightPersistedPreviewUrl.get(persistedKey);
if (existing) {
return existing;
}
const promise = (async (): Promise<string | null> => {
const row = await getDocumentPreviewCache(docId); const row = await getDocumentPreviewCache(docId);
if (!row) return null; if (!row) return null;
@ -57,6 +69,9 @@ export async function getPersistedDocumentPreviewUrl(
return null; return null;
} }
const latestCachedUrl = getInMemoryDocumentPreviewUrl(cacheKey);
if (latestCachedUrl) return latestCachedUrl;
const contentType = row.contentType || 'image/jpeg'; const contentType = row.contentType || 'image/jpeg';
const bytes = row.data; const bytes = row.data;
if (!(bytes instanceof ArrayBuffer) || bytes.byteLength === 0) { if (!(bytes instanceof ArrayBuffer) || bytes.byteLength === 0) {
@ -67,6 +82,16 @@ export async function getPersistedDocumentPreviewUrl(
const url = URL.createObjectURL(new Blob([bytes], { type: contentType })); const url = URL.createObjectURL(new Blob([bytes], { type: contentType }));
setInMemoryDocumentPreviewUrl(cacheKey, url); setInMemoryDocumentPreviewUrl(cacheKey, url);
return url; return url;
})();
inFlightPersistedPreviewUrl.set(persistedKey, promise);
try {
return await promise;
} finally {
if (inFlightPersistedPreviewUrl.get(persistedKey) === promise) {
inFlightPersistedPreviewUrl.delete(persistedKey);
}
}
} }
export async function primeDocumentPreviewCache( export async function primeDocumentPreviewCache(