@pol-studios/powersync 1.0.1 → 1.0.3

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 (46) hide show
  1. package/dist/attachments/index.js +1 -1
  2. package/dist/{chunk-PANEMMTU.js → chunk-3AYXHQ4W.js} +17 -11
  3. package/dist/chunk-3AYXHQ4W.js.map +1 -0
  4. package/dist/{chunk-BJ36QDFN.js → chunk-7EMDVIZX.js} +1 -1
  5. package/dist/chunk-7EMDVIZX.js.map +1 -0
  6. package/dist/{chunk-MB2RC3NS.js → chunk-C2RSTGDC.js} +129 -89
  7. package/dist/chunk-C2RSTGDC.js.map +1 -0
  8. package/dist/{chunk-NPNBGCRC.js → chunk-EJ23MXPQ.js} +1 -1
  9. package/dist/{chunk-NPNBGCRC.js.map → chunk-EJ23MXPQ.js.map} +1 -1
  10. package/dist/{chunk-CHRTN5PF.js → chunk-FPTDATY5.js} +1 -1
  11. package/dist/chunk-FPTDATY5.js.map +1 -0
  12. package/dist/chunk-GMFDCVMZ.js +1285 -0
  13. package/dist/chunk-GMFDCVMZ.js.map +1 -0
  14. package/dist/chunk-OLHGI472.js +1 -0
  15. package/dist/chunk-OLHGI472.js.map +1 -0
  16. package/dist/{chunk-CFCK2LHI.js → chunk-OTJXIRWX.js} +45 -40
  17. package/dist/chunk-OTJXIRWX.js.map +1 -0
  18. package/dist/{chunk-GBGATW2S.js → chunk-V6LJ6MR2.js} +86 -95
  19. package/dist/chunk-V6LJ6MR2.js.map +1 -0
  20. package/dist/connector/index.d.ts +1 -1
  21. package/dist/connector/index.js +2 -2
  22. package/dist/core/index.js +2 -2
  23. package/dist/{index-D952Qr38.d.ts → index-Cb-NI0Ct.d.ts} +9 -2
  24. package/dist/index.d.ts +2 -2
  25. package/dist/index.js +8 -9
  26. package/dist/index.native.d.ts +1 -1
  27. package/dist/index.native.js +9 -10
  28. package/dist/index.web.d.ts +1 -1
  29. package/dist/index.web.js +9 -10
  30. package/dist/platform/index.js.map +1 -1
  31. package/dist/platform/index.native.js +1 -1
  32. package/dist/platform/index.web.js +1 -1
  33. package/dist/provider/index.d.ts +6 -1
  34. package/dist/provider/index.js +6 -6
  35. package/dist/sync/index.js +3 -3
  36. package/package.json +3 -1
  37. package/dist/chunk-42IJ25Q4.js +0 -45
  38. package/dist/chunk-42IJ25Q4.js.map +0 -1
  39. package/dist/chunk-BJ36QDFN.js.map +0 -1
  40. package/dist/chunk-CFCK2LHI.js.map +0 -1
  41. package/dist/chunk-CHRTN5PF.js.map +0 -1
  42. package/dist/chunk-GBGATW2S.js.map +0 -1
  43. package/dist/chunk-H7HZMI4H.js +0 -925
  44. package/dist/chunk-H7HZMI4H.js.map +0 -1
  45. package/dist/chunk-MB2RC3NS.js.map +0 -1
  46. package/dist/chunk-PANEMMTU.js.map +0 -1
