@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,236 @@
1
+ /**
2
+ * API Error Response Standardization
3
+ * Centralized error-to-HTTP mapping for all API routes
4
+ */
5
+
6
+ import { NextResponse } from 'next/server';
7
+ import { logger } from '@/lib/logger';
8
+ import { ApiErrorCode } from './error-codes';
9
+ import {
10
+ DatabaseError,
11
+ DatabaseConfigError,
12
+ ConnectionError,
13
+ AuthenticationError,
14
+ PoolExhaustedError,
15
+ QueryError,
16
+ QueryCancelledError,
17
+ TimeoutError,
18
+ } from '@/lib/db/errors';
19
+ import {
20
+ LLMError,
21
+ LLMAuthError,
22
+ LLMConfigError,
23
+ LLMRateLimitError,
24
+ LLMSafetyError,
25
+ LLMStreamError,
26
+ } from '@/lib/llm/types';
27
+ import { SeedConnectionError } from '@/lib/seed/resolve-connection';
28
+
29
+ // ============================================================================
30
+ // Types
31
+ // ============================================================================
32
+
33
+ export type { ApiErrorCode } from './error-codes';
34
+
35
+ export interface ApiErrorResponse {
36
+ error: string;
37
+ code?: ApiErrorCode;
38
+ statusCode: number;
39
+ retryable?: boolean;
40
+ details?: unknown;
41
+ }
42
+
43
+ // ============================================================================
44
+ // Error → HTTP Mapping
45
+ // ============================================================================
46
+
47
+ export function createErrorResponse(
48
+ error: unknown,
49
+ context?: { route?: string }
50
+ ): NextResponse<ApiErrorResponse> {
51
+ const route = context?.route;
52
+
53
+ // --- Seed Connection Error ---
54
+ if (error instanceof SeedConnectionError) {
55
+ logger.warn('Seed connection error', { route, statusCode: error.statusCode });
56
+ const code = error.statusCode === 403 || error.statusCode === 401
57
+ ? ApiErrorCode.AUTH_ERROR
58
+ : error.statusCode === 400
59
+ ? ApiErrorCode.CONFIG_ERROR
60
+ : undefined;
61
+ return NextResponse.json(
62
+ { error: error.message, ...(code ? { code } : {}), statusCode: error.statusCode },
63
+ { status: error.statusCode }
64
+ );
65
+ }
66
+
67
+ // --- Query Cancelled ---
68
+ if (error instanceof QueryCancelledError) {
69
+ logger.info('Query cancelled', { route, provider: error.provider });
70
+ return NextResponse.json(
71
+ {
72
+ error: error.message,
73
+ code: ApiErrorCode.QUERY_CANCELLED,
74
+ statusCode: 499,
75
+ },
76
+ { status: 499 }
77
+ );
78
+ }
79
+
80
+ // --- DB: QueryError (syntax / execution) ---
81
+ if (error instanceof QueryError) {
82
+ logger.warn('Query error', { route, provider: error.provider });
83
+ return NextResponse.json(
84
+ {
85
+ error: error.message,
86
+ code: ApiErrorCode.QUERY_ERROR,
87
+ statusCode: 400,
88
+ details: error.position !== undefined || error.detail
89
+ ? { position: error.position, detail: error.detail }
90
+ : undefined,
91
+ },
92
+ { status: 400 }
93
+ );
94
+ }
95
+
96
+ // --- DB: DatabaseConfigError ---
97
+ if (error instanceof DatabaseConfigError) {
98
+ logger.warn('Database config error', { route, provider: error.provider });
99
+ return NextResponse.json(
100
+ { error: error.message, code: ApiErrorCode.CONFIG_ERROR, statusCode: 400 },
101
+ { status: 400 }
102
+ );
103
+ }
104
+
105
+ // --- DB: AuthenticationError ---
106
+ if (error instanceof AuthenticationError) {
107
+ logger.warn('Database auth error', { route, provider: error.provider });
108
+ return NextResponse.json(
109
+ { error: error.message, code: ApiErrorCode.AUTH_ERROR, statusCode: 401 },
110
+ { status: 401 }
111
+ );
112
+ }
113
+
114
+ // --- DB: TimeoutError ---
115
+ if (error instanceof TimeoutError) {
116
+ logger.warn('Query timeout', { route, provider: error.provider, duration: error.timeout });
117
+ return NextResponse.json(
118
+ {
119
+ error: 'Query timed out. Please try a simpler query or increase timeout.',
120
+ code: ApiErrorCode.TIMEOUT_ERROR,
121
+ statusCode: 408,
122
+ retryable: true,
123
+ },
124
+ { status: 408 }
125
+ );
126
+ }
127
+
128
+ // --- DB: ConnectionError ---
129
+ if (error instanceof ConnectionError) {
130
+ logger.error('Connection error', error, { route, provider: error.provider });
131
+ return NextResponse.json(
132
+ {
133
+ error: error.message,
134
+ code: ApiErrorCode.CONNECTION_ERROR,
135
+ statusCode: 503,
136
+ retryable: true,
137
+ },
138
+ { status: 503 }
139
+ );
140
+ }
141
+
142
+ // --- DB: PoolExhaustedError ---
143
+ if (error instanceof PoolExhaustedError) {
144
+ logger.error('Pool exhausted', error, { route, provider: error.provider });
145
+ return NextResponse.json(
146
+ {
147
+ error: error.message,
148
+ code: ApiErrorCode.POOL_EXHAUSTED,
149
+ statusCode: 503,
150
+ retryable: true,
151
+ },
152
+ { status: 503 }
153
+ );
154
+ }
155
+
156
+ // --- DB: generic DatabaseError (base class catch-all) ---
157
+ if (error instanceof DatabaseError) {
158
+ logger.error('Database error', error, { route, provider: error.provider });
159
+ return NextResponse.json(
160
+ {
161
+ error: error.message,
162
+ code: (error.code as ApiErrorCode) ?? ApiErrorCode.DATABASE_ERROR,
163
+ statusCode: 500,
164
+ },
165
+ { status: 500 }
166
+ );
167
+ }
168
+
169
+ // --- LLM: Safety ---
170
+ if (error instanceof LLMSafetyError) {
171
+ logger.warn('LLM safety filter triggered', { route, provider: error.provider });
172
+ return NextResponse.json(
173
+ { error: 'The prompt was blocked by safety filters.', code: ApiErrorCode.LLM_SAFETY, statusCode: 400 },
174
+ { status: 400 }
175
+ );
176
+ }
177
+
178
+ // --- LLM: Auth ---
179
+ if (error instanceof LLMAuthError) {
180
+ logger.warn('LLM auth error', { route, provider: error.provider });
181
+ return NextResponse.json(
182
+ { error: 'Invalid API key. Please check your configuration.', code: ApiErrorCode.LLM_AUTH, statusCode: 401 },
183
+ { status: 401 }
184
+ );
185
+ }
186
+
187
+ // --- LLM: Rate Limit ---
188
+ if (error instanceof LLMRateLimitError) {
189
+ logger.warn('LLM rate limit', { route, provider: error.provider });
190
+ return NextResponse.json(
191
+ {
192
+ error: 'AI usage limit reached. Please try again later or check your billing status.',
193
+ code: ApiErrorCode.LLM_RATE_LIMIT,
194
+ statusCode: 429,
195
+ retryable: true,
196
+ },
197
+ { status: 429 }
198
+ );
199
+ }
200
+
201
+ // --- LLM: Config ---
202
+ if (error instanceof LLMConfigError) {
203
+ logger.warn('LLM config error', { route, provider: error.provider });
204
+ return NextResponse.json(
205
+ { error: error.message, code: ApiErrorCode.LLM_CONFIG, statusCode: 503 },
206
+ { status: 503 }
207
+ );
208
+ }
209
+
210
+ // --- LLM: Stream ---
211
+ if (error instanceof LLMStreamError) {
212
+ logger.error('LLM stream error', error, { route, provider: error.provider });
213
+ return NextResponse.json(
214
+ { error: error.message, code: ApiErrorCode.LLM_STREAM, statusCode: 502, retryable: true },
215
+ { status: 502 }
216
+ );
217
+ }
218
+
219
+ // --- LLM: generic LLMError (base class catch-all) ---
220
+ if (error instanceof LLMError) {
221
+ const status = error.statusCode ?? 500;
222
+ logger.error('LLM error', error, { route, provider: error.provider });
223
+ return NextResponse.json(
224
+ { error: error.message, code: ApiErrorCode.LLM_ERROR, statusCode: status },
225
+ { status }
226
+ );
227
+ }
228
+
229
+ // --- Generic Error ---
230
+ const message = error instanceof Error ? error.message : 'Internal server error';
231
+ logger.error('Unhandled error', error, { route });
232
+ return NextResponse.json(
233
+ { error: message, code: ApiErrorCode.INTERNAL_ERROR, statusCode: 500 },
234
+ { status: 500 }
235
+ );
236
+ }
@@ -0,0 +1,41 @@
1
+ /**
2
+ * Route Handler Wrappers
3
+ * Optional HOF wrappers for consistent error handling in API routes
4
+ */
5
+
6
+ import { NextRequest, NextResponse } from 'next/server';
7
+ import { createErrorResponse } from './errors';
8
+
9
+ /**
10
+ * Wraps a database API route handler with standardized error handling.
11
+ * Catches all errors and maps them via createErrorResponse.
12
+ */
13
+ export function withDbErrorHandler(
14
+ handler: (req: NextRequest) => Promise<NextResponse>,
15
+ route?: string
16
+ ): (req: NextRequest) => Promise<NextResponse> {
17
+ return async (req: NextRequest) => {
18
+ try {
19
+ return await handler(req);
20
+ } catch (error) {
21
+ return createErrorResponse(error, { route });
22
+ }
23
+ };
24
+ }
25
+
26
+ /**
27
+ * Wraps an AI API route handler with standardized error handling.
28
+ * Supports both NextResponse (error) and Response (streaming) return types.
29
+ */
30
+ export function withAiErrorHandler(
31
+ handler: (req: NextRequest) => Promise<Response | NextResponse>,
32
+ route?: string
33
+ ): (req: NextRequest) => Promise<Response | NextResponse> {
34
+ return async (req: NextRequest) => {
35
+ try {
36
+ return await handler(req);
37
+ } catch (error) {
38
+ return createErrorResponse(error, { route });
39
+ }
40
+ };
41
+ }
@@ -0,0 +1,105 @@
1
+ import { storage } from '@/lib/storage';
2
+
3
+ export type AuditEventType =
4
+ | 'maintenance'
5
+ | 'kill_session'
6
+ | 'masking_config'
7
+ | 'threshold_config'
8
+ | 'connection_test'
9
+ | 'query_execution'
10
+ | 'managed_connection';
11
+
12
+ export interface AuditEvent {
13
+ id: string;
14
+ timestamp: string;
15
+ type: AuditEventType;
16
+ action: string;
17
+ target: string;
18
+ connectionName?: string;
19
+ user: string;
20
+ result: 'success' | 'failure';
21
+ duration?: number;
22
+ details?: string;
23
+ }
24
+
25
+ const MAX_EVENTS = 1000;
26
+
27
+ export class AuditRingBuffer {
28
+ private events: AuditEvent[] = [];
29
+ private maxSize: number;
30
+
31
+ constructor(maxSize = MAX_EVENTS) {
32
+ this.maxSize = maxSize;
33
+ }
34
+
35
+ push(event: Omit<AuditEvent, 'id' | 'timestamp'>) {
36
+ const fullEvent: AuditEvent = {
37
+ ...event,
38
+ id: `${Date.now()}-${Math.random().toString(36).slice(2, 8)}`,
39
+ timestamp: new Date().toISOString(),
40
+ };
41
+ this.events.push(fullEvent);
42
+ if (this.events.length > this.maxSize) {
43
+ this.events = this.events.slice(-this.maxSize);
44
+ }
45
+ return fullEvent;
46
+ }
47
+
48
+ getAll(): AuditEvent[] {
49
+ return [...this.events];
50
+ }
51
+
52
+ getRecent(count: number): AuditEvent[] {
53
+ return this.events.slice(-count);
54
+ }
55
+
56
+ filter(opts: {
57
+ type?: AuditEventType;
58
+ result?: 'success' | 'failure';
59
+ connectionName?: string;
60
+ since?: string;
61
+ }): AuditEvent[] {
62
+ return this.events.filter((e) => {
63
+ if (opts.type && e.type !== opts.type) return false;
64
+ if (opts.result && e.result !== opts.result) return false;
65
+ if (opts.connectionName && e.connectionName !== opts.connectionName) return false;
66
+ if (opts.since && e.timestamp < opts.since) return false;
67
+ return true;
68
+ });
69
+ }
70
+
71
+ clear() {
72
+ this.events = [];
73
+ }
74
+
75
+ get size() {
76
+ return this.events.length;
77
+ }
78
+
79
+ toJSON(): AuditEvent[] {
80
+ return this.events;
81
+ }
82
+
83
+ loadFrom(events: AuditEvent[]) {
84
+ this.events = events.slice(-this.maxSize);
85
+ }
86
+ }
87
+
88
+ // Global server-side instance
89
+ let _serverBuffer: AuditRingBuffer | null = null;
90
+
91
+ export function getServerAuditBuffer(): AuditRingBuffer {
92
+ if (!_serverBuffer) {
93
+ _serverBuffer = new AuditRingBuffer();
94
+ }
95
+ return _serverBuffer;
96
+ }
97
+
98
+ // Client-side localStorage persistence — delegates to storage module
99
+ export function loadAuditFromStorage(): AuditEvent[] {
100
+ return storage.getAuditLog();
101
+ }
102
+
103
+ export function saveAuditToStorage(events: AuditEvent[]) {
104
+ storage.saveAuditLog(events);
105
+ }
@@ -0,0 +1,87 @@
1
+ import { SignJWT, jwtVerify } from 'jose';
2
+ import { cookies } from 'next/headers';
3
+ import { logger } from '@/lib/logger';
4
+
5
+ function getJwtSecret(): Uint8Array {
6
+ const secret = process.env.JWT_SECRET;
7
+
8
+ if (!secret) {
9
+ if (process.env.NODE_ENV === 'production') {
10
+ throw new Error('JWT_SECRET environment variable is required in production');
11
+ }
12
+ // Development fallback - only for local development
13
+ console.warn('⚠️ JWT_SECRET not set, using development fallback. Set JWT_SECRET in production!');
14
+ return new TextEncoder().encode('development-fallback-secret-32ch');
15
+ }
16
+
17
+ if (secret.length < 32) {
18
+ throw new Error('JWT_SECRET must be at least 32 characters long');
19
+ }
20
+
21
+ return new TextEncoder().encode(secret);
22
+ }
23
+
24
+ // Lazy-initialized to prevent module-level crash if JWT_SECRET is misconfigured.
25
+ // A module-level throw would crash ALL modules that import auth.ts.
26
+ let _jwtSecret: Uint8Array | null = null;
27
+ function jwtSecret(): Uint8Array {
28
+ if (!_jwtSecret) {
29
+ _jwtSecret = getJwtSecret();
30
+ }
31
+ return _jwtSecret;
32
+ }
33
+
34
+ export type Role = 'admin' | 'user';
35
+
36
+ export interface UserPayload {
37
+ role: Role;
38
+ username: string;
39
+ }
40
+
41
+ export async function signJWT(payload: UserPayload) {
42
+ return await new SignJWT({ ...payload })
43
+ .setProtectedHeader({ alg: 'HS256' })
44
+ .setIssuedAt()
45
+ .setExpirationTime('24h')
46
+ .sign(jwtSecret());
47
+ }
48
+
49
+ export async function verifyJWT(token: string) {
50
+ try {
51
+ const { payload } = await jwtVerify(token, jwtSecret());
52
+ return payload as unknown as UserPayload;
53
+ } catch (error) {
54
+ if (error instanceof Error) {
55
+ if (error.message.includes('expired')) {
56
+ logger.debug('JWT token expired', { route: 'auth' });
57
+ } else {
58
+ logger.warn('JWT verification failed', { route: 'auth' });
59
+ }
60
+ }
61
+ return null;
62
+ }
63
+ }
64
+
65
+ export async function getSession() {
66
+ const cookieStore = await cookies();
67
+ const token = cookieStore.get('auth-token')?.value;
68
+ if (!token) return null;
69
+ return await verifyJWT(token);
70
+ }
71
+
72
+ export async function login(role: Role, username?: string) {
73
+ const token = await signJWT({ role, username: username || role });
74
+ const cookieStore = await cookies();
75
+ cookieStore.set('auth-token', token, {
76
+ httpOnly: true,
77
+ secure: process.env.NODE_ENV === 'production',
78
+ sameSite: 'lax',
79
+ maxAge: 60 * 60 * 24, // 1 day
80
+ path: '/',
81
+ });
82
+ }
83
+
84
+ export async function logout() {
85
+ const cookieStore = await cookies();
86
+ cookieStore.delete('auth-token');
87
+ }
@@ -0,0 +1,172 @@
1
+ import { DatabaseType } from '@/lib/types';
2
+
3
+ export interface ParsedConnection {
4
+ type: DatabaseType;
5
+ host?: string;
6
+ port?: string;
7
+ user?: string;
8
+ password?: string;
9
+ database?: string;
10
+ connectionString?: string;
11
+ }
12
+
13
+ /**
14
+ * Parse a database connection string URL into its components.
15
+ * Supports: postgres://, postgresql://, mysql://, mongodb://, mongodb+srv://, redis://
16
+ */
17
+ export function parseConnectionString(input: string): ParsedConnection | null {
18
+ const trimmed = input.trim();
19
+ if (!trimmed) return null;
20
+
21
+ // MongoDB connection strings
22
+ if (trimmed.startsWith('mongodb://') || trimmed.startsWith('mongodb+srv://')) {
23
+ return parseMongoDBString(trimmed);
24
+ }
25
+
26
+ // PostgreSQL
27
+ if (trimmed.startsWith('postgres://') || trimmed.startsWith('postgresql://')) {
28
+ return parseGenericURL(trimmed, 'postgres', '5432');
29
+ }
30
+
31
+ // MySQL
32
+ if (trimmed.startsWith('mysql://')) {
33
+ return parseGenericURL(trimmed, 'mysql', '3306');
34
+ }
35
+
36
+ // Redis
37
+ if (trimmed.startsWith('redis://') || trimmed.startsWith('rediss://')) {
38
+ return parseGenericURL(trimmed, 'redis', '6379');
39
+ }
40
+
41
+ // Oracle
42
+ if (trimmed.startsWith('oracle://')) {
43
+ return parseGenericURL(trimmed, 'oracle', '1521');
44
+ }
45
+
46
+ // MSSQL / SQL Server
47
+ if (trimmed.startsWith('mssql://') || trimmed.startsWith('sqlserver://')) {
48
+ return parseGenericURL(trimmed, 'mssql', '1433');
49
+ }
50
+
51
+ // ADO.NET format: Server=host;Database=db;User Id=user;Password=pass;
52
+ if (/^Server\s*=/i.test(trimmed)) {
53
+ return parseADONetString(trimmed);
54
+ }
55
+
56
+ return null;
57
+ }
58
+
59
+ function parseMongoDBString(uri: string): ParsedConnection {
60
+ const result: ParsedConnection = {
61
+ type: 'mongodb',
62
+ connectionString: uri,
63
+ };
64
+
65
+ try {
66
+ // For mongodb+srv, we can't use URL directly for host/port
67
+ // but we can extract user/pass/database
68
+ const isSRV = uri.startsWith('mongodb+srv://');
69
+
70
+ // Extract database from path
71
+ const withoutProtocol = uri.replace(/^mongodb(\+srv)?:\/\//, '');
72
+ const atIndex = withoutProtocol.indexOf('@');
73
+ const afterAuth = atIndex >= 0 ? withoutProtocol.slice(atIndex + 1) : withoutProtocol;
74
+
75
+ // Split host(s) from path
76
+ const slashIndex = afterAuth.indexOf('/');
77
+ if (slashIndex >= 0) {
78
+ const pathPart = afterAuth.slice(slashIndex + 1);
79
+ const dbName = pathPart.split('?')[0];
80
+ if (dbName) result.database = decodeURIComponent(dbName);
81
+ }
82
+
83
+ // Extract credentials
84
+ if (atIndex >= 0) {
85
+ const authPart = withoutProtocol.slice(0, atIndex);
86
+ const colonIndex = authPart.indexOf(':');
87
+ if (colonIndex >= 0) {
88
+ result.user = decodeURIComponent(authPart.slice(0, colonIndex));
89
+ result.password = decodeURIComponent(authPart.slice(colonIndex + 1));
90
+ } else {
91
+ result.user = decodeURIComponent(authPart);
92
+ }
93
+ }
94
+
95
+ // Extract host/port for non-SRV
96
+ if (!isSRV && slashIndex >= 0) {
97
+ const hostPart = afterAuth.slice(0, slashIndex);
98
+ const firstHost = hostPart.split(',')[0]; // take first host for replica sets
99
+ const [host, port] = firstHost.split(':');
100
+ if (host) result.host = host;
101
+ if (port) result.port = port;
102
+ }
103
+ } catch {
104
+ // If parsing fails, we still have the connectionString
105
+ }
106
+
107
+ return result;
108
+ }
109
+
110
+ function parseADONetString(input: string): ParsedConnection | null {
111
+ try {
112
+ const params: Record<string, string> = {};
113
+ input.split(';').forEach((part) => {
114
+ const eq = part.indexOf('=');
115
+ if (eq > 0) {
116
+ const key = part.slice(0, eq).trim().toLowerCase();
117
+ const val = part.slice(eq + 1).trim();
118
+ params[key] = val;
119
+ }
120
+ });
121
+
122
+ const host = params['server'] || params['data source'] || 'localhost';
123
+ const [hostPart, portPart] = host.split(',');
124
+
125
+ return {
126
+ type: 'mssql',
127
+ host: hostPart || 'localhost',
128
+ port: portPart || '1433',
129
+ user: params['user id'] || params['uid'] || undefined,
130
+ password: params['password'] || params['pwd'] || undefined,
131
+ database: params['database'] || params['initial catalog'] || undefined,
132
+ };
133
+ } catch {
134
+ return null;
135
+ }
136
+ }
137
+
138
+ function parseGenericURL(
139
+ uri: string,
140
+ type: DatabaseType,
141
+ defaultPort: string
142
+ ): ParsedConnection | null {
143
+ try {
144
+ const url = new URL(uri);
145
+
146
+ return {
147
+ type,
148
+ host: url.hostname || 'localhost',
149
+ port: url.port || defaultPort,
150
+ user: url.username ? decodeURIComponent(url.username) : undefined,
151
+ password: url.password ? decodeURIComponent(url.password) : undefined,
152
+ database: url.pathname.slice(1) || undefined, // remove leading /
153
+ };
154
+ } catch {
155
+ return null;
156
+ }
157
+ }
158
+
159
+ /**
160
+ * Detect the database type from a connection string.
161
+ */
162
+ export function detectConnectionStringType(input: string): DatabaseType | null {
163
+ const trimmed = input.trim().toLowerCase();
164
+ if (trimmed.startsWith('postgres://') || trimmed.startsWith('postgresql://')) return 'postgres';
165
+ if (trimmed.startsWith('mysql://')) return 'mysql';
166
+ if (trimmed.startsWith('mongodb://') || trimmed.startsWith('mongodb+srv://')) return 'mongodb';
167
+ if (trimmed.startsWith('redis://') || trimmed.startsWith('rediss://')) return 'redis';
168
+ if (trimmed.startsWith('oracle://')) return 'oracle';
169
+ if (trimmed.startsWith('mssql://') || trimmed.startsWith('sqlserver://')) return 'mssql';
170
+ if (/^server\s*=/i.test(trimmed)) return 'mssql';
171
+ return null;
172
+ }