@pol-studios/powersync 1.0.6 → 1.0.10

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 (128) hide show
  1. package/README.md +933 -0
  2. package/dist/CacheSettingsManager-uz-kbnRH.d.ts +461 -0
  3. package/dist/attachments/index.d.ts +745 -332
  4. package/dist/attachments/index.js +152 -6
  5. package/dist/{types-Cd7RhNqf.d.ts → background-sync-ChCXW-EV.d.ts} +53 -2
  6. package/dist/chunk-24RDMMCL.js +44 -0
  7. package/dist/chunk-24RDMMCL.js.map +1 -0
  8. package/dist/chunk-4TXTAEF2.js +2060 -0
  9. package/dist/chunk-4TXTAEF2.js.map +1 -0
  10. package/dist/chunk-63PXSPIN.js +358 -0
  11. package/dist/chunk-63PXSPIN.js.map +1 -0
  12. package/dist/chunk-654ERHA7.js +1 -0
  13. package/dist/chunk-A4IBBWGO.js +377 -0
  14. package/dist/chunk-A4IBBWGO.js.map +1 -0
  15. package/dist/chunk-BRXQNASY.js +1720 -0
  16. package/dist/chunk-BRXQNASY.js.map +1 -0
  17. package/dist/chunk-CAB26E6F.js +142 -0
  18. package/dist/chunk-CAB26E6F.js.map +1 -0
  19. package/dist/{chunk-EJ23MXPQ.js → chunk-CGL33PL4.js} +3 -1
  20. package/dist/chunk-CGL33PL4.js.map +1 -0
  21. package/dist/{chunk-R4YFWQ3Q.js → chunk-CUCAYK7Z.js} +309 -92
  22. package/dist/chunk-CUCAYK7Z.js.map +1 -0
  23. package/dist/chunk-FV2HXEIY.js +124 -0
  24. package/dist/chunk-FV2HXEIY.js.map +1 -0
  25. package/dist/chunk-HWSNV45P.js +279 -0
  26. package/dist/chunk-HWSNV45P.js.map +1 -0
  27. package/dist/{chunk-62J2DPKX.js → chunk-KN2IZERF.js} +530 -413
  28. package/dist/chunk-KN2IZERF.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-P4HZA6ZT.js +83 -0
  32. package/dist/chunk-P4HZA6ZT.js.map +1 -0
  33. package/dist/chunk-P6WOZO7H.js +49 -0
  34. package/dist/chunk-P6WOZO7H.js.map +1 -0
  35. package/dist/chunk-T4AO7JIG.js +1 -0
  36. package/dist/chunk-TGBT5XBE.js +1 -0
  37. package/dist/{chunk-FPTDATY5.js → chunk-VACPAAQZ.js} +54 -12
  38. package/dist/chunk-VACPAAQZ.js.map +1 -0
  39. package/dist/chunk-WGHNIAF7.js +329 -0
  40. package/dist/chunk-WGHNIAF7.js.map +1 -0
  41. package/dist/{chunk-3AYXHQ4W.js → chunk-WN5ZJ3E2.js} +108 -47
  42. package/dist/chunk-WN5ZJ3E2.js.map +1 -0
  43. package/dist/chunk-XAEII4ZX.js +456 -0
  44. package/dist/chunk-XAEII4ZX.js.map +1 -0
  45. package/dist/chunk-XOY2CJ67.js +289 -0
  46. package/dist/chunk-XOY2CJ67.js.map +1 -0
  47. package/dist/chunk-YHTZ7VMV.js +1 -0
  48. package/dist/chunk-YSTEESEG.js +676 -0
  49. package/dist/chunk-YSTEESEG.js.map +1 -0
  50. package/dist/chunk-Z6VOBGTU.js +32 -0
  51. package/dist/chunk-Z6VOBGTU.js.map +1 -0
  52. package/dist/chunk-ZM4ENYMF.js +230 -0
  53. package/dist/chunk-ZM4ENYMF.js.map +1 -0
  54. package/dist/connector/index.d.ts +236 -4
  55. package/dist/connector/index.js +15 -4
  56. package/dist/core/index.d.ts +16 -3
  57. package/dist/core/index.js +6 -2
  58. package/dist/error/index.d.ts +54 -0
  59. package/dist/error/index.js +7 -0
  60. package/dist/error/index.js.map +1 -0
  61. package/dist/index.d.ts +102 -12
  62. package/dist/index.js +309 -37
  63. package/dist/index.native.d.ts +22 -10
  64. package/dist/index.native.js +309 -38
  65. package/dist/index.web.d.ts +22 -10
  66. package/dist/index.web.js +310 -38
  67. package/dist/maintenance/index.d.ts +118 -0
  68. package/dist/maintenance/index.js +16 -0
  69. package/dist/maintenance/index.js.map +1 -0
  70. package/dist/platform/index.d.ts +16 -1
  71. package/dist/platform/index.js.map +1 -1
  72. package/dist/platform/index.native.d.ts +2 -2
  73. package/dist/platform/index.native.js +1 -1
  74. package/dist/platform/index.web.d.ts +1 -1
  75. package/dist/platform/index.web.js +1 -1
  76. package/dist/pol-attachment-queue-BVAIueoP.d.ts +817 -0
  77. package/dist/provider/index.d.ts +451 -21
  78. package/dist/provider/index.js +32 -13
  79. package/dist/react/index.d.ts +372 -0
  80. package/dist/react/index.js +25 -0
  81. package/dist/react/index.js.map +1 -0
  82. package/dist/storage/index.d.ts +6 -0
  83. package/dist/storage/index.js +42 -0
  84. package/dist/storage/index.js.map +1 -0
  85. package/dist/storage/index.native.d.ts +6 -0
  86. package/dist/storage/index.native.js +40 -0
  87. package/dist/storage/index.native.js.map +1 -0
  88. package/dist/storage/index.web.d.ts +6 -0
  89. package/dist/storage/index.web.js +40 -0
  90. package/dist/storage/index.web.js.map +1 -0
  91. package/dist/storage/upload/index.d.ts +54 -0
  92. package/dist/storage/upload/index.js +15 -0
  93. package/dist/storage/upload/index.js.map +1 -0
  94. package/dist/storage/upload/index.native.d.ts +56 -0
  95. package/dist/storage/upload/index.native.js +15 -0
  96. package/dist/storage/upload/index.native.js.map +1 -0
  97. package/dist/storage/upload/index.web.d.ts +2 -0
  98. package/dist/storage/upload/index.web.js +14 -0
  99. package/dist/storage/upload/index.web.js.map +1 -0
  100. package/dist/supabase-connector-T9vHq_3i.d.ts +202 -0
  101. package/dist/sync/index.d.ts +288 -23
  102. package/dist/sync/index.js +22 -10
  103. package/dist/{index-l3iL9Jte.d.ts → types-B212hgfA.d.ts} +101 -158
  104. package/dist/{types-afHtE1U_.d.ts → types-CDqWh56B.d.ts} +2 -0
  105. package/dist/types-CyvBaAl8.d.ts +60 -0
  106. package/dist/types-D0WcHrq6.d.ts +234 -0
  107. package/package.json +89 -5
  108. package/dist/chunk-32OLICZO.js +0 -1
  109. package/dist/chunk-3AYXHQ4W.js.map +0 -1
  110. package/dist/chunk-5FIMA26D.js +0 -1
  111. package/dist/chunk-62J2DPKX.js.map +0 -1
  112. package/dist/chunk-7EMDVIZX.js.map +0 -1
  113. package/dist/chunk-EJ23MXPQ.js.map +0 -1
  114. package/dist/chunk-FPTDATY5.js.map +0 -1
  115. package/dist/chunk-KCDG2MNP.js +0 -1431
  116. package/dist/chunk-KCDG2MNP.js.map +0 -1
  117. package/dist/chunk-OLHGI472.js +0 -1
  118. package/dist/chunk-PAFBKNL3.js +0 -99
  119. package/dist/chunk-PAFBKNL3.js.map +0 -1
  120. package/dist/chunk-R4YFWQ3Q.js.map +0 -1
  121. package/dist/chunk-V6LJ6MR2.js +0 -740
  122. package/dist/chunk-V6LJ6MR2.js.map +0 -1
  123. package/dist/chunk-VJCL2SWD.js +0 -1
  124. package/dist/failed-upload-store-C0cLxxPz.d.ts +0 -33
  125. /package/dist/{chunk-32OLICZO.js.map → chunk-654ERHA7.js.map} +0 -0
  126. /package/dist/{chunk-5FIMA26D.js.map → chunk-T4AO7JIG.js.map} +0 -0
  127. /package/dist/{chunk-OLHGI472.js.map → chunk-TGBT5XBE.js.map} +0 -0
  128. /package/dist/{chunk-VJCL2SWD.js.map → chunk-YHTZ7VMV.js.map} +0 -0
