Spaces:
Runtime error
Runtime error
File size: 4,977 Bytes
cd8bd0a | 1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 55 56 57 58 59 60 61 62 63 64 65 66 67 68 69 70 71 72 73 74 75 76 77 78 79 80 81 82 83 84 85 86 87 88 89 90 91 92 93 94 95 96 97 98 99 100 101 102 103 104 105 106 107 108 109 110 111 112 113 114 115 116 117 118 119 120 121 122 123 124 125 126 127 128 129 130 131 132 133 134 135 136 137 138 139 140 141 142 143 144 145 146 147 148 149 150 151 152 153 154 155 156 157 158 159 160 161 162 | /**
* Database module: InspectorSessions
* CRUD + snapshot for inspector_sessions and inspector_session_requests tables.
*/
import { randomUUID } from "crypto";
import { getDbInstance } from "./core";
import type { InspectorSessionRow } from "./_rowTypes";
import { InterceptedRequestSchema } from "../../mitm/inspector/types";
import type { InterceptedRequest } from "../../mitm/inspector/types";
interface InspectorSessionDbRow {
id: string;
name: string | null;
started_at: string;
ended_at: string | null;
request_count: number;
profile: string | null;
}
interface InspectorSessionRequestDbRow {
session_id: string;
seq: number;
payload: string;
}
function mapSessionRow(row: InspectorSessionDbRow): InspectorSessionRow {
return {
id: row.id,
name: row.name,
started_at: row.started_at,
ended_at: row.ended_at,
request_count: row.request_count,
profile: row.profile as "llm" | "custom" | "all" | null,
};
}
export function createSession(opts?: {
name?: string;
profile?: "llm" | "custom" | "all";
}): { id: string; started_at: string } {
const db = getDbInstance();
const id = randomUUID();
const started_at = new Date().toISOString();
db.prepare(
`INSERT INTO inspector_sessions (id, name, started_at, profile) VALUES (?, ?, ?, ?)`
).run(id, opts?.name ?? null, started_at, opts?.profile ?? null);
return { id, started_at };
}
export function stopSession(id: string): void {
const db = getDbInstance();
const ended_at = new Date().toISOString();
db.prepare("UPDATE inspector_sessions SET ended_at = ? WHERE id = ?").run(ended_at, id);
}
export function renameSession(id: string, name: string): void {
const db = getDbInstance();
db.prepare("UPDATE inspector_sessions SET name = ? WHERE id = ?").run(name, id);
}
export function listSessions(): InspectorSessionRow[] {
const db = getDbInstance();
const rows = db
.prepare("SELECT * FROM inspector_sessions ORDER BY started_at DESC")
.all() as InspectorSessionDbRow[];
return rows.map(mapSessionRow);
}
export function getSession(id: string): InspectorSessionRow | null {
const db = getDbInstance();
const row = db
.prepare("SELECT * FROM inspector_sessions WHERE id = ?")
.get(id) as InspectorSessionDbRow | undefined;
return row ? mapSessionRow(row) : null;
}
export function appendSessionRequest(sessionId: string, payload: string): number {
const db = getDbInstance();
let insertedSeq = 0;
const runTransaction = db.transaction(() => {
// Get next seq atomically within transaction
const seqRow = db
.prepare(
"SELECT COALESCE(MAX(seq), 0) + 1 AS next_seq FROM inspector_session_requests WHERE session_id = ?"
)
.get(sessionId) as { next_seq: number };
const nextSeq = seqRow.next_seq;
db.prepare(
`INSERT INTO inspector_session_requests (session_id, seq, payload) VALUES (?, ?, ?)`
).run(sessionId, nextSeq, payload);
db.prepare(
"UPDATE inspector_sessions SET request_count = request_count + 1 WHERE id = ?"
).run(sessionId);
insertedSeq = nextSeq;
});
runTransaction();
return insertedSeq;
}
export function getSessionRequests(sessionId: string): Array<{ seq: number; payload: string }> {
const db = getDbInstance();
const rows = db
.prepare(
"SELECT seq, payload FROM inspector_session_requests WHERE session_id = ? ORDER BY seq ASC"
)
.all(sessionId) as InspectorSessionRequestDbRow[];
return rows.map((r) => ({ seq: r.seq, payload: r.payload }));
}
export function deleteSession(id: string): void {
const db = getDbInstance();
// Cascade via FK ON DELETE CASCADE for inspector_session_requests
db.prepare("DELETE FROM inspector_sessions WHERE id = ?").run(id);
}
/**
* Return a parsed + validated snapshot of all requests for the given session,
* sorted by ascending seq.
*
* Returns null when the session does not exist.
* Rows whose payload fails InterceptedRequestSchema validation are silently
* skipped (defensive — protects callers from corrupt/partial rows).
*
* Satisfies master-plan §3.8 (F2 spec) named-export contract.
*/
export function snapshotSession(sessionId: string): InterceptedRequest[] | null {
// 1. Verify session exists.
const session = getSession(sessionId);
if (session === null) return null;
// 2. Retrieve raw rows (already ordered by seq ASC).
const rawRows = getSessionRequests(sessionId);
// 3. Parse each payload JSON, validate via Zod schema, skip bad rows.
const results: InterceptedRequest[] = [];
for (const row of rawRows) {
let parsed: unknown;
try {
parsed = JSON.parse(row.payload);
} catch {
// Corrupt JSON — skip.
continue;
}
const result = InterceptedRequestSchema.safeParse(parsed);
if (result.success) {
results.push(result.data as InterceptedRequest);
}
// Invalid rows are silently skipped per defensive contract.
}
return results;
}
|