86 lines
2.8 KiB
TypeScript
86 lines
2.8 KiB
TypeScript
import { createLogger } from "@homarr/core/infrastructure/logs";
|
|
import { ErrorWithMetadata } from "@homarr/core/infrastructure/logs/error";
|
|
|
|
const logger = createLogger({ module: "settleIntegrations" });
|
|
|
|
interface IntegrationLike {
|
|
id: string;
|
|
name: string;
|
|
kind: string;
|
|
}
|
|
|
|
interface Options<TIntegration extends IntegrationLike, TResult> {
|
|
fallback?: (integration: TIntegration, error: unknown) => TResult;
|
|
throwOnAllFailure?: boolean;
|
|
}
|
|
|
|
export interface IntegrationQueryProvenance {
|
|
failedIntegrationCount: number;
|
|
staleIntegrationCount: number;
|
|
}
|
|
|
|
interface IntegrationQuerySettlement<TResult> {
|
|
results: TResult[];
|
|
failedIntegrationCount: number;
|
|
}
|
|
|
|
const settleIntegrationQueriesInternal = async <TIntegration extends IntegrationLike, TResult>(
|
|
integrations: TIntegration[],
|
|
fn: (integration: TIntegration) => Promise<TResult>,
|
|
options?: Options<TIntegration, TResult>,
|
|
): Promise<IntegrationQuerySettlement<TResult>> => {
|
|
const settled = await Promise.allSettled(integrations.map(async (integration) => fn(integration)));
|
|
const results = settled.flatMap((result, index) => {
|
|
if (result.status === "fulfilled") return [result.value];
|
|
const integration = integrations[index];
|
|
logger.warn(
|
|
new ErrorWithMetadata(
|
|
"Integration query failed",
|
|
{ integrationId: integration?.id, integrationKind: integration?.kind },
|
|
{ cause: result.reason },
|
|
),
|
|
);
|
|
if (options?.fallback && integration) return [options.fallback(integration, result.reason)];
|
|
return [];
|
|
});
|
|
|
|
const firstFailure = settled.find((result) => result.status === "rejected");
|
|
if (
|
|
options?.throwOnAllFailure === true &&
|
|
settled.length > 0 &&
|
|
settled.every((result) => result.status === "rejected") &&
|
|
firstFailure?.status === "rejected"
|
|
) {
|
|
throw firstFailure.reason;
|
|
}
|
|
|
|
return {
|
|
results,
|
|
failedIntegrationCount: settled.filter((result) => result.status === "rejected").length,
|
|
};
|
|
};
|
|
|
|
export async function settleIntegrationQueries<TIntegration extends IntegrationLike, TResult>(
|
|
integrations: TIntegration[],
|
|
fn: (integration: TIntegration) => Promise<TResult>,
|
|
options?: Options<TIntegration, TResult>,
|
|
): Promise<TResult[]> {
|
|
return (await settleIntegrationQueriesInternal(integrations, fn, options)).results;
|
|
}
|
|
|
|
export async function settleIntegrationQueriesWithProvenance<
|
|
TIntegration extends IntegrationLike,
|
|
TResult extends { isStale?: boolean },
|
|
>(
|
|
integrations: TIntegration[],
|
|
fn: (integration: TIntegration) => Promise<TResult>,
|
|
options?: Options<TIntegration, TResult>,
|
|
): Promise<IntegrationQuerySettlement<TResult> & IntegrationQueryProvenance> {
|
|
const settlement = await settleIntegrationQueriesInternal(integrations, fn, options);
|
|
|
|
return {
|
|
...settlement,
|
|
staleIntegrationCount: settlement.results.filter((result) => result.isStale === true).length,
|
|
};
|
|
}
|