Architecture
How the monorepo is laid out — apps, packages, and what touches what.
Layout
apps/
web/ # Next.js 16 — marketing + auth screens
admin/ # Next.js 16 — auth-gated dashboard
api/ # Elysia.js — REST API on Bun
worker/ # Bun process — BullMQ drainer
mcp/ # MCP server — generated from OpenAPI
docs/ # Fumadocs site (this app)
e2e/ # Playwright tests
packages/
auth/ # Better Auth (server + client + admin plugin, RBAC)
db/ # Drizzle ORM, schema, migrations, audit log helper
cache/ # ioredis client + CacheService
jobs/ # BullMQ queue + worker registration + dispatch helper
ai/ # Vercel AI SDK chat + RAG (pgvector)
storage/ # Cloudflare R2 / S3 via Bun.S3Client
email/ # React Email templates + render
analytics/ # PostHog browser provider + consent
ui/ # shadcn/ui primitives + Ladle stories
entities/ # Shared zod schemas + react-query hooks
env/ # Type-safe env (@t3-oss/env-core / nextjs)
logger/ # LogTape category loggers
eslint-config/
typescript-config/Dependency rules
- App code reaches into packages, never sideways into other apps.
packages/dbis the only place that talks to Postgres.packages/cacheis the only place that talks to Redis.packages/storageis the only place that talks to R2.packages/authowns session shape; everything else reads it throughauth-context.ts.
User scoping
The base product is single-user: every resource is owned by the user who created it. Resource tables each carry an owning-user column — posts.author_id, files.owner_id, subscriptions.user_id, embeddings.user_id, webhook_endpoints.user_id. Route handlers scope queries to the authenticated user's id; any route that reads across users (e.g. /search) is gated by admin RBAC, not by a userId filter alone.
There is no organization / multi-tenancy layer in the base. It ships as the restorable multitenancy skill (.claude/skills/multitenancy), which reintroduces org/member/invitation tables, session.activeOrganizationId, and org-scoped resources.