Skip to content

Commit 72f88ba

Browse files
committed
Add offline sync state helper function
1 parent b33dd61 commit 72f88ba

File tree

3 files changed

+103
-0
lines changed

3 files changed

+103
-0
lines changed

crates/core/src/sync/interface.rs

Lines changed: 30 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -4,9 +4,11 @@ use core::ffi::{c_int, c_void};
44
use super::streaming_sync::SyncClient;
55
use super::sync_status::DownloadSyncStatus;
66
use crate::constants::SUBTYPE_JSON;
7+
use crate::create_sqlite_text_fn;
78
use crate::error::PowerSyncError;
89
use crate::schema::Schema;
910
use crate::state::DatabaseState;
11+
use crate::sync::storage_adapter::StorageAdapter;
1012
use crate::sync::subscriptions::{StreamKey, apply_subscriptions};
1113
use alloc::borrow::Cow;
1214
use alloc::boxed::Box;
@@ -285,5 +287,33 @@ pub fn register(db: *mut sqlite::sqlite3, state: Arc<DatabaseState>) -> Result<(
285287
Some(destroy),
286288
)?;
287289

290+
db.create_function_v2(
291+
"powersync_offline_sync_status",
292+
0,
293+
sqlite::UTF8 | sqlite::DIRECTONLY | SQLITE_RESULT_SUBTYPE,
294+
None,
295+
Some(powersync_offline_sync_status),
296+
None,
297+
None,
298+
None,
299+
)?;
300+
288301
Ok(())
289302
}
303+
304+
fn powersync_offline_sync_status_impl(
305+
ctx: *mut sqlite::context,
306+
_args: &[*mut sqlite::value],
307+
) -> Result<String, PowerSyncError> {
308+
let adapter = StorageAdapter::new(ctx.db_handle())?;
309+
let state = adapter.offline_sync_state()?;
310+
let serialized = serde_json::to_string(&state).map_err(PowerSyncError::internal)?;
311+
312+
Ok(serialized)
313+
}
314+
315+
create_sqlite_text_fn!(
316+
powersync_offline_sync_status,
317+
powersync_offline_sync_status_impl,
318+
"powersync_offline_sync_status"
319+
);

dart/test/sync_stream_test.dart

Lines changed: 57 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -591,4 +591,61 @@ void main() {
591591
expect(instructions,
592592
contains(containsPair('CloseSyncStream', {'hide_disconnect': true})));
593593
});
594+
595+
syncTest('persists stream state', (_) {
596+
control(
597+
'subscriptions',
598+
json.encode({
599+
'subscribe': {
600+
'stream': {'name': 'a'},
601+
}
602+
}),
603+
);
604+
605+
control(
606+
'start',
607+
json.encode({
608+
'active_streams': [
609+
{'name': 'a'}
610+
]
611+
}),
612+
);
613+
control(
614+
'line_text',
615+
json.encode(
616+
checkpoint(
617+
lastOpId: 1,
618+
buckets: [
619+
bucketDescription(
620+
'a',
621+
subscriptions: [
622+
{'sub': 0}
623+
],
624+
priority: 1,
625+
)
626+
],
627+
streams: [stream('a', false)],
628+
),
629+
),
630+
);
631+
control('line_text', json.encode(checkpointComplete()));
632+
633+
final [row] = db.select('select powersync_offline_sync_status();');
634+
expect(
635+
json.decode(row[0]),
636+
containsPair('streams', [
637+
{
638+
'name': 'a',
639+
'parameters': null,
640+
// not persisted, only needed for download progress
641+
'associated_buckets': [],
642+
'priority': null, // same
643+
'active': true,
644+
'is_default': false,
645+
'has_explicit_subscription': true,
646+
'expires_at': 1740909600,
647+
'last_synced_at': 1740823200
648+
}
649+
]));
650+
});
594651
}

dart/test/sync_test.dart

Lines changed: 16 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -317,6 +317,22 @@ void _syncTests<T>({
317317
),
318318
),
319319
);
320+
321+
final [row] = db.select('select powersync_offline_sync_status();');
322+
expect(json.decode(row[0]), {
323+
'connected': false,
324+
'connecting': false,
325+
'priority_status': [
326+
{'priority': 2, 'last_synced_at': 1740823800, 'has_synced': true},
327+
{
328+
'priority': 2147483647,
329+
'last_synced_at': 1740823200,
330+
'has_synced': true
331+
}
332+
],
333+
'downloading': null,
334+
'streams': [],
335+
});
320336
});
321337

322338
test('clearing database clears sync status', () {

0 commit comments

Comments
 (0)