Skip to content

Add SQLite support alongside PostgreSQL for flexible deployment#9

Merged
GoJam11 merged 1 commit into
mainfrom
claude/sqlite-deployment-option-793f3s
Jul 8, 2026
Merged

Add SQLite support alongside PostgreSQL for flexible deployment#9
GoJam11 merged 1 commit into
mainfrom
claude/sqlite-deployment-option-793f3s

Conversation

@GoJam11

@GoJam11 GoJam11 commented Jul 8, 2026

Copy link
Copy Markdown
Owner

Summary

This PR adds SQLite as an alternative database backend, allowing the application to run without requiring a separate PostgreSQL instance. The target database is automatically selected at startup based on the DATABASE_URL scheme and is fixed for the lifetime of the deployment to prevent data fragmentation.

Key Changes

  • Dual-dialect schema structure: Split database schema into schema.pg.ts (PostgreSQL) and schema.sqlite.ts (SQLite) with identical column names, defaults, and indexes. The main schema.ts exports the appropriate dialect at runtime based on DATABASE_URL.

  • Database driver detection: Added getDbDriver(), isSqliteUrl(), and getSqliteFilePath() utilities in src/db/config.ts to determine the target database from the connection string (sqlite: / file: schemes → SQLite; postgresql:// → PostgreSQL).

  • SQLite client initialization: Updated src/db/client.ts to create and manage a shared bun:sqlite connection with WAL mode and busy timeout for concurrent access, mirroring the PostgreSQL connection pooling pattern.

  • Migration support for both dialects: Extended src/db/migrate.ts to run SQLite migrations from drizzle/sqlite/ folder alongside PostgreSQL migrations from drizzle/. Added initial SQLite migration (drizzle/sqlite/0000_many_the_hand.sql) and metadata snapshot.

  • Drizzle configuration: Updated drizzle.config.ts to select schema and output folder based on the detected driver, enabling bun run db:generate to work with either database.

  • Docker Compose for SQLite: Added docker-compose.sqlite.yml for zero-dependency SQLite deployments using a named volume for persistence.

  • Documentation and examples: Updated README files (English and Chinese) and .env.example to document SQLite as a deployment option. Added src/db/README.md explaining the dual-dialect architecture and migration workflow.

  • Gitignore updates: Added patterns for SQLite database files (.db, .db-shm, .db-wal, .sqlite, .sqlite3).

Implementation Details

  • Type mapping: PostgreSQL bigint({mode:'number'}) → SQLite integer({mode:'number'}) for 64-bit integers; serialinteger(primaryKey autoIncrement).
  • The database choice is immutable at runtime; switching requires a fresh deployment to avoid split data.
  • SQLite uses embedded mode with no separate connection pool; concurrency is serialized by the driver with WAL mode preventing spurious SQLITE_BUSY errors.
  • All store-layer code (console-store.ts, etc.) remains unchanged; it imports from the unified schema.ts which provides the correct dialect's tables.

https://claude.ai/code/session_01U5dS4D3toAwqRG63xF97p1

Let users run the service without deploying PostgreSQL to cut deployment
cost. The target database is selected from DATABASE_URL at deploy time and
is fixed at runtime (no switching) to avoid data being split across stores:

  postgresql:// / postgres://  -> PostgreSQL (unchanged default)
  sqlite: / file:              -> SQLite (embedded, bun:sqlite)

- config: getDbDriver()/isSqliteUrl()/getSqliteFilePath() infer the driver
  from the connection string (defaults to postgres).
- schema: split into schema.pg.ts (pg-core) and schema.sqlite.ts
  (sqlite-core) with identical columns/defaults/indexes; schema.ts exports
  the active driver's tables at runtime so the store layer is unchanged.
- client: SQLite uses the built-in bun:sqlite + drizzle-orm/bun-sqlite
  (WAL, busy_timeout, auto-created parent dir), one shared connection.
- migrate: per-driver migration folder (drizzle/ vs drizzle/sqlite/) and
  migrator; SQLite skips the pg advisory lock and readiness probe. Adds the
  generated SQLite baseline migration; drizzle.config.ts is driver-aware.
- console-store: API-key quota charge uses portable stepwise atomic writes
  on SQLite instead of the pg-only UPDATE...FROM RETURNING/FOR UPDATE CTE;
  the console-clear guard is bypassed for SQLite (local embedded file).
- server: database reset enumerates tables via sqlite_master on SQLite.
- docs/ops: docker-compose.sqlite.yml, .env.example, READMEs, db/README;
  ignore local *.db/*.sqlite files.

Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01U5dS4D3toAwqRG63xF97p1
@GoJam11 GoJam11 merged commit afb8870 into main Jul 8, 2026
1 check passed
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

2 participants