// What the archive module does, as pure TypeScript over the port in ports.ts. // // Documents are the organization's: every project of the app sees the same ones (a multi-tenant app keeps // all its tenants' documents in its deployment's one organization; partitioning by app tenant is an open // owner decision, B-wave contract ยง11.4). A version is made from a file the project holds: the files // module's read is the membership check, so another project's or organization's file is simply not there, // and the facts a version records are the ones the store measured, never the caller's. import type { ProjectScope } from "@conusai-kit/core" import { type FilesDeps, getFileStorage } from "@conusai-kit/files" import { type DocumentReceipt, type DocumentView, PAGE_LIMIT, type RetentionClassView, type VersionFacts, type VersionView, } from "../contract/index.js" import type { ArchiveReads, ArchiveService, Page } from "./ports.js" /** The service's idempotency key of a request: one project's request id never answers another's. */ export const requestKey = (scope: Pick, requestId: string): string => `app-project:${scope.projectId}:${requestId}` const paged = (query: { after?: string; limit?: number }) => ({ ...(query.after === undefined ? {} : { after: query.after }), limit: query.limit ?? PAGE_LIMIT, }) /** The facts of a version made from a stored file. The reference names the file, and nothing else of it. */ export async function versionFacts(deps: FilesDeps, scope: ProjectScope, fileId: string): Promise { const { file } = await getFileStorage(deps, scope, fileId) return { sha256: file.sha256, mediaType: file.contentType, size: file.size, blobRef: `conusai://blob/files/${file.fileId}`, } } export function listDocuments( reads: ArchiveReads, query: { q?: string; after?: string; limit?: number }, ): Promise> { const q = query.q?.trim() return reads.listDocuments({ ...(q ? { q } : {}), ...paged(query) }) } export async function getDocument( reads: ArchiveReads, documentId: string, ): Promise<{ document: DocumentView; versions: VersionView[] }> { const document = await reads.getDocument(documentId) const versions = await reads.listVersions(documentId, { limit: PAGE_LIMIT }) return { document, versions: versions.rows } } export async function registerDocument( service: ArchiveService, files: FilesDeps, scope: ProjectScope, input: { title: string; kind?: string; retentionClass: string; fileId: string; requestId: string }, ): Promise { const kind = input.kind?.trim() return service.registerDocument({ title: input.title.trim(), ...(kind ? { kind } : {}), retentionClass: input.retentionClass, version: await versionFacts(files, scope, input.fileId), key: requestKey(scope, input.requestId), }) } export async function addVersion( service: ArchiveService, files: FilesDeps, scope: ProjectScope, input: { documentId: string; revision: string; fileId: string; requestId: string }, ): Promise { return service.addVersion({ documentId: input.documentId, revision: input.revision, version: await versionFacts(files, scope, input.fileId), key: requestKey(scope, input.requestId), }) } export function setLegalHold( service: ArchiveService, scope: ProjectScope, input: { documentId: string; revision: string; legalHold: boolean; requestId: string }, ): Promise { return service.setLegalHold({ documentId: input.documentId, revision: input.revision, legalHold: input.legalHold, key: requestKey(scope, input.requestId), }) } export function disposeDocument( service: ArchiveService, scope: ProjectScope, input: { documentId: string; revision: string; requestId: string }, ): Promise { return service.disposeDocument({ documentId: input.documentId, revision: input.revision, key: requestKey(scope, input.requestId), }) } export function listRetentionClasses( reads: ArchiveReads, query: { after?: string; limit?: number }, ): Promise> { return reads.listRetentionClasses(paged(query)) } export async function createRetentionClass( service: ArchiveService, scope: ProjectScope, input: { code: string; keepFor: string; from: RetentionClassView["from"]; requestId: string }, ): Promise<{ code: string }> { await service.createRetentionClass({ code: input.code, keepFor: input.keepFor.trim(), from: input.from, key: requestKey(scope, input.requestId), }) return { code: input.code } } export function listDisposalDue( reads: ArchiveReads, query: { after?: string; limit?: number }, ): Promise> { return reads.listDisposalDue(paged(query)) }