Database
Overview
Section titled “Overview”TheTerms uses Prisma 6 with PostgreSQL. The schema is defined in packages/db/prisma/schema.prisma. All database access goes through the Prisma client — no raw SQL.
Schema
Section titled “Schema”The core entities and their relationships:
Organisation ──┬── User (via Membership with Role) ├── Container │ └── Document │ └── DocumentVersion │ ├── Clause │ └── SigningRequest │ └── SigningResponse (per clause) ├── Invitation (team invites) └── ApiKeyRunning Migrations
Section titled “Running Migrations”For development (schema changes you’ll commit)
Section titled “For development (schema changes you’ll commit)”pnpm db:migrate --name describe-your-changeThis generates a SQL migration file in packages/db/prisma/migrations/. Always commit migration files with your PR.
For initial local setup
Section titled “For initial local setup”pnpm db:pushThis applies the schema directly to your local database. Suitable for first-time setup.
For production (inside Docker)
Section titled “For production (inside Docker)”Migrations run automatically on container startup via the docker-entrypoint.sh script, which calls:
prisma migrate deploy --schema=./packages/db/prisma/schema.prismaPrisma Studio
Section titled “Prisma Studio”To visually browse your local database:
cd packages/dbnpx prisma studioThis opens a web interface at http://localhost:5555.
Common Patterns
Section titled “Common Patterns”Double-cast for JSON fields
Section titled “Double-cast for JSON fields”Prisma’s Json type doesn’t overlap with specific TypeScript interfaces. When reading JSON fields, use a double-cast:
const content = document.content as unknown as DocumentContent;findUnique over findUniqueOrThrow
Section titled “findUnique over findUniqueOrThrow”Prefer findUnique + explicit TRPCError over findUniqueOrThrow for better error messages:
const container = await prisma.container.findUnique({ where: { id } });if (!container) throw new TRPCError({ code: "NOT_FOUND", message: "Container not found" });