UNPKG

@clickup/ent-framework

Version:

A PostgreSQL graph-database-alike library with microsharding and row-level security

145 lines 5.89 kB
"use strict"; var __importDefault = (this && this.__importDefault) || function (mod) { return (mod && mod.__esModule) ? mod : { "default": mod }; }; var _a, _b; Object.defineProperty(exports, "__esModule", { value: true }); exports.PgClientPool = void 0; const defaults_1 = __importDefault(require("lodash/defaults")); const range_1 = __importDefault(require("lodash/range")); const pg_1 = require("pg"); const misc_1 = require("../internal/misc"); const Ref_1 = require("../internal/Ref"); const PgClient_1 = require("./PgClient"); /** * This class carries connection pooling logic only and delegates the rest to * PgClient base class. * * The idea is that in each particular project, people may have they own classes * derived from PgClient, in case the codebase already has some existing * connection pooling solution. They don't have to use PgClientPool. */ class PgClientPool extends (_b = PgClient_1.PgClient) { constructor(options) { super(options); /** Prewarming periodic timer (if scheduled). */ this.prewarmTimeout = new Ref_1.Ref(null); /** Whether the pool has been ended and is not usable anymore. */ this.ended = new Ref_1.Ref(false); this.options = (0, defaults_1.default)({}, options, this.options, _a.DEFAULT_OPTIONS); this.pool = new this.options.Pool({ allowExitOnIdle: true, ...this.options.config, }) .on("connect", (client) => { // Called only once, after the connection is 1st created. const maxConnLifetimeMs = (0, misc_1.maybeCall)(this.options.maxConnLifetimeMs); if (maxConnLifetimeMs > 0) { client.closeAt = Date.now() + Math.round(maxConnLifetimeMs * (0, misc_1.jitter)((0, misc_1.maybeCall)(this.options.maxConnLifetimeJitter))); } // Sets a "default error" handler to not let errors leak to e.g. Jest // and the outside world as "unhandled error". Appending an additional // error handler to EventEmitter doesn't affect the existing error // handlers anyhow, so should be safe. client.on("error", () => { }); }) .on("error", (error) => // Having this hook prevents node from crashing. this.logSwallowedError({ where: 'Pool.on("error")', error, elapsed: null, importance: "low", })); } async acquireConn() { const conn = await this.pool.connect(); const connReleaseOrig = conn.release.bind(conn); conn.release = (arg) => { // Manage maxConnLifetimeMs manually since it's not supported by the // vanilla node-postgres. const needClose = !!(conn.closeAt && Date.now() > conn.closeAt); return connReleaseOrig(arg !== undefined ? arg : needClose); }; return conn; } poolStats() { return { totalConns: this.pool.totalCount, idleConns: this.pool.idleCount, queuedReqs: this.pool.waitingCount, }; } address() { const { host, port, database } = this.options.config; return (host + (port ? `:${port}` : "") + (database ? `/${database}` : "") + "#" + this.shardName); } logSwallowedError(props) { if (!this.ended.current) { super.logSwallowedError(props); } } async end() { if (this.ended.current) { return; } this.ended.current = true; this.prewarmTimeout.current && clearTimeout(this.prewarmTimeout.current); this.prewarmTimeout.current = null; return this.pool.end(); } isEnded() { return this.ended.current; } prewarm() { if (this.prewarmTimeout.current) { // Already scheduled a prewarm, so skipping. return; } if (!this.options.config.min) { return; } const min = Math.min(this.options.config.min, this.options.config.max ?? Infinity, this.pool.totalCount + ((0, misc_1.maybeCall)(this.options.prewarmIntervalStep) || 1)); const toPrewarm = min - this.pool.waitingCount; if (toPrewarm > 0) { const startTime = performance.now(); (0, range_1.default)(toPrewarm).forEach(() => (0, misc_1.runInVoid)(this.pool.query((0, misc_1.maybeCall)(this.options.prewarmQuery)).catch((error) => this.logSwallowedError({ where: `${this.constructor.name}.prewarm`, error, elapsed: Math.round(performance.now() - startTime), importance: "normal", })))); } this.prewarmTimeout.current = setTimeout(() => { this.prewarmTimeout.current = null; this.prewarm(); }, Math.round((0, misc_1.maybeCall)(this.options.prewarmIntervalMs) * (0, misc_1.jitter)((0, misc_1.maybeCall)(this.options.prewarmIntervalJitter)))).unref(); } } exports.PgClientPool = PgClientPool; _a = PgClientPool; /** Default values for the constructor options. */ PgClientPool.DEFAULT_OPTIONS = { ...Reflect.get(_b, "DEFAULT_OPTIONS", _a), Pool: pg_1.Pool, maxConnLifetimeMs: 0, maxConnLifetimeJitter: 0.5, prewarmIntervalStep: 1, /** The default value is half of the default node-postgres'es * idleTimeoutMillis=10s. Together with 1..1.5x jitter * (prewarmIntervalJitter=0.5), it is still slightly below * idleTimeoutMillis, and thus, doesn't let Ent Framework close the * connections prematurely. */ prewarmIntervalMs: 5000, prewarmIntervalJitter: 0.5, prewarmQuery: 'SELECT 1 AS "prewarmQuery"', }; //# sourceMappingURL=PgClientPool.js.map