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 { fallback?: (integration: TIntegration, error: unknown) => TResult; throwOnAllFailure?: boolean; } export interface IntegrationQueryProvenance { failedIntegrationCount: number; staleIntegrationCount: number; } interface IntegrationQuerySettlement { results: TResult[]; failedIntegrationCount: number; } const settleIntegrationQueriesInternal = async ( integrations: TIntegration[], fn: (integration: TIntegration) => Promise, options?: Options, ): Promise> => { 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( integrations: TIntegration[], fn: (integration: TIntegration) => Promise, options?: Options, ): Promise { return (await settleIntegrationQueriesInternal(integrations, fn, options)).results; } export async function settleIntegrationQueriesWithProvenance< TIntegration extends IntegrationLike, TResult extends { isStale?: boolean }, >( integrations: TIntegration[], fn: (integration: TIntegration) => Promise, options?: Options, ): Promise & IntegrationQueryProvenance> { const settlement = await settleIntegrationQueriesInternal(integrations, fn, options); return { ...settlement, staleIntegrationCount: settlement.results.filter((result) => result.isStale === true).length, }; }