80 lines
2.7 KiB
TypeScript
80 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(
|
|
typesenseClient: ReturnType<typeof useTypesenseClient>,
|
|
mainCollection: string,
|
|
paragraphsCollection: string,
|
|
docId: string,
|
|
signal?: AbortSignal
|
|
): Promise<DocumentDetailResult | null> {
|
|
const cached = getCached(mainCollection, docId)
|
|
if (cached) return cached
|
|
|
|
const res = await typesenseClient.multiSearch.perform({
|
|
searches: [{
|
|
collection: mainCollection,
|
|
q: '*',
|
|
query_by: 'title',
|
|
filter_by: `id:=${docId} && $${paragraphsCollection}(id: *)`,
|
|
include_fields: `*, $${paragraphsCollection}(*)`,
|
|
use_cache: true,
|
|
cache_ttl: 3600
|
|
}]
|
|
}, {}, { abortSignal: 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 }
|
|
}
|