@pol-studios/powersync 1.0.6 → 1.0.7

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (118) hide show
  1. package/dist/CacheSettingsManager-1exbOC6S.d.ts +261 -0
  2. package/dist/attachments/index.d.ts +65 -355
  3. package/dist/attachments/index.js +24 -6
  4. package/dist/{types-Cd7RhNqf.d.ts → background-sync-ChCXW-EV.d.ts} +53 -2
  5. package/dist/chunk-4C3RY5SU.js +204 -0
  6. package/dist/chunk-4C3RY5SU.js.map +1 -0
  7. package/dist/{chunk-3AYXHQ4W.js → chunk-53WH2JJV.js} +111 -47
  8. package/dist/chunk-53WH2JJV.js.map +1 -0
  9. package/dist/chunk-A4IBBWGO.js +377 -0
  10. package/dist/chunk-A4IBBWGO.js.map +1 -0
  11. package/dist/chunk-BREGB4WL.js +1768 -0
  12. package/dist/chunk-BREGB4WL.js.map +1 -0
  13. package/dist/{chunk-EJ23MXPQ.js → chunk-CGL33PL4.js} +3 -1
  14. package/dist/chunk-CGL33PL4.js.map +1 -0
  15. package/dist/chunk-DGUM43GV.js +11 -0
  16. package/dist/chunk-DHYUBVP7.js +131 -0
  17. package/dist/chunk-DHYUBVP7.js.map +1 -0
  18. package/dist/chunk-FV2HXEIY.js +124 -0
  19. package/dist/chunk-FV2HXEIY.js.map +1 -0
  20. package/dist/chunk-GKF7TOMT.js +1 -0
  21. package/dist/{chunk-R4YFWQ3Q.js → chunk-H772V6XQ.js} +304 -51
  22. package/dist/chunk-H772V6XQ.js.map +1 -0
  23. package/dist/{chunk-62J2DPKX.js → chunk-HFOFLW5F.js} +396 -412
  24. package/dist/chunk-HFOFLW5F.js.map +1 -0
  25. package/dist/chunk-KGSFAE5B.js +1 -0
  26. package/dist/chunk-LNL64IJZ.js +1 -0
  27. package/dist/chunk-MKD2VCX3.js +32 -0
  28. package/dist/chunk-MKD2VCX3.js.map +1 -0
  29. package/dist/{chunk-7EMDVIZX.js → chunk-N75DEF5J.js} +19 -1
  30. package/dist/chunk-N75DEF5J.js.map +1 -0
  31. package/dist/chunk-P6WOZO7H.js +49 -0
  32. package/dist/chunk-P6WOZO7H.js.map +1 -0
  33. package/dist/chunk-TGBT5XBE.js +1 -0
  34. package/dist/chunk-TGBT5XBE.js.map +1 -0
  35. package/dist/chunk-UEYRTLKE.js +72 -0
  36. package/dist/chunk-UEYRTLKE.js.map +1 -0
  37. package/dist/chunk-WGHNIAF7.js +329 -0
  38. package/dist/chunk-WGHNIAF7.js.map +1 -0
  39. package/dist/chunk-WQ5MPAVC.js +449 -0
  40. package/dist/chunk-WQ5MPAVC.js.map +1 -0
  41. package/dist/{chunk-FPTDATY5.js → chunk-XQAJM2MW.js} +22 -11
  42. package/dist/chunk-XQAJM2MW.js.map +1 -0
  43. package/dist/chunk-YSTEESEG.js +676 -0
  44. package/dist/chunk-YSTEESEG.js.map +1 -0
  45. package/dist/chunk-ZEOKPWUC.js +1165 -0
  46. package/dist/chunk-ZEOKPWUC.js.map +1 -0
  47. package/dist/connector/index.d.ts +182 -3
  48. package/dist/connector/index.js +12 -4
  49. package/dist/core/index.d.ts +5 -3
  50. package/dist/core/index.js +5 -2
  51. package/dist/error/index.d.ts +54 -0
  52. package/dist/error/index.js +8 -0
  53. package/dist/error/index.js.map +1 -0
  54. package/dist/index.d.ts +100 -12
  55. package/dist/index.js +148 -38
  56. package/dist/index.native.d.ts +20 -10
  57. package/dist/index.native.js +148 -39
  58. package/dist/index.web.d.ts +20 -10
  59. package/dist/index.web.js +149 -39
  60. package/dist/maintenance/index.d.ts +118 -0
  61. package/dist/maintenance/index.js +17 -0
  62. package/dist/maintenance/index.js.map +1 -0
  63. package/dist/platform/index.d.ts +16 -1
  64. package/dist/platform/index.js +2 -0
  65. package/dist/platform/index.js.map +1 -1
  66. package/dist/platform/index.native.d.ts +2 -2
  67. package/dist/platform/index.native.js +2 -1
  68. package/dist/platform/index.web.d.ts +1 -1
  69. package/dist/platform/index.web.js +2 -1
  70. package/dist/pol-attachment-queue-C7YNXXhK.d.ts +676 -0
  71. package/dist/provider/index.d.ts +447 -21
  72. package/dist/provider/index.js +33 -13
  73. package/dist/storage/index.d.ts +6 -0
  74. package/dist/storage/index.js +28 -0
  75. package/dist/storage/index.js.map +1 -0
  76. package/dist/storage/index.native.d.ts +6 -0
  77. package/dist/storage/index.native.js +26 -0
  78. package/dist/storage/index.native.js.map +1 -0
  79. package/dist/storage/index.web.d.ts +6 -0
  80. package/dist/storage/index.web.js +26 -0
  81. package/dist/storage/index.web.js.map +1 -0
  82. package/dist/storage/upload/index.d.ts +55 -0
  83. package/dist/storage/upload/index.js +15 -0
  84. package/dist/storage/upload/index.js.map +1 -0
  85. package/dist/storage/upload/index.native.d.ts +57 -0
  86. package/dist/storage/upload/index.native.js +14 -0
  87. package/dist/storage/upload/index.native.js.map +1 -0
  88. package/dist/storage/upload/index.web.d.ts +5 -0
  89. package/dist/storage/upload/index.web.js +14 -0
  90. package/dist/storage/upload/index.web.js.map +1 -0
  91. package/dist/{index-l3iL9Jte.d.ts → supabase-connector-qLm-WHkM.d.ts} +90 -25
  92. package/dist/sync/index.d.ts +288 -23
  93. package/dist/sync/index.js +22 -10
  94. package/dist/types-BVacP54t.d.ts +52 -0
  95. package/dist/types-Bgvx7-E8.d.ts +187 -0
  96. package/dist/{types-afHtE1U_.d.ts → types-CDqWh56B.d.ts} +2 -0
  97. package/package.json +72 -2
  98. package/dist/chunk-32OLICZO.js +0 -1
  99. package/dist/chunk-3AYXHQ4W.js.map +0 -1
  100. package/dist/chunk-5FIMA26D.js +0 -1
  101. package/dist/chunk-62J2DPKX.js.map +0 -1
  102. package/dist/chunk-7EMDVIZX.js.map +0 -1
  103. package/dist/chunk-EJ23MXPQ.js.map +0 -1
  104. package/dist/chunk-FPTDATY5.js.map +0 -1
  105. package/dist/chunk-KCDG2MNP.js +0 -1431
  106. package/dist/chunk-KCDG2MNP.js.map +0 -1
  107. package/dist/chunk-OLHGI472.js +0 -1
  108. package/dist/chunk-PAFBKNL3.js +0 -99
  109. package/dist/chunk-PAFBKNL3.js.map +0 -1
  110. package/dist/chunk-R4YFWQ3Q.js.map +0 -1
  111. package/dist/chunk-V6LJ6MR2.js +0 -740
  112. package/dist/chunk-V6LJ6MR2.js.map +0 -1
  113. package/dist/chunk-VJCL2SWD.js +0 -1
  114. package/dist/failed-upload-store-C0cLxxPz.d.ts +0 -33
  115. /package/dist/{chunk-32OLICZO.js.map → chunk-DGUM43GV.js.map} +0 -0
  116. /package/dist/{chunk-5FIMA26D.js.map → chunk-GKF7TOMT.js.map} +0 -0
  117. /package/dist/{chunk-OLHGI472.js.map → chunk-KGSFAE5B.js.map} +0 -0
  118. /package/dist/{chunk-VJCL2SWD.js.map → chunk-LNL64IJZ.js.map} +0 -0
