Skip to content

Commit c3e4709

Browse files
use a builder pattern to separate incremental watched query types.
1 parent aab75f3 commit c3e4709

19 files changed

+193
-149
lines changed

packages/common/src/client/AbstractPowerSyncDatabase.ts

Lines changed: 16 additions & 39 deletions
Original file line numberDiff line numberDiff line change
@@ -32,9 +32,9 @@ import {
3232
type PowerSyncConnectionOptions,
3333
type RequiredAdditionalConnectionOptions
3434
} from './sync/stream/AbstractStreamingSyncImplementation.js';
35-
import { WatchedQuery, WatchedQueryOptions } from './watched/WatchedQuery.js';
36-
import { OnChangeQueryProcessor, WatchedQueryComparator } from './watched/processors/OnChangeQueryProcessor.js';
37-
import { FalsyComparator } from './watched/processors/comparators.js';
35+
import { IncrementalWatchMode } from './watched/WatchedQueryBuilder.js';
36+
import { WatchedQueryBuilderMap } from './watched/WatchedQueryBuilderMap.js';
37+
import { WatchedQueryComparator } from './watched/processors/comparators.js';
3838

3939
export interface DisconnectAndClearOptions {
4040
/** When set to false, data in local-only tables is preserved. */
@@ -72,22 +72,6 @@ export interface PowerSyncDatabaseOptionsWithSettings extends BasePowerSyncDatab
7272
database: SQLOpenOptions;
7373
}
7474

