@teckedd-code2save/datafy 0.17.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/dist/index.js ADDED
@@ -0,0 +1,4777 @@
1
+ #!/usr/bin/env node
2
+ import {
3
+ BUILTIN_TOOL_ELASTICSEARCH_SEARCH,
4
+ BUILTIN_TOOL_EXECUTE_SQL,
5
+ BUILTIN_TOOL_REDIS_COMMAND,
6
+ BUILTIN_TOOL_SEARCH_OBJECTS,
7
+ ConnectorManager,
8
+ ConnectorRegistry,
9
+ SafeURL,
10
+ getDatabaseTypeFromDSN,
11
+ getDefaultPortForType,
12
+ getToolRegistry,
13
+ isDemoMode,
14
+ mapArgumentsToArray,
15
+ obfuscateDSNPassword,
16
+ parseConnectionInfoFromDSN,
17
+ resolvePort,
18
+ resolveSourceConfigs,
19
+ resolveTransport,
20
+ stripCommentsAndStrings
21
+ } from "./chunk-PQOTKZVK.js";
22
+
23
+ // src/connectors/postgres/index.ts
24
+ import pg from "pg";
25
+
26
+ // src/utils/sql-row-limiter.ts
27
+ var SQLRowLimiter = class {
28
+ /**
29
+ * Check if a SQL statement is a SELECT query that can benefit from row limiting
30
+ * Only handles SELECT queries
31
+ */
32
+ static isSelectQuery(sql2) {
33
+ const trimmed = sql2.trim().toLowerCase();
34
+ return trimmed.startsWith("select");
35
+ }
36
+ /**
37
+ * Check if a SQL statement already has a LIMIT clause.
38
+ * Strips comments and string literals first to avoid false positives.
39
+ */
40
+ static hasLimitClause(sql2) {
41
+ const cleanedSQL = stripCommentsAndStrings(sql2);
42
+ const limitRegex = /\blimit\s+(?:\d+|\$\d+|\?|@p\d+)/i;
43
+ return limitRegex.test(cleanedSQL);
44
+ }
45
+ /**
46
+ * Check if a SQL statement already has a TOP clause (SQL Server).
47
+ * Strips comments and string literals first to avoid false positives.
48
+ */
49
+ static hasTopClause(sql2) {
50
+ const cleanedSQL = stripCommentsAndStrings(sql2);
51
+ const topRegex = /\bselect\s+top\s+\d+/i;
52
+ return topRegex.test(cleanedSQL);
53
+ }
54
+ /**
55
+ * Extract existing LIMIT value from SQL if present.
56
+ * Strips comments and string literals first to avoid false positives.
57
+ */
58
+ static extractLimitValue(sql2) {
59
+ const cleanedSQL = stripCommentsAndStrings(sql2);
60
+ const limitMatch = cleanedSQL.match(/\blimit\s+(\d+)/i);
61
+ if (limitMatch) {
62
+ return parseInt(limitMatch[1], 10);
63
+ }
64
+ return null;
65
+ }
66
+ /**
67
+ * Extract existing TOP value from SQL if present (SQL Server).
68
+ * Strips comments and string literals first to avoid false positives.
69
+ */
70
+ static extractTopValue(sql2) {
71
+ const cleanedSQL = stripCommentsAndStrings(sql2);
72
+ const topMatch = cleanedSQL.match(/\bselect\s+top\s+(\d+)/i);
73
+ if (topMatch) {
74
+ return parseInt(topMatch[1], 10);
75
+ }
76
+ return null;
77
+ }
78
+ /**
79
+ * Add or modify LIMIT clause in a SQL statement
80
+ */
81
+ static applyLimitToQuery(sql2, maxRows) {
82
+ const existingLimit = this.extractLimitValue(sql2);
83
+ if (existingLimit !== null) {
84
+ const effectiveLimit = Math.min(existingLimit, maxRows);
85
+ return sql2.replace(/\blimit\s+\d+/i, `LIMIT ${effectiveLimit}`);
86
+ } else {
87
+ const trimmed = sql2.trim();
88
+ const hasSemicolon = trimmed.endsWith(";");
89
+ const sqlWithoutSemicolon = hasSemicolon ? trimmed.slice(0, -1) : trimmed;
90
+ return `${sqlWithoutSemicolon} LIMIT ${maxRows}${hasSemicolon ? ";" : ""}`;
91
+ }
92
+ }
93
+ /**
94
+ * Add or modify TOP clause in a SQL statement (SQL Server)
95
+ */
96
+ static applyTopToQuery(sql2, maxRows) {
97
+ const existingTop = this.extractTopValue(sql2);
98
+ if (existingTop !== null) {
99
+ const effectiveTop = Math.min(existingTop, maxRows);
100
+ return sql2.replace(/\bselect\s+top\s+\d+/i, `SELECT TOP ${effectiveTop}`);
101
+ } else {
102
+ return sql2.replace(/\bselect\s+/i, `SELECT TOP ${maxRows} `);
103
+ }
104
+ }
105
+ /**
106
+ * Check if a LIMIT clause uses a parameter placeholder (not a literal number).
107
+ * Strips comments and string literals first to avoid false positives.
108
+ */
109
+ static hasParameterizedLimit(sql2) {
110
+ const cleanedSQL = stripCommentsAndStrings(sql2);
111
+ const parameterizedLimitRegex = /\blimit\s+(?:\$\d+|\?|@p\d+)/i;
112
+ return parameterizedLimitRegex.test(cleanedSQL);
113
+ }
114
+ /**
115
+ * Apply maxRows limit to a SELECT query only
116
+ *
117
+ * This method is used by PostgreSQL, MySQL, MariaDB, and SQLite connectors which all support
118
+ * the LIMIT clause syntax. SQL Server uses applyMaxRowsForSQLServer() instead with TOP syntax.
119
+ *
120
+ * For parameterized LIMIT clauses (e.g., LIMIT $1 or LIMIT ?), we wrap the query in a subquery
121
+ * to enforce max_rows as a hard cap, since the parameter value is not known until runtime.
122
+ */
123
+ static applyMaxRows(sql2, maxRows) {
124
+ if (!maxRows || !this.isSelectQuery(sql2)) {
125
+ return sql2;
126
+ }
127
+ if (this.hasParameterizedLimit(sql2)) {
128
+ const trimmed = sql2.trim();
129
+ const hasSemicolon = trimmed.endsWith(";");
130
+ const sqlWithoutSemicolon = hasSemicolon ? trimmed.slice(0, -1) : trimmed;
131
+ return `SELECT * FROM (${sqlWithoutSemicolon}) AS subq LIMIT ${maxRows}${hasSemicolon ? ";" : ""}`;
132
+ }
133
+ return this.applyLimitToQuery(sql2, maxRows);
134
+ }
135
+ /**
136
+ * Apply maxRows limit to a SELECT query using SQL Server TOP syntax
137
+ */
138
+ static applyMaxRowsForSQLServer(sql2, maxRows) {
139
+ if (!maxRows || !this.isSelectQuery(sql2)) {
140
+ return sql2;
141
+ }
142
+ return this.applyTopToQuery(sql2, maxRows);
143
+ }
144
+ };
145
+
146
+ // src/connectors/postgres/index.ts
147
+ var { Pool } = pg;
148
+ var PostgresDSNParser = class {
149
+ async parse(dsn, config) {
150
+ const connectionTimeoutSeconds = config?.connectionTimeoutSeconds;
151
+ const queryTimeoutSeconds = config?.queryTimeoutSeconds;
152
+ if (!this.isValidDSN(dsn)) {
153
+ const obfuscatedDSN = obfuscateDSNPassword(dsn);
154
+ const expectedFormat = this.getSampleDSN();
155
+ throw new Error(
156
+ `Invalid PostgreSQL DSN format.
157
+ Provided: ${obfuscatedDSN}
158
+ Expected: ${expectedFormat}`
159
+ );
160
+ }
161
+ try {
162
+ const url = new SafeURL(dsn);
163
+ const poolConfig = {
164
+ host: url.hostname,
165
+ port: url.port ? parseInt(url.port) : 5432,
166
+ database: url.pathname ? url.pathname.substring(1) : "",
167
+ // Remove leading '/' if exists
168
+ user: url.username,
169
+ password: url.password
170
+ };
171
+ url.forEachSearchParam((value, key) => {
172
+ if (key === "sslmode") {
173
+ if (value === "disable") {
174
+ poolConfig.ssl = false;
175
+ } else if (value === "require") {
176
+ poolConfig.ssl = { rejectUnauthorized: false };
177
+ } else {
178
+ poolConfig.ssl = true;
179
+ }
180
+ }
181
+ });
182
+ if (connectionTimeoutSeconds !== void 0) {
183
+ poolConfig.connectionTimeoutMillis = connectionTimeoutSeconds * 1e3;
184
+ }
185
+ if (queryTimeoutSeconds !== void 0) {
186
+ poolConfig.query_timeout = queryTimeoutSeconds * 1e3;
187
+ }
188
+ return poolConfig;
189
+ } catch (error) {
190
+ throw new Error(
191
+ `Failed to parse PostgreSQL DSN: ${error instanceof Error ? error.message : String(error)}`
192
+ );
193
+ }
194
+ }
195
+ getSampleDSN() {
196
+ return "postgres://postgres:password@localhost:5432/postgres?sslmode=require";
197
+ }
198
+ isValidDSN(dsn) {
199
+ try {
200
+ return dsn.startsWith("postgres://") || dsn.startsWith("postgresql://");
201
+ } catch (error) {
202
+ return false;
203
+ }
204
+ }
205
+ };
206
+ var PostgresConnector = class _PostgresConnector {
207
+ constructor() {
208
+ this.id = "postgres";
209
+ this.name = "PostgreSQL";
210
+ this.dsnParser = new PostgresDSNParser();
211
+ this.pool = null;
212
+ // Source ID is set by ConnectorManager after cloning
213
+ this.sourceId = "default";
214
+ }
215
+ getId() {
216
+ return this.sourceId;
217
+ }
218
+ clone() {
219
+ return new _PostgresConnector();
220
+ }
221
+ async connect(dsn, initScript, config) {
222
+ try {
223
+ const poolConfig = await this.dsnParser.parse(dsn, config);
224
+ if (config?.readonly) {
225
+ poolConfig.options = (poolConfig.options || "") + " -c default_transaction_read_only=on";
226
+ }
227
+ this.pool = new Pool(poolConfig);
228
+ const client = await this.pool.connect();
229
+ client.release();
230
+ } catch (err) {
231
+ console.error("Failed to connect to PostgreSQL database:", err);
232
+ throw err;
233
+ }
234
+ }
235
+ async disconnect() {
236
+ if (this.pool) {
237
+ await this.pool.end();
238
+ this.pool = null;
239
+ }
240
+ }
241
+ async getSchemas() {
242
+ if (!this.pool) {
243
+ throw new Error("Not connected to database");
244
+ }
245
+ const client = await this.pool.connect();
246
+ try {
247
+ const result = await client.query(`
248
+ SELECT schema_name
249
+ FROM information_schema.schemata
250
+ WHERE schema_name NOT IN ('pg_catalog', 'information_schema', 'pg_toast')
251
+ ORDER BY schema_name
252
+ `);
253
+ return result.rows.map((row) => row.schema_name);
254
+ } finally {
255
+ client.release();
256
+ }
257
+ }
258
+ async getTables(schema) {
259
+ if (!this.pool) {
260
+ throw new Error("Not connected to database");
261
+ }
262
+ const client = await this.pool.connect();
263
+ try {
264
+ const schemaToUse = schema || "public";
265
+ const result = await client.query(
266
+ `
267
+ SELECT table_name
268
+ FROM information_schema.tables
269
+ WHERE table_schema = $1
270
+ ORDER BY table_name
271
+ `,
272
+ [schemaToUse]
273
+ );
274
+ return result.rows.map((row) => row.table_name);
275
+ } finally {
276
+ client.release();
277
+ }
278
+ }
279
+ async tableExists(tableName, schema) {
280
+ if (!this.pool) {
281
+ throw new Error("Not connected to database");
282
+ }
283
+ const client = await this.pool.connect();
284
+ try {
285
+ const schemaToUse = schema || "public";
286
+ const result = await client.query(
287
+ `
288
+ SELECT EXISTS (
289
+ SELECT FROM information_schema.tables
290
+ WHERE table_schema = $1
291
+ AND table_name = $2
292
+ )
293
+ `,
294
+ [schemaToUse, tableName]
295
+ );
296
+ return result.rows[0].exists;
297
+ } finally {
298
+ client.release();
299
+ }
300
+ }
301
+ async getTableIndexes(tableName, schema) {
302
+ if (!this.pool) {
303
+ throw new Error("Not connected to database");
304
+ }
305
+ const client = await this.pool.connect();
306
+ try {
307
+ const schemaToUse = schema || "public";
308
+ const result = await client.query(
309
+ `
310
+ SELECT
311
+ i.relname as index_name,
312
+ array_agg(a.attname) as column_names,
313
+ ix.indisunique as is_unique,
314
+ ix.indisprimary as is_primary
315
+ FROM
316
+ pg_class t,
317
+ pg_class i,
318
+ pg_index ix,
319
+ pg_attribute a,
320
+ pg_namespace ns
321
+ WHERE
322
+ t.oid = ix.indrelid
323
+ AND i.oid = ix.indexrelid
324
+ AND a.attrelid = t.oid
325
+ AND a.attnum = ANY(ix.indkey)
326
+ AND t.relkind = 'r'
327
+ AND t.relname = $1
328
+ AND ns.oid = t.relnamespace
329
+ AND ns.nspname = $2
330
+ GROUP BY
331
+ i.relname,
332
+ ix.indisunique,
333
+ ix.indisprimary
334
+ ORDER BY
335
+ i.relname
336
+ `,
337
+ [tableName, schemaToUse]
338
+ );
339
+ return result.rows.map((row) => ({
340
+ index_name: row.index_name,
341
+ column_names: row.column_names,
342
+ is_unique: row.is_unique,
343
+ is_primary: row.is_primary
344
+ }));
345
+ } finally {
346
+ client.release();
347
+ }
348
+ }
349
+ async getTableSchema(tableName, schema) {
350
+ if (!this.pool) {
351
+ throw new Error("Not connected to database");
352
+ }
353
+ const client = await this.pool.connect();
354
+ try {
355
+ const schemaToUse = schema || "public";
356
+ const result = await client.query(
357
+ `
358
+ SELECT
359
+ column_name,
360
+ data_type,
361
+ is_nullable,
362
+ column_default
363
+ FROM information_schema.columns
364
+ WHERE table_schema = $1
365
+ AND table_name = $2
366
+ ORDER BY ordinal_position
367
+ `,
368
+ [schemaToUse, tableName]
369
+ );
370
+ return result.rows;
371
+ } finally {
372
+ client.release();
373
+ }
374
+ }
375
+ async getStoredProcedures(schema) {
376
+ if (!this.pool) {
377
+ throw new Error("Not connected to database");
378
+ }
379
+ const client = await this.pool.connect();
380
+ try {
381
+ const schemaToUse = schema || "public";
382
+ const result = await client.query(
383
+ `
384
+ SELECT
385
+ routine_name
386
+ FROM information_schema.routines
387
+ WHERE routine_schema = $1
388
+ ORDER BY routine_name
389
+ `,
390
+ [schemaToUse]
391
+ );
392
+ return result.rows.map((row) => row.routine_name);
393
+ } finally {
394
+ client.release();
395
+ }
396
+ }
397
+ async getStoredProcedureDetail(procedureName, schema) {
398
+ if (!this.pool) {
399
+ throw new Error("Not connected to database");
400
+ }
401
+ const client = await this.pool.connect();
402
+ try {
403
+ const schemaToUse = schema || "public";
404
+ const result = await client.query(
405
+ `
406
+ SELECT
407
+ routine_name as procedure_name,
408
+ routine_type,
409
+ CASE WHEN routine_type = 'PROCEDURE' THEN 'procedure' ELSE 'function' END as procedure_type,
410
+ external_language as language,
411
+ data_type as return_type,
412
+ routine_definition as definition,
413
+ (
414
+ SELECT string_agg(
415
+ parameter_name || ' ' ||
416
+ parameter_mode || ' ' ||
417
+ data_type,
418
+ ', '
419
+ )
420
+ FROM information_schema.parameters
421
+ WHERE specific_schema = $1
422
+ AND specific_name = $2
423
+ AND parameter_name IS NOT NULL
424
+ ) as parameter_list
425
+ FROM information_schema.routines
426
+ WHERE routine_schema = $1
427
+ AND routine_name = $2
428
+ `,
429
+ [schemaToUse, procedureName]
430
+ );
431
+ if (result.rows.length === 0) {
432
+ throw new Error(`Stored procedure '${procedureName}' not found in schema '${schemaToUse}'`);
433
+ }
434
+ const procedure = result.rows[0];
435
+ let definition = procedure.definition;
436
+ try {
437
+ const oidResult = await client.query(
438
+ `
439
+ SELECT p.oid, p.prosrc
440
+ FROM pg_proc p
441
+ JOIN pg_namespace n ON p.pronamespace = n.oid
442
+ WHERE p.proname = $1
443
+ AND n.nspname = $2
444
+ `,
445
+ [procedureName, schemaToUse]
446
+ );
447
+ if (oidResult.rows.length > 0) {
448
+ if (!definition) {
449
+ const oid = oidResult.rows[0].oid;
450
+ const defResult = await client.query(`SELECT pg_get_functiondef($1)`, [oid]);
451
+ if (defResult.rows.length > 0) {
452
+ definition = defResult.rows[0].pg_get_functiondef;
453
+ } else {
454
+ definition = oidResult.rows[0].prosrc;
455
+ }
456
+ }
457
+ }
458
+ } catch (err) {
459
+ console.error(`Error getting procedure definition: ${err}`);
460
+ }
461
+ return {
462
+ procedure_name: procedure.procedure_name,
463
+ procedure_type: procedure.procedure_type,
464
+ language: procedure.language || "sql",
465
+ parameter_list: procedure.parameter_list || "",
466
+ return_type: procedure.return_type !== "void" ? procedure.return_type : void 0,
467
+ definition: definition || void 0
468
+ };
469
+ } finally {
470
+ client.release();
471
+ }
472
+ }
473
+ async executeSQL(sql2, options, parameters) {
474
+ if (!this.pool) {
475
+ throw new Error("Not connected to database");
476
+ }
477
+ const client = await this.pool.connect();
478
+ try {
479
+ const statements = sql2.split(";").map((statement) => statement.trim()).filter((statement) => statement.length > 0);
480
+ if (statements.length === 1) {
481
+ const processedStatement = SQLRowLimiter.applyMaxRows(statements[0], options.maxRows);
482
+ let result;
483
+ if (parameters && parameters.length > 0) {
484
+ try {
485
+ result = await client.query(processedStatement, parameters);
486
+ } catch (error) {
487
+ console.error(`[PostgreSQL executeSQL] ERROR: ${error.message}`);
488
+ console.error(`[PostgreSQL executeSQL] SQL: ${processedStatement}`);
489
+ console.error(`[PostgreSQL executeSQL] Parameters: ${JSON.stringify(parameters)}`);
490
+ throw error;
491
+ }
492
+ } else {
493
+ result = await client.query(processedStatement);
494
+ }
495
+ return { rows: result.rows, rowCount: result.rowCount ?? result.rows.length };
496
+ } else {
497
+ if (parameters && parameters.length > 0) {
498
+ throw new Error("Parameters are not supported for multi-statement queries in PostgreSQL");
499
+ }
500
+ let allRows = [];
501
+ let totalRowCount = 0;
502
+ await client.query("BEGIN");
503
+ try {
504
+ for (let statement of statements) {
505
+ const processedStatement = SQLRowLimiter.applyMaxRows(statement, options.maxRows);
506
+ const result = await client.query(processedStatement);
507
+ if (result.rows && result.rows.length > 0) {
508
+ allRows.push(...result.rows);
509
+ }
510
+ if (result.rowCount) {
511
+ totalRowCount += result.rowCount;
512
+ }
513
+ }
514
+ await client.query("COMMIT");
515
+ } catch (error) {
516
+ await client.query("ROLLBACK");
517
+ throw error;
518
+ }
519
+ return { rows: allRows, rowCount: totalRowCount };
520
+ }
521
+ } finally {
522
+ client.release();
523
+ }
524
+ }
525
+ };
526
+ var postgresConnector = new PostgresConnector();
527
+ ConnectorRegistry.register(postgresConnector);
528
+
529
+ // src/connectors/sqlserver/index.ts
530
+ import sql from "mssql";
531
+ import { DefaultAzureCredential } from "@azure/identity";
532
+ var SQLServerDSNParser = class {
533
+ async parse(dsn, config) {
534
+ const connectionTimeoutSeconds = config?.connectionTimeoutSeconds;
535
+ const queryTimeoutSeconds = config?.queryTimeoutSeconds;
536
+ if (!this.isValidDSN(dsn)) {
537
+ const obfuscatedDSN = obfuscateDSNPassword(dsn);
538
+ const expectedFormat = this.getSampleDSN();
539
+ throw new Error(
540
+ `Invalid SQL Server DSN format.
541
+ Provided: ${obfuscatedDSN}
542
+ Expected: ${expectedFormat}`
543
+ );
544
+ }
545
+ try {
546
+ const url = new SafeURL(dsn);
547
+ const options = {};
548
+ url.forEachSearchParam((value, key) => {
549
+ if (key === "authentication") {
550
+ options.authentication = value;
551
+ } else if (key === "sslmode") {
552
+ options.sslmode = value;
553
+ } else if (key === "instanceName") {
554
+ options.instanceName = value;
555
+ } else if (key === "domain") {
556
+ options.domain = value;
557
+ }
558
+ });
559
+ if (options.authentication === "ntlm" && !options.domain) {
560
+ throw new Error("NTLM authentication requires 'domain' parameter");
561
+ }
562
+ if (options.domain && options.authentication !== "ntlm") {
563
+ throw new Error("Parameter 'domain' requires 'authentication=ntlm'");
564
+ }
565
+ if (options.sslmode) {
566
+ if (options.sslmode === "disable") {
567
+ options.encrypt = false;
568
+ options.trustServerCertificate = false;
569
+ } else if (options.sslmode === "require") {
570
+ options.encrypt = true;
571
+ options.trustServerCertificate = true;
572
+ }
573
+ }
574
+ const config2 = {
575
+ server: url.hostname,
576
+ port: url.port ? parseInt(url.port) : 1433,
577
+ // Default SQL Server port
578
+ database: url.pathname ? url.pathname.substring(1) : "",
579
+ // Remove leading slash
580
+ options: {
581
+ encrypt: options.encrypt ?? false,
582
+ // Default to unencrypted for development
583
+ trustServerCertificate: options.trustServerCertificate ?? false,
584
+ ...connectionTimeoutSeconds !== void 0 && {
585
+ connectTimeout: connectionTimeoutSeconds * 1e3
586
+ },
587
+ ...queryTimeoutSeconds !== void 0 && {
588
+ requestTimeout: queryTimeoutSeconds * 1e3
589
+ },
590
+ instanceName: options.instanceName
591
+ // Add named instance support
592
+ }
593
+ };
594
+ switch (options.authentication) {
595
+ case "azure-active-directory-access-token": {
596
+ try {
597
+ const credential = new DefaultAzureCredential();
598
+ const token = await credential.getToken("https://database.windows.net/");
599
+ config2.authentication = {
600
+ type: "azure-active-directory-access-token",
601
+ options: {
602
+ token: token.token
603
+ }
604
+ };
605
+ } catch (error) {
606
+ const errorMessage = error instanceof Error ? error.message : String(error);
607
+ throw new Error(`Failed to get Azure AD token: ${errorMessage}`);
608
+ }
609
+ break;
610
+ }
611
+ case "ntlm":
612
+ config2.authentication = {
613
+ type: "ntlm",
614
+ options: {
615
+ domain: options.domain,
616
+ userName: url.username,
617
+ password: url.password
618
+ }
619
+ };
620
+ break;
621
+ default:
622
+ config2.user = url.username;
623
+ config2.password = url.password;
624
+ break;
625
+ }
626
+ return config2;
627
+ } catch (error) {
628
+ throw new Error(
629
+ `Failed to parse SQL Server DSN: ${error instanceof Error ? error.message : String(error)}`
630
+ );
631
+ }
632
+ }
633
+ getSampleDSN() {
634
+ return "sqlserver://username:password@localhost:1433/database?sslmode=disable&instanceName=INSTANCE1";
635
+ }
636
+ isValidDSN(dsn) {
637
+ try {
638
+ return dsn.startsWith("sqlserver://");
639
+ } catch (error) {
640
+ return false;
641
+ }
642
+ }
643
+ };
644
+ var SQLServerConnector = class _SQLServerConnector {
645
+ constructor() {
646
+ this.id = "sqlserver";
647
+ this.name = "SQL Server";
648
+ this.dsnParser = new SQLServerDSNParser();
649
+ // Source ID is set by ConnectorManager after cloning
650
+ this.sourceId = "default";
651
+ }
652
+ getId() {
653
+ return this.sourceId;
654
+ }
655
+ clone() {
656
+ return new _SQLServerConnector();
657
+ }
658
+ async connect(dsn, initScript, config) {
659
+ try {
660
+ this.config = await this.dsnParser.parse(dsn, config);
661
+ if (!this.config.options) {
662
+ this.config.options = {};
663
+ }
664
+ this.connection = await new sql.ConnectionPool(this.config).connect();
665
+ } catch (error) {
666
+ throw error;
667
+ }
668
+ }
669
+ async disconnect() {
670
+ if (this.connection) {
671
+ await this.connection.close();
672
+ this.connection = void 0;
673
+ }
674
+ }
675
+ async getSchemas() {
676
+ if (!this.connection) {
677
+ throw new Error("Not connected to SQL Server database");
678
+ }
679
+ try {
680
+ const result = await this.connection.request().query(`
681
+ SELECT SCHEMA_NAME
682
+ FROM INFORMATION_SCHEMA.SCHEMATA
683
+ ORDER BY SCHEMA_NAME
684
+ `);
685
+ return result.recordset.map((row) => row.SCHEMA_NAME);
686
+ } catch (error) {
687
+ throw new Error(`Failed to get schemas: ${error.message}`);
688
+ }
689
+ }
690
+ async getTables(schema) {
691
+ if (!this.connection) {
692
+ throw new Error("Not connected to SQL Server database");
693
+ }
694
+ try {
695
+ const schemaToUse = schema || "dbo";
696
+ const request = this.connection.request().input("schema", sql.VarChar, schemaToUse);
697
+ const query = `
698
+ SELECT TABLE_NAME
699
+ FROM INFORMATION_SCHEMA.TABLES
700
+ WHERE TABLE_SCHEMA = @schema
701
+ ORDER BY TABLE_NAME
702
+ `;
703
+ const result = await request.query(query);
704
+ return result.recordset.map((row) => row.TABLE_NAME);
705
+ } catch (error) {
706
+ throw new Error(`Failed to get tables: ${error.message}`);
707
+ }
708
+ }
709
+ async tableExists(tableName, schema) {
710
+ if (!this.connection) {
711
+ throw new Error("Not connected to SQL Server database");
712
+ }
713
+ try {
714
+ const schemaToUse = schema || "dbo";
715
+ const request = this.connection.request().input("tableName", sql.VarChar, tableName).input("schema", sql.VarChar, schemaToUse);
716
+ const query = `
717
+ SELECT COUNT(*) as count
718
+ FROM INFORMATION_SCHEMA.TABLES
719
+ WHERE TABLE_NAME = @tableName
720
+ AND TABLE_SCHEMA = @schema
721
+ `;
722
+ const result = await request.query(query);
723
+ return result.recordset[0].count > 0;
724
+ } catch (error) {
725
+ throw new Error(`Failed to check if table exists: ${error.message}`);
726
+ }
727
+ }
728
+ async getTableIndexes(tableName, schema) {
729
+ if (!this.connection) {
730
+ throw new Error("Not connected to SQL Server database");
731
+ }
732
+ try {
733
+ const schemaToUse = schema || "dbo";
734
+ const request = this.connection.request().input("tableName", sql.VarChar, tableName).input("schema", sql.VarChar, schemaToUse);
735
+ const query = `
736
+ SELECT i.name AS index_name,
737
+ i.is_unique,
738
+ i.is_primary_key,
739
+ c.name AS column_name,
740
+ ic.key_ordinal
741
+ FROM sys.indexes i
742
+ INNER JOIN
743
+ sys.index_columns ic ON i.object_id = ic.object_id AND i.index_id = ic.index_id
744
+ INNER JOIN
745
+ sys.columns c ON ic.object_id = c.object_id AND ic.column_id = c.column_id
746
+ INNER JOIN
747
+ sys.tables t ON i.object_id = t.object_id
748
+ INNER JOIN
749
+ sys.schemas s ON t.schema_id = s.schema_id
750
+ WHERE t.name = @tableName
751
+ AND s.name = @schema
752
+ ORDER BY i.name,
753
+ ic.key_ordinal
754
+ `;
755
+ const result = await request.query(query);
756
+ const indexMap = /* @__PURE__ */ new Map();
757
+ for (const row of result.recordset) {
758
+ const indexName = row.index_name;
759
+ const columnName = row.column_name;
760
+ const isUnique = !!row.is_unique;
761
+ const isPrimary = !!row.is_primary_key;
762
+ if (!indexMap.has(indexName)) {
763
+ indexMap.set(indexName, {
764
+ columns: [],
765
+ is_unique: isUnique,
766
+ is_primary: isPrimary
767
+ });
768
+ }
769
+ const indexInfo = indexMap.get(indexName);
770
+ indexInfo.columns.push(columnName);
771
+ }
772
+ const indexes = [];
773
+ indexMap.forEach((info, name) => {
774
+ indexes.push({
775
+ index_name: name,
776
+ column_names: info.columns,
777
+ is_unique: info.is_unique,
778
+ is_primary: info.is_primary
779
+ });
780
+ });
781
+ return indexes;
782
+ } catch (error) {
783
+ throw new Error(`Failed to get indexes for table ${tableName}: ${error.message}`);
784
+ }
785
+ }
786
+ async getTableSchema(tableName, schema) {
787
+ if (!this.connection) {
788
+ throw new Error("Not connected to SQL Server database");
789
+ }
790
+ try {
791
+ const schemaToUse = schema || "dbo";
792
+ const request = this.connection.request().input("tableName", sql.VarChar, tableName).input("schema", sql.VarChar, schemaToUse);
793
+ const query = `
794
+ SELECT COLUMN_NAME as column_name,
795
+ DATA_TYPE as data_type,
796
+ IS_NULLABLE as is_nullable,
797
+ COLUMN_DEFAULT as column_default
798
+ FROM INFORMATION_SCHEMA.COLUMNS
799
+ WHERE TABLE_NAME = @tableName
800
+ AND TABLE_SCHEMA = @schema
801
+ ORDER BY ORDINAL_POSITION
802
+ `;
803
+ const result = await request.query(query);
804
+ return result.recordset;
805
+ } catch (error) {
806
+ throw new Error(`Failed to get schema for table ${tableName}: ${error.message}`);
807
+ }
808
+ }
809
+ async getStoredProcedures(schema) {
810
+ if (!this.connection) {
811
+ throw new Error("Not connected to SQL Server database");
812
+ }
813
+ try {
814
+ const schemaToUse = schema || "dbo";
815
+ const request = this.connection.request().input("schema", sql.VarChar, schemaToUse);
816
+ const query = `
817
+ SELECT ROUTINE_NAME
818
+ FROM INFORMATION_SCHEMA.ROUTINES
819
+ WHERE ROUTINE_SCHEMA = @schema
820
+ AND (ROUTINE_TYPE = 'PROCEDURE' OR ROUTINE_TYPE = 'FUNCTION')
821
+ ORDER BY ROUTINE_NAME
822
+ `;
823
+ const result = await request.query(query);
824
+ return result.recordset.map((row) => row.ROUTINE_NAME);
825
+ } catch (error) {
826
+ throw new Error(`Failed to get stored procedures: ${error.message}`);
827
+ }
828
+ }
829
+ async getStoredProcedureDetail(procedureName, schema) {
830
+ if (!this.connection) {
831
+ throw new Error("Not connected to SQL Server database");
832
+ }
833
+ try {
834
+ const schemaToUse = schema || "dbo";
835
+ const request = this.connection.request().input("procedureName", sql.VarChar, procedureName).input("schema", sql.VarChar, schemaToUse);
836
+ const routineQuery = `
837
+ SELECT ROUTINE_NAME as procedure_name,
838
+ ROUTINE_TYPE,
839
+ DATA_TYPE as return_data_type
840
+ FROM INFORMATION_SCHEMA.ROUTINES
841
+ WHERE ROUTINE_NAME = @procedureName
842
+ AND ROUTINE_SCHEMA = @schema
843
+ `;
844
+ const routineResult = await request.query(routineQuery);
845
+ if (routineResult.recordset.length === 0) {
846
+ throw new Error(`Stored procedure '${procedureName}' not found in schema '${schemaToUse}'`);
847
+ }
848
+ const routine = routineResult.recordset[0];
849
+ const parameterQuery = `
850
+ SELECT PARAMETER_NAME,
851
+ PARAMETER_MODE,
852
+ DATA_TYPE,
853
+ CHARACTER_MAXIMUM_LENGTH,
854
+ ORDINAL_POSITION
855
+ FROM INFORMATION_SCHEMA.PARAMETERS
856
+ WHERE SPECIFIC_NAME = @procedureName
857
+ AND SPECIFIC_SCHEMA = @schema
858
+ ORDER BY ORDINAL_POSITION
859
+ `;
860
+ const parameterResult = await request.query(parameterQuery);
861
+ let parameterList = "";
862
+ if (parameterResult.recordset.length > 0) {
863
+ parameterList = parameterResult.recordset.map(
864
+ (param) => {
865
+ const lengthStr = param.CHARACTER_MAXIMUM_LENGTH > 0 ? `(${param.CHARACTER_MAXIMUM_LENGTH})` : "";
866
+ return `${param.PARAMETER_NAME} ${param.PARAMETER_MODE} ${param.DATA_TYPE}${lengthStr}`;
867
+ }
868
+ ).join(", ");
869
+ }
870
+ const definitionQuery = `
871
+ SELECT definition
872
+ FROM sys.sql_modules sm
873
+ JOIN sys.objects o ON sm.object_id = o.object_id
874
+ JOIN sys.schemas s ON o.schema_id = s.schema_id
875
+ WHERE o.name = @procedureName
876
+ AND s.name = @schema
877
+ `;
878
+ const definitionResult = await request.query(definitionQuery);
879
+ let definition = void 0;
880
+ if (definitionResult.recordset.length > 0) {
881
+ definition = definitionResult.recordset[0].definition;
882
+ }
883
+ return {
884
+ procedure_name: routine.procedure_name,
885
+ procedure_type: routine.ROUTINE_TYPE === "PROCEDURE" ? "procedure" : "function",
886
+ language: "sql",
887
+ // SQL Server procedures are typically in T-SQL
888
+ parameter_list: parameterList,
889
+ return_type: routine.ROUTINE_TYPE === "FUNCTION" ? routine.return_data_type : void 0,
890
+ definition
891
+ };
892
+ } catch (error) {
893
+ throw new Error(`Failed to get stored procedure details: ${error.message}`);
894
+ }
895
+ }
896
+ async executeSQL(sqlQuery, options, parameters) {
897
+ if (!this.connection) {
898
+ throw new Error("Not connected to SQL Server database");
899
+ }
900
+ try {
901
+ let processedSQL = sqlQuery;
902
+ if (options.maxRows) {
903
+ processedSQL = SQLRowLimiter.applyMaxRowsForSQLServer(sqlQuery, options.maxRows);
904
+ }
905
+ const request = this.connection.request();
906
+ if (parameters && parameters.length > 0) {
907
+ parameters.forEach((param, index) => {
908
+ const paramName = `p${index + 1}`;
909
+ if (typeof param === "string") {
910
+ request.input(paramName, sql.VarChar, param);
911
+ } else if (typeof param === "number") {
912
+ if (Number.isInteger(param)) {
913
+ request.input(paramName, sql.Int, param);
914
+ } else {
915
+ request.input(paramName, sql.Float, param);
916
+ }
917
+ } else if (typeof param === "boolean") {
918
+ request.input(paramName, sql.Bit, param);
919
+ } else if (param === null || param === void 0) {
920
+ request.input(paramName, sql.VarChar, param);
921
+ } else if (Array.isArray(param)) {
922
+ request.input(paramName, sql.VarChar, JSON.stringify(param));
923
+ } else {
924
+ request.input(paramName, sql.VarChar, JSON.stringify(param));
925
+ }
926
+ });
927
+ }
928
+ let result;
929
+ try {
930
+ result = await request.query(processedSQL);
931
+ } catch (error) {
932
+ if (parameters && parameters.length > 0) {
933
+ console.error(`[SQL Server executeSQL] ERROR: ${error.message}`);
934
+ console.error(`[SQL Server executeSQL] SQL: ${processedSQL}`);
935
+ console.error(`[SQL Server executeSQL] Parameters: ${JSON.stringify(parameters)}`);
936
+ }
937
+ throw error;
938
+ }
939
+ return {
940
+ rows: result.recordset || [],
941
+ rowCount: result.rowsAffected[0] || 0
942
+ };
943
+ } catch (error) {
944
+ throw new Error(`Failed to execute query: ${error.message}`);
945
+ }
946
+ }
947
+ };
948
+ var sqlServerConnector = new SQLServerConnector();
949
+ ConnectorRegistry.register(sqlServerConnector);
950
+
951
+ // src/connectors/sqlite/index.ts
952
+ import Database from "better-sqlite3";
953
+
954
+ // src/utils/identifier-quoter.ts
955
+ function quoteIdentifier(identifier, dbType) {
956
+ if (/[\0\x08\x09\x1a\n\r]/.test(identifier)) {
957
+ throw new Error(`Invalid identifier: contains control characters: ${identifier}`);
958
+ }
959
+ if (!identifier) {
960
+ throw new Error("Identifier cannot be empty");
961
+ }
962
+ switch (dbType) {
963
+ case "postgres":
964
+ case "sqlite":
965
+ return `"${identifier.replace(/"/g, '""')}"`;
966
+ case "mysql":
967
+ case "mariadb":
968
+ return `\`${identifier.replace(/`/g, "``")}\``;
969
+ case "sqlserver":
970
+ return `[${identifier.replace(/]/g, "]]")}]`;
971
+ default:
972
+ return `"${identifier.replace(/"/g, '""')}"`;
973
+ }
974
+ }
975
+ function quoteQualifiedIdentifier(tableName, schemaName, dbType) {
976
+ const quotedTable = quoteIdentifier(tableName, dbType);
977
+ if (schemaName) {
978
+ const quotedSchema = quoteIdentifier(schemaName, dbType);
979
+ return `${quotedSchema}.${quotedTable}`;
980
+ }
981
+ return quotedTable;
982
+ }
983
+
984
+ // src/connectors/sqlite/index.ts
985
+ var SQLiteDSNParser = class {
986
+ async parse(dsn, config) {
987
+ if (!this.isValidDSN(dsn)) {
988
+ const obfuscatedDSN = obfuscateDSNPassword(dsn);
989
+ const expectedFormat = this.getSampleDSN();
990
+ throw new Error(
991
+ `Invalid SQLite DSN format.
992
+ Provided: ${obfuscatedDSN}
993
+ Expected: ${expectedFormat}`
994
+ );
995
+ }
996
+ try {
997
+ const url = new SafeURL(dsn);
998
+ let dbPath;
999
+ if (url.hostname === "" && url.pathname === "/:memory:") {
1000
+ dbPath = ":memory:";
1001
+ } else {
1002
+ if (url.pathname.startsWith("//")) {
1003
+ dbPath = url.pathname.substring(2);
1004
+ } else if (url.pathname.match(/^\/[A-Za-z]:\//)) {
1005
+ dbPath = url.pathname.substring(1);
1006
+ } else {
1007
+ dbPath = url.pathname;
1008
+ }
1009
+ }
1010
+ return { dbPath };
1011
+ } catch (error) {
1012
+ throw new Error(
1013
+ `Failed to parse SQLite DSN: ${error instanceof Error ? error.message : String(error)}`
1014
+ );
1015
+ }
1016
+ }
1017
+ getSampleDSN() {
1018
+ return "sqlite:///path/to/database.db";
1019
+ }
1020
+ isValidDSN(dsn) {
1021
+ try {
1022
+ return dsn.startsWith("sqlite://");
1023
+ } catch (error) {
1024
+ return false;
1025
+ }
1026
+ }
1027
+ };
1028
+ var SQLiteConnector = class _SQLiteConnector {
1029
+ constructor() {
1030
+ this.id = "sqlite";
1031
+ this.name = "SQLite";
1032
+ this.dsnParser = new SQLiteDSNParser();
1033
+ this.db = null;
1034
+ this.dbPath = ":memory:";
1035
+ // Default to in-memory database
1036
+ // Source ID is set by ConnectorManager after cloning
1037
+ this.sourceId = "default";
1038
+ }
1039
+ getId() {
1040
+ return this.sourceId;
1041
+ }
1042
+ clone() {
1043
+ return new _SQLiteConnector();
1044
+ }
1045
+ /**
1046
+ * Connect to SQLite database
1047
+ * Note: SQLite does not support connection timeouts as it's a local file-based database.
1048
+ * The config parameter is accepted for interface compliance but ignored.
1049
+ */
1050
+ async connect(dsn, initScript, config) {
1051
+ const parsedConfig = await this.dsnParser.parse(dsn, config);
1052
+ this.dbPath = parsedConfig.dbPath;
1053
+ try {
1054
+ const dbOptions = {};
1055
+ if (config?.readonly && this.dbPath !== ":memory:") {
1056
+ dbOptions.readonly = true;
1057
+ }
1058
+ this.db = new Database(this.dbPath, dbOptions);
1059
+ if (initScript) {
1060
+ this.db.exec(initScript);
1061
+ }
1062
+ } catch (error) {
1063
+ console.error("Failed to connect to SQLite database:", error);
1064
+ throw error;
1065
+ }
1066
+ }
1067
+ async disconnect() {
1068
+ if (this.db) {
1069
+ try {
1070
+ if (!this.db.inTransaction) {
1071
+ this.db.close();
1072
+ } else {
1073
+ try {
1074
+ this.db.exec("ROLLBACK");
1075
+ } catch (rollbackError) {
1076
+ }
1077
+ this.db.close();
1078
+ }
1079
+ this.db = null;
1080
+ } catch (error) {
1081
+ console.error("Error during SQLite disconnect:", error);
1082
+ this.db = null;
1083
+ }
1084
+ }
1085
+ return Promise.resolve();
1086
+ }
1087
+ async getSchemas() {
1088
+ if (!this.db) {
1089
+ throw new Error("Not connected to SQLite database");
1090
+ }
1091
+ return ["main"];
1092
+ }
1093
+ async getTables(schema) {
1094
+ if (!this.db) {
1095
+ throw new Error("Not connected to SQLite database");
1096
+ }
1097
+ try {
1098
+ const rows = this.db.prepare(
1099
+ `
1100
+ SELECT name FROM sqlite_master
1101
+ WHERE type='table' AND name NOT LIKE 'sqlite_%'
1102
+ ORDER BY name
1103
+ `
1104
+ ).all();
1105
+ return rows.map((row) => row.name);
1106
+ } catch (error) {
1107
+ throw error;
1108
+ }
1109
+ }
1110
+ async tableExists(tableName, schema) {
1111
+ if (!this.db) {
1112
+ throw new Error("Not connected to SQLite database");
1113
+ }
1114
+ try {
1115
+ const row = this.db.prepare(
1116
+ `
1117
+ SELECT name FROM sqlite_master
1118
+ WHERE type='table' AND name = ?
1119
+ `
1120
+ ).get(tableName);
1121
+ return !!row;
1122
+ } catch (error) {
1123
+ throw error;
1124
+ }
1125
+ }
1126
+ async getTableIndexes(tableName, schema) {
1127
+ if (!this.db) {
1128
+ throw new Error("Not connected to SQLite database");
1129
+ }
1130
+ try {
1131
+ const indexInfoRows = this.db.prepare(
1132
+ `
1133
+ SELECT
1134
+ name as index_name,
1135
+ 0 as is_unique
1136
+ FROM sqlite_master
1137
+ WHERE type = 'index'
1138
+ AND tbl_name = ?
1139
+ `
1140
+ ).all(tableName);
1141
+ const quotedTableName = quoteIdentifier(tableName, "sqlite");
1142
+ const indexListRows = this.db.prepare(`PRAGMA index_list(${quotedTableName})`).all();
1143
+ const indexUniqueMap = /* @__PURE__ */ new Map();
1144
+ for (const indexListRow of indexListRows) {
1145
+ indexUniqueMap.set(indexListRow.name, indexListRow.unique === 1);
1146
+ }
1147
+ const tableInfo = this.db.prepare(`PRAGMA table_info(${quotedTableName})`).all();
1148
+ const pkColumns = tableInfo.filter((col) => col.pk > 0).map((col) => col.name);
1149
+ const results = [];
1150
+ for (const indexInfo of indexInfoRows) {
1151
+ const quotedIndexName = quoteIdentifier(indexInfo.index_name, "sqlite");
1152
+ const indexDetailRows = this.db.prepare(`PRAGMA index_info(${quotedIndexName})`).all();
1153
+ const columnNames = indexDetailRows.map((row) => row.name);
1154
+ results.push({
1155
+ index_name: indexInfo.index_name,
1156
+ column_names: columnNames,
1157
+ is_unique: indexUniqueMap.get(indexInfo.index_name) || false,
1158
+ is_primary: false
1159
+ });
1160
+ }
1161
+ if (pkColumns.length > 0) {
1162
+ results.push({
1163
+ index_name: "PRIMARY",
1164
+ column_names: pkColumns,
1165
+ is_unique: true,
1166
+ is_primary: true
1167
+ });
1168
+ }
1169
+ return results;
1170
+ } catch (error) {
1171
+ throw error;
1172
+ }
1173
+ }
1174
+ async getTableSchema(tableName, schema) {
1175
+ if (!this.db) {
1176
+ throw new Error("Not connected to SQLite database");
1177
+ }
1178
+ try {
1179
+ const quotedTableName = quoteIdentifier(tableName, "sqlite");
1180
+ const rows = this.db.prepare(`PRAGMA table_info(${quotedTableName})`).all();
1181
+ const columns = rows.map((row) => ({
1182
+ column_name: row.name,
1183
+ data_type: row.type,
1184
+ // In SQLite, primary key columns are automatically NOT NULL even if notnull=0
1185
+ is_nullable: row.notnull === 1 || row.pk > 0 ? "NO" : "YES",
1186
+ column_default: row.dflt_value
1187
+ }));
1188
+ return columns;
1189
+ } catch (error) {
1190
+ throw error;
1191
+ }
1192
+ }
1193
+ async getStoredProcedures(schema) {
1194
+ if (!this.db) {
1195
+ throw new Error("Not connected to SQLite database");
1196
+ }
1197
+ return [];
1198
+ }
1199
+ async getStoredProcedureDetail(procedureName, schema) {
1200
+ if (!this.db) {
1201
+ throw new Error("Not connected to SQLite database");
1202
+ }
1203
+ throw new Error(
1204
+ "SQLite does not support stored procedures. Functions are defined programmatically through the SQLite API, not stored in the database."
1205
+ );
1206
+ }
1207
+ async executeSQL(sql2, options, parameters) {
1208
+ if (!this.db) {
1209
+ throw new Error("Not connected to SQLite database");
1210
+ }
1211
+ try {
1212
+ const statements = sql2.split(";").map((statement) => statement.trim()).filter((statement) => statement.length > 0);
1213
+ if (statements.length === 1) {
1214
+ let processedStatement = statements[0];
1215
+ const trimmedStatement = statements[0].toLowerCase().trim();
1216
+ const isReadStatement = trimmedStatement.startsWith("select") || trimmedStatement.startsWith("with") || trimmedStatement.startsWith("explain") || trimmedStatement.startsWith("analyze") || trimmedStatement.startsWith("pragma") && (trimmedStatement.includes("table_info") || trimmedStatement.includes("index_info") || trimmedStatement.includes("index_list") || trimmedStatement.includes("foreign_key_list"));
1217
+ if (options.maxRows) {
1218
+ processedStatement = SQLRowLimiter.applyMaxRows(processedStatement, options.maxRows);
1219
+ }
1220
+ if (isReadStatement) {
1221
+ if (parameters && parameters.length > 0) {
1222
+ try {
1223
+ const rows = this.db.prepare(processedStatement).all(...parameters);
1224
+ return { rows, rowCount: rows.length };
1225
+ } catch (error) {
1226
+ console.error(`[SQLite executeSQL] ERROR: ${error.message}`);
1227
+ console.error(`[SQLite executeSQL] SQL: ${processedStatement}`);
1228
+ console.error(`[SQLite executeSQL] Parameters: ${JSON.stringify(parameters)}`);
1229
+ throw error;
1230
+ }
1231
+ } else {
1232
+ const rows = this.db.prepare(processedStatement).all();
1233
+ return { rows, rowCount: rows.length };
1234
+ }
1235
+ } else {
1236
+ let result;
1237
+ if (parameters && parameters.length > 0) {
1238
+ try {
1239
+ result = this.db.prepare(processedStatement).run(...parameters);
1240
+ } catch (error) {
1241
+ console.error(`[SQLite executeSQL] ERROR: ${error.message}`);
1242
+ console.error(`[SQLite executeSQL] SQL: ${processedStatement}`);
1243
+ console.error(`[SQLite executeSQL] Parameters: ${JSON.stringify(parameters)}`);
1244
+ throw error;
1245
+ }
1246
+ } else {
1247
+ result = this.db.prepare(processedStatement).run();
1248
+ }
1249
+ return { rows: [], rowCount: result.changes };
1250
+ }
1251
+ } else {
1252
+ if (parameters && parameters.length > 0) {
1253
+ throw new Error("Parameters are not supported for multi-statement queries in SQLite");
1254
+ }
1255
+ const readStatements = [];
1256
+ const writeStatements = [];
1257
+ for (const statement of statements) {
1258
+ const trimmedStatement = statement.toLowerCase().trim();
1259
+ if (trimmedStatement.startsWith("select") || trimmedStatement.startsWith("with") || trimmedStatement.startsWith("explain") || trimmedStatement.startsWith("analyze") || trimmedStatement.startsWith("pragma") && (trimmedStatement.includes("table_info") || trimmedStatement.includes("index_info") || trimmedStatement.includes("index_list") || trimmedStatement.includes("foreign_key_list"))) {
1260
+ readStatements.push(statement);
1261
+ } else {
1262
+ writeStatements.push(statement);
1263
+ }
1264
+ }
1265
+ let totalChanges = 0;
1266
+ for (const statement of writeStatements) {
1267
+ const result = this.db.prepare(statement).run();
1268
+ totalChanges += result.changes;
1269
+ }
1270
+ let allRows = [];
1271
+ for (let statement of readStatements) {
1272
+ statement = SQLRowLimiter.applyMaxRows(statement, options.maxRows);
1273
+ const result = this.db.prepare(statement).all();
1274
+ allRows.push(...result);
1275
+ }
1276
+ return { rows: allRows, rowCount: totalChanges + allRows.length };
1277
+ }
1278
+ } catch (error) {
1279
+ throw error;
1280
+ }
1281
+ }
1282
+ };
1283
+ var sqliteConnector = new SQLiteConnector();
1284
+ ConnectorRegistry.register(sqliteConnector);
1285
+
1286
+ // src/connectors/mysql/index.ts
1287
+ import mysql from "mysql2/promise";
1288
+
1289
+ // src/utils/multi-statement-result-parser.ts
1290
+ function isMetadataObject(element) {
1291
+ if (!element || typeof element !== "object" || Array.isArray(element)) {
1292
+ return false;
1293
+ }
1294
+ return "affectedRows" in element || "insertId" in element || "fieldCount" in element || "warningStatus" in element;
1295
+ }
1296
+ function isMultiStatementResult(results) {
1297
+ if (!Array.isArray(results) || results.length === 0) {
1298
+ return false;
1299
+ }
1300
+ const firstElement = results[0];
1301
+ return isMetadataObject(firstElement) || Array.isArray(firstElement);
1302
+ }
1303
+ function extractRowsFromMultiStatement(results) {
1304
+ if (!Array.isArray(results)) {
1305
+ return [];
1306
+ }
1307
+ const allRows = [];
1308
+ for (const result of results) {
1309
+ if (Array.isArray(result)) {
1310
+ allRows.push(...result);
1311
+ }
1312
+ }
1313
+ return allRows;
1314
+ }
1315
+ function extractAffectedRows(results) {
1316
+ if (isMetadataObject(results)) {
1317
+ return results.affectedRows || 0;
1318
+ }
1319
+ if (!Array.isArray(results)) {
1320
+ return 0;
1321
+ }
1322
+ if (isMultiStatementResult(results)) {
1323
+ let totalAffected = 0;
1324
+ for (const result of results) {
1325
+ if (isMetadataObject(result)) {
1326
+ totalAffected += result.affectedRows || 0;
1327
+ } else if (Array.isArray(result)) {
1328
+ totalAffected += result.length;
1329
+ }
1330
+ }
1331
+ return totalAffected;
1332
+ }
1333
+ return results.length;
1334
+ }
1335
+ function parseQueryResults(results) {
1336
+ if (!Array.isArray(results)) {
1337
+ return [];
1338
+ }
1339
+ if (isMultiStatementResult(results)) {
1340
+ return extractRowsFromMultiStatement(results);
1341
+ }
1342
+ return results;
1343
+ }
1344
+
1345
+ // src/connectors/mysql/index.ts
1346
+ var MySQLDSNParser = class {
1347
+ async parse(dsn, config) {
1348
+ const connectionTimeoutSeconds = config?.connectionTimeoutSeconds;
1349
+ if (!this.isValidDSN(dsn)) {
1350
+ const obfuscatedDSN = obfuscateDSNPassword(dsn);
1351
+ const expectedFormat = this.getSampleDSN();
1352
+ throw new Error(
1353
+ `Invalid MySQL DSN format.
1354
+ Provided: ${obfuscatedDSN}
1355
+ Expected: ${expectedFormat}`
1356
+ );
1357
+ }
1358
+ try {
1359
+ const url = new SafeURL(dsn);
1360
+ const config2 = {
1361
+ host: url.hostname,
1362
+ port: url.port ? parseInt(url.port) : 3306,
1363
+ database: url.pathname ? url.pathname.substring(1) : "",
1364
+ // Remove leading '/' if exists
1365
+ user: url.username,
1366
+ password: url.password,
1367
+ multipleStatements: true
1368
+ // Enable native multi-statement support
1369
+ };
1370
+ url.forEachSearchParam((value, key) => {
1371
+ if (key === "sslmode") {
1372
+ if (value === "disable") {
1373
+ config2.ssl = void 0;
1374
+ } else if (value === "require") {
1375
+ config2.ssl = { rejectUnauthorized: false };
1376
+ } else {
1377
+ config2.ssl = {};
1378
+ }
1379
+ }
1380
+ });
1381
+ if (connectionTimeoutSeconds !== void 0) {
1382
+ config2.connectTimeout = connectionTimeoutSeconds * 1e3;
1383
+ }
1384
+ if (url.password && url.password.includes("X-Amz-Credential")) {
1385
+ config2.authPlugins = {
1386
+ mysql_clear_password: () => () => {
1387
+ return Buffer.from(url.password + "\0");
1388
+ }
1389
+ };
1390
+ if (config2.ssl === void 0) {
1391
+ config2.ssl = { rejectUnauthorized: false };
1392
+ }
1393
+ }
1394
+ return config2;
1395
+ } catch (error) {
1396
+ throw new Error(
1397
+ `Failed to parse MySQL DSN: ${error instanceof Error ? error.message : String(error)}`
1398
+ );
1399
+ }
1400
+ }
1401
+ getSampleDSN() {
1402
+ return "mysql://root:password@localhost:3306/mysql?sslmode=require";
1403
+ }
1404
+ isValidDSN(dsn) {
1405
+ try {
1406
+ return dsn.startsWith("mysql://");
1407
+ } catch (error) {
1408
+ return false;
1409
+ }
1410
+ }
1411
+ };
1412
+ var MySQLConnector = class _MySQLConnector {
1413
+ constructor() {
1414
+ this.id = "mysql";
1415
+ this.name = "MySQL";
1416
+ this.dsnParser = new MySQLDSNParser();
1417
+ this.pool = null;
1418
+ // Source ID is set by ConnectorManager after cloning
1419
+ this.sourceId = "default";
1420
+ }
1421
+ getId() {
1422
+ return this.sourceId;
1423
+ }
1424
+ clone() {
1425
+ return new _MySQLConnector();
1426
+ }
1427
+ async connect(dsn, initScript, config) {
1428
+ try {
1429
+ const connectionOptions = await this.dsnParser.parse(dsn, config);
1430
+ this.pool = mysql.createPool(connectionOptions);
1431
+ if (config?.queryTimeoutSeconds !== void 0) {
1432
+ this.queryTimeoutMs = config.queryTimeoutSeconds * 1e3;
1433
+ }
1434
+ const [rows] = await this.pool.query("SELECT 1");
1435
+ } catch (err) {
1436
+ console.error("Failed to connect to MySQL database:", err);
1437
+ throw err;
1438
+ }
1439
+ }
1440
+ async disconnect() {
1441
+ if (this.pool) {
1442
+ await this.pool.end();
1443
+ this.pool = null;
1444
+ }
1445
+ }
1446
+ async getSchemas() {
1447
+ if (!this.pool) {
1448
+ throw new Error("Not connected to database");
1449
+ }
1450
+ try {
1451
+ const [rows] = await this.pool.query(`
1452
+ SELECT SCHEMA_NAME
1453
+ FROM INFORMATION_SCHEMA.SCHEMATA
1454
+ ORDER BY SCHEMA_NAME
1455
+ `);
1456
+ return rows.map((row) => row.SCHEMA_NAME);
1457
+ } catch (error) {
1458
+ console.error("Error getting schemas:", error);
1459
+ throw error;
1460
+ }
1461
+ }
1462
+ async getTables(schema) {
1463
+ if (!this.pool) {
1464
+ throw new Error("Not connected to database");
1465
+ }
1466
+ try {
1467
+ const schemaClause = schema ? "WHERE TABLE_SCHEMA = ?" : "WHERE TABLE_SCHEMA = DATABASE()";
1468
+ const queryParams = schema ? [schema] : [];
1469
+ const [rows] = await this.pool.query(
1470
+ `
1471
+ SELECT TABLE_NAME
1472
+ FROM INFORMATION_SCHEMA.TABLES
1473
+ ${schemaClause}
1474
+ ORDER BY TABLE_NAME
1475
+ `,
1476
+ queryParams
1477
+ );
1478
+ return rows.map((row) => row.TABLE_NAME);
1479
+ } catch (error) {
1480
+ console.error("Error getting tables:", error);
1481
+ throw error;
1482
+ }
1483
+ }
1484
+ async tableExists(tableName, schema) {
1485
+ if (!this.pool) {
1486
+ throw new Error("Not connected to database");
1487
+ }
1488
+ try {
1489
+ const schemaClause = schema ? "WHERE TABLE_SCHEMA = ?" : "WHERE TABLE_SCHEMA = DATABASE()";
1490
+ const queryParams = schema ? [schema, tableName] : [tableName];
1491
+ const [rows] = await this.pool.query(
1492
+ `
1493
+ SELECT COUNT(*) AS COUNT
1494
+ FROM INFORMATION_SCHEMA.TABLES
1495
+ ${schemaClause}
1496
+ AND TABLE_NAME = ?
1497
+ `,
1498
+ queryParams
1499
+ );
1500
+ return rows[0].COUNT > 0;
1501
+ } catch (error) {
1502
+ console.error("Error checking if table exists:", error);
1503
+ throw error;
1504
+ }
1505
+ }
1506
+ async getTableIndexes(tableName, schema) {
1507
+ if (!this.pool) {
1508
+ throw new Error("Not connected to database");
1509
+ }
1510
+ try {
1511
+ const schemaClause = schema ? "TABLE_SCHEMA = ?" : "TABLE_SCHEMA = DATABASE()";
1512
+ const queryParams = schema ? [schema, tableName] : [tableName];
1513
+ const [indexRows] = await this.pool.query(
1514
+ `
1515
+ SELECT
1516
+ INDEX_NAME,
1517
+ COLUMN_NAME,
1518
+ NON_UNIQUE,
1519
+ SEQ_IN_INDEX
1520
+ FROM
1521
+ INFORMATION_SCHEMA.STATISTICS
1522
+ WHERE
1523
+ ${schemaClause}
1524
+ AND TABLE_NAME = ?
1525
+ ORDER BY
1526
+ INDEX_NAME,
1527
+ SEQ_IN_INDEX
1528
+ `,
1529
+ queryParams
1530
+ );
1531
+ const indexMap = /* @__PURE__ */ new Map();
1532
+ for (const row of indexRows) {
1533
+ const indexName = row.INDEX_NAME;
1534
+ const columnName = row.COLUMN_NAME;
1535
+ const isUnique = row.NON_UNIQUE === 0;
1536
+ const isPrimary = indexName === "PRIMARY";
1537
+ if (!indexMap.has(indexName)) {
1538
+ indexMap.set(indexName, {
1539
+ columns: [],
1540
+ is_unique: isUnique,
1541
+ is_primary: isPrimary
1542
+ });
1543
+ }
1544
+ const indexInfo = indexMap.get(indexName);
1545
+ indexInfo.columns.push(columnName);
1546
+ }
1547
+ const results = [];
1548
+ indexMap.forEach((indexInfo, indexName) => {
1549
+ results.push({
1550
+ index_name: indexName,
1551
+ column_names: indexInfo.columns,
1552
+ is_unique: indexInfo.is_unique,
1553
+ is_primary: indexInfo.is_primary
1554
+ });
1555
+ });
1556
+ return results;
1557
+ } catch (error) {
1558
+ console.error("Error getting table indexes:", error);
1559
+ throw error;
1560
+ }
1561
+ }
1562
+ async getTableSchema(tableName, schema) {
1563
+ if (!this.pool) {
1564
+ throw new Error("Not connected to database");
1565
+ }
1566
+ try {
1567
+ const schemaClause = schema ? "WHERE TABLE_SCHEMA = ?" : "WHERE TABLE_SCHEMA = DATABASE()";
1568
+ const queryParams = schema ? [schema, tableName] : [tableName];
1569
+ const [rows] = await this.pool.query(
1570
+ `
1571
+ SELECT
1572
+ COLUMN_NAME as column_name,
1573
+ DATA_TYPE as data_type,
1574
+ IS_NULLABLE as is_nullable,
1575
+ COLUMN_DEFAULT as column_default
1576
+ FROM INFORMATION_SCHEMA.COLUMNS
1577
+ ${schemaClause}
1578
+ AND TABLE_NAME = ?
1579
+ ORDER BY ORDINAL_POSITION
1580
+ `,
1581
+ queryParams
1582
+ );
1583
+ return rows;
1584
+ } catch (error) {
1585
+ console.error("Error getting table schema:", error);
1586
+ throw error;
1587
+ }
1588
+ }
1589
+ async getStoredProcedures(schema) {
1590
+ if (!this.pool) {
1591
+ throw new Error("Not connected to database");
1592
+ }
1593
+ try {
1594
+ const schemaClause = schema ? "WHERE ROUTINE_SCHEMA = ?" : "WHERE ROUTINE_SCHEMA = DATABASE()";
1595
+ const queryParams = schema ? [schema] : [];
1596
+ const [rows] = await this.pool.query(
1597
+ `
1598
+ SELECT ROUTINE_NAME
1599
+ FROM INFORMATION_SCHEMA.ROUTINES
1600
+ ${schemaClause}
1601
+ ORDER BY ROUTINE_NAME
1602
+ `,
1603
+ queryParams
1604
+ );
1605
+ return rows.map((row) => row.ROUTINE_NAME);
1606
+ } catch (error) {
1607
+ console.error("Error getting stored procedures:", error);
1608
+ throw error;
1609
+ }
1610
+ }
1611
+ async getStoredProcedureDetail(procedureName, schema) {
1612
+ if (!this.pool) {
1613
+ throw new Error("Not connected to database");
1614
+ }
1615
+ try {
1616
+ const schemaClause = schema ? "WHERE r.ROUTINE_SCHEMA = ?" : "WHERE r.ROUTINE_SCHEMA = DATABASE()";
1617
+ const queryParams = schema ? [schema, procedureName] : [procedureName];
1618
+ const [rows] = await this.pool.query(
1619
+ `
1620
+ SELECT
1621
+ r.ROUTINE_NAME AS procedure_name,
1622
+ CASE
1623
+ WHEN r.ROUTINE_TYPE = 'PROCEDURE' THEN 'procedure'
1624
+ ELSE 'function'
1625
+ END AS procedure_type,
1626
+ LOWER(r.ROUTINE_TYPE) AS routine_type,
1627
+ r.ROUTINE_DEFINITION,
1628
+ r.DTD_IDENTIFIER AS return_type,
1629
+ (
1630
+ SELECT GROUP_CONCAT(
1631
+ CONCAT(p.PARAMETER_NAME, ' ', p.PARAMETER_MODE, ' ', p.DATA_TYPE)
1632
+ ORDER BY p.ORDINAL_POSITION
1633
+ SEPARATOR ', '
1634
+ )
1635
+ FROM INFORMATION_SCHEMA.PARAMETERS p
1636
+ WHERE p.SPECIFIC_SCHEMA = r.ROUTINE_SCHEMA
1637
+ AND p.SPECIFIC_NAME = r.ROUTINE_NAME
1638
+ AND p.PARAMETER_NAME IS NOT NULL
1639
+ ) AS parameter_list
1640
+ FROM INFORMATION_SCHEMA.ROUTINES r
1641
+ ${schemaClause}
1642
+ AND r.ROUTINE_NAME = ?
1643
+ `,
1644
+ queryParams
1645
+ );
1646
+ if (rows.length === 0) {
1647
+ const schemaName = schema || "current schema";
1648
+ throw new Error(`Stored procedure '${procedureName}' not found in ${schemaName}`);
1649
+ }
1650
+ const procedure = rows[0];
1651
+ let definition = procedure.ROUTINE_DEFINITION;
1652
+ try {
1653
+ const schemaValue = schema || await this.getCurrentSchema();
1654
+ if (procedure.procedure_type === "procedure") {
1655
+ try {
1656
+ const [defRows] = await this.pool.query(`
1657
+ SHOW CREATE PROCEDURE ${schemaValue}.${procedureName}
1658
+ `);
1659
+ if (defRows && defRows.length > 0) {
1660
+ definition = defRows[0]["Create Procedure"];
1661
+ }
1662
+ } catch (err) {
1663
+ console.error(`Error getting procedure definition with SHOW CREATE: ${err}`);
1664
+ }
1665
+ } else {
1666
+ try {
1667
+ const [defRows] = await this.pool.query(`
1668
+ SHOW CREATE FUNCTION ${schemaValue}.${procedureName}
1669
+ `);
1670
+ if (defRows && defRows.length > 0) {
1671
+ definition = defRows[0]["Create Function"];
1672
+ }
1673
+ } catch (innerErr) {
1674
+ console.error(`Error getting function definition with SHOW CREATE: ${innerErr}`);
1675
+ }
1676
+ }
1677
+ if (!definition) {
1678
+ const [bodyRows] = await this.pool.query(
1679
+ `
1680
+ SELECT ROUTINE_DEFINITION, ROUTINE_BODY
1681
+ FROM INFORMATION_SCHEMA.ROUTINES
1682
+ WHERE ROUTINE_SCHEMA = ? AND ROUTINE_NAME = ?
1683
+ `,
1684
+ [schemaValue, procedureName]
1685
+ );
1686
+ if (bodyRows && bodyRows.length > 0) {
1687
+ if (bodyRows[0].ROUTINE_DEFINITION) {
1688
+ definition = bodyRows[0].ROUTINE_DEFINITION;
1689
+ } else if (bodyRows[0].ROUTINE_BODY) {
1690
+ definition = bodyRows[0].ROUTINE_BODY;
1691
+ }
1692
+ }
1693
+ }
1694
+ } catch (error) {
1695
+ console.error(`Error getting procedure/function details: ${error}`);
1696
+ }
1697
+ return {
1698
+ procedure_name: procedure.procedure_name,
1699
+ procedure_type: procedure.procedure_type,
1700
+ language: "sql",
1701
+ // MySQL procedures are generally in SQL
1702
+ parameter_list: procedure.parameter_list || "",
1703
+ return_type: procedure.routine_type === "function" ? procedure.return_type : void 0,
1704
+ definition: definition || void 0
1705
+ };
1706
+ } catch (error) {
1707
+ console.error("Error getting stored procedure detail:", error);
1708
+ throw error;
1709
+ }
1710
+ }
1711
+ // Helper method to get current schema (database) name
1712
+ async getCurrentSchema() {
1713
+ const [rows] = await this.pool.query("SELECT DATABASE() AS DB");
1714
+ return rows[0].DB;
1715
+ }
1716
+ async executeSQL(sql2, options, parameters) {
1717
+ if (!this.pool) {
1718
+ throw new Error("Not connected to database");
1719
+ }
1720
+ const conn = await this.pool.getConnection();
1721
+ try {
1722
+ let processedSQL = sql2;
1723
+ if (options.maxRows) {
1724
+ const statements = sql2.split(";").map((statement) => statement.trim()).filter((statement) => statement.length > 0);
1725
+ const processedStatements = statements.map(
1726
+ (statement) => SQLRowLimiter.applyMaxRows(statement, options.maxRows)
1727
+ );
1728
+ processedSQL = processedStatements.join("; ");
1729
+ if (sql2.trim().endsWith(";")) {
1730
+ processedSQL += ";";
1731
+ }
1732
+ }
1733
+ let results;
1734
+ if (parameters && parameters.length > 0) {
1735
+ try {
1736
+ results = await conn.query({ sql: processedSQL, timeout: this.queryTimeoutMs }, parameters);
1737
+ } catch (error) {
1738
+ console.error(`[MySQL executeSQL] ERROR: ${error.message}`);
1739
+ console.error(`[MySQL executeSQL] SQL: ${processedSQL}`);
1740
+ console.error(`[MySQL executeSQL] Parameters: ${JSON.stringify(parameters)}`);
1741
+ throw error;
1742
+ }
1743
+ } else {
1744
+ results = await conn.query({ sql: processedSQL, timeout: this.queryTimeoutMs });
1745
+ }
1746
+ const [firstResult] = results;
1747
+ const rows = parseQueryResults(firstResult);
1748
+ const rowCount = extractAffectedRows(firstResult);
1749
+ return { rows, rowCount };
1750
+ } catch (error) {
1751
+ console.error("Error executing query:", error);
1752
+ throw error;
1753
+ } finally {
1754
+ conn.release();
1755
+ }
1756
+ }
1757
+ };
1758
+ var mysqlConnector = new MySQLConnector();
1759
+ ConnectorRegistry.register(mysqlConnector);
1760
+
1761
+ // src/connectors/mariadb/index.ts
1762
+ import mariadb from "mariadb";
1763
+ var MariadbDSNParser = class {
1764
+ async parse(dsn, config) {
1765
+ const connectionTimeoutSeconds = config?.connectionTimeoutSeconds;
1766
+ const queryTimeoutSeconds = config?.queryTimeoutSeconds;
1767
+ if (!this.isValidDSN(dsn)) {
1768
+ const obfuscatedDSN = obfuscateDSNPassword(dsn);
1769
+ const expectedFormat = this.getSampleDSN();
1770
+ throw new Error(
1771
+ `Invalid MariaDB DSN format.
1772
+ Provided: ${obfuscatedDSN}
1773
+ Expected: ${expectedFormat}`
1774
+ );
1775
+ }
1776
+ try {
1777
+ const url = new SafeURL(dsn);
1778
+ const connectionConfig = {
1779
+ host: url.hostname,
1780
+ port: url.port ? parseInt(url.port) : 3306,
1781
+ database: url.pathname ? url.pathname.substring(1) : "",
1782
+ // Remove leading '/' if exists
1783
+ user: url.username,
1784
+ password: url.password,
1785
+ multipleStatements: true,
1786
+ // Enable native multi-statement support
1787
+ ...connectionTimeoutSeconds !== void 0 && {
1788
+ connectTimeout: connectionTimeoutSeconds * 1e3
1789
+ },
1790
+ ...queryTimeoutSeconds !== void 0 && {
1791
+ queryTimeout: queryTimeoutSeconds * 1e3
1792
+ }
1793
+ };
1794
+ url.forEachSearchParam((value, key) => {
1795
+ if (key === "sslmode") {
1796
+ if (value === "disable") {
1797
+ connectionConfig.ssl = void 0;
1798
+ } else if (value === "require") {
1799
+ connectionConfig.ssl = { rejectUnauthorized: false };
1800
+ } else {
1801
+ connectionConfig.ssl = {};
1802
+ }
1803
+ }
1804
+ });
1805
+ if (url.password && url.password.includes("X-Amz-Credential")) {
1806
+ if (connectionConfig.ssl === void 0) {
1807
+ connectionConfig.ssl = { rejectUnauthorized: false };
1808
+ }
1809
+ }
1810
+ return connectionConfig;
1811
+ } catch (error) {
1812
+ throw new Error(
1813
+ `Failed to parse MariaDB DSN: ${error instanceof Error ? error.message : String(error)}`
1814
+ );
1815
+ }
1816
+ }
1817
+ getSampleDSN() {
1818
+ return "mariadb://root:password@localhost:3306/db?sslmode=require";
1819
+ }
1820
+ isValidDSN(dsn) {
1821
+ try {
1822
+ return dsn.startsWith("mariadb://");
1823
+ } catch (error) {
1824
+ return false;
1825
+ }
1826
+ }
1827
+ };
1828
+ var MariaDBConnector = class _MariaDBConnector {
1829
+ constructor() {
1830
+ this.id = "mariadb";
1831
+ this.name = "MariaDB";
1832
+ this.dsnParser = new MariadbDSNParser();
1833
+ this.pool = null;
1834
+ // Source ID is set by ConnectorManager after cloning
1835
+ this.sourceId = "default";
1836
+ }
1837
+ getId() {
1838
+ return this.sourceId;
1839
+ }
1840
+ clone() {
1841
+ return new _MariaDBConnector();
1842
+ }
1843
+ async connect(dsn, initScript, config) {
1844
+ try {
1845
+ const connectionConfig = await this.dsnParser.parse(dsn, config);
1846
+ this.pool = mariadb.createPool(connectionConfig);
1847
+ await this.pool.query("SELECT 1");
1848
+ } catch (err) {
1849
+ console.error("Failed to connect to MariaDB database:", err);
1850
+ throw err;
1851
+ }
1852
+ }
1853
+ async disconnect() {
1854
+ if (this.pool) {
1855
+ await this.pool.end();
1856
+ this.pool = null;
1857
+ }
1858
+ }
1859
+ async getSchemas() {
1860
+ if (!this.pool) {
1861
+ throw new Error("Not connected to database");
1862
+ }
1863
+ try {
1864
+ const rows = await this.pool.query(`
1865
+ SELECT SCHEMA_NAME
1866
+ FROM INFORMATION_SCHEMA.SCHEMATA
1867
+ ORDER BY SCHEMA_NAME
1868
+ `);
1869
+ return rows.map((row) => row.SCHEMA_NAME);
1870
+ } catch (error) {
1871
+ console.error("Error getting schemas:", error);
1872
+ throw error;
1873
+ }
1874
+ }
1875
+ async getTables(schema) {
1876
+ if (!this.pool) {
1877
+ throw new Error("Not connected to database");
1878
+ }
1879
+ try {
1880
+ const schemaClause = schema ? "WHERE TABLE_SCHEMA = ?" : "WHERE TABLE_SCHEMA = DATABASE()";
1881
+ const queryParams = schema ? [schema] : [];
1882
+ const rows = await this.pool.query(
1883
+ `
1884
+ SELECT TABLE_NAME
1885
+ FROM INFORMATION_SCHEMA.TABLES
1886
+ ${schemaClause}
1887
+ ORDER BY TABLE_NAME
1888
+ `,
1889
+ queryParams
1890
+ );
1891
+ return rows.map((row) => row.TABLE_NAME);
1892
+ } catch (error) {
1893
+ console.error("Error getting tables:", error);
1894
+ throw error;
1895
+ }
1896
+ }
1897
+ async tableExists(tableName, schema) {
1898
+ if (!this.pool) {
1899
+ throw new Error("Not connected to database");
1900
+ }
1901
+ try {
1902
+ const schemaClause = schema ? "WHERE TABLE_SCHEMA = ?" : "WHERE TABLE_SCHEMA = DATABASE()";
1903
+ const queryParams = schema ? [schema, tableName] : [tableName];
1904
+ const rows = await this.pool.query(
1905
+ `
1906
+ SELECT COUNT(*) AS COUNT
1907
+ FROM INFORMATION_SCHEMA.TABLES
1908
+ ${schemaClause}
1909
+ AND TABLE_NAME = ?
1910
+ `,
1911
+ queryParams
1912
+ );
1913
+ return rows[0].COUNT > 0;
1914
+ } catch (error) {
1915
+ console.error("Error checking if table exists:", error);
1916
+ throw error;
1917
+ }
1918
+ }
1919
+ async getTableIndexes(tableName, schema) {
1920
+ if (!this.pool) {
1921
+ throw new Error("Not connected to database");
1922
+ }
1923
+ try {
1924
+ const schemaClause = schema ? "TABLE_SCHEMA = ?" : "TABLE_SCHEMA = DATABASE()";
1925
+ const queryParams = schema ? [schema, tableName] : [tableName];
1926
+ const indexRows = await this.pool.query(
1927
+ `
1928
+ SELECT
1929
+ INDEX_NAME,
1930
+ COLUMN_NAME,
1931
+ NON_UNIQUE,
1932
+ SEQ_IN_INDEX
1933
+ FROM
1934
+ INFORMATION_SCHEMA.STATISTICS
1935
+ WHERE
1936
+ ${schemaClause}
1937
+ AND TABLE_NAME = ?
1938
+ ORDER BY
1939
+ INDEX_NAME,
1940
+ SEQ_IN_INDEX
1941
+ `,
1942
+ queryParams
1943
+ );
1944
+ const indexMap = /* @__PURE__ */ new Map();
1945
+ for (const row of indexRows) {
1946
+ const indexName = row.INDEX_NAME;
1947
+ const columnName = row.COLUMN_NAME;
1948
+ const isUnique = row.NON_UNIQUE === 0;
1949
+ const isPrimary = indexName === "PRIMARY";
1950
+ if (!indexMap.has(indexName)) {
1951
+ indexMap.set(indexName, {
1952
+ columns: [],
1953
+ is_unique: isUnique,
1954
+ is_primary: isPrimary
1955
+ });
1956
+ }
1957
+ const indexInfo = indexMap.get(indexName);
1958
+ indexInfo.columns.push(columnName);
1959
+ }
1960
+ const results = [];
1961
+ indexMap.forEach((indexInfo, indexName) => {
1962
+ results.push({
1963
+ index_name: indexName,
1964
+ column_names: indexInfo.columns,
1965
+ is_unique: indexInfo.is_unique,
1966
+ is_primary: indexInfo.is_primary
1967
+ });
1968
+ });
1969
+ return results;
1970
+ } catch (error) {
1971
+ console.error("Error getting table indexes:", error);
1972
+ throw error;
1973
+ }
1974
+ }
1975
+ async getTableSchema(tableName, schema) {
1976
+ if (!this.pool) {
1977
+ throw new Error("Not connected to database");
1978
+ }
1979
+ try {
1980
+ const schemaClause = schema ? "WHERE TABLE_SCHEMA = ?" : "WHERE TABLE_SCHEMA = DATABASE()";
1981
+ const queryParams = schema ? [schema, tableName] : [tableName];
1982
+ const rows = await this.pool.query(
1983
+ `
1984
+ SELECT
1985
+ COLUMN_NAME as column_name,
1986
+ DATA_TYPE as data_type,
1987
+ IS_NULLABLE as is_nullable,
1988
+ COLUMN_DEFAULT as column_default
1989
+ FROM INFORMATION_SCHEMA.COLUMNS
1990
+ ${schemaClause}
1991
+ AND TABLE_NAME = ?
1992
+ ORDER BY ORDINAL_POSITION
1993
+ `,
1994
+ queryParams
1995
+ );
1996
+ return rows;
1997
+ } catch (error) {
1998
+ console.error("Error getting table schema:", error);
1999
+ throw error;
2000
+ }
2001
+ }
2002
+ async getStoredProcedures(schema) {
2003
+ if (!this.pool) {
2004
+ throw new Error("Not connected to database");
2005
+ }
2006
+ try {
2007
+ const schemaClause = schema ? "WHERE ROUTINE_SCHEMA = ?" : "WHERE ROUTINE_SCHEMA = DATABASE()";
2008
+ const queryParams = schema ? [schema] : [];
2009
+ const rows = await this.pool.query(
2010
+ `
2011
+ SELECT ROUTINE_NAME
2012
+ FROM INFORMATION_SCHEMA.ROUTINES
2013
+ ${schemaClause}
2014
+ ORDER BY ROUTINE_NAME
2015
+ `,
2016
+ queryParams
2017
+ );
2018
+ return rows.map((row) => row.ROUTINE_NAME);
2019
+ } catch (error) {
2020
+ console.error("Error getting stored procedures:", error);
2021
+ throw error;
2022
+ }
2023
+ }
2024
+ async getStoredProcedureDetail(procedureName, schema) {
2025
+ if (!this.pool) {
2026
+ throw new Error("Not connected to database");
2027
+ }
2028
+ try {
2029
+ const schemaClause = schema ? "WHERE r.ROUTINE_SCHEMA = ?" : "WHERE r.ROUTINE_SCHEMA = DATABASE()";
2030
+ const queryParams = schema ? [schema, procedureName] : [procedureName];
2031
+ const rows = await this.pool.query(
2032
+ `
2033
+ SELECT
2034
+ r.ROUTINE_NAME AS procedure_name,
2035
+ CASE
2036
+ WHEN r.ROUTINE_TYPE = 'PROCEDURE' THEN 'procedure'
2037
+ ELSE 'function'
2038
+ END AS procedure_type,
2039
+ LOWER(r.ROUTINE_TYPE) AS routine_type,
2040
+ r.ROUTINE_DEFINITION,
2041
+ r.DTD_IDENTIFIER AS return_type,
2042
+ (
2043
+ SELECT GROUP_CONCAT(
2044
+ CONCAT(p.PARAMETER_NAME, ' ', p.PARAMETER_MODE, ' ', p.DATA_TYPE)
2045
+ ORDER BY p.ORDINAL_POSITION
2046
+ SEPARATOR ', '
2047
+ )
2048
+ FROM INFORMATION_SCHEMA.PARAMETERS p
2049
+ WHERE p.SPECIFIC_SCHEMA = r.ROUTINE_SCHEMA
2050
+ AND p.SPECIFIC_NAME = r.ROUTINE_NAME
2051
+ AND p.PARAMETER_NAME IS NOT NULL
2052
+ ) AS parameter_list
2053
+ FROM INFORMATION_SCHEMA.ROUTINES r
2054
+ ${schemaClause}
2055
+ AND r.ROUTINE_NAME = ?
2056
+ `,
2057
+ queryParams
2058
+ );
2059
+ if (rows.length === 0) {
2060
+ const schemaName = schema || "current schema";
2061
+ throw new Error(`Stored procedure '${procedureName}' not found in ${schemaName}`);
2062
+ }
2063
+ const procedure = rows[0];
2064
+ let definition = procedure.ROUTINE_DEFINITION;
2065
+ try {
2066
+ const schemaValue = schema || await this.getCurrentSchema();
2067
+ if (procedure.procedure_type === "procedure") {
2068
+ try {
2069
+ const defRows = await this.pool.query(`
2070
+ SHOW CREATE PROCEDURE ${schemaValue}.${procedureName}
2071
+ `);
2072
+ if (defRows && defRows.length > 0) {
2073
+ definition = defRows[0]["Create Procedure"];
2074
+ }
2075
+ } catch (err) {
2076
+ console.error(`Error getting procedure definition with SHOW CREATE: ${err}`);
2077
+ }
2078
+ } else {
2079
+ try {
2080
+ const defRows = await this.pool.query(`
2081
+ SHOW CREATE FUNCTION ${schemaValue}.${procedureName}
2082
+ `);
2083
+ if (defRows && defRows.length > 0) {
2084
+ definition = defRows[0]["Create Function"];
2085
+ }
2086
+ } catch (innerErr) {
2087
+ console.error(`Error getting function definition with SHOW CREATE: ${innerErr}`);
2088
+ }
2089
+ }
2090
+ if (!definition) {
2091
+ const bodyRows = await this.pool.query(
2092
+ `
2093
+ SELECT ROUTINE_DEFINITION, ROUTINE_BODY
2094
+ FROM INFORMATION_SCHEMA.ROUTINES
2095
+ WHERE ROUTINE_SCHEMA = ? AND ROUTINE_NAME = ?
2096
+ `,
2097
+ [schemaValue, procedureName]
2098
+ );
2099
+ if (bodyRows && bodyRows.length > 0) {
2100
+ if (bodyRows[0].ROUTINE_DEFINITION) {
2101
+ definition = bodyRows[0].ROUTINE_DEFINITION;
2102
+ } else if (bodyRows[0].ROUTINE_BODY) {
2103
+ definition = bodyRows[0].ROUTINE_BODY;
2104
+ }
2105
+ }
2106
+ }
2107
+ } catch (error) {
2108
+ console.error(`Error getting procedure/function details: ${error}`);
2109
+ }
2110
+ return {
2111
+ procedure_name: procedure.procedure_name,
2112
+ procedure_type: procedure.procedure_type,
2113
+ language: "sql",
2114
+ // MariaDB procedures are generally in SQL
2115
+ parameter_list: procedure.parameter_list || "",
2116
+ return_type: procedure.routine_type === "function" ? procedure.return_type : void 0,
2117
+ definition: definition || void 0
2118
+ };
2119
+ } catch (error) {
2120
+ console.error("Error getting stored procedure detail:", error);
2121
+ throw error;
2122
+ }
2123
+ }
2124
+ // Helper method to get current schema (database) name
2125
+ async getCurrentSchema() {
2126
+ const rows = await this.pool.query("SELECT DATABASE() AS DB");
2127
+ return rows[0].DB;
2128
+ }
2129
+ async executeSQL(sql2, options, parameters) {
2130
+ if (!this.pool) {
2131
+ throw new Error("Not connected to database");
2132
+ }
2133
+ const conn = await this.pool.getConnection();
2134
+ try {
2135
+ let processedSQL = sql2;
2136
+ if (options.maxRows) {
2137
+ const statements = sql2.split(";").map((statement) => statement.trim()).filter((statement) => statement.length > 0);
2138
+ const processedStatements = statements.map(
2139
+ (statement) => SQLRowLimiter.applyMaxRows(statement, options.maxRows)
2140
+ );
2141
+ processedSQL = processedStatements.join("; ");
2142
+ if (sql2.trim().endsWith(";")) {
2143
+ processedSQL += ";";
2144
+ }
2145
+ }
2146
+ let results;
2147
+ if (parameters && parameters.length > 0) {
2148
+ try {
2149
+ results = await conn.query(processedSQL, parameters);
2150
+ } catch (error) {
2151
+ console.error(`[MariaDB executeSQL] ERROR: ${error.message}`);
2152
+ console.error(`[MariaDB executeSQL] SQL: ${processedSQL}`);
2153
+ console.error(`[MariaDB executeSQL] Parameters: ${JSON.stringify(parameters)}`);
2154
+ throw error;
2155
+ }
2156
+ } else {
2157
+ results = await conn.query(processedSQL);
2158
+ }
2159
+ const rows = parseQueryResults(results);
2160
+ const rowCount = extractAffectedRows(results);
2161
+ return { rows, rowCount };
2162
+ } catch (error) {
2163
+ console.error("Error executing query:", error);
2164
+ throw error;
2165
+ } finally {
2166
+ conn.release();
2167
+ }
2168
+ }
2169
+ };
2170
+ var mariadbConnector = new MariaDBConnector();
2171
+ ConnectorRegistry.register(mariadbConnector);
2172
+
2173
+ // src/connectors/redis/index.ts
2174
+ import { createClient } from "redis";
2175
+ import { URL } from "url";
2176
+ var RedisDSNParser = class {
2177
+ parse(dsn) {
2178
+ try {
2179
+ const url = new URL(dsn);
2180
+ if (url.protocol !== "redis:" && url.protocol !== "rediss:") {
2181
+ throw new Error("Invalid Redis DSN protocol");
2182
+ }
2183
+ const host = url.hostname || "localhost";
2184
+ const port = url.port ? parseInt(url.port, 10) : 6379;
2185
+ const db = url.pathname ? parseInt(url.pathname.slice(1), 10) : 0;
2186
+ const username = url.username || void 0;
2187
+ const password = url.password || void 0;
2188
+ return {
2189
+ host,
2190
+ port,
2191
+ db,
2192
+ username,
2193
+ password,
2194
+ tls: url.protocol === "rediss:"
2195
+ };
2196
+ } catch (error) {
2197
+ throw new Error(`Failed to parse Redis DSN: ${error}`);
2198
+ }
2199
+ }
2200
+ getSampleDSN() {
2201
+ return "redis://localhost:6379/0";
2202
+ }
2203
+ isValidDSN(dsn) {
2204
+ try {
2205
+ const url = new URL(dsn);
2206
+ return url.protocol === "redis:" || url.protocol === "rediss:";
2207
+ } catch {
2208
+ return false;
2209
+ }
2210
+ }
2211
+ };
2212
+ var RedisConnector = class _RedisConnector {
2213
+ constructor() {
2214
+ this.id = "redis";
2215
+ this.name = "Redis";
2216
+ this.dsnParser = new RedisDSNParser();
2217
+ this.client = null;
2218
+ this.sourceId = "default";
2219
+ }
2220
+ getId() {
2221
+ return this.sourceId;
2222
+ }
2223
+ clone() {
2224
+ return new _RedisConnector();
2225
+ }
2226
+ async connect(dsn, _initScript, config) {
2227
+ try {
2228
+ const options = this.dsnParser.parse(dsn);
2229
+ const redisUrl = `redis://${options.username ? `${options.username}:` : ""}${options.password || ""}${options.username || options.password ? "@" : ""}${options.host}:${options.port}/${options.db}`;
2230
+ this.client = createClient({
2231
+ url: redisUrl,
2232
+ socket: {
2233
+ connectTimeout: (config?.connectionTimeoutSeconds || 10) * 1e3
2234
+ }
2235
+ });
2236
+ this.client.on(
2237
+ "error",
2238
+ (err) => console.error("Redis Client Error:", err)
2239
+ );
2240
+ await this.client.connect();
2241
+ console.error(
2242
+ `Connected to Redis at ${options.host}:${options.port} (db: ${options.db})`
2243
+ );
2244
+ } catch (error) {
2245
+ throw new Error(`Failed to connect to Redis: ${error}`);
2246
+ }
2247
+ }
2248
+ async disconnect() {
2249
+ if (this.client) {
2250
+ await this.client.disconnect();
2251
+ this.client = null;
2252
+ }
2253
+ }
2254
+ // SQL-like methods return empty arrays for Redis
2255
+ async getSchemas() {
2256
+ return [];
2257
+ }
2258
+ async getTables() {
2259
+ if (!this.client) throw new Error("Redis not connected");
2260
+ return [];
2261
+ }
2262
+ async getTableSchema() {
2263
+ return [];
2264
+ }
2265
+ async tableExists() {
2266
+ return false;
2267
+ }
2268
+ async getTableIndexes() {
2269
+ return [];
2270
+ }
2271
+ async getStoredProcedures() {
2272
+ return [];
2273
+ }
2274
+ async getStoredProcedureDetail() {
2275
+ return null;
2276
+ }
2277
+ async executeSQL() {
2278
+ throw new Error("Redis does not support SQL. Use executeCommand instead.");
2279
+ }
2280
+ /**
2281
+ * Execute a Redis command
2282
+ * Examples:
2283
+ * GET mykey
2284
+ * SET mykey value
2285
+ * HGETALL myhash
2286
+ * LPUSH mylist value
2287
+ * SADD myset member
2288
+ * ZADD myzset 1 member
2289
+ */
2290
+ async executeCommand(command, options) {
2291
+ if (!this.client) throw new Error("Redis not connected");
2292
+ try {
2293
+ const parts = command.trim().split(/\s+/);
2294
+ const cmd = parts[0].toUpperCase();
2295
+ const args = parts.slice(1);
2296
+ let value;
2297
+ let type = "nil";
2298
+ switch (cmd) {
2299
+ // String commands
2300
+ case "GET":
2301
+ value = await this.client.get(args[0]);
2302
+ type = value ? "string" : "nil";
2303
+ break;
2304
+ case "SET": {
2305
+ const key = args[0];
2306
+ const val = args.slice(1).join(" ");
2307
+ await this.client.set(key, val);
2308
+ value = "OK";
2309
+ type = "string";
2310
+ break;
2311
+ }
2312
+ case "APPEND":
2313
+ value = await this.client.append(args[0], args.slice(1).join(" "));
2314
+ type = "string";
2315
+ break;
2316
+ case "STRLEN":
2317
+ value = await this.client.strLen(args[0]);
2318
+ type = "string";
2319
+ break;
2320
+ case "INCR":
2321
+ value = await this.client.incr(args[0]);
2322
+ type = "string";
2323
+ break;
2324
+ case "DECR":
2325
+ value = await this.client.decr(args[0]);
2326
+ type = "string";
2327
+ break;
2328
+ // Hash commands
2329
+ case "HGET":
2330
+ value = await this.client.hGet(args[0], args[1]);
2331
+ type = value ? "hash" : "nil";
2332
+ break;
2333
+ case "HGETALL":
2334
+ value = await this.client.hGetAll(args[0]);
2335
+ type = "hash";
2336
+ break;
2337
+ case "HKEYS":
2338
+ value = await this.client.hKeys(args[0]);
2339
+ type = "hash";
2340
+ break;
2341
+ case "HVALS":
2342
+ value = await this.client.hVals(args[0]);
2343
+ type = "hash";
2344
+ break;
2345
+ case "HSET": {
2346
+ const key = args[0];
2347
+ const field = args[1];
2348
+ const val = args.slice(2).join(" ");
2349
+ value = await this.client.hSet(key, field, val);
2350
+ type = "hash";
2351
+ break;
2352
+ }
2353
+ case "HDEL":
2354
+ value = await this.client.hDel(args[0], args.slice(1));
2355
+ type = "hash";
2356
+ break;
2357
+ case "HEXISTS":
2358
+ value = await this.client.hExists(args[0], args[1]);
2359
+ type = "hash";
2360
+ break;
2361
+ case "HLEN":
2362
+ value = await this.client.hLen(args[0]);
2363
+ type = "hash";
2364
+ break;
2365
+ // List commands
2366
+ case "LPUSH":
2367
+ value = await this.client.lPush(args[0], args.slice(1));
2368
+ type = "list";
2369
+ break;
2370
+ case "RPUSH":
2371
+ value = await this.client.rPush(args[0], args.slice(1));
2372
+ type = "list";
2373
+ break;
2374
+ case "LPOP":
2375
+ value = await this.client.lPop(args[0]);
2376
+ type = value ? "list" : "nil";
2377
+ break;
2378
+ case "RPOP":
2379
+ value = await this.client.rPop(args[0]);
2380
+ type = value ? "list" : "nil";
2381
+ break;
2382
+ case "LRANGE": {
2383
+ const key = args[0];
2384
+ const start = parseInt(args[1], 10);
2385
+ const stop = parseInt(args[2], 10);
2386
+ value = await this.client.lRange(key, start, stop);
2387
+ type = "list";
2388
+ break;
2389
+ }
2390
+ case "LLEN":
2391
+ value = await this.client.lLen(args[0]);
2392
+ type = "list";
2393
+ break;
2394
+ case "LINDEX": {
2395
+ const key = args[0];
2396
+ const index = parseInt(args[1], 10);
2397
+ value = await this.client.lIndex(key, index);
2398
+ type = value ? "list" : "nil";
2399
+ break;
2400
+ }
2401
+ // Set commands
2402
+ case "SADD":
2403
+ value = await this.client.sAdd(args[0], args.slice(1));
2404
+ type = "set";
2405
+ break;
2406
+ case "SREM":
2407
+ value = await this.client.sRem(args[0], args.slice(1));
2408
+ type = "set";
2409
+ break;
2410
+ case "SMEMBERS":
2411
+ value = await this.client.sMembers(args[0]);
2412
+ type = "set";
2413
+ break;
2414
+ case "SISMEMBER":
2415
+ value = await this.client.sIsMember(args[0], args[1]);
2416
+ type = "set";
2417
+ break;
2418
+ case "SCARD":
2419
+ value = await this.client.sCard(args[0]);
2420
+ type = "set";
2421
+ break;
2422
+ // Sorted Set commands
2423
+ case "ZADD": {
2424
+ const key = args[0];
2425
+ const score = parseFloat(args[1]);
2426
+ const member = args.slice(2).join(" ");
2427
+ value = await this.client.zAdd(key, { score, value: member });
2428
+ type = "zset";
2429
+ break;
2430
+ }
2431
+ case "ZRANGE": {
2432
+ const key = args[0];
2433
+ const start = parseInt(args[1], 10);
2434
+ const stop = parseInt(args[2], 10);
2435
+ value = await this.client.zRange(key, start, stop);
2436
+ type = "zset";
2437
+ break;
2438
+ }
2439
+ case "ZREM":
2440
+ value = await this.client.zRem(args[0], args.slice(1));
2441
+ type = "zset";
2442
+ break;
2443
+ case "ZCARD":
2444
+ value = await this.client.zCard(args[0]);
2445
+ type = "zset";
2446
+ break;
2447
+ case "ZSCORE":
2448
+ value = await this.client.zScore(args[0], args[1]);
2449
+ type = "zset";
2450
+ break;
2451
+ // Generic commands
2452
+ case "DEL":
2453
+ value = await this.client.del(args);
2454
+ type = "string";
2455
+ break;
2456
+ case "EXISTS":
2457
+ value = await this.client.exists(args);
2458
+ type = "string";
2459
+ break;
2460
+ case "KEYS": {
2461
+ const pattern = args[0] || "*";
2462
+ value = await this.client.keys(pattern);
2463
+ if (options?.maxRows && value.length > options.maxRows) {
2464
+ value = value.slice(0, options.maxRows);
2465
+ }
2466
+ type = "string";
2467
+ break;
2468
+ }
2469
+ case "SCAN": {
2470
+ const cursor = parseInt(args[0], 10) || 0;
2471
+ const result = await this.client.scan(cursor);
2472
+ value = result;
2473
+ type = "string";
2474
+ break;
2475
+ }
2476
+ case "TYPE":
2477
+ value = await this.client.type(args[0]);
2478
+ type = "string";
2479
+ break;
2480
+ case "TTL":
2481
+ value = await this.client.ttl(args[0]);
2482
+ type = "string";
2483
+ break;
2484
+ case "EXPIRE": {
2485
+ const key = args[0];
2486
+ const seconds = parseInt(args[1], 10);
2487
+ value = await this.client.expire(key, seconds);
2488
+ type = "string";
2489
+ break;
2490
+ }
2491
+ case "DBSIZE":
2492
+ value = await this.client.dbSize();
2493
+ type = "string";
2494
+ break;
2495
+ case "FLUSHDB":
2496
+ await this.client.flushDb();
2497
+ value = "OK";
2498
+ type = "string";
2499
+ break;
2500
+ case "INFO": {
2501
+ const section = args[0] || "default";
2502
+ value = await this.client.info(section);
2503
+ type = "string";
2504
+ break;
2505
+ }
2506
+ default:
2507
+ throw new Error(`Unknown Redis command: ${cmd}`);
2508
+ }
2509
+ return { value, type };
2510
+ } catch (error) {
2511
+ throw new Error(`Redis command failed: ${error}`);
2512
+ }
2513
+ }
2514
+ setSourceId(sourceId) {
2515
+ this.sourceId = sourceId;
2516
+ }
2517
+ };
2518
+ function createRedisConnector() {
2519
+ return new RedisConnector();
2520
+ }
2521
+ var redisConnector = createRedisConnector();
2522
+ ConnectorRegistry.register(redisConnector);
2523
+
2524
+ // src/connectors/elasticsearch/index.ts
2525
+ import { Client } from "@elastic/elasticsearch";
2526
+ import { URL as URL2 } from "url";
2527
+ var ElasticsearchDSNParser = class {
2528
+ parse(dsn) {
2529
+ try {
2530
+ const url = new URL2(dsn);
2531
+ if (url.protocol !== "elasticsearch:") {
2532
+ throw new Error("Invalid Elasticsearch DSN protocol");
2533
+ }
2534
+ const host = url.hostname || "localhost";
2535
+ const port = url.port ? parseInt(url.port, 10) : 9200;
2536
+ const username = url.username || void 0;
2537
+ const password = url.password || void 0;
2538
+ const indexPattern = url.searchParams.get("index_pattern") || "*";
2539
+ return {
2540
+ host,
2541
+ port,
2542
+ username,
2543
+ password,
2544
+ indexPattern,
2545
+ tls: url.protocol === "elasticsearch:" && url.hostname?.includes("cloud")
2546
+ };
2547
+ } catch (error) {
2548
+ throw new Error(`Failed to parse Elasticsearch DSN: ${error}`);
2549
+ }
2550
+ }
2551
+ getSampleDSN() {
2552
+ return "elasticsearch://localhost:9200?index_pattern=logs-*";
2553
+ }
2554
+ isValidDSN(dsn) {
2555
+ try {
2556
+ const url = new URL2(dsn);
2557
+ return url.protocol === "elasticsearch:";
2558
+ } catch {
2559
+ return false;
2560
+ }
2561
+ }
2562
+ };
2563
+ var ElasticsearchConnector = class _ElasticsearchConnector {
2564
+ constructor() {
2565
+ this.id = "elasticsearch";
2566
+ this.name = "Elasticsearch";
2567
+ this.dsnParser = new ElasticsearchDSNParser();
2568
+ this.client = null;
2569
+ this.sourceId = "default";
2570
+ this.indexPattern = "*";
2571
+ }
2572
+ getId() {
2573
+ return this.sourceId;
2574
+ }
2575
+ clone() {
2576
+ return new _ElasticsearchConnector();
2577
+ }
2578
+ async connect(dsn, _initScript, config) {
2579
+ try {
2580
+ const options = this.dsnParser.parse(dsn);
2581
+ this.indexPattern = options.indexPattern;
2582
+ this.client = new Client({
2583
+ node: `http://${options.host}:${options.port}`,
2584
+ ...options.username && {
2585
+ auth: {
2586
+ username: options.username,
2587
+ password: options.password || ""
2588
+ }
2589
+ },
2590
+ requestTimeout: (config?.queryTimeoutSeconds || 30) * 1e3
2591
+ });
2592
+ const health = await this.client.cluster.health();
2593
+ console.error(
2594
+ `Connected to Elasticsearch (status: ${health.status}, cluster: ${health.cluster_name})`
2595
+ );
2596
+ } catch (error) {
2597
+ throw new Error(`Failed to connect to Elasticsearch: ${error}`);
2598
+ }
2599
+ }
2600
+ async disconnect() {
2601
+ if (this.client) {
2602
+ await this.client.close();
2603
+ this.client = null;
2604
+ }
2605
+ }
2606
+ // SQL-like methods return metadata for Elasticsearch
2607
+ async getSchemas() {
2608
+ if (!this.client) throw new Error("Elasticsearch not connected");
2609
+ const indices = await this.client.indices.get({ index: this.indexPattern });
2610
+ return Object.keys(indices).slice(0, 100);
2611
+ }
2612
+ async getTables() {
2613
+ return this.getSchemas();
2614
+ }
2615
+ async getTableSchema(indexName) {
2616
+ if (!this.client) throw new Error("Elasticsearch not connected");
2617
+ try {
2618
+ const mapping = await this.client.indices.getMapping({ index: indexName });
2619
+ const properties = mapping[indexName]?.mappings?.properties || {};
2620
+ return Object.entries(properties).map(([name, prop]) => ({
2621
+ column_name: name,
2622
+ data_type: prop.type || "text",
2623
+ is_nullable: "yes",
2624
+ column_default: null
2625
+ }));
2626
+ } catch (error) {
2627
+ throw new Error(`Failed to get mapping for index ${indexName}: ${error}`);
2628
+ }
2629
+ }
2630
+ async tableExists(indexName) {
2631
+ if (!this.client) throw new Error("Elasticsearch not connected");
2632
+ return this.client.indices.exists({ index: indexName });
2633
+ }
2634
+ async getTableIndexes() {
2635
+ return [];
2636
+ }
2637
+ async getStoredProcedures() {
2638
+ return [];
2639
+ }
2640
+ async getStoredProcedureDetail() {
2641
+ return null;
2642
+ }
2643
+ async executeSQL() {
2644
+ throw new Error(
2645
+ "Elasticsearch does not support SQL. Use executeCommand instead (JSON query DSL)."
2646
+ );
2647
+ }
2648
+ /**
2649
+ * Execute an Elasticsearch query or aggregation
2650
+ * Pass a JSON query DSL object or a simplified query string
2651
+ *
2652
+ * Examples:
2653
+ * {"index": "logs", "query": {"match_all": {}}}
2654
+ * {"index": "logs", "query": {"term": {"status": "error"}}, "size": 10}
2655
+ * {"index": "logs", "aggs": {"status_counts": {"terms": {"field": "status"}}}}
2656
+ */
2657
+ async executeCommand(query, options) {
2658
+ if (!this.client) throw new Error("Elasticsearch not connected");
2659
+ try {
2660
+ let parsedQuery;
2661
+ try {
2662
+ parsedQuery = JSON.parse(query);
2663
+ } catch {
2664
+ parsedQuery = this.parseSimplifiedQuery(query);
2665
+ }
2666
+ const index = parsedQuery.index || this.indexPattern;
2667
+ const esQuery = {
2668
+ index,
2669
+ body: {
2670
+ query: parsedQuery.query || { match_all: {} },
2671
+ aggs: parsedQuery.aggs,
2672
+ size: options?.maxRows || parsedQuery.size || 10,
2673
+ track_total_hits: true
2674
+ }
2675
+ };
2676
+ if (!esQuery.body.aggs) delete esQuery.body.aggs;
2677
+ const response = await this.client.search(esQuery);
2678
+ return {
2679
+ hits: {
2680
+ total: typeof response.hits.total === "number" ? response.hits.total : response.hits.total?.value || 0,
2681
+ documents: response.hits.hits.map((hit) => ({
2682
+ _id: hit._id,
2683
+ _score: hit._score,
2684
+ ...hit._source
2685
+ }))
2686
+ },
2687
+ aggregations: response.aggregations
2688
+ };
2689
+ } catch (error) {
2690
+ throw new Error(`Elasticsearch query failed: ${error}`);
2691
+ }
2692
+ }
2693
+ /**
2694
+ * Parse simplified query syntax
2695
+ * Examples:
2696
+ * index:logs status:error
2697
+ * index:logs agg:status_counts field:status
2698
+ */
2699
+ parseSimplifiedQuery(query) {
2700
+ const parts = query.split(/\s+/);
2701
+ const parsed = {};
2702
+ for (const part of parts) {
2703
+ if (part.includes(":")) {
2704
+ const [key, value] = part.split(":", 2);
2705
+ if (key === "index") {
2706
+ parsed.index = value;
2707
+ } else if (key === "agg") {
2708
+ parsed.aggs = {
2709
+ aggregation: {
2710
+ terms: { field: value }
2711
+ }
2712
+ };
2713
+ } else if (key === "field") {
2714
+ if (!parsed.query) {
2715
+ parsed.query = { match_all: {} };
2716
+ }
2717
+ } else {
2718
+ if (!parsed.query) {
2719
+ parsed.query = { bool: { must: [] } };
2720
+ } else if (!parsed.query.bool) {
2721
+ parsed.query = { bool: { must: [parsed.query] } };
2722
+ }
2723
+ parsed.query.bool.must.push({
2724
+ term: { [key]: value }
2725
+ });
2726
+ }
2727
+ }
2728
+ }
2729
+ return parsed;
2730
+ }
2731
+ /**
2732
+ * Search across indices with a simple text query
2733
+ */
2734
+ async searchSimple(text, indexPattern = this.indexPattern, options) {
2735
+ if (!this.client) throw new Error("Elasticsearch not connected");
2736
+ const response = await this.client.search({
2737
+ index: indexPattern,
2738
+ body: {
2739
+ query: {
2740
+ multi_match: {
2741
+ query: text,
2742
+ fields: ["*"]
2743
+ }
2744
+ },
2745
+ size: options?.maxRows || 10,
2746
+ track_total_hits: true
2747
+ }
2748
+ });
2749
+ return {
2750
+ hits: {
2751
+ total: typeof response.hits.total === "number" ? response.hits.total : response.hits.total?.value || 0,
2752
+ documents: response.hits.hits.map((hit) => ({
2753
+ _id: hit._id,
2754
+ _score: hit._score,
2755
+ ...hit._source
2756
+ }))
2757
+ }
2758
+ };
2759
+ }
2760
+ setSourceId(sourceId) {
2761
+ this.sourceId = sourceId;
2762
+ }
2763
+ };
2764
+ function createElasticsearchConnector() {
2765
+ return new ElasticsearchConnector();
2766
+ }
2767
+ var elasticsearchConnector = createElasticsearchConnector();
2768
+ ConnectorRegistry.register(elasticsearchConnector);
2769
+
2770
+ // src/server.ts
2771
+ import { McpServer } from "@modelcontextprotocol/sdk/server/mcp.js";
2772
+ import { StreamableHTTPServerTransport } from "@modelcontextprotocol/sdk/server/streamableHttp.js";
2773
+ import { StdioServerTransport } from "@modelcontextprotocol/sdk/server/stdio.js";
2774
+ import express from "express";
2775
+ import path from "path";
2776
+ import { readFileSync } from "fs";
2777
+ import { fileURLToPath } from "url";
2778
+
2779
+ // src/tools/execute-sql.ts
2780
+ import { z } from "zod";
2781
+
2782
+ // src/utils/response-formatter.ts
2783
+ function bigIntReplacer(_key, value) {
2784
+ if (typeof value === "bigint") {
2785
+ return value.toString();
2786
+ }
2787
+ return value;
2788
+ }
2789
+ function formatSuccessResponse(data, meta = {}) {
2790
+ return {
2791
+ success: true,
2792
+ data,
2793
+ ...Object.keys(meta).length > 0 ? { meta } : {}
2794
+ };
2795
+ }
2796
+ function formatErrorResponse(error, code = "ERROR", details) {
2797
+ return {
2798
+ success: false,
2799
+ error,
2800
+ code,
2801
+ ...details ? { details } : {}
2802
+ };
2803
+ }
2804
+ function createToolErrorResponse(error, code = "ERROR", details) {
2805
+ return {
2806
+ content: [
2807
+ {
2808
+ type: "text",
2809
+ text: JSON.stringify(formatErrorResponse(error, code, details), bigIntReplacer, 2),
2810
+ mimeType: "application/json"
2811
+ }
2812
+ ],
2813
+ isError: true
2814
+ };
2815
+ }
2816
+ function createToolSuccessResponse(data, meta = {}) {
2817
+ return {
2818
+ content: [
2819
+ {
2820
+ type: "text",
2821
+ text: JSON.stringify(formatSuccessResponse(data, meta), bigIntReplacer, 2),
2822
+ mimeType: "application/json"
2823
+ }
2824
+ ]
2825
+ };
2826
+ }
2827
+
2828
+ // src/utils/allowed-keywords.ts
2829
+ var allowedKeywords = {
2830
+ postgres: ["select", "with", "explain", "analyze", "show"],
2831
+ mysql: ["select", "with", "explain", "analyze", "show", "describe", "desc"],
2832
+ mariadb: ["select", "with", "explain", "analyze", "show", "describe", "desc"],
2833
+ sqlite: ["select", "with", "explain", "analyze", "pragma"],
2834
+ sqlserver: ["select", "with", "explain", "showplan"]
2835
+ };
2836
+ function isReadOnlySQL(sql2, connectorType) {
2837
+ const cleanedSQL = stripCommentsAndStrings(sql2).trim().toLowerCase();
2838
+ if (!cleanedSQL) {
2839
+ return true;
2840
+ }
2841
+ const firstWord = cleanedSQL.split(/\s+/)[0];
2842
+ const keywordList = allowedKeywords[connectorType] || [];
2843
+ return keywordList.includes(firstWord);
2844
+ }
2845
+
2846
+ // src/requests/store.ts
2847
+ var RequestStore = class {
2848
+ constructor() {
2849
+ this.store = /* @__PURE__ */ new Map();
2850
+ this.maxPerSource = 100;
2851
+ }
2852
+ /**
2853
+ * Add a request to the store
2854
+ * Evicts oldest entry if at capacity
2855
+ */
2856
+ add(request) {
2857
+ const requests = this.store.get(request.sourceId) ?? [];
2858
+ requests.push(request);
2859
+ if (requests.length > this.maxPerSource) {
2860
+ requests.shift();
2861
+ }
2862
+ this.store.set(request.sourceId, requests);
2863
+ }
2864
+ /**
2865
+ * Get requests, optionally filtered by source
2866
+ * Returns newest first
2867
+ */
2868
+ getAll(sourceId) {
2869
+ let requests;
2870
+ if (sourceId) {
2871
+ requests = [...this.store.get(sourceId) ?? []];
2872
+ } else {
2873
+ requests = Array.from(this.store.values()).flat();
2874
+ }
2875
+ return requests.sort(
2876
+ (a, b) => new Date(b.timestamp).getTime() - new Date(a.timestamp).getTime()
2877
+ );
2878
+ }
2879
+ /**
2880
+ * Get total count of requests across all sources
2881
+ */
2882
+ getTotal() {
2883
+ return Array.from(this.store.values()).reduce((sum, arr) => sum + arr.length, 0);
2884
+ }
2885
+ /**
2886
+ * Clear all requests (useful for testing)
2887
+ */
2888
+ clear() {
2889
+ this.store.clear();
2890
+ }
2891
+ };
2892
+
2893
+ // src/requests/index.ts
2894
+ var requestStore = new RequestStore();
2895
+
2896
+ // src/utils/client-identifier.ts
2897
+ function getClientIdentifier(extra) {
2898
+ const userAgent = extra?.requestInfo?.headers?.["user-agent"];
2899
+ if (userAgent) {
2900
+ return userAgent;
2901
+ }
2902
+ return "stdio";
2903
+ }
2904
+
2905
+ // src/utils/tool-handler-helpers.ts
2906
+ function getEffectiveSourceId(sourceId) {
2907
+ return sourceId || "default";
2908
+ }
2909
+ function createReadonlyViolationMessage(toolName, sourceId, connectorType) {
2910
+ return `Tool '${toolName}' cannot execute in readonly mode for source '${sourceId}'. Only read-only SQL operations are allowed: ${allowedKeywords[connectorType]?.join(", ") || "none"}`;
2911
+ }
2912
+ function trackToolRequest(metadata, startTime, extra, success, error) {
2913
+ requestStore.add({
2914
+ id: crypto.randomUUID(),
2915
+ timestamp: (/* @__PURE__ */ new Date()).toISOString(),
2916
+ sourceId: metadata.sourceId,
2917
+ toolName: metadata.toolName,
2918
+ sql: metadata.sql,
2919
+ durationMs: Date.now() - startTime,
2920
+ client: getClientIdentifier(extra),
2921
+ success,
2922
+ error
2923
+ });
2924
+ }
2925
+
2926
+ // src/tools/execute-sql.ts
2927
+ var executeSqlSchema = {
2928
+ sql: z.string().describe("SQL to execute (multiple statements separated by ;)")
2929
+ };
2930
+ function splitSQLStatements(sql2) {
2931
+ return sql2.split(";").map((statement) => statement.trim()).filter((statement) => statement.length > 0);
2932
+ }
2933
+ function areAllStatementsReadOnly(sql2, connectorType) {
2934
+ const statements = splitSQLStatements(sql2);
2935
+ return statements.every((statement) => isReadOnlySQL(statement, connectorType));
2936
+ }
2937
+ function createExecuteSqlToolHandler(sourceId) {
2938
+ return async (args, extra) => {
2939
+ const { sql: sql2 } = args;
2940
+ const startTime = Date.now();
2941
+ const effectiveSourceId = getEffectiveSourceId(sourceId);
2942
+ let success = true;
2943
+ let errorMessage;
2944
+ let result;
2945
+ try {
2946
+ await ConnectorManager.ensureConnected(sourceId);
2947
+ const connector = ConnectorManager.getCurrentConnector(sourceId);
2948
+ const actualSourceId = connector.getId();
2949
+ const registry = getToolRegistry();
2950
+ const toolConfig = registry.getBuiltinToolConfig(BUILTIN_TOOL_EXECUTE_SQL, actualSourceId);
2951
+ const isReadonly = toolConfig?.readonly === true;
2952
+ if (isReadonly && !areAllStatementsReadOnly(sql2, connector.id)) {
2953
+ errorMessage = `Read-only mode is enabled. Only the following SQL operations are allowed: ${allowedKeywords[connector.id]?.join(", ") || "none"}`;
2954
+ success = false;
2955
+ return createToolErrorResponse(errorMessage, "READONLY_VIOLATION");
2956
+ }
2957
+ const executeOptions = {
2958
+ readonly: toolConfig?.readonly,
2959
+ maxRows: toolConfig?.max_rows
2960
+ };
2961
+ result = await connector.executeSQL(sql2, executeOptions);
2962
+ const responseData = {
2963
+ rows: result.rows,
2964
+ count: result.rowCount,
2965
+ source_id: effectiveSourceId
2966
+ };
2967
+ return createToolSuccessResponse(responseData);
2968
+ } catch (error) {
2969
+ success = false;
2970
+ errorMessage = error.message;
2971
+ return createToolErrorResponse(errorMessage, "EXECUTION_ERROR");
2972
+ } finally {
2973
+ trackToolRequest(
2974
+ {
2975
+ sourceId: effectiveSourceId,
2976
+ toolName: effectiveSourceId === "default" ? "execute_sql" : `execute_sql_${effectiveSourceId}`,
2977
+ sql: sql2
2978
+ },
2979
+ startTime,
2980
+ extra,
2981
+ success,
2982
+ errorMessage
2983
+ );
2984
+ }
2985
+ };
2986
+ }
2987
+
2988
+ // src/tools/search-objects.ts
2989
+ import { z as z2 } from "zod";
2990
+ var searchDatabaseObjectsSchema = {
2991
+ object_type: z2.enum(["schema", "table", "column", "procedure", "index"]).describe("Object type to search"),
2992
+ pattern: z2.string().optional().default("%").describe("LIKE pattern (% = any chars, _ = one char). Default: %"),
2993
+ schema: z2.string().optional().describe("Filter to schema"),
2994
+ table: z2.string().optional().describe("Filter to table (requires schema; column/index only)"),
2995
+ detail_level: z2.enum(["names", "summary", "full"]).default("names").describe("Detail: names (minimal), summary (metadata), full (all)"),
2996
+ limit: z2.number().int().positive().max(1e3).default(100).describe("Max results (default: 100, max: 1000)")
2997
+ };
2998
+ function likePatternToRegex(pattern) {
2999
+ const escaped = pattern.replace(/[.*+?^${}()|[\]\\]/g, "\\$&").replace(/%/g, ".*").replace(/_/g, ".");
3000
+ return new RegExp(`^${escaped}$`, "i");
3001
+ }
3002
+ async function getTableRowCount(connector, tableName, schemaName) {
3003
+ try {
3004
+ const qualifiedTable = quoteQualifiedIdentifier(tableName, schemaName, connector.id);
3005
+ const countQuery = `SELECT COUNT(*) as count FROM ${qualifiedTable}`;
3006
+ const result = await connector.executeSQL(countQuery, { maxRows: 1 });
3007
+ if (result.rows && result.rows.length > 0) {
3008
+ return Number(result.rows[0].count || result.rows[0].COUNT || 0);
3009
+ }
3010
+ } catch (error) {
3011
+ return null;
3012
+ }
3013
+ return null;
3014
+ }
3015
+ async function searchSchemas(connector, pattern, detailLevel, limit) {
3016
+ const schemas = await connector.getSchemas();
3017
+ const regex = likePatternToRegex(pattern);
3018
+ const matched = schemas.filter((schema) => regex.test(schema)).slice(0, limit);
3019
+ if (detailLevel === "names") {
3020
+ return matched.map((name) => ({ name }));
3021
+ }
3022
+ const results = await Promise.all(
3023
+ matched.map(async (schemaName) => {
3024
+ try {
3025
+ const tables = await connector.getTables(schemaName);
3026
+ return {
3027
+ name: schemaName,
3028
+ table_count: tables.length
3029
+ };
3030
+ } catch (error) {
3031
+ return {
3032
+ name: schemaName,
3033
+ table_count: 0
3034
+ };
3035
+ }
3036
+ })
3037
+ );
3038
+ return results;
3039
+ }
3040
+ async function searchTables(connector, pattern, schemaFilter, detailLevel, limit) {
3041
+ const regex = likePatternToRegex(pattern);
3042
+ const results = [];
3043
+ let schemasToSearch;
3044
+ if (schemaFilter) {
3045
+ schemasToSearch = [schemaFilter];
3046
+ } else {
3047
+ schemasToSearch = await connector.getSchemas();
3048
+ }
3049
+ for (const schemaName of schemasToSearch) {
3050
+ if (results.length >= limit) break;
3051
+ try {
3052
+ const tables = await connector.getTables(schemaName);
3053
+ const matched = tables.filter((table) => regex.test(table));
3054
+ for (const tableName of matched) {
3055
+ if (results.length >= limit) break;
3056
+ if (detailLevel === "names") {
3057
+ results.push({
3058
+ name: tableName,
3059
+ schema: schemaName
3060
+ });
3061
+ } else if (detailLevel === "summary") {
3062
+ try {
3063
+ const columns = await connector.getTableSchema(tableName, schemaName);
3064
+ const rowCount = await getTableRowCount(connector, tableName, schemaName);
3065
+ results.push({
3066
+ name: tableName,
3067
+ schema: schemaName,
3068
+ column_count: columns.length,
3069
+ row_count: rowCount
3070
+ });
3071
+ } catch (error) {
3072
+ results.push({
3073
+ name: tableName,
3074
+ schema: schemaName,
3075
+ column_count: null,
3076
+ row_count: null
3077
+ });
3078
+ }
3079
+ } else {
3080
+ try {
3081
+ const columns = await connector.getTableSchema(tableName, schemaName);
3082
+ const indexes = await connector.getTableIndexes(tableName, schemaName);
3083
+ const rowCount = await getTableRowCount(connector, tableName, schemaName);
3084
+ results.push({
3085
+ name: tableName,
3086
+ schema: schemaName,
3087
+ column_count: columns.length,
3088
+ row_count: rowCount,
3089
+ columns: columns.map((col) => ({
3090
+ name: col.column_name,
3091
+ type: col.data_type,
3092
+ nullable: col.is_nullable === "YES",
3093
+ default: col.column_default
3094
+ })),
3095
+ indexes: indexes.map((idx) => ({
3096
+ name: idx.index_name,
3097
+ columns: idx.column_names,
3098
+ unique: idx.is_unique,
3099
+ primary: idx.is_primary
3100
+ }))
3101
+ });
3102
+ } catch (error) {
3103
+ results.push({
3104
+ name: tableName,
3105
+ schema: schemaName,
3106
+ error: `Unable to fetch full details: ${error.message}`
3107
+ });
3108
+ }
3109
+ }
3110
+ }
3111
+ } catch (error) {
3112
+ continue;
3113
+ }
3114
+ }
3115
+ return results;
3116
+ }
3117
+ async function searchColumns(connector, pattern, schemaFilter, tableFilter, detailLevel, limit) {
3118
+ const regex = likePatternToRegex(pattern);
3119
+ const results = [];
3120
+ let schemasToSearch;
3121
+ if (schemaFilter) {
3122
+ schemasToSearch = [schemaFilter];
3123
+ } else {
3124
+ schemasToSearch = await connector.getSchemas();
3125
+ }
3126
+ for (const schemaName of schemasToSearch) {
3127
+ if (results.length >= limit) break;
3128
+ try {
3129
+ let tablesToSearch;
3130
+ if (tableFilter) {
3131
+ tablesToSearch = [tableFilter];
3132
+ } else {
3133
+ tablesToSearch = await connector.getTables(schemaName);
3134
+ }
3135
+ for (const tableName of tablesToSearch) {
3136
+ if (results.length >= limit) break;
3137
+ try {
3138
+ const columns = await connector.getTableSchema(tableName, schemaName);
3139
+ const matchedColumns = columns.filter((col) => regex.test(col.column_name));
3140
+ for (const column of matchedColumns) {
3141
+ if (results.length >= limit) break;
3142
+ if (detailLevel === "names") {
3143
+ results.push({
3144
+ name: column.column_name,
3145
+ table: tableName,
3146
+ schema: schemaName
3147
+ });
3148
+ } else {
3149
+ results.push({
3150
+ name: column.column_name,
3151
+ table: tableName,
3152
+ schema: schemaName,
3153
+ type: column.data_type,
3154
+ nullable: column.is_nullable === "YES",
3155
+ default: column.column_default
3156
+ });
3157
+ }
3158
+ }
3159
+ } catch (error) {
3160
+ continue;
3161
+ }
3162
+ }
3163
+ } catch (error) {
3164
+ continue;
3165
+ }
3166
+ }
3167
+ return results;
3168
+ }
3169
+ async function searchProcedures(connector, pattern, schemaFilter, detailLevel, limit) {
3170
+ const regex = likePatternToRegex(pattern);
3171
+ const results = [];
3172
+ let schemasToSearch;
3173
+ if (schemaFilter) {
3174
+ schemasToSearch = [schemaFilter];
3175
+ } else {
3176
+ schemasToSearch = await connector.getSchemas();
3177
+ }
3178
+ for (const schemaName of schemasToSearch) {
3179
+ if (results.length >= limit) break;
3180
+ try {
3181
+ const procedures = await connector.getStoredProcedures(schemaName);
3182
+ const matched = procedures.filter((proc) => regex.test(proc));
3183
+ for (const procName of matched) {
3184
+ if (results.length >= limit) break;
3185
+ if (detailLevel === "names") {
3186
+ results.push({
3187
+ name: procName,
3188
+ schema: schemaName
3189
+ });
3190
+ } else {
3191
+ try {
3192
+ const details = await connector.getStoredProcedureDetail(procName, schemaName);
3193
+ results.push({
3194
+ name: procName,
3195
+ schema: schemaName,
3196
+ type: details.procedure_type,
3197
+ language: details.language,
3198
+ parameters: detailLevel === "full" ? details.parameter_list : void 0,
3199
+ return_type: details.return_type,
3200
+ definition: detailLevel === "full" ? details.definition : void 0
3201
+ });
3202
+ } catch (error) {
3203
+ results.push({
3204
+ name: procName,
3205
+ schema: schemaName,
3206
+ error: `Unable to fetch details: ${error.message}`
3207
+ });
3208
+ }
3209
+ }
3210
+ }
3211
+ } catch (error) {
3212
+ continue;
3213
+ }
3214
+ }
3215
+ return results;
3216
+ }
3217
+ async function searchIndexes(connector, pattern, schemaFilter, tableFilter, detailLevel, limit) {
3218
+ const regex = likePatternToRegex(pattern);
3219
+ const results = [];
3220
+ let schemasToSearch;
3221
+ if (schemaFilter) {
3222
+ schemasToSearch = [schemaFilter];
3223
+ } else {
3224
+ schemasToSearch = await connector.getSchemas();
3225
+ }
3226
+ for (const schemaName of schemasToSearch) {
3227
+ if (results.length >= limit) break;
3228
+ try {
3229
+ let tablesToSearch;
3230
+ if (tableFilter) {
3231
+ tablesToSearch = [tableFilter];
3232
+ } else {
3233
+ tablesToSearch = await connector.getTables(schemaName);
3234
+ }
3235
+ for (const tableName of tablesToSearch) {
3236
+ if (results.length >= limit) break;
3237
+ try {
3238
+ const indexes = await connector.getTableIndexes(tableName, schemaName);
3239
+ const matchedIndexes = indexes.filter((idx) => regex.test(idx.index_name));
3240
+ for (const index of matchedIndexes) {
3241
+ if (results.length >= limit) break;
3242
+ if (detailLevel === "names") {
3243
+ results.push({
3244
+ name: index.index_name,
3245
+ table: tableName,
3246
+ schema: schemaName
3247
+ });
3248
+ } else {
3249
+ results.push({
3250
+ name: index.index_name,
3251
+ table: tableName,
3252
+ schema: schemaName,
3253
+ columns: index.column_names,
3254
+ unique: index.is_unique,
3255
+ primary: index.is_primary
3256
+ });
3257
+ }
3258
+ }
3259
+ } catch (error) {
3260
+ continue;
3261
+ }
3262
+ }
3263
+ } catch (error) {
3264
+ continue;
3265
+ }
3266
+ }
3267
+ return results;
3268
+ }
3269
+ function createSearchDatabaseObjectsToolHandler(sourceId) {
3270
+ return async (args, extra) => {
3271
+ const {
3272
+ object_type,
3273
+ pattern = "%",
3274
+ schema,
3275
+ table,
3276
+ detail_level = "names",
3277
+ limit = 100
3278
+ } = args;
3279
+ const startTime = Date.now();
3280
+ const effectiveSourceId = getEffectiveSourceId(sourceId);
3281
+ let success = true;
3282
+ let errorMessage;
3283
+ try {
3284
+ await ConnectorManager.ensureConnected(sourceId);
3285
+ const connector = ConnectorManager.getCurrentConnector(sourceId);
3286
+ if (table) {
3287
+ if (!schema) {
3288
+ success = false;
3289
+ errorMessage = "The 'table' parameter requires 'schema' to be specified";
3290
+ return createToolErrorResponse(errorMessage, "SCHEMA_REQUIRED");
3291
+ }
3292
+ if (!["column", "index"].includes(object_type)) {
3293
+ success = false;
3294
+ errorMessage = `The 'table' parameter only applies to object_type 'column' or 'index', not '${object_type}'`;
3295
+ return createToolErrorResponse(errorMessage, "INVALID_TABLE_FILTER");
3296
+ }
3297
+ }
3298
+ if (schema) {
3299
+ const schemas = await connector.getSchemas();
3300
+ if (!schemas.includes(schema)) {
3301
+ success = false;
3302
+ errorMessage = `Schema '${schema}' does not exist. Available schemas: ${schemas.join(", ")}`;
3303
+ return createToolErrorResponse(errorMessage, "SCHEMA_NOT_FOUND");
3304
+ }
3305
+ }
3306
+ let results = [];
3307
+ switch (object_type) {
3308
+ case "schema":
3309
+ results = await searchSchemas(connector, pattern, detail_level, limit);
3310
+ break;
3311
+ case "table":
3312
+ results = await searchTables(connector, pattern, schema, detail_level, limit);
3313
+ break;
3314
+ case "column":
3315
+ results = await searchColumns(connector, pattern, schema, table, detail_level, limit);
3316
+ break;
3317
+ case "procedure":
3318
+ results = await searchProcedures(connector, pattern, schema, detail_level, limit);
3319
+ break;
3320
+ case "index":
3321
+ results = await searchIndexes(connector, pattern, schema, table, detail_level, limit);
3322
+ break;
3323
+ default:
3324
+ success = false;
3325
+ errorMessage = `Unsupported object_type: ${object_type}`;
3326
+ return createToolErrorResponse(errorMessage, "INVALID_OBJECT_TYPE");
3327
+ }
3328
+ return createToolSuccessResponse({
3329
+ object_type,
3330
+ pattern,
3331
+ schema,
3332
+ table,
3333
+ detail_level,
3334
+ count: results.length,
3335
+ results,
3336
+ truncated: results.length === limit
3337
+ });
3338
+ } catch (error) {
3339
+ success = false;
3340
+ errorMessage = error.message;
3341
+ return createToolErrorResponse(
3342
+ `Error searching database objects: ${errorMessage}`,
3343
+ "SEARCH_ERROR"
3344
+ );
3345
+ } finally {
3346
+ trackToolRequest(
3347
+ {
3348
+ sourceId: effectiveSourceId,
3349
+ toolName: effectiveSourceId === "default" ? "search_objects" : `search_objects_${effectiveSourceId}`,
3350
+ sql: `search_objects(object_type=${object_type}, pattern=${pattern}, schema=${schema || "all"}, table=${table || "all"}, detail_level=${detail_level})`
3351
+ },
3352
+ startTime,
3353
+ extra,
3354
+ success,
3355
+ errorMessage
3356
+ );
3357
+ }
3358
+ };
3359
+ }
3360
+
3361
+ // src/tools/redis-command-handler.ts
3362
+ import { z as z3 } from "zod";
3363
+ var redisCommandSchema = {
3364
+ command: z3.string().describe("Redis command (e.g., GET key, SET key value, HGETALL hash)")
3365
+ };
3366
+ function createRedisCommandToolHandler(sourceId) {
3367
+ return async (args, extra) => {
3368
+ const { command } = args;
3369
+ const startTime = Date.now();
3370
+ const effectiveSourceId = getEffectiveSourceId(sourceId);
3371
+ let success = true;
3372
+ let errorMessage;
3373
+ let result;
3374
+ try {
3375
+ await ConnectorManager.ensureConnected(sourceId);
3376
+ const connector = ConnectorManager.getCurrentConnector(sourceId);
3377
+ if (connector.id !== "redis") {
3378
+ throw new Error("This tool only works with Redis connectors");
3379
+ }
3380
+ result = await connector.executeCommand(command);
3381
+ const responseData = {
3382
+ value: result.value,
3383
+ type: result.type,
3384
+ source_id: effectiveSourceId
3385
+ };
3386
+ return createToolSuccessResponse(responseData);
3387
+ } catch (error) {
3388
+ success = false;
3389
+ errorMessage = error.message;
3390
+ return createToolErrorResponse(errorMessage, "EXECUTION_ERROR");
3391
+ } finally {
3392
+ const duration = Date.now() - startTime;
3393
+ trackToolRequest({
3394
+ sourceId: effectiveSourceId,
3395
+ toolName: `redis_command_${effectiveSourceId}`,
3396
+ success,
3397
+ duration,
3398
+ errorMessage
3399
+ });
3400
+ }
3401
+ };
3402
+ }
3403
+
3404
+ // src/tools/elasticsearch-search-handler.ts
3405
+ import { z as z4 } from "zod";
3406
+ var elasticsearchSearchSchema = {
3407
+ query: z4.string().describe('Elasticsearch query (JSON DSL or simplified syntax). Examples: {"query": {"match_all": {}}} or {"query": {"term": {"status": "error"}}, "size": 10}')
3408
+ };
3409
+ function createElasticsearchSearchToolHandler(sourceId) {
3410
+ return async (args, extra) => {
3411
+ const { query } = args;
3412
+ const startTime = Date.now();
3413
+ const effectiveSourceId = getEffectiveSourceId(sourceId);
3414
+ let success = true;
3415
+ let errorMessage;
3416
+ let result;
3417
+ try {
3418
+ await ConnectorManager.ensureConnected(sourceId);
3419
+ const connector = ConnectorManager.getCurrentConnector(sourceId);
3420
+ if (connector.id !== "elasticsearch") {
3421
+ throw new Error("This tool only works with Elasticsearch connectors");
3422
+ }
3423
+ result = await connector.executeCommand(query);
3424
+ const responseData = {
3425
+ total_hits: result.hits.total,
3426
+ documents: result.hits.documents,
3427
+ aggregations: result.aggregations,
3428
+ source_id: effectiveSourceId
3429
+ };
3430
+ return createToolSuccessResponse(responseData);
3431
+ } catch (error) {
3432
+ success = false;
3433
+ errorMessage = error.message;
3434
+ return createToolErrorResponse(errorMessage, "EXECUTION_ERROR");
3435
+ } finally {
3436
+ const duration = Date.now() - startTime;
3437
+ trackToolRequest({
3438
+ sourceId: effectiveSourceId,
3439
+ toolName: `elasticsearch_search_${effectiveSourceId}`,
3440
+ success,
3441
+ duration,
3442
+ errorMessage
3443
+ });
3444
+ }
3445
+ };
3446
+ }
3447
+
3448
+ // src/tools/generate-code.ts
3449
+ import { z as z5 } from "zod";
3450
+ var generateCodeSchema = z5.object({
3451
+ query_type: z5.enum(["sql", "redis", "elasticsearch"]).describe("Type of query to convert"),
3452
+ query: z5.string().describe("The original query string"),
3453
+ database_type: z5.enum(["postgres", "mysql", "sqlserver", "sqlite", "redis", "elasticsearch"]).describe("Specific database type"),
3454
+ language: z5.enum(["csharp", "typescript", "both"]).default("both").describe("Target language(s)"),
3455
+ orm_preference: z5.enum(["ef-core", "dapper", "prisma", "all"]).default("all").describe("ORM preference for code generation")
3456
+ });
3457
+ function generateCSharpEFCore(query, dbType) {
3458
+ const selectMatch = query.match(
3459
+ /SELECT\s+(.*?)\s+FROM\s+(\w+)(?:\s+WHERE\s+(.*?))?(?:\s+ORDER BY\s+(.*?))?(?:\s+LIMIT\s+(\d+))?$/i
3460
+ );
3461
+ if (!selectMatch) {
3462
+ return `// Complex query - may require raw SQL or stored procedure
3463
+ await context.Database.SqlQuery<YourEntity>($"""${query}""").ToListAsync();`;
3464
+ }
3465
+ const [, columns, table, where, orderBy, limit] = selectMatch;
3466
+ const tableNamePascal = table.charAt(0).toUpperCase() + table.slice(1);
3467
+ const limitNum = limit ? parseInt(limit) : null;
3468
+ let code = `var result = context.${table}s`;
3469
+ if (where) {
3470
+ code += `
3471
+ .Where(x => ${formatWhereClause(where)})`;
3472
+ }
3473
+ if (orderBy) {
3474
+ code += `
3475
+ .OrderBy(x => ${formatOrderBy(orderBy)})`;
3476
+ }
3477
+ if (limitNum) {
3478
+ code += `
3479
+ .Take(${limitNum})`;
3480
+ }
3481
+ code += `
3482
+ .Select(x => new { ${formatSelectColumns(columns)} })`;
3483
+ code += `
3484
+ .ToListAsync();`;
3485
+ return code;
3486
+ }
3487
+ function generateCSharpDapper(query, dbType) {
3488
+ const connectionType = getConnectionType(dbType);
3489
+ let code = `using (var connection = new ${connectionType}(connectionString))
3490
+ {
3491
+ `;
3492
+ code += ` var result = await connection.QueryAsync<dynamic>(
3493
+ `;
3494
+ code += ` $"""${query}"""
3495
+ `;
3496
+ code += ` );
3497
+ `;
3498
+ code += `}`;
3499
+ return code;
3500
+ }
3501
+ function generateTypeScriptPrisma(query, dbType) {
3502
+ const selectMatch = query.match(
3503
+ /SELECT\s+(.*?)\s+FROM\s+(\w+)(?:\s+WHERE\s+(.*?))?(?:\s+ORDER BY\s+(.*?))?(?:\s+LIMIT\s+(\d+))?$/i
3504
+ );
3505
+ if (!selectMatch) {
3506
+ return `const result = await prisma.$queryRaw\`${query}\`;`;
3507
+ }
3508
+ const [, columns, table, where] = selectMatch;
3509
+ let code = `const result = await prisma.${table}.findMany({
3510
+ `;
3511
+ if (where) {
3512
+ code += ` where: ${formatPrismaWhere(where)},
3513
+ `;
3514
+ }
3515
+ code += ` select: {
3516
+ `;
3517
+ const cols = columns.split(",").map((c) => c.trim()).filter((c) => c !== "*");
3518
+ if (cols.length > 0 && cols[0] !== "*") {
3519
+ code += cols.map((col) => ` ${col}: true,
3520
+ `).join("");
3521
+ } else {
3522
+ code += ` // all fields
3523
+ `;
3524
+ }
3525
+ code += ` },
3526
+ `;
3527
+ code += `});`;
3528
+ return code;
3529
+ }
3530
+ function generateTypeScriptRawClient(query, dbType) {
3531
+ const client = getTypeScriptClient(dbType);
3532
+ let code = `const result = await ${client}.query(
3533
+ `;
3534
+ code += ` \`${query}\`,
3535
+ `;
3536
+ code += ` [/* parameters */]
3537
+ `;
3538
+ code += `);`;
3539
+ return code;
3540
+ }
3541
+ function generateCSharpRedis(command) {
3542
+ const parts = command.trim().split(/\s+/);
3543
+ const cmd = parts[0].toUpperCase();
3544
+ const args = parts.slice(1);
3545
+ let code = `using StackExchange.Redis;
3546
+
3547
+ `;
3548
+ code += `var db = redis.GetDatabase();
3549
+ `;
3550
+ switch (cmd) {
3551
+ case "GET":
3552
+ code += `var value = await db.StringGetAsync("${args[0]}");
3553
+ `;
3554
+ code += `var result = value.ToString();`;
3555
+ break;
3556
+ case "SET":
3557
+ code += `await db.StringSetAsync("${args[0]}", "${args.slice(1).join(" ")}");
3558
+ `;
3559
+ code += `var result = "OK";`;
3560
+ break;
3561
+ case "HGETALL":
3562
+ code += `var hashValues = await db.HashGetAllAsync("${args[0]}");
3563
+ `;
3564
+ code += `var dict = hashValues.ToDictionary(x => x.Name.ToString(), x => x.Value.ToString());`;
3565
+ break;
3566
+ case "LPUSH":
3567
+ code += `await db.ListPushAsync("${args[0]}", new RedisValue[] { ${args.slice(1).map((a) => `"${a}"`).join(", ")} });
3568
+ `;
3569
+ code += `var result = "OK";`;
3570
+ break;
3571
+ case "SADD":
3572
+ code += `await db.SetAddAsync("${args[0]}", new RedisValue[] { ${args.slice(1).map((a) => `"${a}"`).join(", ")} });
3573
+ `;
3574
+ code += `var result = "OK";`;
3575
+ break;
3576
+ default:
3577
+ code += `// StackExchange.Redis equivalent:
3578
+ `;
3579
+ code += `var result = await db.ExecuteAsync("${cmd}", new object[] { ${args.map((a) => `"${a}"`).join(", ")} });`;
3580
+ }
3581
+ return code;
3582
+ }
3583
+ function generateTypeScriptRedis(command) {
3584
+ const parts = command.trim().split(/\s+/);
3585
+ const cmd = parts[0].toLowerCase();
3586
+ const args = parts.slice(1);
3587
+ let code = ``;
3588
+ switch (cmd) {
3589
+ case "get":
3590
+ code = `const value = await redis.get("${args[0]}");
3591
+ const result = value;`;
3592
+ break;
3593
+ case "set":
3594
+ code = `await redis.set("${args[0]}", "${args.slice(1).join(" ")}");
3595
+ const result = "OK";`;
3596
+ break;
3597
+ case "hgetall":
3598
+ code = `const hashValues = await redis.hGetAll("${args[0]}");
3599
+ const result = hashValues;`;
3600
+ break;
3601
+ case "lpush":
3602
+ code = `await redis.lPush("${args[0]}", [${args.slice(1).map((a) => `"${a}"`).join(", ")}]);
3603
+ const result = "OK";`;
3604
+ break;
3605
+ case "sadd":
3606
+ code = `await redis.sAdd("${args[0]}", [${args.slice(1).map((a) => `"${a}"`).join(", ")}]);
3607
+ const result = "OK";`;
3608
+ break;
3609
+ default:
3610
+ code = `const result = await redis.sendCommand(["${cmd}", ${args.map((a) => `"${a}"`).join(", ")}]);`;
3611
+ }
3612
+ return code;
3613
+ }
3614
+ function generateCSharpElasticsearch(query) {
3615
+ let code = `using Elasticsearch.Net;
3616
+ using Nest;
3617
+
3618
+ `;
3619
+ code += `var client = new ElasticClient(settings);
3620
+
3621
+ `;
3622
+ try {
3623
+ const parsed = JSON.parse(query);
3624
+ code += `var response = await client.SearchAsync<dynamic>(s => s
3625
+ `;
3626
+ code += ` .Index("${parsed.index || "*"}")
3627
+ `;
3628
+ if (parsed.query) {
3629
+ code += ` .Query(q => /* TODO: build query from DSL */)
3630
+ `;
3631
+ }
3632
+ code += `);`;
3633
+ } catch {
3634
+ code += `// Parse query string for search
3635
+ `;
3636
+ code += `var response = await client.SearchAsync<dynamic>(s => s
3637
+ `;
3638
+ code += ` .Query(q => q.Match(m => m
3639
+ `;
3640
+ code += ` .Field("*")
3641
+ `;
3642
+ code += ` .Query("${query}")
3643
+ `;
3644
+ code += ` ))
3645
+ `;
3646
+ code += `);`;
3647
+ }
3648
+ return code;
3649
+ }
3650
+ function generateTypeScriptElasticsearch(query) {
3651
+ let code = `import { Client } from "@elastic/elasticsearch";
3652
+
3653
+ `;
3654
+ code += `const client = new Client({ node: "http://localhost:9200" });
3655
+
3656
+ `;
3657
+ try {
3658
+ const parsed = JSON.parse(query);
3659
+ code += `const response = await client.search({
3660
+ `;
3661
+ code += ` index: "${parsed.index || "*"}",
3662
+ `;
3663
+ code += ` body: ${JSON.stringify(parsed, null, 4)}
3664
+ `;
3665
+ code += `});`;
3666
+ } catch {
3667
+ code += `const response = await client.search({
3668
+ `;
3669
+ code += ` index: "*",
3670
+ `;
3671
+ code += ` body: {
3672
+ `;
3673
+ code += ` query: {
3674
+ `;
3675
+ code += ` multi_match: {
3676
+ `;
3677
+ code += ` query: "${query}",
3678
+ `;
3679
+ code += ` fields: ["*"]
3680
+ `;
3681
+ code += ` }
3682
+ `;
3683
+ code += ` }
3684
+ `;
3685
+ code += ` }
3686
+ `;
3687
+ code += `});`;
3688
+ }
3689
+ return code;
3690
+ }
3691
+ function formatWhereClause(where) {
3692
+ return where.replace(/(\w+)\s*=\s*'([^']*)'/g, `x.$1 == "$2"`).replace(/AND/gi, "&&");
3693
+ }
3694
+ function formatOrderBy(orderBy) {
3695
+ const parts = orderBy.split(",").map((p) => p.trim());
3696
+ if (parts.length === 1) {
3697
+ const [col, dir] = parts[0].split(/\s+/);
3698
+ return `x.${col}`;
3699
+ }
3700
+ return `x => ${parts.map((p) => `x.${p.split(/\s+/)[0]}`).join(", ")}`;
3701
+ }
3702
+ function formatSelectColumns(columns) {
3703
+ if (columns.trim() === "*") return "";
3704
+ return columns.split(",").map((c) => {
3705
+ const trimmed = c.trim();
3706
+ return `${trimmed}`;
3707
+ }).join(", ");
3708
+ }
3709
+ function formatPrismaWhere(where) {
3710
+ if (where.includes("=")) {
3711
+ const [col, val] = where.split("=").map((s) => s.trim());
3712
+ return `{ ${col}: "${val.replace(/['"]/g, "")}" }`;
3713
+ }
3714
+ return "{}";
3715
+ }
3716
+ function getConnectionType(dbType) {
3717
+ switch (dbType) {
3718
+ case "postgres":
3719
+ return "NpgsqlConnection";
3720
+ case "mysql":
3721
+ return "MySqlConnection";
3722
+ case "sqlserver":
3723
+ return "SqlConnection";
3724
+ case "sqlite":
3725
+ return "SqliteConnection";
3726
+ default:
3727
+ return "DbConnection";
3728
+ }
3729
+ }
3730
+ function getTypeScriptClient(dbType) {
3731
+ switch (dbType) {
3732
+ case "postgres":
3733
+ return "pool";
3734
+ case "mysql":
3735
+ return "pool";
3736
+ case "sqlserver":
3737
+ return "pool";
3738
+ case "sqlite":
3739
+ return "db";
3740
+ default:
3741
+ return "client";
3742
+ }
3743
+ }
3744
+ function generateCode(request) {
3745
+ const { query_type, query, database_type, language, orm_preference } = request;
3746
+ const response = {
3747
+ query_type,
3748
+ database_type,
3749
+ notes: []
3750
+ };
3751
+ if (language === "csharp" || language === "both") {
3752
+ response.csharp = {
3753
+ explanation: ""
3754
+ };
3755
+ if (query_type === "sql") {
3756
+ if (orm_preference === "ef-core" || orm_preference === "all") {
3757
+ response.csharp.ef_core = generateCSharpEFCore(query, database_type);
3758
+ }
3759
+ if (orm_preference === "dapper" || orm_preference === "all") {
3760
+ response.csharp.dapper = generateCSharpDapper(query, database_type);
3761
+ }
3762
+ response.csharp.explanation = `Generated C# code for ${database_type}. Remember to handle exceptions and use connection strings from configuration.`;
3763
+ } else if (query_type === "redis") {
3764
+ response.csharp.ef_core = generateCSharpRedis(query);
3765
+ response.csharp.explanation = `Install NuGet package: StackExchange.Redis. Initialize client: var redis = ConnectionMultiplexer.Connect(config);`;
3766
+ } else if (query_type === "elasticsearch") {
3767
+ response.csharp.ef_core = generateCSharpElasticsearch(query);
3768
+ response.csharp.explanation = `Install NuGet package: Elasticsearch.Net and NEST. Configure with your Elasticsearch node URL.`;
3769
+ }
3770
+ }
3771
+ if (language === "typescript" || language === "both") {
3772
+ response.typescript = {
3773
+ explanation: ""
3774
+ };
3775
+ if (query_type === "sql") {
3776
+ if (orm_preference === "prisma" || orm_preference === "all") {
3777
+ response.typescript.prisma = generateTypeScriptPrisma(query, database_type);
3778
+ }
3779
+ if (orm_preference === "dapper" || orm_preference === "all") {
3780
+ response.typescript.raw_client = generateTypeScriptRawClient(query, database_type);
3781
+ }
3782
+ response.typescript.explanation = `Generated TypeScript code. Run 'npx prisma generate' after updating schema.`;
3783
+ } else if (query_type === "redis") {
3784
+ response.typescript.prisma = generateTypeScriptRedis(query);
3785
+ response.typescript.explanation = `Install package: npm install redis. Initialize: const redis = createClient();`;
3786
+ } else if (query_type === "elasticsearch") {
3787
+ response.typescript.prisma = generateTypeScriptElasticsearch(query);
3788
+ response.typescript.explanation = `Install package: npm install @elastic/elasticsearch. Configure with your Elasticsearch node.`;
3789
+ }
3790
+ }
3791
+ response.notes.push("Code is auto-generated and may need refinement for production use.");
3792
+ response.notes.push("Always validate generated queries match your data model and business logic.");
3793
+ response.notes.push("For complex queries, consider reviewing the generated code with team members.");
3794
+ return response;
3795
+ }
3796
+
3797
+ // src/tools/generate-code-handler.ts
3798
+ function createGenerateCodeToolHandler() {
3799
+ return async (request) => {
3800
+ try {
3801
+ const params = generateCodeSchema.parse(request.input);
3802
+ const result = generateCode(params);
3803
+ return {
3804
+ content: [
3805
+ {
3806
+ type: "text",
3807
+ text: formatCodeResponse(result)
3808
+ }
3809
+ ]
3810
+ };
3811
+ } catch (error) {
3812
+ return {
3813
+ content: [
3814
+ {
3815
+ type: "text",
3816
+ text: `Error generating code: ${error}`
3817
+ }
3818
+ ],
3819
+ isError: true
3820
+ };
3821
+ }
3822
+ };
3823
+ }
3824
+ function formatCodeResponse(response) {
3825
+ let output = `# Generated Code Conversion
3826
+
3827
+ `;
3828
+ output += `**Query Type:** ${response.query_type}
3829
+ `;
3830
+ output += `**Database:** ${response.database_type}
3831
+
3832
+ `;
3833
+ if (response.csharp) {
3834
+ output += `## C# Implementation
3835
+
3836
+ `;
3837
+ if (response.csharp.ef_core) {
3838
+ output += `### Entity Framework Core
3839
+ \`\`\`csharp
3840
+ `;
3841
+ output += response.csharp.ef_core;
3842
+ output += `
3843
+ \`\`\`
3844
+
3845
+ `;
3846
+ }
3847
+ if (response.csharp.dapper) {
3848
+ output += `### Dapper
3849
+ \`\`\`csharp
3850
+ `;
3851
+ output += response.csharp.dapper;
3852
+ output += `
3853
+ \`\`\`
3854
+
3855
+ `;
3856
+ }
3857
+ output += `**Notes:** ${response.csharp.explanation}
3858
+
3859
+ `;
3860
+ }
3861
+ if (response.typescript) {
3862
+ output += `## TypeScript Implementation
3863
+
3864
+ `;
3865
+ if (response.typescript.prisma) {
3866
+ output += `### Prisma ORM / Client
3867
+ \`\`\`typescript
3868
+ `;
3869
+ output += response.typescript.prisma;
3870
+ output += `
3871
+ \`\`\`
3872
+
3873
+ `;
3874
+ }
3875
+ if (response.typescript.raw_client) {
3876
+ output += `### Raw Client
3877
+ \`\`\`typescript
3878
+ `;
3879
+ output += response.typescript.raw_client;
3880
+ output += `
3881
+ \`\`\`
3882
+
3883
+ `;
3884
+ }
3885
+ output += `**Notes:** ${response.typescript.explanation}
3886
+
3887
+ `;
3888
+ }
3889
+ if (response.notes && response.notes.length > 0) {
3890
+ output += `## Important Notes
3891
+
3892
+ `;
3893
+ response.notes.forEach((note) => {
3894
+ output += `- ${note}
3895
+ `;
3896
+ });
3897
+ }
3898
+ return output;
3899
+ }
3900
+
3901
+ // src/utils/tool-metadata.ts
3902
+ import { z as z6 } from "zod";
3903
+
3904
+ // src/utils/normalize-id.ts
3905
+ function normalizeSourceId(id) {
3906
+ return id.replace(/[^a-zA-Z0-9]/g, "_");
3907
+ }
3908
+
3909
+ // src/utils/tool-metadata.ts
3910
+ function zodToParameters(schema) {
3911
+ const parameters = [];
3912
+ for (const [key, zodType] of Object.entries(schema)) {
3913
+ const description = zodType.description || "";
3914
+ const required = !(zodType instanceof z6.ZodOptional);
3915
+ let type = "string";
3916
+ if (zodType instanceof z6.ZodString) {
3917
+ type = "string";
3918
+ } else if (zodType instanceof z6.ZodNumber) {
3919
+ type = "number";
3920
+ } else if (zodType instanceof z6.ZodBoolean) {
3921
+ type = "boolean";
3922
+ } else if (zodType instanceof z6.ZodArray) {
3923
+ type = "array";
3924
+ } else if (zodType instanceof z6.ZodObject) {
3925
+ type = "object";
3926
+ }
3927
+ parameters.push({
3928
+ name: key,
3929
+ type,
3930
+ required,
3931
+ description
3932
+ });
3933
+ }
3934
+ return parameters;
3935
+ }
3936
+ function getExecuteSqlMetadata(sourceId) {
3937
+ const sourceIds = ConnectorManager.getAvailableSourceIds();
3938
+ const sourceConfig = ConnectorManager.getSourceConfig(sourceId);
3939
+ const dbType = sourceConfig.type;
3940
+ const isSingleSource = sourceIds.length === 1;
3941
+ const registry = getToolRegistry();
3942
+ const toolConfig = registry.getBuiltinToolConfig(BUILTIN_TOOL_EXECUTE_SQL, sourceId);
3943
+ const executeOptions = {
3944
+ readonly: toolConfig?.readonly,
3945
+ maxRows: toolConfig?.max_rows
3946
+ };
3947
+ const toolName = isSingleSource ? "execute_sql" : `execute_sql_${normalizeSourceId(sourceId)}`;
3948
+ const title = isSingleSource ? `Execute SQL (${dbType})` : `Execute SQL on ${sourceId} (${dbType})`;
3949
+ const readonlyNote = executeOptions.readonly ? " [READ-ONLY MODE]" : "";
3950
+ const maxRowsNote = executeOptions.maxRows ? ` (limited to ${executeOptions.maxRows} rows)` : "";
3951
+ const description = isSingleSource ? `Execute SQL queries on the ${dbType} database${readonlyNote}${maxRowsNote}` : `Execute SQL queries on the '${sourceId}' ${dbType} database${readonlyNote}${maxRowsNote}`;
3952
+ const isReadonly = executeOptions.readonly === true;
3953
+ const annotations = {
3954
+ title,
3955
+ readOnlyHint: isReadonly,
3956
+ destructiveHint: !isReadonly,
3957
+ // Can be destructive if not readonly
3958
+ // In readonly mode, queries are more predictable (though still not strictly idempotent due to data changes)
3959
+ // In write mode, queries are definitely not idempotent
3960
+ idempotentHint: false,
3961
+ // Database operations are always against internal/closed systems, not open-world
3962
+ openWorldHint: false
3963
+ };
3964
+ return {
3965
+ name: toolName,
3966
+ description,
3967
+ schema: executeSqlSchema,
3968
+ annotations
3969
+ };
3970
+ }
3971
+ function getSearchObjectsMetadata(sourceId) {
3972
+ const sourceIds = ConnectorManager.getAvailableSourceIds();
3973
+ const sourceConfig = ConnectorManager.getSourceConfig(sourceId);
3974
+ const dbType = sourceConfig.type;
3975
+ const isSingleSource = sourceIds.length === 1;
3976
+ const toolName = isSingleSource ? "search_objects" : `search_objects_${normalizeSourceId(sourceId)}`;
3977
+ const title = isSingleSource ? `Search Database Objects (${dbType})` : `Search Database Objects on ${sourceId} (${dbType})`;
3978
+ const description = isSingleSource ? `Search and list database objects (schemas, tables, columns, procedures, indexes) on the ${dbType} database` : `Search and list database objects (schemas, tables, columns, procedures, indexes) on the '${sourceId}' ${dbType} database`;
3979
+ return {
3980
+ name: toolName,
3981
+ description,
3982
+ title
3983
+ };
3984
+ }
3985
+ function getRedisCommandMetadata(sourceId) {
3986
+ const sourceIds = ConnectorManager.getAvailableSourceIds();
3987
+ const isSingleSource = sourceIds.length === 1;
3988
+ const toolName = isSingleSource ? "redis_command" : `redis_command_${normalizeSourceId(sourceId)}`;
3989
+ const title = isSingleSource ? "Execute Redis Command" : `Execute Redis Command on ${sourceId}`;
3990
+ const description = isSingleSource ? "Execute Redis commands (GET, SET, HGETALL, LPUSH, SADD, ZADD, etc.)" : `Execute Redis commands on the '${sourceId}' Redis instance`;
3991
+ return {
3992
+ name: toolName,
3993
+ description,
3994
+ title
3995
+ };
3996
+ }
3997
+ function getElasticsearchSearchMetadata(sourceId) {
3998
+ const sourceIds = ConnectorManager.getAvailableSourceIds();
3999
+ const isSingleSource = sourceIds.length === 1;
4000
+ const toolName = isSingleSource ? "elasticsearch_search" : `elasticsearch_search_${normalizeSourceId(sourceId)}`;
4001
+ const title = isSingleSource ? "Search Elasticsearch" : `Search Elasticsearch on ${sourceId}`;
4002
+ const description = isSingleSource ? "Execute Elasticsearch queries (search, aggregations) using JSON DSL" : `Execute Elasticsearch queries on the '${sourceId}' cluster`;
4003
+ return {
4004
+ name: toolName,
4005
+ description,
4006
+ title
4007
+ };
4008
+ }
4009
+ function customParamsToToolParams(params) {
4010
+ if (!params || params.length === 0) {
4011
+ return [];
4012
+ }
4013
+ return params.map((param) => ({
4014
+ name: param.name,
4015
+ type: param.type,
4016
+ required: param.required !== false && param.default === void 0,
4017
+ description: param.description
4018
+ }));
4019
+ }
4020
+ function buildExecuteSqlTool(sourceId, toolConfig) {
4021
+ const executeSqlMetadata = getExecuteSqlMetadata(sourceId);
4022
+ const executeSqlParameters = zodToParameters(executeSqlMetadata.schema);
4023
+ const readonly = toolConfig && "readonly" in toolConfig ? toolConfig.readonly : void 0;
4024
+ const max_rows = toolConfig && "max_rows" in toolConfig ? toolConfig.max_rows : void 0;
4025
+ return {
4026
+ name: executeSqlMetadata.name,
4027
+ description: executeSqlMetadata.description,
4028
+ parameters: executeSqlParameters,
4029
+ readonly,
4030
+ max_rows
4031
+ };
4032
+ }
4033
+ function buildSearchObjectsTool(sourceId) {
4034
+ const searchMetadata = getSearchObjectsMetadata(sourceId);
4035
+ return {
4036
+ name: searchMetadata.name,
4037
+ description: searchMetadata.description,
4038
+ parameters: [
4039
+ {
4040
+ name: "object_type",
4041
+ type: "string",
4042
+ required: true,
4043
+ description: "Object type to search"
4044
+ },
4045
+ {
4046
+ name: "pattern",
4047
+ type: "string",
4048
+ required: false,
4049
+ description: "LIKE pattern (% = any chars, _ = one char). Default: %"
4050
+ },
4051
+ {
4052
+ name: "schema",
4053
+ type: "string",
4054
+ required: false,
4055
+ description: "Filter to schema"
4056
+ },
4057
+ {
4058
+ name: "table",
4059
+ type: "string",
4060
+ required: false,
4061
+ description: "Filter to table (requires schema; column/index only)"
4062
+ },
4063
+ {
4064
+ name: "detail_level",
4065
+ type: "string",
4066
+ required: false,
4067
+ description: "Detail: names (minimal), summary (metadata), full (all)"
4068
+ },
4069
+ {
4070
+ name: "limit",
4071
+ type: "integer",
4072
+ required: false,
4073
+ description: "Max results (default: 100, max: 1000)"
4074
+ }
4075
+ ],
4076
+ readonly: true
4077
+ // search_objects is always readonly
4078
+ };
4079
+ }
4080
+ function buildCustomTool(toolConfig) {
4081
+ return {
4082
+ name: toolConfig.name,
4083
+ description: toolConfig.description,
4084
+ parameters: customParamsToToolParams(toolConfig.parameters),
4085
+ statement: toolConfig.statement,
4086
+ readonly: toolConfig.readonly,
4087
+ max_rows: toolConfig.max_rows
4088
+ };
4089
+ }
4090
+ function getToolsForSource(sourceId) {
4091
+ const registry = getToolRegistry();
4092
+ const enabledToolConfigs = registry.getEnabledToolConfigs(sourceId);
4093
+ return enabledToolConfigs.map((toolConfig) => {
4094
+ if (toolConfig.name === "execute_sql") {
4095
+ return buildExecuteSqlTool(sourceId, toolConfig);
4096
+ } else if (toolConfig.name === "search_objects") {
4097
+ return buildSearchObjectsTool(sourceId);
4098
+ } else if (toolConfig.name === "redis_command") {
4099
+ return buildRedisCommandTool(sourceId);
4100
+ } else if (toolConfig.name === "elasticsearch_search") {
4101
+ return buildElasticsearchSearchTool(sourceId);
4102
+ } else if (toolConfig.name === "generate_code") {
4103
+ return buildGenerateCodeTool();
4104
+ } else {
4105
+ return buildCustomTool(toolConfig);
4106
+ }
4107
+ });
4108
+ }
4109
+ function buildRedisCommandTool(sourceId) {
4110
+ const metadata = getRedisCommandMetadata(sourceId);
4111
+ return {
4112
+ name: metadata.name,
4113
+ description: metadata.description,
4114
+ parameters: zodToParameters(redisCommandSchema)
4115
+ };
4116
+ }
4117
+ function buildElasticsearchSearchTool(sourceId) {
4118
+ const metadata = getElasticsearchSearchMetadata(sourceId);
4119
+ return {
4120
+ name: metadata.name,
4121
+ description: metadata.description,
4122
+ parameters: zodToParameters(elasticsearchSearchSchema)
4123
+ };
4124
+ }
4125
+ function getGenerateCodeMetadata() {
4126
+ const annotations = {
4127
+ title: "Generate Code Implementation",
4128
+ readOnlyHint: true,
4129
+ destructiveHint: false,
4130
+ idempotentHint: true,
4131
+ openWorldHint: false
4132
+ };
4133
+ return {
4134
+ name: "generate_code",
4135
+ description: "Convert SQL, Redis, or Elasticsearch queries to equivalent C# and TypeScript code implementations with support for multiple ORMs (EF Core, Dapper, Prisma)",
4136
+ schema: generateCodeSchema.shape,
4137
+ annotations
4138
+ };
4139
+ }
4140
+ function buildGenerateCodeTool() {
4141
+ return {
4142
+ name: "generate_code",
4143
+ description: "Convert queries to C# (EF Core, Dapper) and TypeScript (Prisma) implementations",
4144
+ parameters: zodToParameters(generateCodeSchema.shape)
4145
+ };
4146
+ }
4147
+
4148
+ // src/tools/custom-tool-handler.ts
4149
+ import { z as z7 } from "zod";
4150
+ function buildZodSchemaFromParameters(parameters) {
4151
+ if (!parameters || parameters.length === 0) {
4152
+ return {};
4153
+ }
4154
+ const schemaShape = {};
4155
+ for (const param of parameters) {
4156
+ let fieldSchema;
4157
+ switch (param.type) {
4158
+ case "string":
4159
+ fieldSchema = z7.string().describe(param.description);
4160
+ break;
4161
+ case "integer":
4162
+ fieldSchema = z7.number().int().describe(param.description);
4163
+ break;
4164
+ case "float":
4165
+ fieldSchema = z7.number().describe(param.description);
4166
+ break;
4167
+ case "boolean":
4168
+ fieldSchema = z7.boolean().describe(param.description);
4169
+ break;
4170
+ case "array":
4171
+ fieldSchema = z7.array(z7.unknown()).describe(param.description);
4172
+ break;
4173
+ default:
4174
+ throw new Error(`Unsupported parameter type: ${param.type}`);
4175
+ }
4176
+ if (param.allowed_values && param.allowed_values.length > 0) {
4177
+ if (param.type === "string") {
4178
+ fieldSchema = z7.enum(param.allowed_values).describe(param.description);
4179
+ } else {
4180
+ fieldSchema = fieldSchema.refine(
4181
+ (val) => param.allowed_values.includes(val),
4182
+ {
4183
+ message: `Value must be one of: ${param.allowed_values.join(", ")}`
4184
+ }
4185
+ );
4186
+ }
4187
+ }
4188
+ if (param.default !== void 0 || param.required === false) {
4189
+ fieldSchema = fieldSchema.optional();
4190
+ }
4191
+ schemaShape[param.name] = fieldSchema;
4192
+ }
4193
+ return schemaShape;
4194
+ }
4195
+ function createCustomToolHandler(toolConfig) {
4196
+ const zodSchemaShape = buildZodSchemaFromParameters(toolConfig.parameters);
4197
+ const zodSchema = z7.object(zodSchemaShape);
4198
+ return async (args, extra) => {
4199
+ const startTime = Date.now();
4200
+ let success = true;
4201
+ let errorMessage;
4202
+ let paramValues = [];
4203
+ try {
4204
+ const validatedArgs = zodSchema.parse(args);
4205
+ await ConnectorManager.ensureConnected(toolConfig.source);
4206
+ const connector = ConnectorManager.getCurrentConnector(toolConfig.source);
4207
+ const executeOptions = {
4208
+ readonly: toolConfig.readonly,
4209
+ maxRows: toolConfig.max_rows
4210
+ };
4211
+ const isReadonly = executeOptions.readonly === true;
4212
+ if (isReadonly && !isReadOnlySQL(toolConfig.statement, connector.id)) {
4213
+ errorMessage = createReadonlyViolationMessage(toolConfig.name, toolConfig.source, connector.id);
4214
+ success = false;
4215
+ return createToolErrorResponse(errorMessage, "READONLY_VIOLATION");
4216
+ }
4217
+ paramValues = mapArgumentsToArray(
4218
+ toolConfig.parameters,
4219
+ validatedArgs
4220
+ );
4221
+ const result = await connector.executeSQL(
4222
+ toolConfig.statement,
4223
+ executeOptions,
4224
+ paramValues
4225
+ );
4226
+ const responseData = {
4227
+ rows: result.rows,
4228
+ count: result.rowCount,
4229
+ source_id: toolConfig.source
4230
+ };
4231
+ return createToolSuccessResponse(responseData);
4232
+ } catch (error) {
4233
+ success = false;
4234
+ errorMessage = error.message;
4235
+ if (error instanceof z7.ZodError) {
4236
+ const issues = error.issues.map((i) => `${i.path.join(".")}: ${i.message}`).join("; ");
4237
+ errorMessage = `Parameter validation failed: ${issues}`;
4238
+ } else {
4239
+ errorMessage = `${errorMessage}
4240
+
4241
+ SQL: ${toolConfig.statement}
4242
+ Parameters: ${JSON.stringify(paramValues)}`;
4243
+ }
4244
+ return createToolErrorResponse(errorMessage, "EXECUTION_ERROR");
4245
+ } finally {
4246
+ trackToolRequest(
4247
+ {
4248
+ sourceId: toolConfig.source,
4249
+ toolName: toolConfig.name,
4250
+ sql: toolConfig.statement
4251
+ },
4252
+ startTime,
4253
+ extra,
4254
+ success,
4255
+ errorMessage
4256
+ );
4257
+ }
4258
+ };
4259
+ }
4260
+
4261
+ // src/tools/index.ts
4262
+ function registerTools(server) {
4263
+ const sourceIds = ConnectorManager.getAvailableSourceIds();
4264
+ if (sourceIds.length === 0) {
4265
+ throw new Error("No database sources configured");
4266
+ }
4267
+ const registry = getToolRegistry();
4268
+ registerGenerateCodeTool(server);
4269
+ for (const sourceId of sourceIds) {
4270
+ const enabledTools = registry.getEnabledToolConfigs(sourceId);
4271
+ for (const toolConfig of enabledTools) {
4272
+ if (toolConfig.name === BUILTIN_TOOL_EXECUTE_SQL) {
4273
+ registerExecuteSqlTool(server, sourceId);
4274
+ } else if (toolConfig.name === BUILTIN_TOOL_SEARCH_OBJECTS) {
4275
+ registerSearchObjectsTool(server, sourceId);
4276
+ } else if (toolConfig.name === BUILTIN_TOOL_REDIS_COMMAND) {
4277
+ registerRedisCommandTool(server, sourceId);
4278
+ } else if (toolConfig.name === BUILTIN_TOOL_ELASTICSEARCH_SEARCH) {
4279
+ registerElasticsearchSearchTool(server, sourceId);
4280
+ } else {
4281
+ registerCustomTool(server, sourceId, toolConfig);
4282
+ }
4283
+ }
4284
+ }
4285
+ }
4286
+ function registerExecuteSqlTool(server, sourceId) {
4287
+ const metadata = getExecuteSqlMetadata(sourceId);
4288
+ server.registerTool(
4289
+ metadata.name,
4290
+ {
4291
+ description: metadata.description,
4292
+ inputSchema: metadata.schema,
4293
+ annotations: metadata.annotations
4294
+ },
4295
+ createExecuteSqlToolHandler(sourceId)
4296
+ );
4297
+ }
4298
+ function registerSearchObjectsTool(server, sourceId) {
4299
+ const metadata = getSearchObjectsMetadata(sourceId);
4300
+ server.registerTool(
4301
+ metadata.name,
4302
+ {
4303
+ description: metadata.description,
4304
+ inputSchema: searchDatabaseObjectsSchema,
4305
+ annotations: {
4306
+ title: metadata.title,
4307
+ readOnlyHint: true,
4308
+ destructiveHint: false,
4309
+ idempotentHint: true,
4310
+ openWorldHint: false
4311
+ }
4312
+ },
4313
+ createSearchDatabaseObjectsToolHandler(sourceId)
4314
+ );
4315
+ }
4316
+ function registerRedisCommandTool(server, sourceId) {
4317
+ const metadata = getRedisCommandMetadata(sourceId);
4318
+ server.registerTool(
4319
+ metadata.name,
4320
+ {
4321
+ description: metadata.description,
4322
+ inputSchema: redisCommandSchema,
4323
+ annotations: {
4324
+ title: metadata.title,
4325
+ readOnlyHint: false,
4326
+ destructiveHint: true,
4327
+ idempotentHint: false,
4328
+ openWorldHint: false
4329
+ }
4330
+ },
4331
+ createRedisCommandToolHandler(sourceId)
4332
+ );
4333
+ }
4334
+ function registerElasticsearchSearchTool(server, sourceId) {
4335
+ const metadata = getElasticsearchSearchMetadata(sourceId);
4336
+ server.registerTool(
4337
+ metadata.name,
4338
+ {
4339
+ description: metadata.description,
4340
+ inputSchema: elasticsearchSearchSchema,
4341
+ annotations: {
4342
+ title: metadata.title,
4343
+ readOnlyHint: true,
4344
+ destructiveHint: false,
4345
+ idempotentHint: true,
4346
+ openWorldHint: false
4347
+ }
4348
+ },
4349
+ createElasticsearchSearchToolHandler(sourceId)
4350
+ );
4351
+ }
4352
+ function registerCustomTool(server, sourceId, toolConfig) {
4353
+ if (toolConfig.name === "execute_sql" || toolConfig.name === "search_objects") {
4354
+ return;
4355
+ }
4356
+ const sourceConfig = ConnectorManager.getSourceConfig(sourceId);
4357
+ const dbType = sourceConfig.type;
4358
+ const isReadOnly = isReadOnlySQL(toolConfig.statement, dbType);
4359
+ const zodSchema = buildZodSchemaFromParameters(toolConfig.parameters);
4360
+ server.registerTool(
4361
+ toolConfig.name,
4362
+ {
4363
+ description: toolConfig.description,
4364
+ inputSchema: zodSchema,
4365
+ annotations: {
4366
+ title: `${toolConfig.name} (${dbType})`,
4367
+ readOnlyHint: isReadOnly,
4368
+ destructiveHint: !isReadOnly,
4369
+ idempotentHint: isReadOnly,
4370
+ openWorldHint: false
4371
+ }
4372
+ },
4373
+ createCustomToolHandler(toolConfig)
4374
+ );
4375
+ }
4376
+ function registerGenerateCodeTool(server) {
4377
+ const metadata = getGenerateCodeMetadata();
4378
+ server.registerTool(
4379
+ metadata.name,
4380
+ {
4381
+ description: metadata.description,
4382
+ inputSchema: metadata.schema,
4383
+ annotations: metadata.annotations
4384
+ },
4385
+ createGenerateCodeToolHandler()
4386
+ );
4387
+ }
4388
+
4389
+ // src/api/sources.ts
4390
+ function transformSourceConfig(source) {
4391
+ if (!source.type && source.dsn) {
4392
+ const inferredType = getDatabaseTypeFromDSN(source.dsn);
4393
+ if (inferredType) {
4394
+ source.type = inferredType;
4395
+ }
4396
+ }
4397
+ if (!source.type) {
4398
+ throw new Error(`Source ${source.id} is missing required type field`);
4399
+ }
4400
+ const dataSource = {
4401
+ id: source.id,
4402
+ type: source.type
4403
+ };
4404
+ if (source.description) {
4405
+ dataSource.description = source.description;
4406
+ }
4407
+ if (source.host) {
4408
+ dataSource.host = source.host;
4409
+ }
4410
+ if (source.port !== void 0) {
4411
+ dataSource.port = source.port;
4412
+ }
4413
+ if (source.database) {
4414
+ dataSource.database = source.database;
4415
+ }
4416
+ if (source.user) {
4417
+ dataSource.user = source.user;
4418
+ }
4419
+ if (source.ssh_host) {
4420
+ const sshTunnel = {
4421
+ enabled: true,
4422
+ ssh_host: source.ssh_host
4423
+ };
4424
+ if (source.ssh_port !== void 0) {
4425
+ sshTunnel.ssh_port = source.ssh_port;
4426
+ }
4427
+ if (source.ssh_user) {
4428
+ sshTunnel.ssh_user = source.ssh_user;
4429
+ }
4430
+ dataSource.ssh_tunnel = sshTunnel;
4431
+ }
4432
+ dataSource.tools = getToolsForSource(source.id);
4433
+ return dataSource;
4434
+ }
4435
+ function listSources(req, res) {
4436
+ try {
4437
+ const sourceConfigs = ConnectorManager.getAllSourceConfigs();
4438
+ const sources = sourceConfigs.map((config) => {
4439
+ return transformSourceConfig(config);
4440
+ });
4441
+ res.json(sources);
4442
+ } catch (error) {
4443
+ console.error("Error listing sources:", error);
4444
+ const errorResponse = {
4445
+ error: error instanceof Error ? error.message : "Internal server error"
4446
+ };
4447
+ res.status(500).json(errorResponse);
4448
+ }
4449
+ }
4450
+ function getSource(req, res) {
4451
+ try {
4452
+ const sourceId = req.params.sourceId;
4453
+ const sourceConfig = ConnectorManager.getSourceConfig(sourceId);
4454
+ if (!sourceConfig) {
4455
+ const errorResponse = {
4456
+ error: "Source not found",
4457
+ source_id: sourceId
4458
+ };
4459
+ res.status(404).json(errorResponse);
4460
+ return;
4461
+ }
4462
+ const dataSource = transformSourceConfig(sourceConfig);
4463
+ res.json(dataSource);
4464
+ } catch (error) {
4465
+ console.error(`Error getting source ${req.params.sourceId}:`, error);
4466
+ const errorResponse = {
4467
+ error: error instanceof Error ? error.message : "Internal server error"
4468
+ };
4469
+ res.status(500).json(errorResponse);
4470
+ }
4471
+ }
4472
+
4473
+ // src/api/requests.ts
4474
+ function listRequests(req, res) {
4475
+ try {
4476
+ const sourceId = req.query.source_id;
4477
+ const requests = requestStore.getAll(sourceId);
4478
+ res.json({
4479
+ requests,
4480
+ total: requests.length
4481
+ });
4482
+ } catch (error) {
4483
+ console.error("Error listing requests:", error);
4484
+ res.status(500).json({
4485
+ error: error instanceof Error ? error.message : "Internal server error"
4486
+ });
4487
+ }
4488
+ }
4489
+
4490
+ // src/utils/startup-table.ts
4491
+ var BOX = {
4492
+ topLeft: "\u250C",
4493
+ topRight: "\u2510",
4494
+ bottomLeft: "\u2514",
4495
+ bottomRight: "\u2518",
4496
+ horizontal: "\u2500",
4497
+ vertical: "\u2502",
4498
+ leftT: "\u251C",
4499
+ rightT: "\u2524",
4500
+ bullet: "\u2022"
4501
+ };
4502
+ function parseHostAndDatabase(source) {
4503
+ if (source.dsn) {
4504
+ const parsed = parseConnectionInfoFromDSN(source.dsn);
4505
+ if (parsed) {
4506
+ if (parsed.type === "sqlite") {
4507
+ return { host: "", database: parsed.database || ":memory:" };
4508
+ }
4509
+ if (!parsed.host) {
4510
+ return { host: "", database: parsed.database || "" };
4511
+ }
4512
+ const port = parsed.port ?? getDefaultPortForType(parsed.type);
4513
+ const host2 = port ? `${parsed.host}:${port}` : parsed.host;
4514
+ return { host: host2, database: parsed.database || "" };
4515
+ }
4516
+ return { host: "unknown", database: "" };
4517
+ }
4518
+ const host = source.host ? source.port ? `${source.host}:${source.port}` : source.host : "";
4519
+ const database = source.database || "";
4520
+ return { host, database };
4521
+ }
4522
+ function horizontalLine(width, left, right) {
4523
+ return left + BOX.horizontal.repeat(width - 2) + right;
4524
+ }
4525
+ function fitString(str, width) {
4526
+ if (str.length > width) {
4527
+ return str.slice(0, width - 1) + "\u2026";
4528
+ }
4529
+ return str.padEnd(width);
4530
+ }
4531
+ function formatHostDatabase(host, database) {
4532
+ return host ? database ? `${host}/${database}` : host : database || "";
4533
+ }
4534
+ function generateStartupTable(sources) {
4535
+ if (sources.length === 0) {
4536
+ return "";
4537
+ }
4538
+ const idTypeWidth = Math.max(
4539
+ 20,
4540
+ ...sources.map((s) => `${s.id} (${s.type})`.length)
4541
+ );
4542
+ const hostDbWidth = Math.max(
4543
+ 24,
4544
+ ...sources.map((s) => formatHostDatabase(s.host, s.database).length)
4545
+ );
4546
+ const modeWidth = Math.max(
4547
+ 10,
4548
+ ...sources.map((s) => {
4549
+ const modes = [];
4550
+ if (s.isDemo) modes.push("DEMO");
4551
+ if (s.readonly) modes.push("READ-ONLY");
4552
+ return modes.join(" ").length;
4553
+ })
4554
+ );
4555
+ const totalWidth = 2 + idTypeWidth + 3 + hostDbWidth + 3 + modeWidth + 2;
4556
+ const lines = [];
4557
+ for (let i = 0; i < sources.length; i++) {
4558
+ const source = sources[i];
4559
+ const isFirst = i === 0;
4560
+ const isLast = i === sources.length - 1;
4561
+ if (isFirst) {
4562
+ lines.push(horizontalLine(totalWidth, BOX.topLeft, BOX.topRight));
4563
+ }
4564
+ const idType = fitString(`${source.id} (${source.type})`, idTypeWidth);
4565
+ const hostDb = fitString(
4566
+ formatHostDatabase(source.host, source.database),
4567
+ hostDbWidth
4568
+ );
4569
+ const modes = [];
4570
+ if (source.isDemo) modes.push("DEMO");
4571
+ if (source.readonly) modes.push("READ-ONLY");
4572
+ const modeStr = fitString(modes.join(" "), modeWidth);
4573
+ lines.push(
4574
+ `${BOX.vertical} ${idType} ${BOX.vertical} ${hostDb} ${BOX.vertical} ${modeStr} ${BOX.vertical}`
4575
+ );
4576
+ lines.push(horizontalLine(totalWidth, BOX.leftT, BOX.rightT));
4577
+ for (const tool of source.tools) {
4578
+ const toolLine = ` ${BOX.bullet} ${tool}`;
4579
+ lines.push(
4580
+ `${BOX.vertical} ${fitString(toolLine, totalWidth - 4)} ${BOX.vertical}`
4581
+ );
4582
+ }
4583
+ if (isLast) {
4584
+ lines.push(horizontalLine(totalWidth, BOX.bottomLeft, BOX.bottomRight));
4585
+ } else {
4586
+ lines.push(horizontalLine(totalWidth, BOX.leftT, BOX.rightT));
4587
+ }
4588
+ }
4589
+ return lines.join("\n");
4590
+ }
4591
+ function buildSourceDisplayInfo(sourceConfigs, getToolsForSource2, isDemo) {
4592
+ return sourceConfigs.map((source) => {
4593
+ const { host, database } = parseHostAndDatabase(source);
4594
+ return {
4595
+ id: source.id,
4596
+ type: source.type || "sqlite",
4597
+ host,
4598
+ database,
4599
+ readonly: source.readonly || false,
4600
+ isDemo,
4601
+ tools: getToolsForSource2(source.id)
4602
+ };
4603
+ });
4604
+ }
4605
+
4606
+ // src/server.ts
4607
+ var __filename = fileURLToPath(import.meta.url);
4608
+ var __dirname = path.dirname(__filename);
4609
+ var packageJsonPath = path.join(__dirname, "..", "package.json");
4610
+ var packageJson = JSON.parse(readFileSync(packageJsonPath, "utf8"));
4611
+ var SERVER_NAME = "Datafy MCP Server";
4612
+ var SERVER_VERSION = packageJson.version;
4613
+ function generateBanner(version, modes = []) {
4614
+ const modeText = modes.length > 0 ? ` [${modes.join(" | ")}]` : "";
4615
+ return `
4616
+ ____ _ __
4617
+ | _ \\ __ _| |_ __ _ / _|_ _
4618
+ | | | |/ _\` | __/ _\` | |_| | | |
4619
+ | |_| | (_| | || (_| | _| |_| |
4620
+ |____/ \\__,_|\\__\\__,_|_| \\__, |
4621
+ |___/
4622
+
4623
+ v${version}${modeText} - Minimal Database MCP Server
4624
+ `;
4625
+ }
4626
+ async function main() {
4627
+ try {
4628
+ const sourceConfigsData = await resolveSourceConfigs();
4629
+ if (!sourceConfigsData) {
4630
+ const samples = ConnectorRegistry.getAllSampleDSNs();
4631
+ const sampleFormats = Object.entries(samples).map(([id, dsn]) => ` - ${id}: ${dsn}`).join("\n");
4632
+ console.error(`
4633
+ ERROR: Database connection configuration is required.
4634
+ Please provide configuration in one of these ways (in order of priority):
4635
+
4636
+ 1. Use demo mode: --demo (uses in-memory SQLite with sample employee database)
4637
+ 2. TOML config file: --config=path/to/dbhub.toml or ./dbhub.toml
4638
+ 3. Command line argument: --dsn="your-connection-string"
4639
+ 4. Environment variable: export DSN="your-connection-string"
4640
+ 5. .env file: DSN=your-connection-string
4641
+
4642
+ Example DSN formats:
4643
+ ${sampleFormats}
4644
+
4645
+ Example TOML config (dbhub.toml):
4646
+ [[sources]]
4647
+ id = "my_db"
4648
+ dsn = "postgres://user:pass@localhost:5432/dbname"
4649
+
4650
+ See documentation for more details on configuring database connections.
4651
+ `);
4652
+ process.exit(1);
4653
+ }
4654
+ const connectorManager = new ConnectorManager();
4655
+ const sources = sourceConfigsData.sources;
4656
+ console.error(`Configuration source: ${sourceConfigsData.source}`);
4657
+ await connectorManager.connectWithSources(sources);
4658
+ const { initializeToolRegistry } = await import("./registry-U5HSB2VB.js");
4659
+ initializeToolRegistry({
4660
+ sources: sourceConfigsData.sources,
4661
+ tools: sourceConfigsData.tools
4662
+ });
4663
+ console.error("Tool registry initialized");
4664
+ const createServer = () => {
4665
+ const server = new McpServer({
4666
+ name: SERVER_NAME,
4667
+ version: SERVER_VERSION
4668
+ });
4669
+ registerTools(server);
4670
+ return server;
4671
+ };
4672
+ const transportData = resolveTransport();
4673
+ const port = transportData.type === "http" ? resolvePort().port : null;
4674
+ const activeModes = [];
4675
+ const modeDescriptions = [];
4676
+ const isDemo = isDemoMode();
4677
+ if (isDemo) {
4678
+ activeModes.push("DEMO");
4679
+ modeDescriptions.push("using sample employee database");
4680
+ }
4681
+ if (activeModes.length > 0) {
4682
+ console.error(`Running in ${activeModes.join(" and ")} mode - ${modeDescriptions.join(", ")}`);
4683
+ }
4684
+ console.error(generateBanner(SERVER_VERSION, activeModes));
4685
+ const sourceDisplayInfos = buildSourceDisplayInfo(
4686
+ sources,
4687
+ (sourceId) => getToolsForSource(sourceId).map((t) => t.readonly ? `\u{1F512} ${t.name}` : t.name),
4688
+ isDemo
4689
+ );
4690
+ console.error(generateStartupTable(sourceDisplayInfos));
4691
+ if (transportData.type === "http") {
4692
+ const app = express();
4693
+ app.use(express.json());
4694
+ app.use((req, res, next) => {
4695
+ const origin = req.headers.origin;
4696
+ if (origin && !origin.startsWith("http://localhost") && !origin.startsWith("https://localhost")) {
4697
+ return res.status(403).json({ error: "Forbidden origin" });
4698
+ }
4699
+ res.header("Access-Control-Allow-Origin", origin || "http://localhost");
4700
+ res.header("Access-Control-Allow-Methods", "GET, POST, OPTIONS");
4701
+ res.header("Access-Control-Allow-Headers", "Content-Type, Mcp-Session-Id");
4702
+ res.header("Access-Control-Allow-Credentials", "true");
4703
+ if (req.method === "OPTIONS") {
4704
+ return res.sendStatus(200);
4705
+ }
4706
+ next();
4707
+ });
4708
+ const frontendPath = path.join(__dirname, "public");
4709
+ app.use(express.static(frontendPath));
4710
+ app.get("/healthz", (req, res) => {
4711
+ res.status(200).send("OK");
4712
+ });
4713
+ app.get("/api/sources", listSources);
4714
+ app.get("/api/sources/:sourceId", getSource);
4715
+ app.get("/api/requests", listRequests);
4716
+ app.get("/mcp", (req, res) => {
4717
+ res.status(405).json({
4718
+ error: "Method Not Allowed",
4719
+ message: "SSE streaming is not supported in stateless mode. Use POST requests with JSON responses."
4720
+ });
4721
+ });
4722
+ app.post("/mcp", async (req, res) => {
4723
+ try {
4724
+ const transport = new StreamableHTTPServerTransport({
4725
+ sessionIdGenerator: void 0,
4726
+ // Disable session management for stateless mode
4727
+ enableJsonResponse: true
4728
+ // Use JSON responses (SSE not supported in stateless mode)
4729
+ });
4730
+ const server = createServer();
4731
+ await server.connect(transport);
4732
+ await transport.handleRequest(req, res, req.body);
4733
+ } catch (error) {
4734
+ console.error("Error handling request:", error);
4735
+ if (!res.headersSent) {
4736
+ res.status(500).json({ error: "Internal server error" });
4737
+ }
4738
+ }
4739
+ });
4740
+ if (process.env.NODE_ENV !== "development") {
4741
+ app.get("*", (req, res) => {
4742
+ res.sendFile(path.join(frontendPath, "index.html"));
4743
+ });
4744
+ }
4745
+ app.listen(port, "0.0.0.0", () => {
4746
+ if (process.env.NODE_ENV === "development") {
4747
+ console.error("Development mode detected!");
4748
+ console.error(" Workbench dev server (with HMR): http://localhost:5173");
4749
+ console.error(" Backend API: http://localhost:8080");
4750
+ console.error("");
4751
+ } else {
4752
+ console.error(`Workbench at http://localhost:${port}/`);
4753
+ }
4754
+ console.error(`MCP server endpoint at http://localhost:${port}/mcp`);
4755
+ });
4756
+ } else {
4757
+ const server = createServer();
4758
+ const transport = new StdioServerTransport();
4759
+ await server.connect(transport);
4760
+ console.error("MCP server running on stdio");
4761
+ process.on("SIGINT", async () => {
4762
+ console.error("Shutting down...");
4763
+ await transport.close();
4764
+ process.exit(0);
4765
+ });
4766
+ }
4767
+ } catch (err) {
4768
+ console.error("Fatal error:", err);
4769
+ process.exit(1);
4770
+ }
4771
+ }
4772
+
4773
+ // src/index.ts
4774
+ main().catch((error) => {
4775
+ console.error("Fatal error:", error);
4776
+ process.exit(1);
4777
+ });