43 lines
2.4 KiB
TypeScript
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))
|
|
});
|
|
}
|