@pol-studios/powersync 1.0.19 → 1.0.20

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (32) hide show
  1. package/dist/{chunk-HWSNV45P.js → chunk-FEL22ID3.js} +1 -1
  2. package/dist/{chunk-VACPAAQZ.js → chunk-I2AYMY5O.js} +2 -1
  3. package/dist/chunk-I2AYMY5O.js.map +1 -0
  4. package/dist/{chunk-KN2IZERF.js → chunk-IMRSLJRV.js} +233 -11
  5. package/dist/chunk-IMRSLJRV.js.map +1 -0
  6. package/dist/{chunk-BRXQNASY.js → chunk-N4K7E53V.js} +4 -4
  7. package/dist/{chunk-CUCAYK7Z.js → chunk-PG2NPQG3.js} +2 -2
  8. package/dist/{chunk-63PXSPIN.js → chunk-XOCIONAA.js} +3 -3
  9. package/dist/connector/index.d.ts +180 -4
  10. package/dist/connector/index.js +27 -5
  11. package/dist/core/index.d.ts +8 -1
  12. package/dist/core/index.js +3 -1
  13. package/dist/index.d.ts +5 -5
  14. package/dist/index.js +31 -7
  15. package/dist/index.native.d.ts +4 -4
  16. package/dist/index.native.js +31 -7
  17. package/dist/index.web.d.ts +4 -4
  18. package/dist/index.web.js +31 -7
  19. package/dist/provider/index.d.ts +2 -2
  20. package/dist/provider/index.js +4 -4
  21. package/dist/react/index.d.ts +1 -1
  22. package/dist/react/index.js +3 -3
  23. package/dist/{supabase-connector-T9vHq_3i.d.ts → supabase-connector-WuiFiBnV.d.ts} +2 -1
  24. package/dist/sync/index.js +2 -2
  25. package/dist/{types-B212hgfA.d.ts → types-DiBvmGEi.d.ts} +81 -1
  26. package/package.json +2 -2
  27. package/dist/chunk-KN2IZERF.js.map +0 -1
  28. package/dist/chunk-VACPAAQZ.js.map +0 -1
  29. /package/dist/{chunk-HWSNV45P.js.map → chunk-FEL22ID3.js.map} +0 -0
  30. /package/dist/{chunk-BRXQNASY.js.map → chunk-N4K7E53V.js.map} +0 -0
  31. /package/dist/{chunk-CUCAYK7Z.js.map → chunk-PG2NPQG3.js.map} +0 -0
  32. /package/dist/{chunk-63PXSPIN.js.map → chunk-XOCIONAA.js.map} +0 -0
