search/app/composables/useDocumentDetailFetch.ts

81 lines
2.7 KiB
TypeScript

export interface DocumentDetailResult {
document: Record<string, unknown>
paragraphs: Record<string, unknown>[]
}
const MAX_ENTRIES = 25
function cacheKey(mainCollection: string, docId: string): string {
return `${mainCollection}:${docId}`
}
/**
* Fetch de "documento principal + sus párrafos" (join de Typesense), con
* caché de sesión compartida entre SearchPanel.vue y usePublicationFetch.ts
* — antes cada uno hacía la misma query por separado sin cachear nada, así
* que reabrir el mismo documento en la sesión repetía la ida y vuelta de red.
* Tope de ~25 entradas (documentos completos con cuerpo/párrafos pesan más
* que la metadata de useDocMetaCache) con eviction LRU simple.
*/
export function useDocumentDetailFetch() {
const cache = useState<Map<string, DocumentDetailResult>>('doc-detail-cache', () => new Map())
function getCached(mainCollection: string, docId: string): DocumentDetailResult | undefined {
const key = cacheKey(mainCollection, docId)
const entry = cache.value.get(key)
if (entry) {
cache.value.delete(key)
cache.value.set(key, entry)
}
return entry
}
function setCached(mainCollection: string, docId: string, detail: DocumentDetailResult) {
const key = cacheKey(mainCollection, docId)
cache.value.delete(key)
cache.value.set(key, detail)
if (cache.value.size > MAX_ENTRIES) {
const oldestKey = cache.value.keys().next().value
if (oldestKey !== undefined) cache.value.delete(oldestKey)
}
}
async function fetchDocumentDetail(
documentsApi: ReturnType<typeof useTypesenseClient>['documentsApi'],
mainCollection: string,
paragraphsCollection: string,
docId: string,
signal?: AbortSignal
): Promise<DocumentDetailResult | null> {
const cached = getCached(mainCollection, docId)
if (cached) return cached
const res = await documentsApi.multiSearch({
multiSearchParameters: {},
multiSearchSearchesParameter: {
searches: [{
collection: mainCollection,
q: '*',
queryBy: 'title',
filterBy: `id:=${docId} && $${paragraphsCollection}(id: *)`,
includeFields: `*, $${paragraphsCollection}(*)`
}]
}
}, { signal })
const hit = (res?.results?.[0] as { hits?: Array<{ document: Record<string, unknown> }> })?.hits?.[0]
if (!hit) return null
const docRaw = { ...hit.document }
const raw = docRaw[paragraphsCollection]
const paragraphs = (Array.isArray(raw) ? raw : (raw ? [raw] : [])) as Record<string, unknown>[]
delete docRaw[paragraphsCollection]
const detail: DocumentDetailResult = { document: docRaw, paragraphs }
setCached(mainCollection, docId, detail)
return detail
}
return { fetchDocumentDetail }
}