Skip to content

feat: Migrate config storage from JSON to SQLite #76

@BYK

Description

@BYK

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

  1. Concurrency-safe: Multiple terminals, IDE extensions, and CI/CD parallel jobs can safely access the config
  2. 7-day TTL: Cache entries auto-expire after 7 days of no use (touch-on-read resets TTL)
  3. No new dependencies: Use Node.js 22+ built-in node:sqlite module
  4. 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:

  1. Check if config.json exists
  2. If yes, migrate data to data.db
  3. Delete config.json after successful migration
  4. 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

  • Create src/lib/db/index.ts with connection management
  • Create src/lib/db/schema.ts with DDL
  • Add migration logic from JSON

Phase 2: Migrate auth & defaults

  • Create src/lib/db/auth.ts
  • Create src/lib/db/defaults.ts
  • Update src/lib/config.ts to use new modules

Phase 3: Migrate caches

  • Create src/lib/db/project-cache.ts
  • Create src/lib/db/dsn-cache.ts
  • Create src/lib/db/project-aliases.ts
  • Update src/lib/dsn/cache.ts to use new module

Phase 4: Testing & cleanup

  • Update all tests
  • Remove old JSON implementation code
  • Test migration from existing JSON configs

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

Metadata

Metadata

Assignees

Labels

v0V0 release milestone
No fields configured for issues without a type.

Projects

No projects

Milestone

Relationships

None yet

Development

No branches or pull requests

Issue actions