SQLite Explorer is a VS Code extension that provides a powerful SQLite database viewer and editor. It uses WebAssembly-based SQLite (sql.js) to work across all platforms, including VS Code for Web.
The extension uses a three-layer communication architecture:
┌─────────────────┐ ┌─────────────────┐ ┌─────────────────┐
│ Webview │ ←→ │ Extension Host │ ←→ │ Worker │
│ (viewer.html) │ │ (extension.ts) │ │ (worker.ts) │
└─────────────────┘ └─────────────────┘ └─────────────────┘
↑ ↑ ↑
UI Layer VS Code API SQLite WASM
-
Webview Layer (
core/ui/viewer.html+core/ui/modules/*.js)- Renders the UI (table grid, sidebar, modals)
- Handles user interactions (cell editing, row selection, CRUD operations)
- Communicates with Extension Host via custom RPC protocol
- Modularized logic in
core/ui/modules/(rpc, state, grid, sidebar, etc.)
-
Extension Host Layer (
src/main.ts,src/databaseModel.ts,src/editorController.ts)- Manages VS Code custom editor lifecycle
- Bridges Webview and Worker communication
- Handles file I/O via VS Code workspace API
- Exposes
HostBridgeto the Webview
-
Worker Layer (
src/databaseWorker.ts,src/nativeWorker.ts)- Runs WebAssembly SQLite (sql.js) or Native SQLite (txiki-js)
- Executes all SQL queries
- Handles database operations via
DatabaseOperations
| File | Purpose |
|---|---|
src/main.ts |
Extension activation, command registration |
src/editorController.ts |
Custom editor provider (DatabaseViewerProvider, DatabaseEditorProvider) |
src/databaseModel.ts |
Document model (DatabaseDocument), undo/redo, save/revert |
src/workerFactory.ts |
Worker instantiation, connection setup, file reading |
src/databaseWorker.ts |
Worker entry point, exposes database operations |
src/nativeWorker.ts |
Native SQLite worker (txiki-js), batched IPC via queryBatch |
src/config.ts |
Extension constants, URI scheme, storage keys, typed config accessors |
src/shims.ts |
Polyfills for Symbol.dispose, AbortSignal.throwIfAborted, Promise.withResolvers |
src/hostBridge.ts |
HostBridge - functions exposed to webview (exec, export, etc.) |
src/core/types.ts |
Core type definitions (CellValue, RecordId, QueryResultSet, etc.) |
src/core/rpc.ts |
RPC utilities (buildMethodProxy, processProtocolMessage) |
src/core/sqlite-db.ts |
Database engine wrapper |
src/core/query-builder.ts |
Safe SQL query construction |
src/core/sql-utils.ts |
SQL utilities and escaping |
src/core/json-utils.ts |
JSON Merge Patch (RFC 7396) generate and apply |
src/core/cancellation-utils.ts |
CancellationToken to AbortSignal bridge |
src/core/serialization.ts |
Serialization utilities |
src/core/undo-history.ts |
ModificationTracker for undo/redo |
src/documentRegistry.ts |
Global registry of open DatabaseDocument instances |
src/webviewMessageHandler.ts |
Webview → Extension Host message routing |
src/webview-collection.ts |
Tracks active webview panels per document |
src/virtualFileSystem.ts |
Virtual FS provider for editing cells in tabs |
src/connectionTypes.ts |
Database connection bundle interfaces |
src/helpers.ts |
Helper utilities |
src/html-utils.ts |
HTML utilities |
src/lifecycle.ts |
Resource lifecycle management (Disposable utilities) |
src/tableExporter.ts |
Table export to CSV/JSON/SQL |
src/platform/cryptoShim.ts |
Platform crypto abstraction |
src/platform/threadPool.ts |
Cross-platform worker thread APIs (Browser + Node) |
src/loggingDatabaseOperations.ts |
Decorator for logging SQL queries |
core/ui/modules/settings.js |
UI logic for database settings/pragma editor |
core/ui/modules/web-api.js |
Web demo API module (parent window communication) |
core/ui/viewer.html |
Standalone webview UI |
core/ui/web-viewer.js |
Web demo entry point |
website/app/demo/page.tsx |
Web demo React page |
website/public/sqlite-viewer/worker.js |
Web demo SQLite worker |
website/public/sqlite-viewer/viewer.html |
Web demo bundled viewer |
assets/sqlite3.wasm |
SQLite WebAssembly binary |
The extension uses two distinct RPC protocols for cross-boundary communication.
1. Core RPC (Worker Communication & Extension → Webview)
Defined in src/core/rpc.ts, used for all Worker communication and when the Extension invokes Webview methods.
Request:
{
kind: 'invoke',
correlationId: 'ipc_...',
methodName: 'methodName',
parameters: [arg1, arg2, ...]
}Response:
{
kind: 'result',
correlationId: 'ipc_...',
payload: result, // or errorText: "error"
}2. Webview RPC (Webview → Extension) Used when the Webview invokes Extension Host methods.
Request:
{
channel: 'rpc',
content: {
kind: 'invoke',
messageId: 'rpc_...',
targetMethod: 'methodName',
payload: [arg1, arg2, ...]
}
}Response:
{
channel: 'rpc',
content: {
kind: 'response',
messageId: 'rpc_...',
success: true,
data: result // or errorMessage: "error"
}
}Zero-Copy Transfer:
To transfer large binary data (ArrayBuffers) without copying, use the Transfer wrapper in the RPC layer (supported in Core RPC).
// workerFactory.ts
const data = new Uint8Array(...);
workerProxy.method(new Transfer(data, [data.buffer]));Uint8Array Serialization:
Uint8Array cannot be directly serialized via postMessage (becomes {}). The RPC layer uses Base64 encoding:
// Serialized format (preferred, compact)
{ __type: 'Uint8Array', base64: 'SGVsbG8=' }
// Legacy array format (supported for backward-compatible deserialization only)
{ __type: 'Uint8Array', data: [72, 101, 108, 108, 111] }
// Security: Marker must have exactly 2 keys to prevent collision with user data- Webview serializes in requests, deserializes in responses (
core/ui/modules/api.js) - Extension host deserializes in requests, serializes in responses (
src/editorController.ts)
Worker Log Forwarding:
Workers route logs through RPC using LogEnvelope instead of console.*:
{ kind: 'log', level: 'warn', args: ['message', 42] }The host routes these to the VS Code "SQLite Explorer" output channel via GlobalOutputChannel.
- Scripts: Strict nonce-based policy. No
'unsafe-inline'allowed. - Styles: Nonce-based policy for
<style>tags. Dynamic inline styles are applied via CSSOM (element.style.prop = ...) which is permitted by CSP. - Isolation: Webview communicates only via RPC.
- Grid Rendering: Always use
textContentfor cell values. Never useinnerHTMLwith untrusted data. - Formatting: Use
formatCellValueAsText()for safe display strings.
- Query Parameters: Use prepared statements (
?placeholders) for all values. - Identifiers: Always use
escapeIdentifier()for table/column names. - Schema Validation: Use
validateSqlType()for all user-provided SQL types in DDL. - PRAGMA Values: String values validated via
/^[a-zA-Z0-9_-]+$/whitelist; numeric values checked withNumber.isFinite().
# Install dependencies
npm install
# Full build (compiles extension + worker)
node scripts/build.mjs
# Package extension as .vsix
npm run package
# Development build with sourcemaps
DEV=1 node scripts/build.mjs
# Quick install (build + package + install)
./install.sh
# Run unit tests
npm testout/extension.js- Node.js extension (desktop VS Code)out/extension-browser.js- Browser extension (VS Code Web)out/worker.js- Node.js workerout/worker-browser.js- Browser workercore/ui/viewer.html- Webview UIwebsite/public/sqlite-viewer/viewer.html- Web demo viewer (bundled)assets/sqlite3.wasm- SQLite WASM binary
The project includes a standalone web demo at /demo on the website. This allows users to try SQLite Explorer in their browser without installing the VS Code extension.
Architecture:
┌─────────────────┐ ┌─────────────────┐
│ React Page │ ←→ │ Web Worker │
│ (demo/page.tsx)│ │ (worker.js) │
└─────────────────┘ └─────────────────┘
↓ ↑
┌────────────┐ sql.js WASM
│ iframe │
│(viewer.html)│
└────────────┘
Key differences from VS Code extension:
- Uses
window.parent.postMessageinstead of VS Code API - Loads sql.js directly from CDN
- No file system access (upload-only)
- No undo/redo integration
Building the web demo:
# Build extension (includes web demo viewer)
node scripts/build.mjs
# Generate sample databases
node scripts/generate-samples.mjs
# Build website
cd website && npm run buildThe build uses esbuild with these targets:
- Extension: CJS format, ES2022, externals:
vscode,worker_threads - Worker: ESM format, ES2022, includes sql.js WASM
| Type | Description |
|---|---|
CellValue |
Any value in a SQLite cell (string, number, null, Uint8Array) |
RecordId |
Row identifier (string or number) |
QueryResultSet |
Query results with headers and rows |
ModificationEntry |
Record of a database modification |
DatabaseOperations |
Interface for database operations |
DatabaseDocument |
VS Code custom document for a database file |
HostBridge |
Methods exposed to webview |
// DatabaseEditorProvider implements vsc.CustomEditorProvider
// - openCustomDocument: Creates DatabaseDocument
// - resolveCustomEditor: Creates webview, sets up RPCopenCustomDocument()- Creates DatabaseDocument, loads database into workerresolveCustomEditor()- Creates webview, establishes RPC connection- User edits trigger
recordModification()- Tracked in ModificationTracker save()- Commits changes to database filedispose()- Cleans up worker, removes from DocumentRegistry
The webview handles inline editing:
- Double-click cell → Creates input overlay
- Enter key →
saveCellEdit()sends UPDATE viabackendApi.updateCell() - Escape key →
cancelCellEdit()discards changes
The extension registers a FileSystemProvider to allow editing cell contents in full VS Code editors:
hostBridge.openCellEditor()opens a custom URI.SQLiteFileSystemProvider.readFile()queries the cell data.SQLiteFileSystemProvider.writeFile()triggersdocument.databaseOperations.updateCell().
Database operations are wrapped in LoggingDatabaseOperations which writes all executed SQL (both read and write) to the "SQLite Explorer" output channel for debugging.
The webview provides a UI to configure SQLite PRAGMAs (e.g., WAL mode, Foreign Keys) directly via hostBridge.setPragma().
retainContextWhenHidden is false — webviews are destroyed when hidden to save memory. State survives via vscodeApi.setState()/getState():
persistState()instate.jsdebounces (500ms) and serializes user-facing state (selected table, scroll position, filters, pins, settings)viewer.jsrestores state on re-initialization, including scroll position after grid render- VS Code extension settings (e.g.,
cellEditBehavior) take precedence over restored state - Web demo (
web-api.js) provides no-op stubs since there is no VS Code API
getNodeFs() in sqlite-db.ts safely requires the Node.js fs module, returning undefined in browser environments. Used by sqlite-db.ts (file reading, writing) and tableExporter.ts (streaming export).
updateCellBatch uses SAVEPOINT/RELEASE/ROLLBACK TO instead of BEGIN TRANSACTION so it can be safely called from within outer transactions (e.g., undoColumnDrop). All transaction error handlers use the safeRollback(context) private helper which logs failures instead of throwing — preventing secondary rollback errors from masking the original error.
updateCell and updateCellBatch in sqlite-db.ts probe for SQLite's json_patch() at engine construction time (hasJsonPatch flag). When available, uses json_patch(COALESCE(col, '{}'), ?) in a single UPDATE (no SELECT round-trip). Falls back to JS-side applyMergePatch() from json-utils.ts when JSON1 extension is unavailable.
queryBatch in nativeWorker.ts sends multiple SQL queries in a single IPC round-trip. Used for schema fetching (3 queries → 1 call) and pragma reads.
The Blob Inspector (core/ui/modules/blob-inspector.js) provides preview and editing for BLOB data:
- Supported previews: Images (PNG, JPEG, GIF, WebP), Audio (MP3, WAV, OGG, FLAC), Video (MP4, WebM, MOV), PDF, Text/JSON
- Hex view: Raw binary inspection
- Download/Replace: Save blobs to disk or upload new files
- Uses
backendApi(nothostBridgedirectly) to ensure proper Uint8Array serialization
Settings in package.json → contributes.configuration:
| Setting | Default | Description |
|---|---|---|
sqliteExplorer.maxFileSize |
200 | Max file size in MB (0 = unlimited) |
sqliteExplorer.maxRows |
0 | Max rows to display (0 = unlimited) |
sqliteExplorer.defaultPageSize |
500 | Default page size for pagination |
sqliteExplorer.instantCommit |
"never" | Auto-save strategy (always/never/remote-only) |
sqliteExplorer.doubleClickBehavior |
"inline" | Double-click action (inline/modal/vscode) |
sqliteExplorer.fileOperations |
"native" | File I/O strategy (native/wasm) |
sqliteExplorer.queryTimeout |
30000 | Query execution timeout in ms (prevents runaway queries) |
sqliteExplorer.maxUndoMemory |
52428800 | Max undo history memory in bytes (default 50MB) |
- LIKE wildcards: User input in LIKE queries must use
escapeLikePattern()withESCAPE '\\'clause - SQL types in DDL: Always validate with
validateSqlType()to prevent injection via malicious type strings - NUL bytes: Strings containing
\0must be encoded as hex blobs in exported SQL
- Uint8Array becomes
{}: Never send raw Uint8Array via postMessage - use the marker format - Transfer for blobs: Large binary data should use
Transferwrapper for zero-copy
- VS Code mocks required: Unit tests need
tests/unit/vscode_mock_setup.tsimported first (path-mapped viatsconfig.test.json) - JSON patch tests: Test behavior (merged result) not implementation (SQL function)
- Test organization: 33 test files (237 tests) in
tests/unit/, benchmarks intests/benchmarks/, performance tests intests/performance/ - Test runner: Uses
tsxwith Node's built-in test runner (npx tsx --tsconfig tsconfig.test.json --test tests/unit/*.test.ts) - Readonly mock properties: Use
Object.defineProperty(obj, prop, { value, writable: true, configurable: true })for readonly VS Code API fields likevscode.env.uiKind
src/shims.tsmust be imported early: PolyfillsSymbol.dispose,Symbol.asyncDispose,AbortSignal.throwIfAborted,Promise.withResolversfor older VS Code runtimes that lack these APIs
- WASM not found: Run
node scripts/build.mjsifassets/sqlite3.wasmis missing - Browser vs Node: Check
import.meta.env.VSCODE_BROWSER_EXTfor environment-specific code
// src/config.ts
Ns = 'zknpr'
ExtensionId = 'sqlite-explorer'
FullExtensionId = 'zknpr.sqlite-explorer'
ConfigurationSection = 'sqliteExplorer'
UriScheme = 'sqlite-explorer' // Virtual file system URI scheme
CopilotChatId = 'github.copilot-chat'
// Storage keys: FirstInstallMs, SidebarLeft, SidebarRight, FileNestingPatternsAdded
// Config accessors: getMaximumFileSizeBytes(), getQueryTimeout()- RPC timeout: Check that message format matches expected protocol
- CSP errors: Verify Content-Security-Policy in
editorController.ts - Worker not loading: Check worker path resolution in
workerFactory.ts - WASM not found: Ensure
assets/sqlite3.wasmexists after build
- Extension Host: Use
GlobalOutputChannel?.appendLine()(notconsole.log). Output appears in "SQLite Explorer" output channel. - Worker: Logs route via RPC
LogEnvelopeto "SQLite Explorer" output channel (View → Output → SQLite Explorer) - Webview: Use browser DevTools (Cmd+Shift+P → "Developer: Open Webview Developer Tools")
- Make changes to source files
- Run
node scripts/build.mjsto compile - Press F5 in VS Code to launch Extension Development Host
- Open a
.sqliteor.dbfile to test
- sql.js: WebAssembly SQLite implementation (MIT license)
- @vscode/codicons: VS Code icon font
- esbuild: Fast bundler for extension and worker
- TypeScript: 6.0.2 (requires
"types": ["node"]in tsconfig.json)