Skip to content

Commit 6aca992

Browse files
expose internal_last_applied_checkpoint_request_id in the sync status.
1 parent d40c8da commit 6aca992

6 files changed

Lines changed: 67 additions & 21 deletions

File tree

crates/core/src/sync/storage_adapter.rs

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -134,7 +134,7 @@ impl StorageAdapter {
134134
downloading: None,
135135
streams,
136136
// Checkpoint requests should not be made or compared while offline.
137-
internal_applied_checkpoint_request_id: None,
137+
internal_last_applied_checkpoint_request_id: None,
138138
})
139139
}
140140

crates/core/src/sync/streaming_sync.rs

Lines changed: 7 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -967,6 +967,13 @@ impl StreamingSyncIteration {
967967
timestamp: TimestampMicros,
968968
applied_checkpoint_request_id: Option<i64>,
969969
) {
970+
if let Some(request_id) = applied_checkpoint_request_id {
971+
event.instructions.push(Instruction::LogLine {
972+
severity: LogSeverity::DEBUG,
973+
line: format!("Applied checkpoint request id {request_id}").into(),
974+
});
975+
}
976+
970977
event.instructions.push(Instruction::DidCompleteSync {
971978
applied_checkpoint_request_id,
972979
});

crates/core/src/sync/sync_status.rs

Lines changed: 11 additions & 8 deletions
Original file line numberDiff line numberDiff line change
@@ -53,11 +53,11 @@ pub struct DownloadSyncStatus {
5353
/// received), information about how far the download has progressed.
5454
pub downloading: Option<SyncDownloadProgress>,
5555
pub streams: Vec<ActiveStreamSubscription>,
56-
/// Runtime-only request id from the most recent full checkpoint apply.
56+
/// Runtime-only request id from the most recent applied checkpoint request.
5757
///
5858
/// This is exposed in sync status for SDK internals, but it is not persisted and should not be
5959
/// treated as user-facing download progress.
60-
pub internal_applied_checkpoint_request_id: Option<i64>,
60+
pub internal_last_applied_checkpoint_request_id: Option<i64>,
6161
}
6262

6363
impl DownloadSyncStatus {
@@ -72,13 +72,14 @@ impl DownloadSyncStatus {
7272
self.connected = false;
7373
self.connecting = false;
7474
self.downloading = None;
75+
self.internal_last_applied_checkpoint_request_id = None;
7576
}
7677

7778
pub fn start_connecting(&mut self) {
7879
self.connected = false;
7980
self.downloading = None;
8081
self.connecting = true;
81-
self.internal_applied_checkpoint_request_id = None;
82+
self.internal_last_applied_checkpoint_request_id = None;
8283
self.debug_assert_priority_status_is_sorted();
8384
}
8485

@@ -137,7 +138,7 @@ impl DownloadSyncStatus {
137138
has_synced: Some(true),
138139
});
139140

140-
self.internal_applied_checkpoint_request_id = applied_checkpoint_request_id;
141+
self.internal_last_applied_checkpoint_request_id = applied_checkpoint_request_id;
141142
}
142143
}
143144

@@ -149,7 +150,7 @@ impl Default for DownloadSyncStatus {
149150
downloading: None,
150151
priority_status: Vec::new(),
151152
streams: Vec::new(),
152-
internal_applied_checkpoint_request_id: None,
153+
internal_last_applied_checkpoint_request_id: None,
153154
}
154155
}
155156
}
@@ -193,15 +194,17 @@ impl Serialize for DownloadSyncStatus {
193194
}
194195
}
195196

196-
let field_count = 5 + usize::from(self.internal_applied_checkpoint_request_id.is_some());
197+
let field_count =
198+
5 + usize::from(self.internal_last_applied_checkpoint_request_id.is_some());
197199
let mut serializer = serializer.serialize_struct("DownloadSyncStatus", field_count)?;
198200
serializer.serialize_field("connected", &self.connected)?;
199201
serializer.serialize_field("connecting", &self.connecting)?;
200202
serializer.serialize_field("priority_status", &self.priority_status)?;
201203
serializer.serialize_field("downloading", &self.downloading)?;
202204
serializer.serialize_field("streams", &SerializeStreamsWithProgress(self))?;
203-
if let Some(request_id) = self.internal_applied_checkpoint_request_id {
204-
serializer.serialize_field("internal_applied_checkpoint_request_id", &request_id)?;
205+
if let Some(request_id) = self.internal_last_applied_checkpoint_request_id {
206+
serializer
207+
.serialize_field("internal_last_applied_checkpoint_request_id", &request_id)?;
205208
}
206209

207210
serializer.end()

dart/test/sync_test.dart

Lines changed: 40 additions & 6 deletions
Original file line numberDiff line numberDiff line change
@@ -407,7 +407,7 @@ void _syncTests<T>({
407407
containsPair(
408408
'status',
409409
containsPair(
410-
'internal_applied_checkpoint_request_id', anything))))),
410+
'internal_last_applied_checkpoint_request_id', anything))))),
411411
);
412412
expect(
413413
instructions,
@@ -624,8 +624,8 @@ void _syncTests<T>({
624624
'UpdateSyncStatus',
625625
containsPair(
626626
'status',
627-
containsPair(
628-
'internal_applied_checkpoint_request_id', anything))))),
627+
containsPair('internal_last_applied_checkpoint_request_id',
628+
anything))))),
629629
);
630630
expect(lastAppliedCheckpointRequestId(), isNull);
631631

