Skip to content
Merged
Show file tree
Hide file tree
Changes from 2 commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
5 changes: 5 additions & 0 deletions packages/powersync/lib/sqlite3_common.dart
Original file line number Diff line number Diff line change
@@ -0,0 +1,5 @@
/// Re-exports [sqlite3_common](https://pub.dev/packages/sqlite3) to expose sqlite3_common without
/// adding it as a direct dependency.
library;

export 'package:sqlite_async/sqlite3_common.dart';
35 changes: 34 additions & 1 deletion packages/powersync/lib/src/powersync_database.dart
Original file line number Diff line number Diff line change
Expand Up @@ -153,6 +153,7 @@ class PowerSyncDatabase with SqliteQueries implements SqliteConnection {
await database.initialize();
await database.execute('SELECT powersync_init()');
await updateSchema(schema);
await _updateHasSynced();
}

/// Replace the schema with a new version.
Expand All @@ -175,6 +176,37 @@ class PowerSyncDatabase with SqliteQueries implements SqliteConnection {
return _initialized;
}

Future<void> _updateHasSynced() async {
const syncedSQL =
'SELECT 1 FROM ps_buckets WHERE last_applied_op > 0 LIMIT 1';

// Query the database to see if any data has been synced.
final result = await database.execute(syncedSQL);
final hasSynced = result.rows.isNotEmpty;

if (hasSynced != currentStatus.hasSynced) {
final status = SyncStatus(hasSynced: hasSynced);
_setStatus(status);
}
}

///
/// returns a [Future] which will resolve once the first full sync has completed.
///
Future<void> waitForFirstSync() async {
if (currentStatus.hasSynced ?? false) {
return;
}
final completer = Completer<void>();
statusStream.listen((result) {
if (result.hasSynced ?? false) {
completer.complete();
}
});

return completer.future;
}

@override
bool get closed {
return database.closed;
Expand Down Expand Up @@ -297,7 +329,8 @@ class PowerSyncDatabase with SqliteQueries implements SqliteConnection {

void _setStatus(SyncStatus status) {
if (status != currentStatus) {
currentStatus = status;
currentStatus = status.copyWith(
hasSynced: status.hasSynced ?? status.lastSyncedAt != null);
_statusStreamController.add(status);
}
}
Expand Down
2 changes: 2 additions & 0 deletions packages/powersync/lib/src/streaming_sync.dart
Original file line number Diff line number Diff line change
Expand Up @@ -153,6 +153,7 @@ class StreamingSyncImplementation {
/// To clear errors, use [_noError] instead of null.
void _updateStatus(
{DateTime? lastSyncedAt,
bool? hasSynced,
bool? connected,
bool? connecting,
bool? downloading,
Expand All @@ -164,6 +165,7 @@ class StreamingSyncImplementation {
connected: c,
connecting: !c && (connecting ?? lastStatus.connecting),
lastSyncedAt: lastSyncedAt ?? lastStatus.lastSyncedAt,
hasSynced: hasSynced ?? lastStatus.hasSynced,
downloading: downloading ?? lastStatus.downloading,
uploading: uploading ?? lastStatus.uploading,
uploadError: uploadError == _noError
Expand Down
32 changes: 30 additions & 2 deletions packages/powersync/lib/src/sync_status.dart
Original file line number Diff line number Diff line change
Expand Up @@ -24,6 +24,10 @@ class SyncStatus {
/// Currently this is reset to null after a restart.
final DateTime? lastSyncedAt;

/// Indicates whether there has been at least one full sync, if any.
/// Is null when unknown, for example when state is still being loaded from the database.
final bool? hasSynced;

/// Error during uploading.
///
/// Cleared on the next successful upload.
Expand All @@ -38,6 +42,7 @@ class SyncStatus {
{this.connected = false,
this.connecting = false,
this.lastSyncedAt,
this.hasSynced,
this.downloading = false,
this.uploading = false,
this.downloadError,
Expand All @@ -52,7 +57,30 @@ class SyncStatus {
other.connecting == connecting &&
other.downloadError == downloadError &&
other.uploadError == uploadError &&
other.lastSyncedAt == lastSyncedAt);
other.lastSyncedAt == lastSyncedAt &&
other.hasSynced == hasSynced);
}

SyncStatus copyWith({
bool? connected,
bool? downloading,
bool? uploading,
bool? connecting,
Object? uploadError,
Object? downloadError,
DateTime? lastSyncedAt,
bool? hasSynced,
}) {
return SyncStatus(
connected: connected ?? this.connected,
downloading: downloading ?? this.downloading,
uploading: uploading ?? this.uploading,
connecting: connecting ?? this.connecting,
uploadError: uploadError ?? this.uploadError,
downloadError: downloadError ?? this.downloadError,
lastSyncedAt: lastSyncedAt ?? this.lastSyncedAt,
hasSynced: hasSynced ?? this.hasSynced,
);
}

/// Get the current [downloadError] or [uploadError].
Expand All @@ -68,7 +96,7 @@ class SyncStatus {

@override
String toString() {
return "SyncStatus<connected: $connected connecting: $connecting downloading: $downloading uploading: $uploading lastSyncedAt: $lastSyncedAt error: $anyError>";
return "SyncStatus<connected: $connected connecting: $connecting downloading: $downloading uploading: $uploading lastSyncedAt: $lastSyncedAt, hasSynced: $hasSynced, error: $anyError>";
}
}

Expand Down