@pol-studios/powersync 1.0.22 → 1.0.24
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.d.ts +51 -17
- package/dist/attachments/index.js +6 -2
- package/dist/{chunk-PG2NPQG3.js → chunk-55DKCJV4.js} +25 -5
- package/dist/chunk-55DKCJV4.js.map +1 -0
- package/dist/{chunk-IMRSLJRV.js → chunk-BGBQYQV3.js} +129 -38
- package/dist/chunk-BGBQYQV3.js.map +1 -0
- package/dist/{chunk-ZM4ENYMF.js → chunk-C5ODS3XH.js} +51 -8
- package/dist/chunk-C5ODS3XH.js.map +1 -0
- package/dist/{chunk-4TXTAEF2.js → chunk-CACKC6XG.js} +3 -2
- package/dist/chunk-CACKC6XG.js.map +1 -0
- package/dist/{chunk-XOCIONAA.js → chunk-TIFL2KWE.js} +3 -3
- package/dist/{chunk-N4K7E53V.js → chunk-YVX3A36I.js} +4 -4
- package/dist/connector/index.d.ts +1 -1
- package/dist/connector/index.js +1 -1
- package/dist/index.d.ts +3 -3
- package/dist/index.js +10 -6
- package/dist/index.native.d.ts +3 -3
- package/dist/index.native.js +10 -6
- package/dist/index.web.d.ts +3 -3
- package/dist/index.web.js +10 -6
- package/dist/{pol-attachment-queue-BVAIueoP.d.ts → pol-attachment-queue-BE2HU3Us.d.ts} +71 -7
- package/dist/provider/index.d.ts +2 -2
- package/dist/provider/index.js +4 -4
- package/dist/react/index.d.ts +1 -1
- package/dist/react/index.js +3 -3
- package/dist/{supabase-connector-WuiFiBnV.d.ts → supabase-connector-D2oIl2t8.d.ts} +13 -2
- package/dist/sync/index.d.ts +3 -0
- package/dist/sync/index.js +1 -1
- package/package.json +14 -4
- package/dist/chunk-4TXTAEF2.js.map +0 -1
- package/dist/chunk-IMRSLJRV.js.map +0 -1
- package/dist/chunk-PG2NPQG3.js.map +0 -1
- package/dist/chunk-ZM4ENYMF.js.map +0 -1
- /package/dist/{chunk-XOCIONAA.js.map → chunk-TIFL2KWE.js.map} +0 -0
- /package/dist/{chunk-N4K7E53V.js.map → chunk-YVX3A36I.js.map} +0 -0
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"sources":["../src/attachments/pol-storage-adapter.ts","../src/attachments/types.ts","../src/attachments/state-machine.ts","../src/utils/mimeTypes.ts","../src/attachments/download-manager.ts","../src/attachments/upload-manager.ts","../src/attachments/cache-manager.ts","../src/attachments/pol-attachment-queue.ts"],"sourcesContent":["/**\n * POL Storage Adapter\n *\n * Implements the official @powersync/attachments StorageAdapter interface,\n * bridging our PlatformAdapter file system and remote storage adapters.\n *\n * This adapter combines:\n * - Local file operations via PlatformAdapter.fileSystem\n * - Remote upload/download via AttachmentStorageAdapter (e.g., Supabase)\n */\n\nimport { EncodingType, StorageAdapter } from '@powersync/attachments';\nimport type { PlatformAdapter } from '../platform/types';\nimport type { AttachmentStorageAdapter } from './types';\n\n// Re-export EncodingType for convenience\nexport { EncodingType };\nexport interface PolStorageAdapterOptions {\n /**\n * Platform adapter for local file system operations.\n */\n platform: PlatformAdapter;\n\n /**\n * Remote storage adapter for upload/download operations.\n */\n remoteStorage: AttachmentStorageAdapter;\n\n /**\n * Base directory name for attachments within the cache directory.\n * Defaults to 'attachments'.\n */\n attachmentDirectoryName?: string;\n\n /**\n * Optional logger for diagnostic messages.\n * If provided, will log warnings when deprecated code paths are hit.\n */\n logger?: {\n warn(message: string, ...args: unknown[]): void;\n };\n}\n\n/**\n * Storage adapter that implements the official @powersync/attachments interface.\n *\n * Combines local file system operations (via PlatformAdapter) with remote\n * storage operations (via AttachmentStorageAdapter like Supabase).\n *\n * @example\n * ```typescript\n * const storageAdapter = new PolStorageAdapter({\n * platform: platformAdapter,\n * remoteStorage: supabaseStorageAdapter,\n * });\n * ```\n */\nexport class PolStorageAdapter implements StorageAdapter {\n private readonly platform: PlatformAdapter;\n private readonly remoteStorage: AttachmentStorageAdapter;\n private readonly attachmentDirectoryName: string;\n private readonly userStorageDirectory: string;\n private readonly logger?: {\n warn(message: string, ...args: unknown[]): void;\n };\n constructor(options: PolStorageAdapterOptions) {\n this.platform = options.platform;\n this.remoteStorage = options.remoteStorage;\n this.attachmentDirectoryName = options.attachmentDirectoryName ?? 'attachments';\n this.logger = options.logger;\n\n // User storage directory is the cache directory + attachment directory name\n // Must end with a '/' per the interface contract\n const cacheDir = this.platform.fileSystem.getCacheDirectory();\n this.userStorageDirectory = cacheDir.endsWith('/') ? `${cacheDir}${this.attachmentDirectoryName}/` : `${cacheDir}/${this.attachmentDirectoryName}/`;\n }\n\n // ─── Remote Operations ──────────────────────────────────────────────────────\n\n /**\n * Upload a file to remote storage.\n */\n async uploadFile(filePath: string, data: ArrayBuffer, options?: {\n mediaType?: string;\n }): Promise<void> {\n if (!this.remoteStorage.uploadFile) {\n throw new Error('Remote storage adapter does not support uploads');\n }\n\n // Convert ArrayBuffer to base64 for our upload interface\n const base64 = this._arrayBufferToBase64(data);\n await this.remoteStorage.uploadFile(filePath, base64);\n }\n\n /**\n * Download a file from remote storage.\n *\n * NOTE: This method implements the official @powersync/attachments StorageAdapter\n * interface which requires returning Promise<Blob>. For the memory-optimized\n * file path passthrough, PolAttachmentQueue.downloadRecord() calls\n * remoteStorage.downloadFile() directly to get the file:// path.\n *\n * This method handles base64 string → Blob conversion for backward compatibility.\n */\n async downloadFile(filePath: string): Promise<Blob> {\n const result = await this.remoteStorage.downloadFile(filePath);\n\n // Handle base64 string (convert to Blob)\n if (typeof result === 'string') {\n // If it's a file:// path, we need to read it and convert to Blob\n // (This path is typically not taken since PolAttachmentQueue calls remoteStorage directly)\n if (result.startsWith('file://')) {\n // PRODUCTION WARNING: This path reads the entire file into memory!\n // If you see this warning in production logs, it means a code path is calling\n // PolStorageAdapter.downloadFile() instead of using remoteStorage directly.\n // The optimized path in PolAttachmentQueue.downloadRecord() bypasses this entirely.\n this.logger?.warn('[PolStorageAdapter] PERFORMANCE WARNING: file:// path hit in downloadFile() - ' + 'reading entire file into memory. This should be avoided; use direct file copy instead. ' + `File: ${filePath}`);\n // Read file as base64 and convert to Blob\n const base64Content = await this.platform.fileSystem.readFile(result, 'base64');\n const binaryString = atob(base64Content);\n const bytes = new Uint8Array(binaryString.length);\n for (let i = 0; i < binaryString.length; i++) {\n bytes[i] = binaryString.charCodeAt(i);\n }\n return new Blob([bytes]);\n }\n\n // Regular base64 string\n const binaryString = atob(result);\n const bytes = new Uint8Array(binaryString.length);\n for (let i = 0; i < binaryString.length; i++) {\n bytes[i] = binaryString.charCodeAt(i);\n }\n return new Blob([bytes]);\n }\n return result; // Already a Blob\n }\n\n // ─── Local File Operations ──────────────────────────────────────────────────\n\n /**\n * Write data to a local file.\n */\n async writeFile(fileUri: string, base64Data: string, options?: {\n encoding?: EncodingType;\n }): Promise<void> {\n const encoding = options?.encoding === EncodingType.UTF8 ? 'utf8' : 'base64';\n await this.platform.fileSystem.writeFile(fileUri, base64Data, encoding);\n }\n\n /**\n * Read a local file's contents.\n */\n async readFile(fileUri: string, options?: {\n encoding?: EncodingType;\n mediaType?: string;\n }): Promise<ArrayBuffer> {\n const encoding = options?.encoding === EncodingType.UTF8 ? 'utf8' : 'base64';\n const content = await this.platform.fileSystem.readFile(fileUri, encoding);\n\n // Convert base64 or UTF8 string to ArrayBuffer\n if (encoding === 'base64') {\n return this._base64ToArrayBuffer(content);\n } else {\n // UTF8 string to ArrayBuffer\n const encoder = new TextEncoder();\n return encoder.encode(content).buffer;\n }\n }\n\n /**\n * Delete a local file.\n */\n async deleteFile(uri: string, _options?: {\n filename?: string;\n }): Promise<void> {\n try {\n await this.platform.fileSystem.deleteFile(uri);\n } catch (error) {\n // Ignore errors if file doesn't exist\n const info = await this.platform.fileSystem.getFileInfo(uri);\n if (info?.exists) {\n throw error;\n }\n }\n }\n\n /**\n * Check if a local file exists.\n */\n async fileExists(fileUri: string): Promise<boolean> {\n const info = await this.platform.fileSystem.getFileInfo(fileUri);\n return info?.exists ?? false;\n }\n\n /**\n * Create a directory (with intermediate directories).\n */\n async makeDir(uri: string): Promise<void> {\n await this.platform.fileSystem.makeDirectory(uri, {\n intermediates: true\n });\n }\n\n /**\n * Copy a file from source to destination.\n */\n async copyFile(sourceUri: string, targetUri: string): Promise<void> {\n await this.platform.fileSystem.copyFile(sourceUri, targetUri);\n }\n\n /**\n * Get the user's storage directory for attachments.\n * Returns the cache directory path ending with '/'.\n */\n getUserStorageDirectory(): string {\n return this.userStorageDirectory;\n }\n\n // ─── Helpers ────────────────────────────────────────────────────────────────\n\n /**\n * Warning 8: Optimized base64 encoding using batch processing.\n * Processes in chunks of 1024 bytes for better performance with large files.\n */\n private _arrayBufferToBase64(buffer: ArrayBuffer): string {\n const bytes = new Uint8Array(buffer);\n const chunkSize = 1024;\n const chunks: string[] = [];\n for (let i = 0; i < bytes.length; i += chunkSize) {\n const chunk = bytes.subarray(i, Math.min(i + chunkSize, bytes.length));\n chunks.push(String.fromCharCode.apply(null, Array.from(chunk)));\n }\n return btoa(chunks.join(''));\n }\n private _base64ToArrayBuffer(base64: string): ArrayBuffer {\n const binaryString = atob(base64);\n const bytes = new Uint8Array(binaryString.length);\n for (let i = 0; i < binaryString.length; i++) {\n bytes[i] = binaryString.charCodeAt(i);\n }\n return bytes.buffer;\n }\n}","/**\n * Attachment Queue Types for @pol-studios/powersync\n *\n * This module re-exports types from the official @powersync/attachments package\n * and extends them with POL-specific features like:\n * - FAILED_PERMANENT state for permanent upload failures\n * - Upload metadata fields for retry tracking\n * - Source table configuration for flexible attachment watching\n * - Image compression configuration\n *\n * IMPORTANT: The official AttachmentState enum has different numeric values:\n * Official: QUEUED_SYNC=0, QUEUED_UPLOAD=1, QUEUED_DOWNLOAD=2, SYNCED=3, ARCHIVED=4\n *\n * We extend this with FAILED_PERMANENT=5 for permanent upload failures.\n */\n\n// ─── Re-exports from @powersync/attachments ──────────────────────────────────\n\n// Official types (import with explicit rename to avoid conflicts with our extensions)\nexport { ATTACHMENT_TABLE, type AttachmentRecord as OfficialAttachmentRecord, AttachmentState, AttachmentTable, type AttachmentTableOptions } from \"@powersync/attachments\";\nexport { EncodingType, type StorageAdapter } from \"@powersync/attachments\";\nexport { AbstractAttachmentQueue, type AttachmentQueueOptions as BaseAttachmentQueueOptions, DEFAULT_ATTACHMENT_QUEUE_OPTIONS } from \"@powersync/attachments\";\n\n// PowerSync database type for watchIds callback\nimport type { AbstractPowerSyncDatabase as PowerSyncDBInterface } from \"@powersync/common\";\nexport type { PowerSyncDBInterface };\n\n// ─── POL Extended Attachment State ───────────────────────────────────────────\n\n/**\n * Extended attachment state that includes permanent failure state.\n *\n * Values 0-4 match the official @powersync/attachments AttachmentState.\n * Value 5 (FAILED_PERMANENT) is our extension for uploads that have\n * exhausted retries or encountered unrecoverable errors.\n *\n * @example\n * ```typescript\n * import { AttachmentState } from '@powersync/attachments';\n * import { PolAttachmentState } from '@pol-studios/powersync/attachments';\n *\n * // Use official states\n * const queued = AttachmentState.QUEUED_UPLOAD;\n *\n * // Use our extension\n * const failed = PolAttachmentState.FAILED_PERMANENT;\n * ```\n */\nexport enum PolAttachmentState {\n /** Check if the attachment needs to be uploaded or downloaded */\n QUEUED_SYNC = 0,\n /** Attachment to be uploaded */\n QUEUED_UPLOAD = 1,\n /** Attachment to be downloaded */\n QUEUED_DOWNLOAD = 2,\n /** Attachment has been synced */\n SYNCED = 3,\n /** Attachment has been orphaned, i.e. the associated record has been deleted */\n ARCHIVED = 4,\n /** Permanently failed (exhausted retries or unrecoverable error) - POL extension */\n FAILED_PERMANENT = 5,\n /** Download was skipped due to downloadFilter returning false - POL extension */\n DOWNLOAD_SKIPPED = 6,\n}\n\n// ─── POL Extended Attachment Record ──────────────────────────────────────────\n\n/**\n * Extended attachment record with POL-specific upload tracking fields.\n *\n * Extends the official AttachmentRecord with fields for:\n * - Upload retry tracking\n * - Error information\n * - Upload metadata for onComplete callbacks\n */\nexport interface PolAttachmentRecord {\n /** Unique identifier (typically storage path) */\n id: string;\n /** Filename for display and type inference */\n filename: string;\n /** Local file URI (set after download/upload) */\n local_uri?: string | null;\n /** File size in bytes */\n size?: number;\n /** MIME type of the file */\n media_type?: string;\n /** Timestamp when the attachment was created/modified */\n timestamp?: number;\n /** Current state in the queue */\n state: number; // AttachmentState or PolAttachmentState\n\n // ─── Upload-specific fields (POL extensions) ────────────────────────────────\n\n /** Local file URI in managed cache (for uploads) */\n upload_source_uri?: string | null;\n /** Last upload error message */\n upload_error?: string | null;\n /** HTTP status or error code */\n upload_error_code?: string | null;\n /** Number of upload retry attempts */\n upload_retry_count?: number;\n /** Timestamp for next retry */\n upload_next_retry_at?: number;\n /** JSON-serialized entity metadata for onComplete */\n upload_metadata?: string | null;\n /** Target storage bucket ID */\n upload_bucket_id?: string | null;\n}\n\n// ─── Legacy Watch Configuration (for query-builder backward compatibility) ───\n\n/**\n * Configuration for watching a source table.\n * Used by query-builder utilities to generate SQL queries.\n *\n * Note: Either `pathColumn` or `idColumn` must be provided.\n * `pathColumn` is the preferred option; `idColumn` is deprecated but still supported.\n */\nexport interface WatchConfig {\n /** Source table name */\n table: string;\n /**\n * Column containing attachment path/ID.\n * Preferred over `idColumn`.\n */\n pathColumn?: string;\n /**\n * Column containing attachment ID/path.\n * @deprecated Use `pathColumn` instead. Will be removed in a future version.\n */\n idColumn?: string;\n /** Additional columns to include in context */\n selectColumns?: string[];\n /** Optional WHERE clause fragment (e.g., \"storagePath IS NOT NULL\") */\n where?: string;\n /** Order by column and direction */\n orderBy?: {\n column: string;\n direction: \"ASC\" | \"DESC\";\n };\n}\n\n// ─── Simple Attachment Configuration ──────────────────────────────────────────\n\n/**\n * Simplified attachment configuration for single-table use cases.\n * Use this when you have a single table with attachment paths and don't need\n * complex JOIN queries or custom watch logic.\n *\n * @example\n * ```typescript\n * const config: SimpleAttachmentConfig = {\n * bucket: 'photos',\n * table: 'EquipmentUnitMediaContent',\n * pathColumn: 'storagePath',\n * };\n * ```\n */\nexport interface SimpleAttachmentConfig {\n /** Storage bucket name for this attachment source */\n bucket: string;\n /** Source table name */\n table: string;\n /** Column containing the attachment path/ID */\n pathColumn: string;\n}\n\n/**\n * Legacy context for batch filtering of attachments.\n * @deprecated Use SkipDownloadContext with skipDownload callback instead.\n */\nexport interface BatchFilterContext {\n /** All pending attachment IDs */\n ids: string[];\n /** Source records from watch config (ID → record) */\n records: Map<string, Record<string, unknown>>;\n /** Database access for batch queries */\n db: {\n getAll<T>(sql: string, params?: unknown[]): Promise<T[]>;\n getOptional<T>(sql: string, params?: unknown[]): Promise<T | null>;\n };\n}\n\n// ─── Attachment Source Configuration (2-Callback API) ────────────────────────\n\n/**\n * Configuration for an attachment source.\n *\n * This is a minimal, framework-agnostic interface for configuring attachment sync.\n * It uses a reactive callback pattern that works with any data layer.\n *\n * @example\n * ```typescript\n * const config: AttachmentSourceConfig = {\n * bucket: 'project-assets',\n *\n * // Reactive source of attachment IDs\n * watchIds: (db, onUpdate) => {\n * // Using PowerSync's watch API\n * db.watch('SELECT storagePath FROM photos WHERE storagePath IS NOT NULL', [], {\n * onResult: (results) => {\n * onUpdate(results.rows._array.map(r => r.storagePath));\n * }\n * });\n * },\n *\n * // Optional: skip downloading videos\n * skipDownload: async ({ ids, db }) => {\n * const videos = await db.getAll<{ path: string }>(\n * `SELECT storagePath as path FROM photos WHERE storagePath IN (${ids.map(() => '?').join(',')}) AND mediaType LIKE 'video/%'`,\n * ids\n * );\n * return videos.map(v => v.path);\n * }\n * };\n * ```\n */\nexport interface AttachmentSourceConfig {\n /**\n * Storage bucket name for this attachment source.\n * Maps to the Supabase Storage bucket (or equivalent in other backends).\n */\n bucket: string;\n\n /**\n * Reactive source of attachment IDs. Called once during initialization.\n *\n * This callback should set up a reactive subscription that calls `onUpdate`\n * whenever the set of attachment IDs changes. The queue will:\n * - Queue downloads for new IDs on first emission\n * - Queue downloads for IDs that appear in subsequent emissions\n * - Auto-archive attachments whose IDs disappear from emissions\n *\n * For simple single-table cases, you can use `table` and `pathColumn` instead\n * of providing a custom `watchIds` callback.\n *\n * @param db - PowerSync database instance for querying\n * @param onUpdate - Callback to emit current set of attachment IDs\n * @returns Optional cleanup function to dispose of the watch subscription\n *\n * @example\n * ```typescript\n * watchIds: (db, onUpdate) => {\n * const abort = new AbortController();\n * db.watch(\n * 'SELECT storagePath FROM photos WHERE storagePath IS NOT NULL',\n * [],\n * { onResult: (r) => onUpdate(r.rows._array.map(row => row.storagePath)) },\n * { signal: abort.signal }\n * );\n * return () => abort.abort(); // Cleanup function\n * }\n * ```\n */\n watchIds?: (db: PowerSyncDBInterface, onUpdate: (ids: string[]) => void) => (() => void) | void;\n\n /**\n * Source table name for simplified configuration.\n * Use with `pathColumn` as an alternative to providing a custom `watchIds` callback.\n * Only for simple single-table cases without JOINs.\n *\n * @example\n * ```typescript\n * const config: AttachmentSourceConfig = {\n * bucket: 'photos',\n * table: 'EquipmentUnitMediaContent',\n * pathColumn: 'storagePath',\n * };\n * ```\n */\n table?: string;\n\n /**\n * Column containing the attachment path/ID for simplified configuration.\n * Use with `table` as an alternative to providing a custom `watchIds` callback.\n *\n * @example\n * ```typescript\n * const config: AttachmentSourceConfig = {\n * bucket: 'photos',\n * table: 'EquipmentUnitMediaContent',\n * pathColumn: 'storagePath',\n * };\n * ```\n */\n pathColumn?: string;\n\n /**\n * Optional batch filter to skip downloading certain attachments.\n *\n * Called with all pending attachment IDs. Return the IDs that should\n * be SKIPPED (not downloaded). Useful for:\n * - Filtering out video files on mobile\n * - Skipping large files\n * - Conditional download based on user preferences\n *\n * @param context - Contains pending IDs and database access\n * @returns Promise resolving to array of IDs to skip\n *\n * @example\n * ```typescript\n * skipDownload: async ({ ids, db }) => {\n * // Skip video files\n * const videos = await db.getAll<{ id: string }>(\n * `SELECT storagePath as id FROM photos\n * WHERE storagePath IN (${ids.map(() => '?').join(',')})\n * AND mediaType LIKE 'video/%'`,\n * ids\n * );\n * return videos.map(v => v.id);\n * }\n * ```\n */\n skipDownload?: (context: SkipDownloadContext) => Promise<string[]>;\n}\n\n/**\n * Context passed to the skipDownload callback.\n */\nexport interface SkipDownloadContext {\n /** All pending attachment IDs to evaluate */\n ids: string[];\n\n /** PowerSync database instance for queries */\n db: PowerSyncDBInterface;\n}\n\n/**\n * Full attachment configuration including source configs and handlers.\n * This extends the source config with upload/download handlers and callbacks.\n */\nexport interface AttachmentConfig {\n /** Bucket name in Supabase storage */\n bucket: string;\n\n /**\n * Reactive source of attachment IDs. Called once during initialization.\n * May return an optional cleanup function to dispose of the watch subscription.\n * @see AttachmentSourceConfig.watchIds\n */\n watchIds: AttachmentSourceConfig[\"watchIds\"];\n\n /**\n * Optional batch filter to skip downloading certain attachments.\n * @see AttachmentSourceConfig.skipDownload\n */\n skipDownload?: AttachmentSourceConfig[\"skipDownload\"];\n\n /** Optional: callback when upload completes */\n onUploadComplete?: (attachment: AttachmentRecord) => void;\n\n /** Optional: callback when upload fails */\n onUploadFailed?: (attachment: AttachmentRecord, error: Error) => void;\n\n /** Optional: max cache size in bytes */\n maxCacheBytes?: number;\n\n /** Optional: remote storage adapter for downloads */\n remoteStorage?: AttachmentStorageAdapter;\n\n /** Optional: upload handler for uploads */\n uploadHandler?: UploadHandler;\n\n /**\n * Optional: image compression settings for downloads.\n * Uses Supabase's transform feature to resize/compress images on download.\n * Only applies to supported image formats (jpg, png, webp, avif, gif, heic).\n */\n compression?: Partial<CompressionConfig>;\n\n /**\n * Optional: download configuration for performance tuning.\n * Adjusts concurrency and timeout for downloads.\n * Higher concurrency = faster but more memory/network usage.\n */\n download?: Partial<DownloadConfig>;\n}\n\n// ─── Storage Adapter ─────────────────────────────────────────────────────────\n\n/**\n * Interface for remote attachment storage operations (e.g., Supabase Storage).\n *\n * This is used by PolStorageAdapter to handle remote file operations.\n * For local file operations, we use PlatformAdapter.fileSystem.\n */\nexport interface AttachmentStorageAdapter {\n /**\n * Download a file from remote storage.\n *\n * This method can return different types depending on the implementation:\n *\n * 1. **Blob** (legacy behavior): The file data as a Blob object.\n * Used by web platforms or legacy adapters. This path loads the entire\n * file into memory.\n *\n * 2. **Base64 string**: The file data encoded as a base64 string.\n * Will be converted to Blob by the caller.\n *\n * 3. **file:// URI string** (optimized path): A `file://` URI pointing to a\n * temp file on the local filesystem. This is the **preferred return type**\n * for native platforms as it enables zero-memory-overhead downloads.\n * When this is returned, PolAttachmentQueue.downloadRecord() uses direct\n * file copy instead of loading the file into JS memory, avoiding ~4x\n * memory overhead and OOM crashes on large files.\n *\n * @param filePath - The storage path of the file\n * @returns The file data as a Blob, base64 string, or file:// URI to a temp file\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 (base64 string or Blob)\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// ─── Upload Configuration ────────────────────────────────────────────────────\n\n/**\n * Configuration for the upload engine.\n */\nexport interface UploadConfig {\n /** Maximum concurrent uploads (default: 3) */\n concurrency: number;\n /** Upload timeout per file in ms (default: 120000) */\n timeoutMs: number;\n /** Base retry delay in ms (default: 5000) */\n baseRetryDelayMs: number;\n /** Maximum retry delay in ms (default: 3600000 = 1 hour) */\n maxRetryDelayMs: number;\n /** Days before marking upload as stale (default: 7) */\n staleDaysThreshold: number;\n /** Maximum number of retry attempts before marking as FAILED_PERMANENT (default: 100) */\n maxRetryCount: number;\n}\n\n/**\n * Default upload configuration.\n *\n * Note: baseRetryDelayMs is set to 30s (not 5s) to reduce battery consumption.\n * The exponential backoff sequence will be: 30s, 60s, 2min, 4min, 8min, 16min, 32min, 1hr (capped).\n */\nexport const DEFAULT_UPLOAD_CONFIG: UploadConfig = {\n concurrency: 5,\n timeoutMs: 120_000,\n baseRetryDelayMs: 30_000,\n // 30 seconds base delay for battery efficiency\n maxRetryDelayMs: 3_600_000,\n // 1 hour max\n staleDaysThreshold: 7,\n maxRetryCount: 100\n};\n\n// ─── Upload Handler ──────────────────────────────────────────────────────────\n\n/**\n * Interface for handling file uploads to remote storage.\n * Separate from AttachmentStorageAdapter to allow different implementations.\n */\nexport interface UploadHandler {\n /**\n * Upload a file to remote storage.\n * @param storagePath - The storage path to upload to\n * @param localFileUri - Local file URI to upload from\n * @param mediaType - MIME type of the file\n * @param signal - Optional AbortSignal for cancellation support\n */\n uploadFile(storagePath: string, localFileUri: string, mediaType: string, signal?: AbortSignal): Promise<void>;\n\n /**\n * Optional: resolve the storage bucket for a file path.\n * @param storagePath - The file path to resolve\n * @returns The bucket name\n */\n resolveBucket?(storagePath: string): string;\n}\n\n// ─── Download Configuration ──────────────────────────────────────────────────\n\n/**\n * Configuration for the download engine.\n *\n * Note: These settings are for developers tuning performance.\n * Higher concurrency can speed up downloads but uses more memory and network.\n */\nexport interface DownloadConfig {\n /** Maximum concurrent downloads (default: 3) */\n concurrency: number;\n /** Download timeout per file in ms (default: 120000 = 2 minutes) */\n timeoutMs: number;\n}\n\n/**\n * Default download configuration.\n */\nexport const DEFAULT_DOWNLOAD_CONFIG: DownloadConfig = {\n concurrency: 3,\n timeoutMs: 120_000\n};\n\n// ─── Cache Configuration ─────────────────────────────────────────────────────\n\n/**\n * Configuration for cache management.\n *\n * By default, cache size is unlimited. Set `maxSize` to enable size-based eviction.\n */\nexport interface CacheConfig {\n /** Maximum cache size in bytes. Default: unlimited (Number.MAX_SAFE_INTEGER) */\n maxSize: number;\n /** Stop downloads at this percentage of max (default: 0.95 = 95%). Only applies when maxSize is set. */\n downloadStopThreshold: number;\n /** Trigger eviction at this percentage (default: 1.0 = 100%). Only applies when maxSize is set. */\n evictionTriggerThreshold: number;\n}\n\n/**\n * Default cache configuration.\n *\n * Cache size is unlimited by default. Users can set `maxSize` to enable\n * size-based eviction when storage space is a concern.\n */\nexport const DEFAULT_CACHE_CONFIG: CacheConfig = {\n maxSize: Number.MAX_SAFE_INTEGER,\n // Unlimited by default\n downloadStopThreshold: 0.95,\n evictionTriggerThreshold: 1.0\n};\n\n/**\n * Predefined cache size presets in bytes.\n * Use these constants for user-facing cache limit settings.\n *\n * @example\n * ```typescript\n * // In settings UI\n * const options = [\n * { label: '250 MB', value: CACHE_SIZE_PRESETS.MB_250 },\n * { label: '500 MB', value: CACHE_SIZE_PRESETS.MB_500 },\n * { label: '1 GB', value: CACHE_SIZE_PRESETS.GB_1 },\n * { label: '2 GB', value: CACHE_SIZE_PRESETS.GB_2 },\n * { label: '5 GB', value: CACHE_SIZE_PRESETS.GB_5 },\n * { label: 'Unlimited', value: CACHE_SIZE_PRESETS.UNLIMITED },\n * ];\n *\n * // In config\n * cache: { maxSize: CACHE_SIZE_PRESETS.GB_1 }\n * ```\n */\nexport const CACHE_SIZE_PRESETS = {\n /** 250 MB */\n MB_250: 250 * 1024 * 1024,\n /** 500 MB */\n MB_500: 500 * 1024 * 1024,\n /** 1 GB */\n GB_1: 1 * 1024 * 1024 * 1024,\n /** 2 GB */\n GB_2: 2 * 1024 * 1024 * 1024,\n /** 5 GB */\n GB_5: 5 * 1024 * 1024 * 1024,\n /** Unlimited (no cache eviction) */\n UNLIMITED: Number.MAX_SAFE_INTEGER\n} as const;\n\n/**\n * Type for cache size preset keys.\n */\nexport type CacheSizePreset = keyof typeof CACHE_SIZE_PRESETS;\n\n/**\n * Type for cache size preset values.\n */\nexport type CacheSizeValue = (typeof CACHE_SIZE_PRESETS)[CacheSizePreset];\n\n/**\n * Helper to format bytes as human-readable string.\n *\n * @example\n * ```typescript\n * formatCacheSize(CACHE_SIZE_PRESETS.GB_1) // \"1 GB\"\n * formatCacheSize(CACHE_SIZE_PRESETS.MB_500) // \"500 MB\"\n * formatCacheSize(CACHE_SIZE_PRESETS.UNLIMITED) // \"Unlimited\"\n * ```\n */\nexport function formatCacheSize(bytes: number): string {\n if (bytes === Number.MAX_SAFE_INTEGER) return 'Unlimited';\n if (bytes >= 1024 * 1024 * 1024) {\n const gb = bytes / (1024 * 1024 * 1024);\n return `${gb % 1 === 0 ? gb : gb.toFixed(1)} GB`;\n }\n if (bytes >= 1024 * 1024) {\n const mb = bytes / (1024 * 1024);\n return `${mb % 1 === 0 ? mb : mb.toFixed(1)} MB`;\n }\n if (bytes >= 1024) {\n const kb = bytes / 1024;\n return `${kb % 1 === 0 ? kb : kb.toFixed(1)} KB`;\n }\n return `${bytes} bytes`;\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// ─── Upload Status Types ─────────────────────────────────────────────────────\n\n/**\n * Current phase of an upload operation.\n */\nexport type UploadPhase = \"uploading\" | \"waiting\" | \"complete\" | \"error\" | \"permanent_failure\";\n\n/**\n * Status of an individual upload.\n */\nexport interface UploadStatus {\n /** Attachment ID */\n id: string;\n /** Filename being uploaded */\n filename: string;\n /** Current phase */\n phase: UploadPhase;\n /** Upload progress (0-100) */\n progress?: number;\n /** Error message if failed */\n error?: string;\n}\n\n// ─── Sync Status Types ───────────────────────────────────────────────────────\n\n/**\n * Why downloads are stopped (if not actively syncing).\n */\nexport type AttachmentSyncStatus = \"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 /** Number of uploads waiting to be processed */\n pendingUploadCount: number;\n /** Number of permanently failed uploads */\n failedPermanentCount: number;\n /** Number of uploads failing > staleDaysThreshold */\n staleUploadCount: number;\n /** Currently active uploads */\n activeUploads: UploadStatus[];\n /** Number of attachments skipped by downloadFilter */\n skippedDownloadCount: number;\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/**\n * Alias for PolAttachmentRecord.\n * Use PolAttachmentRecord for new code. The official @powersync/attachments\n * AttachmentRecord is exported as OfficialAttachmentRecord.\n */\nexport type AttachmentRecord = PolAttachmentRecord;","/**\n * Attachment State Machine\n *\n * Defines state transitions, protected states, and validation logic\n * for the POL attachment queue system.\n *\n * @module attachments/state-machine\n */\n\nimport { AttachmentState } from '@powersync/attachments';\nimport { PolAttachmentState } from './types';\n\n// ─── Constants ───────────────────────────────────────────────────────────────\n\n/**\n * States that represent active upload workflows.\n * Records in these states should NEVER be demoted to download states.\n *\n * This fixes a race condition in the parent AbstractAttachmentQueue.watchAttachmentIds():\n * - Upload records have `local_uri = null` (they use `upload_source_uri` instead)\n * - When the CRUD record syncs back, `onAttachmentIdsChange` fires\n * - Parent checks `record.local_uri == null` → true for uploads\n * - Parent changes state from QUEUED_UPLOAD to QUEUED_DOWNLOAD\n * - Upload is abandoned, download fails (file doesn't exist in remote storage yet)\n *\n * By protecting these states, we prevent the parent's logic from interfering\n * with in-progress or queued uploads.\n *\n * @see https://github.com/powersync-ja/powersync-js/blob/main/packages/attachments/src/AbstractAttachmentQueue.ts\n */\nexport const PROTECTED_UPLOAD_STATES = new Set<number>([AttachmentState.QUEUED_UPLOAD, PolAttachmentState.FAILED_PERMANENT]);\n\n/**\n * States that indicate a download is pending or in progress.\n */\nexport const PENDING_DOWNLOAD_STATES = new Set<number>([AttachmentState.QUEUED_DOWNLOAD, AttachmentState.QUEUED_SYNC]);\n\n/**\n * States that indicate the attachment is locally available.\n */\nexport const LOCALLY_AVAILABLE_STATES = new Set<number>([AttachmentState.SYNCED, AttachmentState.QUEUED_UPLOAD]);\n\n// ─── State Transition Helpers ────────────────────────────────────────────────\n\n/**\n * Check if a record is in a protected upload state.\n * Protected states should not be demoted to download states.\n */\nexport function isProtectedUploadState(state: number): boolean {\n return PROTECTED_UPLOAD_STATES.has(state);\n}\n\n/**\n * Check if a record is in a pending download state.\n */\nexport function isPendingDownloadState(state: number): boolean {\n return PENDING_DOWNLOAD_STATES.has(state);\n}\n\n/**\n * Check if a record has a locally available file.\n */\nexport function isLocallyAvailable(state: number): boolean {\n return LOCALLY_AVAILABLE_STATES.has(state);\n}\n\n/**\n * Check if a state transition is allowed.\n * Prevents protected upload states from being demoted to download states.\n *\n * @param currentState - The current state of the record\n * @param newState - The proposed new state\n * @returns true if the transition is allowed\n */\nexport function isStateTransitionAllowed(currentState: number, newState: number): boolean {\n // Never allow protected upload states to be demoted to download states\n if (isProtectedUploadState(currentState) && isPendingDownloadState(newState)) {\n return false;\n }\n return true;\n}\n\n/**\n * Get the appropriate state for a new attachment based on context.\n *\n * @param hasLocalUri - Whether the attachment has a local URI\n * @param hasUploadSourceUri - Whether the attachment has an upload source URI\n * @param currentState - The current state (if any)\n * @returns The appropriate state for the attachment\n */\nexport function determineAttachmentState(hasLocalUri: boolean, hasUploadSourceUri: boolean, currentState?: number): number {\n // If in a protected state, preserve it\n if (currentState !== undefined && isProtectedUploadState(currentState)) {\n return currentState;\n }\n\n // If has upload source, it's an upload\n if (hasUploadSourceUri) {\n return AttachmentState.QUEUED_UPLOAD;\n }\n\n // If has local URI, it's synced\n if (hasLocalUri) {\n return AttachmentState.SYNCED;\n }\n\n // Otherwise, needs download\n return AttachmentState.QUEUED_DOWNLOAD;\n}\n\n// ─── SQL Validation ──────────────────────────────────────────────────────────\n\n/**\n * Validate that a string is a safe SQL identifier.\n * Prevents SQL injection via config values.\n *\n * @param value - The value to validate\n * @param name - The name of the parameter (for error messages)\n * @throws Error if the value is not a valid SQL identifier\n */\nexport function validateSqlIdentifier(value: string, name: string): void {\n if (!/^[a-zA-Z_][a-zA-Z0-9_]*$/.test(value)) {\n throw new Error(`[PolAttachmentQueue] Invalid SQL identifier for ${name}: \"${value}\". ` + `Must match /^[a-zA-Z_][a-zA-Z0-9_]*$/`);\n }\n}\n\n/**\n * Build a SQL IN clause with protected upload states excluded.\n * Used for queries that should not affect upload records.\n */\nexport function getProtectedStatesInClause(): string {\n return `(${AttachmentState.QUEUED_UPLOAD}, ${PolAttachmentState.FAILED_PERMANENT})`;\n}\n\n/**\n * Build a SQL condition that excludes protected upload states.\n */\nexport function getExcludeProtectedStatesCondition(stateColumn = 'state'): string {\n return `${stateColumn} NOT IN ${getProtectedStatesInClause()}`;\n}","/**\n * MIME Type Utilities for @pol-studios/powersync\n *\n * Provides file extension to MIME type mapping for storage operations.\n */\n\n/**\n * MIME type mappings by file extension\n */\nconst MIME_TYPES: Record<string, string> = {\n // Images\n jpg: 'image/jpeg',\n jpeg: 'image/jpeg',\n png: 'image/png',\n gif: 'image/gif',\n webp: 'image/webp',\n heic: 'image/heic',\n heif: 'image/heic',\n svg: 'image/svg+xml',\n bmp: 'image/bmp',\n tiff: 'image/tiff',\n tif: 'image/tiff',\n ico: 'image/x-icon',\n avif: 'image/avif',\n // Videos\n mp4: 'video/mp4',\n mov: 'video/quicktime',\n avi: 'video/x-msvideo',\n webm: 'video/webm',\n mkv: 'video/x-matroska',\n m4v: 'video/x-m4v',\n '3gp': 'video/3gpp',\n flv: 'video/x-flv',\n // Audio\n mp3: 'audio/mpeg',\n wav: 'audio/wav',\n ogg: 'audio/ogg',\n m4a: 'audio/mp4',\n aac: 'audio/aac',\n flac: 'audio/flac',\n wma: 'audio/x-ms-wma',\n // Documents\n pdf: 'application/pdf',\n doc: 'application/msword',\n docx: 'application/vnd.openxmlformats-officedocument.wordprocessingml.document',\n xls: 'application/vnd.ms-excel',\n xlsx: 'application/vnd.openxmlformats-officedocument.spreadsheetml.sheet',\n ppt: 'application/vnd.ms-powerpoint',\n pptx: 'application/vnd.openxmlformats-officedocument.presentationml.presentation',\n txt: 'text/plain',\n csv: 'text/csv',\n rtf: 'application/rtf',\n odt: 'application/vnd.oasis.opendocument.text',\n ods: 'application/vnd.oasis.opendocument.spreadsheet',\n odp: 'application/vnd.oasis.opendocument.presentation',\n // Data formats\n json: 'application/json',\n xml: 'application/xml',\n yaml: 'application/x-yaml',\n yml: 'application/x-yaml',\n // Archives\n zip: 'application/zip',\n rar: 'application/vnd.rar',\n '7z': 'application/x-7z-compressed',\n tar: 'application/x-tar',\n gz: 'application/gzip',\n // Code\n js: 'text/javascript',\n ts: 'text/typescript',\n jsx: 'text/jsx',\n tsx: 'text/tsx',\n css: 'text/css',\n html: 'text/html',\n htm: 'text/html',\n md: 'text/markdown',\n // Other\n woff: 'font/woff',\n woff2: 'font/woff2',\n ttf: 'font/ttf',\n otf: 'font/otf',\n eot: 'application/vnd.ms-fontobject'\n};\n\n/**\n * Default MIME type for unknown file extensions\n */\nconst DEFAULT_MIME_TYPE = 'application/octet-stream';\n\n/**\n * Get MIME type from file extension.\n *\n * @param extension - File extension (with or without leading dot)\n * @returns MIME type string\n *\n * @example\n * ```typescript\n * getMimeType('jpg'); // 'image/jpeg'\n * getMimeType('.png'); // 'image/png'\n * getMimeType('unknown'); // 'application/octet-stream'\n * getMimeType(undefined); // 'application/octet-stream'\n * ```\n */\nexport function getMimeType(extension: string | undefined | null): string {\n if (!extension) {\n return DEFAULT_MIME_TYPE;\n }\n\n // Remove leading dot if present and convert to lowercase\n const ext = extension.replace(/^\\./, '').toLowerCase();\n return MIME_TYPES[ext] ?? DEFAULT_MIME_TYPE;\n}\n\n/**\n * Get MIME type from a file path or filename.\n *\n * @param filePath - Full file path or filename\n * @returns MIME type string\n *\n * @example\n * ```typescript\n * getMimeTypeFromPath('/path/to/image.jpg'); // 'image/jpeg'\n * getMimeTypeFromPath('document.pdf'); // 'application/pdf'\n * getMimeTypeFromPath('file-without-extension'); // 'application/octet-stream'\n * ```\n */\nexport function getMimeTypeFromPath(filePath: string | undefined | null): string {\n if (!filePath) {\n return DEFAULT_MIME_TYPE;\n }\n const ext = filePath.split('.').pop();\n return getMimeType(ext);\n}\n\n/**\n * Check if a MIME type represents an image.\n */\nexport function isImageMimeType(mimeType: string): boolean {\n return mimeType.startsWith('image/');\n}\n\n/**\n * Check if a MIME type represents a video.\n */\nexport function isVideoMimeType(mimeType: string): boolean {\n return mimeType.startsWith('video/');\n}\n\n/**\n * Check if a MIME type represents audio.\n */\nexport function isAudioMimeType(mimeType: string): boolean {\n return mimeType.startsWith('audio/');\n}\n\n/**\n * Check if a MIME type represents a document (PDF, Office, etc.).\n */\nexport function isDocumentMimeType(mimeType: string): boolean {\n return mimeType === 'application/pdf' || mimeType.includes('wordprocessingml') || mimeType.includes('spreadsheetml') || mimeType.includes('presentationml') || mimeType.includes('msword') || mimeType.includes('ms-excel') || mimeType.includes('ms-powerpoint') || mimeType.includes('opendocument');\n}\n\n/**\n * Get file extension from MIME type (reverse lookup).\n * Returns the most common extension for the given MIME type.\n *\n * @param mimeType - MIME type string\n * @returns File extension without leading dot, or undefined if unknown\n */\nexport function getExtensionFromMimeType(mimeType: string): string | undefined {\n // Find the first matching extension\n for (const [ext, mime] of Object.entries(MIME_TYPES)) {\n if (mime === mimeType) {\n return ext;\n }\n }\n return undefined;\n}","/**\n * Download Manager\n *\n * Handles download-related logic including batch filtering,\n * download filter application, and download record processing.\n *\n * @module attachments/download-manager\n */\n\nimport { AttachmentRecord, AttachmentState, EncodingType } from \"@powersync/attachments\";\nimport type { AbstractPowerSyncDatabase as PowerSyncDBInterface } from \"@powersync/common\";\nimport type { LoggerAdapter, PlatformAdapter } from \"../platform/types\";\nimport type { AttachmentStorageAdapter, PolAttachmentRecord } from \"./types\";\nimport { PolAttachmentState } from \"./types\";\nimport { getMimeType } from \"../utils/mimeTypes\";\n\n// ─── Types ───────────────────────────────────────────────────────────────────\n\n/**\n * Dependencies required by the download manager.\n */\nexport interface DownloadManagerDeps {\n /** PowerSync database instance */\n powersync: PowerSyncDBInterface;\n /** Platform adapter for file system operations */\n platform: PlatformAdapter;\n /** Logger adapter */\n logger: LoggerAdapter;\n /** Remote storage adapter for downloading files */\n remoteStorage: AttachmentStorageAdapter;\n /** Storage adapter with file operations */\n storage: {\n fileExists(uri: string): Promise<boolean>;\n writeFile(uri: string, data: string, options?: {\n encoding?: EncodingType;\n }): Promise<void>;\n makeDir(uri: string): Promise<void>;\n copyFile(src: string, dest: string): Promise<void>;\n };\n /** Attachment table name */\n tableName: string;\n /** Function to get local URI from local path */\n getLocalUri: (localPath: string) => string;\n /** Function to get local file path suffix */\n getLocalFilePathSuffix: (filename: string) => string;\n /** Function to update a record */\n update: (record: AttachmentRecord) => Promise<void>;\n /** Whether downloads are enabled */\n downloadAttachments?: boolean;\n /** Callback when download errors occur */\n onDownloadError?: (record: AttachmentRecord, error: Error) => Promise<{\n retry?: boolean;\n }>;\n /** Function to notify progress updates */\n notify: (immediate: boolean) => void;\n /** Function to invalidate stats cache */\n invalidateStatsCache: () => void;\n}\n\n// ─── Download Manager Functions ──────────────────────────────────────────────\n\n/**\n * Convert a Blob to ArrayBuffer with fallback for React Native.\n *\n * Blob.arrayBuffer() is a web API not available in React Native's Hermes engine.\n * Falls back to FileReader.readAsArrayBuffer() which works better than readAsDataURL()\n * in React Native (readAsDataURL hangs but readAsArrayBuffer typically works).\n */\nexport async function blobToArrayBuffer(blob: Blob): Promise<ArrayBuffer> {\n // Try native arrayBuffer first (works in modern web browsers)\n if (typeof blob.arrayBuffer === \"function\") {\n return blob.arrayBuffer();\n }\n\n // Fallback for React Native - use FileReader.readAsArrayBuffer\n // Note: readAsArrayBuffer works better than readAsDataURL in React Native\n return new Promise((resolve, reject) => {\n const reader = new FileReader();\n reader.onloadend = () => {\n if (reader.result instanceof ArrayBuffer) {\n resolve(reader.result);\n } else {\n reject(new Error(\"FileReader did not return ArrayBuffer\"));\n }\n };\n reader.onerror = () => reject(reader.error ?? new Error(\"FileReader.readAsArrayBuffer failed - blob conversion error\"));\n reader.readAsArrayBuffer(blob);\n });\n}\n\n/**\n * Download a single attachment record.\n *\n * Avoids FileReader.readAsDataURL() which hangs in React Native.\n * The parent implementation uses FileReader to convert Blob to base64,\n * but FileReader hangs indefinitely in React Native environments.\n * This override writes the downloaded file directly to the file system\n * using platform-native methods.\n *\n * Note: Filtering is handled by skipDownload callback in the queue.\n */\nexport async function downloadRecord(deps: DownloadManagerDeps, record: AttachmentRecord): Promise<boolean> {\n const {\n logger,\n storage,\n remoteStorage,\n platform\n } = deps;\n\n // Check if downloads are enabled (parent behavior)\n if (!deps.downloadAttachments) {\n return false;\n }\n\n // Set local_uri if not already set (parent behavior)\n if (!record.local_uri) {\n record.local_uri = deps.getLocalFilePathSuffix(record.filename);\n }\n const localFilePathUri = deps.getLocalUri(record.local_uri);\n\n // Check if already downloaded (parent behavior)\n if (await storage.fileExists(localFilePathUri)) {\n logger.debug(`[DownloadManager] Local file already downloaded, marking \"${record.id}\" as synced`);\n await deps.update({\n ...record,\n state: AttachmentState.SYNCED\n });\n return true;\n }\n try {\n // Download file from remote storage\n // MEMORY OPTIMIZATION: Call remoteStorage directly to get file:// path when available\n const downloadResult = await remoteStorage.downloadFile(record.filename);\n\n // Ensure directory exists - use proper path extraction instead of replace()\n const lastSlash = localFilePathUri.lastIndexOf(\"/\");\n const dirPath = lastSlash >= 0 ? localFilePathUri.substring(0, lastSlash + 1) : localFilePathUri;\n await storage.makeDir(dirPath);\n\n // MEMORY OPTIMIZATION: If we got a file:// path, use direct copy (zero memory overhead!)\n if (typeof downloadResult === \"string\" && downloadResult.startsWith(\"file://\")) {\n const tempFilePath = downloadResult;\n try {\n logger.debug(`[DownloadManager] Using direct file copy for \"${record.id}\"`);\n\n // Copy temp file to final destination (no JS memory overhead!)\n await storage.copyFile(tempFilePath, localFilePathUri);\n\n // Get file size for storage stats\n const fileInfo = await platform.fileSystem.getFileInfo(localFilePathUri);\n const fileSize = fileInfo?.exists ? fileInfo.size ?? 0 : 0;\n\n // Determine MIME type from filename extension for the record\n const ext = record.filename.split(\".\").pop()?.toLowerCase();\n const mediaType = getMimeType(ext);\n\n // Update record as synced with size for storage tracking\n await deps.update({\n ...record,\n media_type: mediaType,\n size: fileSize,\n state: AttachmentState.SYNCED\n });\n logger.debug(`[DownloadManager] Downloaded attachment \"${record.id}\" (direct copy)`);\n\n // Invalidate stats cache and notify progress update after successful download\n deps.invalidateStatsCache();\n deps.notify(true);\n return true;\n } finally {\n // Always clean up temp file, even if copy/update fails\n try {\n await platform.fileSystem.deleteFile(tempFilePath);\n } catch {\n // Ignore cleanup errors - temp file will be cleaned up by OS eventually\n }\n }\n }\n\n // FALLBACK: Handle Blob or base64 string (for web platform or legacy adapters)\n let fileBlob: Blob;\n if (typeof downloadResult === \"string\") {\n // Handle base64 string - convert to Blob\n const binaryString = atob(downloadResult);\n const bytes = new Uint8Array(binaryString.length);\n for (let i = 0; i < binaryString.length; i++) {\n bytes[i] = binaryString.charCodeAt(i);\n }\n fileBlob = new Blob([bytes]);\n } else {\n fileBlob = downloadResult;\n }\n\n // FIX: Convert Blob to ArrayBuffer with fallback for React Native\n // Add timeout to prevent indefinite hangs on large files\n const BLOB_TIMEOUT_MS = 30_000;\n const arrayBuffer = await Promise.race([blobToArrayBuffer(fileBlob), new Promise<never>((_, reject) => setTimeout(() => reject(new Error(\"Blob conversion timeout - file too large\")), BLOB_TIMEOUT_MS))]);\n const bytes = new Uint8Array(arrayBuffer);\n\n // Convert to base64 in chunks to avoid call stack overflow for large files\n const CHUNK_SIZE = 0x8000; // 32KB chunks - optimal for spread operator\n const chunks: string[] = [];\n for (let i = 0; i < bytes.length; i += CHUNK_SIZE) {\n chunks.push(String.fromCharCode(...bytes.subarray(i, i + CHUNK_SIZE)));\n }\n const base64Data = btoa(chunks.join(\"\"));\n\n // Write the file using base64 encoding\n await storage.writeFile(localFilePathUri, base64Data, {\n encoding: EncodingType.Base64\n });\n\n // Update record as synced with size for storage tracking\n await deps.update({\n ...record,\n media_type: fileBlob.type,\n size: bytes.length,\n state: AttachmentState.SYNCED\n });\n logger.debug(`[DownloadManager] Downloaded attachment \"${record.id}\"`);\n\n // Invalidate stats cache and notify progress update after successful download\n deps.invalidateStatsCache();\n deps.notify(true);\n return true;\n } catch (e) {\n // Handle download error (parent behavior)\n if (deps.onDownloadError) {\n const {\n retry\n } = await deps.onDownloadError(record, e as Error);\n if (!retry) {\n await deps.update({\n ...record,\n state: AttachmentState.ARCHIVED\n });\n return true;\n }\n }\n }\n return false;\n}","/**\n * Upload Manager\n *\n * Handles upload processing, retry scheduling, error handling,\n * and upload lifecycle management.\n *\n * @module attachments/upload-manager\n */\n\nimport { AttachmentState } from '@powersync/attachments';\nimport type { AbstractPowerSyncDatabase as PowerSyncDBInterface } from '@powersync/common';\nimport type { PlatformAdapter, LoggerAdapter } from '../platform/types';\nimport type { PolAttachmentRecord, UploadConfig, UploadHandler, UploadStatus } from './types';\nimport { PolAttachmentState, DEFAULT_UPLOAD_CONFIG } from './types';\nimport { calculateBackoffDelay, addJitter, AbortError } from '../utils/retry';\n\n// ─── Constants ───────────────────────────────────────────────────────────────\n\nconst UPLOAD_PROCESSING_DELAY_MS = 1000;\nconst MIN_POLL_INTERVAL_MS = 5_000;\n\n// ─── Types ───────────────────────────────────────────────────────────────────\n\n/**\n * Dependencies required by the upload manager.\n */\nexport interface UploadManagerDeps {\n /** PowerSync database instance */\n powersync: PowerSyncDBInterface;\n /** Platform adapter for file system operations */\n platform: PlatformAdapter;\n /** Logger adapter */\n logger: LoggerAdapter;\n /** Attachment table name */\n tableName: string;\n /** Upload handler for uploading files */\n uploadHandler?: UploadHandler;\n /** Upload configuration */\n uploadConfig: UploadConfig;\n /** Callback when upload completes */\n onUploadComplete?: (record: PolAttachmentRecord) => Promise<void>;\n /** Callback when upload fails permanently */\n onUploadFailed?: (record: PolAttachmentRecord, error: Error) => void;\n /** Function to notify progress updates */\n notify: (immediate: boolean) => void;\n}\n\n/**\n * State for the upload manager.\n */\nexport interface UploadManagerState {\n /** Whether upload processing is active */\n processing: boolean;\n /** Whether uploads are paused */\n paused: boolean;\n /** Abort controller for cancellation */\n abortController: AbortController;\n /** Map of active uploads */\n activeUploads: Map<string, UploadStatus>;\n /** Set of active upload promises for cleanup during dispose */\n activePromises: Set<Promise<void>>;\n}\n\n// ─── Error Classification ────────────────────────────────────────────────────\n\n/**\n * Check if an error is permanent (should not retry).\n */\nexport function isPermanentError(error: Error): boolean {\n const message = error.message.toLowerCase();\n const code = extractErrorCode(error);\n\n // Permanent HTTP status codes (4xx except 429)\n const permanentCodes = ['400', '401', '403', '404', '413', '415', '422'];\n if (code && permanentCodes.includes(code)) {\n return true;\n }\n\n // Only match specific error phrases, not generic terms\n if (message.includes('not found') || message.includes('unauthorized') || message.includes('forbidden') || message.includes('access denied')) {\n return true;\n }\n return false;\n}\n\n/**\n * Extract HTTP status code from error message.\n * Uses specific patterns to avoid false positives from numbers in other contexts.\n */\nexport function extractErrorCode(error: Error): string | undefined {\n const message = error.message;\n\n // Try specific HTTP status patterns first\n const patterns = [/\\bstatus[:\\s]+(\\d{3})\\b/i,\n // \"status: 404\" or \"status 404\"\n /\\bHTTP[\\/\\s]+\\d+(?:\\.\\d+)?\\s+(\\d{3})/i,\n // \"HTTP/1.1 404\" or \"HTTP 404\"\n /\\b(\\d{3})\\s+(?:error|failed|forbidden|unauthorized|not\\s+found)/i,\n // \"404 not found\"\n /\\berror[:\\s]+(\\d{3})\\b/i // \"error: 500\" or \"error 500\"\n ];\n for (const pattern of patterns) {\n const match = message.match(pattern);\n if (match) {\n return match[1];\n }\n }\n\n // Fallback: check for Supabase/PostgREST error structure\n if ('status' in (error as any) && typeof (error as any).status === 'number') {\n return String((error as any).status);\n }\n return undefined;\n}\n\n// ─── Upload Query Functions ──────────────────────────────────────────────────\n\n/**\n * Get pending uploads that need retry.\n */\nexport async function getPendingUploads(powersync: PowerSyncDBInterface, tableName: string): Promise<PolAttachmentRecord[]> {\n const now = Date.now();\n return powersync.getAll<PolAttachmentRecord>(`SELECT * FROM ${tableName}\n WHERE state = ? AND (upload_next_retry_at IS NULL OR upload_next_retry_at <= ?)\n ORDER BY timestamp ASC`, [AttachmentState.QUEUED_UPLOAD, now]);\n}\n\n/**\n * Get the soonest retry time for all uploads currently in backoff.\n * Returns null if there are no uploads waiting for retry.\n */\nexport async function getSoonestRetryTime(powersync: PowerSyncDBInterface, tableName: string): Promise<number | null> {\n const now = Date.now();\n const result = await powersync.getOptional<{\n soonest: number | null;\n }>(`SELECT MIN(upload_next_retry_at) as soonest FROM ${tableName}\n WHERE state = ? AND upload_next_retry_at > ?`, [AttachmentState.QUEUED_UPLOAD, now]);\n return result?.soonest ?? null;\n}\n\n/**\n * Get uploads that have permanently failed.\n */\nexport async function getFailedPermanentUploads(powersync: PowerSyncDBInterface, tableName: string): Promise<PolAttachmentRecord[]> {\n return powersync.getAll<PolAttachmentRecord>(`SELECT * FROM ${tableName} WHERE state = ?`, [PolAttachmentState.FAILED_PERMANENT]);\n}\n\n/**\n * Get stale uploads (failing for > staleDaysThreshold).\n */\nexport async function getStaleUploads(powersync: PowerSyncDBInterface, tableName: string, staleDaysThreshold: number): Promise<PolAttachmentRecord[]> {\n const threshold = Date.now() - staleDaysThreshold * 24 * 60 * 60 * 1000;\n return powersync.getAll<PolAttachmentRecord>(`SELECT * FROM ${tableName}\n WHERE state = ? AND timestamp < ?`, [AttachmentState.QUEUED_UPLOAD, threshold]);\n}\n\n/**\n * Get SYNCED uploads that have pending onComplete callbacks.\n */\nexport async function getSyncedUploadsWithPendingCallback(powersync: PowerSyncDBInterface, tableName: string): Promise<PolAttachmentRecord[]> {\n return powersync.getAll<PolAttachmentRecord>(`SELECT * FROM ${tableName}\n WHERE state = ? AND upload_metadata LIKE '%\"onCompleteCallback\":true%'`, [AttachmentState.SYNCED]);\n}\n\n/**\n * Clear the onCompleteCallback flag from a record's metadata.\n */\nexport async function clearUploadCallback(powersync: PowerSyncDBInterface, tableName: string, id: string, uploadMetadata?: string | null): Promise<void> {\n if (!uploadMetadata) return;\n try {\n const metadata = JSON.parse(uploadMetadata) as Record<string, unknown>;\n delete metadata.onCompleteCallback;\n await powersync.execute(`UPDATE ${tableName} SET upload_metadata = ? WHERE id = ?`, [JSON.stringify(metadata), id]);\n } catch {\n // If parsing fails, just clear the whole metadata\n await powersync.execute(`UPDATE ${tableName} SET upload_metadata = NULL WHERE id = ?`, [id]);\n }\n}\n\n// ─── Upload State Management ─────────────────────────────────────────────────\n\n/**\n * Mark an upload as synced (complete).\n */\nexport async function markUploadSynced(deps: UploadManagerDeps, record: PolAttachmentRecord): Promise<void> {\n const {\n powersync,\n tableName,\n logger,\n onUploadComplete\n } = deps;\n await powersync.execute(`UPDATE ${tableName}\n SET state = ?, local_uri = ?, upload_error = NULL, upload_error_code = NULL\n WHERE id = ?`, [AttachmentState.SYNCED, record.upload_source_uri, record.id]);\n logger.info(`[UploadManager] Upload complete: ${record.id}`);\n\n // Call completion callback\n if (onUploadComplete) {\n try {\n await onUploadComplete(record);\n } catch (error) {\n logger.error(`[UploadManager] onUploadComplete failed for ${record.id}:`, error);\n }\n }\n}\n\n/**\n * Mark an upload as permanently failed.\n */\nexport async function markUploadPermanentFailure(deps: UploadManagerDeps, record: PolAttachmentRecord, errorMessage: string, errorCode?: string): Promise<void> {\n const {\n powersync,\n tableName,\n logger,\n onUploadFailed\n } = deps;\n await powersync.execute(`UPDATE ${tableName}\n SET state = ?, upload_error = ?, upload_error_code = ?\n WHERE id = ?`, [PolAttachmentState.FAILED_PERMANENT, errorMessage, errorCode || null, record.id]);\n logger.warn(`[UploadManager] Permanent failure: ${record.id} - ${errorMessage}`);\n if (onUploadFailed) {\n onUploadFailed(record, new Error(errorMessage));\n }\n}\n\n/**\n * Schedule an upload retry with exponential backoff.\n */\nexport async function scheduleUploadRetry(deps: UploadManagerDeps, record: PolAttachmentRecord, error: Error): Promise<void> {\n const {\n powersync,\n tableName,\n logger,\n uploadConfig\n } = deps;\n const retryCount = (record.upload_retry_count || 0) + 1;\n\n // Check if max retry count exceeded\n if (retryCount > uploadConfig.maxRetryCount) {\n logger.warn(`[UploadManager] Max retry count (${uploadConfig.maxRetryCount}) exceeded for ${record.id}`);\n await markUploadPermanentFailure(deps, record, `Max retry count exceeded after ${retryCount - 1} attempts. Last error: ${error.message}`, 'MAX_RETRIES');\n return;\n }\n\n // Calculate next retry with exponential backoff and jitter\n const baseDelay = calculateBackoffDelay(retryCount - 1, {\n baseDelayMs: uploadConfig.baseRetryDelayMs,\n maxDelayMs: uploadConfig.maxRetryDelayMs,\n backoffMultiplier: 2\n });\n const nextRetryAt = Date.now() + addJitter(baseDelay);\n await powersync.execute(`UPDATE ${tableName}\n SET upload_retry_count = ?, upload_next_retry_at = ?, upload_error = ?\n WHERE id = ?`, [retryCount, nextRetryAt, error.message, record.id]);\n const delaySeconds = Math.round((nextRetryAt - Date.now()) / 1000);\n logger.info(`[UploadManager] Scheduled retry ${retryCount}/${uploadConfig.maxRetryCount} for ${record.id} in ${delaySeconds}s`);\n}\n\n// ─── Upload Processing ───────────────────────────────────────────────────────\n\n/**\n * Upload a single record.\n */\nexport async function uploadOne(deps: UploadManagerDeps, state: UploadManagerState, record: PolAttachmentRecord): Promise<void> {\n const {\n platform,\n logger,\n uploadHandler,\n uploadConfig\n } = deps;\n const signal = state.abortController.signal;\n if (signal.aborted) return;\n\n // Verify upload handler is configured\n if (!uploadHandler) {\n await markUploadPermanentFailure(deps, record, 'No upload handler configured');\n return;\n }\n\n // Track active upload\n state.activeUploads.set(record.id, {\n id: record.id,\n filename: record.filename,\n phase: 'uploading'\n });\n try {\n // 1. Verify source file exists\n if (!record.upload_source_uri) {\n await markUploadPermanentFailure(deps, record, 'No source file URI');\n return;\n }\n const fileInfo = await platform.fileSystem.getFileInfo(record.upload_source_uri);\n if (!fileInfo || !fileInfo.exists) {\n await markUploadPermanentFailure(deps, record, 'Source file no longer available');\n return;\n }\n\n // 2. Attempt upload (pass abort signal for actual cancellation)\n await withTimeout(uploadHandler.uploadFile(record.id, record.upload_source_uri, record.media_type || 'application/octet-stream', signal), uploadConfig.timeoutMs, 'Upload timed out', signal);\n\n // 3. Success - mark as synced\n await markUploadSynced(deps, record);\n } catch (error) {\n const err = error instanceof Error ? error : new Error(String(error));\n\n // Don't count aborts (from pause/dispose) as failures\n // Note: DOMException only exists in web environments, not React Native\n const isAbortError = err.name === 'AbortError' || typeof DOMException !== 'undefined' && err instanceof DOMException && err.name === 'AbortError';\n if (isAbortError) {\n logger.debug?.('[UploadManager] Upload aborted (pause/dispose), will retry on resume', {\n id: record.id\n });\n return;\n }\n\n // Log the actual error for debugging\n logger.error(`[UploadManager] Upload failed for ${record.id}:`, err.message);\n if (isPermanentError(err)) {\n await markUploadPermanentFailure(deps, record, err.message, extractErrorCode(err));\n } else {\n await scheduleUploadRetry(deps, record, err);\n }\n } finally {\n state.activeUploads.delete(record.id);\n deps.notify(false);\n }\n}\n\n/**\n * Start the upload processing loop.\n */\nexport async function startUploadProcessing(deps: UploadManagerDeps, state: UploadManagerState): Promise<void> {\n const {\n logger,\n uploadHandler,\n uploadConfig,\n tableName,\n powersync\n } = deps;\n if (state.paused || state.processing || !uploadHandler) return;\n logger.info('[UploadManager] Starting upload processing');\n state.processing = true;\n try {\n while (!state.abortController.signal.aborted) {\n const pending = await getPendingUploads(powersync, tableName);\n if (pending.length > 0) {\n // Process in batches with concurrency limit\n for (let i = 0; i < pending.length; i += uploadConfig.concurrency) {\n if (state.abortController.signal.aborted) break;\n const batch = pending.slice(i, i + uploadConfig.concurrency);\n const uploadPromises = batch.map(record => {\n const promise = uploadOne(deps, state, record);\n // Track promise for graceful shutdown\n state.activePromises.add(promise);\n promise.finally(() => state.activePromises.delete(promise));\n return promise;\n });\n await Promise.allSettled(uploadPromises);\n }\n\n // Small delay between batches to prevent tight loops\n await sleep(UPLOAD_PROCESSING_DELAY_MS, state.abortController.signal);\n } else {\n // No uploads ready now - check if there are any waiting for retry\n const soonestRetry = await getSoonestRetryTime(powersync, tableName);\n if (soonestRetry === null) {\n // No uploads at all, exit the loop\n logger.info('[UploadManager] No pending uploads, stopping processing');\n break;\n }\n\n // Log the uploads that are in backoff and why\n const allQueued = await powersync.getAll<PolAttachmentRecord>(`SELECT id, upload_error, upload_retry_count, upload_next_retry_at FROM ${tableName} WHERE state = ?`, [AttachmentState.QUEUED_UPLOAD]);\n for (const upload of allQueued) {\n const retryIn = upload.upload_next_retry_at ? Math.round((upload.upload_next_retry_at - Date.now()) / 1000) : 0;\n logger.error(`[UploadManager] Upload in backoff: ${upload.id} (retry ${upload.upload_retry_count}, in ${retryIn}s) - Error: ${upload.upload_error || 'unknown'}`);\n }\n\n // Calculate how long to sleep until the soonest retry\n const now = Date.now();\n const sleepDuration = Math.max(MIN_POLL_INTERVAL_MS, Math.min(soonestRetry - now, uploadConfig.maxRetryDelayMs));\n logger.info(`[UploadManager] All uploads in backoff, sleeping ${Math.round(sleepDuration / 1000)}s until next retry`);\n await sleep(sleepDuration, state.abortController.signal);\n }\n }\n } catch (error) {\n logger.error('[UploadManager] Upload loop error:', error);\n } finally {\n state.processing = false;\n logger.info('[UploadManager] Upload processing stopped');\n deps.notify(true);\n }\n}\n\n// ─── Utility Functions ───────────────────────────────────────────────────────\n\n/**\n * Sleep with abort signal support.\n */\nfunction sleep(ms: number, signal?: AbortSignal): Promise<void> {\n return new Promise(resolve => {\n const timer = setTimeout(resolve, ms);\n if (signal) {\n signal.addEventListener('abort', () => {\n clearTimeout(timer);\n resolve();\n }, {\n once: true\n });\n }\n });\n}\n\n/**\n * Enhanced timeout that supports AbortSignal for actual cancellation.\n */\nfunction withTimeout<T>(promise: Promise<T>, ms: number, message: string, signal?: AbortSignal): Promise<T> {\n return new Promise((resolve, reject) => {\n // Handle pre-aborted signal\n if (signal?.aborted) {\n reject(new AbortError());\n return;\n }\n const timer = setTimeout(() => {\n reject(new Error(message));\n }, ms);\n\n // Listen for abort signal\n const abortHandler = () => {\n clearTimeout(timer);\n reject(new AbortError());\n };\n if (signal) {\n signal.addEventListener('abort', abortHandler, {\n once: true\n });\n }\n promise.then(result => {\n clearTimeout(timer);\n if (signal) {\n signal.removeEventListener('abort', abortHandler);\n }\n resolve(result);\n }, err => {\n clearTimeout(timer);\n if (signal) {\n signal.removeEventListener('abort', abortHandler);\n }\n reject(err);\n });\n });\n}\n\n/**\n * Create initial upload manager state.\n */\nexport function createUploadManagerState(): UploadManagerState {\n return {\n processing: false,\n paused: false,\n abortController: new AbortController(),\n activeUploads: new Map(),\n activePromises: new Set()\n };\n}\n\n/**\n * Create upload manager dependencies with defaults.\n */\nexport function createUploadManagerDeps(partial: Omit<UploadManagerDeps, 'uploadConfig'> & {\n uploadConfig?: Partial<UploadConfig>;\n}): UploadManagerDeps {\n return {\n ...partial,\n uploadConfig: {\n ...DEFAULT_UPLOAD_CONFIG,\n ...partial.uploadConfig\n }\n };\n}","/**\n * Cache Manager\n *\n * Handles cache eviction, cleanup logic, and cache management utilities.\n *\n * @module attachments/cache-manager\n */\n\nimport { AttachmentState } from '@powersync/attachments';\nimport type { AbstractPowerSyncDatabase as PowerSyncDBInterface } from '@powersync/common';\nimport type { PlatformAdapter, LoggerAdapter } from '../platform/types';\nimport type { CacheConfig } from './types';\nimport { DEFAULT_CACHE_CONFIG } from './types';\nimport { getMimeType } from '../utils/mimeTypes';\n\n// ─── Local Helpers ────────────────────────────────────────────────────────────\n\n/**\n * Sanitize a storage path for use as a local cache filename.\n * Replaces characters that are unsafe for filesystem paths.\n */\nfunction sanitizePathForCache(storagePath: string): string {\n return storagePath.replace(/[^a-zA-Z0-9._-]/g, '_');\n}\n\n/**\n * Ensure a path has the `file://` URI prefix.\n * Used to standardize URI format throughout the attachment system.\n */\nexport function ensureFileUri(path: string): string {\n return path.startsWith('file://') ? path : `file://${path}`;\n}\n\n/**\n * Strip the `file://` prefix from a URI to get the raw file path.\n * Used when APIs require raw paths instead of URIs.\n */\nexport function stripFileUri(uri: string): string {\n return uri.replace(/^file:\\/\\//, '');\n}\n\n// ─── Types ───────────────────────────────────────────────────────────────────\n\n/**\n * Dependencies required by the cache manager.\n */\nexport interface CacheManagerDeps {\n /** PowerSync database instance */\n powersync: PowerSyncDBInterface;\n /** Platform adapter for file system operations */\n platform: PlatformAdapter;\n /** Logger adapter */\n logger: LoggerAdapter;\n /** Attachment table name */\n tableName: string;\n /** Cache configuration */\n cacheConfig: CacheConfig;\n /** Function to get local URI from local path */\n getLocalUri: (localPath: string) => string;\n /** Function to notify progress updates */\n notify: (immediate: boolean) => void;\n /** Function to invalidate stats cache */\n invalidateStatsCache: () => void;\n}\n\n// ─── Cache Query Functions ───────────────────────────────────────────────────\n\n/**\n * Get total size of cached files.\n */\nexport async function getCachedSize(powersync: PowerSyncDBInterface, tableName: string): Promise<number> {\n const result = await powersync.getOptional<{\n total: number;\n }>(`SELECT COALESCE(SUM(size), 0) as total FROM ${tableName}\n WHERE state = ? AND local_uri IS NOT NULL`, [AttachmentState.SYNCED]);\n return result?.total ?? 0;\n}\n\n/**\n * Get files eligible for eviction, ordered by timestamp (oldest first).\n */\nexport async function getEvictionCandidates(powersync: PowerSyncDBInterface, tableName: string): Promise<Array<{\n id: string;\n local_uri: string;\n size: number;\n}>> {\n return powersync.getAll<{\n id: string;\n local_uri: string;\n size: number;\n }>(`SELECT id, local_uri, size FROM ${tableName}\n WHERE state = ? AND local_uri IS NOT NULL\n ORDER BY timestamp ASC`, [AttachmentState.SYNCED]);\n}\n\n// ─── Cache Management Functions ──────────────────────────────────────────────\n\n/**\n * Enforce the cache size limit by evicting oldest files.\n *\n * This function checks if the current cache size exceeds `cacheConfig.maxSize`.\n * If so, it evicts files (oldest first by timestamp) until the cache is below\n * the max size. Evicted files have their `local_uri` cleared and state reset\n * to `QUEUED_DOWNLOAD` so they can be re-downloaded later if needed.\n *\n * @returns Object with eviction stats: { evictedCount, freedBytes }\n */\nexport async function enforceCacheLimit(deps: CacheManagerDeps): Promise<{\n evictedCount: number;\n freedBytes: number;\n}> {\n const {\n powersync,\n platform,\n logger,\n tableName,\n cacheConfig,\n getLocalUri\n } = deps;\n\n // Skip if unlimited cache\n if (cacheConfig.maxSize === Number.MAX_SAFE_INTEGER) {\n return {\n evictedCount: 0,\n freedBytes: 0\n };\n }\n const currentSize = await getCachedSize(powersync, tableName);\n const targetSize = cacheConfig.maxSize * cacheConfig.evictionTriggerThreshold;\n\n // No eviction needed if below threshold\n if (currentSize <= targetSize) {\n return {\n evictedCount: 0,\n freedBytes: 0\n };\n }\n logger.info(`[CacheManager] Cache size ${formatBytes(currentSize)} exceeds limit ${formatBytes(cacheConfig.maxSize)}, starting eviction...`);\n const candidates = await getEvictionCandidates(powersync, tableName);\n let evictedCount = 0;\n let freedBytes = 0;\n let remainingSize = currentSize;\n for (const candidate of candidates) {\n // Stop evicting once we're below the target\n if (remainingSize <= cacheConfig.maxSize) {\n break;\n }\n\n // Delete the local file\n if (candidate.local_uri) {\n try {\n const fullPath = getLocalUri(candidate.local_uri);\n await platform.fileSystem.deleteFile(fullPath);\n } catch {\n // File may already be gone, continue with record update\n }\n }\n\n // Reset record to QUEUED_DOWNLOAD so it can be re-downloaded later\n await powersync.execute(`UPDATE ${tableName}\n SET state = ?, local_uri = NULL, size = 0\n WHERE id = ?`, [AttachmentState.QUEUED_DOWNLOAD, candidate.id]);\n evictedCount++;\n freedBytes += candidate.size || 0;\n remainingSize -= candidate.size || 0;\n }\n if (evictedCount > 0) {\n deps.invalidateStatsCache();\n deps.notify(true);\n logger.info(`[CacheManager] Evicted ${evictedCount} files, freed ${formatBytes(freedBytes)} (cache now ${formatBytes(remainingSize)})`);\n }\n return {\n evictedCount,\n freedBytes\n };\n}\n\n/**\n * Check if cache is near capacity (at downloadStopThreshold).\n * Used to decide whether to skip starting new downloads.\n *\n * @returns true if cache is at or above the download stop threshold\n */\nexport async function isCacheNearCapacity(deps: CacheManagerDeps): Promise<boolean> {\n const {\n powersync,\n tableName,\n cacheConfig\n } = deps;\n\n // Never near capacity if unlimited\n if (cacheConfig.maxSize === Number.MAX_SAFE_INTEGER) {\n return false;\n }\n const currentSize = await getCachedSize(powersync, tableName);\n const stopThreshold = cacheConfig.maxSize * cacheConfig.downloadStopThreshold;\n return currentSize >= stopThreshold;\n}\n\n/**\n * Format bytes as human-readable string for logging.\n */\nfunction formatBytes(bytes: number): string {\n if (bytes >= 1024 * 1024 * 1024) {\n return `${(bytes / (1024 * 1024 * 1024)).toFixed(2)} GB`;\n }\n if (bytes >= 1024 * 1024) {\n return `${(bytes / (1024 * 1024)).toFixed(1)} MB`;\n }\n if (bytes >= 1024) {\n return `${(bytes / 1024).toFixed(1)} KB`;\n }\n return `${bytes} bytes`;\n}\n\n/**\n * Clear all cached files and re-queue for download.\n * Preserves upload records (QUEUED_UPLOAD, FAILED_PERMANENT).\n */\nexport async function clearCache(deps: CacheManagerDeps): Promise<void> {\n const {\n powersync,\n platform,\n logger,\n tableName,\n getLocalUri\n } = deps;\n logger.info('[CacheManager] Clearing cache...');\n\n // Get only download-related records with local files\n // Exclude upload records to preserve upload source files\n const records = await powersync.getAll<{\n id: string;\n local_uri: string;\n }>(`SELECT id, local_uri FROM ${tableName}\n WHERE local_uri IS NOT NULL\n AND state IN (?, ?, ?)`, [AttachmentState.QUEUED_DOWNLOAD, AttachmentState.SYNCED, AttachmentState.QUEUED_SYNC]);\n\n // Delete local files\n for (const record of records) {\n if (record.local_uri) {\n try {\n const fullPath = getLocalUri(record.local_uri);\n await platform.fileSystem.deleteFile(fullPath);\n } catch {\n // File may already be gone\n }\n }\n }\n\n // Reset only download-related records to QUEUED_DOWNLOAD\n await powersync.execute(`UPDATE ${tableName}\n SET state = ?, local_uri = NULL, size = 0\n WHERE state IN (?, ?, ?)`, [AttachmentState.QUEUED_DOWNLOAD, AttachmentState.QUEUED_DOWNLOAD, AttachmentState.SYNCED, AttachmentState.QUEUED_SYNC]);\n deps.invalidateStatsCache();\n deps.notify(true);\n logger.info('[CacheManager] Cache cleared');\n}\n\n/**\n * Cache a local file into the attachment cache.\n * Used to cache uploaded files to avoid redundant downloads.\n */\nexport async function cacheLocalFile(deps: CacheManagerDeps, storagePath: string, sourceUri: string): Promise<void> {\n const {\n powersync,\n platform,\n logger,\n tableName,\n getLocalUri\n } = deps;\n try {\n // Verify source file exists\n const sourceInfo = await platform.fileSystem.getFileInfo(sourceUri);\n if (!sourceInfo || !sourceInfo.exists) {\n logger.warn(`[CacheManager] Source file does not exist: ${sourceUri}`);\n return;\n }\n\n // Use consistent path sanitization (preserves dots and hyphens)\n const localPath = sanitizePathForCache(storagePath);\n const localUri = getLocalUri(localPath);\n\n // Ensure cache directory exists\n const dir = localUri.substring(0, localUri.lastIndexOf('/'));\n await platform.fileSystem.makeDirectory(dir, {\n intermediates: true\n });\n\n // Copy uploaded file into cache\n await platform.fileSystem.copyFile(sourceUri, localUri);\n\n // Get file size for cache tracking\n const info = await 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',\n jpeg: 'image/jpeg',\n png: 'image/png',\n webp: 'image/webp',\n heic: 'image/heic',\n heif: 'image/heif',\n gif: 'image/gif',\n mp4: 'video/mp4',\n mov: 'video/quicktime'\n };\n const mediaType = mediaTypeMap[ext] ?? 'application/octet-stream';\n\n // Insert as SYNCED, preserving upload metadata columns on conflict\n await powersync.execute(`INSERT INTO ${tableName} (id, filename, media_type, state, local_uri, size, timestamp)\n VALUES (?, ?, ?, ?, ?, ?, ?)\n ON CONFLICT(id) DO UPDATE SET\n local_uri = excluded.local_uri,\n size = excluded.size,\n state = excluded.state,\n timestamp = excluded.timestamp`, [storagePath, storagePath, mediaType, AttachmentState.SYNCED, localPath, size, Date.now()]);\n deps.invalidateStatsCache();\n deps.notify(true);\n logger.info(`[CacheManager] Cached local file: ${storagePath} (${Math.round(size / 1024)}KB)`);\n } catch (err) {\n logger.warn(`[CacheManager] Failed to cache local file: ${storagePath}`, err);\n }\n}\n\n/**\n * Get the local file URI for a storage path.\n * Checks both downloaded files (SYNCED state) and pending uploads (QUEUED_UPLOAD state).\n */\nexport async function getLocalUriForStoragePath(deps: CacheManagerDeps, storagePath: string, storage: {\n fileExists(uri: string): Promise<boolean>;\n}): Promise<string | null> {\n const {\n powersync,\n logger,\n tableName,\n getLocalUri\n } = deps;\n\n // Query both SYNCED downloads and QUEUED_UPLOAD pending uploads\n let result: {\n local_uri: string | null;\n upload_source_uri: string | null;\n state: number;\n } | null = null;\n try {\n result = await powersync.getOptional<{\n local_uri: string | null;\n upload_source_uri: string | null;\n state: number;\n }>(`SELECT local_uri, upload_source_uri, state FROM ${tableName}\n WHERE id = ? AND (\n (state = ? AND local_uri IS NOT NULL) OR\n (state = ? AND upload_source_uri IS NOT NULL)\n )`, [storagePath, AttachmentState.SYNCED, AttachmentState.QUEUED_UPLOAD]);\n } catch (queryError) {\n logger.error(`[CacheManager] getLocalUriForStoragePath query failed:`, {\n storagePath,\n syncedState: AttachmentState.SYNCED,\n queuedUploadState: AttachmentState.QUEUED_UPLOAD,\n error: queryError\n });\n throw queryError;\n }\n if (!result) return null;\n\n // Determine which URI to use based on state\n let localPath: string | null = null;\n if (result.state === AttachmentState.SYNCED && result.local_uri) {\n localPath = result.local_uri;\n } else if (result.state === AttachmentState.QUEUED_UPLOAD && result.upload_source_uri) {\n // For pending uploads, upload_source_uri is already a full path\n const uri = ensureFileUri(result.upload_source_uri);\n\n // Verify file exists\n try {\n const exists = await storage.fileExists(stripFileUri(uri));\n if (!exists) {\n logger.warn(`[CacheManager] Upload source file missing: ${storagePath}`);\n return null;\n }\n } catch (e) {\n logger.warn(`[CacheManager] Failed to verify upload source exists: ${storagePath}`, e);\n }\n return uri;\n }\n if (!localPath) return null;\n\n // getLocalUri returns the full path, ensure it has file:// prefix\n const fullPath = getLocalUri(localPath);\n const uri = ensureFileUri(fullPath);\n\n // Verify file actually exists on disk\n try {\n const exists = await storage.fileExists(stripFileUri(uri));\n if (!exists) {\n logger.warn(`[CacheManager] Cached file missing from disk, re-queuing: ${storagePath}`);\n await powersync.execute(`UPDATE ${tableName} SET state = ?, local_uri = NULL WHERE id = ?`, [AttachmentState.QUEUED_DOWNLOAD, storagePath]);\n return null;\n }\n } catch (e) {\n // If we can't check, assume file exists to avoid unnecessary re-downloads\n logger.warn(`[CacheManager] Failed to verify file exists: ${storagePath}`, e);\n }\n return uri;\n}\n\n/**\n * Copy source file to managed cache for durable uploads.\n */\nexport async function copyToManagedCache(platform: PlatformAdapter, logger: LoggerAdapter, sourceUri: string, storagePath: string): Promise<string> {\n // Sanitize path for local filename\n const localPath = storagePath.replace(/[^a-zA-Z0-9._-]/g, '_');\n const cacheDir = platform.fileSystem.getCacheDirectory();\n const targetUri = `${cacheDir}upload_queue/${localPath}`;\n\n // Ensure directory exists\n const dir = targetUri.substring(0, targetUri.lastIndexOf('/'));\n await platform.fileSystem.makeDirectory(dir, {\n intermediates: true\n });\n\n // Copy file\n await platform.fileSystem.copyFile(sourceUri, targetUri);\n logger.info(`[CacheManager] Copied to managed cache: ${targetUri}`);\n return targetUri;\n}\n\n/**\n * Create cache manager dependencies with defaults.\n */\nexport function createCacheManagerDeps(partial: Omit<CacheManagerDeps, 'cacheConfig'> & {\n cacheConfig?: Partial<CacheConfig>;\n}): CacheManagerDeps {\n return {\n ...partial,\n cacheConfig: {\n ...DEFAULT_CACHE_CONFIG,\n ...partial.cacheConfig\n }\n };\n}","/**\n * POL Attachment Queue\n *\n * Extends the official @powersync/attachments AbstractAttachmentQueue with\n * POL-specific features:\n * - Reactive watchIds callback for attachment ID sources\n * - Optional skipDownload callback for filtering downloads\n * - Durable upload queue with exponential backoff retry\n * - FAILED_PERMANENT state for unrecoverable upload errors\n * - Image compression integration\n * - Upload callbacks (onUploadComplete, onUploadFailed)\n *\n * @example\n * ```typescript\n * const queue = new PolAttachmentQueue({\n * powersync: db,\n * storage: storageAdapter,\n * source: {\n * bucket: 'project-assets',\n * watchIds: (db, onUpdate) => {\n * db.watch('SELECT storagePath FROM photos WHERE storagePath IS NOT NULL', [], {\n * onResult: (r) => onUpdate(r.rows._array.map(row => row.storagePath))\n * });\n * },\n * skipDownload: async ({ ids, db }) => {\n * // Return IDs to skip downloading\n * const videos = await db.getAll('SELECT storagePath FROM photos WHERE mediaType LIKE \"video/%\"');\n * return videos.map(v => v.storagePath);\n * }\n * },\n * });\n *\n * await queue.init();\n * ```\n */\n\nimport { AbstractAttachmentQueue, AttachmentQueueOptions, AttachmentRecord, AttachmentState } from '@powersync/attachments';\nimport type { AbstractPowerSyncDatabase as PowerSyncDBInterface } from '@powersync/common';\nimport type { PlatformAdapter, LoggerAdapter } from '../platform/types';\nimport { PolStorageAdapter } from './pol-storage-adapter';\nimport { type AttachmentStorageAdapter, type PolAttachmentRecord, type AttachmentConfig, type AttachmentSourceConfig, type SkipDownloadContext, type UploadConfig, type UploadHandler, type UploadStatus, type AttachmentSyncStats, type AttachmentSyncStatus, type CompressionConfig, type CacheConfig, type DownloadConfig, PolAttachmentState, DEFAULT_UPLOAD_CONFIG, DEFAULT_COMPRESSION_CONFIG, DEFAULT_CACHE_CONFIG, DEFAULT_DOWNLOAD_CONFIG } from './types';\n\n// Import from split modules\nimport { PROTECTED_UPLOAD_STATES } from './state-machine';\nimport { downloadRecord as downloadRecordImpl, type DownloadManagerDeps } from './download-manager';\nimport { getPendingUploads, getSoonestRetryTime, getFailedPermanentUploads, getStaleUploads, getSyncedUploadsWithPendingCallback, clearUploadCallback as clearUploadCallbackImpl, startUploadProcessing, uploadOne, createUploadManagerState, type UploadManagerDeps, type UploadManagerState } from './upload-manager';\nimport { clearCache as clearCacheImpl, cacheLocalFile as cacheLocalFileImpl, getLocalUriForStoragePath as getLocalUriForStoragePathImpl, copyToManagedCache, enforceCacheLimit, isCacheNearCapacity, type CacheManagerDeps } from './cache-manager';\n\n// ─── Constants ───────────────────────────────────────────────────────────────\n\nconst NOTIFY_THROTTLE_MS = 500;\nconst STATS_CACHE_TTL_MS = 500;\n\n// ─── Extended Options Interface ──────────────────────────────────────────────\n\n/**\n * Options for PolAttachmentQueue that extend the official AttachmentQueueOptions.\n */\nexport interface PolAttachmentQueueOptions extends AttachmentQueueOptions {\n platform: PlatformAdapter;\n remoteStorage: AttachmentStorageAdapter;\n /** Attachment source configuration with reactive watchIds callback */\n source: AttachmentSourceConfig;\n uploadHandler?: UploadHandler;\n uploadConfig?: Partial<UploadConfig>;\n /** Download configuration for concurrency tuning */\n downloadConfig?: Partial<DownloadConfig>;\n onUploadComplete?: (record: PolAttachmentRecord) => Promise<void>;\n onUploadFailed?: (record: PolAttachmentRecord, error: Error) => void;\n compression?: Partial<CompressionConfig>;\n cache?: Partial<CacheConfig>;\n}\n\n/**\n * POL Attachment Queue that extends the official AbstractAttachmentQueue.\n */\nexport class PolAttachmentQueue extends AbstractAttachmentQueue<PolAttachmentQueueOptions> {\n // ─── Private State ─────────────────────────────────────────────────────────\n\n private readonly platform: PlatformAdapter;\n private readonly polLogger: LoggerAdapter;\n private readonly source: AttachmentSourceConfig;\n private readonly remoteStorage: AttachmentStorageAdapter;\n private readonly uploadHandler?: UploadHandler;\n private readonly uploadConfig: UploadConfig;\n private readonly downloadConfig: DownloadConfig;\n private readonly compressionConfig: CompressionConfig;\n private readonly cacheConfig: CacheConfig;\n\n // Upload manager state\n private _uploadState: UploadManagerState;\n\n // Lifecycle state\n private _disposed = false;\n private _initialized = false;\n private _watchGeneration = 0;\n private _watchIdsCleanup: (() => void) | undefined = undefined;\n private _watchMutex: Promise<void> = Promise.resolve();\n private _networkListenerCleanup: (() => void) | undefined = undefined;\n private _wasConnected: boolean | null = null;\n\n // Notification state\n private _progressCallbacks = new Set<(stats: AttachmentSyncStats) => void>();\n private _lastNotifyTime = 0;\n private _notifyTimer: ReturnType<typeof setTimeout> | null = null;\n\n // Stats cache\n private _cachedStats: AttachmentSyncStats | null = null;\n private _cachedStatsTimestamp = 0;\n constructor(options: PolAttachmentQueueOptions) {\n // Create the PolStorageAdapter\n const storage = new PolStorageAdapter({\n platform: options.platform,\n remoteStorage: options.remoteStorage,\n attachmentDirectoryName: options.attachmentDirectoryName,\n logger: options.platform.logger\n });\n\n // CRITICAL: Disable parent's expireCache behavior that deletes SYNCED records\n // beyond cacheLimit (default 100). This caused a bug where downloads would reset\n // at ~140 photos because expireCache deleted records, watchIds re-emitted them,\n // and they got re-queued for download.\n // - syncInterval: 0 disables the periodic expireCache loop\n // - cacheLimit: MAX_SAFE_INTEGER effectively disables count-based cache eviction\n // Our implementation uses size-based cache limits only (configured via cacheConfig.maxSize)\n super({\n ...options,\n storage,\n syncInterval: 0,\n cacheLimit: Number.MAX_SAFE_INTEGER\n });\n this.platform = options.platform;\n this.polLogger = options.platform.logger;\n this.source = options.source;\n this.remoteStorage = options.remoteStorage;\n this.uploadHandler = options.uploadHandler;\n this.uploadConfig = {\n ...DEFAULT_UPLOAD_CONFIG,\n ...options.uploadConfig\n };\n this.downloadConfig = {\n ...DEFAULT_DOWNLOAD_CONFIG,\n ...options.downloadConfig\n };\n this.compressionConfig = {\n ...DEFAULT_COMPRESSION_CONFIG,\n ...options.compression\n };\n this.cacheConfig = {\n ...DEFAULT_CACHE_CONFIG,\n ...options.cache\n };\n\n // Initialize upload state\n this._uploadState = createUploadManagerState();\n }\n\n // ─── Parent Overrides ──────────────────────────────────────────────────────\n\n watchUploads(): void {\n this.polLogger?.debug?.('[PolAttachmentQueue] Parent watchUploads disabled - using custom upload engine');\n }\n\n /**\n * Override parent's expireCache to disable count-based cache eviction.\n *\n * The parent implementation deletes SYNCED/ARCHIVED records beyond cacheLimit,\n * which caused a bug where downloads would reset because:\n * 1. expireCache deleted records beyond cacheLimit (default 100)\n * 2. watchIds still emitted those IDs\n * 3. They got re-created as QUEUED_DOWNLOAD\n * 4. Downloads restarted in an infinite loop\n *\n * Our implementation uses size-based cache limits only (via clearCache/cacheConfig.maxSize).\n */\n async expireCache(): Promise<void> {\n // No-op: Disabled to prevent the 140-photo reset bug\n // Size-based cache management is handled by clearCache() when needed\n }\n\n /**\n * Override parent's watchDownloads to use concurrent downloads.\n *\n * The parent implementation downloads one file at a time.\n * This override processes downloads in parallel batches for faster sync.\n */\n watchDownloads(): void {\n if (!this.options.downloadAttachments) {\n return;\n }\n this.idsToDownload(async ids => {\n // Add all IDs to the download queue\n ids.forEach(id => this.downloadQueue.add(id));\n // Process downloads with concurrency\n this._downloadRecordsConcurrent();\n });\n }\n\n /**\n * Process pending downloads with concurrency control.\n * Downloads multiple files in parallel based on downloadConfig.concurrency.\n * Enforces cache size limits after each batch completes.\n */\n private async _downloadRecordsConcurrent(): Promise<void> {\n if (!this.options.downloadAttachments || this.downloading || this._disposed) {\n return;\n }\n this.downloading = true;\n const concurrency = this.downloadConfig.concurrency;\n const hasMaxSize = this.cacheConfig.maxSize !== Number.MAX_SAFE_INTEGER;\n try {\n while (this.downloadQueue.size > 0 && !this._disposed) {\n // Check if cache is near capacity before starting new batch\n if (hasMaxSize) {\n const nearCapacity = await isCacheNearCapacity(this._getCacheManagerDeps());\n if (nearCapacity) {\n this.polLogger?.warn?.(`[PolAttachmentQueue] Cache at ${Math.round(this.cacheConfig.downloadStopThreshold * 100)}% capacity, pausing downloads`);\n break;\n }\n }\n\n // Get batch of IDs to process\n const batch: string[] = [];\n for (const id of this.downloadQueue) {\n batch.push(id);\n if (batch.length >= concurrency) break;\n }\n if (batch.length === 0) break;\n\n // Remove from queue before processing\n batch.forEach(id => this.downloadQueue.delete(id));\n\n // Process batch concurrently\n const downloadPromises = batch.map(async id => {\n try {\n const record = await this.record(id);\n if (record && record.state !== AttachmentState.SYNCED) {\n await this.downloadRecord(record);\n }\n } catch (err) {\n this.polLogger?.warn?.(`[PolAttachmentQueue] Download failed for ${id}:`, err);\n }\n });\n await Promise.all(downloadPromises);\n\n // Enforce cache limit after each batch completes\n if (hasMaxSize) {\n await enforceCacheLimit(this._getCacheManagerDeps());\n }\n }\n } finally {\n this.downloading = false;\n }\n }\n async uploadAttachment(record: {\n id: string;\n }): Promise<boolean> {\n throw new Error(`uploadAttachment() is disabled in PolAttachmentQueue. ` + `Use queueUpload() for uploads. Record ID: ${record.id}`);\n }\n async watchAttachmentIds(): Promise<void> {\n this.onAttachmentIdsChange(async ids => {\n // Increment generation to detect stale callbacks after async operations\n const generation = ++this._watchGeneration;\n this.polLogger?.debug?.(`[PolAttachmentQueue] Received ${ids.length} attachment IDs from watch`);\n\n // PRE-FILTER: Apply skipDownload callback BEFORE processing to instantly exclude items\n // This makes filtering O(1) lookup and prevents filtered items from ever entering the queue\n let filteredIds = ids;\n let skippedIds: string[] = [];\n if (this.source.skipDownload && ids.length > 0) {\n try {\n // Call skipDownload to get IDs to skip\n const context: SkipDownloadContext = {\n ids,\n db: this.powersync\n };\n skippedIds = await this.source.skipDownload(context);\n\n // Check if a new emission arrived while we were waiting\n if (generation !== this._watchGeneration) {\n this.polLogger?.debug?.('[PolAttachmentQueue] Stale watch callback, ignoring');\n return;\n }\n const skipSet = new Set(skippedIds);\n filteredIds = ids.filter(id => !skipSet.has(id));\n this.polLogger?.info?.(`[PolAttachmentQueue] Pre-filtered: ${ids.length} total, ${skippedIds.length} skipped, ${filteredIds.length} to sync`);\n } catch (error) {\n this.polLogger?.warn?.('[PolAttachmentQueue] skipDownload filter failed, using all IDs:', error);\n filteredIds = ids;\n skippedIds = [];\n }\n }\n\n // Use filtered IDs for all subsequent operations\n this.polLogger?.debug?.(`[PolAttachmentQueue] Syncing attachment IDs: [${filteredIds.length} items]`);\n if (this.initialSync) {\n this.initialSync = false;\n if (filteredIds.length > 0) {\n const placeholders = filteredIds.map(() => '?').join(',');\n await this.powersync.execute(`UPDATE ${this.table}\n SET state = ${AttachmentState.QUEUED_SYNC}\n WHERE state < ${AttachmentState.SYNCED}\n AND state NOT IN (${AttachmentState.QUEUED_UPLOAD}, ${PolAttachmentState.FAILED_PERMANENT}, ${PolAttachmentState.DOWNLOAD_SKIPPED})\n AND id IN (${placeholders})`, filteredIds);\n }\n }\n const attachmentsInDatabase = await this.powersync.getAll<PolAttachmentRecord>(`SELECT * FROM ${this.table} WHERE state < ${AttachmentState.ARCHIVED}`);\n\n // Create Map for O(1) lookups instead of O(n) Array.find()\n const attachmentsMap = new Map(attachmentsInDatabase.map(r => [r.id, r]));\n\n // Process only filtered IDs (non-skipped)\n for (const id of filteredIds) {\n const record = attachmentsMap.get(id);\n if (!record) {\n const newRecord = await this.newAttachmentRecord({\n id,\n state: AttachmentState.QUEUED_SYNC\n });\n await this.saveToQueue(newRecord);\n continue;\n }\n if (PROTECTED_UPLOAD_STATES.has(record.state)) continue;\n if (record.upload_source_uri) continue;\n if (record.state === PolAttachmentState.DOWNLOAD_SKIPPED) continue;\n\n // Check if local file exists - if not, re-queue for download\n if (record.local_uri == null || !(await this.storage.fileExists(this.getLocalUri(record.local_uri)))) {\n await this.update({\n ...record,\n state: AttachmentState.QUEUED_DOWNLOAD\n });\n }\n }\n\n // Mark skipped items with DOWNLOAD_SKIPPED state\n if (skippedIds.length > 0) {\n const placeholders = skippedIds.map(() => '?').join(',');\n await this.powersync.execute(`UPDATE ${this.table}\n SET state = ${PolAttachmentState.DOWNLOAD_SKIPPED}\n WHERE id IN (${placeholders})\n AND state NOT IN (${AttachmentState.QUEUED_UPLOAD}, ${PolAttachmentState.FAILED_PERMANENT}, ${AttachmentState.SYNCED})`, skippedIds);\n }\n\n // Archive items not in either filtered or skipped lists (truly removed from source)\n // IMPORTANT: Also protect upload records (have upload_source_uri) since the source\n // table CRUD record may not have synced yet when watchIds fires after upload success.\n const allValidIds = [...new Set([...filteredIds, ...skippedIds])];\n if (allValidIds.length > 0) {\n const placeholders = allValidIds.map(() => '?').join(',');\n await this.powersync.execute(`UPDATE ${this.table}\n SET state = ${AttachmentState.ARCHIVED}\n WHERE state < ${AttachmentState.ARCHIVED}\n AND state NOT IN (${AttachmentState.QUEUED_UPLOAD}, ${PolAttachmentState.FAILED_PERMANENT})\n AND upload_source_uri IS NULL\n AND id NOT IN (${placeholders})`, allValidIds);\n }\n\n // Notify progress update\n this._invalidateStatsCache();\n this._notify(true);\n });\n }\n async saveToQueue(record: Omit<AttachmentRecord, 'timestamp'>): Promise<AttachmentRecord> {\n const updatedRecord: AttachmentRecord = {\n ...record,\n timestamp: Date.now()\n };\n const existing = await this.powersync.getOptional<AttachmentRecord>(`SELECT id, timestamp, filename, local_uri, media_type, size, state FROM ${this.table} WHERE id = ?`, [record.id]);\n if (existing && PROTECTED_UPLOAD_STATES.has(existing.state)) {\n return existing;\n }\n await this.powersync.execute(`INSERT INTO ${this.table} (id, timestamp, filename, local_uri, media_type, size, state)\n VALUES (?, ?, ?, ?, ?, ?, ?)\n ON CONFLICT(id) DO UPDATE SET\n timestamp = excluded.timestamp,\n filename = excluded.filename,\n local_uri = COALESCE(excluded.local_uri, local_uri),\n media_type = COALESCE(excluded.media_type, media_type),\n size = COALESCE(excluded.size, size),\n state = CASE\n WHEN state IN (${AttachmentState.QUEUED_UPLOAD}, ${PolAttachmentState.FAILED_PERMANENT})\n THEN state\n ELSE excluded.state\n END`, [updatedRecord.id, updatedRecord.timestamp, updatedRecord.filename, updatedRecord.local_uri || null, updatedRecord.media_type || null, updatedRecord.size || null, updatedRecord.state]);\n return updatedRecord;\n }\n\n // ─── Abstract Method Implementations ───────────────────────────────────────\n\n onAttachmentIdsChange(onUpdate: (ids: string[]) => void): void {\n // Serialize watch setup to prevent interleaving when called rapidly\n this._watchMutex = this._watchMutex.then(async () => {\n if (this._disposed) return;\n\n // Clean up previous watch first\n if (this._watchIdsCleanup) {\n try {\n this._watchIdsCleanup();\n } catch (error) {\n this.polLogger?.warn?.('[PolAttachmentQueue] Watch cleanup failed:', error);\n }\n this._watchIdsCleanup = undefined;\n }\n\n // Set up new watch\n try {\n const cleanup = this.source.watchIds(this.powersync, onUpdate);\n if (typeof cleanup === 'function') {\n this._watchIdsCleanup = cleanup;\n }\n } catch (error) {\n this.polLogger?.warn?.('[PolAttachmentQueue] watchIds failed:', error);\n }\n }).catch(err => {\n this.polLogger?.warn?.('[PolAttachmentQueue] Watch mutex error:', err);\n });\n }\n async downloadRecord(record: AttachmentRecord): Promise<boolean> {\n return downloadRecordImpl(this._getDownloadManagerDeps(), record);\n }\n async newAttachmentRecord(record?: Partial<AttachmentRecord>): Promise<AttachmentRecord> {\n if (!record?.id) {\n throw new Error('[PolAttachmentQueue] newAttachmentRecord requires a non-empty record.id');\n }\n return {\n id: record.id,\n filename: record.filename ?? record.id,\n state: record.state ?? AttachmentState.QUEUED_SYNC,\n local_uri: record.local_uri,\n size: record.size,\n media_type: record.media_type,\n timestamp: record.timestamp ?? Date.now()\n };\n }\n\n // ─── Lifecycle ─────────────────────────────────────────────────────────────\n\n async init(): Promise<void> {\n await this._createTableIfNotExists();\n await super.init();\n this._initialized = true;\n await this._migrateUploadColumns();\n if (this.uploadHandler && !this._uploadState.paused) {\n this._startUploadProcessing();\n }\n\n // Repair sizes for existing synced attachments (non-blocking)\n this.repairAttachmentSizes().catch(err => {\n this.polLogger.warn('[PolAttachmentQueue] Failed to repair attachment sizes:', err);\n });\n\n // Reset upload retries on startup if we have network connectivity\n // This ensures stuck uploads retry immediately when users restart the app with internet\n this.platform.network.isConnected().then(async isConnected => {\n if (isConnected && !this._disposed) {\n const resetCount = await this.resetUploadRetries();\n if (resetCount > 0) {\n this.polLogger.info(`[PolAttachmentQueue] Reset ${resetCount} upload retries on startup (network connected)`);\n }\n }\n }).catch(err => {\n this.polLogger.warn('[PolAttachmentQueue] Failed to check network connectivity on startup:', err);\n });\n\n // Subscribe to network connectivity changes to resume uploads/downloads when back online\n // Track previous connection state to detect actual reconnection events\n this._networkListenerCleanup = this.platform.network.addConnectionListener(async isConnected => {\n const wasConnected = this._wasConnected;\n this._wasConnected = isConnected;\n if (this._disposed) return;\n\n // Only trigger retry logic on actual reconnection (was disconnected, now connected)\n const isReconnection = wasConnected === false && isConnected === true;\n if (!isConnected) {\n this.polLogger.debug?.('[PolAttachmentQueue] Network disconnected');\n return;\n }\n\n // Log differently for reconnection vs initial connection\n if (isReconnection) {\n this.polLogger.info('[PolAttachmentQueue] Network reconnected, triggering upload/download retry');\n } else if (wasConnected === null) {\n this.polLogger.debug?.('[PolAttachmentQueue] Initial network state: connected');\n return; // Don't retry on initial connection state report\n } else {\n this.polLogger.debug?.('[PolAttachmentQueue] Network still connected');\n return;\n }\n\n // On reconnection, call retryUploads() to make eligible uploads retry immediately\n // This preserves backoff state while allowing network-blocked uploads to proceed\n if (this.uploadHandler) {\n await this.retryUploads();\n }\n\n // Resume downloads if enabled and not already downloading\n if (this.options.downloadAttachments && !this.downloading) {\n const pendingDownloads = await this.powersync.getAll<{\n id: string;\n }>(`SELECT id FROM ${this.table} WHERE state IN (?, ?)`, [AttachmentState.QUEUED_DOWNLOAD, AttachmentState.QUEUED_SYNC]);\n if (pendingDownloads.length > 0) {\n this.polLogger.info(`[PolAttachmentQueue] Resuming ${pendingDownloads.length} pending downloads`);\n // Add pending downloads to the queue and trigger processing\n pendingDownloads.forEach(r => this.downloadQueue.add(r.id));\n this._downloadRecordsConcurrent();\n }\n }\n });\n this.polLogger.info('[PolAttachmentQueue] Initialized');\n }\n\n /**\n * Dispose the attachment queue and clean up resources.\n * This method is synchronous to ensure callers don't need to await it,\n * but it fires off async cleanup in the background for graceful shutdown.\n */\n dispose(): void {\n this._disposed = true;\n\n // Synchronous cleanup - abort controller signals all pending work to stop\n this._uploadState.abortController.abort();\n\n // Clean up watchIds subscription if present (synchronous)\n if (this._watchIdsCleanup) {\n try {\n this._watchIdsCleanup();\n } catch (error) {\n this.polLogger?.warn?.('[PolAttachmentQueue] watchIds cleanup failed:', error);\n }\n this._watchIdsCleanup = undefined;\n }\n\n // Clean up network listener subscription (synchronous)\n if (this._networkListenerCleanup) {\n try {\n this._networkListenerCleanup();\n } catch (error) {\n this.polLogger?.warn?.('[PolAttachmentQueue] network listener cleanup failed:', error);\n }\n this._networkListenerCleanup = undefined;\n }\n\n // Clear timers synchronously\n if (this._notifyTimer) {\n clearTimeout(this._notifyTimer);\n this._notifyTimer = null;\n }\n\n // Clear state synchronously\n this._progressCallbacks.clear();\n this._uploadState.activeUploads.clear();\n\n // Call parent dispose synchronously\n const parentProto = Object.getPrototypeOf(Object.getPrototypeOf(this));\n if (parentProto && typeof parentProto.dispose === 'function') {\n parentProto.dispose.call(this);\n }\n\n // Fire-and-forget async cleanup (don't block callers)\n this._asyncCleanup().catch(err => {\n this.polLogger?.warn?.('[PolAttachmentQueue] Async cleanup failed:', err);\n });\n }\n\n /**\n * Async cleanup that runs in the background after dispose().\n * Waits for active upload promises to settle gracefully.\n */\n private async _asyncCleanup(): Promise<void> {\n if (this._uploadState.activePromises.size > 0) {\n this.polLogger?.debug?.(`[PolAttachmentQueue] Waiting for ${this._uploadState.activePromises.size} active uploads to finish`);\n await Promise.allSettled([...this._uploadState.activePromises]);\n }\n this._uploadState.activePromises.clear();\n }\n\n // ─── Upload API ────────────────────────────────────────────────────────────\n\n async queueUpload(options: {\n storagePath: string;\n sourceUri: string;\n filename: string;\n mediaType: string;\n bucketId?: string;\n metadata?: Record<string, unknown>;\n }): Promise<void> {\n if (!this.uploadHandler) {\n throw new Error('[PolAttachmentQueue] Upload not configured. Provide uploadHandler in options.');\n }\n if (!this._initialized) {\n throw new Error('[PolAttachmentQueue] Queue not initialized. Call init() first.');\n }\n const managedUri = await copyToManagedCache(this.platform, this.polLogger, options.sourceUri, options.storagePath);\n const fileInfo = await this.platform.fileSystem.getFileInfo(managedUri);\n const size = fileInfo?.exists ? fileInfo.size : 0;\n await this.powersync.execute(`INSERT OR REPLACE INTO ${this.table}\n (id, filename, media_type, state, local_uri, size, timestamp,\n upload_source_uri, upload_retry_count, upload_next_retry_at,\n upload_metadata, upload_bucket_id)\n VALUES (?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?)`, [options.storagePath, options.filename, options.mediaType, AttachmentState.QUEUED_UPLOAD, null, size, Date.now(), managedUri, 0, Date.now(), options.metadata ? JSON.stringify(options.metadata) : null, options.bucketId || null]);\n this.polLogger.info(`[PolAttachmentQueue] Queued upload: ${options.storagePath}`);\n if (!this._uploadState.paused && !this._uploadState.processing) {\n this._startUploadProcessing();\n }\n this._notify(true);\n }\n async getPendingUploads(): Promise<PolAttachmentRecord[]> {\n return getPendingUploads(this.powersync, this.table);\n }\n async getSoonestRetryTime(): Promise<number | null> {\n return getSoonestRetryTime(this.powersync, this.table);\n }\n async getFailedPermanentUploads(): Promise<PolAttachmentRecord[]> {\n return getFailedPermanentUploads(this.powersync, this.table);\n }\n async getStaleUploads(): Promise<PolAttachmentRecord[]> {\n return getStaleUploads(this.powersync, this.table, this.uploadConfig.staleDaysThreshold);\n }\n async getSyncedUploadsWithPendingCallback(): Promise<PolAttachmentRecord[]> {\n return getSyncedUploadsWithPendingCallback(this.powersync, this.table);\n }\n async clearUploadCallback(id: string): Promise<void> {\n const record = await this.record(id);\n if (!record) return;\n const polRecord = record as unknown as PolAttachmentRecord;\n await clearUploadCallbackImpl(this.powersync, this.table, id, polRecord.upload_metadata);\n }\n async retryUpload(id: string): Promise<void> {\n await this.powersync.execute(`UPDATE ${this.table}\n SET state = ?, upload_retry_count = 0, upload_next_retry_at = ?, upload_error = NULL\n WHERE id = ?`, [AttachmentState.QUEUED_UPLOAD, Date.now(), id]);\n if (!this._uploadState.paused && !this._uploadState.processing) {\n this._startUploadProcessing();\n }\n this._notify(true);\n }\n\n /**\n * Reset all uploads in QUEUED_UPLOAD state so they retry immediately.\n * This clears retry counts and errors, allowing uploads to be retried fresh.\n *\n * @returns The number of uploads that were reset\n */\n async resetUploadRetries(): Promise<number> {\n const result = await this.powersync.execute(`UPDATE ${this.table}\n SET upload_retry_count = 0,\n upload_next_retry_at = ?,\n upload_error = NULL\n WHERE state = ?`, [Date.now(), AttachmentState.QUEUED_UPLOAD]);\n const resetCount = result.rowsAffected ?? 0;\n if (resetCount > 0) {\n this.polLogger.info(`[PolAttachmentQueue] Reset ${resetCount} upload retries`);\n if (!this._uploadState.paused && !this._uploadState.processing) {\n this._startUploadProcessing();\n }\n this._notify(true);\n }\n return resetCount;\n }\n\n /**\n * Trigger immediate retry of uploads WITHOUT resetting their retry count.\n * This preserves backoff state for truly failing uploads while allowing\n * uploads that were waiting for network to retry immediately.\n *\n * Only affects uploads that:\n * - Are past their retry time (upload_next_retry_at <= now), OR\n * - Have no retry time set (upload_next_retry_at IS NULL)\n *\n * @returns The number of uploads that will be retried\n */\n async retryUploads(): Promise<number> {\n const now = Date.now();\n\n // Only update uploads that are already eligible for retry (past their backoff time)\n // or have no retry time set. This preserves backoff for uploads still in delay.\n const result = await this.powersync.execute(`UPDATE ${this.table}\n SET upload_next_retry_at = ?\n WHERE state = ?\n AND (upload_next_retry_at IS NULL OR upload_next_retry_at <= ?)`, [now, AttachmentState.QUEUED_UPLOAD, now]);\n const retryCount = result.rowsAffected ?? 0;\n if (retryCount > 0) {\n this.polLogger.info(`[PolAttachmentQueue] Triggering retry for ${retryCount} uploads`);\n if (!this._uploadState.paused && !this._uploadState.processing) {\n this._startUploadProcessing();\n }\n this._notify(true);\n } else {\n this.polLogger.debug?.('[PolAttachmentQueue] No uploads eligible for immediate retry');\n }\n return retryCount;\n }\n async deleteUpload(id: string): Promise<void> {\n const record = await this.record(id);\n if (record) {\n const polRecord = record as unknown as PolAttachmentRecord;\n if (polRecord.upload_source_uri) {\n try {\n await this.platform.fileSystem.deleteFile(polRecord.upload_source_uri);\n } catch {\n // File may already be gone\n }\n }\n }\n await this.powersync.execute(`DELETE FROM ${this.table} WHERE id = ?`, [id]);\n this._notify(true);\n }\n\n /**\n * Re-queue an upload for an orphaned attachment.\n * Use this when a database record exists with a storagePath but the file was never uploaded.\n *\n * This handles the case where:\n * - User took a photo and the database record was created with a storagePath\n * - The attachment queue record was created but disappeared before upload completed\n * - The photo shows locally but was never uploaded to storage\n *\n * @param options.storagePath - The storage path (e.g., \"projectId/fileId.jpg\")\n * @param options.localFileUri - URI to the local file to upload\n * @param options.mediaType - MIME type of the file\n * @param options.bucketId - Optional bucket ID for multi-bucket setups\n * @returns true if upload was queued, false if already exists in valid state\n */\n async requeueOrphanedUpload(options: {\n storagePath: string;\n localFileUri: string;\n mediaType: string;\n bucketId?: string;\n }): Promise<boolean> {\n const {\n storagePath,\n localFileUri,\n mediaType,\n bucketId\n } = options;\n this.polLogger.info(`[PolAttachmentQueue] requeueOrphanedUpload called for: ${storagePath}`);\n if (!this.uploadHandler) {\n this.polLogger.warn('[PolAttachmentQueue] requeueOrphanedUpload: No upload handler configured');\n throw new Error('[PolAttachmentQueue] Upload not configured. Provide uploadHandler in options.');\n }\n if (!this._initialized) {\n this.polLogger.warn('[PolAttachmentQueue] requeueOrphanedUpload: Queue not initialized');\n throw new Error('[PolAttachmentQueue] Queue not initialized. Call init() first.');\n }\n\n // Check if record already exists\n const existingRecord = await this.powersync.getOptional<PolAttachmentRecord>(`SELECT id, state, upload_source_uri FROM ${this.table} WHERE id = ?`, [storagePath]);\n if (existingRecord) {\n this.polLogger.debug(`[PolAttachmentQueue] requeueOrphanedUpload: Found existing record with state=${existingRecord.state}`);\n\n // Already queued for upload - nothing to do\n if (existingRecord.state === AttachmentState.QUEUED_UPLOAD) {\n this.polLogger.info(`[PolAttachmentQueue] requeueOrphanedUpload: Already QUEUED_UPLOAD, skipping: ${storagePath}`);\n return false;\n }\n\n // Failed permanently - let caller decide whether to retry via retryUpload()\n if (existingRecord.state === PolAttachmentState.FAILED_PERMANENT) {\n this.polLogger.info(`[PolAttachmentQueue] requeueOrphanedUpload: Already FAILED_PERMANENT, skipping: ${storagePath}`);\n return false;\n }\n\n // Already synced - file is already uploaded\n if (existingRecord.state === AttachmentState.SYNCED) {\n this.polLogger.info(`[PolAttachmentQueue] requeueOrphanedUpload: Already SYNCED, skipping: ${storagePath}`);\n return false;\n }\n\n // For ARCHIVED or other states, we'll update the record to queue upload\n this.polLogger.info(`[PolAttachmentQueue] requeueOrphanedUpload: Existing record in state=${existingRecord.state}, will re-queue`);\n } else {\n this.polLogger.info(`[PolAttachmentQueue] requeueOrphanedUpload: No existing record found, will create new`);\n }\n\n // Verify source file exists before proceeding\n const fileInfo = await this.platform.fileSystem.getFileInfo(localFileUri);\n if (!fileInfo?.exists) {\n this.polLogger.error(`[PolAttachmentQueue] requeueOrphanedUpload: Source file not found: ${localFileUri}`);\n throw new Error(`Source file not found: ${localFileUri}`);\n }\n this.polLogger.debug(`[PolAttachmentQueue] requeueOrphanedUpload: Source file exists, size=${fileInfo.size}`);\n\n // Copy file to managed cache for durable upload\n const managedUri = await copyToManagedCache(this.platform, this.polLogger, localFileUri, storagePath);\n this.polLogger.debug(`[PolAttachmentQueue] requeueOrphanedUpload: Copied to managed cache: ${managedUri}`);\n\n // Get file size from managed copy\n const managedFileInfo = await this.platform.fileSystem.getFileInfo(managedUri);\n const size = managedFileInfo?.exists ? managedFileInfo.size : fileInfo.size;\n\n // Extract filename from storagePath\n const filename = storagePath.split('/').pop() || storagePath;\n\n // Insert or update record as QUEUED_UPLOAD\n await this.powersync.execute(`INSERT OR REPLACE INTO ${this.table}\n (id, filename, media_type, state, local_uri, size, timestamp,\n upload_source_uri, upload_retry_count, upload_next_retry_at,\n upload_metadata, upload_bucket_id, upload_error, upload_error_code)\n VALUES (?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?)`, [storagePath, filename, mediaType, AttachmentState.QUEUED_UPLOAD, null, size, Date.now(), managedUri, 0,\n // Reset retry count\n Date.now(),\n // Can retry immediately\n null,\n // No metadata\n bucketId || null, null,\n // Clear any previous error\n null]);\n this.polLogger.info(`[PolAttachmentQueue] requeueOrphanedUpload: Queued upload: ${storagePath}`);\n\n // Trigger upload processing if not already running\n if (!this._uploadState.paused && !this._uploadState.processing) {\n this._startUploadProcessing();\n }\n this._notify(true);\n return true;\n }\n\n // Compatibility aliases\n async getRecord(id: string): Promise<PolAttachmentRecord | null> {\n return (await this.record(id)) as unknown as PolAttachmentRecord | null;\n }\n async getFailedUploads(): Promise<PolAttachmentRecord[]> {\n return this.getFailedPermanentUploads();\n }\n async retryFailedUpload(id: string): Promise<void> {\n return this.retryUpload(id);\n }\n async deleteFailedUpload(id: string): Promise<void> {\n return this.deleteUpload(id);\n }\n get activeUploads(): UploadStatus[] {\n return [...this._uploadState.activeUploads.values()];\n }\n pauseUploads(): void {\n this.polLogger.info('[PolAttachmentQueue] Pausing uploads');\n this._uploadState.paused = true;\n this._uploadState.abortController.abort();\n this._uploadState.abortController = new AbortController();\n this._notify(true);\n }\n resumeUploads(): void {\n this.polLogger.info('[PolAttachmentQueue] Resuming uploads');\n this._uploadState.paused = false;\n this._uploadState.abortController = new AbortController();\n if (!this._uploadState.processing) {\n this._startUploadProcessing();\n }\n this._notify(true);\n }\n\n // ─── Cache API ─────────────────────────────────────────────────────────────\n\n async clearCache(): Promise<void> {\n return clearCacheImpl(this._getCacheManagerDeps());\n }\n async cacheLocalFile(storagePath: string, sourceUri: string): Promise<void> {\n return cacheLocalFileImpl(this._getCacheManagerDeps(), storagePath, sourceUri);\n }\n async getLocalUriForStoragePath(storagePath: string): Promise<string | null> {\n return getLocalUriForStoragePathImpl(this._getCacheManagerDeps(), storagePath, this.storage);\n }\n\n /**\n * Purge attachments by their IDs.\n * Archives and deletes local files for the specified attachment IDs.\n * Useful for edge cases where external code needs to force-remove attachments.\n *\n * @param ids - Array of attachment IDs to purge\n */\n async purgeAttachments(ids: string[]): Promise<void> {\n if (ids.length === 0) return;\n\n // Get records to delete local files\n const placeholders = ids.map(() => '?').join(',');\n const records = await this.powersync.getAll<PolAttachmentRecord>(`SELECT id, local_uri FROM ${this.table} WHERE id IN (${placeholders})`, ids);\n\n // Delete local files\n for (const record of records) {\n if (record.local_uri) {\n try {\n await this.platform.fileSystem.deleteFile(this.getLocalUri(record.local_uri));\n } catch {\n // File may already be gone\n }\n }\n }\n\n // Archive the records\n await this.powersync.execute(`UPDATE ${this.table}\n SET state = ${AttachmentState.ARCHIVED}\n WHERE id IN (${placeholders})`, ids);\n this._invalidateStatsCache();\n this._notify(true);\n this.polLogger.info(`[PolAttachmentQueue] Purged ${ids.length} attachments`);\n }\n\n // ─── Progress Subscription ─────────────────────────────────────────────────\n\n onProgress(callback: (stats: AttachmentSyncStats) => void): () => void {\n this._progressCallbacks.add(callback);\n this._notify(true);\n return () => {\n this._progressCallbacks.delete(callback);\n };\n }\n async getStats(): Promise<AttachmentSyncStats> {\n const now = Date.now();\n if (this._cachedStats && now - this._cachedStatsTimestamp < STATS_CACHE_TTL_MS) {\n return {\n ...this._cachedStats,\n status: this._getStatus(this._cachedStats.pendingCount),\n isPaused: this._uploadState.paused,\n isProcessing: this.uploading || this.downloading || this._uploadState.processing,\n // TODO: activeDownloads tracking not yet implemented - downloads are handled by\n // the parent AbstractAttachmentQueue which doesn't expose per-download progress.\n // For now, return empty array. Future: implement download tracking similar to uploads.\n activeDownloads: [],\n activeUploads: this.activeUploads\n };\n }\n interface StatsRow {\n state: number;\n cnt: number;\n sz: number;\n }\n const rows = await this.powersync.getAll<StatsRow>(`SELECT state, COUNT(*) as cnt, COALESCE(SUM(size), 0) as sz\n FROM ${this.table} GROUP BY state`);\n let synced = 0,\n syncedSize = 0,\n pending = 0,\n pendingUpload = 0,\n failedPermanent = 0,\n skippedDownloadCount = 0;\n for (const r of rows) {\n if (r.state === AttachmentState.SYNCED) {\n synced = r.cnt;\n syncedSize = r.sz;\n }\n if (r.state === AttachmentState.QUEUED_DOWNLOAD || r.state === AttachmentState.QUEUED_SYNC) pending += r.cnt;\n if (r.state === AttachmentState.QUEUED_UPLOAD) pendingUpload = r.cnt;\n if (r.state === PolAttachmentState.FAILED_PERMANENT) failedPermanent = r.cnt;\n if (r.state === PolAttachmentState.DOWNLOAD_SKIPPED) skippedDownloadCount = r.cnt;\n }\n const staleUploads = await this.getStaleUploads();\n const stats: AttachmentSyncStats = {\n syncedCount: synced,\n syncedSize,\n pendingCount: pending,\n totalExpected: synced + pending,\n maxCacheSize: this.cacheConfig.maxSize,\n compressionQuality: this.compressionConfig.quality,\n status: this._getStatus(pending),\n isPaused: this._uploadState.paused,\n isProcessing: this.uploading || this.downloading || this._uploadState.processing,\n // TODO: activeDownloads tracking not yet implemented - downloads are handled by\n // the parent AbstractAttachmentQueue which doesn't expose per-download progress.\n // For now, return empty array. Future: implement download tracking similar to uploads.\n activeDownloads: [],\n pendingUploadCount: pendingUpload,\n failedPermanentCount: failedPermanent,\n staleUploadCount: staleUploads.length,\n activeUploads: this.activeUploads,\n skippedDownloadCount\n };\n this._cachedStats = stats;\n this._cachedStatsTimestamp = now;\n return stats;\n }\n\n /**\n * Repair attachment sizes for synced records that have size=0.\n * This backfills sizes from the actual file system for existing downloads.\n * @returns Number of records repaired\n */\n async repairAttachmentSizes(): Promise<number> {\n const records = await this.powersync.getAll<PolAttachmentRecord>(`SELECT id, local_uri FROM ${this.table} WHERE state = ? AND (size IS NULL OR size = 0) AND local_uri IS NOT NULL`, [AttachmentState.SYNCED]);\n let repaired = 0;\n for (const record of records) {\n if (!record.local_uri) continue;\n const localUri = this.getLocalUri(record.local_uri);\n const fileInfo = await this.platform.fileSystem.getFileInfo(localUri);\n if (fileInfo?.exists && fileInfo.size && fileInfo.size > 0) {\n await this.powersync.execute(`UPDATE ${this.table} SET size = ? WHERE id = ?`, [fileInfo.size, record.id]);\n repaired++;\n }\n }\n if (repaired > 0) {\n this._invalidateStatsCache();\n this._notify(true);\n this.polLogger.info(`[PolAttachmentQueue] Repaired sizes for ${repaired} attachments`);\n }\n return repaired;\n }\n\n // ─── Private Helpers ───────────────────────────────────────────────────────\n\n private _getDownloadManagerDeps(): DownloadManagerDeps {\n return {\n powersync: this.powersync,\n platform: this.platform,\n logger: this.polLogger,\n remoteStorage: this.remoteStorage,\n storage: this.storage,\n tableName: this.table,\n getLocalUri: (p: string) => this.getLocalUri(p),\n getLocalFilePathSuffix: (f: string) => this.getLocalFilePathSuffix(f),\n update: (r: AttachmentRecord) => this.update(r),\n downloadAttachments: this.options.downloadAttachments,\n onDownloadError: this.options.onDownloadError,\n notify: (imm: boolean) => this._notify(imm),\n invalidateStatsCache: () => this._invalidateStatsCache()\n };\n }\n private _getUploadManagerDeps(): UploadManagerDeps {\n return {\n powersync: this.powersync,\n platform: this.platform,\n logger: this.polLogger,\n tableName: this.table,\n uploadHandler: this.uploadHandler,\n uploadConfig: this.uploadConfig,\n onUploadComplete: this.options.onUploadComplete,\n onUploadFailed: this.options.onUploadFailed,\n notify: (imm: boolean) => this._notify(imm)\n };\n }\n private _getCacheManagerDeps(): CacheManagerDeps {\n return {\n powersync: this.powersync,\n platform: this.platform,\n logger: this.polLogger,\n tableName: this.table,\n cacheConfig: this.cacheConfig,\n getLocalUri: (p: string) => this.getLocalUri(p),\n notify: (imm: boolean) => this._notify(imm),\n invalidateStatsCache: () => this._invalidateStatsCache()\n };\n }\n private async _startUploadProcessing(): Promise<void> {\n const deps = this._getUploadManagerDeps();\n await startUploadProcessing(deps, this._uploadState);\n }\n private async _createTableIfNotExists(): Promise<void> {\n const result = await this.powersync.getOptional<{\n name: string;\n }>(`SELECT name FROM sqlite_master WHERE type='table' AND name=?`, [this.table]);\n if (result) return;\n await this.powersync.execute(`\n CREATE TABLE IF NOT EXISTS ${this.table} (\n id TEXT PRIMARY KEY NOT NULL,\n filename TEXT,\n local_uri TEXT,\n timestamp INTEGER,\n media_type TEXT,\n size INTEGER,\n state INTEGER DEFAULT 0,\n upload_source_uri TEXT,\n upload_error TEXT,\n upload_error_code TEXT,\n upload_retry_count INTEGER DEFAULT 0,\n upload_next_retry_at INTEGER,\n upload_metadata TEXT,\n upload_bucket_id TEXT\n )\n `);\n }\n private async _migrateUploadColumns(): Promise<void> {\n const tableInfo = await this.powersync.getAll<{\n name: string;\n }>(`PRAGMA table_info(${this.table})`);\n const existingColumns = new Set(tableInfo.map(r => r.name));\n const uploadColumns = [{\n name: 'upload_source_uri',\n type: 'TEXT'\n }, {\n name: 'upload_error',\n type: 'TEXT'\n }, {\n name: 'upload_error_code',\n type: 'TEXT'\n }, {\n name: 'upload_retry_count',\n type: 'INTEGER DEFAULT 0'\n }, {\n name: 'upload_next_retry_at',\n type: 'INTEGER'\n }, {\n name: 'upload_metadata',\n type: 'TEXT'\n }, {\n name: 'upload_bucket_id',\n type: 'TEXT'\n }];\n for (const col of uploadColumns) {\n if (!existingColumns.has(col.name)) {\n await this.powersync.execute(`ALTER TABLE ${this.table} ADD COLUMN ${col.name} ${col.type}`);\n }\n }\n }\n private _invalidateStatsCache(): void {\n this._cachedStats = null;\n this._cachedStatsTimestamp = 0;\n }\n private _getStatus(_pendingCount: number): AttachmentSyncStatus {\n if (this.uploading || this.downloading || this._uploadState.processing) return 'syncing';\n if (this._uploadState.paused) return 'paused';\n return 'complete';\n }\n private _notify(forceImmediate = false): void {\n if (this._disposed) return;\n if (this._progressCallbacks.size === 0) return;\n const now = Date.now();\n const timeSinceLastNotify = now - this._lastNotifyTime;\n if (this._notifyTimer) {\n clearTimeout(this._notifyTimer);\n this._notifyTimer = null;\n }\n const notifyAll = (stats: AttachmentSyncStats) => {\n // Check disposed again since this runs asynchronously\n if (this._disposed) return;\n for (const cb of this._progressCallbacks) {\n try {\n cb(stats);\n } catch (err) {\n this.polLogger.warn('[PolAttachmentQueue] Callback error:', err);\n }\n }\n };\n if (forceImmediate || timeSinceLastNotify >= NOTIFY_THROTTLE_MS) {\n this._lastNotifyTime = now;\n this.getStats().then(notifyAll).catch(err => {\n if (this._disposed) return;\n this.polLogger.warn('[PolAttachmentQueue] Stats error:', err);\n });\n } else {\n const delay = NOTIFY_THROTTLE_MS - timeSinceLastNotify;\n this._notifyTimer = setTimeout(() => {\n if (this._disposed) return; // Check disposed in timer callback\n this._notifyTimer = null;\n this._lastNotifyTime = Date.now();\n this.getStats().then(notifyAll).catch(err => {\n if (this._disposed) return;\n this.polLogger.warn('[PolAttachmentQueue] Stats error:', err);\n });\n }, delay);\n }\n }\n}\n\n// ─── Factory Function ────────────────────────────────────────────────────────\n\n/**\n * Factory function options that combine AttachmentConfig with required runtime dependencies.\n */\nexport interface CreateAttachmentQueueOptions extends AttachmentConfig {\n /** Remote storage adapter for downloading/uploading files */\n remoteStorage: AttachmentStorageAdapter;\n /** Upload handler for processing uploads */\n uploadHandler?: UploadHandler;\n /** Upload configuration */\n uploadConfig?: Partial<UploadConfig>;\n /** Download configuration (concurrency, timeout) */\n downloadConfig?: Partial<DownloadConfig>;\n /** Compression configuration */\n compression?: Partial<CompressionConfig>;\n /** Cache configuration */\n cache?: Partial<CacheConfig>;\n}\nexport function createPolAttachmentQueue(powersync: PowerSyncDBInterface, platform: PlatformAdapter, config: CreateAttachmentQueueOptions): PolAttachmentQueue {\n return new PolAttachmentQueue({\n powersync,\n storage: undefined as never,\n platform,\n remoteStorage: config.remoteStorage,\n source: {\n bucket: config.bucket,\n watchIds: config.watchIds,\n skipDownload: config.skipDownload\n },\n attachmentDirectoryName: 'attachments',\n attachmentTableName: 'attachments',\n performInitialSync: true,\n downloadAttachments: true,\n uploadHandler: config.uploadHandler,\n uploadConfig: config.uploadConfig,\n downloadConfig: config.downloadConfig ?? config.download,\n onUploadComplete: config.onUploadComplete ? async record => config.onUploadComplete!(record) : undefined,\n onUploadFailed: config.onUploadFailed,\n compression: config.compression,\n cache: config.cache ? {\n maxSize: config.maxCacheBytes,\n ...config.cache\n } : config.maxCacheBytes ? {\n maxSize: config.maxCacheBytes\n } : undefined\n });\n}"],"mappings":"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","names":["binaryString","bytes","EncodingType","PolAttachmentState","AttachmentState","AttachmentState","AttachmentState","EncodingType","AttachmentState","bytes","EncodingType","AttachmentState","AttachmentState","AttachmentState","AttachmentState","uri","AbstractAttachmentQueue","AttachmentState","AbstractAttachmentQueue","AttachmentState"]}
|
|
@@ -1,12 +1,12 @@
|
|
|
1
1
|
import {
|
|
2
2
|
createPolAttachmentQueue
|
|
3
|
-
} from "./chunk-
|
|
3
|
+
} from "./chunk-CACKC6XG.js";
|
|
4
4
|
import {
|
|
5
5
|
DEFAULT_SYNC_STATUS
|
|
6
6
|
} from "./chunk-24RDMMCL.js";
|
|
7
7
|
import {
|
|
8
8
|
SupabaseConnector
|
|
9
|
-
} from "./chunk-
|
|
9
|
+
} from "./chunk-BGBQYQV3.js";
|
|
10
10
|
import {
|
|
11
11
|
classifySupabaseError,
|
|
12
12
|
createSyncError
|
|
@@ -355,4 +355,4 @@ export {
|
|
|
355
355
|
useSyncStatusContext,
|
|
356
356
|
useAttachmentQueueContext
|
|
357
357
|
};
|
|
358
|
-
//# sourceMappingURL=chunk-
|
|
358
|
+
//# sourceMappingURL=chunk-TIFL2KWE.js.map
|
|
@@ -3,12 +3,12 @@ import {
|
|
|
3
3
|
} from "./chunk-P6WOZO7H.js";
|
|
4
4
|
import {
|
|
5
5
|
createPolAttachmentQueue
|
|
6
|
-
} from "./chunk-
|
|
6
|
+
} from "./chunk-CACKC6XG.js";
|
|
7
7
|
import {
|
|
8
8
|
HealthMonitor,
|
|
9
9
|
MetricsCollector,
|
|
10
10
|
SyncStatusTracker
|
|
11
|
-
} from "./chunk-
|
|
11
|
+
} from "./chunk-55DKCJV4.js";
|
|
12
12
|
import {
|
|
13
13
|
DEFAULT_CONNECTION_HEALTH,
|
|
14
14
|
DEFAULT_SYNC_METRICS,
|
|
@@ -42,7 +42,7 @@ import {
|
|
|
42
42
|
} from "./chunk-WN5ZJ3E2.js";
|
|
43
43
|
import {
|
|
44
44
|
SupabaseConnector
|
|
45
|
-
} from "./chunk-
|
|
45
|
+
} from "./chunk-BGBQYQV3.js";
|
|
46
46
|
import {
|
|
47
47
|
createSyncError,
|
|
48
48
|
extractEntityIds,
|
|
@@ -1717,4 +1717,4 @@ export {
|
|
|
1717
1717
|
ProviderBridge,
|
|
1718
1718
|
OfflineDataProvider
|
|
1719
1719
|
};
|
|
1720
|
-
//# sourceMappingURL=chunk-
|
|
1720
|
+
//# sourceMappingURL=chunk-YVX3A36I.js.map
|
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
import { A as AuthProvider, h as UploadErrorMiddleware, g as UploadErrorContext, U as UploadErrorClassification } from '../types-DiBvmGEi.js';
|
|
2
2
|
export { i as ConflictBus, C as ConnectorCircuitBreakerConfig, a as ConnectorConfig, c as CrudHandler, D as DEFAULT_RETRY_CONFIG, P as PowerSyncCredentials, e as RetryConfig, R as RetryStrategyConfig, b as SchemaRouter, f as Session, S as SupabaseConnectorOptions, d as defaultSchemaRouter } from '../types-DiBvmGEi.js';
|
|
3
|
-
export { S as SupabaseConnector } from '../supabase-connector-
|
|
3
|
+
export { S as SupabaseConnector } from '../supabase-connector-D2oIl2t8.js';
|
|
4
4
|
import { SupabaseClient } from '@supabase/supabase-js';
|
|
5
5
|
export { P as PowerSyncBackendConnector } from '../types-CDqWh56B.js';
|
|
6
6
|
import '../platform/index.js';
|
package/dist/connector/index.js
CHANGED
package/dist/index.d.ts
CHANGED
|
@@ -3,10 +3,10 @@ export { k as CacheStats, i as ClassifiedError, j as CompactResult, h as Complet
|
|
|
3
3
|
export { ATTACHMENT_DOWNLOAD_TIMEOUT_MS, ATTACHMENT_RETRY_DELAY_MS, AttachmentError, COMPRESSION_MAX_WIDTH, COMPRESSION_SKIP_SIZE_BYTES, COMPRESSION_TARGET_SIZE_BYTES, ConfigurationError, ConnectorError, DEFAULT_ATTACHMENT_CACHE_SIZE, DEFAULT_ATTACHMENT_CONCURRENCY, DEFAULT_COMPRESSION_QUALITY, DEFAULT_MAX_RETRY_ATTEMPTS, DEFAULT_RETRY_BACKOFF_MULTIPLIER, DEFAULT_RETRY_BASE_DELAY_MS, DEFAULT_RETRY_MAX_DELAY_MS, DEFAULT_SYNC_INTERVAL_MS, DEFAULT_SYNC_MODE, DOWNLOAD_STOP_THRESHOLD, EVICTION_TRIGGER_THRESHOLD, HEALTH_CHECK_INTERVAL_MS, HEALTH_CHECK_TIMEOUT_MS, InitializationError, LATENCY_DEGRADED_THRESHOLD_MS, MAX_CONSECUTIVE_FAILURES, PlatformAdapterError, PowerSyncError, STATS_CACHE_TTL_MS, STATUS_NOTIFY_THROTTLE_MS, STORAGE_CRITICAL_THRESHOLD, STORAGE_KEY_ATTACHMENT_SETTINGS, STORAGE_KEY_AUTO_OFFLINE, STORAGE_KEY_ENABLED, STORAGE_KEY_METRICS, STORAGE_KEY_PAUSED, STORAGE_KEY_PREFIX, STORAGE_KEY_SYNC_MODE, STORAGE_WARNING_THRESHOLD, SyncOperationError, classifyError, classifySupabaseError, createSyncError, extractEntityIds, extractHttpStatusCode, extractTableNames, generateFailureId, isRlsError, toSyncOperationError } from './core/index.js';
|
|
4
4
|
import { m as ConflictDetectionConfig, j as ConflictCheckResult } from './types-DiBvmGEi.js';
|
|
5
5
|
export { A as AuthProvider, i as ConflictBus, l as ConflictHandler, n as ConflictListener, k as ConflictResolution, C as ConnectorCircuitBreakerConfig, a as ConnectorConfig, c as CrudHandler, D as DEFAULT_RETRY_CONFIG, F as FieldConflict, P as PowerSyncCredentials, o as ResolutionListener, e as RetryConfig, R as RetryStrategyConfig, b as SchemaRouter, f as Session, S as SupabaseConnectorOptions, U as UploadErrorClassification, g as UploadErrorContext, h as UploadErrorMiddleware, d as defaultSchemaRouter } from './types-DiBvmGEi.js';
|
|
6
|
-
export { S as SupabaseConnector } from './supabase-connector-
|
|
6
|
+
export { S as SupabaseConnector } from './supabase-connector-D2oIl2t8.js';
|
|
7
7
|
export { AuthRefreshError, CircuitBreaker, CircuitBreakerConfig, CircuitBreakerStats, CircuitOpenError, CircuitState, DEFAULT_CIRCUIT_BREAKER_CONFIG, DiscardEntryError, SupabaseAuthOptions, TransactionAbortError, createSupabaseAuth, discardOnHttpStatus, discardOnPgCodes, discardOrphaned, idempotentTables, retryOnHttpStatus, runUploadErrorMiddleware, runUploadErrorMiddlewareSync, successOnPgCodes, tableHandlers } from './connector/index.js';
|
|
8
|
-
export {
|
|
9
|
-
export { CacheManagerDeps, DOWNLOAD_WORKFLOW_STATES, DownloadManagerDeps, LOCALLY_AVAILABLE_STATES, MigrationStats, PENDING_DOWNLOAD_STATES, PROTECTED_UPLOAD_STATES, PolStorageAdapter, PolStorageAdapterOptions, STATE_MAPPING, STATE_NAMES, TERMINAL_STATES, UPLOAD_WORKFLOW_STATES, UploadManagerDeps, UploadManagerState, VALID_STATES, blobToArrayBuffer, buildIdOnlyWatchQuery, buildRecordFetchQuery, buildWatchQuery, cacheLocalFile, clearCache, clearUploadCallback, copyToManagedCache, createCacheManagerDeps, createMigrationStats, createUploadManagerDeps, createUploadManagerState, determineAttachmentState, downloadRecord, enforceCacheLimit, ensureFileUri, extractErrorCode, formatMigrationStats, getCachedSize, getEvictionCandidates, getExcludeProtectedStatesCondition, getFailedPermanentUploads, getLocalUriForStoragePath, getPendingUploads, getProtectedStatesInClause, getSoonestRetryTime, getStaleUploads, getStateName, getSyncedUploadsWithPendingCallback, isCacheNearCapacity, isDownloadWorkflowState, isLocallyAvailable, isPendingDownloadState, isPermanentError, isProtectedUploadState, isStateTransitionAllowed, isTerminalState, isUploadWorkflowState, isValidAttachmentState, markUploadPermanentFailure, markUploadSynced, migrateAttachmentState, migrateAttachmentStateSafe, recordMigration, scheduleUploadRetry, startUploadProcessing, stripFileUri, uploadOne, validateSqlIdentifier, validateSqlIdentifierFromStateMachine, validateWhereClause, watchConfigToSourceConfig } from './attachments/index.js';
|
|
8
|
+
export { f as AttachmentConfig, F as AttachmentRecord, A as AttachmentSourceConfig, x as AttachmentStatsRow, g as AttachmentStorageAdapter, w as AttachmentSyncStats, v as AttachmentSyncStatus, B as BatchFilterContext, n as CACHE_SIZE_PRESETS, l as CacheConfig, y as CacheFileRow, o as CacheSizePreset, p as CacheSizeValue, z as CachedSizeRow, C as CompressionConfig, m as DEFAULT_CACHE_CONFIG, D as DEFAULT_COMPRESSION_CONFIG, k as DEFAULT_DOWNLOAD_CONFIG, h as DEFAULT_UPLOAD_CONFIG, j as DownloadConfig, r as DownloadPhase, s as DownloadStatus, E as EvictRow, I as IdRow, P as PolAttachmentQueue, a as PolAttachmentQueueOptions, d as PolAttachmentRecord, b as PolAttachmentState, S as SimpleAttachmentConfig, e as SkipDownloadContext, U as UploadConfig, i as UploadHandler, t as UploadPhase, u as UploadStatus, W as WatchConfig, c as createPolAttachmentQueue, q as formatCacheSize } from './pol-attachment-queue-BE2HU3Us.js';
|
|
9
|
+
export { CacheManagerDeps, DOWNLOAD_WORKFLOW_STATES, DownloadManagerDeps, LOCALLY_AVAILABLE_STATES, MigrationStats, PENDING_DOWNLOAD_STATES, PROTECTED_UPLOAD_STATES, PolStorageAdapter, PolStorageAdapterOptions, STATE_MAPPING, STATE_NAMES, TERMINAL_STATES, UPLOAD_WORKFLOW_STATES, UploadManagerDeps, UploadManagerState, VALID_STATES, blobToArrayBuffer, buildIdOnlyWatchQuery, buildRecordFetchQuery, buildWatchQuery, cacheLocalFile, clearCache, clearUploadCallback, copyToManagedCache, createCacheManagerDeps, createMigrationStats, createUploadManagerDeps, createUploadManagerState, createWatchIds, determineAttachmentState, downloadRecord, enforceCacheLimit, ensureFileUri, extractErrorCode, extractIdsFromRows, formatMigrationStats, getCachedSize, getEvictionCandidates, getExcludeProtectedStatesCondition, getFailedPermanentUploads, getLocalUriForStoragePath, getPendingUploads, getProtectedStatesInClause, getSoonestRetryTime, getStaleUploads, getStateName, getSyncedUploadsWithPendingCallback, isCacheNearCapacity, isDownloadWorkflowState, isLocallyAvailable, isPendingDownloadState, isPermanentError, isProtectedUploadState, isStateTransitionAllowed, isTerminalState, isUploadWorkflowState, isValidAttachmentState, markUploadPermanentFailure, markUploadSynced, migrateAttachmentState, migrateAttachmentStateSafe, recordMigration, scheduleUploadRetry, startUploadProcessing, stripFileUri, uploadOne, validateSqlIdentifier, validateSqlIdentifierFromStateMachine, validateWhereClause, watchConfigToSourceConfig } from './attachments/index.js';
|
|
10
10
|
export { B as BackgroundSyncOptions, h as BackgroundSyncSystem, e as HealthCheckResult, H as HealthMonitorOptions, M as MetricsCollectorOptions, P as PowerSyncRawStatus, c as SyncControlActions, f as SyncEvent, g as SyncEventListener, d as SyncOperationData, S as SyncScope, a as SyncStatusState, b as SyncStatusTrackerOptions, U as Unsubscribe, i as defineBackgroundSyncTask, j as initializeBackgroundSync, k as isBackgroundSyncRegistered, r as registerBackgroundSync, u as unregisterBackgroundSync } from './background-sync-ChCXW-EV.js';
|
|
11
11
|
export { DeadLetterEntry, DeadLetterQueue, DeadLetterQueueListener, DeadLetterQueueOptions, DeadLetterReason, HealthMonitor, MetricsCollector, SyncStatusTracker, createDeadLetterEntry, generateDLQEntryId } from './sync/index.js';
|
|
12
12
|
import { SupabaseClient } from '@supabase/supabase-js';
|