1- import {
2- AbstractPowerSyncDatabase ,
3- Transaction ,
4- } from "@journeyapps/powersync-sdk-common" ;
5- import { ATTACHMENT_TABLE , AttachmentRecord , AttachmentState } from "./Schema" ;
6- import { EncodingType , StorageAdapter } from "./StorageAdapter" ;
1+ import { AbstractPowerSyncDatabase , Transaction } from '@journeyapps/powersync-sdk-common' ;
2+ import { ATTACHMENT_TABLE , AttachmentRecord , AttachmentState } from './Schema' ;
3+ import { EncodingType , StorageAdapter } from './StorageAdapter' ;
74
85export interface AttachmentQueueOptions {
96 powersync : AbstractPowerSyncDatabase ;
@@ -16,24 +13,24 @@ export interface AttachmentQueueOptions {
1613 * How many attachments to keep in the cache
1714 */
1815 cacheLimit ?: number ;
16+ /**
17+ * The name of the directory where attachments are stored on the device, not the full path
18+ */
1919 attachmentDirectoryName ?: string ;
2020 /**
2121 * Whether to mark the initial watched attachment IDs to be synced
2222 */
2323 performInitialSync ?: boolean ;
2424}
2525
26- export const DEFAULT_ATTACHMENT_QUEUE_OPTIONS : Partial < AttachmentQueueOptions > =
27- {
28- attachmentDirectoryName : "attachments" ,
29- syncInterval : 30_000 ,
30- cacheLimit : 100 ,
31- performInitialSync : true ,
32- } ;
33-
34- export abstract class AbstractAttachmentQueue <
35- T extends AttachmentQueueOptions = AttachmentQueueOptions
36- > {
26+ export const DEFAULT_ATTACHMENT_QUEUE_OPTIONS : Partial < AttachmentQueueOptions > = {
27+ attachmentDirectoryName : 'attachments' ,
28+ syncInterval : 30_000 ,
29+ cacheLimit : 100 ,
30+ performInitialSync : true
31+ } ;
32+
33+ export abstract class AbstractAttachmentQueue < T extends AttachmentQueueOptions = AttachmentQueueOptions > {
3734 uploading : boolean ;
3835 downloading : boolean ;
3936 initialSync : boolean ;
@@ -43,7 +40,7 @@ export abstract class AbstractAttachmentQueue<
4340 constructor ( options : T ) {
4441 this . options = {
4542 ...DEFAULT_ATTACHMENT_QUEUE_OPTIONS ,
46- ...options ,
43+ ...options
4744 } ;
4845 this . downloadQueue = new Set < string > ( ) ;
4946 this . uploading = false ;
@@ -65,9 +62,7 @@ export abstract class AbstractAttachmentQueue<
6562 /**
6663 * Create a new AttachmentRecord, this gets called when the attachment id is not found in the database.
6764 */
68- abstract newAttachmentRecord (
69- record ?: Partial < AttachmentRecord >
70- ) : AttachmentRecord ;
65+ abstract newAttachmentRecord ( record ?: Partial < AttachmentRecord > ) : Promise < AttachmentRecord > ;
7166
7267 protected get powersync ( ) {
7368 return this . options . powersync ;
@@ -82,9 +77,7 @@ export abstract class AbstractAttachmentQueue<
8277 }
8378
8479 get storageDirectory ( ) {
85- return `${ this . storage . getUserStorageDirectory ( ) } ${
86- this . options . attachmentDirectoryName
87- } `;
80+ return `${ this . storage . getUserStorageDirectory ( ) } ${ this . options . attachmentDirectoryName } ` ;
8881 }
8982
9083 async init ( ) {
@@ -110,7 +103,7 @@ export abstract class AbstractAttachmentQueue<
110103
111104 async watchAttachmentIds ( ) {
112105 for await ( const ids of this . attachmentIds ( ) ) {
113- const _ids = `${ ids . map ( ( id ) => `'${ id } '` ) . join ( "," ) } ` ;
106+ const _ids = `${ ids . map ( ( id ) => `'${ id } '` ) . join ( ',' ) } ` ;
114107 console . debug ( `Queuing for sync, attachment IDs: [${ _ids } ]` ) ;
115108
116109 if ( this . initialSync ) {
@@ -127,55 +120,43 @@ export abstract class AbstractAttachmentQueue<
127120 ) ;
128121 }
129122
130- const attachmentsInDatabase =
131- await this . powersync . getAll < AttachmentRecord > (
132- `SELECT * FROM ${ this . table } WHERE state < ${ AttachmentState . ARCHIVED } `
133- ) ;
123+ const attachmentsInDatabase = await this . powersync . getAll < AttachmentRecord > (
124+ `SELECT * FROM ${ this . table } WHERE state < ${ AttachmentState . ARCHIVED } `
125+ ) ;
134126
135127 for ( const id of ids ) {
136128 const record = attachmentsInDatabase . find ( ( r ) => r . id == id ) ;
137129 // 1. ID is not in the database
138130 if ( ! record ) {
139- const newRecord = this . newAttachmentRecord ( {
131+ const newRecord = await this . newAttachmentRecord ( {
140132 id : id ,
141- state : AttachmentState . QUEUED_SYNC ,
133+ state : AttachmentState . QUEUED_SYNC
142134 } ) ;
143- console . debug (
144- `Attachment (${ id } ) not found in database, creating new record`
145- ) ;
135+ console . debug ( `Attachment (${ id } ) not found in database, creating new record` ) ;
146136 await this . saveToQueue ( newRecord ) ;
147- } else if (
148- record . local_uri == null ||
149- ! ( await this . storage . fileExists ( record . local_uri ) )
150- ) {
137+ } else if ( record . local_uri == null || ! ( await this . storage . fileExists ( record . local_uri ) ) ) {
151138 // 2. Attachment in database but no local file, mark as queued download
152- console . debug (
153- `Attachment (${ id } ) found in database but no local file, marking as queued download`
154- ) ;
139+ console . debug ( `Attachment (${ id } ) found in database but no local file, marking as queued download` ) ;
155140 await this . update ( {
156141 ...record ,
157- state : AttachmentState . QUEUED_DOWNLOAD ,
142+ state : AttachmentState . QUEUED_DOWNLOAD
158143 } ) ;
159144 }
160145 }
161146
162147 // 3. Attachment in database and not in AttachmentIds, mark as archived
163148 await this . powersync . execute (
164- `UPDATE ${ this . table } SET state = ${
149+ `UPDATE ${ this . table } SET state = ${ AttachmentState . ARCHIVED } WHERE state < ${
165150 AttachmentState . ARCHIVED
166- } WHERE state < ${ AttachmentState . ARCHIVED } AND id NOT IN (${ ids
167- . map ( ( id ) => `'${ id } '` )
168- . join ( "," ) } )`
151+ } AND id NOT IN (${ ids . map ( ( id ) => `'${ id } '` ) . join ( ',' ) } )`
169152 ) ;
170153 }
171154 }
172155
173- async saveToQueue (
174- record : Omit < AttachmentRecord , "timestamp" >
175- ) : Promise < AttachmentRecord > {
156+ async saveToQueue ( record : Omit < AttachmentRecord , 'timestamp' > ) : Promise < AttachmentRecord > {
176157 const updatedRecord : AttachmentRecord = {
177158 ...record ,
178- timestamp : new Date ( ) . getTime ( ) ,
159+ timestamp : new Date ( ) . getTime ( )
179160 } ;
180161
181162 await this . powersync . execute (
@@ -187,21 +168,18 @@ export abstract class AbstractAttachmentQueue<
187168 updatedRecord . local_uri || null ,
188169 updatedRecord . media_type || null ,
189170 updatedRecord . size || null ,
190- updatedRecord . state ,
171+ updatedRecord . state
191172 ]
192173 ) ;
193174
194175 return updatedRecord ;
195176 }
196177
197178 async record ( id : string ) : Promise < AttachmentRecord | null > {
198- return this . powersync . getOptional < AttachmentRecord > (
199- `SELECT * FROM ${ this . table } WHERE id = ?` ,
200- [ id ]
201- ) ;
179+ return this . powersync . getOptional < AttachmentRecord > ( `SELECT * FROM ${ this . table } WHERE id = ?` , [ id ] ) ;
202180 }
203181
204- async update ( record : Omit < AttachmentRecord , " timestamp" > ) : Promise < void > {
182+ async update ( record : Omit < AttachmentRecord , ' timestamp' > ) : Promise < void > {
205183 const timestamp = new Date ( ) . getTime ( ) ;
206184 await this . powersync . execute (
207185 `UPDATE ${ this . table }
@@ -213,15 +191,7 @@ export abstract class AbstractAttachmentQueue<
213191 media_type = ?,
214192 state = ?
215193 WHERE id = ?` ,
216- [
217- timestamp ,
218- record . filename ,
219- record . local_uri || null ,
220- record . size ,
221- record . media_type ,
222- record . state ,
223- record . id ,
224- ]
194+ [ timestamp , record . filename , record . local_uri || null , record . size , record . media_type , record . state , record . id ]
225195 ) ;
226196 }
227197
@@ -246,7 +216,7 @@ export abstract class AbstractAttachmentQueue<
246216 try {
247217 // Delete file on storage
248218 await this . storage . deleteFile ( uri , {
249- filename : record . filename ,
219+ filename : record . filename
250220 } ) ;
251221 } catch ( e ) {
252222 console . error ( e ) ;
@@ -269,41 +239,37 @@ export abstract class AbstractAttachmentQueue<
269239
270240 async uploadAttachment ( record : AttachmentRecord ) {
271241 if ( ! record . local_uri ) {
272- throw new Error (
273- `No local_uri for record ${ JSON . stringify ( record , null , 2 ) } `
274- ) ;
242+ throw new Error ( `No local_uri for record ${ JSON . stringify ( record , null , 2 ) } ` ) ;
275243 }
276244 try {
277245 if ( ! ( await this . storage . fileExists ( record . local_uri ) ) ) {
278246 console . warn ( `File for ${ record . id } does not exist, skipping upload` ) ;
279247 await this . update ( {
280248 ...record ,
281- state : AttachmentState . QUEUED_DOWNLOAD ,
249+ state : AttachmentState . QUEUED_DOWNLOAD
282250 } ) ;
283251 return true ;
284252 }
285253
286254 const fileBuffer = await this . storage . readFile ( record . local_uri , {
287255 encoding : EncodingType . Base64 ,
288- mediaType : record . media_type ,
256+ mediaType : record . media_type
289257 } ) ;
290258
291259 await this . storage . uploadFile ( record . filename , fileBuffer , {
292- mediaType : record . media_type ,
260+ mediaType : record . media_type
293261 } ) ;
294262 // Mark as uploaded
295263 await this . update ( { ...record , state : AttachmentState . SYNCED } ) ;
296264 console . debug ( `Uploaded attachment "${ record . id } " to Cloud Storage` ) ;
297265 return true ;
298266 } catch ( e : any ) {
299- if ( e . error == " Duplicate" ) {
267+ if ( e . error == ' Duplicate' ) {
300268 console . debug ( `File already uploaded, marking ${ record . id } as synced` ) ;
301269 await this . update ( { ...record , state : AttachmentState . SYNCED } ) ;
302270 return false ;
303271 }
304- console . error (
305- `UploadAttachment error for record ${ JSON . stringify ( record , null , 2 ) } `
306- ) ;
272+ console . error ( `UploadAttachment error for record ${ JSON . stringify ( record , null , 2 ) } ` ) ;
307273 return false ;
308274 }
309275 }
@@ -313,9 +279,7 @@ export abstract class AbstractAttachmentQueue<
313279 record . local_uri = this . getLocalUri ( record . filename ) ;
314280 }
315281 if ( await this . storage . fileExists ( record . local_uri ) ) {
316- console . debug (
317- `Local file already downloaded, marking "${ record . id } " as synced`
318- ) ;
282+ console . debug ( `Local file already downloaded, marking "${ record . id } " as synced` ) ;
319283 await this . update ( { ...record , state : AttachmentState . SYNCED } ) ;
320284 return true ;
321285 }
@@ -328,37 +292,28 @@ export abstract class AbstractAttachmentQueue<
328292 const reader = new FileReader ( ) ;
329293 reader . onloadend = ( ) => {
330294 // remove the header from the result: 'data:*/*;base64,'
331- resolve (
332- reader . result ?. toString ( ) . replace ( / ^ d a t a : .+ ; b a s e 6 4 , / , "" ) || ""
333- ) ;
295+ resolve ( reader . result ?. toString ( ) . replace ( / ^ d a t a : .+ ; b a s e 6 4 , / , '' ) || '' ) ;
334296 } ;
335297 reader . onerror = reject ;
336298 reader . readAsDataURL ( fileBlob ) ;
337299 } ) ;
338300
339301 // Ensure directory exists
340- await this . storage . makeDir ( record . local_uri . replace ( record . filename , "" ) ) ;
302+ await this . storage . makeDir ( record . local_uri . replace ( record . filename , '' ) ) ;
341303 // Write the file
342304 await this . storage . writeFile ( record . local_uri , base64Data , {
343- encoding : EncodingType . Base64 ,
305+ encoding : EncodingType . Base64
344306 } ) ;
345307
346308 await this . update ( {
347309 ...record ,
348310 media_type : fileBlob . type ,
349- state : AttachmentState . SYNCED ,
311+ state : AttachmentState . SYNCED
350312 } ) ;
351313 console . debug ( `Downloaded attachment "${ record . id } "` ) ;
352314 return true ;
353315 } catch ( e ) {
354- console . error (
355- `Download attachment error for record ${ JSON . stringify (
356- record ,
357- null ,
358- 2
359- ) } `,
360- e
361- ) ;
316+ console . error ( `Download attachment error for record ${ JSON . stringify ( record , null , 2 ) } ` , e ) ;
362317 }
363318 return false ;
364319 }
@@ -409,9 +364,9 @@ export abstract class AbstractAttachmentQueue<
409364 }
410365 record = await this . getNextUploadRecord ( ) ;
411366 }
412- console . debug ( " Finished uploading attachments" ) ;
367+ console . debug ( ' Finished uploading attachments' ) ;
413368 } catch ( error ) {
414- console . error ( " Upload failed:" , error ) ;
369+ console . error ( ' Upload failed:' , error ) ;
415370 } finally {
416371 this . uploading = false ;
417372 }
@@ -473,9 +428,9 @@ export abstract class AbstractAttachmentQueue<
473428 }
474429 await this . downloadRecord ( record ) ;
475430 }
476- console . debug ( " Finished downloading attachments" ) ;
431+ console . debug ( ' Finished downloading attachments' ) ;
477432 } catch ( e ) {
478- console . error ( " Downloads failed:" , e ) ;
433+ console . error ( ' Downloads failed:' , e ) ;
479434 } finally {
480435 this . downloading = false ;
481436 }
@@ -486,8 +441,7 @@ export abstract class AbstractAttachmentQueue<
486441 }
487442
488443 async expireCache ( ) {
489- const res = await this . powersync
490- . getAll < AttachmentRecord > ( `SELECT * FROM ${ this . table }
444+ const res = await this . powersync . getAll < AttachmentRecord > ( `SELECT * FROM ${ this . table }
491445 WHERE
492446 state = ${ AttachmentState . SYNCED } OR state = ${ AttachmentState . ARCHIVED }
493447 ORDER BY
0 commit comments