@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,385 @@
1
+ /**
2
+ * Data Masking Utility
3
+ * Auto-detects sensitive columns by name patterns and masks their values.
4
+ * Supports configurable patterns, RBAC, per-cell reveal, and persistence.
5
+ */
6
+
7
+ // ─── Types ───────────────────────────────────────────────────────────────────
8
+
9
+ export type MaskType = 'email' | 'phone' | 'card' | 'ssn' | 'full' | 'partial' | 'ip' | 'date' | 'financial' | 'custom';
10
+
11
+ export interface MaskingPattern {
12
+ id: string;
13
+ name: string;
14
+ columnPatterns: string[]; // regex strings (user-editable)
15
+ maskType: MaskType;
16
+ enabled: boolean;
17
+ isBuiltin: boolean; // builtin patterns cannot be deleted, only disabled
18
+ customMask?: string; // used when maskType === 'custom'
19
+ }
20
+
21
+ export interface MaskingConfig {
22
+ enabled: boolean; // global masking on/off
23
+ patterns: MaskingPattern[];
24
+ roleSettings: {
25
+ admin: { canToggle: boolean; canReveal: boolean };
26
+ user: { canToggle: boolean; canReveal: boolean };
27
+ };
28
+ }
29
+
30
+ /** Legacy interface kept for backward compat (DataProfiler uses it) */
31
+ export interface MaskingRule {
32
+ pattern: RegExp;
33
+ label: string;
34
+ mask: (value: string) => string;
35
+ }
36
+
37
+ // ─── Built-in Masking Rules (legacy, used by detectSensitiveColumns) ─────────
38
+
39
+ const MASKING_RULES: MaskingRule[] = [
40
+ {
41
+ pattern: /^(email|e_mail|user_email|customer_email|contact_email)$/i,
42
+ label: 'Email',
43
+ mask: (v) => {
44
+ const parts = v.split('@');
45
+ if (parts.length !== 2) return '***@***.***';
46
+ const name = parts[0];
47
+ const domain = parts[1];
48
+ return `${name[0]}${'*'.repeat(Math.max(name.length - 1, 2))}@${domain[0]}${'*'.repeat(Math.max(domain.length - 1, 2))}`;
49
+ },
50
+ },
51
+ {
52
+ pattern: /^(password|passwd|pass|pwd|secret|user_password|hashed_password|password_hash|hash)$/i,
53
+ label: 'Password',
54
+ mask: () => '********',
55
+ },
56
+ {
57
+ pattern: /^(ssn|social_security|social_security_number|national_id|national_number)$/i,
58
+ label: 'SSN',
59
+ mask: (v) => `***-**-${v.slice(-4).padStart(4, '*')}`,
60
+ },
61
+ {
62
+ pattern: /^(credit_card|card_number|cc_number|card_num|pan|credit_card_number)$/i,
63
+ label: 'Credit Card',
64
+ mask: (v) => `****-****-****-${v.replace(/\D/g, '').slice(-4).padStart(4, '*')}`,
65
+ },
66
+ {
67
+ pattern: /^(phone|phone_number|mobile|cell|telephone|tel|contact_phone)$/i,
68
+ label: 'Phone',
69
+ mask: (v) => {
70
+ const digits = v.replace(/\D/g, '');
71
+ if (digits.length < 4) return '***';
72
+ return `${'*'.repeat(digits.length - 4)}${digits.slice(-4)}`;
73
+ },
74
+ },
75
+ {
76
+ pattern: /^(token|access_token|refresh_token|api_key|apikey|api_secret|secret_key|auth_token|bearer_token|session_token)$/i,
77
+ label: 'Token/Key',
78
+ mask: (v) => `${v.slice(0, 4)}${'*'.repeat(Math.max(v.length - 8, 4))}${v.slice(-4)}`,
79
+ },
80
+ {
81
+ pattern: /^(address|street|street_address|home_address|billing_address|shipping_address)$/i,
82
+ label: 'Address',
83
+ mask: () => '*** **** ***',
84
+ },
85
+ {
86
+ pattern: /^(ip|ip_address|client_ip|remote_ip|source_ip)$/i,
87
+ label: 'IP Address',
88
+ mask: (v) => {
89
+ const parts = v.split('.');
90
+ if (parts.length === 4) return `${parts[0]}.***.***.${parts[3]}`;
91
+ return '***';
92
+ },
93
+ },
94
+ {
95
+ pattern: /^(salary|income|balance|amount|wage|compensation|net_pay|gross_pay|revenue)$/i,
96
+ label: 'Financial',
97
+ mask: () => '***,***.**',
98
+ },
99
+ {
100
+ pattern: /^(birth|dob|date_of_birth|birthdate|birth_date|birthday)$/i,
101
+ label: 'Birthdate',
102
+ mask: (v) => {
103
+ if (v.length >= 4) return `****-**-${v.slice(-2)}`;
104
+ return '****-**-**';
105
+ },
106
+ },
107
+ ];
108
+
109
+ // ─── Default Masking Config ──────────────────────────────────────────────────
110
+
111
+ export const DEFAULT_MASKING_CONFIG: MaskingConfig = {
112
+ enabled: true,
113
+ patterns: [
114
+ {
115
+ id: 'builtin-email',
116
+ name: 'Email',
117
+ columnPatterns: ['email', 'e_mail', 'user_email', 'customer_email', 'contact_email'],
118
+ maskType: 'email',
119
+ enabled: true,
120
+ isBuiltin: true,
121
+ },
122
+ {
123
+ id: 'builtin-password',
124
+ name: 'Password',
125
+ columnPatterns: ['password', 'passwd', 'pass', 'pwd', 'secret', 'user_password', 'hashed_password', 'password_hash', 'hash'],
126
+ maskType: 'full',
127
+ enabled: true,
128
+ isBuiltin: true,
129
+ },
130
+ {
131
+ id: 'builtin-ssn',
132
+ name: 'SSN',
133
+ columnPatterns: ['ssn', 'social_security', 'social_security_number', 'national_id', 'national_number'],
134
+ maskType: 'ssn',
135
+ enabled: true,
136
+ isBuiltin: true,
137
+ },
138
+ {
139
+ id: 'builtin-card',
140
+ name: 'Credit Card',
141
+ columnPatterns: ['credit_card', 'card_number', 'cc_number', 'card_num', 'pan', 'credit_card_number'],
142
+ maskType: 'card',
143
+ enabled: true,
144
+ isBuiltin: true,
145
+ },
146
+ {
147
+ id: 'builtin-phone',
148
+ name: 'Phone',
149
+ columnPatterns: ['phone', 'phone_number', 'mobile', 'cell', 'telephone', 'tel', 'contact_phone'],
150
+ maskType: 'phone',
151
+ enabled: true,
152
+ isBuiltin: true,
153
+ },
154
+ {
155
+ id: 'builtin-token',
156
+ name: 'Token/Key',
157
+ columnPatterns: ['token', 'access_token', 'refresh_token', 'api_key', 'apikey', 'api_secret', 'secret_key', 'auth_token', 'bearer_token', 'session_token'],
158
+ maskType: 'partial',
159
+ enabled: true,
160
+ isBuiltin: true,
161
+ },
162
+ {
163
+ id: 'builtin-address',
164
+ name: 'Address',
165
+ columnPatterns: ['address', 'street', 'street_address', 'home_address', 'billing_address', 'shipping_address'],
166
+ maskType: 'full',
167
+ enabled: true,
168
+ isBuiltin: true,
169
+ },
170
+ {
171
+ id: 'builtin-ip',
172
+ name: 'IP Address',
173
+ columnPatterns: ['ip', 'ip_address', 'client_ip', 'remote_ip', 'source_ip'],
174
+ maskType: 'ip',
175
+ enabled: true,
176
+ isBuiltin: true,
177
+ },
178
+ {
179
+ id: 'builtin-financial',
180
+ name: 'Financial',
181
+ columnPatterns: ['salary', 'income', 'balance', 'amount', 'wage', 'compensation', 'net_pay', 'gross_pay', 'revenue'],
182
+ maskType: 'financial',
183
+ enabled: true,
184
+ isBuiltin: true,
185
+ },
186
+ {
187
+ id: 'builtin-birthdate',
188
+ name: 'Birthdate',
189
+ columnPatterns: ['birth', 'dob', 'date_of_birth', 'birthdate', 'birth_date', 'birthday'],
190
+ maskType: 'date',
191
+ enabled: true,
192
+ isBuiltin: true,
193
+ },
194
+ ],
195
+ roleSettings: {
196
+ admin: { canToggle: true, canReveal: true },
197
+ user: { canToggle: false, canReveal: false },
198
+ },
199
+ };
200
+
201
+ // ─── MaskType-based Masking Functions ────────────────────────────────────────
202
+
203
+ const MASK_FUNCTIONS: Record<MaskType, (value: string, customMask?: string) => string> = {
204
+ email: (v) => {
205
+ const parts = v.split('@');
206
+ if (parts.length !== 2) return '***@***.***';
207
+ const name = parts[0];
208
+ const domain = parts[1];
209
+ return `${name[0]}${'*'.repeat(Math.max(name.length - 1, 2))}@${domain[0]}${'*'.repeat(Math.max(domain.length - 1, 2))}`;
210
+ },
211
+ phone: (v) => {
212
+ const digits = v.replace(/\D/g, '');
213
+ if (digits.length < 4) return '***';
214
+ return `${'*'.repeat(digits.length - 4)}${digits.slice(-4)}`;
215
+ },
216
+ card: (v) => `****-****-****-${v.replace(/\D/g, '').slice(-4).padStart(4, '*')}`,
217
+ ssn: (v) => `***-**-${v.slice(-4).padStart(4, '*')}`,
218
+ full: () => '********',
219
+ partial: (v) => {
220
+ if (v.length <= 8) return '*'.repeat(v.length);
221
+ return `${v.slice(0, 4)}${'*'.repeat(Math.max(v.length - 8, 4))}${v.slice(-4)}`;
222
+ },
223
+ ip: (v) => {
224
+ const parts = v.split('.');
225
+ if (parts.length === 4) return `${parts[0]}.***.***.${parts[3]}`;
226
+ return '***';
227
+ },
228
+ date: (v) => {
229
+ if (v.length >= 4) return `****-**-${v.slice(-2)}`;
230
+ return '****-**-**';
231
+ },
232
+ financial: () => '***,***.**',
233
+ custom: (_v, customMask) => customMask || '***',
234
+ };
235
+
236
+ // ─── Mask by MaskingPattern ──────────────────────────────────────────────────
237
+
238
+ export function maskByType(value: string, pattern: MaskingPattern): string {
239
+ const fn = MASK_FUNCTIONS[pattern.maskType];
240
+ return fn(value, pattern.customMask);
241
+ }
242
+
243
+ // ─── Config-based Detection ──────────────────────────────────────────────────
244
+
245
+ export function detectSensitiveColumnsFromConfig(
246
+ fields: string[],
247
+ config: MaskingConfig
248
+ ): Map<string, MaskingPattern> {
249
+ const sensitiveMap = new Map<string, MaskingPattern>();
250
+
251
+ const enabledPatterns = config.patterns.filter(p => p.enabled);
252
+
253
+ for (const field of fields) {
254
+ for (const pattern of enabledPatterns) {
255
+ const matched = pattern.columnPatterns.some(cp => {
256
+ try {
257
+ return new RegExp(`^${cp}$`, 'i').test(field);
258
+ } catch {
259
+ return cp.toLowerCase() === field.toLowerCase();
260
+ }
261
+ });
262
+ if (matched) {
263
+ sensitiveMap.set(field, pattern);
264
+ break;
265
+ }
266
+ }
267
+ }
268
+
269
+ return sensitiveMap;
270
+ }
271
+
272
+ // ─── Legacy Detection (backward compat — DataProfiler) ──────────────────────
273
+
274
+ export function detectSensitiveColumns(fields: string[]): Map<string, MaskingRule> {
275
+ const sensitiveMap = new Map<string, MaskingRule>();
276
+
277
+ for (const field of fields) {
278
+ for (const rule of MASKING_RULES) {
279
+ if (rule.pattern.test(field)) {
280
+ sensitiveMap.set(field, rule);
281
+ break;
282
+ }
283
+ }
284
+ }
285
+
286
+ return sensitiveMap;
287
+ }
288
+
289
+ // ─── Mask Value Helpers ──────────────────────────────────────────────────────
290
+
291
+ export function maskValue(value: unknown, rule: MaskingRule): string {
292
+ if (value === null || value === undefined) return 'NULL';
293
+ return rule.mask(String(value));
294
+ }
295
+
296
+ export function maskValueByPattern(value: unknown, pattern: MaskingPattern): string {
297
+ if (value === null || value === undefined) return 'NULL';
298
+ return maskByType(String(value), pattern);
299
+ }
300
+
301
+ // ─── Has Sensitive Columns ───────────────────────────────────────────────────
302
+
303
+ export function hasSensitiveColumns(fields: string[]): boolean {
304
+ return fields.some(field =>
305
+ MASKING_RULES.some(rule => rule.pattern.test(field))
306
+ );
307
+ }
308
+
309
+ // ─── Bulk Masking Utility ────────────────────────────────────────────────────
310
+
311
+ export function applyMaskingToRows(
312
+ rows: Record<string, unknown>[],
313
+ fields: string[],
314
+ sensitiveColumns: Map<string, MaskingPattern>
315
+ ): Record<string, unknown>[] {
316
+ if (sensitiveColumns.size === 0) return rows;
317
+
318
+ return rows.map(row => {
319
+ const maskedRow: Record<string, unknown> = { ...row };
320
+ for (const field of fields) {
321
+ const pattern = sensitiveColumns.get(field);
322
+ if (pattern && maskedRow[field] !== null && maskedRow[field] !== undefined) {
323
+ maskedRow[field] = maskByType(String(maskedRow[field]), pattern);
324
+ }
325
+ }
326
+ return maskedRow;
327
+ });
328
+ }
329
+
330
+ // ─── RBAC Helpers ────────────────────────────────────────────────────────────
331
+
332
+ export function shouldMask(role: string | undefined, config: MaskingConfig): boolean {
333
+ if (!role || role === 'user') {
334
+ // User role: if canToggle is false, masking is always enforced
335
+ if (!config.roleSettings.user.canToggle) return true;
336
+ return config.enabled;
337
+ }
338
+ // Admin: respects config.enabled
339
+ return config.enabled;
340
+ }
341
+
342
+ export function canToggleMasking(role: string | undefined, config: MaskingConfig): boolean {
343
+ if (!role || role === 'user') return config.roleSettings.user.canToggle;
344
+ return config.roleSettings.admin.canToggle;
345
+ }
346
+
347
+ export function canReveal(role: string | undefined, config: MaskingConfig): boolean {
348
+ if (!role || role === 'user') return config.roleSettings.user.canReveal;
349
+ return config.roleSettings.admin.canReveal;
350
+ }
351
+
352
+ // ─── Config Persistence ──────────────────────────────────────────────────────
353
+
354
+ import { storage } from '@/lib/storage';
355
+
356
+ export const MASKING_CONFIG_KEY = 'libredb_masking_config';
357
+
358
+ export function loadMaskingConfig(): MaskingConfig {
359
+ return storage.getMaskingConfig();
360
+ }
361
+
362
+ export function saveMaskingConfig(config: MaskingConfig): void {
363
+ storage.saveMaskingConfig(config);
364
+ }
365
+
366
+ // ─── Preview Samples ─────────────────────────────────────────────────────────
367
+
368
+ export const MASK_TYPE_PREVIEWS: Record<MaskType, { sample: string; label: string }> = {
369
+ email: { sample: 'john.doe@example.com', label: 'Email' },
370
+ phone: { sample: '+1-555-123-4545', label: 'Phone' },
371
+ card: { sample: '4111111111111234', label: 'Credit Card' },
372
+ ssn: { sample: '123-45-6789', label: 'SSN' },
373
+ full: { sample: 'secret_value', label: 'Full Mask' },
374
+ partial: { sample: 'sk-proj-abc123xyz789', label: 'Partial' },
375
+ ip: { sample: '192.168.1.100', label: 'IP Address' },
376
+ date: { sample: '1990-05-15', label: 'Date' },
377
+ financial: { sample: '85000.00', label: 'Financial' },
378
+ custom: { sample: 'any_value', label: 'Custom' },
379
+ };
380
+
381
+ export function getPreviewMasked(maskType: MaskType, customMask?: string): string {
382
+ const preview = MASK_TYPE_PREVIEWS[maskType];
383
+ const fn = MASK_FUNCTIONS[maskType];
384
+ return fn(preview.sample, customMask);
385
+ }
@@ -0,0 +1,325 @@
1
+ /**
2
+ * Base Database Provider
3
+ * Abstract class implementing common provider functionality
4
+ */
5
+
6
+ import {
7
+ type DatabaseProvider,
8
+ type DatabaseType,
9
+ type DatabaseConnection,
10
+ type TableSchema,
11
+ type QueryResult,
12
+ type HealthInfo,
13
+ type MaintenanceType,
14
+ type MaintenanceResult,
15
+ type ProviderOptions,
16
+ type PoolConfig,
17
+ type ConnectionState,
18
+ type MonitoringData,
19
+ type MonitoringOptions,
20
+ type DatabaseOverview,
21
+ type PerformanceMetrics,
22
+ type SlowQueryStats,
23
+ type ActiveSessionDetails,
24
+ type TableStats,
25
+ type IndexStats,
26
+ type StorageStats,
27
+ type ProviderCapabilities,
28
+ type ProviderLabels,
29
+ type PreparedQuery,
30
+ type QueryPrepareOptions,
31
+ DEFAULT_QUERY_TIMEOUT,
32
+ } from './types';
33
+ import { DatabaseConfigError, mapDatabaseError } from './errors';
34
+ import { mergePoolConfig, formatDuration } from './utils/pool-manager';
35
+
36
+ // ============================================================================
37
+ // Base Provider Class
38
+ // ============================================================================
39
+
40
+ export abstract class BaseDatabaseProvider implements DatabaseProvider {
41
+ public readonly type: DatabaseType;
42
+ public readonly config: DatabaseConnection;
43
+
44
+ protected readonly poolConfig: PoolConfig;
45
+ protected readonly queryTimeout: number;
46
+ protected readonly options: ProviderOptions;
47
+ protected state: ConnectionState;
48
+
49
+ protected constructor(
50
+ config: DatabaseConnection,
51
+ options: ProviderOptions = {}
52
+ ) {
53
+ this.type = config.type;
54
+ this.config = config;
55
+ this.options = options;
56
+ this.poolConfig = mergePoolConfig(options.pool);
57
+ this.queryTimeout = options.queryTimeout ?? DEFAULT_QUERY_TIMEOUT;
58
+ this.state = {
59
+ connected: false,
60
+ activeQueries: 0,
61
+ };
62
+ }
63
+
64
+ // ============================================================================
65
+ // Abstract Methods (must be implemented by subclasses)
66
+ // ============================================================================
67
+
68
+ public abstract connect(): Promise<void>;
69
+ public abstract disconnect(): Promise<void>;
70
+ public abstract query(sql: string, params?: unknown[]): Promise<QueryResult>;
71
+ public abstract getSchema(): Promise<TableSchema[]>;
72
+ public abstract getHealth(): Promise<HealthInfo>;
73
+ public abstract runMaintenance(type: MaintenanceType, target?: string): Promise<MaintenanceResult>;
74
+
75
+ // Monitoring methods (must be implemented by subclasses)
76
+ public abstract getOverview(): Promise<DatabaseOverview>;
77
+ public abstract getPerformanceMetrics(): Promise<PerformanceMetrics>;
78
+ public abstract getSlowQueries(options?: { limit?: number }): Promise<SlowQueryStats[]>;
79
+ public abstract getActiveSessions(options?: { limit?: number }): Promise<ActiveSessionDetails[]>;
80
+ public abstract getTableStats(options?: { schema?: string }): Promise<TableStats[]>;
81
+ public abstract getIndexStats(options?: { schema?: string }): Promise<IndexStats[]>;
82
+ public abstract getStorageStats(): Promise<StorageStats[]>;
83
+
84
+ // ============================================================================
85
+ // Common Implementations
86
+ // ============================================================================
87
+
88
+ public isConnected(): boolean {
89
+ return this.state.connected;
90
+ }
91
+
92
+ public async getTables(): Promise<string[]> {
93
+ const schema = await this.getSchema();
94
+ return schema.map((table) => table.name);
95
+ }
96
+
97
+ /**
98
+ * Get comprehensive monitoring data
99
+ * This default implementation calls all monitoring methods in parallel
100
+ * Subclasses can override for optimized implementations
101
+ */
102
+ public async getMonitoringData(options: MonitoringOptions = {}): Promise<MonitoringData> {
103
+ const {
104
+ includeTables = true,
105
+ includeIndexes = true,
106
+ includeStorage = true,
107
+ slowQueryLimit = 10,
108
+ sessionLimit = 50,
109
+ schemaFilter, // undefined = all user schemas
110
+ } = options;
111
+
112
+ // Fetch core data in parallel
113
+ const [overview, performance, slowQueries, activeSessions] = await Promise.all([
114
+ this.getOverview(),
115
+ this.getPerformanceMetrics(),
116
+ this.getSlowQueries({ limit: slowQueryLimit }),
117
+ this.getActiveSessions({ limit: sessionLimit }),
118
+ ]);
119
+
120
+ const result: MonitoringData = {
121
+ timestamp: new Date(),
122
+ overview,
123
+ performance,
124
+ slowQueries,
125
+ activeSessions,
126
+ };
127
+
128
+ // Fetch optional data in parallel
129
+ const optionalPromises: Promise<void>[] = [];
130
+
131
+ if (includeTables) {
132
+ optionalPromises.push(
133
+ this.getTableStats({ schema: schemaFilter }).then((tables) => {
134
+ result.tables = tables;
135
+ })
136
+ );
137
+ }
138
+
139
+ if (includeIndexes) {
140
+ optionalPromises.push(
141
+ this.getIndexStats({ schema: schemaFilter }).then((indexes) => {
142
+ result.indexes = indexes;
143
+ })
144
+ );
145
+ }
146
+
147
+ if (includeStorage) {
148
+ optionalPromises.push(
149
+ this.getStorageStats().then((storage) => {
150
+ result.storage = storage;
151
+ })
152
+ );
153
+ }
154
+
155
+ await Promise.all(optionalPromises);
156
+
157
+ return result;
158
+ }
159
+
160
+ public validate(): void {
161
+ if (!this.config.id) {
162
+ throw new DatabaseConfigError('Connection ID is required', this.type);
163
+ }
164
+
165
+ if (!this.config.type) {
166
+ throw new DatabaseConfigError('Database type is required', this.type);
167
+ }
168
+
169
+ // Subclasses should override for provider-specific validation
170
+ }
171
+
172
+ // ============================================================================
173
+ // Provider Metadata (defaults — subclasses override)
174
+ // ============================================================================
175
+
176
+ public getCapabilities(): ProviderCapabilities {
177
+ return {
178
+ queryLanguage: 'sql',
179
+ supportsExplain: true,
180
+ supportsExternalQueryLimiting: true,
181
+ supportsCreateTable: true,
182
+ supportsMaintenance: true,
183
+ maintenanceOperations: ['vacuum', 'analyze', 'reindex', 'kill', 'optimize', 'check'],
184
+ supportsConnectionString: false,
185
+ defaultPort: null,
186
+ schemaRefreshPattern: '(CREATE|DROP|ALTER|TRUNCATE)\\b',
187
+ };
188
+ }
189
+
190
+ public getLabels(): ProviderLabels {
191
+ return {
192
+ entityName: 'Table',
193
+ entityNamePlural: 'Tables',
194
+ rowName: 'row',
195
+ rowNamePlural: 'rows',
196
+ selectAction: 'Select Top 100',
197
+ generateAction: 'Generate Query',
198
+ analyzeAction: 'Analyze Table',
199
+ vacuumAction: 'Vacuum Table',
200
+ searchPlaceholder: 'Search tables or columns...',
201
+ analyzeGlobalLabel: 'Run Analyze',
202
+ analyzeGlobalTitle: 'Update Statistics',
203
+ analyzeGlobalDesc: "Updates the planner's statistics for all tables in the database to improve query optimization.",
204
+ vacuumGlobalLabel: 'Run Vacuum',
205
+ vacuumGlobalTitle: 'Reclaim Space',
206
+ vacuumGlobalDesc: 'Removes dead rows from tables and returns space to the operating system.',
207
+ };
208
+ }
209
+
210
+ public prepareQuery(query: string, options: QueryPrepareOptions = {}): PreparedQuery {
211
+ return { query, wasLimited: false, limit: options.limit || 500, offset: options.offset || 0 };
212
+ }
213
+
214
+ // ============================================================================
215
+ // Protected Helpers
216
+ // ============================================================================
217
+
218
+ /**
219
+ * Ensure provider is connected before operation
220
+ */
221
+ protected ensureConnected(): void {
222
+ if (!this.state.connected) {
223
+ throw new DatabaseConfigError(
224
+ 'Provider is not connected. Call connect() first.',
225
+ this.type
226
+ );
227
+ }
228
+ }
229
+
230
+ /**
231
+ * Track active query count
232
+ */
233
+ protected async trackQuery<T>(fn: () => Promise<T>): Promise<T> {
234
+ this.state.activeQueries++;
235
+ try {
236
+ return await fn();
237
+ } finally {
238
+ this.state.activeQueries--;
239
+ }
240
+ }
241
+
242
+ /**
243
+ * Measure query execution time
244
+ */
245
+ protected async measureExecution<T>(
246
+ fn: () => Promise<T>
247
+ ): Promise<{ result: T; executionTime: number }> {
248
+ const startTime = performance.now();
249
+ const result = await fn();
250
+ const executionTime = Math.round(performance.now() - startTime);
251
+ return { result, executionTime };
252
+ }
253
+
254
+ /**
255
+ * Map native errors to DatabaseError
256
+ */
257
+ protected mapError(error: unknown, query?: string): Error {
258
+ return mapDatabaseError(error, this.type, query);
259
+ }
260
+
261
+ /**
262
+ * Log error with safe config
263
+ */
264
+ protected logError(operation: string, error: unknown): void {
265
+ const errorMessage = error instanceof Error ? error.message : String(error);
266
+ // Sanitize to prevent log injection via newlines/control chars
267
+ const sanitize = (v: string) => v.replace(/[\r\n]/g, ' ').replace(/[\x00-\x08\x0b\x0c\x0e-\x1f]/g, '');
268
+ console.error(
269
+ `[DB:${sanitize(this.type)}] ${sanitize(operation)} failed: ${sanitize(errorMessage)}`
270
+ );
271
+ }
272
+
273
+ /**
274
+ * Get config without sensitive data for logging
275
+ */
276
+ protected getSafeConfig(): Record<string, unknown> {
277
+ return {
278
+ id: this.config.id,
279
+ name: this.config.name,
280
+ type: this.config.type,
281
+ host: this.config.host,
282
+ port: this.config.port,
283
+ database: this.config.database,
284
+ user: this.config.user,
285
+ // Never log password or connection string
286
+ };
287
+ }
288
+
289
+ /**
290
+ * Build connection info for health check
291
+ */
292
+ protected getConnectionInfo(): string {
293
+ if (this.config.connectionString) {
294
+ // Mask password in connection string
295
+ return this.config.connectionString.replace(/:([^:@]+)@/, ':***@');
296
+ }
297
+ return `${this.config.host}:${this.config.port}/${this.config.database}`;
298
+ }
299
+
300
+ /**
301
+ * Format duration for display
302
+ */
303
+ protected formatDuration(ms: number): string {
304
+ return formatDuration(ms);
305
+ }
306
+
307
+ /**
308
+ * Update connection state
309
+ */
310
+ protected setConnected(connected: boolean): void {
311
+ this.state.connected = connected;
312
+ if (connected) {
313
+ this.state.lastConnected = new Date();
314
+ this.state.lastError = undefined;
315
+ }
316
+ }
317
+
318
+ /**
319
+ * Record connection error
320
+ */
321
+ protected setError(error: Error): void {
322
+ this.state.lastError = error;
323
+ this.state.connected = false;
324
+ }
325
+ }