multibridge
Version:
A multi-database connection framework with centralized configuration
87 lines (86 loc) • 4.12 kB
JavaScript
;
var __importDefault = (this && this.__importDefault) || function (mod) {
return (mod && mod.__esModule) ? mod : { "default": mod };
};
Object.defineProperty(exports, "__esModule", { value: true });
exports.runWithTenant = runWithTenant;
exports.getTenant = getTenant;
const async_hooks_1 = require("async_hooks");
const errors_1 = require("../utils/errors");
const connectionManager_1 = require("../connections/connectionManager");
const loggers_1 = __importDefault(require("../utils/loggers"));
const tenantContext = new async_hooks_1.AsyncLocalStorage();
// Validation pattern: alphanumeric, underscore, hyphen, dot (for database names)
const VALID_ID_PATTERN = /^[a-zA-Z0-9_\-\.]+$/;
const MAX_ID_LENGTH = 255;
const MIN_ID_LENGTH = 1;
function validateTenantInput(tenant) {
const errors = [];
// Validate appid
if (!tenant.appid || typeof tenant.appid !== "string") {
errors.push("appid is required and must be a string");
}
else if (tenant.appid.length < MIN_ID_LENGTH || tenant.appid.length > MAX_ID_LENGTH) {
errors.push(`appid must be between ${MIN_ID_LENGTH} and ${MAX_ID_LENGTH} characters`);
}
else if (!VALID_ID_PATTERN.test(tenant.appid)) {
errors.push("appid contains invalid characters. Only alphanumeric, underscore, hyphen, and dot are allowed");
}
// Validate orgid
if (!tenant.orgid || typeof tenant.orgid !== "string") {
errors.push("orgid is required and must be a string");
}
else if (tenant.orgid.length < MIN_ID_LENGTH || tenant.orgid.length > MAX_ID_LENGTH) {
errors.push(`orgid must be between ${MIN_ID_LENGTH} and ${MAX_ID_LENGTH} characters`);
}
else if (!VALID_ID_PATTERN.test(tenant.orgid)) {
errors.push("orgid contains invalid characters. Only alphanumeric, underscore, hyphen, and dot are allowed");
}
// Validate appdbname
if (!tenant.appdbname || typeof tenant.appdbname !== "string") {
errors.push("appdbname is required and must be a string");
}
else if (tenant.appdbname.length < MIN_ID_LENGTH || tenant.appdbname.length > MAX_ID_LENGTH) {
errors.push(`appdbname must be between ${MIN_ID_LENGTH} and ${MAX_ID_LENGTH} characters`);
}
else if (!VALID_ID_PATTERN.test(tenant.appdbname)) {
errors.push("appdbname contains invalid characters. Only alphanumeric, underscore, hyphen, and dot are allowed");
}
if (errors.length > 0) {
throw new errors_1.ValidationError(`Invalid tenant input: ${errors.join("; ")}`, { tenant });
}
}
async function runWithTenant(tenant, fn, options) {
validateTenantInput(tenant);
// Set tenant context
return tenantContext.run(tenant, async () => {
try {
// Create connection eagerly at the start (default behavior)
// Since runWithTenant implies DB operations, we create connection upfront
if (!options?.lazyConnection) {
try {
await (0, connectionManager_1.getConnection)(tenant);
loggers_1.default.debug(`Connection established for tenant: ${tenant.appid}-${tenant.orgid}-${tenant.appdbname}`);
}
catch (error) {
// If connection creation fails, throw error immediately
// This provides fail-fast behavior
loggers_1.default.error(`Failed to establish connection for tenant: ${error.message}`, {
tenant,
error: error.stack,
});
throw error;
}
}
return await fn();
}
finally {
// Tenant context is automatically cleared by AsyncLocalStorage when this block completes
// Connection remains in cache - it's NOT closed, just no longer associated with this thread
loggers_1.default.debug(`Tenant context cleared for: ${tenant.appid}-${tenant.orgid}-${tenant.appdbname} (connection remains in cache)`);
}
});
}
function getTenant() {
return tenantContext.getStore();
}