UNPKG

@dbcube/core

Version:
1 lines 99.9 kB
{"version":3,"sources":["../src/index.ts","../src/lib/Engine.ts","../src/lib/Arquitecture.ts","../src/lib/Binary.ts","../src/lib/Config.ts","../src/lib/DbConfig.ts","../src/lib/FileLogger.ts","../src/lib/Processors.ts"],"sourcesContent":["import { Engine } from './lib/Engine';\r\nimport { Arquitecture } from './lib/Arquitecture';\r\nimport { Binary } from './lib/Binary';\r\nimport { DbConfig } from './lib/DbConfig';\r\nimport { Config } from './lib/Config';\r\nimport { FileLogger, InterceptController } from './lib/FileLogger';\r\nimport { ComputedFieldProcessor, TableProcessor, TriggerProcessor } from './lib/Processors';\r\n\r\nexport { Config, Engine, Arquitecture, Binary, DbConfig, ComputedFieldProcessor, TableProcessor, TriggerProcessor, FileLogger, InterceptController };\r\n","import path from \"path\";\r\nimport { Binary } from \"./Binary\";\r\nimport { Config as ConfigClass } from './Config';\r\nimport { spawn } from \"child_process\";\r\nimport { ProcessResponse, ResponseEngine } from \"../@types/Engine\";\r\nimport { BinaryType } from \"../@types/Binary\";\r\n\r\nclass Engine{\r\n private name: string;\r\n private config: any;\r\n private arguments: any;\r\n private binary: BinaryType;\r\n private timeout: number;\r\n\r\n constructor(name: string, timeout = 30000){\r\n this.name = name; \r\n this.config = this.setConfig(name);\r\n const binary: BinaryType = Binary.get();\r\n this.binary = {\r\n query_engine: path.resolve(__dirname, '../bin', binary.query_engine),\r\n schema_engine: path.resolve(__dirname, '../bin', binary.schema_engine),\r\n }\r\n this.arguments = this.setArguments();\r\n this.timeout = timeout;\r\n }\r\n\r\n setArguments(){\r\n let args = [];\r\n if(this.config.type=='sqlite'){\r\n args = [\r\n '--id', 'dbcube-'+this.name,\r\n '--database-ref', this.name,\r\n '--database', this.config.config.DATABASE+\".db\",\r\n '--motor', this.config.type\r\n ];\r\n }else{\r\n args = [\r\n '--id', 'dbcube-'+this.name,\r\n '--database-ref', this.name,\r\n '--database', this.config.config.DATABASE,\r\n '--host', this.config.config.HOST,\r\n '--port', this.config.config.PORT,\r\n '--user', this.config.config.USER,\r\n '--password', this.config.config.PASSWORD,\r\n '--motor', this.config.type\r\n ];\r\n }\r\n return args;\r\n }\r\n\r\n setConfig(name: string){\r\n const configInstance = new ConfigClass();\r\n\r\n const configFilePath = path.resolve(process.cwd(), 'dbcube.config.js');\r\n const configFn = require(configFilePath);\r\n\r\n if (typeof configFn === 'function') {\r\n configFn(configInstance);\r\n } else {\r\n console.error('❌ El archivo dbcube.config.js no exporta una función.');\r\n }\r\n \r\n return configInstance.getDatabase(name);\r\n\r\n }\r\n\r\n getConfig(){\r\n return this.config;\r\n }\r\n\r\n async run(binary:string, args: []){\r\n return new Promise<ResponseEngine>((resolve, reject) => {\r\n const child = spawn(this.binary[binary as keyof BinaryType], [...this.arguments, ...args]);\r\n\r\n let stdoutBuffer = '';\r\n let stderrBuffer = '';\r\n let isResolved = false;\r\n\r\n const timeoutId = setTimeout(() => {\r\n if (!isResolved) {\r\n isResolved = true;\r\n child.kill();\r\n reject(new Error('Process timeout'));\r\n }\r\n }, this.timeout);\r\n\r\n const resolveOnce = (response: ResponseEngine) => {\r\n if (!isResolved) {\r\n isResolved = true;\r\n clearTimeout(timeoutId);\r\n resolve(response);\r\n }\r\n };\r\n\r\n child.stdout.on('data', (data) => {\r\n stdoutBuffer += data.toString();\r\n // console.log(stdoutBuffer)\r\n \r\n // Buscar respuesta JSON completa\r\n const match = stdoutBuffer.match(/PROCESS_RESPONSE:(\\{.*\\})/);\r\n if (match) {\r\n try {\r\n const response: ProcessResponse = JSON.parse(match[1]);\r\n resolveOnce({\r\n status: response.status,\r\n message: response.message,\r\n data: response.data\r\n });\r\n } catch (error) {\r\n resolveOnce({\r\n status: 500,\r\n message: 'Failed to parse response JSON',\r\n data: null\r\n });\r\n }\r\n }\r\n });\r\n\r\n child.stderr.on('data', (data) => {\r\n stderrBuffer += data.toString();\r\n // console.log(stderrBuffer)\r\n \r\n // Buscar respuesta JSON completa\r\n const match = stderrBuffer.match(/PROCESS_RESPONSE:(\\{.*\\})/);\r\n if (match) {\r\n try {\r\n const response: ProcessResponse = JSON.parse(match[1]);\r\n resolveOnce({\r\n status: response.status,\r\n message: response.message,\r\n data: response.data\r\n });\r\n } catch (error) {\r\n resolveOnce({\r\n status: 500,\r\n message: 'Failed to parse response JSON',\r\n data: null\r\n });\r\n }\r\n }\r\n });\r\n\r\n\r\n child.on('close', (code) => {\r\n clearTimeout(timeoutId);\r\n if (!isResolved) {\r\n resolveOnce({\r\n status: code === 0 ? 200 : 500,\r\n message: code === 0 ? 'Process completed' : `Process exited with code ${code}`,\r\n data: null\r\n });\r\n }\r\n });\r\n\r\n child.on('error', (error) => {\r\n clearTimeout(timeoutId);\r\n if (!isResolved) {\r\n resolveOnce({\r\n status: 500,\r\n message: `Process error: ${error.message}`,\r\n data: null\r\n });\r\n }\r\n });\r\n\r\n child.unref();\r\n });\r\n }\r\n}\r\n\r\nexport { Engine }","import * as os from 'os';\r\nimport * as path from 'path';\r\nimport * as fs from 'fs';\r\n\r\ninterface SystemInfo {\r\n platform: string;\r\n arch: string;\r\n release: string;\r\n type: string;\r\n endianness: string;\r\n cpus: number;\r\n}\r\n\r\ninterface BinaryInfo {\r\n name: string;\r\n path: string;\r\n exists: boolean;\r\n executable: boolean;\r\n}\r\n\r\nclass Arquitecture {\r\n private systemInfo: SystemInfo;\r\n\r\n constructor() {\r\n this.systemInfo = this.detectSystemInfo();\r\n }\r\n\r\n /**\r\n * Detecta información completa del sistema\r\n */\r\n private detectSystemInfo(): SystemInfo {\r\n return {\r\n platform: os.platform(),\r\n arch: os.arch(),\r\n release: os.release(),\r\n type: os.type(),\r\n endianness: os.endianness(),\r\n cpus: os.cpus().length\r\n };\r\n }\r\n\r\n /**\r\n * Obtiene la plataforma normalizada\r\n */\r\n getPlatform(): string {\r\n const platform = this.systemInfo.platform;\r\n \r\n switch (platform) {\r\n case 'win32':\r\n return 'windows';\r\n case 'darwin':\r\n return 'macos';\r\n case 'linux':\r\n return 'linux';\r\n case 'freebsd':\r\n return 'freebsd';\r\n case 'openbsd':\r\n return 'openbsd';\r\n case 'sunos':\r\n return 'solaris';\r\n default:\r\n return platform;\r\n }\r\n }\r\n\r\n /**\r\n * Obtiene la arquitectura normalizada\r\n */\r\n getArchitecture(): string {\r\n const arch = this.systemInfo.arch;\r\n \r\n switch (arch) {\r\n case 'x64':\r\n return 'x86_64';\r\n case 'x32':\r\n case 'ia32':\r\n return 'i686';\r\n case 'arm64':\r\n return 'aarch64';\r\n case 'arm':\r\n return 'armv7';\r\n case 'ppc64':\r\n return 'powerpc64';\r\n case 'ppc':\r\n return 'powerpc';\r\n case 's390x':\r\n return 's390x';\r\n case 'mips':\r\n return 'mips';\r\n case 'mipsel':\r\n return 'mipsel';\r\n default:\r\n return arch;\r\n }\r\n }\r\n\r\n /**\r\n * Genera el nombre del binario basado en la arquitectura\r\n */\r\n getBinaryName(baseName: string): string {\r\n const platform = this.getPlatform();\r\n const arch = this.getArchitecture();\r\n const extension = platform === 'windows' ? '.exe' : '';\r\n \r\n return `${baseName}-${platform}-${arch}${extension}`;\r\n }\r\n\r\n /**\r\n * Obtiene información completa del sistema\r\n */\r\n getSystemInfo(): SystemInfo {\r\n return { ...this.systemInfo };\r\n }\r\n\r\n /**\r\n * Obtiene el triple de destino (target triple) para Rust\r\n */\r\n getRustTargetTriple(): string {\r\n const platform = this.getPlatform();\r\n const arch = this.getArchitecture();\r\n \r\n // Mapeo de plataformas y arquitecturas a target triples de Rust\r\n const targetMap: { [key: string]: string } = {\r\n 'linux-x86_64': 'x86_64-unknown-linux-gnu',\r\n 'linux-i686': 'i686-unknown-linux-gnu',\r\n 'linux-aarch64': 'aarch64-unknown-linux-gnu',\r\n 'linux-armv7': 'armv7-unknown-linux-gnueabihf',\r\n 'macos-x86_64': 'x86_64-apple-darwin',\r\n 'macos-aarch64': 'aarch64-apple-darwin',\r\n 'windows-x86_64': 'x86_64-pc-windows-msvc',\r\n 'windows-i686': 'i686-pc-windows-msvc',\r\n 'windows-aarch64': 'aarch64-pc-windows-msvc',\r\n 'freebsd-x86_64': 'x86_64-unknown-freebsd',\r\n };\r\n \r\n const key = `${platform}-${arch}`;\r\n return targetMap[key] || `${arch}-unknown-${platform}`;\r\n }\r\n\r\n /**\r\n * Muestra información detallada del sistema\r\n */\r\n printSystemInfo(): void {\r\n console.log('🖥️ System Information:');\r\n console.log('├─ Platform:', this.getPlatform());\r\n console.log('├─ Arquitecture:', this.getArchitecture());\r\n console.log('├─ OS Type:', this.systemInfo.type);\r\n console.log('├─ OS Release:', this.systemInfo.release);\r\n console.log('├─ Endianness:', this.systemInfo.endianness);\r\n console.log('├─ CPUs:', this.systemInfo.cpus);\r\n console.log('└─ Rust Target:', this.getRustTargetTriple());\r\n }\r\n}\r\n\r\nexport { \r\n Arquitecture, \r\n SystemInfo,\r\n BinaryInfo\r\n};","import { BinaryType } from \"../@types/Binary\";\r\nimport { Arquitecture } from \"./Arquitecture\"\r\n\r\nclass Binary{\r\n static get(): BinaryType{\r\n const arch = new Arquitecture();\r\n const platform = arch.getPlatform(); \r\n const architecture = arch.getArchitecture(); \r\n switch(platform){\r\n case \"windows\":\r\n if(architecture==\"x86_64\"){\r\n return {\r\n query_engine: \"query-engine-windows-x64.exe\",\r\n schema_engine: \"schema-engine-windows-x64.exe\"\r\n };\r\n }\r\n break;\r\n }\r\n return {\r\n query_engine: \"\",\r\n schema_engine: \"\"\r\n };\r\n }\r\n}\r\n\r\nexport { Binary }","/**\r\n * Tipo para la configuración de una base de datos\r\n */\r\ntype DatabaseConfig = Record<string, any>;\r\n\r\n/**\r\n * Tipo para la configuración general del ORM\r\n */\r\ninterface ConfigData {\r\n [key: string]: any;\r\n databases?: Record<string, DatabaseConfig>;\r\n}\r\n\r\n/**\r\n * Clase para manejar la configuración del ORM\r\n */\r\nexport class Config {\r\n private data: ConfigData = {};\r\n private databases: Record<string, DatabaseConfig> = {};\r\n\r\n /**\r\n * Establece la configuración\r\n * @param configData - Datos de configuración\r\n */\r\n set(configData: ConfigData): void {\r\n this.data = configData;\r\n\r\n if (configData.databases) {\r\n this.databases = configData.databases;\r\n }\r\n }\r\n\r\n /**\r\n * Obtiene un valor de configuración\r\n * @param key - Clave de configuración\r\n * @returns Valor de configuración\r\n */\r\n get<T = any>(key: string): T {\r\n return this.data[key];\r\n }\r\n\r\n /**\r\n * Obtiene la configuración de una base de datos específica\r\n * @param dbName - Nombre de la base de datos\r\n * @returns Configuración de la base de datos o null\r\n */\r\n getDatabase(dbName: string): DatabaseConfig | null {\r\n return this.databases[dbName] || null;\r\n }\r\n\r\n /**\r\n * Obtiene todas las bases de datos configuradas\r\n * @returns Todas las configuraciones de bases de datos\r\n */\r\n getAllDatabases(): Record<string, DatabaseConfig> {\r\n return this.databases;\r\n }\r\n}\r\n\r\nexport default Config;\r\n","import * as sqlite3 from 'sqlite3';\r\nimport * as path from 'path';\r\nimport fs from 'fs';\r\n\r\nconst rootPath = path.resolve(process.cwd(), 'dbcube');\r\n\r\ninterface DatabaseConfig {\r\n name?: string;\r\n HOST?: string;\r\n USER?: string;\r\n PASSWORD?: string;\r\n DATABASE?: string;\r\n PORT?: number;\r\n}\r\n\r\ninterface QueryResult {\r\n status: 'success' | 'error';\r\n message: string;\r\n data: any | null;\r\n}\r\n\r\ninterface RunResult {\r\n changes: number;\r\n lastID: number;\r\n}\r\n\r\ninterface ParametrizedQueryResult {\r\n query: string;\r\n parameters: any[];\r\n}\r\n\r\n/**\r\n * Main class to handle SQLite database connections and queries.\r\n * Implements the Singleton pattern to ensure a single instance of the connection.\r\n */\r\nclass SQLite {\r\n private db: sqlite3.Database | null = null;\r\n private database?: string;\r\n \r\n constructor(config: DatabaseConfig) {\r\n this.database = config.DATABASE;\r\n }\r\n\r\n ifExist(): Boolean {\r\n if (this.database) {\r\n const dbPath = this.database || ':memory:';\r\n const configPath = path.join(rootPath, dbPath + \".db\");\r\n if (fs.existsSync(configPath)) {\r\n return true;\r\n }\r\n }\r\n return false;\r\n }\r\n\r\n async connect(): Promise<sqlite3.Database> {\r\n if (!this.db) {\r\n try {\r\n const dbPath = this.database || ':memory:';\r\n const configPath = path.join(rootPath, dbPath + \".db\");\r\n \r\n this.db = new sqlite3.Database(configPath, (err) => {\r\n if (err) throw err;\r\n });\r\n }\r\n catch (error) {\r\n throw error;\r\n }\r\n }\r\n return this.db;\r\n }\r\n\r\n async disconnect(): Promise<void> {\r\n if (this.db) {\r\n return new Promise((resolve, reject) => {\r\n this.db!.close((err) => {\r\n if (err) {\r\n return reject(err);\r\n }\r\n this.db = null;\r\n resolve();\r\n });\r\n });\r\n }\r\n }\r\n\r\n /**\r\n * Executes a SQL query on the currently set database.\r\n *\r\n * @param {string} sqlQuery - The SQL query to execute.\r\n * @returns {Promise<QueryResult>} - Returns a JSON object with the status, message, and data (if any).\r\n *\r\n * @example\r\n * const result = await db.query('SELECT * FROM users;');\r\n * console.log(result);\r\n * // { status: 'success', message: 'Query executed successfully', data: [{ id: 1, name: 'John' }, { id: 2, name: 'Jane' }] }\r\n *\r\n * @example\r\n * const result = await db.query('INVALID SQL QUERY;');\r\n * console.log(result);\r\n * // { status: 'error', message: 'SQL syntax error', data: null }\r\n */\r\n async query(sqlQuery: string): Promise<QueryResult> {\r\n // Validar que el parámetro sqlQuery sea una cadena de texto\r\n if (typeof sqlQuery !== 'string') {\r\n throw new Error('The SQL query must be a string.');\r\n }\r\n \r\n // Dividir la consulta en comandos individuales (si hay múltiples comandos separados por ';')\r\n const sqlCommands = sqlQuery.split(';').filter(cmd => cmd.trim().length > 0);\r\n \r\n return new Promise((resolve) => {\r\n try {\r\n // Verificar si la conexión a la base de datos está disponible\r\n if (!this.db) {\r\n throw new Error('Database connection is not available.');\r\n }\r\n \r\n // Ejecutar cada comando SQL\r\n const results: any[] = [];\r\n let commandsProcessed = 0;\r\n \r\n // SQLite no soporta múltiples consultas en una sola llamada, así que las ejecutamos en serie\r\n for (const command of sqlCommands) {\r\n const query = `${command};`;\r\n \r\n // Determinar si la consulta es una SELECT (devuelve resultados) o no\r\n const isSelect = query.trim().toLowerCase().startsWith('select');\r\n \r\n if (isSelect) {\r\n this.db.all(query, [], (err: Error | null, rows: any[]) => {\r\n if (err) {\r\n throw err;\r\n }\r\n \r\n results.push(rows);\r\n commandsProcessed++;\r\n \r\n if (commandsProcessed === sqlCommands.length) {\r\n resolve({\r\n status: 'success',\r\n message: 'Query executed successfully',\r\n data: results.length === 1 ? results[0] : results,\r\n });\r\n }\r\n });\r\n } else {\r\n this.db.run(query, [], function(this: sqlite3.RunResult, err: Error | null) {\r\n if (err) {\r\n throw err;\r\n }\r\n \r\n results.push({ changes: this.changes, lastID: this.lastID });\r\n commandsProcessed++;\r\n \r\n if (commandsProcessed === sqlCommands.length) {\r\n resolve({\r\n status: 'success',\r\n message: 'Query executed successfully',\r\n data: results.length === 1 ? results[0] : results,\r\n });\r\n }\r\n });\r\n }\r\n }\r\n }\r\n catch (error) {\r\n // Manejar errores y devolver la respuesta en formato JSON\r\n resolve({\r\n status: 'error',\r\n message: (error as Error).message || 'An error occurred while executing the query.',\r\n data: null,\r\n });\r\n }\r\n });\r\n }\r\n\r\n /**\r\n * Executes a SQL query with parameters on the currently set database.\r\n *\r\n * @param {string} sqlQuery - The SQL query to execute with placeholders (?).\r\n * @param {any[]} params - Array of parameters to bind to the query placeholders.\r\n * @returns {Promise<QueryResult>} - Returns a JSON object with the status, message, and data (if any).\r\n *\r\n * @example\r\n * const result = await db.queryWithParameters('INSERT INTO users (name, email) VALUES (?, ?)', ['John', 'john@example.com']);\r\n * console.log(result);\r\n * // { status: 'success', message: 'Query executed successfully', data: { changes: 1, lastID: 3 } }\r\n */\r\n async queryWithParameters(sqlQuery: string, params: any[] = []): Promise<QueryResult> {\r\n // Validar que el parámetro sqlQuery sea una cadena de texto\r\n if (typeof sqlQuery !== 'string') {\r\n throw new Error('The SQL query must be a string.');\r\n }\r\n \r\n // Validar que params sea un array\r\n if (!Array.isArray(params)) {\r\n throw new Error('Parameters must be an array.');\r\n }\r\n \r\n return new Promise((resolve) => {\r\n try {\r\n // Verificar si la conexión a la base de datos está disponible\r\n if (!this.db) {\r\n throw new Error('Database connection is not available.');\r\n }\r\n \r\n // Determinar si la consulta es una SELECT (devuelve resultados) o no\r\n const isSelect = sqlQuery.trim().toLowerCase().startsWith('select');\r\n \r\n if (isSelect) {\r\n this.db.all(sqlQuery, params, (err: Error | null, rows: any[]) => {\r\n if (err) {\r\n throw err;\r\n }\r\n \r\n resolve({\r\n status: 'success',\r\n message: 'Query executed successfully',\r\n data: rows,\r\n });\r\n });\r\n } else {\r\n this.db.run(sqlQuery, params, function(this: sqlite3.RunResult, err: Error | null) {\r\n if (err) {\r\n throw err;\r\n }\r\n \r\n resolve({\r\n status: 'success',\r\n message: 'Query executed successfully',\r\n data: { changes: this.changes, lastID: this.lastID },\r\n });\r\n });\r\n }\r\n }\r\n catch (error) {\r\n // Manejar errores y devolver la respuesta en formato JSON\r\n console.log(error);\r\n resolve({\r\n status: 'error',\r\n message: (error as Error).message || 'An error occurred while executing the query.',\r\n data: null,\r\n });\r\n }\r\n });\r\n }\r\n\r\n convertToParameterizedQuery(sql: string): ParametrizedQueryResult {\r\n // Normalizar la consulta SQL (eliminar saltos de línea y espacios extra)\r\n const normalizedSql = sql.replace(/\\s+/g, ' ').trim();\r\n \r\n // Extraer la parte de la consulta base (antes de VALUES)\r\n const baseQueryMatch = normalizedSql.match(/^(.+?)\\s+VALUES\\s*\\(/i);\r\n if (!baseQueryMatch) {\r\n throw new Error('No se pudo encontrar la estructura VALUES en la consulta');\r\n }\r\n \r\n const baseQuery = baseQueryMatch[1];\r\n \r\n // Extraer los valores entre paréntesis (manejo mejorado para contenido complejo)\r\n const valuesStartIndex = normalizedSql.toUpperCase().indexOf('VALUES');\r\n const valuesSection = normalizedSql.substring(valuesStartIndex);\r\n const valuesMatch = valuesSection.match(/VALUES\\s*\\((.+)\\)\\s*;?\\s*$/i);\r\n if (!valuesMatch) {\r\n throw new Error('No se pudieron extraer los valores de la consulta');\r\n }\r\n \r\n const valuesString = valuesMatch[1];\r\n \r\n // Función para parsear los valores considerando @compute\r\n function parseValues(str: string): any[] {\r\n const values: any[] = [];\r\n let currentValue = '';\r\n let inQuotes = false;\r\n let quoteChar = '';\r\n let inCompute = false;\r\n let computeDepth = 0;\r\n \r\n for (let i = 0; i < str.length; i++) {\r\n const char = str[i];\r\n const prevChar = str[i - 1];\r\n \r\n // Detectar inicio de @compute\r\n if (!inQuotes && str.substring(i, i + 8) === '@compute') {\r\n inCompute = true;\r\n }\r\n \r\n // Contar paréntesis dentro de @compute\r\n if (inCompute && char === '(') {\r\n computeDepth++;\r\n } else if (inCompute && char === ')') {\r\n computeDepth--;\r\n if (computeDepth === 0) {\r\n inCompute = false;\r\n }\r\n }\r\n \r\n // Manejo de comillas\r\n if (!inCompute && (char === '\"' || char === \"'\") && prevChar !== '\\\\') {\r\n if (!inQuotes) {\r\n inQuotes = true;\r\n quoteChar = char;\r\n } else if (char === quoteChar) {\r\n // Verificar si es una comilla escapada (doble comilla)\r\n if (str[i + 1] === quoteChar) {\r\n currentValue += char + char;\r\n i++; // Saltar la siguiente comilla\r\n continue;\r\n } else {\r\n inQuotes = false;\r\n quoteChar = '';\r\n }\r\n }\r\n }\r\n \r\n // Detectar separador de valores (coma)\r\n if (!inQuotes && !inCompute && char === ',') {\r\n values.push(cleanValue(currentValue.trim()));\r\n currentValue = '';\r\n continue;\r\n }\r\n \r\n currentValue += char;\r\n }\r\n \r\n // Agregar el último valor\r\n if (currentValue.trim()) {\r\n values.push(cleanValue(currentValue.trim()));\r\n }\r\n \r\n return values;\r\n }\r\n \r\n // Función para limpiar y procesar cada valor\r\n function cleanValue(value: string): any {\r\n value = value.trim();\r\n \r\n // Si es un @compute, no quitar comillas externas\r\n if (value.startsWith('@compute')) {\r\n return value;\r\n }\r\n \r\n // Quitar comillas externas para otros valores\r\n if ((value.startsWith('\"') && value.endsWith('\"')) || \r\n (value.startsWith(\"'\") && value.endsWith(\"'\"))) {\r\n value = value.slice(1, -1);\r\n \r\n // Desescapar comillas dobles\r\n value = value.replace(/''/g, \"'\").replace(/\"\"/g, '\"');\r\n }\r\n \r\n return value;\r\n }\r\n \r\n // Parsear los valores\r\n const parameters = parseValues(valuesString);\r\n \r\n // Crear la consulta parametrizada\r\n const placeholders = parameters.map(() => '?').join(', ');\r\n const parametrizedQuery = `${baseQuery} VALUES (${placeholders});`;\r\n \r\n return {\r\n query: parametrizedQuery,\r\n parameters: parameters\r\n };\r\n }\r\n}\r\n\r\nexport const DbConfig = new SQLite({DATABASE: \"config\"});\r\nexport default DbConfig;","import * as fs from 'fs';\r\nimport * as path from 'path';\r\nimport { EventEmitter } from 'events';\r\n\r\ninterface InterceptOptions {\r\n interceptLog?: boolean;\r\n interceptError?: boolean;\r\n interceptWarn?: boolean;\r\n keepOriginal?: boolean;\r\n useBuffer?: boolean;\r\n}\r\n\r\ninterface ReadOptions {\r\n lines?: number | null;\r\n fromEnd?: boolean;\r\n asArray?: boolean;\r\n}\r\n\r\ninterface WatchOptions {\r\n persistent?: boolean;\r\n interval?: number;\r\n fromEnd?: boolean;\r\n}\r\n\r\ninterface WatchController {\r\n id: string;\r\n stop: () => void;\r\n isWatching: () => boolean;\r\n}\r\n\r\nexport interface InterceptController {\r\n restore: () => void;\r\n commit: () => Promise<boolean>;\r\n discard: () => number;\r\n hasBuffer: () => boolean;\r\n getBufferSize: () => number;\r\n}\r\n\r\ninterface DeleteResult {\r\n filePath: string;\r\n deleted: boolean;\r\n error: string | null;\r\n}\r\n\r\ntype LogLevel = 'INFO' | 'ERROR' | 'WARN' | 'DEBUG';\r\n\r\nexport class FileLogger extends EventEmitter {\r\n private static watchers = new Map<string, (curr: fs.Stats, prev: fs.Stats) => void>(); // Store listener functions\r\n private static buffers = new Map<string, string[]>();\r\n \r\n /**\r\n * Escribe un log en el archivo especificado\r\n * @param filePath - Ruta del archivo de log\r\n * @param message - Mensaje a escribir\r\n * @param level - Nivel del log (INFO, ERROR, WARN, DEBUG)\r\n * @param append - Si debe agregar al final del archivo (default: true)\r\n */\r\n static async write(filePath: string, message: string, level: LogLevel = 'INFO', append: boolean = true): Promise<boolean> {\r\n try {\r\n // Asegurar que el directorio existe\r\n const dir = path.dirname(filePath);\r\n if (!fs.existsSync(dir)) {\r\n fs.mkdirSync(dir, { recursive: true });\r\n }\r\n \r\n // Formatear el mensaje con timestamp\r\n const timestamp = new Date().toISOString();\r\n const formattedMessage = `[${timestamp}] [${level}] ${message}\\n`;\r\n \r\n // Si existe un buffer para este archivo, agregarlo al buffer en lugar de escribir directamente\r\n if (FileLogger.buffers.has(filePath)) {\r\n FileLogger.buffers.get(filePath)!.push(formattedMessage);\r\n return true;\r\n }\r\n \r\n // Escribir al archivo normalmente\r\n if (append) {\r\n await fs.promises.appendFile(filePath, formattedMessage, 'utf8');\r\n } else {\r\n await fs.promises.writeFile(filePath, formattedMessage, 'utf8');\r\n }\r\n \r\n return true;\r\n } catch (error) {\r\n console.error('Error escribiendo log:', error);\r\n throw error;\r\n }\r\n }\r\n \r\n /**\r\n * Inicia un buffer temporal para un archivo de log\r\n * @param filePath - Ruta del archivo de log\r\n */\r\n static startBuffer(filePath: string): void {\r\n if (!FileLogger.buffers.has(filePath)) {\r\n FileLogger.buffers.set(filePath, []);\r\n }\r\n }\r\n \r\n /**\r\n * Confirma y escribe todos los logs del buffer al archivo\r\n * @param filePath - Ruta del archivo de log\r\n */\r\n static async commitBuffer(filePath: string): Promise<boolean> {\r\n const buffer = FileLogger.buffers.get(filePath);\r\n if (buffer && buffer.length > 0) {\r\n try {\r\n // Asegurar que el directorio existe\r\n const dir = path.dirname(filePath);\r\n if (!fs.existsSync(dir)) {\r\n fs.mkdirSync(dir, { recursive: true });\r\n }\r\n \r\n // Escribir todos los logs del buffer\r\n const content = buffer.join('');\r\n await fs.promises.appendFile(filePath, content, 'utf8');\r\n \r\n // Limpiar el buffer\r\n FileLogger.buffers.delete(filePath);\r\n return true;\r\n } catch (error) {\r\n console.error('Error confirmando buffer:', error);\r\n throw error;\r\n }\r\n }\r\n \r\n // Limpiar el buffer aunque esté vacío\r\n FileLogger.buffers.delete(filePath);\r\n return false;\r\n }\r\n \r\n /**\r\n * Descarta todos los logs del buffer sin escribirlos\r\n * @param filePath - Ruta del archivo de log\r\n */\r\n static discardBuffer(filePath: string): number {\r\n if (FileLogger.buffers.has(filePath)) {\r\n const discardedCount = FileLogger.buffers.get(filePath)!.length;\r\n FileLogger.buffers.delete(filePath);\r\n return discardedCount;\r\n }\r\n return 0;\r\n }\r\n \r\n /**\r\n * Verifica si existe un buffer activo para un archivo\r\n * @param filePath - Ruta del archivo de log\r\n */\r\n static hasBuffer(filePath: string): boolean {\r\n return FileLogger.buffers.has(filePath);\r\n }\r\n \r\n /**\r\n * Obtiene el número de logs en el buffer\r\n * @param filePath - Ruta del archivo de log\r\n */\r\n static getBufferSize(filePath: string): number {\r\n const buffer = FileLogger.buffers.get(filePath);\r\n return buffer ? buffer.length : 0;\r\n }\r\n \r\n /**\r\n * Intercepta console.log y console.error para escribir a archivo\r\n * @param filePath - Ruta del archivo de log\r\n * @param options - Opciones de interceptación\r\n */\r\n static interceptConsole(filePath: string, options: InterceptOptions = {}): InterceptController {\r\n const {\r\n interceptLog = true,\r\n interceptError = true,\r\n interceptWarn = true,\r\n keepOriginal = true, // Mantener el comportamiento original de console\r\n useBuffer = false // Usar buffer temporal\r\n } = options;\r\n \r\n // Iniciar buffer si se solicita\r\n if (useBuffer) {\r\n FileLogger.startBuffer(filePath);\r\n }\r\n \r\n // Guardar referencias originales\r\n const originalLog = console.log;\r\n const originalError = console.error;\r\n const originalWarn = console.warn;\r\n \r\n if (interceptLog) {\r\n console.log = function(...args: any[]) {\r\n const message = args.map(arg => \r\n typeof arg === 'object' ? JSON.stringify(arg) : String(arg)\r\n ).join(' ');\r\n \r\n FileLogger.write(filePath, message, 'INFO').catch(err => {\r\n originalError('Error escribiendo log:', err);\r\n });\r\n \r\n if (keepOriginal) {\r\n originalLog.apply(console, args);\r\n }\r\n };\r\n }\r\n \r\n if (interceptError) {\r\n console.error = function(...args: any[]) {\r\n const message = args.map(arg => \r\n typeof arg === 'object' ? JSON.stringify(arg) : String(arg)\r\n ).join(' ');\r\n \r\n FileLogger.write(filePath, message, 'ERROR').catch(err => {\r\n originalError('Error escribiendo error log:', err);\r\n });\r\n \r\n if (keepOriginal) {\r\n originalError.apply(console, args);\r\n }\r\n };\r\n }\r\n \r\n if (interceptWarn) {\r\n console.warn = function(...args: any[]) {\r\n const message = args.map(arg => \r\n typeof arg === 'object' ? JSON.stringify(arg) : String(arg)\r\n ).join(' ');\r\n \r\n FileLogger.write(filePath, message, 'WARN').catch(err => {\r\n originalError('Error escribiendo warn log:', err);\r\n });\r\n \r\n if (keepOriginal) {\r\n originalWarn.apply(console, args);\r\n }\r\n };\r\n }\r\n \r\n // Retornar función para restaurar console original\r\n return {\r\n restore: () => {\r\n if (interceptLog) console.log = originalLog;\r\n if (interceptError) console.error = originalError;\r\n if (interceptWarn) console.warn = originalWarn;\r\n },\r\n commit: async () => {\r\n if (useBuffer) {\r\n return await FileLogger.commitBuffer(filePath);\r\n }\r\n return false;\r\n },\r\n discard: () => {\r\n if (useBuffer) {\r\n return FileLogger.discardBuffer(filePath);\r\n }\r\n return 0;\r\n },\r\n hasBuffer: () => FileLogger.hasBuffer(filePath),\r\n getBufferSize: () => FileLogger.getBufferSize(filePath)\r\n };\r\n }\r\n \r\n /**\r\n * Lee el contenido completo del archivo de log\r\n * @param filePath - Ruta del archivo de log\r\n * @param options - Opciones de lectura\r\n * @returns Contenido del archivo\r\n */\r\n static async read(filePath: string, options: ReadOptions = {}): Promise<string | string[]> {\r\n const { \r\n lines = null, // Número de líneas a leer (null = todas)\r\n fromEnd = false, // Si debe leer desde el final\r\n asArray = false // Si debe retornar como array de líneas\r\n } = options;\r\n \r\n try {\r\n if (!fs.existsSync(filePath)) {\r\n return asArray ? [] : '';\r\n }\r\n \r\n let content = await fs.promises.readFile(filePath, 'utf8');\r\n \r\n if (asArray) {\r\n let linesArray = content.split('\\n').filter(line => line.trim() !== '');\r\n \r\n if (lines !== null) {\r\n if (fromEnd) {\r\n linesArray = linesArray.slice(-lines);\r\n } else {\r\n linesArray = linesArray.slice(0, lines);\r\n }\r\n }\r\n \r\n return linesArray;\r\n }\r\n \r\n // Si se especifica número de líneas y no es array\r\n if (lines !== null) {\r\n let linesArray = content.split('\\n').filter(line => line.trim() !== '');\r\n if (fromEnd) {\r\n linesArray = linesArray.slice(-lines);\r\n } else {\r\n linesArray = linesArray.slice(0, lines);\r\n }\r\n content = linesArray.join('\\n');\r\n }\r\n \r\n return content;\r\n } catch (error) {\r\n console.error('Error leyendo log:', error);\r\n throw error;\r\n }\r\n }\r\n \r\n /**\r\n * Observa un archivo de log en tiempo real\r\n * @param filePath - Ruta del archivo de log\r\n * @param callback - Función callback para nuevas líneas\r\n * @param options - Opciones del watcher\r\n * @returns Objeto con métodos para controlar el watcher\r\n */\r\n static watch(filePath: string, callback: (line: string, filePath: string) => void, options: WatchOptions = {}): WatchController {\r\n const {\r\n persistent = true,\r\n interval = 100,\r\n fromEnd = true // Empezar desde el final del archivo\r\n } = options;\r\n \r\n let lastSize = 0;\r\n let lastPosition = 0;\r\n \r\n // Inicializar posición si el archivo ya existe\r\n if (fs.existsSync(filePath)) {\r\n const stats = fs.statSync(filePath);\r\n lastSize = stats.size;\r\n lastPosition = fromEnd ? stats.size : 0;\r\n }\r\n \r\n // Fixed: Correct listener function signature for fs.watchFile\r\n const listener = async (curr: fs.Stats, prev: fs.Stats) => {\r\n try {\r\n if (curr.size > lastSize) {\r\n // El archivo ha crecido\r\n const stream = fs.createReadStream(filePath, {\r\n start: lastPosition,\r\n end: curr.size - 1,\r\n encoding: 'utf8'\r\n });\r\n \r\n let buffer = '';\r\n \r\n stream.on('data', (chunk) => {\r\n buffer += chunk;\r\n const lines = buffer.split('\\n');\r\n \r\n // Procesar líneas completas\r\n for (let i = 0; i < lines.length - 1; i++) {\r\n if (lines[i].trim()) {\r\n callback(lines[i].trim(), filePath);\r\n }\r\n }\r\n \r\n // Guardar la línea incompleta\r\n buffer = lines[lines.length - 1];\r\n });\r\n \r\n stream.on('end', () => {\r\n // Procesar la última línea si existe\r\n if (buffer.trim()) {\r\n callback(buffer.trim(), filePath);\r\n }\r\n });\r\n \r\n lastSize = curr.size;\r\n lastPosition = curr.size;\r\n }\r\n } catch (error) {\r\n console.error('Error en watcher:', error);\r\n }\r\n };\r\n \r\n // Watcher para cambios en el archivo\r\n fs.watchFile(filePath, { persistent, interval }, listener);\r\n \r\n // Guardar referencia del watcher\r\n const watcherId = `${filePath}_${Date.now()}`;\r\n // Store listener reference for cleanup\r\n FileLogger.watchers.set(watcherId, listener as any);\r\n \r\n // Retornar objeto de control\r\n return {\r\n id: watcherId,\r\n stop: () => {\r\n const storedListener = FileLogger.watchers.get(watcherId);\r\n if (storedListener) {\r\n fs.unwatchFile(filePath, storedListener);\r\n FileLogger.watchers.delete(watcherId);\r\n }\r\n },\r\n isWatching: () => FileLogger.watchers.has(watcherId)\r\n };\r\n }\r\n \r\n /**\r\n * Detiene todos los watchers activos\r\n */\r\n static stopAllWatchers(): void {\r\n for (const [watcherId] of FileLogger.watchers) {\r\n const filePath = watcherId.split('_')[0];\r\n fs.unwatchFile(filePath);\r\n }\r\n FileLogger.watchers.clear();\r\n }\r\n \r\n /**\r\n * Métodos de conveniencia para diferentes niveles de log\r\n */\r\n static async info(filePath: string, message: string): Promise<boolean> {\r\n return this.write(filePath, message, 'INFO');\r\n }\r\n \r\n static async error(filePath: string, message: string): Promise<boolean> {\r\n return this.write(filePath, message, 'ERROR');\r\n }\r\n \r\n static async warn(filePath: string, message: string): Promise<boolean> {\r\n return this.write(filePath, message, 'WARN');\r\n }\r\n \r\n static async debug(filePath: string, message: string): Promise<boolean> {\r\n return this.write(filePath, message, 'DEBUG');\r\n }\r\n \r\n /**\r\n * Limpia logs antiguos\r\n * @param filePath - Ruta del archivo de log\r\n * @param maxLines - Máximo número de líneas a mantener\r\n */\r\n static async cleanup(filePath: string, maxLines: number = 1000): Promise<number> {\r\n try {\r\n const lines = await this.read(filePath, { asArray: true }) as string[];\r\n \r\n if (lines.length > maxLines) {\r\n const keepLines = lines.slice(-maxLines);\r\n const content = keepLines.join('\\n') + '\\n';\r\n await fs.promises.writeFile(filePath, content, 'utf8');\r\n return lines.length - maxLines; // Líneas eliminadas\r\n }\r\n \r\n return 0;\r\n } catch (error) {\r\n console.error('Error limpiando logs:', error);\r\n throw error;\r\n }\r\n }\r\n \r\n /**\r\n * Elimina un archivo de log\r\n * @param filePath - Ruta del archivo de log a eliminar\r\n */\r\n static async deleteLogFile(filePath: string): Promise<boolean> {\r\n try {\r\n if (fs.existsSync(filePath)) {\r\n await fs.promises.unlink(filePath);\r\n return true;\r\n }\r\n return false;\r\n } catch (error) {\r\n console.error('Error eliminando archivo de log:', error);\r\n throw error;\r\n }\r\n }\r\n \r\n /**\r\n * Elimina múltiples archivos de log\r\n * @param filePaths - Array de rutas de archivos de log a eliminar\r\n */\r\n static async deleteLogFiles(filePaths: string[]): Promise<DeleteResult[]> {\r\n const results: DeleteResult[] = [];\r\n \r\n for (const filePath of filePaths) {\r\n try {\r\n const deleted = await this.deleteLogFile(filePath);\r\n results.push({ filePath, deleted, error: null });\r\n } catch (error) {\r\n results.push({ filePath, deleted: false, error: (error as Error).message });\r\n }\r\n }\r\n \r\n return results;\r\n }\r\n}\r\n\r\nexport default FileLogger;","import { ComputedFieldConfig, DataObject, DataType } from \"../@types/DataObject\";\r\nimport { Column, DatabaseType, TableSchema } from \"../@types/Processor\";\r\nimport DbConfig from \"./DbConfig\";\r\n\r\nexport class ComputedFieldProcessor {\r\n\r\n static async getComputedFields(name: string): Promise<any[]> {\r\n let computedFields = [];\r\n if(DbConfig.ifExist()){\r\n await DbConfig.connect()\r\n try {\r\n // Verificar si la tabla computes existe antes de intentar acceder a ella\r\n const tableExistsQuery = `SELECT name FROM sqlite_master WHERE type='table' AND name='dbcube_computes_config'`;\r\n const tableExistsResult = await DbConfig.query(tableExistsQuery);\r\n \r\n if (tableExistsResult.status === 'success' && tableExistsResult.data && tableExistsResult.data.length > 0) {\r\n const queryComputes = await DbConfig.query(`SELECT * FROM dbcube_computes_config WHERE database_ref='${name}'`);\r\n computedFields = queryComputes.data;\r\n } else {\r\n // La tabla no existe, inicializar como array vacío\r\n computedFields = [];\r\n }\r\n } catch (error) {\r\n console.error('Error fetching computed fields:', error);\r\n computedFields = [];\r\n }\r\n await DbConfig.disconnect()\r\n }\r\n \r\n return computedFields;\r\n }\r\n\r\n /**\r\n * Processes computed field instruction and returns the computed value\r\n * @param instruction - The @compute instruction\r\n * @param rowData - The row data containing column values\r\n * @returns The computed value or null if there's an error\r\n */\r\n static processInstruction(instruction: string, rowData: Record<string, any>): any {\r\n try {\r\n // Remove @compute wrapper and extract the function content\r\n const functionMatch = instruction.match(/@compute\\s*\\(\\s*\\(\\s*\\)\\s*=>\\s*{([\\s\\S]*?)}\\s*\\)/);\r\n \r\n if (!functionMatch) {\r\n throw new Error('Invalid @compute instruction format');\r\n }\r