@berthojoris/mcp-mysql-server 1.4.15 → 1.6.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.
@@ -21,59 +21,68 @@ var ToolCategory;
21
21
  ToolCategory["DDL"] = "ddl";
22
22
  ToolCategory["UTILITY"] = "utility";
23
23
  ToolCategory["TRANSACTION"] = "transaction";
24
- ToolCategory["PROCEDURE"] = "procedure"; // Stored procedure operations (CREATE, EXECUTE, DROP procedures)
24
+ ToolCategory["PROCEDURE"] = "procedure";
25
25
  })(ToolCategory || (exports.ToolCategory = ToolCategory = {}));
26
26
  /**
27
27
  * Map of tool names to their categories
28
28
  */
29
29
  exports.toolCategoryMap = {
30
30
  // Database tools
31
- 'listDatabases': ToolCategory.LIST,
32
- 'listTables': ToolCategory.LIST,
33
- 'readTableSchema': ToolCategory.LIST,
31
+ listDatabases: ToolCategory.LIST,
32
+ listTables: ToolCategory.LIST,
33
+ readTableSchema: ToolCategory.LIST,
34
34
  // CRUD tools
35
- 'createRecord': ToolCategory.CREATE,
36
- 'readRecords': ToolCategory.READ,
37
- 'updateRecord': ToolCategory.UPDATE,
38
- 'deleteRecord': ToolCategory.DELETE,
35
+ createRecord: ToolCategory.CREATE,
36
+ readRecords: ToolCategory.READ,
37
+ updateRecord: ToolCategory.UPDATE,
38
+ deleteRecord: ToolCategory.DELETE,
39
39
  // Bulk operations
40
- 'bulkInsert': ToolCategory.CREATE,
41
- 'bulkUpdate': ToolCategory.UPDATE,
42
- 'bulkDelete': ToolCategory.DELETE,
40
+ bulkInsert: ToolCategory.CREATE,
41
+ bulkUpdate: ToolCategory.UPDATE,
42
+ bulkDelete: ToolCategory.DELETE,
43
43
  // Query tools
44
- 'runQuery': ToolCategory.READ,
45
- 'executeSql': ToolCategory.EXECUTE,
44
+ runQuery: ToolCategory.READ,
45
+ executeSql: ToolCategory.EXECUTE,
46
46
  // DDL tools
47
- 'createTable': ToolCategory.DDL,
48
- 'alterTable': ToolCategory.DDL,
49
- 'dropTable': ToolCategory.DDL,
50
- 'executeDdl': ToolCategory.DDL,
47
+ createTable: ToolCategory.DDL,
48
+ alterTable: ToolCategory.DDL,
49
+ dropTable: ToolCategory.DDL,
50
+ executeDdl: ToolCategory.DDL,
51
51
  // Utility tools
52
- 'describeConnection': ToolCategory.UTILITY,
53
- 'testConnection': ToolCategory.UTILITY,
54
- 'getTableRelationships': ToolCategory.UTILITY,
55
- 'exportTableToCSV': ToolCategory.UTILITY,
56
- 'exportQueryToCSV': ToolCategory.UTILITY,
52
+ describeConnection: ToolCategory.UTILITY,
53
+ testConnection: ToolCategory.UTILITY,
54
+ getTableRelationships: ToolCategory.UTILITY,
55
+ exportTableToCSV: ToolCategory.UTILITY,
56
+ exportQueryToCSV: ToolCategory.UTILITY,
57
57
  // Transaction tools
58
- 'beginTransaction': ToolCategory.TRANSACTION,
59
- 'commitTransaction': ToolCategory.TRANSACTION,
60
- 'rollbackTransaction': ToolCategory.TRANSACTION,
61
- 'getTransactionStatus': ToolCategory.TRANSACTION,
62
- 'executeInTransaction': ToolCategory.TRANSACTION,
58
+ beginTransaction: ToolCategory.TRANSACTION,
59
+ commitTransaction: ToolCategory.TRANSACTION,
60
+ rollbackTransaction: ToolCategory.TRANSACTION,
61
+ getTransactionStatus: ToolCategory.TRANSACTION,
62
+ executeInTransaction: ToolCategory.TRANSACTION,
63
63
  // Stored procedure tools
64
- 'listStoredProcedures': ToolCategory.LIST,
65
- 'getStoredProcedureInfo': ToolCategory.LIST,
66
- 'executeStoredProcedure': ToolCategory.PROCEDURE,
67
- 'createStoredProcedure': ToolCategory.PROCEDURE,
68
- 'dropStoredProcedure': ToolCategory.PROCEDURE,
69
- 'showCreateProcedure': ToolCategory.LIST
64
+ listStoredProcedures: ToolCategory.LIST,
65
+ getStoredProcedureInfo: ToolCategory.LIST,
66
+ executeStoredProcedure: ToolCategory.PROCEDURE,
67
+ createStoredProcedure: ToolCategory.PROCEDURE,
68
+ dropStoredProcedure: ToolCategory.PROCEDURE,
69
+ showCreateProcedure: ToolCategory.LIST,
70
+ // Cache management tools
71
+ getCacheStats: ToolCategory.UTILITY,
72
+ getCacheConfig: ToolCategory.UTILITY,
73
+ configureCacheSettings: ToolCategory.UTILITY,
74
+ clearCache: ToolCategory.UTILITY,
75
+ invalidateCacheForTable: ToolCategory.UTILITY,
76
+ // Query optimization tools
77
+ analyzeQuery: ToolCategory.UTILITY,
78
+ getOptimizationHints: ToolCategory.UTILITY,
70
79
  };
