{"version":3,"file":"module-circuit-breaker.mjs","sources":["../../../src/lib/vdom/module-circuit-breaker.ts"],"sourcesContent":["/**\n * @file Module Circuit Breaker\n * @description Circuit breaker for dynamic module loading to prevent\n * cascading failures when module servers/CDNs are unavailable\n */\n\nimport { CircuitBreaker, type CircuitState, CircuitOpenError } from '../utils/resilience';\nimport type { ModuleId } from './types';\n\n/**\n * Module circuit breaker configuration\n */\nexport interface ModuleCircuitBreakerConfig {\n  /** Failure threshold before opening circuit */\n  failureThreshold?: number;\n  /** Success threshold for half-open to close */\n  successThreshold?: number;\n  /** Time to wait before trying half-open (ms) */\n  resetTimeout?: number;\n  /** Time window for failure counting (ms) */\n  failureWindow?: number;\n  /** Callback on state change */\n  onStateChange?: (from: CircuitState, to: CircuitState) => void;\n  /** Callback when circuit opens */\n  onCircuitOpen?: () => void;\n  /** Callback when circuit recovers */\n  onCircuitRecovery?: () => void;\n}\n\n/**\n * Module loading metrics\n */\nexport interface ModuleLoadingMetrics {\n  totalAttempts: number;\n  totalSuccesses: number;\n  totalFailures: number;\n  circuitOpenCount: number;\n  lastFailureTime: Date | null;\n  lastSuccessTime: Date | null;\n  circuitState: CircuitState;\n  failedModules: Set<ModuleId>;\n}\n\n/**\n * Default configuration\n */\nconst DEFAULT_CONFIG: Required<Omit<ModuleCircuitBreakerConfig, 'onStateChange' | 'onCircuitOpen' | 'onCircuitRecovery'>> = {\n  failureThreshold: 5,\n  successThreshold: 2,\n  resetTimeout: 60000, // 1 minute\n  failureWindow: 60000, // 1 minute\n};\n\n/**\n * Module Circuit Breaker\n *\n * Provides circuit breaker protection for dynamic module loading.\n * When module loading fails repeatedly (e.g., CDN is down), the circuit\n * opens and immediately fails subsequent requests, preventing:\n * - Thundering herd on module servers\n * - UI hangs waiting for modules to load\n * - Cascading failures across the application\n *\n * @example\n * ```typescript\n * const breaker = getModuleCircuitBreaker();\n *\n * // Execute module load through circuit breaker\n * const component = await breaker.execute(() => import('./MyModule'));\n *\n * // Check if we can attempt to load modules\n * if (breaker.canLoadModules()) {\n *   await loadModule(moduleId);\n * } else {\n *   showOfflineFallback();\n * }\n * ```\n */\nexport class ModuleCircuitBreaker {\n  private breaker: CircuitBreaker;\n  private config: Required<Omit<ModuleCircuitBreakerConfig, 'onStateChange' | 'onCircuitOpen' | 'onCircuitRecovery'>> & {\n    onStateChange?: ModuleCircuitBreakerConfig['onStateChange'];\n    onCircuitOpen?: ModuleCircuitBreakerConfig['onCircuitOpen'];\n    onCircuitRecovery?: ModuleCircuitBreakerConfig['onCircuitRecovery'];\n  };\n\n  private totalAttempts = 0;\n  private totalSuccesses = 0;\n  private totalFailures = 0;\n  private circuitOpenCount = 0;\n  private lastFailureTime: number | null = null;\n  private lastSuccessTime: number | null = null;\n  private failedModules: Set<ModuleId> = new Set();\n\n  constructor(config: ModuleCircuitBreakerConfig = {}) {\n    this.config = {\n      ...DEFAULT_CONFIG,\n      ...config,\n    };\n\n    this.breaker = new CircuitBreaker({\n      failureThreshold: this.config.failureThreshold,\n      successThreshold: this.config.successThreshold,\n      resetTimeout: this.config.resetTimeout,\n      failureWindow: this.config.failureWindow,\n      onStateChange: (from, to) => {\n        this.handleStateChange(from, to);\n        this.config.onStateChange?.(from, to);\n      },\n    });\n  }\n\n  /**\n   * Get current circuit state\n   */\n  getState(): CircuitState {\n    return this.breaker.getState();\n  }\n\n  /**\n   * Check if module loading is allowed\n   */\n  canLoadModules(): boolean {\n    return this.breaker.getState() !== 'open';\n  }\n\n  /**\n   * Execute a module load through the circuit breaker\n   *\n   * @param loader - The module loading function\n   * @param moduleId - Optional module ID for tracking\n   * @returns The loaded module\n   * @throws CircuitOpenError if circuit is open\n   */\n  async execute<T>(\n    loader: () => Promise<T>,\n    moduleId?: ModuleId\n  ): Promise<T> {\n    this.totalAttempts++;\n\n    const state = this.breaker.getState();\n\n    if (state === 'open') {\n      throw new ModuleCircuitOpenError(\n        'Module loading circuit is open - too many recent failures',\n        moduleId\n      );\n    }\n\n    try {\n      const result = await this.breaker.execute(loader);\n      this.recordSuccess(moduleId);\n      return result;\n    } catch (error) {\n      this.recordFailure(moduleId);\n      throw error;\n    }\n  }\n\n  /**\n   * Execute module load with fallback\n   *\n   * @param loader - The module loading function\n   * @param fallback - Fallback value if circuit is open or load fails\n   * @param moduleId - Optional module ID for tracking\n   * @returns The loaded module or fallback\n   */\n  async executeWithFallback<T>(\n    loader: () => Promise<T>,\n    fallback: T,\n    moduleId?: ModuleId\n  ): Promise<T> {\n    try {\n      return await this.execute(loader, moduleId);\n    } catch {\n      return fallback;\n    }\n  }\n\n  /**\n   * Get loading metrics\n   */\n  getMetrics(): ModuleLoadingMetrics {\n    return {\n      totalAttempts: this.totalAttempts,\n      totalSuccesses: this.totalSuccesses,\n      totalFailures: this.totalFailures,\n      circuitOpenCount: this.circuitOpenCount,\n      lastFailureTime: this.lastFailureTime ? new Date(this.lastFailureTime) : null,\n      lastSuccessTime: this.lastSuccessTime ? new Date(this.lastSuccessTime) : null,\n      circuitState: this.breaker.getState(),\n      failedModules: new Set(this.failedModules),\n    };\n  }\n\n  /**\n   * Force reset the circuit\n   */\n  reset(): void {\n    this.breaker.reset();\n    this.failedModules.clear();\n    console.info('[ModuleCircuitBreaker] Circuit manually reset');\n  }\n\n  /**\n   * Handle circuit state changes\n   */\n  private handleStateChange(from: CircuitState, to: CircuitState): void {\n    if (to === 'open') {\n      this.circuitOpenCount++;\n      console.warn('[ModuleCircuitBreaker] Circuit opened - module loading disabled');\n      console.warn(`[ModuleCircuitBreaker] Failed modules: ${Array.from(this.failedModules).join(', ')}`);\n      this.config.onCircuitOpen?.();\n    } else if (from === 'open' && (to === 'half-open' || to === 'closed')) {\n      console.info('[ModuleCircuitBreaker] Circuit recovering - attempting module loads');\n    } else if (to === 'closed' && from !== 'closed') {\n      this.failedModules.clear();\n      console.info('[ModuleCircuitBreaker] Circuit recovered - module loading enabled');\n      this.config.onCircuitRecovery?.();\n    }\n  }\n\n  /**\n   * Record a successful module load\n   */\n  private recordSuccess(moduleId?: ModuleId): void {\n    this.totalSuccesses++;\n    this.lastSuccessTime = Date.now();\n\n    if (moduleId) {\n      this.failedModules.delete(moduleId);\n    }\n  }\n\n  /**\n   * Record a failed module load\n   */\n  private recordFailure(moduleId?: ModuleId): void {\n    this.totalFailures++;\n    this.lastFailureTime = Date.now();\n\n    if (moduleId) {\n      this.failedModules.add(moduleId);\n    }\n  }\n}\n\n/**\n * Error thrown when module circuit is open\n */\nexport class ModuleCircuitOpenError extends CircuitOpenError {\n  readonly moduleId?: ModuleId;\n\n  constructor(message: string, moduleId?: ModuleId) {\n    super(message);\n    this.name = 'ModuleCircuitOpenError';\n    this.moduleId = moduleId;\n  }\n}\n\n// ============================================================================\n// Global Instance\n// ============================================================================\n\n/**\n * Global module circuit breaker instance\n */\nlet globalModuleCircuitBreaker: ModuleCircuitBreaker | null = null;\n\n/**\n * Get or create the global module circuit breaker\n */\nexport function getModuleCircuitBreaker(\n  config?: ModuleCircuitBreakerConfig\n): ModuleCircuitBreaker {\n  globalModuleCircuitBreaker ??= new ModuleCircuitBreaker(config);\n  return globalModuleCircuitBreaker;\n}\n\n/**\n * Reset the global module circuit breaker\n */\nexport function resetModuleCircuitBreaker(): void {\n  globalModuleCircuitBreaker?.reset();\n}\n\n/**\n * Replace the global module circuit breaker (for testing)\n */\nexport function setModuleCircuitBreaker(breaker: ModuleCircuitBreaker | null): void {\n  globalModuleCircuitBreaker = breaker;\n}\n\n// ============================================================================\n// Helper Functions\n// ============================================================================\n\n/**\n * Wrap a dynamic import with circuit breaker protection\n *\n * @example\n * ```typescript\n * const MyComponent = await withModuleCircuitBreaker(\n *   () => import('./MyComponent'),\n *   'MyComponent'\n * );\n * ```\n */\nexport async function withModuleCircuitBreaker<T>(\n  loader: () => Promise<T>,\n  moduleId?: ModuleId\n): Promise<T> {\n  const breaker = getModuleCircuitBreaker();\n  return breaker.execute(loader, moduleId);\n}\n\n/**\n * Wrap a dynamic import with circuit breaker and fallback\n *\n * @example\n * ```typescript\n * const MyComponent = await withModuleCircuitBreakerFallback(\n *   () => import('./MyComponent'),\n *   FallbackComponent,\n *   'MyComponent'\n * );\n * ```\n */\nexport async function withModuleCircuitBreakerFallback<T>(\n  loader: () => Promise<T>,\n  fallback: T,\n  moduleId?: ModuleId\n): Promise<T> {\n  const breaker = getModuleCircuitBreaker();\n  return breaker.executeWithFallback(loader, fallback, moduleId);\n}\n\n/**\n * Check if module loading is currently available\n */\nexport function canLoadModules(): boolean {\n  const breaker = getModuleCircuitBreaker();\n  return breaker.canLoadModules();\n}\n\n/**\n * Get module loading circuit state\n */\nexport function getModuleCircuitState(): CircuitState {\n  const breaker = getModuleCircuitBreaker();\n  return breaker.getState();\n}\n"],"names":["DEFAULT_CONFIG","ModuleCircuitBreaker","config","CircuitBreaker","from","to","loader","moduleId","ModuleCircuitOpenError","result","error","fallback","CircuitOpenError","message","globalModuleCircuitBreaker","getModuleCircuitBreaker","resetModuleCircuitBreaker","setModuleCircuitBreaker","breaker","withModuleCircuitBreaker","withModuleCircuitBreakerFallback","canLoadModules","getModuleCircuitState"],"mappings":"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"}