infrawise 0.1.0 → 0.2.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.
@@ -0,0 +1,97 @@
1
+ "use strict";
2
+ Object.defineProperty(exports, "__esModule", { value: true });
3
+ exports.extractDynamoMetadata = extractDynamoMetadata;
4
+ exports.validateDynamoAccess = validateDynamoAccess;
5
+ const client_dynamodb_1 = require("@aws-sdk/client-dynamodb");
6
+ const credential_providers_1 = require("@aws-sdk/credential-providers");
7
+ const core_1 = require("../core");
8
+ function createDynamoClient(config) {
9
+ const region = config.aws?.region ?? 'us-east-1';
10
+ const profile = config.aws?.profile;
11
+ const clientConfig = { region };
12
+ if (profile) {
13
+ clientConfig.credentials = (0, credential_providers_1.fromIni)({ profile });
14
+ }
15
+ return new client_dynamodb_1.DynamoDBClient(clientConfig);
16
+ }
17
+ function parseTableDescription(desc) {
18
+ const tableName = desc.TableName ?? 'unknown';
19
+ const partitionKey = desc.KeySchema?.find((k) => k.KeyType === 'HASH')?.AttributeName;
20
+ const sortKey = desc.KeySchema?.find((k) => k.KeyType === 'RANGE')?.AttributeName;
21
+ const indexes = [];
22
+ // Global secondary indexes
23
+ if (desc.GlobalSecondaryIndexes) {
24
+ for (const gsi of desc.GlobalSecondaryIndexes) {
25
+ if (gsi.IndexName)
26
+ indexes.push(gsi.IndexName);
27
+ }
28
+ }
29
+ // Local secondary indexes
30
+ if (desc.LocalSecondaryIndexes) {
31
+ for (const lsi of desc.LocalSecondaryIndexes) {
32
+ if (lsi.IndexName)
33
+ indexes.push(lsi.IndexName);
34
+ }
35
+ }
36
+ return { tableName, partitionKey, sortKey, indexes };
37
+ }
38
+ async function listAllTables(client) {
39
+ const tableNames = [];
40
+ let lastEvaluatedTableName;
41
+ do {
42
+ const command = new client_dynamodb_1.ListTablesCommand({
43
+ ExclusiveStartTableName: lastEvaluatedTableName,
44
+ Limit: 100,
45
+ });
46
+ const response = await client.send(command);
47
+ if (response.TableNames) {
48
+ tableNames.push(...response.TableNames);
49
+ }
50
+ lastEvaluatedTableName = response.LastEvaluatedTableName;
51
+ } while (lastEvaluatedTableName);
52
+ return tableNames;
53
+ }
54
+ async function extractDynamoMetadata(config) {
55
+ const client = createDynamoClient(config);
56
+ const includeTables = config.dynamodb?.includeTables;
57
+ let tableNames;
58
+ try {
59
+ const allTables = await listAllTables(client);
60
+ if (includeTables && includeTables.length > 0) {
61
+ tableNames = allTables.filter((name) => includeTables.includes(name));
62
+ core_1.logger.debug(`Filtered to ${tableNames.length} tables from config`);
63
+ }
64
+ else {
65
+ tableNames = allTables;
66
+ }
67
+ core_1.logger.debug(`Found ${tableNames.length} DynamoDB table(s)`);
68
+ }
69
+ catch (err) {
70
+ throw new core_1.DynamoDBError(err instanceof Error ? err.message : 'Failed to list DynamoDB tables');
71
+ }
72
+ const results = [];
73
+ for (const tableName of tableNames) {
74
+ try {
75
+ const command = new client_dynamodb_1.DescribeTableCommand({ TableName: tableName });
76
+ const response = await client.send(command);
77
+ if (response.Table) {
78
+ results.push(parseTableDescription(response.Table));
79
+ core_1.logger.debug(`Described table: ${tableName}`);
80
+ }
81
+ }
82
+ catch (err) {
83
+ core_1.logger.warn(`Failed to describe table ${tableName}: ${err instanceof Error ? err.message : String(err)}`);
84
+ }
85
+ }
86
+ return results;
87
+ }
88
+ async function validateDynamoAccess(config) {
89
+ const client = createDynamoClient(config);
90
+ try {
91
+ await client.send(new client_dynamodb_1.ListTablesCommand({ Limit: 1 }));
92
+ return true;
93
+ }
94
+ catch {
95
+ return false;
96
+ }
97
+ }
@@ -0,0 +1,119 @@
1
+ "use strict";
2
+ Object.defineProperty(exports, "__esModule", { value: true });
3
+ exports.extractLogsSummary = extractLogsSummary;
4
+ exports.validateLogsAccess = validateLogsAccess;
5
+ const client_cloudwatch_logs_1 = require("@aws-sdk/client-cloudwatch-logs");
6
+ const credential_providers_1 = require("@aws-sdk/credential-providers");
7
+ const core_1 = require("../core");
8
+ // Hard caps to prevent context bloat
9
+ const MAX_LOG_GROUPS = 50;
10
+ const MAX_EVENTS_PER_GROUP = 50;
11
+ function clientConfig(cfg) {
12
+ const region = cfg.region ?? 'us-east-1';
13
+ return cfg.profile
14
+ ? { region, credentials: (0, credential_providers_1.fromIni)({ profile: cfg.profile }) }
15
+ : { region };
16
+ }
17
+ // Strip UUIDs, timestamps, numbers → group similar messages by pattern
18
+ function toPattern(message) {
19
+ return message
20
+ .slice(0, 200)
21
+ .replace(/[0-9a-f]{8}-[0-9a-f]{4}-[0-9a-f]{4}-[0-9a-f]{4}-[0-9a-f]{12}/gi, '<UUID>')
22
+ .replace(/\d{4}-\d{2}-\d{2}[T ]\d{2}:\d{2}:\d{2}(\.\d+)?Z?/g, '<TIMESTAMP>')
23
+ .replace(/\b\d{5,}\b/g, '<NUM>')
24
+ .replace(/\b\d{1,3}\.\d{1,3}\.\d{1,3}\.\d{1,3}\b/g, '<IP>')
25
+ .replace(/Bearer\s+\S+/gi, 'Bearer <TOKEN>')
26
+ .trim();
27
+ }
28
+ function topPatterns(messages, limit = 5) {
29
+ const counts = {};
30
+ for (const msg of messages) {
31
+ const p = toPattern(msg);
32
+ counts[p] = (counts[p] ?? 0) + 1;
33
+ }
34
+ return Object.entries(counts)
35
+ .sort((a, b) => b[1] - a[1])
36
+ .slice(0, limit)
37
+ .map(([pattern, count]) => ({ pattern, count }));
38
+ }
39
+ async function extractLogsSummary(cfg = {}) {
40
+ const client = new client_cloudwatch_logs_1.CloudWatchLogsClient(clientConfig(cfg));
41
+ const windowMs = (cfg.windowHours ?? 24) * 60 * 60 * 1000;
42
+ const startTime = Date.now() - windowMs;
43
+ const summaries = [];
44
+ // Discover log groups
45
+ const logGroups = [];
46
+ try {
47
+ const prefixes = cfg.logGroupPrefixes?.length ? cfg.logGroupPrefixes : [undefined];
48
+ for (const prefix of prefixes) {
49
+ let nextToken;
50
+ do {
51
+ const res = await client.send(new client_cloudwatch_logs_1.DescribeLogGroupsCommand({
52
+ nextToken,
53
+ limit: Math.min(50, MAX_LOG_GROUPS - logGroups.length),
54
+ ...(prefix ? { logGroupNamePrefix: prefix } : {}),
55
+ }));
56
+ for (const lg of res.logGroups ?? []) {
57
+ logGroups.push({ name: lg.logGroupName ?? '', retentionDays: lg.retentionInDays });
58
+ }
59
+ nextToken = res.nextToken;
60
+ if (logGroups.length >= MAX_LOG_GROUPS)
61
+ break;
62
+ } while (nextToken);
63
+ if (logGroups.length >= MAX_LOG_GROUPS)
64
+ break;
65
+ }
66
+ }
67
+ catch (err) {
68
+ core_1.logger.warn(`CloudWatch Logs discovery failed: ${err instanceof Error ? err.message : String(err)}`);
69
+ return summaries;
70
+ }
71
+ // Sample errors from each group — patterns only, never raw messages for unrelated groups
72
+ for (const lg of logGroups) {
73
+ const errorMessages = [];
74
+ const warnMessages = [];
75
+ let lastErrorTime;
76
+ for (const filterPattern of ['ERROR', 'Exception', 'WARN']) {
77
+ if (errorMessages.length + warnMessages.length >= MAX_EVENTS_PER_GROUP)
78
+ break;
79
+ try {
80
+ const res = await client.send(new client_cloudwatch_logs_1.FilterLogEventsCommand({
81
+ logGroupName: lg.name,
82
+ filterPattern,
83
+ startTime,
84
+ limit: 25,
85
+ }));
86
+ for (const event of res.events ?? []) {
87
+ const msg = event.message ?? '';
88
+ if (filterPattern === 'WARN') {
89
+ warnMessages.push(msg);
90
+ }
91
+ else {
92
+ errorMessages.push(msg);
93
+ if (event.timestamp) {
94
+ const ts = new Date(event.timestamp).toISOString();
95
+ if (!lastErrorTime || ts > lastErrorTime)
96
+ lastErrorTime = ts;
97
+ }
98
+ }
99
+ }
100
+ }
101
+ catch {
102
+ // Filter pattern may not match; continue
103
+ }
104
+ }
105
+ summaries.push({
106
+ logGroupName: lg.name,
107
+ retentionDays: lg.retentionDays,
108
+ errorCount: errorMessages.length,
109
+ warnCount: warnMessages.length,
110
+ topErrorPatterns: topPatterns(errorMessages),
111
+ lastErrorTime,
112
+ });
113
+ }
114
+ core_1.logger.debug(`CloudWatch Logs: sampled ${summaries.length} log group(s)`);
115
+ return summaries;
116
+ }
117
+ async function validateLogsAccess(cfg = {}) {
118
+ await new client_cloudwatch_logs_1.CloudWatchLogsClient(clientConfig(cfg)).send(new client_cloudwatch_logs_1.DescribeLogGroupsCommand({ limit: 1 }));
119
+ }
@@ -0,0 +1,109 @@
1
+ "use strict";
2
+ Object.defineProperty(exports, "__esModule", { value: true });
3
+ exports.MongoConnectionError = void 0;
4
+ exports.extractMongoMetadata = extractMongoMetadata;
5
+ exports.validateMongoAccess = validateMongoAccess;
6
+ const mongodb_1 = require("mongodb");
7
+ const core_1 = require("../core");
8
+ const SYSTEM_DATABASES = new Set(['admin', 'local', 'config']);
9
+ class MongoConnectionError extends core_1.InfrawiseError {
10
+ constructor(details) {
11
+ super('Unable to connect to MongoDB.\n\nPossible reasons:\n- invalid connection string\n- port 27017 not accessible\n- wrong credentials\n\nRun: infrawise doctor', undefined, undefined);
12
+ this.name = 'MongoConnectionError';
13
+ if (details) {
14
+ this.message = `Unable to connect to MongoDB.\n\nPossible reasons:\n- invalid connection string\n- port 27017 not accessible\n- wrong credentials\n\nRun: infrawise doctor\n\nDetail: ${details}`;
15
+ }
16
+ }
17
+ }
18
+ exports.MongoConnectionError = MongoConnectionError;
19
+ async function extractMongoMetadata(connectionString, databases) {
20
+ const client = new mongodb_1.MongoClient(connectionString, {
21
+ serverSelectionTimeoutMS: 5000,
22
+ connectTimeoutMS: 5000,
23
+ });
24
+ try {
25
+ await client.connect();
26
+ // Determine which databases to introspect
27
+ let dbNames;
28
+ if (databases && databases.length > 0) {
29
+ dbNames = databases;
30
+ }
31
+ else {
32
+ const adminDb = client.db('admin');
33
+ const dbList = await adminDb.admin().listDatabases();
34
+ dbNames = dbList.databases
35
+ .map((d) => d.name)
36
+ .filter((name) => !SYSTEM_DATABASES.has(name));
37
+ }
38
+ core_1.logger.debug(`Introspecting ${dbNames.length} MongoDB database(s)`);
39
+ const results = [];
40
+ for (const dbName of dbNames) {
41
+ const db = client.db(dbName);
42
+ let collectionNames;
43
+ try {
44
+ const collections = await db.listCollections().toArray();
45
+ collectionNames = collections.map((c) => c.name);
46
+ }
47
+ catch (err) {
48
+ core_1.logger.warn(`Skipping database "${dbName}": ${err instanceof Error ? err.message : String(err)}`);
49
+ continue;
50
+ }
51
+ for (const collName of collectionNames) {
52
+ const collection = db.collection(collName);
53
+ let rawIndexes = [];
54
+ try {
55
+ rawIndexes = (await collection.indexes());
56
+ }
57
+ catch {
58
+ rawIndexes = [];
59
+ }
60
+ let estimatedCount = 0;
61
+ try {
62
+ estimatedCount = await collection.estimatedDocumentCount();
63
+ }
64
+ catch {
65
+ estimatedCount = 0;
66
+ }
67
+ const indexes = rawIndexes.map((idx) => ({
68
+ name: String(idx['name'] ?? ''),
69
+ keys: (idx['key'] ?? {}),
70
+ unique: Boolean(idx['unique']),
71
+ sparse: Boolean(idx['sparse']),
72
+ }));
73
+ results.push({
74
+ database: dbName,
75
+ collection: collName,
76
+ indexes,
77
+ estimatedCount,
78
+ });
79
+ }
80
+ }
81
+ core_1.logger.debug(`Found ${results.length} MongoDB collection(s)`);
82
+ return results;
83
+ }
84
+ catch (err) {
85
+ if (err instanceof MongoConnectionError)
86
+ throw err;
87
+ throw new MongoConnectionError(err instanceof Error ? err.message : String(err));
88
+ }
89
+ finally {
90
+ await client.close().catch(() => undefined);
91
+ }
92
+ }
93
+ async function validateMongoAccess(connectionString) {
94
+ const client = new mongodb_1.MongoClient(connectionString, {
95
+ serverSelectionTimeoutMS: 5000,
96
+ connectTimeoutMS: 5000,
97
+ });
98
+ try {
99
+ await client.connect();
100
+ await client.db('admin').command({ ping: 1 });
101
+ return true;
102
+ }
103
+ catch {
104
+ return false;
105
+ }
106
+ finally {
107
+ await client.close().catch(() => undefined);
108
+ }
109
+ }
@@ -0,0 +1,135 @@
1
+ "use strict";
2
+ var __importDefault = (this && this.__importDefault) || function (mod) {
3
+ return (mod && mod.__esModule) ? mod : { "default": mod };
4
+ };
5
+ Object.defineProperty(exports, "__esModule", { value: true });
6
+ exports.MySQLConnectionError = void 0;
7
+ exports.extractMySQLMetadata = extractMySQLMetadata;
8
+ exports.validateMySQLAccess = validateMySQLAccess;
9
+ const promise_1 = __importDefault(require("mysql2/promise"));
10
+ const core_1 = require("../core");
11
+ const SYSTEM_SCHEMAS = new Set(['information_schema', 'performance_schema', 'mysql', 'sys']);
12
+ class MySQLConnectionError extends core_1.InfrawiseError {
13
+ constructor(details) {
14
+ super('Unable to connect to MySQL.\n\nPossible reasons:\n- invalid connection string\n- port 3306 not accessible\n- wrong credentials\n\nRun: infrawise doctor', undefined, undefined);
15
+ this.name = 'MySQLConnectionError';
16
+ if (details) {
17
+ this.message = `Unable to connect to MySQL.\n\nPossible reasons:\n- invalid connection string\n- port 3306 not accessible\n- wrong credentials\n\nRun: infrawise doctor\n\nDetail: ${details}`;
18
+ }
19
+ }
20
+ }
21
+ exports.MySQLConnectionError = MySQLConnectionError;
22
+ async function extractMySQLMetadata(connectionString) {
23
+ let connection;
24
+ try {
25
+ connection = await promise_1.default.createConnection(connectionString);
26
+ // Get all user tables
27
+ const [tableRows] = await connection.execute(`
28
+ SELECT TABLE_SCHEMA, TABLE_NAME, ENGINE
29
+ FROM information_schema.tables
30
+ WHERE TABLE_TYPE = 'BASE TABLE'
31
+ AND TABLE_SCHEMA NOT IN (${[...SYSTEM_SCHEMAS].map(() => '?').join(', ')})
32
+ ORDER BY TABLE_SCHEMA, TABLE_NAME
33
+ `, [...SYSTEM_SCHEMAS]);
34
+ core_1.logger.debug(`Found ${tableRows.length} MySQL table(s)`);
35
+ // Get all columns
36
+ const [columnRows] = await connection.execute(`
37
+ SELECT TABLE_SCHEMA, TABLE_NAME, COLUMN_NAME
38
+ FROM information_schema.columns
39
+ WHERE TABLE_SCHEMA NOT IN (${[...SYSTEM_SCHEMAS].map(() => '?').join(', ')})
40
+ ORDER BY TABLE_SCHEMA, TABLE_NAME, ORDINAL_POSITION
41
+ `, [...SYSTEM_SCHEMAS]);
42
+ // Get all indexes
43
+ const [indexRows] = await connection.execute(`
44
+ SELECT TABLE_SCHEMA, TABLE_NAME, INDEX_NAME
45
+ FROM information_schema.statistics
46
+ WHERE TABLE_SCHEMA NOT IN (${[...SYSTEM_SCHEMAS].map(() => '?').join(', ')})
47
+ GROUP BY TABLE_SCHEMA, TABLE_NAME, INDEX_NAME
48
+ ORDER BY TABLE_SCHEMA, TABLE_NAME, INDEX_NAME
49
+ `, [...SYSTEM_SCHEMAS]);
50
+ // Get primary keys
51
+ const [pkRows] = await connection.execute(`
52
+ SELECT TABLE_SCHEMA, TABLE_NAME, COLUMN_NAME
53
+ FROM information_schema.key_column_usage
54
+ WHERE CONSTRAINT_NAME = 'PRIMARY'
55
+ AND TABLE_SCHEMA NOT IN (${[...SYSTEM_SCHEMAS].map(() => '?').join(', ')})
56
+ ORDER BY TABLE_SCHEMA, TABLE_NAME, ORDINAL_POSITION
57
+ `, [...SYSTEM_SCHEMAS]);
58
+ // Build lookup maps
59
+ const columnMap = new Map();
60
+ for (const row of columnRows) {
61
+ const key = `${row['TABLE_SCHEMA']}.${row['TABLE_NAME']}`;
62
+ let cols = columnMap.get(key);
63
+ if (!cols) {
64
+ cols = [];
65
+ columnMap.set(key, cols);
66
+ }
67
+ cols.push(row['COLUMN_NAME']);
68
+ }
69
+ const indexMap = new Map();
70
+ for (const row of indexRows) {
71
+ const key = `${row['TABLE_SCHEMA']}.${row['TABLE_NAME']}`;
72
+ let idxs = indexMap.get(key);
73
+ if (!idxs) {
74
+ idxs = [];
75
+ indexMap.set(key, idxs);
76
+ }
77
+ const idxName = row['INDEX_NAME'];
78
+ if (!idxs.includes(idxName))
79
+ idxs.push(idxName);
80
+ }
81
+ const pkMap = new Map();
82
+ for (const row of pkRows) {
83
+ const key = `${row['TABLE_SCHEMA']}.${row['TABLE_NAME']}`;
84
+ let pks = pkMap.get(key);
85
+ if (!pks) {
86
+ pks = [];
87
+ pkMap.set(key, pks);
88
+ }
89
+ pks.push(row['COLUMN_NAME']);
90
+ }
91
+ // Assemble results
92
+ const results = [];
93
+ for (const row of tableRows) {
94
+ const schema = row['TABLE_SCHEMA'];
95
+ const table = row['TABLE_NAME'];
96
+ const engine = row['ENGINE'] ?? 'InnoDB';
97
+ const key = `${schema}.${table}`;
98
+ results.push({
99
+ schema,
100
+ table,
101
+ columns: columnMap.get(key) ?? [],
102
+ indexes: indexMap.get(key) ?? [],
103
+ primaryKeys: pkMap.get(key) ?? [],
104
+ engine,
105
+ });
106
+ }
107
+ return results;
108
+ }
109
+ catch (err) {
110
+ if (err instanceof MySQLConnectionError)
111
+ throw err;
112
+ throw new MySQLConnectionError(err instanceof Error ? err.message : String(err));
113
+ }
114
+ finally {
115
+ if (connection) {
116
+ await connection.end().catch(() => undefined);
117
+ }
118
+ }
119
+ }
120
+ async function validateMySQLAccess(connectionString) {
121
+ let connection;
122
+ try {
123
+ connection = await promise_1.default.createConnection(connectionString);
124
+ await connection.execute('SELECT 1');
125
+ return true;
126
+ }
127
+ catch {
128
+ return false;
129
+ }
130
+ finally {
131
+ if (connection) {
132
+ await connection.end().catch(() => undefined);
133
+ }
134
+ }
135
+ }
@@ -0,0 +1,131 @@
1
+ "use strict";
2
+ Object.defineProperty(exports, "__esModule", { value: true });
3
+ exports.extractPostgresMetadata = extractPostgresMetadata;
4
+ exports.validatePostgresAccess = validatePostgresAccess;
5
+ const pg_1 = require("pg");
6
+ const core_1 = require("../core");
7
+ async function extractPostgresMetadata(connectionString) {
8
+ const pool = new pg_1.Pool({
9
+ connectionString,
10
+ max: 1,
11
+ idleTimeoutMillis: 10000,
12
+ connectionTimeoutMillis: 5000,
13
+ });
14
+ try {
15
+ // Test connection
16
+ const client = await pool.connect();
17
+ try {
18
+ // Get all user tables
19
+ const tablesResult = await client.query(`
20
+ SELECT table_schema, table_name
21
+ FROM information_schema.tables
22
+ WHERE table_type = 'BASE TABLE'
23
+ AND table_schema NOT IN ('pg_catalog', 'information_schema')
24
+ ORDER BY table_schema, table_name
25
+ `);
26
+ core_1.logger.debug(`Found ${tablesResult.rows.length} PostgreSQL table(s)`);
27
+ // Get all columns
28
+ const columnsResult = await client.query(`
29
+ SELECT table_schema, table_name, column_name
30
+ FROM information_schema.columns
31
+ WHERE table_schema NOT IN ('pg_catalog', 'information_schema')
32
+ ORDER BY table_schema, table_name, ordinal_position
33
+ `);
34
+ // Get all indexes
35
+ const indexesResult = await client.query(`
36
+ SELECT schemaname, tablename, indexname
37
+ FROM pg_indexes
38
+ WHERE schemaname NOT IN ('pg_catalog', 'information_schema')
39
+ ORDER BY schemaname, tablename, indexname
40
+ `);
41
+ // Get primary keys
42
+ const primaryKeysResult = await client.query(`
43
+ SELECT
44
+ tc.table_schema,
45
+ tc.table_name,
46
+ kcu.column_name
47
+ FROM information_schema.table_constraints tc
48
+ JOIN information_schema.key_column_usage kcu
49
+ ON tc.constraint_name = kcu.constraint_name
50
+ AND tc.table_schema = kcu.table_schema
51
+ WHERE tc.constraint_type = 'PRIMARY KEY'
52
+ AND tc.table_schema NOT IN ('pg_catalog', 'information_schema')
53
+ ORDER BY tc.table_schema, tc.table_name
54
+ `);
55
+ // Build maps for columns, indexes, primary keys
56
+ const columnMap = new Map();
57
+ for (const row of columnsResult.rows) {
58
+ const key = `${row.table_schema}.${row.table_name}`;
59
+ let cols = columnMap.get(key);
60
+ if (!cols) {
61
+ cols = [];
62
+ columnMap.set(key, cols);
63
+ }
64
+ cols.push(row.column_name);
65
+ }
66
+ const indexMap = new Map();
67
+ for (const row of indexesResult.rows) {
68
+ const key = `${row.schemaname}.${row.tablename}`;
69
+ let idxs = indexMap.get(key);
70
+ if (!idxs) {
71
+ idxs = [];
72
+ indexMap.set(key, idxs);
73
+ }
74
+ idxs.push(row.indexname);
75
+ }
76
+ const pkMap = new Map();
77
+ for (const row of primaryKeysResult.rows) {
78
+ const key = `${row.table_schema}.${row.table_name}`;
79
+ let pks = pkMap.get(key);
80
+ if (!pks) {
81
+ pks = [];
82
+ pkMap.set(key, pks);
83
+ }
84
+ pks.push(row.column_name);
85
+ }
86
+ // Assemble results
87
+ const results = [];
88
+ for (const table of tablesResult.rows) {
89
+ const key = `${table.table_schema}.${table.table_name}`;
90
+ results.push({
91
+ schema: table.table_schema,
92
+ table: table.table_name,
93
+ columns: columnMap.get(key) ?? [],
94
+ indexes: indexMap.get(key) ?? [],
95
+ primaryKeys: pkMap.get(key) ?? [],
96
+ });
97
+ }
98
+ return results;
99
+ }
100
+ finally {
101
+ client.release();
102
+ }
103
+ }
104
+ catch (err) {
105
+ if (err instanceof core_1.PostgresConnectionError)
106
+ throw err;
107
+ throw new core_1.PostgresConnectionError(err instanceof Error ? err.message : 'Unknown error connecting to PostgreSQL');
108
+ }
109
+ finally {
110
+ await pool.end();
111
+ }
112
+ }
113
+ async function validatePostgresAccess(connectionString) {
114
+ const pool = new pg_1.Pool({
115
+ connectionString,
116
+ max: 1,
117
+ connectionTimeoutMillis: 5000,
118
+ });
119
+ try {
120
+ const client = await pool.connect();
121
+ await client.query('SELECT 1');
122
+ client.release();
123
+ return true;
124
+ }
125
+ catch {
126
+ return false;
127
+ }
128
+ finally {
129
+ await pool.end();
130
+ }
131
+ }