Documents
Polymorphic file storage. Mime-type-agnostic — stores PDFs, images, spreadsheets, anything. Owns both the documents row and the GCS object backing it.
Tables owned
| Table | Purpose |
|---|---|
org.documents | Polymorphic attachment rows. (entity_type, entity_id) link + file metadata (name, mime_type, size_bytes, gcs_path). |
Operations
Writes
create(conn, input, bytes)→Document. Uploadsbytesto GCS, inserts the row, returns the hydrated document.input:{ entityType, entityId, name, mimeType }. If the row insert fails, L2 deletes the GCS object before re-raising (atomic within the L2 op). If the L3 transaction rolls back aftercreatereturned successfully, the GCS object is orphaned and cleaned by a sweeper — out of scope here.pg'sPoolClientexposes no rollback callback, so L2 cannot observe L3's commit/rollback after returning.updateMetadata(conn, documentId, patch)→Document. Edits display name. Bytes are immutable — replacement = delete + create.delete(conn, documentId)→void. Deletes the row and the GCS object. (GCS delete is best-effort if the row deletion succeeds; orphan GCS objects are cleaned by a sweeper, out of scope here.)
Reads
getById(conn, documentId)→Document | null. Metadata only.getByIdWithSignedUrl(conn, documentId, ttlSeconds)→Document & { signedUrl: string } | null. For download.listByEntity(conn, entityType, entityId)→Document[]. Documents attached to one entity.listByEntities(conn, entityType, entityIds)→Map<entityId, Document[]>. Batch hydration (e.g., SO list with attachment counts).
Notes
- Component never resolves
entity_idback to its source table. Callers must pass a valid id. entity_typevalues are enum-controlled.- This component is consumed by L3 workflows that generate or accept files (PDF export workflows, user uploads, etc.). The component itself doesn't generate any content.