71
80
  /**
72
81
  * Class to manage feature configuration based on runtime or environment variables
73
82
  */
74
83
  class FeatureConfig {
75
84
  constructor(configStr) {
76
- this.originalConfigString = configStr || process.env.MCP_CONFIG || '';
85
+ this.originalConfigString = configStr || process.env.MCP_CONFIG || "";
77
86
  this.enabledCategories = this.parseConfig(configStr);
78
87
  }
79
88
  /**
@@ -81,14 +90,14 @@ class FeatureConfig {
81
90
  */
82
91
  parseConfig(configStr) {
83
92
  // Priority: 1. Provided config, 2. Environment variable, 3. Enable all
84
- const config = configStr || process.env.MCP_CONFIG || '';
93
+ const config = configStr || process.env.MCP_CONFIG || "";
85
94
  // If config is empty, enable all features
86
95
  if (!config.trim()) {
87
96
  return new Set(Object.values(ToolCategory));
88
97
  }
89
98
  // Parse comma-separated list
90
- const categories = config.split(',').map(c => c.trim().toLowerCase());
91
- const validCategories = categories.filter(c => Object.values(ToolCategory).includes(c));
99
+ const categories = config.split(",").map((c) => c.trim().toLowerCase());
100
+ const validCategories = categories.filter((c) => Object.values(ToolCategory).includes(c));
92
101
  return new Set(validCategories);
93
102
  }
94
103
  /**
@@ -119,47 +128,49 @@ class FeatureConfig {
119
128
  return `Unknown tool '${toolName}'. This tool is not recognized by the MCP server.`;
120
129
  }
121
130
  const isAllPermissions = !this.originalConfigString.trim();
122
- const currentPermissions = isAllPermissions ? 'all' : this.originalConfigString;
131
+ const currentPermissions = isAllPermissions
132
+ ? "all"
133
+ : this.originalConfigString;
123
134
  const actionDescriptions = {
124
- [ToolCategory.LIST]: 'list databases and tables',
125
- [ToolCategory.READ]: 'read data from tables',
126
- [ToolCategory.CREATE]: 'create new records',
127
- [ToolCategory.UPDATE]: 'update existing records',
128
- [ToolCategory.DELETE]: 'delete records',
129
- [ToolCategory.EXECUTE]: 'execute custom SQL queries',
130
- [ToolCategory.DDL]: 'create, alter, or drop tables (schema changes)',
131
- [ToolCategory.UTILITY]: 'use utility functions',
132
- [ToolCategory.TRANSACTION]: 'manage database transactions',
133
- [ToolCategory.PROCEDURE]: 'manage stored procedures'
135
+ [ToolCategory.LIST]: "list databases and tables",
136
+ [ToolCategory.READ]: "read data from tables",
137
+ [ToolCategory.CREATE]: "create new records",
138
+ [ToolCategory.UPDATE]: "update existing records",
139
+ [ToolCategory.DELETE]: "delete records",
140
+ [ToolCategory.EXECUTE]: "execute custom SQL queries",
141
+ [ToolCategory.DDL]: "create, alter, or drop tables (schema changes)",
142
+ [ToolCategory.UTILITY]: "use utility functions",
143
+ [ToolCategory.TRANSACTION]: "manage database transactions",
144
+ [ToolCategory.PROCEDURE]: "manage stored procedures",
134
145
  };
135
146
  const toolDescriptions = {
136
- 'createTable': 'create new tables',
137
- 'alterTable': 'modify table structure',
138
- 'dropTable': 'delete tables',
139
- 'executeDdl': 'execute DDL statements',
140
- 'createRecord': 'insert new records',
141
- 'updateRecord': 'update existing records',
142
- 'deleteRecord': 'delete records',
143
- 'bulkInsert': 'insert multiple records in batches',
144
- 'bulkUpdate': 'update multiple records in batches',
145
- 'bulkDelete': 'delete multiple records in batches',
146
- 'executeSql': 'execute custom SQL statements',
147
- 'runQuery': 'run SELECT queries',
148
- 'beginTransaction': 'start database transactions',
149
- 'commitTransaction': 'commit database transactions',
150
- 'rollbackTransaction': 'rollback database transactions',
151
- 'executeInTransaction': 'execute queries within transactions',
152
- 'createStoredProcedure': 'create stored procedures',
153
- 'dropStoredProcedure': 'delete stored procedures',
154
- 'executeStoredProcedure': 'execute stored procedures',
155
- 'exportTableToCSV': 'export table data to CSV',
156
- 'exportQueryToCSV': 'export query results to CSV'
147
+ createTable: "create new tables",
148
+ alterTable: "modify table structure",
149
+ dropTable: "delete tables",
150
+ executeDdl: "execute DDL statements",
151
+ createRecord: "insert new records",
152
+ updateRecord: "update existing records",
153
+ deleteRecord: "delete records",
154
+ bulkInsert: "insert multiple records in batches",
155
+ bulkUpdate: "update multiple records in batches",
156
+ bulkDelete: "delete multiple records in batches",
157
+ executeSql: "execute custom SQL statements",
158
+ runQuery: "run SELECT queries",
159
+ beginTransaction: "start database transactions",
160
+ commitTransaction: "commit database transactions",
161
+ rollbackTransaction: "rollback database transactions",
162
+ executeInTransaction: "execute queries within transactions",
163
+ createStoredProcedure: "create stored procedures",
164
+ dropStoredProcedure: "delete stored procedures",
165
+ executeStoredProcedure: "execute stored procedures",
166
+ exportTableToCSV: "export table data to CSV",
167
+ exportQueryToCSV: "export query results to CSV",
157
168
  };
158
169
  const toolDescription = toolDescriptions[toolName] || actionDescriptions[category];
159
170
  const requiredPermission = category;
160
- return `Permission denied: Cannot ${toolDescription}. ` +
171
+ return (`Permission denied: Cannot ${toolDescription}. ` +
161
172
  `This action requires '${requiredPermission}' permission, but your current MCP configuration only allows: ${currentPermissions}. ` +
162
- `To enable this feature, update your MCP server configuration to include '${requiredPermission}' in the permissions list.`;
173
+ `To enable this feature, update your MCP server configuration to include '${requiredPermission}' in the permissions list.`);
163
174
  }
164
175
  /**
165
176
  * Check if a category is enabled
@@ -1,12 +1,17 @@
1
- import mysql from 'mysql2/promise';
1
+ import mysql from "mysql2/promise";
2
2
  declare class DatabaseConnection {
3
3
  private static instance;
4
4
  private pool;
5
5
  private activeTransactions;
6
+ private queryCache;
6
7
  private constructor();
7
8
  static getInstance(): DatabaseConnection;
8
9
  getConnection(): Promise<mysql.PoolConnection>;
9
- query<T>(sql: string, params?: any[]): Promise<T>;
10
+ query<T>(sql: string, params?: any[], useCache?: boolean): Promise<T>;
11
+ /**
12
+ * Invalidate cache entries when write operations occur
13
+ */
14
+ private invalidateCacheForWriteOperation;
10
15
  testConnection(): Promise<{
11
16
  connected: boolean;
12
17
  latency: number;
@@ -20,6 +25,20 @@ declare class DatabaseConnection {
20
25
  getQueryLogs(): import("./queryLogger").QueryLog[];
21
26
  getLastQueryLog(): import("./queryLogger").QueryLog | undefined;
22
27
  getFormattedQueryLogs(count?: number): string;
28
+ getCacheStats(): import("../cache/queryCache").CacheStats;
29
+ getCacheConfig(): import("../cache/queryCache").CacheConfig;
30
+ setCacheConfig(config: {
31
+ enabled?: boolean;
32
+ ttlMs?: number;
33
+ maxSize?: number;
34
+ maxMemoryMB?: number;
35
+ }): void;
36
+ clearCache(): number;
37
+ invalidateCache(pattern?: string | RegExp): number;
38
+ invalidateCacheForTable(tableName: string): number;
39
+ enableCache(): void;
40
+ disableCache(): void;
41
+ resetCacheStats(): void;
23
42
  executeInTransaction<T>(transactionId: string, sql: string, params?: any[]): Promise<T>;
24
43
  }
25
44
  export default DatabaseConnection;
@@ -6,6 +6,7 @@ Object.defineProperty(exports, "__esModule", { value: true });
6
6
  const promise_1 = __importDefault(require("mysql2/promise"));
7
7
  const config_1 = require("../config/config");
8
8
  const queryLogger_1 = require("./queryLogger");
9
+ const queryCache_1 = require("../cache/queryCache");
9
10
  class DatabaseConnection {
10
11
  constructor() {
11
12
  this.pool = promise_1.default.createPool({
@@ -16,9 +17,10 @@ class DatabaseConnection {
16
17
  database: config_1.dbConfig.database,
17
18
  waitForConnections: true,
18
19
  connectionLimit: 10,
19
- queueLimit: 0
20
+ queueLimit: 0,
20
21
  });
21
22
  this.activeTransactions = new Map();
23
+ this.queryCache = queryCache_1.QueryCache.getInstance();
22
24
  }
23
25
  static getInstance() {
24
26
  if (!DatabaseConnection.instance) {
@@ -34,20 +36,54 @@ class DatabaseConnection {
34
36
  throw new Error(`Failed to get database connection: ${error}`);
35
37
  }
36
38
  }
37
- async query(sql, params) {
39
+ async query(sql, params, useCache = true) {
40
+ const normalizedSql = sql.trim().toUpperCase();
41
+ const isSelectQuery = normalizedSql.startsWith("SELECT");
42
+ // Check cache for SELECT queries
43
+ if (useCache && isSelectQuery) {
44
+ const cachedEntry = this.queryCache.get(sql, params);
45
+ if (cachedEntry) {
46
+ // Log cache hit
47
+ queryLogger_1.QueryLogger.log(sql, params, 0, "success", undefined, true);
48
+ return cachedEntry.data;
49
+ }
50
+ }
38
51
  const startTime = Date.now();
39
52
  try {
40
53
  const [results] = await this.pool.query(sql, params);
41
54
  const duration = Date.now() - startTime;
42
- queryLogger_1.QueryLogger.log(sql, params, duration, 'success');
55
+ queryLogger_1.QueryLogger.log(sql, params, duration, "success");
56
+ // Cache SELECT query results
57
+ if (useCache && isSelectQuery) {
58
+ this.queryCache.set(sql, params, results);
59
+ }
60
+ // Invalidate cache for write operations
61
+ if (!isSelectQuery) {
62
+ this.invalidateCacheForWriteOperation(sql);
63
+ }
43
64
  return results;
44
65
  }
45
66
  catch (error) {
46
67
  const duration = Date.now() - startTime;
47
- queryLogger_1.QueryLogger.log(sql, params, duration, 'error', error.message);
68
+ queryLogger_1.QueryLogger.log(sql, params, duration, "error", error.message);
48
69
  throw new Error(`Query execution failed: ${error}`);
49
70
  }
50
71
  }
72
+ /**
73
+ * Invalidate cache entries when write operations occur
74
+ */
75
+ invalidateCacheForWriteOperation(sql) {
76
+ // Extract table name from INSERT, UPDATE, DELETE statements
77
+ const insertMatch = sql.match(/INSERT\s+INTO\s+[\`"']?(\w+)[\`"']?/i);
78
+ const updateMatch = sql.match(/UPDATE\s+[\`"']?(\w+)[\`"']?/i);
79
+ const deleteMatch = sql.match(/DELETE\s+FROM\s+[\`"']?(\w+)[\`"']?/i);
80
+ const truncateMatch = sql.match(/TRUNCATE\s+(?:TABLE\s+)?[\`"']?(\w+)[\`"']?/i);
81
+ const dropMatch = sql.match(/DROP\s+TABLE\s+(?:IF\s+EXISTS\s+)?[\`"']?(\w+)[\`"']?/i);
82
+ const match = insertMatch || updateMatch || deleteMatch || truncateMatch || dropMatch;
83
+ if (match && match[1]) {
84
+ this.queryCache.invalidateTable(match[1]);
85
+ }
86
+ }
51
87
  async testConnection() {
52
88
  const startTime = Date.now();
53
89
  try {
@@ -96,7 +132,7 @@ class DatabaseConnection {
96
132
  connection.release();
97
133
  }
98
134
  catch (rollbackError) {
99
- console.error('Failed to rollback after commit error:', rollbackError);
135
+ console.error("Failed to rollback after commit error:", rollbackError);
100
136
  }
101
137
  this.activeTransactions.delete(transactionId);
102
138
  throw new Error(`Failed to commit transaction: ${error}`);
@@ -133,6 +169,36 @@ class DatabaseConnection {
133
169
  getFormattedQueryLogs(count = 1) {
134
170
  return queryLogger_1.QueryLogger.formatLogs(queryLogger_1.QueryLogger.getLastLogs(count));
135
171
  }
172
+ // Cache Management Methods
173
+ getCacheStats() {
174
+ return this.queryCache.getStats();
175
+ }
176
+ getCacheConfig() {
177
+ return this.queryCache.getConfig();
178
+ }
179
+ setCacheConfig(config) {
180
+ this.queryCache.setConfig(config);
181
+ }
182
+ clearCache() {
183
+ const previousSize = this.queryCache.getStats().currentSize;
184
+ this.queryCache.clear();
185
+ return previousSize;
186
+ }
187
+ invalidateCache(pattern) {
188
+ return this.queryCache.invalidate(pattern);
189
+ }
190
+ invalidateCacheForTable(tableName) {
191
+ return this.queryCache.invalidateTable(tableName);
192
+ }
193
+ enableCache() {
194
+ this.queryCache.enable();
195
+ }
196
+ disableCache() {
197
+ this.queryCache.disable();
198
+ }
199
+ resetCacheStats() {
200
+ this.queryCache.resetStats();
201
+ }
136
202
  async executeInTransaction(transactionId, sql, params) {
137
203
  const connection = this.activeTransactions.get(transactionId);
138
204
  if (!connection) {
@@ -142,12 +208,12 @@ class DatabaseConnection {
142
208
  try {
143
209
  const [results] = await connection.query(sql, params);
144
210
  const duration = Date.now() - startTime;
145
- queryLogger_1.QueryLogger.log(sql, params, duration, 'success');
211
+ queryLogger_1.QueryLogger.log(sql, params, duration, "success");
146
212
  return results;
147
213
  }
148
214
  catch (error) {
149
215
  const duration = Date.now() - startTime;
150
- queryLogger_1.QueryLogger.log(sql, params, duration, 'error', error.message);
216
+ queryLogger_1.QueryLogger.log(sql, params, duration, "error", error.message);
151
217
  throw new Error(`Query execution in transaction failed: ${error}`);
152
218
  }
153
219
  }
@@ -3,8 +3,9 @@ export interface QueryLog {
3
3
  params?: any[];
4
4
  duration: number;
5
5
  timestamp: string;
6
- status: 'success' | 'error';
6
+ status: "success" | "error";
7
7
  error?: string;
8
+ cacheHit?: boolean;
8
9
  }
9
10
  export declare class QueryLogger {
10
11
  private static logs;
@@ -27,7 +28,7 @@ export declare class QueryLogger {
27
28
  /**
28
29
  * Log a query execution
29
30
  */
30
- static log(sql: string, params: any[] | undefined, duration: number, status: 'success' | 'error', error?: string): void;
31
+ static log(sql: string, params: any[] | undefined, duration: number, status: "success" | "error", error?: string, cacheHit?: boolean): void;
31
32
  /**
32
33
  * Get all logged queries (returns shallow copy of array)
33
34
  */
@@ -8,34 +8,38 @@ class QueryLogger {
8
8
  static safeStringify(value, maxLength = 100) {
9
9
  try {
10
10
  if (value === null)
11
- return 'null';
11
+ return "null";
12
12
  if (value === undefined)
13
- return 'undefined';
14
- if (typeof value === 'string') {
15
- return value.length > maxLength ? value.substring(0, maxLength) + '...' : value;
13
+ return "undefined";
14
+ if (typeof value === "string") {
15
+ return value.length > maxLength
16
+ ? value.substring(0, maxLength) + "..."
17
+ : value;
16
18
  }
17
- if (typeof value === 'number' || typeof value === 'boolean') {
19
+ if (typeof value === "number" || typeof value === "boolean") {
18
20
  return String(value);
19
21
  }
20
- if (typeof value === 'bigint') {
21
- return value.toString() + 'n';
22
+ if (typeof value === "bigint") {
23
+ return value.toString() + "n";
22
24
  }
23
25
  if (Array.isArray(value)) {
24
26
  if (value.length === 0)
25
- return '[]';
26
- const items = value.slice(0, 3).map(v => this.safeStringify(v, 30));
27
+ return "[]";
28
+ const items = value.slice(0, 3).map((v) => this.safeStringify(v, 30));
27
29
  return value.length > 3
28
- ? `[${items.join(', ')}, ... +${value.length - 3} more]`
29
- : `[${items.join(', ')}]`;
30
+ ? `[${items.join(", ")}, ... +${value.length - 3} more]`
31
+ : `[${items.join(", ")}]`;
30
32
  }
31
- if (typeof value === 'object') {
33
+ if (typeof value === "object") {
32
34
  const str = JSON.stringify(value);
33
- return str.length > maxLength ? str.substring(0, maxLength) + '...}' : str;
35
+ return str.length > maxLength
36
+ ? str.substring(0, maxLength) + "...}"
37
+ : str;
34
38
  }
35
39
  return String(value);
36
40
  }
37
41
  catch (error) {
38
- return '[Unstringifiable]';
42
+ return "[Unstringifiable]";
39
43
  }
40
44
  }
41
45
  /**
@@ -44,7 +48,8 @@ class QueryLogger {
44
48
  static truncateSQL(sql) {
45
49
  if (sql.length <= this.MAX_SQL_LENGTH)
46
50
  return sql;
47
- return sql.substring(0, this.MAX_SQL_LENGTH) + `... [truncated ${sql.length - this.MAX_SQL_LENGTH} chars]`;
51
+ return (sql.substring(0, this.MAX_SQL_LENGTH) +
52
+ `... [truncated ${sql.length - this.MAX_SQL_LENGTH} chars]`);
48
53
  }
49
54
  /**
50
55
  * Create a memory-safe copy of parameters
@@ -60,20 +65,25 @@ class QueryLogger {
60
65
  }
61
66
  catch (error) {
62
67
  // If JSON serialization fails, create safe string representations
63
- return limitedParams.map(p => this.safeStringify(p, 50));
68
+ return limitedParams.map((p) => this.safeStringify(p, 50));
64
69
  }
65
70
  }
66
71
  /**
67
72
  * Log a query execution
68
73
  */
69
- static log(sql, params, duration, status, error) {
74
+ static log(sql, params, duration, status, error, cacheHit) {
70
75
  const log = {
71
76
  sql: this.truncateSQL(sql),
72
77
  params: this.sanitizeParams(params),
73
78
  duration,
74
79
  timestamp: new Date().toISOString(),
75
80
  status,
76
- error: error ? (error.length > 200 ? error.substring(0, 200) + '...' : error) : undefined
81
+ cacheHit: cacheHit,
82
+ error: error
83
+ ? error.length > 200
84
+ ? error.substring(0, 200) + "..."
85
+ : error
86
+ : undefined,
77
87
  };
78
88
  this.logs.push(log);
79
89
  // Keep only the last MAX_LOGS entries
@@ -113,9 +123,9 @@ class QueryLogger {
113
123
  static formatSQL(sql) {
114
124
  // Add line breaks for better readability
115
125
  return sql
116
- .replace(/\s+/g, ' ') // Normalize whitespace
117
- .replace(/\b(SELECT|FROM|WHERE|JOIN|LEFT JOIN|RIGHT JOIN|INNER JOIN|OUTER JOIN|GROUP BY|ORDER BY|HAVING|LIMIT|UNION|INSERT INTO|UPDATE|DELETE FROM|SET|VALUES|CREATE|ALTER|DROP|TRUNCATE|BEGIN|COMMIT|ROLLBACK|CALL)\b/gi, '\n$1')
118
- .replace(/,\s*/g, ',\n ') // Add line breaks after commas
126
+ .replace(/\s+/g, " ") // Normalize whitespace
127
+ .replace(/\b(SELECT|FROM|WHERE|JOIN|LEFT JOIN|RIGHT JOIN|INNER JOIN|OUTER JOIN|GROUP BY|ORDER BY|HAVING|LIMIT|UNION|INSERT INTO|UPDATE|DELETE FROM|SET|VALUES|CREATE|ALTER|DROP|TRUNCATE|BEGIN|COMMIT|ROLLBACK|CALL)\b/gi, "\n$1")
128
+ .replace(/,\s*/g, ",\n ") // Add line breaks after commas
119
129
  .trim();
120
130
  }
121
131
  /**
@@ -124,61 +134,72 @@ class QueryLogger {
124
134
  */
125
135
  static formatLogs(logs) {
126
136
  if (logs.length === 0)
127
- return '';
128
- return logs.map((log, index) => {
137
+ return "";
138
+ return logs
139
+ .map((log, index) => {
129
140
  // Format the SQL for better readability
130
141
  const formattedSQL = this.formatSQL(log.sql);
131
142
  // Format parameters
132
- let paramStr = '';
143
+ let paramStr = "";
133
144
  if (log.params && log.params.length > 0) {
134
145
  try {
135
146
  const paramsJson = JSON.stringify(log.params, null, 2);
136
- paramStr = paramsJson.length > this.MAX_PARAM_LENGTH
137
- ? `\nšŸ“‹ Parameters:\n${paramsJson.substring(0, this.MAX_PARAM_LENGTH)}...`
138
- : `\nšŸ“‹ Parameters:\n${paramsJson}`;
147
+ paramStr =
148
+ paramsJson.length > this.MAX_PARAM_LENGTH
149
+ ? `\nšŸ“‹ Parameters:\n${paramsJson.substring(0, this.MAX_PARAM_LENGTH)}...`
150
+ : `\nšŸ“‹ Parameters:\n${paramsJson}`;
139
151
  }
140
152
  catch (error) {
141
- paramStr = '\nšŸ“‹ Parameters: [Error serializing]';
153
+ paramStr = "\nšŸ“‹ Parameters: [Error serializing]";
142
154
  }
143
155
  }
144
156
  // Format error if present
145
- const errorStr = log.error ? `\nāŒ Error: ${log.error}` : '';
157
+ const errorStr = log.error ? `\nāŒ Error: ${log.error}` : "";
146
158
  // Format status with emoji for better visibility
147
- const statusEmoji = log.status === 'success' ? 'āœ…' : 'āŒ';
148
- const statusText = log.status === 'success' ? 'SUCCESS' : 'ERROR';
159
+ const statusEmoji = log.status === "success" ? "āœ…" : "āŒ";
160
+ const statusText = log.status === "success" ? "SUCCESS" : "ERROR";
161
+ // Format cache hit indicator
162
+ const cacheStr = log.cacheHit
163
+ ? "\nšŸ’¾ Cache: HIT (served from cache)"
164
+ : "";
165
+ const cacheLabel = log.cacheHit ? " [CACHED]" : "";
149
166
  // Build the formatted log entry with clear visual hierarchy
150
167
  return `━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━
151
- ${statusEmoji} SQL Query #${index + 1} - ${statusText}
168
+ ${statusEmoji} SQL Query #${index + 1} - ${statusText}${cacheLabel}
152
169
  ā±ļø Execution Time: ${log.duration}ms
153
- šŸ• Timestamp: ${log.timestamp}
170
+ šŸ• Timestamp: ${log.timestamp}${cacheStr}
154
171
  ━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━
155
172
 
156
173
  šŸ“ SQL Query:
157
174
  ${formattedSQL}${paramStr}${errorStr}`;
158
- }).join('\n\n');
175
+ })
176
+ .join("\n\n");
159
177
  }
160
178
  /**
161
179
  * Get logs as compact formatted string (for backward compatibility)
162
180
  */
163
181
  static formatLogsCompact(logs) {
164
182
  if (logs.length === 0)
165
- return '';
166
- return logs.map((log, index) => {
167
- let paramStr = '';
183
+ return "";
184
+ return logs
185
+ .map((log, index) => {
186
+ let paramStr = "";
168
187
  if (log.params && log.params.length > 0) {
169
188
  try {
170
189
  const paramsJson = JSON.stringify(log.params);
171
- paramStr = paramsJson.length > this.MAX_PARAM_LENGTH
172
- ? ` | Params: ${paramsJson.substring(0, this.MAX_PARAM_LENGTH)}...`
173
- : ` | Params: ${paramsJson}`;
190
+ paramStr =
191
+ paramsJson.length > this.MAX_PARAM_LENGTH
192
+ ? ` | Params: ${paramsJson.substring(0, this.MAX_PARAM_LENGTH)}...`
193
+ : ` | Params: ${paramsJson}`;
174
194
  }
175
195
  catch (error) {
176
- paramStr = ' | Params: [Error serializing]';
196
+ paramStr = " | Params: [Error serializing]";
177
197
  }
178
198
  }
179
- const errorStr = log.error ? ` | Error: ${log.error}` : '';
199
+ const errorStr = log.error ? ` | Error: ${log.error}` : "";
180
200
  return `[${index + 1}] ${log.timestamp} | ${log.sql}${paramStr} | Duration: ${log.duration}ms | Status: ${log.status}${errorStr}`;
181
- }).join('\n');
201
+ })
202
+ .join("\n");
182
203
  }
183
204
  }
184
205
  exports.QueryLogger = QueryLogger;