mirror of
https://github.com/morluto/rea.git
synced 2026-10-29 10:55:57 +00:00
97 lines
3.2 KiB
TypeScript
97 lines
3.2 KiB
TypeScript
import { ProviderSelectionError } from "../domain/errors.js";
|
|
import { err, ok } from "../domain/result.js";
|
|
import type { BinaryTarget } from "../domain/binaryTarget.js";
|
|
import type {
|
|
AnalysisClient,
|
|
AnalysisProvider,
|
|
CapabilityDescriptor,
|
|
ProviderIdentity,
|
|
} from "./AnalysisProvider.js";
|
|
|
|
const compositeIdentity = (
|
|
providers: readonly AnalysisProvider[],
|
|
): ProviderIdentity => ({
|
|
id: `composite:${providers
|
|
.map((provider) => provider.identity().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<string, AnalysisProvider>;
|
|
|
|
constructor(readonly providers: readonly AnalysisProvider[]) {
|
|
if (providers.length === 0)
|
|
throw new RangeError("CompositeProvider requires at least one provider");
|
|
this.#identity = Object.freeze(compositeIdentity(providers));
|
|
const routes = new Map<string, AnalysisProvider>();
|
|
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): AnalysisClient {
|
|
const clients = new Map<AnalysisProvider, AnalysisClient>();
|
|
const clientFor = (provider: AnalysisProvider): AnalysisClient => {
|
|
const existing = clients.get(provider);
|
|
if (existing !== undefined) return existing;
|
|
const created = provider.createClient(target);
|
|
clients.set(provider, created);
|
|
return created;
|
|
};
|
|
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 ProviderSelectionError(operation)))
|
|
: clientFor(provider).execute(operation, parameters, options);
|
|
},
|
|
close: async () => {
|
|
await Promise.allSettled(
|
|
[...clients.values()].map(async (client) => client.close()),
|
|
);
|
|
},
|
|
};
|
|
}
|
|
}
|