| import fs from "node:fs"; |
| import net from "node:net"; |
| import os from "node:os"; |
| import path from "node:path"; |
| import { applyPendingMigrations, ensurePostgresDatabase } from "./client.js"; |
|
|
| type EmbeddedPostgresInstance = { |
| initialise(): Promise<void>; |
| start(): Promise<void>; |
| stop(): Promise<void>; |
| }; |
|
|
| 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 EmbeddedPostgresTestSupport = { |
| supported: boolean; |
| reason?: string; |
| }; |
|
|
| export type EmbeddedPostgresTestDatabase = { |
| connectionString: string; |
| cleanup(): Promise<void>; |
| }; |
|
|
| let embeddedPostgresSupportPromise: Promise<EmbeddedPostgresTestSupport> | null = null; |
|
|
| async function getEmbeddedPostgresCtor(): Promise<EmbeddedPostgresCtor> { |
| const mod = await import("embedded-postgres"); |
| return mod.default as EmbeddedPostgresCtor; |
| } |
|
|
| async function getAvailablePort(): Promise<number> { |
| return await new Promise((resolve, reject) => { |
| const server = net.createServer(); |
| server.unref(); |
| server.on("error", reject); |
| server.listen(0, "127.0.0.1", () => { |
| const address = server.address(); |
| if (!address || typeof address === "string") { |
| server.close(() => reject(new Error("Failed to allocate test port"))); |
| return; |
| } |
| const { port } = address; |
| server.close((error) => { |
| if (error) reject(error); |
| else resolve(port); |
| }); |
| }); |
| }); |
| } |
|
|
| function formatEmbeddedPostgresError(error: unknown): string { |
| if (error instanceof Error && error.message.length > 0) return error.message; |
| if (typeof error === "string" && error.length > 0) return error; |
| return "embedded Postgres startup failed"; |
| } |
|
|
| async function probeEmbeddedPostgresSupport(): Promise<EmbeddedPostgresTestSupport> { |
| const dataDir = fs.mkdtempSync(path.join(os.tmpdir(), "paperclip-embedded-postgres-probe-")); |
| const port = await getAvailablePort(); |
| const EmbeddedPostgres = await getEmbeddedPostgresCtor(); |
| const instance = new EmbeddedPostgres({ |
| databaseDir: dataDir, |
| user: "paperclip", |
| password: "paperclip", |
| port, |
| persistent: true, |
| initdbFlags: ["--encoding=UTF8", "--locale=C", "--lc-messages=C"], |
| onLog: () => {}, |
| onError: () => {}, |
| }); |
|
|
| try { |
| await instance.initialise(); |
| await instance.start(); |
| return { supported: true }; |
| } catch (error) { |
| return { |
| supported: false, |
| reason: formatEmbeddedPostgresError(error), |
| }; |
| } finally { |
| await instance.stop().catch(() => {}); |
| fs.rmSync(dataDir, { recursive: true, force: true }); |
| } |
| } |
|
|
| export async function getEmbeddedPostgresTestSupport(): Promise<EmbeddedPostgresTestSupport> { |
| if (!embeddedPostgresSupportPromise) { |
| embeddedPostgresSupportPromise = probeEmbeddedPostgresSupport(); |
| } |
| return await embeddedPostgresSupportPromise; |
| } |
|
|
| export async function startEmbeddedPostgresTestDatabase( |
| tempDirPrefix: string, |
| ): Promise<EmbeddedPostgresTestDatabase> { |
| const dataDir = fs.mkdtempSync(path.join(os.tmpdir(), tempDirPrefix)); |
| const port = await getAvailablePort(); |
| const EmbeddedPostgres = await getEmbeddedPostgresCtor(); |
| const instance = new EmbeddedPostgres({ |
| databaseDir: dataDir, |
| user: "paperclip", |
| password: "paperclip", |
| port, |
| persistent: true, |
| initdbFlags: ["--encoding=UTF8", "--locale=C", "--lc-messages=C"], |
| onLog: () => {}, |
| onError: () => {}, |
| }); |
|
|
| try { |
| await instance.initialise(); |
| await instance.start(); |
|
|
| const adminConnectionString = `postgres://paperclip:paperclip@127.0.0.1:${port}/postgres`; |
| await ensurePostgresDatabase(adminConnectionString, "paperclip"); |
| const connectionString = `postgres://paperclip:paperclip@127.0.0.1:${port}/paperclip`; |
| await applyPendingMigrations(connectionString); |
|
|
| return { |
| connectionString, |
| cleanup: async () => { |
| await instance.stop().catch(() => {}); |
| fs.rmSync(dataDir, { recursive: true, force: true }); |
| }, |
| }; |
| } catch (error) { |
| await instance.stop().catch(() => {}); |
| fs.rmSync(dataDir, { recursive: true, force: true }); |
| throw new Error( |
| `Failed to start embedded PostgreSQL test database: ${formatEmbeddedPostgresError(error)}`, |
| ); |
| } |
| } |
|
|