@teckedd-code2save/datafy 0.17.0 → 1.0.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.
@@ -302,7 +302,9 @@ var SSHTunnel = class {
302
302
  privateKey,
303
303
  targetConfig.passphrase,
304
304
  previousStream,
305
- `jump host ${i + 1}`
305
+ `jump host ${i + 1}`,
306
+ targetConfig.keepaliveInterval,
307
+ targetConfig.keepaliveCountMax
306
308
  );
307
309
  console.error(` \u2192 Forwarding through ${jumpHost.host}:${jumpHost.port} to ${nextHost.host}:${nextHost.port}`);
308
310
  forwardStream = await this.forwardTo(client, nextHost.host, nextHost.port);
@@ -328,7 +330,9 @@ var SSHTunnel = class {
328
330
  privateKey,
329
331
  targetConfig.passphrase,
330
332
  previousStream,
331
- jumpHosts.length > 0 ? "target host" : void 0
333
+ jumpHosts.length > 0 ? "target host" : void 0,
334
+ targetConfig.keepaliveInterval,
335
+ targetConfig.keepaliveCountMax
332
336
  );
333
337
  this.sshClients.push(finalClient);
334
338
  return finalClient;
@@ -336,7 +340,7 @@ var SSHTunnel = class {
336
340
  /**
337
341
  * Connect to a single SSH host.
338
342
  */
339
- connectToHost(hostInfo, password, privateKey, passphrase, sock, label) {
343
+ connectToHost(hostInfo, password, privateKey, passphrase, sock, label, keepaliveInterval, keepaliveCountMax) {
340
344
  return new Promise((resolve, reject) => {
341
345
  const client = new Client();
342
346
  const sshConfig = {
@@ -356,6 +360,17 @@ var SSHTunnel = class {
356
360
  if (sock) {
357
361
  sshConfig.sock = sock;
358
362
  }
363
+ if (keepaliveInterval !== void 0) {
364
+ if (Number.isNaN(keepaliveInterval) || keepaliveInterval < 0) {
365
+ const desc = label || `${hostInfo.host}:${hostInfo.port}`;
366
+ console.warn(
367
+ `Invalid SSH keepaliveInterval (${keepaliveInterval}) for ${desc}; keepalive configuration will be ignored.`
368
+ );
369
+ } else if (keepaliveInterval > 0) {
370
+ sshConfig.keepaliveInterval = keepaliveInterval * 1e3;
371
+ sshConfig.keepaliveCountMax = keepaliveCountMax ?? 3;
372
+ }
373
+ }
359
374
  const onError = (err) => {
360
375
  client.removeListener("ready", onReady);
361
376
  client.destroy();
@@ -1017,6 +1032,27 @@ function resolveSSHConfig() {
1017
1032
  config.proxyJump = process.env.SSH_PROXY_JUMP;
1018
1033
  sources.push("SSH_PROXY_JUMP from environment");
1019
1034
  }
1035
+ const parseNonNegativeInteger = (value, name) => {
1036
+ const parsed = Number(value);
1037
+ if (!Number.isInteger(parsed) || parsed < 0) {
1038
+ throw new Error(`Invalid value for ${name}: "${value}". Expected a non-negative integer.`);
1039
+ }
1040
+ return parsed;
1041
+ };
1042
+ if (args["ssh-keepalive-interval"]) {
1043
+ config.keepaliveInterval = parseNonNegativeInteger(args["ssh-keepalive-interval"], "ssh-keepalive-interval");
1044
+ sources.push("ssh-keepalive-interval from command line");
1045
+ } else if (process.env.SSH_KEEPALIVE_INTERVAL) {
1046
+ config.keepaliveInterval = parseNonNegativeInteger(process.env.SSH_KEEPALIVE_INTERVAL, "SSH_KEEPALIVE_INTERVAL");
1047
+ sources.push("SSH_KEEPALIVE_INTERVAL from environment");
1048
+ }
1049
+ if (args["ssh-keepalive-count-max"]) {
1050
+ config.keepaliveCountMax = parseNonNegativeInteger(args["ssh-keepalive-count-max"], "ssh-keepalive-count-max");
1051
+ sources.push("ssh-keepalive-count-max from command line");
1052
+ } else if (process.env.SSH_KEEPALIVE_COUNT_MAX) {
1053
+ config.keepaliveCountMax = parseNonNegativeInteger(process.env.SSH_KEEPALIVE_COUNT_MAX, "SSH_KEEPALIVE_COUNT_MAX");
1054
+ sources.push("SSH_KEEPALIVE_COUNT_MAX from environment");
1055
+ }
1020
1056
  if (!config.host || !config.username) {
1021
1057
  throw new Error("SSH tunnel configuration requires at least --ssh-host and --ssh-user");
1022
1058
  }
@@ -1096,6 +1132,8 @@ async function resolveSourceConfigs() {
1096
1132
  source.ssh_password = sshResult.config.password;
1097
1133
  source.ssh_key = sshResult.config.privateKey;
1098
1134
  source.ssh_passphrase = sshResult.config.passphrase;
1135
+ source.ssh_keepalive_interval = sshResult.config.keepaliveInterval;
1136
+ source.ssh_keepalive_count_max = sshResult.config.keepaliveCountMax;
1099
1137
  }
1100
1138
  if (dsnResult.isDemo) {
1101
1139
  const { getSqliteInMemorySetupSql } = await import("./demo-loader-PSMTLZ2T.js");
@@ -1345,6 +1383,18 @@ function validateSourceConfig(source, configPath) {
1345
1383
  );
1346
1384
  }
1347
1385
  }
1386
+ if (source.search_path !== void 0) {
1387
+ if (source.type !== "postgres") {
1388
+ throw new Error(
1389
+ `Configuration file ${configPath}: source '${source.id}' has 'search_path' but it is only supported for PostgreSQL sources.`
1390
+ );
1391
+ }
1392
+ if (typeof source.search_path !== "string" || source.search_path.trim().length === 0) {
1393
+ throw new Error(
1394
+ `Configuration file ${configPath}: source '${source.id}' has invalid search_path. Must be a non-empty string of comma-separated schema names (e.g., "myschema,public").`
1395
+ );
1396
+ }
1397
+ }
1348
1398
  if (source.readonly !== void 0) {
1349
1399
  throw new Error(
1350
1400
  `Configuration file ${configPath}: source '${source.id}' has 'readonly' field, but readonly must be configured per-tool, not per-source. Move 'readonly' to [[tools]] configuration instead.`
@@ -1412,7 +1462,8 @@ function buildDSNFromSource(source) {
1412
1462
  `Source '${source.id}': 'database' field is required for SQLite`
1413
1463
  );
1414
1464
  }
1415
- return `sqlite:///${source.database}`;
1465
+ const prefix = source.database.startsWith("/") ? "sqlite://" : "sqlite:///";
1466
+ return `${prefix}${source.database}`;
1416
1467
  }
1417
1468
  if (source.type === "redis") {
1418
1469
  const host = source.host || "localhost";
@@ -1596,7 +1647,9 @@ var ConnectorManager = class {
1596
1647
  password: source.ssh_password,
1597
1648
  privateKey: source.ssh_key,
1598
1649
  passphrase: source.ssh_passphrase,
1599
- proxyJump: source.ssh_proxy_jump
1650
+ proxyJump: source.ssh_proxy_jump,
1651
+ keepaliveInterval: source.ssh_keepalive_interval,
1652
+ keepaliveCountMax: source.ssh_keepalive_count_max
1600
1653
  };
1601
1654
  if (!sshConfig.password && !sshConfig.privateKey) {
1602
1655
  throw new Error(
@@ -1637,6 +1690,9 @@ var ConnectorManager = class {
1637
1690
  if (source.readonly !== void 0) {
1638
1691
  config.readonly = source.readonly;
1639
1692
  }
1693
+ if (source.search_path) {
1694
+ config.searchPath = source.search_path;
1695
+ }
1640
1696
  await connector.connect(actualDSN, source.init_script, config);
1641
1697
  this.connectors.set(sourceId, connector);
1642
1698
  if (!this.sourceIds.includes(sourceId)) {
@@ -1772,60 +1828,213 @@ var ConnectorManager = class {
1772
1828
  };
1773
1829
 
1774
1830
  // src/utils/sql-parser.ts
1775
- function stripCommentsAndStrings(sql) {
1776
- let result = "";
1831
+ var TokenType = { Plain: 0, Comment: 1, QuotedBlock: 2 };
1832
+ function plainToken(i) {
1833
+ return { type: TokenType.Plain, end: i + 1 };
1834
+ }
1835
+ function scanSingleLineComment(sql, i) {
1836
+ if (sql[i] !== "-" || sql[i + 1] !== "-") {
1837
+ return null;
1838
+ }
1839
+ let j = i;
1840
+ while (j < sql.length && sql[j] !== "\n") {
1841
+ j++;
1842
+ }
1843
+ return { type: TokenType.Comment, end: j };
1844
+ }
1845
+ function scanMultiLineComment(sql, i) {
1846
+ if (sql[i] !== "/" || sql[i + 1] !== "*") {
1847
+ return null;
1848
+ }
1849
+ let j = i + 2;
1850
+ while (j < sql.length && !(sql[j] === "*" && sql[j + 1] === "/")) {
1851
+ j++;
1852
+ }
1853
+ if (j < sql.length) {
1854
+ j += 2;
1855
+ }
1856
+ return { type: TokenType.Comment, end: j };
1857
+ }
1858
+ function scanNestedMultiLineComment(sql, i) {
1859
+ if (sql[i] !== "/" || sql[i + 1] !== "*") {
1860
+ return null;
1861
+ }
1862
+ let j = i + 2;
1863
+ let depth = 1;
1864
+ while (j < sql.length && depth > 0) {
1865
+ if (sql[j] === "/" && sql[j + 1] === "*") {
1866
+ depth++;
1867
+ j += 2;
1868
+ } else if (sql[j] === "*" && sql[j + 1] === "/") {
1869
+ depth--;
1870
+ j += 2;
1871
+ } else {
1872
+ j++;
1873
+ }
1874
+ }
1875
+ return { type: TokenType.Comment, end: j };
1876
+ }
1877
+ function scanSingleQuotedString(sql, i) {
1878
+ if (sql[i] !== "'") {
1879
+ return null;
1880
+ }
1881
+ let j = i + 1;
1882
+ while (j < sql.length) {
1883
+ if (sql[j] === "'" && sql[j + 1] === "'") {
1884
+ j += 2;
1885
+ } else if (sql[j] === "'") {
1886
+ j++;
1887
+ break;
1888
+ } else {
1889
+ j++;
1890
+ }
1891
+ }
1892
+ return { type: TokenType.QuotedBlock, end: j };
1893
+ }
1894
+ function scanDoubleQuotedString(sql, i) {
1895
+ if (sql[i] !== '"') {
1896
+ return null;
1897
+ }
1898
+ let j = i + 1;
1899
+ while (j < sql.length) {
1900
+ if (sql[j] === '"' && sql[j + 1] === '"') {
1901
+ j += 2;
1902
+ } else if (sql[j] === '"') {
1903
+ j++;
1904
+ break;
1905
+ } else {
1906
+ j++;
1907
+ }
1908
+ }
1909
+ return { type: TokenType.QuotedBlock, end: j };
1910
+ }
1911
+ var dollarQuoteOpenRegex = /^\$([a-zA-Z_]\w*)?\$/;
1912
+ function scanDollarQuotedBlock(sql, i) {
1913
+ if (sql[i] !== "$") {
1914
+ return null;
1915
+ }
1916
+ const next = sql[i + 1];
1917
+ if (next >= "0" && next <= "9") {
1918
+ return null;
1919
+ }
1920
+ const remaining = sql.substring(i);
1921
+ const m = dollarQuoteOpenRegex.exec(remaining);
1922
+ if (!m) {
1923
+ return null;
1924
+ }
1925
+ const tag = m[0];
1926
+ const bodyStart = i + tag.length;
1927
+ const closeIdx = sql.indexOf(tag, bodyStart);
1928
+ const end = closeIdx !== -1 ? closeIdx + tag.length : sql.length;
1929
+ return { type: TokenType.QuotedBlock, end };
1930
+ }
1931
+ function scanBacktickQuotedIdentifier(sql, i) {
1932
+ if (sql[i] !== "`") {
1933
+ return null;
1934
+ }
1935
+ let j = i + 1;
1936
+ while (j < sql.length) {
1937
+ if (sql[j] === "`" && sql[j + 1] === "`") {
1938
+ j += 2;
1939
+ } else if (sql[j] === "`") {
1940
+ j++;
1941
+ break;
1942
+ } else {
1943
+ j++;
1944
+ }
1945
+ }
1946
+ return { type: TokenType.QuotedBlock, end: j };
1947
+ }
1948
+ function scanBracketQuotedIdentifier(sql, i) {
1949
+ if (sql[i] !== "[") {
1950
+ return null;
1951
+ }
1952
+ let j = i + 1;
1953
+ while (j < sql.length) {
1954
+ if (sql[j] === "]" && sql[j + 1] === "]") {
1955
+ j += 2;
1956
+ } else if (sql[j] === "]") {
1957
+ j++;
1958
+ break;
1959
+ } else {
1960
+ j++;
1961
+ }
1962
+ }
1963
+ return { type: TokenType.QuotedBlock, end: j };
1964
+ }
1965
+ function scanTokenAnsi(sql, i) {
1966
+ return scanSingleLineComment(sql, i) ?? scanMultiLineComment(sql, i) ?? scanSingleQuotedString(sql, i) ?? scanDoubleQuotedString(sql, i) ?? plainToken(i);
1967
+ }
1968
+ function scanTokenPostgres(sql, i) {
1969
+ return scanSingleLineComment(sql, i) ?? scanNestedMultiLineComment(sql, i) ?? scanSingleQuotedString(sql, i) ?? scanDoubleQuotedString(sql, i) ?? scanDollarQuotedBlock(sql, i) ?? plainToken(i);
1970
+ }
1971
+ function scanTokenMySQL(sql, i) {
1972
+ return scanSingleLineComment(sql, i) ?? scanMultiLineComment(sql, i) ?? scanSingleQuotedString(sql, i) ?? scanDoubleQuotedString(sql, i) ?? scanBacktickQuotedIdentifier(sql, i) ?? plainToken(i);
1973
+ }
1974
+ function scanTokenSQLite(sql, i) {
1975
+ return scanSingleLineComment(sql, i) ?? scanMultiLineComment(sql, i) ?? scanSingleQuotedString(sql, i) ?? scanDoubleQuotedString(sql, i) ?? scanBacktickQuotedIdentifier(sql, i) ?? scanBracketQuotedIdentifier(sql, i) ?? plainToken(i);
1976
+ }
1977
+ function scanTokenSQLServer(sql, i) {
1978
+ return scanSingleLineComment(sql, i) ?? scanMultiLineComment(sql, i) ?? scanSingleQuotedString(sql, i) ?? scanDoubleQuotedString(sql, i) ?? scanBracketQuotedIdentifier(sql, i) ?? plainToken(i);
1979
+ }
1980
+ var dialectScanners = {
1981
+ postgres: scanTokenPostgres,
1982
+ mysql: scanTokenMySQL,
1983
+ mariadb: scanTokenMySQL,
1984
+ sqlite: scanTokenSQLite,
1985
+ sqlserver: scanTokenSQLServer
1986
+ };
1987
+ function getScanner(dialect) {
1988
+ return dialect ? dialectScanners[dialect] ?? scanTokenAnsi : scanTokenAnsi;
1989
+ }
1990
+ function stripCommentsAndStrings(sql, dialect) {
1991
+ const scanToken = getScanner(dialect);
1992
+ const parts = [];
1993
+ let plainStart = -1;
1777
1994
  let i = 0;
1778
1995
  while (i < sql.length) {
1779
- if (sql[i] === "-" && sql[i + 1] === "-") {
1780
- while (i < sql.length && sql[i] !== "\n") {
1781
- i++;
1996
+ const token = scanToken(sql, i);
1997
+ if (token.type === TokenType.Plain) {
1998
+ if (plainStart === -1) {
1999
+ plainStart = i;
1782
2000
  }
1783
- result += " ";
1784
- continue;
1785
- }
1786
- if (sql[i] === "/" && sql[i + 1] === "*") {
1787
- i += 2;
1788
- while (i < sql.length && !(sql[i] === "*" && sql[i + 1] === "/")) {
1789
- i++;
2001
+ } else {
2002
+ if (plainStart !== -1) {
2003
+ parts.push(sql.substring(plainStart, i));
2004
+ plainStart = -1;
1790
2005
  }
1791
- i += 2;
1792
- result += " ";
1793
- continue;
2006
+ parts.push(" ");
1794
2007
  }
1795
- if (sql[i] === "'") {
1796
- i++;
1797
- while (i < sql.length) {
1798
- if (sql[i] === "'" && sql[i + 1] === "'") {
1799
- i += 2;
1800
- } else if (sql[i] === "'") {
1801
- i++;
1802
- break;
1803
- } else {
1804
- i++;
1805
- }
2008
+ i = token.end;
2009
+ }
2010
+ if (plainStart !== -1) {
2011
+ parts.push(sql.substring(plainStart));
2012
+ }
2013
+ return parts.join("");
2014
+ }
2015
+ function splitSQLStatements(sql, dialect) {
2016
+ const scanToken = getScanner(dialect);
2017
+ const statements = [];
2018
+ let stmtStart = 0;
2019
+ let i = 0;
2020
+ while (i < sql.length) {
2021
+ if (sql[i] === ";") {
2022
+ const trimmed2 = sql.substring(stmtStart, i).trim();
2023
+ if (trimmed2.length > 0) {
2024
+ statements.push(trimmed2);
1806
2025
  }
1807
- result += " ";
1808
- continue;
1809
- }
1810
- if (sql[i] === '"') {
2026
+ stmtStart = i + 1;
1811
2027
  i++;
1812
- while (i < sql.length) {
1813
- if (sql[i] === '"' && sql[i + 1] === '"') {
1814
- i += 2;
1815
- } else if (sql[i] === '"') {
1816
- i++;
1817
- break;
1818
- } else {
1819
- i++;
1820
- }
1821
- }
1822
- result += " ";
1823
2028
  continue;
1824
2029
  }
1825
- result += sql[i];
1826
- i++;
2030
+ const token = scanToken(sql, i);
2031
+ i = token.end;
2032
+ }
2033
+ const trimmed = sql.substring(stmtStart).trim();
2034
+ if (trimmed.length > 0) {
2035
+ statements.push(trimmed);
1827
2036
  }
1828
- return result;
2037
+ return statements;
1829
2038
  }
1830
2039
 
1831
2040
  // src/utils/parameter-mapper.ts
@@ -2172,6 +2381,7 @@ export {
2172
2381
  getDatabaseTypeFromDSN,
2173
2382
  getDefaultPortForType,
2174
2383
  stripCommentsAndStrings,
2384
+ splitSQLStatements,
2175
2385
  isDemoMode,
2176
2386
  resolveTransport,
2177
2387
  resolvePort,
package/dist/index.js CHANGED
@@ -17,8 +17,9 @@ import {
17
17
  resolvePort,
18
18
  resolveSourceConfigs,
19
19
  resolveTransport,
20
+ splitSQLStatements,
20
21
  stripCommentsAndStrings
21
- } from "./chunk-PQOTKZVK.js";
22
+ } from "./chunk-KAW6SKAC.js";
22
23
 
23
24
  // src/connectors/postgres/index.ts
24
25
  import pg from "pg";
@@ -143,6 +144,36 @@ var SQLRowLimiter = class {
143
144
  }
144
145
  };
145
146
 
147
+ // src/utils/identifier-quoter.ts
148
+ function quoteIdentifier(identifier, dbType) {
149
+ if (/[\0\x08\x09\x1a\n\r]/.test(identifier)) {
150
+ throw new Error(`Invalid identifier: contains control characters: ${identifier}`);
151
+ }
152
+ if (!identifier) {
153
+ throw new Error("Identifier cannot be empty");
154
+ }
155
+ switch (dbType) {
156
+ case "postgres":
157
+ case "sqlite":
158
+ return `"${identifier.replace(/"/g, '""')}"`;
159
+ case "mysql":
160
+ case "mariadb":
161
+ return `\`${identifier.replace(/`/g, "``")}\``;
162
+ case "sqlserver":
163
+ return `[${identifier.replace(/]/g, "]]")}]`;
164
+ default:
165
+ return `"${identifier.replace(/"/g, '""')}"`;
166
+ }
167
+ }
168
+ function quoteQualifiedIdentifier(tableName, schemaName, dbType) {
169
+ const quotedTable = quoteIdentifier(tableName, dbType);
170
+ if (schemaName) {
171
+ const quotedSchema = quoteIdentifier(schemaName, dbType);
172
+ return `${quotedSchema}.${quotedTable}`;
173
+ }
174
+ return quotedTable;
175
+ }
176
+
146
177
  // src/connectors/postgres/index.ts
147
178
  var { Pool } = pg;
148
179
  var PostgresDSNParser = class {
@@ -211,6 +242,8 @@ var PostgresConnector = class _PostgresConnector {
211
242
  this.pool = null;
212
243
  // Source ID is set by ConnectorManager after cloning
213
244
  this.sourceId = "default";
245
+ // Default schema for discovery methods (first entry from search_path, or "public")
246
+ this.defaultSchema = "public";
214
247
  }
215
248
  getId() {
216
249
  return this.sourceId;
@@ -219,11 +252,21 @@ var PostgresConnector = class _PostgresConnector {
219
252
  return new _PostgresConnector();
220
253
  }
221
254
  async connect(dsn, initScript, config) {
255
+ this.defaultSchema = "public";
222
256
  try {
223
257
  const poolConfig = await this.dsnParser.parse(dsn, config);
224
258
  if (config?.readonly) {
225
259
  poolConfig.options = (poolConfig.options || "") + " -c default_transaction_read_only=on";
226
260
  }
261
+ if (config?.searchPath) {
262
+ const schemas = config.searchPath.split(",").map((s) => s.trim()).filter((s) => s.length > 0);
263
+ if (schemas.length > 0) {
264
+ this.defaultSchema = schemas[0];
265
+ const quotedSchemas = schemas.map((s) => quoteIdentifier(s, "postgres"));
266
+ const optionsValue = quotedSchemas.join(",").replace(/\\/g, "\\\\").replace(/ /g, "\\ ");
267
+ poolConfig.options = (poolConfig.options || "") + ` -c search_path=${optionsValue}`;
268
+ }
269
+ }
227
270
  this.pool = new Pool(poolConfig);
228
271
  const client = await this.pool.connect();
229
272
  client.release();
@@ -261,7 +304,7 @@ var PostgresConnector = class _PostgresConnector {
261
304
  }
262
305
  const client = await this.pool.connect();
263
306
  try {
264
- const schemaToUse = schema || "public";
307
+ const schemaToUse = schema || this.defaultSchema;
265
308
  const result = await client.query(
266
309
  `
267
310
  SELECT table_name
@@ -282,7 +325,7 @@ var PostgresConnector = class _PostgresConnector {
282
325
  }
283
326
  const client = await this.pool.connect();
284
327
  try {
285
- const schemaToUse = schema || "public";
328
+ const schemaToUse = schema || this.defaultSchema;
286
329
  const result = await client.query(
287
330
  `
288
331
  SELECT EXISTS (
@@ -304,7 +347,7 @@ var PostgresConnector = class _PostgresConnector {
304
347
  }
305
348
  const client = await this.pool.connect();
306
349
  try {
307
- const schemaToUse = schema || "public";
350
+ const schemaToUse = schema || this.defaultSchema;
308
351
  const result = await client.query(
309
352
  `
310
353
  SELECT
@@ -352,18 +395,27 @@ var PostgresConnector = class _PostgresConnector {
352
395
  }
353
396
  const client = await this.pool.connect();
354
397
  try {
355
- const schemaToUse = schema || "public";
398
+ const schemaToUse = schema || this.defaultSchema;
356
399
  const result = await client.query(
357
400
  `
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
401
+ SELECT
402
+ c.column_name,
403
+ c.data_type,
404
+ c.is_nullable,
405
+ c.column_default,
406
+ pgd.description
407
+ FROM information_schema.columns c
408
+ LEFT JOIN pg_catalog.pg_namespace nsp
409
+ ON nsp.nspname = c.table_schema
410
+ LEFT JOIN pg_catalog.pg_class cls
411
+ ON cls.relnamespace = nsp.oid
412
+ AND cls.relname = c.table_name
413
+ LEFT JOIN pg_catalog.pg_description pgd
414
+ ON pgd.objoid = cls.oid
415
+ AND pgd.objsubid = c.ordinal_position
416
+ WHERE c.table_schema = $1
417
+ AND c.table_name = $2
418
+ ORDER BY c.ordinal_position
367
419
  `,
368
420
  [schemaToUse, tableName]
369
421
  );
@@ -372,22 +424,82 @@ var PostgresConnector = class _PostgresConnector {
372
424
  client.release();
373
425
  }
374
426
  }
375
- async getStoredProcedures(schema) {
427
+ async getTableRowCount(tableName, schema) {
376
428
  if (!this.pool) {
377
429
  throw new Error("Not connected to database");
378
430
  }
379
431
  const client = await this.pool.connect();
380
432
  try {
381
- const schemaToUse = schema || "public";
433
+ const schemaToUse = schema || this.defaultSchema;
382
434
  const result = await client.query(
383
435
  `
384
- SELECT
436
+ SELECT c.reltuples::bigint as count
437
+ FROM pg_class c
438
+ JOIN pg_namespace n ON n.oid = c.relnamespace
439
+ WHERE c.relname = $1
440
+ AND n.nspname = $2
441
+ AND c.relkind IN ('r','p','m','f')
442
+ `,
443
+ [tableName, schemaToUse]
444
+ );
445
+ if (result.rows.length > 0) {
446
+ const count = Number(result.rows[0].count);
447
+ return count >= 0 ? count : null;
448
+ }
449
+ return null;
450
+ } finally {
451
+ client.release();
452
+ }
453
+ }
454
+ async getTableComment(tableName, schema) {
455
+ if (!this.pool) {
456
+ throw new Error("Not connected to database");
457
+ }
458
+ const client = await this.pool.connect();
459
+ try {
460
+ const schemaToUse = schema || this.defaultSchema;
461
+ const result = await client.query(
462
+ `
463
+ SELECT obj_description(c.oid) as table_comment
464
+ FROM pg_catalog.pg_class c
465
+ JOIN pg_catalog.pg_namespace n ON n.oid = c.relnamespace
466
+ WHERE c.relname = $1
467
+ AND n.nspname = $2
468
+ AND c.relkind IN ('r','p','m','f')
469
+ `,
470
+ [tableName, schemaToUse]
471
+ );
472
+ if (result.rows.length > 0) {
473
+ return result.rows[0].table_comment || null;
474
+ }
475
+ return null;
476
+ } finally {
477
+ client.release();
478
+ }
479
+ }
480
+ async getStoredProcedures(schema, routineType) {
481
+ if (!this.pool) {
482
+ throw new Error("Not connected to database");
483
+ }
484
+ const client = await this.pool.connect();
485
+ try {
486
+ const schemaToUse = schema || this.defaultSchema;
487
+ const params = [schemaToUse];
488
+ let typeFilter = "";
489
+ if (routineType === "function") {
490
+ typeFilter = " AND routine_type = 'FUNCTION'";
491
+ } else if (routineType === "procedure") {
492
+ typeFilter = " AND routine_type = 'PROCEDURE'";
493
+ }
494
+ const result = await client.query(
495
+ `
496
+ SELECT
385
497
  routine_name
386
498
  FROM information_schema.routines
387
- WHERE routine_schema = $1
499
+ WHERE routine_schema = $1${typeFilter}
388
500
  ORDER BY routine_name
389
501
  `,
390
- [schemaToUse]
502
+ params
391
503
  );
392
504
  return result.rows.map((row) => row.routine_name);
393
505
  } finally {
@@ -400,7 +512,7 @@ var PostgresConnector = class _PostgresConnector {
400
512
  }
401
513
  const client = await this.pool.connect();
402
514
  try {
403
- const schemaToUse = schema || "public";
515
+ const schemaToUse = schema || this.defaultSchema;
404
516
  const result = await client.query(
405
517
  `
406
518
  SELECT
@@ -476,7 +588,7 @@ var PostgresConnector = class _PostgresConnector {
476
588
  }
477
589
  const client = await this.pool.connect();
478
590
  try {
479
- const statements = sql2.split(";").map((statement) => statement.trim()).filter((statement) => statement.length > 0);
591
+ const statements = splitSQLStatements(sql2, "postgres");
480
592
  if (statements.length === 1) {
481
593
  const processedStatement = SQLRowLimiter.applyMaxRows(statements[0], options.maxRows);
482
594
  let result;
@@ -791,33 +903,78 @@ var SQLServerConnector = class _SQLServerConnector {
791
903
  const schemaToUse = schema || "dbo";
792
904
  const request = this.connection.request().input("tableName", sql.VarChar, tableName).input("schema", sql.VarChar, schemaToUse);
793
905
  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
906
+ SELECT c.COLUMN_NAME as column_name,
907
+ c.DATA_TYPE as data_type,
908
+ c.IS_NULLABLE as is_nullable,
909
+ c.COLUMN_DEFAULT as column_default,
910
+ ep.value as description
911
+ FROM INFORMATION_SCHEMA.COLUMNS c
912
+ LEFT JOIN sys.columns sc
913
+ ON sc.name = c.COLUMN_NAME
914
+ AND sc.object_id = OBJECT_ID(QUOTENAME(c.TABLE_SCHEMA) + '.' + QUOTENAME(c.TABLE_NAME))
915
+ LEFT JOIN sys.extended_properties ep
916
+ ON ep.major_id = sc.object_id
917
+ AND ep.minor_id = sc.column_id
918
+ AND ep.name = 'MS_Description'
919
+ WHERE c.TABLE_NAME = @tableName
920
+ AND c.TABLE_SCHEMA = @schema
921
+ ORDER BY c.ORDINAL_POSITION
802
922
  `;
803
923
  const result = await request.query(query);
804
- return result.recordset;
924
+ return result.recordset.map((row) => ({
925
+ ...row,
926
+ description: row.description || null
927
+ }));
805
928
  } catch (error) {
806
929
  throw new Error(`Failed to get schema for table ${tableName}: ${error.message}`);
807
930
  }
808
931
  }
809
- async getStoredProcedures(schema) {
932
+ async getTableComment(tableName, schema) {
933
+ if (!this.connection) {
934
+ throw new Error("Not connected to SQL Server database");
935
+ }
936
+ try {
937
+ const schemaToUse = schema || "dbo";
938
+ const request = this.connection.request().input("tableName", sql.VarChar, tableName).input("schema", sql.VarChar, schemaToUse);
939
+ const query = `
940
+ SELECT ep.value as table_comment
941
+ FROM sys.extended_properties ep
942
+ JOIN sys.tables t ON ep.major_id = t.object_id
943
+ JOIN sys.schemas s ON t.schema_id = s.schema_id
944
+ WHERE ep.minor_id = 0
945
+ AND ep.name = 'MS_Description'
946
+ AND t.name = @tableName
947
+ AND s.name = @schema
948
+ `;
949
+ const result = await request.query(query);
950
+ if (result.recordset.length > 0) {
951
+ return result.recordset[0].table_comment || null;
952
+ }
953
+ return null;
954
+ } catch (error) {
955
+ return null;
956
+ }
957
+ }
958
+ async getStoredProcedures(schema, routineType) {
810
959
  if (!this.connection) {
811
960
  throw new Error("Not connected to SQL Server database");
812
961
  }
813
962
  try {
814
963
  const schemaToUse = schema || "dbo";
815
964
  const request = this.connection.request().input("schema", sql.VarChar, schemaToUse);
965
+ let typeFilter;
966
+ if (routineType === "function") {
967
+ typeFilter = "AND ROUTINE_TYPE = 'FUNCTION'";
968
+ } else if (routineType === "procedure") {
969
+ typeFilter = "AND ROUTINE_TYPE = 'PROCEDURE'";
970
+ } else {
971
+ typeFilter = "AND (ROUTINE_TYPE = 'PROCEDURE' OR ROUTINE_TYPE = 'FUNCTION')";
972
+ }
816
973
  const query = `
817
974
  SELECT ROUTINE_NAME
818
975
  FROM INFORMATION_SCHEMA.ROUTINES
819
976
  WHERE ROUTINE_SCHEMA = @schema
820
- AND (ROUTINE_TYPE = 'PROCEDURE' OR ROUTINE_TYPE = 'FUNCTION')
977
+ ${typeFilter}
821
978
  ORDER BY ROUTINE_NAME
822
979
  `;
823
980
  const result = await request.query(query);
@@ -950,38 +1107,6 @@ ConnectorRegistry.register(sqlServerConnector);
950
1107
 
951
1108
  // src/connectors/sqlite/index.ts
952
1109
  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
1110
  var SQLiteDSNParser = class {
986
1111
  async parse(dsn, config) {
987
1112
  if (!this.isValidDSN(dsn)) {
@@ -1183,14 +1308,15 @@ var SQLiteConnector = class _SQLiteConnector {
1183
1308
  data_type: row.type,
1184
1309
  // In SQLite, primary key columns are automatically NOT NULL even if notnull=0
1185
1310
  is_nullable: row.notnull === 1 || row.pk > 0 ? "NO" : "YES",
1186
- column_default: row.dflt_value
1311
+ column_default: row.dflt_value,
1312
+ description: null
1187
1313
  }));
1188
1314
  return columns;
1189
1315
  } catch (error) {
1190
1316
  throw error;
1191
1317
  }
1192
1318
  }
1193
- async getStoredProcedures(schema) {
1319
+ async getStoredProcedures(schema, routineType) {
1194
1320
  if (!this.db) {
1195
1321
  throw new Error("Not connected to SQLite database");
1196
1322
  }
@@ -1209,7 +1335,7 @@ var SQLiteConnector = class _SQLiteConnector {
1209
1335
  throw new Error("Not connected to SQLite database");
1210
1336
  }
1211
1337
  try {
1212
- const statements = sql2.split(";").map((statement) => statement.trim()).filter((statement) => statement.length > 0);
1338
+ const statements = splitSQLStatements(sql2, "sqlite");
1213
1339
  if (statements.length === 1) {
1214
1340
  let processedStatement = statements[0];
1215
1341
  const trimmedStatement = statements[0].toLowerCase().trim();
@@ -1568,11 +1694,12 @@ var MySQLConnector = class _MySQLConnector {
1568
1694
  const queryParams = schema ? [schema, tableName] : [tableName];
1569
1695
  const [rows] = await this.pool.query(
1570
1696
  `
1571
- SELECT
1572
- COLUMN_NAME as column_name,
1573
- DATA_TYPE as data_type,
1697
+ SELECT
1698
+ COLUMN_NAME as column_name,
1699
+ DATA_TYPE as data_type,
1574
1700
  IS_NULLABLE as is_nullable,
1575
- COLUMN_DEFAULT as column_default
1701
+ COLUMN_DEFAULT as column_default,
1702
+ COLUMN_COMMENT as description
1576
1703
  FROM INFORMATION_SCHEMA.COLUMNS
1577
1704
  ${schemaClause}
1578
1705
  AND TABLE_NAME = ?
@@ -1580,24 +1707,57 @@ var MySQLConnector = class _MySQLConnector {
1580
1707
  `,
1581
1708
  queryParams
1582
1709
  );
1583
- return rows;
1710
+ return rows.map((row) => ({
1711
+ ...row,
1712
+ description: row.description || null
1713
+ }));
1584
1714
  } catch (error) {
1585
1715
  console.error("Error getting table schema:", error);
1586
1716
  throw error;
1587
1717
  }
1588
1718
  }
1589
- async getStoredProcedures(schema) {
1719
+ async getTableComment(tableName, schema) {
1720
+ if (!this.pool) {
1721
+ throw new Error("Not connected to database");
1722
+ }
1723
+ try {
1724
+ const schemaClause = schema ? "WHERE TABLE_SCHEMA = ?" : "WHERE TABLE_SCHEMA = DATABASE()";
1725
+ const queryParams = schema ? [schema, tableName] : [tableName];
1726
+ const [rows] = await this.pool.query(
1727
+ `
1728
+ SELECT TABLE_COMMENT
1729
+ FROM INFORMATION_SCHEMA.TABLES
1730
+ ${schemaClause}
1731
+ AND TABLE_NAME = ?
1732
+ `,
1733
+ queryParams
1734
+ );
1735
+ if (rows.length > 0) {
1736
+ return rows[0].TABLE_COMMENT || null;
1737
+ }
1738
+ return null;
1739
+ } catch (error) {
1740
+ return null;
1741
+ }
1742
+ }
1743
+ async getStoredProcedures(schema, routineType) {
1590
1744
  if (!this.pool) {
1591
1745
  throw new Error("Not connected to database");
1592
1746
  }
1593
1747
  try {
1594
1748
  const schemaClause = schema ? "WHERE ROUTINE_SCHEMA = ?" : "WHERE ROUTINE_SCHEMA = DATABASE()";
1595
1749
  const queryParams = schema ? [schema] : [];
1750
+ let typeFilter = "";
1751
+ if (routineType === "function") {
1752
+ typeFilter = " AND ROUTINE_TYPE = 'FUNCTION'";
1753
+ } else if (routineType === "procedure") {
1754
+ typeFilter = " AND ROUTINE_TYPE = 'PROCEDURE'";
1755
+ }
1596
1756
  const [rows] = await this.pool.query(
1597
1757
  `
1598
1758
  SELECT ROUTINE_NAME
1599
1759
  FROM INFORMATION_SCHEMA.ROUTINES
1600
- ${schemaClause}
1760
+ ${schemaClause}${typeFilter}
1601
1761
  ORDER BY ROUTINE_NAME
1602
1762
  `,
1603
1763
  queryParams
@@ -1721,7 +1881,7 @@ var MySQLConnector = class _MySQLConnector {
1721
1881
  try {
1722
1882
  let processedSQL = sql2;
1723
1883
  if (options.maxRows) {
1724
- const statements = sql2.split(";").map((statement) => statement.trim()).filter((statement) => statement.length > 0);
1884
+ const statements = splitSQLStatements(sql2, "mysql");
1725
1885
  const processedStatements = statements.map(
1726
1886
  (statement) => SQLRowLimiter.applyMaxRows(statement, options.maxRows)
1727
1887
  );
@@ -1759,7 +1919,7 @@ var mysqlConnector = new MySQLConnector();
1759
1919
  ConnectorRegistry.register(mysqlConnector);
1760
1920
 
1761
1921
  // src/connectors/mariadb/index.ts
1762
- import mariadb from "mariadb";
1922
+ import { createPool } from "mariadb";
1763
1923
  var MariadbDSNParser = class {
1764
1924
  async parse(dsn, config) {
1765
1925
  const connectionTimeoutSeconds = config?.connectionTimeoutSeconds;
@@ -1843,7 +2003,7 @@ var MariaDBConnector = class _MariaDBConnector {
1843
2003
  async connect(dsn, initScript, config) {
1844
2004
  try {
1845
2005
  const connectionConfig = await this.dsnParser.parse(dsn, config);
1846
- this.pool = mariadb.createPool(connectionConfig);
2006
+ this.pool = createPool(connectionConfig);
1847
2007
  await this.pool.query("SELECT 1");
1848
2008
  } catch (err) {
1849
2009
  console.error("Failed to connect to MariaDB database:", err);
@@ -1981,11 +2141,12 @@ var MariaDBConnector = class _MariaDBConnector {
1981
2141
  const queryParams = schema ? [schema, tableName] : [tableName];
1982
2142
  const rows = await this.pool.query(
1983
2143
  `
1984
- SELECT
1985
- COLUMN_NAME as column_name,
1986
- DATA_TYPE as data_type,
2144
+ SELECT
2145
+ COLUMN_NAME as column_name,
2146
+ DATA_TYPE as data_type,
1987
2147
  IS_NULLABLE as is_nullable,
1988
- COLUMN_DEFAULT as column_default
2148
+ COLUMN_DEFAULT as column_default,
2149
+ COLUMN_COMMENT as description
1989
2150
  FROM INFORMATION_SCHEMA.COLUMNS
1990
2151
  ${schemaClause}
1991
2152
  AND TABLE_NAME = ?
@@ -1993,24 +2154,57 @@ var MariaDBConnector = class _MariaDBConnector {
1993
2154
  `,
1994
2155
  queryParams
1995
2156
  );
1996
- return rows;
2157
+ return rows.map((row) => ({
2158
+ ...row,
2159
+ description: row.description || null
2160
+ }));
1997
2161
  } catch (error) {
1998
2162
  console.error("Error getting table schema:", error);
1999
2163
  throw error;
2000
2164
  }
2001
2165
  }
2002
- async getStoredProcedures(schema) {
2166
+ async getTableComment(tableName, schema) {
2167
+ if (!this.pool) {
2168
+ throw new Error("Not connected to database");
2169
+ }
2170
+ try {
2171
+ const schemaClause = schema ? "WHERE TABLE_SCHEMA = ?" : "WHERE TABLE_SCHEMA = DATABASE()";
2172
+ const queryParams = schema ? [schema, tableName] : [tableName];
2173
+ const rows = await this.pool.query(
2174
+ `
2175
+ SELECT TABLE_COMMENT
2176
+ FROM INFORMATION_SCHEMA.TABLES
2177
+ ${schemaClause}
2178
+ AND TABLE_NAME = ?
2179
+ `,
2180
+ queryParams
2181
+ );
2182
+ if (rows.length > 0) {
2183
+ return rows[0].TABLE_COMMENT || null;
2184
+ }
2185
+ return null;
2186
+ } catch (error) {
2187
+ return null;
2188
+ }
2189
+ }
2190
+ async getStoredProcedures(schema, routineType) {
2003
2191
  if (!this.pool) {
2004
2192
  throw new Error("Not connected to database");
2005
2193
  }
2006
2194
  try {
2007
2195
  const schemaClause = schema ? "WHERE ROUTINE_SCHEMA = ?" : "WHERE ROUTINE_SCHEMA = DATABASE()";
2008
2196
  const queryParams = schema ? [schema] : [];
2197
+ let typeFilter = "";
2198
+ if (routineType === "function") {
2199
+ typeFilter = " AND ROUTINE_TYPE = 'FUNCTION'";
2200
+ } else if (routineType === "procedure") {
2201
+ typeFilter = " AND ROUTINE_TYPE = 'PROCEDURE'";
2202
+ }
2009
2203
  const rows = await this.pool.query(
2010
2204
  `
2011
2205
  SELECT ROUTINE_NAME
2012
2206
  FROM INFORMATION_SCHEMA.ROUTINES
2013
- ${schemaClause}
2207
+ ${schemaClause}${typeFilter}
2014
2208
  ORDER BY ROUTINE_NAME
2015
2209
  `,
2016
2210
  queryParams
@@ -2134,7 +2328,7 @@ var MariaDBConnector = class _MariaDBConnector {
2134
2328
  try {
2135
2329
  let processedSQL = sql2;
2136
2330
  if (options.maxRows) {
2137
- const statements = sql2.split(";").map((statement) => statement.trim()).filter((statement) => statement.length > 0);
2331
+ const statements = splitSQLStatements(sql2, "mariadb");
2138
2332
  const processedStatements = statements.map(
2139
2333
  (statement) => SQLRowLimiter.applyMaxRows(statement, options.maxRows)
2140
2334
  );
@@ -2834,7 +3028,7 @@ var allowedKeywords = {
2834
3028
  sqlserver: ["select", "with", "explain", "showplan"]
2835
3029
  };
2836
3030
  function isReadOnlySQL(sql2, connectorType) {
2837
- const cleanedSQL = stripCommentsAndStrings(sql2).trim().toLowerCase();
3031
+ const cleanedSQL = stripCommentsAndStrings(sql2, connectorType).trim().toLowerCase();
2838
3032
  if (!cleanedSQL) {
2839
3033
  return true;
2840
3034
  }
@@ -2927,11 +3121,8 @@ function trackToolRequest(metadata, startTime, extra, success, error) {
2927
3121
  var executeSqlSchema = {
2928
3122
  sql: z.string().describe("SQL to execute (multiple statements separated by ;)")
2929
3123
  };
2930
- function splitSQLStatements(sql2) {
2931
- return sql2.split(";").map((statement) => statement.trim()).filter((statement) => statement.length > 0);
2932
- }
2933
3124
  function areAllStatementsReadOnly(sql2, connectorType) {
2934
- const statements = splitSQLStatements(sql2);
3125
+ const statements = splitSQLStatements(sql2, connectorType);
2935
3126
  return statements.every((statement) => isReadOnlySQL(statement, connectorType));
2936
3127
  }
2937
3128
  function createExecuteSqlToolHandler(sourceId) {
@@ -2988,7 +3179,7 @@ function createExecuteSqlToolHandler(sourceId) {
2988
3179
  // src/tools/search-objects.ts
2989
3180
  import { z as z2 } from "zod";
2990
3181
  var searchDatabaseObjectsSchema = {
2991
- object_type: z2.enum(["schema", "table", "column", "procedure", "index"]).describe("Object type to search"),
3182
+ object_type: z2.enum(["schema", "table", "column", "procedure", "function", "index"]).describe("Object type to search"),
2992
3183
  pattern: z2.string().optional().default("%").describe("LIKE pattern (% = any chars, _ = one char). Default: %"),
2993
3184
  schema: z2.string().optional().describe("Filter to schema"),
2994
3185
  table: z2.string().optional().describe("Filter to table (requires schema; column/index only)"),
@@ -3001,6 +3192,9 @@ function likePatternToRegex(pattern) {
3001
3192
  }
3002
3193
  async function getTableRowCount(connector, tableName, schemaName) {
3003
3194
  try {
3195
+ if (connector.getTableRowCount) {
3196
+ return await connector.getTableRowCount(tableName, schemaName);
3197
+ }
3004
3198
  const qualifiedTable = quoteQualifiedIdentifier(tableName, schemaName, connector.id);
3005
3199
  const countQuery = `SELECT COUNT(*) as count FROM ${qualifiedTable}`;
3006
3200
  const result = await connector.executeSQL(countQuery, { maxRows: 1 });
@@ -3012,6 +3206,16 @@ async function getTableRowCount(connector, tableName, schemaName) {
3012
3206
  }
3013
3207
  return null;
3014
3208
  }
3209
+ async function getTableComment(connector, tableName, schemaName) {
3210
+ try {
3211
+ if (connector.getTableComment) {
3212
+ return await connector.getTableComment(tableName, schemaName);
3213
+ }
3214
+ return null;
3215
+ } catch (error) {
3216
+ return null;
3217
+ }
3218
+ }
3015
3219
  async function searchSchemas(connector, pattern, detailLevel, limit) {
3016
3220
  const schemas = await connector.getSchemas();
3017
3221
  const regex = likePatternToRegex(pattern);
@@ -3062,11 +3266,13 @@ async function searchTables(connector, pattern, schemaFilter, detailLevel, limit
3062
3266
  try {
3063
3267
  const columns = await connector.getTableSchema(tableName, schemaName);
3064
3268
  const rowCount = await getTableRowCount(connector, tableName, schemaName);
3269
+ const comment = await getTableComment(connector, tableName, schemaName);
3065
3270
  results.push({
3066
3271
  name: tableName,
3067
3272
  schema: schemaName,
3068
3273
  column_count: columns.length,
3069
- row_count: rowCount
3274
+ row_count: rowCount,
3275
+ ...comment ? { comment } : {}
3070
3276
  });
3071
3277
  } catch (error) {
3072
3278
  results.push({
@@ -3081,16 +3287,19 @@ async function searchTables(connector, pattern, schemaFilter, detailLevel, limit
3081
3287
  const columns = await connector.getTableSchema(tableName, schemaName);
3082
3288
  const indexes = await connector.getTableIndexes(tableName, schemaName);
3083
3289
  const rowCount = await getTableRowCount(connector, tableName, schemaName);
3290
+ const comment = await getTableComment(connector, tableName, schemaName);
3084
3291
  results.push({
3085
3292
  name: tableName,
3086
3293
  schema: schemaName,
3087
3294
  column_count: columns.length,
3088
3295
  row_count: rowCount,
3296
+ ...comment ? { comment } : {},
3089
3297
  columns: columns.map((col) => ({
3090
3298
  name: col.column_name,
3091
3299
  type: col.data_type,
3092
3300
  nullable: col.is_nullable === "YES",
3093
- default: col.column_default
3301
+ default: col.column_default,
3302
+ ...col.description ? { description: col.description } : {}
3094
3303
  })),
3095
3304
  indexes: indexes.map((idx) => ({
3096
3305
  name: idx.index_name,
@@ -3152,7 +3361,8 @@ async function searchColumns(connector, pattern, schemaFilter, tableFilter, deta
3152
3361
  schema: schemaName,
3153
3362
  type: column.data_type,
3154
3363
  nullable: column.is_nullable === "YES",
3155
- default: column.column_default
3364
+ default: column.column_default,
3365
+ ...column.description ? { description: column.description } : {}
3156
3366
  });
3157
3367
  }
3158
3368
  }
@@ -3166,7 +3376,7 @@ async function searchColumns(connector, pattern, schemaFilter, tableFilter, deta
3166
3376
  }
3167
3377
  return results;
3168
3378
  }
3169
- async function searchProcedures(connector, pattern, schemaFilter, detailLevel, limit) {
3379
+ async function searchProcedures(connector, pattern, schemaFilter, detailLevel, limit, routineType) {
3170
3380
  const regex = likePatternToRegex(pattern);
3171
3381
  const results = [];
3172
3382
  let schemasToSearch;
@@ -3178,7 +3388,7 @@ async function searchProcedures(connector, pattern, schemaFilter, detailLevel, l
3178
3388
  for (const schemaName of schemasToSearch) {
3179
3389
  if (results.length >= limit) break;
3180
3390
  try {
3181
- const procedures = await connector.getStoredProcedures(schemaName);
3391
+ const procedures = await connector.getStoredProcedures(schemaName, routineType);
3182
3392
  const matched = procedures.filter((proc) => regex.test(proc));
3183
3393
  for (const procName of matched) {
3184
3394
  if (results.length >= limit) break;
@@ -3315,7 +3525,10 @@ function createSearchDatabaseObjectsToolHandler(sourceId) {
3315
3525
  results = await searchColumns(connector, pattern, schema, table, detail_level, limit);
3316
3526
  break;
3317
3527
  case "procedure":
3318
- results = await searchProcedures(connector, pattern, schema, detail_level, limit);
3528
+ results = await searchProcedures(connector, pattern, schema, detail_level, limit, "procedure");
3529
+ break;
3530
+ case "function":
3531
+ results = await searchProcedures(connector, pattern, schema, detail_level, limit, "function");
3319
3532
  break;
3320
3533
  case "index":
3321
3534
  results = await searchIndexes(connector, pattern, schema, table, detail_level, limit);
@@ -3796,9 +4009,9 @@ function generateCode(request) {
3796
4009
 
3797
4010
  // src/tools/generate-code-handler.ts
3798
4011
  function createGenerateCodeToolHandler() {
3799
- return async (request) => {
4012
+ return async (args, extra) => {
3800
4013
  try {
3801
- const params = generateCodeSchema.parse(request.input);
4014
+ const params = generateCodeSchema.parse(args);
3802
4015
  const result = generateCode(params);
3803
4016
  return {
3804
4017
  content: [
@@ -3975,7 +4188,7 @@ function getSearchObjectsMetadata(sourceId) {
3975
4188
  const isSingleSource = sourceIds.length === 1;
3976
4189
  const toolName = isSingleSource ? "search_objects" : `search_objects_${normalizeSourceId(sourceId)}`;
3977
4190
  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`;
4191
+ const description = isSingleSource ? `Search and list database objects (schemas, tables, columns, procedures, functions, indexes) on the ${dbType} database` : `Search and list database objects (schemas, tables, columns, procedures, functions, indexes) on the '${sourceId}' ${dbType} database`;
3979
4192
  return {
3980
4193
  name: toolName,
3981
4194
  description,
@@ -4655,7 +4868,7 @@ See documentation for more details on configuring database connections.
4655
4868
  const sources = sourceConfigsData.sources;
4656
4869
  console.error(`Configuration source: ${sourceConfigsData.source}`);
4657
4870
  await connectorManager.connectWithSources(sources);
4658
- const { initializeToolRegistry } = await import("./registry-U5HSB2VB.js");
4871
+ const { initializeToolRegistry } = await import("./registry-TGSADHCH.js");
4659
4872
  initializeToolRegistry({
4660
4873
  sources: sourceConfigsData.sources,
4661
4874
  tools: sourceConfigsData.tools
@@ -4693,9 +4906,6 @@ See documentation for more details on configuring database connections.
4693
4906
  app.use(express.json());
4694
4907
  app.use((req, res, next) => {
4695
4908
  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
4909
  res.header("Access-Control-Allow-Origin", origin || "http://localhost");
4700
4910
  res.header("Access-Control-Allow-Methods", "GET, POST, OPTIONS");
4701
4911
  res.header("Access-Control-Allow-Headers", "Content-Type, Mcp-Session-Id");
@@ -2,7 +2,7 @@ import {
2
2
  ToolRegistry,
3
3
  getToolRegistry,
4
4
  initializeToolRegistry
5
- } from "./chunk-PQOTKZVK.js";
5
+ } from "./chunk-KAW6SKAC.js";
6
6
  export {
7
7
  ToolRegistry,
8
8
  getToolRegistry,
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@teckedd-code2save/datafy",
3
- "version": "0.17.0",
3
+ "version": "1.0.0",
4
4
  "mcpName": "io.github.teckedd-code2save/datafy",
5
5
  "description": "Minimal, token-efficient Database MCP Server for PostgreSQL, MySQL, SQL Server, SQLite, MariaDB, Redis, and Elasticsearch with code generation",
6
6
  "repository": {
@@ -17,21 +17,6 @@
17
17
  "LICENSE",
18
18
  "README.md"
19
19
  ],
20
- "scripts": {
21
- "build": "pnpm run generate:api-types && tsup && cd frontend && pnpm run build",
22
- "build:backend": "pnpm run generate:api-types && tsup",
23
- "build:frontend": "cd frontend && pnpm run build",
24
- "start": "node dist/index.js",
25
- "dev": "concurrently --kill-others \"pnpm run dev:backend\" \"pnpm run dev:frontend\"",
26
- "dev:backend": "NODE_ENV=development tsx src/index.ts --transport=http",
27
- "dev:frontend": "cd frontend && pnpm run dev",
28
- "crossdev": "cross-env NODE_ENV=development tsx src/index.ts",
29
- "generate:api-types": "openapi-typescript src/api/openapi.yaml -o src/api/openapi.d.ts",
30
- "test": "vitest run",
31
- "test:unit": "vitest run --project unit",
32
- "test:watch": "vitest",
33
- "test:integration": "vitest run --project integration"
34
- },
35
20
  "keywords": [],
36
21
  "author": "",
37
22
  "license": "MIT",
@@ -86,5 +71,19 @@
86
71
  "include": [
87
72
  "src/**/*"
88
73
  ],
89
- "packageManager": "pnpm@10.12.1+sha512.f0dda8580f0ee9481c5c79a1d927b9164f2c478e90992ad268bbb2465a736984391d6333d2c327913578b2804af33474ca554ba29c04a8b13060a717675ae3ac"
74
+ "scripts": {
75
+ "build": "pnpm run generate:api-types && tsup && cd frontend && pnpm run build",
76
+ "build:backend": "pnpm run generate:api-types && tsup",
77
+ "build:frontend": "cd frontend && pnpm run build",
78
+ "start": "node dist/index.js",
79
+ "dev": "concurrently --kill-others \"pnpm run dev:backend\" \"pnpm run dev:frontend\"",
80
+ "dev:backend": "NODE_ENV=development tsx src/index.ts --transport=http",
81
+ "dev:frontend": "cd frontend && pnpm run dev",
82
+ "crossdev": "cross-env NODE_ENV=development tsx src/index.ts",
83
+ "generate:api-types": "openapi-typescript src/api/openapi.yaml -o src/api/openapi.d.ts",
84
+ "test": "vitest run",
85
+ "test:unit": "vitest run --project unit",
86
+ "test:watch": "vitest",
87
+ "test:integration": "vitest run --project integration"
88
+ }
90
89
  }