@dbcube/core 5.2.2 → 5.2.4

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
package/dist/index.cjs CHANGED
@@ -186,8 +186,7 @@ var Downloader = class {
186
186
  static currentSpinner = null;
187
187
  static VERSION_URLS = {
188
188
  query: "https://raw.githubusercontent.com/Dbcube/binaries/main/query-engines.json",
189
- schema: "https://raw.githubusercontent.com/Dbcube/binaries/main/schema-engines.json",
190
- sqlite: "https://raw.githubusercontent.com/Dbcube/binaries/main/sqlite-engines.json"
189
+ schema: "https://raw.githubusercontent.com/Dbcube/binaries/main/schema-engines.json"
191
190
  };
192
191
  /**
193
192
  * Fetch latest version from GitHub
@@ -357,7 +356,7 @@ var Downloader = class {
357
356
  spinner: "dots12"
358
357
  }).start();
359
358
  const binariesToProcess = [];
360
- for (const prefix of ["query", "schema", "sqlite"]) {
359
+ for (const prefix of ["query", "schema"]) {
361
360
  try {
362
361
  const localVersion = this.getLocalVersion(binDir, prefix);
363
362
  const remoteVersion = await this.fetchLatestVersion(prefix);
@@ -522,34 +521,46 @@ var Downloader = class {
522
521
  }
523
522
  static extractBinary(zipPath, outputPath, prefix) {
524
523
  return new Promise((resolve5, reject) => {
525
- let extracted = false;
524
+ const outDir = path.dirname(outputPath);
525
+ let extracted = 0;
526
+ const pending = [];
526
527
  fs.createReadStream(zipPath).pipe(unzipper.Parse()).on("entry", (entry) => {
527
- if (entry.type === "File" && !extracted) {
528
- extracted = true;
529
- const writeStream = fs.createWriteStream(outputPath);
528
+ if (entry.type !== "File") {
529
+ entry.autodrain();
530
+ return;
531
+ }
532
+ const isMain = extracted === 0;
533
+ extracted++;
534
+ const target = isMain ? outputPath : path.join(outDir, path.basename(entry.path));
535
+ pending.push(new Promise((res, rej) => {
536
+ const writeStream = fs.createWriteStream(target);
530
537
  entry.pipe(writeStream);
531
538
  writeStream.on("finish", () => {
532
539
  if (process.platform !== "win32") {
533
- fs.chmodSync(outputPath, 493);
540
+ try {
541
+ fs.chmodSync(target, 493);
542
+ } catch {
543
+ }
534
544
  }
535
- this.cleanupFile(zipPath);
536
- resolve5();
545
+ res();
537
546
  });
538
- writeStream.on("error", (err) => {
539
- this.cleanupFile(zipPath);
540
- reject(err);
541
- });
542
- } else {
543
- entry.autodrain();
544
- }
547
+ writeStream.on("error", rej);
548
+ }));
545
549
  }).on("error", (err) => {
546
550
  this.cleanupFile(zipPath);
547
551
  reject(err);
548
552
  }).on("close", () => {
549
- if (!extracted) {
553
+ Promise.all(pending).then(() => {
550
554
  this.cleanupFile(zipPath);
551
- reject(new Error(`No se encontr\xF3 archivo v\xE1lido en el ZIP para ${prefix}`));
552
- }
555
+ if (extracted === 0) {
556
+ reject(new Error(`No se encontr\xF3 archivo v\xE1lido en el ZIP para ${prefix}`));
557
+ } else {
558
+ resolve5();
559
+ }
560
+ }).catch((err) => {
561
+ this.cleanupFile(zipPath);
562
+ reject(err);
563
+ });
553
564
  });
554
565
  });
555
566
  }
@@ -943,7 +954,7 @@ var DaemonClient = class _DaemonClient {
943
954
  });
944
955
  }
945
956
  async execute(dml, txId) {
946
- const payload = { action: "execute", dml: JSON.stringify(dml) };
957
+ const payload = { action: "execute", dml };
947
958
  if (txId) payload.tx_id = txId;
948
959
  return this.send(payload);
949
960
  }
@@ -1010,8 +1021,8 @@ var Engine = class {
1010
1021
  const binaryPath = this.binary["query_engine"];
1011
1022
  if (!binaryPath) return null;
1012
1023
  const client = DaemonClient.get(this.name, binaryPath, this.arguments);
1013
- const ok = await client.ensure();
1014
- if (!ok) {
1024
+ const ok2 = await client.ensure();
1025
+ if (!ok2) {
1015
1026
  this.daemonFailed = true;
1016
1027
  return null;
1017
1028
  }
@@ -1224,13 +1235,264 @@ var Engine = class {
1224
1235
 
1225
1236
  // src/lib/QueryEngine.ts
1226
1237
  var import_path5 = __toESM(require("path"));
1238
+
1239
+ // src/lib/EmbeddedEngine.ts
1240
+ var fs4 = __toESM(require("fs"));
1241
+ var path5 = __toESM(require("path"));
1242
+ if (!process.env.UV_THREADPOOL_SIZE) {
1243
+ process.env.UV_THREADPOOL_SIZE = "16";
1244
+ }
1245
+ var backend = null;
1246
+ var loadFailed = false;
1247
+ var handles = /* @__PURE__ */ new Map();
1248
+ var connecting = /* @__PURE__ */ new Map();
1249
+ function platTag() {
1250
+ const arch2 = process.arch === "arm64" ? "arm64" : "x64";
1251
+ if (process.platform === "win32") return { plat: "windows", ext: "dll", arch: arch2 };
1252
+ if (process.platform === "darwin") return { plat: "macos", ext: "dylib", arch: arch2 };
1253
+ return { plat: "linux", ext: "so", arch: arch2 };
1254
+ }
1255
+ function findFile(name) {
1256
+ const candidates = [
1257
+ path5.resolve(process.cwd(), ".dbcube", "bin", name),
1258
+ path5.resolve(process.cwd(), "node_modules", ".dbcube", "bin", name)
1259
+ ];
1260
+ for (const c of candidates) {
1261
+ if (fs4.existsSync(c)) return c;
1262
+ }
1263
+ return null;
1264
+ }
1265
+ function ok(data) {
1266
+ return { status: 200, message: "OK", data };
1267
+ }
1268
+ function errResp(e) {
1269
+ const msg = e instanceof Error ? e.message : String(e);
1270
+ return { status: 500, message: msg, data: null };
1271
+ }
1272
+ function loadNapiBackend() {
1273
+ const { plat, arch: arch2 } = platTag();
1274
+ const addonPath = findFile(`query-engine-node-${plat}-${arch2}.node`);
1275
+ if (!addonPath) return null;
1276
+ try {
1277
+ const addon = require(addonPath);
1278
+ if (typeof addon.connect !== "function") return null;
1279
+ return {
1280
+ async connect(cfgJson) {
1281
+ return Number(await addon.connect(cfgJson));
1282
+ },
1283
+ async execute(handle, dml, txId) {
1284
+ try {
1285
+ const rows = await addon.execute(handle, dml, txId ?? void 0);
1286
+ return ok(rows);
1287
+ } catch (e) {
1288
+ return errResp(e);
1289
+ }
1290
+ },
1291
+ async executeBatch(handle, ops) {
1292
+ try {
1293
+ const results = await addon.executeBatch(handle, ops);
1294
+ return ok(results);
1295
+ } catch (e) {
1296
+ return errResp(e);
1297
+ }
1298
+ },
1299
+ async raw(handle, query, params, txId) {
1300
+ try {
1301
+ const rows = await addon.raw(handle, query, params, txId ?? void 0);
1302
+ return ok(rows);
1303
+ } catch (e) {
1304
+ return errResp(e);
1305
+ }
1306
+ },
1307
+ async begin(handle) {
1308
+ try {
1309
+ const txId = await addon.begin(handle);
1310
+ return ok({ tx_id: txId });
1311
+ } catch (e) {
1312
+ return errResp(e);
1313
+ }
1314
+ },
1315
+ async commit(_handle, txId) {
1316
+ try {
1317
+ await addon.commit(txId);
1318
+ return ok(null);
1319
+ } catch (e) {
1320
+ return errResp(e);
1321
+ }
1322
+ },
1323
+ async rollback(_handle, txId) {
1324
+ try {
1325
+ await addon.rollback(txId);
1326
+ return ok(null);
1327
+ } catch (e) {
1328
+ return errResp(e);
1329
+ }
1330
+ },
1331
+ async disconnect(handle) {
1332
+ try {
1333
+ addon.disconnect(handle);
1334
+ } catch {
1335
+ }
1336
+ }
1337
+ };
1338
+ } catch {
1339
+ return null;
1340
+ }
1341
+ }
1342
+ function loadKoffiBackend() {
1343
+ const { plat, ext, arch: arch2 } = platTag();
1344
+ const libPath = findFile(`query-engine-embedded-${plat}-${arch2}.${ext}`);
1345
+ if (!libPath) return null;
1346
+ try {
1347
+ const koffi = require("koffi");
1348
+ const lib = koffi.load(libPath);
1349
+ const cConnect = lib.func("dbc_connect", "void *", ["str"]);
1350
+ const cExecute = lib.func("dbc_execute", "void *", ["uint64", "str", "str"]);
1351
+ const cExecuteBatch = lib.func("dbc_execute_batch", "void *", ["uint64", "str"]);
1352
+ const cRaw = lib.func("dbc_raw", "void *", ["uint64", "str", "str", "str"]);
1353
+ const cBegin = lib.func("dbc_begin", "void *", ["uint64"]);
1354
+ const cCommit = lib.func("dbc_commit", "void *", ["uint64", "str"]);
1355
+ const cRollback = lib.func("dbc_rollback", "void *", ["uint64", "str"]);
1356
+ const cDisconnect = lib.func("dbc_disconnect", "void *", ["uint64"]);
1357
+ const cFree = lib.func("dbc_free", "void", ["void *"]);
1358
+ const take = (ptr) => {
1359
+ if (!ptr) return { status: 500, message: "embedded engine returned null", data: null };
1360
+ const s = koffi.decode(ptr, "char", -1);
1361
+ cFree(ptr);
1362
+ try {
1363
+ const r = JSON.parse(s);
1364
+ return { status: r.status ?? 500, message: r.message ?? "", data: r.data ?? null };
1365
+ } catch (e) {
1366
+ return { status: 500, message: `Invalid embedded response: ${e.message}`, data: null };
1367
+ }
1368
+ };
1369
+ const call = (fn, ...args) => new Promise((resolve5, reject) => {
1370
+ fn.async(...args, (err, ptr) => {
1371
+ if (err) return reject(err);
1372
+ resolve5(take(ptr));
1373
+ });
1374
+ });
1375
+ return {
1376
+ async connect(cfgJson) {
1377
+ const res = await call(cConnect, cfgJson);
1378
+ if (res.status !== 200 || !res.data?.handle) {
1379
+ throw new Error(String(res.message || "embedded connect failed"));
1380
+ }
1381
+ return Number(res.data.handle);
1382
+ },
1383
+ execute: (handle, dml, txId) => call(cExecute, handle, JSON.stringify(dml), txId),
1384
+ executeBatch: (handle, ops) => call(cExecuteBatch, handle, JSON.stringify(ops)),
1385
+ raw: (handle, query, params, txId) => call(cRaw, handle, query, JSON.stringify(params ?? []), txId),
1386
+ begin: (handle) => call(cBegin, handle),
1387
+ commit: (handle, txId) => call(cCommit, handle, txId),
1388
+ rollback: (handle, txId) => call(cRollback, handle, txId),
1389
+ async disconnect(handle) {
1390
+ try {
1391
+ await call(cDisconnect, handle);
1392
+ } catch {
1393
+ }
1394
+ }
1395
+ };
1396
+ } catch {
1397
+ return null;
1398
+ }
1399
+ }
1400
+ function loadBackend() {
1401
+ if (backend) return backend;
1402
+ if (loadFailed) return null;
1403
+ if (process.env.DBCUBE_EMBEDDED === "0" || process.env.DBCUBE_EMBEDDED === "false") {
1404
+ loadFailed = true;
1405
+ return null;
1406
+ }
1407
+ backend = loadNapiBackend() ?? loadKoffiBackend();
1408
+ if (!backend) loadFailed = true;
1409
+ return backend;
1410
+ }
1411
+ var EmbeddedEngine = class {
1412
+ /** true si algún backend nativo está disponible en esta instalación. */
1413
+ static available() {
1414
+ return loadBackend() !== null;
1415
+ }
1416
+ static async handleFor(connectionId, config) {
1417
+ const existing = handles.get(connectionId);
1418
+ if (existing) return existing;
1419
+ const inFlight = connecting.get(connectionId);
1420
+ if (inFlight) return inFlight;
1421
+ const promise = (async () => {
1422
+ const be = loadBackend();
1423
+ if (!be) throw new Error("embedded engine not available");
1424
+ const motor = config.type === "postgres" ? "postgresql" : config.type;
1425
+ const cfg = {
1426
+ databaseRef: connectionId,
1427
+ motor,
1428
+ database: config.type === "sqlite" ? `${config.config.DATABASE}.db` : config.config.DATABASE,
1429
+ host: config.config.HOST,
1430
+ port: config.config.PORT != null ? Number(config.config.PORT) : void 0,
1431
+ user: config.config.USER,
1432
+ password: config.config.PASSWORD,
1433
+ maxConnections: config.pool?.maxConnections,
1434
+ minConnections: config.pool?.minConnections,
1435
+ acquireTimeoutMs: config.pool?.acquireTimeoutMs,
1436
+ idleTimeoutMs: config.pool?.idleTimeoutMs
1437
+ };
1438
+ const handle = await be.connect(JSON.stringify(cfg));
1439
+ handles.set(connectionId, handle);
1440
+ return handle;
1441
+ })();
1442
+ connecting.set(connectionId, promise);
1443
+ try {
1444
+ return await promise;
1445
+ } finally {
1446
+ connecting.delete(connectionId);
1447
+ }
1448
+ }
1449
+ static async executeDml(connectionId, config, dml, txId) {
1450
+ const be = loadBackend();
1451
+ const handle = await this.handleFor(connectionId, config);
1452
+ return be.execute(handle, dml, txId ?? null);
1453
+ }
1454
+ static async executeBatch(connectionId, config, ops) {
1455
+ const be = loadBackend();
1456
+ const handle = await this.handleFor(connectionId, config);
1457
+ return be.executeBatch(handle, ops);
1458
+ }
1459
+ static async rawQuery(connectionId, config, query, params, txId) {
1460
+ const be = loadBackend();
1461
+ const handle = await this.handleFor(connectionId, config);
1462
+ return be.raw(handle, query, params ?? [], txId ?? null);
1463
+ }
1464
+ static async begin(connectionId, config) {
1465
+ const be = loadBackend();
1466
+ const handle = await this.handleFor(connectionId, config);
1467
+ return be.begin(handle);
1468
+ }
1469
+ static async commit(connectionId, config, txId) {
1470
+ const be = loadBackend();
1471
+ const handle = await this.handleFor(connectionId, config);
1472
+ return be.commit(handle, txId);
1473
+ }
1474
+ static async rollback(connectionId, config, txId) {
1475
+ const be = loadBackend();
1476
+ const handle = await this.handleFor(connectionId, config);
1477
+ return be.rollback(handle, txId);
1478
+ }
1479
+ static async disconnect(connectionId) {
1480
+ const be = loadBackend();
1481
+ const handle = handles.get(connectionId);
1482
+ if (be && handle) {
1483
+ handles.delete(connectionId);
1484
+ await be.disconnect(handle);
1485
+ }
1486
+ }
1487
+ };
1488
+
1489
+ // src/lib/QueryEngine.ts
1227
1490
  var import_module2 = require("module");
1228
1491
  var net2 = __toESM(require("net"));
1229
1492
  var import_child_process3 = require("child_process");
1230
1493
  var globalTcpServers = /* @__PURE__ */ new Map();
1231
- var globalTcpConnections = /* @__PURE__ */ new Map();
1232
- var connectionQueues = /* @__PURE__ */ new Map();
1233
- var connectionProcessing = /* @__PURE__ */ new Map();
1494
+ var socketPools = /* @__PURE__ */ new Map();
1495
+ var CLIENT_POOL_SIZE = 10;
1234
1496
  var queryCache = /* @__PURE__ */ new Map();
1235
1497
  var MAX_CACHE_SIZE = 500;
1236
1498
  var QueryEngine = class {
@@ -1241,9 +1503,9 @@ var QueryEngine = class {
1241
1503
  timeout;
1242
1504
  connectionId;
1243
1505
  tcpPort;
1244
- constructor(name, timeout = 3e4) {
1506
+ constructor(name, timeout = 3e4, explicitConfig) {
1245
1507
  this.name = name;
1246
- this.config = this.setConfig(name);
1508
+ this.config = explicitConfig ?? this.setConfig(name);
1247
1509
  this.arguments = this.setArguments();
1248
1510
  this.timeout = this.config?.daemon?.requestTimeoutMs ?? timeout;
1249
1511
  this.connectionId = `${name}_query_engine_${this.config.type}_${this.config.config.DATABASE}_${this.config.config.HOST || "localhost"}`;
@@ -1373,38 +1635,79 @@ var QueryEngine = class {
1373
1635
  }
1374
1636
  return command;
1375
1637
  }
1638
+ /** El motor embebido (FFI) es el camino por defecto cuando la librería
1639
+ * nativa está disponible; el daemon TCP queda como fallback y como modo
1640
+ * multi-proceso explícito. */
1641
+ useEmbedded() {
1642
+ return EmbeddedEngine.available();
1643
+ }
1376
1644
  /**
1377
- * Executes a DML plan over the persistent TCP server.
1645
+ * Executes a DML plan. Embedded engine when available (in-process FFI,
1646
+ * no network hop); persistent TCP daemon otherwise.
1378
1647
  * Pass txId to run it inside an active transaction.
1379
1648
  */
1380
1649
  async executeDml(dml, txId) {
1381
- const command = { action: "execute", dml: JSON.stringify(dml) };
1650
+ if (this.useEmbedded()) {
1651
+ return EmbeddedEngine.executeDml(this.connectionId, this.config, dml, txId);
1652
+ }
1653
+ const command = { action: "execute", dml };
1382
1654
  if (txId) command.tx_id = txId;
1383
1655
  return this.executeWithTcpServer(command);
1384
1656
  }
1385
1657
  /**
1386
- * Executes raw SQL (or a MongoDB command document) with bound parameters
1387
- * over the persistent TCP server.
1658
+ * Atomic batch: N write plans inside one transaction with a single
1659
+ * engine round-trip. Falls back to begin/ops/commit over the daemon
1660
+ * when the embedded engine is not available.
1661
+ */
1662
+ async executeBatch(ops) {
1663
+ if (this.useEmbedded()) {
1664
+ return EmbeddedEngine.executeBatch(this.connectionId, this.config, ops);
1665
+ }
1666
+ const txId = await this.beginTransaction();
1667
+ try {
1668
+ const results = [];
1669
+ for (const op of ops) {
1670
+ const res = await this.executeDml(op, txId);
1671
+ if (res.status !== 200) {
1672
+ throw new Error(String(res.message));
1673
+ }
1674
+ results.push(res.data);
1675
+ }
1676
+ await this.commitTransaction(txId);
1677
+ return { status: 200, message: "Batch committed", data: results };
1678
+ } catch (error) {
1679
+ try {
1680
+ await this.rollbackTransaction(txId);
1681
+ } catch {
1682
+ }
1683
+ return { status: 500, message: error?.message ?? String(error), data: null };
1684
+ }
1685
+ }
1686
+ /**
1687
+ * Executes raw SQL (or a MongoDB command document) with bound parameters.
1388
1688
  */
1389
1689
  async rawQuery(query, params = [], txId) {
1690
+ if (this.useEmbedded()) {
1691
+ return EmbeddedEngine.rawQuery(this.connectionId, this.config, query, params, txId);
1692
+ }
1390
1693
  const command = { action: "raw", query, params };
1391
1694
  if (txId) command.tx_id = txId;
1392
1695
  return this.executeWithTcpServer(command);
1393
1696
  }
1394
- /** Starts a server-side transaction and returns its id. */
1697
+ /** Starts a transaction and returns its id. */
1395
1698
  async beginTransaction() {
1396
- const res = await this.executeWithTcpServer({ action: "begin" });
1699
+ const res = this.useEmbedded() ? await EmbeddedEngine.begin(this.connectionId, this.config) : await this.executeWithTcpServer({ action: "begin" });
1397
1700
  if (res.status !== 200 || !res.data?.tx_id) {
1398
1701
  throw new Error(String(res.message || "Failed to begin transaction (update the query-engine binary: npx dbcube update)"));
1399
1702
  }
1400
1703
  return res.data.tx_id;
1401
1704
  }
1402
1705
  async commitTransaction(txId) {
1403
- const res = await this.executeWithTcpServer({ action: "commit", tx_id: txId });
1706
+ const res = this.useEmbedded() ? await EmbeddedEngine.commit(this.connectionId, this.config, txId) : await this.executeWithTcpServer({ action: "commit", tx_id: txId });
1404
1707
  if (res.status !== 200) throw new Error(String(res.message || "Failed to commit transaction"));
1405
1708
  }
1406
1709
  async rollbackTransaction(txId) {
1407
- const res = await this.executeWithTcpServer({ action: "rollback", tx_id: txId });
1710
+ const res = this.useEmbedded() ? await EmbeddedEngine.rollback(this.connectionId, this.config, txId) : await this.executeWithTcpServer({ action: "rollback", tx_id: txId });
1408
1711
  if (res.status !== 200) throw new Error(String(res.message || "Failed to rollback transaction"));
1409
1712
  }
1410
1713
  async executeWithTcpServer(command) {
@@ -1423,7 +1726,63 @@ var QueryEngine = class {
1423
1726
  await this.startTcpServer();
1424
1727
  await this.waitForServerReady();
1425
1728
  }
1426
- return this.sendTcpRequestFast(command);
1729
+ const port = globalTcpServers.get(this.connectionId).port;
1730
+ const socket = await this.acquireSocket(port);
1731
+ try {
1732
+ const result = await this.executeOnConnection(socket, command);
1733
+ this.releaseSocket(socket, false);
1734
+ return result;
1735
+ } catch (error) {
1736
+ this.releaseSocket(socket, true);
1737
+ throw error;
1738
+ }
1739
+ }
1740
+ poolFor() {
1741
+ let pool = socketPools.get(this.connectionId);
1742
+ if (!pool) {
1743
+ pool = { free: [], total: 0, waiters: [] };
1744
+ socketPools.set(this.connectionId, pool);
1745
+ }
1746
+ return pool;
1747
+ }
1748
+ async acquireSocket(port) {
1749
+ const pool = this.poolFor();
1750
+ while (pool.free.length > 0) {
1751
+ const s = pool.free.pop();
1752
+ if (!s.destroyed) return s;
1753
+ pool.total--;
1754
+ }
1755
+ if (pool.total < CLIENT_POOL_SIZE) {
1756
+ pool.total++;
1757
+ try {
1758
+ return await this.createNewConnection(port);
1759
+ } catch (e) {
1760
+ pool.total--;
1761
+ throw e;
1762
+ }
1763
+ }
1764
+ return new Promise((resolve5) => pool.waiters.push(resolve5));
1765
+ }
1766
+ releaseSocket(socket, broken) {
1767
+ const pool = socketPools.get(this.connectionId);
1768
+ if (!pool) {
1769
+ socket.destroy();
1770
+ return;
1771
+ }
1772
+ if (broken || socket.destroyed) {
1773
+ pool.total--;
1774
+ try {
1775
+ socket.destroy();
1776
+ } catch {
1777
+ }
1778
+ return;
1779
+ }
1780
+ const waiter = pool.waiters.shift();
1781
+ if (waiter) {
1782
+ waiter(socket);
1783
+ } else {
1784
+ pool.free.push(socket);
1785
+ }
1427
1786
  }
1428
1787
  async waitForServerReady() {
1429
1788
  const maxRetries = 10;
@@ -1517,58 +1876,6 @@ var QueryEngine = class {
1517
1876
  });
1518
1877
  });
1519
1878
  }
1520
- async sendTcpRequestFast(command) {
1521
- return new Promise((resolve5, reject) => {
1522
- let queue = connectionQueues.get(this.connectionId);
1523
- if (!queue) {
1524
- queue = [];
1525
- connectionQueues.set(this.connectionId, queue);
1526
- }
1527
- queue.push({ command, resolve: resolve5, reject });
1528
- this.processQueue();
1529
- });
1530
- }
1531
- async processQueue() {
1532
- if (connectionProcessing.get(this.connectionId)) {
1533
- return;
1534
- }
1535
- const queue = connectionQueues.get(this.connectionId);
1536
- if (!queue || queue.length === 0) {
1537
- return;
1538
- }
1539
- connectionProcessing.set(this.connectionId, true);
1540
- try {
1541
- const serverInfo = globalTcpServers.get(this.connectionId);
1542
- if (!serverInfo) {
1543
- throw new Error("Server not initialized");
1544
- }
1545
- while (queue.length > 0) {
1546
- const request = queue.shift();
1547
- if (!request) break;
1548
- try {
1549
- let connection = globalTcpConnections.get(this.connectionId);
1550
- if (!connection || connection.destroyed || connection.readyState !== "open") {
1551
- connection = await this.createNewConnection(serverInfo.port);
1552
- globalTcpConnections.set(this.connectionId, connection);
1553
- }
1554
- const result = await this.executeOnConnection(connection, request.command);
1555
- request.resolve(result);
1556
- } catch (error) {
1557
- const staleConnection = globalTcpConnections.get(this.connectionId);
1558
- if (staleConnection) {
1559
- try {
1560
- staleConnection.destroy();
1561
- } catch {
1562
- }
1563
- }
1564
- globalTcpConnections.delete(this.connectionId);
1565
- request.reject(error);
1566
- }
1567
- }
1568
- } finally {
1569
- connectionProcessing.set(this.connectionId, false);
1570
- }
1571
- }
1572
1879
  async createNewConnection(port) {
1573
1880
  return new Promise((resolve5, reject) => {
1574
1881
  const client = new net2.Socket();
@@ -1716,11 +2023,16 @@ var QueryEngine = class {
1716
2023
  });
1717
2024
  }
1718
2025
  async disconnect() {
1719
- const connection = globalTcpConnections.get(this.connectionId);
1720
- if (connection && connection.readyState === "open") {
1721
- connection.write(JSON.stringify({ action: "disconnect" }));
1722
- connection.destroy();
1723
- globalTcpConnections.delete(this.connectionId);
2026
+ await EmbeddedEngine.disconnect(this.connectionId);
2027
+ const pool = socketPools.get(this.connectionId);
2028
+ if (pool) {
2029
+ for (const s of pool.free) {
2030
+ try {
2031
+ s.destroy();
2032
+ } catch {
2033
+ }
2034
+ }
2035
+ socketPools.delete(this.connectionId);
1724
2036
  }
1725
2037
  const serverInfo = globalTcpServers.get(this.connectionId);
1726
2038
  if (serverInfo && serverInfo.process && !serverInfo.process.killed) {
@@ -1740,347 +2052,70 @@ var QueryEngine = class {
1740
2052
  }
1741
2053
  };
1742
2054
 
1743
- // src/lib/SqliteExecutor.ts
1744
- var import_child_process4 = require("child_process");
1745
- var path6 = __toESM(require("path"));
1746
- var fs4 = __toESM(require("fs"));
1747
- var import_util = require("util");
1748
- var import_module3 = require("module");
1749
- var import_url3 = require("url");
1750
- var import_path6 = require("path");
1751
- var import_meta3 = {};
1752
- var execAsync = (0, import_util.promisify)(import_child_process4.exec);
1753
- var SqliteExecutor = class {
1754
- binaryPath;
1755
- dbPath;
1756
- constructor(dbPath) {
1757
- this.dbPath = dbPath;
1758
- this.binaryPath = this.getBinaryPath();
1759
- }
1760
- findVersionedBinary(binDir, platform2) {
1761
- try {
1762
- const files = fs4.readdirSync(binDir);
1763
- const extension = platform2 === "win32" ? ".exe" : "";
1764
- const platformName = platform2 === "win32" ? "windows" : platform2 === "darwin" ? "macos" : "linux";
1765
- const pattern = new RegExp(`^sqlite-engine-v\\d+\\.\\d+\\.\\d+-${platformName}-x64${extension.replace(".", "\\.")}$`);
1766
- const matchingFile = files.find((f) => pattern.test(f));
1767
- if (matchingFile) {
1768
- return path6.join(binDir, matchingFile);
1769
- }
1770
- } catch (error) {
1771
- }
1772
- return null;
1773
- }
1774
- getBinaryPath() {
1775
- const __filename2 = typeof import_meta3 !== "undefined" && import_meta3.url ? (0, import_url3.fileURLToPath)(import_meta3.url) : "";
1776
- const __dirname = __filename2 ? (0, import_path6.dirname)(__filename2) : process.cwd();
1777
- const possibleDirs = [
1778
- path6.resolve(process.cwd(), ".dbcube", "bin"),
1779
- path6.resolve(process.cwd(), "node_modules", ".dbcube", "bin"),
1780
- path6.resolve(__dirname, "..", "bin")
1781
- ];
1782
- const platform2 = process.platform;
1783
- const extension = platform2 === "win32" ? ".exe" : "";
1784
- const platformName = platform2 === "win32" ? "windows" : platform2 === "darwin" ? "macos" : "linux";
1785
- for (const dir of possibleDirs) {
1786
- const versionedPath = this.findVersionedBinary(dir, platform2);
1787
- if (versionedPath && fs4.existsSync(versionedPath)) {
1788
- return versionedPath;
1789
- }
1790
- }
1791
- const binaryName = `sqlite-engine-${platformName}-x64${extension}`;
1792
- for (const dir of possibleDirs) {
1793
- const fullPath = path6.join(dir, binaryName);
1794
- if (fs4.existsSync(fullPath)) {
1795
- return fullPath;
1796
- }
1797
- }
1798
- const fallbackName = `sqlite-engine${extension}`;
1799
- for (const dir of possibleDirs) {
1800
- const fullPath = path6.join(dir, fallbackName);
1801
- if (fs4.existsSync(fullPath)) {
1802
- return fullPath;
1803
- }
1804
- }
1805
- return path6.join(possibleDirs[0], binaryName);
1806
- }
1807
- async executeBinary(args) {
1808
- const escapedArgs = args.map((arg) => {
1809
- if (arg.includes(" ") || arg.includes('"') || arg.includes("(") || arg.includes(")")) {
1810
- return `"${arg.replace(/"/g, '\\"')}"`;
1811
- }
1812
- return arg;
1813
- });
1814
- const command = `"${this.binaryPath}" ${escapedArgs.join(" ")}`;
1815
- try {
1816
- const { stdout, stderr } = await execAsync(command, {
1817
- maxBuffer: 10 * 1024 * 1024,
1818
- // 10MB buffer
1819
- timeout: 3e4
1820
- // 30s timeout
1821
- });
1822
- if (stderr && stderr.trim()) {
1823
- console.warn("SQLite Engine Warning:", stderr);
1824
- }
1825
- const result = JSON.parse(stdout.trim());
1826
- return result;
1827
- } catch (error) {
1828
- if (error.stdout) {
1829
- try {
1830
- const result = JSON.parse(error.stdout.trim());
1831
- return result;
1832
- } catch (parseError) {
1833
- }
1834
- }
1835
- throw new Error(`SQLite execution failed: ${error.message}`);
1836
- }
1837
- }
1838
- async connect() {
1839
- try {
1840
- const result = await this.executeBinary([
1841
- "--action",
1842
- "connect",
1843
- "--database",
1844
- this.dbPath
1845
- ]);
1846
- return result.status === "success";
1847
- } catch (error) {
1848
- return false;
1849
- }
1850
- }
1851
- async exists() {
1852
- try {
1853
- const result = await this.executeBinary([
1854
- "--action",
1855
- "exists",
1856
- "--database",
1857
- this.dbPath
1858
- ]);
1859
- return result.status === "success" && result.data === true;
1860
- } catch (error) {
1861
- return false;
1862
- }
1863
- }
1864
- async query(sql, params) {
1865
- const args = [
1866
- "--action",
1867
- "query",
1868
- "--database",
1869
- this.dbPath,
1870
- "--query",
1871
- sql
1872
- ];
1873
- if (params && params.length > 0) {
1874
- args.push("--params", JSON.stringify(params));
1875
- }
1876
- return this.executeBinary(args);
1877
- }
1878
- // Método para múltiples queries (como lo hace better-sqlite3)
1879
- async queryMultiple(sql) {
1880
- return this.query(sql);
1881
- }
1882
- // Método prepare que simula prepared statements
1883
- prepare(sql) {
1884
- return {
1885
- all: async (...params) => {
1886
- const result = await this.query(sql, params);
1887
- if (result.status === "error") {
1888
- throw new Error(result.message);
1889
- }
1890
- return Array.isArray(result.data) ? result.data : [];
1891
- },
1892
- run: async (...params) => {
1893
- const result = await this.query(sql, params);
1894
- if (result.status === "error") {
1895
- throw new Error(result.message);
1896
- }
1897
- if (typeof result.data === "object" && result.data.hasOwnProperty("changes") && result.data.hasOwnProperty("lastID")) {
1898
- return result.data;
1899
- }
1900
- return { changes: 0, lastID: 0 };
1901
- }
1902
- };
1903
- }
1904
- // Para compatibilidad con better-sqlite3 API sincrona usando deasync si es necesario
1905
- prepareSync(sql) {
1906
- const __filename2 = typeof import_meta3 !== "undefined" && import_meta3.url ? (0, import_url3.fileURLToPath)(import_meta3.url) : "";
1907
- const requireUrl = __filename2 || import_meta3.url;
1908
- const require2 = (0, import_module3.createRequire)(requireUrl);
1909
- const deasync = require2("deasync");
1910
- return {
1911
- all: (...params) => {
1912
- let result;
1913
- let done = false;
1914
- let error;
1915
- this.query(sql, params).then((res) => {
1916
- if (res.status === "error") {
1917
- error = new Error(res.message);
1918
- } else {
1919
- result = Array.isArray(res.data) ? res.data : [];
1920
- }
1921
- done = true;
1922
- }).catch((err) => {
1923
- error = err;
1924
- done = true;
1925
- });
1926
- deasync.loopWhile(() => !done);
1927
- if (error) throw error;
1928
- return result;
1929
- },
1930
- run: (...params) => {
1931
- let result;
1932
- let done = false;
1933
- let error;
1934
- this.query(sql, params).then((res) => {
1935
- if (res.status === "error") {
1936
- error = new Error(res.message);
1937
- } else if (typeof res.data === "object" && res.data.hasOwnProperty("changes") && res.data.hasOwnProperty("lastID")) {
1938
- result = res.data;
1939
- } else {
1940
- result = { changes: 0, lastID: 0 };
1941
- }
1942
- done = true;
1943
- }).catch((err) => {
1944
- error = err;
1945
- done = true;
1946
- });
1947
- deasync.loopWhile(() => !done);
1948
- if (error) throw error;
1949
- return result;
1950
- }
1951
- };
1952
- }
1953
- };
1954
-
1955
2055
  // src/lib/DbConfig.ts
1956
2056
  var path7 = __toESM(require("path"));
1957
2057
  var import_fs2 = __toESM(require("fs"));
1958
2058
  var rootPath = path7.resolve(process.cwd(), ".dbcube");
1959
2059
  var SQLite = class {
1960
- executor = null;
1961
2060
  database;
2061
+ engine = null;
1962
2062
  constructor(config) {
1963
- this.database = config.DATABASE;
2063
+ this.database = config.DATABASE || "config";
2064
+ }
2065
+ /** Ruta del archivo SQLite interno (.dbcube/<database>.db). */
2066
+ dbFilePath() {
2067
+ return path7.join(rootPath, this.database + ".db");
2068
+ }
2069
+ /** QueryEngine sqlite apuntando al archivo interno, con config explícito
2070
+ * (no vive en dbcube.config.js). La ruta relativa `.dbcube/<db>` deja que
2071
+ * QueryEngine le añada `.db` → `.dbcube/<db>.db`, relativo al CWD. */
2072
+ getEngine() {
2073
+ if (!this.engine) {
2074
+ const relativeDb = path7.join(".dbcube", this.database).replace(/\\/g, "/");
2075
+ this.engine = new QueryEngine(`dbcube-internal-${this.database}`, 3e4, {
2076
+ type: "sqlite",
2077
+ config: { DATABASE: relativeDb }
2078
+ });
2079
+ }
2080
+ return this.engine;
1964
2081
  }
1965
2082
  async ifExist() {
1966
- if (this.database) {
1967
- const dbPath = this.database || ":memory:";
1968
- const configPath = path7.join(rootPath, dbPath + ".db");
1969
- if (!import_fs2.default.existsSync(rootPath)) {
1970
- import_fs2.default.mkdirSync(rootPath, { recursive: true });
1971
- }
1972
- if (import_fs2.default.existsSync(configPath)) {
1973
- return true;
1974
- }
1975
- if (!this.executor) {
1976
- this.executor = new SqliteExecutor(configPath);
1977
- }
1978
- return await this.executor.exists();
2083
+ if (!import_fs2.default.existsSync(rootPath)) {
2084
+ import_fs2.default.mkdirSync(rootPath, { recursive: true });
1979
2085
  }
1980
- return false;
2086
+ return import_fs2.default.existsSync(this.dbFilePath());
1981
2087
  }
1982
2088
  async connect() {
1983
- return new Promise(async (resolve5, reject) => {
1984
- try {
1985
- if (!this.executor) {
1986
- const dbPath = this.database || ":memory:";
1987
- const configPath = path7.join(rootPath, dbPath + ".db");
1988
- if (!import_fs2.default.existsSync(rootPath)) {
1989
- import_fs2.default.mkdirSync(rootPath, { recursive: true });
1990
- }
1991
- this.executor = new SqliteExecutor(configPath);
1992
- const connected = await this.executor.connect();
1993
- if (!connected) {
1994
- throw new Error("Failed to connect to SQLite database");
1995
- }
1996
- }
1997
- resolve5(this.executor);
1998
- } catch (error) {
1999
- reject(error);
2000
- }
2001
- });
2089
+ if (!import_fs2.default.existsSync(rootPath)) {
2090
+ import_fs2.default.mkdirSync(rootPath, { recursive: true });
2091
+ }
2092
+ return this.getEngine();
2002
2093
  }
2003
2094
  async disconnect() {
2004
- return new Promise((resolve5) => {
2005
- if (this.executor) {
2006
- this.executor = null;
2007
- }
2008
- resolve5();
2009
- });
2010
2095
  }
2011
2096
  async query(sqlQuery) {
2012
- return new Promise(async (resolve5) => {
2013
- try {
2014
- if (typeof sqlQuery !== "string") {
2015
- throw new Error("The SQL query must be a string.");
2016
- }
2017
- if (!this.executor) {
2018
- await this.connect();
2019
- }
2020
- if (!this.executor) {
2021
- throw new Error("Database connection is not available.");
2022
- }
2023
- const result = await this.executor.queryMultiple(sqlQuery);
2024
- if (result.status === "error") {
2025
- resolve5({
2026
- status: "error",
2027
- message: result.message,
2028
- data: null
2029
- });
2030
- } else {
2031
- resolve5({
2032
- status: "success",
2033
- message: "Query executed successfully",
2034
- data: result.data
2035
- });
2036
- }
2037
- } catch (error) {
2038
- resolve5({
2039
- status: "error",
2040
- message: error.message || "An error occurred while executing the query.",
2041
- data: null
2042
- });
2043
- }
2044
- });
2097
+ return this.queryWithParameters(sqlQuery, []);
2045
2098
  }
2046
2099
  async queryWithParameters(sqlQuery, params = []) {
2047
- return new Promise(async (resolve5) => {
2048
- try {
2049
- if (typeof sqlQuery !== "string") {
2050
- throw new Error("The SQL query must be a string.");
2051
- }
2052
- if (!Array.isArray(params)) {
2053
- throw new Error("Parameters must be an array.");
2054
- }
2055
- if (!this.executor) {
2056
- await this.connect();
2057
- }
2058
- if (!this.executor) {
2059
- throw new Error("Database connection is not available.");
2060
- }
2061
- const result = await this.executor.query(sqlQuery, params);
2062
- if (result.status === "error") {
2063
- resolve5({
2064
- status: "error",
2065
- message: result.message,
2066
- data: null
2067
- });
2068
- } else {
2069
- resolve5({
2070
- status: "success",
2071
- message: "Query executed successfully",
2072
- data: result.data
2073
- });
2074
- }
2075
- } catch (error) {
2076
- console.log(error);
2077
- resolve5({
2078
- status: "error",
2079
- message: error.message || "An error occurred while executing the query.",
2080
- data: null
2081
- });
2100
+ try {
2101
+ if (typeof sqlQuery !== "string") {
2102
+ throw new Error("The SQL query must be a string.");
2082
2103
  }
2083
- });
2104
+ if (!Array.isArray(params)) {
2105
+ throw new Error("Parameters must be an array.");
2106
+ }
2107
+ const response = await this.getEngine().rawQuery(sqlQuery, params);
2108
+ if (response.status !== 200) {
2109
+ return { status: "error", message: response.message || "Query failed", data: null };
2110
+ }
2111
+ return { status: "success", message: "Query executed successfully", data: response.data };
2112
+ } catch (error) {
2113
+ return {
2114
+ status: "error",
2115
+ message: error.message || "An error occurred while executing the query.",
2116
+ data: null
2117
+ };
2118
+ }
2084
2119
  }
2085
2120
  convertToParameterizedQuery(sql) {
2086
2121
  const normalizedSql = sql.replace(/\s+/g, " ").trim();