@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.
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 +137 -104
  6. package/dist/index.umd.js.map +1 -1
  7. package/dist/worker/SharedSyncImplementation.umd.js +137 -110
  8. package/dist/worker/SharedSyncImplementation.umd.js.map +1 -1
  9. package/dist/worker/WASQLiteDB.umd.js +15 -1
  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 +5 -0
  24. package/lib/src/db/adapters/AsyncDatabaseConnection.js +5 -0
  25. package/lib/src/db/adapters/LockedAsyncDatabaseAdapter.d.ts +6 -1
  26. package/lib/src/db/adapters/LockedAsyncDatabaseAdapter.js +20 -5
  27. package/lib/src/db/adapters/WorkerWrappedAsyncDatabaseConnection.d.ts +5 -1
  28. package/lib/src/db/adapters/WorkerWrappedAsyncDatabaseConnection.js +12 -5
  29. package/lib/src/db/sync/SharedWebStreamingSyncImplementation.d.ts +0 -4
  30. package/lib/src/db/sync/SharedWebStreamingSyncImplementation.js +3 -8
  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 +6 -11
  38. package/lib/src/worker/sync/SharedSyncImplementation.js +73 -64
  39. package/lib/src/worker/sync/SharedSyncImplementation.worker.js +1 -1
  40. package/lib/src/worker/sync/WorkerClient.d.ts +1 -3
  41. package/lib/src/worker/sync/WorkerClient.js +3 -27
  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 +5 -0
  46. package/src/db/adapters/LockedAsyncDatabaseAdapter.ts +22 -5
  47. package/src/db/adapters/WorkerWrappedAsyncDatabaseConnection.ts +16 -4
  48. package/src/db/sync/SharedWebStreamingSyncImplementation.ts +5 -11
  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 +85 -78
  53. package/src/worker/sync/SharedSyncImplementation.worker.ts +1 -1
  54. package/src/worker/sync/WorkerClient.ts +4 -30
  55. package/dist/a730f7ca717b02234beb.wasm +0 -0
  56. package/dist/aa2f408d64445fed090e.wasm +0 -0
@@ -76,10 +76,6 @@ export type WrappedSyncPort = {
76
76
  db?: DBAdapter;
77
77
  currentSubscriptions: SubscribedStream[];
78
78
  closeListeners: (() => void | Promise<void>)[];
79
- /**
80
- * If we can use Navigator locks to detect if the client has closed.
81
- */
82
- isProtectedFromClose: boolean;
83
79
  isClosing: boolean;
84
80
  };
85
81
 
@@ -176,14 +172,26 @@ export class SharedSyncImplementation extends BaseObserver<SharedSyncImplementat
176
172
  /**
177
173
  * Gets the last client port which we know is safe from unexpected closes.
178
174
  */
179
- protected get lastWrappedPort(): WrappedSyncPort | undefined {
180
- // Find the last port which is protected from close
181
- for (let i = this.ports.length - 1; i >= 0; i--) {
182
- if (this.ports[i].isProtectedFromClose && !this.ports[i].isClosing) {
183
- return this.ports[i];
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
+ }
184
182
  }
185
- }
186
- return;
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
+ });
187
195
  }
188
196
 
189
197
  async waitForStatus(status: SyncStatusOptions): Promise<void> {
@@ -232,44 +240,51 @@ export class SharedSyncImplementation extends BaseObserver<SharedSyncImplementat
232
240
  async setParams(params: SharedSyncInitOptions) {
233
241
  await this.portMutex.runExclusive(async () => {
234
242
  this.collectActiveSubscriptions();
235
- if (this.syncParams) {
236
- // Cannot modify already existing sync implementation params
237
- return;
238
- }
239
-
240
- // First time setting params
241
- this.syncParams = params;
242
- if (params.streamOptions?.flags?.broadcastLogs) {
243
- this.logger = this.broadCastLogger;
244
- }
243
+ });
245
244
 
246
- const lockedAdapter = new LockedAsyncDatabaseAdapter({
247
- name: params.dbParams.dbFilename,
248
- openConnection: async () => {
249
- // Gets a connection from the clients when a new connection is requested.
250
- return await this.openInternalDB();
251
- },
252
- logger: this.logger,
253
- reOpenOnConnectionClosed: true
254
- });
255
- this.distributedDB = lockedAdapter;
256
- await lockedAdapter.init();
257
-
258
- lockedAdapter.registerListener({
259
- databaseReOpened: () => {
260
- // We may have missed some table updates while the database was closed.
261
- // We can poke the crud in case we missed any updates.
262
- this.connectionManager.syncStreamImplementation?.triggerCrudUpload();
263
- }
264
- });
245
+ if (this.syncParams) {
246
+ // Cannot modify already existing sync implementation params
247
+ return;
248
+ }
265
249
 
266
- self.onerror = (event) => {
267
- // Share any uncaught events on the broadcast logger
268
- this.logger.error('Uncaught exception in PowerSync shared sync worker', event);
269
- };
250
+ // First time setting params
251
+ this.syncParams = params;
252
+ if (params.streamOptions?.flags?.broadcastLogs) {
253
+ this.logger = this.broadCastLogger;
254
+ }
270
255
 
271
- this.iterateListeners((l) => l.initialized?.());
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
272
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();
279
+ }
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?.());
273
288
  }
