|
|
|
@@ -0,0 +1,163 @@
|
|
|
|
|
// The procedure contracts of the archive module: schemas and names, no handler and no port.
|
|
|
|
|
//
|
|
|
|
|
// The module is a client of the platform's Archive service (B-wave contract §3.14): documents, their
|
|
|
|
|
// versions and their retention live there, in the deployment's organization, and the bytes in the
|
|
|
|
|
// platform's Blob store. A read reads; a change is an `effect` (it calls an external service and writes
|
|
|
|
|
// nothing of this app's own). A version is made from a file the project holds, so a procedure names a
|
|
|
|
|
// `fileId`, never a hash or a Blob reference of the caller's. The service's int64 fields are their digits
|
|
|
|
|
// here, so what a page loads is JSON.
|
|
|
|
|
|
|
|
|
|
import { procedure } from "@conusai-kit/core"
|
|
|
|
|
import { z } from "zod"
|
|
|
|
|
import { ARCHIVE_PERMISSIONS, RETENTION_BASES } from "./constants.js"
|
|
|
|
|
|
|
|
|
|
const inProject = { org: z.string(), workspace: z.string(), project: z.string() }
|
|
|
|
|
const page = { after: z.string().max(128).optional(), limit: z.number().int().min(1).max(200).optional() }
|
|
|
|
|
/** What a form generates once per submission, so a retried request applies once. */
|
|
|
|
|
const requestId = z.string().uuid()
|
|
|
|
|
const documentId = z.string().min(1).max(64)
|
|
|
|
|
const fileId = z.string().uuid()
|
|
|
|
|
/** A revision as its digits: the service's are 64-bit. */
|
|
|
|
|
const revision = z.string().regex(/^\d{1,19}$/)
|
|
|
|
|
|
|
|
|
|
export const documentViewSchema = z.object({
|
|
|
|
|
documentId: z.string(),
|
|
|
|
|
title: z.string(),
|
|
|
|
|
/** An optional UNTDID 1001 document code. */
|
|
|
|
|
kind: z.string().nullable(),
|
|
|
|
|
retentionClass: z.string(),
|
|
|
|
|
legalHold: z.boolean(),
|
|
|
|
|
status: z.enum(["active", "disposed"]),
|
|
|
|
|
/** Whether the document is past changing: disposed, or erased. */
|
|
|
|
|
disposed: z.boolean(),
|
|
|
|
|
revision,
|
|
|
|
|
createdAt: z.string(),
|
|
|
|
|
lastVersionAt: z.string(),
|
|
|
|
|
disposedAt: z.string().nullable(),
|
|
|
|
|
})
|
|
|
|
|
export type DocumentView = z.output<typeof documentViewSchema>
|
|
|
|
|
|
|
|
|
|
export const versionViewSchema = z.object({
|
|
|
|
|
versionId: z.string(),
|
|
|
|
|
sequence: z.number().int(),
|
|
|
|
|
sha256: z.string(),
|
|
|
|
|
mediaType: z.string(),
|
|
|
|
|
/** Bytes, as digits. */
|
|
|
|
|
size: z.string(),
|
|
|
|
|
createdAt: z.string(),
|
|
|
|
|
})
|
|
|
|
|
export type VersionView = z.output<typeof versionViewSchema>
|
|
|
|
|
|
|
|
|
|
export const retentionClassViewSchema = z.object({
|
|
|
|
|
code: z.string(),
|
|
|
|
|
keepFor: z.string(),
|
|
|
|
|
from: z.enum(RETENTION_BASES),
|
|
|
|
|
createdAt: z.string(),
|
|
|
|
|
})
|
|
|
|
|
export type RetentionClassView = z.output<typeof retentionClassViewSchema>
|
|
|
|
|
|
|
|
|
|
/** A version's immutable facts, as the use cases take them from the stored file. */
|
|
|
|
|
export interface VersionFacts {
|
|
|
|
|
readonly sha256: string
|
|
|
|
|
readonly mediaType: string
|
|
|
|
|
readonly size: number
|
|
|
|
|
readonly blobRef: string
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
const receipt = z.object({ documentId: z.string(), revision })
|
|
|
|
|
export type DocumentReceipt = z.output<typeof receipt>
|
|
|
|
|
|
|
|
|
|
const documentPage = z.object({ rows: z.array(documentViewSchema), after: z.string().optional() })
|
|
|
|
|
|
|
|
|
|
export const documentList = procedure("archive.document.list")
|
|
|
|
|
.kind("read")
|
|
|
|
|
.scope("project")
|
|
|
|
|
.permission(ARCHIVE_PERMISSIONS.read)
|
|
|
|
|
.input(z.object({ ...inProject, q: z.string().max(200).optional(), ...page }))
|
|
|
|
|
.output(documentPage)
|
|
|
|
|
.contract()
|
|
|
|
|
|
|
|
|
|
export const documentGet = procedure("archive.document.get")
|
|
|
|
|
.kind("read")
|
|
|
|
|
.scope("project")
|
|
|
|
|
.permission(ARCHIVE_PERMISSIONS.read)
|
|
|
|
|
.input(z.object({ ...inProject, documentId }))
|
|
|
|
|
.output(z.object({ document: documentViewSchema, versions: z.array(versionViewSchema) }))
|
|
|
|
|
.contract()
|
|
|
|
|
|
|
|
|
|
export const documentRegister = procedure("archive.document.register")
|
|
|
|
|
.kind("effect")
|
|
|
|
|
.scope("project")
|
|
|
|
|
.permission(ARCHIVE_PERMISSIONS.write)
|
|
|
|
|
.input(
|
|
|
|
|
z.object({
|
|
|
|
|
...inProject,
|
|
|
|
|
title: z.string().trim().min(1).max(200),
|
|
|
|
|
kind: z
|
|
|
|
|
.string()
|
|
|
|
|
.regex(/^\d{1,4}$/)
|
|
|
|
|
.optional(),
|
|
|
|
|
retentionClass: z.string().min(1).max(64),
|
|
|
|
|
fileId,
|
|
|
|
|
requestId,
|
|
|
|
|
}),
|
|
|
|
|
)
|
|
|
|
|
.output(receipt)
|
|
|
|
|
.contract()
|
|
|
|
|
|
|
|
|
|
export const versionAdd = procedure("archive.version.add")
|
|
|
|
|
.kind("effect")
|
|
|
|
|
.scope("project")
|
|
|
|
|
.permission(ARCHIVE_PERMISSIONS.write)
|
|
|
|
|
.input(z.object({ ...inProject, documentId, revision, fileId, requestId }))
|
|
|
|
|
.output(receipt)
|
|
|
|
|
.contract()
|
|
|
|
|
|
|
|
|
|
export const documentHold = procedure("archive.document.hold")
|
|
|
|
|
.kind("effect")
|
|
|
|
|
.scope("project")
|
|
|
|
|
.permission(ARCHIVE_PERMISSIONS.manage)
|
|
|
|
|
.input(z.object({ ...inProject, documentId, revision, legalHold: z.boolean(), requestId }))
|
|
|
|
|
.output(receipt)
|
|
|
|
|
.contract()
|
|
|
|
|
|
|
|
|
|
export const documentDispose = procedure("archive.document.dispose")
|
|
|
|
|
.kind("effect")
|
|
|
|
|
.scope("project")
|
|
|
|
|
.permission(ARCHIVE_PERMISSIONS.manage)
|
|
|
|
|
.input(z.object({ ...inProject, documentId, revision, requestId }))
|
|
|
|
|
.output(receipt)
|
|
|
|
|
.contract()
|
|
|
|
|
|
|
|
|
|
export const retentionList = procedure("archive.retention.list")
|
|
|
|
|
.kind("read")
|
|
|
|
|
.scope("project")
|
|
|
|
|
.permission(ARCHIVE_PERMISSIONS.read)
|
|
|
|
|
.input(z.object({ ...inProject, ...page }))
|
|
|
|
|
.output(z.object({ rows: z.array(retentionClassViewSchema), after: z.string().optional() }))
|
|
|
|
|
.contract()
|
|
|
|
|
|
|
|
|
|
export const retentionCreate = procedure("archive.retention.create")
|
|
|
|
|
.kind("effect")
|
|
|
|
|
.scope("project")
|
|
|
|
|
.permission(ARCHIVE_PERMISSIONS.manage)
|
|
|
|
|
.input(
|
|
|
|
|
z.object({
|
|
|
|
|
...inProject,
|
|
|
|
|
code: z.string().regex(/^[A-Za-z0-9_-]{1,64}$/),
|
|
|
|
|
/** An ISO 8601 duration such as P10Y; the service reads it. */
|
|
|
|
|
keepFor: z.string().min(2).max(64),
|
|
|
|
|
from: z.enum(RETENTION_BASES),
|
|
|
|
|
requestId,
|
|
|
|
|
}),
|
|
|
|
|
)
|
|
|
|
|
.output(z.object({ code: z.string() }))
|
|
|
|
|
.contract()
|
|
|
|
|
|
|
|
|
|
export const disposalList = procedure("archive.disposal.list")
|
|
|
|
|
.kind("read")
|
|
|
|
|
.scope("project")
|
|
|
|
|
.permission(ARCHIVE_PERMISSIONS.read)
|
|
|
|
|
.input(z.object({ ...inProject, ...page }))
|
|
|
|
|
.output(documentPage)
|
|
|
|
|
.contract()
|