import { spawn } from 'node:child_process'; import { Server } from 'node:net'; import type { ConnectionDetails, TunnelConfig } from '../../types/pg/tunnel.js'; import { getPsqlConfigs, sshTunnel } from './bastion.js'; /** * A small wrapper around tunnel-ssh so that other code doesn't have to worry about whether there is or is not a tunnel. */ export declare class Tunnel { private readonly bastionTunnel; private readonly events; /** * Creates a new Tunnel instance. * * @param bastionTunnel - The SSH tunnel server or void if no tunnel is needed */ constructor(bastionTunnel: Server | void); /** * Creates and connects to an SSH tunnel. * * @param connectionDetails - The database connection details with attachment information * @param tunnelConfig - The tunnel configuration object * @param tunnelFn - The function to create the SSH tunnel (default: sshTunnel) * @returns Promise that resolves to a new Tunnel instance */ static connect(connectionDetails: ConnectionDetails, tunnelConfig: TunnelConfig, tunnelFn: typeof sshTunnel): Promise; /** * Closes the tunnel if it exists, or emits a fake close event if no tunnel is needed. * * @returns void */ close(): void; /** * Waits for the tunnel to close. * * @returns Promise that resolves when the tunnel closes * @throws Error if the secure tunnel fails */ waitForClose(): Promise; } export default class PsqlService { private readonly connectionDetails; private readonly getPsqlConfigsFn; private readonly spawnFn; private readonly tunnelFn; constructor(connectionDetails: ConnectionDetails, getPsqlConfigsFn?: typeof getPsqlConfigs, spawnFn?: typeof spawn, tunnelFn?: typeof sshTunnel); /** * Executes a file containing SQL commands using the instance's database connection details. * It uses the `getPsqlConfigs` function to get the configuration for the database and the tunnel, * and then calls the `runWithTunnel` function to execute the file. * * @param file - The path to the SQL file to execute * @param psqlCmdArgs - Additional command-line arguments for psql (default: []) * @returns Promise that resolves to the query result as a string */ execFile(file: string, psqlCmdArgs?: string[]): Promise; /** * Executes a PostgreSQL query using the instance's database connection details. * It uses the `getPsqlConfigs` function to get the configuration for the database and the tunnel, * and then calls the `runWithTunnel` function to execute the query. * * @param query - The SQL query to execute * @param psqlCmdArgs - Additional command-line arguments for psql (default: []) * @returns Promise that resolves to the query result as a string */ execQuery(query: string, psqlCmdArgs?: string[]): Promise; /** * Fetches the PostgreSQL version from the database by executing the `SHOW server_version` query. * * @returns Promise that resolves to the PostgreSQL version as a string (or undefined). */ fetchVersion(): Promise; /** * Executes a PostgreSQL interactive session using the instance's database connection details. * It uses the `getPsqlConfigs` function to get the configuration for the database and the tunnel, * and then calls the `runWithTunnel` function to execute the query. * * @param psqlCmdArgs - Additional command-line arguments for psql (default: []) * @returns Promise that resolves to the query result as a string */ interactiveSession(psqlCmdArgs?: string[]): Promise; /** * Runs the psql command with tunnel support. * * @param tunnelConfig - The tunnel configuration object * @param options - The options for spawning the psql process * @returns Promise that resolves to the query result as a string */ runWithTunnel(tunnelConfig: TunnelConfig, options: Parameters[0]): Promise; /** * Consumes a stream and returns its content as a string. * * @param inputStream - The input stream to consume * @returns Promise that resolves to the stream content as a string */ private consumeStream; /** * Kills a child process if it hasn't been killed already. * According to node.js docs, sending a kill to a process won't cause an error * but could have unintended consequences if the PID gets reassigned. * To be on the safe side, check if the process was already killed before sending the signal. * * @param childProcess - The child process to kill * @param signal - The signal to send to the process * @returns void */ private kill; /** * Creates the options for spawning the psql process for a SQL file execution. * * @param file - The path to the SQL file to execute * @param dbEnv - The database environment variables * @param psqlCmdArgs - Additional command-line arguments for psql (default: []) * @returns Object containing child process options, database environment, and psql arguments */ private psqlFileOptions; /** * Creates the options for spawning the psql process for an interactive psql session. * * @param prompt - The prompt to use for the interactive psql session * @param dbEnv - The database environment variables * @param psqlCmdArgs - Additional command-line arguments for psql (default: []) * @returns Object containing child process options, database environment, and psql arguments */ private psqlInteractiveOptions; /** * Creates the options for spawning the psql process for a single query execution. * * @param query - The SQL query to execute * @param dbEnv - The database environment variables * @param psqlCmdArgs - Additional command-line arguments for psql (default: []) * @returns Object containing child process options, database environment, and psql arguments */ private psqlQueryOptions; /** * Spawns the psql process with the given options. * * @param options - The options for spawning the psql process * @returns The spawned child process */ private spawnPsql; /** * Traps SIGINT so that ctrl+c can be used by psql without killing the parent node process. * You can use ctrl+c in psql to kill running queries while keeping the psql process open. * This code is to stop the parent node process (heroku CLI) from exiting. * If the parent Heroku CLI node process exits, then psql will exit as it is a child process. * * @param childProcess - The child process to forward signals to * @returns Function to restore the original signal handlers */ private trapAndForwardSignalsToChildProcess; /** * Waits for the psql process to exit and handles any errors. * * @param psql - The psql process event emitter * @throws Error if psql exits with non-zero code or if psql command is not found * @returns Promise that resolves to void when psql exits */ private waitForPSQLExit; }