140 lines
4.7 KiB
TypeScript
140 lines
4.7 KiB
TypeScript
// 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<ProjectScope, "projectId">, 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<VersionFacts> {
|
|
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<Page<DocumentView>> {
|
|
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<DocumentReceipt> {
|
|
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<DocumentReceipt> {
|
|
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<DocumentReceipt> {
|
|
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<DocumentReceipt> {
|
|
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<Page<RetentionClassView>> {
|
|
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<Page<DocumentView>> {
|
|
return reads.listDisposalDue(paged(query))
|
|
}
|