@@ -0,0 +1 @@
1
+ {"version":3,"sources":["../src/connector/types.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\n/**\n * Configuration passed to PowerSyncProvider for connector setup\n */\nexport interface ConnectorConfig {\n /**\n * Custom schema routing function.\n * Determines which Supabase schema a table belongs to.\n * @default Returns 'public' for all tables\n *\n * @example\n * ```typescript\n * schemaRouter: (table) => {\n * if (['Profile', 'Comment'].includes(table)) return 'core';\n * return 'public';\n * }\n * ```\n */\n schemaRouter?: SchemaRouter;\n\n /**\n * Custom CRUD handler for complex mutations.\n * @default Uses standard upsert/update/delete operations\n */\n crudHandler?: CrudHandler;\n\n /**\n * Token refresh configuration.\n */\n tokenRefresh?: {\n /** Refresh token when it expires within this many seconds (default: 60) */\n refreshThresholdSeconds?: number;\n };\n\n /**\n * Optional retry configuration for upload failures.\n * Allows customizing retry attempts, delays, and backoff for different error types.\n * @default DEFAULT_RETRY_CONFIG\n */\n retryConfig?: Partial<RetryConfig>;\n}\n\n// ─── Schema Routing ──────────────────────────────────────────────────────────\n\n/**\n * Function that determines which Supabase schema a table belongs to.\n *\n * @param tableName - The name of the table\n * @returns The schema name (e.g., 'public', 'core')\n */\nexport type SchemaRouter = (tableName: string) => string;\n\n/**\n * Default schema router that returns 'public' for all tables\n */\nexport const defaultSchemaRouter: SchemaRouter = () => 'public';\n\n// ─── CRUD Handling ───────────────────────────────────────────────────────────\n\n/**\n * Custom handler for CRUD operations.\n *\n * Return `true` from a handler to indicate the operation was handled.\n * Return `false` to fall back to default behavior.\n */\nexport interface CrudHandler {\n /**\n * Handle a PUT operation (insert or replace).\n * @param entry - The CRUD entry\n * @param supabase - Supabase client\n * @param schema - The resolved schema for this table\n * @returns true if handled, false to use default behavior\n */\n handlePut?(entry: CrudEntry, supabase: SupabaseClient, schema: string): Promise<boolean>;\n\n /**\n * Handle a PATCH operation (update).\n * @param entry - The CRUD entry\n * @param supabase - Supabase client\n * @param schema - The resolved schema for this table\n * @returns true if handled, false to use default behavior\n */\n handlePatch?(entry: CrudEntry, supabase: SupabaseClient, schema: string): Promise<boolean>;\n\n /**\n * Handle a DELETE operation.\n * @param entry - The CRUD entry\n * @param supabase - Supabase client\n * @param schema - The resolved schema for this table\n * @returns true if handled, false to use default behavior\n */\n handleDelete?(entry: CrudEntry, supabase: SupabaseClient, schema: string): Promise<boolean>;\n}\n\n// ─── Credentials ─────────────────────────────────────────────────────────────\n\n/**\n * Credentials returned by fetchCredentials\n */\nexport interface PowerSyncCredentials {\n /** PowerSync service endpoint URL */\n endpoint: string;\n /** JWT token for authentication */\n token: string;\n /** When the token expires */\n expiresAt?: Date;\n}\n\n// ─── Retry Configuration ─────────────────────────────────────────────────────\n\n/**\n * Configuration for a single retry category\n */\nexport interface RetryStrategyConfig {\n /** Maximum number of retry attempts */\n maxRetries: number;\n /** Initial delay in milliseconds */\n baseDelayMs: number;\n /** Maximum delay cap in milliseconds */\n maxDelayMs: number;\n /** Multiplier for exponential backoff */\n backoffMultiplier: number;\n}\n\n/**\n * Full retry configuration for the connector\n */\nexport interface RetryConfig {\n /** Retry config for transient errors (network, server 5xx) */\n transient: RetryStrategyConfig;\n /** Retry config for permanent errors (RLS, validation, constraints) */\n permanent: RetryStrategyConfig;\n}\n\n/**\n * Default retry configuration\n *\n * Uses fast exponential backoff for transient errors (network issues):\n * - Transient: 1s → 2s → 4s\n *\n * Permanent errors (RLS violations, constraint errors) 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};\n\n// ─── Re-export from Core ─────────────────────────────────────────────────────\n\n// Re-export PowerSyncBackendConnector from core/types to maintain API compatibility\nexport type { PowerSyncBackendConnector } from '../core/types';","/**\n * Conflict Detection for @pol-studios/powersync\n *\n * Provides version-based conflict detection using AuditLog attribution.\n * Only queries AuditLog when version mismatch is detected.\n */\n\nimport type { SupabaseClient } from '@supabase/supabase-js';\nimport type { AbstractPowerSyncDatabase } from '../core/types';\nimport type { FieldConflict, ConflictCheckResult, ConflictDetectionConfig } from './types';\n\n/**\n * 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 } from './types';\nimport type { ConflictBus } from '../conflicts/conflict-bus';\nimport type { ConflictHandler, ConflictDetectionConfig, ConflictCheckResult, ConflictResolution } from '../conflicts/types';\nimport { defaultSchemaRouter, DEFAULT_RETRY_CONFIG } from './types';\nimport { classifySupabaseError } from '../core/errors';\nimport { detectConflicts, fetchServerVersion, getLocalVersion, hasVersionColumn } from '../conflicts/detect';\nimport { withExponentialBackoff } 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 private autoRetryPaused: boolean = false;\n constructor(options: SupabaseConnectorOptions) {\n this.supabase = options.supabaseClient;\n this.powerSyncUrl = options.powerSyncUrl;\n this.schemaRouter = options.schemaRouter ?? defaultSchemaRouter;\n this.crudHandler = options.crudHandler;\n this.logger = options.logger;\n this.onTransactionSuccess = options.onTransactionSuccess;\n this.onTransactionFailure = options.onTransactionFailure;\n this.onTransactionComplete = options.onTransactionComplete;\n this.shouldUploadFn = options.shouldUpload;\n\n // Conflict detection options\n this.conflictDetection = options.conflictDetection;\n this.conflictHandler = options.conflictHandler;\n this.conflictBus = options.conflictBus;\n\n // Initialize retry configuration by merging user options with defaults\n this.retryConfig = {\n transient: {\n ...DEFAULT_RETRY_CONFIG.transient,\n ...options.retryConfig?.transient\n },\n permanent: {\n ...DEFAULT_RETRY_CONFIG.permanent,\n ...options.retryConfig?.permanent\n }\n };\n\n // Subscribe to resolution events from the conflict bus\n // This stores resolutions so that on retry, we apply them instead of re-detecting\n if (this.conflictBus) {\n this.unsubscribeResolution = this.conflictBus.onResolution((table, recordId, resolution) => {\n // Key by table:recordId to avoid collisions across tables with same UUID\n const key = `${table}:${recordId}`;\n if (__DEV__) {\n console.log('[Connector] Storing resolution for retry:', {\n table,\n recordId,\n key,\n resolution\n });\n }\n this.resolvedConflicts.set(key, resolution);\n });\n }\n }\n\n /**\n * Clean up resources (unsubscribe from event listeners).\n * Call this when the connector is no longer needed.\n */\n destroy(): void {\n this.isDestroyed = true;\n if (this.unsubscribeResolution) {\n this.unsubscribeResolution();\n this.unsubscribeResolution = undefined;\n }\n this.resolvedConflicts.clear();\n this.versionColumnPromises.clear();\n }\n\n // ─── Retry Control Methods ─────────────────────────────────────────────────\n\n /**\n * Pause automatic retry of failed uploads.\n * Use this when the user goes offline intentionally or wants manual control.\n */\n pauseAutoRetry(): void {\n this.autoRetryPaused = true;\n if (__DEV__) {\n console.log('[Connector] Auto-retry paused');\n }\n }\n\n /**\n * Resume automatic retry of failed uploads.\n */\n resumeAutoRetry(): void {\n this.autoRetryPaused = false;\n if (__DEV__) {\n console.log('[Connector] Auto-retry resumed');\n }\n }\n\n // ─── 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 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 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 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\n // Phase 2: Select config based on classified error type, then retry\n const selectedConfig = classified.isPermanent ? this.retryConfig.permanent : this.retryConfig.transient;\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 } 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 // 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 * @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 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 // 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 this.logger?.error('[Connector] Transaction completion FAILED:', {\n errorMessage: error instanceof Error ? error.message : String(error),\n classified,\n isPermanent: classified.isPermanent,\n entries: successfulEntries.map(e => ({\n table: e.table,\n op: e.op,\n id: e.id\n }))\n });\n\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 });\n }\n this.logger?.error('[Connector] Transaction completion error:', {\n error,\n classified,\n entries: successfulEntries.map(e => ({\n table: e.table,\n op: e.op,\n id: e.id\n }))\n });\n\n // Notify failure with classification\n this.onTransactionFailure?.(successfulEntries, error instanceof Error ? error : new Error(String(error)), classified);\n\n // Re-throw for PowerSync's native retry mechanism\n throw error;\n }\n }\n\n /**\n * Process a transaction without conflict detection.\n * 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\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: No try-catch - stop on first failure to maintain transaction atomicity\n if (this.crudHandler) {\n // Process all entries individually when custom handler is configured\n for (const entry of entries) {\n await this.processWithRetry(entry);\n successfulEntries.push(entry);\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: No try-catch - stop on first failure to maintain transaction 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 await this.processWithRetry(entry);\n successfulEntries.push(entry);\n }\n }\n\n // Finalize the transaction using the shared helper\n await this.finalizeTransaction({\n transaction,\n successfulEntries\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: No try-catch - first failure throws to maintain atomicity\n for (const entry of entries) {\n await this.processWithRetry(entry);\n successful.push(entry);\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: No try-catch - first failure throws to maintain atomicity\n for (const entry of entries) {\n await this.processWithRetry(entry);\n successful.push(entry);\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":[]}
@@ -0,0 +1 @@
1
+ //# sourceMappingURL=chunk-KGSFAE5B.js.map
@@ -0,0 +1 @@
1
+ //# sourceMappingURL=chunk-LNL64IJZ.js.map
@@ -0,0 +1,32 @@
1
+ // src/storage/types.ts
2
+ function resolveBucketFromConfig(config, storagePath) {
3
+ if (config.resolver) {
4
+ const resolved = config.resolver(storagePath);
5
+ if (resolved !== void 0) {
6
+ return resolved;
7
+ }
8
+ }
9
+ if (config.bucketMap) {
10
+ for (const [prefix, bucket] of config.bucketMap) {
11
+ if (storagePath.startsWith(prefix)) {
12
+ return bucket;
13
+ }
14
+ }
15
+ }
16
+ return config.defaultBucket;
17
+ }
18
+
19
+ // src/storage/upload/types.ts
20
+ var DEFAULT_UPLOAD_NOTIFICATION = {
21
+ enabled: true,
22
+ autoClear: true,
23
+ onProgressTitle: "Uploading...",
24
+ onCompleteTitle: "Upload complete",
25
+ onErrorTitle: "Upload failed"
26
+ };
27
+
28
+ export {
29
+ resolveBucketFromConfig,
30
+ DEFAULT_UPLOAD_NOTIFICATION
31
+ };
32
+ //# sourceMappingURL=chunk-MKD2VCX3.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"sources":["../src/storage/types.ts","../src/storage/upload/types.ts"],"sourcesContent":["/**\n * Storage Types for @pol-studios/powersync\n *\n * Defines interfaces for pluggable storage adapters supporting\n * different backend storage providers (Supabase, S3, etc.).\n */\n\n// ─── Bucket Resolution ──────────────────────────────────────────────────────\n\n/**\n * Function type for resolving storage bucket from a file path.\n *\n * @example\n * ```typescript\n * const resolver: BucketResolver = (path) => {\n * if (path.startsWith('avatars/')) return 'user-avatars';\n * if (path.startsWith('docs/')) return 'documents';\n * return 'default-bucket';\n * };\n * ```\n */\nexport type BucketResolver = (storagePath: string) => string;\n\n/**\n * Configuration for bucket resolution.\n * Supports multiple strategies: default bucket, prefix mapping, or custom resolver.\n */\nexport interface BucketConfig {\n /** Default bucket when no other rule matches */\n defaultBucket: string;\n\n /**\n * Map of path prefixes to bucket names.\n * Checked in iteration order (use Map to preserve order).\n *\n * @example\n * ```typescript\n * const bucketMap = new Map([\n * ['avatars/', 'user-avatars'],\n * ['docs/', 'documents'],\n * ]);\n * ```\n */\n bucketMap?: Map<string, string>;\n\n /**\n * Custom resolver function (takes precedence over bucketMap).\n * Return undefined to fall through to bucketMap/defaultBucket.\n */\n resolver?: (storagePath: string) => string | undefined;\n}\n\n/**\n * Resolve bucket from a storage path using BucketConfig.\n *\n * Resolution order:\n * 1. Custom resolver (if provided and returns a value)\n * 2. bucketMap prefix matching (if provided)\n * 3. defaultBucket\n */\nexport function resolveBucketFromConfig(config: BucketConfig, storagePath: string): string {\n // Try custom resolver first\n if (config.resolver) {\n const resolved = config.resolver(storagePath);\n if (resolved !== undefined) {\n return resolved;\n }\n }\n\n // Try bucket map prefix matching\n if (config.bucketMap) {\n for (const [prefix, bucket] of config.bucketMap) {\n if (storagePath.startsWith(prefix)) {\n return bucket;\n }\n }\n }\n\n // Fall back to default\n return config.defaultBucket;\n}\n\n// ─── Remote Storage Adapter ─────────────────────────────────────────────────\n\n/**\n * Interface for remote storage download adapters.\n * Implement this to support non-Supabase storage backends.\n */\nexport interface RemoteStorageAdapter {\n /**\n * Download a file from remote storage to local path.\n */\n download(remotePath: string, localPath: string): Promise<void>;\n\n /**\n * Get a signed download URL for a remote file.\n */\n getDownloadUrl(remotePath: string): Promise<string>;\n\n /**\n * Check if a file exists in remote storage.\n */\n exists(remotePath: string): Promise<boolean>;\n}\n\n// ─── Upload Handler ─────────────────────────────────────────────────────────\n\n/**\n * Options for upload operations.\n */\nexport interface UploadOptions {\n contentType?: string;\n onProgress?: (progress: {\n loaded: number;\n total: number;\n }) => void;\n}\n\n/**\n * Interface for attachment upload handlers.\n * Implement this to support non-Supabase upload backends.\n */\nexport interface AttachmentUploadHandler {\n /**\n * Upload a file from local path to remote storage.\n */\n upload(localPath: string, remotePath: string, options?: UploadOptions): Promise<void>;\n\n /**\n * Get a signed upload URL (optional, for direct uploads).\n */\n getUploadUrl?(remotePath: string): Promise<string>;\n}\n\n/**\n * Interface for platform-specific upload handlers.\n * Used by SupabaseUploadHandler implementations.\n *\n * Note: This is named StorageUploadHandler to avoid conflicts with\n * the UploadHandler interface in attachments/types.ts which has\n * slightly different semantics.\n */\nexport interface StorageUploadHandler {\n /**\n * Upload a file to remote storage.\n *\n * @param storagePath - Remote path in storage bucket\n * @param localFileUri - Local file URI (file:// on native, blob URL on web)\n * @param mediaType - MIME type of the file\n * @param signal - Optional AbortSignal for cancellation\n */\n uploadFile(storagePath: string, localFileUri: string, mediaType: string, signal?: AbortSignal): Promise<void>;\n\n /**\n * Resolve the storage bucket for a given path.\n */\n resolveBucket(storagePath: string): string;\n}\n\n// ─── Supabase Storage Configuration ─────────────────────────────────────────\n\nimport type { LoggerAdapter } from '../platform/types';\n\n/**\n * Options for creating a SupabaseStorageAdapter.\n */\nexport interface SupabaseStorageAdapterOptions {\n /** Supabase client instance */\n client: any; // SupabaseClient - using any to avoid version conflicts\n\n /** Bucket configuration for multi-bucket routing */\n bucketConfig: BucketConfig;\n\n /**\n * Custom signed URL expiry in seconds.\n * @default 60 (short-lived for security)\n */\n signedUrlExpiry?: number;\n\n /**\n * Optional logger for diagnostic messages.\n * If not provided, falls back to console.warn for warnings.\n */\n logger?: LoggerAdapter;\n}\n\n// ─── Download Result ────────────────────────────────────────────────────────\n\n/**\n * Result of a download operation.\n */\nexport interface DownloadResult {\n /** Local file path where the file was saved */\n localPath: string;\n /** MIME type of the downloaded file */\n mimeType: string;\n /** Size of the downloaded file in bytes */\n size: number;\n}\n\n// ─── PowerSync StorageAdapter Interface ─────────────────────────────────────\n\n/**\n * PowerSync StorageAdapter interface from @powersync/attachments.\n * Re-declared here to avoid import dependency.\n */\nexport interface PowerSyncStorageAdapter {\n /** Upload a file to remote storage */\n uploadFile(filename: string, data: ArrayBuffer, options?: {\n mediaType?: string;\n }): Promise<void>;\n\n /** Download a file from remote storage */\n downloadFile(filePath: string): Promise<Blob>;\n\n /** Write a file to local storage */\n writeFile(fileURI: string, base64Data: string, options?: {\n encoding?: 'utf8' | 'base64';\n }): Promise<void>;\n\n /** Read a file from local storage */\n readFile(fileURI: string, options?: {\n encoding?: 'utf8' | 'base64';\n mediaType?: string;\n }): Promise<ArrayBuffer>;\n\n /** Delete a local file */\n deleteFile(uri: string, options?: {\n filename?: string;\n }): Promise<void>;\n\n /** Check if a local file exists */\n fileExists(fileURI: string): Promise<boolean>;\n\n /** Create a directory */\n makeDir(uri: string): Promise<void>;\n\n /** Copy a file */\n copyFile(sourceUri: string, targetUri: string): Promise<void>;\n\n /** Get the user storage directory */\n getUserStorageDirectory(): string;\n}","/**\n * Upload Handler Types for @pol-studios/powersync\n *\n * Defines types for platform-specific upload handlers.\n */\n\nimport type { BucketConfig } from '../types';\n\n/**\n * Options for creating a SupabaseUploadHandler.\n */\nexport interface SupabaseUploadHandlerOptions {\n /** Supabase client instance */\n supabaseClient: any; // SupabaseClient - using any to avoid version conflicts\n\n /** Bucket configuration for multi-bucket routing */\n bucketConfig: BucketConfig;\n}\n\n/**\n * Event handlers for upload progress tracking.\n */\nexport interface UploadEventHandlers {\n /** Called with progress updates during upload */\n onProgress?: (progress: {\n loaded: number;\n total: number;\n }) => void;\n\n /** Called when upload completes successfully */\n onComplete?: () => void;\n\n /** Called when upload fails */\n onError?: (error: Error) => void;\n}\n\n/**\n * Configuration for native upload notifications (Android).\n */\nexport interface UploadNotificationConfig {\n /** Whether to show upload notifications */\n enabled: boolean;\n\n /** Whether to auto-clear notification on completion */\n autoClear: boolean;\n\n /** Title shown during upload progress */\n onProgressTitle: string;\n\n /** Title shown when upload completes */\n onCompleteTitle: string;\n\n /** Title shown when upload fails */\n onErrorTitle: string;\n}\n\n/**\n * Default notification configuration for Android uploads.\n */\nexport const DEFAULT_UPLOAD_NOTIFICATION: UploadNotificationConfig = {\n enabled: true,\n autoClear: true,\n onProgressTitle: 'Uploading...',\n onCompleteTitle: 'Upload complete',\n onErrorTitle: 'Upload failed'\n};"],"mappings":";AA4DO,SAAS,wBAAwB,QAAsB,aAA6B;AAEzF,MAAI,OAAO,UAAU;AACnB,UAAM,WAAW,OAAO,SAAS,WAAW;AAC5C,QAAI,aAAa,QAAW;AAC1B,aAAO;AAAA,IACT;AAAA,EACF;AAGA,MAAI,OAAO,WAAW;AACpB,eAAW,CAAC,QAAQ,MAAM,KAAK,OAAO,WAAW;AAC/C,UAAI,YAAY,WAAW,MAAM,GAAG;AAClC,eAAO;AAAA,MACT;AAAA,IACF;AAAA,EACF;AAGA,SAAO,OAAO;AAChB;;;ACrBO,IAAM,8BAAwD;AAAA,EACnE,SAAS;AAAA,EACT,WAAW;AAAA,EACX,iBAAiB;AAAA,EACjB,iBAAiB;AAAA,EACjB,cAAc;AAChB;","names":[]}
@@ -130,6 +130,24 @@ function createWebPlatformAdapter(logger) {
130
130
  }
131
131
  }
