{"version":3,"file":"polling-provider.mjs","sources":["../../../../src/lib/flags/providers/polling-provider.ts"],"sourcesContent":["/**\n * @fileoverview Polling provider for periodic flag updates.\n *\n * Wraps another provider and polls for updates at configurable intervals.\n * Features:\n * - Configurable polling interval\n * - Visibility-aware polling (pause when hidden)\n * - Jitter to prevent thundering herd\n * - Automatic failure handling\n *\n * @module flags/providers/polling-provider\n *\n * @example\n * ```typescript\n * const remoteProvider = new RemoteProvider({ endpoint: '...' });\n * const pollingProvider = new PollingProvider({\n *   provider: remoteProvider,\n *   interval: 30000, // 30 seconds\n *   pauseWhenHidden: true,\n *   jitter: 0.1, // 10% jitter\n * });\n *\n * await pollingProvider.initialize();\n * pollingProvider.startPolling();\n * ```\n */\n\nimport type {\n  FeatureFlag,\n  Segment,\n  SegmentId,\n} from '../advanced/types';\nimport type {\n  FlagProvider,\n  PollingProviderConfig,\n  FlagChangeListener,\n  FlagChangeEvent,\n  ProviderStats,\n} from './types';\n\n// ============================================================================\n// Constants\n// ============================================================================\n\nconst DEFAULT_INTERVAL = 60000; // 1 minute\nconst DEFAULT_MAX_FAILURES = 5;\nconst DEFAULT_JITTER = 0.1; // 10%\n\n// ============================================================================\n// Polling Provider\n// ============================================================================\n\n/**\n * Polling wrapper that periodically refreshes flags from an underlying provider.\n */\nexport class PollingProvider implements FlagProvider {\n  readonly name: string;\n  readonly priority: number;\n\n  private provider: FlagProvider;\n  private listeners = new Set<FlagChangeListener>();\n  private ready = false;\n  private config: Required<Omit<PollingProviderConfig, 'provider'>> & {\n    provider: FlagProvider;\n  };\n  private pollingTimer: ReturnType<typeof setTimeout> | null = null;\n  private isPolling = false;\n  private consecutiveFailures = 0;\n  private lastPollTime: Date | null = null;\n  private cachedFlags: FeatureFlag[] = [];\n  private cachedSegments: Segment[] = [];\n  private visibilityHandler: (() => void) | null = null;\n  private isPaused = false;\n\n  constructor(config: PollingProviderConfig) {\n    this.name = config.name ?? `polling-${config.provider.name}`;\n    this.priority = config.priority ?? config.provider.priority;\n\n    this.config = {\n      name: this.name,\n      priority: this.priority,\n      debug: config.debug ?? false,\n      provider: config.provider,\n      interval: config.interval ?? DEFAULT_INTERVAL,\n      pauseWhenHidden: config.pauseWhenHidden ?? true,\n      jitter: config.jitter ?? DEFAULT_JITTER,\n      maxFailures: config.maxFailures ?? DEFAULT_MAX_FAILURES,\n    };\n\n    this.provider = config.provider;\n  }\n\n  // ==========================================================================\n  // Initialization\n  // ==========================================================================\n\n  /**\n   * Initialize the provider.\n   */\n  async initialize(): Promise<void> {\n    this.log('Initializing polling provider');\n\n    // Initialize underlying provider\n    await this.provider.initialize();\n\n    // Initial fetch\n    await this.poll();\n\n    // Setup visibility handling\n    if (this.config.pauseWhenHidden && typeof document !== 'undefined') {\n      this.setupVisibilityHandler();\n    }\n\n    this.ready = true;\n    this.log('Polling provider initialized');\n  }\n\n  // ==========================================================================\n  // Polling Control\n  // ==========================================================================\n\n  /**\n   * Start polling for updates.\n   */\n  startPolling(): void {\n    if (this.isPolling) {\n      return;\n    }\n\n    this.isPolling = true;\n    this.scheduleNextPoll();\n    this.log('Polling started');\n  }\n\n  /**\n   * Stop polling.\n   */\n  stopPolling(): void {\n    if (this.pollingTimer) {\n      clearTimeout(this.pollingTimer);\n      this.pollingTimer = null;\n    }\n    this.isPolling = false;\n    this.log('Polling stopped');\n  }\n\n  /**\n   * Pause polling (e.g., when page is hidden).\n   */\n  pausePolling(): void {\n    if (!this.isPaused) {\n      this.isPaused = true;\n      if (this.pollingTimer) {\n        clearTimeout(this.pollingTimer);\n        this.pollingTimer = null;\n      }\n      this.log('Polling paused');\n    }\n  }\n\n  /**\n   * Resume polling.\n   */\n  resumePolling(): void {\n    if (this.isPaused && this.isPolling) {\n      this.isPaused = false;\n      this.scheduleNextPoll();\n      this.log('Polling resumed');\n    }\n  }\n\n  /**\n   * Trigger an immediate poll.\n   */\n  async pollNow(): Promise<void> {\n    await this.poll();\n  }\n\n  /**\n   * Get all flags.\n   */\n  async getFlags(): Promise<readonly FeatureFlag[]> {\n    return Promise.resolve(this.cachedFlags);\n  }\n\n  /**\n   * Get a flag by key.\n   */\n  async getFlag(key: string): Promise<FeatureFlag | null> {\n    return Promise.resolve(this.cachedFlags.find((f) => f.key === key) ?? null);\n  }\n\n  /**\n   * Get all segments.\n   */\n  async getSegments(): Promise<readonly Segment[]> {\n    return Promise.resolve(this.cachedSegments);\n  }\n\n  // ==========================================================================\n  // Visibility Handling\n  // ==========================================================================\n\n  /**\n   * Get a segment by ID.\n   */\n  async getSegment(id: SegmentId): Promise<Segment | null> {\n    return Promise.resolve(this.cachedSegments.find((s) => s.id === id) ?? null);\n  }\n\n  /**\n   * Check if the provider is ready.\n   */\n  isReady(): boolean {\n    return this.ready && this.provider.isReady();\n  }\n\n  // ==========================================================================\n  // Flag Operations\n  // ==========================================================================\n\n  /**\n   * Check if the provider is healthy.\n   */\n  async isHealthy(): Promise<boolean> {\n    return (\n      this.consecutiveFailures < this.config.maxFailures &&\n      (await this.provider.isHealthy())\n    );\n  }\n\n  /**\n   * Get polling status.\n   */\n  getPollingStatus(): {\n    isPolling: boolean;\n    isPaused: boolean;\n    lastPollTime: Date | null;\n    consecutiveFailures: number;\n    interval: number;\n  } {\n    return {\n      isPolling: this.isPolling,\n      isPaused: this.isPaused,\n      lastPollTime: this.lastPollTime,\n      consecutiveFailures: this.consecutiveFailures,\n      interval: this.config.interval,\n    };\n  }\n\n  // ==========================================================================\n  // Segment Operations\n  // ==========================================================================\n\n  /**\n   * Get provider statistics.\n   */\n  getStats(): ProviderStats {\n    return {\n      flagCount: this.cachedFlags.length,\n      segmentCount: this.cachedSegments.length,\n      requestCount: 0,\n      errorCount: this.consecutiveFailures,\n      lastRefresh: this.lastPollTime ?? undefined,\n    };\n  }\n\n  /**\n   * Subscribe to flag changes.\n   */\n  subscribe(listener: FlagChangeListener): () => void {\n    this.listeners.add(listener);\n    return () => {\n      this.listeners.delete(listener);\n    };\n  }\n\n  // ==========================================================================\n  // Status and Health\n  // ==========================================================================\n\n  /**\n   * Shutdown the provider.\n   */\n  async shutdown(): Promise<void> {\n    this.stopPolling();\n    this.removeVisibilityHandler();\n    await this.provider.shutdown();\n    this.ready = false;\n    this.listeners.clear();\n    this.log('Polling provider shutdown');\n  }\n\n  /**\n   * Update the polling interval.\n   */\n  setInterval(interval: number): void {\n    // Update config immutably\n    this.config = { ...this.config, interval };\n    if (this.isPolling) {\n      this.stopPolling();\n      this.startPolling();\n    }\n  }\n\n  private scheduleNextPoll(): void {\n    if (!this.isPolling || this.isPaused) {\n      return;\n    }\n\n    // Apply jitter to prevent thundering herd\n    const jitter = this.config.jitter * (Math.random() * 2 - 1);\n    const interval = this.config.interval * (1 + jitter);\n\n    this.pollingTimer = setTimeout(() => {\n      void this.poll().finally(() => {\n        this.scheduleNextPoll();\n      });\n    }, interval);\n  }\n\n  private async poll(): Promise<void> {\n    this.log('Polling for updates...');\n\n    try {\n      const previousFlags = [...this.cachedFlags];\n      const newFlags = await this.provider.getFlags();\n      this.cachedFlags = [...newFlags];\n\n      // Also fetch segments if available\n      if (this.provider.getSegments) {\n        const segments = await this.provider.getSegments();\n        this.cachedSegments = [...segments];\n      }\n\n      this.lastPollTime = new Date();\n      this.consecutiveFailures = 0;\n\n      // Detect and emit changes\n      this.detectChanges(previousFlags, this.cachedFlags);\n\n      this.log(`Poll complete: ${this.cachedFlags.length} flags`);\n    } catch (error) {\n      this.consecutiveFailures++;\n      this.log(`Poll failed (${this.consecutiveFailures}/${this.config.maxFailures}):`, error);\n\n      // Stop polling after too many failures\n      if (this.consecutiveFailures >= this.config.maxFailures) {\n        this.log('Max failures reached, stopping polling');\n        this.stopPolling();\n      }\n    }\n  }\n\n  // ==========================================================================\n  // Subscription\n  // ==========================================================================\n\n  private detectChanges(\n    previousFlags: FeatureFlag[],\n    newFlags: FeatureFlag[]\n  ): void {\n    const previousMap = new Map(previousFlags.map((f) => [f.key, f]));\n    const newMap = new Map(newFlags.map((f) => [f.key, f]));\n\n    // Detect additions and updates\n    for (const [key, flag] of newMap) {\n      const previous = previousMap.get(key);\n      if (!previous) {\n        this.emitChange({\n          type: 'added',\n          flagKey: key,\n          newFlag: flag,\n          timestamp: new Date(),\n          source: this.name,\n        });\n      } else if (JSON.stringify(previous) !== JSON.stringify(flag)) {\n        this.emitChange({\n          type: 'updated',\n          flagKey: key,\n          previousFlag: previous,\n          newFlag: flag,\n          timestamp: new Date(),\n          source: this.name,\n        });\n      }\n    }\n\n    // Detect deletions\n    for (const [key, previous] of previousMap) {\n      if (!newMap.has(key)) {\n        this.emitChange({\n          type: 'deleted',\n          flagKey: key,\n          previousFlag: previous,\n          timestamp: new Date(),\n          source: this.name,\n        });\n      }\n    }\n  }\n\n  private setupVisibilityHandler(): void {\n    this.visibilityHandler = (): void => {\n      if (document.hidden) {\n        this.pausePolling();\n      } else {\n        this.resumePolling();\n      }\n    };\n\n    document.addEventListener('visibilitychange', this.visibilityHandler);\n  }\n\n  // ==========================================================================\n  // Shutdown\n  // ==========================================================================\n\n  private removeVisibilityHandler(): void {\n    if (this.visibilityHandler && typeof document !== 'undefined') {\n      document.removeEventListener('visibilitychange', this.visibilityHandler);\n      this.visibilityHandler = null;\n    }\n  }\n\n  // ==========================================================================\n  // Configuration\n  // ==========================================================================\n\n  private emitChange(event: FlagChangeEvent): void {\n    for (const listener of this.listeners) {\n      try {\n        listener(event);\n      } catch (error) {\n        this.log('Error in change listener:', error);\n      }\n    }\n  }\n\n  // ==========================================================================\n  // Utilities\n  // ==========================================================================\n\n  private log(message: string, ...args: unknown[]): void {\n    if (this.config.debug) {\n      // eslint-disable-next-line no-console\n      console.log(`[PollingProvider] ${message}`, ...args);\n    }\n  }\n}\n\n// ============================================================================\n// Factory Function\n// ============================================================================\n\n/**\n * Create a polling provider instance.\n */\nexport function createPollingProvider(\n  config: PollingProviderConfig\n): PollingProvider {\n  return new PollingProvider(config);\n}\n"],"names":["PollingProvider","config","key","f","id","s","listener","interval","jitter","previousFlags","newFlags","segments","error","previousMap","newMap","flag","previous","event","message","args","createPollingProvider"],"mappings":"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"}