diff --git a/.gitignore b/.gitignore index 4f29602..ce51d38 100644 --- a/.gitignore +++ b/.gitignore @@ -155,9 +155,3 @@ dist vite.config.js.timestamp-* vite.config.ts.timestamp-* .vite/ - -# SQLite database files -data/ -*.db -*.db-shm -*.db-wal diff --git a/apps/server/.gitignore b/apps/server/.gitignore new file mode 100644 index 0000000..fd0ed34 --- /dev/null +++ b/apps/server/.gitignore @@ -0,0 +1,5 @@ +# SQLite database files +data/ +*.db +*.db-shm +*.db-wal diff --git a/apps/server/AGENTS.md b/apps/server/AGENTS.md index 63745d0..fd967c3 100644 --- a/apps/server/AGENTS.md +++ b/apps/server/AGENTS.md @@ -378,45 +378,22 @@ const fingerprint = createHash('sha256') - 专业库不一定解决所有问题(如并发去重) - 对于简单场景,手动实现 vs 库的选择主要取决于可维护性而非功能 -### PostgreSQL → SQLite 迁移 +### SQLite 数据库使用说明 -**迁移时间**: 2026-01-26 - -**原因**: 项目需要在嵌入环境中运行,无法安装 PostgreSQL - -**技术选择**: -- **驱动**: `better-sqlite3` v11.8.1 (跨平台,成熟稳定,原生模块) +**技术栈**: +- **驱动**: `better-sqlite3` v11.8.1 (原生模块,跨平台) - **类型定义**: `@types/better-sqlite3` v7.6.12 -- **主键**: TEXT 存储 UUIDv7 (保持全局唯一性,36 字符字符串) -- **时间戳**: INTEGER 存储 Unix 毫秒时间戳 (`integer({ mode: 'timestamp_ms' })`) -- **布尔值**: INTEGER 存储 0/1 (`integer({ mode: 'boolean' })`) - **数据库文件**: `./data/app.db` -**变更内容**: -1. **依赖**: `postgres` → `better-sqlite3` + `@types/better-sqlite3` -2. **Drizzle 方言**: `dialect: 'postgresql'` → `dialect: 'sqlite'` -3. **Schema 导入**: `drizzle-orm/pg-core` → `drizzle-orm/sqlite-core` -4. **表定义**: `pgTable` → `sqliteTable` -5. **数据库连接**: `drizzle-orm/postgres-js` → `drizzle-orm/better-sqlite3` -6. **数据类型映射**: - - UUID: `uuid()` → `text()` (使用 `uuidv7()` 生成) - - 时间戳: `timestamp({ withTimezone: true })` → `integer({ mode: 'timestamp_ms' })` - - 布尔值: `boolean()` → `integer({ mode: 'boolean' })` - - 默认值: `defaultNow()` → `$defaultFn(() => new Date())` -7. **环境变量**: `DATABASE_URL` 从 URL 格式改为文件路径 (`./data/app.db`) -8. **目录自动创建**: 添加了数据库文件目录的自动创建逻辑 +**数据类型策略**: +- **主键**: TEXT 存储 UUIDv7 (36 字符字符串,全局唯一) +- **时间戳**: INTEGER 存储 Unix 毫秒时间戳 (`integer({ mode: 'timestamp_ms' })`) + - Drizzle 自动转换 `Date` ↔ `number` +- **布尔值**: INTEGER 存储 0/1 (`integer({ mode: 'boolean' })`) + - Drizzle 自动转换 `boolean` ↔ `0/1` -**数据库连接层变更**: +**数据库连接**: ```typescript -// Before (PostgreSQL) -import { drizzle } from 'drizzle-orm/postgres-js' -export const createDB = () => - drizzle({ - connection: { url: env.DATABASE_URL, prepare: true }, - schema, - }) - -// After (SQLite) import { drizzle } from 'drizzle-orm/better-sqlite3' import Database from 'better-sqlite3' import { mkdirSync } from 'node:fs' @@ -432,75 +409,33 @@ export const createDB = () => { } ``` -**Schema 变更示例**: -```typescript -// Before (PostgreSQL) -import { pgTable, text, boolean, timestamp, uuid } from 'drizzle-orm/pg-core' -export const todoTable = pgTable('todo', { - id: uuid('id').primaryKey().$defaultFn(() => uuidv7()), - title: text('title').notNull(), - completed: boolean('completed').notNull().default(false), - createdAt: timestamp('created_at', { withTimezone: true }).notNull().defaultNow(), -}) - -// After (SQLite) -import { sqliteTable, text, integer } from 'drizzle-orm/sqlite-core' -export const todoTable = sqliteTable('todo', { - id: text('id').primaryKey().$defaultFn(() => uuidv7()), - title: text('title').notNull(), - completed: integer('completed', { mode: 'boolean' }).notNull().default(false), - createdAt: integer('created_at', { mode: 'timestamp_ms' }).notNull().$defaultFn(() => new Date()), -}) -``` - -**兼容性保证**: -- ✅ 所有业务逻辑保持不变 -- ✅ ORPC 契约和类型完全兼容 -- ✅ 应用层代码无需修改 -- ✅ Drizzle 自动处理类型转换 (Date ↔ number, boolean ↔ 0/1) - -**构建验证**: -- ✅ `bun typecheck` - 类型检查通过 -- ✅ `bun fix` - 代码质量检查通过 -- ✅ `bun run build` - Vite 构建成功 -- ✅ `bun db:push` - 数据库初始化成功 -- ✅ SQLite 文件已创建 (`./data/app.db`, 12KB) - **注意事项**: -- SQLite 是文件数据库,适合嵌入环境和开发环境 -- 不支持多进程并发写入 (适合单应用实例) -- 性能足够支持中小型应用 (日请求 < 10万) +- SQLite 是文件数据库,适合嵌入环境和单实例应用 +- 不支持多进程并发写入 - 数据库文件和 WAL 文件 (`.db-shm`, `.db-wal`) 已添加到 `.gitignore` -- `better-sqlite3` 是原生模块,需要匹配生产环境的 OS 和架构 (构建输出会提示) +- `better-sqlite3` 是原生模块,构建时需匹配生产环境 OS 和架构 -**Drizzle 时间戳模式**: -- `timestamp_ms`: Unix 毫秒时间戳 (推荐,精度更高) -- `timestamp`: Unix 秒时间戳 -- Drizzle 自动转换 `Date` 对象 ↔ 数字 +### AGENTS.md 文档维护原则 -**命令对比**: -```bash -# 开发环境初始化 (推荐) -bun db:push +**核心原则**:AGENTS.md 只记录当前项目状态,不记录重构历史 -# 生产环境迁移 -bun db:generate # 生成迁移文件 -bun db:migrate # 执行迁移 +1. **当前状态优先**:只描述项目当前使用的技术栈、架构和最佳实践 +2. **无需向后兼容**:不保留旧技术栈的文档,重构后直接更新为新状态 +3. **避免历史记录**:不记录"从 X 迁移到 Y"的过程,只记录"当前使用 Y" +4. **保持简洁**:删除过时信息,避免上下文过长影响 AI 理解 +5. **及时同步**:架构变更后立即更新文档,确保文档与代码一致 -# 数据库管理 UI -bun db:studio -``` +**何时更新 AGENTS.md**: +- 更换技术栈(如数据库、框架) +- 修改项目架构或目录结构 +- 添加/移除重要依赖 +- 发现重要的最佳实践或经验教训 -### Git 工作流要求 - -**重要原则**:保持代码仓库与文档同步 - -当遇到技术问题、做出架构决策、或发现重要经验时: -1. **立即更新 AGENTS.md**:记录问题、原因、解决方案 -2. **持续同步**:每次重大变更后更新文档 -3. **版本关联**:在文档中标注相关的库版本、commit hash - -这确保未来的开发者(包括 AI 助手)能快速理解项目历史和技术选择。 +**何时不更新 AGENTS.md**: +- 日常功能开发 +- Bug 修复 +- 代码重构(不涉及架构变更) +- 临时实验或 POC ---