From e41c4e451553b3daa4e5154e227a7cbe5f37794b Mon Sep 17 00:00:00 2001 From: imbytecat Date: Sat, 7 Feb 2026 03:29:51 +0800 Subject: [PATCH] =?UTF-8?q?docs:=20=E6=9B=B4=E6=96=B0=20AGENTS.md=20?= =?UTF-8?q?=E6=96=87=E6=A1=A3=E7=BB=93=E6=9E=84=E5=92=8C=E5=86=85=E5=AE=B9?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit - 新增根目录 AGENTS.md 作为 monorepo 总览 - 移动 desktop AGENTS.md 从 src-tauri/ 到 apps/desktop/ - 修正 server AGENTS.md 目录结构 (src/server/api/ 而非 src/orpc/) - 明确 desktop 为纯 Tauri 壳子,无前端代码,通过 sidecar 加载 server --- AGENTS.md | 200 ++++++++++++++++ apps/desktop/AGENTS.md | 171 ++++++++++++++ apps/desktop/src-tauri/AGENTS.md | 357 ---------------------------- apps/server/AGENTS.md | 384 +++++++++++++------------------ 4 files changed, 525 insertions(+), 587 deletions(-) create mode 100644 AGENTS.md create mode 100644 apps/desktop/AGENTS.md delete mode 100644 apps/desktop/src-tauri/AGENTS.md diff --git a/AGENTS.md b/AGENTS.md new file mode 100644 index 0000000..66c3375 --- /dev/null +++ b/AGENTS.md @@ -0,0 +1,200 @@ +# AGENTS.md - AI Coding Agent Guidelines + +Guidelines for AI agents working in this Bun monorepo. + +## Project Overview + +- **Monorepo**: Bun workspaces + Turborepo orchestration +- **Runtime**: Bun (see `mise.toml` for version) +- **Apps**: + - `apps/server` - TanStack Start fullstack web app (see `apps/server/AGENTS.md`) + - `apps/desktop` - Tauri v2 desktop shell, loads server via sidecar (see `apps/desktop/AGENTS.md`) +- **Packages**: `packages/utils`, `packages/tsconfig` (shared configs) + +## Build / Lint / Test Commands + +### Root Commands (via Turbo) +```bash +bun dev # Start all apps in dev mode +bun build # Build all apps +bun fix # Lint + format (Biome auto-fix) +bun typecheck # TypeScript check across monorepo +``` + +### Server App (`apps/server`) +```bash +bun dev # Vite dev server (localhost:3000) +bun build # Production build → .output/ +bun fix # Biome auto-fix +bun typecheck # TypeScript check + +# Database (Drizzle) +bun db:generate # Generate migrations from schema +bun db:migrate # Run migrations +bun db:push # Push schema (dev only) +bun db:studio # Open Drizzle Studio +``` + +### Desktop App (`apps/desktop`) +```bash +bun dev # Copy sidecar + start Tauri dev +bun build # Copy sidecar + build installer + +# Rust (from apps/desktop/src-tauri/) +cargo check # Compile check +cargo clippy # Linter +cargo fmt # Formatter +``` + +### Testing +No test framework configured yet. When adding tests: +```bash +bun test path/to/test.ts # Run single test file +bun test -t "pattern" # Run tests matching pattern +cargo test test_name -- --nocapture # Rust single test with output +``` + +## Code Style (TypeScript) + +### Formatting (Biome) +- **Indent**: 2 spaces +- **Line endings**: LF +- **Quotes**: Single `'` +- **Semicolons**: Omit (ASI) +- **Arrow parentheses**: Always `(x) => x` + +### Imports +Biome auto-organizes. Order: +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 Strictness +- `strict: true` +- `noUncheckedIndexedAccess: true` - array/object access returns `T | undefined` +- `noImplicitOverride: true` +- `verbatimModuleSyntax: true` +- Use `@/*` path aliases (maps to `src/*`) + +### Naming Conventions +| Type | Convention | Example | +|------|------------|---------| +| Files (utils) | kebab-case | `auth-utils.ts` | +| Files (components) | PascalCase | `UserProfile.tsx` | +| Components | PascalCase arrow | `const Button = () => {}` | +| Functions | camelCase | `getUserById` | +| Constants | UPPER_SNAKE | `MAX_RETRIES` | +| Types/Interfaces | PascalCase | `UserProfile` | + +### React Patterns +```typescript +// Components: arrow functions (enforced by Biome) +const MyComponent = ({ title }: { title: string }) => { + return
{title}
+} + +// Routes: TanStack Router file conventions +export const Route = createFileRoute('/')({ + component: Home, +}) + +// Data fetching: TanStack Query +const { data } = useSuspenseQuery(orpc.todo.list.queryOptions()) +``` + +### Error Handling +- Use `try-catch` for async operations +- Throw descriptive errors +- ORPC: Use `ORPCError` with proper codes (`NOT_FOUND`, `INPUT_VALIDATION_FAILED`) +- Never use empty catch blocks + +## Code Style (Rust - Tauri) + +- **Indent**: 4 spaces +- **Naming**: snake_case (functions), PascalCase (types), SCREAMING_SNAKE (consts) +- Use `expect("中文消息")` over `unwrap()` +- Async: `tokio` runtime, `tauri::async_runtime::spawn` +- Run `cargo fmt` and `cargo clippy` before commit + +## Database (Drizzle ORM) + +```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()), +}) +``` + +## Environment Variables + +- Use `@t3-oss/env-core` with Zod validation in `src/env.ts` +- Server vars: no prefix +- Client vars: `VITE_` prefix required +- Never commit `.env` files + +## Critical Rules + +**DO:** +- Run `bun fix` before committing +- Use `@/*` path aliases (not relative imports) +- Let React Compiler handle memoization (no manual `useMemo`/`useCallback`) +- Include `createdAt`/`updatedAt` on all tables + +**DON'T:** +- Edit `src/routeTree.gen.ts` (auto-generated) +- Use `as any`, `@ts-ignore`, `@ts-expect-error` +- Commit `.env` files +- Use empty catch blocks `catch(e) {}` +- Use `unwrap()` in Rust without `expect()` + +## Git Workflow + +1. Make changes following style guide +2. `bun fix` - auto-format and lint +3. `bun typecheck` - verify types +4. `bun dev` - test locally +5. Commit with descriptive message + +## Directory Structure + +``` +. +├── apps/ +│ ├── server/ # TanStack Start fullstack app +│ │ ├── src/ +│ │ │ ├── client/ # ORPC client, Query client +│ │ │ ├── components/ +│ │ │ ├── routes/ # File-based routing +│ │ │ └── server/ # API layer + database +│ │ │ ├── api/ # ORPC contracts, routers, middlewares +│ │ │ └── db/ # Drizzle schema +│ │ └── AGENTS.md +│ └── desktop/ # Tauri v2 shell (no frontend src) +│ ├── src-tauri/ # Rust Tauri code +│ │ ├── src/ # Rust source +│ │ └── binaries/ # Sidecar binaries +│ └── AGENTS.md +├── packages/ +│ ├── tsconfig/ # Shared TS configs +│ └── utils/ # Shared utilities +├── biome.json # Linting/formatting config +├── turbo.json # Turbo task orchestration +└── package.json # Workspace root +``` + +## See Also + +- `apps/server/AGENTS.md` - Detailed TanStack Start / ORPC patterns +- `apps/desktop/AGENTS.md` - Rust / Tauri development guide diff --git a/apps/desktop/AGENTS.md b/apps/desktop/AGENTS.md new file mode 100644 index 0000000..eaa0206 --- /dev/null +++ b/apps/desktop/AGENTS.md @@ -0,0 +1,171 @@ +# AGENTS.md - Desktop App Guidelines + +Tauri v2 desktop shell - a lightweight wrapper that loads the server app via sidecar. + +## Architecture + +- **Type**: Tauri v2 desktop application (shell only) +- **Design**: Tauri provides native desktop APIs; all web logic handled by sidecar +- **Sidecar**: The compiled server binary runs as a child process +- **Dev mode**: Connects to `localhost:3000` (requires server dev running) +- **Prod mode**: Automatically starts sidecar binary + +**This app has NO frontend src** - it loads the server app entirely. + +## Commands + +```bash +# Development (from apps/desktop/) +bun dev # Copy sidecar + start Tauri dev + +# Build +bun build # Copy sidecar + build Tauri installer + +# Rust Commands (from src-tauri/) +cargo check # Compile check +cargo clippy # Linter +cargo fmt # Formatter +cargo test # Run tests +cargo test test_name -- --nocapture # Single test with output +``` + +## Directory Structure + +``` +apps/desktop/ +├── src-tauri/ # Rust Tauri code +│ ├── src/ +│ │ ├── main.rs # Entry point (calls lib::run) +│ │ ├── lib.rs # Core app logic (plugins, commands, state) +│ │ ├── commands/ +│ │ │ └── mod.rs # Native desktop commands +│ │ └── sidecar.rs # Sidecar process management +│ ├── binaries/ # Sidecar binaries (copied from server build) +│ ├── capabilities/ # Tauri v2 permission config +│ ├── icons/ # App icons +│ ├── Cargo.toml # Rust dependencies +│ └── tauri.conf.json # Tauri configuration +├── copy.ts # Script to copy server binary to binaries/ +├── package.json +└── tsconfig.json +``` + +## Development Workflow + +1. **Start server dev first**: `cd ../server && bun dev` +2. **Start Tauri**: `bun dev` (from apps/desktop/) +3. Tauri connects to localhost:3000 with HMR support + +## Rust Code Style + +### Formatting +- **Indent**: 4 spaces +- **Line width**: 100 chars +- Run `cargo fmt` before commit + +### Naming +| Type | Convention | Example | +|------|------------|---------| +| Functions/variables | snake_case | `find_available_port` | +| Types/structs/enums | PascalCase | `SidecarProcess` | +| Constants | SCREAMING_SNAKE | `DEFAULT_PORT` | + +### Imports +```rust +// Order: std → external crates → internal modules (separated by blank lines) +use std::sync::Mutex; + +use tauri::Manager; +use tauri_plugin_shell::ShellExt; + +use crate::sidecar::SidecarProcess; +``` + +### Error Handling +```rust +// Use expect() with Chinese error messages +let sidecar = app_handle + .shell() + .sidecar("server") + .expect("无法找到 server sidecar"); + +// Log with emoji for clear feedback +println!("✓ Sidecar 启动成功!"); +eprintln!("✗ Sidecar 启动失败"); +``` + +### Async Code +```rust +// Use Tauri's async runtime for spawning +tauri::async_runtime::spawn(async move { + let port = find_available_port(3000).await; + // ... +}); +``` + +## Tauri Patterns + +### Command Definition +```rust +#[tauri::command] +fn greet(name: &str) -> String { + format!("Hello, {}!", name) +} + +// Register in Builder +.invoke_handler(tauri::generate_handler![commands::greet]) +``` + +### State Management +```rust +struct SidecarProcess(Mutex>); + +// Register state +app.manage(SidecarProcess(Mutex::new(None))); + +// Access state +if let Some(state) = app_handle.try_state::() { + *state.0.lock().unwrap() = Some(child); +} +``` + +### Sidecar Lifecycle +```rust +// Start sidecar with environment +let sidecar = app_handle + .shell() + .sidecar("server") + .expect("无法找到 server sidecar") + .env("PORT", port.to_string()); + +// Cleanup on exit +match event { + tauri::RunEvent::ExitRequested { .. } | tauri::RunEvent::Exit => { + if let Some(child) = process.take() { + let _ = child.kill(); + } + } + _ => {} +} +``` + +## Critical Rules + +**DO:** +- Run `cargo fmt` and `cargo clippy` before commit +- Use `expect("中文消息")` instead of `unwrap()` +- Always cleanup sidecar on app exit +- Declare sidecar in `tauri.conf.json` → `bundle.externalBin` + +**DON'T:** +- Edit `gen/schemas/` (auto-generated) +- Use `unwrap()` in production code without context +- Block the async runtime (use `spawn_blocking`) + +## Pre-commit Checklist + +- [ ] `cargo fmt` - formatting +- [ ] `cargo clippy` - linting +- [ ] `cargo check` - compiles +- [ ] `cargo test` - tests pass +- [ ] Tauri app starts and exits cleanly diff --git a/apps/desktop/src-tauri/AGENTS.md b/apps/desktop/src-tauri/AGENTS.md deleted file mode 100644 index b86eeda..0000000 --- a/apps/desktop/src-tauri/AGENTS.md +++ /dev/null @@ -1,357 +0,0 @@ -# AGENTS.md - Tauri Shell 项目开发指南 - -本文档为 AI 编程助手和开发者提供项目规范、构建命令和代码风格指南。 - -## 项目概览 - -- **项目类型**: Tauri v2 桌面应用(轻量级壳子) -- **后端**: Rust (Edition 2021) -- **架构**: Sidecar 模式 - Sidecar App 承载主要业务逻辑 -- **设计理念**: Tauri 仅提供原生桌面能力(文件对话框、系统通知等),Web 逻辑全部由 Sidecar App 处理 -- **开发模式**: 使用 localhost:3000(需手动启动开发服务器) -- **生产模式**: 自动启动 Sidecar 二进制 -- **异步运行时**: Tokio -- **Rust 版本**: 1.92.0+ -- **工具管理**: 使用 mise 管理 Rust 和 Tauri CLI 版本(见 `mise.toml`) - -## 构建、测试、运行命令 - -### 开发运行 -```bash -# 开发模式运行 (需要先启动开发服务器) -# 终端 1: 启动前端开发服务器 -bun run dev - -# 终端 2: 启动 Tauri 应用 -tauri dev - -# 或者使用单命令并行启动(需要配置 package.json) -bun run dev:tauri -``` - -**开发模式说明**: -- 开发模式下,Tauri 直接连接到 `localhost:3000`(不启动 sidecar 二进制) -- 需要手动运行 `bun run dev` 来启动开发服务器 -- 支持热重载(HMR),无需重启 Tauri 应用 - -### 构建 -```bash -# 开发构建 (debug mode) -cargo build - -# 生产构建 -cargo build --release - -# Tauri 应用打包 (生成安装程序) -tauri build -``` - -### 代码检查 -```bash -# 编译检查 (不生成二进制) -cargo check - -# Clippy 代码质量检查 -cargo clippy - -# Clippy 严格模式 (所有警告视为错误) -cargo clippy -- -D warnings - -# 代码格式化检查 -cargo fmt -- --check - -# 自动格式化代码 -cargo fmt -``` - -### 测试 -```bash -# 运行所有测试 -cargo test - -# 运行单个测试 (按名称过滤) -cargo test test_function_name - -# 运行特定模块的测试 -cargo test module_name:: - -# 显示测试输出 (包括 println!) -cargo test -- --nocapture - -# 运行单个测试并显示输出 -cargo test test_name -- --nocapture -``` - -### 清理 -```bash -# 清理构建产物 -cargo clean -``` - -## 项目结构 - -``` -server-desktop/ -├── src/ -│ ├── main.rs # 入口文件 (仅调用 lib::run) -│ ├── lib.rs # 核心应用逻辑 (注册插件、命令、状态) -│ ├── commands/ -│ │ └── mod.rs # 原生桌面功能命令 (文件对话框、通知等) -│ └── sidecar.rs # Sidecar 进程管理 (启动、端口扫描、清理) -├── binaries/ # Sidecar 二进制文件 -│ └── app-* # Sidecar App 可执行文件 (示例: app) -├── capabilities/ # Tauri v2 权限配置 -│ └── default.json -├── icons/ # 应用图标资源 -├── gen/schemas/ # 自动生成的 Schema (不要手动编辑) -├── Cargo.toml # Rust 项目配置 -├── tauri.conf.json # Tauri 应用配置 -├── build.rs # Rust 构建脚本 -└── mise.toml # 开发工具版本管理 -``` - -## Rust 代码风格指南 - -### 导入 (Imports) - -- 使用标准库、外部 crate、当前 crate 的顺序,用空行分隔 -- 按字母顺序排列 -- 优先使用具体导入而非通配符 `*` - -```rust -// ✅ 推荐 -use std::sync::Mutex; -use std::time::Duration; - -use tauri::Manager; -use tauri_plugin_shell::ShellExt; -use tauri_plugin_shell::process::{CommandEvent, CommandChild}; - -// ❌ 避免 -use tauri::*; -``` - -### 命名规范 - -- **函数和变量**: `snake_case` -- **类型、结构体、枚举、Trait**: `PascalCase` -- **常量和静态变量**: `SCREAMING_SNAKE_CASE` -- **生命周期参数**: 简短小写字母,如 `'a`, `'b` - -```rust -// ✅ 推荐 -struct SidecarProcess(Mutex>); -const DEFAULT_PORT: u16 = 3000; -async fn find_available_port(start: u16) -> u16 { } - -// ❌ 避免 -struct sidecar_process { } -const defaultPort: u16 = 3000; -``` - -### 类型注解 - -- 函数参数必须有类型注解 -- 函数返回值必须明确声明 (除非返回 `()`) -- 优先使用具体类型而非 `impl Trait` (除非必要) -- 使用 `&str` 而非 `String` 作为只读字符串参数 - -```rust -// ✅ 推荐 -#[tauri::command] -fn greet(name: &str) -> String { - format!("Hello, {}!", name) -} - -async fn is_port_available(port: u16) -> bool { - tokio::net::TcpListener::bind(format!("127.0.0.1:{}", port)) - .await - .is_ok() -} -``` - -### 错误处理 - -- 使用 `Result` 返回可能失败的操作 -- 使用 `expect()` 时提供有意义的错误消息 (中文) -- 避免 `unwrap()` 在生产代码中,除非逻辑上保证不会 panic -- 使用 `?` 操作符传播错误 -- 记录关键错误信息到控制台 - -```rust -// ✅ 推荐 -let sidecar = app_handle - .shell() - .sidecar("app") - .expect("无法找到 app sidecar"); - -let (mut rx, child) = sidecar.spawn().expect("启动 sidecar 失败"); - -// 日志记录 -eprintln!("✗ Sidecar App 启动失败"); -println!("✓ Sidecar App 启动成功!"); - -// ❌ 避免 -let data = read_file().unwrap(); // 无上下文信息 -``` - -### 异步代码 - -- 使用 `async/await` 而非手动创建 Future -- Tauri 内部使用 `tauri::async_runtime::spawn` 启动异步任务 -- 使用 Tokio 的异步 API (如 `tokio::net::TcpListener`) -- 避免阻塞异步运行时 (使用 `tokio::task::spawn_blocking`) - -```rust -// ✅ 推荐 -tauri::async_runtime::spawn(async move { - let port = find_available_port(3000).await; - // ... -}); -``` - -### 格式化 - -- 使用 `cargo fmt` 自动格式化 -- 缩进: 4 空格 -- 行宽: 100 字符 (rustfmt 默认) -- 结构体和枚举的字段每行一个 (如果超过一定长度) -- 链式调用适当换行提高可读性 - -### 注释 - -- 使用中文注释说明复杂逻辑 -- 代码块前添加简短说明注释 -- 避免显而易见的注释 - -```rust -// ✅ 推荐 -// 全局状态:存储 Sidecar App 进程句柄 -struct SidecarProcess(Mutex>); - -// 检查端口是否可用 -async fn is_port_available(port: u16) -> bool { } -``` - -## Tauri 特定规范 - -### 模块组织 - -- **`lib.rs`**: 主入口,负责注册插件、命令、状态管理 -- **`commands/mod.rs`**: 所有 Tauri 命令集中定义,命令必须是 `pub fn` -- **`sidecar.rs`**: Sidecar 进程管理逻辑,导出公共 API(`spawn_sidecar`, `cleanup_sidecar_process`) - -```rust -// lib.rs - 模块声明 -mod commands; -mod sidecar; - -use sidecar::SidecarProcess; - -// 注册命令时使用模块路径 -.invoke_handler(tauri::generate_handler![commands::greet]) -``` - -### 命令定义 - -- 使用 `#[tauri::command]` 宏标记命令 -- 命令函数必须是公开的或在 `invoke_handler` 中注册 -- 参数类型必须实现 `serde::Deserialize` -- 返回类型必须实现 `serde::Serialize` - -```rust -#[tauri::command] -fn greet(name: &str) -> String { - format!("Hello, {}!", name) -} - -// 在 Builder 中注册 -.invoke_handler(tauri::generate_handler![greet]) -``` - -### 状态管理 - -- 使用 `app.manage()` 注册全局状态 -- 状态必须实现 `Send + Sync` -- 使用 `Mutex` 或 `RwLock` 保证线程安全 - -```rust -struct SidecarProcess(Mutex>); - -// 注册状态 -app.manage(SidecarProcess(Mutex::new(None))); - -// 访问状态 -if let Some(state) = app_handle.try_state::() { - *state.0.lock().unwrap() = Some(child); -} -``` - -### Sidecar 进程管理 - -- Sidecar 二进制必须在 `tauri.conf.json` 的 `bundle.externalBin` 中声明 -- 使用 `app.shell().sidecar()` 启动 sidecar -- 在应用退出时清理子进程 (监听 `RunEvent::ExitRequested`) - -```rust -// 启动 sidecar -let sidecar = app_handle - .shell() - .sidecar("app") - .expect("无法找到 app sidecar") - .env("PORT", port.to_string()); - -// 清理进程 -match event { - tauri::RunEvent::ExitRequested { .. } | tauri::RunEvent::Exit => { - if let Some(child) = process.take() { - let _ = child.kill(); - } - } - _ => {} -} -``` - -## 依赖管理 - -- 在 `Cargo.toml` 中明确声明依赖版本 -- 使用语义化版本 (如 `"2"` 表示兼容 2.x.x) -- 仅启用需要的 feature 以减少编译时间和二进制大小 - -```toml -tauri = { version = "2", features = [] } -tauri-plugin-opener = "2" -tauri-plugin-shell = "2" -serde = { version = "1", features = ["derive"] } -tokio = { version = "1", features = ["net"] } -``` - -## 开发工具 - -推荐安装以下 VSCode 扩展: -- `tauri-apps.tauri-vscode` - Tauri 官方支持 -- `rust-lang.rust-analyzer` - Rust 语言服务器 - -## 最佳实践 - -1. **开发环境配置**: - - 开发模式下需先启动前端开发服务器(`bun run dev`),再启动 Tauri(`tauri dev`) - - 生产构建自动打包 sidecar 二进制,无需额外配置 -2. **进程生命周期**: 始终在应用退出时清理子进程和资源 -3. **端口管理**: - - 开发模式固定使用 3000 端口(与开发服务器匹配) - - 生产模式使用端口扫描避免硬编码端口冲突 -4. **超时处理**: 异步操作设置合理的超时时间 (如 5 秒) -5. **日志**: 使用表情符号 (✓/✗/🔧/🚀) 和中文消息提供清晰的状态反馈 -6. **错误退出**: 关键错误时调用 `std::process::exit(1)` -7. **窗口配置**: 使用 `WebviewWindowBuilder` 动态创建窗口 - -## 提交代码前检查清单 - -- [ ] `cargo fmt` 格式化通过 -- [ ] `cargo clippy` 无警告 -- [ ] `cargo check` 编译通过 -- [ ] `cargo test` 测试通过 -- [ ] 更新相关注释和文档 -- [ ] 检查是否有 `unwrap()` 需要替换为 `expect()` -- [ ] 验证 Tauri 应用正常启动和退出 diff --git a/apps/server/AGENTS.md b/apps/server/AGENTS.md index 11bc1ea..c2d9202 100644 --- a/apps/server/AGENTS.md +++ b/apps/server/AGENTS.md @@ -1,155 +1,121 @@ -# AGENTS.md - AI Coding Agent Guidelines +# AGENTS.md - Server App Guidelines -本文档为 AI 编程助手提供此 TanStack Start 全栈项目的开发规范和指南。 +TanStack Start fullstack web app with ORPC (contract-first RPC). -## 项目概览 +## Tech Stack -- **框架**: TanStack Start (React SSR 框架,文件路由) -- **运行时**: Bun -- **语言**: TypeScript (strict mode, ESNext) -- **样式**: Tailwind CSS v4 -- **数据库**: PostgreSQL + Drizzle ORM -- **状态管理**: TanStack Query -- **路由**: TanStack Router (文件路由) -- **RPC**: ORPC (类型安全 RPC,契约优先) -- **构建工具**: Vite + Turbo -- **代码质量**: Biome (格式化 + Lint) -- **桌面壳** (可选): Tauri v2 (详见 `src-tauri/AGENTS.md`) +- **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 -## 构建、Lint 和测试命令 +## Commands -### 开发 ```bash -bun dev # 使用 Turbo 并行启动 Tauri + Vite 开发服务器 -bun dev:vite # 仅启动 Vite 开发服务器 (localhost:3000) -bun dev:tauri # 启动 Tauri 桌面应用 -bun db:studio # 打开 Drizzle Studio 数据库管理界面 +# 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 ``` -### 构建 -```bash -bun build # 完整构建 (Vite → 编译 → Tauri 打包) -bun build:vite # 仅构建 Vite (输出到 .output/) -bun build:compile # 编译为独立可执行文件 (使用 build.ts) -bun build:tauri # 构建 Tauri 桌面安装包 +## 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 ``` -### 代码质量 -```bash -bun typecheck # 运行 TypeScript 编译器检查 (tsc -b) -bun fix # 运行 Biome 自动修复格式和 Lint 问题 -biome check . # 检查但不自动修复 -biome format --write . # 仅格式化代码 -``` +## ORPC Pattern -### 数据库 -```bash -bun db:generate # 从 schema 生成迁移文件 -bun db:migrate # 执行数据库迁移 -bun db:push # 直接推送 schema 变更 (仅开发环境) -``` - -### 测试 -**注意**: 当前未配置测试框架。添加测试时: -- 使用 Vitest 或 Bun 内置测试运行器 -- 运行单个测试文件: `bun test path/to/test.ts` -- 运行特定测试: `bun test -t "测试名称模式"` - -## 代码风格指南 - -### 格式化 (Biome) - -**缩进**: 2 空格 (不使用 tab) -**换行符**: LF (Unix 风格) -**引号**: 单引号 `'string'` -**分号**: 按需 (ASI - 自动分号插入) -**箭头函数括号**: 始终使用 `(x) => x` - -示例: +### 1. Define Contract (`src/server/api/contracts/feature.contract.ts`) ```typescript -const myFunc = (value: string) => { - return value.toUpperCase() -} -``` - -### 导入组织 - -Biome 自动组织导入。顺序: -1. 外部依赖 -2. 内部导入 (使用 `@/*` 别名) -3. 类型导入 (仅导入类型时使用 `type` 关键字) - -示例: -```typescript -import { createFileRoute } from '@tanstack/react-router' import { oc } from '@orpc/contract' +import { createSelectSchema } from 'drizzle-zod' import { z } from 'zod' -import { db } from '@/db' -import { todoTable } from '@/db/schema' -import type { ReactNode } from 'react' +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) ``` -### TypeScript - -**严格模式**: 启用了额外的严格检查 -- `strict: true` -- `noUncheckedIndexedAccess: true` - 数组/对象索引返回 `T | undefined` -- `noImplicitOverride: true` -- `noFallthroughCasesInSwitch: true` - -**模块解析**: `bundler` 模式 + `verbatimModuleSyntax` -- 导入时始终使用 `.ts`/`.tsx` 扩展名 -- 使用 `@/*` 路径别名指向 `src/*` - -**类型注解**: -- 公共 API 的函数参数和返回类型必须注解 -- 优先使用显式类型而非 `any` -- 对象形状用 `type`,可扩展契约用 `interface` -- 不可变 props 使用 `Readonly` - -### 命名规范 - -- **文件**: 工具函数用 kebab-case,组件用 PascalCase - - `utils.ts`, `todo.tsx`, `NotFound.tsx` -- **路由**: 遵循 TanStack Router 约定 - - `routes/index.tsx` → `/` - - `routes/__root.tsx` → 根布局 -- **组件**: PascalCase 箭头函数 (Biome 规则 `useArrowFunction` 强制) -- **函数**: camelCase -- **常量**: 真常量用 UPPER_SNAKE_CASE,配置对象用 camelCase -- **类型/接口**: PascalCase - -### React 模式 - -**组件**: 使用箭头函数 +### 2. Implement Router (`src/server/api/routers/feature.router.ts`) ```typescript -const MyComponent = ({ title }: { title: string }) => { - return
{title}
-} -``` +import { ORPCError } from '@orpc/server' +import { db } from '../middlewares' +import { os } from '../server' -**路由**: 使用 `createFileRoute` 定义路由 -```typescript -export const Route = createFileRoute('/')({ - component: Home, +export const list = os.feature.list.use(db).handler(async ({ context }) => { + return await context.db.query.featureTable.findMany() }) ``` -**数据获取**: 使用 TanStack Query hooks -- `useSuspenseQuery` - 保证有数据 -- `useQuery` - 数据可能为空 +### 3. Register in Index Files +```typescript +// src/server/api/contracts/index.ts +import * as feature from './feature.contract' +export const contract = { feature } -**Props**: 禁止直接修改 props (Biome 规则 `noReactPropAssignments`) +// src/server/api/routers/index.ts +import * as feature from './feature.router' +export const router = os.router({ feature }) +``` -### 数据库 Schema (Drizzle) +### 4. Use in Components +```typescript +import { useSuspenseQuery, useMutation } from '@tanstack/react-query' +import { orpc } from '@/client/orpc.client' -- 在 `src/db/schema/*.ts` 定义 schema -- 从 `src/db/schema/index.ts` 导出 -- 使用 `drizzle-orm/pg-core` 的 PostgreSQL 类型 -- 主键使用 `uuidv7()` (需要 PostgreSQL 扩展) -- 始终包含 `createdAt` 和 `updatedAt` 时间戳 +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' @@ -162,116 +128,74 @@ export const myTable = pgTable('my_table', { }) ``` -### 环境变量 +## Code Style -- 使用 `@t3-oss/env-core` 进行类型安全的环境变量验证 -- 在 `src/env.ts` 定义 schema -- 服务端变量: 无前缀 -- 客户端变量: 必须有 `VITE_` 前缀 -- 使用 Zod schema 验证 +### 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 { ... }`) -- 异步操作使用 try-catch -- 抛出带有描述性消息的错误 -- 用户界面错误优先使用 Result 类型或错误边界 -- 适当记录错误 (避免记录敏感数据) - -### 样式 (Tailwind CSS) - -- 使用 Tailwind v4 工具类 -- 通过 `@/styles.css?url` 导入样式 -- 优先使用组合而非自定义 CSS -- 响应式修饰符: `sm:`, `md:`, `lg:` -- UI 文本适当使用中文 - -## 目录结构 - -``` -src/ -├── components/ # 可复用 React 组件 -├── db/ -│ ├── schema/ # Drizzle schema 定义 -│ └── index.ts # 数据库实例 -├── integrations/ # 第三方集成 (TanStack Query/Router) -├── lib/ # 工具函数 -├── orpc/ # ORPC (RPC 层) -│ ├── contracts/ # 契约定义 (input/output schemas) -│ ├── handlers/ # 服务端过程实现 -│ ├── middlewares/ # 中间件 (如 DB provider) -│ ├── contract.ts # 契约聚合 -│ ├── router.ts # 路由组合 -│ ├── server.ts # 服务端实例 -│ └── client.ts # 同构客户端 -├── routes/ # TanStack Router 文件路由 -│ ├── __root.tsx # 根布局 -│ ├── index.tsx # 首页 -│ └── api/rpc.$.ts # ORPC HTTP 端点 -├── env.ts # 环境变量验证 -└── router.tsx # 路由配置 -``` - -## 重要提示 - -- **禁止** 编辑 `src/routeTree.gen.ts` - 自动生成 -- **禁止** 提交 `.env` 文件 - 使用 `.env.example` 作为模板 -- **必须** 在提交前运行 `bun fix` -- **必须** 使用 `@/*` 路径别名而非相对导入 -- **必须** 利用 React Compiler (babel-plugin-react-compiler) - 避免手动 memoization - -## Git 工作流 - -1. 按照上述风格指南进行修改 -2. 运行 `bun fix` 自动格式化和 lint -3. 运行 `bun typecheck` 确保类型安全 -4. 使用 `bun dev` 本地测试变更 -5. 使用清晰的描述性消息提交 - -## 常见模式 - -### 创建 ORPC 过程 - -**步骤 1: 定义契约** (`src/orpc/contracts/my-feature.ts`) ```typescript -import { oc } from '@orpc/contract' +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 myContract = { - get: oc.input(z.object({ id: z.uuid() })).output(mySchema), - create: oc.input(createSchema).output(mySchema), -} +export const env = createEnv({ + server: { + DATABASE_URL: z.string().url(), + }, + clientPrefix: 'VITE_', + client: { + VITE_API_URL: z.string().optional(), + }, +}) ``` -**步骤 2: 实现处理器** (`src/orpc/handlers/my-feature.ts`) -```typescript -import { os } from '@/orpc/server' -import { dbProvider } from '@/orpc/middlewares' +## Critical Rules -export const get = os.myFeature.get - .use(dbProvider) - .handler(async ({ context, input }) => { - return await context.db.query.myTable.findFirst(...) - }) -``` +**DO:** +- Run `bun fix` before committing +- Use `@/*` path aliases +- Include `createdAt`/`updatedAt` on all tables +- Use `ORPCError` with proper codes -**步骤 3: 注册到契约和路由** -```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 }) -``` - -**步骤 4: 在组件中使用** -```typescript -import { orpc } from '@/orpc' -const query = useSuspenseQuery(orpc.myFeature.get.queryOptions({ id })) -const mutation = useMutation(orpc.myFeature.create.mutationOptions()) -``` - ---- - -**最后更新**: 2026-01-18 -**项目版本**: 基于 package.json 依赖版本 +**DON'T:** +- Edit `src/routeTree.gen.ts` (auto-generated) +- Use `as any`, `@ts-ignore`, `@ts-expect-error` +- Commit `.env` files +- Use empty catch blocks