@pol-studios/powersync 1.0.19 → 1.0.20

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (32) hide show
  1. package/dist/{chunk-HWSNV45P.js → chunk-FEL22ID3.js} +1 -1
  2. package/dist/{chunk-VACPAAQZ.js → chunk-I2AYMY5O.js} +2 -1
  3. package/dist/chunk-I2AYMY5O.js.map +1 -0
  4. package/dist/{chunk-KN2IZERF.js → chunk-IMRSLJRV.js} +233 -11
  5. package/dist/chunk-IMRSLJRV.js.map +1 -0
  6. package/dist/{chunk-BRXQNASY.js → chunk-N4K7E53V.js} +4 -4
  7. package/dist/{chunk-CUCAYK7Z.js → chunk-PG2NPQG3.js} +2 -2
  8. package/dist/{chunk-63PXSPIN.js → chunk-XOCIONAA.js} +3 -3
  9. package/dist/connector/index.d.ts +180 -4
  10. package/dist/connector/index.js +27 -5
  11. package/dist/core/index.d.ts +8 -1
  12. package/dist/core/index.js +3 -1
  13. package/dist/index.d.ts +5 -5
  14. package/dist/index.js +31 -7
  15. package/dist/index.native.d.ts +4 -4
  16. package/dist/index.native.js +31 -7
  17. package/dist/index.web.d.ts +4 -4
  18. package/dist/index.web.js +31 -7
  19. package/dist/provider/index.d.ts +2 -2
  20. package/dist/provider/index.js +4 -4
  21. package/dist/react/index.d.ts +1 -1
  22. package/dist/react/index.js +3 -3
  23. package/dist/{supabase-connector-T9vHq_3i.d.ts → supabase-connector-WuiFiBnV.d.ts} +2 -1
  24. package/dist/sync/index.js +2 -2
  25. package/dist/{types-B212hgfA.d.ts → types-DiBvmGEi.d.ts} +81 -1
  26. package/package.json +2 -2
  27. package/dist/chunk-KN2IZERF.js.map +0 -1
  28. package/dist/chunk-VACPAAQZ.js.map +0 -1
  29. /package/dist/{chunk-HWSNV45P.js.map → chunk-FEL22ID3.js.map} +0 -0
  30. /package/dist/{chunk-BRXQNASY.js.map → chunk-N4K7E53V.js.map} +0 -0
  31. /package/dist/{chunk-CUCAYK7Z.js.map → chunk-PG2NPQG3.js.map} +0 -0
  32. /package/dist/{chunk-63PXSPIN.js.map → chunk-XOCIONAA.js.map} +0 -0
@@ -276,4 +276,4 @@ export {
276
276
  CircuitOpenError,
277
277
  CircuitBreaker
278
278
  };
