UNPKG

@teamnet/ic-orm

Version:

Database Management System

1,105 lines (963 loc) 30.7 kB
const MongoDB = require('mongodb'); const MongoClient = MongoDB.MongoClient; const EMPTYARRAY = []; const EMPTYOBJECT = {}; const INSERTPROJECTION = { projection: { _id: 1 } }; const BUCKETNAME = { bucketName: 'db' }; const BLACKLIST = { dbms: 1 }; global.ObjectID = MongoDB.ObjectID; // Instancia compartida de MongoClient let sharedClient = null; let reconnectAttempts = 0; const MAX_RECONNECT_ATTEMPTS = 5; const RECONNECT_INTERVAL = 2000; // 2 segundos /** * Función para cerrar la conexión a MongoDB * @param {Function} [callback] - Función opcional que se ejecutará después de cerrar la conexión * @returns {Promise<void>} - Promesa que se resuelve cuando la conexión se ha cerrado */ exports.disconnect = function (callback) { return new Promise((resolve, reject) => { if (!sharedClient) { if (callback) callback(); return resolve(); } try { if (sharedClient.topology && sharedClient.topology.isConnected()) { sharedClient.close(false, () => { console.log('Conexión a MongoDB cerrada explícitamente'); sharedClient = null; if (callback) callback(); resolve(); }); } else { sharedClient = null; if (callback) callback(); resolve(); } } catch (err) { console.error('Error al cerrar la conexión con MongoDB:', err); sharedClient = null; if (callback) callback(err); reject(err); } }); }; /** * Intenta reconectar con MongoDB después de un error * @param {Object} options - Opciones de conexión * @param {Function} callback - Callback a ejecutar después de la reconexión */ function attemptReconnect(options, callback) { if (reconnectAttempts >= MAX_RECONNECT_ATTEMPTS) { console.error( `Máximo número de intentos de reconexión (${MAX_RECONNECT_ATTEMPTS}) alcanzado` ); reconnectAttempts = 0; return callback( new Error('No se pudo reconectar a MongoDB después de múltiples intentos') ); } reconnectAttempts++; console.log( `Intentando reconectar a MongoDB (intento ${reconnectAttempts}/${MAX_RECONNECT_ATTEMPTS})...` ); setTimeout(() => { // Extraer configuración de conexión var maxPoolSize = 10; var connectOptions = options; if (options && typeof options === 'object' && options.maxPoolSize) { maxPoolSize = options.maxPoolSize; connectOptions = options.options; } MongoClient.connect( connectOptions, { useNewUrlParser: true, useUnifiedTopology: true, maxPoolSize: maxPoolSize, serverSelectionTimeoutMS: 5000, // Timeout para la selección de servidor }, function (err, client) { if (err) { console.error('Error al reconectar a MongoDB:', err); return attemptReconnect(options, callback); } console.log('Reconexión a MongoDB exitosa'); reconnectAttempts = 0; sharedClient = client; // Configurar manejadores de eventos setupEventHandlers(client, options); callback(null, sharedClient); } ); }, RECONNECT_INTERVAL); } /** * Configura los manejadores de eventos para el cliente de MongoDB * @param {Object} client - Cliente de MongoDB * @param {Object} options - Opciones originales de conexión (para reconexión) */ function setupEventHandlers(client, originalOptions) { client.on('close', () => { console.log('Conexión a MongoDB cerrada'); if (sharedClient === client) { sharedClient = null; } }); client.on('error', (err) => { console.error('Error en la conexión a MongoDB:', err); // No intentamos reconectar automáticamente aquí porque el driver ya lo hace }); client.on('timeout', () => { console.warn('Timeout en la conexión a MongoDB'); }); } /** * Función para inicializar y obtener el cliente compartido de MongoDB * @param {Object|string} options - Opciones de conexión u objeto con configuración * @param {Function} callback - Callback a ejecutar con el cliente */ function getClient(options, callback) { if (sharedClient) { return callback(null, sharedClient); } // Configuraciones por defecto para el cliente MongoDB var mongoOptions = { useNewUrlParser: true, useUnifiedTopology: true, maxPoolSize: 10, // valor por defecto serverSelectionTimeoutMS: 30000, // 30 segundos de timeout para selección de servidor socketTimeoutMS: 45000, // 45 segundos de timeout para operaciones de socket connectTimeoutMS: 30000 // 30 segundos de timeout para conexión inicial }; // Extraer la URI de conexión y las opciones de configuración var connectURI; if (options && typeof options === 'object') { // Si recibimos un objeto de configuración if (options.maxPoolSize) { mongoOptions.maxPoolSize = parseInt(options.maxPoolSize, 10) || 10; } connectURI = options.options; // La URI está en options.options } else { // Si solo recibimos la URI como string connectURI = options; } // Conectar usando la URI limpia y las opciones del cliente MongoClient.connect(connectURI, mongoOptions, function(err, client) { if (err) { console.error('Error al conectar con MongoDB:', err); return callback(err); } sharedClient = client; // Configurar manejadores de eventos setupEventHandlers(client, options); callback(null, sharedClient); }); } function select(client, cmd) { var builder = cmd.builder; var opt = builder.options; var filter = WHERE(builder); var options = {}; var fields = FIELDS(builder); if (fields) options.projection = fields; if (opt.take) options.limit = opt.take; if (opt.skip) options.skip = opt.skip; if (filter.sort) options.sort = filter.sort; builder.db.$debug && builder.db.$debug({ collection: client.$database + '.' + opt.table, condition: filter.where, options: options, }); client .db(client.$database) .collection(opt.table) .find(filter.where, options) .toArray(function (err, response) { err && client.$opt.onerror && client.$opt.onerror(err, opt, builder); var rows = response ? response : EMPTYARRAY; if (opt.first) rows = rows.length ? rows[0] : null; // checks joins if (!err && builder.$joins) { client.$dbms._join(rows, builder); setImmediate(builder.db.$next); } else builder.$callback(err, rows); }); } function bigselect(client, cmd) { var builder = cmd.builder; var opt = builder.options; var filter = WHERE(builder); var options = {}; var fields = FIELDS(builder); if (fields) options.projection = fields; if (opt.take) options.limit = opt.take; if (opt.skip) options.skip = opt.skip; if (!filter.sort) filter.sort = { _id: 1 }; builder.db.$debug && builder.db.$debug({ collection: client.$database + '.' + opt.table, condition: filter.where, options: options, }); client .db(client.$database) .collection(opt.table) .find(filter.where, options) .sort(filter.sort) .allowDiskUse() .toArray(function (err, response) { err && client.$opt.onerror && client.$opt.onerror(err, opt, builder); var rows = response ? response : EMPTYARRAY; if (opt.first) rows = rows.length ? rows[0] : null; // checks joins if (!err && builder.$joins) { client.$dbms._join(rows, builder); setImmediate(builder.db.$next); } else builder.$callback(err, rows); }); } function query(client, cmd) { var builder = cmd.builder; var opt = builder.options; var filter = WHERE(builder); var options = {}; var fields = FIELDS(builder); if (fields) options.projection = fields; if (opt.take) options.limit = opt.take; if (opt.skip) options.skip = opt.skip; if (filter.sort) options.sort = filter.sort; var col = client.db(client.$database).collection(cmd.query); cmd.value( col, function (err, response) { if (opt.first && Array.isArray(response)) response = response[0]; err && client.$opt.onerror && client.$opt.onerror(err, opt, builder); builder.$callback(err, response); }, filter.where, options ); } function listCollections(client, cmd) { var builder = cmd.builder; var opt = builder.options; client .db(client.$database) .listCollections() .toArray(function (err, response) { err && client.$opt.onerror && client.$opt.onerror(err, opt, builder); builder.$callback(err, response); }); } async function listDatabases(client, cmd) { var builder = cmd.builder; var opt = builder.options; var result = await client.db(client.$database).admin().listDatabases(); builder.$callback(result); } function pipeLines(client, cmd) { var builder = cmd.builder; var opt = builder.options; var filter = WHERE(builder, true); client .db(client.$database) .collection(opt.table) .aggregate([filter, cmd.improved]) .toArray(function (err, response) { err && client.$opt.onerror && client.$opt.onerror(err, opt, builder); builder.$callback(err, response); }); } function list(client, cmd) { var builder = cmd.builder; var opt = builder.options; var filter = WHERE(builder); var options = {}; var fields = FIELDS(builder); if (fields) options.projection = fields; if (opt.take) options.limit = opt.take; if (opt.skip) options.skip = opt.skip; if (filter.sort) options.sort = filter.sort; builder.db.$debug && builder.db.$debug({ collection: client.$database + '.' + opt.table, condition: filter.where, options: options, }); var db = client.db(client.$database).collection(opt.table); db.countDocuments(filter.where, function (err, count) { if (err) { err && client.$opt.onerror && client.$opt.onerror(err, opt, builder); builder.$callback(err, null); } else { db.find(filter.where, options).toArray(function (err, response) { if (!err && builder.$joins) { client.$dbms._joins(response, builder, count || 0); setImmediate(builder.db.$next); } else builder.$callback(err, response, count); }); } }); } function scalar(client, cmd) { var builder = cmd.builder; var opt = builder.options; var filter = WHERE(builder, true); var cmdgroup = '$' + cmd.name; // builder.db.$debug && builder.db.$debug(q); switch (cmd.scalar) { case 'count': client .db(client.$database) .collection(opt.table) .aggregate([filter, { $count: 'count' }]) .toArray(function (err, response) { err && client.$opt.onerror && client.$opt.onerror(err, opt, builder); var rescount = 0; if (response.length) rescount = response[0].count; builder.$callback(err, rescount); }); break; case 'avg': client .db(client.$database) .collection(opt.table) .aggregate([ filter, { $group: { _id: '', average: { $avg: cmdgroup } } }, ]) .toArray(function (err, response) { err && client.$opt.onerror && client.$opt.onerror(err, opt, builder); builder.$callback(err, response[0].average); }); break; case 'min': client .db(client.$database) .collection(opt.table) .aggregate([filter, { $group: { _id: '', min: { $min: cmdgroup } } }]) .toArray(function (err, response) { err && client.$opt.onerror && client.$opt.onerror(err, opt, builder); builder.$callback(err, response[0].min); }); break; case 'max': client .db(client.$database) .collection(opt.table) .aggregate([filter, { $group: { _id: '', max: { $max: cmdgroup } } }]) .toArray(function (err, response) { err && client.$opt.onerror && client.$opt.onerror(err, opt, builder); builder.$callback(err, response[0].max); }); break; case 'sum': client .db(client.$database) .collection(opt.table) .aggregate([filter, { $group: { _id: '', sum: { $sum: cmdgroup } } }]) .toArray(function (err, response) { err && client.$opt.onerror && client.$opt.onerror(err, opt, builder); builder.$callback(err, response[0].sum); }); break; case 'group': client .db(client.$database) .collection(opt.table) .aggregate([filter, { $count: 'count' }]) .toArray(function (error, count) { error && client.$opt.onerror && client.$opt.onerror(error, opt, builder); client .db(client.$database) .collection(opt.table) .aggregate([ filter, { $group: { _id: cmdgroup, count: { $sum: 1 } } }, { $sort: { count: -1 } }, ]) .toArray(function (err, response) { err && client.$opt.onerror && client.$opt.onerror(err, opt, builder); var obj = {}; if (count && count[0] && count[0].count) obj.total = count[0].count; else obj.total = 0; obj.items = response; builder.$callback(err, obj); }); }); break; } } async function insert(client, cmd) { var builder = cmd.builder; var keys = Object.keys(cmd.builder.value); var opt = builder.options; var params = {}; for (var i = 0; i < keys.length; i++) { var key = keys[i]; var val = cmd.builder.value[key]; if (val === undefined || BLACKLIST[key]) continue; if (cmd.builder.options.fields && cmd.builder.options.fields.length) { var skip = true; for (var j = 0; j < cmd.builder.options.fields.length; j++) { if ( cmd.builder.options.fields[j] == key || cmd.builder.options.fields[j] == key.substring(1) ) { skip = false; break; } } if (skip) continue; } switch (key[0]) { case '&': key = key.substring(1); var gencode = val; gencode.key = key; break; case '%': key = key.substring(1); var gensearch = val; gensearch.key = key; break; case '-': case '+': case '*': case '/': key = key.substring(1); break; case '<': case '>': key = key.substring(1); break; } params[key] = val == null ? null : typeof val === 'function' ? val(cmd.builder.value) : val; } if (gencode) { var db = DBMS(); var codegen = await db .one(gencode.indexes) .where('code', gencode.precode) .promise(); db.modify(gencode.indexes, { '+index': +1 }).where('code', gencode.precode); var indexcode = codegen.index + 1; indexcode = indexcode.toString(); while (indexcode.length < gencode.output) { indexcode = '0' + indexcode; } params[gencode.key] = gencode.precode + indexcode; if (gensearch) { var one = gensearch.one; var two = gensearch.two; params[gensearch.key] = params[one] + gensearch.join + params[two]; } } builder.db.$debug && builder.db.$debug(); client .db(client.$database) .collection(opt.table) .insertOne(params, function (err, response) { err && client.$opt.onerror && client.$opt.onerror(err, opt, builder); builder.$callback( err, response && response.result && response.result.n ? 1 : 0, params ); }); } function insertexists(client, cmd) { var builder = cmd.builder; var opt = builder.options; var filter = WHERE(builder); // builder.db.$debug && builder.db.$debug(q); client .db(client.$database) .collection(opt.table) .findOne(filter.where, INSERTPROJECTION, function (err, response) { if (err || response) { err && client.$opt.onerror && client.$opt.onerror(err, opt, builder); builder.$callback(err, 0); } else insert(client, cmd); }); } function modify(client, cmd) { cmd.builder.options.transform && cmd.builder.options.transform( cmd.builder.value, cmd.builder.db.$output, cmd.builder.db.$lastoutput ); var keys = Object.keys(cmd.builder.value); var params = null; var increment = null; for (var i = 0; i < keys.length; i++) { var key = keys[i]; var val = cmd.builder.value[key]; if (val === undefined || BLACKLIST[key]) continue; if (cmd.builder.options.fields && cmd.builder.options.fields.length) { var skip = true; for (var j = 0; j < cmd.builder.options.fields.length; j++) { if ( cmd.builder.options.fields[j] == key || cmd.builder.options.fields[j] == key.substring(1) ) { skip = false; break; } } if (skip) continue; } var c = key[0]; if (typeof val === 'function') val = val(cmd.builder.value); switch (c) { case '-': case '+': case '*': case '/': !increment && (increment = {}); key = key.substring(1); increment[key] = val ? val : 0; break; case '<': case '>': key = key.substring(1); break; default: !params && (params = {}); params[key] = val; break; } } var builder = cmd.builder; var opt = builder.options; var filter = WHERE(builder); // builder.db.$debug && builder.db.$debug(q); var upd = {}; increment && (upd.$inc = increment); params && (upd.$set = params); var col = client.db(client.$database).collection(opt.table); var callback = function (err, response) { err && client.$opt.onerror && client.$opt.onerror(err, opt, builder); var count = response && response.modifiedCount ? response.modifiedCount : 0; if (!count && cmd.insert) { if (cmd.insert !== true) cmd.builder.value = cmd.insert; cmd.builder.options.insert && cmd.builder.options.insert( cmd.builder.value, cmd.builder.options.insertparams ); insert(client, cmd); } else { builder.$callback(err, count); } }; if (opt.first) col.updateOne(filter.where, upd, callback); else col.updateMany(filter.where, upd, callback); } function remove(client, cmd) { var builder = cmd.builder; var opt = builder.options; var filter = WHERE(builder); // builder.db.$debug && builder.db.$debug(q); var col = client.db(client.$database).collection(opt.table); var callback = function (err, response) { err && client.$opt.onerror && client.$opt.onerror(err, opt, builder); builder.$callback(err, response && response.result ? response.result.n : 0); }; if (opt.first) col.deleteOne(filter.where, callback); else col.deleteMany(filter.where, callback); } exports.run = function(opt, self, cmd) { // Pasar la configuración completa a getClient, incluyendo maxPoolSize si existe var clientOptions = { options: opt.options, // URI de conexión maxPoolSize: opt.maxPoolSize || 10 // Usar maxPoolSize de la configuración o valor por defecto }; // A partir de este punto getClient se encarga de extraer maxPoolSize correctamente getClient(clientOptions, function(err, client) { if (err) { opt.onerror && opt.onerror(err); if (cmd.builder) cmd.builder.$callback(err); else cmd.db.$next(err); return; } // Configurar propiedades necesarias client.$opt = opt; client.$dbms = self; client.$database = opt.database; // Ejecutar la operación correspondiente switch (cmd.type) { case 'transaction': case 'commit': case 'rollback': cmd.$next(new Error('"' + cmd.type + '" no está implementado.')); break; case 'bigFind': bigselect(client, cmd); break; case 'find': case 'read': select(client, cmd); break; case 'diff': var cb = cmd.builder.$callback; cmd.builder.$callback = function (err, response) { cb.call( cmd.builder, err, err ? EMPTYOBJECT : compareArrayDiff(cmd.key, response, [cmd.form]) ); }; select(client, cmd); break; case 'list': list(client, cmd); break; case 'listCollections': listCollections(client, cmd); break; case 'listDatabases': listDatabases(client, cmd); break; case 'pipeLines': pipeLines(client, cmd); break; case 'scalar': scalar(client, cmd); break; case 'insert': if (cmd.unique) insertexists(client, cmd); else insert(client, cmd); break; case 'update': case 'modify': modify(client, cmd); break; case 'remove': remove(client, cmd); break; case 'query': query(client, cmd); break; default: if (cmd.builder && cmd.builder.$callback) { cmd.builder.$callback( new Error('Operación "' + cmd.type + '" no encontrada') ); } else if (cmd.$callback) { cmd.$callback( new Error('Operación "' + cmd.type + '" no encontrada') ); } break; } }); }; exports.blob_read = function (opt, id, callback, conn) { getClient(opt.options, function (err, client) { if (err) { callback(err); return; } if (conn.table && conn.table !== 'default') BUCKETNAME.bucketName = conn.table; else BUCKETNAME.bucketName = 'db'; var done = () => {}; // No cerrar la conexión var bucket = new MongoDB.GridFSBucket(client.db(opt.database), BUCKETNAME); var stream = bucket.openDownloadStream(id); stream.on('error', done); stream.on('end', done); callback(null, stream); }); }; exports.blob_write = function (opt, stream, name, callback, conn) { getClient(opt.options, function (err, client) { if (err) { callback(err); return; } if (conn.table && conn.table !== 'default') BUCKETNAME.bucketName = conn.table; else BUCKETNAME.bucketName = 'db'; var options = global.U ? { contentType: U.getContentType(U.getExtension(name)) } : null; var bucket = new MongoDB.GridFSBucket(client.db(opt.database), BUCKETNAME); var writer = bucket.openUploadStream(name, options); stream .pipe(writer) .on('error', function (err) { callback(err); }) .on('finish', function () { callback(null, writer.id); }); }); }; exports.blob_remove = function (opt, id, callback, conn) { getClient(opt.options, function (err, client) { if (err) { callback && callback(err); return; } if (conn.table && conn.table !== 'default') BUCKETNAME.bucketName = conn.table; else BUCKETNAME.bucketName = 'db'; var bucket = new MongoDB.GridFSBucket(client.db(opt.database), BUCKETNAME); bucket.delete(id, function (err) { callback && callback(err); }); }); }; function eqgtlt(cmd) { var val; if (cmd.compare === '=' || cmd.compare === '==') { val = { '$eq': cmd.value }; } else if (cmd.compare === '>' || cmd.compare === 'gt') { val = { '$gt': cmd.value }; } else if (cmd.compare === '>=' || cmd.compare === 'gte') { val = { '$gte': cmd.value }; } else if (cmd.compare === '<' || cmd.compare === 'lt') { val = { '$lt': cmd.value }; } else if (cmd.compare === '<=' || cmd.compare === 'lte') { val = { '$lte': cmd.value }; } return val; } /** * Función para generar un array de diferencias entre objeto y formulario * @param {string} key - Clave primaria * @param {Object} data - Datos de la base de datos * @param {Array} form - Formulario con datos a comparar * @returns {Array} - Array con diferencias */ function compareArrayDiff(key, data, form) { var arr = []; var tmp; if (!(data instanceof Array)) { if (!data) return EMPTYARRAY; tmp = []; tmp.push(data); data = tmp; } if (!(form instanceof Array)) { tmp = []; tmp.push(form); form = tmp; } for (var i = 0; i < form.length; i++) { var keys = Object.keys(form[i]); for (var j = 0; j < data.length; j++) { if (data[j][key] == form[i][key]) { var json = {}; json[key] = form[i][key]; var b = false; for (var k = 0; k < keys.length; k++) { var m = keys[k]; if (form[i][m] != data[j][m]) { json[m] = form[i][m]; json['old_' + m] = data[j][m]; b = true; } } if (b) arr.push(json); } } } return arr; } function WHERE(builder, scalar) { // , group var condition = {}; var sort = null; var tmp = null; var filter; var value; for (var i = 0; i < builder.$commands.length; i++) { var cmd = builder.$commands[i]; switch (cmd.type) { case 'where': value = cmd.compare === '=' ? cmd.value : eqgtlt(cmd); if (tmp) { filter = {}; filter[cmd.name] = value; tmp.push(filter); } else condition[cmd.name] = value; break; case 'in': if (typeof cmd.value === 'function') cmd.value = cmd.value(); value = cmd.value instanceof Array ? { $in: cmd.value } : cmd.value; if (tmp) { filter = {}; filter[cmd.name] = value; tmp.push(filter); } else condition[cmd.name] = value; break; case 'notin': if (typeof cmd.value === 'function') cmd.value = cmd.value(); value = cmd.value instanceof Array ? { $nin: cmd.value } : { $ne: cmd.value }; if (tmp) { filter = {}; filter[cmd.name] = value; tmp.push(filter); } else condition[cmd.name] = value; break; case 'between': value = { $gte: cmd.a, $lte: cmd.b, $exists: true, $nin: [null, ''] }; if (tmp) { filter = {}; filter[cmd.name] = value; tmp.push(filter); } else condition[cmd.name] = value; break; case 'search': value = (cmd.compare === '*' ? '' : cmd.compare === 'beg' ? '^' : '') + // Escapar caracteres especiales de RegEx en cmd.value String(cmd.value || '').replace(/[.*+?^${}()|[\]\\]/g, '\\$&') + (cmd.compare === 'end' ? '$' : ''); var v = ['[aáä]', '[eéë]', '[iíï]', '[oóö]', '[uúü]']; for (let i = 0; i < v.length; i++) value = value.replace(new RegExp(v[i].replace(/[.*+?^${}()|[\]\\]/g, '\\$&'), 'gi'), v[i]); value = { $regex: value, $options: 'i' }; if (tmp) { filter = {}; filter[cmd.name] = value; tmp.push(filter); } else condition[cmd.name] = value; break; case 'fulltext': value = new RegExp( (cmd.compare === '*' ? '' : cmd.compare === 'beg' ? '^' : '') + // Escapar caracteres especiales de RegEx en cmd.value String(cmd.value || '').replace(/[.*+?^${}()|[\]\\]/g, '\\$&') + (cmd.compare === 'end' ? '$' : ''), 'i' ); if (tmp) { filter = {}; filter[cmd.name] = value; tmp.push(filter); } else condition[cmd.name] = value; break; case 'contains': value = { $exists: true, $nin: [null, ''] }; if (tmp) { filter = {}; filter[cmd.name] = value; tmp.push(filter); } else condition[cmd.name] = value; break; case 'empty': value = { $exists: false, $in: [null, ''] }; if (tmp) { filter = {}; filter[cmd.name] = value; tmp.push(filter); } else condition[cmd.name] = value; break; case 'month': case 'year': case 'day': case 'hour': case 'minute': new Error( 'Not implemented', "MongoDB doesn't support date function for quering." ); break; case 'query': if (tmp) { filter = {}; tmp.push(cmd.query); } else { var arr = Object.keys(cmd.query); for (var j = 0; j < arr.length; j++) condition[arr[j]] = cmd.query[arr[j]]; } break; case 'or': tmp = []; continue; case 'end': condition.$or = tmp; tmp = null; break; case 'and': break; case 'sort': !sort && (sort = {}); sort[cmd.name] = cmd.desc ? -1 : 1; break; case 'regexp': if (tmp) { filter = {}; filter[cmd.name] = cmd.value; tmp.push(filter); } else condition[cmd.name] = cmd.value; break; } } if (scalar) return { $match: condition }; else return { where: condition, sort: sort }; } function FIELDS(builder) { var output = null; var fields = builder.options.fields; if (fields && fields.length) { output = {}; for (var i = 0; i < fields.length; i++) { var name = fields[i]; var is = name[0] === '-'; if (is) name = name.substring(1); output[name] = is ? 0 : 1; } if (builder.$joinmeta) output[builder.$joinmeta.a] = 1; } return output; } /** * Funcion para manejar el cierre de conexiones y terminar el proceso * @param {string} signal - Señal recibida (SIGINT, SIGTERM) */ function handleProcessTermination(signal) { console.log(`Recibida señal ${signal}, cerrando conexiones...`); exports .disconnect() .then(() => { console.log(`Conexiones cerradas correctamente debido a ${signal}`); // Dar un breve tiempo para que los logs se escriban antes de salir setTimeout(() => process.exit(0), 100); }) .catch((err) => { console.error(`Error al cerrar conexiones: ${err.message || err}`); process.exit(1); }); // Si el proceso no termina en 5 segundos, forzar la salida setTimeout(() => { console.error( `Tiempo de espera agotado para cerrar conexiones, forzando salida...` ); process.exit(1); }, 5000); } // Manejar señales de terminación del proceso process.on('SIGINT', () => handleProcessTermination('SIGINT')); process.on('SIGTERM', () => handleProcessTermination('SIGTERM'));