@powersync/common 1.23.0 → 1.25.0
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/dist/bundle.mjs +3 -3
- package/lib/client/AbstractPowerSyncDatabase.d.ts +12 -2
- package/lib/client/AbstractPowerSyncDatabase.js +53 -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 +22 -8
- package/lib/client/sync/stream/AbstractStreamingSyncImplementation.d.ts +8 -1
- package/lib/client/sync/stream/AbstractStreamingSyncImplementation.js +109 -39
- 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 +1 -1
- package/lib/db/crud/SyncStatus.d.ts +29 -0
- package/lib/db/crud/SyncStatus.js +46 -2
- 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;
|
|
@@ -141,6 +141,7 @@ export declare abstract class AbstractPowerSyncDatabase extends BaseObserver<Pow
|
|
|
141
141
|
* Whether a connection to the PowerSync service is currently open.
|
|
142
142
|
*/
|
|
143
143
|
get connected(): boolean;
|
|
144
|
+
get connecting(): boolean;
|
|
144
145
|
/**
|
|
145
146
|
* Opens the DBAdapter given open options using a default open factory
|
|
146
147
|
*/
|
|
@@ -152,9 +153,18 @@ export declare abstract class AbstractPowerSyncDatabase extends BaseObserver<Pow
|
|
|
152
153
|
*/
|
|
153
154
|
waitForReady(): Promise<void>;
|
|
154
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.
|
|
155
162
|
* @returns A promise which will resolve once the first full sync has completed.
|
|
156
163
|
*/
|
|
157
|
-
waitForFirstSync(
|
|
164
|
+
waitForFirstSync(request?: AbortSignal | {
|
|
165
|
+
signal?: AbortSignal;
|
|
166
|
+
priority?: number;
|
|
167
|
+
}): Promise<void>;
|
|
158
168
|
/**
|
|
159
169
|
* Allows for extended implementations to execute custom initialization
|
|
160
170
|
* logic as part of the total init process
|
|
@@ -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
|
/**
|
|
@@ -114,6 +118,9 @@ export class AbstractPowerSyncDatabase extends BaseObserver {
|
|
|
114
118
|
get connected() {
|
|
115
119
|
return this.currentStatus?.connected || false;
|
|
116
120
|
}
|
|
121
|
+
get connecting() {
|
|
122
|
+
return this.currentStatus?.connecting || false;
|
|
123
|
+
}
|
|
117
124
|
/**
|
|
118
125
|
* @returns A promise which will resolve once initialization is completed.
|
|
119
126
|
*/
|
|
@@ -124,16 +131,27 @@ export class AbstractPowerSyncDatabase extends BaseObserver {
|
|
|
124
131
|
await this._isReadyPromise;
|
|
125
132
|
}
|
|
126
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.
|
|
127
140
|
* @returns A promise which will resolve once the first full sync has completed.
|
|
128
141
|
*/
|
|
129
|
-
async waitForFirstSync(
|
|
130
|
-
|
|
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)) {
|
|
131
149
|
return;
|
|
132
150
|
}
|
|
133
151
|
return new Promise((resolve) => {
|
|
134
152
|
const dispose = this.registerListener({
|
|
135
153
|
statusChanged: (status) => {
|
|
136
|
-
if (status
|
|
154
|
+
if (statusMatches(status)) {
|
|
137
155
|
dispose();
|
|
138
156
|
resolve();
|
|
139
157
|
}
|
|
@@ -174,19 +192,36 @@ export class AbstractPowerSyncDatabase extends BaseObserver {
|
|
|
174
192
|
.map((n) => parseInt(n));
|
|
175
193
|
}
|
|
176
194
|
catch (e) {
|
|
177
|
-
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}`);
|
|
178
196
|
}
|
|
179
|
-
// Validate >=0.
|
|
180
|
-
if (versionInts[0] != 0 || versionInts[1] <
|
|
181
|
-
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}`);
|
|
182
200
|
}
|
|
183
201
|
}
|
|
184
202
|
async updateHasSynced() {
|
|
185
|
-
const result = await this.database.
|
|
186
|
-
|
|
187
|
-
const
|
|
188
|
-
|
|
189
|
-
|
|
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;
|
|
190
225
|
this.iterateListeners((l) => l.statusChanged?.(this.currentStatus));
|
|
191
226
|
}
|
|
192
227
|
}
|
|
@@ -242,7 +277,7 @@ export class AbstractPowerSyncDatabase extends BaseObserver {
|
|
|
242
277
|
const { retryDelayMs, crudUploadThrottleMs } = this.resolvedConnectionOptions(options);
|
|
243
278
|
this.syncStreamImplementation = this.generateSyncStreamImplementation(connector, {
|
|
244
279
|
retryDelayMs,
|
|
245
|
-
crudUploadThrottleMs
|
|
280
|
+
crudUploadThrottleMs
|
|
246
281
|
});
|
|
247
282
|
this.syncStatusListenerDisposer = this.syncStreamImplementation.registerListener({
|
|
248
283
|
statusChanged: (status) => {
|
|
@@ -299,12 +334,15 @@ export class AbstractPowerSyncDatabase extends BaseObserver {
|
|
|
299
334
|
*/
|
|
300
335
|
async close(options = DEFAULT_POWERSYNC_CLOSE_OPTIONS) {
|
|
301
336
|
await this.waitForReady();
|
|
337
|
+
if (this.closed) {
|
|
338
|
+
return;
|
|
339
|
+
}
|
|
302
340
|
const { disconnect } = options;
|
|
303
341
|
if (disconnect) {
|
|
304
342
|
await this.disconnect();
|
|
305
343
|
}
|
|
306
344
|
await this.syncStreamImplementation?.dispose();
|
|
307
|
-
this.database.close();
|
|
345
|
+
await this.database.close();
|
|
308
346
|
this.closed = true;
|
|
309
347
|
}
|
|
310
348
|
/**
|
|
@@ -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 = 10;
|
|
13
13
|
const SYNC_QUEUE_REQUEST_LOW_WATER = 5;
|
|
14
14
|
// Keep alive message is sent every period
|
|
15
15
|
const KEEP_ALIVE_MS = 20_000;
|
|
16
16
|
// The ACK must be received in this period
|
|
17
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
|
}>;
|