feat: Initial release MCP Outline PostgreSQL v1.0.0
86 tools across 12 modules for direct PostgreSQL access to Outline Wiki: - Documents (19), Collections (14), Users (9), Groups (8) - Comments (6), Shares (5), Revisions (3), Events (3) - Attachments (5), File Operations (4), OAuth (8), Auth (2) Co-Authored-By: Claude Opus 4.5 <noreply@anthropic.com>
This commit is contained in:
158
src/pg-client.ts
Normal file
158
src/pg-client.ts
Normal file
@@ -0,0 +1,158 @@
|
||||
/**
|
||||
* MCP Outline PostgreSQL - PostgreSQL Client
|
||||
* @author Descomplicar® | @link descomplicar.pt | @copyright 2026
|
||||
*/
|
||||
|
||||
import { Pool, PoolConfig, QueryResult, QueryResultRow } from 'pg';
|
||||
import { DatabaseConfig } from './config/database.js';
|
||||
import { logger } from './utils/logger.js';
|
||||
|
||||
export class PgClient {
|
||||
private pool: Pool;
|
||||
private isConnected: boolean = false;
|
||||
|
||||
constructor(config: DatabaseConfig) {
|
||||
const poolConfig: PoolConfig = config.connectionString
|
||||
? {
|
||||
connectionString: config.connectionString,
|
||||
max: config.max,
|
||||
idleTimeoutMillis: config.idleTimeoutMillis,
|
||||
connectionTimeoutMillis: config.connectionTimeoutMillis
|
||||
}
|
||||
: {
|
||||
host: config.host,
|
||||
port: config.port,
|
||||
user: config.user,
|
||||
password: config.password,
|
||||
database: config.database,
|
||||
ssl: config.ssl ? { rejectUnauthorized: false } : false,
|
||||
max: config.max,
|
||||
idleTimeoutMillis: config.idleTimeoutMillis,
|
||||
connectionTimeoutMillis: config.connectionTimeoutMillis
|
||||
};
|
||||
|
||||
this.pool = new Pool(poolConfig);
|
||||
|
||||
// Handle pool errors
|
||||
this.pool.on('error', (err) => {
|
||||
logger.error('Unexpected PostgreSQL pool error', { error: err.message });
|
||||
});
|
||||
}
|
||||
|
||||
/**
|
||||
* Get the underlying pool for direct access
|
||||
*/
|
||||
getPool(): Pool {
|
||||
return this.pool;
|
||||
}
|
||||
|
||||
/**
|
||||
* Test database connection
|
||||
*/
|
||||
async testConnection(): Promise<boolean> {
|
||||
try {
|
||||
const client = await this.pool.connect();
|
||||
await client.query('SELECT 1');
|
||||
client.release();
|
||||
this.isConnected = true;
|
||||
logger.info('PostgreSQL connection successful');
|
||||
return true;
|
||||
} catch (error) {
|
||||
logger.error('PostgreSQL connection failed', {
|
||||
error: error instanceof Error ? error.message : String(error)
|
||||
});
|
||||
this.isConnected = false;
|
||||
return false;
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Execute a query with parameters
|
||||
*/
|
||||
async query<T extends QueryResultRow = any>(sql: string, params?: any[]): Promise<T[]> {
|
||||
const start = Date.now();
|
||||
try {
|
||||
const result = await this.pool.query<T>(sql, params);
|
||||
const duration = Date.now() - start;
|
||||
|
||||
logger.debug('Query executed', {
|
||||
sql: sql.substring(0, 100),
|
||||
duration,
|
||||
rowCount: result.rowCount
|
||||
});
|
||||
|
||||
return result.rows;
|
||||
} catch (error) {
|
||||
const duration = Date.now() - start;
|
||||
logger.error('Query failed', {
|
||||
sql: sql.substring(0, 100),
|
||||
duration,
|
||||
error: error instanceof Error ? error.message : String(error)
|
||||
});
|
||||
throw error;
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Execute a query and return the full result
|
||||
*/
|
||||
async queryRaw<T extends QueryResultRow = any>(sql: string, params?: any[]): Promise<QueryResult<T>> {
|
||||
return this.pool.query<T>(sql, params);
|
||||
}
|
||||
|
||||
/**
|
||||
* Execute a query and return a single row
|
||||
*/
|
||||
async queryOne<T extends QueryResultRow = any>(sql: string, params?: any[]): Promise<T | null> {
|
||||
const rows = await this.query<T>(sql, params);
|
||||
return rows.length > 0 ? rows[0] : null;
|
||||
}
|
||||
|
||||
/**
|
||||
* Execute multiple queries in a transaction
|
||||
*/
|
||||
async transaction<T>(callback: (client: any) => Promise<T>): Promise<T> {
|
||||
const client = await this.pool.connect();
|
||||
try {
|
||||
await client.query('BEGIN');
|
||||
const result = await callback(client);
|
||||
await client.query('COMMIT');
|
||||
return result;
|
||||
} catch (error) {
|
||||
await client.query('ROLLBACK');
|
||||
throw error;
|
||||
} finally {
|
||||
client.release();
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Close the pool
|
||||
*/
|
||||
async close(): Promise<void> {
|
||||
await this.pool.end();
|
||||
this.isConnected = false;
|
||||
logger.info('PostgreSQL pool closed');
|
||||
}
|
||||
|
||||
/**
|
||||
* Check if connected
|
||||
*/
|
||||
isPoolConnected(): boolean {
|
||||
return this.isConnected;
|
||||
}
|
||||
}
|
||||
|
||||
// Export a singleton factory
|
||||
let instance: PgClient | null = null;
|
||||
|
||||
export function createPgClient(config: DatabaseConfig): PgClient {
|
||||
if (!instance) {
|
||||
instance = new PgClient(config);
|
||||
}
|
||||
return instance;
|
||||
}
|
||||
|
||||
export function getPgClient(): PgClient | null {
|
||||
return instance;
|
||||
}
|
||||
Reference in New Issue
Block a user