@@ -0,0 +1 @@
1
+ {"version":3,"sources":["../src/connector/types.ts","../src/connector/middleware/upload-error.ts","../src/connector/errors.ts","../src/conflicts/detect.ts","../src/connector/supabase-connector.ts"],"sourcesContent":["/**\n * Connector Types for @pol-studios/powersync\n *\n * Defines interfaces for PowerSync backend connectors.\n */\n\nimport type { SupabaseClient } from '@supabase/supabase-js';\nimport type { AbstractPowerSyncDatabase, ClassifiedError, CrudEntry } from '../core/types';\nimport type { LoggerAdapter } from '../platform/types';\nimport type { ConflictHandler, ConflictDetectionConfig } from '../conflicts/types';\nimport type { ConflictBus } from '../conflicts/conflict-bus';\n// Re-export ConflictBus type for convenience\nexport type { ConflictBus } from '../conflicts/conflict-bus';\n\n// ─── Connector Configuration ─────────────────────────────────────────────────\n\n/**\n * Circuit breaker configuration for the connector.\n * Prevents cascading failures by stopping requests when service is down.\n */\nexport interface ConnectorCircuitBreakerConfig {\n /**\n * Enable circuit breaker pattern.\n * @default false\n */\n enabled: boolean;\n /**\n * Number of failures required to trip the circuit.\n * @default 5\n */\n failureThreshold?: number;\n /**\n * Time window in ms for counting failures.\n * @default 60000 (60 seconds)\n */\n failureWindowMs?: number;\n /**\n * Initial delay before transitioning from OPEN to HALF_OPEN.\n * @default 1000 (1 second)\n */\n initialRecoveryDelayMs?: number;\n /**\n * Maximum delay for exponential backoff.\n * @default 32000 (32 seconds)\n */\n maxRecoveryDelayMs?: number;\n}\n\n/**\n * Options for creating a SupabaseConnector\n */\nexport interface SupabaseConnectorOptions {\n /** Supabase client instance */\n supabaseClient: SupabaseClient;\n /** PowerSync service URL */\n powerSyncUrl: string;\n /**\n * Optional: Custom schema routing function.\n * Determines which Supabase schema a table belongs to.\n * @default Returns 'public' for all tables\n */\n schemaRouter?: SchemaRouter;\n /**\n * Optional: Custom CRUD handler for complex mutations.\n * Allows overriding default upsert/update/delete behavior.\n */\n crudHandler?: CrudHandler;\n /** Logger for debugging */\n logger?: LoggerAdapter;\n /** Called when a transaction is successfully uploaded */\n onTransactionSuccess?: (entries: CrudEntry[]) => void;\n /** Called when a transaction fails to upload */\n onTransactionFailure?: (entries: CrudEntry[], error: Error, classified: ClassifiedError) => void;\n /** Called when a transaction is fully completed (after transaction.complete()) */\n onTransactionComplete?: (entries: CrudEntry[]) => void;\n /** Function to check if upload should proceed. Used for sync mode gating. */\n shouldUpload?: () => boolean;\n /**\n * Optional: Configuration for version-based conflict detection.\n * When enabled, checks for conflicts before uploading changes.\n */\n conflictDetection?: ConflictDetectionConfig;\n /**\n * Optional: Handler for conflict resolution.\n * If not provided, conflicts are logged and upload proceeds.\n */\n conflictHandler?: ConflictHandler;\n /**\n * Optional: Event bus for publishing conflict events.\n * Use this to notify the UI layer about detected conflicts.\n */\n conflictBus?: ConflictBus;\n /**\n * Optional: Configuration for retry behavior on upload failures.\n * Allows customizing retry attempts, delays, and backoff for different error types.\n * @default DEFAULT_RETRY_CONFIG\n */\n retryConfig?: Partial<RetryConfig>;\n /**\n * Optional: Configuration for circuit breaker pattern.\n * When enabled, prevents cascading failures by stopping requests when\n * the service is experiencing high failure rates.\n * @default { enabled: false }\n */\n circuitBreaker?: ConnectorCircuitBreakerConfig;\n /**\n * Optional: Middleware chain for classifying upload errors.\n * Allows customizing error handling for specific tables or error codes.\n *\n * Middleware functions are called in order until one returns a non-'continue' result:\n * - 'success': Treat as successful (e.g., idempotent duplicate)\n * - 'retry': Use the retry configuration to retry the operation\n * - 'discard': Permanent failure, remove from queue without retry\n * - 'continue': Pass to the next middleware in the chain\n *\n * If all middleware return 'continue', the default classification is used.\n *\n * @example\n * ```typescript\n * uploadErrorMiddleware: [\n * // Treat duplicate key as success for idempotent tables\n * idempotentTables(['ReadReceipt', 'Like', 'Bookmark']),\n * // Discard FK violations (orphaned records)\n * discardOrphaned(),\n * // Custom handling\n * ({ entry, pgCode }) => {\n * if (entry.table === 'MyTable' && pgCode === '23505') {\n * return 'success';\n * }\n * return 'continue';\n * },\n * ]\n * ```\n */\n uploadErrorMiddleware?: UploadErrorMiddleware[];\n}\n\n/**\n * Configuration passed to PowerSyncProvider for connector setup\n */\nexport interface ConnectorConfig {\n /**\n * Custom schema routing function.\n * Determines which Supabase schema a table belongs to.\n * @default Returns 'public' for all tables\n *\n * @example\n * ```typescript\n * schemaRouter: (table) => {\n * if (['Profile', 'Comment'].includes(table)) return 'core';\n * return 'public';\n * }\n * ```\n */\n schemaRouter?: SchemaRouter;\n\n /**\n * Custom CRUD handler for complex mutations.\n * @default Uses standard upsert/update/delete operations\n */\n crudHandler?: CrudHandler;\n\n /**\n * Token refresh configuration.\n */\n tokenRefresh?: {\n /** Refresh token when it expires within this many seconds (default: 60) */\n refreshThresholdSeconds?: number;\n };\n\n /**\n * Optional retry configuration for upload failures.\n * Allows customizing retry attempts, delays, and backoff for different error types.\n * @default DEFAULT_RETRY_CONFIG\n */\n retryConfig?: Partial<RetryConfig>;\n}\n\n// ─── Schema Routing ──────────────────────────────────────────────────────────\n\n/**\n * Function that determines which Supabase schema a table belongs to.\n *\n * @param tableName - The name of the table\n * @returns The schema name (e.g., 'public', 'core')\n */\nexport type SchemaRouter = (tableName: string) => string;\n\n/**\n * Default schema router that returns 'public' for all tables\n */\nexport const defaultSchemaRouter: SchemaRouter = () => 'public';\n\n// ─── CRUD Handling ───────────────────────────────────────────────────────────\n\n/**\n * Custom handler for CRUD operations.\n *\n * Return `true` from a handler to indicate the operation was handled.\n * Return `false` to fall back to default behavior.\n */\nexport interface CrudHandler {\n /**\n * Handle a PUT operation (insert or replace).\n * @param entry - The CRUD entry\n * @param supabase - Supabase client\n * @param schema - The resolved schema for this table\n * @returns true if handled, false to use default behavior\n */\n handlePut?(entry: CrudEntry, supabase: SupabaseClient, schema: string): Promise<boolean>;\n\n /**\n * Handle a PATCH operation (update).\n * @param entry - The CRUD entry\n * @param supabase - Supabase client\n * @param schema - The resolved schema for this table\n * @returns true if handled, false to use default behavior\n */\n handlePatch?(entry: CrudEntry, supabase: SupabaseClient, schema: string): Promise<boolean>;\n\n /**\n * Handle a DELETE operation.\n * @param entry - The CRUD entry\n * @param supabase - Supabase client\n * @param schema - The resolved schema for this table\n * @returns true if handled, false to use default behavior\n */\n handleDelete?(entry: CrudEntry, supabase: SupabaseClient, schema: string): Promise<boolean>;\n}\n\n// ─── Credentials ─────────────────────────────────────────────────────────────\n\n/**\n * Credentials returned by fetchCredentials\n */\nexport interface PowerSyncCredentials {\n /** PowerSync service endpoint URL */\n endpoint: string;\n /** JWT token for authentication */\n token: string;\n /** When the token expires */\n expiresAt?: Date;\n}\n\n// ─── Retry Configuration ─────────────────────────────────────────────────────\n\n/**\n * Configuration for a single retry category\n */\nexport interface RetryStrategyConfig {\n /** Maximum number of retry attempts */\n maxRetries: number;\n /** Initial delay in milliseconds */\n baseDelayMs: number;\n /** Maximum delay cap in milliseconds */\n maxDelayMs: number;\n /** Multiplier for exponential backoff */\n backoffMultiplier: number;\n}\n\n/**\n * Full retry configuration for the connector\n */\nexport interface RetryConfig {\n /** Retry config for transient errors (network, server 5xx) */\n transient: RetryStrategyConfig;\n /** Retry config for permanent errors (validation, constraints) */\n permanent: RetryStrategyConfig;\n /** Retry config for RLS/permission errors (42501, row-level security violations) */\n rls: RetryStrategyConfig;\n}\n\n/**\n * Default retry configuration\n *\n * Uses fast exponential backoff for transient errors (network issues):\n * - Transient: 1s → 2s → 4s\n *\n * RLS/permission errors (42501, row-level security violations) use extended delays\n * because parent data may need time to sync before child records can be inserted.\n * - RLS: 30s → 60s → 120s → 120s → 120s (5 retries over ~7.5 minutes)\n *\n * Other permanent errors (validation, constraints) get NO retries\n * because they will never succeed - fail fast and surface to user.\n * PowerSync's native retry mechanism will re-attempt on next sync cycle.\n */\nexport const DEFAULT_RETRY_CONFIG: RetryConfig = {\n transient: {\n maxRetries: 3,\n baseDelayMs: 1000,\n // 1 second initial delay\n maxDelayMs: 4000,\n // 4 second cap\n backoffMultiplier: 2 // 1s → 2s → 4s\n },\n permanent: {\n maxRetries: 0,\n // No retries - permanent errors won't succeed\n baseDelayMs: 1000,\n // Irrelevant with 0 retries, but needed for type\n maxDelayMs: 1000,\n // Irrelevant with 0 retries\n backoffMultiplier: 1 // Irrelevant with 0 retries\n },\n rls: {\n maxRetries: 5,\n // Extended retries for RLS errors\n baseDelayMs: 30000,\n // 30 seconds initial delay\n maxDelayMs: 120000,\n // 2 minutes max delay\n backoffMultiplier: 2 // 30s → 60s → 120s → 120s → 120s\n }\n};\n\n// ─── Auth Abstraction ────────────────────────────────────────────────────────\n\n/**\n * Session representation for authentication.\n * Used by AuthProvider to communicate auth state.\n */\nexport interface Session {\n /** Access token for API requests */\n accessToken: string;\n /** When the token expires (optional) */\n expiresAt?: Date;\n /** User information (optional) */\n user?: {\n id: string;\n };\n}\n\n/**\n * Authentication provider interface.\n * Abstracts auth for the connector - works with any auth backend.\n *\n * @example\n * ```typescript\n * // Using built-in Supabase adapter\n * const auth = createSupabaseAuth(supabaseClient);\n *\n * // Custom implementation\n * const auth: AuthProvider = {\n * getSession: async () => ({ accessToken: myToken }),\n * refreshSession: async () => {\n * const newToken = await myAuthService.refresh();\n * return { accessToken: newToken };\n * },\n * onAuthStateChange: (cb) => {\n * return myAuthService.subscribe(cb);\n * },\n * };\n * ```\n */\nexport interface AuthProvider {\n /**\n * Get the current session.\n * @returns Current session or null if not authenticated\n */\n getSession(): Promise<Session | null>;\n\n /**\n * Refresh the current session.\n * @throws Error if refresh fails (e.g., refresh token expired)\n * @returns New session with fresh access token\n */\n refreshSession(): Promise<Session>;\n\n /**\n * Subscribe to auth state changes.\n * @param cb - Callback fired when auth state changes\n * @returns Unsubscribe function\n */\n onAuthStateChange(cb: (session: Session | null) => void): () => void;\n}\n\n// ─── Upload Error Middleware ─────────────────────────────────────────────────\n\n/**\n * Classification result for upload errors.\n * Determines how the connector should handle a failed upload.\n *\n * - 'success': Treat as successful (e.g., idempotent duplicate)\n * - 'retry': Use the retry configuration to retry the operation\n * - 'discard': Permanent failure, remove from queue without retry\n * - 'continue': Pass to the next middleware in the chain\n * - 'fail_transaction': Abort the entire transaction immediately without retry\n */\nexport type UploadErrorClassification = 'success' | 'retry' | 'discard' | 'continue' | 'fail_transaction';\n\n/**\n * Context provided to upload error middleware functions.\n * Contains all information needed to classify an upload error.\n */\nexport interface UploadErrorContext {\n /** The CRUD entry that failed */\n entry: CrudEntry;\n /** The error that occurred */\n error: Error;\n /** PostgreSQL error code if available (e.g., '23505' for unique violation) */\n pgCode: string | undefined;\n /** HTTP status code if available */\n httpStatusCode: number | undefined;\n /** The classified error from the default classifier */\n classified: ClassifiedError;\n /** The original error object (may contain additional properties) */\n originalError: unknown;\n /** The schema the table belongs to */\n schema: string;\n /** Supabase client for queries (use sparingly - prefer local checks) */\n supabase: SupabaseClient;\n}\n\n/**\n * Middleware function for classifying upload errors.\n *\n * Middleware functions are called in order until one returns a non-'continue' result.\n * Return 'continue' to pass to the next middleware in the chain.\n *\n * @example\n * ```typescript\n * const myMiddleware: UploadErrorMiddleware = ({ entry, pgCode }) => {\n * if (entry.table === 'MyTable' && pgCode === '23505') {\n * return 'success'; // Treat as successful\n * }\n * return 'continue'; // Let the next middleware handle it\n * };\n * ```\n */\nexport type UploadErrorMiddleware = (context: UploadErrorContext) => Promise<UploadErrorClassification> | UploadErrorClassification;\n\n// ─── Re-export from Core ─────────────────────────────────────────────────────\n\n// Re-export PowerSyncBackendConnector from core/types to maintain API compatibility\nexport type { PowerSyncBackendConnector } from '../core/types';","/**\n * Upload Error Middleware for @pol-studios/powersync\n *\n * Provides a middleware pattern for classifying and handling upload errors.\n * Includes built-in factory functions for common patterns and an executor\n * for running the middleware chain.\n */\n\nimport type { UploadErrorClassification, UploadErrorContext, UploadErrorMiddleware } from '../types';\n\n// ─── Built-in Middleware Factories ───────────────────────────────────────────\n\n/**\n * Create middleware that treats duplicate key errors (23505) as success\n * for specified tables.\n *\n * This is useful for idempotent operations where re-inserting the same\n * record should be treated as a success, not an error.\n *\n * @param tables - Array of table names to treat as idempotent\n * @returns Middleware function\n *\n * @example\n * ```typescript\n * uploadErrorMiddleware: [\n * idempotentTables(['ReadReceipt', 'Like', 'Bookmark']),\n * ]\n * ```\n */\nexport function idempotentTables(tables: string[]): UploadErrorMiddleware {\n const tableSet = new Set(tables);\n return ({\n entry,\n pgCode\n }) => {\n if (tableSet.has(entry.table) && pgCode === '23505') {\n return 'success';\n }\n return 'continue';\n };\n}\n\n/**\n * Create middleware that discards entries with foreign key violations (23503).\n *\n * This is useful for handling orphaned records where the parent record\n * was deleted before the child record could be uploaded.\n *\n * @returns Middleware function\n *\n * @example\n * ```typescript\n * uploadErrorMiddleware: [\n * discardOrphaned(),\n * ]\n * ```\n */\nexport function discardOrphaned(): UploadErrorMiddleware {\n return ({\n pgCode\n }) => {\n if (pgCode === '23503') {\n return 'discard';\n }\n return 'continue';\n };\n}\n\n/**\n * Create middleware that discards entries with specific PostgreSQL error codes.\n *\n * @param codes - Array of PostgreSQL error codes to discard\n * @returns Middleware function\n *\n * @example\n * ```typescript\n * uploadErrorMiddleware: [\n * // Discard check constraint violations and not-null violations\n * discardOnPgCodes(['23514', '23502']),\n * ]\n * ```\n */\nexport function discardOnPgCodes(codes: string[]): UploadErrorMiddleware {\n const codeSet = new Set(codes);\n return ({\n pgCode\n }) => {\n if (pgCode && codeSet.has(pgCode)) {\n return 'discard';\n }\n return 'continue';\n };\n}\n\n/**\n * Create middleware that treats specific PostgreSQL error codes as success\n * for specific tables.\n *\n * @param config - Map of table names to arrays of PostgreSQL error codes to treat as success\n * @returns Middleware function\n *\n * @example\n * ```typescript\n * uploadErrorMiddleware: [\n * successOnPgCodes({\n * 'ReadReceipt': ['23505'], // Duplicate key is OK\n * 'Notification': ['23505', '23503'], // Duplicate and FK violations are OK\n * }),\n * ]\n * ```\n */\nexport function successOnPgCodes(config: Record<string, string[]>): UploadErrorMiddleware {\n return ({\n entry,\n pgCode\n }) => {\n const codes = config[entry.table];\n if (codes && pgCode && codes.includes(pgCode)) {\n return 'success';\n }\n return 'continue';\n };\n}\n\n/**\n * Create middleware that retries entries on specific HTTP status codes.\n *\n * @param statusCodes - Array of HTTP status codes to retry\n * @returns Middleware function\n *\n * @example\n * ```typescript\n * uploadErrorMiddleware: [\n * // Retry on rate limiting and server errors\n * retryOnHttpStatus([429, 500, 502, 503, 504]),\n * ]\n * ```\n */\nexport function retryOnHttpStatus(statusCodes: number[]): UploadErrorMiddleware {\n const statusSet = new Set(statusCodes);\n return ({\n httpStatusCode\n }) => {\n if (httpStatusCode && statusSet.has(httpStatusCode)) {\n return 'retry';\n }\n return 'continue';\n };\n}\n\n/**\n * Create middleware that discards entries on specific HTTP status codes.\n *\n * @param statusCodes - Array of HTTP status codes to discard\n * @returns Middleware function\n *\n * @example\n * ```typescript\n * uploadErrorMiddleware: [\n * // Discard on 400 Bad Request (malformed data)\n * discardOnHttpStatus([400]),\n * ]\n * ```\n */\nexport function discardOnHttpStatus(statusCodes: number[]): UploadErrorMiddleware {\n const statusSet = new Set(statusCodes);\n return ({\n httpStatusCode\n }) => {\n if (httpStatusCode && statusSet.has(httpStatusCode)) {\n return 'discard';\n }\n return 'continue';\n };\n}\n\n/**\n * Create middleware that applies table-specific handling.\n *\n * @param handlers - Map of table names to handler functions\n * @returns Middleware function\n *\n * @example\n * ```typescript\n * uploadErrorMiddleware: [\n * tableHandlers({\n * 'AuditLog': () => 'discard', // Never retry audit logs\n * 'UserSession': ({ pgCode }) =>\n * pgCode === '23505' ? 'success' : 'continue',\n * }),\n * ]\n * ```\n */\nexport function tableHandlers(handlers: Record<string, UploadErrorMiddleware>): UploadErrorMiddleware {\n return context => {\n const handler = handlers[context.entry.table];\n if (handler) {\n return handler(context);\n }\n return 'continue';\n };\n}\n\n// ─── Middleware Executor ─────────────────────────────────────────────────────\n\n/**\n * Run upload error middleware chain and return the classification.\n *\n * Executes middleware in order until one returns a non-'continue' result.\n * If all middleware return 'continue', returns the default classification.\n *\n * @param context - The error context to pass to middleware\n * @param middleware - Array of middleware functions to execute\n * @param defaultClassification - Classification to use if all middleware return 'continue'\n * @returns The final classification\n *\n * @example\n * ```typescript\n * const classification = await runUploadErrorMiddleware(\n * context,\n * [idempotentTables(['ReadReceipt']), discardOrphaned()],\n * 'retry' // Default if no middleware matches\n * );\n * ```\n */\nexport async function runUploadErrorMiddleware(context: UploadErrorContext, middleware: UploadErrorMiddleware[], defaultClassification: UploadErrorClassification = 'retry'): Promise<UploadErrorClassification> {\n for (const mw of middleware) {\n const result = await mw(context);\n if (result !== 'continue') {\n return result;\n }\n }\n return defaultClassification;\n}\n\n/**\n * Synchronous version of runUploadErrorMiddleware for middleware that don't use async.\n *\n * @param context - The error context to pass to middleware\n * @param middleware - Array of middleware functions to execute\n * @param defaultClassification - Classification to use if all middleware return 'continue'\n * @returns The final classification\n */\nexport function runUploadErrorMiddlewareSync(context: UploadErrorContext, middleware: UploadErrorMiddleware[], defaultClassification: UploadErrorClassification = 'retry'): UploadErrorClassification {\n for (const mw of middleware) {\n const result = mw(context);\n // If the middleware returns a promise, throw an error\n if (result && typeof result === 'object' && 'then' in result) {\n throw new Error('Async middleware detected in runUploadErrorMiddlewareSync. ' + 'Use runUploadErrorMiddleware instead.');\n }\n // At this point we know result is UploadErrorClassification (not a Promise)\n const syncResult = result as UploadErrorClassification;\n if (syncResult !== 'continue') {\n return syncResult;\n }\n }\n return defaultClassification;\n}","/**\n * Custom errors for the @pol-studios/powersync connector.\n *\n * These errors are used by the upload error middleware to control\n * how failed CRUD entries are handled.\n */\n\n/**\n * Error thrown when a CRUD entry is discarded by middleware.\n * When this error is thrown, the entry is removed from the queue without retry.\n */\nexport class DiscardEntryError extends Error {\n constructor(message: string) {\n super(message);\n this.name = 'DiscardEntryError';\n }\n}\n\n/**\n * Error thrown when a transaction is aborted by middleware.\n * When thrown, stops the entire transaction immediately without retry.\n */\nexport class TransactionAbortError extends Error {\n constructor(message: string, public readonly cause?: Error) {\n super(message);\n this.name = 'TransactionAbortError';\n }\n}","/**\n * Conflict Detection for @pol-studios/powersync\n *\n * Provides version-based conflict detection using AuditLog attribution.\n * Only queries AuditLog when version mismatch is detected.\n */\n\nimport type { SupabaseClient } from '@supabase/supabase-js';\nimport type { AbstractPowerSyncDatabase } from '../core/types';\nimport type { FieldConflict, ConflictCheckResult, ConflictDetectionConfig } from './types';\n\n/**\n * Error thrown when conflict detection fails due to external issues\n * (e.g., AuditLog query failure). Callers should handle this and decide\n * whether to retry, fail the sync, or escalate to the user.\n */\nexport class ConflictDetectionError extends Error {\n constructor(message: string, options?: {\n cause?: unknown;\n }) {\n super(message);\n this.name = 'ConflictDetectionError';\n this.cause = options?.cause;\n }\n}\n\n/**\n * Deep equality comparison for detecting field changes.\n *\n * Handles:\n * - Primitive types (string, number, boolean, null, undefined)\n * - NaN values (NaN === NaN returns true)\n * - Date objects (compared by getTime() value)\n * - Arrays (recursive element comparison)\n * - Plain objects (recursive property comparison)\n * - Circular references (protected by maxDepth limit)\n *\n * @param a - First value to compare\n * @param b - Second value to compare\n * @param maxDepth - Maximum recursion depth to prevent infinite loops (default: 10)\n * @returns true if values are deeply equal, false otherwise\n */\nfunction deepEqual(a: unknown, b: unknown, maxDepth = 10): boolean {\n // Strict equality handles primitives, same reference, null === null, undefined === undefined\n if (a === b) return true;\n\n // Handle NaN (NaN !== NaN in JavaScript, but we want NaN === NaN for conflict detection)\n if (typeof a === 'number' && typeof b === 'number') {\n if (Number.isNaN(a) && Number.isNaN(b)) return true;\n }\n\n // Handle null and undefined (both must be checked since undefined !== null)\n if (a === null || a === undefined || b === null || b === undefined) return false;\n\n // Type mismatch\n if (typeof a !== typeof b) return false;\n\n // Non-object types that aren't equal already returned above\n if (typeof a !== 'object') return false;\n\n // Depth limit to prevent infinite loops from circular references\n if (maxDepth <= 0) {\n console.warn('[deepEqual] Max depth reached, falling back to reference equality');\n return a === b;\n }\n\n // Handle Date objects - compare by timestamp\n if (a instanceof Date && b instanceof Date) {\n return a.getTime() === b.getTime();\n }\n\n // Handle Date vs non-Date mismatch\n if (a instanceof Date || b instanceof Date) return false;\n\n // Array type mismatch\n if (Array.isArray(a) !== Array.isArray(b)) return false;\n const keysA = Object.keys(a as object);\n const keysB = Object.keys(b as object);\n if (keysA.length !== keysB.length) return false;\n for (const key of keysA) {\n if (!keysB.includes(key)) return false;\n if (!deepEqual((a as Record<string, unknown>)[key], (b as Record<string, unknown>)[key], maxDepth - 1)) {\n return false;\n }\n }\n return true;\n}\n\n/**\n * Regex to validate table names and prevent SQL injection.\n * Only allows alphanumeric characters and underscores, starting with a letter or underscore.\n */\nconst TABLE_NAME_REGEX = /^[a-zA-Z_][a-zA-Z0-9_]*$/;\n\n/**\n * Validates a table name to prevent SQL injection.\n * @throws Error if the table name is invalid\n */\nfunction validateTableName(table: string): void {\n if (!TABLE_NAME_REGEX.test(table)) {\n throw new Error(`Invalid table name: ${table}`);\n }\n}\nconst DEFAULT_IGNORED_FIELDS = ['updatedAt', 'createdAt', '_version', 'id'];\n\n/**\n * Detect conflicts between local pending changes and server state.\n *\n * Uses a two-step approach:\n * 1. Version match (local._version == server._version) → Sync immediately\n * 2. Version mismatch → Query AuditLog for field changes and attribution\n *\n * @param table - The table name\n * @param recordId - The record ID\n * @param localVersion - Version number from local SQLite\n * @param serverVersion - Version number from server (fetched separately)\n * @param pendingChanges - Fields with local changes to sync\n * @param supabase - Supabase client for AuditLog queries\n * @param config - Optional detection configuration\n * @returns Conflict check result with field-level details\n */\nexport async function detectConflicts(table: string, recordId: string, localVersion: number, serverVersion: number, pendingChanges: Record<string, unknown>, supabase: SupabaseClient, config?: ConflictDetectionConfig): Promise<ConflictCheckResult> {\n const ignoredFields = new Set([...DEFAULT_IGNORED_FIELDS, ...(config?.ignoredFields ?? [])]);\n\n // Filter out ignored fields from pending changes\n const filteredPendingChanges: Record<string, unknown> = {};\n for (const [field, value] of Object.entries(pendingChanges)) {\n if (!ignoredFields.has(field)) {\n filteredPendingChanges[field] = value;\n }\n }\n\n // Step 1: Version match = no conflict possible\n if (localVersion === serverVersion) {\n return {\n hasConflict: false,\n conflicts: [],\n nonConflictingChanges: Object.keys(filteredPendingChanges),\n table,\n recordId\n };\n }\n\n // Step 2: Version mismatch - query AuditLog for changes since our version\n const {\n data: auditLogs,\n error\n } = await supabase.schema('core').from('AuditLog').select('oldRecord, newRecord, changeBy, changeAt').eq('tableName', table).eq('recordId_text', recordId).order('changeAt', {\n ascending: false\n }).limit(20); // Recent changes should be sufficient\n\n if (error) {\n // Don't assume no conflict - throw and let caller decide how to handle\n throw new ConflictDetectionError(`AuditLog query failed for ${table}/${recordId}`, {\n cause: error\n });\n }\n\n // Build map of server-changed fields with attribution\n // Key: field name, Value: most recent change info\n const serverChanges = new Map<string, {\n newValue: unknown;\n changedBy: string | null;\n changedAt: Date;\n }>();\n for (const log of auditLogs ?? []) {\n const oldRec = log.oldRecord as Record<string, unknown> | null;\n const newRec = log.newRecord as Record<string, unknown> | null;\n if (!oldRec || !newRec) continue;\n for (const [field, newValue] of Object.entries(newRec)) {\n // Skip ignored fields\n if (ignoredFields.has(field)) continue;\n\n // Only track if field actually changed AND we don't already have a more recent change\n // Use deep equality to properly compare arrays and objects\n if (!deepEqual(oldRec[field], newValue) && !serverChanges.has(field)) {\n serverChanges.set(field, {\n newValue,\n changedBy: log.changeBy as string | null,\n changedAt: new Date(log.changeAt as string)\n });\n }\n }\n }\n\n // Compare pending changes against server changes\n const conflicts: FieldConflict[] = [];\n const nonConflictingChanges: string[] = [];\n for (const [field, localValue] of Object.entries(filteredPendingChanges)) {\n if (serverChanges.has(field)) {\n // This field was changed on server - conflict!\n const serverChange = serverChanges.get(field)!;\n conflicts.push({\n field,\n localValue,\n serverValue: serverChange.newValue,\n changedBy: serverChange.changedBy,\n changedAt: serverChange.changedAt\n });\n } else {\n // Field wasn't changed on server - safe to sync\n nonConflictingChanges.push(field);\n }\n }\n return {\n hasConflict: conflicts.length > 0,\n conflicts,\n nonConflictingChanges,\n table,\n recordId\n };\n}\n\n/**\n * Check if a table has a _version column for conflict detection.\n *\n * @param table - The table name\n * @param db - PowerSync database instance\n * @returns True if the table has version tracking\n */\nexport async function hasVersionColumn(table: string, db: AbstractPowerSyncDatabase): Promise<boolean> {\n try {\n // Validate table name to prevent SQL injection\n validateTableName(table);\n\n // Query the PowerSync internal schema for column info\n const result = await db.getAll<{\n name: string;\n }>(`PRAGMA table_info(\"${table}\")`);\n return result.some(col => col.name === '_version');\n } catch {\n return false;\n }\n}\n\n/**\n * Fetch the current server version for a record.\n *\n * @param table - The table name\n * @param recordId - The record ID\n * @param schema - The Supabase schema (default: 'public')\n * @param supabase - Supabase client\n * @returns The server version number, or null if record not found\n */\nexport async function fetchServerVersion(table: string, recordId: string, schema: string, supabase: SupabaseClient): Promise<number | null> {\n const query = schema === 'public' ? supabase.from(table) : (supabase.schema(schema) as unknown as ReturnType<typeof supabase.schema>).from(table);\n const {\n data,\n error\n } = await query.select('_version').eq('id', recordId).single();\n if (error || !data) {\n return null;\n }\n return (data as {\n _version?: number;\n })._version ?? null;\n}\n\n/**\n * Get the local version for a record from PowerSync SQLite.\n *\n * @param table - The table name\n * @param recordId - The record ID\n * @param db - PowerSync database instance\n * @returns The local version number, or null if not found\n */\nexport async function getLocalVersion(table: string, recordId: string, db: AbstractPowerSyncDatabase): Promise<number | null> {\n // Validate table name to prevent SQL injection\n validateTableName(table);\n const result = await db.get<{\n _version?: number;\n }>(`SELECT _version FROM \"${table}\" WHERE id = ?`, [recordId]);\n return result?._version ?? null;\n}","/**\n * Supabase Connector for PowerSync\n *\n * A generic, configurable connector that handles:\n * - Authentication with Supabase JWT tokens\n * - Uploading local changes back to Supabase\n * - Schema routing for multi-schema databases\n * - Custom CRUD handling for complex operations\n * - Version-based conflict detection (when enabled)\n */\n\nimport type { SupabaseClient } from '@supabase/supabase-js';\nimport type { AbstractPowerSyncDatabase, ClassifiedError, CrudEntry } from '../core/types';\nimport type { LoggerAdapter } from '../platform/types';\nimport type { SupabaseConnectorOptions, PowerSyncBackendConnector, PowerSyncCredentials, SchemaRouter, CrudHandler, RetryConfig, UploadErrorMiddleware, UploadErrorContext } from './types';\nimport { runUploadErrorMiddleware } from './middleware';\nimport { DiscardEntryError, TransactionAbortError } from './errors';\nimport type { ConflictBus } from '../conflicts/conflict-bus';\nimport type { ConflictHandler, ConflictDetectionConfig, ConflictCheckResult, ConflictResolution } from '../conflicts/types';\nimport { defaultSchemaRouter, DEFAULT_RETRY_CONFIG } from './types';\nimport { classifySupabaseError, isRlsError, extractHttpStatusCode } from '../core/errors';\nimport { detectConflicts, fetchServerVersion, getLocalVersion, hasVersionColumn } from '../conflicts/detect';\nimport { withExponentialBackoff } from '../utils/retry';\n\n/**\n * Custom error for validation failures (payload size, required fields, etc.)\n */\nclass ValidationError extends Error {\n constructor(message: string) {\n super(message);\n this.name = 'ValidationError';\n }\n}\n\n/**\n * Check if an error is an authentication/authorization error (401 or JWT-related).\n * Used to trigger token refresh on auth failures during CRUD operations.\n *\n * Uses specific patterns with word boundaries to avoid false positives\n * (e.g., \"Cache entry expired\" should NOT match).\n */\nfunction isAuthError(error: unknown): boolean {\n if (!error) return false;\n\n // Check for HTTP 401/403 status codes\n if (typeof error === 'object' && error !== null) {\n const statusCode = (error as {\n status?: number;\n statusCode?: number;\n }).status ?? (error as {\n status?: number;\n statusCode?: number;\n }).statusCode;\n if (statusCode === 401 || statusCode === 403) {\n return true;\n }\n\n // Also check string code for compatibility\n const code = (error as {\n code?: string | number;\n }).code;\n if (code === '401' || code === '403' || code === '42501') {\n return true;\n }\n }\n const message = error instanceof Error ? error.message.toLowerCase() : String(error).toLowerCase();\n\n // Use specific patterns to avoid false positives like \"Cache entry expired\"\n const authPatterns = [/\\bjwt\\s+(expired|invalid|malformed)\\b/, /\\btoken\\s+(expired|invalid|revoked)\\b/, /\\bsession\\s+(expired|invalid)\\b/, /\\bunauthorized\\s*(access|request|error)?\\b/, /\\baccess\\s+(denied|forbidden)\\b/, /\\bnot\\s+authorized\\b/, /\\bauth(entication)?\\s+(failed|error|required)\\b/, /\\bpermission\\s+denied\\b/, /\\binvalid\\s+(credentials|api[_\\s]?key)\\b/, /\\brls\\b.*\\bpolicy\\b/, /\\brow[_\\s]?level[_\\s]?security\\b/, /\\b42501\\b/ // PostgreSQL insufficient privilege error code\n ];\n return authPatterns.some(pattern => pattern.test(message));\n}\n\n/**\n * Wrap a promise with a timeout that cleans up when the race completes.\n * Prevents timer leaks that would cause unhandled promise rejections.\n *\n * @param promise - The promise to wrap\n * @param ms - Timeout in milliseconds\n * @param message - Error message if timeout occurs\n */\nfunction withTimeout<T>(promise: Promise<T>, ms: number, message: string): Promise<T> {\n let timeoutId: ReturnType<typeof setTimeout>;\n const timeoutPromise = new Promise<never>((_, reject) => {\n timeoutId = setTimeout(() => reject(new Error(message)), ms);\n });\n return Promise.race([promise, timeoutPromise]).finally(() => clearTimeout(timeoutId));\n}\n\n// Maximum size limit for CRUD payloads (with margin for safety)\nconst MAX_PAYLOAD_SIZE = 900 * 1024; // 900KB\n\n/**\n * Update type enum matching @powersync/common\n */\nenum UpdateType {\n PUT = 'PUT',\n PATCH = 'PATCH',\n DELETE = 'DELETE',\n}\n\n/**\n * Group CRUD entries by table name for batched processing.\n */\nfunction groupEntriesByTable(entries: CrudEntry[]): Map<string, CrudEntry[]> {\n const grouped = new Map<string, CrudEntry[]>();\n for (const entry of entries) {\n const existing = grouped.get(entry.table);\n if (existing) {\n existing.push(entry);\n } else {\n grouped.set(entry.table, [entry]);\n }\n }\n return grouped;\n}\n\n/**\n * Context for transaction finalization (DRY helper).\n */\ninterface TransactionFinalizationContext {\n transaction: {\n complete: () => Promise<void>;\n };\n successfulEntries: CrudEntry[];\n discardedEntries?: CrudEntry[];\n partialResolutions?: Array<{\n originalConflict: ConflictCheckResult;\n syncedFields: string[];\n }>;\n}\n\n/**\n * Generic Supabase connector for PowerSync.\n *\n * This connector handles authentication and CRUD uploads to Supabase.\n * It supports configurable schema routing and custom CRUD handlers\n * for complex use cases.\n *\n * @example Basic usage\n * ```typescript\n * const connector = new SupabaseConnector({\n * supabaseClient: supabase,\n * powerSyncUrl: 'https://your-powersync-instance.com',\n * });\n * ```\n *\n * @example With schema routing\n * ```typescript\n * const connector = new SupabaseConnector({\n * supabaseClient: supabase,\n * powerSyncUrl: 'https://your-powersync-instance.com',\n * schemaRouter: (table) => {\n * if (['Profile', 'Comment', 'CommentSection'].includes(table)) {\n * return 'core';\n * }\n * return 'public';\n * },\n * });\n * ```\n *\n * @example With custom CRUD handler\n * ```typescript\n * const connector = new SupabaseConnector({\n * supabaseClient: supabase,\n * powerSyncUrl: 'https://your-powersync-instance.com',\n * crudHandler: {\n * async handlePut(entry, supabase, schema) {\n * // Custom handling for specific tables\n * if (entry.table === 'SpecialTable') {\n * await myCustomUpsert(entry);\n * return true; // Handled\n * }\n * return false; // Use default\n * },\n * },\n * });\n * ```\n */\nexport class SupabaseConnector implements PowerSyncBackendConnector {\n private readonly supabase: SupabaseClient;\n private readonly powerSyncUrl: string;\n private readonly schemaRouter: SchemaRouter;\n private readonly crudHandler?: CrudHandler;\n private readonly logger?: LoggerAdapter;\n private readonly onTransactionSuccess?: (entries: CrudEntry[]) => void;\n private readonly onTransactionFailure?: (entries: CrudEntry[], error: Error, classified: ClassifiedError) => void;\n private readonly onTransactionComplete?: (entries: CrudEntry[]) => void;\n private readonly shouldUploadFn?: () => boolean;\n\n // Conflict detection configuration\n private readonly conflictDetection?: ConflictDetectionConfig;\n private readonly conflictHandler?: ConflictHandler;\n private readonly conflictBus?: ConflictBus;\n\n // Cache for version column existence checks (table -> hasVersionColumn)\n private versionColumnCache = new Map<string, boolean>();\n\n // Active project IDs for scoped sync (optional feature)\n private activeProjectIds: string[] = [];\n\n // Store resolutions for retry - when PowerSync retries, we apply stored resolutions\n // instead of re-detecting conflicts that the user already resolved\n private resolvedConflicts = new Map<string, ConflictResolution>();\n\n // Cleanup function for resolution listener subscription\n private unsubscribeResolution?: () => void;\n\n // Promise-based locking for version column checks to prevent duplicate queries\n private versionColumnPromises = new Map<string, Promise<boolean>>();\n\n // Flag to track if connector has been destroyed\n private isDestroyed = false;\n\n // Retry configuration\n private retryConfig: RetryConfig;\n\n // Track completion failures for circuit breaker logic\n // Maps transaction fingerprint (hash of entry IDs) to failure tracking info\n private completionFailures = new Map<string, {\n count: number;\n lastAttempt: Date;\n }>();\n private static readonly COMPLETION_MAX_FAILURES = 3;\n private static readonly COMPLETION_EXTENDED_TIMEOUT_MS = 60_000; // 60s timeout for retry\n private autoRetryPaused: boolean = false;\n\n // Per-entry cooldown tracking for failed operations\n // Maps entry key (table:id) to the timestamp when it can be retried\n private entryCooldowns = new Map<string, number>();\n private static readonly COOLDOWN_DURATION_MS = 60_000; // 1 minute cooldown after exhausting retries\n\n // Upload error middleware chain\n private readonly uploadErrorMiddleware: UploadErrorMiddleware[];\n constructor(options: SupabaseConnectorOptions) {\n this.supabase = options.supabaseClient;\n this.powerSyncUrl = options.powerSyncUrl;\n this.schemaRouter = options.schemaRouter ?? defaultSchemaRouter;\n this.crudHandler = options.crudHandler;\n this.logger = options.logger;\n this.onTransactionSuccess = options.onTransactionSuccess;\n this.onTransactionFailure = options.onTransactionFailure;\n this.onTransactionComplete = options.onTransactionComplete;\n this.shouldUploadFn = options.shouldUpload;\n\n // Conflict detection options\n this.conflictDetection = options.conflictDetection;\n this.conflictHandler = options.conflictHandler;\n this.conflictBus = options.conflictBus;\n\n // Initialize retry configuration by merging user options with defaults\n this.retryConfig = {\n transient: {\n ...DEFAULT_RETRY_CONFIG.transient,\n ...options.retryConfig?.transient\n },\n permanent: {\n ...DEFAULT_RETRY_CONFIG.permanent,\n ...options.retryConfig?.permanent\n },\n rls: {\n ...DEFAULT_RETRY_CONFIG.rls,\n ...options.retryConfig?.rls\n }\n };\n\n // Initialize upload error middleware chain\n this.uploadErrorMiddleware = options.uploadErrorMiddleware ?? [];\n\n // Subscribe to resolution events from the conflict bus\n // This stores resolutions so that on retry, we apply them instead of re-detecting\n if (this.conflictBus) {\n this.unsubscribeResolution = this.conflictBus.onResolution((table, recordId, resolution) => {\n // Key by table:recordId to avoid collisions across tables with same UUID\n const key = `${table}:${recordId}`;\n if (__DEV__) {\n console.log('[Connector] Storing resolution for retry:', {\n table,\n recordId,\n key,\n resolution\n });\n }\n this.resolvedConflicts.set(key, resolution);\n });\n }\n }\n\n /**\n * Clean up resources (unsubscribe from event listeners).\n * Call this when the connector is no longer needed.\n */\n destroy(): void {\n this.isDestroyed = true;\n if (this.unsubscribeResolution) {\n this.unsubscribeResolution();\n this.unsubscribeResolution = undefined;\n }\n this.resolvedConflicts.clear();\n this.versionColumnPromises.clear();\n this.completionFailures.clear();\n }\n\n /**\n * Generate a fingerprint for a set of entries to track completion failures.\n * Uses sorted entry IDs to create a consistent fingerprint regardless of order.\n */\n private generateTransactionFingerprint(entries: CrudEntry[]): string {\n // Sort IDs for consistency and join them\n const sortedIds = entries.map(e => `${e.table}:${e.id}`).sort();\n return sortedIds.join('|');\n }\n\n // ─── Retry Control Methods ─────────────────────────────────────────────────\n\n /**\n * Pause automatic retry of failed uploads.\n * Use this when the user goes offline intentionally or wants manual control.\n */\n pauseAutoRetry(): void {\n this.autoRetryPaused = true;\n if (__DEV__) {\n console.log('[Connector] Auto-retry paused');\n }\n }\n\n /**\n * Resume automatic retry of failed uploads.\n */\n resumeAutoRetry(): void {\n this.autoRetryPaused = false;\n if (__DEV__) {\n console.log('[Connector] Auto-retry resumed');\n }\n }\n\n // ─── Private Retry Logic ───────────────────────────────────────────────────\n\n /**\n * Process a single CRUD entry with exponential backoff retry.\n *\n * This method uses a two-phase approach:\n * 1. First attempt - try the operation to classify the error type\n * 2. Retry phase - use the appropriate config (transient vs permanent) based on classification\n *\n * This fixes the issue where reassigning selectedConfig inside withExponentialBackoff's\n * callback had no effect because the config was already destructured at call time.\n *\n * @param entry - The CRUD entry to process\n * @throws Error if all retries exhausted (for critical failures)\n */\n private async processWithRetry(entry: CrudEntry): Promise<void> {\n if (this.isDestroyed) {\n throw new Error('Connector destroyed');\n }\n\n // Check if this entry is in cooldown from a previous failure\n const entryKey = `${entry.table}:${entry.id}`;\n const cooldownUntil = this.entryCooldowns.get(entryKey);\n if (cooldownUntil && Date.now() < cooldownUntil) {\n const remainingMs = cooldownUntil - Date.now();\n if (__DEV__) {\n console.log('[Connector] Entry in cooldown, skipping:', {\n table: entry.table,\n id: entry.id,\n remainingSec: Math.round(remainingMs / 1000)\n });\n }\n // Throw a specific error so PowerSync keeps the entry but we don't spam retries\n throw new Error(`Entry in cooldown for ${Math.round(remainingMs / 1000)}s`);\n }\n const classified = {\n isPermanent: false,\n pgCode: undefined as string | undefined,\n userMessage: ''\n };\n let lastError: Error | undefined;\n\n // Phase 1: First attempt - no retry, just to classify the error type\n try {\n await this.processCrudEntry(entry);\n this.entryCooldowns.delete(entryKey); // Clear any previous cooldown on success\n return; // Success on first try\n } catch (error) {\n lastError = error instanceof Error ? error : new Error(String(error));\n const classifiedError = classifySupabaseError(error);\n classified.isPermanent = classifiedError.isPermanent;\n classified.pgCode = classifiedError.pgCode;\n classified.userMessage = classifiedError.userMessage;\n\n // If first attempt failed with auth error, try refresh and one more attempt\n if (isAuthError(error)) {\n if (__DEV__) {\n console.log('[Connector] Auth error detected, refreshing session before retry:', {\n table: entry.table,\n op: entry.op,\n id: entry.id\n });\n }\n try {\n await this.supabase.auth.refreshSession();\n await this.processCrudEntry(entry);\n this.entryCooldowns.delete(entryKey); // Clear any previous cooldown on success\n return; // Success after token refresh\n } catch (retryError) {\n lastError = retryError instanceof Error ? retryError : new Error(String(retryError));\n const retriedClassification = classifySupabaseError(retryError);\n classified.isPermanent = retriedClassification.isPermanent;\n classified.pgCode = retriedClassification.pgCode;\n classified.userMessage = retriedClassification.userMessage;\n }\n }\n\n // Log the initial failure\n if (__DEV__) {\n console.log('[Connector] Initial attempt failed, will retry with appropriate config:', {\n table: entry.table,\n op: entry.op,\n id: entry.id,\n isPermanent: classified.isPermanent,\n pgCode: classified.pgCode,\n userMessage: classified.userMessage\n });\n }\n this.logger?.warn('[Connector] Initial attempt failed:', {\n table: entry.table,\n op: entry.op,\n id: entry.id,\n error: lastError.message,\n isPermanent: classified.isPermanent\n });\n\n // Run upload error middleware if configured\n if (this.uploadErrorMiddleware.length > 0) {\n const classifiedError = classifySupabaseError(error);\n const schema = this.schemaRouter(entry.table);\n const middlewareContext: UploadErrorContext = {\n entry,\n error: lastError,\n pgCode: classified.pgCode,\n httpStatusCode: extractHttpStatusCode(error),\n classified: classifiedError,\n originalError: error,\n schema,\n supabase: this.supabase\n };\n const middlewareResult = await runUploadErrorMiddleware(middlewareContext, this.uploadErrorMiddleware, 'continue' // Default to continue if no middleware matches\n );\n if (__DEV__) {\n console.log('[Connector] Upload error middleware result:', {\n table: entry.table,\n id: entry.id,\n result: middlewareResult\n });\n }\n switch (middlewareResult) {\n case 'success':\n // Treat as successful - clear cooldown and return\n this.entryCooldowns.delete(entryKey);\n this.logger?.info('[Connector] Entry treated as success by middleware:', {\n table: entry.table,\n id: entry.id,\n pgCode: classified.pgCode\n });\n return;\n case 'discard':\n // Discard entry - throw DiscardEntryError to signal removal from queue\n this.entryCooldowns.delete(entryKey);\n this.logger?.warn('[Connector] Entry discarded by middleware:', {\n table: entry.table,\n id: entry.id,\n pgCode: classified.pgCode,\n error: lastError.message\n });\n throw new DiscardEntryError(`Entry discarded by middleware: ${entry.table}:${entry.id} (${classified.pgCode || 'unknown'})`);\n case 'retry':\n // Continue with retry flow (fall through to Phase 2)\n break;\n case 'continue':\n // Use default classification (fall through to Phase 2)\n break;\n case 'fail_transaction':\n // Abort entire transaction immediately\n this.logger?.error('[Connector] Transaction aborted by middleware:', {\n table: entry.table,\n id: entry.id,\n pgCode: classified.pgCode,\n error: lastError.message\n });\n throw new TransactionAbortError(`Transaction aborted by middleware: ${entry.table}:${entry.id}`, lastError);\n }\n }\n }\n\n // Phase 2: Select config based on classified error type, then retry\n // RLS errors (42501) get extended retries since parent data may need to sync first\n const isRlsPermissionError = lastError && isRlsError(lastError);\n const selectedConfig = isRlsPermissionError ? this.retryConfig.rls : classified.isPermanent ? this.retryConfig.permanent : this.retryConfig.transient;\n if (__DEV__ && isRlsPermissionError) {\n console.log('[Connector] RLS/permission error detected, using extended retry config:', {\n table: entry.table,\n op: entry.op,\n id: entry.id,\n maxRetries: selectedConfig.maxRetries,\n baseDelayMs: selectedConfig.baseDelayMs,\n maxDelayMs: selectedConfig.maxDelayMs\n });\n }\n try {\n await withExponentialBackoff(async () => {\n await this.processCrudEntry(entry);\n }, selectedConfig, {\n onRetry: (attempt, delay, error) => {\n this.logger?.debug('[Connector] Retry attempt:', {\n table: entry.table,\n op: entry.op,\n id: entry.id,\n attempt,\n delay,\n error: error.message\n });\n if (__DEV__) {\n console.log('[Connector] Retry attempt:', {\n table: entry.table,\n op: entry.op,\n id: entry.id,\n attempt,\n maxRetries: selectedConfig.maxRetries,\n delayMs: delay,\n errorMessage: error.message\n });\n }\n }\n });\n // Retries succeeded - clear any cooldown\n this.entryCooldowns.delete(entryKey);\n } catch (error) {\n // All retries exhausted - let PowerSync's native retry mechanism handle it\n const finalError = error instanceof Error ? error : new Error(String(error));\n\n // Determine error category for logging\n const category: 'transient' | 'permanent' | 'unknown' = classified.isPermanent ? 'permanent' : classified.pgCode ? 'transient' : 'unknown';\n if (__DEV__) {\n console.log('[Connector] CRUD entry failed after retries, leaving in ps_crud:', {\n table: entry.table,\n op: entry.op,\n id: entry.id,\n category,\n error: finalError.message\n });\n }\n this.logger?.error('[Connector] CRUD entry failed after retries:', {\n table: entry.table,\n op: entry.op,\n id: entry.id,\n error: finalError.message,\n category\n });\n\n // Set cooldown to prevent rapid retries from PowerSync's sync cycle\n // This gives time for parent data to sync or conditions to change\n const cooldownMs = SupabaseConnector.COOLDOWN_DURATION_MS;\n this.entryCooldowns.set(entryKey, Date.now() + cooldownMs);\n if (__DEV__) {\n console.log('[Connector] Entry placed in cooldown:', {\n table: entry.table,\n id: entry.id,\n cooldownSec: cooldownMs / 1000\n });\n }\n\n // ALWAYS throw after exhausting retries to prevent transaction.complete()\n // from removing the entry from PowerSync's queue. This ensures:\n // 1. Data stays in PowerSync's durable SQLite queue\n // 2. PowerSync's native retry mechanism will retry the transaction\n // 3. No reliance on AsyncStorage persistence (which is fire-and-forget)\n //\n // For transient errors: PowerSync will retry the whole transaction later\n // For permanent errors: User intervention needed, but data isn't lost\n throw finalError;\n }\n }\n\n /**\n * Set the active project IDs for scoped sync.\n * Call this when user selects/opens projects.\n */\n setActiveProjectIds(projectIds: string[]): void {\n this.activeProjectIds = projectIds;\n }\n\n /**\n * Get the current active project IDs.\n */\n getActiveProjectIds(): string[] {\n return this.activeProjectIds;\n }\n\n /**\n * Get credentials for PowerSync connection.\n * Uses Supabase session token.\n *\n * Note: Token refresh is handled by Supabase's startAutoRefresh() which must be\n * called on app initialization. getSession() returns the auto-refreshed token.\n */\n async fetchCredentials(): Promise<PowerSyncCredentials> {\n this.logger?.debug('[Connector] Fetching credentials...');\n const {\n data: {\n session\n },\n error\n } = await this.supabase.auth.getSession();\n if (error) {\n this.logger?.error('[Connector] Auth error:', error);\n throw new Error(`Failed to get Supabase session: ${error.message}`);\n }\n if (!session) {\n this.logger?.error('[Connector] No active session');\n throw new Error('No active Supabase session');\n }\n this.logger?.debug('[Connector] Credentials fetched, token expires at:', session.expires_at);\n return {\n endpoint: this.powerSyncUrl,\n token: session.access_token,\n expiresAt: session.expires_at ? new Date(session.expires_at * 1000) : undefined\n };\n }\n\n /**\n * Upload local changes to Supabase.\n * Called automatically by PowerSync when there are pending uploads.\n *\n * When conflict detection is enabled:\n * 1. Checks if table has _version column (cached)\n * 2. If yes, compares local vs server version\n * 3. On version mismatch, queries AuditLog for field conflicts\n * 4. If conflicts found, calls handler or publishes to conflict bus\n * 5. Applies resolution or skips entry based on handler response\n */\n async uploadData(database: AbstractPowerSyncDatabase): Promise<void> {\n // P4.3: Check if connector has been destroyed\n if (this.isDestroyed) {\n throw new Error('Connector destroyed - aborting upload');\n }\n\n // Network reachability gate - check FIRST, before any network calls\n // This enables instant blocking when offline without waiting for timeouts\n // CRITICAL: Must THROW (not return) to keep entries in ps_crud for retry\n // A silent return would cause PowerSync to call complete() and discard the transaction\n if (this.shouldUploadFn && !this.shouldUploadFn()) {\n this.logger?.debug('[Connector] Upload blocked - not currently permitted, will retry');\n throw new Error('Upload not permitted - will retry on next sync cycle');\n }\n\n // CRITICAL: Ensure session is fresh before uploading\n // This prevents RLS failures (42501) from stale/expired tokens\n // Without this, auth.uid() in RLS policies may return NULL or fail\n const {\n data: {\n session\n },\n error: sessionError\n } = await this.supabase.auth.getSession();\n if (sessionError || !session) {\n const noSessionError = new Error('No active session - cannot upload data');\n if (__DEV__) {\n console.error('[Connector] uploadData failed: no session');\n }\n throw noSessionError;\n }\n if (__DEV__) {\n console.log('[Connector] uploadData called, fetching next CRUD transaction...');\n }\n const transaction = await database.getNextCrudTransaction();\n\n // P1.2: Validate transaction is not empty\n if (!transaction || transaction.crud.length === 0) {\n if (__DEV__) {\n console.log('[Connector] No pending CRUD transaction found or transaction is empty');\n }\n return;\n }\n if (__DEV__) {\n console.log('[Connector] Transaction fetched:', {\n crudCount: transaction.crud.length,\n entries: transaction.crud.map(e => ({\n table: e.table,\n op: e.op,\n id: e.id,\n opDataKeys: e.opData ? Object.keys(e.opData) : []\n }))\n });\n }\n\n // If conflict detection is disabled, use standard upload\n const conflictDetectionEnabled = this.conflictDetection?.enabled !== false;\n if (!conflictDetectionEnabled) {\n await this.processTransaction(transaction, database);\n return;\n }\n\n // Process with conflict detection\n const {\n crud\n } = transaction;\n const skipTables = new Set(this.conflictDetection?.skipTables ?? []);\n const entriesToProcess: CrudEntry[] = [];\n // Entries queued for UI resolution - these should NOT complete the transaction (leave in queue)\n const entriesQueuedForUI: CrudEntry[] = [];\n // Entries resolved with keep-server - these WILL complete the transaction (discard local changes)\n const entriesDiscarded: CrudEntry[] = [];\n\n // Track partial resolutions to re-emit remaining conflicts after sync completes\n const partialResolutions: Array<{\n originalConflict: ConflictCheckResult;\n syncedFields: string[];\n }> = [];\n for (const entry of crud) {\n // Skip DELETE operations - no conflict checking needed\n if (entry.op === 'DELETE') {\n entriesToProcess.push(entry);\n continue;\n }\n\n // Skip tables in the skip list\n if (skipTables.has(entry.table)) {\n entriesToProcess.push(entry);\n continue;\n }\n\n // Check for existing resolution from a previous retry\n // This prevents re-detecting conflicts that the user already resolved\n // Key by table:recordId to handle same UUID in different tables\n const resolutionKey = `${entry.table}:${entry.id}`;\n const existingResolution = this.resolvedConflicts.get(resolutionKey);\n if (existingResolution) {\n if (__DEV__) {\n console.log('[Connector] Applying stored resolution for retry:', {\n table: entry.table,\n id: entry.id,\n key: resolutionKey,\n resolution: existingResolution\n });\n }\n // Remove from stored resolutions (single use)\n this.resolvedConflicts.delete(resolutionKey);\n switch (existingResolution.action) {\n case 'overwrite':\n // Proceed with upload (overwrite server)\n entriesToProcess.push(entry);\n break;\n case 'keep-server':\n // Discard local changes - mark for completion (removes from queue)\n entriesDiscarded.push(entry);\n break;\n case 'partial':\n // P5.3: Validate that partial resolution has at least one field\n if (!existingResolution.fields || existingResolution.fields.length === 0) {\n throw new Error('Partial resolution requires at least one field');\n }\n // Only sync specified fields\n const partialEntry: CrudEntry = {\n ...entry,\n opData: this.filterFields(entry.opData ?? {}, existingResolution.fields)\n };\n entriesToProcess.push(partialEntry);\n break;\n }\n continue;\n }\n\n // Check for version column (cached)\n const hasVersion = await this.checkVersionColumn(entry.table, database);\n if (!hasVersion) {\n entriesToProcess.push(entry);\n continue;\n }\n\n // Get local and server versions\n const localVersion = await getLocalVersion(entry.table, entry.id, database);\n const schema = this.schemaRouter(entry.table);\n const serverVersion = await fetchServerVersion(entry.table, entry.id, schema, this.supabase);\n\n // If we can't get versions, skip conflict check and proceed\n if (localVersion === null || serverVersion === null) {\n entriesToProcess.push(entry);\n continue;\n }\n\n // Detect conflicts\n const conflictResult = await detectConflicts(entry.table, entry.id, localVersion, serverVersion, entry.opData ?? {}, this.supabase, this.conflictDetection);\n if (!conflictResult.hasConflict) {\n entriesToProcess.push(entry);\n continue;\n }\n\n // Emit conflict event to bus if available\n this.conflictBus?.emitConflict(conflictResult);\n\n // Handle conflict with handler if available\n if (this.conflictHandler) {\n const resolution = await this.conflictHandler.onConflict(conflictResult);\n if (resolution === null) {\n // Queue for UI - skip this entry, leave in queue for later resolution\n entriesQueuedForUI.push(entry);\n if (__DEV__) {\n console.log('[Connector] Conflict queued for UI resolution:', {\n table: entry.table,\n id: entry.id,\n conflicts: conflictResult.conflicts.map(c => c.field)\n });\n }\n continue;\n }\n switch (resolution.action) {\n case 'overwrite':\n // Proceed with upload (overwrite server)\n entriesToProcess.push(entry);\n break;\n case 'keep-server':\n // Discard local changes - mark for completion (removes from queue)\n entriesDiscarded.push(entry);\n if (__DEV__) {\n console.log('[Connector] Conflict resolved with keep-server, discarding local changes:', {\n table: entry.table,\n id: entry.id\n });\n }\n break;\n case 'partial':\n // P5.3: Validate that partial resolution has at least one field\n if (!resolution.fields || resolution.fields.length === 0) {\n throw new Error('Partial resolution requires at least one field');\n }\n // Only sync specified fields\n const partialEntry: CrudEntry = {\n ...entry,\n opData: this.filterFields(entry.opData ?? {}, resolution.fields)\n };\n entriesToProcess.push(partialEntry);\n\n // Track this partial resolution to re-emit remaining conflicts after sync\n partialResolutions.push({\n originalConflict: conflictResult,\n syncedFields: resolution.fields\n });\n break;\n }\n } else {\n // No handler - log conflict and proceed with upload\n this.logger?.warn('[Connector] Conflict detected but no handler:', {\n table: entry.table,\n id: entry.id,\n conflicts: conflictResult.conflicts\n });\n entriesToProcess.push(entry);\n }\n }\n\n // If any entries are queued for UI resolution, we must NOT complete the transaction.\n // Per PowerSync docs, uploadData() must THROW on failure to keep entries in ps_crud.\n // A silent return would cause PowerSync to think upload succeeded and remove entries.\n if (entriesQueuedForUI.length > 0) {\n if (__DEV__) {\n console.log('[Connector] Entries queued for UI resolution, leaving in queue:', {\n queuedForUI: entriesQueuedForUI.length,\n discarded: entriesDiscarded.length,\n toProcess: entriesToProcess.length\n });\n }\n // Fire onTransactionComplete to notify listeners, but NOT onTransactionSuccess\n // because we're not completing the transaction. Entries remain in the queue.\n this.onTransactionComplete?.(entriesQueuedForUI);\n // MUST throw to prevent PowerSync from removing entries from ps_crud\n throw new Error('Entries queued for UI resolution - retry will occur on next sync cycle');\n }\n\n // If all entries were discarded (keep-server) with none to process,\n // still complete the transaction to remove them from the queue\n if (entriesToProcess.length === 0 && entriesDiscarded.length > 0) {\n if (__DEV__) {\n console.log('[Connector] All entries resolved with keep-server, completing transaction to discard local changes');\n }\n try {\n await transaction.complete();\n // Fire success (entries were \"successfully\" discarded)\n this.onTransactionSuccess?.(entriesDiscarded);\n this.onTransactionComplete?.(entriesDiscarded);\n } catch (error) {\n const classified = classifySupabaseError(error);\n this.onTransactionFailure?.(entriesDiscarded, error instanceof Error ? error : new Error(String(error)), classified);\n throw error;\n }\n return;\n }\n\n // Process remaining entries with retry logic\n const successfulEntries: CrudEntry[] = [];\n\n // CRITICAL: Process entries atomically - STOP on first failure\n // PowerSync transactions must be atomic: either ALL entries succeed or NONE are committed\n // If we continue after a failure, we violate atomicity and risk partial uploads\n for (const entry of entriesToProcess) {\n if (__DEV__) {\n console.log('[Connector] Processing CRUD entry with retry:', {\n table: entry.table,\n op: entry.op,\n id: entry.id,\n opData: entry.opData\n });\n }\n\n // No try-catch here - let errors propagate immediately to stop processing\n // This keeps the ENTIRE transaction in ps_crud for retry\n await this.processWithRetry(entry);\n successfulEntries.push(entry);\n }\n\n // Finalize the transaction using the shared helper\n await this.finalizeTransaction({\n transaction,\n successfulEntries,\n discardedEntries: entriesDiscarded,\n partialResolutions\n });\n }\n\n /**\n * Finalize a transaction by completing it after all entries processed successfully.\n * Extracted to eliminate duplication between uploadData and processTransaction.\n *\n * Implements circuit breaker logic for completion failures:\n * - On first failure: retry once with extended timeout (60s)\n * - After 3 failures for same entries: log and return without throwing\n * (data is safely in Supabase via idempotent upserts, preventing infinite retry loop)\n *\n * @param context - The finalization context containing results and transaction\n */\n private async finalizeTransaction(context: TransactionFinalizationContext): Promise<void> {\n const {\n transaction,\n successfulEntries,\n discardedEntries = [],\n partialResolutions = []\n } = context;\n if (__DEV__) {\n console.log('[Connector] All CRUD entries processed, completing transaction...');\n }\n\n // Generate fingerprint for tracking completion failures\n const allEntries = [...successfulEntries, ...discardedEntries];\n const fingerprint = this.generateTransactionFingerprint(allEntries);\n\n // Check existing failure count for circuit breaker\n const failureInfo = this.completionFailures.get(fingerprint);\n const currentFailureCount = failureInfo?.count ?? 0;\n\n // Circuit breaker: if we've failed COMPLETION_MAX_FAILURES times, don't throw\n // Data is already in Supabase (upserts are idempotent), so we log and return\n if (currentFailureCount >= SupabaseConnector.COMPLETION_MAX_FAILURES) {\n this.logger?.warn('[Connector] Circuit breaker triggered - completion failed too many times, bypassing throw:', {\n fingerprint: fingerprint.substring(0, 50) + '...',\n failureCount: currentFailureCount,\n entriesCount: allEntries.length,\n message: 'Data is safely in Supabase. Returning without throw to prevent infinite retry loop.'\n });\n if (__DEV__) {\n console.warn('[Connector] CIRCUIT BREAKER: Completion failed', currentFailureCount, 'times. Data is in Supabase - returning without throw to break retry loop.');\n }\n\n // Clear the failure tracking since we're breaking out\n this.completionFailures.delete(fingerprint);\n\n // Still notify callbacks that we \"succeeded\" (data is in Supabase)\n this.onTransactionSuccess?.(successfulEntries);\n this.onTransactionComplete?.(successfulEntries);\n return;\n }\n try {\n // P1.5: Add timeout on transaction.complete() to prevent indefinite hangs\n await withTimeout(transaction.complete(), 30000, 'Transaction complete timeout');\n if (__DEV__) {\n console.log('[Connector] Transaction completed successfully:', {\n entriesCount: successfulEntries.length,\n discardedCount: discardedEntries.length\n });\n }\n\n // Clear completion failure tracking on success\n this.completionFailures.delete(fingerprint);\n\n // P3.1: Clear resolved conflicts for successful entries to prevent unbounded growth\n for (const entry of successfulEntries) {\n this.resolvedConflicts.delete(`${entry.table}:${entry.id}`);\n }\n for (const entry of discardedEntries) {\n this.resolvedConflicts.delete(`${entry.table}:${entry.id}`);\n }\n\n // P3.1: Apply LRU eviction if map grows too large\n if (this.resolvedConflicts.size > 100) {\n const oldest = [...this.resolvedConflicts.keys()][0];\n if (oldest) {\n this.resolvedConflicts.delete(oldest);\n }\n }\n\n // After partial sync completes, re-emit conflicts for remaining fields\n if (this.conflictBus && partialResolutions.length > 0) {\n for (const {\n originalConflict,\n syncedFields\n } of partialResolutions) {\n const syncedFieldSet = new Set(syncedFields);\n\n // Find conflicts that weren't synced\n const remainingConflicts = originalConflict.conflicts.filter(c => !syncedFieldSet.has(c.field));\n if (remainingConflicts.length > 0) {\n if (__DEV__) {\n console.log('[Connector] Re-emitting conflict for remaining fields:', {\n table: originalConflict.table,\n recordId: originalConflict.recordId,\n syncedFields,\n remainingConflictFields: remainingConflicts.map(c => c.field)\n });\n }\n\n // Emit new conflict for remaining fields\n this.conflictBus.emitConflict({\n ...originalConflict,\n conflicts: remainingConflicts,\n // All remaining are conflicts now - clear nonConflictingChanges since\n // the non-conflicting ones were already synced in the partial resolution\n nonConflictingChanges: []\n });\n }\n }\n }\n\n // P1.4: Notify success AFTER complete() to ensure data is durably persisted\n this.onTransactionSuccess?.(successfulEntries);\n\n // Notify completion (after transaction.complete() succeeds)\n this.onTransactionComplete?.(successfulEntries);\n } catch (error) {\n const classified = classifySupabaseError(error);\n const newFailureCount = currentFailureCount + 1;\n this.logger?.error('[Connector] Transaction completion FAILED:', {\n errorMessage: error instanceof Error ? error.message : String(error),\n classified,\n isPermanent: classified.isPermanent,\n failureCount: newFailureCount,\n maxFailures: SupabaseConnector.COMPLETION_MAX_FAILURES,\n entries: successfulEntries.map(e => ({\n table: e.table,\n op: e.op,\n id: e.id\n }))\n });\n\n // Additional debug info in development\n if (__DEV__) {\n console.error('[Connector] Transaction completion error details:', {\n errorKeys: error && typeof error === 'object' ? Object.keys(error) : [],\n errorObject: JSON.stringify(error, null, 2),\n failureCount: newFailureCount\n });\n }\n\n // On FIRST failure, retry once with extended timeout before incrementing counter\n if (currentFailureCount === 0) {\n if (__DEV__) {\n console.log('[Connector] First completion failure - retrying with extended timeout (60s)...');\n }\n try {\n await withTimeout(transaction.complete(), SupabaseConnector.COMPLETION_EXTENDED_TIMEOUT_MS, 'Transaction complete extended timeout');\n\n // Extended retry succeeded!\n if (__DEV__) {\n console.log('[Connector] Transaction completed on extended retry');\n }\n\n // Clear any tracking and notify success\n this.completionFailures.delete(fingerprint);\n this.onTransactionSuccess?.(successfulEntries);\n this.onTransactionComplete?.(successfulEntries);\n return;\n } catch (retryError) {\n // Extended retry also failed - update failure count and continue to throw\n if (__DEV__) {\n console.warn('[Connector] Extended retry also failed:', {\n error: retryError instanceof Error ? retryError.message : String(retryError)\n });\n }\n }\n }\n\n // Update failure tracking\n this.completionFailures.set(fingerprint, {\n count: newFailureCount,\n lastAttempt: new Date()\n });\n\n // Clean up old failure entries (LRU-style) to prevent unbounded growth\n if (this.completionFailures.size > 50) {\n const oldestKey = [...this.completionFailures.keys()][0];\n if (oldestKey) {\n this.completionFailures.delete(oldestKey);\n }\n }\n this.logger?.error('[Connector] Transaction completion error:', {\n error,\n classified,\n failureCount: newFailureCount,\n willRetry: newFailureCount < SupabaseConnector.COMPLETION_MAX_FAILURES,\n entries: successfulEntries.map(e => ({\n table: e.table,\n op: e.op,\n id: e.id\n }))\n });\n\n // Notify failure with classification\n this.onTransactionFailure?.(successfulEntries, error instanceof Error ? error : new Error(String(error)), classified);\n\n // Re-throw for PowerSync's native retry mechanism\n // (Circuit breaker at top of function will catch repeated failures)\n throw error;\n }\n }\n\n /**\n * Process a transaction without conflict detection.\n * Uses batched operations for PUT and DELETE, individual processing for PATCH.\n */\n private async processTransaction(transaction: {\n crud: CrudEntry[];\n complete: () => Promise<void>;\n }, _database: AbstractPowerSyncDatabase): Promise<void> {\n const successfulEntries: CrudEntry[] = [];\n const discardedEntries: CrudEntry[] = [];\n\n // Group entries by table for batched processing\n const entriesByTable = groupEntriesByTable(transaction.crud);\n if (__DEV__) {\n console.log('[Connector] Processing transaction with batching:', {\n totalEntries: transaction.crud.length,\n tables: Array.from(entriesByTable.keys()),\n entriesPerTable: Object.fromEntries(Array.from(entriesByTable.entries()).map(([table, entries]) => [table, {\n total: entries.length,\n put: entries.filter(e => e.op === UpdateType.PUT).length,\n patch: entries.filter(e => e.op === UpdateType.PATCH).length,\n delete: entries.filter(e => e.op === UpdateType.DELETE).length\n }]))\n });\n }\n\n // Process each table's entries\n for (const [table, entries] of entriesByTable) {\n const schema = this.schemaRouter(table);\n\n // Separate entries by operation type\n const putEntries = entries.filter(e => e.op === UpdateType.PUT);\n const patchEntries = entries.filter(e => e.op === UpdateType.PATCH);\n const deleteEntries = entries.filter(e => e.op === UpdateType.DELETE);\n\n // Check if custom handler exists - if so, process individually\n // CRITICAL: Only catch DiscardEntryError - other errors propagate to maintain atomicity\n if (this.crudHandler) {\n // Process all entries individually when custom handler is configured\n for (const entry of entries) {\n try {\n await this.processWithRetry(entry);\n successfulEntries.push(entry);\n } catch (error) {\n if (error instanceof DiscardEntryError) {\n // Entry was intentionally discarded by middleware - treat as \"completed\"\n discardedEntries.push(entry);\n continue;\n }\n // Other errors propagate to maintain transaction atomicity\n throw error;\n }\n }\n continue;\n }\n\n // Batch PUT operations (upserts)\n // CRITICAL: Errors propagate immediately to maintain transaction atomicity\n if (putEntries.length > 0) {\n const successful = await this.processBatchedPuts(table, schema, putEntries);\n successfulEntries.push(...successful);\n }\n\n // Batch DELETE operations\n // CRITICAL: Errors propagate immediately to maintain transaction atomicity\n if (deleteEntries.length > 0) {\n const successful = await this.processBatchedDeletes(table, schema, deleteEntries);\n successfulEntries.push(...successful);\n }\n\n // PATCH operations still need individual processing (different fields per entry)\n // CRITICAL: Only catch DiscardEntryError - other errors propagate to maintain atomicity\n for (const entry of patchEntries) {\n if (__DEV__) {\n console.log('[Connector] Processing PATCH entry individually:', {\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 if (error instanceof DiscardEntryError) {\n // Entry was intentionally discarded by middleware - treat as \"completed\"\n discardedEntries.push(entry);\n continue;\n }\n // Other errors propagate to maintain transaction atomicity\n throw error;\n }\n }\n }\n\n // Finalize the transaction using the shared helper\n // Both successful and discarded entries are included for transaction completion\n // (discarded entries should be removed from queue, just like successful ones)\n await this.finalizeTransaction({\n transaction,\n successfulEntries,\n discardedEntries: discardedEntries.length > 0 ? discardedEntries : undefined\n });\n }\n\n /**\n * Process batched PUT (upsert) operations for a single table.\n * Falls back to individual processing if batch fails.\n * CRITICAL: Throws on first failure to maintain transaction atomicity.\n * @returns Array of successfully processed entries\n * @throws Error on first failure - keeps entire transaction in ps_crud\n */\n private async processBatchedPuts(table: string, schema: string, entries: CrudEntry[]): Promise<CrudEntry[]> {\n const successful: CrudEntry[] = [];\n if (__DEV__) {\n console.log('[Connector] Processing batched PUTs:', {\n schema,\n table,\n count: entries.length\n });\n }\n\n // Build the rows for upsert\n const rows = entries.map(entry => ({\n id: entry.id,\n ...entry.opData\n }));\n\n // Get the correct Supabase query builder for this table's schema\n const query = schema === 'public' ? this.supabase.from(table) : (this.supabase.schema(schema) as unknown as ReturnType<typeof this.supabase.schema>).from(table);\n const {\n error\n } = await query.upsert(rows, {\n onConflict: 'id'\n });\n if (error) {\n if (__DEV__) {\n console.warn('[Connector] Batched PUT failed, falling back to individual processing:', {\n schema,\n table,\n error: error.message\n });\n }\n\n // Batch failed - fall back to individual processing\n // CRITICAL: Only catch DiscardEntryError - other errors propagate to maintain atomicity\n for (const entry of entries) {\n try {\n await this.processWithRetry(entry);\n successful.push(entry);\n } catch (e) {\n if (e instanceof DiscardEntryError) {\n // Entry was intentionally discarded by middleware - treat as \"completed\"\n successful.push(entry);\n continue;\n }\n // Other errors propagate to maintain transaction atomicity\n throw e;\n }\n }\n } else {\n if (__DEV__) {\n console.log('[Connector] Batched PUT successful:', {\n schema,\n table,\n count: entries.length\n });\n }\n successful.push(...entries);\n }\n return successful;\n }\n\n /**\n * Process batched DELETE operations for a single table.\n * Falls back to individual processing if batch fails.\n * CRITICAL: Throws on first failure to maintain transaction atomicity.\n * @returns Array of successfully processed entries\n * @throws Error on first failure - keeps entire transaction in ps_crud\n */\n private async processBatchedDeletes(table: string, schema: string, entries: CrudEntry[]): Promise<CrudEntry[]> {\n const successful: CrudEntry[] = [];\n if (__DEV__) {\n console.log('[Connector] Processing batched DELETEs:', {\n schema,\n table,\n count: entries.length\n });\n }\n\n // Collect all IDs for batch delete\n const ids = entries.map(entry => entry.id);\n\n // Get the correct Supabase query builder for this table's schema\n const query = schema === 'public' ? this.supabase.from(table) : (this.supabase.schema(schema) as unknown as ReturnType<typeof this.supabase.schema>).from(table);\n const {\n error\n } = await query.delete().in('id', ids);\n if (error) {\n if (__DEV__) {\n console.warn('[Connector] Batched DELETE failed, falling back to individual processing:', {\n schema,\n table,\n error: error.message\n });\n }\n\n // Batch failed - fall back to individual processing\n // CRITICAL: Only catch DiscardEntryError - other errors propagate to maintain atomicity\n for (const entry of entries) {\n try {\n await this.processWithRetry(entry);\n successful.push(entry);\n } catch (e) {\n if (e instanceof DiscardEntryError) {\n // Entry was intentionally discarded by middleware - treat as \"completed\"\n successful.push(entry);\n continue;\n }\n // Other errors propagate to maintain transaction atomicity\n throw e;\n }\n }\n } else {\n if (__DEV__) {\n console.log('[Connector] Batched DELETE successful:', {\n schema,\n table,\n count: entries.length\n });\n }\n successful.push(...entries);\n }\n return successful;\n }\n\n /**\n * Check if a table has a _version column (cached).\n * P4.1: Uses Promise-based locking to prevent duplicate concurrent queries.\n */\n private async checkVersionColumn(table: string, db: AbstractPowerSyncDatabase): Promise<boolean> {\n // Return cached result if available\n if (this.versionColumnCache.has(table)) {\n return this.versionColumnCache.get(table)!;\n }\n\n // Check if there's already a pending query for this table\n if (!this.versionColumnPromises.has(table)) {\n // Create a new promise for this table's query\n this.versionColumnPromises.set(table, hasVersionColumn(table, db).then(result => {\n this.versionColumnCache.set(table, result);\n this.versionColumnPromises.delete(table);\n return result;\n }).catch(error => {\n // Clean up promise on error so retries are possible\n this.versionColumnPromises.delete(table);\n throw error;\n }));\n }\n\n // Wait for the (possibly shared) promise\n return this.versionColumnPromises.get(table)!;\n }\n\n /**\n * Filter opData to only include specified fields.\n * Used for partial sync resolution.\n */\n private filterFields(opData: Record<string, unknown>, fields: string[]): Record<string, unknown> {\n const fieldSet = new Set(fields);\n const filtered: Record<string, unknown> = {};\n for (const [key, value] of Object.entries(opData)) {\n if (fieldSet.has(key)) {\n filtered[key] = value;\n }\n }\n return filtered;\n }\n\n /**\n * Process a single CRUD operation.\n *\n * All synced tables use `id` as their UUID primary key column.\n */\n private async processCrudEntry(entry: CrudEntry): Promise<void> {\n const table = entry.table;\n const id = entry.id; // PowerSync sends UUID as the entry.id\n const schema = this.schemaRouter(table);\n\n // P5.1: Skip empty PATCH operations that would result in no-op updates\n if (entry.op === UpdateType.PATCH && Object.keys(entry.opData ?? {}).length === 0) {\n this.logger?.debug(`[Connector] Skipping empty PATCH for ${entry.table}:${entry.id}`);\n return; // Empty PATCH is a no-op - treating as success to clear from queue\n }\n\n // P5.2: Validate payload size to prevent Supabase failures with unhelpful errors\n if (entry.opData) {\n const payloadSize = JSON.stringify(entry.opData).length;\n if (payloadSize > MAX_PAYLOAD_SIZE) {\n throw new ValidationError(`Payload too large (${(payloadSize / 1024).toFixed(0)}KB > 900KB) for ${schema}.${table}`);\n }\n }\n\n // Try custom handler first\n if (this.crudHandler) {\n let handled = false;\n switch (entry.op) {\n case UpdateType.PUT:\n handled = (await this.crudHandler.handlePut?.(entry, this.supabase, schema)) ?? false;\n break;\n case UpdateType.PATCH:\n handled = (await this.crudHandler.handlePatch?.(entry, this.supabase, schema)) ?? false;\n break;\n case UpdateType.DELETE:\n handled = (await this.crudHandler.handleDelete?.(entry, this.supabase, schema)) ?? false;\n break;\n }\n if (handled) {\n this.logger?.debug(`[Connector] Custom handler processed ${entry.op} for ${schema}.${table}`);\n return;\n }\n }\n\n // Default behavior\n // Get the correct Supabase query builder for this table's schema\n const query = schema === 'public' ? this.supabase.from(table) : (this.supabase.schema(schema) as unknown as ReturnType<typeof this.supabase.schema>).from(table);\n switch (entry.op) {\n case UpdateType.PUT:\n if (__DEV__) {\n console.log('[Connector] Executing PUT/UPSERT:', {\n schema,\n table,\n id,\n data: {\n id,\n ...entry.opData\n }\n });\n }\n // Insert/upsert using id column\n const {\n data: upsertData,\n error: upsertError\n } = await query.upsert({\n id,\n ...entry.opData\n }, {\n onConflict: 'id'\n }).select();\n if (upsertError) {\n if (__DEV__) {\n console.error('[Connector] PUT/UPSERT FAILED:', {\n schema,\n table,\n id,\n error: upsertError,\n errorMessage: upsertError.message,\n errorCode: upsertError.code,\n errorDetails: upsertError.details,\n errorHint: upsertError.hint\n });\n }\n throw new Error(`Upsert failed for ${schema}.${table}: ${upsertError.message}`);\n }\n if (__DEV__) {\n console.log('[Connector] PUT/UPSERT SUCCESS:', {\n schema,\n table,\n id,\n responseData: upsertData\n });\n }\n break;\n case UpdateType.PATCH:\n if (__DEV__) {\n console.log('[Connector] Executing PATCH/UPDATE:', {\n schema,\n table,\n id,\n opData: entry.opData\n });\n }\n // Update by id column\n const {\n data: updateData,\n error: updateError\n } = await query.update(entry.opData).eq('id', id).select();\n if (updateError) {\n if (__DEV__) {\n console.error('[Connector] PATCH/UPDATE FAILED:', {\n schema,\n table,\n id,\n error: updateError,\n errorMessage: updateError.message,\n errorCode: updateError.code,\n errorDetails: updateError.details,\n errorHint: updateError.hint\n });\n }\n throw new Error(`Update failed for ${schema}.${table}: ${updateError.message}`);\n }\n if (__DEV__) {\n console.log('[Connector] PATCH/UPDATE SUCCESS:', {\n schema,\n table,\n id,\n responseData: updateData\n });\n }\n break;\n case UpdateType.DELETE:\n if (__DEV__) {\n console.log('[Connector] Executing DELETE:', {\n schema,\n table,\n id\n });\n }\n // Delete by id column\n const {\n data: deleteData,\n error: deleteError\n } = await query.delete().eq('id', id).select();\n if (deleteError) {\n if (__DEV__) {\n console.error('[Connector] DELETE FAILED:', {\n schema,\n table,\n id,\n error: deleteError,\n errorMessage: deleteError.message,\n errorCode: deleteError.code,\n errorDetails: deleteError.details,\n errorHint: deleteError.hint\n });\n }\n throw new Error(`Delete failed for ${schema}.${table}: ${deleteError.message}`);\n }\n if (__DEV__) {\n console.log('[Connector] DELETE SUCCESS:', {\n schema,\n table,\n id,\n responseData: deleteData\n });\n }\n break;\n }\n this.logger?.debug(`[Connector] Processed ${entry.op} for ${schema}.${table} (id: ${id})`);\n }\n}"],"mappings":"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","names":["classifiedError"]}
@@ -8,7 +8,7 @@ import {
8
8
  HealthMonitor,
9
9
  MetricsCollector,
10
10
  SyncStatusTracker
11
- } from "./chunk-CUCAYK7Z.js";
11
+ } from "./chunk-PG2NPQG3.js";
12
12
  import {
13
13
  DEFAULT_CONNECTION_HEALTH,
14
14
  DEFAULT_SYNC_METRICS,
@@ -42,12 +42,12 @@ import {
42
42
  } from "./chunk-WN5ZJ3E2.js";
43
43
  import {
44
44
  SupabaseConnector
45
- } from "./chunk-KN2IZERF.js";
45
+ } from "./chunk-IMRSLJRV.js";
46
46
  import {
47
47
  createSyncError,
48
48
  extractEntityIds,
49
49
  extractTableNames
50
- } from "./chunk-VACPAAQZ.js";
50
+ } from "./chunk-I2AYMY5O.js";
51
51
 
