UNPKG

postgres-pool

Version:

Node postgres connection pool implementation for node-pg

301 lines (296 loc) 14.4 kB
import { EventEmitter } from 'node:events'; import { ConnectionOptions } from 'node:tls'; import pg, { QueryResultRow, QueryResult } from 'pg'; declare const assignmentCompatibilityHack: unique symbol; interface TypeRecord<T, U, V> { ' _emitterType'?: T; ' _eventsType'?: U; ' _emitType'?: V; } declare type InnerEEMethodReturnType<T, TValue, FValue> = T extends (...args: any[]) => any ? ReturnType<T> extends void | undefined ? FValue : TValue : FValue; declare type EEMethodReturnType<T, S extends string, TValue, FValue = void> = S extends keyof T ? InnerEEMethodReturnType<T[S], TValue, FValue> : FValue; declare type ListenerType<T> = [T] extends [(...args: infer U) => any] ? U : [T] extends [void] ? [] : [T]; declare type OverriddenMethods<TEmitter, TEventRecord, TEmitRecord = TEventRecord> = { on<P extends keyof TEventRecord, T>(this: T, event: P, listener: (...args: ListenerType<TEventRecord[P]>) => void): EEMethodReturnType<TEmitter, 'on', T>; on(event: typeof assignmentCompatibilityHack, listener: (...args: any[]) => any): void; addListener<P extends keyof TEventRecord, T>(this: T, event: P, listener: (...args: ListenerType<TEventRecord[P]>) => void): EEMethodReturnType<TEmitter, 'addListener', T>; addListener(event: typeof assignmentCompatibilityHack, listener: (...args: any[]) => any): void; addEventListener<P extends keyof TEventRecord, T>(this: T, event: P, listener: (...args: ListenerType<TEventRecord[P]>) => void): EEMethodReturnType<TEmitter, 'addEventListener', T>; addEventListener(event: typeof assignmentCompatibilityHack, listener: (...args: any[]) => any): void; removeListener<P extends keyof TEventRecord, T>(this: T, event: P, listener: (...args: any[]) => any): EEMethodReturnType<TEmitter, 'removeListener', T>; removeListener(event: typeof assignmentCompatibilityHack, listener: (...args: any[]) => any): void; removeEventListener<P extends keyof TEventRecord, T>(this: T, event: P, listener: (...args: any[]) => any): EEMethodReturnType<TEmitter, 'removeEventListener', T>; removeEventListener(event: typeof assignmentCompatibilityHack, listener: (...args: any[]) => any): void; once<P extends keyof TEventRecord, T>(this: T, event: P, listener: (...args: ListenerType<TEventRecord[P]>) => void): EEMethodReturnType<TEmitter, 'once', T>; once(event: typeof assignmentCompatibilityHack, listener: (...args: any[]) => any): void; emit<P extends keyof TEmitRecord, T>(this: T, event: P, ...args: ListenerType<TEmitRecord[P]>): EEMethodReturnType<TEmitter, 'emit', T>; emit(event: typeof assignmentCompatibilityHack, ...args: any[]): void; }; declare type OverriddenKeys = keyof OverriddenMethods<any, any, any>; declare type StrictEventEmitter<TEmitterType, TEventRecord, TEmitRecord = TEventRecord, UnneededMethods extends Exclude<OverriddenKeys, keyof TEmitterType> = Exclude<OverriddenKeys, keyof TEmitterType>, NeededMethods extends Exclude<OverriddenKeys, UnneededMethods> = Exclude<OverriddenKeys, UnneededMethods>> = TypeRecord<TEmitterType, TEventRecord, TEmitRecord> & Pick<TEmitterType, Exclude<keyof TEmitterType, OverriddenKeys>> & Pick<OverriddenMethods<TEmitterType, TEventRecord, TEmitRecord>, NeededMethods>; declare class PostgresPoolError extends Error { code: string; constructor(message: string, code: string); } interface SslSettings { /** * TLS options for the underlying socket connection. */ ssl?: ConnectionOptions; } interface SslSettingsOrAwsRdsSsl { /** * TLS options for the underlying socket connection. * NOTE: `aws-rds` sets up strict tls connection details for connecting to AWS RDS instances */ ssl?: ConnectionOptions | 'aws-rds'; } interface PoolOptionsBase { /** * Number of connections to store in the pool */ poolSize: number; /** * Milliseconds until an idle connection is closed and removed from the active connection pool */ idleTimeoutMillis: number; /** * Milliseconds to wait for an available connection before throwing an error that no connection is available */ waitForAvailableConnectionTimeoutMillis: number; /** * Milliseconds to wait to connect to postgres */ connectionTimeoutMillis: number; /** * Number of retries to attempt when there's an error matching `retryConnectionErrorCodes`. A value of 0 * will disable connection retry. */ retryConnectionMaxRetries: number; /** * Milliseconds to wait between retry connection attempts after receiving a connection error with code * that matches `retryConnectionErrorCodes`. A value of 0 will try reconnecting immediately. */ retryConnectionWaitMillis: number; /** * Error codes to trigger a connection retry. Eg. ENOTFOUND, EAI_AGAIN */ retryConnectionErrorCodes: string[]; /** * If connect should be retried when the database throws "the database system is starting up" * NOTE: This typically happens during a fail over scenario when a read-replica is being promoted to master */ reconnectOnDatabaseIsStartingError: boolean; /** * Milliseconds to wait between retry connection attempts while the database is starting up. Allows you to throttle * how many retries should happen until databaseStartupTimeoutMillis expires. A value of 0 will * retry the query immediately. */ waitForDatabaseStartupMillis: number; /** * If connection attempts continually return "the database system is starting up", this is the total number of milliseconds * to wait until an error is thrown. */ databaseStartupTimeoutMillis: number; /** * If the query should be retried when the database throws "cannot execute X in a read-only transaction" * NOTE: This typically happens during a fail over scenario when a read-replica is being promoted to master */ reconnectOnReadOnlyTransactionError: boolean; /** * Milliseconds to wait between retry queries while the connection is marked as read-only. Allows you to throttle * how many retries should happen until readOnlyTransactionReconnectTimeoutMillis expires. A value of 0 will * try reconnecting immediately. */ waitForReconnectReadOnlyTransactionMillis: number; /** * If queries continually return "cannot execute X in a read-only transaction", this is the total number of * milliseconds to wait until an error is thrown. */ readOnlyTransactionReconnectTimeoutMillis: number; /** * If the query should be retried when the database throws "Client has encountered a connection error and is not queryable" * NOTE: This typically happens during a fail-over scenario with the cluster */ reconnectOnConnectionError: boolean; /** * Milliseconds to wait between retry queries after receiving a connection error. Allows you to throttle * how many retries should happen until connectionReconnectTimeoutMillis expires. A value of 0 will * try reconnecting immediately. */ waitForReconnectConnectionMillis: number; /** * If queries continually return "Client has encountered a connection error and is not queryable", this is the total number of * milliseconds to wait until an error is thrown. */ connectionReconnectTimeoutMillis: number; /** * Specifies the regular expression to find named parameters in a query */ namedParameterFindRegExp: RegExp; /** * Returns the regular expression used to replace a named parameter in a query */ getNamedParameterReplaceRegExp: (namedParameter: string) => RegExp; /** * Gets the name of a named parameter without the symbols. This should correspond to the key in the query value object */ getNamedParameterName: (namedParameterWithSymbols: string) => string; /** * Throw an error if a query takes longer than the specified milliseconds */ query_timeout?: number; /** * Abort a query statement if it takes longer than the specified milliseconds */ statement_timeout?: number; } interface PoolOptionsExplicit { host: string; database: string; user?: string; password?: string; port?: number; poolSize?: number; idleTimeoutMillis?: number; waitForAvailableConnectionTimeoutMillis?: number; connectionTimeoutMillis?: number; retryConnectionMaxRetries?: number; retryConnectionWaitMillis?: number; retryConnectionErrorCodes?: string[]; reconnectOnDatabaseIsStartingError?: boolean; waitForDatabaseStartupMillis?: number; databaseStartupTimeoutMillis?: number; reconnectOnReadOnlyTransactionError?: boolean; waitForReconnectReadOnlyTransactionMillis?: number; readOnlyTransactionReconnectTimeoutMillis?: number; reconnectOnConnectionError?: boolean; waitForReconnectConnectionMillis?: number; connectionReconnectTimeoutMillis?: number; namedParameterFindRegExp?: RegExp; getNamedParameterReplaceRegExp?: (namedParameter: string) => RegExp; getNamedParameterName?: (namedParameterWithSymbols: string) => string; query_timeout?: number; statement_timeout?: number; } interface PoolOptionsImplicit { connectionString: string; poolSize?: number; idleTimeoutMillis?: number; waitForAvailableConnectionTimeoutMillis?: number; connectionTimeoutMillis?: number; retryConnectionMaxRetries?: number; retryConnectionWaitMillis?: number; retryConnectionErrorCodes?: string[]; reconnectOnDatabaseIsStartingError?: boolean; waitForDatabaseStartupMillis?: number; databaseStartupTimeoutMillis?: number; reconnectOnReadOnlyTransactionError?: boolean; waitForReconnectReadOnlyTransactionMillis?: number; readOnlyTransactionReconnectTimeoutMillis?: number; reconnectOnConnectionError?: boolean; waitForReconnectConnectionMillis?: number; connectionReconnectTimeoutMillis?: number; namedParameterFindRegExp?: RegExp; getNamedParameterReplaceRegExp?: (namedParameter: string) => RegExp; getNamedParameterName?: (namedParameterWithSymbols: string) => string; query_timeout?: number; statement_timeout?: number; } type PoolClient = pg.Client & { uniqueId: string; idleTimeoutTimer?: NodeJS.Timeout; release: (removeConnection?: boolean) => Promise<void>; errorHandler: (err: Error) => void; }; interface ConnectionAddedToPoolParams { connectionId: PoolClient['uniqueId']; retryAttempt: number; startTime: bigint; } interface PoolEvents { connectionRequestQueued: () => void; connectionRequestDequeued: () => void; connectionAddedToPool: (params: ConnectionAddedToPoolParams) => void; connectionRemovedFromPool: () => void; connectionIdle: () => void; connectionRemovedFromIdlePool: () => void; idleConnectionActivated: () => void; queryDeniedForReadOnlyTransaction: () => void; queryDeniedForConnectionError: () => void; waitingForDatabaseToStart: () => void; retryConnectionOnError: () => void; error: (error: Error, client?: PoolClient) => void; } type PoolEmitter = StrictEventEmitter<EventEmitter, PoolEvents>; declare const Pool_base: new () => PoolEmitter; declare class Pool extends Pool_base { /** * Gets the number of queued requests waiting for a database connection * @returns Number of queued requests */ get waitingCount(): number; /** * Gets the number of idle connections * @returns Number of idle connections */ get idleCount(): number; /** * Gets the total number of connections in the pool * @returns Total number of connections */ get totalCount(): number; protected options: PoolOptionsBase & SslSettings & (PoolOptionsExplicit | PoolOptionsImplicit); protected connectionQueueEventEmitter: EventEmitter; protected connections: string[]; protected idleConnections: PoolClient[]; protected connectionQueue: string[]; protected isEnding: boolean; constructor(options: SslSettingsOrAwsRdsSsl & (PoolOptionsExplicit | PoolOptionsImplicit)); /** * Gets a client connection from the pool. * Note: You must call `.release()` when finished with the client connection object. That will release the connection back to the pool to be used by other requests. * @returns Client connection */ connect(): Promise<PoolClient>; /** * Gets a connection to the database and executes the specified query using named parameters. This method will release the connection back to the pool when the query has finished. * @param {string} text * @param {object} values - Keys represent named parameters in the query * @returns Results from query */ query<TRow extends QueryResultRow = any>(text: string, values: Record<string, any>): Promise<QueryResult<TRow>>; /** * Gets a connection to the database and executes the specified query. This method will release the connection back to the pool when the query has finished. * @param {string} text * @param {object[]} values * @returns Results from query */ query<TRow extends QueryResultRow = any>(text: string, values?: any[]): Promise<QueryResult<TRow>>; /** * Drains the pool of all active client connections and prevents additional connections * @returns */ end(): Promise<void>; /** * Drains the pool of all idle client connections. */ drainIdleConnections(): Promise<void>; private _query; /** * Creates a new client connection to add to the pool * @param {string} connectionId * @param {number} [retryAttempt] * @param {bigint} [createConnectionStartTime] - High-resolution time (in nanoseconds) for when the connection was created * @param {[number,number]} [databaseStartupStartTime] - hrtime when the db was first listed as starting up * @returns Client connection */ private _createConnection; /** * Removes the client connection from the pool and tries to gracefully shut it down * @param {PoolClient} client */ private _removeConnection; } export { Pool, PostgresPoolError }; export type { ConnectionAddedToPoolParams, PoolClient, PoolOptionsBase, PoolOptionsExplicit, PoolOptionsImplicit, SslSettings, SslSettingsOrAwsRdsSsl };