75-
export enum IncrementalWatchMode {
76-
COMPARISON = 'comparison'
77-
}
78-
79-
export interface WatchComparatorOptions<DataType> {
80-
mode: IncrementalWatchMode.COMPARISON;
81-
comparator?: WatchedQueryComparator<DataType>;
82-
}
83-
84-
export type WatchProcessorOptions<DataType> = WatchComparatorOptions<DataType>;
85-
86-
export interface IncrementalWatchOptions<DataType> {
87-
watch: WatchedQueryOptions<DataType>;
88-
processor?: WatchProcessorOptions<DataType>;
89-
}
90-
9175
export interface SQLWatchOptions {
9276
signal?: AbortSignal;
9377
tables?: string[];
@@ -109,7 +93,7 @@ export interface SQLWatchOptions {
10993
* Optional comparator which will be used to compare the results of the query.
11094
* The watched query will only yield results if the comparator returns false.
11195
*/
112-
processor?: WatchProcessorOptions<QueryResult>;
96+
comparator?: WatchedQueryComparator<QueryResult>;
11397
}
11498

11599
export interface WatchOnChangeEvent {
@@ -891,20 +875,14 @@ export abstract class AbstractPowerSyncDatabase extends BaseObserver<PowerSyncDB
891875
}
892876

893877
// TODO names
894-
incrementalWatch<DataType>(options: IncrementalWatchOptions<DataType>): WatchedQuery<DataType> {
895-
const { watch, processor } = options;
896-
897-
switch (options.processor?.mode) {
898-
case IncrementalWatchMode.COMPARISON:
899-
default:
900-
return new OnChangeQueryProcessor({
901-
db: this,
902-
comparator: processor?.comparator ?? {
903-
checkEquality: (a, b) => JSON.stringify(a) == JSON.stringify(b)
904-
},
905-
watchOptions: watch
906-
});
878+
incrementalWatch<Mode extends IncrementalWatchMode>(options: { mode: Mode }): WatchedQueryBuilderMap[Mode] {
879+
const { mode } = options;
880+
const builderFactory = WatchedQueryBuilderMap[mode];
881+
if (!builderFactory) {
882+
debugger;
883+
throw new Error(`Unsupported watch mode: ${mode}`);
907884
}
885+
return builderFactory(this);
908886
}
909887

910888
/**
@@ -925,8 +903,11 @@ export abstract class AbstractPowerSyncDatabase extends BaseObserver<PowerSyncDB
925903
throw new Error('onResult is required');
926904
}
927905

928-
// Uses shared incremental watch logic under the hook, but maintains the same external API as the old watch method.
929-
const watchedQuery = this.incrementalWatch({
906+
const { comparator } = options ?? {};
907+
// This watch method which provides static SQL is currently only compatible with the comparison mode.
908+
// Uses shared incremental watch logic under the hood, but maintains the same external API as the old watch method.
909+
const watchedQuery = this.incrementalWatch({ mode: IncrementalWatchMode.COMPARISON }).build({
910+
comparator,
930911
watch: {
931912
query: {
932913
compile: () => ({
@@ -938,10 +919,6 @@ export abstract class AbstractPowerSyncDatabase extends BaseObserver<PowerSyncDB
938919
placeholderData: null,
939920
reportFetching: false,
940921
throttleMs: options?.throttleMs ?? DEFAULT_WATCH_THROTTLE_MS
941-
},
942-
processor: options?.processor ?? {
943-
mode: IncrementalWatchMode.COMPARISON,
944-
comparator: FalsyComparator
945922
}
946923
});
947924

Lines changed: 12 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,12 @@
1+
import { WatchedQuery } from './WatchedQuery.js';
2+
3+
export enum IncrementalWatchMode {
4+
COMPARISON = 'comparison'
5+
}
6+
7+
/**
8+
* Builds a {@link WatchedQuery} instance given a set of options.
9+
*/
10+
export interface WatchedQueryBuilder {
11+
build<DataType>(options: {}): WatchedQuery<DataType>;
12+
}
Lines changed: 17 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,17 @@
1+
import { AbstractPowerSyncDatabase } from '../AbstractPowerSyncDatabase.js';
2+
import { ComparisonWatchedQueryBuilder } from './processors/ComparisonWatchedQueryBuilder.js';
3+
import { IncrementalWatchMode } from './WatchedQueryBuilder.js';
4+
5+
/**
6+
* @internal
7+
*/
8+
export const WatchedQueryBuilderMap = {
9+
[IncrementalWatchMode.COMPARISON]: (db: AbstractPowerSyncDatabase) => new ComparisonWatchedQueryBuilder(db)
10+
};
11+
12+
/**
13+
* @internal
14+
*/
15+
export type WatchedQueryBuilderMap = {
16+
[key in IncrementalWatchMode]: ReturnType<(typeof WatchedQueryBuilderMap)[key]>;
17+
};
Lines changed: 24 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,24 @@
1+
import { AbstractPowerSyncDatabase } from '../../../client/AbstractPowerSyncDatabase.js';
2+
import { WatchedQuery, WatchedQueryOptions } from '../WatchedQuery.js';
3+
import { WatchedQueryBuilder } from '../WatchedQueryBuilder.js';
4+
import { WatchedQueryComparator } from './comparators.js';
5+
import { OnChangeQueryProcessor } from './OnChangeQueryProcessor.js';
6+
7+
export interface ComparisonWatchProcessorOptions<DataType> {
8+
comparator?: WatchedQueryComparator<DataType>;
9+
watch: WatchedQueryOptions<DataType>;
10+
}
11+
12+
export class ComparisonWatchedQueryBuilder implements WatchedQueryBuilder {
13+
constructor(protected db: AbstractPowerSyncDatabase) {}
14+
15+
build<DataType>(options: ComparisonWatchProcessorOptions<DataType>): WatchedQuery<DataType> {
16+
return new OnChangeQueryProcessor({
17+
db: this.db,
18+
comparator: options.comparator ?? {
19+
checkEquality: (a, b) => JSON.stringify(a) == JSON.stringify(b)
20+
},
21+
watchOptions: options.watch
22+
});
23+
}
24+
}

packages/common/src/client/watched/processors/OnChangeQueryProcessor.ts

Lines changed: 1 addition & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -1,9 +1,6 @@
11
import { WatchedQueryState } from '../WatchedQuery.js';
22
import { AbstractQueryProcessor, AbstractQueryProcessorOptions, LinkQueryOptions } from './AbstractQueryProcessor.js';
3-
4-
export interface WatchedQueryComparator<Data> {
5-
checkEquality: (current: Data, previous: Data) => boolean;
6-
}
3+
import { WatchedQueryComparator } from './comparators.js';
74

85
/**
96
* @internal

packages/common/src/client/watched/processors/comparators.ts

Lines changed: 3 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -1,4 +1,6 @@
1-
import { WatchedQueryComparator } from './OnChangeQueryProcessor.js';
1+
export interface WatchedQueryComparator<Data> {
2+
checkEquality: (current: Data, previous: Data) => boolean;
3+
}
24

35
export type ArrayComparatorOptions<ItemType> = {
46
compareBy: (item: ItemType) => string;

packages/common/src/index.ts

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -34,6 +34,7 @@ export * from './client/watched/GetAllQuery.js';
3434
export * from './client/watched/processors/AbstractQueryProcessor.js';
3535
export * from './client/watched/processors/comparators.js';
3636
export * from './client/watched/WatchedQuery.js';
37+
export * from './client/watched/WatchedQueryBuilder.js';
3738

3839
export * from './utils/AbortOperation.js';
3940
export * from './utils/BaseObserver.js';

packages/react/README.md

Lines changed: 2 additions & 9 deletions
Original file line numberDiff line numberDiff line change
@@ -345,12 +345,9 @@ function MyWidget() {
345345
// Note that isFetching is set (by default) whenever the query is being fetched/checked.
346346
// This will result in `MyWidget` re-rendering for any change to the `cats` table.
347347
const { data, isLoading, isFetching } = useQuery(`SELECT * FROM cats WHERE breed = 'tabby'`, [], {
348-
processor: {
349-
mode: IncrementalWatchMode.COMPARISON,
350348
comparator: new ArrayComparator({
351349
compareBy: (cat) => JSON.stringify(cat)
352350
})
353-
},
354351
})
355352

356353
// ... Widget code
@@ -375,12 +372,8 @@ function MyWidget() {
375372
// When reportFetching == false the object returned from useQuery will only be changed when the data, isLoading or error state changes.
376373
// This method performs a comparison in memory in order to determine changes.
377374
const { data, isLoading } = useQuery(`SELECT * FROM cats WHERE breed = 'tabby'`, [], {
378-
processor: {
379-
mode: 'comparison',
380-
comparator: new ArrayComparator({
381-
compareBy: (cat) => JSON.stringify(cat)
382-
})
383-
},
375+
comparator: new ArrayComparator({
376+
compareBy: (cat) => JSON.stringify(cat)
384377
reportFetching: false
385378
})
386379

packages/react/src/QueryStore.ts

Lines changed: 13 additions & 9 deletions
Original file line numberDiff line numberDiff line change
@@ -1,4 +1,4 @@
1-
import { AbstractPowerSyncDatabase, WatchCompatibleQuery, WatchedQuery } from '@powersync/common';
1+
import { AbstractPowerSyncDatabase, IncrementalWatchMode, WatchCompatibleQuery, WatchedQuery } from '@powersync/common';
22
import { AdditionalOptions } from './hooks/watched/watch-types';
33

44
export function generateQueryKey(
@@ -19,14 +19,18 @@ export class QueryStore {
1919
return this.cache.get(key);
2020
}
2121

22-
const watchedQuery = this.db.incrementalWatch({
23-
watch: {
24-
query,
25-
placeholderData: [],
26-
throttleMs: options.throttleMs
27-
},
28-
processor: options.processor
29-
});
22+
const watchedQuery = this.db
23+
.incrementalWatch({
24+
mode: IncrementalWatchMode.COMPARISON
25+
})
26+
.build({
27+
watch: {
28+
query,
29+
placeholderData: [],
30+
throttleMs: options.throttleMs
31+
},
32+
comparator: options.comparator
33+
});
3034

3135
const disposer = watchedQuery.registerListener({
3236
closed: () => {

packages/react/src/hooks/suspense/useSuspenseQuery.ts

Lines changed: 3 additions & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -1,4 +1,4 @@
1-
import { CompilableQuery, FalsyComparator, IncrementalWatchMode } from '@powersync/common';
1+
import { CompilableQuery, FalsyComparator } from '@powersync/common';
22
import { AdditionalOptions } from '../watched/watch-types';
33
import { SuspenseQueryResult } from './SuspenseQueryResult';
44
import { useSingleSuspenseQuery } from './useSingleSuspenseQuery';
@@ -36,10 +36,8 @@ export const useSuspenseQuery = <T = any>(
3636
default:
3737
return useWatchedSuspenseQuery<T>(query, parameters, {
3838
...options,
39-
processor: options.processor ?? {
40-
mode: IncrementalWatchMode.COMPARISON,
41-
comparator: FalsyComparator // Default comparator that always reports changed result sets
42-
}
39+
// Default comparator that always reports changed result sets
40+
comparator: options.comparator ?? FalsyComparator
4341
});
4442
}
4543
};

0 commit comments

Comments
 (0)