@@ -652,13 +652,18 @@ void _syncTests<T>({
652652
),
653653
),
654654
);
655+
expect(
656+
appliedInstructions,
657+
contains(containsPair('LogLine',
658+
{'severity': 'DEBUG', 'line': 'Applied checkpoint request id 1'})),
659+
);
655660
expect(
656661
appliedInstructions,
657662
contains(containsPair(
658663
'UpdateSyncStatus',
659664
containsPair(
660665
'status',
661-
containsPair('internal_applied_checkpoint_request_id', 1),
666+
containsPair('internal_last_applied_checkpoint_request_id', 1),
662667
),
663668
)),
664669
);
@@ -678,7 +683,7 @@ void _syncTests<T>({
678683
containsPair(
679684
'status',
680685
containsPair(
681-
'internal_applied_checkpoint_request_id', anything))))),
686+
'internal_last_applied_checkpoint_request_id', anything))))),
682687
);
683688

684689
final [row] = db.select('select powersync_offline_sync_status();');
@@ -691,6 +696,29 @@ void _syncTests<T>({
691696
db.select(r"SELECT * FROM ps_buckets WHERE name = '$local'"), isEmpty);
692697
});
693698

699+
syncTest('disconnect clears applied checkpoint request id from status', (_) {
700+
invokeControl('start', null);
701+
702+
pushCheckpoint(buckets: priorityBuckets, writeCheckpoint: '1');
703+
pushCheckpointComplete();
704+
705+
final instructions = invokeControl('stop', null);
706+
expect(
707+
instructions,
708+
contains(containsPair(
709+
'UpdateSyncStatus',
710+
containsPair(
711+
'status',
712+
allOf(
713+
containsPair('connected', false),
714+
isNot(containsPair(
715+
'internal_last_applied_checkpoint_request_id', anything)),
716+
),
717+
),
718+
)),
719+
);
720+
});
721+
694722
syncTest('local writes clear checkpoint request high-water marks', (_) {
695723
invokeControl('start', null);
696724

@@ -1160,7 +1188,13 @@ void _syncTests<T>({
11601188

11611189
// Now complete the upload process.
11621190
probeLocalTargetOp(1);
1163-
invokeControl('completed_upload', null);
1191+
final uploadCompleteInstructions =
1192+
invokeControl('completed_upload', null);
1193+
expect(
1194+
uploadCompleteInstructions,
1195+
contains(containsPair('LogLine',
1196+
{'severity': 'DEBUG', 'line': 'Applied checkpoint request id 1'})),
1197+
);
11641198

11651199
// This should apply the pending write checkpoint.
11661200
expect(fetchRows(), [

docs/sync.md

Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -64,7 +64,7 @@ what SDKs need to do.
6464
ids; it does not update that gate.
6565
- Resolve explicit checkpoint waiters from `DidCompleteSync.applied_checkpoint_request_id`. SDKs
6666
that drive waiters from status snapshots can also watch
67-
`UpdateSyncStatus.status.internal_applied_checkpoint_request_id`. Treat that status field as
67+
`UpdateSyncStatus.status.internal_last_applied_checkpoint_request_id`. Treat that status field as
6868
runtime-only SDK state, not persisted checkpoint state or app-visible progress.
6969

7070
Most `powersync_control` commands return a JSON-encoded array of instructions for the client.
@@ -107,7 +107,7 @@ interface UpdateSyncStatus {
107107
priority_status: [],
108108
downloading: null | DownloadProgress,
109109
streams: [],
110-
internal_applied_checkpoint_request_id?: number,
110+
internal_last_applied_checkpoint_request_id?: number,
111111
}
112112

113113
interface DidCompleteSync {

docs/write-checkpoint-requests.md

Lines changed: 6 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -121,10 +121,12 @@ DidCompleteSync { applied_checkpoint_request_id?: checkpoint.write_checkpoint }
121121
SDKs should resolve `requestCheckpoint()` / `waitForSync()` waiters when this value is greater than
122122
or equal to the requested id.
123123

124-
Core also includes the same applied request id in
125-
`UpdateSyncStatus.status.internal_applied_checkpoint_request_id` for the status update emitted with
126-
the completed checkpoint. SDKs may use that for status-stream waiters, but should treat it as
127-
internal, runtime-only state rather than app-visible progress or persisted checkpoint state.
124+
Core also includes the applied request id in
125+
`UpdateSyncStatus.status.internal_last_applied_checkpoint_request_id` when a status update follows a
126+
checkpoint apply with a `write_checkpoint`. Later status updates without an applied request id,
127+
including disconnect updates, clear the field. SDKs may use this for status-stream waiters, but
128+
should treat it as internal, runtime-only state rather than app-visible progress or persisted
129+
checkpoint state.
128130

129131
## Control Commands
130132

0 commit comments

Comments
 (0)