Files
template-app/modules/knowledge/core/status.ts
T
2026-11-14 09:56:45 +00:00

50 lines
2.0 KiB
TypeScript

// The status a page reads: the module flag, the search service's readiness (separate from the
// indexing indicator, which is the per-document rows), and the document counts by progress state.
import { type DocumentStatus, documents, type KnowledgeRefusal } from "../contract/index.js"
import { type KnowledgeDeps, type KnowledgeReadiness, type KnowledgeScope, knowledgeEnabled } from "./ports.js"
const PAGE = 200
export interface KnowledgeStatus {
enabled: boolean
service: {
state: "registered" | "indexing" | "ready" | "degraded" | "disabled" | "unavailable"
reasons: string[]
refusal?: KnowledgeRefusal
}
documents: Record<DocumentStatus, number>
}
/** What the status read needs; `readiness` is absent in a process that binds no search backend. */
export type KnowledgeStatusDeps = Pick<KnowledgeDeps, "data" | "flags"> & {
readonly readiness?: KnowledgeReadiness | undefined
}
export async function knowledgeStatusOf(deps: KnowledgeStatusDeps, scope: KnowledgeScope): Promise<KnowledgeStatus> {
const counts: Record<DocumentStatus, number> = { queued: 0, indexing: 0, indexed: 0, failed: 0, cancelled: 0 }
let cursor: string | undefined
for (;;) {
const page = await deps.data
.forScope(scope)
.of(documents)
.list({ limit: PAGE, ...(cursor === undefined ? {} : { cursor }) })
for (const row of page.items) counts[row.status as DocumentStatus] += 1
if (!page.hasMore) break
cursor = page.cursor
}
const enabled = await knowledgeEnabled(deps, scope)
const readiness =
deps.readiness === undefined
? { state: "unavailable" as const, reasons: ["the knowledge search service is not bound in this process"] }
: await deps.readiness.readiness(scope).catch((error: unknown) => ({
state: "unavailable" as const,
reasons: [
error instanceof Error
? (error.message.split("\n")[0] ?? error.message)
: "the search service did not answer",
],
}))
return { enabled, service: readiness, documents: counts }
}