@@ -3,7 +3,6 @@ import Logger, { ILogger } from 'js-logger';
33import { SyncStatus , SyncStatusOptions } from '../../../db/crud/SyncStatus.js' ;
44import { AbortOperation } from '../../../utils/AbortOperation.js' ;
55import { BaseListener , BaseObserver , BaseObserverInterface , Disposable } from '../../../utils/BaseObserver.js' ;
6- import { throttleLeadingTrailing } from '../../../utils/async.js' ;
76import { BucketStorageAdapter , PowerSyncControlCommand } from '../bucket/BucketStorageAdapter.js' ;
87import { CrudEntry } from '../bucket/CrudEntry.js' ;
98import { AbstractRemote , FetchStrategy , SyncStreamOptions } from './AbstractRemote.js' ;
@@ -17,7 +16,7 @@ import {
1716 doneResult ,
1817 injectable ,
1918 InjectableIterator ,
20- map ,
19+ notifyIterator ,
2120 SimpleAsyncIterator ,
2221 valueResult
2322} from '../../../utils/stream_transform.js' ;
@@ -221,16 +220,13 @@ export abstract class AbstractStreamingSyncImplementation
221220{
222221 protected options : AbstractStreamingSyncImplementationOptions ;
223222 protected abortController : AbortController | null ;
224- // In rare cases, mostly for tests, uploads can be triggered without being properly connected.
225- // This allows ensuring that all upload processes can be aborted.
226- protected uploadAbortController : AbortController | undefined ;
227223 protected crudUpdateListener ?: ( ) => void ;
228224 protected streamingSyncPromise ?: Promise < void > ;
229225 protected logger : ILogger ;
230226 private activeStreams : SubscribedStream [ ] ;
231227 private connectionMayHaveChanged = false ;
228+ private crudUploadNotifier = notifyIterator ( ) ;
232229
233- private isUploadingCrud : boolean = false ;
234230 private notifyCompletedUploads ?: ( ) => void ;
235231 private handleActiveStreamsChange ?: ( ) => void ;
236232
@@ -254,17 +250,7 @@ export abstract class AbstractStreamingSyncImplementation
254250 } ) ;
255251 this . abortController = null ;
256252
257- this . triggerCrudUpload = throttleLeadingTrailing ( ( ) => {
258- if ( ! this . syncStatus . connected || this . isUploadingCrud ) {
259- return ;
260- }
261-
262- this . isUploadingCrud = true ;
263- this . _uploadAllCrud ( ) . finally ( ( ) => {
264- this . notifyCompletedUploads ?.( ) ;
265- this . isUploadingCrud = false ;
266- } ) ;
267- } , this . options . crudUploadThrottleMs ! ) ;
253+ this . triggerCrudUpload = ( ) => this . crudUploadNotifier . notify ( ) ;
268254 }
269255
270256 async waitForReady ( ) { }
@@ -320,7 +306,6 @@ export abstract class AbstractStreamingSyncImplementation
320306 super . dispose ( ) ;
321307 this . crudUpdateListener ?.( ) ;
322308 this . crudUpdateListener = undefined ;
323- this . uploadAbortController ?. abort ( ) ;
324309 }
325310
326311 abstract obtainLock < T > ( lockOptions : LockOptions < T > ) : Promise < T > ;
@@ -334,7 +319,19 @@ export abstract class AbstractStreamingSyncImplementation
334319 return checkpoint ;
335320 }
336321
337- protected async _uploadAllCrud ( ) : Promise < void > {
322+ private async crudUploadLoop ( signal : AbortSignal ) : Promise < void > {
323+ while ( ! signal . aborted ) {
324+ await Promise . all ( [
325+ // Start the initial CRUD upload on connect. Then, keep polling until we're done.
326+ this . _uploadAllCrud ( signal ) ,
327+ this . delayRetry ( signal , this . options . crudUploadThrottleMs ! )
328+ ] ) ;
329+
330+ await this . crudUploadNotifier . next ( ) ;
331+ }
332+ }
333+
334+ private async _uploadAllCrud ( signal : AbortSignal ) : Promise < void > {
338335 return this . obtainLock ( {
339336 type : LockType . CRUD ,
340337 callback : async ( ) => {
@@ -343,17 +340,7 @@ export abstract class AbstractStreamingSyncImplementation
343340 */
344341 let checkedCrudItem : CrudEntry | undefined ;
345342
346- const controller = new AbortController ( ) ;
347- this . uploadAbortController = controller ;
348- this . abortController ?. signal . addEventListener (
349- 'abort' ,
350- ( ) => {
351- controller . abort ( ) ;
352- } ,
353- { once : true }
354- ) ;
355-
356- while ( ! controller . signal . aborted ) {
343+ while ( ! signal . aborted ) {
357344 try {
358345 /**
359346 * This is the first item in the FIFO CRUD queue.
@@ -398,7 +385,7 @@ The next upload iteration will be delayed.`);
398385 uploadError : ex as Error
399386 }
400387 } ) ;
401- await this . delayRetry ( controller . signal ) ;
388+ await this . delayRetry ( signal ) ;
402389 if ( ! this . isConnected ) {
403390 // Exit the upload loop if the sync stream is no longer connected
404391 break ;
@@ -407,14 +394,15 @@ The next upload iteration will be delayed.`);
407394 `Caught exception when uploading. Upload will retry after a delay. Exception: ${ ( ex as Error ) . message } `
408395 ) ;
409396 } finally {
397+ this . notifyCompletedUploads ?.( ) ;
398+
410399 this . updateSyncStatus ( {
411400 dataFlow : {
412401 uploading : false
413402 }
414403 } ) ;
415404 }
416405 }
417- this . uploadAbortController = undefined ;
418406 }
419407 } ) ;
420408 }
@@ -469,15 +457,7 @@ The next upload iteration will be delayed.`);
469457 this . updateSyncStatus ( { connected : false , connecting : false } ) ;
470458 }
471459
472- /**
473- * @deprecated use [connect instead]
474- */
475- async streamingSync ( signal ?: AbortSignal , options ?: PowerSyncConnectionOptions ) : Promise < void > {
476- if ( ! signal ) {
477- this . abortController = new AbortController ( ) ;
478- signal = this . abortController . signal ;
479- }
480-
460+ private async streamingSync ( signal : AbortSignal , options ?: PowerSyncConnectionOptions ) : Promise < void > {
481461 /**
482462 * Listen for CRUD updates and trigger upstream uploads
483463 */
@@ -509,6 +489,8 @@ The next upload iteration will be delayed.`);
509489 } ) ;
510490 } ) ;
511491
492+ this . crudUploadLoop ( signal ) ;
493+
512494 /**
513495 * This loops runs until [retry] is false or the abort signal is set to aborted.
514496 * Aborting the nestedAbortController will:
@@ -902,14 +884,13 @@ The next upload iteration will be delayed.`);
902884 this . iterateListeners ( ( cb ) => cb . statusUpdated ?.( options ) ) ;
903885 }
904886
905- private async delayRetry ( signal ?: AbortSignal ) : Promise < void > {
887+ private async delayRetry ( signal ?: AbortSignal , delay = this . options . retryDelayMs ) : Promise < void > {
906888 return new Promise ( ( resolve ) => {
907889 if ( signal ?. aborted ) {
908890 // If the signal is already aborted, resolve immediately
909891 resolve ( ) ;
910892 return ;
911893 }
912- const { retryDelayMs } = this . options ;
913894
914895 let timeoutId : ReturnType < typeof setTimeout > | undefined ;
915896
@@ -923,7 +904,7 @@ The next upload iteration will be delayed.`);
923904 } ;
924905
925906 signal ?. addEventListener ( 'abort' , endDelay , { once : true } ) ;
926- timeoutId = setTimeout ( endDelay , retryDelayMs ) ;
907+ timeoutId = setTimeout ( endDelay , delay ) ;
927908 } ) ;
928909 }
929910
0 commit comments