import { EventEmitter } from 'events'; import { PgListenClient, PgPool, PgPoolConfig } from './pg-types'; /** * A node-postgres pool config / connection string, optionally carrying the * BullMQ-specific `schema` (the connection-level namespace for all queues) and * `skipVersionCheck` (bypass the minimum-server-version assertion). */ export type PostgresPoolConfig = PgPoolConfig & { schema?: string; skipVersionCheck?: boolean; }; /** * What the user may pass as the PostgreSQL `connection` option: * * - an already-constructed `pg.Pool` instance (we use it as-is and do NOT close * it on `close()` — the caller owns its lifecycle), or * - a node-postgres pool config / connection string (we lazily `require('pg')` * and construct the pool ourselves, owning its lifecycle). * * The optional `schema` (the namespace for all queues) is only read from the * **config-object** form, because that is the only case where we build the pool * ourselves and can pin each connection's `search_path` to it. A bare * connection string or an already-constructed `pg.Pool` always uses * {@link DEFAULT_SCHEMA} — a raw pool cannot carry a `schema`, and we cannot set * the `search_path` on a pool we did not create. To select a different schema, * pass a config object (a connection string can be wrapped as * `{ connectionString, schema }`; a pre-built pool must be configured with the * desired `search_path` by the caller). */ export type PostgresConnectionOptions = PgPool | PostgresPoolConfig | string; /** * Owns the PostgreSQL connection resources for a single backend: * * - a `pg.Pool` for regular, short-lived queries, and * - a dedicated, long-lived `LISTEN` client used by the blocking * "wait for job" primitive (lazily established). * * Lifecycle mirrors {@link RedisConnection}: it is an {@link EventEmitter} that * surfaces normalized `'ready' | 'error' | 'close'` events, exposes * {@link PostgresConnection.waitUntilReady} (which also runs the schema * migrations exactly once, on a dedicated checked-out client) and * {@link PostgresConnection.close}. */ export declare class PostgresConnection extends EventEmitter { readonly pool: PgPool; /** * The PostgreSQL schema (namespace) this connection's queues live in. It is * applied to every pooled connection's `search_path`, so the `.sql` command * files (and the operation functions) reference unqualified names and stay * portable — the schema selects the namespace, never the SQL itself. */ readonly schema: string; /** * `true` when this instance constructed the pool (and must therefore close * it). `false` when the user passed in their own `pg.Pool`. */ private readonly ownsPool; /** * When `true`, the minimum-server-version assertion in {@link runMigrations} * is skipped. Only settable via a config object (a raw `pg.Pool` or a bare * connection string always run the check). */ private readonly skipVersionCheck; private readyPromise; private closing; private listenClient; /** * Memoizes the in-flight {@link PostgresConnection.getListenClient} * establishment so concurrent first-callers (e.g. a backend naming its * connection in `waitUntilReady` while its consume loop also asks for the * LISTEN client) all share one connection instead of each opening a * duplicate. */ private listenClientPromise; /** * When this instance owns the pool, the lazily-required `pg` module and the * resolved client config (with the pinned `search_path`) used to build a * *dedicated standalone* `LISTEN` connection — so a long-lived `LISTEN` never * consumes a pool slot. Undefined when the user passed their own `pg.Pool` * (then the `LISTEN` client is checked out of that pool instead). */ private readonly pgModule; private readonly listenClientConfig; /** * `true` when {@link listenClient} is a standalone `pg.Client` we must `end()` * (owned pool); `false` when it is a pooled client we must `release()`. */ private listenClientIsStandalone; constructor(connection: PostgresConnectionOptions); /** * Forwards an underlying pool / LISTEN-client error as this connection's * `'error'` event, but only when a listener is attached. `EventEmitter.emit` * throws when emitting `'error'` with no listeners, so an unguarded forward * would turn an idle-client error into a hard process crash. Mirrors the * guard in {@link RedisConnection}. */ private emitError; /** * Resolves once the pool is reachable and the schema is up to date. * * Idempotent and memoized: the migration runs exactly once per connection, * on a single dedicated client checked out of the pool (so the migration's * advisory lock and transaction share one session — see {@link runMigrations}). */ waitUntilReady(): Promise; private bootstrap; /** * Returns the dedicated client used for `LISTEN`/`NOTIFY`, establishing it on * first use. * * When this connection owns the pool we use a *standalone* `pg.Client` (its * own dedicated TCP connection) so the long-lived `LISTEN` never consumes a * pool slot — this is what lets the query pool run at `max: 1` without * deadlocking. When the user supplied their own `pg.Pool` we check a client * out of it (so such pools should be sized `>= 2`). */ getListenClient(): Promise; /** * Truthy once {@link PostgresConnection.close} has begun. */ get isClosing(): Promise | undefined; /** * Closes the connection: releases the `LISTEN` client and (if owned) ends the * pool. Safe to call multiple times. */ close(): Promise; /** * Forcibly tears down the connection. For PostgreSQL there is no distinct * "disconnect without waiting" semantics beyond closing, so this delegates to * {@link PostgresConnection.close}. */ disconnect(): Promise; }