Files
openbridge-token-usage-viewer/AGENTS.md
imbytecat 03c6b84a39 feat: 添加 ORPC 类型安全 RPC 层集成指南
- 添加 ORPC 类型安全 RPC 层的完整集成指南,包括合约定义、处理器实现、路由注册及在组件中使用的方法。
2026-01-18 03:51:48 +08:00

324 lines
9.5 KiB
Markdown

# AGENTS.md - AI Coding Agent Guidelines
This document provides comprehensive guidelines for AI coding agents working in this TanStack Start fullstack starter codebase.
## Project Overview
- **Framework**: TanStack Start (React SSR framework with file-based routing)
- **Runtime**: Bun
- **Language**: TypeScript (strict mode, ESNext)
- **Styling**: Tailwind CSS v4
- **Database**: PostgreSQL with Drizzle ORM
- **State Management**: TanStack Query
- **Routing**: TanStack Router (file-based)
- **RPC**: ORPC (type-safe RPC with contract-first design)
- **Build Tool**: Vite
- **Linter/Formatter**: Biome
## Build, Lint, and Test Commands
### Development
```bash
bun dev # Start development server
bun db:studio # Open Drizzle Studio for database management
```
### Building
```bash
bun build # Build for production (outputs to .output/)
bun compile # Compile to standalone executable (out/server)
bun serve # Preview production build
```
### Code Quality
```bash
bun typecheck # Run TypeScript compiler (tsc -b)
bun fix # Run Biome linter and formatter (auto-fix issues)
biome check . # Check without auto-fix
biome format --write . # Format code only
```
### Database
```bash
bun db:generate # Generate migration files from schema
bun db:migrate # Run migrations
bun db:push # Push schema changes directly (dev only)
```
### Testing
**Note**: No test framework is currently configured. When adding tests:
- Use Vitest or Bun's built-in test runner
- Run single test file: `bun test path/to/test.ts`
- Run specific test: `bun test -t "test name pattern"`
## Code Style Guidelines
### Formatting (Biome)
**Indentation**: 2 spaces (not tabs)
**Line Endings**: LF (Unix-style)
**Quotes**: Single quotes for strings
**Semicolons**: As needed (ASI - automatic semicolon insertion)
**Arrow Parens**: Always use parentheses `(x) => x`
Example:
```typescript
const myFunc = (value: string) => {
return value.toUpperCase()
}
```
### Import Organization
Imports are auto-organized by Biome. Order:
1. External dependencies
2. Internal imports using `@/*` alias
3. Type imports (use `type` keyword when importing only types)
Example:
```typescript
import { createFileRoute } from '@tanstack/react-router'
import { createServerFn } from '@tanstack/react-start'
import { db } from '@/db'
import type { ReactNode } from 'react'
```
### TypeScript
**Strict Mode**: Enabled with additional strictness flags
- `strict: true`
- `noUncheckedIndexedAccess: true` - Array/object indexing returns `T | undefined`
- `noImplicitOverride: true`
- `noFallthroughCasesInSwitch: true`
**Module Resolution**: `bundler` mode with `verbatimModuleSyntax`
- Always use `.ts`/`.tsx` extensions in imports
- Use `@/*` path alias for `src/*`
**Type Annotations**:
- Always annotate function parameters and return types for public APIs
- Prefer explicit types over `any`
- Use `type` for object shapes, `interface` for extendable contracts
- Use `Readonly<T>` for immutable props
Example:
```typescript
function getTodos(): Promise<Todo[]> {
return db.query.todoTable.findMany()
}
type Props = Readonly<{
children: ReactNode
}>
```
### Naming Conventions
- **Files**: kebab-case for utilities, PascalCase for components
- `utils.ts`, `todo.tsx`, `NotFound.tsx`
- **Routes**: Use TanStack Router conventions
- `routes/index.tsx``/`
- `routes/todo.tsx``/todo`
- `routes/__root.tsx` → Root layout
- **Components**: PascalCase function declarations
- **Functions**: camelCase
- **Constants**: UPPER_SNAKE_CASE for true constants, camelCase for config objects
- **Types/Interfaces**: PascalCase
### React Patterns
**Components**: Use arrow functions (enforced by Biome rule `useArrowFunction`)
```typescript
const MyComponent = ({ title }: { title: string }) => {
return <div>{title}</div>
}
```
**Server Functions**: Use TanStack Start's `createServerFn`
```typescript
const getTodos = createServerFn({ method: 'GET' }).handler(async () => {
const todos = await db.query.todoTable.findMany()
return todos
})
```
**Routing**: Use `createFileRoute` for route definitions
```typescript
export const Route = createFileRoute('/todo')({
component: Todo,
})
```
**Data Fetching**: Use TanStack Query hooks
- `useSuspenseQuery` for guaranteed data
- `useQuery` when data might be optional
**Props**: No direct prop mutations (enforced by `noReactPropAssignments`)
### Database Schema (Drizzle)
- Define schemas in `src/db/schema/*.ts`
- Export from `src/db/schema/index.ts`
- Use PostgreSQL types from `drizzle-orm/pg-core`
- Use `uuidv7()` for primary keys (requires PostgreSQL extension)
- Always include `createdAt` and `updatedAt` timestamps
Example:
```typescript
import { pgTable, text, timestamp, uuid } from 'drizzle-orm/pg-core'
import { sql } from 'drizzle-orm'
export const myTable = pgTable('my_table', {
id: uuid('id').primaryKey().default(sql`uuidv7()`),
name: text('name').notNull(),
createdAt: timestamp('created_at', { withTimezone: true })
.notNull()
.defaultNow(),
updatedAt: timestamp('updated_at', { withTimezone: true })
.notNull()
.defaultNow()
.$onUpdateFn(() => new Date()),
})
```
### Environment Variables
- Use `@t3-oss/env-core` for type-safe env validation
- Define schema in `src/env.ts`
- Server vars: No prefix
- Client vars: Must have `VITE_` prefix
- Always validate with Zod schemas
### Error Handling
- Use try-catch for async operations
- Throw errors with descriptive messages
- Prefer Result types or error boundaries for user-facing errors
- Log errors appropriately (avoid logging sensitive data)
### Styling (Tailwind CSS)
- Use Tailwind v4 utility classes
- Import styles via `@/styles.css?url`
- Prefer composition over custom CSS
- Use responsive modifiers: `sm:`, `md:`, `lg:`
- Use Chinese text for UI when appropriate (as seen in codebase)
## File Structure
```
src/
├── components/ # Reusable React components
├── db/
│ ├── schema/ # Drizzle schema definitions
│ └── index.ts # Database instance
├── lib/ # Utility functions
├── orpc/ # ORPC (RPC layer)
│ ├── contracts/ # Contract definitions (input/output schemas)
│ ├── handlers/ # Server-side procedure implementations
│ ├── middlewares/ # Middleware (e.g., DB provider)
│ ├── contract.ts # Contract aggregation
│ ├── router.ts # Router composition
│ ├── server.ts # Server instance
│ ├── client.ts # Isomorphic client
│ └── types.ts # Type utilities
├── routes/ # TanStack Router file-based routes
│ ├── __root.tsx # Root layout
│ ├── index.tsx # Home page
│ └── api/rpc.$.ts # ORPC HTTP endpoint
├── env.ts # Environment variable validation
├── index.ts # Application entry point
├── router.tsx # Router configuration
├── routeTree.gen.ts # Auto-generated (DO NOT EDIT)
└── styles.css # Global styles
```
## Important Notes
- **DO NOT** edit `src/routeTree.gen.ts` - it's auto-generated
- **DO NOT** commit `.env` files - use `.env.example` for templates
- **DO** run `bun fix` before committing to ensure code quality
- **DO** use the `@/*` path alias instead of relative imports
- **DO** leverage React Compiler (babel-plugin-react-compiler) - avoid manual memoization
## Git Workflow
1. Make changes following the style guidelines above
2. Run `bun fix` to auto-format and lint
3. Run `bun typecheck` to ensure type safety
4. Test changes locally with `bun dev`
5. Commit with clear, descriptive messages
## Common Patterns
### Adding a New Route
1. Create `src/routes/my-route.tsx`
2. Export route with `createFileRoute`
3. Route tree auto-updates on save
### Adding Database Table
1. Create schema in `src/db/schema/my-table.ts`
2. Export from `src/db/schema/index.ts`
3. Run `bun db:generate` to create migration
4. Run `bun db:migrate` to apply migration
### Creating Server Function
```typescript
const myServerFn = createServerFn({ method: 'POST' })
.validator((data) => mySchema.parse(data))
.handler(async ({ data }) => {
// Server-side logic here
return result
})
```
### Creating ORPC Procedures
**Step 1: Define Contract** (`src/orpc/contracts/my-feature.ts`)
```typescript
import { oc } from '@orpc/contract'
import { z } from 'zod'
export const myContract = {
get: oc.input(z.object({ id: z.uuid() })).output(mySchema),
create: oc.input(createSchema).output(mySchema),
}
```
**Step 2: Implement Handler** (`src/orpc/handlers/my-feature.ts`)
```typescript
import { os } from '@/orpc/server'
import { dbProvider } from '@/orpc/middlewares'
export const get = os.myFeature.get
.use(dbProvider)
.handler(async ({ context, input }) => {
const item = await context.db.query.myTable.findFirst(...)
return item
})
```
**Step 3: Register in Contract & Router**
```typescript
// src/orpc/contract.ts
export const contract = {
myFeature: myContract,
}
// src/orpc/router.ts
import * as myFeature from './handlers/my-feature'
export const router = os.router({ myFeature })
```
**Step 4: Use in Component**
```typescript
import { orpc } from '@/orpc'
const query = useSuspenseQuery(orpc.myFeature.get.queryOptions({ id }))
const mutation = useMutation(orpc.myFeature.create.mutationOptions())
```
---
**Last Updated**: 2026-01-18
**Project Version**: Based on package.json dependencies