@powersync/web 1.21.0 → 1.22.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/index.umd.js +1053 -690
- package/dist/index.umd.js.map +1 -1
- package/dist/worker/SharedSyncImplementation.umd.js +228 -172
- package/dist/worker/SharedSyncImplementation.umd.js.map +1 -1
- package/dist/worker/WASQLiteDB.umd.js +66 -65
- package/dist/worker/WASQLiteDB.umd.js.map +1 -1
- package/dist/worker/node_modules_bson_lib_bson_mjs.umd.js +565 -384
- package/dist/worker/node_modules_bson_lib_bson_mjs.umd.js.map +1 -1
- package/dist/worker/node_modules_crypto-browserify_index_js.umd.js +326 -189
- package/dist/worker/node_modules_crypto-browserify_index_js.umd.js.map +1 -1
- package/lib/package.json +2 -2
- package/lib/src/db/PowerSyncDatabase.d.ts +1 -2
- package/lib/src/db/PowerSyncDatabase.js +1 -12
- package/lib/src/db/sync/SharedWebStreamingSyncImplementation.js +15 -9
- package/lib/src/worker/sync/SharedSyncImplementation.d.ts +18 -11
- package/lib/src/worker/sync/SharedSyncImplementation.js +145 -94
- package/lib/src/worker/sync/SharedSyncImplementation.worker.js +11 -6
- package/lib/tsconfig.tsbuildinfo +1 -1
- package/package.json +3 -3
package/lib/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@powersync/web",
|
|
3
|
-
"version": "1.
|
|
3
|
+
"version": "1.22.0",
|
|
4
4
|
"description": "PowerSync web SDK. Sync Postgres, MongoDB or MySQL with SQLite in your web app",
|
|
5
5
|
"main": "lib/src/index.js",
|
|
6
6
|
"types": "lib/src/index.d.ts",
|
|
@@ -61,7 +61,7 @@
|
|
|
61
61
|
"license": "Apache-2.0",
|
|
62
62
|
"peerDependencies": {
|
|
63
63
|
"@journeyapps/wa-sqlite": "^1.2.4",
|
|
64
|
-
"@powersync/common": "workspace:^1.
|
|
64
|
+
"@powersync/common": "workspace:^1.32.0"
|
|
65
65
|
},
|
|
66
66
|
"dependencies": {
|
|
67
67
|
"@powersync/common": "workspace:*",
|
|
@@ -1,4 +1,4 @@
|
|
|
1
|
-
import { type BucketStorageAdapter, type PowerSyncBackendConnector, type PowerSyncCloseOptions, type
|
|
1
|
+
import { type BucketStorageAdapter, type PowerSyncBackendConnector, type PowerSyncCloseOptions, type RequiredAdditionalConnectionOptions, AbstractPowerSyncDatabase, DBAdapter, PowerSyncDatabaseOptions, PowerSyncDatabaseOptionsWithDBAdapter, PowerSyncDatabaseOptionsWithOpenFactory, PowerSyncDatabaseOptionsWithSettings, StreamingSyncImplementation } from '@powersync/common';
|
|
2
2
|
import { Mutex } from 'async-mutex';
|
|
3
3
|
import { ResolvedWebSQLOpenOptions, WebSQLFlags } from './adapters/web-sql-flags';
|
|
4
4
|
export interface WebPowerSyncFlags extends WebSQLFlags {
|
|
@@ -69,7 +69,6 @@ export declare class PowerSyncDatabase extends AbstractPowerSyncDatabase {
|
|
|
69
69
|
* multiple tabs are not enabled.
|
|
70
70
|
*/
|
|
71
71
|
close(options?: PowerSyncCloseOptions): Promise<void>;
|
|
72
|
-
connect(connector: PowerSyncBackendConnector, options?: PowerSyncConnectionOptions): Promise<void>;
|
|
73
72
|
protected generateBucketStorageAdapter(): BucketStorageAdapter;
|
|
74
73
|
protected runExclusive<T>(cb: () => Promise<T>): Promise<any>;
|
|
75
74
|
protected generateSyncStreamImplementation(connector: PowerSyncBackendConnector, options: RequiredAdditionalConnectionOptions): StreamingSyncImplementation;
|
|
@@ -81,21 +81,10 @@ export class PowerSyncDatabase extends AbstractPowerSyncDatabase {
|
|
|
81
81
|
disconnect: options.disconnect ?? !this.resolvedFlags.enableMultiTabs
|
|
82
82
|
});
|
|
83
83
|
}
|
|
84
|
-
connect(connector, options) {
|
|
85
|
-
/**
|
|
86
|
-
* Using React strict mode might cause calls to connect to fire multiple times
|
|
87
|
-
* Connect is wrapped inside a lock in order to prevent race conditions internally between multiple
|
|
88
|
-
* connection attempts.
|
|
89
|
-
*/
|
|
90
|
-
return this.runExclusive(() => {
|
|
91
|
-
this.options.logger?.debug('Attempting to connect to PowerSync instance');
|
|
92
|
-
return super.connect(connector, options);
|
|
93
|
-
});
|
|
94
|
-
}
|
|
95
84
|
generateBucketStorageAdapter() {
|
|
96
85
|
return new SqliteBucketStorage(this.database, AbstractPowerSyncDatabase.transactionMutex);
|
|
97
86
|
}
|
|
98
|
-
runExclusive(cb) {
|
|
87
|
+
async runExclusive(cb) {
|
|
99
88
|
if (this.resolvedFlags.ssrMode) {
|
|
100
89
|
return PowerSyncDatabase.SHARED_MUTEX.runExclusive(cb);
|
|
101
90
|
}
|
|
@@ -153,9 +153,6 @@ export class SharedWebStreamingSyncImplementation extends WebStreamingSyncImplem
|
|
|
153
153
|
*/
|
|
154
154
|
async connect(options) {
|
|
155
155
|
await this.waitForReady();
|
|
156
|
-
// This is needed since a new tab won't have any reference to the
|
|
157
|
-
// shared worker sync implementation since that is only created on the first call to `connect`.
|
|
158
|
-
await this.disconnect();
|
|
159
156
|
return this.syncManager.connect(options);
|
|
160
157
|
}
|
|
161
158
|
async disconnect() {
|
|
@@ -171,12 +168,21 @@ export class SharedWebStreamingSyncImplementation extends WebStreamingSyncImplem
|
|
|
171
168
|
}
|
|
172
169
|
async dispose() {
|
|
173
170
|
await this.waitForReady();
|
|
174
|
-
|
|
175
|
-
|
|
176
|
-
event
|
|
177
|
-
|
|
178
|
-
|
|
179
|
-
|
|
171
|
+
await new Promise((resolve) => {
|
|
172
|
+
// Listen for the close acknowledgment from the worker
|
|
173
|
+
this.messagePort.addEventListener('message', (event) => {
|
|
174
|
+
const payload = event.data;
|
|
175
|
+
if (payload?.event === SharedSyncClientEvent.CLOSE_ACK) {
|
|
176
|
+
resolve();
|
|
177
|
+
}
|
|
178
|
+
});
|
|
179
|
+
// Signal the shared worker that this client is closing its connection to the worker
|
|
180
|
+
const closeMessagePayload = {
|
|
181
|
+
event: SharedSyncClientEvent.CLOSE_CLIENT,
|
|
182
|
+
data: {}
|
|
183
|
+
};
|
|
184
|
+
this.messagePort.postMessage(closeMessagePayload);
|
|
185
|
+
});
|
|
180
186
|
// Release the proxy
|
|
181
187
|
this.syncManager[Comlink.releaseProxy]();
|
|
182
188
|
this.messagePort.close();
|
|
@@ -1,9 +1,11 @@
|
|
|
1
|
-
import { type
|
|
1
|
+
import { type ILogger, type ILogLevel, type PowerSyncConnectionOptions, type StreamingSyncImplementation, type StreamingSyncImplementationListener, type SyncStatusOptions, BaseObserver, ConnectionManager, DBAdapter, SyncStatus } from '@powersync/common';
|
|
2
|
+
import { Mutex } from 'async-mutex';
|
|
2
3
|
import * as Comlink from 'comlink';
|
|
3
4
|
import { WebStreamingSyncImplementation, WebStreamingSyncImplementationOptions } from '../../db/sync/WebStreamingSyncImplementation';
|
|
4
5
|
import { ResolvedWebSQLOpenOptions } from '../../db/adapters/web-sql-flags';
|
|
5
6
|
import { AbstractSharedSyncClientProvider } from './AbstractSharedSyncClientProvider';
|
|
6
7
|
/**
|
|
8
|
+
* @internal
|
|
7
9
|
* Manual message events for shared sync clients
|
|
8
10
|
*/
|
|
9
11
|
export declare enum SharedSyncClientEvent {
|
|
@@ -11,8 +13,12 @@ export declare enum SharedSyncClientEvent {
|
|
|
11
13
|
* This client requests the shared sync manager should
|
|
12
14
|
* close it's connection to the client.
|
|
13
15
|
*/
|
|
14
|
-
CLOSE_CLIENT = "close-client"
|
|
16
|
+
CLOSE_CLIENT = "close-client",
|
|
17
|
+
CLOSE_ACK = "close-ack"
|
|
15
18
|
}
|
|
19
|
+
/**
|
|
20
|
+
* @internal
|
|
21
|
+
*/
|
|
16
22
|
export type ManualSharedSyncPayload = {
|
|
17
23
|
event: SharedSyncClientEvent;
|
|
18
24
|
data: any;
|
|
@@ -51,7 +57,6 @@ export type RemoteOperationAbortController = {
|
|
|
51
57
|
*/
|
|
52
58
|
export declare class SharedSyncImplementation extends BaseObserver<SharedSyncImplementationListener> implements StreamingSyncImplementation {
|
|
53
59
|
protected ports: WrappedSyncPort[];
|
|
54
|
-
protected syncStreamClient: AbstractStreamingSyncImplementation | null;
|
|
55
60
|
protected isInitialized: Promise<void>;
|
|
56
61
|
protected statusListener?: () => void;
|
|
57
62
|
protected fetchCredentialsController?: RemoteOperationAbortController;
|
|
@@ -60,41 +65,43 @@ export declare class SharedSyncImplementation extends BaseObserver<SharedSyncImp
|
|
|
60
65
|
protected syncParams: SharedSyncInitOptions | null;
|
|
61
66
|
protected logger: ILogger;
|
|
62
67
|
protected lastConnectOptions: PowerSyncConnectionOptions | undefined;
|
|
68
|
+
protected portMutex: Mutex;
|
|
69
|
+
protected connectionManager: ConnectionManager;
|
|
63
70
|
syncStatus: SyncStatus;
|
|
64
71
|
broadCastLogger: ILogger;
|
|
65
72
|
constructor();
|
|
66
|
-
waitForStatus(status: SyncStatusOptions): Promise<void>;
|
|
67
|
-
waitUntilStatusMatches(predicate: (status: SyncStatus) => boolean): Promise<void>;
|
|
68
73
|
get lastSyncedAt(): Date | undefined;
|
|
69
74
|
get isConnected(): boolean;
|
|
75
|
+
waitForStatus(status: SyncStatusOptions): Promise<void>;
|
|
76
|
+
waitUntilStatusMatches(predicate: (status: SyncStatus) => boolean): Promise<void>;
|
|
70
77
|
waitForReady(): Promise<void>;
|
|
71
78
|
setLogLevel(level: ILogLevel): void;
|
|
72
79
|
/**
|
|
73
80
|
* Configures the DBAdapter connection and a streaming sync client.
|
|
74
81
|
*/
|
|
75
82
|
setParams(params: SharedSyncInitOptions): Promise<void>;
|
|
76
|
-
dispose(): Promise<void
|
|
83
|
+
dispose(): Promise<void>;
|
|
77
84
|
/**
|
|
78
85
|
* Connects to the PowerSync backend instance.
|
|
79
86
|
* Multiple tabs can safely call this in their initialization.
|
|
80
87
|
* The connection will simply be reconnected whenever a new tab
|
|
81
88
|
* connects.
|
|
82
89
|
*/
|
|
83
|
-
connect(options?: PowerSyncConnectionOptions): Promise<
|
|
84
|
-
disconnect(): Promise<
|
|
90
|
+
connect(options?: PowerSyncConnectionOptions): Promise<void>;
|
|
91
|
+
disconnect(): Promise<void>;
|
|
85
92
|
/**
|
|
86
93
|
* Adds a new client tab's message port to the list of connected ports
|
|
87
94
|
*/
|
|
88
|
-
addPort(port: MessagePort): void
|
|
95
|
+
addPort(port: MessagePort): Promise<void>;
|
|
89
96
|
/**
|
|
90
97
|
* Removes a message port client from this manager's managed
|
|
91
98
|
* clients.
|
|
92
99
|
*/
|
|
93
|
-
removePort(port: MessagePort): Promise<void>;
|
|
100
|
+
removePort(port: MessagePort): Promise<() => void>;
|
|
94
101
|
triggerCrudUpload(): void;
|
|
95
|
-
obtainLock<T>(lockOptions: LockOptions<T>): Promise<T>;
|
|
96
102
|
hasCompletedSync(): Promise<boolean>;
|
|
97
103
|
getWriteCheckpoint(): Promise<string>;
|
|
104
|
+
protected withSyncImplementation<T>(callback: (sync: StreamingSyncImplementation) => Promise<T>): Promise<T>;
|
|
98
105
|
protected generateStreamingImplementation(): WebStreamingSyncImplementation;
|
|
99
106
|
protected openInternalDB(): Promise<void>;
|
|
100
107
|
/**
|
|
@@ -1,13 +1,13 @@
|
|
|
1
|
-
import { AbortOperation, BaseObserver, createLogger, SqliteBucketStorage, SyncStatus } from '@powersync/common';
|
|
1
|
+
import { AbortOperation, BaseObserver, ConnectionManager, createLogger, SqliteBucketStorage, SyncStatus } from '@powersync/common';
|
|
2
2
|
import { Mutex } from 'async-mutex';
|
|
3
3
|
import * as Comlink from 'comlink';
|
|
4
4
|
import { WebRemote } from '../../db/sync/WebRemote';
|
|
5
5
|
import { WebStreamingSyncImplementation } from '../../db/sync/WebStreamingSyncImplementation';
|
|
6
6
|
import { LockedAsyncDatabaseAdapter } from '../../db/adapters/LockedAsyncDatabaseAdapter';
|
|
7
7
|
import { WorkerWrappedAsyncDatabaseConnection } from '../../db/adapters/WorkerWrappedAsyncDatabaseConnection';
|
|
8
|
-
import { getNavigatorLocks } from '../../shared/navigator';
|
|
9
8
|
import { BroadcastLogger } from './BroadcastLogger';
|
|
10
9
|
/**
|
|
10
|
+
* @internal
|
|
11
11
|
* Manual message events for shared sync clients
|
|
12
12
|
*/
|
|
13
13
|
export var SharedSyncClientEvent;
|
|
@@ -17,14 +17,20 @@ export var SharedSyncClientEvent;
|
|
|
17
17
|
* close it's connection to the client.
|
|
18
18
|
*/
|
|
19
19
|
SharedSyncClientEvent["CLOSE_CLIENT"] = "close-client";
|
|
20
|
+
SharedSyncClientEvent["CLOSE_ACK"] = "close-ack";
|
|
20
21
|
})(SharedSyncClientEvent || (SharedSyncClientEvent = {}));
|
|
22
|
+
/**
|
|
23
|
+
* HACK: The shared implementation wraps and provides its own
|
|
24
|
+
* PowerSyncBackendConnector when generating the streaming sync implementation.
|
|
25
|
+
* We provide this unused placeholder when connecting with the ConnectionManager.
|
|
26
|
+
*/
|
|
27
|
+
const CONNECTOR_PLACEHOLDER = {};
|
|
21
28
|
/**
|
|
22
29
|
* @internal
|
|
23
30
|
* Shared sync implementation which runs inside a shared webworker
|
|
24
31
|
*/
|
|
25
32
|
export class SharedSyncImplementation extends BaseObserver {
|
|
26
33
|
ports;
|
|
27
|
-
syncStreamClient;
|
|
28
34
|
isInitialized;
|
|
29
35
|
statusListener;
|
|
30
36
|
fetchCredentialsController;
|
|
@@ -33,6 +39,8 @@ export class SharedSyncImplementation extends BaseObserver {
|
|
|
33
39
|
syncParams;
|
|
34
40
|
logger;
|
|
35
41
|
lastConnectOptions;
|
|
42
|
+
portMutex;
|
|
43
|
+
connectionManager;
|
|
36
44
|
syncStatus;
|
|
37
45
|
broadCastLogger;
|
|
38
46
|
constructor() {
|
|
@@ -40,9 +48,9 @@ export class SharedSyncImplementation extends BaseObserver {
|
|
|
40
48
|
this.ports = [];
|
|
41
49
|
this.dbAdapter = null;
|
|
42
50
|
this.syncParams = null;
|
|
43
|
-
this.syncStreamClient = null;
|
|
44
51
|
this.logger = createLogger('shared-sync');
|
|
45
52
|
this.lastConnectOptions = undefined;
|
|
53
|
+
this.portMutex = new Mutex();
|
|
46
54
|
this.isInitialized = new Promise((resolve) => {
|
|
47
55
|
const callback = this.registerListener({
|
|
48
56
|
initialized: () => {
|
|
@@ -53,20 +61,43 @@ export class SharedSyncImplementation extends BaseObserver {
|
|
|
53
61
|
});
|
|
54
62
|
this.syncStatus = new SyncStatus({});
|
|
55
63
|
this.broadCastLogger = new BroadcastLogger(this.ports);
|
|
56
|
-
|
|
57
|
-
|
|
58
|
-
|
|
59
|
-
|
|
60
|
-
|
|
61
|
-
|
|
62
|
-
|
|
63
|
-
|
|
64
|
+
this.connectionManager = new ConnectionManager({
|
|
65
|
+
createSyncImplementation: async () => {
|
|
66
|
+
return this.portMutex.runExclusive(async () => {
|
|
67
|
+
await this.waitForReady();
|
|
68
|
+
if (!this.dbAdapter) {
|
|
69
|
+
await this.openInternalDB();
|
|
70
|
+
}
|
|
71
|
+
const sync = this.generateStreamingImplementation();
|
|
72
|
+
const onDispose = sync.registerListener({
|
|
73
|
+
statusChanged: (status) => {
|
|
74
|
+
this.updateAllStatuses(status.toJSON());
|
|
75
|
+
}
|
|
76
|
+
});
|
|
77
|
+
return {
|
|
78
|
+
sync,
|
|
79
|
+
onDispose
|
|
80
|
+
};
|
|
81
|
+
});
|
|
82
|
+
},
|
|
83
|
+
logger: this.logger
|
|
84
|
+
});
|
|
64
85
|
}
|
|
65
86
|
get lastSyncedAt() {
|
|
66
|
-
return this.
|
|
87
|
+
return this.connectionManager.syncStreamImplementation?.lastSyncedAt;
|
|
67
88
|
}
|
|
68
89
|
get isConnected() {
|
|
69
|
-
return this.
|
|
90
|
+
return this.connectionManager.syncStreamImplementation?.isConnected ?? false;
|
|
91
|
+
}
|
|
92
|
+
async waitForStatus(status) {
|
|
93
|
+
return this.withSyncImplementation(async (sync) => {
|
|
94
|
+
return sync.waitForStatus(status);
|
|
95
|
+
});
|
|
96
|
+
}
|
|
97
|
+
async waitUntilStatusMatches(predicate) {
|
|
98
|
+
return this.withSyncImplementation(async (sync) => {
|
|
99
|
+
return sync.waitUntilStatusMatches(predicate);
|
|
100
|
+
});
|
|
70
101
|
}
|
|
71
102
|
async waitForReady() {
|
|
72
103
|
return this.isInitialized;
|
|
@@ -79,25 +110,34 @@ export class SharedSyncImplementation extends BaseObserver {
|
|
|
79
110
|
* Configures the DBAdapter connection and a streaming sync client.
|
|
80
111
|
*/
|
|
81
112
|
async setParams(params) {
|
|
82
|
-
|
|
83
|
-
|
|
84
|
-
|
|
85
|
-
|
|
86
|
-
|
|
87
|
-
|
|
88
|
-
|
|
89
|
-
|
|
90
|
-
|
|
91
|
-
//
|
|
92
|
-
this.
|
|
93
|
-
|
|
94
|
-
|
|
95
|
-
|
|
113
|
+
await this.portMutex.runExclusive(async () => {
|
|
114
|
+
if (this.syncParams) {
|
|
115
|
+
// Cannot modify already existing sync implementation params
|
|
116
|
+
// But we can ask for a DB adapter, if required, at this point.
|
|
117
|
+
if (!this.dbAdapter) {
|
|
118
|
+
await this.openInternalDB();
|
|
119
|
+
}
|
|
120
|
+
return;
|
|
121
|
+
}
|
|
122
|
+
// First time setting params
|
|
123
|
+
this.syncParams = params;
|
|
124
|
+
if (params.streamOptions?.flags?.broadcastLogs) {
|
|
125
|
+
this.logger = this.broadCastLogger;
|
|
126
|
+
}
|
|
127
|
+
self.onerror = (event) => {
|
|
128
|
+
// Share any uncaught events on the broadcast logger
|
|
129
|
+
this.logger.error('Uncaught exception in PowerSync shared sync worker', event);
|
|
130
|
+
};
|
|
131
|
+
if (!this.dbAdapter) {
|
|
132
|
+
await this.openInternalDB();
|
|
133
|
+
}
|
|
134
|
+
this.iterateListeners((l) => l.initialized?.());
|
|
135
|
+
});
|
|
96
136
|
}
|
|
97
137
|
async dispose() {
|
|
98
138
|
await this.waitForReady();
|
|
99
139
|
this.statusListener?.();
|
|
100
|
-
return this.
|
|
140
|
+
return this.connectionManager.close();
|
|
101
141
|
}
|
|
102
142
|
/**
|
|
103
143
|
* Connects to the PowerSync backend instance.
|
|
@@ -106,99 +146,110 @@ export class SharedSyncImplementation extends BaseObserver {
|
|
|
106
146
|
* connects.
|
|
107
147
|
*/
|
|
108
148
|
async connect(options) {
|
|
109
|
-
|
|
110
|
-
|
|
111
|
-
return getNavigatorLocks().request('shared-sync-connect', async () => {
|
|
112
|
-
if (!this.dbAdapter) {
|
|
113
|
-
await this.openInternalDB();
|
|
114
|
-
}
|
|
115
|
-
this.syncStreamClient = this.generateStreamingImplementation();
|
|
116
|
-
this.lastConnectOptions = options;
|
|
117
|
-
this.syncStreamClient.registerListener({
|
|
118
|
-
statusChanged: (status) => {
|
|
119
|
-
this.updateAllStatuses(status.toJSON());
|
|
120
|
-
}
|
|
121
|
-
});
|
|
122
|
-
await this.syncStreamClient.connect(options);
|
|
123
|
-
});
|
|
149
|
+
this.lastConnectOptions = options;
|
|
150
|
+
return this.connectionManager.connect(CONNECTOR_PLACEHOLDER, options);
|
|
124
151
|
}
|
|
125
152
|
async disconnect() {
|
|
126
|
-
|
|
127
|
-
// This effectively queues connect and disconnect calls. Ensuring multiple tabs' requests are synchronized
|
|
128
|
-
return getNavigatorLocks().request('shared-sync-connect', async () => {
|
|
129
|
-
await this.syncStreamClient?.disconnect();
|
|
130
|
-
await this.syncStreamClient?.dispose();
|
|
131
|
-
this.syncStreamClient = null;
|
|
132
|
-
});
|
|
153
|
+
return this.connectionManager.disconnect();
|
|
133
154
|
}
|
|
134
155
|
/**
|
|
135
156
|
* Adds a new client tab's message port to the list of connected ports
|
|
136
157
|
*/
|
|
137
|
-
addPort(port) {
|
|
138
|
-
|
|
139
|
-
|
|
140
|
-
|
|
141
|
-
|
|
142
|
-
|
|
143
|
-
|
|
144
|
-
|
|
145
|
-
|
|
146
|
-
|
|
147
|
-
|
|
158
|
+
async addPort(port) {
|
|
159
|
+
await this.portMutex.runExclusive(() => {
|
|
160
|
+
const portProvider = {
|
|
161
|
+
port,
|
|
162
|
+
clientProvider: Comlink.wrap(port)
|
|
163
|
+
};
|
|
164
|
+
this.ports.push(portProvider);
|
|
165
|
+
// Give the newly connected client the latest status
|
|
166
|
+
const status = this.connectionManager.syncStreamImplementation?.syncStatus;
|
|
167
|
+
if (status) {
|
|
168
|
+
portProvider.clientProvider.statusChanged(status.toJSON());
|
|
169
|
+
}
|
|
170
|
+
});
|
|
148
171
|
}
|
|
149
172
|
/**
|
|
150
173
|
* Removes a message port client from this manager's managed
|
|
151
174
|
* clients.
|
|
152
175
|
*/
|
|
153
176
|
async removePort(port) {
|
|
154
|
-
|
|
155
|
-
if
|
|
156
|
-
|
|
157
|
-
|
|
158
|
-
|
|
159
|
-
|
|
160
|
-
|
|
161
|
-
|
|
162
|
-
/**
|
|
163
|
-
* The port might currently be in use. Any active functions might
|
|
164
|
-
* not resolve. Abort them here.
|
|
165
|
-
*/
|
|
166
|
-
[this.fetchCredentialsController, this.uploadDataController].forEach((abortController) => {
|
|
167
|
-
if (abortController?.activePort.port == port) {
|
|
168
|
-
abortController.controller.abort(new AbortOperation('Closing pending requests after client port is removed'));
|
|
177
|
+
// Remove the port within a mutex context.
|
|
178
|
+
// Warns if the port is not found. This should not happen in practice.
|
|
179
|
+
// We return early if the port is not found.
|
|
180
|
+
const { trackedPort, shouldReconnect } = await this.portMutex.runExclusive(async () => {
|
|
181
|
+
const index = this.ports.findIndex((p) => p.port == port);
|
|
182
|
+
if (index < 0) {
|
|
183
|
+
this.logger.warn(`Could not remove port ${port} since it is not present in active ports.`);
|
|
184
|
+
return {};
|
|
169
185
|
}
|
|
186
|
+
const trackedPort = this.ports[index];
|
|
187
|
+
// Remove from the list of active ports
|
|
188
|
+
this.ports.splice(index, 1);
|
|
189
|
+
/**
|
|
190
|
+
* The port might currently be in use. Any active functions might
|
|
191
|
+
* not resolve. Abort them here.
|
|
192
|
+
*/
|
|
193
|
+
[this.fetchCredentialsController, this.uploadDataController].forEach((abortController) => {
|
|
194
|
+
if (abortController?.activePort.port == port) {
|
|
195
|
+
abortController.controller.abort(new AbortOperation('Closing pending requests after client port is removed'));
|
|
196
|
+
}
|
|
197
|
+
});
|
|
198
|
+
const shouldReconnect = !!this.connectionManager.syncStreamImplementation && this.ports.length > 0;
|
|
199
|
+
return {
|
|
200
|
+
shouldReconnect,
|
|
201
|
+
trackedPort
|
|
202
|
+
};
|
|
170
203
|
});
|
|
171
|
-
|
|
204
|
+
if (!trackedPort) {
|
|
205
|
+
// We could not find the port to remove
|
|
206
|
+
return () => { };
|
|
207
|
+
}
|
|
172
208
|
if (this.dbAdapter && this.dbAdapter == trackedPort.db) {
|
|
173
209
|
if (shouldReconnect) {
|
|
174
|
-
await this.disconnect();
|
|
210
|
+
await this.connectionManager.disconnect();
|
|
175
211
|
}
|
|
176
212
|
// Clearing the adapter will result in a new one being opened in connect
|
|
177
213
|
this.dbAdapter = null;
|
|
178
214
|
if (shouldReconnect) {
|
|
179
|
-
await this.connect(this.lastConnectOptions);
|
|
215
|
+
await this.connectionManager.connect(CONNECTOR_PLACEHOLDER, this.lastConnectOptions);
|
|
180
216
|
}
|
|
181
217
|
}
|
|
182
218
|
if (trackedPort.db) {
|
|
183
|
-
trackedPort.db.close();
|
|
219
|
+
await trackedPort.db.close();
|
|
184
220
|
}
|
|
185
221
|
// Release proxy
|
|
186
|
-
trackedPort.clientProvider[Comlink.releaseProxy]();
|
|
222
|
+
return () => trackedPort.clientProvider[Comlink.releaseProxy]();
|
|
187
223
|
}
|
|
188
224
|
triggerCrudUpload() {
|
|
189
|
-
this.
|
|
190
|
-
|
|
191
|
-
|
|
192
|
-
await this.waitForReady();
|
|
193
|
-
return this.syncStreamClient.obtainLock(lockOptions);
|
|
225
|
+
this.withSyncImplementation(async (sync) => {
|
|
226
|
+
sync.triggerCrudUpload();
|
|
227
|
+
});
|
|
194
228
|
}
|
|
195
229
|
async hasCompletedSync() {
|
|
196
|
-
|
|
197
|
-
|
|
230
|
+
return this.withSyncImplementation(async (sync) => {
|
|
231
|
+
return sync.hasCompletedSync();
|
|
232
|
+
});
|
|
198
233
|
}
|
|
199
234
|
async getWriteCheckpoint() {
|
|
235
|
+
return this.withSyncImplementation(async (sync) => {
|
|
236
|
+
return sync.getWriteCheckpoint();
|
|
237
|
+
});
|
|
238
|
+
}
|
|
239
|
+
async withSyncImplementation(callback) {
|
|
200
240
|
await this.waitForReady();
|
|
201
|
-
|
|
241
|
+
if (this.connectionManager.syncStreamImplementation) {
|
|
242
|
+
return callback(this.connectionManager.syncStreamImplementation);
|
|
243
|
+
}
|
|
244
|
+
const sync = await new Promise((resolve) => {
|
|
245
|
+
const dispose = this.connectionManager.registerListener({
|
|
246
|
+
syncStreamCreated: (sync) => {
|
|
247
|
+
resolve(sync);
|
|
248
|
+
dispose?.();
|
|
249
|
+
}
|
|
250
|
+
});
|
|
251
|
+
});
|
|
252
|
+
return callback(sync);
|
|
202
253
|
}
|
|
203
254
|
generateStreamingImplementation() {
|
|
204
255
|
// This should only be called after initialization has completed
|
|
@@ -301,13 +352,13 @@ export class SharedSyncImplementation extends BaseObserver {
|
|
|
301
352
|
* A function only used for unit tests which updates the internal
|
|
302
353
|
* sync stream client and all tab client's sync status
|
|
303
354
|
*/
|
|
304
|
-
_testUpdateAllStatuses(status) {
|
|
305
|
-
if (!this.
|
|
355
|
+
async _testUpdateAllStatuses(status) {
|
|
356
|
+
if (!this.connectionManager.syncStreamImplementation) {
|
|
306
357
|
// This is just for testing purposes
|
|
307
|
-
this.
|
|
358
|
+
this.connectionManager.syncStreamImplementation = this.generateStreamingImplementation();
|
|
308
359
|
}
|
|
309
360
|
// Only assigning, don't call listeners for this test
|
|
310
|
-
this.
|
|
361
|
+
this.connectionManager.syncStreamImplementation.syncStatus = new SyncStatus(status);
|
|
311
362
|
this.updateAllStatuses(status);
|
|
312
363
|
}
|
|
313
364
|
}
|
|
@@ -1,22 +1,27 @@
|
|
|
1
|
-
import * as Comlink from 'comlink';
|
|
2
|
-
import { SharedSyncImplementation, SharedSyncClientEvent } from './SharedSyncImplementation';
|
|
3
1
|
import { createBaseLogger } from '@powersync/common';
|
|
2
|
+
import * as Comlink from 'comlink';
|
|
3
|
+
import { SharedSyncClientEvent, SharedSyncImplementation } from './SharedSyncImplementation';
|
|
4
4
|
const _self = self;
|
|
5
5
|
const logger = createBaseLogger();
|
|
6
6
|
logger.useDefaults();
|
|
7
7
|
const sharedSyncImplementation = new SharedSyncImplementation();
|
|
8
|
-
_self.onconnect = function (event) {
|
|
8
|
+
_self.onconnect = async function (event) {
|
|
9
9
|
const port = event.ports[0];
|
|
10
10
|
/**
|
|
11
11
|
* Adds an extra listener which can remove this port
|
|
12
12
|
* from the list of monitored ports.
|
|
13
13
|
*/
|
|
14
|
-
port.addEventListener('message', (event) => {
|
|
14
|
+
port.addEventListener('message', async (event) => {
|
|
15
15
|
const payload = event.data;
|
|
16
16
|
if (payload?.event == SharedSyncClientEvent.CLOSE_CLIENT) {
|
|
17
|
-
sharedSyncImplementation.removePort(port);
|
|
17
|
+
const release = await sharedSyncImplementation.removePort(port);
|
|
18
|
+
port.postMessage({
|
|
19
|
+
event: SharedSyncClientEvent.CLOSE_ACK,
|
|
20
|
+
data: {}
|
|
21
|
+
});
|
|
22
|
+
release?.();
|
|
18
23
|
}
|
|
19
24
|
});
|
|
25
|
+
await sharedSyncImplementation.addPort(port);
|
|
20
26
|
Comlink.expose(sharedSyncImplementation, port);
|
|
21
|
-
sharedSyncImplementation.addPort(port);
|
|
22
27
|
};
|