@pol-studios/powersync 1.0.0

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (64) hide show
  1. package/dist/attachments/index.d.ts +399 -0
  2. package/dist/attachments/index.js +16 -0
  3. package/dist/attachments/index.js.map +1 -0
  4. package/dist/chunk-32OLICZO.js +1 -0
  5. package/dist/chunk-32OLICZO.js.map +1 -0
  6. package/dist/chunk-4FJVBR3X.js +227 -0
  7. package/dist/chunk-4FJVBR3X.js.map +1 -0
  8. package/dist/chunk-7BPTGEVG.js +1 -0
  9. package/dist/chunk-7BPTGEVG.js.map +1 -0
  10. package/dist/chunk-7JQZBZ5N.js +1 -0
  11. package/dist/chunk-7JQZBZ5N.js.map +1 -0
  12. package/dist/chunk-BJ36QDFN.js +290 -0
  13. package/dist/chunk-BJ36QDFN.js.map +1 -0
  14. package/dist/chunk-CFCK2LHI.js +1002 -0
  15. package/dist/chunk-CFCK2LHI.js.map +1 -0
  16. package/dist/chunk-CHRTN5PF.js +322 -0
  17. package/dist/chunk-CHRTN5PF.js.map +1 -0
  18. package/dist/chunk-FLHDT4TS.js +327 -0
  19. package/dist/chunk-FLHDT4TS.js.map +1 -0
  20. package/dist/chunk-GBGATW2S.js +749 -0
  21. package/dist/chunk-GBGATW2S.js.map +1 -0
  22. package/dist/chunk-NPNBGCRC.js +65 -0
  23. package/dist/chunk-NPNBGCRC.js.map +1 -0
  24. package/dist/chunk-Q3LFFMRR.js +925 -0
  25. package/dist/chunk-Q3LFFMRR.js.map +1 -0
  26. package/dist/chunk-T225XEML.js +298 -0
  27. package/dist/chunk-T225XEML.js.map +1 -0
  28. package/dist/chunk-W7HSR35B.js +1 -0
  29. package/dist/chunk-W7HSR35B.js.map +1 -0
  30. package/dist/connector/index.d.ts +5 -0
  31. package/dist/connector/index.js +14 -0
  32. package/dist/connector/index.js.map +1 -0
  33. package/dist/core/index.d.ts +197 -0
  34. package/dist/core/index.js +96 -0
  35. package/dist/core/index.js.map +1 -0
  36. package/dist/index-nae7nzib.d.ts +147 -0
  37. package/dist/index.d.ts +68 -0
  38. package/dist/index.js +191 -0
  39. package/dist/index.js.map +1 -0
  40. package/dist/index.native.d.ts +14 -0
  41. package/dist/index.native.js +195 -0
  42. package/dist/index.native.js.map +1 -0
  43. package/dist/index.web.d.ts +14 -0
  44. package/dist/index.web.js +195 -0
  45. package/dist/index.web.js.map +1 -0
  46. package/dist/platform/index.d.ts +280 -0
  47. package/dist/platform/index.js +14 -0
  48. package/dist/platform/index.js.map +1 -0
  49. package/dist/platform/index.native.d.ts +37 -0
  50. package/dist/platform/index.native.js +7 -0
  51. package/dist/platform/index.native.js.map +1 -0
  52. package/dist/platform/index.web.d.ts +37 -0
  53. package/dist/platform/index.web.js +7 -0
  54. package/dist/platform/index.web.js.map +1 -0
  55. package/dist/provider/index.d.ts +873 -0
  56. package/dist/provider/index.js +63 -0
  57. package/dist/provider/index.js.map +1 -0
  58. package/dist/supabase-connector-D14-kl5v.d.ts +232 -0
  59. package/dist/sync/index.d.ts +421 -0
  60. package/dist/sync/index.js +14 -0
  61. package/dist/sync/index.js.map +1 -0
  62. package/dist/types-Cd7RhNqf.d.ts +224 -0
  63. package/dist/types-afHtE1U_.d.ts +391 -0
  64. package/package.json +101 -0
