Summary
Migrate from ~/.sentry/config.json (JSON file) to ~/.sentry/data.db (SQLite) for all caching and configuration data.
Problem
The current JSON-based config has a read-modify-write race condition. If two processes (e.g., IDE extension + terminal) both read, modify, and write config simultaneously, one will overwrite the other's changes.
Goals
- Concurrency-safe: Multiple terminals, IDE extensions, and CI/CD parallel jobs can safely access the config
- 7-day TTL: Cache entries auto-expire after 7 days of no use (touch-on-read resets TTL)
- No new dependencies: Use Node.js 22+ built-in
node:sqlite module
- No encryption: Rely on file permissions (0600/0700) like SSH keys
Database Schema
-- Authentication credentials (single row)
CREATE TABLE auth (
id INTEGER PRIMARY KEY CHECK (id = 1),
token TEXT,
refresh_token TEXT,
expires_at INTEGER,
issued_at INTEGER,
updated_at INTEGER NOT NULL DEFAULT (unixepoch() * 1000)
);
-- Default org/project settings (single row)
CREATE TABLE defaults (
id INTEGER PRIMARY KEY CHECK (id = 1),
organization TEXT,
project TEXT,
updated_at INTEGER NOT NULL DEFAULT (unixepoch() * 1000)
);
-- Project cache (org:project -> project info)
CREATE TABLE project_cache (
cache_key TEXT PRIMARY KEY,
org_slug TEXT NOT NULL,
org_name TEXT NOT NULL,
project_slug TEXT NOT NULL,
project_name TEXT NOT NULL,
cached_at INTEGER NOT NULL DEFAULT (unixepoch() * 1000),
last_accessed INTEGER NOT NULL DEFAULT (unixepoch() * 1000)
);
-- DSN cache (directory -> detected DSN)
CREATE TABLE dsn_cache (
directory TEXT PRIMARY KEY,
dsn TEXT NOT NULL,
project_id TEXT NOT NULL,
org_id TEXT,
source TEXT NOT NULL,
source_path TEXT,
resolved_org_slug TEXT,
resolved_org_name TEXT,
resolved_project_slug TEXT,
resolved_project_name TEXT,
cached_at INTEGER NOT NULL DEFAULT (unixepoch() * 1000),
last_accessed INTEGER NOT NULL DEFAULT (unixepoch() * 1000)
);
-- Project aliases (A, B, C -> org/project for short issue IDs)
CREATE TABLE project_aliases (
alias TEXT PRIMARY KEY,
org_slug TEXT NOT NULL,
project_slug TEXT NOT NULL,
dsn_fingerprint TEXT,
cached_at INTEGER NOT NULL DEFAULT (unixepoch() * 1000),
last_accessed INTEGER NOT NULL DEFAULT (unixepoch() * 1000)
);
-- Schema version for future migrations
CREATE TABLE schema_version (
version INTEGER PRIMARY KEY
);
File Structure
src/lib/
├── config.ts # Re-exports from db modules (backward compat)
├── db/
│ ├── index.ts # Database connection, init, migrate
│ ├── schema.ts # Schema DDL and version management
│ ├── auth.ts # Auth operations
│ ├── defaults.ts # Default org/project operations
│ ├── project-cache.ts # Project cache operations
│ ├── dsn-cache.ts # DSN cache operations
│ └── project-aliases.ts # Project alias operations
└── dsn/
└── cache.ts # Re-exports from db/dsn-cache.ts
Public API
The public API surface remains unchanged. All existing functions will be re-exported:
Auth: getAuthToken, setAuthToken, clearAuth, isAuthenticated, refreshToken
Defaults: getDefaultOrganization, getDefaultProject, setDefaults
Project Cache: getCachedProject, setCachedProject, getCachedProjectByDsnKey, setCachedProjectByDsnKey, clearProjectCache
Project Aliases: getProjectAliases, setProjectAliases, getProjectByAlias, clearProjectAliases
DSN Cache: getCachedDsn, setCachedDsn, updateCachedResolution, clearDsnCache
TTL Implementation
- 7-day TTL for cache tables (
project_cache, dsn_cache, project_aliases)
- Touch-on-read: Update
last_accessed timestamp on every read
- Lazy cleanup: Delete expired entries periodically (on write operations)
- Auth/defaults: No TTL (persist indefinitely)
Migration Strategy
On first run with new code:
- Check if
config.json exists
- If yes, migrate data to
data.db
- Delete
config.json after successful migration
- Log: "Migrated config from JSON to SQLite for better concurrency support"
SQLite Configuration
import { DatabaseSync } from 'node:sqlite';
const db = new DatabaseSync(join(getConfigDir(), 'data.db'));
db.exec('PRAGMA journal_mode = WAL');
db.exec('PRAGMA busy_timeout = 5000');
db.exec('PRAGMA foreign_keys = ON');
Implementation Phases
Phase 1: Database foundation
Phase 2: Migrate auth & defaults
Phase 3: Migrate caches
Phase 4: Testing & cleanup
Files to Modify
| File |
Change |
src/lib/db/*.ts |
NEW - All database modules |
src/lib/config.ts |
MODIFY - Re-export from db modules |
src/lib/dsn/cache.ts |
MODIFY - Re-export from db/dsn-cache.ts |
test/lib/config.test.ts |
MODIFY - Update tests |
test/lib/dsn/cache.test.ts |
MODIFY - Update tests |
test/helpers.ts |
MODIFY - Add helper for test databases |
Callers (No Changes Needed)
These files import from config but won't need changes since we preserve the API:
src/context.ts
src/lib/api-client.ts
src/lib/oauth.ts
src/lib/resolve-target.ts
src/commands/auth/*.ts
src/commands/project/list.ts
src/commands/issue/*.ts
Risks & Mitigations
| Risk |
Mitigation |
Node.js node:sqlite is relatively new |
Stable in Node 22+, our minimum version |
Users with existing config.json |
Auto-migration handles transparently |
Bun's node:sqlite compatibility |
Test thoroughly; Bun claims full compatibility |
| Database corruption |
SQLite is robust; WAL mode adds safety |
Summary
Migrate from
~/.sentry/config.json(JSON file) to~/.sentry/data.db(SQLite) for all caching and configuration data.Problem
The current JSON-based config has a read-modify-write race condition. If two processes (e.g., IDE extension + terminal) both read, modify, and write config simultaneously, one will overwrite the other's changes.
Goals
node:sqlitemoduleDatabase Schema
File Structure
Public API
The public API surface remains unchanged. All existing functions will be re-exported:
Auth:
getAuthToken,setAuthToken,clearAuth,isAuthenticated,refreshTokenDefaults:
getDefaultOrganization,getDefaultProject,setDefaultsProject Cache:
getCachedProject,setCachedProject,getCachedProjectByDsnKey,setCachedProjectByDsnKey,clearProjectCacheProject Aliases:
getProjectAliases,setProjectAliases,getProjectByAlias,clearProjectAliasesDSN Cache:
getCachedDsn,setCachedDsn,updateCachedResolution,clearDsnCacheTTL Implementation
project_cache,dsn_cache,project_aliases)last_accessedtimestamp on every readMigration Strategy
On first run with new code:
config.jsonexistsdata.dbconfig.jsonafter successful migrationSQLite Configuration
Implementation Phases
Phase 1: Database foundation
src/lib/db/index.tswith connection managementsrc/lib/db/schema.tswith DDLPhase 2: Migrate auth & defaults
src/lib/db/auth.tssrc/lib/db/defaults.tssrc/lib/config.tsto use new modulesPhase 3: Migrate caches
src/lib/db/project-cache.tssrc/lib/db/dsn-cache.tssrc/lib/db/project-aliases.tssrc/lib/dsn/cache.tsto use new modulePhase 4: Testing & cleanup
Files to Modify
src/lib/db/*.tssrc/lib/config.tssrc/lib/dsn/cache.tstest/lib/config.test.tstest/lib/dsn/cache.test.tstest/helpers.tsCallers (No Changes Needed)
These files import from config but won't need changes since we preserve the API:
src/context.tssrc/lib/api-client.tssrc/lib/oauth.tssrc/lib/resolve-target.tssrc/commands/auth/*.tssrc/commands/project/list.tssrc/commands/issue/*.tsRisks & Mitigations
node:sqliteis relatively newconfig.jsonnode:sqlitecompatibility