{"version":3,"file":"persistent-offline-queue.mjs","sources":["../../../src/lib/services/persistent-offline-queue.ts"],"sourcesContent":["/**\n * @file Persistent Offline Queue\n * @description IndexedDB-backed queue for offline request persistence\n *\n * This module provides a robust offline queue that persists requests to\n * IndexedDB, ensuring they survive page refreshes and are automatically\n * processed when the network becomes available.\n */\n\nimport { networkMonitor } from '../utils/networkStatus';\nimport { ErrorReporter } from '../monitoring/ErrorReporter';\nimport { globalEventBus } from '../shared/event-utils';\n\n// ============================================================================\n// Types\n// ============================================================================\n\n/**\n * Queue item status\n */\nexport type QueueItemStatus = 'pending' | 'processing' | 'completed' | 'failed' | 'expired';\n\n/**\n * Serialized request for storage\n */\nexport interface QueuedRequest {\n  /** Unique request ID */\n  id: string;\n  /** Request URL */\n  url: string;\n  /** HTTP method */\n  method: string;\n  /** Request headers */\n  headers: Record<string, string>;\n  /** Request body (serialized) */\n  body: string | null;\n  /** Creation timestamp */\n  createdAt: number;\n  /** Expiration timestamp */\n  expiresAt: number;\n  /** Request priority (higher = more important) */\n  priority: number;\n  /** Current retry count */\n  retryCount: number;\n  /** Maximum retries allowed */\n  maxRetries: number;\n  /** Current status */\n  status: QueueItemStatus;\n  /** Last error message if failed */\n  lastError?: string | undefined;\n  /** Additional metadata */\n  metadata?: Record<string, unknown> | undefined;\n}\n\n/**\n * Queue options\n */\nexport interface OfflineQueueOptions {\n  /** Database name */\n  dbName?: string;\n  /** Store name */\n  storeName?: string;\n  /** Default expiration time in ms (default: 24 hours) */\n  defaultExpiration?: number;\n  /** Maximum queue size */\n  maxQueueSize?: number;\n  /** Maximum retries per request */\n  maxRetries?: number;\n  /** Base retry delay in ms */\n  retryDelayBase?: number;\n  /** Number of requests to process at once */\n  batchSize?: number;\n  /** Automatically process queue when online */\n  autoProcess?: boolean;\n}\n\n/**\n * Enqueue options\n */\nexport interface EnqueueOptions {\n  /** Request priority (default: 0) */\n  priority?: number;\n  /** Custom expiration time in ms */\n  expiration?: number;\n  /** Custom max retries */\n  maxRetries?: number;\n  /** Additional metadata to store */\n  metadata?: Record<string, unknown>;\n}\n\n/**\n * Queue statistics\n */\nexport interface QueueStats {\n  pending: number;\n  processing: number;\n  completed: number;\n  failed: number;\n  total: number;\n}\n\n/**\n * Queue events for the event bus\n */\nexport interface OfflineQueueEvents {\n  'offlineQueue:enqueued': { id: string; url: string };\n  'offlineQueue:completed': { id: string; url: string };\n  'offlineQueue:failed': { id: string; url: string; error: string; retriesExhausted?: boolean };\n  'offlineQueue:expired': { id: string; url: string };\n  'offlineQueue:processing': { count: number };\n}\n\n// Extend global events\n// declare module '../utils/eventEmitter' {\n//   // eslint-disable-next-line @typescript-eslint/no-empty-object-type\n//   interface GlobalEvents extends OfflineQueueEvents {}\n// }\n\n// ============================================================================\n// IndexedDB Wrapper\n// ============================================================================\n\n/**\n * Simple IndexedDB wrapper for the offline queue\n */\nclass QueueDatabase {\n  private readonly dbName: string;\n  private readonly storeName: string;\n  private db: IDBDatabase | null = null;\n  private dbVersion = 1;\n\n  constructor(dbName: string, storeName: string) {\n    this.dbName = dbName;\n    this.storeName = storeName;\n  }\n\n  /**\n   * Open the database\n   */\n  async open(): Promise<void> {\n    if (this.db) return;\n\n    return new Promise((resolve, reject) => {\n      const request = indexedDB.open(this.dbName, this.dbVersion);\n\n      request.onerror = () => {\n        reject(new Error(`Failed to open database: ${request.error?.message}`));\n      };\n\n      request.onsuccess = () => {\n        this.db = request.result;\n        resolve();\n      };\n\n      request.onupgradeneeded = (event) => {\n        const db = (event.target as IDBOpenDBRequest).result;\n\n        if (!db.objectStoreNames.contains(this.storeName)) {\n          const store = db.createObjectStore(this.storeName, { keyPath: 'id' });\n          store.createIndex('status', 'status', { unique: false });\n          store.createIndex('createdAt', 'createdAt', { unique: false });\n          store.createIndex('priority', 'priority', { unique: false });\n          store.createIndex('expiresAt', 'expiresAt', { unique: false });\n        }\n      };\n    });\n  }\n\n  /**\n   * Close the database\n   */\n  close(): void {\n    if (this.db) {\n      this.db.close();\n      this.db = null;\n    }\n  }\n\n  /**\n   * Add an item\n   */\n  async add(item: QueuedRequest): Promise<void> {\n    return new Promise((resolve, reject) => {\n      const store = this.getStore('readwrite');\n      const request = store.add(item);\n      request.onsuccess = () => resolve();\n      request.onerror = () => reject(new Error(request.error?.message ?? 'Failed to add item'));\n    });\n  }\n\n  /**\n   * Update an item\n   */\n  async put(item: QueuedRequest): Promise<void> {\n    return new Promise((resolve, reject) => {\n      const store = this.getStore('readwrite');\n      const request = store.put(item);\n      request.onsuccess = () => resolve();\n      request.onerror = () => reject(new Error(request.error?.message ?? 'Failed to put item'));\n    });\n  }\n\n  /**\n   * Get an item by ID\n   */\n  async get(id: string): Promise<QueuedRequest | undefined> {\n    return new Promise((resolve, reject) => {\n      const store = this.getStore('readonly');\n      const request = store.get(id);\n      request.onsuccess = () => resolve(request.result as QueuedRequest | undefined);\n      request.onerror = () => reject(new Error(request.error?.message ?? 'Failed to get item'));\n    });\n  }\n\n  /**\n   * Delete an item\n   */\n  async delete(id: string): Promise<void> {\n    return new Promise((resolve, reject) => {\n      const store = this.getStore('readwrite');\n      const request = store.delete(id);\n      request.onsuccess = () => resolve();\n      request.onerror = () => reject(new Error(request.error?.message ?? 'Failed to delete item'));\n    });\n  }\n\n  /**\n   * Get all items\n   */\n  async getAll(): Promise<QueuedRequest[]> {\n    return new Promise((resolve, reject) => {\n      const store = this.getStore('readonly');\n      const request = store.getAll();\n      request.onsuccess = () => resolve(request.result as QueuedRequest[]);\n      request.onerror = () => reject(new Error(request.error?.message ?? 'Failed to get all items'));\n    });\n  }\n\n  /**\n   * Get items by status\n   */\n  async getByStatus(status: QueueItemStatus, limit?: number): Promise<QueuedRequest[]> {\n    return new Promise((resolve, reject) => {\n      const store = this.getStore('readonly');\n      const index = store.index('status');\n      const request = index.getAll(status, limit);\n      request.onsuccess = () => resolve(request.result as QueuedRequest[]);\n      request.onerror = () => reject(new Error(request.error?.message ?? 'Failed to get by status'));\n    });\n  }\n\n  /**\n   * Count items by status\n   */\n  async countByStatus(status: QueueItemStatus): Promise<number> {\n    return new Promise((resolve, reject) => {\n      const store = this.getStore('readonly');\n      const index = store.index('status');\n      const request = index.count(status);\n      request.onsuccess = () => resolve(request.result);\n      request.onerror = () => reject(new Error(request.error?.message ?? 'Failed to count by status'));\n    });\n  }\n\n  /**\n   * Count all items\n   */\n  async count(): Promise<number> {\n    return new Promise((resolve, reject) => {\n      const store = this.getStore('readonly');\n      const request = store.count();\n      request.onsuccess = () => resolve(request.result);\n      request.onerror = () => reject(new Error(request.error?.message ?? 'Failed to count'));\n    });\n  }\n\n  /**\n   * Clear all items\n   */\n  async clear(): Promise<void> {\n    return new Promise((resolve, reject) => {\n      const store = this.getStore('readwrite');\n      const request = store.clear();\n      request.onsuccess = () => resolve();\n      request.onerror = () => reject(new Error(request.error?.message ?? 'Failed to clear'));\n    });\n  }\n\n  /**\n   * Delete expired items\n   */\n  async deleteExpired(now: number): Promise<number> {\n    const items = await this.getAll();\n    const expired = items.filter((item) => item.expiresAt < now);\n\n    for (const item of expired) {\n      await this.delete(item.id);\n      globalEventBus.emitSync('offlineQueue:expired', {\n        id: item.id,\n        url: item.url,\n      });\n    }\n\n    return expired.length;\n  }\n\n  /**\n   * Get transaction store\n   */\n  private getStore(mode: IDBTransactionMode): IDBObjectStore {\n    if (!this.db) {\n      throw new Error('Database not open');\n    }\n    const transaction = this.db.transaction(this.storeName, mode);\n    return transaction.objectStore(this.storeName);\n  }\n}\n\n// ============================================================================\n// Persistent Offline Queue\n// ============================================================================\n\nconst DEFAULT_OPTIONS: Required<OfflineQueueOptions> = {\n  dbName: 'offline-queue',\n  storeName: 'requests',\n  defaultExpiration: 24 * 60 * 60 * 1000, // 24 hours\n  maxQueueSize: 1000,\n  maxRetries: 5,\n  retryDelayBase: 1000,\n  batchSize: 10,\n  autoProcess: true,\n};\n\n/**\n * Persistent offline queue backed by IndexedDB\n *\n * @example\n * ```tsx\n * const queue = new PersistentOfflineQueue();\n * await queue.init();\n *\n * // Enqueue a request when offline\n * const request = new Request('/api/data', {\n *   method: 'POST',\n *   body: JSON.stringify(data)\n * });\n * await queue.enqueue(request, { priority: 1 });\n *\n * // Queue is automatically processed when online\n * ```\n */\nexport class PersistentOfflineQueue {\n  private options: Required<OfflineQueueOptions>;\n  private db: QueueDatabase | null = null;\n  private isProcessing = false;\n  private unsubscribe: (() => void) | null = null;\n  private initialized = false;\n\n  constructor(options: OfflineQueueOptions = {}) {\n    this.options = { ...DEFAULT_OPTIONS, ...options };\n  }\n\n  /**\n   * Initialize the queue (must be called before use)\n   */\n  async init(): Promise<void> {\n    if (this.initialized) return;\n\n    this.db = new QueueDatabase(this.options.dbName, this.options.storeName);\n    await this.db.open();\n    this.initialized = true;\n\n    // Listen for online events if autoProcess is enabled\n    if (this.options.autoProcess) {\n      this.unsubscribe = networkMonitor.onStatusChange((status) => {\n        if (status.online) {\n          void this.processQueue();\n        }\n      });\n    }\n\n    // Clean expired items on init\n    await this.cleanExpired();\n\n    // Process queue if we're online\n    if (networkMonitor.isOnline() && this.options.autoProcess) {\n      void this.processQueue();\n    }\n  }\n\n  /**\n   * Add a request to the queue\n   *\n   * @param request - Request to queue\n   * @param options - Enqueue options\n   * @returns The queued request ID\n   */\n  async enqueue(request: Request, options?: EnqueueOptions): Promise<string> {\n    const db = this.getDb();\n\n    // Check queue size\n    const count = await db.count();\n    if (count >= this.options.maxQueueSize) {\n      throw new Error('Offline queue is full');\n    }\n\n    const id = crypto.randomUUID();\n    const now = Date.now();\n\n    // Clone and serialize request\n    const clonedRequest = request.clone();\n    let body: string | null = null;\n\n    if (request.body) {\n      body = await clonedRequest.text();\n    }\n\n    const item: QueuedRequest = {\n      id,\n      url: request.url,\n      method: request.method,\n      headers: Object.fromEntries(request.headers.entries()),\n      body,\n      createdAt: now,\n      expiresAt: now + (options?.expiration ?? this.options.defaultExpiration),\n      priority: options?.priority ?? 0,\n      retryCount: 0,\n      maxRetries: options?.maxRetries ?? this.options.maxRetries,\n      status: 'pending',\n      metadata: options?.metadata,\n    };\n\n    await db.add(item);\n\n    globalEventBus.emitSync('offlineQueue:enqueued', { id, url: item.url });\n\n    // Try processing if online\n    if (networkMonitor.isOnline() && this.options.autoProcess) {\n      void this.processQueue();\n    }\n\n    return id;\n  }\n\n  /**\n   * Process pending requests in the queue\n   */\n  async processQueue(): Promise<void> {\n    const db = this.getDb();\n\n    if (this.isProcessing || !networkMonitor.isOnline()) {\n      return;\n    }\n\n    this.isProcessing = true;\n\n    try {\n      // Clean expired first\n      await this.cleanExpired();\n\n      // Get pending items sorted by priority\n      const pending = await db.getByStatus('pending', this.options.batchSize);\n\n      // Sort by priority (higher first), then by creation time\n      pending.sort((a, b) => {\n        if (b.priority !== a.priority) {\n          return b.priority - a.priority;\n        }\n        return a.createdAt - b.createdAt;\n      });\n\n      if (pending.length > 0) {\n        globalEventBus.emitSync('offlineQueue:processing', { count: pending.length });\n      }\n\n      for (const item of pending) {\n        if (!networkMonitor.isOnline()) break;\n\n        await this.processItem(item);\n      }\n    } finally {\n      this.isProcessing = false;\n    }\n\n    // Check for more pending items\n    const remaining = await this.getPendingCount();\n    if (remaining > 0 && networkMonitor.isOnline()) {\n      // Schedule next batch with a small delay\n      setTimeout(() => void this.processQueue(), 100);\n    }\n  }\n\n  /**\n   * Get count of pending items\n   */\n  async getPendingCount(): Promise<number> {\n    return this.getDb().countByStatus('pending');\n  }\n\n  /**\n   * Get all items in the queue\n   */\n  async getAll(): Promise<QueuedRequest[]> {\n    return this.getDb().getAll();\n  }\n\n  /**\n   * Get queue statistics\n   */\n  async getStats(): Promise<QueueStats> {\n    const db = this.getDb();\n\n    const [pending, processing, completed, failed] = await Promise.all([\n      db.countByStatus('pending'),\n      db.countByStatus('processing'),\n      db.countByStatus('completed'),\n      db.countByStatus('failed'),\n    ]);\n\n    return {\n      pending,\n      processing,\n      completed,\n      failed,\n      total: pending + processing + completed + failed,\n    };\n  }\n\n  /**\n   * Remove an item from the queue\n   */\n  async remove(id: string): Promise<void> {\n    await this.getDb().delete(id);\n  }\n\n  /**\n   * Clear all items from the queue\n   */\n  async clear(): Promise<void> {\n    await this.getDb().clear();\n  }\n\n  /**\n   * Retry a failed item\n   */\n  async retryFailed(id: string): Promise<void> {\n    const db = this.getDb();\n\n    const item = await db.get(id);\n    if (item?.status === 'failed') {\n      await db.put({\n        ...item,\n        status: 'pending',\n        retryCount: 0,\n        expiresAt: Date.now() + this.options.defaultExpiration,\n      });\n\n      if (networkMonitor.isOnline()) {\n        void this.processQueue();\n      }\n    }\n  }\n\n  /**\n   * Retry all failed items\n   */\n  async retryAllFailed(): Promise<number> {\n    const db = this.getDb();\n\n    const failed = await db.getByStatus('failed');\n    const now = Date.now();\n\n    for (const item of failed) {\n      await db.put({\n        ...item,\n        status: 'pending',\n        retryCount: 0,\n        expiresAt: now + this.options.defaultExpiration,\n      });\n    }\n\n    if (networkMonitor.isOnline() && failed.length > 0) {\n      void this.processQueue();\n    }\n\n    return failed.length;\n  }\n\n  /**\n   * Check if the queue is empty\n   */\n  async isEmpty(): Promise<boolean> {\n    const count = await this.getDb().count();\n    return count === 0;\n  }\n\n  /**\n   * Check if queue is currently processing\n   */\n  isQueueProcessing(): boolean {\n    return this.isProcessing;\n  }\n\n  /**\n   * Force process the queue (even if already processing)\n   */\n  async forceProcess(): Promise<void> {\n    this.isProcessing = false;\n    await this.processQueue();\n  }\n\n  /**\n   * Dispose resources\n   */\n  dispose(): void {\n    this.unsubscribe?.();\n    this.db?.close();\n    this.db = null;\n    this.initialized = false;\n  }\n\n  /**\n   * Ensure the queue is initialized\n   */\n  private ensureInitialized(): void {\n    if (!this.initialized || !this.db) {\n      throw new Error('Queue not initialized. Call init() first.');\n    }\n  }\n\n  /**\n   * Get the database instance (throws if not initialized)\n   */\n  private getDb(): QueueDatabase {\n    this.ensureInitialized();\n    return this.db as QueueDatabase;\n  }\n\n  /**\n   * Process a single queue item\n   *\n   * Note: This method intentionally uses raw fetch() rather than apiClient because:\n   * 1. Queued requests store their own headers/method/body from the original request\n   * 2. The offline queue replays exact requests that were made when offline\n   * 3. Using apiClient would re-apply interceptors/auth that may have changed\n   *\n   * @see {@link @/lib/api/api-client} for making new API calls\n   */\n  private async processItem(item: QueuedRequest): Promise<void> {\n    this.ensureInitialized();\n\n    // Mark as processing\n    await this.updateStatus(item.id, 'processing');\n\n    try {\n      // Raw fetch is intentional - replay exact request stored in queue\n      const response = await fetch(item.url, {\n        method: item.method,\n        headers: item.headers,\n        body: item.body ?? null,\n      });\n\n      if (response.ok) {\n        await this.updateStatus(item.id, 'completed');\n        globalEventBus.emitSync('offlineQueue:completed', {\n          id: item.id,\n          url: item.url,\n          response: response.status,\n        });\n      } else if (response.status >= 400 && response.status < 500) {\n        // Client error - don't retry\n        await this.updateStatus(item.id, 'failed', `HTTP ${response.status}`);\n        globalEventBus.emitSync('offlineQueue:failed', {\n          id: item.id,\n          url: item.url,\n          error: new Error(`HTTP ${response.status}`),\n        });\n      } else {\n        // Server error - retry\n        await this.handleRetry(item, `HTTP ${response.status}`);\n      }\n    } catch (error) {\n      const errorMessage = error instanceof Error ? error.message : String(error);\n      await this.handleRetry(item, errorMessage);\n    }\n  }\n\n  /**\n   * Handle retry logic for failed requests\n   */\n  private async handleRetry(item: QueuedRequest, error: string): Promise<void> {\n    this.ensureInitialized();\n\n    const newRetryCount = item.retryCount + 1;\n\n    if (newRetryCount >= item.maxRetries) {\n      await this.updateStatus(item.id, 'failed', error);\n\n      globalEventBus.emitSync('offlineQueue:failed', {\n        id: item.id,\n        url: item.url,\n        error: new Error(error),\n        // retriesExhausted: true,\n      });\n\n      // Report to error monitoring\n      ErrorReporter.reportError(new Error(`Offline request failed: ${error}`), {\n        action: 'offline_queue_failed',\n        metadata: {\n          id: item.id,\n          url: item.url,\n          method: item.method,\n          retryCount: item.retryCount,\n          createdAt: new Date(item.createdAt).toISOString(),\n        },\n      });\n    } else {\n      // Update retry count and reset to pending\n      const updatedItem: QueuedRequest = {\n        ...item,\n        retryCount: newRetryCount,\n        status: 'pending',\n        lastError: error,\n      };\n      await this.getDb().put(updatedItem);\n\n      // Schedule retry with exponential backoff\n      const delay = this.options.retryDelayBase * Math.pow(2, newRetryCount);\n      setTimeout(() => void this.processQueue(), delay);\n    }\n  }\n\n  /**\n   * Update item status\n   */\n  private async updateStatus(\n    id: string,\n    status: QueueItemStatus,\n    error?: string\n  ): Promise<void> {\n    const db = this.getDb();\n\n    const item = await db.get(id);\n    if (item) {\n      await db.put({\n        ...item,\n        status,\n        ...(error !== undefined && { lastError: error }),\n      });\n    }\n  }\n\n  /**\n   * Clean expired items\n   */\n  private async cleanExpired(): Promise<void> {\n    await this.getDb().deleteExpired(Date.now());\n  }\n}\n\n/**\n * Global offline queue instance\n */\nexport const offlineQueue = new PersistentOfflineQueue();\n"],"names":["QueueDatabase","dbName","storeName","resolve","reject","request","event","db","store","item","id","status","limit","now","expired","globalEventBus","mode","DEFAULT_OPTIONS","PersistentOfflineQueue","options","networkMonitor","clonedRequest","body","pending","a","b","processing","completed","failed","response","error","errorMessage","newRetryCount","ErrorReporter","updatedItem","delay","offlineQueue"],"mappings":"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"}