| |
| |
| |
| |
| |
| |
| |
| |
| |
|
|
|
|
| export interface CredentialHealthStatus {
|
| connectionId: string;
|
| provider: string;
|
|
|
| status: "active" | "error" | "unknown";
|
| lastTested: Date;
|
| lastError?: string;
|
| lastErrorType?: string;
|
| lastErrorSource?: string;
|
|
|
| consecutiveFailures: number;
|
|
|
| responseTimeMs?: number;
|
| }
|
|
|
| export interface CredentialCacheEntry {
|
| status: CredentialHealthStatus;
|
|
|
| expiresAt: number;
|
| }
|
|
|
|
|
|
|
| const DEFAULT_TTL_MS = 5 * 60 * 1000;
|
| const STALE_THRESHOLD_MS = 10 * 60 * 1000;
|
| const MAX_ENTRIES = 500;
|
|
|
|
|
|
|
| declare global {
|
| var __omnirouteCredentialCache:
|
| | {
|
| initialized: boolean;
|
| cache: Map<string, CredentialCacheEntry>;
|
| }
|
| | undefined;
|
| }
|
|
|
| function getCacheState() {
|
| if (!globalThis.__omnirouteCredentialCache) {
|
| globalThis.__omnirouteCredentialCache = {
|
| initialized: false,
|
| cache: new Map(),
|
| };
|
| }
|
| return globalThis.__omnirouteCredentialCache;
|
| }
|
|
|
|
|
|
|
| |
| |
| |
|
|
| export function getCredentialHealth(connectionId: string): CredentialHealthStatus | undefined {
|
| const state = getCacheState();
|
| const entry = state.cache.get(connectionId);
|
|
|
| if (!entry) return undefined;
|
| if (Date.now() > entry.expiresAt) {
|
|
|
| state.cache.delete(connectionId);
|
| return undefined;
|
| }
|
|
|
| return entry.status;
|
| }
|
|
|
| |
| |
| |
| |
| |
| |
|
|
| export function isCredentialHealthy(connectionId: string): boolean | undefined {
|
| const status = getCredentialHealth(connectionId);
|
| if (!status) return undefined;
|
|
|
| if (status.status === "active") return true;
|
| if (status.status === "error") return false;
|
|
|
| return undefined;
|
| }
|
|
|
| |
| |
|
|
| export function isCredentialStale(connectionId: string): boolean {
|
| const status = getCredentialHealth(connectionId);
|
| if (!status) return true;
|
|
|
| const age = Date.now() - status.lastTested.getTime();
|
| return age > STALE_THRESHOLD_MS;
|
| }
|
|
|
| |
| |
|
|
| export function setCredentialHealth(
|
| connectionId: string,
|
| provider: string,
|
| status: "active" | "error" | "unknown",
|
| lastError?: string,
|
| lastErrorType?: string,
|
| lastErrorSource?: string,
|
| responseTimeMs?: number
|
| ): void {
|
| const state = getCacheState();
|
|
|
|
|
| if (state.cache.size >= MAX_ENTRIES) {
|
| const oldest = state.cache.entries().next().value;
|
| if (oldest) state.cache.delete(oldest[0]);
|
| }
|
|
|
| const prev = state.cache.get(connectionId);
|
| const consecutiveFailures =
|
| status === "error"
|
| ? (prev?.status.consecutiveFailures ?? 0) + 1
|
| : status === "active"
|
| ? 0
|
| : (prev?.status.consecutiveFailures ?? 0);
|
|
|
| state.cache.set(connectionId, {
|
| status: {
|
| connectionId,
|
| provider,
|
| status,
|
| lastTested: new Date(),
|
| lastError,
|
| lastErrorType,
|
| lastErrorSource,
|
| consecutiveFailures,
|
| responseTimeMs,
|
| },
|
| expiresAt: Date.now() + DEFAULT_TTL_MS,
|
| });
|
| }
|
|
|
| |
| |
|
|
| export function removeCredentialHealth(connectionId: string): void {
|
| const state = getCacheState();
|
| state.cache.delete(connectionId);
|
| }
|
|
|
| |
| |
|
|
| export function getAllCredentialHealth(): Record<string, CredentialHealthStatus> {
|
| const state = getCacheState();
|
| const result: Record<string, CredentialHealthStatus> = {};
|
|
|
| for (const [id, entry] of state.cache.entries()) {
|
| if (Date.now() <= entry.expiresAt) {
|
| result[id] = entry.status;
|
| } else {
|
| state.cache.delete(id);
|
| }
|
| }
|
|
|
| return result;
|
| }
|
|
|
| |
| |
|
|
| export function getCredentialHealthSummary(): {
|
| total: number;
|
| healthy: number;
|
| failed: number;
|
| unknown: number;
|
| stale: number;
|
| } {
|
| const all = getAllCredentialHealth();
|
| const entries = Object.values(all);
|
| const now = Date.now();
|
|
|
| return {
|
| total: entries.length,
|
| healthy: entries.filter((e) => e.status === "active").length,
|
| failed: entries.filter((e) => e.status === "error").length,
|
| unknown: entries.filter((e) => e.status === "unknown").length,
|
| stale: entries.filter((e) => now - e.lastTested.getTime() > STALE_THRESHOLD_MS).length,
|
| };
|
| }
|
|
|
| |
| |
|
|
| export function initCredentialCache(): void {
|
| getCacheState().initialized = true;
|
| }
|
|
|