@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.
- package/README.md +0 -1
- package/dist/attachments/index.d.ts +1 -1
- package/dist/{background-sync-ChCXW-EV.d.ts → background-sync-CVR3PkFi.d.ts} +1 -1
- package/dist/{chunk-BGBQYQV3.js → chunk-BC2SRII2.js} +180 -299
- package/dist/chunk-BC2SRII2.js.map +1 -0
- package/dist/{chunk-YSTEESEG.js → chunk-C2ACBYBZ.js} +208 -11
- package/dist/chunk-C2ACBYBZ.js.map +1 -0
- package/dist/{chunk-24RDMMCL.js → chunk-FNYQFILT.js} +1 -1
- package/dist/chunk-FNYQFILT.js.map +1 -0
- package/dist/{chunk-YVX3A36I.js → chunk-JCGOZVWL.js} +406 -331
- package/dist/chunk-JCGOZVWL.js.map +1 -0
- package/dist/{chunk-WGHNIAF7.js → chunk-QREWE3NR.js} +2 -2
- package/dist/{chunk-TIFL2KWE.js → chunk-RBPWEOIV.js} +3 -3
- package/dist/{chunk-55DKCJV4.js → chunk-RE5HWLCB.js} +124 -13
- package/dist/chunk-RE5HWLCB.js.map +1 -0
- package/dist/connector/index.d.ts +4 -4
- package/dist/connector/index.js +1 -2
- package/dist/core/index.d.ts +2 -2
- package/dist/generator/cli.js +6 -1
- package/dist/generator/index.d.ts +1 -0
- package/dist/generator/index.js +9 -1
- package/dist/generator/index.js.map +1 -1
- package/dist/index.d.ts +7 -7
- package/dist/index.js +27 -17
- package/dist/index.native.d.ts +5 -5
- package/dist/index.native.js +27 -17
- package/dist/index.web.d.ts +5 -5
- package/dist/index.web.js +27 -17
- package/dist/maintenance/index.d.ts +2 -2
- package/dist/maintenance/index.js +2 -2
- package/dist/platform/index.d.ts +1 -1
- package/dist/platform/index.native.d.ts +1 -1
- package/dist/platform/index.web.d.ts +1 -1
- package/dist/provider/index.d.ts +188 -28
- package/dist/provider/index.js +17 -7
- package/dist/react/index.d.ts +2 -2
- package/dist/react/index.js +3 -3
- package/dist/storage/index.d.ts +1 -1
- package/dist/storage/index.native.d.ts +1 -1
- package/dist/storage/index.web.d.ts +1 -1
- package/dist/{supabase-connector-D2oIl2t8.d.ts → supabase-connector-C4YpH_l3.d.ts} +23 -25
- package/dist/sync/index.d.ts +42 -5
- package/dist/sync/index.js +2 -2
- package/dist/{types-DiBvmGEi.d.ts → types-CpM2_LhU.d.ts} +17 -24
- package/dist/{types-CDqWh56B.d.ts → types-Dv1uf0LZ.d.ts} +16 -1
- package/package.json +2 -2
- package/dist/chunk-24RDMMCL.js.map +0 -1
- package/dist/chunk-55DKCJV4.js.map +0 -1
- package/dist/chunk-BGBQYQV3.js.map +0 -1
- package/dist/chunk-YSTEESEG.js.map +0 -1
- package/dist/chunk-YVX3A36I.js.map +0 -1
- /package/dist/{chunk-WGHNIAF7.js.map → chunk-QREWE3NR.js.map} +0 -0
- /package/dist/{chunk-TIFL2KWE.js.map → chunk-RBPWEOIV.js.map} +0 -0
|
@@ -1,5 +1,5 @@
|
|
|
1
1
|
import { SupabaseClient } from '@supabase/supabase-js';
|
|
2
|
-
import { C as CrudEntry, i as ClassifiedError } from './types-
|
|
2
|
+
import { C as CrudEntry, i as ClassifiedError } from './types-Dv1uf0LZ.js';
|
|
3
3
|
import { LoggerAdapter } from './platform/index.js';
|
|
4
4
|
|
|
5
5
|
/**
|
|
@@ -201,9 +201,9 @@ interface SupabaseConnectorOptions {
|
|
|
201
201
|
*/
|
|
202
202
|
conflictBus?: ConflictBus;
|
|
203
203
|
/**
|
|
204
|
-
*
|
|
205
|
-
*
|
|
206
|
-
*
|
|
204
|
+
* @deprecated No longer used. The connector no longer has internal retry loops.
|
|
205
|
+
* PowerSync SDK handles all retries naturally via its uploadData() recall mechanism.
|
|
206
|
+
* Use `uploadErrorMiddleware` to classify errors as transient (retry) or permanent (fail).
|
|
207
207
|
*/
|
|
208
208
|
retryConfig?: Partial<RetryConfig>;
|
|
209
209
|
/**
|
|
@@ -215,15 +215,17 @@ interface SupabaseConnectorOptions {
|
|
|
215
215
|
circuitBreaker?: ConnectorCircuitBreakerConfig;
|
|
216
216
|
/**
|
|
217
217
|
* Optional: Middleware chain for classifying upload errors.
|
|
218
|
-
*
|
|
218
|
+
* This is the single source of truth for error handling decisions.
|
|
219
219
|
*
|
|
220
220
|
* Middleware functions are called in order until one returns a non-'continue' result:
|
|
221
|
-
* - 'success': Treat as
|
|
222
|
-
* - '
|
|
223
|
-
* - '
|
|
224
|
-
* - '
|
|
221
|
+
* - 'success': Treat as completed (e.g., idempotent duplicate already exists)
|
|
222
|
+
* - 'discard': Remove from queue silently (e.g., orphaned record)
|
|
223
|
+
* - 'retry': Transient error - throw to let PowerSync retry on next sync cycle
|
|
224
|
+
* - 'fail_transaction': Permanent error - record failure and surface to user
|
|
225
|
+
* - 'continue': Pass to next middleware (default: uses isPermanent from classifySupabaseError)
|
|
225
226
|
*
|
|
226
|
-
*
|
|
227
|
+
* Architecture: No internal retry loops. PowerSync SDK handles all retries.
|
|
228
|
+
* This keeps error handling simple and predictable.
|
|
227
229
|
*
|
|
228
230
|
* @example
|
|
229
231
|
* ```typescript
|
|
@@ -275,9 +277,7 @@ interface ConnectorConfig {
|
|
|
275
277
|
refreshThresholdSeconds?: number;
|
|
276
278
|
};
|
|
277
279
|
/**
|
|
278
|
-
*
|
|
279
|
-
* Allows customizing retry attempts, delays, and backoff for different error types.
|
|
280
|
-
* @default DEFAULT_RETRY_CONFIG
|
|
280
|
+
* @deprecated No longer used. PowerSync SDK handles all retries.
|
|
281
281
|
*/
|
|
282
282
|
retryConfig?: Partial<RetryConfig>;
|
|
283
283
|
}
|
|
@@ -360,18 +360,11 @@ interface RetryConfig {
|
|
|
360
360
|
rls: RetryStrategyConfig;
|
|
361
361
|
}
|
|
362
362
|
/**
|
|
363
|
-
*
|
|
363
|
+
* @deprecated No longer used. The connector no longer has internal retry loops.
|
|
364
|
+
* PowerSync SDK handles all retries via its uploadData() recall mechanism.
|
|
365
|
+
* Use `uploadErrorMiddleware` to classify errors as transient (retry) or permanent (fail).
|
|
364
366
|
*
|
|
365
|
-
*
|
|
366
|
-
* - Transient: 1s → 2s → 4s
|
|
367
|
-
*
|
|
368
|
-
* RLS/permission errors (42501, row-level security violations) use extended delays
|
|
369
|
-
* because parent data may need time to sync before child records can be inserted.
|
|
370
|
-
* - RLS: 30s → 60s → 120s → 120s → 120s (5 retries over ~7.5 minutes)
|
|
371
|
-
*
|
|
372
|
-
* Other permanent errors (validation, constraints) get NO retries
|
|
373
|
-
* because they will never succeed - fail fast and surface to user.
|
|
374
|
-
* PowerSync's native retry mechanism will re-attempt on next sync cycle.
|
|
367
|
+
* Kept for backwards compatibility only.
|
|
375
368
|
*/
|
|
376
369
|
declare const DEFAULT_RETRY_CONFIG: RetryConfig;
|
|
377
370
|
/**
|
|
@@ -23,6 +23,11 @@ type EntitySyncState = 'idle' | 'saving' | 'syncing' | 'synced' | 'error';
|
|
|
23
23
|
* - 'offline': No sync at all (fully disconnected)
|
|
24
24
|
*/
|
|
25
25
|
type SyncMode = 'push-pull' | 'pull-only' | 'offline';
|
|
26
|
+
/**
|
|
27
|
+
* Reason why uploads are currently blocked.
|
|
28
|
+
* Used by getUploadBlockReason() to provide detailed status for UI.
|
|
29
|
+
*/
|
|
30
|
+
type UploadBlockReason = 'offline_mode' | 'pull_only_mode' | 'network_unreachable' | 'auto_offline' | 'disconnected' | 'connecting' | 'uploading' | 'none';
|
|
26
31
|
/**
|
|
27
32
|
* CrudEntry represents a pending CRUD operation in the sync queue
|
|
28
33
|
*/
|
|
@@ -209,6 +214,16 @@ interface FailedTransaction {
|
|
|
209
214
|
affectedEntityIds: string[];
|
|
210
215
|
/** Table names involved in this transaction */
|
|
211
216
|
affectedTables: string[];
|
|
217
|
+
/**
|
|
218
|
+
* Estimated next retry time.
|
|
219
|
+
* @deprecated With simplified architecture, PowerSync SDK controls retry timing.
|
|
220
|
+
*/
|
|
221
|
+
nextRetryAt?: Date;
|
|
222
|
+
/**
|
|
223
|
+
* Current backoff delay in ms.
|
|
224
|
+
* @deprecated With simplified architecture, PowerSync SDK controls retry timing.
|
|
225
|
+
*/
|
|
226
|
+
backoffMs?: number;
|
|
212
227
|
}
|
|
213
228
|
/**
|
|
214
229
|
* Result of classifying a Supabase/PostgreSQL error
|
|
@@ -390,4 +405,4 @@ interface IntegrityCheckRow {
|
|
|
390
405
|
[key: string]: unknown;
|
|
391
406
|
}
|
|
392
407
|
|
|
393
|
-
export type { AbstractPowerSyncDatabase as A, CrudEntry as C, DownloadProgress as D, EntitySyncState as E, FailedTransaction as F, IntegrityResult as I, PowerSyncBackendConnector as P, SyncMode as S, TableCacheStats as T, SyncStatus as a, ConnectionHealth as b, StorageInfo as c, StorageQuota as d, SyncMetrics as e, SyncError as f, SyncErrorType as g, CompletedTransaction as h, ClassifiedError as i, CompactResult as j, CacheStats as k, CrudTransaction as l, SqliteTableRow as m, DbStatRow as n, CountRow as o, PageSizeRow as p, PageCountRow as q, FreelistCountRow as r, IntegrityCheckRow as s };
|
|
408
|
+
export type { AbstractPowerSyncDatabase as A, CrudEntry as C, DownloadProgress as D, EntitySyncState as E, FailedTransaction as F, IntegrityResult as I, PowerSyncBackendConnector as P, SyncMode as S, TableCacheStats as T, UploadBlockReason as U, SyncStatus as a, ConnectionHealth as b, StorageInfo as c, StorageQuota as d, SyncMetrics as e, SyncError as f, SyncErrorType as g, CompletedTransaction as h, ClassifiedError as i, CompactResult as j, CacheStats as k, CrudTransaction as l, SqliteTableRow as m, DbStatRow as n, CountRow as o, PageSizeRow as p, PageCountRow as q, FreelistCountRow as r, IntegrityCheckRow as s };
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@pol-studios/powersync",
|
|
3
|
-
"version": "1.0.
|
|
3
|
+
"version": "1.0.30",
|
|
4
4
|
"description": "Enterprise PowerSync integration for offline-first applications",
|
|
5
5
|
"license": "UNLICENSED",
|
|
6
6
|
"type": "module",
|
|
@@ -141,7 +141,7 @@
|
|
|
141
141
|
"commander": "^12.0.0",
|
|
142
142
|
"jiti": "^2.4.0",
|
|
143
143
|
"picocolors": "^1.0.0",
|
|
144
|
-
"@pol-studios/db": "1.0.
|
|
144
|
+
"@pol-studios/db": "1.0.59"
|
|
145
145
|
},
|
|
146
146
|
"peerDependencies": {
|
|
147
147
|
"@powersync/react-native": ">=1.0.8",
|
|
@@ -1 +0,0 @@
|
|
|
1
|
-
{"version":3,"sources":["../src/provider/types.ts"],"sourcesContent":["/**\n * Provider Types for @pol-studios/powersync\n *\n * Defines configuration and context interfaces for the PowerSyncProvider.\n */\n\nimport type { SupabaseClient } from '@supabase/supabase-js';\nimport type { QueryClient } from '@tanstack/react-query';\nimport type { AbstractPowerSyncDatabase, SyncStatus, ConnectionHealth, SyncMetrics, CrudEntry, FailedTransaction, CompletedTransaction, SyncMode, DownloadProgress } from '../core/types';\nimport type { PlatformAdapter } from '../platform/types';\nimport type { ConnectorConfig } from '../connector/types';\nimport type { AttachmentConfig } from '../attachments/types';\nimport type { PolAttachmentQueue } from '../attachments/pol-attachment-queue';\nimport type { SupabaseConnector } from '../connector/supabase-connector';\nimport type { ConflictBus } from '../conflicts/conflict-bus';\n\n// ─── Provider Configuration ──────────────────────────────────────────────────\n\n/**\n * Main configuration for PowerSyncProvider.\n *\n * @template TSchema - The PowerSync schema type\n *\n * @example\n * ```typescript\n * const config: PowerSyncConfig<AppSchema> = {\n * platform: createNativePlatformAdapter(logger),\n * schema: AppSchema,\n * powerSyncUrl: 'https://your-powersync-instance.com',\n * supabaseClient: supabase,\n * dbFilename: 'myapp.db',\n * sync: {\n * autoConnect: true,\n * },\n * };\n * ```\n */\nexport interface PowerSyncConfig<TSchema = unknown> {\n /**\n * Platform adapter for platform-specific operations.\n * Use createNativePlatformAdapter() for React Native or createWebPlatformAdapter() for Web.\n */\n platform: PlatformAdapter;\n\n /**\n * PowerSync schema definition.\n * This defines the tables and their structure for the local database.\n */\n schema: TSchema;\n\n /**\n * PowerSync service URL.\n * @example \"https://your-instance.powersync.journeyapps.com\"\n */\n powerSyncUrl: string;\n\n /**\n * Supabase client instance.\n * Used for authentication and as the backend for CRUD uploads.\n */\n supabaseClient: SupabaseClient;\n\n /**\n * Optional: TanStack Query client for cache invalidation.\n * If provided, will invalidate queries when sync completes.\n */\n queryClient?: QueryClient;\n\n /**\n * Optional: Database filename.\n * @default \"powersync.db\"\n */\n dbFilename?: string;\n\n /**\n * Optional: Connector configuration for custom CRUD handling.\n */\n connector?: ConnectorConfig;\n\n /**\n * Optional: Attachment queue configuration for offline file caching.\n *\n * @example\n * ```typescript\n * attachments: {\n * source: { table: 'photos', idColumn: 'storage_path' },\n * remoteStorage: supabaseStorageAdapter,\n * }\n * ```\n */\n attachments?: AttachmentConfig;\n\n /**\n * Optional: Sync behavior configuration.\n */\n sync?: SyncConfig;\n}\n\n/**\n * Sync behavior configuration.\n */\nexport interface SyncConfig {\n /**\n * Automatically connect when the provider mounts and there's an authenticated session.\n * @default true\n */\n autoConnect?: boolean;\n\n /**\n * Sync interval for periodic sync checks (in milliseconds).\n * Set to 0 to disable periodic sync.\n * @default 0 (disabled)\n */\n syncInterval?: number;\n\n /**\n * Enable health monitoring.\n * @default true\n */\n enableHealthMonitoring?: boolean;\n\n /**\n * Enable metrics collection.\n * @default true\n */\n enableMetrics?: boolean;\n}\n\n// ─── Context Types ───────────────────────────────────────────────────────────\n\n/**\n * Value provided by the main PowerSyncContext.\n *\n * @template TSchema - The PowerSync schema type\n */\nexport interface PowerSyncContextValue<TSchema = unknown> {\n /**\n * The PowerSync database instance.\n * Will be null if not initialized or if initialization failed.\n */\n db: AbstractPowerSyncDatabase | null;\n\n /**\n * The Supabase connector instance.\n * Will be null if not initialized.\n */\n connector: SupabaseConnector | null;\n\n /**\n * The attachment queue instance.\n * Will be null if attachments are not configured or not initialized.\n */\n attachmentQueue: PolAttachmentQueue | null;\n\n /**\n * Whether the PowerSync database is ready for use.\n */\n isReady: boolean;\n\n /**\n * Whether the provider is currently initializing.\n */\n isInitializing: boolean;\n\n /**\n * Whether the attachment queue is ready for use.\n * This is separate from isReady because the attachment queue initializes\n * asynchronously after the database is ready.\n *\n * States:\n * - `false`: Attachment queue is still initializing\n * - `true` with `attachmentQueue !== null`: Ready and available\n * - `true` with `attachmentQueue === null`: Either attachments not configured, OR initialization failed\n */\n attachmentQueueReady: boolean;\n\n /**\n * Error that occurred during initialization, if any.\n */\n error: Error | null;\n\n /**\n * The schema used for this database.\n */\n schema: TSchema;\n\n /**\n * The platform adapter instance.\n */\n platform: PlatformAdapter;\n\n /**\n * The conflict bus for subscribing to conflict events.\n * Use this to wire up conflict UI components.\n */\n conflictBus: ConflictBus;\n}\n\n/**\n * Value provided by SyncStatusContext.\n */\nexport interface SyncStatusContextValue {\n /**\n * Current sync status.\n */\n status: SyncStatus;\n\n /**\n * Pending mutations waiting to be uploaded.\n */\n pendingMutations: CrudEntry[];\n\n /**\n * Number of pending mutations.\n */\n pendingCount: number;\n\n /**\n * Whether data is currently being uploaded to the server.\n * This is the authoritative source for upload activity.\n */\n isUploading: boolean;\n\n /**\n * Whether data is currently being downloaded from the server.\n * This is the authoritative source for download activity.\n */\n isDownloading: boolean;\n\n /**\n * Whether sync is currently paused (offline mode).\n */\n isPaused: boolean;\n\n /**\n * Current sync mode: 'push-pull' (full sync), 'pull-only' (download only), or 'offline' (no sync).\n */\n syncMode: SyncMode;\n\n /**\n * Timestamp of the last successful sync.\n */\n lastSyncedAt: Date | null;\n\n /**\n * Error that occurred during connection, if any.\n */\n connectionError: Error | null;\n\n /**\n * Failed transactions that need attention.\n */\n failedTransactions: FailedTransaction[];\n\n /**\n * Whether there are any upload errors.\n */\n hasUploadErrors: boolean;\n\n /**\n * Count of permanent errors that need user action.\n */\n permanentErrorCount: number;\n\n /**\n * Clear a specific failure by its ID.\n */\n clearFailure: (failureId: string) => void;\n\n /**\n * Clear all failures.\n */\n clearAllFailures: () => void;\n\n /**\n * Completed transactions history.\n */\n completedTransactions: CompletedTransaction[];\n\n /**\n * Clear the completed transaction history.\n */\n clearCompletedHistory: () => void;\n\n /**\n * Set the sync mode.\n * @param mode - The sync mode to set\n */\n setSyncMode: (mode: SyncMode) => Promise<void>;\n\n /**\n * Set the force next upload flag.\n * When true, the next sync cycle will upload regardless of sync mode.\n */\n setForceNextUpload: (force: boolean) => void;\n\n /**\n * Discard a specific pending mutation by its client ID.\n * Uses safe disconnect/reconnect pattern to avoid transaction conflicts.\n * @throws Error if upload is in progress\n */\n discardPendingMutation: (clientId: number) => Promise<void>;\n\n /**\n * Discard all pending mutations.\n * Uses safe disconnect/reconnect pattern to avoid transaction conflicts.\n * @throws Error if upload is in progress\n */\n discardAllPendingMutations: () => Promise<void>;\n\n /**\n * Pause automatic retry of failed uploads.\n * Useful when user is actively resolving conflicts.\n */\n pauseAutoRetry: () => void;\n\n /**\n * Resume automatic retry of failed uploads.\n */\n resumeAutoRetry: () => void;\n}\n\n// ─── Split Context Types (Performance Optimization) ─────────────────────────\n// These focused contexts allow components to subscribe to only the data they need,\n// preventing re-renders when unrelated data changes.\n\n/**\n * Connection state - changes rarely (on connect/disconnect).\n * Use this for components that only need to display connection status.\n */\nexport interface ConnectionStatusContextValue {\n /** Whether connected to the PowerSync service */\n connected: boolean;\n /** Whether currently attempting to connect */\n connecting: boolean;\n /** Whether initial sync has completed */\n hasSynced: boolean;\n /** Timestamp of last successful sync */\n lastSyncedAt: Date | null;\n /** Error that occurred during connection, if any */\n connectionError: Error | null;\n}\n\n/**\n * Sync activity - changes during active sync.\n * Use this for components that display sync progress.\n */\nexport interface SyncActivityContextValue {\n /** Whether data is currently being uploaded to the server */\n uploading: boolean;\n /** Whether data is currently being downloaded from the server */\n downloading: boolean;\n /** Download progress details */\n downloadProgress: DownloadProgress | null;\n}\n\n/**\n * Pending mutations - changes on local writes.\n * Use this for components that display pending upload count or list.\n */\nexport interface PendingMutationsContextValue {\n /** Pending mutations waiting to be uploaded */\n pendingMutations: CrudEntry[];\n /** Number of pending mutations */\n pendingCount: number;\n /** Discard a specific pending mutation by its client ID */\n discardPendingMutation: (clientId: number) => Promise<void>;\n /** Discard all pending mutations */\n discardAllPendingMutations: () => Promise<void>;\n /**\n * Add a pending mutation to the list.\n * Called by mutation hooks (useDbInsert, useDbUpdate, useDbDelete, useDbUpsert)\n * after a successful write. Includes createdAt timestamp for display.\n */\n addPendingMutation: (entry: CrudEntry) => void;\n /**\n * Remove a pending mutation by its entity ID.\n * Called when a transaction completes sync.\n */\n removePendingMutation: (id: string) => void;\n}\n\n/**\n * Failed transactions - changes on failures.\n * Use this for components that display sync errors.\n */\nexport interface FailedTransactionsContextValue {\n /** Failed transactions that need attention */\n failedTransactions: FailedTransaction[];\n /** Whether there are any upload errors */\n hasUploadErrors: boolean;\n /** Count of permanent errors that need user action */\n permanentErrorCount: number;\n /** Clear a specific failure by its ID */\n clearFailure: (failureId: string) => void;\n /** Clear all failures */\n clearAllFailures: () => void;\n /** Pause automatic retry of failed uploads */\n pauseAutoRetry: () => void;\n /** Resume automatic retry of failed uploads */\n resumeAutoRetry: () => void;\n /**\n * Retry a specific failed transaction.\n *\n * This removes the failure from tracking and triggers a sync.\n * The actual CRUD entries remain in PowerSync's queue - this just\n * clears our error tracking so the sync can retry them.\n *\n * If the sync fails to start, the failure is re-recorded.\n */\n retryFailure: (failureId: string) => Promise<void>;\n}\n\n/**\n * Completed transactions - changes on successful syncs.\n * Use this for components that display sync history.\n */\nexport interface CompletedTransactionsContextValue {\n /** Recently completed transactions */\n completedTransactions: CompletedTransaction[];\n /** Clear the completed transaction history */\n clearCompletedHistory: () => void;\n /** Clear a specific completed transaction by ID */\n clearCompletedItem: (completedId: string) => void;\n /**\n * Completed transactions that occurred AFTER the last notification display.\n * Use this for toast/banner notifications to avoid showing stale historical counts.\n */\n newCompletedTransactions: CompletedTransaction[];\n /**\n * Mark notifications as seen (updates the lastNotificationTime).\n * Call this when a sync notification is displayed or auto-dismissed.\n */\n markNotificationsAsSeen: () => void;\n}\n\n/**\n * Sync mode control - changes rarely (user action).\n * Use this for components that display/control sync mode.\n */\nexport interface SyncModeContextValue {\n /** Current sync mode */\n syncMode: SyncMode;\n /** Whether sync is currently paused (offline mode) */\n isPaused: boolean;\n /**\n * Whether offline mode was automatically set due to network loss.\n * When true, sync will auto-resume when network returns.\n * When false (user manually chose offline), sync won't auto-resume.\n */\n isAutoOffline: boolean;\n /**\n * Whether the network is currently reachable.\n * This is used as a gate to block uploads even when syncMode is 'push-pull'.\n */\n networkReachable: boolean;\n /** Set the sync mode (manual - won't auto-resume) */\n setSyncMode: (mode: SyncMode) => Promise<void>;\n /** Set the force next upload flag */\n setForceNextUpload: (force: boolean) => void;\n}\n\n/**\n * Value provided by ConnectionHealthContext.\n */\nexport interface ConnectionHealthContextValue {\n /**\n * Current connection health status.\n */\n health: ConnectionHealth;\n}\n\n/**\n * Value provided by SyncMetricsContext.\n */\nexport interface SyncMetricsContextValue {\n /**\n * Current sync metrics.\n */\n metrics: SyncMetrics;\n}\n\n// ─── Provider Props ──────────────────────────────────────────────────────────\n\n/**\n * Props for the PowerSyncProvider component.\n *\n * @template TSchema - The PowerSync schema type\n */\nexport interface PowerSyncProviderProps<TSchema = unknown> {\n /**\n * PowerSync configuration.\n */\n config: PowerSyncConfig<TSchema>;\n\n /**\n * Child components to render.\n */\n children: React.ReactNode;\n\n /**\n * Called when the database is initialized and ready.\n */\n onReady?: () => void;\n\n /**\n * Called when an error occurs during initialization.\n */\n onError?: (error: Error) => void;\n\n /**\n * Called when sync status changes.\n */\n onSyncStatusChange?: (status: SyncStatus) => void;\n}\n\n// ─── Default Values ──────────────────────────────────────────────────────────\n\n/**\n * Default sync status.\n */\nexport const DEFAULT_SYNC_STATUS: SyncStatus = {\n connected: false,\n connecting: false,\n hasSynced: false,\n lastSyncedAt: null,\n uploading: false,\n downloading: false,\n downloadProgress: null,\n failedTransactions: [],\n hasUploadErrors: false,\n permanentErrorCount: 0\n};\n\n/**\n * Default connection health.\n */\nexport const DEFAULT_CONNECTION_HEALTH: ConnectionHealth = {\n status: 'disconnected',\n latency: null,\n lastHealthCheck: null,\n consecutiveFailures: 0,\n reconnectAttempts: 0\n};\n\n/**\n * Default sync metrics.\n */\nexport const DEFAULT_SYNC_METRICS: SyncMetrics = {\n totalSyncs: 0,\n successfulSyncs: 0,\n failedSyncs: 0,\n lastSyncDuration: null,\n averageSyncDuration: null,\n totalDataDownloaded: 0,\n totalDataUploaded: 0,\n lastError: null\n};\n\n/**\n * Default sync configuration.\n */\nexport const DEFAULT_SYNC_CONFIG: Required<SyncConfig> = {\n autoConnect: true,\n syncInterval: 0,\n enableHealthMonitoring: true,\n enableMetrics: true\n};"],"mappings":";AAygBO,IAAM,sBAAkC;AAAA,EAC7C,WAAW;AAAA,EACX,YAAY;AAAA,EACZ,WAAW;AAAA,EACX,cAAc;AAAA,EACd,WAAW;AAAA,EACX,aAAa;AAAA,EACb,kBAAkB;AAAA,EAClB,oBAAoB,CAAC;AAAA,EACrB,iBAAiB;AAAA,EACjB,qBAAqB;AACvB;AAKO,IAAM,4BAA8C;AAAA,EACzD,QAAQ;AAAA,EACR,SAAS;AAAA,EACT,iBAAiB;AAAA,EACjB,qBAAqB;AAAA,EACrB,mBAAmB;AACrB;AAKO,IAAM,uBAAoC;AAAA,EAC/C,YAAY;AAAA,EACZ,iBAAiB;AAAA,EACjB,aAAa;AAAA,EACb,kBAAkB;AAAA,EAClB,qBAAqB;AAAA,EACrB,qBAAqB;AAAA,EACrB,mBAAmB;AAAA,EACnB,WAAW;AACb;AAKO,IAAM,sBAA4C;AAAA,EACvD,aAAa;AAAA,EACb,cAAc;AAAA,EACd,wBAAwB;AAAA,EACxB,eAAe;AACjB;","names":[]}
|
|
@@ -1 +0,0 @@
|
|
|
1
|
-
{"version":3,"sources":["../src/sync/status-tracker.ts","../src/sync/metrics-collector.ts","../src/sync/health-monitor.ts"],"sourcesContent":["/**\n * Sync Status Tracker for @pol-studios/powersync\n *\n * Tracks and normalizes PowerSync status changes, providing a consistent\n * interface for status updates with throttling support.\n */\n\nimport type { SyncStatus, DownloadProgress, CrudEntry, FailedTransaction, SyncError, CompletedTransaction, SyncMode } from '../core/types';\nimport { generateFailureId } from '../core/errors';\nimport type { LoggerAdapter, AsyncStorageAdapter } from '../platform/types';\nimport type { SyncStatusState, SyncStatusTrackerOptions, PowerSyncRawStatus, Unsubscribe } from './types';\nimport { STORAGE_KEY_PAUSED, STORAGE_KEY_SYNC_MODE, STORAGE_KEY_AUTO_OFFLINE, STATUS_NOTIFY_THROTTLE_MS } from '../core/constants';\nconst STORAGE_KEY_COMPLETED_TRANSACTIONS = '@pol-powersync:completed_transactions';\nconst STORAGE_KEY_FAILED_TRANSACTIONS = '@pol-powersync:failed_transactions';\n\n// Maximum number of completed transactions to retain (oldest entries evicted first)\nconst MAX_COMPLETED_TRANSACTIONS = 1000;\nimport { DEFAULT_SYNC_STATUS } from '../provider/types';\n\n/**\n * Tracks sync status from PowerSync and provides normalized updates.\n *\n * Features:\n * - Normalizes raw PowerSync status to a consistent format\n * - Throttles notifications to prevent UI thrashing\n * - Tracks pending mutations count\n * - Persists and restores paused state\n *\n * @example\n * ```typescript\n * const tracker = new SyncStatusTracker({\n * storage,\n * logger,\n * onStatusChange: (status) => console.log('Status:', status),\n * });\n *\n * // Register with PowerSync\n * db.registerListener({\n * statusChanged: (rawStatus) => tracker.handleStatusChange(rawStatus),\n * });\n *\n * // Get current status\n * const status = tracker.getStatus();\n * ```\n */\nexport class SyncStatusTracker {\n private readonly storage: AsyncStorageAdapter;\n private readonly logger: LoggerAdapter;\n private readonly notifyThrottleMs: number;\n private readonly onStatusChange?: (status: SyncStatus) => void;\n private _state: SyncStatusState;\n private _pendingMutations: CrudEntry[] = [];\n private _lastNotifyTime = 0;\n private _notifyTimer: ReturnType<typeof setTimeout> | null = null;\n private _listeners = new Set<(status: SyncStatus) => void>();\n private _syncModeListeners = new Set<(mode: SyncMode) => void>();\n\n // Force next upload flag for \"Sync Now\" functionality\n private _forceNextUpload = false;\n\n // Network reachability gate - blocks uploads instantly when network is unreachable\n private _networkReachable = true;\n private _networkRestoreTimer: ReturnType<typeof setTimeout> | null = null;\n private readonly _networkRestoreDelayMs = 1500; // 1.5 seconds delay before restoring\n\n // Debounce timer for persist operations to avoid race conditions\n private _persistDebounceTimer: ReturnType<typeof setTimeout> | null = null;\n\n // Track download progress separately to preserve it when offline\n private _lastProgress: DownloadProgress | null = null;\n\n // Failed transaction tracking\n private _failedTransactions: FailedTransaction[] = [];\n private readonly _maxStoredFailures = 50;\n private readonly _failureTTLMs = 24 * 60 * 60 * 1000; // 24 hours\n private _failureListeners = new Set<(failures: FailedTransaction[]) => void>();\n\n // Completed transaction tracking (limited to MAX_COMPLETED_TRANSACTIONS entries)\n private _completedTransactions: CompletedTransaction[] = [];\n private _completedListeners = new Set<(completed: CompletedTransaction[]) => void>();\n\n // Track when notifications were last displayed/dismissed for \"auto-dismiss on display\"\n // This allows filtering completed transactions to only show new ones since last display\n private _lastNotificationTime: number = Date.now();\n\n // Auto-offline flag: tracks whether offline mode was set automatically (network loss)\n // vs manually (user chose offline). Persisted so auto-restore works after app restart.\n private _isAutoOffline = false;\n constructor(storage: AsyncStorageAdapter, logger: LoggerAdapter, options: SyncStatusTrackerOptions = {}) {\n this.storage = storage;\n this.logger = logger;\n this.notifyThrottleMs = options.notifyThrottleMs ?? STATUS_NOTIFY_THROTTLE_MS;\n this.onStatusChange = options.onStatusChange;\n this._state = {\n status: {\n ...DEFAULT_SYNC_STATUS\n },\n syncMode: 'push-pull',\n lastUpdated: new Date()\n };\n }\n\n // ─── Initialization ────────────────────────────────────────────────────────\n\n /**\n * Initialize the tracker by loading persisted state.\n * Includes migration from old isPaused boolean to new syncMode.\n */\n async init(): Promise<void> {\n try {\n // Try to load new sync mode first\n const modeValue = await this.storage.getItem(STORAGE_KEY_SYNC_MODE);\n if (modeValue && ['push-pull', 'pull-only', 'offline'].includes(modeValue)) {\n this._state.syncMode = modeValue as SyncMode;\n this.logger.debug('[StatusTracker] Loaded sync mode:', this._state.syncMode);\n } else {\n // Migrate from old isPaused boolean\n const pausedValue = await this.storage.getItem(STORAGE_KEY_PAUSED);\n if (pausedValue === 'true') {\n this._state.syncMode = 'offline';\n await this.storage.setItem(STORAGE_KEY_SYNC_MODE, 'offline');\n this.logger.debug('[StatusTracker] Migrated isPaused=true to syncMode=offline');\n } else {\n this._state.syncMode = 'push-pull';\n }\n // Clean up old key\n await this.storage.removeItem(STORAGE_KEY_PAUSED);\n }\n } catch (err) {\n this.logger.warn('[StatusTracker] Failed to load sync mode:', err);\n }\n\n // Load persisted auto-offline flag\n try {\n const autoOfflineValue = await this.storage.getItem(STORAGE_KEY_AUTO_OFFLINE);\n this._isAutoOffline = autoOfflineValue === 'true';\n this.logger.debug('[StatusTracker] Loaded isAutoOffline:', this._isAutoOffline);\n } catch (err) {\n this.logger.warn('[StatusTracker] Failed to load auto-offline flag:', err);\n }\n\n // Load persisted completed transactions\n try {\n const completedJson = await this.storage.getItem(STORAGE_KEY_COMPLETED_TRANSACTIONS);\n if (completedJson) {\n const parsed = JSON.parse(completedJson) as Array<Omit<CompletedTransaction, 'completedAt'> & {\n completedAt: string;\n }>;\n this._completedTransactions = parsed.map(item => {\n const remappedEntries = item.entries.map(e => this.remapEntry(e)).filter((e): e is CrudEntry => e !== null);\n return {\n ...item,\n completedAt: new Date(item.completedAt),\n entries: remappedEntries\n };\n }).filter(item => !isNaN(item.completedAt.getTime()) && item.entries.length > 0);\n this.logger.debug('[StatusTracker] Loaded', this._completedTransactions.length, 'completed transactions');\n }\n } catch (err) {\n this.logger.warn('[StatusTracker] Failed to load completed transactions:', err);\n }\n\n // Load persisted failed transactions\n try {\n const failedJson = await this.storage.getItem(STORAGE_KEY_FAILED_TRANSACTIONS);\n if (failedJson) {\n const parsed = JSON.parse(failedJson) as Array<Omit<FailedTransaction, 'firstFailedAt' | 'lastFailedAt' | 'error'> & {\n firstFailedAt: string;\n lastFailedAt: string;\n error: Omit<SyncError, 'timestamp'> & {\n timestamp: string;\n };\n }>;\n this._failedTransactions = parsed.map(item => {\n const remappedEntries = item.entries.map(e => this.remapEntry(e)).filter((e): e is CrudEntry => e !== null);\n return {\n ...item,\n firstFailedAt: new Date(item.firstFailedAt),\n lastFailedAt: new Date(item.lastFailedAt),\n error: {\n ...item.error,\n timestamp: new Date(item.error.timestamp)\n },\n entries: remappedEntries\n };\n }).filter(item => !isNaN(item.firstFailedAt.getTime()) && !isNaN(item.lastFailedAt.getTime()) && item.entries.length > 0);\n this.logger.debug('[StatusTracker] Loaded', this._failedTransactions.length, 'failed transactions');\n }\n } catch (err) {\n this.logger.warn('[StatusTracker] Failed to load failed transactions:', err);\n }\n\n // Clean up any stale failures that were persisted\n this.cleanupStaleFailures();\n }\n\n /**\n * Dispose the tracker and clear timers.\n */\n dispose(): void {\n if (this._notifyTimer) {\n clearTimeout(this._notifyTimer);\n this._notifyTimer = null;\n }\n if (this._persistDebounceTimer) {\n clearTimeout(this._persistDebounceTimer);\n this._persistDebounceTimer = null;\n }\n if (this._networkRestoreTimer) {\n clearTimeout(this._networkRestoreTimer);\n this._networkRestoreTimer = null;\n }\n this._listeners.clear();\n this._syncModeListeners.clear();\n this._failureListeners.clear();\n this._completedListeners.clear();\n }\n\n // ─── Status Getters ────────────────────────────────────────────────────────\n\n /**\n * Get the current sync status.\n */\n getStatus(): SyncStatus {\n const baseStatus = this._state.status;\n\n // Build the status with failed transaction info\n const status: SyncStatus = {\n ...baseStatus,\n failedTransactions: this._failedTransactions,\n hasUploadErrors: this._failedTransactions.length > 0,\n permanentErrorCount: this._failedTransactions.filter(f => f.isPermanent).length\n };\n\n // If offline, use saved progress instead of live (which would be null)\n if (this._state.syncMode === 'offline' && this._lastProgress) {\n return {\n ...status,\n downloadProgress: this._lastProgress\n };\n }\n return status;\n }\n\n // ─── Sync Mode Getters ────────────────────────────────────────────────────────\n\n /**\n * Get the current sync mode.\n */\n getSyncMode(): SyncMode {\n return this._state.syncMode;\n }\n\n /**\n * Check if uploads are allowed based on current sync mode and network reachability.\n */\n canUpload(): boolean {\n return this._networkReachable && this._state.syncMode === 'push-pull';\n }\n\n /**\n * Check if downloads are allowed based on current sync mode.\n */\n canDownload(): boolean {\n return this._state.syncMode !== 'offline';\n }\n\n /**\n * Set the force next upload flag for \"Sync Now\" functionality.\n */\n setForceNextUpload(force: boolean): void {\n this._forceNextUpload = force;\n this.logger.debug('[StatusTracker] Force next upload set to:', force);\n }\n\n /**\n * Clear the force next upload flag.\n * Should be called after all pending uploads have been processed.\n */\n clearForceNextUpload(): void {\n if (this._forceNextUpload) {\n this._forceNextUpload = false;\n this.logger.debug('[StatusTracker] Force next upload flag cleared');\n }\n }\n\n /**\n * Check if upload should proceed, considering force flag and network reachability.\n * NOTE: Does NOT auto-reset the flag - caller must use clearForceNextUpload()\n * after all uploads are complete. This prevents race conditions when\n * PowerSync calls uploadData() multiple times for multiple transactions.\n */\n shouldUpload(): boolean {\n // Force flag bypasses all gates (user explicitly requested sync)\n if (this._forceNextUpload) {\n return true;\n }\n // Network gate - instant block when unreachable (0ms, no timeouts)\n if (!this._networkReachable) {\n return false;\n }\n return this._state.syncMode === 'push-pull';\n }\n\n /**\n * Set network reachability state.\n * - When unreachable: Instantly blocks uploads (0ms)\n * - When reachable: Delayed restore (1-2 seconds) to avoid flickering on brief disconnects\n */\n setNetworkReachable(reachable: boolean): void {\n // Clear any pending restore timer\n if (this._networkRestoreTimer) {\n clearTimeout(this._networkRestoreTimer);\n this._networkRestoreTimer = null;\n }\n if (!reachable) {\n // Instant block when network becomes unreachable\n if (this._networkReachable) {\n this._networkReachable = false;\n this.logger.debug('[StatusTracker] Network unreachable - uploads blocked instantly');\n }\n } else {\n // Delayed restore when network becomes reachable\n if (!this._networkReachable) {\n this.logger.debug('[StatusTracker] Network reachable - scheduling delayed restore');\n this._networkRestoreTimer = setTimeout(() => {\n this._networkRestoreTimer = null;\n this._networkReachable = true;\n this.logger.debug('[StatusTracker] Network restored - uploads enabled');\n }, this._networkRestoreDelayMs);\n }\n }\n }\n\n /**\n * Get current network reachability state.\n */\n isNetworkReachable(): boolean {\n return this._networkReachable;\n }\n\n /**\n * Get pending mutations.\n */\n getPendingMutations(): CrudEntry[] {\n return this._pendingMutations;\n }\n\n /**\n * Get pending mutation count.\n */\n getPendingCount(): number {\n return this._pendingMutations.length;\n }\n\n // ─── Status Updates ────────────────────────────────────────────────────────\n\n /**\n * Handle a raw status update from PowerSync.\n */\n handleStatusChange(rawStatus: PowerSyncRawStatus): void {\n const progress = rawStatus.downloadProgress;\n const dataFlow = rawStatus.dataFlowStatus;\n\n // Build normalized download progress\n let downloadProgress: DownloadProgress | null = null;\n if (progress && progress.totalOperations && progress.totalOperations > 0) {\n downloadProgress = {\n current: progress.downloadedOperations ?? 0,\n target: progress.totalOperations,\n percentage: Math.round((progress.downloadedFraction ?? 0) * 100)\n };\n // Save progress for when paused\n this._lastProgress = downloadProgress;\n }\n\n // Build normalized status (failed transaction fields are added in getStatus())\n const newStatus: SyncStatus = {\n connected: rawStatus.connected ?? false,\n connecting: rawStatus.connecting ?? false,\n hasSynced: rawStatus.hasSynced ?? false,\n lastSyncedAt: rawStatus.lastSyncedAt ?? null,\n uploading: dataFlow?.uploading ?? false,\n downloading: dataFlow?.downloading ?? false,\n downloadProgress,\n // These are computed from _failedTransactions in getStatus()\n failedTransactions: [],\n hasUploadErrors: false,\n permanentErrorCount: 0\n };\n\n // Check if status actually changed\n const changed = this._hasStatusChanged(newStatus);\n this._state = {\n status: newStatus,\n syncMode: this._state.syncMode,\n lastUpdated: new Date()\n };\n if (changed) {\n this._notifyListeners();\n }\n }\n\n /**\n * Update pending mutations from a CRUD transaction.\n */\n updatePendingMutations(mutations: CrudEntry[]): void {\n this._pendingMutations = mutations;\n }\n\n /**\n * Valid sync modes for runtime validation.\n */\n private static readonly VALID_SYNC_MODES: SyncMode[] = ['push-pull', 'pull-only', 'offline'];\n\n /**\n * Set the sync mode.\n */\n async setSyncMode(mode: SyncMode): Promise<void> {\n // Runtime validation\n if (!SyncStatusTracker.VALID_SYNC_MODES.includes(mode)) {\n this.logger.warn('[StatusTracker] Invalid sync mode, ignoring:', mode);\n return;\n }\n if (this._state.syncMode === mode) return;\n const previousMode = this._state.syncMode;\n this._state.syncMode = mode;\n\n // Persist to storage\n try {\n await this.storage.setItem(STORAGE_KEY_SYNC_MODE, mode);\n this.logger.info('[StatusTracker] Sync mode changed:', previousMode, '->', mode);\n } catch (err) {\n this.logger.warn('[StatusTracker] Failed to persist sync mode:', err);\n }\n\n // Notify sync mode listeners\n for (const listener of this._syncModeListeners) {\n try {\n listener(mode);\n } catch (err) {\n this.logger.warn('[StatusTracker] Sync mode listener error:', err);\n }\n }\n this._notifyListeners(true);\n }\n\n // ─── Auto-Offline Management ──────────────────────────────────────────────\n\n /**\n * Get whether offline mode was set automatically (network loss) vs manually.\n * Used to determine if sync should auto-resume when network returns.\n */\n getIsAutoOffline(): boolean {\n return this._isAutoOffline;\n }\n\n /**\n * Set the auto-offline flag and persist it.\n * @param isAuto - true if offline was set automatically, false if user chose offline\n */\n async setIsAutoOffline(isAuto: boolean): Promise<void> {\n if (this._isAutoOffline === isAuto) return;\n this._isAutoOffline = isAuto;\n\n // Persist to storage\n try {\n await this.storage.setItem(STORAGE_KEY_AUTO_OFFLINE, isAuto ? 'true' : 'false');\n this.logger.debug('[StatusTracker] Auto-offline flag changed:', isAuto);\n } catch (err) {\n this.logger.warn('[StatusTracker] Failed to persist auto-offline flag:', err);\n }\n }\n\n // ─── Subscriptions ─────────────────────────────────────────────────────────\n\n /**\n * Subscribe to status changes.\n * @returns Unsubscribe function\n */\n onStatusUpdate(listener: (status: SyncStatus) => void): Unsubscribe {\n this._listeners.add(listener);\n // Immediately call with current status\n listener(this.getStatus());\n return () => {\n this._listeners.delete(listener);\n };\n }\n\n /**\n * Subscribe to sync mode changes.\n * @returns Unsubscribe function\n */\n onSyncModeChange(listener: (mode: SyncMode) => void): Unsubscribe {\n this._syncModeListeners.add(listener);\n listener(this._state.syncMode);\n return () => {\n this._syncModeListeners.delete(listener);\n };\n }\n\n // ─── Failed Transaction Tracking ────────────────────────────────────────────\n\n /**\n * Record a transaction failure.\n * If a failure for the same entries already exists, updates the retry count.\n * Otherwise, creates a new failure record.\n *\n * @param preserveMetadata - Optional. If provided, preserves retryCount and firstFailedAt from a previous failure.\n */\n recordTransactionFailure(entries: CrudEntry[], error: SyncError, isPermanent: boolean, affectedEntityIds: string[], affectedTables: string[], preserveMetadata?: {\n retryCount: number;\n firstFailedAt: Date;\n }): void {\n const now = new Date();\n\n // Normalize entries to plain objects to avoid CrudEntry.toJSON() remapping issues\n const normalizedEntries = this.normalizeEntries(entries);\n const entryIds = normalizedEntries.map(e => e.id).sort().join(',');\n\n // Check if a failure for these entries already exists\n const existingIndex = this._failedTransactions.findIndex(f => {\n const existingIds = f.entries.map(e => e.id).sort().join(',');\n return existingIds === entryIds;\n });\n if (existingIndex !== -1) {\n // Update existing failure\n const existing = this._failedTransactions[existingIndex];\n this._failedTransactions[existingIndex] = {\n ...existing,\n error,\n retryCount: existing.retryCount + 1,\n lastFailedAt: now,\n isPermanent\n };\n } else {\n // Create new failure record\n const newFailure: FailedTransaction = {\n id: generateFailureId(normalizedEntries),\n entries: normalizedEntries,\n error,\n retryCount: preserveMetadata?.retryCount ?? 1,\n firstFailedAt: preserveMetadata?.firstFailedAt ?? now,\n lastFailedAt: now,\n isPermanent,\n affectedEntityIds,\n affectedTables\n };\n this._failedTransactions.push(newFailure);\n\n // Enforce max stored failures (remove oldest)\n if (this._failedTransactions.length > this._maxStoredFailures) {\n this._failedTransactions.sort((a, b) => a.firstFailedAt.getTime() - b.firstFailedAt.getTime());\n this._failedTransactions = this._failedTransactions.slice(-this._maxStoredFailures);\n }\n }\n this._schedulePersist();\n this._notifyFailureListeners();\n this._notifyListeners();\n }\n\n /**\n * Clear a specific failure by ID.\n */\n clearFailure(failureId: string): void {\n const initialLength = this._failedTransactions.length;\n this._failedTransactions = this._failedTransactions.filter(f => f.id !== failureId);\n if (this._failedTransactions.length !== initialLength) {\n this._schedulePersist();\n this._notifyFailureListeners();\n this._notifyListeners();\n }\n }\n\n /**\n * Clear all failures.\n */\n clearAllFailures(): void {\n if (this._failedTransactions.length === 0) return;\n this._failedTransactions = [];\n this._schedulePersist();\n this._notifyFailureListeners();\n this._notifyListeners();\n }\n\n /**\n * Remove a failed transaction from tracking and return its entries.\n * This is a \"pop\" operation - the failure is removed from the list.\n *\n * Note: The actual CRUD entries remain in PowerSync's ps_crud table\n * until successfully uploaded. This just removes from our tracking.\n *\n * @param failureId - The failure ID to remove\n * @returns The CrudEntry[] that were in the failure, or null if not found\n */\n takeFailureForRetry(failureId: string): CrudEntry[] | null {\n const failure = this._failedTransactions.find(f => f.id === failureId);\n if (!failure) {\n this.logger.warn('[StatusTracker] Failure not found for retry:', failureId);\n return null;\n }\n\n // Remove from failed list\n this._failedTransactions = this._failedTransactions.filter(f => f.id !== failureId);\n this._notifyFailureListeners();\n this._schedulePersist();\n this.logger.info('[StatusTracker] Retrieved failure for retry:', failureId, 'entries:', failure.entries.length);\n return failure.entries;\n }\n\n /**\n * Get failures affecting a specific entity.\n */\n getFailuresForEntity(entityId: string): FailedTransaction[] {\n return this._failedTransactions.filter(f => f.affectedEntityIds.includes(entityId));\n }\n\n /**\n * Get all failed transactions.\n */\n getFailedTransactions(): FailedTransaction[] {\n return [...this._failedTransactions];\n }\n\n /**\n * Check if there are any upload errors.\n */\n hasUploadErrors(): boolean {\n return this._failedTransactions.length > 0;\n }\n\n /**\n * Get count of permanent errors.\n */\n getPermanentErrorCount(): number {\n return this._failedTransactions.filter(f => f.isPermanent).length;\n }\n\n /**\n * Subscribe to failure changes.\n * @returns Unsubscribe function\n */\n onFailureChange(listener: (failures: FailedTransaction[]) => void): Unsubscribe {\n this._failureListeners.add(listener);\n // Immediately call with current failures\n listener(this.getFailedTransactions());\n return () => {\n this._failureListeners.delete(listener);\n };\n }\n\n /**\n * Clean up stale failures (older than TTL).\n */\n cleanupStaleFailures(): void {\n const cutoff = Date.now() - this._failureTTLMs;\n const initialLength = this._failedTransactions.length;\n this._failedTransactions = this._failedTransactions.filter(f => f.lastFailedAt.getTime() > cutoff);\n if (this._failedTransactions.length !== initialLength) {\n this.logger.debug(`[StatusTracker] Cleaned up ${initialLength - this._failedTransactions.length} stale failures`);\n this._schedulePersist();\n this._notifyFailureListeners();\n this._notifyListeners();\n }\n }\n\n // ─── Completed Transaction Tracking ────────────────────────────────────────\n\n /**\n * Record a successfully completed transaction.\n * Creates a CompletedTransaction record and adds it to history.\n */\n recordTransactionComplete(entries: CrudEntry[]): void {\n // Normalize entries to plain objects to avoid CrudEntry.toJSON() remapping issues\n const normalizedEntries = this.normalizeEntries(entries);\n\n // Extract unique affected tables\n const affectedTables = [...new Set(normalizedEntries.map(e => e.table))];\n\n // Extract unique affected entity IDs\n const affectedEntityIds = [...new Set(normalizedEntries.map(e => e.id))];\n\n // Generate unique ID\n const id = `completed_${Date.now()}_${Math.random().toString(36).slice(2, 9)}`;\n const completed: CompletedTransaction = {\n id,\n entries: normalizedEntries,\n completedAt: new Date(),\n affectedTables,\n affectedEntityIds\n };\n\n // Add to front of array (most recent first)\n this._completedTransactions.unshift(completed);\n\n // Limit completed history to prevent unbounded memory growth (oldest entries evicted)\n if (this._completedTransactions.length > MAX_COMPLETED_TRANSACTIONS) {\n this._completedTransactions = this._completedTransactions.slice(0, MAX_COMPLETED_TRANSACTIONS);\n }\n this._schedulePersist();\n this._notifyCompletedListeners();\n this.logger.debug(`[StatusTracker] Recorded completed transaction: ${completed.id} (${entries.length} entries)`);\n }\n\n /**\n * Get all completed transactions.\n */\n getCompletedTransactions(): CompletedTransaction[] {\n return [...this._completedTransactions];\n }\n\n /**\n * Clear completed transaction history.\n */\n clearCompletedHistory(): void {\n if (this._completedTransactions.length === 0) return;\n this._completedTransactions = [];\n this._schedulePersist();\n this._notifyCompletedListeners();\n this.logger.debug('[StatusTracker] Cleared completed transaction history');\n }\n\n /**\n * Clear a specific completed transaction by ID.\n */\n clearCompletedItem(completedId: string): void {\n const initialLength = this._completedTransactions.length;\n this._completedTransactions = this._completedTransactions.filter(c => c.id !== completedId);\n if (this._completedTransactions.length !== initialLength) {\n this._schedulePersist();\n this._notifyCompletedListeners();\n this.logger.debug('[StatusTracker] Cleared completed transaction:', completedId);\n }\n }\n\n /**\n * Subscribe to completed transaction changes.\n * @returns Unsubscribe function\n */\n onCompletedChange(listener: (completed: CompletedTransaction[]) => void): Unsubscribe {\n this._completedListeners.add(listener);\n // Immediately call with current completed transactions\n listener(this.getCompletedTransactions());\n return () => {\n this._completedListeners.delete(listener);\n };\n }\n\n // ─── Notification Tracking ─────────────────────────────────────────────────\n\n /**\n * Get completed transactions that occurred AFTER the last notification time.\n * This is used for displaying \"X changes synced\" notifications to avoid\n * showing stale counts from historical completed transactions.\n */\n getNewCompletedTransactions(): CompletedTransaction[] {\n return this._completedTransactions.filter(tx => tx.completedAt.getTime() > this._lastNotificationTime);\n }\n\n /**\n * Mark notifications as seen by updating the last notification time.\n * Call this when the notification is displayed or dismissed.\n */\n markNotificationsAsSeen(): void {\n this._lastNotificationTime = Date.now();\n this.logger.debug('[StatusTracker] Notifications marked as seen');\n // Notify listeners so UI can update (newCompletedTransactions will now be empty)\n this._notifyCompletedListeners();\n }\n\n /**\n * Get the timestamp of when notifications were last displayed/dismissed.\n */\n getLastNotificationTime(): number {\n return this._lastNotificationTime;\n }\n\n // ─── Private Methods ───────────────────────────────────────────────────────\n\n /**\n * Schedule a debounced persist operation.\n * This prevents race conditions from multiple rapid persist calls.\n */\n private _schedulePersist(): void {\n if (this._persistDebounceTimer) {\n clearTimeout(this._persistDebounceTimer);\n }\n this._persistDebounceTimer = setTimeout(() => {\n this._persistDebounceTimer = null;\n this._persistTransactions();\n }, 100); // 100ms debounce\n }\n\n /**\n * Persist completed and failed transactions to storage.\n */\n private async _persistTransactions(): Promise<void> {\n try {\n await Promise.all([this.storage.setItem(STORAGE_KEY_COMPLETED_TRANSACTIONS, JSON.stringify(this._completedTransactions)), this.storage.setItem(STORAGE_KEY_FAILED_TRANSACTIONS, JSON.stringify(this._failedTransactions))]);\n } catch (err) {\n this.logger.warn('[StatusTracker] Failed to persist transactions:', err);\n }\n }\n private _hasStatusChanged(newStatus: SyncStatus): boolean {\n const old = this._state.status;\n return old.connected !== newStatus.connected || old.connecting !== newStatus.connecting || old.hasSynced !== newStatus.hasSynced || old.uploading !== newStatus.uploading || old.downloading !== newStatus.downloading || old.lastSyncedAt?.getTime() !== newStatus.lastSyncedAt?.getTime() || old.downloadProgress?.current !== newStatus.downloadProgress?.current || old.downloadProgress?.target !== newStatus.downloadProgress?.target;\n }\n\n /**\n * Notify all listeners of status changes with throttling.\n *\n * Uses a \"dirty\" flag pattern: when throttled, we schedule a timer\n * but get the CURRENT state when the timer fires, not the stale state\n * from when the timer was scheduled. This ensures rapid state changes\n * during the throttle window aren't lost.\n */\n private _notifyListeners(forceImmediate = false): void {\n const now = Date.now();\n const timeSinceLastNotify = now - this._lastNotifyTime;\n\n // If a timer is already scheduled, don't reschedule - just let it fire\n // and it will pick up the current (latest) state at that time\n if (this._notifyTimer && !forceImmediate) {\n return; // Already scheduled, will get current state when it fires\n }\n if (this._notifyTimer) {\n clearTimeout(this._notifyTimer);\n this._notifyTimer = null;\n }\n const notify = () => {\n this._notifyTimer = null;\n this._lastNotifyTime = Date.now();\n // Get CURRENT state at notification time, not stale state\n const status = this.getStatus();\n\n // Call the main callback\n this.onStatusChange?.(status);\n\n // Call all listeners\n for (const listener of this._listeners) {\n try {\n listener(status);\n } catch (err) {\n this.logger.warn('[StatusTracker] Listener error:', err);\n }\n }\n };\n if (forceImmediate || timeSinceLastNotify >= this.notifyThrottleMs) {\n notify();\n } else {\n const delayMs = this.notifyThrottleMs - timeSinceLastNotify;\n this._notifyTimer = setTimeout(notify, delayMs);\n }\n }\n private _notifyFailureListeners(): void {\n const failures = this.getFailedTransactions();\n for (const listener of this._failureListeners) {\n try {\n listener(failures);\n } catch (err) {\n this.logger.warn('[StatusTracker] Failure listener error:', err);\n }\n }\n }\n private _notifyCompletedListeners(): void {\n const completed = this.getCompletedTransactions();\n for (const listener of this._completedListeners) {\n try {\n listener(completed);\n } catch (err) {\n this.logger.warn('[StatusTracker] Completed listener error:', err);\n }\n }\n }\n\n /**\n * Remap a CrudEntry from persisted JSON (handles toJSON() property remapping).\n * PowerSync's CrudEntry.toJSON() remaps: opData→data, table→type, clientId→op_id, transactionId→tx_id\n *\n * @returns The remapped CrudEntry, or null if critical fields (table, id) are missing\n */\n private remapEntry(entry: any): CrudEntry | null {\n const table = entry.table ?? entry.type;\n const id = entry.id;\n\n // Validate critical fields\n if (!table || typeof table !== 'string') {\n this.logger.warn('[StatusTracker] Invalid CrudEntry: missing or invalid table field', entry);\n return null;\n }\n if (!id || typeof id !== 'string') {\n this.logger.warn('[StatusTracker] Invalid CrudEntry: missing or invalid id field', entry);\n return null;\n }\n return {\n id,\n clientId: entry.clientId ?? entry.op_id ?? 0,\n op: entry.op,\n table,\n opData: entry.opData ?? entry.data,\n transactionId: entry.transactionId ?? entry.tx_id\n };\n }\n\n /**\n * Normalize CrudEntry array to plain objects to avoid CrudEntry.toJSON() remapping issues.\n * PowerSync's CrudEntry.toJSON() remaps property names which breaks deserialization.\n */\n private normalizeEntries(entries: CrudEntry[]): CrudEntry[] {\n return entries.map(e => ({\n id: e.id,\n clientId: e.clientId,\n op: e.op,\n table: e.table,\n opData: e.opData,\n transactionId: e.transactionId\n }));\n }\n}","/**\n * Sync Metrics Collector for @pol-studios/powersync\n *\n * Collects and persists sync operation metrics for monitoring and debugging.\n */\n\nimport type { SyncMetrics, SyncError, SyncErrorType } from '../core/types';\nimport type { AsyncStorageAdapter, LoggerAdapter } from '../platform/types';\nimport type { MetricsCollectorOptions, SyncOperationData, Unsubscribe } from './types';\nimport { STORAGE_KEY_METRICS } from '../core/constants';\nimport { classifyError } from '../core/errors';\nimport { DEFAULT_SYNC_METRICS } from '../provider/types';\n\n/**\n * Collects sync metrics including operation counts, timing, and errors.\n *\n * Features:\n * - Tracks sync operation success/failure rates\n * - Calculates average sync duration\n * - Monitors data transfer amounts\n * - Persists metrics to storage for continuity across sessions\n * - Records last error for debugging\n *\n * @example\n * ```typescript\n * const collector = new MetricsCollector({\n * storage,\n * logger,\n * onMetricsChange: (metrics) => updateUI(metrics),\n * });\n *\n * // Start tracking a sync\n * const startTime = Date.now();\n *\n * // On sync complete\n * collector.recordSync({\n * durationMs: Date.now() - startTime,\n * success: true,\n * operationsDownloaded: 150,\n * });\n *\n * // Get current metrics\n * const metrics = collector.getMetrics();\n * ```\n */\nexport class MetricsCollector {\n private readonly storage: AsyncStorageAdapter;\n private readonly logger: LoggerAdapter;\n private readonly storageKey: string;\n private readonly persistMetrics: boolean;\n private readonly onMetricsChange?: (metrics: SyncMetrics) => void;\n private _metrics: SyncMetrics;\n private _listeners = new Set<(metrics: SyncMetrics) => void>();\n private _initialized = false;\n\n // Track active sync for timing\n private _syncStartTime: number | null = null;\n private _wasSyncing = false;\n\n // Debounced persistence\n private _persistTimeout: ReturnType<typeof setTimeout> | null = null;\n private readonly PERSIST_DEBOUNCE_MS = 100;\n constructor(storage: AsyncStorageAdapter, logger: LoggerAdapter, options: MetricsCollectorOptions = {}) {\n this.storage = storage;\n this.logger = logger;\n this.storageKey = options.storageKey ?? STORAGE_KEY_METRICS;\n this.persistMetrics = options.persistMetrics ?? true;\n this.onMetricsChange = options.onMetricsChange;\n this._metrics = {\n ...DEFAULT_SYNC_METRICS\n };\n }\n\n // ─── Initialization ────────────────────────────────────────────────────────\n\n /**\n * Initialize the collector by loading persisted metrics.\n */\n async init(): Promise<void> {\n if (this._initialized) return;\n try {\n const stored = await this.storage.getItem(this.storageKey);\n if (stored) {\n const parsed = JSON.parse(stored);\n // Restore Date objects\n if (parsed.lastError?.timestamp) {\n parsed.lastError.timestamp = new Date(parsed.lastError.timestamp);\n }\n this._metrics = {\n ...DEFAULT_SYNC_METRICS,\n ...parsed\n };\n this.logger.debug('[MetricsCollector] Loaded persisted metrics');\n }\n } catch (err) {\n this.logger.warn('[MetricsCollector] Failed to load metrics:', err);\n }\n this._initialized = true;\n }\n\n /**\n * Dispose the collector.\n */\n dispose(): void {\n if (this._persistTimeout) {\n clearTimeout(this._persistTimeout);\n this._persistTimeout = null;\n }\n this._listeners.clear();\n }\n\n // ─── Getters ───────────────────────────────────────────────────────────────\n\n /**\n * Get current sync metrics.\n */\n getMetrics(): SyncMetrics {\n return {\n ...this._metrics\n };\n }\n\n // ─── Recording ─────────────────────────────────────────────────────────────\n\n /**\n * Record a completed sync operation.\n */\n async recordSync(data: SyncOperationData): Promise<void> {\n const {\n durationMs,\n success,\n operationsDownloaded,\n operationsUploaded,\n error\n } = data;\n const totalSyncs = this._metrics.totalSyncs + 1;\n const successfulSyncs = this._metrics.successfulSyncs + (success ? 1 : 0);\n const failedSyncs = this._metrics.failedSyncs + (success ? 0 : 1);\n\n // Calculate running average duration (only for successful syncs)\n let averageSyncDuration = this._metrics.averageSyncDuration;\n if (success) {\n if (averageSyncDuration !== null) {\n averageSyncDuration = (averageSyncDuration * (successfulSyncs - 1) + durationMs) / successfulSyncs;\n } else {\n averageSyncDuration = durationMs;\n }\n }\n\n // Estimate data transfer (rough approximation: ~100 bytes per operation)\n const bytesPerOp = 100;\n const downloaded = (operationsDownloaded ?? 0) * bytesPerOp;\n const uploaded = (operationsUploaded ?? 0) * bytesPerOp;\n\n // Build error record if failed\n let lastError: SyncError | null = this._metrics.lastError;\n if (!success && error) {\n const errorType = classifyError(error);\n lastError = {\n type: errorType,\n message: error.message,\n userMessage: error.message,\n // Use original message as user message for metrics\n timestamp: new Date(),\n isPermanent: false\n };\n }\n this._metrics = {\n totalSyncs,\n successfulSyncs,\n failedSyncs,\n lastSyncDuration: durationMs,\n averageSyncDuration: averageSyncDuration !== null ? Math.round(averageSyncDuration) : null,\n totalDataDownloaded: this._metrics.totalDataDownloaded + downloaded,\n totalDataUploaded: this._metrics.totalDataUploaded + uploaded,\n lastError\n };\n this._schedulePersist();\n this._notifyListeners();\n }\n\n /**\n * Record a sync error without a full sync operation.\n */\n async recordError(error: Error): Promise<void> {\n this._metrics = {\n ...this._metrics,\n failedSyncs: this._metrics.failedSyncs + 1,\n lastError: {\n type: classifyError(error),\n message: error.message,\n userMessage: error.message,\n // Use original message as user message for metrics\n timestamp: new Date(),\n isPermanent: false\n }\n };\n this._schedulePersist();\n this._notifyListeners();\n }\n\n /**\n * Record an upload operation.\n */\n async recordUpload(operationCount: number): Promise<void> {\n const bytesPerOp = 100;\n this._metrics = {\n ...this._metrics,\n totalDataUploaded: this._metrics.totalDataUploaded + operationCount * bytesPerOp\n };\n this._schedulePersist();\n this._notifyListeners();\n }\n\n /**\n * Clear all metrics and start fresh.\n */\n async reset(): Promise<void> {\n this._metrics = {\n ...DEFAULT_SYNC_METRICS\n };\n await this._persist();\n this._notifyListeners();\n }\n\n // ─── Sync Timing Helpers ───────────────────────────────────────────────────\n\n /**\n * Called when sync starts (downloading becomes true).\n */\n markSyncStart(): void {\n if (!this._wasSyncing) {\n this._syncStartTime = Date.now();\n this._wasSyncing = true;\n }\n }\n\n /**\n * Called when sync ends (downloading becomes false).\n * Returns the duration if a sync was in progress.\n */\n markSyncEnd(): number | null {\n if (this._wasSyncing && this._syncStartTime !== null) {\n const duration = Date.now() - this._syncStartTime;\n this._syncStartTime = null;\n this._wasSyncing = false;\n return duration;\n }\n return null;\n }\n\n /**\n * Check if sync is currently in progress.\n */\n isSyncInProgress(): boolean {\n return this._wasSyncing;\n }\n\n // ─── Subscriptions ─────────────────────────────────────────────────────────\n\n /**\n * Subscribe to metrics changes.\n * @returns Unsubscribe function\n */\n onMetricsUpdate(listener: (metrics: SyncMetrics) => void): Unsubscribe {\n this._listeners.add(listener);\n listener(this.getMetrics());\n return () => {\n this._listeners.delete(listener);\n };\n }\n\n // ─── Private Methods ───────────────────────────────────────────────────────\n\n private _schedulePersist(): void {\n if (this._persistTimeout) return; // Already scheduled\n\n this._persistTimeout = setTimeout(() => {\n this._persistTimeout = null;\n this._persist().catch(err => {\n this.logger.warn('[MetricsCollector] Persist failed:', err);\n });\n }, this.PERSIST_DEBOUNCE_MS);\n }\n private async _persist(): Promise<void> {\n if (!this.persistMetrics) return;\n try {\n await this.storage.setItem(this.storageKey, JSON.stringify(this._metrics));\n } catch (err) {\n this.logger.warn('[MetricsCollector] Failed to persist metrics:', err);\n }\n }\n private _notifyListeners(): void {\n const metrics = this.getMetrics();\n\n // Call main callback\n this.onMetricsChange?.(metrics);\n\n // Call all listeners\n for (const listener of this._listeners) {\n try {\n listener(metrics);\n } catch (err) {\n this.logger.warn('[MetricsCollector] Listener error:', err);\n }\n }\n }\n}","/**\n * Connection Health Monitor for @pol-studios/powersync\n *\n * Monitors database connection health with periodic checks and latency tracking.\n */\n\nimport type { ConnectionHealth } from '../core/types';\nimport type { AbstractPowerSyncDatabase } from '../core/types';\nimport type { LoggerAdapter } from '../platform/types';\nimport type { HealthMonitorOptions, HealthCheckResult, Unsubscribe } from './types';\nimport { HEALTH_CHECK_INTERVAL_MS, HEALTH_CHECK_TIMEOUT_MS, LATENCY_DEGRADED_THRESHOLD_MS, MAX_CONSECUTIVE_FAILURES } from '../core/constants';\nimport { DEFAULT_CONNECTION_HEALTH } from '../provider/types';\n\n/**\n * Monitors connection health with periodic checks.\n *\n * Features:\n * - Periodic health checks with configurable interval\n * - Latency measurement and degraded state detection\n * - Consecutive failure tracking\n * - Auto-recovery detection\n *\n * @example\n * ```typescript\n * const monitor = new HealthMonitor(db, logger, {\n * checkIntervalMs: 30000,\n * onHealthChange: (health) => {\n * if (health.status === 'degraded') {\n * showWarning('Connection is slow');\n * }\n * },\n * });\n *\n * // Start monitoring\n * monitor.start();\n *\n * // Get current health\n * const health = monitor.getHealth();\n *\n * // Stop when done\n * monitor.stop();\n * ```\n */\nexport class HealthMonitor {\n private readonly logger: LoggerAdapter;\n private readonly checkIntervalMs: number;\n private readonly checkTimeoutMs: number;\n private readonly degradedThresholdMs: number;\n private readonly maxConsecutiveFailures: number;\n private readonly onHealthChange?: (health: ConnectionHealth) => void;\n private _db: AbstractPowerSyncDatabase | null = null;\n private _health: ConnectionHealth;\n private _intervalId: ReturnType<typeof setInterval> | null = null;\n private _listeners = new Set<(health: ConnectionHealth) => void>();\n private _running = false;\n private _paused = false;\n private _pendingTimers = new Set<ReturnType<typeof setTimeout>>();\n constructor(logger: LoggerAdapter, options: HealthMonitorOptions = {}) {\n this.logger = logger;\n this.checkIntervalMs = options.checkIntervalMs ?? HEALTH_CHECK_INTERVAL_MS;\n this.checkTimeoutMs = options.checkTimeoutMs ?? HEALTH_CHECK_TIMEOUT_MS;\n this.degradedThresholdMs = options.degradedThresholdMs ?? LATENCY_DEGRADED_THRESHOLD_MS;\n this.maxConsecutiveFailures = options.maxConsecutiveFailures ?? MAX_CONSECUTIVE_FAILURES;\n this.onHealthChange = options.onHealthChange;\n this._health = {\n ...DEFAULT_CONNECTION_HEALTH\n };\n }\n\n // ─── Lifecycle ─────────────────────────────────────────────────────────────\n\n /**\n * Set the database instance to monitor.\n */\n setDatabase(db: AbstractPowerSyncDatabase | null): void {\n this._db = db;\n if (!db) {\n // Reset health when database is cleared\n this._updateHealth({\n status: 'disconnected',\n latency: null,\n lastHealthCheck: new Date(),\n consecutiveFailures: 0,\n reconnectAttempts: this._health.reconnectAttempts\n });\n }\n }\n\n /**\n * Start the health monitor.\n */\n start(): void {\n if (this._running) return;\n this.logger.info('[HealthMonitor] Starting');\n this._running = true;\n\n // Perform initial check\n this._checkHealth().catch(err => {\n this.logger.warn('[HealthMonitor] Initial check error:', err);\n });\n\n // Set up periodic checks\n this._intervalId = setInterval(() => {\n if (!this._paused) {\n this._checkHealth().catch(err => {\n this.logger.warn('[HealthMonitor] Periodic check error:', err);\n });\n }\n }, this.checkIntervalMs);\n }\n\n /**\n * Stop the health monitor.\n */\n stop(): void {\n if (!this._running) return;\n this.logger.info('[HealthMonitor] Stopping');\n this._running = false;\n if (this._intervalId) {\n clearInterval(this._intervalId);\n this._intervalId = null;\n }\n }\n\n /**\n * Pause health checks temporarily.\n */\n pause(): void {\n this._paused = true;\n // Clear any pending timeout timers to prevent leaks\n this._pendingTimers.forEach(clearTimeout);\n this._pendingTimers.clear();\n this._updateHealth({\n ...this._health,\n status: 'disconnected'\n });\n }\n\n /**\n * Resume health checks.\n */\n resume(): void {\n this._paused = false;\n // Perform immediate check on resume\n this._checkHealth().catch(err => {\n this.logger.warn('[HealthMonitor] Resume check error:', err);\n });\n }\n\n /**\n * Dispose the monitor and clear all resources.\n */\n dispose(): void {\n this.stop();\n // Clear any pending timeout timers to prevent leaks\n this._pendingTimers.forEach(clearTimeout);\n this._pendingTimers.clear();\n this._listeners.clear();\n }\n\n // ─── Getters ───────────────────────────────────────────────────────────────\n\n /**\n * Get current connection health.\n */\n getHealth(): ConnectionHealth {\n return {\n ...this._health\n };\n }\n\n /**\n * Check if the monitor is running.\n */\n isRunning(): boolean {\n return this._running;\n }\n\n // ─── Manual Checks ─────────────────────────────────────────────────────────\n\n /**\n * Perform an immediate health check.\n * @returns The result of the health check\n */\n async checkNow(): Promise<HealthCheckResult> {\n return this._checkHealth();\n }\n\n /**\n * Record a reconnection attempt.\n */\n recordReconnectAttempt(): void {\n this._updateHealth({\n ...this._health,\n reconnectAttempts: this._health.reconnectAttempts + 1\n });\n }\n\n /**\n * Reset reconnection attempts counter (call on successful connection).\n */\n resetReconnectAttempts(): void {\n if (this._health.reconnectAttempts > 0) {\n this._updateHealth({\n ...this._health,\n reconnectAttempts: 0\n });\n }\n }\n\n // ─── Subscriptions ─────────────────────────────────────────────────────────\n\n /**\n * Subscribe to health changes.\n * @returns Unsubscribe function\n */\n onHealthUpdate(listener: (health: ConnectionHealth) => void): Unsubscribe {\n this._listeners.add(listener);\n listener(this.getHealth());\n return () => {\n this._listeners.delete(listener);\n };\n }\n\n // ─── Private Methods ───────────────────────────────────────────────────────\n\n private async _checkHealth(): Promise<HealthCheckResult> {\n if (!this._db || this._paused) {\n return {\n success: false,\n error: new Error('Database not available or paused'),\n timestamp: new Date()\n };\n }\n const startTime = Date.now();\n const timestamp = new Date();\n try {\n // Execute a simple query with timeout\n await this._withTimeout(this._db.get('SELECT 1'), this.checkTimeoutMs);\n const latencyMs = Date.now() - startTime;\n\n // Determine status based on latency\n const status: ConnectionHealth['status'] = latencyMs < this.degradedThresholdMs ? 'healthy' : 'degraded';\n this._updateHealth({\n status,\n latency: latencyMs,\n lastHealthCheck: timestamp,\n consecutiveFailures: 0,\n reconnectAttempts: this._health.reconnectAttempts\n });\n return {\n success: true,\n latencyMs,\n timestamp\n };\n } catch (err) {\n const error = err instanceof Error ? err : new Error(String(err));\n this.logger.warn('[HealthMonitor] Health check failed:', error.message);\n const consecutiveFailures = this._health.consecutiveFailures + 1;\n const status: ConnectionHealth['status'] = consecutiveFailures >= this.maxConsecutiveFailures ? 'disconnected' : 'degraded';\n this._updateHealth({\n status,\n latency: null,\n lastHealthCheck: timestamp,\n consecutiveFailures,\n reconnectAttempts: this._health.reconnectAttempts\n });\n return {\n success: false,\n error,\n timestamp\n };\n }\n }\n private _updateHealth(health: ConnectionHealth): void {\n const changed = this._hasHealthChanged(health);\n this._health = health;\n if (changed) {\n this._notifyListeners();\n }\n }\n private _hasHealthChanged(newHealth: ConnectionHealth): boolean {\n const old = this._health;\n return old.status !== newHealth.status || old.latency !== newHealth.latency || old.consecutiveFailures !== newHealth.consecutiveFailures || old.reconnectAttempts !== newHealth.reconnectAttempts;\n }\n private _notifyListeners(): void {\n const health = this.getHealth();\n\n // Call main callback\n this.onHealthChange?.(health);\n\n // Call all listeners\n for (const listener of this._listeners) {\n try {\n listener(health);\n } catch (err) {\n this.logger.warn('[HealthMonitor] Listener error:', err);\n }\n }\n }\n private _withTimeout<T>(promise: Promise<T>, timeoutMs: number): Promise<T> {\n return new Promise((resolve, reject) => {\n const timer = setTimeout(() => {\n this._pendingTimers.delete(timer);\n reject(new Error(`Health check timeout after ${timeoutMs}ms`));\n }, timeoutMs);\n this._pendingTimers.add(timer);\n promise.then(result => {\n this._pendingTimers.delete(timer);\n clearTimeout(timer);\n resolve(result);\n }, error => {\n this._pendingTimers.delete(timer);\n clearTimeout(timer);\n reject(error);\n });\n });\n }\n}"],"mappings":"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","names":[]}
|