import { Database } from 'bun:sqlite'; import { mkdir } from 'node:fs/promises'; import { dirname } from 'node:path'; import { env } from './env.ts'; await mkdir(dirname(env.stateDbPath), { recursive: true }); const db = new Database(env.stateDbPath, { create: true }); db.exec(` CREATE TABLE IF NOT EXISTS users ( user_id TEXT PRIMARY KEY, created_at INTEGER NOT NULL, provisioned_at INTEGER, deleted_at INTEGER ); CREATE TABLE IF NOT EXISTS activity ( user_id TEXT PRIMARY KEY, last_used INTEGER NOT NULL ); `); const now = () => Date.now(); export const state = { recordCreate: (userId: string): void => { db.prepare( `INSERT INTO users (user_id, created_at) VALUES (?, ?) ON CONFLICT(user_id) DO UPDATE SET deleted_at = NULL`, ).run(userId, now()); }, markProvisioned: (userId: string): void => { db.prepare(`UPDATE users SET provisioned_at = ? WHERE user_id = ?`).run(now(), userId); }, markDeleted: (userId: string): void => { db.prepare(`UPDATE users SET deleted_at = ? WHERE user_id = ?`).run(now(), userId); db.prepare(`DELETE FROM activity WHERE user_id = ?`).run(userId); }, touch: (userId: string): void => { db.prepare( `INSERT INTO activity (user_id, last_used) VALUES (?, ?) ON CONFLICT(user_id) DO UPDATE SET last_used = excluded.last_used`, ).run(userId, now()); }, // 查找过期:最后使用时间距今 > timeoutMs findIdle: (timeoutMs: number): string[] => { const cutoff = now() - timeoutMs; return db .prepare(`SELECT user_id FROM activity WHERE last_used < ?`) .all(cutoff) .map((r: any) => r.user_id); }, clearActivity: (userId: string): void => { db.prepare(`DELETE FROM activity WHERE user_id = ?`).run(userId); }, };