Skip to content

documents

Filed documents with title, category, issue and expiry date and one file, shown on the records they are linked to. A document is visible only through a linked record the viewer can read, and only while the record that owns its file is readable too, so linking never widens access to the file. Documents arrive through capture rules (a file saved into a configured field) or through the system-only import for migrations, which keeps the caller’s id, refuses an id another record already uses, and stamps createdAt with the import time (issuedAt carries the business date; the event store does not backdate); users link, unlink, edit and delete them. Read a record’s documents with for-record, count expired and expiring ones with expiry-overview, list them with an expiry state and an expiring filter with overview (the documents list screen), and add the documents tab to a record screen with documentsTab().

From recipes-documents — the smallest working mount:

// kumiko-feature-version: 1
// Documents Sample
//
// A `contract` entity with a file field becomes a document source without knowing about the
// documents feature: the capture rule below files every saved contract file as a document of
// category "contract" and links it to the contract and to its property. Who sees the document
// follows from who may read the linked records and the contract that owns the file.
import { createDocumentsFeature } from "@cosmicdrift/kumiko-bundled-features/documents";
import {
createEntity,
createFileField,
createTextField,
defineEntityCreateHandler,
defineEntityDetailHandler,
defineEntityListHandler,
defineEntityUpdateHandler,
defineFeature,
} from "@cosmicdrift/kumiko-framework/engine";
const text = () =>
createTextField({ personal: false, reason: "is_business_data", required: true, maxLength: 120 });
export const propertyEntity = createEntity({
table: "read_sample_documents_properties",
fields: { name: text() },
access: { read: { PropertyReader: "all", TenantAdmin: "all" } },
});
export const contractEntity = createEntity({
table: "read_sample_documents_contracts",
fields: { name: text(), propertyId: text(), file: createFileField() },
access: { read: { ContractReader: "all", TenantAdmin: "all" } },
});
const access = { roles: ["PropertyReader", "ContractReader", "TenantAdmin"] };
export const propertyManagement = defineFeature("property-management", (r) => {
r.requires({ features: ["files", "documents"] });
r.entity({ name: "property", ...propertyEntity });
r.entity({ name: "contract", ...contractEntity });
for (const [name, entity] of [
["property", propertyEntity],
["contract", contractEntity],
] as const) {
r.writeHandler(defineEntityCreateHandler(name, entity, { access }));
r.writeHandler(defineEntityUpdateHandler(name, entity, { access: { roles: ["TenantAdmin"] } }));
r.queryHandler(defineEntityListHandler(name, entity, { access }));
r.queryHandler(defineEntityDetailHandler(name, entity, { access }));
}
});
export const documents = createDocumentsFeature({
categories: ["contract", "insurance"],
linkTargets: ["property", "contract"],
access,
capture: [
{
entity: "contract",
field: "file",
category: "contract",
title: (row) => `Contract ${String(row["name"])}`,
links: (row) => [{ entityType: "property", entityId: String(row["propertyId"]) }],
},
],
});

📄 On GitHub: samples/recipes/documents/src/feature.ts

What this feature needs to run (Requires, top) and the write commands this feature provides (Provides, bottom).

flowchart TB
  n_documents["documents"]
  subgraph how_reqs["Requires"]
    n_files["files"]
  end
  subgraph how_provides["Provides"]
    n_cmd_documents_write_filed_document_create(["create"])
    n_cmd_documents_write_filed_document_delete(["delete"])
    n_cmd_documents_write_filed_document_update(["update"])
    n_cmd_documents_write_import(["import"])
    n_cmd_documents_write_link(["link"])
    n_cmd_documents_write_unlink(["unlink"])
  end
  n_files --> n_documents
  n_documents --> n_cmd_documents_write_filed_document_create
  n_documents --> n_cmd_documents_write_filed_document_delete
  n_documents --> n_cmd_documents_write_filed_document_update
  n_documents --> n_cmd_documents_write_import
  n_documents --> n_cmd_documents_write_link
  n_documents --> n_cmd_documents_write_unlink

Provides — write commands this feature registers (dispatch them through the command bus):

Start with recipes-documents for a step-by-step walkthrough with runnable code and integration tests.

  • Requires: files
  • Activation: always on (not toggleable)