214 lines
6.6 KiB
Markdown
214 lines
6.6 KiB
Markdown
# AGENTS.md - Server App Guidelines
|
|
|
|
TanStack Start fullstack web app with ORPC (contract-first RPC).
|
|
|
|
## Tech Stack
|
|
|
|
> **⚠️ This project uses Bun — NOT Node.js / npm. All commands use `bun`. Never use `npm`, `npx`, or `node`.**
|
|
|
|
- **Framework**: TanStack Start (React 19 SSR, file-based routing)
|
|
- **Runtime**: Bun — **NOT Node.js**
|
|
- **Package Manager**: Bun — **NOT npm / yarn / pnpm**
|
|
- **Language**: TypeScript (strict mode)
|
|
- **Styling**: Tailwind CSS v4
|
|
- **Database**: PostgreSQL + Drizzle ORM
|
|
- **State**: TanStack Query v5
|
|
- **RPC**: ORPC (contract-first, type-safe)
|
|
- **Build**: Vite + Nitro
|
|
|
|
## Commands
|
|
|
|
```bash
|
|
# Development
|
|
bun dev # Vite dev server (localhost:3000)
|
|
bun db:studio # Drizzle Studio GUI
|
|
|
|
# Build
|
|
bun build # Production build → .output/
|
|
bun compile # Compile to standalone binary (current platform, depends on build)
|
|
bun compile:darwin # Compile for macOS (default: arm64)
|
|
bun compile:darwin:arm64 # Compile for macOS arm64
|
|
bun compile:darwin:x64 # Compile for macOS x64
|
|
bun compile:linux # Compile for Linux (default: x64)
|
|
bun compile:linux:arm64 # Compile for Linux arm64
|
|
bun compile:linux:x64 # Compile for Linux x64
|
|
bun compile:windows # Compile for Windows (default: x64)
|
|
bun compile:windows:x64 # Compile for Windows x64
|
|
|
|
# Code Quality
|
|
bun fix # Biome auto-fix
|
|
bun typecheck # TypeScript check
|
|
|
|
# Database
|
|
bun db:generate # Generate migrations from schema
|
|
bun db:migrate # Run migrations
|
|
bun db:push # Push schema directly (dev only)
|
|
|
|
# Testing (not yet configured)
|
|
bun test path/to/test.ts # Run single test
|
|
bun test -t "pattern" # Run tests matching pattern
|
|
```
|
|
|
|
## Directory Structure
|
|
|
|
```
|
|
src/
|
|
├── client/ # Client-side code
|
|
│ ├── orpc.client.ts # ORPC isomorphic client
|
|
│ └── query-client.ts # TanStack Query client
|
|
├── components/ # React components
|
|
├── routes/ # TanStack Router file routes
|
|
│ ├── __root.tsx # Root layout
|
|
│ ├── index.tsx # Home page
|
|
│ └── api/
|
|
│ └── rpc.$.ts # ORPC HTTP endpoint
|
|
├── server/ # Server-side code
|
|
│ ├── api/ # ORPC layer
|
|
│ │ ├── contracts/ # Input/output schemas (Zod)
|
|
│ │ ├── middlewares/ # Middleware (db provider, auth)
|
|
│ │ ├── routers/ # Handler implementations
|
|
│ │ ├── context.ts # Request context
|
|
│ │ ├── server.ts # ORPC server instance
|
|
│ │ └── types.ts # Type exports
|
|
│ └── db/
|
|
│ ├── schema/ # Drizzle table definitions
|
|
│ └── 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
|
|
```
|
|
|
|
## ORPC Pattern
|
|
|
|
### 1. Define Contract (`src/server/api/contracts/feature.contract.ts`)
|
|
```typescript
|
|
import { oc } from '@orpc/contract'
|
|
import { createSelectSchema } from 'drizzle-zod'
|
|
import { z } from 'zod'
|
|
import { featureTable } from '@/server/db/schema'
|
|
|
|
const selectSchema = createSelectSchema(featureTable)
|
|
|
|
export const list = oc.input(z.void()).output(z.array(selectSchema))
|
|
export const create = oc.input(insertSchema).output(selectSchema)
|
|
```
|
|
|
|
### 2. Implement Router (`src/server/api/routers/feature.router.ts`)
|
|
```typescript
|
|
import { ORPCError } from '@orpc/server'
|
|
import { db } from '../middlewares'
|
|
import { os } from '../server'
|
|
|
|
export const list = os.feature.list.use(db).handler(async ({ context }) => {
|
|
return await context.db.query.featureTable.findMany()
|
|
})
|
|
```
|
|
|
|
### 3. Register in Index Files
|
|
```typescript
|
|
// src/server/api/contracts/index.ts
|
|
import * as feature from './feature.contract'
|
|
export const contract = { feature }
|
|
|
|
// src/server/api/routers/index.ts
|
|
import * as feature from './feature.router'
|
|
export const router = os.router({ feature })
|
|
```
|
|
|
|
### 4. Use in Components
|
|
```typescript
|
|
import { useSuspenseQuery, useMutation } from '@tanstack/react-query'
|
|
import { orpc } from '@/client/orpc.client'
|
|
|
|
const { data } = useSuspenseQuery(orpc.feature.list.queryOptions())
|
|
const mutation = useMutation(orpc.feature.create.mutationOptions())
|
|
```
|
|
|
|
## Database Schema (Drizzle)
|
|
|
|
```typescript
|
|
import { pgTable, text, timestamp, uuid } from 'drizzle-orm/pg-core'
|
|
import { sql } from 'drizzle-orm'
|
|
|
|
export const myTable = pgTable('my_table', {
|
|
id: uuid().primaryKey().default(sql`uuidv7()`),
|
|
name: text().notNull(),
|
|
createdAt: timestamp({ withTimezone: true }).notNull().defaultNow(),
|
|
updatedAt: timestamp({ withTimezone: true }).notNull().defaultNow().$onUpdateFn(() => new Date()),
|
|
})
|
|
```
|
|
|
|
## Code Style
|
|
|
|
### Formatting (Biome)
|
|
- **Indent**: 2 spaces
|
|
- **Quotes**: Single `'`
|
|
- **Semicolons**: Omit (ASI)
|
|
- **Arrow parens**: Always `(x) => x`
|
|
|
|
### Imports
|
|
Biome auto-organizes:
|
|
1. External packages
|
|
2. Internal `@/*` aliases
|
|
3. Type imports (`import type { ... }`)
|
|
|
|
```typescript
|
|
import { createFileRoute } from '@tanstack/react-router'
|
|
import { z } from 'zod'
|
|
import { db } from '@/server/db'
|
|
import type { ReactNode } from 'react'
|
|
```
|
|
|
|
### TypeScript
|
|
- `strict: true`
|
|
- `noUncheckedIndexedAccess: true` - array access returns `T | undefined`
|
|
- Use `@/*` path aliases (maps to `src/*`)
|
|
|
|
### Naming
|
|
| Type | Convention | Example |
|
|
|------|------------|---------|
|
|
| Files (utils) | kebab-case | `auth-utils.ts` |
|
|
| Files (components) | PascalCase | `UserProfile.tsx` |
|
|
| Components | PascalCase arrow | `const Button = () => {}` |
|
|
| Functions | camelCase | `getUserById` |
|
|
| Types | PascalCase | `UserProfile` |
|
|
|
|
### React
|
|
- Use arrow functions for components (Biome enforced)
|
|
- Use `useSuspenseQuery` for guaranteed data
|
|
- Let React Compiler handle memoization (no manual `useMemo`/`useCallback`)
|
|
|
|
## Environment Variables
|
|
|
|
```typescript
|
|
// src/env.ts - using @t3-oss/env-core
|
|
import { createEnv } from '@t3-oss/env-core'
|
|
import { z } from 'zod'
|
|
|
|
export const env = createEnv({
|
|
server: {
|
|
DATABASE_URL: z.string().url(),
|
|
},
|
|
clientPrefix: 'VITE_',
|
|
client: {
|
|
VITE_API_URL: z.string().optional(),
|
|
},
|
|
})
|
|
```
|
|
|
|
## Critical Rules
|
|
|
|
**DO:**
|
|
- Run `bun fix` before committing
|
|
- Use `@/*` path aliases
|
|
- Include `createdAt`/`updatedAt` on all tables
|
|
- Use `ORPCError` with proper codes
|
|
|
|
**DON'T:**
|
|
- Use `npm`, `npx`, `node`, `yarn`, `pnpm` — always use `bun` / `bunx`
|
|
- Edit `src/routeTree.gen.ts` (auto-generated)
|
|
- Use `as any`, `@ts-ignore`, `@ts-expect-error`
|
|
- Commit `.env` files
|
|
- Use empty catch blocks
|