Files
WRNexusJS/packages/db/src/session-store.ts
T

50 lines
1.8 KiB
TypeScript

/**
* A persistent, process-shared session backend built on `bun:sqlite` (sync, so
* it satisfies `SessionBackend` without a load/save wrapper). Sessions survive
* restarts and are shared by every worker pointed at the same file.
*
* import { setSessionBackend } from "@wrnexus/core";
* import { sqliteSessionStore } from "@wrnexus/db/session";
* setSessionBackend(sqliteSessionStore("./sessions.db"));
*/
import { Database } from "bun:sqlite";
import type { SessionBackend, SessionEntry } from "@wrnexus/core";
export function sqliteSessionStore(path = "sessions.db"): SessionBackend {
const db = new Database(path);
db.run("PRAGMA journal_mode = WAL");
db.run(
"CREATE TABLE IF NOT EXISTS sessions (id TEXT PRIMARY KEY, data TEXT NOT NULL, expiresAt INTEGER NOT NULL)",
);
const getStmt = db.query("SELECT data, expiresAt FROM sessions WHERE id = ?");
const setStmt = db.query(
"INSERT INTO sessions (id, data, expiresAt) VALUES (?, ?, ?) " +
"ON CONFLICT(id) DO UPDATE SET data = excluded.data, expiresAt = excluded.expiresAt",
);
const delStmt = db.query("DELETE FROM sessions WHERE id = ?");
const gcStmt = db.query("DELETE FROM sessions WHERE expiresAt <= ?");
return {
get(id): SessionEntry | undefined {
const row = getStmt.get(id) as { data: string; expiresAt: number } | null;
if (!row) return undefined;
try {
return { data: JSON.parse(row.data) as Record<string, unknown>, expiresAt: row.expiresAt };
} catch (error) {
console.warn(`[wrnexus:db] discarded corrupt session '${id}'`, error);
return undefined;
}
},
set(id, entry) {
setStmt.run(id, JSON.stringify(entry.data), entry.expiresAt);
},
delete(id) {
delStmt.run(id);
},
gc(now) {
gcStmt.run(now);
},
};
}