Conventions
TypeScript, formatting, import order, component design, and dependency rules enforced across the Aurea monorepo.
TypeScript
- Strict mode is on in every package (
"strict": truevia@repo/typescript-config/base.json). anyis forbidden. Useunknownand narrow, or reach for a proper generic.- All configs extend
@repo/typescript-config—base.json,nextjs.json, orreact-library.json. tsc --noEmit(ornext typegen && tsc --noEmitfor Next.js apps) is a required CI step.
Formatting — Prettier
All files are formatted by Prettier with these settings (.prettierrc.js):
module.exports = {
semi: false,
singleQuote: true,
trailingComma: 'es5',
tabWidth: 2,
printWidth: 100,
endOfLine: 'auto',
}Formatting is enforced in CI (bun run format:check) and applied automatically by lint-staged
on every commit via Husky.
Import ordering
Imports are sorted in three groups, separated by blank lines:
- Node built-ins (
node:fs,node:path, …) - External packages (npm — alphabetical within group)
- Internal / workspace paths (
@/*,@repo/*, relative./)
Example:
import { readFileSync } from 'node:fs'
import { Injectable } from '@nestjs/common'
import { z } from 'zod'
import { CasesService } from '@/modules/cases/cases.service'
import { toMcpContent } from './helpers'The ESLint import/order rule (via @repo/eslint-config/base) enforces this automatically.
Component design — atomic design
@repo/ui follows a strict atomic design hierarchy:
| Level | Path | Examples |
|---|---|---|
| Atoms | components/atoms/ | Button, Badge, Input |
| Molecules | components/molecules/ | FormField, SearchBar |
| Organisms | components/organisms/ | Header, DataTable |
Apps consume components via the @repo/ui workspace package — never by copying files.
API auth & tenancy
- Auth-by-default. Every GraphQL query/mutation and the
/mcpendpoint require a verified Clerk bearer token (ClerkAuthGuard). Agency-only operations add@Roles('agency_member')+RolesGuard. The guard memoizes the localusersrow on the request (req.dbUser) - one indexed query, no Clerk API round-trip. - Tenancy scoping is server-side. Services build their Drizzle
wherewithscopeByRole(dbUser, { clientField }): agency members see all rows, clients only their own. The SPA never filters by role. - Subscriptions use
graphql-wson the same/graphqlpath; the socket authenticates viaconnectionParams.Authorizationat connect time and is closed4403otherwise. - Enums follow the three-sided pattern: canonical
constarray in@repo/shared/constants, compile-checkedpgEnumtuple in the Drizzle schema, and a const-object GraphQL enum. The API imports@repo/sharedtypes only (import type) - its swc/node runtime cannot load the package's raw.tssources.
File storage (S3-presigned)
- Metadata in Postgres, bytes in S3-compatible storage. Confidential client files (Document Center) never touch the public Payload CMS and are never proxied through the API process.
- Authorize-before-presign. The API is the single authorization point: it role/ownership
checks the caller, then mints a short-lived (~5 min) presigned URL (
S3Service); the browser PUTs/GETs the bytes straight to the bucket. Object keys are server-generated (clients/<clientId>/<documentId>/<sanitized-name>) and never exposed via GraphQL. - S3 is the source of truth for uploads.
confirmUploadverifies the landed object viaHeadObject(size/mime) instead of trusting client-reported values; violating objects are deleted and the row never flips touploaded.
Commit messages
Commits follow Conventional Commits (feat:, fix:, chore:, docs:, etc.) enforced by
commitlint + Husky.
Dependency direction
| Allowed | Forbidden |
|---|---|
apps/* → packages/* | packages/* → apps/* |
packages/ui → packages/shared | packages/shared → packages/ui |
anything → packages/tooling/* (config) | packages/tooling/* → runtime packages |
Turborepo's dependsOn config mirrors this graph so that build order is always correct.