import { existsSync, readFileSync, rmSync } from "node:fs"; import { createServer } from "node:net"; import path from "node:path"; import { ensurePostgresDatabase, getPostgresDataDirectory } from "./client.js"; import { resolveDatabaseTarget } from "./runtime-config.js"; type EmbeddedPostgresInstance = { initialise(): Promise; start(): Promise; stop(): Promise; }; type EmbeddedPostgresCtor = new (opts: { databaseDir: string; user: string; password: string; port: number; persistent: boolean; initdbFlags?: string[]; onLog?: (message: unknown) => void; onError?: (message: unknown) => void; }) => EmbeddedPostgresInstance; export type MigrationConnection = { connectionString: string; source: string; stop: () => Promise; }; function toError(error: unknown, fallbackMessage: string): Error { if (error instanceof Error) return error; if (error === undefined) return new Error(fallbackMessage); if (typeof error === "string") return new Error(`${fallbackMessage}: ${error}`); try { return new Error(`${fallbackMessage}: ${JSON.stringify(error)}`); } catch { return new Error(`${fallbackMessage}: ${String(error)}`); } } function readRunningPostmasterPid(postmasterPidFile: string): number | null { if (!existsSync(postmasterPidFile)) return null; try { const pid = Number(readFileSync(postmasterPidFile, "utf8").split("\n")[0]?.trim()); if (!Number.isInteger(pid) || pid <= 0) return null; process.kill(pid, 0); return pid; } catch { return null; } } function readPidFilePort(postmasterPidFile: string): number | null { if (!existsSync(postmasterPidFile)) return null; try { const lines = readFileSync(postmasterPidFile, "utf8").split("\n"); const port = Number(lines[3]?.trim()); return Number.isInteger(port) && port > 0 ? port : null; } catch { return null; } } async function isPortInUse(port: number): Promise { return await new Promise((resolve) => { const server = createServer(); server.unref(); server.once("error", (error: NodeJS.ErrnoException) => { resolve(error.code === "EADDRINUSE"); }); server.listen(port, "127.0.0.1", () => { server.close(); resolve(false); }); }); } async function findAvailablePort(startPort: number): Promise { const maxLookahead = 20; let port = startPort; for (let i = 0; i < maxLookahead; i += 1, port += 1) { if (!(await isPortInUse(port))) return port; } throw new Error( `Embedded PostgreSQL could not find a free port from ${startPort} to ${startPort + maxLookahead - 1}`, ); } async function loadEmbeddedPostgresCtor(): Promise { try { const mod = await import("embedded-postgres"); return mod.default as EmbeddedPostgresCtor; } catch { throw new Error( "Embedded PostgreSQL support requires dependency `embedded-postgres`. Reinstall dependencies and try again.", ); } } async function ensureEmbeddedPostgresConnection( dataDir: string, preferredPort: number, ): Promise { const EmbeddedPostgres = await loadEmbeddedPostgresCtor(); const selectedPort = await findAvailablePort(preferredPort); const postmasterPidFile = path.resolve(dataDir, "postmaster.pid"); const pgVersionFile = path.resolve(dataDir, "PG_VERSION"); const runningPid = readRunningPostmasterPid(postmasterPidFile); const runningPort = readPidFilePort(postmasterPidFile); const preferredAdminConnectionString = `postgres://paperclip:paperclip@127.0.0.1:${preferredPort}/postgres`; if (!runningPid && existsSync(pgVersionFile)) { try { const actualDataDir = await getPostgresDataDirectory(preferredAdminConnectionString); const matchesDataDir = typeof actualDataDir === "string" && path.resolve(actualDataDir) === path.resolve(dataDir); if (!matchesDataDir) { throw new Error("reachable postgres does not use the expected embedded data directory"); } await ensurePostgresDatabase(preferredAdminConnectionString, "paperclip"); process.emitWarning( `Adopting an existing PostgreSQL instance on port ${preferredPort} for embedded data dir ${dataDir} because postmaster.pid is missing.`, ); return { connectionString: `postgres://paperclip:paperclip@127.0.0.1:${preferredPort}/paperclip`, source: `embedded-postgres@${preferredPort}`, stop: async () => {}, }; } catch { // Fall through and attempt to start the configured embedded cluster. } } if (runningPid) { const port = runningPort ?? preferredPort; const adminConnectionString = `postgres://paperclip:paperclip@127.0.0.1:${port}/postgres`; await ensurePostgresDatabase(adminConnectionString, "paperclip"); return { connectionString: `postgres://paperclip:paperclip@127.0.0.1:${port}/paperclip`, source: `embedded-postgres@${port}`, stop: async () => {}, }; } const instance = new EmbeddedPostgres({ databaseDir: dataDir, user: "paperclip", password: "paperclip", port: selectedPort, persistent: true, initdbFlags: ["--encoding=UTF8", "--locale=C"], onLog: () => {}, onError: () => {}, }); if (!existsSync(path.resolve(dataDir, "PG_VERSION"))) { try { await instance.initialise(); } catch (error) { throw toError( error, `Failed to initialize embedded PostgreSQL cluster in ${dataDir} on port ${selectedPort}`, ); } } if (existsSync(postmasterPidFile)) { rmSync(postmasterPidFile, { force: true }); } try { await instance.start(); } catch (error) { throw toError(error, `Failed to start embedded PostgreSQL on port ${selectedPort}`); } const adminConnectionString = `postgres://paperclip:paperclip@127.0.0.1:${selectedPort}/postgres`; await ensurePostgresDatabase(adminConnectionString, "paperclip"); return { connectionString: `postgres://paperclip:paperclip@127.0.0.1:${selectedPort}/paperclip`, source: `embedded-postgres@${selectedPort}`, stop: async () => { await instance.stop(); }, }; } export async function resolveMigrationConnection(): Promise { const target = resolveDatabaseTarget(); if (target.mode === "postgres") { return { connectionString: target.connectionString, source: target.source, stop: async () => {}, }; } return ensureEmbeddedPostgresConnection(target.dataDir, target.port); }