@pol-studios/powersync 1.0.25 → 1.0.32

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 (118) hide show
  1. package/README.md +0 -1
  2. package/dist/{CacheSettingsManager-uz-kbnRH.d.ts → CacheSettingsManager-0H_7thHW.d.ts} +21 -3
  3. package/dist/attachments/index.d.ts +30 -30
  4. package/dist/attachments/index.js +13 -4
  5. package/dist/{background-sync-ChCXW-EV.d.ts → background-sync-BujnI3IR.d.ts} +1 -1
  6. package/dist/{chunk-55DKCJV4.js → chunk-2RDWLXJW.js} +408 -78
  7. package/dist/chunk-2RDWLXJW.js.map +1 -0
  8. package/dist/{chunk-P4HZA6ZT.js → chunk-4665ZSE5.js} +2 -2
  9. package/dist/chunk-4665ZSE5.js.map +1 -0
  10. package/dist/{chunk-XOY2CJ67.js → chunk-4F5B5CZ7.js} +3 -3
  11. package/dist/chunk-5WRI5ZAA.js +31 -0
  12. package/dist/{chunk-BGBQYQV3.js → chunk-65A3SYJZ.js} +193 -299
  13. package/dist/chunk-65A3SYJZ.js.map +1 -0
  14. package/dist/chunk-6SZ64KCZ.js +755 -0
  15. package/dist/chunk-6SZ64KCZ.js.map +1 -0
  16. package/dist/{chunk-YSTEESEG.js → chunk-74TBHWJ4.js} +122 -11
  17. package/dist/chunk-74TBHWJ4.js.map +1 -0
  18. package/dist/chunk-ANXWYQEJ.js +1 -0
  19. package/dist/chunk-ANXWYQEJ.js.map +1 -0
  20. package/dist/{chunk-CAB26E6F.js → chunk-C4J4MLER.js} +29 -24
  21. package/dist/chunk-C4J4MLER.js.map +1 -0
  22. package/dist/{chunk-C5ODS3XH.js → chunk-EOW7JK7Q.js} +9 -16
  23. package/dist/chunk-EOW7JK7Q.js.map +1 -0
  24. package/dist/chunk-HRAVPIAZ.js +220 -0
  25. package/dist/chunk-HRAVPIAZ.js.map +1 -0
  26. package/dist/{chunk-XAEII4ZX.js → chunk-NUGQOTEM.js} +32 -4
  27. package/dist/chunk-NUGQOTEM.js.map +1 -0
  28. package/dist/chunk-OGUFUZSY.js +5415 -0
  29. package/dist/chunk-OGUFUZSY.js.map +1 -0
  30. package/dist/{chunk-VB737IVN.js → chunk-P4D6BQ4X.js} +328 -706
  31. package/dist/chunk-P4D6BQ4X.js.map +1 -0
  32. package/dist/{chunk-CACKC6XG.js → chunk-PGEDE6IM.js} +136 -89
  33. package/dist/chunk-PGEDE6IM.js.map +1 -0
  34. package/dist/{chunk-A4IBBWGO.js → chunk-RALHHPTU.js} +1 -1
  35. package/dist/chunk-RIDSPLE5.js +42 -0
  36. package/dist/chunk-RIDSPLE5.js.map +1 -0
  37. package/dist/{chunk-Z6VOBGTU.js → chunk-UOMHWUHV.js} +2 -12
  38. package/dist/chunk-UOMHWUHV.js.map +1 -0
  39. package/dist/{chunk-WGHNIAF7.js → chunk-YONQYTVH.js} +2 -2
  40. package/dist/chunk-ZAN22NGL.js +13 -0
  41. package/dist/chunk-ZAN22NGL.js.map +1 -0
  42. package/dist/config/index.d.ts +200 -0
  43. package/dist/config/index.js +23 -0
  44. package/dist/config/index.js.map +1 -0
  45. package/dist/connector/index.d.ts +23 -5
  46. package/dist/connector/index.js +4 -2
  47. package/dist/core/index.d.ts +2 -2
  48. package/dist/core/index.js +1 -0
  49. package/dist/error/index.js +1 -0
  50. package/dist/generator/index.js +2 -0
  51. package/dist/generator/index.js.map +1 -1
  52. package/dist/index.d.ts +19 -16
  53. package/dist/index.js +88 -46
  54. package/dist/index.native.d.ts +18 -14
  55. package/dist/index.native.js +93 -44
  56. package/dist/index.web.d.ts +17 -14
  57. package/dist/index.web.js +88 -46
  58. package/dist/maintenance/index.d.ts +2 -2
  59. package/dist/maintenance/index.js +3 -2
  60. package/dist/platform/index.d.ts +1 -1
  61. package/dist/platform/index.js +2 -0
  62. package/dist/platform/index.js.map +1 -1
  63. package/dist/platform/index.native.d.ts +1 -1
  64. package/dist/platform/index.native.js +1 -0
  65. package/dist/platform/index.web.d.ts +1 -1
  66. package/dist/platform/index.web.js +1 -0
  67. package/dist/pol-attachment-queue-DqBvLAEY.d.ts +255 -0
  68. package/dist/provider/index.d.ts +319 -124
  69. package/dist/provider/index.js +21 -16
  70. package/dist/provider/index.native.d.ts +108 -0
  71. package/dist/provider/index.native.js +121 -0
  72. package/dist/provider/index.native.js.map +1 -0
  73. package/dist/provider/index.web.d.ts +16 -0
  74. package/dist/provider/index.web.js +112 -0
  75. package/dist/provider/index.web.js.map +1 -0
  76. package/dist/react/index.d.ts +16 -65
  77. package/dist/react/index.js +2 -9
  78. package/dist/storage/index.d.ts +5 -4
  79. package/dist/storage/index.js +12 -9
  80. package/dist/storage/index.native.d.ts +5 -4
  81. package/dist/storage/index.native.js +8 -5
  82. package/dist/storage/index.web.d.ts +5 -4
  83. package/dist/storage/index.web.js +11 -8
  84. package/dist/storage/upload/index.d.ts +4 -3
  85. package/dist/storage/upload/index.js +4 -2
  86. package/dist/storage/upload/index.native.d.ts +4 -3
  87. package/dist/storage/upload/index.native.js +4 -2
  88. package/dist/storage/upload/index.web.d.ts +2 -1
  89. package/dist/storage/upload/index.web.js +4 -2
  90. package/dist/{supabase-connector-D2oIl2t8.d.ts → supabase-connector-HMxBA9Kg.d.ts} +23 -25
  91. package/dist/sync/index.d.ts +183 -11
  92. package/dist/sync/index.js +13 -3
  93. package/dist/{types-CyvBaAl8.d.ts → types-6QHGELuY.d.ts} +4 -1
  94. package/dist/{types-CDqWh56B.d.ts → types-B9MptP7E.d.ts} +13 -1
  95. package/dist/types-BhAEsJj-.d.ts +330 -0
  96. package/dist/{types-D0WcHrq6.d.ts → types-CGMibJKD.d.ts} +8 -0
  97. package/dist/{types-DiBvmGEi.d.ts → types-DqJnP50o.d.ts} +22 -24
  98. package/dist/{pol-attachment-queue-BE2HU3Us.d.ts → types-JCEhw2Lf.d.ts} +139 -346
  99. package/package.json +18 -4
  100. package/dist/chunk-24RDMMCL.js +0 -44
  101. package/dist/chunk-24RDMMCL.js.map +0 -1
  102. package/dist/chunk-55DKCJV4.js.map +0 -1
  103. package/dist/chunk-654ERHA7.js +0 -1
  104. package/dist/chunk-BGBQYQV3.js.map +0 -1
  105. package/dist/chunk-C5ODS3XH.js.map +0 -1
  106. package/dist/chunk-CAB26E6F.js.map +0 -1
  107. package/dist/chunk-CACKC6XG.js.map +0 -1
  108. package/dist/chunk-P4HZA6ZT.js.map +0 -1
  109. package/dist/chunk-TIFL2KWE.js +0 -358
  110. package/dist/chunk-TIFL2KWE.js.map +0 -1
  111. package/dist/chunk-VB737IVN.js.map +0 -1
  112. package/dist/chunk-XAEII4ZX.js.map +0 -1
  113. package/dist/chunk-YSTEESEG.js.map +0 -1
  114. package/dist/chunk-Z6VOBGTU.js.map +0 -1
  115. /package/dist/{chunk-XOY2CJ67.js.map → chunk-4F5B5CZ7.js.map} +0 -0
  116. /package/dist/{chunk-654ERHA7.js.map → chunk-5WRI5ZAA.js.map} +0 -0
  117. /package/dist/{chunk-A4IBBWGO.js.map → chunk-RALHHPTU.js.map} +0 -0
  118. /package/dist/{chunk-WGHNIAF7.js.map → chunk-YONQYTVH.js.map} +0 -0