52
52
  // src/provider/PowerSyncProvider.tsx
53
53
  import { useEffect, useState, useRef, useMemo, useCallback } from "react";
@@ -1717,4 +1717,4 @@ export {
1717
1717
  ProviderBridge,
1718
1718
  OfflineDataProvider
1719
1719
  };
1720
- //# sourceMappingURL=chunk-BRXQNASY.js.map
1720
+ //# sourceMappingURL=chunk-N4K7E53V.js.map
@@ -17,7 +17,7 @@ import {
17
17
  import {
18
18
  classifyError,
19
19
  generateFailureId
20
- } from "./chunk-VACPAAQZ.js";
20
+ } from "./chunk-I2AYMY5O.js";
21
21
 
22
22
  // src/sync/status-tracker.ts
23
23
  var STORAGE_KEY_COMPLETED_TRANSACTIONS = "@pol-powersync:completed_transactions";
@@ -1221,4 +1221,4 @@ export {
1221
1221
  MetricsCollector,
1222
1222
  HealthMonitor
1223
1223
  };
1224
- //# sourceMappingURL=chunk-CUCAYK7Z.js.map
1224
+ //# sourceMappingURL=chunk-PG2NPQG3.js.map
@@ -6,11 +6,11 @@ import {
6
6
  } from "./chunk-24RDMMCL.js";
