@libredb/studio 0.9.7

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (572) hide show
  1. package/.claude/settings.local.json +127 -0
  2. package/.cursorrules +426 -0
  3. package/.devin/wiki.json +143 -0
  4. package/.dockerignore +80 -0
  5. package/.env.example +159 -0
  6. package/.github/ISSUE_TEMPLATE/bug_report.md +49 -0
  7. package/.github/ISSUE_TEMPLATE/feature_request.md +29 -0
  8. package/.github/PULL_REQUEST_TEMPLATE.md +57 -0
  9. package/.github/workflows/ci.yml +185 -0
  10. package/.github/workflows/codeql.yml +57 -0
  11. package/.github/workflows/docker-build-push.yml +118 -0
  12. package/.github/workflows/helm-release.yml +113 -0
  13. package/CLAUDE.md +265 -0
  14. package/CODE_OF_CONDUCT.md +124 -0
  15. package/CONTRIBUTING.md +154 -0
  16. package/Dockerfile +73 -0
  17. package/LICENSE +21 -0
  18. package/README.md +614 -0
  19. package/SECURITY.md +107 -0
  20. package/artifacthub-repo.yml +4 -0
  21. package/bun.lock +1714 -0
  22. package/bunfig.toml +3 -0
  23. package/charts/libredb-studio/.helmignore +11 -0
  24. package/charts/libredb-studio/Chart.lock +6 -0
  25. package/charts/libredb-studio/Chart.yaml +50 -0
  26. package/charts/libredb-studio/README.md +206 -0
  27. package/charts/libredb-studio/templates/NOTES.txt +59 -0
  28. package/charts/libredb-studio/templates/_helpers.tpl +135 -0
  29. package/charts/libredb-studio/templates/configmap.yaml +37 -0
  30. package/charts/libredb-studio/templates/deployment.yaml +184 -0
  31. package/charts/libredb-studio/templates/hpa.yaml +32 -0
  32. package/charts/libredb-studio/templates/ingress.yaml +41 -0
  33. package/charts/libredb-studio/templates/networkpolicy.yaml +50 -0
  34. package/charts/libredb-studio/templates/pdb.yaml +18 -0
  35. package/charts/libredb-studio/templates/pvc.yaml +23 -0
  36. package/charts/libredb-studio/templates/secret.yaml +30 -0
  37. package/charts/libredb-studio/templates/seed-configmap.yaml +11 -0
  38. package/charts/libredb-studio/templates/service.yaml +22 -0
  39. package/charts/libredb-studio/templates/serviceaccount.yaml +13 -0
  40. package/charts/libredb-studio/values.schema.json +246 -0
  41. package/charts/libredb-studio/values.yaml +286 -0
  42. package/components.json +22 -0
  43. package/conductor/code_styleguides/typescript.md +43 -0
  44. package/conductor/product-guidelines.md +43 -0
  45. package/conductor/product.md +3 -0
  46. package/conductor/setup_state.json +1 -0
  47. package/conductor/tech-stack.md +39 -0
  48. package/conductor/tracks/enhance_postgres_monitoring_20251227/metadata.json +8 -0
  49. package/conductor/tracks/enhance_postgres_monitoring_20251227/plan.md +44 -0
  50. package/conductor/tracks/enhance_postgres_monitoring_20251227/spec.md +31 -0
  51. package/conductor/tracks.md +8 -0
  52. package/conductor/workflow.md +333 -0
  53. package/database-compose.yml +55 -0
  54. package/docker/postgres-init/01-extensions.sql +10 -0
  55. package/docker/postgres-init/02-sample-data.sql +585 -0
  56. package/docker/postgres.yml +68 -0
  57. package/docker-compose.yml +38 -0
  58. package/docs/AI_PLAN.md +74 -0
  59. package/docs/API_DOCS.md +875 -0
  60. package/docs/ARCHITECTURE.md +218 -0
  61. package/docs/DATABASE_PROVIDERS.md +358 -0
  62. package/docs/FEATURES.md +116 -0
  63. package/docs/HELM_CHART.md +252 -0
  64. package/docs/LOGIN_PAGE.md +178 -0
  65. package/docs/MONACO_EDITOR_PERFORMANCE.md +315 -0
  66. package/docs/OIDC_ARCH.md +681 -0
  67. package/docs/OIDC_SETUP.md +322 -0
  68. package/docs/POSTGRES_METRICS.md +516 -0
  69. package/docs/QUERY_OPTIMIZATION.md +370 -0
  70. package/docs/SEED_CONNECTIONS.md +468 -0
  71. package/docs/SQL_ALIAS_COMPLETION.md +190 -0
  72. package/docs/STORAGE_ARCHITECTURE.md +565 -0
  73. package/docs/STORAGE_QUICK_SETUP.md +419 -0
  74. package/docs/TECHNICAL_PLAN.md +36 -0
  75. package/docs/THEMING.md +345 -0
  76. package/docs/adding-a-new-database-provider.md +642 -0
  77. package/docs/backlogs/000-PLATFORM_DATA_SYNC_DATABASE.md +360 -0
  78. package/docs/backlogs/001-INLINE_DATA_EDITING.md +118 -0
  79. package/docs/backlogs/002-DATA_IMPORT.md +215 -0
  80. package/docs/backlogs/003-QUERY_TIME_MACHINE.md +183 -0
  81. package/docs/backlogs/004-AI_DATA_STORYTELLER.md +292 -0
  82. package/docs/backlogs/005-QUERY_PLAYGROUND.md +352 -0
  83. package/docs/backlogs/006-DATA_MASKING.md +418 -0
  84. package/docs/enterprise-features.md +718 -0
  85. package/docs/kubernetes-helm-chart-artifacthub-plan.md +803 -0
  86. package/docs/medium-koyeb-article-en.md +215 -0
  87. package/docs/plans/test-plans.md +445 -0
  88. package/docs/releases/RELEASE.V0.3.0.md +22 -0
  89. package/docs/releases/RELEASE.V0.4.0.md +154 -0
  90. package/docs/releases/RELEASE.V0.5.0.md +252 -0
  91. package/docs/releases/RELEASE_v0.5.6.md +145 -0
  92. package/docs/releases/RELEASE_v0.6.1.md +303 -0
  93. package/docs/releases/RELEASE_v0.6.7.md +292 -0
  94. package/docs/releases/RELEASE_v0.7.0.md +332 -0
  95. package/docs/releases/RELEASE_v0.8.0.md +521 -0
  96. package/docs/sampledb/titanic.sql +1379 -0
  97. package/docs/superpowers/plans/2026-03-25-seed-connections.md +1362 -0
  98. package/docs/superpowers/specs/2026-03-25-seed-connections-design.md +590 -0
  99. package/e2e/admin-dashboard.spec.ts +64 -0
  100. package/e2e/connection-management.spec.ts +58 -0
  101. package/e2e/export.spec.ts +34 -0
  102. package/e2e/login.spec.ts +85 -0
  103. package/e2e/query-execution.spec.ts +35 -0
  104. package/e2e/tab-management.spec.ts +64 -0
  105. package/eslint.config.mjs +28 -0
  106. package/fly.toml +43 -0
  107. package/next.config.ts +32 -0
  108. package/package.json +130 -0
  109. package/playwright.config.ts +34 -0
  110. package/postcss.config.mjs +7 -0
  111. package/public/favicon-32x32.png +0 -0
  112. package/public/favicon.ico +0 -0
  113. package/public/file.svg +1 -0
  114. package/public/globe.svg +1 -0
  115. package/public/logo.svg +32 -0
  116. package/public/next.svg +1 -0
  117. package/public/screenshots/code-generator.png +0 -0
  118. package/public/screenshots/connection-modal.png +0 -0
  119. package/public/screenshots/data-profiler.png +0 -0
  120. package/public/screenshots/erd-diagram.png +0 -0
  121. package/public/screenshots/hero-editor.png +0 -0
  122. package/public/screenshots/nl2sql.png +0 -0
  123. package/public/vercel.svg +1 -0
  124. package/public/window.svg +1 -0
  125. package/render.yaml +58 -0
  126. package/scripts/merge-lcov.mjs +239 -0
  127. package/sonar-project.properties +16 -0
  128. package/src/app/admin/error.tsx +46 -0
  129. package/src/app/admin/page.tsx +10 -0
  130. package/src/app/api/admin/audit/route.ts +52 -0
  131. package/src/app/api/admin/fleet-health/route.ts +81 -0
  132. package/src/app/api/ai/autopilot/route.ts +105 -0
  133. package/src/app/api/ai/chat/route.ts +132 -0
  134. package/src/app/api/ai/describe-schema/route.ts +52 -0
  135. package/src/app/api/ai/explain/route.ts +86 -0
  136. package/src/app/api/ai/impact/route.ts +97 -0
  137. package/src/app/api/ai/index-advisor/route.ts +98 -0
  138. package/src/app/api/ai/nl2sql/route.ts +87 -0
  139. package/src/app/api/ai/query-safety/route.ts +87 -0
  140. package/src/app/api/auth/login/route.ts +62 -0
  141. package/src/app/api/auth/logout/route.ts +25 -0
  142. package/src/app/api/auth/me/route.ts +10 -0
  143. package/src/app/api/auth/oidc/callback/route.ts +82 -0
  144. package/src/app/api/auth/oidc/login/route.ts +43 -0
  145. package/src/app/api/connections/managed/route.ts +35 -0
  146. package/src/app/api/db/cancel/route.ts +42 -0
  147. package/src/app/api/db/disconnect/route.ts +28 -0
  148. package/src/app/api/db/health/route.ts +49 -0
  149. package/src/app/api/db/maintenance/route.ts +72 -0
  150. package/src/app/api/db/monitoring/route.ts +62 -0
  151. package/src/app/api/db/multi-query/route.ts +116 -0
  152. package/src/app/api/db/pool-stats/route.ts +37 -0
  153. package/src/app/api/db/profile/route.ts +144 -0
  154. package/src/app/api/db/provider-meta/route.ts +49 -0
  155. package/src/app/api/db/query/route.ts +50 -0
  156. package/src/app/api/db/schema/route.ts +47 -0
  157. package/src/app/api/db/schema-snapshot/route.ts +42 -0
  158. package/src/app/api/db/test-connection/route.ts +55 -0
  159. package/src/app/api/db/transaction/route.ts +111 -0
  160. package/src/app/api/storage/[collection]/route.ts +67 -0
  161. package/src/app/api/storage/config/route.ts +17 -0
  162. package/src/app/api/storage/migrate/route.ts +45 -0
  163. package/src/app/api/storage/route.ts +32 -0
  164. package/src/app/error.tsx +49 -0
  165. package/src/app/global-error.tsx +55 -0
  166. package/src/app/globals.css +146 -0
  167. package/src/app/icon.svg +42 -0
  168. package/src/app/layout.tsx +34 -0
  169. package/src/app/login/login-form.tsx +301 -0
  170. package/src/app/login/page.tsx +11 -0
  171. package/src/app/monitoring/page.tsx +8 -0
  172. package/src/app/not-found.tsx +29 -0
  173. package/src/app/page.tsx +5 -0
  174. package/src/components/AIAutopilotPanel.tsx +238 -0
  175. package/src/components/CodeGenerator.tsx +271 -0
  176. package/src/components/CommandPalette.tsx +227 -0
  177. package/src/components/ConnectionModal.tsx +759 -0
  178. package/src/components/CreateTableModal.tsx +281 -0
  179. package/src/components/DataCharts.tsx +962 -0
  180. package/src/components/DataImportModal.tsx +582 -0
  181. package/src/components/DataProfiler.tsx +335 -0
  182. package/src/components/DatabaseDocs.tsx +251 -0
  183. package/src/components/MaskingSettings.tsx +414 -0
  184. package/src/components/MobileNav.tsx +50 -0
  185. package/src/components/NL2SQLPanel.tsx +281 -0
  186. package/src/components/PivotTable.tsx +257 -0
  187. package/src/components/QueryEditor.tsx +760 -0
  188. package/src/components/QueryHistory.tsx +344 -0
  189. package/src/components/QuerySafetyDialog.tsx +290 -0
  190. package/src/components/ResultsGrid.tsx +644 -0
  191. package/src/components/SaveQueryModal.tsx +104 -0
  192. package/src/components/SavedQueries.tsx +128 -0
  193. package/src/components/SchemaDiagram.tsx +473 -0
  194. package/src/components/SchemaDiff.tsx +473 -0
  195. package/src/components/SnapshotTimeline.tsx +116 -0
  196. package/src/components/Studio.tsx +639 -0
  197. package/src/components/TestDataGenerator.tsx +261 -0
  198. package/src/components/VisualExplain.tsx +820 -0
  199. package/src/components/admin/AdminDashboard.tsx +163 -0
  200. package/src/components/admin/tabs/AuditTab.tsx +531 -0
  201. package/src/components/admin/tabs/MonitoringEmbed.tsx +11 -0
  202. package/src/components/admin/tabs/OperationsTab.tsx +646 -0
  203. package/src/components/admin/tabs/OverviewTab.tsx +1328 -0
  204. package/src/components/admin/tabs/SecurityTab.tsx +284 -0
  205. package/src/components/community-section.tsx +92 -0
  206. package/src/components/icons/db-icons.tsx +84 -0
  207. package/src/components/libredb-logo.tsx +61 -0
  208. package/src/components/monitoring/MonitoringDashboard.tsx +345 -0
  209. package/src/components/monitoring/tabs/MetricChart.tsx +82 -0
  210. package/src/components/monitoring/tabs/OverviewTab.tsx +263 -0
  211. package/src/components/monitoring/tabs/PerformanceTab.tsx +254 -0
  212. package/src/components/monitoring/tabs/PoolTab.tsx +174 -0
  213. package/src/components/monitoring/tabs/QueriesTab.tsx +287 -0
  214. package/src/components/monitoring/tabs/SessionsTab.tsx +316 -0
  215. package/src/components/monitoring/tabs/StorageTab.tsx +335 -0
  216. package/src/components/monitoring/tabs/TablesTab.tsx +300 -0
  217. package/src/components/results-grid/ResultCard.tsx +111 -0
  218. package/src/components/results-grid/RowDetailSheet.tsx +178 -0
  219. package/src/components/results-grid/StatsBar.tsx +201 -0
  220. package/src/components/results-grid/index.ts +1 -0
  221. package/src/components/results-grid/utils.ts +23 -0
  222. package/src/components/schema-explorer/ColumnList.tsx +53 -0
  223. package/src/components/schema-explorer/SchemaExplorer.tsx +182 -0
  224. package/src/components/schema-explorer/TableItem.tsx +210 -0
  225. package/src/components/schema-explorer/index.ts +1 -0
  226. package/src/components/sidebar/ConnectionItem.tsx +105 -0
  227. package/src/components/sidebar/ConnectionsList.tsx +62 -0
  228. package/src/components/sidebar/Sidebar.tsx +130 -0
  229. package/src/components/sidebar/index.ts +2 -0
  230. package/src/components/studio/BottomPanel.tsx +286 -0
  231. package/src/components/studio/QueryToolbar.tsx +180 -0
  232. package/src/components/studio/StudioDesktopHeader.tsx +114 -0
  233. package/src/components/studio/StudioMobileHeader.tsx +340 -0
  234. package/src/components/studio/StudioTabBar.tsx +82 -0
  235. package/src/components/studio/index.ts +5 -0
  236. package/src/components/ui/accordion.tsx +66 -0
  237. package/src/components/ui/alert-dialog.tsx +157 -0
  238. package/src/components/ui/alert.tsx +66 -0
  239. package/src/components/ui/aspect-ratio.tsx +11 -0
  240. package/src/components/ui/avatar.tsx +53 -0
  241. package/src/components/ui/badge.tsx +46 -0
  242. package/src/components/ui/breadcrumb.tsx +109 -0
  243. package/src/components/ui/button-group.tsx +83 -0
  244. package/src/components/ui/button.tsx +60 -0
  245. package/src/components/ui/calendar.tsx +216 -0
  246. package/src/components/ui/card.tsx +92 -0
  247. package/src/components/ui/carousel.tsx +241 -0
  248. package/src/components/ui/chart.tsx +357 -0
  249. package/src/components/ui/checkbox.tsx +32 -0
  250. package/src/components/ui/collapsible.tsx +33 -0
  251. package/src/components/ui/command.tsx +184 -0
  252. package/src/components/ui/context-menu.tsx +252 -0
  253. package/src/components/ui/dialog.tsx +143 -0
  254. package/src/components/ui/drawer.tsx +135 -0
  255. package/src/components/ui/dropdown-menu.tsx +257 -0
  256. package/src/components/ui/empty.tsx +104 -0
  257. package/src/components/ui/field.tsx +248 -0
  258. package/src/components/ui/form.tsx +167 -0
  259. package/src/components/ui/hover-card.tsx +44 -0
  260. package/src/components/ui/input-group.tsx +170 -0
  261. package/src/components/ui/input-otp.tsx +77 -0
  262. package/src/components/ui/input.tsx +21 -0
  263. package/src/components/ui/item.tsx +193 -0
  264. package/src/components/ui/kbd.tsx +28 -0
  265. package/src/components/ui/label.tsx +24 -0
  266. package/src/components/ui/menubar.tsx +276 -0
  267. package/src/components/ui/navigation-menu.tsx +168 -0
  268. package/src/components/ui/pagination.tsx +127 -0
  269. package/src/components/ui/popover.tsx +48 -0
  270. package/src/components/ui/progress.tsx +31 -0
  271. package/src/components/ui/radio-group.tsx +45 -0
  272. package/src/components/ui/resizable.tsx +56 -0
  273. package/src/components/ui/scroll-area.tsx +58 -0
  274. package/src/components/ui/select.tsx +187 -0
  275. package/src/components/ui/separator.tsx +28 -0
  276. package/src/components/ui/sheet.tsx +139 -0
  277. package/src/components/ui/sidebar.tsx +726 -0
  278. package/src/components/ui/skeleton.tsx +13 -0
  279. package/src/components/ui/slider.tsx +63 -0
  280. package/src/components/ui/sonner.tsx +40 -0
  281. package/src/components/ui/spinner.tsx +16 -0
  282. package/src/components/ui/switch.tsx +31 -0
  283. package/src/components/ui/table.tsx +116 -0
  284. package/src/components/ui/tabs.tsx +66 -0
  285. package/src/components/ui/textarea.tsx +18 -0
  286. package/src/components/ui/toggle-group.tsx +83 -0
  287. package/src/components/ui/toggle.tsx +47 -0
  288. package/src/components/ui/tooltip.tsx +61 -0
  289. package/src/exports/components.ts +15 -0
  290. package/src/exports/index.ts +4 -0
  291. package/src/exports/providers.ts +4 -0
  292. package/src/exports/types.ts +26 -0
  293. package/src/hooks/use-ai-chat.ts +182 -0
  294. package/src/hooks/use-all-connections.ts +66 -0
  295. package/src/hooks/use-api-call.ts +71 -0
  296. package/src/hooks/use-auth.ts +51 -0
  297. package/src/hooks/use-connection-form.ts +349 -0
  298. package/src/hooks/use-connection-manager.ts +169 -0
  299. package/src/hooks/use-connection-payload.ts +15 -0
  300. package/src/hooks/use-inline-editing.ts +109 -0
  301. package/src/hooks/use-mobile.ts +20 -0
  302. package/src/hooks/use-monitoring-data.ts +270 -0
  303. package/src/hooks/use-provider-metadata.ts +62 -0
  304. package/src/hooks/use-query-execution.ts +478 -0
  305. package/src/hooks/use-storage-sync.ts +259 -0
  306. package/src/hooks/use-tab-manager.ts +231 -0
  307. package/src/hooks/use-toast.ts +20 -0
  308. package/src/hooks/use-transaction-control.ts +64 -0
  309. package/src/lib/api/error-codes.ts +30 -0
  310. package/src/lib/api/errors.ts +236 -0
  311. package/src/lib/api/with-error-handler.ts +41 -0
  312. package/src/lib/audit.ts +105 -0
  313. package/src/lib/auth.ts +87 -0
  314. package/src/lib/connection-string-parser.ts +172 -0
  315. package/src/lib/data-masking.ts +385 -0
  316. package/src/lib/db/base-provider.ts +325 -0
  317. package/src/lib/db/errors.ts +317 -0
  318. package/src/lib/db/factory.ts +324 -0
  319. package/src/lib/db/index.ts +123 -0
  320. package/src/lib/db/providers/document/index.ts +6 -0
  321. package/src/lib/db/providers/document/mongodb.ts +992 -0
  322. package/src/lib/db/providers/keyvalue/redis.ts +554 -0
  323. package/src/lib/db/providers/sql/index.ts +11 -0
  324. package/src/lib/db/providers/sql/mssql.ts +1065 -0
  325. package/src/lib/db/providers/sql/mysql.ts +978 -0
  326. package/src/lib/db/providers/sql/oracle.ts +1044 -0
  327. package/src/lib/db/providers/sql/postgres.ts +1179 -0
  328. package/src/lib/db/providers/sql/sql-base.ts +174 -0
  329. package/src/lib/db/providers/sql/sqlite.ts +721 -0
  330. package/src/lib/db/types.ts +437 -0
  331. package/src/lib/db/utils/pool-manager.ts +287 -0
  332. package/src/lib/db/utils/query-limiter.ts +239 -0
  333. package/src/lib/db-ui-config.ts +86 -0
  334. package/src/lib/editor/mongodb-completions.ts +172 -0
  335. package/src/lib/editor/sql-completions.ts +280 -0
  336. package/src/lib/llm/base-provider.ts +117 -0
  337. package/src/lib/llm/factory.ts +102 -0
  338. package/src/lib/llm/index.ts +90 -0
  339. package/src/lib/llm/providers/custom.ts +181 -0
  340. package/src/lib/llm/providers/gemini.ts +126 -0
  341. package/src/lib/llm/providers/ollama.ts +154 -0
  342. package/src/lib/llm/providers/openai.ts +146 -0
  343. package/src/lib/llm/types.ts +173 -0
  344. package/src/lib/llm/utils/config.ts +187 -0
  345. package/src/lib/llm/utils/retry.ts +119 -0
  346. package/src/lib/llm/utils/streaming.ts +202 -0
  347. package/src/lib/logger.ts +127 -0
  348. package/src/lib/monitoring-thresholds.ts +44 -0
  349. package/src/lib/oidc.ts +262 -0
  350. package/src/lib/query-generators.ts +61 -0
  351. package/src/lib/schema-diff/diff-engine.ts +273 -0
  352. package/src/lib/schema-diff/migration-generator.ts +208 -0
  353. package/src/lib/schema-diff/types.ts +55 -0
  354. package/src/lib/seed/config-loader.ts +79 -0
  355. package/src/lib/seed/connection-filter.ts +49 -0
  356. package/src/lib/seed/credential-resolver.ts +62 -0
  357. package/src/lib/seed/index.ts +40 -0
  358. package/src/lib/seed/resolve-connection.ts +57 -0
  359. package/src/lib/seed/types.ts +69 -0
  360. package/src/lib/sql/alias-extractor.ts +267 -0
  361. package/src/lib/sql/index.ts +8 -0
  362. package/src/lib/sql/statement-splitter.ts +167 -0
  363. package/src/lib/sql/types.ts +40 -0
  364. package/src/lib/ssh/tunnel.ts +142 -0
  365. package/src/lib/storage/factory.ts +84 -0
  366. package/src/lib/storage/index.ts +14 -0
  367. package/src/lib/storage/local-storage.ts +99 -0
  368. package/src/lib/storage/providers/postgres.ts +225 -0
  369. package/src/lib/storage/providers/sqlite.ts +153 -0
  370. package/src/lib/storage/storage-facade.ts +272 -0
  371. package/src/lib/storage/types.ts +75 -0
  372. package/src/lib/time-series-buffer.ts +58 -0
  373. package/src/lib/types.ts +173 -0
  374. package/src/lib/utils.ts +6 -0
  375. package/src/proxy.ts +104 -0
  376. package/src/types/db-drivers.d.ts +23 -0
  377. package/src/types/html2canvas.d.ts +9 -0
  378. package/tests/api/admin/audit.test.ts +178 -0
  379. package/tests/api/admin/fleet-health.test.ts +183 -0
  380. package/tests/api/ai/autopilot.test.ts +174 -0
  381. package/tests/api/ai/chat.test.ts +250 -0
  382. package/tests/api/ai/describe-schema.test.ts +266 -0
  383. package/tests/api/ai/explain.test.ts +199 -0
  384. package/tests/api/ai/impact.test.ts +168 -0
  385. package/tests/api/ai/index-advisor.test.ts +171 -0
  386. package/tests/api/ai/nl2sql.test.ts +202 -0
  387. package/tests/api/ai/query-safety.test.ts +196 -0
  388. package/tests/api/auth/login.test.ts +170 -0
  389. package/tests/api/auth/logout.test.ts +140 -0
  390. package/tests/api/auth/me.test.ts +73 -0
  391. package/tests/api/auth/oidc-callback.test.ts +215 -0
  392. package/tests/api/auth/oidc-login.test.ts +127 -0
  393. package/tests/api/db/cancel.test.ts +198 -0
  394. package/tests/api/db/disconnect.test.ts +124 -0
  395. package/tests/api/db/health.test.ts +222 -0
  396. package/tests/api/db/maintenance.test.ts +263 -0
  397. package/tests/api/db/monitoring.test.ts +221 -0
  398. package/tests/api/db/multi-query.test.ts +316 -0
  399. package/tests/api/db/pool-stats.test.ts +135 -0
  400. package/tests/api/db/profile.test.ts +330 -0
  401. package/tests/api/db/provider-meta.test.ts +193 -0
  402. package/tests/api/db/query.test.ts +314 -0
  403. package/tests/api/db/schema-snapshot.test.ts +170 -0
  404. package/tests/api/db/schema.test.ts +191 -0
  405. package/tests/api/db/test-connection.test.ts +185 -0
  406. package/tests/api/db/transaction.test.ts +314 -0
  407. package/tests/api/proxy.test.ts +191 -0
  408. package/tests/api/seed/managed-route.test.ts +113 -0
  409. package/tests/api/storage/config.test.ts +42 -0
  410. package/tests/api/storage/storage-routes.test.ts +309 -0
  411. package/tests/components/AIAutopilotPanel.test.tsx +756 -0
  412. package/tests/components/AdminPage.test.tsx +33 -0
  413. package/tests/components/CodeGenerator.test.tsx +182 -0
  414. package/tests/components/CommandPalette.test.tsx +428 -0
  415. package/tests/components/CommunitySection.test.tsx +91 -0
  416. package/tests/components/ConnectionModal.mobile.test.tsx +284 -0
  417. package/tests/components/ConnectionModal.test.tsx +570 -0
  418. package/tests/components/CreateTableModal.test.tsx +383 -0
  419. package/tests/components/DataCharts.test.tsx +739 -0
  420. package/tests/components/DataImportModal.test.tsx +751 -0
  421. package/tests/components/DataProfiler.test.tsx +589 -0
  422. package/tests/components/DatabaseDocs.test.tsx +353 -0
  423. package/tests/components/LoginPage.test.tsx +163 -0
  424. package/tests/components/LoginPageOIDC.test.tsx +92 -0
  425. package/tests/components/MaskingSettings.test.tsx +498 -0
  426. package/tests/components/MobileNav.test.tsx +30 -0
  427. package/tests/components/MonitoringPage.test.tsx +32 -0
  428. package/tests/components/NL2SQLPanel.test.tsx +621 -0
  429. package/tests/components/Page.test.tsx +33 -0
  430. package/tests/components/PivotTable.test.tsx +350 -0
  431. package/tests/components/QueryEditor.test.tsx +1730 -0
  432. package/tests/components/QueryHistory.test.tsx +572 -0
  433. package/tests/components/QuerySafetyDialog.test.tsx +586 -0
  434. package/tests/components/ResultsGrid.test.tsx +804 -0
  435. package/tests/components/RootLayout.test.tsx +83 -0
  436. package/tests/components/SaveQueryModal.test.tsx +25 -0
  437. package/tests/components/SavedQueries.test.tsx +43 -0
  438. package/tests/components/SchemaDiagram.test.tsx +1034 -0
  439. package/tests/components/SchemaDiff.test.tsx +906 -0
  440. package/tests/components/SnapshotTimeline.test.tsx +174 -0
  441. package/tests/components/Studio.test.tsx +1030 -0
  442. package/tests/components/TestDataGenerator.test.tsx +291 -0
  443. package/tests/components/VisualExplain.test.tsx +704 -0
  444. package/tests/components/admin/AdminDashboard.test.tsx +205 -0
  445. package/tests/components/admin/AuditTab.test.tsx +220 -0
  446. package/tests/components/admin/MonitoringEmbed.test.tsx +58 -0
  447. package/tests/components/admin/OperationsTab.test.tsx +975 -0
  448. package/tests/components/admin/OverviewTab.test.tsx +254 -0
  449. package/tests/components/admin/SecurityTab.test.tsx +467 -0
  450. package/tests/components/monitoring/MetricChart.test.tsx +111 -0
  451. package/tests/components/monitoring/MonitoringDashboard.test.tsx +259 -0
  452. package/tests/components/monitoring/OverviewTab.test.tsx +78 -0
  453. package/tests/components/monitoring/PerformanceTab.test.tsx +87 -0
  454. package/tests/components/monitoring/PoolTab.test.tsx +42 -0
  455. package/tests/components/monitoring/QueriesTab.test.tsx +80 -0
  456. package/tests/components/monitoring/SessionsTab.test.tsx +154 -0
  457. package/tests/components/monitoring/StorageTab.test.tsx +127 -0
  458. package/tests/components/monitoring/TablesTab.test.tsx +153 -0
  459. package/tests/components/results-grid/ResultCard.test.tsx +105 -0
  460. package/tests/components/results-grid/RowDetailSheet.test.tsx +308 -0
  461. package/tests/components/results-grid/StatsBar.test.tsx +162 -0
  462. package/tests/components/schema-explorer/ColumnList.test.tsx +151 -0
  463. package/tests/components/schema-explorer/SchemaExplorer.test.tsx +461 -0
  464. package/tests/components/schema-explorer/TableItem.test.tsx +415 -0
  465. package/tests/components/sidebar/ConnectionItem.test.tsx +201 -0
  466. package/tests/components/sidebar/ConnectionsList.test.tsx +176 -0
  467. package/tests/components/sidebar/Sidebar.test.tsx +187 -0
  468. package/tests/components/studio/BottomPanel.test.tsx +383 -0
  469. package/tests/components/studio/QueryToolbar.test.tsx +321 -0
  470. package/tests/components/studio/StudioDesktopHeader.test.tsx +377 -0
  471. package/tests/components/studio/StudioMobileHeader.test.tsx +198 -0
  472. package/tests/components/studio/StudioTabBar.test.tsx +331 -0
  473. package/tests/fixtures/connections.ts +96 -0
  474. package/tests/fixtures/masking-configs.ts +86 -0
  475. package/tests/fixtures/query-results.ts +71 -0
  476. package/tests/fixtures/schemas.ts +64 -0
  477. package/tests/fixtures/seed-connections/invalid-config.yaml +7 -0
  478. package/tests/fixtures/seed-connections/minimal-config.yaml +8 -0
  479. package/tests/fixtures/seed-connections/mixed-credentials.yaml +23 -0
  480. package/tests/fixtures/seed-connections/multi-role-config.yaml +30 -0
  481. package/tests/fixtures/seed-connections/valid-config.json +15 -0
  482. package/tests/fixtures/seed-connections/valid-config.yaml +51 -0
  483. package/tests/helpers/mock-fetch.ts +59 -0
  484. package/tests/helpers/mock-monaco.ts +112 -0
  485. package/tests/helpers/mock-navigation.ts +28 -0
  486. package/tests/helpers/mock-next.ts +80 -0
  487. package/tests/helpers/mock-provider.ts +133 -0
  488. package/tests/helpers/mock-sonner.ts +29 -0
  489. package/tests/helpers/render-with-providers.tsx +19 -0
  490. package/tests/hooks/use-ai-chat.test.ts +600 -0
  491. package/tests/hooks/use-auth.test.ts +371 -0
  492. package/tests/hooks/use-connection-form.test.ts +743 -0
  493. package/tests/hooks/use-connection-manager.test.ts +466 -0
  494. package/tests/hooks/use-inline-editing.test.ts +321 -0
  495. package/tests/hooks/use-mobile.test.ts +177 -0
  496. package/tests/hooks/use-monitoring-data.test.ts +819 -0
  497. package/tests/hooks/use-provider-metadata.test.ts +228 -0
  498. package/tests/hooks/use-query-execution.test.ts +1212 -0
  499. package/tests/hooks/use-tab-manager.test.ts +756 -0
  500. package/tests/hooks/use-toast.test.ts +74 -0
  501. package/tests/hooks/use-transaction-control.test.ts +211 -0
  502. package/tests/integration/db/mongodb-provider.test.ts +698 -0
  503. package/tests/integration/db/mssql-provider.test.ts +840 -0
  504. package/tests/integration/db/mysql-provider.test.ts +872 -0
  505. package/tests/integration/db/oracle-provider.test.ts +843 -0
  506. package/tests/integration/db/postgres-provider.test.ts +1382 -0
  507. package/tests/integration/db/redis-provider.test.ts +526 -0
  508. package/tests/integration/db/sqlite-provider.test.ts +480 -0
  509. package/tests/integration/seed/seed-pipeline.test.ts +102 -0
  510. package/tests/isolated/factory-singleton.test.ts +150 -0
  511. package/tests/isolated/use-storage-sync.test.ts +389 -0
  512. package/tests/run-components.sh +196 -0
  513. package/tests/setup-dom.ts +58 -0
  514. package/tests/setup.ts +40 -0
  515. package/tests/unit/api-errors.test.ts +210 -0
  516. package/tests/unit/code-generator-functions.test.ts +271 -0
  517. package/tests/unit/components/column-list.test.tsx +190 -0
  518. package/tests/unit/components/data-import-modal.test.tsx +441 -0
  519. package/tests/unit/components/studio-mobile-header.test.tsx +327 -0
  520. package/tests/unit/data-charts-functions.test.ts +496 -0
  521. package/tests/unit/data-import-functions.test.ts +320 -0
  522. package/tests/unit/data-import-utils.test.ts +125 -0
  523. package/tests/unit/db/base-provider.test.ts +517 -0
  524. package/tests/unit/db/errors.test.ts +403 -0
  525. package/tests/unit/db/factory.test.ts +436 -0
  526. package/tests/unit/db/pool-manager.test.ts +440 -0
  527. package/tests/unit/db/query-limiter.test.ts +387 -0
  528. package/tests/unit/db/sql-base.test.ts +438 -0
  529. package/tests/unit/lib/api/error-codes.test.ts +39 -0
  530. package/tests/unit/lib/audit.test.ts +326 -0
  531. package/tests/unit/lib/auth.test.ts +146 -0
  532. package/tests/unit/lib/connection-string-parser.test.ts +424 -0
  533. package/tests/unit/lib/data-masking.test.ts +583 -0
  534. package/tests/unit/lib/db-icons.test.tsx +41 -0
  535. package/tests/unit/lib/monitoring-thresholds.test.ts +133 -0
  536. package/tests/unit/lib/oidc.test.ts +509 -0
  537. package/tests/unit/lib/query-generators.test.ts +127 -0
  538. package/tests/unit/lib/storage/factory.test.ts +71 -0
  539. package/tests/unit/lib/storage/local-storage.test.ts +114 -0
  540. package/tests/unit/lib/storage/providers/postgres.test.ts +312 -0
  541. package/tests/unit/lib/storage/providers/sqlite.test.ts +232 -0
  542. package/tests/unit/lib/storage/storage-facade-extended.test.ts +331 -0
  543. package/tests/unit/lib/storage/storage-facade.test.ts +184 -0
  544. package/tests/unit/lib/storage.test.ts +317 -0
  545. package/tests/unit/lib/time-series-buffer.test.ts +212 -0
  546. package/tests/unit/lib/utils.test.ts +24 -0
  547. package/tests/unit/llm/base-provider.test.ts +238 -0
  548. package/tests/unit/llm/config.test.ts +262 -0
  549. package/tests/unit/llm/custom-provider.test.ts +281 -0
  550. package/tests/unit/llm/gemini-provider.test.ts +248 -0
  551. package/tests/unit/llm/llm-factory.test.ts +155 -0
  552. package/tests/unit/llm/ollama-provider.test.ts +288 -0
  553. package/tests/unit/llm/openai-provider.test.ts +324 -0
  554. package/tests/unit/llm/retry.test.ts +180 -0
  555. package/tests/unit/llm/streaming.test.ts +355 -0
  556. package/tests/unit/logger.test.ts +198 -0
  557. package/tests/unit/mongodb-completions.test.ts +516 -0
  558. package/tests/unit/pivot-table-functions.test.ts +76 -0
  559. package/tests/unit/query-cancelled-error.test.ts +81 -0
  560. package/tests/unit/schema-diff/diff-engine.test.ts +367 -0
  561. package/tests/unit/schema-diff/migration-generator.test.ts +513 -0
  562. package/tests/unit/seed/config-loader.test.ts +73 -0
  563. package/tests/unit/seed/connection-filter.test.ts +91 -0
  564. package/tests/unit/seed/credential-resolver.test.ts +85 -0
  565. package/tests/unit/seed/index.test.ts +72 -0
  566. package/tests/unit/seed/resolve-connection.test.ts +74 -0
  567. package/tests/unit/seed/types.test.ts +129 -0
  568. package/tests/unit/sql/alias-extractor.test.ts +444 -0
  569. package/tests/unit/sql/statement-splitter.test.ts +348 -0
  570. package/tests/unit/sql-completions.test.ts +463 -0
  571. package/tests/unit/ssh-tunnel.test.ts +465 -0
  572. package/tsconfig.json +42 -0
