2026-02-16 19:07:37 -06:00
|
|
|
import { drizzle as drizzlePg } from "drizzle-orm/postgres-js";
|
2026-02-18 11:45:43 -06:00
|
|
|
import { migrate as migratePg } from "drizzle-orm/postgres-js/migrator";
|
2026-02-16 13:31:52 -06:00
|
|
|
import postgres from "postgres";
|
|
|
|
|
import * as schema from "./schema/index.js";
|
|
|
|
|
|
|
|
|
|
export function createDb(url: string) {
|
|
|
|
|
const sql = postgres(url);
|
2026-02-16 19:07:37 -06:00
|
|
|
return drizzlePg(sql, { schema });
|
|
|
|
|
}
|
|
|
|
|
|
2026-02-18 11:45:43 -06:00
|
|
|
export type MigrationBootstrapResult =
|
|
|
|
|
| { migrated: true; reason: "migrated-empty-db"; tableCount: 0 }
|
|
|
|
|
| { migrated: false; reason: "already-migrated"; tableCount: number }
|
|
|
|
|
| { migrated: false; reason: "not-empty-no-migration-journal"; tableCount: number };
|
2026-02-16 19:07:37 -06:00
|
|
|
|
2026-02-18 11:45:43 -06:00
|
|
|
export async function migratePostgresIfEmpty(url: string): Promise<MigrationBootstrapResult> {
|
|
|
|
|
const sql = postgres(url, { max: 1 });
|
2026-02-16 19:07:37 -06:00
|
|
|
|
2026-02-18 11:45:43 -06:00
|
|
|
try {
|
|
|
|
|
const journal = await sql<{ regclass: string | null }[]>`
|
|
|
|
|
select to_regclass('public.__drizzle_migrations') as regclass
|
|
|
|
|
`;
|
|
|
|
|
|
|
|
|
|
const tableCountResult = await sql<{ count: number }[]>`
|
|
|
|
|
select count(*)::int as count
|
|
|
|
|
from information_schema.tables
|
|
|
|
|
where table_schema = 'public'
|
|
|
|
|
and table_type = 'BASE TABLE'
|
|
|
|
|
`;
|
|
|
|
|
|
|
|
|
|
const tableCount = tableCountResult[0]?.count ?? 0;
|
|
|
|
|
|
|
|
|
|
if (journal[0]?.regclass) {
|
|
|
|
|
return { migrated: false, reason: "already-migrated", tableCount };
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
if (tableCount > 0) {
|
|
|
|
|
return { migrated: false, reason: "not-empty-no-migration-journal", tableCount };
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
const db = drizzlePg(sql);
|
|
|
|
|
const migrationsFolder = new URL("./migrations", import.meta.url).pathname;
|
|
|
|
|
await migratePg(db, { migrationsFolder });
|
|
|
|
|
|
|
|
|
|
return { migrated: true, reason: "migrated-empty-db", tableCount: 0 };
|
|
|
|
|
} finally {
|
|
|
|
|
await sql.end();
|
|
|
|
|
}
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
export async function ensurePostgresDatabase(
|
|
|
|
|
url: string,
|
|
|
|
|
databaseName: string,
|
|
|
|
|
): Promise<"created" | "exists"> {
|
|
|
|
|
if (!/^[A-Za-z_][A-Za-z0-9_]*$/.test(databaseName)) {
|
|
|
|
|
throw new Error(`Unsafe database name: ${databaseName}`);
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
const sql = postgres(url, { max: 1 });
|
|
|
|
|
try {
|
|
|
|
|
const existing = await sql<{ one: number }[]>`
|
|
|
|
|
select 1 as one from pg_database where datname = ${databaseName} limit 1
|
|
|
|
|
`;
|
|
|
|
|
if (existing.length > 0) return "exists";
|
|
|
|
|
|
|
|
|
|
await sql.unsafe(`create database "${databaseName}"`);
|
|
|
|
|
return "created";
|
|
|
|
|
} finally {
|
|
|
|
|
await sql.end();
|
|
|
|
|
}
|
2026-02-16 13:31:52 -06:00
|
|
|
}
|
|
|
|
|
|
|
|
|
|
export type Db = ReturnType<typeof createDb>;
|