7
7
  import {
8
8
  SupabaseConnector
9
- } from "./chunk-KN2IZERF.js";
9
+ } from "./chunk-IMRSLJRV.js";
10
10
  import {
11
11
  classifySupabaseError,
12
12
  createSyncError
13
- } from "./chunk-VACPAAQZ.js";
13
+ } from "./chunk-I2AYMY5O.js";
14
14
 
15
15
  // src/react/provider.tsx
16
16
  import { useEffect, useState, useRef, useMemo } from "react";
@@ -355,4 +355,4 @@ export {
355
355
  useSyncStatusContext,
356
356
  useAttachmentQueueContext
357
357
  };
358
- //# sourceMappingURL=chunk-63PXSPIN.js.map
358
+ //# sourceMappingURL=chunk-XOCIONAA.js.map
@@ -1,10 +1,32 @@
1
- import { A as AuthProvider } from '../types-B212hgfA.js';
2
- export { g as ConflictBus, C as ConnectorCircuitBreakerConfig, a as ConnectorConfig, c as CrudHandler, D as DEFAULT_RETRY_CONFIG, P as PowerSyncCredentials, e as RetryConfig, R as RetryStrategyConfig, b as SchemaRouter, f as Session, S as SupabaseConnectorOptions, d as defaultSchemaRouter } from '../types-B212hgfA.js';
3
- export { S as SupabaseConnector } from '../supabase-connector-T9vHq_3i.js';
1
+ import { A as AuthProvider, h as UploadErrorMiddleware, g as UploadErrorContext, U as UploadErrorClassification } from '../types-DiBvmGEi.js';
2
+ export { i as ConflictBus, C as ConnectorCircuitBreakerConfig, a as ConnectorConfig, c as CrudHandler, D as DEFAULT_RETRY_CONFIG, P as PowerSyncCredentials, e as RetryConfig, R as RetryStrategyConfig, b as SchemaRouter, f as Session, S as SupabaseConnectorOptions, d as defaultSchemaRouter } from '../types-DiBvmGEi.js';
3
+ export { S as SupabaseConnector } from '../supabase-connector-WuiFiBnV.js';
4
4
  import { SupabaseClient } from '@supabase/supabase-js';
