5.3 KiB
5.3 KiB
Phase 14: PostgreSQL Migration - Context
Gathered: 2026-04-04 Status: Ready for planning
## Phase BoundaryReplace SQLite with PostgreSQL as the sole database. Make all database operations async. Establish PGlite-based test infrastructure. Provide a one-time data migration script and Docker Compose for local Postgres development.
## Implementation DecisionsMigration Strategy
- D-01: Clean rewrite of
src/db/schema.tsusingdrizzle-orm/pg-core(pgTable, serial, text, numeric, timestamp, etc.) — not a conversion of the SQLite schema - D-02: Start fresh Postgres migration history in a new directory (e.g.,
drizzle-pg/) — keep existingdrizzle/SQLite migrations archived for reference - D-03:
src/db/index.tsswitches frombun:sqlite+drizzle-orm/bun-sqlitetodrizzle-orm/node-postgres(ordrizzle-orm/postgres-js) with async connection
Data Migration Script
- D-04: Standalone TypeScript script (e.g.,
scripts/migrate-sqlite-to-postgres.ts) that reads from SQLite file and writes to Postgres — not a Drizzle migration - D-05: Script handles type conversions: integer timestamps → proper Postgres
timestampcolumns,realweight →numericordouble precision, text → text - D-06: Script preserves all IDs and foreign key relationships — no ID remapping
Test Infrastructure
- D-07:
createTestDb()returns an async PGlite-backed Drizzle instance — same API shape as current, but async - D-08: Per-test fresh PGlite instance with migrations applied (matches current in-memory SQLite pattern, avoids test pollution)
- D-09: All service and route tests updated from sync to async database operations
Docker Compose
- D-10: Separate
docker-compose.dev.ymlfor development with Postgres service — keep existingdocker-compose.ymlfor production (updated to include Postgres) - D-11: PostgreSQL 16 (latest stable)
- D-12: Environment variable
DATABASE_URLfor Postgres connection string (replacesDATABASE_PATHfor SQLite)
Claude's Discretion
- Drizzle Postgres driver choice (
node-postgresvspostgres-js) — pick based on Bun compatibility and async performance - PGlite configuration details (version, extensions)
- Column type mapping specifics beyond the ones called out (e.g., whether to use
serialvsinteger().primaryKey()) - Migration script error handling and progress reporting
- Whether to use
drizzle-orm/pglitedriver or generic pg driver for tests
<canonical_refs>
Canonical References
Downstream agents MUST read these before planning or implementing.
Database Schema & Config
src/db/schema.ts— Current SQLite schema (source of truth for tables/columns to migrate)src/db/index.ts— Current database initialization (bun:sqlite + drizzle)drizzle.config.ts— Current Drizzle Kit config (sqlite dialect)drizzle/— Existing SQLite migration files (10 migrations, reference only)
Test Infrastructure
tests/helpers/db.ts— Current test database helper (in-memory SQLite, migration application, seed)
Services (all need sync → async)
src/server/services/*.ts— 9 service files that use synchronous Drizzle operationssrc/server/routes/*.ts— 9 route files that call services
Tests (all need updating)
tests/services/*.test.ts— 9 service test filestests/routes/*.test.ts— 8 route test filestests/mcp/tools.test.ts— MCP tools test
Docker
docker-compose.yml— Current production compose (SQLite volumes, no Postgres)
</canonical_refs>
<code_context>
Existing Code Insights
Reusable Assets
- Drizzle ORM already in use — schema definition pattern transfers directly to pg-core
- Service layer architecture with DI (db as first param) — makes swapping the db instance straightforward
- Zod schemas in
src/shared/schemas.ts— validation layer is database-agnostic, no changes needed - TanStack Query hooks — frontend is fully decoupled from database, no changes needed
Established Patterns
- Service DI pattern: All services take
dbas first parameter — this means swapping SQLite for Postgres only requires changing whatdbis, not how services use it - Sync Drizzle calls: Current code uses
.run(),.get(),.all()synchronously — Postgres requires.execute()/ await on all queries - Test pattern:
createTestDb()creates isolated DB, applies migrations, seeds — same pattern works with PGlite - Timestamps as integers:
{ mode: "timestamp" }on integer columns — Postgres can use nativetimestamptype
Integration Points
src/db/index.ts— Single point of database creation (good: only one file to change for connection)src/server/index.ts— Where db is provided to Hono context via middlewaretests/helpers/db.ts— Single test DB factory (good: only one file to change for test infra)drizzle.config.ts— Needs dialect change from sqlite to postgresql
</code_context>
## Specific IdeasNo specific requirements — open to standard approaches for SQLite-to-Postgres migration with Drizzle ORM.
## Deferred IdeasNone — discussion stayed within phase scope
Phase: 14-postgresql-migration Context gathered: 2026-04-04