@powersync/common 1.23.0 → 1.24.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 +1 -0
- package/lib/client/AbstractPowerSyncDatabase.js +3 -0
- package/lib/client/sync/stream/AbstractRemote.d.ts +16 -1
- package/lib/client/sync/stream/AbstractRemote.js +20 -7
- package/lib/client/sync/stream/AbstractStreamingSyncImplementation.d.ts +5 -1
- package/lib/client/sync/stream/AbstractStreamingSyncImplementation.js +21 -7
- package/lib/db/crud/SyncStatus.d.ts +2 -0
- package/lib/db/crud/SyncStatus.js +5 -1
- package/package.json +1 -1
|
@@ -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
|
*/
|
|
@@ -114,6 +114,9 @@ export class AbstractPowerSyncDatabase extends BaseObserver {
|
|
|
114
114
|
get connected() {
|
|
115
115
|
return this.currentStatus?.connected || false;
|
|
116
116
|
}
|
|
117
|
+
get connecting() {
|
|
118
|
+
return this.currentStatus?.connecting || false;
|
|
119
|
+
}
|
|
117
120
|
/**
|
|
118
121
|
* @returns A promise which will resolve once initialization is completed.
|
|
119
122
|
*/
|
|
@@ -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
|
*/
|
|
@@ -9,13 +9,25 @@ import { version as POWERSYNC_JS_VERSION } from '../../../../package.json';
|
|
|
9
9
|
const POWERSYNC_TRAILING_SLASH_MATCH = /\/+$/;
|
|
10
10
|
// Refresh at least 30 sec before it expires
|
|
11
11
|
const REFRESH_CREDENTIALS_SAFETY_PERIOD_MS = 30_000;
|
|
12
|
-
const SYNC_QUEUE_REQUEST_N = 10;
|
|
13
12
|
const SYNC_QUEUE_REQUEST_LOW_WATER = 5;
|
|
14
13
|
// Keep alive message is sent every period
|
|
15
14
|
const KEEP_ALIVE_MS = 20_000;
|
|
16
15
|
// The ACK must be received in this period
|
|
17
16
|
const KEEP_ALIVE_LIFETIME_MS = 30_000;
|
|
18
17
|
export const DEFAULT_REMOTE_LOGGER = Logger.get('PowerSyncRemote');
|
|
18
|
+
export var FetchStrategy;
|
|
19
|
+
(function (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
|
+
FetchStrategy["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
|
+
FetchStrategy["Sequential"] = "sequential";
|
|
30
|
+
})(FetchStrategy || (FetchStrategy = {}));
|
|
19
31
|
/**
|
|
20
32
|
* Class wrapper for providing a fetch implementation.
|
|
21
33
|
* The class wrapper is used to distinguish the fetchImplementation
|
|
@@ -144,7 +156,8 @@ export class AbstractRemote {
|
|
|
144
156
|
* Connects to the sync/stream websocket endpoint
|
|
145
157
|
*/
|
|
146
158
|
async socketStream(options) {
|
|
147
|
-
const { path } = options;
|
|
159
|
+
const { path, fetchStrategy = FetchStrategy.Buffered } = options;
|
|
160
|
+
const syncQueueRequestSize = fetchStrategy == FetchStrategy.Buffered ? 10 : 1;
|
|
148
161
|
const request = await this.buildRequest(path);
|
|
149
162
|
const bson = await this.getBSON();
|
|
150
163
|
// Add the user agent in the setup payload - we can't set custom
|
|
@@ -197,7 +210,7 @@ export class AbstractRemote {
|
|
|
197
210
|
// Helps to prevent double close scenarios
|
|
198
211
|
rsocket.onClose(() => (socketIsClosed = true));
|
|
199
212
|
// We initially request this amount and expect these to arrive eventually
|
|
200
|
-
let pendingEventsCount =
|
|
213
|
+
let pendingEventsCount = syncQueueRequestSize;
|
|
201
214
|
const disposeClosedListener = stream.registerListener({
|
|
202
215
|
closed: () => {
|
|
203
216
|
closeSocket();
|
|
@@ -211,7 +224,7 @@ export class AbstractRemote {
|
|
|
211
224
|
metadata: Buffer.from(bson.serialize({
|
|
212
225
|
path
|
|
213
226
|
}))
|
|
214
|
-
},
|
|
227
|
+
}, syncQueueRequestSize, // The initial N amount
|
|
215
228
|
{
|
|
216
229
|
onError: (e) => {
|
|
217
230
|
// Don't log closed as an error
|
|
@@ -250,10 +263,10 @@ export class AbstractRemote {
|
|
|
250
263
|
const l = stream.registerListener({
|
|
251
264
|
lowWater: async () => {
|
|
252
265
|
// Request to fill up the queue
|
|
253
|
-
const required =
|
|
266
|
+
const required = syncQueueRequestSize - pendingEventsCount;
|
|
254
267
|
if (required > 0) {
|
|
255
|
-
socket.request(
|
|
256
|
-
pendingEventsCount =
|
|
268
|
+
socket.request(syncQueueRequestSize - pendingEventsCount);
|
|
269
|
+
pendingEventsCount = syncQueueRequestSize;
|
|
257
270
|
}
|
|
258
271
|
},
|
|
259
272
|
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
|
*/
|
|
@@ -4,6 +4,7 @@ import { AbortOperation } from '../../../utils/AbortOperation.js';
|
|
|
4
4
|
import { BaseObserver } from '../../../utils/BaseObserver.js';
|
|
5
5
|
import { throttleLeadingTrailing } from '../../../utils/throttle.js';
|
|
6
6
|
import { SyncDataBucket } from '../bucket/SyncDataBucket.js';
|
|
7
|
+
import { FetchStrategy } from './AbstractRemote.js';
|
|
7
8
|
import { isStreamingKeepalive, isStreamingSyncCheckpoint, isStreamingSyncCheckpointComplete, isStreamingSyncCheckpointDiff, isStreamingSyncData } from './streaming-sync-types.js';
|
|
8
9
|
export var LockType;
|
|
9
10
|
(function (LockType) {
|
|
@@ -24,6 +25,7 @@ export const DEFAULT_STREAMING_SYNC_OPTIONS = {
|
|
|
24
25
|
};
|
|
25
26
|
export const DEFAULT_STREAM_CONNECTION_OPTIONS = {
|
|
26
27
|
connectionMethod: SyncStreamConnectionMethod.WEB_SOCKET,
|
|
28
|
+
fetchStrategy: FetchStrategy.Buffered,
|
|
27
29
|
params: {}
|
|
28
30
|
};
|
|
29
31
|
export class AbstractStreamingSyncImplementation extends BaseObserver {
|
|
@@ -39,6 +41,7 @@ export class AbstractStreamingSyncImplementation extends BaseObserver {
|
|
|
39
41
|
this.options = { ...DEFAULT_STREAMING_SYNC_OPTIONS, ...options };
|
|
40
42
|
this.syncStatus = new SyncStatus({
|
|
41
43
|
connected: false,
|
|
44
|
+
connecting: false,
|
|
42
45
|
lastSyncedAt: undefined,
|
|
43
46
|
dataFlow: {
|
|
44
47
|
uploading: false,
|
|
@@ -208,7 +211,7 @@ The next upload iteration will be delayed.`);
|
|
|
208
211
|
}
|
|
209
212
|
this.streamingSyncPromise = undefined;
|
|
210
213
|
this.abortController = null;
|
|
211
|
-
this.updateSyncStatus({ connected: false });
|
|
214
|
+
this.updateSyncStatus({ connected: false, connecting: false });
|
|
212
215
|
}
|
|
213
216
|
/**
|
|
214
217
|
* @deprecated use [connect instead]
|
|
@@ -239,6 +242,7 @@ The next upload iteration will be delayed.`);
|
|
|
239
242
|
this.crudUpdateListener = undefined;
|
|
240
243
|
this.updateSyncStatus({
|
|
241
244
|
connected: false,
|
|
245
|
+
connecting: false,
|
|
242
246
|
dataFlow: {
|
|
243
247
|
downloading: false
|
|
244
248
|
}
|
|
@@ -251,6 +255,7 @@ The next upload iteration will be delayed.`);
|
|
|
251
255
|
* - Close any sync stream ReadableStreams (which will also close any established network requests)
|
|
252
256
|
*/
|
|
253
257
|
while (true) {
|
|
258
|
+
this.updateSyncStatus({ connecting: true });
|
|
254
259
|
try {
|
|
255
260
|
if (signal?.aborted) {
|
|
256
261
|
break;
|
|
@@ -281,6 +286,7 @@ The next upload iteration will be delayed.`);
|
|
|
281
286
|
else {
|
|
282
287
|
this.logger.error(ex);
|
|
283
288
|
}
|
|
289
|
+
// On error, wait a little before retrying
|
|
284
290
|
await this.delayRetry();
|
|
285
291
|
}
|
|
286
292
|
finally {
|
|
@@ -289,13 +295,13 @@ The next upload iteration will be delayed.`);
|
|
|
289
295
|
nestedAbortController = new AbortController();
|
|
290
296
|
}
|
|
291
297
|
this.updateSyncStatus({
|
|
292
|
-
connected: false
|
|
298
|
+
connected: false,
|
|
299
|
+
connecting: true // May be unnecessary
|
|
293
300
|
});
|
|
294
|
-
// On error, wait a little before retrying
|
|
295
301
|
}
|
|
296
302
|
}
|
|
297
303
|
// Mark as disconnected if here
|
|
298
|
-
this.updateSyncStatus({ connected: false });
|
|
304
|
+
this.updateSyncStatus({ connected: false, connecting: false });
|
|
299
305
|
}
|
|
300
306
|
async streamingSyncIteration(signal, options) {
|
|
301
307
|
return await this.obtainLock({
|
|
@@ -335,9 +341,16 @@ The next upload iteration will be delayed.`);
|
|
|
335
341
|
client_id: clientId
|
|
336
342
|
}
|
|
337
343
|
};
|
|
338
|
-
|
|
339
|
-
|
|
340
|
-
|
|
344
|
+
let stream;
|
|
345
|
+
if (resolvedOptions?.connectionMethod == SyncStreamConnectionMethod.HTTP) {
|
|
346
|
+
stream = await this.options.remote.postStream(syncOptions);
|
|
347
|
+
}
|
|
348
|
+
else {
|
|
349
|
+
stream = await this.options.remote.socketStream({
|
|
350
|
+
...syncOptions,
|
|
351
|
+
...{ fetchStrategy: resolvedOptions.fetchStrategy }
|
|
352
|
+
});
|
|
353
|
+
}
|
|
341
354
|
this.logger.debug('Stream established. Processing events');
|
|
342
355
|
while (!stream.closed) {
|
|
343
356
|
const line = await stream.read();
|
|
@@ -490,6 +503,7 @@ The next upload iteration will be delayed.`);
|
|
|
490
503
|
updateSyncStatus(options) {
|
|
491
504
|
const updatedStatus = new SyncStatus({
|
|
492
505
|
connected: options.connected ?? this.syncStatus.connected,
|
|
506
|
+
connecting: !options.connected && (options.connecting ?? this.syncStatus.connecting),
|
|
493
507
|
lastSyncedAt: options.lastSyncedAt ?? this.syncStatus.lastSyncedAt,
|
|
494
508
|
dataFlow: {
|
|
495
509
|
...this.syncStatus.dataFlowStatus,
|
|
@@ -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
|