npm package discovery and stats viewer.

Discover Tips

  • General search

    [free text search, go nuts!]

  • Package details

    pkg:[package-name]

  • User packages

    @[username]

Sponsor

Optimize Toolset

I’ve always been into building performant and accessible sites, but lately I’ve been taking it extremely seriously. So much so that I’ve been building a tool to help me optimize and monitor the sites that I build to make sure that I’m making an attempt to offer the best experience to those who visit them. If you’re into performant, accessible and SEO friendly sites, you might like it too! You can check it out at Optimize Toolset.

About

Hi, 👋, I’m Ryan Hefner  and I built this site for me, and you! The goal of this site was to provide an easy way for me to check the stats on my npm packages, both for prioritizing issues and updates, and to give me a little kick in the pants to keep up on stuff.

As I was building it, I realized that I was actually using the tool to build the tool, and figured I might as well put this out there and hopefully others will find it to be a fast and useful way to search and browse npm packages as I have.

If you’re interested in other things I’m working on, follow me on Twitter or check out the open source projects I’ve been publishing on GitHub.

I am also working on a Twitter bot for this site to tweet the most popular, newest, random packages from npm. Please follow that account now and it will start sending out packages soon–ish.

Open Software & Tools

This site wouldn’t be possible without the immense generosity and tireless efforts from the people who make contributions to the world and share their work via open source initiatives. Thank you 🙏

© 2025 – Pkg Stats / Ryan Hefner

@vidal747/mariadb-pool-cuenti

v1.0.4

Published

Gestor inteligente de pools de conexiones para MariaDB/MySQL que maneja múltiples bases de datos de empresas (multi-tenant) de forma dinámica y eficiente

Readme

MariaDB Pool Cuenti

Un gestor inteligente de pools de conexiones para MariaDB/MySQL que maneja múltiples bases de datos de empresas (multi-tenant) de forma dinámica y eficiente.

🌟 Características

  • Multi-tenant: Maneja conexiones a múltiples bases de datos de empresas
  • Pool dinámico: Crea y gestiona pools de conexiones bajo demanda
  • Inicialización perezosa: Solo inicializa conexiones cuando se necesitan
  • Configuración centralizada: Almacena configuración de servidores en base de datos
  • Soporte para proxy: Maneja tanto conexiones directas como pools
  • Query helpers: Métodos auxiliares para consultas SQL con parámetros nombrados
  • TypeScript: Completamente tipado para mejor experiencia de desarrollo

📦 Instalación

npm install mariadb-pool-cuenti

🔧 Configuración

Variables de entorno

Crea un archivo .env en tu proyecto con las siguientes variables:

# Configuración de la base de datos principal
DB_HOST=localhost
DB_USER=root
DB_PASSWORD=tu_password_aqui
DB_NAME=nombre_base_datos
DB_PORT=3306
DB_POOL_SIZE=5

# Configuración de pools de tenant (opcional)
TENANT_POOL_SIZE=10

Estructura de base de datos requerida

El paquete necesita estas tablas en tu base de datos principal:

-- Tabla de servidores
CREATE TABLE Servidores (
    id INT PRIMARY KEY AUTO_INCREMENT,
    ip VARCHAR(255) NOT NULL,
    puerto INT NOT NULL DEFAULT 3306,
    usuario VARCHAR(255) NOT NULL,
    clave VARCHAR(255) NOT NULL,
    es_proxy TINYINT(1) DEFAULT 0,
    initialSize INT DEFAULT 5,
    maxActive INT DEFAULT 10,
    maxIdle INT DEFAULT 5,
    maxTiempoInatividad INT DEFAULT 300,
    maxTiempoEsperaEntregarConecion INT DEFAULT 60
);

-- Tabla de aplicaciones por empresa
CREATE TABLE aplicaciones_empresa (
    id INT PRIMARY KEY AUTO_INCREMENT,
    id_empresa INT NOT NULL,
    id_servidor INT NOT NULL,
    nombreBaseDatos VARCHAR(255) NOT NULL,
    FOREIGN KEY (id_servidor) REFERENCES Servidores(id)
);

