@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.
- package/.claude/settings.local.json +127 -0
- package/.cursorrules +426 -0
- package/.devin/wiki.json +143 -0
- package/.dockerignore +80 -0
- package/.env.example +159 -0
- package/.github/ISSUE_TEMPLATE/bug_report.md +49 -0
- package/.github/ISSUE_TEMPLATE/feature_request.md +29 -0
- package/.github/PULL_REQUEST_TEMPLATE.md +57 -0
- package/.github/workflows/ci.yml +185 -0
- package/.github/workflows/codeql.yml +57 -0
- package/.github/workflows/docker-build-push.yml +118 -0
- package/.github/workflows/helm-release.yml +113 -0
- package/CLAUDE.md +265 -0
- package/CODE_OF_CONDUCT.md +124 -0
- package/CONTRIBUTING.md +154 -0
- package/Dockerfile +73 -0
- package/LICENSE +21 -0
- package/README.md +614 -0
- package/SECURITY.md +107 -0
- package/artifacthub-repo.yml +4 -0
- package/bun.lock +1714 -0
- package/bunfig.toml +3 -0
- package/charts/libredb-studio/.helmignore +11 -0
- package/charts/libredb-studio/Chart.lock +6 -0
- package/charts/libredb-studio/Chart.yaml +50 -0
- package/charts/libredb-studio/README.md +206 -0
- package/charts/libredb-studio/templates/NOTES.txt +59 -0
- package/charts/libredb-studio/templates/_helpers.tpl +135 -0
- package/charts/libredb-studio/templates/configmap.yaml +37 -0
- package/charts/libredb-studio/templates/deployment.yaml +184 -0
- package/charts/libredb-studio/templates/hpa.yaml +32 -0
- package/charts/libredb-studio/templates/ingress.yaml +41 -0
- package/charts/libredb-studio/templates/networkpolicy.yaml +50 -0
- package/charts/libredb-studio/templates/pdb.yaml +18 -0
- package/charts/libredb-studio/templates/pvc.yaml +23 -0
- package/charts/libredb-studio/templates/secret.yaml +30 -0
- package/charts/libredb-studio/templates/seed-configmap.yaml +11 -0
- package/charts/libredb-studio/templates/service.yaml +22 -0
- package/charts/libredb-studio/templates/serviceaccount.yaml +13 -0
- package/charts/libredb-studio/values.schema.json +246 -0
- package/charts/libredb-studio/values.yaml +286 -0
- package/components.json +22 -0
- package/conductor/code_styleguides/typescript.md +43 -0
- package/conductor/product-guidelines.md +43 -0
- package/conductor/product.md +3 -0
- package/conductor/setup_state.json +1 -0
- package/conductor/tech-stack.md +39 -0
- package/conductor/tracks/enhance_postgres_monitoring_20251227/metadata.json +8 -0
- package/conductor/tracks/enhance_postgres_monitoring_20251227/plan.md +44 -0
- package/conductor/tracks/enhance_postgres_monitoring_20251227/spec.md +31 -0
- package/conductor/tracks.md +8 -0
- package/conductor/workflow.md +333 -0
- package/database-compose.yml +55 -0
- package/docker/postgres-init/01-extensions.sql +10 -0
- package/docker/postgres-init/02-sample-data.sql +585 -0
- package/docker/postgres.yml +68 -0
- package/docker-compose.yml +38 -0
- package/docs/AI_PLAN.md +74 -0
- package/docs/API_DOCS.md +875 -0
- package/docs/ARCHITECTURE.md +218 -0
- package/docs/DATABASE_PROVIDERS.md +358 -0
- package/docs/FEATURES.md +116 -0
- package/docs/HELM_CHART.md +252 -0
- package/docs/LOGIN_PAGE.md +178 -0
- package/docs/MONACO_EDITOR_PERFORMANCE.md +315 -0
- package/docs/OIDC_ARCH.md +681 -0
- package/docs/OIDC_SETUP.md +322 -0
- package/docs/POSTGRES_METRICS.md +516 -0
- package/docs/QUERY_OPTIMIZATION.md +370 -0
- package/docs/SEED_CONNECTIONS.md +468 -0
- package/docs/SQL_ALIAS_COMPLETION.md +190 -0
- package/docs/STORAGE_ARCHITECTURE.md +565 -0
- package/docs/STORAGE_QUICK_SETUP.md +419 -0
- package/docs/TECHNICAL_PLAN.md +36 -0
- package/docs/THEMING.md +345 -0
- package/docs/adding-a-new-database-provider.md +642 -0
- package/docs/backlogs/000-PLATFORM_DATA_SYNC_DATABASE.md +360 -0
- package/docs/backlogs/001-INLINE_DATA_EDITING.md +118 -0
- package/docs/backlogs/002-DATA_IMPORT.md +215 -0
- package/docs/backlogs/003-QUERY_TIME_MACHINE.md +183 -0
- package/docs/backlogs/004-AI_DATA_STORYTELLER.md +292 -0
- package/docs/backlogs/005-QUERY_PLAYGROUND.md +352 -0
- package/docs/backlogs/006-DATA_MASKING.md +418 -0
- package/docs/enterprise-features.md +718 -0
- package/docs/kubernetes-helm-chart-artifacthub-plan.md +803 -0
- package/docs/medium-koyeb-article-en.md +215 -0
- package/docs/plans/test-plans.md +445 -0
- package/docs/releases/RELEASE.V0.3.0.md +22 -0
- package/docs/releases/RELEASE.V0.4.0.md +154 -0
- package/docs/releases/RELEASE.V0.5.0.md +252 -0
- package/docs/releases/RELEASE_v0.5.6.md +145 -0
- package/docs/releases/RELEASE_v0.6.1.md +303 -0
- package/docs/releases/RELEASE_v0.6.7.md +292 -0
- package/docs/releases/RELEASE_v0.7.0.md +332 -0
- package/docs/releases/RELEASE_v0.8.0.md +521 -0
- package/docs/sampledb/titanic.sql +1379 -0
- package/docs/superpowers/plans/2026-03-25-seed-connections.md +1362 -0
- package/docs/superpowers/specs/2026-03-25-seed-connections-design.md +590 -0
- package/e2e/admin-dashboard.spec.ts +64 -0
- package/e2e/connection-management.spec.ts +58 -0
- package/e2e/export.spec.ts +34 -0
- package/e2e/login.spec.ts +85 -0
- package/e2e/query-execution.spec.ts +35 -0
- package/e2e/tab-management.spec.ts +64 -0
- package/eslint.config.mjs +28 -0
- package/fly.toml +43 -0
- package/next.config.ts +32 -0
- package/package.json +130 -0
- package/playwright.config.ts +34 -0
- package/postcss.config.mjs +7 -0
- package/public/favicon-32x32.png +0 -0
- package/public/favicon.ico +0 -0
- package/public/file.svg +1 -0
- package/public/globe.svg +1 -0
- package/public/logo.svg +32 -0
- package/public/next.svg +1 -0
- package/public/screenshots/code-generator.png +0 -0
- package/public/screenshots/connection-modal.png +0 -0
- package/public/screenshots/data-profiler.png +0 -0
- package/public/screenshots/erd-diagram.png +0 -0
- package/public/screenshots/hero-editor.png +0 -0
- package/public/screenshots/nl2sql.png +0 -0
- package/public/vercel.svg +1 -0
- package/public/window.svg +1 -0
- package/render.yaml +58 -0
- package/scripts/merge-lcov.mjs +239 -0
- package/sonar-project.properties +16 -0
- package/src/app/admin/error.tsx +46 -0
- package/src/app/admin/page.tsx +10 -0
- package/src/app/api/admin/audit/route.ts +52 -0
- package/src/app/api/admin/fleet-health/route.ts +81 -0
- package/src/app/api/ai/autopilot/route.ts +105 -0
- package/src/app/api/ai/chat/route.ts +132 -0
- package/src/app/api/ai/describe-schema/route.ts +52 -0
- package/src/app/api/ai/explain/route.ts +86 -0
- package/src/app/api/ai/impact/route.ts +97 -0
- package/src/app/api/ai/index-advisor/route.ts +98 -0
- package/src/app/api/ai/nl2sql/route.ts +87 -0
- package/src/app/api/ai/query-safety/route.ts +87 -0
- package/src/app/api/auth/login/route.ts +62 -0
- package/src/app/api/auth/logout/route.ts +25 -0
- package/src/app/api/auth/me/route.ts +10 -0
- package/src/app/api/auth/oidc/callback/route.ts +82 -0
- package/src/app/api/auth/oidc/login/route.ts +43 -0
- package/src/app/api/connections/managed/route.ts +35 -0
- package/src/app/api/db/cancel/route.ts +42 -0
- package/src/app/api/db/disconnect/route.ts +28 -0
- package/src/app/api/db/health/route.ts +49 -0
- package/src/app/api/db/maintenance/route.ts +72 -0
- package/src/app/api/db/monitoring/route.ts +62 -0
- package/src/app/api/db/multi-query/route.ts +116 -0
- package/src/app/api/db/pool-stats/route.ts +37 -0
- package/src/app/api/db/profile/route.ts +144 -0
- package/src/app/api/db/provider-meta/route.ts +49 -0
- package/src/app/api/db/query/route.ts +50 -0
- package/src/app/api/db/schema/route.ts +47 -0
- package/src/app/api/db/schema-snapshot/route.ts +42 -0
- package/src/app/api/db/test-connection/route.ts +55 -0
- package/src/app/api/db/transaction/route.ts +111 -0
- package/src/app/api/storage/[collection]/route.ts +67 -0
- package/src/app/api/storage/config/route.ts +17 -0
- package/src/app/api/storage/migrate/route.ts +45 -0
- package/src/app/api/storage/route.ts +32 -0
- package/src/app/error.tsx +49 -0
- package/src/app/global-error.tsx +55 -0
- package/src/app/globals.css +146 -0
- package/src/app/icon.svg +42 -0
- package/src/app/layout.tsx +34 -0
- package/src/app/login/login-form.tsx +301 -0
- package/src/app/login/page.tsx +11 -0
- package/src/app/monitoring/page.tsx +8 -0
- package/src/app/not-found.tsx +29 -0
- package/src/app/page.tsx +5 -0
- package/src/components/AIAutopilotPanel.tsx +238 -0
- package/src/components/CodeGenerator.tsx +271 -0
- package/src/components/CommandPalette.tsx +227 -0
- package/src/components/ConnectionModal.tsx +759 -0
- package/src/components/CreateTableModal.tsx +281 -0
- package/src/components/DataCharts.tsx +962 -0
- package/src/components/DataImportModal.tsx +582 -0
- package/src/components/DataProfiler.tsx +335 -0
- package/src/components/DatabaseDocs.tsx +251 -0
- package/src/components/MaskingSettings.tsx +414 -0
- package/src/components/MobileNav.tsx +50 -0
- package/src/components/NL2SQLPanel.tsx +281 -0
- package/src/components/PivotTable.tsx +257 -0
- package/src/components/QueryEditor.tsx +760 -0
- package/src/components/QueryHistory.tsx +344 -0
- package/src/components/QuerySafetyDialog.tsx +290 -0
- package/src/components/ResultsGrid.tsx +644 -0
- package/src/components/SaveQueryModal.tsx +104 -0
- package/src/components/SavedQueries.tsx +128 -0
- package/src/components/SchemaDiagram.tsx +473 -0
- package/src/components/SchemaDiff.tsx +473 -0
- package/src/components/SnapshotTimeline.tsx +116 -0
- package/src/components/Studio.tsx +639 -0
- package/src/components/TestDataGenerator.tsx +261 -0
- package/src/components/VisualExplain.tsx +820 -0
- package/src/components/admin/AdminDashboard.tsx +163 -0
- package/src/components/admin/tabs/AuditTab.tsx +531 -0
- package/src/components/admin/tabs/MonitoringEmbed.tsx +11 -0
- package/src/components/admin/tabs/OperationsTab.tsx +646 -0
- package/src/components/admin/tabs/OverviewTab.tsx +1328 -0
- package/src/components/admin/tabs/SecurityTab.tsx +284 -0
- package/src/components/community-section.tsx +92 -0
- package/src/components/icons/db-icons.tsx +84 -0
- package/src/components/libredb-logo.tsx +61 -0
- package/src/components/monitoring/MonitoringDashboard.tsx +345 -0
- package/src/components/monitoring/tabs/MetricChart.tsx +82 -0
- package/src/components/monitoring/tabs/OverviewTab.tsx +263 -0
- package/src/components/monitoring/tabs/PerformanceTab.tsx +254 -0
- package/src/components/monitoring/tabs/PoolTab.tsx +174 -0
- package/src/components/monitoring/tabs/QueriesTab.tsx +287 -0
- package/src/components/monitoring/tabs/SessionsTab.tsx +316 -0
- package/src/components/monitoring/tabs/StorageTab.tsx +335 -0
- package/src/components/monitoring/tabs/TablesTab.tsx +300 -0
- package/src/components/results-grid/ResultCard.tsx +111 -0
- package/src/components/results-grid/RowDetailSheet.tsx +178 -0
- package/src/components/results-grid/StatsBar.tsx +201 -0
- package/src/components/results-grid/index.ts +1 -0
- package/src/components/results-grid/utils.ts +23 -0
- package/src/components/schema-explorer/ColumnList.tsx +53 -0
- package/src/components/schema-explorer/SchemaExplorer.tsx +182 -0
- package/src/components/schema-explorer/TableItem.tsx +210 -0
- package/src/components/schema-explorer/index.ts +1 -0
- package/src/components/sidebar/ConnectionItem.tsx +105 -0
- package/src/components/sidebar/ConnectionsList.tsx +62 -0
- package/src/components/sidebar/Sidebar.tsx +130 -0
- package/src/components/sidebar/index.ts +2 -0
- package/src/components/studio/BottomPanel.tsx +286 -0
- package/src/components/studio/QueryToolbar.tsx +180 -0
- package/src/components/studio/StudioDesktopHeader.tsx +114 -0
- package/src/components/studio/StudioMobileHeader.tsx +340 -0
- package/src/components/studio/StudioTabBar.tsx +82 -0
- package/src/components/studio/index.ts +5 -0
- package/src/components/ui/accordion.tsx +66 -0
- package/src/components/ui/alert-dialog.tsx +157 -0
- package/src/components/ui/alert.tsx +66 -0
- package/src/components/ui/aspect-ratio.tsx +11 -0
- package/src/components/ui/avatar.tsx +53 -0
- package/src/components/ui/badge.tsx +46 -0
- package/src/components/ui/breadcrumb.tsx +109 -0
- package/src/components/ui/button-group.tsx +83 -0
- package/src/components/ui/button.tsx +60 -0
- package/src/components/ui/calendar.tsx +216 -0
- package/src/components/ui/card.tsx +92 -0
- package/src/components/ui/carousel.tsx +241 -0
- package/src/components/ui/chart.tsx +357 -0
- package/src/components/ui/checkbox.tsx +32 -0
- package/src/components/ui/collapsible.tsx +33 -0
- package/src/components/ui/command.tsx +184 -0
- package/src/components/ui/context-menu.tsx +252 -0
- package/src/components/ui/dialog.tsx +143 -0
- package/src/components/ui/drawer.tsx +135 -0
- package/src/components/ui/dropdown-menu.tsx +257 -0
- package/src/components/ui/empty.tsx +104 -0
- package/src/components/ui/field.tsx +248 -0
- package/src/components/ui/form.tsx +167 -0
- package/src/components/ui/hover-card.tsx +44 -0
- package/src/components/ui/input-group.tsx +170 -0
- package/src/components/ui/input-otp.tsx +77 -0
- package/src/components/ui/input.tsx +21 -0
- package/src/components/ui/item.tsx +193 -0
- package/src/components/ui/kbd.tsx +28 -0
- package/src/components/ui/label.tsx +24 -0
- package/src/components/ui/menubar.tsx +276 -0
- package/src/components/ui/navigation-menu.tsx +168 -0
- package/src/components/ui/pagination.tsx +127 -0
- package/src/components/ui/popover.tsx +48 -0
- package/src/components/ui/progress.tsx +31 -0
- package/src/components/ui/radio-group.tsx +45 -0
- package/src/components/ui/resizable.tsx +56 -0
- package/src/components/ui/scroll-area.tsx +58 -0
- package/src/components/ui/select.tsx +187 -0
- package/src/components/ui/separator.tsx +28 -0
- package/src/components/ui/sheet.tsx +139 -0
- package/src/components/ui/sidebar.tsx +726 -0
- package/src/components/ui/skeleton.tsx +13 -0
- package/src/components/ui/slider.tsx +63 -0
- package/src/components/ui/sonner.tsx +40 -0
- package/src/components/ui/spinner.tsx +16 -0
- package/src/components/ui/switch.tsx +31 -0
- package/src/components/ui/table.tsx +116 -0
- package/src/components/ui/tabs.tsx +66 -0
- package/src/components/ui/textarea.tsx +18 -0
- package/src/components/ui/toggle-group.tsx +83 -0
- package/src/components/ui/toggle.tsx +47 -0
- package/src/components/ui/tooltip.tsx +61 -0
- package/src/exports/components.ts +15 -0
- package/src/exports/index.ts +4 -0
- package/src/exports/providers.ts +4 -0
- package/src/exports/types.ts +26 -0
- package/src/hooks/use-ai-chat.ts +182 -0
- package/src/hooks/use-all-connections.ts +66 -0
- package/src/hooks/use-api-call.ts +71 -0
- package/src/hooks/use-auth.ts +51 -0
- package/src/hooks/use-connection-form.ts +349 -0
- package/src/hooks/use-connection-manager.ts +169 -0
- package/src/hooks/use-connection-payload.ts +15 -0
- package/src/hooks/use-inline-editing.ts +109 -0
- package/src/hooks/use-mobile.ts +20 -0
- package/src/hooks/use-monitoring-data.ts +270 -0
- package/src/hooks/use-provider-metadata.ts +62 -0
- package/src/hooks/use-query-execution.ts +478 -0
- package/src/hooks/use-storage-sync.ts +259 -0
- package/src/hooks/use-tab-manager.ts +231 -0
- package/src/hooks/use-toast.ts +20 -0
- package/src/hooks/use-transaction-control.ts +64 -0
- package/src/lib/api/error-codes.ts +30 -0
- package/src/lib/api/errors.ts +236 -0
- package/src/lib/api/with-error-handler.ts +41 -0
- package/src/lib/audit.ts +105 -0
- package/src/lib/auth.ts +87 -0
- package/src/lib/connection-string-parser.ts +172 -0
- package/src/lib/data-masking.ts +385 -0
- package/src/lib/db/base-provider.ts +325 -0
- package/src/lib/db/errors.ts +317 -0
- package/src/lib/db/factory.ts +324 -0
- package/src/lib/db/index.ts +123 -0
- package/src/lib/db/providers/document/index.ts +6 -0
- package/src/lib/db/providers/document/mongodb.ts +992 -0
- package/src/lib/db/providers/keyvalue/redis.ts +554 -0
- package/src/lib/db/providers/sql/index.ts +11 -0
- package/src/lib/db/providers/sql/mssql.ts +1065 -0
- package/src/lib/db/providers/sql/mysql.ts +978 -0
- package/src/lib/db/providers/sql/oracle.ts +1044 -0
- package/src/lib/db/providers/sql/postgres.ts +1179 -0
- package/src/lib/db/providers/sql/sql-base.ts +174 -0
- package/src/lib/db/providers/sql/sqlite.ts +721 -0
- package/src/lib/db/types.ts +437 -0
- package/src/lib/db/utils/pool-manager.ts +287 -0
- package/src/lib/db/utils/query-limiter.ts +239 -0
- package/src/lib/db-ui-config.ts +86 -0
- package/src/lib/editor/mongodb-completions.ts +172 -0
- package/src/lib/editor/sql-completions.ts +280 -0
- package/src/lib/llm/base-provider.ts +117 -0
- package/src/lib/llm/factory.ts +102 -0
- package/src/lib/llm/index.ts +90 -0
- package/src/lib/llm/providers/custom.ts +181 -0
- package/src/lib/llm/providers/gemini.ts +126 -0
- package/src/lib/llm/providers/ollama.ts +154 -0
- package/src/lib/llm/providers/openai.ts +146 -0
- package/src/lib/llm/types.ts +173 -0
- package/src/lib/llm/utils/config.ts +187 -0
- package/src/lib/llm/utils/retry.ts +119 -0
- package/src/lib/llm/utils/streaming.ts +202 -0
- package/src/lib/logger.ts +127 -0
- package/src/lib/monitoring-thresholds.ts +44 -0
- package/src/lib/oidc.ts +262 -0
- package/src/lib/query-generators.ts +61 -0
- package/src/lib/schema-diff/diff-engine.ts +273 -0
- package/src/lib/schema-diff/migration-generator.ts +208 -0
- package/src/lib/schema-diff/types.ts +55 -0
- package/src/lib/seed/config-loader.ts +79 -0
- package/src/lib/seed/connection-filter.ts +49 -0
- package/src/lib/seed/credential-resolver.ts +62 -0
- package/src/lib/seed/index.ts +40 -0
- package/src/lib/seed/resolve-connection.ts +57 -0
- package/src/lib/seed/types.ts +69 -0
- package/src/lib/sql/alias-extractor.ts +267 -0
- package/src/lib/sql/index.ts +8 -0
- package/src/lib/sql/statement-splitter.ts +167 -0
- package/src/lib/sql/types.ts +40 -0
- package/src/lib/ssh/tunnel.ts +142 -0
- package/src/lib/storage/factory.ts +84 -0
- package/src/lib/storage/index.ts +14 -0
- package/src/lib/storage/local-storage.ts +99 -0
- package/src/lib/storage/providers/postgres.ts +225 -0
- package/src/lib/storage/providers/sqlite.ts +153 -0
- package/src/lib/storage/storage-facade.ts +272 -0
- package/src/lib/storage/types.ts +75 -0
- package/src/lib/time-series-buffer.ts +58 -0
- package/src/lib/types.ts +173 -0
- package/src/lib/utils.ts +6 -0
- package/src/proxy.ts +104 -0
- package/src/types/db-drivers.d.ts +23 -0
- package/src/types/html2canvas.d.ts +9 -0
- package/tests/api/admin/audit.test.ts +178 -0
- package/tests/api/admin/fleet-health.test.ts +183 -0
- package/tests/api/ai/autopilot.test.ts +174 -0
- package/tests/api/ai/chat.test.ts +250 -0
- package/tests/api/ai/describe-schema.test.ts +266 -0
- package/tests/api/ai/explain.test.ts +199 -0
- package/tests/api/ai/impact.test.ts +168 -0
- package/tests/api/ai/index-advisor.test.ts +171 -0
- package/tests/api/ai/nl2sql.test.ts +202 -0
- package/tests/api/ai/query-safety.test.ts +196 -0
- package/tests/api/auth/login.test.ts +170 -0
- package/tests/api/auth/logout.test.ts +140 -0
- package/tests/api/auth/me.test.ts +73 -0
- package/tests/api/auth/oidc-callback.test.ts +215 -0
- package/tests/api/auth/oidc-login.test.ts +127 -0
- package/tests/api/db/cancel.test.ts +198 -0
- package/tests/api/db/disconnect.test.ts +124 -0
- package/tests/api/db/health.test.ts +222 -0
- package/tests/api/db/maintenance.test.ts +263 -0
- package/tests/api/db/monitoring.test.ts +221 -0
- package/tests/api/db/multi-query.test.ts +316 -0
- package/tests/api/db/pool-stats.test.ts +135 -0
- package/tests/api/db/profile.test.ts +330 -0
- package/tests/api/db/provider-meta.test.ts +193 -0
- package/tests/api/db/query.test.ts +314 -0
- package/tests/api/db/schema-snapshot.test.ts +170 -0
- package/tests/api/db/schema.test.ts +191 -0
- package/tests/api/db/test-connection.test.ts +185 -0
- package/tests/api/db/transaction.test.ts +314 -0
- package/tests/api/proxy.test.ts +191 -0
- package/tests/api/seed/managed-route.test.ts +113 -0
- package/tests/api/storage/config.test.ts +42 -0
- package/tests/api/storage/storage-routes.test.ts +309 -0
- package/tests/components/AIAutopilotPanel.test.tsx +756 -0
- package/tests/components/AdminPage.test.tsx +33 -0
- package/tests/components/CodeGenerator.test.tsx +182 -0
- package/tests/components/CommandPalette.test.tsx +428 -0
- package/tests/components/CommunitySection.test.tsx +91 -0
- package/tests/components/ConnectionModal.mobile.test.tsx +284 -0
- package/tests/components/ConnectionModal.test.tsx +570 -0
- package/tests/components/CreateTableModal.test.tsx +383 -0
- package/tests/components/DataCharts.test.tsx +739 -0
- package/tests/components/DataImportModal.test.tsx +751 -0
- package/tests/components/DataProfiler.test.tsx +589 -0
- package/tests/components/DatabaseDocs.test.tsx +353 -0
- package/tests/components/LoginPage.test.tsx +163 -0
- package/tests/components/LoginPageOIDC.test.tsx +92 -0
- package/tests/components/MaskingSettings.test.tsx +498 -0
- package/tests/components/MobileNav.test.tsx +30 -0
- package/tests/components/MonitoringPage.test.tsx +32 -0
- package/tests/components/NL2SQLPanel.test.tsx +621 -0
- package/tests/components/Page.test.tsx +33 -0
- package/tests/components/PivotTable.test.tsx +350 -0
- package/tests/components/QueryEditor.test.tsx +1730 -0
- package/tests/components/QueryHistory.test.tsx +572 -0
- package/tests/components/QuerySafetyDialog.test.tsx +586 -0
- package/tests/components/ResultsGrid.test.tsx +804 -0
- package/tests/components/RootLayout.test.tsx +83 -0
- package/tests/components/SaveQueryModal.test.tsx +25 -0
- package/tests/components/SavedQueries.test.tsx +43 -0
- package/tests/components/SchemaDiagram.test.tsx +1034 -0
- package/tests/components/SchemaDiff.test.tsx +906 -0
- package/tests/components/SnapshotTimeline.test.tsx +174 -0
- package/tests/components/Studio.test.tsx +1030 -0
- package/tests/components/TestDataGenerator.test.tsx +291 -0
- package/tests/components/VisualExplain.test.tsx +704 -0
- package/tests/components/admin/AdminDashboard.test.tsx +205 -0
- package/tests/components/admin/AuditTab.test.tsx +220 -0
- package/tests/components/admin/MonitoringEmbed.test.tsx +58 -0
- package/tests/components/admin/OperationsTab.test.tsx +975 -0
- package/tests/components/admin/OverviewTab.test.tsx +254 -0
- package/tests/components/admin/SecurityTab.test.tsx +467 -0
- package/tests/components/monitoring/MetricChart.test.tsx +111 -0
- package/tests/components/monitoring/MonitoringDashboard.test.tsx +259 -0
- package/tests/components/monitoring/OverviewTab.test.tsx +78 -0
- package/tests/components/monitoring/PerformanceTab.test.tsx +87 -0
- package/tests/components/monitoring/PoolTab.test.tsx +42 -0
- package/tests/components/monitoring/QueriesTab.test.tsx +80 -0
- package/tests/components/monitoring/SessionsTab.test.tsx +154 -0
- package/tests/components/monitoring/StorageTab.test.tsx +127 -0
- package/tests/components/monitoring/TablesTab.test.tsx +153 -0
- package/tests/components/results-grid/ResultCard.test.tsx +105 -0
- package/tests/components/results-grid/RowDetailSheet.test.tsx +308 -0
- package/tests/components/results-grid/StatsBar.test.tsx +162 -0
- package/tests/components/schema-explorer/ColumnList.test.tsx +151 -0
- package/tests/components/schema-explorer/SchemaExplorer.test.tsx +461 -0
- package/tests/components/schema-explorer/TableItem.test.tsx +415 -0
- package/tests/components/sidebar/ConnectionItem.test.tsx +201 -0
- package/tests/components/sidebar/ConnectionsList.test.tsx +176 -0
- package/tests/components/sidebar/Sidebar.test.tsx +187 -0
- package/tests/components/studio/BottomPanel.test.tsx +383 -0
- package/tests/components/studio/QueryToolbar.test.tsx +321 -0
- package/tests/components/studio/StudioDesktopHeader.test.tsx +377 -0
- package/tests/components/studio/StudioMobileHeader.test.tsx +198 -0
- package/tests/components/studio/StudioTabBar.test.tsx +331 -0
- package/tests/fixtures/connections.ts +96 -0
- package/tests/fixtures/masking-configs.ts +86 -0
- package/tests/fixtures/query-results.ts +71 -0
- package/tests/fixtures/schemas.ts +64 -0
- package/tests/fixtures/seed-connections/invalid-config.yaml +7 -0
- package/tests/fixtures/seed-connections/minimal-config.yaml +8 -0
- package/tests/fixtures/seed-connections/mixed-credentials.yaml +23 -0
- package/tests/fixtures/seed-connections/multi-role-config.yaml +30 -0
- package/tests/fixtures/seed-connections/valid-config.json +15 -0
- package/tests/fixtures/seed-connections/valid-config.yaml +51 -0
- package/tests/helpers/mock-fetch.ts +59 -0
- package/tests/helpers/mock-monaco.ts +112 -0
- package/tests/helpers/mock-navigation.ts +28 -0
- package/tests/helpers/mock-next.ts +80 -0
- package/tests/helpers/mock-provider.ts +133 -0
- package/tests/helpers/mock-sonner.ts +29 -0
- package/tests/helpers/render-with-providers.tsx +19 -0
- package/tests/hooks/use-ai-chat.test.ts +600 -0
- package/tests/hooks/use-auth.test.ts +371 -0
- package/tests/hooks/use-connection-form.test.ts +743 -0
- package/tests/hooks/use-connection-manager.test.ts +466 -0
- package/tests/hooks/use-inline-editing.test.ts +321 -0
- package/tests/hooks/use-mobile.test.ts +177 -0
- package/tests/hooks/use-monitoring-data.test.ts +819 -0
- package/tests/hooks/use-provider-metadata.test.ts +228 -0
- package/tests/hooks/use-query-execution.test.ts +1212 -0
- package/tests/hooks/use-tab-manager.test.ts +756 -0
- package/tests/hooks/use-toast.test.ts +74 -0
- package/tests/hooks/use-transaction-control.test.ts +211 -0
- package/tests/integration/db/mongodb-provider.test.ts +698 -0
- package/tests/integration/db/mssql-provider.test.ts +840 -0
- package/tests/integration/db/mysql-provider.test.ts +872 -0
- package/tests/integration/db/oracle-provider.test.ts +843 -0
- package/tests/integration/db/postgres-provider.test.ts +1382 -0
- package/tests/integration/db/redis-provider.test.ts +526 -0
- package/tests/integration/db/sqlite-provider.test.ts +480 -0
- package/tests/integration/seed/seed-pipeline.test.ts +102 -0
- package/tests/isolated/factory-singleton.test.ts +150 -0
- package/tests/isolated/use-storage-sync.test.ts +389 -0
- package/tests/run-components.sh +196 -0
- package/tests/setup-dom.ts +58 -0
- package/tests/setup.ts +40 -0
- package/tests/unit/api-errors.test.ts +210 -0
- package/tests/unit/code-generator-functions.test.ts +271 -0
- package/tests/unit/components/column-list.test.tsx +190 -0
- package/tests/unit/components/data-import-modal.test.tsx +441 -0
- package/tests/unit/components/studio-mobile-header.test.tsx +327 -0
- package/tests/unit/data-charts-functions.test.ts +496 -0
- package/tests/unit/data-import-functions.test.ts +320 -0
- package/tests/unit/data-import-utils.test.ts +125 -0
- package/tests/unit/db/base-provider.test.ts +517 -0
- package/tests/unit/db/errors.test.ts +403 -0
- package/tests/unit/db/factory.test.ts +436 -0
- package/tests/unit/db/pool-manager.test.ts +440 -0
- package/tests/unit/db/query-limiter.test.ts +387 -0
- package/tests/unit/db/sql-base.test.ts +438 -0
- package/tests/unit/lib/api/error-codes.test.ts +39 -0
- package/tests/unit/lib/audit.test.ts +326 -0
- package/tests/unit/lib/auth.test.ts +146 -0
- package/tests/unit/lib/connection-string-parser.test.ts +424 -0
- package/tests/unit/lib/data-masking.test.ts +583 -0
- package/tests/unit/lib/db-icons.test.tsx +41 -0
- package/tests/unit/lib/monitoring-thresholds.test.ts +133 -0
- package/tests/unit/lib/oidc.test.ts +509 -0
- package/tests/unit/lib/query-generators.test.ts +127 -0
- package/tests/unit/lib/storage/factory.test.ts +71 -0
- package/tests/unit/lib/storage/local-storage.test.ts +114 -0
- package/tests/unit/lib/storage/providers/postgres.test.ts +312 -0
- package/tests/unit/lib/storage/providers/sqlite.test.ts +232 -0
- package/tests/unit/lib/storage/storage-facade-extended.test.ts +331 -0
- package/tests/unit/lib/storage/storage-facade.test.ts +184 -0
- package/tests/unit/lib/storage.test.ts +317 -0
- package/tests/unit/lib/time-series-buffer.test.ts +212 -0
- package/tests/unit/lib/utils.test.ts +24 -0
- package/tests/unit/llm/base-provider.test.ts +238 -0
- package/tests/unit/llm/config.test.ts +262 -0
- package/tests/unit/llm/custom-provider.test.ts +281 -0
- package/tests/unit/llm/gemini-provider.test.ts +248 -0
- package/tests/unit/llm/llm-factory.test.ts +155 -0
- package/tests/unit/llm/ollama-provider.test.ts +288 -0
- package/tests/unit/llm/openai-provider.test.ts +324 -0
- package/tests/unit/llm/retry.test.ts +180 -0
- package/tests/unit/llm/streaming.test.ts +355 -0
- package/tests/unit/logger.test.ts +198 -0
- package/tests/unit/mongodb-completions.test.ts +516 -0
- package/tests/unit/pivot-table-functions.test.ts +76 -0
- package/tests/unit/query-cancelled-error.test.ts +81 -0
- package/tests/unit/schema-diff/diff-engine.test.ts +367 -0
- package/tests/unit/schema-diff/migration-generator.test.ts +513 -0
- package/tests/unit/seed/config-loader.test.ts +73 -0
- package/tests/unit/seed/connection-filter.test.ts +91 -0
- package/tests/unit/seed/credential-resolver.test.ts +85 -0
- package/tests/unit/seed/index.test.ts +72 -0
- package/tests/unit/seed/resolve-connection.test.ts +74 -0
- package/tests/unit/seed/types.test.ts +129 -0
- package/tests/unit/sql/alias-extractor.test.ts +444 -0
- package/tests/unit/sql/statement-splitter.test.ts +348 -0
- package/tests/unit/sql-completions.test.ts +463 -0
- package/tests/unit/ssh-tunnel.test.ts +465 -0
- package/tsconfig.json +42 -0
|
@@ -0,0 +1,317 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Database Error Classes
|
|
3
|
+
* Custom error types for database operations
|
|
4
|
+
*/
|
|
5
|
+
|
|
6
|
+
import type { DatabaseType } from './types';
|
|
7
|
+
import { ApiErrorCode } from '@/lib/api/error-codes';
|
|
8
|
+
|
|
9
|
+
// ============================================================================
|
|
10
|
+
// Base Database Error
|
|
11
|
+
// ============================================================================
|
|
12
|
+
|
|
13
|
+
/**
|
|
14
|
+
* Base error class for all database-related errors
|
|
15
|
+
*/
|
|
16
|
+
export class DatabaseError extends Error {
|
|
17
|
+
constructor(
|
|
18
|
+
message: string,
|
|
19
|
+
public readonly provider?: DatabaseType,
|
|
20
|
+
public readonly code?: ApiErrorCode,
|
|
21
|
+
public readonly query?: string
|
|
22
|
+
) {
|
|
23
|
+
super(message);
|
|
24
|
+
this.name = 'DatabaseError';
|
|
25
|
+
Object.setPrototypeOf(this, DatabaseError.prototype);
|
|
26
|
+
}
|
|
27
|
+
|
|
28
|
+
toJSON() {
|
|
29
|
+
return {
|
|
30
|
+
name: this.name,
|
|
31
|
+
message: this.message,
|
|
32
|
+
provider: this.provider,
|
|
33
|
+
code: this.code,
|
|
34
|
+
// Don't expose full query in production for security
|
|
35
|
+
query: this.query ? this.query.substring(0, 100) + '...' : undefined,
|
|
36
|
+
};
|
|
37
|
+
}
|
|
38
|
+
}
|
|
39
|
+
|
|
40
|
+
// ============================================================================
|
|
41
|
+
// Configuration Errors
|
|
42
|
+
// ============================================================================
|
|
43
|
+
|
|
44
|
+
/**
|
|
45
|
+
* Configuration error - missing or invalid configuration
|
|
46
|
+
*/
|
|
47
|
+
export class DatabaseConfigError extends DatabaseError {
|
|
48
|
+
constructor(message: string, provider?: DatabaseType) {
|
|
49
|
+
super(message, provider, ApiErrorCode.CONFIG_ERROR);
|
|
50
|
+
this.name = 'DatabaseConfigError';
|
|
51
|
+
Object.setPrototypeOf(this, DatabaseConfigError.prototype);
|
|
52
|
+
}
|
|
53
|
+
}
|
|
54
|
+
|
|
55
|
+
// ============================================================================
|
|
56
|
+
// Connection Errors
|
|
57
|
+
// ============================================================================
|
|
58
|
+
|
|
59
|
+
/**
|
|
60
|
+
* Connection error - failed to connect to database
|
|
61
|
+
*/
|
|
62
|
+
export class ConnectionError extends DatabaseError {
|
|
63
|
+
constructor(
|
|
64
|
+
message: string,
|
|
65
|
+
provider?: DatabaseType,
|
|
66
|
+
public readonly host?: string,
|
|
67
|
+
public readonly port?: number
|
|
68
|
+
) {
|
|
69
|
+
super(message, provider, ApiErrorCode.CONNECTION_ERROR);
|
|
70
|
+
this.name = 'ConnectionError';
|
|
71
|
+
Object.setPrototypeOf(this, ConnectionError.prototype);
|
|
72
|
+
}
|
|
73
|
+
}
|
|
74
|
+
|
|
75
|
+
/**
|
|
76
|
+
* Authentication error - invalid credentials
|
|
77
|
+
*/
|
|
78
|
+
export class AuthenticationError extends DatabaseError {
|
|
79
|
+
constructor(message: string, provider?: DatabaseType) {
|
|
80
|
+
super(message, provider, ApiErrorCode.AUTH_ERROR);
|
|
81
|
+
this.name = 'AuthenticationError';
|
|
82
|
+
Object.setPrototypeOf(this, AuthenticationError.prototype);
|
|
83
|
+
}
|
|
84
|
+
}
|
|
85
|
+
|
|
86
|
+
/**
|
|
87
|
+
* Pool exhausted error - no available connections in pool
|
|
88
|
+
*/
|
|
89
|
+
export class PoolExhaustedError extends DatabaseError {
|
|
90
|
+
constructor(
|
|
91
|
+
message: string,
|
|
92
|
+
provider?: DatabaseType,
|
|
93
|
+
public readonly poolSize?: number
|
|
94
|
+
) {
|
|
95
|
+
super(message, provider, ApiErrorCode.POOL_EXHAUSTED);
|
|
96
|
+
this.name = 'PoolExhaustedError';
|
|
97
|
+
Object.setPrototypeOf(this, PoolExhaustedError.prototype);
|
|
98
|
+
}
|
|
99
|
+
}
|
|
100
|
+
|
|
101
|
+
// ============================================================================
|
|
102
|
+
// Query Errors
|
|
103
|
+
// ============================================================================
|
|
104
|
+
|
|
105
|
+
/**
|
|
106
|
+
* Query error - SQL syntax or execution error
|
|
107
|
+
*/
|
|
108
|
+
export class QueryError extends DatabaseError {
|
|
109
|
+
constructor(
|
|
110
|
+
message: string,
|
|
111
|
+
provider?: DatabaseType,
|
|
112
|
+
query?: string,
|
|
113
|
+
public readonly position?: number,
|
|
114
|
+
public readonly detail?: string
|
|
115
|
+
) {
|
|
116
|
+
super(message, provider, ApiErrorCode.QUERY_ERROR, query);
|
|
117
|
+
this.name = 'QueryError';
|
|
118
|
+
Object.setPrototypeOf(this, QueryError.prototype);
|
|
119
|
+
}
|
|
120
|
+
}
|
|
121
|
+
|
|
122
|
+
/**
|
|
123
|
+
* Timeout error - query or connection timeout
|
|
124
|
+
*/
|
|
125
|
+
export class TimeoutError extends DatabaseError {
|
|
126
|
+
constructor(
|
|
127
|
+
message: string,
|
|
128
|
+
provider?: DatabaseType,
|
|
129
|
+
public readonly timeout?: number,
|
|
130
|
+
query?: string
|
|
131
|
+
) {
|
|
132
|
+
super(message, provider, ApiErrorCode.TIMEOUT_ERROR, query);
|
|
133
|
+
this.name = 'TimeoutError';
|
|
134
|
+
Object.setPrototypeOf(this, TimeoutError.prototype);
|
|
135
|
+
}
|
|
136
|
+
}
|
|
137
|
+
|
|
138
|
+
/**
|
|
139
|
+
* Query cancelled error - user-initiated cancellation
|
|
140
|
+
*/
|
|
141
|
+
export class QueryCancelledError extends DatabaseError {
|
|
142
|
+
constructor(message: string, provider?: DatabaseType, query?: string) {
|
|
143
|
+
super(message, provider, ApiErrorCode.QUERY_CANCELLED, query);
|
|
144
|
+
this.name = 'QueryCancelledError';
|
|
145
|
+
Object.setPrototypeOf(this, QueryCancelledError.prototype);
|
|
146
|
+
}
|
|
147
|
+
}
|
|
148
|
+
|
|
149
|
+
// ============================================================================
|
|
150
|
+
// Type Guards
|
|
151
|
+
// ============================================================================
|
|
152
|
+
|
|
153
|
+
export function isDatabaseError(error: unknown): error is DatabaseError {
|
|
154
|
+
return error instanceof DatabaseError;
|
|
155
|
+
}
|
|
156
|
+
|
|
157
|
+
export function isConnectionError(error: unknown): error is ConnectionError {
|
|
158
|
+
return error instanceof ConnectionError;
|
|
159
|
+
}
|
|
160
|
+
|
|
161
|
+
export function isQueryError(error: unknown): error is QueryError {
|
|
162
|
+
return error instanceof QueryError;
|
|
163
|
+
}
|
|
164
|
+
|
|
165
|
+
export function isTimeoutError(error: unknown): error is TimeoutError {
|
|
166
|
+
return error instanceof TimeoutError;
|
|
167
|
+
}
|
|
168
|
+
|
|
169
|
+
export function isAuthenticationError(error: unknown): error is AuthenticationError {
|
|
170
|
+
return error instanceof AuthenticationError;
|
|
171
|
+
}
|
|
172
|
+
|
|
173
|
+
export function isQueryCancelledError(error: unknown): error is QueryCancelledError {
|
|
174
|
+
return error instanceof QueryCancelledError;
|
|
175
|
+
}
|
|
176
|
+
|
|
177
|
+
// ============================================================================
|
|
178
|
+
// Error Mapping Utilities
|
|
179
|
+
// ============================================================================
|
|
180
|
+
|
|
181
|
+
/**
|
|
182
|
+
* Check if an error is retryable
|
|
183
|
+
*/
|
|
184
|
+
export function isRetryableError(error: unknown): boolean {
|
|
185
|
+
if (!isDatabaseError(error)) {
|
|
186
|
+
// Network errors are typically retryable
|
|
187
|
+
if (error instanceof TypeError && error.message.includes('fetch')) {
|
|
188
|
+
return true;
|
|
189
|
+
}
|
|
190
|
+
return false;
|
|
191
|
+
}
|
|
192
|
+
|
|
193
|
+
// Auth and config errors are not retryable
|
|
194
|
+
if (error instanceof AuthenticationError || error instanceof DatabaseConfigError) {
|
|
195
|
+
return false;
|
|
196
|
+
}
|
|
197
|
+
|
|
198
|
+
// Query syntax errors are not retryable
|
|
199
|
+
if (error instanceof QueryError && error.position !== undefined) {
|
|
200
|
+
return false;
|
|
201
|
+
}
|
|
202
|
+
|
|
203
|
+
// Connection and timeout errors may be retryable
|
|
204
|
+
return true;
|
|
205
|
+
}
|
|
206
|
+
|
|
207
|
+
/**
|
|
208
|
+
* Map native database errors to our error types
|
|
209
|
+
*/
|
|
210
|
+
export function mapDatabaseError(
|
|
211
|
+
error: unknown,
|
|
212
|
+
provider: DatabaseType,
|
|
213
|
+
query?: string
|
|
214
|
+
): DatabaseError {
|
|
215
|
+
if (isDatabaseError(error)) {
|
|
216
|
+
return error;
|
|
217
|
+
}
|
|
218
|
+
|
|
219
|
+
if (!(error instanceof Error)) {
|
|
220
|
+
return new DatabaseError(String(error), provider);
|
|
221
|
+
}
|
|
222
|
+
|
|
223
|
+
const message = error.message.toLowerCase();
|
|
224
|
+
|
|
225
|
+
// Connection errors
|
|
226
|
+
if (
|
|
227
|
+
message.includes('econnrefused') ||
|
|
228
|
+
message.includes('connection refused') ||
|
|
229
|
+
message.includes('connect etimedout') ||
|
|
230
|
+
message.includes('getaddrinfo')
|
|
231
|
+
) {
|
|
232
|
+
return new ConnectionError(
|
|
233
|
+
`Failed to connect to ${provider} database: ${error.message}`,
|
|
234
|
+
provider
|
|
235
|
+
);
|
|
236
|
+
}
|
|
237
|
+
|
|
238
|
+
// Authentication errors
|
|
239
|
+
if (
|
|
240
|
+
message.includes('password') ||
|
|
241
|
+
message.includes('authentication') ||
|
|
242
|
+
message.includes('access denied') ||
|
|
243
|
+
message.includes('permission denied')
|
|
244
|
+
) {
|
|
245
|
+
return new AuthenticationError(
|
|
246
|
+
`Authentication failed: ${error.message}`,
|
|
247
|
+
provider
|
|
248
|
+
);
|
|
249
|
+
}
|
|
250
|
+
|
|
251
|
+
// Query cancellation (must check before timeout — 'canceling statement' is cancellation, not timeout)
|
|
252
|
+
if (
|
|
253
|
+
message.includes('canceling statement') ||
|
|
254
|
+
message.includes('query execution was interrupted') ||
|
|
255
|
+
message.includes('query was cancelled') ||
|
|
256
|
+
message.includes('kill query')
|
|
257
|
+
) {
|
|
258
|
+
return new QueryCancelledError(
|
|
259
|
+
'Query was cancelled',
|
|
260
|
+
provider,
|
|
261
|
+
query
|
|
262
|
+
);
|
|
263
|
+
}
|
|
264
|
+
|
|
265
|
+
// Timeout errors
|
|
266
|
+
if (
|
|
267
|
+
message.includes('timeout') ||
|
|
268
|
+
message.includes('timed out')
|
|
269
|
+
) {
|
|
270
|
+
return new TimeoutError(
|
|
271
|
+
`Query timeout: ${error.message}`,
|
|
272
|
+
provider,
|
|
273
|
+
undefined,
|
|
274
|
+
query
|
|
275
|
+
);
|
|
276
|
+
}
|
|
277
|
+
|
|
278
|
+
// Oracle errors
|
|
279
|
+
if (message.includes('ora-01017') || message.includes('invalid username/password')) {
|
|
280
|
+
return new AuthenticationError(`Authentication failed: ${error.message}`, provider);
|
|
281
|
+
}
|
|
282
|
+
if (message.includes('ora-12541') || message.includes('ora-12154') || message.includes('tns:')) {
|
|
283
|
+
return new ConnectionError(`Failed to connect to Oracle: ${error.message}`, provider);
|
|
284
|
+
}
|
|
285
|
+
if (message.includes('ora-00942')) {
|
|
286
|
+
return new QueryError(`Table or view does not exist: ${error.message}`, provider, query);
|
|
287
|
+
}
|
|
288
|
+
|
|
289
|
+
// MSSQL errors
|
|
290
|
+
if (message.includes('login failed')) {
|
|
291
|
+
return new AuthenticationError(`Authentication failed: ${error.message}`, provider);
|
|
292
|
+
}
|
|
293
|
+
if (message.includes('cannot open database')) {
|
|
294
|
+
return new ConnectionError(`Database not found: ${error.message}`, provider);
|
|
295
|
+
}
|
|
296
|
+
|
|
297
|
+
// Query errors (PostgreSQL specific)
|
|
298
|
+
if (message.includes('syntax error') || message.includes('column') || message.includes('relation')) {
|
|
299
|
+
return new QueryError(
|
|
300
|
+
error.message,
|
|
301
|
+
provider,
|
|
302
|
+
query,
|
|
303
|
+
(error as { position?: number }).position
|
|
304
|
+
);
|
|
305
|
+
}
|
|
306
|
+
|
|
307
|
+
// Pool errors
|
|
308
|
+
if (message.includes('pool') || message.includes('too many connections')) {
|
|
309
|
+
return new PoolExhaustedError(
|
|
310
|
+
`Connection pool error: ${error.message}`,
|
|
311
|
+
provider
|
|
312
|
+
);
|
|
313
|
+
}
|
|
314
|
+
|
|
315
|
+
// Generic database error
|
|
316
|
+
return new DatabaseError(error.message, provider, undefined, query);
|
|
317
|
+
}
|
|
@@ -0,0 +1,324 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Database Provider Factory
|
|
3
|
+
* Creates appropriate provider instance based on connection type
|
|
4
|
+
* Uses dynamic imports to reduce memory footprint - providers are loaded on demand
|
|
5
|
+
*/
|
|
6
|
+
|
|
7
|
+
import {
|
|
8
|
+
type DatabaseProvider,
|
|
9
|
+
type DatabaseConnection,
|
|
10
|
+
type ProviderOptions,
|
|
11
|
+
} from './types';
|
|
12
|
+
import { DatabaseConfigError } from './errors';
|
|
13
|
+
import { createSSHTunnel, closeSSHTunnel } from '@/lib/ssh/tunnel';
|
|
14
|
+
import { logger } from '@/lib/logger';
|
|
15
|
+
|
|
16
|
+
// ============================================================================
|
|
17
|
+
// Provider Factory
|
|
18
|
+
// ============================================================================
|
|
19
|
+
|
|
20
|
+
/**
|
|
21
|
+
* Create a database provider based on connection configuration
|
|
22
|
+
* Uses dynamic imports to load providers on-demand, reducing initial memory usage
|
|
23
|
+
*
|
|
24
|
+
* @param connection - Database connection configuration
|
|
25
|
+
* @param options - Optional provider options (pooling, timeout, etc.)
|
|
26
|
+
* @returns Promise<DatabaseProvider> instance
|
|
27
|
+
* @throws DatabaseConfigError if connection type is not supported
|
|
28
|
+
*
|
|
29
|
+
* @example
|
|
30
|
+
* // SQL Database
|
|
31
|
+
* const provider = await createDatabaseProvider({
|
|
32
|
+
* id: '1',
|
|
33
|
+
* name: 'My PostgreSQL',
|
|
34
|
+
* type: 'postgres',
|
|
35
|
+
* host: 'localhost',
|
|
36
|
+
* port: 5432,
|
|
37
|
+
* database: 'mydb',
|
|
38
|
+
* user: 'admin',
|
|
39
|
+
* password: 'secret',
|
|
40
|
+
* createdAt: new Date(),
|
|
41
|
+
* });
|
|
42
|
+
*
|
|
43
|
+
* // MongoDB
|
|
44
|
+
* const mongoProvider = await createDatabaseProvider({
|
|
45
|
+
* id: '2',
|
|
46
|
+
* name: 'My MongoDB',
|
|
47
|
+
* type: 'mongodb',
|
|
48
|
+
* connectionString: 'mongodb://localhost:27017/mydb',
|
|
49
|
+
* createdAt: new Date(),
|
|
50
|
+
* });
|
|
51
|
+
*
|
|
52
|
+
* await provider.connect();
|
|
53
|
+
* const result = await provider.query('SELECT * FROM users');
|
|
54
|
+
* await provider.disconnect();
|
|
55
|
+
*/
|
|
56
|
+
export async function createDatabaseProvider(
|
|
57
|
+
connection: DatabaseConnection,
|
|
58
|
+
options: ProviderOptions = {}
|
|
59
|
+
): Promise<DatabaseProvider> {
|
|
60
|
+
// Sanitize user-controlled values to prevent log injection
|
|
61
|
+
const sanitize = (v: string) => v.replace(/[\r\n]/g, ' ').replace(/[\x00-\x08\x0b\x0c\x0e-\x1f]/g, '');
|
|
62
|
+
console.log(`[DB] Creating ${sanitize(connection.type)} provider for "${sanitize(connection.name || '')}"`);
|
|
63
|
+
|
|
64
|
+
switch (connection.type) {
|
|
65
|
+
// SQL Databases - dynamically imported to reduce memory
|
|
66
|
+
case 'postgres': {
|
|
67
|
+
const { PostgresProvider } = await import('./providers/sql/postgres');
|
|
68
|
+
return new PostgresProvider(connection, options);
|
|
69
|
+
}
|
|
70
|
+
|
|
71
|
+
case 'mysql': {
|
|
72
|
+
const { MySQLProvider } = await import('./providers/sql/mysql');
|
|
73
|
+
return new MySQLProvider(connection, options);
|
|
74
|
+
}
|
|
75
|
+
|
|
76
|
+
case 'sqlite': {
|
|
77
|
+
const { SQLiteProvider } = await import('./providers/sql/sqlite');
|
|
78
|
+
return new SQLiteProvider(connection, options);
|
|
79
|
+
}
|
|
80
|
+
|
|
81
|
+
case 'oracle': {
|
|
82
|
+
const { OracleProvider } = await import('./providers/sql/oracle');
|
|
83
|
+
return new OracleProvider(connection, options);
|
|
84
|
+
}
|
|
85
|
+
|
|
86
|
+
case 'mssql': {
|
|
87
|
+
const { MSSQLProvider } = await import('./providers/sql/mssql');
|
|
88
|
+
return new MSSQLProvider(connection, options);
|
|
89
|
+
}
|
|
90
|
+
|
|
91
|
+
// Document Databases - dynamically imported
|
|
92
|
+
case 'mongodb': {
|
|
93
|
+
const { MongoDBProvider } = await import('./providers/document/mongodb');
|
|
94
|
+
return new MongoDBProvider(connection, options);
|
|
95
|
+
}
|
|
96
|
+
|
|
97
|
+
// Key-Value Stores - dynamically imported
|
|
98
|
+
case 'redis': {
|
|
99
|
+
const { RedisProvider } = await import('./providers/keyvalue/redis');
|
|
100
|
+
return new RedisProvider(connection, options);
|
|
101
|
+
}
|
|
102
|
+
|
|
103
|
+
default:
|
|
104
|
+
throw new DatabaseConfigError(
|
|
105
|
+
`Unknown database type: ${connection.type}. Supported types: postgres, mysql, sqlite, oracle, mssql, mongodb, redis`,
|
|
106
|
+
connection.type
|
|
107
|
+
);
|
|
108
|
+
}
|
|
109
|
+
}
|
|
110
|
+
|
|
111
|
+
// ============================================================================
|
|
112
|
+
// Provider Cache (for connection reuse)
|
|
113
|
+
// ============================================================================
|
|
114
|
+
|
|
115
|
+
interface CachedProvider {
|
|
116
|
+
provider: DatabaseProvider;
|
|
117
|
+
lastUsed: number;
|
|
118
|
+
}
|
|
119
|
+
|
|
120
|
+
const providerCache = new Map<string, CachedProvider>();
|
|
121
|
+
|
|
122
|
+
/** Idle timeout: evict providers unused for 30 minutes */
|
|
123
|
+
const IDLE_TIMEOUT_MS = 30 * 60 * 1000;
|
|
124
|
+
/** Sweep interval: check for idle providers every 5 minutes */
|
|
125
|
+
const SWEEP_INTERVAL_MS = 5 * 60 * 1000;
|
|
126
|
+
|
|
127
|
+
let sweepTimer: ReturnType<typeof setInterval> | null = null;
|
|
128
|
+
|
|
129
|
+
/**
|
|
130
|
+
* Evict providers that have been idle longer than maxIdleMs.
|
|
131
|
+
* Called by the periodic sweep timer, but also exported for direct testing.
|
|
132
|
+
*
|
|
133
|
+
* @returns number of evicted providers
|
|
134
|
+
*/
|
|
135
|
+
export async function evictIdleProviders(maxIdleMs: number = IDLE_TIMEOUT_MS): Promise<number> {
|
|
136
|
+
const now = Date.now();
|
|
137
|
+
let evicted = 0;
|
|
138
|
+
|
|
139
|
+
for (const [id, entry] of providerCache) {
|
|
140
|
+
if (now - entry.lastUsed >= maxIdleMs) {
|
|
141
|
+
logger.info(`[DB] Evicting idle provider: ${id} (idle ${Math.round((now - entry.lastUsed) / 60000)}min)`);
|
|
142
|
+
try {
|
|
143
|
+
await entry.provider.disconnect();
|
|
144
|
+
} catch (error) {
|
|
145
|
+
logger.warn(`[DB] Error disconnecting idle provider ${id}`, { connectionId: id, error: String(error) });
|
|
146
|
+
}
|
|
147
|
+
providerCache.delete(id);
|
|
148
|
+
// Also close SSH tunnel
|
|
149
|
+
try {
|
|
150
|
+
await closeSSHTunnel(id);
|
|
151
|
+
} catch { /* ignore */ }
|
|
152
|
+
evicted++;
|
|
153
|
+
}
|
|
154
|
+
}
|
|
155
|
+
|
|
156
|
+
// Stop sweeping if cache is empty
|
|
157
|
+
if (providerCache.size === 0 && sweepTimer) {
|
|
158
|
+
clearInterval(sweepTimer);
|
|
159
|
+
sweepTimer = null;
|
|
160
|
+
}
|
|
161
|
+
|
|
162
|
+
return evicted;
|
|
163
|
+
}
|
|
164
|
+
|
|
165
|
+
function startIdleSweep(): void {
|
|
166
|
+
if (sweepTimer) return;
|
|
167
|
+
sweepTimer = setInterval(() => { evictIdleProviders(); }, SWEEP_INTERVAL_MS);
|
|
168
|
+
// Allow process to exit even if timer is running
|
|
169
|
+
if (sweepTimer && typeof sweepTimer === 'object' && 'unref' in sweepTimer) {
|
|
170
|
+
sweepTimer.unref();
|
|
171
|
+
}
|
|
172
|
+
}
|
|
173
|
+
|
|
174
|
+
/**
|
|
175
|
+
* Get or create a database provider with caching
|
|
176
|
+
* Useful for API routes to reuse connections
|
|
177
|
+
*
|
|
178
|
+
* @param connection - Database connection configuration
|
|
179
|
+
* @param options - Optional provider options
|
|
180
|
+
* @returns Cached or new DatabaseProvider instance
|
|
181
|
+
*/
|
|
182
|
+
export async function getOrCreateProvider(
|
|
183
|
+
connection: DatabaseConnection,
|
|
184
|
+
options: ProviderOptions = {}
|
|
185
|
+
): Promise<DatabaseProvider> {
|
|
186
|
+
const cacheKey = connection.id;
|
|
187
|
+
|
|
188
|
+
// Check cache
|
|
189
|
+
const cached = providerCache.get(cacheKey);
|
|
190
|
+
|
|
191
|
+
if (cached?.provider.isConnected()) {
|
|
192
|
+
cached.lastUsed = Date.now();
|
|
193
|
+
return cached.provider;
|
|
194
|
+
}
|
|
195
|
+
|
|
196
|
+
// If SSH tunnel is configured, create tunnel first and rewrite connection
|
|
197
|
+
let effectiveConnection = connection;
|
|
198
|
+
let tunnel: Awaited<ReturnType<typeof createSSHTunnel>> | null = null;
|
|
199
|
+
if (connection.sshTunnel?.enabled && connection.host && connection.port) {
|
|
200
|
+
tunnel = await createSSHTunnel(
|
|
201
|
+
connection.id,
|
|
202
|
+
connection.sshTunnel,
|
|
203
|
+
connection.host,
|
|
204
|
+
connection.port
|
|
205
|
+
);
|
|
206
|
+
// Rewrite connection to point to local tunnel endpoint
|
|
207
|
+
effectiveConnection = {
|
|
208
|
+
...connection,
|
|
209
|
+
host: tunnel.localHost,
|
|
210
|
+
port: tunnel.localPort,
|
|
211
|
+
};
|
|
212
|
+
}
|
|
213
|
+
|
|
214
|
+
// Create new provider (async - dynamically loads the provider module)
|
|
215
|
+
const provider = await createDatabaseProvider(effectiveConnection, options);
|
|
216
|
+
try {
|
|
217
|
+
await provider.connect();
|
|
218
|
+
} catch (error) {
|
|
219
|
+
// Clean up SSH tunnel if provider connect fails to prevent FD leak
|
|
220
|
+
if (tunnel) {
|
|
221
|
+
await tunnel.close().catch(() => {});
|
|
222
|
+
}
|
|
223
|
+
throw error;
|
|
224
|
+
}
|
|
225
|
+
|
|
226
|
+
// Cache it
|
|
227
|
+
providerCache.set(cacheKey, { provider, lastUsed: Date.now() });
|
|
228
|
+
|
|
229
|
+
// Start idle sweep if not already running
|
|
230
|
+
startIdleSweep();
|
|
231
|
+
|
|
232
|
+
return provider;
|
|
233
|
+
}
|
|
234
|
+
|
|
235
|
+
/**
|
|
236
|
+
* Remove a provider from cache and disconnect
|
|
237
|
+
*/
|
|
238
|
+
export async function removeProvider(connectionId: string): Promise<void> {
|
|
239
|
+
const cached = providerCache.get(connectionId);
|
|
240
|
+
|
|
241
|
+
if (cached) {
|
|
242
|
+
try {
|
|
243
|
+
await cached.provider.disconnect();
|
|
244
|
+
} catch (error) {
|
|
245
|
+
logger.warn(`Error disconnecting provider ${connectionId}`, { connectionId, error: String(error) });
|
|
246
|
+
}
|
|
247
|
+
providerCache.delete(connectionId);
|
|
248
|
+
}
|
|
249
|
+
|
|
250
|
+
// Close SSH tunnel if exists
|
|
251
|
+
try {
|
|
252
|
+
await closeSSHTunnel(connectionId);
|
|
253
|
+
} catch (error) {
|
|
254
|
+
logger.warn(`Error closing SSH tunnel for ${connectionId}`, { connectionId, error: String(error) });
|
|
255
|
+
}
|
|
256
|
+
}
|
|
257
|
+
|
|
258
|
+
/**
|
|
259
|
+
* Clear all cached providers
|
|
260
|
+
*/
|
|
261
|
+
export async function clearProviderCache(): Promise<void> {
|
|
262
|
+
// Stop idle sweep
|
|
263
|
+
if (sweepTimer) {
|
|
264
|
+
clearInterval(sweepTimer);
|
|
265
|
+
sweepTimer = null;
|
|
266
|
+
}
|
|
267
|
+
|
|
268
|
+
const disconnectPromises: Promise<void>[] = [];
|
|
269
|
+
|
|
270
|
+
for (const [id, entry] of providerCache) {
|
|
271
|
+
disconnectPromises.push(
|
|
272
|
+
entry.provider.disconnect().catch((error) => {
|
|
273
|
+
console.error(`[DB] Error disconnecting provider ${id}:`, error);
|
|
274
|
+
})
|
|
275
|
+
);
|
|
276
|
+
}
|
|
277
|
+
|
|
278
|
+
await Promise.all(disconnectPromises);
|
|
279
|
+
providerCache.clear();
|
|
280
|
+
}
|
|
281
|
+
|
|
282
|
+
/**
|
|
283
|
+
* Get cache statistics
|
|
284
|
+
*/
|
|
285
|
+
export function getProviderCacheStats(): { size: number; connections: string[] } {
|
|
286
|
+
return {
|
|
287
|
+
size: providerCache.size,
|
|
288
|
+
connections: Array.from(providerCache.keys()),
|
|
289
|
+
};
|
|
290
|
+
}
|
|
291
|
+
|
|
292
|
+
// ============================================================================
|
|
293
|
+
// Graceful Shutdown
|
|
294
|
+
// ============================================================================
|
|
295
|
+
|
|
296
|
+
let shutdownRegistered = false;
|
|
297
|
+
|
|
298
|
+
/**
|
|
299
|
+
* Register process signal handlers for graceful shutdown.
|
|
300
|
+
* Safe to call multiple times — handlers are only registered once.
|
|
301
|
+
*/
|
|
302
|
+
export function registerShutdownHandlers(): void {
|
|
303
|
+
if (shutdownRegistered) return;
|
|
304
|
+
shutdownRegistered = true;
|
|
305
|
+
|
|
306
|
+
const shutdown = async (signal: string) => {
|
|
307
|
+
logger.info(`[DB] Received ${signal}, closing all database connections...`);
|
|
308
|
+
try {
|
|
309
|
+
await clearProviderCache();
|
|
310
|
+
logger.info('[DB] All database connections closed gracefully');
|
|
311
|
+
} catch (error) {
|
|
312
|
+
logger.error('[DB] Error during graceful shutdown', { error: String(error) });
|
|
313
|
+
}
|
|
314
|
+
process.exit(0);
|
|
315
|
+
};
|
|
316
|
+
|
|
317
|
+
process.on('SIGTERM', () => shutdown('SIGTERM'));
|
|
318
|
+
process.on('SIGINT', () => shutdown('SIGINT'));
|
|
319
|
+
}
|
|
320
|
+
|
|
321
|
+
// Auto-register on server-side (not during tests)
|
|
322
|
+
if (typeof process !== 'undefined' && process.env.NODE_ENV !== 'test') {
|
|
323
|
+
registerShutdownHandlers();
|
|
324
|
+
}
|