数据库
Elftia 使用 better-sqlite3 + Drizzle ORM 作为本地数据库方案,所有数据库操作通过 Worker Thread 异步执行。
技术选型
| 项 | 选择 | 原因 |
|---|---|---|
| 数据库引擎 | better-sqlite3 | 嵌入式、零配置、高性能 |
| ORM | Drizzle ORM | 类型安全、零运行时开销、SQL-like API |
| 模式 | WAL (Write-Ahead Logging) | 支持并发读取、写入不阻塞读取 |
| 访问模式 | Worker Thread RPC | 避免主线程阻塞 |
数据库位置
{userData}/elftia.db # 主数据库文件
{userData}/elftia.db-wal # WAL 日志
{userData}/elftia.db-shm # 共享内存文件
{userData} 在不同平台上的路径:
- Windows:
%APPDATA%/elftia/ - macOS:
~/Library/Application Support/elftia/ - Linux:
~/.config/elftia/
Worker Thread 架构
所有数据库操作通过 DbClient -> db.worker.ts 的 RPC 模式执行:
sequenceDiagram
participant S as 服务层 (Service)
participant C as DbClient (主线程)
participant W as db.worker.ts (Worker Thread)
participant DB as SQLite (WAL)
Note over S,DB: 初始化
C->>W: 创建 Worker({dbPath})
W->>DB: new Database(dbPath)
W->>DB: PRAGMA journal_mode = WAL
W->>DB: drizzle(db) + migrate()
W-->>C: ready 信号
Note over S,DB: 正常操作
S->>C: db.chatSessionsList()
C->>C: id = ++seq
C->>W: postMessage({id, method: 'chatSessions:list', params})
W->>DB: SELECT * FROM chat_sessions ...
DB-->>W: 结果行
W-->>C: postMessage({id, result: rows})
C->>C: pending[id].resolve(rows)
C-->>S: Promise<Session[]>
DbClient 核心实现
{/* packages/desktop/app/main/workers/DbClient.ts */}
class DbClient extends EventEmitter implements DbRpc {
private worker: Worker;
private seq = 0;
private pending = Map<number, { resolve, reject }>;
constructor(dbPath: string, options?: DbClientOptions) {
this.worker = new Worker(workerPath, {
workerData: { dbPath, diagnosticsDir },
});
this.worker.on('message', (msg) => this.handleMessage(msg));
}
async ready(): Promise<void>;
private async send(method: string, params: unknown): Promise<unknown> {
const id = ++this.seq;
return new Promise((resolve, reject) => {
this.pending.set(id, { resolve, reject });
this.worker.postMessage({ id, method, params });
});
}
// 100+ 类型化方法
chatSessionsList(): Promise<ChatSession[]>;
chatMessagesInsert(msg: ChatMessageInput): Promise<void>;
auditLogInsert(entry: AuditLogEntry): Promise<void>;
}
RPC 类型定义
{/* packages/desktop/app/main/workers/types.ts */}
interface DbRequest {
id: number;
method: string;
params: unknown;
}
interface DbResponse {
id: number;
result?: unknown;
error?: { message: string; stack?: string };
}
interface DbRpc {
chatSessionsList(): Promise<ChatSession[]>;
chatSessionsGet(id: string): Promise<ChatSession | null>;
chatSessionsCreate(input: ChatSessionInput): Promise<ChatSession>;
chatSessionsUpdate(id: string, data: Partial<ChatSession>): Promise<void>;
chatSessionsDelete(id: string): Promise<void>;
// ... 100+ 方法
}
Schema 组织
数据库 Schema 使用 Drizzle ORM 定义,位于 packages/desktop/app/main/db/schema/ 目录:
packages/desktop/app/main/db/schema/
├── index.ts # 统一导出
├── accounts.ts # 用户账户
├── chat.ts # 聊天消息
├── sessions.ts # 聊天会话
├── llm.ts # LLM 提供商/模型/密钥
├── projects.ts # 项目
├── settings.ts # 应用设置
├── attachments.ts # 附件
├── media.ts # 媒体资源
└── templates.ts # 模板
核心数据表
聊天相关
{/* 简化的表结构定义 */}
// 聊天会话
interface ChatSession {
id: string; // UUID
title: string; // 会话标题
type: 'chat' | 'roleplay' | 'agent';
agentId?: string; // 关联的 Agent ID
personaId?: string; // 关联的 Persona ID
providerId?: string; // LLM 提供商 ID
modelId?: string; // 模型 ID
rpConfig?: RPConfig; // RP 配置 (JSON)
createdAt: number;
updatedAt: number;
lastMessageAt?: number;
messageCount: number;
pinned: boolean;
folderId?: string;
}
// 聊天消息
interface ChatMessage {
id: string; // UUID
sessionId: string; // 所属会话 ID
role: 'user' | 'assistant' | 'system';
content: string; // 消息内容
parentId?: string; // 父消息 ID (分支支持)
reasoning?: string; // 推理内容 (thinking)
toolCalls?: ToolCall[]; // 工具调用 (JSON)
meta?: MessageMeta; // 元数据 (providerId, modelId, tokenCount)
createdAt: number;
}
账户相关
// 用户账户
interface Account {
id: string;
email: string;
displayName?: string;
avatarUrl?: string;
createdAt: number;
}
// 账户令牌
interface AccountToken {
id: string;
accountId: string;
provider: string; // OAuth 提供商
accessToken: string; // 加密存储
refreshToken?: string;
expiresAt?: number;
}
LLM 配置
// LLM 提供商
interface LLMProvider {
id: string;
name: string;
type: string; // openai, anthropic, google 等
apiKey?: string; // 加密存储
baseUrl?: string; // 自定义 API 端点
enabled: boolean;
models: string[]; // 可用模型列表
}
// API 密钥池 (多密钥支持)
interface ApiKeyEntry {
id: string;
providerId: string;
label?: string;
apiKey: string; // 加密存储
weight: number; // 轮询权重
enabled: boolean;
createdAt: number;
}
项目相关
// 项目
interface Project {
id: string;
name: string;
path: string; // 文件系统路径
description?: string;
createdAt: number;
updatedAt: number;
}
安全审计
// 审计日志
interface AuditLogEntry {
id: string;
timestamp: number;
eventType: string; // tool_executed, injection_detected 等
severity: 'info' | 'warning' | 'critical';
channelId?: string;
userId?: string;
toolName?: string;
details: string; // JSON 序列化的详情
}
媒体与资源
// 媒体资源
interface MediaResource {
id: string;
sessionId: string;
messageId: string;
filePath: string;
mimeType: string;
thumbnailPath?: string;
size: number;
createdAt: number;
}
// 附件
interface Attachment {
id: string;
sessionId: string;
messageId: string;
name: string;
type: string;
url?: string;
size: number;
}
其他核心表
| 表 | 用途 | Schema 文件 |
|---|---|---|
settings | 应用设置 (key-value) | schema/settings.ts |
templates | 图片模板 | schema/templates.ts |
channel_instances | Channel 渠道实例 | Worker db/channels.ts |
channel_users | Channel 用户 | Worker db/channelUsers.ts |
character_cards | 角色卡 | Worker db/characterCards.ts |
world_info_books | 世界信息书籍 | Worker db/worldInfo.ts |
world_info_entries | 世界信息条目 | Worker db/worldInfo.ts |
custom_agents | 自定义 Agent | Worker db/customAgents.ts |
personas | Persona 定义 | Worker db/personas.ts |
user_skills | 用户 Skill | Worker db/userSkills.ts |
tags | 标签 | Worker db/tags.ts |
notes | 笔记索引 | Worker db/notes.ts |
note_folders | 笔记文件夹 | Worker db/noteFolders.ts |
cron_jobs | 定时任务 | CronService 文件系统 |
DB Worker 模块
数据库操作的具体实现分布在 packages/desktop/app/main/workers/db/ 目录下的多个模块中:
workers/db/
├── index.ts # 主分发器,根据 method 前缀路由到对应模块
├── chatSessions.ts # chatSessions:* 方法
├── chatMessages.ts # chatMessages:* 方法
├── customAgents.ts # customAgents:* 方法
├── personas.ts # personas:* 方法
├── characterCards.ts # characterCards:* 方法
├── worldInfo.ts # worldInfo:* 方法
├── groupChat.ts # groupChat:* 方法
├── channels.ts # channels:* 方法
├── channelUsers.ts # channelUsers:* 方法
├── auditLog.ts # auditLog:* 方法
├── apiKeys.ts # apiKeys:* 方法
├── tags.ts # tags:* 方法
├── notes.ts # notes:* 方法
├── noteFolders.ts # noteFolders:* 方法
├── userSkills.ts # userSkills:* 方法
├── rpDefaults.ts # rpDefaults:* 方法
└── characterSprites.ts # sprites:* 方法
迁移系统
Drizzle 迁移
Schema 变更通过 Drizzle 的 migrate() 自动应用:
{/* packages/desktop/app/main/db/index.ts */}
function initDatabase() {
const sqlite = new Database(dbPath);
sqlite.pragma('journal_mode = WAL');
sqlite.pragma('foreign_keys = ON');
const db = drizzle(sqlite);
migrate(db, { migrationsFolder: 'drizzle' });
}
自定义迁移
复杂的数据迁移逻辑在 migrations.ts 中实现:
{/* 自定义迁移示例 */}
async function migrateApiKeyPool(db: DbRpc) {
const providers = await db.llmProvidersList();
for (const p of providers) {
if (p.apiKey) {
await db.apiKeysInsert({
providerId: p.id,
apiKey: p.apiKey,
weight: 1,
enabled: true,
});
}
}
}
初始化时序
sequenceDiagram
participant M as Main Process
participant C as DbClient
participant W as db.worker.ts
participant D as SQLite
M->>C: new DbClient(dbPath)
C->>W: 创建 Worker Thread
W->>D: new Database(dbPath)
W->>D: PRAGMA journal_mode = WAL
W->>D: PRAGMA foreign_keys = ON
W->>W: 注册所有 RPC 方法
W-->>C: ready 信号
M->>C: await db.ready()
Note over M: Worker 初始化完成后继续
M->>M: initDatabase()
Note over M: Drizzle ORM 初始化 + migrate
M->>M: 创建依赖 db 的服务
关键: await db.ready() 必须在创建其他服务之前完成,防止 Worker 初始化期间出现 SQLITE_BUSY 错误。
数据库优化
DatabaseOptimizer 服务定期执行维护操作:
| 操作 | 说明 | 频率 |
|---|---|---|
PRAGMA optimize | 统计信息更新 | 应用关闭时 |
VACUUM | 回收空间 | 手动触发 |
ANALYZE | 更新查询计划 | 定期 |
| WAL checkpoint | 合并 WAL 到主文件 | SQLite 自动 |
相关文件
| 文件 | 说明 |
|---|---|
packages/desktop/app/main/db/index.ts | 数据库初始化 (WAL + Drizzle + migrate) |
packages/desktop/app/main/db/schema/ | Drizzle ORM Schema 定义目录 |
packages/desktop/app/main/workers/DbClient.ts | 数据库 Worker 客户端 |
packages/desktop/app/main/workers/db.worker.ts | 数据库 Worker 实现 |
packages/desktop/app/main/workers/db/ | 按领域拆分的 DB 操作模块 |
packages/desktop/app/main/workers/types.ts | DbRpc 接口定义 |
packages/desktop/app/main/services/persistence/db-optimizer/DatabaseOptimizer.ts | 数据库优化服务 |
packages/desktop/app/main/services/platform/migration/MigrationService.ts | 自定义迁移服务 |