# High-Level Architecture - LibreDB Studio
This document outlines the architectural patterns, tech stack, and system design for LibreDB Studio, a web-based SQL IDE for cloud-native teams.
## System Overview
LibreDB Studio is a hybrid, cloud-native database management tool that provides an IDE-like experience in the browser. It supports **11 database backends** via a Strategy Pattern abstraction: PostgreSQL, MySQL, SQLite, Oracle, SQL Server, MongoDB, Couchbase, ClickHouse, Apache Druid, Redis, LibreDB.
It runs in two modes: as a **standalone Next.js app** and as an **embedded npm package** (`@libredb/studio`) consumed by libredb-platform. See [§4.6](#46-workspace-abstraction-npm-package-embedding).
## 1. Core Tech Stack
| Layer | Technology |
|-------|-----------|
| Framework | Next.js 16 (App Router) with React 19 |
| Runtime | Bun / Node.js |
| Language | TypeScript (strict mode) |
| Styling | Tailwind CSS 4 + Shadcn/UI |
| Animations | Framer Motion v12 |
| SQL Editor | Monaco Editor |
| Data Grid | TanStack React Table + react-virtual |
| AI | Multi-model (Gemini, OpenAI, Ollama, Custom) |
| Auth | JWT (`jose`) + OIDC SSO (`openid-client`) |
| Charts | Recharts |
| Containerization | Docker (multi-stage Bun build) |
## 2. High-Level Architecture Diagram
```mermaid
graph TD
User((User)) -->|HTTPS| Frontend[Next.js Frontend
App Router + React 19]
Frontend -->|API Calls| API[Next.js API Routes
src/app/api]
subgraph "Application Core"
API -->|Auth| AuthLib[src/lib/auth.ts
JWT + OIDC]
API -->|Query| DBFactory[Provider Factory
src/lib/db/factory.ts]
API -->|AI| LLMFactory[LLM Factory
src/lib/llm/]
end
subgraph "Database Providers (Strategy Pattern)"
DBFactory --> SQL[SQL Providers]
DBFactory --> Document[Document Providers]
DBFactory --> KeyValue[Key-Value Providers]
SQL --> PG[(PostgreSQL)]
SQL --> MySQL[(MySQL)]
SQL --> SQLite[(SQLite)]
SQL --> Oracle[(Oracle)]
SQL --> MSSQL[(SQL Server)]
SQL --> ClickHouse[(ClickHouse)]
SQL --> Druid[(Apache Druid)]
Document --> MongoDB[(MongoDB)]
Document --> Couchbase[(Couchbase)]
KeyValue --> Redis[(Redis)]
end
subgraph "AI Providers (Strategy Pattern)"
LLMFactory --> Gemini[[Gemini]]
LLMFactory --> OpenAI[[OpenAI]]
LLMFactory --> Ollama[[Ollama]]
LLMFactory --> CustomLLM[[Custom]]
end
subgraph "Security"
AuthLib -->|Session| JWT[HTTP-Only JWT Cookies]
AuthLib -->|SSO| OIDC[OIDC Provider
Auth0 / Keycloak / Okta / Azure AD]
end
```
## 3. Database Provider Architecture
```mermaid
classDiagram
class BaseDatabaseProvider {
<>
+connect()
+disconnect()
+executeQuery()
+getSchema()
+getHealth()
+getCapabilities() ProviderCapabilities
+getLabels() ProviderLabels
+prepareQuery() PreparedQuery
}
class SQLBaseProvider {
<>
+beginTransaction()
+commitTransaction()
+rollbackTransaction()
+cancelQuery()
}
BaseDatabaseProvider <|-- SQLBaseProvider
BaseDatabaseProvider <|-- MongoDBProvider
BaseDatabaseProvider <|-- CouchbaseProvider
BaseDatabaseProvider <|-- RedisProvider
SQLBaseProvider <|-- PostgresProvider
SQLBaseProvider <|-- MySQLProvider
SQLBaseProvider <|-- SQLiteProvider
SQLBaseProvider <|-- OracleProvider
SQLBaseProvider <|-- MSSQLProvider
SQLBaseProvider <|-- ClickHouseProvider
SQLBaseProvider <|-- DruidProvider
```
Each provider implements:
- **`getCapabilities()`** - queryLanguage, supportsExplain, supportsCreateTable, maintenanceOperations, etc.
- **`getLabels()`** - entityName, selectAction, searchPlaceholder, etc. (drives all UI text)
- **`prepareQuery()`** - handles query limiting per-provider (SQL LIMIT injection vs MongoDB native)
Adding a new database type requires: **1 provider class** + **1 entry in `db-ui-config.ts`**.
`CouchbaseProvider` extends `BaseDatabaseProvider` even though SQL++ is a SQL dialect: SQL++ quotes identifiers with doubled backticks, which `escapeIdentifier()` produces for no existing type, so it owns its quoting and declares its SQL-ness through `queryLanguage: 'sql'` instead. Being reached over HTTP is **not** the reason — `ClickHouseProvider` and `DruidProvider` add no driver either, and both extend `SQLBaseProvider`, because double-quoted identifiers and `LIMIT n OFFSET m` are correct in both dialects. Each of the three is a directory rather than a single file, with its wire format behind a transport seam that provider logic never bypasses. See [`docs/providers/couchbase.md`](providers/couchbase.md), [`clickhouse.md`](providers/clickhouse.md) and [`druid.md`](providers/druid.md).
## 4. Key Architectural Patterns
### 4.1. Strategy Pattern (Database & LLM)
Both database and LLM layers use the Strategy Pattern with a factory:
- `src/lib/db/factory.ts` - Creates the correct database provider based on connection type
- `src/lib/llm/factory.ts` - Creates the correct LLM provider based on configuration
No `isMongoDB` / `=== 'mongodb'` checks outside provider classes. All behavior differences are driven through capabilities and labels.
### 4.2. Authentication Flow
```mermaid
sequenceDiagram
participant U as User
participant F as Frontend
participant A as API (/api/auth)
participant O as OIDC Provider
alt Local Auth
U->>F: Email + Password
F->>A: POST /api/auth/login
A->>F: Set HTTP-Only JWT Cookie
else OIDC SSO
U->>F: Click SSO Login
F->>O: Redirect (PKCE)
O->>F: Authorization Code
F->>A: GET /api/auth/oidc/callback
A->>O: Token Exchange
A->>F: Set HTTP-Only JWT Cookie
end
```
Controlled by `NEXT_PUBLIC_AUTH_PROVIDER` (`local` | `oidc`). Both flows result in the same JWT session cookie. Proxy (`src/proxy.ts`) enforces RBAC (admin vs user roles).
### 4.3. Multi-Statement Execution
`src/lib/sql/statement-splitter.ts` splits SQL input into individual statements, handling:
- String literals (single/double quotes)
- Block and line comments
- Dollar-quoting (PostgreSQL)
Multi-statement queries execute sequentially via `POST /api/db/multi-query`.
### 4.4. Storage Abstraction Layer
- **Write-through cache architecture**: localStorage (L1 cache) + optional server storage (L2 persistent)
- **Three storage modes** controlled by `STORAGE_PROVIDER` env var:
- `local` (default): Browser localStorage only, zero configuration
- `sqlite`: Server-side SQLite file via `better-sqlite3`
- `postgres`: Server-side PostgreSQL via `pg`
- **`useStorageSync` hook** in Studio.tsx: discovers mode at runtime via `/api/storage/config`, pulls on mount, pushes mutations (debounced 500ms)
- **Migration**: First login auto-migrates localStorage to server; `libredb_server_migrated` flag prevents re-migration
- **Graceful degradation**: If server unreachable, localStorage continues working
### 4.5. Client State Management
- **Storage module** (`src/lib/storage/`) for persistent data: connections, query history, saved queries, schema snapshots, chart configs, audit log, masking config, threshold config
- **React hooks** for UI state: tabs, active connection, execution status
- **Custom hooks** extracted from Studio.tsx: `useAuth`, `useConnectionManager`, `useTabManager`, `useTransactionControl`, `useQueryExecution`, `useInlineEditing`
### 4.6. Workspace Abstraction (npm package embedding)
Studio ships both as a standalone app and as the `@libredb/studio` npm package consumed by libredb-platform (built with `tsup` via `build:lib`).
- **`src/workspace/`** — `StudioWorkspace.tsx` is the embeddable shell. Its adapter hooks (`hooks/use-connection-adapter`, `hooks/use-query-adapter`) let the host (standalone or platform) supply connections and query execution, so the same UI runs in both contexts.
- **`src/exports/`** — barrel modules (`components.ts`, `providers.ts`, `workspace.ts`, `types.ts`) that define the package's public surface; `package.json` `exports`/`main`/`module` point at the tsup `dist/` output.
- Platform integration rules (Tailwind tokens, Lucide stroke widths, chunk scanning) live in `CLAUDE.md`.
### 4.7. Standalone Boot Flow (`src/instrumentation.ts`)
Next.js runs `register()` once per server worker, **only** when Studio boots its own server (never when `@libredb/studio` is imported by libredb-platform) and only on the Node.js runtime. On standalone boot it:
1. **Bootstraps missing auth env** (`src/lib/auth-bootstrap.ts`, #109). When `JWT_SECRET` / `ADMIN_PASSWORD` are absent they are generated once, persisted to `/auth-bootstrap.json` (mode `0600`), and injected into `process.env` before any secret reader runs; the admin password is printed once. Explicitly set env vars always win. Disable with `AUTH_BOOTSTRAP=off|false|0` (case-insensitive); an unrecognized value warns and stays on. In OIDC mode only the JWT secret is generated.
2. **Runs the auth-config preflight** (`src/lib/config/auth-preflight.ts`, #227). A `JWT_SECRET` that is set but shorter than 32 characters prints an operator-facing banner (length only, never the value) and exits with code 1. It runs *after* bootstrap so a generated secret is validated too. This is the one step that intentionally stops boot: `GET /api/db/health` is the Kubernetes livenessProbe and the Docker/PaaS health check, so signalling the failure there would restart the pod forever and hide the login screen's actionable 503; refusing to start costs nothing because a too-short secret can sign no session at all.
3. **Seeds the embedded LibreDB sample** (`src/lib/seed/libredb-sample.ts`). Unless `LIBREDB_EMBEDDED_SAMPLE=false`, it creates `/sample.libredb` (idempotently, atomic rename) and `GET /api/connections/managed` then advertises an editable, dismissable "Sample (LibreDB)" connection pointing at it.
4. **Seeds the embedded SQLite sample, asynchronously** (`src/lib/seed/sqlite-sample.ts`). Unless `SQLITE_EMBEDDED_SAMPLE=false`, it fires-and-forgets a copy of the vendored `seed-assets/sqlite/employee.db` template to `/sample-employees.db` (idempotent, atomic rename) — boot never waits. While the copy is in flight the managed-connections API advertises the seed id in `pendingSeeds`; `useConnectionManager` polls (1s, max 30) so "Sample (Employees)" appears without a page refresh.
Failures in the bootstrap and seeding steps are logged and swallowed — boot never breaks. The preflight in step 2 is the deliberate exception.
### 4.8. SQLite Driver Selection (`src/lib/db/providers/sql/sqlite-driver.ts`)
The SQLite **DB provider** is runtime-adaptive: it loads `bun:sqlite` under Bun and `node:sqlite` under plain Node (npx / brew / deb installs run `node server.js`). `LIBREDB_SQLITE_DRIVER=bun|node` forces a driver (used by tests). This is distinct from the **storage layer**, whose SQLite backend uses `better-sqlite3`.
## 5. Directory Structure
```
src/
├── app/ # Next.js App Router
│ ├── api/
│ │ ├── auth/ # Login/logout/me + OIDC (PKCE, callback)
│ │ ├── ai/ # chat, nl2sql, explain, query-safety, index-advisor, impact, describe-schema, autopilot
│ │ ├── db/ # Query, schema, health, maintenance, transactions
│ │ ├── storage/ # Storage sync API (config, CRUD, migrate)
│ │ ├── connections/ # managed/ — built-in (seeded) connections listing
│ │ └── admin/ # Fleet health, audit
│ ├── admin/ # Admin dashboard (RBAC protected) — layout.tsx renders the
│ │ │ # shell; one route per section, each independently
│ │ │ # linkable/refreshable. `/admin` redirects to the default
│ │ │ # section and maps legacy `?tab=` links (src/lib/admin-sections.ts)
│ │ ├── overview/ # Fleet health, quick actions
│ │ ├── operations/ # Maintenance operations
│ │ ├── monitoring/ # Embedded monitoring dashboard
│ │ ├── security/ # Data masking, access control
│ │ └── audit/ # Audit log
│ ├── monitoring/ # Monitoring dashboard page
│ └── login/ # Login page
├── components/
│ ├── Studio.tsx # Main application shell (standalone)
│ ├── QueryEditor.tsx # Monaco SQL editor wrapper
│ ├── ResultsGrid.tsx # Virtualized data grid
│ ├── SchemaDiagram.tsx # React Flow ERD viewer
│ ├── sidebar/ # ConnectionsList, ConnectionItem
│ ├── studio/ # StudioTabBar, QueryToolbar, BottomPanel
│ ├── results-grid/ # ResultCard, RowDetailSheet, StatsBar
│ ├── admin/ # AdminDashboard shell (5 section routes) + tabs/ panels
│ ├── monitoring/ # MonitoringDashboard + tabs
│ ├── schema-explorer/ # SchemaExplorer
│ └── ui/ # Shadcn/UI primitives
├── workspace/ # Embeddable shell (StudioWorkspace) + host adapter hooks
├── exports/ # Public npm-package barrel exports (tsup build:lib)
├── hooks/ # Custom React hooks
└── lib/
├── db/ # Database provider module
│ ├── providers/
│ │ ├── sql/ # postgres, mysql, sqlite (+ sqlite-driver runtime adapter), oracle, mssql, clickhouse/ (transport seam + SQL over HTTP), druid/ (transport seam + SQL over POST /druid/v2/sql)
│ │ ├── document/ # mongodb, couchbase/ (transport seam + SQL++ over REST)
│ │ ├── keyvalue/ # redis
│ │ └── embedded/ # libredb (built-in embedded provider for the sample connection)
│ ├── factory.ts # Provider factory
│ └── types.ts # Database types
├── llm/ # LLM provider module
├── editor/ # Monaco completions (SQL + MongoDB)
├── schema-diff/ # Diff engine + migration SQL generator
├── sql/ # Statement splitter, alias extractor
├── seed/ # Seed connections (config, filter, credential resolver) + libredb-sample seeding
├── config/ # auth-env.ts — single JWT_SECRET reader (auth.ts, proxy.ts, oidc.ts)
├── api/ # API error codes + schema-route helpers
├── ssh/ # SSH tunnel support
├── auth.ts # JWT utilities
├── auth-bootstrap.ts # Zero-config first-run auth bootstrap (runs in instrumentation)
├── oidc.ts # OIDC utilities
└── storage/ # Storage abstraction layer
├── index.ts # Barrel export
├── storage-facade.ts # Public sync API + CustomEvent dispatch
├── local-storage.ts # Pure localStorage CRUD
├── factory.ts # Env-based provider factory
└── providers/ # SQLite + PostgreSQL backends
```
## 6. Deployment
- **Docker / Helm**: Multi-stage Bun build with standalone Next.js output; these channels bind `0.0.0.0`. Canonical image `ghcr.io/libredb/libredb-studio`.
- **Native channels** (`bin/studio.js` npx launcher, Homebrew tap, `.deb`/`.rpm`, Snap, standalone tarballs; sources under `bin/` and `packaging/`): local-first, bind `127.0.0.1` by default unless `--host`/`HOSTNAME` opts in. The npx launcher ships as a pure library and downloads the SHA256-verified standalone server tarball from GitHub Releases. Full matrix and per-channel details in [`docs/DISTRIBUTION.md`](DISTRIBUTION.md).
- **Health Check**: `GET /api/db/health`
- **Stateless API**: API routes are stateless, suitable for horizontal scaling
- **Environment**: Configured via `.env.local` (see CLAUDE.md for full variable list). Missing auth secrets are generated on first standalone boot — see [§4.7](#47-standalone-boot-flow-srcinstrumentationts).