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:
@@ -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"
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,2 @@
|
||||
export * from './pool-registry.mjs';
|
||||
export * from './poolSingleton.mjs';
|
||||
@@ -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();
|
||||
}
|
||||
@@ -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 };
|
||||
Reference in New Issue
Block a user