@pol-studios/powersync 1.0.24 → 1.0.30

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 (53) hide show
  1. package/README.md +0 -1
  2. package/dist/attachments/index.d.ts +1 -1
  3. package/dist/{background-sync-ChCXW-EV.d.ts → background-sync-CVR3PkFi.d.ts} +1 -1
  4. package/dist/{chunk-BGBQYQV3.js → chunk-BC2SRII2.js} +180 -299
  5. package/dist/chunk-BC2SRII2.js.map +1 -0
  6. package/dist/{chunk-YSTEESEG.js → chunk-C2ACBYBZ.js} +208 -11
  7. package/dist/chunk-C2ACBYBZ.js.map +1 -0
  8. package/dist/{chunk-24RDMMCL.js → chunk-FNYQFILT.js} +1 -1
  9. package/dist/chunk-FNYQFILT.js.map +1 -0
  10. package/dist/{chunk-YVX3A36I.js → chunk-JCGOZVWL.js} +406 -331
  11. package/dist/chunk-JCGOZVWL.js.map +1 -0
  12. package/dist/{chunk-WGHNIAF7.js → chunk-QREWE3NR.js} +2 -2
  13. package/dist/{chunk-TIFL2KWE.js → chunk-RBPWEOIV.js} +3 -3
  14. package/dist/{chunk-55DKCJV4.js → chunk-RE5HWLCB.js} +124 -13
  15. package/dist/chunk-RE5HWLCB.js.map +1 -0
  16. package/dist/connector/index.d.ts +4 -4
  17. package/dist/connector/index.js +1 -2
  18. package/dist/core/index.d.ts +2 -2
  19. package/dist/generator/cli.js +6 -1
  20. package/dist/generator/index.d.ts +1 -0
  21. package/dist/generator/index.js +9 -1
  22. package/dist/generator/index.js.map +1 -1
  23. package/dist/index.d.ts +7 -7
  24. package/dist/index.js +27 -17
  25. package/dist/index.native.d.ts +5 -5
  26. package/dist/index.native.js +27 -17
  27. package/dist/index.web.d.ts +5 -5
  28. package/dist/index.web.js +27 -17
  29. package/dist/maintenance/index.d.ts +2 -2
  30. package/dist/maintenance/index.js +2 -2
  31. package/dist/platform/index.d.ts +1 -1
  32. package/dist/platform/index.native.d.ts +1 -1
  33. package/dist/platform/index.web.d.ts +1 -1
  34. package/dist/provider/index.d.ts +188 -28
  35. package/dist/provider/index.js +17 -7
  36. package/dist/react/index.d.ts +2 -2
  37. package/dist/react/index.js +3 -3
  38. package/dist/storage/index.d.ts +1 -1
  39. package/dist/storage/index.native.d.ts +1 -1
  40. package/dist/storage/index.web.d.ts +1 -1
  41. package/dist/{supabase-connector-D2oIl2t8.d.ts → supabase-connector-C4YpH_l3.d.ts} +23 -25
  42. package/dist/sync/index.d.ts +42 -5
  43. package/dist/sync/index.js +2 -2
  44. package/dist/{types-DiBvmGEi.d.ts → types-CpM2_LhU.d.ts} +17 -24
  45. package/dist/{types-CDqWh56B.d.ts → types-Dv1uf0LZ.d.ts} +16 -1
  46. package/package.json +2 -2
  47. package/dist/chunk-24RDMMCL.js.map +0 -1
  48. package/dist/chunk-55DKCJV4.js.map +0 -1
  49. package/dist/chunk-BGBQYQV3.js.map +0 -1
  50. package/dist/chunk-YSTEESEG.js.map +0 -1
  51. package/dist/chunk-YVX3A36I.js.map +0 -1
  52. /package/dist/{chunk-WGHNIAF7.js.map → chunk-QREWE3NR.js.map} +0 -0
  53. /package/dist/{chunk-TIFL2KWE.js.map → chunk-RBPWEOIV.js.map} +0 -0
@@ -0,0 +1 @@
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, UploadBlockReason } 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 // Exponential backoff configuration for retry timing\n private readonly _backoffBaseMs = 1000; // 1 second base\n private readonly _backoffMaxMs = 60000; // 60 seconds max\n private readonly _backoffMultiplier = 2; // Double each retry\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 * Get the reason why uploads are currently blocked.\n * Returns 'none' if uploads are ready to proceed.\n *\n * Priority order (first match wins):\n * 1. offline_mode - User explicitly chose offline mode\n * 2. auto_offline - System went offline automatically (network loss with auto-offline flag)\n * 3. pull_only_mode - Pull-only mode active\n * 4. network_unreachable - No internet connection\n * 5. disconnected - PowerSync not connected\n * 6. connecting - PowerSync is connecting\n * 7. uploading - Actively uploading\n * 8. none - Ready to sync\n */\n getUploadBlockReason(): UploadBlockReason {\n const status = this._state.status;\n const syncMode = this._state.syncMode;\n\n // Check sync mode first\n if (syncMode === 'offline') {\n // Distinguish between manual offline and auto-offline\n if (this._isAutoOffline) {\n return 'auto_offline';\n }\n return 'offline_mode';\n }\n if (syncMode === 'pull-only') {\n return 'pull_only_mode';\n }\n\n // Check network reachability\n if (!this._networkReachable) {\n return 'network_unreachable';\n }\n\n // Check PowerSync connection state\n if (!status.connected && !status.connecting) {\n return 'disconnected';\n }\n if (status.connecting) {\n return 'connecting';\n }\n\n // Check if currently uploading\n if (status.uploading) {\n return 'uploading';\n }\n\n // Ready to sync\n return 'none';\n }\n\n /**\n * Get a human-readable description of why uploads are blocked.\n * Returns null if uploads are not blocked.\n */\n getUploadBlockDescription(): string | null {\n const reason = this.getUploadBlockReason();\n switch (reason) {\n case 'offline_mode':\n return 'Offline mode is enabled';\n case 'auto_offline':\n return 'Went offline due to network loss';\n case 'pull_only_mode':\n return 'Pull-only mode is active';\n case 'network_unreachable':\n return 'No internet connection';\n case 'disconnected':\n return 'Not connected to sync service';\n case 'connecting':\n return 'Connecting to sync service';\n case 'uploading':\n return 'Upload in progress';\n case 'none':\n return null;\n default:\n return null;\n }\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 * Compute exponential backoff delay for a given retry count.\n * Uses base 1s, max 60s, multiplier 2.\n *\n * @param retryCount - Number of retries (1-based)\n * @returns Backoff delay in milliseconds\n */\n private _computeBackoffMs(retryCount: number): number {\n // retryCount is 1-based, so for first retry we use base delay\n const exponent = Math.max(0, retryCount - 1);\n const delay = this._backoffBaseMs * Math.pow(this._backoffMultiplier, exponent);\n return Math.min(delay, this._backoffMaxMs);\n }\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 const newRetryCount = existing.retryCount + 1;\n\n // Compute backoff (no backoff for permanent errors)\n const backoffMs = isPermanent ? undefined : this._computeBackoffMs(newRetryCount);\n const nextRetryAt = backoffMs ? new Date(now.getTime() + backoffMs) : undefined;\n this._failedTransactions[existingIndex] = {\n ...existing,\n error,\n retryCount: newRetryCount,\n lastFailedAt: now,\n isPermanent,\n backoffMs,\n nextRetryAt\n };\n } else {\n // Create new failure record\n const retryCount = preserveMetadata?.retryCount ?? 1;\n\n // Compute backoff (no backoff for permanent errors)\n const backoffMs = isPermanent ? undefined : this._computeBackoffMs(retryCount);\n const nextRetryAt = backoffMs ? new Date(now.getTime() + backoffMs) : undefined;\n const newFailure: FailedTransaction = {\n id: generateFailureId(normalizedEntries),\n entries: normalizedEntries,\n error,\n retryCount,\n firstFailedAt: preserveMetadata?.firstFailedAt ?? now,\n lastFailedAt: now,\n isPermanent,\n affectedEntityIds,\n affectedTables,\n backoffMs,\n nextRetryAt\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 failures for successfully synced entries.\n * Removes any failed transaction that contains entries with the given IDs.\n *\n * This is called when `onTransactionSuccess` fires - if any entry in a failed\n * transaction has now succeeded, we remove that entire failure record.\n *\n * @param entryIds - Array of CrudEntry.id values that succeeded\n */\n clearSuccessfulEntries(entryIds: string[]): void {\n if (entryIds.length === 0 || this._failedTransactions.length === 0) return;\n const entryIdSet = new Set(entryIds);\n const initialLength = this._failedTransactions.length;\n\n // Remove failures where ANY entry has succeeded\n // If one entry in a transaction succeeds, the whole transaction succeeded\n this._failedTransactions = this._failedTransactions.filter(failure => {\n const hasSuccessfulEntry = failure.entries.some(entry => entryIdSet.has(entry.id));\n return !hasSuccessfulEntry;\n });\n if (this._failedTransactions.length !== initialLength) {\n const clearedCount = initialLength - this._failedTransactions.length;\n this.logger.debug(`[StatusTracker] Cleared ${clearedCount} failures for successful entries`);\n this._schedulePersist();\n this._notifyFailureListeners();\n this._notifyListeners();\n }\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,8 +1,8 @@
1
- import { A as AuthProvider, h as UploadErrorMiddleware, g as UploadErrorContext, U as UploadErrorClassification } from '../types-DiBvmGEi.js';
2
- export { i as ConflictBus, C as ConnectorCircuitBreakerConfig, a as ConnectorConfig, c as CrudHandler, D as DEFAULT_RETRY_CONFIG, P as PowerSyncCredentials, e as RetryConfig, R as RetryStrategyConfig, b as SchemaRouter, f as Session, S as SupabaseConnectorOptions, d as defaultSchemaRouter } from '../types-DiBvmGEi.js';
3
- export { S as SupabaseConnector } from '../supabase-connector-D2oIl2t8.js';
1
+ import { A as AuthProvider, h as UploadErrorMiddleware, g as UploadErrorContext, U as UploadErrorClassification } from '../types-CpM2_LhU.js';
2
+ export { i as ConflictBus, C as ConnectorCircuitBreakerConfig, a as ConnectorConfig, c as CrudHandler, D as DEFAULT_RETRY_CONFIG, P as PowerSyncCredentials, e as RetryConfig, R as RetryStrategyConfig, b as SchemaRouter, f as Session, S as SupabaseConnectorOptions, d as defaultSchemaRouter } from '../types-CpM2_LhU.js';
3
+ export { S as SupabaseConnector } from '../supabase-connector-C4YpH_l3.js';
4
4
  import { SupabaseClient } from '@supabase/supabase-js';
5
- export { P as PowerSyncBackendConnector } from '../types-CDqWh56B.js';
5
+ export { P as PowerSyncBackendConnector } from '../types-Dv1uf0LZ.js';
6
6
  import '../platform/index.js';
7
7
 
