Files
fullstack-starter/AGENTS.md
T

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> (not bun <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. ClientuseSuspenseQuery(orpc.feature.list.queryOptions()) / useMutation(orpc.feature.create.mutationOptions())

Database (Drizzle ORM v1 beta)

  • Driver: drizzle-orm/postgres-js (NOT bun-sql) | Validation: drizzle-orm/zod (NOT drizzle-zod)
  • Relations: defineRelations() in src/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 (see src/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 push and migrate on 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 in src/env.ts
  • Server vars: no prefix | Client vars: VITE_ prefix required
  • Never commit .env files

Development Principles

  1. No backward compatibility — Rapid iteration. Always use latest API/patterns. No deprecated fallbacks.
  2. Always sync documentation — Code changes → immediately update AGENTS.md and related docs.
  3. 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

  1. 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