@@ -1 +0,0 @@
1
- {"version":3,"sources":["../src/provider/context.ts","../src/provider/PowerSyncProvider.tsx","../src/provider/hooks.ts"],"sourcesContent":["/**\n * React Contexts for @pol-studios/powersync\n *\n * This module creates the React contexts used by the PowerSyncProvider.\n */\n\nimport { createContext } from 'react';\nimport type {\n PowerSyncContextValue,\n SyncStatusContextValue,\n ConnectionHealthContextValue,\n SyncMetricsContextValue,\n DEFAULT_SYNC_STATUS,\n DEFAULT_CONNECTION_HEALTH,\n DEFAULT_SYNC_METRICS,\n} from './types';\nimport type { AttachmentQueue } from '../attachments/attachment-queue';\n\n// ─── Main PowerSync Context ──────────────────────────────────────────────────\n\n/**\n * Main context for PowerSync database instance and related state.\n *\n * Provides access to:\n * - PowerSync database instance\n * - SupabaseConnector instance\n * - AttachmentQueue instance (if configured)\n * - Initialization state\n *\n * @example\n * ```typescript\n * const { db, isReady, error } = useContext(PowerSyncContext);\n * ```\n */\nexport const PowerSyncContext = createContext<PowerSyncContextValue | null>(null);\n\nPowerSyncContext.displayName = 'PowerSyncContext';\n\n// ─── Sync Status Context ─────────────────────────────────────────────────────\n\n/**\n * Context for sync status updates.\n *\n * Provides access to:\n * - Current sync status (connected, syncing, etc.)\n * - Pending mutations count\n * - Paused state\n * - Last synced timestamp\n *\n * @example\n * ```typescript\n * const { status, pendingCount, isPaused } = useContext(SyncStatusContext);\n * ```\n */\nexport const SyncStatusContext = createContext<SyncStatusContextValue | null>(null);\n\nSyncStatusContext.displayName = 'SyncStatusContext';\n\n// ─── Connection Health Context ───────────────────────────────────────────────\n\n/**\n * Context for connection health monitoring.\n *\n * Provides access to:\n * - Health status (healthy, degraded, disconnected)\n * - Latency measurements\n * - Health check timestamps\n * - Failure counts\n *\n * @example\n * ```typescript\n * const { health } = useContext(ConnectionHealthContext);\n * if (health.status === 'degraded') {\n * showWarning('Connection is slow');\n * }\n * ```\n */\nexport const ConnectionHealthContext = createContext<ConnectionHealthContextValue | null>(null);\n\nConnectionHealthContext.displayName = 'ConnectionHealthContext';\n\n// ─── Sync Metrics Context ────────────────────────────────────────────────────\n\n/**\n * Context for sync metrics and statistics.\n *\n * Provides access to:\n * - Sync operation counts\n * - Sync durations\n * - Data transfer amounts\n * - Error tracking\n *\n * @example\n * ```typescript\n * const { metrics } = useContext(SyncMetricsContext);\n * console.log(`Total syncs: ${metrics.totalSyncs}`);\n * ```\n */\nexport const SyncMetricsContext = createContext<SyncMetricsContextValue | null>(null);\n\nSyncMetricsContext.displayName = 'SyncMetricsContext';\n\n// ─── Attachment Queue Context ────────────────────────────────────────────────\n\n/**\n * Context for the attachment queue (if configured).\n *\n * Provides direct access to the AttachmentQueue instance for:\n * - Checking attachment sync stats\n * - Pausing/resuming downloads\n * - Getting local URIs for attachments\n *\n * @example\n * ```typescript\n * const attachmentQueue = useContext(AttachmentQueueContext);\n * if (attachmentQueue) {\n * const stats = await attachmentQueue.getStats();\n * console.log(`Downloaded: ${stats.syncedCount}/${stats.totalExpected}`);\n * }\n * ```\n */\nexport const AttachmentQueueContext = createContext<AttachmentQueue | null>(null);\n\nAttachmentQueueContext.displayName = 'AttachmentQueueContext';\n","/**\n * PowerSyncProvider Component for @pol-studios/powersync\n *\n * Main provider component that initializes and manages the PowerSync database,\n * connector, attachment queue, and all monitoring services.\n */\n\nimport React, {\n useEffect,\n useState,\n useRef,\n useMemo,\n useCallback,\n} from 'react';\nimport type { Session } from '@supabase/supabase-js';\nimport type { AbstractPowerSyncDatabase, SyncStatus, ConnectionHealth, SyncMetrics, CrudEntry, FailedTransaction, CompletedTransaction, SyncMode } from '../core/types';\nimport { SupabaseConnector } from '../connector/supabase-connector';\nimport { extractEntityIds, extractTableNames, createSyncError } from '../core/errors';\nimport { AttachmentQueue } from '../attachments/attachment-queue';\nimport { SyncStatusTracker } from '../sync/status-tracker';\nimport { MetricsCollector } from '../sync/metrics-collector';\nimport { HealthMonitor } from '../sync/health-monitor';\nimport {\n PowerSyncContext,\n SyncStatusContext,\n ConnectionHealthContext,\n SyncMetricsContext,\n AttachmentQueueContext,\n} from './context';\nimport type {\n PowerSyncProviderProps,\n PowerSyncContextValue,\n SyncStatusContextValue,\n ConnectionHealthContextValue,\n SyncMetricsContextValue,\n} from './types';\nimport {\n DEFAULT_SYNC_STATUS,\n DEFAULT_CONNECTION_HEALTH,\n DEFAULT_SYNC_METRICS,\n} from './types';\n\n/**\n * PowerSyncProvider initializes and manages the PowerSync database and related services.\n *\n * Features:\n * - Initializes PowerSync database using platform adapter\n * - Creates and manages SupabaseConnector\n * - Connects/disconnects based on auth state\n * - Tracks sync status and connection health\n * - Optionally initializes AttachmentQueue\n * - Provides all contexts to children\n * - Handles cleanup on unmount\n *\n * @example\n * ```tsx\n * import { PowerSyncProvider, usePowerSync, useSyncStatus } from '@pol-studios/powersync';\n *\n * function App() {\n * return (\n * <PowerSyncProvider\n * config={{\n * platform: createNativePlatformAdapter(logger),\n * schema: AppSchema,\n * powerSyncUrl: 'https://your-powersync.com',\n * supabaseClient: supabase,\n * }}\n * onReady={() => console.log('PowerSync ready!')}\n * onError={(err) => console.error('PowerSync error:', err)}\n * >\n * <MainApp />\n * </PowerSyncProvider>\n * );\n * }\n * ```\n */\nexport function PowerSyncProvider<TSchema = unknown>({\n config,\n children,\n onReady,\n onError,\n onSyncStatusChange,\n}: PowerSyncProviderProps<TSchema>): React.ReactElement {\n const {\n platform,\n schema,\n powerSyncUrl,\n supabaseClient,\n dbFilename = 'powersync.db',\n connector: connectorConfig,\n attachments: attachmentConfig,\n sync: syncConfig,\n } = config;\n\n const logger = platform.logger;\n\n // Merge sync config with defaults\n const mergedSyncConfig = {\n autoConnect: syncConfig?.autoConnect ?? true,\n syncInterval: syncConfig?.syncInterval ?? 0,\n enableHealthMonitoring: syncConfig?.enableHealthMonitoring ?? true,\n enableMetrics: syncConfig?.enableMetrics ?? true,\n };\n\n // ─── State ─────────────────────────────────────────────────────────────────\n\n const [db, setDb] = useState<AbstractPowerSyncDatabase | null>(null);\n const [connector, setConnector] = useState<SupabaseConnector | null>(null);\n const [attachmentQueue, setAttachmentQueue] = useState<AttachmentQueue | null>(null);\n const [isReady, setIsReady] = useState(false);\n const [isInitializing, setIsInitializing] = useState(true);\n const [error, setError] = useState<Error | null>(null);\n const [session, setSession] = useState<Session | null>(null);\n\n // Sync status state\n const [syncStatus, setSyncStatus] = useState<SyncStatus>(DEFAULT_SYNC_STATUS);\n const [pendingMutations, setPendingMutations] = useState<CrudEntry[]>([]);\n // Combined sync mode state to ensure atomic updates and prevent React batching issues\n const [syncModeState, setSyncModeState] = useState<{ loaded: boolean; mode: SyncMode }>({\n loaded: false,\n mode: 'push-pull',\n });\n const [lastSyncedAt, setLastSyncedAt] = useState<Date | null>(null);\n const [connectionError, setConnectionError] = useState<Error | null>(null);\n const [failedTransactions, setFailedTransactions] = useState<FailedTransaction[]>([]);\n const [completedTransactions, setCompletedTransactions] = useState<CompletedTransaction[]>([]);\n\n // Health and metrics state\n const [connectionHealth, setConnectionHealth] = useState<ConnectionHealth>(DEFAULT_CONNECTION_HEALTH);\n const [syncMetrics, setSyncMetrics] = useState<SyncMetrics>(DEFAULT_SYNC_METRICS);\n\n // ─── Refs ──────────────────────────────────────────────────────────────────\n\n const statusTrackerRef = useRef<SyncStatusTracker | null>(null);\n const metricsCollectorRef = useRef<MetricsCollector | null>(null);\n const healthMonitorRef = useRef<HealthMonitor | null>(null);\n const attachmentQueueRef = useRef<AttachmentQueue | null>(null);\n const listenerUnsubscribeRef = useRef<(() => void) | null>(null);\n const wasSyncingRef = useRef(false);\n const initializingRef = useRef(false);\n // Track when database is being/has been closed to prevent operations on closed db\n const dbClosedRef = useRef(false);\n\n // ─── Initialize Monitoring Services ────────────────────────────────────────\n\n useEffect(() => {\n // Create status tracker\n const statusTracker = new SyncStatusTracker(\n platform.storage,\n logger,\n {\n onStatusChange: (status) => {\n setSyncStatus(status);\n setLastSyncedAt(status.lastSyncedAt);\n onSyncStatusChange?.(status);\n },\n }\n );\n statusTrackerRef.current = statusTracker;\n\n // Create metrics collector\n const metricsCollector = new MetricsCollector(\n platform.storage,\n logger,\n {\n onMetricsChange: setSyncMetrics,\n }\n );\n metricsCollectorRef.current = metricsCollector;\n\n // Create health monitor\n const healthMonitor = new HealthMonitor(\n logger,\n {\n onHealthChange: setConnectionHealth,\n }\n );\n healthMonitorRef.current = healthMonitor;\n\n // Initialize async with timeout fallback\n const initPromise = Promise.all([\n statusTracker.init(),\n metricsCollector.init(),\n ]);\n\n const timeoutPromise = new Promise<[void, void]>((resolve) => {\n setTimeout(() => {\n logger.warn('[PowerSyncProvider] Sync mode state load timed out, using default (push-pull)');\n resolve([undefined, undefined]);\n }, 5000); // 5 second timeout\n });\n\n Promise.race([initPromise, timeoutPromise]).then(() => {\n logger.info('[PowerSyncProvider] Sync mode state loaded:', {\n mode: statusTracker.getSyncMode(),\n });\n // Use single state update to ensure atomic change and prevent React batching issues\n setSyncModeState({ loaded: true, mode: statusTracker.getSyncMode() });\n });\n\n // Cleanup\n return () => {\n statusTracker.dispose();\n metricsCollector.dispose();\n healthMonitor.dispose();\n };\n }, [platform, logger, onSyncStatusChange]);\n\n // ─── Auth State Listener ───────────────────────────────────────────────────\n\n useEffect(() => {\n logger.debug('[PowerSyncProvider] Setting up auth listener');\n\n // Get initial session\n supabaseClient.auth.getSession().then(({ data: { session: initialSession } }) => {\n logger.debug('[PowerSyncProvider] Initial session:', !!initialSession);\n setSession(initialSession);\n });\n\n // Listen for auth changes\n const { data: { subscription } } = supabaseClient.auth.onAuthStateChange(\n (_event, newSession) => {\n logger.debug('[PowerSyncProvider] Auth state changed, hasSession:', !!newSession);\n setSession(newSession);\n }\n );\n\n return () => {\n subscription.unsubscribe();\n };\n }, [supabaseClient, logger]);\n\n // ─── Database Initialization ───────────────────────────────────────────────\n\n useEffect(() => {\n // Guard against StrictMode double-mounting\n // The ref persists across the quick unmount/remount cycle\n if (initializingRef.current) {\n logger.debug('[PowerSyncProvider] Already initializing, skipping...');\n return;\n }\n initializingRef.current = true;\n\n // Use a controller object to track cancellation\n const controller = { cancelled: false };\n\n const initDatabase = async () => {\n try {\n logger.info('[PowerSyncProvider] Initializing database...');\n\n // Create database using platform adapter (includes init())\n const database = await platform.createDatabase({\n dbFilename,\n schema,\n });\n\n // Check if cancelled during async operation\n if (controller.cancelled) {\n logger.debug('[PowerSyncProvider] Init cancelled, closing database...');\n await database.close();\n initializingRef.current = false;\n return;\n }\n\n logger.info('[PowerSyncProvider] Database initialized');\n setDb(database);\n setIsReady(true);\n setIsInitializing(false);\n\n // Set database on health monitor\n healthMonitorRef.current?.setDatabase(database);\n\n // Start health monitoring if enabled\n if (mergedSyncConfig.enableHealthMonitoring) {\n healthMonitorRef.current?.start();\n }\n\n onReady?.();\n } catch (err) {\n const initError = err instanceof Error ? err : new Error(String(err));\n logger.error('[PowerSyncProvider] Initialization failed:', initError);\n\n // Only update state if not cancelled\n if (!controller.cancelled) {\n setError(initError);\n setIsInitializing(false);\n onError?.(initError);\n }\n\n // Reset ref on error so retry is possible\n initializingRef.current = false;\n }\n };\n\n initDatabase();\n\n return () => {\n // Mark as cancelled - the async operation will check this\n controller.cancelled = true;\n // Reset initializingRef so StrictMode double-mount can reinitialize\n initializingRef.current = false;\n };\n }, [platform, dbFilename, schema, logger, mergedSyncConfig.enableHealthMonitoring, onReady, onError]);\n\n // ─── Connect to PowerSync ──────────────────────────────────────────────────\n\n useEffect(() => {\n // Console.log fallback for critical diagnostic point (always visible regardless of logger level)\n if (__DEV__) {\n console.log('[PowerSyncProvider] Connect effect triggered:', {\n hasDb: !!db,\n hasSession: !!session,\n autoConnect: mergedSyncConfig.autoConnect,\n syncModeState,\n });\n }\n\n // Use info level for critical connect path so it always appears\n logger.info('[PowerSyncProvider] Connect effect - db:', !!db, 'session:', !!session, 'autoConnect:', mergedSyncConfig.autoConnect, 'syncModeLoaded:', syncModeState.loaded, 'syncMode:', syncModeState.mode);\n\n // Individual checks with logging for each early return condition\n if (!db) {\n logger.debug('[PowerSyncProvider] Connect effect - waiting for db');\n return;\n }\n if (!session) {\n logger.debug('[PowerSyncProvider] Connect effect - no session');\n return;\n }\n if (!mergedSyncConfig.autoConnect) {\n logger.debug('[PowerSyncProvider] Connect effect - autoConnect disabled');\n return;\n }\n\n // Wait for sync mode state to be loaded before deciding\n if (!syncModeState.loaded) {\n logger.info('[PowerSyncProvider] Waiting for sync mode state to load...');\n return;\n }\n\n // Skip connect if offline mode\n if (syncModeState.mode === 'offline') {\n logger.debug('[PowerSyncProvider] Skipping connect - offline mode');\n return;\n }\n\n const connectPowerSync = async () => {\n try {\n setConnectionError(null);\n // Create connector with failure callbacks\n const statusTracker = statusTrackerRef.current;\n const newConnector = new SupabaseConnector({\n supabaseClient,\n powerSyncUrl,\n schemaRouter: connectorConfig?.schemaRouter,\n crudHandler: connectorConfig?.crudHandler,\n logger,\n // Check if uploads should be performed based on sync mode\n shouldUpload: () => statusTrackerRef.current?.shouldUpload() ?? true,\n // Clear failures when transaction succeeds\n onTransactionSuccess: (entries) => {\n if (!statusTracker) return;\n const entityIds = extractEntityIds(entries);\n entityIds.forEach(id => {\n // Find and clear failures affecting this entity\n const failures = statusTracker.getFailuresForEntity(id);\n failures.forEach(f => statusTracker.clearFailure(f.id));\n });\n // Update local state\n setFailedTransactions(statusTracker.getFailedTransactions());\n },\n // Record failures when transaction fails\n onTransactionFailure: (entries, error, classified) => {\n if (!statusTracker) return;\n statusTracker.recordTransactionFailure(\n entries,\n createSyncError(classified, error.message),\n classified.isPermanent,\n extractEntityIds(entries),\n extractTableNames(entries)\n );\n // Update local state\n setFailedTransactions(statusTracker.getFailedTransactions());\n },\n // Record completed transactions\n onTransactionComplete: (entries) => {\n if (!statusTracker) return;\n statusTracker.recordTransactionComplete(entries);\n // Update local state\n setCompletedTransactions(statusTracker.getCompletedTransactions());\n },\n });\n\n setConnector(newConnector);\n\n // Check if already connected\n if (db.connected) {\n logger.debug('[PowerSyncProvider] Already connected, reconnecting...');\n await db.disconnect();\n }\n\n logger.info('[PowerSyncProvider] Connecting to PowerSync...');\n await db.connect(newConnector);\n logger.info('[PowerSyncProvider] Connected successfully');\n\n // Reset reconnect attempts on successful connection\n healthMonitorRef.current?.resetReconnectAttempts();\n } catch (err) {\n const connectError = err instanceof Error ? err : new Error(String(err));\n logger.error('[PowerSyncProvider] Connection failed:', connectError);\n setConnectionError(connectError);\n healthMonitorRef.current?.recordReconnectAttempt();\n }\n };\n\n connectPowerSync();\n }, [db, session, powerSyncUrl, supabaseClient, connectorConfig, mergedSyncConfig.autoConnect, syncModeState, logger]);\n\n // ─── Status Listener ───────────────────────────────────────────────────────\n\n useEffect(() => {\n if (!db) return;\n\n // Set initial status\n const initialStatus = db.currentStatus;\n if (initialStatus) {\n statusTrackerRef.current?.handleStatusChange(initialStatus);\n }\n\n // Register listener\n const unsubscribe = db.registerListener({\n statusChanged: (status) => {\n statusTrackerRef.current?.handleStatusChange(status as Record<string, unknown>);\n\n // Track sync timing for metrics\n const dataFlow = (status as Record<string, unknown>).dataFlowStatus as Record<string, boolean> | undefined;\n const isDownloading = dataFlow?.downloading ?? false;\n const progress = (status as Record<string, unknown>).downloadProgress as Record<string, number> | undefined;\n\n if (isDownloading && !wasSyncingRef.current) {\n metricsCollectorRef.current?.markSyncStart();\n }\n\n if (!isDownloading && wasSyncingRef.current) {\n const duration = metricsCollectorRef.current?.markSyncEnd();\n if (duration !== null && duration !== undefined) {\n metricsCollectorRef.current?.recordSync({\n durationMs: duration,\n success: true,\n operationsDownloaded: progress?.totalOperations ?? 0,\n });\n }\n }\n\n wasSyncingRef.current = isDownloading;\n },\n });\n\n listenerUnsubscribeRef.current = unsubscribe;\n\n return () => {\n unsubscribe();\n listenerUnsubscribeRef.current = null;\n };\n }, [db]);\n\n // ─── Watch ps_crud for Real-time Pending Count ─────────────────────────────\n\n // Track previous pending count to detect when all uploads complete\n const prevPendingCountRef = useRef<number>(0);\n\n /**\n * Parse raw ps_crud row into a CrudEntry object.\n * The ps_crud table has columns: id, tx_id, data\n * The data column is a JSON string with: op, type (table name), and operation data\n */\n const parseCrudRow = useCallback((row: { id: string; tx_id: number | null; data: string }): CrudEntry | null => {\n try {\n const parsed = JSON.parse(row.data);\n // Map PowerSync operation types to our CrudEntry op types\n const opMap: Record<string, 'PUT' | 'PATCH' | 'DELETE'> = {\n 'PUT': 'PUT',\n 'PATCH': 'PATCH',\n 'DELETE': 'DELETE',\n };\n const op = opMap[parsed.op] ?? 'PUT';\n\n // Extract entity ID: prefer parsed.data.id (actual record UUID), fallback to parsed.id, then row.id\n const entityId = (parsed.data?.id as string) ?? parsed.id ?? row.id;\n\n return {\n id: entityId,\n clientId: parseInt(row.id, 10) || 0,\n op,\n table: parsed.type ?? 'unknown',\n opData: parsed.data ?? undefined, // Ensure null becomes undefined\n transactionId: row.tx_id ?? undefined,\n };\n } catch (e) {\n logger.warn('[PowerSyncProvider] Failed to parse CRUD entry:', e, row);\n return null;\n }\n }, [logger]);\n\n useEffect(() => {\n if (!db) return;\n\n const abortController = new AbortController();\n\n // Watch the ps_crud table to get real-time pending upload count\n // This fires on local mutations, not just sync status changes\n db.watch(\n 'SELECT COUNT(*) as count FROM ps_crud',\n [],\n {\n onResult: async (results: { rows?: { _array?: Array<{ count: number }> } }) => {\n // Guard against operations on closed database\n if (dbClosedRef.current) {\n logger.debug('[PowerSyncProvider] Skipping watch callback - database is closed');\n return;\n }\n\n const count = results.rows?._array?.[0]?.count ?? 0;\n const prevCount = prevPendingCountRef.current;\n prevPendingCountRef.current = count;\n\n // Fetch actual CRUD entries when count > 0\n let mutations: CrudEntry[] = [];\n if (count > 0) {\n try {\n // Double-check database is still open before query\n if (dbClosedRef.current) {\n logger.debug('[PowerSyncProvider] Skipping getAll - database is closed');\n return;\n }\n // Query the actual ps_crud rows to get real data\n const rows = await db.getAll<{ id: string; tx_id: number | null; data: string }>(\n 'SELECT id, tx_id, data FROM ps_crud ORDER BY id ASC'\n );\n mutations = rows\n .map(parseCrudRow)\n .filter((entry): entry is CrudEntry => entry !== null);\n } catch (e) {\n // Check if this is a \"database is not open\" error and handle gracefully\n const errorMessage = e instanceof Error ? e.message : String(e);\n if (errorMessage.includes('not open') || errorMessage.includes('closed')) {\n logger.debug('[PowerSyncProvider] Database closed during CRUD fetch, ignoring');\n return;\n }\n logger.warn('[PowerSyncProvider] Failed to fetch CRUD entries:', e);\n // Fall back to empty array - count will still be accurate from the watch\n }\n }\n\n // Final check before updating state\n if (dbClosedRef.current) {\n return;\n }\n\n statusTrackerRef.current?.updatePendingMutations(mutations);\n setPendingMutations(mutations);\n\n // Clear force upload flag when pending count drops to 0\n // This ensures all pending transactions get uploaded during \"Sync Now\"\n // and then the flag is cleared when done\n if (prevCount > 0 && count === 0) {\n logger.debug('[PowerSyncProvider] All pending uploads complete, clearing force flag');\n statusTrackerRef.current?.clearForceNextUpload();\n }\n },\n onError: (error: Error) => {\n // Check if this is a \"database is not open\" error and handle gracefully\n const errorMessage = error.message ?? '';\n if (errorMessage.includes('not open') || errorMessage.includes('closed') || dbClosedRef.current) {\n logger.debug('[PowerSyncProvider] Watch error during database close, ignoring');\n return;\n }\n logger.warn('[PowerSyncProvider] Error watching ps_crud:', error);\n },\n },\n {\n signal: abortController.signal,\n throttleMs: 100, // Debounce to avoid excessive updates during bulk operations\n }\n );\n\n return () => {\n abortController.abort();\n };\n }, [db, logger, parseCrudRow]);\n\n // ─── Attachment Queue Initialization ───────────────────────────────────────\n\n useEffect(() => {\n if (!db || !attachmentConfig || attachmentQueueRef.current) {\n return;\n }\n\n const initAttachmentQueue = async () => {\n try {\n logger.info('[PowerSyncProvider] Initializing attachment queue...');\n\n const queue = new AttachmentQueue({\n powersync: db,\n platform,\n config: attachmentConfig,\n });\n\n await queue.init();\n\n attachmentQueueRef.current = queue;\n setAttachmentQueue(queue);\n logger.info('[PowerSyncProvider] Attachment queue initialized');\n } catch (err) {\n logger.error('[PowerSyncProvider] Attachment queue initialization failed:', err);\n }\n };\n\n initAttachmentQueue();\n\n return () => {\n attachmentQueueRef.current?.dispose();\n attachmentQueueRef.current = null;\n };\n }, [db, attachmentConfig, platform, logger]);\n\n // ─── Cleanup ───────────────────────────────────────────────────────────────\n\n useEffect(() => {\n // Reset closed flag when db changes (new db instance)\n if (db) {\n dbClosedRef.current = false;\n }\n\n return () => {\n // Cleanup on unmount\n logger.info('[PowerSyncProvider] Cleaning up...');\n\n // Mark database as closed FIRST to prevent any pending operations\n dbClosedRef.current = true;\n\n listenerUnsubscribeRef.current?.();\n attachmentQueueRef.current?.dispose();\n healthMonitorRef.current?.stop();\n\n if (db) {\n // Use async IIFE to properly sequence disconnect and close\n (async () => {\n try {\n await db.disconnect();\n await db.close();\n } catch (err) {\n // Only log if it's not a \"database already closed\" error\n const errorMessage = err instanceof Error ? err.message : String(err);\n if (!errorMessage.includes('not open') && !errorMessage.includes('closed')) {\n logger.warn('[PowerSyncProvider] Error during cleanup:', err);\n }\n }\n })();\n }\n };\n }, [db, logger]);\n\n // ─── Clear Failure Functions ────────────────────────────────────────────────\n\n const clearFailure = useCallback((failureId: string) => {\n const tracker = statusTrackerRef.current;\n if (!tracker) {\n logger.warn('[PowerSyncProvider] Cannot clear failure - tracker not initialized');\n return;\n }\n tracker.clearFailure(failureId);\n setFailedTransactions(tracker.getFailedTransactions());\n }, [logger]);\n\n const clearAllFailures = useCallback(() => {\n const tracker = statusTrackerRef.current;\n if (!tracker) {\n logger.warn('[PowerSyncProvider] Cannot clear failures - tracker not initialized');\n return;\n }\n tracker.clearAllFailures();\n setFailedTransactions(tracker.getFailedTransactions());\n }, [logger]);\n\n const clearCompletedHistory = useCallback(() => {\n const tracker = statusTrackerRef.current;\n if (!tracker) {\n logger.warn('[PowerSyncProvider] Cannot clear completed history - tracker not initialized');\n return;\n }\n tracker.clearCompletedHistory();\n setCompletedTransactions(tracker.getCompletedTransactions());\n }, [logger]);\n\n const setSyncMode = useCallback(async (mode: SyncMode) => {\n const tracker = statusTrackerRef.current;\n if (!tracker) {\n logger.warn('[PowerSyncProvider] Cannot set sync mode - tracker not initialized');\n return;\n }\n await tracker.setSyncMode(mode);\n setSyncModeState({ loaded: true, mode });\n }, [logger]);\n\n const setForceNextUpload = useCallback((force: boolean) => {\n const tracker = statusTrackerRef.current;\n if (!tracker) {\n logger.warn('[PowerSyncProvider] Cannot set force upload - tracker not initialized');\n return;\n }\n tracker.setForceNextUpload(force);\n }, [logger]);\n\n // ─── Discard Mutation Functions ────────────────────────────────────────────\n\n const discardPendingMutation = useCallback(async (clientId: number) => {\n if (!db || !connector) {\n logger.warn('[PowerSync] Cannot discard - not initialized');\n return;\n }\n if (syncStatus.uploading) {\n throw new Error('Cannot discard while upload is in progress');\n }\n\n logger.info('[PowerSync] Discarding pending mutation:', clientId);\n\n // Disconnect to ensure no active transaction\n await db.disconnect();\n\n try {\n await db.execute('DELETE FROM ps_crud WHERE id = ?', [clientId]);\n logger.info('[PowerSync] Mutation discarded successfully');\n } finally {\n // Always reconnect\n await db.connect(connector);\n }\n }, [db, connector, syncStatus.uploading, logger]);\n\n const discardAllPendingMutations = useCallback(async () => {\n if (!db || !connector) {\n logger.warn('[PowerSync] Cannot discard all - not initialized');\n return;\n }\n if (syncStatus.uploading) {\n throw new Error('Cannot discard while upload is in progress');\n }\n\n logger.info('[PowerSync] Discarding all pending mutations');\n\n await db.disconnect();\n\n try {\n await db.execute('DELETE FROM ps_crud');\n logger.info('[PowerSync] All mutations discarded successfully');\n } finally {\n await db.connect(connector);\n }\n }, [db, connector, syncStatus.uploading, logger]);\n\n // ─── Context Values ────────────────────────────────────────────────────────\n\n const powerSyncContextValue = useMemo<PowerSyncContextValue<TSchema>>(\n () => ({\n db,\n connector,\n attachmentQueue,\n isReady,\n isInitializing,\n error,\n schema,\n platform,\n }),\n [db, connector, attachmentQueue, isReady, isInitializing, error, schema, platform]\n );\n\n const syncStatusContextValue = useMemo<SyncStatusContextValue>(\n () => ({\n status: syncStatus,\n pendingMutations,\n pendingCount: pendingMutations.length,\n // Expose uploading/downloading directly from syncStatus for reliable activity detection\n isUploading: syncStatus.uploading,\n isDownloading: syncStatus.downloading,\n isPaused: syncModeState.mode === 'offline',\n syncMode: syncModeState.mode,\n lastSyncedAt,\n // Connection error for consumers to display\n connectionError,\n // Failed transaction fields\n failedTransactions,\n hasUploadErrors: failedTransactions.length > 0,\n permanentErrorCount: failedTransactions.filter(f => f.isPermanent).length,\n // Clear failure functions\n clearFailure,\n clearAllFailures,\n // Completed transaction fields\n completedTransactions,\n clearCompletedHistory,\n // Sync mode control functions\n setSyncMode,\n setForceNextUpload,\n // Discard mutation functions\n discardPendingMutation,\n discardAllPendingMutations,\n }),\n [syncStatus, pendingMutations, syncModeState.mode, lastSyncedAt, connectionError, failedTransactions, clearFailure, clearAllFailures, completedTransactions, clearCompletedHistory, setSyncMode, setForceNextUpload, discardPendingMutation, discardAllPendingMutations]\n );\n\n const connectionHealthContextValue = useMemo<ConnectionHealthContextValue>(\n () => ({ health: connectionHealth }),\n [connectionHealth]\n );\n\n const syncMetricsContextValue = useMemo<SyncMetricsContextValue>(\n () => ({ metrics: syncMetrics }),\n [syncMetrics]\n );\n\n // ─── Render ────────────────────────────────────────────────────────────────\n\n return (\n <PowerSyncContext.Provider value={powerSyncContextValue as PowerSyncContextValue}>\n <SyncStatusContext.Provider value={syncStatusContextValue}>\n <ConnectionHealthContext.Provider value={connectionHealthContextValue}>\n <SyncMetricsContext.Provider value={syncMetricsContextValue}>\n <AttachmentQueueContext.Provider value={attachmentQueue}>\n {children}\n </AttachmentQueueContext.Provider>\n </SyncMetricsContext.Provider>\n </ConnectionHealthContext.Provider>\n </SyncStatusContext.Provider>\n </PowerSyncContext.Provider>\n );\n}\n","/**\n * React Hooks for @pol-studios/powersync\n *\n * This module provides React hooks for accessing PowerSync functionality\n * within the provider's context.\n */\n\nimport { useContext, useCallback, useMemo, useRef, useState, useEffect } from 'react';\nimport type {\n AbstractPowerSyncDatabase,\n SyncStatus,\n ConnectionHealth,\n SyncMetrics,\n CrudEntry,\n EntitySyncState,\n FailedTransaction,\n CompletedTransaction,\n SyncError,\n} from '../core/types';\nimport type { PlatformAdapter } from '../platform/types';\nimport type { PowerSyncContextValue, SyncStatusContextValue, ConnectionHealthContextValue, SyncMetricsContextValue } from './types';\nimport type { SyncScope, SyncControlActions } from '../sync/types';\nimport type { AttachmentQueue } from '../attachments/attachment-queue';\nimport type { SupabaseConnector } from '../connector/supabase-connector';\nimport {\n PowerSyncContext,\n SyncStatusContext,\n ConnectionHealthContext,\n SyncMetricsContext,\n AttachmentQueueContext,\n} from './context';\nimport type { SyncMode } from '../core/types';\n// Note: STORAGE_KEY_PAUSED and STORAGE_KEY_SYNC_MODE are handled by the status tracker\n\n// ─── Main Hook ───────────────────────────────────────────────────────────────\n\n/**\n * Hook to access the PowerSync database and related services.\n *\n * @returns PowerSync context value with database, connector, and state\n * @throws Error if used outside of PowerSyncProvider\n *\n * @example\n * ```typescript\n * function MyComponent() {\n * const { db, isReady, error } = usePowerSync();\n *\n * if (!isReady) return <LoadingSpinner />;\n * if (error) return <Error message={error.message} />;\n *\n * // Use db for queries\n * const users = await db.getAll('SELECT * FROM users');\n * }\n * ```\n */\nexport function usePowerSync<TSchema = unknown>(): PowerSyncContextValue<TSchema> {\n const context = useContext(PowerSyncContext);\n\n if (!context) {\n throw new Error('usePowerSync must be used within a PowerSyncProvider');\n }\n\n return context as PowerSyncContextValue<TSchema>;\n}\n\n// ─── Sync Status Hook ────────────────────────────────────────────────────────\n\n/**\n * Hook to access the current sync status.\n *\n * @returns Sync status with connection state, pending uploads, and progress\n * @throws Error if used outside of PowerSyncProvider\n *\n * @example\n * ```typescript\n * function SyncIndicator() {\n * const { status, pendingCount, isPaused } = useSyncStatus();\n *\n * if (status.downloading) {\n * const { current, target, percentage } = status.downloadProgress ?? {};\n * return <Progress value={percentage} label={`${current}/${target}`} />;\n * }\n *\n * if (pendingCount > 0) {\n * return <Badge>{pendingCount} pending uploads</Badge>;\n * }\n *\n * return <Text>Synced</Text>;\n * }\n * ```\n */\nexport function useSyncStatus(): SyncStatusContextValue {\n const context = useContext(SyncStatusContext);\n\n if (!context) {\n throw new Error('useSyncStatus must be used within a PowerSyncProvider');\n }\n\n return context;\n}\n\n// ─── Sync Control Hook ───────────────────────────────────────────────────────\n\n/**\n * Hook to control sync operations.\n *\n * @returns Actions for triggering, pausing, and resuming sync\n *\n * @example\n * ```typescript\n * function SyncControls() {\n * const { triggerSync, syncNow, pause, resume, disconnect, setSyncMode } = useSyncControl();\n * const { isPaused, syncMode } = useSyncStatus();\n *\n * return (\n * <View>\n * <Button onPress={syncNow}>Sync Now</Button>\n * {isPaused ? (\n * <Button onPress={resume}>Resume</Button>\n * ) : (\n * <Button onPress={pause}>Pause</Button>\n * )}\n * </View>\n * );\n * }\n * ```\n */\nexport function useSyncControl(): SyncControlActions {\n const { db, connector, platform } = usePowerSync();\n const { setSyncMode: setContextSyncMode, setForceNextUpload } = useSyncStatus();\n const scopeRef = useRef<SyncScope | null>(null);\n\n const setSyncMode = useCallback(async (mode: SyncMode) => {\n // Update sync mode via context (which updates the status tracker)\n await setContextSyncMode(mode);\n\n if (mode === 'offline') {\n // Disconnect when going offline\n if (db?.connected) {\n platform.logger.info('[useSyncControl] Mode changed to offline - disconnecting');\n await db.disconnect();\n }\n } else if (db && connector && !db.connected) {\n // Reconnect when leaving offline mode\n platform.logger.info('[useSyncControl] Mode changed to', mode, '- reconnecting');\n await db.connect(connector);\n }\n }, [db, connector, platform, setContextSyncMode]);\n\n const syncNow = useCallback(async () => {\n if (!db || !connector) {\n platform.logger.warn('[useSyncControl] Cannot sync - database not initialized');\n return;\n }\n\n // Set force flag to ensure uploads happen regardless of mode\n setForceNextUpload(true);\n\n platform.logger.info('[useSyncControl] Sync Now triggered - forcing full sync');\n\n // Reconnect to trigger sync cycle\n if (db.connected) {\n await db.disconnect();\n }\n\n await db.connect(connector);\n platform.logger.info('[useSyncControl] Connected, sync should start automatically');\n }, [db, connector, platform, setForceNextUpload]);\n\n const triggerSync = useCallback(async () => {\n if (!db || !connector) {\n platform.logger.warn('[useSyncControl] Cannot trigger sync - not initialized');\n return;\n }\n\n // Set sync mode to push-pull via context (keeps tracker state in sync)\n await setContextSyncMode('push-pull');\n\n // Disconnect and reconnect to force a fresh sync\n if (db.connected) {\n platform.logger.info('[useSyncControl] Disconnecting to force fresh sync...');\n await db.disconnect();\n }\n\n platform.logger.info('[useSyncControl] Connecting...');\n await db.connect(connector);\n platform.logger.info('[useSyncControl] Connected, sync should start automatically');\n }, [db, connector, platform, setContextSyncMode]);\n\n const pause = useCallback(async () => {\n await setSyncMode('offline');\n platform.logger.info('[useSyncControl] Sync paused');\n }, [setSyncMode, platform]);\n\n const resume = useCallback(async () => {\n await setSyncMode('push-pull');\n platform.logger.info('[useSyncControl] Sync resumed');\n }, [setSyncMode, platform]);\n\n const disconnect = useCallback(async () => {\n if (!db) {\n platform.logger.warn('[useSyncControl] Cannot disconnect - not initialized');\n return;\n }\n\n platform.logger.info('[useSyncControl] Disconnecting...');\n await db.disconnect();\n platform.logger.info('[useSyncControl] Disconnected');\n }, [db, platform]);\n\n const setScope = useCallback((scope: SyncScope | null) => {\n scopeRef.current = scope;\n\n if (connector && scope) {\n // Update connector with new project IDs if it supports scoped sync\n connector.setActiveProjectIds(scope.ids);\n platform.logger.info('[useSyncControl] Scope set:', scope);\n }\n }, [connector, platform]);\n\n return useMemo(\n () => ({\n triggerSync,\n syncNow,\n pause,\n resume,\n disconnect,\n setScope,\n setSyncMode,\n }),\n [triggerSync, syncNow, pause, resume, disconnect, setScope, setSyncMode]\n );\n}\n\n// ─── Sync Mode Hook ──────────────────────────────────────────────────────────\n\n/**\n * Hook to get and set the current sync mode.\n *\n * @returns Object with current mode, setter, and capability flags\n *\n * @example\n * ```typescript\n * function SyncModeSelector() {\n * const { mode, setMode, canUpload, canDownload } = useSyncMode();\n *\n * return (\n * <View>\n * <Text>Current mode: {mode}</Text>\n * <Text>Can upload: {canUpload ? 'Yes' : 'No'}</Text>\n * <Button onPress={() => setMode('pull-only')}>Download Only</Button>\n * </View>\n * );\n * }\n * ```\n */\nexport function useSyncMode(): {\n mode: SyncMode;\n setMode: (mode: SyncMode) => Promise<void>;\n canUpload: boolean;\n canDownload: boolean;\n} {\n const { syncMode } = useSyncStatus();\n const { setSyncMode } = useSyncControl();\n\n return useMemo(() => ({\n mode: syncMode,\n setMode: setSyncMode,\n canUpload: syncMode === 'push-pull',\n canDownload: syncMode !== 'offline',\n }), [syncMode, setSyncMode]);\n}\n\n// ─── Connection Health Hook ──────────────────────────────────────────────────\n\n/**\n * Hook to access connection health status.\n *\n * @returns Current connection health with latency and failure tracking\n * @throws Error if used outside of PowerSyncProvider\n *\n * @example\n * ```typescript\n * function ConnectionIndicator() {\n * const health = useConnectionHealth();\n *\n * const statusColor = {\n * healthy: 'green',\n * degraded: 'yellow',\n * disconnected: 'red',\n * }[health.status];\n *\n * return (\n * <View>\n * <StatusDot color={statusColor} />\n * {health.latency && <Text>{health.latency}ms</Text>}\n * </View>\n * );\n * }\n * ```\n */\nexport function useConnectionHealth(): ConnectionHealth {\n const context = useContext(ConnectionHealthContext);\n\n if (!context) {\n throw new Error('useConnectionHealth must be used within a PowerSyncProvider');\n }\n\n return context.health;\n}\n\n// ─── Sync Metrics Hook ───────────────────────────────────────────────────────\n\n/**\n * Hook to access sync metrics.\n *\n * @returns Sync metrics including success rates, timing, and data transfer\n * @throws Error if used outside of PowerSyncProvider\n *\n * @example\n * ```typescript\n * function SyncStats() {\n * const metrics = useSyncMetrics();\n *\n * const successRate = metrics.totalSyncs > 0\n * ? (metrics.successfulSyncs / metrics.totalSyncs * 100).toFixed(1)\n * : 100;\n *\n * return (\n * <View>\n * <Text>Total syncs: {metrics.totalSyncs}</Text>\n * <Text>Success rate: {successRate}%</Text>\n * <Text>Avg duration: {metrics.averageSyncDuration ?? 'N/A'}ms</Text>\n * </View>\n * );\n * }\n * ```\n */\nexport function useSyncMetrics(): SyncMetrics {\n const context = useContext(SyncMetricsContext);\n\n if (!context) {\n throw new Error('useSyncMetrics must be used within a PowerSyncProvider');\n }\n\n return context.metrics;\n}\n\n// ─── Attachment Queue Hook ───────────────────────────────────────────────────\n\n/**\n * Hook to access the attachment queue (if configured).\n *\n * @returns AttachmentQueue instance or null if not configured\n *\n * @example\n * ```typescript\n * function PhotoStats() {\n * const attachmentQueue = useAttachmentQueue();\n * const [stats, setStats] = useState(null);\n *\n * useEffect(() => {\n * if (!attachmentQueue) return;\n *\n * return attachmentQueue.onProgress((newStats) => {\n * setStats(newStats);\n * });\n * }, [attachmentQueue]);\n *\n * if (!stats) return null;\n *\n * return (\n * <View>\n * <Text>Photos: {stats.syncedCount}/{stats.totalExpected}</Text>\n * <Text>Cache used: {formatBytes(stats.syncedSize)}</Text>\n * </View>\n * );\n * }\n * ```\n */\nexport function useAttachmentQueue(): AttachmentQueue | null {\n return useContext(AttachmentQueueContext);\n}\n\n// ─── Database Query Hook ─────────────────────────────────────────────────────\n\n/**\n * Hook to get the PowerSync database instance.\n * Throws if not ready.\n *\n * @returns The PowerSync database instance\n * @throws Error if not initialized or used outside of PowerSyncProvider\n *\n * @example\n * ```typescript\n * function UserList() {\n * const db = useDatabase();\n * const [users, setUsers] = useState([]);\n *\n * useEffect(() => {\n * db.getAll('SELECT * FROM users').then(setUsers);\n * }, [db]);\n *\n * return <FlatList data={users} />;\n * }\n * ```\n */\nexport function useDatabase(): AbstractPowerSyncDatabase {\n const { db, isReady, error } = usePowerSync();\n\n if (error) {\n throw error;\n }\n\n if (!isReady || !db) {\n throw new Error('PowerSync database is not ready');\n }\n\n return db;\n}\n\n// ─── Platform Adapter Hook ───────────────────────────────────────────────────\n\n/**\n * Hook to access the platform adapter.\n *\n * @returns The platform adapter instance\n *\n * @example\n * ```typescript\n * function FileViewer({ filePath }) {\n * const { platform } = usePowerSync();\n *\n * const handleOpen = async () => {\n * const content = await platform.fileSystem.readFile(filePath);\n * // Process content...\n * };\n *\n * return <Button onPress={handleOpen}>Open File</Button>;\n * }\n * ```\n */\nexport function usePlatform(): PlatformAdapter {\n const { platform } = usePowerSync();\n return platform;\n}\n\n// ─── Online Status Hook ──────────────────────────────────────────────────────\n\n/**\n * Hook to track online/offline status using the platform's network adapter.\n *\n * @returns Whether the device is currently connected to the internet\n *\n * @example\n * ```typescript\n * function OfflineBanner() {\n * const isOnline = useOnlineStatus();\n *\n * if (isOnline) return null;\n *\n * return <Banner type=\"warning\">You are offline</Banner>;\n * }\n * ```\n */\nexport function useOnlineStatus(): boolean {\n const { platform } = usePowerSync();\n const [isOnline, setIsOnline] = useState(true);\n\n useEffect(() => {\n // Get initial status\n platform.network.isConnected().then(setIsOnline);\n\n // Subscribe to changes\n const unsubscribe = platform.network.addConnectionListener(setIsOnline);\n\n return unsubscribe;\n }, [platform]);\n\n return isOnline;\n}\n\n// ─── Pending Mutations Hook ──────────────────────────────────────────────────\n\n/**\n * Hook to get pending mutations that need to be uploaded.\n *\n * @returns Array of pending CRUD entries and count\n *\n * @example\n * ```typescript\n * function PendingChanges() {\n * const { mutations, count } = usePendingMutations();\n *\n * if (count === 0) return null;\n *\n * return (\n * <View>\n * <Text>{count} changes pending upload</Text>\n * <FlatList\n * data={mutations}\n * renderItem={({ item }) => (\n * <Text>{item.op} on {item.table}</Text>\n * )}\n * />\n * </View>\n * );\n * }\n * ```\n */\nexport function usePendingMutations(): { mutations: CrudEntry[]; count: number } {\n const { pendingMutations, pendingCount } = useSyncStatus();\n\n return useMemo(\n () => ({\n mutations: pendingMutations,\n count: pendingCount,\n }),\n [pendingMutations, pendingCount]\n );\n}\n\n// ─── Is Syncing Hook ─────────────────────────────────────────────────────────\n\n/**\n * Hook to check if sync is currently active.\n *\n * @returns Whether sync is currently in progress (uploading or downloading)\n *\n * @example\n * ```typescript\n * function SyncButton() {\n * const isSyncing = useIsSyncing();\n * const { triggerSync } = useSyncControl();\n *\n * return (\n * <Button\n * onPress={triggerSync}\n * disabled={isSyncing}\n * >\n * {isSyncing ? 'Syncing...' : 'Sync Now'}\n * </Button>\n * );\n * }\n * ```\n */\nexport function useIsSyncing(): boolean {\n const { status } = useSyncStatus();\n return status.uploading || status.downloading;\n}\n\n// ─── Download Progress Hook ──────────────────────────────────────────────────\n\n/**\n * Hook to get download progress during sync.\n *\n * @returns Download progress or null if not downloading\n *\n * @example\n * ```typescript\n * function DownloadProgress() {\n * const progress = useDownloadProgress();\n *\n * if (!progress) return null;\n *\n * return (\n * <ProgressBar\n * value={progress.percentage}\n * label={`${progress.current}/${progress.target} operations`}\n * />\n * );\n * }\n * ```\n */\nexport function useDownloadProgress() {\n const { status } = useSyncStatus();\n return status.downloadProgress;\n}\n\n// ─── Entity Sync Status Hook ─────────────────────────────────────────────────\n\n/**\n * Return type for useEntitySyncStatus hook.\n */\nexport interface EntitySyncStatusResult {\n /** Current sync state for this entity */\n state: EntitySyncState;\n /** Error details if state is 'error' */\n error: SyncError | null;\n /** Number of pending operations for this entity */\n pendingOperations: number;\n /** The failed transaction if any */\n failedTransaction: FailedTransaction | null;\n /** Dismiss the failure (remove from tracking) */\n dismiss: () => void;\n}\n\n// Track recently synced entities with timestamps\nconst recentlySyncedEntities = new Map<string, number>();\nconst SYNCED_DISPLAY_DURATION_MS = 3000; // Show 'synced' state for 3 seconds\n\n/**\n * Hook to get sync status for a specific entity.\n *\n * Combines local mutation state (from pending mutations) with\n * failure state to provide a unified status for UI.\n *\n * @param entityId - The entity ID to check status for\n * @returns Unified sync state and actions\n *\n * @example\n * ```typescript\n * function EquipmentHeader({ unitId }) {\n * const { state, error, dismiss } = useEntitySyncStatus(unitId);\n *\n * const borderColor = {\n * idle: 'transparent',\n * saving: 'orange',\n * syncing: 'amber',\n * synced: 'green',\n * error: 'red',\n * }[state];\n * }\n * ```\n */\nexport function useEntitySyncStatus(entityId: string | undefined): EntitySyncStatusResult {\n // Use top-level failedTransactions from context (updated immediately on failure)\n const { status, pendingMutations, clearFailure, failedTransactions } = useSyncStatus();\n const [, forceUpdate] = useState(0);\n\n // Find if entity is in pending mutations\n // Check both entry.id (PowerSync's internal CRUD entry ID) and entry.opData?.id (the actual record ID)\n const entityPendingMutations = useMemo(() => {\n if (!entityId) return [];\n return pendingMutations.filter(entry =>\n entry.id === entityId || String(entry.opData?.id) === entityId\n );\n }, [entityId, pendingMutations]);\n\n // Find if entity has a failed transaction\n // Use top-level failedTransactions (not status.failedTransactions) for immediate updates\n const failedTransaction = useMemo(() => {\n if (!entityId) return null;\n return failedTransactions.find(\n ft => ft.affectedEntityIds.includes(entityId)\n ) ?? null;\n }, [entityId, failedTransactions]);\n\n // Track transition from syncing to synced\n const wasSyncingRef = useRef(false);\n const isCurrentlySyncing = entityPendingMutations.length > 0;\n\n // When entity transitions from syncing to not syncing (and no error),\n // mark it as recently synced\n useEffect(() => {\n if (!entityId) return;\n\n if (wasSyncingRef.current && !isCurrentlySyncing && !failedTransaction) {\n recentlySyncedEntities.set(entityId, Date.now());\n\n // Schedule cleanup after the display duration\n const timer = setTimeout(() => {\n const syncedAt = recentlySyncedEntities.get(entityId);\n if (syncedAt && Date.now() - syncedAt >= SYNCED_DISPLAY_DURATION_MS) {\n recentlySyncedEntities.delete(entityId);\n forceUpdate(n => n + 1);\n }\n }, SYNCED_DISPLAY_DURATION_MS);\n\n return () => clearTimeout(timer);\n }\n\n wasSyncingRef.current = isCurrentlySyncing;\n }, [entityId, isCurrentlySyncing, failedTransaction]);\n\n // Determine current state\n const state = useMemo((): EntitySyncState => {\n if (!entityId) return 'idle';\n\n // Check for ANY failure (highest priority)\n // Both permanent and transient failures should show error state\n // The UI can use failedTransaction.isPermanent to distinguish if needed\n if (failedTransaction) {\n return 'error';\n }\n\n // Check if currently syncing\n if (entityPendingMutations.length > 0) {\n return 'syncing';\n }\n\n // Check if recently synced\n const syncedAt = recentlySyncedEntities.get(entityId);\n if (syncedAt && Date.now() - syncedAt < SYNCED_DISPLAY_DURATION_MS) {\n return 'synced';\n }\n\n return 'idle';\n }, [entityId, failedTransaction, entityPendingMutations.length]);\n\n // Get error from failed transaction\n const error = failedTransaction?.error ?? null;\n\n // Dismiss function - clears the failure from tracking\n const dismiss = useCallback(() => {\n if (failedTransaction) {\n clearFailure(failedTransaction.id);\n }\n }, [failedTransaction, clearFailure]);\n\n return {\n state,\n error,\n pendingOperations: entityPendingMutations.length,\n failedTransaction,\n dismiss,\n };\n}\n\n// ─── Upload Status Hook ──────────────────────────────────────────────────────\n\n/**\n * Return type for useUploadStatus hook.\n */\nexport interface UploadStatusResult {\n /** Number of operations waiting to upload */\n pendingCount: number;\n /** Number of failed transactions */\n failedCount: number;\n /** Number of permanent failures needing user action */\n permanentFailureCount: number;\n /** Whether there are any errors */\n hasErrors: boolean;\n /** Whether there are permanent errors needing attention */\n hasPermanentErrors: boolean;\n /** All failed transactions */\n failedTransactions: FailedTransaction[];\n /** Trigger sync retry (reconnect) */\n retryAll: () => Promise<void>;\n /** Dismiss all failures */\n dismissAll: () => void;\n}\n\n/**\n * Hook to get overall upload status across all entities.\n *\n * @returns Upload status with counts and actions\n *\n * @example\n * ```typescript\n * function SyncStatusBar() {\n * const { pendingCount, failedCount, hasPermanentErrors, retryAll } = useUploadStatus();\n *\n * if (hasPermanentErrors) {\n * return <Banner onRetry={retryAll}>\n * {failedCount} changes failed to sync\n * </Banner>;\n * }\n * }\n * ```\n */\nexport function useUploadStatus(): UploadStatusResult {\n // Use top-level failedTransactions from context (updated immediately on failure)\n const { status, pendingCount, clearAllFailures, failedTransactions, hasUploadErrors, permanentErrorCount } = useSyncStatus();\n const { triggerSync } = useSyncControl();\n\n // Retry all by triggering a fresh sync\n const retryAll = useCallback(async () => {\n await triggerSync();\n }, [triggerSync]);\n\n // Dismiss all failures\n const dismissAll = useCallback(() => {\n clearAllFailures();\n }, [clearAllFailures]);\n\n return useMemo(\n () => ({\n pendingCount,\n failedCount: failedTransactions.length,\n permanentFailureCount: permanentErrorCount,\n hasErrors: hasUploadErrors,\n hasPermanentErrors: permanentErrorCount > 0,\n failedTransactions,\n retryAll,\n dismissAll,\n }),\n [\n pendingCount,\n failedTransactions,\n permanentErrorCount,\n hasUploadErrors,\n retryAll,\n dismissAll,\n ]\n );\n}\n\n// ─── Sync Activity Hook ──────────────────────────────────────────────────────\n\n/**\n * Return type for useSyncActivity hook.\n */\nexport interface SyncActivityResult {\n /** Pending CRUD entries waiting to be synced */\n pending: CrudEntry[];\n /** Failed transactions that need attention */\n failed: FailedTransaction[];\n /** Recently completed transactions */\n completed: CompletedTransaction[];\n /** Counts summary */\n counts: {\n pending: number;\n failed: number;\n completed: number;\n };\n /** Whether there is any sync activity to show (pending or failed) */\n hasActivity: boolean;\n /** Retry all failed transactions */\n retryAll: () => Promise<void>;\n /** Dismiss a specific failure */\n dismissFailure: (failureId: string) => void;\n /** Clear all completed transactions from the list */\n clearCompleted: () => void;\n}\n\n/**\n * Hook to get comprehensive sync activity including pending, failed, and completed transactions.\n *\n * Uses the provider's completed transaction tracking via the status tracker.\n *\n * @returns Sync activity with all transaction states and actions\n *\n * @example\n * ```typescript\n * function SyncActivityBanner() {\n * const { pending, failed, completed, counts, hasActivity, retryAll, clearCompleted } = useSyncActivity();\n *\n * if (!hasActivity && completed.length === 0) return null;\n *\n * return (\n * <View>\n * <Text>{counts.pending} syncing, {counts.failed} failed</Text>\n * {failed.length > 0 && <Button onPress={retryAll}>Retry All</Button>}\n * </View>\n * );\n * }\n * ```\n */\nexport function useSyncActivity(): SyncActivityResult {\n const {\n pendingMutations,\n clearFailure,\n failedTransactions,\n completedTransactions,\n clearCompletedHistory,\n isUploading,\n isDownloading,\n } = useSyncStatus();\n const { triggerSync } = useSyncControl();\n\n // Retry all by triggering a fresh sync\n const retryAll = useCallback(async () => {\n await triggerSync();\n }, [triggerSync]);\n\n // Dismiss a specific failure\n const dismissFailure = useCallback((failureId: string) => {\n clearFailure(failureId);\n }, [clearFailure]);\n\n // Clear all completed transactions\n const clearCompleted = useCallback(() => {\n clearCompletedHistory();\n }, [clearCompletedHistory]);\n\n const counts = useMemo(() => ({\n pending: pendingMutations.length,\n failed: failedTransactions.length,\n completed: completedTransactions.length,\n }), [pendingMutations.length, failedTransactions.length, completedTransactions.length]);\n\n // Use PowerSync's uploading/downloading status flags for reliable activity detection\n // This fixes the \"stuck on Syncing...\" bug where getNextCrudTransaction() returns null\n // during active upload (when transaction is locked), causing stale pendingMutations state\n const hasActivity = isUploading || isDownloading || failedTransactions.length > 0;\n\n return useMemo(\n () => ({\n pending: pendingMutations,\n failed: failedTransactions,\n completed: completedTransactions,\n counts,\n hasActivity,\n retryAll,\n dismissFailure,\n clearCompleted,\n }),\n [pendingMutations, failedTransactions, completedTransactions, counts, hasActivity, retryAll, dismissFailure, clearCompleted]\n );\n}\n"],"mappings":"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","names":["error","useCallback","useMemo","useRef","useState","useEffect","useRef","useCallback","useMemo","useState","useEffect"]}
@@ -1 +0,0 @@
1
- {"version":3,"sources":["../src/connector/types.ts","../src/conflicts/detect.ts","../src/connector/supabase-connector.ts"],"sourcesContent":["/**\n * Connector Types for @pol-studios/powersync\n *\n * Defines interfaces for PowerSync backend connectors.\n */\n\nimport type { SupabaseClient } from '@supabase/supabase-js';\nimport type { AbstractPowerSyncDatabase, ClassifiedError, CrudEntry } from '../core/types';\nimport type { LoggerAdapter } from '../platform/types';\nimport type { ConflictHandler, ConflictDetectionConfig } from '../conflicts/types';\nimport type { ConflictBus } from '../conflicts/conflict-bus';\n\n// Re-export ConflictBus type for convenience\nexport type { ConflictBus } from '../conflicts/conflict-bus';\n\n// ─── Connector Configuration ─────────────────────────────────────────────────\n\n/**\n * Options for creating a SupabaseConnector\n */\nexport interface SupabaseConnectorOptions {\n /** Supabase client instance */\n supabaseClient: SupabaseClient;\n /** PowerSync service URL */\n powerSyncUrl: string;\n /**\n * Optional: Custom schema routing function.\n * Determines which Supabase schema a table belongs to.\n * @default Returns 'public' for all tables\n */\n schemaRouter?: SchemaRouter;\n /**\n * Optional: Custom CRUD handler for complex mutations.\n * Allows overriding default upsert/update/delete behavior.\n */\n crudHandler?: CrudHandler;\n /** Logger for debugging */\n logger?: LoggerAdapter;\n /** Called when a transaction is successfully uploaded */\n onTransactionSuccess?: (entries: CrudEntry[]) => void;\n /** Called when a transaction fails to upload */\n onTransactionFailure?: (\n entries: CrudEntry[],\n error: Error,\n classified: ClassifiedError\n ) => void;\n /** Called when a transaction is fully completed (after transaction.complete()) */\n onTransactionComplete?: (entries: CrudEntry[]) => void;\n /** Function to check if upload should proceed. Used for sync mode gating. */\n shouldUpload?: () => boolean;\n /**\n * Optional: Configuration for version-based conflict detection.\n * When enabled, checks for conflicts before uploading changes.\n */\n conflictDetection?: ConflictDetectionConfig;\n /**\n * Optional: Handler for conflict resolution.\n * If not provided, conflicts are logged and upload proceeds.\n */\n conflictHandler?: ConflictHandler;\n /**\n * Optional: Event bus for publishing conflict events.\n * Use this to notify the UI layer about detected conflicts.\n */\n conflictBus?: ConflictBus;\n}\n\n/**\n * Configuration passed to PowerSyncProvider for connector setup\n */\nexport interface ConnectorConfig {\n /**\n * Custom schema routing function.\n * Determines which Supabase schema a table belongs to.\n * @default Returns 'public' for all tables\n *\n * @example\n * ```typescript\n * schemaRouter: (table) => {\n * if (['Profile', 'Comment'].includes(table)) return 'core';\n * return 'public';\n * }\n * ```\n */\n schemaRouter?: SchemaRouter;\n\n /**\n * Custom CRUD handler for complex mutations.\n * @default Uses standard upsert/update/delete operations\n */\n crudHandler?: CrudHandler;\n\n /**\n * Token refresh configuration.\n */\n tokenRefresh?: {\n /** Refresh token when it expires within this many seconds (default: 60) */\n refreshThresholdSeconds?: number;\n };\n}\n\n// ─── Schema Routing ──────────────────────────────────────────────────────────\n\n/**\n * Function that determines which Supabase schema a table belongs to.\n *\n * @param tableName - The name of the table\n * @returns The schema name (e.g., 'public', 'core')\n */\nexport type SchemaRouter = (tableName: string) => string;\n\n/**\n * Default schema router that returns 'public' for all tables\n */\nexport const defaultSchemaRouter: SchemaRouter = () => 'public';\n\n// ─── CRUD Handling ───────────────────────────────────────────────────────────\n\n/**\n * Custom handler for CRUD operations.\n *\n * Return `true` from a handler to indicate the operation was handled.\n * Return `false` to fall back to default behavior.\n */\nexport interface CrudHandler {\n /**\n * Handle a PUT operation (insert or replace).\n * @param entry - The CRUD entry\n * @param supabase - Supabase client\n * @param schema - The resolved schema for this table\n * @returns true if handled, false to use default behavior\n */\n handlePut?(\n entry: CrudEntry,\n supabase: SupabaseClient,\n schema: string\n ): Promise<boolean>;\n\n /**\n * Handle a PATCH operation (update).\n * @param entry - The CRUD entry\n * @param supabase - Supabase client\n * @param schema - The resolved schema for this table\n * @returns true if handled, false to use default behavior\n */\n handlePatch?(\n entry: CrudEntry,\n supabase: SupabaseClient,\n schema: string\n ): Promise<boolean>;\n\n /**\n * Handle a DELETE operation.\n * @param entry - The CRUD entry\n * @param supabase - Supabase client\n * @param schema - The resolved schema for this table\n * @returns true if handled, false to use default behavior\n */\n handleDelete?(\n entry: CrudEntry,\n supabase: SupabaseClient,\n schema: string\n ): Promise<boolean>;\n}\n\n// ─── Credentials ─────────────────────────────────────────────────────────────\n\n/**\n * Credentials returned by fetchCredentials\n */\nexport interface PowerSyncCredentials {\n /** PowerSync service endpoint URL */\n endpoint: string;\n /** JWT token for authentication */\n token: string;\n /** When the token expires */\n expiresAt?: Date;\n}\n\n// ─── Re-export from Core ─────────────────────────────────────────────────────\n\n// Re-export PowerSyncBackendConnector from core/types to maintain API compatibility\nexport type { PowerSyncBackendConnector } from '../core/types';\n","/**\n * Conflict Detection for @pol-studios/powersync\n *\n * Provides version-based conflict detection using AuditLog attribution.\n * Only queries AuditLog when version mismatch is detected.\n */\n\nimport type { SupabaseClient } from '@supabase/supabase-js';\nimport type { AbstractPowerSyncDatabase } from '../core/types';\nimport type { FieldConflict, ConflictCheckResult, ConflictDetectionConfig } from './types';\n\n/**\n * Regex to validate table names and prevent SQL injection.\n * Only allows alphanumeric characters and underscores, starting with a letter or underscore.\n */\nconst TABLE_NAME_REGEX = /^[a-zA-Z_][a-zA-Z0-9_]*$/;\n\n/**\n * Validates a table name to prevent SQL injection.\n * @throws Error if the table name is invalid\n */\nfunction validateTableName(table: string): void {\n if (!TABLE_NAME_REGEX.test(table)) {\n throw new Error(`Invalid table name: ${table}`);\n }\n}\n\nconst DEFAULT_IGNORED_FIELDS = ['updatedAt', 'createdAt', '_version', 'id'];\n\n/**\n * Detect conflicts between local pending changes and server state.\n *\n * Uses a two-step approach:\n * 1. Version match (local._version == server._version) → Sync immediately\n * 2. Version mismatch → Query AuditLog for field changes and attribution\n *\n * @param table - The table name\n * @param recordId - The record ID\n * @param localVersion - Version number from local SQLite\n * @param serverVersion - Version number from server (fetched separately)\n * @param pendingChanges - Fields with local changes to sync\n * @param supabase - Supabase client for AuditLog queries\n * @param config - Optional detection configuration\n * @returns Conflict check result with field-level details\n */\nexport async function detectConflicts(\n table: string,\n recordId: string,\n localVersion: number,\n serverVersion: number,\n pendingChanges: Record<string, unknown>,\n supabase: SupabaseClient,\n config?: ConflictDetectionConfig\n): Promise<ConflictCheckResult> {\n const ignoredFields = new Set([\n ...DEFAULT_IGNORED_FIELDS,\n ...(config?.ignoredFields ?? []),\n ]);\n\n // Filter out ignored fields from pending changes\n const filteredPendingChanges: Record<string, unknown> = {};\n for (const [field, value] of Object.entries(pendingChanges)) {\n if (!ignoredFields.has(field)) {\n filteredPendingChanges[field] = value;\n }\n }\n\n // Step 1: Version match = no conflict possible\n if (localVersion === serverVersion) {\n return {\n hasConflict: false,\n conflicts: [],\n nonConflictingChanges: Object.keys(filteredPendingChanges),\n table,\n recordId,\n };\n }\n\n // Step 2: Version mismatch - query AuditLog for changes since our version\n const { data: auditLogs, error } = await supabase\n .schema('core')\n .from('AuditLog')\n .select('oldRecord, newRecord, changeBy, changeAt')\n .eq('tableName', table)\n .eq('recordId_text', recordId)\n .order('changeAt', { ascending: false })\n .limit(20); // Recent changes should be sufficient\n\n if (error) {\n console.warn('[detectConflicts] Failed to query AuditLog:', error);\n // On error, assume no conflict and let sync proceed\n // (Server will reject if there's a real issue)\n return {\n hasConflict: false,\n conflicts: [],\n nonConflictingChanges: Object.keys(filteredPendingChanges),\n table,\n recordId,\n };\n }\n\n // Build map of server-changed fields with attribution\n // Key: field name, Value: most recent change info\n const serverChanges = new Map<string, {\n newValue: unknown;\n changedBy: string | null;\n changedAt: Date;\n }>();\n\n for (const log of auditLogs ?? []) {\n const oldRec = log.oldRecord as Record<string, unknown> | null;\n const newRec = log.newRecord as Record<string, unknown> | null;\n if (!oldRec || !newRec) continue;\n\n for (const [field, newValue] of Object.entries(newRec)) {\n // Skip ignored fields\n if (ignoredFields.has(field)) continue;\n\n // Only track if field actually changed AND we don't already have a more recent change\n if (oldRec[field] !== newValue && !serverChanges.has(field)) {\n serverChanges.set(field, {\n newValue,\n changedBy: log.changeBy as string | null,\n changedAt: new Date(log.changeAt as string),\n });\n }\n }\n }\n\n // Compare pending changes against server changes\n const conflicts: FieldConflict[] = [];\n const nonConflictingChanges: string[] = [];\n\n for (const [field, localValue] of Object.entries(filteredPendingChanges)) {\n if (serverChanges.has(field)) {\n // This field was changed on server - conflict!\n const serverChange = serverChanges.get(field)!;\n conflicts.push({\n field,\n localValue,\n serverValue: serverChange.newValue,\n changedBy: serverChange.changedBy,\n changedAt: serverChange.changedAt,\n });\n } else {\n // Field wasn't changed on server - safe to sync\n nonConflictingChanges.push(field);\n }\n }\n\n return {\n hasConflict: conflicts.length > 0,\n conflicts,\n nonConflictingChanges,\n table,\n recordId,\n };\n}\n\n/**\n * Check if a table has a _version column for conflict detection.\n *\n * @param table - The table name\n * @param db - PowerSync database instance\n * @returns True if the table has version tracking\n */\nexport async function hasVersionColumn(\n table: string,\n db: AbstractPowerSyncDatabase\n): Promise<boolean> {\n try {\n // Validate table name to prevent SQL injection\n validateTableName(table);\n\n // Query the PowerSync internal schema for column info\n const result = await db.getAll<{ name: string }>(\n `PRAGMA table_info(\"${table}\")`\n );\n return result.some(col => col.name === '_version');\n } catch {\n return false;\n }\n}\n\n/**\n * Fetch the current server version for a record.\n *\n * @param table - The table name\n * @param recordId - The record ID\n * @param schema - The Supabase schema (default: 'public')\n * @param supabase - Supabase client\n * @returns The server version number, or null if record not found\n */\nexport async function fetchServerVersion(\n table: string,\n recordId: string,\n schema: string,\n supabase: SupabaseClient\n): Promise<number | null> {\n const query = schema === 'public'\n ? supabase.from(table)\n : (supabase.schema(schema) as unknown as ReturnType<typeof supabase.schema>).from(table);\n\n const { data, error } = await query\n .select('_version')\n .eq('id', recordId)\n .single();\n\n if (error || !data) {\n return null;\n }\n\n return (data as { _version?: number })._version ?? null;\n}\n\n/**\n * Get the local version for a record from PowerSync SQLite.\n *\n * @param table - The table name\n * @param recordId - The record ID\n * @param db - PowerSync database instance\n * @returns The local version number, or null if not found\n */\nexport async function getLocalVersion(\n table: string,\n recordId: string,\n db: AbstractPowerSyncDatabase\n): Promise<number | null> {\n // Validate table name to prevent SQL injection\n validateTableName(table);\n\n const result = await db.get<{ _version?: number }>(\n `SELECT _version FROM \"${table}\" WHERE id = ?`,\n [recordId]\n );\n return result?._version ?? null;\n}\n","/**\n * Supabase Connector for PowerSync\n *\n * A generic, configurable connector that handles:\n * - Authentication with Supabase JWT tokens\n * - Uploading local changes back to Supabase\n * - Schema routing for multi-schema databases\n * - Custom CRUD handling for complex operations\n * - Version-based conflict detection (when enabled)\n */\n\nimport type { SupabaseClient } from '@supabase/supabase-js';\nimport type { AbstractPowerSyncDatabase, ClassifiedError, CrudEntry } from '../core/types';\nimport type { LoggerAdapter } from '../platform/types';\nimport type {\n SupabaseConnectorOptions,\n PowerSyncBackendConnector,\n PowerSyncCredentials,\n SchemaRouter,\n CrudHandler,\n} from './types';\nimport type { ConflictBus } from '../conflicts/conflict-bus';\nimport type { ConflictHandler, ConflictDetectionConfig, ConflictCheckResult } from '../conflicts/types';\nimport { defaultSchemaRouter } from './types';\nimport { classifySupabaseError } from '../core/errors';\nimport { detectConflicts, fetchServerVersion, getLocalVersion, hasVersionColumn } from '../conflicts/detect';\n\n/**\n * Update type enum matching @powersync/common\n */\nenum UpdateType {\n PUT = 'PUT',\n PATCH = 'PATCH',\n DELETE = 'DELETE',\n}\n\n/**\n * Generic Supabase connector for PowerSync.\n *\n * This connector handles authentication and CRUD uploads to Supabase.\n * It supports configurable schema routing and custom CRUD handlers\n * for complex use cases.\n *\n * @example Basic usage\n * ```typescript\n * const connector = new SupabaseConnector({\n * supabaseClient: supabase,\n * powerSyncUrl: 'https://your-powersync-instance.com',\n * });\n * ```\n *\n * @example With schema routing\n * ```typescript\n * const connector = new SupabaseConnector({\n * supabaseClient: supabase,\n * powerSyncUrl: 'https://your-powersync-instance.com',\n * schemaRouter: (table) => {\n * if (['Profile', 'Comment', 'CommentSection'].includes(table)) {\n * return 'core';\n * }\n * return 'public';\n * },\n * });\n * ```\n *\n * @example With custom CRUD handler\n * ```typescript\n * const connector = new SupabaseConnector({\n * supabaseClient: supabase,\n * powerSyncUrl: 'https://your-powersync-instance.com',\n * crudHandler: {\n * async handlePut(entry, supabase, schema) {\n * // Custom handling for specific tables\n * if (entry.table === 'SpecialTable') {\n * await myCustomUpsert(entry);\n * return true; // Handled\n * }\n * return false; // Use default\n * },\n * },\n * });\n * ```\n */\nexport class SupabaseConnector implements PowerSyncBackendConnector {\n private readonly supabase: SupabaseClient;\n private readonly powerSyncUrl: string;\n private readonly schemaRouter: SchemaRouter;\n private readonly crudHandler?: CrudHandler;\n private readonly logger?: LoggerAdapter;\n private readonly onTransactionSuccess?: (entries: CrudEntry[]) => void;\n private readonly onTransactionFailure?: (\n entries: CrudEntry[],\n error: Error,\n classified: ClassifiedError\n ) => void;\n private readonly onTransactionComplete?: (entries: CrudEntry[]) => void;\n private readonly shouldUploadFn?: () => boolean;\n\n // Conflict detection configuration\n private readonly conflictDetection?: ConflictDetectionConfig;\n private readonly conflictHandler?: ConflictHandler;\n private readonly conflictBus?: ConflictBus;\n\n // Cache for version column existence checks (table -> hasVersionColumn)\n private versionColumnCache = new Map<string, boolean>();\n\n // Active project IDs for scoped sync (optional feature)\n private activeProjectIds: string[] = [];\n\n constructor(options: SupabaseConnectorOptions) {\n this.supabase = options.supabaseClient;\n this.powerSyncUrl = options.powerSyncUrl;\n this.schemaRouter = options.schemaRouter ?? defaultSchemaRouter;\n this.crudHandler = options.crudHandler;\n this.logger = options.logger;\n this.onTransactionSuccess = options.onTransactionSuccess;\n this.onTransactionFailure = options.onTransactionFailure;\n this.onTransactionComplete = options.onTransactionComplete;\n this.shouldUploadFn = options.shouldUpload;\n\n // Conflict detection options\n this.conflictDetection = options.conflictDetection;\n this.conflictHandler = options.conflictHandler;\n this.conflictBus = options.conflictBus;\n }\n\n /**\n * Set the active project IDs for scoped sync.\n * Call this when user selects/opens projects.\n */\n setActiveProjectIds(projectIds: string[]): void {\n this.activeProjectIds = projectIds;\n }\n\n /**\n * Get the current active project IDs.\n */\n getActiveProjectIds(): string[] {\n return this.activeProjectIds;\n }\n\n /**\n * Get credentials for PowerSync connection.\n * Uses Supabase session token.\n *\n * Note: Token refresh is handled by Supabase's startAutoRefresh() which must be\n * called on app initialization. getSession() returns the auto-refreshed token.\n */\n async fetchCredentials(): Promise<PowerSyncCredentials> {\n this.logger?.debug('[Connector] Fetching credentials...');\n\n const {\n data: { session },\n error,\n } = await this.supabase.auth.getSession();\n\n if (error) {\n this.logger?.error('[Connector] Auth error:', error);\n throw new Error(`Failed to get Supabase session: ${error.message}`);\n }\n\n if (!session) {\n this.logger?.error('[Connector] No active session');\n throw new Error('No active Supabase session');\n }\n\n this.logger?.debug(\n '[Connector] Credentials fetched, token expires at:',\n session.expires_at\n );\n\n return {\n endpoint: this.powerSyncUrl,\n token: session.access_token,\n expiresAt: session.expires_at\n ? new Date(session.expires_at * 1000)\n : undefined,\n };\n }\n\n /**\n * Upload local changes to Supabase.\n * Called automatically by PowerSync when there are pending uploads.\n *\n * When conflict detection is enabled:\n * 1. Checks if table has _version column (cached)\n * 2. If yes, compares local vs server version\n * 3. On version mismatch, queries AuditLog for field conflicts\n * 4. If conflicts found, calls handler or publishes to conflict bus\n * 5. Applies resolution or skips entry based on handler response\n */\n async uploadData(database: AbstractPowerSyncDatabase): Promise<void> {\n // Check if uploads are allowed based on sync mode\n if (this.shouldUploadFn && !this.shouldUploadFn()) {\n if (__DEV__) {\n console.log('[Connector] Upload skipped - sync mode does not allow uploads');\n }\n this.logger?.debug('[Connector] Upload skipped - sync mode does not allow uploads');\n return;\n }\n\n if (__DEV__) {\n console.log('[Connector] uploadData called, fetching next CRUD transaction...');\n }\n\n const transaction = await database.getNextCrudTransaction();\n\n if (!transaction) {\n if (__DEV__) {\n console.log('[Connector] No pending CRUD transaction found');\n }\n return;\n }\n\n if (__DEV__) {\n console.log('[Connector] Transaction fetched:', {\n crudCount: transaction.crud.length,\n entries: transaction.crud.map(e => ({\n table: e.table,\n op: e.op,\n id: e.id,\n opDataKeys: e.opData ? Object.keys(e.opData) : [],\n })),\n });\n }\n\n // If conflict detection is disabled, use standard upload\n const conflictDetectionEnabled = this.conflictDetection?.enabled !== false;\n if (!conflictDetectionEnabled) {\n await this.processTransaction(transaction, database);\n return;\n }\n\n // Process with conflict detection\n const { crud } = transaction;\n const skipTables = new Set(this.conflictDetection?.skipTables ?? []);\n const entriesToProcess: CrudEntry[] = [];\n // Entries queued for UI resolution - these should NOT complete the transaction (leave in queue)\n const entriesQueuedForUI: CrudEntry[] = [];\n // Entries resolved with keep-server - these WILL complete the transaction (discard local changes)\n const entriesDiscarded: CrudEntry[] = [];\n\n // Track partial resolutions to re-emit remaining conflicts after sync completes\n const partialResolutions: Array<{\n originalConflict: ConflictCheckResult;\n syncedFields: string[];\n }> = [];\n\n for (const entry of crud) {\n // Skip DELETE operations - no conflict checking needed\n if (entry.op === 'DELETE') {\n entriesToProcess.push(entry);\n continue;\n }\n\n // Skip tables in the skip list\n if (skipTables.has(entry.table)) {\n entriesToProcess.push(entry);\n continue;\n }\n\n // Check for version column (cached)\n const hasVersion = await this.checkVersionColumn(entry.table, database);\n if (!hasVersion) {\n entriesToProcess.push(entry);\n continue;\n }\n\n // Get local and server versions\n const localVersion = await getLocalVersion(entry.table, entry.id, database);\n const schema = this.schemaRouter(entry.table);\n const serverVersion = await fetchServerVersion(\n entry.table,\n entry.id,\n schema,\n this.supabase\n );\n\n // If we can't get versions, skip conflict check and proceed\n if (localVersion === null || serverVersion === null) {\n entriesToProcess.push(entry);\n continue;\n }\n\n // Detect conflicts\n const conflictResult = await detectConflicts(\n entry.table,\n entry.id,\n localVersion,\n serverVersion,\n entry.opData ?? {},\n this.supabase,\n this.conflictDetection\n );\n\n if (!conflictResult.hasConflict) {\n entriesToProcess.push(entry);\n continue;\n }\n\n // Emit conflict event to bus if available\n this.conflictBus?.emitConflict(conflictResult);\n\n // Handle conflict with handler if available\n if (this.conflictHandler) {\n const resolution = await this.conflictHandler.onConflict(conflictResult);\n\n if (resolution === null) {\n // Queue for UI - skip this entry, leave in queue for later resolution\n entriesQueuedForUI.push(entry);\n if (__DEV__) {\n console.log('[Connector] Conflict queued for UI resolution:', {\n table: entry.table,\n id: entry.id,\n conflicts: conflictResult.conflicts.map(c => c.field),\n });\n }\n continue;\n }\n\n switch (resolution.action) {\n case 'overwrite':\n // Proceed with upload (overwrite server)\n entriesToProcess.push(entry);\n break;\n\n case 'keep-server':\n // Discard local changes - mark for completion (removes from queue)\n entriesDiscarded.push(entry);\n if (__DEV__) {\n console.log('[Connector] Conflict resolved with keep-server, discarding local changes:', {\n table: entry.table,\n id: entry.id,\n });\n }\n break;\n\n case 'partial':\n // Only sync specified fields\n const partialEntry: CrudEntry = {\n ...entry,\n opData: this.filterFields(entry.opData ?? {}, resolution.fields),\n };\n entriesToProcess.push(partialEntry);\n\n // Track this partial resolution to re-emit remaining conflicts after sync\n partialResolutions.push({\n originalConflict: conflictResult,\n syncedFields: resolution.fields,\n });\n break;\n }\n } else {\n // No handler - log conflict and proceed with upload\n console.warn('[Connector] Conflict detected but no handler:', {\n table: entry.table,\n id: entry.id,\n conflicts: conflictResult.conflicts,\n });\n entriesToProcess.push(entry);\n }\n }\n\n // If any entries are queued for UI resolution, we must NOT complete the transaction\n // This leaves those entries in the PowerSync queue for later resolution\n if (entriesQueuedForUI.length > 0) {\n if (__DEV__) {\n console.log('[Connector] Entries queued for UI resolution, leaving in queue:', {\n queuedForUI: entriesQueuedForUI.length,\n discarded: entriesDiscarded.length,\n toProcess: entriesToProcess.length,\n });\n }\n // Fire onTransactionComplete to notify listeners, but NOT onTransactionSuccess\n // because we're not completing the transaction. Entries remain in the queue.\n this.onTransactionComplete?.(entriesQueuedForUI);\n return;\n }\n\n // If all entries were discarded (keep-server) with none to process,\n // still complete the transaction to remove them from the queue\n if (entriesToProcess.length === 0 && entriesDiscarded.length > 0) {\n if (__DEV__) {\n console.log('[Connector] All entries resolved with keep-server, completing transaction to discard local changes');\n }\n try {\n await transaction.complete();\n // Fire success (entries were \"successfully\" discarded)\n this.onTransactionSuccess?.(entriesDiscarded);\n this.onTransactionComplete?.(entriesDiscarded);\n } catch (error) {\n const classified = classifySupabaseError(error);\n this.onTransactionFailure?.(\n entriesDiscarded,\n error instanceof Error ? error : new Error(String(error)),\n classified\n );\n throw error;\n }\n return;\n }\n\n // Process remaining entries\n try {\n for (const entry of entriesToProcess) {\n if (__DEV__) {\n console.log('[Connector] Processing CRUD entry:', {\n table: entry.table,\n op: entry.op,\n id: entry.id,\n opData: entry.opData,\n });\n }\n await this.processCrudEntry(entry);\n }\n\n if (__DEV__) {\n console.log('[Connector] All CRUD entries processed, completing transaction...');\n }\n\n await transaction.complete();\n\n if (__DEV__) {\n console.log('[Connector] Transaction completed successfully:', {\n entriesCount: entriesToProcess.length,\n discardedCount: entriesDiscarded.length,\n });\n }\n\n // After partial sync completes, re-emit conflicts for remaining fields\n if (this.conflictBus && partialResolutions.length > 0) {\n for (const { originalConflict, syncedFields } of partialResolutions) {\n const syncedFieldSet = new Set(syncedFields);\n\n // Find conflicts that weren't synced\n const remainingConflicts = originalConflict.conflicts.filter(\n c => !syncedFieldSet.has(c.field)\n );\n\n if (remainingConflicts.length > 0) {\n if (__DEV__) {\n console.log('[Connector] Re-emitting conflict for remaining fields:', {\n table: originalConflict.table,\n recordId: originalConflict.recordId,\n syncedFields,\n remainingConflictFields: remainingConflicts.map(c => c.field),\n });\n }\n\n // Emit new conflict for remaining fields\n this.conflictBus.emitConflict({\n ...originalConflict,\n conflicts: remainingConflicts,\n // All remaining are conflicts now - clear nonConflictingChanges since\n // the non-conflicting ones were already synced in the partial resolution\n nonConflictingChanges: [],\n });\n }\n }\n }\n\n // Notify success\n this.onTransactionSuccess?.(entriesToProcess);\n\n // Notify completion (after transaction.complete() succeeds)\n this.onTransactionComplete?.(entriesToProcess);\n } catch (error) {\n const classified = classifySupabaseError(error);\n\n // Always log to console for debugging\n console.error('[PowerSync Connector] Upload FAILED:', {\n errorMessage: error instanceof Error ? error.message : String(error),\n errorKeys: error && typeof error === 'object' ? Object.keys(error) : [],\n errorObject: JSON.stringify(error, null, 2),\n classified,\n isPermanent: classified.isPermanent,\n entries: entriesToProcess.map(e => ({\n table: e.table,\n op: e.op,\n id: e.id,\n })),\n });\n\n this.logger?.error('[Connector] Upload error:', {\n error,\n classified,\n entries: entriesToProcess.map(e => ({ table: e.table, op: e.op, id: e.id })),\n });\n\n // Notify failure with classification\n this.onTransactionFailure?.(\n entriesToProcess,\n error instanceof Error ? error : new Error(String(error)),\n classified\n );\n\n // Re-throw for PowerSync's native retry mechanism\n throw error;\n }\n }\n\n /**\n * Process a transaction without conflict detection.\n * Used when conflict detection is disabled.\n */\n private async processTransaction(\n transaction: { crud: CrudEntry[]; complete: () => Promise<void> },\n _database: AbstractPowerSyncDatabase\n ): Promise<void> {\n try {\n for (const entry of transaction.crud) {\n if (__DEV__) {\n console.log('[Connector] Processing CRUD entry:', {\n table: entry.table,\n op: entry.op,\n id: entry.id,\n opData: entry.opData,\n });\n }\n await this.processCrudEntry(entry);\n }\n\n if (__DEV__) {\n console.log('[Connector] All CRUD entries processed, completing transaction...');\n }\n\n await transaction.complete();\n\n if (__DEV__) {\n console.log('[Connector] Transaction completed successfully:', {\n entriesCount: transaction.crud.length,\n });\n }\n\n // Notify success\n this.onTransactionSuccess?.(transaction.crud);\n\n // Notify completion (after transaction.complete() succeeds)\n this.onTransactionComplete?.(transaction.crud);\n } catch (error) {\n const classified = classifySupabaseError(error);\n\n // Always log to console for debugging\n console.error('[PowerSync Connector] Upload FAILED:', {\n errorMessage: error instanceof Error ? error.message : String(error),\n errorKeys: error && typeof error === 'object' ? Object.keys(error) : [],\n errorObject: JSON.stringify(error, null, 2),\n classified,\n isPermanent: classified.isPermanent,\n entries: transaction.crud.map(e => ({\n table: e.table,\n op: e.op,\n id: e.id,\n })),\n });\n\n this.logger?.error('[Connector] Upload error:', {\n error,\n classified,\n entries: transaction.crud.map(e => ({ table: e.table, op: e.op, id: e.id })),\n });\n\n // Notify failure with classification\n this.onTransactionFailure?.(\n transaction.crud,\n error instanceof Error ? error : new Error(String(error)),\n classified\n );\n\n // Re-throw for PowerSync's native retry mechanism\n throw error;\n }\n }\n\n /**\n * Check if a table has a _version column (cached).\n */\n private async checkVersionColumn(\n table: string,\n db: AbstractPowerSyncDatabase\n ): Promise<boolean> {\n if (this.versionColumnCache.has(table)) {\n return this.versionColumnCache.get(table)!;\n }\n\n const hasVersion = await hasVersionColumn(table, db);\n this.versionColumnCache.set(table, hasVersion);\n return hasVersion;\n }\n\n /**\n * Filter opData to only include specified fields.\n * Used for partial sync resolution.\n */\n private filterFields(\n opData: Record<string, unknown>,\n fields: string[]\n ): Record<string, unknown> {\n const fieldSet = new Set(fields);\n const filtered: Record<string, unknown> = {};\n for (const [key, value] of Object.entries(opData)) {\n if (fieldSet.has(key)) {\n filtered[key] = value;\n }\n }\n return filtered;\n }\n\n /**\n * Process a single CRUD operation.\n *\n * UUID-native tables (public schema, post-migration) use `id` as the UUID column.\n * Core schema tables (Profile, Comment, CommentSection) still use a separate `uuid` column.\n */\n /**\n * Process a single CRUD operation.\n *\n * All synced tables use `id` as their UUID primary key column.\n */\n private async processCrudEntry(entry: CrudEntry): Promise<void> {\n const table = entry.table;\n const id = entry.id; // PowerSync sends UUID as the entry.id\n const schema = this.schemaRouter(table);\n\n // Try custom handler first\n if (this.crudHandler) {\n let handled = false;\n\n switch (entry.op) {\n case UpdateType.PUT:\n handled =\n (await this.crudHandler.handlePut?.(entry, this.supabase, schema)) ??\n false;\n break;\n case UpdateType.PATCH:\n handled =\n (await this.crudHandler.handlePatch?.(\n entry,\n this.supabase,\n schema\n )) ?? false;\n break;\n case UpdateType.DELETE:\n handled =\n (await this.crudHandler.handleDelete?.(\n entry,\n this.supabase,\n schema\n )) ?? false;\n break;\n }\n\n if (handled) {\n this.logger?.debug(\n `[Connector] Custom handler processed ${entry.op} for ${schema}.${table}`\n );\n return;\n }\n }\n\n // Default behavior\n // Get the correct Supabase query builder for this table's schema\n const query =\n schema === 'public'\n ? this.supabase.from(table)\n : (this.supabase.schema(schema) as unknown as ReturnType<typeof this.supabase.schema>).from(table);\n\n switch (entry.op) {\n case UpdateType.PUT:\n if (__DEV__) {\n console.log('[Connector] Executing PUT/UPSERT:', {\n schema,\n table,\n id,\n data: { id, ...entry.opData },\n });\n }\n // Insert/upsert using id column\n const { data: upsertData, error: upsertError } = await query.upsert(\n { id, ...entry.opData },\n { onConflict: 'id' }\n ).select();\n\n if (upsertError) {\n if (__DEV__) {\n console.error('[Connector] PUT/UPSERT FAILED:', {\n schema,\n table,\n id,\n error: upsertError,\n errorMessage: upsertError.message,\n errorCode: upsertError.code,\n errorDetails: upsertError.details,\n errorHint: upsertError.hint,\n });\n }\n throw new Error(\n `Upsert failed for ${schema}.${table}: ${upsertError.message}`\n );\n }\n if (__DEV__) {\n console.log('[Connector] PUT/UPSERT SUCCESS:', {\n schema,\n table,\n id,\n responseData: upsertData,\n });\n }\n break;\n\n case UpdateType.PATCH:\n if (__DEV__) {\n console.log('[Connector] Executing PATCH/UPDATE:', {\n schema,\n table,\n id,\n opData: entry.opData,\n });\n }\n // Update by id column\n const { data: updateData, error: updateError } = await query\n .update(entry.opData)\n .eq('id', id)\n .select();\n\n if (updateError) {\n if (__DEV__) {\n console.error('[Connector] PATCH/UPDATE FAILED:', {\n schema,\n table,\n id,\n error: updateError,\n errorMessage: updateError.message,\n errorCode: updateError.code,\n errorDetails: updateError.details,\n errorHint: updateError.hint,\n });\n }\n throw new Error(\n `Update failed for ${schema}.${table}: ${updateError.message}`\n );\n }\n if (__DEV__) {\n console.log('[Connector] PATCH/UPDATE SUCCESS:', {\n schema,\n table,\n id,\n responseData: updateData,\n });\n }\n break;\n\n case UpdateType.DELETE:\n if (__DEV__) {\n console.log('[Connector] Executing DELETE:', {\n schema,\n table,\n id,\n });\n }\n // Delete by id column\n const { data: deleteData, error: deleteError } = await query.delete().eq('id', id).select();\n\n if (deleteError) {\n if (__DEV__) {\n console.error('[Connector] DELETE FAILED:', {\n schema,\n table,\n id,\n error: deleteError,\n errorMessage: deleteError.message,\n errorCode: deleteError.code,\n errorDetails: deleteError.details,\n errorHint: deleteError.hint,\n });\n }\n throw new Error(\n `Delete failed for ${schema}.${table}: ${deleteError.message}`\n );\n }\n if (__DEV__) {\n console.log('[Connector] DELETE SUCCESS:', {\n schema,\n table,\n id,\n responseData: deleteData,\n });\n }\n break;\n }\n\n this.logger?.debug(\n `[Connector] Processed ${entry.op} for ${schema}.${table} (id: ${id})`\n );\n }\n}\n"],"mappings":"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","names":[]}
@@ -1 +0,0 @@
1
- {"version":3,"sources":["../src/platform/index.native.ts"],"sourcesContent":["/**\n * React Native Platform Adapter for @pol-studios/powersync\n *\n * Implements the PlatformAdapter interface using React Native specific APIs:\n * - @powersync/react-native for SQLite database\n * - expo-file-system for file operations\n * - @react-native-async-storage/async-storage for key-value storage\n * - @react-native-community/netinfo for network monitoring\n * - expo-image-manipulator for image compression\n */\n\nimport type {\n PlatformAdapter,\n DatabaseOptions,\n FileSystemAdapter,\n AsyncStorageAdapter,\n NetworkAdapter,\n LoggerAdapter,\n ImageProcessorAdapter,\n FileInfo,\n CompressedImage,\n CompressionOptions,\n ConnectionType,\n} from './types';\nimport type { AbstractPowerSyncDatabase } from '../core/types';\n\n/**\n * Create a React Native platform adapter\n *\n * @param logger - Logger implementation to use\n * @returns Platform adapter configured for React Native\n *\n * @example\n * ```typescript\n * import { createNativePlatformAdapter } from '@pol-studios/powersync/platform';\n *\n * const logger = {\n * debug: console.log,\n * info: console.log,\n * warn: console.warn,\n * error: console.error,\n * };\n *\n * const platform = createNativePlatformAdapter(logger);\n * ```\n */\nexport function createNativePlatformAdapter(\n logger: LoggerAdapter\n): PlatformAdapter {\n // Lazy imports to avoid loading these modules until needed\n // This also helps with tree-shaking in web builds\n let PowerSyncDatabase: typeof import('@powersync/react-native').PowerSyncDatabase;\n let OPSqliteOpenFactory: typeof import('@powersync/op-sqlite').OPSqliteOpenFactory;\n // Use 'any' for FileSystem to handle type changes between expo-file-system versions\n let FileSystem: any;\n let AsyncStorage: typeof import('@react-native-async-storage/async-storage').default;\n let NetInfo: typeof import('@react-native-community/netinfo').default;\n let ImageManipulator: typeof import('expo-image-manipulator');\n\n const loadDependencies = async () => {\n if (!PowerSyncDatabase) {\n const psModule = await import('@powersync/react-native');\n PowerSyncDatabase = psModule.PowerSyncDatabase;\n }\n if (!OPSqliteOpenFactory) {\n const opSqliteModule = await import('@powersync/op-sqlite');\n OPSqliteOpenFactory = opSqliteModule.OPSqliteOpenFactory;\n }\n if (!FileSystem) {\n FileSystem = await import('expo-file-system');\n }\n if (!AsyncStorage) {\n const asModule = await import('@react-native-async-storage/async-storage');\n AsyncStorage = asModule.default;\n }\n if (!NetInfo) {\n const niModule = await import('@react-native-community/netinfo');\n NetInfo = niModule.default;\n }\n if (!ImageManipulator) {\n ImageManipulator = await import('expo-image-manipulator');\n }\n };\n\n // File system adapter implementation\n const fileSystem: FileSystemAdapter = {\n async readFile(uri: string, encoding: 'base64' | 'utf8' = 'utf8'): Promise<string> {\n if (!FileSystem) await loadDependencies();\n return FileSystem!.readAsStringAsync(uri, {\n encoding:\n encoding === 'base64'\n ? FileSystem!.EncodingType.Base64\n : FileSystem!.EncodingType.UTF8,\n });\n },\n\n async writeFile(\n uri: string,\n data: string,\n encoding: 'base64' | 'utf8' = 'utf8'\n ): Promise<void> {\n if (!FileSystem) await loadDependencies();\n // Ensure parent directory exists\n const parentDir = uri.substring(0, uri.lastIndexOf('/'));\n if (parentDir) {\n await FileSystem!.makeDirectoryAsync(parentDir, { intermediates: true }).catch(\n () => {\n // Directory may already exist\n }\n );\n }\n await FileSystem!.writeAsStringAsync(uri, data, {\n encoding:\n encoding === 'base64'\n ? FileSystem!.EncodingType.Base64\n : FileSystem!.EncodingType.UTF8,\n });\n },\n\n async deleteFile(uri: string): Promise<void> {\n if (!FileSystem) await loadDependencies();\n await FileSystem!.deleteAsync(uri, { idempotent: true });\n },\n\n async copyFile(source: string, destination: string): Promise<void> {\n if (!FileSystem) await loadDependencies();\n await FileSystem!.copyAsync({ from: source, to: destination });\n },\n\n async moveFile(source: string, destination: string): Promise<void> {\n if (!FileSystem) await loadDependencies();\n await FileSystem!.moveAsync({ from: source, to: destination });\n },\n\n async getFileInfo(uri: string): Promise<FileInfo | null> {\n if (!FileSystem) await loadDependencies();\n try {\n const info = await FileSystem!.getInfoAsync(uri);\n if (!info.exists) return null;\n return {\n exists: true,\n size: 'size' in info ? (info.size ?? 0) : 0,\n isDirectory: info.isDirectory ?? false,\n modificationTime:\n 'modificationTime' in info && info.modificationTime\n ? new Date(info.modificationTime * 1000)\n : undefined,\n };\n } catch {\n return null;\n }\n },\n\n async makeDirectory(\n uri: string,\n options?: { intermediates?: boolean }\n ): Promise<void> {\n if (!FileSystem) await loadDependencies();\n await FileSystem!.makeDirectoryAsync(uri, {\n intermediates: options?.intermediates ?? true,\n });\n },\n\n getDocumentsDirectory(): string {\n // Note: This will be set after first load\n // Return a placeholder that will work for path construction\n return FileSystem?.documentDirectory ?? '';\n },\n\n getCacheDirectory(): string {\n return FileSystem?.cacheDirectory ?? '';\n },\n\n async getFreeDiskSpace(): Promise<number> {\n if (!FileSystem) await loadDependencies();\n return FileSystem!.getFreeDiskStorageAsync();\n },\n };\n\n // Async storage adapter implementation\n const storage: AsyncStorageAdapter = {\n async getItem(key: string): Promise<string | null> {\n if (!AsyncStorage) await loadDependencies();\n return AsyncStorage!.getItem(key);\n },\n\n async setItem(key: string, value: string): Promise<void> {\n if (!AsyncStorage) await loadDependencies();\n await AsyncStorage!.setItem(key, value);\n },\n\n async removeItem(key: string): Promise<void> {\n if (!AsyncStorage) await loadDependencies();\n await AsyncStorage!.removeItem(key);\n },\n\n async multiGet(keys: string[]): Promise<[string, string | null][]> {\n if (!AsyncStorage) await loadDependencies();\n const result = await AsyncStorage!.multiGet(keys);\n return result as [string, string | null][];\n },\n\n async multiSet(entries: [string, string][]): Promise<void> {\n if (!AsyncStorage) await loadDependencies();\n await AsyncStorage!.multiSet(entries);\n },\n };\n\n // Network adapter implementation\n const network: NetworkAdapter = {\n async isConnected(): Promise<boolean> {\n if (!NetInfo) await loadDependencies();\n const state = await NetInfo!.fetch();\n return state.isConnected ?? false;\n },\n\n async getConnectionType(): Promise<ConnectionType> {\n if (!NetInfo) await loadDependencies();\n const state = await NetInfo!.fetch();\n const type = state.type;\n if (type === 'wifi') return 'wifi';\n if (type === 'cellular') return 'cellular';\n if (type === 'ethernet') return 'ethernet';\n if (type === 'none') return 'none';\n return 'unknown';\n },\n\n addConnectionListener(callback: (isConnected: boolean) => void): () => void {\n // NetInfo must be loaded synchronously for listener setup\n // This is typically called after initialization\n if (!NetInfo) {\n logger.warn(\n '[Platform] NetInfo not loaded, connection listener may not work immediately'\n );\n // Load and set up listener asynchronously\n loadDependencies().then(() => {\n NetInfo!.addEventListener((state) => {\n callback(state.isConnected ?? false);\n });\n });\n return () => {};\n }\n\n const unsubscribe = NetInfo.addEventListener((state) => {\n callback(state.isConnected ?? false);\n });\n return unsubscribe;\n },\n };\n\n // Image processor adapter implementation\n const imageProcessor: ImageProcessorAdapter = {\n async compress(\n uri: string,\n options: CompressionOptions\n ): Promise<CompressedImage> {\n if (!ImageManipulator) await loadDependencies();\n\n const actions: import('expo-image-manipulator').Action[] = [];\n\n // Add resize action if maxWidth or maxHeight specified\n if (options.maxWidth || options.maxHeight) {\n actions.push({\n resize: {\n width: options.maxWidth,\n height: options.maxHeight,\n },\n });\n }\n\n // Determine output format\n let format: import('expo-image-manipulator').SaveFormat;\n switch (options.format) {\n case 'png':\n format = ImageManipulator!.SaveFormat.PNG;\n break;\n case 'webp':\n format = ImageManipulator!.SaveFormat.WEBP;\n break;\n case 'jpeg':\n default:\n format = ImageManipulator!.SaveFormat.JPEG;\n break;\n }\n\n const result = await ImageManipulator!.manipulateAsync(uri, actions, {\n compress: options.quality,\n format,\n });\n\n return {\n uri: result.uri,\n width: result.width,\n height: result.height,\n };\n },\n };\n\n // Main platform adapter\n return {\n async createDatabase(\n options: DatabaseOptions\n ): Promise<AbstractPowerSyncDatabase> {\n if (!PowerSyncDatabase) await loadDependencies();\n\n logger.info('[Platform] Creating PowerSync database:', options.dbFilename);\n\n const db = new PowerSyncDatabase!({\n schema: options.schema as any,\n database: new OPSqliteOpenFactory!({\n dbFilename: options.dbFilename,\n }),\n });\n\n logger.info('[Platform] Initializing database...');\n await db.init();\n\n // Verify database is queryable before returning\n // This prevents race conditions where db.connect() is called before SQLite is truly ready\n const maxAttempts = 3;\n for (let attempt = 0; attempt < maxAttempts; attempt++) {\n try {\n await db.get('SELECT 1');\n logger.info('[Platform] Database initialized and verified');\n return db as unknown as AbstractPowerSyncDatabase;\n } catch (err) {\n if (attempt < maxAttempts - 1) {\n logger.warn(`[Platform] Database readiness check failed (attempt ${attempt + 1}/${maxAttempts}), retrying...`);\n await new Promise(r => setTimeout(r, 100 * Math.pow(2, attempt))); // 100ms, 200ms, 400ms\n } else {\n logger.error('[Platform] Database failed readiness verification after all attempts');\n throw new Error(`Database failed readiness verification: ${err instanceof Error ? err.message : err}`);\n }\n }\n }\n\n // TypeScript: unreachable but needed for return type\n throw new Error('Database readiness verification failed');\n },\n\n fileSystem,\n storage,\n network,\n logger,\n imageProcessor,\n };\n}\n\n// Re-export types for convenience\nexport type { PlatformAdapter, LoggerAdapter } from './types';\n"],"mappings":"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","names":[]}