@@ -9,14 +9,17 @@ import {
99 UpdateNotification ,
1010 isBatchedUpdateNotification
1111} 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' ;
1413import { UploadQueueStats } from '../db/crud/UploadQueueStatus.js' ;
1514import { Schema } from '../db/schema/Schema.js' ;
1615import { BaseObserver } from '../utils/BaseObserver.js' ;
1716import { ControlledExecutor } from '../utils/ControlledExecutor.js' ;
1817import { symbolAsyncIterator , throttleTrailing } from '../utils/async.js' ;
19- import { ConnectionManager } from './ConnectionManager.js' ;
18+ import {
19+ ConnectionManager ,
20+ CreateSyncImplementationOptions ,
21+ InternalSubscriptionAdapter
22+ } from './ConnectionManager.js' ;
2023import { CustomQuery } from './CustomQuery.js' ;
2124import { ArrayQueryDefinition , Query } from './Query.js' ;
2225import { SQLOpenFactory , SQLOpenOptions , isDBAdapter , isSQLOpenFactory , isSQLOpenOptions } from './SQLOpenFactory.js' ;
@@ -40,6 +43,8 @@ import { TriggerManagerImpl } from './triggers/TriggerManagerImpl.js';
4043import { DEFAULT_WATCH_THROTTLE_MS , WatchCompatibleQuery } from './watched/WatchedQuery.js' ;
4144import { OnChangeQueryProcessor } from './watched/processors/OnChangeQueryProcessor.js' ;
4245import { WatchedQueryComparator } from './watched/processors/comparators.js' ;
46+ import { coreStatusToJs , CoreSyncStatus } from './sync/stream/core-instruction.js' ;
47+ import { SyncStream } from './sync/sync-streams.js' ;
4348
4449export interface DisconnectAndClearOptions {
4550 /** When set to false, data in local-only tables is preserved. */
@@ -182,6 +187,7 @@ export abstract class AbstractPowerSyncDatabase extends BaseObserver<PowerSyncDB
182187 protected bucketStorageAdapter : BucketStorageAdapter ;
183188 protected _isReadyPromise : Promise < void > ;
184189 protected connectionManager : ConnectionManager ;
190+ private subscriptions : InternalSubscriptionAdapter ;
185191
186192 get syncStreamImplementation ( ) {
187193 return this . connectionManager . syncStreamImplementation ;
@@ -236,10 +242,18 @@ export abstract class AbstractPowerSyncDatabase extends BaseObserver<PowerSyncDB
236242 this . runExclusiveMutex = new Mutex ( ) ;
237243
238244 // 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+ } ;
239254 this . connectionManager = new ConnectionManager ( {
240255 createSyncImplementation : async ( connector , options ) => {
241256 await this . waitForReady ( ) ;
242-
243257 return this . runExclusive ( async ( ) => {
244258 const sync = this . generateSyncStreamImplementation ( connector , this . resolvedConnectionOptions ( options ) ) ;
245259 const onDispose = sync . registerListener ( {
@@ -304,7 +318,7 @@ export abstract class AbstractPowerSyncDatabase extends BaseObserver<PowerSyncDB
304318
305319 protected abstract generateSyncStreamImplementation (
306320 connector : PowerSyncBackendConnector ,
307- options : RequiredAdditionalConnectionOptions
321+ options : CreateSyncImplementationOptions & RequiredAdditionalConnectionOptions
308322 ) : StreamingSyncImplementation ;
309323
310324 protected abstract generateBucketStorageAdapter ( ) : BucketStorageAdapter ;
@@ -338,13 +352,18 @@ export abstract class AbstractPowerSyncDatabase extends BaseObserver<PowerSyncDB
338352 ? ( status : SyncStatus ) => status . hasSynced
339353 : ( status : SyncStatus ) => status . statusForPriority ( priority ) . hasSynced ;
340354
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 ) ) {
342360 return ;
343361 }
362+
344363 return new Promise ( ( resolve ) => {
345364 const dispose = this . registerListener ( {
346365 statusChanged : ( status ) => {
347- if ( statusMatches ( status ) ) {
366+ if ( predicate ( status ) ) {
348367 dispose ( ) ;
349368 resolve ( ) ;
350369 }
@@ -373,7 +392,7 @@ export abstract class AbstractPowerSyncDatabase extends BaseObserver<PowerSyncDB
373392 await this . bucketStorageAdapter . init ( ) ;
374393 await this . _loadVersion ( ) ;
375394 await this . updateSchema ( this . options . schema ) ;
376- await this . updateHasSynced ( ) ;
395+ await this . resolveOfflineSyncStatus ( ) ;
377396 await this . database . execute ( 'PRAGMA RECURSIVE_TRIGGERS=TRUE' ) ;
378397 this . ready = true ;
379398 this . iterateListeners ( ( cb ) => cb . initialized ?.( ) ) ;
@@ -403,30 +422,13 @@ export abstract class AbstractPowerSyncDatabase extends BaseObserver<PowerSyncDB
403422 }
404423 }
405424
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 ;
415428
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 ;
425429 const updatedStatus = new SyncStatus ( {
426430 ...this . currentStatus . toJSON ( ) ,
427- hasSynced,
428- priorityStatusEntries,
429- lastSyncedAt : lastCompleteSync
431+ ...coreStatusToJs ( parsed )
430432 } ) ;
431433
432434 if ( ! updatedStatus . isEqual ( this . currentStatus ) ) {
@@ -471,7 +473,9 @@ export abstract class AbstractPowerSyncDatabase extends BaseObserver<PowerSyncDB
471473 }
472474
473475 // 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 {
475479 return {
476480 ...options ,
477481 retryDelayMs :
@@ -540,6 +544,18 @@ export abstract class AbstractPowerSyncDatabase extends BaseObserver<PowerSyncDB
540544 this . iterateListeners ( ( l ) => l . statusChanged ?.( this . currentStatus ) ) ;
541545 }
542546
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+
543559 /**
544560 * Close the database, releasing resources.
545561 *
0 commit comments