Review funkt
This commit is contained in:
207
node_modules/bullmq/dist/esm/postgres/postgres-connection.js
generated
vendored
Normal file
207
node_modules/bullmq/dist/esm/postgres/postgres-connection.js
generated
vendored
Normal file
@@ -0,0 +1,207 @@
|
||||
import { __rest } from "tslib";
|
||||
import { EventEmitter } from 'events';
|
||||
import { isPgPool, } from './pg-types';
|
||||
import { DEFAULT_SCHEMA, quoteSchemaName, runMigrations } from './migrator';
|
||||
/**
|
||||
* Lazily loads the optional `pg` (node-postgres) driver. Redis-only users never
|
||||
* hit this path, so they never need `pg` installed.
|
||||
*
|
||||
* Only reached when the caller passes a config/connection string (not an
|
||||
* already-constructed `pg.Pool`). In native ESM environments where `require` is
|
||||
* unavailable, callers should pass a `pg.Pool` instance instead.
|
||||
*/
|
||||
function loadPgModule() {
|
||||
try {
|
||||
if (typeof require === 'function') {
|
||||
return require('pg');
|
||||
}
|
||||
}
|
||||
catch (_a) {
|
||||
// Fall through to the friendly error below.
|
||||
}
|
||||
throw new Error("The PostgreSQL backend could not load the optional 'pg' package. " +
|
||||
'Install it with `npm install pg`. In a native ESM environment, pass an ' +
|
||||
'already-constructed `pg.Pool` instance as the connection instead of a ' +
|
||||
'config object or connection 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 class PostgresConnection extends EventEmitter {
|
||||
constructor(connection) {
|
||||
super();
|
||||
/**
|
||||
* `true` when {@link listenClient} is a standalone `pg.Client` we must `end()`
|
||||
* (owned pool); `false` when it is a pooled client we must `release()`.
|
||||
*/
|
||||
this.listenClientIsStandalone = false;
|
||||
if (isPgPool(connection)) {
|
||||
this.pool = connection;
|
||||
this.ownsPool = false;
|
||||
this.schema = DEFAULT_SCHEMA;
|
||||
this.skipVersionCheck = false;
|
||||
this.pgModule = undefined;
|
||||
this.listenClientConfig = undefined;
|
||||
}
|
||||
else {
|
||||
const pg = loadPgModule();
|
||||
const _a = typeof connection === 'string'
|
||||
? {
|
||||
schema: undefined,
|
||||
skipVersionCheck: undefined,
|
||||
connectionString: connection,
|
||||
}
|
||||
: connection, { schema, skipVersionCheck } = _a, poolConfig = __rest(_a, ["schema", "skipVersionCheck"]);
|
||||
this.schema = schema !== null && schema !== void 0 ? schema : DEFAULT_SCHEMA;
|
||||
this.skipVersionCheck = skipVersionCheck !== null && skipVersionCheck !== void 0 ? skipVersionCheck : false;
|
||||
// Validate early so a bad schema name fails fast (and before any DDL).
|
||||
const quotedSchema = quoteSchemaName(this.schema);
|
||||
// Pin every pooled connection's search_path to the schema so the `.sql`
|
||||
// command files use unqualified, portable names. Quoted to match the
|
||||
// migration's quoted CREATE SCHEMA (case-preserving).
|
||||
const searchPathOption = `-c search_path=${quotedSchema}`;
|
||||
const existingOptions = poolConfig.options;
|
||||
const resolvedConfig = Object.assign(Object.assign({}, poolConfig), { options: existingOptions
|
||||
? `${existingOptions} ${searchPathOption}`
|
||||
: searchPathOption });
|
||||
this.pool = new pg.Pool(resolvedConfig);
|
||||
this.ownsPool = true;
|
||||
// Keep the means to build a dedicated LISTEN connection on demand.
|
||||
this.pgModule = pg;
|
||||
this.listenClientConfig = resolvedConfig;
|
||||
}
|
||||
// The pool emits 'error' for idle clients that drop; surface it but never
|
||||
// let it crash the process — hence the guarded {@link emitError} (a bare
|
||||
// `emit('error')` with no listeners throws).
|
||||
this.pool.on('error', err => this.emitError(err));
|
||||
}
|
||||
/**
|
||||
* 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}.
|
||||
*/
|
||||
emitError(err) {
|
||||
if (this.listenerCount('error') > 0) {
|
||||
this.emit('error', err);
|
||||
}
|
||||
}
|
||||
/**
|
||||
* 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}).
|
||||
*/
|
||||
async waitUntilReady() {
|
||||
if (!this.readyPromise) {
|
||||
this.readyPromise = this.bootstrap();
|
||||
}
|
||||
return this.readyPromise;
|
||||
}
|
||||
async bootstrap() {
|
||||
const client = await this.pool.connect();
|
||||
try {
|
||||
await runMigrations(client, this.schema, {
|
||||
skipVersionCheck: this.skipVersionCheck,
|
||||
});
|
||||
}
|
||||
finally {
|
||||
client.release();
|
||||
}
|
||||
// Defer so listeners attached synchronously after construction still fire.
|
||||
setTimeout(() => this.emit('ready'), 0);
|
||||
}
|
||||
/**
|
||||
* 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`).
|
||||
*/
|
||||
async getListenClient() {
|
||||
// Memoize the establishment promise (not just the resolved client) so two
|
||||
// callers racing on the first use don't each open a connection — the
|
||||
// `await` below is exactly where a second caller would otherwise slip in.
|
||||
if (!this.listenClientPromise) {
|
||||
this.listenClientPromise = (async () => {
|
||||
if (this.pgModule && this.listenClientConfig) {
|
||||
const client = new this.pgModule.Client(this.listenClientConfig);
|
||||
await client.connect();
|
||||
client.on('error', err => this.emitError(err));
|
||||
this.listenClientIsStandalone = true;
|
||||
this.listenClient = client;
|
||||
return client;
|
||||
}
|
||||
else {
|
||||
const client = await this.pool.connect();
|
||||
client.on('error', err => this.emitError(err));
|
||||
this.listenClientIsStandalone = false;
|
||||
this.listenClient = client;
|
||||
return client;
|
||||
}
|
||||
})();
|
||||
}
|
||||
return this.listenClientPromise;
|
||||
}
|
||||
/**
|
||||
* Truthy once {@link PostgresConnection.close} has begun.
|
||||
*/
|
||||
get isClosing() {
|
||||
return this.closing;
|
||||
}
|
||||
/**
|
||||
* Closes the connection: releases the `LISTEN` client and (if owned) ends the
|
||||
* pool. Safe to call multiple times.
|
||||
*/
|
||||
async close() {
|
||||
if (!this.closing) {
|
||||
this.closing = (async () => {
|
||||
var _a, _b;
|
||||
// Await any in-flight establishment so we never leak a LISTEN client
|
||||
// whose `getListenClient` promise was still pending when close() ran.
|
||||
const client = (_a = this.listenClient) !== null && _a !== void 0 ? _a : (await ((_b = this.listenClientPromise) === null || _b === void 0 ? void 0 : _b.catch(() => undefined)));
|
||||
this.listenClientPromise = undefined;
|
||||
if (client) {
|
||||
client.removeAllListeners();
|
||||
if (this.listenClientIsStandalone) {
|
||||
// Standalone dedicated connection: end it outright.
|
||||
await client.end();
|
||||
}
|
||||
else {
|
||||
// Pooled client: return it to the pool.
|
||||
client.release();
|
||||
}
|
||||
this.listenClient = undefined;
|
||||
}
|
||||
if (this.ownsPool) {
|
||||
await this.pool.end();
|
||||
}
|
||||
this.emit('close');
|
||||
})();
|
||||
}
|
||||
return this.closing;
|
||||
}
|
||||
/**
|
||||
* Forcibly tears down the connection. For PostgreSQL there is no distinct
|
||||
* "disconnect without waiting" semantics beyond closing, so this delegates to
|
||||
* {@link PostgresConnection.close}.
|
||||
*/
|
||||
async disconnect() {
|
||||
return this.close();
|
||||
}
|
||||
}
|
||||
Reference in New Issue
Block a user