🚀 Uso básico

Importación

import dbManager from "mariadb-pool-cuenti";
// o
import { DBManager } from "mariadb-pool-cuenti";

Obtener conexión de empresa

// Obtener conexión para una empresa específica
const conn = await dbManager.getConnectionEmpresa(123);

try {
  // Realizar consultas
  const usuarios = await conn.query("SELECT * FROM usuarios");
  console.log(usuarios);
} finally {
  // Siempre liberar la conexión
  conn.release();
}

Usando el pool principal

// Para consultas en la base de datos principal
const conn = await dbManager.getPoolBases();

try {
  const empresas = await conn.query("SELECT * FROM aplicaciones_empresa");
  console.log(empresas);
} finally {
  conn.release();
}

📚 API Detallada

DBManager.getInstance()

Obtiene la instancia singleton del gestor.

const dbManager = DBManager.getInstance();

getConnectionEmpresa(idEmpresa: number): Promise<ExtendedConnection>

Obtiene una conexión a la base de datos de una empresa específica.

Parámetros:

  • idEmpresa: ID numérico de la empresa

Retorna: Promise<ExtendedConnection> - Conexión extendida con métodos auxiliares

Ejemplo:

const conn = await dbManager.getConnectionEmpresa(123);
// La conexión ya está configurada para usar la BD de la empresa 123

getPoolBases(): Promise<ExtendedConnection>

Obtiene una conexión al pool principal (base de datos de configuración).

Retorna: Promise<ExtendedConnection> - Conexión al pool principal

borrarEmpresaServer(idEmpresa: number): void

Elimina una empresa del cache interno, forzando reconfiguración en próximo acceso.

Parámetros:

  • idEmpresa: ID de la empresa a eliminar del cache

isReady(): boolean

Verifica si el servicio está inicializado y listo para usar.

Retorna: boolean - true si está listo

closeAll(): Promise<void>

Cierra todas las conexiones y pools de manera ordenada.

🔍 ExtendedConnection

Las conexiones retornadas incluyen métodos auxiliares adicionales:

queryFormat(query: string, params?: QueryParams): string

Formatea una consulta SQL reemplazando parámetros nombrados.

const sql = conn.queryFormat(
  "SELECT * FROM usuarios WHERE id = :id AND activo = :activo",
  { id: 123, activo: 1 }
);
// Resultado: "SELECT * FROM usuarios WHERE id = 123 AND activo = 1"

query2<T>(query: string, params?: QueryParams): Promise<T>

Ejecuta una consulta SQL con parámetros nombrados.

const usuarios = await conn.query2<Usuario[]>(
  "SELECT * FROM usuarios WHERE edad > :edad AND ciudad = :ciudad",
  { edad: 18, ciudad: "Madrid" }
);

💡 Ejemplos avanzados

Manejo completo de conexiones

import dbManager from "mariadb-pool-cuenti";

async function obtenerDatosEmpresa(idEmpresa: number) {
  let conn;

  try {
    // Verificar que el servicio esté listo
    if (!dbManager.isReady()) {
      console.log("Servicio inicializándose...");
    }

    // Obtener conexión
    conn = await dbManager.getConnectionEmpresa(idEmpresa);

    // Consulta con parámetros nombrados
    const usuarios = await conn.query2<any[]>(
      "SELECT * FROM usuarios WHERE activo = :activo",
      { activo: 1 }
    );

    return usuarios;
  } catch (error) {
    console.error("Error al obtener datos:", error);
    throw error;
  } finally {
    // Siempre liberar la conexión
    if (conn) {
      conn.release();
    }
  }
}

Usando con Express.js

import express from "express";
import dbManager from "mariadb-pool-cuenti";

const app = express();

app.get("/empresas/:id/usuarios", async (req, res) => {
  const idEmpresa = parseInt(req.params.id);

  try {
    const conn = await dbManager.getConnectionEmpresa(idEmpresa);

    const usuarios = await conn.query2(
      "SELECT id, nombre, email FROM usuarios WHERE activo = :activo",
      { activo: 1 }
    );

    res.json(usuarios);
    conn.release();
  } catch (error) {
    console.error("Error:", error);
    res.status(500).json({ error: "Error interno del servidor" });
  }
});

