// The collections: list them with their document counts, create one. A collection is a named // bucket; its name is the slug every procedure takes, so a page never handles an id. import { conflict, notFound, validationFailed } from "@conusai-kit/core" import { type CollectionView, collections, documents } from "../contract/index.js" import type { KnowledgeDeps, KnowledgeScope } from "./ports.js" const PAGE = 200 export async function listCollections( deps: KnowledgeDeps, scope: KnowledgeScope, ): Promise<{ collections: CollectionView[] }> { const found = [] let cursor: string | undefined for (;;) { const page = await deps.data .forScope(scope) .of(collections) .list({ order: [{ field: "createdAt", direction: "asc" }], limit: PAGE, ...(cursor === undefined ? {} : { cursor }), }) found.push(...page.items) if (!page.hasMore) break cursor = page.cursor } const views: CollectionView[] = [] for (const row of found) { const counts = await documentCounts(deps, scope, row.name) views.push({ name: row.name, documentCount: counts.total, indexedCount: counts.indexed, createdBy: row.createdBy, }) } return { collections: views } } async function documentCounts( deps: KnowledgeDeps, scope: KnowledgeScope, collectionName: string, ): Promise<{ total: number; indexed: number }> { let total = 0 let indexed = 0 let cursor: string | undefined for (;;) { const page = await deps.data .forScope(scope) .of(documents) .list({ where: [{ field: "collectionName", op: "eq", value: collectionName }], limit: PAGE, ...(cursor === undefined ? {} : { cursor }), }) for (const row of page.items) { total += 1 if (row.status === "indexed") indexed += 1 } if (!page.hasMore) break cursor = page.cursor } return { total, indexed } } /** * Creates a collection, idempotently: a name that already exists answers with the existing * collection and writes nothing, so a retried request never audits twice. */ export async function createCollection( deps: KnowledgeDeps, scope: KnowledgeScope, name: string, ): Promise { const repos = deps.data.forScope(scope) const existing = (await repos.of(collections).list({ where: [{ field: "name", op: "eq", value: name }], limit: 1 })) .items[0] if (existing) { const counts = await documentCounts(deps, scope, name) return { name: existing.name, documentCount: counts.total, indexedCount: counts.indexed, createdBy: existing.createdBy, } } try { const row = await deps.data.unitOfWork.run(scope, async (tx) => { const inserted = await tx.of(collections).insert({ name, createdBy: scope.actor.userId }) return inserted }) return { name: row.name, documentCount: 0, indexedCount: 0, createdBy: row.createdBy } } catch (error) { // A concurrent create with the same name lost the race: answer the winner's row. if ((error as { status?: number }).status === 409) { const winner = (await repos.of(collections).list({ where: [{ field: "name", op: "eq", value: name }], limit: 1 })) .items[0] if (winner) { const counts = await documentCounts(deps, scope, name) return { name: winner.name, documentCount: counts.total, indexedCount: counts.indexed, createdBy: winner.createdBy, } } } throw error } } /** One collection by name, or NotFound: the resolver's own read is the membership check. */ export async function requireCollection(deps: KnowledgeDeps, scope: KnowledgeScope, name: string) { const row = ( await deps.data .forScope(scope) .of(collections) .list({ where: [{ field: "name", op: "eq", value: name }], limit: 1 }) ).items[0] if (!row) throw notFound("no such collection") return row } export const unknownCollection = (name: string): never => { throw validationFailed([{ path: "collectionName", message: "no such collection" }], `no collection ${name}`) } export const takenCollection = (): never => { throw conflict("this collection name is taken") }