@@ -9,14 +9,17 @@ import {
9
9
UpdateNotification ,
10
10
isBatchedUpdateNotification
11
11
} from '../db/DBAdapter.js' ;
12
- import { FULL_SYNC_PRIORITY } from '../db/crud/SyncProgress.js' ;
13
- import { SyncPriorityStatus , SyncStatus } from '../db/crud/SyncStatus.js' ;
12
+ import { SyncStatus } from '../db/crud/SyncStatus.js' ;
14
13
import { UploadQueueStats } from '../db/crud/UploadQueueStatus.js' ;
15
14
import { Schema } from '../db/schema/Schema.js' ;
16
15
import { BaseObserver } from '../utils/BaseObserver.js' ;
17
16
import { ControlledExecutor } from '../utils/ControlledExecutor.js' ;
18
17
import { symbolAsyncIterator , throttleTrailing } from '../utils/async.js' ;
19
- import { ConnectionManager } from './ConnectionManager.js' ;
18
+ import {
19
+ ConnectionManager ,
20
+ CreateSyncImplementationOptions ,
21
+ InternalSubscriptionAdapter
22
+ } from './ConnectionManager.js' ;
20
23
import { CustomQuery } from './CustomQuery.js' ;
21
24
import { ArrayQueryDefinition , Query } from './Query.js' ;
22
25
import { SQLOpenFactory , SQLOpenOptions , isDBAdapter , isSQLOpenFactory , isSQLOpenOptions } from './SQLOpenFactory.js' ;
@@ -40,6 +43,8 @@ import { TriggerManagerImpl } from './triggers/TriggerManagerImpl.js';
40
43
import { DEFAULT_WATCH_THROTTLE_MS , WatchCompatibleQuery } from './watched/WatchedQuery.js' ;
41
44
import { OnChangeQueryProcessor } from './watched/processors/OnChangeQueryProcessor.js' ;
42
45
import { WatchedQueryComparator } from './watched/processors/comparators.js' ;
46
+ import { coreStatusToJs , CoreSyncStatus } from './sync/stream/core-instruction.js' ;
47
+ import { SyncStream } from './sync/sync-streams.js' ;
43
48
44
49
export interface DisconnectAndClearOptions {
45
50
/** When set to false, data in local-only tables is preserved. */
@@ -182,6 +187,7 @@ export abstract class AbstractPowerSyncDatabase extends BaseObserver<PowerSyncDB
182
187
protected bucketStorageAdapter : BucketStorageAdapter ;
183
188
protected _isReadyPromise : Promise < void > ;
184
189
protected connectionManager : ConnectionManager ;
190
+ private subscriptions : InternalSubscriptionAdapter ;
185
191
186
192
get syncStreamImplementation ( ) {
187
193
return this . connectionManager . syncStreamImplementation ;
@@ -236,10 +242,18 @@ export abstract class AbstractPowerSyncDatabase extends BaseObserver<PowerSyncDB
236
242
this . runExclusiveMutex = new Mutex ( ) ;
237
243
238
244
// Start async init
245
+ this . subscriptions = {
246
+ firstStatusMatching : ( predicate , abort ) => this . waitForStatus ( predicate , abort ) ,
247
+ resolveOfflineSyncStatus : ( ) => this . resolveOfflineSyncStatus ( ) ,
248
+ rustSubscriptionsCommand : async ( payload ) => {
249
+ await this . writeTransaction ( ( tx ) => {
250
+ return tx . execute ( 'select powersync_control(?,?)' , [ 'subscriptions' , JSON . stringify ( payload ) ] ) ;
251
+ } ) ;
252
+ }
253
+ } ;
239
254
this . connectionManager = new ConnectionManager ( {
240
255
createSyncImplementation : async ( connector , options ) => {
241
256
await this . waitForReady ( ) ;
242
-
243
257
return this . runExclusive ( async ( ) => {
244
258
const sync = this . generateSyncStreamImplementation ( connector , this . resolvedConnectionOptions ( options ) ) ;
245
259
const onDispose = sync . registerListener ( {
@@ -304,7 +318,7 @@ export abstract class AbstractPowerSyncDatabase extends BaseObserver<PowerSyncDB
304
318
305
319
protected abstract generateSyncStreamImplementation (
306
320
connector : PowerSyncBackendConnector ,
307
- options : RequiredAdditionalConnectionOptions
321
+ options : CreateSyncImplementationOptions & RequiredAdditionalConnectionOptions
308
322
) : StreamingSyncImplementation ;
309
323
310
324
protected abstract generateBucketStorageAdapter ( ) : BucketStorageAdapter ;
@@ -338,13 +352,18 @@ export abstract class AbstractPowerSyncDatabase extends BaseObserver<PowerSyncDB
338
352
? ( status : SyncStatus ) => status . hasSynced
339
353
: ( status : SyncStatus ) => status . statusForPriority ( priority ) . hasSynced ;
340
354
341
- if ( statusMatches ( this . currentStatus ) ) {
355
+ return this . waitForStatus ( statusMatches , signal ) ;
356
+ }
357
+
358
+ private async waitForStatus ( predicate : ( status : SyncStatus ) => any , signal ?: AbortSignal ) : Promise < void > {
359
+ if ( predicate ( this . currentStatus ) ) {
342
360
return ;
343
361
}
362
+
344
363
return new Promise ( ( resolve ) => {
345
364
const dispose = this . registerListener ( {
346
365
statusChanged : ( status ) => {
347
- if ( statusMatches ( status ) ) {
366
+ if ( predicate ( status ) ) {
348
367
dispose ( ) ;
349
368
resolve ( ) ;
350
369
}
@@ -373,7 +392,7 @@ export abstract class AbstractPowerSyncDatabase extends BaseObserver<PowerSyncDB
373
392
await this . bucketStorageAdapter . init ( ) ;
374
393
await this . _loadVersion ( ) ;
375
394
await this . updateSchema ( this . options . schema ) ;
376
- await this . updateHasSynced ( ) ;
395
+ await this . resolveOfflineSyncStatus ( ) ;
377
396
await this . database . execute ( 'PRAGMA RECURSIVE_TRIGGERS=TRUE' ) ;
378
397
this . ready = true ;
379
398
this . iterateListeners ( ( cb ) => cb . initialized ?.( ) ) ;
@@ -403,30 +422,13 @@ export abstract class AbstractPowerSyncDatabase extends BaseObserver<PowerSyncDB
403
422
}
404
423
}
405
424
406
- protected async updateHasSynced ( ) {
407
- const result = await this . database . getAll < { priority : number ; last_synced_at : string } > (
408
- 'SELECT priority, last_synced_at FROM ps_sync_state ORDER BY priority DESC'
409
- ) ;
410
- let lastCompleteSync : Date | undefined ;
411
- const priorityStatusEntries : SyncPriorityStatus [ ] = [ ] ;
412
-
413
- for ( const { priority, last_synced_at } of result ) {
414
- const parsedDate = new Date ( last_synced_at + 'Z' ) ;
425
+ protected async resolveOfflineSyncStatus ( ) {
426
+ const result = await this . database . get < { r : string } > ( 'SELECT powersync_offline_sync_status() as r' ) ;
427
+ const parsed = JSON . parse ( result . r ) as CoreSyncStatus ;
415
428
416
- if ( priority == FULL_SYNC_PRIORITY ) {
417
- // This lowest-possible priority represents a complete sync.
418
- lastCompleteSync = parsedDate ;
419
- } else {
420
- priorityStatusEntries . push ( { priority, hasSynced : true , lastSyncedAt : parsedDate } ) ;
421
- }
422
- }
423
-
424
- const hasSynced = lastCompleteSync != null ;
425
429
const updatedStatus = new SyncStatus ( {
426
430
...this . currentStatus . toJSON ( ) ,
427
- hasSynced,
428
- priorityStatusEntries,
429
- lastSyncedAt : lastCompleteSync
431
+ ...coreStatusToJs ( parsed )
430
432
} ) ;
431
433
432
434
if ( ! updatedStatus . isEqual ( this . currentStatus ) ) {
@@ -471,7 +473,9 @@ export abstract class AbstractPowerSyncDatabase extends BaseObserver<PowerSyncDB
471
473
}
472
474
473
475
// Use the options passed in during connect, or fallback to the options set during database creation or fallback to the default options
474
- resolvedConnectionOptions ( options ?: PowerSyncConnectionOptions ) : RequiredAdditionalConnectionOptions {
476
+ protected resolvedConnectionOptions (
477
+ options : CreateSyncImplementationOptions
478
+ ) : CreateSyncImplementationOptions & RequiredAdditionalConnectionOptions {
475
479
return {
476
480
...options ,
477
481
retryDelayMs :
@@ -540,6 +544,18 @@ export abstract class AbstractPowerSyncDatabase extends BaseObserver<PowerSyncDB
540
544
this . iterateListeners ( ( l ) => l . statusChanged ?.( this . currentStatus ) ) ;
541
545
}
542
546
547
+ /**
548
+ * Create a sync stream to query its status or to subscribe to it.
549
+ *
550
+ * @param name The name of the stream to subscribe to.
551
+ * @param params Optional parameters for the stream subscription.
552
+ * @returns A {@link SyncStream} instance that can be subscribed to.
553
+ * @experimental Sync streams are currently in alpha.
554
+ */
555
+ syncStream ( name : string , params ?: Record < string , any > ) : SyncStream {
556
+ return this . connectionManager . stream ( this . subscriptions , name , params ?? null ) ;
557
+ }
558
+
543
559
/**
544
560
* Close the database, releasing resources.
545
561
*
0 commit comments