@clickup/ent-framework
Version:
A PostgreSQL graph-database-alike library with microsharding and row-level security
145 lines • 5.89 kB
JavaScript
"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