@@ -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 */\nfunction 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":[]}
@@ -0,0 +1,329 @@
1
+ import {
2
+ usePowerSync
3
+ } from "./chunk-YSTEESEG.js";
4
+
5
+ // src/utils/format.ts
6
+ function formatBytes(bytes, decimals = 2) {
7
+ if (bytes === 0) return "0 B";
8
+ const k = 1024;
9
+ const dm = decimals < 0 ? 0 : decimals;
10
+ const sizes = ["B", "KB", "MB", "GB", "TB"];
11
+ const i = Math.floor(Math.log(bytes) / Math.log(k));
12
+ return `${parseFloat((bytes / Math.pow(k, i)).toFixed(dm))} ${sizes[i]}`;
13
+ }
14
+
15
+ // src/maintenance/database-maintenance.ts
16
+ var CACHE_STATS_TIMEOUT_MS = 1e4;
17
+ var TABLE_COUNT_BATCH_SIZE = 50;
18
+ var STORAGE_WARNING_THRESHOLD = 100 * 1024 * 1024;
19
+ var STORAGE_CRITICAL_THRESHOLD = 50 * 1024 * 1024;
20
+ var INTERNAL_TABLES = /* @__PURE__ */ new Set(["ps_crud", "ps_oplog", "ps_buckets", "ps_untyped", "ps_tx"]);
21
+ var USER_TABLE_PATTERN = /^ps_data__(local__)?(.+)$/;
22
+ async function getCacheStatsImpl(db, logger) {
23
+ let timeoutId = null;
24
+ const timeoutPromise = new Promise((resolve) => {
25
+ timeoutId = setTimeout(() => {
26
+ logger?.warn("[getCacheStats] Query timed out after 10 seconds");
27
+ resolve(null);
28
+ }, CACHE_STATS_TIMEOUT_MS);
29
+ });
30
+ const fetchStatsPromise = (async () => {
31
+ try {
32
+ const allTables = await db.getAll(`SELECT name FROM sqlite_master WHERE type='table' AND name NOT LIKE 'sqlite_%' ORDER BY name`);
33
+ const tableSizes = /* @__PURE__ */ new Map();
34
+ let dbstatAvailable = false;
35
+ try {
36
+ const sizeResults = await db.getAll(`SELECT COALESCE(tbl, name) as name, SUM(pgsize) as size FROM dbstat GROUP BY COALESCE(tbl, name)`);
37
+ logger?.debug("[getCacheStats] DBSTAT query returned", sizeResults?.length, "entries");
38
+ for (const row of sizeResults) {
39
+ const size = Number(row.size) || 0;
40
+ if (size > 0) {
41
+ tableSizes.set(row.name, size);
42
+ dbstatAvailable = true;
43
+ }
44
+ }
45
+ logger?.debug("[getCacheStats] tableSizes map has", tableSizes.size, "tables with sizes");
46
+ } catch (dbstatError) {
47
+ logger?.debug("[getCacheStats] DBSTAT query FAILED:", dbstatError);
48
+ }
49
+ const countMap = /* @__PURE__ */ new Map();
50
+ for (let i = 0; i < allTables.length; i += TABLE_COUNT_BATCH_SIZE) {
51
+ const batch = allTables.slice(i, i + TABLE_COUNT_BATCH_SIZE);
52
+ const batchQuery = batch.map(({
53
+ name
54
+ }) => {
55
+ const escapedLiteral = name.replace(/'/g, "''");
56
+ const escapedIdentifier = name.replace(/"/g, '""');
57
+ return `SELECT '${escapedLiteral}' as name, COUNT(*) as c FROM "${escapedIdentifier}"`;
58
+ }).join(" UNION ALL ");
59
+ if (batchQuery) {
60
+ const batchCounts = await db.getAll(batchQuery);
61
+ batchCounts.forEach((row) => countMap.set(row.name, Number(row.c) || 0));
62
+ }
63
+ }
64
+ if (!dbstatAvailable && allTables.length > 0) {
65
+ logger?.debug("[getCacheStats] DBSTAT unavailable, using row-count estimation fallback");
66
+ try {
67
+ const pageSizeResult = await db.get("PRAGMA page_size");
68
+ const pageCountResult = await db.get("PRAGMA page_count");
69
+ const pageSizeBytes2 = pageSizeResult?.page_size ?? 4096;
70
+ const totalPages2 = pageCountResult?.page_count ?? 0;
71
+ const totalDbBytes = pageSizeBytes2 * totalPages2;
72
+ let totalRowCount = 0;
73
+ countMap.forEach((count) => {
74
+ totalRowCount += count;
75
+ });
76
+ const usableBytes = totalDbBytes * 0.9;
77
+ if (totalRowCount > 0) {
78
+ for (const {
79
+ name
80
+ } of allTables) {
81
+ const rowCount = countMap.get(name) ?? 0;
82
+ if (rowCount > 0) {
83
+ const estimatedBytes = Math.round(rowCount / totalRowCount * usableBytes);
84
+ tableSizes.set(name, estimatedBytes);
85
+ }
86
+ }
87
+ logger?.debug("[getCacheStats] Estimated sizes for", tableSizes.size, "tables from", totalRowCount, "total rows");
88
+ }
89
+ } catch (fallbackError) {
90
+ logger?.debug("[getCacheStats] Size estimation fallback FAILED:", fallbackError);
91
+ }
92
+ }
93
+ const tableStats = [];
94
+ for (const {
95
+ name
96
+ } of allTables) {
97
+ const rowCount = countMap.get(name) ?? 0;
98
+ const storageBytes = tableSizes.get(name) ?? 0;
99
+ const match = name.match(USER_TABLE_PATTERN);
100
+ if (match) {
101
+ tableStats.push({
102
+ name: match[2],
103
+ internalName: name,
104
+ rowCount,
105
+ storageBytes,
106
+ storageFormatted: storageBytes > 0 ? formatBytes(storageBytes) : "N/A",
107
+ isInternal: false,
108
+ isLocalOnly: !!match[1]
109
+ });
110
+ } else if (INTERNAL_TABLES.has(name) || name.startsWith("ps_") || name.startsWith("__powersync") || name.startsWith("_powersync")) {
111
+ tableStats.push({
112
+ name,
113
+ internalName: name,
114
+ rowCount,
115
+ storageBytes,
116
+ storageFormatted: storageBytes > 0 ? formatBytes(storageBytes) : "N/A",
117
+ isInternal: true,
118
+ isLocalOnly: false
119
+ });
120
+ }
121
+ }
122
+ tableStats.sort((a, b) => {
123
+ if (a.isInternal !== b.isInternal) return a.isInternal ? 1 : -1;
124
+ return b.rowCount - a.rowCount;
125
+ });
126
+ const userTables = tableStats.filter((t) => !t.isInternal);
127
+ const totalRows = userTables.reduce((sum, t) => sum + t.rowCount, 0);
128
+ const pageSize = await db.get("PRAGMA page_size");
129
+ const pageCount = await db.get("PRAGMA page_count");
130
+ const freelistCount = await db.get("PRAGMA freelist_count");
131
+ const pageSizeBytes = pageSize?.page_size ?? 4096;
132
+ const totalPages = pageCount?.page_count ?? 0;
133
+ const freePages = freelistCount?.freelist_count ?? 0;
134
+ const usedPages = totalPages - freePages;
135
+ const totalBytes = pageSizeBytes * totalPages;
136
+ const usedBytes = pageSizeBytes * usedPages;
137
+ const reclaimableBytes = pageSizeBytes * freePages;
138
+ return {
139
+ tables: tableStats,
140
+ totalTables: userTables.length,
141
+ totalRows,
142
+ storage: {
143
+ used: usedBytes,
144
+ usedFormatted: formatBytes(usedBytes),
145
+ total: totalBytes,
146
+ totalFormatted: formatBytes(totalBytes),
147
+ reclaimable: reclaimableBytes,
148
+ reclaimableFormatted: formatBytes(reclaimableBytes)
149
+ }
150
+ };
151
+ } catch (err) {
152
+ logger?.error("[getCacheStats] error:", err);
153
+ return null;
154
+ } finally {
155
+ if (timeoutId) clearTimeout(timeoutId);
156
+ }
157
+ })();
158
+ return Promise.race([fetchStatsPromise, timeoutPromise]);
159
+ }
160
+ async function compactDatabaseImpl(db) {
161
+ try {
162
+ const pageSizeBefore = await db.get("PRAGMA page_size");
163
+ const pageCountBefore = await db.get("PRAGMA page_count");
164
+ const bytesBefore = (pageSizeBefore?.page_size ?? 0) * (pageCountBefore?.page_count ?? 0);
165
+ await db.execute("VACUUM");
166
+ const pageCountAfter = await db.get("PRAGMA page_count");
167
+ const bytesAfter = (pageSizeBefore?.page_size ?? 0) * (pageCountAfter?.page_count ?? 0);
168
+ return {
169
+ success: true,
170
+ bytesReclaimed: Math.max(0, bytesBefore - bytesAfter)
171
+ };
172
+ } catch (err) {
173
+ return {
174
+ success: false,
175
+ bytesReclaimed: 0,
176
+ error: err instanceof Error ? err.message : String(err)
177
+ };
178
+ }
179
+ }
180
+ async function checkIntegrityImpl(db) {
181
+ try {
182
+ const results = await db.getAll("PRAGMA integrity_check");
183
+ const issues = [];
184
+ for (const row of results) {
185
+ const value = row.integrity_check;
186
+ if (value && value !== "ok") {
187
+ issues.push(String(value));
188
+ }
189
+ }
190
+ return {
191
+ isHealthy: issues.length === 0,
192
+ issues
193
+ };
194
+ } catch (err) {
195
+ return {
196
+ isHealthy: false,
197
+ issues: [err instanceof Error ? err.message : String(err)]
198
+ };
199
+ }
200
+ }
201
+ async function checkStorageQuotaImpl(db, getFreeDiskSpace) {
202
+ try {
203
+ const freeSpace = await getFreeDiskSpace();
204
+ let databaseSize = 0;
205
+ if (db) {
206
+ const pageSize = await db.get("PRAGMA page_size");
207
+ const pageCount = await db.get("PRAGMA page_count");
208
+ databaseSize = (pageSize?.page_size ?? 0) * (pageCount?.page_count ?? 0);
209
+ }
210
+ let status = "ok";
211
+ if (freeSpace < STORAGE_CRITICAL_THRESHOLD) {
212
+ status = "critical";
213
+ } else if (freeSpace < STORAGE_WARNING_THRESHOLD) {
214
+ status = "warning";
215
+ }
216
+ return {
217
+ deviceFreeSpace: freeSpace,
218
+ databaseSize,
219
+ warningThreshold: STORAGE_WARNING_THRESHOLD,
220
+ criticalThreshold: STORAGE_CRITICAL_THRESHOLD,
221
+ status
222
+ };
223
+ } catch {
224
+ return {
225
+ deviceFreeSpace: 0,
226
+ databaseSize: 0,
227
+ warningThreshold: STORAGE_WARNING_THRESHOLD,
228
+ criticalThreshold: STORAGE_CRITICAL_THRESHOLD,
229
+ status: "critical"
230
+ };
231
+ }
232
+ }
233
+
234
+ // src/maintenance/useDatabaseMaintenance.ts
235
+ import { c as _c } from "react/compiler-runtime";
236
+ function useDatabaseMaintenance() {
237
+ const $ = _c(15);
238
+ const {
239
+ db,
240
+ platform
241
+ } = usePowerSync();
242
+ let t0;
243
+ if ($[0] !== db || $[1] !== platform.logger) {
244
+ t0 = async () => {
245
+ if (!db) {
246
+ return null;
247
+ }
248
+ return getCacheStatsImpl(db, platform.logger);
249
+ };
250
+ $[0] = db;
251
+ $[1] = platform.logger;
252
+ $[2] = t0;
253
+ } else {
254
+ t0 = $[2];
255
+ }
256
+ const getCacheStats = t0;
257
+ let t1;
258
+ if ($[3] !== db) {
259
+ t1 = async () => {
260
+ if (!db) {
261
+ return {
262
+ success: false,
263
+ bytesReclaimed: 0,
264
+ error: "Database not initialized"
265
+ };
266
+ }
267
+ return compactDatabaseImpl(db);
268
+ };
269
+ $[3] = db;
270
+ $[4] = t1;
271
+ } else {
272
+ t1 = $[4];
273
+ }
274
+ const compactDatabase = t1;
275
+ let t2;
276
+ if ($[5] !== db) {
277
+ t2 = async () => {
278
+ if (!db) {
279
+ return {
280
+ isHealthy: false,
281
+ issues: ["Database not initialized"]
282
+ };
283
+ }
284
+ return checkIntegrityImpl(db);
285
+ };
286
+ $[5] = db;
287
+ $[6] = t2;
288
+ } else {
289
+ t2 = $[6];
290
+ }
291
+ const checkIntegrity = t2;
292
+ let t3;
293
+ if ($[7] !== db || $[8] !== platform.fileSystem) {
294
+ t3 = async () => checkStorageQuotaImpl(db, () => platform.fileSystem.getFreeDiskSpace());
295
+ $[7] = db;
296
+ $[8] = platform.fileSystem;
297
+ $[9] = t3;
298
+ } else {
299
+ t3 = $[9];
300
+ }
301
+ const checkStorageQuota = t3;
302
+ let t4;
303
+ if ($[10] !== checkIntegrity || $[11] !== checkStorageQuota || $[12] !== compactDatabase || $[13] !== getCacheStats) {
304
+ t4 = {
305
+ getCacheStats,
306
+ compactDatabase,
307
+ checkIntegrity,
308
+ checkStorageQuota
309
+ };
310
+ $[10] = checkIntegrity;
311
+ $[11] = checkStorageQuota;
312
+ $[12] = compactDatabase;
313
+ $[13] = getCacheStats;
314
+ $[14] = t4;
315
+ } else {
316
+ t4 = $[14];
317
+ }
318
+ return t4;
319
+ }
320
+
321
+ export {
322
+ formatBytes,
323
+ getCacheStatsImpl,
324
+ compactDatabaseImpl,
325
+ checkIntegrityImpl,
326
+ checkStorageQuotaImpl,
327
+ useDatabaseMaintenance
328
+ };
329
+ //# sourceMappingURL=chunk-WGHNIAF7.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"sources":["../src/utils/format.ts","../src/maintenance/database-maintenance.ts","../src/maintenance/useDatabaseMaintenance.ts"],"sourcesContent":["/**\n * Format bytes to human-readable string\n *\n * @param bytes - Number of bytes to format\n * @param decimals - Number of decimal places (default: 2)\n * @returns Formatted string like \"1.5 MB\", \"256 KB\", etc.\n */\nexport function formatBytes(bytes: number, decimals = 2): string {\n if (bytes === 0) return '0 B';\n const k = 1024;\n const dm = decimals < 0 ? 0 : decimals;\n const sizes = ['B', 'KB', 'MB', 'GB', 'TB'];\n const i = Math.floor(Math.log(bytes) / Math.log(k));\n return `${parseFloat((bytes / Math.pow(k, i)).toFixed(dm))} ${sizes[i]}`;\n}","/**\n * Pure Database Maintenance Functions for @pol-studios/powersync\n *\n * This module contains stateless, pure functions for database maintenance operations.\n * These functions can be called by hooks or used directly in other contexts.\n *\n * All functions accept the database as a parameter rather than accessing it from context,\n * making them testable and reusable across different environments.\n */\n\nimport type { AbstractPowerSyncDatabase } from '../core/types';\nimport type { LoggerAdapter } from '../platform/types';\nimport type { CacheStats, CompactResult, IntegrityResult, StorageQuota, TableCacheStats } from '../core/types';\nimport { formatBytes } from '../utils/format';\n\n// ─── Constants ────────────────────────────────────────────────────────────────\n\n/** Timeout for getCacheStats to prevent hanging if db is locked */\nconst CACHE_STATS_TIMEOUT_MS = 10000;\n\n/** Batch size for table count queries to avoid N+1 queries */\nconst TABLE_COUNT_BATCH_SIZE = 50;\n\n/** Storage warning threshold (100MB free space) */\nconst STORAGE_WARNING_THRESHOLD = 100 * 1024 * 1024;\n\n/** Storage critical threshold (50MB free space) */\nconst STORAGE_CRITICAL_THRESHOLD = 50 * 1024 * 1024;\n\n/** Known internal PowerSync tables */\nconst INTERNAL_TABLES = new Set(['ps_crud', 'ps_oplog', 'ps_buckets', 'ps_untyped', 'ps_tx']);\n\n/** Pattern to match user tables: ps_data__[local__]tablename */\nconst USER_TABLE_PATTERN = /^ps_data__(local__)?(.+)$/;\n\n// ─── Helper Types ─────────────────────────────────────────────────────────────\n\ninterface SqliteTableRow {\n name: string;\n}\ninterface DbStatRow {\n name: string;\n size: number;\n}\ninterface TableCountRow {\n name: string;\n c: number;\n}\ninterface PageSizeRow {\n page_size: number;\n}\ninterface PageCountRow {\n page_count: number;\n}\ninterface FreelistCountRow {\n freelist_count: number;\n}\ninterface IntegrityCheckRow {\n integrity_check: string;\n}\n\n// ─── getCacheStatsImpl ────────────────────────────────────────────────────────\n\n/**\n * Get detailed cache statistics for all tables in the database.\n *\n * This function:\n * - Queries all tables from sqlite_master\n * - Tries to get table sizes from dbstat (with fallback estimation)\n * - Batches table row counts in groups of 50\n * - Calculates storage info from PRAGMA page_size, page_count, freelist_count\n *\n * @param db - The PowerSync database instance\n * @param logger - Optional logger for debug output\n * @returns Cache statistics or null if operation times out or fails\n */\nexport async function getCacheStatsImpl(db: AbstractPowerSyncDatabase, logger?: LoggerAdapter): Promise<CacheStats | null> {\n let timeoutId: ReturnType<typeof setTimeout> | null = null;\n const timeoutPromise = new Promise<null>(resolve => {\n timeoutId = setTimeout(() => {\n logger?.warn('[getCacheStats] Query timed out after 10 seconds');\n resolve(null);\n }, CACHE_STATS_TIMEOUT_MS);\n });\n const fetchStatsPromise = (async (): Promise<CacheStats | null> => {\n try {\n // Get all tables from SQLite (excluding sqlite internal tables)\n const allTables = await db.getAll<SqliteTableRow>(`SELECT name FROM sqlite_master WHERE type='table' AND name NOT LIKE 'sqlite_%' ORDER BY name`);\n\n // Query all table sizes from dbstat in one go\n // Use COALESCE(tbl, name) to attribute index sizes to their parent tables\n const tableSizes = new Map<string, number>();\n let dbstatAvailable = false;\n try {\n const sizeResults = await db.getAll<DbStatRow>(`SELECT COALESCE(tbl, name) as name, SUM(pgsize) as size FROM dbstat GROUP BY COALESCE(tbl, name)`);\n logger?.debug('[getCacheStats] DBSTAT query returned', sizeResults?.length, 'entries');\n for (const row of sizeResults) {\n // Handle potential type coercion issues - SQLite may return size as string or null\n const size = Number(row.size) || 0;\n if (size > 0) {\n tableSizes.set(row.name, size);\n dbstatAvailable = true;\n }\n }\n logger?.debug('[getCacheStats] tableSizes map has', tableSizes.size, 'tables with sizes');\n } catch (dbstatError) {\n logger?.debug('[getCacheStats] DBSTAT query FAILED:', dbstatError);\n }\n\n // Batch table counts to avoid N+1 queries (50+ tables = 1+ second freeze)\n const countMap = new Map<string, number>();\n for (let i = 0; i < allTables.length; i += TABLE_COUNT_BATCH_SIZE) {\n const batch = allTables.slice(i, i + TABLE_COUNT_BATCH_SIZE);\n const batchQuery = batch.map(({\n name\n }) => {\n // Escape quotes for SQL safety\n const escapedLiteral = name.replace(/'/g, \"''\");\n const escapedIdentifier = name.replace(/\"/g, '\"\"');\n return `SELECT '${escapedLiteral}' as name, COUNT(*) as c FROM \"${escapedIdentifier}\"`;\n }).join(' UNION ALL ');\n if (batchQuery) {\n const batchCounts = await db.getAll<TableCountRow>(batchQuery);\n batchCounts.forEach(row => countMap.set(row.name, Number(row.c) || 0));\n }\n }\n\n // Fallback: estimate table sizes based on row count if DBSTAT is not available\n if (!dbstatAvailable && allTables.length > 0) {\n logger?.debug('[getCacheStats] DBSTAT unavailable, using row-count estimation fallback');\n try {\n const pageSizeResult = await db.get<PageSizeRow>('PRAGMA page_size');\n const pageCountResult = await db.get<PageCountRow>('PRAGMA page_count');\n const pageSizeBytes = pageSizeResult?.page_size ?? 4096;\n const totalPages = pageCountResult?.page_count ?? 0;\n const totalDbBytes = pageSizeBytes * totalPages;\n\n // Calculate total rows across all tables\n let totalRowCount = 0;\n countMap.forEach(count => {\n totalRowCount += count;\n });\n\n // Distribute bytes proportionally by row count\n // Reserve ~10% for overhead (indexes, metadata, etc.)\n const usableBytes = totalDbBytes * 0.9;\n if (totalRowCount > 0) {\n for (const {\n name\n } of allTables) {\n const rowCount = countMap.get(name) ?? 0;\n if (rowCount > 0) {\n const estimatedBytes = Math.round(rowCount / totalRowCount * usableBytes);\n tableSizes.set(name, estimatedBytes);\n }\n }\n logger?.debug('[getCacheStats] Estimated sizes for', tableSizes.size, 'tables from', totalRowCount, 'total rows');\n }\n } catch (fallbackError) {\n logger?.debug('[getCacheStats] Size estimation fallback FAILED:', fallbackError);\n }\n }\n\n // Build table stats array\n const tableStats: TableCacheStats[] = [];\n for (const {\n name\n } of allTables) {\n const rowCount = countMap.get(name) ?? 0;\n const storageBytes = tableSizes.get(name) ?? 0;\n const match = name.match(USER_TABLE_PATTERN);\n if (match) {\n // User table (ps_data__[local__]tablename)\n tableStats.push({\n name: match[2],\n internalName: name,\n rowCount,\n storageBytes,\n storageFormatted: storageBytes > 0 ? formatBytes(storageBytes) : 'N/A',\n isInternal: false,\n isLocalOnly: !!match[1]\n });\n } else if (INTERNAL_TABLES.has(name) || name.startsWith('ps_') || name.startsWith('__powersync') || name.startsWith('_powersync')) {\n // Internal PowerSync table\n tableStats.push({\n name,\n internalName: name,\n rowCount,\n storageBytes,\n storageFormatted: storageBytes > 0 ? formatBytes(storageBytes) : 'N/A',\n isInternal: true,\n isLocalOnly: false\n });\n }\n }\n\n // Sort: user tables first (by row count desc), then internal tables\n tableStats.sort((a, b) => {\n if (a.isInternal !== b.isInternal) return a.isInternal ? 1 : -1;\n return b.rowCount - a.rowCount;\n });\n\n // Calculate totals for user tables only\n const userTables = tableStats.filter(t => !t.isInternal);\n const totalRows = userTables.reduce((sum, t) => sum + t.rowCount, 0);\n\n // Get storage info from SQLite PRAGMAs\n const pageSize = await db.get<PageSizeRow>('PRAGMA page_size');\n const pageCount = await db.get<PageCountRow>('PRAGMA page_count');\n const freelistCount = await db.get<FreelistCountRow>('PRAGMA freelist_count');\n const pageSizeBytes = pageSize?.page_size ?? 4096;\n const totalPages = pageCount?.page_count ?? 0;\n const freePages = freelistCount?.freelist_count ?? 0;\n const usedPages = totalPages - freePages;\n const totalBytes = pageSizeBytes * totalPages;\n const usedBytes = pageSizeBytes * usedPages;\n const reclaimableBytes = pageSizeBytes * freePages;\n return {\n tables: tableStats,\n totalTables: userTables.length,\n totalRows,\n storage: {\n used: usedBytes,\n usedFormatted: formatBytes(usedBytes),\n total: totalBytes,\n totalFormatted: formatBytes(totalBytes),\n reclaimable: reclaimableBytes,\n reclaimableFormatted: formatBytes(reclaimableBytes)\n }\n };\n } catch (err) {\n logger?.error('[getCacheStats] error:', err);\n return null;\n } finally {\n if (timeoutId) clearTimeout(timeoutId);\n }\n })();\n\n // Race between actual fetch and timeout\n return Promise.race([fetchStatsPromise, timeoutPromise]);\n}\n\n// ─── compactDatabaseImpl ──────────────────────────────────────────────────────\n\n/**\n * Compact the database by running VACUUM.\n * This reclaims free pages and reduces the database file size.\n *\n * @param db - The PowerSync database instance\n * @returns Result of the compaction operation including bytes reclaimed\n */\nexport async function compactDatabaseImpl(db: AbstractPowerSyncDatabase): Promise<CompactResult> {\n try {\n // Get page count before VACUUM\n const pageSizeBefore = await db.get<PageSizeRow>('PRAGMA page_size');\n const pageCountBefore = await db.get<PageCountRow>('PRAGMA page_count');\n const bytesBefore = (pageSizeBefore?.page_size ?? 0) * (pageCountBefore?.page_count ?? 0);\n\n // Run VACUUM to compact the database\n await db.execute('VACUUM');\n\n // Get page count after VACUUM\n const pageCountAfter = await db.get<PageCountRow>('PRAGMA page_count');\n const bytesAfter = (pageSizeBefore?.page_size ?? 0) * (pageCountAfter?.page_count ?? 0);\n return {\n success: true,\n bytesReclaimed: Math.max(0, bytesBefore - bytesAfter)\n };\n } catch (err) {\n return {\n success: false,\n bytesReclaimed: 0,\n error: err instanceof Error ? err.message : String(err)\n };\n }\n}\n\n// ─── checkIntegrityImpl ───────────────────────────────────────────────────────\n\n/**\n * Check the integrity of the database.\n * Runs SQLite's PRAGMA integrity_check.\n *\n * @param db - The PowerSync database instance\n * @returns Result indicating health status and any issues found\n */\nexport async function checkIntegrityImpl(db: AbstractPowerSyncDatabase): Promise<IntegrityResult> {\n try {\n const results = await db.getAll<IntegrityCheckRow>('PRAGMA integrity_check');\n const issues: string[] = [];\n for (const row of results) {\n const value = row.integrity_check;\n if (value && value !== 'ok') {\n issues.push(String(value));\n }\n }\n return {\n isHealthy: issues.length === 0,\n issues\n };\n } catch (err) {\n return {\n isHealthy: false,\n issues: [err instanceof Error ? err.message : String(err)]\n };\n }\n}\n\n// ─── checkStorageQuotaImpl ────────────────────────────────────────────────────\n\n/**\n * Check device storage quota and database size.\n * Useful for warning users about low storage situations.\n *\n * @param db - The PowerSync database instance (can be null)\n * @param getFreeDiskSpace - Callback to get free disk space in bytes\n * @returns Storage quota information with status\n */\nexport async function checkStorageQuotaImpl(db: AbstractPowerSyncDatabase | null, getFreeDiskSpace: () => Promise<number>): Promise<StorageQuota> {\n try {\n const freeSpace = await getFreeDiskSpace();\n\n // Get database size if db is available\n let databaseSize = 0;\n if (db) {\n const pageSize = await db.get<PageSizeRow>('PRAGMA page_size');\n const pageCount = await db.get<PageCountRow>('PRAGMA page_count');\n databaseSize = (pageSize?.page_size ?? 0) * (pageCount?.page_count ?? 0);\n }\n\n // Determine status based on free space thresholds\n let status: 'ok' | 'warning' | 'critical' = 'ok';\n if (freeSpace < STORAGE_CRITICAL_THRESHOLD) {\n status = 'critical';\n } else if (freeSpace < STORAGE_WARNING_THRESHOLD) {\n status = 'warning';\n }\n return {\n deviceFreeSpace: freeSpace,\n databaseSize,\n warningThreshold: STORAGE_WARNING_THRESHOLD,\n criticalThreshold: STORAGE_CRITICAL_THRESHOLD,\n status\n };\n } catch {\n // On error, return critical status as a safety measure\n return {\n deviceFreeSpace: 0,\n databaseSize: 0,\n warningThreshold: STORAGE_WARNING_THRESHOLD,\n criticalThreshold: STORAGE_CRITICAL_THRESHOLD,\n status: 'critical'\n };\n }\n}","import { c as _c } from \"react/compiler-runtime\";\n/**\n * React Hook for Database Maintenance Utilities\n *\n * This module provides a React hook that wraps the pure database maintenance\n * functions with stable function references via useCallback.\n */\n\nimport { useCallback } from 'react';\nimport { usePowerSync } from '../provider/hooks';\nimport type { DatabaseMaintenanceUtils } from './types';\nimport { getCacheStatsImpl, compactDatabaseImpl, checkIntegrityImpl, checkStorageQuotaImpl } from './database-maintenance';\n\n/**\n * Hook for database maintenance utilities.\n *\n * Use this hook when you need to perform database maintenance operations.\n * These operations are expensive - call sparingly (e.g., settings screens).\n *\n * Each function has a stable reference via useCallback.\n *\n * @example\n * const { getCacheStats, compactDatabase } = useDatabaseMaintenance();\n * const stats = await getCacheStats();\n */\nexport function useDatabaseMaintenance() {\n const $ = _c(15);\n const {\n db,\n platform\n } = usePowerSync();\n let t0;\n if ($[0] !== db || $[1] !== platform.logger) {\n t0 = async () => {\n if (!db) {\n return null;\n }\n return getCacheStatsImpl(db, platform.logger);\n };\n $[0] = db;\n $[1] = platform.logger;\n $[2] = t0;\n } else {\n t0 = $[2];\n }\n const getCacheStats = t0;\n let t1;\n if ($[3] !== db) {\n t1 = async () => {\n if (!db) {\n return {\n success: false,\n bytesReclaimed: 0,\n error: \"Database not initialized\"\n };\n }\n return compactDatabaseImpl(db);\n };\n $[3] = db;\n $[4] = t1;\n } else {\n t1 = $[4];\n }\n const compactDatabase = t1;\n let t2;\n if ($[5] !== db) {\n t2 = async () => {\n if (!db) {\n return {\n isHealthy: false,\n issues: [\"Database not initialized\"]\n };\n }\n return checkIntegrityImpl(db);\n };\n $[5] = db;\n $[6] = t2;\n } else {\n t2 = $[6];\n }\n const checkIntegrity = t2;\n let t3;\n if ($[7] !== db || $[8] !== platform.fileSystem) {\n t3 = async () => checkStorageQuotaImpl(db, () => platform.fileSystem.getFreeDiskSpace());\n $[7] = db;\n $[8] = platform.fileSystem;\n $[9] = t3;\n } else {\n t3 = $[9];\n }\n const checkStorageQuota = t3;\n let t4;\n if ($[10] !== checkIntegrity || $[11] !== checkStorageQuota || $[12] !== compactDatabase || $[13] !== getCacheStats) {\n t4 = {\n getCacheStats,\n compactDatabase,\n checkIntegrity,\n checkStorageQuota\n };\n $[10] = checkIntegrity;\n $[11] = checkStorageQuota;\n $[12] = compactDatabase;\n $[13] = getCacheStats;\n $[14] = t4;\n } else {\n t4 = $[14];\n }\n return t4;\n}"],"mappings":"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","names":["pageSizeBytes","totalPages"]}