@@ -19,7 +19,6 @@ import { throttleTrailing } from '../utils/async.js';
1919import { mutexRunExclusive } from '../utils/mutex.js' ;
2020import { SQLOpenFactory , SQLOpenOptions , isDBAdapter , isSQLOpenFactory , isSQLOpenOptions } from './SQLOpenFactory.js' ;
2121import { PowerSyncBackendConnector } from './connection/PowerSyncBackendConnector.js' ;
22- import { runOnSchemaChange } from './runOnSchemaChange.js' ;
2322import { BucketStorageAdapter , PSInternalTable } from './sync/bucket/BucketStorageAdapter.js' ;
2423import { CrudBatch } from './sync/bucket/CrudBatch.js' ;
2524import { CrudEntry , CrudEntryJSON } from './sync/bucket/CrudEntry.js' ;
@@ -34,8 +33,7 @@ import {
3433 type RequiredAdditionalConnectionOptions
3534} from './sync/stream/AbstractStreamingSyncImplementation.js' ;
3635import { WatchedQuery } from './watched/WatchedQuery.js' ;
37- import { WatchedQueryImpl } from './watched/WatchedQueryImpl.js' ;
38- import { OnChangeQueryProcessor } from './watched/processors/OnChangeQueryProcessor.js' ;
36+ import { OnChangeQueryProcessor , WatchedQueryComparator } from './watched/processors/OnChangeQueryProcessor.js' ;
3937
4038export interface DisconnectAndClearOptions {
4139 /** When set to false, data in local-only tables is preserved. */
@@ -89,6 +87,12 @@ export interface SQLWatchOptions {
8987 * Emits an empty result set immediately
9088 */
9189 triggerImmediate ?: boolean ;
90+
91+ /**
92+ * Optional comparator which will be used to compare the results of the query.
93+ * The watched query will only yield results if the comparator returns false.
94+ */
95+ comparator ?: WatchedQueryComparator < QueryResult > ;
9296}
9397
9498export interface WatchOnChangeEvent {
@@ -868,25 +872,28 @@ export abstract class AbstractPowerSyncDatabase extends BaseObserver<PowerSyncDB
868872 }
869873
870874 // TODO names
871- incrementalWatch < T > ( options : {
875+ incrementalWatch < DataType > ( options : {
872876 sql : string ;
873877 parameters ?: any [ ] ;
874878 throttleMs ?: number ;
875- queryExecutor ?: ( ) => Promise < T [ ] > ;
879+ customExecutor ?: {
880+ initialData : DataType ;
881+ execute : ( ) => Promise < DataType > ;
882+ } ;
876883 reportFetching ?: boolean ;
877- } ) : WatchedQuery < T > {
878- return new WatchedQueryImpl ( {
879- processor : new OnChangeQueryProcessor ( {
880- db : this ,
881- compareBy : ( item ) => JSON . stringify ( item ) , // TODO make configurable
882- watchedQuery : {
883- query : options . sql ,
884- parameters : options . parameters ,
885- throttleMs : options . throttleMs ?? DEFAULT_WATCH_THROTTLE_MS ,
886- queryExecutor : options . queryExecutor ,
887- reportFetching : options . reportFetching
888- }
889- } )
884+ } ) : WatchedQuery < DataType > {
885+ return new OnChangeQueryProcessor ( {
886+ db : this ,
887+ comparator : {
888+ checkEquality : ( a , b ) => JSON . stringify ( a ) == JSON . stringify ( b )
889+ } ,
890+ query : {
891+ sql : options . sql ,
892+ parameters : options . parameters ,
893+ throttleMs : options . throttleMs ?? DEFAULT_WATCH_THROTTLE_MS ,
894+ customExecutor : options . customExecutor ,
895+ reportFetching : options . reportFetching
896+ }
890897 } ) ;
891898 }
892899
@@ -908,38 +915,42 @@ export abstract class AbstractPowerSyncDatabase extends BaseObserver<PowerSyncDB
908915 throw new Error ( 'onResult is required' ) ;
909916 }
910917
911- const watchQuery = async ( abortSignal : AbortSignal ) => {
912- try {
913- const resolvedTables = await this . resolveTables ( sql , parameters , options ) ;
914- // Fetch initial data
915- const result = await this . executeReadOnly ( sql , parameters ) ;
916- onResult ( result ) ;
917-
918- this . onChangeWithCallback (
919- {
920- onChange : async ( ) => {
921- try {
922- const result = await this . executeReadOnly ( sql , parameters ) ;
923- onResult ( result ) ;
924- } catch ( error ) {
925- onError ?.( error ) ;
926- }
927- } ,
928- onError
918+ const watch = new OnChangeQueryProcessor ( {
919+ db : this ,
920+ // Comparisons are disabled if no comparator is provided
921+ comparator : options ?. comparator ,
922+ query : {
923+ sql,
924+ parameters,
925+ throttleMs : options ?. throttleMs ?? DEFAULT_WATCH_THROTTLE_MS ,
926+ reportFetching : false ,
927+ // The default watch implementation returns QueryResult as the Data type
928+ customExecutor : {
929+ execute : async ( ) => {
930+ return this . executeReadOnly ( sql , parameters ) ;
929931 } ,
930- {
931- ...( options ?? { } ) ,
932- tables : resolvedTables ,
933- // Override the abort signal since we intercept it
934- signal : abortSignal
935- }
936- ) ;
937- } catch ( error ) {
938- onError ?.( error ) ;
932+ initialData : null
933+ }
939934 }
940- } ;
935+ } ) ;
941936
942- runOnSchemaChange ( watchQuery , this , options ) ;
937+ const dispose = watch . subscribe ( {
938+ onData : ( data ) => {
939+ if ( ! data ) {
940+ // This should not happen. We only use null for the initial data.
941+ return ;
942+ }
943+ onResult ( data ) ;
944+ } ,
945+ onError : ( error ) => {
946+ onError ( error ) ;
947+ }
948+ } ) ;
949+
950+ options ?. signal ?. addEventListener ( 'abort' , ( ) => {
951+ dispose ( ) ;
952+ watch . close ( ) ;
953+ } ) ;
943954 }
944955
945956 /**
0 commit comments