@zz1996/dbhub-dameng 0.1.7 → 1.2.5-dameng.621

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.
Files changed (32) hide show
  1. package/README.md +126 -137
  2. package/dbhub.dameng.toml.example +4 -11
  3. package/dist/{chunk-SQA2ISDE.js → chunk-2JL4KCP5.js} +21 -119
  4. package/dist/chunk-3RJRH3CN.js +121 -0
  5. package/dist/chunk-4276LPPU.js +179 -0
  6. package/dist/chunk-FU2ZJE4E.js +16 -0
  7. package/dist/chunk-HDPZAIDL.js +155 -0
  8. package/dist/{chunk-2A2QF3CS.js → chunk-HGU7ILEZ.js} +215 -68
  9. package/dist/{chunk-IPK7BYBL.js → chunk-JFWX35TB.js} +0 -1
  10. package/dist/chunk-O3PEWKBB.js +302 -0
  11. package/dist/dameng-NKF7NZKE.js +250 -0
  12. package/dist/index.js +461 -251
  13. package/dist/{mariadb-7F72IRB4.js → mariadb-MMC6WMEG.js} +86 -53
  14. package/dist/{mysql-A43SL7UM.js → mysql-TESH7KKV.js} +126 -50
  15. package/dist/{postgres-VFNLBWMF.js → postgres-C24SBQ4I.js} +154 -36
  16. package/dist/public/assets/index-BJ-1UrcV.css +1 -0
  17. package/dist/public/assets/index-BdBaFSHd.js +148 -0
  18. package/dist/public/assets/postgres-BpcazhJg.svg +22 -0
  19. package/dist/public/assets/sqlserver-ByfFYYpV.svg +11 -0
  20. package/dist/public/favicon.svg +57 -0
  21. package/dist/public/index.html +14 -0
  22. package/dist/public/logo-full-light.svg +58 -0
  23. package/dist/{registry-XSX2VZTD.js → registry-WVSFVS35.js} +3 -2
  24. package/dist/{sqlite-IOUAYHGE.js → sqlite-7BNGDMBZ.js} +57 -42
  25. package/dist/sqlserver-KZPPK67Q.js +1042 -0
  26. package/package.json +22 -65
  27. package/dist/chunk-RTB262PR.js +0 -60
  28. package/dist/chunk-ZNQTMARG.js +0 -127
  29. package/dist/dameng-5KYI5JBK.js +0 -898
  30. package/dist/sqlserver-OE4SFH4B.js +0 -587
  31. package/docs/README.md +0 -14
  32. package/docs/dameng-connector-plan.md +0 -143
@@ -2,21 +2,30 @@ import {
2
2
  isDriverNotInstalled
3
3
  } from "./chunk-WVVMH6FJ.js";
4
4
  import {
5
- ConnectorRegistry,
6
- SafeURL,
7
5
  getDatabaseTypeFromDSN,
8
6
  getDefaultPortForType,
9
- parseConnectionInfoFromDSN,
7
+ parseConnectionInfoFromDSN
8
+ } from "./chunk-3RJRH3CN.js";
9
+ import {
10
+ ConnectorRegistry,
11
+ SafeURL,
10
12
  stripCommentsAndStrings
11
- } from "./chunk-SQA2ISDE.js";
13
+ } from "./chunk-2JL4KCP5.js";
12
14
 
13
15
  // src/tools/builtin-tools.ts
14
16
  var BUILTIN_TOOL_EXECUTE_SQL = "execute_sql";
15
17
  var BUILTIN_TOOL_SEARCH_OBJECTS = "search_objects";
18
+ var BUILTIN_TOOL_EXPLAIN_SQL = "explain_sql";
19
+ var BUILTIN_TOOL_HEALTH_CHECK = "health_check";
16
20
  var BUILTIN_TOOLS = [
17
21
  BUILTIN_TOOL_EXECUTE_SQL,
18
22
  BUILTIN_TOOL_SEARCH_OBJECTS
19
23
  ];
