multibridge
Version:
A multi-database connection framework with centralized configuration
195 lines (194 loc) • 8.29 kB
JavaScript
;
var __importDefault = (this && this.__importDefault) || function (mod) {
return (mod && mod.__esModule) ? mod : { "default": mod };
};
Object.defineProperty(exports, "__esModule", { value: true });
exports.executeQuery = executeQuery;
const connectionManager_1 = require("../connections/connectionManager");
const mongodbHelper_1 = require("../helpers/mongodbHelper");
const cassandraHelper_1 = require("../helpers/cassandraHelper");
const loggers_1 = __importDefault(require("./loggers"));
const envConfig_1 = require("../config/envConfig");
const tenantContext_1 = require("../context/tenantContext");
const errors_1 = require("./errors");
/**
* Execute query with timeout
*/
async function executeWithTimeout(promise, timeoutMs, queryInfo) {
if (timeoutMs <= 0) {
return promise;
}
let timeoutId = null;
const timeoutPromise = new Promise((_, reject) => {
timeoutId = setTimeout(() => {
reject(new errors_1.TimeoutError(`Query timeout after ${timeoutMs}ms: ${queryInfo}`, {
timeoutMs,
queryInfo,
}));
}, timeoutMs);
});
try {
const result = await Promise.race([promise, timeoutPromise]);
// Clear timeout if promise resolved first
if (timeoutId) {
clearTimeout(timeoutId);
}
return result;
}
catch (error) {
// Clear timeout on error
if (timeoutId) {
clearTimeout(timeoutId);
}
throw error;
}
}
/**
* Executes a query using the connection determined by the tenant context.
* For PostgreSQL and MySQL, the native connection.query is used.
* For MongoDB and Cassandra, corresponding helper functions are invoked.
*/
async function executeQuery(query, params) {
const tenant = (0, tenantContext_1.getTenant)();
const tenantContext = tenant ? { appid: tenant.appid, orgid: tenant.orgid } : {};
try {
const { connection, dbType } = await (0, connectionManager_1.getConnection)();
switch (dbType) {
case "postgres": {
const timeout = envConfig_1.envConfig.POSTGRES_QUERY_TIMEOUT_MS;
let queryString;
let queryParams;
if (typeof query === "string") {
queryString = query;
queryParams = params;
}
else if (typeof query === "object" && "query" in query && typeof query.query === "string") {
queryString = query.query;
queryParams = params || query.params;
}
else {
throw new errors_1.QueryError("PostgreSQL query must be a string or SQLQuery object", {
...tenantContext,
query,
});
}
const queryPromise = connection.query(queryString, queryParams || []);
const result = await executeWithTimeout(queryPromise, timeout, `PostgreSQL query: ${queryString.substring(0, 100)}`);
loggers_1.default.debug("PostgreSQL query executed", {
...tenantContext,
queryLength: queryString.length,
});
return result;
}
case "mysql": {
const timeout = envConfig_1.envConfig.MYSQL_QUERY_TIMEOUT_MS;
let queryString;
let queryParams;
if (typeof query === "string") {
queryString = query;
queryParams = params;
}
else if (typeof query === "object" && "query" in query && typeof query.query === "string") {
queryString = query.query;
queryParams = params || query.params;
}
else {
throw new errors_1.QueryError("MySQL query must be a string or SQLQuery object", {
...tenantContext,
query,
});
}
const queryPromise = connection.query(queryString, queryParams);
const result = await executeWithTimeout(queryPromise, timeout, `MySQL query: ${queryString.substring(0, 100)}`);
loggers_1.default.debug("MySQL query executed", {
...tenantContext,
queryLength: queryString.length,
});
return result;
}
case "mongodb": {
const timeout = envConfig_1.envConfig.MONGODB_QUERY_TIMEOUT_MS;
let mongoQuery;
if (typeof query === "object" && "collection" in query && "method" in query) {
mongoQuery = query;
}
else {
throw new errors_1.QueryError("MongoDB query must be an object with 'collection' and 'method' properties", {
...tenantContext,
query,
});
}
const queryPromise = (0, mongodbHelper_1.executeMongoQuery)(connection.db, mongoQuery);
const result = await executeWithTimeout(queryPromise, timeout, `MongoDB ${mongoQuery.collection}.${mongoQuery.method}()`);
loggers_1.default.debug("MongoDB query executed", {
...tenantContext,
collection: mongoQuery.collection,
method: mongoQuery.method,
});
return result;
}
case "cassandra": {
const timeout = envConfig_1.envConfig.CASSANDRA_QUERY_TIMEOUT_MS;
let queryString;
let queryParams;
if (typeof query === "string") {
queryString = query;
queryParams = params;
}
else if (typeof query === "object" && "query" in query && typeof query.query === "string") {
queryString = query.query;
queryParams = params || query.params;
}
else {
throw new errors_1.QueryError("Cassandra query must be a string or CassandraQuery object", {
...tenantContext,
query,
});
}
const queryPromise = (0, cassandraHelper_1.executeCassandraQuery)(connection, queryString, queryParams);
const result = await executeWithTimeout(queryPromise, timeout, `Cassandra query: ${queryString.substring(0, 100)}`);
loggers_1.default.debug("Cassandra query executed", {
...tenantContext,
queryLength: queryString.length,
});
return result;
}
default:
throw new errors_1.QueryError(`Unsupported database type: ${dbType}`, {
...tenantContext,
dbType,
});
}
}
catch (error) {
const queryInfo = typeof query === "string"
? query.substring(0, 100)
: typeof query === "object" && "collection" in query
? `${query.collection}.${query.method}()`
: "unknown query";
// Try to get dbType, but don't fail if getConnection fails
let dbType = "unknown";
try {
const connectionData = await (0, connectionManager_1.getConnection)();
dbType = connectionData.dbType;
}
catch {
// Ignore - we're already in error handling
}
loggers_1.default.error(`Error executing query: ${error.message}`, {
...tenantContext,
dbType,
query: queryInfo,
error: error.stack,
});
// Re-throw with context
if (error instanceof errors_1.QueryError || error instanceof errors_1.TimeoutError) {
throw error;
}
throw new errors_1.QueryError(`Query execution failed: ${error.message}`, {
...tenantContext,
query: queryInfo,
originalError: error,
});
}
}