@@ -0,0 +1,1362 @@
1
+ # Seed Connections Implementation Plan
2
+
3
+ > **For agentic workers:** REQUIRED SUB-SKILL: Use superpowers:subagent-driven-development (recommended) or superpowers:executing-plans to implement this plan task-by-task. Steps use checkbox (`- [ ]`) syntax for tracking.
4
+
5
+ **Goal:** Enable pre-configured database connections via YAML/JSON config file with role-based access control and hybrid managed/unmanaged model.
6
+
7
+ **Architecture:** Dedicated `src/lib/seed/` module reads a volume-mounted config file at runtime (TTL-cached), resolves `${ENV_VAR}` credentials from `process.env`, filters by user role, and serves managed connections via a new API endpoint. A shared `resolveConnection()` utility is injected into 13 existing DB API routes to handle `seed:` prefixed connection IDs server-side. Client hooks merge managed connections with user connections, sending `connectionId` instead of full credentials for managed connections.
8
+
9
+ **Tech Stack:** TypeScript, Zod v4 (validation — project uses `^4.1.12`), `yaml` npm package (YAML parsing), Next.js API routes, existing JWT auth (`jose`)
10
+
11
+ **Spec:** `docs/superpowers/specs/2026-03-25-seed-connections-design.md`
12
+
13
+ **Important notes:**
14
+ - Project uses **Zod v4** (`^4.1.12`). All schema code uses v4 API (`.check()` instead of `.refine()` for some patterns, `z.object()` still supports `.strict()`). Verify Zod v4 compatibility at each step.
15
+ - **`disconnect/route.ts` is EXCLUDED** from `resolveConnection()` injection — it already accepts `connectionId` as a cache key for provider teardown, not connection establishment. The `seed:X` prefixed IDs flow naturally because `resolveConnection()` sets `id = "seed:X"` which becomes the cache key.
16
+ - **`POST /api/db/health`** (connection-level health check) IS included as an affected route.
17
+ - `pool-stats/route.ts` and `provider-meta/route.ts` are both **POST** routes, not GET.
18
+
19
+ ---
20
+
21
+ ## File Map
22
+
23
+ ### New Files
24
+
25
+ | File | Responsibility |
26
+ |------|---------------|
27
+ | `src/lib/seed/types.ts` | Zod v4 schemas + TS types: `SeedConfig`, `SeedConnection`, `SeedDefaults`, `ManagedConnection` |
28
+ | `src/lib/seed/config-loader.ts` | Read YAML/JSON from disk, validate with Zod, TTL cache |
29
+ | `src/lib/seed/credential-resolver.ts` | Resolve `${VAR}` patterns from `process.env`, per-connection error isolation |
30
+ | `src/lib/seed/connection-filter.ts` | Merge defaults, filter by role, map to `ManagedConnection` |
31
+ | `src/lib/seed/resolve-connection.ts` | Shared utility for all API routes: detect `seed:` prefix, resolve full credentials, verify role |
32
+ | `src/lib/seed/index.ts` | Barrel export: `getManagedConnections(roles)` + `getSeedConnectionById()` + `getSeedConnectionByIdUnfiltered()` |
33
+ | `src/hooks/use-connection-payload.ts` | Shared helper: `buildConnectionPayload(conn)` — returns `{ connectionId }` or `{ connection }` based on `managed` flag |
34
+ | `src/app/api/connections/managed/route.ts` | `GET /api/connections/managed` — auth + role filter + credential stripping |
35
+ | `charts/libredb-studio/templates/seed-configmap.yaml` | Helm ConfigMap for seed config |
36
+ | `tests/fixtures/seed-connections/valid-config.yaml` | Full valid config fixture |
37
+ | `tests/fixtures/seed-connections/valid-config.json` | Same config in JSON format (for format detection test) |
38
+ | `tests/fixtures/seed-connections/minimal-config.yaml` | Minimum required fields only |
39
+ | `tests/fixtures/seed-connections/invalid-config.yaml` | Validation failure cases |
40
+ | `tests/fixtures/seed-connections/mixed-credentials.yaml` | Some `${VAR}`, some plaintext |
41
+ | `tests/fixtures/seed-connections/multi-role-config.yaml` | Different roles per connection |
42
+ | `tests/unit/seed/types.test.ts` | Zod schema validation tests |
43
+ | `tests/unit/seed/config-loader.test.ts` | File read, parse, cache, error handling |
44
+ | `tests/unit/seed/credential-resolver.test.ts` | Env var resolution, skip, warn |
45
+ | `tests/unit/seed/connection-filter.test.ts` | Role filter, defaults merge, mapping |
46
+ | `tests/unit/seed/index.test.ts` | Orchestrator + getSeedConnectionById tests |
47
+ | `tests/unit/seed/resolve-connection.test.ts` | Seed prefix detection, role check, fallback |
48
+ | `tests/api/seed/managed-route.test.ts` | API endpoint auth, filter, strip, errors |
49
+ | `tests/integration/seed/seed-pipeline.test.ts` | Full pipeline + multi-route resolution |
50
+
51
+ **Not in this plan (future task):** `e2e/seed-connections.spec.ts` — Playwright E2E test for managed connections in sidebar. Requires running app with seed config, best handled as a separate task after core implementation is stable.
52
+
53
+ ### Modified Files
54
+
55
+ | File | Change |
56
+ |------|--------|
57
+ | `src/lib/types.ts:42-61` | Add `managed?: boolean`, `seedId?: string` to `DatabaseConnection` |
58
+ | `src/lib/audit.ts` | Add `'managed_connection'` to `AuditEventType` |
59
+ | `src/app/api/db/query/route.ts` | Import `resolveConnection`, use before `getOrCreateProvider` |
60
+ | `src/app/api/db/schema/route.ts` | Same pattern (also change body parsing to `req.json()`) |
61
+ | `src/app/api/db/multi-query/route.ts` | Same pattern |
62
+ | `src/app/api/db/transaction/route.ts` | Same pattern |
63
+ | `src/app/api/db/cancel/route.ts` | Same pattern |
64
+ | `src/app/api/db/maintenance/route.ts` | Same pattern |
65
+ | `src/app/api/db/monitoring/route.ts` | Same pattern |
66
+ | `src/app/api/db/pool-stats/route.ts` | Same pattern (POST route) |
67
+ | `src/app/api/db/profile/route.ts` | Same pattern |
68
+ | `src/app/api/db/provider-meta/route.ts` | Same pattern (POST route) |
69
+ | `src/app/api/db/test-connection/route.ts` | Same pattern |
70
+ | `src/app/api/db/schema-snapshot/route.ts` | Same pattern |
71
+ | `src/app/api/db/health/route.ts` | Same pattern (POST connection health check) |
72
+ | `src/hooks/use-connection-manager.ts` | Fetch managed connections, merge with user connections, update `fetchSchema` |
73
+ | `src/hooks/use-query-execution.ts` | Use `buildConnectionPayload()` at all 5 fetch sites |
74
+ | `src/hooks/use-transaction-control.ts` | Use `buildConnectionPayload()` |
75
+ | `src/components/sidebar/ConnectionItem.tsx:82-106` | Lock icon for managed, hide edit/delete |
76
+ | `charts/libredb-studio/values.yaml` | Add `seedConnections` section |
77
+ | `charts/libredb-studio/values.schema.json` | Add `seedConnections` schema |
78
+ | `charts/libredb-studio/templates/deployment.yaml` | Volume mount + env vars |
79
+ | `docker-compose.yml` | Add seed config volume mount example |
80
+ | `.env.example` | Document new env vars |
81
+
82
+ **NOT modified:** `src/app/api/db/disconnect/route.ts` — already accepts `connectionId` as cache key, `seed:X` IDs work naturally.
83
+
84
+ ---
85
+
86
+ ## Task 1: Install `yaml` dependency + add test fixtures
87
+
88
+ **Files:**
89
+ - Modify: `package.json`
90
+ - Create: `tests/fixtures/seed-connections/valid-config.yaml`
91
+ - Create: `tests/fixtures/seed-connections/valid-config.json`
92
+ - Create: `tests/fixtures/seed-connections/minimal-config.yaml`
93
+ - Create: `tests/fixtures/seed-connections/invalid-config.yaml`
94
+ - Create: `tests/fixtures/seed-connections/mixed-credentials.yaml`
95
+ - Create: `tests/fixtures/seed-connections/multi-role-config.yaml`
96
+
97
+ - [ ] **Step 1: Install yaml package**
98
+
99
+ ```bash
100
+ bun add yaml
101
+ ```
102
+
103
+ - [ ] **Step 2: Create valid-config.yaml fixture**
104
+
105
+ ```yaml
106
+ # tests/fixtures/seed-connections/valid-config.yaml
107
+ version: "1"
108
+
109
+ defaults:
110
+ managed: true
111
+ environment: production
112
+
113
+ connections:
114
+ - id: "test-postgres"
115
+ name: "Test PostgreSQL"
116
+ type: postgres
117
+ host: pg.internal
118
+ port: 5432
119
+ database: testdb
120
+ user: "testuser"
121
+ password: "${TEST_PG_PASSWORD}"
122
+ environment: production
123
+ group: "Backend"
124
+ roles: ["admin"]
125
+ managed: true
126
+ color: "#10B981"
127
+
128
+ - id: "test-mysql"
129
+ name: "Test MySQL"
130
+ type: mysql
131
+ host: mysql.internal
132
+ port: 3306
133
+ database: appdb
134
+ user: "devuser"
135
+ password: "${TEST_MYSQL_PASSWORD}"
136
+ environment: staging
137
+ group: "Backend"
138
+ roles: ["*"]
139
+ managed: false
140
+
141
+ - id: "test-mongo"
142
+ name: "Test MongoDB"
143
+ type: mongodb
144
+ connectionString: "${TEST_MONGO_URI}"
145
+ group: "Platform"
146
+ roles: ["admin"]
147
+ managed: true
148
+
149
+ - id: "test-redis"
150
+ name: "Test Redis"
151
+ type: redis
152
+ host: redis.internal
153
+ port: 6379
154
+ database: "0"
155
+ password: "${TEST_REDIS_PASSWORD}"
156
+ roles: ["*"]
157
+ managed: true
158
+ ```
159
+
160
+ - [ ] **Step 3: Create valid-config.json fixture** (same data, JSON format)
161
+
162
+ ```json
163
+ {
164
+ "version": "1",
165
+ "defaults": { "managed": true, "environment": "production" },
166
+ "connections": [
167
+ {
168
+ "id": "test-postgres",
169
+ "name": "Test PostgreSQL",
170
+ "type": "postgres",
171
+ "host": "pg.internal",
172
+ "port": 5432,
173
+ "password": "${TEST_PG_PASSWORD}",
174
+ "roles": ["admin"]
175
+ }
176
+ ]
177
+ }
178
+ ```
179
+
180
+ - [ ] **Step 4: Create minimal-config.yaml, invalid-config.yaml, mixed-credentials.yaml, multi-role-config.yaml**
181
+
182
+ (Same content as previous plan version — these fixtures are unchanged)
183
+
184
+ - [ ] **Step 5: Commit**
185
+
186
+ ```bash
187
+ git add package.json bun.lockb tests/fixtures/seed-connections/
188
+ git commit -m "feat(seed): add yaml dependency and test fixtures for seed connections"
189
+ ```
190
+
191
+ ---
192
+
193
+ ## Task 2: Types + Zod v4 Schemas (`src/lib/seed/types.ts`)
194
+
195
+ **Files:**
196
+ - Modify: `src/lib/types.ts:42-61`
197
+ - Create: `src/lib/seed/types.ts`
198
+ - Create: `tests/unit/seed/types.test.ts`
199
+
200
+ **Important:** Project uses Zod v4 (`^4.1.12`). Key v4 changes: `z.object()` still works, `.strict()` still works, `.safeParse()` returns `{ success, data, error }`, `.refine()` still works. Verify with `bun test` at each step.
201
+
202
+ - [ ] **Step 1: Add `managed` and `seedId` to DatabaseConnection**
203
+
204
+ In `src/lib/types.ts`, add two optional fields after `instanceName?` (line 60):
205
+
206
+ ```typescript
207
+ managed?: boolean; // true = admin-controlled, read-only in UI
208
+ seedId?: string; // stable reference to seed config ID
209
+ ```
210
+
211
+ - [ ] **Step 2: Write failing tests for Zod schemas**
212
+
213
+ Create `tests/unit/seed/types.test.ts` — same tests as previous plan, but **without `'prefer'` in SSLMode** and with Zod v4 API compatibility confirmed:
214
+
215
+ ```typescript
216
+ import { describe, it, expect } from 'bun:test';
217
+ import {
218
+ SeedConnectionSchema,
219
+ SeedConfigSchema,
220
+ SeedDefaultsSchema,
221
+ } from '@/lib/seed/types';
222
+
223
+ describe('SeedConnectionSchema', () => {
224
+ const validConn = {
225
+ id: 'test-pg',
226
+ name: 'Test PG',
227
+ type: 'postgres',
228
+ host: 'localhost',
229
+ port: 5432,
230
+ roles: ['admin'],
231
+ };
232
+
233
+ it('accepts a valid connection', () => {
234
+ const result = SeedConnectionSchema.safeParse(validConn);
235
+ expect(result.success).toBe(true);
236
+ });
237
+
238
+ it('rejects invalid id format (uppercase)', () => {
239
+ const result = SeedConnectionSchema.safeParse({ ...validConn, id: 'INVALID' });
240
+ expect(result.success).toBe(false);
241
+ });
242
+
243
+ it('rejects empty name', () => {
244
+ const result = SeedConnectionSchema.safeParse({ ...validConn, name: '' });
245
+ expect(result.success).toBe(false);
246
+ });
247
+
248
+ it('rejects demo type', () => {
249
+ const result = SeedConnectionSchema.safeParse({ ...validConn, type: 'demo' });
250
+ expect(result.success).toBe(false);
251
+ });
252
+
253
+ it('rejects empty roles array', () => {
254
+ const result = SeedConnectionSchema.safeParse({ ...validConn, roles: [] });
255
+ expect(result.success).toBe(false);
256
+ });
257
+
258
+ it('accepts wildcard role', () => {
259
+ const result = SeedConnectionSchema.safeParse({ ...validConn, roles: ['*'] });
260
+ expect(result.success).toBe(true);
261
+ });
262
+
263
+ it('rejects unknown roles like data-team', () => {
264
+ const result = SeedConnectionSchema.safeParse({ ...validConn, roles: ['data-team'] });
265
+ expect(result.success).toBe(false);
266
+ });
267
+
268
+ it('accepts combined admin and user roles', () => {
269
+ const result = SeedConnectionSchema.safeParse({ ...validConn, roles: ['admin', 'user'] });
270
+ expect(result.success).toBe(true);
271
+ });
272
+
273
+ it('rejects invalid port range', () => {
274
+ const result = SeedConnectionSchema.safeParse({ ...validConn, port: 99999 });
275
+ expect(result.success).toBe(false);
276
+ });
277
+
278
+ it('accepts valid color hex', () => {
279
+ const result = SeedConnectionSchema.safeParse({ ...validConn, color: '#10B981' });
280
+ expect(result.success).toBe(true);
281
+ });
282
+
283
+ it('rejects invalid color format', () => {
284
+ const result = SeedConnectionSchema.safeParse({ ...validConn, color: 'red' });
285
+ expect(result.success).toBe(false);
286
+ });
287
+
288
+ it('accepts all 7 valid database types', () => {
289
+ for (const type of ['postgres', 'mysql', 'sqlite', 'mongodb', 'redis', 'oracle', 'mssql']) {
290
+ const result = SeedConnectionSchema.safeParse({ ...validConn, type });
291
+ expect(result.success).toBe(true);
292
+ }
293
+ });
294
+ });
295
+
296
+ describe('SeedConfigSchema', () => {
297
+ it('accepts valid config with version 1', () => {
298
+ const result = SeedConfigSchema.safeParse({
299
+ version: '1',
300
+ connections: [{ id: 'a', name: 'A', type: 'postgres', host: 'h', roles: ['*'] }],
301
+ });
302
+ expect(result.success).toBe(true);
303
+ });
304
+
305
+ it('rejects version 2', () => {
306
+ const result = SeedConfigSchema.safeParse({
307
+ version: '2',
308
+ connections: [{ id: 'a', name: 'A', type: 'postgres', host: 'h', roles: ['*'] }],
309
+ });
310
+ expect(result.success).toBe(false);
311
+ });
312
+
313
+ it('rejects duplicate connection IDs', () => {
314
+ const result = SeedConfigSchema.safeParse({
315
+ version: '1',
316
+ connections: [
317
+ { id: 'dup', name: 'A', type: 'postgres', host: 'h', roles: ['*'] },
318
+ { id: 'dup', name: 'B', type: 'mysql', host: 'h', roles: ['*'] },
319
+ ],
320
+ });
321
+ expect(result.success).toBe(false);
322
+ });
323
+
324
+ it('rejects empty connections array', () => {
325
+ const result = SeedConfigSchema.safeParse({ version: '1', connections: [] });
326
+ expect(result.success).toBe(false);
327
+ });
328
+ });
329
+
330
+ describe('SeedDefaultsSchema', () => {
331
+ it('accepts valid ssl config with mode require', () => {
332
+ const result = SeedDefaultsSchema.safeParse({
333
+ ssl: { mode: 'require', rejectUnauthorized: true },
334
+ });
335
+ expect(result.success).toBe(true);
336
+ });
337
+
338
+ it('rejects ssl mode prefer (not in SSLMode type)', () => {
339
+ const result = SeedDefaultsSchema.safeParse({
340
+ ssl: { mode: 'prefer' },
341
+ });
342
+ expect(result.success).toBe(false);
343
+ });
344
+
345
+ it('rejects invalid environment', () => {
346
+ const result = SeedDefaultsSchema.safeParse({ environment: 'unknown' });
347
+ expect(result.success).toBe(false);
348
+ });
349
+ });
350
+ ```
351
+
352
+ - [ ] **Step 3: Run tests to verify they fail**
353
+
354
+ ```bash
355
+ bun test tests/unit/seed/types.test.ts
356
+ ```
357
+
358
+ Expected: FAIL — `@/lib/seed/types` does not exist yet
359
+
360
+ - [ ] **Step 4: Implement types.ts**
361
+
362
+ Create `src/lib/seed/types.ts`:
363
+
364
+ ```typescript
365
+ import { z } from 'zod';
366
+ import type { DatabaseConnection } from '@/lib/types';
367
+
368
+ // SSLMode matches src/lib/types.ts line 21 — NO 'prefer'
369
+ const SSLModeSchema = z.enum(['disable', 'require', 'verify-ca', 'verify-full']);
370
+
371
+ const SSLConfigSchema = z.object({
372
+ mode: SSLModeSchema.optional(),
373
+ rejectUnauthorized: z.boolean().optional(),
374
+ caCert: z.string().optional(),
375
+ clientCert: z.string().optional(),
376
+ clientKey: z.string().optional(),
377
+ }).optional();
378
+
379
+ const ConnectionEnvironmentSchema = z.enum([
380
+ 'production', 'staging', 'development', 'local', 'other',
381
+ ]);
382
+
383
+ // Allowed roles in current iteration (matches JWT role: 'admin' | 'user' + wildcard)
384
+ const AllowedRoleSchema = z.enum(['*', 'admin', 'user']);
385
+
386
+ const SeedDatabaseType = z.enum([
387
+ 'postgres', 'mysql', 'sqlite', 'mongodb', 'redis', 'oracle', 'mssql',
388
+ ]);
389
+
390
+ export const SeedDefaultsSchema = z.object({
391
+ managed: z.boolean().optional(),
392
+ environment: ConnectionEnvironmentSchema.optional(),
393
+ ssl: SSLConfigSchema,
394
+ });
395
+
396
+ export const SeedConnectionSchema = z.object({
397
+ id: z.string().min(1).max(64).regex(/^[a-z0-9-]+$/, 'ID must be lowercase alphanumeric with hyphens'),
398
+ name: z.string().min(1).max(128),
399
+ type: SeedDatabaseType,
400
+ host: z.string().optional(),
401
+ port: z.number().int().min(1).max(65535).optional(),
402
+ database: z.string().optional(),
403
+ user: z.string().optional(),
404
+ password: z.string().optional(),
405
+ connectionString: z.string().optional(),
406
+ environment: ConnectionEnvironmentSchema.optional(),
407
+ group: z.string().max(64).optional(),
408
+ color: z.string().regex(/^#[0-9A-Fa-f]{6}$/).optional(),
409
+ roles: z.array(AllowedRoleSchema).min(1, 'At least one role is required'),
410
+ managed: z.boolean().optional(),
411
+ ssl: SSLConfigSchema,
412
+ serviceName: z.string().optional(),
413
+ instanceName: z.string().optional(),
414
+ });
415
+
416
+ export const SeedConfigSchema = z.object({
417
+ version: z.literal('1'),
418
+ defaults: SeedDefaultsSchema.optional(),
419
+ connections: z.array(SeedConnectionSchema).min(1, 'At least one connection is required'),
420
+ }).refine(
421
+ (cfg) => new Set(cfg.connections.map((c) => c.id)).size === cfg.connections.length,
422
+ { message: 'Connection IDs must be unique' },
423
+ );
424
+
425
+ export type SeedConnection = z.infer<typeof SeedConnectionSchema>;
426
+ export type SeedDefaults = z.infer<typeof SeedDefaultsSchema>;
427
+ export type SeedConfig = z.infer<typeof SeedConfigSchema>;
428
+
429
+ export interface ManagedConnection extends DatabaseConnection {
430
+ managed: boolean;
431
+ roles: string[];
432
+ seedId: string;
433
+ }
434
+ ```
435
+
436
+ - [ ] **Step 5: Run tests to verify they pass**
437
+
438
+ ```bash
439
+ bun test tests/unit/seed/types.test.ts
440
+ ```
441
+
442
+ Expected: All PASS. If Zod v4 API differs, adjust accordingly.
443
+
444
+ - [ ] **Step 6: Commit**
445
+
446
+ ```bash
447
+ git add src/lib/types.ts src/lib/seed/types.ts tests/unit/seed/types.test.ts
448
+ git commit -m "feat(seed): add Zod v4 schemas and types for seed connections"
449
+ ```
450
+
451
+ ---
452
+
453
+ ## Task 3: Config Loader (`src/lib/seed/config-loader.ts`)
454
+
455
+ **Files:**
456
+ - Create: `src/lib/seed/config-loader.ts`
457
+ - Create: `tests/unit/seed/config-loader.test.ts`
458
+
459
+ - [ ] **Step 1: Write failing tests**
460
+
461
+ Create `tests/unit/seed/config-loader.test.ts`:
462
+
463
+ ```typescript
464
+ import { describe, it, expect, beforeEach, afterEach } from 'bun:test';
465
+ import path from 'path';
466
+ import { loadConfig, resetCache } from '@/lib/seed/config-loader';
467
+
468
+ const FIXTURES = path.resolve(__dirname, '../../../fixtures/seed-connections');
469
+
470
+ describe('config-loader', () => {
471
+ beforeEach(() => {
472
+ resetCache();
473
+ });
474
+
475
+ afterEach(() => {
476
+ delete process.env.SEED_CONFIG_PATH;
477
+ delete process.env.SEED_CACHE_TTL_MS;
478
+ });
479
+
480
+ it('loads and parses valid YAML config', async () => {
481
+ process.env.SEED_CONFIG_PATH = path.join(FIXTURES, 'valid-config.yaml');
482
+ const config = await loadConfig();
483
+ expect(config).not.toBeNull();
484
+ expect(config!.version).toBe('1');
485
+ expect(config!.connections).toHaveLength(4);
486
+ expect(config!.connections[0].id).toBe('test-postgres');
487
+ });
488
+
489
+ it('loads and parses valid JSON config', async () => {
490
+ process.env.SEED_CONFIG_PATH = path.join(FIXTURES, 'valid-config.json');
491
+ const config = await loadConfig();
492
+ expect(config).not.toBeNull();
493
+ expect(config!.version).toBe('1');
494
+ expect(config!.connections).toHaveLength(1);
495
+ });
496
+
497
+ it('returns null when config file does not exist', async () => {
498
+ process.env.SEED_CONFIG_PATH = '/nonexistent/path/config.yaml';
499
+ const config = await loadConfig();
500
+ expect(config).toBeNull();
501
+ });
502
+
503
+ it('throws on invalid YAML (validation fails)', async () => {
504
+ process.env.SEED_CONFIG_PATH = path.join(FIXTURES, 'invalid-config.yaml');
505
+ await expect(loadConfig()).rejects.toThrow();
506
+ });
507
+
508
+ it('uses default path when SEED_CONFIG_PATH not set', async () => {
509
+ const config = await loadConfig();
510
+ expect(config).toBeNull();
511
+ });
512
+
513
+ it('caches result within TTL', async () => {
514
+ process.env.SEED_CONFIG_PATH = path.join(FIXTURES, 'valid-config.yaml');
515
+ process.env.SEED_CACHE_TTL_MS = '60000';
516
+ const config1 = await loadConfig();
517
+ const config2 = await loadConfig();
518
+ expect(config1).toBe(config2); // same reference
519
+ });
520
+
521
+ it('reloads after cache reset', async () => {
522
+ process.env.SEED_CONFIG_PATH = path.join(FIXTURES, 'valid-config.yaml');
523
+ const config1 = await loadConfig();
524
+ resetCache();
525
+ const config2 = await loadConfig();
526
+ expect(config1).not.toBe(config2); // different reference
527
+ expect(config1!.connections).toHaveLength(config2!.connections.length);
528
+ });
529
+
530
+ it('loads minimal config with only required fields', async () => {
531
+ process.env.SEED_CONFIG_PATH = path.join(FIXTURES, 'minimal-config.yaml');
532
+ const config = await loadConfig();
533
+ expect(config).not.toBeNull();
534
+ expect(config!.connections).toHaveLength(1);
535
+ });
536
+ });
537
+ ```
538
+
539
+ - [ ] **Step 2: Run tests to verify they fail**
540
+
541
+ ```bash
542
+ bun test tests/unit/seed/config-loader.test.ts
543
+ ```
544
+
545
+ - [ ] **Step 3: Implement config-loader.ts**
546
+
547
+ Create `src/lib/seed/config-loader.ts` — same implementation as before (readFile → parse YAML/JSON → Zod validate → TTL cache).
548
+
549
+ - [ ] **Step 4: Run tests to verify they pass**
550
+
551
+ ```bash
552
+ bun test tests/unit/seed/config-loader.test.ts
553
+ ```
554
+
555
+ - [ ] **Step 5: Commit**
556
+
557
+ ```bash
558
+ git add src/lib/seed/config-loader.ts tests/unit/seed/config-loader.test.ts
559
+ git commit -m "feat(seed): implement config loader with YAML/JSON parsing and TTL cache"
560
+ ```
561
+
562
+ ---
563
+
564
+ ## Task 4: Credential Resolver (`src/lib/seed/credential-resolver.ts`)
565
+
566
+ **Files:**
567
+ - Create: `src/lib/seed/credential-resolver.ts`
568
+ - Create: `tests/unit/seed/credential-resolver.test.ts`
569
+
570
+ - [ ] **Step 1: Write failing tests** (same as before)
571
+ - [ ] **Step 2: Run tests to verify they fail**
572
+ - [ ] **Step 3: Implement credential-resolver.ts** (same as before)
573
+ - [ ] **Step 4: Run tests to verify they pass**
574
+ - [ ] **Step 5: Commit**
575
+
576
+ ```bash
577
+ git add src/lib/seed/credential-resolver.ts tests/unit/seed/credential-resolver.test.ts
578
+ git commit -m "feat(seed): implement credential resolver with env var injection"
579
+ ```
580
+
581
+ ---
582
+
583
+ ## Task 5: Connection Filter (`src/lib/seed/connection-filter.ts`)
584
+
585
+ **Files:**
586
+ - Create: `src/lib/seed/connection-filter.ts`
587
+ - Create: `tests/unit/seed/connection-filter.test.ts`
588
+
589
+ - [ ] **Step 1: Write failing tests** (same as before)
590
+ - [ ] **Step 2: Run tests to verify they fail**
591
+ - [ ] **Step 3: Implement connection-filter.ts** (same as before)
592
+ - [ ] **Step 4: Run tests to verify they pass**
593
+ - [ ] **Step 5: Commit**
594
+
595
+ ```bash
596
+ git add src/lib/seed/connection-filter.ts tests/unit/seed/connection-filter.test.ts
597
+ git commit -m "feat(seed): implement connection filter with role matching and defaults merge"
598
+ ```
599
+
600
+ ---
601
+
602
+ ## Task 6: Barrel Export + Orchestrator (`src/lib/seed/index.ts`) + Tests
603
+
604
+ **Files:**
605
+ - Create: `src/lib/seed/index.ts`
606
+ - Create: `tests/unit/seed/index.test.ts`
607
+
608
+ - [ ] **Step 1: Write failing tests**
609
+
610
+ Create `tests/unit/seed/index.test.ts`:
611
+
612
+ ```typescript
613
+ import { describe, it, expect, beforeEach, afterEach } from 'bun:test';
614
+ import path from 'path';
615
+ import {
616
+ getManagedConnections,
617
+ getSeedConnectionById,
618
+ getSeedConnectionByIdUnfiltered,
619
+ resetCache,
620
+ } from '@/lib/seed';
621
+ import { resetPlaintextWarnings } from '@/lib/seed/credential-resolver';
622
+
623
+ const FIXTURES = path.resolve(__dirname, '../../../fixtures/seed-connections');
624
+
625
+ describe('seed/index orchestrator', () => {
626
+ beforeEach(() => {
627
+ resetCache();
628
+ resetPlaintextWarnings();
629
+ process.env.SEED_CONFIG_PATH = path.join(FIXTURES, 'multi-role-config.yaml');
630
+ process.env.ADMIN_PG_PASS = 'admin-secret';
631
+ process.env.USER_MYSQL_PASS = 'user-secret';
632
+ process.env.SHARED_PG_PASS = 'shared-secret';
633
+ process.env.BOTH_PG_PASS = 'both-secret';
634
+ });
635
+
636
+ afterEach(() => {
637
+ delete process.env.SEED_CONFIG_PATH;
638
+ delete process.env.ADMIN_PG_PASS;
639
+ delete process.env.USER_MYSQL_PASS;
640
+ delete process.env.SHARED_PG_PASS;
641
+ delete process.env.BOTH_PG_PASS;
642
+ });
643
+
644
+ it('getManagedConnections returns role-filtered connections', async () => {
645
+ const adminConns = await getManagedConnections(['admin']);
646
+ expect(adminConns.length).toBeGreaterThanOrEqual(3); // admin-only, everyone, admin-and-user
647
+
648
+ const userConns = await getManagedConnections(['user']);
649
+ const userIds = userConns.map((c) => c.seedId);
650
+ expect(userIds).toContain('everyone');
651
+ expect(userIds).toContain('user-only');
652
+ expect(userIds).not.toContain('admin-only');
653
+ });
654
+
655
+ it('getSeedConnectionById returns connection with role check', async () => {
656
+ const conn = await getSeedConnectionById('everyone', ['user']);
657
+ expect(conn).not.toBeNull();
658
+ expect(conn!.seedId).toBe('everyone');
659
+ expect(conn!.password).toBe('shared-secret');
660
+ });
661
+
662
+ it('getSeedConnectionById returns null when role mismatches', async () => {
663
+ const conn = await getSeedConnectionById('admin-only', ['user']);
664
+ expect(conn).toBeNull();
665
+ });
666
+
667
+ it('getSeedConnectionByIdUnfiltered returns connection regardless of role', async () => {
668
+ const conn = await getSeedConnectionByIdUnfiltered('admin-only');
669
+ expect(conn).not.toBeNull();
670
+ expect(conn!.seedId).toBe('admin-only');
671
+ });
672
+
673
+ it('getSeedConnectionByIdUnfiltered returns null for nonexistent ID', async () => {
674
+ const conn = await getSeedConnectionByIdUnfiltered('nonexistent');
675
+ expect(conn).toBeNull();
676
+ });
677
+
678
+ it('returns empty array when config file missing', async () => {
679
+ process.env.SEED_CONFIG_PATH = '/nonexistent.yaml';
680
+ resetCache();
681
+ const conns = await getManagedConnections(['admin']);
682
+ expect(conns).toHaveLength(0);
683
+ });
684
+ });
685
+ ```
686
+
687
+ - [ ] **Step 2: Run tests to verify they fail**
688
+
689
+ ```bash
690
+ bun test tests/unit/seed/index.test.ts
691
+ ```
692
+
693
+ - [ ] **Step 3: Implement index.ts**
694
+
695
+ Create `src/lib/seed/index.ts`:
696
+
697
+ ```typescript
698
+ import { loadConfig, resetCache } from './config-loader';
699
+ import { resolveAllCredentials } from './credential-resolver';
700
+ import { filterByRoles, mergeDefaults } from './connection-filter';
701
+ import type { ManagedConnection } from './types';
702
+
703
+ export type { ManagedConnection, SeedConfig, SeedConnection, SeedDefaults } from './types';
704
+ export { SeedConfigSchema, SeedConnectionSchema, SeedDefaultsSchema } from './types';
705
+ export { resetCache } from './config-loader';
706
+ export { resetPlaintextWarnings } from './credential-resolver';
707
+
708
+ async function loadAndResolve(): Promise<ManagedConnection[]> {
709
+ const config = await loadConfig();
710
+ if (!config) return [];
711
+
712
+ const withDefaults = config.connections.map((conn) =>
713
+ mergeDefaults(conn, config.defaults),
714
+ );
715
+
716
+ const resolved = resolveAllCredentials(withDefaults);
717
+ // Return all resolved connections (unfiltered) for internal use
718
+ return filterByRoles(resolved, ['*', 'admin', 'user']);
719
+ }
720
+
721
+ export async function getManagedConnections(roles: string[]): Promise<ManagedConnection[]> {
722
+ const config = await loadConfig();
723
+ if (!config) return [];
724
+
725
+ const withDefaults = config.connections.map((conn) =>
726
+ mergeDefaults(conn, config.defaults),
727
+ );
728
+
729
+ const resolved = resolveAllCredentials(withDefaults);
730
+ return filterByRoles(resolved, roles);
731
+ }
732
+
733
+ export async function getSeedConnectionById(
734
+ seedId: string,
735
+ roles: string[],
736
+ ): Promise<ManagedConnection | null> {
737
+ const all = await getManagedConnections(roles);
738
+ return all.find((c) => c.seedId === seedId) ?? null;
739
+ }
740
+
741
+ /**
742
+ * Get seed connection by ID WITHOUT role filtering.
743
+ * Used only for 403-vs-404 differentiation in resolveConnection().
744
+ */
745
+ export async function getSeedConnectionByIdUnfiltered(
746
+ seedId: string,
747
+ ): Promise<ManagedConnection | null> {
748
+ const all = await loadAndResolve();
749
+ return all.find((c) => c.seedId === seedId) ?? null;
750
+ }
751
+ ```
752
+
753
+ - [ ] **Step 4: Run tests to verify they pass**
754
+
755
+ ```bash
756
+ bun test tests/unit/seed/index.test.ts
757
+ ```
758
+
759
+ - [ ] **Step 5: Run all seed unit tests**
760
+
761
+ ```bash
762
+ bun test tests/unit/seed/
763
+ ```
764
+
765
+ Expected: All PASS
766
+
767
+ - [ ] **Step 6: Commit**
768
+
769
+ ```bash
770
+ git add src/lib/seed/index.ts tests/unit/seed/index.test.ts
771
+ git commit -m "feat(seed): add barrel export with orchestrator and unfiltered lookup"
772
+ ```
773
+
774
+ ---
775
+
776
+ ## Task 7: Resolve Connection Utility (`src/lib/seed/resolve-connection.ts`)
777
+
778
+ **Files:**
779
+ - Modify: `src/lib/audit.ts` (add `'managed_connection'` event type)
780
+ - Create: `src/lib/seed/resolve-connection.ts`
781
+ - Create: `tests/unit/seed/resolve-connection.test.ts`
782
+
783
+ - [ ] **Step 1: Add managed_connection to AuditEventType**
784
+
785
+ In `src/lib/audit.ts`, add to the `AuditEventType` union:
786
+
787
+ ```typescript
788
+ export type AuditEventType =
789
+ | 'maintenance'
790
+ | 'kill_session'
791
+ | 'masking_config'
792
+ | 'threshold_config'
793
+ | 'connection_test'
794
+ | 'query_execution'
795
+ | 'managed_connection'; // NEW
796
+ ```
797
+
798
+ - [ ] **Step 2: Write failing tests**
799
+
800
+ Create `tests/unit/seed/resolve-connection.test.ts`:
801
+
802
+ ```typescript
803
+ import { describe, it, expect, beforeEach, afterEach } from 'bun:test';
804
+ import path from 'path';
805
+ import type { DatabaseConnection } from '@/lib/types';
806
+
807
+ const FIXTURES = path.resolve(__dirname, '../../../fixtures/seed-connections');
808
+ process.env.SEED_CONFIG_PATH = path.join(FIXTURES, 'multi-role-config.yaml');
809
+ process.env.ADMIN_PG_PASS = 'admin-secret';
810
+ process.env.USER_MYSQL_PASS = 'user-secret';
811
+ process.env.SHARED_PG_PASS = 'shared-secret';
812
+ process.env.BOTH_PG_PASS = 'both-secret';
813
+
814
+ import { resolveConnection, SeedConnectionError } from '@/lib/seed/resolve-connection';
815
+ import { resetCache } from '@/lib/seed/config-loader';
816
+
817
+ describe('resolve-connection', () => {
818
+ beforeEach(() => {
819
+ resetCache();
820
+ });
821
+
822
+ it('returns connection object as-is when no connectionId', async () => {
823
+ const conn: DatabaseConnection = {
824
+ id: 'user-conn', name: 'User DB', type: 'postgres', host: 'localhost', createdAt: new Date(),
825
+ };
826
+ const result = await resolveConnection({ connection: conn }, { role: 'user', username: 'test' });
827
+ expect(result.id).toBe('user-conn');
828
+ });
829
+
830
+ it('resolves seed connection by connectionId', async () => {
831
+ const result = await resolveConnection(
832
+ { connectionId: 'seed:everyone' },
833
+ { role: 'user', username: 'test' },
834
+ );
835
+ expect(result.id).toBe('seed:everyone');
836
+ expect(result.password).toBe('shared-secret');
837
+ });
838
+
839
+ it('throws 403 when role does not have access', async () => {
840
+ try {
841
+ await resolveConnection({ connectionId: 'seed:admin-only' }, { role: 'user', username: 'test' });
842
+ expect(true).toBe(false); // should not reach
843
+ } catch (err) {
844
+ expect(err).toBeInstanceOf(SeedConnectionError);
845
+ expect((err as SeedConnectionError).statusCode).toBe(403);
846
+ }
847
+ });
848
+
849
+ it('throws 404 when seed connection does not exist', async () => {
850
+ try {
851
+ await resolveConnection({ connectionId: 'seed:nonexistent' }, { role: 'admin', username: 'test' });
852
+ expect(true).toBe(false);
853
+ } catch (err) {
854
+ expect(err).toBeInstanceOf(SeedConnectionError);
855
+ expect((err as SeedConnectionError).statusCode).toBe(404);
856
+ }
857
+ });
858
+
859
+ it('admin can access admin-only connections', async () => {
860
+ const result = await resolveConnection(
861
+ { connectionId: 'seed:admin-only' },
862
+ { role: 'admin', username: 'test' },
863
+ );
864
+ expect(result.password).toBe('admin-secret');
865
+ });
866
+
867
+ it('throws 400 when neither connection nor connectionId', async () => {
868
+ try {
869
+ await resolveConnection({}, { role: 'admin', username: 'test' });
870
+ expect(true).toBe(false);
871
+ } catch (err) {
872
+ expect(err).toBeInstanceOf(SeedConnectionError);
873
+ expect((err as SeedConnectionError).statusCode).toBe(400);
874
+ }
875
+ });
876
+ });
877
+ ```
878
+
879
+ - [ ] **Step 3: Run tests to verify they fail**
880
+ - [ ] **Step 4: Implement resolve-connection.ts**
881
+
882
+ Create `src/lib/seed/resolve-connection.ts`:
883
+
884
+ ```typescript
885
+ import type { DatabaseConnection } from '@/lib/types';
886
+ import { getSeedConnectionById, getSeedConnectionByIdUnfiltered } from './index';
887
+ import { logger } from '@/lib/logger';
888
+
889
+ export class SeedConnectionError extends Error {
890
+ constructor(message: string, public statusCode: number) {
891
+ super(message);
892
+ this.name = 'SeedConnectionError';
893
+ }
894
+ }
895
+
896
+ export async function resolveConnection(
897
+ body: { connection?: DatabaseConnection; connectionId?: string },
898
+ session: { role: string; username: string },
899
+ ): Promise<DatabaseConnection> {
900
+ const { connection, connectionId } = body;
901
+
902
+ if (connection && !connectionId) {
903
+ return connection;
904
+ }
905
+
906
+ if (connectionId) {
907
+ if (!connectionId.startsWith('seed:')) {
908
+ throw new SeedConnectionError('Invalid connection ID format', 400);
909
+ }
910
+
911
+ const seedId = connectionId.slice(5);
912
+ const seedConn = await getSeedConnectionById(seedId, [session.role]);
913
+
914
+ if (!seedConn) {
915
+ // Differentiate 403 vs 404 using unfiltered lookup
916
+ const exists = await getSeedConnectionByIdUnfiltered(seedId);
917
+ if (exists) {
918
+ logger.warn('Seed connection access denied', {
919
+ route: 'seed/resolve-connection',
920
+ connectionId: seedId,
921
+ user: session.username,
922
+ role: session.role,
923
+ });
924
+ throw new SeedConnectionError(
925
+ `Access denied: connection "${seedId}" not available for role "${session.role}"`,
926
+ 403,
927
+ );
928
+ }
929
+ throw new SeedConnectionError(`Seed connection "${seedId}" not found`, 404);
930
+ }
931
+
932
+ logger.debug('Resolved seed connection', {
933
+ route: 'seed/resolve-connection',
934
+ connectionId: seedId,
935
+ user: session.username,
936
+ });
937
+
938
+ return seedConn;
939
+ }
940
+
941
+ throw new SeedConnectionError('Either connection or connectionId is required', 400);
942
+ }
943
+ ```
944
+
945
+ - [ ] **Step 5: Run tests to verify they pass**
946
+ - [ ] **Step 6: Commit**
947
+
948
+ ```bash
949
+ git add src/lib/audit.ts src/lib/seed/resolve-connection.ts tests/unit/seed/resolve-connection.test.ts
950
+ git commit -m "feat(seed): implement resolveConnection with 403/404 differentiation and audit"
951
+ ```
952
+
953
+ ---
954
+
955
+ ## Task 8: API Endpoint (`GET /api/connections/managed`)
956
+
957
+ **Files:**
958
+ - Create: `src/app/api/connections/managed/route.ts`
959
+ - Create: `tests/api/seed/managed-route.test.ts`
960
+
961
+ - [ ] **Step 1: Write failing tests** (same as before, with auth mock)
962
+ - [ ] **Step 2: Run tests to verify they fail**
963
+ - [ ] **Step 3: Implement the API route**
964
+
965
+ Create `src/app/api/connections/managed/route.ts`:
966
+
967
+ ```typescript
968
+ import { NextResponse } from 'next/server';
969
+ import { getSession } from '@/lib/auth';
970
+ import { getManagedConnections } from '@/lib/seed';
971
+ import { logger } from '@/lib/logger';
972
+
973
+ export const dynamic = 'force-dynamic';
974
+
975
+ export async function GET() {
976
+ try {
977
+ const session = await getSession();
978
+ if (!session) {
979
+ return NextResponse.json({ error: 'Authentication required' }, { status: 401 });
980
+ }
981
+
982
+ const connections = await getManagedConnections([session.role]);
983
+
984
+ const sanitized = connections.map((conn) => {
985
+ if (conn.managed) {
986
+ const { password, connectionString, ...rest } = conn;
987
+ return rest;
988
+ }
989
+ return conn;
990
+ });
991
+
992
+ const cacheTTL = Number(process.env.SEED_CACHE_TTL_MS) || 60_000;
993
+
994
+ return NextResponse.json({ connections: sanitized, cacheHint: cacheTTL });
995
+ } catch (error) {
996
+ logger.error('Failed to load managed connections', {
997
+ route: 'GET /api/connections/managed',
998
+ error: (error as Error).message,
999
+ });
1000
+ return NextResponse.json({ error: 'Failed to load managed connections' }, { status: 500 });
1001
+ }
1002
+ }
1003
+ ```
1004
+
1005
+ - [ ] **Step 4: Run tests to verify they pass**
1006
+ - [ ] **Step 5: Commit**
1007
+
1008
+ ```bash
1009
+ git add src/app/api/connections/managed/route.ts tests/api/seed/managed-route.test.ts
1010
+ git commit -m "feat(seed): add GET /api/connections/managed endpoint"
1011
+ ```
1012
+
1013
+ ---
1014
+
1015
+ ## Task 9: Integrate `resolveConnection` into all DB API routes
1016
+
1017
+ **Files:**
1018
+ - Modify: 13 routes in `src/app/api/db/` (all POST, **excluding** `disconnect/route.ts`)
1019
+
1020
+ **Note:** `disconnect/route.ts` is excluded — it already accepts `connectionId` as a cache key. The `seed:X` IDs flow naturally through the provider cache.
1021
+
1022
+ - [ ] **Step 1: Read each route to identify body extraction pattern**
1023
+
1024
+ All affected routes use one of:
1025
+ - Pattern A: `const { connection, ... } = await request.json()`
1026
+ - Pattern B: `const connection = JSON.parse(await request.text())` (schema route only)
1027
+
1028
+ - [ ] **Step 2: Modify each route with resolveConnection**
1029
+
1030
+ For each route, add at the top:
1031
+
1032
+ ```typescript
1033
+ import { resolveConnection, SeedConnectionError } from '@/lib/seed/resolve-connection';
1034
+ import { getSession } from '@/lib/auth';
1035
+ ```
1036
+
1037
+ Change body extraction:
1038
+
1039
+ ```typescript
1040
+ const body = await request.json();
1041
+ const session = await getSession();
1042
+ if (!session) {
1043
+ return NextResponse.json({ error: 'Authentication required' }, { status: 401 });
1044
+ }
1045
+ const connection = await resolveConnection(body, session);
1046
+ ```
1047
+
1048
+ Add to catch block:
1049
+
1050
+ ```typescript
1051
+ if (error instanceof SeedConnectionError) {
1052
+ return NextResponse.json({ error: error.message }, { status: error.statusCode });
1053
+ }
1054
+ ```
1055
+
1056
+ **Special case — `schema/route.ts`:** Currently uses `req.text()` + `JSON.parse()`. Change to `req.json()` for consistency. Client will send `{ connection: conn }` or `{ connectionId: "seed:X" }`.
1057
+
1058
+ **Special case — `maintenance/route.ts`:** Already has session check. Reuse existing session.
1059
+
1060
+ Apply to all 13 routes:
1061
+ 1. `query/route.ts`
1062
+ 2. `multi-query/route.ts`
1063
+ 3. `schema/route.ts`
1064
+ 4. `transaction/route.ts`
1065
+ 5. `cancel/route.ts`
1066
+ 6. `maintenance/route.ts`
1067
+ 7. `monitoring/route.ts`
1068
+ 8. `pool-stats/route.ts` (POST)
1069
+ 9. `profile/route.ts`
1070
+ 10. `provider-meta/route.ts` (POST)
1071
+ 11. `test-connection/route.ts`
1072
+ 12. `schema-snapshot/route.ts`
1073
+ 13. `health/route.ts` (POST connection health check — needs auth added)
1074
+
1075
+ - [ ] **Step 3: Run all existing tests**
1076
+
1077
+ ```bash
1078
+ bun run test
1079
+ ```
1080
+
1081
+ Expected: All PASS
1082
+
1083
+ - [ ] **Step 4: Commit**
1084
+
1085
+ ```bash
1086
+ git add src/app/api/db/
1087
+ git commit -m "feat(seed): integrate resolveConnection into all DB API routes"
1088
+ ```
1089
+
1090
+ ---
1091
+
1092
+ ## Task 10: Shared client helper + useConnectionManager merge
1093
+
1094
+ **Files:**
1095
+ - Create: `src/hooks/use-connection-payload.ts`
1096
+ - Modify: `src/hooks/use-connection-manager.ts`
1097
+
1098
+ - [ ] **Step 1: Create shared helper**
1099
+
1100
+ Create `src/hooks/use-connection-payload.ts`:
1101
+
1102
+ ```typescript
1103
+ import type { DatabaseConnection } from '@/lib/types';
1104
+
1105
+ /**
1106
+ * Builds the connection portion of an API request body.
1107
+ * For managed connections: sends { connectionId: "seed:X" } (no credentials).
1108
+ * For user connections: sends { connection: conn } (full object).
1109
+ */
1110
+ export function buildConnectionPayload(
1111
+ conn: DatabaseConnection,
1112
+ ): { connectionId: string } | { connection: DatabaseConnection } {
1113
+ if (conn.managed && conn.seedId) {
1114
+ return { connectionId: `seed:${conn.seedId}` };
1115
+ }
1116
+ return { connection: conn };
1117
+ }
1118
+ ```
1119
+
1120
+ - [ ] **Step 2: Update useConnectionManager — add managed connection fetch + merge**
1121
+
1122
+ In `src/hooks/use-connection-manager.ts`, after the demo connection fetch block and before the `return` statement of the initialization effect:
1123
+
1124
+ ```typescript
1125
+ // Fetch managed (seed) connections
1126
+ try {
1127
+ const managedRes = await fetch('/api/connections/managed');
1128
+ if (managedRes.ok) {
1129
+ const { connections: managedConns } = await managedRes.json();
1130
+ if (managedConns?.length > 0) {
1131
+ // ... merge logic as described in spec Section 4
1132
+ }
1133
+ }
1134
+ } catch {
1135
+ // Managed connections are optional
1136
+ }
1137
+ ```
1138
+
1139
+ - [ ] **Step 3: Update fetchSchema to use buildConnectionPayload**
1140
+
1141
+ In `use-connection-manager.ts`, `fetchSchema` callback (line 27-30):
1142
+
1143
+ ```typescript
1144
+ // Before:
1145
+ body: JSON.stringify(conn),
1146
+
1147
+ // After:
1148
+ import { buildConnectionPayload } from './use-connection-payload';
1149
+ body: JSON.stringify(buildConnectionPayload(conn)),
1150
+ ```
1151
+
1152
+ **Important:** The schema route was updated in Task 9 to use `req.json()` + `resolveConnection()`. The client must now send `{ connection: conn }` (wrapped) or `{ connectionId: "seed:X" }`, NOT the bare `conn` object. `buildConnectionPayload()` handles both cases correctly.
1153
+
1154
+ - [ ] **Step 4: Update health pulse fetch** (line ~171):
1155
+
1156
+ ```typescript
1157
+ body: JSON.stringify(buildConnectionPayload(conn)),
1158
+ ```
1159
+
1160
+ - [ ] **Step 5: Run tests**
1161
+
1162
+ ```bash
1163
+ bun run test:hooks
1164
+ ```
1165
+
1166
+ - [ ] **Step 6: Commit**
1167
+
1168
+ ```bash
1169
+ git add src/hooks/use-connection-payload.ts src/hooks/use-connection-manager.ts
1170
+ git commit -m "feat(seed): add managed connection merge to useConnectionManager"
1171
+ ```
1172
+
1173
+ ---
1174
+
1175
+ ## Task 11: Client hooks — useQueryExecution + useTransactionControl
1176
+
1177
+ **Files:**
1178
+ - Modify: `src/hooks/use-query-execution.ts`
1179
+ - Modify: `src/hooks/use-transaction-control.ts`
1180
+
1181
+ - [ ] **Step 1: Update ALL 6 fetch calls in useQueryExecution**
1182
+
1183
+ Import helper and update each fetch site:
1184
+
1185
+ ```typescript
1186
+ import { buildConnectionPayload } from './use-connection-payload';
1187
+ ```
1188
+
1189
+ **Site 1 — Playground BEGIN** (line 150):
1190
+ ```typescript
1191
+ body: JSON.stringify({ ...buildConnectionPayload(activeConnection), action: 'begin' }),
1192
+ ```
1193
+
1194
+ **Site 2 — Main query** (line ~179):
1195
+ ```typescript
1196
+ body: JSON.stringify({
1197
+ ...buildConnectionPayload(activeConnection),
1198
+ ...(useTransaction ? { action: 'query', sql, options } : { sql, options, ...(!useMultiQuery && { queryId }) }),
1199
+ }),
1200
+ ```
1201
+
1202
+ **Site 3 — Background EXPLAIN query** (line ~198-202):
1203
+ ```typescript
1204
+ body: JSON.stringify({
1205
+ ...buildConnectionPayload(activeConnection),
1206
+ sql: explainSql,
1207
+ options: {},
1208
+ }),
1209
+ ```
1210
+
1211
+ **Site 4 — Playground rollback success** (line ~357):
1212
+ ```typescript
1213
+ body: JSON.stringify({ ...buildConnectionPayload(activeConnection), action: 'rollback' }),
1214
+ ```
1215
+
1216
+ **Site 5 — Playground rollback error** (line ~382):
1217
+ ```typescript
1218
+ body: JSON.stringify({ ...buildConnectionPayload(activeConnection), action: 'rollback' }),
1219
+ ```
1220
+
1221
+ **Site 6 — Cancel query** (line ~433):
1222
+ ```typescript
1223
+ body: JSON.stringify({
1224
+ ...buildConnectionPayload(activeConnection),
1225
+ queryId: activeQueryIdRef.current,
1226
+ }),
1227
+ ```
1228
+
1229
+ - [ ] **Step 2: Update useTransactionControl** (line 20-24):
1230
+
1231
+ ```typescript
1232
+ import { buildConnectionPayload } from './use-connection-payload';
1233
+
1234
+ body: JSON.stringify({
1235
+ ...buildConnectionPayload(activeConnection),
1236
+ action,
1237
+ }),
1238
+ ```
1239
+
1240
+ - [ ] **Step 3: Run tests**
1241
+
1242
+ ```bash
1243
+ bun run test:hooks
1244
+ ```
1245
+
1246
+ - [ ] **Step 4: Commit**
1247
+
1248
+ ```bash
1249
+ git add src/hooks/use-query-execution.ts src/hooks/use-transaction-control.ts
1250
+ git commit -m "feat(seed): send connectionId for managed connections in all hook fetch calls"
1251
+ ```
1252
+
1253
+ ---
1254
+
1255
+ ## Task 12: UI — Lock icon + hide edit/delete for managed connections
1256
+
1257
+ **Files:**
1258
+ - Modify: `src/components/sidebar/ConnectionItem.tsx`
1259
+
1260
+ - [ ] **Step 1: Add lock icon and conditional buttons**
1261
+
1262
+ Import Lock icon, add managed lock indicator, wrap edit/delete with `!conn.managed` check.
1263
+
1264
+ - [ ] **Step 2: Run component tests**
1265
+
1266
+ ```bash
1267
+ bun run test:components
1268
+ ```
1269
+
1270
+ - [ ] **Step 3: Commit**
1271
+
1272
+ ```bash
1273
+ git add src/components/sidebar/ConnectionItem.tsx
1274
+ git commit -m "feat(seed): add lock icon and hide edit/delete for managed connections"
1275
+ ```
1276
+
1277
+ ---
1278
+
1279
+ ## Task 13: Integration Tests
1280
+
1281
+ **Files:**
1282
+ - Create: `tests/integration/seed/seed-pipeline.test.ts`
1283
+
1284
+ - [ ] **Step 1: Write integration tests** — full pipeline, partial failure, hot-reload, defaults merge, audit trail
1285
+ - [ ] **Step 2: Run integration tests**
1286
+
1287
+ ```bash
1288
+ bun test tests/integration/seed/
1289
+ ```
1290
+
1291
+ - [ ] **Step 3: Commit**
1292
+
1293
+ ```bash
1294
+ git add tests/integration/seed/
1295
+ git commit -m "test(seed): add integration tests for full seed pipeline"
1296
+ ```
1297
+
1298
+ ---
1299
+
1300
+ ## Task 14: Helm chart + Docker + env documentation
1301
+
1302
+ **Files:**
1303
+ - Create: `charts/libredb-studio/templates/seed-configmap.yaml`
1304
+ - Modify: `charts/libredb-studio/values.yaml`
1305
+ - Modify: `charts/libredb-studio/values.schema.json`
1306
+ - Modify: `charts/libredb-studio/templates/deployment.yaml`
1307
+ - Modify: `docker-compose.yml`
1308
+ - Modify: `.env.example`
1309
+
1310
+ - [ ] **Step 1: Create seed-configmap.yaml**
1311
+ - [ ] **Step 2: Add seedConnections to values.yaml**
1312
+ - [ ] **Step 3: Update values.schema.json**
1313
+ - [ ] **Step 4: Update deployment.yaml** (volume mount + env vars)
1314
+ - [ ] **Step 5: Update docker-compose.yml** (commented example)
1315
+ - [ ] **Step 6: Update .env.example** (new env vars)
1316
+ - [ ] **Step 7: Lint Helm chart**
1317
+
1318
+ ```bash
1319
+ helm lint charts/libredb-studio --strict
1320
+ helm template test charts/libredb-studio --set secrets.jwtSecret=test-secret-32-chars-minimum-here --set secrets.adminPassword=test123 --set secrets.userPassword=test123 --set seedConnections.enabled=true
1321
+ ```
1322
+
1323
+ - [ ] **Step 8: Commit**
1324
+
1325
+ ```bash
1326
+ git add charts/ docker-compose.yml .env.example
1327
+ git commit -m "feat(seed): add Helm chart, Docker, and env documentation for seed connections"
1328
+ ```
1329
+
1330
+ ---
1331
+
1332
+ ## Task 15: CI Verification
1333
+
1334
+ - [ ] **Step 1: Lint**
1335
+
1336
+ ```bash
1337
+ bun run lint
1338
+ ```
1339
+
1340
+ - [ ] **Step 2: Type check**
1341
+
1342
+ ```bash
1343
+ bun run typecheck
1344
+ ```
1345
+
1346
+ - [ ] **Step 3: Run all tests**
1347
+
1348
+ ```bash
1349
+ bun run test
1350
+ ```
1351
+
1352
+ - [ ] **Step 4: Build**
1353
+
1354
+ ```bash
1355
+ bun run build
1356
+ ```
1357
+
1358
+ - [ ] **Step 5: Fix any failures and commit**
1359
+
1360
+ ```bash
1361
+ git log --oneline -15 # verify all seed commits
1362
+ ```