import type { ConnectionStringMethod } from './Connect.constants' import type { ConnectionStringPooler } from './Connect.types' export const DEFAULT_PORT = '5432' export const PASSWORD_PLACEHOLDER = '[YOUR-PASSWORD]' /** Appends query params to a connection string, joining with `?` or `&` as needed */ export const appendConnectionStringParams = (uri: string, params: string) => !uri || !params ? uri : `${uri}${uri.includes('?') ? '&' : '?'}${params}` export type ConnectionParams = { host: string port: string user: string database: string /** Raw query string including the leading `?`, or '' when the URI has none */ search: string } export const resolveConnectionString = ({ connectionMethod, useSharedPooler, connectionStringPooler, }: { connectionMethod: ConnectionStringMethod useSharedPooler: boolean connectionStringPooler: ConnectionStringPooler | undefined }) => { if (!connectionStringPooler) return '' if (connectionMethod === 'direct') { return connectionStringPooler.direct ?? '' } if (connectionMethod === 'session') { return connectionStringPooler.sessionShared ?? '' } if (useSharedPooler || !connectionStringPooler.transactionDedicated) { return connectionStringPooler.transactionShared ?? '' } return connectionStringPooler.transactionDedicated ?? '' } export const parseConnectionParams = (connectionString: string): ConnectionParams => { if (!connectionString) { return { host: 'hidden', port: DEFAULT_PORT, user: 'hidden', database: 'hidden', search: '', } } try { const parsed = new URL(connectionString) // The URL parser percent-encodes characters that aren't valid in user-info // (e.g. brackets in the self-hosted `postgres.[POOLER_TENANT_ID]` placeholder). // Decode so the displayed string matches the literal we wrote. const decode = (value: string) => { try { return decodeURIComponent(value) } catch { return value } } return { host: parsed.hostname || 'hidden', port: parsed.port || DEFAULT_PORT, user: parsed.username ? decode(parsed.username) : 'hidden', database: parsed.pathname?.replace(/^\//, '') || 'hidden', search: parsed.search, } } catch (error) { return { host: 'hidden', port: DEFAULT_PORT, user: 'hidden', database: 'hidden', search: '', } } } export const buildSafeConnectionString = ( connectionString: string, params: ConnectionParams ): string => { if (!connectionString) return '' return `postgresql://${params.user}:${PASSWORD_PLACEHOLDER}@${params.host}:${params.port}/${params.database}${params.search}` } export const buildPsqlCommand = (params: ConnectionParams) => params.search ? // Query params (e.g. sslmode) can't be expressed as psql flags, so fall // back to the URI form — psql prompts for the password. `psql "postgresql://${params.user}@${params.host}:${params.port}/${params.database}${params.search}"` : `psql -h ${params.host} -p ${params.port} -d ${params.database} -U ${params.user}` export const buildJdbcString = (params: ConnectionParams) => { // pgJDBC (42.7.4+) spells libpq's `sslnegotiation` as `sslNegotiation` const extraParams = params.search ? `&${params.search.slice(1).replace('sslnegotiation=', 'sslNegotiation=')}` : '' return `jdbc:postgresql://${params.host}:${params.port}/${params.database}?user=${params.user}&password=${PASSWORD_PLACEHOLDER}${extraParams}` } export const buildConnectionStringWithPassword = ( connectionString: string, password: string ): string => { if (!connectionString || !password) return connectionString const encodedPassword = (() => { try { return encodeURIComponent(password) } catch { return password } })() return connectionString.split(PASSWORD_PLACEHOLDER).join(encodedPassword) } export const buildConnectionParameters = (params: ConnectionParams) => [ { key: 'host', value: params.host }, { key: 'port', value: params.port }, { key: 'database', value: params.database }, { key: 'user', value: params.user }, ]