@powersync/common 0.0.0-dev-20250207081035 → 0.0.0-dev-20250319141441
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 -2
- package/lib/client/AbstractPowerSyncDatabase.js +54 -15
- package/lib/client/sync/bucket/BucketStorageAdapter.d.ts +6 -1
- package/lib/client/sync/bucket/SqliteBucketStorage.d.ts +2 -2
- package/lib/client/sync/bucket/SqliteBucketStorage.js +34 -10
- package/lib/client/sync/stream/AbstractRemote.d.ts +16 -1
- package/lib/client/sync/stream/AbstractRemote.js +24 -10
- package/lib/client/sync/stream/AbstractStreamingSyncImplementation.d.ts +8 -1
- package/lib/client/sync/stream/AbstractStreamingSyncImplementation.js +100 -35
- package/lib/client/sync/stream/streaming-sync-types.d.ts +8 -1
- package/lib/client/sync/stream/streaming-sync-types.js +3 -0
- package/lib/db/DBAdapter.d.ts +4 -1
- package/lib/db/crud/SyncStatus.d.ts +27 -0
- package/lib/db/crud/SyncStatus.js +41 -1
- package/lib/utils/DataStream.js +5 -6
- package/package.json +1 -3
|
@@ -10,7 +10,7 @@ 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 { type AdditionalConnectionOptions, type PowerSyncConnectionOptions,
|
|
13
|
+
import { StreamingSyncImplementation, StreamingSyncImplementationListener, type AdditionalConnectionOptions, type PowerSyncConnectionOptions, 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;
|
|
@@ -153,9 +153,18 @@ export declare abstract class AbstractPowerSyncDatabase extends BaseObserver<Pow
|
|
|
153
153
|
*/
|
|
154
154
|
waitForReady(): Promise<void>;
|
|
155
155
|
/**
|
|
156
|
+
* Wait for the first sync operation to complete.
|
|
157
|
+
*
|
|
158
|
+
* @argument request Either an abort signal (after which the promise will complete regardless of
|
|
159
|
+
* whether a full sync was completed) or an object providing an abort signal and a priority target.
|
|
160
|
+
* When a priority target is set, the promise may complete when all buckets with the given (or higher)
|
|
161
|
+
* priorities have been synchronized. This can be earlier than a complete sync.
|
|
156
162
|
* @returns A promise which will resolve once the first full sync has completed.
|
|
157
163
|
*/
|
|
158
|
-
waitForFirstSync(
|
|
164
|
+
waitForFirstSync(request?: AbortSignal | {
|
|
165
|
+
signal?: AbortSignal;
|
|
166
|
+
priority?: number;
|
|
167
|
+
}): Promise<void>;
|
|
159
168
|
/**
|
|
160
169
|
* Allows for extended implementations to execute custom initialization
|
|
161
170
|
* logic as part of the total init process
|
|
@@ -256,6 +265,7 @@ export declare abstract class AbstractPowerSyncDatabase extends BaseObserver<Pow
|
|
|
256
265
|
* and optionally return results.
|
|
257
266
|
*/
|
|
258
267
|
execute(sql: string, parameters?: any[]): Promise<QueryResult>;
|
|
268
|
+
executeRaw(sql: string, parameters?: any[]): Promise<any[][]>;
|
|
259
269
|
/**
|
|
260
270
|
* Execute a write query (INSERT/UPDATE/DELETE) multiple times with each parameter set
|
|
261
271
|
* and optionally return results.
|
|
@@ -9,12 +9,12 @@ import { ControlledExecutor } from '../utils/ControlledExecutor.js';
|
|
|
9
9
|
import { mutexRunExclusive } from '../utils/mutex.js';
|
|
10
10
|
import { throttleTrailing } from '../utils/throttle.js';
|
|
11
11
|
import { isDBAdapter, isSQLOpenFactory, isSQLOpenOptions } from './SQLOpenFactory.js';
|
|
12
|
+
import { runOnSchemaChange } from './runOnSchemaChange.js';
|
|
12
13
|
import { PSInternalTable } from './sync/bucket/BucketStorageAdapter.js';
|
|
13
14
|
import { CrudBatch } from './sync/bucket/CrudBatch.js';
|
|
14
15
|
import { CrudEntry } from './sync/bucket/CrudEntry.js';
|
|
15
16
|
import { CrudTransaction } from './sync/bucket/CrudTransaction.js';
|
|
16
17
|
import { DEFAULT_CRUD_UPLOAD_THROTTLE_MS, DEFAULT_RETRY_DELAY_MS } from './sync/stream/AbstractStreamingSyncImplementation.js';
|
|
17
|
-
import { runOnSchemaChange } from './runOnSchemaChange.js';
|
|
18
18
|
const POWERSYNC_TABLE_MATCH = /(^ps_data__|^ps_data_local__)/;
|
|
19
19
|
const DEFAULT_DISCONNECT_CLEAR_OPTIONS = {
|
|
20
20
|
clearLocal: true
|
|
@@ -42,6 +42,10 @@ export const DEFAULT_LOCK_TIMEOUT_MS = 120_000; // 2 mins
|
|
|
42
42
|
export const isPowerSyncDatabaseOptionsWithSettings = (test) => {
|
|
43
43
|
return typeof test == 'object' && isSQLOpenOptions(test.database);
|
|
44
44
|
};
|
|
45
|
+
/**
|
|
46
|
+
* The priority used by the core extension to indicate that a full sync was completed.
|
|
47
|
+
*/
|
|
48
|
+
const FULL_SYNC_PRIORITY = 2147483647;
|
|
45
49
|
export class AbstractPowerSyncDatabase extends BaseObserver {
|
|
46
50
|
options;
|
|
47
51
|
/**
|
|
@@ -127,16 +131,27 @@ export class AbstractPowerSyncDatabase extends BaseObserver {
|
|
|
127
131
|
await this._isReadyPromise;
|
|
128
132
|
}
|
|
129
133
|
/**
|
|
134
|
+
* Wait for the first sync operation to complete.
|
|
135
|
+
*
|
|
136
|
+
* @argument request Either an abort signal (after which the promise will complete regardless of
|
|
137
|
+
* whether a full sync was completed) or an object providing an abort signal and a priority target.
|
|
138
|
+
* When a priority target is set, the promise may complete when all buckets with the given (or higher)
|
|
139
|
+
* priorities have been synchronized. This can be earlier than a complete sync.
|
|
130
140
|
* @returns A promise which will resolve once the first full sync has completed.
|
|
131
141
|
*/
|
|
132
|
-
async waitForFirstSync(
|
|
133
|
-
|
|
142
|
+
async waitForFirstSync(request) {
|
|
143
|
+
const signal = request instanceof AbortSignal ? request : request?.signal;
|
|
144
|
+
const priority = request && 'priority' in request ? request.priority : undefined;
|
|
145
|
+
const statusMatches = priority === undefined
|
|
146
|
+
? (status) => status.hasSynced
|
|
147
|
+
: (status) => status.statusForPriority(priority).hasSynced;
|
|
148
|
+
if (statusMatches(this.currentStatus)) {
|
|
134
149
|
return;
|
|
135
150
|
}
|
|
136
151
|
return new Promise((resolve) => {
|
|
137
152
|
const dispose = this.registerListener({
|
|
138
153
|
statusChanged: (status) => {
|
|
139
|
-
if (status
|
|
154
|
+
if (statusMatches(status)) {
|
|
140
155
|
dispose();
|
|
141
156
|
resolve();
|
|
142
157
|
}
|
|
@@ -177,19 +192,36 @@ export class AbstractPowerSyncDatabase extends BaseObserver {
|
|
|
177
192
|
.map((n) => parseInt(n));
|
|
178
193
|
}
|
|
179
194
|
catch (e) {
|
|
180
|
-
throw new Error(`Unsupported powersync extension version. Need >=0.
|
|
195
|
+
throw new Error(`Unsupported powersync extension version. Need >=0.3.11 <1.0.0, got: ${this.sdkVersion}. Details: ${e.message}`);
|
|
181
196
|
}
|
|
182
|
-
// Validate >=0.
|
|
183
|
-
if (versionInts[0] != 0 || versionInts[1] <
|
|
184
|
-
throw new Error(`Unsupported powersync extension version. Need >=0.
|
|
197
|
+
// Validate >=0.3.11 <1.0.0
|
|
198
|
+
if (versionInts[0] != 0 || versionInts[1] < 3 || (versionInts[1] == 3 && versionInts[2] < 11)) {
|
|
199
|
+
throw new Error(`Unsupported powersync extension version. Need >=0.3.11 <1.0.0, got: ${this.sdkVersion}`);
|
|
185
200
|
}
|
|
186
201
|
}
|
|
187
202
|
async updateHasSynced() {
|
|
188
|
-
const result = await this.database.
|
|
189
|
-
|
|
190
|
-
const
|
|
191
|
-
|
|
192
|
-
|
|
203
|
+
const result = await this.database.getAll('SELECT priority, last_synced_at FROM ps_sync_state ORDER BY priority DESC');
|
|
204
|
+
let lastCompleteSync;
|
|
205
|
+
const priorityStatusEntries = [];
|
|
206
|
+
for (const { priority, last_synced_at } of result) {
|
|
207
|
+
const parsedDate = new Date(last_synced_at + 'Z');
|
|
208
|
+
if (priority == FULL_SYNC_PRIORITY) {
|
|
209
|
+
// This lowest-possible priority represents a complete sync.
|
|
210
|
+
lastCompleteSync = parsedDate;
|
|
211
|
+
}
|
|
212
|
+
else {
|
|
213
|
+
priorityStatusEntries.push({ priority, hasSynced: true, lastSyncedAt: parsedDate });
|
|
214
|
+
}
|
|
215
|
+
}
|
|
216
|
+
const hasSynced = lastCompleteSync != null;
|
|
217
|
+
const updatedStatus = new SyncStatus({
|
|
218
|
+
...this.currentStatus.toJSON(),
|
|
219
|
+
hasSynced,
|
|
220
|
+
priorityStatusEntries,
|
|
221
|
+
lastSyncedAt: lastCompleteSync
|
|
222
|
+
});
|
|
223
|
+
if (!updatedStatus.isEqual(this.currentStatus)) {
|
|
224
|
+
this.currentStatus = updatedStatus;
|
|
193
225
|
this.iterateListeners((l) => l.statusChanged?.(this.currentStatus));
|
|
194
226
|
}
|
|
195
227
|
}
|
|
@@ -245,7 +277,7 @@ export class AbstractPowerSyncDatabase extends BaseObserver {
|
|
|
245
277
|
const { retryDelayMs, crudUploadThrottleMs } = this.resolvedConnectionOptions(options);
|
|
246
278
|
this.syncStreamImplementation = this.generateSyncStreamImplementation(connector, {
|
|
247
279
|
retryDelayMs,
|
|
248
|
-
crudUploadThrottleMs
|
|
280
|
+
crudUploadThrottleMs
|
|
249
281
|
});
|
|
250
282
|
this.syncStatusListenerDisposer = this.syncStreamImplementation.registerListener({
|
|
251
283
|
statusChanged: (status) => {
|
|
@@ -302,12 +334,15 @@ export class AbstractPowerSyncDatabase extends BaseObserver {
|
|
|
302
334
|
*/
|
|
303
335
|
async close(options = DEFAULT_POWERSYNC_CLOSE_OPTIONS) {
|
|
304
336
|
await this.waitForReady();
|
|
337
|
+
if (this.closed) {
|
|
338
|
+
return;
|
|
339
|
+
}
|
|
305
340
|
const { disconnect } = options;
|
|
306
341
|
if (disconnect) {
|
|
307
342
|
await this.disconnect();
|
|
308
343
|
}
|
|
309
344
|
await this.syncStreamImplementation?.dispose();
|
|
310
|
-
this.database.close();
|
|
345
|
+
await this.database.close();
|
|
311
346
|
this.closed = true;
|
|
312
347
|
}
|
|
313
348
|
/**
|
|
@@ -424,6 +459,10 @@ export class AbstractPowerSyncDatabase extends BaseObserver {
|
|
|
424
459
|
await this.waitForReady();
|
|
425
460
|
return this.database.execute(sql, parameters);
|
|
426
461
|
}
|
|
462
|
+
async executeRaw(sql, parameters) {
|
|
463
|
+
await this.waitForReady();
|
|
464
|
+
return this.database.executeRaw(sql, parameters);
|
|
465
|
+
}
|
|
427
466
|
/**
|
|
428
467
|
* Execute a write query (INSERT/UPDATE/DELETE) multiple times with each parameter set
|
|
429
468
|
* and optionally return results.
|
|
@@ -2,6 +2,10 @@ import { BaseListener, BaseObserver, Disposable } from '../../../utils/BaseObser
|
|
|
2
2
|
import { CrudBatch } from './CrudBatch.js';
|
|
3
3
|
import { CrudEntry, OpId } from './CrudEntry.js';
|
|
4
4
|
import { SyncDataBatch } from './SyncDataBatch.js';
|
|
5
|
+
export interface BucketDescription {
|
|
6
|
+
name: string;
|
|
7
|
+
priority: number;
|
|
8
|
+
}
|
|
5
9
|
export interface Checkpoint {
|
|
6
10
|
last_op_id: OpId;
|
|
7
11
|
buckets: BucketChecksum[];
|
|
@@ -25,6 +29,7 @@ export interface SyncLocalDatabaseResult {
|
|
|
25
29
|
}
|
|
26
30
|
export interface BucketChecksum {
|
|
27
31
|
bucket: string;
|
|
32
|
+
priority?: number;
|
|
28
33
|
/**
|
|
29
34
|
* 32-bit unsigned hash.
|
|
30
35
|
*/
|
|
@@ -51,7 +56,7 @@ export interface BucketStorageAdapter extends BaseObserver<BucketStorageListener
|
|
|
51
56
|
setTargetCheckpoint(checkpoint: Checkpoint): Promise<void>;
|
|
52
57
|
startSession(): void;
|
|
53
58
|
getBucketStates(): Promise<BucketState[]>;
|
|
54
|
-
syncLocalDatabase(checkpoint: Checkpoint): Promise<{
|
|
59
|
+
syncLocalDatabase(checkpoint: Checkpoint, priority?: number): Promise<{
|
|
55
60
|
checkpointValid: boolean;
|
|
56
61
|
ready: boolean;
|
|
57
62
|
failures?: any[];
|
|
@@ -37,14 +37,14 @@ export declare class SqliteBucketStorage extends BaseObserver<BucketStorageListe
|
|
|
37
37
|
*/
|
|
38
38
|
private deleteBucket;
|
|
39
39
|
hasCompletedSync(): Promise<boolean>;
|
|
40
|
-
syncLocalDatabase(checkpoint: Checkpoint): Promise<SyncLocalDatabaseResult>;
|
|
40
|
+
syncLocalDatabase(checkpoint: Checkpoint, priority?: number): Promise<SyncLocalDatabaseResult>;
|
|
41
41
|
/**
|
|
42
42
|
* Atomically update the local state to the current checkpoint.
|
|
43
43
|
*
|
|
44
44
|
* This includes creating new tables, dropping old tables, and copying data over from the oplog.
|
|
45
45
|
*/
|
|
46
46
|
private updateObjectsFromBuckets;
|
|
47
|
-
validateChecksums(checkpoint: Checkpoint): Promise<SyncLocalDatabaseResult>;
|
|
47
|
+
validateChecksums(checkpoint: Checkpoint, priority: number | undefined): Promise<SyncLocalDatabaseResult>;
|
|
48
48
|
/**
|
|
49
49
|
* Force a compact, for tests.
|
|
50
50
|
*/
|
|
@@ -106,8 +106,8 @@ export class SqliteBucketStorage extends BaseObserver {
|
|
|
106
106
|
}
|
|
107
107
|
return completed;
|
|
108
108
|
}
|
|
109
|
-
async syncLocalDatabase(checkpoint) {
|
|
110
|
-
const r = await this.validateChecksums(checkpoint);
|
|
109
|
+
async syncLocalDatabase(checkpoint, priority) {
|
|
110
|
+
const r = await this.validateChecksums(checkpoint, priority);
|
|
111
111
|
if (!r.checkpointValid) {
|
|
112
112
|
this.logger.error('Checksums failed for', r.checkpointFailures);
|
|
113
113
|
for (const b of r.checkpointFailures ?? []) {
|
|
@@ -115,17 +115,21 @@ export class SqliteBucketStorage extends BaseObserver {
|
|
|
115
115
|
}
|
|
116
116
|
return { ready: false, checkpointValid: false, checkpointFailures: r.checkpointFailures };
|
|
117
117
|
}
|
|
118
|
-
const
|
|
118
|
+
const buckets = checkpoint.buckets;
|
|
119
|
+
if (priority !== undefined) {
|
|
120
|
+
buckets.filter((b) => hasMatchingPriority(priority, b));
|
|
121
|
+
}
|
|
122
|
+
const bucketNames = buckets.map((b) => b.bucket);
|
|
119
123
|
await this.writeTransaction(async (tx) => {
|
|
120
124
|
await tx.execute(`UPDATE ps_buckets SET last_op = ? WHERE name IN (SELECT json_each.value FROM json_each(?))`, [
|
|
121
125
|
checkpoint.last_op_id,
|
|
122
126
|
JSON.stringify(bucketNames)
|
|
123
127
|
]);
|
|
124
|
-
if (checkpoint.write_checkpoint) {
|
|
128
|
+
if (priority == null && checkpoint.write_checkpoint) {
|
|
125
129
|
await tx.execute("UPDATE ps_buckets SET last_op = ? WHERE name = '$local'", [checkpoint.write_checkpoint]);
|
|
126
130
|
}
|
|
127
131
|
});
|
|
128
|
-
const valid = await this.updateObjectsFromBuckets(checkpoint);
|
|
132
|
+
const valid = await this.updateObjectsFromBuckets(checkpoint, priority);
|
|
129
133
|
if (!valid) {
|
|
130
134
|
this.logger.debug('Not at a consistent checkpoint - cannot update local db');
|
|
131
135
|
return { ready: false, checkpointValid: true };
|
|
@@ -141,19 +145,36 @@ export class SqliteBucketStorage extends BaseObserver {
|
|
|
141
145
|
*
|
|
142
146
|
* This includes creating new tables, dropping old tables, and copying data over from the oplog.
|
|
143
147
|
*/
|
|
144
|
-
async updateObjectsFromBuckets(checkpoint) {
|
|
148
|
+
async updateObjectsFromBuckets(checkpoint, priority) {
|
|
149
|
+
let arg = '';
|
|
150
|
+
if (priority !== undefined) {
|
|
151
|
+
const affectedBuckets = [];
|
|
152
|
+
for (const desc of checkpoint.buckets) {
|
|
153
|
+
if (hasMatchingPriority(priority, desc)) {
|
|
154
|
+
affectedBuckets.push(desc.bucket);
|
|
155
|
+
}
|
|
156
|
+
}
|
|
157
|
+
arg = JSON.stringify({ priority, buckets: affectedBuckets });
|
|
158
|
+
}
|
|
145
159
|
return this.writeTransaction(async (tx) => {
|
|
146
160
|
const { insertId: result } = await tx.execute('INSERT INTO powersync_operations(op, data) VALUES(?, ?)', [
|
|
147
161
|
'sync_local',
|
|
148
|
-
|
|
162
|
+
arg
|
|
149
163
|
]);
|
|
150
164
|
return result == 1;
|
|
151
165
|
});
|
|
152
166
|
}
|
|
153
|
-
async validateChecksums(checkpoint) {
|
|
154
|
-
|
|
167
|
+
async validateChecksums(checkpoint, priority) {
|
|
168
|
+
if (priority !== undefined) {
|
|
169
|
+
// Only validate the buckets within the priority we care about
|
|
170
|
+
const newBuckets = checkpoint.buckets.filter((cs) => hasMatchingPriority(priority, cs));
|
|
171
|
+
checkpoint = { ...checkpoint, buckets: newBuckets };
|
|
172
|
+
}
|
|
173
|
+
const rs = await this.db.execute('SELECT powersync_validate_checkpoint(?) as result', [
|
|
174
|
+
JSON.stringify({ ...checkpoint })
|
|
175
|
+
]);
|
|
155
176
|
const resultItem = rs.rows?.item(0);
|
|
156
|
-
this.logger.debug('validateChecksums result item', resultItem);
|
|
177
|
+
this.logger.debug('validateChecksums priority, checkpoint, result item', priority, checkpoint, resultItem);
|
|
157
178
|
if (!resultItem) {
|
|
158
179
|
return {
|
|
159
180
|
checkpointValid: false,
|
|
@@ -304,3 +325,6 @@ export class SqliteBucketStorage extends BaseObserver {
|
|
|
304
325
|
// No-op for now
|
|
305
326
|
}
|
|
306
327
|
}
|
|
328
|
+
function hasMatchingPriority(priority, bucket) {
|
|
329
|
+
return bucket.priority != null && bucket.priority <= priority;
|
|
330
|
+
}
|
|
@@ -16,6 +16,21 @@ export type SyncStreamOptions = {
|
|
|
16
16
|
abortSignal?: AbortSignal;
|
|
17
17
|
fetchOptions?: Request;
|
|
18
18
|
};
|
|
19
|
+
export declare enum FetchStrategy {
|
|
20
|
+
/**
|
|
21
|
+
* Queues multiple sync events before processing, reducing round-trips.
|
|
22
|
+
* This comes at the cost of more processing overhead, which may cause ACK timeouts on older/weaker devices for big enough datasets.
|
|
23
|
+
*/
|
|
24
|
+
Buffered = "buffered",
|
|
25
|
+
/**
|
|
26
|
+
* Processes each sync event immediately before requesting the next.
|
|
27
|
+
* This reduces processing overhead and improves real-time responsiveness.
|
|
28
|
+
*/
|
|
29
|
+
Sequential = "sequential"
|
|
30
|
+
}
|
|
31
|
+
export type SocketSyncStreamOptions = SyncStreamOptions & {
|
|
32
|
+
fetchStrategy: FetchStrategy;
|
|
33
|
+
};
|
|
19
34
|
export type FetchImplementation = typeof fetch;
|
|
20
35
|
/**
|
|
21
36
|
* Class wrapper for providing a fetch implementation.
|
|
@@ -72,7 +87,7 @@ export declare abstract class AbstractRemote {
|
|
|
72
87
|
/**
|
|
73
88
|
* Connects to the sync/stream websocket endpoint
|
|
74
89
|
*/
|
|
75
|
-
socketStream(options:
|
|
90
|
+
socketStream(options: SocketSyncStreamOptions): Promise<DataStream<StreamingSyncLine>>;
|
|
76
91
|
/**
|
|
77
92
|
* Connects to the sync/stream http endpoint
|
|
78
93
|
*/
|
|
@@ -3,19 +3,32 @@ import ndjsonStream from 'can-ndjson-stream';
|
|
|
3
3
|
import Logger from 'js-logger';
|
|
4
4
|
import { RSocketConnector } from 'rsocket-core';
|
|
5
5
|
import { WebsocketClientTransport } from 'rsocket-websocket-client';
|
|
6
|
+
import PACKAGE from '../../../../package.json' with { type: 'json' };
|
|
6
7
|
import { AbortOperation } from '../../../utils/AbortOperation.js';
|
|
7
8
|
import { DataStream } from '../../../utils/DataStream.js';
|
|
8
|
-
import { version as POWERSYNC_JS_VERSION } from '../../../../package.json';
|
|
9
9
|
const POWERSYNC_TRAILING_SLASH_MATCH = /\/+$/;
|
|
10
|
+
const POWERSYNC_JS_VERSION = PACKAGE.version;
|
|
10
11
|
// Refresh at least 30 sec before it expires
|
|
11
12
|
const REFRESH_CREDENTIALS_SAFETY_PERIOD_MS = 30_000;
|
|
12
|
-
const SYNC_QUEUE_REQUEST_N = 1;
|
|
13
13
|
const SYNC_QUEUE_REQUEST_LOW_WATER = 5;
|
|
14
14
|
// Keep alive message is sent every period
|
|
15
|
-
const KEEP_ALIVE_MS =
|
|
15
|
+
const KEEP_ALIVE_MS = 20_000;
|
|
16
16
|
// The ACK must be received in this period
|
|
17
|
-
const KEEP_ALIVE_LIFETIME_MS =
|
|
17
|
+
const KEEP_ALIVE_LIFETIME_MS = 30_000;
|
|
18
18
|
export const DEFAULT_REMOTE_LOGGER = Logger.get('PowerSyncRemote');
|
|
19
|
+
export var FetchStrategy;
|
|
20
|
+
(function (FetchStrategy) {
|
|
21
|
+
/**
|
|
22
|
+
* Queues multiple sync events before processing, reducing round-trips.
|
|
23
|
+
* This comes at the cost of more processing overhead, which may cause ACK timeouts on older/weaker devices for big enough datasets.
|
|
24
|
+
*/
|
|
25
|
+
FetchStrategy["Buffered"] = "buffered";
|
|
26
|
+
/**
|
|
27
|
+
* Processes each sync event immediately before requesting the next.
|
|
28
|
+
* This reduces processing overhead and improves real-time responsiveness.
|
|
29
|
+
*/
|
|
30
|
+
FetchStrategy["Sequential"] = "sequential";
|
|
31
|
+
})(FetchStrategy || (FetchStrategy = {}));
|
|
19
32
|
/**
|
|
20
33
|
* Class wrapper for providing a fetch implementation.
|
|
21
34
|
* The class wrapper is used to distinguish the fetchImplementation
|
|
@@ -144,7 +157,8 @@ export class AbstractRemote {
|
|
|
144
157
|
* Connects to the sync/stream websocket endpoint
|
|
145
158
|
*/
|
|
146
159
|
async socketStream(options) {
|
|
147
|
-
const { path } = options;
|
|
160
|
+
const { path, fetchStrategy = FetchStrategy.Buffered } = options;
|
|
161
|
+
const syncQueueRequestSize = fetchStrategy == FetchStrategy.Buffered ? 10 : 1;
|
|
148
162
|
const request = await this.buildRequest(path);
|
|
149
163
|
const bson = await this.getBSON();
|
|
150
164
|
// Add the user agent in the setup payload - we can't set custom
|
|
@@ -197,7 +211,7 @@ export class AbstractRemote {
|
|
|
197
211
|
// Helps to prevent double close scenarios
|
|
198
212
|
rsocket.onClose(() => (socketIsClosed = true));
|
|
199
213
|
// We initially request this amount and expect these to arrive eventually
|
|
200
|
-
let pendingEventsCount =
|
|
214
|
+
let pendingEventsCount = syncQueueRequestSize;
|
|
201
215
|
const disposeClosedListener = stream.registerListener({
|
|
202
216
|
closed: () => {
|
|
203
217
|
closeSocket();
|
|
@@ -211,7 +225,7 @@ export class AbstractRemote {
|
|
|
211
225
|
metadata: Buffer.from(bson.serialize({
|
|
212
226
|
path
|
|
213
227
|
}))
|
|
214
|
-
},
|
|
228
|
+
}, syncQueueRequestSize, // The initial N amount
|
|
215
229
|
{
|
|
216
230
|
onError: (e) => {
|
|
217
231
|
// Don't log closed as an error
|
|
@@ -250,10 +264,10 @@ export class AbstractRemote {
|
|
|
250
264
|
const l = stream.registerListener({
|
|
251
265
|
lowWater: async () => {
|
|
252
266
|
// Request to fill up the queue
|
|
253
|
-
const required =
|
|
267
|
+
const required = syncQueueRequestSize - pendingEventsCount;
|
|
254
268
|
if (required > 0) {
|
|
255
|
-
socket.request(
|
|
256
|
-
pendingEventsCount =
|
|
269
|
+
socket.request(syncQueueRequestSize - pendingEventsCount);
|
|
270
|
+
pendingEventsCount = syncQueueRequestSize;
|
|
257
271
|
}
|
|
258
272
|
},
|
|
259
273
|
closed: () => {
|
|
@@ -2,7 +2,7 @@ import Logger, { ILogger } from 'js-logger';
|
|
|
2
2
|
import { SyncStatus, SyncStatusOptions } from '../../../db/crud/SyncStatus.js';
|
|
3
3
|
import { BaseListener, BaseObserver, Disposable } from '../../../utils/BaseObserver.js';
|
|
4
4
|
import { BucketStorageAdapter } from '../bucket/BucketStorageAdapter.js';
|
|
5
|
-
import { AbstractRemote } from './AbstractRemote.js';
|
|
5
|
+
import { AbstractRemote, FetchStrategy } from './AbstractRemote.js';
|
|
6
6
|
import { StreamingSyncRequestParameterType } from './streaming-sync-types.js';
|
|
7
7
|
export declare enum LockType {
|
|
8
8
|
CRUD = "crud",
|
|
@@ -56,6 +56,10 @@ export interface BaseConnectionOptions {
|
|
|
56
56
|
* Defaults to a HTTP streaming connection.
|
|
57
57
|
*/
|
|
58
58
|
connectionMethod?: SyncStreamConnectionMethod;
|
|
59
|
+
/**
|
|
60
|
+
* The fetch strategy to use when streaming updates from the PowerSync backend instance.
|
|
61
|
+
*/
|
|
62
|
+
fetchStrategy?: FetchStrategy;
|
|
59
63
|
/**
|
|
60
64
|
* These parameters are passed to the sync rules, and will be available under the`user_parameters` object.
|
|
61
65
|
*/
|
|
@@ -95,6 +99,7 @@ export interface StreamingSyncImplementation extends BaseObserver<StreamingSyncI
|
|
|
95
99
|
triggerCrudUpload: () => void;
|
|
96
100
|
waitForReady(): Promise<void>;
|
|
97
101
|
waitForStatus(status: SyncStatusOptions): Promise<void>;
|
|
102
|
+
waitUntilStatusMatches(predicate: (status: SyncStatus) => boolean): Promise<void>;
|
|
98
103
|
}
|
|
99
104
|
export declare const DEFAULT_CRUD_UPLOAD_THROTTLE_MS = 1000;
|
|
100
105
|
export declare const DEFAULT_RETRY_DELAY_MS = 5000;
|
|
@@ -116,6 +121,7 @@ export declare abstract class AbstractStreamingSyncImplementation extends BaseOb
|
|
|
116
121
|
constructor(options: AbstractStreamingSyncImplementationOptions);
|
|
117
122
|
waitForReady(): Promise<void>;
|
|
118
123
|
waitForStatus(status: SyncStatusOptions): Promise<void>;
|
|
124
|
+
waitUntilStatusMatches(predicate: (status: SyncStatus) => boolean): Promise<void>;
|
|
119
125
|
get lastSyncedAt(): Date | undefined;
|
|
120
126
|
get isConnected(): boolean;
|
|
121
127
|
protected get logger(): Logger.ILogger;
|
|
@@ -130,6 +136,7 @@ export declare abstract class AbstractStreamingSyncImplementation extends BaseOb
|
|
|
130
136
|
* @deprecated use [connect instead]
|
|
131
137
|
*/
|
|
132
138
|
streamingSync(signal?: AbortSignal, options?: PowerSyncConnectionOptions): Promise<void>;
|
|
139
|
+
private collectLocalBucketState;
|
|
133
140
|
protected streamingSyncIteration(signal: AbortSignal, options?: PowerSyncConnectionOptions): Promise<{
|
|
134
141
|
retry?: boolean;
|
|
135
142
|
}>;
|