279
- //# sourceMappingURL=chunk-HWSNV45P.js.map
279
+ //# sourceMappingURL=chunk-FEL22ID3.js.map
@@ -354,6 +354,7 @@ export {
354
354
  ConfigurationError,
355
355
  classifyError,
356
356
  toSyncOperationError,
357
+ extractHttpStatusCode,
357
358
  classifySupabaseError,
358
359
  createSyncError,
359
360
  generateFailureId,
@@ -361,4 +362,4 @@ export {
361
362
  extractTableNames,
362
363
  isRlsError
363
364
  };
364
- //# sourceMappingURL=chunk-VACPAAQZ.js.map
365
+ //# sourceMappingURL=chunk-I2AYMY5O.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"sources":["../src/core/errors.ts"],"sourcesContent":["/**\n * Custom Error Classes for @pol-studios/powersync\n *\n * This module provides specialized error classes for different failure scenarios.\n */\n\nimport type { SyncErrorType, ClassifiedError, SyncError, CrudEntry } from './types';\n\n/**\n * Base error class for PowerSync-related errors\n */\nexport class PowerSyncError extends Error {\n constructor(message: string) {\n super(message);\n this.name = 'PowerSyncError';\n // Maintains proper stack trace for where our error was thrown (only in V8)\n if (Error.captureStackTrace) {\n Error.captureStackTrace(this, PowerSyncError);\n }\n }\n}\n\n/**\n * Error thrown when PowerSync initialization fails\n */\nexport class InitializationError extends PowerSyncError {\n constructor(message: string, public readonly cause?: Error) {\n super(message);\n this.name = 'InitializationError';\n }\n}\n\n/**\n * Error thrown when a sync operation fails\n */\nexport class SyncOperationError extends PowerSyncError {\n constructor(message: string, public readonly errorType: SyncErrorType, public readonly cause?: Error) {\n super(message);\n this.name = 'SyncOperationError';\n }\n\n /**\n * Whether this error can be automatically retried\n */\n get isRetryable(): boolean {\n return this.errorType === 'network' || this.errorType === 'server';\n }\n\n /**\n * Get a user-friendly error message\n */\n get userFriendlyMessage(): string {\n switch (this.errorType) {\n case 'network':\n return 'Unable to connect. Check your internet connection.';\n case 'auth':\n return 'Session expired. Please sign in again.';\n case 'server':\n return 'Server is temporarily unavailable. Try again later.';\n case 'conflict':\n return 'Your changes conflict with recent updates.';\n case 'quota':\n return 'Device storage is full. Free up space to continue.';\n default:\n return 'An unexpected error occurred. Please try again.';\n }\n }\n}\n\n/**\n * Error thrown when a connector operation fails\n */\nexport class ConnectorError extends PowerSyncError {\n constructor(message: string, public readonly operation: 'fetchCredentials' | 'uploadData', public readonly cause?: Error) {\n super(message);\n this.name = 'ConnectorError';\n }\n}\n\n/**\n * Error thrown when an attachment operation fails\n */\nexport class AttachmentError extends PowerSyncError {\n constructor(message: string, public readonly attachmentId: string, public readonly operation: 'download' | 'compress' | 'delete' | 'evict', public readonly cause?: Error) {\n super(message);\n this.name = 'AttachmentError';\n }\n}\n\n/**\n * Error thrown when the platform adapter is missing required functionality\n */\nexport class PlatformAdapterError extends PowerSyncError {\n constructor(message: string, public readonly missingFeature: string) {\n super(message);\n this.name = 'PlatformAdapterError';\n }\n}\n\n/**\n * Error thrown when configuration is invalid\n */\nexport class ConfigurationError extends PowerSyncError {\n constructor(message: string, public readonly configKey?: string) {\n super(message);\n this.name = 'ConfigurationError';\n }\n}\n\n// ─── Error Classification Utilities ──────────────────────────────────────────\n\n/** Pattern definitions for error classification */\nconst ERROR_PATTERNS: Record<SyncErrorType, RegExp> = {\n // Network errors - specific patterns to avoid false positives\n network: /\\bnetwork\\s+(error|failed|failure)\\b|\\bfetch\\s+failed\\b|\\bfailed\\s+to\\s+fetch\\b|\\beconnrefused\\b|\\betimedout\\b|\\boffline\\b|\\bconnection\\s+(refused|reset|closed)\\b/i,\n // Auth errors - specific patterns for authentication/authorization failures\n auth: /\\b401\\b|\\b403\\b|http\\s*40[13]\\b|\\bunauthorized\\s+(access|request|error)?\\b|\\baccess\\s+(denied|forbidden)\\b|\\bnot\\s+authorized\\b|\\bjwt\\s+(expired|invalid)\\b|\\btoken\\s+(expired|invalid)\\b|\\bsession\\s+expired\\b/i,\n // Server errors - specific HTTP 5xx codes and server-specific messages\n server: /\\b50[0-4]\\b|http\\s*5\\d{2}\\b|\\binternal\\s+server\\s+error\\b|\\bservice\\s+unavailable\\b|\\bbad\\s+gateway\\b|\\bgateway\\s+timeout\\b/i,\n // Conflict errors - data concurrency issues\n conflict: /\\bconflict\\b|\\bconcurrent\\s+(update|modification)\\b|\\bversion\\s+mismatch\\b|\\boptimistic\\s+lock\\b/i,\n // Validation errors - data validation failures\n validation: /\\bvalidation\\s+(error|failed)\\b|\\bconstraint\\s+violation\\b|\\binvalid\\s+(data|input|value)\\b|\\brequired\\s+field\\b|\\bschema\\s+error\\b/i,\n // Quota errors - storage/resource limit issues\n quota: /\\bquota\\s+(exceeded|limit)\\b|\\bstorage\\s+(full|limit)\\b|\\benospc\\b|\\bdisk\\s+(full|space)\\b|\\bout\\s+of\\s+(memory|space)\\b/i,\n unknown: /.*/\n};\n\n/**\n * Classify an error into a SyncErrorType based on its message\n *\n * @param error - The error to classify\n * @returns The classified error type\n */\nexport function classifyError(error: Error): SyncErrorType {\n const message = error.message || '';\n\n // Check patterns in priority order (more specific first)\n if (ERROR_PATTERNS.auth.test(message)) return 'auth';\n if (ERROR_PATTERNS.server.test(message)) return 'server';\n if (ERROR_PATTERNS.network.test(message)) return 'network';\n if (ERROR_PATTERNS.conflict.test(message)) return 'conflict';\n if (ERROR_PATTERNS.validation.test(message)) return 'validation';\n if (ERROR_PATTERNS.quota.test(message)) return 'quota';\n return 'unknown';\n}\n\n/**\n * Create a SyncOperationError from a regular Error\n *\n * @param error - The original error\n * @param message - Optional custom message\n * @returns A classified SyncOperationError\n */\nexport function toSyncOperationError(error: Error, message?: string): SyncOperationError {\n const errorType = classifyError(error);\n return new SyncOperationError(message || error.message, errorType, error);\n}\n\n// ─── Supabase/PostgreSQL Error Classification ────────────────────────────────\n\n/**\n * PostgreSQL error code ranges and their meanings.\n * See: https://www.postgresql.org/docs/current/errcodes-appendix.html\n */\nconst PG_ERROR_CODES = {\n // Class 23 - Integrity Constraint Violation (permanent - data issue)\n UNIQUE_VIOLATION: '23505',\n FOREIGN_KEY_VIOLATION: '23503',\n NOT_NULL_VIOLATION: '23502',\n CHECK_VIOLATION: '23514',\n // Class 42 - Syntax Error or Access Rule Violation (permanent - schema issue)\n UNDEFINED_TABLE: '42P01',\n UNDEFINED_COLUMN: '42703',\n INSUFFICIENT_PRIVILEGE: '42501',\n // Class 08 - Connection Exception (transient)\n CONNECTION_FAILURE: '08006',\n CONNECTION_DOES_NOT_EXIST: '08003',\n // Class 53 - Insufficient Resources (transient)\n OUT_OF_MEMORY: '53200',\n DISK_FULL: '53100',\n // Class 40 - Transaction Rollback (transient)\n SERIALIZATION_FAILURE: '40001',\n DEADLOCK_DETECTED: '40P01'\n} as const;\n\n/**\n * User-friendly messages for PostgreSQL error codes\n */\nconst PG_ERROR_MESSAGES: Record<string, string> = {\n [PG_ERROR_CODES.UNIQUE_VIOLATION]: 'This record already exists or conflicts with existing data.',\n [PG_ERROR_CODES.FOREIGN_KEY_VIOLATION]: 'This record references data that no longer exists.',\n [PG_ERROR_CODES.NOT_NULL_VIOLATION]: 'A required field is missing.',\n [PG_ERROR_CODES.CHECK_VIOLATION]: 'The data does not meet validation requirements.',\n [PG_ERROR_CODES.UNDEFINED_TABLE]: 'The database table does not exist.',\n [PG_ERROR_CODES.UNDEFINED_COLUMN]: 'A database column does not exist.',\n [PG_ERROR_CODES.INSUFFICIENT_PRIVILEGE]: 'You do not have permission to perform this action.'\n};\n\n/**\n * Extract HTTP status code from an error object.\n *\n * Checks for common status code properties and falls back to pattern matching\n * in the error message for 4xx/5xx codes.\n */\nexport function extractHttpStatusCode(error: unknown): number | undefined {\n if (!error || typeof error !== 'object') return undefined;\n const err = error as Record<string, unknown>;\n\n // Direct properties\n if (typeof err.status === 'number') return err.status;\n if (typeof err.statusCode === 'number') return err.statusCode;\n\n // Check nested error object (Supabase format)\n if (err.error && typeof err.error === 'object') {\n const nested = err.error as Record<string, unknown>;\n if (typeof nested.status === 'number') return nested.status;\n if (typeof nested.statusCode === 'number') return nested.statusCode;\n }\n\n // Pattern match in message\n const message = String(err.message || '');\n const match = message.match(/\\b(4\\d{2}|5\\d{2})\\b/);\n if (match) return parseInt(match[1], 10);\n return undefined;\n}\n\n/**\n * Extract PostgreSQL error code from a Supabase error.\n *\n * Supabase errors from PostgREST typically include the PostgreSQL error code\n * in the error object or message.\n */\nfunction extractPgCode(error: unknown): string | undefined {\n if (!error || typeof error !== 'object') return undefined;\n const err = error as Record<string, unknown>;\n\n // Supabase error format: { code: \"PGRST...\", details: \"...\", hint: \"...\", message: \"...\" }\n // PostgreSQL error format: { code: \"23505\", ... }\n if (typeof err.code === 'string') {\n // Check if it's a direct PostgreSQL code (5 chars, starts with digit)\n if (/^\\d{5}$/.test(err.code)) {\n return err.code;\n }\n // Extract from PGRST format or message\n const match = err.code.match(/\\d{5}/) || String(err.message || '').match(/\\d{5}/);\n if (match) return match[0];\n }\n\n // Check in details or hint\n if (typeof err.details === 'string') {\n const match = err.details.match(/\\b(\\d{5})\\b/);\n if (match) return match[1];\n }\n return undefined;\n}\n\n/**\n * User-friendly messages for PostgREST error codes (PGRST*).\n * See: https://postgrest.org/en/stable/references/errors.html\n */\nfunction getPostgRESTMessage(code: string, fallback: string): string {\n const messages: Record<string, string> = {\n 'PGRST116': 'Record not found or query returned no results.',\n 'PGRST204': 'Column not found in the database.',\n 'PGRST301': 'Row-level security prevented this operation.',\n 'PGRST302': 'The requested operation is not allowed.'\n };\n return messages[code] || `Database error: ${fallback}`;\n}\n\n/**\n * Classify a Supabase/PostgreSQL error into a structured result.\n *\n * This function analyzes errors from Supabase operations and determines:\n * - The error type category\n * - Whether the error is permanent (won't be fixed by retry)\n * - Whether it's a conflict with existing data\n * - A user-friendly message\n *\n * @param error - The error from a Supabase operation\n * @returns Classified error information\n *\n * @example\n * ```typescript\n * try {\n * await supabase.from('users').insert({ email: 'duplicate@test.com' });\n * } catch (error) {\n * const classified = classifySupabaseError(error);\n * if (classified.isPermanent) {\n * // Show error to user - retry won't help\n * }\n * }\n * ```\n */\nexport function classifySupabaseError(error: unknown): ClassifiedError {\n const pgCode = extractPgCode(error);\n const httpStatusCode = extractHttpStatusCode(error);\n const errorMessage = error instanceof Error ? error.message : String(error);\n\n // Default result\n let result: ClassifiedError = {\n type: 'unknown',\n isPermanent: false,\n isConflict: false,\n pgCode,\n userMessage: 'An unexpected error occurred. Please try again.'\n };\n\n // Check for PostgREST error codes (PGRST*)\n if (error && typeof error === 'object') {\n const err = error as Record<string, unknown>;\n const code = String(err.code || '');\n if (code.startsWith('PGRST')) {\n result.type = 'validation';\n result.isPermanent = true;\n result.userMessage = getPostgRESTMessage(code, errorMessage);\n return result;\n }\n }\n\n // First, check HTTP status code for quick classification\n if (httpStatusCode) {\n // 4xx client errors (except 401/403 which are auth errors handled separately)\n if (httpStatusCode >= 400 && httpStatusCode < 500 && httpStatusCode !== 401 && httpStatusCode !== 403) {\n result.type = 'validation';\n result.isPermanent = true;\n result.userMessage = 'The request was rejected. Please check your data.';\n return result;\n }\n\n // 5xx server errors (transient)\n if (httpStatusCode >= 500) {\n result.type = 'server';\n result.isPermanent = false;\n result.userMessage = 'Server temporarily unavailable. Will retry.';\n return result;\n }\n }\n\n // Second, check PostgreSQL error code (most reliable)\n if (pgCode) {\n // Class 23 - Integrity Constraint Violations (permanent)\n if (pgCode.startsWith('23')) {\n result.type = pgCode === PG_ERROR_CODES.UNIQUE_VIOLATION ? 'conflict' : 'validation';\n result.isPermanent = true;\n result.isConflict = pgCode === PG_ERROR_CODES.UNIQUE_VIOLATION;\n result.userMessage = PG_ERROR_MESSAGES[pgCode] || 'Data validation failed.';\n return result;\n }\n\n // Class 42 - Syntax/Access Errors (permanent - schema issue)\n if (pgCode.startsWith('42')) {\n result.type = pgCode === PG_ERROR_CODES.INSUFFICIENT_PRIVILEGE ? 'auth' : 'validation';\n result.isPermanent = true;\n result.userMessage = PG_ERROR_MESSAGES[pgCode] || 'Database schema error.';\n return result;\n }\n\n // Class 08 - Connection Exceptions (transient)\n if (pgCode.startsWith('08')) {\n result.type = 'network';\n result.isPermanent = false;\n result.userMessage = 'Connection lost. Will retry automatically.';\n return result;\n }\n\n // Class 53 - Insufficient Resources (transient, but may need action)\n if (pgCode.startsWith('53')) {\n result.type = 'quota';\n result.isPermanent = pgCode === PG_ERROR_CODES.DISK_FULL;\n result.userMessage = 'Server resources exhausted. Please try again later.';\n return result;\n }\n\n // Class 40 - Transaction Rollback (transient)\n if (pgCode.startsWith('40')) {\n result.type = 'conflict';\n result.isPermanent = false;\n result.isConflict = true;\n result.userMessage = 'Concurrent update detected. Retrying...';\n return result;\n }\n }\n\n // Fall back to pattern matching on error message\n const lowerMessage = errorMessage.toLowerCase();\n\n // Network errors (transient)\n // Use specific patterns to avoid false positives\n const isNetworkError = /\\bnetwork\\s+(error|request|failed|failure|unavailable)\\b/.test(lowerMessage) || /\\bfetch\\s+(failed|error)\\b/.test(lowerMessage) || /\\bfailed\\s+to\\s+fetch\\b/.test(lowerMessage) || /\\beconnrefused\\b/.test(lowerMessage) || /\\betimedout\\b/.test(lowerMessage) || /\\bconnection\\s+(timed?\\s*out|refused|reset|closed)\\b/.test(lowerMessage) || /\\boffline\\b/.test(lowerMessage) || /\\bdns\\s+(error|failed|lookup)\\b/.test(lowerMessage) || /\\bsocket\\s+(error|closed|hang\\s*up)\\b/.test(lowerMessage) || /\\bno\\s+internet\\b/.test(lowerMessage) || /\\brequest\\s+timeout\\b/.test(lowerMessage);\n if (isNetworkError) {\n result.type = 'network';\n result.isPermanent = false;\n result.userMessage = 'Network error. Will retry when connected.';\n return result;\n }\n\n // Auth errors (may be permanent if token is invalid)\n // Use specific patterns to avoid false positives (e.g., 'cache expired' matching 'expired')\n const isAuthError = /\\b401\\b/.test(lowerMessage) || /\\b403\\b/.test(lowerMessage) || /http 401|http 403/.test(lowerMessage) || /\\bauth(entication|orization)?\\s+(error|failed|required)\\b/.test(lowerMessage) || /\\bunauthorized\\s+(access|request|error)?\\b/.test(lowerMessage) || /\\baccess\\s+(denied|forbidden)\\b/.test(lowerMessage) || /\\bnot\\s+authorized\\b/.test(lowerMessage) || /\\bjwt\\b/.test(lowerMessage) || /\\bbearer\\s+token\\b/.test(lowerMessage) || /\\baccess\\s+token\\b/.test(lowerMessage);\n if (isAuthError) {\n result.type = 'auth';\n // Check for specific token expiration patterns, avoiding false positives\n const isTokenExpired = lowerMessage.includes('jwt expired') || lowerMessage.includes('token expired') || lowerMessage.includes('session expired') || lowerMessage.includes('jwt has expired') || lowerMessage.includes('refresh token expired') || lowerMessage.includes('access token expired') || lowerMessage.includes('token is expired') || lowerMessage.includes('token has expired');\n const isInvalidToken = lowerMessage.includes('invalid token') || lowerMessage.includes('invalid jwt') || lowerMessage.includes('malformed token') || lowerMessage.includes('token invalid') || lowerMessage.includes('jwt invalid') || lowerMessage.includes('invalid signature');\n result.isPermanent = isTokenExpired || isInvalidToken;\n result.userMessage = 'Authentication error. Please sign in again.';\n return result;\n }\n\n // Client errors (permanent - request is malformed or rejected)\n if (/400|406|bad request|not acceptable|422|unprocessable/i.test(lowerMessage)) {\n result.type = 'validation';\n result.isPermanent = true;\n result.userMessage = 'The request was rejected. Please check your data.';\n return result;\n }\n\n // Server errors (transient)\n // Use specific patterns with word boundaries to avoid false positives\n const isServerError = /\\b500\\b/.test(lowerMessage) || /\\b502\\b/.test(lowerMessage) || /\\b503\\b/.test(lowerMessage) || /\\b504\\b/.test(lowerMessage) || /http\\s*5\\d{2}\\b/.test(lowerMessage) || /\\binternal\\s+server\\s+error\\b/.test(lowerMessage) || /\\bservice\\s+unavailable\\b/.test(lowerMessage) || /\\bserver\\s+(error|unavailable|down|overloaded)\\b/.test(lowerMessage) || /\\bbad\\s+gateway\\b/.test(lowerMessage) || /\\bgateway\\s+timeout\\b/.test(lowerMessage);\n if (isServerError) {\n result.type = 'server';\n result.isPermanent = false;\n result.userMessage = 'Server temporarily unavailable. Will retry.';\n return result;\n }\n\n // Constraint/validation errors in message (permanent)\n if (/duplicate|unique|constraint|violates|invalid|required/.test(lowerMessage)) {\n result.type = 'validation';\n result.isPermanent = true;\n result.isConflict = lowerMessage.includes('duplicate') || lowerMessage.includes('unique');\n result.userMessage = 'Data validation failed. Please check your input.';\n return result;\n }\n return result;\n}\n\n/**\n * Create a SyncError from a classified error result.\n */\nexport function createSyncError(classified: ClassifiedError, originalMessage: string): SyncError {\n return {\n type: classified.type,\n message: originalMessage,\n userMessage: classified.userMessage,\n timestamp: new Date(),\n pgCode: classified.pgCode,\n isPermanent: classified.isPermanent\n };\n}\n\n/**\n * Generate a unique ID for a failed transaction.\n * Uses a combination of timestamp and entry IDs to ensure uniqueness.\n */\nexport function generateFailureId(entries: CrudEntry[]): string {\n const timestamp = Date.now();\n const entryIds = entries.map(e => e.id).join('-');\n return `failure-${timestamp}-${entryIds.substring(0, 32)}`;\n}\n\n/**\n * Extract entity IDs from CRUD entries.\n * Includes both the PowerSync entry ID and the record's 'id' from opData if different.\n * This ensures we can match failures to entities regardless of which ID format is used.\n */\nexport function extractEntityIds(entries: CrudEntry[]): string[] {\n const ids: string[] = [];\n for (const entry of entries) {\n // Always include the PowerSync entry ID\n ids.push(entry.id);\n // Also include the record's 'id' field from opData if it exists and is different\n if (entry.opData?.id !== undefined && String(entry.opData.id) !== entry.id) {\n ids.push(String(entry.opData.id));\n }\n }\n return [...new Set(ids)];\n}\n\n/**\n * Extract unique table names from CRUD entries.\n */\nexport function extractTableNames(entries: CrudEntry[]): string[] {\n return [...new Set(entries.map(entry => entry.table))];\n}\n\n/**\n * Check if an error is an RLS/permission error (PostgreSQL 42501 - insufficient_privilege).\n *\n * RLS errors are special because they may resolve when parent data syncs.\n * For example, inserting a child record may fail until the parent record syncs\n * and satisfies the RLS policy conditions.\n *\n * @param error - The error to check\n * @returns true if the error is an RLS/permission error\n */\nexport function isRlsError(error: unknown): boolean {\n if (!error) return false;\n\n // Check for PostgreSQL error code 42501 (insufficient_privilege)\n if (typeof error === 'object' && error !== null) {\n const err = error as Record<string, unknown>;\n const code = String(err.code || '');\n\n // Direct PostgreSQL code match\n if (code === '42501' || code === PG_ERROR_CODES.INSUFFICIENT_PRIVILEGE) {\n return true;\n }\n\n // Check in nested error object\n if (err.error && typeof err.error === 'object') {\n const nested = err.error as Record<string, unknown>;\n if (String(nested.code || '') === '42501') {\n return true;\n }\n }\n }\n\n // Also check error message for RLS-related patterns\n const message = error instanceof Error ? error.message.toLowerCase() : String(error).toLowerCase();\n const rlsPatterns = [/\\b42501\\b/,\n // PostgreSQL error code\n /\\brls\\b.*\\b(policy|violation|error)\\b/, /\\brow[-_\\s]?level[-_\\s]?security\\b/,\n // row-level, row_level, row level\n /\\bpolicy\\s+.*\\bviolat(ed|ion)\\b/, /\\bpermission\\s+denied\\b.*\\b(policy|rls)\\b/, /\\binsufficient[-_\\s]?privilege\\b/, /violates\\s+row[-_\\s]?lev/ // \"violates row-lev...\" (truncated messages)\n ];\n return rlsPatterns.some(pattern => pattern.test(message));\n}"],"mappings":"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","names":[]}
@@ -3,8 +3,9 @@ import {
3
3
  } from "./chunk-FV2HXEIY.js";
4
4
  import {
5
5
  classifySupabaseError,
6
+ extractHttpStatusCode,
6
7
  isRlsError
7
- } from "./chunk-VACPAAQZ.js";
8
+ } from "./chunk-I2AYMY5O.js";
8
9
 
