@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.
- package/README.md +126 -137
- package/dbhub.dameng.toml.example +4 -11
- package/dist/{chunk-SQA2ISDE.js → chunk-2JL4KCP5.js} +21 -119
- package/dist/chunk-3RJRH3CN.js +121 -0
- package/dist/chunk-4276LPPU.js +179 -0
- package/dist/chunk-FU2ZJE4E.js +16 -0
- package/dist/chunk-HDPZAIDL.js +155 -0
- package/dist/{chunk-2A2QF3CS.js → chunk-HGU7ILEZ.js} +215 -68
- package/dist/{chunk-IPK7BYBL.js → chunk-JFWX35TB.js} +0 -1
- package/dist/chunk-O3PEWKBB.js +302 -0
- package/dist/dameng-NKF7NZKE.js +250 -0
- package/dist/index.js +461 -251
- package/dist/{mariadb-7F72IRB4.js → mariadb-MMC6WMEG.js} +86 -53
- package/dist/{mysql-A43SL7UM.js → mysql-TESH7KKV.js} +126 -50
- package/dist/{postgres-VFNLBWMF.js → postgres-C24SBQ4I.js} +154 -36
- package/dist/public/assets/index-BJ-1UrcV.css +1 -0
- package/dist/public/assets/index-BdBaFSHd.js +148 -0
- package/dist/public/assets/postgres-BpcazhJg.svg +22 -0
- package/dist/public/assets/sqlserver-ByfFYYpV.svg +11 -0
- package/dist/public/favicon.svg +57 -0
- package/dist/public/index.html +14 -0
- package/dist/public/logo-full-light.svg +58 -0
- package/dist/{registry-XSX2VZTD.js → registry-WVSFVS35.js} +3 -2
- package/dist/{sqlite-IOUAYHGE.js → sqlite-7BNGDMBZ.js} +57 -42
- package/dist/sqlserver-KZPPK67Q.js +1042 -0
- package/package.json +22 -65
- package/dist/chunk-RTB262PR.js +0 -60
- package/dist/chunk-ZNQTMARG.js +0 -127
- package/dist/dameng-5KYI5JBK.js +0 -898
- package/dist/sqlserver-OE4SFH4B.js +0 -587
- package/docs/README.md +0 -14
- 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-
|
|
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
|
|
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
|
-
|
|
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"
|
|
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
|
|
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
|
-
|
|
1048
|
-
|
|
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 =
|
|
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 =
|
|
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
|
|
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
|
|
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
|
-
|
|
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.
|
|
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
|
-
|
|
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
|
-
|
|
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.
|
|
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.
|
|
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
|
-
|
|
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
|
|
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
|
|
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: ${
|
|
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,
|