@pol-studios/powersync 1.0.19 → 1.0.21
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/dist/{chunk-HWSNV45P.js → chunk-FEL22ID3.js} +1 -1
- package/dist/{chunk-VACPAAQZ.js → chunk-I2AYMY5O.js} +2 -1
- package/dist/chunk-I2AYMY5O.js.map +1 -0
- package/dist/{chunk-KN2IZERF.js → chunk-IMRSLJRV.js} +233 -11
- package/dist/chunk-IMRSLJRV.js.map +1 -0
- package/dist/{chunk-BRXQNASY.js → chunk-N4K7E53V.js} +4 -4
- package/dist/{chunk-CUCAYK7Z.js → chunk-PG2NPQG3.js} +2 -2
- package/dist/{chunk-63PXSPIN.js → chunk-XOCIONAA.js} +3 -3
- package/dist/connector/index.d.ts +180 -4
- package/dist/connector/index.js +27 -5
- package/dist/core/index.d.ts +8 -1
- package/dist/core/index.js +3 -1
- package/dist/index.d.ts +5 -5
- package/dist/index.js +31 -7
- package/dist/index.native.d.ts +4 -4
- package/dist/index.native.js +31 -7
- package/dist/index.web.d.ts +4 -4
- package/dist/index.web.js +31 -7
- package/dist/provider/index.d.ts +2 -2
- package/dist/provider/index.js +4 -4
- package/dist/react/index.d.ts +1 -1
- package/dist/react/index.js +3 -3
- package/dist/{supabase-connector-T9vHq_3i.d.ts → supabase-connector-WuiFiBnV.d.ts} +2 -1
- package/dist/sync/index.js +2 -2
- package/dist/{types-B212hgfA.d.ts → types-DiBvmGEi.d.ts} +81 -1
- package/package.json +2 -2
- package/dist/chunk-KN2IZERF.js.map +0 -1
- package/dist/chunk-VACPAAQZ.js.map +0 -1
- /package/dist/{chunk-HWSNV45P.js.map → chunk-FEL22ID3.js.map} +0 -0
- /package/dist/{chunk-BRXQNASY.js.map → chunk-N4K7E53V.js.map} +0 -0
- /package/dist/{chunk-CUCAYK7Z.js.map → chunk-PG2NPQG3.js.map} +0 -0
- /package/dist/{chunk-63PXSPIN.js.map → chunk-XOCIONAA.js.map} +0 -0
|
@@ -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-
|
|
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-
|
|
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
|
-
|
|
966
|
-
|
|
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
|
-
|
|
988
|
-
|
|
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
|
-
|
|
1032
|
-
|
|
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
|
-
|
|
1077
|
-
|
|
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-
|
|
1519
|
+
//# sourceMappingURL=chunk-IMRSLJRV.js.map
|