Todos los Microservicios saludables.

Nuevo microservicio Plugins + cambios a microservicios anteriores, creación de módulos para conexiones a bases de datos y ajustes en las variables de entorno.
This commit is contained in:
2025-10-10 15:11:17 +00:00
parent a31b411437
commit ba6b4fef4f
47 changed files with 5303 additions and 2492 deletions
+16
View File
@@ -0,0 +1,16 @@
{
"name": "@suitecoffee/db",
"version": "0.1.0",
"private": true,
"type": "module",
"description": "Utilidades de acceso a Postgres para SuiteCoffee (pool por proceso + helpers multi-tenant).",
"exports": {
".": "./src/index.mjs"
},
"main": "./src/index.mjs",
"files": ["src"],
"sideEffects": false,
"peerDependencies": {
"pg": "^8.12.0"
}
}
+2
View File
@@ -0,0 +1,2 @@
export * from './pool-registry.mjs';
export * from './poolSingleton.mjs';
+54
View File
@@ -0,0 +1,54 @@
import { Pool } from 'pg';
const REGISTRY = new Map();
export function getPool(name = 'core', cfg = {}) {
if (REGISTRY.has(name)) return REGISTRY.get(name);
const pool = new Pool({
connectionString: process.env.PG_URL,
max: Number(process.env.PG_POOL_MAX ?? 10),
idleTimeoutMillis: Number(process.env.PG_IDLE_MS ?? 30000),
connectionTimeoutMillis: Number(process.env.PG_CONN_MS ?? 5000),
statement_timeout: Number(process.env.PG_STMT_MS ?? 15000),
ssl: process.env.PGSSL === 'true' ? { rejectUnauthorized: false } : undefined,
...cfg
});
pool.on('error', (err) => {
// ideal: reemplazar con pino/sentry
console.error(`[pg:${name}] pool error`, err);
});
REGISTRY.set(name, pool);
return pool;
}
function assertTenantSchema(schema) {
if (!/^tenant_[a-f0-9-]{16,36}$/i.test(schema)) {
throw new Error('Invalid tenant schema');
}
return `"${schema.replace(/"/g, '""')}"`;
}
export async function withTenant(poolName, tenantSchema, fn) {
const pool = getPool(poolName);
const client = await pool.connect();
try {
await client.query('BEGIN');
await client.query(`SET LOCAL search_path TO ${assertTenantSchema(tenantSchema)}`);
const res = await fn(client);
await client.query('COMMIT');
return res;
} catch (e) {
try { await client.query('ROLLBACK'); } catch {}
throw e;
} finally {
client.release();
}
}
export async function shutdownAll() {
await Promise.all([...REGISTRY.values()].map(p => p.end()));
REGISTRY.clear();
}
+46
View File
@@ -0,0 +1,46 @@
// Coneción Singleton a base de datos.
import { Pool } from 'pg';
class Database {
constructor() {
if (Database.instance) {
return Database.instance;
}
const config = {
host: process.env.DB_HOST,
user: process.env.DB_USER,
password: process.env.DB_PASS,
database: process.env.DB_NAME,
port: process.env.DB_LOCAL_PORT ? Number(process.env.DB_LOCAL_PORT) : undefined,
ssl: process.env.PGSSL === 'true' ? { rejectUnauthorized: false } : undefined,
};
this.connection = new Pool(config);
Database.instance = this;
}
async query(sql, params) {
return this.connection.query(sql,params);
}
async connect() { /* Definida solo para evitar errores */
return this.connection.connect();
}
async getClient() {
return this.connection.connect();
}
async release() {
await this.connection.end();
}
}
// const db = new Database();
// db.query('SELECT * FROM users');
const pool = new Database();
export default pool;
export { Database };