@bytebase/dbhub 0.20.0 → 0.21.1

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 CHANGED
@@ -117,4 +117,10 @@ See [Testing](.claude/skills/testing/SKILL.md) and [Debug](https://dbhub.ai/conf
117
117
 
118
118
  ## Star History
119
119
 
120
- ![Star History Chart](https://api.star-history.com/svg?repos=bytebase/dbhub&type=Date)
120
+ <a href="https://www.star-history.com/?repos=bytebase%2Fdbhub&type=date&legend=top-left">
121
+ <picture>
122
+ <source media="(prefers-color-scheme: dark)" srcset="https://api.star-history.com/image?repos=bytebase/dbhub&type=date&theme=dark&legend=top-left" />
123
+ <source media="(prefers-color-scheme: light)" srcset="https://api.star-history.com/image?repos=bytebase/dbhub&type=date&legend=top-left" />
124
+ <img alt="Star History Chart" src="https://api.star-history.com/image?repos=bytebase/dbhub&type=date&legend=top-left" />
125
+ </picture>
126
+ </a>
@@ -1,3 +1,6 @@
1
+ import {
2
+ isDriverNotInstalled
3
+ } from "./chunk-GWPUVYLO.js";
1
4
  import {
2
5
  ConnectorRegistry,
3
6
  SafeURL,
@@ -898,7 +901,7 @@ async function resolveSourceConfigs() {
898
901
  source.ssh_keepalive_count_max = sshResult.config.keepaliveCountMax;
899
902
  }
900
903
  if (dsnResult.isDemo) {
901
- const { getSqliteInMemorySetupSql } = await import("./demo-loader-FM5OJVDA.js");
904
+ const { getSqliteInMemorySetupSql } = await import("./demo-loader-PSMTLZ2T.js");
902
905
  source.init_script = getSqliteInMemorySetupSql();
903
906
  }
904
907
  return {
@@ -1314,8 +1317,18 @@ function buildDSNFromSource(source) {
1314
1317
  }
1315
1318
 
1316
1319
  // src/utils/aws-rds-signer.ts
1317
- import { Signer } from "@aws-sdk/rds-signer";
1318
1320
  async function generateRdsAuthToken(params) {
1321
+ let Signer;
1322
+ try {
1323
+ ({ Signer } = await import("@aws-sdk/rds-signer"));
1324
+ } catch (error) {
1325
+ if (isDriverNotInstalled(error, "@aws-sdk/rds-signer")) {
1326
+ throw new Error(
1327
+ 'AWS IAM authentication requires the "@aws-sdk/rds-signer" package. Install it with: pnpm add @aws-sdk/rds-signer'
1328
+ );
1329
+ }
1330
+ throw error;
1331
+ }
1319
1332
  const signer = new Signer({
1320
1333
  hostname: params.hostname,
1321
1334
  port: params.port,
@@ -0,0 +1,35 @@
1
+ // src/utils/module-loader.ts
2
+ var MISSING_MODULE_RE = /Cannot find (?:package|module) '([^']+)'/;
3
+ function isDriverNotInstalled(err, driver) {
4
+ if (!(err instanceof Error) || !("code" in err) || err.code !== "ERR_MODULE_NOT_FOUND") {
5
+ return false;
6
+ }
7
+ const match = err.message.match(MISSING_MODULE_RE);
8
+ if (!match) {
9
+ return false;
10
+ }
11
+ const missingSpecifier = match[1];
12
+ return missingSpecifier === driver || missingSpecifier.startsWith(`${driver}/`);
13
+ }
14
+ async function loadConnectors(connectorModules) {
15
+ await Promise.all(
16
+ connectorModules.map(async ({ load, name, driver }) => {
17
+ try {
18
+ await load();
19
+ } catch (err) {
20
+ if (isDriverNotInstalled(err, driver)) {
21
+ console.error(
22
+ `Skipping ${name} connector: driver package "${driver}" not installed.`
23
+ );
24
+ } else {
25
+ throw err;
26
+ }
27
+ }
28
+ })
29
+ );
30
+ }
31
+
32
+ export {
33
+ isDriverNotInstalled,
34
+ loadConnectors
35
+ };
@@ -0,0 +1,60 @@
1
+ // src/utils/multi-statement-result-parser.ts
2
+ function isMetadataObject(element) {
3
+ if (!element || typeof element !== "object" || Array.isArray(element)) {
4
+ return false;
5
+ }
6
+ return "affectedRows" in element || "insertId" in element || "fieldCount" in element || "warningStatus" in element;
7
+ }
8
+ function isMultiStatementResult(results) {
9
+ if (!Array.isArray(results) || results.length === 0) {
10
+ return false;
11
+ }
12
+ const firstElement = results[0];
13
+ return isMetadataObject(firstElement) || Array.isArray(firstElement);
14
+ }
15
+ function extractRowsFromMultiStatement(results) {
16
+ if (!Array.isArray(results)) {
17
+ return [];
18
+ }
19
+ const allRows = [];
20
+ for (const result of results) {
21
+ if (Array.isArray(result)) {
22
+ allRows.push(...result);
23
+ }
24
+ }
25
+ return allRows;
26
+ }
27
+ function extractAffectedRows(results) {
28
+ if (isMetadataObject(results)) {
29
+ return results.affectedRows || 0;
30
+ }
31
+ if (!Array.isArray(results)) {
32
+ return 0;
33
+ }
34
+ if (isMultiStatementResult(results)) {
35
+ let totalAffected = 0;
36
+ for (const result of results) {
37
+ if (isMetadataObject(result)) {
38
+ totalAffected += result.affectedRows || 0;
39
+ } else if (Array.isArray(result)) {
40
+ totalAffected += result.length;
41
+ }
42
+ }
43
+ return totalAffected;
44
+ }
45
+ return results.length;
46
+ }
47
+ function parseQueryResults(results) {
48
+ if (!Array.isArray(results)) {
49
+ return [];
50
+ }
51
+ if (isMultiStatementResult(results)) {
52
+ return extractRowsFromMultiStatement(results);
53
+ }
54
+ return results;
55
+ }
56
+
57
+ export {
58
+ extractAffectedRows,
59
+ parseQueryResults
60
+ };
@@ -1,5 +1,3 @@
1
- import "./chunk-WWAWV7DQ.js";
2
-
3
1
  // src/config/demo-loader.ts
4
2
  import fs from "fs";
5
3
  import path from "path";
package/dist/index.js CHANGED
@@ -12,7 +12,10 @@ import {
12
12
  resolveSourceConfigs,
13
13
  resolveTomlConfigPath,
14
14
  resolveTransport
15
- } from "./chunk-25VMLRAQ.js";
15
+ } from "./chunk-6JFMPX62.js";
16
+ import {
17
+ loadConnectors
18
+ } from "./chunk-GWPUVYLO.js";
16
19
  import {
17
20
  quoteQualifiedIdentifier
18
21
  } from "./chunk-JFWX35TB.js";
@@ -24,7 +27,6 @@ import {
24
27
  splitSQLStatements,
25
28
  stripCommentsAndStrings
26
29
  } from "./chunk-C7WEAPX4.js";
27
- import "./chunk-WWAWV7DQ.js";
28
30
 
29
31
  // src/server.ts
30
32
  import { McpServer } from "@modelcontextprotocol/sdk/server/mcp.js";
@@ -91,20 +93,76 @@ function createToolSuccessResponse(data, meta = {}) {
91
93
 
92
94
  // src/utils/allowed-keywords.ts
93
95
  var allowedKeywords = {
94
- postgres: ["select", "with", "explain", "analyze", "show"],
95
- mysql: ["select", "with", "explain", "analyze", "show", "describe", "desc"],
96
- mariadb: ["select", "with", "explain", "analyze", "show", "describe", "desc"],
97
- sqlite: ["select", "with", "explain", "analyze", "pragma"],
96
+ postgres: ["select", "with", "explain", "show"],
97
+ mysql: ["select", "with", "explain", "show", "describe", "desc"],
98
+ mariadb: ["select", "with", "explain", "show", "describe", "desc"],
99
+ sqlite: ["select", "with", "explain", "pragma"],
98
100
  sqlserver: ["select", "with", "explain", "showplan"]
99
101
  };
102
+ var mutatingKeywords = [
103
+ "insert",
104
+ "update",
105
+ "delete",
106
+ "drop",
107
+ "alter",
108
+ "create",
109
+ "truncate",
110
+ "merge",
111
+ "grant",
112
+ "revoke",
113
+ "rename"
114
+ ];
115
+ var mutatingPattern = new RegExp(
116
+ `\\b(?:${mutatingKeywords.join("|")})\\b`,
117
+ "i"
118
+ );
119
+ var mutatingPatternWithReplace = new RegExp(
120
+ `\\b(?:${mutatingKeywords.join("|")}|replace\\s+(?:(?:low_priority|delayed)\\s+)?into)\\b`,
121
+ "i"
122
+ );
123
+ var mutatingPatterns = {
124
+ postgres: mutatingPattern,
125
+ mysql: mutatingPatternWithReplace,
126
+ mariadb: mutatingPatternWithReplace,
127
+ sqlite: mutatingPatternWithReplace,
128
+ sqlserver: mutatingPattern
129
+ };
130
+ var selectIntoPattern = /\bselect\b[\s\S]+\binto\b/i;
131
+ var explainAnalyzePattern = /^explain\s+(?:\([^)]*\banalyze\b(?!\s*(?:=\s*)?(?:false|off|0)\b)[^)]*\)|\banalyze\b(?!\s*(?:=\s*)?(?:false|off|0)\b)(?:\s+verbose\b)?)/i;
100
132
  function isReadOnlySQL(sql, connectorType) {
101
- const cleanedSQL = stripCommentsAndStrings(sql, connectorType).trim().toLowerCase();
133
+ return checkReadOnly(
134
+ stripCommentsAndStrings(sql, connectorType).trim().toLowerCase(),
135
+ connectorType
136
+ );
137
+ }
138
+ function checkReadOnly(cleanedSQL, connectorType) {
102
139
  if (!cleanedSQL) {
103
140
  return false;
104
141
  }
105
- const firstWord = cleanedSQL.split(/\s+/)[0];
142
+ const firstWord = cleanedSQL.match(/\S+/)?.[0] ?? "";
106
143
  const keywordList = allowedKeywords[connectorType] || [];
107
- return keywordList.includes(firstWord);
144
+ if (!keywordList.includes(firstWord)) {
145
+ return false;
146
+ }
147
+ if (firstWord === "with") {
148
+ const pattern = mutatingPatterns[connectorType] ?? mutatingPattern;
149
+ if (pattern.test(cleanedSQL)) {
150
+ return false;
151
+ }
152
+ }
153
+ if ((firstWord === "select" || firstWord === "with") && selectIntoPattern.test(cleanedSQL)) {
154
+ return false;
155
+ }
156
+ if (firstWord === "explain") {
157
+ const m = explainAnalyzePattern.exec(cleanedSQL);
158
+ if (m) {
159
+ const afterExplain = cleanedSQL.slice(m[0].length).trim();
160
+ if (afterExplain && !checkReadOnly(afterExplain, connectorType)) {
161
+ return false;
162
+ }
163
+ }
164
+ }
165
+ return true;
108
166
  }
109
167
 
110
168
  // src/requests/store.ts
@@ -1363,7 +1421,7 @@ See documentation for more details on configuring database connections.
1363
1421
  const sources = sourceConfigsData.sources;
1364
1422
  console.error(`Configuration source: ${sourceConfigsData.source}`);
1365
1423
  await connectorManager.connectWithSources(sources);
1366
- const { initializeToolRegistry: initializeToolRegistry2 } = await import("./registry-FOASCI6Y.js");
1424
+ const { initializeToolRegistry: initializeToolRegistry2 } = await import("./registry-I2JQWFDX.js");
1367
1425
  initializeToolRegistry2({
1368
1426
  sources: sourceConfigsData.sources,
1369
1427
  tools: sourceConfigsData.tools
@@ -1489,44 +1547,13 @@ See documentation for more details on configuring database connections.
1489
1547
  }
1490
1548
  }
1491
1549
 
1492
- // src/utils/module-loader.ts
1493
- var MISSING_MODULE_RE = /Cannot find (?:package|module) '([^']+)'/;
1494
- function isDriverNotInstalled(err, driver) {
1495
- if (!(err instanceof Error) || !("code" in err) || err.code !== "ERR_MODULE_NOT_FOUND") {
1496
- return false;
1497
- }
1498
- const match = err.message.match(MISSING_MODULE_RE);
1499
- if (!match) {
1500
- return false;
1501
- }
1502
- const missingSpecifier = match[1];
1503
- return missingSpecifier === driver || missingSpecifier.startsWith(`${driver}/`);
1504
- }
1505
- async function loadConnectors(connectorModules2) {
1506
- await Promise.all(
1507
- connectorModules2.map(async ({ load, name, driver }) => {
1508
- try {
1509
- await load();
1510
- } catch (err) {
1511
- if (isDriverNotInstalled(err, driver)) {
1512
- console.error(
1513
- `Skipping ${name} connector: driver package "${driver}" not installed.`
1514
- );
1515
- } else {
1516
- throw err;
1517
- }
1518
- }
1519
- })
1520
- );
1521
- }
1522
-
1523
1550
  // src/index.ts
1524
1551
  var connectorModules = [
1525
- { load: () => import("./postgres-JB3LPXGR.js"), name: "PostgreSQL", driver: "pg" },
1526
- { load: () => import("./sqlserver-LGFLHJHL.js"), name: "SQL Server", driver: "mssql" },
1527
- { load: () => import("./sqlite-5LT56F5B.js"), name: "SQLite", driver: "better-sqlite3" },
1528
- { load: () => import("./mysql-FOCVUTPX.js"), name: "MySQL", driver: "mysql2" },
1529
- { load: () => import("./mariadb-L3YMONWJ.js"), name: "MariaDB", driver: "mariadb" }
1552
+ { load: () => import("./postgres-4PCNQDGV.js"), name: "PostgreSQL", driver: "pg" },
1553
+ { load: () => import("./sqlserver-JBR6X37Z.js"), name: "SQL Server", driver: "mssql" },
1554
+ { load: () => import("./sqlite-FSCLCRIH.js"), name: "SQLite", driver: "better-sqlite3" },
1555
+ { load: () => import("./mysql-I35IQ2GH.js"), name: "MySQL", driver: "mysql2" },
1556
+ { load: () => import("./mariadb-VGTS4WXE.js"), name: "MariaDB", driver: "mariadb" }
1530
1557
  ];
1531
1558
  loadConnectors(connectorModules).then(() => main()).catch((error) => {
1532
1559
  console.error("Fatal error:", error);