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 🙏

© 2026 – Pkg Stats / Ryan Hefner

mariadb-pool-cuenti

v1.5.9

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 🗄️

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

  • Pool de conexiones dinámico para múltiples bases de datos
  • Multi-tenant con configuración automática por empresa
  • Reconexión automática con backoff exponencial
  • Verificación de salud automática de conexiones
  • Manejo robusto de errores con timeouts configurables
  • Cleanup automático de recursos
  • 🔥 NUEVO: Detección de fugas - Sistema inteligente para prevenir connection leaks
  • Memory leak prevention con detección automática
  • Soporte para conexiones directas (proxy) y pools
  • Logging detallado para debugging
  • TypeScript con tipado completo

📦 Instalación

npm install [email protected]

🛠️ Configuración

Variables de Entorno

# Configuración del ambiente (dev o pro)
ENVIRONMENT_DATA_BASE=dev

# Configuración de la base de datos principal - DESARROLLO
DB_HOST_DEV=localhost
DB_USER_DEV=root
DB_PWD_DEV=tu_password_aqui
DATABASE_DEV=nombre_base_datos_dev
DB_PORT_DEV=3306
POOL_SIZE_BASE_DEV=5

# Configuración de la base de datos principal - PRODUCCIÓN
DB_HOST_PRO=host_produccion
DB_USER_PRO=usuario_produccion
DB_PWD_PRO=password_produccion
DATABASE_PRO=nombre_base_datos_pro
DB_PORT_PRO=3306
POOL_SIZE_BASE_PRO=15

# Configuración de la aplicación
ID_APPLICATION=1

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

🔧 Uso Básico

1. Importar y usar

import dbManager from "mariadb-pool-cuenti";

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

// Ejecutar consulta
const resultado = await conn.query("SELECT * FROM tabla WHERE id = ?", [1]);

// Liberar conexión (importante!)
await conn.safeRelease();

2. Método simplificado (recomendado)

import dbManager from "mariadb-pool-cuenti";

// Ejecutar consulta con manejo automático de conexiones
const resultado = await dbManager.executeQuery(
  12345, // ID de empresa
  "SELECT * FROM usuarios WHERE activo = :activo",
  { activo: 1 }
);

console.log(resultado);

4. Detección y prevención de fugas de conexiones 🔥

import dbManager, { 
  getConnectionStats, 
  cleanupStaleConnections 
} from "mariadb-pool-cuenti";

// 1. Obtener estadísticas de conexiones para diagnóstico
const stats = getConnectionStats();
console.log("Estadísticas de conexiones:", stats);

// 2. Ejecutar limpieza y detección de fugas
const result = await cleanupStaleConnections();
console.log(`Limpieza completada: ${result.closed} conexiones cerradas, ${result.leaksDetected} fugas detectadas`);

// 3. Patrón seguro para prevenir fugas de conexiones
async function executeSafely(empresaId, callback) {
  const conn = await dbManager.getConnectionEmpresa(empresaId);
  try {
    // Ejecutar las operaciones de base de datos dentro del callback
    return await callback(conn);
  } finally {
    // Garantizar que siempre se libere la conexión
    await conn.safeRelease();
  }
}

// Uso del patrón seguro
const usuarios = await executeSafely(1, async (conn) => {
  return conn.query("SELECT * FROM usuarios WHERE activo = 1");
});

console.log("Usuarios activos:", usuarios);

4. Manejo completo con Express

import express from "express";
import dbManager, { shutdown } from "mariadb-pool-cuenti";

const app = express();

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

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

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

const server = app.listen(3000, () => {
  console.log("Servidor iniciado en puerto 3000");
});

// Manejo de cierre sincronizado
process.on("SIGINT", async () => {
  console.log("Cerrando servidor...");

  server.close(() => {
    console.log("Servidor cerrado");
    // La librería se cerrará automáticamente
    process.exit(0);
  });
});

process.on("SIGTERM", async () => {
  console.log("Cerrando aplicación por SIGTERM...");

  server.close(() => {
    console.log("Aplicación cerrada");
    // La librería se cerrará automáticamente
    process.exit(0);
  });
});

🔍 Monitoreo

Verificar estado del servicio

// Verificar si está listo
if (dbManager.isReady()) {
  console.log("Servicio listo para usar");
}

// Obtener estadísticas de pools
const stats = dbManager.getPoolStats();
console.log("Estadísticas:", stats);

🚨 Manejo de Errores