5
5
  export { P as PowerSyncBackendConnector } from '../types-CDqWh56B.js';
6
6
  import '../platform/index.js';
7
7
 
8
+ /**
9
+ * Custom errors for the @pol-studios/powersync connector.
10
+ *
11
+ * These errors are used by the upload error middleware to control
12
+ * how failed CRUD entries are handled.
13
+ */
14
+ /**
15
+ * Error thrown when a CRUD entry is discarded by middleware.
16
+ * When this error is thrown, the entry is removed from the queue without retry.
17
+ */
18
+ declare class DiscardEntryError extends Error {
19
+ constructor(message: string);
20
+ }
21
+ /**
22
+ * Error thrown when a transaction is aborted by middleware.
23
+ * When thrown, stops the entire transaction immediately without retry.
24
+ */
25
+ declare class TransactionAbortError extends Error {
26
+ readonly cause?: Error;
27
+ constructor(message: string, cause?: Error);
28
+ }
29
+
8
30
  /**
9
31
  * Supabase Auth Provider for @pol-studios/powersync
10
32
  *
@@ -234,4 +256,158 @@ declare class CircuitBreaker {
234
256
  private log;
235
257
  }
236
258
 
237
- export { AuthProvider, AuthRefreshError, CircuitBreaker, type CircuitBreakerConfig, type CircuitBreakerStats, CircuitOpenError, type CircuitState, DEFAULT_CIRCUIT_BREAKER_CONFIG, type SupabaseAuthOptions, createSupabaseAuth };
259
+ /**
260
+ * Upload Error Middleware for @pol-studios/powersync
261
+ *
262
+ * Provides a middleware pattern for classifying and handling upload errors.
263
+ * Includes built-in factory functions for common patterns and an executor
264
+ * for running the middleware chain.
265
+ */
266
+
267
+ /**
268
+ * Create middleware that treats duplicate key errors (23505) as success
269
+ * for specified tables.
270
+ *
271
+ * This is useful for idempotent operations where re-inserting the same
272
+ * record should be treated as a success, not an error.
273
+ *
274
+ * @param tables - Array of table names to treat as idempotent
275
+ * @returns Middleware function
276
+ *
277
+ * @example
278
+ * ```typescript
279
+ * uploadErrorMiddleware: [
280
+ * idempotentTables(['ReadReceipt', 'Like', 'Bookmark']),
281
+ * ]
282
+ * ```
283
+ */
284
+ declare function idempotentTables(tables: string[]): UploadErrorMiddleware;
285
+ /**
286
+ * Create middleware that discards entries with foreign key violations (23503).
287
+ *
288
+ * This is useful for handling orphaned records where the parent record
289
+ * was deleted before the child record could be uploaded.
290
+ *
291
+ * @returns Middleware function
292
+ *
293
+ * @example
294
+ * ```typescript
295
+ * uploadErrorMiddleware: [
296
+ * discardOrphaned(),
297
+ * ]
298
+ * ```
299
+ */
300
+ declare function discardOrphaned(): UploadErrorMiddleware;
301
+ /**
302
+ * Create middleware that discards entries with specific PostgreSQL error codes.
303
+ *
304
+ * @param codes - Array of PostgreSQL error codes to discard
305
+ * @returns Middleware function
306
+ *
307
+ * @example
308
+ * ```typescript
309
+ * uploadErrorMiddleware: [
310
+ * // Discard check constraint violations and not-null violations
311
+ * discardOnPgCodes(['23514', '23502']),
312
+ * ]
313
+ * ```
314
+ */
315
+ declare function discardOnPgCodes(codes: string[]): UploadErrorMiddleware;
316
+ /**
317
+ * Create middleware that treats specific PostgreSQL error codes as success
318
+ * for specific tables.
319
+ *
320
+ * @param config - Map of table names to arrays of PostgreSQL error codes to treat as success
321
+ * @returns Middleware function
322
+ *
323
+ * @example
324
+ * ```typescript
325
+ * uploadErrorMiddleware: [
326
+ * successOnPgCodes({
327
+ * 'ReadReceipt': ['23505'], // Duplicate key is OK
328
+ * 'Notification': ['23505', '23503'], // Duplicate and FK violations are OK
329
+ * }),
330
+ * ]
331
+ * ```
332
+ */
333
+ declare function successOnPgCodes(config: Record<string, string[]>): UploadErrorMiddleware;
334
+ /**
335
+ * Create middleware that retries entries on specific HTTP status codes.
336
+ *
337
+ * @param statusCodes - Array of HTTP status codes to retry
338
+ * @returns Middleware function
339
+ *
340
+ * @example
341
+ * ```typescript
342
+ * uploadErrorMiddleware: [
343
+ * // Retry on rate limiting and server errors
344
+ * retryOnHttpStatus([429, 500, 502, 503, 504]),
345
+ * ]
346
+ * ```
347
+ */
348
+ declare function retryOnHttpStatus(statusCodes: number[]): UploadErrorMiddleware;
349
+ /**
350
+ * Create middleware that discards entries on specific HTTP status codes.
351
+ *
352
+ * @param statusCodes - Array of HTTP status codes to discard
353
+ * @returns Middleware function
354
+ *
355
+ * @example
356
+ * ```typescript
357
+ * uploadErrorMiddleware: [
358
+ * // Discard on 400 Bad Request (malformed data)
359
+ * discardOnHttpStatus([400]),
360
+ * ]
361
+ * ```
362
+ */
363
+ declare function discardOnHttpStatus(statusCodes: number[]): UploadErrorMiddleware;
364
+ /**
365
+ * Create middleware that applies table-specific handling.
366
+ *
367
+ * @param handlers - Map of table names to handler functions
368
+ * @returns Middleware function
369
+ *
370
+ * @example
371
+ * ```typescript
372
+ * uploadErrorMiddleware: [
373
+ * tableHandlers({
374
+ * 'AuditLog': () => 'discard', // Never retry audit logs
375
+ * 'UserSession': ({ pgCode }) =>
376
+ * pgCode === '23505' ? 'success' : 'continue',
377
+ * }),
378
+ * ]
379
+ * ```
380
+ */
381
+ declare function tableHandlers(handlers: Record<string, UploadErrorMiddleware>): UploadErrorMiddleware;
382
+ /**
383
+ * Run upload error middleware chain and return the classification.
384
+ *
385
+ * Executes middleware in order until one returns a non-'continue' result.
386
+ * If all middleware return 'continue', returns the default classification.
387
+ *
388
+ * @param context - The error context to pass to middleware
389
+ * @param middleware - Array of middleware functions to execute
390
+ * @param defaultClassification - Classification to use if all middleware return 'continue'
391
+ * @returns The final classification
392
+ *
393
+ * @example
394
+ * ```typescript
395
+ * const classification = await runUploadErrorMiddleware(
396
+ * context,
397
+ * [idempotentTables(['ReadReceipt']), discardOrphaned()],
398
+ * 'retry' // Default if no middleware matches
399
+ * );
400
+ * ```
401
+ */
402
+ declare function runUploadErrorMiddleware(context: UploadErrorContext, middleware: UploadErrorMiddleware[], defaultClassification?: UploadErrorClassification): Promise<UploadErrorClassification>;
403
+ /**
404
+ * Synchronous version of runUploadErrorMiddleware for middleware that don't use async.
405
+ *
406
+ * @param context - The error context to pass to middleware
407
+ * @param middleware - Array of middleware functions to execute
408
+ * @param defaultClassification - Classification to use if all middleware return 'continue'
409
+ * @returns The final classification
410
+ */
411
+ declare function runUploadErrorMiddlewareSync(context: UploadErrorContext, middleware: UploadErrorMiddleware[], defaultClassification?: UploadErrorClassification): UploadErrorClassification;
412
+
413
+ export { AuthProvider, AuthRefreshError, CircuitBreaker, type CircuitBreakerConfig, type CircuitBreakerStats, CircuitOpenError, type CircuitState, DEFAULT_CIRCUIT_BREAKER_CONFIG, DiscardEntryError, type SupabaseAuthOptions, TransactionAbortError, UploadErrorClassification, UploadErrorContext, UploadErrorMiddleware, createSupabaseAuth, discardOnHttpStatus, discardOnPgCodes, discardOrphaned, idempotentTables, retryOnHttpStatus, runUploadErrorMiddleware, runUploadErrorMiddlewareSync, successOnPgCodes, tableHandlers };
@@ -4,22 +4,44 @@ import {
4
4
  CircuitOpenError,
5
5
  DEFAULT_CIRCUIT_BREAKER_CONFIG,
6
6
  createSupabaseAuth
7
- } from "../chunk-HWSNV45P.js";
7
+ } from "../chunk-FEL22ID3.js";
8
8
  import {
9
9
  DEFAULT_RETRY_CONFIG,
10
+ DiscardEntryError,
10
11
  SupabaseConnector,
11
- defaultSchemaRouter
12
- } from "../chunk-KN2IZERF.js";
12
+ TransactionAbortError,
13
+ defaultSchemaRouter,
14
+ discardOnHttpStatus,
15
+ discardOnPgCodes,
16
+ discardOrphaned,
17
+ idempotentTables,
18
+ retryOnHttpStatus,
19
+ runUploadErrorMiddleware,
20
+ runUploadErrorMiddlewareSync,
21
+ successOnPgCodes,
22
+ tableHandlers
23
+ } from "../chunk-IMRSLJRV.js";
13
24
  import "../chunk-FV2HXEIY.js";