274
289
 
275
290
  async dispose() {
@@ -303,7 +318,6 @@ export class SharedSyncImplementation extends BaseObserver<SharedSyncImplementat
303
318
  clientProvider: Comlink.wrap<AbstractSharedSyncClientProvider>(port),
304
319
  currentSubscriptions: [],
305
320
  closeListeners: [],
306
- isProtectedFromClose: false,
307
321
  isClosing: false
308
322
  } satisfies WrappedSyncPort;
309
323
  this.ports.push(portProvider);
@@ -409,7 +423,7 @@ export class SharedSyncImplementation extends BaseObserver<SharedSyncImplementat
409
423
  remote: new WebRemote(
410
424
  {
411
425
  invalidateCredentials: async () => {
412
- const lastPort = this.lastWrappedPort;
426
+ const lastPort = await this.getLastWrappedPort();
413
427
  if (!lastPort) {
414
428
  throw new Error('No client port found to invalidate credentials');
415
429
  }
@@ -421,7 +435,7 @@ export class SharedSyncImplementation extends BaseObserver<SharedSyncImplementat
421
435
  }
422
436
  },
423
437
  fetchCredentials: async () => {
424
- const lastPort = this.lastWrappedPort;
438
+ const lastPort = await this.getLastWrappedPort();
425
439
  if (!lastPort) {
426
440
  throw new Error('No client port found to fetch credentials');
427
441
  }
@@ -447,7 +461,7 @@ export class SharedSyncImplementation extends BaseObserver<SharedSyncImplementat
447
461
  this.logger
448
462
  ),
449
463
  uploadCrud: async () => {
450
- const lastPort = this.lastWrappedPort;
464
+ const lastPort = await this.getLastWrappedPort();
451
465
  if (!lastPort) {
452
466
  throw new Error('No client port found to upload crud');
453
467
  }
@@ -483,12 +497,17 @@ export class SharedSyncImplementation extends BaseObserver<SharedSyncImplementat
483
497
  protected async openInternalDB() {
484
498
  while (true) {
485
499
  try {
486
- const lastClient = this.lastWrappedPort;
487
- if (!lastClient) {
500
+ const client = await this.getRandomWrappedPort();
501
+ if (!client) {
488
502
  // Should not really happen in practice
489
503
  throw new Error(`Could not open DB connection since no client is connected.`);
490
504
  }
491
505
 
506
+ // Fail-safe timeout for opening a database connection.
507
+ const timeout = setTimeout(() => {
508
+ abortController.abort();
509
+ }, 10_000);
510
+
492
511
  /**
493
512
  * Handle cases where the client might close while opening a connection.
494
513
  */
@@ -498,21 +517,20 @@ export class SharedSyncImplementation extends BaseObserver<SharedSyncImplementat
498
517
  };
499
518
 
500
519
  const removeCloseListener = () => {
501
- const index = lastClient.closeListeners.indexOf(closeListener);
520
+ const index = client.closeListeners.indexOf(closeListener);
502
521
  if (index >= 0) {
503
- lastClient.closeListeners.splice(index, 1);
522
+ client.closeListeners.splice(index, 1);
504
523
  }
505
524
  };
506
525
 
507
- lastClient.closeListeners.push(closeListener);
526
+ client.closeListeners.push(closeListener);
508
527
 
509
- const workerPort = await withAbort(
510
- () => lastClient.clientProvider.getDBWorkerPort(),
511
- abortController.signal
512
- ).catch((ex) => {
513
- removeCloseListener();
514
- throw ex;
515
- });
528
+ const workerPort = await withAbort(() => client.clientProvider.getDBWorkerPort(), abortController.signal).catch(
529
+ (ex) => {
530
+ removeCloseListener();
531
+ throw ex;
532
+ }
533
+ );
516
534
 
517
535
  const remote = Comlink.wrap<OpenAsyncDatabaseConnection>(workerPort);
518
536
  const identifier = this.syncParams!.dbParams.dbFilename;
@@ -528,6 +546,8 @@ export class SharedSyncImplementation extends BaseObserver<SharedSyncImplementat
528
546
  removeCloseListener();
529
547
  });
530
548
 
549
+ clearTimeout(timeout);
550
+
531
551
  const wrapped = new WorkerWrappedAsyncDatabaseConnection({
532
552
  remote,
533
553
  baseConnection: db,
@@ -536,15 +556,15 @@ export class SharedSyncImplementation extends BaseObserver<SharedSyncImplementat
536
556
  // that and ensure pending requests are aborted when the tab is closed.
537
557
  remoteCanCloseUnexpectedly: true
538
558
  });
539
- lastClient.closeListeners.push(async () => {
559
+ client.closeListeners.push(async () => {
540
560
  this.logger.info('Aborting open connection because associated tab closed.');
541
561
  /**
542
562
  * Don't await this close operation. It might never resolve if the tab is closed.
543
- * We run the close operation first, before marking the remote as closed. This gives the database some chance
544
- * 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.
545
565
  */
546
- wrapped.close().catch((ex) => this.logger.warn('error closing database connection', ex));
547
566
  wrapped.markRemoteClosed();
567
+ wrapped.close().catch((ex) => this.logger.warn('error closing database connection', ex));
548
568
  });
549
569
 
550
570
  return wrapped;
@@ -563,19 +583,6 @@ export class SharedSyncImplementation extends BaseObserver<SharedSyncImplementat
563
583
  this.syncStatus = new SyncStatus(status);
564
584
  this.ports.forEach((p) => p.clientProvider.statusChanged(status));
565
585
  }
566
-
567
- /**
568
- * A function only used for unit tests which updates the internal
569
- * sync stream client and all tab client's sync status
570
- */
571
- async _testUpdateAllStatuses(status: SyncStatusOptions) {
572
- if (!this.connectionManager.syncStreamImplementation) {
573
- throw new Error('Cannot update status without a sync stream implementation');
574
- }
575
- // Only assigning, don't call listeners for this test
576
- this.connectionManager.syncStreamImplementation!.syncStatus = new SyncStatus(status);
577
- this.updateAllStatuses(status);
578
- }
579
586
  }
580
587
 
581
588
  /**
@@ -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 {
@@ -24,9 +24,6 @@ export class WorkerClient {
24
24
  private readonly port: MessagePort
25
25
  ) {
26
26
  Comlink.expose(this, this.port);
27
- }
28
-
29
- async initialize() {
30
27
  /**
31
28
  * Adds an extra listener which can remove this port
32
29
  * from the list of monitored ports.
@@ -37,16 +34,6 @@ export class WorkerClient {
37
34
  await this.removePort();
38
35
  }
39
36
  });
40
-
41
- /**
42
- * Keep a reference to the resolved port promise.
43
- * The init timing is difficult to predict due to the async message passing.
44
- * We only want to use a port if we are know it's been protected from being closed.
45
- * The lock based close signal will be added asynchronously. We need to use the
46
- * added port once the lock is configured.
47
- */
48
- this.resolvedPortPromise = this.sync.addPort(this.port);
49
- this.resolvedPort = await this.resolvedPortPromise;
50
37
  }
51
38
 
52
39
  private async removePort() {
@@ -70,18 +57,9 @@ export class WorkerClient {
70
57
  * it can consider the connection to be closed.
71
58
  */
72
59
  async addLockBasedCloseSignal(name: string) {
73
- if (!this.resolvedPortPromise) {
74
- // The init logic above is actually synchronous, so this should not happen.
75
- this.sync.broadCastLogger.warn('addLockBasedCloseSignal called before port promise registered');
76
- } else {
77
- const wrappedPort = await this.resolvedPortPromise;
78
- /**
79
- * The client registered a navigator lock. We now can guarantee detecting if the client has closed.
80
- * E.g. before this point: It's possible some ports might have been created and closed before the
81
- * lock based close signal is added. We should not trust those ports.
82
- */
83
- wrappedPort.isProtectedFromClose = true;
84
- }
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
85
63
  getNavigatorLocks().request(name, async () => {
86
64
  await this.removePort();
87
65
  });
@@ -121,8 +99,4 @@ export class WorkerClient {
121
99
  disconnect() {
122
100
  return this.sync.disconnect();
123
101
  }
124
-
125
- async _testUpdateAllStatuses(status: SyncStatusOptions) {
126
- return this.sync._testUpdateAllStatuses(status);
127
- }
128
102
  }
Binary file
Binary file