@powersync/web 0.0.0-dev-20251127205344 → 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.
Files changed (56) hide show
  1. package/dist/1807036ae51c10ee4d23.wasm +0 -0
  2. package/dist/{10072fe45f0a8fab0a0e.wasm → 307d8ce2280e3bae09d5.wasm} +0 -0
  3. package/dist/{6e435e51534839845554.wasm → cd8b9e8f4c87bf81c169.wasm} +0 -0
  4. package/dist/e797080f5ed0b5324166.wasm +0 -0
  5. package/dist/index.umd.js +336 -177
  6. package/dist/index.umd.js.map +1 -1
  7. package/dist/worker/SharedSyncImplementation.umd.js +310 -143
  8. package/dist/worker/SharedSyncImplementation.umd.js.map +1 -1
  9. package/dist/worker/WASQLiteDB.umd.js +19 -5
  10. package/dist/worker/WASQLiteDB.umd.js.map +1 -1
  11. package/dist/worker/node_modules_journeyapps_wa-sqlite_dist_mc-wa-sqlite-async_mjs.umd.js +2 -2
  12. package/dist/worker/node_modules_journeyapps_wa-sqlite_dist_mc-wa-sqlite-async_mjs.umd.js.map +1 -1
  13. package/dist/worker/node_modules_journeyapps_wa-sqlite_dist_mc-wa-sqlite_mjs.umd.js +2 -2
  14. package/dist/worker/node_modules_journeyapps_wa-sqlite_dist_mc-wa-sqlite_mjs.umd.js.map +1 -1
  15. package/dist/worker/node_modules_journeyapps_wa-sqlite_dist_wa-sqlite-async_mjs.umd.js +2 -2
  16. package/dist/worker/node_modules_journeyapps_wa-sqlite_dist_wa-sqlite-async_mjs.umd.js.map +1 -1
  17. package/dist/worker/node_modules_journeyapps_wa-sqlite_dist_wa-sqlite_mjs.umd.js +2 -2
  18. package/dist/worker/node_modules_journeyapps_wa-sqlite_dist_wa-sqlite_mjs.umd.js.map +1 -1
  19. package/dist/worker/node_modules_journeyapps_wa-sqlite_src_examples_IDBBatchAtomicVFS_js.umd.js +20 -23
  20. package/dist/worker/node_modules_journeyapps_wa-sqlite_src_examples_IDBBatchAtomicVFS_js.umd.js.map +1 -1
  21. package/lib/src/db/PowerSyncDatabase.d.ts +1 -1
  22. package/lib/src/db/PowerSyncDatabase.js +4 -4
  23. package/lib/src/db/adapters/AsyncDatabaseConnection.d.ts +8 -0
  24. package/lib/src/db/adapters/AsyncDatabaseConnection.js +11 -1
  25. package/lib/src/db/adapters/LockedAsyncDatabaseAdapter.d.ts +14 -0
  26. package/lib/src/db/adapters/LockedAsyncDatabaseAdapter.js +61 -8
  27. package/lib/src/db/adapters/WorkerWrappedAsyncDatabaseConnection.d.ts +5 -1
  28. package/lib/src/db/adapters/WorkerWrappedAsyncDatabaseConnection.js +15 -7
  29. package/lib/src/db/sync/SharedWebStreamingSyncImplementation.d.ts +1 -4
  30. package/lib/src/db/sync/SharedWebStreamingSyncImplementation.js +45 -31
  31. package/lib/src/worker/sync/MockSyncService.d.ts +2 -0
  32. package/lib/src/worker/sync/MockSyncService.js +3 -0
  33. package/lib/src/worker/sync/MockSyncServiceTypes.d.ts +101 -0
  34. package/lib/src/worker/sync/MockSyncServiceTypes.js +1 -0
  35. package/lib/src/worker/sync/MockSyncServiceWorker.d.ts +56 -0
  36. package/lib/src/worker/sync/MockSyncServiceWorker.js +369 -0
  37. package/lib/src/worker/sync/SharedSyncImplementation.d.ts +18 -8
  38. package/lib/src/worker/sync/SharedSyncImplementation.js +174 -108
  39. package/lib/src/worker/sync/SharedSyncImplementation.worker.js +1 -1
  40. package/lib/src/worker/sync/WorkerClient.d.ts +4 -5
  41. package/lib/src/worker/sync/WorkerClient.js +5 -7
  42. package/lib/tsconfig.tsbuildinfo +1 -1
  43. package/package.json +3 -3
  44. package/src/db/PowerSyncDatabase.ts +13 -15
  45. package/src/db/adapters/AsyncDatabaseConnection.ts +12 -0
  46. package/src/db/adapters/LockedAsyncDatabaseAdapter.ts +71 -9
  47. package/src/db/adapters/WorkerWrappedAsyncDatabaseConnection.ts +19 -6
  48. package/src/db/sync/SharedWebStreamingSyncImplementation.ts +56 -40
  49. package/src/worker/sync/MockSyncService.ts +3 -0
  50. package/src/worker/sync/MockSyncServiceTypes.ts +71 -0
  51. package/src/worker/sync/MockSyncServiceWorker.ts +406 -0
  52. package/src/worker/sync/SharedSyncImplementation.ts +201 -120
  53. package/src/worker/sync/SharedSyncImplementation.worker.ts +1 -1
  54. package/src/worker/sync/WorkerClient.ts +6 -11
  55. package/dist/a730f7ca717b02234beb.wasm +0 -0
  56. package/dist/aa2f408d64445fed090e.wasm +0 -0
