@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,144 @@
1
+ import { NextRequest, NextResponse } from 'next/server';
2
+ import { getOrCreateProvider } from '@/lib/db/factory';
3
+ import { createErrorResponse } from '@/lib/api/errors';
4
+ import { resolveConnection } from '@/lib/seed/resolve-connection';
5
+ import { getSession } from '@/lib/auth';
6
+
7
+ export async function POST(req: NextRequest) {
8
+ try {
9
+ const body = await req.json();
10
+ const { tableName, columns } = body;
11
+
12
+ const session = await getSession();
13
+ if (!session) {
14
+ return NextResponse.json({ error: 'Authentication required' }, { status: 401 });
15
+ }
16
+
17
+ const connection = await resolveConnection(body, session);
18
+
19
+ if (!tableName) {
20
+ return NextResponse.json({ error: 'tableName is required' }, { status: 400 });
21
+ }
22
+
23
+ const provider = await getOrCreateProvider(connection);
24
+
25
+ {
26
+ const capabilities = provider.getCapabilities();
27
+ const isSQL = capabilities.queryLanguage === 'sql';
28
+
29
+ if (!isSQL) {
30
+ // MongoDB profiling
31
+ const profileQuery = JSON.stringify({
32
+ collection: tableName,
33
+ operation: 'aggregate',
34
+ pipeline: [
35
+ { $sample: { size: 1000 } },
36
+ { $project: Object.fromEntries((columns || []).map((c: string) => [c, 1])) },
37
+ ],
38
+ });
39
+ const sampleResult = await provider.query(profileQuery);
40
+ const totalCountResult = await provider.query(JSON.stringify({
41
+ collection: tableName,
42
+ operation: 'countDocuments',
43
+ filter: {},
44
+ }));
45
+
46
+ const totalRows = totalCountResult.rows[0]?.count || sampleResult.rows.length;
47
+ const columnProfiles = (columns || []).map((col: string) => {
48
+ const values = sampleResult.rows.map(r => r[col]).filter(v => v !== undefined);
49
+ const nullCount = sampleResult.rows.length - values.length;
50
+ const distinctValues = new Set(values.map(v => JSON.stringify(v)));
51
+
52
+ return {
53
+ name: col,
54
+ type: typeof values[0] || 'unknown',
55
+ totalRows,
56
+ nullCount,
57
+ nullPercent: sampleResult.rows.length > 0 ? Math.round((nullCount / sampleResult.rows.length) * 100) : 0,
58
+ distinctCount: distinctValues.size,
59
+ sampleValues: values.slice(0, 5).map(v => String(v)),
60
+ };
61
+ });
62
+
63
+ return NextResponse.json({ tableName, totalRows, columns: columnProfiles });
64
+ }
65
+
66
+ // SQL profiling
67
+ const colList = (columns || []) as string[];
68
+ if (colList.length === 0) {
69
+ return NextResponse.json({ error: 'No columns to profile' }, { status: 400 });
70
+ }
71
+
72
+ // Get total row count
73
+ const countResult = await provider.query(`SELECT COUNT(*) as total FROM ${tableName}`);
74
+ const totalRows = Number(countResult.rows[0]?.total || 0);
75
+
76
+ // Build profiling query for each column
77
+ const profileParts = colList.slice(0, 20).map((col) => {
78
+ const safeCol = `"${col}"`;
79
+ return `
80
+ SELECT
81
+ '${col.replace(/'/g, "''")}' as column_name,
82
+ COUNT(*) as total_count,
83
+ COUNT(${safeCol}) as non_null_count,
84
+ COUNT(*) - COUNT(${safeCol}) as null_count,
85
+ COUNT(DISTINCT ${safeCol}) as distinct_count,
86
+ MIN(${safeCol}::text) as min_value,
87
+ MAX(${safeCol}::text) as max_value
88
+ FROM ${tableName}
89
+ `;
90
+ });
91
+
92
+ const columnProfiles: { name: string; totalRows: number; nullCount: number; nullPercent: number; distinctCount: number; minValue?: unknown; maxValue?: unknown; error?: string; sampleValues?: string[] }[] = [];
93
+
94
+ for (const sql of profileParts) {
95
+ try {
96
+ const result = await provider.query(sql);
97
+ const row = result.rows[0];
98
+ if (row) {
99
+ const nullCount = Number(row.null_count || 0);
100
+ const total = Number(row.total_count || 0);
101
+
102
+ columnProfiles.push({
103
+ name: String(row.column_name),
104
+ totalRows: total,
105
+ nullCount,
106
+ nullPercent: total > 0 ? Math.round((nullCount / total) * 100) : 0,
107
+ distinctCount: Number(row.distinct_count || 0),
108
+ minValue: row.min_value,
109
+ maxValue: row.max_value,
110
+ });
111
+ }
112
+ } catch {
113
+ // Skip columns that can't be profiled (e.g., binary)
114
+ columnProfiles.push({
115
+ name: colList[columnProfiles.length],
116
+ totalRows,
117
+ nullCount: 0,
118
+ nullPercent: 0,
119
+ distinctCount: 0,
120
+ error: 'Could not profile this column',
121
+ });
122
+ }
123
+ }
124
+
125
+ // Get sample values for top 5 columns
126
+ const topCols = colList.slice(0, 5);
127
+ const safeCols = topCols.map(c => `"${c}"`).join(', ');
128
+ try {
129
+ const sampleResult = await provider.query(
130
+ `SELECT ${safeCols} FROM ${tableName} LIMIT 5`
131
+ );
132
+ for (const profile of columnProfiles) {
133
+ if (topCols.includes(profile.name)) {
134
+ profile.sampleValues = sampleResult.rows.map(r => String(r[profile.name] ?? 'NULL')).slice(0, 5);
135
+ }
136
+ }
137
+ } catch { /* skip sample values on error */ }
138
+
139
+ return NextResponse.json({ tableName, totalRows, columns: columnProfiles });
140
+ }
141
+ } catch (error) {
142
+ return createErrorResponse(error, { route: 'api/db/profile' });
143
+ }
144
+ }
@@ -0,0 +1,49 @@
1
+ import { NextRequest, NextResponse } from 'next/server';
2
+ import { getOrCreateProvider } from '@/lib/db';
3
+ import { createErrorResponse } from '@/lib/api/errors';
4
+ import { resolveConnection } from '@/lib/seed/resolve-connection';
5
+ import { getSession } from '@/lib/auth';
6
+
7
+ export const dynamic = 'force-dynamic';
8
+
9
+ export async function POST(req: NextRequest) {
10
+ try {
11
+ let body;
12
+ try {
13
+ body = await req.json();
14
+ } catch {
15
+ return NextResponse.json({ error: 'Empty request body' }, { status: 400 });
16
+ }
17
+
18
+ if (!body || (typeof body === 'object' && Object.keys(body).length === 0)) {
19
+ return NextResponse.json({ error: 'Empty request body' }, { status: 400 });
20
+ }
21
+
22
+ const session = await getSession();
23
+ if (!session) {
24
+ return NextResponse.json({ error: 'Authentication required' }, { status: 401 });
25
+ }
26
+
27
+ // Support both formats: { connectionId: "seed:X" }, { connection: {...} }, or bare connection object
28
+ const connection = await resolveConnection(
29
+ body.connectionId ? body : (body.connection ? body : { connection: body }),
30
+ session,
31
+ );
32
+
33
+ if (!connection.type) {
34
+ return NextResponse.json(
35
+ { error: 'Valid connection configuration is required' },
36
+ { status: 400 }
37
+ );
38
+ }
39
+
40
+ const provider = await getOrCreateProvider(connection);
41
+
42
+ return NextResponse.json({
43
+ capabilities: provider.getCapabilities(),
44
+ labels: provider.getLabels(),
45
+ });
46
+ } catch (error) {
47
+ return createErrorResponse(error, { route: 'api/db/provider-meta' });
48
+ }
49
+ }
@@ -0,0 +1,50 @@
1
+ import { NextRequest, NextResponse } from 'next/server';
2
+ import { getOrCreateProvider } from '@/lib/db';
3
+ import { createErrorResponse } from '@/lib/api/errors';
4
+ import { resolveConnection } from '@/lib/seed/resolve-connection';
5
+ import { getSession } from '@/lib/auth';
6
+
7
+ export async function POST(req: NextRequest) {
8
+ try {
9
+ const body = await req.json();
10
+ const { sql, options = {}, queryId } = body;
11
+
12
+ const session = await getSession();
13
+ if (!session) {
14
+ return NextResponse.json({ error: 'Authentication required' }, { status: 401 });
15
+ }
16
+
17
+ const connection = await resolveConnection(body, session);
18
+
19
+ if (!sql) {
20
+ return NextResponse.json(
21
+ { error: 'Connection and query are required' },
22
+ { status: 400 }
23
+ );
24
+ }
25
+
26
+ const provider = await getOrCreateProvider(connection);
27
+ const prepared = provider.prepareQuery(sql, options);
28
+
29
+ // Pass queryId to provider for cancellation tracking
30
+ const supportsCancel = 'cancelQuery' in provider;
31
+ const result = supportsCancel && queryId
32
+ ? await (provider as unknown as { query(sql: string, params?: unknown[], queryId?: string): ReturnType<typeof provider.query> }).query(prepared.query, undefined, queryId)
33
+ : await provider.query(prepared.query);
34
+
35
+ const hasMore = result.rows.length === prepared.limit;
36
+
37
+ return NextResponse.json({
38
+ ...result,
39
+ pagination: {
40
+ limit: prepared.limit,
41
+ offset: prepared.offset,
42
+ hasMore,
43
+ totalReturned: result.rows.length,
44
+ wasLimited: prepared.wasLimited,
45
+ },
46
+ });
47
+ } catch (error) {
48
+ return createErrorResponse(error, { route: 'api/db/query' });
49
+ }
50
+ }
@@ -0,0 +1,47 @@
1
+ import { NextRequest, NextResponse } from 'next/server';
2
+ import { getOrCreateProvider } from '@/lib/db';
3
+ import { createErrorResponse } from '@/lib/api/errors';
4
+ import { resolveConnection } from '@/lib/seed/resolve-connection';
5
+ import { getSession } from '@/lib/auth';
6
+
7
+ export const dynamic = 'force-dynamic';
8
+
9
+ export async function POST(req: NextRequest) {
10
+ try {
11
+ let body;
12
+ try {
13
+ body = await req.json();
14
+ } catch {
15
+ return NextResponse.json({ error: 'Empty request body' }, { status: 400 });
16
+ }
17
+
18
+ if (!body || (typeof body === 'object' && Object.keys(body).length === 0)) {
19
+ return NextResponse.json({ error: 'Empty request body' }, { status: 400 });
20
+ }
21
+
22
+ const session = await getSession();
23
+ if (!session) {
24
+ return NextResponse.json({ error: 'Authentication required' }, { status: 401 });
25
+ }
26
+
27
+ // Support both formats: { connectionId: "seed:X" }, { connection: {...} }, or bare connection object
28
+ const connection = await resolveConnection(
29
+ body.connectionId ? body : (body.connection ? body : { connection: body }),
30
+ session,
31
+ );
32
+
33
+ if (!connection.type) {
34
+ return NextResponse.json(
35
+ { error: 'Valid connection configuration is required' },
36
+ { status: 400 }
37
+ );
38
+ }
39
+
40
+ const provider = await getOrCreateProvider(connection);
41
+ const schema = await provider.getSchema();
42
+
43
+ return NextResponse.json(schema);
44
+ } catch (error) {
45
+ return createErrorResponse(error, { route: 'api/db/schema' });
46
+ }
47
+ }
@@ -0,0 +1,42 @@
1
+ import { NextRequest, NextResponse } from 'next/server';
2
+ import { createDatabaseProvider } from '@/lib/db/factory';
3
+ import { createErrorResponse } from '@/lib/api/errors';
4
+ import { resolveConnection } from '@/lib/seed/resolve-connection';
5
+ import { getSession } from '@/lib/auth';
6
+
7
+ export async function POST(request: NextRequest) {
8
+ let provider = null;
9
+
10
+ try {
11
+ const body = await request.json();
12
+
13
+ const session = await getSession();
14
+ if (!session) {
15
+ return NextResponse.json({ error: 'Authentication required' }, { status: 401 });
16
+ }
17
+
18
+ const connection = await resolveConnection(body, session);
19
+
20
+ provider = await createDatabaseProvider(connection);
21
+ await provider.connect();
22
+
23
+ const schema = await provider.getSchema();
24
+
25
+ await provider.disconnect();
26
+ provider = null;
27
+
28
+ return NextResponse.json({
29
+ schema,
30
+ connectionId: connection.id,
31
+ connectionName: connection.name,
32
+ databaseType: connection.type,
33
+ timestamp: new Date().toISOString(),
34
+ });
35
+ } catch (error) {
36
+ if (provider) {
37
+ try { await provider.disconnect(); } catch { /* ignore */ }
38
+ }
39
+
40
+ return createErrorResponse(error, { route: 'api/db/schema-snapshot' });
41
+ }
42
+ }
@@ -0,0 +1,55 @@
1
+ import { NextRequest, NextResponse } from 'next/server';
2
+ import { createDatabaseProvider } from '@/lib/db/factory';
3
+ import { createErrorResponse } from '@/lib/api/errors';
4
+ import { resolveConnection } from '@/lib/seed/resolve-connection';
5
+ import { getSession } from '@/lib/auth';
6
+
7
+ export async function POST(req: NextRequest) {
8
+ let provider = null;
9
+
10
+ try {
11
+ const body = await req.json();
12
+
13
+ const session = await getSession();
14
+ if (!session) {
15
+ return NextResponse.json({ error: 'Authentication required' }, { status: 401 });
16
+ }
17
+
18
+ // Support both formats: { connectionId: "seed:X" }, { connection: {...} }, or bare connection object
19
+ const connection = await resolveConnection(
20
+ body.connectionId ? body : (body.connection ? body : { connection: body }),
21
+ session,
22
+ );
23
+
24
+ if (!connection.type) {
25
+ return NextResponse.json(
26
+ { success: false, error: 'Connection configuration is required' },
27
+ { status: 400 }
28
+ );
29
+ }
30
+
31
+ provider = await createDatabaseProvider(connection, { queryTimeout: 10000 });
32
+ await provider.connect();
33
+
34
+ // Run a lightweight query to verify the connection actually works
35
+ const startTime = Date.now();
36
+ await provider.getHealth();
37
+ const latency = Date.now() - startTime;
38
+
39
+ await provider.disconnect();
40
+ provider = null;
41
+
42
+ return NextResponse.json({
43
+ success: true,
44
+ message: 'Connection successful',
45
+ latency,
46
+ });
47
+ } catch (error) {
48
+ // Ensure we disconnect on error
49
+ if (provider) {
50
+ try { await provider.disconnect(); } catch { /* ignore */ }
51
+ }
52
+
53
+ return createErrorResponse(error, { route: 'api/db/test-connection' });
54
+ }
55
+ }
@@ -0,0 +1,111 @@
1
+ import { NextRequest, NextResponse } from 'next/server';
2
+ import { getOrCreateProvider } from '@/lib/db';
3
+ import { createErrorResponse } from '@/lib/api/errors';
4
+ import { resolveConnection } from '@/lib/seed/resolve-connection';
5
+ import { getSession } from '@/lib/auth';
6
+
7
+ interface TransactionProvider {
8
+ beginTransaction(): Promise<void>;
9
+ commitTransaction(): Promise<void>;
10
+ rollbackTransaction(): Promise<void>;
11
+ isInTransaction(): boolean;
12
+ queryInTransaction(sql: string, params?: unknown[]): Promise<{ rows: Record<string, unknown>[]; fields: string[]; rowCount: number; executionTime: number }>;
13
+ }
14
+
15
+ function isTransactionProvider(provider: unknown): provider is TransactionProvider {
16
+ return (
17
+ typeof provider === 'object' &&
18
+ provider !== null &&
19
+ 'beginTransaction' in provider &&
20
+ 'commitTransaction' in provider &&
21
+ 'rollbackTransaction' in provider
22
+ );
23
+ }
24
+
25
+ export async function POST(req: NextRequest) {
26
+ try {
27
+ const body = await req.json();
28
+ const { action, sql, options = {} } = body;
29
+
30
+ const session = await getSession();
31
+ if (!session) {
32
+ return NextResponse.json({ error: 'Authentication required' }, { status: 401 });
33
+ }
34
+
35
+ const connection = await resolveConnection(body, session);
36
+
37
+ if (!action) {
38
+ return NextResponse.json(
39
+ { error: 'Connection and action are required' },
40
+ { status: 400 }
41
+ );
42
+ }
43
+
44
+ const provider = await getOrCreateProvider(connection);
45
+
46
+ if (!isTransactionProvider(provider)) {
47
+ return NextResponse.json(
48
+ { error: 'Transaction control is not supported for this database type' },
49
+ { status: 400 }
50
+ );
51
+ }
52
+
53
+ switch (action) {
54
+ case 'begin': {
55
+ await provider.beginTransaction();
56
+ return NextResponse.json({ status: 'active', message: 'Transaction started' });
57
+ }
58
+
59
+ case 'commit': {
60
+ await provider.commitTransaction();
61
+ return NextResponse.json({ status: 'committed', message: 'Transaction committed' });
62
+ }
63
+
64
+ case 'rollback': {
65
+ await provider.rollbackTransaction();
66
+ return NextResponse.json({ status: 'rolled_back', message: 'Transaction rolled back' });
67
+ }
68
+
69
+ case 'query': {
70
+ if (!sql) {
71
+ return NextResponse.json(
72
+ { error: 'SQL query is required for transaction query' },
73
+ { status: 400 }
74
+ );
75
+ }
76
+
77
+ // Apply limit for SELECT queries within transaction
78
+ const prepared = provider.prepareQuery(sql, options);
79
+ const result = await provider.queryInTransaction(prepared.query);
80
+
81
+ const hasMore = result.rows.length === prepared.limit;
82
+
83
+ return NextResponse.json({
84
+ ...result,
85
+ inTransaction: true,
86
+ pagination: {
87
+ limit: prepared.limit,
88
+ offset: prepared.offset,
89
+ hasMore,
90
+ totalReturned: result.rows.length,
91
+ wasLimited: prepared.wasLimited,
92
+ },
93
+ });
94
+ }
95
+
96
+ case 'status': {
97
+ return NextResponse.json({
98
+ inTransaction: provider.isInTransaction(),
99
+ });
100
+ }
101
+
102
+ default:
103
+ return NextResponse.json(
104
+ { error: `Unknown transaction action: ${action}. Valid: begin, commit, rollback, query, status` },
105
+ { status: 400 }
106
+ );
107
+ }
108
+ } catch (error) {
109
+ return createErrorResponse(error, { route: 'api/db/transaction' });
110
+ }
111
+ }
@@ -0,0 +1,67 @@
1
+ /**
2
+ * PUT /api/storage/[collection]
3
+ * Updates a single storage collection for the authenticated user.
4
+ * Only works when server storage is enabled.
5
+ */
6
+
7
+ import { NextRequest, NextResponse } from 'next/server';
8
+ import { getSession } from '@/lib/auth';
9
+ import { getStorageProvider } from '@/lib/storage/factory';
10
+ import { STORAGE_COLLECTIONS, type StorageCollection } from '@/lib/storage/types';
11
+ import { createErrorResponse } from '@/lib/api/errors';
12
+
13
+ export async function PUT(
14
+ request: NextRequest,
15
+ { params }: { params: Promise<{ collection: string }> }
16
+ ) {
17
+ try {
18
+ const provider = await getStorageProvider();
19
+ if (!provider) {
20
+ return NextResponse.json(
21
+ { error: 'Server storage is not enabled' },
22
+ { status: 404 }
23
+ );
24
+ }
25
+
26
+ const session = await getSession();
27
+ if (!session) {
28
+ return NextResponse.json({ error: 'Unauthorized' }, { status: 401 });
29
+ }
30
+
31
+ const { collection } = await params;
32
+
33
+ if (!STORAGE_COLLECTIONS.includes(collection as StorageCollection)) {
34
+ return NextResponse.json(
35
+ { error: `Invalid collection: ${collection}` },
36
+ { status: 400 }
37
+ );
38
+ }
39
+
40
+ let body;
41
+ try {
42
+ body = await request.json();
43
+ } catch {
44
+ return NextResponse.json(
45
+ { error: 'Invalid JSON in request body' },
46
+ { status: 400 }
47
+ );
48
+ }
49
+
50
+ if (body.data === undefined || body.data === null) {
51
+ return NextResponse.json(
52
+ { error: 'Missing required field: data' },
53
+ { status: 400 }
54
+ );
55
+ }
56
+
57
+ await provider.setCollection(
58
+ session.username,
59
+ collection as StorageCollection,
60
+ body.data
61
+ );
62
+
63
+ return NextResponse.json({ ok: true });
64
+ } catch (error) {
65
+ return createErrorResponse(error, { route: 'PUT /api/storage/[collection]' });
66
+ }
67
+ }
@@ -0,0 +1,17 @@
1
+ /**
2
+ * GET /api/storage/config
3
+ * Returns storage configuration (public endpoint, no auth required).
4
+ * Client uses this to discover if server-side storage is enabled at runtime.
5
+ */
6
+
7
+ import { NextResponse } from 'next/server';
8
+ import { getStorageConfig } from '@/lib/storage/factory';
9
+ import { createErrorResponse } from '@/lib/api/errors';
10
+
11
+ export async function GET() {
12
+ try {
13
+ return NextResponse.json(getStorageConfig());
14
+ } catch (error) {
15
+ return createErrorResponse(error, { route: 'GET /api/storage/config' });
16
+ }
17
+ }
@@ -0,0 +1,45 @@
1
+ /**
2
+ * POST /api/storage/migrate
3
+ * Migrates localStorage data to server storage.
4
+ * Client sends all its localStorage collections; server merges them.
5
+ * Only works when server storage is enabled.
6
+ */
7
+
8
+ import { NextRequest, NextResponse } from 'next/server';
9
+ import { getSession } from '@/lib/auth';
10
+ import { getStorageProvider } from '@/lib/storage/factory';
11
+ import type { StorageData } from '@/lib/storage/types';
12
+ import { createErrorResponse } from '@/lib/api/errors';
13
+
14
+ export async function POST(request: NextRequest) {
15
+ try {
16
+ const provider = await getStorageProvider();
17
+ if (!provider) {
18
+ return NextResponse.json(
19
+ { error: 'Server storage is not enabled' },
20
+ { status: 404 }
21
+ );
22
+ }
23
+
24
+ const session = await getSession();
25
+ if (!session) {
26
+ return NextResponse.json({ error: 'Unauthorized' }, { status: 401 });
27
+ }
28
+
29
+ let body: Partial<StorageData>;
30
+ try {
31
+ body = (await request.json()) as Partial<StorageData>;
32
+ } catch {
33
+ return NextResponse.json(
34
+ { error: 'Invalid JSON in request body' },
35
+ { status: 400 }
36
+ );
37
+ }
38
+
39
+ await provider.mergeData(session.username, body);
40
+
41
+ return NextResponse.json({ ok: true, migrated: Object.keys(body) });
42
+ } catch (error) {
43
+ return createErrorResponse(error, { route: 'POST /api/storage/migrate' });
44
+ }
45
+ }
@@ -0,0 +1,32 @@
1
+ /**
2
+ * GET /api/storage
3
+ * Returns all storage data for the authenticated user.
4
+ * Only works when server storage is enabled.
5
+ */
6
+
7
+ import { NextResponse } from 'next/server';
8
+ import { getSession } from '@/lib/auth';
9
+ import { getStorageProvider } from '@/lib/storage/factory';
10
+ import { createErrorResponse } from '@/lib/api/errors';
11
+
12
+ export async function GET() {
13
+ try {
14
+ const provider = await getStorageProvider();
15
+ if (!provider) {
16
+ return NextResponse.json(
17
+ { error: 'Server storage is not enabled' },
18
+ { status: 404 }
19
+ );
20
+ }
21
+
22
+ const session = await getSession();
23
+ if (!session) {
24
+ return NextResponse.json({ error: 'Unauthorized' }, { status: 401 });
25
+ }
26
+
27
+ const data = await provider.getAllData(session.username);
28
+ return NextResponse.json(data);
29
+ } catch (error) {
30
+ return createErrorResponse(error, { route: 'GET /api/storage' });
31
+ }
32
+ }