13 KiB
phase, plan, type, wave, depends_on, files_modified, autonomous, requirements, must_haves
| phase | plan | type | wave | depends_on | files_modified | autonomous | requirements | must_haves | |||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|
| 14-postgresql-migration | 01 | execute | 1 |
|
true |
|
|
Purpose: Everything else in this phase depends on these files. Schema and DB config must exist before services, routes, or tests can be converted. Output: Working schema.ts (pg-core), index.ts (postgres.js), tests/helpers/db.ts (PGlite), drizzle.config.ts (postgresql), generated migration in drizzle-pg/
<execution_context> @$HOME/.claude/get-shit-done/workflows/execute-plan.md @$HOME/.claude/get-shit-done/templates/summary.md </execution_context>
@.planning/PROJECT.md @.planning/ROADMAP.md @.planning/STATE.md @.planning/phases/14-postgresql-migration/14-CONTEXT.md @.planning/phases/14-postgresql-migration/14-RESEARCH.md@src/db/schema.ts @src/db/index.ts @src/db/migrate.ts @src/db/seed.ts @src/shared/types.ts @tests/helpers/db.ts @drizzle.config.ts @package.json
Task 1: Install dependencies and rewrite schema + DB config files package.json, src/db/schema.ts, src/db/index.ts, src/db/migrate.ts, src/db/seed.ts, src/shared/types.ts, drizzle.config.ts src/db/schema.ts, src/db/index.ts, src/db/migrate.ts, src/db/seed.ts, src/shared/types.ts, drizzle.config.ts, package.json **Step 1: Install packages** ```bash bun add postgres @electric-sql/pglite bun remove better-sqlite3 @types/better-sqlite3 ```Step 2: Rewrite src/db/schema.ts -- Clean rewrite per D-01. Replace all sqliteTable with pgTable, all imports from drizzle-orm/sqlite-core with drizzle-orm/pg-core.
Column type mapping (apply to ALL 12 tables):
integer("id").primaryKey({ autoIncrement: true })->serial("id").primaryKey()text("col")->text("col")(unchanged)real("weight_grams")->doublePrecision("weight_grams")real("sort_order")->doublePrecision("sort_order")integer("price_cents")->integer("price_cents")(unchanged)integer("col", { mode: "timestamp" }).$defaultFn(() => new Date())->timestamp("col").notNull().defaultNow()integer("col", { mode: "timestamp" }).notNull()(no default, e.g., expiresAt) ->timestamp("col").notNull()integer("used").notNull().default(0)->boolean("used").notNull().default(false)(oauthCodes table)integer("quantity").notNull().default(1)->integer("quantity").notNull().default(1)(unchanged)
Tables to rewrite (12 total): categories, items, threads, threadCandidates, setups, setupItems, settings, users, sessions, apiKeys, oauthClients, oauthCodes, oauthTokens.
Import statement:
import { boolean, doublePrecision, integer, pgTable, serial, text, timestamp } from "drizzle-orm/pg-core";
Preserve ALL foreign key references and cascade rules exactly as they are. Preserve all .unique() constraints. Preserve all .default() values.
For settings table: keep text("key").primaryKey() (no serial).
For sessions table: keep text("id").primaryKey() (no serial).
Step 3: Rewrite src/db/index.ts per D-03:
import { drizzle } from "drizzle-orm/postgres-js";
import postgres from "postgres";
import * as schema from "./schema.ts";
const connectionString = process.env.DATABASE_URL || "postgresql://gearbox:gearbox@localhost:5432/gearbox";
const queryClient = postgres(connectionString);
export const db = drizzle(queryClient, { schema });
Step 4: Rewrite src/db/migrate.ts:
import { drizzle } from "drizzle-orm/postgres-js";
import { migrate } from "drizzle-orm/postgres-js/migrator";
import postgres from "postgres";
const connectionString = process.env.DATABASE_URL || "postgresql://gearbox:gearbox@localhost:5432/gearbox";
const migrationClient = postgres(connectionString, { max: 1 });
const db = drizzle(migrationClient);
await migrate(db, { migrationsFolder: "./drizzle-pg" });
await migrationClient.end();
console.log("Migrations applied successfully");
Step 5: Rewrite src/db/seed.ts to async:
import { db } from "./index.ts";
import { categories } from "./schema.ts";
export async function seedDefaults() {
const existing = await db.select().from(categories);
if (existing.length === 0) {
await db.insert(categories).values({
name: "Uncategorized",
icon: "package",
});
}
}
Step 6: Update src/shared/types.ts -- No changes needed to the file content itself. The types infer from schema which still exports the same table names. Verify the file still compiles after schema change.
Step 7: Update drizzle.config.ts per D-02:
import { defineConfig } from "drizzle-kit";
export default defineConfig({
out: "./drizzle-pg",
schema: "./src/db/schema.ts",
dialect: "postgresql",
dbCredentials: {
url: process.env.DATABASE_URL || "postgresql://gearbox:gearbox@localhost:5432/gearbox",
},
});
export async function createTestDb() { const db = drizzle({ schema });
// Apply migrations from the new PostgreSQL migration directory await migrate(db, { migrationsFolder: "./drizzle-pg" });
// Seed default Uncategorized category await db.insert(schema.categories).values({ name: "Uncategorized", icon: "package" });
return db; }
Key changes from current:
- Import from `drizzle-orm/pglite` instead of `drizzle-orm/bun-sqlite`
- `migrate` from `drizzle-orm/pglite/migrator` instead of `drizzle-orm/bun-sqlite/migrator`
- Function is now `async` (returns Promise)
- No `Database` import from `bun:sqlite`
- No `":memory:"` -- PGlite creates an in-memory Postgres instance by default
- Migration folder changed to `./drizzle-pg`
- `db.insert(...).values(...).run()` becomes `await db.insert(...).values(...)`
**Step 2: Generate initial PostgreSQL migration:**
```bash
bunx drizzle-kit generate
This reads the updated drizzle.config.ts (dialect: "postgresql", schema: src/db/schema.ts) and generates SQL migration files in drizzle-pg/.
Step 3: Verify migration was generated and is complete:
ls drizzle-pg/
cat drizzle-pg/*.sql
Confirm the SQL contains CREATE TABLE statements for all 12 tables with correct Postgres types (serial, text, timestamp, boolean, double precision, etc.). Count the CREATE TABLE statements -- there must be at least 12 (categories, items, threads, thread_candidates, setups, setup_items, settings, users, sessions, api_keys, oauth_clients, oauth_codes, oauth_tokens).
Step 4: Quick smoke test -- verify PGlite test helper works:
bun -e "
import { createTestDb } from './tests/helpers/db.ts';
const db = await createTestDb();
const cats = await db.select().from((await import('./src/db/schema.ts')).categories);
console.log('Categories:', cats.length);
if (cats.length !== 1) { console.error('FAIL: expected 1 category'); process.exit(1); }
console.log('PGlite test helper works!');
"
<success_criteria> All database foundation files rewritten for PostgreSQL. Schema uses pg-core types. DB connection uses postgres.js. Test helper uses PGlite. Initial migration generated with all 12+ tables. No SQLite references remain in these files. Lint passes. </success_criteria>
After completion, create `.planning/phases/14-postgresql-migration/14-01-SUMMARY.md`