// Graceful shutdown
process.on("SIGTERM", async () => {
  console.log("Cerrando conexiones...");
  await dbManager.closeAll();
  process.exit(0);
});

Transacciones

async function transferirFondos(
  idEmpresa: number,
  fromUser: number,
  toUser: number,
  amount: number
) {
  const conn = await dbManager.getConnectionEmpresa(idEmpresa);

  try {
    await conn.beginTransaction();

    // Debitar cuenta origen
    await conn.query2(
      "UPDATE cuentas SET saldo = saldo - :amount WHERE usuario_id = :userId",
      { amount, userId: fromUser }
    );

    // Acreditar cuenta destino
    await conn.query2(
      "UPDATE cuentas SET saldo = saldo + :amount WHERE usuario_id = :userId",
      { amount, userId: toUser }
    );

    await conn.commit();
    console.log("Transferencia completada");
  } catch (error) {
    await conn.rollback();
    console.error("Error en transferencia:", error);
    throw error;
  } finally {
    conn.release();
  }
}

🏗️ Arquitectura

Flujo de funcionamiento

  1. Inicialización perezosa: El servicio no se conecta hasta que se solicite una conexión
  2. Consulta de configuración: Al solicitar una empresa, consulta su configuración en la BD principal
  3. Creación de pool: Si es la primera vez, crea un pool para ese servidor
  4. Cache interno: Mantiene cache de configuraciones para evitar consultas repetidas
  5. Conexión optimizada: Reutiliza pools existentes para empresas del mismo servidor

Tipos de conexión

  • Pool (es_proxy = 0): Crea un pool de conexiones reutilizables
  • Directa (es_proxy = 1): Crea conexiones directas sin pool

🔒 Mejores prácticas

1. Siempre liberar conexiones

// ✅ Correcto
const conn = await dbManager.getConnectionEmpresa(123);
try {
  // usar conexión
} finally {
  conn.release(); // Siempre liberar
}

// ❌ Incorrecto - puede causar memory leaks
const conn = await dbManager.getConnectionEmpresa(123);
const data = await conn.query("SELECT * FROM tabla");
// Conexión no liberada

2. Manejo de errores

try {
  const conn = await dbManager.getConnectionEmpresa(123);
  // operaciones
  conn.release();
} catch (error) {
  if (error.message.includes("no configurada")) {
    console.log("Empresa no existe en configuración");
  }
  throw error;
}

3. Graceful shutdown

// En tu aplicación principal
process.on("SIGTERM", async () => {
  await dbManager.closeAll();
  process.exit(0);
});

4. Validación de empresas

// Verificar si una empresa existe antes de usarla
async function validarEmpresa(idEmpresa: number): Promise<boolean> {
  try {
    const conn = await dbManager.getConnectionEmpresa(idEmpresa);
    conn.release();
    return true;
  } catch (error) {
    if (error.message.includes("no configurada")) {
      return false;
    }
    throw error;
  }
}

🐛 Debugging

Logs del sistema

El paquete emite logs detallados para debugging:

[BASE] Inicializando pool de conexiones principal...
[BASE] Pool principal inicializado correctamente
[TENANT] Obteniendo conexión para empresa 123
[TENANT] Consultando configuración para empresa 123
[TENANT] Creando nuevo servidor 1 para empresa 123
[TENANT] Conexión establecida para empresa 123 en BD: empresa_123

Variables de entorno para debugging

# Para más logs de MariaDB
DEBUG=mariadb:*

📄 Licencia

MIT

🤝 Contribuir

Las contribuciones son bienvenidas. Por favor:

  1. Fork el proyecto
  2. Crea una rama para tu feature
  3. Commit tus cambios
  4. Push a tu rama
  5. Abre un Pull Request

📞 Soporte

Para reportar bugs o solicitar features, por favor abre un issue en el repositorio.


Nota: Este paquete está diseñado para aplicaciones multi-tenant que requieren gestión dinámica de conexiones a bases de datos por empresa.