@pol-studios/powersync 1.0.1 → 1.0.3
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/attachments/index.js +1 -1
- package/dist/{chunk-PANEMMTU.js → chunk-3AYXHQ4W.js} +17 -11
- package/dist/chunk-3AYXHQ4W.js.map +1 -0
- package/dist/{chunk-BJ36QDFN.js → chunk-7EMDVIZX.js} +1 -1
- package/dist/chunk-7EMDVIZX.js.map +1 -0
- package/dist/{chunk-MB2RC3NS.js → chunk-C2RSTGDC.js} +129 -89
- package/dist/chunk-C2RSTGDC.js.map +1 -0
- package/dist/{chunk-NPNBGCRC.js → chunk-EJ23MXPQ.js} +1 -1
- package/dist/{chunk-NPNBGCRC.js.map → chunk-EJ23MXPQ.js.map} +1 -1
- package/dist/{chunk-CHRTN5PF.js → chunk-FPTDATY5.js} +1 -1
- package/dist/chunk-FPTDATY5.js.map +1 -0
- package/dist/chunk-GMFDCVMZ.js +1285 -0
- package/dist/chunk-GMFDCVMZ.js.map +1 -0
- package/dist/chunk-OLHGI472.js +1 -0
- package/dist/chunk-OLHGI472.js.map +1 -0
- package/dist/{chunk-CFCK2LHI.js → chunk-OTJXIRWX.js} +45 -40
- package/dist/chunk-OTJXIRWX.js.map +1 -0
- package/dist/{chunk-GBGATW2S.js → chunk-V6LJ6MR2.js} +86 -95
- package/dist/chunk-V6LJ6MR2.js.map +1 -0
- package/dist/connector/index.d.ts +1 -1
- package/dist/connector/index.js +2 -2
- package/dist/core/index.js +2 -2
- package/dist/{index-D952Qr38.d.ts → index-Cb-NI0Ct.d.ts} +9 -2
- package/dist/index.d.ts +2 -2
- package/dist/index.js +8 -9
- package/dist/index.native.d.ts +1 -1
- package/dist/index.native.js +9 -10
- package/dist/index.web.d.ts +1 -1
- package/dist/index.web.js +9 -10
- package/dist/platform/index.js.map +1 -1
- package/dist/platform/index.native.js +1 -1
- package/dist/platform/index.web.js +1 -1
- package/dist/provider/index.d.ts +6 -1
- package/dist/provider/index.js +6 -6
- package/dist/sync/index.js +3 -3
- package/package.json +3 -1
- package/dist/chunk-42IJ25Q4.js +0 -45
- package/dist/chunk-42IJ25Q4.js.map +0 -1
- package/dist/chunk-BJ36QDFN.js.map +0 -1
- package/dist/chunk-CFCK2LHI.js.map +0 -1
- package/dist/chunk-CHRTN5PF.js.map +0 -1
- package/dist/chunk-GBGATW2S.js.map +0 -1
- package/dist/chunk-H7HZMI4H.js +0 -925
- package/dist/chunk-H7HZMI4H.js.map +0 -1
- package/dist/chunk-MB2RC3NS.js.map +0 -1
- package/dist/chunk-PANEMMTU.js.map +0 -1
|
@@ -1 +0,0 @@
|
|
|
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(\n message: string,\n public readonly cause?: Error\n ) {\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(\n message: string,\n public readonly errorType: SyncErrorType,\n public readonly cause?: Error\n ) {\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(\n message: string,\n public readonly operation: 'fetchCredentials' | 'uploadData',\n public readonly cause?: Error\n ) {\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(\n message: string,\n public readonly attachmentId: string,\n public readonly operation: 'download' | 'compress' | 'delete' | 'evict',\n public readonly cause?: Error\n ) {\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(\n message: string,\n public readonly missingFeature: string\n ) {\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(\n message: string,\n public readonly configKey?: string\n ) {\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: /network|fetch|econnrefused|etimedout|offline/i,\n auth: /401|403|auth|token|unauthorized|forbidden/i,\n server: /500|502|503|504|internal server/i,\n conflict: /conflict|concurrent|version/i,\n validation: /validation|constraint|invalid|required/i,\n quota: /quota|storage|enospc|disk/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\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(\n error: Error,\n message?: string\n): 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\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\n // Class 08 - Connection Exception (transient)\n CONNECTION_FAILURE: '08006',\n CONNECTION_DOES_NOT_EXIST: '08003',\n\n // Class 53 - Insufficient Resources (transient)\n OUT_OF_MEMORY: '53200',\n DISK_FULL: '53100',\n\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\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\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\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\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 if (/network|fetch|econnrefused|etimedout|offline|dns|socket/.test(lowerMessage)) {\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 if (/401|403|auth|token|unauthorized|forbidden|jwt|expired/.test(lowerMessage)) {\n result.type = 'auth';\n result.isPermanent = lowerMessage.includes('expired') || lowerMessage.includes('invalid');\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 if (/500|502|503|504|internal server|service unavailable/.test(lowerMessage)) {\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\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"],"mappings":"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","names":[]}
|
|
@@ -1 +0,0 @@
|
|
|
1
|
-
{"version":3,"sources":["../src/attachments/types.ts","../src/attachments/attachment-queue.ts"],"sourcesContent":["/**\n * Attachment Queue Types for @pol-studios/powersync\n *\n * Defines interfaces for the attachment queue system that handles\n * offline file caching with download, compression, and eviction.\n */\n\n// ─── Attachment States ───────────────────────────────────────────────────────\n\n/**\n * State of an attachment in the queue.\n * NOTE: These do NOT match @powersync/attachments enum ordering (values 0-2 differ).\n */\nexport enum AttachmentState {\n /** Waiting to be downloaded */\n QUEUED_DOWNLOAD = 0,\n /** Waiting for initial sync */\n QUEUED_SYNC = 1,\n /** Waiting to be uploaded */\n QUEUED_UPLOAD = 2,\n /** Fully synced (downloaded or uploaded) */\n SYNCED = 3,\n /** Archived (removed from sync but record kept) */\n ARCHIVED = 4,\n}\n\n// ─── Attachment Record ───────────────────────────────────────────────────────\n\n/**\n * Record representing an attachment in the queue database.\n */\nexport interface AttachmentRecord {\n /** Unique identifier (typically storage path) */\n id: string;\n /** Filename for display and type inference */\n filename: string;\n /** MIME type of the file */\n media_type: string;\n /** Current state in the queue */\n state: AttachmentState;\n /** Local file URI (set after download) */\n local_uri?: string | null;\n /** File size in bytes */\n size?: number;\n /** Timestamp when the attachment was created */\n timestamp?: number;\n}\n\n// ─── Source Table Configuration ──────────────────────────────────────────────\n\n/**\n * Configuration for the source table that contains attachment references.\n * This makes the attachment queue reusable across different tables/projects.\n */\nexport interface AttachmentSourceConfig {\n /**\n * Table name containing attachment references.\n * @example \"EquipmentUnitMediaContent\"\n */\n table: string;\n\n /**\n * Column containing the storage path / attachment ID.\n * @example \"storagePath\"\n */\n idColumn: string;\n\n /**\n * Column to order by for \"newest first\" downloads.\n * Set to null to skip ordering.\n * @example \"takenOn\"\n */\n orderByColumn: string | null;\n\n /**\n * Optional filter config to exclude attachments from archived/unsynced projects.\n * When set, the attachment query JOINs through intermediary tables to ensure\n * only attachments belonging to synced projects are downloaded.\n */\n projectFilter?: {\n /** Foreign key column in the source table (e.g., \"equipmentUnitId\") */\n foreignKey: string;\n /** Intermediary table to JOIN through (e.g., \"EquipmentFixtureUnit\") */\n intermediaryTable: string;\n /** Column in intermediary table that links to ProjectDatabase (e.g., \"projectDatabaseId\") */\n projectForeignKey: string;\n };\n}\n\n// ─── Storage Adapter ─────────────────────────────────────────────────────────\n\n/**\n * Interface for attachment storage operations (e.g., Supabase Storage).\n */\nexport interface AttachmentStorageAdapter {\n /**\n * Download a file from remote storage.\n * @param filePath - The storage path of the file\n * @returns The file data as a Blob or base64 string\n */\n downloadFile(filePath: string): Promise<Blob | string>;\n\n /**\n * Upload a file to remote storage (optional - not all queues need upload).\n * @param filePath - The storage path to upload to\n * @param data - The file data to upload\n */\n uploadFile?(filePath: string, data: Blob | string): Promise<void>;\n\n /**\n * Delete a file from remote storage (optional).\n * @param filePath - The storage path of the file\n */\n deleteFile?(filePath: string): Promise<void>;\n\n /**\n * Resolve the storage bucket for a file path.\n * Allows routing different files to different buckets.\n * @param filePath - The file path to resolve\n * @returns The bucket name\n */\n resolveBucket?(filePath: string): string;\n}\n\n// ─── Compression Configuration ───────────────────────────────────────────────\n\n/**\n * Configuration for image compression.\n */\nexport interface CompressionConfig {\n /** Enable compression (default: true) */\n enabled: boolean;\n /** Compression quality 0.0-1.0 (default: 0.7) */\n quality: number;\n /** Max width before resizing (default: 2048) */\n maxWidth: number;\n /** Skip files under this size in bytes (default: 100KB) */\n skipSizeBytes: number;\n /** Skip if already under this size in bytes (default: 300KB) */\n targetSizeBytes: number;\n}\n\n/**\n * Default compression configuration.\n */\nexport const DEFAULT_COMPRESSION_CONFIG: CompressionConfig = {\n enabled: true,\n quality: 0.7,\n maxWidth: 2048,\n skipSizeBytes: 100_000,\n targetSizeBytes: 300_000,\n};\n\n// ─── Download Configuration ──────────────────────────────────────────────────\n\n/**\n * Configuration for the download engine.\n */\nexport interface DownloadConfig {\n /** Maximum concurrent downloads (default: 50) */\n concurrency: number;\n /** Download timeout per file in ms (default: 60000) */\n timeoutMs: number;\n /** Retry delay between batches in ms (default: 5000) */\n retryDelayMs: number;\n}\n\n/**\n * Default download configuration.\n */\nexport const DEFAULT_DOWNLOAD_CONFIG: DownloadConfig = {\n concurrency: 50,\n timeoutMs: 60_000,\n retryDelayMs: 5_000,\n};\n\n// ─── Cache Configuration ─────────────────────────────────────────────────────\n\n/**\n * Configuration for cache management.\n */\nexport interface CacheConfig {\n /** Maximum cache size in bytes (default: 5GB) */\n maxSize: number;\n /** Stop downloads at this percentage of max (default: 0.95 = 95%) */\n downloadStopThreshold: number;\n /** Trigger eviction at this percentage (default: 1.0 = 100%) */\n evictionTriggerThreshold: number;\n}\n\n/**\n * Default cache configuration.\n */\nexport const DEFAULT_CACHE_CONFIG: CacheConfig = {\n maxSize: 5 * 1024 * 1024 * 1024, // 5 GB\n downloadStopThreshold: 0.95,\n evictionTriggerThreshold: 1.0,\n};\n\n// ─── Attachment Queue Configuration ──────────────────────────────────────────\n\n/**\n * Full configuration for the attachment queue.\n */\nexport interface AttachmentQueueConfig {\n /** Source table configuration */\n source: AttachmentSourceConfig;\n\n /** Storage adapter for downloading files */\n storage: AttachmentStorageAdapter;\n\n /** Table name for storing attachment records (default: \"photo_attachments\") */\n attachmentTableName?: string;\n\n /** Perform initial sync on initialization (default: true) */\n performInitialSync?: boolean;\n\n /** Download configuration */\n download?: Partial<DownloadConfig>;\n\n /** Cache configuration */\n cache?: Partial<CacheConfig>;\n\n /** Compression configuration */\n compression?: Partial<CompressionConfig>;\n\n /**\n * Called when a download fails.\n * Return { retry: true } to retry, { retry: false } to skip.\n */\n onDownloadError?: (\n attachment: AttachmentRecord,\n error: Error\n ) => Promise<{ retry: boolean }>;\n\n /**\n * Called when sync progress changes.\n */\n onProgress?: (stats: AttachmentSyncStats) => void;\n}\n\n// ─── Download Status Types ───────────────────────────────────────────────────\n\n/**\n * Current phase of a download operation.\n */\nexport type DownloadPhase = 'downloading' | 'compressing' | 'complete' | 'error';\n\n/**\n * Status of an individual download.\n */\nexport interface DownloadStatus {\n /** Attachment ID */\n id: string;\n /** Filename being downloaded */\n filename: string;\n /** Current phase */\n phase: DownloadPhase;\n}\n\n// ─── Sync Status Types ───────────────────────────────────────────────────────\n\n/**\n * Why downloads are stopped (if not actively syncing).\n */\nexport type AttachmentSyncStatus =\n | 'syncing' // Actively downloading\n | 'paused' // User manually paused\n | 'cache_full' // Stopped because cache hit capacity\n | 'complete'; // All attachments downloaded\n\n/**\n * Statistics about the attachment sync progress.\n */\nexport interface AttachmentSyncStats {\n /** Number of attachments that have been downloaded */\n syncedCount: number;\n /** Total size of synced attachments in bytes */\n syncedSize: number;\n /** Number of attachments waiting to be downloaded */\n pendingCount: number;\n /** Total expected attachments (synced + pending) */\n totalExpected: number;\n /** Maximum cache size in bytes */\n maxCacheSize: number;\n /** Current compression quality (0.1 to 1.0) */\n compressionQuality: number;\n /** Current sync status */\n status: AttachmentSyncStatus;\n /** Whether downloads are paused */\n isPaused: boolean;\n /** Whether currently processing downloads */\n isProcessing: boolean;\n /** Currently active downloads */\n activeDownloads: DownloadStatus[];\n}\n\n// ─── SQL Row Types (for internal use) ────────────────────────────────────────\n\n/** Row from stats query */\nexport interface AttachmentStatsRow {\n state: number;\n cnt: number;\n sz: number;\n}\n\n/** Row for cache file operations */\nexport interface CacheFileRow {\n id: string;\n local_uri: string;\n}\n\n/** Row for eviction operations */\nexport interface EvictRow {\n id: string;\n local_uri: string;\n size: number;\n}\n\n/** Row for cached size queries */\nexport interface CachedSizeRow {\n total: number;\n}\n\n/** Row for ID queries */\nexport interface IdRow {\n id: string;\n}\n","/**\n * Attachment Queue for @pol-studios/powersync\n *\n * Platform-agnostic attachment queue that handles:\n * - Parallel downloads with configurable concurrency\n * - Cache management with eviction\n * - Image compression\n * - Pause/resume functionality\n * - Progress tracking\n */\n\nimport type { AbstractPowerSyncDatabase } from '../core/types';\nimport type { PlatformAdapter, LoggerAdapter } from '../platform/types';\nimport {\n AttachmentState,\n type AttachmentRecord,\n type AttachmentQueueConfig,\n type AttachmentSyncStats,\n type AttachmentSyncStatus,\n type DownloadStatus,\n type DownloadPhase,\n type AttachmentStatsRow,\n type CacheFileRow,\n type EvictRow,\n type CachedSizeRow,\n type IdRow,\n DEFAULT_COMPRESSION_CONFIG,\n DEFAULT_DOWNLOAD_CONFIG,\n DEFAULT_CACHE_CONFIG,\n} from './types';\n\n// ─── Internal Types ──────────────────────────────────────────────────────────\n\ninterface DownloadResult {\n id: string;\n size: number;\n}\n\n// ─── Constants ───────────────────────────────────────────────────────────────\n\nconst NOTIFY_THROTTLE_MS = 500;\nconst STATS_CACHE_TTL_MS = 500;\nconst CACHE_SIZE_TTL_MS = 5000;\n\n/**\n * Platform-agnostic attachment queue for offline file caching.\n *\n * @example\n * ```typescript\n * const queue = new AttachmentQueue({\n * powersync: db,\n * platform: platformAdapter,\n * config: {\n * source: {\n * table: 'EquipmentUnitMediaContent',\n * idColumn: 'storagePath',\n * orderByColumn: 'takenOn',\n * },\n * storage: storageAdapter,\n * },\n * });\n *\n * await queue.init();\n * ```\n */\nexport class AttachmentQueue {\n // ─── Dependencies ──────────────────────────────────────────────────────────\n\n private readonly powersync: AbstractPowerSyncDatabase;\n private readonly platform: PlatformAdapter;\n private readonly config: AttachmentQueueConfig;\n private readonly logger: LoggerAdapter;\n private readonly tableName: string;\n\n // ─── Configuration ─────────────────────────────────────────────────────────\n\n private readonly concurrency: number;\n private readonly downloadTimeoutMs: number;\n private readonly retryDelayMs: number;\n private readonly downloadStopThreshold: number;\n private readonly evictionTriggerThreshold: number;\n\n // ─── State ─────────────────────────────────────────────────────────────────\n\n private _initialized = false;\n private _paused = false;\n private _processing = false;\n private _cacheFull = false;\n private _resumeRequested = false;\n private _maxCacheSize: number;\n private _compressionQuality: number;\n private _abort = new AbortController();\n private _downloads = new Map<string, DownloadStatus>();\n\n // ─── Caching ───────────────────────────────────────────────────────────────\n\n private _cachedSize = 0;\n private _cachedSizeTimestamp = 0;\n private _cachedStats: AttachmentSyncStats | null = null;\n private _cachedStatsTimestamp = 0;\n\n // ─── Notification ──────────────────────────────────────────────────────────\n\n private _lastNotifyTime = 0;\n private _notifyTimer: ReturnType<typeof setTimeout> | null = null;\n private _progressCallbacks = new Set<(stats: AttachmentSyncStats) => void>();\n private _watcherInterval: ReturnType<typeof setInterval> | null = null;\n\n // ─── Constructor ───────────────────────────────────────────────────────────\n\n constructor(options: {\n powersync: AbstractPowerSyncDatabase;\n platform: PlatformAdapter;\n config: AttachmentQueueConfig;\n }) {\n this.powersync = options.powersync;\n this.platform = options.platform;\n this.config = options.config;\n this.logger = options.platform.logger;\n this.tableName = options.config.attachmentTableName ?? 'photo_attachments';\n\n // Merge with defaults\n const downloadConfig = { ...DEFAULT_DOWNLOAD_CONFIG, ...options.config.download };\n const cacheConfig = { ...DEFAULT_CACHE_CONFIG, ...options.config.cache };\n const compressionConfig = { ...DEFAULT_COMPRESSION_CONFIG, ...options.config.compression };\n\n this.concurrency = downloadConfig.concurrency;\n this.downloadTimeoutMs = downloadConfig.timeoutMs;\n this.retryDelayMs = downloadConfig.retryDelayMs;\n this.downloadStopThreshold = cacheConfig.downloadStopThreshold;\n this.evictionTriggerThreshold = cacheConfig.evictionTriggerThreshold;\n this._maxCacheSize = cacheConfig.maxSize;\n this._compressionQuality = compressionConfig.quality;\n }\n\n // ─── Initialization ────────────────────────────────────────────────────────\n\n /**\n * Initialize the attachment queue.\n * Creates the attachment table if needed and starts watching for downloads.\n */\n async init(): Promise<void> {\n if (this._initialized) return;\n\n this.logger.info('[AttachmentQueue] Initializing...');\n\n // Create attachment table\n await this.powersync.execute(`\n CREATE TABLE IF NOT EXISTS ${this.tableName} (\n id TEXT PRIMARY KEY,\n filename TEXT NOT NULL,\n media_type TEXT,\n state INTEGER NOT NULL DEFAULT ${AttachmentState.QUEUED_DOWNLOAD},\n local_uri TEXT,\n size INTEGER DEFAULT 0,\n timestamp INTEGER\n )\n `);\n\n this._initialized = true;\n\n // Start watching for downloads if configured\n if (this.config.performInitialSync !== false) {\n this._startDownloadWatcher();\n }\n\n this.logger.info('[AttachmentQueue] Initialized');\n }\n\n /**\n * Dispose the attachment queue.\n */\n dispose(): void {\n this._abort.abort();\n if (this._watcherInterval) {\n clearInterval(this._watcherInterval);\n this._watcherInterval = null;\n }\n if (this._notifyTimer) {\n clearTimeout(this._notifyTimer);\n }\n this._progressCallbacks.clear();\n this._initialized = false;\n }\n\n // ─── Public API ────────────────────────────────────────────────────────────\n\n /**\n * Subscribe to real-time progress updates.\n * Returns an unsubscribe function.\n */\n onProgress(callback: (stats: AttachmentSyncStats) => void): () => void {\n this._progressCallbacks.add(callback);\n // Immediately notify with current stats\n this._notify(true);\n return () => {\n this._progressCallbacks.delete(callback);\n };\n }\n\n /** Whether downloads are paused */\n get paused(): boolean {\n return this._paused;\n }\n\n /** Whether currently processing downloads */\n get processing(): boolean {\n return this._processing;\n }\n\n /**\n * Set the maximum cache size.\n */\n async setMaxCacheSize(bytes: number): Promise<void> {\n const oldLimit = this._maxCacheSize;\n this._maxCacheSize = bytes;\n this.logger.info(\n `[AttachmentQueue] Cache limit changed: ${Math.round(oldLimit / 1024 / 1024)}MB → ${Math.round(bytes / 1024 / 1024)}MB`\n );\n\n if (bytes < oldLimit) {\n const currentSize = await this._getCachedSizeWithCache();\n const downloadStopLimit = bytes * this.downloadStopThreshold;\n\n if (currentSize >= downloadStopLimit) {\n this._cacheFull = true;\n this._abort.abort();\n this._abort = new AbortController();\n this._notify(true);\n await this._evictIfNeeded();\n } else {\n this._notify(true);\n }\n } else if (bytes > oldLimit && this._cacheFull) {\n this._cacheFull = false;\n this._notify(true);\n if (!this._paused && !this._processing) {\n this._startDownloads();\n }\n } else {\n this._notify(true);\n }\n }\n\n /**\n * Set the compression quality (0.1 to 1.0).\n */\n setCompressionQuality(quality: number): void {\n this._compressionQuality = Math.max(0.1, Math.min(1.0, quality));\n this._notify(true);\n }\n\n /** Get the current compression quality */\n getCompressionQuality(): number {\n return this._compressionQuality;\n }\n\n /**\n * Pause downloads.\n */\n pause(): void {\n this.logger.info('[AttachmentQueue] Pausing downloads');\n this._paused = true;\n this._abort.abort();\n this._abort = new AbortController();\n this._notify(true);\n }\n\n /**\n * Resume downloads.\n */\n resume(): void {\n this.logger.info('[AttachmentQueue] Resuming downloads');\n this._paused = false;\n this._abort = new AbortController();\n this._notify(true);\n\n if (this._processing) {\n this._resumeRequested = true;\n } else {\n this._startDownloads();\n }\n }\n\n /**\n * Get current sync statistics.\n */\n async getStats(): Promise<AttachmentSyncStats> {\n const now = Date.now();\n\n if (\n this._cachedStats &&\n now - this._cachedStatsTimestamp < STATS_CACHE_TTL_MS\n ) {\n return {\n ...this._cachedStats,\n compressionQuality: this._compressionQuality,\n status: this._getStatus(this._cachedStats.pendingCount),\n isPaused: this._paused,\n isProcessing: this._processing,\n activeDownloads: [...this._downloads.values()],\n };\n }\n\n const rows = await this.powersync.getAll<AttachmentStatsRow>(\n `SELECT state, COUNT(*) as cnt, COALESCE(SUM(size), 0) as sz\n FROM ${this.tableName} GROUP BY state`\n );\n\n let synced = 0;\n let syncedSize = 0;\n let pending = 0;\n\n for (const r of rows) {\n if (r.state === AttachmentState.SYNCED) {\n synced = r.cnt;\n syncedSize = r.sz;\n }\n if (\n r.state === AttachmentState.QUEUED_DOWNLOAD ||\n r.state === AttachmentState.QUEUED_SYNC\n ) {\n pending += r.cnt;\n }\n }\n\n this._cachedSize = syncedSize;\n this._cachedSizeTimestamp = now;\n\n const stats: AttachmentSyncStats = {\n syncedCount: synced,\n syncedSize,\n pendingCount: pending,\n totalExpected: synced + pending,\n maxCacheSize: this._maxCacheSize,\n compressionQuality: this._compressionQuality,\n status: this._getStatus(pending),\n isPaused: this._paused,\n isProcessing: this._processing,\n activeDownloads: [...this._downloads.values()],\n };\n\n this._cachedStats = stats;\n this._cachedStatsTimestamp = now;\n\n return stats;\n }\n\n /**\n * Clear all cached files and re-queue for download.\n */\n async clearCache(): Promise<void> {\n const wasPaused = this._paused;\n if (!wasPaused) this.pause();\n\n try {\n // Wait for processing to stop\n let attempts = 0;\n while (this._processing && attempts < 50) {\n await this._sleep(100, this._abort.signal);\n attempts++;\n }\n\n // Delete all cached files\n const withFiles = await this.powersync.getAll<CacheFileRow>(\n `SELECT id, local_uri FROM ${this.tableName} WHERE local_uri IS NOT NULL`\n );\n\n for (const row of withFiles) {\n try {\n await this.platform.fileSystem.deleteFile(row.local_uri);\n } catch {\n // File may already be gone\n }\n }\n\n // Re-queue all records\n await this.powersync.execute(\n `UPDATE ${this.tableName} SET state = ?, local_uri = NULL, size = 0`,\n [AttachmentState.QUEUED_DOWNLOAD]\n );\n } finally {\n this._cachedSize = 0;\n this._cachedSizeTimestamp = 0;\n this._cachedStats = null;\n this._cachedStatsTimestamp = 0;\n this._cacheFull = false;\n\n if (!wasPaused) {\n this.resume();\n } else {\n this._notify(true);\n }\n }\n }\n\n /**\n * Get an attachment record by ID.\n */\n async getRecord(id: string): Promise<AttachmentRecord | null> {\n return this.powersync.get<AttachmentRecord>(\n `SELECT * FROM ${this.tableName} WHERE id = ?`,\n [id]\n );\n }\n\n /**\n * Get the local file URI for an attachment.\n */\n getLocalUri(localPath: string): string {\n const cacheDir = this.platform.fileSystem.getCacheDirectory();\n return `${cacheDir}attachments/${localPath}`;\n }\n\n /**\n * Cache a local file (e.g. one just uploaded) into the attachment cache.\n * This avoids a redundant download by copying the source file directly\n * into the cache directory and marking it as SYNCED.\n */\n async cacheLocalFile(storagePath: string, sourceUri: string): Promise<void> {\n if (!this._initialized) {\n this.logger.warn('[AttachmentQueue] cacheLocalFile called before init');\n return;\n }\n\n try {\n // Verify source file exists (temp files can be cleaned by OS)\n const sourceInfo = await this.platform.fileSystem.getFileInfo(sourceUri);\n if (!sourceInfo || !sourceInfo.exists) {\n this.logger.warn(`[AttachmentQueue] Source file does not exist: ${sourceUri}`);\n return;\n }\n\n // Same path sanitization as _downloadOne\n const localPath = storagePath.replace(/[^a-zA-Z0-9]/g, '_');\n const localUri = this.getLocalUri(localPath);\n\n // Ensure cache directory exists\n const dir = localUri.substring(0, localUri.lastIndexOf('/'));\n await this.platform.fileSystem.makeDirectory(dir, { intermediates: true });\n\n // Copy uploaded file into cache\n await this.platform.fileSystem.copyFile(sourceUri, localUri);\n\n // Get file size for cache tracking\n const info = await this.platform.fileSystem.getFileInfo(localUri);\n const size = (info && info.exists) ? info.size : 0;\n\n // Infer media type from extension\n const ext = storagePath.split('.').pop()?.toLowerCase() ?? '';\n const mediaTypeMap: Record<string, string> = {\n jpg: 'image/jpeg', jpeg: 'image/jpeg', png: 'image/png',\n webp: 'image/webp', heic: 'image/heic', heif: 'image/heif',\n gif: 'image/gif', mp4: 'video/mp4', mov: 'video/quicktime',\n };\n const mediaType = mediaTypeMap[ext] ?? 'application/octet-stream';\n\n // INSERT OR REPLACE as SYNCED\n await this.powersync.execute(\n `INSERT OR REPLACE INTO ${this.tableName} (id, filename, media_type, state, local_uri, size, timestamp)\n VALUES (?, ?, ?, ?, ?, ?, ?)`,\n [storagePath, storagePath, mediaType, AttachmentState.SYNCED, localPath, size, Date.now()]\n );\n\n // Invalidate stats caches\n this._cachedStats = null;\n this._cachedStatsTimestamp = 0;\n this._cachedSizeTimestamp = 0;\n\n // Notify subscribers\n this._notify(true);\n\n this.logger.info(`[AttachmentQueue] Cached local file: ${storagePath} (${Math.round(size / 1024)}KB)`);\n } catch (err) {\n this.logger.warn(`[AttachmentQueue] Failed to cache local file: ${storagePath}`, err);\n }\n }\n\n // ─── Download Watcher ──────────────────────────────────────────────────────\n\n private _startDownloadWatcher(): void {\n const { table, idColumn, projectFilter } = this.config.source;\n\n let query: string;\n if (projectFilter) {\n const { foreignKey, intermediaryTable, projectForeignKey } = projectFilter;\n query = `\n SELECT m.${idColumn} as id\n FROM ${table} m\n JOIN ${intermediaryTable} u ON m.${foreignKey} = u.id\n JOIN ProjectDatabase p ON u.${projectForeignKey} = p.id\n WHERE m.${idColumn} IS NOT NULL AND m.${idColumn} != ''\n `;\n } else {\n query = `SELECT ${idColumn} as id FROM ${table}\n WHERE ${idColumn} IS NOT NULL AND ${idColumn} != ''`;\n }\n\n // Use a simple interval-based approach since we can't rely on powersync.watch\n const checkForNewAttachments = async () => {\n // Check if disposed or aborted before executing\n if (this._abort.signal.aborted || !this._initialized) {\n return;\n }\n\n try {\n const result = await this.powersync.getAll<IdRow>(query);\n // Handle potential null/undefined result (defensive)\n const ids = (result ?? []).map((r) => r.id);\n\n // No attachments found - this is normal for new projects\n if (ids.length === 0) {\n return;\n }\n\n // Sync new attachments to queue\n await this._syncAttachmentIds(ids);\n\n // Start downloads if not paused\n if (!this._paused && !this._processing) {\n this._startDownloads();\n }\n } catch (err) {\n // Handle expected conditions gracefully (new projects, initial sync)\n const errorMessage = String(err);\n if (\n errorMessage.includes('Result set is empty') ||\n errorMessage.includes('no such table') ||\n errorMessage.includes('SQLITE_EMPTY')\n ) {\n // This is expected during initial sync or for new projects - log at debug level\n this.logger.debug('[AttachmentQueue] No attachments found in source table (expected for new projects)');\n return;\n }\n this.logger.warn('[AttachmentQueue] Watch error:', err);\n }\n };\n\n // Initial check\n checkForNewAttachments();\n\n // Periodic check every 30 seconds - store reference for cleanup\n this._watcherInterval = setInterval(checkForNewAttachments, 30000);\n }\n\n private async _syncAttachmentIds(ids: string[]): Promise<void> {\n for (const id of ids) {\n const existing = await this.getRecord(id);\n if (!existing) {\n await this.powersync.execute(\n `INSERT OR IGNORE INTO ${this.tableName} (id, filename, state, timestamp)\n VALUES (?, ?, ?, ?)`,\n [id, id, AttachmentState.QUEUED_DOWNLOAD, Date.now()]\n );\n }\n }\n }\n\n // ─── Download Engine ───────────────────────────────────────────────────────\n\n private async _startDownloads(): Promise<void> {\n if (this._paused || this._processing) {\n if (this._processing) {\n this._resumeRequested = true;\n }\n return;\n }\n\n this.logger.info('[AttachmentQueue] Starting downloads');\n this._processing = true;\n this._resumeRequested = false;\n const signal = this._abort.signal;\n\n try {\n let toProcess = await this._getIdsToDownload();\n let prevQueueSize = 0;\n\n while (toProcess.length > 0 && !signal.aborted) {\n const ordered = await this._orderByNewest(toProcess);\n let currentCachedSize = await this._getCachedSizeWithCache();\n const downloadStopLimit = this._maxCacheSize * this.downloadStopThreshold;\n\n if (currentCachedSize >= downloadStopLimit) {\n this._cacheFull = true;\n this.logger.info('[AttachmentQueue] Cache at capacity, stopping downloads');\n break;\n }\n\n this._cacheFull = false;\n\n for (let i = 0; i < ordered.length; i += this.concurrency) {\n if (signal.aborted) break;\n\n if (currentCachedSize >= downloadStopLimit) {\n this._cacheFull = true;\n break;\n }\n\n const chunk = ordered.slice(i, i + this.concurrency);\n const results = await Promise.allSettled(\n chunk.map((id) => this._downloadOne(id, signal))\n );\n\n const successful: DownloadResult[] = [];\n for (const result of results) {\n if (result.status === 'fulfilled' && result.value) {\n successful.push(result.value);\n }\n }\n\n if (successful.length > 0) {\n await this._batchUpdateSizes(successful);\n currentCachedSize = await this._getCachedSizeWithCache(true);\n }\n }\n\n if (signal.aborted) break;\n\n toProcess = await this._getIdsToDownload();\n\n if (toProcess.length > 0 && toProcess.length >= prevQueueSize) {\n await this._sleep(this.retryDelayMs, signal);\n }\n prevQueueSize = toProcess.length;\n }\n } catch (err) {\n this.logger.error('[AttachmentQueue] Download loop error:', err);\n } finally {\n this._processing = false;\n this._notify(true);\n\n if (this._resumeRequested && !this._paused) {\n this._resumeRequested = false;\n this._startDownloads();\n }\n }\n }\n\n private async _downloadOne(\n id: string,\n signal: AbortSignal\n ): Promise<DownloadResult | null> {\n if (signal.aborted) return null;\n\n const record = await this.getRecord(id);\n if (!record || signal.aborted) return null;\n\n this._downloads.set(id, {\n id,\n filename: record.filename,\n phase: 'downloading',\n });\n\n try {\n // Download from storage\n const data = await this._withTimeout(\n this.config.storage.downloadFile(record.filename),\n this.downloadTimeoutMs,\n `Download timeout: ${record.filename}`\n );\n\n if (signal.aborted) return null;\n\n // Save to local file\n const localPath = `${id.replace(/[^a-zA-Z0-9]/g, '_')}`;\n const localUri = this.getLocalUri(localPath);\n\n // Ensure directory exists\n const dir = localUri.substring(0, localUri.lastIndexOf('/'));\n await this.platform.fileSystem.makeDirectory(dir, { intermediates: true });\n\n // Write file\n const content = data instanceof Blob\n ? await this._blobToBase64(data)\n : data;\n await this.platform.fileSystem.writeFile(localUri, content, 'base64');\n\n // Compress if applicable\n await this._compressImage(localUri, id, record.filename);\n\n // Get final size\n const info = await this.platform.fileSystem.getFileInfo(localUri);\n if (info && info.exists) {\n // Update record\n await this.powersync.execute(\n `UPDATE ${this.tableName}\n SET state = ?, local_uri = ?, size = ?\n WHERE id = ?`,\n [AttachmentState.SYNCED, localPath, info.size, id]\n );\n\n return { id, size: info.size };\n }\n\n return null;\n } catch (err) {\n this.logger.warn(`[AttachmentQueue] Failed: ${record.filename}`, err);\n return null;\n } finally {\n this._downloads.delete(id);\n this._notify();\n }\n }\n\n private async _batchUpdateSizes(results: DownloadResult[]): Promise<void> {\n if (results.length === 0) return;\n\n for (const { id, size } of results) {\n await this.powersync.execute(\n `UPDATE ${this.tableName} SET size = ? WHERE id = ? AND state = ?`,\n [size, id, AttachmentState.SYNCED]\n );\n }\n\n this._cachedSizeTimestamp = 0;\n }\n\n private async _getIdsToDownload(): Promise<string[]> {\n const result = await this.powersync.getAll<IdRow>(\n `SELECT id FROM ${this.tableName}\n WHERE state IN (?, ?)`,\n [AttachmentState.QUEUED_DOWNLOAD, AttachmentState.QUEUED_SYNC]\n );\n return result.map((r) => r.id);\n }\n\n // ─── Eviction ──────────────────────────────────────────────────────────────\n\n private async _evictIfNeeded(): Promise<void> {\n const evictionTrigger = this._maxCacheSize * this.evictionTriggerThreshold;\n const targetSize = this._maxCacheSize * this.downloadStopThreshold;\n\n let currentSize = await this._getCachedSizeWithCache(true);\n\n if (currentSize <= evictionTrigger) return;\n\n this.logger.info('[AttachmentQueue] Starting eviction...');\n\n while (currentSize > targetSize) {\n const toEvict = await this.powersync.getAll<EvictRow>(\n `SELECT id, local_uri, size FROM ${this.tableName}\n WHERE state = ${AttachmentState.SYNCED} AND size > 0 LIMIT 100`\n );\n\n if (toEvict.length === 0) break;\n\n for (const row of toEvict) {\n if (currentSize <= targetSize) break;\n\n if (row.local_uri) {\n try {\n await this.platform.fileSystem.deleteFile(this.getLocalUri(row.local_uri));\n await this.powersync.execute(\n `UPDATE ${this.tableName}\n SET state = ?, local_uri = NULL, size = 0 WHERE id = ?`,\n [AttachmentState.QUEUED_DOWNLOAD, row.id]\n );\n currentSize -= row.size;\n } catch {\n // Skip failed deletions\n }\n }\n }\n\n this._cachedSizeTimestamp = 0;\n currentSize = await this._getCachedSizeWithCache(true);\n }\n\n this._cacheFull = currentSize > targetSize;\n this._notify(true);\n\n if (!this._cacheFull && !this._paused && !this._processing) {\n this._startDownloads();\n }\n }\n\n // ─── Compression ───────────────────────────────────────────────────────────\n\n private async _compressImage(\n localUri: string,\n id: string,\n filename: string\n ): Promise<void> {\n if (!this._isImage(filename)) return;\n if (!this.platform.imageProcessor) return;\n\n const compressionConfig = { ...DEFAULT_COMPRESSION_CONFIG, ...this.config.compression };\n if (!compressionConfig.enabled) return;\n\n try {\n const info = await this.platform.fileSystem.getFileInfo(localUri);\n if (!info || !info.exists) return;\n\n const originalSize = info.size;\n\n if (originalSize < compressionConfig.skipSizeBytes) return;\n if (originalSize < compressionConfig.targetSizeBytes) return;\n\n // Update phase\n const existing = this._downloads.get(id);\n if (existing) {\n this._downloads.set(id, { ...existing, phase: 'compressing' });\n this._notify(false);\n }\n\n const result = await this.platform.imageProcessor.compress(localUri, {\n quality: this._compressionQuality,\n maxWidth: originalSize > 1_000_000 ? compressionConfig.maxWidth : undefined,\n format: 'jpeg',\n });\n\n const compressedInfo = await this.platform.fileSystem.getFileInfo(result.uri);\n if (!compressedInfo || !compressedInfo.exists) return;\n\n if (compressedInfo.size >= originalSize) {\n await this.platform.fileSystem.deleteFile(result.uri);\n return;\n }\n\n // Replace original with compressed\n await this.platform.fileSystem.deleteFile(localUri);\n await this.platform.fileSystem.moveFile(result.uri, localUri);\n\n this.logger.info(\n `[AttachmentQueue] Compressed ${filename}: ${Math.round(originalSize / 1024)}KB → ${Math.round(compressedInfo.size / 1024)}KB`\n );\n } catch (err) {\n this.logger.warn(`[AttachmentQueue] Compression failed for ${filename}:`, err);\n }\n }\n\n // ─── Helpers ───────────────────────────────────────────────────────────────\n\n private _isImage(filename: string): boolean {\n const ext = filename.split('.').pop()?.toLowerCase();\n return ['jpg', 'jpeg', 'png', 'webp', 'heic', 'heif'].includes(ext ?? '');\n }\n\n private _getStatus(pendingCount: number): AttachmentSyncStatus {\n if (this._processing) return 'syncing';\n if (this._paused) return 'paused';\n if (this._cacheFull && pendingCount > 0) return 'cache_full';\n return 'complete';\n }\n\n private async _getCachedSizeWithCache(forceRefresh = false): Promise<number> {\n const now = Date.now();\n\n if (!forceRefresh && this._cachedSizeTimestamp > 0 && now - this._cachedSizeTimestamp < CACHE_SIZE_TTL_MS) {\n return this._cachedSize;\n }\n\n const result = await this.powersync.get<CachedSizeRow>(\n `SELECT COALESCE(SUM(size), 0) as total FROM ${this.tableName}\n WHERE state = ${AttachmentState.SYNCED}`\n );\n\n this._cachedSize = result?.total ?? 0;\n this._cachedSizeTimestamp = now;\n return this._cachedSize;\n }\n\n private async _orderByNewest(ids: string[]): Promise<string[]> {\n if (ids.length <= 3) return ids;\n\n const { table, idColumn, orderByColumn } = this.config.source;\n if (!orderByColumn) return ids;\n\n try {\n const idSet = new Set(ids);\n const ordered = await this.powersync.getAll<{ id: string }>(\n `SELECT ${idColumn} as id FROM ${table}\n WHERE ${idColumn} IS NOT NULL AND ${idColumn} != ''\n ORDER BY ${orderByColumn} DESC NULLS LAST\n LIMIT ?`,\n [ids.length * 2]\n );\n\n const result: string[] = [];\n for (const row of ordered) {\n if (idSet.has(row.id)) {\n result.push(row.id);\n idSet.delete(row.id);\n }\n }\n\n for (const id of ids) {\n if (idSet.has(id)) {\n result.push(id);\n }\n }\n\n return result;\n } catch {\n return ids;\n }\n }\n\n private _sleep(ms: number, signal: AbortSignal): Promise<void> {\n return new Promise((resolve) => {\n const onAbort = () => {\n clearTimeout(timer);\n resolve();\n };\n const timer = setTimeout(() => {\n signal.removeEventListener('abort', onAbort);\n resolve();\n }, ms);\n signal.addEventListener('abort', onAbort, { once: true });\n });\n }\n\n private _withTimeout<T>(promise: Promise<T>, ms: number, message: string): Promise<T> {\n return new Promise((resolve, reject) => {\n const timer = setTimeout(() => reject(new Error(message)), ms);\n promise.then(\n (result) => {\n clearTimeout(timer);\n resolve(result);\n },\n (err) => {\n clearTimeout(timer);\n reject(err);\n }\n );\n });\n }\n\n private async _blobToBase64(blob: Blob): Promise<string> {\n return new Promise((resolve, reject) => {\n const reader = new FileReader();\n reader.onloadend = () => {\n const base64 = (reader.result as string).split(',')[1];\n resolve(base64);\n };\n reader.onerror = reject;\n reader.readAsDataURL(blob);\n });\n }\n\n private _notify(forceImmediate = false): void {\n if (this._progressCallbacks.size === 0) return;\n\n const now = Date.now();\n const timeSinceLastNotify = now - this._lastNotifyTime;\n\n if (this._notifyTimer) {\n clearTimeout(this._notifyTimer);\n this._notifyTimer = null;\n }\n\n const notifyAll = (stats: AttachmentSyncStats) => {\n for (const cb of this._progressCallbacks) {\n try {\n cb(stats);\n } catch (err) {\n this.logger.warn('[AttachmentQueue] Callback error:', err);\n }\n }\n };\n\n if (forceImmediate || timeSinceLastNotify >= NOTIFY_THROTTLE_MS) {\n this._lastNotifyTime = now;\n this.getStats().then(notifyAll).catch(() => {});\n } else {\n const delay = NOTIFY_THROTTLE_MS - timeSinceLastNotify;\n this._notifyTimer = setTimeout(() => {\n this._notifyTimer = null;\n this._lastNotifyTime = Date.now();\n this.getStats().then(notifyAll).catch(() => {});\n }, delay);\n }\n }\n}\n"],"mappings":"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","names":["AttachmentState"]}
|