24
+ var ALL_BUILTIN_TOOL_NAMES = [
25
+ ...BUILTIN_TOOLS,
26
+ BUILTIN_TOOL_EXPLAIN_SQL,
27
+ BUILTIN_TOOL_HEALTH_CHECK
28
+ ];
20
29
 
21
30
  // src/utils/ssh-tunnel.ts
22
31
  import { Client } from "ssh2";
@@ -580,55 +589,59 @@ function parseCommandLineArgs() {
580
589
  }
581
590
  return parsedManually;
582
591
  }
583
- function loadEnvFiles() {
592
+ function findEnvFile() {
584
593
  const isDevelopment = process.env.NODE_ENV === "development" || process.argv[1]?.includes("tsx");
585
594
  const envFileNames = isDevelopment ? [".env.local", ".env"] : [".env"];
586
595
  const envPaths = [];
587
596
  for (const fileName of envFileNames) {
588
597
  envPaths.push(
589
- fileName,
598
+ path.join(process.cwd(), fileName),
590
599
  // Current working directory
591
- path.join(__dirname, "..", "..", fileName),
600
+ path.join(__dirname, "..", "..", fileName)
592
601
  // Two levels up (src/config -> src -> root)
593
- path.join(process.cwd(), fileName)
594
- // Explicit current working directory
595
602
  );
596
603
  }
597
604
  for (const envPath of envPaths) {
598
- console.error(`Checking for env file: ${envPath}`);
599
605
  if (fs.existsSync(envPath)) {
600
- dotenv.config({ path: envPath });
601
- if (process.env.READONLY !== void 0) {
602
- console.error("\nERROR: READONLY environment variable is no longer supported.");
603
- console.error("Use dbhub.toml with [[tools]] configuration instead:\n");
604
- console.error(" [[sources]]");
605
- console.error(' id = "default"');
606
- console.error(' dsn = "..."\n');
607
- console.error(" [[tools]]");
608
- console.error(' name = "execute_sql"');
609
- console.error(' source = "default"');
610
- console.error(" readonly = true\n");
611
- console.error("See https://dbhub.ai/tools/execute-sql#read-only-mode for details.\n");
612
- process.exit(1);
613
- }
614
- if (process.env.MAX_ROWS !== void 0) {
615
- console.error("\nERROR: MAX_ROWS environment variable is no longer supported.");
616
- console.error("Use dbhub.toml with [[tools]] configuration instead:\n");
617
- console.error(" [[sources]]");
618
- console.error(' id = "default"');
619
- console.error(' dsn = "..."\n');
620
- console.error(" [[tools]]");
621
- console.error(' name = "execute_sql"');
622
- console.error(' source = "default"');
623
- console.error(" max_rows = 1000\n");
624
- console.error("See https://dbhub.ai/tools/execute-sql#row-limiting for details.\n");
625
- process.exit(1);
626
- }
627
- return path.basename(envPath);
606
+ return envPath;
628
607
  }
629
608
  }
630
609
  return null;
631
610
  }
611
+ function loadEnvFiles() {
612
+ const envPath = findEnvFile();
613
+ if (envPath) {
614
+ dotenv.config({ path: envPath });
615
+ if (process.env.READONLY !== void 0) {
616
+ console.error("\nERROR: READONLY environment variable is no longer supported.");
617
+ console.error("Use dbhub.toml with [[tools]] configuration instead:\n");
618
+ console.error(" [[sources]]");
619
+ console.error(' id = "default"');
620
+ console.error(' dsn = "..."\n');
621
+ console.error(" [[tools]]");
622
+ console.error(' name = "execute_sql"');
623
+ console.error(' source = "default"');
624
+ console.error(" readonly = true\n");
625
+ console.error("See https://dbhub.ai/tools/execute-sql#read-only-mode for details.\n");
626
+ process.exit(1);
627
+ }
628
+ if (process.env.MAX_ROWS !== void 0) {
629
+ console.error("\nERROR: MAX_ROWS environment variable is no longer supported.");
630
+ console.error("Use dbhub.toml with [[tools]] configuration instead:\n");
631
+ console.error(" [[sources]]");
632
+ console.error(' id = "default"');
633
+ console.error(' dsn = "..."\n');
634
+ console.error(" [[tools]]");
635
+ console.error(' name = "execute_sql"');
636
+ console.error(' source = "default"');
637
+ console.error(" max_rows = 1000\n");
638
+ console.error("See https://dbhub.ai/tools/execute-sql#row-limiting for details.\n");
639
+ process.exit(1);
640
+ }
641
+ return path.basename(envPath);
642
+ }
643
+ return null;
644
+ }
632
645
  function isDemoMode() {
633
646
  const args = parseCommandLineArgs();
634
647
  return args.demo === "true";
@@ -649,7 +662,7 @@ function buildDSNFromEnvParams() {
649
662
  return null;
650
663
  }
651
664
  }
652
- const supportedTypes = ["postgres", "postgresql", "mysql", "mariadb", "sqlserver", "sqlite", "dameng", "dm"];
665
+ const supportedTypes = ["postgres", "postgresql", "mysql", "mariadb", "sqlserver", "sqlite", "dameng"];
653
666
  if (!supportedTypes.includes(dbType.toLowerCase())) {
654
667
  throw new Error(`Unsupported DB_TYPE: ${dbType}. Supported types: ${supportedTypes.join(", ")}`);
655
668
  }
@@ -668,7 +681,6 @@ function buildDSNFromEnvParams() {
668
681
  port = "1433";
669
682
  break;
670
683
  case "dameng":
671
- case "dm":
672
684
  port = "5236";
673
685
  break;
674
686
  case "sqlite":
@@ -686,8 +698,7 @@ function buildDSNFromEnvParams() {
686
698
  const encodedUser = encodeURIComponent(user);
687
699
  const encodedPassword = encodeURIComponent(password);
688
700
  const encodedDbName = encodeURIComponent(dbNameStr);
689
- const normalizedType = dbType.toLowerCase();
690
- const protocol = normalizedType === "postgresql" ? "postgres" : normalizedType === "dm" ? "dameng" : normalizedType;
701
+ const protocol = dbType.toLowerCase() === "postgresql" ? "postgres" : dbType.toLowerCase();
691
702
  const dsn = `${protocol}://${encodedUser}:${encodedPassword}@${dbHost}:${port}/${encodedDbName}`;
692
703
  return {
693
704
  dsn,
@@ -799,6 +810,21 @@ function resolveAllowedHosts() {
799
810
  function splitHostList(value) {
800
811
  return value.split(",").map((h) => h.trim()).filter((h) => h.length > 0);
801
812
  }
813
+ function resolveAuthTokens() {
814
+ const args = parseCommandLineArgs();
815
+ const cliValue = requireFlagValue("auth-token", args, "secret1,secret2");
816
+ if (cliValue !== void 0) {
817
+ return { tokens: splitTokenList(cliValue), source: "command line argument" };
818
+ }
819
+ const envValue = process.env.DBHUB_AUTH_TOKEN?.trim();
820
+ if (envValue) {
821
+ return { tokens: splitTokenList(envValue), source: "environment variable" };
822
+ }
823
+ return { tokens: [], source: "default" };
824
+ }
825
+ function splitTokenList(value) {
826
+ return value.split(",").map((t) => t.trim()).filter((t) => t.length > 0);
827
+ }
802
828
  function redactDSN(dsn) {
803
829
  try {
804
830
  const url = new URL(dsn);
@@ -928,6 +954,9 @@ function resolveSSHConfig() {
928
954
  };
929
955
  }
930
956
  async function resolveSourceConfigs() {
957
+ if (parseCommandLineArgs().config) {
958
+ loadEnvFiles();
959
+ }
931
960
  if (!isDemoMode()) {
932
961
  const tomlConfig = loadTomlConfig();
933
962
  if (tomlConfig) {
@@ -937,6 +966,11 @@ async function resolveSourceConfigs() {
937
966
  "The --id flag cannot be used with TOML configuration. TOML config defines source IDs directly. Either remove the --id flag or use command-line DSN configuration instead."
938
967
  );
939
968
  }
969
+ if (parseCommandLineArgs().dsn) {
970
+ throw new Error(
971
+ `The --dsn flag cannot be used with TOML configuration (${tomlConfig.source}). TOML config defines database sources directly and supports multiple databases, while a DSN configures a single database. Either remove the --dsn flag or drop the --config flag.`
972
+ );
973
+ }
940
974
  return tomlConfig;
941
975
  }
942
976
  }
@@ -960,6 +994,8 @@ async function resolveSourceConfigs() {
960
994
  dbType = "mariadb";
961
995
  } else if (protocol === "sqlserver") {
962
996
  dbType = "sqlserver";
997
+ } else if (protocol === "dameng") {
998
+ dbType = "dameng";
963
999
  } else if (protocol === "sqlite") {
964
1000
  dbType = "sqlite";
965
1001
  } else {
@@ -1044,8 +1080,9 @@ function loadTomlConfig() {
1044
1080
  }
1045
1081
  function resolveTomlConfigPath() {
1046
1082
  const args = parseCommandLineArgs();
1047
- if (args.config) {
1048
- const configPath = expandHomeDir(args.config);
1083
+ const configValue = requireFlagValue("config", args, "./dbhub.toml");
1084
+ if (configValue) {
1085
+ const configPath = expandHomeDir(configValue);
1049
1086
  if (!fs2.existsSync(configPath)) {
1050
1087
  throw new Error(
1051
1088
  `Configuration file specified by --config flag not found: ${configPath}`
@@ -1053,10 +1090,6 @@ function resolveTomlConfigPath() {
1053
1090
  }
1054
1091
  return configPath;
1055
1092
  }
1056
- const defaultConfigPath = path2.join(process.cwd(), "dbhub.toml");
1057
- if (fs2.existsSync(defaultConfigPath)) {
1058
- return defaultConfigPath;
1059
- }
1060
1093
  return null;
1061
1094
  }
1062
1095
  function validateTomlConfig(config, configPath) {
@@ -1126,7 +1159,7 @@ function validateToolsConfig(tools, sources, configPath) {
1126
1159
  `Configuration file ${configPath}: tool '${tool.name}' references unknown source '${tool.source}'`
1127
1160
  );
1128
1161
  }
1129
- const isBuiltin = BUILTIN_TOOLS.includes(tool.name);
1162
+ const isBuiltin = ALL_BUILTIN_TOOL_NAMES.includes(tool.name);
1130
1163
  const isExecuteSql = tool.name === BUILTIN_TOOL_EXECUTE_SQL;
1131
1164
  if (isBuiltin) {
1132
1165
  if (tool.description || tool.statement || tool.parameters) {
@@ -1211,6 +1244,11 @@ function validateDSNFieldConflicts(source, configPath) {
1211
1244
  if (source.database && info.database && source.database !== info.database) {
1212
1245
  conflict("database", source.database, info.database);
1213
1246
  }
1247
+ if (source.database && !info.database) {
1248
+ throw new Error(
1249
+ `Configuration file ${configPath}: source '${source.id}' has a 'database' field but the DSN names no database. The field is ignored at connection time \u2014 add the database to the DSN, or use individual connection parameters instead of a DSN.`
1250
+ );
1251
+ }
1214
1252
  if (source.user && info.user && source.user !== info.user) {
1215
1253
  conflict("user", source.user, info.user);
1216
1254
  }
@@ -1276,6 +1314,18 @@ function validateSourceConfig(source, configPath) {
1276
1314
  );
1277
1315
  }
1278
1316
  }
1317
+ if (source.aws_profile !== void 0) {
1318
+ if (typeof source.aws_profile !== "string" || source.aws_profile.trim().length === 0) {
1319
+ throw new Error(
1320
+ `Configuration file ${configPath}: source '${source.id}' has invalid aws_profile. Must be a non-empty string.`
1321
+ );
1322
+ }
1323
+ if (source.aws_iam_auth !== true) {
1324
+ throw new Error(
1325
+ `Configuration file ${configPath}: source '${source.id}' aws_profile requires aws_iam_auth = true.`
1326
+ );
1327
+ }
1328
+ }
1279
1329
  if (source.aws_iam_auth === true) {
1280
1330
  const validIamTypes = ["postgres", "mysql", "mariadb"];
1281
1331
  if (!source.type || !validIamTypes.includes(source.type)) {
@@ -1303,6 +1353,18 @@ function validateSourceConfig(source, configPath) {
1303
1353
  );
1304
1354
  }
1305
1355
  }
1356
+ if (source.pool_max_connections !== void 0) {
1357
+ if (source.type !== "postgres") {
1358
+ throw new Error(
1359
+ `Configuration file ${configPath}: source '${source.id}' has 'pool_max_connections' but it is only supported for PostgreSQL sources.`
1360
+ );
1361
+ }
1362
+ if (!Number.isInteger(source.pool_max_connections) || source.pool_max_connections < 1 || source.pool_max_connections > 1e3) {
1363
+ throw new Error(
1364
+ `Configuration file ${configPath}: source '${source.id}' has invalid pool_max_connections. Must be an integer between 1 and 1000.`
1365
+ );
1366
+ }
1367
+ }
1306
1368
  if (source.ssh_port !== void 0) {
1307
1369
  if (typeof source.ssh_port !== "number" || source.ssh_port <= 0 || source.ssh_port > 65535) {
1308
1370
  throw new Error(
@@ -1418,6 +1480,30 @@ function validateSourceConfig(source, configPath) {
1418
1480
  );
1419
1481
  }
1420
1482
  }
1483
+ if (source.charset !== void 0) {
1484
+ if (source.type !== "mysql" && source.type !== "mariadb") {
1485
+ throw new Error(
1486
+ `Configuration file ${configPath}: source '${source.id}' has 'charset' but it is only supported for MySQL and MariaDB sources.`
1487
+ );
1488
+ }
1489
+ if (typeof source.charset !== "string" || source.charset.trim() === "") {
1490
+ throw new Error(
1491
+ `Configuration file ${configPath}: source '${source.id}' has invalid charset '${source.charset}'. Must be a non-empty string naming a character set (e.g. "utf8mb4").`
1492
+ );
1493
+ }
1494
+ }
1495
+ if (source.collation !== void 0) {
1496
+ if (source.type !== "mysql" && source.type !== "mariadb") {
1497
+ throw new Error(
1498
+ `Configuration file ${configPath}: source '${source.id}' has 'collation' but it is only supported for MySQL and MariaDB sources.`
1499
+ );
1500
+ }
1501
+ if (typeof source.collation !== "string" || source.collation.trim() === "") {
1502
+ throw new Error(
1503
+ `Configuration file ${configPath}: source '${source.id}' has invalid collation '${source.collation}'. Must be a non-empty string naming a collation (e.g. "utf8mb4_0900_ai_ci").`
1504
+ );
1505
+ }
1506
+ }
1421
1507
  if (source.readonly !== void 0) {
1422
1508
  throw new Error(
1423
1509
  `Configuration file ${configPath}: source '${source.id}' has 'readonly' field, but readonly must be configured per-tool, not per-source. Move 'readonly' to [[tools]] configuration instead.`
@@ -1429,6 +1515,20 @@ function validateSourceConfig(source, configPath) {
1429
1515
  );
1430
1516
  }
1431
1517
  }
1518
+ function resolveSqliteDatabasePath(database, configPath) {
1519
+ if (database === ":memory:") {
1520
+ return database;
1521
+ }
1522
+ const expanded = expandHomeDir(database);
1523
+ const absolute = path2.isAbsolute(expanded) ? expanded : path2.resolve(path2.dirname(configPath), expanded);
1524
+ return absolute.replace(/\\/g, "/");
1525
+ }
1526
+ function buildSqliteDSN(database) {
1527
+ if (database === ":memory:") {
1528
+ return "sqlite:///:memory:";
1529
+ }
1530
+ return `sqlite://${database.startsWith("/") ? database : `/${database}`}`;
1531
+ }
1432
1532
  function processSourceConfigs(sources, configPath) {
1433
1533
  return sources.map((source) => {
1434
1534
  const processed = { ...source };
@@ -1439,7 +1539,7 @@ function processSourceConfigs(sources, configPath) {
1439
1539
  processed.sslrootcert = expandHomeDir(processed.sslrootcert);
1440
1540
  }
1441
1541
  if (processed.type === "sqlite" && processed.database) {
1442
- processed.database = expandHomeDir(processed.database);
1542
+ processed.database = resolveSqliteDatabasePath(processed.database, configPath);
1443
1543
  }
1444
1544
  if (processed.dsn && processed.dsn.startsWith("sqlite:///~")) {
1445
1545
  processed.dsn = `sqlite:///${expandHomeDir(processed.dsn.substring(11))}`;
@@ -1562,7 +1662,7 @@ function buildDSNFromSource(source) {
1562
1662
  `Source '${source.id}': 'database' field is required for SQLite`
1563
1663
  );
1564
1664
  }
1565
- return `sqlite:///${source.database}`;
1665
+ return buildSqliteDSN(source.database);
1566
1666
  }
1567
1667
  const isAwsIamPasswordless = source.aws_iam_auth === true && ["postgres", "mysql", "mariadb"].includes(source.type);
1568
1668
  const passwordRequired = source.authentication !== "azure-active-directory-access-token" && !isAwsIamPasswordless;
@@ -1622,12 +1722,17 @@ async function generateRdsAuthToken(params) {
1622
1722
  }
1623
1723
  throw error;
1624
1724
  }
1625
- const signer = new Signer({
1725
+ const signerConfig = {
1626
1726
  hostname: params.hostname,
1627
1727
  port: params.port,
1628
1728
  username: params.username,
1629
1729
  region: params.region
1630
- });
1730
+ };
1731
+ if (params.profile) {
1732
+ const { fromIni } = await import("@aws-sdk/credential-providers");
1733
+ signerConfig.credentials = fromIni({ profile: params.profile });
1734
+ }
1735
+ const signer = new Signer(signerConfig);
1631
1736
  return signer.getAuthToken();
1632
1737
  }
1633
1738
 
@@ -1646,9 +1751,10 @@ var AUTH_CODES = {
1646
1751
  mysql: ["ER_ACCESS_DENIED_ERROR", 1045, 1698],
1647
1752
  mariadb: ["ER_ACCESS_DENIED_ERROR", 1045, 1698],
1648
1753
  sqlserver: ["ELOGIN"],
1649
- sqlite: [],
1754
+ dameng: [-2501],
1755
+ // Invalid username or password
1756
+ sqlite: []
1650
1757
  // no network/auth layer
1651
- dameng: [-2501, -2504, "ELOGIN"]
1652
1758
  };
1653
1759
  function unreachableMessage(sourceId) {
1654
1760
  return `Source '${sourceId}' is unreachable. Verify the database is running and reachable (host, port, network), then retry.`;
@@ -1747,7 +1853,7 @@ var ConnectorManager = class {
1747
1853
  if (!lazySource) {
1748
1854
  if (sourceId) {
1749
1855
  throw new Error(
1750
- `Source '${sourceId}' not found. Available sources: ${this.sourceIds.join(", ")}`
1856
+ `Source '${sourceId}' not found. Available sources: ${this.getAvailableSourceIds().join(", ")}`
1751
1857
  );
1752
1858
  } else {
1753
1859
  throw new Error("No sources configured. Call connectWithSources() first.");
@@ -1786,6 +1892,7 @@ var ConnectorManager = class {
1786
1892
  const dsn = await this.buildConnectionDSN(source);
1787
1893
  console.error(` - ${sourceId}: ${redactDSN(dsn)}`);
1788
1894
  let actualDSN = dsn;
1895
+ let tunnel;
1789
1896
  if (source.ssh_host) {
1790
1897
  const sshConfigPath = getDefaultSSHConfigPath();
1791
1898
  let resolvedSSHConfig = null;
@@ -1829,7 +1936,7 @@ var ConnectorManager = class {
1829
1936
  const url = new URL(dsn);
1830
1937
  const targetHost = url.hostname;
1831
1938
  const targetPort = parseInt(url.port) || this.getDefaultPort(dsn);
1832
- const tunnel = new SSHTunnel();
1939
+ tunnel = new SSHTunnel();
1833
1940
  let tunnelInfo;
1834
1941
  try {
1835
1942
  tunnelInfo = await tunnel.establish(sshConfig, {
@@ -1865,13 +1972,34 @@ var ConnectorManager = class {
1865
1972
  if (source.query_timeout !== void 0 && connector.id !== "sqlite") {
1866
1973
  config.queryTimeoutSeconds = source.query_timeout;
1867
1974
  }
1975
+ if (source.pool_max_connections !== void 0) {
1976
+ config.poolMaxConnections = source.pool_max_connections;
1977
+ }
1868
1978
  if (source.search_path) {
1869
1979
  config.searchPath = source.search_path;
1870
1980
  }
1871
1981
  if (source.timezone) {
1872
1982
  config.timezone = source.timezone;
1873
1983
  }
1874
- await connector.connect(actualDSN, source.init_script, config);
1984
+ if (source.charset) {
1985
+ config.charset = source.charset;
1986
+ }
1987
+ if (source.collation) {
1988
+ config.collation = source.collation;
1989
+ }
1990
+ try {
1991
+ await connector.connect(actualDSN, source.init_script, config);
1992
+ } catch (error) {
1993
+ if (tunnel) {
1994
+ this.sshTunnels.delete(sourceId);
1995
+ try {
1996
+ await tunnel.close();
1997
+ } catch (closeError) {
1998
+ console.error(`Error closing SSH tunnel for source '${sourceId}':`, closeError);
1999
+ }
2000
+ }
2001
+ throw error;
2002
+ }
1875
2003
  this.connectors.set(sourceId, connector);
1876
2004
  if (!this.sourceIds.includes(sourceId)) {
1877
2005
  this.sourceIds.push(sourceId);
@@ -1921,7 +2049,7 @@ var ConnectorManager = class {
1921
2049
  if (!connector) {
1922
2050
  if (sourceId) {
1923
2051
  throw new Error(
1924
- `Source '${sourceId}' not found. Available sources: ${this.sourceIds.join(", ")}`
2052
+ `Source '${sourceId}' not found. Available sources: ${this.getAvailableSourceIds().join(", ")}`
1925
2053
  );
1926
2054
  } else {
1927
2055
  throw new Error("No sources connected. Call connectWithSources() first.");
@@ -1958,6 +2086,14 @@ var ConnectorManager = class {
1958
2086
  getSourceIds() {
1959
2087
  return [...this.sourceIds];
1960
2088
  }
2089
+ /**
2090
+ * Source IDs that can actually serve a request: connected, or registered for
2091
+ * lazy (re)connection on first use. Used for error messages so a source that
2092
+ * is neither is not reported as available.
2093
+ */
2094
+ getAvailableSourceIds() {
2095
+ return this.sourceIds.filter((id) => this.connectors.has(id) || this.lazySources.has(id));
2096
+ }
1961
2097
  /** Get all available source IDs */
1962
2098
  static getAvailableSourceIds() {
1963
2099
  if (!managerInstance) {
@@ -2035,7 +2171,7 @@ var ConnectorManager = class {
2035
2171
  error
2036
2172
  );
2037
2173
  } finally {
2038
- if (!this.isDisconnecting && this.sourceConfigs.has(sourceId)) {
2174
+ if (!this.isDisconnecting && this.connectors.has(sourceId)) {
2039
2175
  this.scheduleIamRefresh(source);
2040
2176
  }
2041
2177
  }
@@ -2062,7 +2198,14 @@ var ConnectorManager = class {
2062
2198
  if (this.isDisconnecting) {
2063
2199
  return;
2064
2200
  }
2065
- await this.connectSource(source);
2201
+ try {
2202
+ await this.connectSource(source);
2203
+ } catch (error) {
2204
+ if (!this.isDisconnecting && this.sourceConfigs.has(sourceId)) {
2205
+ this.lazySources.set(sourceId, source);
2206
+ }
2207
+ throw error;
2208
+ }
2066
2209
  }
2067
2210
  /**
2068
2211
  * Build a connection DSN, optionally replacing password with
@@ -2098,7 +2241,8 @@ var ConnectorManager = class {
2098
2241
  hostname,
2099
2242
  port,
2100
2243
  username,
2101
- region: source.aws_region
2244
+ region: source.aws_region,
2245
+ profile: source.aws_profile
2102
2246
  });
2103
2247
  const queryParams = new Map(parsed.searchParams);
2104
2248
  const currentSslMode = queryParams.get("sslmode");
@@ -2118,6 +2262,8 @@ var ConnectorManager = class {
2118
2262
 
2119
2263
  // src/utils/parameter-mapper.ts
2120
2264
  var PARAMETER_STYLES = {
2265
+ dameng: "positional",
2266
+ // ?, ?, ? (native dmdb binds)
2121
2267
  postgres: "numbered",
2122
2268
  // $1, $2, $3
2123
2269
  mysql: "positional",
@@ -2126,10 +2272,8 @@ var PARAMETER_STYLES = {
2126
2272
  // ?, ?, ?
2127
2273
  sqlserver: "named",
2128
2274
  // @p1, @p2, @p3
2129
- sqlite: "positional",
2275
+ sqlite: "positional"
2130
2276
  // ?, ?, ?
2131
- dameng: "positional"
2132
- // ?, ?, ? (translated to :1, :2, :3 by the connector)
2133
2277
  };
2134
2278
  function detectParameterStyle(statement) {
2135
2279
  const cleanedSQL = stripCommentsAndStrings(statement);
@@ -2242,7 +2386,7 @@ var ToolRegistry = class {
2242
2386
  * Check if a tool name is a built-in tool
2243
2387
  */
2244
2388
  isBuiltinTool(toolName) {
2245
- return BUILTIN_TOOLS.includes(toolName);
2389
+ return ALL_BUILTIN_TOOL_NAMES.includes(toolName);
2246
2390
  }
2247
2391
  /**
2248
2392
  * Validate a custom tool parameter definition
@@ -2314,10 +2458,10 @@ var ToolRegistry = class {
2314
2458
  `Tool '${toolConfig.name}' references unknown source '${toolConfig.source}'. Available sources: ${availableSources.join(", ")}`
2315
2459
  );
2316
2460
  }
2317
- for (const builtinName of BUILTIN_TOOLS) {
2461
+ for (const builtinName of ALL_BUILTIN_TOOL_NAMES) {
2318
2462
  if (toolConfig.name === builtinName || toolConfig.name.startsWith(`${builtinName}_`)) {
2319
2463
  throw new Error(
2320
- `Tool name '${toolConfig.name}' conflicts with built-in tool naming pattern. Custom tools cannot use names starting with: ${BUILTIN_TOOLS.join(", ")}`
2464
+ `Tool name '${toolConfig.name}' conflicts with built-in tool naming pattern. Custom tools cannot use names starting with: ${ALL_BUILTIN_TOOL_NAMES.join(", ")}`
2321
2465
  );
2322
2466
  }
2323
2467
  }
@@ -2449,9 +2593,12 @@ export {
2449
2593
  resolvePort,
2450
2594
  resolveHost,
2451
2595
  resolveAllowedHosts,
2596
+ resolveAuthTokens,
2452
2597
  resolveSourceConfigs,
2453
2598
  BUILTIN_TOOL_EXECUTE_SQL,
2454
2599
  BUILTIN_TOOL_SEARCH_OBJECTS,
2600
+ BUILTIN_TOOL_EXPLAIN_SQL,
2601
+ BUILTIN_TOOL_HEALTH_CHECK,
2455
2602
  loadTomlConfig,
2456
2603
  resolveTomlConfigPath,
2457
2604
  classifyConnectionError,
@@ -9,7 +9,6 @@ function quoteIdentifier(identifier, dbType) {
9
9
  switch (dbType) {
10
10
  case "postgres":
11
11
  case "sqlite":
12
- case "dameng":
13
12
  return `"${identifier.replace(/"/g, '""')}"`;
14
13
  case "mysql":
15
14
  case "mariadb":