@@ -1,44 +0,0 @@
1
- // src/provider/types.ts
2
- var DEFAULT_SYNC_STATUS = {
3
- connected: false,
4
- connecting: false,
5
- hasSynced: false,
6
- lastSyncedAt: null,
7
- uploading: false,
8
- downloading: false,
9
- downloadProgress: null,
10
- failedTransactions: [],
11
- hasUploadErrors: false,
12
- permanentErrorCount: 0
13
- };
14
- var DEFAULT_CONNECTION_HEALTH = {
15
- status: "disconnected",
16
- latency: null,
17
- lastHealthCheck: null,
18
- consecutiveFailures: 0,
19
- reconnectAttempts: 0
20
- };
21
- var DEFAULT_SYNC_METRICS = {
22
- totalSyncs: 0,
23
- successfulSyncs: 0,
24
- failedSyncs: 0,
25
- lastSyncDuration: null,
26
- averageSyncDuration: null,
27
- totalDataDownloaded: 0,
28
- totalDataUploaded: 0,
29
- lastError: null
30
- };
31
- var DEFAULT_SYNC_CONFIG = {
32
- autoConnect: true,
33
- syncInterval: 0,
34
- enableHealthMonitoring: true,
35
- enableMetrics: true
36
- };
37
-
38
- export {
39
- DEFAULT_SYNC_STATUS,
40
- DEFAULT_CONNECTION_HEALTH,
41
- DEFAULT_SYNC_METRICS,
42
- DEFAULT_SYNC_CONFIG
43
- };
44
- //# sourceMappingURL=chunk-24RDMMCL.js.map
@@ -1 +0,0 @@
1
- {"version":3,"sources":["../src/provider/types.ts"],"sourcesContent":["/**\n * Provider Types for @pol-studios/powersync\n *\n * Defines configuration and context interfaces for the PowerSyncProvider.\n */\n\nimport type { SupabaseClient } from '@supabase/supabase-js';\nimport type { QueryClient } from '@tanstack/react-query';\nimport type { AbstractPowerSyncDatabase, SyncStatus, ConnectionHealth, SyncMetrics, CrudEntry, FailedTransaction, CompletedTransaction, SyncMode, DownloadProgress } from '../core/types';\nimport type { PlatformAdapter } from '../platform/types';\nimport type { ConnectorConfig } from '../connector/types';\nimport type { AttachmentConfig } from '../attachments/types';\nimport type { PolAttachmentQueue } from '../attachments/pol-attachment-queue';\nimport type { SupabaseConnector } from '../connector/supabase-connector';\nimport type { ConflictBus } from '../conflicts/conflict-bus';\n\n// ─── Provider Configuration ──────────────────────────────────────────────────\n\n/**\n * Main configuration for PowerSyncProvider.\n *\n * @template TSchema - The PowerSync schema type\n *\n * @example\n * ```typescript\n * const config: PowerSyncConfig<AppSchema> = {\n * platform: createNativePlatformAdapter(logger),\n * schema: AppSchema,\n * powerSyncUrl: 'https://your-powersync-instance.com',\n * supabaseClient: supabase,\n * dbFilename: 'myapp.db',\n * sync: {\n * autoConnect: true,\n * },\n * };\n * ```\n */\nexport interface PowerSyncConfig<TSchema = unknown> {\n /**\n * Platform adapter for platform-specific operations.\n * Use createNativePlatformAdapter() for React Native or createWebPlatformAdapter() for Web.\n */\n platform: PlatformAdapter;\n\n /**\n * PowerSync schema definition.\n * This defines the tables and their structure for the local database.\n */\n schema: TSchema;\n\n /**\n * PowerSync service URL.\n * @example \"https://your-instance.powersync.journeyapps.com\"\n */\n powerSyncUrl: string;\n\n /**\n * Supabase client instance.\n * Used for authentication and as the backend for CRUD uploads.\n */\n supabaseClient: SupabaseClient;\n\n /**\n * Optional: TanStack Query client for cache invalidation.\n * If provided, will invalidate queries when sync completes.\n */\n queryClient?: QueryClient;\n\n /**\n * Optional: Database filename.\n * @default \"powersync.db\"\n */\n dbFilename?: string;\n\n /**\n * Optional: Connector configuration for custom CRUD handling.\n */\n connector?: ConnectorConfig;\n\n /**\n * Optional: Attachment queue configuration for offline file caching.\n *\n * @example\n * ```typescript\n * attachments: {\n * source: { table: 'photos', idColumn: 'storage_path' },\n * remoteStorage: supabaseStorageAdapter,\n * }\n * ```\n */\n attachments?: AttachmentConfig;\n\n /**\n * Optional: Sync behavior configuration.\n */\n sync?: SyncConfig;\n}\n\n/**\n * Sync behavior configuration.\n */\nexport interface SyncConfig {\n /**\n * Automatically connect when the provider mounts and there's an authenticated session.\n * @default true\n */\n autoConnect?: boolean;\n\n /**\n * Sync interval for periodic sync checks (in milliseconds).\n * Set to 0 to disable periodic sync.\n * @default 0 (disabled)\n */\n syncInterval?: number;\n\n /**\n * Enable health monitoring.\n * @default true\n */\n enableHealthMonitoring?: boolean;\n\n /**\n * Enable metrics collection.\n * @default true\n */\n enableMetrics?: boolean;\n}\n\n// ─── Context Types ───────────────────────────────────────────────────────────\n\n/**\n * Value provided by the main PowerSyncContext.\n *\n * @template TSchema - The PowerSync schema type\n */\nexport interface PowerSyncContextValue<TSchema = unknown> {\n /**\n * The PowerSync database instance.\n * Will be null if not initialized or if initialization failed.\n */\n db: AbstractPowerSyncDatabase | null;\n\n /**\n * The Supabase connector instance.\n * Will be null if not initialized.\n */\n connector: SupabaseConnector | null;\n\n /**\n * The attachment queue instance.\n * Will be null if attachments are not configured or not initialized.\n */\n attachmentQueue: PolAttachmentQueue | null;\n\n /**\n * Whether the PowerSync database is ready for use.\n */\n isReady: boolean;\n\n /**\n * Whether the provider is currently initializing.\n */\n isInitializing: boolean;\n\n /**\n * Whether the attachment queue is ready for use.\n * This is separate from isReady because the attachment queue initializes\n * asynchronously after the database is ready.\n *\n * States:\n * - `false`: Attachment queue is still initializing\n * - `true` with `attachmentQueue !== null`: Ready and available\n * - `true` with `attachmentQueue === null`: Either attachments not configured, OR initialization failed\n */\n attachmentQueueReady: boolean;\n\n /**\n * Error that occurred during initialization, if any.\n */\n error: Error | null;\n\n /**\n * The schema used for this database.\n */\n schema: TSchema;\n\n /**\n * The platform adapter instance.\n */\n platform: PlatformAdapter;\n\n /**\n * The conflict bus for subscribing to conflict events.\n * Use this to wire up conflict UI components.\n */\n conflictBus: ConflictBus;\n}\n\n/**\n * Value provided by SyncStatusContext.\n */\nexport interface SyncStatusContextValue {\n /**\n * Current sync status.\n */\n status: SyncStatus;\n\n /**\n * Pending mutations waiting to be uploaded.\n */\n pendingMutations: CrudEntry[];\n\n /**\n * Number of pending mutations.\n */\n pendingCount: number;\n\n /**\n * Whether data is currently being uploaded to the server.\n * This is the authoritative source for upload activity.\n */\n isUploading: boolean;\n\n /**\n * Whether data is currently being downloaded from the server.\n * This is the authoritative source for download activity.\n */\n isDownloading: boolean;\n\n /**\n * Whether sync is currently paused (offline mode).\n */\n isPaused: boolean;\n\n /**\n * Current sync mode: 'push-pull' (full sync), 'pull-only' (download only), or 'offline' (no sync).\n */\n syncMode: SyncMode;\n\n /**\n * Timestamp of the last successful sync.\n */\n lastSyncedAt: Date | null;\n\n /**\n * Error that occurred during connection, if any.\n */\n connectionError: Error | null;\n\n /**\n * Failed transactions that need attention.\n */\n failedTransactions: FailedTransaction[];\n\n /**\n * Whether there are any upload errors.\n */\n hasUploadErrors: boolean;\n\n /**\n * Count of permanent errors that need user action.\n */\n permanentErrorCount: number;\n\n /**\n * Clear a specific failure by its ID.\n */\n clearFailure: (failureId: string) => void;\n\n /**\n * Clear all failures.\n */\n clearAllFailures: () => void;\n\n /**\n * Completed transactions history.\n */\n completedTransactions: CompletedTransaction[];\n\n /**\n * Clear the completed transaction history.\n */\n clearCompletedHistory: () => void;\n\n /**\n * Set the sync mode.\n * @param mode - The sync mode to set\n */\n setSyncMode: (mode: SyncMode) => Promise<void>;\n\n /**\n * Set the force next upload flag.\n * When true, the next sync cycle will upload regardless of sync mode.\n */\n setForceNextUpload: (force: boolean) => void;\n\n /**\n * Discard a specific pending mutation by its client ID.\n * Uses safe disconnect/reconnect pattern to avoid transaction conflicts.\n * @throws Error if upload is in progress\n */\n discardPendingMutation: (clientId: number) => Promise<void>;\n\n /**\n * Discard all pending mutations.\n * Uses safe disconnect/reconnect pattern to avoid transaction conflicts.\n * @throws Error if upload is in progress\n */\n discardAllPendingMutations: () => Promise<void>;\n\n /**\n * Pause automatic retry of failed uploads.\n * Useful when user is actively resolving conflicts.\n */\n pauseAutoRetry: () => void;\n\n /**\n * Resume automatic retry of failed uploads.\n */\n resumeAutoRetry: () => void;\n}\n\n// ─── Split Context Types (Performance Optimization) ─────────────────────────\n// These focused contexts allow components to subscribe to only the data they need,\n// preventing re-renders when unrelated data changes.\n\n/**\n * Connection state - changes rarely (on connect/disconnect).\n * Use this for components that only need to display connection status.\n */\nexport interface ConnectionStatusContextValue {\n /** Whether connected to the PowerSync service */\n connected: boolean;\n /** Whether currently attempting to connect */\n connecting: boolean;\n /** Whether initial sync has completed */\n hasSynced: boolean;\n /** Timestamp of last successful sync */\n lastSyncedAt: Date | null;\n /** Error that occurred during connection, if any */\n connectionError: Error | null;\n}\n\n/**\n * Sync activity - changes during active sync.\n * Use this for components that display sync progress.\n */\nexport interface SyncActivityContextValue {\n /** Whether data is currently being uploaded to the server */\n uploading: boolean;\n /** Whether data is currently being downloaded from the server */\n downloading: boolean;\n /** Download progress details */\n downloadProgress: DownloadProgress | null;\n}\n\n/**\n * Pending mutations - changes on local writes.\n * Use this for components that display pending upload count or list.\n */\nexport interface PendingMutationsContextValue {\n /** Pending mutations waiting to be uploaded */\n pendingMutations: CrudEntry[];\n /** Number of pending mutations */\n pendingCount: number;\n /** Discard a specific pending mutation by its client ID */\n discardPendingMutation: (clientId: number) => Promise<void>;\n /** Discard all pending mutations */\n discardAllPendingMutations: () => Promise<void>;\n /**\n * Add a pending mutation to the list.\n * Called by mutation hooks (useDbInsert, useDbUpdate, useDbDelete, useDbUpsert)\n * after a successful write. Includes createdAt timestamp for display.\n */\n addPendingMutation: (entry: CrudEntry) => void;\n /**\n * Remove a pending mutation by its entity ID.\n * Called when a transaction completes sync.\n */\n removePendingMutation: (id: string) => void;\n}\n\n/**\n * Failed transactions - changes on failures.\n * Use this for components that display sync errors.\n */\nexport interface FailedTransactionsContextValue {\n /** Failed transactions that need attention */\n failedTransactions: FailedTransaction[];\n /** Whether there are any upload errors */\n hasUploadErrors: boolean;\n /** Count of permanent errors that need user action */\n permanentErrorCount: number;\n /** Clear a specific failure by its ID */\n clearFailure: (failureId: string) => void;\n /** Clear all failures */\n clearAllFailures: () => void;\n /** Pause automatic retry of failed uploads */\n pauseAutoRetry: () => void;\n /** Resume automatic retry of failed uploads */\n resumeAutoRetry: () => void;\n /**\n * Retry a specific failed transaction.\n *\n * This removes the failure from tracking and triggers a sync.\n * The actual CRUD entries remain in PowerSync's queue - this just\n * clears our error tracking so the sync can retry them.\n *\n * If the sync fails to start, the failure is re-recorded.\n */\n retryFailure: (failureId: string) => Promise<void>;\n}\n\n/**\n * Completed transactions - changes on successful syncs.\n * Use this for components that display sync history.\n */\nexport interface CompletedTransactionsContextValue {\n /** Recently completed transactions */\n completedTransactions: CompletedTransaction[];\n /** Clear the completed transaction history */\n clearCompletedHistory: () => void;\n /** Clear a specific completed transaction by ID */\n clearCompletedItem: (completedId: string) => void;\n /**\n * Completed transactions that occurred AFTER the last notification display.\n * Use this for toast/banner notifications to avoid showing stale historical counts.\n */\n newCompletedTransactions: CompletedTransaction[];\n /**\n * Mark notifications as seen (updates the lastNotificationTime).\n * Call this when a sync notification is displayed or auto-dismissed.\n */\n markNotificationsAsSeen: () => void;\n}\n\n/**\n * Sync mode control - changes rarely (user action).\n * Use this for components that display/control sync mode.\n */\nexport interface SyncModeContextValue {\n /** Current sync mode */\n syncMode: SyncMode;\n /** Whether sync is currently paused (offline mode) */\n isPaused: boolean;\n /**\n * Whether offline mode was automatically set due to network loss.\n * When true, sync will auto-resume when network returns.\n * When false (user manually chose offline), sync won't auto-resume.\n */\n isAutoOffline: boolean;\n /**\n * Whether the network is currently reachable.\n * This is used as a gate to block uploads even when syncMode is 'push-pull'.\n */\n networkReachable: boolean;\n /** Set the sync mode (manual - won't auto-resume) */\n setSyncMode: (mode: SyncMode) => Promise<void>;\n /** Set the force next upload flag */\n setForceNextUpload: (force: boolean) => void;\n}\n\n/**\n * Value provided by ConnectionHealthContext.\n */\nexport interface ConnectionHealthContextValue {\n /**\n * Current connection health status.\n */\n health: ConnectionHealth;\n}\n\n/**\n * Value provided by SyncMetricsContext.\n */\nexport interface SyncMetricsContextValue {\n /**\n * Current sync metrics.\n */\n metrics: SyncMetrics;\n}\n\n// ─── Provider Props ──────────────────────────────────────────────────────────\n\n/**\n * Props for the PowerSyncProvider component.\n *\n * @template TSchema - The PowerSync schema type\n */\nexport interface PowerSyncProviderProps<TSchema = unknown> {\n /**\n * PowerSync configuration.\n */\n config: PowerSyncConfig<TSchema>;\n\n /**\n * Child components to render.\n */\n children: React.ReactNode;\n\n /**\n * Called when the database is initialized and ready.\n */\n onReady?: () => void;\n\n /**\n * Called when an error occurs during initialization.\n */\n onError?: (error: Error) => void;\n\n /**\n * Called when sync status changes.\n */\n onSyncStatusChange?: (status: SyncStatus) => void;\n}\n\n// ─── Default Values ──────────────────────────────────────────────────────────\n\n/**\n * Default sync status.\n */\nexport const DEFAULT_SYNC_STATUS: SyncStatus = {\n connected: false,\n connecting: false,\n hasSynced: false,\n lastSyncedAt: null,\n uploading: false,\n downloading: false,\n downloadProgress: null,\n failedTransactions: [],\n hasUploadErrors: false,\n permanentErrorCount: 0\n};\n\n/**\n * Default connection health.\n */\nexport const DEFAULT_CONNECTION_HEALTH: ConnectionHealth = {\n status: 'disconnected',\n latency: null,\n lastHealthCheck: null,\n consecutiveFailures: 0,\n reconnectAttempts: 0\n};\n\n/**\n * Default sync metrics.\n */\nexport const DEFAULT_SYNC_METRICS: SyncMetrics = {\n totalSyncs: 0,\n successfulSyncs: 0,\n failedSyncs: 0,\n lastSyncDuration: null,\n averageSyncDuration: null,\n totalDataDownloaded: 0,\n totalDataUploaded: 0,\n lastError: null\n};\n\n/**\n * Default sync configuration.\n */\nexport const DEFAULT_SYNC_CONFIG: Required<SyncConfig> = {\n autoConnect: true,\n syncInterval: 0,\n enableHealthMonitoring: true,\n enableMetrics: true\n};"],"mappings":";AAygBO,IAAM,sBAAkC;AAAA,EAC7C,WAAW;AAAA,EACX,YAAY;AAAA,EACZ,WAAW;AAAA,EACX,cAAc;AAAA,EACd,WAAW;AAAA,EACX,aAAa;AAAA,EACb,kBAAkB;AAAA,EAClB,oBAAoB,CAAC;AAAA,EACrB,iBAAiB;AAAA,EACjB,qBAAqB;AACvB;AAKO,IAAM,4BAA8C;AAAA,EACzD,QAAQ;AAAA,EACR,SAAS;AAAA,EACT,iBAAiB;AAAA,EACjB,qBAAqB;AAAA,EACrB,mBAAmB;AACrB;AAKO,IAAM,uBAAoC;AAAA,EAC/C,YAAY;AAAA,EACZ,iBAAiB;AAAA,EACjB,aAAa;AAAA,EACb,kBAAkB;AAAA,EAClB,qBAAqB;AAAA,EACrB,qBAAqB;AAAA,EACrB,mBAAmB;AAAA,EACnB,WAAW;AACb;AAKO,IAAM,sBAA4C;AAAA,EACvD,aAAa;AAAA,EACb,cAAc;AAAA,EACd,wBAAwB;AAAA,EACxB,eAAe;AACjB;","names":[]}
@@ -1 +0,0 @@
1
- {"version":3,"sources":["../src/sync/status-tracker.ts","../src/sync/metrics-collector.ts","../src/sync/health-monitor.ts"],"sourcesContent":["/**\n * Sync Status Tracker for @pol-studios/powersync\n *\n * Tracks and normalizes PowerSync status changes, providing a consistent\n * interface for status updates with throttling support.\n */\n\nimport type { SyncStatus, DownloadProgress, CrudEntry, FailedTransaction, SyncError, CompletedTransaction, SyncMode } from '../core/types';\nimport { generateFailureId } from '../core/errors';\nimport type { LoggerAdapter, AsyncStorageAdapter } from '../platform/types';\nimport type { SyncStatusState, SyncStatusTrackerOptions, PowerSyncRawStatus, Unsubscribe } from './types';\nimport { STORAGE_KEY_PAUSED, STORAGE_KEY_SYNC_MODE, STORAGE_KEY_AUTO_OFFLINE, STATUS_NOTIFY_THROTTLE_MS } from '../core/constants';\nconst STORAGE_KEY_COMPLETED_TRANSACTIONS = '@pol-powersync:completed_transactions';\nconst STORAGE_KEY_FAILED_TRANSACTIONS = '@pol-powersync:failed_transactions';\n\n// Maximum number of completed transactions to retain (oldest entries evicted first)\nconst MAX_COMPLETED_TRANSACTIONS = 1000;\nimport { DEFAULT_SYNC_STATUS } from '../provider/types';\n\n/**\n * Tracks sync status from PowerSync and provides normalized updates.\n *\n * Features:\n * - Normalizes raw PowerSync status to a consistent format\n * - Throttles notifications to prevent UI thrashing\n * - Tracks pending mutations count\n * - Persists and restores paused state\n *\n * @example\n * ```typescript\n * const tracker = new SyncStatusTracker({\n * storage,\n * logger,\n * onStatusChange: (status) => console.log('Status:', status),\n * });\n *\n * // Register with PowerSync\n * db.registerListener({\n * statusChanged: (rawStatus) => tracker.handleStatusChange(rawStatus),\n * });\n *\n * // Get current status\n * const status = tracker.getStatus();\n * ```\n */\nexport class SyncStatusTracker {\n private readonly storage: AsyncStorageAdapter;\n private readonly logger: LoggerAdapter;\n private readonly notifyThrottleMs: number;\n private readonly onStatusChange?: (status: SyncStatus) => void;\n private _state: SyncStatusState;\n private _pendingMutations: CrudEntry[] = [];\n private _lastNotifyTime = 0;\n private _notifyTimer: ReturnType<typeof setTimeout> | null = null;\n private _listeners = new Set<(status: SyncStatus) => void>();\n private _syncModeListeners = new Set<(mode: SyncMode) => void>();\n\n // Force next upload flag for \"Sync Now\" functionality\n private _forceNextUpload = false;\n\n // Network reachability gate - blocks uploads instantly when network is unreachable\n private _networkReachable = true;\n private _networkRestoreTimer: ReturnType<typeof setTimeout> | null = null;\n private readonly _networkRestoreDelayMs = 1500; // 1.5 seconds delay before restoring\n\n // Debounce timer for persist operations to avoid race conditions\n private _persistDebounceTimer: ReturnType<typeof setTimeout> | null = null;\n\n // Track download progress separately to preserve it when offline\n private _lastProgress: DownloadProgress | null = null;\n\n // Failed transaction tracking\n private _failedTransactions: FailedTransaction[] = [];\n private readonly _maxStoredFailures = 50;\n private readonly _failureTTLMs = 24 * 60 * 60 * 1000; // 24 hours\n private _failureListeners = new Set<(failures: FailedTransaction[]) => void>();\n\n // Completed transaction tracking (limited to MAX_COMPLETED_TRANSACTIONS entries)\n private _completedTransactions: CompletedTransaction[] = [];\n private _completedListeners = new Set<(completed: CompletedTransaction[]) => void>();\n\n // Track when notifications were last displayed/dismissed for \"auto-dismiss on display\"\n // This allows filtering completed transactions to only show new ones since last display\n private _lastNotificationTime: number = Date.now();\n\n // Auto-offline flag: tracks whether offline mode was set automatically (network loss)\n // vs manually (user chose offline). Persisted so auto-restore works after app restart.\n private _isAutoOffline = false;\n constructor(storage: AsyncStorageAdapter, logger: LoggerAdapter, options: SyncStatusTrackerOptions = {}) {\n this.storage = storage;\n this.logger = logger;\n this.notifyThrottleMs = options.notifyThrottleMs ?? STATUS_NOTIFY_THROTTLE_MS;\n this.onStatusChange = options.onStatusChange;\n this._state = {\n status: {\n ...DEFAULT_SYNC_STATUS\n },\n syncMode: 'push-pull',\n lastUpdated: new Date()\n };\n }\n\n // ─── Initialization ────────────────────────────────────────────────────────\n\n /**\n * Initialize the tracker by loading persisted state.\n * Includes migration from old isPaused boolean to new syncMode.\n */\n async init(): Promise<void> {\n try {\n // Try to load new sync mode first\n const modeValue = await this.storage.getItem(STORAGE_KEY_SYNC_MODE);\n if (modeValue && ['push-pull', 'pull-only', 'offline'].includes(modeValue)) {\n this._state.syncMode = modeValue as SyncMode;\n this.logger.debug('[StatusTracker] Loaded sync mode:', this._state.syncMode);\n } else {\n // Migrate from old isPaused boolean\n const pausedValue = await this.storage.getItem(STORAGE_KEY_PAUSED);\n if (pausedValue === 'true') {\n this._state.syncMode = 'offline';\n await this.storage.setItem(STORAGE_KEY_SYNC_MODE, 'offline');\n this.logger.debug('[StatusTracker] Migrated isPaused=true to syncMode=offline');\n } else {\n this._state.syncMode = 'push-pull';\n }\n // Clean up old key\n await this.storage.removeItem(STORAGE_KEY_PAUSED);\n }\n } catch (err) {\n this.logger.warn('[StatusTracker] Failed to load sync mode:', err);\n }\n\n // Load persisted auto-offline flag\n try {\n const autoOfflineValue = await this.storage.getItem(STORAGE_KEY_AUTO_OFFLINE);\n this._isAutoOffline = autoOfflineValue === 'true';\n this.logger.debug('[StatusTracker] Loaded isAutoOffline:', this._isAutoOffline);\n } catch (err) {\n this.logger.warn('[StatusTracker] Failed to load auto-offline flag:', err);\n }\n\n // Load persisted completed transactions\n try {\n const completedJson = await this.storage.getItem(STORAGE_KEY_COMPLETED_TRANSACTIONS);\n if (completedJson) {\n const parsed = JSON.parse(completedJson) as Array<Omit<CompletedTransaction, 'completedAt'> & {\n completedAt: string;\n }>;\n this._completedTransactions = parsed.map(item => {\n const remappedEntries = item.entries.map(e => this.remapEntry(e)).filter((e): e is CrudEntry => e !== null);\n return {\n ...item,\n completedAt: new Date(item.completedAt),\n entries: remappedEntries\n };\n }).filter(item => !isNaN(item.completedAt.getTime()) && item.entries.length > 0);\n this.logger.debug('[StatusTracker] Loaded', this._completedTransactions.length, 'completed transactions');\n }\n } catch (err) {\n this.logger.warn('[StatusTracker] Failed to load completed transactions:', err);\n }\n\n // Load persisted failed transactions\n try {\n const failedJson = await this.storage.getItem(STORAGE_KEY_FAILED_TRANSACTIONS);\n if (failedJson) {\n const parsed = JSON.parse(failedJson) as Array<Omit<FailedTransaction, 'firstFailedAt' | 'lastFailedAt' | 'error'> & {\n firstFailedAt: string;\n lastFailedAt: string;\n error: Omit<SyncError, 'timestamp'> & {\n timestamp: string;\n };\n }>;\n this._failedTransactions = parsed.map(item => {\n const remappedEntries = item.entries.map(e => this.remapEntry(e)).filter((e): e is CrudEntry => e !== null);\n return {\n ...item,\n firstFailedAt: new Date(item.firstFailedAt),\n lastFailedAt: new Date(item.lastFailedAt),\n error: {\n ...item.error,\n timestamp: new Date(item.error.timestamp)\n },\n entries: remappedEntries\n };\n }).filter(item => !isNaN(item.firstFailedAt.getTime()) && !isNaN(item.lastFailedAt.getTime()) && item.entries.length > 0);\n this.logger.debug('[StatusTracker] Loaded', this._failedTransactions.length, 'failed transactions');\n }\n } catch (err) {\n this.logger.warn('[StatusTracker] Failed to load failed transactions:', err);\n }\n\n // Clean up any stale failures that were persisted\n this.cleanupStaleFailures();\n }\n\n /**\n * Dispose the tracker and clear timers.\n */\n dispose(): void {\n if (this._notifyTimer) {\n clearTimeout(this._notifyTimer);\n this._notifyTimer = null;\n }\n if (this._persistDebounceTimer) {\n clearTimeout(this._persistDebounceTimer);\n this._persistDebounceTimer = null;\n }\n if (this._networkRestoreTimer) {\n clearTimeout(this._networkRestoreTimer);\n this._networkRestoreTimer = null;\n }\n this._listeners.clear();\n this._syncModeListeners.clear();\n this._failureListeners.clear();\n this._completedListeners.clear();\n }\n\n // ─── Status Getters ────────────────────────────────────────────────────────\n\n /**\n * Get the current sync status.\n */\n getStatus(): SyncStatus {\n const baseStatus = this._state.status;\n\n // Build the status with failed transaction info\n const status: SyncStatus = {\n ...baseStatus,\n failedTransactions: this._failedTransactions,\n hasUploadErrors: this._failedTransactions.length > 0,\n permanentErrorCount: this._failedTransactions.filter(f => f.isPermanent).length\n };\n\n // If offline, use saved progress instead of live (which would be null)\n if (this._state.syncMode === 'offline' && this._lastProgress) {\n return {\n ...status,\n downloadProgress: this._lastProgress\n };\n }\n return status;\n }\n\n // ─── Sync Mode Getters ────────────────────────────────────────────────────────\n\n /**\n * Get the current sync mode.\n */\n getSyncMode(): SyncMode {\n return this._state.syncMode;\n }\n\n /**\n * Check if uploads are allowed based on current sync mode and network reachability.\n */\n canUpload(): boolean {\n return this._networkReachable && this._state.syncMode === 'push-pull';\n }\n\n /**\n * Check if downloads are allowed based on current sync mode.\n */\n canDownload(): boolean {\n return this._state.syncMode !== 'offline';\n }\n\n /**\n * Set the force next upload flag for \"Sync Now\" functionality.\n */\n setForceNextUpload(force: boolean): void {\n this._forceNextUpload = force;\n this.logger.debug('[StatusTracker] Force next upload set to:', force);\n }\n\n /**\n * Clear the force next upload flag.\n * Should be called after all pending uploads have been processed.\n */\n clearForceNextUpload(): void {\n if (this._forceNextUpload) {\n this._forceNextUpload = false;\n this.logger.debug('[StatusTracker] Force next upload flag cleared');\n }\n }\n\n /**\n * Check if upload should proceed, considering force flag and network reachability.\n * NOTE: Does NOT auto-reset the flag - caller must use clearForceNextUpload()\n * after all uploads are complete. This prevents race conditions when\n * PowerSync calls uploadData() multiple times for multiple transactions.\n */\n shouldUpload(): boolean {\n // Force flag bypasses all gates (user explicitly requested sync)\n if (this._forceNextUpload) {\n return true;\n }\n // Network gate - instant block when unreachable (0ms, no timeouts)\n if (!this._networkReachable) {\n return false;\n }\n return this._state.syncMode === 'push-pull';\n }\n\n /**\n * Set network reachability state.\n * - When unreachable: Instantly blocks uploads (0ms)\n * - When reachable: Delayed restore (1-2 seconds) to avoid flickering on brief disconnects\n */\n setNetworkReachable(reachable: boolean): void {\n // Clear any pending restore timer\n if (this._networkRestoreTimer) {\n clearTimeout(this._networkRestoreTimer);\n this._networkRestoreTimer = null;\n }\n if (!reachable) {\n // Instant block when network becomes unreachable\n if (this._networkReachable) {\n this._networkReachable = false;\n this.logger.debug('[StatusTracker] Network unreachable - uploads blocked instantly');\n }\n } else {\n // Delayed restore when network becomes reachable\n if (!this._networkReachable) {\n this.logger.debug('[StatusTracker] Network reachable - scheduling delayed restore');\n this._networkRestoreTimer = setTimeout(() => {\n this._networkRestoreTimer = null;\n this._networkReachable = true;\n this.logger.debug('[StatusTracker] Network restored - uploads enabled');\n }, this._networkRestoreDelayMs);\n }\n }\n }\n\n /**\n * Get current network reachability state.\n */\n isNetworkReachable(): boolean {\n return this._networkReachable;\n }\n\n /**\n * Get pending mutations.\n */\n getPendingMutations(): CrudEntry[] {\n return this._pendingMutations;\n }\n\n /**\n * Get pending mutation count.\n */\n getPendingCount(): number {\n return this._pendingMutations.length;\n }\n\n // ─── Status Updates ────────────────────────────────────────────────────────\n\n /**\n * Handle a raw status update from PowerSync.\n */\n handleStatusChange(rawStatus: PowerSyncRawStatus): void {\n const progress = rawStatus.downloadProgress;\n const dataFlow = rawStatus.dataFlowStatus;\n\n // Build normalized download progress\n let downloadProgress: DownloadProgress | null = null;\n if (progress && progress.totalOperations && progress.totalOperations > 0) {\n downloadProgress = {\n current: progress.downloadedOperations ?? 0,\n target: progress.totalOperations,\n percentage: Math.round((progress.downloadedFraction ?? 0) * 100)\n };\n // Save progress for when paused\n this._lastProgress = downloadProgress;\n }\n\n // Build normalized status (failed transaction fields are added in getStatus())\n const newStatus: SyncStatus = {\n connected: rawStatus.connected ?? false,\n connecting: rawStatus.connecting ?? false,\n hasSynced: rawStatus.hasSynced ?? false,\n lastSyncedAt: rawStatus.lastSyncedAt ?? null,\n uploading: dataFlow?.uploading ?? false,\n downloading: dataFlow?.downloading ?? false,\n downloadProgress,\n // These are computed from _failedTransactions in getStatus()\n failedTransactions: [],\n hasUploadErrors: false,\n permanentErrorCount: 0\n };\n\n // Check if status actually changed\n const changed = this._hasStatusChanged(newStatus);\n this._state = {\n status: newStatus,\n syncMode: this._state.syncMode,\n lastUpdated: new Date()\n };\n if (changed) {\n this._notifyListeners();\n }\n }\n\n /**\n * Update pending mutations from a CRUD transaction.\n */\n updatePendingMutations(mutations: CrudEntry[]): void {\n this._pendingMutations = mutations;\n }\n\n /**\n * Valid sync modes for runtime validation.\n */\n private static readonly VALID_SYNC_MODES: SyncMode[] = ['push-pull', 'pull-only', 'offline'];\n\n /**\n * Set the sync mode.\n */\n async setSyncMode(mode: SyncMode): Promise<void> {\n // Runtime validation\n if (!SyncStatusTracker.VALID_SYNC_MODES.includes(mode)) {\n this.logger.warn('[StatusTracker] Invalid sync mode, ignoring:', mode);\n return;\n }\n if (this._state.syncMode === mode) return;\n const previousMode = this._state.syncMode;\n this._state.syncMode = mode;\n\n // Persist to storage\n try {\n await this.storage.setItem(STORAGE_KEY_SYNC_MODE, mode);\n this.logger.info('[StatusTracker] Sync mode changed:', previousMode, '->', mode);\n } catch (err) {\n this.logger.warn('[StatusTracker] Failed to persist sync mode:', err);\n }\n\n // Notify sync mode listeners\n for (const listener of this._syncModeListeners) {\n try {\n listener(mode);\n } catch (err) {\n this.logger.warn('[StatusTracker] Sync mode listener error:', err);\n }\n }\n this._notifyListeners(true);\n }\n\n // ─── Auto-Offline Management ──────────────────────────────────────────────\n\n /**\n * Get whether offline mode was set automatically (network loss) vs manually.\n * Used to determine if sync should auto-resume when network returns.\n */\n getIsAutoOffline(): boolean {\n return this._isAutoOffline;\n }\n\n /**\n * Set the auto-offline flag and persist it.\n * @param isAuto - true if offline was set automatically, false if user chose offline\n */\n async setIsAutoOffline(isAuto: boolean): Promise<void> {\n if (this._isAutoOffline === isAuto) return;\n this._isAutoOffline = isAuto;\n\n // Persist to storage\n try {\n await this.storage.setItem(STORAGE_KEY_AUTO_OFFLINE, isAuto ? 'true' : 'false');\n this.logger.debug('[StatusTracker] Auto-offline flag changed:', isAuto);\n } catch (err) {\n this.logger.warn('[StatusTracker] Failed to persist auto-offline flag:', err);\n }\n }\n\n // ─── Subscriptions ─────────────────────────────────────────────────────────\n\n /**\n * Subscribe to status changes.\n * @returns Unsubscribe function\n */\n onStatusUpdate(listener: (status: SyncStatus) => void): Unsubscribe {\n this._listeners.add(listener);\n // Immediately call with current status\n listener(this.getStatus());\n return () => {\n this._listeners.delete(listener);\n };\n }\n\n /**\n * Subscribe to sync mode changes.\n * @returns Unsubscribe function\n */\n onSyncModeChange(listener: (mode: SyncMode) => void): Unsubscribe {\n this._syncModeListeners.add(listener);\n listener(this._state.syncMode);\n return () => {\n this._syncModeListeners.delete(listener);\n };\n }\n\n // ─── Failed Transaction Tracking ────────────────────────────────────────────\n\n /**\n * Record a transaction failure.\n * If a failure for the same entries already exists, updates the retry count.\n * Otherwise, creates a new failure record.\n *\n * @param preserveMetadata - Optional. If provided, preserves retryCount and firstFailedAt from a previous failure.\n */\n recordTransactionFailure(entries: CrudEntry[], error: SyncError, isPermanent: boolean, affectedEntityIds: string[], affectedTables: string[], preserveMetadata?: {\n retryCount: number;\n firstFailedAt: Date;\n }): void {\n const now = new Date();\n\n // Normalize entries to plain objects to avoid CrudEntry.toJSON() remapping issues\n const normalizedEntries = this.normalizeEntries(entries);\n const entryIds = normalizedEntries.map(e => e.id).sort().join(',');\n\n // Check if a failure for these entries already exists\n const existingIndex = this._failedTransactions.findIndex(f => {\n const existingIds = f.entries.map(e => e.id).sort().join(',');\n return existingIds === entryIds;\n });\n if (existingIndex !== -1) {\n // Update existing failure\n const existing = this._failedTransactions[existingIndex];\n this._failedTransactions[existingIndex] = {\n ...existing,\n error,\n retryCount: existing.retryCount + 1,\n lastFailedAt: now,\n isPermanent\n };\n } else {\n // Create new failure record\n const newFailure: FailedTransaction = {\n id: generateFailureId(normalizedEntries),\n entries: normalizedEntries,\n error,\n retryCount: preserveMetadata?.retryCount ?? 1,\n firstFailedAt: preserveMetadata?.firstFailedAt ?? now,\n lastFailedAt: now,\n isPermanent,\n affectedEntityIds,\n affectedTables\n };\n this._failedTransactions.push(newFailure);\n\n // Enforce max stored failures (remove oldest)\n if (this._failedTransactions.length > this._maxStoredFailures) {\n this._failedTransactions.sort((a, b) => a.firstFailedAt.getTime() - b.firstFailedAt.getTime());\n this._failedTransactions = this._failedTransactions.slice(-this._maxStoredFailures);\n }\n }\n this._schedulePersist();\n this._notifyFailureListeners();\n this._notifyListeners();\n }\n\n /**\n * Clear a specific failure by ID.\n */\n clearFailure(failureId: string): void {\n const initialLength = this._failedTransactions.length;\n this._failedTransactions = this._failedTransactions.filter(f => f.id !== failureId);\n if (this._failedTransactions.length !== initialLength) {\n this._schedulePersist();\n this._notifyFailureListeners();\n this._notifyListeners();\n }\n }\n\n /**\n * Clear all failures.\n */\n clearAllFailures(): void {\n if (this._failedTransactions.length === 0) return;\n this._failedTransactions = [];\n this._schedulePersist();\n this._notifyFailureListeners();\n this._notifyListeners();\n }\n\n /**\n * Remove a failed transaction from tracking and return its entries.\n * This is a \"pop\" operation - the failure is removed from the list.\n *\n * Note: The actual CRUD entries remain in PowerSync's ps_crud table\n * until successfully uploaded. This just removes from our tracking.\n *\n * @param failureId - The failure ID to remove\n * @returns The CrudEntry[] that were in the failure, or null if not found\n */\n takeFailureForRetry(failureId: string): CrudEntry[] | null {\n const failure = this._failedTransactions.find(f => f.id === failureId);\n if (!failure) {\n this.logger.warn('[StatusTracker] Failure not found for retry:', failureId);\n return null;\n }\n\n // Remove from failed list\n this._failedTransactions = this._failedTransactions.filter(f => f.id !== failureId);\n this._notifyFailureListeners();\n this._schedulePersist();\n this.logger.info('[StatusTracker] Retrieved failure for retry:', failureId, 'entries:', failure.entries.length);\n return failure.entries;\n }\n\n /**\n * Get failures affecting a specific entity.\n */\n getFailuresForEntity(entityId: string): FailedTransaction[] {\n return this._failedTransactions.filter(f => f.affectedEntityIds.includes(entityId));\n }\n\n /**\n * Get all failed transactions.\n */\n getFailedTransactions(): FailedTransaction[] {\n return [...this._failedTransactions];\n }\n\n /**\n * Check if there are any upload errors.\n */\n hasUploadErrors(): boolean {\n return this._failedTransactions.length > 0;\n }\n\n /**\n * Get count of permanent errors.\n */\n getPermanentErrorCount(): number {\n return this._failedTransactions.filter(f => f.isPermanent).length;\n }\n\n /**\n * Subscribe to failure changes.\n * @returns Unsubscribe function\n */\n onFailureChange(listener: (failures: FailedTransaction[]) => void): Unsubscribe {\n this._failureListeners.add(listener);\n // Immediately call with current failures\n listener(this.getFailedTransactions());\n return () => {\n this._failureListeners.delete(listener);\n };\n }\n\n /**\n * Clean up stale failures (older than TTL).\n */\n cleanupStaleFailures(): void {\n const cutoff = Date.now() - this._failureTTLMs;\n const initialLength = this._failedTransactions.length;\n this._failedTransactions = this._failedTransactions.filter(f => f.lastFailedAt.getTime() > cutoff);\n if (this._failedTransactions.length !== initialLength) {\n this.logger.debug(`[StatusTracker] Cleaned up ${initialLength - this._failedTransactions.length} stale failures`);\n this._schedulePersist();\n this._notifyFailureListeners();\n this._notifyListeners();\n }\n }\n\n // ─── Completed Transaction Tracking ────────────────────────────────────────\n\n /**\n * Record a successfully completed transaction.\n * Creates a CompletedTransaction record and adds it to history.\n */\n recordTransactionComplete(entries: CrudEntry[]): void {\n // Normalize entries to plain objects to avoid CrudEntry.toJSON() remapping issues\n const normalizedEntries = this.normalizeEntries(entries);\n\n // Extract unique affected tables\n const affectedTables = [...new Set(normalizedEntries.map(e => e.table))];\n\n // Extract unique affected entity IDs\n const affectedEntityIds = [...new Set(normalizedEntries.map(e => e.id))];\n\n // Generate unique ID\n const id = `completed_${Date.now()}_${Math.random().toString(36).slice(2, 9)}`;\n const completed: CompletedTransaction = {\n id,\n entries: normalizedEntries,\n completedAt: new Date(),\n affectedTables,\n affectedEntityIds\n };\n\n // Add to front of array (most recent first)\n this._completedTransactions.unshift(completed);\n\n // Limit completed history to prevent unbounded memory growth (oldest entries evicted)\n if (this._completedTransactions.length > MAX_COMPLETED_TRANSACTIONS) {\n this._completedTransactions = this._completedTransactions.slice(0, MAX_COMPLETED_TRANSACTIONS);\n }\n this._schedulePersist();\n this._notifyCompletedListeners();\n this.logger.debug(`[StatusTracker] Recorded completed transaction: ${completed.id} (${entries.length} entries)`);\n }\n\n /**\n * Get all completed transactions.\n */\n getCompletedTransactions(): CompletedTransaction[] {\n return [...this._completedTransactions];\n }\n\n /**\n * Clear completed transaction history.\n */\n clearCompletedHistory(): void {\n if (this._completedTransactions.length === 0) return;\n this._completedTransactions = [];\n this._schedulePersist();\n this._notifyCompletedListeners();\n this.logger.debug('[StatusTracker] Cleared completed transaction history');\n }\n\n /**\n * Clear a specific completed transaction by ID.\n */\n clearCompletedItem(completedId: string): void {\n const initialLength = this._completedTransactions.length;\n this._completedTransactions = this._completedTransactions.filter(c => c.id !== completedId);\n if (this._completedTransactions.length !== initialLength) {\n this._schedulePersist();\n this._notifyCompletedListeners();\n this.logger.debug('[StatusTracker] Cleared completed transaction:', completedId);\n }\n }\n\n /**\n * Subscribe to completed transaction changes.\n * @returns Unsubscribe function\n */\n onCompletedChange(listener: (completed: CompletedTransaction[]) => void): Unsubscribe {\n this._completedListeners.add(listener);\n // Immediately call with current completed transactions\n listener(this.getCompletedTransactions());\n return () => {\n this._completedListeners.delete(listener);\n };\n }\n\n // ─── Notification Tracking ─────────────────────────────────────────────────\n\n /**\n * Get completed transactions that occurred AFTER the last notification time.\n * This is used for displaying \"X changes synced\" notifications to avoid\n * showing stale counts from historical completed transactions.\n */\n getNewCompletedTransactions(): CompletedTransaction[] {\n return this._completedTransactions.filter(tx => tx.completedAt.getTime() > this._lastNotificationTime);\n }\n\n /**\n * Mark notifications as seen by updating the last notification time.\n * Call this when the notification is displayed or dismissed.\n */\n markNotificationsAsSeen(): void {\n this._lastNotificationTime = Date.now();\n this.logger.debug('[StatusTracker] Notifications marked as seen');\n // Notify listeners so UI can update (newCompletedTransactions will now be empty)\n this._notifyCompletedListeners();\n }\n\n /**\n * Get the timestamp of when notifications were last displayed/dismissed.\n */\n getLastNotificationTime(): number {\n return this._lastNotificationTime;\n }\n\n // ─── Private Methods ───────────────────────────────────────────────────────\n\n /**\n * Schedule a debounced persist operation.\n * This prevents race conditions from multiple rapid persist calls.\n */\n private _schedulePersist(): void {\n if (this._persistDebounceTimer) {\n clearTimeout(this._persistDebounceTimer);\n }\n this._persistDebounceTimer = setTimeout(() => {\n this._persistDebounceTimer = null;\n this._persistTransactions();\n }, 100); // 100ms debounce\n }\n\n /**\n * Persist completed and failed transactions to storage.\n */\n private async _persistTransactions(): Promise<void> {\n try {\n await Promise.all([this.storage.setItem(STORAGE_KEY_COMPLETED_TRANSACTIONS, JSON.stringify(this._completedTransactions)), this.storage.setItem(STORAGE_KEY_FAILED_TRANSACTIONS, JSON.stringify(this._failedTransactions))]);\n } catch (err) {\n this.logger.warn('[StatusTracker] Failed to persist transactions:', err);\n }\n }\n private _hasStatusChanged(newStatus: SyncStatus): boolean {\n const old = this._state.status;\n return old.connected !== newStatus.connected || old.connecting !== newStatus.connecting || old.hasSynced !== newStatus.hasSynced || old.uploading !== newStatus.uploading || old.downloading !== newStatus.downloading || old.lastSyncedAt?.getTime() !== newStatus.lastSyncedAt?.getTime() || old.downloadProgress?.current !== newStatus.downloadProgress?.current || old.downloadProgress?.target !== newStatus.downloadProgress?.target;\n }\n\n /**\n * Notify all listeners of status changes with throttling.\n *\n * Uses a \"dirty\" flag pattern: when throttled, we schedule a timer\n * but get the CURRENT state when the timer fires, not the stale state\n * from when the timer was scheduled. This ensures rapid state changes\n * during the throttle window aren't lost.\n */\n private _notifyListeners(forceImmediate = false): void {\n const now = Date.now();\n const timeSinceLastNotify = now - this._lastNotifyTime;\n\n // If a timer is already scheduled, don't reschedule - just let it fire\n // and it will pick up the current (latest) state at that time\n if (this._notifyTimer && !forceImmediate) {\n return; // Already scheduled, will get current state when it fires\n }\n if (this._notifyTimer) {\n clearTimeout(this._notifyTimer);\n this._notifyTimer = null;\n }\n const notify = () => {\n this._notifyTimer = null;\n this._lastNotifyTime = Date.now();\n // Get CURRENT state at notification time, not stale state\n const status = this.getStatus();\n\n // Call the main callback\n this.onStatusChange?.(status);\n\n // Call all listeners\n for (const listener of this._listeners) {\n try {\n listener(status);\n } catch (err) {\n this.logger.warn('[StatusTracker] Listener error:', err);\n }\n }\n };\n if (forceImmediate || timeSinceLastNotify >= this.notifyThrottleMs) {\n notify();\n } else {\n const delayMs = this.notifyThrottleMs - timeSinceLastNotify;\n this._notifyTimer = setTimeout(notify, delayMs);\n }\n }\n private _notifyFailureListeners(): void {\n const failures = this.getFailedTransactions();\n for (const listener of this._failureListeners) {\n try {\n listener(failures);\n } catch (err) {\n this.logger.warn('[StatusTracker] Failure listener error:', err);\n }\n }\n }\n private _notifyCompletedListeners(): void {\n const completed = this.getCompletedTransactions();\n for (const listener of this._completedListeners) {\n try {\n listener(completed);\n } catch (err) {\n this.logger.warn('[StatusTracker] Completed listener error:', err);\n }\n }\n }\n\n /**\n * Remap a CrudEntry from persisted JSON (handles toJSON() property remapping).\n * PowerSync's CrudEntry.toJSON() remaps: opData→data, table→type, clientId→op_id, transactionId→tx_id\n *\n * @returns The remapped CrudEntry, or null if critical fields (table, id) are missing\n */\n private remapEntry(entry: any): CrudEntry | null {\n const table = entry.table ?? entry.type;\n const id = entry.id;\n\n // Validate critical fields\n if (!table || typeof table !== 'string') {\n this.logger.warn('[StatusTracker] Invalid CrudEntry: missing or invalid table field', entry);\n return null;\n }\n if (!id || typeof id !== 'string') {\n this.logger.warn('[StatusTracker] Invalid CrudEntry: missing or invalid id field', entry);\n return null;\n }\n return {\n id,\n clientId: entry.clientId ?? entry.op_id ?? 0,\n op: entry.op,\n table,\n opData: entry.opData ?? entry.data,\n transactionId: entry.transactionId ?? entry.tx_id\n };\n }\n\n /**\n * Normalize CrudEntry array to plain objects to avoid CrudEntry.toJSON() remapping issues.\n * PowerSync's CrudEntry.toJSON() remaps property names which breaks deserialization.\n */\n private normalizeEntries(entries: CrudEntry[]): CrudEntry[] {\n return entries.map(e => ({\n id: e.id,\n clientId: e.clientId,\n op: e.op,\n table: e.table,\n opData: e.opData,\n transactionId: e.transactionId\n }));\n }\n}","/**\n * Sync Metrics Collector for @pol-studios/powersync\n *\n * Collects and persists sync operation metrics for monitoring and debugging.\n */\n\nimport type { SyncMetrics, SyncError, SyncErrorType } from '../core/types';\nimport type { AsyncStorageAdapter, LoggerAdapter } from '../platform/types';\nimport type { MetricsCollectorOptions, SyncOperationData, Unsubscribe } from './types';\nimport { STORAGE_KEY_METRICS } from '../core/constants';\nimport { classifyError } from '../core/errors';\nimport { DEFAULT_SYNC_METRICS } from '../provider/types';\n\n/**\n * Collects sync metrics including operation counts, timing, and errors.\n *\n * Features:\n * - Tracks sync operation success/failure rates\n * - Calculates average sync duration\n * - Monitors data transfer amounts\n * - Persists metrics to storage for continuity across sessions\n * - Records last error for debugging\n *\n * @example\n * ```typescript\n * const collector = new MetricsCollector({\n * storage,\n * logger,\n * onMetricsChange: (metrics) => updateUI(metrics),\n * });\n *\n * // Start tracking a sync\n * const startTime = Date.now();\n *\n * // On sync complete\n * collector.recordSync({\n * durationMs: Date.now() - startTime,\n * success: true,\n * operationsDownloaded: 150,\n * });\n *\n * // Get current metrics\n * const metrics = collector.getMetrics();\n * ```\n */\nexport class MetricsCollector {\n private readonly storage: AsyncStorageAdapter;\n private readonly logger: LoggerAdapter;\n private readonly storageKey: string;\n private readonly persistMetrics: boolean;\n private readonly onMetricsChange?: (metrics: SyncMetrics) => void;\n private _metrics: SyncMetrics;\n private _listeners = new Set<(metrics: SyncMetrics) => void>();\n private _initialized = false;\n\n // Track active sync for timing\n private _syncStartTime: number | null = null;\n private _wasSyncing = false;\n\n // Debounced persistence\n private _persistTimeout: ReturnType<typeof setTimeout> | null = null;\n private readonly PERSIST_DEBOUNCE_MS = 100;\n constructor(storage: AsyncStorageAdapter, logger: LoggerAdapter, options: MetricsCollectorOptions = {}) {\n this.storage = storage;\n this.logger = logger;\n this.storageKey = options.storageKey ?? STORAGE_KEY_METRICS;\n this.persistMetrics = options.persistMetrics ?? true;\n this.onMetricsChange = options.onMetricsChange;\n this._metrics = {\n ...DEFAULT_SYNC_METRICS\n };\n }\n\n // ─── Initialization ────────────────────────────────────────────────────────\n\n /**\n * Initialize the collector by loading persisted metrics.\n */\n async init(): Promise<void> {\n if (this._initialized) return;\n try {\n const stored = await this.storage.getItem(this.storageKey);\n if (stored) {\n const parsed = JSON.parse(stored);\n // Restore Date objects\n if (parsed.lastError?.timestamp) {\n parsed.lastError.timestamp = new Date(parsed.lastError.timestamp);\n }\n this._metrics = {\n ...DEFAULT_SYNC_METRICS,\n ...parsed\n };\n this.logger.debug('[MetricsCollector] Loaded persisted metrics');\n }\n } catch (err) {\n this.logger.warn('[MetricsCollector] Failed to load metrics:', err);\n }\n this._initialized = true;\n }\n\n /**\n * Dispose the collector.\n */\n dispose(): void {\n if (this._persistTimeout) {\n clearTimeout(this._persistTimeout);\n this._persistTimeout = null;\n }\n this._listeners.clear();\n }\n\n // ─── Getters ───────────────────────────────────────────────────────────────\n\n /**\n * Get current sync metrics.\n */\n getMetrics(): SyncMetrics {\n return {\n ...this._metrics\n };\n }\n\n // ─── Recording ─────────────────────────────────────────────────────────────\n\n /**\n * Record a completed sync operation.\n */\n async recordSync(data: SyncOperationData): Promise<void> {\n const {\n durationMs,\n success,\n operationsDownloaded,\n operationsUploaded,\n error\n } = data;\n const totalSyncs = this._metrics.totalSyncs + 1;\n const successfulSyncs = this._metrics.successfulSyncs + (success ? 1 : 0);\n const failedSyncs = this._metrics.failedSyncs + (success ? 0 : 1);\n\n // Calculate running average duration (only for successful syncs)\n let averageSyncDuration = this._metrics.averageSyncDuration;\n if (success) {\n if (averageSyncDuration !== null) {\n averageSyncDuration = (averageSyncDuration * (successfulSyncs - 1) + durationMs) / successfulSyncs;\n } else {\n averageSyncDuration = durationMs;\n }\n }\n\n // Estimate data transfer (rough approximation: ~100 bytes per operation)\n const bytesPerOp = 100;\n const downloaded = (operationsDownloaded ?? 0) * bytesPerOp;\n const uploaded = (operationsUploaded ?? 0) * bytesPerOp;\n\n // Build error record if failed\n let lastError: SyncError | null = this._metrics.lastError;\n if (!success && error) {\n const errorType = classifyError(error);\n lastError = {\n type: errorType,\n message: error.message,\n userMessage: error.message,\n // Use original message as user message for metrics\n timestamp: new Date(),\n isPermanent: false\n };\n }\n this._metrics = {\n totalSyncs,\n successfulSyncs,\n failedSyncs,\n lastSyncDuration: durationMs,\n averageSyncDuration: averageSyncDuration !== null ? Math.round(averageSyncDuration) : null,\n totalDataDownloaded: this._metrics.totalDataDownloaded + downloaded,\n totalDataUploaded: this._metrics.totalDataUploaded + uploaded,\n lastError\n };\n this._schedulePersist();\n this._notifyListeners();\n }\n\n /**\n * Record a sync error without a full sync operation.\n */\n async recordError(error: Error): Promise<void> {\n this._metrics = {\n ...this._metrics,\n failedSyncs: this._metrics.failedSyncs + 1,\n lastError: {\n type: classifyError(error),\n message: error.message,\n userMessage: error.message,\n // Use original message as user message for metrics\n timestamp: new Date(),\n isPermanent: false\n }\n };\n this._schedulePersist();\n this._notifyListeners();\n }\n\n /**\n * Record an upload operation.\n */\n async recordUpload(operationCount: number): Promise<void> {\n const bytesPerOp = 100;\n this._metrics = {\n ...this._metrics,\n totalDataUploaded: this._metrics.totalDataUploaded + operationCount * bytesPerOp\n };\n this._schedulePersist();\n this._notifyListeners();\n }\n\n /**\n * Clear all metrics and start fresh.\n */\n async reset(): Promise<void> {\n this._metrics = {\n ...DEFAULT_SYNC_METRICS\n };\n await this._persist();\n this._notifyListeners();\n }\n\n // ─── Sync Timing Helpers ───────────────────────────────────────────────────\n\n /**\n * Called when sync starts (downloading becomes true).\n */\n markSyncStart(): void {\n if (!this._wasSyncing) {\n this._syncStartTime = Date.now();\n this._wasSyncing = true;\n }\n }\n\n /**\n * Called when sync ends (downloading becomes false).\n * Returns the duration if a sync was in progress.\n */\n markSyncEnd(): number | null {\n if (this._wasSyncing && this._syncStartTime !== null) {\n const duration = Date.now() - this._syncStartTime;\n this._syncStartTime = null;\n this._wasSyncing = false;\n return duration;\n }\n return null;\n }\n\n /**\n * Check if sync is currently in progress.\n */\n isSyncInProgress(): boolean {\n return this._wasSyncing;\n }\n\n // ─── Subscriptions ─────────────────────────────────────────────────────────\n\n /**\n * Subscribe to metrics changes.\n * @returns Unsubscribe function\n */\n onMetricsUpdate(listener: (metrics: SyncMetrics) => void): Unsubscribe {\n this._listeners.add(listener);\n listener(this.getMetrics());\n return () => {\n this._listeners.delete(listener);\n };\n }\n\n // ─── Private Methods ───────────────────────────────────────────────────────\n\n private _schedulePersist(): void {\n if (this._persistTimeout) return; // Already scheduled\n\n this._persistTimeout = setTimeout(() => {\n this._persistTimeout = null;\n this._persist().catch(err => {\n this.logger.warn('[MetricsCollector] Persist failed:', err);\n });\n }, this.PERSIST_DEBOUNCE_MS);\n }\n private async _persist(): Promise<void> {\n if (!this.persistMetrics) return;\n try {\n await this.storage.setItem(this.storageKey, JSON.stringify(this._metrics));\n } catch (err) {\n this.logger.warn('[MetricsCollector] Failed to persist metrics:', err);\n }\n }\n private _notifyListeners(): void {\n const metrics = this.getMetrics();\n\n // Call main callback\n this.onMetricsChange?.(metrics);\n\n // Call all listeners\n for (const listener of this._listeners) {\n try {\n listener(metrics);\n } catch (err) {\n this.logger.warn('[MetricsCollector] Listener error:', err);\n }\n }\n }\n}","/**\n * Connection Health Monitor for @pol-studios/powersync\n *\n * Monitors database connection health with periodic checks and latency tracking.\n */\n\nimport type { ConnectionHealth } from '../core/types';\nimport type { AbstractPowerSyncDatabase } from '../core/types';\nimport type { LoggerAdapter } from '../platform/types';\nimport type { HealthMonitorOptions, HealthCheckResult, Unsubscribe } from './types';\nimport { HEALTH_CHECK_INTERVAL_MS, HEALTH_CHECK_TIMEOUT_MS, LATENCY_DEGRADED_THRESHOLD_MS, MAX_CONSECUTIVE_FAILURES } from '../core/constants';\nimport { DEFAULT_CONNECTION_HEALTH } from '../provider/types';\n\n/**\n * Monitors connection health with periodic checks.\n *\n * Features:\n * - Periodic health checks with configurable interval\n * - Latency measurement and degraded state detection\n * - Consecutive failure tracking\n * - Auto-recovery detection\n *\n * @example\n * ```typescript\n * const monitor = new HealthMonitor(db, logger, {\n * checkIntervalMs: 30000,\n * onHealthChange: (health) => {\n * if (health.status === 'degraded') {\n * showWarning('Connection is slow');\n * }\n * },\n * });\n *\n * // Start monitoring\n * monitor.start();\n *\n * // Get current health\n * const health = monitor.getHealth();\n *\n * // Stop when done\n * monitor.stop();\n * ```\n */\nexport class HealthMonitor {\n private readonly logger: LoggerAdapter;\n private readonly checkIntervalMs: number;\n private readonly checkTimeoutMs: number;\n private readonly degradedThresholdMs: number;\n private readonly maxConsecutiveFailures: number;\n private readonly onHealthChange?: (health: ConnectionHealth) => void;\n private _db: AbstractPowerSyncDatabase | null = null;\n private _health: ConnectionHealth;\n private _intervalId: ReturnType<typeof setInterval> | null = null;\n private _listeners = new Set<(health: ConnectionHealth) => void>();\n private _running = false;\n private _paused = false;\n private _pendingTimers = new Set<ReturnType<typeof setTimeout>>();\n constructor(logger: LoggerAdapter, options: HealthMonitorOptions = {}) {\n this.logger = logger;\n this.checkIntervalMs = options.checkIntervalMs ?? HEALTH_CHECK_INTERVAL_MS;\n this.checkTimeoutMs = options.checkTimeoutMs ?? HEALTH_CHECK_TIMEOUT_MS;\n this.degradedThresholdMs = options.degradedThresholdMs ?? LATENCY_DEGRADED_THRESHOLD_MS;\n this.maxConsecutiveFailures = options.maxConsecutiveFailures ?? MAX_CONSECUTIVE_FAILURES;\n this.onHealthChange = options.onHealthChange;\n this._health = {\n ...DEFAULT_CONNECTION_HEALTH\n };\n }\n\n // ─── Lifecycle ─────────────────────────────────────────────────────────────\n\n /**\n * Set the database instance to monitor.\n */\n setDatabase(db: AbstractPowerSyncDatabase | null): void {\n this._db = db;\n if (!db) {\n // Reset health when database is cleared\n this._updateHealth({\n status: 'disconnected',\n latency: null,\n lastHealthCheck: new Date(),\n consecutiveFailures: 0,\n reconnectAttempts: this._health.reconnectAttempts\n });\n }\n }\n\n /**\n * Start the health monitor.\n */\n start(): void {\n if (this._running) return;\n this.logger.info('[HealthMonitor] Starting');\n this._running = true;\n\n // Perform initial check\n this._checkHealth().catch(err => {\n this.logger.warn('[HealthMonitor] Initial check error:', err);\n });\n\n // Set up periodic checks\n this._intervalId = setInterval(() => {\n if (!this._paused) {\n this._checkHealth().catch(err => {\n this.logger.warn('[HealthMonitor] Periodic check error:', err);\n });\n }\n }, this.checkIntervalMs);\n }\n\n /**\n * Stop the health monitor.\n */\n stop(): void {\n if (!this._running) return;\n this.logger.info('[HealthMonitor] Stopping');\n this._running = false;\n if (this._intervalId) {\n clearInterval(this._intervalId);\n this._intervalId = null;\n }\n }\n\n /**\n * Pause health checks temporarily.\n */\n pause(): void {\n this._paused = true;\n // Clear any pending timeout timers to prevent leaks\n this._pendingTimers.forEach(clearTimeout);\n this._pendingTimers.clear();\n this._updateHealth({\n ...this._health,\n status: 'disconnected'\n });\n }\n\n /**\n * Resume health checks.\n */\n resume(): void {\n this._paused = false;\n // Perform immediate check on resume\n this._checkHealth().catch(err => {\n this.logger.warn('[HealthMonitor] Resume check error:', err);\n });\n }\n\n /**\n * Dispose the monitor and clear all resources.\n */\n dispose(): void {\n this.stop();\n // Clear any pending timeout timers to prevent leaks\n this._pendingTimers.forEach(clearTimeout);\n this._pendingTimers.clear();\n this._listeners.clear();\n }\n\n // ─── Getters ───────────────────────────────────────────────────────────────\n\n /**\n * Get current connection health.\n */\n getHealth(): ConnectionHealth {\n return {\n ...this._health\n };\n }\n\n /**\n * Check if the monitor is running.\n */\n isRunning(): boolean {\n return this._running;\n }\n\n // ─── Manual Checks ─────────────────────────────────────────────────────────\n\n /**\n * Perform an immediate health check.\n * @returns The result of the health check\n */\n async checkNow(): Promise<HealthCheckResult> {\n return this._checkHealth();\n }\n\n /**\n * Record a reconnection attempt.\n */\n recordReconnectAttempt(): void {\n this._updateHealth({\n ...this._health,\n reconnectAttempts: this._health.reconnectAttempts + 1\n });\n }\n\n /**\n * Reset reconnection attempts counter (call on successful connection).\n */\n resetReconnectAttempts(): void {\n if (this._health.reconnectAttempts > 0) {\n this._updateHealth({\n ...this._health,\n reconnectAttempts: 0\n });\n }\n }\n\n // ─── Subscriptions ─────────────────────────────────────────────────────────\n\n /**\n * Subscribe to health changes.\n * @returns Unsubscribe function\n */\n onHealthUpdate(listener: (health: ConnectionHealth) => void): Unsubscribe {\n this._listeners.add(listener);\n listener(this.getHealth());\n return () => {\n this._listeners.delete(listener);\n };\n }\n\n // ─── Private Methods ───────────────────────────────────────────────────────\n\n private async _checkHealth(): Promise<HealthCheckResult> {\n if (!this._db || this._paused) {\n return {\n success: false,\n error: new Error('Database not available or paused'),\n timestamp: new Date()\n };\n }\n const startTime = Date.now();\n const timestamp = new Date();\n try {\n // Execute a simple query with timeout\n await this._withTimeout(this._db.get('SELECT 1'), this.checkTimeoutMs);\n const latencyMs = Date.now() - startTime;\n\n // Determine status based on latency\n const status: ConnectionHealth['status'] = latencyMs < this.degradedThresholdMs ? 'healthy' : 'degraded';\n this._updateHealth({\n status,\n latency: latencyMs,\n lastHealthCheck: timestamp,\n consecutiveFailures: 0,\n reconnectAttempts: this._health.reconnectAttempts\n });\n return {\n success: true,\n latencyMs,\n timestamp\n };\n } catch (err) {\n const error = err instanceof Error ? err : new Error(String(err));\n this.logger.warn('[HealthMonitor] Health check failed:', error.message);\n const consecutiveFailures = this._health.consecutiveFailures + 1;\n const status: ConnectionHealth['status'] = consecutiveFailures >= this.maxConsecutiveFailures ? 'disconnected' : 'degraded';\n this._updateHealth({\n status,\n latency: null,\n lastHealthCheck: timestamp,\n consecutiveFailures,\n reconnectAttempts: this._health.reconnectAttempts\n });\n return {\n success: false,\n error,\n timestamp\n };\n }\n }\n private _updateHealth(health: ConnectionHealth): void {\n const changed = this._hasHealthChanged(health);\n this._health = health;\n if (changed) {\n this._notifyListeners();\n }\n }\n private _hasHealthChanged(newHealth: ConnectionHealth): boolean {\n const old = this._health;\n return old.status !== newHealth.status || old.latency !== newHealth.latency || old.consecutiveFailures !== newHealth.consecutiveFailures || old.reconnectAttempts !== newHealth.reconnectAttempts;\n }\n private _notifyListeners(): void {\n const health = this.getHealth();\n\n // Call main callback\n this.onHealthChange?.(health);\n\n // Call all listeners\n for (const listener of this._listeners) {\n try {\n listener(health);\n } catch (err) {\n this.logger.warn('[HealthMonitor] Listener error:', err);\n }\n }\n }\n private _withTimeout<T>(promise: Promise<T>, timeoutMs: number): Promise<T> {\n return new Promise((resolve, reject) => {\n const timer = setTimeout(() => {\n this._pendingTimers.delete(timer);\n reject(new Error(`Health check timeout after ${timeoutMs}ms`));\n }, timeoutMs);\n this._pendingTimers.add(timer);\n promise.then(result => {\n this._pendingTimers.delete(timer);\n clearTimeout(timer);\n resolve(result);\n }, error => {\n this._pendingTimers.delete(timer);\n clearTimeout(timer);\n reject(error);\n });\n });\n }\n}"],"mappings":"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","names":[]}
@@ -1 +0,0 @@
1
- //# sourceMappingURL=chunk-654ERHA7.js.map
@@ -1 +0,0 @@
1
- {"version":3,"sources":["../src/connector/types.ts","../src/connector/middleware/upload-error.ts","../src/connector/errors.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// Re-export ConflictBus type for convenience\nexport type { ConflictBus } from '../conflicts/conflict-bus';\n\n// ─── Connector Configuration ─────────────────────────────────────────────────\n\n/**\n * Circuit breaker configuration for the connector.\n * Prevents cascading failures by stopping requests when service is down.\n */\nexport interface ConnectorCircuitBreakerConfig {\n /**\n * Enable circuit breaker pattern.\n * @default false\n */\n enabled: boolean;\n /**\n * Number of failures required to trip the circuit.\n * @default 5\n */\n failureThreshold?: number;\n /**\n * Time window in ms for counting failures.\n * @default 60000 (60 seconds)\n */\n failureWindowMs?: number;\n /**\n * Initial delay before transitioning from OPEN to HALF_OPEN.\n * @default 1000 (1 second)\n */\n initialRecoveryDelayMs?: number;\n /**\n * Maximum delay for exponential backoff.\n * @default 32000 (32 seconds)\n */\n maxRecoveryDelayMs?: number;\n}\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?: (entries: CrudEntry[], error: Error, classified: ClassifiedError) => 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 * Optional: Configuration for retry behavior on upload failures.\n * Allows customizing retry attempts, delays, and backoff for different error types.\n * @default DEFAULT_RETRY_CONFIG\n */\n retryConfig?: Partial<RetryConfig>;\n /**\n * Optional: Configuration for circuit breaker pattern.\n * When enabled, prevents cascading failures by stopping requests when\n * the service is experiencing high failure rates.\n * @default { enabled: false }\n */\n circuitBreaker?: ConnectorCircuitBreakerConfig;\n /**\n * Optional: Middleware chain for classifying upload errors.\n * Allows customizing error handling for specific tables or error codes.\n *\n * Middleware functions are called in order until one returns a non-'continue' result:\n * - 'success': Treat as successful (e.g., idempotent duplicate)\n * - 'retry': Use the retry configuration to retry the operation\n * - 'discard': Permanent failure, remove from queue without retry\n * - 'continue': Pass to the next middleware in the chain\n *\n * If all middleware return 'continue', the default classification is used.\n *\n * @example\n * ```typescript\n * uploadErrorMiddleware: [\n * // Treat duplicate key as success for idempotent tables\n * idempotentTables(['ReadReceipt', 'Like', 'Bookmark']),\n * // Discard FK violations (orphaned records)\n * discardOrphaned(),\n * // Custom handling\n * ({ entry, pgCode }) => {\n * if (entry.table === 'MyTable' && pgCode === '23505') {\n * return 'success';\n * }\n * return 'continue';\n * },\n * ]\n * ```\n */\n uploadErrorMiddleware?: UploadErrorMiddleware[];\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 * Optional retry configuration for upload failures.\n * Allows customizing retry attempts, delays, and backoff for different error types.\n * @default DEFAULT_RETRY_CONFIG\n */\n retryConfig?: Partial<RetryConfig>;\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?(entry: CrudEntry, supabase: SupabaseClient, schema: string): 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?(entry: CrudEntry, supabase: SupabaseClient, schema: string): 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?(entry: CrudEntry, supabase: SupabaseClient, schema: string): 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// ─── Retry Configuration ─────────────────────────────────────────────────────\n\n/**\n * Configuration for a single retry category\n */\nexport interface RetryStrategyConfig {\n /** Maximum number of retry attempts */\n maxRetries: number;\n /** Initial delay in milliseconds */\n baseDelayMs: number;\n /** Maximum delay cap in milliseconds */\n maxDelayMs: number;\n /** Multiplier for exponential backoff */\n backoffMultiplier: number;\n}\n\n/**\n * Full retry configuration for the connector\n */\nexport interface RetryConfig {\n /** Retry config for transient errors (network, server 5xx) */\n transient: RetryStrategyConfig;\n /** Retry config for permanent errors (validation, constraints) */\n permanent: RetryStrategyConfig;\n /** Retry config for RLS/permission errors (42501, row-level security violations) */\n rls: RetryStrategyConfig;\n}\n\n/**\n * Default retry configuration\n *\n * Uses fast exponential backoff for transient errors (network issues):\n * - Transient: 1s → 2s → 4s\n *\n * RLS/permission errors (42501, row-level security violations) use extended delays\n * because parent data may need time to sync before child records can be inserted.\n * - RLS: 30s → 60s → 120s → 120s → 120s (5 retries over ~7.5 minutes)\n *\n * Other permanent errors (validation, constraints) get NO retries\n * because they will never succeed - fail fast and surface to user.\n * PowerSync's native retry mechanism will re-attempt on next sync cycle.\n */\nexport const DEFAULT_RETRY_CONFIG: RetryConfig = {\n transient: {\n maxRetries: 3,\n baseDelayMs: 1000,\n // 1 second initial delay\n maxDelayMs: 4000,\n // 4 second cap\n backoffMultiplier: 2 // 1s → 2s → 4s\n },\n permanent: {\n maxRetries: 0,\n // No retries - permanent errors won't succeed\n baseDelayMs: 1000,\n // Irrelevant with 0 retries, but needed for type\n maxDelayMs: 1000,\n // Irrelevant with 0 retries\n backoffMultiplier: 1 // Irrelevant with 0 retries\n },\n rls: {\n maxRetries: 5,\n // Extended retries for RLS errors\n baseDelayMs: 30000,\n // 30 seconds initial delay\n maxDelayMs: 120000,\n // 2 minutes max delay\n backoffMultiplier: 2 // 30s → 60s → 120s → 120s → 120s\n }\n};\n\n// ─── Auth Abstraction ────────────────────────────────────────────────────────\n\n/**\n * Session representation for authentication.\n * Used by AuthProvider to communicate auth state.\n */\nexport interface Session {\n /** Access token for API requests */\n accessToken: string;\n /** When the token expires (optional) */\n expiresAt?: Date;\n /** User information (optional) */\n user?: {\n id: string;\n };\n}\n\n/**\n * Authentication provider interface.\n * Abstracts auth for the connector - works with any auth backend.\n *\n * @example\n * ```typescript\n * // Using built-in Supabase adapter\n * const auth = createSupabaseAuth(supabaseClient);\n *\n * // Custom implementation\n * const auth: AuthProvider = {\n * getSession: async () => ({ accessToken: myToken }),\n * refreshSession: async () => {\n * const newToken = await myAuthService.refresh();\n * return { accessToken: newToken };\n * },\n * onAuthStateChange: (cb) => {\n * return myAuthService.subscribe(cb);\n * },\n * };\n * ```\n */\nexport interface AuthProvider {\n /**\n * Get the current session.\n * @returns Current session or null if not authenticated\n */\n getSession(): Promise<Session | null>;\n\n /**\n * Refresh the current session.\n * @throws Error if refresh fails (e.g., refresh token expired)\n * @returns New session with fresh access token\n */\n refreshSession(): Promise<Session>;\n\n /**\n * Subscribe to auth state changes.\n * @param cb - Callback fired when auth state changes\n * @returns Unsubscribe function\n */\n onAuthStateChange(cb: (session: Session | null) => void): () => void;\n}\n\n// ─── Upload Error Middleware ─────────────────────────────────────────────────\n\n/**\n * Classification result for upload errors.\n * Determines how the connector should handle a failed upload.\n *\n * - 'success': Treat as successful (e.g., idempotent duplicate)\n * - 'retry': Use the retry configuration to retry the operation\n * - 'discard': Permanent failure, remove from queue without retry\n * - 'continue': Pass to the next middleware in the chain\n * - 'fail_transaction': Abort the entire transaction immediately without retry\n */\nexport type UploadErrorClassification = 'success' | 'retry' | 'discard' | 'continue' | 'fail_transaction';\n\n/**\n * Context provided to upload error middleware functions.\n * Contains all information needed to classify an upload error.\n */\nexport interface UploadErrorContext {\n /** The CRUD entry that failed */\n entry: CrudEntry;\n /** The error that occurred */\n error: Error;\n /** PostgreSQL error code if available (e.g., '23505' for unique violation) */\n pgCode: string | undefined;\n /** HTTP status code if available */\n httpStatusCode: number | undefined;\n /** The classified error from the default classifier */\n classified: ClassifiedError;\n /** The original error object (may contain additional properties) */\n originalError: unknown;\n /** The schema the table belongs to */\n schema: string;\n /** Supabase client for queries (use sparingly - prefer local checks) */\n supabase: SupabaseClient;\n}\n\n/**\n * Middleware function for classifying upload errors.\n *\n * Middleware functions are called in order until one returns a non-'continue' result.\n * Return 'continue' to pass to the next middleware in the chain.\n *\n * @example\n * ```typescript\n * const myMiddleware: UploadErrorMiddleware = ({ entry, pgCode }) => {\n * if (entry.table === 'MyTable' && pgCode === '23505') {\n * return 'success'; // Treat as successful\n * }\n * return 'continue'; // Let the next middleware handle it\n * };\n * ```\n */\nexport type UploadErrorMiddleware = (context: UploadErrorContext) => Promise<UploadErrorClassification> | UploadErrorClassification;\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 * Upload Error Middleware for @pol-studios/powersync\n *\n * Provides a middleware pattern for classifying and handling upload errors.\n * Includes built-in factory functions for common patterns and an executor\n * for running the middleware chain.\n */\n\nimport type { UploadErrorClassification, UploadErrorContext, UploadErrorMiddleware } from '../types';\n\n// ─── Built-in Middleware Factories ───────────────────────────────────────────\n\n/**\n * Create middleware that treats duplicate key errors (23505) as success\n * for specified tables.\n *\n * This is useful for idempotent operations where re-inserting the same\n * record should be treated as a success, not an error.\n *\n * @param tables - Array of table names to treat as idempotent\n * @returns Middleware function\n *\n * @example\n * ```typescript\n * uploadErrorMiddleware: [\n * idempotentTables(['ReadReceipt', 'Like', 'Bookmark']),\n * ]\n * ```\n */\nexport function idempotentTables(tables: string[]): UploadErrorMiddleware {\n const tableSet = new Set(tables);\n return ({\n entry,\n pgCode\n }) => {\n if (tableSet.has(entry.table) && pgCode === '23505') {\n return 'success';\n }\n return 'continue';\n };\n}\n\n/**\n * Create middleware that discards entries with foreign key violations (23503).\n *\n * This is useful for handling orphaned records where the parent record\n * was deleted before the child record could be uploaded.\n *\n * @returns Middleware function\n *\n * @example\n * ```typescript\n * uploadErrorMiddleware: [\n * discardOrphaned(),\n * ]\n * ```\n */\nexport function discardOrphaned(): UploadErrorMiddleware {\n return ({\n pgCode\n }) => {\n if (pgCode === '23503') {\n return 'discard';\n }\n return 'continue';\n };\n}\n\n/**\n * Create middleware that discards entries with specific PostgreSQL error codes.\n *\n * @param codes - Array of PostgreSQL error codes to discard\n * @returns Middleware function\n *\n * @example\n * ```typescript\n * uploadErrorMiddleware: [\n * // Discard check constraint violations and not-null violations\n * discardOnPgCodes(['23514', '23502']),\n * ]\n * ```\n */\nexport function discardOnPgCodes(codes: string[]): UploadErrorMiddleware {\n const codeSet = new Set(codes);\n return ({\n pgCode\n }) => {\n if (pgCode && codeSet.has(pgCode)) {\n return 'discard';\n }\n return 'continue';\n };\n}\n\n/**\n * Create middleware that treats specific PostgreSQL error codes as success\n * for specific tables.\n *\n * @param config - Map of table names to arrays of PostgreSQL error codes to treat as success\n * @returns Middleware function\n *\n * @example\n * ```typescript\n * uploadErrorMiddleware: [\n * successOnPgCodes({\n * 'ReadReceipt': ['23505'], // Duplicate key is OK\n * 'Notification': ['23505', '23503'], // Duplicate and FK violations are OK\n * }),\n * ]\n * ```\n */\nexport function successOnPgCodes(config: Record<string, string[]>): UploadErrorMiddleware {\n return ({\n entry,\n pgCode\n }) => {\n const codes = config[entry.table];\n if (codes && pgCode && codes.includes(pgCode)) {\n return 'success';\n }\n return 'continue';\n };\n}\n\n/**\n * Create middleware that retries entries on specific HTTP status codes.\n *\n * @param statusCodes - Array of HTTP status codes to retry\n * @returns Middleware function\n *\n * @example\n * ```typescript\n * uploadErrorMiddleware: [\n * // Retry on rate limiting and server errors\n * retryOnHttpStatus([429, 500, 502, 503, 504]),\n * ]\n * ```\n */\nexport function retryOnHttpStatus(statusCodes: number[]): UploadErrorMiddleware {\n const statusSet = new Set(statusCodes);\n return ({\n httpStatusCode\n }) => {\n if (httpStatusCode && statusSet.has(httpStatusCode)) {\n return 'retry';\n }\n return 'continue';\n };\n}\n\n/**\n * Create middleware that discards entries on specific HTTP status codes.\n *\n * @param statusCodes - Array of HTTP status codes to discard\n * @returns Middleware function\n *\n * @example\n * ```typescript\n * uploadErrorMiddleware: [\n * // Discard on 400 Bad Request (malformed data)\n * discardOnHttpStatus([400]),\n * ]\n * ```\n */\nexport function discardOnHttpStatus(statusCodes: number[]): UploadErrorMiddleware {\n const statusSet = new Set(statusCodes);\n return ({\n httpStatusCode\n }) => {\n if (httpStatusCode && statusSet.has(httpStatusCode)) {\n return 'discard';\n }\n return 'continue';\n };\n}\n\n/**\n * Create middleware that applies table-specific handling.\n *\n * @param handlers - Map of table names to handler functions\n * @returns Middleware function\n *\n * @example\n * ```typescript\n * uploadErrorMiddleware: [\n * tableHandlers({\n * 'AuditLog': () => 'discard', // Never retry audit logs\n * 'UserSession': ({ pgCode }) =>\n * pgCode === '23505' ? 'success' : 'continue',\n * }),\n * ]\n * ```\n */\nexport function tableHandlers(handlers: Record<string, UploadErrorMiddleware>): UploadErrorMiddleware {\n return context => {\n const handler = handlers[context.entry.table];\n if (handler) {\n return handler(context);\n }\n return 'continue';\n };\n}\n\n// ─── Middleware Executor ─────────────────────────────────────────────────────\n\n/**\n * Run upload error middleware chain and return the classification.\n *\n * Executes middleware in order until one returns a non-'continue' result.\n * If all middleware return 'continue', returns the default classification.\n *\n * @param context - The error context to pass to middleware\n * @param middleware - Array of middleware functions to execute\n * @param defaultClassification - Classification to use if all middleware return 'continue'\n * @returns The final classification\n *\n * @example\n * ```typescript\n * const classification = await runUploadErrorMiddleware(\n * context,\n * [idempotentTables(['ReadReceipt']), discardOrphaned()],\n * 'retry' // Default if no middleware matches\n * );\n * ```\n */\nexport async function runUploadErrorMiddleware(context: UploadErrorContext, middleware: UploadErrorMiddleware[], defaultClassification: UploadErrorClassification = 'retry'): Promise<UploadErrorClassification> {\n for (const mw of middleware) {\n const result = await mw(context);\n if (result !== 'continue') {\n return result;\n }\n }\n return defaultClassification;\n}\n\n/**\n * Synchronous version of runUploadErrorMiddleware for middleware that don't use async.\n *\n * @param context - The error context to pass to middleware\n * @param middleware - Array of middleware functions to execute\n * @param defaultClassification - Classification to use if all middleware return 'continue'\n * @returns The final classification\n */\nexport function runUploadErrorMiddlewareSync(context: UploadErrorContext, middleware: UploadErrorMiddleware[], defaultClassification: UploadErrorClassification = 'retry'): UploadErrorClassification {\n for (const mw of middleware) {\n const result = mw(context);\n // If the middleware returns a promise, throw an error\n if (result && typeof result === 'object' && 'then' in result) {\n throw new Error('Async middleware detected in runUploadErrorMiddlewareSync. ' + 'Use runUploadErrorMiddleware instead.');\n }\n // At this point we know result is UploadErrorClassification (not a Promise)\n const syncResult = result as UploadErrorClassification;\n if (syncResult !== 'continue') {\n return syncResult;\n }\n }\n return defaultClassification;\n}","/**\n * Custom errors for the @pol-studios/powersync connector.\n *\n * These errors are used by the upload error middleware to control\n * how failed CRUD entries are handled.\n */\n\n/**\n * Error thrown when a CRUD entry is discarded by middleware.\n * When this error is thrown, the entry is removed from the queue without retry.\n */\nexport class DiscardEntryError extends Error {\n constructor(message: string) {\n super(message);\n this.name = 'DiscardEntryError';\n }\n}\n\n/**\n * Error thrown when a transaction is aborted by middleware.\n * When thrown, stops the entire transaction immediately without retry.\n */\nexport class TransactionAbortError extends Error {\n constructor(message: string, public readonly cause?: Error) {\n super(message);\n this.name = 'TransactionAbortError';\n }\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 * Error thrown when conflict detection fails due to external issues\n * (e.g., AuditLog query failure). Callers should handle this and decide\n * whether to retry, fail the sync, or escalate to the user.\n */\nexport class ConflictDetectionError extends Error {\n constructor(message: string, options?: {\n cause?: unknown;\n }) {\n super(message);\n this.name = 'ConflictDetectionError';\n this.cause = options?.cause;\n }\n}\n\n/**\n * Deep equality comparison for detecting field changes.\n *\n * Handles:\n * - Primitive types (string, number, boolean, null, undefined)\n * - NaN values (NaN === NaN returns true)\n * - Date objects (compared by getTime() value)\n * - Arrays (recursive element comparison)\n * - Plain objects (recursive property comparison)\n * - Circular references (protected by maxDepth limit)\n *\n * @param a - First value to compare\n * @param b - Second value to compare\n * @param maxDepth - Maximum recursion depth to prevent infinite loops (default: 10)\n * @returns true if values are deeply equal, false otherwise\n */\nfunction deepEqual(a: unknown, b: unknown, maxDepth = 10): boolean {\n // Strict equality handles primitives, same reference, null === null, undefined === undefined\n if (a === b) return true;\n\n // Handle NaN (NaN !== NaN in JavaScript, but we want NaN === NaN for conflict detection)\n if (typeof a === 'number' && typeof b === 'number') {\n if (Number.isNaN(a) && Number.isNaN(b)) return true;\n }\n\n // Handle null and undefined (both must be checked since undefined !== null)\n if (a === null || a === undefined || b === null || b === undefined) return false;\n\n // Type mismatch\n if (typeof a !== typeof b) return false;\n\n // Non-object types that aren't equal already returned above\n if (typeof a !== 'object') return false;\n\n // Depth limit to prevent infinite loops from circular references\n if (maxDepth <= 0) {\n console.warn('[deepEqual] Max depth reached, falling back to reference equality');\n return a === b;\n }\n\n // Handle Date objects - compare by timestamp\n if (a instanceof Date && b instanceof Date) {\n return a.getTime() === b.getTime();\n }\n\n // Handle Date vs non-Date mismatch\n if (a instanceof Date || b instanceof Date) return false;\n\n // Array type mismatch\n if (Array.isArray(a) !== Array.isArray(b)) return false;\n const keysA = Object.keys(a as object);\n const keysB = Object.keys(b as object);\n if (keysA.length !== keysB.length) return false;\n for (const key of keysA) {\n if (!keysB.includes(key)) return false;\n if (!deepEqual((a as Record<string, unknown>)[key], (b as Record<string, unknown>)[key], maxDepth - 1)) {\n return false;\n }\n }\n return true;\n}\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}\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(table: string, recordId: string, localVersion: number, serverVersion: number, pendingChanges: Record<string, unknown>, supabase: SupabaseClient, config?: ConflictDetectionConfig): Promise<ConflictCheckResult> {\n const ignoredFields = new Set([...DEFAULT_IGNORED_FIELDS, ...(config?.ignoredFields ?? [])]);\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 {\n data: auditLogs,\n error\n } = await supabase.schema('core').from('AuditLog').select('oldRecord, newRecord, changeBy, changeAt').eq('tableName', table).eq('recordId_text', recordId).order('changeAt', {\n ascending: false\n }).limit(20); // Recent changes should be sufficient\n\n if (error) {\n // Don't assume no conflict - throw and let caller decide how to handle\n throw new ConflictDetectionError(`AuditLog query failed for ${table}/${recordId}`, {\n cause: error\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 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 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 // Use deep equality to properly compare arrays and objects\n if (!deepEqual(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 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 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(table: string, db: AbstractPowerSyncDatabase): 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<{\n name: string;\n }>(`PRAGMA table_info(\"${table}\")`);\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(table: string, recordId: string, schema: string, supabase: SupabaseClient): Promise<number | null> {\n const query = schema === 'public' ? supabase.from(table) : (supabase.schema(schema) as unknown as ReturnType<typeof supabase.schema>).from(table);\n const {\n data,\n error\n } = await query.select('_version').eq('id', recordId).single();\n if (error || !data) {\n return null;\n }\n return (data as {\n _version?: number;\n })._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(table: string, recordId: string, db: AbstractPowerSyncDatabase): Promise<number | null> {\n // Validate table name to prevent SQL injection\n validateTableName(table);\n const result = await db.get<{\n _version?: number;\n }>(`SELECT _version FROM \"${table}\" WHERE id = ?`, [recordId]);\n return result?._version ?? null;\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 { SupabaseConnectorOptions, PowerSyncBackendConnector, PowerSyncCredentials, SchemaRouter, CrudHandler, RetryConfig, UploadErrorMiddleware, UploadErrorContext } from './types';\nimport { runUploadErrorMiddleware } from './middleware';\nimport { DiscardEntryError, TransactionAbortError } from './errors';\nimport type { ConflictBus } from '../conflicts/conflict-bus';\nimport type { ConflictHandler, ConflictDetectionConfig, ConflictCheckResult, ConflictResolution } from '../conflicts/types';\nimport { defaultSchemaRouter, DEFAULT_RETRY_CONFIG } from './types';\nimport { classifySupabaseError, isRlsError, extractHttpStatusCode } from '../core/errors';\nimport { detectConflicts, fetchServerVersion, getLocalVersion, hasVersionColumn } from '../conflicts/detect';\nimport { withExponentialBackoff } from '../utils/retry';\n\n/**\n * Custom error for validation failures (payload size, required fields, etc.)\n */\nclass ValidationError extends Error {\n constructor(message: string) {\n super(message);\n this.name = 'ValidationError';\n }\n}\n\n/**\n * Check if an error is an authentication/authorization error (401 or JWT-related).\n * Used to trigger token refresh on auth failures during CRUD operations.\n *\n * Uses specific patterns with word boundaries to avoid false positives\n * (e.g., \"Cache entry expired\" should NOT match).\n */\nfunction isAuthError(error: unknown): boolean {\n if (!error) return false;\n\n // Check for HTTP 401/403 status codes\n if (typeof error === 'object' && error !== null) {\n const statusCode = (error as {\n status?: number;\n statusCode?: number;\n }).status ?? (error as {\n status?: number;\n statusCode?: number;\n }).statusCode;\n if (statusCode === 401 || statusCode === 403) {\n return true;\n }\n\n // Also check string code for compatibility\n const code = (error as {\n code?: string | number;\n }).code;\n if (code === '401' || code === '403' || code === '42501') {\n return true;\n }\n }\n const message = error instanceof Error ? error.message.toLowerCase() : String(error).toLowerCase();\n\n // Use specific patterns to avoid false positives like \"Cache entry expired\"\n const authPatterns = [/\\bjwt\\s+(expired|invalid|malformed)\\b/, /\\btoken\\s+(expired|invalid|revoked)\\b/, /\\bsession\\s+(expired|invalid)\\b/, /\\bunauthorized\\s*(access|request|error)?\\b/, /\\baccess\\s+(denied|forbidden)\\b/, /\\bnot\\s+authorized\\b/, /\\bauth(entication)?\\s+(failed|error|required)\\b/, /\\bpermission\\s+denied\\b/, /\\binvalid\\s+(credentials|api[_\\s]?key)\\b/, /\\brls\\b.*\\bpolicy\\b/, /\\brow[_\\s]?level[_\\s]?security\\b/, /\\b42501\\b/ // PostgreSQL insufficient privilege error code\n ];\n return authPatterns.some(pattern => pattern.test(message));\n}\n\n/**\n * Wrap a promise with a timeout that cleans up when the race completes.\n * Prevents timer leaks that would cause unhandled promise rejections.\n *\n * @param promise - The promise to wrap\n * @param ms - Timeout in milliseconds\n * @param message - Error message if timeout occurs\n */\nfunction withTimeout<T>(promise: Promise<T>, ms: number, message: string): Promise<T> {\n let timeoutId: ReturnType<typeof setTimeout>;\n const timeoutPromise = new Promise<never>((_, reject) => {\n timeoutId = setTimeout(() => reject(new Error(message)), ms);\n });\n return Promise.race([promise, timeoutPromise]).finally(() => clearTimeout(timeoutId));\n}\n\n// Maximum size limit for CRUD payloads (with margin for safety)\nconst MAX_PAYLOAD_SIZE = 900 * 1024; // 900KB\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 * Group CRUD entries by table name for batched processing.\n */\nfunction groupEntriesByTable(entries: CrudEntry[]): Map<string, CrudEntry[]> {\n const grouped = new Map<string, CrudEntry[]>();\n for (const entry of entries) {\n const existing = grouped.get(entry.table);\n if (existing) {\n existing.push(entry);\n } else {\n grouped.set(entry.table, [entry]);\n }\n }\n return grouped;\n}\n\n/**\n * Context for transaction finalization (DRY helper).\n */\ninterface TransactionFinalizationContext {\n transaction: {\n complete: () => Promise<void>;\n };\n successfulEntries: CrudEntry[];\n discardedEntries?: CrudEntry[];\n partialResolutions?: Array<{\n originalConflict: ConflictCheckResult;\n syncedFields: string[];\n }>;\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?: (entries: CrudEntry[], error: Error, classified: ClassifiedError) => 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 // Store resolutions for retry - when PowerSync retries, we apply stored resolutions\n // instead of re-detecting conflicts that the user already resolved\n private resolvedConflicts = new Map<string, ConflictResolution>();\n\n // Cleanup function for resolution listener subscription\n private unsubscribeResolution?: () => void;\n\n // Promise-based locking for version column checks to prevent duplicate queries\n private versionColumnPromises = new Map<string, Promise<boolean>>();\n\n // Flag to track if connector has been destroyed\n private isDestroyed = false;\n\n // Retry configuration\n private retryConfig: RetryConfig;\n\n // Track completion failures for circuit breaker logic\n // Maps transaction fingerprint (hash of entry IDs) to failure tracking info\n private completionFailures = new Map<string, {\n count: number;\n lastAttempt: Date;\n }>();\n private static readonly COMPLETION_MAX_FAILURES = 3;\n private static readonly COMPLETION_EXTENDED_TIMEOUT_MS = 60_000; // 60s timeout for retry\n private static readonly MAX_STORED_RESOLUTIONS = 100;\n private autoRetryPaused: boolean = false;\n\n // Per-entry cooldown tracking for failed operations\n // Maps entry key (table:id) to the timestamp when it can be retried\n private entryCooldowns = new Map<string, number>();\n private static readonly COOLDOWN_DURATION_MS = 60_000; // 1 minute cooldown after exhausting retries\n private static readonly MAX_ENTRY_COOLDOWNS = 200;\n\n // Upload error middleware chain\n private readonly uploadErrorMiddleware: UploadErrorMiddleware[];\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 // Initialize retry configuration by merging user options with defaults\n this.retryConfig = {\n transient: {\n ...DEFAULT_RETRY_CONFIG.transient,\n ...options.retryConfig?.transient\n },\n permanent: {\n ...DEFAULT_RETRY_CONFIG.permanent,\n ...options.retryConfig?.permanent\n },\n rls: {\n ...DEFAULT_RETRY_CONFIG.rls,\n ...options.retryConfig?.rls\n }\n };\n\n // Initialize upload error middleware chain\n this.uploadErrorMiddleware = options.uploadErrorMiddleware ?? [];\n\n // Subscribe to resolution events from the conflict bus\n // This stores resolutions so that on retry, we apply them instead of re-detecting\n if (this.conflictBus) {\n this.unsubscribeResolution = this.conflictBus.onResolution((table, recordId, resolution) => {\n // Key by table:recordId to avoid collisions across tables with same UUID\n const key = `${table}:${recordId}`;\n if (__DEV__) {\n console.log('[Connector] Storing resolution for retry:', {\n table,\n recordId,\n key,\n resolution\n });\n }\n // Apply LRU eviction before adding new entry to prevent unbounded growth\n if (this.resolvedConflicts.size >= SupabaseConnector.MAX_STORED_RESOLUTIONS) {\n const firstKey = this.resolvedConflicts.keys().next().value;\n if (firstKey) this.resolvedConflicts.delete(firstKey);\n }\n this.resolvedConflicts.set(key, resolution);\n });\n }\n }\n\n /**\n * Clean up resources (unsubscribe from event listeners).\n * Call this when the connector is no longer needed.\n */\n destroy(): void {\n this.isDestroyed = true;\n if (this.unsubscribeResolution) {\n this.unsubscribeResolution();\n this.unsubscribeResolution = undefined;\n }\n this.resolvedConflicts.clear();\n this.versionColumnPromises.clear();\n this.completionFailures.clear();\n }\n\n /**\n * Generate a fingerprint for a set of entries to track completion failures.\n * Uses sorted entry IDs to create a consistent fingerprint regardless of order.\n */\n private generateTransactionFingerprint(entries: CrudEntry[]): string {\n // Sort IDs for consistency and join them\n const sortedIds = entries.map(e => `${e.table}:${e.id}`).sort();\n return sortedIds.join('|');\n }\n\n // ─── Retry Control Methods ─────────────────────────────────────────────────\n\n /**\n * Pause automatic retry of failed uploads.\n * Use this when the user goes offline intentionally or wants manual control.\n */\n pauseAutoRetry(): void {\n this.autoRetryPaused = true;\n if (__DEV__) {\n console.log('[Connector] Auto-retry paused');\n }\n }\n\n /**\n * Resume automatic retry of failed uploads.\n */\n resumeAutoRetry(): void {\n this.autoRetryPaused = false;\n if (__DEV__) {\n console.log('[Connector] Auto-retry resumed');\n }\n }\n\n // ─── Private Retry Logic ───────────────────────────────────────────────────\n\n /**\n * Process a single CRUD entry with exponential backoff retry.\n *\n * This method uses a two-phase approach:\n * 1. First attempt - try the operation to classify the error type\n * 2. Retry phase - use the appropriate config (transient vs permanent) based on classification\n *\n * This fixes the issue where reassigning selectedConfig inside withExponentialBackoff's\n * callback had no effect because the config was already destructured at call time.\n *\n * @param entry - The CRUD entry to process\n * @throws Error if all retries exhausted (for critical failures)\n */\n private async processWithRetry(entry: CrudEntry): Promise<void> {\n if (this.isDestroyed) {\n throw new Error('Connector destroyed');\n }\n\n // Check if this entry is in cooldown from a previous failure\n const entryKey = `${entry.table}:${entry.id}`;\n const cooldownUntil = this.entryCooldowns.get(entryKey);\n if (cooldownUntil) {\n if (Date.now() >= cooldownUntil) {\n // Cooldown expired, clean up the entry and continue processing\n this.entryCooldowns.delete(entryKey);\n } else {\n const remainingMs = cooldownUntil - Date.now();\n if (__DEV__) {\n console.log('[Connector] Entry in cooldown, skipping:', {\n table: entry.table,\n id: entry.id,\n remainingSec: Math.round(remainingMs / 1000)\n });\n }\n // Throw a specific error so PowerSync keeps the entry but we don't spam retries\n throw new Error(`Entry in cooldown for ${Math.round(remainingMs / 1000)}s`);\n }\n }\n const classified = {\n isPermanent: false,\n pgCode: undefined as string | undefined,\n userMessage: ''\n };\n let lastError: Error | undefined;\n\n // Phase 1: First attempt - no retry, just to classify the error type\n try {\n await this.processCrudEntry(entry);\n this.entryCooldowns.delete(entryKey); // Clear any previous cooldown on success\n return; // Success on first try\n } catch (error) {\n lastError = error instanceof Error ? error : new Error(String(error));\n const classifiedError = classifySupabaseError(error);\n classified.isPermanent = classifiedError.isPermanent;\n classified.pgCode = classifiedError.pgCode;\n classified.userMessage = classifiedError.userMessage;\n\n // If first attempt failed with auth error, try refresh and one more attempt\n if (isAuthError(error)) {\n if (__DEV__) {\n console.log('[Connector] Auth error detected, refreshing session before retry:', {\n table: entry.table,\n op: entry.op,\n id: entry.id\n });\n }\n try {\n await this.supabase.auth.refreshSession();\n await this.processCrudEntry(entry);\n this.entryCooldowns.delete(entryKey); // Clear any previous cooldown on success\n return; // Success after token refresh\n } catch (retryError) {\n lastError = retryError instanceof Error ? retryError : new Error(String(retryError));\n const retriedClassification = classifySupabaseError(retryError);\n classified.isPermanent = retriedClassification.isPermanent;\n classified.pgCode = retriedClassification.pgCode;\n classified.userMessage = retriedClassification.userMessage;\n }\n }\n\n // Log the initial failure\n if (__DEV__) {\n console.log('[Connector] Initial attempt failed, will retry with appropriate config:', {\n table: entry.table,\n op: entry.op,\n id: entry.id,\n isPermanent: classified.isPermanent,\n pgCode: classified.pgCode,\n userMessage: classified.userMessage\n });\n }\n this.logger?.warn('[Connector] Initial attempt failed:', {\n table: entry.table,\n op: entry.op,\n id: entry.id,\n error: lastError.message,\n isPermanent: classified.isPermanent\n });\n\n // Run upload error middleware if configured\n if (this.uploadErrorMiddleware.length > 0) {\n const classifiedError = classifySupabaseError(error);\n const schema = this.schemaRouter(entry.table);\n const middlewareContext: UploadErrorContext = {\n entry,\n error: lastError,\n pgCode: classified.pgCode,\n httpStatusCode: extractHttpStatusCode(error),\n classified: classifiedError,\n originalError: error,\n schema,\n supabase: this.supabase\n };\n const middlewareResult = await runUploadErrorMiddleware(middlewareContext, this.uploadErrorMiddleware, 'continue' // Default to continue if no middleware matches\n );\n if (__DEV__) {\n console.log('[Connector] Upload error middleware result:', {\n table: entry.table,\n id: entry.id,\n result: middlewareResult\n });\n }\n switch (middlewareResult) {\n case 'success':\n // Treat as successful - clear cooldown and return\n this.entryCooldowns.delete(entryKey);\n this.logger?.info('[Connector] Entry treated as success by middleware:', {\n table: entry.table,\n id: entry.id,\n pgCode: classified.pgCode\n });\n return;\n case 'discard':\n // Discard entry - throw DiscardEntryError to signal removal from queue\n this.entryCooldowns.delete(entryKey);\n this.logger?.warn('[Connector] Entry discarded by middleware:', {\n table: entry.table,\n id: entry.id,\n pgCode: classified.pgCode,\n error: lastError.message\n });\n throw new DiscardEntryError(`Entry discarded by middleware: ${entry.table}:${entry.id} (${classified.pgCode || 'unknown'})`);\n case 'retry':\n // Continue with retry flow (fall through to Phase 2)\n break;\n case 'continue':\n // Use default classification (fall through to Phase 2)\n break;\n case 'fail_transaction':\n // Abort entire transaction immediately\n this.logger?.error('[Connector] Transaction aborted by middleware:', {\n table: entry.table,\n id: entry.id,\n pgCode: classified.pgCode,\n error: lastError.message\n });\n throw new TransactionAbortError(`Transaction aborted by middleware: ${entry.table}:${entry.id}`, lastError);\n }\n }\n }\n\n // Phase 2: Select config based on classified error type, then retry\n // RLS errors (42501) get extended retries since parent data may need to sync first\n const isRlsPermissionError = lastError && isRlsError(lastError);\n const selectedConfig = isRlsPermissionError ? this.retryConfig.rls : classified.isPermanent ? this.retryConfig.permanent : this.retryConfig.transient;\n if (__DEV__ && isRlsPermissionError) {\n console.log('[Connector] RLS/permission error detected, using extended retry config:', {\n table: entry.table,\n op: entry.op,\n id: entry.id,\n maxRetries: selectedConfig.maxRetries,\n baseDelayMs: selectedConfig.baseDelayMs,\n maxDelayMs: selectedConfig.maxDelayMs\n });\n }\n try {\n await withExponentialBackoff(async () => {\n await this.processCrudEntry(entry);\n }, selectedConfig, {\n onRetry: (attempt, delay, error) => {\n this.logger?.debug('[Connector] Retry attempt:', {\n table: entry.table,\n op: entry.op,\n id: entry.id,\n attempt,\n delay,\n error: error.message\n });\n if (__DEV__) {\n console.log('[Connector] Retry attempt:', {\n table: entry.table,\n op: entry.op,\n id: entry.id,\n attempt,\n maxRetries: selectedConfig.maxRetries,\n delayMs: delay,\n errorMessage: error.message\n });\n }\n }\n });\n // Retries succeeded - clear any cooldown\n this.entryCooldowns.delete(entryKey);\n } catch (error) {\n // All retries exhausted - let PowerSync's native retry mechanism handle it\n const finalError = error instanceof Error ? error : new Error(String(error));\n\n // Determine error category for logging\n const category: 'transient' | 'permanent' | 'unknown' = classified.isPermanent ? 'permanent' : classified.pgCode ? 'transient' : 'unknown';\n if (__DEV__) {\n console.log('[Connector] CRUD entry failed after retries, leaving in ps_crud:', {\n table: entry.table,\n op: entry.op,\n id: entry.id,\n category,\n error: finalError.message\n });\n }\n this.logger?.error('[Connector] CRUD entry failed after retries:', {\n table: entry.table,\n op: entry.op,\n id: entry.id,\n error: finalError.message,\n category\n });\n\n // Set cooldown to prevent rapid retries from PowerSync's sync cycle\n // This gives time for parent data to sync or conditions to change\n const cooldownMs = SupabaseConnector.COOLDOWN_DURATION_MS;\n // Evict oldest entry if at capacity (LRU eviction)\n if (this.entryCooldowns.size >= SupabaseConnector.MAX_ENTRY_COOLDOWNS) {\n const firstKey = this.entryCooldowns.keys().next().value;\n if (firstKey) this.entryCooldowns.delete(firstKey);\n }\n this.entryCooldowns.set(entryKey, Date.now() + cooldownMs);\n if (__DEV__) {\n console.log('[Connector] Entry placed in cooldown:', {\n table: entry.table,\n id: entry.id,\n cooldownSec: cooldownMs / 1000\n });\n }\n\n // ALWAYS throw after exhausting retries to prevent transaction.complete()\n // from removing the entry from PowerSync's queue. This ensures:\n // 1. Data stays in PowerSync's durable SQLite queue\n // 2. PowerSync's native retry mechanism will retry the transaction\n // 3. No reliance on AsyncStorage persistence (which is fire-and-forget)\n //\n // For transient errors: PowerSync will retry the whole transaction later\n // For permanent errors: User intervention needed, but data isn't lost\n throw finalError;\n }\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 const {\n data: {\n session\n },\n error\n } = await this.supabase.auth.getSession();\n if (error) {\n this.logger?.error('[Connector] Auth error:', error);\n throw new Error(`Failed to get Supabase session: ${error.message}`);\n }\n if (!session) {\n this.logger?.error('[Connector] No active session');\n throw new Error('No active Supabase session');\n }\n this.logger?.debug('[Connector] Credentials fetched, token expires at:', session.expires_at);\n return {\n endpoint: this.powerSyncUrl,\n token: session.access_token,\n expiresAt: session.expires_at ? new Date(session.expires_at * 1000) : 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 // P4.3: Check if connector has been destroyed\n if (this.isDestroyed) {\n throw new Error('Connector destroyed - aborting upload');\n }\n\n // Network reachability gate - check FIRST, before any network calls\n // This enables instant blocking when offline without waiting for timeouts\n // CRITICAL: Must THROW (not return) to keep entries in ps_crud for retry\n // A silent return would cause PowerSync to call complete() and discard the transaction\n if (this.shouldUploadFn && !this.shouldUploadFn()) {\n this.logger?.debug('[Connector] Upload blocked - not currently permitted, will retry');\n throw new Error('Upload not permitted - will retry on next sync cycle');\n }\n\n // CRITICAL: Ensure session is fresh before uploading\n // This prevents RLS failures (42501) from stale/expired tokens\n // Without this, auth.uid() in RLS policies may return NULL or fail\n const {\n data: {\n session\n },\n error: sessionError\n } = await this.supabase.auth.getSession();\n if (sessionError || !session) {\n const noSessionError = new Error('No active session - cannot upload data');\n if (__DEV__) {\n console.error('[Connector] uploadData failed: no session');\n }\n throw noSessionError;\n }\n if (__DEV__) {\n console.log('[Connector] uploadData called, fetching next CRUD transaction...');\n }\n const transaction = await database.getNextCrudTransaction();\n\n // P1.2: Validate transaction is not empty\n if (!transaction || transaction.crud.length === 0) {\n if (__DEV__) {\n console.log('[Connector] No pending CRUD transaction found or transaction is empty');\n }\n return;\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 {\n crud\n } = 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 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 existing resolution from a previous retry\n // This prevents re-detecting conflicts that the user already resolved\n // Key by table:recordId to handle same UUID in different tables\n const resolutionKey = `${entry.table}:${entry.id}`;\n const existingResolution = this.resolvedConflicts.get(resolutionKey);\n if (existingResolution) {\n if (__DEV__) {\n console.log('[Connector] Applying stored resolution for retry:', {\n table: entry.table,\n id: entry.id,\n key: resolutionKey,\n resolution: existingResolution\n });\n }\n // Remove from stored resolutions (single use)\n this.resolvedConflicts.delete(resolutionKey);\n switch (existingResolution.action) {\n case 'overwrite':\n // Proceed with upload (overwrite server)\n entriesToProcess.push(entry);\n break;\n case 'keep-server':\n // Discard local changes - mark for completion (removes from queue)\n entriesDiscarded.push(entry);\n break;\n case 'partial':\n // P5.3: Validate that partial resolution has at least one field\n if (!existingResolution.fields || existingResolution.fields.length === 0) {\n throw new Error('Partial resolution requires at least one field');\n }\n // Only sync specified fields\n const partialEntry: CrudEntry = {\n ...entry,\n opData: this.filterFields(entry.opData ?? {}, existingResolution.fields)\n };\n entriesToProcess.push(partialEntry);\n break;\n }\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(entry.table, entry.id, schema, this.supabase);\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(entry.table, entry.id, localVersion, serverVersion, entry.opData ?? {}, this.supabase, this.conflictDetection);\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 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 switch (resolution.action) {\n case 'overwrite':\n // Proceed with upload (overwrite server)\n entriesToProcess.push(entry);\n break;\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 case 'partial':\n // P5.3: Validate that partial resolution has at least one field\n if (!resolution.fields || resolution.fields.length === 0) {\n throw new Error('Partial resolution requires at least one field');\n }\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 this.logger?.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 // Per PowerSync docs, uploadData() must THROW on failure to keep entries in ps_crud.\n // A silent return would cause PowerSync to think upload succeeded and remove entries.\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 // MUST throw to prevent PowerSync from removing entries from ps_crud\n throw new Error('Entries queued for UI resolution - retry will occur on next sync cycle');\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?.(entriesDiscarded, error instanceof Error ? error : new Error(String(error)), classified);\n throw error;\n }\n return;\n }\n\n // Process remaining entries with retry logic\n const successfulEntries: CrudEntry[] = [];\n\n // CRITICAL: Process entries atomically - STOP on first failure\n // PowerSync transactions must be atomic: either ALL entries succeed or NONE are committed\n // If we continue after a failure, we violate atomicity and risk partial uploads\n for (const entry of entriesToProcess) {\n if (__DEV__) {\n console.log('[Connector] Processing CRUD entry with retry:', {\n table: entry.table,\n op: entry.op,\n id: entry.id,\n opData: entry.opData\n });\n }\n\n // No try-catch here - let errors propagate immediately to stop processing\n // This keeps the ENTIRE transaction in ps_crud for retry\n await this.processWithRetry(entry);\n successfulEntries.push(entry);\n }\n\n // Finalize the transaction using the shared helper\n await this.finalizeTransaction({\n transaction,\n successfulEntries,\n discardedEntries: entriesDiscarded,\n partialResolutions\n });\n }\n\n /**\n * Finalize a transaction by completing it after all entries processed successfully.\n * Extracted to eliminate duplication between uploadData and processTransaction.\n *\n * Implements circuit breaker logic for completion failures:\n * - On first failure: retry once with extended timeout (60s)\n * - After 3 failures for same entries: log and return without throwing\n * (data is safely in Supabase via idempotent upserts, preventing infinite retry loop)\n *\n * @param context - The finalization context containing results and transaction\n */\n private async finalizeTransaction(context: TransactionFinalizationContext): Promise<void> {\n const {\n transaction,\n successfulEntries,\n discardedEntries = [],\n partialResolutions = []\n } = context;\n if (__DEV__) {\n console.log('[Connector] All CRUD entries processed, completing transaction...');\n }\n\n // Generate fingerprint for tracking completion failures\n const allEntries = [...successfulEntries, ...discardedEntries];\n const fingerprint = this.generateTransactionFingerprint(allEntries);\n\n // Check existing failure count for circuit breaker\n const failureInfo = this.completionFailures.get(fingerprint);\n const currentFailureCount = failureInfo?.count ?? 0;\n\n // Circuit breaker: if we've failed COMPLETION_MAX_FAILURES times, don't throw\n // Data is already in Supabase (upserts are idempotent), so we log and return\n if (currentFailureCount >= SupabaseConnector.COMPLETION_MAX_FAILURES) {\n this.logger?.warn('[Connector] Circuit breaker triggered - completion failed too many times, bypassing throw:', {\n fingerprint: fingerprint.substring(0, 50) + '...',\n failureCount: currentFailureCount,\n entriesCount: allEntries.length,\n message: 'Data is safely in Supabase. Returning without throw to prevent infinite retry loop.'\n });\n if (__DEV__) {\n console.warn('[Connector] CIRCUIT BREAKER: Completion failed', currentFailureCount, 'times. Data is in Supabase - returning without throw to break retry loop.');\n }\n\n // Clear the failure tracking since we're breaking out\n this.completionFailures.delete(fingerprint);\n\n // Still notify callbacks that we \"succeeded\" (data is in Supabase)\n this.onTransactionSuccess?.(successfulEntries);\n this.onTransactionComplete?.(successfulEntries);\n return;\n }\n try {\n // P1.5: Add timeout on transaction.complete() to prevent indefinite hangs\n await withTimeout(transaction.complete(), 30000, 'Transaction complete timeout');\n if (__DEV__) {\n console.log('[Connector] Transaction completed successfully:', {\n entriesCount: successfulEntries.length,\n discardedCount: discardedEntries.length\n });\n }\n\n // Clear completion failure tracking on success\n this.completionFailures.delete(fingerprint);\n\n // P3.1: Clear resolved conflicts for successful entries to prevent unbounded growth\n for (const entry of successfulEntries) {\n this.resolvedConflicts.delete(`${entry.table}:${entry.id}`);\n }\n for (const entry of discardedEntries) {\n this.resolvedConflicts.delete(`${entry.table}:${entry.id}`);\n }\n\n // P3.1: Apply LRU eviction if map grows too large\n if (this.resolvedConflicts.size > SupabaseConnector.MAX_STORED_RESOLUTIONS) {\n const oldest = [...this.resolvedConflicts.keys()][0];\n if (oldest) {\n this.resolvedConflicts.delete(oldest);\n }\n }\n\n // After partial sync completes, re-emit conflicts for remaining fields\n if (this.conflictBus && partialResolutions.length > 0) {\n for (const {\n originalConflict,\n syncedFields\n } of partialResolutions) {\n const syncedFieldSet = new Set(syncedFields);\n\n // Find conflicts that weren't synced\n const remainingConflicts = originalConflict.conflicts.filter(c => !syncedFieldSet.has(c.field));\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 // P1.4: Notify success AFTER complete() to ensure data is durably persisted\n this.onTransactionSuccess?.(successfulEntries);\n\n // Notify completion (after transaction.complete() succeeds)\n this.onTransactionComplete?.(successfulEntries);\n } catch (error) {\n const classified = classifySupabaseError(error);\n const newFailureCount = currentFailureCount + 1;\n this.logger?.error('[Connector] Transaction completion FAILED:', {\n errorMessage: error instanceof Error ? error.message : String(error),\n classified,\n isPermanent: classified.isPermanent,\n failureCount: newFailureCount,\n maxFailures: SupabaseConnector.COMPLETION_MAX_FAILURES,\n entries: successfulEntries.map(e => ({\n table: e.table,\n op: e.op,\n id: e.id\n }))\n });\n\n // Additional debug info in development\n if (__DEV__) {\n console.error('[Connector] Transaction completion error details:', {\n errorKeys: error && typeof error === 'object' ? Object.keys(error) : [],\n errorObject: JSON.stringify(error, null, 2),\n failureCount: newFailureCount\n });\n }\n\n // On FIRST failure, retry once with extended timeout before incrementing counter\n if (currentFailureCount === 0) {\n if (__DEV__) {\n console.log('[Connector] First completion failure - retrying with extended timeout (60s)...');\n }\n try {\n await withTimeout(transaction.complete(), SupabaseConnector.COMPLETION_EXTENDED_TIMEOUT_MS, 'Transaction complete extended timeout');\n\n // Extended retry succeeded!\n if (__DEV__) {\n console.log('[Connector] Transaction completed on extended retry');\n }\n\n // Clear any tracking and notify success\n this.completionFailures.delete(fingerprint);\n this.onTransactionSuccess?.(successfulEntries);\n this.onTransactionComplete?.(successfulEntries);\n return;\n } catch (retryError) {\n // Extended retry also failed - update failure count and continue to throw\n if (__DEV__) {\n console.warn('[Connector] Extended retry also failed:', {\n error: retryError instanceof Error ? retryError.message : String(retryError)\n });\n }\n }\n }\n\n // Update failure tracking\n this.completionFailures.set(fingerprint, {\n count: newFailureCount,\n lastAttempt: new Date()\n });\n\n // Clean up old failure entries (LRU-style) to prevent unbounded growth\n if (this.completionFailures.size > 50) {\n const oldestKey = [...this.completionFailures.keys()][0];\n if (oldestKey) {\n this.completionFailures.delete(oldestKey);\n }\n }\n this.logger?.error('[Connector] Transaction completion error:', {\n error,\n classified,\n failureCount: newFailureCount,\n willRetry: newFailureCount < SupabaseConnector.COMPLETION_MAX_FAILURES,\n entries: successfulEntries.map(e => ({\n table: e.table,\n op: e.op,\n id: e.id\n }))\n });\n\n // Notify failure with classification\n this.onTransactionFailure?.(successfulEntries, error instanceof Error ? error : new Error(String(error)), classified);\n\n // Re-throw for PowerSync's native retry mechanism\n // (Circuit breaker at top of function will catch repeated failures)\n throw error;\n }\n }\n\n /**\n * Process a transaction without conflict detection.\n * Uses batched operations for PUT and DELETE, individual processing for PATCH.\n */\n private async processTransaction(transaction: {\n crud: CrudEntry[];\n complete: () => Promise<void>;\n }, _database: AbstractPowerSyncDatabase): Promise<void> {\n const successfulEntries: CrudEntry[] = [];\n const discardedEntries: CrudEntry[] = [];\n\n // Group entries by table for batched processing\n const entriesByTable = groupEntriesByTable(transaction.crud);\n if (__DEV__) {\n console.log('[Connector] Processing transaction with batching:', {\n totalEntries: transaction.crud.length,\n tables: Array.from(entriesByTable.keys()),\n entriesPerTable: Object.fromEntries(Array.from(entriesByTable.entries()).map(([table, entries]) => [table, {\n total: entries.length,\n put: entries.filter(e => e.op === UpdateType.PUT).length,\n patch: entries.filter(e => e.op === UpdateType.PATCH).length,\n delete: entries.filter(e => e.op === UpdateType.DELETE).length\n }]))\n });\n }\n\n // Process each table's entries\n for (const [table, entries] of entriesByTable) {\n const schema = this.schemaRouter(table);\n\n // Separate entries by operation type\n const putEntries = entries.filter(e => e.op === UpdateType.PUT);\n const patchEntries = entries.filter(e => e.op === UpdateType.PATCH);\n const deleteEntries = entries.filter(e => e.op === UpdateType.DELETE);\n\n // Check if custom handler exists - if so, process individually\n // CRITICAL: Only catch DiscardEntryError - other errors propagate to maintain atomicity\n if (this.crudHandler) {\n // Process all entries individually when custom handler is configured\n for (const entry of entries) {\n try {\n await this.processWithRetry(entry);\n successfulEntries.push(entry);\n } catch (error) {\n if (error instanceof DiscardEntryError) {\n // Entry was intentionally discarded by middleware - treat as \"completed\"\n discardedEntries.push(entry);\n continue;\n }\n // Other errors propagate to maintain transaction atomicity\n throw error;\n }\n }\n continue;\n }\n\n // Batch PUT operations (upserts)\n // CRITICAL: Errors propagate immediately to maintain transaction atomicity\n if (putEntries.length > 0) {\n const {\n successful,\n discarded\n } = await this.processBatchedPuts(table, schema, putEntries);\n successfulEntries.push(...successful);\n discardedEntries.push(...discarded);\n }\n\n // Batch DELETE operations\n // CRITICAL: Errors propagate immediately to maintain transaction atomicity\n if (deleteEntries.length > 0) {\n const {\n successful,\n discarded\n } = await this.processBatchedDeletes(table, schema, deleteEntries);\n successfulEntries.push(...successful);\n discardedEntries.push(...discarded);\n }\n\n // Batch PATCH operations with parallel processing (different fields per entry)\n // CRITICAL: Errors propagate immediately to maintain transaction atomicity\n if (patchEntries.length > 0) {\n const {\n successful,\n discarded\n } = await this.processBatchedPatches(table, schema, patchEntries);\n successfulEntries.push(...successful);\n discardedEntries.push(...discarded);\n }\n }\n\n // Finalize the transaction using the shared helper\n // Both successful and discarded entries are included for transaction completion\n // (discarded entries should be removed from queue, just like successful ones)\n await this.finalizeTransaction({\n transaction,\n successfulEntries,\n discardedEntries: discardedEntries.length > 0 ? discardedEntries : undefined\n });\n }\n\n /**\n * Process batched PUT (upsert) operations for a single table.\n * Falls back to individual processing if batch fails.\n * CRITICAL: Throws on first failure to maintain transaction atomicity.\n * @returns Object containing successful and discarded entries\n * @throws Error on first failure - keeps entire transaction in ps_crud\n */\n private async processBatchedPuts(table: string, schema: string, entries: CrudEntry[]): Promise<{\n successful: CrudEntry[];\n discarded: CrudEntry[];\n }> {\n const successful: CrudEntry[] = [];\n const discarded: CrudEntry[] = [];\n if (__DEV__) {\n console.log('[Connector] Processing batched PUTs:', {\n schema,\n table,\n count: entries.length\n });\n }\n\n // Build the rows for upsert\n const rows = entries.map(entry => ({\n id: entry.id,\n ...entry.opData\n }));\n\n // Get the correct Supabase query builder for this table's schema\n const query = schema === 'public' ? this.supabase.from(table) : (this.supabase.schema(schema) as unknown as ReturnType<typeof this.supabase.schema>).from(table);\n const {\n error\n } = await query.upsert(rows, {\n onConflict: 'id'\n });\n if (error) {\n if (__DEV__) {\n console.warn('[Connector] Batched PUT failed, falling back to individual processing:', {\n schema,\n table,\n error: error.message\n });\n }\n\n // Batch failed - fall back to individual processing\n // CRITICAL: Only catch DiscardEntryError - other errors propagate to maintain atomicity\n for (const entry of entries) {\n try {\n await this.processWithRetry(entry);\n successful.push(entry);\n } catch (e) {\n if (e instanceof DiscardEntryError) {\n // Entry was intentionally discarded by middleware - track separately\n discarded.push(entry);\n continue;\n }\n // Other errors propagate to maintain transaction atomicity\n throw e;\n }\n }\n } else {\n if (__DEV__) {\n console.log('[Connector] Batched PUT successful:', {\n schema,\n table,\n count: entries.length\n });\n }\n successful.push(...entries);\n }\n return {\n successful,\n discarded\n };\n }\n\n /**\n * Process batched DELETE operations for a single table.\n * Falls back to individual processing if batch fails.\n * CRITICAL: Throws on first failure to maintain transaction atomicity.\n * @returns Object containing successful and discarded entries\n * @throws Error on first failure - keeps entire transaction in ps_crud\n */\n private async processBatchedDeletes(table: string, schema: string, entries: CrudEntry[]): Promise<{\n successful: CrudEntry[];\n discarded: CrudEntry[];\n }> {\n const successful: CrudEntry[] = [];\n const discarded: CrudEntry[] = [];\n if (__DEV__) {\n console.log('[Connector] Processing batched DELETEs:', {\n schema,\n table,\n count: entries.length\n });\n }\n\n // Collect all IDs for batch delete\n const ids = entries.map(entry => entry.id);\n\n // Get the correct Supabase query builder for this table's schema\n const query = schema === 'public' ? this.supabase.from(table) : (this.supabase.schema(schema) as unknown as ReturnType<typeof this.supabase.schema>).from(table);\n const {\n error\n } = await query.delete().in('id', ids);\n if (error) {\n if (__DEV__) {\n console.warn('[Connector] Batched DELETE failed, falling back to individual processing:', {\n schema,\n table,\n error: error.message\n });\n }\n\n // Batch failed - fall back to individual processing\n // CRITICAL: Only catch DiscardEntryError - other errors propagate to maintain atomicity\n for (const entry of entries) {\n try {\n await this.processWithRetry(entry);\n successful.push(entry);\n } catch (e) {\n if (e instanceof DiscardEntryError) {\n // Entry was intentionally discarded by middleware - track separately\n discarded.push(entry);\n continue;\n }\n // Other errors propagate to maintain transaction atomicity\n throw e;\n }\n }\n } else {\n if (__DEV__) {\n console.log('[Connector] Batched DELETE successful:', {\n schema,\n table,\n count: entries.length\n });\n }\n successful.push(...entries);\n }\n return {\n successful,\n discarded\n };\n }\n\n /**\n * Process batched PATCH (update) operations for a single table.\n * Uses parallel processing with configurable concurrency since each PATCH may have different fields.\n * CRITICAL: Throws on first non-DiscardEntryError failure to maintain transaction atomicity.\n * @param batchSize Number of concurrent PATCH operations (default: 5)\n * @returns Object containing successful and discarded entries\n * @throws Error on first non-DiscardEntryError failure - keeps entire transaction in ps_crud\n */\n private async processBatchedPatches(table: string, schema: string, entries: CrudEntry[], batchSize: number = 5): Promise<{\n successful: CrudEntry[];\n discarded: CrudEntry[];\n }> {\n const successful: CrudEntry[] = [];\n const discarded: CrudEntry[] = [];\n if (__DEV__) {\n console.log('[Connector] Processing batched PATCHes:', {\n schema,\n table,\n count: entries.length,\n batchSize\n });\n }\n\n // Process in batches of batchSize concurrently\n // Uses Promise.all for fail-fast behavior on non-DiscardEntryError\n for (let i = 0; i < entries.length; i += batchSize) {\n const batch = entries.slice(i, i + batchSize);\n if (__DEV__) {\n console.log('[Connector] Processing PATCH batch:', {\n batchIndex: Math.floor(i / batchSize) + 1,\n totalBatches: Math.ceil(entries.length / batchSize),\n batchSize: batch.length\n });\n }\n\n // Marker type for tracking discarded vs successful entries\n type BatchResult = {\n status: 'success';\n entry: CrudEntry;\n } | {\n status: 'discarded';\n entry: CrudEntry;\n };\n\n // Use Promise.all for fail-fast behavior\n // Wrap each operation to catch DiscardEntryError (which is expected/acceptable)\n // while letting other errors propagate immediately\n const results = await Promise.all(batch.map(async (entry): Promise<BatchResult> => {\n try {\n await this.processWithRetry(entry);\n return {\n status: 'success',\n entry\n };\n } catch (error) {\n if (error instanceof DiscardEntryError) {\n // Entry was intentionally discarded by middleware - treat as \"completed\"\n return {\n status: 'discarded',\n entry\n };\n }\n // Non-DiscardEntryError - rethrow to fail-fast\n if (__DEV__) {\n console.error('[Connector] PATCH batch failed with non-discard error:', {\n table,\n entryId: entry.id,\n error: error instanceof Error ? error.message : String(error)\n });\n }\n throw error;\n }\n }));\n\n // Process results - all completed successfully (or were discarded)\n for (const result of results) {\n if (result.status === 'success') {\n successful.push(result.entry);\n } else {\n discarded.push(result.entry);\n }\n }\n }\n if (__DEV__) {\n console.log('[Connector] Batched PATCH complete:', {\n schema,\n table,\n successfulCount: successful.length,\n discardedCount: discarded.length\n });\n }\n return {\n successful,\n discarded\n };\n }\n\n /**\n * Check if a table has a _version column (cached).\n * P4.1: Uses Promise-based locking to prevent duplicate concurrent queries.\n */\n private async checkVersionColumn(table: string, db: AbstractPowerSyncDatabase): Promise<boolean> {\n // Return cached result if available\n if (this.versionColumnCache.has(table)) {\n return this.versionColumnCache.get(table)!;\n }\n\n // Check if there's already a pending query for this table\n if (!this.versionColumnPromises.has(table)) {\n // Create a new promise for this table's query\n this.versionColumnPromises.set(table, hasVersionColumn(table, db).then(result => {\n this.versionColumnCache.set(table, result);\n this.versionColumnPromises.delete(table);\n return result;\n }).catch(error => {\n // Clean up promise on error so retries are possible\n this.versionColumnPromises.delete(table);\n throw error;\n }));\n }\n\n // Wait for the (possibly shared) promise\n return this.versionColumnPromises.get(table)!;\n }\n\n /**\n * Filter opData to only include specified fields.\n * Used for partial sync resolution.\n */\n private filterFields(opData: Record<string, unknown>, fields: string[]): 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 * 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 // P5.1: Skip empty PATCH operations that would result in no-op updates\n if (entry.op === UpdateType.PATCH && Object.keys(entry.opData ?? {}).length === 0) {\n this.logger?.debug(`[Connector] Skipping empty PATCH for ${entry.table}:${entry.id}`);\n return; // Empty PATCH is a no-op - treating as success to clear from queue\n }\n\n // P5.2: Validate payload size to prevent Supabase failures with unhelpful errors\n if (entry.opData) {\n const payloadSize = JSON.stringify(entry.opData).length;\n if (payloadSize > MAX_PAYLOAD_SIZE) {\n throw new ValidationError(`Payload too large (${(payloadSize / 1024).toFixed(0)}KB > 900KB) for ${schema}.${table}`);\n }\n }\n\n // Try custom handler first\n if (this.crudHandler) {\n let handled = false;\n switch (entry.op) {\n case UpdateType.PUT:\n handled = (await this.crudHandler.handlePut?.(entry, this.supabase, schema)) ?? false;\n break;\n case UpdateType.PATCH:\n handled = (await this.crudHandler.handlePatch?.(entry, this.supabase, schema)) ?? false;\n break;\n case UpdateType.DELETE:\n handled = (await this.crudHandler.handleDelete?.(entry, this.supabase, schema)) ?? false;\n break;\n }\n if (handled) {\n this.logger?.debug(`[Connector] Custom handler processed ${entry.op} for ${schema}.${table}`);\n return;\n }\n }\n\n // Default behavior\n // Get the correct Supabase query builder for this table's schema\n const query = schema === 'public' ? this.supabase.from(table) : (this.supabase.schema(schema) as unknown as ReturnType<typeof this.supabase.schema>).from(table);\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: {\n id,\n ...entry.opData\n }\n });\n }\n // Insert/upsert using id column\n const {\n data: upsertData,\n error: upsertError\n } = await query.upsert({\n id,\n ...entry.opData\n }, {\n onConflict: 'id'\n }).select();\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(`Upsert failed for ${schema}.${table}: ${upsertError.message}`);\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 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 {\n data: updateData,\n error: updateError\n } = await query.update(entry.opData).eq('id', id).select();\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(`Update failed for ${schema}.${table}: ${updateError.message}`);\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 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 {\n data: deleteData,\n error: deleteError\n } = await query.delete().eq('id', id).select();\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(`Delete failed for ${schema}.${table}: ${deleteError.message}`);\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 this.logger?.debug(`[Connector] Processed ${entry.op} for ${schema}.${table} (id: ${id})`);\n }\n}"],"mappings":"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","names":["classifiedError"]}