UNPKG

@dbcube/core

Version:
1,614 lines (1,606 loc) 79.4 kB
// src/lib/Engine.ts import path3 from "path"; // src/lib/Arquitecture.ts import * as os from "os"; var Arquitecture = class { systemInfo; constructor() { this.systemInfo = this.detectSystemInfo(); } /** * Detecta información completa del sistema */ detectSystemInfo() { return { platform: os.platform(), arch: os.arch(), release: os.release(), type: os.type(), endianness: os.endianness(), cpus: os.cpus().length }; } /** * Obtiene la plataforma normalizada */ getPlatform() { const platform2 = this.systemInfo.platform; switch (platform2) { case "win32": return "windows"; case "darwin": return "macos"; case "linux": return "linux"; case "freebsd": return "freebsd"; case "openbsd": return "openbsd"; case "sunos": return "solaris"; default: return platform2; } } /** * Obtiene la arquitectura normalizada */ getArchitecture() { const arch2 = this.systemInfo.arch; switch (arch2) { case "x64": return "x86_64"; case "x32": case "ia32": return "i686"; case "arm64": return "aarch64"; case "arm": return "armv7"; case "ppc64": return "powerpc64"; case "ppc": return "powerpc"; case "s390x": return "s390x"; case "mips": return "mips"; case "mipsel": return "mipsel"; default: return arch2; } } /** * Genera el nombre del binario basado en la arquitectura */ getBinaryName(baseName) { const platform2 = this.getPlatform(); const arch2 = this.getArchitecture(); const extension = platform2 === "windows" ? ".exe" : ""; return `${baseName}-${platform2}-${arch2}${extension}`; } /** * Obtiene información completa del sistema */ getSystemInfo() { return { ...this.systemInfo }; } /** * Obtiene el triple de destino (target triple) para Rust */ getRustTargetTriple() { const platform2 = this.getPlatform(); const arch2 = this.getArchitecture(); const targetMap = { "linux-x86_64": "x86_64-unknown-linux-gnu", "linux-i686": "i686-unknown-linux-gnu", "linux-aarch64": "aarch64-unknown-linux-gnu", "linux-armv7": "armv7-unknown-linux-gnueabihf", "macos-x86_64": "x86_64-apple-darwin", "macos-aarch64": "aarch64-apple-darwin", "windows-x86_64": "x86_64-pc-windows-msvc", "windows-i686": "i686-pc-windows-msvc", "windows-aarch64": "aarch64-pc-windows-msvc", "freebsd-x86_64": "x86_64-unknown-freebsd" }; const key = `${platform2}-${arch2}`; return targetMap[key] || `${arch2}-unknown-${platform2}`; } /** * Muestra información detallada del sistema */ printSystemInfo() { console.log("\u{1F5A5}\uFE0F System Information:"); console.log("\u251C\u2500 Platform:", this.getPlatform()); console.log("\u251C\u2500 Arquitecture:", this.getArchitecture()); console.log("\u251C\u2500 OS Type:", this.systemInfo.type); console.log("\u251C\u2500 OS Release:", this.systemInfo.release); console.log("\u251C\u2500 Endianness:", this.systemInfo.endianness); console.log("\u251C\u2500 CPUs:", this.systemInfo.cpus); console.log("\u2514\u2500 Rust Target:", this.getRustTargetTriple()); } }; // src/lib/Donwloader.ts import * as fs from "fs"; import * as path from "path"; import * as os2 from "os"; import followRedirects from "follow-redirects"; import * as unzipper from "unzipper"; import ora from "ora"; import chalk from "chalk"; var { https } = followRedirects; var Downloader = class { static mainSpinner = null; static currentSpinner = null; static get(prefix) { const arch2 = new Arquitecture(); const platform2 = arch2.getPlatform(); const architecture = arch2.getArchitecture(); const platformMap = { windows: "windows", linux: "linux", darwin: "macos" }; const archMap = { x86_64: "x64", aarch64: "arm64" }; const plat = platformMap[platform2]; const archSuffix = archMap[architecture]; if (plat && archSuffix) { const baseName = `${prefix}-engine-${plat}-${archSuffix}`; const binaryName = platform2 === "windows" ? `${baseName}.exe` : baseName; const url = `https://github.com/Dbcube/binaries/releases/download/${prefix}-engine/${prefix}-engine-latest-${plat}-${archSuffix}.zip`; return { name: binaryName, url, query_engine: binaryName, schema_engine: `${prefix}-engine-${plat}-${archSuffix}${platform2 === "windows" ? ".exe" : ""}` }; } return { name: "", url: "", query_engine: "", schema_engine: "" }; } static async download(targetDir) { const binaries = ["schema", "query", "sqlite"]; const binDir = targetDir || this.getDefaultBinDir(); fs.mkdirSync(binDir, { recursive: true }); this.mainSpinner = ora({ text: chalk.blue("Descargando binarios necesarios..."), spinner: "dots12" }).start(); const binariesToDownload = []; let existingCount = 0; for (const prefix of binaries) { const binaryInfo = this.get(prefix); if (!binaryInfo.name || !binaryInfo.url) { throw new Error(`Plataforma o arquitectura no soportada para ${prefix}`); } const finalBinaryPath = path.join(binDir, binaryInfo.name); if (fs.existsSync(finalBinaryPath)) { existingCount++; continue; } binariesToDownload.push({ prefix, binaryInfo, tempZipPath: path.join(os2.tmpdir(), `dbcube-${prefix}-${Date.now()}.zip`), finalBinaryPath }); } if (binariesToDownload.length === 0) { this.mainSpinner.succeed(chalk.green("Todos los binarios ya est\xE1n descargados")); return; } this.updateMainProgress("paralelo", existingCount, binaries.length, "downloading"); try { await Promise.all(binariesToDownload.map(async (binary, index) => { const maxRetries = 3; let attempt = 0; while (attempt <= maxRetries) { try { await this.downloadFileWithProgress(binary.binaryInfo.url, binary.tempZipPath, binary.prefix); await this.extractBinary(binary.tempZipPath, binary.finalBinaryPath, binary.prefix); const completed = existingCount + index + 1; this.updateMainProgress(binary.prefix, completed, binaries.length, "completed"); break; } catch (error) { const errorMessage = error instanceof Error ? error.message : "Error desconocido"; if (attempt < maxRetries && (errorMessage.includes("ECONNRESET") || errorMessage.includes("timeout") || errorMessage.includes("ETIMEDOUT") || errorMessage.includes("ENOTFOUND"))) { attempt++; this.updateMainProgress(binary.prefix, existingCount, binaries.length, "retrying", attempt); await new Promise((resolve5) => setTimeout(resolve5, 1e3 + Math.random() * 1e3)); } else { throw new Error(`Error descargando ${binary.prefix}: ${errorMessage}`); } } } })); this.mainSpinner.succeed(chalk.green("Binarios descargados correctamente")); } catch (error) { const errorMessage = error instanceof Error ? error.message : "Error desconocido"; this.mainSpinner.fail(chalk.red(`Error en descarga paralela: ${errorMessage}`)); throw error; } } static updateMainProgress(binary, current, total, status, attempt) { const progressBar = this.createProgressBar(current, total); const statusEmojis = { downloading: "\u{1F4E5}", extracting: "\u{1F4E6}", completed: "\u2705", exists: "\u2705", retrying: "\u{1F504}" }; const statusMessages = { downloading: chalk.blue("descargando"), extracting: chalk.yellow("extrayendo"), completed: chalk.green("completado"), exists: chalk.gray("existe"), retrying: chalk.yellow(`reintentando (${attempt}/${3})`) }; const emoji = statusEmojis[status] || "\u{1F4E5}"; const message = statusMessages[status] || status; this.mainSpinner.text = `${progressBar} ${emoji} ${chalk.bold(binary)} - ${message}`; } static createProgressBar(current, total, width = 20) { const filled = Math.round(current / total * width); const empty = width - filled; const filledBar = chalk.green("\u2588".repeat(filled)); const emptyBar = chalk.gray("\u2591".repeat(empty)); const percentage = chalk.bold(`${current}/${total}`); return `[${filledBar}${emptyBar}] ${percentage}`; } static downloadFileWithProgress(url, outputPath, prefix) { return new Promise((resolve5, reject) => { const request = https.get(url, { timeout: 0 }, (response) => { if (response.statusCode === 302 || response.statusCode === 301) { const redirectUrl = response.headers.location; if (redirectUrl) { return this.downloadFileWithProgress(redirectUrl, outputPath, prefix).then(resolve5).catch(reject); } } if (response.statusCode !== 200) { reject(new Error(`HTTP ${response.statusCode} para ${prefix}`)); return; } const file = fs.createWriteStream(outputPath); const totalBytes = parseInt(response.headers["content-length"] || "0", 10); let downloadedBytes = 0; response.on("data", (chunk) => { downloadedBytes += chunk.length; file.write(chunk); if (totalBytes > 0) { const progress = { downloaded: downloadedBytes, total: totalBytes, percentage: downloadedBytes / totalBytes * 100 }; this.updateDownloadProgress(prefix, progress); } }); response.on("end", () => { file.end(); resolve5(); }); response.on("error", (err) => { file.close(); this.cleanupFile(outputPath); reject(err); }); file.on("error", (err) => { file.close(); this.cleanupFile(outputPath); reject(err); }); }); request.on("error", reject); request.on("timeout", () => { request.destroy(); reject(new Error(`Timeout descargando ${prefix}`)); }); }); } static updateDownloadProgress(binary, progress) { const percentage = progress.percentage.toFixed(1); const downloaded = (progress.downloaded / 1024 / 1024).toFixed(1); const total = (progress.total / 1024 / 1024).toFixed(1); const barWidth = 15; const filled = Math.round(progress.percentage / 100 * barWidth); const empty = barWidth - filled; const progressBar = chalk.green("\u2588".repeat(filled)) + chalk.gray("\u2591".repeat(empty)); const progressText = `[${progressBar}] ${percentage}% (${downloaded}/${total}MB)`; this.mainSpinner.text = `\u{1F4E5} ${chalk.bold(binary)} - ${progressText}`; } static extractBinary(zipPath, outputPath, prefix) { return new Promise((resolve5, reject) => { let extracted = false; fs.createReadStream(zipPath).pipe(unzipper.Parse()).on("entry", (entry) => { if (entry.type === "File" && !extracted) { extracted = true; const writeStream = fs.createWriteStream(outputPath); entry.pipe(writeStream); writeStream.on("finish", () => { if (process.platform !== "win32") { fs.chmodSync(outputPath, 493); } this.cleanupFile(zipPath); resolve5(); }); writeStream.on("error", (err) => { this.cleanupFile(zipPath); reject(err); }); } else { entry.autodrain(); } }).on("error", (err) => { this.cleanupFile(zipPath); reject(err); }).on("close", () => { if (!extracted) { this.cleanupFile(zipPath); reject(new Error(`No se encontr\xF3 archivo v\xE1lido en el ZIP para ${prefix}`)); } }); }); } static cleanupFile(filePath) { try { if (fs.existsSync(filePath)) { fs.unlinkSync(filePath); } } catch { } } static getDefaultBinDir() { const possibleDirs = [ path.resolve(process.cwd(), ".dbcube", "bin"), path.resolve(process.cwd(), "node_modules", ".dbcube", "bin"), path.resolve(__dirname, "..", "bin") ]; for (const dir of possibleDirs) { try { if (!fs.existsSync(dir)) { fs.mkdirSync(dir, { recursive: true }); } const testFile = path.join(dir, ".test"); fs.writeFileSync(testFile, "test"); fs.unlinkSync(testFile); return dir; } catch { continue; } } const tempDir = path.join(os2.tmpdir(), ".dbcube", "bin"); fs.mkdirSync(tempDir, { recursive: true }); return tempDir; } }; // src/lib/Binary.ts import * as fs2 from "fs"; import * as path2 from "path"; import * as os3 from "os"; var Binary = class { static isDownloading = false; static downloadPromise = null; static async ensureBinariesExist() { if (this.isDownloading && this.downloadPromise) { await this.downloadPromise; return; } const binDir = this.getBinDir(); const binaries = ["schema", "query", "sqlite"]; const allExist = binaries.every((prefix) => { const binaryInfo = Downloader.get(prefix); if (!binaryInfo.name) return false; const binaryPath = path2.join(binDir, binaryInfo.name); return fs2.existsSync(binaryPath); }); if (allExist) { return; } if (!this.isDownloading) { this.isDownloading = true; this.downloadPromise = this.downloadBinaries(); try { await this.downloadPromise; } finally { this.isDownloading = false; this.downloadPromise = null; } } } static async downloadBinaries() { try { const binDir = this.getBinDir(); await Downloader.download(binDir); } catch (error) { console.warn("\u26A0\uFE0F DBCube: Error descargando binarios:", error.message); console.log("\u{1F527} Los binarios se intentar\xE1n descargar en la pr\xF3xima ejecuci\xF3n"); } } static getBinDir() { const possibleDirs = [ path2.resolve(process.cwd(), ".dbcube", "bin"), path2.resolve(process.cwd(), "node_modules", ".dbcube", "bin"), path2.resolve(__dirname, "..", "bin") ]; for (const dir of possibleDirs) { try { if (!fs2.existsSync(dir)) { fs2.mkdirSync(dir, { recursive: true }); } return dir; } catch { continue; } } const tempDir = path2.join(os3.tmpdir(), ".dbcube", "bin"); fs2.mkdirSync(tempDir, { recursive: true }); return tempDir; } static async get() { await this.ensureBinariesExist(); const arch2 = new Arquitecture(); const platform2 = arch2.getPlatform(); const architecture = arch2.getArchitecture(); const binDir = this.getBinDir(); const getFullPath = (binaryName) => { return path2.join(binDir, binaryName); }; switch (platform2) { case "windows": if (architecture == "x86_64") { return { query_engine: getFullPath("query-engine-windows-x64.exe"), schema_engine: getFullPath("schema-engine-windows-x64.exe") }; } if (architecture == "aarch64") { return { query_engine: getFullPath("query-engine-windows-arm64.exe"), schema_engine: getFullPath("schema-engine-windows-arm64.exe") }; } break; case "linux": if (architecture == "x86_64") { return { query_engine: getFullPath("query-engine-linux-x64"), schema_engine: getFullPath("schema-engine-linux-x64") }; } if (architecture == "aarch64") { return { query_engine: getFullPath("query-engine-linux-arm64"), schema_engine: getFullPath("schema-engine-linux-arm64") }; } break; case "macos": if (architecture == "x86_64") { return { query_engine: getFullPath("query-engine-macos-x64"), schema_engine: getFullPath("schema-engine-macos-x64") }; } if (architecture == "aarch64") { return { query_engine: getFullPath("query-engine-macos-arm64"), schema_engine: getFullPath("schema-engine-macos-arm64") }; } break; } return { query_engine: "", schema_engine: "" }; } }; // src/lib/Config.ts var Config = class { data = {}; databases = {}; /** * Establece la configuración * @param configData - Datos de configuración */ set(configData) { this.data = configData; if (configData.databases) { this.databases = configData.databases; } } /** * Obtiene un valor de configuración * @param key - Clave de configuración * @returns Valor de configuración */ get(key) { return this.data[key]; } /** * Obtiene la configuración de una base de datos específica * @param dbName - Nombre de la base de datos * @returns Configuración de la base de datos o null */ getDatabase(dbName) { return this.databases[dbName] || null; } /** * Obtiene todas las bases de datos configuradas * @returns Todas las configuraciones de bases de datos */ getAllDatabases() { return this.databases; } }; // src/lib/Engine.ts import { spawn } from "child_process"; import { createRequire } from "module"; var Engine = class { name; config; arguments; binary = null; timeout; constructor(name, timeout = 3e4) { this.name = name; this.config = this.setConfig(name); this.arguments = this.setArguments(); this.timeout = timeout; } async initializeBinary() { if (!this.binary) { this.binary = await Binary.get(); } } setArguments() { let args = []; if (this.config.type == "sqlite") { args = [ "--id", "dbcube-" + this.name, "--database-ref", this.name, "--database", this.config.config.DATABASE + ".db", "--motor", this.config.type ]; } else { args = [ "--id", "dbcube-" + this.name, "--database-ref", this.name, "--database", this.config.config.DATABASE, "--host", this.config.config.HOST, "--port", this.config.config.PORT, "--user", this.config.config.USER, "--password", this.config.config.PASSWORD, "--motor", this.config.type ]; } return args; } setConfig(name) { const configInstance = new Config(); try { const configFilePath = path3.resolve(process.cwd(), "dbcube.config.js"); const requireUrl = typeof __filename !== "undefined" ? __filename : process.cwd(); const require2 = createRequire(requireUrl); delete require2.cache[require2.resolve(configFilePath)]; const configModule = require2(configFilePath); const configFn = configModule.default || configModule; if (typeof configFn === "function") { configFn(configInstance); } else { console.error("\u274C El archivo dbcube.config.js no exporta una funci\xF3n."); } } catch (error) { console.error("\u274C Error loading config file:", error.message); if (error.code === "MODULE_NOT_FOUND") { console.error("\u274C Config file not found, please create a dbcube.config.js file"); } } return configInstance.getDatabase(name); } getConfig() { return this.config; } async run(binary, args) { await this.initializeBinary(); if (!this.binary) { throw new Error("Binary not initialized"); } return new Promise((resolve5, reject) => { const child = spawn(this.binary[binary], [...this.arguments, ...args]); let stdoutBuffer = ""; let stderrBuffer = ""; let isResolved = false; const timeoutId = setTimeout(() => { if (!isResolved) { isResolved = true; child.kill(); reject(new Error("Process timeout")); } }, this.timeout); const resolveOnce = (response) => { if (!isResolved) { isResolved = true; clearTimeout(timeoutId); resolve5(response); } }; child.stdout.on("data", (data) => { stdoutBuffer += data.toString(); const match = stdoutBuffer.match(/PROCESS_RESPONSE:(\{.*\})/); if (match) { try { const response = JSON.parse(match[1]); resolveOnce({ status: response.status, message: response.message, data: response.data }); } catch (error) { resolveOnce({ status: 500, message: "Failed to parse response JSON", data: null }); } } }); child.stderr.on("data", (data) => { stderrBuffer += data.toString(); const match = stderrBuffer.match(/PROCESS_RESPONSE:(\{.*\})/); if (match) { try { const response = JSON.parse(match[1]); resolveOnce({ status: response.status, message: response.message, data: response.data }); } catch (error) { resolveOnce({ status: 500, message: "Failed to parse response JSON", data: null }); } } }); child.on("close", (code) => { clearTimeout(timeoutId); if (!isResolved) { resolveOnce({ status: code === 0 ? 200 : 500, message: code === 0 ? "Process completed" : `Process exited with code ${code}`, data: null }); } }); child.on("error", (error) => { clearTimeout(timeoutId); if (!isResolved) { resolveOnce({ status: 500, message: `Process error: ${error.message}`, data: null }); } }); child.unref(); }); } }; // src/lib/QueryEngine.ts import path4 from "path"; import { createRequire as createRequire2 } from "module"; import * as net from "net"; import { spawn as spawn2 } from "child_process"; var globalTcpServers = /* @__PURE__ */ new Map(); var globalTcpConnections = /* @__PURE__ */ new Map(); var queryCache = /* @__PURE__ */ new Map(); var cacheSize = 0; var MAX_CACHE_SIZE = 500; var QueryEngine = class { name; config; arguments; binary = null; timeout; connectionId; tcpPort; constructor(name, timeout = 3e4) { this.name = name; this.config = this.setConfig(name); this.arguments = this.setArguments(); this.timeout = timeout; this.connectionId = `${name}_${this.config.type}_${this.config.config.DATABASE}_${this.config.config.HOST || "localhost"}`; this.tcpPort = this.generatePort(); } generatePort() { return 9944; } async findAvailablePort(startPort) { for (let port = startPort; port >= 9900; port--) { if (await this.isPortAvailable(port)) { return port; } } throw new Error("No available ports found in range 9900-9944"); } isPortAvailable(port) { return new Promise((resolve5) => { const tester = net.createServer(); tester.once("error", () => resolve5(false)); tester.once("listening", () => { tester.close(); resolve5(true); }); tester.listen(port, "127.0.0.1"); }); } async initializeBinary() { if (!this.binary) { this.binary = await Binary.get(); } } setArguments() { let args = []; if (this.config.type == "sqlite") { args = [ "--id", "dbcube-" + this.name, "--database-ref", this.name, "--database", this.config.config.DATABASE + ".db", "--motor", this.config.type ]; } else { args = [ "--id", "dbcube-" + this.name, "--database-ref", this.name, "--database", this.config.config.DATABASE, "--host", this.config.config.HOST, "--port", this.config.config.PORT, "--user", this.config.config.USER, "--password", this.config.config.PASSWORD, "--motor", this.config.type ]; } return args; } setConfig(name) { const configInstance = new Config(); try { const configFilePath = path4.resolve(process.cwd(), "dbcube.config.js"); const requireUrl = typeof __filename !== "undefined" ? __filename : process.cwd(); const require2 = createRequire2(requireUrl); delete require2.cache[require2.resolve(configFilePath)]; const configModule = require2(configFilePath); const configFn = configModule.default || configModule; if (typeof configFn === "function") { configFn(configInstance); } else { console.error("\u274C El archivo dbcube.config.js no exporta una funci\xF3n."); } } catch (error) { console.error("\u274C Error loading config file:", error.message); if (error.code === "MODULE_NOT_FOUND") { console.error("\u274C Config file not found, please create a dbcube.config.js file"); } } return configInstance.getDatabase(name); } getConfig() { return this.config; } async run(binary, args) { const actionIndex = args.findIndex((arg) => arg === "--action"); const isExecuteAction = actionIndex !== -1 && args[actionIndex + 1] === "execute"; if (isExecuteAction) { return this.executeWithTcpServer(args); } else { return this.createProcess(binary, args); } } async executeWithTcpServer(args) { const serverInfo = globalTcpServers.get(this.connectionId); if (!serverInfo || !serverInfo.process || serverInfo.process.killed) { await this.startTcpServer(); await this.waitForServerReady(); } return this.sendTcpRequestFast(args); } async waitForServerReady() { const maxRetries = 10; const retryDelay = 500; for (let i = 0; i < maxRetries; i++) { if (await this.isServerResponding()) { return; } await this.sleep(retryDelay); } throw new Error("TCP server failed to become ready within timeout"); } async isServerResponding() { return new Promise((resolve5) => { const client = new net.Socket(); const timeout = setTimeout(() => { client.destroy(); resolve5(false); }, 1e3); client.connect(this.tcpPort, "127.0.0.1", () => { clearTimeout(timeout); client.destroy(); resolve5(true); }); client.on("error", () => { clearTimeout(timeout); resolve5(false); }); }); } sleep(ms) { return new Promise((resolve5) => setTimeout(resolve5, ms)); } getCachedDML(dmlJson) { if (queryCache.has(dmlJson)) { return queryCache.get(dmlJson); } const parsed = JSON.parse(dmlJson); if (cacheSize < MAX_CACHE_SIZE) { queryCache.set(dmlJson, parsed); cacheSize++; } return parsed; } async startTcpServer() { await this.initializeBinary(); if (!this.binary) { throw new Error("Binary not initialized"); } this.tcpPort = await this.findAvailablePort(this.tcpPort); return new Promise((resolve5, reject) => { const serverArgs = [...this.arguments, "--action", "server", "--port", this.tcpPort.toString()]; const serverProcess = spawn2(this.binary["query_engine"], serverArgs); let started = false; const timeout = setTimeout(() => { if (!started) { serverProcess.kill(); reject(new Error("TCP server startup timeout")); } }, 15e3); serverProcess.stdout.on("data", (data) => { const output = data.toString(); if (output.includes("TCP Server listening on")) { if (!started) { started = true; clearTimeout(timeout); globalTcpServers.set(this.connectionId, { port: this.tcpPort, process: serverProcess }); resolve5(); } } }); serverProcess.stderr.on("data", (data) => { console.error(`[TCP Server Error] ${data.toString().trim()}`); }); serverProcess.on("close", (code) => { globalTcpServers.delete(this.connectionId); }); serverProcess.on("error", (error) => { if (!started) { clearTimeout(timeout); reject(error); } }); }); } async sendTcpRequest(args) { return new Promise((resolve5, reject) => { const client = new net.Socket(); let responseBuffer = ""; let isResolved = false; const timeout = setTimeout(() => { if (!isResolved) { isResolved = true; client.destroy(); reject(new Error("TCP request timeout")); } }, this.timeout * 2); client.connect(this.tcpPort, "127.0.0.1", () => { const dmlIndex = args.findIndex((arg) => arg === "--dml"); const dmlJson = dmlIndex !== -1 ? args[dmlIndex + 1] : null; if (!dmlJson) { if (!isResolved) { isResolved = true; clearTimeout(timeout); client.destroy(); reject(new Error("No DML found in arguments")); } return; } const command = { action: "execute", dml: dmlJson }; client.write(JSON.stringify(command)); }); client.on("data", (data) => { responseBuffer += data.toString(); const match = responseBuffer.match(/PROCESS_RESPONSE:(\{.*\})/); if (match && !isResolved) { isResolved = true; clearTimeout(timeout); client.destroy(); try { const response = JSON.parse(match[1]); resolve5({ status: response.status, message: response.message, data: response.data }); } catch (error) { reject(new Error("Failed to parse TCP response")); } } }); client.on("error", (error) => { if (!isResolved) { isResolved = true; clearTimeout(timeout); reject(error); } }); client.on("close", () => { if (!isResolved) { clearTimeout(timeout); if (!responseBuffer.includes("PROCESS_RESPONSE:")) { reject(new Error("Connection closed without valid response")); } } }); }); } async sendTcpRequestFast(args) { const existingConnection = globalTcpConnections.get(this.connectionId); if (existingConnection && existingConnection.readyState === "open") { try { return await this.sendOnExistingConnection(existingConnection, args); } catch (error) { globalTcpConnections.delete(this.connectionId); existingConnection.destroy(); } } return await this.createPersistentConnection(args); } async sendOnExistingConnection(connection, args) { return new Promise((resolve5, reject) => { const dmlIndex = args.findIndex((arg) => arg === "--dml"); const dmlJson = dmlIndex !== -1 ? args[dmlIndex + 1] : null; if (!dmlJson) { reject(new Error("No DML found in arguments")); return; } let responseBuffer = ""; let isResolved = false; const timeout = setTimeout(() => { if (!isResolved) { isResolved = true; reject(new Error("TCP request timeout")); } }, 100); const onData = (data) => { responseBuffer += data.toString(); const match = responseBuffer.match(/PROCESS_RESPONSE:(\{.*\})/); if (match && !isResolved) { isResolved = true; clearTimeout(timeout); connection.off("data", onData); connection.off("error", onError); try { const response = JSON.parse(match[1]); resolve5({ status: response.status, message: response.message, data: response.data }); } catch (error) { reject(new Error("Failed to parse TCP response")); } } }; const onError = (error) => { if (!isResolved) { isResolved = true; clearTimeout(timeout); connection.off("data", onData); connection.off("error", onError); reject(error); } }; connection.on("data", onData); connection.on("error", onError); const command = { action: "execute", dml: dmlJson }; connection.write(JSON.stringify(command)); }); } async createPersistentConnection(args) { return new Promise((resolve5, reject) => { const client = new net.Socket(); let responseBuffer = ""; let isResolved = false; client.setNoDelay(true); client.setKeepAlive(true, 6e4); const timeout = setTimeout(() => { if (!isResolved) { isResolved = true; client.destroy(); reject(new Error("TCP connection timeout")); } }, 5e3); client.connect(this.tcpPort, "127.0.0.1", () => { clearTimeout(timeout); const dmlIndex = args.findIndex((arg) => arg === "--dml"); const dmlJson = dmlIndex !== -1 ? args[dmlIndex + 1] : null; if (!dmlJson) { if (!isResolved) { isResolved = true; client.destroy(); reject(new Error("No DML found in arguments")); } return; } globalTcpConnections.set(this.connectionId, client); const command = { action: "execute", dml: dmlJson }; client.write(JSON.stringify(command)); }); client.on("data", (data) => { responseBuffer += data.toString(); const match = responseBuffer.match(/PROCESS_RESPONSE:(\{.*\})/); if (match && !isResolved) { isResolved = true; try { const response = JSON.parse(match[1]); resolve5({ status: response.status, message: response.message, data: response.data }); } catch (error) { reject(new Error("Failed to parse TCP response")); } } }); client.on("error", (error) => { if (!isResolved) { isResolved = true; globalTcpConnections.delete(this.connectionId); reject(error); } }); client.on("close", () => { globalTcpConnections.delete(this.connectionId); if (!isResolved && !responseBuffer.includes("PROCESS_RESPONSE:")) { reject(new Error("Connection closed without valid response")); } }); }); } async createProcess(binary, args) { await this.initializeBinary(); if (!this.binary) { throw new Error("Binary not initialized"); } return new Promise((resolve5, reject) => { const child = spawn2(this.binary[binary], [...this.arguments, ...args]); let stdoutBuffer = ""; let stderrBuffer = ""; let isResolved = false; const timeoutId = setTimeout(() => { if (!isResolved) { isResolved = true; child.kill(); reject(new Error("Process timeout")); } }, this.timeout); const resolveOnce = (response) => { if (!isResolved) { isResolved = true; clearTimeout(timeoutId); resolve5(response); } }; child.stdout.on("data", (data) => { stdoutBuffer += data.toString(); const match = stdoutBuffer.match(/PROCESS_RESPONSE:(\{.*\})/); if (match) { try { const response = JSON.parse(match[1]); resolveOnce({ status: response.status, message: response.message, data: response.data }); } catch (error) { resolveOnce({ status: 500, message: "Failed to parse response JSON", data: null }); } } }); child.stderr.on("data", (data) => { stderrBuffer += data.toString(); const match = stderrBuffer.match(/PROCESS_RESPONSE:(\{.*\})/); if (match) { try { const response = JSON.parse(match[1]); resolveOnce({ status: response.status, message: response.message, data: response.data }); } catch (error) { resolveOnce({ status: 500, message: "Failed to parse response JSON", data: null }); } } }); child.on("close", (code) => { clearTimeout(timeoutId); if (!isResolved) { resolveOnce({ status: code === 0 ? 200 : 500, message: code === 0 ? "Process completed" : `Process exited with code ${code}`, data: null }); } }); child.on("error", (error) => { clearTimeout(timeoutId); if (!isResolved) { resolveOnce({ status: 500, message: `Process error: ${error.message}`, data: null }); } }); child.unref(); }); } async disconnect() { const connection = globalTcpConnections.get(this.connectionId); if (connection && connection.readyState === "open") { connection.write(JSON.stringify({ action: "disconnect" })); connection.destroy(); globalTcpConnections.delete(this.connectionId); } const serverInfo = globalTcpServers.get(this.connectionId); if (serverInfo && serverInfo.process && !serverInfo.process.killed) { serverInfo.process.kill(); globalTcpServers.delete(this.connectionId); return { status: 200, message: "TCP server and connections stopped", data: null }; } return { status: 200, message: "No TCP server to stop", data: null }; } }; // src/lib/SqliteExecutor.ts import { exec } from "child_process"; import * as path5 from "path"; import * as fs3 from "fs"; import { promisify } from "util"; import { createRequire as createRequire3 } from "module"; var execAsync = promisify(exec); var SqliteExecutor = class { binaryPath; dbPath; constructor(dbPath) { this.dbPath = dbPath; this.binaryPath = this.getBinaryPath(); } getBinaryPath() { const possibleDirs = [ path5.resolve(process.cwd(), ".dbcube", "bin"), path5.resolve(process.cwd(), "node_modules", ".dbcube", "bin"), path5.resolve(__dirname, "..", "bin") ]; const platform2 = process.platform; const extension = platform2 === "win32" ? ".exe" : ""; const binaryName = `sqlite-engine-${platform2 === "win32" ? "windows" : platform2 === "darwin" ? "macos" : "linux"}-x64${extension}`; for (const dir of possibleDirs) { const fullPath = path5.join(dir, binaryName); if (fs3.existsSync(fullPath)) { return fullPath; } } const fallbackName = `sqlite-engine${extension}`; for (const dir of possibleDirs) { const fullPath = path5.join(dir, fallbackName); if (fs3.existsSync(fullPath)) { return fullPath; } } return path5.join(possibleDirs[0], binaryName); } async executeBinary(args) { const escapedArgs = args.map((arg) => { if (arg.includes(" ") || arg.includes('"') || arg.includes("(") || arg.includes(")")) { return `"${arg.replace(/"/g, '\\"')}"`; } return arg; }); const command = `"${this.binaryPath}" ${escapedArgs.join(" ")}`; try { const { stdout, stderr } = await execAsync(command, { maxBuffer: 10 * 1024 * 1024, // 10MB buffer timeout: 3e4 // 30s timeout }); if (stderr && stderr.trim()) { console.warn("SQLite Engine Warning:", stderr); } const result = JSON.parse(stdout.trim()); return result; } catch (error) { if (error.stdout) { try { const result = JSON.parse(error.stdout.trim()); return result; } catch (parseError) { } } throw new Error(`SQLite execution failed: ${error.message}`); } } async connect() { try { const result = await this.executeBinary([ "--action", "connect", "--database", this.dbPath ]); return result.status === "success"; } catch (error) { return false; } } async exists() { try { const result = await this.executeBinary([ "--action", "exists", "--database", this.dbPath ]); return result.status === "success" && result.data === true; } catch (error) { return false; } } async query(sql, params) { const args = [ "--action", "query", "--database", this.dbPath, "--query", sql ]; if (params && params.length > 0) { args.push("--params", JSON.stringify(params)); } return this.executeBinary(args); } // Método para múltiples queries (como lo hace better-sqlite3) async queryMultiple(sql) { return this.query(sql); } // Método prepare que simula prepared statements prepare(sql) { return { all: async (...params) => { const result = await this.query(sql, params); if (result.status === "error") { throw new Error(result.message); } return Array.isArray(result.data) ? result.data : []; }, run: async (...params) => { const result = await this.query(sql, params); if (result.status === "error") { throw new Error(result.message); } if (typeof result.data === "object" && result.data.hasOwnProperty("changes") && result.data.hasOwnProperty("lastID")) { return result.data; } return { changes: 0, lastID: 0 }; } }; } // Para compatibilidad con better-sqlite3 API sincrona usando deasync si es necesario prepareSync(sql) { const requireUrl = typeof __filename !== "undefined" ? __filename : process.cwd(); const require2 = createRequire3(requireUrl); const deasync = require2("deasync"); return { all: (...params) => { let result; let done = false; let error; this.query(sql, params).then((res) => { if (res.status === "error") { error = new Error(res.message); } else { result = Array.isArray(res.data) ? res.data : []; } done = true; }).catch((err) => { error = err; done = true; }); deasync.loopWhile(() => !done); if (error) throw error; return result; }, run: (...params) => { let result; let done = false; let error; this.query(sql, params).then((res) => { if (res.status === "error") { error = new Error(res.message); } else if (typeof res.data === "object" && res.data.hasOwnProperty("changes") && res.data.hasOwnProperty("lastID")) { result = res.data; } else { result = { changes: 0, lastID: 0 }; } done = true; }).catch((err) => { error = err; done = true; }); deasync.loopWhile(() => !done); if (error) throw error; return result; } }; } }; // src/lib/DbConfig.ts import * as path6 from "path"; import fs4 from "fs"; var rootPath = path6.resolve(process.cwd(), ".dbcube"); var SQLite = class { executor = null; database; constructor(config) { this.database = config.DATABASE; } async ifExist() { if (this.database) { const dbPath = this.database || ":memory:"; const configPath = path6.join(rootPath, dbPath + ".db"); if (!fs4.existsSync(rootPath)) { fs4.mkdirSync(rootPath, { recursive: true }); } if (fs4.existsSync(configPath)) { return true; } if (!this.executor) { this.executor = new SqliteExecutor(configPath); } return await this.executor.exists(); } return false; } async connect() { return new Promise(async (resolve5, reject) => { try { if (!this.executor) { const dbPath = this.database || ":memory:"; const configPath = path6.join(rootPath, dbPath + ".db"); if (!fs4.existsSync(rootPath)) { fs4.mkdirSync(rootPath, { recursive: true }); } this.executor = new SqliteExecutor(configPath); const connected = await this.executor.connect(); if (!connected) { throw new Error("Failed to connect to SQLite database"); } } resolve5(this.executor); } catch (error) { reject(error); } }); } async disconnect() { return new Promise((resolve5) => { if (this.executor) { this.executor = null; } resolve5(); }); } async query(sqlQuery) { return new Promise(async (resolve5) => { try { if (typeof sqlQuery !== "string") { throw new Error("The SQL query must be a string."); } if (!this.executor) { await this.connect(); } if (!this.executor) { throw new Error("Database connection is not available."); } const result = await this.executor.queryMultiple(sqlQuery); if (result.status === "error") { resolve5({ status: "error", message: result.message, data: null }); } else { resolve5({ status: "success", message: "Query executed successfully", data: result.data }); } } catch (error) { resolve5({ status: "error", message: error.message || "An error occurred while executing the query.", data: null }); } }); } async queryWithParameters(sqlQuery, params = []) { return new Promise(async (resolve5) => { try { if (typeof sqlQuery !== "string") { throw new Error("The SQL query must be a string."); } if (!Array.isArray(params)) { throw new Error("Parameters must be an array."); } if (!this.executor) { await this.connect(); } if (!this.executor) { throw new Error("Database connection is not available."); } const result = await this.executor.query(sqlQuery, params); if (result.status === "error") { resolve5({ status: "error", message: result.message, data: null }); } else { resolve5({ status: "success", message: "Query executed successfully", data: result.data }); } } catch (error) { console.log(error); resolve5({ status: "error", message: error.message || "An error occurred while executing the query.", data: null }); } }); } convertToParameterizedQuery(sql) { const normalizedSql = sql.replace(/\s+/g, " ").trim(); const baseQueryMatch = normalizedSql.match(/^(.+?)\s+VALUES\s*\(/i); if (!baseQueryMatch) { throw new Error("No se pudo encontrar la estructura VALUES en la consulta"); } const baseQuery = baseQueryMatch[1]; const valuesStartIndex = normalizedSql.toUpperCase().indexOf("VALUES"); const valuesSection = normalizedSql.substring(valuesStartIndex); const valuesMatch = valuesSection.match(/VALUES\s*\((.+)\)\s*;?\s*$/i); if (!valuesMatch) { throw new Error("No se pudieron extraer los valores de la consulta"); } const valuesString = valuesMatch[1]; function parseValues(str) { const values = []; let currentValue = ""; let inQuotes = false; let quoteChar = ""; let inCompute = false; let computeDepth = 0; for (let i = 0; i < str.length; i++) { const char = str[i]; const prevChar = str[i - 1]; if (!inQuotes && str.substring(i, i + 8) === "@compute") { inCompute = true; } if (inCompute && char === "(") { computeDepth++; } else if (inCompute && char === ")") { computeDepth--; if (computeDepth === 0) { inCompute = false; } } if (!inCompute && (char === '"' || char === "'") && prevChar !== "\\") { if (!inQuotes) { inQuotes = true; quoteChar = char; } else if (char === quoteChar) { if (str[i + 1] === quoteChar) { currentValue += char + char; i++; continue; } else { inQuotes = false; quoteChar = ""; } } } if (!inQuotes && !inCompute && char === ",") { values.push(cleanValue(currentValue.trim())); currentValue = ""; continue; }