La librería maneja automáticamente:

  • Sin timeouts artificiales - Permite consultas extremadamente largas
  • Motor MariaDB nativo - Maneja sus propios timeouts optimizados
  • Reconexiones automáticas con backoff exponencial
  • Errores de configuración con mensajes descriptivos
  • Cleanup automático al terminar el proceso

Ejemplo de manejo de errores:

try {
  const resultado = await dbManager.executeQuery(
    empresaId,
    "SELECT * FROM tabla_grande WHERE condicion = :valor",
    { valor: "algún_valor" }
  );
} catch (error) {
  if (error.message.includes("timeout")) {
    console.error("La consulta tardó demasiado - considera optimizar");
  } else if (error.message.includes("Variables de entorno")) {
    console.error("Problema de configuración:", error.message);
  } else {
    console.error("Error inesperado:", error);
  }
}

📊 Configuración Avanzada

Para Bases de Datos Muy Grandes

# La librería v1.3.0+ NO usa timeouts artificiales
# Esto permite consultas que tomen horas si es necesario

# Pools más grandes para alta concurrencia
POOL_SIZE_BASE_PRO=20
TENANT_POOL_SIZE=25

# Configuración recomendada en tu servidor MariaDB
# wait_timeout = 28800          # 8 horas
# interactive_timeout = 28800   # 8 horas
# max_execution_time = 0        # Sin límite (solo para consultas SELECT)

Para Desarrollo

# Configuración más pequeña para desarrollo
POOL_SIZE_BASE_DEV=3
TENANT_POOL_SIZE=5

🆕 Novedades en v1.5.9

✅ Detección Avanzada de Fugas de Conexiones

  • Problema resuelto: "A possible connection leak on thread XXXX (connection not returned to pool for 120001ms)"
  • Método getConnectionStats(): Obtiene estadísticas detalladas de todas las conexiones
  • Método cleanupStaleConnections(): Detecta y cierra automáticamente conexiones con fugas
  • Tracking de actividad: Registro automático de última actividad para cada conexión
  • Limpieza periódica: Escaneo regular para prevenir fugas y cerrar conexiones antiguas

✅ Mejoras en el Diagnóstico de Problemas

  • Estadísticas detalladas: Información completa sobre el estado del pool y conexiones
  • Identificación temprana: Detección de fugas después de 2 minutos sin actividad
  • Cierre inteligente: Liberación automática de conexiones muy antiguas (>5 minutos)
  • Prevención proactiva: Actualización automática de timestamps en cada actividad
  • Patrones seguros: Ejemplos de uso para garantizar liberación de conexiones

🔧 Migración desde versiones anteriores

Cambios en Variables de Entorno

# ANTES (v1.1.1 y anteriores)
DB_HOST=localhost
DB_USER=root
DB_PASSWORD=password
DB_NAME=database

# AHORA (v1.1.3)
ENVIRONMENT_DATA_BASE=dev
DB_HOST_DEV=localhost
DB_USER_DEV=root
DB_PWD_DEV=password
DATABASE_DEV=database

Código sin cambios necesarios

// Tu código existente sigue funcionando igual
import dbManager from "mariadb-pool-cuenti";

const resultado = await dbManager.executeQuery(empresaId, sql, params);
// ¡Sin cambios necesarios!

📋 Ejemplos de Cierre Seguro

// Ver ejemplos completos en:
// /examples/safe-shutdown-example.js
// /examples/shutdown-example.js

// Ejemplo básico:
import { shutdown, isShuttingDown } from "mariadb-pool-cuenti";

process.on("SIGINT", async () => {
  if (isShuttingDown()) return;
  
  console.log("Cerrando aplicación...");
  
  // 1. Detener servidor web si existe
  if (server) server.close();
  
  // 2. Cerrar conexiones a base de datos
  await shutdown();
  
  console.log("Cierre exitoso");
  process.exit(0);
});

🎯 Casos de Uso

1. Aplicación SaaS Multi-tenant

// Cada cliente (empresa) tiene su propia base de datos
const clienteId = req.user.empresaId;
const datos = await dbManager.executeQuery(clienteId, sql, params);

2. Cierre Seguro con Manejo de Errores

import { shutdown, isShuttingDown } from "mariadb-pool-cuenti";

