import { Pool } from 'pg'; import type { AssessorRepository } from '../../src/persistence/assessor-repository'; import { reportReadPortFrom, type ReportReadPort } from '../../src/reporting/report-read-port'; import { PostgresAssessorRepository } from '../postgres/postgres-assessor-repository'; import { repositoryConnectionString, sslConfiguration, type RepositoryEnvironment } from '../repository-factory'; /** Session option that makes PostgreSQL reject every write of this pool. */ export const REPORT_READ_ONLY_SESSION_OPTIONS = '-c default_transaction_read_only=on'; export interface ReportReadRuntime { port: ReportReadPort; readOnlyDatabase: boolean; close(): Promise; } /** * The Report Engine's own, separate database reader. It uses its own small pool on the same * canonical database, whose sessions are read-only, runs no migrations and starts no rebuild. */ export async function createPostgresReportReadRuntime(environment: RepositoryEnvironment): Promise { const connectionString = repositoryConnectionString(environment); if (!connectionString) throw new Error('Der Report-Leser benötigt die bestehende PostgreSQL-Verbindung.'); const hostname = new URL(connectionString).hostname; const sslMode = environment.ASSESSOR_DATABASE_SSL?.trim() || (['localhost', '127.0.0.1', '[::1]'].includes(hostname) ? 'disable' : 'require'); const pool = new Pool({ connectionString, ssl: sslConfiguration(sslMode), max: 2, connectionTimeoutMillis: 5000, idleTimeoutMillis: 30000, application_name: 'assessor-report-reader', options: REPORT_READ_ONLY_SESSION_OPTIONS }); try { const repository = await PostgresAssessorRepository.openReadOnly(pool); const port = reportReadPortFrom(repository, async () => { const result = await pool.query<{ revision: string }>('SELECT revision FROM assessor_repository_state WHERE singleton = true'); return Number(result.rows[0]?.revision ?? -1); }); return { port, readOnlyDatabase: true, close: () => pool.end() }; } catch (error) { await pool.end().catch(() => undefined); throw error; } } /** In-memory operation has no database; the port only narrows the running repository to reads. */ export function memoryReportReadRuntime(repository: AssessorRepository): ReportReadRuntime { return { port: reportReadPortFrom(repository, async () => repository.revision), readOnlyDatabase: false, async close() {} }; }