14
- import "../chunk-VACPAAQZ.js";
25
+ import "../chunk-I2AYMY5O.js";
15
26
  export {
16
27
  AuthRefreshError,
17
28
  CircuitBreaker,
18
29
  CircuitOpenError,
19
30
  DEFAULT_CIRCUIT_BREAKER_CONFIG,
20
31
  DEFAULT_RETRY_CONFIG,
32
+ DiscardEntryError,
21
33
  SupabaseConnector,
34
+ TransactionAbortError,
22
35
  createSupabaseAuth,
23
- defaultSchemaRouter
36
+ defaultSchemaRouter,
37
+ discardOnHttpStatus,
38
+ discardOnPgCodes,
39
+ discardOrphaned,
40
+ idempotentTables,
41
+ retryOnHttpStatus,
42
+ runUploadErrorMiddleware,
43
+ runUploadErrorMiddlewareSync,
44
+ successOnPgCodes,
45
+ tableHandlers
24
46
  };
25
47
  //# sourceMappingURL=index.js.map
@@ -151,6 +151,13 @@ declare function classifyError(error: Error): SyncErrorType;
151
151
  * @returns A classified SyncOperationError
152
152
  */
153
153
  declare function toSyncOperationError(error: Error, message?: string): SyncOperationError;
154
+ /**
155
+ * Extract HTTP status code from an error object.
156
+ *
157
+ * Checks for common status code properties and falls back to pattern matching
158
+ * in the error message for 4xx/5xx codes.
159
+ */
160
+ declare function extractHttpStatusCode(error: unknown): number | undefined;
154
161
  /**
155
162
  * Classify a Supabase/PostgreSQL error into a structured result.
156
163
  *
@@ -207,4 +214,4 @@ declare function extractTableNames(entries: CrudEntry[]): string[];
207
214
  */
