pg-lens-mcp 0.1.0-alpha

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/CHANGELOG.md ADDED
@@ -0,0 +1,43 @@
1
+ # Changelog
2
+
3
+ All notable changes to this project will be documented in this file.
4
+
5
+ The format is based on [Keep a Changelog](https://keepachangelog.com/en/1.0.0/),
6
+ and this project adheres to [Semantic Versioning](https://semver.org/spec/v2.0.0.html).
7
+
8
+ ## [0.1.0-alpha] - 2026-01-25
9
+
10
+ ### Added
11
+ - Initial release of pg-lens-mcp
12
+ - **8 database exploration tools**:
13
+ - `list_schemas` - List all non-system schemas
14
+ - `list_tables` - List tables in a schema
15
+ - `search_column` - Find tables containing a column pattern
16
+ - `get_table_info` - Get comprehensive table schema with columns, indexes, and relationships
17
+ - `get_table_data` - Query table data with structured filters and pagination
18
+ - `execute_query` - Execute custom read-only SQL queries
19
+ - `explain_query` - Get query execution plans without running queries
20
+ - `explain_analyze` - Profile actual query performance with timing
21
+ - **Security features**:
22
+ - Database-enforced READ ONLY transactions (not keyword-based)
23
+ - SQL injection protection via parameterized queries
24
+ - Structured filters instead of raw WHERE clauses
25
+ - 30-second query timeout to prevent hanging queries
26
+ - **Production-ready infrastructure**:
27
+ - Configurable connection pooling with max connections, idle timeout, and connection timeout
28
+ - Health check on startup with clear error messages
29
+ - Graceful shutdown handlers for SIGINT/SIGTERM
30
+ - **Developer experience**:
31
+ - Token-optimized markdown table output (~40-60% reduction)
32
+ - Comprehensive documentation with examples
33
+ - Support for npx, Docker, and direct Node.js usage
34
+ - TypeScript with strict mode enabled
35
+ - Modular architecture (13 focused files)
36
+
37
+ ### Security
38
+ - All queries run in PostgreSQL's READ ONLY transaction mode
39
+ - Structured filters prevent SQL injection in WHERE clauses
40
+ - Input validation at multiple layers
41
+ - Connection timeouts prevent resource exhaustion
42
+
43
+ [0.1.0-alpha]: https://github.com/YhannHommet/pg-lens-mcp/releases/tag/v0.1.0-alpha
package/LICENSE ADDED
@@ -0,0 +1,21 @@
1
+ MIT License
2
+
3
+ Copyright (c) 2026 Yohann
4
+
5
+ Permission is hereby granted, free of charge, to any person obtaining a copy
6
+ of this software and associated documentation files (the "Software"), to deal
7
+ in the Software without restriction, including without limitation the rights
8
+ to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
9
+ copies of the Software, and to permit persons to whom the Software is
10
+ furnished to do so, subject to the following conditions:
11
+
12
+ The above copyright notice and this permission notice shall be included in all
13
+ copies or substantial portions of the Software.
14
+
15
+ THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
16
+ IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
17
+ FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
18
+ AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
19
+ LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
20
+ OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
21
+ SOFTWARE.
package/README.md ADDED
@@ -0,0 +1,483 @@
1
+ <div align="center">
2
+
3
+ # PG Lens MCP Server
4
+
5
+ **Securely connect AI assistants to your PostgreSQL databases**
6
+
7
+ ![Version](https://img.shields.io/badge/version-0.1.0--alpha-blue?style=for-the-badge)
8
+ ![License](https://img.shields.io/badge/license-MIT-green?style=for-the-badge)
9
+ ![Node](https://img.shields.io/badge/node-%3E%3D18-brightgreen?style=for-the-badge)
10
+ ![TypeScript](https://img.shields.io/badge/TypeScript-5.7-blue?style=for-the-badge)
11
+
12
+ [Features](#-features) • [Installation](#-installation) • [Configuration](#️-configuration) • [Tools](#️-available-tools) • [Security](#-security)
13
+
14
+ </div>
15
+
16
+ ---
17
+
18
+ ## ✨ Features
19
+
20
+ | Feature | Description |
21
+ |---------|-------------|
22
+ | **Read-Only by Design** | All queries execute in database-enforced READ ONLY transactions |
23
+ | **Full Schema Discovery** | Explore schemas, tables, columns, indexes, and relationships |
24
+ | **Query Performance Analysis** | Built-in EXPLAIN and EXPLAIN ANALYZE for optimization |
25
+ | **SQL-Injection Safe** | Structured filters with parameterized queries |
26
+ | **Token-Optimized Output** | Markdown table formatting reduces AI token usage by ~40-60% |
27
+ | **8 Powerful Tools** | Complete toolkit for database exploration and analysis |
28
+ | **Production-Ready** | Configurable connection pooling with timeouts and health checks |
29
+
30
+ ---
31
+
32
+ ## 🏗️ Architecture
33
+
34
+ ```mermaid
35
+ graph LR
36
+ A[Claude/AI Assistant] -->|MCP Protocol| B[PostgreSQL MCP Server]
37
+ B -->|READ ONLY Transactions| C[(PostgreSQL Database)]
38
+
39
+ style B fill:#4CAF50,color:#fff
40
+ style C fill:#336791,color:#fff
41
+ ```
42
+
43
+ The server acts as a secure bridge between AI assistants and your PostgreSQL database, enforcing read-only access at the database transaction level.
44
+
45
+ ---
46
+
47
+ ## 📦 Installation
48
+
49
+ ```bash
50
+ git clone https://github.com/YhannHommet/pg-lens-mcp.git
51
+ cd pg-lens-mcp
52
+ npm install
53
+ npm run build
54
+ ```
55
+
56
+ ---
57
+
58
+ ## ⚙️ Configuration
59
+
60
+ ### Environment Variables
61
+
62
+ Configure the connection using environment variables:
63
+
64
+ | Variable | Description | Default |
65
+ |----------|-------------|---------|
66
+ | `DB_HOST` | PostgreSQL host | `localhost` |
67
+ | `DB_PORT` | PostgreSQL port | `5432` |
68
+ | `DB_DATABASE` | Database name | `postgres` |
69
+ | `DB_USERNAME` | Database user | `postgres` |
70
+ | `DB_PASSWORD` | Database password | `postgres` |
71
+ | `DB_SCHEMA` | Default schema | `public` |
72
+ | `DB_MAX_CONNECTIONS` | Connection pool size | `10` |
73
+ | `DB_IDLE_TIMEOUT_MS` | Idle connection timeout | `30000` |
74
+ | `DB_CONNECTION_TIMEOUT_MS` | Connection attempt timeout | `5000` |
75
+
76
+ ### MCP Configuration
77
+
78
+ Add to your Claude Desktop config (`~/.claude/claude_desktop_config.json`):
79
+
80
+ ##### Option 1: Direct Node.js (Local Installation)
81
+
82
+ ```json
83
+ {
84
+ "mcpServers": {
85
+ "postgres": {
86
+ "command": "node",
87
+ "args": ["/absolute/path/to/postgres-server/dist/index.js"],
88
+ "env": {
89
+ "DB_HOST": "localhost",
90
+ "DB_PORT": "5432",
91
+ "DB_DATABASE": "your_database",
92
+ "DB_USERNAME": "your_username",
93
+ "DB_PASSWORD": "your_password",
94
+ "DB_SCHEMA": "public"
95
+ }
96
+ }
97
+ }
98
+ }
99
+ ```
100
+
101
+ ##### Option 2: Using npx (No Installation Required)
102
+
103
+ ```json
104
+ {
105
+ "mcpServers": {
106
+ "postgres": {
107
+ "command": "npx",
108
+ "args": [
109
+ "-y",
110
+ "pg-lens-mcp"
111
+ ],
112
+ "env": {
113
+ "DB_HOST": "localhost",
114
+ "DB_PORT": "5432",
115
+ "DB_DATABASE": "your_database",
116
+ "DB_USERNAME": "your_username",
117
+ "DB_PASSWORD": "your_password",
118
+ "DB_SCHEMA": "public"
119
+ }
120
+ }
121
+ }
122
+ }
123
+ ```
124
+
125
+
126
+
127
+ ##### Option 3: Using Docker
128
+
129
+ ```json
130
+ {
131
+ "mcpServers": {
132
+ "postgres": {
133
+ "command": "docker",
134
+ "args": [
135
+ "run",
136
+ "--rm",
137
+ "-i",
138
+ "--network=host",
139
+ "-e", "DB_HOST=localhost",
140
+ "-e", "DB_PORT=5432",
141
+ "-e", "DB_DATABASE=your_database",
142
+ "-e", "DB_USERNAME=your_username",
143
+ "-e", "DB_PASSWORD=your_password",
144
+ "-e", "DB_SCHEMA=public",
145
+ "pg-lens-mcp:latest"
146
+ ]
147
+ }
148
+ }
149
+ }
150
+ ```
151
+
152
+ **Docker-specific notes:**
153
+ - Use `--network=host` for connecting to localhost databases
154
+ - For remote databases, you can remove `--network=host`
155
+ - For databases in other Docker containers, use custom networks:
156
+ ```json
157
+ "args": [
158
+ "run", "--rm", "-i",
159
+ "--network=your_docker_network",
160
+ "-e", "DB_HOST=postgres_container_name",
161
+ ...
162
+ ]
163
+ ```
164
+
165
+ > **💡 Tip:** Use a read-only database user for extra security, even though all queries run in READ ONLY transactions.
166
+
167
+ ---
168
+
169
+ ## 🛠️ Available Tools
170
+
171
+ ### 🗂️ Schema Discovery
172
+
173
+ <details>
174
+ <summary><b>list_schemas</b> — List all non-system schemas</summary>
175
+
176
+ Discover all user-defined schemas in your database.
177
+
178
+ **Example usage:**
179
+ ```
180
+ "List all schemas in the database"
181
+ ```
182
+
183
+ **Returns:** Markdown table with schema names and owners
184
+
185
+ </details>
186
+
187
+ <details>
188
+ <summary><b>list_tables</b> — List all tables in a schema</summary>
189
+
190
+ **Parameters:**
191
+ - `schema` *(optional)* — Schema name (default: `public`)
192
+
193
+ **Example usage:**
194
+ ```
195
+ "Show me all tables in the public schema"
196
+ ```
197
+
198
+ **Returns:** Markdown table with table names and types (TABLE, VIEW, etc.)
199
+
200
+ </details>
201
+
202
+ <details>
203
+ <summary><b>search_column</b> — Find tables containing a column pattern</summary>
204
+
205
+ **Parameters:**
206
+ - `column_pattern` *(required)* — Partial or full column name (case-insensitive)
207
+
208
+ **Example usage:**
209
+ ```
210
+ "Find all tables that have an 'email' column"
211
+ ```
212
+
213
+ **Returns:** Markdown table showing schema, table, column name, data type, and nullability
214
+
215
+ </details>
216
+
217
+ <details>
218
+ <summary><b>get_table_info</b> — Get comprehensive table schema</summary>
219
+
220
+ **Parameters:**
221
+ - `table_name` *(required)* — Name of the table to inspect
222
+ - `schema` *(optional)* — Schema name (default: `public`)
223
+
224
+ **Example usage:**
225
+ ```
226
+ "Show me the complete structure of the users table"
227
+ ```
228
+
229
+ **Returns:** JSON with:
230
+ - Column details (name, type, nullability, defaults)
231
+ - Primary keys
232
+ - Foreign key relationships
233
+ - Indexes with uniqueness information
234
+
235
+ </details>
236
+
237
+ ---
238
+
239
+ ### 📊 Data Querying
240
+
241
+ <details>
242
+ <summary><b>get_table_data</b> — Query table data with structured filters</summary>
243
+
244
+ **Parameters:**
245
+ - `table_name` *(required)* — Table to query
246
+ - `schema` *(optional)* — Schema name (default: `public`)
247
+ - `columns` *(optional)* — Specific columns to select (default: all)
248
+ - `filters` *(optional)* — **Structured filters** (SQL injection safe!)
249
+ ```typescript
250
+ [{
251
+ column: "status",
252
+ operator: "=", // Options: =, !=, <, >, <=, >=, LIKE, ILIKE, IN, IS NULL, IS NOT NULL
253
+ value: "active"
254
+ }]
255
+ ```
256
+ - `limit` *(optional)* — Max rows to return (default: 100, max: 1000)
257
+ - `offset` *(optional)* — Rows to skip for pagination
258
+ - `order_by` *(optional)* — Column to sort by
259
+ - `order_direction` *(optional)* — `ASC` or `DESC` (default: `ASC`)
260
+
261
+ **Example usage:**
262
+ ```
263
+ "Get the first 20 active users created after 2024-01-01, ordered by creation date"
264
+ ```
265
+
266
+ **Returns:** Markdown table with:
267
+ - Query results
268
+ - Metadata (total rows, returned rows, pagination info)
269
+
270
+ </details>
271
+
272
+ <details>
273
+ <summary><b>execute_query</b> — Execute custom read-only SQL</summary>
274
+
275
+ **Parameters:**
276
+ - `query` *(required)* — SQL SELECT query
277
+ - `params` *(optional)* — Query parameters for `$1`, `$2`, etc.
278
+
279
+ **Example usage:**
280
+ ```
281
+ "Execute this query:
282
+ SELECT u.name, COUNT(o.id) as order_count
283
+ FROM users u
284
+ LEFT JOIN orders o ON u.id = o.user_id
285
+ GROUP BY u.name
286
+ ORDER BY order_count DESC
287
+ LIMIT 10"
288
+ ```
289
+
290
+ **Returns:** Markdown table with query results
291
+
292
+ **Security:** Runs in `BEGIN TRANSACTION READ ONLY` — PostgreSQL itself enforces no writes can occur
293
+
294
+ </details>
295
+
296
+ ---
297
+
298
+ ### ⚡ Performance Analysis
299
+
300
+ <details>
301
+ <summary><b>explain_query</b> — Get query execution plan (without running query)</summary>
302
+
303
+ **Parameters:**
304
+ - `query` *(required)* — SQL query to analyze
305
+ - `format` *(optional)* — `text`, `json`, or `yaml` (default: `json`)
306
+ - `verbose` *(optional)* — Include verbose details (default: `false`)
307
+
308
+ **Example usage:**
309
+ ```
310
+ "Explain how PostgreSQL would execute: SELECT * FROM users WHERE email LIKE '%@example.com'"
311
+ ```
312
+
313
+ **Returns:** Query execution plan showing:
314
+ - Scan types (Sequential Scan, Index Scan, etc.)
315
+ - Estimated costs and row counts
316
+ - Join strategies
317
+
318
+ **Use case:** Understanding query performance before optimization
319
+
320
+ </details>
321
+
322
+ <details>
323
+ <summary><b>explain_analyze</b> — Execute and profile query performance</summary>
324
+
325
+ **Parameters:**
326
+ - `query` *(required)* — SQL query to analyze
327
+ - `format` *(optional)* — `text` or `json` (default: `json`)
328
+ - `buffers` *(optional)* — Include buffer usage stats (default: `false`)
329
+ - `timing` *(optional)* — Include timing info (default: `true`)
330
+ - `verbose` *(optional)* — Verbose output (default: `false`)
331
+
332
+ **Example usage:**
333
+ ```
334
+ "Analyze the actual performance of: SELECT * FROM large_table WHERE indexed_column = 'value'"
335
+ ```
336
+
337
+ **Returns:** Actual execution statistics including:
338
+ - Real execution time
339
+ - Actual rows processed vs. estimated
340
+ - Buffer hits/misses (if `buffers: true`)
341
+ - Node-level timing breakdown
342
+
343
+ ⚠️ **Note:** This actually executes the query (in READ ONLY mode). May be slow on large datasets.
344
+
345
+ </details>
346
+
347
+ ---
348
+
349
+ ## 🔐 Security
350
+
351
+ ### Database-Enforced Read-Only Access
352
+
353
+ Unlike simple keyword filtering, this server uses **PostgreSQL's transactional READ ONLY mode**:
354
+
355
+ ```typescript
356
+ await client.query('BEGIN TRANSACTION READ ONLY');
357
+ const result = await client.query(userQuery); // ← PostgreSQL blocks ANY writes
358
+ await client.query('COMMIT');
359
+ ```
360
+
361
+ **Why this matters:**
362
+
363
+ ✅ **No false positives** — Queries containing words like "UPDATE" or "INSERT" in strings/comments work fine
364
+ ✅ **No bypasses** — Cannot be circumvented via stored procedures, functions, or extensions
365
+ ✅ **Database-level guarantee** — PostgreSQL itself enforces the read-only constraint
366
+
367
+ ### SQL Injection Protection
368
+
369
+ **Structured filters** replace dangerous string concatenation:
370
+
371
+ ❌ **Unsafe approach:**
372
+ ```javascript
373
+ query += ` WHERE ${userInput}` // Direct concatenation = SQL injection risk
374
+ ```
375
+
376
+ ✅ **Our approach:**
377
+ ```typescript
378
+ filters: [{
379
+ column: "status",
380
+ operator: "=",
381
+ value: "active"
382
+ }]
383
+ // Becomes: WHERE "status" = $1
384
+ // PostgreSQL handles escaping automatically
385
+ ```
386
+
387
+ All user inputs are properly parameterized, eliminating SQL injection vectors.
388
+
389
+ ---
390
+
391
+ ## 🚀 Development
392
+
393
+ ```bash
394
+ # Run in development mode with auto-reload
395
+ npm run dev
396
+
397
+ # Build for production
398
+ npm run build
399
+
400
+ # Run production build
401
+ npm start
402
+ ```
403
+
404
+ ---
405
+
406
+ ## 🧪 Testing the Server
407
+
408
+ ### Quick Test
409
+
410
+ ```bash
411
+ # Set your database credentials
412
+ export DB_HOST=localhost
413
+ export DB_DATABASE=your_database
414
+ export DB_USERNAME=your_username
415
+ export DB_PASSWORD=your_password
416
+
417
+ # Start the server
418
+ node dist/index.js
419
+ ```
420
+
421
+ **Expected output:**
422
+ ```
423
+ ✓ Database connection verified
424
+ ✓ PostgreSQL MCP Server running on stdio
425
+ ```
426
+
427
+ ### Testing with Claude Desktop
428
+
429
+ 1. Add the server to your MCP configuration
430
+ 2. Restart Claude Desktop
431
+ 3. Try these example prompts:
432
+ - "List all schemas in the database"
433
+ - "Show me the structure of the users table"
434
+ - "Find all tables with a 'created_at' column"
435
+ - "Explain the query plan for SELECT * FROM large_table LIMIT 10"
436
+
437
+ ---
438
+
439
+ ## 📋 Troubleshooting
440
+
441
+ ### Connection Issues
442
+
443
+ **"password authentication failed"**
444
+ - Check `DB_USERNAME` and `DB_PASSWORD` are correct
445
+ - Verify the user has access to the specified database
446
+
447
+ **"Connection timeout"**
448
+ - Check `DB_HOST` is reachable
449
+ - Verify PostgreSQL is running on `DB_PORT`
450
+ - Check firewall rules if connecting remotely
451
+
452
+ **"database does not exist"**
453
+ - Verify `DB_DATABASE` name is correct
454
+ - List available databases: `psql -l`
455
+
456
+ ### Performance
457
+
458
+ If queries are slow:
459
+ 1. Use `explain_analyze` tool to identify bottlenecks
460
+ 2. Check if indexes exist on frequently queried columns
461
+ 3. Consider adjusting `DB_MAX_CONNECTIONS` based on your workload
462
+
463
+ ---
464
+
465
+ ## 📄 License
466
+
467
+ MIT License
468
+
469
+ ---
470
+
471
+ ## 🤝 Contributing
472
+
473
+ Contributions are welcome! Please feel free to submit issues or pull requests.
474
+
475
+ ---
476
+
477
+ <div align="center">
478
+
479
+ **Built for the Model Context Protocol ecosystem**
480
+
481
+ Made with ❤️ for AI-assisted database exploration
482
+
483
+ </div>
@@ -0,0 +1,19 @@
1
+ /**
2
+ * Database configuration with environment variable validation
3
+ */
4
+ export interface DatabaseConfig {
5
+ host: string;
6
+ port: number;
7
+ database: string;
8
+ user: string;
9
+ password: string;
10
+ schema: string;
11
+ maxConnections: number;
12
+ idleTimeoutMs: number;
13
+ connectionTimeoutMs: number;
14
+ }
15
+ /**
16
+ * Load and validate database configuration from environment variables
17
+ */
18
+ export declare function loadConfig(): DatabaseConfig;
19
+ export declare const config: DatabaseConfig;
package/dist/config.js ADDED
@@ -0,0 +1,38 @@
1
+ /**
2
+ * Database configuration with environment variable validation
3
+ */
4
+ function getEnvVar(key, defaultValue) {
5
+ const value = process.env[key];
6
+ if (!value && defaultValue === undefined) {
7
+ throw new Error(`Missing required environment variable: ${key}`);
8
+ }
9
+ return value || defaultValue;
10
+ }
11
+ function getEnvInt(key, defaultValue) {
12
+ const value = process.env[key];
13
+ if (!value)
14
+ return defaultValue;
15
+ const parsed = parseInt(value, 10);
16
+ if (isNaN(parsed)) {
17
+ throw new Error(`Invalid integer for ${key}: ${value}`);
18
+ }
19
+ return parsed;
20
+ }
21
+ /**
22
+ * Load and validate database configuration from environment variables
23
+ */
24
+ export function loadConfig() {
25
+ return {
26
+ host: getEnvVar("DB_HOST", "localhost"),
27
+ port: getEnvInt("DB_PORT", 5432),
28
+ database: getEnvVar("DB_DATABASE", "postgres"),
29
+ user: getEnvVar("DB_USERNAME", "postgres"),
30
+ password: getEnvVar("DB_PASSWORD", "postgres"),
31
+ schema: getEnvVar("DB_SCHEMA", "public"),
32
+ // Production-ready pool settings
33
+ maxConnections: getEnvInt("DB_MAX_CONNECTIONS", 10),
34
+ idleTimeoutMs: getEnvInt("DB_IDLE_TIMEOUT_MS", 30000),
35
+ connectionTimeoutMs: getEnvInt("DB_CONNECTION_TIMEOUT_MS", 5000),
36
+ };
37
+ }
38
+ export const config = loadConfig();
@@ -0,0 +1,22 @@
1
+ /**
2
+ * PostgreSQL connection pool management with health checks
3
+ */
4
+ import pg from "pg";
5
+ /**
6
+ * Create and configure the PostgreSQL connection pool
7
+ */
8
+ export declare const pool: import("pg").Pool;
9
+ /**
10
+ * Verify database connection is working
11
+ * @throws Error if connection fails
12
+ */
13
+ export declare function healthCheck(): Promise<void>;
14
+ /**
15
+ * Gracefully shutdown the connection pool
16
+ */
17
+ export declare function shutdown(): Promise<void>;
18
+ /**
19
+ * Execute a query in a READ ONLY transaction
20
+ * This provides database-enforced read-only guarantee
21
+ */
22
+ export declare function executeReadOnly<T extends pg.QueryResultRow = pg.QueryResultRow>(query: string, params?: unknown[]): Promise<pg.QueryResult<T>>;
@@ -0,0 +1,66 @@
1
+ /**
2
+ * PostgreSQL connection pool management with health checks
3
+ */
4
+ import pg from "pg";
5
+ import { config } from "../config.js";
6
+ const { Pool } = pg;
7
+ /**
8
+ * Create and configure the PostgreSQL connection pool
9
+ */
10
+ export const pool = new Pool({
11
+ host: config.host,
12
+ port: config.port,
13
+ database: config.database,
14
+ user: config.user,
15
+ password: config.password,
16
+ max: config.maxConnections,
17
+ idleTimeoutMillis: config.idleTimeoutMs,
18
+ connectionTimeoutMillis: config.connectionTimeoutMs,
19
+ });
20
+ /**
21
+ * Verify database connection is working
22
+ * @throws Error if connection fails
23
+ */
24
+ export async function healthCheck() {
25
+ const client = await pool.connect();
26
+ try {
27
+ await client.query("SELECT 1");
28
+ console.error("✓ Database connection verified");
29
+ }
30
+ catch (error) {
31
+ console.error("✗ Database connection failed:", error);
32
+ throw new Error(`Failed to connect to PostgreSQL at ${config.host}:${config.port}/${config.database}`);
33
+ }
34
+ finally {
35
+ client.release();
36
+ }
37
+ }
38
+ /**
39
+ * Gracefully shutdown the connection pool
40
+ */
41
+ export async function shutdown() {
42
+ await pool.end();
43
+ console.error("✓ Database connection pool closed");
44
+ }
45
+ /**
46
+ * Execute a query in a READ ONLY transaction
47
+ * This provides database-enforced read-only guarantee
48
+ */
49
+ export async function executeReadOnly(query, params) {
50
+ const client = await pool.connect();
51
+ try {
52
+ // Set query timeout to prevent long-running queries
53
+ await client.query("SET statement_timeout = 30000"); // 30 seconds
54
+ await client.query("BEGIN TRANSACTION READ ONLY");
55
+ const result = await client.query(query, params);
56
+ await client.query("COMMIT");
57
+ return result;
58
+ }
59
+ catch (error) {
60
+ await client.query("ROLLBACK");
61
+ throw error;
62
+ }
63
+ finally {
64
+ client.release();
65
+ }
66
+ }