8
8
  /**
@@ -20,8 +20,7 @@ import {
20
20
  runUploadErrorMiddlewareSync,
21
21
  successOnPgCodes,
22
22
  tableHandlers
23
- } from "../chunk-BGBQYQV3.js";
24
- import "../chunk-FV2HXEIY.js";
23
+ } from "../chunk-BC2SRII2.js";
25
24
  import "../chunk-I2AYMY5O.js";
26
25
  export {
27
26
  AuthRefreshError,
@@ -1,5 +1,5 @@
1
- import { g as SyncErrorType, i as ClassifiedError, f as SyncError, C as CrudEntry } from '../types-CDqWh56B.js';
2
- export { A as AbstractPowerSyncDatabase, k as CacheStats, j as CompactResult, h as CompletedTransaction, b as ConnectionHealth, o as CountRow, l as CrudTransaction, n as DbStatRow, D as DownloadProgress, E as EntitySyncState, F as FailedTransaction, r as FreelistCountRow, s as IntegrityCheckRow, I as IntegrityResult, q as PageCountRow, p as PageSizeRow, P as PowerSyncBackendConnector, m as SqliteTableRow, c as StorageInfo, d as StorageQuota, e as SyncMetrics, S as SyncMode, a as SyncStatus, T as TableCacheStats } from '../types-CDqWh56B.js';
1
+ import { g as SyncErrorType, i as ClassifiedError, f as SyncError, C as CrudEntry } from '../types-Dv1uf0LZ.js';
2
+ export { A as AbstractPowerSyncDatabase, k as CacheStats, j as CompactResult, h as CompletedTransaction, b as ConnectionHealth, o as CountRow, l as CrudTransaction, n as DbStatRow, D as DownloadProgress, E as EntitySyncState, F as FailedTransaction, r as FreelistCountRow, s as IntegrityCheckRow, I as IntegrityResult, q as PageCountRow, p as PageSizeRow, P as PowerSyncBackendConnector, m as SqliteTableRow, c as StorageInfo, d as StorageQuota, e as SyncMetrics, S as SyncMode, a as SyncStatus, T as TableCacheStats, U as UploadBlockReason } from '../types-Dv1uf0LZ.js';
3
3
 
4
4
  /**
5
5
  * Constants for @pol-studios/powersync
@@ -439,7 +439,10 @@ function getUploadOrder(graph) {
439
439
  // src/generator/generator.ts
440
440
  function mapTypeToPowerSync(tsType, columnName, decimalPatterns) {
441
441
  const cleanType = tsType.trim().replace(/\s*\|\s*null/g, "");
442
- if (cleanType.includes("Json") || cleanType.includes("unknown") || cleanType.includes("{")) {
442
+ if (cleanType.includes("Json")) {
443
+ return { type: "column.text", isJson: true };
444
+ }
445
+ if (cleanType.includes("unknown") || cleanType.includes("{")) {
443
446
  return null;
444
447
  }
445
448
  if (cleanType.includes("[]")) {
@@ -474,6 +477,8 @@ function generateColumnDefs(table, decimalPatterns) {
474
477
  comment = " // boolean stored as 0/1";
475
478
  } else if (mapping.isEnum) {
476
479
  comment = " // enum stored as text";
480
+ } else if (mapping.isJson) {
481
+ comment = " // JSON stored as text";
477
482
  }
478
483
  columnDefs.push(` ${columnName}: ${mapping.type},${comment}`);
479
484
  }
@@ -202,6 +202,7 @@ interface ColumnMapping {
202
202
  type: 'column.text' | 'column.integer' | 'column.real';
203
203
  isEnum?: boolean;
204
204
  isBoolean?: boolean;
205
+ isJson?: boolean;
205
206
  }
206
207
  interface GenerateResult {
207
208
  success: boolean;
@@ -482,7 +482,13 @@ function formatDependencyGraph(graph) {
482
482
  // src/generator/generator.ts
483
483
  function mapTypeToPowerSync(tsType, columnName, decimalPatterns) {
484
484
  const cleanType = tsType.trim().replace(/\s*\|\s*null/g, "");
485
- if (cleanType.includes("Json") || cleanType.includes("unknown") || cleanType.includes("{")) {
485
+ if (cleanType.includes("Json")) {
486
+ return {
487
+ type: "column.text",
488
+ isJson: true
489
+ };
490
+ }
491
+ if (cleanType.includes("unknown") || cleanType.includes("{")) {
486
492
  return null;
487
493
  }
488
494
  if (cleanType.includes("[]")) {
@@ -529,6 +535,8 @@ function generateColumnDefs(table, decimalPatterns) {
529
535
  comment = " // boolean stored as 0/1";
530
536
  } else if (mapping.isEnum) {
531
537
  comment = " // enum stored as text";
538
+ } else if (mapping.isJson) {
539
+ comment = " // JSON stored as text";
532
540
  }
533
541
  columnDefs.push(` ${columnName}: ${mapping.type},${comment}`);
534
542
  }
@@ -1 +1 @@
1
- {"version":3,"sources":["../../src/generator/config.ts","../../src/generator/generator.ts","../../src/generator/parser.ts","../../src/generator/templates.ts","../../src/generator/fk-dependencies.ts"],"sourcesContent":["/**\n * Configuration types and helpers for PowerSync schema generator\n */\n\nexport interface TableConfig {\n /** Table name (PascalCase as it appears in database.types.ts) */\n name: string;\n /** Schema name (defaults to 'public') */\n schema?: string;\n /** Enable ps_crud timestamp tracking for optimistic UI updates */\n trackMetadata?: boolean;\n /**\n * Sync the primary key column (normally skipped as PowerSync handles it internally).\n *\n * Use this for tables with integer PKs that are referenced by FKs in other tables.\n * Example: `Group.id` is referenced by `UserGroup.groupId`, so Group needs `syncPrimaryKey: true`\n * to ensure the integer ID is available for client-side joins.\n */\n syncPrimaryKey?: boolean;\n /** @deprecated Use `syncPrimaryKey` instead */\n includeId?: boolean;\n /** Columns to skip for this specific table (in addition to global skipColumns) */\n skipColumns?: string[];\n /** Only include these columns (overrides skipColumns if specified) */\n onlyColumns?: string[];\n /** Alias for the table in PowerSync schema (for conflicting names across schemas) */\n alias?: string;\n /** Table is local-only (not synced through PowerSync) */\n localOnly?: boolean;\n}\nexport interface GeneratorConfig {\n /** Path to Supabase-generated database.types.ts file */\n typesPath: string;\n /** Output path for generated PowerSync schema */\n outputPath: string;\n /** Tables to include in the PowerSync schema */\n tables: TableConfig[];\n /** Columns to always skip (in addition to defaults like 'id') */\n skipColumns?: string[];\n /** Column name patterns that should use column.real for decimal values */\n decimalPatterns?: string[];\n /** Additional schemas to track (besides 'public' which is the default) */\n schemas?: string[];\n /** Generate indexes for FK and common columns (default: true) */\n autoIndexes?: boolean;\n /** Additional columns to index (exact column names) */\n indexColumns?: string[];\n /** Column patterns that should be indexed (regex patterns) */\n indexPatterns?: string[];\n}\n\n/**\n * Define a PowerSync generator configuration with type safety\n */\nexport function defineConfig(config: GeneratorConfig): GeneratorConfig {\n return config;\n}\n\n/**\n * Default columns that are skipped during generation\n */\nexport const DEFAULT_SKIP_COLUMNS = ['id',\n// PowerSync handles id automatically\n// Legacy numeric ID columns - typically not needed after UUID migration\n'legacyId'];\n\n/**\n * Default column name patterns that indicate decimal values\n */\nexport const DEFAULT_DECIMAL_PATTERNS = ['hours', 'watts', 'voltage', 'rate', 'amount', 'price', 'cost', 'total'];\n\n/**\n * Default column patterns that should be indexed\n * These are regex patterns matched against column names\n */\nexport const DEFAULT_INDEX_PATTERNS = ['Id$',\n// FK columns ending in Id (e.g., projectId, userId)\n'^createdAt$', '^updatedAt$', '^status$', '^type$'];\n\n/**\n * Required authentication/access control tables for offline auth\n * These tables are needed for proper offline access control\n */\nexport interface RequiredAuthTable {\n /** Table name (PascalCase) */\n name: string;\n /** Schema name */\n schema: string;\n /** Whether this table requires syncPrimaryKey to be true */\n requiresSyncPrimaryKey?: boolean;\n}\nexport const REQUIRED_AUTH_TABLES: RequiredAuthTable[] = [{\n name: 'UserAccess',\n schema: 'core'\n}, {\n name: 'UserGroup',\n schema: 'core'\n}, {\n name: 'GroupAccessKey',\n schema: 'core',\n requiresSyncPrimaryKey: true\n}, {\n name: 'Group',\n schema: 'core',\n requiresSyncPrimaryKey: true\n}];","/**\n * PowerSync schema generator\n *\n * Converts Supabase database.types.ts into PowerSync schema definitions\n */\n\nimport * as fs from 'fs';\nimport * as path from 'path';\nimport type { GeneratorConfig, TableConfig } from './config.js';\nimport { DEFAULT_SKIP_COLUMNS, DEFAULT_DECIMAL_PATTERNS, DEFAULT_INDEX_PATTERNS, REQUIRED_AUTH_TABLES } from './config.js';\nimport { parseTypesFile, type ParsedTable } from './parser.js';\nimport { generateTableDefinition, generateLocalOnlyTableDefinition, generateOutputFile, generateIndexDefinitions, type IndexDefinition } from './templates.js';\nimport { detectFKDependencies, type DependencyGraph } from './fk-dependencies.js';\nexport interface ColumnMapping {\n type: 'column.text' | 'column.integer' | 'column.real';\n isEnum?: boolean;\n isBoolean?: boolean;\n}\nexport interface GenerateResult {\n success: boolean;\n tablesGenerated: number;\n outputPath: string;\n errors: string[];\n warnings: string[];\n /** Generated output (included when dryRun is true) */\n output?: string;\n /** Number of indexes generated */\n indexesGenerated?: number;\n /** FK dependency graph (when detected) */\n dependencyGraph?: DependencyGraph;\n}\n\n/**\n * Map TypeScript types to PowerSync column types\n */\nexport function mapTypeToPowerSync(tsType: string, columnName: string, decimalPatterns: string[]): ColumnMapping | null {\n // Clean up the type (remove nullability)\n const cleanType = tsType.trim().replace(/\\s*\\|\\s*null/g, '');\n\n // Skip complex types that can't be stored in SQLite\n if (cleanType.includes('Json') || cleanType.includes('unknown') || cleanType.includes('{')) {\n return null;\n }\n\n // Array types - skip\n if (cleanType.includes('[]')) {\n return null;\n }\n\n // Boolean -> integer (0/1)\n if (cleanType === 'boolean') {\n return {\n type: 'column.integer',\n isBoolean: true\n };\n }\n\n // Number types\n if (cleanType === 'number') {\n // Use real for columns that might have decimals\n if (decimalPatterns.some(pattern => columnName.toLowerCase().includes(pattern.toLowerCase()))) {\n return {\n type: 'column.real'\n };\n }\n return {\n type: 'column.integer'\n };\n }\n\n // String types\n if (cleanType === 'string') {\n return {\n type: 'column.text'\n };\n }\n\n // Enum types (Database[\"schema\"][\"Enums\"][\"EnumName\"]) -> store as text\n if (cleanType.includes('Database[') && cleanType.includes('Enums')) {\n return {\n type: 'column.text',\n isEnum: true\n };\n }\n\n // Default to text for unknown types (likely enums or other string-like types)\n return {\n type: 'column.text'\n };\n}\n\n/**\n * Generate column definitions for a table\n */\nexport function generateColumnDefs(table: ParsedTable, decimalPatterns: string[]): string[] {\n const columnDefs: string[] = [];\n for (const [columnName, tsType] of table.columns) {\n const mapping = mapTypeToPowerSync(tsType, columnName, decimalPatterns);\n if (mapping) {\n // Add comment for boolean and enum columns\n let comment = '';\n if (mapping.isBoolean) {\n comment = ' // boolean stored as 0/1';\n } else if (mapping.isEnum) {\n comment = ' // enum stored as text';\n }\n columnDefs.push(` ${columnName}: ${mapping.type},${comment}`);\n }\n }\n return columnDefs;\n}\n\n/**\n * Validate that required auth tables are included in config\n * Returns warnings for missing tables or misconfigured tables\n */\nexport function validateAuthTables(tables: TableConfig[]): string[] {\n const warnings: string[] = [];\n for (const authTable of REQUIRED_AUTH_TABLES) {\n const configTable = tables.find(t => t.name === authTable.name && (t.schema ?? 'public') === authTable.schema);\n if (!configTable) {\n warnings.push(`Missing required auth table: ${authTable.schema}.${authTable.name} - offline access control may not work correctly`);\n } else if (authTable.requiresSyncPrimaryKey && !configTable.syncPrimaryKey) {\n warnings.push(`Auth table ${authTable.schema}.${authTable.name} should have syncPrimaryKey: true for proper FK references`);\n }\n }\n return warnings;\n}\n\n/**\n * Validate FK references to ensure all referenced tables are in the config\n * Returns warnings for missing FK targets\n */\nexport function validateFKReferences(dependencyGraph: DependencyGraph, configuredTables: Set<string>): string[] {\n const warnings: string[] = [];\n for (const fk of dependencyGraph.fkRelationships) {\n if (!configuredTables.has(fk.referencedTable)) {\n warnings.push(`FK reference ${fk.table}.${fk.column} references table '${fk.referencedTable}' which is not in the config`);\n }\n }\n return warnings;\n}\n\n/**\n * Generate PowerSync schema from configuration\n */\nexport async function generateSchema(config: GeneratorConfig, options?: {\n cwd?: string;\n verbose?: boolean;\n dryRun?: boolean;\n}): Promise<GenerateResult> {\n const cwd = options?.cwd ?? process.cwd();\n const verbose = options?.verbose ?? false;\n const dryRun = options?.dryRun ?? false;\n const result: GenerateResult = {\n success: false,\n tablesGenerated: 0,\n outputPath: '',\n errors: [],\n warnings: []\n };\n\n // Resolve paths relative to cwd\n const typesPath = path.isAbsolute(config.typesPath) ? config.typesPath : path.resolve(cwd, config.typesPath);\n const outputPath = path.isAbsolute(config.outputPath) ? config.outputPath : path.resolve(cwd, config.outputPath);\n result.outputPath = outputPath;\n\n // Validate required auth tables\n const authWarnings = validateAuthTables(config.tables);\n result.warnings.push(...authWarnings);\n\n // Check if types file exists\n if (!fs.existsSync(typesPath)) {\n result.errors.push(`Types file not found: ${typesPath}`);\n return result;\n }\n\n // Read types file\n if (verbose) {\n console.log(`Reading types from: ${typesPath}`);\n }\n const typesContent = fs.readFileSync(typesPath, 'utf-8');\n\n // Build skip columns set\n const skipColumns = new Set([...DEFAULT_SKIP_COLUMNS, ...(config.skipColumns ?? [])]);\n\n // Build decimal patterns\n const decimalPatterns = [...DEFAULT_DECIMAL_PATTERNS, ...(config.decimalPatterns ?? [])];\n\n // Parse tables from types file\n const parsedTables = parseTypesFile(typesContent, config.tables, skipColumns);\n\n // Check for tables that weren't found\n for (const tableConfig of config.tables) {\n const found = parsedTables.some(t => t.name === tableConfig.name);\n if (!found) {\n result.warnings.push(`Table '${tableConfig.name}' not found in schema '${tableConfig.schema ?? 'public'}'`);\n }\n }\n if (parsedTables.length === 0) {\n result.errors.push('No tables were parsed successfully');\n return result;\n }\n\n // Build index patterns (auto-indexes enabled by default)\n const autoIndexes = config.autoIndexes ?? true;\n const indexPatterns = autoIndexes ? [...DEFAULT_INDEX_PATTERNS, ...(config.indexPatterns ?? [])] : config.indexPatterns ?? [];\n const indexColumns = config.indexColumns ?? [];\n\n // Detect FK dependencies\n const dependencyGraph = detectFKDependencies(parsedTables);\n result.dependencyGraph = dependencyGraph;\n\n // Validate FK references - warn if any FK targets a table not in config\n const configuredTableNames = new Set(config.tables.map(t => t.name));\n const fkWarnings = validateFKReferences(dependencyGraph, configuredTableNames);\n result.warnings.push(...fkWarnings);\n if (verbose && dependencyGraph.fkRelationships.length > 0) {\n console.log(`\\nFK Dependencies detected: ${dependencyGraph.fkRelationships.length}`);\n for (const fk of dependencyGraph.fkRelationships) {\n console.log(` ${fk.table}.${fk.column} -> ${fk.referencedTable}`);\n }\n console.log(`\\nRecommended upload order:`);\n dependencyGraph.uploadOrder.forEach((table, i) => {\n console.log(` ${i + 1}. ${table}`);\n });\n console.log('');\n }\n\n // Generate table definitions - separate synced and local-only tables\n const syncedTableDefs: string[] = [];\n const localOnlyTableDefs: string[] = [];\n let totalIndexes = 0;\n for (const table of parsedTables) {\n const isLocalOnly = table.config.localOnly ?? false;\n const tableAlias = table.config.alias;\n if (verbose) {\n const syncPK = table.config.syncPrimaryKey || table.config.includeId;\n const flags = [table.config.trackMetadata ? '[trackMetadata]' : '', syncPK ? '[syncPrimaryKey]' : '', isLocalOnly ? '[localOnly]' : '', tableAlias ? `[alias: ${tableAlias}]` : ''].filter(Boolean).join(' ');\n console.log(`Processing ${table.schema}.${table.name} (${table.columns.size} columns)${flags ? ' ' + flags : ''}`);\n }\n const columnDefs = generateColumnDefs(table, decimalPatterns);\n if (columnDefs.length === 0) {\n result.warnings.push(`Table '${table.name}' has no syncable columns`);\n continue;\n }\n\n // Generate indexes for this table (use alias for index naming if provided)\n const effectiveTableName = tableAlias ?? table.name;\n const columnNames = [...table.columns.keys()];\n const indexes = indexPatterns.length > 0 || indexColumns.length > 0 ? generateIndexDefinitions(effectiveTableName, columnNames, indexPatterns, indexColumns) : [];\n totalIndexes += indexes.length;\n if (verbose && indexes.length > 0) {\n console.log(` Indexes: ${indexes.map(i => i.name).join(', ')}`);\n }\n\n // Generate table definition based on localOnly flag\n if (isLocalOnly) {\n localOnlyTableDefs.push(generateLocalOnlyTableDefinition(table, columnDefs, indexes));\n } else {\n syncedTableDefs.push(generateTableDefinition(table, columnDefs, indexes));\n }\n }\n\n // Combine synced and local-only table definitions\n const tableDefs = [...syncedTableDefs, ...localOnlyTableDefs];\n result.indexesGenerated = totalIndexes;\n\n // Collect unique schemas\n const schemas = [...new Set(config.tables.map(t => t.schema ?? 'public'))];\n\n // Generate output file content\n const relativePath = path.relative(cwd, typesPath);\n // Filter tables that were successfully generated (check for both name and alias)\n const generatedTables = parsedTables.filter(t => {\n const effectiveName = t.config.alias ?? t.name;\n return tableDefs.some(def => def.includes(`const ${effectiveName} =`));\n });\n const output = generateOutputFile(generatedTables, tableDefs, schemas, relativePath);\n\n // If dry-run, return output without writing\n if (dryRun) {\n result.success = true;\n result.tablesGenerated = tableDefs.length;\n result.output = output;\n return result;\n }\n\n // Ensure output directory exists\n const outputDir = path.dirname(outputPath);\n if (!fs.existsSync(outputDir)) {\n fs.mkdirSync(outputDir, {\n recursive: true\n });\n }\n\n // Write output file\n fs.writeFileSync(outputPath, output);\n result.success = true;\n result.tablesGenerated = tableDefs.length;\n return result;\n}","/**\n * Parser for Supabase database.types.ts files\n *\n * Extracts table definitions and column types from the generated TypeScript types\n */\n\nimport type { TableConfig } from './config.js';\nexport interface ColumnInfo {\n name: string;\n tsType: string;\n isNullable: boolean;\n}\nexport interface ParsedTable {\n name: string;\n schema: string;\n columns: Map<string, string>;\n config: TableConfig;\n}\nexport interface ParseOptions {\n /** Columns to skip */\n skipColumns: Set<string>;\n /**\n * Include the id column (normally skipped).\n * Use for tables with integer PKs referenced by FKs in other tables.\n */\n syncPrimaryKey?: boolean;\n /** @deprecated Use `syncPrimaryKey` instead */\n includeId?: boolean;\n /** Only include these columns (overrides skipColumns if specified) */\n onlyColumns?: string[];\n}\n\n/**\n * Parse the Row type from a table definition and extract columns\n */\nexport function parseRowType(tableContent: string, options: ParseOptions): Map<string, string> {\n const columns = new Map<string, string>();\n\n // Find the Row block - handles nested braces in type definitions\n const rowMatch = tableContent.match(/Row:\\s*\\{([^}]+(?:\\{[^}]*\\}[^}]*)*)\\}/s);\n if (!rowMatch) return columns;\n const rowContent = rowMatch[1];\n\n // syncPrimaryKey takes precedence, with includeId as fallback for backwards compat\n const includePrimaryKey = options.syncPrimaryKey ?? options.includeId ?? false;\n\n // If onlyColumns is specified, use it as a whitelist (overrides skipColumns)\n const onlyColumnsSet = options.onlyColumns ? new Set(options.onlyColumns) : null;\n\n // Parse each column: \"columnName: type\" or \"columnName?: type\"\n const columnRegex = /(\\w+)\\??:\\s*([^,\\n]+)/g;\n let match;\n while ((match = columnRegex.exec(rowContent)) !== null) {\n const [, columnName, columnType] = match;\n\n // Determine if column should be included\n let shouldInclude: boolean;\n if (onlyColumnsSet) {\n // onlyColumns mode: only include explicitly listed columns\n // Exception: include 'id' if syncPrimaryKey is true\n shouldInclude = onlyColumnsSet.has(columnName) || includePrimaryKey && columnName === 'id';\n } else {\n // skipColumns mode: include unless explicitly skipped\n // Exception: include 'id' if syncPrimaryKey is true\n const isSkipped = options.skipColumns.has(columnName);\n shouldInclude = !isSkipped || includePrimaryKey && columnName === 'id';\n }\n if (shouldInclude) {\n columns.set(columnName, columnType.trim());\n }\n }\n return columns;\n}\n\n/**\n * Escape special regex characters in a string\n */\nfunction escapeRegex(str: string): string {\n return str.replace(/[.*+?^${}()|[\\]\\\\]/g, '\\\\$&');\n}\n\n/**\n * Extract a table definition from the database.types.ts content\n *\n * Uses bracket-counting to properly handle nested arrays in Relationships\n * (e.g., columns: [\"parentId\"] inside relationship objects)\n */\nexport function extractTableDef(content: string, tableName: string, schema: string): string | null {\n // Escape special characters in schema and table names for regex safety\n const escapedSchema = escapeRegex(schema);\n const escapedTableName = escapeRegex(tableName);\n\n // Find the schema section\n const schemaRegex = new RegExp(`${escapedSchema}:\\\\s*\\\\{[\\\\s\\\\S]*?Tables:\\\\s*\\\\{`, 'g');\n const schemaMatch = schemaRegex.exec(content);\n if (!schemaMatch) return null;\n const startIndex = schemaMatch.index;\n const searchContent = content.slice(startIndex);\n\n // Find the start of this specific table\n // Use negative lookbehind (?<![A-Za-z]) to avoid matching table names that are\n // substrings of other names (e.g., \"Tag\" in \"CommentTag\")\n const tableStartRegex = new RegExp(`(?<![A-Za-z])${escapedTableName}:\\\\s*\\\\{`, 'g');\n const tableStartMatch = tableStartRegex.exec(searchContent);\n if (!tableStartMatch) return null;\n\n // Use bracket counting to find the matching closing brace\n const tableStartIndex = tableStartMatch.index;\n const openBraceIndex = tableStartMatch.index + tableStartMatch[0].length - 1;\n let braceCount = 1;\n let i = openBraceIndex + 1;\n while (i < searchContent.length && braceCount > 0) {\n const char = searchContent[i];\n if (char === '{') braceCount++;else if (char === '}') braceCount--;\n i++;\n }\n if (braceCount !== 0) return null;\n return searchContent.slice(tableStartIndex, i);\n}\n\n/**\n * Parse a database.types.ts file and extract specified tables\n */\nexport function parseTypesFile(content: string, tables: TableConfig[], globalSkipColumns: Set<string>): ParsedTable[] {\n const parsedTables: ParsedTable[] = [];\n for (const tableConfig of tables) {\n const {\n name,\n schema = 'public',\n syncPrimaryKey,\n includeId,\n skipColumns: tableSkipColumns,\n onlyColumns\n } = tableConfig;\n const tableDef = extractTableDef(content, name, schema);\n if (!tableDef) {\n continue;\n }\n\n // Merge global and per-table skipColumns\n const mergedSkipColumns = new Set([...globalSkipColumns, ...(tableSkipColumns ?? [])]);\n const columns = parseRowType(tableDef, {\n skipColumns: mergedSkipColumns,\n syncPrimaryKey,\n includeId,\n onlyColumns\n });\n if (columns.size > 0) {\n parsedTables.push({\n name,\n schema,\n columns,\n config: tableConfig\n });\n }\n }\n return parsedTables;\n}\n\n/**\n * Get all available schemas from the types file\n */\nexport function getAvailableSchemas(content: string): string[] {\n const schemas: string[] = [];\n const schemaRegex = /(\\w+):\\s*\\{[\\s\\S]*?Tables:\\s*\\{/g;\n let match;\n while ((match = schemaRegex.exec(content)) !== null) {\n schemas.push(match[1]);\n }\n return schemas;\n}\n\n/**\n * Get all table names in a schema\n */\nexport function getTablesInSchema(content: string, schema: string): string[] {\n const tables: string[] = [];\n\n // Find the schema section\n const schemaRegex = new RegExp(`${schema}:\\\\s*\\\\{[\\\\s\\\\S]*?Tables:\\\\s*\\\\{([\\\\s\\\\S]*?)\\\\}\\\\s*Views:`, 'g');\n const schemaMatch = schemaRegex.exec(content);\n if (!schemaMatch) return tables;\n const tablesContent = schemaMatch[1];\n\n // Find table names (they're at the start of each table definition)\n const tableNameRegex = /^\\s*(\\w+):\\s*\\{/gm;\n let match;\n while ((match = tableNameRegex.exec(tablesContent)) !== null) {\n tables.push(match[1]);\n }\n return tables;\n}","/**\n * Output templates for PowerSync schema generation\n */\n\nimport type { ParsedTable } from './parser.js';\nexport interface IndexDefinition {\n name: string;\n columns: string[];\n}\n\n/**\n * Convert table name to snake_case for index naming\n */\nfunction toSnakeCase(str: string): string {\n return str.replace(/([a-z])([A-Z])/g, '$1_$2').toLowerCase();\n}\n\n/**\n * Generate index definitions for a table based on column patterns\n *\n * @param tableName - The table name (PascalCase)\n * @param columns - Array of column names in the table\n * @param indexPatterns - Regex patterns to match against column names\n * @param additionalColumns - Specific column names to always index\n * @returns Array of index definitions\n */\nexport function generateIndexDefinitions(tableName: string, columns: string[], indexPatterns: string[], additionalColumns: string[] = []): IndexDefinition[] {\n const indexes: IndexDefinition[] = [];\n const snakeTableName = toSnakeCase(tableName);\n\n // Combine pattern matching and explicit columns\n const columnsToIndex = new Set<string>();\n\n // Pre-compile regex patterns with error handling\n const compiledPatterns: RegExp[] = [];\n for (const pattern of indexPatterns) {\n try {\n compiledPatterns.push(new RegExp(pattern));\n } catch {\n console.warn(`Warning: Invalid index pattern regex \"${pattern}\" - skipping`);\n }\n }\n\n // Match columns against patterns\n for (const column of columns) {\n // Skip 'id' column - it's the primary key and already indexed\n if (column === 'id') continue;\n for (const regex of compiledPatterns) {\n if (regex.test(column)) {\n columnsToIndex.add(column);\n break;\n }\n }\n }\n\n // Add explicitly specified columns (if they exist in the table)\n for (const column of additionalColumns) {\n if (columns.includes(column) && column !== 'id') {\n columnsToIndex.add(column);\n }\n }\n\n // Create index definitions\n for (const column of columnsToIndex) {\n const snakeColumn = toSnakeCase(column);\n indexes.push({\n name: `idx_${snakeTableName}_${snakeColumn}`,\n columns: [column]\n });\n }\n\n // Sort by index name for deterministic output\n indexes.sort((a, b) => a.name.localeCompare(b.name));\n return indexes;\n}\n\n/**\n * File header template\n */\nexport function generateHeader(typesPath: string): string {\n return `/**\n * PowerSync Schema Definition\n *\n * AUTO-GENERATED from ${typesPath}\n * Run: npx @pol-studios/powersync generate-schema\n *\n * DO NOT EDIT MANUALLY - changes will be overwritten\n */\n\nimport { column, Schema, Table } from \"@powersync/react-native\";\n`;\n}\n\n/**\n * Format indexes object for output (PowerSync SDK v1.32.0+ format)\n * Changed from array format [{ name, columns }] to object format { name: columns }\n */\nfunction formatIndexes(indexes: IndexDefinition[]): string {\n if (indexes.length === 0) return '';\n const indexLines = indexes.map(idx => {\n const columnsStr = idx.columns.map(c => `'${c}'`).join(', ');\n return ` '${idx.name}': [${columnsStr}],`;\n });\n return `indexes: {\\n${indexLines.join('\\n')}\\n }`;\n}\n\n/**\n * Generate the table definition for a parsed table\n */\nexport function generateTableDefinition(table: ParsedTable, columnDefs: string[], indexes: IndexDefinition[] = []): string {\n // Use alias if provided, otherwise use table name\n const effectiveName = table.config.alias ?? table.name;\n if (columnDefs.length === 0) {\n return `// ${effectiveName} - no syncable columns found`;\n }\n\n // Build options object\n const optionsParts: string[] = [];\n if (table.config.trackMetadata) {\n optionsParts.push('trackMetadata: true');\n }\n if (indexes.length > 0) {\n optionsParts.push(formatIndexes(indexes));\n }\n const optionsStr = optionsParts.length > 0 ? `, {\\n ${optionsParts.join(',\\n ')}\\n}` : '';\n return `const ${effectiveName} = new Table({\n${columnDefs.join('\\n')}\n}${optionsStr});`;\n}\n\n/**\n * Generate the table definition for a local-only table (not synced through PowerSync)\n */\nexport function generateLocalOnlyTableDefinition(table: ParsedTable, columnDefs: string[], indexes: IndexDefinition[] = []): string {\n // Use alias if provided, otherwise use table name\n const effectiveName = table.config.alias ?? table.name;\n if (columnDefs.length === 0) {\n return `// ${effectiveName} - no syncable columns found`;\n }\n\n // Build options object - always include localOnly: true\n const optionsParts: string[] = ['localOnly: true'];\n if (indexes.length > 0) {\n optionsParts.push(formatIndexes(indexes));\n }\n const optionsStr = `, {\\n ${optionsParts.join(',\\n ')}\\n}`;\n return `// Local-only table (not synced)\nconst ${effectiveName} = new Table({\n${columnDefs.join('\\n')}\n}${optionsStr});`;\n}\n\n/**\n * Generate the schema export section\n */\nexport function generateSchemaExport(tables: ParsedTable[]): string {\n // Use alias if provided, otherwise use table name\n const tableNames = tables.map(t => t.config.alias ?? t.name);\n return `// ============================================================================\n// SCHEMA EXPORT\n// ============================================================================\n\n// NOTE: photo_attachments is NOT included here.\n// The AttachmentQueue from @powersync/attachments creates and manages\n// its own internal SQLite table (not a view) during queue.init().\n// This allows INSERT/UPDATE operations to work correctly.\n\nexport const AppSchema = new Schema({\n${tableNames.map(name => ` ${name},`).join('\\n')}\n});\n\nexport type Database = (typeof AppSchema)[\"types\"];`;\n}\n\n/**\n * Generate schema mapping utilities\n */\nexport function generateSchemaMapping(tables: ParsedTable[], schemas: string[]): string {\n // Group tables by non-public schemas (use alias if provided)\n const schemaGroups = new Map<string, string[]>();\n for (const schema of schemas) {\n if (schema !== 'public') {\n schemaGroups.set(schema, []);\n }\n }\n for (const table of tables) {\n const effectiveName = table.config.alias ?? table.name;\n if (table.schema !== 'public' && schemaGroups.has(table.schema)) {\n schemaGroups.get(table.schema)!.push(effectiveName);\n }\n }\n const sections: string[] = [`// ============================================================================\n// SCHEMA MAPPING FOR CONNECTOR\n// ============================================================================`];\n\n // Generate constants for each non-public schema\n for (const [schema, tableNames] of schemaGroups) {\n if (tableNames.length > 0) {\n const constName = `${schema.toUpperCase()}_SCHEMA_TABLES`;\n sections.push(`\n// Tables in the '${schema}' schema (need .schema('${schema}') in Supabase queries)\nexport const ${constName} = new Set([\n${tableNames.map(name => ` \"${name}\",`).join('\\n')}\n]);`);\n }\n }\n\n // Generate helper function\n const schemaChecks = Array.from(schemaGroups.entries()).filter(([, names]) => names.length > 0).map(([schema]) => {\n const constName = `${schema.toUpperCase()}_SCHEMA_TABLES`;\n return ` if (${constName}.has(tableName)) return \"${schema}\";`;\n });\n if (schemaChecks.length > 0) {\n sections.push(`\n/**\n * Get the Supabase schema for a table\n */\nexport function getTableSchema(tableName: string): ${schemas.map(s => `\"${s}\"`).join(' | ')} {\n${schemaChecks.join('\\n')}\n return \"public\";\n}`);\n } else {\n sections.push(`\n/**\n * Get the Supabase schema for a table\n */\nexport function getTableSchema(tableName: string): \"public\" {\n return \"public\";\n}`);\n }\n return sections.join('\\n');\n}\n\n/**\n * Generate the FK detection utility (helpful for consumers)\n */\nexport function generateFKUtility(): string {\n return `\n// ============================================================================\n// FOREIGN KEY UTILITIES\n// ============================================================================\n\n/**\n * Check if a column name represents a foreign key reference\n * Convention: columns ending in 'Id' are foreign keys (e.g., projectId -> Project table)\n */\nexport function isForeignKeyColumn(columnName: string): boolean {\n return columnName.endsWith('Id') && columnName !== 'id';\n}\n\n/**\n * Get the referenced table name from a foreign key column\n * e.g., 'projectId' -> 'Project', 'equipmentFixtureUnitId' -> 'EquipmentFixtureUnit'\n */\nexport function getForeignKeyTable(columnName: string): string | null {\n if (!isForeignKeyColumn(columnName)) return null;\n // Remove 'Id' suffix and capitalize first letter\n const baseName = columnName.slice(0, -2);\n return baseName.charAt(0).toUpperCase() + baseName.slice(1);\n}`;\n}\n\n/**\n * Generate complete output file\n */\nexport function generateOutputFile(tables: ParsedTable[], tableDefs: string[], schemas: string[], typesPath: string): string {\n return `${generateHeader(typesPath)}\n\n// ============================================================================\n// TABLE DEFINITIONS\n// ============================================================================\n\n${tableDefs.join('\\n\\n')}\n\n${generateSchemaExport(tables)}\n\n${generateSchemaMapping(tables, ['public', ...schemas.filter(s => s !== 'public')])}\n${generateFKUtility()}\n`;\n}","/**\n * Foreign Key Dependency Detection\n *\n * Analyzes parsed tables to detect FK relationships and determine\n * optimal upload order for offline-first sync.\n */\n\nimport type { ParsedTable } from './parser.js';\nexport interface FKDependency {\n /** Table containing the FK column */\n table: string;\n /** FK column name (e.g., 'projectId') */\n column: string;\n /** Referenced table name (e.g., 'Project') */\n referencedTable: string;\n}\nexport interface DependencyGraph {\n /** Map of table -> tables it depends on (has FK references to) */\n dependencies: Map<string, string[]>;\n /** Map of table -> tables that depend on it (have FKs referencing this table) */\n dependents: Map<string, string[]>;\n /** Topologically sorted table names for optimal upload order */\n uploadOrder: string[];\n /** All detected FK relationships */\n fkRelationships: FKDependency[];\n}\n\n/**\n * Convert a FK column name to its referenced table name\n * Convention: columns ending in 'Id' reference the PascalCase table\n * e.g., projectId -> Project, equipmentUnitId -> EquipmentUnit\n */\nexport function fkColumnToTableName(columnName: string): string | null {\n if (!columnName.endsWith('Id') || columnName === 'id') {\n return null;\n }\n // Remove 'Id' suffix and capitalize first letter\n const baseName = columnName.slice(0, -2);\n return baseName.charAt(0).toUpperCase() + baseName.slice(1);\n}\n\n/**\n * Detect FK dependencies from parsed tables\n *\n * Uses naming convention: columns ending in 'Id' reference the PascalCase table\n * Only considers references to tables that are in the provided list (ignores external references)\n */\nexport function detectFKDependencies(tables: ParsedTable[]): DependencyGraph {\n const tableNames = new Set(tables.map(t => t.name));\n const dependencies = new Map<string, string[]>();\n const dependents = new Map<string, string[]>();\n const fkRelationships: FKDependency[] = [];\n\n // Initialize maps for all tables\n for (const table of tables) {\n dependencies.set(table.name, []);\n dependents.set(table.name, []);\n }\n\n // Detect FK relationships\n for (const table of tables) {\n for (const [columnName] of table.columns) {\n const referencedTable = fkColumnToTableName(columnName);\n\n // Only track references to tables in our schema\n if (referencedTable && tableNames.has(referencedTable)) {\n // This table depends on the referenced table\n const tableDeps = dependencies.get(table.name) || [];\n if (!tableDeps.includes(referencedTable)) {\n tableDeps.push(referencedTable);\n dependencies.set(table.name, tableDeps);\n }\n\n // The referenced table has this table as a dependent\n const refDeps = dependents.get(referencedTable) || [];\n if (!refDeps.includes(table.name)) {\n refDeps.push(table.name);\n dependents.set(referencedTable, refDeps);\n }\n\n // Record the FK relationship\n fkRelationships.push({\n table: table.name,\n column: columnName,\n referencedTable\n });\n }\n }\n }\n\n // Calculate upload order via topological sort\n const uploadOrder = getUploadOrder({\n dependencies\n });\n return {\n dependencies,\n dependents,\n uploadOrder,\n fkRelationships\n };\n}\n\n/**\n * Get optimal upload order using Kahn's topological sort algorithm\n *\n * Tables with no dependencies are uploaded first, then tables that depend on them, etc.\n * This ensures that when a row references another table, the referenced row exists first.\n */\nexport function getUploadOrder(graph: Pick<DependencyGraph, 'dependencies'>): string[] {\n const result: string[] = [];\n const inDegree = new Map<string, number>();\n const adjList = new Map<string, string[]>();\n\n // Initialize in-degree (number of dependencies) for each table\n for (const [table, deps] of graph.dependencies) {\n inDegree.set(table, deps.length);\n adjList.set(table, []);\n }\n\n // Build adjacency list (reverse of dependencies - who depends on this table)\n for (const [table, deps] of graph.dependencies) {\n for (const dep of deps) {\n const list = adjList.get(dep) || [];\n list.push(table);\n adjList.set(dep, list);\n }\n }\n\n // Start with tables that have no dependencies\n const queue: string[] = [];\n for (const [table, degree] of inDegree) {\n if (degree === 0) {\n queue.push(table);\n }\n }\n\n // Sort queue alphabetically for deterministic output\n queue.sort();\n\n // Process queue\n while (queue.length > 0) {\n const table = queue.shift()!;\n result.push(table);\n\n // Reduce in-degree for all tables that depend on this one\n const dependentTables = adjList.get(table) || [];\n for (const dependent of dependentTables) {\n const newDegree = (inDegree.get(dependent) || 0) - 1;\n inDegree.set(dependent, newDegree);\n if (newDegree === 0) {\n queue.push(dependent);\n // Keep queue sorted for deterministic output\n queue.sort();\n }\n }\n }\n\n // Check for cycles (if result doesn't include all tables)\n if (result.length < graph.dependencies.size) {\n // There's a cycle - just append remaining tables alphabetically\n const remaining = [...graph.dependencies.keys()].filter(t => !result.includes(t)).sort();\n result.push(...remaining);\n }\n return result;\n}\n\n/**\n * Get tables that have no dependencies (root tables)\n * These are typically reference/lookup tables like User, Project, etc.\n */\nexport function getRootTables(graph: DependencyGraph): string[] {\n const roots: string[] = [];\n for (const [table, deps] of graph.dependencies) {\n if (deps.length === 0) {\n roots.push(table);\n }\n }\n return roots.sort();\n}\n\n/**\n * Get tables that have no dependents (leaf tables)\n * These are typically transaction/event tables that reference other tables\n */\nexport function getLeafTables(graph: DependencyGraph): string[] {\n const leaves: string[] = [];\n for (const [table, deps] of graph.dependents) {\n if (deps.length === 0) {\n leaves.push(table);\n }\n }\n return leaves.sort();\n}\n\n/**\n * Format the dependency graph as a human-readable string\n */\nexport function formatDependencyGraph(graph: DependencyGraph): string {\n const lines: string[] = [];\n lines.push('FK Dependencies:');\n for (const fk of graph.fkRelationships) {\n lines.push(` ${fk.table}.${fk.column} -> ${fk.referencedTable}`);\n }\n if (graph.fkRelationships.length === 0) {\n lines.push(' (none detected)');\n }\n lines.push('');\n lines.push('Upload Order (topological):');\n graph.uploadOrder.forEach((table, i) => {\n const deps = graph.dependencies.get(table) || [];\n const depStr = deps.length > 0 ? ` (depends on: ${deps.join(', ')})` : ' (root)';\n lines.push(` ${i + 1}. ${table}${depStr}`);\n });\n return lines.join('\\n');\n}"],"mappings":"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","names":[]}
1
+ {"version":3,"sources":["../../src/generator/config.ts","../../src/generator/generator.ts","../../src/generator/parser.ts","../../src/generator/templates.ts","../../src/generator/fk-dependencies.ts"],"sourcesContent":["/**\n * Configuration types and helpers for PowerSync schema generator\n */\n\nexport interface TableConfig {\n /** Table name (PascalCase as it appears in database.types.ts) */\n name: string;\n /** Schema name (defaults to 'public') */\n schema?: string;\n /** Enable ps_crud timestamp tracking for optimistic UI updates */\n trackMetadata?: boolean;\n /**\n * Sync the primary key column (normally skipped as PowerSync handles it internally).\n *\n * Use this for tables with integer PKs that are referenced by FKs in other tables.\n * Example: `Group.id` is referenced by `UserGroup.groupId`, so Group needs `syncPrimaryKey: true`\n * to ensure the integer ID is available for client-side joins.\n */\n syncPrimaryKey?: boolean;\n /** @deprecated Use `syncPrimaryKey` instead */\n includeId?: boolean;\n /** Columns to skip for this specific table (in addition to global skipColumns) */\n skipColumns?: string[];\n /** Only include these columns (overrides skipColumns if specified) */\n onlyColumns?: string[];\n /** Alias for the table in PowerSync schema (for conflicting names across schemas) */\n alias?: string;\n /** Table is local-only (not synced through PowerSync) */\n localOnly?: boolean;\n}\nexport interface GeneratorConfig {\n /** Path to Supabase-generated database.types.ts file */\n typesPath: string;\n /** Output path for generated PowerSync schema */\n outputPath: string;\n /** Tables to include in the PowerSync schema */\n tables: TableConfig[];\n /** Columns to always skip (in addition to defaults like 'id') */\n skipColumns?: string[];\n /** Column name patterns that should use column.real for decimal values */\n decimalPatterns?: string[];\n /** Additional schemas to track (besides 'public' which is the default) */\n schemas?: string[];\n /** Generate indexes for FK and common columns (default: true) */\n autoIndexes?: boolean;\n /** Additional columns to index (exact column names) */\n indexColumns?: string[];\n /** Column patterns that should be indexed (regex patterns) */\n indexPatterns?: string[];\n}\n\n/**\n * Define a PowerSync generator configuration with type safety\n */\nexport function defineConfig(config: GeneratorConfig): GeneratorConfig {\n return config;\n}\n\n/**\n * Default columns that are skipped during generation\n */\nexport const DEFAULT_SKIP_COLUMNS = ['id',\n// PowerSync handles id automatically\n// Legacy numeric ID columns - typically not needed after UUID migration\n'legacyId'];\n\n/**\n * Default column name patterns that indicate decimal values\n */\nexport const DEFAULT_DECIMAL_PATTERNS = ['hours', 'watts', 'voltage', 'rate', 'amount', 'price', 'cost', 'total'];\n\n/**\n * Default column patterns that should be indexed\n * These are regex patterns matched against column names\n */\nexport const DEFAULT_INDEX_PATTERNS = ['Id$',\n// FK columns ending in Id (e.g., projectId, userId)\n'^createdAt$', '^updatedAt$', '^status$', '^type$'];\n\n/**\n * Required authentication/access control tables for offline auth\n * These tables are needed for proper offline access control\n */\nexport interface RequiredAuthTable {\n /** Table name (PascalCase) */\n name: string;\n /** Schema name */\n schema: string;\n /** Whether this table requires syncPrimaryKey to be true */\n requiresSyncPrimaryKey?: boolean;\n}\nexport const REQUIRED_AUTH_TABLES: RequiredAuthTable[] = [{\n name: 'UserAccess',\n schema: 'core'\n}, {\n name: 'UserGroup',\n schema: 'core'\n}, {\n name: 'GroupAccessKey',\n schema: 'core',\n requiresSyncPrimaryKey: true\n}, {\n name: 'Group',\n schema: 'core',\n requiresSyncPrimaryKey: true\n}];","/**\n * PowerSync schema generator\n *\n * Converts Supabase database.types.ts into PowerSync schema definitions\n */\n\nimport * as fs from 'fs';\nimport * as path from 'path';\nimport type { GeneratorConfig, TableConfig } from './config.js';\nimport { DEFAULT_SKIP_COLUMNS, DEFAULT_DECIMAL_PATTERNS, DEFAULT_INDEX_PATTERNS, REQUIRED_AUTH_TABLES } from './config.js';\nimport { parseTypesFile, type ParsedTable } from './parser.js';\nimport { generateTableDefinition, generateLocalOnlyTableDefinition, generateOutputFile, generateIndexDefinitions, type IndexDefinition } from './templates.js';\nimport { detectFKDependencies, type DependencyGraph } from './fk-dependencies.js';\nexport interface ColumnMapping {\n type: 'column.text' | 'column.integer' | 'column.real';\n isEnum?: boolean;\n isBoolean?: boolean;\n isJson?: boolean;\n}\nexport interface GenerateResult {\n success: boolean;\n tablesGenerated: number;\n outputPath: string;\n errors: string[];\n warnings: string[];\n /** Generated output (included when dryRun is true) */\n output?: string;\n /** Number of indexes generated */\n indexesGenerated?: number;\n /** FK dependency graph (when detected) */\n dependencyGraph?: DependencyGraph;\n}\n\n/**\n * Map TypeScript types to PowerSync column types\n */\nexport function mapTypeToPowerSync(tsType: string, columnName: string, decimalPatterns: string[]): ColumnMapping | null {\n // Clean up the type (remove nullability)\n const cleanType = tsType.trim().replace(/\\s*\\|\\s*null/g, '');\n\n // JSON types -> store as TEXT (will be parsed/stringified by type-transformer)\n if (cleanType.includes('Json')) {\n return {\n type: 'column.text',\n isJson: true\n };\n }\n\n // Skip complex types that can't be stored in SQLite\n if (cleanType.includes('unknown') || cleanType.includes('{')) {\n return null;\n }\n\n // Array types - skip\n if (cleanType.includes('[]')) {\n return null;\n }\n\n // Boolean -> integer (0/1)\n if (cleanType === 'boolean') {\n return {\n type: 'column.integer',\n isBoolean: true\n };\n }\n\n // Number types\n if (cleanType === 'number') {\n // Use real for columns that might have decimals\n if (decimalPatterns.some(pattern => columnName.toLowerCase().includes(pattern.toLowerCase()))) {\n return {\n type: 'column.real'\n };\n }\n return {\n type: 'column.integer'\n };\n }\n\n // String types\n if (cleanType === 'string') {\n return {\n type: 'column.text'\n };\n }\n\n // Enum types (Database[\"schema\"][\"Enums\"][\"EnumName\"]) -> store as text\n if (cleanType.includes('Database[') && cleanType.includes('Enums')) {\n return {\n type: 'column.text',\n isEnum: true\n };\n }\n\n // Default to text for unknown types (likely enums or other string-like types)\n return {\n type: 'column.text'\n };\n}\n\n/**\n * Generate column definitions for a table\n */\nexport function generateColumnDefs(table: ParsedTable, decimalPatterns: string[]): string[] {\n const columnDefs: string[] = [];\n for (const [columnName, tsType] of table.columns) {\n const mapping = mapTypeToPowerSync(tsType, columnName, decimalPatterns);\n if (mapping) {\n // Add comment for boolean, enum, and json columns\n let comment = '';\n if (mapping.isBoolean) {\n comment = ' // boolean stored as 0/1';\n } else if (mapping.isEnum) {\n comment = ' // enum stored as text';\n } else if (mapping.isJson) {\n comment = ' // JSON stored as text';\n }\n columnDefs.push(` ${columnName}: ${mapping.type},${comment}`);\n }\n }\n return columnDefs;\n}\n\n/**\n * Validate that required auth tables are included in config\n * Returns warnings for missing tables or misconfigured tables\n */\nexport function validateAuthTables(tables: TableConfig[]): string[] {\n const warnings: string[] = [];\n for (const authTable of REQUIRED_AUTH_TABLES) {\n const configTable = tables.find(t => t.name === authTable.name && (t.schema ?? 'public') === authTable.schema);\n if (!configTable) {\n warnings.push(`Missing required auth table: ${authTable.schema}.${authTable.name} - offline access control may not work correctly`);\n } else if (authTable.requiresSyncPrimaryKey && !configTable.syncPrimaryKey) {\n warnings.push(`Auth table ${authTable.schema}.${authTable.name} should have syncPrimaryKey: true for proper FK references`);\n }\n }\n return warnings;\n}\n\n/**\n * Validate FK references to ensure all referenced tables are in the config\n * Returns warnings for missing FK targets\n */\nexport function validateFKReferences(dependencyGraph: DependencyGraph, configuredTables: Set<string>): string[] {\n const warnings: string[] = [];\n for (const fk of dependencyGraph.fkRelationships) {\n if (!configuredTables.has(fk.referencedTable)) {\n warnings.push(`FK reference ${fk.table}.${fk.column} references table '${fk.referencedTable}' which is not in the config`);\n }\n }\n return warnings;\n}\n\n/**\n * Generate PowerSync schema from configuration\n */\nexport async function generateSchema(config: GeneratorConfig, options?: {\n cwd?: string;\n verbose?: boolean;\n dryRun?: boolean;\n}): Promise<GenerateResult> {\n const cwd = options?.cwd ?? process.cwd();\n const verbose = options?.verbose ?? false;\n const dryRun = options?.dryRun ?? false;\n const result: GenerateResult = {\n success: false,\n tablesGenerated: 0,\n outputPath: '',\n errors: [],\n warnings: []\n };\n\n // Resolve paths relative to cwd\n const typesPath = path.isAbsolute(config.typesPath) ? config.typesPath : path.resolve(cwd, config.typesPath);\n const outputPath = path.isAbsolute(config.outputPath) ? config.outputPath : path.resolve(cwd, config.outputPath);\n result.outputPath = outputPath;\n\n // Validate required auth tables\n const authWarnings = validateAuthTables(config.tables);\n result.warnings.push(...authWarnings);\n\n // Check if types file exists\n if (!fs.existsSync(typesPath)) {\n result.errors.push(`Types file not found: ${typesPath}`);\n return result;\n }\n\n // Read types file\n if (verbose) {\n console.log(`Reading types from: ${typesPath}`);\n }\n const typesContent = fs.readFileSync(typesPath, 'utf-8');\n\n // Build skip columns set\n const skipColumns = new Set([...DEFAULT_SKIP_COLUMNS, ...(config.skipColumns ?? [])]);\n\n // Build decimal patterns\n const decimalPatterns = [...DEFAULT_DECIMAL_PATTERNS, ...(config.decimalPatterns ?? [])];\n\n // Parse tables from types file\n const parsedTables = parseTypesFile(typesContent, config.tables, skipColumns);\n\n // Check for tables that weren't found\n for (const tableConfig of config.tables) {\n const found = parsedTables.some(t => t.name === tableConfig.name);\n if (!found) {\n result.warnings.push(`Table '${tableConfig.name}' not found in schema '${tableConfig.schema ?? 'public'}'`);\n }\n }\n if (parsedTables.length === 0) {\n result.errors.push('No tables were parsed successfully');\n return result;\n }\n\n // Build index patterns (auto-indexes enabled by default)\n const autoIndexes = config.autoIndexes ?? true;\n const indexPatterns = autoIndexes ? [...DEFAULT_INDEX_PATTERNS, ...(config.indexPatterns ?? [])] : config.indexPatterns ?? [];\n const indexColumns = config.indexColumns ?? [];\n\n // Detect FK dependencies\n const dependencyGraph = detectFKDependencies(parsedTables);\n result.dependencyGraph = dependencyGraph;\n\n // Validate FK references - warn if any FK targets a table not in config\n const configuredTableNames = new Set(config.tables.map(t => t.name));\n const fkWarnings = validateFKReferences(dependencyGraph, configuredTableNames);\n result.warnings.push(...fkWarnings);\n if (verbose && dependencyGraph.fkRelationships.length > 0) {\n console.log(`\\nFK Dependencies detected: ${dependencyGraph.fkRelationships.length}`);\n for (const fk of dependencyGraph.fkRelationships) {\n console.log(` ${fk.table}.${fk.column} -> ${fk.referencedTable}`);\n }\n console.log(`\\nRecommended upload order:`);\n dependencyGraph.uploadOrder.forEach((table, i) => {\n console.log(` ${i + 1}. ${table}`);\n });\n console.log('');\n }\n\n // Generate table definitions - separate synced and local-only tables\n const syncedTableDefs: string[] = [];\n const localOnlyTableDefs: string[] = [];\n let totalIndexes = 0;\n for (const table of parsedTables) {\n const isLocalOnly = table.config.localOnly ?? false;\n const tableAlias = table.config.alias;\n if (verbose) {\n const syncPK = table.config.syncPrimaryKey || table.config.includeId;\n const flags = [table.config.trackMetadata ? '[trackMetadata]' : '', syncPK ? '[syncPrimaryKey]' : '', isLocalOnly ? '[localOnly]' : '', tableAlias ? `[alias: ${tableAlias}]` : ''].filter(Boolean).join(' ');\n console.log(`Processing ${table.schema}.${table.name} (${table.columns.size} columns)${flags ? ' ' + flags : ''}`);\n }\n const columnDefs = generateColumnDefs(table, decimalPatterns);\n if (columnDefs.length === 0) {\n result.warnings.push(`Table '${table.name}' has no syncable columns`);\n continue;\n }\n\n // Generate indexes for this table (use alias for index naming if provided)\n const effectiveTableName = tableAlias ?? table.name;\n const columnNames = [...table.columns.keys()];\n const indexes = indexPatterns.length > 0 || indexColumns.length > 0 ? generateIndexDefinitions(effectiveTableName, columnNames, indexPatterns, indexColumns) : [];\n totalIndexes += indexes.length;\n if (verbose && indexes.length > 0) {\n console.log(` Indexes: ${indexes.map(i => i.name).join(', ')}`);\n }\n\n // Generate table definition based on localOnly flag\n if (isLocalOnly) {\n localOnlyTableDefs.push(generateLocalOnlyTableDefinition(table, columnDefs, indexes));\n } else {\n syncedTableDefs.push(generateTableDefinition(table, columnDefs, indexes));\n }\n }\n\n // Combine synced and local-only table definitions\n const tableDefs = [...syncedTableDefs, ...localOnlyTableDefs];\n result.indexesGenerated = totalIndexes;\n\n // Collect unique schemas\n const schemas = [...new Set(config.tables.map(t => t.schema ?? 'public'))];\n\n // Generate output file content\n const relativePath = path.relative(cwd, typesPath);\n // Filter tables that were successfully generated (check for both name and alias)\n const generatedTables = parsedTables.filter(t => {\n const effectiveName = t.config.alias ?? t.name;\n return tableDefs.some(def => def.includes(`const ${effectiveName} =`));\n });\n const output = generateOutputFile(generatedTables, tableDefs, schemas, relativePath);\n\n // If dry-run, return output without writing\n if (dryRun) {\n result.success = true;\n result.tablesGenerated = tableDefs.length;\n result.output = output;\n return result;\n }\n\n // Ensure output directory exists\n const outputDir = path.dirname(outputPath);\n if (!fs.existsSync(outputDir)) {\n fs.mkdirSync(outputDir, {\n recursive: true\n });\n }\n\n // Write output file\n fs.writeFileSync(outputPath, output);\n result.success = true;\n result.tablesGenerated = tableDefs.length;\n return result;\n}","/**\n * Parser for Supabase database.types.ts files\n *\n * Extracts table definitions and column types from the generated TypeScript types\n */\n\nimport type { TableConfig } from './config.js';\nexport interface ColumnInfo {\n name: string;\n tsType: string;\n isNullable: boolean;\n}\nexport interface ParsedTable {\n name: string;\n schema: string;\n columns: Map<string, string>;\n config: TableConfig;\n}\nexport interface ParseOptions {\n /** Columns to skip */\n skipColumns: Set<string>;\n /**\n * Include the id column (normally skipped).\n * Use for tables with integer PKs referenced by FKs in other tables.\n */\n syncPrimaryKey?: boolean;\n /** @deprecated Use `syncPrimaryKey` instead */\n includeId?: boolean;\n /** Only include these columns (overrides skipColumns if specified) */\n onlyColumns?: string[];\n}\n\n/**\n * Parse the Row type from a table definition and extract columns\n */\nexport function parseRowType(tableContent: string, options: ParseOptions): Map<string, string> {\n const columns = new Map<string, string>();\n\n // Find the Row block - handles nested braces in type definitions\n const rowMatch = tableContent.match(/Row:\\s*\\{([^}]+(?:\\{[^}]*\\}[^}]*)*)\\}/s);\n if (!rowMatch) return columns;\n const rowContent = rowMatch[1];\n\n // syncPrimaryKey takes precedence, with includeId as fallback for backwards compat\n const includePrimaryKey = options.syncPrimaryKey ?? options.includeId ?? false;\n\n // If onlyColumns is specified, use it as a whitelist (overrides skipColumns)\n const onlyColumnsSet = options.onlyColumns ? new Set(options.onlyColumns) : null;\n\n // Parse each column: \"columnName: type\" or \"columnName?: type\"\n const columnRegex = /(\\w+)\\??:\\s*([^,\\n]+)/g;\n let match;\n while ((match = columnRegex.exec(rowContent)) !== null) {\n const [, columnName, columnType] = match;\n\n // Determine if column should be included\n let shouldInclude: boolean;\n if (onlyColumnsSet) {\n // onlyColumns mode: only include explicitly listed columns\n // Exception: include 'id' if syncPrimaryKey is true\n shouldInclude = onlyColumnsSet.has(columnName) || includePrimaryKey && columnName === 'id';\n } else {\n // skipColumns mode: include unless explicitly skipped\n // Exception: include 'id' if syncPrimaryKey is true\n const isSkipped = options.skipColumns.has(columnName);\n shouldInclude = !isSkipped || includePrimaryKey && columnName === 'id';\n }\n if (shouldInclude) {\n columns.set(columnName, columnType.trim());\n }\n }\n return columns;\n}\n\n/**\n * Escape special regex characters in a string\n */\nfunction escapeRegex(str: string): string {\n return str.replace(/[.*+?^${}()|[\\]\\\\]/g, '\\\\$&');\n}\n\n/**\n * Extract a table definition from the database.types.ts content\n *\n * Uses bracket-counting to properly handle nested arrays in Relationships\n * (e.g., columns: [\"parentId\"] inside relationship objects)\n */\nexport function extractTableDef(content: string, tableName: string, schema: string): string | null {\n // Escape special characters in schema and table names for regex safety\n const escapedSchema = escapeRegex(schema);\n const escapedTableName = escapeRegex(tableName);\n\n // Find the schema section\n const schemaRegex = new RegExp(`${escapedSchema}:\\\\s*\\\\{[\\\\s\\\\S]*?Tables:\\\\s*\\\\{`, 'g');\n const schemaMatch = schemaRegex.exec(content);\n if (!schemaMatch) return null;\n const startIndex = schemaMatch.index;\n const searchContent = content.slice(startIndex);\n\n // Find the start of this specific table\n // Use negative lookbehind (?<![A-Za-z]) to avoid matching table names that are\n // substrings of other names (e.g., \"Tag\" in \"CommentTag\")\n const tableStartRegex = new RegExp(`(?<![A-Za-z])${escapedTableName}:\\\\s*\\\\{`, 'g');\n const tableStartMatch = tableStartRegex.exec(searchContent);\n if (!tableStartMatch) return null;\n\n // Use bracket counting to find the matching closing brace\n const tableStartIndex = tableStartMatch.index;\n const openBraceIndex = tableStartMatch.index + tableStartMatch[0].length - 1;\n let braceCount = 1;\n let i = openBraceIndex + 1;\n while (i < searchContent.length && braceCount > 0) {\n const char = searchContent[i];\n if (char === '{') braceCount++;else if (char === '}') braceCount--;\n i++;\n }\n if (braceCount !== 0) return null;\n return searchContent.slice(tableStartIndex, i);\n}\n\n/**\n * Parse a database.types.ts file and extract specified tables\n */\nexport function parseTypesFile(content: string, tables: TableConfig[], globalSkipColumns: Set<string>): ParsedTable[] {\n const parsedTables: ParsedTable[] = [];\n for (const tableConfig of tables) {\n const {\n name,\n schema = 'public',\n syncPrimaryKey,\n includeId,\n skipColumns: tableSkipColumns,\n onlyColumns\n } = tableConfig;\n const tableDef = extractTableDef(content, name, schema);\n if (!tableDef) {\n continue;\n }\n\n // Merge global and per-table skipColumns\n const mergedSkipColumns = new Set([...globalSkipColumns, ...(tableSkipColumns ?? [])]);\n const columns = parseRowType(tableDef, {\n skipColumns: mergedSkipColumns,\n syncPrimaryKey,\n includeId,\n onlyColumns\n });\n if (columns.size > 0) {\n parsedTables.push({\n name,\n schema,\n columns,\n config: tableConfig\n });\n }\n }\n return parsedTables;\n}\n\n/**\n * Get all available schemas from the types file\n */\nexport function getAvailableSchemas(content: string): string[] {\n const schemas: string[] = [];\n const schemaRegex = /(\\w+):\\s*\\{[\\s\\S]*?Tables:\\s*\\{/g;\n let match;\n while ((match = schemaRegex.exec(content)) !== null) {\n schemas.push(match[1]);\n }\n return schemas;\n}\n\n/**\n * Get all table names in a schema\n */\nexport function getTablesInSchema(content: string, schema: string): string[] {\n const tables: string[] = [];\n\n // Find the schema section\n const schemaRegex = new RegExp(`${schema}:\\\\s*\\\\{[\\\\s\\\\S]*?Tables:\\\\s*\\\\{([\\\\s\\\\S]*?)\\\\}\\\\s*Views:`, 'g');\n const schemaMatch = schemaRegex.exec(content);\n if (!schemaMatch) return tables;\n const tablesContent = schemaMatch[1];\n\n // Find table names (they're at the start of each table definition)\n const tableNameRegex = /^\\s*(\\w+):\\s*\\{/gm;\n let match;\n while ((match = tableNameRegex.exec(tablesContent)) !== null) {\n tables.push(match[1]);\n }\n return tables;\n}","/**\n * Output templates for PowerSync schema generation\n */\n\nimport type { ParsedTable } from './parser.js';\nexport interface IndexDefinition {\n name: string;\n columns: string[];\n}\n\n/**\n * Convert table name to snake_case for index naming\n */\nfunction toSnakeCase(str: string): string {\n return str.replace(/([a-z])([A-Z])/g, '$1_$2').toLowerCase();\n}\n\n/**\n * Generate index definitions for a table based on column patterns\n *\n * @param tableName - The table name (PascalCase)\n * @param columns - Array of column names in the table\n * @param indexPatterns - Regex patterns to match against column names\n * @param additionalColumns - Specific column names to always index\n * @returns Array of index definitions\n */\nexport function generateIndexDefinitions(tableName: string, columns: string[], indexPatterns: string[], additionalColumns: string[] = []): IndexDefinition[] {\n const indexes: IndexDefinition[] = [];\n const snakeTableName = toSnakeCase(tableName);\n\n // Combine pattern matching and explicit columns\n const columnsToIndex = new Set<string>();\n\n // Pre-compile regex patterns with error handling\n const compiledPatterns: RegExp[] = [];\n for (const pattern of indexPatterns) {\n try {\n compiledPatterns.push(new RegExp(pattern));\n } catch {\n console.warn(`Warning: Invalid index pattern regex \"${pattern}\" - skipping`);\n }\n }\n\n // Match columns against patterns\n for (const column of columns) {\n // Skip 'id' column - it's the primary key and already indexed\n if (column === 'id') continue;\n for (const regex of compiledPatterns) {\n if (regex.test(column)) {\n columnsToIndex.add(column);\n break;\n }\n }\n }\n\n // Add explicitly specified columns (if they exist in the table)\n for (const column of additionalColumns) {\n if (columns.includes(column) && column !== 'id') {\n columnsToIndex.add(column);\n }\n }\n\n // Create index definitions\n for (const column of columnsToIndex) {\n const snakeColumn = toSnakeCase(column);\n indexes.push({\n name: `idx_${snakeTableName}_${snakeColumn}`,\n columns: [column]\n });\n }\n\n // Sort by index name for deterministic output\n indexes.sort((a, b) => a.name.localeCompare(b.name));\n return indexes;\n}\n\n/**\n * File header template\n */\nexport function generateHeader(typesPath: string): string {\n return `/**\n * PowerSync Schema Definition\n *\n * AUTO-GENERATED from ${typesPath}\n * Run: npx @pol-studios/powersync generate-schema\n *\n * DO NOT EDIT MANUALLY - changes will be overwritten\n */\n\nimport { column, Schema, Table } from \"@powersync/react-native\";\n`;\n}\n\n/**\n * Format indexes object for output (PowerSync SDK v1.32.0+ format)\n * Changed from array format [{ name, columns }] to object format { name: columns }\n */\nfunction formatIndexes(indexes: IndexDefinition[]): string {\n if (indexes.length === 0) return '';\n const indexLines = indexes.map(idx => {\n const columnsStr = idx.columns.map(c => `'${c}'`).join(', ');\n return ` '${idx.name}': [${columnsStr}],`;\n });\n return `indexes: {\\n${indexLines.join('\\n')}\\n }`;\n}\n\n/**\n * Generate the table definition for a parsed table\n */\nexport function generateTableDefinition(table: ParsedTable, columnDefs: string[], indexes: IndexDefinition[] = []): string {\n // Use alias if provided, otherwise use table name\n const effectiveName = table.config.alias ?? table.name;\n if (columnDefs.length === 0) {\n return `// ${effectiveName} - no syncable columns found`;\n }\n\n // Build options object\n const optionsParts: string[] = [];\n if (table.config.trackMetadata) {\n optionsParts.push('trackMetadata: true');\n }\n if (indexes.length > 0) {\n optionsParts.push(formatIndexes(indexes));\n }\n const optionsStr = optionsParts.length > 0 ? `, {\\n ${optionsParts.join(',\\n ')}\\n}` : '';\n return `const ${effectiveName} = new Table({\n${columnDefs.join('\\n')}\n}${optionsStr});`;\n}\n\n/**\n * Generate the table definition for a local-only table (not synced through PowerSync)\n */\nexport function generateLocalOnlyTableDefinition(table: ParsedTable, columnDefs: string[], indexes: IndexDefinition[] = []): string {\n // Use alias if provided, otherwise use table name\n const effectiveName = table.config.alias ?? table.name;\n if (columnDefs.length === 0) {\n return `// ${effectiveName} - no syncable columns found`;\n }\n\n // Build options object - always include localOnly: true\n const optionsParts: string[] = ['localOnly: true'];\n if (indexes.length > 0) {\n optionsParts.push(formatIndexes(indexes));\n }\n const optionsStr = `, {\\n ${optionsParts.join(',\\n ')}\\n}`;\n return `// Local-only table (not synced)\nconst ${effectiveName} = new Table({\n${columnDefs.join('\\n')}\n}${optionsStr});`;\n}\n\n/**\n * Generate the schema export section\n */\nexport function generateSchemaExport(tables: ParsedTable[]): string {\n // Use alias if provided, otherwise use table name\n const tableNames = tables.map(t => t.config.alias ?? t.name);\n return `// ============================================================================\n// SCHEMA EXPORT\n// ============================================================================\n\n// NOTE: photo_attachments is NOT included here.\n// The AttachmentQueue from @powersync/attachments creates and manages\n// its own internal SQLite table (not a view) during queue.init().\n// This allows INSERT/UPDATE operations to work correctly.\n\nexport const AppSchema = new Schema({\n${tableNames.map(name => ` ${name},`).join('\\n')}\n});\n\nexport type Database = (typeof AppSchema)[\"types\"];`;\n}\n\n/**\n * Generate schema mapping utilities\n */\nexport function generateSchemaMapping(tables: ParsedTable[], schemas: string[]): string {\n // Group tables by non-public schemas (use alias if provided)\n const schemaGroups = new Map<string, string[]>();\n for (const schema of schemas) {\n if (schema !== 'public') {\n schemaGroups.set(schema, []);\n }\n }\n for (const table of tables) {\n const effectiveName = table.config.alias ?? table.name;\n if (table.schema !== 'public' && schemaGroups.has(table.schema)) {\n schemaGroups.get(table.schema)!.push(effectiveName);\n }\n }\n const sections: string[] = [`// ============================================================================\n// SCHEMA MAPPING FOR CONNECTOR\n// ============================================================================`];\n\n // Generate constants for each non-public schema\n for (const [schema, tableNames] of schemaGroups) {\n if (tableNames.length > 0) {\n const constName = `${schema.toUpperCase()}_SCHEMA_TABLES`;\n sections.push(`\n// Tables in the '${schema}' schema (need .schema('${schema}') in Supabase queries)\nexport const ${constName} = new Set([\n${tableNames.map(name => ` \"${name}\",`).join('\\n')}\n]);`);\n }\n }\n\n // Generate helper function\n const schemaChecks = Array.from(schemaGroups.entries()).filter(([, names]) => names.length > 0).map(([schema]) => {\n const constName = `${schema.toUpperCase()}_SCHEMA_TABLES`;\n return ` if (${constName}.has(tableName)) return \"${schema}\";`;\n });\n if (schemaChecks.length > 0) {\n sections.push(`\n/**\n * Get the Supabase schema for a table\n */\nexport function getTableSchema(tableName: string): ${schemas.map(s => `\"${s}\"`).join(' | ')} {\n${schemaChecks.join('\\n')}\n return \"public\";\n}`);\n } else {\n sections.push(`\n/**\n * Get the Supabase schema for a table\n */\nexport function getTableSchema(tableName: string): \"public\" {\n return \"public\";\n}`);\n }\n return sections.join('\\n');\n}\n\n/**\n * Generate the FK detection utility (helpful for consumers)\n */\nexport function generateFKUtility(): string {\n return `\n// ============================================================================\n// FOREIGN KEY UTILITIES\n// ============================================================================\n\n/**\n * Check if a column name represents a foreign key reference\n * Convention: columns ending in 'Id' are foreign keys (e.g., projectId -> Project table)\n */\nexport function isForeignKeyColumn(columnName: string): boolean {\n return columnName.endsWith('Id') && columnName !== 'id';\n}\n\n/**\n * Get the referenced table name from a foreign key column\n * e.g., 'projectId' -> 'Project', 'equipmentFixtureUnitId' -> 'EquipmentFixtureUnit'\n */\nexport function getForeignKeyTable(columnName: string): string | null {\n if (!isForeignKeyColumn(columnName)) return null;\n // Remove 'Id' suffix and capitalize first letter\n const baseName = columnName.slice(0, -2);\n return baseName.charAt(0).toUpperCase() + baseName.slice(1);\n}`;\n}\n\n/**\n * Generate complete output file\n */\nexport function generateOutputFile(tables: ParsedTable[], tableDefs: string[], schemas: string[], typesPath: string): string {\n return `${generateHeader(typesPath)}\n\n// ============================================================================\n// TABLE DEFINITIONS\n// ============================================================================\n\n${tableDefs.join('\\n\\n')}\n\n${generateSchemaExport(tables)}\n\n${generateSchemaMapping(tables, ['public', ...schemas.filter(s => s !== 'public')])}\n${generateFKUtility()}\n`;\n}","/**\n * Foreign Key Dependency Detection\n *\n * Analyzes parsed tables to detect FK relationships and determine\n * optimal upload order for offline-first sync.\n */\n\nimport type { ParsedTable } from './parser.js';\nexport interface FKDependency {\n /** Table containing the FK column */\n table: string;\n /** FK column name (e.g., 'projectId') */\n column: string;\n /** Referenced table name (e.g., 'Project') */\n referencedTable: string;\n}\nexport interface DependencyGraph {\n /** Map of table -> tables it depends on (has FK references to) */\n dependencies: Map<string, string[]>;\n /** Map of table -> tables that depend on it (have FKs referencing this table) */\n dependents: Map<string, string[]>;\n /** Topologically sorted table names for optimal upload order */\n uploadOrder: string[];\n /** All detected FK relationships */\n fkRelationships: FKDependency[];\n}\n\n/**\n * Convert a FK column name to its referenced table name\n * Convention: columns ending in 'Id' reference the PascalCase table\n * e.g., projectId -> Project, equipmentUnitId -> EquipmentUnit\n */\nexport function fkColumnToTableName(columnName: string): string | null {\n if (!columnName.endsWith('Id') || columnName === 'id') {\n return null;\n }\n // Remove 'Id' suffix and capitalize first letter\n const baseName = columnName.slice(0, -2);\n return baseName.charAt(0).toUpperCase() + baseName.slice(1);\n}\n\n/**\n * Detect FK dependencies from parsed tables\n *\n * Uses naming convention: columns ending in 'Id' reference the PascalCase table\n * Only considers references to tables that are in the provided list (ignores external references)\n */\nexport function detectFKDependencies(tables: ParsedTable[]): DependencyGraph {\n const tableNames = new Set(tables.map(t => t.name));\n const dependencies = new Map<string, string[]>();\n const dependents = new Map<string, string[]>();\n const fkRelationships: FKDependency[] = [];\n\n // Initialize maps for all tables\n for (const table of tables) {\n dependencies.set(table.name, []);\n dependents.set(table.name, []);\n }\n\n // Detect FK relationships\n for (const table of tables) {\n for (const [columnName] of table.columns) {\n const referencedTable = fkColumnToTableName(columnName);\n\n // Only track references to tables in our schema\n if (referencedTable && tableNames.has(referencedTable)) {\n // This table depends on the referenced table\n const tableDeps = dependencies.get(table.name) || [];\n if (!tableDeps.includes(referencedTable)) {\n tableDeps.push(referencedTable);\n dependencies.set(table.name, tableDeps);\n }\n\n // The referenced table has this table as a dependent\n const refDeps = dependents.get(referencedTable) || [];\n if (!refDeps.includes(table.name)) {\n refDeps.push(table.name);\n dependents.set(referencedTable, refDeps);\n }\n\n // Record the FK relationship\n fkRelationships.push({\n table: table.name,\n column: columnName,\n referencedTable\n });\n }\n }\n }\n\n // Calculate upload order via topological sort\n const uploadOrder = getUploadOrder({\n dependencies\n });\n return {\n dependencies,\n dependents,\n uploadOrder,\n fkRelationships\n };\n}\n\n/**\n * Get optimal upload order using Kahn's topological sort algorithm\n *\n * Tables with no dependencies are uploaded first, then tables that depend on them, etc.\n * This ensures that when a row references another table, the referenced row exists first.\n */\nexport function getUploadOrder(graph: Pick<DependencyGraph, 'dependencies'>): string[] {\n const result: string[] = [];\n const inDegree = new Map<string, number>();\n const adjList = new Map<string, string[]>();\n\n // Initialize in-degree (number of dependencies) for each table\n for (const [table, deps] of graph.dependencies) {\n inDegree.set(table, deps.length);\n adjList.set(table, []);\n }\n\n // Build adjacency list (reverse of dependencies - who depends on this table)\n for (const [table, deps] of graph.dependencies) {\n for (const dep of deps) {\n const list = adjList.get(dep) || [];\n list.push(table);\n adjList.set(dep, list);\n }\n }\n\n // Start with tables that have no dependencies\n const queue: string[] = [];\n for (const [table, degree] of inDegree) {\n if (degree === 0) {\n queue.push(table);\n }\n }\n\n // Sort queue alphabetically for deterministic output\n queue.sort();\n\n // Process queue\n while (queue.length > 0) {\n const table = queue.shift()!;\n result.push(table);\n\n // Reduce in-degree for all tables that depend on this one\n const dependentTables = adjList.get(table) || [];\n for (const dependent of dependentTables) {\n const newDegree = (inDegree.get(dependent) || 0) - 1;\n inDegree.set(dependent, newDegree);\n if (newDegree === 0) {\n queue.push(dependent);\n // Keep queue sorted for deterministic output\n queue.sort();\n }\n }\n }\n\n // Check for cycles (if result doesn't include all tables)\n if (result.length < graph.dependencies.size) {\n // There's a cycle - just append remaining tables alphabetically\n const remaining = [...graph.dependencies.keys()].filter(t => !result.includes(t)).sort();\n result.push(...remaining);\n }\n return result;\n}\n\n/**\n * Get tables that have no dependencies (root tables)\n * These are typically reference/lookup tables like User, Project, etc.\n */\nexport function getRootTables(graph: DependencyGraph): string[] {\n const roots: string[] = [];\n for (const [table, deps] of graph.dependencies) {\n if (deps.length === 0) {\n roots.push(table);\n }\n }\n return roots.sort();\n}\n\n/**\n * Get tables that have no dependents (leaf tables)\n * These are typically transaction/event tables that reference other tables\n */\nexport function getLeafTables(graph: DependencyGraph): string[] {\n const leaves: string[] = [];\n for (const [table, deps] of graph.dependents) {\n if (deps.length === 0) {\n leaves.push(table);\n }\n }\n return leaves.sort();\n}\n\n/**\n * Format the dependency graph as a human-readable string\n */\nexport function formatDependencyGraph(graph: DependencyGraph): string {\n const lines: string[] = [];\n lines.push('FK Dependencies:');\n for (const fk of graph.fkRelationships) {\n lines.push(` ${fk.table}.${fk.column} -> ${fk.referencedTable}`);\n }\n if (graph.fkRelationships.length === 0) {\n lines.push(' (none detected)');\n }\n lines.push('');\n lines.push('Upload Order (topological):');\n graph.uploadOrder.forEach((table, i) => {\n const deps = graph.dependencies.get(table) || [];\n const depStr = deps.length > 0 ? ` (depends on: ${deps.join(', ')})` : ' (root)';\n lines.push(` ${i + 1}. ${table}${depStr}`);\n });\n return lines.join('\\n');\n}"],"mappings":"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","names":[]}