@powersync/common 1.34.0 → 1.36.0

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (32) hide show
  1. package/dist/bundle.cjs +5 -5
  2. package/dist/bundle.mjs +3 -3
  3. package/dist/index.d.cts +2934 -0
  4. package/lib/client/AbstractPowerSyncDatabase.d.ts +61 -5
  5. package/lib/client/AbstractPowerSyncDatabase.js +103 -29
  6. package/lib/client/CustomQuery.d.ts +22 -0
  7. package/lib/client/CustomQuery.js +42 -0
  8. package/lib/client/Query.d.ts +97 -0
  9. package/lib/client/Query.js +1 -0
  10. package/lib/client/sync/bucket/BucketStorageAdapter.d.ts +2 -2
  11. package/lib/client/sync/stream/AbstractRemote.js +31 -19
  12. package/lib/client/sync/stream/AbstractStreamingSyncImplementation.d.ts +3 -2
  13. package/lib/client/sync/stream/AbstractStreamingSyncImplementation.js +19 -5
  14. package/lib/client/watched/GetAllQuery.d.ts +32 -0
  15. package/lib/client/watched/GetAllQuery.js +24 -0
  16. package/lib/client/watched/WatchedQuery.d.ts +98 -0
  17. package/lib/client/watched/WatchedQuery.js +12 -0
  18. package/lib/client/watched/processors/AbstractQueryProcessor.d.ts +67 -0
  19. package/lib/client/watched/processors/AbstractQueryProcessor.js +135 -0
  20. package/lib/client/watched/processors/DifferentialQueryProcessor.d.ts +121 -0
  21. package/lib/client/watched/processors/DifferentialQueryProcessor.js +166 -0
  22. package/lib/client/watched/processors/OnChangeQueryProcessor.d.ts +33 -0
  23. package/lib/client/watched/processors/OnChangeQueryProcessor.js +76 -0
  24. package/lib/client/watched/processors/comparators.d.ts +30 -0
  25. package/lib/client/watched/processors/comparators.js +34 -0
  26. package/lib/index.d.ts +8 -0
  27. package/lib/index.js +8 -0
  28. package/lib/utils/BaseObserver.d.ts +3 -4
  29. package/lib/utils/BaseObserver.js +3 -0
  30. package/lib/utils/MetaBaseObserver.d.ts +29 -0
  31. package/lib/utils/MetaBaseObserver.js +50 -0
  32. package/package.json +12 -7
@@ -6,12 +6,15 @@ import { UploadQueueStats } from '../db/crud/UploadQueueStatus.js';
6
6
  import { Schema } from '../db/schema/Schema.js';
7
7
  import { BaseObserver } from '../utils/BaseObserver.js';
8
8
  import { ConnectionManager } from './ConnectionManager.js';
9
+ import { ArrayQueryDefinition, Query } from './Query.js';
9
10
  import { SQLOpenFactory, SQLOpenOptions } from './SQLOpenFactory.js';
10
11
  import { PowerSyncBackendConnector } from './connection/PowerSyncBackendConnector.js';
11
12
  import { BucketStorageAdapter } from './sync/bucket/BucketStorageAdapter.js';
12
13
  import { CrudBatch } from './sync/bucket/CrudBatch.js';
13
14
  import { CrudTransaction } from './sync/bucket/CrudTransaction.js';
14
15
  import { StreamingSyncImplementation, StreamingSyncImplementationListener, type AdditionalConnectionOptions, type PowerSyncConnectionOptions, type RequiredAdditionalConnectionOptions } from './sync/stream/AbstractStreamingSyncImplementation.js';
