@@ -14,7 +14,7 @@ import {
14
14
import { AbstractRemote } from './AbstractRemote' ;
15
15
import ndjsonStream from 'can-ndjson-stream' ;
16
16
import { BucketChecksum , BucketStorageAdapter , Checkpoint } from '../bucket/BucketStorageAdapter' ;
17
- import { SyncStatus } from '../../../db/crud/SyncStatus' ;
17
+ import { SyncStatus , SyncStatusOptions } from '../../../db/crud/SyncStatus' ;
18
18
import { SyncDataBucket } from '../bucket/SyncDataBucket' ;
19
19
import { BaseObserver , BaseListener } from '../../../utils/BaseObserver' ;
20
20
@@ -48,31 +48,38 @@ export const DEFAULT_STREAMING_SYNC_OPTIONS = {
48
48
logger : Logger . get ( 'PowerSyncStream' )
49
49
} ;
50
50
51
+ const CRUD_UPLOAD_DEBOUNCE_MS = 1000 ;
52
+
51
53
export abstract class AbstractStreamingSyncImplementation extends BaseObserver < StreamingSyncImplementationListener > {
52
- protected _lastSyncedAt : Date ;
54
+ protected _lastSyncedAt : Date | null ;
53
55
protected options : AbstractStreamingSyncImplementationOptions ;
54
56
55
- private isUploadingCrud : boolean ;
56
-
57
- protected _isConnected : boolean ;
57
+ syncStatus : SyncStatus ;
58
58
59
59
constructor ( options : AbstractStreamingSyncImplementationOptions ) {
60
60
super ( ) ;
61
61
this . options = { ...DEFAULT_STREAMING_SYNC_OPTIONS , ...options } ;
62
- this . isUploadingCrud = false ;
63
- this . _isConnected = false ;
62
+ this . syncStatus = new SyncStatus ( {
63
+ connected : false ,
64
+ lastSyncedAt : null ,
65
+ dataFlow : {
66
+ uploading : false ,
67
+ downloading : false
68
+ }
69
+ } ) ;
64
70
}
65
71
66
72
get lastSyncedAt ( ) {
67
- return new Date ( this . _lastSyncedAt ) ;
73
+ const lastSynced = this . syncStatus . lastSyncedAt ;
74
+ return lastSynced && new Date ( lastSynced ) ;
68
75
}
69
76
70
77
protected get logger ( ) {
71
78
return this . options . logger ! ;
72
79
}
73
80
74
81
get isConnected ( ) {
75
- return this . _isConnected ;
82
+ return this . syncStatus . connected ;
76
83
}
77
84
78
85
abstract obtainLock < T > ( lockOptions : LockOptions < T > ) : Promise < T > ;
@@ -81,29 +88,51 @@ export abstract class AbstractStreamingSyncImplementation extends BaseObserver<S
81
88
return this . options . adapter . hasCompletedSync ( ) ;
82
89
}
83
90
84
- triggerCrudUpload ( ) {
85
- if ( this . isUploadingCrud ) {
86
- return ;
87
- }
88
- this . _uploadAllCrud ( ) ;
89
- }
91
+ triggerCrudUpload = _ . debounce (
92
+ ( ) => {
93
+ if ( ! this . syncStatus . connected || this . syncStatus . dataFlowStatus . uploading ) {
94
+ return ;
95
+ }
96
+ this . _uploadAllCrud ( ) ;
97
+ } ,
98
+ CRUD_UPLOAD_DEBOUNCE_MS ,
99
+ { trailing : true }
100
+ ) ;
90
101
91
102
protected async _uploadAllCrud ( ) : Promise < void > {
92
- this . isUploadingCrud = true ;
93
- while ( true ) {
94
- try {
95
- const done = await this . uploadCrudBatch ( ) ;
96
- if ( done ) {
97
- this . isUploadingCrud = false ;
98
- break ;
103
+ return this . obtainLock ( {
104
+ type : LockType . CRUD ,
105
+ callback : async ( ) => {
106
+ this . updateSyncStatus ( {
107
+ dataFlow : {
108
+ uploading : true
109
+ }
110
+ } ) ;
111
+ while ( true ) {
112
+ try {
113
+ const done = await this . uploadCrudBatch ( ) ;
114
+ if ( done ) {
115
+ break ;
116
+ }
117
+ } catch ( ex ) {
118
+ this . updateSyncStatus ( {
119
+ connected : false ,
120
+ dataFlow : {
121
+ uploading : false
122
+ }
123
+ } ) ;
124
+ await this . delayRetry ( ) ;
125
+ break ;
126
+ } finally {
127
+ this . updateSyncStatus ( {
128
+ dataFlow : {
129
+ uploading : false
130
+ }
131
+ } ) ;
132
+ }
99
133
}
100
- } catch ( ex ) {
101
- this . updateSyncStatus ( false ) ;
102
- await this . delayRetry ( ) ;
103
- this . isUploadingCrud = false ;
104
- break ;
105
134
}
106
- }
135
+ } ) ;
107
136
}
108
137
109
138
protected async uploadCrudBatch ( ) : Promise < boolean > {
@@ -123,6 +152,15 @@ export abstract class AbstractStreamingSyncImplementation extends BaseObserver<S
123
152
}
124
153
125
154
async streamingSync ( signal ?: AbortSignal ) : Promise < void > {
155
+ signal ?. addEventListener ( 'abort' , ( ) => {
156
+ this . updateSyncStatus ( {
157
+ connected : false ,
158
+ dataFlow : {
159
+ downloading : false
160
+ }
161
+ } ) ;
162
+ } ) ;
163
+
126
164
while ( true ) {
127
165
try {
128
166
if ( signal ?. aborted ) {
@@ -132,7 +170,9 @@ export abstract class AbstractStreamingSyncImplementation extends BaseObserver<S
132
170
// Continue immediately
133
171
} catch ( ex ) {
134
172
this . logger . error ( ex ) ;
135
- this . updateSyncStatus ( false ) ;
173
+ this . updateSyncStatus ( {
174
+ connected : false
175
+ } ) ;
136
176
// On error, wait a little before retrying
137
177
await this . delayRetry ( ) ;
138
178
}
@@ -173,7 +213,13 @@ export abstract class AbstractStreamingSyncImplementation extends BaseObserver<S
173
213
signal
174
214
) ) {
175
215
// A connection is active and messages are being received
176
- this . updateSyncStatus ( true ) ;
216
+ if ( ! this . syncStatus . connected ) {
217
+ // There is a connection now
218
+ _ . defer ( ( ) => this . triggerCrudUpload ( ) ) ;
219
+ this . updateSyncStatus ( {
220
+ connected : true
221
+ } ) ;
222
+ }
177
223
178
224
if ( isStreamingSyncCheckpoint ( line ) ) {
179
225
targetCheckpoint = line . checkpoint ;
@@ -204,7 +250,13 @@ export abstract class AbstractStreamingSyncImplementation extends BaseObserver<S
204
250
} else {
205
251
appliedCheckpoint = _ . clone ( targetCheckpoint ) ;
206
252
this . logger . debug ( 'validated checkpoint' , appliedCheckpoint ) ;
207
- this . updateSyncStatus ( true , new Date ( ) ) ;
253
+ this . updateSyncStatus ( {
254
+ connected : true ,
255
+ lastSyncedAt : new Date ( ) ,
256
+ dataFlow : {
257
+ downloading : false
258
+ }
259
+ } ) ;
208
260
}
209
261
210
262
validatedCheckpoint = _ . clone ( targetCheckpoint ) ;
@@ -242,6 +294,11 @@ export abstract class AbstractStreamingSyncImplementation extends BaseObserver<S
242
294
await this . options . adapter . setTargetCheckpoint ( targetCheckpoint ) ;
243
295
} else if ( isStreamingSyncData ( line ) ) {
244
296
const { data } = line ;
297
+ this . updateSyncStatus ( {
298
+ dataFlow : {
299
+ downloading : true
300
+ }
301
+ } ) ;
245
302
await this . options . adapter . saveSyncData ( { buckets : [ SyncDataBucket . fromRow ( data ) ] } ) ;
246
303
} else if ( isStreamingKeepalive ( line ) ) {
247
304
const remaining_seconds = line . token_expires_in ;
@@ -255,7 +312,10 @@ export abstract class AbstractStreamingSyncImplementation extends BaseObserver<S
255
312
this . logger . debug ( 'Sync complete' ) ;
256
313
257
314
if ( _ . isEqual ( targetCheckpoint , appliedCheckpoint ) ) {
258
- this . updateSyncStatus ( true , new Date ( ) ) ;
315
+ this . updateSyncStatus ( {
316
+ connected : true ,
317
+ lastSyncedAt : new Date ( )
318
+ } ) ;
259
319
} else if ( _ . isEqual ( validatedCheckpoint , targetCheckpoint ) ) {
260
320
const result = await this . options . adapter . syncLocalDatabase ( targetCheckpoint ) ;
261
321
if ( ! result . checkpointValid ) {
@@ -268,7 +328,13 @@ export abstract class AbstractStreamingSyncImplementation extends BaseObserver<S
268
328
// Continue waiting.
269
329
} else {
270
330
appliedCheckpoint = _ . clone ( targetCheckpoint ) ;
271
- this . updateSyncStatus ( true , new Date ( ) ) ;
331
+ this . updateSyncStatus ( {
332
+ connected : true ,
333
+ lastSyncedAt : new Date ( ) ,
334
+ dataFlow : {
335
+ downloading : false
336
+ }
337
+ } ) ;
272
338
}
273
339
}
274
340
}
@@ -300,14 +366,16 @@ export abstract class AbstractStreamingSyncImplementation extends BaseObserver<S
300
366
}
301
367
}
302
368
303
- private updateSyncStatus ( connected : boolean , lastSyncedAt ?: Date ) {
304
- const takeSnapShot = ( ) => [ this . _isConnected , this . _lastSyncedAt ?. valueOf ( ) ] ;
369
+ protected updateSyncStatus ( options : SyncStatusOptions ) {
370
+ const updatedStatus = new SyncStatus ( {
371
+ connected : options . connected ?? this . syncStatus . connected ,
372
+ lastSyncedAt : options . lastSyncedAt ?? this . syncStatus . lastSyncedAt ,
373
+ dataFlow : _ . merge ( _ . clone ( this . syncStatus . dataFlowStatus ) , options . dataFlow ?? { } )
374
+ } ) ;
305
375
306
- const previousValues = takeSnapShot ( ) ;
307
- this . _lastSyncedAt = lastSyncedAt ?? this . lastSyncedAt ;
308
- this . _isConnected = connected ;
309
- if ( ! _ . isEqual ( previousValues , takeSnapShot ( ) ) ) {
310
- this . iterateListeners ( ( cb ) => cb . statusChanged ?.( new SyncStatus ( this . isConnected , this . lastSyncedAt ) ) ) ;
376
+ if ( ! this . syncStatus . isEqual ( updatedStatus ) ) {
377
+ this . syncStatus = updatedStatus ;
378
+ this . iterateListeners ( ( cb ) => cb . statusChanged ?.( updatedStatus ) ) ;
311
379
}
312
380
}
313
381
0 commit comments