@bytebase/dbhub 1.4.0 → 1.5.0

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/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@bytebase/dbhub",
3
- "version": "1.4.0",
3
+ "version": "1.5.0",
4
4
  "mcpName": "io.github.bytebase/dbhub",
5
5
  "description": "Token-efficient database MCP server for PostgreSQL, MySQL, MariaDB, SQL Server, Oracle, SQLite",
6
6
  "repository": {
@@ -1,179 +0,0 @@
1
- import {
2
- computeHitRatioPct,
3
- toNullableNumber
4
- } from "./chunk-FU2ZJE4E.js";
5
- import {
6
- obfuscateDSNPassword
7
- } from "./chunk-IXC4NQGC.js";
8
-
9
- // src/connectors/mysql-family-health-check.ts
10
- async function getMySQLFamilyHealthCheck(query) {
11
- const [processlistRows, maxConnRows, bufferPoolRows, trxOutcome] = await Promise.all([
12
- query(`
13
- SELECT
14
- COUNT(*) AS total,
15
- SUM(CASE WHEN COMMAND != 'Sleep' THEN 1 ELSE 0 END) AS active,
16
- SUM(CASE WHEN COMMAND = 'Sleep' THEN 1 ELSE 0 END) AS idle,
17
- MAX(CASE WHEN COMMAND != 'Sleep' THEN TIME ELSE NULL END) AS longest_active_query_seconds
18
- FROM information_schema.PROCESSLIST
19
- WHERE ID <> CONNECTION_ID()
20
- `),
21
- query(`SHOW VARIABLES LIKE 'max_connections'`),
22
- query(`
23
- SHOW GLOBAL STATUS WHERE Variable_name IN ('Innodb_buffer_pool_read_requests', 'Innodb_buffer_pool_reads')
24
- `),
25
- // Joining PROCESSLIST against INNODB_TRX to detect idle-in-transaction
26
- // sessions requires the PROCESS privilege - unlike a plain PROCESSLIST
27
- // select, which silently narrows to the caller's own session instead of
28
- // erroring. Run concurrently with the other queries above, but resolve
29
- // rather than reject on failure so it degrades instead of failing the
30
- // whole health check.
31
- query(`
32
- SELECT
33
- COUNT(*) AS idle_in_transaction,
34
- MAX(TIMESTAMPDIFF(SECOND, t.trx_started, NOW())) AS longest_idle_in_transaction_seconds
35
- FROM information_schema.INNODB_TRX t
36
- JOIN information_schema.PROCESSLIST p ON p.ID = t.trx_mysql_thread_id
37
- WHERE p.COMMAND = 'Sleep'
38
- `).then(
39
- (rows) => ({ ok: true, rows }),
40
- () => ({ ok: false })
41
- )
42
- ]);
43
- const notes = [];
44
- let idleInTransaction = 0;
45
- let longestIdleInTransactionSeconds = null;
46
- if (trxOutcome.ok) {
47
- idleInTransaction = Number(trxOutcome.rows[0].idle_in_transaction);
48
- longestIdleInTransactionSeconds = toNullableNumber(trxOutcome.rows[0].longest_idle_in_transaction_seconds);
49
- } else {
50
- notes.push(
51
- "Connected user lacks the PROCESS privilege: PROCESSLIST visibility is restricted to the connecting user's own sessions, and this diagnostic session is excluded from the count, so connection counts and active-query duration may under-report (down to 0). Idle-in-transaction detection is unavailable."
52
- );
53
- }
54
- const proc = processlistRows[0];
55
- const bufferPoolStats = Object.fromEntries(bufferPoolRows.map((row) => [row.Variable_name, row.Value]));
56
- const readRequests = Number(bufferPoolStats.Innodb_buffer_pool_read_requests ?? 0);
57
- const reads = Number(bufferPoolStats.Innodb_buffer_pool_reads ?? 0);
58
- return {
59
- connections: {
60
- total: Number(proc.total),
61
- active: Number(proc.active),
62
- idle: Number(proc.idle),
63
- idleInTransaction,
64
- // MySQL/MariaDB's InnoDB has no equivalent of Postgres's "idle in
65
- // transaction (aborted)" state - a failed statement doesn't leave the
66
- // session's transaction in a distinct aborted-but-open state the way
67
- // Postgres does - so idleInTransactionAborted is left undefined.
68
- maxConnections: maxConnRows.length > 0 ? Number(maxConnRows[0].Value) : null,
69
- longestIdleInTransactionSeconds,
70
- longestActiveQuerySeconds: toNullableNumber(proc.longest_active_query_seconds)
71
- },
72
- bufferCache: {
73
- hitRatioPct: computeHitRatioPct(readRequests, reads),
74
- blocksHit: readRequests - reads,
75
- blocksRead: reads
76
- },
77
- ...notes.length > 0 ? { notes } : {}
78
- };
79
- }
80
-
81
- // src/utils/dsn-database.ts
82
- var MissingDatabaseError = class extends Error {
83
- constructor(message) {
84
- super(message);
85
- this.name = "MissingDatabaseError";
86
- }
87
- };
88
- function requireDatabaseInDSN(database, dsn, label) {
89
- if (database) {
90
- return;
91
- }
92
- throw new MissingDatabaseError(
93
- `${label} DSN must name a database.
94
- Provided: ${obfuscateDSNPassword(dsn)}
95
- Add the database to the DSN, e.g. ...:3306/mydb
96
- To work with several databases, define one [[sources]] entry per database in a TOML config file: https://dbhub.ai/config/toml`
97
- );
98
- }
99
-
100
- // src/utils/multi-statement-result-parser.ts
101
- function isMetadataObject(element) {
102
- if (!element || typeof element !== "object" || Array.isArray(element)) {
103
- return false;
104
- }
105
- return "affectedRows" in element || "insertId" in element || "fieldCount" in element || "warningStatus" in element;
106
- }
107
- function isMultiStatementResult(results) {
108
- if (!Array.isArray(results) || results.length === 0) {
109
- return false;
110
- }
111
- const firstElement = results[0];
112
- return isMetadataObject(firstElement) || Array.isArray(firstElement);
113
- }
114
- function buildResultSetsFromMultiStatement(results, statementTexts) {
115
- const sql = statementTexts?.length === results.length ? statementTexts : void 0;
116
- return results.map((result, index) => {
117
- if (Array.isArray(result)) {
118
- return { sql: sql?.[index], rows: result, rowCount: result.length };
119
- }
120
- if (isMetadataObject(result)) {
121
- return { sql: sql?.[index], rows: [], rowCount: result.affectedRows || 0 };
122
- }
123
- return { sql: sql?.[index], rows: [], rowCount: 0 };
124
- });
125
- }
126
- function parseQueryResultSets(results, statementTexts) {
127
- const singleStatementSql = statementTexts?.length === 1 ? statementTexts[0] : void 0;
128
- if (isMetadataObject(results)) {
129
- return [{ sql: singleStatementSql, rows: [], rowCount: results.affectedRows || 0 }];
130
- }
131
- if (!Array.isArray(results)) {
132
- return [{ sql: singleStatementSql, rows: [], rowCount: 0 }];
133
- }
134
- if (isMultiStatementResult(results)) {
135
- return buildResultSetsFromMultiStatement(results, statementTexts);
136
- }
137
- return [{ sql: singleStatementSql, rows: results, rowCount: results.length }];
138
- }
139
-
140
- // src/utils/readonly-transaction.ts
141
- function isClientSideTimeout(error) {
142
- return typeof error === "object" && error !== null && error.code === "PROTOCOL_SEQUENCE_TIMEOUT";
143
- }
144
- async function withReadOnlyTransaction(conn, readonly, supportsReadOnlyTransaction, execute) {
145
- if (!readonly) {
146
- return execute();
147
- }
148
- try {
149
- await conn.query(
150
- supportsReadOnlyTransaction ? "START TRANSACTION READ ONLY" : "START TRANSACTION"
151
- );
152
- const result = await execute();
153
- await conn.query(supportsReadOnlyTransaction ? "COMMIT" : "ROLLBACK");
154
- return result;
155
- } catch (error) {
156
- if (!isClientSideTimeout(error)) {
157
- try {
158
- await conn.query("ROLLBACK");
159
- } catch {
160
- }
161
- }
162
- throw error;
163
- }
164
- }
165
-
166
- // src/utils/server-flavor.ts
167
- function isTiDBVersion(version) {
168
- return typeof version === "string" && /tidb/i.test(version);
169
- }
170
-
171
- export {
172
- getMySQLFamilyHealthCheck,
173
- MissingDatabaseError,
174
- requireDatabaseInDSN,
175
- parseQueryResultSets,
176
- isClientSideTimeout,
177
- withReadOnlyTransaction,
178
- isTiDBVersion
179
- };