@powersync/common 0.0.0-dev-20250528152729 → 0.0.0-dev-20250529141956
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 +2 -2
- package/lib/client/AbstractPowerSyncDatabase.d.ts +1 -1
- package/lib/client/AbstractPowerSyncDatabase.js +3 -3
- package/lib/client/ConnectionManager.d.ts +9 -1
- package/lib/client/ConnectionManager.js +12 -4
- package/lib/client/sync/stream/AbstractRemote.js +1 -1
- package/lib/client/sync/stream/AbstractStreamingSyncImplementation.js +8 -2
- package/lib/client/sync/stream/WebsocketClientTransport.d.ts +15 -0
- package/lib/client/sync/stream/WebsocketClientTransport.js +48 -0
- package/package.json +1 -1
|
@@ -122,7 +122,7 @@ export declare abstract class AbstractPowerSyncDatabase extends BaseObserver<Pow
|
|
|
122
122
|
get syncStreamImplementation(): StreamingSyncImplementation | null;
|
|
123
123
|
protected _schema: Schema;
|
|
124
124
|
private _database;
|
|
125
|
-
protected
|
|
125
|
+
protected runExclusiveMutex: Mutex;
|
|
126
126
|
constructor(options: PowerSyncDatabaseOptionsWithDBAdapter);
|
|
127
127
|
constructor(options: PowerSyncDatabaseOptionsWithOpenFactory);
|
|
128
128
|
constructor(options: PowerSyncDatabaseOptionsWithSettings);
|
|
@@ -69,7 +69,7 @@ export class AbstractPowerSyncDatabase extends BaseObserver {
|
|
|
69
69
|
}
|
|
70
70
|
_schema;
|
|
71
71
|
_database;
|
|
72
|
-
|
|
72
|
+
runExclusiveMutex;
|
|
73
73
|
constructor(options) {
|
|
74
74
|
super();
|
|
75
75
|
this.options = options;
|
|
@@ -96,7 +96,7 @@ export class AbstractPowerSyncDatabase extends BaseObserver {
|
|
|
96
96
|
this._schema = schema;
|
|
97
97
|
this.ready = false;
|
|
98
98
|
this.sdkVersion = '';
|
|
99
|
-
this.
|
|
99
|
+
this.runExclusiveMutex = new Mutex();
|
|
100
100
|
// Start async init
|
|
101
101
|
this.connectionManager = new ConnectionManager({
|
|
102
102
|
createSyncImplementation: async (connector, options) => {
|
|
@@ -298,7 +298,7 @@ export class AbstractPowerSyncDatabase extends BaseObserver {
|
|
|
298
298
|
* Locking here is mostly only important on web for multiple tab scenarios.
|
|
299
299
|
*/
|
|
300
300
|
runExclusive(callback) {
|
|
301
|
-
return this.
|
|
301
|
+
return this.runExclusiveMutex.runExclusive(callback);
|
|
302
302
|
}
|
|
303
303
|
/**
|
|
304
304
|
* Connects to stream of events from the PowerSync instance.
|
|
@@ -7,6 +7,10 @@ import { PowerSyncConnectionOptions, StreamingSyncImplementation } from './sync/
|
|
|
7
7
|
*/
|
|
8
8
|
export interface ConnectionManagerSyncImplementationResult {
|
|
9
9
|
sync: StreamingSyncImplementation;
|
|
10
|
+
/**
|
|
11
|
+
* Additional cleanup function which is called after the sync stream implementation
|
|
12
|
+
* is disposed.
|
|
13
|
+
*/
|
|
10
14
|
onDispose: () => Promise<void> | void;
|
|
11
15
|
}
|
|
12
16
|
/**
|
|
@@ -54,7 +58,11 @@ export declare class ConnectionManager extends BaseObserver<ConnectionManagerLis
|
|
|
54
58
|
*/
|
|
55
59
|
protected pendingConnectionOptions: StoredConnectionOptions | null;
|
|
56
60
|
syncStreamImplementation: StreamingSyncImplementation | null;
|
|
57
|
-
|
|
61
|
+
/**
|
|
62
|
+
* Additional cleanup function which is called after the sync stream implementation
|
|
63
|
+
* is disposed.
|
|
64
|
+
*/
|
|
65
|
+
protected syncDisposer: (() => Promise<void> | void) | null;
|
|
58
66
|
constructor(options: ConnectionManagerOptions);
|
|
59
67
|
get logger(): ILogger;
|
|
60
68
|
close(): Promise<void>;
|
|
@@ -27,6 +27,10 @@ export class ConnectionManager extends BaseObserver {
|
|
|
27
27
|
*/
|
|
28
28
|
pendingConnectionOptions;
|
|
29
29
|
syncStreamImplementation;
|
|
30
|
+
/**
|
|
31
|
+
* Additional cleanup function which is called after the sync stream implementation
|
|
32
|
+
* is disposed.
|
|
33
|
+
*/
|
|
30
34
|
syncDisposer;
|
|
31
35
|
constructor(options) {
|
|
32
36
|
super();
|
|
@@ -42,6 +46,7 @@ export class ConnectionManager extends BaseObserver {
|
|
|
42
46
|
return this.options.logger;
|
|
43
47
|
}
|
|
44
48
|
async close() {
|
|
49
|
+
await this.syncStreamImplementation?.dispose();
|
|
45
50
|
await this.syncDisposer?.();
|
|
46
51
|
}
|
|
47
52
|
async connect(connector, options) {
|
|
@@ -58,7 +63,7 @@ export class ConnectionManager extends BaseObserver {
|
|
|
58
63
|
// If we do already have pending options, a disconnect has already been performed.
|
|
59
64
|
// The connectInternal method also does a sanity disconnect to prevent straggler connections.
|
|
60
65
|
// We should also disconnect if we have already completed a connection attempt.
|
|
61
|
-
if (!hadPendingOptions) {
|
|
66
|
+
if (!hadPendingOptions || this.syncStreamImplementation) {
|
|
62
67
|
await this.disconnectInternal();
|
|
63
68
|
}
|
|
64
69
|
// Triggers a connect which checks if pending options are available after the connect completes.
|
|
@@ -97,6 +102,9 @@ export class ConnectionManager extends BaseObserver {
|
|
|
97
102
|
// A disconnect could have cleared this.
|
|
98
103
|
return;
|
|
99
104
|
}
|
|
105
|
+
if (this.disconnectingPromise) {
|
|
106
|
+
return;
|
|
107
|
+
}
|
|
100
108
|
const { connector, options } = this.pendingConnectionOptions;
|
|
101
109
|
appliedOptions = options;
|
|
102
110
|
this.pendingConnectionOptions = null;
|
|
@@ -139,14 +147,14 @@ export class ConnectionManager extends BaseObserver {
|
|
|
139
147
|
// A disconnect is already in progress
|
|
140
148
|
return this.disconnectingPromise;
|
|
141
149
|
}
|
|
142
|
-
// Wait if a sync stream implementation is being created before closing it
|
|
143
|
-
// (syncStreamImplementation must be assigned before we can properly dispose it)
|
|
144
|
-
await this.syncStreamInitPromise;
|
|
145
150
|
this.disconnectingPromise = this.performDisconnect();
|
|
146
151
|
await this.disconnectingPromise;
|
|
147
152
|
this.disconnectingPromise = null;
|
|
148
153
|
}
|
|
149
154
|
async performDisconnect() {
|
|
155
|
+
// Wait if a sync stream implementation is being created before closing it
|
|
156
|
+
// (syncStreamImplementation must be assigned before we can properly dispose it)
|
|
157
|
+
await this.syncStreamInitPromise;
|
|
150
158
|
// Keep reference to the sync stream implementation and disposer
|
|
151
159
|
// The class members will be cleared before we trigger the disconnect
|
|
152
160
|
// to prevent any further calls to the sync stream implementation.
|
|
@@ -2,10 +2,10 @@ import { Buffer } from 'buffer';
|
|
|
2
2
|
import ndjsonStream from 'can-ndjson-stream';
|
|
3
3
|
import Logger from 'js-logger';
|
|
4
4
|
import { RSocketConnector } from 'rsocket-core';
|
|
5
|
-
import { WebsocketClientTransport } from 'rsocket-websocket-client';
|
|
6
5
|
import PACKAGE from '../../../../package.json' with { type: 'json' };
|
|
7
6
|
import { AbortOperation } from '../../../utils/AbortOperation.js';
|
|
8
7
|
import { DataStream } from '../../../utils/DataStream.js';
|
|
8
|
+
import { WebsocketClientTransport } from './WebsocketClientTransport.js';
|
|
9
9
|
const POWERSYNC_TRAILING_SLASH_MATCH = /\/+$/;
|
|
10
10
|
const POWERSYNC_JS_VERSION = PACKAGE.version;
|
|
11
11
|
const SYNC_QUEUE_REQUEST_LOW_WATER = 5;
|
|
@@ -298,12 +298,16 @@ The next upload iteration will be delayed.`);
|
|
|
298
298
|
* Either:
|
|
299
299
|
* - A network request failed with a failed connection or not OKAY response code.
|
|
300
300
|
* - There was a sync processing error.
|
|
301
|
-
*
|
|
301
|
+
* - The connection was aborted.
|
|
302
|
+
* This loop will retry after a delay if the connection was not aborted.
|
|
302
303
|
* The nested abort controller will cleanup any open network requests and streams.
|
|
303
304
|
* The WebRemote should only abort pending fetch requests or close active Readable streams.
|
|
304
305
|
*/
|
|
306
|
+
let delay = true;
|
|
305
307
|
if (ex instanceof AbortOperation) {
|
|
306
308
|
this.logger.warn(ex);
|
|
309
|
+
delay = false;
|
|
310
|
+
// A disconnect was requested, we should not delay since there is no explicit retry
|
|
307
311
|
}
|
|
308
312
|
else {
|
|
309
313
|
this.logger.error(ex);
|
|
@@ -314,7 +318,9 @@ The next upload iteration will be delayed.`);
|
|
|
314
318
|
}
|
|
315
319
|
});
|
|
316
320
|
// On error, wait a little before retrying
|
|
317
|
-
|
|
321
|
+
if (delay) {
|
|
322
|
+
await this.delayRetry();
|
|
323
|
+
}
|
|
318
324
|
}
|
|
319
325
|
finally {
|
|
320
326
|
if (!signal.aborted) {
|
|
@@ -0,0 +1,15 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Adapted from rsocket-websocket-client
|
|
3
|
+
* https://github.com/rsocket/rsocket-js/blob/e224cf379e747c4f1ddc4f2fa111854626cc8575/packages/rsocket-websocket-client/src/WebsocketClientTransport.ts#L17
|
|
4
|
+
* This adds additional error handling for React Native iOS.
|
|
5
|
+
* This particularly adds a close listener to handle cases where the WebSocket
|
|
6
|
+
* connection closes immediately after opening without emitting an error.
|
|
7
|
+
*/
|
|
8
|
+
import { ClientTransport, Closeable, Demultiplexer, DuplexConnection, FrameHandler, Multiplexer, Outbound } from 'rsocket-core';
|
|
9
|
+
import { ClientOptions } from 'rsocket-websocket-client';
|
|
10
|
+
export declare class WebsocketClientTransport implements ClientTransport {
|
|
11
|
+
private readonly url;
|
|
12
|
+
private readonly factory;
|
|
13
|
+
constructor(options: ClientOptions);
|
|
14
|
+
connect(multiplexerDemultiplexerFactory: (outbound: Outbound & Closeable) => Multiplexer & Demultiplexer & FrameHandler): Promise<DuplexConnection>;
|
|
15
|
+
}
|
|
@@ -0,0 +1,48 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Adapted from rsocket-websocket-client
|
|
3
|
+
* https://github.com/rsocket/rsocket-js/blob/e224cf379e747c4f1ddc4f2fa111854626cc8575/packages/rsocket-websocket-client/src/WebsocketClientTransport.ts#L17
|
|
4
|
+
* This adds additional error handling for React Native iOS.
|
|
5
|
+
* This particularly adds a close listener to handle cases where the WebSocket
|
|
6
|
+
* connection closes immediately after opening without emitting an error.
|
|
7
|
+
*/
|
|
8
|
+
import { Deserializer } from 'rsocket-core';
|
|
9
|
+
import { WebsocketDuplexConnection } from 'rsocket-websocket-client/dist/WebsocketDuplexConnection.js';
|
|
10
|
+
export class WebsocketClientTransport {
|
|
11
|
+
url;
|
|
12
|
+
factory;
|
|
13
|
+
constructor(options) {
|
|
14
|
+
this.url = options.url;
|
|
15
|
+
this.factory = options.wsCreator ?? ((url) => new WebSocket(url));
|
|
16
|
+
}
|
|
17
|
+
connect(multiplexerDemultiplexerFactory) {
|
|
18
|
+
return new Promise((resolve, reject) => {
|
|
19
|
+
const websocket = this.factory(this.url);
|
|
20
|
+
websocket.binaryType = 'arraybuffer';
|
|
21
|
+
let removeListeners;
|
|
22
|
+
const openListener = () => {
|
|
23
|
+
removeListeners();
|
|
24
|
+
resolve(new WebsocketDuplexConnection(websocket, new Deserializer(), multiplexerDemultiplexerFactory));
|
|
25
|
+
};
|
|
26
|
+
const errorListener = (ev) => {
|
|
27
|
+
removeListeners();
|
|
28
|
+
reject(ev.error);
|
|
29
|
+
};
|
|
30
|
+
/**
|
|
31
|
+
* In some cases, such as React Native iOS, the WebSocket connection may close immediately after opening
|
|
32
|
+
* without and error. In such cases, we need to handle the close event to reject the promise.
|
|
33
|
+
*/
|
|
34
|
+
const closeListener = () => {
|
|
35
|
+
removeListeners();
|
|
36
|
+
reject(new Error('WebSocket connection closed while opening'));
|
|
37
|
+
};
|
|
38
|
+
removeListeners = () => {
|
|
39
|
+
websocket.removeEventListener('open', openListener);
|
|
40
|
+
websocket.removeEventListener('error', errorListener);
|
|
41
|
+
websocket.removeEventListener('close', closeListener);
|
|
42
|
+
};
|
|
43
|
+
websocket.addEventListener('open', openListener);
|
|
44
|
+
websocket.addEventListener('error', errorListener);
|
|
45
|
+
websocket.addEventListener('close', closeListener);
|
|
46
|
+
});
|
|
47
|
+
}
|
|
48
|
+
}
|