From 7a06a577ce1e4e9e5ccd64fc43ea83009c970013 Mon Sep 17 00:00:00 2001 From: Dotta Date: Fri, 13 Mar 2026 20:56:19 -0500 Subject: [PATCH] Fix dev startup with embedded postgres reuse --- packages/db/package.json | 1 + packages/db/src/migration-runtime.ts | 38 ++++++++------ pnpm-lock.yaml | 3 ++ server/src/index.ts | 76 +++++++++++++++------------- 4 files changed, 69 insertions(+), 49 deletions(-) diff --git a/packages/db/package.json b/packages/db/package.json index 1dae4bde..90f1b3c7 100644 --- a/packages/db/package.json +++ b/packages/db/package.json @@ -35,6 +35,7 @@ "dependencies": { "@paperclipai/shared": "workspace:*", "drizzle-orm": "^0.38.4", + "embedded-postgres": "^18.1.0-beta.16", "postgres": "^3.4.5" }, "devDependencies": { diff --git a/packages/db/src/migration-runtime.ts b/packages/db/src/migration-runtime.ts index e07bdf04..23a15b1c 100644 --- a/packages/db/src/migration-runtime.ts +++ b/packages/db/src/migration-runtime.ts @@ -1,7 +1,5 @@ import { existsSync, readFileSync, rmSync } from "node:fs"; -import { createRequire } from "node:module"; import path from "node:path"; -import { fileURLToPath, pathToFileURL } from "node:url"; import { ensurePostgresDatabase } from "./client.js"; import { resolveDatabaseTarget } from "./runtime-config.js"; @@ -52,17 +50,8 @@ function readPidFilePort(postmasterPidFile: string): number | null { } async function loadEmbeddedPostgresCtor(): Promise { - const require = createRequire(import.meta.url); - const resolveCandidates = [ - path.resolve(fileURLToPath(new URL("../..", import.meta.url))), - path.resolve(fileURLToPath(new URL("../../server", import.meta.url))), - path.resolve(fileURLToPath(new URL("../../cli", import.meta.url))), - process.cwd(), - ]; - try { - const resolvedModulePath = require.resolve("embedded-postgres", { paths: resolveCandidates }); - const mod = await import(pathToFileURL(resolvedModulePath).href); + const mod = await import("embedded-postgres"); return mod.default as EmbeddedPostgresCtor; } catch { throw new Error( @@ -79,6 +68,20 @@ async function ensureEmbeddedPostgresConnection( const postmasterPidFile = path.resolve(dataDir, "postmaster.pid"); const runningPid = readRunningPostmasterPid(postmasterPidFile); const runningPort = readPidFilePort(postmasterPidFile); + const preferredAdminConnectionString = `postgres://paperclip:paperclip@127.0.0.1:${preferredPort}/postgres`; + + if (!runningPid) { + try { + await ensurePostgresDatabase(preferredAdminConnectionString, "paperclip"); + 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; @@ -108,10 +111,15 @@ async function ensureEmbeddedPostgresConnection( if (existsSync(postmasterPidFile)) { rmSync(postmasterPidFile, { force: true }); } - await instance.start(); + try { + await instance.start(); + } catch (error) { + throw new Error( + `Failed to start embedded PostgreSQL at ${dataDir} on port ${preferredPort}: ${error instanceof Error ? error.message : String(error)}`, + ); + } - const adminConnectionString = `postgres://paperclip:paperclip@127.0.0.1:${preferredPort}/postgres`; - await ensurePostgresDatabase(adminConnectionString, "paperclip"); + await ensurePostgresDatabase(preferredAdminConnectionString, "paperclip"); return { connectionString: `postgres://paperclip:paperclip@127.0.0.1:${preferredPort}/paperclip`, diff --git a/pnpm-lock.yaml b/pnpm-lock.yaml index f6820f52..9b83d7cd 100644 --- a/pnpm-lock.yaml +++ b/pnpm-lock.yaml @@ -226,6 +226,9 @@ importers: drizzle-orm: specifier: ^0.38.4 version: 0.38.4(@electric-sql/pglite@0.3.15)(@types/react@19.2.14)(kysely@0.28.11)(pg@8.18.0)(postgres@3.4.8)(react@19.2.4) + embedded-postgres: + specifier: ^18.1.0-beta.16 + version: 18.1.0-beta.16 postgres: specifier: ^3.4.5 version: 3.4.8 diff --git a/server/src/index.ts b/server/src/index.ts index 27b559eb..0b1d0744 100644 --- a/server/src/index.ts +++ b/server/src/index.ts @@ -323,45 +323,53 @@ export async function startServer(): Promise { if (runningPid) { logger.warn(`Embedded PostgreSQL already running; reusing existing process (pid=${runningPid}, port=${port})`); } else { - const detectedPort = await detectPort(configuredPort); - if (detectedPort !== configuredPort) { - logger.warn(`Embedded PostgreSQL port is in use; using next free port (requestedPort=${configuredPort}, selectedPort=${detectedPort})`); - } - port = detectedPort; - logger.info(`Using embedded PostgreSQL because no DATABASE_URL set (dataDir=${dataDir}, port=${port})`); - embeddedPostgres = new EmbeddedPostgres({ - databaseDir: dataDir, - user: "paperclip", - password: "paperclip", - port, - persistent: true, - initdbFlags: ["--encoding=UTF8", "--locale=C"], - onLog: appendEmbeddedPostgresLog, - onError: appendEmbeddedPostgresLog, - }); - - if (!clusterAlreadyInitialized) { + const configuredAdminConnectionString = `postgres://paperclip:paperclip@127.0.0.1:${configuredPort}/postgres`; + try { + await ensurePostgresDatabase(configuredAdminConnectionString, "paperclip"); + logger.warn( + `Embedded PostgreSQL appears to already be reachable without a pid file; reusing existing server on configured port ${configuredPort}`, + ); + } catch { + const detectedPort = await detectPort(configuredPort); + if (detectedPort !== configuredPort) { + logger.warn(`Embedded PostgreSQL port is in use; using next free port (requestedPort=${configuredPort}, selectedPort=${detectedPort})`); + } + port = detectedPort; + logger.info(`Using embedded PostgreSQL because no DATABASE_URL set (dataDir=${dataDir}, port=${port})`); + embeddedPostgres = new EmbeddedPostgres({ + databaseDir: dataDir, + user: "paperclip", + password: "paperclip", + port, + persistent: true, + initdbFlags: ["--encoding=UTF8", "--locale=C"], + onLog: appendEmbeddedPostgresLog, + onError: appendEmbeddedPostgresLog, + }); + + if (!clusterAlreadyInitialized) { + try { + await embeddedPostgres.initialise(); + } catch (err) { + logEmbeddedPostgresFailure("initialise", err); + throw err; + } + } else { + logger.info(`Embedded PostgreSQL cluster already exists (${clusterVersionFile}); skipping init`); + } + + if (existsSync(postmasterPidFile)) { + logger.warn("Removing stale embedded PostgreSQL lock file"); + rmSync(postmasterPidFile, { force: true }); + } try { - await embeddedPostgres.initialise(); + await embeddedPostgres.start(); } catch (err) { - logEmbeddedPostgresFailure("initialise", err); + logEmbeddedPostgresFailure("start", err); throw err; } - } else { - logger.info(`Embedded PostgreSQL cluster already exists (${clusterVersionFile}); skipping init`); + embeddedPostgresStartedByThisProcess = true; } - - if (existsSync(postmasterPidFile)) { - logger.warn("Removing stale embedded PostgreSQL lock file"); - rmSync(postmasterPidFile, { force: true }); - } - try { - await embeddedPostgres.start(); - } catch (err) { - logEmbeddedPostgresFailure("start", err); - throw err; - } - embeddedPostgresStartedByThisProcess = true; } const embeddedAdminConnectionString = `postgres://paperclip:paperclip@127.0.0.1:${port}/postgres`;