9
10
  // src/connector/types.ts
10
11
  var defaultSchemaRouter = () => "public";
@@ -40,6 +41,121 @@ var DEFAULT_RETRY_CONFIG = {
40
41
  }
41
42
  };
42
43
 
44
+ // src/connector/middleware/upload-error.ts
45
+ function idempotentTables(tables) {
46
+ const tableSet = new Set(tables);
47
+ return ({
48
+ entry,
49
+ pgCode
50
+ }) => {
51
+ if (tableSet.has(entry.table) && pgCode === "23505") {
52
+ return "success";
53
+ }
54
+ return "continue";
55
+ };
56
+ }
57
+ function discardOrphaned() {
58
+ return ({
59
+ pgCode
60
+ }) => {
61
+ if (pgCode === "23503") {
62
+ return "discard";
63
+ }
64
+ return "continue";
65
+ };
66
+ }
67
+ function discardOnPgCodes(codes) {
68
+ const codeSet = new Set(codes);
69
+ return ({
70
+ pgCode
71
+ }) => {
72
+ if (pgCode && codeSet.has(pgCode)) {
73
+ return "discard";
74
+ }
75
+ return "continue";
76
+ };
77
+ }
78
+ function successOnPgCodes(config) {
79
+ return ({
80
+ entry,
81
+ pgCode
82
+ }) => {
83
+ const codes = config[entry.table];
84
+ if (codes && pgCode && codes.includes(pgCode)) {
85
+ return "success";
86
+ }
87
+ return "continue";
88
+ };
89
+ }
90
+ function retryOnHttpStatus(statusCodes) {
91
+ const statusSet = new Set(statusCodes);
92
+ return ({
93
+ httpStatusCode
94
+ }) => {
95
+ if (httpStatusCode && statusSet.has(httpStatusCode)) {
96
+ return "retry";
97
+ }
98
+ return "continue";
99
+ };
100
+ }
101
+ function discardOnHttpStatus(statusCodes) {
102
+ const statusSet = new Set(statusCodes);
103
+ return ({
104
+ httpStatusCode
105
+ }) => {
106
+ if (httpStatusCode && statusSet.has(httpStatusCode)) {
107
+ return "discard";
108
+ }
109
+ return "continue";
110
+ };
111
+ }
112
+ function tableHandlers(handlers) {
113
+ return (context) => {
114
+ const handler = handlers[context.entry.table];
115
+ if (handler) {
116
+ return handler(context);
117
+ }
118
+ return "continue";
119
+ };
120
+ }
121
+ async function runUploadErrorMiddleware(context, middleware, defaultClassification = "retry") {
122
+ for (const mw of middleware) {
123
+ const result = await mw(context);
124
+ if (result !== "continue") {
125
+ return result;
126
+ }
127
+ }
128
+ return defaultClassification;
129
+ }
130
+ function runUploadErrorMiddlewareSync(context, middleware, defaultClassification = "retry") {
131
+ for (const mw of middleware) {
132
+ const result = mw(context);
133
+ if (result && typeof result === "object" && "then" in result) {
134
+ throw new Error("Async middleware detected in runUploadErrorMiddlewareSync. Use runUploadErrorMiddleware instead.");
135
+ }
136
+ const syncResult = result;
137
+ if (syncResult !== "continue") {
138
+ return syncResult;
139
+ }
140
+ }
141
+ return defaultClassification;
142
+ }
143
+
144
+ // src/connector/errors.ts
145
+ var DiscardEntryError = class extends Error {
146
+ constructor(message) {
147
+ super(message);
148
+ this.name = "DiscardEntryError";
149
+ }
150
+ };
151
+ var TransactionAbortError = class extends Error {
152
+ constructor(message, cause) {
153
+ super(message);
154
+ this.cause = cause;
155
+ this.name = "TransactionAbortError";
156
+ }
157
+ };
158
+
43
159
  // src/conflicts/detect.ts
