Skip to content

Commit a6d73fa

Browse files
expose current checkpoint request id for retries
1 parent 6aca992 commit a6d73fa

4 files changed

Lines changed: 64 additions & 3 deletions

File tree

crates/core/src/sync/interface.rs

Lines changed: 8 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -289,6 +289,14 @@ pub fn register(db: *mut sqlite::sqlite3, state: Rc<DatabaseState>) -> Result<()
289289

290290
return Ok(());
291291
}
292+
"current_checkpoint_request_id" => {
293+
let adapter = state.storage_adapter(db)?;
294+
match adapter.last_checkpoint_request_id()? {
295+
Some(request_id) => ctx.result_int64(request_id),
296+
None => ctx.result_null(),
297+
}
298+
return Ok(());
299+
}
292300
"line_text" => SyncControlRequest::SyncEvent(SyncEvent::TextLine {
293301
data: if payload.value_type() == ColumnType::Text {
294302
payload.text()

dart/test/sync_test.dart

Lines changed: 29 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -197,6 +197,10 @@ void _syncTests<T>({
197197
return invokeControlScalar('local_target_op', opId);
198198
}
199199

200+
Object? currentCheckpointRequestId() {
201+
return invokeControlScalar('current_checkpoint_request_id', null);
202+
}
203+
200204
ResultSet fetchRows() {
201205
return db.select('select * from items');
202206
}
@@ -462,6 +466,31 @@ void _syncTests<T>({
462466
expect(lastRequestedCheckpointRequestId(), 3);
463467
});
464468

469+
syncTest('reports current checkpoint request id without incrementing', (_) {
470+
expect(currentCheckpointRequestId(), isNull);
471+
472+
invokeControlRaw('start', null);
473+
invokeControlRaw('seed_checkpoint_request_id', 1);
474+
expect(currentCheckpointRequestId(), 1);
475+
476+
expect(currentCheckpointRequestId(), 1);
477+
expect(nextCheckpointRequestId(), 2);
478+
expect(currentCheckpointRequestId(), 2);
479+
expect(lastRequestedCheckpointRequestId(), 2);
480+
481+
pushCheckpoint(buckets: priorityBuckets, writeCheckpoint: '2');
482+
pushCheckpointComplete();
483+
expect(lastAppliedCheckpointRequestId(), 2);
484+
expect(currentCheckpointRequestId(), 2);
485+
486+
db.execute("insert into items (id, col) values ('local', 'data');");
487+
expect(lastAppliedCheckpointRequestId(), isNull);
488+
expect(currentCheckpointRequestId(), 2);
489+
490+
expect(nextCheckpointRequestId(), 3);
491+
expect(currentCheckpointRequestId(), 3);
492+
});
493+
465494
syncTest('seeds requested checkpoint request ids from service state', (_) {
466495
final startInstructions = invokeControlRaw('start', null);
467496
expect(streamLastCheckpointRequestId(startInstructions), isNull);

docs/sync.md

Lines changed: 10 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -38,11 +38,14 @@ The following commands are supported:
3838
10. `next_checkpoint_request_id`: No payload. During an active sync iteration after checkpoint
3939
request state exists locally, allocates and returns the next checkpoint request id as an
4040
integer result.
41-
11. `local_target_op`: Payload is `null`, an integer, or an integer string. Probes, updates or
41+
11. `current_checkpoint_request_id`: No payload. Returns the current checkpoint request sequence
42+
value as an integer result, or SQL `NULL` if absent. This command does not allocate a new id and
43+
can run outside a sync iteration.
44+
12. `local_target_op`: Payload is `null`, an integer, or an integer string. Probes, updates or
4245
clears the local target op and returns the previously-observed value as an integer result, or
4346
SQL `NULL` if there was no target. This command can run outside of a sync iteration and does not
4447
affect it.
45-
12. `seed_checkpoint_request_id`: Payload is a positive integer or integer string. After receiving
48+
13. `seed_checkpoint_request_id`: Payload is a positive integer or integer string. After receiving
4649
`EstablishSyncStream`, SDKs should reconcile the local hint with service-side
4750
checkpoint-request state, then seed core with the accepted positive id.
4851

@@ -62,13 +65,17 @@ what SDKs need to do.
6265
id to the service, then store the accepted id with `powersync_control('local_target_op', id)`.
6366
- `local_target_op` is the apply gate for local writes. `next_checkpoint_request_id` only allocates
6467
ids; it does not update that gate.
68+
- To retry a checkpoint request without incrementing the counter, read
69+
`powersync_control('current_checkpoint_request_id', NULL)` and repost that id when the SDK's
70+
runtime last-applied checkpoint request id is absent or lower.
6571
- Resolve explicit checkpoint waiters from `DidCompleteSync.applied_checkpoint_request_id`. SDKs
6672
that drive waiters from status snapshots can also watch
6773
`UpdateSyncStatus.status.internal_last_applied_checkpoint_request_id`. Treat that status field as
6874
runtime-only SDK state, not persisted checkpoint state or app-visible progress.
6975

7076
Most `powersync_control` commands return a JSON-encoded array of instructions for the client.
71-
`next_checkpoint_request_id` and `local_target_op` return scalar values directly.
77+
`next_checkpoint_request_id`, `current_checkpoint_request_id` and `local_target_op` return values
78+
directly.
7279

7380
```typescript
7481
type Instruction = { LogLine: LogLine }

docs/write-checkpoint-requests.md

Lines changed: 17 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -59,6 +59,16 @@ the service lost its record, posting the local hint recreates the service-side s
5959
seeded request counter. SDKs should wait for the connection reconciliation and seed step before
6060
creating checkpoint requests.
6161

62+
To retry an existing checkpoint request without advancing the counter, SDKs can read:
63+
64+
```text
65+
powersync_control('current_checkpoint_request_id', NULL)
66+
```
67+
68+
Core returns the current sequence value as a SQLite integer, or SQL `NULL` when the counter has not
69+
been seeded. SDKs can compare this with their runtime last-applied checkpoint request id and repost
70+
the current id when the applied id is absent or lower.
71+
6272
## Local Write Gate
6373

6474
A local write records CRUD and sets:
@@ -145,6 +155,13 @@ checkpoint state.
145155
to the service, retrying posts the same id again, which is safe because the service treats the
146156
latest posted id as effective state.
147157

158+
`powersync_control('current_checkpoint_request_id', NULL)`
159+
160+
- Returns: current checkpoint request sequence value as a SQLite integer, or SQL `NULL` if absent.
161+
- Does not allocate a new id.
162+
- SDKs should compare this with their runtime last-applied checkpoint request id to decide whether
163+
to repost the current id.
164+
148165
`powersync_control('local_target_op', value)`
149166

150167
- Payload `NULL`: return current target without changing it.

0 commit comments

Comments
 (0)