@powersync/common 0.0.0-dev-20250625140957 → 0.0.0-dev-20250701144132
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 +1 -1
- package/lib/client/sync/bucket/BucketStorageAdapter.d.ts +1 -1
- package/lib/client/sync/bucket/SqliteBucketStorage.d.ts +1 -1
- package/lib/client/sync/stream/AbstractRemote.d.ts +1 -2
- package/lib/client/sync/stream/AbstractRemote.js +30 -8
- package/lib/client/sync/stream/AbstractStreamingSyncImplementation.js +24 -8
- package/lib/utils/DataStream.d.ts +11 -13
- package/lib/utils/DataStream.js +5 -18
- package/package.json +3 -4
- package/dist/bundle.cjs +0 -22
|
@@ -90,5 +90,5 @@ export interface BucketStorageAdapter extends BaseObserver<BucketStorageListener
|
|
|
90
90
|
/**
|
|
91
91
|
* Invokes the `powersync_control` function for the sync client.
|
|
92
92
|
*/
|
|
93
|
-
control(op: PowerSyncControlCommand, payload: string |
|
|
93
|
+
control(op: PowerSyncControlCommand, payload: string | Uint8Array | null): Promise<string>;
|
|
94
94
|
}
|
|
@@ -56,7 +56,7 @@ export declare class SqliteBucketStorage extends BaseObserver<BucketStorageListe
|
|
|
56
56
|
* Set a target checkpoint.
|
|
57
57
|
*/
|
|
58
58
|
setTargetCheckpoint(checkpoint: Checkpoint): Promise<void>;
|
|
59
|
-
control(op: PowerSyncControlCommand, payload: string | ArrayBuffer | null): Promise<string>;
|
|
59
|
+
control(op: PowerSyncControlCommand, payload: string | Uint8Array | ArrayBuffer | null): Promise<string>;
|
|
60
60
|
hasMigratedSubkeys(): Promise<boolean>;
|
|
61
61
|
migrateToFixedSubkeys(): Promise<void>;
|
|
62
62
|
static _subkeyMigrationKey: string;
|
|
@@ -1,5 +1,4 @@
|
|
|
1
1
|
import type { BSON } from 'bson';
|
|
2
|
-
import { Buffer } from 'buffer';
|
|
3
2
|
import { type fetch } from 'cross-fetch';
|
|
4
3
|
import Logger, { ILogger } from 'js-logger';
|
|
5
4
|
import { DataStream } from '../../../utils/DataStream.js';
|
|
@@ -133,7 +132,7 @@ export declare abstract class AbstractRemote {
|
|
|
133
132
|
* @param bson A BSON encoder and decoder. When set, the data stream will be requested with a BSON payload
|
|
134
133
|
* (required for compatibility with older sync services).
|
|
135
134
|
*/
|
|
136
|
-
socketStreamRaw<T>(options: SocketSyncStreamOptions, map: (buffer:
|
|
135
|
+
socketStreamRaw<T>(options: SocketSyncStreamOptions, map: (buffer: Uint8Array) => T, bson?: typeof BSON): Promise<DataStream<T>>;
|
|
137
136
|
/**
|
|
138
137
|
* Connects to the sync/stream http endpoint, parsing lines as JSON.
|
|
139
138
|
*/
|
|
@@ -10,8 +10,12 @@ const POWERSYNC_JS_VERSION = PACKAGE.version;
|
|
|
10
10
|
const SYNC_QUEUE_REQUEST_LOW_WATER = 5;
|
|
11
11
|
// Keep alive message is sent every period
|
|
12
12
|
const KEEP_ALIVE_MS = 20_000;
|
|
13
|
-
//
|
|
14
|
-
const
|
|
13
|
+
// One message of any type must be received in this period.
|
|
14
|
+
const SOCKET_TIMEOUT_MS = 30_000;
|
|
15
|
+
// One keepalive message must be received in this period.
|
|
16
|
+
// If there is a backlog of messages (for example on slow connections), keepalive messages could be delayed
|
|
17
|
+
// significantly. Therefore this is longer than the socket timeout.
|
|
18
|
+
const KEEP_ALIVE_LIFETIME_MS = 90_000;
|
|
15
19
|
export const DEFAULT_REMOTE_LOGGER = Logger.get('PowerSyncRemote');
|
|
16
20
|
export var FetchStrategy;
|
|
17
21
|
(function (FetchStrategy) {
|
|
@@ -208,12 +212,25 @@ export class AbstractRemote {
|
|
|
208
212
|
// headers with websockets on web. The browser userAgent is however added
|
|
209
213
|
// automatically as a header.
|
|
210
214
|
const userAgent = this.getUserAgent();
|
|
215
|
+
let keepAliveTimeout;
|
|
216
|
+
const resetTimeout = () => {
|
|
217
|
+
clearTimeout(keepAliveTimeout);
|
|
218
|
+
keepAliveTimeout = setTimeout(() => {
|
|
219
|
+
this.logger.error(`No data received on WebSocket in ${SOCKET_TIMEOUT_MS}ms, closing connection.`);
|
|
220
|
+
stream.close();
|
|
221
|
+
}, SOCKET_TIMEOUT_MS);
|
|
222
|
+
};
|
|
223
|
+
resetTimeout();
|
|
211
224
|
const url = this.options.socketUrlTransformer(request.url);
|
|
212
225
|
const connector = new RSocketConnector({
|
|
213
226
|
transport: new WebsocketClientTransport({
|
|
214
227
|
url,
|
|
215
228
|
wsCreator: (url) => {
|
|
216
|
-
|
|
229
|
+
const socket = this.createSocket(url);
|
|
230
|
+
socket.addEventListener('message', (event) => {
|
|
231
|
+
resetTimeout();
|
|
232
|
+
});
|
|
233
|
+
return socket;
|
|
217
234
|
}
|
|
218
235
|
}),
|
|
219
236
|
setup: {
|
|
@@ -236,16 +253,20 @@ export class AbstractRemote {
|
|
|
236
253
|
}
|
|
237
254
|
catch (ex) {
|
|
238
255
|
this.logger.error(`Failed to connect WebSocket`, ex);
|
|
256
|
+
clearTimeout(keepAliveTimeout);
|
|
239
257
|
throw ex;
|
|
240
258
|
}
|
|
259
|
+
resetTimeout();
|
|
241
260
|
const stream = new DataStream({
|
|
242
261
|
logger: this.logger,
|
|
243
262
|
pressure: {
|
|
244
263
|
lowWaterMark: SYNC_QUEUE_REQUEST_LOW_WATER
|
|
245
|
-
}
|
|
264
|
+
},
|
|
265
|
+
mapLine: map
|
|
246
266
|
});
|
|
247
267
|
let socketIsClosed = false;
|
|
248
268
|
const closeSocket = () => {
|
|
269
|
+
clearTimeout(keepAliveTimeout);
|
|
249
270
|
if (socketIsClosed) {
|
|
250
271
|
return;
|
|
251
272
|
}
|
|
@@ -307,7 +328,7 @@ export class AbstractRemote {
|
|
|
307
328
|
if (!data) {
|
|
308
329
|
return;
|
|
309
330
|
}
|
|
310
|
-
stream.enqueueData(
|
|
331
|
+
stream.enqueueData(data);
|
|
311
332
|
},
|
|
312
333
|
onComplete: () => {
|
|
313
334
|
stream.close();
|
|
@@ -418,7 +439,8 @@ export class AbstractRemote {
|
|
|
418
439
|
const decoder = new TextDecoder();
|
|
419
440
|
let buffer = '';
|
|
420
441
|
const stream = new DataStream({
|
|
421
|
-
logger: this.logger
|
|
442
|
+
logger: this.logger,
|
|
443
|
+
mapLine: mapLine
|
|
422
444
|
});
|
|
423
445
|
const l = stream.registerListener({
|
|
424
446
|
lowWater: async () => {
|
|
@@ -429,7 +451,7 @@ export class AbstractRemote {
|
|
|
429
451
|
if (done) {
|
|
430
452
|
const remaining = buffer.trim();
|
|
431
453
|
if (remaining.length != 0) {
|
|
432
|
-
stream.enqueueData(
|
|
454
|
+
stream.enqueueData(remaining);
|
|
433
455
|
}
|
|
434
456
|
stream.close();
|
|
435
457
|
await closeReader();
|
|
@@ -441,7 +463,7 @@ export class AbstractRemote {
|
|
|
441
463
|
for (var i = 0; i < lines.length - 1; i++) {
|
|
442
464
|
var l = lines[i].trim();
|
|
443
465
|
if (l.length > 0) {
|
|
444
|
-
stream.enqueueData(
|
|
466
|
+
stream.enqueueData(l);
|
|
445
467
|
didCompleteLine = true;
|
|
446
468
|
}
|
|
447
469
|
}
|
|
@@ -668,19 +668,35 @@ The next upload iteration will be delayed.`);
|
|
|
668
668
|
data: instr.request
|
|
669
669
|
};
|
|
670
670
|
if (resolvedOptions.connectionMethod == SyncStreamConnectionMethod.HTTP) {
|
|
671
|
-
controlInvocations = await remote.postStreamRaw(syncOptions, (line) =>
|
|
672
|
-
|
|
673
|
-
|
|
674
|
-
|
|
671
|
+
controlInvocations = await remote.postStreamRaw(syncOptions, (line) => {
|
|
672
|
+
if (typeof line == 'string') {
|
|
673
|
+
return {
|
|
674
|
+
command: PowerSyncControlCommand.PROCESS_TEXT_LINE,
|
|
675
|
+
payload: line
|
|
676
|
+
};
|
|
677
|
+
}
|
|
678
|
+
else {
|
|
679
|
+
// Directly enqueued by us
|
|
680
|
+
return line;
|
|
681
|
+
}
|
|
682
|
+
});
|
|
675
683
|
}
|
|
676
684
|
else {
|
|
677
685
|
controlInvocations = await remote.socketStreamRaw({
|
|
678
686
|
...syncOptions,
|
|
679
687
|
fetchStrategy: resolvedOptions.fetchStrategy
|
|
680
|
-
}, (
|
|
681
|
-
|
|
682
|
-
|
|
683
|
-
|
|
688
|
+
}, (payload) => {
|
|
689
|
+
if (payload instanceof Uint8Array) {
|
|
690
|
+
return {
|
|
691
|
+
command: PowerSyncControlCommand.PROCESS_BSON_LINE,
|
|
692
|
+
payload: payload
|
|
693
|
+
};
|
|
694
|
+
}
|
|
695
|
+
else {
|
|
696
|
+
// Directly enqueued by us
|
|
697
|
+
return payload;
|
|
698
|
+
}
|
|
699
|
+
});
|
|
684
700
|
}
|
|
685
701
|
try {
|
|
686
702
|
while (!controlInvocations.closed) {
|
|
@@ -1,6 +1,7 @@
|
|
|
1
1
|
import { ILogger } from 'js-logger';
|
|
2
2
|
import { BaseListener, BaseObserver } from './BaseObserver.js';
|
|
3
|
-
export type DataStreamOptions = {
|
|
3
|
+
export type DataStreamOptions<ParsedData, SourceData> = {
|
|
4
|
+
mapLine?: (line: SourceData) => ParsedData;
|
|
4
5
|
/**
|
|
5
6
|
* Close the stream if any consumer throws an error
|
|
6
7
|
*/
|
|
@@ -28,13 +29,14 @@ export declare const DEFAULT_PRESSURE_LIMITS: {
|
|
|
28
29
|
* native JS streams or async iterators.
|
|
29
30
|
* This is handy for environments such as React Native which need polyfills for the above.
|
|
30
31
|
*/
|
|
31
|
-
export declare class DataStream<
|
|
32
|
-
protected options?: DataStreamOptions | undefined;
|
|
33
|
-
dataQueue:
|
|
32
|
+
export declare class DataStream<ParsedData, SourceData = any> extends BaseObserver<DataStreamListener<ParsedData>> {
|
|
33
|
+
protected options?: DataStreamOptions<ParsedData, SourceData> | undefined;
|
|
34
|
+
dataQueue: SourceData[];
|
|
34
35
|
protected isClosed: boolean;
|
|
35
36
|
protected processingPromise: Promise<void> | null;
|
|
36
37
|
protected logger: ILogger;
|
|
37
|
-
|
|
38
|
+
protected mapLine: (line: SourceData) => ParsedData;
|
|
39
|
+
constructor(options?: DataStreamOptions<ParsedData, SourceData> | undefined);
|
|
38
40
|
get highWatermark(): number;
|
|
39
41
|
get lowWatermark(): number;
|
|
40
42
|
get closed(): boolean;
|
|
@@ -42,22 +44,18 @@ export declare class DataStream<Data extends any = any> extends BaseObserver<Dat
|
|
|
42
44
|
/**
|
|
43
45
|
* Enqueues data for the consumers to read
|
|
44
46
|
*/
|
|
45
|
-
enqueueData(data:
|
|
47
|
+
enqueueData(data: SourceData): void;
|
|
46
48
|
/**
|
|
47
49
|
* Reads data once from the data stream
|
|
48
50
|
* @returns a Data payload or Null if the stream closed.
|
|
49
51
|
*/
|
|
50
|
-
read(): Promise<
|
|
52
|
+
read(): Promise<ParsedData | null>;
|
|
51
53
|
/**
|
|
52
54
|
* Executes a callback for each data item in the stream
|
|
53
55
|
*/
|
|
54
|
-
forEach(callback: DataStreamCallback<
|
|
56
|
+
forEach(callback: DataStreamCallback<ParsedData>): () => void;
|
|
55
57
|
protected processQueue(): Promise<void>;
|
|
56
|
-
/**
|
|
57
|
-
* Creates a new data stream which is a map of the original
|
|
58
|
-
*/
|
|
59
|
-
map<ReturnData>(callback: (data: Data) => ReturnData): DataStream<ReturnData>;
|
|
60
58
|
protected hasDataReader(): boolean;
|
|
61
59
|
protected _processQueue(): Promise<void>;
|
|
62
|
-
protected iterateAsyncErrored(cb: (l:
|
|
60
|
+
protected iterateAsyncErrored(cb: (l: Partial<DataStreamListener<ParsedData>>) => Promise<void>): Promise<void>;
|
|
63
61
|
}
|
package/lib/utils/DataStream.js
CHANGED
|
@@ -15,12 +15,14 @@ export class DataStream extends BaseObserver {
|
|
|
15
15
|
isClosed;
|
|
16
16
|
processingPromise;
|
|
17
17
|
logger;
|
|
18
|
+
mapLine;
|
|
18
19
|
constructor(options) {
|
|
19
20
|
super();
|
|
20
21
|
this.options = options;
|
|
21
22
|
this.processingPromise = null;
|
|
22
23
|
this.isClosed = false;
|
|
23
24
|
this.dataQueue = [];
|
|
25
|
+
this.mapLine = options?.mapLine ?? ((line) => line);
|
|
24
26
|
this.logger = options?.logger ?? Logger.get('DataStream');
|
|
25
27
|
if (options?.closeOnError) {
|
|
26
28
|
const l = this.registerListener({
|
|
@@ -110,22 +112,6 @@ export class DataStream extends BaseObserver {
|
|
|
110
112
|
}
|
|
111
113
|
return (this.processingPromise = this._processQueue());
|
|
112
114
|
}
|
|
113
|
-
/**
|
|
114
|
-
* Creates a new data stream which is a map of the original
|
|
115
|
-
*/
|
|
116
|
-
map(callback) {
|
|
117
|
-
const stream = new DataStream(this.options);
|
|
118
|
-
const l = this.registerListener({
|
|
119
|
-
data: async (data) => {
|
|
120
|
-
stream.enqueueData(callback(data));
|
|
121
|
-
},
|
|
122
|
-
closed: () => {
|
|
123
|
-
stream.close();
|
|
124
|
-
l?.();
|
|
125
|
-
}
|
|
126
|
-
});
|
|
127
|
-
return stream;
|
|
128
|
-
}
|
|
129
115
|
hasDataReader() {
|
|
130
116
|
return Array.from(this.listeners.values()).some((l) => !!l.data);
|
|
131
117
|
}
|
|
@@ -136,7 +122,8 @@ export class DataStream extends BaseObserver {
|
|
|
136
122
|
}
|
|
137
123
|
if (this.dataQueue.length) {
|
|
138
124
|
const data = this.dataQueue.shift();
|
|
139
|
-
|
|
125
|
+
const mapped = this.mapLine(data);
|
|
126
|
+
await this.iterateAsyncErrored(async (l) => l.data?.(mapped));
|
|
140
127
|
}
|
|
141
128
|
if (this.dataQueue.length <= this.lowWatermark) {
|
|
142
129
|
await this.iterateAsyncErrored(async (l) => l.lowWater?.());
|
|
@@ -148,7 +135,7 @@ export class DataStream extends BaseObserver {
|
|
|
148
135
|
}
|
|
149
136
|
}
|
|
150
137
|
async iterateAsyncErrored(cb) {
|
|
151
|
-
for (let i of
|
|
138
|
+
for (let i of this.listeners.values()) {
|
|
152
139
|
try {
|
|
153
140
|
await cb(i);
|
|
154
141
|
}
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@powersync/common",
|
|
3
|
-
"version": "0.0.0-dev-
|
|
3
|
+
"version": "0.0.0-dev-20250701144132",
|
|
4
4
|
"publishConfig": {
|
|
5
5
|
"registry": "https://registry.npmjs.org/",
|
|
6
6
|
"access": "public"
|
|
@@ -13,9 +13,8 @@
|
|
|
13
13
|
"exports": {
|
|
14
14
|
".": {
|
|
15
15
|
"import": "./dist/bundle.mjs",
|
|
16
|
-
"
|
|
17
|
-
"types": "./lib/index.d.ts"
|
|
18
|
-
"default": "./dist/bundle.mjs"
|
|
16
|
+
"default": "./dist/bundle.mjs",
|
|
17
|
+
"types": "./lib/index.d.ts"
|
|
19
18
|
}
|
|
20
19
|
},
|
|
21
20
|
"author": "JOURNEYAPPS",
|