44
160
  var ConflictDetectionError = class extends Error {
45
161
  constructor(message, options) {
@@ -275,6 +391,8 @@ var SupabaseConnector = class _SupabaseConnector {
275
391
  entryCooldowns = /* @__PURE__ */ new Map();
276
392
  static COOLDOWN_DURATION_MS = 6e4;
277
393
  // 1 minute cooldown after exhausting retries
394
+ // Upload error middleware chain
395
+ uploadErrorMiddleware;
278
396
  constructor(options) {
279
397
  this.supabase = options.supabaseClient;
280
398
  this.powerSyncUrl = options.powerSyncUrl;
@@ -302,6 +420,7 @@ var SupabaseConnector = class _SupabaseConnector {
302
420
  ...options.retryConfig?.rls
303
421
  }
304
422
  };
423
+ this.uploadErrorMiddleware = options.uploadErrorMiddleware ?? [];
305
424
  if (this.conflictBus) {
306
425
  this.unsubscribeResolution = this.conflictBus.onResolution((table, recordId, resolution) => {
307
426
  const key = `${table}:${recordId}`;
@@ -444,6 +563,64 @@ var SupabaseConnector = class _SupabaseConnector {
444
563
  error: lastError.message,
445
564
  isPermanent: classified.isPermanent
446
565
  });
566
+ if (this.uploadErrorMiddleware.length > 0) {
567
+ const classifiedError2 = classifySupabaseError(error);
568
+ const schema = this.schemaRouter(entry.table);
569
+ const middlewareContext = {
570
+ entry,
571
+ error: lastError,
572
+ pgCode: classified.pgCode,
573
+ httpStatusCode: extractHttpStatusCode(error),
574
+ classified: classifiedError2,
575
+ originalError: error,
576
+ schema,
577
+ supabase: this.supabase
578
+ };
579
+ const middlewareResult = await runUploadErrorMiddleware(
580
+ middlewareContext,
581
+ this.uploadErrorMiddleware,
582
+ "continue"
583
+ // Default to continue if no middleware matches
584
+ );
585
+ if (__DEV__) {
586
+ console.log("[Connector] Upload error middleware result:", {
587
+ table: entry.table,
588
+ id: entry.id,
589
+ result: middlewareResult
590
+ });
591
+ }
592
+ switch (middlewareResult) {
593
+ case "success":
594
+ this.entryCooldowns.delete(entryKey);
595
+ this.logger?.info("[Connector] Entry treated as success by middleware:", {
596
+ table: entry.table,
597
+ id: entry.id,
598
+ pgCode: classified.pgCode
599
+ });
600
+ return;
601
+ case "discard":
602
+ this.entryCooldowns.delete(entryKey);
603
+ this.logger?.warn("[Connector] Entry discarded by middleware:", {
604
+ table: entry.table,
605
+ id: entry.id,
606
+ pgCode: classified.pgCode,
607
+ error: lastError.message
608
+ });
609
+ throw new DiscardEntryError(`Entry discarded by middleware: ${entry.table}:${entry.id} (${classified.pgCode || "unknown"})`);
610
+ case "retry":
611
+ break;
612
+ case "continue":
613
+ break;
614
+ case "fail_transaction":
615
+ this.logger?.error("[Connector] Transaction aborted by middleware:", {
616
+ table: entry.table,
617
+ id: entry.id,
618
+ pgCode: classified.pgCode,
619
+ error: lastError.message
620
+ });
621
+ throw new TransactionAbortError(`Transaction aborted by middleware: ${entry.table}:${entry.id}`, lastError);
622
+ }
623
+ }
447
624
  }
448
625
  const isRlsPermissionError = lastError && isRlsError(lastError);
449
626
  const selectedConfig = isRlsPermissionError ? this.retryConfig.rls : classified.isPermanent ? this.retryConfig.permanent : this.retryConfig.transient;
@@ -942,6 +1119,7 @@ var SupabaseConnector = class _SupabaseConnector {
942
1119
  */
943
1120
  async processTransaction(transaction, _database) {
944
1121
  const successfulEntries = [];
1122
+ const discardedEntries = [];
945
1123
  const entriesByTable = groupEntriesByTable(transaction.crud);
946
1124
  if (__DEV__) {
947
1125
  console.log("[Connector] Processing transaction with batching:", {
@@ -962,8 +1140,16 @@ var SupabaseConnector = class _SupabaseConnector {
962
1140
  const deleteEntries = entries.filter((e) => e.op === "DELETE" /* DELETE */);
963
1141
  if (this.crudHandler) {
964
1142
  for (const entry of entries) {
965
- await this.processWithRetry(entry);
966
- successfulEntries.push(entry);
1143
+ try {
1144
+ await this.processWithRetry(entry);
1145
+ successfulEntries.push(entry);
1146
+ } catch (error) {
1147
+ if (error instanceof DiscardEntryError) {
1148
+ discardedEntries.push(entry);
1149
+ continue;
1150
+ }
1151
+ throw error;
1152
+ }
967
1153
  }
968
1154
  continue;
969
1155
  }
@@ -984,13 +1170,22 @@ var SupabaseConnector = class _SupabaseConnector {
984
1170
  opData: entry.opData
985
1171
  });
986
1172
  }
987
- await this.processWithRetry(entry);
988
- successfulEntries.push(entry);
1173
+ try {
1174
+ await this.processWithRetry(entry);
1175
+ successfulEntries.push(entry);
1176
+ } catch (error) {
1177
+ if (error instanceof DiscardEntryError) {
1178
+ discardedEntries.push(entry);
1179
+ continue;
1180
+ }
1181
+ throw error;
1182
+ }
989
1183
  }
990
1184
  }
991
1185
  await this.finalizeTransaction({
992
1186
  transaction,
993
- successfulEntries
1187
+ successfulEntries,
1188
+ discardedEntries: discardedEntries.length > 0 ? discardedEntries : void 0
994
1189
  });
995
1190
  }
996
1191
  /**
@@ -1028,8 +1223,16 @@ var SupabaseConnector = class _SupabaseConnector {
1028
1223
  });
1029
1224
  }
1030
1225
  for (const entry of entries) {
1031
- await this.processWithRetry(entry);
1032
- successful.push(entry);
1226
+ try {
1227
+ await this.processWithRetry(entry);
1228
+ successful.push(entry);
1229
+ } catch (e) {
1230
+ if (e instanceof DiscardEntryError) {
1231
+ successful.push(entry);
1232
+ continue;
1233
+ }
1234
+ throw e;
1235
+ }
1033
1236
  }
1034
1237
  } else {
1035
1238
  if (__DEV__) {
@@ -1073,8 +1276,16 @@ var SupabaseConnector = class _SupabaseConnector {
1073
1276
  });
1074
1277
  }
1075
1278
  for (const entry of entries) {
1076
- await this.processWithRetry(entry);
1077
- successful.push(entry);
1279
+ try {
1280
+ await this.processWithRetry(entry);
1281
+ successful.push(entry);
1282
+ } catch (e) {
1283
+ if (e instanceof DiscardEntryError) {
1284
+ successful.push(entry);
1285
+ continue;
1286
+ }
1287
+ throw e;
1288
+ }
1078
1289
  }
1079
1290
  } else {
1080
1291
  if (__DEV__) {
@@ -1287,6 +1498,17 @@ var SupabaseConnector = class _SupabaseConnector {
1287
1498
  export {
1288
1499
  defaultSchemaRouter,
1289
1500
  DEFAULT_RETRY_CONFIG,
1501
+ idempotentTables,
1502
+ discardOrphaned,
1503
+ discardOnPgCodes,
1504
+ successOnPgCodes,
1505
+ retryOnHttpStatus,
1506
+ discardOnHttpStatus,
1507
+ tableHandlers,
1508
+ runUploadErrorMiddleware,
1509
+ runUploadErrorMiddlewareSync,
1510
+ DiscardEntryError,
1511
+ TransactionAbortError,
1290
1512
  ConflictDetectionError,
1291
1513
  detectConflicts,
1292
1514
  hasVersionColumn,
@@ -1294,4 +1516,4 @@ export {
1294
1516
  getLocalVersion,
1295
1517
  SupabaseConnector
1296
1518
  };
1297
- //# sourceMappingURL=chunk-KN2IZERF.js.map
1519
+ //# sourceMappingURL=chunk-IMRSLJRV.js.map