diff --git a/apps/web/src/components/nosql-explorer/connection-service.ts b/apps/web/src/components/nosql-explorer/connection-service.ts index 8a49d01b..5b2f15ca 100644 --- a/apps/web/src/components/nosql-explorer/connection-service.ts +++ b/apps/web/src/components/nosql-explorer/connection-service.ts @@ -89,7 +89,13 @@ export const getConnections = async ( _userId: string, encryptionKey: CryptoKey ): Promise => { - const raw = (await proxyRequest("GET", "/api/v1/nosql/connections")) ?? []; + const rawAll = (await proxyRequest("GET", "/api/v1/nosql/connections")) ?? []; + // Drop malformed rows — any item missing `id`/`encryptedData`/`iv` would crash + // downstream renders that key off `conn.id`. + const raw = rawAll.filter( + (c): c is ConnectionRaw => + !!c && typeof c === "object" && typeof c.id === "string" && !!c.encryptedData && !!c.iv + ); const results = await Promise.allSettled( raw.map(async (conn): Promise => { diff --git a/apps/web/src/components/nosql-explorer/explorer-sidebar.tsx b/apps/web/src/components/nosql-explorer/explorer-sidebar.tsx index 0d622965..12e65ef8 100644 --- a/apps/web/src/components/nosql-explorer/explorer-sidebar.tsx +++ b/apps/web/src/components/nosql-explorer/explorer-sidebar.tsx @@ -363,6 +363,9 @@ export function ExplorerSidebar({ const matchesSearch = (text: string) => text.toLowerCase().includes(searchQuery.toLowerCase()); const filteredConnections = connections.filter(node => { + // Drop malformed nodes — any entry missing `connection.id` would crash + // the renderer (which reads `node.connection.id` as the React key). + if (!node?.connection || typeof node.connection.id !== "string") return false; if (!searchQuery) return true; if (matchesSearch(node.connection.name)) return true; diff --git a/apps/web/src/components/nosql-explorer/tab-bar.tsx b/apps/web/src/components/nosql-explorer/tab-bar.tsx index 26939dee..90cb3873 100644 --- a/apps/web/src/components/nosql-explorer/tab-bar.tsx +++ b/apps/web/src/components/nosql-explorer/tab-bar.tsx @@ -33,11 +33,15 @@ export function TabBar({ tabs, activeTabId, onTabChange, onTabClose, onCloseAll return
; } + // Defensive: filter out malformed entries — a single undefined/missing-id tab + // would crash the whole component (`tab.id` read on undefined inside .map). + const safeTabs = tabs.filter((t): t is ExplorerTab => !!t && typeof t.id === "string"); + return (
- {tabs.map((tab) => { + {safeTabs.map((tab) => { const isActive = activeTabId === tab.id; const isFiltered = hasActiveQuery(tab); return (