diff --git a/.jules/bolt.md b/.jules/bolt.md new file mode 100644 index 00000000..04630a4a --- /dev/null +++ b/.jules/bolt.md @@ -0,0 +1,5 @@ +## 2025-01-30 - Replace length checks on full memory fetches with Astro DB aggregations + +**Learning:** When retrieving counts for multiple tables or filtered sets (e.g. `Project`, `AgentTask`, `Request`) to display dashboard KPIs, performing full memory queries (using `db.select().from(Table)`) just to read their `.length` property creates significant N+1 and memory overhead, especially for larger data sets. In this codebase's architecture using Drizzle ORM over Astro DB, you can offload these aggregations directly to the database. + +**Action:** Replaced full row fetches mapping to `.length` arrays in `src/pages/api/dashboard-kpis.ts` with direct Drizzle ORM `db.select({ count: count() })` along with appropriate `where` conditions (`eq`, `gte`, `inArray`). This performs the counting fully on the SQLite layer, dramatically reducing memory payload and computation time. diff --git a/src/pages/api/dashboard-kpis.ts b/src/pages/api/dashboard-kpis.ts index 5cf2dd56..a9127015 100644 --- a/src/pages/api/dashboard-kpis.ts +++ b/src/pages/api/dashboard-kpis.ts @@ -19,11 +19,6 @@ function countRunningSessions(sessions: unknown[]): number { }).length; } -function isOpenQueueStatus(st: string): boolean { - const s = String(st).toLowerCase(); - return s === 'open' || s === 'in_progress'; -} - export const GET: APIRoute = async () => { const base = { projectCount: 0, @@ -42,30 +37,26 @@ export const GET: APIRoute = async () => { }; try { - const { db, Project, AgentTask, Request, AgentAppIssue, AgentDependencyRequest, eq } = + const { db, Project, AgentTask, Request, AgentAppIssue, AgentDependencyRequest, eq, count, inArray, gte } = await loadAstroDb(); const today = new Date(); today.setHours(0, 0, 0, 0); - const [projects, tasksAll, openRequests, issuesAll, depsAll] = await Promise.all([ - db.select().from(Project), - db.select().from(AgentTask), - db.select().from(Request).where(eq(Request.status, 'pending')), - db.select().from(AgentAppIssue), - db.select().from(AgentDependencyRequest), + const [projectsRes, tasksTotalRes, tasksTodayRes, openRequestsRes, openIssuesRes, openDepsRes] = await Promise.all([ + db.select({ count: count() }).from(Project), + db.select({ count: count() }).from(AgentTask), + db.select({ count: count() }).from(AgentTask).where(gte(AgentTask.createdAt, today)), + db.select({ count: count() }).from(Request).where(eq(Request.status, 'pending')), + db.select({ count: count() }).from(AgentAppIssue).where(inArray(AgentAppIssue.status, ['open', 'in_progress'])), + db.select({ count: count() }).from(AgentDependencyRequest).where(inArray(AgentDependencyRequest.status, ['open', 'in_progress'])), ]); - const tasksTodayCount = tasksAll.filter((t) => { - const d = t.createdAt instanceof Date ? t.createdAt : new Date(t.createdAt as Date); - return d >= today; - }).length; - - base.projectCount = projects.length; - base.tasksTotal = tasksAll.length; - base.tasksToday = tasksTodayCount; - base.openRequests = openRequests.length; - base.openAppIssues = issuesAll.filter((r) => isOpenQueueStatus(String(r.status))).length; - base.openDependencyRequests = depsAll.filter((r) => isOpenQueueStatus(String(r.status))).length; + base.projectCount = Number(projectsRes[0]?.count ?? 0); + base.tasksTotal = Number(tasksTotalRes[0]?.count ?? 0); + base.tasksToday = Number(tasksTodayRes[0]?.count ?? 0); + base.openRequests = Number(openRequestsRes[0]?.count ?? 0); + base.openAppIssues = Number(openIssuesRes[0]?.count ?? 0); + base.openDependencyRequests = Number(openDepsRes[0]?.count ?? 0); } catch (e) { const msg = e instanceof Error ? e.message : String(e); base.dbError = msg;