import { useCallback, useEffect, useMemo, useState } from "react"; import { referenceOf, useCaminoQuery, type ExactReference } from "@quixos/camino-react"; import { queries } from "./gen/client.js"; import { asInterface, type JobNode } from "./model.js"; import { mergeCachedAnnotationEntries } from "./work.js"; type Entry = { node: JobNode }; /** Successes outlive request history. Read every retained page instead of * treating the first bounded graph query as the entire semantic cache. */ export function useCachedAnnotations(reference?: ExactReference | null) { const query = useCaminoQuery(queries.DocumentView, { enabled: !!reference, root: reference ? asInterface(reference, "document") : undefined, input: {}, admission: { query: queries.DocumentInputs, input: {} }, }); const [pages, setPages] = useState<{ object: string; entries: Record }>({ object: "", entries: {}, }); const report = useCallback( (cursor: string, entries: readonly Entry[] | null) => { const object = reference?.object ?? ""; setPages((previous) => { const next = previous.object === object ? { ...previous.entries } : {}; if (entries) next[cursor] = entries; else delete next[cursor]; return { object, entries: next }; }); }, [reference?.object], ); const node = query.status === "ready" && referenceOf(query.data.root).object === reference?.object ? query.data.root : null; const entries = useMemo( () => mergeCachedAnnotationEntries( [ ...(node?.results.entries ?? []), ...(pages.object === reference?.object ? Object.values(pages.entries).flat() : []), ], node?.jobs.entries ?? [], ), [node, pages, reference?.object], ); return { ready: !!node, entries, continuation: node?.results.pageInfo.hasNextPage && node.results.pageInfo.endCursor ? ( ) : null, }; } function CachedResultsTail({ reference, cursor, report, }: { reference: ExactReference; cursor: string; report(cursor: string, entries: readonly Entry[] | null): void; }) { const query = useCaminoQuery(queries.CachedResultsView, { root: asInterface(reference, "document"), input: { after: cursor }, admission: { query: queries.CachedResultsInputs, input: { after: cursor } }, }); const results = query.status === "ready" ? query.data.root.results : null; useEffect(() => { if (results) report(cursor, results.entries); return () => report(cursor, null); }, [results, cursor, report]); return results?.pageInfo.hasNextPage && results.pageInfo.endCursor ? ( ) : null; }