- 新增根目录 AGENTS.md 作为 monorepo 总览 - 移动 desktop AGENTS.md 从 src-tauri/ 到 apps/desktop/ - 修正 server AGENTS.md 目录结构 (src/server/api/ 而非 src/orpc/) - 明确 desktop 为纯 Tauri 壳子,无前端代码,通过 sidecar 加载 server
5.9 KiB
5.9 KiB
AGENTS.md - Server App Guidelines
TanStack Start fullstack web app with ORPC (contract-first RPC).
Tech Stack
- Framework: TanStack Start (React 19 SSR, file-based routing)
- Runtime: Bun
- 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
# 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
# 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)
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)
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
// 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
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)
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:
- External packages
- Internal
@/*aliases - Type imports (
import type { ... })
import { createFileRoute } from '@tanstack/react-router'
import { z } from 'zod'
import { db } from '@/server/db'
import type { ReactNode } from 'react'
TypeScript
strict: truenoUncheckedIndexedAccess: true- array access returnsT | undefined- Use
@/*path aliases (maps tosrc/*)
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
useSuspenseQueryfor guaranteed data - Let React Compiler handle memoization (no manual
useMemo/useCallback)
Environment Variables
// 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 fixbefore committing - Use
@/*path aliases - Include
createdAt/updatedAton all tables - Use
ORPCErrorwith proper codes
DON'T:
- Edit
src/routeTree.gen.ts(auto-generated) - Use
as any,@ts-ignore,@ts-expect-error - Commit
.envfiles - Use empty catch blocks