132
132
  return 10 * 1024 * 1024 * 1024;
133
+ },
134
+ async downloadFile(url, localPath) {
135
+ const response = await fetch(url);
136
+ if (!response.ok) {
137
+ throw new Error(`Download failed with status ${response.status}: ${url}`);
138
+ }
139
+ const blob = await response.blob();
140
+ const reader = new FileReader();
141
+ const base64 = await new Promise((resolve, reject) => {
142
+ reader.onload = () => {
143
+ const dataUrl = reader.result;
144
+ const base64Data = dataUrl.split(",")[1];
145
+ resolve(base64Data);
146
+ };
147
+ reader.onerror = () => reject(reader.error);
148
+ reader.readAsDataURL(blob);
149
+ });
150
+ await fileStorage.set(localPath, base64);
133
151
  }
134
152
  };
135
153
  const storage = {
@@ -287,4 +305,4 @@ function createWebPlatformAdapter(logger) {
287
305
  export {
288
306
  createWebPlatformAdapter
289
307
  };
290
- //# sourceMappingURL=chunk-7EMDVIZX.js.map
308
+ //# sourceMappingURL=chunk-N75DEF5J.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"sources":["../src/platform/index.web.ts"],"sourcesContent":["/**\n * Web Platform Adapter for @pol-studios/powersync\n *\n * Implements the PlatformAdapter interface using Web APIs:\n * - @powersync/web for SQLite database (via wa-sqlite)\n * - IndexedDB for file storage (via idb-keyval pattern)\n * - localStorage for key-value storage\n * - navigator.onLine + events for network monitoring\n * - Canvas API for image compression\n */\n\nimport type { PlatformAdapter, DatabaseOptions, FileSystemAdapter, AsyncStorageAdapter, NetworkAdapter, LoggerAdapter, ImageProcessorAdapter, FileInfo, CompressedImage, CompressionOptions, ConnectionType } from './types';\nimport type { AbstractPowerSyncDatabase } from '../core/types';\n\n// IndexedDB database name for file storage\nconst FILE_STORAGE_DB_NAME = 'powersync-files';\nconst FILE_STORAGE_STORE_NAME = 'files';\n\n/**\n * Simple IndexedDB wrapper for file storage\n */\nclass IndexedDBFileStorage {\n private dbPromise: Promise<IDBDatabase> | null = null;\n private getDB(): Promise<IDBDatabase> {\n if (!this.dbPromise) {\n this.dbPromise = new Promise((resolve, reject) => {\n const request = indexedDB.open(FILE_STORAGE_DB_NAME, 1);\n request.onerror = () => reject(request.error);\n request.onsuccess = () => resolve(request.result);\n request.onupgradeneeded = () => {\n const db = request.result;\n if (!db.objectStoreNames.contains(FILE_STORAGE_STORE_NAME)) {\n db.createObjectStore(FILE_STORAGE_STORE_NAME);\n }\n };\n });\n }\n return this.dbPromise;\n }\n async get(key: string): Promise<string | null> {\n const db = await this.getDB();\n return new Promise((resolve, reject) => {\n const transaction = db.transaction(FILE_STORAGE_STORE_NAME, 'readonly');\n const store = transaction.objectStore(FILE_STORAGE_STORE_NAME);\n const request = store.get(key);\n request.onerror = () => reject(request.error);\n request.onsuccess = () => resolve(request.result ?? null);\n });\n }\n async set(key: string, value: string): Promise<void> {\n const db = await this.getDB();\n return new Promise((resolve, reject) => {\n const transaction = db.transaction(FILE_STORAGE_STORE_NAME, 'readwrite');\n const store = transaction.objectStore(FILE_STORAGE_STORE_NAME);\n const request = store.put(value, key);\n request.onerror = () => reject(request.error);\n request.onsuccess = () => resolve();\n });\n }\n async delete(key: string): Promise<void> {\n const db = await this.getDB();\n return new Promise((resolve, reject) => {\n const transaction = db.transaction(FILE_STORAGE_STORE_NAME, 'readwrite');\n const store = transaction.objectStore(FILE_STORAGE_STORE_NAME);\n const request = store.delete(key);\n request.onerror = () => reject(request.error);\n request.onsuccess = () => resolve();\n });\n }\n async has(key: string): Promise<boolean> {\n const db = await this.getDB();\n return new Promise((resolve, reject) => {\n const transaction = db.transaction(FILE_STORAGE_STORE_NAME, 'readonly');\n const store = transaction.objectStore(FILE_STORAGE_STORE_NAME);\n const request = store.count(key);\n request.onerror = () => reject(request.error);\n request.onsuccess = () => resolve(request.result > 0);\n });\n }\n}\n\n/**\n * Create a Web platform adapter\n *\n * @param logger - Logger implementation to use\n * @returns Platform adapter configured for Web/PWA\n *\n * @example\n * ```typescript\n * import { createWebPlatformAdapter } from '@pol-studios/powersync/platform';\n *\n * const logger = {\n * debug: console.log,\n * info: console.log,\n * warn: console.warn,\n * error: console.error,\n * };\n *\n * const platform = createWebPlatformAdapter(logger);\n * ```\n */\nexport function createWebPlatformAdapter(logger: LoggerAdapter): PlatformAdapter {\n const fileStorage = new IndexedDBFileStorage();\n\n // Lazy load PowerSync web\n let PowerSyncDatabase: typeof import('@powersync/web').PowerSyncDatabase;\n let WASQLiteOpenFactory: typeof import('@powersync/web').WASQLiteOpenFactory;\n const loadPowerSync = async () => {\n if (!PowerSyncDatabase) {\n const psModule = await import('@powersync/web');\n PowerSyncDatabase = psModule.PowerSyncDatabase;\n WASQLiteOpenFactory = psModule.WASQLiteOpenFactory;\n }\n };\n\n // File system adapter implementation using IndexedDB\n const fileSystem: FileSystemAdapter = {\n async readFile(uri: string, encoding: 'base64' | 'utf8' = 'utf8'): Promise<string> {\n const content = await fileStorage.get(uri);\n if (content === null) {\n throw new Error(`File not found: ${uri}`);\n }\n return content;\n },\n async writeFile(uri: string, data: string, encoding: 'base64' | 'utf8' = 'utf8'): Promise<void> {\n await fileStorage.set(uri, data);\n },\n async deleteFile(uri: string): Promise<void> {\n await fileStorage.delete(uri);\n },\n async copyFile(source: string, destination: string): Promise<void> {\n const content = await fileStorage.get(source);\n if (content === null) {\n throw new Error(`Source file not found: ${source}`);\n }\n await fileStorage.set(destination, content);\n },\n async moveFile(source: string, destination: string): Promise<void> {\n const content = await fileStorage.get(source);\n if (content === null) {\n throw new Error(`Source file not found: ${source}`);\n }\n await fileStorage.set(destination, content);\n await fileStorage.delete(source);\n },\n async getFileInfo(uri: string): Promise<FileInfo | null> {\n const exists = await fileStorage.has(uri);\n if (!exists) return null;\n const content = await fileStorage.get(uri);\n return {\n exists: true,\n size: content ? content.length : 0,\n isDirectory: false // IndexedDB doesn't have directories\n };\n },\n async makeDirectory(uri: string, options?: {\n intermediates?: boolean;\n }): Promise<void> {\n // No-op for IndexedDB - directories are virtual\n },\n getDocumentsDirectory(): string {\n return '/powersync-docs/';\n },\n getCacheDirectory(): string {\n return '/powersync-cache/';\n },\n async getFreeDiskSpace(): Promise<number> {\n // Try to use Storage API if available\n if ('storage' in navigator && 'estimate' in navigator.storage) {\n try {\n const estimate = await navigator.storage.estimate();\n const quota = estimate.quota ?? 0;\n const usage = estimate.usage ?? 0;\n return quota - usage;\n } catch {\n // Fall through to default\n }\n }\n // Return a large default value if Storage API not available\n return 10 * 1024 * 1024 * 1024; // 10 GB default\n },\n async downloadFile(url: string, localPath: string): Promise<void> {\n // Fetch the file as a blob\n const response = await fetch(url);\n if (!response.ok) {\n throw new Error(`Download failed with status ${response.status}: ${url}`);\n }\n\n // Convert blob to base64 for IndexedDB storage\n const blob = await response.blob();\n const reader = new FileReader();\n const base64 = await new Promise<string>((resolve, reject) => {\n reader.onload = () => {\n const dataUrl = reader.result as string;\n // Extract base64 data after the comma (skip data:mime;base64,)\n const base64Data = dataUrl.split(',')[1];\n resolve(base64Data);\n };\n reader.onerror = () => reject(reader.error);\n reader.readAsDataURL(blob);\n });\n\n // Store in IndexedDB via fileStorage\n await fileStorage.set(localPath, base64);\n }\n };\n\n // Async storage adapter using localStorage\n const storage: AsyncStorageAdapter = {\n async getItem(key: string): Promise<string | null> {\n try {\n return localStorage.getItem(key);\n } catch {\n logger.warn('[Platform] localStorage.getItem failed for:', key);\n return null;\n }\n },\n async setItem(key: string, value: string): Promise<void> {\n try {\n localStorage.setItem(key, value);\n } catch (e) {\n logger.error('[Platform] localStorage.setItem failed:', e);\n throw e;\n }\n },\n async removeItem(key: string): Promise<void> {\n try {\n localStorage.removeItem(key);\n } catch {\n // Ignore removal errors\n }\n },\n async multiGet(keys: string[]): Promise<[string, string | null][]> {\n return keys.map(key => [key, localStorage.getItem(key)]);\n },\n async multiSet(entries: [string, string][]): Promise<void> {\n for (const [key, value] of entries) {\n localStorage.setItem(key, value);\n }\n }\n };\n\n // Network adapter using navigator.onLine and events\n const network: NetworkAdapter = {\n async isConnected(): Promise<boolean> {\n return navigator.onLine;\n },\n async getConnectionType(): Promise<ConnectionType> {\n if (!navigator.onLine) return 'none';\n\n // Try to use Network Information API if available\n const nav = navigator as Navigator & {\n connection?: {\n effectiveType?: string;\n type?: string;\n };\n };\n if (nav.connection) {\n const type = nav.connection.type;\n if (type === 'wifi') return 'wifi';\n if (type === 'cellular') return 'cellular';\n if (type === 'ethernet') return 'ethernet';\n }\n return 'unknown';\n },\n addConnectionListener(callback: (isConnected: boolean) => void): () => void {\n const handleOnline = () => callback(true);\n const handleOffline = () => callback(false);\n window.addEventListener('online', handleOnline);\n window.addEventListener('offline', handleOffline);\n return () => {\n window.removeEventListener('online', handleOnline);\n window.removeEventListener('offline', handleOffline);\n };\n }\n };\n\n // Image processor using Canvas API\n const imageProcessor: ImageProcessorAdapter = {\n async compress(uri: string, options: CompressionOptions): Promise<CompressedImage> {\n return new Promise((resolve, reject) => {\n const img = new Image();\n img.crossOrigin = 'anonymous';\n img.onload = () => {\n try {\n // Calculate target dimensions\n let width = img.width;\n let height = img.height;\n if (options.maxWidth && width > options.maxWidth) {\n height = height * options.maxWidth / width;\n width = options.maxWidth;\n }\n if (options.maxHeight && height > options.maxHeight) {\n width = width * options.maxHeight / height;\n height = options.maxHeight;\n }\n\n // Create canvas and draw image\n const canvas = document.createElement('canvas');\n canvas.width = Math.round(width);\n canvas.height = Math.round(height);\n const ctx = canvas.getContext('2d');\n if (!ctx) {\n reject(new Error('Failed to get canvas context'));\n return;\n }\n ctx.drawImage(img, 0, 0, canvas.width, canvas.height);\n\n // Determine mime type\n let mimeType: string;\n switch (options.format) {\n case 'png':\n mimeType = 'image/png';\n break;\n case 'webp':\n mimeType = 'image/webp';\n break;\n case 'jpeg':\n default:\n mimeType = 'image/jpeg';\n break;\n }\n\n // Convert to data URL\n const dataUrl = canvas.toDataURL(mimeType, options.quality);\n resolve({\n uri: dataUrl,\n width: canvas.width,\n height: canvas.height\n });\n } catch (e) {\n reject(e);\n }\n };\n img.onerror = () => {\n reject(new Error(`Failed to load image: ${uri}`));\n };\n img.src = uri;\n });\n }\n };\n\n // Main platform adapter\n return {\n async createDatabase(options: DatabaseOptions): Promise<AbstractPowerSyncDatabase> {\n await loadPowerSync();\n logger.info('[Platform] Creating PowerSync web database:', options.dbFilename);\n const db = new PowerSyncDatabase!({\n schema: options.schema as any,\n database: new WASQLiteOpenFactory!({\n dbFilename: options.dbFilename\n })\n });\n logger.info('[Platform] Initializing database...');\n await db.init();\n\n // Verify database is queryable before returning\n // This prevents race conditions where db.connect() is called before SQLite is truly ready\n const maxAttempts = 3;\n for (let attempt = 0; attempt < maxAttempts; attempt++) {\n try {\n await db.get('SELECT 1');\n logger.info('[Platform] Database initialized and verified');\n return db as unknown as AbstractPowerSyncDatabase;\n } catch (err) {\n if (attempt < maxAttempts - 1) {\n logger.warn(`[Platform] Database readiness check failed (attempt ${attempt + 1}/${maxAttempts}), retrying...`);\n await new Promise(r => setTimeout(r, 100 * Math.pow(2, attempt))); // 100ms, 200ms, 400ms\n } else {\n logger.error('[Platform] Database failed readiness verification after all attempts');\n throw new Error(`Database failed readiness verification: ${err instanceof Error ? err.message : err}`);\n }\n }\n }\n\n // TypeScript: unreachable but needed for return type\n throw new Error('Database readiness verification failed');\n },\n fileSystem,\n storage,\n network,\n logger,\n imageProcessor\n };\n}\n\n// Re-export types for convenience\nexport type { PlatformAdapter, LoggerAdapter } from './types';"],"mappings":"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","names":[]}
@@ -0,0 +1,49 @@
1
+ // src/error/PowerSyncErrorBoundary.tsx
2
+ import React, { Component } from "react";
3
+ import { jsx } from "react/jsx-runtime";
4
+ var PowerSyncErrorBoundary = class extends Component {
5
+ constructor(props) {
6
+ super(props);
7
+ this.state = {
8
+ hasError: false,
9
+ error: null,
10
+ retryKey: 0
11
+ };
12
+ }
13
+ static getDerivedStateFromError(error) {
14
+ return {
15
+ hasError: true,
16
+ error
17
+ };
18
+ }
19
+ componentDidCatch(error) {
20
+ this.props.onError?.(error);
21
+ }
22
+ handleRetry = () => {
23
+ this.setState((prev) => ({
24
+ hasError: false,
25
+ error: null,
26
+ retryKey: prev.retryKey + 1
27
+ }));
28
+ };
29
+ render() {
30
+ const {
31
+ hasError,
32
+ error,
33
+ retryKey
34
+ } = this.state;
35
+ const {
36
+ children,
37
+ fallback
38
+ } = this.props;
39
+ if (hasError && error) {
40
+ return fallback(error, this.handleRetry);
41
+ }
42
+ return /* @__PURE__ */ jsx(React.Fragment, { children }, retryKey);
43
+ }
44
+ };
45
+
46
+ export {
47
+ PowerSyncErrorBoundary
48
+ };
49
+ //# sourceMappingURL=chunk-P6WOZO7H.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"sources":["../src/error/PowerSyncErrorBoundary.tsx"],"sourcesContent":["import React, { Component, ReactNode } from 'react';\ninterface PowerSyncErrorBoundaryProps {\n children: ReactNode;\n /**\n * Custom fallback UI when an error occurs.\n * Must be provided since there is no platform-agnostic default.\n */\n fallback: (error: Error, retry: () => void) => ReactNode;\n /**\n * Callback when an error is caught.\n */\n onError?: (error: Error) => void;\n}\ninterface PowerSyncErrorBoundaryState {\n hasError: boolean;\n error: Error | null;\n retryKey: number;\n}\n\n/**\n * Error boundary for PowerSync initialization errors.\n *\n * Catches errors during PowerSync initialization and provides\n * a retry mechanism to attempt reinitialization.\n *\n * Note: The fallback prop is required because there is no\n * platform-agnostic default UI.\n *\n * @example\n * ```tsx\n * <PowerSyncErrorBoundary\n * fallback={(error, retry) => (\n * <View>\n * <Text>Error: {error.message}</Text>\n * <Button onPress={retry}>Retry</Button>\n * </View>\n * )}\n * onError={(error) => console.error('PowerSync error:', error)}\n * >\n * <PowerSyncProvider config={config}>\n * <App />\n * </PowerSyncProvider>\n * </PowerSyncErrorBoundary>\n * ```\n */\nexport class PowerSyncErrorBoundary extends Component<PowerSyncErrorBoundaryProps, PowerSyncErrorBoundaryState> {\n constructor(props: PowerSyncErrorBoundaryProps) {\n super(props);\n this.state = {\n hasError: false,\n error: null,\n retryKey: 0\n };\n }\n static getDerivedStateFromError(error: Error): Partial<PowerSyncErrorBoundaryState> {\n return {\n hasError: true,\n error\n };\n }\n componentDidCatch(error: Error): void {\n this.props.onError?.(error);\n }\n handleRetry = (): void => {\n this.setState(prev => ({\n hasError: false,\n error: null,\n retryKey: prev.retryKey + 1\n }));\n };\n render(): ReactNode {\n const {\n hasError,\n error,\n retryKey\n } = this.state;\n const {\n children,\n fallback\n } = this.props;\n if (hasError && error) {\n return fallback(error, this.handleRetry);\n }\n\n // Use key to force remount on retry\n return <React.Fragment key={retryKey}>{children}</React.Fragment>;\n }\n}"],"mappings":";AAAA,OAAO,SAAS,iBAA4B;AAqFjC;AAxCJ,IAAM,yBAAN,cAAqC,UAAoE;AAAA,EAC9G,YAAY,OAAoC;AAC9C,UAAM,KAAK;AACX,SAAK,QAAQ;AAAA,MACX,UAAU;AAAA,MACV,OAAO;AAAA,MACP,UAAU;AAAA,IACZ;AAAA,EACF;AAAA,EACA,OAAO,yBAAyB,OAAoD;AAClF,WAAO;AAAA,MACL,UAAU;AAAA,MACV;AAAA,IACF;AAAA,EACF;AAAA,EACA,kBAAkB,OAAoB;AACpC,SAAK,MAAM,UAAU,KAAK;AAAA,EAC5B;AAAA,EACA,cAAc,MAAY;AACxB,SAAK,SAAS,WAAS;AAAA,MACrB,UAAU;AAAA,MACV,OAAO;AAAA,MACP,UAAU,KAAK,WAAW;AAAA,IAC5B,EAAE;AAAA,EACJ;AAAA,EACA,SAAoB;AAClB,UAAM;AAAA,MACJ;AAAA,MACA;AAAA,MACA;AAAA,IACF,IAAI,KAAK;AACT,UAAM;AAAA,MACJ;AAAA,MACA;AAAA,IACF,IAAI,KAAK;AACT,QAAI,YAAY,OAAO;AACrB,aAAO,SAAS,OAAO,KAAK,WAAW;AAAA,IACzC;AAGA,WAAO,oBAAC,MAAM,UAAN,EAA+B,YAAX,QAAoB;AAAA,EAClD;AACF;","names":[]}
@@ -0,0 +1 @@
1
+ //# sourceMappingURL=chunk-TGBT5XBE.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"sources":[],"sourcesContent":[],"mappings":"","names":[]}
@@ -0,0 +1,72 @@
1
+ import {
2
+ resolveBucketFromConfig
3
+ } from "./chunk-MKD2VCX3.js";
4
+
5
+ // src/storage/upload/SupabaseUploadHandler.web.ts
6
+ var SupabaseUploadHandler = class {
7
+ supabase;
8
+ bucketConfig;
9
+ constructor(options) {
10
+ this.supabase = options.supabaseClient;
11
+ this.bucketConfig = options.bucketConfig;
12
+ }
13
+ /**
14
+ * Upload a file to Supabase Storage using fetch.
15
+ * Supports AbortSignal for cancellation.
16
+ */
17
+ async uploadFile(storagePath, localFileUri, mediaType, signal) {
18
+ if (signal?.aborted) {
19
+ throw new DOMException("Operation aborted", "AbortError");
20
+ }
21
+ const bucket = this.resolveBucket(storagePath);
22
+ const {
23
+ data,
24
+ error
25
+ } = await this.supabase.storage.from(bucket).createSignedUploadUrl(storagePath);
26
+ if (error || !data?.signedUrl) {
27
+ throw new Error(error?.message || "Failed to create signed upload URL");
28
+ }
29
+ if (signal?.aborted) {
30
+ throw new DOMException("Operation aborted", "AbortError");
31
+ }
32
+ const fileResponse = await fetch(localFileUri, {
33
+ signal
34
+ });
35
+ if (!fileResponse.ok) {
36
+ throw new Error(`Failed to read file: ${fileResponse.statusText}`);
37
+ }
38
+ const blob = await fileResponse.blob();
39
+ if (signal?.aborted) {
40
+ throw new DOMException("Operation aborted", "AbortError");
41
+ }
42
+ const uploadResponse = await fetch(data.signedUrl, {
43
+ method: "PUT",
44
+ headers: {
45
+ "Content-Type": mediaType
46
+ },
47
+ body: blob,
48
+ signal
49
+ });
50
+ if (!uploadResponse.ok) {
51
+ throw new Error(`Upload failed with status ${uploadResponse.status}`);
52
+ }
53
+ }
54
+ /**
55
+ * Resolve the storage bucket for a given path.
56
+ */
57
+ resolveBucket(storagePath) {
58
+ return resolveBucketFromConfig(this.bucketConfig, storagePath);
59
+ }
60
+ };
61
+ function createSupabaseUploadHandler(supabaseClient, bucketConfig) {
62
+ return new SupabaseUploadHandler({
63
+ supabaseClient,
64
+ bucketConfig
65
+ });
66
+ }
67
+
68
+ export {
69
+ SupabaseUploadHandler,
70
+ createSupabaseUploadHandler
71
+ };
72
+ //# sourceMappingURL=chunk-UEYRTLKE.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"sources":["../src/storage/upload/SupabaseUploadHandler.web.ts"],"sourcesContent":["/**\n * Supabase Upload Handler for Web\n *\n * Implements UploadHandler interface using the fetch API\n * for browser-based uploads.\n */\n\nimport type { StorageUploadHandler, BucketConfig } from '../types';\nimport { resolveBucketFromConfig } from '../types';\nimport type { SupabaseUploadHandlerOptions } from './types';\n\n/**\n * Web upload handler using the fetch API.\n *\n * Features:\n * - Signed URL-based uploads\n * - AbortSignal cancellation support\n * - Blob/File upload support\n *\n * @example\n * ```typescript\n * const handler = createSupabaseUploadHandler(supabaseClient, {\n * defaultBucket: 'attachments',\n * bucketMap: new Map([['avatars/', 'user-avatars']]),\n * });\n *\n * await handler.uploadFile(\n * 'photos/image.jpg',\n * blobUrl,\n * 'image/jpeg'\n * );\n * ```\n */\nexport class SupabaseUploadHandler implements StorageUploadHandler {\n private supabase: any;\n private bucketConfig: BucketConfig;\n constructor(options: SupabaseUploadHandlerOptions) {\n this.supabase = options.supabaseClient;\n this.bucketConfig = options.bucketConfig;\n }\n\n /**\n * Upload a file to Supabase Storage using fetch.\n * Supports AbortSignal for cancellation.\n */\n async uploadFile(storagePath: string, localFileUri: string, mediaType: string, signal?: AbortSignal): Promise<void> {\n // Check if already aborted\n if (signal?.aborted) {\n throw new DOMException('Operation aborted', 'AbortError');\n }\n const bucket = this.resolveBucket(storagePath);\n\n // 1. Get signed upload URL from Supabase\n const {\n data,\n error\n } = await this.supabase.storage.from(bucket).createSignedUploadUrl(storagePath);\n if (error || !data?.signedUrl) {\n throw new Error(error?.message || 'Failed to create signed upload URL');\n }\n\n // Check abort after async operation\n if (signal?.aborted) {\n throw new DOMException('Operation aborted', 'AbortError');\n }\n\n // 2. Fetch the file content\n // The localFileUri can be:\n // - A blob URL (blob:...)\n // - A data URL (data:...)\n // - An object URL from URL.createObjectURL\n const fileResponse = await fetch(localFileUri, {\n signal\n });\n if (!fileResponse.ok) {\n throw new Error(`Failed to read file: ${fileResponse.statusText}`);\n }\n const blob = await fileResponse.blob();\n\n // Check abort after blob read\n if (signal?.aborted) {\n throw new DOMException('Operation aborted', 'AbortError');\n }\n\n // 3. Upload to signed URL\n const uploadResponse = await fetch(data.signedUrl, {\n method: 'PUT',\n headers: {\n 'Content-Type': mediaType\n },\n body: blob,\n signal\n });\n if (!uploadResponse.ok) {\n throw new Error(`Upload failed with status ${uploadResponse.status}`);\n }\n }\n\n /**\n * Resolve the storage bucket for a given path.\n */\n resolveBucket(storagePath: string): string {\n return resolveBucketFromConfig(this.bucketConfig, storagePath);\n }\n}\n\n/**\n * Factory function for creating a SupabaseUploadHandler.\n */\nexport function createSupabaseUploadHandler(supabaseClient: any, bucketConfig: BucketConfig): SupabaseUploadHandler {\n return new SupabaseUploadHandler({\n supabaseClient,\n bucketConfig\n });\n}"],"mappings":";;;;;AAiCO,IAAM,wBAAN,MAA4D;AAAA,EACzD;AAAA,EACA;AAAA,EACR,YAAY,SAAuC;AACjD,SAAK,WAAW,QAAQ;AACxB,SAAK,eAAe,QAAQ;AAAA,EAC9B;AAAA;AAAA;AAAA;AAAA;AAAA,EAMA,MAAM,WAAW,aAAqB,cAAsB,WAAmB,QAAqC;AAElH,QAAI,QAAQ,SAAS;AACnB,YAAM,IAAI,aAAa,qBAAqB,YAAY;AAAA,IAC1D;AACA,UAAM,SAAS,KAAK,cAAc,WAAW;AAG7C,UAAM;AAAA,MACJ;AAAA,MACA;AAAA,IACF,IAAI,MAAM,KAAK,SAAS,QAAQ,KAAK,MAAM,EAAE,sBAAsB,WAAW;AAC9E,QAAI,SAAS,CAAC,MAAM,WAAW;AAC7B,YAAM,IAAI,MAAM,OAAO,WAAW,oCAAoC;AAAA,IACxE;AAGA,QAAI,QAAQ,SAAS;AACnB,YAAM,IAAI,aAAa,qBAAqB,YAAY;AAAA,IAC1D;AAOA,UAAM,eAAe,MAAM,MAAM,cAAc;AAAA,MAC7C;AAAA,IACF,CAAC;AACD,QAAI,CAAC,aAAa,IAAI;AACpB,YAAM,IAAI,MAAM,wBAAwB,aAAa,UAAU,EAAE;AAAA,IACnE;AACA,UAAM,OAAO,MAAM,aAAa,KAAK;AAGrC,QAAI,QAAQ,SAAS;AACnB,YAAM,IAAI,aAAa,qBAAqB,YAAY;AAAA,IAC1D;AAGA,UAAM,iBAAiB,MAAM,MAAM,KAAK,WAAW;AAAA,MACjD,QAAQ;AAAA,MACR,SAAS;AAAA,QACP,gBAAgB;AAAA,MAClB;AAAA,MACA,MAAM;AAAA,MACN;AAAA,IACF,CAAC;AACD,QAAI,CAAC,eAAe,IAAI;AACtB,YAAM,IAAI,MAAM,6BAA6B,eAAe,MAAM,EAAE;AAAA,IACtE;AAAA,EACF;AAAA;AAAA;AAAA;AAAA,EAKA,cAAc,aAA6B;AACzC,WAAO,wBAAwB,KAAK,cAAc,WAAW;AAAA,EAC/D;AACF;AAKO,SAAS,4BAA4B,gBAAqB,cAAmD;AAClH,SAAO,IAAI,sBAAsB;AAAA,IAC/B;AAAA,IACA;AAAA,EACF,CAAC;AACH;","names":[]}