UNPKG

multibridge

Version:

A multi-database connection framework with centralized configuration

87 lines (86 loc) 4.12 kB
"use strict"; 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(); }