ConusAI vendor v0.1.0-beta.114
ci / gate (push) Waiting to run

This commit is contained in:
ConusAI vendor v0.1.0-beta.114
2026-11-14 09:56:45 +00:00
commit 1ce8d23c35
867 changed files with 122104 additions and 0 deletions
+139
View File
@@ -0,0 +1,139 @@
// 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))
}
+66
View File
@@ -0,0 +1,66 @@
// The port the archive module's use cases are written against: the platform's Archive service, in this
// module's own words. adapters/service.ts is the one place that knows `@conusai-sdk/archive` exists, and
// it turns every refusal into the kit's typed app error (`businessAppError`) before core sees it.
import { token } from "@conusai-kit/core"
import type { DocumentReceipt, DocumentView, RetentionClassView, VersionFacts, VersionView } from "../contract/index.js"
export interface Page<T> {
readonly rows: T[]
readonly after?: string
}
/** What a `read` procedure may ask the service: nothing that changes a row. */
export interface ArchiveReads {
listDocuments(query: { q?: string; after?: string; limit: number }): Promise<Page<DocumentView>>
getDocument(documentId: string): Promise<DocumentView>
listVersions(documentId: string, query: { after?: string; limit: number }): Promise<Page<VersionView>>
listRetentionClasses(query: { after?: string; limit: number }): Promise<Page<RetentionClassView>>
listDisposalDue(query: { after?: string; limit: number }): Promise<Page<DocumentView>>
}
export interface ArchiveService extends ArchiveReads {
registerDocument(input: {
title: string
kind?: string
retentionClass: string
version: VersionFacts
key: string
}): Promise<DocumentReceipt>
addVersion(input: {
documentId: string
revision: string
version: VersionFacts
key: string
}): Promise<DocumentReceipt>
setLegalHold(input: {
documentId: string
revision: string
legalHold: boolean
key: string
}): Promise<DocumentReceipt>
disposeDocument(input: { documentId: string; revision: string; key: string }): Promise<DocumentReceipt>
createRetentionClass(input: {
code: string
keepFor: string
from: RetentionClassView["from"]
key: string
}): Promise<void>
}
/** The read view a `read` or `write` handler receives: the kind is a capability, not a label. */
export const archiveReadsOf = (service: ArchiveReads): ArchiveReads => ({
listDocuments: (query) => service.listDocuments(query),
getDocument: (documentId) => service.getDocument(documentId),
listVersions: (documentId, query) => service.listVersions(documentId, query),
listRetentionClasses: (query) => service.listRetentionClasses(query),
listDisposalDue: (query) => service.listDisposalDue(query),
})
/**
* Bound by a composition root or a test that brings its own client (another organization, the
* business-services fake). With nothing bound, the module calls this deployment's service.
*/
export const ARCHIVE_SERVICE = token<ArchiveService>("archive.service", {
views: { read: archiveReadsOf, write: archiveReadsOf },
})