Assessor_2.0/server/reporting/report-read-runtime.ts

56 lines
2.4 KiB
TypeScript

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<void>;
}
/**
* 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<ReportReadRuntime> {
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() {}
};
}