@pol-studios/powersync 1.0.3 → 1.0.6
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/dist/chunk-5FIMA26D.js +1 -0
- package/dist/{chunk-C2RSTGDC.js → chunk-62J2DPKX.js} +497 -43
- package/dist/chunk-62J2DPKX.js.map +1 -0
- package/dist/{chunk-GMFDCVMZ.js → chunk-KCDG2MNP.js} +161 -15
- package/dist/chunk-KCDG2MNP.js.map +1 -0
- package/dist/chunk-PAFBKNL3.js +99 -0
- package/dist/chunk-PAFBKNL3.js.map +1 -0
- package/dist/{chunk-OTJXIRWX.js → chunk-R4YFWQ3Q.js} +1 -1
- package/dist/chunk-R4YFWQ3Q.js.map +1 -0
- package/dist/connector/index.d.ts +2 -1
- package/dist/connector/index.js +4 -1
- package/dist/failed-upload-store-C0cLxxPz.d.ts +33 -0
- package/dist/{index-Cb-NI0Ct.d.ts → index-l3iL9Jte.d.ts} +72 -1
- package/dist/index.d.ts +142 -4
- package/dist/index.js +52 -6
- package/dist/index.native.d.ts +4 -3
- package/dist/index.native.js +52 -6
- package/dist/index.web.d.ts +4 -3
- package/dist/index.web.js +52 -6
- package/dist/provider/index.d.ts +258 -3
- package/dist/provider/index.js +28 -3
- package/dist/sync/index.d.ts +1 -0
- package/dist/sync/index.js +9 -3
- package/package.json +1 -1
- package/dist/chunk-7JQZBZ5N.js +0 -1
- package/dist/chunk-C2RSTGDC.js.map +0 -1
- package/dist/chunk-GMFDCVMZ.js.map +0 -1
- package/dist/chunk-OTJXIRWX.js.map +0 -1
- /package/dist/{chunk-7JQZBZ5N.js.map → chunk-5FIMA26D.js.map} +0 -0
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"sources":["../src/connector/types.ts","../src/conflicts/detect.ts","../src/utils/retry.ts","../src/connector/supabase-connector.ts"],"sourcesContent":["/**\n * Connector Types for @pol-studios/powersync\n *\n * Defines interfaces for PowerSync backend connectors.\n */\n\nimport type { SupabaseClient } from '@supabase/supabase-js';\nimport type { AbstractPowerSyncDatabase, ClassifiedError, CrudEntry } from '../core/types';\nimport type { LoggerAdapter } from '../platform/types';\nimport type { ConflictHandler, ConflictDetectionConfig } from '../conflicts/types';\nimport type { ConflictBus } from '../conflicts/conflict-bus';\n\n// Re-export ConflictBus type for convenience\nexport type { ConflictBus } from '../conflicts/conflict-bus';\n\n// ─── Connector Configuration ─────────────────────────────────────────────────\n\n/**\n * Options for creating a SupabaseConnector\n */\nexport interface SupabaseConnectorOptions {\n /** Supabase client instance */\n supabaseClient: SupabaseClient;\n /** PowerSync service URL */\n powerSyncUrl: string;\n /**\n * Optional: Custom schema routing function.\n * Determines which Supabase schema a table belongs to.\n * @default Returns 'public' for all tables\n */\n schemaRouter?: SchemaRouter;\n /**\n * Optional: Custom CRUD handler for complex mutations.\n * Allows overriding default upsert/update/delete behavior.\n */\n crudHandler?: CrudHandler;\n /** Logger for debugging */\n logger?: LoggerAdapter;\n /** Called when a transaction is successfully uploaded */\n onTransactionSuccess?: (entries: CrudEntry[]) => void;\n /** Called when a transaction fails to upload */\n onTransactionFailure?: (entries: CrudEntry[], error: Error, classified: ClassifiedError) => void;\n /** Called when a transaction is fully completed (after transaction.complete()) */\n onTransactionComplete?: (entries: CrudEntry[]) => void;\n /** Function to check if upload should proceed. Used for sync mode gating. */\n shouldUpload?: () => boolean;\n /**\n * Optional: Configuration for version-based conflict detection.\n * When enabled, checks for conflicts before uploading changes.\n */\n conflictDetection?: ConflictDetectionConfig;\n /**\n * Optional: Handler for conflict resolution.\n * If not provided, conflicts are logged and upload proceeds.\n */\n conflictHandler?: ConflictHandler;\n /**\n * Optional: Event bus for publishing conflict events.\n * Use this to notify the UI layer about detected conflicts.\n */\n conflictBus?: ConflictBus;\n /**\n * Optional: Configuration for retry behavior on upload failures.\n * Allows customizing retry attempts, delays, and backoff for different error types.\n * @default DEFAULT_RETRY_CONFIG\n */\n retryConfig?: Partial<RetryConfig>;\n}\n\n/**\n * Configuration passed to PowerSyncProvider for connector setup\n */\nexport interface ConnectorConfig {\n /**\n * Custom schema routing function.\n * Determines which Supabase schema a table belongs to.\n * @default Returns 'public' for all tables\n *\n * @example\n * ```typescript\n * schemaRouter: (table) => {\n * if (['Profile', 'Comment'].includes(table)) return 'core';\n * return 'public';\n * }\n * ```\n */\n schemaRouter?: SchemaRouter;\n\n /**\n * Custom CRUD handler for complex mutations.\n * @default Uses standard upsert/update/delete operations\n */\n crudHandler?: CrudHandler;\n\n /**\n * Token refresh configuration.\n */\n tokenRefresh?: {\n /** Refresh token when it expires within this many seconds (default: 60) */\n refreshThresholdSeconds?: number;\n };\n\n /**\n * Optional retry configuration for upload failures.\n * Allows customizing retry attempts, delays, and backoff for different error types.\n * @default DEFAULT_RETRY_CONFIG\n */\n retryConfig?: Partial<RetryConfig>;\n}\n\n// ─── Schema Routing ──────────────────────────────────────────────────────────\n\n/**\n * Function that determines which Supabase schema a table belongs to.\n *\n * @param tableName - The name of the table\n * @returns The schema name (e.g., 'public', 'core')\n */\nexport type SchemaRouter = (tableName: string) => string;\n\n/**\n * Default schema router that returns 'public' for all tables\n */\nexport const defaultSchemaRouter: SchemaRouter = () => 'public';\n\n// ─── CRUD Handling ───────────────────────────────────────────────────────────\n\n/**\n * Custom handler for CRUD operations.\n *\n * Return `true` from a handler to indicate the operation was handled.\n * Return `false` to fall back to default behavior.\n */\nexport interface CrudHandler {\n /**\n * Handle a PUT operation (insert or replace).\n * @param entry - The CRUD entry\n * @param supabase - Supabase client\n * @param schema - The resolved schema for this table\n * @returns true if handled, false to use default behavior\n */\n handlePut?(entry: CrudEntry, supabase: SupabaseClient, schema: string): Promise<boolean>;\n\n /**\n * Handle a PATCH operation (update).\n * @param entry - The CRUD entry\n * @param supabase - Supabase client\n * @param schema - The resolved schema for this table\n * @returns true if handled, false to use default behavior\n */\n handlePatch?(entry: CrudEntry, supabase: SupabaseClient, schema: string): Promise<boolean>;\n\n /**\n * Handle a DELETE operation.\n * @param entry - The CRUD entry\n * @param supabase - Supabase client\n * @param schema - The resolved schema for this table\n * @returns true if handled, false to use default behavior\n */\n handleDelete?(entry: CrudEntry, supabase: SupabaseClient, schema: string): Promise<boolean>;\n}\n\n// ─── Credentials ─────────────────────────────────────────────────────────────\n\n/**\n * Credentials returned by fetchCredentials\n */\nexport interface PowerSyncCredentials {\n /** PowerSync service endpoint URL */\n endpoint: string;\n /** JWT token for authentication */\n token: string;\n /** When the token expires */\n expiresAt?: Date;\n}\n\n// ─── Retry Configuration ─────────────────────────────────────────────────────\n\n/**\n * Configuration for a single retry category\n */\nexport interface RetryStrategyConfig {\n /** Maximum number of retry attempts */\n maxRetries: number;\n /** Initial delay in milliseconds */\n baseDelayMs: number;\n /** Maximum delay cap in milliseconds */\n maxDelayMs: number;\n /** Multiplier for exponential backoff */\n backoffMultiplier: number;\n}\n\n/**\n * Full retry configuration for the connector\n */\nexport interface RetryConfig {\n /** Retry config for transient errors (network, server 5xx) */\n transient: RetryStrategyConfig;\n /** Retry config for permanent errors (RLS, validation, constraints) */\n permanent: RetryStrategyConfig;\n}\n\n/**\n * Default retry configuration\n */\nexport const DEFAULT_RETRY_CONFIG: RetryConfig = {\n transient: {\n maxRetries: 3,\n baseDelayMs: 1000,\n maxDelayMs: 30000,\n backoffMultiplier: 2\n },\n permanent: {\n maxRetries: 2,\n baseDelayMs: 5000,\n maxDelayMs: 300000,\n backoffMultiplier: 3\n }\n};\n\n// ─── Re-export from Core ─────────────────────────────────────────────────────\n\n// Re-export PowerSyncBackendConnector from core/types to maintain API compatibility\nexport type { PowerSyncBackendConnector } from '../core/types';","/**\n * Conflict Detection for @pol-studios/powersync\n *\n * Provides version-based conflict detection using AuditLog attribution.\n * Only queries AuditLog when version mismatch is detected.\n */\n\nimport type { SupabaseClient } from '@supabase/supabase-js';\nimport type { AbstractPowerSyncDatabase } from '../core/types';\nimport type { FieldConflict, ConflictCheckResult, ConflictDetectionConfig } from './types';\n\n/**\n * Regex to validate table names and prevent SQL injection.\n * Only allows alphanumeric characters and underscores, starting with a letter or underscore.\n */\nconst TABLE_NAME_REGEX = /^[a-zA-Z_][a-zA-Z0-9_]*$/;\n\n/**\n * Validates a table name to prevent SQL injection.\n * @throws Error if the table name is invalid\n */\nfunction validateTableName(table: string): void {\n if (!TABLE_NAME_REGEX.test(table)) {\n throw new Error(`Invalid table name: ${table}`);\n }\n}\nconst DEFAULT_IGNORED_FIELDS = ['updatedAt', 'createdAt', '_version', 'id'];\n\n/**\n * Detect conflicts between local pending changes and server state.\n *\n * Uses a two-step approach:\n * 1. Version match (local._version == server._version) → Sync immediately\n * 2. Version mismatch → Query AuditLog for field changes and attribution\n *\n * @param table - The table name\n * @param recordId - The record ID\n * @param localVersion - Version number from local SQLite\n * @param serverVersion - Version number from server (fetched separately)\n * @param pendingChanges - Fields with local changes to sync\n * @param supabase - Supabase client for AuditLog queries\n * @param config - Optional detection configuration\n * @returns Conflict check result with field-level details\n */\nexport async function detectConflicts(table: string, recordId: string, localVersion: number, serverVersion: number, pendingChanges: Record<string, unknown>, supabase: SupabaseClient, config?: ConflictDetectionConfig): Promise<ConflictCheckResult> {\n const ignoredFields = new Set([...DEFAULT_IGNORED_FIELDS, ...(config?.ignoredFields ?? [])]);\n\n // Filter out ignored fields from pending changes\n const filteredPendingChanges: Record<string, unknown> = {};\n for (const [field, value] of Object.entries(pendingChanges)) {\n if (!ignoredFields.has(field)) {\n filteredPendingChanges[field] = value;\n }\n }\n\n // Step 1: Version match = no conflict possible\n if (localVersion === serverVersion) {\n return {\n hasConflict: false,\n conflicts: [],\n nonConflictingChanges: Object.keys(filteredPendingChanges),\n table,\n recordId\n };\n }\n\n // Step 2: Version mismatch - query AuditLog for changes since our version\n const {\n data: auditLogs,\n error\n } = await supabase.schema('core').from('AuditLog').select('oldRecord, newRecord, changeBy, changeAt').eq('tableName', table).eq('recordId_text', recordId).order('changeAt', {\n ascending: false\n }).limit(20); // Recent changes should be sufficient\n\n if (error) {\n console.warn('[detectConflicts] Failed to query AuditLog:', error);\n // On error, assume no conflict and let sync proceed\n // (Server will reject if there's a real issue)\n return {\n hasConflict: false,\n conflicts: [],\n nonConflictingChanges: Object.keys(filteredPendingChanges),\n table,\n recordId\n };\n }\n\n // Build map of server-changed fields with attribution\n // Key: field name, Value: most recent change info\n const serverChanges = new Map<string, {\n newValue: unknown;\n changedBy: string | null;\n changedAt: Date;\n }>();\n for (const log of auditLogs ?? []) {\n const oldRec = log.oldRecord as Record<string, unknown> | null;\n const newRec = log.newRecord as Record<string, unknown> | null;\n if (!oldRec || !newRec) continue;\n for (const [field, newValue] of Object.entries(newRec)) {\n // Skip ignored fields\n if (ignoredFields.has(field)) continue;\n\n // Only track if field actually changed AND we don't already have a more recent change\n if (oldRec[field] !== newValue && !serverChanges.has(field)) {\n serverChanges.set(field, {\n newValue,\n changedBy: log.changeBy as string | null,\n changedAt: new Date(log.changeAt as string)\n });\n }\n }\n }\n\n // Compare pending changes against server changes\n const conflicts: FieldConflict[] = [];\n const nonConflictingChanges: string[] = [];\n for (const [field, localValue] of Object.entries(filteredPendingChanges)) {\n if (serverChanges.has(field)) {\n // This field was changed on server - conflict!\n const serverChange = serverChanges.get(field)!;\n conflicts.push({\n field,\n localValue,\n serverValue: serverChange.newValue,\n changedBy: serverChange.changedBy,\n changedAt: serverChange.changedAt\n });\n } else {\n // Field wasn't changed on server - safe to sync\n nonConflictingChanges.push(field);\n }\n }\n return {\n hasConflict: conflicts.length > 0,\n conflicts,\n nonConflictingChanges,\n table,\n recordId\n };\n}\n\n/**\n * Check if a table has a _version column for conflict detection.\n *\n * @param table - The table name\n * @param db - PowerSync database instance\n * @returns True if the table has version tracking\n */\nexport async function hasVersionColumn(table: string, db: AbstractPowerSyncDatabase): Promise<boolean> {\n try {\n // Validate table name to prevent SQL injection\n validateTableName(table);\n\n // Query the PowerSync internal schema for column info\n const result = await db.getAll<{\n name: string;\n }>(`PRAGMA table_info(\"${table}\")`);\n return result.some(col => col.name === '_version');\n } catch {\n return false;\n }\n}\n\n/**\n * Fetch the current server version for a record.\n *\n * @param table - The table name\n * @param recordId - The record ID\n * @param schema - The Supabase schema (default: 'public')\n * @param supabase - Supabase client\n * @returns The server version number, or null if record not found\n */\nexport async function fetchServerVersion(table: string, recordId: string, schema: string, supabase: SupabaseClient): Promise<number | null> {\n const query = schema === 'public' ? supabase.from(table) : (supabase.schema(schema) as unknown as ReturnType<typeof supabase.schema>).from(table);\n const {\n data,\n error\n } = await query.select('_version').eq('id', recordId).single();\n if (error || !data) {\n return null;\n }\n return (data as {\n _version?: number;\n })._version ?? null;\n}\n\n/**\n * Get the local version for a record from PowerSync SQLite.\n *\n * @param table - The table name\n * @param recordId - The record ID\n * @param db - PowerSync database instance\n * @returns The local version number, or null if not found\n */\nexport async function getLocalVersion(table: string, recordId: string, db: AbstractPowerSyncDatabase): Promise<number | null> {\n // Validate table name to prevent SQL injection\n validateTableName(table);\n const result = await db.get<{\n _version?: number;\n }>(`SELECT _version FROM \"${table}\" WHERE id = ?`, [recordId]);\n return result?._version ?? null;\n}","/**\n * Exponential backoff retry utilities for resilient network operations.\n *\n * Provides configurable retry logic with exponential backoff, jitter,\n * and abort signal support for cancellation.\n */\n\n/**\n * Configuration for exponential backoff retry behavior.\n */\nexport interface BackoffConfig {\n /** Maximum number of retry attempts (0 = no retries, just one attempt) */\n maxRetries: number;\n /** Base delay in milliseconds before first retry */\n baseDelayMs: number;\n /** Maximum delay cap in milliseconds */\n maxDelayMs: number;\n /** Multiplier applied to delay for each subsequent retry (typically 2) */\n backoffMultiplier: number;\n}\n\n/**\n * Options for the withExponentialBackoff function.\n */\nexport interface BackoffOptions {\n /** Optional AbortSignal for cancellation support */\n signal?: AbortSignal;\n /** Callback invoked before each retry attempt */\n onRetry?: (attempt: number, delay: number, error: Error) => void;\n}\n\n/**\n * Error thrown when an operation is aborted via AbortSignal.\n */\nexport class AbortError extends Error {\n constructor(message = 'Operation aborted') {\n super(message);\n this.name = 'AbortError';\n }\n}\n\n/**\n * Error thrown when all retry attempts have been exhausted.\n */\nexport class RetryExhaustedError extends Error {\n /** The last error that caused the final retry to fail */\n readonly cause: Error;\n /** Total number of attempts made */\n readonly attempts: number;\n constructor(cause: Error, attempts: number) {\n super(`Retry exhausted after ${attempts} attempt(s): ${cause.message}`);\n this.name = 'RetryExhaustedError';\n this.cause = cause;\n this.attempts = attempts;\n }\n}\n\n/**\n * Calculates the delay for a given retry attempt using exponential backoff.\n *\n * Formula: min(baseDelay * (multiplier ^ attempt), maxDelay)\n *\n * @param attempt - The current attempt number (0-indexed)\n * @param config - Backoff configuration\n * @returns Delay in milliseconds (without jitter)\n *\n * @example\n * ```ts\n * const config = { baseDelayMs: 1000, maxDelayMs: 30000, backoffMultiplier: 2 };\n * calculateBackoffDelay(0, config); // 1000\n * calculateBackoffDelay(1, config); // 2000\n * calculateBackoffDelay(2, config); // 4000\n * calculateBackoffDelay(5, config); // 30000 (capped at maxDelayMs)\n * ```\n */\nexport function calculateBackoffDelay(attempt: number, config: Pick<BackoffConfig, 'baseDelayMs' | 'maxDelayMs' | 'backoffMultiplier'>): number {\n const {\n baseDelayMs,\n maxDelayMs,\n backoffMultiplier\n } = config;\n\n // Ensure non-negative attempt number\n const safeAttempt = Math.max(0, attempt);\n\n // Calculate exponential delay\n const exponentialDelay = baseDelayMs * Math.pow(backoffMultiplier, safeAttempt);\n\n // Cap at maxDelayMs\n return Math.min(exponentialDelay, maxDelayMs);\n}\n\n/**\n * Adds jitter (±10%) to a delay value to prevent thundering herd.\n *\n * @param delay - Base delay in milliseconds\n * @returns Delay with random jitter applied\n */\nexport function addJitter(delay: number): number {\n // Generate random factor between 0.9 and 1.1 (±10%)\n const jitterFactor = 0.9 + Math.random() * 0.2;\n return Math.round(delay * jitterFactor);\n}\n\n/**\n * Sleep utility that respects AbortSignal for cancellation.\n *\n * @param ms - Duration to sleep in milliseconds\n * @param signal - Optional AbortSignal for cancellation\n * @returns Promise that resolves after the delay or rejects if aborted\n * @throws {AbortError} If the signal is aborted during sleep\n *\n * @example\n * ```ts\n * const controller = new AbortController();\n *\n * // Sleep for 1 second\n * await sleep(1000);\n *\n * // Sleep with cancellation support\n * await sleep(1000, controller.signal);\n *\n * // Cancel the sleep\n * controller.abort();\n * ```\n */\nexport function sleep(ms: number, signal?: AbortSignal): Promise<void> {\n return new Promise((resolve, reject) => {\n // Check if already aborted\n if (signal?.aborted) {\n reject(new AbortError());\n return;\n }\n\n // Handle zero or negative duration\n if (ms <= 0) {\n resolve();\n return;\n }\n let timeoutId: ReturnType<typeof setTimeout> | undefined;\n const handleAbort = () => {\n if (timeoutId !== undefined) {\n clearTimeout(timeoutId);\n }\n reject(new AbortError());\n };\n\n // Set up abort listener if signal provided\n if (signal) {\n signal.addEventListener('abort', handleAbort, {\n once: true\n });\n }\n timeoutId = setTimeout(() => {\n // Clean up abort listener\n if (signal) {\n signal.removeEventListener('abort', handleAbort);\n }\n resolve();\n }, ms);\n });\n}\n\n/**\n * Default backoff configuration suitable for most network operations.\n */\nexport const DEFAULT_BACKOFF_CONFIG: BackoffConfig = {\n maxRetries: 3,\n baseDelayMs: 1000,\n maxDelayMs: 30000,\n backoffMultiplier: 2\n};\n\n/**\n * Executes a function with exponential backoff retry logic.\n *\n * Retries the provided function on failure using exponential backoff with jitter.\n * Supports cancellation via AbortSignal and provides hooks for retry observation.\n *\n * @param fn - Async function to execute with retry logic\n * @param config - Backoff configuration (maxRetries, delays, multiplier)\n * @param options - Optional abort signal and retry callback\n * @returns Promise resolving to the function result\n * @throws {AbortError} If operation is aborted via signal\n * @throws {RetryExhaustedError} If all retry attempts fail\n *\n * @example\n * ```ts\n * const controller = new AbortController();\n *\n * const result = await withExponentialBackoff(\n * async () => {\n * const response = await fetch('https://api.example.com/data');\n * if (!response.ok) throw new Error('Request failed');\n * return response.json();\n * },\n * {\n * maxRetries: 3,\n * baseDelayMs: 1000,\n * maxDelayMs: 30000,\n * backoffMultiplier: 2,\n * },\n * {\n * signal: controller.signal,\n * onRetry: (attempt, delay, error) => {\n * console.log(`Retry ${attempt} in ${delay}ms: ${error.message}`);\n * },\n * }\n * );\n * ```\n */\nexport async function withExponentialBackoff<T>(fn: () => Promise<T>, config: BackoffConfig, options?: BackoffOptions): Promise<T> {\n const {\n maxRetries,\n baseDelayMs,\n maxDelayMs,\n backoffMultiplier\n } = config;\n const {\n signal,\n onRetry\n } = options ?? {};\n\n // Check for immediate abort\n if (signal?.aborted) {\n throw new AbortError();\n }\n\n // Validate config\n const safeMaxRetries = Math.max(0, Math.floor(maxRetries));\n const totalAttempts = safeMaxRetries + 1; // Initial attempt + retries\n\n let lastError: Error | undefined;\n for (let attempt = 0; attempt < totalAttempts; attempt++) {\n // Check for abort before each attempt\n if (signal?.aborted) {\n throw new AbortError();\n }\n try {\n return await fn();\n } catch (error) {\n lastError = error instanceof Error ? error : new Error(String(error));\n\n // Check if this was the last attempt\n const isLastAttempt = attempt === totalAttempts - 1;\n if (isLastAttempt) {\n break;\n }\n\n // Check for abort before sleeping\n if (signal?.aborted) {\n throw new AbortError();\n }\n\n // Calculate delay with jitter\n const baseDelay = calculateBackoffDelay(attempt, {\n baseDelayMs,\n maxDelayMs,\n backoffMultiplier\n });\n const delayWithJitter = addJitter(baseDelay);\n\n // Notify about upcoming retry\n onRetry?.(attempt + 1, delayWithJitter, lastError);\n\n // Wait before retry\n await sleep(delayWithJitter, signal);\n }\n }\n\n // All attempts exhausted\n throw new RetryExhaustedError(lastError!, totalAttempts);\n}","/**\n * Supabase Connector for PowerSync\n *\n * A generic, configurable connector that handles:\n * - Authentication with Supabase JWT tokens\n * - Uploading local changes back to Supabase\n * - Schema routing for multi-schema databases\n * - Custom CRUD handling for complex operations\n * - Version-based conflict detection (when enabled)\n */\n\nimport type { SupabaseClient } from '@supabase/supabase-js';\nimport type { AbstractPowerSyncDatabase, ClassifiedError, CrudEntry } from '../core/types';\nimport type { LoggerAdapter } from '../platform/types';\nimport type { SupabaseConnectorOptions, PowerSyncBackendConnector, PowerSyncCredentials, SchemaRouter, CrudHandler, RetryConfig } from './types';\nimport type { ConflictBus } from '../conflicts/conflict-bus';\nimport type { ConflictHandler, ConflictDetectionConfig, ConflictCheckResult, ConflictResolution } from '../conflicts/types';\nimport { defaultSchemaRouter, DEFAULT_RETRY_CONFIG } from './types';\nimport { classifySupabaseError } from '../core/errors';\nimport { detectConflicts, fetchServerVersion, getLocalVersion, hasVersionColumn } from '../conflicts/detect';\nimport { withExponentialBackoff, calculateBackoffDelay } from '../utils/retry';\nimport { failedUploadStore, type FailedUpload } from '../sync/failed-upload-store';\n\n/**\n * Update type enum matching @powersync/common\n */\nenum UpdateType {\n PUT = 'PUT',\n PATCH = 'PATCH',\n DELETE = 'DELETE',\n}\n\n/**\n * Generic Supabase connector for PowerSync.\n *\n * This connector handles authentication and CRUD uploads to Supabase.\n * It supports configurable schema routing and custom CRUD handlers\n * for complex use cases.\n *\n * @example Basic usage\n * ```typescript\n * const connector = new SupabaseConnector({\n * supabaseClient: supabase,\n * powerSyncUrl: 'https://your-powersync-instance.com',\n * });\n * ```\n *\n * @example With schema routing\n * ```typescript\n * const connector = new SupabaseConnector({\n * supabaseClient: supabase,\n * powerSyncUrl: 'https://your-powersync-instance.com',\n * schemaRouter: (table) => {\n * if (['Profile', 'Comment', 'CommentSection'].includes(table)) {\n * return 'core';\n * }\n * return 'public';\n * },\n * });\n * ```\n *\n * @example With custom CRUD handler\n * ```typescript\n * const connector = new SupabaseConnector({\n * supabaseClient: supabase,\n * powerSyncUrl: 'https://your-powersync-instance.com',\n * crudHandler: {\n * async handlePut(entry, supabase, schema) {\n * // Custom handling for specific tables\n * if (entry.table === 'SpecialTable') {\n * await myCustomUpsert(entry);\n * return true; // Handled\n * }\n * return false; // Use default\n * },\n * },\n * });\n * ```\n */\nexport class SupabaseConnector implements PowerSyncBackendConnector {\n private readonly supabase: SupabaseClient;\n private readonly powerSyncUrl: string;\n private readonly schemaRouter: SchemaRouter;\n private readonly crudHandler?: CrudHandler;\n private readonly logger?: LoggerAdapter;\n private readonly onTransactionSuccess?: (entries: CrudEntry[]) => void;\n private readonly onTransactionFailure?: (entries: CrudEntry[], error: Error, classified: ClassifiedError) => void;\n private readonly onTransactionComplete?: (entries: CrudEntry[]) => void;\n private readonly shouldUploadFn?: () => boolean;\n\n // Conflict detection configuration\n private readonly conflictDetection?: ConflictDetectionConfig;\n private readonly conflictHandler?: ConflictHandler;\n private readonly conflictBus?: ConflictBus;\n\n // Cache for version column existence checks (table -> hasVersionColumn)\n private versionColumnCache = new Map<string, boolean>();\n\n // Active project IDs for scoped sync (optional feature)\n private activeProjectIds: string[] = [];\n\n // Store resolutions for retry - when PowerSync retries, we apply stored resolutions\n // instead of re-detecting conflicts that the user already resolved\n private resolvedConflicts = new Map<string, ConflictResolution>();\n\n // Cleanup function for resolution listener subscription\n private unsubscribeResolution?: () => void;\n\n // Retry configuration\n private retryConfig: RetryConfig;\n private autoRetryPaused: boolean = false;\n constructor(options: SupabaseConnectorOptions) {\n this.supabase = options.supabaseClient;\n this.powerSyncUrl = options.powerSyncUrl;\n this.schemaRouter = options.schemaRouter ?? defaultSchemaRouter;\n this.crudHandler = options.crudHandler;\n this.logger = options.logger;\n this.onTransactionSuccess = options.onTransactionSuccess;\n this.onTransactionFailure = options.onTransactionFailure;\n this.onTransactionComplete = options.onTransactionComplete;\n this.shouldUploadFn = options.shouldUpload;\n\n // Conflict detection options\n this.conflictDetection = options.conflictDetection;\n this.conflictHandler = options.conflictHandler;\n this.conflictBus = options.conflictBus;\n\n // Initialize retry configuration by merging user options with defaults\n this.retryConfig = {\n transient: {\n ...DEFAULT_RETRY_CONFIG.transient,\n ...options.retryConfig?.transient\n },\n permanent: {\n ...DEFAULT_RETRY_CONFIG.permanent,\n ...options.retryConfig?.permanent\n }\n };\n\n // Subscribe to resolution events from the conflict bus\n // This stores resolutions so that on retry, we apply them instead of re-detecting\n if (this.conflictBus) {\n this.unsubscribeResolution = this.conflictBus.onResolution((table, recordId, resolution) => {\n // Key by table:recordId to avoid collisions across tables with same UUID\n const key = `${table}:${recordId}`;\n if (__DEV__) {\n console.log('[Connector] Storing resolution for retry:', {\n table,\n recordId,\n key,\n resolution\n });\n }\n this.resolvedConflicts.set(key, resolution);\n });\n }\n }\n\n /**\n * Clean up resources (unsubscribe from event listeners).\n * Call this when the connector is no longer needed.\n */\n destroy(): void {\n if (this.unsubscribeResolution) {\n this.unsubscribeResolution();\n this.unsubscribeResolution = undefined;\n }\n this.resolvedConflicts.clear();\n }\n\n // ─── Retry Control Methods ─────────────────────────────────────────────────\n\n /**\n * Pause automatic retry of failed uploads.\n * Use this when the user goes offline intentionally or wants manual control.\n */\n pauseAutoRetry(): void {\n this.autoRetryPaused = true;\n if (__DEV__) {\n console.log('[Connector] Auto-retry paused');\n }\n }\n\n /**\n * Resume automatic retry of failed uploads.\n */\n resumeAutoRetry(): void {\n this.autoRetryPaused = false;\n if (__DEV__) {\n console.log('[Connector] Auto-retry resumed');\n }\n }\n\n /**\n * Manually retry all failed uploads that are ready for retry.\n * This processes entries from the failed upload store.\n */\n async retryFailedUploads(): Promise<void> {\n const retryableUploads = failedUploadStore.getRetryable();\n if (retryableUploads.length === 0) {\n if (__DEV__) {\n console.log('[Connector] No failed uploads ready for retry');\n }\n return;\n }\n if (__DEV__) {\n console.log('[Connector] Manually retrying failed uploads:', {\n count: retryableUploads.length,\n entries: retryableUploads.map(u => ({\n table: u.table,\n id: u.id,\n operation: u.operation\n }))\n });\n }\n for (const upload of retryableUploads) {\n try {\n // Reconstruct the CrudEntry from the failed upload\n // Use a synthetic clientId since we don't have the original\n const entry: CrudEntry = {\n table: upload.table,\n op: upload.operation,\n id: upload.id,\n clientId: Date.now(),\n // Synthetic clientId for retry\n opData: upload.data\n };\n await this.processWithRetry(entry);\n\n // Success - remove from failed store\n failedUploadStore.remove(upload.id);\n if (__DEV__) {\n console.log('[Connector] Retry succeeded for:', {\n table: upload.table,\n id: upload.id\n });\n }\n } catch (error) {\n // Failed again - update is handled in processWithRetry\n if (__DEV__) {\n console.log('[Connector] Retry failed again for:', {\n table: upload.table,\n id: upload.id,\n error: error instanceof Error ? error.message : String(error)\n });\n }\n }\n }\n }\n\n /**\n * Clear all failed uploads from the store.\n * Use with caution - this discards all pending retries.\n */\n clearFailedUploads(): void {\n failedUploadStore.clear();\n if (__DEV__) {\n console.log('[Connector] Failed uploads cleared');\n }\n }\n\n /**\n * Get all failed uploads from the store.\n */\n getFailedUploads(): FailedUpload[] {\n return failedUploadStore.getAll();\n }\n\n // ─── Private Retry Logic ───────────────────────────────────────────────────\n\n /**\n * Process a single CRUD entry with exponential backoff retry.\n *\n * @param entry - The CRUD entry to process\n * @throws Error if all retries exhausted (for critical failures)\n */\n private async processWithRetry(entry: CrudEntry): Promise<void> {\n const classified = {\n isPermanent: false,\n pgCode: undefined as string | undefined,\n userMessage: ''\n };\n\n // Select retry config based on whether this is a retry from failed store\n // For initial attempts, we'll classify the error after the first failure\n let selectedConfig = this.retryConfig.transient;\n let lastError: Error | undefined;\n try {\n await withExponentialBackoff(async () => {\n try {\n await this.processCrudEntry(entry);\n } catch (error) {\n // Classify the error to determine retry strategy\n const classifiedError = classifySupabaseError(error);\n classified.isPermanent = classifiedError.isPermanent;\n classified.pgCode = classifiedError.pgCode;\n classified.userMessage = classifiedError.userMessage;\n\n // Switch to permanent retry config if error is permanent\n if (classifiedError.isPermanent) {\n selectedConfig = this.retryConfig.permanent;\n }\n lastError = error instanceof Error ? error : new Error(String(error));\n if (__DEV__) {\n console.log('[Connector] CRUD operation failed, will retry:', {\n table: entry.table,\n op: entry.op,\n id: entry.id,\n isPermanent: classifiedError.isPermanent,\n pgCode: classifiedError.pgCode,\n userMessage: classifiedError.userMessage\n });\n }\n throw error;\n }\n }, selectedConfig, {\n onRetry: (attempt, delay, error) => {\n this.logger?.debug('[Connector] Retry attempt:', {\n table: entry.table,\n op: entry.op,\n id: entry.id,\n attempt,\n delay,\n error: error.message\n });\n if (__DEV__) {\n console.log('[Connector] Retry attempt:', {\n table: entry.table,\n op: entry.op,\n id: entry.id,\n attempt,\n delayMs: delay,\n errorMessage: error.message\n });\n }\n }\n });\n } catch (error) {\n // All retries exhausted - add to failed upload store\n const finalError = lastError ?? (error instanceof Error ? error : new Error(String(error)));\n\n // Calculate next retry time based on error category\n const category: 'transient' | 'permanent' | 'unknown' = classified.isPermanent ? 'permanent' : classified.pgCode ? 'transient' : 'unknown';\n const retryConfig = classified.isPermanent ? this.retryConfig.permanent : this.retryConfig.transient;\n\n // Calculate next retry delay (for future manual or scheduled retry)\n const nextRetryDelay = calculateBackoffDelay(retryConfig.maxRetries, retryConfig);\n const nextRetryAt = Date.now() + nextRetryDelay;\n failedUploadStore.add({\n table: entry.table,\n operation: entry.op as 'PUT' | 'PATCH' | 'DELETE',\n data: entry.opData ?? {},\n error: {\n message: finalError.message,\n code: classified.pgCode,\n category\n },\n retryCount: retryConfig.maxRetries,\n lastAttempt: Date.now(),\n nextRetryAt\n });\n if (__DEV__) {\n console.log('[Connector] Entry added to failed upload store:', {\n table: entry.table,\n op: entry.op,\n id: entry.id,\n category,\n nextRetryAt: new Date(nextRetryAt).toISOString()\n });\n }\n this.logger?.error('[Connector] CRUD entry failed after retries:', {\n table: entry.table,\n op: entry.op,\n id: entry.id,\n error: finalError.message,\n category,\n nextRetryAt\n });\n\n // Only re-throw for critical failures that should stop the transaction\n // Non-critical failures are stored for later retry\n if (classified.isPermanent) {\n throw finalError;\n }\n }\n }\n\n /**\n * Set the active project IDs for scoped sync.\n * Call this when user selects/opens projects.\n */\n setActiveProjectIds(projectIds: string[]): void {\n this.activeProjectIds = projectIds;\n }\n\n /**\n * Get the current active project IDs.\n */\n getActiveProjectIds(): string[] {\n return this.activeProjectIds;\n }\n\n /**\n * Get credentials for PowerSync connection.\n * Uses Supabase session token.\n *\n * Note: Token refresh is handled by Supabase's startAutoRefresh() which must be\n * called on app initialization. getSession() returns the auto-refreshed token.\n */\n async fetchCredentials(): Promise<PowerSyncCredentials> {\n this.logger?.debug('[Connector] Fetching credentials...');\n const {\n data: {\n session\n },\n error\n } = await this.supabase.auth.getSession();\n if (error) {\n this.logger?.error('[Connector] Auth error:', error);\n throw new Error(`Failed to get Supabase session: ${error.message}`);\n }\n if (!session) {\n this.logger?.error('[Connector] No active session');\n throw new Error('No active Supabase session');\n }\n this.logger?.debug('[Connector] Credentials fetched, token expires at:', session.expires_at);\n return {\n endpoint: this.powerSyncUrl,\n token: session.access_token,\n expiresAt: session.expires_at ? new Date(session.expires_at * 1000) : undefined\n };\n }\n\n /**\n * Upload local changes to Supabase.\n * Called automatically by PowerSync when there are pending uploads.\n *\n * When conflict detection is enabled:\n * 1. Checks if table has _version column (cached)\n * 2. If yes, compares local vs server version\n * 3. On version mismatch, queries AuditLog for field conflicts\n * 4. If conflicts found, calls handler or publishes to conflict bus\n * 5. Applies resolution or skips entry based on handler response\n */\n async uploadData(database: AbstractPowerSyncDatabase): Promise<void> {\n // Check if uploads are allowed based on sync mode\n if (this.shouldUploadFn && !this.shouldUploadFn()) {\n if (__DEV__) {\n console.log('[Connector] Upload skipped - sync mode does not allow uploads');\n }\n this.logger?.debug('[Connector] Upload skipped - sync mode does not allow uploads');\n return;\n }\n\n // Process any retryable failed uploads if auto-retry is not paused\n if (!this.autoRetryPaused) {\n const retryableUploads = failedUploadStore.getRetryable();\n if (retryableUploads.length > 0) {\n if (__DEV__) {\n console.log('[Connector] Processing retryable failed uploads:', {\n count: retryableUploads.length\n });\n }\n for (const upload of retryableUploads) {\n try {\n // Reconstruct the CrudEntry from the failed upload\n // Use a synthetic clientId since we don't have the original\n const entry: CrudEntry = {\n table: upload.table,\n op: upload.operation,\n id: upload.id,\n clientId: Date.now(),\n // Synthetic clientId for retry\n opData: upload.data\n };\n await this.processWithRetry(entry);\n\n // Success - remove from failed store\n failedUploadStore.remove(upload.id);\n if (__DEV__) {\n console.log('[Connector] Retried upload succeeded:', {\n table: upload.table,\n id: upload.id\n });\n }\n } catch (error) {\n // Failed again - already handled in processWithRetry (updated in store)\n if (__DEV__) {\n console.log('[Connector] Retried upload failed again:', {\n table: upload.table,\n id: upload.id,\n error: error instanceof Error ? error.message : String(error)\n });\n }\n }\n }\n }\n }\n if (__DEV__) {\n console.log('[Connector] uploadData called, fetching next CRUD transaction...');\n }\n const transaction = await database.getNextCrudTransaction();\n if (!transaction) {\n if (__DEV__) {\n console.log('[Connector] No pending CRUD transaction found');\n }\n return;\n }\n if (__DEV__) {\n console.log('[Connector] Transaction fetched:', {\n crudCount: transaction.crud.length,\n entries: transaction.crud.map(e => ({\n table: e.table,\n op: e.op,\n id: e.id,\n opDataKeys: e.opData ? Object.keys(e.opData) : []\n }))\n });\n }\n\n // If conflict detection is disabled, use standard upload\n const conflictDetectionEnabled = this.conflictDetection?.enabled !== false;\n if (!conflictDetectionEnabled) {\n await this.processTransaction(transaction, database);\n return;\n }\n\n // Process with conflict detection\n const {\n crud\n } = transaction;\n const skipTables = new Set(this.conflictDetection?.skipTables ?? []);\n const entriesToProcess: CrudEntry[] = [];\n // Entries queued for UI resolution - these should NOT complete the transaction (leave in queue)\n const entriesQueuedForUI: CrudEntry[] = [];\n // Entries resolved with keep-server - these WILL complete the transaction (discard local changes)\n const entriesDiscarded: CrudEntry[] = [];\n\n // Track partial resolutions to re-emit remaining conflicts after sync completes\n const partialResolutions: Array<{\n originalConflict: ConflictCheckResult;\n syncedFields: string[];\n }> = [];\n for (const entry of crud) {\n // Skip DELETE operations - no conflict checking needed\n if (entry.op === 'DELETE') {\n entriesToProcess.push(entry);\n continue;\n }\n\n // Skip tables in the skip list\n if (skipTables.has(entry.table)) {\n entriesToProcess.push(entry);\n continue;\n }\n\n // Check for existing resolution from a previous retry\n // This prevents re-detecting conflicts that the user already resolved\n // Key by table:recordId to handle same UUID in different tables\n const resolutionKey = `${entry.table}:${entry.id}`;\n const existingResolution = this.resolvedConflicts.get(resolutionKey);\n if (existingResolution) {\n if (__DEV__) {\n console.log('[Connector] Applying stored resolution for retry:', {\n table: entry.table,\n id: entry.id,\n key: resolutionKey,\n resolution: existingResolution\n });\n }\n // Remove from stored resolutions (single use)\n this.resolvedConflicts.delete(resolutionKey);\n switch (existingResolution.action) {\n case 'overwrite':\n // Proceed with upload (overwrite server)\n entriesToProcess.push(entry);\n break;\n case 'keep-server':\n // Discard local changes - mark for completion (removes from queue)\n entriesDiscarded.push(entry);\n break;\n case 'partial':\n // Only sync specified fields\n const partialEntry: CrudEntry = {\n ...entry,\n opData: this.filterFields(entry.opData ?? {}, existingResolution.fields)\n };\n entriesToProcess.push(partialEntry);\n break;\n }\n continue;\n }\n\n // Check for version column (cached)\n const hasVersion = await this.checkVersionColumn(entry.table, database);\n if (!hasVersion) {\n entriesToProcess.push(entry);\n continue;\n }\n\n // Get local and server versions\n const localVersion = await getLocalVersion(entry.table, entry.id, database);\n const schema = this.schemaRouter(entry.table);\n const serverVersion = await fetchServerVersion(entry.table, entry.id, schema, this.supabase);\n\n // If we can't get versions, skip conflict check and proceed\n if (localVersion === null || serverVersion === null) {\n entriesToProcess.push(entry);\n continue;\n }\n\n // Detect conflicts\n const conflictResult = await detectConflicts(entry.table, entry.id, localVersion, serverVersion, entry.opData ?? {}, this.supabase, this.conflictDetection);\n if (!conflictResult.hasConflict) {\n entriesToProcess.push(entry);\n continue;\n }\n\n // Emit conflict event to bus if available\n this.conflictBus?.emitConflict(conflictResult);\n\n // Handle conflict with handler if available\n if (this.conflictHandler) {\n const resolution = await this.conflictHandler.onConflict(conflictResult);\n if (resolution === null) {\n // Queue for UI - skip this entry, leave in queue for later resolution\n entriesQueuedForUI.push(entry);\n if (__DEV__) {\n console.log('[Connector] Conflict queued for UI resolution:', {\n table: entry.table,\n id: entry.id,\n conflicts: conflictResult.conflicts.map(c => c.field)\n });\n }\n continue;\n }\n switch (resolution.action) {\n case 'overwrite':\n // Proceed with upload (overwrite server)\n entriesToProcess.push(entry);\n break;\n case 'keep-server':\n // Discard local changes - mark for completion (removes from queue)\n entriesDiscarded.push(entry);\n if (__DEV__) {\n console.log('[Connector] Conflict resolved with keep-server, discarding local changes:', {\n table: entry.table,\n id: entry.id\n });\n }\n break;\n case 'partial':\n // Only sync specified fields\n const partialEntry: CrudEntry = {\n ...entry,\n opData: this.filterFields(entry.opData ?? {}, resolution.fields)\n };\n entriesToProcess.push(partialEntry);\n\n // Track this partial resolution to re-emit remaining conflicts after sync\n partialResolutions.push({\n originalConflict: conflictResult,\n syncedFields: resolution.fields\n });\n break;\n }\n } else {\n // No handler - log conflict and proceed with upload\n console.warn('[Connector] Conflict detected but no handler:', {\n table: entry.table,\n id: entry.id,\n conflicts: conflictResult.conflicts\n });\n entriesToProcess.push(entry);\n }\n }\n\n // If any entries are queued for UI resolution, we must NOT complete the transaction\n // This leaves those entries in the PowerSync queue for later resolution\n if (entriesQueuedForUI.length > 0) {\n if (__DEV__) {\n console.log('[Connector] Entries queued for UI resolution, leaving in queue:', {\n queuedForUI: entriesQueuedForUI.length,\n discarded: entriesDiscarded.length,\n toProcess: entriesToProcess.length\n });\n }\n // Fire onTransactionComplete to notify listeners, but NOT onTransactionSuccess\n // because we're not completing the transaction. Entries remain in the queue.\n this.onTransactionComplete?.(entriesQueuedForUI);\n return;\n }\n\n // If all entries were discarded (keep-server) with none to process,\n // still complete the transaction to remove them from the queue\n if (entriesToProcess.length === 0 && entriesDiscarded.length > 0) {\n if (__DEV__) {\n console.log('[Connector] All entries resolved with keep-server, completing transaction to discard local changes');\n }\n try {\n await transaction.complete();\n // Fire success (entries were \"successfully\" discarded)\n this.onTransactionSuccess?.(entriesDiscarded);\n this.onTransactionComplete?.(entriesDiscarded);\n } catch (error) {\n const classified = classifySupabaseError(error);\n this.onTransactionFailure?.(entriesDiscarded, error instanceof Error ? error : new Error(String(error)), classified);\n throw error;\n }\n return;\n }\n\n // Process remaining entries with retry logic\n // Track entries that failed critically (permanent errors that should stop the transaction)\n const criticalFailures: {\n entry: CrudEntry;\n error: Error;\n }[] = [];\n const successfulEntries: CrudEntry[] = [];\n for (const entry of entriesToProcess) {\n if (__DEV__) {\n console.log('[Connector] Processing CRUD entry with retry:', {\n table: entry.table,\n op: entry.op,\n id: entry.id,\n opData: entry.opData\n });\n }\n try {\n await this.processWithRetry(entry);\n successfulEntries.push(entry);\n } catch (error) {\n // Only processWithRetry throws for permanent/critical failures\n // Transient failures are added to the failed store and don't throw\n criticalFailures.push({\n entry,\n error: error instanceof Error ? error : new Error(String(error))\n });\n }\n }\n\n // If there are critical failures, we need to handle them appropriately\n if (criticalFailures.length > 0) {\n const firstFailure = criticalFailures[0];\n const classified = classifySupabaseError(firstFailure.error);\n\n // Always log to console for debugging\n console.error('[PowerSync Connector] Critical upload failure:', {\n errorMessage: firstFailure.error.message,\n classified,\n isPermanent: classified.isPermanent,\n criticalCount: criticalFailures.length,\n successCount: successfulEntries.length,\n entries: criticalFailures.map(f => ({\n table: f.entry.table,\n op: f.entry.op,\n id: f.entry.id\n }))\n });\n this.logger?.error('[Connector] Critical upload failure:', {\n error: firstFailure.error,\n classified,\n entries: criticalFailures.map(f => ({\n table: f.entry.table,\n op: f.entry.op,\n id: f.entry.id\n }))\n });\n\n // Notify failure with classification\n this.onTransactionFailure?.(criticalFailures.map(f => f.entry), firstFailure.error, classified);\n\n // Re-throw for PowerSync's native retry mechanism only for critical failures\n throw firstFailure.error;\n }\n if (__DEV__) {\n console.log('[Connector] All CRUD entries processed, completing transaction...');\n }\n try {\n await transaction.complete();\n if (__DEV__) {\n console.log('[Connector] Transaction completed successfully:', {\n entriesCount: successfulEntries.length,\n discardedCount: entriesDiscarded.length\n });\n }\n\n // After partial sync completes, re-emit conflicts for remaining fields\n if (this.conflictBus && partialResolutions.length > 0) {\n for (const {\n originalConflict,\n syncedFields\n } of partialResolutions) {\n const syncedFieldSet = new Set(syncedFields);\n\n // Find conflicts that weren't synced\n const remainingConflicts = originalConflict.conflicts.filter(c => !syncedFieldSet.has(c.field));\n if (remainingConflicts.length > 0) {\n if (__DEV__) {\n console.log('[Connector] Re-emitting conflict for remaining fields:', {\n table: originalConflict.table,\n recordId: originalConflict.recordId,\n syncedFields,\n remainingConflictFields: remainingConflicts.map(c => c.field)\n });\n }\n\n // Emit new conflict for remaining fields\n this.conflictBus.emitConflict({\n ...originalConflict,\n conflicts: remainingConflicts,\n // All remaining are conflicts now - clear nonConflictingChanges since\n // the non-conflicting ones were already synced in the partial resolution\n nonConflictingChanges: []\n });\n }\n }\n }\n\n // Notify success\n this.onTransactionSuccess?.(successfulEntries);\n\n // Notify completion (after transaction.complete() succeeds)\n this.onTransactionComplete?.(successfulEntries);\n } catch (error) {\n const classified = classifySupabaseError(error);\n\n // Always log to console for debugging\n console.error('[PowerSync Connector] Transaction completion FAILED:', {\n errorMessage: error instanceof Error ? error.message : String(error),\n errorKeys: error && typeof error === 'object' ? Object.keys(error) : [],\n errorObject: JSON.stringify(error, null, 2),\n classified,\n isPermanent: classified.isPermanent,\n entries: successfulEntries.map(e => ({\n table: e.table,\n op: e.op,\n id: e.id\n }))\n });\n this.logger?.error('[Connector] Transaction completion error:', {\n error,\n classified,\n entries: successfulEntries.map(e => ({\n table: e.table,\n op: e.op,\n id: e.id\n }))\n });\n\n // Notify failure with classification\n this.onTransactionFailure?.(successfulEntries, error instanceof Error ? error : new Error(String(error)), classified);\n\n // Re-throw for PowerSync's native retry mechanism\n throw error;\n }\n }\n\n /**\n * Process a transaction without conflict detection.\n * Used when conflict detection is disabled.\n */\n private async processTransaction(transaction: {\n crud: CrudEntry[];\n complete: () => Promise<void>;\n }, _database: AbstractPowerSyncDatabase): Promise<void> {\n // Track entries that failed critically (permanent errors that should stop the transaction)\n const criticalFailures: {\n entry: CrudEntry;\n error: Error;\n }[] = [];\n const successfulEntries: CrudEntry[] = [];\n for (const entry of transaction.crud) {\n if (__DEV__) {\n console.log('[Connector] Processing CRUD entry with retry:', {\n table: entry.table,\n op: entry.op,\n id: entry.id,\n opData: entry.opData\n });\n }\n try {\n await this.processWithRetry(entry);\n successfulEntries.push(entry);\n } catch (error) {\n // Only processWithRetry throws for permanent/critical failures\n // Transient failures are added to the failed store and don't throw\n criticalFailures.push({\n entry,\n error: error instanceof Error ? error : new Error(String(error))\n });\n }\n }\n\n // If there are critical failures, we need to handle them appropriately\n if (criticalFailures.length > 0) {\n const firstFailure = criticalFailures[0];\n const classified = classifySupabaseError(firstFailure.error);\n\n // Always log to console for debugging\n console.error('[PowerSync Connector] Critical upload failure:', {\n errorMessage: firstFailure.error.message,\n classified,\n isPermanent: classified.isPermanent,\n criticalCount: criticalFailures.length,\n successCount: successfulEntries.length,\n entries: criticalFailures.map(f => ({\n table: f.entry.table,\n op: f.entry.op,\n id: f.entry.id\n }))\n });\n this.logger?.error('[Connector] Critical upload failure:', {\n error: firstFailure.error,\n classified,\n entries: criticalFailures.map(f => ({\n table: f.entry.table,\n op: f.entry.op,\n id: f.entry.id\n }))\n });\n\n // Notify failure with classification\n this.onTransactionFailure?.(criticalFailures.map(f => f.entry), firstFailure.error, classified);\n\n // Re-throw for PowerSync's native retry mechanism only for critical failures\n throw firstFailure.error;\n }\n if (__DEV__) {\n console.log('[Connector] All CRUD entries processed, completing transaction...');\n }\n try {\n await transaction.complete();\n if (__DEV__) {\n console.log('[Connector] Transaction completed successfully:', {\n entriesCount: successfulEntries.length\n });\n }\n\n // Notify success\n this.onTransactionSuccess?.(successfulEntries);\n\n // Notify completion (after transaction.complete() succeeds)\n this.onTransactionComplete?.(successfulEntries);\n } catch (error) {\n const classified = classifySupabaseError(error);\n\n // Always log to console for debugging\n console.error('[PowerSync Connector] Transaction completion FAILED:', {\n errorMessage: error instanceof Error ? error.message : String(error),\n errorKeys: error && typeof error === 'object' ? Object.keys(error) : [],\n errorObject: JSON.stringify(error, null, 2),\n classified,\n isPermanent: classified.isPermanent,\n entries: successfulEntries.map(e => ({\n table: e.table,\n op: e.op,\n id: e.id\n }))\n });\n this.logger?.error('[Connector] Transaction completion error:', {\n error,\n classified,\n entries: successfulEntries.map(e => ({\n table: e.table,\n op: e.op,\n id: e.id\n }))\n });\n\n // Notify failure with classification\n this.onTransactionFailure?.(successfulEntries, error instanceof Error ? error : new Error(String(error)), classified);\n\n // Re-throw for PowerSync's native retry mechanism\n throw error;\n }\n }\n\n /**\n * Check if a table has a _version column (cached).\n */\n private async checkVersionColumn(table: string, db: AbstractPowerSyncDatabase): Promise<boolean> {\n if (this.versionColumnCache.has(table)) {\n return this.versionColumnCache.get(table)!;\n }\n const hasVersion = await hasVersionColumn(table, db);\n this.versionColumnCache.set(table, hasVersion);\n return hasVersion;\n }\n\n /**\n * Filter opData to only include specified fields.\n * Used for partial sync resolution.\n */\n private filterFields(opData: Record<string, unknown>, fields: string[]): Record<string, unknown> {\n const fieldSet = new Set(fields);\n const filtered: Record<string, unknown> = {};\n for (const [key, value] of Object.entries(opData)) {\n if (fieldSet.has(key)) {\n filtered[key] = value;\n }\n }\n return filtered;\n }\n\n /**\n * Process a single CRUD operation.\n *\n * UUID-native tables (public schema, post-migration) use `id` as the UUID column.\n * Core schema tables (Profile, Comment, CommentSection) still use a separate `uuid` column.\n */\n /**\n * Process a single CRUD operation.\n *\n * All synced tables use `id` as their UUID primary key column.\n */\n private async processCrudEntry(entry: CrudEntry): Promise<void> {\n const table = entry.table;\n const id = entry.id; // PowerSync sends UUID as the entry.id\n const schema = this.schemaRouter(table);\n\n // Try custom handler first\n if (this.crudHandler) {\n let handled = false;\n switch (entry.op) {\n case UpdateType.PUT:\n handled = (await this.crudHandler.handlePut?.(entry, this.supabase, schema)) ?? false;\n break;\n case UpdateType.PATCH:\n handled = (await this.crudHandler.handlePatch?.(entry, this.supabase, schema)) ?? false;\n break;\n case UpdateType.DELETE:\n handled = (await this.crudHandler.handleDelete?.(entry, this.supabase, schema)) ?? false;\n break;\n }\n if (handled) {\n this.logger?.debug(`[Connector] Custom handler processed ${entry.op} for ${schema}.${table}`);\n return;\n }\n }\n\n // Default behavior\n // Get the correct Supabase query builder for this table's schema\n const query = schema === 'public' ? this.supabase.from(table) : (this.supabase.schema(schema) as unknown as ReturnType<typeof this.supabase.schema>).from(table);\n switch (entry.op) {\n case UpdateType.PUT:\n if (__DEV__) {\n console.log('[Connector] Executing PUT/UPSERT:', {\n schema,\n table,\n id,\n data: {\n id,\n ...entry.opData\n }\n });\n }\n // Insert/upsert using id column\n const {\n data: upsertData,\n error: upsertError\n } = await query.upsert({\n id,\n ...entry.opData\n }, {\n onConflict: 'id'\n }).select();\n if (upsertError) {\n if (__DEV__) {\n console.error('[Connector] PUT/UPSERT FAILED:', {\n schema,\n table,\n id,\n error: upsertError,\n errorMessage: upsertError.message,\n errorCode: upsertError.code,\n errorDetails: upsertError.details,\n errorHint: upsertError.hint\n });\n }\n throw new Error(`Upsert failed for ${schema}.${table}: ${upsertError.message}`);\n }\n if (__DEV__) {\n console.log('[Connector] PUT/UPSERT SUCCESS:', {\n schema,\n table,\n id,\n responseData: upsertData\n });\n }\n break;\n case UpdateType.PATCH:\n if (__DEV__) {\n console.log('[Connector] Executing PATCH/UPDATE:', {\n schema,\n table,\n id,\n opData: entry.opData\n });\n }\n // Update by id column\n const {\n data: updateData,\n error: updateError\n } = await query.update(entry.opData).eq('id', id).select();\n if (updateError) {\n if (__DEV__) {\n console.error('[Connector] PATCH/UPDATE FAILED:', {\n schema,\n table,\n id,\n error: updateError,\n errorMessage: updateError.message,\n errorCode: updateError.code,\n errorDetails: updateError.details,\n errorHint: updateError.hint\n });\n }\n throw new Error(`Update failed for ${schema}.${table}: ${updateError.message}`);\n }\n if (__DEV__) {\n console.log('[Connector] PATCH/UPDATE SUCCESS:', {\n schema,\n table,\n id,\n responseData: updateData\n });\n }\n break;\n case UpdateType.DELETE:\n if (__DEV__) {\n console.log('[Connector] Executing DELETE:', {\n schema,\n table,\n id\n });\n }\n // Delete by id column\n const {\n data: deleteData,\n error: deleteError\n } = await query.delete().eq('id', id).select();\n if (deleteError) {\n if (__DEV__) {\n console.error('[Connector] DELETE FAILED:', {\n schema,\n table,\n id,\n error: deleteError,\n errorMessage: deleteError.message,\n errorCode: deleteError.code,\n errorDetails: deleteError.details,\n errorHint: deleteError.hint\n });\n }\n throw new Error(`Delete failed for ${schema}.${table}: ${deleteError.message}`);\n }\n if (__DEV__) {\n console.log('[Connector] DELETE SUCCESS:', {\n schema,\n table,\n id,\n responseData: deleteData\n });\n }\n break;\n }\n this.logger?.debug(`[Connector] Processed ${entry.op} for ${schema}.${table} (id: ${id})`);\n }\n}"],"mappings":"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","names":[]}
|
|
@@ -8,10 +8,13 @@ import {
|
|
|
8
8
|
HealthMonitor,
|
|
9
9
|
MetricsCollector,
|
|
10
10
|
SyncStatusTracker
|
|
11
|
-
} from "./chunk-
|
|
11
|
+
} from "./chunk-R4YFWQ3Q.js";
|
|
12
12
|
import {
|
|
13
13
|
SupabaseConnector
|
|
14
|
-
} from "./chunk-
|
|
14
|
+
} from "./chunk-62J2DPKX.js";
|
|
15
|
+
import {
|
|
16
|
+
failedUploadStore
|
|
17
|
+
} from "./chunk-PAFBKNL3.js";
|
|
15
18
|
import {
|
|
16
19
|
createSyncError,
|
|
17
20
|
extractEntityIds,
|
|
@@ -24,6 +27,18 @@ var PowerSyncContext = createContext(null);
|
|
|
24
27
|
PowerSyncContext.displayName = "PowerSyncContext";
|
|
25
28
|
var SyncStatusContext = createContext(null);
|
|
26
29
|
SyncStatusContext.displayName = "SyncStatusContext";
|
|
30
|
+
var ConnectionStatusContext = createContext(null);
|
|
31
|
+
ConnectionStatusContext.displayName = "ConnectionStatusContext";
|
|
32
|
+
var SyncActivityContext = createContext(null);
|
|
33
|
+
SyncActivityContext.displayName = "SyncActivityContext";
|
|
34
|
+
var PendingMutationsContext = createContext(null);
|
|
35
|
+
PendingMutationsContext.displayName = "PendingMutationsContext";
|
|
36
|
+
var FailedTransactionsContext = createContext(null);
|
|
37
|
+
FailedTransactionsContext.displayName = "FailedTransactionsContext";
|
|
38
|
+
var CompletedTransactionsContext = createContext(null);
|
|
39
|
+
CompletedTransactionsContext.displayName = "CompletedTransactionsContext";
|
|
40
|
+
var SyncModeContext = createContext(null);
|
|
41
|
+
SyncModeContext.displayName = "SyncModeContext";
|
|
27
42
|
var ConnectionHealthContext = createContext(null);
|
|
28
43
|
ConnectionHealthContext.displayName = "ConnectionHealthContext";
|
|
29
44
|
var SyncMetricsContext = createContext(null);
|
|
@@ -131,6 +146,7 @@ function PowerSyncProvider({
|
|
|
131
146
|
const attachmentQueueRef = useRef(null);
|
|
132
147
|
const listenerUnsubscribeRef = useRef(null);
|
|
133
148
|
const wasSyncingRef = useRef(false);
|
|
149
|
+
const wasUploadingRef = useRef(false);
|
|
134
150
|
const initializingRef = useRef(false);
|
|
135
151
|
const dbClosedRef = useRef(false);
|
|
136
152
|
const connectorRef = useRef(null);
|
|
@@ -151,11 +167,11 @@ function PowerSyncProvider({
|
|
|
151
167
|
onHealthChange: setConnectionHealth
|
|
152
168
|
});
|
|
153
169
|
healthMonitorRef.current = healthMonitor;
|
|
154
|
-
const initPromise = Promise.all([statusTracker.init(), metricsCollector.init()]);
|
|
170
|
+
const initPromise = Promise.all([statusTracker.init(), metricsCollector.init(), failedUploadStore.load()]);
|
|
155
171
|
const timeoutPromise = new Promise((resolve) => {
|
|
156
172
|
setTimeout(() => {
|
|
157
173
|
logger.warn("[PowerSyncProvider] Sync mode state load timed out, using default (push-pull)");
|
|
158
|
-
resolve([void 0, void 0]);
|
|
174
|
+
resolve([void 0, void 0, void 0]);
|
|
159
175
|
}, 5e3);
|
|
160
176
|
});
|
|
161
177
|
Promise.race([initPromise, timeoutPromise]).then(() => {
|
|
@@ -283,6 +299,7 @@ function PowerSyncProvider({
|
|
|
283
299
|
powerSyncUrl,
|
|
284
300
|
schemaRouter: connectorConfig?.schemaRouter,
|
|
285
301
|
crudHandler: connectorConfig?.crudHandler,
|
|
302
|
+
retryConfig: connectorConfig?.retryConfig,
|
|
286
303
|
logger,
|
|
287
304
|
// Conflict detection - enabled by default
|
|
288
305
|
conflictDetection: {
|
|
@@ -322,8 +339,12 @@ function PowerSyncProvider({
|
|
|
322
339
|
}
|
|
323
340
|
logger.info("[PowerSyncProvider] Connecting to PowerSync...");
|
|
324
341
|
await db.connect(newConnector);
|
|
325
|
-
|
|
326
|
-
|
|
342
|
+
if (db.connected) {
|
|
343
|
+
logger.info("[PowerSyncProvider] Connected successfully");
|
|
344
|
+
healthMonitorRef.current?.resetReconnectAttempts();
|
|
345
|
+
} else {
|
|
346
|
+
logger.warn("[PowerSyncProvider] Connection initiated but streaming not established - check PowerSync URL");
|
|
347
|
+
}
|
|
327
348
|
} catch (err_0) {
|
|
328
349
|
const connectError = err_0 instanceof Error ? err_0 : new Error(String(err_0));
|
|
329
350
|
logger.error("[PowerSyncProvider] Connection failed:", connectError);
|
|
@@ -344,12 +365,15 @@ function PowerSyncProvider({
|
|
|
344
365
|
statusTrackerRef.current?.handleStatusChange(status_0);
|
|
345
366
|
const dataFlow = status_0.dataFlowStatus;
|
|
346
367
|
const isDownloading = dataFlow?.downloading ?? false;
|
|
368
|
+
const isUploading = dataFlow?.uploading ?? false;
|
|
347
369
|
const progress = status_0.downloadProgress;
|
|
348
370
|
if (isDownloading && !wasSyncingRef.current) {
|
|
371
|
+
logger.info("[PowerSyncProvider] Downloading from server...");
|
|
349
372
|
metricsCollectorRef.current?.markSyncStart();
|
|
350
373
|
}
|
|
351
374
|
if (!isDownloading && wasSyncingRef.current) {
|
|
352
375
|
const duration = metricsCollectorRef.current?.markSyncEnd();
|
|
376
|
+
logger.info("[PowerSyncProvider] Download complete", duration ? `(${duration}ms)` : "");
|
|
353
377
|
if (duration !== null && duration !== void 0) {
|
|
354
378
|
metricsCollectorRef.current?.recordSync({
|
|
355
379
|
durationMs: duration,
|
|
@@ -358,7 +382,14 @@ function PowerSyncProvider({
|
|
|
358
382
|
});
|
|
359
383
|
}
|
|
360
384
|
}
|
|
385
|
+
if (isUploading && !wasUploadingRef.current) {
|
|
386
|
+
logger.info("[PowerSyncProvider] Uploading local changes...");
|
|
387
|
+
}
|
|
388
|
+
if (!isUploading && wasUploadingRef.current) {
|
|
389
|
+
logger.info("[PowerSyncProvider] Upload complete");
|
|
390
|
+
}
|
|
361
391
|
wasSyncingRef.current = isDownloading;
|
|
392
|
+
wasUploadingRef.current = isUploading;
|
|
362
393
|
}
|
|
363
394
|
});
|
|
364
395
|
listenerUnsubscribeRef.current = unsubscribe;
|
|
@@ -584,6 +615,27 @@ function PowerSyncProvider({
|
|
|
584
615
|
await db.connect(connector);
|
|
585
616
|
}
|
|
586
617
|
}, [db, connector, syncStatus.uploading, logger]);
|
|
618
|
+
const pauseAutoRetry = useCallback(() => {
|
|
619
|
+
if (!connector) {
|
|
620
|
+
logger.warn("[PowerSyncProvider] Cannot pause auto-retry - connector not initialized");
|
|
621
|
+
return;
|
|
622
|
+
}
|
|
623
|
+
connector.pauseAutoRetry();
|
|
624
|
+
}, [connector, logger]);
|
|
625
|
+
const resumeAutoRetry = useCallback(() => {
|
|
626
|
+
if (!connector) {
|
|
627
|
+
logger.warn("[PowerSyncProvider] Cannot resume auto-retry - connector not initialized");
|
|
628
|
+
return;
|
|
629
|
+
}
|
|
630
|
+
connector.resumeAutoRetry();
|
|
631
|
+
}, [connector, logger]);
|
|
632
|
+
const retryFailedUploads = useCallback(async () => {
|
|
633
|
+
if (!connector) {
|
|
634
|
+
logger.warn("[PowerSyncProvider] Cannot retry failed uploads - connector not initialized");
|
|
635
|
+
return;
|
|
636
|
+
}
|
|
637
|
+
await connector.retryFailedUploads();
|
|
638
|
+
}, [connector, logger]);
|
|
587
639
|
const powerSyncContextValue = useMemo(() => ({
|
|
588
640
|
db,
|
|
589
641
|
connector,
|
|
@@ -622,15 +674,57 @@ function PowerSyncProvider({
|
|
|
622
674
|
setForceNextUpload,
|
|
623
675
|
// Discard mutation functions
|
|
624
676
|
discardPendingMutation,
|
|
625
|
-
discardAllPendingMutations
|
|
626
|
-
|
|
677
|
+
discardAllPendingMutations,
|
|
678
|
+
// Retry control functions
|
|
679
|
+
pauseAutoRetry,
|
|
680
|
+
resumeAutoRetry,
|
|
681
|
+
retryFailedUploads
|
|
682
|
+
}), [syncStatus, pendingMutations, syncModeState.mode, lastSyncedAt, connectionError, failedTransactions, clearFailure, clearAllFailures, completedTransactions, clearCompletedHistory, setSyncMode, setForceNextUpload, discardPendingMutation, discardAllPendingMutations, pauseAutoRetry, resumeAutoRetry, retryFailedUploads]);
|
|
627
683
|
const connectionHealthContextValue = useMemo(() => ({
|
|
628
684
|
health: connectionHealth
|
|
629
685
|
}), [connectionHealth]);
|
|
630
686
|
const syncMetricsContextValue = useMemo(() => ({
|
|
631
687
|
metrics: syncMetrics
|
|
632
688
|
}), [syncMetrics]);
|
|
633
|
-
|
|
689
|
+
const connectionStatusValue = useMemo(() => ({
|
|
690
|
+
connected: syncStatus.connected,
|
|
691
|
+
connecting: syncStatus.connecting,
|
|
692
|
+
hasSynced: syncStatus.hasSynced,
|
|
693
|
+
lastSyncedAt,
|
|
694
|
+
connectionError
|
|
695
|
+
}), [syncStatus.connected, syncStatus.connecting, syncStatus.hasSynced, lastSyncedAt, connectionError]);
|
|
696
|
+
const syncActivityValue = useMemo(() => ({
|
|
697
|
+
uploading: syncStatus.uploading,
|
|
698
|
+
downloading: syncStatus.downloading,
|
|
699
|
+
downloadProgress: syncStatus.downloadProgress
|
|
700
|
+
}), [syncStatus.uploading, syncStatus.downloading, syncStatus.downloadProgress]);
|
|
701
|
+
const pendingMutationsValue = useMemo(() => ({
|
|
702
|
+
pendingMutations,
|
|
703
|
+
pendingCount: pendingMutations.length,
|
|
704
|
+
discardPendingMutation,
|
|
705
|
+
discardAllPendingMutations
|
|
706
|
+
}), [pendingMutations, discardPendingMutation, discardAllPendingMutations]);
|
|
707
|
+
const failedTransactionsValue = useMemo(() => ({
|
|
708
|
+
failedTransactions,
|
|
709
|
+
hasUploadErrors: failedTransactions.length > 0,
|
|
710
|
+
permanentErrorCount: failedTransactions.filter((f_1) => f_1.isPermanent).length,
|
|
711
|
+
clearFailure,
|
|
712
|
+
clearAllFailures,
|
|
713
|
+
pauseAutoRetry,
|
|
714
|
+
resumeAutoRetry,
|
|
715
|
+
retryFailedUploads
|
|
716
|
+
}), [failedTransactions, clearFailure, clearAllFailures, pauseAutoRetry, resumeAutoRetry, retryFailedUploads]);
|
|
717
|
+
const completedTransactionsValue = useMemo(() => ({
|
|
718
|
+
completedTransactions,
|
|
719
|
+
clearCompletedHistory
|
|
720
|
+
}), [completedTransactions, clearCompletedHistory]);
|
|
721
|
+
const syncModeValue = useMemo(() => ({
|
|
722
|
+
syncMode: syncModeState.mode,
|
|
723
|
+
isPaused: syncModeState.mode === "offline",
|
|
724
|
+
setSyncMode,
|
|
725
|
+
setForceNextUpload
|
|
726
|
+
}), [syncModeState.mode, setSyncMode, setForceNextUpload]);
|
|
727
|
+
return /* @__PURE__ */ jsx(PowerSyncContext.Provider, { value: powerSyncContextValue, children: /* @__PURE__ */ jsx(ConnectionStatusContext.Provider, { value: connectionStatusValue, children: /* @__PURE__ */ jsx(SyncActivityContext.Provider, { value: syncActivityValue, children: /* @__PURE__ */ jsx(PendingMutationsContext.Provider, { value: pendingMutationsValue, children: /* @__PURE__ */ jsx(FailedTransactionsContext.Provider, { value: failedTransactionsValue, children: /* @__PURE__ */ jsx(CompletedTransactionsContext.Provider, { value: completedTransactionsValue, children: /* @__PURE__ */ jsx(SyncModeContext.Provider, { value: syncModeValue, children: /* @__PURE__ */ jsx(SyncStatusContext.Provider, { value: syncStatusContextValue, children: /* @__PURE__ */ jsx(ConnectionHealthContext.Provider, { value: connectionHealthContextValue, children: /* @__PURE__ */ jsx(SyncMetricsContext.Provider, { value: syncMetricsContextValue, children: /* @__PURE__ */ jsx(AttachmentQueueContext.Provider, { value: attachmentQueue, children }) }) }) }) }) }) }) }) }) }) });
|
|
634
728
|
}
|
|
635
729
|
|
|
636
730
|
// src/provider/hooks.ts
|
|
@@ -650,6 +744,48 @@ function useSyncStatus() {
|
|
|
650
744
|
}
|
|
651
745
|
return context;
|
|
652
746
|
}
|
|
747
|
+
function useConnectionStatus() {
|
|
748
|
+
const context = useContext(ConnectionStatusContext);
|
|
749
|
+
if (!context) {
|
|
750
|
+
throw new Error("useConnectionStatus must be used within a PowerSyncProvider");
|
|
751
|
+
}
|
|
752
|
+
return context;
|
|
753
|
+
}
|
|
754
|
+
function useSyncActivityContext() {
|
|
755
|
+
const context = useContext(SyncActivityContext);
|
|
756
|
+
if (!context) {
|
|
757
|
+
throw new Error("useSyncActivityContext must be used within a PowerSyncProvider");
|
|
758
|
+
}
|
|
759
|
+
return context;
|
|
760
|
+
}
|
|
761
|
+
function usePendingMutationsContext() {
|
|
762
|
+
const context = useContext(PendingMutationsContext);
|
|
763
|
+
if (!context) {
|
|
764
|
+
throw new Error("usePendingMutationsContext must be used within a PowerSyncProvider");
|
|
765
|
+
}
|
|
766
|
+
return context;
|
|
767
|
+
}
|
|
768
|
+
function useFailedTransactionsContext() {
|
|
769
|
+
const context = useContext(FailedTransactionsContext);
|
|
770
|
+
if (!context) {
|
|
771
|
+
throw new Error("useFailedTransactionsContext must be used within a PowerSyncProvider");
|
|
772
|
+
}
|
|
773
|
+
return context;
|
|
774
|
+
}
|
|
775
|
+
function useCompletedTransactionsContext() {
|
|
776
|
+
const context = useContext(CompletedTransactionsContext);
|
|
777
|
+
if (!context) {
|
|
778
|
+
throw new Error("useCompletedTransactionsContext must be used within a PowerSyncProvider");
|
|
779
|
+
}
|
|
780
|
+
return context;
|
|
781
|
+
}
|
|
782
|
+
function useSyncModeContext() {
|
|
783
|
+
const context = useContext(SyncModeContext);
|
|
784
|
+
if (!context) {
|
|
785
|
+
throw new Error("useSyncModeContext must be used within a PowerSyncProvider");
|
|
786
|
+
}
|
|
787
|
+
return context;
|
|
788
|
+
}
|
|
653
789
|
function useSyncControl() {
|
|
654
790
|
const $ = _c(35);
|
|
655
791
|
const {
|
|
@@ -660,7 +796,7 @@ function useSyncControl() {
|
|
|
660
796
|
const {
|
|
661
797
|
setSyncMode: setContextSyncMode,
|
|
662
798
|
setForceNextUpload
|
|
663
|
-
} =
|
|
799
|
+
} = useSyncModeContext();
|
|
664
800
|
const scopeRef = useRef2(null);
|
|
665
801
|
let t0;
|
|
666
802
|
if ($[0] !== connector || $[1] !== db || $[2] !== platform || $[3] !== setContextSyncMode) {
|
|
@@ -823,11 +959,9 @@ function useSyncControl() {
|
|
|
823
959
|
function useSyncMode() {
|
|
824
960
|
const $ = _c(5);
|
|
825
961
|
const {
|
|
826
|
-
syncMode
|
|
827
|
-
} = useSyncStatus();
|
|
828
|
-
const {
|
|
962
|
+
syncMode,
|
|
829
963
|
setSyncMode
|
|
830
|
-
} =
|
|
964
|
+
} = useSyncModeContext();
|
|
831
965
|
const t0 = syncMode === "push-pull";
|
|
832
966
|
const t1 = syncMode !== "offline";
|
|
833
967
|
let t2;
|
|
@@ -1261,12 +1395,24 @@ export {
|
|
|
1261
1395
|
ConflictBus,
|
|
1262
1396
|
PowerSyncContext,
|
|
1263
1397
|
SyncStatusContext,
|
|
1398
|
+
ConnectionStatusContext,
|
|
1399
|
+
SyncActivityContext,
|
|
1400
|
+
PendingMutationsContext,
|
|
1401
|
+
FailedTransactionsContext,
|
|
1402
|
+
CompletedTransactionsContext,
|
|
1403
|
+
SyncModeContext,
|
|
1264
1404
|
ConnectionHealthContext,
|
|
1265
1405
|
SyncMetricsContext,
|
|
1266
1406
|
AttachmentQueueContext,
|
|
1267
1407
|
PowerSyncProvider,
|
|
1268
1408
|
usePowerSync,
|
|
1269
1409
|
useSyncStatus,
|
|
1410
|
+
useConnectionStatus,
|
|
1411
|
+
useSyncActivityContext,
|
|
1412
|
+
usePendingMutationsContext,
|
|
1413
|
+
useFailedTransactionsContext,
|
|
1414
|
+
useCompletedTransactionsContext,
|
|
1415
|
+
useSyncModeContext,
|
|
1270
1416
|
useSyncControl,
|
|
1271
1417
|
useSyncMode,
|
|
1272
1418
|
useConnectionHealth,
|
|
@@ -1282,4 +1428,4 @@ export {
|
|
|
1282
1428
|
useUploadStatus,
|
|
1283
1429
|
useSyncActivity
|
|
1284
1430
|
};
|
|
1285
|
-
//# sourceMappingURL=chunk-
|
|
1431
|
+
//# sourceMappingURL=chunk-KCDG2MNP.js.map
|