1- import { AbstractPowerSyncDatabase , ILogger } from '@powersync/common' ;
1+ import { AbstractPowerSyncDatabase , ILogger , Transaction } from '@powersync/common' ;
22import { AttachmentContext } from './AttachmentContext.js' ;
33import { LocalStorageAdapter } from './LocalStorageAdapter.js' ;
44import { RemoteStorageAdapter } from './RemoteStorageAdapter.js' ;
@@ -7,20 +7,68 @@ import { SyncingService } from './SyncingService.js';
77import { WatchedAttachmentItem } from './WatchedAttachmentItem.js' ;
88import { AttachmentService } from './AttachmentService.js' ;
99
10+ /**
11+ * AttachmentQueue manages the lifecycle and synchronization of attachments
12+ * between local and remote storage.
13+ *
14+ * Provides automatic synchronization, upload/download queuing, attachment monitoring,
15+ * verification and repair of local files, and cleanup of archived attachments.
16+ */
1017export class AttachmentQueue {
18+ /** Timer for periodic synchronization operations */
1119 periodicSyncTimer ?: ReturnType < typeof setInterval > ;
20+
21+ /** Context for managing attachment records in the database */
1222 context : AttachmentContext ;
23+
24+ /** Service for synchronizing attachments between local and remote storage */
1325 syncingService : SyncingService ;
26+
27+ /** Adapter for local file storage operations */
1428 localStorage : LocalStorageAdapter ;
29+
30+ /** Adapter for remote file storage operations */
1531 remoteStorage : RemoteStorageAdapter ;
32+
33+ /** @deprecated Directory path for storing attachments */
1634 attachmentsDirectory ?: string ;
35+
36+ /** Name of the database table storing attachment records */
1737 tableName ?: string ;
38+
39+ /** Logger instance for diagnostic information */
1840 logger ?: ILogger ;
41+
42+ /** Interval in milliseconds between periodic sync operations. Default: 30000 (30 seconds) */
43+ syncIntervalMs : number = 30 * 1000 ;
44+
45+ /** Duration in milliseconds to throttle sync operations */
1946 syncThrottleDuration : number ;
47+
48+ /** Whether to automatically download remote attachments. Default: true */
2049 downloadAttachments : boolean = true ;
50+
51+ /** Maximum number of archived attachments to keep before cleanup. Default: 100 */
2152 archivedCacheLimit : number ;
53+
54+ /** Service for managing attachment-related database operations */
2255 attachmentService : AttachmentService ;
2356
57+ /**
58+ * Creates a new AttachmentQueue instance.
59+ *
60+ * @param options - Configuration options
61+ * @param options.db - PowerSync database instance
62+ * @param options.remoteStorage - Remote storage adapter for upload/download operations
63+ * @param options.localStorage - Local storage adapter for file persistence
64+ * @param options.watchAttachments - Callback for monitoring attachment changes in your data model
65+ * @param options.tableName - Name of the table to store attachment records. Default: 'ps_attachment_queue'
66+ * @param options.logger - Logger instance. Defaults to db.logger
67+ * @param options.syncIntervalMs - Interval between automatic syncs in milliseconds. Default: 30000
68+ * @param options.syncThrottleDuration - Throttle duration for sync operations in milliseconds. Default: 1000
69+ * @param options.downloadAttachments - Whether to automatically download remote attachments. Default: true
70+ * @param options.archivedCacheLimit - Maximum archived attachments before cleanup. Default: 100
71+ */
2472 constructor ( {
2573 db,
2674 localStorage,
@@ -57,8 +105,30 @@ export class AttachmentQueue {
57105 this . archivedCacheLimit = archivedCacheLimit ;
58106 }
59107
108+ /**
109+ * Callback function to watch for changes in attachment references in your data model.
110+ *
111+ * This method should be implemented to monitor changes in your application's
112+ * data that reference attachments. When attachments are added, removed, or modified,
113+ * this callback should trigger the onUpdate function with the current set of attachments.
114+ *
115+ * @param onUpdate - Callback to invoke when attachment references change
116+ * @throws Error indicating this method must be implemented by the user
117+ */
118+ watchAttachments ( onUpdate : ( attachement : WatchedAttachmentItem [ ] ) => Promise < void > ) : void {
119+ throw new Error ( 'watchAttachments should be implemented by the user of AttachmentQueue' ) ;
60120 }
61121
122+ /**
123+ * Starts the attachment synchronization process.
124+ *
125+ * This method:
126+ * - Stops any existing sync operations
127+ * - Sets up periodic synchronization based on syncIntervalMs
128+ * - Registers listeners for active attachment changes
129+ * - Processes watched attachments to queue uploads/downloads
130+ * - Handles state transitions for archived and new attachments
131+ */
62132 async startSync ( ) : Promise < void > {
63133 await this . stopSync ( ) ;
64134
@@ -156,20 +226,43 @@ export class AttachmentQueue {
156226 } ) ;
157227 }
158228
159- // Sync storage with all active attachments
229+ /**
230+ * Synchronizes all active attachments between local and remote storage.
231+ *
232+ * This is called automatically at regular intervals when sync is started,
233+ * but can also be called manually to trigger an immediate sync.
234+ */
160235 async syncStorage ( ) : Promise < void > {
161236 const activeAttachments = await this . context . getActiveAttachments ( ) ;
162237 await this . localStorage . initialize ( ) ;
163238 await this . syncingService . processAttachments ( activeAttachments ) ;
164239 await this . syncingService . deleteArchivedAttachments ( ) ;
165240 }
166241
242+ /**
243+ * Stops the attachment synchronization process.
244+ *
245+ * Clears the periodic sync timer and closes all active attachment watchers.
246+ */
167247 async stopSync ( ) : Promise < void > {
168248 clearInterval ( this . periodicSyncTimer ) ;
169249 this . periodicSyncTimer = undefined ;
170250 await this . attachmentService . watchActiveAttachments ( ) . close ( ) ;
171251 }
172252
253+ /**
254+ * Saves a file to local storage and queues it for upload to remote storage.
255+ *
256+ * @param options - File save options
257+ * @param options.data - The file data as ArrayBuffer, Blob, or base64 string
258+ * @param options.fileExtension - File extension (e.g., 'jpg', 'pdf')
259+ * @param options.mediaType - MIME type of the file (e.g., 'image/jpeg')
260+ * @param options.metaData - Optional metadata to associate with the attachment
261+ * @param options.id - Optional custom ID. If not provided, a UUID will be generated
262+ * @param options.updateHook - Optional callback to execute additional database operations
263+ * within the same transaction as the attachment creation
264+ * @returns Promise resolving to the created attachment record
265+ */
173266 async saveFile ( {
174267 data,
175268 fileExtension,
@@ -178,6 +271,7 @@ export class AttachmentQueue {
178271 id,
179272 updateHook
180273 } : {
274+ // TODO: create a dedicated type for data
181275 data : ArrayBuffer | Blob | string ;
182276 fileExtension : string ;
183277 mediaType ?: string ;
@@ -210,6 +304,14 @@ export class AttachmentQueue {
210304 return attachment ;
211305 }
212306
307+ /**
308+ * Verifies the integrity of all attachment records and repairs inconsistencies.
309+ *
310+ * This method checks each attachment record against the local filesystem and:
311+ * - Updates localUri if the file exists at a different path
312+ * - Archives attachments with missing local files that haven't been uploaded
313+ * - Requeues synced attachments for download if their local files are missing
314+ */
213315 verifyAttachments = async ( ) : Promise < void > => {
214316 const attachments = await this . context . getAttachments ( ) ;
215317 const updates : AttachmentRecord [ ] = [ ] ;
0 commit comments