(slug: string)
| 258 | * shipped a regression test for). |
| 259 | */ |
| 260 | export function deleteProjectRow(slug: string): void { |
| 261 | const db = getDb(); |
| 262 | const childTables = [ |
| 263 | // tasks must come before scheduled_jobs / sessions because both have |
| 264 | // optional FK references back to tasks(id) on delete-set-null. Order |
| 265 | // within the loop otherwise doesn't matter — we just need every |
| 266 | // child row gone before the parent project row. |
| 267 | "tasks", |
| 268 | // approval_comments cascades from approvals. |
| 269 | "approvals", |
| 270 | "approval_policies", |
| 271 | "questions", |
| 272 | "cost_events", |
| 273 | "oauth_tokens", |
| 274 | "mcp_tokens", |
| 275 | // scheduled_job_runs cascades from scheduled_jobs. |
| 276 | "scheduled_jobs", |
| 277 | // transcript_events cascades from sessions. |
| 278 | "sessions", |
| 279 | "agent_actions", |
| 280 | "sequence_runs", |
| 281 | ]; |
| 282 | for (const table of childTables) { |
| 283 | try { |
| 284 | db.prepare(`DELETE FROM ${table} WHERE project_slug = ?`).run(slug); |
| 285 | } catch { |
| 286 | // table missing on this install (e.g., older DB pre-migration). |
| 287 | // Per-table try/catch so one missing table doesn't block the rest. |
| 288 | } |
| 289 | } |
| 290 | db.prepare("DELETE FROM projects WHERE slug = ?").run(slug); |
| 291 | } |
| 292 | |
| 293 | /** |
| 294 | * Per-project hide-list for preset MCP catalog entries. See migration |
no test coverage detected