16
+ import { WatchCompatibleQuery } from './watched/WatchedQuery.js';
17
+ import { WatchedQueryComparator } from './watched/processors/comparators.js';
15
18
  export interface DisconnectAndClearOptions {
16
19
  /** When set to false, data in local-only tables is preserved. */
17
20
  clearLocal?: boolean;
@@ -44,7 +47,7 @@ export interface PowerSyncDatabaseOptionsWithOpenFactory extends BasePowerSyncDa
44
47
  export interface PowerSyncDatabaseOptionsWithSettings extends BasePowerSyncDatabaseOptions {
45
48
  database: SQLOpenOptions;
46
49
  }
47
- export interface SQLWatchOptions {
50
+ export interface SQLOnChangeOptions {
48
51
  signal?: AbortSignal;
49
52
  tables?: string[];
50
53
  /** The minimum interval between queries. */
@@ -56,6 +59,17 @@ export interface SQLWatchOptions {
56
59
  * by not removing PowerSync table name prefixes
57
60
  */
58
61
  rawTableNames?: boolean;
62
+ /**
63
+ * Emits an empty result set immediately
64
+ */
65
+ triggerImmediate?: boolean;
66
+ }
67
+ export interface SQLWatchOptions extends SQLOnChangeOptions {
68
+ /**
69
+ * Optional comparator which will be used to compare the results of the query.
70
+ * The watched query will only yield results if the comparator returns false.
71
+ */
72
+ comparator?: WatchedQueryComparator<QueryResult>;
59
73
  }
60
74
  export interface WatchOnChangeEvent {
61
75
  changedTables: string[];
@@ -71,6 +85,8 @@ export interface WatchOnChangeHandler {
71
85
  export interface PowerSyncDBListener extends StreamingSyncImplementationListener {
72
86
  initialized: () => void;
73
87
  schemaChanged: (schema: Schema) => void;
88
+ closing: () => Promise<void> | void;
89
+ closed: () => Promise<void> | void;
74
90
  }
75
91
  export interface PowerSyncCloseOptions {
76
92
  /**
@@ -81,7 +97,6 @@ export interface PowerSyncCloseOptions {
81
97
  disconnect?: boolean;
82
98
  }
83
99
  export declare const DEFAULT_POWERSYNC_CLOSE_OPTIONS: PowerSyncCloseOptions;
84
- export declare const DEFAULT_WATCH_THROTTLE_MS = 30;
85
100
  export declare const DEFAULT_POWERSYNC_DB_OPTIONS: {
86
101
  retryDelayMs: number;
87
102
  crudUploadThrottleMs: number;
@@ -186,6 +201,11 @@ export declare abstract class AbstractPowerSyncDatabase extends BaseObserver<Pow
186
201
  */
187
202
  init(): Promise<void>;
188
203
  resolvedConnectionOptions(options?: PowerSyncConnectionOptions): RequiredAdditionalConnectionOptions;
204
+ /**
205
+ * @deprecated Use {@link AbstractPowerSyncDatabase#close} instead.
206
+ * Clears all listeners registered by {@link AbstractPowerSyncDatabase#registerListener}.
207
+ */
208
+ dispose(): void;
189
209
  /**
190
210
  * Locking mechanism for exclusively running critical portions of connect/disconnect operations.
191
211
  * Locking here is mostly only important on web for multiple tab scenarios.
@@ -389,6 +409,42 @@ export declare abstract class AbstractPowerSyncDatabase extends BaseObserver<Pow
389
409
  * ```
390
410
  */
391
411
  watch(sql: string, parameters?: any[], handler?: WatchHandler, options?: SQLWatchOptions): void;
412
+ /**
413
+ * Allows defining a query which can be used to build a {@link WatchedQuery}.
414
+ * The defined query will be executed with {@link AbstractPowerSyncDatabase#getAll}.
415
+ * An optional mapper function can be provided to transform the results.
416
+ *
417
+ * @example
418
+ * ```javascript
419
+ * const watchedTodos = powersync.query({
420
+ * sql: `SELECT photo_id as id FROM todos WHERE photo_id IS NOT NULL`,
421
+ * parameters: [],
422
+ * mapper: (row) => ({
423
+ * ...row,
424
+ * created_at: new Date(row.created_at as string)
425
+ * })
426
+ * })
427
+ * .watch()
428
+ * // OR use .differentialWatch() for fine-grained watches.
429
+ * ```
430
+ */
431
+ query<RowType>(query: ArrayQueryDefinition<RowType>): Query<RowType>;
432
+ /**
433
+ * Allows building a {@link WatchedQuery} using an existing {@link WatchCompatibleQuery}.
434
+ * The watched query will use the provided {@link WatchCompatibleQuery.execute} method to query results.
435
+ *
436
+ * @example
437
+ * ```javascript
438
+ *
439
+ * // Potentially a query from an ORM like Drizzle
440
+ * const query = db.select().from(lists);
441
+ *
442
+ * const watchedTodos = powersync.customQuery(query)
443
+ * .watch()
444
+ * // OR use .differentialWatch() for fine-grained watches.
445
+ * ```
446
+ */
447
+ customQuery<RowType>(query: WatchCompatibleQuery<RowType[]>): Query<RowType>;
392
448
  /**
393
449
  * Execute a read query every time the source tables are modified.
394
450
  * Use {@link SQLWatchOptions.throttleMs} to specify the minimum interval between queries.
@@ -437,7 +493,7 @@ export declare abstract class AbstractPowerSyncDatabase extends BaseObserver<Pow
437
493
  * }
438
494
  * ```
439
495
  */
440
- onChange(options?: SQLWatchOptions): AsyncIterable<WatchOnChangeEvent>;
496
+ onChange(options?: SQLOnChangeOptions): AsyncIterable<WatchOnChangeEvent>;
441
497
  /**
442
498
  * See {@link onChangeWithCallback}.
443
499
  *
@@ -452,7 +508,7 @@ export declare abstract class AbstractPowerSyncDatabase extends BaseObserver<Pow
452
508
  * }
453
509
  * ```
454
510
  */
455
- onChange(handler?: WatchOnChangeHandler, options?: SQLWatchOptions): () => void;
511
+ onChange(handler?: WatchOnChangeHandler, options?: SQLOnChangeOptions): () => void;
456
512
  /**
457
513
  * Invoke the provided callback on any changes to any of the specified tables.
458
514
  *
@@ -465,7 +521,7 @@ export declare abstract class AbstractPowerSyncDatabase extends BaseObserver<Pow
465
521
  * @param options Options for configuring watch behavior
466
522
  * @returns A dispose function to stop watching for changes
467
523
  */
468
- onChangeWithCallback(handler?: WatchOnChangeHandler, options?: SQLWatchOptions): () => void;
524
+ onChangeWithCallback(handler?: WatchOnChangeHandler, options?: SQLOnChangeOptions): () => void;
469
525
  /**
470
526
  * Create a Stream of changes to any of the specified tables.
471
527
  *
@@ -9,13 +9,15 @@ import { BaseObserver } from '../utils/BaseObserver.js';
9
9
  import { ControlledExecutor } from '../utils/ControlledExecutor.js';
10
10
  import { throttleTrailing } from '../utils/async.js';
11
11
  import { ConnectionManager } from './ConnectionManager.js';
12
+ import { CustomQuery } from './CustomQuery.js';
12
13
  import { isDBAdapter, isSQLOpenFactory, isSQLOpenOptions } from './SQLOpenFactory.js';
13
- import { runOnSchemaChange } from './runOnSchemaChange.js';
14
14
  import { PSInternalTable } from './sync/bucket/BucketStorageAdapter.js';
15
15
  import { CrudBatch } from './sync/bucket/CrudBatch.js';
16
16
  import { CrudEntry } from './sync/bucket/CrudEntry.js';
17
17
  import { CrudTransaction } from './sync/bucket/CrudTransaction.js';
18
18
  import { DEFAULT_CRUD_UPLOAD_THROTTLE_MS, DEFAULT_RETRY_DELAY_MS } from './sync/stream/AbstractStreamingSyncImplementation.js';
19
+ import { DEFAULT_WATCH_THROTTLE_MS } from './watched/WatchedQuery.js';
20
+ import { OnChangeQueryProcessor } from './watched/processors/OnChangeQueryProcessor.js';
19
21
  const POWERSYNC_TABLE_MATCH = /(^ps_data__|^ps_data_local__)/;
20
22
  const DEFAULT_DISCONNECT_CLEAR_OPTIONS = {
21
23
  clearLocal: true
@@ -23,7 +25,6 @@ const DEFAULT_DISCONNECT_CLEAR_OPTIONS = {
23
25
  export const DEFAULT_POWERSYNC_CLOSE_OPTIONS = {
24
26
  disconnect: true
25
27
  };
26
- export const DEFAULT_WATCH_THROTTLE_MS = 30;
27
28
  export const DEFAULT_POWERSYNC_DB_OPTIONS = {
28
29
  retryDelayMs: 5000,
29
30
  crudUploadThrottleMs: DEFAULT_CRUD_UPLOAD_THROTTLE_MS
@@ -285,6 +286,13 @@ export class AbstractPowerSyncDatabase extends BaseObserver {
285
286
  crudUploadThrottleMs: options?.crudUploadThrottleMs ?? this.options.crudUploadThrottleMs ?? DEFAULT_CRUD_UPLOAD_THROTTLE_MS
286
287
  };
287
288
  }
289
+ /**
290
+ * @deprecated Use {@link AbstractPowerSyncDatabase#close} instead.
291
+ * Clears all listeners registered by {@link AbstractPowerSyncDatabase#registerListener}.
292
+ */
293
+ dispose() {
294
+ return super.dispose();
295
+ }
288
296
  /**
289
297
  * Locking mechanism for exclusively running critical portions of connect/disconnect operations.
290
298
  * Locking here is mostly only important on web for multiple tab scenarios.
@@ -341,6 +349,7 @@ export class AbstractPowerSyncDatabase extends BaseObserver {
341
349
  if (this.closed) {
342
350
  return;
343
351
  }
352
+ await this.iterateAsyncListeners(async (cb) => cb.closing?.());
344
353
  const { disconnect } = options;
345
354
  if (disconnect) {
346
355
  await this.disconnect();
@@ -348,6 +357,7 @@ export class AbstractPowerSyncDatabase extends BaseObserver {
348
357
  await this.connectionManager.close();
349
358
  await this.database.close();
350
359
  this.closed = true;
360
+ await this.iterateAsyncListeners(async (cb) => cb.closed?.());
351
361
  }
352
362
  /**
353
363
  * Get upload queue size estimate and count.
@@ -594,6 +604,60 @@ export class AbstractPowerSyncDatabase extends BaseObserver {
594
604
  const options = handlerOrOptions;
595
605
  return this.watchWithAsyncGenerator(sql, parameters, options);
596
606
  }
607
+ /**
608
+ * Allows defining a query which can be used to build a {@link WatchedQuery}.
609
+ * The defined query will be executed with {@link AbstractPowerSyncDatabase#getAll}.
610
+ * An optional mapper function can be provided to transform the results.
611
+ *
612
+ * @example
613
+ * ```javascript
614
+ * const watchedTodos = powersync.query({
615
+ * sql: `SELECT photo_id as id FROM todos WHERE photo_id IS NOT NULL`,
616
+ * parameters: [],
617
+ * mapper: (row) => ({
618
+ * ...row,
619
+ * created_at: new Date(row.created_at as string)
620
+ * })
621
+ * })
622
+ * .watch()
623
+ * // OR use .differentialWatch() for fine-grained watches.
624
+ * ```
625
+ */
626
+ query(query) {
627
+ const { sql, parameters = [], mapper } = query;
628
+ const compatibleQuery = {
629
+ compile: () => ({
630
+ sql,
631
+ parameters
632
+ }),
633
+ execute: async ({ sql, parameters }) => {
634
+ const result = await this.getAll(sql, parameters);
635
+ return mapper ? result.map(mapper) : result;
636
+ }
637
+ };
638
+ return this.customQuery(compatibleQuery);
639
+ }
640
+ /**
641
+ * Allows building a {@link WatchedQuery} using an existing {@link WatchCompatibleQuery}.
642
+ * The watched query will use the provided {@link WatchCompatibleQuery.execute} method to query results.
643
+ *
644
+ * @example
645
+ * ```javascript
646
+ *
647
+ * // Potentially a query from an ORM like Drizzle
648
+ * const query = db.select().from(lists);
649
+ *
650
+ * const watchedTodos = powersync.customQuery(query)
651
+ * .watch()
652
+ * // OR use .differentialWatch() for fine-grained watches.
653
+ * ```
654
+ */
655
+ customQuery(query) {
656
+ return new CustomQuery({
657
+ db: this,
658
+ query
659
+ });
660
+ }
597
661
  /**
598
662
  * Execute a read query every time the source tables are modified.
599
663
  * Use {@link SQLWatchOptions.throttleMs} to specify the minimum interval between queries.
@@ -611,35 +675,42 @@ export class AbstractPowerSyncDatabase extends BaseObserver {
611
675
  if (!onResult) {
612
676
  throw new Error('onResult is required');
613
677
  }
614
- const watchQuery = async (abortSignal) => {
615
- try {
616
- const resolvedTables = await this.resolveTables(sql, parameters, options);
617
- // Fetch initial data
618
- const result = await this.executeReadOnly(sql, parameters);
619
- onResult(result);
620
- this.onChangeWithCallback({
621
- onChange: async () => {
622
- try {
623
- const result = await this.executeReadOnly(sql, parameters);
624
- onResult(result);
625
- }
626
- catch (error) {
627
- onError?.(error);
628
- }
629
- },
630
- onError
631
- }, {
632
- ...(options ?? {}),
633
- tables: resolvedTables,
634
- // Override the abort signal since we intercept it
635
- signal: abortSignal
636
- });
678
+ const { comparator } = options ?? {};
679
+ // This API yields a QueryResult type.
680
+ // This is not a standard Array result, which makes it incompatible with the .query API.
681
+ const watchedQuery = new OnChangeQueryProcessor({
682
+ db: this,
683
+ comparator,
684
+ placeholderData: null,
685
+ watchOptions: {
686
+ query: {
687
+ compile: () => ({
688
+ sql: sql,
689
+ parameters: parameters ?? []
690
+ }),
691
+ execute: () => this.executeReadOnly(sql, parameters)
692
+ },
693
+ reportFetching: false,
694
+ throttleMs: options?.throttleMs ?? DEFAULT_WATCH_THROTTLE_MS,
695
+ triggerOnTables: options?.tables
637
696
  }
638
- catch (error) {
639
- onError?.(error);
697
+ });
698
+ const dispose = watchedQuery.registerListener({
699
+ onData: (data) => {
700
+ if (!data) {
701
+ // This should not happen. We only use null for the initial data.
702
+ return;
703
+ }
704
+ onResult(data);
705
+ },
706
+ onError: (error) => {
707
+ onError(error);
640
708
  }
641
- };
642
- runOnSchemaChange(watchQuery, this, options);
709
+ });
710
+ options?.signal?.addEventListener('abort', () => {
711
+ dispose();
712
+ watchedQuery.close();
713
+ });
643
714
  }
644
715
  /**
645
716
  * Execute a read query every time the source tables are modified.
@@ -729,6 +800,9 @@ export class AbstractPowerSyncDatabase extends BaseObserver {
729
800
  return;
730
801
  executor.schedule({ changedTables: intersection });
731
802
  }), throttleMs);
803
+ if (options?.triggerImmediate) {
804
+ executor.schedule({ changedTables: [] });
805
+ }
732
806
  const dispose = this.database.registerListener({
733
807
  tablesUpdated: async (update) => {
734
808
  try {
@@ -0,0 +1,22 @@
1
+ import { AbstractPowerSyncDatabase } from './AbstractPowerSyncDatabase.js';
2
+ import { Query, StandardWatchedQueryOptions } from './Query.js';
3
+ import { DifferentialQueryProcessor, DifferentialWatchedQueryOptions } from './watched/processors/DifferentialQueryProcessor.js';
4
+ import { OnChangeQueryProcessor } from './watched/processors/OnChangeQueryProcessor.js';
5
+ import { WatchCompatibleQuery, WatchedQueryOptions } from './watched/WatchedQuery.js';
6
+ /**
7
+ * @internal
8
+ */
9
+ export interface CustomQueryOptions<RowType> {
10
+ db: AbstractPowerSyncDatabase;
11
+ query: WatchCompatibleQuery<RowType[]>;
12
+ }
13
+ /**
14
+ * @internal
15
+ */
16
+ export declare class CustomQuery<RowType> implements Query<RowType> {
17
+ protected options: CustomQueryOptions<RowType>;
18
+ constructor(options: CustomQueryOptions<RowType>);
19
+ protected resolveOptions(options: WatchedQueryOptions): WatchedQueryOptions;
20
+ watch(watchOptions: StandardWatchedQueryOptions<RowType>): OnChangeQueryProcessor<RowType[]>;
21
+ differentialWatch(differentialWatchOptions: DifferentialWatchedQueryOptions<RowType>): DifferentialQueryProcessor<RowType>;
22
+ }
@@ -0,0 +1,42 @@
1
+ import { FalsyComparator } from './watched/processors/comparators.js';
2
+ import { DifferentialQueryProcessor } from './watched/processors/DifferentialQueryProcessor.js';
3
+ import { OnChangeQueryProcessor } from './watched/processors/OnChangeQueryProcessor.js';
4
+ import { DEFAULT_WATCH_QUERY_OPTIONS } from './watched/WatchedQuery.js';
5
+ /**
6
+ * @internal
7
+ */
8
+ export class CustomQuery {
9
+ options;
10
+ constructor(options) {
11
+ this.options = options;
12
+ }
13
+ resolveOptions(options) {
14
+ return {
15
+ reportFetching: options?.reportFetching ?? DEFAULT_WATCH_QUERY_OPTIONS.reportFetching,
16
+ throttleMs: options?.throttleMs ?? DEFAULT_WATCH_QUERY_OPTIONS.throttleMs,
17
+ triggerOnTables: options?.triggerOnTables
18
+ };
19
+ }
20
+ watch(watchOptions) {
21
+ return new OnChangeQueryProcessor({
22
+ db: this.options.db,
23
+ comparator: watchOptions?.comparator ?? FalsyComparator,
24
+ placeholderData: watchOptions?.placeholderData ?? [],
25
+ watchOptions: {
26
+ ...this.resolveOptions(watchOptions),
27
+ query: this.options.query
28
+ }
29
+ });
30
+ }
31
+ differentialWatch(differentialWatchOptions) {
32
+ return new DifferentialQueryProcessor({
33
+ db: this.options.db,
34
+ rowComparator: differentialWatchOptions?.rowComparator,
35
+ placeholderData: differentialWatchOptions?.placeholderData ?? [],
36
+ watchOptions: {
37
+ ...this.resolveOptions(differentialWatchOptions),
38
+ query: this.options.query
39
+ }
40
+ });
41
+ }
42
+ }
@@ -0,0 +1,97 @@
1
+ import { WatchedQueryComparator } from './watched/processors/comparators.js';
2
+ import { DifferentialWatchedQuery, DifferentialWatchedQueryOptions } from './watched/processors/DifferentialQueryProcessor.js';
3
+ import { StandardWatchedQuery } from './watched/processors/OnChangeQueryProcessor.js';
4
+ import { WatchedQueryOptions } from './watched/WatchedQuery.js';
5
+ /**
6
+ * Query parameters for {@link ArrayQueryDefinition#parameters}
7
+ */
8
+ export type QueryParam = string | number | boolean | null | undefined | bigint | Uint8Array;
9
+ /**
10
+ * Options for building a query with {@link AbstractPowerSyncDatabase#query}.
11
+ * This query will be executed with {@link AbstractPowerSyncDatabase#getAll}.
12
+ */
13
+ export interface ArrayQueryDefinition<RowType = unknown> {
14
+ sql: string;
15
+ parameters?: ReadonlyArray<Readonly<QueryParam>>;
16
+ /**
17
+ * Maps the raw SQLite row to a custom typed object.
18
+ * @example
19
+ * ```javascript
20
+ * mapper: (row) => ({
21
+ * ...row,
22
+ * created_at: new Date(row.created_at as string),
23
+ * })
24
+ * ```
25
+ */
26
+ mapper?: (row: Record<string, unknown>) => RowType;
27
+ }
28
+ /**
29
+ * Options for {@link Query#watch}.
30
+ */
31
+ export interface StandardWatchedQueryOptions<RowType> extends WatchedQueryOptions {
32
+ /**
33
+ * The underlying watched query implementation (re)evaluates the query on any SQLite table change.
34
+ *
35
+ * Providing this optional comparator can be used to filter duplicate result set emissions when the result set is unchanged.
36
+ * The comparator compares the previous and current result set.
37
+ *
38
+ * For an efficient comparator see {@link ArrayComparator}.
39
+ *
40
+ * @example
41
+ * ```javascript
42
+ * comparator: new ArrayComparator({
43
+ * compareBy: (item) => JSON.stringify(item)
44
+ * })
45
+ * ```
46
+ */
47
+ comparator?: WatchedQueryComparator<RowType[]>;
48
+ /**
49
+ * The initial data state reported while the query is loading for the first time.
50
+ * @default []
51
+ */
52
+ placeholderData?: RowType[];
53
+ }
54
+ export interface Query<RowType> {
55
+ /**
56
+ * Creates a {@link WatchedQuery} which watches and emits results of the linked query.
57
+ *
58
+ * By default the returned watched query will emit changes whenever a change to the underlying SQLite tables is made.
59
+ * These changes might not be relevant to the query, but the query will emit a new result set.
60
+ *
61
+ * A {@link StandardWatchedQueryOptions#comparator} can be provided to limit the data emissions. The watched query will still
62
+ * query the underlying DB on underlying table changes, but the result will only be emitted if the comparator detects a change in the results.
63
+ *
64
+ * The comparator in this method is optimized and returns early as soon as it detects a change. Each data emission will correlate to a change in the result set,
65
+ * but note that the result set will not maintain internal object references to the previous result set. If internal object references are needed,
66
+ * consider using {@link Query#differentialWatch} instead.
67
+ */
68
+ watch(options?: StandardWatchedQueryOptions<RowType>): StandardWatchedQuery<ReadonlyArray<Readonly<RowType>>>;
69
+ /**
70
+ * Creates a {@link WatchedQuery} which watches and emits results of the linked query.
71
+ *
72
+ * This query method watches for changes in the underlying SQLite tables and runs the query on each table change.
73
+ * The difference between the current and previous result set is computed.
74
+ * The watched query will not emit changes if the result set is identical to the previous result set.
75
+ *
76
+ * If the result set is different, the watched query will emit the new result set and emit a detailed diff of the changes via the `onData` and `onDiff` listeners.
77
+ *
78
+ * The deep differentiation allows maintaining result set object references between result emissions.
79
+ * The {@link DifferentialWatchedQuery#state} `data` array will contain the previous row references for unchanged rows.
80
+ *
81
+ * @example
82
+ * ```javascript
83
+ * const watchedLists = powerSync.query({sql: 'SELECT * FROM lists'})
84
+ * .differentialWatch();
85
+ *
86
+ * const disposeListener = watchedLists.registerListener({
87
+ * onData: (lists) => {
88
+ * console.log('The latest result set for the query is', lists);
89
+ * },
90
+ * onDiff: (diff) => {
91
+ * console.log('The lists result set has changed since the last emission', diff.added, diff.removed, diff.updated, diff.all)
92
+ * }
93
+ * })
94
+ * ```
95
+ */
96
+ differentialWatch(options?: DifferentialWatchedQueryOptions<RowType>): DifferentialWatchedQuery<RowType>;
97
+ }
@@ -0,0 +1 @@
1
+ export {};
@@ -1,4 +1,4 @@
1
- import { BaseListener, BaseObserver, Disposable } from '../../../utils/BaseObserver.js';
1
+ import { BaseListener, BaseObserverInterface, Disposable } from '../../../utils/BaseObserver.js';
2
2
  import { CrudBatch } from './CrudBatch.js';
3
3
  import { CrudEntry, OpId } from './CrudEntry.js';
4
4
  import { SyncDataBatch } from './SyncDataBatch.js';
@@ -62,7 +62,7 @@ export declare enum PowerSyncControlCommand {
62
62
  export interface BucketStorageListener extends BaseListener {
63
63
  crudUpdate: () => void;
64
64
  }
65
- export interface BucketStorageAdapter extends BaseObserver<BucketStorageListener>, Disposable {
65
+ export interface BucketStorageAdapter extends BaseObserverInterface<BucketStorageListener>, Disposable {
66
66
  init(): Promise<void>;
67
67
  saveSyncData(batch: SyncDataBatch, fixedKeyFormat?: boolean): Promise<void>;
68
68
  removeBuckets(buckets: string[]): Promise<void>;
@@ -204,6 +204,22 @@ export class AbstractRemote {
204
204
  // headers with websockets on web. The browser userAgent is however added
205
205
  // automatically as a header.
206
206
  const userAgent = this.getUserAgent();
207
+ const stream = new DataStream({
208
+ logger: this.logger,
209
+ pressure: {
210
+ lowWaterMark: SYNC_QUEUE_REQUEST_LOW_WATER
211
+ },
212
+ mapLine: map
213
+ });
214
+ // Handle upstream abort
215
+ if (options.abortSignal?.aborted) {
216
+ throw new AbortOperation('Connection request aborted');
217
+ }
218
+ else {
219
+ options.abortSignal?.addEventListener('abort', () => {
220
+ stream.close();
221
+ }, { once: true });
222
+ }
207
223
  let keepAliveTimeout;
208
224
  const resetTimeout = () => {
209
225
  clearTimeout(keepAliveTimeout);
@@ -213,12 +229,22 @@ export class AbstractRemote {
213
229
  }, SOCKET_TIMEOUT_MS);
214
230
  };
215
231
  resetTimeout();
232
+ // Typescript complains about this being `never` if it's not assigned here.
233
+ // This is assigned in `wsCreator`.
234
+ let disposeSocketConnectionTimeout = () => { };
216
235
  const url = this.options.socketUrlTransformer(request.url);
217
236
  const connector = new RSocketConnector({
218
237
  transport: new WebsocketClientTransport({
219
238
  url,
220
239
  wsCreator: (url) => {
221
240
  const socket = this.createSocket(url);
241
+ disposeSocketConnectionTimeout = stream.registerListener({
242
+ closed: () => {
243
+ // Allow closing the underlying WebSocket if the stream was closed before the
244
+ // RSocket connect completed. This should effectively abort the request.
245
+ socket.close();
246
+ }
247
+ });
222
248
  socket.addEventListener('message', (event) => {
223
249
  resetTimeout();
224
250
  });
@@ -242,20 +268,18 @@ export class AbstractRemote {
242
268
  let rsocket;
243
269
  try {
244
270
  rsocket = await connector.connect();
271
+ // The connection is established, we no longer need to monitor the initial timeout
272
+ disposeSocketConnectionTimeout();
245
273
  }
246
274
  catch (ex) {
247
275
  this.logger.error(`Failed to connect WebSocket`, ex);
248
276
  clearTimeout(keepAliveTimeout);
277
+ if (!stream.closed) {
278
+ await stream.close();
279
+ }
249
280
  throw ex;
250
281
  }
251
282
  resetTimeout();
252
- const stream = new DataStream({
253
- logger: this.logger,
254
- pressure: {
255
- lowWaterMark: SYNC_QUEUE_REQUEST_LOW_WATER
256
- },
257
- mapLine: map
258
- });
259
283
  let socketIsClosed = false;
260
284
  const closeSocket = () => {
261
285
  clearTimeout(keepAliveTimeout);
@@ -341,18 +365,6 @@ export class AbstractRemote {
341
365
  l();
342
366
  }
343
367
  });
344
- /**
345
- * Handle abort operations here.
346
- * Unfortunately cannot insert them into the connection.
347
- */
348
- if (options.abortSignal?.aborted) {
349
- stream.close();
350
- }
351
- else {
352
- options.abortSignal?.addEventListener('abort', () => {
353
- stream.close();
354
- });
355
- }
356
368
  return stream;
357
369
  }
358
370
  /**
@@ -1,6 +1,6 @@
1
1
  import { ILogger } from 'js-logger';
2
2
  import { SyncStatus, SyncStatusOptions } from '../../../db/crud/SyncStatus.js';
3
- import { BaseListener, BaseObserver, Disposable } from '../../../utils/BaseObserver.js';
3
+ import { BaseListener, BaseObserver, BaseObserverInterface, Disposable } from '../../../utils/BaseObserver.js';
4
4
  import { BucketStorageAdapter } from '../bucket/BucketStorageAdapter.js';
5
5
  import { AbstractRemote, FetchStrategy } from './AbstractRemote.js';
6
6
  import { StreamingSyncRequestParameterType } from './streaming-sync-types.js';
@@ -136,7 +136,7 @@ export interface AdditionalConnectionOptions {
136
136
  }
137
137
  /** @internal */
138
138
  export type RequiredAdditionalConnectionOptions = Required<AdditionalConnectionOptions>;
139
- export interface StreamingSyncImplementation extends BaseObserver<StreamingSyncImplementationListener>, Disposable {
139
+ export interface StreamingSyncImplementation extends BaseObserverInterface<StreamingSyncImplementationListener>, Disposable {
140
140
  /**
141
141
  * Connects to the sync service
142
142
  */
@@ -168,6 +168,7 @@ export declare abstract class AbstractStreamingSyncImplementation extends BaseOb
168
168
  protected _lastSyncedAt: Date | null;
169
169
  protected options: AbstractStreamingSyncImplementationOptions;
170
170
  protected abortController: AbortController | null;
171
+ protected uploadAbortController: AbortController | null;
171
172
  protected crudUpdateListener?: () => void;
172
173
  protected streamingSyncPromise?: Promise<void>;
173
174
  protected logger: ILogger;