From fa73f59c62a77582fb9fd2d88740040bfb11a36e Mon Sep 17 00:00:00 2001 From: Kristof Siket Date: Mon, 2 Mar 2026 12:04:38 +0100 Subject: [PATCH 1/3] Migrate connection string from deprecated apiKeys to new connections model Read `connections[0].endpoints.direct.connectionString` from the Management API response instead of manually constructing a DSN from `apiKeys[0].directConnection` fields. Falls back to the legacy path when `connections` is absent for backward compatibility with older API versions. Closes PTL-1046 --- create-db/src/core/database.ts | 49 +++++++++++++++++++--------------- create-db/src/types.ts | 20 ++++++++++++++ 2 files changed, 48 insertions(+), 21 deletions(-) diff --git a/create-db/src/core/database.ts b/create-db/src/core/database.ts index f0f2117..4c1f622 100644 --- a/create-db/src/core/database.ts +++ b/create-db/src/core/database.ts @@ -1,7 +1,30 @@ import { randomUUID } from "crypto"; -import type { CreateDatabaseResult, ApiResponse } from "../types.js"; +import type { ApiResponse, CreateDatabaseResult, DatabaseRecord } from "../types.js"; import { sendAnalytics } from "../utils/analytics.js"; +function buildLegacyConnectionString( + result: ApiResponse, + database: DatabaseRecord | undefined +): string | null { + const apiKeys = database?.apiKeys; + const directConnDetails = result.data + ? apiKeys?.[0]?.directConnection + : result.databases?.[0]?.apiKeys?.[0]?.ppgDirectConnection; + + if (!directConnDetails?.host) return null; + + const user = directConnDetails.user + ? encodeURIComponent(String(directConnDetails.user)) + : ""; + const pass = directConnDetails.pass + ? encodeURIComponent(String(directConnDetails.pass)) + : ""; + const port = directConnDetails.port ? `:${directConnDetails.port}` : ""; + const dbName = directConnDetails.database || "postgres"; + + return `postgresql://${user}:${pass}@${directConnDetails.host}${port}/${dbName}?sslmode=require`; +} + export function getCommandName(): string { const executable = process.argv[1] || "create-db"; if (executable.includes("create-pg")) return "create-pg"; @@ -116,27 +139,11 @@ export async function createDatabaseCore( const database = result.data?.database ?? result.databases?.[0]; const projectId = result.data?.id ?? result.id ?? ""; - const apiKeys = database?.apiKeys; - const directConnDetails = result.data - ? apiKeys?.[0]?.directConnection - : result.databases?.[0]?.apiKeys?.[0]?.ppgDirectConnection; - - const directUser = directConnDetails?.user - ? encodeURIComponent(String(directConnDetails.user)) - : ""; - const directPass = directConnDetails?.pass - ? encodeURIComponent(String(directConnDetails.pass)) - : ""; - const directHost = directConnDetails?.host; - const directPort = directConnDetails?.port - ? `:${directConnDetails.port}` - : ""; - const directDbName = directConnDetails?.database || "postgres"; - + const connection = database?.connections?.[0]; const connectionString = - directConnDetails && directHost - ? `postgresql://${directUser}:${directPass}@${directHost}${directPort}/${directDbName}?sslmode=require` - : null; + connection?.endpoints?.direct?.connectionString + ?? connection?.endpoints?.pooled?.connectionString + ?? buildLegacyConnectionString(result, database); const claimUrl = `${claimDbWorkerUrl}/claim?projectID=${projectId}&utm_source=${userAgent || getCommandName()}&utm_medium=cli`; diff --git a/create-db/src/types.ts b/create-db/src/types.ts index 33375c4..7cb97ae 100644 --- a/create-db/src/types.ts +++ b/create-db/src/types.ts @@ -80,11 +80,31 @@ export interface ApiResponseData { database?: DatabaseRecord; } +export interface ConnectionEndpoint { + host: string; + port: number; + connectionString?: string; +} + +export interface Connection { + id: string; + type: string; + name: string; + kind: "postgres" | "accelerate"; + endpoints: { + direct?: ConnectionEndpoint; + pooled?: ConnectionEndpoint; + accelerate?: ConnectionEndpoint; + }; + database: { id: string; name: string }; +} + export interface DatabaseRecord { name?: string; region?: { id?: string; }; + connections?: Connection[]; apiKeys?: ApiKey[]; } From 0ba6ca299733c49eae7f2cd976179b5c5e51c9e0 Mon Sep 17 00:00:00 2001 From: Kristof Siket Date: Mon, 2 Mar 2026 12:22:37 +0100 Subject: [PATCH 2/3] Use .find() to select a connection with a usable connectionString The API doesn't guarantee ordering of connections[], so the first entry might be an accelerate connection without direct/pooled endpoints. Use .find() to locate a connection that has a connectionString available. --- create-db/src/core/database.ts | 6 +++++- 1 file changed, 5 insertions(+), 1 deletion(-) diff --git a/create-db/src/core/database.ts b/create-db/src/core/database.ts index 4c1f622..b67ccbf 100644 --- a/create-db/src/core/database.ts +++ b/create-db/src/core/database.ts @@ -139,7 +139,11 @@ export async function createDatabaseCore( const database = result.data?.database ?? result.databases?.[0]; const projectId = result.data?.id ?? result.id ?? ""; - const connection = database?.connections?.[0]; + const connection = database?.connections?.find( + (c) => + Boolean(c.endpoints?.direct?.connectionString) || + Boolean(c.endpoints?.pooled?.connectionString), + ); const connectionString = connection?.endpoints?.direct?.connectionString ?? connection?.endpoints?.pooled?.connectionString From a62d08ab1bfbaab49bc35f6fc13758f87e7fa582 Mon Sep 17 00:00:00 2001 From: Kristof Siket Date: Mon, 2 Mar 2026 14:59:34 +0100 Subject: [PATCH 3/3] refactor: remove legacy connection string fallback MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit Use connectionString directly from the connections endpoint array. Drop buildLegacyConnectionString helper — the API always returns the connections array with connectionString populated. --- create-db/src/core/database.ts | 27 ++------------------------- 1 file changed, 2 insertions(+), 25 deletions(-) diff --git a/create-db/src/core/database.ts b/create-db/src/core/database.ts index b67ccbf..700712d 100644 --- a/create-db/src/core/database.ts +++ b/create-db/src/core/database.ts @@ -1,30 +1,7 @@ import { randomUUID } from "crypto"; -import type { ApiResponse, CreateDatabaseResult, DatabaseRecord } from "../types.js"; +import type { ApiResponse, CreateDatabaseResult } from "../types.js"; import { sendAnalytics } from "../utils/analytics.js"; -function buildLegacyConnectionString( - result: ApiResponse, - database: DatabaseRecord | undefined -): string | null { - const apiKeys = database?.apiKeys; - const directConnDetails = result.data - ? apiKeys?.[0]?.directConnection - : result.databases?.[0]?.apiKeys?.[0]?.ppgDirectConnection; - - if (!directConnDetails?.host) return null; - - const user = directConnDetails.user - ? encodeURIComponent(String(directConnDetails.user)) - : ""; - const pass = directConnDetails.pass - ? encodeURIComponent(String(directConnDetails.pass)) - : ""; - const port = directConnDetails.port ? `:${directConnDetails.port}` : ""; - const dbName = directConnDetails.database || "postgres"; - - return `postgresql://${user}:${pass}@${directConnDetails.host}${port}/${dbName}?sslmode=require`; -} - export function getCommandName(): string { const executable = process.argv[1] || "create-db"; if (executable.includes("create-pg")) return "create-pg"; @@ -147,7 +124,7 @@ export async function createDatabaseCore( const connectionString = connection?.endpoints?.direct?.connectionString ?? connection?.endpoints?.pooled?.connectionString - ?? buildLegacyConnectionString(result, database); + ?? null; const claimUrl = `${claimDbWorkerUrl}/claim?projectID=${projectId}&utm_source=${userAgent || getCommandName()}&utm_medium=cli`;