@@ -2,14 +2,14 @@ import {
2
2
  AbortOperation,
3
3
  BaseObserver,
4
4
  ConnectionManager,
5
- createLogger,
6
5
  DBAdapter,
7
6
  PowerSyncBackendConnector,
8
7
  SqliteBucketStorage,
9
8
  SubscribedStream,
10
9
  SyncStatus,
11
- type ILogger,
10
+ createLogger,
12
11
  type ILogLevel,
12
+ type ILogger,
13
13
  type PowerSyncConnectionOptions,
14
14
  type StreamingSyncImplementation,
15
15
  type StreamingSyncImplementationListener,
@@ -25,8 +25,8 @@ import {
25
25
 
26
26
  import { OpenAsyncDatabaseConnection } from '../../db/adapters/AsyncDatabaseConnection';
27
27
  import { LockedAsyncDatabaseAdapter } from '../../db/adapters/LockedAsyncDatabaseAdapter';
28
- import { ResolvedWebSQLOpenOptions } from '../../db/adapters/web-sql-flags';
29
28
  import { WorkerWrappedAsyncDatabaseConnection } from '../../db/adapters/WorkerWrappedAsyncDatabaseConnection';
29
+ import { ResolvedWebSQLOpenOptions } from '../../db/adapters/web-sql-flags';
30
30
  import { AbstractSharedSyncClientProvider } from './AbstractSharedSyncClientProvider';
31
31
  import { BroadcastLogger } from './BroadcastLogger';
32
32
 
@@ -76,6 +76,7 @@ export type WrappedSyncPort = {
76
76
  db?: DBAdapter;
77
77
  currentSubscriptions: SubscribedStream[];
78
78
  closeListeners: (() => void | Promise<void>)[];
79
+ isClosing: boolean;
79
80
  };
80
81
 
81
82
  /**
@@ -106,7 +107,6 @@ export class SharedSyncImplementation extends BaseObserver<SharedSyncImplementat
106
107
  protected fetchCredentialsController?: RemoteOperationAbortController;
107
108
  protected uploadDataController?: RemoteOperationAbortController;
108
109
 
109
- protected dbAdapter: DBAdapter | null;
110
110
  protected syncParams: SharedSyncInitOptions | null;
111
111
  protected logger: ILogger;
112
112
  protected lastConnectOptions: PowerSyncConnectionOptions | undefined;
@@ -116,11 +116,11 @@ export class SharedSyncImplementation extends BaseObserver<SharedSyncImplementat
116
116
  protected connectionManager: ConnectionManager;
117
117
  syncStatus: SyncStatus;
118
118
  broadCastLogger: ILogger;
119
+ protected distributedDB: DBAdapter | null;
119
120
 
120
121
  constructor() {
121
122
  super();
122
123
  this.ports = [];
123
- this.dbAdapter = null;
124
124
  this.syncParams = null;
125
125
  this.logger = createLogger('shared-sync');
126
126
  this.lastConnectOptions = undefined;
@@ -135,29 +135,27 @@ export class SharedSyncImplementation extends BaseObserver<SharedSyncImplementat
135
135
  });
136
136
  });
137
137
 
138
+ // Should be configured once we get params
139
+ this.distributedDB = null;
140
+
138
141
  this.syncStatus = new SyncStatus({});
139
142
  this.broadCastLogger = new BroadcastLogger(this.ports);
140
143
 
141
144
  this.connectionManager = new ConnectionManager({
142
145
  createSyncImplementation: async () => {
143
- return this.portMutex.runExclusive(async () => {
144
- await this.waitForReady();
145
- if (!this.dbAdapter) {
146
- await this.openInternalDB();
147
- }
148
-
149
- const sync = this.generateStreamingImplementation();
150
- const onDispose = sync.registerListener({
151
- statusChanged: (status) => {
152
- this.updateAllStatuses(status.toJSON());
153
- }
154
- });
146
+ await this.waitForReady();
155
147
 
156
- return {
157
- sync,
158
- onDispose
159
- };
148
+ const sync = this.generateStreamingImplementation();
149
+ const onDispose = sync.registerListener({
150
+ statusChanged: (status) => {
151
+ this.updateAllStatuses(status.toJSON());
152
+ }
160
153
  });
154
+
155
+ return {
156
+ sync,
157
+ onDispose
158
+ };
161
159
  },
162
160
  logger: this.logger
163
161
  });
@@ -171,6 +169,31 @@ export class SharedSyncImplementation extends BaseObserver<SharedSyncImplementat
171
169
  return this.connectionManager.syncStreamImplementation?.isConnected ?? false;
172
170
  }
173
171
 
172
+ /**
173
+ * Gets the last client port which we know is safe from unexpected closes.
174
+ */
175
+ protected async getLastWrappedPort(): Promise<WrappedSyncPort | undefined> {
176
+ // Find the last port which is not closing
177
+ return await this.portMutex.runExclusive(() => {
178
+ for (let i = this.ports.length - 1; i >= 0; i--) {
179
+ if (!this.ports[i].isClosing) {
180
+ return this.ports[i];
181
+ }
182
+ }
183
+ return;
184
+ });
185
+ }
186
+
187
+ /**
188
+ * In some very rare cases a specific tab might not respond to requests.
189
+ * This returns a random port which is not closing.
190
+ */
191
+ protected async getRandomWrappedPort(): Promise<WrappedSyncPort | undefined> {
192
+ return await this.portMutex.runExclusive(() => {
193
+ return this.ports[Math.floor(Math.random() * this.ports.length)];
194
+ });
195
+ }
196
+
174
197
  async waitForStatus(status: SyncStatusOptions): Promise<void> {
175
198
  return this.withSyncImplementation(async (sync) => {
176
199
  return sync.waitForStatus(status);
@@ -217,33 +240,51 @@ export class SharedSyncImplementation extends BaseObserver<SharedSyncImplementat
217
240
  async setParams(params: SharedSyncInitOptions) {
218
241
  await this.portMutex.runExclusive(async () => {
219
242
  this.collectActiveSubscriptions();
220
- if (this.syncParams) {
221
- // Cannot modify already existing sync implementation params
222
- // But we can ask for a DB adapter, if required, at this point.
223
-
224
- if (!this.dbAdapter) {
225
- await this.openInternalDB();
226
- }
227
- return;
228
- }
243
+ });
229
244
 
230
- // First time setting params
231
- this.syncParams = params;
232
- if (params.streamOptions?.flags?.broadcastLogs) {
233
- this.logger = this.broadCastLogger;
234
- }
245
+ if (this.syncParams) {
246
+ // Cannot modify already existing sync implementation params
247
+ return;
248
+ }
235
249
 
236
- self.onerror = (event) => {
237
- // Share any uncaught events on the broadcast logger
238
- this.logger.error('Uncaught exception in PowerSync shared sync worker', event);
239
- };
250
+ // First time setting params
251
+ this.syncParams = params;
252
+ if (params.streamOptions?.flags?.broadcastLogs) {
253
+ this.logger = this.broadCastLogger;
254
+ }
240
255
 
241
- if (!this.dbAdapter) {
242
- await this.openInternalDB();
256
+ const lockedAdapter = new LockedAsyncDatabaseAdapter({
257
+ name: params.dbParams.dbFilename,
258
+ openConnection: async () => {
259
+ // Gets a connection from the clients when a new connection is requested.
260
+ const db = await this.openInternalDB();
261
+ db.registerListener({
262
+ closing: () => {
263
+ lockedAdapter.reOpenInternalDB();
264
+ }
265
+ });
266
+ return db;
267
+ },
268
+ logger: this.logger,
269
+ reOpenOnConnectionClosed: true
270
+ });
271
+ this.distributedDB = lockedAdapter;
272
+ await lockedAdapter.init();
273
+
274
+ lockedAdapter.registerListener({
275
+ databaseReOpened: () => {
276
+ // We may have missed some table updates while the database was closed.
277
+ // We can poke the crud in case we missed any updates.
278
+ this.connectionManager.syncStreamImplementation?.triggerCrudUpload();
243
279
  }
244
-
245
- this.iterateListeners((l) => l.initialized?.());
246
280
  });
281
+
282
+ self.onerror = (event) => {
283
+ // Share any uncaught events on the broadcast logger
284
+ this.logger.error('Uncaught exception in PowerSync shared sync worker', event);
285
+ };
286
+
287
+ this.iterateListeners((l) => l.initialized?.());
247
288
  }
248
289
 
249
290
  async dispose() {
@@ -276,7 +317,8 @@ export class SharedSyncImplementation extends BaseObserver<SharedSyncImplementat
276
317
  port,
277
318
  clientProvider: Comlink.wrap<AbstractSharedSyncClientProvider>(port),
278
319
  currentSubscriptions: [],
279
- closeListeners: []
320
+ closeListeners: [],
321
+ isClosing: false
280
322
  } satisfies WrappedSyncPort;
281
323
  this.ports.push(portProvider);
282
324
 
@@ -295,14 +337,17 @@ export class SharedSyncImplementation extends BaseObserver<SharedSyncImplementat
295
337
  * clients.
296
338
  */
297
339
  async removePort(port: WrappedSyncPort) {
340
+ // Ports might be removed faster than we can process them.
341
+ port.isClosing = true;
342
+
298
343
  // Remove the port within a mutex context.
299
344
  // Warns if the port is not found. This should not happen in practice.
300
345
  // We return early if the port is not found.
301
- const { trackedPort, shouldReconnect } = await this.portMutex.runExclusive(async () => {
346
+ return await this.portMutex.runExclusive(async () => {
302
347
  const index = this.ports.findIndex((p) => p == port);
303
348
  if (index < 0) {
304
349
  this.logger.warn(`Could not remove port ${port} since it is not present in active ports.`);
305
- return {};
350
+ return () => {};
306
351
  }
307
352
 
308
353
  const trackedPort = this.ports[index];
@@ -321,47 +366,15 @@ export class SharedSyncImplementation extends BaseObserver<SharedSyncImplementat
321
366
  }
322
367
  });
323
368
 
324
- const shouldReconnect = !!this.connectionManager.syncStreamImplementation && this.ports.length > 0;
325
-
326
- /**
327
- * If the current database adapter is the one that is being closed, we need to disconnect from the backend.
328
- * We can disconnect in the portMutex lock. This ensures the disconnect is not affected by potential other
329
- * connect operations coming from other tabs.
330
- */
331
- if (this.dbAdapter && this.dbAdapter == trackedPort.db) {
332
- this.logger.debug(`Disconnecting due to closed database: should reconnect: ${shouldReconnect}`);
333
- this.dbAdapter = null;
334
- // Unconditionally close the connection because the database it's writing to has just been closed.
335
- // The connection has been closed previously, this might throw. We should be able to ignore it.
336
- await this.connectionManager
337
- .disconnect()
338
- .catch((ex) => this.logger.warn('Error while disconnecting. Will attempt to reconnect.', ex));
369
+ // Close the worker wrapped database connection, we can't accurately rely on this connection
370
+ for (const closeListener of trackedPort.closeListeners) {
371
+ await closeListener();
339
372
  }
340
373
 
341
- return {
342
- shouldReconnect,
343
- trackedPort
344
- };
345
- });
346
-
347
- if (!trackedPort) {
348
- // We could not find the port to remove
349
- return () => {};
350
- }
351
-
352
- for (const closeListener of trackedPort.closeListeners) {
353
- await closeListener();
354
- }
355
-
356
- if (shouldReconnect) {
357
- await this.connectionManager.connect(CONNECTOR_PLACEHOLDER, this.lastConnectOptions ?? {});
358
- }
359
-
360
- // Re-index subscriptions, the subscriptions of the removed port would no longer be considered.
361
- this.collectActiveSubscriptions();
374
+ this.collectActiveSubscriptions();
362
375
 
363
- // Release proxy
364
- return () => trackedPort.clientProvider[Comlink.releaseProxy]();
376
+ return () => trackedPort.clientProvider[Comlink.releaseProxy]();
377
+ });
365
378
  }
366
379
 
367
380
  triggerCrudUpload() {
@@ -406,11 +419,14 @@ export class SharedSyncImplementation extends BaseObserver<SharedSyncImplementat
406
419
  const syncParams = this.syncParams!;
407
420
  // Create a new StreamingSyncImplementation for each connect call. This is usually done is all SDKs.
408
421
  return new WebStreamingSyncImplementation({
409
- adapter: new SqliteBucketStorage(this.dbAdapter!, this.logger),
422
+ adapter: new SqliteBucketStorage(this.distributedDB!, this.logger),
410
423
  remote: new WebRemote(
411
424
  {
412
425
  invalidateCredentials: async () => {
413
- const lastPort = this.ports[this.ports.length - 1];
426
+ const lastPort = await this.getLastWrappedPort();
427
+ if (!lastPort) {
428
+ throw new Error('No client port found to invalidate credentials');
429
+ }
414
430
  try {
415
431
  this.logger.log('calling the last port client provider to invalidate credentials');
416
432
  lastPort.clientProvider.invalidateCredentials();
@@ -419,7 +435,10 @@ export class SharedSyncImplementation extends BaseObserver<SharedSyncImplementat
419
435
  }
420
436
  },
421
437
  fetchCredentials: async () => {
422
- const lastPort = this.ports[this.ports.length - 1];
438
+ const lastPort = await this.getLastWrappedPort();
439
+ if (!lastPort) {
440
+ throw new Error('No client port found to fetch credentials');
441
+ }
423
442
  return new Promise(async (resolve, reject) => {
424
443
  const abortController = new AbortController();
425
444
  this.fetchCredentialsController = {
@@ -442,7 +461,10 @@ export class SharedSyncImplementation extends BaseObserver<SharedSyncImplementat
442
461
  this.logger
443
462
  ),
444
463
  uploadCrud: async () => {
445
- const lastPort = this.ports[this.ports.length - 1];
464
+ const lastPort = await this.getLastWrappedPort();
465
+ if (!lastPort) {
466
+ throw new Error('No client port found to upload crud');
467
+ }
446
468
 
447
469
  return new Promise(async (resolve, reject) => {
448
470
  const abortController = new AbortController();
@@ -469,19 +491,63 @@ export class SharedSyncImplementation extends BaseObserver<SharedSyncImplementat
469
491
  });
470
492
  }
471
493
 
494
+ /**
495
+ * Opens a worker wrapped database connection. Using the last connected client port.
496
+ */
472
497
  protected async openInternalDB() {
473
- const lastClient = this.ports[this.ports.length - 1];
474
- if (!lastClient) {
475
- // Should not really happen in practice
476
- throw new Error(`Could not open DB connection since no client is connected.`);
477
- }
478
- const workerPort = await lastClient.clientProvider.getDBWorkerPort();
479
- const remote = Comlink.wrap<OpenAsyncDatabaseConnection>(workerPort);
480
- const identifier = this.syncParams!.dbParams.dbFilename;
481
- const db = await remote(this.syncParams!.dbParams);
482
- const locked = new LockedAsyncDatabaseAdapter({
483
- name: identifier,
484
- openConnection: async () => {
498
+ while (true) {
499
+ try {
500
+ const client = await this.getRandomWrappedPort();
501
+ if (!client) {
502
+ // Should not really happen in practice
503
+ throw new Error(`Could not open DB connection since no client is connected.`);
504
+ }
505
+
506
+ // Fail-safe timeout for opening a database connection.
507
+ const timeout = setTimeout(() => {
508
+ abortController.abort();
509
+ }, 10_000);
510
+
511
+ /**
512
+ * Handle cases where the client might close while opening a connection.
513
+ */
514
+ const abortController = new AbortController();
515
+ const closeListener = () => {
516
+ abortController.abort();
517
+ };
518
+
519
+ const removeCloseListener = () => {
520
+ const index = client.closeListeners.indexOf(closeListener);
521
+ if (index >= 0) {
522
+ client.closeListeners.splice(index, 1);
523
+ }
524
+ };
525
+
526
+ client.closeListeners.push(closeListener);
527
+
528
+ const workerPort = await withAbort(() => client.clientProvider.getDBWorkerPort(), abortController.signal).catch(
529
+ (ex) => {
530
+ removeCloseListener();
531
+ throw ex;
532
+ }
533
+ );
534
+
535
+ const remote = Comlink.wrap<OpenAsyncDatabaseConnection>(workerPort);
536
+ const identifier = this.syncParams!.dbParams.dbFilename;
537
+
538
+ /**
539
+ * The open could fail if the tab is closed while we're busy opening the database.
540
+ * This operation is typically executed inside an exclusive portMutex lock.
541
+ * We typically execute the closeListeners using the portMutex in a different context.
542
+ * We can't rely on the closeListeners to abort the operation if the tab is closed.
543
+ */
544
+ const db = await withAbort(() => remote(this.syncParams!.dbParams), abortController.signal).finally(() => {
545
+ // We can remove the close listener here since we no longer need it past this point.
546
+ removeCloseListener();
547
+ });
548
+
549
+ clearTimeout(timeout);
550
+
485
551
  const wrapped = new WorkerWrappedAsyncDatabaseConnection({
486
552
  remote,
487
553
  baseConnection: db,
@@ -490,23 +556,23 @@ export class SharedSyncImplementation extends BaseObserver<SharedSyncImplementat
490
556
  // that and ensure pending requests are aborted when the tab is closed.
491
557
  remoteCanCloseUnexpectedly: true
492
558
  });
493
- lastClient.closeListeners.push(async () => {
559
+ client.closeListeners.push(async () => {
494
560
  this.logger.info('Aborting open connection because associated tab closed.');
495
561
  /**
496
562
  * Don't await this close operation. It might never resolve if the tab is closed.
497
- * We run the close operation first, before marking the remote as closed. This gives the database some chance
498
- * to close the connection.
563
+ * We mark the remote as closed first, this will reject any pending requests.
564
+ * We then call close. The close operation is configured to fire-and-forget, the main promise will reject immediately.
499
565
  */
500
- wrapped.close().catch((ex) => this.logger.warn('error closing database connection', ex));
501
566
  wrapped.markRemoteClosed();
567
+ wrapped.close().catch((ex) => this.logger.warn('error closing database connection', ex));
502
568
  });
503
569
 
504
570
  return wrapped;
505
- },
506
- logger: this.logger
507
- });
508
- await locked.init();
509
- this.dbAdapter = lastClient.db = locked;
571
+ } catch (ex) {
572
+ this.logger.warn('Error opening internal DB', ex);
573
+ await new Promise((resolve) => setTimeout(resolve, 1000));
574
+ }
575
+ }
510
576
  }
511
577
 
512
578
  /**
@@ -517,17 +583,32 @@ export class SharedSyncImplementation extends BaseObserver<SharedSyncImplementat
517
583
  this.syncStatus = new SyncStatus(status);
518
584
  this.ports.forEach((p) => p.clientProvider.statusChanged(status));
519
585
  }
586
+ }
520
587
 
521
- /**
522
- * A function only used for unit tests which updates the internal
523
- * sync stream client and all tab client's sync status
524
- */
525
- async _testUpdateAllStatuses(status: SyncStatusOptions) {
526
- if (!this.connectionManager.syncStreamImplementation) {
527
- throw new Error('Cannot update status without a sync stream implementation');
588
+ /**
589
+ * Runs the action with an abort controller.
590
+ */
591
+ function withAbort<T>(action: () => Promise<T>, signal: AbortSignal): Promise<T> {
592
+ return new Promise((resolve, reject) => {
593
+ if (signal.aborted) {
594
+ reject(new AbortOperation('Operation aborted by abort controller'));
595
+ return;
528
596
  }
529
- // Only assigning, don't call listeners for this test
530
- this.connectionManager.syncStreamImplementation!.syncStatus = new SyncStatus(status);
531
- this.updateAllStatuses(status);
532
- }
597
+
598
+ function handleAbort() {
599
+ signal.removeEventListener('abort', handleAbort);
600
+ reject(new AbortOperation('Operation aborted by abort controller'));
601
+ }
602
+
603
+ signal.addEventListener('abort', handleAbort, { once: true });
604
+
605
+ function completePromise(action: () => void) {
606
+ signal.removeEventListener('abort', handleAbort);
607
+ action();
608
+ }
609
+
610
+ action()
611
+ .then((data) => completePromise(() => resolve(data)))
612
+ .catch((e) => completePromise(() => reject(e)));
613
+ });
533
614
  }
@@ -10,5 +10,5 @@ const sharedSyncImplementation = new SharedSyncImplementation();
10
10
 
11
11
  _self.onconnect = async function (event: MessageEvent<string>) {
12
12
  const port = event.ports[0];
13
- await new WorkerClient(sharedSyncImplementation, port).initialize();
13
+ new WorkerClient(sharedSyncImplementation, port);
14
14
  };
@@ -1,4 +1,4 @@
1
- import { ILogLevel, PowerSyncConnectionOptions, SubscribedStream, SyncStatusOptions } from '@powersync/common';
1
+ import { ILogLevel, PowerSyncConnectionOptions, SubscribedStream } from '@powersync/common';
2
2
  import * as Comlink from 'comlink';
3
3
  import { getNavigatorLocks } from '../../shared/navigator';
4
4
  import {
@@ -17,15 +17,13 @@ import {
17
17
  */
18
18
  export class WorkerClient {
19
19
  private resolvedPort: WrappedSyncPort | null = null;
20
+ protected resolvedPortPromise: Promise<WrappedSyncPort> | null = null;
20
21
 
21
22
  constructor(
22
23
  private readonly sync: SharedSyncImplementation,
23
24
  private readonly port: MessagePort
24
25
  ) {
25
26
  Comlink.expose(this, this.port);
26
- }
27
-
28
- async initialize() {
29
27
  /**
30
28
  * Adds an extra listener which can remove this port
31
29
  * from the list of monitored ports.
@@ -36,8 +34,6 @@ export class WorkerClient {
36
34
  await this.removePort();
37
35
  }
38
36
  });
39
-
40
- this.resolvedPort = await this.sync.addPort(this.port);
41
37
  }
42
38
 
43
39
  private async removePort() {
@@ -60,7 +56,10 @@ export class WorkerClient {
60
56
  * When the client tab is closed, its lock will be returned. So when the shared worker attempts to acquire the lock,
61
57
  * it can consider the connection to be closed.
62
58
  */
63
- addLockBasedCloseSignal(name: string) {
59
+ async addLockBasedCloseSignal(name: string) {
60
+ // Only add the port once the lock has been obtained on the client.
61
+ this.resolvedPort = await this.sync.addPort(this.port);
62
+ // Don't await this lock request
64
63
  getNavigatorLocks().request(name, async () => {
65
64
  await this.removePort();
66
65
  });
@@ -100,8 +99,4 @@ export class WorkerClient {
100
99
  disconnect() {
101
100
  return this.sync.disconnect();
102
101
  }
103
-
104
- async _testUpdateAllStatuses(status: SyncStatusOptions) {
105
- return this.sync._testUpdateAllStatuses(status);
106
- }
107
102
  }
Binary file
Binary file