@dbcube/core
Version:
1,614 lines (1,606 loc) • 79.4 kB
JavaScript
// 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;
}