// Manejador de cierre seguro
async function handleShutdown(signal) {
  if (isShuttingDown()) {
    console.log(`Ya cerrando, ignorando señal ${signal}`);
    return;
  }
  
  console.log(`Señal ${signal} recibida, cerrando servicios...`);
  
  try {
    // 1. Detener servidor HTTP
    await new Promise(resolve => server.close(resolve));
    console.log("Servidor HTTP detenido");
    
    // 2. Cerrar conexiones de Redis/caché
    if (redisClient && redisClient.isOpen) {
      await redisClient.quit();
      console.log("Conexión Redis cerrada");
    }
    
    // 3. Cerrar conexiones a MariaDB
    await shutdown();
    console.log("Conexiones MariaDB cerradas");
    
    // 4. Cerrar otros recursos
    // ...
    
    console.log("Cierre exitoso");
    process.exit(0);
  } catch (error) {
    console.error("Error durante cierre:", error);
    process.exit(1);
  }
}

// Configurar manejadores de señales
['SIGINT', 'SIGTERM', 'SIGQUIT'].forEach(signal => {
  process.once(signal, () => handleShutdown(signal));
});

// Manejar errores no capturados y promesas rechazadas
process.once('uncaughtException', async (error) => {
  console.error("Error no capturado:", error);
  await handleShutdown('uncaughtException');
});

process.once('unhandledRejection', async (reason) => {
  console.error("Promesa rechazada no manejada:", reason);
  await handleShutdown('unhandledRejection');
});

3. Consultas a Bases de Datos Extremadamente Grandes

// Sin timeouts artificiales - permite consultas que tomen horas
const reporteCompleto = await dbManager.executeQuery(
  empresaId,
  `SELECT 
     t.id, t.fecha, t.monto, c.nombre, p.descripcion
   FROM transacciones t 
   JOIN clientes c ON t.cliente_id = c.id
   JOIN productos p ON t.producto_id = p.id
   WHERE t.fecha BETWEEN :inicio AND :fin
   ORDER BY t.fecha`,
  { inicio: "2020-01-01", fin: "2024-12-31" }
);
// Esta consulta puede tomar 30 minutos, 2 horas, o el tiempo que necesite
console.log(`Procesadas ${reporteCompleto.length} transacciones`);

🛡️ Seguridad

  • Consultas parametrizadas por defecto
  • Conexiones SSL soportadas
  • Timeouts para prevenir ataques DoS
  • Validación de configuración antes de inicializar
  • Manejo seguro de errores sin exposición de detalles internos

📈 Rendimiento

  • Pools dinámicos solo cuando se necesitan
  • Conexiones reutilizadas entre consultas
  • Health checks automáticos
  • Reconexión inteligente con backoff
  • Memory leak prevention

🔍 Troubleshooting

Errores "Connection leak" o "Connection not returned to pool"

// Problema: 
// - "A possible connection leak on thread 4826769 (connection not returned to pool for 120001ms)"
// - "Has connection.release() been called?"

// Solución:
import { getConnectionStats, cleanupStaleConnections } from "mariadb-pool-cuenti";

// 1. Revisar estadísticas para identificar fugas
const stats = getConnectionStats();
console.log(`Total conexiones: ${stats.total.connections}`);
console.log(`Conexiones por empresa:`, stats.byEmpresa);

// 2. Si hay fugas, ejecutar limpieza de conexiones antiguas
if (stats.byEmpresa[1]?.potential_leaks > 0) {
  console.log("Detectadas posibles fugas de conexiones, limpiando...");
  const result = await cleanupStaleConnections();
  console.log(`Limpieza completada: ${result.closed} conexiones cerradas, ${result.leaksDetected} fugas detectadas`);
}

// 3. Revisar en tu código si todas las conexiones se liberan correctamente
// ✅ CORRECTO: Liberar conexión después de usarla
const conn = await dbManager.getConnectionEmpresa(1);
try {
  const result = await conn.query("SELECT * FROM tabla");
  // Procesar resultado...
} finally {
  // Siempre liberar la conexión, incluso si hay errores
  await conn.safeRelease();
}

// ❌ INCORRECTO: Olvidar liberar la conexión
const conn = await dbManager.getConnectionEmpresa(1);
const result = await conn.query("SELECT * FROM tabla");
// ¡Falta conn.safeRelease()! - Esto causará una fuga de conexión

🤝 Contribuir

Las contribuciones son bienvenidas. Por favor:

  1. Fork el proyecto
  2. Crea una rama para tu feature (git checkout -b feature/mi-feature)
  3. Commit tus cambios (git commit -am 'Agregar mi feature')
  4. Push a la rama (git push origin feature/mi-feature)
  5. Crea un Pull Request

📄 Licencia

MIT License - ver archivo LICENSE para más detalles.

🏷️ Versión

v1.5.9 - Detección avanzada de fugas de conexiones (connection leaks) y manejo mejorado de recursos


¿Problemas o preguntas? Abre un issue en GitHub.