208
215
  declare function isRlsError(error: unknown): boolean;
209
216
 
210
- export { ATTACHMENT_DOWNLOAD_TIMEOUT_MS, ATTACHMENT_RETRY_DELAY_MS, AttachmentError, COMPRESSION_MAX_WIDTH, COMPRESSION_SKIP_SIZE_BYTES, COMPRESSION_TARGET_SIZE_BYTES, ClassifiedError, ConfigurationError, ConnectorError, CrudEntry, DEFAULT_ATTACHMENT_CACHE_SIZE, DEFAULT_ATTACHMENT_CONCURRENCY, DEFAULT_COMPRESSION_QUALITY, DEFAULT_MAX_RETRY_ATTEMPTS, DEFAULT_RETRY_BACKOFF_MULTIPLIER, DEFAULT_RETRY_BASE_DELAY_MS, DEFAULT_RETRY_MAX_DELAY_MS, DEFAULT_SYNC_INTERVAL_MS, DEFAULT_SYNC_MODE, DOWNLOAD_STOP_THRESHOLD, EVICTION_TRIGGER_THRESHOLD, HEALTH_CHECK_INTERVAL_MS, HEALTH_CHECK_TIMEOUT_MS, InitializationError, LATENCY_DEGRADED_THRESHOLD_MS, MAX_CONSECUTIVE_FAILURES, PlatformAdapterError, PowerSyncError, STATS_CACHE_TTL_MS, STATUS_NOTIFY_THROTTLE_MS, STORAGE_CRITICAL_THRESHOLD, STORAGE_KEY_ATTACHMENT_SETTINGS, STORAGE_KEY_AUTO_OFFLINE, STORAGE_KEY_ENABLED, STORAGE_KEY_METRICS, STORAGE_KEY_PAUSED, STORAGE_KEY_PREFIX, STORAGE_KEY_SYNC_MODE, STORAGE_WARNING_THRESHOLD, SyncError, SyncErrorType, SyncOperationError, classifyError, classifySupabaseError, createSyncError, extractEntityIds, extractTableNames, generateFailureId, isRlsError, toSyncOperationError };
217
+ export { ATTACHMENT_DOWNLOAD_TIMEOUT_MS, ATTACHMENT_RETRY_DELAY_MS, AttachmentError, COMPRESSION_MAX_WIDTH, COMPRESSION_SKIP_SIZE_BYTES, COMPRESSION_TARGET_SIZE_BYTES, ClassifiedError, ConfigurationError, ConnectorError, CrudEntry, DEFAULT_ATTACHMENT_CACHE_SIZE, DEFAULT_ATTACHMENT_CONCURRENCY, DEFAULT_COMPRESSION_QUALITY, DEFAULT_MAX_RETRY_ATTEMPTS, DEFAULT_RETRY_BACKOFF_MULTIPLIER, DEFAULT_RETRY_BASE_DELAY_MS, DEFAULT_RETRY_MAX_DELAY_MS, DEFAULT_SYNC_INTERVAL_MS, DEFAULT_SYNC_MODE, DOWNLOAD_STOP_THRESHOLD, EVICTION_TRIGGER_THRESHOLD, HEALTH_CHECK_INTERVAL_MS, HEALTH_CHECK_TIMEOUT_MS, InitializationError, LATENCY_DEGRADED_THRESHOLD_MS, MAX_CONSECUTIVE_FAILURES, PlatformAdapterError, PowerSyncError, STATS_CACHE_TTL_MS, STATUS_NOTIFY_THROTTLE_MS, STORAGE_CRITICAL_THRESHOLD, STORAGE_KEY_ATTACHMENT_SETTINGS, STORAGE_KEY_AUTO_OFFLINE, STORAGE_KEY_ENABLED, STORAGE_KEY_METRICS, STORAGE_KEY_PAUSED, STORAGE_KEY_PREFIX, STORAGE_KEY_SYNC_MODE, STORAGE_WARNING_THRESHOLD, SyncError, SyncErrorType, SyncOperationError, classifyError, classifySupabaseError, createSyncError, extractEntityIds, extractHttpStatusCode, extractTableNames, generateFailureId, isRlsError, toSyncOperationError };