Assessor_2.0/src/comparison/comparison-read-port.ts

43 lines
2.4 KiB
TypeScript

import type { AssessorRepository } from '../persistence/assessor-repository';
/** Declared read-only port of the Comparison Engine: it reads the Core and never writes to it. */
export type ComparisonReadMethods = Pick<AssessorRepository, 'getProject' | 'listAssessments' | 'loadAssessmentModel'
| 'queryUnifiedResults' | 'listPolicyEvaluations' | 'listGpoBackupInstances'>;
export interface ComparisonReadPort extends ComparisonReadMethods { currentRevision(): Promise<number> }
/** Bounded per method; models and policy evaluations are large. */
const MAX_ENTRIES = 8;
/**
* Reads are shared within one repository revision: both timeslots of a request load the same project-wide
* policy evaluations and backups, and switching A or B reloads the same models. Any committed change starts
* a new revision and drops every entry, so a read never serves an outdated state. Failed reads are not kept.
*/
export function comparisonReadPortFrom(repository: ComparisonReadMethods, revision: () => Promise<number>): ComparisonReadPort {
let cachedRevision: number | undefined;
const caches = new Map<string, Map<string, Promise<unknown>>>();
const shared = <A extends unknown[], R>(name: string, read: (...args: A) => Promise<R>) => async (...args: A): Promise<R> => {
const current = await revision();
if (current !== cachedRevision) { caches.clear(); cachedRevision = current; }
const cache = caches.get(name) ?? caches.set(name, new Map()).get(name)!;
const key = JSON.stringify(args);
let pending = cache.get(key) as Promise<R> | undefined;
if (!pending) {
if (cache.size >= MAX_ENTRIES) cache.delete(cache.keys().next().value!);
pending = read(...args);
cache.set(key, pending);
pending.catch(() => { if (cache.get(key) === pending) cache.delete(key); });
}
return pending;
};
return Object.freeze({
currentRevision: revision,
getProject: repository.getProject.bind(repository),
listAssessments: shared('assessments', repository.listAssessments.bind(repository)),
loadAssessmentModel: shared('model', repository.loadAssessmentModel.bind(repository)),
queryUnifiedResults: shared('results', repository.queryUnifiedResults.bind(repository)),
listPolicyEvaluations: shared('evaluations', repository.listPolicyEvaluations.bind(repository)),
listGpoBackupInstances: shared('backups', repository.listGpoBackupInstances.bind(repository))
});
}