@powersync/web 1.30.0 → 1.32.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/_journeyapps_wa-sqlite-_journeyapps_wa-sqlite_src_examples_AccessHandlePoolVFS_js-_journeyapp-89f0ba.index.umd.js +1867 -0
- package/dist/_journeyapps_wa-sqlite-_journeyapps_wa-sqlite_src_examples_AccessHandlePoolVFS_js-_journeyapp-89f0ba.index.umd.js.map +1 -0
- package/dist/_journeyapps_wa-sqlite_src_examples_AccessHandlePoolVFS_js-_journeyapps_wa-sqlite_src_example-2530150.index.umd.js +555 -0
- package/dist/_journeyapps_wa-sqlite_src_examples_AccessHandlePoolVFS_js-_journeyapps_wa-sqlite_src_example-2530150.index.umd.js.map +1 -0
- package/dist/_journeyapps_wa-sqlite_src_examples_AccessHandlePoolVFS_js-_journeyapps_wa-sqlite_src_example-2530151.index.umd.js +555 -0
- package/dist/_journeyapps_wa-sqlite_src_examples_AccessHandlePoolVFS_js-_journeyapps_wa-sqlite_src_example-2530151.index.umd.js.map +1 -0
- package/dist/index.umd.js +5022 -38504
- package/dist/index.umd.js.map +1 -1
- package/dist/worker/SharedSyncImplementation.umd.js +819 -2220
- package/dist/worker/SharedSyncImplementation.umd.js.map +1 -1
- package/dist/worker/WASQLiteDB.umd.js +524 -2121
- package/dist/worker/WASQLiteDB.umd.js.map +1 -1
- package/dist/worker/{node_modules_bson_lib_bson_mjs.umd.js → node_modules_pnpm_bson_6_10_4_node_modules_bson_lib_bson_mjs.umd.js} +8 -8
- package/dist/worker/node_modules_pnpm_bson_6_10_4_node_modules_bson_lib_bson_mjs.umd.js.map +1 -0
- package/dist/worker/node_modules_pnpm_journeyapps_wa-sqlite_1_4_1_node_modules_journeyapps_wa-sqlite_dist_mc-wa-s-3a94cf.umd.js +44 -0
- package/dist/worker/node_modules_pnpm_journeyapps_wa-sqlite_1_4_1_node_modules_journeyapps_wa-sqlite_dist_mc-wa-s-3a94cf.umd.js.map +1 -0
- package/dist/worker/node_modules_pnpm_journeyapps_wa-sqlite_1_4_1_node_modules_journeyapps_wa-sqlite_dist_mc-wa-s-868779.umd.js +44 -0
- package/dist/worker/node_modules_pnpm_journeyapps_wa-sqlite_1_4_1_node_modules_journeyapps_wa-sqlite_dist_mc-wa-s-868779.umd.js.map +1 -0
- package/dist/worker/node_modules_pnpm_journeyapps_wa-sqlite_1_4_1_node_modules_journeyapps_wa-sqlite_dist_wa-sqli-f60d0d.umd.js +44 -0
- package/dist/worker/node_modules_pnpm_journeyapps_wa-sqlite_1_4_1_node_modules_journeyapps_wa-sqlite_dist_wa-sqli-f60d0d.umd.js.map +1 -0
- package/dist/worker/node_modules_pnpm_journeyapps_wa-sqlite_1_4_1_node_modules_journeyapps_wa-sqlite_dist_wa-sqlite_mjs.umd.js +44 -0
- package/dist/worker/node_modules_pnpm_journeyapps_wa-sqlite_1_4_1_node_modules_journeyapps_wa-sqlite_dist_wa-sqlite_mjs.umd.js.map +1 -0
- package/dist/worker/{node_modules_journeyapps_wa-sqlite_src_examples_IDBBatchAtomicVFS_js.umd.js → node_modules_pnpm_journeyapps_wa-sqlite_1_4_1_node_modules_journeyapps_wa-sqlite_src_examples-0d2437.umd.js} +32 -32
- package/dist/worker/node_modules_pnpm_journeyapps_wa-sqlite_1_4_1_node_modules_journeyapps_wa-sqlite_src_examples-0d2437.umd.js.map +1 -0
- package/dist/worker/{node_modules_journeyapps_wa-sqlite_src_examples_OPFSCoopSyncVFS_js.umd.js → node_modules_pnpm_journeyapps_wa-sqlite_1_4_1_node_modules_journeyapps_wa-sqlite_src_examples-1d4e74.umd.js} +24 -24
- package/dist/worker/node_modules_pnpm_journeyapps_wa-sqlite_1_4_1_node_modules_journeyapps_wa-sqlite_src_examples-1d4e74.umd.js.map +1 -0
- package/dist/worker/{node_modules_journeyapps_wa-sqlite_src_examples_AccessHandlePoolVFS_js.umd.js → node_modules_pnpm_journeyapps_wa-sqlite_1_4_1_node_modules_journeyapps_wa-sqlite_src_examples-3622cf.umd.js} +24 -24
- package/dist/worker/node_modules_pnpm_journeyapps_wa-sqlite_1_4_1_node_modules_journeyapps_wa-sqlite_src_examples-3622cf.umd.js.map +1 -0
- package/lib/package.json +27 -23
- package/lib/src/db/NavigatorTriggerClaimManager.d.ts +6 -0
- package/lib/src/db/NavigatorTriggerClaimManager.js +20 -0
- package/lib/src/db/PowerSyncDatabase.d.ts +5 -2
- package/lib/src/db/PowerSyncDatabase.js +49 -11
- package/lib/src/db/adapters/AbstractWebPowerSyncDatabaseOpenFactory.d.ts +1 -1
- package/lib/src/db/adapters/AbstractWebPowerSyncDatabaseOpenFactory.js +1 -1
- package/lib/src/db/adapters/AbstractWebSQLOpenFactory.d.ts +2 -2
- package/lib/src/db/adapters/AbstractWebSQLOpenFactory.js +2 -2
- package/lib/src/db/adapters/AsyncDatabaseConnection.d.ts +1 -1
- package/lib/src/db/adapters/LockedAsyncDatabaseAdapter.d.ts +21 -4
- package/lib/src/db/adapters/LockedAsyncDatabaseAdapter.js +116 -22
- package/lib/src/db/adapters/WebDBAdapter.d.ts +5 -2
- package/lib/src/db/adapters/WorkerWrappedAsyncDatabaseConnection.d.ts +7 -3
- package/lib/src/db/adapters/WorkerWrappedAsyncDatabaseConnection.js +14 -7
- package/lib/src/db/adapters/wa-sqlite/InternalWASQLiteDBAdapter.d.ts +12 -0
- package/lib/src/db/adapters/wa-sqlite/InternalWASQLiteDBAdapter.js +19 -0
- package/lib/src/db/adapters/wa-sqlite/WASQLiteConnection.d.ts +2 -2
- package/lib/src/db/adapters/wa-sqlite/WASQLiteConnection.js +11 -2
- package/lib/src/db/adapters/wa-sqlite/WASQLiteDBAdapter.d.ts +4 -4
- package/lib/src/db/adapters/wa-sqlite/WASQLiteDBAdapter.js +6 -6
- package/lib/src/db/adapters/wa-sqlite/WASQLiteOpenFactory.d.ts +5 -5
- package/lib/src/db/adapters/wa-sqlite/WASQLiteOpenFactory.js +7 -7
- package/lib/src/db/adapters/wa-sqlite/WASQLitePowerSyncDatabaseOpenFactory.d.ts +1 -1
- package/lib/src/db/adapters/wa-sqlite/WASQLitePowerSyncDatabaseOpenFactory.js +3 -3
- package/lib/src/db/sync/SharedWebStreamingSyncImplementation.d.ts +6 -9
- package/lib/src/db/sync/SharedWebStreamingSyncImplementation.js +54 -38
- package/lib/src/db/sync/WebRemote.js +1 -1
- package/lib/src/db/sync/WebStreamingSyncImplementation.d.ts +1 -1
- package/lib/src/db/sync/WebStreamingSyncImplementation.js +1 -1
- package/lib/src/index.d.ts +12 -12
- package/lib/src/index.js +12 -12
- package/lib/src/worker/db/SharedWASQLiteConnection.d.ts +2 -2
- package/lib/src/worker/db/WASQLiteDB.worker.js +3 -3
- package/lib/src/worker/db/WorkerWASQLiteConnection.d.ts +2 -2
- package/lib/src/worker/db/WorkerWASQLiteConnection.js +1 -1
- package/lib/src/worker/db/open-worker-database.d.ts +2 -2
- package/lib/src/worker/db/open-worker-database.js +1 -1
- package/lib/src/worker/sync/BroadcastLogger.d.ts +1 -1
- package/lib/src/worker/sync/SharedSyncImplementation.d.ts +21 -11
- package/lib/src/worker/sync/SharedSyncImplementation.js +209 -113
- package/lib/src/worker/sync/SharedSyncImplementation.worker.js +3 -3
- package/lib/src/worker/sync/WorkerClient.d.ts +4 -5
- package/lib/src/worker/sync/WorkerClient.js +8 -10
- package/lib/tsconfig.tsbuildinfo +1 -1
- package/package.json +23 -19
- package/src/db/NavigatorTriggerClaimManager.ts +23 -0
- package/src/db/PowerSyncDatabase.ts +64 -22
- package/src/db/adapters/AbstractWebPowerSyncDatabaseOpenFactory.ts +1 -1
- package/src/db/adapters/AbstractWebSQLOpenFactory.ts +3 -3
- package/src/db/adapters/AsyncDatabaseConnection.ts +1 -1
- package/src/db/adapters/LockedAsyncDatabaseAdapter.ts +138 -33
- package/src/db/adapters/WebDBAdapter.ts +6 -2
- package/src/db/adapters/WorkerWrappedAsyncDatabaseConnection.ts +20 -8
- package/src/db/adapters/wa-sqlite/InternalWASQLiteDBAdapter.ts +23 -0
- package/src/db/adapters/wa-sqlite/WASQLiteConnection.ts +13 -5
- package/src/db/adapters/wa-sqlite/WASQLiteDBAdapter.ts +8 -8
- package/src/db/adapters/wa-sqlite/WASQLiteOpenFactory.ts +9 -9
- package/src/db/adapters/wa-sqlite/WASQLitePowerSyncDatabaseOpenFactory.ts +3 -3
- package/src/db/sync/SharedWebStreamingSyncImplementation.ts +69 -51
- package/src/db/sync/WebRemote.ts +1 -1
- package/src/db/sync/WebStreamingSyncImplementation.ts +2 -2
- package/src/index.ts +12 -12
- package/src/worker/db/SharedWASQLiteConnection.ts +2 -2
- package/src/worker/db/WASQLiteDB.worker.ts +5 -6
- package/src/worker/db/WorkerWASQLiteConnection.ts +2 -2
- package/src/worker/db/open-worker-database.ts +2 -2
- package/src/worker/sync/BroadcastLogger.ts +1 -1
- package/src/worker/sync/SharedSyncImplementation.ts +241 -126
- package/src/worker/sync/SharedSyncImplementation.worker.ts +3 -3
- package/src/worker/sync/WorkerClient.ts +10 -14
- package/dist/worker/node_modules_bson_lib_bson_mjs.umd.js.map +0 -1
- package/dist/worker/node_modules_journeyapps_wa-sqlite_dist_mc-wa-sqlite-async_mjs.umd.js +0 -44
- package/dist/worker/node_modules_journeyapps_wa-sqlite_dist_mc-wa-sqlite-async_mjs.umd.js.map +0 -1
- package/dist/worker/node_modules_journeyapps_wa-sqlite_dist_mc-wa-sqlite_mjs.umd.js +0 -44
- package/dist/worker/node_modules_journeyapps_wa-sqlite_dist_mc-wa-sqlite_mjs.umd.js.map +0 -1
- package/dist/worker/node_modules_journeyapps_wa-sqlite_dist_wa-sqlite-async_mjs.umd.js +0 -44
- package/dist/worker/node_modules_journeyapps_wa-sqlite_dist_wa-sqlite-async_mjs.umd.js.map +0 -1
- package/dist/worker/node_modules_journeyapps_wa-sqlite_dist_wa-sqlite_mjs.umd.js +0 -44
- package/dist/worker/node_modules_journeyapps_wa-sqlite_dist_wa-sqlite_mjs.umd.js.map +0 -1
- package/dist/worker/node_modules_journeyapps_wa-sqlite_src_examples_AccessHandlePoolVFS_js.umd.js.map +0 -1
- package/dist/worker/node_modules_journeyapps_wa-sqlite_src_examples_IDBBatchAtomicVFS_js.umd.js.map +0 -1
- package/dist/worker/node_modules_journeyapps_wa-sqlite_src_examples_OPFSCoopSyncVFS_js.umd.js.map +0 -1
- /package/bin/{powersync.js → powersync.cjs} +0 -0
|
@@ -1,11 +1,11 @@
|
|
|
1
|
-
import { AbortOperation, BaseObserver, ConnectionManager,
|
|
1
|
+
import { AbortOperation, BaseObserver, ConnectionManager, SqliteBucketStorage, SyncStatus, createLogger } from '@powersync/common';
|
|
2
2
|
import { Mutex } from 'async-mutex';
|
|
3
3
|
import * as Comlink from 'comlink';
|
|
4
|
-
import { WebRemote } from '../../db/sync/WebRemote';
|
|
5
|
-
import { WebStreamingSyncImplementation } from '../../db/sync/WebStreamingSyncImplementation';
|
|
6
|
-
import { LockedAsyncDatabaseAdapter } from '../../db/adapters/LockedAsyncDatabaseAdapter';
|
|
7
|
-
import { WorkerWrappedAsyncDatabaseConnection } from '../../db/adapters/WorkerWrappedAsyncDatabaseConnection';
|
|
8
|
-
import { BroadcastLogger } from './BroadcastLogger';
|
|
4
|
+
import { WebRemote } from '../../db/sync/WebRemote.js';
|
|
5
|
+
import { WebStreamingSyncImplementation } from '../../db/sync/WebStreamingSyncImplementation.js';
|
|
6
|
+
import { LockedAsyncDatabaseAdapter } from '../../db/adapters/LockedAsyncDatabaseAdapter.js';
|
|
7
|
+
import { WorkerWrappedAsyncDatabaseConnection } from '../../db/adapters/WorkerWrappedAsyncDatabaseConnection.js';
|
|
8
|
+
import { BroadcastLogger } from './BroadcastLogger.js';
|
|
9
9
|
/**
|
|
10
10
|
* @internal
|
|
11
11
|
* Manual message events for shared sync clients
|
|
@@ -35,7 +35,6 @@ export class SharedSyncImplementation extends BaseObserver {
|
|
|
35
35
|
statusListener;
|
|
36
36
|
fetchCredentialsController;
|
|
37
37
|
uploadDataController;
|
|
38
|
-
dbAdapter;
|
|
39
38
|
syncParams;
|
|
40
39
|
logger;
|
|
41
40
|
lastConnectOptions;
|
|
@@ -44,10 +43,10 @@ export class SharedSyncImplementation extends BaseObserver {
|
|
|
44
43
|
connectionManager;
|
|
45
44
|
syncStatus;
|
|
46
45
|
broadCastLogger;
|
|
46
|
+
distributedDB;
|
|
47
47
|
constructor() {
|
|
48
48
|
super();
|
|
49
49
|
this.ports = [];
|
|
50
|
-
this.dbAdapter = null;
|
|
51
50
|
this.syncParams = null;
|
|
52
51
|
this.logger = createLogger('shared-sync');
|
|
53
52
|
this.lastConnectOptions = undefined;
|
|
@@ -60,26 +59,23 @@ export class SharedSyncImplementation extends BaseObserver {
|
|
|
60
59
|
}
|
|
61
60
|
});
|
|
62
61
|
});
|
|
62
|
+
// Should be configured once we get params
|
|
63
|
+
this.distributedDB = null;
|
|
63
64
|
this.syncStatus = new SyncStatus({});
|
|
64
65
|
this.broadCastLogger = new BroadcastLogger(this.ports);
|
|
65
66
|
this.connectionManager = new ConnectionManager({
|
|
66
67
|
createSyncImplementation: async () => {
|
|
67
|
-
|
|
68
|
-
|
|
69
|
-
|
|
70
|
-
|
|
68
|
+
await this.waitForReady();
|
|
69
|
+
const sync = this.generateStreamingImplementation();
|
|
70
|
+
const onDispose = sync.registerListener({
|
|
71
|
+
statusChanged: (status) => {
|
|
72
|
+
this.updateAllStatuses(status.toJSON());
|
|
71
73
|
}
|
|
72
|
-
const sync = this.generateStreamingImplementation();
|
|
73
|
-
const onDispose = sync.registerListener({
|
|
74
|
-
statusChanged: (status) => {
|
|
75
|
-
this.updateAllStatuses(status.toJSON());
|
|
76
|
-
}
|
|
77
|
-
});
|
|
78
|
-
return {
|
|
79
|
-
sync,
|
|
80
|
-
onDispose
|
|
81
|
-
};
|
|
82
74
|
});
|
|
75
|
+
return {
|
|
76
|
+
sync,
|
|
77
|
+
onDispose
|
|
78
|
+
};
|
|
83
79
|
},
|
|
84
80
|
logger: this.logger
|
|
85
81
|
});
|
|
@@ -90,6 +86,30 @@ export class SharedSyncImplementation extends BaseObserver {
|
|
|
90
86
|
get isConnected() {
|
|
91
87
|
return this.connectionManager.syncStreamImplementation?.isConnected ?? false;
|
|
92
88
|
}
|
|
89
|
+
/**
|
|
90
|
+
* Gets the last client port which we know is safe from unexpected closes.
|
|
91
|
+
*/
|
|
92
|
+
async getLastWrappedPort() {
|
|
93
|
+
// Find the last port which is not closing
|
|
94
|
+
return await this.portMutex.runExclusive(() => {
|
|
95
|
+
for (let i = this.ports.length - 1; i >= 0; i--) {
|
|
96
|
+
if (!this.ports[i].isClosing) {
|
|
97
|
+
return this.ports[i];
|
|
98
|
+
}
|
|
99
|
+
}
|
|
100
|
+
return;
|
|
101
|
+
});
|
|
102
|
+
}
|
|
103
|
+
/**
|
|
104
|
+
* In some very rare cases a specific tab might not respond to requests.
|
|
105
|
+
* This returns a random port which is not closing.
|
|
106
|
+
*/
|
|
107
|
+
async getRandomWrappedPort() {
|
|
108
|
+
return await this.portMutex.runExclusive(() => {
|
|
109
|
+
const nonClosingPorts = this.ports.filter((p) => !p.isClosing);
|
|
110
|
+
return nonClosingPorts[Math.floor(Math.random() * nonClosingPorts.length)];
|
|
111
|
+
});
|
|
112
|
+
}
|
|
93
113
|
async waitForStatus(status) {
|
|
94
114
|
return this.withSyncImplementation(async (sync) => {
|
|
95
115
|
return sync.waitForStatus(status);
|
|
@@ -130,28 +150,53 @@ export class SharedSyncImplementation extends BaseObserver {
|
|
|
130
150
|
async setParams(params) {
|
|
131
151
|
await this.portMutex.runExclusive(async () => {
|
|
132
152
|
this.collectActiveSubscriptions();
|
|
133
|
-
|
|
134
|
-
|
|
135
|
-
|
|
136
|
-
|
|
137
|
-
|
|
138
|
-
|
|
139
|
-
|
|
140
|
-
|
|
141
|
-
|
|
142
|
-
|
|
143
|
-
|
|
144
|
-
|
|
145
|
-
|
|
146
|
-
|
|
147
|
-
|
|
148
|
-
|
|
149
|
-
|
|
150
|
-
|
|
151
|
-
|
|
153
|
+
});
|
|
154
|
+
if (this.syncParams) {
|
|
155
|
+
// Cannot modify already existing sync implementation params
|
|
156
|
+
return;
|
|
157
|
+
}
|
|
158
|
+
// First time setting params
|
|
159
|
+
this.syncParams = params;
|
|
160
|
+
if (params.streamOptions?.flags?.broadcastLogs) {
|
|
161
|
+
this.logger = this.broadCastLogger;
|
|
162
|
+
}
|
|
163
|
+
const lockedAdapter = new LockedAsyncDatabaseAdapter({
|
|
164
|
+
name: params.dbParams.dbFilename,
|
|
165
|
+
openConnection: async () => {
|
|
166
|
+
// Gets a connection from the clients when a new connection is requested.
|
|
167
|
+
const db = await this.openInternalDB();
|
|
168
|
+
db.registerListener({
|
|
169
|
+
closing: () => {
|
|
170
|
+
lockedAdapter.reOpenInternalDB();
|
|
171
|
+
}
|
|
172
|
+
});
|
|
173
|
+
return db;
|
|
174
|
+
},
|
|
175
|
+
logger: this.logger,
|
|
176
|
+
reOpenOnConnectionClosed: true
|
|
177
|
+
});
|
|
178
|
+
this.distributedDB = lockedAdapter;
|
|
179
|
+
await lockedAdapter.init();
|
|
180
|
+
lockedAdapter.registerListener({
|
|
181
|
+
databaseReOpened: () => {
|
|
182
|
+
// We may have missed some table updates while the database was closed.
|
|
183
|
+
// We can poke the crud in case we missed any updates.
|
|
184
|
+
this.connectionManager.syncStreamImplementation?.triggerCrudUpload();
|
|
185
|
+
/**
|
|
186
|
+
* FIXME or IMPROVE ME
|
|
187
|
+
* The Rust client implementation stores sync state on the connection level.
|
|
188
|
+
* Reopening the database causes a state machine error which should cause the
|
|
189
|
+
* StreamingSyncImplementation to reconnect. It would be nicer if we could trigger
|
|
190
|
+
* this reconnect earlier.
|
|
191
|
+
* This reconnect is not required for IndexedDB.
|
|
192
|
+
*/
|
|
152
193
|
}
|
|
153
|
-
this.iterateListeners((l) => l.initialized?.());
|
|
154
194
|
});
|
|
195
|
+
self.onerror = (event) => {
|
|
196
|
+
// Share any uncaught events on the broadcast logger
|
|
197
|
+
this.logger.error('Uncaught exception in PowerSync shared sync worker', event);
|
|
198
|
+
};
|
|
199
|
+
this.iterateListeners((l) => l.initialized?.());
|
|
155
200
|
}
|
|
156
201
|
async dispose() {
|
|
157
202
|
await this.waitForReady();
|
|
@@ -180,7 +225,8 @@ export class SharedSyncImplementation extends BaseObserver {
|
|
|
180
225
|
port,
|
|
181
226
|
clientProvider: Comlink.wrap(port),
|
|
182
227
|
currentSubscriptions: [],
|
|
183
|
-
closeListeners: []
|
|
228
|
+
closeListeners: [],
|
|
229
|
+
isClosing: false
|
|
184
230
|
};
|
|
185
231
|
this.ports.push(portProvider);
|
|
186
232
|
// Give the newly connected client the latest status
|
|
@@ -196,14 +242,16 @@ export class SharedSyncImplementation extends BaseObserver {
|
|
|
196
242
|
* clients.
|
|
197
243
|
*/
|
|
198
244
|
async removePort(port) {
|
|
245
|
+
// Ports might be removed faster than we can process them.
|
|
246
|
+
port.isClosing = true;
|
|
199
247
|
// Remove the port within a mutex context.
|
|
200
248
|
// Warns if the port is not found. This should not happen in practice.
|
|
201
249
|
// We return early if the port is not found.
|
|
202
|
-
|
|
250
|
+
return await this.portMutex.runExclusive(async () => {
|
|
203
251
|
const index = this.ports.findIndex((p) => p == port);
|
|
204
252
|
if (index < 0) {
|
|
205
253
|
this.logger.warn(`Could not remove port ${port} since it is not present in active ports.`);
|
|
206
|
-
return {};
|
|
254
|
+
return () => { };
|
|
207
255
|
}
|
|
208
256
|
const trackedPort = this.ports[index];
|
|
209
257
|
// Remove from the list of active ports
|
|
@@ -217,35 +265,13 @@ export class SharedSyncImplementation extends BaseObserver {
|
|
|
217
265
|
abortController.controller.abort(new AbortOperation('Closing pending requests after client port is removed'));
|
|
218
266
|
}
|
|
219
267
|
});
|
|
220
|
-
|
|
221
|
-
|
|
222
|
-
|
|
223
|
-
trackedPort
|
|
224
|
-
};
|
|
225
|
-
});
|
|
226
|
-
if (!trackedPort) {
|
|
227
|
-
// We could not find the port to remove
|
|
228
|
-
return () => { };
|
|
229
|
-
}
|
|
230
|
-
for (const closeListener of trackedPort.closeListeners) {
|
|
231
|
-
await closeListener();
|
|
232
|
-
}
|
|
233
|
-
if (this.dbAdapter && this.dbAdapter == trackedPort.db) {
|
|
234
|
-
// Unconditionally close the connection because the database it's writing to has just been closed.
|
|
235
|
-
// The connection has been closed previously, this might throw. We should be able to ignore it.
|
|
236
|
-
await this.connectionManager
|
|
237
|
-
.disconnect()
|
|
238
|
-
.catch((ex) => this.logger.warn('Error while disconnecting. Will attempt to reconnect.', ex));
|
|
239
|
-
// Clearing the adapter will result in a new one being opened in connect
|
|
240
|
-
this.dbAdapter = null;
|
|
241
|
-
if (shouldReconnect) {
|
|
242
|
-
await this.connectionManager.connect(CONNECTOR_PLACEHOLDER, this.lastConnectOptions ?? {});
|
|
268
|
+
// Close the worker wrapped database connection, we can't accurately rely on this connection
|
|
269
|
+
for (const closeListener of trackedPort.closeListeners) {
|
|
270
|
+
await closeListener();
|
|
243
271
|
}
|
|
244
|
-
|
|
245
|
-
|
|
246
|
-
|
|
247
|
-
// Release proxy
|
|
248
|
-
return () => trackedPort.clientProvider[Comlink.releaseProxy]();
|
|
272
|
+
this.collectActiveSubscriptions();
|
|
273
|
+
return () => trackedPort.clientProvider[Comlink.releaseProxy]();
|
|
274
|
+
});
|
|
249
275
|
}
|
|
250
276
|
triggerCrudUpload() {
|
|
251
277
|
this.withSyncImplementation(async (sync) => {
|
|
@@ -282,10 +308,13 @@ export class SharedSyncImplementation extends BaseObserver {
|
|
|
282
308
|
const syncParams = this.syncParams;
|
|
283
309
|
// Create a new StreamingSyncImplementation for each connect call. This is usually done is all SDKs.
|
|
284
310
|
return new WebStreamingSyncImplementation({
|
|
285
|
-
adapter: new SqliteBucketStorage(this.
|
|
311
|
+
adapter: new SqliteBucketStorage(this.distributedDB, this.logger),
|
|
286
312
|
remote: new WebRemote({
|
|
287
313
|
invalidateCredentials: async () => {
|
|
288
|
-
const lastPort = this.
|
|
314
|
+
const lastPort = await this.getLastWrappedPort();
|
|
315
|
+
if (!lastPort) {
|
|
316
|
+
throw new Error('No client port found to invalidate credentials');
|
|
317
|
+
}
|
|
289
318
|
try {
|
|
290
319
|
this.logger.log('calling the last port client provider to invalidate credentials');
|
|
291
320
|
lastPort.clientProvider.invalidateCredentials();
|
|
@@ -295,7 +324,10 @@ export class SharedSyncImplementation extends BaseObserver {
|
|
|
295
324
|
}
|
|
296
325
|
},
|
|
297
326
|
fetchCredentials: async () => {
|
|
298
|
-
const lastPort = this.
|
|
327
|
+
const lastPort = await this.getLastWrappedPort();
|
|
328
|
+
if (!lastPort) {
|
|
329
|
+
throw new Error('No client port found to fetch credentials');
|
|
330
|
+
}
|
|
299
331
|
return new Promise(async (resolve, reject) => {
|
|
300
332
|
const abortController = new AbortController();
|
|
301
333
|
this.fetchCredentialsController = {
|
|
@@ -317,7 +349,10 @@ export class SharedSyncImplementation extends BaseObserver {
|
|
|
317
349
|
}
|
|
318
350
|
}, this.logger),
|
|
319
351
|
uploadCrud: async () => {
|
|
320
|
-
const lastPort = this.
|
|
352
|
+
const lastPort = await this.getLastWrappedPort();
|
|
353
|
+
if (!lastPort) {
|
|
354
|
+
throw new Error('No client port found to upload crud');
|
|
355
|
+
}
|
|
321
356
|
return new Promise(async (resolve, reject) => {
|
|
322
357
|
const abortController = new AbortController();
|
|
323
358
|
this.uploadDataController = {
|
|
@@ -343,38 +378,81 @@ export class SharedSyncImplementation extends BaseObserver {
|
|
|
343
378
|
logger: this.logger
|
|
344
379
|
});
|
|
345
380
|
}
|
|
381
|
+
/**
|
|
382
|
+
* Opens a worker wrapped database connection. Using the last connected client port.
|
|
383
|
+
*/
|
|
346
384
|
async openInternalDB() {
|
|
347
|
-
const
|
|
348
|
-
if (!
|
|
385
|
+
const client = await this.getRandomWrappedPort();
|
|
386
|
+
if (!client) {
|
|
349
387
|
// Should not really happen in practice
|
|
350
388
|
throw new Error(`Could not open DB connection since no client is connected.`);
|
|
351
389
|
}
|
|
352
|
-
|
|
390
|
+
// Fail-safe timeout for opening a database connection.
|
|
391
|
+
const timeout = setTimeout(() => {
|
|
392
|
+
abortController.abort();
|
|
393
|
+
}, 10_000);
|
|
394
|
+
/**
|
|
395
|
+
* Handle cases where the client might close while opening a connection.
|
|
396
|
+
*/
|
|
397
|
+
const abortController = new AbortController();
|
|
398
|
+
const closeListener = () => {
|
|
399
|
+
abortController.abort();
|
|
400
|
+
};
|
|
401
|
+
const removeCloseListener = () => {
|
|
402
|
+
const index = client.closeListeners.indexOf(closeListener);
|
|
403
|
+
if (index >= 0) {
|
|
404
|
+
client.closeListeners.splice(index, 1);
|
|
405
|
+
}
|
|
406
|
+
};
|
|
407
|
+
client.closeListeners.push(closeListener);
|
|
408
|
+
const workerPort = await withAbort({
|
|
409
|
+
action: () => client.clientProvider.getDBWorkerPort(),
|
|
410
|
+
signal: abortController.signal,
|
|
411
|
+
cleanupOnAbort: (port) => {
|
|
412
|
+
port.close();
|
|
413
|
+
}
|
|
414
|
+
}).catch((ex) => {
|
|
415
|
+
removeCloseListener();
|
|
416
|
+
throw ex;
|
|
417
|
+
});
|
|
353
418
|
const remote = Comlink.wrap(workerPort);
|
|
354
419
|
const identifier = this.syncParams.dbParams.dbFilename;
|
|
355
|
-
|
|
356
|
-
|
|
357
|
-
|
|
358
|
-
|
|
359
|
-
|
|
360
|
-
|
|
361
|
-
|
|
362
|
-
|
|
363
|
-
|
|
364
|
-
|
|
365
|
-
|
|
366
|
-
|
|
367
|
-
|
|
368
|
-
|
|
369
|
-
|
|
370
|
-
|
|
371
|
-
|
|
372
|
-
|
|
373
|
-
|
|
374
|
-
|
|
420
|
+
/**
|
|
421
|
+
* The open could fail if the tab is closed while we're busy opening the database.
|
|
422
|
+
* This operation is typically executed inside an exclusive portMutex lock.
|
|
423
|
+
* We typically execute the closeListeners using the portMutex in a different context.
|
|
424
|
+
* We can't rely on the closeListeners to abort the operation if the tab is closed.
|
|
425
|
+
*/
|
|
426
|
+
const db = await withAbort({
|
|
427
|
+
action: () => remote(this.syncParams.dbParams),
|
|
428
|
+
signal: abortController.signal,
|
|
429
|
+
cleanupOnAbort: (db) => {
|
|
430
|
+
db.close();
|
|
431
|
+
}
|
|
432
|
+
}).finally(() => {
|
|
433
|
+
// We can remove the close listener here since we no longer need it past this point.
|
|
434
|
+
removeCloseListener();
|
|
435
|
+
});
|
|
436
|
+
clearTimeout(timeout);
|
|
437
|
+
const wrapped = new WorkerWrappedAsyncDatabaseConnection({
|
|
438
|
+
remote,
|
|
439
|
+
baseConnection: db,
|
|
440
|
+
identifier,
|
|
441
|
+
// It's possible for this worker to outlive the client hosting the database for us. We need to be prepared for
|
|
442
|
+
// that and ensure pending requests are aborted when the tab is closed.
|
|
443
|
+
remoteCanCloseUnexpectedly: true
|
|
444
|
+
});
|
|
445
|
+
client.closeListeners.push(async () => {
|
|
446
|
+
this.logger.info('Aborting open connection because associated tab closed.');
|
|
447
|
+
/**
|
|
448
|
+
* Don't await this close operation. It might never resolve if the tab is closed.
|
|
449
|
+
* We mark the remote as closed first, this will reject any pending requests.
|
|
450
|
+
* We then call close. The close operation is configured to fire-and-forget, the main promise will reject immediately.
|
|
451
|
+
*/
|
|
452
|
+
wrapped.markRemoteClosed();
|
|
453
|
+
wrapped.close().catch((ex) => this.logger.warn('error closing database connection', ex));
|
|
375
454
|
});
|
|
376
|
-
|
|
377
|
-
this.dbAdapter = lastClient.db = locked;
|
|
455
|
+
return wrapped;
|
|
378
456
|
}
|
|
379
457
|
/**
|
|
380
458
|
* A method to update the all shared statuses for each
|
|
@@ -384,16 +462,34 @@ export class SharedSyncImplementation extends BaseObserver {
|
|
|
384
462
|
this.syncStatus = new SyncStatus(status);
|
|
385
463
|
this.ports.forEach((p) => p.clientProvider.statusChanged(status));
|
|
386
464
|
}
|
|
387
|
-
|
|
388
|
-
|
|
389
|
-
|
|
390
|
-
|
|
391
|
-
|
|
392
|
-
|
|
393
|
-
|
|
465
|
+
}
|
|
466
|
+
/**
|
|
467
|
+
* Runs the action with an abort controller.
|
|
468
|
+
*/
|
|
469
|
+
function withAbort(options) {
|
|
470
|
+
const { action, signal, cleanupOnAbort } = options;
|
|
471
|
+
return new Promise((resolve, reject) => {
|
|
472
|
+
if (signal.aborted) {
|
|
473
|
+
reject(new AbortOperation('Operation aborted by abort controller'));
|
|
474
|
+
return;
|
|
394
475
|
}
|
|
395
|
-
|
|
396
|
-
|
|
397
|
-
|
|
398
|
-
|
|
476
|
+
function handleAbort() {
|
|
477
|
+
signal.removeEventListener('abort', handleAbort);
|
|
478
|
+
reject(new AbortOperation('Operation aborted by abort controller'));
|
|
479
|
+
}
|
|
480
|
+
signal.addEventListener('abort', handleAbort, { once: true });
|
|
481
|
+
function completePromise(action) {
|
|
482
|
+
signal.removeEventListener('abort', handleAbort);
|
|
483
|
+
action();
|
|
484
|
+
}
|
|
485
|
+
action()
|
|
486
|
+
.then((data) => {
|
|
487
|
+
// We already rejected due to the abort, allow for cleanup
|
|
488
|
+
if (signal.aborted) {
|
|
489
|
+
return completePromise(() => cleanupOnAbort?.(data));
|
|
490
|
+
}
|
|
491
|
+
completePromise(() => resolve(data));
|
|
492
|
+
})
|
|
493
|
+
.catch((e) => completePromise(() => reject(e)));
|
|
494
|
+
});
|
|
399
495
|
}
|
|
@@ -1,11 +1,11 @@
|
|
|
1
1
|
import { createBaseLogger } from '@powersync/common';
|
|
2
|
-
import { SharedSyncImplementation } from './SharedSyncImplementation';
|
|
3
|
-
import { WorkerClient } from './WorkerClient';
|
|
2
|
+
import { SharedSyncImplementation } from './SharedSyncImplementation.js';
|
|
3
|
+
import { WorkerClient } from './WorkerClient.js';
|
|
4
4
|
const _self = self;
|
|
5
5
|
const logger = createBaseLogger();
|
|
6
6
|
logger.useDefaults();
|
|
7
7
|
const sharedSyncImplementation = new SharedSyncImplementation();
|
|
8
8
|
_self.onconnect = async function (event) {
|
|
9
9
|
const port = event.ports[0];
|
|
10
|
-
|
|
10
|
+
new WorkerClient(sharedSyncImplementation, port);
|
|
11
11
|
};
|
|
@@ -1,5 +1,5 @@
|
|
|
1
|
-
import {
|
|
2
|
-
import {
|
|
1
|
+
import { ILogLevel, PowerSyncConnectionOptions, SubscribedStream } from '@powersync/common';
|
|
2
|
+
import { SharedSyncImplementation, SharedSyncInitOptions, WrappedSyncPort } from './SharedSyncImplementation.js';
|
|
3
3
|
/**
|
|
4
4
|
* A client to the shared sync worker.
|
|
5
5
|
*
|
|
@@ -10,8 +10,8 @@ export declare class WorkerClient {
|
|
|
10
10
|
private readonly sync;
|
|
11
11
|
private readonly port;
|
|
12
12
|
private resolvedPort;
|
|
13
|
+
protected resolvedPortPromise: Promise<WrappedSyncPort> | null;
|
|
13
14
|
constructor(sync: SharedSyncImplementation, port: MessagePort);
|
|
14
|
-
initialize(): Promise<void>;
|
|
15
15
|
private removePort;
|
|
16
16
|
/**
|
|
17
17
|
* Called by a client after obtaining a lock with a random name.
|
|
@@ -19,7 +19,7 @@ export declare class WorkerClient {
|
|
|
19
19
|
* When the client tab is closed, its lock will be returned. So when the shared worker attempts to acquire the lock,
|
|
20
20
|
* it can consider the connection to be closed.
|
|
21
21
|
*/
|
|
22
|
-
addLockBasedCloseSignal(name: string): void
|
|
22
|
+
addLockBasedCloseSignal(name: string): Promise<void>;
|
|
23
23
|
setLogLevel(level: ILogLevel): void;
|
|
24
24
|
triggerCrudUpload(): void;
|
|
25
25
|
setParams(params: SharedSyncInitOptions, subscriptions: SubscribedStream[]): Promise<void>;
|
|
@@ -28,5 +28,4 @@ export declare class WorkerClient {
|
|
|
28
28
|
connect(options?: PowerSyncConnectionOptions): Promise<void>;
|
|
29
29
|
updateSubscriptions(subscriptions: SubscribedStream[]): void;
|
|
30
30
|
disconnect(): Promise<void>;
|
|
31
|
-
_testUpdateAllStatuses(status: SyncStatusOptions): Promise<void>;
|
|
32
31
|
}
|
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
import * as Comlink from 'comlink';
|
|
2
|
-
import {
|
|
3
|
-
import {
|
|
2
|
+
import { getNavigatorLocks } from '../../shared/navigator.js';
|
|
3
|
+
import { SharedSyncClientEvent } from './SharedSyncImplementation.js';
|
|
4
4
|
/**
|
|
5
5
|
* A client to the shared sync worker.
|
|
6
6
|
*
|
|
@@ -11,11 +11,11 @@ export class WorkerClient {
|
|
|
11
11
|
sync;
|
|
12
12
|
port;
|
|
13
13
|
resolvedPort = null;
|
|
14
|
+
resolvedPortPromise = null;
|
|
14
15
|
constructor(sync, port) {
|
|
15
16
|
this.sync = sync;
|
|
16
17
|
this.port = port;
|
|
17
|
-
|
|
18
|
-
async initialize() {
|
|
18
|
+
Comlink.expose(this, this.port);
|
|
19
19
|
/**
|
|
20
20
|
* Adds an extra listener which can remove this port
|
|
21
21
|
* from the list of monitored ports.
|
|
@@ -26,8 +26,6 @@ export class WorkerClient {
|
|
|
26
26
|
await this.removePort();
|
|
27
27
|
}
|
|
28
28
|
});
|
|
29
|
-
this.resolvedPort = await this.sync.addPort(this.port);
|
|
30
|
-
Comlink.expose(this, this.port);
|
|
31
29
|
}
|
|
32
30
|
async removePort() {
|
|
33
31
|
if (this.resolvedPort) {
|
|
@@ -48,7 +46,10 @@ export class WorkerClient {
|
|
|
48
46
|
* When the client tab is closed, its lock will be returned. So when the shared worker attempts to acquire the lock,
|
|
49
47
|
* it can consider the connection to be closed.
|
|
50
48
|
*/
|
|
51
|
-
addLockBasedCloseSignal(name) {
|
|
49
|
+
async addLockBasedCloseSignal(name) {
|
|
50
|
+
// Only add the port once the lock has been obtained on the client.
|
|
51
|
+
this.resolvedPort = await this.sync.addPort(this.port);
|
|
52
|
+
// Don't await this lock request
|
|
52
53
|
getNavigatorLocks().request(name, async () => {
|
|
53
54
|
await this.removePort();
|
|
54
55
|
});
|
|
@@ -80,7 +81,4 @@ export class WorkerClient {
|
|
|
80
81
|
disconnect() {
|
|
81
82
|
return this.sync.disconnect();
|
|
82
83
|
}
|
|
83
|
-
async _testUpdateAllStatuses(status) {
|
|
84
|
-
return this.sync._testUpdateAllStatuses(status);
|
|
85
|
-
}
|
|
86
84
|
}
|