aps-agent/server/core/storage/db.ts

93 lines
3.2 KiB
TypeScript
Raw Blame History

This file contains ambiguous Unicode characters

This file contains Unicode characters that might be confused with other characters. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.

/**
* SQLite 存储(任务 A3.7,依据 ADR-0007 与决策 D16)。
*
* - 驱动用 Node 内置 node:sqlite:零原生依赖,不需要为 Electron ABI 重编译
* - 时间统一存整数毫秒,主键由应用侧生成(ADR-0007 两条约定)
* - 表按 ADR-0008 的「平台库」建;审计、待确认动作、桌面授权三组等各自功能落地时再加
*/
import { mkdirSync } from 'node:fs';
import { dirname } from 'node:path';
import { DatabaseSync } from 'node:sqlite';
export type Database = DatabaseSync;
const MIGRATIONS: Array<{ version: number; sql: string }> = [
{
version: 1,
sql: `
create table if not exists projects (
project_id text primary key,
name text not null,
status text not null,
writable integer not null default 1,
created_at integer not null
);
create table if not exists sessions (
session_id text primary key,
project_id text,
scope text not null,
status text not null,
title text not null,
owner_user_id text not null,
owner_tenant_id text not null,
owner_display_name text not null,
owner_client_kind text not null,
created_at integer not null
);
create index if not exists idx_sessions_project on sessions(project_id, created_at);
create table if not exists messages (
message_id text primary key,
session_id text not null,
role text not null,
text text not null,
ts integer not null
);
create index if not exists idx_messages_session on messages(session_id, ts);
create table if not exists idempotency_keys (
idempotency_key text primary key,
session_id text not null,
message_id text not null,
run_id text not null,
created_at integer not null
);
`,
},
{
version: 2,
sql: `
create table if not exists context_checkpoints (
session_id text primary key,
summary text not null,
summary_source_ids text not null,
included_message_ids text not null,
last_message_id text,
pinned text not null default '[]',
estimated_chars integer not null default 0,
updated_at integer not null
);
`,
},
];
export function openDatabase(path: string): Database {
if (path !== ':memory:') mkdirSync(dirname(path), { recursive: true });
const db = new DatabaseSync(path);
try {
db.exec('pragma journal_mode = WAL');
} catch {
/* 内存库不支持 WAL */
}
db.exec('pragma foreign_keys = ON');
migrate(db);
return db;
}
export function migrate(db: Database): void {
// 先建迁移表本身,否则第一次检查版本号就会报 no such table
db.exec('create table if not exists schema_migrations (version integer primary key, applied_at integer not null)');
for (const migration of MIGRATIONS) {
const applied = db.prepare('select version from schema_migrations where version = ?').get(migration.version);
if (applied) continue;
db.exec(migration.sql);
db.prepare('insert into schema_migrations (version, applied_at) values (?, ?)').run(migration.version, Date.now());
}
}