@powersync/common 0.0.0-dev-20250715104704 → 0.0.0-dev-20250722092404
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.
- package/dist/bundle.cjs +5 -5
- package/dist/bundle.mjs +3 -3
- package/lib/client/AbstractPowerSyncDatabase.d.ts +58 -8
- package/lib/client/AbstractPowerSyncDatabase.js +100 -36
- package/lib/client/CustomQuery.d.ts +25 -0
- package/lib/client/CustomQuery.js +41 -0
- package/lib/client/Query.d.ts +97 -0
- package/lib/client/Query.js +1 -0
- package/lib/client/sync/bucket/BucketStorageAdapter.d.ts +2 -2
- package/lib/client/sync/bucket/SqliteBucketStorage.js +14 -14
- package/lib/client/sync/stream/AbstractRemote.js +31 -19
- package/lib/client/sync/stream/AbstractStreamingSyncImplementation.d.ts +5 -5
- package/lib/client/sync/stream/AbstractStreamingSyncImplementation.js +30 -12
- package/lib/client/watched/GetAllQuery.d.ts +32 -0
- package/lib/client/watched/GetAllQuery.js +24 -0
- package/lib/client/watched/WatchedQuery.d.ts +93 -0
- package/lib/client/watched/WatchedQuery.js +12 -0
- package/lib/client/watched/processors/AbstractQueryProcessor.d.ts +67 -0
- package/lib/client/watched/processors/AbstractQueryProcessor.js +136 -0
- package/lib/client/watched/processors/DifferentialQueryProcessor.d.ts +121 -0
- package/lib/client/watched/processors/DifferentialQueryProcessor.js +164 -0
- package/lib/client/watched/processors/OnChangeQueryProcessor.d.ts +33 -0
- package/lib/client/watched/processors/OnChangeQueryProcessor.js +74 -0
- package/lib/client/watched/processors/comparators.d.ts +30 -0
- package/lib/client/watched/processors/comparators.js +34 -0
- package/lib/db/schema/RawTable.d.ts +4 -0
- package/lib/db/schema/RawTable.js +4 -0
- package/lib/index.d.ts +7 -1
- package/lib/index.js +7 -1
- package/lib/utils/BaseObserver.d.ts +3 -4
- package/lib/utils/BaseObserver.js +3 -0
- package/lib/utils/MetaBaseObserver.d.ts +29 -0
- package/lib/utils/MetaBaseObserver.js +50 -0
- package/package.json +1 -1
|
@@ -1,17 +1,20 @@
|
|
|
1
1
|
import { Mutex } from 'async-mutex';
|
|
2
|
-
import
|
|
2
|
+
import { ILogger } from 'js-logger';
|
|
3
3
|
import { DBAdapter, QueryResult, Transaction } from '../db/DBAdapter.js';
|
|
4
4
|
import { SyncStatus } from '../db/crud/SyncStatus.js';
|
|
5
5
|
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
|
|
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,10 +97,8 @@ 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
|
-
logger: Logger.ILogger;
|
|
88
102
|
crudUploadThrottleMs: number;
|
|
89
103
|
};
|
|
90
104
|
export declare const DEFAULT_CRUD_BATCH_LIMIT = 100;
|
|
@@ -118,6 +132,7 @@ export declare abstract class AbstractPowerSyncDatabase extends BaseObserver<Pow
|
|
|
118
132
|
protected _schema: Schema;
|
|
119
133
|
private _database;
|
|
120
134
|
protected runExclusiveMutex: Mutex;
|
|
135
|
+
logger: ILogger;
|
|
121
136
|
constructor(options: PowerSyncDatabaseOptionsWithDBAdapter);
|
|
122
137
|
constructor(options: PowerSyncDatabaseOptionsWithOpenFactory);
|
|
123
138
|
constructor(options: PowerSyncDatabaseOptionsWithSettings);
|
|
@@ -180,7 +195,6 @@ export declare abstract class AbstractPowerSyncDatabase extends BaseObserver<Pow
|
|
|
180
195
|
* Cannot be used while connected - this should only be called before {@link AbstractPowerSyncDatabase.connect}.
|
|
181
196
|
*/
|
|
182
197
|
updateSchema(schema: Schema): Promise<void>;
|
|
183
|
-
get logger(): Logger.ILogger;
|
|
184
198
|
/**
|
|
185
199
|
* Wait for initialization to complete.
|
|
186
200
|
* While initializing is automatic, this helps to catch and report initialization errors.
|
|
@@ -390,6 +404,42 @@ export declare abstract class AbstractPowerSyncDatabase extends BaseObserver<Pow
|
|
|
390
404
|
* ```
|
|
391
405
|
*/
|
|
392
406
|
watch(sql: string, parameters?: any[], handler?: WatchHandler, options?: SQLWatchOptions): void;
|
|
407
|
+
/**
|
|
408
|
+
* Allows defining a query which can be used to build a {@link WatchedQuery}.
|
|
409
|
+
* The defined query will be executed with {@link AbstractPowerSyncDatabase#getAll}.
|
|
410
|
+
* An optional mapper function can be provided to transform the results.
|
|
411
|
+
*
|
|
412
|
+
* @example
|
|
413
|
+
* ```javascript
|
|
414
|
+
* const watchedTodos = powersync.query({
|
|
415
|
+
* sql: `SELECT photo_id as id FROM todos WHERE photo_id IS NOT NULL`,
|
|
416
|
+
* parameters: [],
|
|
417
|
+
* mapper: (row) => ({
|
|
418
|
+
* ...row,
|
|
419
|
+
* created_at: new Date(row.created_at as string)
|
|
420
|
+
* })
|
|
421
|
+
* })
|
|
422
|
+
* .watch()
|
|
423
|
+
* // OR use .differentialWatch() for fine-grained watches.
|
|
424
|
+
* ```
|
|
425
|
+
*/
|
|
426
|
+
query<RowType>(query: ArrayQueryDefinition<RowType>): Query<RowType>;
|
|
427
|
+
/**
|
|
428
|
+
* Allows building a {@link WatchedQuery} using an existing {@link WatchCompatibleQuery}.
|
|
429
|
+
* The watched query will use the provided {@link WatchCompatibleQuery.execute} method to query results.
|
|
430
|
+
*
|
|
431
|
+
* @example
|
|
432
|
+
* ```javascript
|
|
433
|
+
*
|
|
434
|
+
* // Potentially a query from an ORM like Drizzle
|
|
435
|
+
* const query = db.select().from(lists);
|
|
436
|
+
*
|
|
437
|
+
* const watchedTodos = powersync.customQuery(query)
|
|
438
|
+
* .watch()
|
|
439
|
+
* // OR use .differentialWatch() for fine-grained watches.
|
|
440
|
+
* ```
|
|
441
|
+
*/
|
|
442
|
+
customQuery<RowType>(query: WatchCompatibleQuery<RowType[]>): Query<RowType>;
|
|
393
443
|
/**
|
|
394
444
|
* Execute a read query every time the source tables are modified.
|
|
395
445
|
* Use {@link SQLWatchOptions.throttleMs} to specify the minimum interval between queries.
|
|
@@ -438,7 +488,7 @@ export declare abstract class AbstractPowerSyncDatabase extends BaseObserver<Pow
|
|
|
438
488
|
* }
|
|
439
489
|
* ```
|
|
440
490
|
*/
|
|
441
|
-
onChange(options?:
|
|
491
|
+
onChange(options?: SQLOnChangeOptions): AsyncIterable<WatchOnChangeEvent>;
|
|
442
492
|
/**
|
|
443
493
|
* See {@link onChangeWithCallback}.
|
|
444
494
|
*
|
|
@@ -453,7 +503,7 @@ export declare abstract class AbstractPowerSyncDatabase extends BaseObserver<Pow
|
|
|
453
503
|
* }
|
|
454
504
|
* ```
|
|
455
505
|
*/
|
|
456
|
-
onChange(handler?: WatchOnChangeHandler, options?:
|
|
506
|
+
onChange(handler?: WatchOnChangeHandler, options?: SQLOnChangeOptions): () => void;
|
|
457
507
|
/**
|
|
458
508
|
* Invoke the provided callback on any changes to any of the specified tables.
|
|
459
509
|
*
|
|
@@ -466,7 +516,7 @@ export declare abstract class AbstractPowerSyncDatabase extends BaseObserver<Pow
|
|
|
466
516
|
* @param options Options for configuring watch behavior
|
|
467
517
|
* @returns A dispose function to stop watching for changes
|
|
468
518
|
*/
|
|
469
|
-
onChangeWithCallback(handler?: WatchOnChangeHandler, options?:
|
|
519
|
+
onChangeWithCallback(handler?: WatchOnChangeHandler, options?: SQLOnChangeOptions): () => void;
|
|
470
520
|
/**
|
|
471
521
|
* Create a Stream of changes to any of the specified tables.
|
|
472
522
|
*
|
|
@@ -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,10 +25,8 @@ 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
|
-
logger: Logger.get('PowerSyncDatabase'),
|
|
30
30
|
crudUploadThrottleMs: DEFAULT_CRUD_UPLOAD_THROTTLE_MS
|
|
31
31
|
};
|
|
32
32
|
export const DEFAULT_CRUD_BATCH_LIMIT = 100;
|
|
@@ -64,6 +64,7 @@ export class AbstractPowerSyncDatabase extends BaseObserver {
|
|
|
64
64
|
_schema;
|
|
65
65
|
_database;
|
|
66
66
|
runExclusiveMutex;
|
|
67
|
+
logger;
|
|
67
68
|
constructor(options) {
|
|
68
69
|
super();
|
|
69
70
|
this.options = options;
|
|
@@ -83,6 +84,7 @@ export class AbstractPowerSyncDatabase extends BaseObserver {
|
|
|
83
84
|
else {
|
|
84
85
|
throw new Error('The provided `database` option is invalid.');
|
|
85
86
|
}
|
|
87
|
+
this.logger = options.logger ?? Logger.get(`PowerSyncDatabase[${this._database.name}]`);
|
|
86
88
|
this.bucketStorageAdapter = this.generateBucketStorageAdapter();
|
|
87
89
|
this.closed = false;
|
|
88
90
|
this.currentStatus = new SyncStatus({});
|
|
@@ -262,16 +264,13 @@ export class AbstractPowerSyncDatabase extends BaseObserver {
|
|
|
262
264
|
schema.validate();
|
|
263
265
|
}
|
|
264
266
|
catch (ex) {
|
|
265
|
-
this.
|
|
267
|
+
this.logger.warn('Schema validation failed. Unexpected behaviour could occur', ex);
|
|
266
268
|
}
|
|
267
269
|
this._schema = schema;
|
|
268
270
|
await this.database.execute('SELECT powersync_replace_schema(?)', [JSON.stringify(this.schema.toJSON())]);
|
|
269
271
|
await this.database.refreshSchema();
|
|
270
272
|
this.iterateListeners(async (cb) => cb.schemaChanged?.(schema));
|
|
271
273
|
}
|
|
272
|
-
get logger() {
|
|
273
|
-
return this.options.logger;
|
|
274
|
-
}
|
|
275
274
|
/**
|
|
276
275
|
* Wait for initialization to complete.
|
|
277
276
|
* While initializing is automatic, this helps to catch and report initialization errors.
|
|
@@ -343,6 +342,7 @@ export class AbstractPowerSyncDatabase extends BaseObserver {
|
|
|
343
342
|
if (this.closed) {
|
|
344
343
|
return;
|
|
345
344
|
}
|
|
345
|
+
await this.iterateAsyncListeners(async (cb) => cb.closing?.());
|
|
346
346
|
const { disconnect } = options;
|
|
347
347
|
if (disconnect) {
|
|
348
348
|
await this.disconnect();
|
|
@@ -350,6 +350,7 @@ export class AbstractPowerSyncDatabase extends BaseObserver {
|
|
|
350
350
|
await this.connectionManager.close();
|
|
351
351
|
await this.database.close();
|
|
352
352
|
this.closed = true;
|
|
353
|
+
await this.iterateAsyncListeners(async (cb) => cb.closed?.());
|
|
353
354
|
}
|
|
354
355
|
/**
|
|
355
356
|
* Get upload queue size estimate and count.
|
|
@@ -596,6 +597,60 @@ export class AbstractPowerSyncDatabase extends BaseObserver {
|
|
|
596
597
|
const options = handlerOrOptions;
|
|
597
598
|
return this.watchWithAsyncGenerator(sql, parameters, options);
|
|
598
599
|
}
|
|
600
|
+
/**
|
|
601
|
+
* Allows defining a query which can be used to build a {@link WatchedQuery}.
|
|
602
|
+
* The defined query will be executed with {@link AbstractPowerSyncDatabase#getAll}.
|
|
603
|
+
* An optional mapper function can be provided to transform the results.
|
|
604
|
+
*
|
|
605
|
+
* @example
|
|
606
|
+
* ```javascript
|
|
607
|
+
* const watchedTodos = powersync.query({
|
|
608
|
+
* sql: `SELECT photo_id as id FROM todos WHERE photo_id IS NOT NULL`,
|
|
609
|
+
* parameters: [],
|
|
610
|
+
* mapper: (row) => ({
|
|
611
|
+
* ...row,
|
|
612
|
+
* created_at: new Date(row.created_at as string)
|
|
613
|
+
* })
|
|
614
|
+
* })
|
|
615
|
+
* .watch()
|
|
616
|
+
* // OR use .differentialWatch() for fine-grained watches.
|
|
617
|
+
* ```
|
|
618
|
+
*/
|
|
619
|
+
query(query) {
|
|
620
|
+
const { sql, parameters = [], mapper } = query;
|
|
621
|
+
const compatibleQuery = {
|
|
622
|
+
compile: () => ({
|
|
623
|
+
sql,
|
|
624
|
+
parameters
|
|
625
|
+
}),
|
|
626
|
+
execute: async ({ sql, parameters }) => {
|
|
627
|
+
const result = await this.getAll(sql, parameters);
|
|
628
|
+
return mapper ? result.map(mapper) : result;
|
|
629
|
+
}
|
|
630
|
+
};
|
|
631
|
+
return this.customQuery(compatibleQuery);
|
|
632
|
+
}
|
|
633
|
+
/**
|
|
634
|
+
* Allows building a {@link WatchedQuery} using an existing {@link WatchCompatibleQuery}.
|
|
635
|
+
* The watched query will use the provided {@link WatchCompatibleQuery.execute} method to query results.
|
|
636
|
+
*
|
|
637
|
+
* @example
|
|
638
|
+
* ```javascript
|
|
639
|
+
*
|
|
640
|
+
* // Potentially a query from an ORM like Drizzle
|
|
641
|
+
* const query = db.select().from(lists);
|
|
642
|
+
*
|
|
643
|
+
* const watchedTodos = powersync.customQuery(query)
|
|
644
|
+
* .watch()
|
|
645
|
+
* // OR use .differentialWatch() for fine-grained watches.
|
|
646
|
+
* ```
|
|
647
|
+
*/
|
|
648
|
+
customQuery(query) {
|
|
649
|
+
return new CustomQuery({
|
|
650
|
+
db: this,
|
|
651
|
+
query
|
|
652
|
+
});
|
|
653
|
+
}
|
|
599
654
|
/**
|
|
600
655
|
* Execute a read query every time the source tables are modified.
|
|
601
656
|
* Use {@link SQLWatchOptions.throttleMs} to specify the minimum interval between queries.
|
|
@@ -609,39 +664,45 @@ export class AbstractPowerSyncDatabase extends BaseObserver {
|
|
|
609
664
|
* @param options Options for configuring watch behavior
|
|
610
665
|
*/
|
|
611
666
|
watchWithCallback(sql, parameters, handler, options) {
|
|
612
|
-
const { onResult, onError = (e) => this.
|
|
667
|
+
const { onResult, onError = (e) => this.logger.error(e) } = handler ?? {};
|
|
613
668
|
if (!onResult) {
|
|
614
669
|
throw new Error('onResult is required');
|
|
615
670
|
}
|
|
616
|
-
const
|
|
617
|
-
|
|
618
|
-
|
|
619
|
-
|
|
620
|
-
|
|
621
|
-
|
|
622
|
-
|
|
623
|
-
|
|
624
|
-
|
|
625
|
-
|
|
626
|
-
|
|
627
|
-
|
|
628
|
-
|
|
629
|
-
|
|
630
|
-
|
|
631
|
-
|
|
632
|
-
|
|
633
|
-
}, {
|
|
634
|
-
...(options ?? {}),
|
|
635
|
-
tables: resolvedTables,
|
|
636
|
-
// Override the abort signal since we intercept it
|
|
637
|
-
signal: abortSignal
|
|
638
|
-
});
|
|
671
|
+
const { comparator } = options ?? {};
|
|
672
|
+
// This API yields a QueryResult type.
|
|
673
|
+
// This is not a standard Array result, which makes it incompatible with the .query API.
|
|
674
|
+
const watchedQuery = new OnChangeQueryProcessor({
|
|
675
|
+
db: this,
|
|
676
|
+
comparator,
|
|
677
|
+
placeholderData: null,
|
|
678
|
+
watchOptions: {
|
|
679
|
+
query: {
|
|
680
|
+
compile: () => ({
|
|
681
|
+
sql: sql,
|
|
682
|
+
parameters: parameters ?? []
|
|
683
|
+
}),
|
|
684
|
+
execute: () => this.executeReadOnly(sql, parameters)
|
|
685
|
+
},
|
|
686
|
+
reportFetching: false,
|
|
687
|
+
throttleMs: options?.throttleMs ?? DEFAULT_WATCH_THROTTLE_MS
|
|
639
688
|
}
|
|
640
|
-
|
|
641
|
-
|
|
689
|
+
});
|
|
690
|
+
const dispose = watchedQuery.registerListener({
|
|
691
|
+
onData: (data) => {
|
|
692
|
+
if (!data) {
|
|
693
|
+
// This should not happen. We only use null for the initial data.
|
|
694
|
+
return;
|
|
695
|
+
}
|
|
696
|
+
onResult(data);
|
|
697
|
+
},
|
|
698
|
+
onError: (error) => {
|
|
699
|
+
onError(error);
|
|
642
700
|
}
|
|
643
|
-
};
|
|
644
|
-
|
|
701
|
+
});
|
|
702
|
+
options?.signal?.addEventListener('abort', () => {
|
|
703
|
+
dispose();
|
|
704
|
+
watchedQuery.close();
|
|
705
|
+
});
|
|
645
706
|
}
|
|
646
707
|
/**
|
|
647
708
|
* Execute a read query every time the source tables are modified.
|
|
@@ -715,7 +776,7 @@ export class AbstractPowerSyncDatabase extends BaseObserver {
|
|
|
715
776
|
* @returns A dispose function to stop watching for changes
|
|
716
777
|
*/
|
|
717
778
|
onChangeWithCallback(handler, options) {
|
|
718
|
-
const { onChange, onError = (e) => this.
|
|
779
|
+
const { onChange, onError = (e) => this.logger.error(e) } = handler ?? {};
|
|
719
780
|
if (!onChange) {
|
|
720
781
|
throw new Error('onChange is required');
|
|
721
782
|
}
|
|
@@ -731,6 +792,9 @@ export class AbstractPowerSyncDatabase extends BaseObserver {
|
|
|
731
792
|
return;
|
|
732
793
|
executor.schedule({ changedTables: intersection });
|
|
733
794
|
}), throttleMs);
|
|
795
|
+
if (options?.triggerImmediate) {
|
|
796
|
+
executor.schedule({ changedTables: [] });
|
|
797
|
+
}
|
|
734
798
|
const dispose = this.database.registerListener({
|
|
735
799
|
tablesUpdated: async (update) => {
|
|
736
800
|
try {
|
|
@@ -0,0 +1,25 @@
|
|
|
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): {
|
|
20
|
+
reportFetching: boolean | undefined;
|
|
21
|
+
throttleMs: number | undefined;
|
|
22
|
+
};
|
|
23
|
+
watch(watchOptions: StandardWatchedQueryOptions<RowType>): OnChangeQueryProcessor<RowType[]>;
|
|
24
|
+
differentialWatch(differentialWatchOptions: DifferentialWatchedQueryOptions<RowType>): DifferentialQueryProcessor<RowType>;
|
|
25
|
+
}
|
|
@@ -0,0 +1,41 @@
|
|
|
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
|
+
};
|
|
18
|
+
}
|
|
19
|
+
watch(watchOptions) {
|
|
20
|
+
return new OnChangeQueryProcessor({
|
|
21
|
+
db: this.options.db,
|
|
22
|
+
comparator: watchOptions?.comparator ?? FalsyComparator,
|
|
23
|
+
placeholderData: watchOptions?.placeholderData ?? [],
|
|
24
|
+
watchOptions: {
|
|
25
|
+
...this.resolveOptions(watchOptions),
|
|
26
|
+
query: this.options.query
|
|
27
|
+
}
|
|
28
|
+
});
|
|
29
|
+
}
|
|
30
|
+
differentialWatch(differentialWatchOptions) {
|
|
31
|
+
return new DifferentialQueryProcessor({
|
|
32
|
+
db: this.options.db,
|
|
33
|
+
comparator: differentialWatchOptions?.comparator,
|
|
34
|
+
placeholderData: differentialWatchOptions?.placeholderData ?? [],
|
|
35
|
+
watchOptions: {
|
|
36
|
+
...this.resolveOptions(differentialWatchOptions),
|
|
37
|
+
query: this.options.query
|
|
38
|
+
}
|
|
39
|
+
});
|
|
40
|
+
}
|
|
41
|
+
}
|
|
@@ -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,
|
|
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
|
|
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>;
|
|
@@ -64,11 +64,11 @@ export class SqliteBucketStorage extends BaseObserver {
|
|
|
64
64
|
async saveSyncData(batch, fixedKeyFormat = false) {
|
|
65
65
|
await this.writeTransaction(async (tx) => {
|
|
66
66
|
for (const b of batch.buckets) {
|
|
67
|
-
|
|
67
|
+
await tx.execute('INSERT INTO powersync_operations(op, data) VALUES(?, ?)', [
|
|
68
68
|
'save',
|
|
69
69
|
JSON.stringify({ buckets: [b.toJSON(fixedKeyFormat)] })
|
|
70
70
|
]);
|
|
71
|
-
this.logger.debug(
|
|
71
|
+
this.logger.debug(`Saved batch of data for bucket: ${b.bucket}, operations: ${b.data.length}`);
|
|
72
72
|
}
|
|
73
73
|
});
|
|
74
74
|
}
|
|
@@ -84,7 +84,7 @@ export class SqliteBucketStorage extends BaseObserver {
|
|
|
84
84
|
await this.writeTransaction(async (tx) => {
|
|
85
85
|
await tx.execute('INSERT INTO powersync_operations(op, data) VALUES(?, ?)', ['delete_bucket', bucket]);
|
|
86
86
|
});
|
|
87
|
-
this.logger.debug(
|
|
87
|
+
this.logger.debug(`Done deleting bucket ${bucket}`);
|
|
88
88
|
}
|
|
89
89
|
async hasCompletedSync() {
|
|
90
90
|
if (this._hasCompletedSync) {
|
|
@@ -106,6 +106,12 @@ export class SqliteBucketStorage extends BaseObserver {
|
|
|
106
106
|
}
|
|
107
107
|
return { ready: false, checkpointValid: false, checkpointFailures: r.checkpointFailures };
|
|
108
108
|
}
|
|
109
|
+
if (priority == null) {
|
|
110
|
+
this.logger.debug(`Validated checksums checkpoint ${checkpoint.last_op_id}`);
|
|
111
|
+
}
|
|
112
|
+
else {
|
|
113
|
+
this.logger.debug(`Validated checksums for partial checkpoint ${checkpoint.last_op_id}, priority ${priority}`);
|
|
114
|
+
}
|
|
109
115
|
let buckets = checkpoint.buckets;
|
|
110
116
|
if (priority !== undefined) {
|
|
111
117
|
buckets = buckets.filter((b) => hasMatchingPriority(priority, b));
|
|
@@ -122,7 +128,6 @@ export class SqliteBucketStorage extends BaseObserver {
|
|
|
122
128
|
});
|
|
123
129
|
const valid = await this.updateObjectsFromBuckets(checkpoint, priority);
|
|
124
130
|
if (!valid) {
|
|
125
|
-
this.logger.debug('Not at a consistent checkpoint - cannot update local db');
|
|
126
131
|
return { ready: false, checkpointValid: true };
|
|
127
132
|
}
|
|
128
133
|
return {
|
|
@@ -175,7 +180,6 @@ export class SqliteBucketStorage extends BaseObserver {
|
|
|
175
180
|
JSON.stringify({ ...checkpoint })
|
|
176
181
|
]);
|
|
177
182
|
const resultItem = rs.rows?.item(0);
|
|
178
|
-
this.logger.debug('validateChecksums priority, checkpoint, result item', priority, checkpoint, resultItem);
|
|
179
183
|
if (!resultItem) {
|
|
180
184
|
return {
|
|
181
185
|
checkpointValid: false,
|
|
@@ -208,30 +212,26 @@ export class SqliteBucketStorage extends BaseObserver {
|
|
|
208
212
|
}
|
|
209
213
|
const seqBefore = rs[0]['seq'];
|
|
210
214
|
const opId = await cb();
|
|
211
|
-
this.logger.debug(`[updateLocalTarget] Updating target to checkpoint ${opId}`);
|
|
212
215
|
return this.writeTransaction(async (tx) => {
|
|
213
216
|
const anyData = await tx.execute('SELECT 1 FROM ps_crud LIMIT 1');
|
|
214
217
|
if (anyData.rows?.length) {
|
|
215
218
|
// if isNotEmpty
|
|
216
|
-
this.logger.debug(
|
|
219
|
+
this.logger.debug(`New data uploaded since write checkpoint ${opId} - need new write checkpoint`);
|
|
217
220
|
return false;
|
|
218
221
|
}
|
|
219
222
|
const rs = await tx.execute("SELECT seq FROM sqlite_sequence WHERE name = 'ps_crud'");
|
|
220
223
|
if (!rs.rows?.length) {
|
|
221
224
|
// assert isNotEmpty
|
|
222
|
-
throw new Error('
|
|
225
|
+
throw new Error('SQLite Sequence should not be empty');
|
|
223
226
|
}
|
|
224
227
|
const seqAfter = rs.rows?.item(0)['seq'];
|
|
225
|
-
this.logger.debug('seqAfter', JSON.stringify(rs.rows?.item(0)));
|
|
226
228
|
if (seqAfter != seqBefore) {
|
|
227
|
-
this.logger.debug(
|
|
229
|
+
this.logger.debug(`New data uploaded since write checpoint ${opId} - need new write checkpoint (sequence updated)`);
|
|
228
230
|
// New crud data may have been uploaded since we got the checkpoint. Abort.
|
|
229
231
|
return false;
|
|
230
232
|
}
|
|
231
|
-
|
|
232
|
-
|
|
233
|
-
]);
|
|
234
|
-
this.logger.debug(['[updateLocalTarget] Response from updating target_op ', JSON.stringify(response)]);
|
|
233
|
+
this.logger.debug(`Updating target write checkpoint to ${opId}`);
|
|
234
|
+
await tx.execute("UPDATE ps_buckets SET target_op = CAST(? as INTEGER) WHERE name='$local'", [opId]);
|
|
235
235
|
return true;
|
|
236
236
|
});
|
|
237
237
|
}
|