50 lines
1.8 KiB
TypeScript
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);
|
|
},
|
|
};
|
|
}
|