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