Documentation Index
Fetch the complete documentation index at: https://mintlify.com/felixdotgo/querybox/llms.txt
Use this file to discover all available pages before exploring further.
QueryBox’s backend is organized into three core services that handle connection management, plugin execution, and credential storage. All services are written in Go and bound to the Wails application for frontend access.
Service Architecture
ConnectionService
Location: services/connection.go
Wails Binding: github.com/felixdotgo/querybox/services.ConnectionService
Manages the lifecycle of database connections, including CRUD operations, credential storage, and event emission.
Data Model
type Connection struct {
ID string `json:"id"` // UUID v4
Name string `json:"name"` // User-defined display name
DriverType string `json:"driver_type"` // Plugin name (e.g., "mysql", "postgres")
CredentialKey string `json:"credential_key"` // Keyring key ("connection:<uuid>")
CreatedAt string `json:"created_at"` // RFC3339Nano UTC
UpdatedAt string `json:"updated_at"` // RFC3339Nano UTC
}
Note: The credential_key is not the actual credential—it’s a reference to the secret stored in CredManager.
Public API
ListConnections
ListConnections(ctx context.Context) ([]Connection, error)
Returns all stored connections ordered by creation time (newest first).
Frontend Usage:
import { ListConnections } from '@/bindings/github.com/felixdotgo/querybox/services/connectionservice'
const connections = await ListConnections()
Implementation: services/connection.go:177
CreateConnection
CreateConnection(ctx context.Context, name string, driverType string, credential string) (Connection, error)
Creates a new connection record.
Parameters:
name: Display name for the connection
driverType: Plugin identifier (must match a discovered plugin)
credential: JSON-encoded credential blob (structure defined by plugin’s auth forms)
Flow:
- Generate UUID for connection ID
- Derive credential key:
"connection:<uuid>"
- Store credential via
CredManager.Store(key, credential)
- Insert metadata into
connections.db
- Emit
connection:created event with full Connection object
Frontend Usage:
import { CreateConnection } from '@/bindings/github.com/felixdotgo/querybox/services/connectionservice'
const cred = JSON.stringify({ host: 'localhost', port: 5432, user: 'admin', password: 'secret' })
const conn = await CreateConnection('My Database', 'postgres', cred)
Implementation: services/connection.go:233
Emits: connection:created event → Frontend updates connection list reactively (no re-fetch needed)
GetConnection
GetConnection(ctx context.Context, id string) (Connection, error)
Retrieves a single connection by ID.
Implementation: services/connection.go:208
GetCredential
GetCredential(ctx context.Context, id string) (string, error)
Retrieves the raw credential JSON for a connection.
Security Note: This method returns sensitive data. The frontend uses it only when needed (e.g., executing queries, testing connections). Credentials are never cached in frontend state.
Flow:
- Look up connection metadata to get
credential_key
- Call
CredManager.Get(credential_key)
- Return raw credential string
Frontend Usage:
import { GetCredential } from '@/bindings/github.com/felixdotgo/querybox/services/connectionservice'
const credJSON = await GetCredential(conn.id)
const cred = JSON.parse(credJSON)
Implementation: services/connection.go:273
DeleteConnection
DeleteConnection(ctx context.Context, id string) error
Deletes a connection and its associated credential.
Flow:
- Look up
credential_key from connection metadata
- Best-effort delete from CredManager (ignores errors)
- Delete row from
connections.db
- Emit
connection:deleted event with connection ID
Frontend Usage:
import { DeleteConnection } from '@/bindings/github.com/felixdotgo/querybox/services/connectionservice'
await DeleteConnection(conn.id)
// Frontend event listener removes connection from UI state
Implementation: services/connection.go:299
Emits: connection:deleted event → Frontend removes connection from list
Database Schema
CREATE TABLE IF NOT EXISTS connections (
id TEXT PRIMARY KEY,
name TEXT NOT NULL,
driver_type TEXT NOT NULL,
credential_key TEXT,
created_at DATETIME DEFAULT (strftime('%Y-%m-%dT%H:%M:%fZ','now')),
updated_at DATETIME DEFAULT (strftime('%Y-%m-%dT%H:%M:%fZ','now'))
);
Location: Platform-specific user config directory + querybox/connections.db
Migration: Legacy credential_blob column migrated to keyring on service initialization (services/connection.go:113)
PluginManager
Location: services/pluginmgr/pluginmgr.go
Wails Binding: github.com/felixdotgo/querybox/services/pluginmgr.Manager
Discovery and on-demand execution of database driver plugins.
Plugin Discovery
Scan Locations (in order of precedence):
- User directory:
<UserConfigDir>/querybox/plugins/
- Bundled directory:
<executable_dir>/bin/plugins/
On startup, bundled plugins are copied to the user directory to ensure updates ship with new releases.
Scan Process (pluginmgr.go:264):
- Find all executable files in scan directories
- Probe each with
plugin info command (5s timeout)
- Parse JSON response to populate metadata
- Store in in-memory registry (
map[string]PluginInfo)
- Emit
plugins:ready event when complete
Plugin Info
type PluginInfo struct {
ID string `json:"id"` // Filename
Name string `json:"name"` // Display name from metadata
Path string `json:"path"` // Full filesystem path
Running bool `json:"running"` // Always false (on-demand model)
Type int `json:"type"` // 1 = DRIVER (proto enum)
Version string `json:"version"`
Description string `json:"description"`
URL string `json:"url"`
Author string `json:"author"`
Capabilities []string `json:"capabilities"`
Tags []string `json:"tags"`
License string `json:"license"`
IconURL string `json:"icon_url"`
Contact string `json:"contact"`
Metadata map[string]string `json:"metadata"`
Settings map[string]string `json:"settings"`
LastError string `json:"lastError"` // Probe failure message
}
Public API
ListPlugins
ListPlugins() []PluginInfo
Returns all discovered plugins. Does not trigger a rescan.
Frontend Usage:
import { ListPlugins } from '@/bindings/github.com/felixdotgo/querybox/services/pluginmgr/manager'
const plugins = await ListPlugins()
Implementation: pluginmgr.go:456
Rescan
Clears the registry and performs a full plugin rescan. Useful after installing new plugins without restarting the app.
Implementation: pluginmgr.go:610
ExecPlugin
ExecPlugin(name string, connection map[string]string, query string, options map[string]string) (*plugin.ExecResponse, error)
Executes a query via the named plugin.
Parameters:
name: Plugin ID (filename)
connection: Credential map (typically parsed from GetCredential())
query: Query string (SQL, NoSQL command, etc.)
options: Optional flags (e.g., {"explain-query": "yes"})
Flow:
- Look up plugin path in registry
- Spawn subprocess:
<plugin> exec
- Write JSON request to stdin:
{
"connection": {"host": "...", "user": "..."},
"query": "SELECT * FROM users",
"options": {}
}
- Read protobuf-JSON response from stdout (30s timeout)
- Parse into
plugin.ExecResponse
- Terminate subprocess
Response Structure (protobuf):
type ExecResponse struct {
Error string `json:"error"` // Non-empty on plugin error
Result *ExecResult `json:"result"` // Typed result (SQL/KV/Document)
}
type ExecResult struct {
Payload isExecResult_Payload `json:"..."` // Oneof: sql, kv, document
}
Frontend Usage:
import { ExecPlugin } from '@/bindings/github.com/felixdotgo/querybox/services/pluginmgr/manager'
import { GetCredential } from '@/bindings/github.com/felixdotgo/querybox/services/connectionservice'
const credJSON = await GetCredential(conn.id)
const credMap = JSON.parse(credJSON)
const result = await ExecPlugin(conn.driver_type, credMap, 'SELECT * FROM users', {})
Implementation: pluginmgr.go:474
GetConnectionTree
GetConnectionTree(name string, connection map[string]string) (*plugin.ConnectionTreeResponse, error)
Fetches hierarchical structure of database objects (databases → tables → columns).
Flow:
- Spawn
<plugin> connection-tree
- Write JSON request:
{"connection": {...}}
- Read protobuf-JSON response (30s timeout)
- Parse into
ConnectionTreeResponse
Response Structure:
type ConnectionTreeResponse struct {
Nodes []*TreeNode `json:"nodes"`
}
type TreeNode struct {
Label string `json:"label"` // Display name
NodeType string `json:"node_type"` // "database", "table", "column", etc.
Children []*TreeNode `json:"children"` // Recursive children
Actions []*Action `json:"actions"` // Context menu actions
}
Frontend Usage:
import { GetConnectionTree } from '@/bindings/github.com/felixdotgo/querybox/services/pluginmgr/manager'
const tree = await GetConnectionTree(conn.driver_type, credMap)
Implementation: pluginmgr.go:621
ExecTreeAction
ExecTreeAction(name string, connection map[string]string, actionQuery string, options map[string]string) (*plugin.ExecResponse, error)
Convenience wrapper that delegates to ExecPlugin(). Used when a tree node action is clicked.
Implementation: pluginmgr.go:696
TestConnection
TestConnection(name string, connection map[string]string) (*plugin.TestConnectionResponse, error)
Tests connection parameters without persisting a connection.
Flow:
- Spawn
<plugin> test-connection
- Write JSON request:
{"connection": {...}}
- Read response (15s timeout)
- Parse into
TestConnectionResponse
Response Structure:
type TestConnectionResponse struct {
Ok bool `json:"ok"` // true if connection succeeded
Message string `json:"message"` // Success message or error details
}
Frontend Usage (in New Connection form):
import { TestConnection } from '@/bindings/github.com/felixdotgo/querybox/services/pluginmgr/manager'
const result = await TestConnection('mysql', { host: 'localhost', user: 'root', password: 'secret' })
if (result.ok) {
console.log('✓', result.message)
} else {
console.error('✗', result.message)
}
Implementation: pluginmgr.go:706
GetPluginAuthForms(name string) (map[string]*plugin.AuthForm, error)
Retrieves structured authentication form definitions for a plugin.
Flow:
- Spawn
<plugin> authforms
- Read protobuf-JSON response (2s timeout)
- Parse into
map[formID]*AuthForm
Response Structure:
type AuthForm struct {
Name string `json:"name"` // Form display name
Description string `json:"description"`
Fields []*FormField `json:"fields"` // Form fields
}
type FormField struct {
Key string `json:"key"` // Credential map key
Label string `json:"label"` // Display label
Type string `json:"type"` // "text", "password", "number", "file"
Required bool `json:"required"`
DefaultValue string `json:"default_value"`
}
Frontend Usage:
import { GetPluginAuthForms } from '@/bindings/github.com/felixdotgo/querybox/services/pluginmgr/manager'
const forms = await GetPluginAuthForms('postgres')
// Render dynamic form based on forms['default'].fields
Implementation: pluginmgr.go:776
CredManager
Location: services/credmanager/credmanager.go
Wails Binding: None (internal service)
Provides secure credential storage with automatic fallback when OS keyring is unavailable.
Architecture
Credential Backends
| Backend | Platform Support | Persistence | Use Case |
|---|
| OS Keyring | macOS: Keychain Windows: Credential Manager Linux: libsecret, KWallet | ✓ Persistent, encrypted | Default for desktop |
| SQLite | All platforms | ✓ Persistent, plaintext† | Headless servers, containers |
| Memory | All platforms | ✗ Session-only | Filesystem unavailable |
† SQLite credentials are not encrypted at rest. Use OS keyring for production.
Public API
Store
Store(key string, secret string) error
Stores a secret under the given key.
Implementation: credmanager.go:114
Get
Get(key string) (string, error)
Retrieves a secret previously stored.
Returns: "secret not found" error if key doesn’t exist.
Implementation: credmanager.go:135
Delete
Removes a secret. Only the active backend is consulted.
Implementation: credmanager.go:159
Backend
Returns the active backend: "keyring", "sqlite", or "memory".
Implementation: credmanager.go:177
Keyring Probing
On initialization, CredManager tests keyring availability:
func probeKeyring() bool {
keyring.Set("querybox", "__availability_probe__", "__probe__")
_, err := keyring.Get("querybox", "__availability_probe__")
keyring.Delete("querybox", "__availability_probe__")
return err == nil
}
Implementation: credmanager.go:49
If the probe fails, CredManager falls back to SQLite.
SQLite Schema
CREATE TABLE IF NOT EXISTS credentials (
key TEXT PRIMARY KEY,
secret TEXT NOT NULL
);
Location: <UserConfigDir>/querybox/credentials.db
Service Lifecycle
Initialization (main.go)
func main() {
connSvc := services.NewConnectionService() // Opens SQLite, runs migrations
mgr := pluginmgr.New() // Starts async plugin scan
app := application.New(application.Options{
Services: []application.Service{
application.NewService(connSvc),
application.NewService(mgr),
application.NewService(appSvc),
},
})
// Inject app reference for event emission
connSvc.SetApp(app)
mgr.SetApp(app)
app.Run()
}
Shutdown
Wails calls Shutdown() on all bound services when the app quits:
- ConnectionService: Closes SQLite connection (
connection.go:140)
- PluginManager: No-op (no background processes)
- CredManager: Call
Close() manually if needed (not automatic)
Error Handling
Service Errors
All public methods return error as the last return value. Frontend bindings surface these as rejected promises.
Frontend Example:
try {
const conn = await CreateConnection('My DB', 'mysql', credJSON)
} catch (err) {
console.error('Failed to create connection:', err)
}
Plugin Errors
Plugin failures are captured in the response:
resp, err := mgr.ExecPlugin(...)
if err != nil {
// Subprocess timeout, crash, or invalid JSON
}
if resp.Error != "" {
// Plugin reported an error (e.g., syntax error)
}
Event Emission Errors
Event emission is nil-safe. If app is nil (e.g., in tests), events are silently dropped.
Testing
Unit Tests
Services are designed to be testable without Wails:
svc := NewConnectionService() // No app reference
conn, err := svc.CreateConnection(ctx, "test", "mysql", credJSON)
// Events not emitted (app is nil)
Mocking
Internal dependencies use function variables for test injection:
// credmanager_test.go
keyringSet = func(service, key, secret string) error {
return errors.New("mock error")
}
Examples:
credmanager/credmanager_test.go
connection_test.go
pluginmgr/pluginmgr_test.go
Next Steps