79 lines
2.7 KiB
TypeScript
79 lines
2.7 KiB
TypeScript
/**
|
||
* 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
|
||
);
|
||
`,
|
||
},
|
||
];
|
||
|
||
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());
|
||
}
|
||
} |