@@ -13,6 +13,7 @@ import 'package:powersync_core/src/powersync_update_notification.dart';
13
13
import 'package:powersync_core/src/schema.dart' ;
14
14
import 'package:powersync_core/src/schema_logic.dart' ;
15
15
import 'package:powersync_core/src/schema_logic.dart' as schema_logic;
16
+ import 'package:powersync_core/src/sync/connection_manager.dart' ;
16
17
import 'package:powersync_core/src/sync/options.dart' ;
17
18
import 'package:powersync_core/src/sync/sync_status.dart' ;
18
19
@@ -43,16 +44,13 @@ mixin PowerSyncDatabaseMixin implements SqliteConnection {
43
44
@Deprecated ("This field is unused, pass params to connect() instead" )
44
45
Map <String , dynamic >? clientParams;
45
46
47
+ late final ConnectionManager _connections;
48
+
46
49
/// Current connection status.
47
- SyncStatus currentStatus =
48
- const SyncStatus (connected: false , lastSyncedAt: null );
50
+ SyncStatus get currentStatus => _connections.currentStatus;
49
51
50
52
/// Use this stream to subscribe to connection status updates.
51
- late final Stream <SyncStatus > statusStream;
52
-
53
- @protected
54
- StreamController <SyncStatus > statusStreamController =
55
- StreamController <SyncStatus >.broadcast ();
53
+ Stream <SyncStatus > get statusStream => _connections.statusStream;
56
54
57
55
late final ActiveDatabaseGroup _activeGroup;
58
56
@@ -82,15 +80,6 @@ mixin PowerSyncDatabaseMixin implements SqliteConnection {
82
80
@protected
83
81
Future <void > get isInitialized;
84
82
85
- /// The abort controller for the current sync iteration.
86
- ///
87
- /// null when disconnected, present when connecting or connected.
88
- ///
89
- /// The controller must only be accessed from within a critical section of the
90
- /// sync mutex.
91
- @protected
92
- AbortController ? _abortActiveSync;
93
-
94
83
@protected
95
84
Future <void > baseInit () async {
96
85
String identifier = 'memory' ;
@@ -108,8 +97,7 @@ mixin PowerSyncDatabaseMixin implements SqliteConnection {
108
97
'instantiation logic if this is not intentional' ,
109
98
);
110
99
}
111
-
112
- statusStream = statusStreamController.stream;
100
+ _connections = ConnectionManager (this );
113
101
updates = powerSyncUpdateNotifications (database.updates);
114
102
115
103
await database.initialize ();
@@ -216,33 +204,7 @@ mixin PowerSyncDatabaseMixin implements SqliteConnection {
216
204
@protected
217
205
@visibleForTesting
218
206
void setStatus (SyncStatus status) {
219
- if (status != currentStatus) {
220
- final newStatus = SyncStatus (
221
- connected: status.connected,
222
- downloading: status.downloading,
223
- uploading: status.uploading,
224
- connecting: status.connecting,
225
- uploadError: status.uploadError,
226
- downloadError: status.downloadError,
227
- priorityStatusEntries: status.priorityStatusEntries,
228
- downloadProgress: status.downloadProgress,
229
- // Note that currently the streaming sync implementation will never set
230
- // hasSynced. lastSyncedAt implies that syncing has completed at some
231
- // point (hasSynced = true).
232
- // The previous values of hasSynced should be preserved here.
233
- lastSyncedAt: status.lastSyncedAt ?? currentStatus.lastSyncedAt,
234
- hasSynced: status.lastSyncedAt != null
235
- ? true
236
- : status.hasSynced ?? currentStatus.hasSynced,
237
- );
238
-
239
- // If the absence of hasSynced was the only difference, the new states
240
- // would be equal and don't require an event. So, check again.
241
- if (newStatus != currentStatus) {
242
- currentStatus = newStatus;
243
- statusStreamController.add (currentStatus);
244
- }
245
- }
207
+ _connections.manuallyChangeSyncStatus (status);
246
208
}
247
209
248
210
@override
@@ -269,7 +231,7 @@ mixin PowerSyncDatabaseMixin implements SqliteConnection {
269
231
270
232
// If there are paused subscriptionso n the status stream, don't delay
271
233
// closing the database because of that.
272
- unawaited (statusStreamController .close () );
234
+ _connections .close ();
273
235
await _activeGroup.close ();
274
236
}
275
237
}
@@ -303,60 +265,7 @@ mixin PowerSyncDatabaseMixin implements SqliteConnection {
303
265
params: params,
304
266
);
305
267
306
- // ignore: deprecated_member_use_from_same_package
307
- clientParams = params;
308
- var thisConnectAborter = AbortController ();
309
- final zone = Zone .current;
310
-
311
- late void Function () retryHandler;
312
-
313
- Future <void > connectWithSyncLock () async {
314
- // Ensure there has not been a subsequent connect() call installing a new
315
- // sync client.
316
- assert (identical (_abortActiveSync, thisConnectAborter));
317
- assert (! thisConnectAborter.aborted);
318
-
319
- await connectInternal (
320
- connector: connector,
321
- options: resolvedOptions,
322
- abort: thisConnectAborter,
323
- // Run follow-up async tasks in the parent zone, a new one is introduced
324
- // while we hold the lock (and async tasks won't hold the sync lock).
325
- asyncWorkZone: zone,
326
- );
327
-
328
- thisConnectAborter.onCompletion.whenComplete (retryHandler);
329
- }
330
-
331
- // If the sync encounters a failure without being aborted, retry
332
- retryHandler = Zone .current.bindCallback (() async {
333
- _activeGroup.syncConnectMutex.lock (() async {
334
- // Is this still supposed to be active? (abort is only called within
335
- // mutex)
336
- if (! thisConnectAborter.aborted) {
337
- // We only change _abortActiveSync after disconnecting, which resets
338
- // the abort controller.
339
- assert (identical (_abortActiveSync, thisConnectAborter));
340
-
341
- // We need a new abort controller for this attempt
342
- _abortActiveSync = thisConnectAborter = AbortController ();
343
-
344
- logger.warning ('Sync client failed, retrying...' );
345
- await connectWithSyncLock ();
346
- }
347
- });
348
- });
349
-
350
- await _activeGroup.syncConnectMutex.lock (() async {
351
- // Disconnect a previous sync client, if one is active.
352
- await _abortCurrentSync ();
353
- assert (_abortActiveSync == null );
354
-
355
- // Install the abort controller for this particular connect call, allowing
356
- // it to be disconnected.
357
- _abortActiveSync = thisConnectAborter;
358
- await connectWithSyncLock ();
359
- });
268
+ await _connections.connect (connector: connector, options: resolvedOptions);
360
269
}
361
270
362
271
/// Internal method to establish a sync client connection.
@@ -378,27 +287,7 @@ mixin PowerSyncDatabaseMixin implements SqliteConnection {
378
287
///
379
288
/// Use [connect] to connect again.
380
289
Future <void > disconnect () async {
381
- // Also wrap this in the sync mutex to ensure there's no race between us
382
- // connecting and disconnecting.
383
- await _activeGroup.syncConnectMutex.lock (_abortCurrentSync);
384
-
385
- setStatus (
386
- SyncStatus (connected: false , lastSyncedAt: currentStatus.lastSyncedAt));
387
- }
388
-
389
- Future <void > _abortCurrentSync () async {
390
- if (_abortActiveSync case final disconnector? ) {
391
- /// Checking `disconnecter.aborted` prevents race conditions
392
- /// where multiple calls to `disconnect` can attempt to abort
393
- /// the controller more than once before it has finished aborting.
394
- if (disconnector.aborted == false ) {
395
- await disconnector.abort ();
396
- _abortActiveSync = null ;
397
- } else {
398
- /// Wait for the abort to complete. Continue updating the sync status after completed
399
- await disconnector.onCompletion;
400
- }
401
- }
290
+ await _connections.disconnect ();
402
291
}
403
292
404
293
/// Disconnect and clear the database.
@@ -416,8 +305,7 @@ mixin PowerSyncDatabaseMixin implements SqliteConnection {
416
305
await tx.execute ('select powersync_clear(?)' , [clearLocal ? 1 : 0 ]);
417
306
});
418
307
// The data has been deleted - reset these
419
- currentStatus = SyncStatus (lastSyncedAt: null , hasSynced: false );
420
- statusStreamController.add (currentStatus);
308
+ setStatus (SyncStatus (lastSyncedAt: null , hasSynced: false ));
421
309
}
422
310
423
311
@Deprecated ('Use [disconnectAndClear] instead.' )
@@ -439,9 +327,7 @@ mixin PowerSyncDatabaseMixin implements SqliteConnection {
439
327
schema.validate ();
440
328
441
329
await _activeGroup.syncConnectMutex.lock (() async {
442
- if (_abortActiveSync != null ) {
443
- throw AssertionError ('Cannot update schema while connected' );
444
- }
330
+ _connections.checkNotConnected ();
445
331
446
332
this .schema = schema;
447
333
await database.writeLock ((tx) => schema_logic.updateSchema (tx, schema));
0 commit comments