22 lines
2.4 KiB
TypeScript
22 lines
2.4 KiB
TypeScript
import type { AnalysisSnapshot, AnalyzerFailure } from "./policy.ts";
|
|
export interface AnalysisRequest { readonly analyzerKey: string; readonly paths: readonly string[] }
|
|
export interface AnalyzerAdapter { analyze(paths: readonly string[], workspaceRevision: number, signal?: AbortSignal): Promise<AnalysisSnapshot> }
|
|
export class AnalysisCoordinator {
|
|
constructor(private readonly adapters: ReadonlyMap<string, AnalyzerAdapter>) {}
|
|
async run(requests: readonly AnalysisRequest[], revision: number, signal?: AbortSignal): Promise<ReadonlyMap<string, AnalysisSnapshot>> {
|
|
const grouped = new Map<string, Set<string>>();
|
|
for (const request of requests) { const paths = grouped.get(request.analyzerKey) ?? new Set<string>(); for (const path of request.paths) paths.add(path); grouped.set(request.analyzerKey, paths); }
|
|
const entries = await Promise.all([...grouped].sort(([a], [b]) => a.localeCompare(b)).map(async ([key, paths]) => {
|
|
const adapter = this.adapters.get(key);
|
|
const sorted = [...paths].sort();
|
|
if (!adapter) return [key, failed(key, revision, sorted, { kind: "missing", message: `No analyzer registered for ${key}` })] as const;
|
|
try { return [key, deepFreeze(await adapter.analyze(sorted, revision, signal))] as const; }
|
|
catch (error) { return [key, failed(key, revision, sorted, { kind: "internal", message: error instanceof Error ? error.message : String(error) })] as const; }
|
|
}));
|
|
return readonlyMap(entries);
|
|
}
|
|
}
|
|
function failed(key: string, workspaceRevision: number, paths: readonly string[], failure: AnalyzerFailure): AnalysisSnapshot { return Object.freeze({ analyzerKey: key, workspaceRevision, paths: Object.freeze([...paths]), findings: Object.freeze([]), durationMs: 0, failure: Object.freeze(failure) }); }
|
|
export function deepFreeze<T>(value: T): T { if (value && typeof value === "object" && !Object.isFrozen(value)) { Object.freeze(value); for (const item of Object.values(value as Record<string, unknown>)) deepFreeze(item); } return value; }
|
|
export function readonlyMap<K, V>(entries: readonly (readonly [K, V])[]): ReadonlyMap<K, V> { const map = new Map(entries); Object.freeze(map); return new Proxy(map, { get(target, prop) { if (prop === "set" || prop === "delete" || prop === "clear") return undefined; const value = Reflect.get(target, prop, target) as unknown; return typeof value === "function" ? (value as Function).bind(target) : value; } }); }
|