@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,1362 @@
|
|
|
1
|
+
# Seed Connections Implementation Plan
|
|
2
|
+
|
|
3
|
+
> **For agentic workers:** REQUIRED SUB-SKILL: Use superpowers:subagent-driven-development (recommended) or superpowers:executing-plans to implement this plan task-by-task. Steps use checkbox (`- [ ]`) syntax for tracking.
|
|
4
|
+
|
|
5
|
+
**Goal:** Enable pre-configured database connections via YAML/JSON config file with role-based access control and hybrid managed/unmanaged model.
|
|
6
|
+
|
|
7
|
+
**Architecture:** Dedicated `src/lib/seed/` module reads a volume-mounted config file at runtime (TTL-cached), resolves `${ENV_VAR}` credentials from `process.env`, filters by user role, and serves managed connections via a new API endpoint. A shared `resolveConnection()` utility is injected into 13 existing DB API routes to handle `seed:` prefixed connection IDs server-side. Client hooks merge managed connections with user connections, sending `connectionId` instead of full credentials for managed connections.
|
|
8
|
+
|
|
9
|
+
**Tech Stack:** TypeScript, Zod v4 (validation — project uses `^4.1.12`), `yaml` npm package (YAML parsing), Next.js API routes, existing JWT auth (`jose`)
|
|
10
|
+
|
|
11
|
+
**Spec:** `docs/superpowers/specs/2026-03-25-seed-connections-design.md`
|
|
12
|
+
|
|
13
|
+
**Important notes:**
|
|
14
|
+
- Project uses **Zod v4** (`^4.1.12`). All schema code uses v4 API (`.check()` instead of `.refine()` for some patterns, `z.object()` still supports `.strict()`). Verify Zod v4 compatibility at each step.
|
|
15
|
+
- **`disconnect/route.ts` is EXCLUDED** from `resolveConnection()` injection — it already accepts `connectionId` as a cache key for provider teardown, not connection establishment. The `seed:X` prefixed IDs flow naturally because `resolveConnection()` sets `id = "seed:X"` which becomes the cache key.
|
|
16
|
+
- **`POST /api/db/health`** (connection-level health check) IS included as an affected route.
|
|
17
|
+
- `pool-stats/route.ts` and `provider-meta/route.ts` are both **POST** routes, not GET.
|
|
18
|
+
|
|
19
|
+
---
|
|
20
|
+
|
|
21
|
+
## File Map
|
|
22
|
+
|
|
23
|
+
### New Files
|
|
24
|
+
|
|
25
|
+
| File | Responsibility |
|
|
26
|
+
|------|---------------|
|
|
27
|
+
| `src/lib/seed/types.ts` | Zod v4 schemas + TS types: `SeedConfig`, `SeedConnection`, `SeedDefaults`, `ManagedConnection` |
|
|
28
|
+
| `src/lib/seed/config-loader.ts` | Read YAML/JSON from disk, validate with Zod, TTL cache |
|
|
29
|
+
| `src/lib/seed/credential-resolver.ts` | Resolve `${VAR}` patterns from `process.env`, per-connection error isolation |
|
|
30
|
+
| `src/lib/seed/connection-filter.ts` | Merge defaults, filter by role, map to `ManagedConnection` |
|
|
31
|
+
| `src/lib/seed/resolve-connection.ts` | Shared utility for all API routes: detect `seed:` prefix, resolve full credentials, verify role |
|
|
32
|
+
| `src/lib/seed/index.ts` | Barrel export: `getManagedConnections(roles)` + `getSeedConnectionById()` + `getSeedConnectionByIdUnfiltered()` |
|
|
33
|
+
| `src/hooks/use-connection-payload.ts` | Shared helper: `buildConnectionPayload(conn)` — returns `{ connectionId }` or `{ connection }` based on `managed` flag |
|
|
34
|
+
| `src/app/api/connections/managed/route.ts` | `GET /api/connections/managed` — auth + role filter + credential stripping |
|
|
35
|
+
| `charts/libredb-studio/templates/seed-configmap.yaml` | Helm ConfigMap for seed config |
|
|
36
|
+
| `tests/fixtures/seed-connections/valid-config.yaml` | Full valid config fixture |
|
|
37
|
+
| `tests/fixtures/seed-connections/valid-config.json` | Same config in JSON format (for format detection test) |
|
|
38
|
+
| `tests/fixtures/seed-connections/minimal-config.yaml` | Minimum required fields only |
|
|
39
|
+
| `tests/fixtures/seed-connections/invalid-config.yaml` | Validation failure cases |
|
|
40
|
+
| `tests/fixtures/seed-connections/mixed-credentials.yaml` | Some `${VAR}`, some plaintext |
|
|
41
|
+
| `tests/fixtures/seed-connections/multi-role-config.yaml` | Different roles per connection |
|
|
42
|
+
| `tests/unit/seed/types.test.ts` | Zod schema validation tests |
|
|
43
|
+
| `tests/unit/seed/config-loader.test.ts` | File read, parse, cache, error handling |
|
|
44
|
+
| `tests/unit/seed/credential-resolver.test.ts` | Env var resolution, skip, warn |
|
|
45
|
+
| `tests/unit/seed/connection-filter.test.ts` | Role filter, defaults merge, mapping |
|
|
46
|
+
| `tests/unit/seed/index.test.ts` | Orchestrator + getSeedConnectionById tests |
|
|
47
|
+
| `tests/unit/seed/resolve-connection.test.ts` | Seed prefix detection, role check, fallback |
|
|
48
|
+
| `tests/api/seed/managed-route.test.ts` | API endpoint auth, filter, strip, errors |
|
|
49
|
+
| `tests/integration/seed/seed-pipeline.test.ts` | Full pipeline + multi-route resolution |
|
|
50
|
+
|
|
51
|
+
**Not in this plan (future task):** `e2e/seed-connections.spec.ts` — Playwright E2E test for managed connections in sidebar. Requires running app with seed config, best handled as a separate task after core implementation is stable.
|
|
52
|
+
|
|
53
|
+
### Modified Files
|
|
54
|
+
|
|
55
|
+
| File | Change |
|
|
56
|
+
|------|--------|
|
|
57
|
+
| `src/lib/types.ts:42-61` | Add `managed?: boolean`, `seedId?: string` to `DatabaseConnection` |
|
|
58
|
+
| `src/lib/audit.ts` | Add `'managed_connection'` to `AuditEventType` |
|
|
59
|
+
| `src/app/api/db/query/route.ts` | Import `resolveConnection`, use before `getOrCreateProvider` |
|
|
60
|
+
| `src/app/api/db/schema/route.ts` | Same pattern (also change body parsing to `req.json()`) |
|
|
61
|
+
| `src/app/api/db/multi-query/route.ts` | Same pattern |
|
|
62
|
+
| `src/app/api/db/transaction/route.ts` | Same pattern |
|
|
63
|
+
| `src/app/api/db/cancel/route.ts` | Same pattern |
|
|
64
|
+
| `src/app/api/db/maintenance/route.ts` | Same pattern |
|
|
65
|
+
| `src/app/api/db/monitoring/route.ts` | Same pattern |
|
|
66
|
+
| `src/app/api/db/pool-stats/route.ts` | Same pattern (POST route) |
|
|
67
|
+
| `src/app/api/db/profile/route.ts` | Same pattern |
|
|
68
|
+
| `src/app/api/db/provider-meta/route.ts` | Same pattern (POST route) |
|
|
69
|
+
| `src/app/api/db/test-connection/route.ts` | Same pattern |
|
|
70
|
+
| `src/app/api/db/schema-snapshot/route.ts` | Same pattern |
|
|
71
|
+
| `src/app/api/db/health/route.ts` | Same pattern (POST connection health check) |
|
|
72
|
+
| `src/hooks/use-connection-manager.ts` | Fetch managed connections, merge with user connections, update `fetchSchema` |
|
|
73
|
+
| `src/hooks/use-query-execution.ts` | Use `buildConnectionPayload()` at all 5 fetch sites |
|
|
74
|
+
| `src/hooks/use-transaction-control.ts` | Use `buildConnectionPayload()` |
|
|
75
|
+
| `src/components/sidebar/ConnectionItem.tsx:82-106` | Lock icon for managed, hide edit/delete |
|
|
76
|
+
| `charts/libredb-studio/values.yaml` | Add `seedConnections` section |
|
|
77
|
+
| `charts/libredb-studio/values.schema.json` | Add `seedConnections` schema |
|
|
78
|
+
| `charts/libredb-studio/templates/deployment.yaml` | Volume mount + env vars |
|
|
79
|
+
| `docker-compose.yml` | Add seed config volume mount example |
|
|
80
|
+
| `.env.example` | Document new env vars |
|
|
81
|
+
|
|
82
|
+
**NOT modified:** `src/app/api/db/disconnect/route.ts` — already accepts `connectionId` as cache key, `seed:X` IDs work naturally.
|
|
83
|
+
|
|
84
|
+
---
|
|
85
|
+
|
|
86
|
+
## Task 1: Install `yaml` dependency + add test fixtures
|
|
87
|
+
|
|
88
|
+
**Files:**
|
|
89
|
+
- Modify: `package.json`
|
|
90
|
+
- Create: `tests/fixtures/seed-connections/valid-config.yaml`
|
|
91
|
+
- Create: `tests/fixtures/seed-connections/valid-config.json`
|
|
92
|
+
- Create: `tests/fixtures/seed-connections/minimal-config.yaml`
|
|
93
|
+
- Create: `tests/fixtures/seed-connections/invalid-config.yaml`
|
|
94
|
+
- Create: `tests/fixtures/seed-connections/mixed-credentials.yaml`
|
|
95
|
+
- Create: `tests/fixtures/seed-connections/multi-role-config.yaml`
|
|
96
|
+
|
|
97
|
+
- [ ] **Step 1: Install yaml package**
|
|
98
|
+
|
|
99
|
+
```bash
|
|
100
|
+
bun add yaml
|
|
101
|
+
```
|
|
102
|
+
|
|
103
|
+
- [ ] **Step 2: Create valid-config.yaml fixture**
|
|
104
|
+
|
|
105
|
+
```yaml
|
|
106
|
+
# tests/fixtures/seed-connections/valid-config.yaml
|
|
107
|
+
version: "1"
|
|
108
|
+
|
|
109
|
+
defaults:
|
|
110
|
+
managed: true
|
|
111
|
+
environment: production
|
|
112
|
+
|
|
113
|
+
connections:
|
|
114
|
+
- id: "test-postgres"
|
|
115
|
+
name: "Test PostgreSQL"
|
|
116
|
+
type: postgres
|
|
117
|
+
host: pg.internal
|
|
118
|
+
port: 5432
|
|
119
|
+
database: testdb
|
|
120
|
+
user: "testuser"
|
|
121
|
+
password: "${TEST_PG_PASSWORD}"
|
|
122
|
+
environment: production
|
|
123
|
+
group: "Backend"
|
|
124
|
+
roles: ["admin"]
|
|
125
|
+
managed: true
|
|
126
|
+
color: "#10B981"
|
|
127
|
+
|
|
128
|
+
- id: "test-mysql"
|
|
129
|
+
name: "Test MySQL"
|
|
130
|
+
type: mysql
|
|
131
|
+
host: mysql.internal
|
|
132
|
+
port: 3306
|
|
133
|
+
database: appdb
|
|
134
|
+
user: "devuser"
|
|
135
|
+
password: "${TEST_MYSQL_PASSWORD}"
|
|
136
|
+
environment: staging
|
|
137
|
+
group: "Backend"
|
|
138
|
+
roles: ["*"]
|
|
139
|
+
managed: false
|
|
140
|
+
|
|
141
|
+
- id: "test-mongo"
|
|
142
|
+
name: "Test MongoDB"
|
|
143
|
+
type: mongodb
|
|
144
|
+
connectionString: "${TEST_MONGO_URI}"
|
|
145
|
+
group: "Platform"
|
|
146
|
+
roles: ["admin"]
|
|
147
|
+
managed: true
|
|
148
|
+
|
|
149
|
+
- id: "test-redis"
|
|
150
|
+
name: "Test Redis"
|
|
151
|
+
type: redis
|
|
152
|
+
host: redis.internal
|
|
153
|
+
port: 6379
|
|
154
|
+
database: "0"
|
|
155
|
+
password: "${TEST_REDIS_PASSWORD}"
|
|
156
|
+
roles: ["*"]
|
|
157
|
+
managed: true
|
|
158
|
+
```
|
|
159
|
+
|
|
160
|
+
- [ ] **Step 3: Create valid-config.json fixture** (same data, JSON format)
|
|
161
|
+
|
|
162
|
+
```json
|
|
163
|
+
{
|
|
164
|
+
"version": "1",
|
|
165
|
+
"defaults": { "managed": true, "environment": "production" },
|
|
166
|
+
"connections": [
|
|
167
|
+
{
|
|
168
|
+
"id": "test-postgres",
|
|
169
|
+
"name": "Test PostgreSQL",
|
|
170
|
+
"type": "postgres",
|
|
171
|
+
"host": "pg.internal",
|
|
172
|
+
"port": 5432,
|
|
173
|
+
"password": "${TEST_PG_PASSWORD}",
|
|
174
|
+
"roles": ["admin"]
|
|
175
|
+
}
|
|
176
|
+
]
|
|
177
|
+
}
|
|
178
|
+
```
|
|
179
|
+
|
|
180
|
+
- [ ] **Step 4: Create minimal-config.yaml, invalid-config.yaml, mixed-credentials.yaml, multi-role-config.yaml**
|
|
181
|
+
|
|
182
|
+
(Same content as previous plan version — these fixtures are unchanged)
|
|
183
|
+
|
|
184
|
+
- [ ] **Step 5: Commit**
|
|
185
|
+
|
|
186
|
+
```bash
|
|
187
|
+
git add package.json bun.lockb tests/fixtures/seed-connections/
|
|
188
|
+
git commit -m "feat(seed): add yaml dependency and test fixtures for seed connections"
|
|
189
|
+
```
|
|
190
|
+
|
|
191
|
+
---
|
|
192
|
+
|
|
193
|
+
## Task 2: Types + Zod v4 Schemas (`src/lib/seed/types.ts`)
|
|
194
|
+
|
|
195
|
+
**Files:**
|
|
196
|
+
- Modify: `src/lib/types.ts:42-61`
|
|
197
|
+
- Create: `src/lib/seed/types.ts`
|
|
198
|
+
- Create: `tests/unit/seed/types.test.ts`
|
|
199
|
+
|
|
200
|
+
**Important:** Project uses Zod v4 (`^4.1.12`). Key v4 changes: `z.object()` still works, `.strict()` still works, `.safeParse()` returns `{ success, data, error }`, `.refine()` still works. Verify with `bun test` at each step.
|
|
201
|
+
|
|
202
|
+
- [ ] **Step 1: Add `managed` and `seedId` to DatabaseConnection**
|
|
203
|
+
|
|
204
|
+
In `src/lib/types.ts`, add two optional fields after `instanceName?` (line 60):
|
|
205
|
+
|
|
206
|
+
```typescript
|
|
207
|
+
managed?: boolean; // true = admin-controlled, read-only in UI
|
|
208
|
+
seedId?: string; // stable reference to seed config ID
|
|
209
|
+
```
|
|
210
|
+
|
|
211
|
+
- [ ] **Step 2: Write failing tests for Zod schemas**
|
|
212
|
+
|
|
213
|
+
Create `tests/unit/seed/types.test.ts` — same tests as previous plan, but **without `'prefer'` in SSLMode** and with Zod v4 API compatibility confirmed:
|
|
214
|
+
|
|
215
|
+
```typescript
|
|
216
|
+
import { describe, it, expect } from 'bun:test';
|
|
217
|
+
import {
|
|
218
|
+
SeedConnectionSchema,
|
|
219
|
+
SeedConfigSchema,
|
|
220
|
+
SeedDefaultsSchema,
|
|
221
|
+
} from '@/lib/seed/types';
|
|
222
|
+
|
|
223
|
+
describe('SeedConnectionSchema', () => {
|
|
224
|
+
const validConn = {
|
|
225
|
+
id: 'test-pg',
|
|
226
|
+
name: 'Test PG',
|
|
227
|
+
type: 'postgres',
|
|
228
|
+
host: 'localhost',
|
|
229
|
+
port: 5432,
|
|
230
|
+
roles: ['admin'],
|
|
231
|
+
};
|
|
232
|
+
|
|
233
|
+
it('accepts a valid connection', () => {
|
|
234
|
+
const result = SeedConnectionSchema.safeParse(validConn);
|
|
235
|
+
expect(result.success).toBe(true);
|
|
236
|
+
});
|
|
237
|
+
|
|
238
|
+
it('rejects invalid id format (uppercase)', () => {
|
|
239
|
+
const result = SeedConnectionSchema.safeParse({ ...validConn, id: 'INVALID' });
|
|
240
|
+
expect(result.success).toBe(false);
|
|
241
|
+
});
|
|
242
|
+
|
|
243
|
+
it('rejects empty name', () => {
|
|
244
|
+
const result = SeedConnectionSchema.safeParse({ ...validConn, name: '' });
|
|
245
|
+
expect(result.success).toBe(false);
|
|
246
|
+
});
|
|
247
|
+
|
|
248
|
+
it('rejects demo type', () => {
|
|
249
|
+
const result = SeedConnectionSchema.safeParse({ ...validConn, type: 'demo' });
|
|
250
|
+
expect(result.success).toBe(false);
|
|
251
|
+
});
|
|
252
|
+
|
|
253
|
+
it('rejects empty roles array', () => {
|
|
254
|
+
const result = SeedConnectionSchema.safeParse({ ...validConn, roles: [] });
|
|
255
|
+
expect(result.success).toBe(false);
|
|
256
|
+
});
|
|
257
|
+
|
|
258
|
+
it('accepts wildcard role', () => {
|
|
259
|
+
const result = SeedConnectionSchema.safeParse({ ...validConn, roles: ['*'] });
|
|
260
|
+
expect(result.success).toBe(true);
|
|
261
|
+
});
|
|
262
|
+
|
|
263
|
+
it('rejects unknown roles like data-team', () => {
|
|
264
|
+
const result = SeedConnectionSchema.safeParse({ ...validConn, roles: ['data-team'] });
|
|
265
|
+
expect(result.success).toBe(false);
|
|
266
|
+
});
|
|
267
|
+
|
|
268
|
+
it('accepts combined admin and user roles', () => {
|
|
269
|
+
const result = SeedConnectionSchema.safeParse({ ...validConn, roles: ['admin', 'user'] });
|
|
270
|
+
expect(result.success).toBe(true);
|
|
271
|
+
});
|
|
272
|
+
|
|
273
|
+
it('rejects invalid port range', () => {
|
|
274
|
+
const result = SeedConnectionSchema.safeParse({ ...validConn, port: 99999 });
|
|
275
|
+
expect(result.success).toBe(false);
|
|
276
|
+
});
|
|
277
|
+
|
|
278
|
+
it('accepts valid color hex', () => {
|
|
279
|
+
const result = SeedConnectionSchema.safeParse({ ...validConn, color: '#10B981' });
|
|
280
|
+
expect(result.success).toBe(true);
|
|
281
|
+
});
|
|
282
|
+
|
|
283
|
+
it('rejects invalid color format', () => {
|
|
284
|
+
const result = SeedConnectionSchema.safeParse({ ...validConn, color: 'red' });
|
|
285
|
+
expect(result.success).toBe(false);
|
|
286
|
+
});
|
|
287
|
+
|
|
288
|
+
it('accepts all 7 valid database types', () => {
|
|
289
|
+
for (const type of ['postgres', 'mysql', 'sqlite', 'mongodb', 'redis', 'oracle', 'mssql']) {
|
|
290
|
+
const result = SeedConnectionSchema.safeParse({ ...validConn, type });
|
|
291
|
+
expect(result.success).toBe(true);
|
|
292
|
+
}
|
|
293
|
+
});
|
|
294
|
+
});
|
|
295
|
+
|
|
296
|
+
describe('SeedConfigSchema', () => {
|
|
297
|
+
it('accepts valid config with version 1', () => {
|
|
298
|
+
const result = SeedConfigSchema.safeParse({
|
|
299
|
+
version: '1',
|
|
300
|
+
connections: [{ id: 'a', name: 'A', type: 'postgres', host: 'h', roles: ['*'] }],
|
|
301
|
+
});
|
|
302
|
+
expect(result.success).toBe(true);
|
|
303
|
+
});
|
|
304
|
+
|
|
305
|
+
it('rejects version 2', () => {
|
|
306
|
+
const result = SeedConfigSchema.safeParse({
|
|
307
|
+
version: '2',
|
|
308
|
+
connections: [{ id: 'a', name: 'A', type: 'postgres', host: 'h', roles: ['*'] }],
|
|
309
|
+
});
|
|
310
|
+
expect(result.success).toBe(false);
|
|
311
|
+
});
|
|
312
|
+
|
|
313
|
+
it('rejects duplicate connection IDs', () => {
|
|
314
|
+
const result = SeedConfigSchema.safeParse({
|
|
315
|
+
version: '1',
|
|
316
|
+
connections: [
|
|
317
|
+
{ id: 'dup', name: 'A', type: 'postgres', host: 'h', roles: ['*'] },
|
|
318
|
+
{ id: 'dup', name: 'B', type: 'mysql', host: 'h', roles: ['*'] },
|
|
319
|
+
],
|
|
320
|
+
});
|
|
321
|
+
expect(result.success).toBe(false);
|
|
322
|
+
});
|
|
323
|
+
|
|
324
|
+
it('rejects empty connections array', () => {
|
|
325
|
+
const result = SeedConfigSchema.safeParse({ version: '1', connections: [] });
|
|
326
|
+
expect(result.success).toBe(false);
|
|
327
|
+
});
|
|
328
|
+
});
|
|
329
|
+
|
|
330
|
+
describe('SeedDefaultsSchema', () => {
|
|
331
|
+
it('accepts valid ssl config with mode require', () => {
|
|
332
|
+
const result = SeedDefaultsSchema.safeParse({
|
|
333
|
+
ssl: { mode: 'require', rejectUnauthorized: true },
|
|
334
|
+
});
|
|
335
|
+
expect(result.success).toBe(true);
|
|
336
|
+
});
|
|
337
|
+
|
|
338
|
+
it('rejects ssl mode prefer (not in SSLMode type)', () => {
|
|
339
|
+
const result = SeedDefaultsSchema.safeParse({
|
|
340
|
+
ssl: { mode: 'prefer' },
|
|
341
|
+
});
|
|
342
|
+
expect(result.success).toBe(false);
|
|
343
|
+
});
|
|
344
|
+
|
|
345
|
+
it('rejects invalid environment', () => {
|
|
346
|
+
const result = SeedDefaultsSchema.safeParse({ environment: 'unknown' });
|
|
347
|
+
expect(result.success).toBe(false);
|
|
348
|
+
});
|
|
349
|
+
});
|
|
350
|
+
```
|
|
351
|
+
|
|
352
|
+
- [ ] **Step 3: Run tests to verify they fail**
|
|
353
|
+
|
|
354
|
+
```bash
|
|
355
|
+
bun test tests/unit/seed/types.test.ts
|
|
356
|
+
```
|
|
357
|
+
|
|
358
|
+
Expected: FAIL — `@/lib/seed/types` does not exist yet
|
|
359
|
+
|
|
360
|
+
- [ ] **Step 4: Implement types.ts**
|
|
361
|
+
|
|
362
|
+
Create `src/lib/seed/types.ts`:
|
|
363
|
+
|
|
364
|
+
```typescript
|
|
365
|
+
import { z } from 'zod';
|
|
366
|
+
import type { DatabaseConnection } from '@/lib/types';
|
|
367
|
+
|
|
368
|
+
// SSLMode matches src/lib/types.ts line 21 — NO 'prefer'
|
|
369
|
+
const SSLModeSchema = z.enum(['disable', 'require', 'verify-ca', 'verify-full']);
|
|
370
|
+
|
|
371
|
+
const SSLConfigSchema = z.object({
|
|
372
|
+
mode: SSLModeSchema.optional(),
|
|
373
|
+
rejectUnauthorized: z.boolean().optional(),
|
|
374
|
+
caCert: z.string().optional(),
|
|
375
|
+
clientCert: z.string().optional(),
|
|
376
|
+
clientKey: z.string().optional(),
|
|
377
|
+
}).optional();
|
|
378
|
+
|
|
379
|
+
const ConnectionEnvironmentSchema = z.enum([
|
|
380
|
+
'production', 'staging', 'development', 'local', 'other',
|
|
381
|
+
]);
|
|
382
|
+
|
|
383
|
+
// Allowed roles in current iteration (matches JWT role: 'admin' | 'user' + wildcard)
|
|
384
|
+
const AllowedRoleSchema = z.enum(['*', 'admin', 'user']);
|
|
385
|
+
|
|
386
|
+
const SeedDatabaseType = z.enum([
|
|
387
|
+
'postgres', 'mysql', 'sqlite', 'mongodb', 'redis', 'oracle', 'mssql',
|
|
388
|
+
]);
|
|
389
|
+
|
|
390
|
+
export const SeedDefaultsSchema = z.object({
|
|
391
|
+
managed: z.boolean().optional(),
|
|
392
|
+
environment: ConnectionEnvironmentSchema.optional(),
|
|
393
|
+
ssl: SSLConfigSchema,
|
|
394
|
+
});
|
|
395
|
+
|
|
396
|
+
export const SeedConnectionSchema = z.object({
|
|
397
|
+
id: z.string().min(1).max(64).regex(/^[a-z0-9-]+$/, 'ID must be lowercase alphanumeric with hyphens'),
|
|
398
|
+
name: z.string().min(1).max(128),
|
|
399
|
+
type: SeedDatabaseType,
|
|
400
|
+
host: z.string().optional(),
|
|
401
|
+
port: z.number().int().min(1).max(65535).optional(),
|
|
402
|
+
database: z.string().optional(),
|
|
403
|
+
user: z.string().optional(),
|
|
404
|
+
password: z.string().optional(),
|
|
405
|
+
connectionString: z.string().optional(),
|
|
406
|
+
environment: ConnectionEnvironmentSchema.optional(),
|
|
407
|
+
group: z.string().max(64).optional(),
|
|
408
|
+
color: z.string().regex(/^#[0-9A-Fa-f]{6}$/).optional(),
|
|
409
|
+
roles: z.array(AllowedRoleSchema).min(1, 'At least one role is required'),
|
|
410
|
+
managed: z.boolean().optional(),
|
|
411
|
+
ssl: SSLConfigSchema,
|
|
412
|
+
serviceName: z.string().optional(),
|
|
413
|
+
instanceName: z.string().optional(),
|
|
414
|
+
});
|
|
415
|
+
|
|
416
|
+
export const SeedConfigSchema = z.object({
|
|
417
|
+
version: z.literal('1'),
|
|
418
|
+
defaults: SeedDefaultsSchema.optional(),
|
|
419
|
+
connections: z.array(SeedConnectionSchema).min(1, 'At least one connection is required'),
|
|
420
|
+
}).refine(
|
|
421
|
+
(cfg) => new Set(cfg.connections.map((c) => c.id)).size === cfg.connections.length,
|
|
422
|
+
{ message: 'Connection IDs must be unique' },
|
|
423
|
+
);
|
|
424
|
+
|
|
425
|
+
export type SeedConnection = z.infer<typeof SeedConnectionSchema>;
|
|
426
|
+
export type SeedDefaults = z.infer<typeof SeedDefaultsSchema>;
|
|
427
|
+
export type SeedConfig = z.infer<typeof SeedConfigSchema>;
|
|
428
|
+
|
|
429
|
+
export interface ManagedConnection extends DatabaseConnection {
|
|
430
|
+
managed: boolean;
|
|
431
|
+
roles: string[];
|
|
432
|
+
seedId: string;
|
|
433
|
+
}
|
|
434
|
+
```
|
|
435
|
+
|
|
436
|
+
- [ ] **Step 5: Run tests to verify they pass**
|
|
437
|
+
|
|
438
|
+
```bash
|
|
439
|
+
bun test tests/unit/seed/types.test.ts
|
|
440
|
+
```
|
|
441
|
+
|
|
442
|
+
Expected: All PASS. If Zod v4 API differs, adjust accordingly.
|
|
443
|
+
|
|
444
|
+
- [ ] **Step 6: Commit**
|
|
445
|
+
|
|
446
|
+
```bash
|
|
447
|
+
git add src/lib/types.ts src/lib/seed/types.ts tests/unit/seed/types.test.ts
|
|
448
|
+
git commit -m "feat(seed): add Zod v4 schemas and types for seed connections"
|
|
449
|
+
```
|
|
450
|
+
|
|
451
|
+
---
|
|
452
|
+
|
|
453
|
+
## Task 3: Config Loader (`src/lib/seed/config-loader.ts`)
|
|
454
|
+
|
|
455
|
+
**Files:**
|
|
456
|
+
- Create: `src/lib/seed/config-loader.ts`
|
|
457
|
+
- Create: `tests/unit/seed/config-loader.test.ts`
|
|
458
|
+
|
|
459
|
+
- [ ] **Step 1: Write failing tests**
|
|
460
|
+
|
|
461
|
+
Create `tests/unit/seed/config-loader.test.ts`:
|
|
462
|
+
|
|
463
|
+
```typescript
|
|
464
|
+
import { describe, it, expect, beforeEach, afterEach } from 'bun:test';
|
|
465
|
+
import path from 'path';
|
|
466
|
+
import { loadConfig, resetCache } from '@/lib/seed/config-loader';
|
|
467
|
+
|
|
468
|
+
const FIXTURES = path.resolve(__dirname, '../../../fixtures/seed-connections');
|
|
469
|
+
|
|
470
|
+
describe('config-loader', () => {
|
|
471
|
+
beforeEach(() => {
|
|
472
|
+
resetCache();
|
|
473
|
+
});
|
|
474
|
+
|
|
475
|
+
afterEach(() => {
|
|
476
|
+
delete process.env.SEED_CONFIG_PATH;
|
|
477
|
+
delete process.env.SEED_CACHE_TTL_MS;
|
|
478
|
+
});
|
|
479
|
+
|
|
480
|
+
it('loads and parses valid YAML config', async () => {
|
|
481
|
+
process.env.SEED_CONFIG_PATH = path.join(FIXTURES, 'valid-config.yaml');
|
|
482
|
+
const config = await loadConfig();
|
|
483
|
+
expect(config).not.toBeNull();
|
|
484
|
+
expect(config!.version).toBe('1');
|
|
485
|
+
expect(config!.connections).toHaveLength(4);
|
|
486
|
+
expect(config!.connections[0].id).toBe('test-postgres');
|
|
487
|
+
});
|
|
488
|
+
|
|
489
|
+
it('loads and parses valid JSON config', async () => {
|
|
490
|
+
process.env.SEED_CONFIG_PATH = path.join(FIXTURES, 'valid-config.json');
|
|
491
|
+
const config = await loadConfig();
|
|
492
|
+
expect(config).not.toBeNull();
|
|
493
|
+
expect(config!.version).toBe('1');
|
|
494
|
+
expect(config!.connections).toHaveLength(1);
|
|
495
|
+
});
|
|
496
|
+
|
|
497
|
+
it('returns null when config file does not exist', async () => {
|
|
498
|
+
process.env.SEED_CONFIG_PATH = '/nonexistent/path/config.yaml';
|
|
499
|
+
const config = await loadConfig();
|
|
500
|
+
expect(config).toBeNull();
|
|
501
|
+
});
|
|
502
|
+
|
|
503
|
+
it('throws on invalid YAML (validation fails)', async () => {
|
|
504
|
+
process.env.SEED_CONFIG_PATH = path.join(FIXTURES, 'invalid-config.yaml');
|
|
505
|
+
await expect(loadConfig()).rejects.toThrow();
|
|
506
|
+
});
|
|
507
|
+
|
|
508
|
+
it('uses default path when SEED_CONFIG_PATH not set', async () => {
|
|
509
|
+
const config = await loadConfig();
|
|
510
|
+
expect(config).toBeNull();
|
|
511
|
+
});
|
|
512
|
+
|
|
513
|
+
it('caches result within TTL', async () => {
|
|
514
|
+
process.env.SEED_CONFIG_PATH = path.join(FIXTURES, 'valid-config.yaml');
|
|
515
|
+
process.env.SEED_CACHE_TTL_MS = '60000';
|
|
516
|
+
const config1 = await loadConfig();
|
|
517
|
+
const config2 = await loadConfig();
|
|
518
|
+
expect(config1).toBe(config2); // same reference
|
|
519
|
+
});
|
|
520
|
+
|
|
521
|
+
it('reloads after cache reset', async () => {
|
|
522
|
+
process.env.SEED_CONFIG_PATH = path.join(FIXTURES, 'valid-config.yaml');
|
|
523
|
+
const config1 = await loadConfig();
|
|
524
|
+
resetCache();
|
|
525
|
+
const config2 = await loadConfig();
|
|
526
|
+
expect(config1).not.toBe(config2); // different reference
|
|
527
|
+
expect(config1!.connections).toHaveLength(config2!.connections.length);
|
|
528
|
+
});
|
|
529
|
+
|
|
530
|
+
it('loads minimal config with only required fields', async () => {
|
|
531
|
+
process.env.SEED_CONFIG_PATH = path.join(FIXTURES, 'minimal-config.yaml');
|
|
532
|
+
const config = await loadConfig();
|
|
533
|
+
expect(config).not.toBeNull();
|
|
534
|
+
expect(config!.connections).toHaveLength(1);
|
|
535
|
+
});
|
|
536
|
+
});
|
|
537
|
+
```
|
|
538
|
+
|
|
539
|
+
- [ ] **Step 2: Run tests to verify they fail**
|
|
540
|
+
|
|
541
|
+
```bash
|
|
542
|
+
bun test tests/unit/seed/config-loader.test.ts
|
|
543
|
+
```
|
|
544
|
+
|
|
545
|
+
- [ ] **Step 3: Implement config-loader.ts**
|
|
546
|
+
|
|
547
|
+
Create `src/lib/seed/config-loader.ts` — same implementation as before (readFile → parse YAML/JSON → Zod validate → TTL cache).
|
|
548
|
+
|
|
549
|
+
- [ ] **Step 4: Run tests to verify they pass**
|
|
550
|
+
|
|
551
|
+
```bash
|
|
552
|
+
bun test tests/unit/seed/config-loader.test.ts
|
|
553
|
+
```
|
|
554
|
+
|
|
555
|
+
- [ ] **Step 5: Commit**
|
|
556
|
+
|
|
557
|
+
```bash
|
|
558
|
+
git add src/lib/seed/config-loader.ts tests/unit/seed/config-loader.test.ts
|
|
559
|
+
git commit -m "feat(seed): implement config loader with YAML/JSON parsing and TTL cache"
|
|
560
|
+
```
|
|
561
|
+
|
|
562
|
+
---
|
|
563
|
+
|
|
564
|
+
## Task 4: Credential Resolver (`src/lib/seed/credential-resolver.ts`)
|
|
565
|
+
|
|
566
|
+
**Files:**
|
|
567
|
+
- Create: `src/lib/seed/credential-resolver.ts`
|
|
568
|
+
- Create: `tests/unit/seed/credential-resolver.test.ts`
|
|
569
|
+
|
|
570
|
+
- [ ] **Step 1: Write failing tests** (same as before)
|
|
571
|
+
- [ ] **Step 2: Run tests to verify they fail**
|
|
572
|
+
- [ ] **Step 3: Implement credential-resolver.ts** (same as before)
|
|
573
|
+
- [ ] **Step 4: Run tests to verify they pass**
|
|
574
|
+
- [ ] **Step 5: Commit**
|
|
575
|
+
|
|
576
|
+
```bash
|
|
577
|
+
git add src/lib/seed/credential-resolver.ts tests/unit/seed/credential-resolver.test.ts
|
|
578
|
+
git commit -m "feat(seed): implement credential resolver with env var injection"
|
|
579
|
+
```
|
|
580
|
+
|
|
581
|
+
---
|
|
582
|
+
|
|
583
|
+
## Task 5: Connection Filter (`src/lib/seed/connection-filter.ts`)
|
|
584
|
+
|
|
585
|
+
**Files:**
|
|
586
|
+
- Create: `src/lib/seed/connection-filter.ts`
|
|
587
|
+
- Create: `tests/unit/seed/connection-filter.test.ts`
|
|
588
|
+
|
|
589
|
+
- [ ] **Step 1: Write failing tests** (same as before)
|
|
590
|
+
- [ ] **Step 2: Run tests to verify they fail**
|
|
591
|
+
- [ ] **Step 3: Implement connection-filter.ts** (same as before)
|
|
592
|
+
- [ ] **Step 4: Run tests to verify they pass**
|
|
593
|
+
- [ ] **Step 5: Commit**
|
|
594
|
+
|
|
595
|
+
```bash
|
|
596
|
+
git add src/lib/seed/connection-filter.ts tests/unit/seed/connection-filter.test.ts
|
|
597
|
+
git commit -m "feat(seed): implement connection filter with role matching and defaults merge"
|
|
598
|
+
```
|
|
599
|
+
|
|
600
|
+
---
|
|
601
|
+
|
|
602
|
+
## Task 6: Barrel Export + Orchestrator (`src/lib/seed/index.ts`) + Tests
|
|
603
|
+
|
|
604
|
+
**Files:**
|
|
605
|
+
- Create: `src/lib/seed/index.ts`
|
|
606
|
+
- Create: `tests/unit/seed/index.test.ts`
|
|
607
|
+
|
|
608
|
+
- [ ] **Step 1: Write failing tests**
|
|
609
|
+
|
|
610
|
+
Create `tests/unit/seed/index.test.ts`:
|
|
611
|
+
|
|
612
|
+
```typescript
|
|
613
|
+
import { describe, it, expect, beforeEach, afterEach } from 'bun:test';
|
|
614
|
+
import path from 'path';
|
|
615
|
+
import {
|
|
616
|
+
getManagedConnections,
|
|
617
|
+
getSeedConnectionById,
|
|
618
|
+
getSeedConnectionByIdUnfiltered,
|
|
619
|
+
resetCache,
|
|
620
|
+
} from '@/lib/seed';
|
|
621
|
+
import { resetPlaintextWarnings } from '@/lib/seed/credential-resolver';
|
|
622
|
+
|
|
623
|
+
const FIXTURES = path.resolve(__dirname, '../../../fixtures/seed-connections');
|
|
624
|
+
|
|
625
|
+
describe('seed/index orchestrator', () => {
|
|
626
|
+
beforeEach(() => {
|
|
627
|
+
resetCache();
|
|
628
|
+
resetPlaintextWarnings();
|
|
629
|
+
process.env.SEED_CONFIG_PATH = path.join(FIXTURES, 'multi-role-config.yaml');
|
|
630
|
+
process.env.ADMIN_PG_PASS = 'admin-secret';
|
|
631
|
+
process.env.USER_MYSQL_PASS = 'user-secret';
|
|
632
|
+
process.env.SHARED_PG_PASS = 'shared-secret';
|
|
633
|
+
process.env.BOTH_PG_PASS = 'both-secret';
|
|
634
|
+
});
|
|
635
|
+
|
|
636
|
+
afterEach(() => {
|
|
637
|
+
delete process.env.SEED_CONFIG_PATH;
|
|
638
|
+
delete process.env.ADMIN_PG_PASS;
|
|
639
|
+
delete process.env.USER_MYSQL_PASS;
|
|
640
|
+
delete process.env.SHARED_PG_PASS;
|
|
641
|
+
delete process.env.BOTH_PG_PASS;
|
|
642
|
+
});
|
|
643
|
+
|
|
644
|
+
it('getManagedConnections returns role-filtered connections', async () => {
|
|
645
|
+
const adminConns = await getManagedConnections(['admin']);
|
|
646
|
+
expect(adminConns.length).toBeGreaterThanOrEqual(3); // admin-only, everyone, admin-and-user
|
|
647
|
+
|
|
648
|
+
const userConns = await getManagedConnections(['user']);
|
|
649
|
+
const userIds = userConns.map((c) => c.seedId);
|
|
650
|
+
expect(userIds).toContain('everyone');
|
|
651
|
+
expect(userIds).toContain('user-only');
|
|
652
|
+
expect(userIds).not.toContain('admin-only');
|
|
653
|
+
});
|
|
654
|
+
|
|
655
|
+
it('getSeedConnectionById returns connection with role check', async () => {
|
|
656
|
+
const conn = await getSeedConnectionById('everyone', ['user']);
|
|
657
|
+
expect(conn).not.toBeNull();
|
|
658
|
+
expect(conn!.seedId).toBe('everyone');
|
|
659
|
+
expect(conn!.password).toBe('shared-secret');
|
|
660
|
+
});
|
|
661
|
+
|
|
662
|
+
it('getSeedConnectionById returns null when role mismatches', async () => {
|
|
663
|
+
const conn = await getSeedConnectionById('admin-only', ['user']);
|
|
664
|
+
expect(conn).toBeNull();
|
|
665
|
+
});
|
|
666
|
+
|
|
667
|
+
it('getSeedConnectionByIdUnfiltered returns connection regardless of role', async () => {
|
|
668
|
+
const conn = await getSeedConnectionByIdUnfiltered('admin-only');
|
|
669
|
+
expect(conn).not.toBeNull();
|
|
670
|
+
expect(conn!.seedId).toBe('admin-only');
|
|
671
|
+
});
|
|
672
|
+
|
|
673
|
+
it('getSeedConnectionByIdUnfiltered returns null for nonexistent ID', async () => {
|
|
674
|
+
const conn = await getSeedConnectionByIdUnfiltered('nonexistent');
|
|
675
|
+
expect(conn).toBeNull();
|
|
676
|
+
});
|
|
677
|
+
|
|
678
|
+
it('returns empty array when config file missing', async () => {
|
|
679
|
+
process.env.SEED_CONFIG_PATH = '/nonexistent.yaml';
|
|
680
|
+
resetCache();
|
|
681
|
+
const conns = await getManagedConnections(['admin']);
|
|
682
|
+
expect(conns).toHaveLength(0);
|
|
683
|
+
});
|
|
684
|
+
});
|
|
685
|
+
```
|
|
686
|
+
|
|
687
|
+
- [ ] **Step 2: Run tests to verify they fail**
|
|
688
|
+
|
|
689
|
+
```bash
|
|
690
|
+
bun test tests/unit/seed/index.test.ts
|
|
691
|
+
```
|
|
692
|
+
|
|
693
|
+
- [ ] **Step 3: Implement index.ts**
|
|
694
|
+
|
|
695
|
+
Create `src/lib/seed/index.ts`:
|
|
696
|
+
|
|
697
|
+
```typescript
|
|
698
|
+
import { loadConfig, resetCache } from './config-loader';
|
|
699
|
+
import { resolveAllCredentials } from './credential-resolver';
|
|
700
|
+
import { filterByRoles, mergeDefaults } from './connection-filter';
|
|
701
|
+
import type { ManagedConnection } from './types';
|
|
702
|
+
|
|
703
|
+
export type { ManagedConnection, SeedConfig, SeedConnection, SeedDefaults } from './types';
|
|
704
|
+
export { SeedConfigSchema, SeedConnectionSchema, SeedDefaultsSchema } from './types';
|
|
705
|
+
export { resetCache } from './config-loader';
|
|
706
|
+
export { resetPlaintextWarnings } from './credential-resolver';
|
|
707
|
+
|
|
708
|
+
async function loadAndResolve(): Promise<ManagedConnection[]> {
|
|
709
|
+
const config = await loadConfig();
|
|
710
|
+
if (!config) return [];
|
|
711
|
+
|
|
712
|
+
const withDefaults = config.connections.map((conn) =>
|
|
713
|
+
mergeDefaults(conn, config.defaults),
|
|
714
|
+
);
|
|
715
|
+
|
|
716
|
+
const resolved = resolveAllCredentials(withDefaults);
|
|
717
|
+
// Return all resolved connections (unfiltered) for internal use
|
|
718
|
+
return filterByRoles(resolved, ['*', 'admin', 'user']);
|
|
719
|
+
}
|
|
720
|
+
|
|
721
|
+
export async function getManagedConnections(roles: string[]): Promise<ManagedConnection[]> {
|
|
722
|
+
const config = await loadConfig();
|
|
723
|
+
if (!config) return [];
|
|
724
|
+
|
|
725
|
+
const withDefaults = config.connections.map((conn) =>
|
|
726
|
+
mergeDefaults(conn, config.defaults),
|
|
727
|
+
);
|
|
728
|
+
|
|
729
|
+
const resolved = resolveAllCredentials(withDefaults);
|
|
730
|
+
return filterByRoles(resolved, roles);
|
|
731
|
+
}
|
|
732
|
+
|
|
733
|
+
export async function getSeedConnectionById(
|
|
734
|
+
seedId: string,
|
|
735
|
+
roles: string[],
|
|
736
|
+
): Promise<ManagedConnection | null> {
|
|
737
|
+
const all = await getManagedConnections(roles);
|
|
738
|
+
return all.find((c) => c.seedId === seedId) ?? null;
|
|
739
|
+
}
|
|
740
|
+
|
|
741
|
+
/**
|
|
742
|
+
* Get seed connection by ID WITHOUT role filtering.
|
|
743
|
+
* Used only for 403-vs-404 differentiation in resolveConnection().
|
|
744
|
+
*/
|
|
745
|
+
export async function getSeedConnectionByIdUnfiltered(
|
|
746
|
+
seedId: string,
|
|
747
|
+
): Promise<ManagedConnection | null> {
|
|
748
|
+
const all = await loadAndResolve();
|
|
749
|
+
return all.find((c) => c.seedId === seedId) ?? null;
|
|
750
|
+
}
|
|
751
|
+
```
|
|
752
|
+
|
|
753
|
+
- [ ] **Step 4: Run tests to verify they pass**
|
|
754
|
+
|
|
755
|
+
```bash
|
|
756
|
+
bun test tests/unit/seed/index.test.ts
|
|
757
|
+
```
|
|
758
|
+
|
|
759
|
+
- [ ] **Step 5: Run all seed unit tests**
|
|
760
|
+
|
|
761
|
+
```bash
|
|
762
|
+
bun test tests/unit/seed/
|
|
763
|
+
```
|
|
764
|
+
|
|
765
|
+
Expected: All PASS
|
|
766
|
+
|
|
767
|
+
- [ ] **Step 6: Commit**
|
|
768
|
+
|
|
769
|
+
```bash
|
|
770
|
+
git add src/lib/seed/index.ts tests/unit/seed/index.test.ts
|
|
771
|
+
git commit -m "feat(seed): add barrel export with orchestrator and unfiltered lookup"
|
|
772
|
+
```
|
|
773
|
+
|
|
774
|
+
---
|
|
775
|
+
|
|
776
|
+
## Task 7: Resolve Connection Utility (`src/lib/seed/resolve-connection.ts`)
|
|
777
|
+
|
|
778
|
+
**Files:**
|
|
779
|
+
- Modify: `src/lib/audit.ts` (add `'managed_connection'` event type)
|
|
780
|
+
- Create: `src/lib/seed/resolve-connection.ts`
|
|
781
|
+
- Create: `tests/unit/seed/resolve-connection.test.ts`
|
|
782
|
+
|
|
783
|
+
- [ ] **Step 1: Add managed_connection to AuditEventType**
|
|
784
|
+
|
|
785
|
+
In `src/lib/audit.ts`, add to the `AuditEventType` union:
|
|
786
|
+
|
|
787
|
+
```typescript
|
|
788
|
+
export type AuditEventType =
|
|
789
|
+
| 'maintenance'
|
|
790
|
+
| 'kill_session'
|
|
791
|
+
| 'masking_config'
|
|
792
|
+
| 'threshold_config'
|
|
793
|
+
| 'connection_test'
|
|
794
|
+
| 'query_execution'
|
|
795
|
+
| 'managed_connection'; // NEW
|
|
796
|
+
```
|
|
797
|
+
|
|
798
|
+
- [ ] **Step 2: Write failing tests**
|
|
799
|
+
|
|
800
|
+
Create `tests/unit/seed/resolve-connection.test.ts`:
|
|
801
|
+
|
|
802
|
+
```typescript
|
|
803
|
+
import { describe, it, expect, beforeEach, afterEach } from 'bun:test';
|
|
804
|
+
import path from 'path';
|
|
805
|
+
import type { DatabaseConnection } from '@/lib/types';
|
|
806
|
+
|
|
807
|
+
const FIXTURES = path.resolve(__dirname, '../../../fixtures/seed-connections');
|
|
808
|
+
process.env.SEED_CONFIG_PATH = path.join(FIXTURES, 'multi-role-config.yaml');
|
|
809
|
+
process.env.ADMIN_PG_PASS = 'admin-secret';
|
|
810
|
+
process.env.USER_MYSQL_PASS = 'user-secret';
|
|
811
|
+
process.env.SHARED_PG_PASS = 'shared-secret';
|
|
812
|
+
process.env.BOTH_PG_PASS = 'both-secret';
|
|
813
|
+
|
|
814
|
+
import { resolveConnection, SeedConnectionError } from '@/lib/seed/resolve-connection';
|
|
815
|
+
import { resetCache } from '@/lib/seed/config-loader';
|
|
816
|
+
|
|
817
|
+
describe('resolve-connection', () => {
|
|
818
|
+
beforeEach(() => {
|
|
819
|
+
resetCache();
|
|
820
|
+
});
|
|
821
|
+
|
|
822
|
+
it('returns connection object as-is when no connectionId', async () => {
|
|
823
|
+
const conn: DatabaseConnection = {
|
|
824
|
+
id: 'user-conn', name: 'User DB', type: 'postgres', host: 'localhost', createdAt: new Date(),
|
|
825
|
+
};
|
|
826
|
+
const result = await resolveConnection({ connection: conn }, { role: 'user', username: 'test' });
|
|
827
|
+
expect(result.id).toBe('user-conn');
|
|
828
|
+
});
|
|
829
|
+
|
|
830
|
+
it('resolves seed connection by connectionId', async () => {
|
|
831
|
+
const result = await resolveConnection(
|
|
832
|
+
{ connectionId: 'seed:everyone' },
|
|
833
|
+
{ role: 'user', username: 'test' },
|
|
834
|
+
);
|
|
835
|
+
expect(result.id).toBe('seed:everyone');
|
|
836
|
+
expect(result.password).toBe('shared-secret');
|
|
837
|
+
});
|
|
838
|
+
|
|
839
|
+
it('throws 403 when role does not have access', async () => {
|
|
840
|
+
try {
|
|
841
|
+
await resolveConnection({ connectionId: 'seed:admin-only' }, { role: 'user', username: 'test' });
|
|
842
|
+
expect(true).toBe(false); // should not reach
|
|
843
|
+
} catch (err) {
|
|
844
|
+
expect(err).toBeInstanceOf(SeedConnectionError);
|
|
845
|
+
expect((err as SeedConnectionError).statusCode).toBe(403);
|
|
846
|
+
}
|
|
847
|
+
});
|
|
848
|
+
|
|
849
|
+
it('throws 404 when seed connection does not exist', async () => {
|
|
850
|
+
try {
|
|
851
|
+
await resolveConnection({ connectionId: 'seed:nonexistent' }, { role: 'admin', username: 'test' });
|
|
852
|
+
expect(true).toBe(false);
|
|
853
|
+
} catch (err) {
|
|
854
|
+
expect(err).toBeInstanceOf(SeedConnectionError);
|
|
855
|
+
expect((err as SeedConnectionError).statusCode).toBe(404);
|
|
856
|
+
}
|
|
857
|
+
});
|
|
858
|
+
|
|
859
|
+
it('admin can access admin-only connections', async () => {
|
|
860
|
+
const result = await resolveConnection(
|
|
861
|
+
{ connectionId: 'seed:admin-only' },
|
|
862
|
+
{ role: 'admin', username: 'test' },
|
|
863
|
+
);
|
|
864
|
+
expect(result.password).toBe('admin-secret');
|
|
865
|
+
});
|
|
866
|
+
|
|
867
|
+
it('throws 400 when neither connection nor connectionId', async () => {
|
|
868
|
+
try {
|
|
869
|
+
await resolveConnection({}, { role: 'admin', username: 'test' });
|
|
870
|
+
expect(true).toBe(false);
|
|
871
|
+
} catch (err) {
|
|
872
|
+
expect(err).toBeInstanceOf(SeedConnectionError);
|
|
873
|
+
expect((err as SeedConnectionError).statusCode).toBe(400);
|
|
874
|
+
}
|
|
875
|
+
});
|
|
876
|
+
});
|
|
877
|
+
```
|
|
878
|
+
|
|
879
|
+
- [ ] **Step 3: Run tests to verify they fail**
|
|
880
|
+
- [ ] **Step 4: Implement resolve-connection.ts**
|
|
881
|
+
|
|
882
|
+
Create `src/lib/seed/resolve-connection.ts`:
|
|
883
|
+
|
|
884
|
+
```typescript
|
|
885
|
+
import type { DatabaseConnection } from '@/lib/types';
|
|
886
|
+
import { getSeedConnectionById, getSeedConnectionByIdUnfiltered } from './index';
|
|
887
|
+
import { logger } from '@/lib/logger';
|
|
888
|
+
|
|
889
|
+
export class SeedConnectionError extends Error {
|
|
890
|
+
constructor(message: string, public statusCode: number) {
|
|
891
|
+
super(message);
|
|
892
|
+
this.name = 'SeedConnectionError';
|
|
893
|
+
}
|
|
894
|
+
}
|
|
895
|
+
|
|
896
|
+
export async function resolveConnection(
|
|
897
|
+
body: { connection?: DatabaseConnection; connectionId?: string },
|
|
898
|
+
session: { role: string; username: string },
|
|
899
|
+
): Promise<DatabaseConnection> {
|
|
900
|
+
const { connection, connectionId } = body;
|
|
901
|
+
|
|
902
|
+
if (connection && !connectionId) {
|
|
903
|
+
return connection;
|
|
904
|
+
}
|
|
905
|
+
|
|
906
|
+
if (connectionId) {
|
|
907
|
+
if (!connectionId.startsWith('seed:')) {
|
|
908
|
+
throw new SeedConnectionError('Invalid connection ID format', 400);
|
|
909
|
+
}
|
|
910
|
+
|
|
911
|
+
const seedId = connectionId.slice(5);
|
|
912
|
+
const seedConn = await getSeedConnectionById(seedId, [session.role]);
|
|
913
|
+
|
|
914
|
+
if (!seedConn) {
|
|
915
|
+
// Differentiate 403 vs 404 using unfiltered lookup
|
|
916
|
+
const exists = await getSeedConnectionByIdUnfiltered(seedId);
|
|
917
|
+
if (exists) {
|
|
918
|
+
logger.warn('Seed connection access denied', {
|
|
919
|
+
route: 'seed/resolve-connection',
|
|
920
|
+
connectionId: seedId,
|
|
921
|
+
user: session.username,
|
|
922
|
+
role: session.role,
|
|
923
|
+
});
|
|
924
|
+
throw new SeedConnectionError(
|
|
925
|
+
`Access denied: connection "${seedId}" not available for role "${session.role}"`,
|
|
926
|
+
403,
|
|
927
|
+
);
|
|
928
|
+
}
|
|
929
|
+
throw new SeedConnectionError(`Seed connection "${seedId}" not found`, 404);
|
|
930
|
+
}
|
|
931
|
+
|
|
932
|
+
logger.debug('Resolved seed connection', {
|
|
933
|
+
route: 'seed/resolve-connection',
|
|
934
|
+
connectionId: seedId,
|
|
935
|
+
user: session.username,
|
|
936
|
+
});
|
|
937
|
+
|
|
938
|
+
return seedConn;
|
|
939
|
+
}
|
|
940
|
+
|
|
941
|
+
throw new SeedConnectionError('Either connection or connectionId is required', 400);
|
|
942
|
+
}
|
|
943
|
+
```
|
|
944
|
+
|
|
945
|
+
- [ ] **Step 5: Run tests to verify they pass**
|
|
946
|
+
- [ ] **Step 6: Commit**
|
|
947
|
+
|
|
948
|
+
```bash
|
|
949
|
+
git add src/lib/audit.ts src/lib/seed/resolve-connection.ts tests/unit/seed/resolve-connection.test.ts
|
|
950
|
+
git commit -m "feat(seed): implement resolveConnection with 403/404 differentiation and audit"
|
|
951
|
+
```
|
|
952
|
+
|
|
953
|
+
---
|
|
954
|
+
|
|
955
|
+
## Task 8: API Endpoint (`GET /api/connections/managed`)
|
|
956
|
+
|
|
957
|
+
**Files:**
|
|
958
|
+
- Create: `src/app/api/connections/managed/route.ts`
|
|
959
|
+
- Create: `tests/api/seed/managed-route.test.ts`
|
|
960
|
+
|
|
961
|
+
- [ ] **Step 1: Write failing tests** (same as before, with auth mock)
|
|
962
|
+
- [ ] **Step 2: Run tests to verify they fail**
|
|
963
|
+
- [ ] **Step 3: Implement the API route**
|
|
964
|
+
|
|
965
|
+
Create `src/app/api/connections/managed/route.ts`:
|
|
966
|
+
|
|
967
|
+
```typescript
|
|
968
|
+
import { NextResponse } from 'next/server';
|
|
969
|
+
import { getSession } from '@/lib/auth';
|
|
970
|
+
import { getManagedConnections } from '@/lib/seed';
|
|
971
|
+
import { logger } from '@/lib/logger';
|
|
972
|
+
|
|
973
|
+
export const dynamic = 'force-dynamic';
|
|
974
|
+
|
|
975
|
+
export async function GET() {
|
|
976
|
+
try {
|
|
977
|
+
const session = await getSession();
|
|
978
|
+
if (!session) {
|
|
979
|
+
return NextResponse.json({ error: 'Authentication required' }, { status: 401 });
|
|
980
|
+
}
|
|
981
|
+
|
|
982
|
+
const connections = await getManagedConnections([session.role]);
|
|
983
|
+
|
|
984
|
+
const sanitized = connections.map((conn) => {
|
|
985
|
+
if (conn.managed) {
|
|
986
|
+
const { password, connectionString, ...rest } = conn;
|
|
987
|
+
return rest;
|
|
988
|
+
}
|
|
989
|
+
return conn;
|
|
990
|
+
});
|
|
991
|
+
|
|
992
|
+
const cacheTTL = Number(process.env.SEED_CACHE_TTL_MS) || 60_000;
|
|
993
|
+
|
|
994
|
+
return NextResponse.json({ connections: sanitized, cacheHint: cacheTTL });
|
|
995
|
+
} catch (error) {
|
|
996
|
+
logger.error('Failed to load managed connections', {
|
|
997
|
+
route: 'GET /api/connections/managed',
|
|
998
|
+
error: (error as Error).message,
|
|
999
|
+
});
|
|
1000
|
+
return NextResponse.json({ error: 'Failed to load managed connections' }, { status: 500 });
|
|
1001
|
+
}
|
|
1002
|
+
}
|
|
1003
|
+
```
|
|
1004
|
+
|
|
1005
|
+
- [ ] **Step 4: Run tests to verify they pass**
|
|
1006
|
+
- [ ] **Step 5: Commit**
|
|
1007
|
+
|
|
1008
|
+
```bash
|
|
1009
|
+
git add src/app/api/connections/managed/route.ts tests/api/seed/managed-route.test.ts
|
|
1010
|
+
git commit -m "feat(seed): add GET /api/connections/managed endpoint"
|
|
1011
|
+
```
|
|
1012
|
+
|
|
1013
|
+
---
|
|
1014
|
+
|
|
1015
|
+
## Task 9: Integrate `resolveConnection` into all DB API routes
|
|
1016
|
+
|
|
1017
|
+
**Files:**
|
|
1018
|
+
- Modify: 13 routes in `src/app/api/db/` (all POST, **excluding** `disconnect/route.ts`)
|
|
1019
|
+
|
|
1020
|
+
**Note:** `disconnect/route.ts` is excluded — it already accepts `connectionId` as a cache key. The `seed:X` IDs flow naturally through the provider cache.
|
|
1021
|
+
|
|
1022
|
+
- [ ] **Step 1: Read each route to identify body extraction pattern**
|
|
1023
|
+
|
|
1024
|
+
All affected routes use one of:
|
|
1025
|
+
- Pattern A: `const { connection, ... } = await request.json()`
|
|
1026
|
+
- Pattern B: `const connection = JSON.parse(await request.text())` (schema route only)
|
|
1027
|
+
|
|
1028
|
+
- [ ] **Step 2: Modify each route with resolveConnection**
|
|
1029
|
+
|
|
1030
|
+
For each route, add at the top:
|
|
1031
|
+
|
|
1032
|
+
```typescript
|
|
1033
|
+
import { resolveConnection, SeedConnectionError } from '@/lib/seed/resolve-connection';
|
|
1034
|
+
import { getSession } from '@/lib/auth';
|
|
1035
|
+
```
|
|
1036
|
+
|
|
1037
|
+
Change body extraction:
|
|
1038
|
+
|
|
1039
|
+
```typescript
|
|
1040
|
+
const body = await request.json();
|
|
1041
|
+
const session = await getSession();
|
|
1042
|
+
if (!session) {
|
|
1043
|
+
return NextResponse.json({ error: 'Authentication required' }, { status: 401 });
|
|
1044
|
+
}
|
|
1045
|
+
const connection = await resolveConnection(body, session);
|
|
1046
|
+
```
|
|
1047
|
+
|
|
1048
|
+
Add to catch block:
|
|
1049
|
+
|
|
1050
|
+
```typescript
|
|
1051
|
+
if (error instanceof SeedConnectionError) {
|
|
1052
|
+
return NextResponse.json({ error: error.message }, { status: error.statusCode });
|
|
1053
|
+
}
|
|
1054
|
+
```
|
|
1055
|
+
|
|
1056
|
+
**Special case — `schema/route.ts`:** Currently uses `req.text()` + `JSON.parse()`. Change to `req.json()` for consistency. Client will send `{ connection: conn }` or `{ connectionId: "seed:X" }`.
|
|
1057
|
+
|
|
1058
|
+
**Special case — `maintenance/route.ts`:** Already has session check. Reuse existing session.
|
|
1059
|
+
|
|
1060
|
+
Apply to all 13 routes:
|
|
1061
|
+
1. `query/route.ts`
|
|
1062
|
+
2. `multi-query/route.ts`
|
|
1063
|
+
3. `schema/route.ts`
|
|
1064
|
+
4. `transaction/route.ts`
|
|
1065
|
+
5. `cancel/route.ts`
|
|
1066
|
+
6. `maintenance/route.ts`
|
|
1067
|
+
7. `monitoring/route.ts`
|
|
1068
|
+
8. `pool-stats/route.ts` (POST)
|
|
1069
|
+
9. `profile/route.ts`
|
|
1070
|
+
10. `provider-meta/route.ts` (POST)
|
|
1071
|
+
11. `test-connection/route.ts`
|
|
1072
|
+
12. `schema-snapshot/route.ts`
|
|
1073
|
+
13. `health/route.ts` (POST connection health check — needs auth added)
|
|
1074
|
+
|
|
1075
|
+
- [ ] **Step 3: Run all existing tests**
|
|
1076
|
+
|
|
1077
|
+
```bash
|
|
1078
|
+
bun run test
|
|
1079
|
+
```
|
|
1080
|
+
|
|
1081
|
+
Expected: All PASS
|
|
1082
|
+
|
|
1083
|
+
- [ ] **Step 4: Commit**
|
|
1084
|
+
|
|
1085
|
+
```bash
|
|
1086
|
+
git add src/app/api/db/
|
|
1087
|
+
git commit -m "feat(seed): integrate resolveConnection into all DB API routes"
|
|
1088
|
+
```
|
|
1089
|
+
|
|
1090
|
+
---
|
|
1091
|
+
|
|
1092
|
+
## Task 10: Shared client helper + useConnectionManager merge
|
|
1093
|
+
|
|
1094
|
+
**Files:**
|
|
1095
|
+
- Create: `src/hooks/use-connection-payload.ts`
|
|
1096
|
+
- Modify: `src/hooks/use-connection-manager.ts`
|
|
1097
|
+
|
|
1098
|
+
- [ ] **Step 1: Create shared helper**
|
|
1099
|
+
|
|
1100
|
+
Create `src/hooks/use-connection-payload.ts`:
|
|
1101
|
+
|
|
1102
|
+
```typescript
|
|
1103
|
+
import type { DatabaseConnection } from '@/lib/types';
|
|
1104
|
+
|
|
1105
|
+
/**
|
|
1106
|
+
* Builds the connection portion of an API request body.
|
|
1107
|
+
* For managed connections: sends { connectionId: "seed:X" } (no credentials).
|
|
1108
|
+
* For user connections: sends { connection: conn } (full object).
|
|
1109
|
+
*/
|
|
1110
|
+
export function buildConnectionPayload(
|
|
1111
|
+
conn: DatabaseConnection,
|
|
1112
|
+
): { connectionId: string } | { connection: DatabaseConnection } {
|
|
1113
|
+
if (conn.managed && conn.seedId) {
|
|
1114
|
+
return { connectionId: `seed:${conn.seedId}` };
|
|
1115
|
+
}
|
|
1116
|
+
return { connection: conn };
|
|
1117
|
+
}
|
|
1118
|
+
```
|
|
1119
|
+
|
|
1120
|
+
- [ ] **Step 2: Update useConnectionManager — add managed connection fetch + merge**
|
|
1121
|
+
|
|
1122
|
+
In `src/hooks/use-connection-manager.ts`, after the demo connection fetch block and before the `return` statement of the initialization effect:
|
|
1123
|
+
|
|
1124
|
+
```typescript
|
|
1125
|
+
// Fetch managed (seed) connections
|
|
1126
|
+
try {
|
|
1127
|
+
const managedRes = await fetch('/api/connections/managed');
|
|
1128
|
+
if (managedRes.ok) {
|
|
1129
|
+
const { connections: managedConns } = await managedRes.json();
|
|
1130
|
+
if (managedConns?.length > 0) {
|
|
1131
|
+
// ... merge logic as described in spec Section 4
|
|
1132
|
+
}
|
|
1133
|
+
}
|
|
1134
|
+
} catch {
|
|
1135
|
+
// Managed connections are optional
|
|
1136
|
+
}
|
|
1137
|
+
```
|
|
1138
|
+
|
|
1139
|
+
- [ ] **Step 3: Update fetchSchema to use buildConnectionPayload**
|
|
1140
|
+
|
|
1141
|
+
In `use-connection-manager.ts`, `fetchSchema` callback (line 27-30):
|
|
1142
|
+
|
|
1143
|
+
```typescript
|
|
1144
|
+
// Before:
|
|
1145
|
+
body: JSON.stringify(conn),
|
|
1146
|
+
|
|
1147
|
+
// After:
|
|
1148
|
+
import { buildConnectionPayload } from './use-connection-payload';
|
|
1149
|
+
body: JSON.stringify(buildConnectionPayload(conn)),
|
|
1150
|
+
```
|
|
1151
|
+
|
|
1152
|
+
**Important:** The schema route was updated in Task 9 to use `req.json()` + `resolveConnection()`. The client must now send `{ connection: conn }` (wrapped) or `{ connectionId: "seed:X" }`, NOT the bare `conn` object. `buildConnectionPayload()` handles both cases correctly.
|
|
1153
|
+
|
|
1154
|
+
- [ ] **Step 4: Update health pulse fetch** (line ~171):
|
|
1155
|
+
|
|
1156
|
+
```typescript
|
|
1157
|
+
body: JSON.stringify(buildConnectionPayload(conn)),
|
|
1158
|
+
```
|
|
1159
|
+
|
|
1160
|
+
- [ ] **Step 5: Run tests**
|
|
1161
|
+
|
|
1162
|
+
```bash
|
|
1163
|
+
bun run test:hooks
|
|
1164
|
+
```
|
|
1165
|
+
|
|
1166
|
+
- [ ] **Step 6: Commit**
|
|
1167
|
+
|
|
1168
|
+
```bash
|
|
1169
|
+
git add src/hooks/use-connection-payload.ts src/hooks/use-connection-manager.ts
|
|
1170
|
+
git commit -m "feat(seed): add managed connection merge to useConnectionManager"
|
|
1171
|
+
```
|
|
1172
|
+
|
|
1173
|
+
---
|
|
1174
|
+
|
|
1175
|
+
## Task 11: Client hooks — useQueryExecution + useTransactionControl
|
|
1176
|
+
|
|
1177
|
+
**Files:**
|
|
1178
|
+
- Modify: `src/hooks/use-query-execution.ts`
|
|
1179
|
+
- Modify: `src/hooks/use-transaction-control.ts`
|
|
1180
|
+
|
|
1181
|
+
- [ ] **Step 1: Update ALL 6 fetch calls in useQueryExecution**
|
|
1182
|
+
|
|
1183
|
+
Import helper and update each fetch site:
|
|
1184
|
+
|
|
1185
|
+
```typescript
|
|
1186
|
+
import { buildConnectionPayload } from './use-connection-payload';
|
|
1187
|
+
```
|
|
1188
|
+
|
|
1189
|
+
**Site 1 — Playground BEGIN** (line 150):
|
|
1190
|
+
```typescript
|
|
1191
|
+
body: JSON.stringify({ ...buildConnectionPayload(activeConnection), action: 'begin' }),
|
|
1192
|
+
```
|
|
1193
|
+
|
|
1194
|
+
**Site 2 — Main query** (line ~179):
|
|
1195
|
+
```typescript
|
|
1196
|
+
body: JSON.stringify({
|
|
1197
|
+
...buildConnectionPayload(activeConnection),
|
|
1198
|
+
...(useTransaction ? { action: 'query', sql, options } : { sql, options, ...(!useMultiQuery && { queryId }) }),
|
|
1199
|
+
}),
|
|
1200
|
+
```
|
|
1201
|
+
|
|
1202
|
+
**Site 3 — Background EXPLAIN query** (line ~198-202):
|
|
1203
|
+
```typescript
|
|
1204
|
+
body: JSON.stringify({
|
|
1205
|
+
...buildConnectionPayload(activeConnection),
|
|
1206
|
+
sql: explainSql,
|
|
1207
|
+
options: {},
|
|
1208
|
+
}),
|
|
1209
|
+
```
|
|
1210
|
+
|
|
1211
|
+
**Site 4 — Playground rollback success** (line ~357):
|
|
1212
|
+
```typescript
|
|
1213
|
+
body: JSON.stringify({ ...buildConnectionPayload(activeConnection), action: 'rollback' }),
|
|
1214
|
+
```
|
|
1215
|
+
|
|
1216
|
+
**Site 5 — Playground rollback error** (line ~382):
|
|
1217
|
+
```typescript
|
|
1218
|
+
body: JSON.stringify({ ...buildConnectionPayload(activeConnection), action: 'rollback' }),
|
|
1219
|
+
```
|
|
1220
|
+
|
|
1221
|
+
**Site 6 — Cancel query** (line ~433):
|
|
1222
|
+
```typescript
|
|
1223
|
+
body: JSON.stringify({
|
|
1224
|
+
...buildConnectionPayload(activeConnection),
|
|
1225
|
+
queryId: activeQueryIdRef.current,
|
|
1226
|
+
}),
|
|
1227
|
+
```
|
|
1228
|
+
|
|
1229
|
+
- [ ] **Step 2: Update useTransactionControl** (line 20-24):
|
|
1230
|
+
|
|
1231
|
+
```typescript
|
|
1232
|
+
import { buildConnectionPayload } from './use-connection-payload';
|
|
1233
|
+
|
|
1234
|
+
body: JSON.stringify({
|
|
1235
|
+
...buildConnectionPayload(activeConnection),
|
|
1236
|
+
action,
|
|
1237
|
+
}),
|
|
1238
|
+
```
|
|
1239
|
+
|
|
1240
|
+
- [ ] **Step 3: Run tests**
|
|
1241
|
+
|
|
1242
|
+
```bash
|
|
1243
|
+
bun run test:hooks
|
|
1244
|
+
```
|
|
1245
|
+
|
|
1246
|
+
- [ ] **Step 4: Commit**
|
|
1247
|
+
|
|
1248
|
+
```bash
|
|
1249
|
+
git add src/hooks/use-query-execution.ts src/hooks/use-transaction-control.ts
|
|
1250
|
+
git commit -m "feat(seed): send connectionId for managed connections in all hook fetch calls"
|
|
1251
|
+
```
|
|
1252
|
+
|
|
1253
|
+
---
|
|
1254
|
+
|
|
1255
|
+
## Task 12: UI — Lock icon + hide edit/delete for managed connections
|
|
1256
|
+
|
|
1257
|
+
**Files:**
|
|
1258
|
+
- Modify: `src/components/sidebar/ConnectionItem.tsx`
|
|
1259
|
+
|
|
1260
|
+
- [ ] **Step 1: Add lock icon and conditional buttons**
|
|
1261
|
+
|
|
1262
|
+
Import Lock icon, add managed lock indicator, wrap edit/delete with `!conn.managed` check.
|
|
1263
|
+
|
|
1264
|
+
- [ ] **Step 2: Run component tests**
|
|
1265
|
+
|
|
1266
|
+
```bash
|
|
1267
|
+
bun run test:components
|
|
1268
|
+
```
|
|
1269
|
+
|
|
1270
|
+
- [ ] **Step 3: Commit**
|
|
1271
|
+
|
|
1272
|
+
```bash
|
|
1273
|
+
git add src/components/sidebar/ConnectionItem.tsx
|
|
1274
|
+
git commit -m "feat(seed): add lock icon and hide edit/delete for managed connections"
|
|
1275
|
+
```
|
|
1276
|
+
|
|
1277
|
+
---
|
|
1278
|
+
|
|
1279
|
+
## Task 13: Integration Tests
|
|
1280
|
+
|
|
1281
|
+
**Files:**
|
|
1282
|
+
- Create: `tests/integration/seed/seed-pipeline.test.ts`
|
|
1283
|
+
|
|
1284
|
+
- [ ] **Step 1: Write integration tests** — full pipeline, partial failure, hot-reload, defaults merge, audit trail
|
|
1285
|
+
- [ ] **Step 2: Run integration tests**
|
|
1286
|
+
|
|
1287
|
+
```bash
|
|
1288
|
+
bun test tests/integration/seed/
|
|
1289
|
+
```
|
|
1290
|
+
|
|
1291
|
+
- [ ] **Step 3: Commit**
|
|
1292
|
+
|
|
1293
|
+
```bash
|
|
1294
|
+
git add tests/integration/seed/
|
|
1295
|
+
git commit -m "test(seed): add integration tests for full seed pipeline"
|
|
1296
|
+
```
|
|
1297
|
+
|
|
1298
|
+
---
|
|
1299
|
+
|
|
1300
|
+
## Task 14: Helm chart + Docker + env documentation
|
|
1301
|
+
|
|
1302
|
+
**Files:**
|
|
1303
|
+
- Create: `charts/libredb-studio/templates/seed-configmap.yaml`
|
|
1304
|
+
- Modify: `charts/libredb-studio/values.yaml`
|
|
1305
|
+
- Modify: `charts/libredb-studio/values.schema.json`
|
|
1306
|
+
- Modify: `charts/libredb-studio/templates/deployment.yaml`
|
|
1307
|
+
- Modify: `docker-compose.yml`
|
|
1308
|
+
- Modify: `.env.example`
|
|
1309
|
+
|
|
1310
|
+
- [ ] **Step 1: Create seed-configmap.yaml**
|
|
1311
|
+
- [ ] **Step 2: Add seedConnections to values.yaml**
|
|
1312
|
+
- [ ] **Step 3: Update values.schema.json**
|
|
1313
|
+
- [ ] **Step 4: Update deployment.yaml** (volume mount + env vars)
|
|
1314
|
+
- [ ] **Step 5: Update docker-compose.yml** (commented example)
|
|
1315
|
+
- [ ] **Step 6: Update .env.example** (new env vars)
|
|
1316
|
+
- [ ] **Step 7: Lint Helm chart**
|
|
1317
|
+
|
|
1318
|
+
```bash
|
|
1319
|
+
helm lint charts/libredb-studio --strict
|
|
1320
|
+
helm template test charts/libredb-studio --set secrets.jwtSecret=test-secret-32-chars-minimum-here --set secrets.adminPassword=test123 --set secrets.userPassword=test123 --set seedConnections.enabled=true
|
|
1321
|
+
```
|
|
1322
|
+
|
|
1323
|
+
- [ ] **Step 8: Commit**
|
|
1324
|
+
|
|
1325
|
+
```bash
|
|
1326
|
+
git add charts/ docker-compose.yml .env.example
|
|
1327
|
+
git commit -m "feat(seed): add Helm chart, Docker, and env documentation for seed connections"
|
|
1328
|
+
```
|
|
1329
|
+
|
|
1330
|
+
---
|
|
1331
|
+
|
|
1332
|
+
## Task 15: CI Verification
|
|
1333
|
+
|
|
1334
|
+
- [ ] **Step 1: Lint**
|
|
1335
|
+
|
|
1336
|
+
```bash
|
|
1337
|
+
bun run lint
|
|
1338
|
+
```
|
|
1339
|
+
|
|
1340
|
+
- [ ] **Step 2: Type check**
|
|
1341
|
+
|
|
1342
|
+
```bash
|
|
1343
|
+
bun run typecheck
|
|
1344
|
+
```
|
|
1345
|
+
|
|
1346
|
+
- [ ] **Step 3: Run all tests**
|
|
1347
|
+
|
|
1348
|
+
```bash
|
|
1349
|
+
bun run test
|
|
1350
|
+
```
|
|
1351
|
+
|
|
1352
|
+
- [ ] **Step 4: Build**
|
|
1353
|
+
|
|
1354
|
+
```bash
|
|
1355
|
+
bun run build
|
|
1356
|
+
```
|
|
1357
|
+
|
|
1358
|
+
- [ ] **Step 5: Fix any failures and commit**
|
|
1359
|
+
|
|
1360
|
+
```bash
|
|
1361
|
+
git log --oneline -15 # verify all seed commits
|
|
1362
|
+
```
|