@@ -0,0 +1 @@
1
+ {"version":3,"sources":["../src/provider/types.ts","../src/sync/status-tracker.ts","../src/sync/metrics-collector.ts","../src/sync/health-monitor.ts"],"sourcesContent":["/**\n * Provider Types for @pol-studios/powersync\n *\n * Defines configuration and context interfaces for the PowerSyncProvider.\n */\n\nimport type { SupabaseClient } from '@supabase/supabase-js';\nimport type { QueryClient } from '@tanstack/react-query';\nimport type { AbstractPowerSyncDatabase, SyncStatus, ConnectionHealth, SyncMetrics, CrudEntry, FailedTransaction, CompletedTransaction, SyncMode } from '../core/types';\nimport type { PlatformAdapter } from '../platform/types';\nimport type { ConnectorConfig } from '../connector/types';\nimport type { AttachmentQueueConfig } from '../attachments/types';\nimport type { AttachmentQueue } from '../attachments/attachment-queue';\nimport type { SupabaseConnector } from '../connector/supabase-connector';\n\n// ─── Provider Configuration ──────────────────────────────────────────────────\n\n/**\n * Main configuration for PowerSyncProvider.\n *\n * @template TSchema - The PowerSync schema type\n *\n * @example\n * ```typescript\n * const config: PowerSyncConfig<AppSchema> = {\n * platform: createNativePlatformAdapter(logger),\n * schema: AppSchema,\n * powerSyncUrl: 'https://your-powersync-instance.com',\n * supabaseClient: supabase,\n * dbFilename: 'myapp.db',\n * sync: {\n * autoConnect: true,\n * },\n * };\n * ```\n */\nexport interface PowerSyncConfig<TSchema = unknown> {\n /**\n * Platform adapter for platform-specific operations.\n * Use createNativePlatformAdapter() for React Native or createWebPlatformAdapter() for Web.\n */\n platform: PlatformAdapter;\n\n /**\n * PowerSync schema definition.\n * This defines the tables and their structure for the local database.\n */\n schema: TSchema;\n\n /**\n * PowerSync service URL.\n * @example \"https://your-instance.powersync.journeyapps.com\"\n */\n powerSyncUrl: string;\n\n /**\n * Supabase client instance.\n * Used for authentication and as the backend for CRUD uploads.\n */\n supabaseClient: SupabaseClient;\n\n /**\n * Optional: TanStack Query client for cache invalidation.\n * If provided, will invalidate queries when sync completes.\n */\n queryClient?: QueryClient;\n\n /**\n * Optional: Database filename.\n * @default \"powersync.db\"\n */\n dbFilename?: string;\n\n /**\n * Optional: Connector configuration for custom CRUD handling.\n */\n connector?: ConnectorConfig;\n\n /**\n * Optional: Attachment queue configuration for offline file caching.\n */\n attachments?: AttachmentQueueConfig;\n\n /**\n * Optional: Sync behavior configuration.\n */\n sync?: SyncConfig;\n}\n\n/**\n * Sync behavior configuration.\n */\nexport interface SyncConfig {\n /**\n * Automatically connect when the provider mounts and there's an authenticated session.\n * @default true\n */\n autoConnect?: boolean;\n\n /**\n * Sync interval for periodic sync checks (in milliseconds).\n * Set to 0 to disable periodic sync.\n * @default 0 (disabled)\n */\n syncInterval?: number;\n\n /**\n * Enable health monitoring.\n * @default true\n */\n enableHealthMonitoring?: boolean;\n\n /**\n * Enable metrics collection.\n * @default true\n */\n enableMetrics?: boolean;\n}\n\n// ─── Context Types ───────────────────────────────────────────────────────────\n\n/**\n * Value provided by the main PowerSyncContext.\n *\n * @template TSchema - The PowerSync schema type\n */\nexport interface PowerSyncContextValue<TSchema = unknown> {\n /**\n * The PowerSync database instance.\n * Will be null if not initialized or if initialization failed.\n */\n db: AbstractPowerSyncDatabase | null;\n\n /**\n * The Supabase connector instance.\n * Will be null if not initialized.\n */\n connector: SupabaseConnector | null;\n\n /**\n * The attachment queue instance.\n * Will be null if attachments are not configured or not initialized.\n */\n attachmentQueue: AttachmentQueue | null;\n\n /**\n * Whether the PowerSync database is ready for use.\n */\n isReady: boolean;\n\n /**\n * Whether the provider is currently initializing.\n */\n isInitializing: boolean;\n\n /**\n * Error that occurred during initialization, if any.\n */\n error: Error | null;\n\n /**\n * The schema used for this database.\n */\n schema: TSchema;\n\n /**\n * The platform adapter instance.\n */\n platform: PlatformAdapter;\n}\n\n/**\n * Value provided by SyncStatusContext.\n */\nexport interface SyncStatusContextValue {\n /**\n * Current sync status.\n */\n status: SyncStatus;\n\n /**\n * Pending mutations waiting to be uploaded.\n */\n pendingMutations: CrudEntry[];\n\n /**\n * Number of pending mutations.\n */\n pendingCount: number;\n\n /**\n * Whether data is currently being uploaded to the server.\n * This is the authoritative source for upload activity.\n */\n isUploading: boolean;\n\n /**\n * Whether data is currently being downloaded from the server.\n * This is the authoritative source for download activity.\n */\n isDownloading: boolean;\n\n /**\n * Whether sync is currently paused (offline mode).\n */\n isPaused: boolean;\n\n /**\n * Current sync mode: 'push-pull' (full sync), 'pull-only' (download only), or 'offline' (no sync).\n */\n syncMode: SyncMode;\n\n /**\n * Timestamp of the last successful sync.\n */\n lastSyncedAt: Date | null;\n\n /**\n * Error that occurred during connection, if any.\n */\n connectionError?: Error | null;\n\n /**\n * Failed transactions that need attention.\n */\n failedTransactions: FailedTransaction[];\n\n /**\n * Whether there are any upload errors.\n */\n hasUploadErrors: boolean;\n\n /**\n * Count of permanent errors that need user action.\n */\n permanentErrorCount: number;\n\n /**\n * Clear a specific failure by its ID.\n */\n clearFailure: (failureId: string) => void;\n\n /**\n * Clear all failures.\n */\n clearAllFailures: () => void;\n\n /**\n * Completed transactions history.\n */\n completedTransactions: CompletedTransaction[];\n\n /**\n * Clear the completed transaction history.\n */\n clearCompletedHistory: () => void;\n\n /**\n * Set the sync mode.\n * @param mode - The sync mode to set\n */\n setSyncMode: (mode: SyncMode) => Promise<void>;\n\n /**\n * Set the force next upload flag.\n * When true, the next sync cycle will upload regardless of sync mode.\n */\n setForceNextUpload: (force: boolean) => void;\n\n /**\n * Discard a specific pending mutation by its client ID.\n * Uses safe disconnect/reconnect pattern to avoid transaction conflicts.\n * @throws Error if upload is in progress\n */\n discardPendingMutation: (clientId: number) => Promise<void>;\n\n /**\n * Discard all pending mutations.\n * Uses safe disconnect/reconnect pattern to avoid transaction conflicts.\n * @throws Error if upload is in progress\n */\n discardAllPendingMutations: () => Promise<void>;\n}\n\n/**\n * Value provided by ConnectionHealthContext.\n */\nexport interface ConnectionHealthContextValue {\n /**\n * Current connection health status.\n */\n health: ConnectionHealth;\n}\n\n/**\n * Value provided by SyncMetricsContext.\n */\nexport interface SyncMetricsContextValue {\n /**\n * Current sync metrics.\n */\n metrics: SyncMetrics;\n}\n\n// ─── Provider Props ──────────────────────────────────────────────────────────\n\n/**\n * Props for the PowerSyncProvider component.\n *\n * @template TSchema - The PowerSync schema type\n */\nexport interface PowerSyncProviderProps<TSchema = unknown> {\n /**\n * PowerSync configuration.\n */\n config: PowerSyncConfig<TSchema>;\n\n /**\n * Child components to render.\n */\n children: React.ReactNode;\n\n /**\n * Called when the database is initialized and ready.\n */\n onReady?: () => void;\n\n /**\n * Called when an error occurs during initialization.\n */\n onError?: (error: Error) => void;\n\n /**\n * Called when sync status changes.\n */\n onSyncStatusChange?: (status: SyncStatus) => void;\n}\n\n// ─── Default Values ──────────────────────────────────────────────────────────\n\n/**\n * Default sync status.\n */\nexport const DEFAULT_SYNC_STATUS: SyncStatus = {\n connected: false,\n connecting: false,\n hasSynced: false,\n lastSyncedAt: null,\n uploading: false,\n downloading: false,\n downloadProgress: null,\n failedTransactions: [],\n hasUploadErrors: false,\n permanentErrorCount: 0,\n};\n\n/**\n * Default connection health.\n */\nexport const DEFAULT_CONNECTION_HEALTH: ConnectionHealth = {\n status: 'disconnected',\n latency: null,\n lastHealthCheck: null,\n consecutiveFailures: 0,\n reconnectAttempts: 0,\n};\n\n/**\n * Default sync metrics.\n */\nexport const DEFAULT_SYNC_METRICS: SyncMetrics = {\n totalSyncs: 0,\n successfulSyncs: 0,\n failedSyncs: 0,\n lastSyncDuration: null,\n averageSyncDuration: null,\n totalDataDownloaded: 0,\n totalDataUploaded: 0,\n lastError: null,\n};\n\n/**\n * Default sync configuration.\n */\nexport const DEFAULT_SYNC_CONFIG: Required<SyncConfig> = {\n autoConnect: true,\n syncInterval: 0,\n enableHealthMonitoring: true,\n enableMetrics: true,\n};\n","/**\n * Sync Status Tracker for @pol-studios/powersync\n *\n * Tracks and normalizes PowerSync status changes, providing a consistent\n * interface for status updates with throttling support.\n */\n\nimport type { SyncStatus, DownloadProgress, CrudEntry, FailedTransaction, SyncError, CompletedTransaction, SyncMode } from '../core/types';\nimport { generateFailureId } from '../core/errors';\nimport type { LoggerAdapter, AsyncStorageAdapter } from '../platform/types';\nimport type {\n SyncStatusState,\n SyncStatusTrackerOptions,\n PowerSyncRawStatus,\n Unsubscribe,\n} from './types';\nimport { STORAGE_KEY_PAUSED, STORAGE_KEY_SYNC_MODE, STATUS_NOTIFY_THROTTLE_MS } from '../core/constants';\nimport { DEFAULT_SYNC_STATUS } from '../provider/types';\n\n/**\n * Tracks sync status from PowerSync and provides normalized updates.\n *\n * Features:\n * - Normalizes raw PowerSync status to a consistent format\n * - Throttles notifications to prevent UI thrashing\n * - Tracks pending mutations count\n * - Persists and restores paused state\n *\n * @example\n * ```typescript\n * const tracker = new SyncStatusTracker({\n * storage,\n * logger,\n * onStatusChange: (status) => console.log('Status:', status),\n * });\n *\n * // Register with PowerSync\n * db.registerListener({\n * statusChanged: (rawStatus) => tracker.handleStatusChange(rawStatus),\n * });\n *\n * // Get current status\n * const status = tracker.getStatus();\n * ```\n */\nexport class SyncStatusTracker {\n private readonly storage: AsyncStorageAdapter;\n private readonly logger: LoggerAdapter;\n private readonly notifyThrottleMs: number;\n private readonly onStatusChange?: (status: SyncStatus) => void;\n\n private _state: SyncStatusState;\n private _pendingMutations: CrudEntry[] = [];\n private _lastNotifyTime = 0;\n private _notifyTimer: ReturnType<typeof setTimeout> | null = null;\n private _listeners = new Set<(status: SyncStatus) => void>();\n private _syncModeListeners = new Set<(mode: SyncMode) => void>();\n\n // Force next upload flag for \"Sync Now\" functionality\n private _forceNextUpload = false;\n\n // Track download progress separately to preserve it when offline\n private _lastProgress: DownloadProgress | null = null;\n\n // Failed transaction tracking\n private _failedTransactions: FailedTransaction[] = [];\n private readonly _maxStoredFailures = 50;\n private readonly _failureTTLMs = 24 * 60 * 60 * 1000; // 24 hours\n private _failureListeners = new Set<(failures: FailedTransaction[]) => void>();\n\n // Completed transaction tracking\n private _completedTransactions: CompletedTransaction[] = [];\n private readonly _maxCompletedHistory = 20;\n private _completedListeners = new Set<(completed: CompletedTransaction[]) => void>();\n\n constructor(\n storage: AsyncStorageAdapter,\n logger: LoggerAdapter,\n options: SyncStatusTrackerOptions = {}\n ) {\n this.storage = storage;\n this.logger = logger;\n this.notifyThrottleMs = options.notifyThrottleMs ?? STATUS_NOTIFY_THROTTLE_MS;\n this.onStatusChange = options.onStatusChange;\n\n this._state = {\n status: { ...DEFAULT_SYNC_STATUS },\n syncMode: 'push-pull',\n lastUpdated: new Date(),\n };\n }\n\n // ─── Initialization ────────────────────────────────────────────────────────\n\n /**\n * Initialize the tracker by loading persisted state.\n * Includes migration from old isPaused boolean to new syncMode.\n */\n async init(): Promise<void> {\n try {\n // Try to load new sync mode first\n const modeValue = await this.storage.getItem(STORAGE_KEY_SYNC_MODE);\n if (modeValue && ['push-pull', 'pull-only', 'offline'].includes(modeValue)) {\n this._state.syncMode = modeValue as SyncMode;\n this.logger.debug('[StatusTracker] Loaded sync mode:', this._state.syncMode);\n } else {\n // Migrate from old isPaused boolean\n const pausedValue = await this.storage.getItem(STORAGE_KEY_PAUSED);\n if (pausedValue === 'true') {\n this._state.syncMode = 'offline';\n await this.storage.setItem(STORAGE_KEY_SYNC_MODE, 'offline');\n this.logger.debug('[StatusTracker] Migrated isPaused=true to syncMode=offline');\n } else {\n this._state.syncMode = 'push-pull';\n }\n // Clean up old key\n await this.storage.removeItem(STORAGE_KEY_PAUSED);\n }\n } catch (err) {\n this.logger.warn('[StatusTracker] Failed to load sync mode:', err);\n }\n }\n\n /**\n * Dispose the tracker and clear timers.\n */\n dispose(): void {\n if (this._notifyTimer) {\n clearTimeout(this._notifyTimer);\n this._notifyTimer = null;\n }\n this._listeners.clear();\n this._syncModeListeners.clear();\n this._failureListeners.clear();\n this._completedListeners.clear();\n }\n\n // ─── Status Getters ────────────────────────────────────────────────────────\n\n /**\n * Get the current sync status.\n */\n getStatus(): SyncStatus {\n const baseStatus = this._state.status;\n\n // Build the status with failed transaction info\n const status: SyncStatus = {\n ...baseStatus,\n failedTransactions: this._failedTransactions,\n hasUploadErrors: this._failedTransactions.length > 0,\n permanentErrorCount: this._failedTransactions.filter(f => f.isPermanent).length,\n };\n\n // If offline, use saved progress instead of live (which would be null)\n if (this._state.syncMode === 'offline' && this._lastProgress) {\n return {\n ...status,\n downloadProgress: this._lastProgress,\n };\n }\n return status;\n }\n\n // ─── Sync Mode Getters ────────────────────────────────────────────────────────\n\n /**\n * Get the current sync mode.\n */\n getSyncMode(): SyncMode {\n return this._state.syncMode;\n }\n\n /**\n * Get whether sync is paused (offline mode).\n * @deprecated Use getSyncMode() instead\n */\n isPaused(): boolean {\n return this._state.syncMode === 'offline';\n }\n\n /**\n * Check if uploads are allowed based on current sync mode.\n */\n canUpload(): boolean {\n return this._state.syncMode === 'push-pull';\n }\n\n /**\n * Check if downloads are allowed based on current sync mode.\n */\n canDownload(): boolean {\n return this._state.syncMode !== 'offline';\n }\n\n /**\n * Set the force next upload flag for \"Sync Now\" functionality.\n */\n setForceNextUpload(force: boolean): void {\n this._forceNextUpload = force;\n this.logger.debug('[StatusTracker] Force next upload set to:', force);\n }\n\n /**\n * Clear the force next upload flag.\n * Should be called after all pending uploads have been processed.\n */\n clearForceNextUpload(): void {\n if (this._forceNextUpload) {\n this._forceNextUpload = false;\n this.logger.debug('[StatusTracker] Force next upload flag cleared');\n }\n }\n\n /**\n * Check if upload should proceed, considering force flag.\n * NOTE: Does NOT auto-reset the flag - caller must use clearForceNextUpload()\n * after all uploads are complete. This prevents race conditions when\n * PowerSync calls uploadData() multiple times for multiple transactions.\n */\n shouldUpload(): boolean {\n // Return true if force flag is set OR if mode is push-pull\n if (this._forceNextUpload) {\n return true;\n }\n return this._state.syncMode === 'push-pull';\n }\n\n /**\n * Get pending mutations.\n */\n getPendingMutations(): CrudEntry[] {\n return this._pendingMutations;\n }\n\n /**\n * Get pending mutation count.\n */\n getPendingCount(): number {\n return this._pendingMutations.length;\n }\n\n // ─── Status Updates ────────────────────────────────────────────────────────\n\n /**\n * Handle a raw status update from PowerSync.\n */\n handleStatusChange(rawStatus: PowerSyncRawStatus): void {\n const progress = rawStatus.downloadProgress;\n const dataFlow = rawStatus.dataFlowStatus;\n\n // Build normalized download progress\n let downloadProgress: DownloadProgress | null = null;\n if (progress && progress.totalOperations && progress.totalOperations > 0) {\n downloadProgress = {\n current: progress.downloadedOperations ?? 0,\n target: progress.totalOperations,\n percentage: Math.round((progress.downloadedFraction ?? 0) * 100),\n };\n // Save progress for when paused\n this._lastProgress = downloadProgress;\n }\n\n // Build normalized status (failed transaction fields are added in getStatus())\n const newStatus: SyncStatus = {\n connected: rawStatus.connected ?? false,\n connecting: rawStatus.connecting ?? false,\n hasSynced: rawStatus.hasSynced ?? false,\n lastSyncedAt: rawStatus.lastSyncedAt ?? null,\n uploading: dataFlow?.uploading ?? false,\n downloading: dataFlow?.downloading ?? false,\n downloadProgress,\n // These are computed from _failedTransactions in getStatus()\n failedTransactions: [],\n hasUploadErrors: false,\n permanentErrorCount: 0,\n };\n\n // Check if status actually changed\n const changed = this._hasStatusChanged(newStatus);\n\n this._state = {\n status: newStatus,\n syncMode: this._state.syncMode,\n lastUpdated: new Date(),\n };\n\n if (changed) {\n this._notifyListeners();\n }\n }\n\n /**\n * Update pending mutations from a CRUD transaction.\n */\n updatePendingMutations(mutations: CrudEntry[]): void {\n this._pendingMutations = mutations;\n }\n\n /**\n * Valid sync modes for runtime validation.\n */\n private static readonly VALID_SYNC_MODES: SyncMode[] = ['push-pull', 'pull-only', 'offline'];\n\n /**\n * Set the sync mode.\n */\n async setSyncMode(mode: SyncMode): Promise<void> {\n // Runtime validation\n if (!SyncStatusTracker.VALID_SYNC_MODES.includes(mode)) {\n this.logger.warn('[StatusTracker] Invalid sync mode, ignoring:', mode);\n return;\n }\n\n if (this._state.syncMode === mode) return;\n\n const previousMode = this._state.syncMode;\n this._state.syncMode = mode;\n\n // Persist to storage\n try {\n await this.storage.setItem(STORAGE_KEY_SYNC_MODE, mode);\n this.logger.info('[StatusTracker] Sync mode changed:', previousMode, '->', mode);\n } catch (err) {\n this.logger.warn('[StatusTracker] Failed to persist sync mode:', err);\n }\n\n // Notify sync mode listeners\n for (const listener of this._syncModeListeners) {\n try {\n listener(mode);\n } catch (err) {\n this.logger.warn('[StatusTracker] Sync mode listener error:', err);\n }\n }\n\n this._notifyListeners(true);\n }\n\n /**\n * Set paused state.\n * @deprecated Use setSyncMode() instead\n */\n async setPaused(paused: boolean): Promise<void> {\n await this.setSyncMode(paused ? 'offline' : 'push-pull');\n }\n\n // ─── Subscriptions ─────────────────────────────────────────────────────────\n\n /**\n * Subscribe to status changes.\n * @returns Unsubscribe function\n */\n onStatusUpdate(listener: (status: SyncStatus) => void): Unsubscribe {\n this._listeners.add(listener);\n // Immediately call with current status\n listener(this.getStatus());\n return () => {\n this._listeners.delete(listener);\n };\n }\n\n /**\n * Subscribe to sync mode changes.\n * @returns Unsubscribe function\n */\n onSyncModeChange(listener: (mode: SyncMode) => void): Unsubscribe {\n this._syncModeListeners.add(listener);\n listener(this._state.syncMode);\n return () => {\n this._syncModeListeners.delete(listener);\n };\n }\n\n /**\n * Subscribe to paused state changes.\n * @deprecated Use onSyncModeChange() instead\n * @returns Unsubscribe function\n */\n onPausedChange(listener: (isPaused: boolean) => void): Unsubscribe {\n // Wrap the listener to convert SyncMode to isPaused boolean\n const wrappedListener = (mode: SyncMode) => {\n listener(mode === 'offline');\n };\n this._syncModeListeners.add(wrappedListener);\n listener(this._state.syncMode === 'offline');\n return () => {\n this._syncModeListeners.delete(wrappedListener);\n };\n }\n\n // ─── Failed Transaction Tracking ────────────────────────────────────────────\n\n /**\n * Record a transaction failure.\n * If a failure for the same entries already exists, updates the retry count.\n * Otherwise, creates a new failure record.\n */\n recordTransactionFailure(\n entries: CrudEntry[],\n error: SyncError,\n isPermanent: boolean,\n affectedEntityIds: string[],\n affectedTables: string[]\n ): void {\n const now = new Date();\n const entryIds = entries.map(e => e.id).sort().join(',');\n\n // Check if a failure for these entries already exists\n const existingIndex = this._failedTransactions.findIndex(f => {\n const existingIds = f.entries.map(e => e.id).sort().join(',');\n return existingIds === entryIds;\n });\n\n if (existingIndex !== -1) {\n // Update existing failure\n const existing = this._failedTransactions[existingIndex];\n this._failedTransactions[existingIndex] = {\n ...existing,\n error,\n retryCount: existing.retryCount + 1,\n lastFailedAt: now,\n isPermanent,\n };\n } else {\n // Create new failure record\n const newFailure: FailedTransaction = {\n id: generateFailureId(entries),\n entries,\n error,\n retryCount: 1,\n firstFailedAt: now,\n lastFailedAt: now,\n isPermanent,\n affectedEntityIds,\n affectedTables,\n };\n\n this._failedTransactions.push(newFailure);\n\n // Enforce max stored failures (remove oldest)\n if (this._failedTransactions.length > this._maxStoredFailures) {\n this._failedTransactions.sort(\n (a, b) => a.firstFailedAt.getTime() - b.firstFailedAt.getTime()\n );\n this._failedTransactions = this._failedTransactions.slice(-this._maxStoredFailures);\n }\n }\n\n this._notifyFailureListeners();\n this._notifyListeners();\n }\n\n /**\n * Clear a specific failure by ID.\n */\n clearFailure(failureId: string): void {\n const initialLength = this._failedTransactions.length;\n this._failedTransactions = this._failedTransactions.filter(f => f.id !== failureId);\n\n if (this._failedTransactions.length !== initialLength) {\n this._notifyFailureListeners();\n this._notifyListeners();\n }\n }\n\n /**\n * Clear all failures.\n */\n clearAllFailures(): void {\n if (this._failedTransactions.length === 0) return;\n\n this._failedTransactions = [];\n this._notifyFailureListeners();\n this._notifyListeners();\n }\n\n /**\n * Get failures affecting a specific entity.\n */\n getFailuresForEntity(entityId: string): FailedTransaction[] {\n return this._failedTransactions.filter(f =>\n f.affectedEntityIds.includes(entityId)\n );\n }\n\n /**\n * Get all failed transactions.\n */\n getFailedTransactions(): FailedTransaction[] {\n return [...this._failedTransactions];\n }\n\n /**\n * Check if there are any upload errors.\n */\n hasUploadErrors(): boolean {\n return this._failedTransactions.length > 0;\n }\n\n /**\n * Get count of permanent errors.\n */\n getPermanentErrorCount(): number {\n return this._failedTransactions.filter(f => f.isPermanent).length;\n }\n\n /**\n * Subscribe to failure changes.\n * @returns Unsubscribe function\n */\n onFailureChange(listener: (failures: FailedTransaction[]) => void): Unsubscribe {\n this._failureListeners.add(listener);\n // Immediately call with current failures\n listener(this.getFailedTransactions());\n return () => {\n this._failureListeners.delete(listener);\n };\n }\n\n /**\n * Clean up stale failures (older than TTL).\n */\n cleanupStaleFailures(): void {\n const cutoff = Date.now() - this._failureTTLMs;\n const initialLength = this._failedTransactions.length;\n\n this._failedTransactions = this._failedTransactions.filter(\n f => f.lastFailedAt.getTime() > cutoff\n );\n\n if (this._failedTransactions.length !== initialLength) {\n this.logger.debug(\n `[StatusTracker] Cleaned up ${initialLength - this._failedTransactions.length} stale failures`\n );\n this._notifyFailureListeners();\n this._notifyListeners();\n }\n }\n\n // ─── Completed Transaction Tracking ────────────────────────────────────────\n\n /**\n * Record a successfully completed transaction.\n * Creates a CompletedTransaction record and adds it to history.\n */\n recordTransactionComplete(entries: CrudEntry[]): void {\n // Extract unique affected tables\n const affectedTables = [...new Set(entries.map(e => e.table))];\n\n // Extract unique affected entity IDs\n const affectedEntityIds = [...new Set(entries.map(e => e.id))];\n\n // Generate unique ID\n const id = `completed_${Date.now()}_${Math.random().toString(36).slice(2, 9)}`;\n\n const completed: CompletedTransaction = {\n id,\n entries,\n completedAt: new Date(),\n affectedTables,\n affectedEntityIds,\n };\n\n // Add to front of array (most recent first)\n this._completedTransactions.unshift(completed);\n\n // Trim to max history\n if (this._completedTransactions.length > this._maxCompletedHistory) {\n this._completedTransactions = this._completedTransactions.slice(0, this._maxCompletedHistory);\n }\n\n this._notifyCompletedListeners();\n this.logger.debug(\n `[StatusTracker] Recorded completed transaction: ${completed.id} (${entries.length} entries)`\n );\n }\n\n /**\n * Get all completed transactions.\n */\n getCompletedTransactions(): CompletedTransaction[] {\n return [...this._completedTransactions];\n }\n\n /**\n * Clear completed transaction history.\n */\n clearCompletedHistory(): void {\n if (this._completedTransactions.length === 0) return;\n\n this._completedTransactions = [];\n this._notifyCompletedListeners();\n this.logger.debug('[StatusTracker] Cleared completed transaction history');\n }\n\n /**\n * Subscribe to completed transaction changes.\n * @returns Unsubscribe function\n */\n onCompletedChange(listener: (completed: CompletedTransaction[]) => void): Unsubscribe {\n this._completedListeners.add(listener);\n // Immediately call with current completed transactions\n listener(this.getCompletedTransactions());\n return () => {\n this._completedListeners.delete(listener);\n };\n }\n\n // ─── Private Methods ───────────────────────────────────────────────────────\n\n private _hasStatusChanged(newStatus: SyncStatus): boolean {\n const old = this._state.status;\n return (\n old.connected !== newStatus.connected ||\n old.connecting !== newStatus.connecting ||\n old.hasSynced !== newStatus.hasSynced ||\n old.uploading !== newStatus.uploading ||\n old.downloading !== newStatus.downloading ||\n old.lastSyncedAt?.getTime() !== newStatus.lastSyncedAt?.getTime() ||\n old.downloadProgress?.current !== newStatus.downloadProgress?.current ||\n old.downloadProgress?.target !== newStatus.downloadProgress?.target\n );\n }\n\n private _notifyListeners(forceImmediate = false): void {\n const now = Date.now();\n const timeSinceLastNotify = now - this._lastNotifyTime;\n\n if (this._notifyTimer) {\n clearTimeout(this._notifyTimer);\n this._notifyTimer = null;\n }\n\n const notify = () => {\n this._lastNotifyTime = Date.now();\n const status = this.getStatus();\n\n // Call the main callback\n this.onStatusChange?.(status);\n\n // Call all listeners\n for (const listener of this._listeners) {\n try {\n listener(status);\n } catch (err) {\n this.logger.warn('[StatusTracker] Listener error:', err);\n }\n }\n };\n\n if (forceImmediate || timeSinceLastNotify >= this.notifyThrottleMs) {\n notify();\n } else {\n const delay = this.notifyThrottleMs - timeSinceLastNotify;\n this._notifyTimer = setTimeout(notify, delay);\n }\n }\n\n private _notifyFailureListeners(): void {\n const failures = this.getFailedTransactions();\n for (const listener of this._failureListeners) {\n try {\n listener(failures);\n } catch (err) {\n this.logger.warn('[StatusTracker] Failure listener error:', err);\n }\n }\n }\n\n private _notifyCompletedListeners(): void {\n const completed = this.getCompletedTransactions();\n for (const listener of this._completedListeners) {\n try {\n listener(completed);\n } catch (err) {\n this.logger.warn('[StatusTracker] Completed listener error:', err);\n }\n }\n }\n}\n","/**\n * Sync Metrics Collector for @pol-studios/powersync\n *\n * Collects and persists sync operation metrics for monitoring and debugging.\n */\n\nimport type { SyncMetrics, SyncError, SyncErrorType } from '../core/types';\nimport type { AsyncStorageAdapter, LoggerAdapter } from '../platform/types';\nimport type { MetricsCollectorOptions, SyncOperationData, Unsubscribe } from './types';\nimport { STORAGE_KEY_METRICS } from '../core/constants';\nimport { classifyError } from '../core/errors';\nimport { DEFAULT_SYNC_METRICS } from '../provider/types';\n\n/**\n * Collects sync metrics including operation counts, timing, and errors.\n *\n * Features:\n * - Tracks sync operation success/failure rates\n * - Calculates average sync duration\n * - Monitors data transfer amounts\n * - Persists metrics to storage for continuity across sessions\n * - Records last error for debugging\n *\n * @example\n * ```typescript\n * const collector = new MetricsCollector({\n * storage,\n * logger,\n * onMetricsChange: (metrics) => updateUI(metrics),\n * });\n *\n * // Start tracking a sync\n * const startTime = Date.now();\n *\n * // On sync complete\n * collector.recordSync({\n * durationMs: Date.now() - startTime,\n * success: true,\n * operationsDownloaded: 150,\n * });\n *\n * // Get current metrics\n * const metrics = collector.getMetrics();\n * ```\n */\nexport class MetricsCollector {\n private readonly storage: AsyncStorageAdapter;\n private readonly logger: LoggerAdapter;\n private readonly storageKey: string;\n private readonly persistMetrics: boolean;\n private readonly onMetricsChange?: (metrics: SyncMetrics) => void;\n\n private _metrics: SyncMetrics;\n private _listeners = new Set<(metrics: SyncMetrics) => void>();\n private _initialized = false;\n\n // Track active sync for timing\n private _syncStartTime: number | null = null;\n private _wasSyncing = false;\n\n constructor(\n storage: AsyncStorageAdapter,\n logger: LoggerAdapter,\n options: MetricsCollectorOptions = {}\n ) {\n this.storage = storage;\n this.logger = logger;\n this.storageKey = options.storageKey ?? STORAGE_KEY_METRICS;\n this.persistMetrics = options.persistMetrics ?? true;\n this.onMetricsChange = options.onMetricsChange;\n\n this._metrics = { ...DEFAULT_SYNC_METRICS };\n }\n\n // ─── Initialization ────────────────────────────────────────────────────────\n\n /**\n * Initialize the collector by loading persisted metrics.\n */\n async init(): Promise<void> {\n if (this._initialized) return;\n\n try {\n const stored = await this.storage.getItem(this.storageKey);\n if (stored) {\n const parsed = JSON.parse(stored);\n // Restore Date objects\n if (parsed.lastError?.timestamp) {\n parsed.lastError.timestamp = new Date(parsed.lastError.timestamp);\n }\n this._metrics = { ...DEFAULT_SYNC_METRICS, ...parsed };\n this.logger.debug('[MetricsCollector] Loaded persisted metrics');\n }\n } catch (err) {\n this.logger.warn('[MetricsCollector] Failed to load metrics:', err);\n }\n\n this._initialized = true;\n }\n\n /**\n * Dispose the collector.\n */\n dispose(): void {\n this._listeners.clear();\n }\n\n // ─── Getters ───────────────────────────────────────────────────────────────\n\n /**\n * Get current sync metrics.\n */\n getMetrics(): SyncMetrics {\n return { ...this._metrics };\n }\n\n // ─── Recording ─────────────────────────────────────────────────────────────\n\n /**\n * Record a completed sync operation.\n */\n async recordSync(data: SyncOperationData): Promise<void> {\n const { durationMs, success, operationsDownloaded, operationsUploaded, error } = data;\n\n const totalSyncs = this._metrics.totalSyncs + 1;\n const successfulSyncs = this._metrics.successfulSyncs + (success ? 1 : 0);\n const failedSyncs = this._metrics.failedSyncs + (success ? 0 : 1);\n\n // Calculate running average duration (only for successful syncs)\n let averageSyncDuration = this._metrics.averageSyncDuration;\n if (success) {\n if (averageSyncDuration !== null) {\n averageSyncDuration =\n (averageSyncDuration * (successfulSyncs - 1) + durationMs) / successfulSyncs;\n } else {\n averageSyncDuration = durationMs;\n }\n }\n\n // Estimate data transfer (rough approximation: ~100 bytes per operation)\n const bytesPerOp = 100;\n const downloaded = (operationsDownloaded ?? 0) * bytesPerOp;\n const uploaded = (operationsUploaded ?? 0) * bytesPerOp;\n\n // Build error record if failed\n let lastError: SyncError | null = this._metrics.lastError;\n if (!success && error) {\n const errorType = classifyError(error);\n lastError = {\n type: errorType,\n message: error.message,\n userMessage: error.message, // Use original message as user message for metrics\n timestamp: new Date(),\n isPermanent: false,\n };\n }\n\n this._metrics = {\n totalSyncs,\n successfulSyncs,\n failedSyncs,\n lastSyncDuration: durationMs,\n averageSyncDuration: averageSyncDuration !== null ? Math.round(averageSyncDuration) : null,\n totalDataDownloaded: this._metrics.totalDataDownloaded + downloaded,\n totalDataUploaded: this._metrics.totalDataUploaded + uploaded,\n lastError,\n };\n\n await this._persist();\n this._notifyListeners();\n }\n\n /**\n * Record a sync error without a full sync operation.\n */\n async recordError(error: Error): Promise<void> {\n this._metrics = {\n ...this._metrics,\n failedSyncs: this._metrics.failedSyncs + 1,\n lastError: {\n type: classifyError(error),\n message: error.message,\n userMessage: error.message, // Use original message as user message for metrics\n timestamp: new Date(),\n isPermanent: false,\n },\n };\n\n await this._persist();\n this._notifyListeners();\n }\n\n /**\n * Record an upload operation.\n */\n async recordUpload(operationCount: number): Promise<void> {\n const bytesPerOp = 100;\n this._metrics = {\n ...this._metrics,\n totalDataUploaded: this._metrics.totalDataUploaded + operationCount * bytesPerOp,\n };\n\n await this._persist();\n this._notifyListeners();\n }\n\n /**\n * Clear all metrics and start fresh.\n */\n async reset(): Promise<void> {\n this._metrics = { ...DEFAULT_SYNC_METRICS };\n await this._persist();\n this._notifyListeners();\n }\n\n // ─── Sync Timing Helpers ───────────────────────────────────────────────────\n\n /**\n * Called when sync starts (downloading becomes true).\n */\n markSyncStart(): void {\n if (!this._wasSyncing) {\n this._syncStartTime = Date.now();\n this._wasSyncing = true;\n }\n }\n\n /**\n * Called when sync ends (downloading becomes false).\n * Returns the duration if a sync was in progress.\n */\n markSyncEnd(): number | null {\n if (this._wasSyncing && this._syncStartTime !== null) {\n const duration = Date.now() - this._syncStartTime;\n this._syncStartTime = null;\n this._wasSyncing = false;\n return duration;\n }\n return null;\n }\n\n /**\n * Check if sync is currently in progress.\n */\n isSyncInProgress(): boolean {\n return this._wasSyncing;\n }\n\n // ─── Subscriptions ─────────────────────────────────────────────────────────\n\n /**\n * Subscribe to metrics changes.\n * @returns Unsubscribe function\n */\n onMetricsUpdate(listener: (metrics: SyncMetrics) => void): Unsubscribe {\n this._listeners.add(listener);\n listener(this.getMetrics());\n return () => {\n this._listeners.delete(listener);\n };\n }\n\n // ─── Private Methods ───────────────────────────────────────────────────────\n\n private async _persist(): Promise<void> {\n if (!this.persistMetrics) return;\n\n try {\n await this.storage.setItem(this.storageKey, JSON.stringify(this._metrics));\n } catch (err) {\n this.logger.warn('[MetricsCollector] Failed to persist metrics:', err);\n }\n }\n\n private _notifyListeners(): void {\n const metrics = this.getMetrics();\n\n // Call main callback\n this.onMetricsChange?.(metrics);\n\n // Call all listeners\n for (const listener of this._listeners) {\n try {\n listener(metrics);\n } catch (err) {\n this.logger.warn('[MetricsCollector] Listener error:', err);\n }\n }\n }\n}\n","/**\n * Connection Health Monitor for @pol-studios/powersync\n *\n * Monitors database connection health with periodic checks and latency tracking.\n */\n\nimport type { ConnectionHealth } from '../core/types';\nimport type { AbstractPowerSyncDatabase } from '../core/types';\nimport type { LoggerAdapter } from '../platform/types';\nimport type { HealthMonitorOptions, HealthCheckResult, Unsubscribe } from './types';\nimport {\n HEALTH_CHECK_INTERVAL_MS,\n HEALTH_CHECK_TIMEOUT_MS,\n LATENCY_DEGRADED_THRESHOLD_MS,\n MAX_CONSECUTIVE_FAILURES,\n} from '../core/constants';\nimport { DEFAULT_CONNECTION_HEALTH } from '../provider/types';\n\n/**\n * Monitors connection health with periodic checks.\n *\n * Features:\n * - Periodic health checks with configurable interval\n * - Latency measurement and degraded state detection\n * - Consecutive failure tracking\n * - Auto-recovery detection\n *\n * @example\n * ```typescript\n * const monitor = new HealthMonitor(db, logger, {\n * checkIntervalMs: 30000,\n * onHealthChange: (health) => {\n * if (health.status === 'degraded') {\n * showWarning('Connection is slow');\n * }\n * },\n * });\n *\n * // Start monitoring\n * monitor.start();\n *\n * // Get current health\n * const health = monitor.getHealth();\n *\n * // Stop when done\n * monitor.stop();\n * ```\n */\nexport class HealthMonitor {\n private readonly logger: LoggerAdapter;\n private readonly checkIntervalMs: number;\n private readonly checkTimeoutMs: number;\n private readonly degradedThresholdMs: number;\n private readonly maxConsecutiveFailures: number;\n private readonly onHealthChange?: (health: ConnectionHealth) => void;\n\n private _db: AbstractPowerSyncDatabase | null = null;\n private _health: ConnectionHealth;\n private _intervalId: ReturnType<typeof setInterval> | null = null;\n private _listeners = new Set<(health: ConnectionHealth) => void>();\n private _running = false;\n private _paused = false;\n\n constructor(\n logger: LoggerAdapter,\n options: HealthMonitorOptions = {}\n ) {\n this.logger = logger;\n this.checkIntervalMs = options.checkIntervalMs ?? HEALTH_CHECK_INTERVAL_MS;\n this.checkTimeoutMs = options.checkTimeoutMs ?? HEALTH_CHECK_TIMEOUT_MS;\n this.degradedThresholdMs = options.degradedThresholdMs ?? LATENCY_DEGRADED_THRESHOLD_MS;\n this.maxConsecutiveFailures = options.maxConsecutiveFailures ?? MAX_CONSECUTIVE_FAILURES;\n this.onHealthChange = options.onHealthChange;\n\n this._health = { ...DEFAULT_CONNECTION_HEALTH };\n }\n\n // ─── Lifecycle ─────────────────────────────────────────────────────────────\n\n /**\n * Set the database instance to monitor.\n */\n setDatabase(db: AbstractPowerSyncDatabase | null): void {\n this._db = db;\n\n if (!db) {\n // Reset health when database is cleared\n this._updateHealth({\n status: 'disconnected',\n latency: null,\n lastHealthCheck: new Date(),\n consecutiveFailures: 0,\n reconnectAttempts: this._health.reconnectAttempts,\n });\n }\n }\n\n /**\n * Start the health monitor.\n */\n start(): void {\n if (this._running) return;\n\n this.logger.info('[HealthMonitor] Starting');\n this._running = true;\n\n // Perform initial check\n this._checkHealth();\n\n // Set up periodic checks\n this._intervalId = setInterval(() => {\n if (!this._paused) {\n this._checkHealth();\n }\n }, this.checkIntervalMs);\n }\n\n /**\n * Stop the health monitor.\n */\n stop(): void {\n if (!this._running) return;\n\n this.logger.info('[HealthMonitor] Stopping');\n this._running = false;\n\n if (this._intervalId) {\n clearInterval(this._intervalId);\n this._intervalId = null;\n }\n }\n\n /**\n * Pause health checks temporarily.\n */\n pause(): void {\n this._paused = true;\n this._updateHealth({\n ...this._health,\n status: 'disconnected',\n });\n }\n\n /**\n * Resume health checks.\n */\n resume(): void {\n this._paused = false;\n // Perform immediate check on resume\n this._checkHealth();\n }\n\n /**\n * Dispose the monitor and clear all resources.\n */\n dispose(): void {\n this.stop();\n this._listeners.clear();\n }\n\n // ─── Getters ───────────────────────────────────────────────────────────────\n\n /**\n * Get current connection health.\n */\n getHealth(): ConnectionHealth {\n return { ...this._health };\n }\n\n /**\n * Check if the monitor is running.\n */\n isRunning(): boolean {\n return this._running;\n }\n\n // ─── Manual Checks ─────────────────────────────────────────────────────────\n\n /**\n * Perform an immediate health check.\n * @returns The result of the health check\n */\n async checkNow(): Promise<HealthCheckResult> {\n return this._checkHealth();\n }\n\n /**\n * Record a reconnection attempt.\n */\n recordReconnectAttempt(): void {\n this._updateHealth({\n ...this._health,\n reconnectAttempts: this._health.reconnectAttempts + 1,\n });\n }\n\n /**\n * Reset reconnection attempts counter (call on successful connection).\n */\n resetReconnectAttempts(): void {\n if (this._health.reconnectAttempts > 0) {\n this._updateHealth({\n ...this._health,\n reconnectAttempts: 0,\n });\n }\n }\n\n // ─── Subscriptions ─────────────────────────────────────────────────────────\n\n /**\n * Subscribe to health changes.\n * @returns Unsubscribe function\n */\n onHealthUpdate(listener: (health: ConnectionHealth) => void): Unsubscribe {\n this._listeners.add(listener);\n listener(this.getHealth());\n return () => {\n this._listeners.delete(listener);\n };\n }\n\n // ─── Private Methods ───────────────────────────────────────────────────────\n\n private async _checkHealth(): Promise<HealthCheckResult> {\n if (!this._db || this._paused) {\n return {\n success: false,\n error: new Error('Database not available or paused'),\n timestamp: new Date(),\n };\n }\n\n const startTime = Date.now();\n const timestamp = new Date();\n\n try {\n // Execute a simple query with timeout\n await this._withTimeout(\n this._db.get('SELECT 1'),\n this.checkTimeoutMs\n );\n\n const latencyMs = Date.now() - startTime;\n\n // Determine status based on latency\n const status: ConnectionHealth['status'] =\n latencyMs < this.degradedThresholdMs ? 'healthy' : 'degraded';\n\n this._updateHealth({\n status,\n latency: latencyMs,\n lastHealthCheck: timestamp,\n consecutiveFailures: 0,\n reconnectAttempts: this._health.reconnectAttempts,\n });\n\n return {\n success: true,\n latencyMs,\n timestamp,\n };\n } catch (err) {\n const error = err instanceof Error ? err : new Error(String(err));\n this.logger.warn('[HealthMonitor] Health check failed:', error.message);\n\n const consecutiveFailures = this._health.consecutiveFailures + 1;\n const status: ConnectionHealth['status'] =\n consecutiveFailures >= this.maxConsecutiveFailures ? 'disconnected' : 'degraded';\n\n this._updateHealth({\n status,\n latency: null,\n lastHealthCheck: timestamp,\n consecutiveFailures,\n reconnectAttempts: this._health.reconnectAttempts,\n });\n\n return {\n success: false,\n error,\n timestamp,\n };\n }\n }\n\n private _updateHealth(health: ConnectionHealth): void {\n const changed = this._hasHealthChanged(health);\n this._health = health;\n\n if (changed) {\n this._notifyListeners();\n }\n }\n\n private _hasHealthChanged(newHealth: ConnectionHealth): boolean {\n const old = this._health;\n return (\n old.status !== newHealth.status ||\n old.latency !== newHealth.latency ||\n old.consecutiveFailures !== newHealth.consecutiveFailures ||\n old.reconnectAttempts !== newHealth.reconnectAttempts\n );\n }\n\n private _notifyListeners(): void {\n const health = this.getHealth();\n\n // Call main callback\n this.onHealthChange?.(health);\n\n // Call all listeners\n for (const listener of this._listeners) {\n try {\n listener(health);\n } catch (err) {\n this.logger.warn('[HealthMonitor] Listener error:', err);\n }\n }\n }\n\n private _withTimeout<T>(promise: Promise<T>, timeoutMs: number): Promise<T> {\n return new Promise((resolve, reject) => {\n const timer = setTimeout(() => {\n reject(new Error(`Health check timeout after ${timeoutMs}ms`));\n }, timeoutMs);\n\n promise.then(\n (result) => {\n clearTimeout(timer);\n resolve(result);\n },\n (error) => {\n clearTimeout(timer);\n reject(error);\n }\n );\n });\n }\n}\n"],"mappings":"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","names":[]}
@@ -0,0 +1,322 @@
1
+ // src/core/errors.ts
2
+ var PowerSyncError = class _PowerSyncError extends Error {
3
+ constructor(message) {
4
+ super(message);
5
+ this.name = "PowerSyncError";
6
+ if (Error.captureStackTrace) {
7
+ Error.captureStackTrace(this, _PowerSyncError);
8
+ }
9
+ }
10
+ };
11
+ var InitializationError = class extends PowerSyncError {
12
+ constructor(message, cause) {
13
+ super(message);
14
+ this.cause = cause;
15
+ this.name = "InitializationError";
16
+ }
17
+ };
18
+ var SyncOperationError = class extends PowerSyncError {
19
+ constructor(message, errorType, cause) {
20
+ super(message);
21
+ this.errorType = errorType;
22
+ this.cause = cause;
23
+ this.name = "SyncOperationError";
24
+ }
25
+ /**
26
+ * Whether this error can be automatically retried
27
+ */
28
+ get isRetryable() {
29
+ return this.errorType === "network" || this.errorType === "server";
30
+ }
31
+ /**
32
+ * Get a user-friendly error message
33
+ */
34
+ get userFriendlyMessage() {
35
+ switch (this.errorType) {
36
+ case "network":
37
+ return "Unable to connect. Check your internet connection.";
38
+ case "auth":
39
+ return "Session expired. Please sign in again.";
40
+ case "server":
41
+ return "Server is temporarily unavailable. Try again later.";
42
+ case "conflict":
43
+ return "Your changes conflict with recent updates.";
44
+ case "quota":
45
+ return "Device storage is full. Free up space to continue.";
46
+ default:
47
+ return "An unexpected error occurred. Please try again.";
48
+ }
49
+ }
50
+ };
51
+ var ConnectorError = class extends PowerSyncError {
52
+ constructor(message, operation, cause) {
53
+ super(message);
54
+ this.operation = operation;
55
+ this.cause = cause;
56
+ this.name = "ConnectorError";
57
+ }
58
+ };
59
+ var AttachmentError = class extends PowerSyncError {
60
+ constructor(message, attachmentId, operation, cause) {
61
+ super(message);
62
+ this.attachmentId = attachmentId;
63
+ this.operation = operation;
64
+ this.cause = cause;
65
+ this.name = "AttachmentError";
66
+ }
67
+ };
68
+ var PlatformAdapterError = class extends PowerSyncError {
69
+ constructor(message, missingFeature) {
70
+ super(message);
71
+ this.missingFeature = missingFeature;
72
+ this.name = "PlatformAdapterError";
73
+ }
74
+ };
75
+ var ConfigurationError = class extends PowerSyncError {
76
+ constructor(message, configKey) {
77
+ super(message);
78
+ this.configKey = configKey;
79
+ this.name = "ConfigurationError";
80
+ }
81
+ };
82
+ var ERROR_PATTERNS = {
83
+ network: /network|fetch|econnrefused|etimedout|offline/i,
84
+ auth: /401|403|auth|token|unauthorized|forbidden/i,
85
+ server: /500|502|503|504|internal server/i,
86
+ conflict: /conflict|concurrent|version/i,
87
+ validation: /validation|constraint|invalid|required/i,
88
+ quota: /quota|storage|enospc|disk/i,
89
+ unknown: /.*/
90
+ };
91
+ function classifyError(error) {
92
+ const message = error.message || "";
93
+ if (ERROR_PATTERNS.auth.test(message)) return "auth";
94
+ if (ERROR_PATTERNS.server.test(message)) return "server";
95
+ if (ERROR_PATTERNS.network.test(message)) return "network";
96
+ if (ERROR_PATTERNS.conflict.test(message)) return "conflict";
97
+ if (ERROR_PATTERNS.validation.test(message)) return "validation";
98
+ if (ERROR_PATTERNS.quota.test(message)) return "quota";
99
+ return "unknown";
100
+ }
101
+ function toSyncOperationError(error, message) {
102
+ const errorType = classifyError(error);
103
+ return new SyncOperationError(message || error.message, errorType, error);
104
+ }
105
+ var PG_ERROR_CODES = {
106
+ // Class 23 - Integrity Constraint Violation (permanent - data issue)
107
+ UNIQUE_VIOLATION: "23505",
108
+ FOREIGN_KEY_VIOLATION: "23503",
109
+ NOT_NULL_VIOLATION: "23502",
110
+ CHECK_VIOLATION: "23514",
111
+ // Class 42 - Syntax Error or Access Rule Violation (permanent - schema issue)
112
+ UNDEFINED_TABLE: "42P01",
113
+ UNDEFINED_COLUMN: "42703",
114
+ INSUFFICIENT_PRIVILEGE: "42501",
115
+ // Class 08 - Connection Exception (transient)
116
+ CONNECTION_FAILURE: "08006",
117
+ CONNECTION_DOES_NOT_EXIST: "08003",
118
+ // Class 53 - Insufficient Resources (transient)
119
+ OUT_OF_MEMORY: "53200",
120
+ DISK_FULL: "53100",
121
+ // Class 40 - Transaction Rollback (transient)
122
+ SERIALIZATION_FAILURE: "40001",
123
+ DEADLOCK_DETECTED: "40P01"
124
+ };
125
+ var PG_ERROR_MESSAGES = {
126
+ [PG_ERROR_CODES.UNIQUE_VIOLATION]: "This record already exists or conflicts with existing data.",
127
+ [PG_ERROR_CODES.FOREIGN_KEY_VIOLATION]: "This record references data that no longer exists.",
128
+ [PG_ERROR_CODES.NOT_NULL_VIOLATION]: "A required field is missing.",
129
+ [PG_ERROR_CODES.CHECK_VIOLATION]: "The data does not meet validation requirements.",
130
+ [PG_ERROR_CODES.UNDEFINED_TABLE]: "The database table does not exist.",
131
+ [PG_ERROR_CODES.UNDEFINED_COLUMN]: "A database column does not exist.",
132
+ [PG_ERROR_CODES.INSUFFICIENT_PRIVILEGE]: "You do not have permission to perform this action."
133
+ };
134
+ function extractHttpStatusCode(error) {
135
+ if (!error || typeof error !== "object") return void 0;
136
+ const err = error;
137
+ if (typeof err.status === "number") return err.status;
138
+ if (typeof err.statusCode === "number") return err.statusCode;
139
+ if (err.error && typeof err.error === "object") {
140
+ const nested = err.error;
141
+ if (typeof nested.status === "number") return nested.status;
142
+ if (typeof nested.statusCode === "number") return nested.statusCode;
143
+ }
144
+ const message = String(err.message || "");
145
+ const match = message.match(/\b(4\d{2}|5\d{2})\b/);
146
+ if (match) return parseInt(match[1], 10);
147
+ return void 0;
148
+ }
149
+ function extractPgCode(error) {
150
+ if (!error || typeof error !== "object") return void 0;
151
+ const err = error;
152
+ if (typeof err.code === "string") {
153
+ if (/^\d{5}$/.test(err.code)) {
154
+ return err.code;
155
+ }
156
+ const match = err.code.match(/\d{5}/) || String(err.message || "").match(/\d{5}/);
157
+ if (match) return match[0];
158
+ }
159
+ if (typeof err.details === "string") {
160
+ const match = err.details.match(/\b(\d{5})\b/);
161
+ if (match) return match[1];
162
+ }
163
+ return void 0;
164
+ }
165
+ function getPostgRESTMessage(code, fallback) {
166
+ const messages = {
167
+ "PGRST116": "Record not found or query returned no results.",
168
+ "PGRST204": "Column not found in the database.",
169
+ "PGRST301": "Row-level security prevented this operation.",
170
+ "PGRST302": "The requested operation is not allowed."
171
+ };
172
+ return messages[code] || `Database error: ${fallback}`;
173
+ }
174
+ function classifySupabaseError(error) {
175
+ const pgCode = extractPgCode(error);
176
+ const httpStatusCode = extractHttpStatusCode(error);
177
+ const errorMessage = error instanceof Error ? error.message : String(error);
178
+ let result = {
179
+ type: "unknown",
180
+ isPermanent: false,
181
+ isConflict: false,
182
+ pgCode,
183
+ userMessage: "An unexpected error occurred. Please try again."
184
+ };
185
+ if (error && typeof error === "object") {
186
+ const err = error;
187
+ const code = String(err.code || "");
188
+ if (code.startsWith("PGRST")) {
189
+ result.type = "validation";
190
+ result.isPermanent = true;
191
+ result.userMessage = getPostgRESTMessage(code, errorMessage);
192
+ return result;
193
+ }
194
+ }
195
+ if (httpStatusCode) {
196
+ if (httpStatusCode >= 400 && httpStatusCode < 500 && httpStatusCode !== 401 && httpStatusCode !== 403) {
197
+ result.type = "validation";
198
+ result.isPermanent = true;
199
+ result.userMessage = "The request was rejected. Please check your data.";
200
+ return result;
201
+ }
202
+ if (httpStatusCode >= 500) {
203
+ result.type = "server";
204
+ result.isPermanent = false;
205
+ result.userMessage = "Server temporarily unavailable. Will retry.";
206
+ return result;
207
+ }
208
+ }
209
+ if (pgCode) {
210
+ if (pgCode.startsWith("23")) {
211
+ result.type = pgCode === PG_ERROR_CODES.UNIQUE_VIOLATION ? "conflict" : "validation";
212
+ result.isPermanent = true;
213
+ result.isConflict = pgCode === PG_ERROR_CODES.UNIQUE_VIOLATION;
214
+ result.userMessage = PG_ERROR_MESSAGES[pgCode] || "Data validation failed.";
215
+ return result;
216
+ }
217
+ if (pgCode.startsWith("42")) {
218
+ result.type = pgCode === PG_ERROR_CODES.INSUFFICIENT_PRIVILEGE ? "auth" : "validation";
219
+ result.isPermanent = true;
220
+ result.userMessage = PG_ERROR_MESSAGES[pgCode] || "Database schema error.";
221
+ return result;
222
+ }
223
+ if (pgCode.startsWith("08")) {
224
+ result.type = "network";
225
+ result.isPermanent = false;
226
+ result.userMessage = "Connection lost. Will retry automatically.";
227
+ return result;
228
+ }
229
+ if (pgCode.startsWith("53")) {
230
+ result.type = "quota";
231
+ result.isPermanent = pgCode === PG_ERROR_CODES.DISK_FULL;
232
+ result.userMessage = "Server resources exhausted. Please try again later.";
233
+ return result;
234
+ }
235
+ if (pgCode.startsWith("40")) {
236
+ result.type = "conflict";
237
+ result.isPermanent = false;
238
+ result.isConflict = true;
239
+ result.userMessage = "Concurrent update detected. Retrying...";
240
+ return result;
241
+ }
242
+ }
243
+ const lowerMessage = errorMessage.toLowerCase();
244
+ if (/network|fetch|econnrefused|etimedout|offline|dns|socket/.test(lowerMessage)) {
245
+ result.type = "network";
246
+ result.isPermanent = false;
247
+ result.userMessage = "Network error. Will retry when connected.";
248
+ return result;
249
+ }
250
+ if (/401|403|auth|token|unauthorized|forbidden|jwt|expired/.test(lowerMessage)) {
251
+ result.type = "auth";
252
+ result.isPermanent = lowerMessage.includes("expired") || lowerMessage.includes("invalid");
253
+ result.userMessage = "Authentication error. Please sign in again.";
254
+ return result;
255
+ }
256
+ if (/400|406|bad request|not acceptable|422|unprocessable/i.test(lowerMessage)) {
257
+ result.type = "validation";
258
+ result.isPermanent = true;
259
+ result.userMessage = "The request was rejected. Please check your data.";
260
+ return result;
261
+ }
262
+ if (/500|502|503|504|internal server|service unavailable/.test(lowerMessage)) {
263
+ result.type = "server";
264
+ result.isPermanent = false;
265
+ result.userMessage = "Server temporarily unavailable. Will retry.";
266
+ return result;
267
+ }
268
+ if (/duplicate|unique|constraint|violates|invalid|required/.test(lowerMessage)) {
269
+ result.type = "validation";
270
+ result.isPermanent = true;
271
+ result.isConflict = lowerMessage.includes("duplicate") || lowerMessage.includes("unique");
272
+ result.userMessage = "Data validation failed. Please check your input.";
273
+ return result;
274
+ }
275
+ return result;
276
+ }
277
+ function createSyncError(classified, originalMessage) {
278
+ return {
279
+ type: classified.type,
280
+ message: originalMessage,
281
+ userMessage: classified.userMessage,
282
+ timestamp: /* @__PURE__ */ new Date(),
283
+ pgCode: classified.pgCode,
284
+ isPermanent: classified.isPermanent
285
+ };
286
+ }
287
+ function generateFailureId(entries) {
288
+ const timestamp = Date.now();
289
+ const entryIds = entries.map((e) => e.id).join("-");
290
+ return `failure-${timestamp}-${entryIds.substring(0, 32)}`;
291
+ }
292
+ function extractEntityIds(entries) {
293
+ const ids = [];
294
+ for (const entry of entries) {
295
+ ids.push(entry.id);
296
+ if (entry.opData?.id !== void 0 && String(entry.opData.id) !== entry.id) {
297
+ ids.push(String(entry.opData.id));
298
+ }
299
+ }
300
+ return [...new Set(ids)];
301
+ }
302
+ function extractTableNames(entries) {
303
+ return [...new Set(entries.map((entry) => entry.table))];
304
+ }
305
+
306
+ export {
307
+ PowerSyncError,
308
+ InitializationError,
309
+ SyncOperationError,
310
+ ConnectorError,
311
+ AttachmentError,
312
+ PlatformAdapterError,
313
+ ConfigurationError,
314
+ classifyError,
315
+ toSyncOperationError,
316
+ classifySupabaseError,
317
+ createSyncError,
318
+ generateFailureId,
319
+ extractEntityIds,
320
+ extractTableNames
321
+ };
322
+ //# sourceMappingURL=chunk-CHRTN5PF.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"sources":["../src/core/errors.ts"],"sourcesContent":["/**\n * Custom Error Classes for @pol-studios/powersync\n *\n * This module provides specialized error classes for different failure scenarios.\n */\n\nimport type { SyncErrorType, ClassifiedError, SyncError, CrudEntry } from './types';\n\n/**\n * Base error class for PowerSync-related errors\n */\nexport class PowerSyncError extends Error {\n constructor(message: string) {\n super(message);\n this.name = 'PowerSyncError';\n // Maintains proper stack trace for where our error was thrown (only in V8)\n if (Error.captureStackTrace) {\n Error.captureStackTrace(this, PowerSyncError);\n }\n }\n}\n\n/**\n * Error thrown when PowerSync initialization fails\n */\nexport class InitializationError extends PowerSyncError {\n constructor(\n message: string,\n public readonly cause?: Error\n ) {\n super(message);\n this.name = 'InitializationError';\n }\n}\n\n/**\n * Error thrown when a sync operation fails\n */\nexport class SyncOperationError extends PowerSyncError {\n constructor(\n message: string,\n public readonly errorType: SyncErrorType,\n public readonly cause?: Error\n ) {\n super(message);\n this.name = 'SyncOperationError';\n }\n\n /**\n * Whether this error can be automatically retried\n */\n get isRetryable(): boolean {\n return this.errorType === 'network' || this.errorType === 'server';\n }\n\n /**\n * Get a user-friendly error message\n */\n get userFriendlyMessage(): string {\n switch (this.errorType) {\n case 'network':\n return 'Unable to connect. Check your internet connection.';\n case 'auth':\n return 'Session expired. Please sign in again.';\n case 'server':\n return 'Server is temporarily unavailable. Try again later.';\n case 'conflict':\n return 'Your changes conflict with recent updates.';\n case 'quota':\n return 'Device storage is full. Free up space to continue.';\n default:\n return 'An unexpected error occurred. Please try again.';\n }\n }\n}\n\n/**\n * Error thrown when a connector operation fails\n */\nexport class ConnectorError extends PowerSyncError {\n constructor(\n message: string,\n public readonly operation: 'fetchCredentials' | 'uploadData',\n public readonly cause?: Error\n ) {\n super(message);\n this.name = 'ConnectorError';\n }\n}\n\n/**\n * Error thrown when an attachment operation fails\n */\nexport class AttachmentError extends PowerSyncError {\n constructor(\n message: string,\n public readonly attachmentId: string,\n public readonly operation: 'download' | 'compress' | 'delete' | 'evict',\n public readonly cause?: Error\n ) {\n super(message);\n this.name = 'AttachmentError';\n }\n}\n\n/**\n * Error thrown when the platform adapter is missing required functionality\n */\nexport class PlatformAdapterError extends PowerSyncError {\n constructor(\n message: string,\n public readonly missingFeature: string\n ) {\n super(message);\n this.name = 'PlatformAdapterError';\n }\n}\n\n/**\n * Error thrown when configuration is invalid\n */\nexport class ConfigurationError extends PowerSyncError {\n constructor(\n message: string,\n public readonly configKey?: string\n ) {\n super(message);\n this.name = 'ConfigurationError';\n }\n}\n\n// ─── Error Classification Utilities ──────────────────────────────────────────\n\n/** Pattern definitions for error classification */\nconst ERROR_PATTERNS: Record<SyncErrorType, RegExp> = {\n network: /network|fetch|econnrefused|etimedout|offline/i,\n auth: /401|403|auth|token|unauthorized|forbidden/i,\n server: /500|502|503|504|internal server/i,\n conflict: /conflict|concurrent|version/i,\n validation: /validation|constraint|invalid|required/i,\n quota: /quota|storage|enospc|disk/i,\n unknown: /.*/,\n};\n\n/**\n * Classify an error into a SyncErrorType based on its message\n *\n * @param error - The error to classify\n * @returns The classified error type\n */\nexport function classifyError(error: Error): SyncErrorType {\n const message = error.message || '';\n\n // Check patterns in priority order (more specific first)\n if (ERROR_PATTERNS.auth.test(message)) return 'auth';\n if (ERROR_PATTERNS.server.test(message)) return 'server';\n if (ERROR_PATTERNS.network.test(message)) return 'network';\n if (ERROR_PATTERNS.conflict.test(message)) return 'conflict';\n if (ERROR_PATTERNS.validation.test(message)) return 'validation';\n if (ERROR_PATTERNS.quota.test(message)) return 'quota';\n\n return 'unknown';\n}\n\n/**\n * Create a SyncOperationError from a regular Error\n *\n * @param error - The original error\n * @param message - Optional custom message\n * @returns A classified SyncOperationError\n */\nexport function toSyncOperationError(\n error: Error,\n message?: string\n): SyncOperationError {\n const errorType = classifyError(error);\n return new SyncOperationError(message || error.message, errorType, error);\n}\n\n// ─── Supabase/PostgreSQL Error Classification ────────────────────────────────\n\n/**\n * PostgreSQL error code ranges and their meanings.\n * See: https://www.postgresql.org/docs/current/errcodes-appendix.html\n */\nconst PG_ERROR_CODES = {\n // Class 23 - Integrity Constraint Violation (permanent - data issue)\n UNIQUE_VIOLATION: '23505',\n FOREIGN_KEY_VIOLATION: '23503',\n NOT_NULL_VIOLATION: '23502',\n CHECK_VIOLATION: '23514',\n\n // Class 42 - Syntax Error or Access Rule Violation (permanent - schema issue)\n UNDEFINED_TABLE: '42P01',\n UNDEFINED_COLUMN: '42703',\n INSUFFICIENT_PRIVILEGE: '42501',\n\n // Class 08 - Connection Exception (transient)\n CONNECTION_FAILURE: '08006',\n CONNECTION_DOES_NOT_EXIST: '08003',\n\n // Class 53 - Insufficient Resources (transient)\n OUT_OF_MEMORY: '53200',\n DISK_FULL: '53100',\n\n // Class 40 - Transaction Rollback (transient)\n SERIALIZATION_FAILURE: '40001',\n DEADLOCK_DETECTED: '40P01',\n} as const;\n\n/**\n * User-friendly messages for PostgreSQL error codes\n */\nconst PG_ERROR_MESSAGES: Record<string, string> = {\n [PG_ERROR_CODES.UNIQUE_VIOLATION]: 'This record already exists or conflicts with existing data.',\n [PG_ERROR_CODES.FOREIGN_KEY_VIOLATION]: 'This record references data that no longer exists.',\n [PG_ERROR_CODES.NOT_NULL_VIOLATION]: 'A required field is missing.',\n [PG_ERROR_CODES.CHECK_VIOLATION]: 'The data does not meet validation requirements.',\n [PG_ERROR_CODES.UNDEFINED_TABLE]: 'The database table does not exist.',\n [PG_ERROR_CODES.UNDEFINED_COLUMN]: 'A database column does not exist.',\n [PG_ERROR_CODES.INSUFFICIENT_PRIVILEGE]: 'You do not have permission to perform this action.',\n};\n\n/**\n * Extract HTTP status code from an error object.\n *\n * Checks for common status code properties and falls back to pattern matching\n * in the error message for 4xx/5xx codes.\n */\nfunction extractHttpStatusCode(error: unknown): number | undefined {\n if (!error || typeof error !== 'object') return undefined;\n const err = error as Record<string, unknown>;\n\n // Direct properties\n if (typeof err.status === 'number') return err.status;\n if (typeof err.statusCode === 'number') return err.statusCode;\n\n // Check nested error object (Supabase format)\n if (err.error && typeof err.error === 'object') {\n const nested = err.error as Record<string, unknown>;\n if (typeof nested.status === 'number') return nested.status;\n if (typeof nested.statusCode === 'number') return nested.statusCode;\n }\n\n // Pattern match in message\n const message = String(err.message || '');\n const match = message.match(/\\b(4\\d{2}|5\\d{2})\\b/);\n if (match) return parseInt(match[1], 10);\n\n return undefined;\n}\n\n/**\n * Extract PostgreSQL error code from a Supabase error.\n *\n * Supabase errors from PostgREST typically include the PostgreSQL error code\n * in the error object or message.\n */\nfunction extractPgCode(error: unknown): string | undefined {\n if (!error || typeof error !== 'object') return undefined;\n\n const err = error as Record<string, unknown>;\n\n // Supabase error format: { code: \"PGRST...\", details: \"...\", hint: \"...\", message: \"...\" }\n // PostgreSQL error format: { code: \"23505\", ... }\n if (typeof err.code === 'string') {\n // Check if it's a direct PostgreSQL code (5 chars, starts with digit)\n if (/^\\d{5}$/.test(err.code)) {\n return err.code;\n }\n // Extract from PGRST format or message\n const match = err.code.match(/\\d{5}/) || String(err.message || '').match(/\\d{5}/);\n if (match) return match[0];\n }\n\n // Check in details or hint\n if (typeof err.details === 'string') {\n const match = err.details.match(/\\b(\\d{5})\\b/);\n if (match) return match[1];\n }\n\n return undefined;\n}\n\n/**\n * User-friendly messages for PostgREST error codes (PGRST*).\n * See: https://postgrest.org/en/stable/references/errors.html\n */\nfunction getPostgRESTMessage(code: string, fallback: string): string {\n const messages: Record<string, string> = {\n 'PGRST116': 'Record not found or query returned no results.',\n 'PGRST204': 'Column not found in the database.',\n 'PGRST301': 'Row-level security prevented this operation.',\n 'PGRST302': 'The requested operation is not allowed.',\n };\n return messages[code] || `Database error: ${fallback}`;\n}\n\n/**\n * Classify a Supabase/PostgreSQL error into a structured result.\n *\n * This function analyzes errors from Supabase operations and determines:\n * - The error type category\n * - Whether the error is permanent (won't be fixed by retry)\n * - Whether it's a conflict with existing data\n * - A user-friendly message\n *\n * @param error - The error from a Supabase operation\n * @returns Classified error information\n *\n * @example\n * ```typescript\n * try {\n * await supabase.from('users').insert({ email: 'duplicate@test.com' });\n * } catch (error) {\n * const classified = classifySupabaseError(error);\n * if (classified.isPermanent) {\n * // Show error to user - retry won't help\n * }\n * }\n * ```\n */\nexport function classifySupabaseError(error: unknown): ClassifiedError {\n const pgCode = extractPgCode(error);\n const httpStatusCode = extractHttpStatusCode(error);\n const errorMessage = error instanceof Error ? error.message : String(error);\n\n // Default result\n let result: ClassifiedError = {\n type: 'unknown',\n isPermanent: false,\n isConflict: false,\n pgCode,\n userMessage: 'An unexpected error occurred. Please try again.',\n };\n\n // Check for PostgREST error codes (PGRST*)\n if (error && typeof error === 'object') {\n const err = error as Record<string, unknown>;\n const code = String(err.code || '');\n\n if (code.startsWith('PGRST')) {\n result.type = 'validation';\n result.isPermanent = true;\n result.userMessage = getPostgRESTMessage(code, errorMessage);\n return result;\n }\n }\n\n // First, check HTTP status code for quick classification\n if (httpStatusCode) {\n // 4xx client errors (except 401/403 which are auth errors handled separately)\n if (httpStatusCode >= 400 && httpStatusCode < 500 && httpStatusCode !== 401 && httpStatusCode !== 403) {\n result.type = 'validation';\n result.isPermanent = true;\n result.userMessage = 'The request was rejected. Please check your data.';\n return result;\n }\n\n // 5xx server errors (transient)\n if (httpStatusCode >= 500) {\n result.type = 'server';\n result.isPermanent = false;\n result.userMessage = 'Server temporarily unavailable. Will retry.';\n return result;\n }\n }\n\n // Second, check PostgreSQL error code (most reliable)\n if (pgCode) {\n // Class 23 - Integrity Constraint Violations (permanent)\n if (pgCode.startsWith('23')) {\n result.type = pgCode === PG_ERROR_CODES.UNIQUE_VIOLATION ? 'conflict' : 'validation';\n result.isPermanent = true;\n result.isConflict = pgCode === PG_ERROR_CODES.UNIQUE_VIOLATION;\n result.userMessage = PG_ERROR_MESSAGES[pgCode] || 'Data validation failed.';\n return result;\n }\n\n // Class 42 - Syntax/Access Errors (permanent - schema issue)\n if (pgCode.startsWith('42')) {\n result.type = pgCode === PG_ERROR_CODES.INSUFFICIENT_PRIVILEGE ? 'auth' : 'validation';\n result.isPermanent = true;\n result.userMessage = PG_ERROR_MESSAGES[pgCode] || 'Database schema error.';\n return result;\n }\n\n // Class 08 - Connection Exceptions (transient)\n if (pgCode.startsWith('08')) {\n result.type = 'network';\n result.isPermanent = false;\n result.userMessage = 'Connection lost. Will retry automatically.';\n return result;\n }\n\n // Class 53 - Insufficient Resources (transient, but may need action)\n if (pgCode.startsWith('53')) {\n result.type = 'quota';\n result.isPermanent = pgCode === PG_ERROR_CODES.DISK_FULL;\n result.userMessage = 'Server resources exhausted. Please try again later.';\n return result;\n }\n\n // Class 40 - Transaction Rollback (transient)\n if (pgCode.startsWith('40')) {\n result.type = 'conflict';\n result.isPermanent = false;\n result.isConflict = true;\n result.userMessage = 'Concurrent update detected. Retrying...';\n return result;\n }\n }\n\n // Fall back to pattern matching on error message\n const lowerMessage = errorMessage.toLowerCase();\n\n // Network errors (transient)\n if (/network|fetch|econnrefused|etimedout|offline|dns|socket/.test(lowerMessage)) {\n result.type = 'network';\n result.isPermanent = false;\n result.userMessage = 'Network error. Will retry when connected.';\n return result;\n }\n\n // Auth errors (may be permanent if token is invalid)\n if (/401|403|auth|token|unauthorized|forbidden|jwt|expired/.test(lowerMessage)) {\n result.type = 'auth';\n result.isPermanent = lowerMessage.includes('expired') || lowerMessage.includes('invalid');\n result.userMessage = 'Authentication error. Please sign in again.';\n return result;\n }\n\n // Client errors (permanent - request is malformed or rejected)\n if (/400|406|bad request|not acceptable|422|unprocessable/i.test(lowerMessage)) {\n result.type = 'validation';\n result.isPermanent = true;\n result.userMessage = 'The request was rejected. Please check your data.';\n return result;\n }\n\n // Server errors (transient)\n if (/500|502|503|504|internal server|service unavailable/.test(lowerMessage)) {\n result.type = 'server';\n result.isPermanent = false;\n result.userMessage = 'Server temporarily unavailable. Will retry.';\n return result;\n }\n\n // Constraint/validation errors in message (permanent)\n if (/duplicate|unique|constraint|violates|invalid|required/.test(lowerMessage)) {\n result.type = 'validation';\n result.isPermanent = true;\n result.isConflict = lowerMessage.includes('duplicate') || lowerMessage.includes('unique');\n result.userMessage = 'Data validation failed. Please check your input.';\n return result;\n }\n\n return result;\n}\n\n/**\n * Create a SyncError from a classified error result.\n */\nexport function createSyncError(classified: ClassifiedError, originalMessage: string): SyncError {\n return {\n type: classified.type,\n message: originalMessage,\n userMessage: classified.userMessage,\n timestamp: new Date(),\n pgCode: classified.pgCode,\n isPermanent: classified.isPermanent,\n };\n}\n\n/**\n * Generate a unique ID for a failed transaction.\n * Uses a combination of timestamp and entry IDs to ensure uniqueness.\n */\nexport function generateFailureId(entries: CrudEntry[]): string {\n const timestamp = Date.now();\n const entryIds = entries.map(e => e.id).join('-');\n return `failure-${timestamp}-${entryIds.substring(0, 32)}`;\n}\n\n/**\n * Extract entity IDs from CRUD entries.\n * Includes both the PowerSync entry ID and the record's 'id' from opData if different.\n * This ensures we can match failures to entities regardless of which ID format is used.\n */\nexport function extractEntityIds(entries: CrudEntry[]): string[] {\n const ids: string[] = [];\n for (const entry of entries) {\n // Always include the PowerSync entry ID\n ids.push(entry.id);\n // Also include the record's 'id' field from opData if it exists and is different\n if (entry.opData?.id !== undefined && String(entry.opData.id) !== entry.id) {\n ids.push(String(entry.opData.id));\n }\n }\n return [...new Set(ids)];\n}\n\n/**\n * Extract unique table names from CRUD entries.\n */\nexport function extractTableNames(entries: CrudEntry[]): string[] {\n return [...new Set(entries.map(entry => entry.table))];\n}\n"],"mappings":";AAWO,IAAM,iBAAN,MAAM,wBAAuB,MAAM;AAAA,EACxC,YAAY,SAAiB;AAC3B,UAAM,OAAO;AACb,SAAK,OAAO;AAEZ,QAAI,MAAM,mBAAmB;AAC3B,YAAM,kBAAkB,MAAM,eAAc;AAAA,IAC9C;AAAA,EACF;AACF;AAKO,IAAM,sBAAN,cAAkC,eAAe;AAAA,EACtD,YACE,SACgB,OAChB;AACA,UAAM,OAAO;AAFG;AAGhB,SAAK,OAAO;AAAA,EACd;AACF;AAKO,IAAM,qBAAN,cAAiC,eAAe;AAAA,EACrD,YACE,SACgB,WACA,OAChB;AACA,UAAM,OAAO;AAHG;AACA;AAGhB,SAAK,OAAO;AAAA,EACd;AAAA;AAAA;AAAA;AAAA,EAKA,IAAI,cAAuB;AACzB,WAAO,KAAK,cAAc,aAAa,KAAK,cAAc;AAAA,EAC5D;AAAA;AAAA;AAAA;AAAA,EAKA,IAAI,sBAA8B;AAChC,YAAQ,KAAK,WAAW;AAAA,MACtB,KAAK;AACH,eAAO;AAAA,MACT,KAAK;AACH,eAAO;AAAA,MACT,KAAK;AACH,eAAO;AAAA,MACT,KAAK;AACH,eAAO;AAAA,MACT,KAAK;AACH,eAAO;AAAA,MACT;AACE,eAAO;AAAA,IACX;AAAA,EACF;AACF;AAKO,IAAM,iBAAN,cAA6B,eAAe;AAAA,EACjD,YACE,SACgB,WACA,OAChB;AACA,UAAM,OAAO;AAHG;AACA;AAGhB,SAAK,OAAO;AAAA,EACd;AACF;AAKO,IAAM,kBAAN,cAA8B,eAAe;AAAA,EAClD,YACE,SACgB,cACA,WACA,OAChB;AACA,UAAM,OAAO;AAJG;AACA;AACA;AAGhB,SAAK,OAAO;AAAA,EACd;AACF;AAKO,IAAM,uBAAN,cAAmC,eAAe;AAAA,EACvD,YACE,SACgB,gBAChB;AACA,UAAM,OAAO;AAFG;AAGhB,SAAK,OAAO;AAAA,EACd;AACF;AAKO,IAAM,qBAAN,cAAiC,eAAe;AAAA,EACrD,YACE,SACgB,WAChB;AACA,UAAM,OAAO;AAFG;AAGhB,SAAK,OAAO;AAAA,EACd;AACF;AAKA,IAAM,iBAAgD;AAAA,EACpD,SAAS;AAAA,EACT,MAAM;AAAA,EACN,QAAQ;AAAA,EACR,UAAU;AAAA,EACV,YAAY;AAAA,EACZ,OAAO;AAAA,EACP,SAAS;AACX;AAQO,SAAS,cAAc,OAA6B;AACzD,QAAM,UAAU,MAAM,WAAW;AAGjC,MAAI,eAAe,KAAK,KAAK,OAAO,EAAG,QAAO;AAC9C,MAAI,eAAe,OAAO,KAAK,OAAO,EAAG,QAAO;AAChD,MAAI,eAAe,QAAQ,KAAK,OAAO,EAAG,QAAO;AACjD,MAAI,eAAe,SAAS,KAAK,OAAO,EAAG,QAAO;AAClD,MAAI,eAAe,WAAW,KAAK,OAAO,EAAG,QAAO;AACpD,MAAI,eAAe,MAAM,KAAK,OAAO,EAAG,QAAO;AAE/C,SAAO;AACT;AASO,SAAS,qBACd,OACA,SACoB;AACpB,QAAM,YAAY,cAAc,KAAK;AACrC,SAAO,IAAI,mBAAmB,WAAW,MAAM,SAAS,WAAW,KAAK;AAC1E;AAQA,IAAM,iBAAiB;AAAA;AAAA,EAErB,kBAAkB;AAAA,EAClB,uBAAuB;AAAA,EACvB,oBAAoB;AAAA,EACpB,iBAAiB;AAAA;AAAA,EAGjB,iBAAiB;AAAA,EACjB,kBAAkB;AAAA,EAClB,wBAAwB;AAAA;AAAA,EAGxB,oBAAoB;AAAA,EACpB,2BAA2B;AAAA;AAAA,EAG3B,eAAe;AAAA,EACf,WAAW;AAAA;AAAA,EAGX,uBAAuB;AAAA,EACvB,mBAAmB;AACrB;AAKA,IAAM,oBAA4C;AAAA,EAChD,CAAC,eAAe,gBAAgB,GAAG;AAAA,EACnC,CAAC,eAAe,qBAAqB,GAAG;AAAA,EACxC,CAAC,eAAe,kBAAkB,GAAG;AAAA,EACrC,CAAC,eAAe,eAAe,GAAG;AAAA,EAClC,CAAC,eAAe,eAAe,GAAG;AAAA,EAClC,CAAC,eAAe,gBAAgB,GAAG;AAAA,EACnC,CAAC,eAAe,sBAAsB,GAAG;AAC3C;AAQA,SAAS,sBAAsB,OAAoC;AACjE,MAAI,CAAC,SAAS,OAAO,UAAU,SAAU,QAAO;AAChD,QAAM,MAAM;AAGZ,MAAI,OAAO,IAAI,WAAW,SAAU,QAAO,IAAI;AAC/C,MAAI,OAAO,IAAI,eAAe,SAAU,QAAO,IAAI;AAGnD,MAAI,IAAI,SAAS,OAAO,IAAI,UAAU,UAAU;AAC9C,UAAM,SAAS,IAAI;AACnB,QAAI,OAAO,OAAO,WAAW,SAAU,QAAO,OAAO;AACrD,QAAI,OAAO,OAAO,eAAe,SAAU,QAAO,OAAO;AAAA,EAC3D;AAGA,QAAM,UAAU,OAAO,IAAI,WAAW,EAAE;AACxC,QAAM,QAAQ,QAAQ,MAAM,qBAAqB;AACjD,MAAI,MAAO,QAAO,SAAS,MAAM,CAAC,GAAG,EAAE;AAEvC,SAAO;AACT;AAQA,SAAS,cAAc,OAAoC;AACzD,MAAI,CAAC,SAAS,OAAO,UAAU,SAAU,QAAO;AAEhD,QAAM,MAAM;AAIZ,MAAI,OAAO,IAAI,SAAS,UAAU;AAEhC,QAAI,UAAU,KAAK,IAAI,IAAI,GAAG;AAC5B,aAAO,IAAI;AAAA,IACb;AAEA,UAAM,QAAQ,IAAI,KAAK,MAAM,OAAO,KAAK,OAAO,IAAI,WAAW,EAAE,EAAE,MAAM,OAAO;AAChF,QAAI,MAAO,QAAO,MAAM,CAAC;AAAA,EAC3B;AAGA,MAAI,OAAO,IAAI,YAAY,UAAU;AACnC,UAAM,QAAQ,IAAI,QAAQ,MAAM,aAAa;AAC7C,QAAI,MAAO,QAAO,MAAM,CAAC;AAAA,EAC3B;AAEA,SAAO;AACT;AAMA,SAAS,oBAAoB,MAAc,UAA0B;AACnE,QAAM,WAAmC;AAAA,IACvC,YAAY;AAAA,IACZ,YAAY;AAAA,IACZ,YAAY;AAAA,IACZ,YAAY;AAAA,EACd;AACA,SAAO,SAAS,IAAI,KAAK,mBAAmB,QAAQ;AACtD;AA0BO,SAAS,sBAAsB,OAAiC;AACrE,QAAM,SAAS,cAAc,KAAK;AAClC,QAAM,iBAAiB,sBAAsB,KAAK;AAClD,QAAM,eAAe,iBAAiB,QAAQ,MAAM,UAAU,OAAO,KAAK;AAG1E,MAAI,SAA0B;AAAA,IAC5B,MAAM;AAAA,IACN,aAAa;AAAA,IACb,YAAY;AAAA,IACZ;AAAA,IACA,aAAa;AAAA,EACf;AAGA,MAAI,SAAS,OAAO,UAAU,UAAU;AACtC,UAAM,MAAM;AACZ,UAAM,OAAO,OAAO,IAAI,QAAQ,EAAE;AAElC,QAAI,KAAK,WAAW,OAAO,GAAG;AAC5B,aAAO,OAAO;AACd,aAAO,cAAc;AACrB,aAAO,cAAc,oBAAoB,MAAM,YAAY;AAC3D,aAAO;AAAA,IACT;AAAA,EACF;AAGA,MAAI,gBAAgB;AAElB,QAAI,kBAAkB,OAAO,iBAAiB,OAAO,mBAAmB,OAAO,mBAAmB,KAAK;AACrG,aAAO,OAAO;AACd,aAAO,cAAc;AACrB,aAAO,cAAc;AACrB,aAAO;AAAA,IACT;AAGA,QAAI,kBAAkB,KAAK;AACzB,aAAO,OAAO;AACd,aAAO,cAAc;AACrB,aAAO,cAAc;AACrB,aAAO;AAAA,IACT;AAAA,EACF;AAGA,MAAI,QAAQ;AAEV,QAAI,OAAO,WAAW,IAAI,GAAG;AAC3B,aAAO,OAAO,WAAW,eAAe,mBAAmB,aAAa;AACxE,aAAO,cAAc;AACrB,aAAO,aAAa,WAAW,eAAe;AAC9C,aAAO,cAAc,kBAAkB,MAAM,KAAK;AAClD,aAAO;AAAA,IACT;AAGA,QAAI,OAAO,WAAW,IAAI,GAAG;AAC3B,aAAO,OAAO,WAAW,eAAe,yBAAyB,SAAS;AAC1E,aAAO,cAAc;AACrB,aAAO,cAAc,kBAAkB,MAAM,KAAK;AAClD,aAAO;AAAA,IACT;AAGA,QAAI,OAAO,WAAW,IAAI,GAAG;AAC3B,aAAO,OAAO;AACd,aAAO,cAAc;AACrB,aAAO,cAAc;AACrB,aAAO;AAAA,IACT;AAGA,QAAI,OAAO,WAAW,IAAI,GAAG;AAC3B,aAAO,OAAO;AACd,aAAO,cAAc,WAAW,eAAe;AAC/C,aAAO,cAAc;AACrB,aAAO;AAAA,IACT;AAGA,QAAI,OAAO,WAAW,IAAI,GAAG;AAC3B,aAAO,OAAO;AACd,aAAO,cAAc;AACrB,aAAO,aAAa;AACpB,aAAO,cAAc;AACrB,aAAO;AAAA,IACT;AAAA,EACF;AAGA,QAAM,eAAe,aAAa,YAAY;AAG9C,MAAI,0DAA0D,KAAK,YAAY,GAAG;AAChF,WAAO,OAAO;AACd,WAAO,cAAc;AACrB,WAAO,cAAc;AACrB,WAAO;AAAA,EACT;AAGA,MAAI,wDAAwD,KAAK,YAAY,GAAG;AAC9E,WAAO,OAAO;AACd,WAAO,cAAc,aAAa,SAAS,SAAS,KAAK,aAAa,SAAS,SAAS;AACxF,WAAO,cAAc;AACrB,WAAO;AAAA,EACT;AAGA,MAAI,wDAAwD,KAAK,YAAY,GAAG;AAC9E,WAAO,OAAO;AACd,WAAO,cAAc;AACrB,WAAO,cAAc;AACrB,WAAO;AAAA,EACT;AAGA,MAAI,sDAAsD,KAAK,YAAY,GAAG;AAC5E,WAAO,OAAO;AACd,WAAO,cAAc;AACrB,WAAO,cAAc;AACrB,WAAO;AAAA,EACT;AAGA,MAAI,wDAAwD,KAAK,YAAY,GAAG;AAC9E,WAAO,OAAO;AACd,WAAO,cAAc;AACrB,WAAO,aAAa,aAAa,SAAS,WAAW,KAAK,aAAa,SAAS,QAAQ;AACxF,WAAO,cAAc;AACrB,WAAO;AAAA,EACT;AAEA,SAAO;AACT;AAKO,SAAS,gBAAgB,YAA6B,iBAAoC;AAC/F,SAAO;AAAA,IACL,MAAM,WAAW;AAAA,IACjB,SAAS;AAAA,IACT,aAAa,WAAW;AAAA,IACxB,WAAW,oBAAI,KAAK;AAAA,IACpB,QAAQ,WAAW;AAAA,IACnB,aAAa,WAAW;AAAA,EAC1B;AACF;AAMO,SAAS,kBAAkB,SAA8B;AAC9D,QAAM,YAAY,KAAK,IAAI;AAC3B,QAAM,WAAW,QAAQ,IAAI,OAAK,EAAE,EAAE,EAAE,KAAK,GAAG;AAChD,SAAO,WAAW,SAAS,IAAI,SAAS,UAAU,GAAG,EAAE,CAAC;AAC1D;AAOO,SAAS,iBAAiB,SAAgC;AAC/D,QAAM,MAAgB,CAAC;AACvB,aAAW,SAAS,SAAS;AAE3B,QAAI,KAAK,MAAM,EAAE;AAEjB,QAAI,MAAM,QAAQ,OAAO,UAAa,OAAO,MAAM,OAAO,EAAE,MAAM,MAAM,IAAI;AAC1E,UAAI,KAAK,OAAO,MAAM,OAAO,EAAE,CAAC;AAAA,IAClC;AAAA,EACF;AACA,SAAO,CAAC,GAAG,IAAI,IAAI,GAAG,CAAC;AACzB;AAKO,SAAS,kBAAkB,SAAgC;AAChE,SAAO,CAAC,GAAG,IAAI,IAAI,QAAQ,IAAI,WAAS,MAAM,KAAK,CAAC,CAAC;AACvD;","names":[]}