@hotmeshio/hotmesh
Version:
Permanent-Memory Workflows & AI Agents
215 lines (214 loc) • 8.7 kB
JavaScript
;
Object.defineProperty(exports, "__esModule", { value: true });
exports.PostgresConnection = void 0;
const __1 = require("..");
const utils_1 = require("../../../modules/utils");
class PostgresConnection extends __1.AbstractConnection {
constructor() {
super(...arguments);
this.defaultOptions = {
host: 'postgres',
port: 5432,
user: 'postgres',
password: 'password',
database: 'hotmesh',
max: 20,
idleTimeoutMillis: 30000,
};
}
/**
* Get comprehensive connection statistics for monitoring taskQueue pooling effectiveness
*/
static getConnectionStats() {
const taskQueueDetails = Array.from(this.taskQueueConnections.entries()).map(([key, connection]) => ({
key,
connectionId: connection.getConnectionId() || 'unknown',
reusedCount: connection.reusedCount || 0,
}));
return {
totalPoolClients: this.poolClientInstances.size,
totalConnections: this.connectionInstances.size,
taskQueueConnections: this.taskQueueConnections.size,
taskQueueDetails,
};
}
/**
* Log current connection statistics - useful for debugging connection pooling
*/
static logConnectionStats(logger) {
const stats = this.getConnectionStats();
const message = `PostgreSQL Connection Stats: ${stats.totalConnections} total connections, ${stats.taskQueueConnections} taskQueue pools, ${stats.totalPoolClients} pool clients`;
if (logger) {
logger.info('postgres-connection-stats', {
...stats,
message,
});
}
else {
console.log(message, stats);
}
}
/**
* Check taskQueue pooling effectiveness - returns metrics about connection reuse
*/
static getPoolingEffectiveness() {
const stats = this.getConnectionStats();
const totalReuses = stats.taskQueueDetails.reduce((sum, detail) => sum + detail.reusedCount, 0);
const averageReusesPerPool = stats.taskQueueConnections > 0
? totalReuses / stats.taskQueueConnections
: 0;
const poolingEfficiency = stats.totalConnections > 0
? stats.taskQueueConnections / stats.totalConnections * 100
: 0;
return {
totalConnections: stats.totalConnections,
taskQueuePools: stats.taskQueueConnections,
totalReuses,
averageReusesPerPool: Math.round(averageReusesPerPool * 100) / 100,
poolingEfficiency: Math.round(poolingEfficiency * 100) / 100,
};
}
async createConnection(clientConstructor, options, config = {}) {
try {
let connection;
if (config.provider === 'postgres.poolclient' ||
PostgresConnection.isPoolClient(clientConstructor)) {
// It's a PoolClient
connection = clientConstructor;
if (config.connect) {
const client = await clientConstructor.connect();
//register the connection singularly to be 'released' later
PostgresConnection.poolClientInstances.add(client);
this.poolClientInstance = client;
}
}
else {
// It's a Client
connection = new clientConstructor(options);
await connection.connect();
await connection.query('SELECT 1');
}
//register the connection statically to be 'ended' later
PostgresConnection.connectionInstances.add(connection);
return connection;
}
catch (error) {
PostgresConnection.logger.error(`postgres-provider-connection-failed`, {
host: options.host ?? 'unknown',
database: options.database ?? 'unknown',
port: options.port ?? 'unknown',
error,
});
throw new Error(`postgres-provider-connection-failed: ${error.message}`);
}
}
getClient() {
if (!this.connection) {
throw new Error('Postgres client is not connected');
}
return this.poolClientInstance || this.connection;
}
/**
* Get the connection ID for monitoring purposes
*/
getConnectionId() {
return this.id;
}
static async disconnectAll() {
//log stats
//this.logConnectionStats();
if (!this.disconnecting) {
this.disconnecting = true;
await this.disconnectPoolClients();
await this.disconnectConnections();
this.taskQueueConnections.clear();
this.disconnecting = false;
}
}
static async disconnectPoolClients() {
Array.from(this.poolClientInstances.values()).map((instance) => {
instance.release();
});
this.poolClientInstances.clear();
}
static async disconnectConnections() {
Array.from(this.connectionInstances.values()).map((instance) => {
instance.end();
});
this.connectionInstances.clear();
}
async closeConnection(connection) {
//no-op (handled by disconnectAll)
}
static isPoolClient(client) {
return !(isNaN(client?.totalCount) && isNaN(client?.idleCount));
}
/**
* Creates a taskQueue-based connection key for connection pooling.
* This allows multiple providers (store, sub, stream) to reuse the same connection
* when they share the same taskQueue and database configuration.
*/
static createTaskQueueConnectionKey(taskQueue, options, provider) {
const configHash = (0, utils_1.hashOptions)(options);
const providerType = provider?.split('.')[0] || 'postgres';
return `${providerType}:${taskQueue || 'default'}:${configHash}`;
}
/**
* Gets or creates a PostgreSQL connection based on taskQueue and database configuration.
* If a connection already exists for the same taskQueue + config, it will be reused.
* This optimization reduces connection overhead for PostgreSQL providers.
*/
static async getOrCreateTaskQueueConnection(id, taskQueue, clientConstructor, options, config = {}) {
// Only use taskQueue pooling for PostgreSQL providers
if (!taskQueue || !config.provider?.startsWith('postgres')) {
return await this.connect(id, clientConstructor, options, config);
}
const connectionKey = this.createTaskQueueConnectionKey(taskQueue, options, config.provider);
// Check if we already have a connection for this taskQueue + config
if (this.taskQueueConnections.has(connectionKey)) {
const existingConnection = this.taskQueueConnections.get(connectionKey);
// Track reuse count for monitoring
existingConnection.reusedCount =
(existingConnection.reusedCount || 0) + 1;
this.logger.debug('postgres-connection-reused', {
connectionKey,
taskQueue,
originalId: existingConnection.id,
newId: id,
reuseCount: existingConnection.reusedCount,
});
return existingConnection;
}
// Create new connection and cache it for the taskQueue
const newConnection = await this.connect(id, clientConstructor, options, config);
// Initialize reuse tracking
newConnection.reusedCount = 0;
this.taskQueueConnections.set(connectionKey, newConnection);
this.logger.debug('postgres-connection-created-for-taskqueue', {
connectionKey,
taskQueue,
connectionId: id,
});
return newConnection;
}
static async getTransactionClient(transactionClient) {
let client;
let type;
if (PostgresConnection.isPoolClient(transactionClient)) {
type = 'poolclient';
client = await transactionClient.connect();
}
else {
type = 'client';
client = transactionClient;
}
return [type, client];
}
}
exports.PostgresConnection = PostgresConnection;
//statically track all clients (//call 'release')
PostgresConnection.poolClientInstances = new Set();
//statically track all connections (//call 'end')
PostgresConnection.connectionInstances = new Set();
//track connections by taskQueue + database config for reuse
PostgresConnection.taskQueueConnections = new Map();