Architecture
Where each piece of the Cafébec platform lives and why.
Five apps, three shared packages, one database. Everything runs from a single VM behind Traefik.
Apps
apps/api — NestJS REST service. Every mutating request flows
through an @CurrentActor() guard that resolves either a Better Auth
session cookie or an Authorization: Bearer vnd_… API key into the
same Actor object. Controllers never care which track authenticated
the request; the audit log records both identically.
apps/web — Vite + React 19 + TanStack Router SPA. Reads the
API through its own fetch wrapper with credentials: 'include';
behind Traefik in prod, both land on *.cafebec.ca so the session
cookie is same-origin.
apps/mcp — Standalone @modelcontextprotocol/sdk HTTP server.
Each tool call goes through the REST API using a system API key
rather than touching Prisma directly — keeping business logic and
audit trail in one place.
apps/docs — This site. Fumadocs on TanStack Start, prerendered
to static HTML at build time, served by nginx. Bakes
api.cafebec.ca/openapi.json into the bundle at image build — no
runtime dependency on the API.
Shared packages
packages/schemas — Zod schemas. The contract across API, MCP
server, operator console. Types derive from schemas; no hand-written
TS types for API shapes.
packages/db — Prisma schema + migrations + seed. A GiST
exclusion constraint on machine_agreement prevents overlapping
active agreements in the same direction — the only Phase-1
constraint that lives in raw SQL instead of Prisma schema.
packages/settlement-engine — Pure functions for commission
and invoice math. No DB access. Every path is covered by
fixture-based tests to the cent. The API orchestrates the engine
with persistence; the engine stays framework-agnostic.
Database
Postgres 16 with pgvector, btree_gist, citext, pgcrypto
extensions. Same image in dev (via docker-compose) and prod (via the
deploy compose). Prisma owns migrations; the one raw-SQL migration
adds the agreement-overlap exclusion constraint.
Design principles
- Tests before features for the settlement engine and CSV import. The math and the import transforms are load-bearing; regressions are expensive.
- Schemas are the source of truth. No hand-written API types. Zod drives runtime validation + TS types + the OpenAPI spec.
- Pure logic in packages, side effects in apps. The settlement engine never touches a database.
- Every write is auditable. API, MCP, seed scripts — all go through the same audited service methods. No direct DB writes.
- Agent-friendly error design. Structured errors carry a
codeandnext_actions. An agent that fails should always know what to try next.