@powersync/common 0.0.0-dev-20240920103931 → 0.0.0-dev-20250207081035
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.mjs +3 -3
- package/lib/client/AbstractPowerSyncDatabase.d.ts +12 -13
- package/lib/client/AbstractPowerSyncDatabase.js +49 -36
- package/lib/client/compilableQueryWatch.d.ts +7 -0
- package/lib/client/compilableQueryWatch.js +37 -0
- package/lib/client/runOnSchemaChange.d.ts +2 -0
- package/lib/client/runOnSchemaChange.js +23 -0
- package/lib/client/sync/bucket/SqliteBucketStorage.js +2 -4
- package/lib/client/sync/stream/AbstractRemote.js +7 -3
- package/lib/client/sync/stream/AbstractStreamingSyncImplementation.d.ts +24 -5
- package/lib/client/sync/stream/AbstractStreamingSyncImplementation.js +13 -7
- package/lib/db/DBAdapter.d.ts +4 -0
- package/lib/db/crud/SyncStatus.d.ts +2 -0
- package/lib/db/crud/SyncStatus.js +5 -1
- package/lib/db/schema/Column.d.ts +1 -1
- package/lib/db/schema/Column.js +3 -3
- package/lib/db/schema/Schema.d.ts +1 -1
- package/lib/db/schema/Schema.js +10 -0
- package/lib/index.d.ts +2 -0
- package/lib/index.js +2 -0
- package/package.json +2 -2
|
@@ -10,25 +10,18 @@ import { PowerSyncBackendConnector } from './connection/PowerSyncBackendConnecto
|
|
|
10
10
|
import { BucketStorageAdapter } from './sync/bucket/BucketStorageAdapter.js';
|
|
11
11
|
import { CrudBatch } from './sync/bucket/CrudBatch.js';
|
|
12
12
|
import { CrudTransaction } from './sync/bucket/CrudTransaction.js';
|
|
13
|
-
import { PowerSyncConnectionOptions, StreamingSyncImplementation, StreamingSyncImplementationListener } from './sync/stream/AbstractStreamingSyncImplementation.js';
|
|
13
|
+
import { type AdditionalConnectionOptions, type PowerSyncConnectionOptions, StreamingSyncImplementation, StreamingSyncImplementationListener, type RequiredAdditionalConnectionOptions } from './sync/stream/AbstractStreamingSyncImplementation.js';
|
|
14
14
|
export interface DisconnectAndClearOptions {
|
|
15
15
|
/** When set to false, data in local-only tables is preserved. */
|
|
16
16
|
clearLocal?: boolean;
|
|
17
17
|
}
|
|
18
|
-
export interface BasePowerSyncDatabaseOptions {
|
|
18
|
+
export interface BasePowerSyncDatabaseOptions extends AdditionalConnectionOptions {
|
|
19
19
|
/** Schema used for the local database. */
|
|
20
20
|
schema: Schema;
|
|
21
21
|
/**
|
|
22
|
-
*
|
|
23
|
-
* from the PowerSync backend after an error occurs.
|
|
22
|
+
* @deprecated Use {@link retryDelayMs} instead as this will be removed in future releases.
|
|
24
23
|
*/
|
|
25
24
|
retryDelay?: number;
|
|
26
|
-
/**
|
|
27
|
-
* Backend Connector CRUD operations are throttled
|
|
28
|
-
* to occur at most every `crudUploadThrottleMs`
|
|
29
|
-
* milliseconds.
|
|
30
|
-
*/
|
|
31
|
-
crudUploadThrottleMs?: number;
|
|
32
25
|
logger?: ILogger;
|
|
33
26
|
}
|
|
34
27
|
export interface PowerSyncDatabaseOptions extends BasePowerSyncDatabaseOptions {
|
|
@@ -56,6 +49,8 @@ export interface SQLWatchOptions {
|
|
|
56
49
|
/** The minimum interval between queries. */
|
|
57
50
|
throttleMs?: number;
|
|
58
51
|
/**
|
|
52
|
+
* @deprecated All tables specified in {@link tables} will be watched, including PowerSync tables with prefixes.
|
|
53
|
+
*
|
|
59
54
|
* Allows for watching any SQL table
|
|
60
55
|
* by not removing PowerSync table name prefixes
|
|
61
56
|
*/
|
|
@@ -74,6 +69,7 @@ export interface WatchOnChangeHandler {
|
|
|
74
69
|
}
|
|
75
70
|
export interface PowerSyncDBListener extends StreamingSyncImplementationListener {
|
|
76
71
|
initialized: () => void;
|
|
72
|
+
schemaChanged: (schema: Schema) => void;
|
|
77
73
|
}
|
|
78
74
|
export interface PowerSyncCloseOptions {
|
|
79
75
|
/**
|
|
@@ -86,10 +82,11 @@ export interface PowerSyncCloseOptions {
|
|
|
86
82
|
export declare const DEFAULT_POWERSYNC_CLOSE_OPTIONS: PowerSyncCloseOptions;
|
|
87
83
|
export declare const DEFAULT_WATCH_THROTTLE_MS = 30;
|
|
88
84
|
export declare const DEFAULT_POWERSYNC_DB_OPTIONS: {
|
|
89
|
-
|
|
85
|
+
retryDelayMs: number;
|
|
90
86
|
logger: Logger.ILogger;
|
|
91
87
|
crudUploadThrottleMs: number;
|
|
92
88
|
};
|
|
89
|
+
export declare const DEFAULT_CRUD_BATCH_LIMIT = 100;
|
|
93
90
|
/**
|
|
94
91
|
* Requesting nested or recursive locks can block the application in some circumstances.
|
|
95
92
|
* This default lock timeout will act as a failsafe to throw an error if a lock cannot
|
|
@@ -144,11 +141,12 @@ export declare abstract class AbstractPowerSyncDatabase extends BaseObserver<Pow
|
|
|
144
141
|
* Whether a connection to the PowerSync service is currently open.
|
|
145
142
|
*/
|
|
146
143
|
get connected(): boolean;
|
|
144
|
+
get connecting(): boolean;
|
|
147
145
|
/**
|
|
148
146
|
* Opens the DBAdapter given open options using a default open factory
|
|
149
147
|
*/
|
|
150
148
|
protected abstract openDBAdapter(options: PowerSyncDatabaseOptionsWithSettings): DBAdapter;
|
|
151
|
-
protected abstract generateSyncStreamImplementation(connector: PowerSyncBackendConnector): StreamingSyncImplementation;
|
|
149
|
+
protected abstract generateSyncStreamImplementation(connector: PowerSyncBackendConnector, options: RequiredAdditionalConnectionOptions): StreamingSyncImplementation;
|
|
152
150
|
protected abstract generateBucketStorageAdapter(): BucketStorageAdapter;
|
|
153
151
|
/**
|
|
154
152
|
* @returns A promise which will resolve once initialization is completed.
|
|
@@ -181,6 +179,7 @@ export declare abstract class AbstractPowerSyncDatabase extends BaseObserver<Pow
|
|
|
181
179
|
* While initializing is automatic, this helps to catch and report initialization errors.
|
|
182
180
|
*/
|
|
183
181
|
init(): Promise<void>;
|
|
182
|
+
resolvedConnectionOptions(options?: PowerSyncConnectionOptions): RequiredAdditionalConnectionOptions;
|
|
184
183
|
/**
|
|
185
184
|
* Connects to stream of events from the PowerSync instance.
|
|
186
185
|
*/
|
|
@@ -230,7 +229,7 @@ export declare abstract class AbstractPowerSyncDatabase extends BaseObserver<Pow
|
|
|
230
229
|
* data by transaction. One batch may contain data from multiple transactions,
|
|
231
230
|
* and a single transaction may be split over multiple batches.
|
|
232
231
|
*/
|
|
233
|
-
getCrudBatch(limit
|
|
232
|
+
getCrudBatch(limit?: number): Promise<CrudBatch | null>;
|
|
234
233
|
/**
|
|
235
234
|
* Get the next recorded transaction to upload.
|
|
236
235
|
*
|
|
@@ -13,7 +13,8 @@ import { PSInternalTable } from './sync/bucket/BucketStorageAdapter.js';
|
|
|
13
13
|
import { CrudBatch } from './sync/bucket/CrudBatch.js';
|
|
14
14
|
import { CrudEntry } from './sync/bucket/CrudEntry.js';
|
|
15
15
|
import { CrudTransaction } from './sync/bucket/CrudTransaction.js';
|
|
16
|
-
import { DEFAULT_CRUD_UPLOAD_THROTTLE_MS } from './sync/stream/AbstractStreamingSyncImplementation.js';
|
|
16
|
+
import { DEFAULT_CRUD_UPLOAD_THROTTLE_MS, DEFAULT_RETRY_DELAY_MS } from './sync/stream/AbstractStreamingSyncImplementation.js';
|
|
17
|
+
import { runOnSchemaChange } from './runOnSchemaChange.js';
|
|
17
18
|
const POWERSYNC_TABLE_MATCH = /(^ps_data__|^ps_data_local__)/;
|
|
18
19
|
const DEFAULT_DISCONNECT_CLEAR_OPTIONS = {
|
|
19
20
|
clearLocal: true
|
|
@@ -23,10 +24,11 @@ export const DEFAULT_POWERSYNC_CLOSE_OPTIONS = {
|
|
|
23
24
|
};
|
|
24
25
|
export const DEFAULT_WATCH_THROTTLE_MS = 30;
|
|
25
26
|
export const DEFAULT_POWERSYNC_DB_OPTIONS = {
|
|
26
|
-
|
|
27
|
+
retryDelayMs: 5000,
|
|
27
28
|
logger: Logger.get('PowerSyncDatabase'),
|
|
28
29
|
crudUploadThrottleMs: DEFAULT_CRUD_UPLOAD_THROTTLE_MS
|
|
29
30
|
};
|
|
31
|
+
export const DEFAULT_CRUD_BATCH_LIMIT = 100;
|
|
30
32
|
/**
|
|
31
33
|
* Requesting nested or recursive locks can block the application in some circumstances.
|
|
32
34
|
* This default lock timeout will act as a failsafe to throw an error if a lock cannot
|
|
@@ -112,6 +114,9 @@ export class AbstractPowerSyncDatabase extends BaseObserver {
|
|
|
112
114
|
get connected() {
|
|
113
115
|
return this.currentStatus?.connected || false;
|
|
114
116
|
}
|
|
117
|
+
get connecting() {
|
|
118
|
+
return this.currentStatus?.connecting || false;
|
|
119
|
+
}
|
|
115
120
|
/**
|
|
116
121
|
* @returns A promise which will resolve once initialization is completed.
|
|
117
122
|
*/
|
|
@@ -172,11 +177,11 @@ export class AbstractPowerSyncDatabase extends BaseObserver {
|
|
|
172
177
|
.map((n) => parseInt(n));
|
|
173
178
|
}
|
|
174
179
|
catch (e) {
|
|
175
|
-
throw new Error(`Unsupported powersync extension version. Need
|
|
180
|
+
throw new Error(`Unsupported powersync extension version. Need >=0.2.0 <1.0.0, got: ${this.sdkVersion}. Details: ${e.message}`);
|
|
176
181
|
}
|
|
177
|
-
// Validate
|
|
178
|
-
if (versionInts[0] != 0 || versionInts[1]
|
|
179
|
-
throw new Error(`Unsupported powersync extension version. Need
|
|
182
|
+
// Validate >=0.2.0 <1.0.0
|
|
183
|
+
if (versionInts[0] != 0 || versionInts[1] < 2 || versionInts[2] < 0) {
|
|
184
|
+
throw new Error(`Unsupported powersync extension version. Need >=0.2.0 <1.0.0, got: ${this.sdkVersion}`);
|
|
180
185
|
}
|
|
181
186
|
}
|
|
182
187
|
async updateHasSynced() {
|
|
@@ -210,6 +215,8 @@ export class AbstractPowerSyncDatabase extends BaseObserver {
|
|
|
210
215
|
}
|
|
211
216
|
this._schema = schema;
|
|
212
217
|
await this.database.execute('SELECT powersync_replace_schema(?)', [JSON.stringify(this.schema.toJSON())]);
|
|
218
|
+
await this.database.refreshSchema();
|
|
219
|
+
this.iterateListeners(async (cb) => cb.schemaChanged?.(schema));
|
|
213
220
|
}
|
|
214
221
|
/**
|
|
215
222
|
* Wait for initialization to complete.
|
|
@@ -218,6 +225,13 @@ export class AbstractPowerSyncDatabase extends BaseObserver {
|
|
|
218
225
|
async init() {
|
|
219
226
|
return this.waitForReady();
|
|
220
227
|
}
|
|
228
|
+
// Use the options passed in during connect, or fallback to the options set during database creation or fallback to the default options
|
|
229
|
+
resolvedConnectionOptions(options) {
|
|
230
|
+
return {
|
|
231
|
+
retryDelayMs: options?.retryDelayMs ?? this.options.retryDelayMs ?? this.options.retryDelay ?? DEFAULT_RETRY_DELAY_MS,
|
|
232
|
+
crudUploadThrottleMs: options?.crudUploadThrottleMs ?? this.options.crudUploadThrottleMs ?? DEFAULT_CRUD_UPLOAD_THROTTLE_MS
|
|
233
|
+
};
|
|
234
|
+
}
|
|
221
235
|
/**
|
|
222
236
|
* Connects to stream of events from the PowerSync instance.
|
|
223
237
|
*/
|
|
@@ -228,7 +242,11 @@ export class AbstractPowerSyncDatabase extends BaseObserver {
|
|
|
228
242
|
if (this.closed) {
|
|
229
243
|
throw new Error('Cannot connect using a closed client');
|
|
230
244
|
}
|
|
231
|
-
|
|
245
|
+
const { retryDelayMs, crudUploadThrottleMs } = this.resolvedConnectionOptions(options);
|
|
246
|
+
this.syncStreamImplementation = this.generateSyncStreamImplementation(connector, {
|
|
247
|
+
retryDelayMs,
|
|
248
|
+
crudUploadThrottleMs,
|
|
249
|
+
});
|
|
232
250
|
this.syncStatusListenerDisposer = this.syncStreamImplementation.registerListener({
|
|
233
251
|
statusChanged: (status) => {
|
|
234
252
|
this.currentStatus = new SyncStatus({
|
|
@@ -326,7 +344,7 @@ export class AbstractPowerSyncDatabase extends BaseObserver {
|
|
|
326
344
|
* data by transaction. One batch may contain data from multiple transactions,
|
|
327
345
|
* and a single transaction may be split over multiple batches.
|
|
328
346
|
*/
|
|
329
|
-
async getCrudBatch(limit) {
|
|
347
|
+
async getCrudBatch(limit = DEFAULT_CRUD_BATCH_LIMIT) {
|
|
330
348
|
const result = await this.getAll(`SELECT id, tx_id, data FROM ${PSInternalTable.CRUD} ORDER BY id ASC LIMIT ?`, [limit + 1]);
|
|
331
349
|
const all = result.map((row) => CrudEntry.fromRow(row)) ?? [];
|
|
332
350
|
let haveMore = false;
|
|
@@ -386,13 +404,13 @@ export class AbstractPowerSyncDatabase extends BaseObserver {
|
|
|
386
404
|
if (writeCheckpoint) {
|
|
387
405
|
const check = await tx.execute(`SELECT 1 FROM ${PSInternalTable.CRUD} LIMIT 1`);
|
|
388
406
|
if (!check.rows?.length) {
|
|
389
|
-
await tx.execute(`UPDATE ${PSInternalTable.BUCKETS} SET target_op = ? WHERE name='$local'`, [
|
|
407
|
+
await tx.execute(`UPDATE ${PSInternalTable.BUCKETS} SET target_op = CAST(? as INTEGER) WHERE name='$local'`, [
|
|
390
408
|
writeCheckpoint
|
|
391
409
|
]);
|
|
392
410
|
}
|
|
393
411
|
}
|
|
394
412
|
else {
|
|
395
|
-
await tx.execute(`UPDATE ${PSInternalTable.BUCKETS} SET target_op = ? WHERE name='$local'`, [
|
|
413
|
+
await tx.execute(`UPDATE ${PSInternalTable.BUCKETS} SET target_op = CAST(? as INTEGER) WHERE name='$local'`, [
|
|
396
414
|
this.bucketStorageAdapter.getMaxOpId()
|
|
397
415
|
]);
|
|
398
416
|
}
|
|
@@ -502,7 +520,7 @@ export class AbstractPowerSyncDatabase extends BaseObserver {
|
|
|
502
520
|
if (!onResult) {
|
|
503
521
|
throw new Error('onResult is required');
|
|
504
522
|
}
|
|
505
|
-
|
|
523
|
+
const watchQuery = async (abortSignal) => {
|
|
506
524
|
try {
|
|
507
525
|
const resolvedTables = await this.resolveTables(sql, parameters, options);
|
|
508
526
|
// Fetch initial data
|
|
@@ -521,13 +539,16 @@ export class AbstractPowerSyncDatabase extends BaseObserver {
|
|
|
521
539
|
onError
|
|
522
540
|
}, {
|
|
523
541
|
...(options ?? {}),
|
|
524
|
-
tables: resolvedTables
|
|
542
|
+
tables: resolvedTables,
|
|
543
|
+
// Override the abort signal since we intercept it
|
|
544
|
+
signal: abortSignal
|
|
525
545
|
});
|
|
526
546
|
}
|
|
527
547
|
catch (error) {
|
|
528
548
|
onError?.(error);
|
|
529
549
|
}
|
|
530
|
-
}
|
|
550
|
+
};
|
|
551
|
+
runOnSchemaChange(watchQuery, this, options);
|
|
531
552
|
}
|
|
532
553
|
/**
|
|
533
554
|
* Execute a read query every time the source tables are modified.
|
|
@@ -536,17 +557,18 @@ export class AbstractPowerSyncDatabase extends BaseObserver {
|
|
|
536
557
|
*/
|
|
537
558
|
watchWithAsyncGenerator(sql, parameters, options) {
|
|
538
559
|
return new EventIterator((eventOptions) => {
|
|
539
|
-
|
|
540
|
-
|
|
541
|
-
|
|
542
|
-
|
|
543
|
-
|
|
544
|
-
|
|
545
|
-
tables: resolvedTables
|
|
546
|
-
})) {
|
|
547
|
-
eventOptions.push(await this.executeReadOnly(sql, parameters));
|
|
560
|
+
const handler = {
|
|
561
|
+
onResult: (result) => {
|
|
562
|
+
eventOptions.push(result);
|
|
563
|
+
},
|
|
564
|
+
onError: (error) => {
|
|
565
|
+
eventOptions.fail(error);
|
|
548
566
|
}
|
|
549
|
-
}
|
|
567
|
+
};
|
|
568
|
+
this.watchWithCallback(sql, parameters, handler, options);
|
|
569
|
+
options?.signal?.addEventListener('abort', () => {
|
|
570
|
+
eventOptions.stop();
|
|
571
|
+
});
|
|
550
572
|
});
|
|
551
573
|
}
|
|
552
574
|
async resolveTables(sql, parameters, options) {
|
|
@@ -588,7 +610,7 @@ export class AbstractPowerSyncDatabase extends BaseObserver {
|
|
|
588
610
|
throw new Error('onChange is required');
|
|
589
611
|
}
|
|
590
612
|
const resolvedOptions = options ?? {};
|
|
591
|
-
const watchedTables = new Set(resolvedOptions
|
|
613
|
+
const watchedTables = new Set((resolvedOptions?.tables ?? []).flatMap((table) => [table, `ps_data__${table}`, `ps_data_local__${table}`]));
|
|
592
614
|
const changedTables = new Set();
|
|
593
615
|
const throttleMs = resolvedOptions.throttleMs ?? DEFAULT_WATCH_THROTTLE_MS;
|
|
594
616
|
const executor = new ControlledExecutor(async (e) => {
|
|
@@ -602,8 +624,7 @@ export class AbstractPowerSyncDatabase extends BaseObserver {
|
|
|
602
624
|
const dispose = this.database.registerListener({
|
|
603
625
|
tablesUpdated: async (update) => {
|
|
604
626
|
try {
|
|
605
|
-
|
|
606
|
-
this.processTableUpdates(update, rawTableNames, changedTables);
|
|
627
|
+
this.processTableUpdates(update, changedTables);
|
|
607
628
|
flushTableUpdates();
|
|
608
629
|
}
|
|
609
630
|
catch (error) {
|
|
@@ -652,19 +673,11 @@ export class AbstractPowerSyncDatabase extends BaseObserver {
|
|
|
652
673
|
}
|
|
653
674
|
changedTables.clear();
|
|
654
675
|
}
|
|
655
|
-
processTableUpdates(updateNotification,
|
|
676
|
+
processTableUpdates(updateNotification, changedTables) {
|
|
656
677
|
const tables = isBatchedUpdateNotification(updateNotification)
|
|
657
678
|
? updateNotification.tables
|
|
658
679
|
: [updateNotification.table];
|
|
659
|
-
const
|
|
660
|
-
if (!filteredTables.length) {
|
|
661
|
-
return;
|
|
662
|
-
}
|
|
663
|
-
// Remove any PowerSync table prefixes if necessary
|
|
664
|
-
const mappedTableNames = rawTableNames
|
|
665
|
-
? filteredTables
|
|
666
|
-
: filteredTables.map((t) => t.replace(POWERSYNC_TABLE_MATCH, ''));
|
|
667
|
-
for (const table of mappedTableNames) {
|
|
680
|
+
for (const table of tables) {
|
|
668
681
|
changedTables.add(table);
|
|
669
682
|
}
|
|
670
683
|
}
|
|
@@ -0,0 +1,7 @@
|
|
|
1
|
+
import { CompilableQuery } from './../types/types.js';
|
|
2
|
+
import { AbstractPowerSyncDatabase, SQLWatchOptions } from './AbstractPowerSyncDatabase.js';
|
|
3
|
+
export interface CompilableQueryWatchHandler<T> {
|
|
4
|
+
onResult: (results: T[]) => void;
|
|
5
|
+
onError?: (error: Error) => void;
|
|
6
|
+
}
|
|
7
|
+
export declare function compilableQueryWatch<T>(db: AbstractPowerSyncDatabase, query: CompilableQuery<T>, handler: CompilableQueryWatchHandler<T>, options?: SQLWatchOptions): void;
|
|
@@ -0,0 +1,37 @@
|
|
|
1
|
+
import { runOnSchemaChange } from './runOnSchemaChange.js';
|
|
2
|
+
export function compilableQueryWatch(db, query, handler, options) {
|
|
3
|
+
const { onResult, onError = (e) => { } } = handler ?? {};
|
|
4
|
+
if (!onResult) {
|
|
5
|
+
throw new Error('onResult is required');
|
|
6
|
+
}
|
|
7
|
+
const watchQuery = async (abortSignal) => {
|
|
8
|
+
try {
|
|
9
|
+
const toSql = query.compile();
|
|
10
|
+
const resolvedTables = await db.resolveTables(toSql.sql, toSql.parameters, options);
|
|
11
|
+
// Fetch initial data
|
|
12
|
+
const result = await query.execute();
|
|
13
|
+
onResult(result);
|
|
14
|
+
db.onChangeWithCallback({
|
|
15
|
+
onChange: async () => {
|
|
16
|
+
try {
|
|
17
|
+
const result = await query.execute();
|
|
18
|
+
onResult(result);
|
|
19
|
+
}
|
|
20
|
+
catch (error) {
|
|
21
|
+
onError(error);
|
|
22
|
+
}
|
|
23
|
+
},
|
|
24
|
+
onError
|
|
25
|
+
}, {
|
|
26
|
+
...(options ?? {}),
|
|
27
|
+
tables: resolvedTables,
|
|
28
|
+
// Override the abort signal since we intercept it
|
|
29
|
+
signal: abortSignal
|
|
30
|
+
});
|
|
31
|
+
}
|
|
32
|
+
catch (error) {
|
|
33
|
+
onError(error);
|
|
34
|
+
}
|
|
35
|
+
};
|
|
36
|
+
runOnSchemaChange(watchQuery, db, options);
|
|
37
|
+
}
|
|
@@ -0,0 +1,23 @@
|
|
|
1
|
+
export function runOnSchemaChange(callback, db, options) {
|
|
2
|
+
const triggerWatchedQuery = () => {
|
|
3
|
+
const abortController = new AbortController();
|
|
4
|
+
let disposeSchemaListener = null;
|
|
5
|
+
const stopWatching = () => {
|
|
6
|
+
abortController.abort('Abort triggered');
|
|
7
|
+
disposeSchemaListener?.();
|
|
8
|
+
disposeSchemaListener = null;
|
|
9
|
+
// Stop listening to upstream abort for this watch
|
|
10
|
+
options?.signal?.removeEventListener('abort', stopWatching);
|
|
11
|
+
};
|
|
12
|
+
options?.signal?.addEventListener('abort', stopWatching);
|
|
13
|
+
disposeSchemaListener = db.registerListener({
|
|
14
|
+
schemaChanged: async () => {
|
|
15
|
+
stopWatching();
|
|
16
|
+
// Re trigger the watched query (recursively), setTimeout ensures that we don't modify the list of listeners while iterating through them
|
|
17
|
+
setTimeout(() => triggerWatchedQuery(), 0);
|
|
18
|
+
}
|
|
19
|
+
});
|
|
20
|
+
callback(abortController.signal);
|
|
21
|
+
};
|
|
22
|
+
triggerWatchedQuery();
|
|
23
|
+
}
|
|
@@ -63,7 +63,7 @@ export class SqliteBucketStorage extends BaseObserver {
|
|
|
63
63
|
*/
|
|
64
64
|
startSession() { }
|
|
65
65
|
async getBucketStates() {
|
|
66
|
-
const result = await this.db.getAll(
|
|
66
|
+
const result = await this.db.getAll("SELECT name as bucket, cast(last_op as TEXT) as op_id FROM ps_buckets WHERE pending_delete = 0 AND name != '$local'");
|
|
67
67
|
return result;
|
|
68
68
|
}
|
|
69
69
|
async saveSyncData(batch) {
|
|
@@ -204,9 +204,7 @@ export class SqliteBucketStorage extends BaseObserver {
|
|
|
204
204
|
this.compactCounter = 0;
|
|
205
205
|
}
|
|
206
206
|
async updateLocalTarget(cb) {
|
|
207
|
-
const rs1 = await this.db.getAll("SELECT target_op FROM ps_buckets WHERE name = '$local' AND target_op = ?", [
|
|
208
|
-
MAX_OP_ID
|
|
209
|
-
]);
|
|
207
|
+
const rs1 = await this.db.getAll("SELECT target_op FROM ps_buckets WHERE name = '$local' AND target_op = CAST(? as INTEGER)", [MAX_OP_ID]);
|
|
210
208
|
if (!rs1.length) {
|
|
211
209
|
// Nothing to update
|
|
212
210
|
return false;
|
|
@@ -6,14 +6,15 @@ import { WebsocketClientTransport } from 'rsocket-websocket-client';
|
|
|
6
6
|
import { AbortOperation } from '../../../utils/AbortOperation.js';
|
|
7
7
|
import { DataStream } from '../../../utils/DataStream.js';
|
|
8
8
|
import { version as POWERSYNC_JS_VERSION } from '../../../../package.json';
|
|
9
|
+
const POWERSYNC_TRAILING_SLASH_MATCH = /\/+$/;
|
|
9
10
|
// Refresh at least 30 sec before it expires
|
|
10
11
|
const REFRESH_CREDENTIALS_SAFETY_PERIOD_MS = 30_000;
|
|
11
|
-
const SYNC_QUEUE_REQUEST_N =
|
|
12
|
+
const SYNC_QUEUE_REQUEST_N = 1;
|
|
12
13
|
const SYNC_QUEUE_REQUEST_LOW_WATER = 5;
|
|
13
14
|
// Keep alive message is sent every period
|
|
14
|
-
const KEEP_ALIVE_MS =
|
|
15
|
+
const KEEP_ALIVE_MS = 60_000;
|
|
15
16
|
// The ACK must be received in this period
|
|
16
|
-
const KEEP_ALIVE_LIFETIME_MS =
|
|
17
|
+
const KEEP_ALIVE_LIFETIME_MS = 90_000;
|
|
17
18
|
export const DEFAULT_REMOTE_LOGGER = Logger.get('PowerSyncRemote');
|
|
18
19
|
/**
|
|
19
20
|
* Class wrapper for providing a fetch implementation.
|
|
@@ -61,6 +62,9 @@ export class AbstractRemote {
|
|
|
61
62
|
return this.credentials;
|
|
62
63
|
}
|
|
63
64
|
this.credentials = await this.connector.fetchCredentials();
|
|
65
|
+
if (this.credentials?.endpoint.match(POWERSYNC_TRAILING_SLASH_MATCH)) {
|
|
66
|
+
throw new Error(`A trailing forward slash "/" was found in the fetchCredentials endpoint: "${this.credentials.endpoint}". Remove the trailing forward slash "/" to fix this error.`);
|
|
67
|
+
}
|
|
64
68
|
return this.credentials;
|
|
65
69
|
}
|
|
66
70
|
getUserAgent() {
|
|
@@ -20,10 +20,9 @@ export interface LockOptions<T> {
|
|
|
20
20
|
type: LockType;
|
|
21
21
|
signal?: AbortSignal;
|
|
22
22
|
}
|
|
23
|
-
export interface AbstractStreamingSyncImplementationOptions {
|
|
23
|
+
export interface AbstractStreamingSyncImplementationOptions extends AdditionalConnectionOptions {
|
|
24
24
|
adapter: BucketStorageAdapter;
|
|
25
25
|
uploadCrud: () => Promise<void>;
|
|
26
|
-
crudUploadThrottleMs?: number;
|
|
27
26
|
/**
|
|
28
27
|
* An identifier for which PowerSync DB this sync implementation is
|
|
29
28
|
* linked to. Most commonly DB name, but not restricted to DB name.
|
|
@@ -31,7 +30,6 @@ export interface AbstractStreamingSyncImplementationOptions {
|
|
|
31
30
|
identifier?: string;
|
|
32
31
|
logger?: ILogger;
|
|
33
32
|
remote: AbstractRemote;
|
|
34
|
-
retryDelayMs?: number;
|
|
35
33
|
}
|
|
36
34
|
export interface StreamingSyncImplementationListener extends BaseListener {
|
|
37
35
|
/**
|
|
@@ -48,7 +46,10 @@ export interface StreamingSyncImplementationListener extends BaseListener {
|
|
|
48
46
|
* Configurable options to be used when connecting to the PowerSync
|
|
49
47
|
* backend instance.
|
|
50
48
|
*/
|
|
51
|
-
export interface PowerSyncConnectionOptions {
|
|
49
|
+
export interface PowerSyncConnectionOptions extends BaseConnectionOptions, AdditionalConnectionOptions {
|
|
50
|
+
}
|
|
51
|
+
/** @internal */
|
|
52
|
+
export interface BaseConnectionOptions {
|
|
52
53
|
/**
|
|
53
54
|
* The connection method to use when streaming updates from
|
|
54
55
|
* the PowerSync backend instance.
|
|
@@ -60,6 +61,22 @@ export interface PowerSyncConnectionOptions {
|
|
|
60
61
|
*/
|
|
61
62
|
params?: Record<string, StreamingSyncRequestParameterType>;
|
|
62
63
|
}
|
|
64
|
+
/** @internal */
|
|
65
|
+
export interface AdditionalConnectionOptions {
|
|
66
|
+
/**
|
|
67
|
+
* Delay for retrying sync streaming operations
|
|
68
|
+
* from the PowerSync backend after an error occurs.
|
|
69
|
+
*/
|
|
70
|
+
retryDelayMs?: number;
|
|
71
|
+
/**
|
|
72
|
+
* Backend Connector CRUD operations are throttled
|
|
73
|
+
* to occur at most every `crudUploadThrottleMs`
|
|
74
|
+
* milliseconds.
|
|
75
|
+
*/
|
|
76
|
+
crudUploadThrottleMs?: number;
|
|
77
|
+
}
|
|
78
|
+
/** @internal */
|
|
79
|
+
export type RequiredAdditionalConnectionOptions = Required<AdditionalConnectionOptions>;
|
|
63
80
|
export interface StreamingSyncImplementation extends BaseObserver<StreamingSyncImplementationListener>, Disposable {
|
|
64
81
|
/**
|
|
65
82
|
* Connects to the sync service
|
|
@@ -80,12 +97,14 @@ export interface StreamingSyncImplementation extends BaseObserver<StreamingSyncI
|
|
|
80
97
|
waitForStatus(status: SyncStatusOptions): Promise<void>;
|
|
81
98
|
}
|
|
82
99
|
export declare const DEFAULT_CRUD_UPLOAD_THROTTLE_MS = 1000;
|
|
100
|
+
export declare const DEFAULT_RETRY_DELAY_MS = 5000;
|
|
83
101
|
export declare const DEFAULT_STREAMING_SYNC_OPTIONS: {
|
|
84
102
|
retryDelayMs: number;
|
|
85
103
|
logger: Logger.ILogger;
|
|
86
104
|
crudUploadThrottleMs: number;
|
|
87
105
|
};
|
|
88
|
-
export
|
|
106
|
+
export type RequiredPowerSyncConnectionOptions = Required<BaseConnectionOptions>;
|
|
107
|
+
export declare const DEFAULT_STREAM_CONNECTION_OPTIONS: RequiredPowerSyncConnectionOptions;
|
|
89
108
|
export declare abstract class AbstractStreamingSyncImplementation extends BaseObserver<StreamingSyncImplementationListener> implements StreamingSyncImplementation {
|
|
90
109
|
protected _lastSyncedAt: Date | null;
|
|
91
110
|
protected options: AbstractStreamingSyncImplementationOptions;
|
|
@@ -2,9 +2,9 @@ import Logger from 'js-logger';
|
|
|
2
2
|
import { SyncStatus } from '../../../db/crud/SyncStatus.js';
|
|
3
3
|
import { AbortOperation } from '../../../utils/AbortOperation.js';
|
|
4
4
|
import { BaseObserver } from '../../../utils/BaseObserver.js';
|
|
5
|
+
import { throttleLeadingTrailing } from '../../../utils/throttle.js';
|
|
5
6
|
import { SyncDataBucket } from '../bucket/SyncDataBucket.js';
|
|
6
7
|
import { isStreamingKeepalive, isStreamingSyncCheckpoint, isStreamingSyncCheckpointComplete, isStreamingSyncCheckpointDiff, isStreamingSyncData } from './streaming-sync-types.js';
|
|
7
|
-
import { throttleLeadingTrailing } from '../../../utils/throttle.js';
|
|
8
8
|
export var LockType;
|
|
9
9
|
(function (LockType) {
|
|
10
10
|
LockType["CRUD"] = "crud";
|
|
@@ -16,8 +16,9 @@ export var SyncStreamConnectionMethod;
|
|
|
16
16
|
SyncStreamConnectionMethod["WEB_SOCKET"] = "web-socket";
|
|
17
17
|
})(SyncStreamConnectionMethod || (SyncStreamConnectionMethod = {}));
|
|
18
18
|
export const DEFAULT_CRUD_UPLOAD_THROTTLE_MS = 1000;
|
|
19
|
+
export const DEFAULT_RETRY_DELAY_MS = 5000;
|
|
19
20
|
export const DEFAULT_STREAMING_SYNC_OPTIONS = {
|
|
20
|
-
retryDelayMs:
|
|
21
|
+
retryDelayMs: DEFAULT_RETRY_DELAY_MS,
|
|
21
22
|
logger: Logger.get('PowerSyncStream'),
|
|
22
23
|
crudUploadThrottleMs: DEFAULT_CRUD_UPLOAD_THROTTLE_MS
|
|
23
24
|
};
|
|
@@ -38,6 +39,7 @@ export class AbstractStreamingSyncImplementation extends BaseObserver {
|
|
|
38
39
|
this.options = { ...DEFAULT_STREAMING_SYNC_OPTIONS, ...options };
|
|
39
40
|
this.syncStatus = new SyncStatus({
|
|
40
41
|
connected: false,
|
|
42
|
+
connecting: false,
|
|
41
43
|
lastSyncedAt: undefined,
|
|
42
44
|
dataFlow: {
|
|
43
45
|
uploading: false,
|
|
@@ -121,7 +123,7 @@ export class AbstractStreamingSyncImplementation extends BaseObserver {
|
|
|
121
123
|
*/
|
|
122
124
|
const nextCrudItem = await this.options.adapter.nextCrudItem();
|
|
123
125
|
if (nextCrudItem) {
|
|
124
|
-
if (nextCrudItem.
|
|
126
|
+
if (nextCrudItem.clientId == checkedCrudItem?.clientId) {
|
|
125
127
|
// This will force a higher log level than exceptions which are caught here.
|
|
126
128
|
this.logger.warn(`Potentially previously uploaded CRUD entries are still present in the upload queue.
|
|
127
129
|
Make sure to handle uploads and complete CRUD transactions or batches by calling and awaiting their [.complete()] method.
|
|
@@ -207,7 +209,7 @@ The next upload iteration will be delayed.`);
|
|
|
207
209
|
}
|
|
208
210
|
this.streamingSyncPromise = undefined;
|
|
209
211
|
this.abortController = null;
|
|
210
|
-
this.updateSyncStatus({ connected: false });
|
|
212
|
+
this.updateSyncStatus({ connected: false, connecting: false });
|
|
211
213
|
}
|
|
212
214
|
/**
|
|
213
215
|
* @deprecated use [connect instead]
|
|
@@ -238,6 +240,7 @@ The next upload iteration will be delayed.`);
|
|
|
238
240
|
this.crudUpdateListener = undefined;
|
|
239
241
|
this.updateSyncStatus({
|
|
240
242
|
connected: false,
|
|
243
|
+
connecting: false,
|
|
241
244
|
dataFlow: {
|
|
242
245
|
downloading: false
|
|
243
246
|
}
|
|
@@ -250,6 +253,7 @@ The next upload iteration will be delayed.`);
|
|
|
250
253
|
* - Close any sync stream ReadableStreams (which will also close any established network requests)
|
|
251
254
|
*/
|
|
252
255
|
while (true) {
|
|
256
|
+
this.updateSyncStatus({ connecting: true });
|
|
253
257
|
try {
|
|
254
258
|
if (signal?.aborted) {
|
|
255
259
|
break;
|
|
@@ -280,6 +284,7 @@ The next upload iteration will be delayed.`);
|
|
|
280
284
|
else {
|
|
281
285
|
this.logger.error(ex);
|
|
282
286
|
}
|
|
287
|
+
// On error, wait a little before retrying
|
|
283
288
|
await this.delayRetry();
|
|
284
289
|
}
|
|
285
290
|
finally {
|
|
@@ -288,13 +293,13 @@ The next upload iteration will be delayed.`);
|
|
|
288
293
|
nestedAbortController = new AbortController();
|
|
289
294
|
}
|
|
290
295
|
this.updateSyncStatus({
|
|
291
|
-
connected: false
|
|
296
|
+
connected: false,
|
|
297
|
+
connecting: true // May be unnecessary
|
|
292
298
|
});
|
|
293
|
-
// On error, wait a little before retrying
|
|
294
299
|
}
|
|
295
300
|
}
|
|
296
301
|
// Mark as disconnected if here
|
|
297
|
-
this.updateSyncStatus({ connected: false });
|
|
302
|
+
this.updateSyncStatus({ connected: false, connecting: false });
|
|
298
303
|
}
|
|
299
304
|
async streamingSyncIteration(signal, options) {
|
|
300
305
|
return await this.obtainLock({
|
|
@@ -489,6 +494,7 @@ The next upload iteration will be delayed.`);
|
|
|
489
494
|
updateSyncStatus(options) {
|
|
490
495
|
const updatedStatus = new SyncStatus({
|
|
491
496
|
connected: options.connected ?? this.syncStatus.connected,
|
|
497
|
+
connecting: !options.connected && (options.connecting ?? this.syncStatus.connecting),
|
|
492
498
|
lastSyncedAt: options.lastSyncedAt ?? this.syncStatus.lastSyncedAt,
|
|
493
499
|
dataFlow: {
|
|
494
500
|
...this.syncStatus.dataFlowStatus,
|
package/lib/db/DBAdapter.d.ts
CHANGED
|
@@ -90,6 +90,10 @@ export interface DBAdapter extends BaseObserverInterface<DBAdapterListener>, DBG
|
|
|
90
90
|
readTransaction: <T>(fn: (tx: Transaction) => Promise<T>, options?: DBLockOptions) => Promise<T>;
|
|
91
91
|
writeLock: <T>(fn: (tx: LockContext) => Promise<T>, options?: DBLockOptions) => Promise<T>;
|
|
92
92
|
writeTransaction: <T>(fn: (tx: Transaction) => Promise<T>, options?: DBLockOptions) => Promise<T>;
|
|
93
|
+
/**
|
|
94
|
+
* This method refreshes the schema information across all connections. This is for advanced use cases, and should generally not be needed.
|
|
95
|
+
*/
|
|
96
|
+
refreshSchema: () => Promise<void>;
|
|
93
97
|
}
|
|
94
98
|
export declare function isBatchedUpdateNotification(update: BatchedUpdateNotification | UpdateNotification): update is BatchedUpdateNotification;
|
|
95
99
|
export declare function extractTableUpdates(update: BatchedUpdateNotification | UpdateNotification): string[];
|
|
@@ -4,6 +4,7 @@ export type SyncDataFlowStatus = Partial<{
|
|
|
4
4
|
}>;
|
|
5
5
|
export type SyncStatusOptions = {
|
|
6
6
|
connected?: boolean;
|
|
7
|
+
connecting?: boolean;
|
|
7
8
|
dataFlow?: SyncDataFlowStatus;
|
|
8
9
|
lastSyncedAt?: Date;
|
|
9
10
|
hasSynced?: boolean;
|
|
@@ -15,6 +16,7 @@ export declare class SyncStatus {
|
|
|
15
16
|
* true if currently connected.
|
|
16
17
|
*/
|
|
17
18
|
get connected(): boolean;
|
|
19
|
+
get connecting(): boolean;
|
|
18
20
|
/**
|
|
19
21
|
* Time that a last sync has fully completed, if any.
|
|
20
22
|
* Currently this is reset to null after a restart.
|
|
@@ -9,6 +9,9 @@ export class SyncStatus {
|
|
|
9
9
|
get connected() {
|
|
10
10
|
return this.options.connected ?? false;
|
|
11
11
|
}
|
|
12
|
+
get connecting() {
|
|
13
|
+
return this.options.connecting ?? false;
|
|
14
|
+
}
|
|
12
15
|
/**
|
|
13
16
|
* Time that a last sync has fully completed, if any.
|
|
14
17
|
* Currently this is reset to null after a restart.
|
|
@@ -44,11 +47,12 @@ export class SyncStatus {
|
|
|
44
47
|
}
|
|
45
48
|
getMessage() {
|
|
46
49
|
const dataFlow = this.dataFlowStatus;
|
|
47
|
-
return `SyncStatus<connected: ${this.connected} lastSyncedAt: ${this.lastSyncedAt} hasSynced: ${this.hasSynced}. Downloading: ${dataFlow.downloading}. Uploading: ${dataFlow.uploading}`;
|
|
50
|
+
return `SyncStatus<connected: ${this.connected} connecting: ${this.connecting} lastSyncedAt: ${this.lastSyncedAt} hasSynced: ${this.hasSynced}. Downloading: ${dataFlow.downloading}. Uploading: ${dataFlow.uploading}`;
|
|
48
51
|
}
|
|
49
52
|
toJSON() {
|
|
50
53
|
return {
|
|
51
54
|
connected: this.connected,
|
|
55
|
+
connecting: this.connecting,
|
|
52
56
|
dataFlow: this.dataFlowStatus,
|
|
53
57
|
lastSyncedAt: this.lastSyncedAt,
|
|
54
58
|
hasSynced: this.hasSynced
|
|
@@ -12,7 +12,7 @@ export type BaseColumnType<T extends number | string | null> = {
|
|
|
12
12
|
};
|
|
13
13
|
export type ColumnsType = Record<string, BaseColumnType<any>>;
|
|
14
14
|
export type ExtractColumnValueType<T extends BaseColumnType<any>> = T extends BaseColumnType<infer R> ? R : unknown;
|
|
15
|
-
export declare const MAX_AMOUNT_OF_COLUMNS =
|
|
15
|
+
export declare const MAX_AMOUNT_OF_COLUMNS = 1999;
|
|
16
16
|
export declare const column: {
|
|
17
17
|
text: BaseColumnType<string | null>;
|
|
18
18
|
integer: BaseColumnType<number | null>;
|
package/lib/db/schema/Column.js
CHANGED
|
@@ -14,9 +14,9 @@ const integer = {
|
|
|
14
14
|
const real = {
|
|
15
15
|
type: ColumnType.REAL
|
|
16
16
|
};
|
|
17
|
-
//
|
|
18
|
-
//
|
|
19
|
-
export const MAX_AMOUNT_OF_COLUMNS =
|
|
17
|
+
// powersync-sqlite-core limits the number of column per table to 1999, due to internal SQLite limits.
|
|
18
|
+
// In earlier versions this was limited to 63.
|
|
19
|
+
export const MAX_AMOUNT_OF_COLUMNS = 1999;
|
|
20
20
|
export const column = {
|
|
21
21
|
text,
|
|
22
22
|
integer,
|