@@ -144,6 +144,8 @@ export class WalStream {
144144 */
145145 private isStartingReplication = true ;
146146
147+ private initialSnapshotPromise : Promise < void > | null = null ;
148+
147149 constructor ( options : WalStreamOptions ) {
148150 this . logger = options . logger ?? defaultLogger ;
149151 this . storage = options . storage ;
@@ -442,6 +444,24 @@ WHERE oid = $1::regclass`,
442444 // This makes sure we don't skip any changes applied before starting this snapshot,
443445 // in the case of snapshot retries.
444446 // We could alternatively commit at the replication slot LSN.
447+
448+ // Get the current LSN.
449+ // The data will only be consistent once incremental replication has passed that point.
450+ // We have to get this LSN _after_ we have finished the table snapshots.
451+ //
452+ // There are basically two relevant LSNs here:
453+ // A: The LSN before the snapshot starts. We don't explicitly record this on the PowerSync side,
454+ // but it is implicitly recorded in the replication slot.
455+ // B: The LSN after the table snapshot is complete, which is what we get here.
456+ // When we do the snapshot queries, the data that we get back for each chunk could match the state
457+ // anywhere between A and B. To actually have a consistent state on our side, we need to:
458+ // 1. Complete the snapshot.
459+ // 2. Wait until logical replication has caught up with all the change between A and B.
460+ // Calling `markSnapshotDone(LSN B)` covers that.
461+ const rs = await db . query ( `select pg_current_wal_lsn() as lsn` ) ;
462+ const noCommitBefore = rs . rows [ 0 ] [ 0 ] ;
463+
464+ await batch . markAllSnapshotDone ( noCommitBefore ) ;
445465 await batch . commit ( ZERO_LSN ) ;
446466 }
447467 ) ;
@@ -482,27 +502,11 @@ WHERE oid = $1::regclass`,
482502 // replication afterwards.
483503 await db . query ( 'BEGIN' ) ;
484504 try {
485- let tableLsnNotBefore : string ;
486505 await this . snapshotTable ( batch , db , table , limited ) ;
487506
488- // Get the current LSN.
489- // The data will only be consistent once incremental replication has passed that point.
490- // We have to get this LSN _after_ we have finished the table snapshot.
491- //
492- // There are basically two relevant LSNs here:
493- // A: The LSN before the snapshot starts. We don't explicitly record this on the PowerSync side,
494- // but it is implicitly recorded in the replication slot.
495- // B: The LSN after the table snapshot is complete, which is what we get here.
496- // When we do the snapshot queries, the data that we get back for each chunk could match the state
497- // anywhere between A and B. To actually have a consistent state on our side, we need to:
498- // 1. Complete the snapshot.
499- // 2. Wait until logical replication has caught up with all the change between A and B.
500- // Calling `markSnapshotDone(LSN B)` covers that.
501- const rs = await db . query ( `select pg_current_wal_lsn() as lsn` ) ;
502- tableLsnNotBefore = rs . rows [ 0 ] [ 0 ] ;
503507 // Side note: A ROLLBACK would probably also be fine here, since we only read in this transaction.
504508 await db . query ( 'COMMIT' ) ;
505- const [ resultTable ] = await batch . markSnapshotDone ( [ table ] , tableLsnNotBefore ) ;
509+ const [ resultTable ] = await batch . markTableSnapshotDone ( [ table ] ) ;
506510 this . relationCache . update ( resultTable ) ;
507511 return resultTable ;
508512 } catch ( e ) {
@@ -818,7 +822,8 @@ WHERE oid = $1::regclass`,
818822 // If anything errors here, the entire replication process is halted, and
819823 // all connections automatically closed, including this one.
820824 const initReplicationConnection = await this . connections . replicationConnection ( ) ;
821- await this . initReplication ( initReplicationConnection ) ;
825+ this . initialSnapshotPromise = this . initReplication ( initReplicationConnection ) ;
826+ await this . initialSnapshotPromise ;
822827 await initReplicationConnection . end ( ) ;
823828
824829 // At this point, the above connection has often timed out, so we start a new one
@@ -831,6 +836,18 @@ WHERE oid = $1::regclass`,
831836 }
832837 }
833838
839+ /**
840+ * After calling replicate(), call this to wait for the initial snapshot to complete.
841+ *
842+ * For tests only.
843+ */
844+ async waitForInitialSnapshot ( ) {
845+ if ( this . initialSnapshotPromise == null ) {
846+ throw new ReplicationAssertionError ( `Initial snapshot not started yet` ) ;
847+ }
848+ return this . initialSnapshotPromise ;
849+ }
850+
834851 async initReplication ( replicationConnection : pgwire . PgConnection ) {
835852 const result = await this . initSlot ( ) ;
836853 if ( result . needsInitialSync ) {
0 commit comments