Fix dev startup with embedded postgres reuse

This commit is contained in:
Dotta
2026-03-13 20:56:19 -05:00
parent 920bc4c70f
commit 7a06a577ce
4 changed files with 69 additions and 49 deletions

View File

@@ -35,6 +35,7 @@
"dependencies": { "dependencies": {
"@paperclipai/shared": "workspace:*", "@paperclipai/shared": "workspace:*",
"drizzle-orm": "^0.38.4", "drizzle-orm": "^0.38.4",
"embedded-postgres": "^18.1.0-beta.16",
"postgres": "^3.4.5" "postgres": "^3.4.5"
}, },
"devDependencies": { "devDependencies": {

View File

@@ -1,7 +1,5 @@
import { existsSync, readFileSync, rmSync } from "node:fs"; import { existsSync, readFileSync, rmSync } from "node:fs";
import { createRequire } from "node:module";
import path from "node:path"; import path from "node:path";
import { fileURLToPath, pathToFileURL } from "node:url";
import { ensurePostgresDatabase } from "./client.js"; import { ensurePostgresDatabase } from "./client.js";
import { resolveDatabaseTarget } from "./runtime-config.js"; import { resolveDatabaseTarget } from "./runtime-config.js";
@@ -52,17 +50,8 @@ function readPidFilePort(postmasterPidFile: string): number | null {
} }
async function loadEmbeddedPostgresCtor(): Promise<EmbeddedPostgresCtor> { async function loadEmbeddedPostgresCtor(): Promise<EmbeddedPostgresCtor> {
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 { try {
const resolvedModulePath = require.resolve("embedded-postgres", { paths: resolveCandidates }); const mod = await import("embedded-postgres");
const mod = await import(pathToFileURL(resolvedModulePath).href);
return mod.default as EmbeddedPostgresCtor; return mod.default as EmbeddedPostgresCtor;
} catch { } catch {
throw new Error( throw new Error(
@@ -79,6 +68,20 @@ async function ensureEmbeddedPostgresConnection(
const postmasterPidFile = path.resolve(dataDir, "postmaster.pid"); const postmasterPidFile = path.resolve(dataDir, "postmaster.pid");
const runningPid = readRunningPostmasterPid(postmasterPidFile); const runningPid = readRunningPostmasterPid(postmasterPidFile);
const runningPort = readPidFilePort(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) { if (runningPid) {
const port = runningPort ?? preferredPort; const port = runningPort ?? preferredPort;
@@ -108,10 +111,15 @@ async function ensureEmbeddedPostgresConnection(
if (existsSync(postmasterPidFile)) { if (existsSync(postmasterPidFile)) {
rmSync(postmasterPidFile, { force: true }); 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(preferredAdminConnectionString, "paperclip");
await ensurePostgresDatabase(adminConnectionString, "paperclip");
return { return {
connectionString: `postgres://paperclip:paperclip@127.0.0.1:${preferredPort}/paperclip`, connectionString: `postgres://paperclip:paperclip@127.0.0.1:${preferredPort}/paperclip`,

3
pnpm-lock.yaml generated
View File

@@ -226,6 +226,9 @@ importers:
drizzle-orm: drizzle-orm:
specifier: ^0.38.4 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) 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: postgres:
specifier: ^3.4.5 specifier: ^3.4.5
version: 3.4.8 version: 3.4.8

View File

@@ -323,45 +323,53 @@ export async function startServer(): Promise<StartedServer> {
if (runningPid) { if (runningPid) {
logger.warn(`Embedded PostgreSQL already running; reusing existing process (pid=${runningPid}, port=${port})`); logger.warn(`Embedded PostgreSQL already running; reusing existing process (pid=${runningPid}, port=${port})`);
} else { } else {
const detectedPort = await detectPort(configuredPort); const configuredAdminConnectionString = `postgres://paperclip:paperclip@127.0.0.1:${configuredPort}/postgres`;
if (detectedPort !== configuredPort) { try {
logger.warn(`Embedded PostgreSQL port is in use; using next free port (requestedPort=${configuredPort}, selectedPort=${detectedPort})`); await ensurePostgresDatabase(configuredAdminConnectionString, "paperclip");
} logger.warn(
port = detectedPort; `Embedded PostgreSQL appears to already be reachable without a pid file; reusing existing server on configured port ${configuredPort}`,
logger.info(`Using embedded PostgreSQL because no DATABASE_URL set (dataDir=${dataDir}, port=${port})`); );
embeddedPostgres = new EmbeddedPostgres({ } catch {
databaseDir: dataDir, const detectedPort = await detectPort(configuredPort);
user: "paperclip", if (detectedPort !== configuredPort) {
password: "paperclip", logger.warn(`Embedded PostgreSQL port is in use; using next free port (requestedPort=${configuredPort}, selectedPort=${detectedPort})`);
port, }
persistent: true, port = detectedPort;
initdbFlags: ["--encoding=UTF8", "--locale=C"], logger.info(`Using embedded PostgreSQL because no DATABASE_URL set (dataDir=${dataDir}, port=${port})`);
onLog: appendEmbeddedPostgresLog, embeddedPostgres = new EmbeddedPostgres({
onError: appendEmbeddedPostgresLog, databaseDir: dataDir,
}); user: "paperclip",
password: "paperclip",
if (!clusterAlreadyInitialized) { 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 { try {
await embeddedPostgres.initialise(); await embeddedPostgres.start();
} catch (err) { } catch (err) {
logEmbeddedPostgresFailure("initialise", err); logEmbeddedPostgresFailure("start", err);
throw err; throw err;
} }
} else { embeddedPostgresStartedByThisProcess = true;
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.start();
} catch (err) {
logEmbeddedPostgresFailure("start", err);
throw err;
}
embeddedPostgresStartedByThisProcess = true;
} }
const embeddedAdminConnectionString = `postgres://paperclip:paperclip@127.0.0.1:${port}/postgres`; const embeddedAdminConnectionString = `postgres://paperclip:paperclip@127.0.0.1:${port}/postgres`;