6.2 KiB
AGENTS.md - AI Coding Agent Guidelines
Project Overview
This project uses Bun exclusively. Do NOT use Node.js / npm / yarn / pnpm. Always use
bun run <script>(notbun <script>) to avoid conflicts with Bun built-in subcommands.
- Framework: TanStack Start (React 19 SSR, file-based routing)
- Runtime/PM: Bun (see
mise.toml) — NOT Node.js / npm / yarn / pnpm - Language: TypeScript (strict mode) | Styling: Tailwind CSS v4
- Database: PostgreSQL + Drizzle ORM v1 beta (
drizzle-orm/postgres-js, RQBv2) - State: TanStack Query v5 | RPC: ORPC (contract-first) | Build: Vite + Nitro
Commands
bun run dev # Vite dev server (localhost:3000)
bun run build # Production build → .output/
bun run compile # Compile to standalone binary (current platform)
bun run fix # Biome auto-fix (lint + format)
bun run typecheck # TypeScript check
bun run db:push # Push schema to dev DB (dev only, fast iteration)
bun run db:generate # Generate migration SQL files (for production)
bun run db:migrate # Apply migrations (production deployment)
bun run db:studio # Drizzle Studio GUI
bun test path/to/test.ts # Run single test (not yet configured)
Code Style
Formatting (Biome): 2-space indent, LF, single quotes, no semicolons, always arrow parens (x) => x
Imports — Biome auto-organizes: 1) External → 2) @/* aliases → 3) import type
import { createFileRoute } from '@tanstack/react-router'
import { db } from '@/server/db'
import type { ReactNode } from 'react'
TypeScript: strict: true, noUncheckedIndexedAccess, verbatimModuleSyntax. Use @/* path aliases (→ src/*).
Naming: Files (utils): kebab-case.ts | Files (components): PascalCase.tsx | Components: const Button = () => {} | Functions: camelCase | Constants: UPPER_SNAKE | Types: PascalCase
React: Arrow function components (Biome enforced). Routes: export const Route = createFileRoute(...). Data: useSuspenseQuery(orpc.feature.list.queryOptions())
Errors: try-catch for async; ORPCError with codes (NOT_FOUND, INPUT_VALIDATION_FAILED). Never empty catch blocks.
ORPC Pattern
1. Contract (src/server/api/contracts/feature.contract.ts) — Zod schemas for input/output
2. Router (src/server/api/routers/feature.router.ts) — Handler implementations using os.feature.list.use(db).handler(...)
3. Register — Export from contracts/index.ts and routers/index.ts
4. Client — useSuspenseQuery(orpc.feature.list.queryOptions()) / useMutation(orpc.feature.create.mutationOptions())
Database (Drizzle ORM v1 beta)
- Driver:
drizzle-orm/postgres-js(NOTbun-sql) | Validation:drizzle-orm/zod(NOTdrizzle-zod) - Relations:
defineRelations()insrc/server/db/relations.ts(RQBv2 —drizzle()only needs{ relations }) - Query style: RQBv2 object syntax —
orderBy: { createdAt: 'desc' },where: { id: 1 } - Schema: All tables must include
id(UUIDv7),createdAt,updatedAt(seesrc/server/db/fields.ts)
Database Workflow
- Local dev:
db:push— fast iteration, no migration files needed - Production:
db:generate→ review SQL →db:migrate— versioned, auditable - Never mix
pushandmigrateon the same database instance drizzle/migration directory is committed to Git
⚠️ drizzle.config.ts Caveat
drizzle.config.ts runs outside Vite — @/* path aliases do not work. Use relative imports (./src/env).
Environment Variables
- Validated via
@t3-oss/env-core+ Zod insrc/env.ts - Server vars: no prefix | Client vars:
VITE_prefix required - Never commit
.envfiles
Development Principles
- No backward compatibility — Rapid iteration. Always use latest API/patterns. No deprecated fallbacks.
- Always sync documentation — Code changes → immediately update
AGENTS.mdand related docs. - Forward-only migration — Fully adopt new APIs when upgrading. No mixing old/new patterns.
Critical Rules
DO: bun run fix before committing | @/* path aliases | createdAt/updatedAt on all tables | ORPCError with proper codes | drizzle-orm/zod for validation | RQBv2 object syntax | Update docs with code changes
DON'T: npm/npx/node/yarn/pnpm | Edit routeTree.gen.ts | as any/@ts-ignore/@ts-expect-error | Commit .env | Empty catch blocks | Import from drizzle-zod | RQBv1 callback-style API | drizzle-orm/bun-sql driver | Pass schema to drizzle() | Import os from @orpc/server in middleware (use @/server/api/server) | Leave docs out of sync
Git Workflow
- Make changes → 2.
bun run fix→ 3.bun run typecheck→ 4.bun run dev(test) → 5. Commit
Directory Structure
src/
├── client/orpc.ts # ORPC client + TanStack Query utils
├── components/ # React components
├── routes/ # TanStack Router file routes
│ ├── __root.tsx # Root layout
│ └── api/ # API routes (OpenAPI, health, RPC)
├── server/
│ ├── api/
│ │ ├── contracts/ # ORPC input/output schemas (Zod)
│ │ ├── routers/ # ORPC handler implementations
│ │ ├── middlewares/ # Middleware (db, auth)
│ │ ├── interceptors.ts # Shared error interceptors
│ │ ├── context.ts # Request context
│ │ ├── server.ts # ORPC server instance
│ │ └── types.ts # Type exports
│ └── db/
│ ├── schema/ # Drizzle table definitions
│ ├── fields.ts # Shared field builders
│ ├── relations.ts # Drizzle relations (RQBv2)
│ └── index.ts # Database instance
├── env.ts # Environment variable validation
├── router.tsx # Router configuration
├── routeTree.gen.ts # Auto-generated (DO NOT EDIT)
└── styles.css # Tailwind entry