@@ -9,21 +9,23 @@ import {
9
9
} from '@powersync/lib-services-framework' ;
10
10
import {
11
11
BroadcastIterable ,
12
+ CHECKPOINT_INVALIDATE_ALL ,
12
13
CheckpointChanges ,
14
+ deserializeParameterLookup ,
13
15
GetCheckpointChangesOptions ,
14
16
InternalOpId ,
15
17
internalToExternalOpId ,
18
+ mergeAsyncIterables ,
16
19
ProtocolOpId ,
17
20
ReplicationCheckpoint ,
18
21
storage ,
19
22
utils ,
20
23
WatchWriteCheckpointOptions ,
21
- CHECKPOINT_INVALIDATE_ALL ,
22
- deserializeParameterLookup
24
+ WriteCheckpointResult
23
25
} from '@powersync/service-core' ;
24
- import { SqliteJsonRow , ParameterLookup , SqlSyncRules } from '@powersync/service-sync-rules' ;
26
+ import { JSONBig } from '@powersync/service-jsonbig' ;
27
+ import { ParameterLookup , SqliteJsonRow , SqlSyncRules } from '@powersync/service-sync-rules' ;
25
28
import * as bson from 'bson' ;
26
- import { wrapWithAbort } from 'ix/asynciterable/operators/withabort.js' ;
27
29
import { LRUCache } from 'lru-cache' ;
28
30
import * as timers from 'timers/promises' ;
29
31
import { MongoBucketStorage } from '../MongoBucketStorage.js' ;
@@ -41,7 +43,6 @@ import { MongoBucketBatch } from './MongoBucketBatch.js';
41
43
import { MongoCompactor } from './MongoCompactor.js' ;
42
44
import { MongoWriteCheckpointAPI } from './MongoWriteCheckpointAPI.js' ;
43
45
import { idPrefixFilter , mapOpEntry , readSingleBatch } from './util.js' ;
44
- import { JSONBig } from '@powersync/service-jsonbig' ;
45
46
46
47
export class MongoSyncBucketStorage
47
48
extends BaseObserver < storage . SyncRulesBucketStorageListener >
@@ -68,7 +69,8 @@ export class MongoSyncBucketStorage
68
69
this . db = factory . db ;
69
70
this . writeCheckpointAPI = new MongoWriteCheckpointAPI ( {
70
71
db : this . db ,
71
- mode : writeCheckpointMode
72
+ mode : writeCheckpointMode ,
73
+ sync_rules_id : group_id
72
74
} ) ;
73
75
}
74
76
@@ -86,13 +88,6 @@ export class MongoSyncBucketStorage
86
88
) ;
87
89
}
88
90
89
- createCustomWriteCheckpoint ( checkpoint : storage . BatchedCustomWriteCheckpointOptions ) : Promise < bigint > {
90
- return this . writeCheckpointAPI . createCustomWriteCheckpoint ( {
91
- ...checkpoint ,
92
- sync_rules_id : this . group_id
93
- } ) ;
94
- }
95
-
96
91
createManagedWriteCheckpoint ( checkpoint : storage . ManagedWriteCheckpointOptions ) : Promise < bigint > {
97
92
return this . writeCheckpointAPI . createManagedWriteCheckpoint ( checkpoint ) ;
98
93
}
@@ -704,8 +699,7 @@ export class MongoSyncBucketStorage
704
699
if ( doc == null ) {
705
700
// Sync rules not present or not active.
706
701
// Abort the connections - clients will have to retry later.
707
- // Should this error instead?
708
- return ;
702
+ throw new ServiceError ( ErrorCode . PSYNC_S2302 , 'No active sync rules available' ) ;
709
703
}
710
704
711
705
yield this . makeActiveCheckpoint ( doc ) ;
@@ -749,7 +743,7 @@ export class MongoSyncBucketStorage
749
743
}
750
744
if ( doc . state != storage . SyncRuleState . ACTIVE && doc . state != storage . SyncRuleState . ERRORED ) {
751
745
// Sync rules have changed - abort and restart.
752
- // Should this error instead?
746
+ // We do a soft close of the stream here - no error
753
747
break ;
754
748
}
755
749
@@ -772,28 +766,60 @@ export class MongoSyncBucketStorage
772
766
/**
773
767
* User-specific watch on the latest checkpoint and/or write checkpoint.
774
768
*/
775
- async * watchWriteCheckpoint ( options : WatchWriteCheckpointOptions ) : AsyncIterable < storage . StorageCheckpointUpdate > {
776
- const { user_id , signal } = options ;
769
+ async * watchCheckpointChanges ( options : WatchWriteCheckpointOptions ) : AsyncIterable < storage . StorageCheckpointUpdate > {
770
+ const { signal } = options ;
777
771
let lastCheckpoint : utils . InternalOpId | null = null ;
778
772
let lastWriteCheckpoint : bigint | null = null ;
773
+ let lastWriteCheckpointDoc : WriteCheckpointResult | null = null ;
774
+ let nextWriteCheckpoint : bigint | null = null ;
775
+ let lastCheckpointEvent : ReplicationCheckpoint | null = null ;
776
+ let receivedWriteCheckpoint = false ;
777
+
778
+ const writeCheckpointIter = this . writeCheckpointAPI . watchUserWriteCheckpoint ( {
779
+ user_id : options . user_id ,
780
+ signal,
781
+ sync_rules_id : this . group_id
782
+ } ) ;
783
+ const iter = mergeAsyncIterables < ReplicationCheckpoint | storage . WriteCheckpointResult > (
784
+ [ this . sharedIter , writeCheckpointIter ] ,
785
+ signal
786
+ ) ;
779
787
780
- const iter = wrapWithAbort ( this . sharedIter , signal ) ;
781
788
for await ( const event of iter ) {
782
- const { checkpoint, lsn } = event ;
789
+ if ( 'checkpoint' in event ) {
790
+ lastCheckpointEvent = event ;
791
+ } else {
792
+ lastWriteCheckpointDoc = event ;
793
+ receivedWriteCheckpoint = true ;
794
+ }
795
+
796
+ if ( lastCheckpointEvent == null || ! receivedWriteCheckpoint ) {
797
+ // We need to wait until we received at least on checkpoint, and one write checkpoint.
798
+ continue ;
799
+ }
783
800
784
801
// lsn changes are not important by itself.
785
802
// What is important is:
786
803
// 1. checkpoint (op_id) changes.
787
804
// 2. write checkpoint changes for the specific user
788
805
789
- const lsnFilters : Record < string , string > = lsn ? { 1 : lsn } : { } ;
806
+ const lsn = lastCheckpointEvent ?. lsn ;
790
807
791
- const currentWriteCheckpoint = await this . lastWriteCheckpoint ( {
792
- user_id,
793
- heads : {
794
- ...lsnFilters
808
+ if (
809
+ lastWriteCheckpointDoc != null &&
810
+ ( lastWriteCheckpointDoc . lsn == null || ( lsn != null && lsn >= lastWriteCheckpointDoc . lsn ) )
811
+ ) {
812
+ const writeCheckpoint = lastWriteCheckpointDoc . id ;
813
+ if ( nextWriteCheckpoint == null || ( writeCheckpoint != null && writeCheckpoint > nextWriteCheckpoint ) ) {
814
+ nextWriteCheckpoint = writeCheckpoint ;
795
815
}
796
- } ) ;
816
+ // We used the doc - clear it
817
+ lastWriteCheckpointDoc = null ;
818
+ }
819
+
820
+ const { checkpoint } = lastCheckpointEvent ;
821
+
822
+ const currentWriteCheckpoint = nextWriteCheckpoint ;
797
823
798
824
if ( currentWriteCheckpoint == lastWriteCheckpoint && checkpoint == lastCheckpoint ) {
799
825
// No change - wait for next one
@@ -815,7 +841,7 @@ export class MongoSyncBucketStorage
815
841
lastCheckpoint = checkpoint ;
816
842
817
843
yield {
818
- base : event ,
844
+ base : lastCheckpointEvent ,
819
845
writeCheckpoint : currentWriteCheckpoint ,
820
846
update : updates
821
847
} ;
0 commit comments