import { AnalysisCapabilityUnavailableError } from "../../domain/analysisErrorCore.js"; import { err, ok } from "../../domain/result.js"; import type { BinaryTarget } from "../../domain/binaryTargetTypes.js"; import type { AnalysisProfileCommitment } from "../../domain/analysisProfile.js"; import type { AnalysisClient, AnalysisClientContext, AnalysisProvider, CapabilityDescriptor, ProviderIdentity, } from "../AnalysisProvider.js"; import { closeAnalysisClient } from "./AnalysisClientCleanup.js"; /** Synthetic identity for a deterministic provider set. */ const compositeProviderIdentity = ( identities: readonly ProviderIdentity[], ): ProviderIdentity => ({ id: `composite:${identities .map(({ id }) => id) .sort() .join("+")}`, name: "REA composite analysis provider", version: null, }); /** Deterministically route disjoint operations without eagerly starting children. */ export class CompositeProvider implements AnalysisProvider { readonly #identity: ProviderIdentity; readonly #capabilities: readonly CapabilityDescriptor[]; readonly #providerByOperation: ReadonlyMap; constructor(readonly providers: readonly AnalysisProvider[]) { if (providers.length === 0) throw new RangeError("CompositeProvider requires at least one provider"); this.#identity = Object.freeze( compositeProviderIdentity( providers.map((provider) => provider.identity()), ), ); const routes = new Map(); const capabilities: CapabilityDescriptor[] = []; for (const provider of providers) { for (const descriptor of provider.capabilities()) { if (routes.has(descriptor.operation)) throw new TypeError( `Multiple providers declare operation ${descriptor.operation}`, ); routes.set(descriptor.operation, provider); capabilities.push(descriptor); } } this.#providerByOperation = routes; this.#capabilities = Object.freeze( capabilities.sort( (left, right) => left.operation.localeCompare(right.operation) || left.provider.id.localeCompare(right.provider.id), ), ); } identity(): ProviderIdentity { return this.#identity; } capabilities(): readonly CapabilityDescriptor[] { return this.#capabilities; } createClient( target: BinaryTarget, profile?: AnalysisProfileCommitment, context?: AnalysisClientContext, ): AnalysisClient { const clients = new Map(); const clientFor = (provider: AnalysisProvider): AnalysisClient => { const existing = clients.get(provider); if (existing !== undefined) return existing; const created = provider.createClient( target, profile?.provider.id === provider.identity().id ? profile : undefined, context, ); clients.set(provider, created); return created; }; const close: AnalysisClient["close"] = async (options) => { const outcomes = await Promise.all( [...clients.entries()].map(([provider, client]) => closeAnalysisClient(client, provider.identity().id, options), ), ); return outcomes.find((outcome) => !outcome.ok) ?? ok(null); }; return { execute: (operation, parameters, options) => { if (operation === "health") return Promise.resolve( ok({ result: null, rawResult: null, provider: this.#identity, limitations: [], locations: [], subject: null, }), ); const provider = this.#providerByOperation.get(operation); return provider === undefined ? Promise.resolve( err( new AnalysisCapabilityUnavailableError( this.#identity.id, operation, "operation is not declared by this provider set", ), ), ) : clientFor(provider).execute(operation, parameters, options); }, runtimeLineageSnapshots: () => [...clients.values()] .flatMap((client) => client.runtimeLineageSnapshots?.() ?? []) .sort((left, right) => left.provider.id.localeCompare(right.provider.id), ), requestActivitySnapshots: () => [...clients.values()] .flatMap((client) => client.requestActivitySnapshots?.() ?? []) .sort((left, right) => left.provider.id.localeCompare(right.provider.id), ), operationHealthSnapshot: () => { for (const client of clients.values()) { const snapshot = client.operationHealthSnapshot?.(); if (snapshot !== undefined && snapshot.state !== "idle") return snapshot; } for (const client of clients.values()) { const snapshot = client.operationHealthSnapshot?.(); if (snapshot !== undefined) return snapshot; } return undefined; }, close, }; } }