@powersync/web 0.0.0-dev-20251129133952 → 0.0.0-dev-20251201150812
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/1807036ae51c10ee4d23.wasm +0 -0
- package/dist/{10072fe45f0a8fab0a0e.wasm → 307d8ce2280e3bae09d5.wasm} +0 -0
- package/dist/{6e435e51534839845554.wasm → cd8b9e8f4c87bf81c169.wasm} +0 -0
- package/dist/e797080f5ed0b5324166.wasm +0 -0
- package/dist/index.umd.js +137 -104
- package/dist/index.umd.js.map +1 -1
- package/dist/worker/SharedSyncImplementation.umd.js +137 -110
- package/dist/worker/SharedSyncImplementation.umd.js.map +1 -1
- package/dist/worker/WASQLiteDB.umd.js +15 -1
- package/dist/worker/WASQLiteDB.umd.js.map +1 -1
- package/dist/worker/node_modules_journeyapps_wa-sqlite_dist_mc-wa-sqlite-async_mjs.umd.js +2 -2
- package/dist/worker/node_modules_journeyapps_wa-sqlite_dist_mc-wa-sqlite-async_mjs.umd.js.map +1 -1
- package/dist/worker/node_modules_journeyapps_wa-sqlite_dist_mc-wa-sqlite_mjs.umd.js +2 -2
- package/dist/worker/node_modules_journeyapps_wa-sqlite_dist_mc-wa-sqlite_mjs.umd.js.map +1 -1
- package/dist/worker/node_modules_journeyapps_wa-sqlite_dist_wa-sqlite-async_mjs.umd.js +2 -2
- package/dist/worker/node_modules_journeyapps_wa-sqlite_dist_wa-sqlite-async_mjs.umd.js.map +1 -1
- package/dist/worker/node_modules_journeyapps_wa-sqlite_dist_wa-sqlite_mjs.umd.js +2 -2
- package/dist/worker/node_modules_journeyapps_wa-sqlite_dist_wa-sqlite_mjs.umd.js.map +1 -1
- package/dist/worker/node_modules_journeyapps_wa-sqlite_src_examples_IDBBatchAtomicVFS_js.umd.js +20 -23
- package/dist/worker/node_modules_journeyapps_wa-sqlite_src_examples_IDBBatchAtomicVFS_js.umd.js.map +1 -1
- package/lib/src/db/PowerSyncDatabase.d.ts +1 -1
- package/lib/src/db/PowerSyncDatabase.js +4 -4
- package/lib/src/db/adapters/AsyncDatabaseConnection.d.ts +5 -0
- package/lib/src/db/adapters/AsyncDatabaseConnection.js +5 -0
- package/lib/src/db/adapters/LockedAsyncDatabaseAdapter.d.ts +6 -1
- package/lib/src/db/adapters/LockedAsyncDatabaseAdapter.js +20 -5
- package/lib/src/db/adapters/WorkerWrappedAsyncDatabaseConnection.d.ts +5 -1
- package/lib/src/db/adapters/WorkerWrappedAsyncDatabaseConnection.js +12 -5
- package/lib/src/db/sync/SharedWebStreamingSyncImplementation.d.ts +0 -4
- package/lib/src/db/sync/SharedWebStreamingSyncImplementation.js +3 -8
- package/lib/src/worker/sync/MockSyncService.d.ts +2 -0
- package/lib/src/worker/sync/MockSyncService.js +3 -0
- package/lib/src/worker/sync/MockSyncServiceTypes.d.ts +101 -0
- package/lib/src/worker/sync/MockSyncServiceTypes.js +1 -0
- package/lib/src/worker/sync/MockSyncServiceWorker.d.ts +56 -0
- package/lib/src/worker/sync/MockSyncServiceWorker.js +369 -0
- package/lib/src/worker/sync/SharedSyncImplementation.d.ts +6 -11
- package/lib/src/worker/sync/SharedSyncImplementation.js +73 -64
- package/lib/src/worker/sync/SharedSyncImplementation.worker.js +1 -1
- package/lib/src/worker/sync/WorkerClient.d.ts +1 -3
- package/lib/src/worker/sync/WorkerClient.js +3 -27
- package/lib/tsconfig.tsbuildinfo +1 -1
- package/package.json +3 -3
- package/src/db/PowerSyncDatabase.ts +13 -15
- package/src/db/adapters/AsyncDatabaseConnection.ts +5 -0
- package/src/db/adapters/LockedAsyncDatabaseAdapter.ts +22 -5
- package/src/db/adapters/WorkerWrappedAsyncDatabaseConnection.ts +16 -4
- package/src/db/sync/SharedWebStreamingSyncImplementation.ts +5 -11
- package/src/worker/sync/MockSyncService.ts +3 -0
- package/src/worker/sync/MockSyncServiceTypes.ts +71 -0
- package/src/worker/sync/MockSyncServiceWorker.ts +406 -0
- package/src/worker/sync/SharedSyncImplementation.ts +85 -78
- package/src/worker/sync/SharedSyncImplementation.worker.ts +1 -1
- package/src/worker/sync/WorkerClient.ts +4 -30
- package/dist/a730f7ca717b02234beb.wasm +0 -0
- package/dist/aa2f408d64445fed090e.wasm +0 -0
|
@@ -89,14 +89,25 @@ export class SharedSyncImplementation extends BaseObserver {
|
|
|
89
89
|
/**
|
|
90
90
|
* Gets the last client port which we know is safe from unexpected closes.
|
|
91
91
|
*/
|
|
92
|
-
|
|
93
|
-
// Find the last port which is
|
|
94
|
-
|
|
95
|
-
|
|
96
|
-
|
|
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
|
+
}
|
|
97
99
|
}
|
|
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
|
+
return this.ports[Math.floor(Math.random() * this.ports.length)];
|
|
110
|
+
});
|
|
100
111
|
}
|
|
101
112
|
async waitForStatus(status) {
|
|
102
113
|
return this.withSyncImplementation(async (sync) => {
|
|
@@ -138,39 +149,45 @@ export class SharedSyncImplementation extends BaseObserver {
|
|
|
138
149
|
async setParams(params) {
|
|
139
150
|
await this.portMutex.runExclusive(async () => {
|
|
140
151
|
this.collectActiveSubscriptions();
|
|
141
|
-
|
|
142
|
-
|
|
143
|
-
|
|
144
|
-
|
|
145
|
-
|
|
146
|
-
|
|
147
|
-
|
|
148
|
-
|
|
152
|
+
});
|
|
153
|
+
if (this.syncParams) {
|
|
154
|
+
// Cannot modify already existing sync implementation params
|
|
155
|
+
return;
|
|
156
|
+
}
|
|
157
|
+
// First time setting params
|
|
158
|
+
this.syncParams = params;
|
|
159
|
+
if (params.streamOptions?.flags?.broadcastLogs) {
|
|
160
|
+
this.logger = this.broadCastLogger;
|
|
161
|
+
}
|
|
162
|
+
const lockedAdapter = new LockedAsyncDatabaseAdapter({
|
|
163
|
+
name: params.dbParams.dbFilename,
|
|
164
|
+
openConnection: async () => {
|
|
165
|
+
// Gets a connection from the clients when a new connection is requested.
|
|
166
|
+
const db = await this.openInternalDB();
|
|
167
|
+
db.registerListener({
|
|
168
|
+
closing: () => {
|
|
169
|
+
lockedAdapter.reOpenInternalDB();
|
|
170
|
+
}
|
|
171
|
+
});
|
|
172
|
+
return db;
|
|
173
|
+
},
|
|
174
|
+
logger: this.logger,
|
|
175
|
+
reOpenOnConnectionClosed: true
|
|
176
|
+
});
|
|
177
|
+
this.distributedDB = lockedAdapter;
|
|
178
|
+
await lockedAdapter.init();
|
|
179
|
+
lockedAdapter.registerListener({
|
|
180
|
+
databaseReOpened: () => {
|
|
181
|
+
// We may have missed some table updates while the database was closed.
|
|
182
|
+
// We can poke the crud in case we missed any updates.
|
|
183
|
+
this.connectionManager.syncStreamImplementation?.triggerCrudUpload();
|
|
149
184
|
}
|
|
150
|
-
const lockedAdapter = new LockedAsyncDatabaseAdapter({
|
|
151
|
-
name: params.dbParams.dbFilename,
|
|
152
|
-
openConnection: async () => {
|
|
153
|
-
// Gets a connection from the clients when a new connection is requested.
|
|
154
|
-
return await this.openInternalDB();
|
|
155
|
-
},
|
|
156
|
-
logger: this.logger,
|
|
157
|
-
reOpenOnConnectionClosed: true
|
|
158
|
-
});
|
|
159
|
-
this.distributedDB = lockedAdapter;
|
|
160
|
-
await lockedAdapter.init();
|
|
161
|
-
lockedAdapter.registerListener({
|
|
162
|
-
databaseReOpened: () => {
|
|
163
|
-
// We may have missed some table updates while the database was closed.
|
|
164
|
-
// We can poke the crud in case we missed any updates.
|
|
165
|
-
this.connectionManager.syncStreamImplementation?.triggerCrudUpload();
|
|
166
|
-
}
|
|
167
|
-
});
|
|
168
|
-
self.onerror = (event) => {
|
|
169
|
-
// Share any uncaught events on the broadcast logger
|
|
170
|
-
this.logger.error('Uncaught exception in PowerSync shared sync worker', event);
|
|
171
|
-
};
|
|
172
|
-
this.iterateListeners((l) => l.initialized?.());
|
|
173
185
|
});
|
|
186
|
+
self.onerror = (event) => {
|
|
187
|
+
// Share any uncaught events on the broadcast logger
|
|
188
|
+
this.logger.error('Uncaught exception in PowerSync shared sync worker', event);
|
|
189
|
+
};
|
|
190
|
+
this.iterateListeners((l) => l.initialized?.());
|
|
174
191
|
}
|
|
175
192
|
async dispose() {
|
|
176
193
|
await this.waitForReady();
|
|
@@ -200,7 +217,6 @@ export class SharedSyncImplementation extends BaseObserver {
|
|
|
200
217
|
clientProvider: Comlink.wrap(port),
|
|
201
218
|
currentSubscriptions: [],
|
|
202
219
|
closeListeners: [],
|
|
203
|
-
isProtectedFromClose: false,
|
|
204
220
|
isClosing: false
|
|
205
221
|
};
|
|
206
222
|
this.ports.push(portProvider);
|
|
@@ -286,7 +302,7 @@ export class SharedSyncImplementation extends BaseObserver {
|
|
|
286
302
|
adapter: new SqliteBucketStorage(this.distributedDB, this.logger),
|
|
287
303
|
remote: new WebRemote({
|
|
288
304
|
invalidateCredentials: async () => {
|
|
289
|
-
const lastPort = this.
|
|
305
|
+
const lastPort = await this.getLastWrappedPort();
|
|
290
306
|
if (!lastPort) {
|
|
291
307
|
throw new Error('No client port found to invalidate credentials');
|
|
292
308
|
}
|
|
@@ -299,7 +315,7 @@ export class SharedSyncImplementation extends BaseObserver {
|
|
|
299
315
|
}
|
|
300
316
|
},
|
|
301
317
|
fetchCredentials: async () => {
|
|
302
|
-
const lastPort = this.
|
|
318
|
+
const lastPort = await this.getLastWrappedPort();
|
|
303
319
|
if (!lastPort) {
|
|
304
320
|
throw new Error('No client port found to fetch credentials');
|
|
305
321
|
}
|
|
@@ -324,7 +340,7 @@ export class SharedSyncImplementation extends BaseObserver {
|
|
|
324
340
|
}
|
|
325
341
|
}, this.logger),
|
|
326
342
|
uploadCrud: async () => {
|
|
327
|
-
const lastPort = this.
|
|
343
|
+
const lastPort = await this.getLastWrappedPort();
|
|
328
344
|
if (!lastPort) {
|
|
329
345
|
throw new Error('No client port found to upload crud');
|
|
330
346
|
}
|
|
@@ -359,11 +375,15 @@ export class SharedSyncImplementation extends BaseObserver {
|
|
|
359
375
|
async openInternalDB() {
|
|
360
376
|
while (true) {
|
|
361
377
|
try {
|
|
362
|
-
const
|
|
363
|
-
if (!
|
|
378
|
+
const client = await this.getRandomWrappedPort();
|
|
379
|
+
if (!client) {
|
|
364
380
|
// Should not really happen in practice
|
|
365
381
|
throw new Error(`Could not open DB connection since no client is connected.`);
|
|
366
382
|
}
|
|
383
|
+
// Fail-safe timeout for opening a database connection.
|
|
384
|
+
const timeout = setTimeout(() => {
|
|
385
|
+
abortController.abort();
|
|
386
|
+
}, 10_000);
|
|
367
387
|
/**
|
|
368
388
|
* Handle cases where the client might close while opening a connection.
|
|
369
389
|
*/
|
|
@@ -372,13 +392,13 @@ export class SharedSyncImplementation extends BaseObserver {
|
|
|
372
392
|
abortController.abort();
|
|
373
393
|
};
|
|
374
394
|
const removeCloseListener = () => {
|
|
375
|
-
const index =
|
|
395
|
+
const index = client.closeListeners.indexOf(closeListener);
|
|
376
396
|
if (index >= 0) {
|
|
377
|
-
|
|
397
|
+
client.closeListeners.splice(index, 1);
|
|
378
398
|
}
|
|
379
399
|
};
|
|
380
|
-
|
|
381
|
-
const workerPort = await withAbort(() =>
|
|
400
|
+
client.closeListeners.push(closeListener);
|
|
401
|
+
const workerPort = await withAbort(() => client.clientProvider.getDBWorkerPort(), abortController.signal).catch((ex) => {
|
|
382
402
|
removeCloseListener();
|
|
383
403
|
throw ex;
|
|
384
404
|
});
|
|
@@ -394,6 +414,7 @@ export class SharedSyncImplementation extends BaseObserver {
|
|
|
394
414
|
// We can remove the close listener here since we no longer need it past this point.
|
|
395
415
|
removeCloseListener();
|
|
396
416
|
});
|
|
417
|
+
clearTimeout(timeout);
|
|
397
418
|
const wrapped = new WorkerWrappedAsyncDatabaseConnection({
|
|
398
419
|
remote,
|
|
399
420
|
baseConnection: db,
|
|
@@ -402,15 +423,15 @@ export class SharedSyncImplementation extends BaseObserver {
|
|
|
402
423
|
// that and ensure pending requests are aborted when the tab is closed.
|
|
403
424
|
remoteCanCloseUnexpectedly: true
|
|
404
425
|
});
|
|
405
|
-
|
|
426
|
+
client.closeListeners.push(async () => {
|
|
406
427
|
this.logger.info('Aborting open connection because associated tab closed.');
|
|
407
428
|
/**
|
|
408
429
|
* Don't await this close operation. It might never resolve if the tab is closed.
|
|
409
|
-
* We
|
|
410
|
-
*
|
|
430
|
+
* We mark the remote as closed first, this will reject any pending requests.
|
|
431
|
+
* We then call close. The close operation is configured to fire-and-forget, the main promise will reject immediately.
|
|
411
432
|
*/
|
|
412
|
-
wrapped.close().catch((ex) => this.logger.warn('error closing database connection', ex));
|
|
413
433
|
wrapped.markRemoteClosed();
|
|
434
|
+
wrapped.close().catch((ex) => this.logger.warn('error closing database connection', ex));
|
|
414
435
|
});
|
|
415
436
|
return wrapped;
|
|
416
437
|
}
|
|
@@ -428,18 +449,6 @@ export class SharedSyncImplementation extends BaseObserver {
|
|
|
428
449
|
this.syncStatus = new SyncStatus(status);
|
|
429
450
|
this.ports.forEach((p) => p.clientProvider.statusChanged(status));
|
|
430
451
|
}
|
|
431
|
-
/**
|
|
432
|
-
* A function only used for unit tests which updates the internal
|
|
433
|
-
* sync stream client and all tab client's sync status
|
|
434
|
-
*/
|
|
435
|
-
async _testUpdateAllStatuses(status) {
|
|
436
|
-
if (!this.connectionManager.syncStreamImplementation) {
|
|
437
|
-
throw new Error('Cannot update status without a sync stream implementation');
|
|
438
|
-
}
|
|
439
|
-
// Only assigning, don't call listeners for this test
|
|
440
|
-
this.connectionManager.syncStreamImplementation.syncStatus = new SyncStatus(status);
|
|
441
|
-
this.updateAllStatuses(status);
|
|
442
|
-
}
|
|
443
452
|
}
|
|
444
453
|
/**
|
|
445
454
|
* Runs the action with an abort controller.
|
|
@@ -7,5 +7,5 @@ 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,4 +1,4 @@
|
|
|
1
|
-
import { ILogLevel, PowerSyncConnectionOptions, SubscribedStream
|
|
1
|
+
import { ILogLevel, PowerSyncConnectionOptions, SubscribedStream } from '@powersync/common';
|
|
2
2
|
import { SharedSyncImplementation, SharedSyncInitOptions, WrappedSyncPort } from './SharedSyncImplementation';
|
|
3
3
|
/**
|
|
4
4
|
* A client to the shared sync worker.
|
|
@@ -12,7 +12,6 @@ export declare class WorkerClient {
|
|
|
12
12
|
private resolvedPort;
|
|
13
13
|
protected resolvedPortPromise: Promise<WrappedSyncPort> | null;
|
|
14
14
|
constructor(sync: SharedSyncImplementation, port: MessagePort);
|
|
15
|
-
initialize(): Promise<void>;
|
|
16
15
|
private removePort;
|
|
17
16
|
/**
|
|
18
17
|
* Called by a client after obtaining a lock with a random name.
|
|
@@ -29,5 +28,4 @@ export declare class WorkerClient {
|
|
|
29
28
|
connect(options?: PowerSyncConnectionOptions): Promise<void>;
|
|
30
29
|
updateSubscriptions(subscriptions: SubscribedStream[]): void;
|
|
31
30
|
disconnect(): Promise<void>;
|
|
32
|
-
_testUpdateAllStatuses(status: SyncStatusOptions): Promise<void>;
|
|
33
31
|
}
|
|
@@ -16,8 +16,6 @@ export class WorkerClient {
|
|
|
16
16
|
this.sync = sync;
|
|
17
17
|
this.port = port;
|
|
18
18
|
Comlink.expose(this, this.port);
|
|
19
|
-
}
|
|
20
|
-
async initialize() {
|
|
21
19
|
/**
|
|
22
20
|
* Adds an extra listener which can remove this port
|
|
23
21
|
* from the list of monitored ports.
|
|
@@ -28,15 +26,6 @@ export class WorkerClient {
|
|
|
28
26
|
await this.removePort();
|
|
29
27
|
}
|
|
30
28
|
});
|
|
31
|
-
/**
|
|
32
|
-
* Keep a reference to the resolved port promise.
|
|
33
|
-
* The init timing is difficult to predict due to the async message passing.
|
|
34
|
-
* We only want to use a port if we are know it's been protected from being closed.
|
|
35
|
-
* The lock based close signal will be added asynchronously. We need to use the
|
|
36
|
-
* added port once the lock is configured.
|
|
37
|
-
*/
|
|
38
|
-
this.resolvedPortPromise = this.sync.addPort(this.port);
|
|
39
|
-
this.resolvedPort = await this.resolvedPortPromise;
|
|
40
29
|
}
|
|
41
30
|
async removePort() {
|
|
42
31
|
if (this.resolvedPort) {
|
|
@@ -58,19 +47,9 @@ export class WorkerClient {
|
|
|
58
47
|
* it can consider the connection to be closed.
|
|
59
48
|
*/
|
|
60
49
|
async addLockBasedCloseSignal(name) {
|
|
61
|
-
|
|
62
|
-
|
|
63
|
-
|
|
64
|
-
}
|
|
65
|
-
else {
|
|
66
|
-
const wrappedPort = await this.resolvedPortPromise;
|
|
67
|
-
/**
|
|
68
|
-
* The client registered a navigator lock. We now can guarantee detecting if the client has closed.
|
|
69
|
-
* E.g. before this point: It's possible some ports might have been created and closed before the
|
|
70
|
-
* lock based close signal is added. We should not trust those ports.
|
|
71
|
-
*/
|
|
72
|
-
wrappedPort.isProtectedFromClose = true;
|
|
73
|
-
}
|
|
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
|
|
74
53
|
getNavigatorLocks().request(name, async () => {
|
|
75
54
|
await this.removePort();
|
|
76
55
|
});
|
|
@@ -102,7 +81,4 @@ export class WorkerClient {
|
|
|
102
81
|
disconnect() {
|
|
103
82
|
return this.sync.disconnect();
|
|
104
83
|
}
|
|
105
|
-
async _testUpdateAllStatuses(status) {
|
|
106
|
-
return this.sync._testUpdateAllStatuses(status);
|
|
107
|
-
}
|
|
108
84
|
}
|